AudioDispatcher.java (15967B)
1 /* 2 * _______ _____ _____ _____ 3 * |__ __| | __ \ / ____| __ \ 4 * | | __ _ _ __ ___ ___ ___| | | | (___ | |__) | 5 * | |/ _` | '__/ __|/ _ \/ __| | | |\___ \| ___/ 6 * | | (_| | | \__ \ (_) \__ \ |__| |____) | | 7 * |_|\__,_|_| |___/\___/|___/_____/|_____/|_| 8 * 9 * ------------------------------------------------------------- 10 * 11 * TarsosDSP is developed by Joren Six at IPEM, University Ghent 12 * 13 * ------------------------------------------------------------- 14 * 15 * Info: http://0110.be/tag/TarsosDSP 16 * Github: https://github.com/JorenSix/TarsosDSP 17 * Releases: http://0110.be/releases/TarsosDSP/ 18 * 19 * TarsosDSP includes modified source code by various authors, 20 * for credits and info, see README. 21 * 22 */ 23 24 25 package be.tarsos.dsp; 26 27 import java.io.IOException; 28 import java.util.List; 29 import java.util.concurrent.CopyOnWriteArrayList; 30 import java.util.logging.Level; 31 import java.util.logging.Logger; 32 33 import be.tarsos.dsp.io.TarsosDSPAudioFloatConverter; 34 import be.tarsos.dsp.io.TarsosDSPAudioFormat; 35 import be.tarsos.dsp.io.TarsosDSPAudioInputStream; 36 37 38 /** 39 * This class plays a file and sends float arrays to registered AudioProcessor 40 * implementors. This class can be used to feed FFT's, pitch detectors, audio players, ... 41 * Using a (blocking) audio player it is even possible to synchronize execution of 42 * AudioProcessors and sound. This behavior can be used for visualization. 43 * @author Joren Six 44 */ 45 public class AudioDispatcher implements Runnable { 46 47 48 /** 49 * Log messages. 50 */ 51 private static final Logger LOG = Logger.getLogger(AudioDispatcher.class.getName()); 52 53 /** 54 * The audio stream (in bytes), conversion to float happens at the last 55 * moment. 56 */ 57 private final TarsosDSPAudioInputStream audioInputStream; 58 59 /** 60 * This buffer is reused again and again to store audio data using the float 61 * data type. 62 */ 63 private float[] audioFloatBuffer; 64 65 /** 66 * This buffer is reused again and again to store audio data using the byte 67 * data type. 68 */ 69 private byte[] audioByteBuffer; 70 71 /** 72 * A list of registered audio processors. The audio processors are 73 * responsible for actually doing the digital signal processing 74 */ 75 private final List<AudioProcessor> audioProcessors; 76 77 /** 78 * Converter converts an array of floats to an array of bytes (and vice 79 * versa). 80 */ 81 private final TarsosDSPAudioFloatConverter converter; 82 83 private final TarsosDSPAudioFormat format; 84 85 /** 86 * The floatOverlap: the number of elements that are copied in the buffer 87 * from the previous buffer. Overlap should be smaller (strict) than the 88 * buffer size and can be zero. Defined in number of samples. 89 */ 90 private int floatOverlap, floatStepSize; 91 92 /** 93 * The overlap and stepsize defined not in samples but in bytes. So it 94 * depends on the bit depth. Since the int datatype is used only 8,16,24,... 95 * bits or 1,2,3,... bytes are supported. 96 */ 97 private int byteOverlap, byteStepSize; 98 99 100 /** 101 * The number of bytes to skip before processing starts. 102 */ 103 private long bytesToSkip; 104 105 /** 106 * Position in the stream in bytes. e.g. if 44100 bytes are processed and 16 107 * bits per frame are used then you are 0.5 seconds into the stream. 108 */ 109 private long bytesProcessed; 110 111 112 /** 113 * The audio event that is send through the processing chain. 114 */ 115 private AudioEvent audioEvent; 116 117 /** 118 * If true the dispatcher stops dispatching audio. 119 */ 120 private boolean stopped; 121 122 /** 123 * If true then the first buffer is only filled up to buffer size - hop size 124 * E.g. if the buffer is 2048 and the hop size is 48 then you get 2000 times 125 * zero 0 and 48 actual audio samples. During the next iteration you get 126 * mostly zeros and 96 samples. 127 */ 128 private boolean zeroPadFirstBuffer; 129 130 /** 131 * If true then the last buffer is zero padded. Otherwise the buffer is 132 * shortened to the remaining number of samples. If false then the audio 133 * processors must be prepared to handle shorter audio buffers. 134 */ 135 private boolean zeroPadLastBuffer; 136 137 /** 138 * Create a new dispatcher from a stream. 139 * 140 * @param stream 141 * The stream to read data from. 142 * @param audioBufferSize 143 * The size of the buffer defines how much samples are processed 144 * in one step. Common values are 1024,2048. 145 * @param bufferOverlap 146 * How much consecutive buffers overlap (in samples). Half of the 147 * AudioBufferSize is common (512, 1024) for an FFT. 148 */ 149 public AudioDispatcher(final TarsosDSPAudioInputStream stream, final int audioBufferSize, final int bufferOverlap){ 150 // The copy on write list allows concurrent modification of the list while 151 // it is iterated. A nice feature to have when adding AudioProcessors while 152 // the AudioDispatcher is running. 153 audioProcessors = new CopyOnWriteArrayList<AudioProcessor>(); 154 audioInputStream = stream; 155 156 format = audioInputStream.getFormat(); 157 158 159 setStepSizeAndOverlap(audioBufferSize, bufferOverlap); 160 161 audioEvent = new AudioEvent(format); 162 audioEvent.setFloatBuffer(audioFloatBuffer); 163 audioEvent.setOverlap(bufferOverlap); 164 165 converter = TarsosDSPAudioFloatConverter.getConverter(format); 166 167 stopped = false; 168 169 bytesToSkip = 0; 170 171 zeroPadLastBuffer = true; 172 } 173 174 /** 175 * Skip a number of seconds before processing the stream. 176 * @param seconds 177 */ 178 public void skip(double seconds){ 179 bytesToSkip = Math.round(seconds * format.getSampleRate()) * format.getFrameSize(); 180 } 181 182 /** 183 * Set a new step size and overlap size. Both in number of samples. Watch 184 * out with this method: it should be called after a batch of samples is 185 * processed, not during. 186 * 187 * @param audioBufferSize 188 * The size of the buffer defines how much samples are processed 189 * in one step. Common values are 1024,2048. 190 * @param bufferOverlap 191 * How much consecutive buffers overlap (in samples). Half of the 192 * AudioBufferSize is common (512, 1024) for an FFT. 193 */ 194 public void setStepSizeAndOverlap(final int audioBufferSize, final int bufferOverlap){ 195 audioFloatBuffer = new float[audioBufferSize]; 196 floatOverlap = bufferOverlap; 197 floatStepSize = audioFloatBuffer.length - floatOverlap; 198 199 audioByteBuffer = new byte[audioFloatBuffer.length * format.getFrameSize()]; 200 byteOverlap = floatOverlap * format.getFrameSize(); 201 byteStepSize = floatStepSize * format.getFrameSize(); 202 } 203 204 /** 205 * if zero pad is true then the first buffer is only filled up to buffer size - hop size 206 * E.g. if the buffer is 2048 and the hop size is 48 then you get 2000x0 and 48 filled audio samples 207 * @param zeroPadFirstBuffer true if the buffer should be zeroPadFirstBuffer, false otherwise. 208 */ 209 public void setZeroPadFirstBuffer(boolean zeroPadFirstBuffer){ 210 this.zeroPadFirstBuffer = zeroPadFirstBuffer; 211 } 212 213 /** 214 * If zero pad last buffer is true then the last buffer is filled with zeros until the normal amount 215 * of elements are present in the buffer. Otherwise the buffer only contains the last elements and no zeros. 216 * By default it is set to true. 217 * 218 * @param zeroPadLastBuffer 219 */ 220 public void setZeroPadLastBuffer(boolean zeroPadLastBuffer) { 221 this.zeroPadLastBuffer = zeroPadLastBuffer; 222 } 223 224 225 /** 226 * Adds an AudioProcessor to the chain of processors. 227 * 228 * @param audioProcessor 229 * The AudioProcessor to add. 230 */ 231 public void addAudioProcessor(final AudioProcessor audioProcessor) { 232 audioProcessors.add(audioProcessor); 233 LOG.fine("Added an audioprocessor to the list of processors: " + audioProcessor.toString()); 234 } 235 236 /** 237 * Removes an AudioProcessor to the chain of processors and calls its <code>processingFinished</code> method. 238 * 239 * @param audioProcessor 240 * The AudioProcessor to remove. 241 */ 242 public void removeAudioProcessor(final AudioProcessor audioProcessor) { 243 audioProcessors.remove(audioProcessor); 244 audioProcessor.processingFinished(); 245 LOG.fine("Remove an audioprocessor to the list of processors: " + audioProcessor.toString()); 246 } 247 248 public void run() { 249 250 int bytesRead = 0; 251 252 if(bytesToSkip!=0){ 253 skipToStart(); 254 } 255 256 //Read the first (and in some cases last) audio block. 257 try { 258 //needed to get correct time info when skipping first x seconds 259 audioEvent.setBytesProcessed(bytesProcessed); 260 bytesRead = readNextAudioBlock(); 261 } catch (IOException e) { 262 String message="Error while reading audio input stream: " + e.getMessage(); 263 LOG.warning(message); 264 throw new Error(message); 265 } 266 267 // As long as the stream has not ended 268 while (bytesRead != 0 && !stopped) { 269 270 //Makes sure the right buffers are processed, they can be changed by audio processors. 271 for (final AudioProcessor processor : audioProcessors) { 272 if(!processor.process(audioEvent)){ 273 //skip to the next audio processors if false is returned. 274 break; 275 } 276 } 277 278 if(!stopped){ 279 //Update the number of bytes processed; 280 bytesProcessed += bytesRead; 281 audioEvent.setBytesProcessed(bytesProcessed); 282 283 // Read, convert and process consecutive overlapping buffers. 284 // Slide the buffer. 285 try { 286 bytesRead = readNextAudioBlock(); 287 audioEvent.setOverlap(floatOverlap); 288 } catch (IOException e) { 289 String message="Error while reading audio input stream: " + e.getMessage(); 290 LOG.warning(message); 291 throw new Error(message); 292 } 293 } 294 } 295 296 // Notify all processors that no more data is available. 297 // when stop() is called processingFinished is called explicitly, no need to do this again. 298 // The explicit call is to prevent timing issues. 299 if(!stopped){ 300 stop(); 301 } 302 } 303 304 305 private void skipToStart() { 306 long skipped = 0l; 307 try{ 308 skipped = audioInputStream.skip(bytesToSkip); 309 if(skipped !=bytesToSkip){ 310 throw new IOException(); 311 } 312 bytesProcessed += bytesToSkip; 313 }catch(IOException e){ 314 String message=String.format("Did not skip the expected amount of bytes, %d skipped, %d expected!", skipped,bytesToSkip); 315 LOG.warning(message); 316 throw new Error(message); 317 } 318 } 319 320 /** 321 * Stops dispatching audio data. 322 */ 323 public void stop() { 324 stopped = true; 325 for (final AudioProcessor processor : audioProcessors) { 326 processor.processingFinished(); 327 } 328 try { 329 audioInputStream.close(); 330 } catch (IOException e) { 331 LOG.log(Level.SEVERE, "Closing audio stream error.", e); 332 } 333 } 334 335 /** 336 * Reads the next audio block. It tries to read the number of bytes defined 337 * by the audio buffer size minus the overlap. If the expected number of 338 * bytes could not be read either the end of the stream is reached or 339 * something went wrong. 340 * 341 * The behavior for the first and last buffer is defined by their corresponding the zero pad settings. The method also handles the case if 342 * the first buffer is also the last. 343 * 344 * @return The number of bytes read. 345 * @throws IOException 346 * When something goes wrong while reading the stream. In 347 * particular, an IOException is thrown if the input stream has 348 * been closed. 349 */ 350 private int readNextAudioBlock() throws IOException { 351 assert floatOverlap < audioFloatBuffer.length; 352 353 // Is this the first buffer? 354 boolean isFirstBuffer = (bytesProcessed ==0 || bytesProcessed == bytesToSkip); 355 356 final int offsetInBytes; 357 358 final int offsetInSamples; 359 360 final int bytesToRead; 361 //Determine the amount of bytes to read from the stream 362 if(isFirstBuffer && !zeroPadFirstBuffer){ 363 //If this is the first buffer and we do not want to zero pad the 364 //first buffer then read a full buffer 365 bytesToRead = audioByteBuffer.length; 366 // With an offset in bytes of zero; 367 offsetInBytes = 0; 368 offsetInSamples=0; 369 }else{ 370 //In all other cases read the amount of bytes defined by the step size 371 bytesToRead = byteStepSize; 372 offsetInBytes = byteOverlap; 373 offsetInSamples = floatOverlap; 374 } 375 376 //Shift the audio information using array copy since it is probably faster than manually shifting it. 377 // No need to do this on the first buffer 378 if(!isFirstBuffer && audioFloatBuffer.length == floatOverlap + floatStepSize ){ 379 System.arraycopy(audioFloatBuffer,floatStepSize, audioFloatBuffer,0 ,floatOverlap); 380 /* 381 for(int i = floatStepSize ; i < floatStepSize+floatOverlap ; i++){ 382 audioFloatBuffer[i-floatStepSize] = audioFloatBuffer[i]; 383 }*/ 384 } 385 386 // Total amount of bytes read 387 int totalBytesRead = 0; 388 389 // The amount of bytes read from the stream during one iteration. 390 int bytesRead=0; 391 392 // Is the end of the stream reached? 393 boolean endOfStream = false; 394 395 // Always try to read the 'bytesToRead' amount of bytes. 396 // unless the stream is closed (stopped is true) or no bytes could be read during one iteration 397 while(!stopped && !endOfStream && totalBytesRead<bytesToRead){ 398 try{ 399 bytesRead = audioInputStream.read(audioByteBuffer, offsetInBytes + totalBytesRead , bytesToRead - totalBytesRead); 400 }catch(IndexOutOfBoundsException e){ 401 // The pipe decoder generates an out of bounds if end 402 // of stream is reached. Ugly hack... 403 bytesRead = -1; 404 } 405 if(bytesRead == -1){ 406 // The end of the stream is reached if the number of bytes read during this iteration equals -1 407 endOfStream = true; 408 }else{ 409 // Otherwise add the number of bytes read to the total 410 totalBytesRead += bytesRead; 411 } 412 } 413 414 if(endOfStream){ 415 // Could not read a full buffer from the stream, there are two options: 416 if(zeroPadLastBuffer){ 417 //Make sure the last buffer has the same length as all other buffers and pad with zeros 418 for(int i = offsetInBytes + totalBytesRead; i < audioByteBuffer.length; i++){ 419 audioByteBuffer[i] = 0; 420 } 421 converter.toFloatArray(audioByteBuffer, offsetInBytes, audioFloatBuffer, offsetInSamples, floatStepSize); 422 }else{ 423 // Send a smaller buffer through the chain. 424 byte[] audioByteBufferContent = audioByteBuffer; 425 audioByteBuffer = new byte[offsetInBytes + totalBytesRead]; 426 for(int i = 0 ; i < audioByteBuffer.length ; i++){ 427 audioByteBuffer[i] = audioByteBufferContent[i]; 428 } 429 int totalSamplesRead = totalBytesRead/format.getFrameSize(); 430 audioFloatBuffer = new float[offsetInSamples + totalBytesRead/format.getFrameSize()]; 431 converter.toFloatArray(audioByteBuffer, offsetInBytes, audioFloatBuffer, offsetInSamples, totalSamplesRead); 432 433 434 } 435 }else if(bytesToRead == totalBytesRead) { 436 // The expected amount of bytes have been read from the stream. 437 if(isFirstBuffer && !zeroPadFirstBuffer){ 438 converter.toFloatArray(audioByteBuffer, 0, audioFloatBuffer, 0, audioFloatBuffer.length); 439 }else{ 440 converter.toFloatArray(audioByteBuffer, offsetInBytes, audioFloatBuffer, offsetInSamples, floatStepSize); 441 } 442 } else if(!stopped) { 443 // If the end of the stream has not been reached and the number of bytes read is not the 444 // expected amount of bytes, then we are in an invalid state; 445 throw new IOException(String.format("The end of the audio stream has not been reached and the number of bytes read (%d) is not equal " 446 + "to the expected amount of bytes(%d).", totalBytesRead,bytesToRead)); 447 } 448 449 450 // Makes sure AudioEvent contains correct info. 451 audioEvent.setFloatBuffer(audioFloatBuffer); 452 audioEvent.setOverlap(offsetInSamples); 453 454 return totalBytesRead; 455 } 456 457 public TarsosDSPAudioFormat getFormat(){ 458 return format; 459 } 460 461 /** 462 * 463 * @return The currently processed number of seconds. 464 */ 465 public float secondsProcessed(){ 466 return bytesProcessed / (format.getSampleSizeInBits() / 8) / format.getSampleRate() / format.getChannels() ; 467 } 468 469 public void setAudioFloatBuffer(float[] audioBuffer){ 470 audioFloatBuffer = audioBuffer; 471 } 472 /** 473 * @return True if the dispatcher is stopped or the end of stream has been reached. 474 */ 475 public boolean isStopped(){ 476 return stopped; 477 } 478 479 }