plectrum

Plectrum: instrument tuner for Android
Log | Files | Refs | README | LICENSE

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 }