package net.reliableresponse.notification.sip; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Enumeration; import java.util.Vector; import javax.media.Buffer; import javax.media.DataSink; import javax.media.IncompatibleSourceException; import javax.media.MediaLocator; import javax.media.datasink.DataSinkErrorEvent; import javax.media.datasink.DataSinkEvent; import javax.media.datasink.DataSinkListener; import javax.media.datasink.EndOfStreamEvent; import javax.media.protocol.BufferTransferHandler; import javax.media.protocol.DataSource; import javax.media.protocol.PullBufferDataSource; import javax.media.protocol.PullBufferStream; import javax.media.protocol.PushBufferDataSource; import javax.media.protocol.PushBufferStream; import javax.media.protocol.SourceStream; import net.reliableresponse.notification.broker.BrokerFactory; /** * This SilenceDataSink class reads from a DataSource and checks * for periods of low volume, representing background noises */ public class SilenceDataSink implements DataSink, BufferTransferHandler { // Silence related vars long minimumSilenceLength = 800; int minimumSilenceThreshold = BrokerFactory.getConfigurationBroker().getIntValue("sip.silencethreshold", 10); int lastSample = -1; int silenceLength = 0; long currentIndex = 0; long silenceStartIndex = 0; boolean inSilence = true; boolean pickedUp = false; boolean pickedUpFirstPass = true; DataSource source; PullBufferStream pullStrms[] = null; PushBufferStream pushStrms[] = null; // Data sink listeners. private Vector listeners = new Vector(1); // Stored all the streams that are not yet finished (i.e. EOM // has not been received. SourceStream unfinishedStrms[] = null; // Loop threads to pull data from a PullBufferDataSource. // There is one thread per each PullSourceStream. Loop loops[] = null; Buffer readBuffer; Vector silenceListeners; Vector pickupListeners; FileOutputStream out; public SilenceDataSink() { silenceListeners = new Vector(); pickupListeners = new Vector(); try { out = new FileOutputStream("/tmp/foo.pcm"); } catch (FileNotFoundException e) { BrokerFactory.getLoggingBroker().logError(e); } } /** * Sets the media source this <code>MediaHandler</code> * should use to obtain content. */ public void setSource(DataSource source) throws IncompatibleSourceException { // Different types of DataSources need to handled differently. if (source instanceof PushBufferDataSource) { pushStrms = ((PushBufferDataSource) source).getStreams(); unfinishedStrms = new SourceStream[pushStrms.length]; // Set the transfer handler to receive pushed data from // the push DataSource. for (int i = 0; i < pushStrms.length; i++) { pushStrms[i].setTransferHandler(this); unfinishedStrms[i] = pushStrms[i]; } } else if (source instanceof PullBufferDataSource) { pullStrms = ((PullBufferDataSource) source).getStreams(); unfinishedStrms = new SourceStream[pullStrms.length]; // For pull data sources, we'll start a thread per // stream to pull data from the source. loops = new Loop[pullStrms.length]; for (int i = 0; i < pullStrms.length; i++) { loops[i] = new Loop(this, pullStrms[i]); unfinishedStrms[i] = pullStrms[i]; } } else { // This handler only handles push or pull buffer datasource. throw new IncompatibleSourceException(); } this.source = source; readBuffer = new Buffer(); } /** * For completeness, DataSink's require this method. * But we don't need it. */ public void setOutputLocator(MediaLocator ml) { } public MediaLocator getOutputLocator() { return null; } public String getContentType() { return source.getContentType(); } /** * Our DataSink does not need to be opened. */ public void open() { } public void start() { try { source.start(); } catch (IOException e) { System.err.println(e); } // Start the processing loop if we are dealing with a // PullBufferDataSource. if (loops != null) { for (int i = 0; i < loops.length; i++) loops[i].restart(); } } public void stop() { try { source.stop(); } catch (IOException e) { System.err.println(e); } // Start the processing loop if we are dealing with a // PullBufferDataSource. if (loops != null) { for (int i = 0; i < loops.length; i++) loops[i].pause(); } } public void close() { stop(); if (loops != null) { for (int i = 0; i < loops.length; i++) loops[i].kill(); } } public void addDataSinkListener(DataSinkListener dsl) { if (dsl != null) if (!listeners.contains(dsl)) listeners.addElement(dsl); } public void removeDataSinkListener(DataSinkListener dsl) { if (dsl != null) listeners.removeElement(dsl); } protected void sendEvent(DataSinkEvent event) { if (!listeners.isEmpty()) { synchronized (listeners) { Enumeration list = listeners.elements(); while (list.hasMoreElements()) { DataSinkListener listener = (DataSinkListener) list .nextElement(); listener.dataSinkUpdate(event); } } } } /** * This will get called when there's data pushed from the * PushBufferDataSource. */ public void transferData(PushBufferStream stream) { try { stream.read(readBuffer); } catch (IOException e) { System.err.println(e); sendEvent(new DataSinkErrorEvent(this, e.getMessage())); return; } printDataInfo(readBuffer); // Check to see if we are done with all the streams. if (readBuffer.isEOM() && checkDone(stream)) { sendEvent(new EndOfStreamEvent(this)); } } /** * This is called from the Loop thread to pull data from * the PullBufferStream. */ public boolean readPullData(PullBufferStream stream) { try { stream.read(readBuffer); } catch (IOException e) { System.err.println(e); return true; } printDataInfo(readBuffer); if (readBuffer.isEOM()) { // Check to see if we are done with all the streams. if (checkDone(stream)) { System.err.println("All done!"); close(); } return true; } return false; } /** * Check to see if all the streams are processed. */ public boolean checkDone(SourceStream strm) { boolean done = true; for (int i = 0; i < unfinishedStrms.length; i++) { if (strm == unfinishedStrms[i]) unfinishedStrms[i] = null; else if (unfinishedStrms[i] != null) { // There's at least one stream that's not done. done = false; } } return done; } private void fireSilenceChange (boolean inSilence) { BrokerFactory.getLoggingBroker().logDebug("Silence "+(inSilence?"Start":"End")+": "+currentIndex); for (int i = 0; i < silenceListeners.size(); i++) { SilenceListener listener = (SilenceListener)silenceListeners.elementAt(i); if (inSilence) { listener.handleSilenceStart(); } else { listener.handleSilenceEnd(); } } if (inSilence) { if (!pickedUp) { long totalLength = currentIndex - silenceStartIndex; // BrokerFactory.getLoggingBroker().logDebug("silence start = "+silenceStartIndex); // BrokerFactory.getLoggingBroker().logDebug("current index = "+currentIndex); // BrokerFactory.getLoggingBroker().logDebug("total length = "+totalLength); if ((totalLength <14000) || (totalLength >18000)) { if (!pickedUpFirstPass) { pickedUpFirstPass = true; } else { pickedUp = true; firePickup(); } } } } silenceStartIndex = currentIndex; } private void firePickup () { BrokerFactory.getLoggingBroker().logDebug("PICKED UP!"); for (int i = 0; i < pickupListeners.size(); i++) { PickupListener listener = (PickupListener)pickupListeners.elementAt(i); listener.handlePickup(); } } void printDataInfo(Buffer buffer) { byte[] data = (byte[])buffer.getData(); try { out.write (data, buffer.getOffset(), buffer.getLength()); } catch (IOException e) { BrokerFactory.getLoggingBroker().logError(e); } for (int i = buffer.getOffset(); i < buffer.getLength(); i++) { int n = data[i]; if (n < 0) n+=256; if (lastSample == -1) lastSample = n; boolean thisMove = Math.abs(n-lastSample) > minimumSilenceThreshold; lastSample = n; if (inSilence && thisMove) { if (inSilence) { inSilence = false; fireSilenceChange(inSilence); } } if ((!inSilence) && (thisMove)) { silenceLength = 0; } if ((!inSilence) && (!thisMove)) { silenceLength ++; if (silenceLength > minimumSilenceLength) { inSilence = true; fireSilenceChange(inSilence); } } currentIndex++; } if (buffer.isEOM()) System.err.println(" Got EOM!"); } public Object[] getControls() { return new Object[0]; } public Object getControl(String name) { return null; } public void addSilenceListener(SilenceListener silenceListener) { silenceListeners.addElement(silenceListener); } public void addPickupListener(PickupListener pickupListener) { pickupListeners.addElement(pickupListener); } } /** * A thread class to implement a processing loop. * This loop reads data from a PullBufferDataSource. */ class Loop extends Thread { SilenceDataSink handler; PullBufferStream stream; boolean paused = true; boolean killed = false; public Loop(SilenceDataSink handler, PullBufferStream stream) { this.handler = handler; this.stream = stream; start(); } public synchronized void restart() { paused = false; notify(); } /** * This is the correct way to pause a thread; unlike suspend. */ public synchronized void pause() { paused = true; } /** * This is the correct way to kill a thread; unlike stop. */ public synchronized void kill() { killed = true; notify(); } /** * This is the processing loop to pull data from a * PullBufferDataSource. */ public void run() { while (!killed) { try { while (paused && !killed) { wait(); } } catch (InterruptedException e) { } if (!killed) { boolean done = handler.readPullData(stream); if (done) pause(); } } } }