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. The data 044 * is unnormalised - that is, it is stored in this class in its original format 045 * in the form of a byte array. This is for speed during audio playback. 046 * 047 * If you require normalised data (data as an integer array for example) use the 048 * method {@link #getSamplesAsByteBuffer()} and use the {@link ByteBuffer}'s 049 * methods asXXXBuffer (e.g. ByteBuffer#asShortBuffer) to get the samples in a 050 * normalised form. 051 * 052 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 053 * @created 8 Jun 2011 054 * 055 */ 056public class SampleChunk extends Audio 057{ 058 /** The samples in the chunk */ 059 private byte[] samples = new byte[1]; 060 061 /** The timecode of the start of the sample chunk */ 062 private AudioTimecode startTimecode = new AudioTimecode(0); 063 064 /** 065 * Create a new SampleChunk buffer with the given audio format, but do not 066 * initialise the samples. 067 * 068 * @param af 069 * 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 and the given audio 078 * format. 079 * 080 * @param samples 081 * The samples to initialise with 082 * @param af 083 * The audio format of the samples 084 */ 085 public SampleChunk(final byte[] samples, final AudioFormat af) 086 { 087 this.setSamples(samples); 088 super.format = af; 089 } 090 091 /** 092 * Create a new sample chunk using the given samples and the given audio 093 * format. 094 * 095 * @param samples 096 * The samples to initialise with 097 * @param af 098 * The audio format of the samples 099 * @param tc 100 * The audio timecode of these samples 101 */ 102 public SampleChunk(final byte[] samples, final AudioFormat af, final AudioTimecode tc) 103 { 104 this.setSamples(samples); 105 this.startTimecode = tc; 106 super.format = af; 107 } 108 109 /** 110 * Set the samples in this sample chunk. 111 * 112 * @param samples 113 * the samples in this sample chunk. 114 */ 115 public void setSamples(final byte[] samples) 116 { 117 synchronized (this.samples) 118 { 119 this.samples = samples; 120 } 121 } 122 123 /** 124 * Get the samples in this sample chunk 125 * 126 * @return the samples in this sample chunk 127 */ 128 public byte[] getSamples() 129 { 130 return this.samples; 131 } 132 133 /** 134 * Returns the number of samples in this sample chunk. If there are 128 135 * stereo samples, this method will return 256. That is, it does not 136 * normalise for the number of channels. However, it does normalise for the 137 * size of each sample. So if this is a 16-bit buffer of 256 bytes length, 138 * this method will return 128. 139 * 140 * @return the number of samples in this sample chunk. 141 */ 142 public int getNumberOfSamples() 143 { 144 return this.samples.length / (this.format.getNBits() / 8); 145 } 146 147 /** 148 * Returns a {@link ByteBuffer} that can be used to create views of the 149 * samples in the object. For example, to get short integers, you can get 150 * {@link #getSamplesAsByteBuffer()}.asShortBuffer() 151 * 152 * @return A {@link ByteBuffer} 153 */ 154 public ByteBuffer getSamplesAsByteBuffer() 155 { 156 if (this.samples == null) 157 return null; 158 159 ByteOrder bo = null; 160 161 if (this.format.isBigEndian()) 162 bo = ByteOrder.BIG_ENDIAN; 163 else 164 bo = ByteOrder.LITTLE_ENDIAN; 165 166 return ByteBuffer.wrap(this.samples).order(bo); 167 } 168 169 /** 170 * Returns an appropriate sample buffer for this sample chunk. If an 171 * appropriate sample buffer cannot be found, null will be returned. This 172 * will wrap the samples array from the underlying chunk, so you should only 173 * side-affect the samples if you're sure they will not be reused. 174 * 175 * @return An appropriate {@link SampleBuffer} 176 */ 177 public SampleBuffer getSampleBuffer() 178 { 179 return SampleBufferFactory.createSampleBuffer(this, this.format); 180 } 181 182 /** 183 * Set the timecode at the start of this audio chunk. 184 * 185 * @param startTimecode 186 * the timecode at the start of the chunk. 187 */ 188 public void setStartTimecode(final AudioTimecode startTimecode) 189 { 190 this.startTimecode = startTimecode; 191 } 192 193 /** 194 * Get the timecode at the start of this audio chunk. 195 * 196 * @return the timecode at the start of the chunk. 197 */ 198 public AudioTimecode getStartTimecode() 199 { 200 return this.startTimecode; 201 } 202 203 /** 204 * Return a slice of data from the sample array. The indices are based on 205 * the samples in the byte array, not the bytes themselves. 206 * <p> 207 * The assumption is that samples are whole numbers of bytes. So, if the 208 * sample size was 16-bits, then passing in 2 for the start index would 209 * actually index the byte at index 4 in the underlying sample array. The 210 * order of the bytes is unchanged. 211 * 212 * @param start 213 * The start index of the sample. 214 * @param length 215 * The length of the samples get. 216 * @return The sample slice as a new {@link SampleChunk} 217 */ 218 public SampleChunk getSampleSlice(final int start, final int length) 219 { 220 final int nBytesPerSample = this.format.getNBits() / 8; 221 final int startSampleByteIndex = start * nBytesPerSample; 222 final byte[] newSamples = new byte[length * nBytesPerSample]; 223 224 synchronized (this.samples) 225 { 226 System.arraycopy(this.samples, startSampleByteIndex, 227 newSamples, 0, length * nBytesPerSample); 228 } 229 230 final SampleChunk s = new SampleChunk(this.format); 231 s.setSamples(newSamples); 232 233 // Set the timestamp to the start of this new slice 234 final double samplesPerChannelPerMillisec = this.format.getSampleRateKHz(); 235 s.setStartTimecode(new AudioTimecode( 236 this.getStartTimecode().getTimecodeInMilliseconds() + 237 (long) (start / samplesPerChannelPerMillisec))); 238 239 return s; 240 } 241 242 /** 243 * Prepends the given samples to the front of this sample chunk. It is 244 * expected that the given samples are in the same format as this sample 245 * chunk; if they are not an exception is thrown. Side-affects this sample 246 * chunk and will return a reference to this sample chunk. 247 * 248 * @param sample 249 * the samples to add 250 * @return This sample chunk with the bytes prepended 251 */ 252 public SampleChunk prepend(final SampleChunk sample) 253 { 254 // Check the sample formats are the same 255 if (!sample.getFormat().equals(this.format)) 256 throw new IllegalArgumentException("Sample types are not equivalent"); 257 258 // Get the samples from the given chunk 259 final byte[] x1 = sample.getSamplesAsByteBuffer().array(); 260 261 // Create an array for the concatenated pair 262 final byte[] newSamples = new byte[this.samples.length + x1.length]; 263 264 // Loop through adding the new samples 265 System.arraycopy(x1, 0, newSamples, 0, x1.length); 266 267 synchronized (this.samples) 268 { 269 System.arraycopy(this.samples, 0, newSamples, x1.length, this.samples.length); 270 } 271 272 // Update this object 273 this.samples = newSamples; 274 this.setStartTimecode(sample.getStartTimecode().clone()); 275 return this; 276 } 277 278 /** 279 * Appends the given samples to the end of this sample chunk. It is expected 280 * that the given samples are in the same format as this sample chunk; if 281 * they are not an exception is thrown. Side-affects this sample chunk and 282 * will return a reference to this sample chunk. 283 * 284 * @param sample 285 * the samples to add 286 * @return This sample chunk with the bytes appended 287 */ 288 public SampleChunk append(final SampleChunk sample) 289 { 290 // Check the sample formats are the same 291 if (!sample.getFormat().equals(this.format)) 292 throw new IllegalArgumentException("Sample types are not equivalent"); 293 294 // Get the samples from the given chunk 295 final byte[] x1 = sample.getSamplesAsByteBuffer().array(); 296 297 // Create an array for the concatenated pair 298 final byte[] newSamples = new byte[this.samples.length + x1.length]; 299 300 synchronized (this.samples) 301 { 302 System.arraycopy(this.samples, 0, newSamples, 0, this.samples.length); 303 } 304 305 System.arraycopy(x1, 0, newSamples, this.samples.length, x1.length); 306 307 // Update this object 308 this.samples = newSamples; 309 return this; 310 } 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override 316 public SampleChunk clone() 317 { 318 return new SampleChunk(this.samples.clone(), this.format.clone(), this.startTimecode == null ? new AudioTimecode( 319 0) 320 : this.startTimecode.clone()); 321 } 322 323 /** 324 * Pads the sample chunk to the given size with zeros. 325 * 326 * @param requiredSampleSetSize 327 * The required sample size 328 */ 329 public void pad(final int requiredSampleSetSize) 330 { 331 final byte[] samples = new byte[requiredSampleSetSize * (this.format.getNBits() / 8)]; 332 System.arraycopy(this.samples, 0, samples, 0, this.samples.length); 333 this.samples = samples; 334 } 335}