import java.io.*; import javax.sound.sampled.*; /** * This class is a thread that continuously listens for ultrasound * through the microphone and notifies the UltrasoundUpdateListener it * was instantiated with whenever ultrasound is detected. Any class * that instantiates an UltrasoundListener must provide it with an * object that implements the UltrasoundUpdateListener interface so that * the UltrasoundListener class can properly notify the appropriate * class of ultrasound updates. * * @author Tony Offer * @author Chris Palistrant * @version 1.0 */ public class UltrasoundListener extends Thread implements Constants { // Reference to that object that will receive ultrasound updates. protected UltrasoundUpdateListener updateListener = null; // This is the data line that will be used for capturing audio from // the microphone. protected TargetDataLine targetDataLine = null; // This array of UltrasoundAnalyzer objects behaves like a thread pool // for analyzing sets of data. When a block of N*CHANNELS audio samples // is ready to be analyzed by the ultrasound detection algorithm, one of // the threads in this thread pool will be used to do the processing. protected UltrasoundAnalyzer [] analyzerPool = null; // The current index in the pool of UltrasondAnalyzer threads. This // index increments, with wraparound, through the 'analyzerPool' array // each time a new set of data is ready for analysis. protected int analyzerPoolInd = 0; // Indicates whether or not ultrasound has been detected. This // boolean is used for "debouncing" purposes so that multiple // ultrasound detections are not produced from the same ultrasound // pulse. protected boolean usDebounced = false; // A count that is used for "debouncing" purposes. Once this // integer reaches a certain value, it is safe to assume that an // ultrasound pulse has been detected. protected int posCount = 0; // This count is used for "un-debouncing" a positive ultrasound // detection. When enough ultrasound detections have come in, // usDebounced will be asserted. In order for usDebounced to become // de-asserted and for the ultrasound to become "un-debounced," this // count must increase to the debounce value. This count is // incremented every time a negative detection occurs while // usDebounced is asserted. This count is necessary so that a // single negative ultrasound detection in a long string of positive // ultrasound detections does not de-assert usDebounced, causing // another, immediate ultrasound detection even though there was // really just one pulse of ultrasound. protected int negCount = 0; // Boolean indicating whether or not to display extra debugging // information. protected boolean debug = false; // Array of the timestamps of the most recent positive ultrasound // detections. As soon as ultrasound is debounced completely, this // array will be analyzed to find the timestamp closest to the // actual first detection of ultrasound. protected long [] timesDetected = null; // Number of positive ultrasound detections in a row that are // required before a positive ultrasound identification can be made. public static final int debounceValue = 5; // The number of channels available on the audio system protected int CHANNELS = 1; /** * This default constructor assumes a sampling rate of 44100 Hz, a * target frequency of 21000 Hz, an N value of 420, and debugging * information turned off. A reference to an * UltrasoundUpdateListener object must be passed to this * constructor because the UltrasoundListener class needs some way * of notifying outside classes of ultrasound updates. * * @param outsideUpdateListener Reference to the object that * implements the * UltrasoundUpdateListener * interface. */ public UltrasoundListener(UltrasoundUpdateListener outsideUpdateListener) { this(outsideUpdateListener, false); } public UltrasoundListener(UltrasoundUpdateListener outsideUpdateListener, boolean inDebug) { updateListener = outsideUpdateListener; debug = inDebug; // Set up everything related to the audio system for recording // Create an AudioFormat object for defining the recording // format. AudioFormat audioFormat = new AudioFormat(SAMPLING_RATE, SAMPLE_SIZE, CHANNELS, SIGN, BIGEND); // Make a target data line info with the given audio format DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat); try { if (debug) System.out.println("Making Data Line..."); // Before we get a line, we check to ensure that is supported. if(!AudioSystem.isLineSupported(dataLineInfo)) { CHANNELS = 2; audioFormat = new AudioFormat(SAMPLING_RATE, SAMPLE_SIZE, CHANNELS, SIGN, BIGEND); dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat); } if (debug) System.out.println("Getting Line..."); // Get the TargetDataLine from the audio system. targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo); targetDataLine.open(audioFormat); } catch (Exception e) { System.out.println("Exiting prematurely:" + e); System.exit(1); } // Create and start all the UltrasoundAnalyzer threads that will // be used in the thread pool. analyzerPool = new UltrasoundAnalyzer[NUMTHREADS]; for (int i = 0; i < analyzerPool.length; i++) { analyzerPool[i] = new UltrasoundAnalyzer(this, debug, CHANNELS); analyzerPool[i].start(); } // The array of recent ultrasound detection timestamps should // have as many entries as the amount of required positive // detections before ultrasound is fully debounced. timesDetected = new long[debounceValue]; } /** * This is the main processing method of this class, where sound is * recorded and the samples obtained are analyzed. Since the * UltrasoundDetector object analyzes N samples at a time, each * iteration of this method reads N samples from the microphone then * starts another thread for analyzing the samples. */ public void run() { // Start recording on the data line. targetDataLine.start(); // For now, just loop forever. while (true) { // Declare a new buffer so that new sample values are // sent to the analyzer threads. Note that sample size // is 16 bits and recording format is stereo, so there // are 4 total bytes per sample. byte [] newBuffer = new byte[N*2*CHANNELS]; // Read N*4 bytes of data from the microphone into the // sample buffer. int cnt = targetDataLine.read(newBuffer,0, newBuffer.length); if (cnt != newBuffer.length) { System.out.println("UltrasoundListener:" + "N*2*CHANNELS bytes were not " + "read from the target data line."); System.exit(1); } // Use an UltrasoundAnalyzer thread from the pool to handle // the N samples just read from the microphone. This // thread will notify this UltrasoundListener if // ultrasound was detected. // Wait until the analyzer thread at the current index in // the thread pool becomes available for work. int unavailableCount = 0; while (!analyzerPool[analyzerPoolInd].available()) { if (debug) { unavailableCount++; if (unavailableCount > 50) { System.out.println("Analyzer " + analyzerPoolInd + " currently unavailable " + "to process sample data."); unavailableCount = 0; } } } // Send the buffer of recorded audio data to the analyzer // thread at the current index in the thread pool. If // this thread really was not available to do work, // print an error and exit. if (!analyzerPool[analyzerPoolInd].analyze(newBuffer/*, blockNum++*/)) { System.out.println("Tried to analyze data before " + "analyzer was ready."); System.exit(1); } // The analyzer thread at the current index in the thread // pool is currently being used. Increment the index so // that a new thread can be used the next time a set of data // is ready to be analyzed. analyzerPoolInd = (analyzerPoolInd + 1) % NUMTHREADS; } } /** * Provides a means by which UltrasoundAnalyzer threads can notify * this UltrasoundListener class when a block of samples is done * being analyzed. * * @param usDetected Whether or not ultrasound was detected in * the samples. * @param timeDone The time at which analysis of the samples * was completed. */ public synchronized void analysisDone(boolean usDetected, long timeDone) { /*** TESTING ***/ //System.out.println("Analysis Done"); // If ultrasound was detected in the samples sent to the // UltrasoundAnalyzer and the ultrasound detection has not yet // been debounced, increase the debounce count and check to see // if the count is high enough. if (usDetected && !usDebounced) { // Ultrasound has been detected for one set of audio // samples. Remember the timestamp of this detection. timesDetected[posCount] = timeDone; posCount++; // If enough ultrasound detections have occurred in a row, // set usDebounced and notify the UltrasoundUpdateListener // that ultrasound has been detected at this point in time. if (posCount >= debounceValue) { usDebounced = true; // Reset the negative ultrasound detection count so that // the number of negative ultrasound detections can be // properly tracked. negCount = 0; // Find the smallest timestamp on the list of recent // ultrasound detections so that the timestamp closest // to the beginning of the ultrasound is sent back to // the ultrasound update listener. long smallestTime = timesDetected[0]; for (int i = 1; i < timesDetected.length; i++) { if (timesDetected[i] < smallestTime) smallestTime = timesDetected[i]; } /*** TESTING ***/ /* if (debug) System.out.println("Ultrasound processing detection " + "time: " + (System.currentTimeMillis() - smallestTime)); */ // Pass the timestamp (in milliseconds since Jan. // 1 1970) to the UltrasoundUpdateListener so that it // knows when the ultrasound was detected. updateListener.ultrasoundDetected(smallestTime); } } else if (!usDetected && usDebounced) { // There has been a negative ultrasound detection and // usDebounced is currently asserted, so increase the // negative ultrasound detection count. negCount++; // When the negative ultrasound detection count reaches the // debounce value, usDebounced can be safely de-asserted. if (negCount >= debounceValue) { posCount = 0; usDebounced = false; } } else if (usDetected && usDebounced) { // If ultrasound has been debounced and ultrasound was just // detected, reset the negative ultrasound count. This is // because usDebounced should only be de-asserted if // debounceValue negative ultrasound detections occur in a // row. negCount = 0; } else if (!usDetected && !usDebounced) { // If ultrasound has not been debounced and a negative // ultrasound detection occurs, make sure to reset the // positive ultrasound count. This is to ensure that // ultrasound is only debounced when debounceValue positive // ultrasound detections occur in a row. posCount = 0; } } }