import java.io.*; import java.net.*; import javax.sound.sampled.*; /** * This class periodically broadcasts its host computer's * room information via 802.11. Concurrently with this broadcast, an * ultrasound pulse is emitted from the speakers. Any nearby mobile * clients listening for these broadcasts will hear the 802.11 room * information and the ultrasound pulse, thus correctly identifying its * location. *
* The ultrasound pulse comes from a WAV file whose name is hard-coded * into this class as a constant, and the room information to transmit is * passed into this class's constructor. * * @author Tony Offer * @author Chris Palistrant * @version 1.0 */ public class Broadcaster extends Thread implements LineListener { /***** Instance and Class Variables *****/ // Socket for communicating using datagrams. Datagrams are used for // this application because it is not desirable that the server beacon // knows about the mobile clients it is communicating with. The server // beacon should just send out locationing messages and the nearby // clients should either receive these messages or not. private MulticastSocket socket; // Boolean dictates whether or not this thread should keep running. private boolean keepRunning; // Buffer for holding the outgoing message. private byte [] buf; // Audio input stream for reading data from a sound file. private AudioInputStream audioStream; // This Clip is a DataLine object through which audio data from the // audio input stream travel to get to the system's speakers. private Clip audioClip; /**** TESTING ****/ private int counter; /*- Constants -*/ // The name of the WAV file that contains the ultrasound pulse. public static final String WAVEFILE = "ultrasound.wav"; // Broadcast address //public static final String GROUPADDR = "239.255.255.254"; public static final String GROUPADDR = "235.255.255.254"; //public static final String BCASTADDR = "255.255.255.255"; //public static final String BCASTADDR = "10.0.1.60"; // Arbitrary port number. Mobile clients should listen on this port. public static final int PORTNUM = 8019; // Default message to broadcast if non is supplied. public static final String DEFAULTMSG = "Unidentified Room"; // Minimum amount of time (in milliseconds) to wait before re- // broadcasting. public int MINTIME = 2500; // Maximum amount of time (in milliseconds) to wait before re- // broadcasting. public int MAXTIME = 7500; /***** Methods *****/ /** * This is a default constructor that is provided in the case that * an array of bytes containing room-identifying information is not * provided. By default, the message "Unidentified Room" will be * broadcasted. */ public Broadcaster() { // Convert default string message to byte array and send to main // constructor. this(DEFAULTMSG.getBytes()); } /** * This is the main class constructor. It takes an array of bytes to be * saved in an instance variable for later broadcast. Since this class * is a thread, the broadcasting will not begin until the start() method * is called. This constructor establishes both the datagram * communication system and the audio playback system. * * @param msg Array of bytes containing room-identifying information */ public Broadcaster(byte [] msg) { super("BroadcasterThread"); // Initialize everything related to the communication system. initComm(msg); // Initialize everything related to the playback of audio. initSound(); // Keep this thread going until something tells it to stop. keepRunning = true; /**** TESTING ****/ //counter = 0; } /** * This is where all the thread activity occurs in a continous loop. * During each loop iteration, an 802.11 message is broadcasted with * the room-identifying information stored in 'buf' and an ultrasound * pulse is generated by playing the wave file 'WAVEFILE.' At the end * of each loop iteration, the thread sleeps for MINTIME to MAXTIME * milliseconds. */ public void run() { // This loop runs forever until an IO exception occurs. In theory, // the computer on which this Broadcaster thread is running is the // server beacon in an indoor positioning system, so this thread // should never die. boolean alternating = true; /*** TESTING ***/ while (keepRunning) { try { // Put the broadcast address into an appropriate form // for sockets. InetAddress group = null; try { group = InetAddress.getByName(GROUPADDR); //socket.joinGroup(group); } catch (UnknownHostException uhe) { uhe.printStackTrace(); System.out.println("Unknown Host Exception"); } catch (IOException ioe) { ioe.printStackTrace(); System.out.println("IO Exception"); } /**** TESTING ****/ /* String dumbthing = DEFAULTMSG + " " + counter; buf = dumbthing.getBytes(); counter++; */ // Create a new packet to be broadcasted. DatagramPacket packet = new DatagramPacket(buf, buf.length, group, PORTNUM); /*** TESTING ***/ if (alternating) System.out.println("DEBUG: Sending " + new String(buf)); else System.out.println("DEBUG: Sending " + new String(buf)); alternating = !alternating; // Send the packet. socket.send(packet); // Play the sound file specified by WAVEFILE. This is done // by starting the data line in the Clip object // 'audioClip'. When the clip is done playing, a stop // event will trigger the method 'update(...)' to stop and // rewind the clip. audioClip.start(); // Sleep for MINTIME to MAXTIME milliseconds before // going back up to the top of the loop. try { sleep( (long)(java.lang.Math.random()*(MAXTIME-MINTIME) + MINTIME) ); } catch (InterruptedException e) { System.out.println("Error: Broadcaster thread sleep " + "interrupted."); e.printStackTrace(); } } catch (IOException ioe) { ioe.printStackTrace(); keepRunning = false; } } // Done using this socket. Broadcaster thread is done. Close // this socket. socket.close(); // Done playing audio. The audio clip should be closed. audioClip.close(); } /** * This method is used by external classes running this Broadcaster * thread. The purpose of this method is to terminate the thread by * setting the internal 'keepRunning' boolean to false so that the * thread's run() method breaks out of the while loop and gracefully * shuts down all audio and communication systems. */ public void terminate() { keepRunning = false; } /** * This method performs all the necessary initialization for datagram * communication. It should be called from the Broadcaster thread's * constructor. This method will ensure that the size of 'msg' is not * too big. * * @param msg Array of bytes to broadcast */ private void initComm(byte [] msg) { // Establish a new datagram socket. try { socket = new MulticastSocket(); // Set the time-to-live high enough so that messages aren't // discarded right away. socket.setTimeToLive(40); } catch (SocketException se) { se.printStackTrace(); } catch (SecurityException se) { se.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } // Check to make sure that 'msg' is not too large for the send // buffer. int sendBufferSize = 0; try { sendBufferSize = socket.getSendBufferSize(); } catch (SocketException se) { se.printStackTrace(); } if (msg.length > sendBufferSize) { System.out.println("Error: Message " + new String(msg) + " is too large for send buffer."); System.exit(0); } buf = msg; /**** TESTING ****/ /*try { System.out.println("Broadcasting: " + socket.getBroadcast()); } catch (SocketException se) { se.printStackTrace(); }*/ // Make sure to enable broadcasting on this socket. /*try { socket.setBroadcast(true); } catch (SocketException se) { se.printStackTrace(); }*/ } /** * This method initializes everything related to the playback of the * sound file specified by WAVEFILE. It should be called by the * Broadcaster thread's constructor. After this method returns, * the system's sound card should be ready to receive an audio * stream and send it to the system's speakers. */ private void initSound() { File soundFile = new File(WAVEFILE); // First make sure that the configuration file exists and is // readable. boolean fileError = false; if (!soundFile.exists()) { System.out.println("Error: Wave file " + WAVEFILE + " does not exist."); fileError = true; } if (!soundFile.canRead()) { System.out.println("Error: Could not read wave file " + WAVEFILE + "."); fileError = true; } if (fileError) System.exit(0); // Read the wave file into an audio stream for byte access. audioStream = null; try { audioStream = AudioSystem.getAudioInputStream(soundFile); } catch (Exception e) { e.printStackTrace(); System.exit(1); } // Get information from the sound file about its sampling frequency, // number of channels, sample size, etc. AudioFormat audioFormat = audioStream.getFormat(); // Establish a clip with the given audio format and open it. Also // add this Broadcaster thread as a line listener to the clip so // that this thread can listen for STOP events on the clip. audioClip = null; DataLine.Info info = new DataLine.Info(Clip.class, audioFormat); try { audioClip = (Clip) AudioSystem.getLine(info); audioClip.addLineListener(this); audioClip.open(audioStream); } catch (LineUnavailableException lue) { lue.printStackTrace(); System.exit(1); } catch (IOException ioe) { ioe.printStackTrace(); System.exit(1); } catch (IllegalStateException ise) { ise.printStackTrace(); System.exit(1); } catch (Exception e) { e.printStackTrace(); System.exit(1); } } /** * This method is called by the 'audioClip' data line whenever an event * occurs. In this Broadcaster thread class, the only event of * interest is the STOP event, which will occur when the clip has * finished going through all of its audio frames. When the stop * event occurs, this method will "rewind" the clip to the beginning * by setting the frame position to 0. * * @param event The line event that has occured in the clip object */ public void update(LineEvent event) { if (event.getType().equals(LineEvent.Type.STOP)) { // First stop the clip, then rewind it so that it // can be re-played as needed. audioClip.stop(); audioClip.setFramePosition(0); } } }