import java.io.*; import javax.sound.sampled.*; /** * This class is used for listening for ultrasound through the microphone * and notifies the UltrasoundUpdateListener it was instantiated with * when 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 implements Constants { // 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; // 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() { this(false); } public UltrasoundListener(boolean inDebug) { debug = inDebug; // Set up everything related to the audio system for recording // Make two audio formats: one with 1 channel and another with 2 // channels since certain platforms only support one or the // other. AudioFormat audioFormat = new AudioFormat(SAMPLING_RATE, SAMPLE_SIZE, 1, SIGN, BIGEND); // Make a target data line info with the given audio format DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat); /**TESTING**/ //System.out.println("Is the Format Supported?" + // dataLineInfo.isFormatSupported(audioFormat)); // If one channel recording is not supported, make a new audio // format with two channels and set the static global variable // CHANNELS for use throughout the code. /*if (!dataLineInfo.isFormatSupported(audioFormat)) { CHANNELS = 2; audioFormat = new AudioFormat(SAMPLING_RATE, SAMPLE_SIZE, CHANNELS, SIGN, BIGEND); dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat); }*/ try { if (debug) System.out.println("Making Data Line..."); /*** TESTING ***/ Mixer.Info [] mixers = AudioSystem.getMixerInfo(); for (int k = 0; k < mixers.length; k++) System.out.println("Available mixer: " + mixers[k].getDescription()); // 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); } // 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. * * @param msec The length of time that the microphone should record * in milliseconds. * */ public boolean record(long msec) { // Start recording on the data line. targetDataLine.start(); // Loop for a length of time based on current time plus // the given time. long t1 = msec + System.currentTimeMillis(); while(t1 > System.currentTimeMillis()) { // 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); } /*** 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, newBuffer, debug, CHANNELS).start(); // If ultrasound has been debounced during this // iteration, immediately return, indicating a positive // ultrasound detection. if (usDebounced) { usDebounced = false; return true; } } //once we are done stop recording targetDataLine.stop(); // Return whether or not ultrasound was debounced while // recording. boolean returnValue = usDebounced; usDebounced = false; return returnValue; } /** * 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) { count++; /*** TESTING ***/ if (debug) System.out.println("Debounce count incremented: " + 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; count = 0; /*** TESTING ***/ if (debug) System.out.println("Debounce count reset due to " + "US detection."); } } else { count = 0; usDebounced = false; /*** TESTING ***/ if (debug) System.out.println("Debounce count reset due to " + "non-US detection."); } } public static void main(String[] args){ UltrasoundListener usl = new UltrasoundListener(true); //UltrasoundListener usl = new UltrasoundListener(false); while(true){ usl.record(2000); try{ Thread.sleep(4000); }catch(Exception e){ System.out.println("Cannot wait."); } } } }