/** * The UltrasoundAnalyzer thread class takes an array of bytes, * concatenates them into 16-bit values in little endian format, then * creates an UltrasoundDetector object to decide whether or not * the samples contain the desired frequency. The boolean decision as * to whether or not ultrasound was present in the block of samples is * sent back to the UltrasoundListener object that instantiated this * thread. *
* UltrasoundAnalyzer is a continuously running thread. As long as the * available() method indicates that the thread is ready to receive data, * analysis begins by calling the analyze() method with the array of bytes * to be analyzed. Once the analysis is complete, this thread signals * the UltrasoundListener object and becomes available for another set * of data. * * @author Tony Offer * @author Chris Palistrant * @version 1.0 */ public class UltrasoundAnalyzer extends Thread implements Constants { // The UltrasoundListener that this thread will notify if it // detects ultrasound in the stream of samples. protected UltrasoundListener usl = null; // The UltrasoundDetector object that this thread will use to // analyze the samples. protected UltrasoundDetector usd = null; // The byte array containing the byte samples that will be analyzed // for the presence of ultrasound. protected byte [] byteSamples = null; // Used to "stop" and "start" this analyzer thread, as the thread's // run() method will monitor this boolean in order to decide whether // or not to proceed with the data analysis. This will be accessed // by more than one thread, so declare it as volatile. protected volatile boolean analyzing = false; // Boolean governing output of extra debugging information boolean debug = false; protected int CHANNELS = 1; //protected int order; /** * The main UltrasoundAnalyzer constructor takes a reference to an * UltrasoundListener and a byte array containing the samples * to be analyzed. If ultrasound is detected in the byte array, the * UltrasoundListener is notified. * * @param usListener The object that will be notified of * ultrasound detections. * @param samples The byte array containing the sample values to * be analyzed for ultrasound. * @param channels The number of channels on which the audio was * recorded. */ public UltrasoundAnalyzer(UltrasoundListener usListener, boolean inDebug, int channels) { usl = usListener; debug = inDebug; CHANNELS = channels; // Create a new UltrasoundDetector object for analyzing the // samples and initialize the detector. usd = new UltrasoundDetector(SAMPLING_RATE, TARGET_FREQUENCY, N, debug); usd.init(); } /** * The UltrasoundAnalyzer thread first concatenates the array of * bytes into 16-bit little endian samples, then uses the * UltrasoundDetector to determine whether or not the samples * contain ultrasound and notifies the UltrasoundListener * accordingly. */ public void run() { while (true) { try { // If this thread object is not currently analyzing, put // the thread context on a wait set for this thread // object so that the currently running thread won't do // anything until it is later notified by another thread. if (!analyzing) { // The currently running thread can only wait on this // UltrasoundAnalyzer object if it owns the object's // monitor, so first synchronize on this object. synchronized(this) { while (!analyzing) wait(); } } } catch (InterruptedException ie) { ie.printStackTrace(); System.exit(1); } // Concatenate the samples double [] samples = littleEndConcat(); // Analyze the samples and send the result back to the // UltrasoundListener that instantiated this thread. usl.analysisDone(usd.analyze(samples)/*, order*/); // This thread is done analyzing for now. Reflect this in // the boolean control variable. analyzing = false; } } /** * Returns a boolean indicating whether or not this thread object is * available to begin analysis of a new data set. If this thread * object is currently analyzing a stream of samples, it cannot allow * another thread to begin a new analysis, overwriting the array of * byte samples, so this method returns false. This method will return * true when this thread object is currently not analyzing a set of * samples. * * @return Boolean indicating whether or not this thread is available * for analyzing a new set of data. */ public boolean available() { return !analyzing; } /** * Begin DSP analysis of data in order to determine whether or not * ultrasound is present in the set of audio samples. This method * notifies the thread waiting on this UltrasoundAnalyzer object to * wake up and begin processing the bytes passed in to this method. * If this UltrasoundAnalyzer is not available to analyze a new set * of data, this method simply returns false and does nothing. This * method must be synchronized to ensure that the thread calling it * owns the UltrasoundAnalyzer object's monitor. * * @param samples The byte array of audio data to be analyzed for * ultrasound * @return Whether or not this UltrasoundAnalyzer object was able * to service the request to analyze data. */ public synchronized boolean analyze(byte [] samples/*, int theOrder*/) { // If this UltrasoundAnalyzer object is not ready to analyze a // new set of data, immediately return, indicating failure. if (analyzing) return false; byteSamples = samples; //order = theOrder; // Ensure that the length of byteSamples is N*2*CHANNELS, since // UltrasoundDetector expects exactly N samples for its // analysis. If sample size is 16 bits and recording format is // stereo, there are 4 total bytes per sample. if (byteSamples.length != (N*2*CHANNELS)) { System.out.println("UltrasoundAnalyzer: The number of bytes " + "for analysis is not N*2*CHANNELS where N " + "is " + N + " and CHANNELS is " + CHANNELS); System.exit(1); } // This UltrasoundAnalyzer thread is not currently analyzing a set // of data, but it will be now, so set the 'analyzing' control // boolean and wake up the thread waiting on this UltrasoundAnalyzer // object so that analysis of the newly obtained 'byteSamples' can // commence. analyzing = true; notify(); return true; } /** * Helper method for concatenating all the values in a byte array * into 16-bit double values using the little endian format. * * @return Array of double values containing the concatenated * bytes. */ protected double [] littleEndConcat() { double [] samples = new double[byteSamples.length/(2*CHANNELS)]; java.lang.Byte highOrder = null, lowOrder = null, highOrder2 = null, lowOrder2 = null; int sample = 0; // Go through the entire byteSamples array two at a time, only // looking at one channel. //for (int i=0, j=0; i < byteSamples.length-1; i+=2, j++) { for (int i = 0; i < byteSamples.length; i += (2*CHANNELS) ) { // Little endian means the low order byte is listed first. lowOrder = new java.lang.Byte(byteSamples[i]); highOrder = new java.lang.Byte(byteSamples[i+1]); sample = (highOrder.intValue() * 256) | (0xFF & lowOrder.intValue()); // If we are recording in stereo, each sample consists of four // bytes: two for the left channel and two for the right channel. // The left and right channel must be added together in order // to get a single, mono sequence of samples. if (CHANNELS > 1) { lowOrder2 = new java.lang.Byte(byteSamples[i+2]); highOrder2 = new java.lang.Byte(byteSamples[i+3]); sample += ((highOrder2.intValue() * 256) | (0xFF & lowOrder2.intValue())); // Make sure not to clip. If the number in 'sample' // will not fit in a byte, max it out at 32767. if (sample > 32767) sample = 32767; } samples[i/(2*CHANNELS)] = (double) sample; /*** TESTING ***/ /* if (debug) { System.out.println("Number of channels: " + CHANNELS); System.out.println(this.toString()+" >>Frame " + i/(2*CHANNELS) + "<<"); System.out.println(this.toString()+ " " + byteSamples[i]); System.out.println(this.toString()+ " " + byteSamples[i+1]); System.out.println(this.toString()+ " " + byteSamples[i+2]); System.out.println(this.toString()+ " " + byteSamples[i+3]); System.out.println(this.toString() + "--------------------------"); }*/ } return samples; } }