001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.audio; 034 035import java.nio.ByteBuffer; 036import java.nio.ByteOrder; 037 038import org.openimaj.audio.samples.SampleBuffer; 039import org.openimaj.audio.samples.SampleBufferFactory; 040import org.openimaj.audio.timecode.AudioTimecode; 041 042/** 043 * Represents a chunk of an audio file and stores the raw audio data. 044 * The data is unnormalised - that is, it is stored in this class in its 045 * original format in the form of a byte array. This is for speed during 046 * audio playback. 047 * 048 * If you require normalised data (data as an integer array for example) 049 * use the method {@link #getSamplesAsByteBuffer()} and use the 050 * {@link ByteBuffer}'s methods asXXXBuffer (e.g. ByteBuffer#asShortBuffer) 051 * to get the samples in a normalised form. 052 * 053 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 054 * @created 8 Jun 2011 055 * 056 */ 057public class SampleChunk extends Audio 058{ 059 /** The samples in the chunk */ 060 private byte[] samples = new byte[1]; 061 062 /** The timecode of the start of the sample chunk */ 063 private AudioTimecode startTimecode = new AudioTimecode(0); 064 065 /** 066 * Create a new SampleChunk buffer with the given 067 * audio format, but do not initialise the samples. 068 * 069 * @param af The audio format of the samples 070 */ 071 public SampleChunk( final AudioFormat af ) 072 { 073 this( new byte[1], af ); 074 } 075 076 /** 077 * Create a new sample chunk using the given samples 078 * and the given audio format. 079 * 080 * @param samples The samples to initialise with 081 * @param af The audio format of the samples 082 */ 083 public SampleChunk( final byte[] samples, final AudioFormat af ) 084 { 085 this.setSamples( samples ); 086 super.format = af; 087 } 088 089 /** 090 * Create a new sample chunk using the given samples 091 * and the given audio format. 092 * 093 * @param samples The samples to initialise with 094 * @param af The audio format of the samples 095 * @param tc The audio timecode of these samples 096 */ 097 public SampleChunk( final byte[] samples, final AudioFormat af, final AudioTimecode tc ) 098 { 099 this.setSamples( samples ); 100 this.startTimecode = tc; 101 super.format = af; 102 } 103 104 /** 105 * Set the samples in this sample chunk. 106 * @param samples the samples in this sample chunk. 107 */ 108 public void setSamples( final byte[] samples ) 109 { 110 synchronized( this.samples ) 111 { 112 this.samples = samples; 113 } 114 } 115 116 /** 117 * Get the samples in this sample chunk 118 * @return the samples in this sample chunk 119 */ 120 public byte[] getSamples() 121 { 122 return this.samples; 123 } 124 125 /** 126 * Returns the number of samples in this sample chunk. If there are 128 127 * stereo samples, this method will return 256. That is, it does not 128 * normalise for the number of channels. However, it does normalise 129 * for the size of each sample. So if this is a 16-bit buffer of 256 130 * bytes length, this method will return 128. 131 * 132 * @return the number of samples in this sample chunk. 133 */ 134 public int getNumberOfSamples() 135 { 136 return this.samples.length / (this.format.getNBits()/8); 137 } 138 139 /** 140 * Returns a {@link ByteBuffer} that can be used to create 141 * views of the samples in the object. For example, to get short 142 * integers, you can get {@link #getSamplesAsByteBuffer()}.asShortBuffer() 143 * 144 * @return A {@link ByteBuffer} 145 */ 146 public ByteBuffer getSamplesAsByteBuffer() 147 { 148 if( this.samples == null ) return null; 149 150 ByteOrder bo = null; 151 152 if( this.format.isBigEndian() ) 153 bo = ByteOrder.BIG_ENDIAN; 154 else bo = ByteOrder.LITTLE_ENDIAN; 155 156 return ByteBuffer.wrap( this.samples ).order( bo ); 157 } 158 159 /** 160 * Returns an appropriate sample buffer for this 161 * sample chunk. If an appropriate sample buffer 162 * cannot be found, null will be returned. This will 163 * wrap the samples array from the underlying chunk, 164 * so you should only side-affect the samples if you're 165 * sure they will not be reused. 166 * 167 * @return An appropriate {@link SampleBuffer} 168 */ 169 public SampleBuffer getSampleBuffer() 170 { 171 return SampleBufferFactory.createSampleBuffer( this, this.format ); 172 } 173 174 /** 175 * Set the timecode at the start of this audio chunk. 176 * @param startTimecode the timecode at the start of the chunk. 177 */ 178 public void setStartTimecode( final AudioTimecode startTimecode ) 179 { 180 this.startTimecode = startTimecode; 181 } 182 183 /** 184 * Get the timecode at the start of this audio chunk. 185 * @return the timecode at the start of the chunk. 186 */ 187 public AudioTimecode getStartTimecode() 188 { 189 return this.startTimecode; 190 } 191 192 /** 193 * Return a slice of data from the sample array. The indices are based 194 * on the samples in the byte array, not the bytes themselves. 195 * <p> 196 * The assumption is that samples are whole numbers of bytes. So, if the sample 197 * size was 16-bits, then passing in 2 for the start index would actually 198 * index the byte at index 4 in the underlying sample array. The order 199 * of the bytes is unchanged. 200 * 201 * @param start The start index of the sample. 202 * @param length The length of the samples get. 203 * @return The sample slice as a new {@link SampleChunk} 204 */ 205 public SampleChunk getSampleSlice( final int start, final int length ) 206 { 207 final int nBytesPerSample = this.format.getNBits()/8; 208 final int startSampleByteIndex = start * nBytesPerSample; 209 final byte[] newSamples = new byte[length * nBytesPerSample]; 210 211 synchronized( this.samples ) 212 { 213 System.arraycopy( this.samples, startSampleByteIndex, 214 newSamples, 0, length*nBytesPerSample ); 215 } 216 217 final SampleChunk s = new SampleChunk( this.format ); 218 s.setSamples( newSamples ); 219 220 // Set the timestamp to the start of this new slice 221 final double samplesPerChannelPerMillisec = this.format.getSampleRateKHz(); 222 s.setStartTimecode( new AudioTimecode( 223 this.getStartTimecode().getTimecodeInMilliseconds() + 224 (long)(start / samplesPerChannelPerMillisec) ) ); 225 226 return s; 227 } 228 229 /** 230 * Prepends the given samples to the front of this sample chunk. It is 231 * expected that the given samples are in the same format as this 232 * sample chunk; if they are not an exception is thrown. 233 * Side-affects this sample chunk and will return a reference to 234 * this sample chunk. 235 * 236 * @param sample the samples to add 237 * @return This sample chunk with the bytes prepended 238 */ 239 public SampleChunk prepend( final SampleChunk sample ) 240 { 241 // Check the sample formats are the same 242 if( !sample.getFormat().equals( this.format ) ) 243 throw new IllegalArgumentException("Sample types are not equivalent"); 244 245 // Get the samples from the given chunk 246 final byte[] x1 = sample.getSamplesAsByteBuffer().array(); 247 248 // Create an array for the concatenated pair 249 final byte[] newSamples = new byte[ this.samples.length + x1.length ]; 250 251 // Loop through adding the new samples 252 System.arraycopy( x1, 0, newSamples, 0, x1.length ); 253 254 synchronized( this.samples ) 255 { 256 System.arraycopy( this.samples, 0, newSamples, x1.length, this.samples.length ); 257 } 258 259 // Update this object 260 this.samples = newSamples; 261 this.setStartTimecode( sample.getStartTimecode().clone() ); 262 return this; 263 } 264 265 /** 266 * Appends the given samples to the end of this sample chunk. It is 267 * expected that the given samples are in the same format as this 268 * sample chunk; if they are not an exception is thrown. 269 * Side-affects this sample chunk and will return a reference to 270 * this sample chunk. 271 * 272 * @param sample the samples to add 273 * @return This sample chunk with the bytes appended 274 */ 275 public SampleChunk append( final SampleChunk sample ) 276 { 277 // Check the sample formats are the same 278 if( !sample.getFormat().equals( this.format ) ) 279 throw new IllegalArgumentException("Sample types are not equivalent"); 280 281 // Get the samples from the given chunk 282 final byte[] x1 = sample.getSamplesAsByteBuffer().array(); 283 284 // Create an array for the concatenated pair 285 final byte[] newSamples = new byte[ this.samples.length + x1.length ]; 286 287 synchronized( this.samples ) 288 { 289 System.arraycopy( this.samples, 0, newSamples, 0, this.samples.length ); 290 } 291 292 System.arraycopy( x1, 0, newSamples, this.samples.length, x1.length ); 293 294 // Update this object 295 this.samples = newSamples; 296 return this; 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override 303 public SampleChunk clone() 304 { 305 return new SampleChunk( this.samples.clone(), this.format.clone(), this.startTimecode.clone() ); 306 } 307 308 /** 309 * Pads the sample chunk to the given size with zeros. 310 * @param requiredSampleSetSize The required sample size 311 */ 312 public void pad( final int requiredSampleSetSize ) 313 { 314 final byte[] samples = new byte[requiredSampleSetSize*(this.format.getNBits()/8)]; 315 System.arraycopy( this.samples, 0, samples, 0, this.samples.length ); 316 this.samples = samples; 317 } 318}