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 { // 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; // Buffer for holding the bytes that are read from the // targetDataLine and that are to be analyzed by the // UltrasoundDetector object. protected byte [] sampleBuffer; // 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 count = 0; // Boolean indicating whether or not to display extra debugging // information. protected boolean DEBUG = false; // Values to use for creating an AudioFormat object. public static final float SAMPLE_RATE = 44100.0F; public static final int SAMPLE_SIZE = 16; // in bits public static final int CHANNELS = 1; // mono public static final boolean SIGN = true; // Signed values public static final boolean BIGEND = false; // little endian // 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; /** * 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 debug) { updateListener = outsideUpdateListener; DEBUG = debug; // The buffer for holding samples should be N*2 bytes long since // the UltrasoundDetector class analyzes N samples at a time and // each of our samples, using the audio format specified, is 2 // bytes long. sampleBuffer = new byte[UltrasoundAnalyzer.N*2]; // Set up everything related to the audio system for recording // Create an AudioFormat object for defining the recording // format. AudioFormat audioFormat = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE, CHANNELS, SIGN, BIGEND); try { if (DEBUG) System.out.println("Making Data Line..."); // Make a target data line info with the given audio format DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat); // Get the TargetDataLine from the audio system. targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo); if (DEBUG) System.out.println("Getting Line..."); targetDataLine.open(audioFormat); } catch (Exception e) { System.out.println("Exiting prematurely:" + e); System.exit(1); } } /** * 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) { // Read N*2 bytes of data from the microphone into the // sample buffer. int cnt = targetDataLine.read(sampleBuffer, 0, sampleBuffer.length); if (cnt != sampleBuffer.length) { System.out.println("UltrasoundListener: N*2 bytes were not " + " read from the target data line."); System.exit(1); } /*** TESTING ***/ //System.out.println(cnt + " bytes read from microphone." + // " Starting analyzer thread..."); // Start a new UltrasoundAnalyzer thread to handle the N // samples just read from the microphone. This thread will // notify this UltrasoundListener if ultrasound was detected. new UltrasoundAnalyzer(this, (byte []) sampleBuffer.clone(), DEBUG).start(); } } /** * 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. */ public synchronized void analysisDone(boolean usDetected) { /*** 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) { count++; // 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 (count >= debounceValue) { usDebounced = true; // Pass a current timestamp (in milliseconds since Jan. // 1 1970) to the UltrasoundUpdateListener so that it // knows when the ultrasound was detected. updateListener.ultrasoundDetected(new java.util.Date().getTime()); } } else if (!usDetected) { count = 0; usDebounced = false; } } }