From fad4b65f6f71cc31b1fab262e3c85b96bb00b2bf Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov <lyubomir.marinov@jitsi.org> Date: Mon, 3 Jun 2013 13:30:57 +0300 Subject: [PATCH] Improves AudioMixer for one-to-one calls, strives towards pass-through behavior. --- src/org/jitsi/impl/neomedia/ArrayIOUtils.java | 85 +- .../codec/audio/speex/SpeexResampler.java | 23 +- .../impl/neomedia/conference/AudioMixer.java | 778 ++++++------ .../AudioMixerPushBufferStream.java | 1129 ++++++++--------- .../AudioMixingPushBufferDataSource.java | 202 ++- .../AudioMixingPushBufferStream.java | 296 ++--- .../neomedia/conference/DataSourceFilter.java | 3 +- ...aSourceDesc.java => InDataSourceDesc.java} | 157 ++- ...InputStreamDesc.java => InStreamDesc.java} | 42 +- .../neomedia/conference/IntArrayCache.java | 96 ++ .../neomedia/device/AudioMediaDeviceImpl.java | 4 +- .../device/AudioMixerMediaDevice.java | 10 +- .../neomedia/device/MediaDeviceSession.java | 2 - .../service/neomedia/DTMFInbandTone.java | 215 ++-- 14 files changed, 1507 insertions(+), 1535 deletions(-) rename src/org/jitsi/impl/neomedia/conference/{InputDataSourceDesc.java => InDataSourceDesc.java} (71%) rename src/org/jitsi/impl/neomedia/conference/{InputStreamDesc.java => InStreamDesc.java} (78%) create mode 100644 src/org/jitsi/impl/neomedia/conference/IntArrayCache.java diff --git a/src/org/jitsi/impl/neomedia/ArrayIOUtils.java b/src/org/jitsi/impl/neomedia/ArrayIOUtils.java index dceda9da..1416e58b 100644 --- a/src/org/jitsi/impl/neomedia/ArrayIOUtils.java +++ b/src/org/jitsi/impl/neomedia/ArrayIOUtils.java @@ -15,6 +15,25 @@ public class ArrayIOUtils { + /** + * Reads an integer from a specific series of bytes starting the reading at + * a specific offset in it. + * + * @param in the series of bytes to read an integer from + * @param inOffset the offset in <tt>in</tt> at which the reading of the + * integer is to start + * @return an integer read from the specified series of bytes starting at + * the specified offset in it + */ + public static int readInt(byte[] in, int inOffset) + { + return + (in[inOffset + 3] << 24) + | ((in[inOffset + 2] & 0xFF) << 16) + | ((in[inOffset + 1] & 0xFF) << 8) + | (in[inOffset] & 0xFF); + } + /** * Reads a short integer from a specific series of bytes starting the * reading at a specific offset in it. The difference with @@ -22,33 +41,49 @@ public class ArrayIOUtils * <tt>int</tt> which has been formed by reading two bytes, not a * <tt>short</tt>. * - * @param input the series of bytes to read the short integer from - * @param inputOffset the offset in <tt>input</tt> at which the reading of - * the short integer is to start + * @param in the series of bytes to read the short integer from + * @param inOffset the offset in <tt>in</tt> at which the reading of the + * short integer is to start * @return a short integer in the form of <tt>int</tt> read from the * specified series of bytes starting at the specified offset in it */ - public static int readInt16(byte[] input, int inputOffset) + public static int readInt16(byte[] in, int inOffset) { - return ((input[inputOffset + 1] << 8) | (input[inputOffset] & 0x00FF)); + return ((in[inOffset + 1] << 8) | (in[inOffset] & 0x00FF)); } /** * Reads a short integer from a specific series of bytes starting the * reading at a specific offset in it. * - * @param input the series of bytes to read the short integer from - * @param inputOffset the offset in <tt>input</tt> at which the reading of - * the short integer is to start + * @param in the series of bytes to read the short integer from + * @param inOffset the offset in <tt>in</tt> at which the reading of the + * short integer is to start * @return a short integer in the form of <tt>short</tt> read from the * specified series of bytes starting at the specified offset in it */ - public static short readShort(byte[] input, int inputOffset) + public static short readShort(byte[] in, int inOffset) { - return - (short) - ((input[inputOffset + 1] << 8) - | (input[inputOffset] & 0x00FF)); + return (short) ((in[inOffset + 1] << 8) | (in[inOffset] & 0x00FF)); + } + + /** + * Converts an integer to a series of bytes and writes the result into a + * specific output array of bytes starting the writing at a specific offset + * in it. + * + * @param in the integer to be written out as a series of bytes + * @param out the output to receive the conversion of the specified + * integer to a series of bytes + * @param outOffset the offset in <tt>out</tt> at which the writing of the + * result of the conversion is to be started + */ + public static void writeInt(int in, byte[] out, int outOffset) + { + out[outOffset] = (byte) (in & 0xFF); + out[outOffset + 1] = (byte) ((in >>> 8) & 0xFF); + out[outOffset + 2] = (byte) ((in >>> 16) & 0xFF); + out[outOffset + 3] = (byte) (in >> 24); } /** @@ -58,18 +93,18 @@ public static short readShort(byte[] input, int inputOffset) * is that the input is an <tt>int</tt> and just two bytes of it are * written. * - * @param input the short integer to be written out as a series of bytes + * @param in the short integer to be written out as a series of bytes * specified as an integer i.e. the value to be converted is contained in * only two of the four bytes made available by the integer - * @param output the output to receive the conversion of the specified short + * @param out the output to receive the conversion of the specified short * integer to a series of bytes - * @param outputOffset the offset in <tt>output</tt> at which the writing of - * the result of the conversion is to be started + * @param outOffset the offset in <tt>out</tt> at which the writing of the + * result of the conversion is to be started */ - public static void writeInt16(int input, byte[] output, int outputOffset) + public static void writeInt16(int in, byte[] out, int outOffset) { - output[outputOffset] = (byte) (input & 0xFF); - output[outputOffset + 1] = (byte) (input >> 8); + out[outOffset] = (byte) (in & 0xFF); + out[outOffset + 1] = (byte) (in >> 8); } /** @@ -77,15 +112,15 @@ public static void writeInt16(int input, byte[] output, int outputOffset) * a specific output array of bytes starting the writing at a specific * offset in it. * - * @param input the short integer to be written out as a series of bytes + * @param in the short integer to be written out as a series of bytes * specified as <tt>short</tt> - * @param output the output to receive the conversion of the specified short + * @param out the output to receive the conversion of the specified short * integer to a series of bytes - * @param outputOffset the offset in <tt>output</tt> at which the writing of + * @param outOffset the offset in <tt>out</tt> at which the writing of * the result of the conversion is to be started */ - public static void writeShort(short input, byte[] output, int outputOffset) + public static void writeShort(short in, byte[] out, int outOffset) { - writeInt16(input, output, outputOffset); + writeInt16(in, out, outOffset); } } diff --git a/src/org/jitsi/impl/neomedia/codec/audio/speex/SpeexResampler.java b/src/org/jitsi/impl/neomedia/codec/audio/speex/SpeexResampler.java index 75c6c3c4..d9cae165 100644 --- a/src/org/jitsi/impl/neomedia/codec/audio/speex/SpeexResampler.java +++ b/src/org/jitsi/impl/neomedia/codec/audio/speex/SpeexResampler.java @@ -326,7 +326,7 @@ protected int doProcess(Buffer inBuffer, Buffer outBuffer) if (resampler == 0) return BUFFER_PROCESSED_FAILED; - byte[] input = (byte[]) inBuffer.getData(); + byte[] in = (byte[]) inBuffer.getData(); int inLength = inBuffer.getLength(); int frameSize = channels * (inAudioFormat.getSampleSizeInBits() / 8); @@ -337,17 +337,26 @@ protected int doProcess(Buffer inBuffer, Buffer outBuffer) */ int inSampleCount = inLength / frameSize; int outSampleCount = (inSampleCount * outSampleRate) / inSampleRate; - byte[] output + byte[] out = validateByteArraySize( outBuffer, outSampleCount * frameSize, false); - outSampleCount - = Speex.speex_resampler_process_interleaved_int( - resampler, - input, inBuffer.getOffset(), inSampleCount, - output, 0, outSampleCount); + /* + * XXX The method Speex.speex_resampler_process_interleaved_int will + * crash if in is null. + */ + if (inSampleCount == 0) + outSampleCount = 0; + else + { + outSampleCount + = Speex.speex_resampler_process_interleaved_int( + resampler, + in, inBuffer.getOffset(), inSampleCount, + out, 0, outSampleCount); + } outBuffer.setFormat(outAudioFormat); outBuffer.setLength(outSampleCount * frameSize); outBuffer.setOffset(0); diff --git a/src/org/jitsi/impl/neomedia/conference/AudioMixer.java b/src/org/jitsi/impl/neomedia/conference/AudioMixer.java index b8f073cb..0ccdef4d 100644 --- a/src/org/jitsi/impl/neomedia/conference/AudioMixer.java +++ b/src/org/jitsi/impl/neomedia/conference/AudioMixer.java @@ -21,7 +21,6 @@ import org.jitsi.impl.neomedia.device.*; import org.jitsi.impl.neomedia.protocol.*; import org.jitsi.util.*; -// disambiguation /** * Represents an audio mixer which manages the mixing of multiple audio streams @@ -29,7 +28,7 @@ * multiple input audio streams. * <p> * The input audio streams are provided to the <tt>AudioMixer</tt> through - * {@link #addInputDataSource(DataSource)} in the form of input + * {@link #addInDataSource(DataSource)} in the form of input * <tt>DataSource</tt>s giving access to one or more input * <tt>SourceStreams</tt>. * </p> @@ -38,22 +37,16 @@ * streams is provided by the <tt>AudioMixer</tt> in the form of a * <tt>AudioMixingPushBufferDataSource</tt> giving access to a * <tt>AudioMixingPushBufferStream</tt>. Such an output is obtained through - * {@link #createOutputDataSource()}. The <tt>AudioMixer</tt> is able to provide + * {@link #createOutDataSource()}. The <tt>AudioMixer</tt> is able to provide * multiple output audio streams at one and the same time, though, each of them * containing the mix of a subset of the input audio streams. * </p> * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public class AudioMixer { - /** - * The <tt>Logger</tt> used by the <tt>AudioMixer</tt> class and its - * instances for logging output. - */ - private static final Logger logger = Logger.getLogger(AudioMixer.class); - /** * The default output <tt>AudioFormat</tt> in which <tt>AudioMixer</tt>, * <tt>AudioMixingPushBufferDataSource</tt> and @@ -68,6 +61,48 @@ public class AudioMixer AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED); + /** + * The <tt>Logger</tt> used by the <tt>AudioMixer</tt> class and its + * instances for logging output. + */ + private static final Logger logger = Logger.getLogger(AudioMixer.class); + + /** + * Gets the <tt>Format</tt> in which a specific <tt>DataSource</tt> + * provides stream data. + * + * @param dataSource the <tt>DataSource</tt> for which the <tt>Format</tt> + * in which it provides stream data is to be determined + * @return the <tt>Format</tt> in which the specified <tt>dataSource</tt> + * provides stream data if it was determined; otherwise, <tt>null</tt> + */ + private static Format getFormat(DataSource dataSource) + { + FormatControl formatControl + = (FormatControl) + dataSource.getControl(FormatControl.class.getName()); + + return (formatControl == null) ? null : formatControl.getFormat(); + } + + /** + * Gets the <tt>Format</tt> in which a specific <tt>SourceStream</tt> + * provides data. + * + * @param stream the <tt>SourceStream</tt> for which the <tt>Format</tt> in + * which it provides data is to be determined + * @return the <tt>Format</tt> in which the specified <tt>SourceStream</tt> + * provides data if it was determined; otherwise, <tt>null</tt> + */ + private static Format getFormat(SourceStream stream) + { + if (stream instanceof PushBufferStream) + return ((PushBufferStream) stream).getFormat(); + if (stream instanceof PullBufferStream) + return ((PullBufferStream) stream).getFormat(); + return null; + } + /** * The <tt>BufferControl</tt> of this instance and, respectively, its * <tt>AudioMixingPushBufferDataSource</tt>s. @@ -97,15 +132,28 @@ public class AudioMixer * The collection of input <tt>DataSource</tt>s this instance reads audio * data from. */ - private final List<InputDataSourceDesc> inputDataSources - = new ArrayList<InputDataSourceDesc>(); + private final List<InDataSourceDesc> inDataSources + = new ArrayList<InDataSourceDesc>(); + + /** + * The cache of <tt>int</tt> arrays utilized by this instance for the + * purposes of reducing garbage collection. + */ + final IntArrayCache intArrayCache = new IntArrayCache(); /** * The <tt>AudioMixingPushBufferDataSource</tt> which contains the mix of - * <tt>inputDataSources</tt> excluding <tt>captureDevice</tt> and is thus + * <tt>inDataSources</tt> excluding <tt>captureDevice</tt> and is thus * meant for playback on the local peer in a call. */ - private final AudioMixingPushBufferDataSource localOutputDataSource; + private final AudioMixingPushBufferDataSource localOutDataSource; + + /** + * The output <tt>AudioMixerPushBufferStream</tt> through which this + * instance pushes audio sample data to + * <tt>AudioMixingPushBufferStream</tt>s to be mixed. + */ + private AudioMixerPushBufferStream outStream; /** * The number of output <tt>AudioMixingPushBufferDataSource</tt>s reading @@ -115,13 +163,6 @@ public class AudioMixer */ private int started; - /** - * The output <tt>AudioMixerPushBufferStream</tt> through which this - * instance pushes audio sample data to - * <tt>AudioMixingPushBufferStream</tt>s to be mixed. - */ - private AudioMixerPushBufferStream outputStream; - /** * Initializes a new <tt>AudioMixer</tt> instance. Because JMF's * <tt>Manager.createMergingDataSource(DataSource[])</tt> requires the @@ -162,10 +203,10 @@ public AudioMixer(CaptureDevice captureDevice) this.captureDevice = captureDevice; - this.localOutputDataSource = createOutputDataSource(); - addInputDataSource( - (DataSource) this.captureDevice, - this.localOutputDataSource); + this.localOutDataSource = createOutDataSource(); + addInDataSource( + (DataSource) this.captureDevice, + this.localOutDataSource); } /** @@ -174,12 +215,12 @@ public AudioMixer(CaptureDevice captureDevice) * specified <tt>DataSource</tt> indeed provides audio, the respective * contributions to the mix are always included. * - * @param inputDataSource a new <tt>DataSource</tt> to input audio to this + * @param inDataSource a new <tt>DataSource</tt> to input audio to this * instance */ - public void addInputDataSource(DataSource inputDataSource) + public void addInDataSource(DataSource inDataSource) { - addInputDataSource(inputDataSource, null); + addInDataSource(inDataSource, null); } /** @@ -189,30 +230,30 @@ public void addInputDataSource(DataSource inputDataSource) * contributions to the mix will be excluded from the mix output provided * through a specific <tt>AudioMixingPushBufferDataSource</tt>. * - * @param inputDataSource a new <tt>DataSource</tt> to input audio to this + * @param inDataSource a new <tt>DataSource</tt> to input audio to this * instance - * @param outputDataSource the <tt>AudioMixingPushBufferDataSource</tt> to - * not include the audio contributions of <tt>inputDataSource</tt> in the + * @param outDataSource the <tt>AudioMixingPushBufferDataSource</tt> to + * not include the audio contributions of <tt>inDataSource</tt> in the * mix it outputs */ - void addInputDataSource( - DataSource inputDataSource, - AudioMixingPushBufferDataSource outputDataSource) + void addInDataSource( + DataSource inDataSource, + AudioMixingPushBufferDataSource outDataSource) { - if (inputDataSource == null) - throw new NullPointerException("inputDataSource"); + if (inDataSource == null) + throw new NullPointerException("inDataSource"); - synchronized (inputDataSources) + synchronized (inDataSources) { - for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) - if (inputDataSource.equals(inputDataSourceDesc.inputDataSource)) - throw new IllegalArgumentException("inputDataSource"); + for (InDataSourceDesc inDataSourceDesc : inDataSources) + if (inDataSource.equals(inDataSourceDesc.inDataSource)) + throw new IllegalArgumentException("inDataSource"); - InputDataSourceDesc inputDataSourceDesc - = new InputDataSourceDesc( - inputDataSource, - outputDataSource); - boolean added = inputDataSources.add(inputDataSourceDesc); + InDataSourceDesc inDataSourceDesc + = new InDataSourceDesc( + inDataSource, + outDataSource); + boolean added = inDataSources.add(inDataSourceDesc); if (added) { @@ -220,18 +261,18 @@ void addInputDataSource( { logger.trace( "Added input DataSource with hashCode " - + inputDataSource.hashCode()); + + inDataSource.hashCode()); } /* - * If the other inputDataSources have already been connected, + * If the other inDataSources have already been connected, * connect to the new one as well. */ if (connected > 0) { try { - inputDataSourceDesc.connect(this); + inDataSourceDesc.connect(this); } catch (IOException ioex) { @@ -239,19 +280,19 @@ void addInputDataSource( } } - // Update outputStream with any new inputStreams. - if (outputStream != null) - getOutputStream(); + // Update outStream with any new inStreams. + if (outStream != null) + getOutStream(); /* - * If the other inputDataSources have been started, start the + * If the other inDataSources have been started, start the * new one as well. */ if (started > 0) { try { - inputDataSourceDesc.start(); + inDataSourceDesc.start(); } catch (IOException ioe) { @@ -275,24 +316,21 @@ void addInputDataSource( void connect() throws IOException { - synchronized (inputDataSources) + synchronized (inDataSources) { if (connected == 0) { - for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) + for (InDataSourceDesc inDataSourceDesc : inDataSources) try { - inputDataSourceDesc.connect(this); + inDataSourceDesc.connect(this); } catch (IOException ioe) { - logger - .error( - "Failed to connect to inputDataSource " - + MediaStreamImpl - .toString( - inputDataSourceDesc - .inputDataSource), + logger.error( + "Failed to connect to inDataSource " + + MediaStreamImpl.toString( + inDataSourceDesc.inDataSource), ioe); throw ioe; } @@ -304,8 +342,8 @@ void connect() * bufferLength may change so make sure that the bufferLengths * of the input streams are equal. */ - if (outputStream != null) - outputStream.equalizeInputStreamBufferLength(); + if (outStream != null) + outStream.equalizeInStreamBufferLength(); } connected++; @@ -321,12 +359,12 @@ void connect() * <tt>DataSource</tt> added to this instance. * * @param dataSource the <tt>DataSource</tt> to connect to - * @param inputDataSource the <tt>DataSource</tt> which is the cause for + * @param inDataSource the <tt>DataSource</tt> which is the cause for * <tt>dataSource</tt> to exist in this <tt>AudioMixer</tt> * @throws IOException if anything wrong happens while connecting to * <tt>dataSource</tt> */ - protected void connect(DataSource dataSource, DataSource inputDataSource) + protected void connect(DataSource dataSource, DataSource inDataSource) throws IOException { dataSource.connect(); @@ -339,44 +377,44 @@ protected void connect(DataSource dataSource, DataSource inputDataSource) * in a separate thread as are, for example, input <tt>DataSource</tt>s * which are being transcoded. * - * @param inputDataSource the <tt>InputDataSourceDesc</tt> of the input + * @param inDataSource the <tt>InDataSourceDesc</tt> of the input * <tt>DataSource</tt> which has finished its connecting procedure * @throws IOException if anything wrong happens while including - * <tt>inputDataSource</tt> into the mix + * <tt>inDataSource</tt> into the mix */ - void connected(InputDataSourceDesc inputDataSource) + void connected(InDataSourceDesc inDataSource) throws IOException { - synchronized (inputDataSources) + synchronized (inDataSources) { - if (inputDataSources.contains(inputDataSource) + if (inDataSources.contains(inDataSource) && (connected > 0)) { if (started > 0) - inputDataSource.start(); - if (outputStream != null) - getOutputStream(); + inDataSource.start(); + if (outStream != null) + getOutStream(); } } } /** - * Creates a new <tt>InputStreamDesc</tt> instance which is to describe a + * Creates a new <tt>InStreamDesc</tt> instance which is to describe a * specific input <tt>SourceStream</tt> originating from a specific input - * <tt>DataSource</tt> given by its <tt>InputDataSourceDesc</tt>. + * <tt>DataSource</tt> given by its <tt>InDataSourceDesc</tt>. * - * @param inputStream the input <tt>SourceStream</tt> to be described by the + * @param inStream the input <tt>SourceStream</tt> to be described by the * new instance - * @param inputDataSourceDesc the input <tt>DataSource</tt> given by its - * <tt>InputDataSourceDesc</tt> to be described by the new instance - * @return a new <tt>InputStreamDesc</tt> instance which describes the + * @param inDataSourceDesc the input <tt>DataSource</tt> given by its + * <tt>InDataSourceDesc</tt> to be described by the new instance + * @return a new <tt>InStreamDesc</tt> instance which describes the * specified input <tt>SourceStream</tt> and <tt>DataSource</tt> */ - private InputStreamDesc createInputStreamDesc( - SourceStream inputStream, - InputDataSourceDesc inputDataSourceDesc) + private InStreamDesc createInStreamDesc( + SourceStream inStream, + InDataSourceDesc inDataSourceDesc) { - return new InputStreamDesc(inputStream, inputDataSourceDesc); + return new InStreamDesc(inStream, inDataSourceDesc); } /** @@ -393,7 +431,7 @@ private InputStreamDesc createInputStreamDesc( * to a single audio stream representing the mix of the audio streams input * into this <tt>AudioMixer</tt> through its input <tt>DataSource</tt>s */ - public AudioMixingPushBufferDataSource createOutputDataSource() + public AudioMixingPushBufferDataSource createOutDataSource() { return new AudioMixingPushBufferDataSource(this); } @@ -403,28 +441,28 @@ public AudioMixingPushBufferDataSource createOutputDataSource() * specific input <tt>DataSource</tt> into a specific output * <tt>Format</tt>. * - * @param inputDataSourceDesc the <tt>InputDataSourceDesc</tt> describing + * @param inDataSourceDesc the <tt>InDataSourceDesc</tt> describing * the input <tt>DataSource</tt> to be transcoded into the specified output * <tt>Format</tt> and to receive the transcoding <tt>DataSource</tt> - * @param outputFormat the <tt>Format</tt> in which the tracks of the input + * @param outFormat the <tt>Format</tt> in which the tracks of the input * <tt>DataSource</tt> are to be transcoded * @return <tt>true</tt> if a new transcoding <tt>DataSource</tt> has been * created for the input <tt>DataSource</tt> described by - * <tt>inputDataSourceDesc</tt>; otherwise, <tt>false</tt> + * <tt>inDataSourceDesc</tt>; otherwise, <tt>false</tt> * @throws IOException if an error occurs while creating the transcoding * <tt>DataSource</tt>, connecting to it or staring it */ private boolean createTranscodingDataSource( - InputDataSourceDesc inputDataSourceDesc, - Format outputFormat) + InDataSourceDesc inDataSourceDesc, + Format outFormat) throws IOException { - if (inputDataSourceDesc.createTranscodingDataSource(outputFormat)) + if (inDataSourceDesc.createTranscodingDataSource(outFormat)) { if (connected > 0) - inputDataSourceDesc.connect(this); + inDataSourceDesc.connect(this); if (started > 0) - inputDataSourceDesc.start(); + inDataSourceDesc.start(); return true; } else @@ -441,7 +479,7 @@ private boolean createTranscodingDataSource( */ void disconnect() { - synchronized (inputDataSources) + synchronized (inDataSources) { if (connected <= 0) return; @@ -450,16 +488,16 @@ void disconnect() if (connected == 0) { - for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) - inputDataSourceDesc.disconnect(); + for (InDataSourceDesc inDataSourceDesc : inDataSources) + inDataSourceDesc.disconnect(); /* - * XXX Make the outputStream to release the inputStreams. + * XXX Make the outStream to release the inStreams. * Otherwise, the PushBufferStream ones which have been wrapped * into CachingPushBufferStream may remaing waiting. */ - outputStream.setInputStreams(null); - outputStream = null; + outStream.setInStreams(null); + outStream = null; } } } @@ -479,8 +517,8 @@ BufferControl getBufferControl() { BufferControl captureDeviceBufferControl = (BufferControl) - ((Controls) captureDevice) - .getControl(BufferControl.class.getName()); + ((Controls) captureDevice).getControl( + BufferControl.class.getName()); if (captureDeviceBufferControl != null) bufferControl @@ -514,50 +552,6 @@ String getContentType() return ContentDescriptor.RAW; } - /** - * Gets an <tt>InputStreamDesc</tt> from a specific existing list of - * <tt>InputStreamDesc</tt>s which describes a specific - * <tt>SourceStream</tt>. If such an <tt>InputStreamDesc</tt> does not - * exist, returns <tt>null</tt>. - * - * @param inputStream the <tt>SourceStream</tt> to locate an - * <tt>InputStreamDesc</tt> for in <tt>existingInputStreamDescs</tt> - * @param existingInputStreamDescs the list of existing - * <tt>InputStreamDesc</tt>s in which an <tt>InputStreamDesc</tt> for - * <tt>inputStream</tt> is to be located - * @return an <tt>InputStreamDesc</tt> from - * <tt>existingInputStreamDescs</tt> which describes <tt>inputStream</tt> if - * such an <tt>InputStreamDesc</tt> exists; otherwise, <tt>null</tt> - */ - private InputStreamDesc getExistingInputStreamDesc( - SourceStream inputStream, - InputStreamDesc[] existingInputStreamDescs) - { - if (existingInputStreamDescs == null) - return null; - - for (InputStreamDesc existingInputStreamDesc - : existingInputStreamDescs) - { - SourceStream existingInputStream - = existingInputStreamDesc.getInputStream(); - - if (existingInputStream == inputStream) - return existingInputStreamDesc; - if ((existingInputStream instanceof BufferStreamAdapter<?>) - && (((BufferStreamAdapter<?>) existingInputStream) - .getStream() - == inputStream)) - return existingInputStreamDesc; - if ((existingInputStream instanceof CachingPushBufferStream) - && (((CachingPushBufferStream) existingInputStream) - .getStream() - == inputStream)) - return existingInputStreamDesc; - } - return null; - } - /** * Gets the duration of each one of the output streams produced by this * <tt>AudioMixer</tt>. @@ -571,41 +565,44 @@ Time getDuration() } /** - * Gets the <tt>Format</tt> in which a specific <tt>DataSource</tt> - * provides stream data. + * Gets an <tt>InStreamDesc</tt> from a specific existing list of + * <tt>InStreamDesc</tt>s which describes a specific + * <tt>SourceStream</tt>. If such an <tt>InStreamDesc</tt> does not + * exist, returns <tt>null</tt>. * - * @param dataSource the <tt>DataSource</tt> for which the <tt>Format</tt> - * in which it provides stream data is to be determined - * @return the <tt>Format</tt> in which the specified <tt>dataSource</tt> - * provides stream data if it was determined; otherwise, <tt>null</tt> + * @param inStream the <tt>SourceStream</tt> to locate an + * <tt>InStreamDesc</tt> for in <tt>existingInStreamDescs</tt> + * @param existingInStreamDescs the list of existing + * <tt>InStreamDesc</tt>s in which an <tt>InStreamDesc</tt> for + * <tt>inStream</tt> is to be located + * @return an <tt>InStreamDesc</tt> from + * <tt>existingInStreamDescs</tt> which describes <tt>inStream</tt> if + * such an <tt>InStreamDesc</tt> exists; otherwise, <tt>null</tt> */ - private static Format getFormat(DataSource dataSource) + private InStreamDesc getExistingInStreamDesc( + SourceStream inStream, + InStreamDesc[] existingInStreamDescs) { - FormatControl formatControl - = (FormatControl) dataSource.getControl( - FormatControl.class.getName()); - - return (formatControl == null) ? null : formatControl.getFormat(); - } + if (existingInStreamDescs == null) + return null; - /** - * Gets the <tt>Format</tt> in which a specific - * <tt>SourceStream</tt> provides data. - * - * @param stream - * the <tt>SourceStream</tt> for which the - * <tt>Format</tt> in which it provides data is to be - * determined - * @return the <tt>Format</tt> in which the specified - * <tt>SourceStream</tt> provides data if it was determined; - * otherwise, <tt>null</tt> - */ - private static Format getFormat(SourceStream stream) - { - if (stream instanceof PushBufferStream) - return ((PushBufferStream) stream).getFormat(); - if (stream instanceof PullBufferStream) - return ((PullBufferStream) stream).getFormat(); + for (InStreamDesc existingInStreamDesc + : existingInStreamDescs) + { + SourceStream existingInStream + = existingInStreamDesc.getInStream(); + + if (existingInStream == inStream) + return existingInStreamDesc; + if ((existingInStream instanceof BufferStreamAdapter<?>) + && (((BufferStreamAdapter<?>) existingInStream).getStream() + == inStream)) + return existingInStreamDesc; + if ((existingInStream instanceof CachingPushBufferStream) + && (((CachingPushBufferStream) existingInStream).getStream() + == inStream)) + return existingInStreamDesc; + } return null; } @@ -638,114 +635,103 @@ FormatControl[] getFormatControls() } /** - * Gets the <tt>SourceStream</tt>s (in the form of <tt>InputStreamDesc</tt>) + * Gets the <tt>SourceStream</tt>s (in the form of <tt>InStreamDesc</tt>) * of a specific <tt>DataSource</tt> (provided in the form of - * <tt>InputDataSourceDesc</tt>) which produce data in a specific + * <tt>InDataSourceDesc</tt>) which produce data in a specific * <tt>AudioFormat</tt> (or a matching one). * - * @param inputDataSourceDesc the <tt>DataSource</tt> (in the form of - * <tt>InputDataSourceDesc</tt>) which is to be examined for + * @param inDataSourceDesc the <tt>DataSource</tt> (in the form of + * <tt>InDataSourceDesc</tt>) which is to be examined for * <tt>SourceStreams</tt> producing data in the specified * <tt>AudioFormat</tt> - * @param outputFormat the <tt>AudioFormat</tt> in which the collected + * @param outFormat the <tt>AudioFormat</tt> in which the collected * <tt>SourceStream</tt>s are to produce data - * @param existingInputStreams the <tt>InputStreamDesc</tt> instances which + * @param existingInStreams the <tt>InStreamDesc</tt> instances which * already exist and which are used to avoid creating multiple - * <tt>InputStreamDesc</tt>s for input <tt>SourceStream</tt>s which already + * <tt>InStreamDesc</tt>s for input <tt>SourceStream</tt>s which already * have ones - * @param inputStreams the <tt>List</tt> of <tt>InputStreamDesc</tt> in + * @param inStreams the <tt>List</tt> of <tt>InStreamDesc</tt> in * which the discovered <tt>SourceStream</tt>s are to be returned * @return <tt>true</tt> if <tt>SourceStream</tt>s produced by the specified * input <tt>DataSource</tt> and outputting data in the specified * <tt>AudioFormat</tt> were discovered and reported in - * <tt>inputStreams</tt>; otherwise, <tt>false</tt> + * <tt>inStreams</tt>; otherwise, <tt>false</tt> */ - private boolean getInputStreamsFromInputDataSource( - InputDataSourceDesc inputDataSourceDesc, - AudioFormat outputFormat, - InputStreamDesc[] existingInputStreams, - List<InputStreamDesc> inputStreams) + private boolean getInStreamsFromInDataSource( + InDataSourceDesc inDataSourceDesc, + AudioFormat outFormat, + InStreamDesc[] existingInStreams, + List<InStreamDesc> inStreams) { - SourceStream[] inputDataSourceStreams - = inputDataSourceDesc.getStreams(); + SourceStream[] inDataSourceStreams = inDataSourceDesc.getStreams(); - if (inputDataSourceStreams != null) + if (inDataSourceStreams != null) { boolean added = false; - for (SourceStream inputStream : inputDataSourceStreams) + for (SourceStream inStream : inDataSourceStreams) { - Format inputFormat = getFormat(inputStream); + Format inFormat = getFormat(inStream); - if ((inputFormat != null) - && matches(inputFormat, outputFormat)) + if ((inFormat != null) && matches(inFormat, outFormat)) { - InputStreamDesc inputStreamDesc - = getExistingInputStreamDesc( - inputStream, - existingInputStreams); - - if (inputStreamDesc == null) - inputStreamDesc - = createInputStreamDesc( - inputStream, - inputDataSourceDesc); - if (inputStreams.add(inputStreamDesc)) + InStreamDesc inStreamDesc + = getExistingInStreamDesc(inStream, existingInStreams); + + if (inStreamDesc == null) + inStreamDesc + = createInStreamDesc(inStream, inDataSourceDesc); + if (inStreams.add(inStreamDesc)) added = true; } } return added; } - DataSource inputDataSource - = inputDataSourceDesc.getEffectiveInputDataSource(); + DataSource inDataSource = inDataSourceDesc.getEffectiveInDataSource(); - if (inputDataSource == null) + if (inDataSource == null) return false; - Format inputFormat = getFormat(inputDataSource); + Format inFormat = getFormat(inDataSource); - if ((inputFormat != null) && !matches(inputFormat, outputFormat)) + if ((inFormat != null) && !matches(inFormat, outFormat)) { - if (inputDataSource instanceof PushDataSource) + if (inDataSource instanceof PushDataSource) { - for (PushSourceStream inputStream - : ((PushDataSource) inputDataSource).getStreams()) + for (PushSourceStream inStream + : ((PushDataSource) inDataSource).getStreams()) { - InputStreamDesc inputStreamDesc - = getExistingInputStreamDesc( - inputStream, - existingInputStreams); - - if (inputStreamDesc == null) - inputStreamDesc - = createInputStreamDesc( + InStreamDesc inStreamDesc + = getExistingInStreamDesc(inStream, existingInStreams); + + if (inStreamDesc == null) + inStreamDesc + = createInStreamDesc( new PushBufferStreamAdapter( - inputStream, - inputFormat), - inputDataSourceDesc); - inputStreams.add(inputStreamDesc); + inStream, + inFormat), + inDataSourceDesc); + inStreams.add(inStreamDesc); } return true; } - if (inputDataSource instanceof PullDataSource) + if (inDataSource instanceof PullDataSource) { - for (PullSourceStream inputStream - : ((PullDataSource) inputDataSource).getStreams()) + for (PullSourceStream inStream + : ((PullDataSource) inDataSource).getStreams()) { - InputStreamDesc inputStreamDesc - = getExistingInputStreamDesc( - inputStream, - existingInputStreams); - - if (inputStreamDesc == null) - inputStreamDesc - = createInputStreamDesc( + InStreamDesc inStreamDesc + = getExistingInStreamDesc(inStream, existingInStreams); + + if (inStreamDesc == null) + inStreamDesc + = createInStreamDesc( new PullBufferStreamAdapter( - inputStream, - inputFormat), - inputDataSourceDesc); - inputStreams.add(inputStreamDesc); + inStream, + inFormat), + inDataSourceDesc); + inStreams.add(inStreamDesc); } return true; } @@ -754,54 +740,54 @@ && matches(inputFormat, outputFormat)) } /** - * Gets the <tt>SourceStream</tt>s (in the form of <tt>InputStreamDesc</tt>) + * Gets the <tt>SourceStream</tt>s (in the form of <tt>InStreamDesc</tt>) * of the <tt>DataSource</tt>s from which this <tt>AudioMixer</tt> reads * data which produce data in a specific <tt>AudioFormat</tt>. When an input * <tt>DataSource</tt> does not have such <tt>SourceStream</tt>s, an attempt * is made to transcode its tracks so that such <tt>SourceStream</tt>s can * be retrieved from it after transcoding. * - * @param outputFormat the <tt>AudioFormat</tt> in which the retrieved + * @param outFormat the <tt>AudioFormat</tt> in which the retrieved * <tt>SourceStream</tt>s are to produce data - * @param existingInputStreams the <tt>SourceStream</tt>s which are already + * @param existingInStreams the <tt>SourceStream</tt>s which are already * known to this <tt>AudioMixer</tt> * @return a new collection of <tt>SourceStream</tt>s (in the form of - * <tt>InputStreamDesc</tt>) retrieved from the input <tt>DataSource</tt>s + * <tt>InStreamDesc</tt>) retrieved from the input <tt>DataSource</tt>s * of this <tt>AudioMixer</tt> and producing data in the specified * <tt>AudioFormat</tt> * @throws IOException if anything wrong goes while retrieving the input * <tt>SourceStream</tt>s from the input <tt>DataSource</tt>s */ - private Collection<InputStreamDesc> getInputStreamsFromInputDataSources( - AudioFormat outputFormat, - InputStreamDesc[] existingInputStreams) + private Collection<InStreamDesc> getInStreamsFromInDataSources( + AudioFormat outFormat, + InStreamDesc[] existingInStreams) throws IOException { - List<InputStreamDesc> inputStreams = new ArrayList<InputStreamDesc>(); + List<InStreamDesc> inStreams = new ArrayList<InStreamDesc>(); - synchronized (inputDataSources) + synchronized (inDataSources) { - for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) + for (InDataSourceDesc inDataSourceDesc : inDataSources) { boolean got - = getInputStreamsFromInputDataSource( - inputDataSourceDesc, - outputFormat, - existingInputStreams, - inputStreams); + = getInStreamsFromInDataSource( + inDataSourceDesc, + outFormat, + existingInStreams, + inStreams); if (!got && createTranscodingDataSource( - inputDataSourceDesc, - outputFormat)) - getInputStreamsFromInputDataSource( - inputDataSourceDesc, - outputFormat, - existingInputStreams, - inputStreams); + inDataSourceDesc, + outFormat)) + getInStreamsFromInDataSource( + inDataSourceDesc, + outFormat, + existingInStreams, + inStreams); } } - return inputStreams; + return inStreams; } /** @@ -815,9 +801,9 @@ && createTranscodingDataSource( * this <tt>AudioMixer</tt> and is thus meant for playback on the local peer * in a call */ - public AudioMixingPushBufferDataSource getLocalOutputDataSource() + public AudioMixingPushBufferDataSource getLocalOutDataSource() { - return localOutputDataSource; + return localOutDataSource; } /** @@ -831,24 +817,24 @@ public AudioMixingPushBufferDataSource getLocalOutputDataSource() * produce data and which is to be the output <tt>Format</tt> of * this <tt>AudioMixer</tt> */ - private AudioFormat getOutputFormatFromInputDataSources() + private AudioFormat getOutFormatFromInDataSources() { String formatControlType = FormatControl.class.getName(); - AudioFormat outputFormat = null; + AudioFormat outFormat = null; - synchronized (inputDataSources) + synchronized (inDataSources) { - for (InputDataSourceDesc inputDataSource : inputDataSources) + for (InDataSourceDesc inDataSource : inDataSources) { - DataSource effectiveInputDataSource - = inputDataSource.getEffectiveInputDataSource(); + DataSource effectiveInDataSource + = inDataSource.getEffectiveInDataSource(); - if (effectiveInputDataSource == null) + if (effectiveInDataSource == null) continue; FormatControl formatControl = (FormatControl) - effectiveInputDataSource.getControl(formatControlType); + effectiveInDataSource.getControl(formatControlType); if (formatControl != null) { @@ -869,7 +855,7 @@ private AudioFormat getOutputFormatFromInputDataSources() if ((AudioFormat.LITTLE_ENDIAN == endian) || (Format.NOT_SPECIFIED == endian)) { - outputFormat = format; + outFormat = format; break; } } @@ -878,17 +864,16 @@ private AudioFormat getOutputFormatFromInputDataSources() } } - if (outputFormat == null) - outputFormat = DEFAULT_OUTPUT_FORMAT; + if (outFormat == null) + outFormat = DEFAULT_OUTPUT_FORMAT; if (logger.isTraceEnabled()) { logger.trace( - "Determined outputFormat of AudioMixer" - + " from inputDataSources to be " - + outputFormat); + "Determined outFormat of AudioMixer from inDataSources" + + " to be " + outFormat); } - return outputFormat; + return outFormat; } /** @@ -898,45 +883,74 @@ private AudioFormat getOutputFormatFromInputDataSources() * output <tt>AudioMixingPushBufferStream</tt>s for audio mixing. * * @return the <tt>AudioMixerPushBufferStream</tt> which reads data from - * the input <tt>DataSource</tt>s of this - * <tt>AudioMixer</tt> and pushes it to output - * <tt>AudioMixingPushBufferStream</tt>s for audio mixing + * the input <tt>DataSource</tt>s of this <tt>AudioMixer</tt> and pushes it + * to output <tt>AudioMixingPushBufferStream</tt>s for audio mixing */ - AudioMixerPushBufferStream getOutputStream() + AudioMixerPushBufferStream getOutStream() { - synchronized (inputDataSources) + synchronized (inDataSources) { - AudioFormat outputFormat - = (outputStream == null) - ? getOutputFormatFromInputDataSources() - : outputStream.getFormat(); + AudioFormat outFormat + = (outStream == null) + ? getOutFormatFromInDataSources() + : outStream.getFormat(); - setOutputFormatToInputDataSources(outputFormat); + setOutFormatToInDataSources(outFormat); - Collection<InputStreamDesc> inputStreams; + Collection<InStreamDesc> inStreams; try { - inputStreams - = getInputStreamsFromInputDataSources( - outputFormat, - (outputStream == null) - ? null - : outputStream.getInputStreams()); + inStreams + = getInStreamsFromInDataSources( + outFormat, + (outStream == null) ? null : outStream.getInStreams()); } catch (IOException ioex) { throw new UndeclaredThrowableException(ioex); } - if (outputStream == null) - outputStream - = new AudioMixerPushBufferStream(this, outputFormat); - outputStream.setInputStreams(inputStreams); - return outputStream; + if (outStream == null) + outStream = new AudioMixerPushBufferStream(this, outFormat); + outStream.setInStreams(inStreams); + return outStream; } } + /** + * Searches this object's <tt>inDataSource</tt>s for one that matches + * <tt>inDataSource</tt>, and returns it's associated + * <tt>TranscodingDataSource</tt>. Currently this is only used when + * the <tt>MediaStream</tt> needs access to the codec chain used to + * playback one of it's <tt>ReceiveStream</tt>s. + * + * @param inDataSource the <tt>DataSource</tt> to search for. + * @return The <tt>TranscodingDataSource</tt> associated with + * <tt>inDataSource</tt>, if we can find one, <tt>null</tt> otherwise. + */ + public TranscodingDataSource getTranscodingDataSource( + DataSource inDataSource) + { + for (InDataSourceDesc inDataSourceDesc : inDataSources) + { + DataSource ourDataSource = inDataSourceDesc.getInDataSource(); + + if (ourDataSource == inDataSource) + return inDataSourceDesc.getTranscodingDataSource(); + else if (ourDataSource instanceof ReceiveStreamPushBufferDataSource) + { + // Sometimes the inDataSource has come to AudioMixer wrapped in + // a ReceiveStreamPushBufferDataSource. We consider it to match. + if (((ReceiveStreamPushBufferDataSource) ourDataSource) + .getDataSource() + == inDataSource) + return inDataSourceDesc.getTranscodingDataSource(); + } + } + return null; + } + /** * Determines whether a specific <tt>Format</tt> matches a specific * <tt>Format</tt> in the sense of JMF <tt>Format</tt> matching. @@ -946,16 +960,13 @@ AudioMixerPushBufferStream getOutputStream() * <tt>Format</tt>s to match is for both of them to have one and the * same encoding. * - * @param input - * the <tt>Format</tt> for which it is required to determine - * whether it matches a specific <tt>Format</tt> - * @param pattern - * the <tt>Format</tt> against which the specified - * <tt>input</tt> is to be matched - * @return <tt>true</tt> if the specified - * <tt>input<tt> matches the specified <tt>pattern</tt> in - * the sense of JMF <tt>Format</tt> matching; otherwise, - * <tt>false</tt> + * @param input the <tt>Format</tt> for which it is required to determine + * whether it matches a specific <tt>Format</tt> + * @param pattern the <tt>Format</tt> against which the specified + * <tt>input</tt> is to be matched + * @return <tt>true</tt> if the specified <tt>input<tt> matches the + * specified <tt>pattern</tt> in the sense of JMF <tt>Format</tt> matching; + * otherwise, <tt>false</tt> */ private boolean matches(Format input, AudioFormat pattern) { @@ -997,25 +1008,25 @@ protected void read( * <tt>DataSource</tt>s of this <tt>AudioMixer</tt> from which it reads * audio to be mixed */ - public void removeInputDataSources(DataSourceFilter dataSourceFilter) + public void removeInDataSources(DataSourceFilter dataSourceFilter) { - synchronized (inputDataSources) + synchronized (inDataSources) { - Iterator<InputDataSourceDesc> inputDataSourceIter - = inputDataSources.iterator(); + Iterator<InDataSourceDesc> inDataSourceIter + = inDataSources.iterator(); boolean removed = false; - while (inputDataSourceIter.hasNext()) + while (inDataSourceIter.hasNext()) { - if (dataSourceFilter - .accept(inputDataSourceIter.next().inputDataSource)) + if (dataSourceFilter.accept( + inDataSourceIter.next().inDataSource)) { - inputDataSourceIter.remove(); + inDataSourceIter.remove(); removed = true; } } - if (removed && (outputStream != null)) - getOutputStream(); + if (removed && (outStream != null)) + getOutStream(); } } @@ -1025,49 +1036,43 @@ public void removeInputDataSources(DataSourceFilter dataSourceFilter) * <tt>AudioMixer</tt> in an attempt to not have to perform explicit * transcoding of the input <tt>SourceStream</tt>s. * - * @param outputFormat - * the <tt>AudioFormat</tt> in which the input - * <tt>DataSource</tt>s of this <tt>AudioMixer</tt> are - * to be instructed to output + * @param outFormat the <tt>AudioFormat</tt> in which the input + * <tt>DataSource</tt>s of this <tt>AudioMixer</tt> are to be instructed to + * output */ - private void setOutputFormatToInputDataSources(AudioFormat outputFormat) + private void setOutFormatToInDataSources(AudioFormat outFormat) { String formatControlType = FormatControl.class.getName(); - synchronized (inputDataSources) + synchronized (inDataSources) { - for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) + for (InDataSourceDesc inDataSourceDesc : inDataSources) { FormatControl formatControl = (FormatControl) - inputDataSourceDesc.getControl(formatControlType); + inDataSourceDesc.getControl(formatControlType); if (formatControl != null) { - Format inputFormat = formatControl.getFormat(); + Format inFormat = formatControl.getFormat(); - if ((inputFormat == null) - || !matches(inputFormat, outputFormat)) + if ((inFormat == null) || !matches(inFormat, outFormat)) { Format setFormat - = formatControl.setFormat(outputFormat); + = formatControl.setFormat(outFormat); if (setFormat == null) - logger - .error( - "Failed to set format of inputDataSource to " - + outputFormat); - else if (setFormat != outputFormat) - logger - .warn( - "Failed to change format of inputDataSource from " - + setFormat - + " to " - + outputFormat); + logger.error( + "Failed to set format of inDataSource to " + + outFormat); + else if (setFormat != outFormat) + logger.warn( + "Failed to change format of inDataSource" + + " from " + setFormat + " to " + + outFormat); else if (logger.isTraceEnabled()) - logger - .trace( - "Set format of inputDataSource to " + logger.trace( + "Set format of inDataSource to " + setFormat); } } @@ -1078,30 +1083,30 @@ else if (logger.isTraceEnabled()) /** * Starts the input <tt>DataSource</tt>s of this <tt>AudioMixer</tt>. * - * @param outputStream the <tt>AudioMixerPushBufferStream</tt> which - * requests this <tt>AudioMixer</tt> to start. If <tt>outputStream</tt> is + * @param outStream the <tt>AudioMixerPushBufferStream</tt> which + * requests this <tt>AudioMixer</tt> to start. If <tt>outStream</tt> is * the current one and only <tt>AudioMixerPushBufferStream</tt> of this * <tt>AudioMixer</tt>, this <tt>AudioMixer</tt> starts if it hasn't started * yet. Otherwise, the request is ignored. * @throws IOException if any of the input <tt>DataSource</tt>s of this * <tt>AudioMixer</tt> throws such an exception while attempting to start it */ - void start(AudioMixerPushBufferStream outputStream) + void start(AudioMixerPushBufferStream outStream) throws IOException { - synchronized (inputDataSources) + synchronized (inDataSources) { /* - * AudioMixer has only one outputStream at a time and only its - * current outputStream knows when it has to start (and stop). + * AudioMixer has only one outStream at a time and only its + * current outStream knows when it has to start (and stop). */ - if (this.outputStream != outputStream) + if (this.outStream != outStream) return; if (started == 0) { - for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) - inputDataSourceDesc.start(); + for (InDataSourceDesc inDataSourceDesc : inDataSources) + inDataSourceDesc.start(); } started++; @@ -1111,24 +1116,24 @@ void start(AudioMixerPushBufferStream outputStream) /** * Stops the input <tt>DataSource</tt>s of this <tt>AudioMixer</tt>. * - * @param outputStream the <tt>AudioMixerPushBufferStream</tt> which - * requests this <tt>AudioMixer</tt> to stop. If <tt>outputStream</tt> is + * @param outStream the <tt>AudioMixerPushBufferStream</tt> which + * requests this <tt>AudioMixer</tt> to stop. If <tt>outStream</tt> is * the current one and only <tt>AudioMixerPushBufferStream</tt> of this * <tt>AudioMixer</tt>, this <tt>AudioMixer</tt> stops. Otherwise, the * request is ignored. * @throws IOException if any of the input <tt>DataSource</tt>s of this * <tt>AudioMixer</tt> throws such an exception while attempting to stop it */ - void stop(AudioMixerPushBufferStream outputStream) + void stop(AudioMixerPushBufferStream outStream) throws IOException { - synchronized (inputDataSources) + synchronized (inDataSources) { /* - * AudioMixer has only one outputStream at a time and only its - * current outputStream known when it has to stop (and start). + * AudioMixer has only one outStream at a time and only its + * current outStream known when it has to stop (and start). */ - if (this.outputStream != outputStream) + if (this.outStream != outStream) return; if (started <= 0) @@ -1138,42 +1143,9 @@ void stop(AudioMixerPushBufferStream outputStream) if (started == 0) { - for (InputDataSourceDesc inputDataSourceDesc : inputDataSources) - inputDataSourceDesc.stop(); + for (InDataSourceDesc inDataSourceDesc : inDataSources) + inDataSourceDesc.stop(); } } } - - /** - * Searches this object's <tt>inputDataSource</tt>s for one that matches - * <tt>inputDataSource</tt>, and returns it's associated - * <tt>TranscodingDataSource</tt>. Currently this is only used when - * the <tt>MediaStream</tt> needs access to the codec chain used to - * playback one of it's <tt>ReceiveStream</tt>s. - * - * @param inputDataSource the <tt>DataSource</tt> to search for. - * - * @return The <tt>TranscodingDataSource</tt> associated with - * <tt>inputDataSource</tt>, if we can find one, <tt>null</tt> otherwise. - */ - public TranscodingDataSource - getTranscodingDataSource(DataSource inputDataSource) - { - for(InputDataSourceDesc inputDataSourceDesc : inputDataSources) - { - DataSource ourDataSource = inputDataSourceDesc.getInputDataSource(); - if(ourDataSource == inputDataSource) - return inputDataSourceDesc.getTranscodingDataSource(); - else if(ourDataSource instanceof ReceiveStreamPushBufferDataSource) - { - //sometimes the inputDataSource has come to AudioMixer - //wrapped in a ReceiveStreamPushBufferDataSource. We consider - //it to match - if(((ReceiveStreamPushBufferDataSource) ourDataSource) - .getDataSource() == inputDataSource) - return inputDataSourceDesc.getTranscodingDataSource(); - } - } - return null; - } } diff --git a/src/org/jitsi/impl/neomedia/conference/AudioMixerPushBufferStream.java b/src/org/jitsi/impl/neomedia/conference/AudioMixerPushBufferStream.java index ed1d5085..946b58b8 100644 --- a/src/org/jitsi/impl/neomedia/conference/AudioMixerPushBufferStream.java +++ b/src/org/jitsi/impl/neomedia/conference/AudioMixerPushBufferStream.java @@ -38,11 +38,133 @@ class AudioMixerPushBufferStream implements PushBufferStream { /** - * The <tt>Logger</tt> used by the <tt>AudioMixerPushBufferStream</tt> class - * and its instances for logging output. + * Describes a specific set of audio samples read from a specific set of + * input streams specified by their <tt>InStreamDesc</tt>s. */ - private static final Logger logger - = Logger.getLogger(AudioMixerPushBufferStream.class); + private static class InSampleDesc + { + /** + * The <tt>Buffer</tt> into which media data is to be read from + * {@link #inStreams}. + */ + private SoftReference<Buffer> buffer; + + /** + * The <tt>AudioFormat</tt> of {@link #inSamples}. + */ + private final AudioFormat format; + + /** + * The set of audio samples read from {@link #inStreams}. + */ + public final int[][] inSamples; + + /** + * The set of input streams from which {@link #inSamples} were read. + */ + public final InStreamDesc[] inStreams; + + /** + * The time stamp of <tt>inSamples</tt> to be reported in the + * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when + * mixes are read from them. + */ + private long timeStamp = Buffer.TIME_UNKNOWN; + + /** + * Initializes a new <tt>InSampleDesc</tt> instance which is to + * describe a specific set of audio samples read from a specific set of + * input streams specified by their <tt>InStreamDesc</tt>s. + * + * @param inSamples the set of audio samples read from + * <tt>inStreams</tt> + * @param inStreams the set of input streams from which + * <tt>inSamples</tt> were read + * @param format the <tt>AudioFormat</tt> of <tt>inSamples</tt> + */ + public InSampleDesc( + int[][] inSamples, + InStreamDesc[] inStreams, + AudioFormat format) + { + this.inSamples = inSamples; + this.inStreams = inStreams; + this.format = format; + } + + /** + * Gets the <tt>Buffer</tt> into which media data is to be read from the + * input streams associated with this instance. + * + * @param create the indicator which determines whether the + * <tt>Buffer</tt> is to be created in case it does not exist + * @return the <tt>Buffer</tt> into which media data is to be read from + * the input streams associated with this instance + */ + public Buffer getBuffer(boolean create) + { + Buffer buffer = (this.buffer == null) ? null : this.buffer.get(); + + if ((buffer == null) && create) + { + buffer = new Buffer(); + setBuffer(buffer); + } + return buffer; + } + + /** + * Gets the time stamp of <tt>inSamples</tt> to be reported in the + * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when + * mixes are read from them. + * + * @return the time stamp of <tt>inSamples</tt> to be reported in the + * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when + * mixes are read from them + */ + public long getTimeStamp() + { + return timeStamp; + } + + /** + * Sets the <tt>Buffer</tt> into which media data is to be read from the + * input streams associated with this instance. + * + * @param buffer the <tt>Buffer</tt> into which media data is to be read + * from the input streams associated with this instance + */ + public void setBuffer(Buffer buffer) + { + this.buffer + = (buffer == null) ? null : new SoftReference<Buffer>(buffer); + } + + /** + * Sets the time stamp of <tt>inSamples</tt> to be reported in the + * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when + * mixes are read from them. + * + * @param timeStamp the time stamp of <tt>inSamples</tt> to be + * reported in the <tt>Buffer</tt>s of the + * <tt>AudioMixingPushBufferStream</tt>s when mixes are read from them + */ + public void setTimeStamp(long timeStamp) + { + if (this.timeStamp == Buffer.TIME_UNKNOWN) + this.timeStamp = timeStamp; + else + { + /* + * Setting the timeStamp more than once does not make sense + * because the inStreams will report different timeStamps so + * only one should be picked up where the very reading from + * inStreams takes place. + */ + throw new IllegalStateException("timeStamp"); + } + } + } /** * The factor which scales a <tt>short</tt> value to an <tt>int</tt> value. @@ -50,6 +172,13 @@ class AudioMixerPushBufferStream private static final float INT_TO_SHORT_RATIO = Integer.MAX_VALUE / (float) Short.MAX_VALUE; + /** + * The <tt>Logger</tt> used by the <tt>AudioMixerPushBufferStream</tt> class + * and its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(AudioMixerPushBufferStream.class); + /** * The factor which scales an <tt>int</tt> value to a <tt>short</tt> value. */ @@ -71,47 +200,36 @@ class AudioMixerPushBufferStream private final AudioMixer audioMixer; /** - * The <tt>SourceStream</tt>s (in the form of <tt>InputStreamDesc</tt> so + * The <tt>SourceStream</tt>s (in the form of <tt>InStreamDesc</tt> so * that this instance can track back the * <tt>AudioMixingPushBufferDataSource</tt> which outputs the mixed audio * stream and determine whether the associated <tt>SourceStream</tt> is to * be included into the mix) from which this instance reads its data. */ - private InputStreamDesc[] inputStreams; + private InStreamDesc[] inStreams; /** * The <tt>Object</tt> which synchronizes the access to - * {@link #inputStreams}-related members. - */ - private final Object inputStreamsSyncRoot = new Object(); - - /** - * The cache of <tt>int</tt> arrays managed by this instance for the - * purposes of reducing garbage collection. + * {@link #inStreams}-related members. */ - private SoftReference<List<int[]>> intArrays; - - /** - * The <tt>Object</tt> which synchronizes the access to {@link #intArrays}. - */ - private final Object intArraysSyncRoot = new Object(); + private final Object inStreamsSyncRoot = new Object(); /** * The <tt>AudioFormat</tt> of the <tt>Buffer</tt> read during the last read - * from one of the {@link #inputStreams}. Only used for debugging purposes. + * from one of the {@link #inStreams}. Only used for debugging purposes. */ - private AudioFormat lastReadInputFormat; + private AudioFormat lastReadInFormat; /** * The <tt>AudioFormat</tt> of the data this instance outputs. */ - private final AudioFormat outputFormat; + private final AudioFormat outFormat; /** * The <tt>AudioMixingPushBufferStream</tt>s to which this instance pushes * data for audio mixing. */ - private final List<AudioMixingPushBufferStream> outputStreams + private final List<AudioMixingPushBufferStream> outStreams = new ArrayList<AudioMixingPushBufferStream>(); /** @@ -144,15 +262,15 @@ public void transferData(PushBufferStream stream) * * @param audioMixer the <tt>AudioMixer</tt> which creates this instance and * for which it is to output data - * @param outputFormat the <tt>AudioFormat</tt> in which the new instance is - * to output data + * @param outFormat the <tt>AudioFormat</tt> in which the new instance is to + * output data */ public AudioMixerPushBufferStream( AudioMixer audioMixer, - AudioFormat outputFormat) + AudioFormat outFormat) { this.audioMixer = audioMixer; - this.outputFormat = outputFormat; + this.outFormat = outFormat; } /** @@ -160,24 +278,24 @@ public AudioMixerPushBufferStream( * such streams to which this instance is to push the data for audio mixing * it reads from its input <tt>SourceStream</tt>s. * - * @param outputStream the <tt>AudioMixingPushBufferStream</tt> to add to + * @param outStream the <tt>AudioMixingPushBufferStream</tt> to add to * the collection of such streams to which this instance is to push the data * for audio mixing it reads from its input <tt>SourceStream</tt>s - * @throws IOException if <tt>outputStream</tt> was the first + * @throws IOException if <tt>outStream</tt> was the first * <tt>AudioMixingPushBufferStream</tt> and the <tt>AudioMixer</tt> failed * to start */ - void addOutputStream(AudioMixingPushBufferStream outputStream) + void addOutStream(AudioMixingPushBufferStream outStream) throws IOException { - if (outputStream == null) - throw new IllegalArgumentException("outputStream"); + if (outStream == null) + throw new IllegalArgumentException("outStream"); - synchronized (outputStreams) + synchronized (outStreams) { - if (!outputStreams.contains(outputStream) - && outputStreams.add(outputStream) - && (outputStreams.size() == 1)) + if (!outStreams.contains(outStream) + && outStreams.add(outStream) + && (outStreams.size() == 1)) { boolean started = false; @@ -189,64 +307,12 @@ void addOutputStream(AudioMixingPushBufferStream outputStream) finally { if (!started) - outputStreams.remove(outputStream); + outStreams.remove(outStream); } } } } - public int[] allocateIntArray(int minSize) - { - synchronized (intArraysSyncRoot) - { - List<int[]> intArrays - = (this.intArrays == null) ? null : this.intArrays.get(); - - if (intArrays != null) - { - Iterator<int[]> i = intArrays.iterator(); - - while (i.hasNext()) - { - int[] intArray = i.next(); - - if (intArray.length >= minSize) - { - i.remove(); - return intArray; - } - } - } - } - - return new int[minSize]; - } - - public void deallocateIntArray(int[] intArray) - { - if (intArray == null) - return; - - synchronized (intArraysSyncRoot) - { - List<int[]> intArrays; - - if ((this.intArrays == null) - || ((intArrays = this.intArrays.get()) == null)) - { - intArrays = new LinkedList<int[]>(); - this.intArrays = new SoftReference<List<int[]>>(intArrays); - } - - if (intArrays.size() != 0) - for (int[] element : intArrays) - if (element == intArray) - return; - - intArrays.add(intArray); - } - } - /** * Implements {@link SourceStream#endOfStream()}. Delegates to the input * <tt>SourceStreams</tt> of this instance. @@ -256,11 +322,11 @@ public void deallocateIntArray(int[] intArray) */ public boolean endOfStream() { - synchronized (inputStreamsSyncRoot) + synchronized (inStreamsSyncRoot) { - if (inputStreams != null) - for (InputStreamDesc inputStreamDesc : inputStreams) - if (!inputStreamDesc.getInputStream().endOfStream()) + if (inStreams != null) + for (InStreamDesc inStreamDesc : inStreams) + if (!inStreamDesc.getInStream().endOfStream()) return false; } return true; @@ -268,34 +334,34 @@ public boolean endOfStream() /** * Attempts to equalize the length in milliseconds of the buffering - * performed by the <tt>inputStreams</tt> in order to always read and mix + * performed by the <tt>inStreams</tt> in order to always read and mix * one and the same length in milliseconds. */ - void equalizeInputStreamBufferLength() + void equalizeInStreamBufferLength() { - synchronized (inputStreamsSyncRoot) + synchronized (inStreamsSyncRoot) { - if ((inputStreams == null) || (inputStreams.length < 1)) + if ((inStreams == null) || (inStreams.length < 1)) return; /* - * The first inputStream is expected to be from the CaptureDevice + * The first inStream is expected to be from the CaptureDevice * and no custom BufferControl is provided for it so the * bufferLength is whatever it says. */ - BufferControl bufferControl = getBufferControl(inputStreams[0]); + BufferControl bufferControl = getBufferControl(inStreams[0]); long bufferLength = (bufferControl == null) ? CachingPushBufferStream.DEFAULT_BUFFER_LENGTH : bufferControl.getBufferLength(); - for (int i = 1; i < inputStreams.length; i++) + for (int i = 1; i < inStreams.length; i++) { - BufferControl inputStreamBufferControl - = getBufferControl(inputStreams[i]); + BufferControl inStreamBufferControl + = getBufferControl(inStreams[i]); - if (inputStreamBufferControl != null) - inputStreamBufferControl.setBufferLength(bufferLength); + if (inStreamBufferControl != null) + inStreamBufferControl.setBufferLength(bufferLength); } } } @@ -306,7 +372,7 @@ void equalizeInputStreamBufferLength() * <tt>DataSource</tt>, its transcoding <tt>DataSource</tt> if any or the * very input stream. * - * @param inputStreamDesc an <tt>InputStreamDesc</tt> which describes the + * @param inStreamDesc an <tt>InStreamDesc</tt> which describes the * input stream and its originating <tt>DataSource</tt>s from which the * <tt>BufferControl</tt> is to be retrieved * @return the <tt>BufferControl</tt> of the specified input stream found in @@ -314,21 +380,21 @@ void equalizeInputStreamBufferLength() * or the very input stream if such a control exists; otherwise, * <tt>null</tt> */ - private BufferControl getBufferControl(InputStreamDesc inputStreamDesc) + private BufferControl getBufferControl(InStreamDesc inStreamDesc) { - InputDataSourceDesc inputDataSourceDesc - = inputStreamDesc.inputDataSourceDesc; + InDataSourceDesc inDataSourceDesc + = inStreamDesc.inDataSourceDesc; - // Try the DataSource which directly provides the specified inputStream. - DataSource effectiveInputDataSource - = inputDataSourceDesc.getEffectiveInputDataSource(); + // Try the DataSource which directly provides the specified inStream. + DataSource effectiveInDataSource + = inDataSourceDesc.getEffectiveInDataSource(); String bufferControlType = BufferControl.class.getName(); - if (effectiveInputDataSource != null) + if (effectiveInDataSource != null) { BufferControl bufferControl = (BufferControl) - effectiveInputDataSource.getControl(bufferControlType); + effectiveInDataSource.getControl(bufferControlType); if (bufferControl != null) return bufferControl; @@ -336,25 +402,25 @@ private BufferControl getBufferControl(InputStreamDesc inputStreamDesc) /* * If transcoding is taking place and the transcodingDataSource does not - * have a BufferControl, try the inputDataSource which is being + * have a BufferControl, try the inDataSource which is being * transcoded. */ - DataSource inputDataSource = inputDataSourceDesc.inputDataSource; + DataSource inDataSource = inDataSourceDesc.inDataSource; - if ((inputDataSource != null) - && (inputDataSource != effectiveInputDataSource)) + if ((inDataSource != null) + && (inDataSource != effectiveInDataSource)) { BufferControl bufferControl - = (BufferControl) inputDataSource.getControl(bufferControlType); + = (BufferControl) inDataSource.getControl(bufferControlType); if (bufferControl != null) return bufferControl; } - // If everything else has failed, try the very inputStream. + // If everything else has failed, try the very inStream. return (BufferControl) - inputStreamDesc.getInputStream().getControl(bufferControlType); + inStreamDesc.getInStream().getControl(bufferControlType); } /** @@ -382,18 +448,18 @@ public long getContentLength() { long contentLength = 0; - synchronized (inputStreamsSyncRoot) + synchronized (inStreamsSyncRoot) { - if (inputStreams != null) - for (InputStreamDesc inputStreamDesc : inputStreams) + if (inStreams != null) + for (InStreamDesc inStreamDesc : inStreams) { - long inputContentLength - = inputStreamDesc.getInputStream().getContentLength(); + long inContentLength + = inStreamDesc.getInStream().getContentLength(); - if (LENGTH_UNKNOWN == inputContentLength) + if (LENGTH_UNKNOWN == inContentLength) return LENGTH_UNKNOWN; - if (contentLength < inputContentLength) - contentLength = inputContentLength; + if (contentLength < inContentLength) + contentLength = inContentLength; } } return contentLength; @@ -409,21 +475,21 @@ public long getContentLength() */ public AudioFormat getFormat() { - return outputFormat; + return outFormat; } /** * Gets the <tt>SourceStream</tt>s (in the form of - * <tt>InputStreamDesc</tt>s) from which this instance reads audio samples. + * <tt>InStreamDesc</tt>s) from which this instance reads audio samples. * - * @return an array of <tt>InputStreamDesc</tt>s which describe the input + * @return an array of <tt>InStreamDesc</tt>s which describe the input * <tt>SourceStream</tt>s from which this instance reads audio samples */ - InputStreamDesc[] getInputStreams() + InStreamDesc[] getInStreams() { - synchronized (inputStreamsSyncRoot) + synchronized (inStreamsSyncRoot) { - return (inputStreams == null) ? null : inputStreams.clone(); + return (inStreams == null) ? null : inStreams.clone(); } } @@ -442,54 +508,57 @@ InputStreamDesc[] getInputStreams() public void read(Buffer buffer) throws IOException { - InputSampleDesc inputSampleDesc; - int inputStreamCount; + InSampleDesc inSampleDesc; + int inStreamCount; + AudioFormat format = getFormat(); - synchronized (inputStreamsSyncRoot) + synchronized (inStreamsSyncRoot) { - InputStreamDesc[] thisInputStreams = this.inputStreams; + InStreamDesc[] inStreams = this.inStreams; - if ((thisInputStreams == null) || (thisInputStreams.length == 0)) + if ((inStreams == null) || (inStreams.length == 0)) return; else { - inputSampleDesc = (InputSampleDesc) buffer.getData(); - inputStreamCount = thisInputStreams.length; - if (inputSampleDesc != null) + inSampleDesc = (InSampleDesc) buffer.getData(); + // format + if ((inSampleDesc != null) && inSampleDesc.format != format) + inSampleDesc = null; + // inStreams + inStreamCount = inStreams.length; + if (inSampleDesc != null) { - InputStreamDesc[] inputSampleDescInputStreams - = inputSampleDesc.inputStreams; + InStreamDesc[] inSampleDescInStreams + = inSampleDesc.inStreams; - if (inputSampleDescInputStreams.length == inputStreamCount) + if (inSampleDescInStreams.length == inStreamCount) { - for (int i = 0; i < inputStreamCount; i++) - if (inputSampleDescInputStreams[i] - != thisInputStreams[i]) + for (int i = 0; i < inStreamCount; i++) + if (inSampleDescInStreams[i] != inStreams[i]) { - inputSampleDesc = null; + inSampleDesc = null; break; } } else - inputSampleDesc = null; + inSampleDesc = null; } - if (inputSampleDesc == null) + if (inSampleDesc == null) { - inputSampleDesc - = new InputSampleDesc( - new int[inputStreamCount][], - thisInputStreams.clone()); + inSampleDesc + = new InSampleDesc( + new int[inStreamCount][], + inStreams.clone(), + format); } } } - AudioFormat outputFormat = getFormat(); - int maxInputSampleCount; + int maxInSampleCount; try { - maxInputSampleCount - = readInputPushBufferStreams(outputFormat, inputSampleDesc); + maxInSampleCount = readInPushBufferStreams(format, inSampleDesc); } catch (UnsupportedFormatException ufex) { @@ -499,22 +568,22 @@ public void read(Buffer buffer) throw ioex; } - maxInputSampleCount + maxInSampleCount = Math.max( - maxInputSampleCount, - readInputPullBufferStreams( - outputFormat, - maxInputSampleCount, - inputSampleDesc)); + maxInSampleCount, + readInPullBufferStreams( + format, + maxInSampleCount, + inSampleDesc)); - buffer.setData(inputSampleDesc); - buffer.setLength(maxInputSampleCount); + buffer.setData(inSampleDesc); + buffer.setLength(maxInSampleCount); /* * Convey the timeStamp so that it can be reported by the Buffers of * the AudioMixingPushBufferStreams when mixes are read from them. */ - long timeStamp = inputSampleDesc.getTimeStamp(); + long timeStamp = inSampleDesc.getTimeStamp(); if (timeStamp != Buffer.TIME_UNKNOWN) buffer.setTimeStamp(timeStamp); @@ -527,13 +596,13 @@ public void read(Buffer buffer) * the <tt>PullBufferStream</tt>s but the very <tt>PullBufferStream</tt> may * not honor the request. * - * @param outputFormat the <tt>AudioFormat</tt> in which the audio samples + * @param outFormat the <tt>AudioFormat</tt> in which the audio samples * read from the <tt>PullBufferStream</tt>s are to be converted before being * returned - * @param outputSampleCount the maximum number of audio samples to be read + * @param outSampleCount the maximum number of audio samples to be read * from each of the <tt>PullBufferStream</tt>s but the very * <tt>PullBufferStream</tt>s may not honor the request - * @param inputSampleDesc an <tt>InputStreamDesc</tt> which specifies the + * @param inSampleDesc an <tt>InStreamDesc</tt> which specifies the * input streams to be read and the collection of audio samples in which the * read audio samples are to be returned * @return the maximum number of audio samples actually read from the input @@ -541,23 +610,22 @@ public void read(Buffer buffer) * @throws IOException if anything goes wrong while reading the specified * input streams */ - private int readInputPullBufferStreams( - AudioFormat outputFormat, - int outputSampleCount, - InputSampleDesc inputSampleDesc) + private int readInPullBufferStreams( + AudioFormat outFormat, + int outSampleCount, + InSampleDesc inSampleDesc) throws IOException { - InputStreamDesc[] inputStreams = inputSampleDesc.inputStreams; - int maxInputSampleCount = 0; - - for (InputStreamDesc inputStream : inputStreams) - if (inputStream.getInputStream() instanceof PullBufferStream) - throw - new UnsupportedOperationException( - AudioMixerPushBufferStream.class.getSimpleName() - + ".readInputPullBufferStreams" - + "(AudioFormat,int,InputSampleDesc)"); - return maxInputSampleCount; + InStreamDesc[] inStreams = inSampleDesc.inStreams; + int maxInSampleCount = 0; + + for (InStreamDesc inStream : inStreams) + if (inStream.getInStream() instanceof PullBufferStream) + throw new UnsupportedOperationException( + AudioMixerPushBufferStream.class.getSimpleName() + + ".readInPullBufferStreams" + + "(AudioFormat,int,InSampleDesc)"); + return maxInSampleCount; } /** @@ -567,174 +635,173 @@ private int readInputPullBufferStreams( * <tt>PushBufferStream</tt> but the very <tt>PushBufferStream</tt> may not * honor the request. * - * @param inputStreamDesc an <tt>InputStreamDesc</tt> which specifies the + * @param inStreamDesc an <tt>InStreamDesc</tt> which specifies the * input <tt>PushBufferStream</tt> to read from - * @param outputFormat the <tt>AudioFormat</tt> to which the samples read - * from <tt>inputStream</tt> are to be converted before being returned + * @param outFormat the <tt>AudioFormat</tt> to which the samples read + * from <tt>inStream</tt> are to be converted before being returned * @param sampleCount the maximum number of samples which the read operation - * should attempt to read from <tt>inputStream</tt> but the very - * <tt>inputStream</tt> may not honor the request - * @param outputBuffer the <tt>Buffer</tt> into which the array of - * <tt>int</tt> audio samples read from the specified <tt>inputStream</tt> + * should attempt to read from <tt>inStream</tt> but the very + * <tt>inStream</tt> may not honor the request + * @param outBuffer the <tt>Buffer</tt> into which the array of + * <tt>int</tt> audio samples read from the specified <tt>inStream</tt> * is to be written * @throws IOException if anything wrong happens while reading - * <tt>inputStream</tt> + * <tt>inStream</tt> * @throws UnsupportedFormatException if converting the samples read from - * <tt>inputStream</tt> to <tt>outputFormat</tt> fails + * <tt>inStream</tt> to <tt>outFormat</tt> fails */ - private void readInputPushBufferStream( - InputStreamDesc inputStreamDesc, - AudioFormat outputFormat, + private void readInPushBufferStream( + InStreamDesc inStreamDesc, + AudioFormat outFormat, int sampleCount, - Buffer outputBuffer) + Buffer outBuffer) throws IOException, UnsupportedFormatException { - PushBufferStream inputStream - = (PushBufferStream) inputStreamDesc.getInputStream(); - AudioFormat inputStreamFormat - = (AudioFormat) inputStream.getFormat(); - Buffer inputBuffer = inputStreamDesc.getBuffer(true); + PushBufferStream inStream + = (PushBufferStream) inStreamDesc.getInStream(); + AudioFormat inStreamFormat = (AudioFormat) inStream.getFormat(); + Buffer inBuffer = inStreamDesc.getBuffer(true); if (sampleCount != 0) { - Class<?> inputDataType = inputStreamFormat.getDataType(); - - if (Format.byteArray.equals(inputDataType)) + if (Format.byteArray.equals(inStreamFormat.getDataType())) { - Object data = inputBuffer.getData(); + Object data = inBuffer.getData(); int length - = sampleCount - * (inputStreamFormat.getSampleSizeInBits() / 8); + = sampleCount * (inStreamFormat.getSampleSizeInBits() / 8); if (!(data instanceof byte[]) || (((byte[]) data).length != length)) - inputBuffer.setData(new byte[length]); - inputBuffer.setLength(0); - inputBuffer.setOffset(0); + inBuffer.setData(new byte[length]); + inBuffer.setLength(0); + inBuffer.setOffset(0); } else { throw new UnsupportedFormatException( "!Format.getDataType().equals(byte[].class)", - inputStreamFormat); + inStreamFormat); } } audioMixer.read( - inputStream, - inputBuffer, - inputStreamDesc.inputDataSourceDesc.inputDataSource); + inStream, + inBuffer, + inStreamDesc.inDataSourceDesc.inDataSource); /* - * If the media is to be discarded, don't even bother with the - * checks and the conversion. + * If the media is to be discarded, don't even bother with the checks + * and the conversion. */ - if (inputBuffer.isDiscard()) + if (inBuffer.isDiscard()) { - outputBuffer.setDiscard(true); + outBuffer.setDiscard(true); return; } - int inputLength = inputBuffer.getLength(); + int inLength = inBuffer.getLength(); - if (inputLength <= 0) + if (inLength <= 0) { - outputBuffer.setDiscard(true); + outBuffer.setDiscard(true); return; } - AudioFormat inputFormat = (AudioFormat) inputBuffer.getFormat(); + AudioFormat inFormat = (AudioFormat) inBuffer.getFormat(); - if (inputFormat == null) - inputFormat = inputStreamFormat; + if (inFormat == null) + inFormat = inStreamFormat; - if (logger.isTraceEnabled() - && (lastReadInputFormat != null) - && !lastReadInputFormat.matches(inputFormat)) + if (logger.isTraceEnabled()) { - lastReadInputFormat = inputFormat; - logger.trace( - "Read inputSamples in different format " - + lastReadInputFormat); + if (lastReadInFormat == null) + lastReadInFormat = inFormat; + else if (!lastReadInFormat.matches(inFormat)) + { + lastReadInFormat = inFormat; + logger.trace( + "Read inSamples in different format " + + lastReadInFormat); + } } - int inputFormatSigned = inputFormat.getSigned(); + int inFormatSigned = inFormat.getSigned(); - if ((inputFormatSigned != AudioFormat.SIGNED) - && (inputFormatSigned != Format.NOT_SPECIFIED)) + if ((inFormatSigned != AudioFormat.SIGNED) + && (inFormatSigned != Format.NOT_SPECIFIED)) { throw new UnsupportedFormatException( "AudioFormat.getSigned()", - inputFormat); + inFormat); } - int inputChannels = inputFormat.getChannels(); - int outputChannels = outputFormat.getChannels(); + int inChannels = inFormat.getChannels(); + int outChannels = outFormat.getChannels(); - if ((inputChannels != outputChannels) - && (inputChannels != Format.NOT_SPECIFIED) - && (outputChannels != Format.NOT_SPECIFIED)) + if ((inChannels != outChannels) + && (inChannels != Format.NOT_SPECIFIED) + && (outChannels != Format.NOT_SPECIFIED)) { logger.error( - "Read inputFormat with channels " - + inputChannels - + " while expected outputFormat channels is " - + outputChannels); + "Read inFormat with channels " + inChannels + + " while expected outFormat channels is " + + outChannels); throw new UnsupportedFormatException( "AudioFormat.getChannels()", - inputFormat); + inFormat); } // Warn about different sampleRates. - double inputSampleRate = inputFormat.getSampleRate(); - double outputSampleRate = outputFormat.getSampleRate(); + double inSampleRate = inFormat.getSampleRate(); + double outSampleRate = outFormat.getSampleRate(); - if (inputSampleRate != outputSampleRate) + if (inSampleRate != outSampleRate) { logger.warn( - "Read inputFormat with sampleRate " - + inputSampleRate - + " while expected outputFormat sampleRate is " - + outputSampleRate); + "Read inFormat with sampleRate " + inSampleRate + + " while expected outFormat sampleRate is " + + outSampleRate); } - Object inputData = inputBuffer.getData(); + Object inData = inBuffer.getData(); - if (inputData == null) + if (inData == null) { - outputBuffer.setDiscard(true); + outBuffer.setDiscard(true); } - else if (inputData instanceof byte[]) + else if (inData instanceof byte[]) { - int inputSampleSizeInBits = inputFormat.getSampleSizeInBits(); - int outputSampleSizeInBits = outputFormat.getSampleSizeInBits(); + int inSampleSizeInBits = inFormat.getSampleSizeInBits(); + int outSampleSizeInBits = outFormat.getSampleSizeInBits(); if (logger.isTraceEnabled() - && (inputSampleSizeInBits != outputSampleSizeInBits)) + && (inSampleSizeInBits != outSampleSizeInBits)) { logger.trace( - "Read inputFormat with sampleSizeInBits " - + inputSampleSizeInBits + "Read inFormat with sampleSizeInBits " + + inSampleSizeInBits + ". Will convert to sampleSizeInBits " - + outputSampleSizeInBits); + + outSampleSizeInBits); } - byte[] inputSamples = (byte[]) inputData; - int outputLength; - int[] outputSamples; + byte[] inSamples = (byte[]) inData; + int outLength; + int[] outSamples; - switch (inputSampleSizeInBits) + switch (inSampleSizeInBits) { case 16: - outputLength = inputLength / 2; - outputSamples - = validateIntArraySize(outputBuffer, outputLength); - for (int i = 0; i < outputLength; i++) + outLength = inLength / 2; + outSamples + = audioMixer.intArrayCache.validateIntArraySize( + outBuffer, + outLength); + for (int i = 0; i < outLength; i++) { - int sample = ArrayIOUtils.readInt16(inputSamples, i * 2); + int sample = ArrayIOUtils.readInt16(inSamples, i * 2); - switch (outputSampleSizeInBits) + switch (outSampleSizeInBits) { case 16: break; @@ -746,21 +813,23 @@ else if (inputData instanceof byte[]) default: throw new UnsupportedFormatException( "AudioFormat.getSampleSizeInBits()", - outputFormat); + outFormat); } - outputSamples[i] = sample; + outSamples[i] = sample; } break; case 32: - outputLength = inputSamples.length / 4; - outputSamples - = validateIntArraySize(outputBuffer, outputLength); - for (int i = 0; i < outputLength; i++) + outLength = inSamples.length / 4; + outSamples + = audioMixer.intArrayCache.validateIntArraySize( + outBuffer, + outLength); + for (int i = 0; i < outLength; i++) { - int sample = readInt(inputSamples, i * 4); + int sample = ArrayIOUtils.readInt(inSamples, i * 4); - switch (outputSampleSizeInBits) + switch (outSampleSizeInBits) { case 16: sample = Math.round(sample * SHORT_TO_INT_RATIO); @@ -772,10 +841,10 @@ else if (inputData instanceof byte[]) default: throw new UnsupportedFormatException( "AudioFormat.getSampleSizeInBits()", - outputFormat); + outFormat); } - outputSamples[i] = sample; + outSamples[i] = sample; } break; case 8: @@ -783,20 +852,20 @@ else if (inputData instanceof byte[]) default: throw new UnsupportedFormatException( "AudioFormat.getSampleSizeInBits()", - inputFormat); + inFormat); } - outputBuffer.setFlags(inputBuffer.getFlags()); - outputBuffer.setFormat(outputFormat); - outputBuffer.setLength(outputLength); - outputBuffer.setOffset(0); - outputBuffer.setTimeStamp(inputBuffer.getTimeStamp()); + outBuffer.setFlags(inBuffer.getFlags()); + outBuffer.setFormat(outFormat); + outBuffer.setLength(outLength); + outBuffer.setOffset(0); + outBuffer.setTimeStamp(inBuffer.getTimeStamp()); } else { throw new UnsupportedFormatException( - "Format.getDataType().equals(" + inputData.getClass() + ")", - inputFormat); + "Format.getDataType().equals(" + inData.getClass() + ")", + inFormat); } } @@ -804,10 +873,10 @@ else if (inputData instanceof byte[]) * Reads audio samples from the input <tt>PushBufferStream</tt>s of this * instance and converts them to a specific output <tt>AudioFormat</tt>. * - * @param outputFormat the <tt>AudioFormat</tt> in which the audio samples + * @param outFormat the <tt>AudioFormat</tt> in which the audio samples * read from the <tt>PushBufferStream</tt>s are to be converted before being * returned - * @param inputSampleDesc an <tt>InputSampleDesc</tt> which specifies the + * @param inSampleDesc an <tt>InSampleDesc</tt> which specifies the * input streams to be read and the collection of audio samples in which * the read audio samples are to be returned * @return the maximum number of audio samples actually read from the input @@ -815,30 +884,30 @@ else if (inputData instanceof byte[]) * @throws IOException if anything wrong happens while reading the specified * input streams * @throws UnsupportedFormatException if any of the input streams provides - * media in a format different than <tt>outputFormat</tt> + * media in a format different than <tt>outFormat</tt> */ - private int readInputPushBufferStreams( - AudioFormat outputFormat, - InputSampleDesc inputSampleDesc) + private int readInPushBufferStreams( + AudioFormat outFormat, + InSampleDesc inSampleDesc) throws IOException, UnsupportedFormatException { - InputStreamDesc[] inputStreams = inputSampleDesc.inputStreams; - Buffer buffer = inputSampleDesc.getBuffer(true); - int maxInputSampleCount = 0; - int[][] inputSamples = inputSampleDesc.inputSamples; + InStreamDesc[] inStreams = inSampleDesc.inStreams; + Buffer buffer = inSampleDesc.getBuffer(true); + int maxInSampleCount = 0; + int[][] inSamples = inSampleDesc.inSamples; - for (int i = 0; i < inputStreams.length; i++) + for (int i = 0; i < inStreams.length; i++) { - InputStreamDesc inputStreamDesc = inputStreams[i]; - SourceStream inputStream = inputStreamDesc.getInputStream(); + InStreamDesc inStreamDesc = inStreams[i]; + SourceStream inStream = inStreamDesc.getInStream(); - if (inputStream instanceof PushBufferStream) + if (inStream instanceof PushBufferStream) { buffer.setDiscard(false); buffer.setLength(0); - readInputPushBufferStream( - inputStreamDesc, outputFormat, maxInputSampleCount, + readInPushBufferStream( + inStreamDesc, outFormat, maxInSampleCount, buffer); int sampleCount; @@ -868,18 +937,16 @@ private int readInputPushBufferStreams( if ((TRACE_NON_CONTRIBUTING_READ_COUNT > 0) && logger.isTraceEnabled()) { - inputStreamDesc.nonContributingReadCount++; - if (inputStreamDesc.nonContributingReadCount + inStreamDesc.nonContributingReadCount++; + if (inStreamDesc.nonContributingReadCount >= TRACE_NON_CONTRIBUTING_READ_COUNT) { logger.trace( "Failed to read actual inputSamples more than " - + inputStreamDesc - .nonContributingReadCount + + inStreamDesc.nonContributingReadCount + " times from inputStream with hash code " - + inputStreamDesc - .getInputStream().hashCode()); - inputStreamDesc.nonContributingReadCount = 0; + + inStreamDesc.getInStream().hashCode()); + inStreamDesc.nonContributingReadCount = 0; } } } @@ -898,10 +965,10 @@ private int readInputPushBufferStreams( if (samples.length > sampleCount) Arrays.fill(samples, sampleCount, samples.length, 0); - inputSamples[i] = samples; + inSamples[i] = samples; - if (maxInputSampleCount < samples.length) - maxInputSampleCount = samples.length; + if (maxInSampleCount < samples.length) + maxInSampleCount = samples.length; /* * Convey the timeStamp so that it can be set to the Buffers @@ -910,35 +977,16 @@ private int readInputPushBufferStreams( * timeStamps, only use the first meaningful timestamp for * now. */ - if (inputSampleDesc.getTimeStamp() == Buffer.TIME_UNKNOWN) - inputSampleDesc.setTimeStamp(buffer.getTimeStamp()); + if (inSampleDesc.getTimeStamp() == Buffer.TIME_UNKNOWN) + inSampleDesc.setTimeStamp(buffer.getTimeStamp()); continue; } } - inputSamples[i] = null; + inSamples[i] = null; } - return maxInputSampleCount; - } - - /** - * Reads an integer from a specific series of bytes starting the reading at - * a specific offset in it. - * - * @param input the series of bytes to read an integer from - * @param inputOffset the offset in <tt>input</tt> at which the reading of - * the integer is to start - * @return an integer read from the specified series of bytes starting at - * the specified offset in it - */ - private static int readInt(byte[] input, int inputOffset) - { - return - (input[inputOffset + 3] << 24) - | ((input[inputOffset + 2] & 0xFF) << 16) - | ((input[inputOffset + 1] & 0xFF) << 8) - | (input[inputOffset] & 0xFF); + return maxInSampleCount; } /** @@ -946,21 +994,21 @@ private static int readInt(byte[] input, int inputOffset) * collection of such streams to which this instance pushes the data for * audio mixing it reads from its input <tt>SourceStream</tt>s. * - * @param outputStream the <tt>AudioMixingPushBufferStream</tt> to remove + * @param outStream the <tt>AudioMixingPushBufferStream</tt> to remove * from the collection of such streams to which this instance pushes the * data for audio mixing it reads from its input <tt>SourceStream</tt>s - * @throws IOException if <tt>outputStream</tt> was the last + * @throws IOException if <tt>outStream</tt> was the last * <tt>AudioMixingPushBufferStream</tt> and the <tt>AudioMixer</tt> failed * to stop */ - void removeOutputStream(AudioMixingPushBufferStream outputStream) + void removeOutStream(AudioMixingPushBufferStream outStream) throws IOException { - synchronized (outputStreams) + synchronized (outStreams) { - if ((outputStream != null) - && outputStreams.remove(outputStream) - && outputStreams.isEmpty()) + if ((outStream != null) + && outStreams.remove(outStream) + && outStreams.isEmpty()) audioMixer.stop(this); } } @@ -974,96 +1022,107 @@ void removeOutputStream(AudioMixingPushBufferStream outputStream) * the output mix are not pushed to the * <tt>AudioMixingPushBufferStream</tt>. * - * @param outputStream the <tt>AudioMixingPushBufferStream</tt> to push the + * @param outStream the <tt>AudioMixingPushBufferStream</tt> to push the * specified set of audio samples to - * @param inputSampleDesc the set of audio samples to be pushed to - * <tt>outputStream</tt> for audio mixing - * @param maxInputSampleCount the maximum number of audio samples available - * in <tt>inputSamples</tt> + * @param inSampleDesc the set of audio samples to be pushed to + * <tt>outStream</tt> for audio mixing + * @param maxInSampleCount the maximum number of audio samples available + * in <tt>inSamples</tt> */ - private void setInputSamples( - AudioMixingPushBufferStream outputStream, - InputSampleDesc inputSampleDesc, - int maxInputSampleCount) + private void setInSamples( + AudioMixingPushBufferStream outStream, + InSampleDesc inSampleDesc, + int maxInSampleCount) { - int[][] inputSamples = inputSampleDesc.inputSamples; - InputStreamDesc[] inputStreams = inputSampleDesc.inputStreams; + int[][] inSamples = inSampleDesc.inSamples; + InStreamDesc[] inStreams = inSampleDesc.inStreams; - inputSamples = inputSamples.clone(); + inSamples = inSamples.clone(); CaptureDevice captureDevice = audioMixer.captureDevice; - AudioMixingPushBufferDataSource outputDataSource - = outputStream.getDataSource(); - boolean outputDataSourceIsSendingDTMF + AudioMixingPushBufferDataSource outDataSource + = outStream.getDataSource(); + boolean outDataSourceIsSendingDTMF = (captureDevice instanceof AudioMixingPushBufferDataSource) - ? outputDataSource.isSendingDTMF() + ? outDataSource.isSendingDTMF() : false; - boolean outputDataSourceIsMute = outputDataSource.isMute(); + boolean outDataSourceIsMute = outDataSource.isMute(); - for (int i = 0; i < inputSamples.length; i++) + for (int i = 0, o = 0; i < inSamples.length; i++) { - InputStreamDesc inputStreamDesc = inputStreams[i]; - DataSource inputDataSource - = inputStreamDesc.inputDataSourceDesc.inputDataSource; + InStreamDesc inStreamDesc = inStreams[i]; + DataSource inDataSource + = inStreamDesc.inDataSourceDesc.inDataSource; - if (outputDataSourceIsSendingDTMF - && (inputDataSource == captureDevice)) + if (outDataSourceIsSendingDTMF && (inDataSource == captureDevice)) { - PushBufferStream inputStream - = (PushBufferStream) inputStreamDesc.getInputStream(); - AudioFormat inputStreamFormat - = (AudioFormat) inputStream.getFormat(); - - double sampleRate = inputStreamFormat.getSampleRate(); - int sampleSizeInBits = inputStreamFormat.getSampleSizeInBits(); - + PushBufferStream inStream + = (PushBufferStream) inStreamDesc.getInStream(); + AudioFormat inStreamFormat = (AudioFormat) inStream.getFormat(); // Generate the inband DTMF signal. - inputSamples[i] - = outputDataSource.getNextToneSignal( - sampleRate, - sampleSizeInBits); - if (maxInputSampleCount < inputSamples[i].length) - maxInputSampleCount = inputSamples[i].length; + int[] nextToneSignal + = outDataSource.getNextToneSignal( + inStreamFormat.getSampleRate(), + inStreamFormat.getSampleSizeInBits()); + + inSamples[i] = nextToneSignal; + if (maxInSampleCount < nextToneSignal.length) + maxInSampleCount = nextToneSignal.length; } - else if (outputDataSource.equals( - inputStreamDesc.getOutputDataSource()) - || (outputDataSourceIsMute - && (inputDataSource == captureDevice))) + else if (outDataSource.equals(inStreamDesc.getOutDataSource()) + || (outDataSourceIsMute && (inDataSource == captureDevice))) { - inputSamples[i] = null; + inSamples[i] = null; + } + + /* + * Have the samples of the contributing streams at the head of the + * sample set (and the non-contributing at the tail) in order to + * optimize determining the number of contributing streams later on + * and, consequently, the mixing. + */ + int[] inStreamSamples = inSamples[i]; + + if (inStreamSamples != null) + { + if (i != o) + { + inSamples[o] = inStreamSamples; + inSamples[i] = null; + } + o++; } } - outputStream.setInputSamples( - inputSamples, - maxInputSampleCount, - inputSampleDesc.getTimeStamp()); + outStream.setInSamples( + inSamples, + maxInSampleCount, + inSampleDesc.getTimeStamp()); } /** - * Sets the <tt>SourceStream</tt>s (in the form of <tt>InputStreamDesc</tt>) + * Sets the <tt>SourceStream</tt>s (in the form of <tt>InStreamDesc</tt>) * from which this instance is to read audio samples and push them to the * <tt>AudioMixingPushBufferStream</tt>s for audio mixing. * - * @param inputStreams the <tt>SourceStream</tt>s (in the form of - * <tt>InputStreamDesc</tt>) from which this instance is to read audio + * @param inStreams the <tt>SourceStream</tt>s (in the form of + * <tt>InStreamDesc</tt>) from which this instance is to read audio * samples and push them to the <tt>AudioMixingPushBufferStream</tt>s for * audio mixing */ - void setInputStreams(Collection<InputStreamDesc> inputStreams) + void setInStreams(Collection<InStreamDesc> inStreams) { - InputStreamDesc[] oldValue; - InputStreamDesc[] newValue - = (null == inputStreams) + InStreamDesc[] oldValue; + InStreamDesc[] newValue + = (null == inStreams) ? null - : inputStreams.toArray( - new InputStreamDesc[inputStreams.size()]); + : inStreams.toArray(new InStreamDesc[inStreams.size()]); - synchronized (inputStreamsSyncRoot) + synchronized (inStreamsSyncRoot) { - oldValue = this.inputStreams; + oldValue = this.inStreams; - this.inputStreams = newValue; + this.inStreams = newValue; } boolean valueIsChanged = !Arrays.equals(oldValue, newValue); @@ -1078,36 +1137,36 @@ void setInputStreams(Collection<InputStreamDesc> inputStreams) boolean skippedForTransferHandler = false; - for (InputStreamDesc inputStreamDesc : newValue) + for (InStreamDesc inStreamDesc : newValue) { - SourceStream inputStream = inputStreamDesc.getInputStream(); + SourceStream inStream = inStreamDesc.getInStream(); - if (!(inputStream instanceof PushBufferStream)) + if (!(inStream instanceof PushBufferStream)) continue; if (!skippedForTransferHandler) { skippedForTransferHandler = true; continue; } - if (!(inputStream instanceof CachingPushBufferStream)) + if (!(inStream instanceof CachingPushBufferStream)) { - PushBufferStream cachingInputStream + PushBufferStream cachingInStream = new CachingPushBufferStream( - (PushBufferStream) inputStream); + (PushBufferStream) inStream); - inputStreamDesc.setInputStream(cachingInputStream); + inStreamDesc.setInStream(cachingInStream); if (logger.isTraceEnabled()) logger.trace( "Created CachingPushBufferStream" + " with hashCode " - + cachingInputStream.hashCode() - + " for inputStream with hashCode " - + inputStream.hashCode()); + + cachingInStream.hashCode() + + " for inStream with hashCode " + + inStream.hashCode()); } } setTransferHandler(newValue, transferHandler); - equalizeInputStreamBufferLength(); + equalizeInStreamBufferLength(); if (logger.isTraceEnabled()) { @@ -1117,15 +1176,13 @@ void setInputStreams(Collection<InputStreamDesc> inputStreams) if (difference > 0) logger.trace( - "Added " - + difference - + " inputStream(s) and the total is " + "Added " + difference + + " inStream(s) and the total is " + newValueLength); else if (difference < 0) logger.trace( - "Removed " - + difference - + " inputStream(s) and the total is " + "Removed " + difference + + " inStream(s) and the total is " + newValueLength); } } @@ -1144,47 +1201,46 @@ else if (difference < 0) */ public void setTransferHandler(BufferTransferHandler transferHandler) { - throw - new UnsupportedOperationException( - AudioMixerPushBufferStream.class.getSimpleName() - + ".setTransferHandler(BufferTransferHandler)"); + throw new UnsupportedOperationException( + AudioMixerPushBufferStream.class.getSimpleName() + + ".setTransferHandler(BufferTransferHandler)"); } /** * Sets a specific <tt>BufferTransferHandler</tt> to a specific collection - * of <tt>SourceStream</tt>s (in the form of <tt>InputStreamDesc</tt>) + * of <tt>SourceStream</tt>s (in the form of <tt>InStreamDesc</tt>) * abstracting the differences among the various types of * <tt>SourceStream</tt>s. * - * @param inputStreams the input <tt>SourceStream</tt>s to which the + * @param inStreams the input <tt>SourceStream</tt>s to which the * specified <tt>BufferTransferHandler</tt> is to be set * @param transferHandler the <tt>BufferTransferHandler</tt> to be set to - * the specified <tt>inputStreams</tt> + * the specified <tt>inStreams</tt> */ private void setTransferHandler( - InputStreamDesc[] inputStreams, - BufferTransferHandler transferHandler) + InStreamDesc[] inStreams, + BufferTransferHandler transferHandler) { - if ((inputStreams == null) || (inputStreams.length <= 0)) + if ((inStreams == null) || (inStreams.length <= 0)) return; boolean transferHandlerIsSet = false; - for (InputStreamDesc inputStreamDesc : inputStreams) + for (InStreamDesc inStreamDesc : inStreams) { - SourceStream inputStream = inputStreamDesc.getInputStream(); + SourceStream inStream = inStreamDesc.getInStream(); - if (inputStream instanceof PushBufferStream) + if (inStream instanceof PushBufferStream) { - BufferTransferHandler inputStreamTransferHandler; - PushBufferStream inputPushBufferStream - = (PushBufferStream) inputStream; + BufferTransferHandler inStreamTransferHandler; + PushBufferStream inPushBufferStream + = (PushBufferStream) inStream; if (transferHandler == null) - inputStreamTransferHandler = null; + inStreamTransferHandler = null; else if (transferHandlerIsSet) { - inputStreamTransferHandler = new BufferTransferHandler() + inStreamTransferHandler = new BufferTransferHandler() { public void transferData(PushBufferStream stream) { @@ -1198,15 +1254,15 @@ public void transferData(PushBufferStream stream) } else { - inputStreamTransferHandler + inStreamTransferHandler = new StreamSubstituteBufferTransferHandler( - transferHandler, - inputPushBufferStream, - this); + transferHandler, + inPushBufferStream, + this); } - inputPushBufferStream.setTransferHandler( - inputStreamTransferHandler); + inPushBufferStream.setTransferHandler( + inStreamTransferHandler); transferHandlerIsSet = true; } @@ -1234,180 +1290,35 @@ protected void transferData(Buffer buffer) throw new UndeclaredThrowableException(ex); } - InputSampleDesc inputSampleDesc = (InputSampleDesc) buffer.getData(); - int[][] inputSamples = inputSampleDesc.inputSamples; - int maxInputSampleCount = buffer.getLength(); + InSampleDesc inSampleDesc = (InSampleDesc) buffer.getData(); + int[][] inSamples = inSampleDesc.inSamples; + int maxInSampleCount = buffer.getLength(); - if ((inputSamples == null) - || (inputSamples.length == 0) - || (maxInputSampleCount <= 0)) + if ((inSamples == null) + || (inSamples.length == 0) + || (maxInSampleCount <= 0)) return; - AudioMixingPushBufferStream[] outputStreams; + AudioMixingPushBufferStream[] outStreams; - synchronized (this.outputStreams) + synchronized (this.outStreams) { - outputStreams - = this.outputStreams.toArray( + outStreams + = this.outStreams.toArray( new AudioMixingPushBufferStream[ - this.outputStreams.size()]); + this.outStreams.size()]); } - for (AudioMixingPushBufferStream outputStream : outputStreams) - setInputSamples(outputStream, inputSampleDesc, maxInputSampleCount); + for (AudioMixingPushBufferStream outStream : outStreams) + setInSamples(outStream, inSampleDesc, maxInSampleCount); /* * The input samples have already been delivered to the output streams * and are no longer necessary. */ - for (int i = 0; i < inputSamples.length; i++) - { - deallocateIntArray(inputSamples[i]); - inputSamples[i] = null; - } - } - - private int[] validateIntArraySize(Buffer buffer, int newSize) - { - Object data = buffer.getData(); - int[] intArray; - - if (data instanceof int[]) - { - intArray = (int[]) data; - if (intArray.length < newSize) - { - deallocateIntArray(intArray); - intArray = null; - } - } - else - intArray = null; - if (intArray == null) - { - intArray = allocateIntArray(newSize); - buffer.setData(intArray); - } - return intArray; - } - - /** - * Describes a specific set of audio samples read from a specific set of - * input streams specified by their <tt>InputStreamDesc</tt>s. - */ - private static class InputSampleDesc - { - /** - * The <tt>Buffer</tt> into which media data is to be read from - * {@link #inputStreams}. - */ - private SoftReference<Buffer> buffer; - - /** - * The set of audio samples read from {@link #inputStreams}. - */ - public final int[][] inputSamples; - - /** - * The set of input streams from which {@link #inputSamples} were read. - */ - public final InputStreamDesc[] inputStreams; - - /** - * The time stamp of <tt>inputSamples</tt> to be reported in the - * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when - * mixes are read from them. - */ - private long timeStamp = Buffer.TIME_UNKNOWN; - - /** - * Initializes a new <tt>InputSampleDesc</tt> instance which is to - * describe a specific set of audio samples read from a specific set of - * input streams specified by their <tt>InputStreamDesc</tt>s. - * - * @param inputSamples the set of audio samples read from - * <tt>inputStreams</tt> - * @param inputStreams the set of input streams from which - * <tt>inputSamples</tt> were read - */ - public InputSampleDesc( - int[][] inputSamples, - InputStreamDesc[] inputStreams) + for (int i = 0; i < inSamples.length; i++) { - this.inputSamples = inputSamples; - this.inputStreams = inputStreams; - } - - /** - * Gets the <tt>Buffer</tt> into which media data is to be read from the - * input streams associated with this instance. - * - * @param create the indicator which determines whether the - * <tt>Buffer</tt> is to be created in case it does not exist - * @return the <tt>Buffer</tt> into which media data is to be read from - * the input streams associated with this instance - */ - public Buffer getBuffer(boolean create) - { - Buffer buffer = (this.buffer == null) ? null : this.buffer.get(); - - if ((buffer == null) && create) - { - buffer = new Buffer(); - setBuffer(buffer); - } - return buffer; - } - - /** - * Gets the time stamp of <tt>inputSamples</tt> to be reported in the - * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when - * mixes are read from them. - * - * @return the time stamp of <tt>inputSamples</tt> to be reported in the - * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when - * mixes are read from them - */ - public long getTimeStamp() - { - return timeStamp; - } - - /** - * Sets the <tt>Buffer</tt> into which media data is to be read from the - * input streams associated with this instance. - * - * @param buffer the <tt>Buffer</tt> into which media data is to be read - * from the input streams associated with this instance - */ - public void setBuffer(Buffer buffer) - { - this.buffer - = (buffer == null) ? null : new SoftReference<Buffer>(buffer); - } - - /** - * Sets the time stamp of <tt>inputSamples</tt> to be reported in the - * <tt>Buffer</tt>s of the <tt>AudioMixingPushBufferStream</tt>s when - * mixes are read from them. - * - * @param timeStamp the time stamp of <tt>inputSamples</tt> to be - * reported in the <tt>Buffer</tt>s of the - * <tt>AudioMixingPushBufferStream</tt>s when mixes are read from them - */ - public void setTimeStamp(long timeStamp) - { - if (this.timeStamp == Buffer.TIME_UNKNOWN) - this.timeStamp = timeStamp; - else - { - /* - * Setting the timeStamp more than once does not make sense - * because the inputStreams will report different timeStamps so - * only one should be picked up where the very reading from - * inputStreams takes place. - */ - throw new IllegalStateException("timeStamp"); - } + audioMixer.intArrayCache.deallocateIntArray(inSamples[i]); + inSamples[i] = null; } } } diff --git a/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferDataSource.java b/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferDataSource.java index 97b70379..802089af 100644 --- a/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferDataSource.java +++ b/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferDataSource.java @@ -24,7 +24,7 @@ * <tt>PushBufferStream</tt> containing the result of the audio mixing of * <tt>DataSource</tt>s. * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public class AudioMixingPushBufferDataSource extends PushBufferDataSource @@ -45,7 +45,7 @@ public class AudioMixingPushBufferDataSource * <tt>DataSource</tt>s and pushing the data of this output * <tt>PushBufferDataSource</tt>. */ - private final AudioMixer audioMixer; + final AudioMixer audioMixer; /** * The indicator which determines whether this <tt>DataSource</tt> is @@ -53,12 +53,18 @@ public class AudioMixingPushBufferDataSource */ private boolean connected; + /** + * The indicator which determines whether this <tt>DataSource</tt> is set + * to transmit "silence" instead of the actual media. + */ + private boolean mute = false; + /** * The one and only <tt>PushBufferStream</tt> this * <tt>PushBufferDataSource</tt> provides to its clients and containing the * result of the audio mixing performed by <tt>audioMixer</tt>. */ - private AudioMixingPushBufferStream outputStream; + private AudioMixingPushBufferStream outStream; /** * The indicator which determines whether this <tt>DataSource</tt> is @@ -66,16 +72,11 @@ public class AudioMixingPushBufferDataSource */ private boolean started; - /** - * The indicator which determines whether this <tt>DataSource</tt> is set - * to transmit "silence" instead of the actual media. - */ - private boolean mute = false; - /** * The tones to send via inband DTMF, if not empty. */ - private LinkedList<DTMFInbandTone> tones = new LinkedList<DTMFInbandTone>(); + private final LinkedList<DTMFInbandTone> tones + = new LinkedList<DTMFInbandTone>(); /** * Initializes a new <tt>AudioMixingPushBufferDataSource</tt> instance which @@ -91,30 +92,30 @@ public AudioMixingPushBufferDataSource(AudioMixer audioMixer) this.audioMixer = audioMixer; } + /** + * Adds a new inband DTMF tone to send. + * + * @param tone the DTMF tone to send. + */ + public void addDTMF(DTMFInbandTone tone) + { + tones.add(tone); + } + /** * Adds a new input <tt>DataSource</tt> to be mixed by the associated * <tt>AudioMixer</tt> of this instance and to not have its audio * contributions included in the mixing output represented by this * <tt>DataSource</tt>. * - * @param inputDataSource a <tt>DataSource</tt> to be added for mixing to + * @param inDataSource a <tt>DataSource</tt> to be added for mixing to * the <tt>AudioMixer</tt> associate with this instance and to not have its * audio contributions included in the mixing output represented by this * <tt>DataSource</tt> */ - public void addInputDataSource(DataSource inputDataSource) - { - audioMixer.addInputDataSource(inputDataSource, this); - } - - /** - * The input <tt>DataSource</tt> has been updated. - * @param inputDataSource the <tt>DataSource</tt> that was updated. - */ - public void updateInputDataSource(DataSource inputDataSource) + public void addInDataSource(DataSource inDataSource) { - // just update the input streams - audioMixer.getOutputStream(); + audioMixer.addInDataSource(inDataSource, this); } /** @@ -155,7 +156,7 @@ public synchronized void disconnect() if (connected) { - outputStream = null; + outStream = null; connected = false; audioMixer.disconnect(); @@ -240,12 +241,9 @@ else if ((formatControls == null) || (formatControls.length < 1)) Object[] controls = new Object[1 + formatControls.length]; controls[0] = bufferControl; - System - .arraycopy( - formatControls, - 0, - controls, - 1, + System.arraycopy( + formatControls, 0, + controls, 1, formatControls.length); return controls; } @@ -278,6 +276,20 @@ public FormatControl[] getFormatControls() return audioMixer.getFormatControls(); } + /** + * Gets the next inband DTMF tone signal. + * + * @param sampleRate The sampling frequency (codec clock rate) in Hz of the + * stream which will encapsulate this signal. + * @param sampleSizeInBits The size of each sample (8 for a byte, 16 for a + * short and 32 for an int) + * @return The data array containing the DTMF signal. + */ + public int[] getNextToneSignal(double sampleRate, int sampleSizeInBits) + { + return tones.poll().getAudioSamples(sampleRate, sampleSizeInBits); + } + /** * Implements {@link PushBufferDataSource#getStreams()}. Gets a * <tt>PushBufferStream</tt> which reads data from the associated @@ -290,38 +302,69 @@ public FormatControl[] getFormatControls() @Override public synchronized PushBufferStream[] getStreams() { - if (connected && (outputStream == null)) + if (connected && (outStream == null)) { - AudioMixerPushBufferStream audioMixerOutputStream - = audioMixer.getOutputStream(); + AudioMixerPushBufferStream audioMixerOutStream + = audioMixer.getOutStream(); - if (audioMixerOutputStream != null) + if (audioMixerOutStream != null) { - outputStream + outStream = new AudioMixingPushBufferStream( - audioMixerOutputStream, + audioMixerOutStream, this); if (started) try { - outputStream.start(); + outStream.start(); } catch (IOException ioex) { - logger - .error( + logger.error( "Failed to start " - + outputStream.getClass().getSimpleName() - + " with hashCode " - + outputStream.hashCode(), + + outStream.getClass().getSimpleName() + + " with hashCode " + outStream.hashCode(), ioex); } } } return - (outputStream == null) + (outStream == null) ? new PushBufferStream[0] - : new PushBufferStream[] { outputStream }; + : new PushBufferStream[] { outStream }; + } + + /** + * Determines whether this <tt>DataSource</tt> is mute. + * + * @return <tt>true</tt> if this <tt>DataSource</tt> is mute; otherwise, + * <tt>false</tt> + */ + public boolean isMute() + { + return mute; + } + + /** + * Determines whether this <tt>DataSource</tt> sends a DTMF tone. + * + * @return <tt>true</tt> if this <tt>DataSource</tt> is sending a DTMF tone; + * otherwise, <tt>false</tt>. + */ + public boolean isSendingDTMF() + { + return !tones.isEmpty(); + } + + /** + * Sets the mute state of this <tt>DataSource</tt>. + * + * @param mute <tt>true</tt> to mute this <tt>DataSource</tt>; otherwise, + * <tt>false</tt> + */ + public void setMute(boolean mute) + { + this.mute = mute; } /** @@ -340,8 +383,8 @@ public synchronized void start() if (!started) { started = true; - if (outputStream != null) - outputStream.start(); + if (outStream != null) + outStream.start(); } } @@ -361,72 +404,19 @@ public synchronized void stop() if (started) { started = false; - if (outputStream != null) - outputStream.stop(); - } - } - - /** - * Determines whether this <tt>DataSource</tt> is mute. - * - * @return <tt>true</tt> if this <tt>DataSource</tt> is mute; otherwise, - * <tt>false</tt> - */ - public boolean isMute() - { - return this.mute; - } - - /** - * Sets the mute state of this <tt>DataSource</tt>. - * - * @param mute <tt>true</tt> to mute this <tt>DataSource</tt>; otherwise, - * <tt>false</tt> - */ - public void setMute(boolean mute) - { - if (this.mute != mute) - { - this.mute = mute; + if (outStream != null) + outStream.stop(); } } /** - * Adds a new inband DTMF tone to send. - * - * @param tone the DTMF tone to send. - */ - public void addDTMF(DTMFInbandTone tone) - { - this.tones.add(tone); - } - - /** - * Determines whether this <tt>DataSource</tt> sends a DTMF tone. - * - * @return <tt>true</tt> if this <tt>DataSource</tt> is sending a DTMF tone; - * otherwise, <tt>false</tt>. - */ - public boolean isSendingDTMF() - { - return !this.tones.isEmpty(); - } - - /** - * Gets the next inband DTMF tone signal. - * - * @param samplingFrequency The sampling frequency (codec clock rate) in Hz - * of the stream which will encapsulate this signal. - * @param sampleSizeInBits The size of each sample (8 for a byte, 16 for a - * short and 32 for an int) + * The input <tt>DataSource</tt> has been updated. * - * @return The data array containing the DTMF signal. + * @param inDataSource the <tt>DataSource</tt> that was updated. */ - public int[] getNextToneSignal( - double samplingFrequency, - int sampleSizeInBits) + public void updateInDataSource(DataSource inDataSource) { - DTMFInbandTone tone = tones.poll(); - return tone.getAudioSamples(samplingFrequency, sampleSizeInBits); + // just update the input streams + audioMixer.getOutStream(); } } diff --git a/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferStream.java b/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferStream.java index cac446f4..a2f812d0 100644 --- a/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferStream.java +++ b/src/org/jitsi/impl/neomedia/conference/AudioMixingPushBufferStream.java @@ -7,6 +7,7 @@ package org.jitsi.impl.neomedia.conference; import java.io.*; +import java.util.*; import javax.media.*; import javax.media.format.*; @@ -34,6 +35,36 @@ public class AudioMixingPushBufferStream private static final Logger logger = Logger.getLogger(AudioMixingPushBufferStream.class); + /** + * Gets the maximum possible value for an audio sample of a specific + * <tt>AudioFormat</tt>. + * + * @param outFormat the <tt>AudioFormat</tt> of which to get the maximum + * possible value for an audio sample + * @return the maximum possible value for an audio sample of the specified + * <tt>AudioFormat</tt> + * @throws UnsupportedFormatException if the specified <tt>outFormat</tt> + * is not supported by the underlying implementation + */ + private static int getMaxOutSample(AudioFormat outFormat) + throws UnsupportedFormatException + { + switch(outFormat.getSampleSizeInBits()) + { + case 8: + return Byte.MAX_VALUE; + case 16: + return Short.MAX_VALUE; + case 32: + return Integer.MAX_VALUE; + case 24: + default: + throw new UnsupportedFormatException( + "Format.getSampleSizeInBits()", + outFormat); + } + } + /** * The <tt>AudioMixerPushBufferStream</tt> which reads data from the input * <tt>DataSource</tt>s and pushes it to this instance to be mixed. @@ -51,23 +82,23 @@ public class AudioMixingPushBufferStream * The collection of input audio samples still not mixed and read through * this <tt>AudioMixingPushBufferStream</tt>. */ - private int[][] inputSamples; + private int[][] inSamples; /** * The maximum number of per-stream audio samples available through - * <tt>inputSamples</tt>. + * <tt>inSamples</tt>. */ - private int maxInputSampleCount; + private int maxInSampleCount; /** * The <tt>Object</tt> which synchronizes the access to the data to be read - * from this <tt>PushBufferStream</tt> i.e. to {@link #inputSamples}, - * {@link #maxInputSampleCount} and {@link #timeStamp}. + * from this <tt>PushBufferStream</tt> i.e. to {@link #inSamples}, + * {@link #maxInSampleCount} and {@link #timeStamp}. */ private final Object readSyncRoot = new Object(); /** - * The time stamp of {@link #inputSamples} to be reported in the specified + * The time stamp of {@link #inSamples} to be reported in the specified * <tt>Buffer</tt> when data is read from this instance. */ private long timeStamp; @@ -93,8 +124,8 @@ public class AudioMixingPushBufferStream * input data to not be mixed in the output of the new instance */ AudioMixingPushBufferStream( - AudioMixerPushBufferStream audioMixerStream, - AudioMixingPushBufferDataSource dataSource) + AudioMixerPushBufferStream audioMixerStream, + AudioMixingPushBufferDataSource dataSource) { this.audioMixerStream = audioMixerStream; this.dataSource = dataSource; @@ -111,7 +142,7 @@ public class AudioMixingPushBufferStream public boolean endOfStream() { /* - * TODO If the inputSamples haven't been consumed yet, don't report the + * TODO If the inSamples haven't been consumed yet, don't report the * end of this stream even if the wrapped stream has reached its end. */ return audioMixerStream.endOfStream(); @@ -170,118 +201,102 @@ public AudioFormat getFormat() return audioMixerStream.getFormat(); } - /** - * Gets the maximum possible value for an audio sample of a specific - * <tt>AudioFormat</tt>. - * - * @param outputFormat the <tt>AudioFormat</tt> of which to get the maximum - * possible value for an audio sample - * @return the maximum possible value for an audio sample of the specified - * <tt>AudioFormat</tt> - * @throws UnsupportedFormatException if the specified <tt>outputFormat</tt> - * is not supported by the underlying implementation - */ - private static int getMaxOutputSample(AudioFormat outputFormat) - throws UnsupportedFormatException - { - switch(outputFormat.getSampleSizeInBits()) - { - case 8: - return Byte.MAX_VALUE; - case 16: - return Short.MAX_VALUE; - case 32: - return Integer.MAX_VALUE; - case 24: - default: - throw - new UnsupportedFormatException( - "Format.getSampleSizeInBits()", - outputFormat); - } - } - /** * Mixes as in audio mixing a specified collection of audio sample sets and * returns the resulting mix audio sample set in a specific * <tt>AudioFormat</tt>. * - * @param inputSamples the collection of audio sample sets to be mixed into + * @param inSamples the collection of audio sample sets to be mixed into * one audio sample set in the sense of audio mixing - * @param outputFormat the <tt>AudioFormat</tt> in which the resulting mix - * audio sample set is to be produced - * @param outputSampleCount the size of the resulting mix audio sample set + * @param outFormat the <tt>AudioFormat</tt> in which the resulting mix + * audio sample set is to be produced. The <tt>format</tt> property of the + * specified <tt>outBuffer</tt> is expected to be set to the same value but + * it is provided as a method argument in order to avoid casting from + * <tt>Format</tt> to <tt>AudioFormat</tt>. + * @param outSampleCount the size of the resulting mix audio sample set * to be produced * @return the resulting audio sample set of the audio mixing of the * specified input audio sample sets */ - private static int[] mix( - int[][] inputSamples, - AudioFormat outputFormat, - int outputSampleCount) + private int[] mix( + int[][] inSamples, + AudioFormat outFormat, + int outSampleCount) { - int[] outputSamples = new int[outputSampleCount]; + int[] outSamples + = dataSource.audioMixer.intArrayCache.allocateIntArray( + outSampleCount); /* * The trivial case of performing audio mixing the audio of a single * stream. Then there is nothing to mix and the input becomes the * output. */ - if (inputSamples.length == 1) + if ((inSamples.length == 1) || (inSamples[1] == null)) { - int[] inputStreamSamples = inputSamples[0]; + int[] inStreamSamples = inSamples[0]; + int inStreamSampleCount; - if (inputStreamSamples != null) + if (inStreamSamples == null) { + inStreamSampleCount = 0; + } + else + { + inStreamSampleCount + = Math.min(inStreamSamples.length, outSampleCount); System.arraycopy( - inputStreamSamples, 0, - outputSamples, 0, - inputStreamSamples.length); + inStreamSamples, 0, + outSamples, 0, + inStreamSampleCount); } - return outputSamples; + if (inStreamSampleCount != outSampleCount) + Arrays.fill(outSamples, inStreamSampleCount, outSampleCount, 0); + return outSamples; } - int maxOutputSample; + int maxOutSample; try { - maxOutputSample = getMaxOutputSample(outputFormat); + maxOutSample = getMaxOutSample(outFormat); } catch (UnsupportedFormatException ufex) { throw new UnsupportedOperationException(ufex); } - for (int[] inputStreamSamples : inputSamples) + Arrays.fill(outSamples, 0, outSampleCount, 0); + for (int[] inStreamSamples : inSamples) { - if (inputStreamSamples == null) + if (inStreamSamples == null) continue; - int inputStreamSampleCount = inputStreamSamples.length; + int inStreamSampleCount + = Math.min(inStreamSamples.length, outSampleCount); - if (inputStreamSampleCount <= 0) + if (inStreamSampleCount == 0) continue; - for (int i = 0; i < inputStreamSampleCount; i++) + for (int i = 0; i < inStreamSampleCount; i++) { - int inputStreamSample = inputStreamSamples[i]; - int outputSample = outputSamples[i]; - - outputSamples[i] - = inputStreamSample - + outputSample - - Math.round( - inputStreamSample - * (outputSample - / (float) maxOutputSample)); + int inStreamSample = inStreamSamples[i]; + int outSample = outSamples[i]; + + outSamples[i] + = inStreamSample + + outSample + - Math.round( + inStreamSample + * (outSample / (float) maxOutSample)); } } - return outputSamples; + return outSamples; } /** * Implements {@link PushBufferStream#read(Buffer)}. If - * <tt>inputSamples</tt> are available, mixes them and writes the mix to the + * <tt>inSamples</tt> are available, mixes them and writes the mix to the * specified <tt>Buffer</tt> performing the necessary data type conversions. * * @param buffer the <tt>Buffer</tt> to receive the data read from this @@ -292,77 +307,76 @@ private static int[] mix( public void read(Buffer buffer) throws IOException { - int[][] inputSamples; - int maxInputSampleCount; + int[][] inSamples; + int maxInSampleCount; long timeStamp; synchronized (readSyncRoot) { - inputSamples = this.inputSamples; - maxInputSampleCount = this.maxInputSampleCount; + inSamples = this.inSamples; + maxInSampleCount = this.maxInSampleCount; timeStamp = this.timeStamp; - this.inputSamples = null; - this.maxInputSampleCount = 0; + this.inSamples = null; + this.maxInSampleCount = 0; this.timeStamp = Buffer.TIME_UNKNOWN; } - int inputSampleCount = (inputSamples == null) ? 0 : inputSamples.length; - - if ((inputSampleCount == 0) - || (maxInputSampleCount <= 0)) + if ((inSamples == null) + || (inSamples.length == 0) + || (maxInSampleCount <= 0)) { - buffer.setLength(0); + buffer.setDiscard(true); return; } - AudioFormat outputFormat = getFormat(); - int[] outputSamples - = mix(inputSamples, outputFormat, maxInputSampleCount); - - Class<?> outputDataType = outputFormat.getDataType(); + AudioFormat outFormat = getFormat(); + int[] outSamples = mix(inSamples, outFormat, maxInSampleCount); + int outSampleCount = Math.min(maxInSampleCount, outSamples.length); - if (Format.byteArray.equals(outputDataType)) + if (Format.byteArray.equals(outFormat.getDataType())) { - byte[] outputData = null; + int outLength; Object o = buffer.getData(); + byte[] outData = null; + if (o instanceof byte[]) - outputData = (byte[]) o; + outData = (byte[]) o; - switch (outputFormat.getSampleSizeInBits()) + switch (outFormat.getSampleSizeInBits()) { case 16: - if (outputData == null || - outputData.length < outputSamples.length * 2) - outputData = new byte[outputSamples.length * 2]; - for (int i = 0; i < outputSamples.length; i++) - ArrayIOUtils.writeInt16(outputSamples[i], outputData, i * 2); + outLength = outSampleCount * 2; + if ((outData == null) || (outData.length < outLength)) + outData = new byte[outLength]; + for (int i = 0; i < outSampleCount; i++) + ArrayIOUtils.writeInt16(outSamples[i], outData, i * 2); break; case 32: - if (outputData == null || - outputData.length < outputSamples.length * 4) - outputData = new byte[outputSamples.length * 4]; - for (int i = 0; i < outputSamples.length; i++) - writeInt(outputSamples[i], outputData, i * 4); + outLength = outSampleCount * 4; + if ((outData == null) || (outData.length < outLength)) + outData = new byte[outLength]; + for (int i = 0; i < outSampleCount; i++) + ArrayIOUtils.writeInt(outSamples[i], outData, i * 4); break; case 8: case 24: default: - throw - new UnsupportedOperationException( - "AudioMixingPushBufferStream.read(Buffer)"); + throw new UnsupportedOperationException( + "AudioMixingPushBufferStream.read(Buffer)"); } - buffer.setData(outputData); - buffer.setFormat(outputFormat); - buffer.setLength(outputData.length); + buffer.setData(outData); + buffer.setFormat(outFormat); + buffer.setLength(outLength); buffer.setOffset(0); buffer.setTimeStamp(timeStamp); } else - throw - new UnsupportedOperationException( - "AudioMixingPushBufferStream.read(Buffer)"); + { + throw new UnsupportedOperationException( + "AudioMixingPushBufferStream.read(Buffer)"); + } } /** @@ -370,22 +384,19 @@ public void read(Buffer buffer) * audio mixing by this stream when data is read from it. Triggers a push to * the clients of this stream. * - * @param inputSamples the collection of audio sample sets to be mixed by + * @param inSamples the collection of audio sample sets to be mixed by * this stream when data is read from it - * @param maxInputSampleCount the maximum number of per-stream audio samples - * available through <tt>inputSamples</tt> - * @param timeStamp the time stamp of <tt>inputSamples</tt> to be reported + * @param maxInSampleCount the maximum number of per-stream audio samples + * available through <tt>inSamples</tt> + * @param timeStamp the time stamp of <tt>inSamples</tt> to be reported * in the specified <tt>Buffer</tt> when data is read from this instance */ - void setInputSamples( - int[][] inputSamples, - int maxInputSampleCount, - long timeStamp) + void setInSamples(int[][] inSamples, int maxInSampleCount, long timeStamp) { synchronized (readSyncRoot) { - this.inputSamples = inputSamples; - this.maxInputSampleCount = maxInputSampleCount; + this.inSamples = inSamples; + this.maxInSampleCount = maxInSampleCount; } BufferTransferHandler transferHandler = this.transferHandler; @@ -417,13 +428,10 @@ public void setTransferHandler(BufferTransferHandler transferHandler) synchronized void start() throws IOException { - audioMixerStream.addOutputStream(this); + audioMixerStream.addOutStream(this); if (logger.isTraceEnabled()) - logger - .trace( - "Started " - + getClass().getSimpleName() - + " with hashCode " + logger.trace( + "Started " + getClass().getSimpleName() + " with hashCode " + hashCode()); } @@ -436,32 +444,10 @@ synchronized void start() synchronized void stop() throws IOException { - audioMixerStream.removeOutputStream(this); + audioMixerStream.removeOutStream(this); if (logger.isTraceEnabled()) - logger - .trace( - "Stopped " - + getClass().getSimpleName() - + " with hashCode " + logger.trace( + "Stopped " + getClass().getSimpleName() + " with hashCode " + hashCode()); } - - /** - * Converts an integer to a series of bytes and writes the result into a - * specific output array of bytes starting the writing at a specific offset - * in it. - * - * @param input the integer to be written out as a series of bytes - * @param output the output to receive the conversion of the specified - * integer to a series of bytes - * @param outputOffset the offset in <tt>output</tt> at which the writing of - * the result of the conversion is to be started - */ - private static void writeInt(int input, byte[] output, int outputOffset) - { - output[outputOffset] = (byte) (input & 0xFF); - output[outputOffset + 1] = (byte) ((input >>> 8) & 0xFF); - output[outputOffset + 2] = (byte) ((input >>> 16) & 0xFF); - output[outputOffset + 3] = (byte) (input >> 24); - } } diff --git a/src/org/jitsi/impl/neomedia/conference/DataSourceFilter.java b/src/org/jitsi/impl/neomedia/conference/DataSourceFilter.java index 91609abf..a2552b24 100644 --- a/src/org/jitsi/impl/neomedia/conference/DataSourceFilter.java +++ b/src/org/jitsi/impl/neomedia/conference/DataSourceFilter.java @@ -12,11 +12,10 @@ * Represents a filter which determines whether a specific <tt>DataSource</tt> * is to be selected or deselected by the caller of the filter. * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ public interface DataSourceFilter { - /** * Determines whether a specific <tt>DataSource</tt> is accepted by this * filter i.e. whether the caller of this filter should include it in its diff --git a/src/org/jitsi/impl/neomedia/conference/InputDataSourceDesc.java b/src/org/jitsi/impl/neomedia/conference/InDataSourceDesc.java similarity index 71% rename from src/org/jitsi/impl/neomedia/conference/InputDataSourceDesc.java rename to src/org/jitsi/impl/neomedia/conference/InDataSourceDesc.java index af2d950b..4ebcd3d8 100644 --- a/src/org/jitsi/impl/neomedia/conference/InputDataSourceDesc.java +++ b/src/org/jitsi/impl/neomedia/conference/InDataSourceDesc.java @@ -26,24 +26,24 @@ * extracted into its own file for the sake of clarity. * </p> * - * @author Lubomir Marinov + * @author Lyubomir Marinov */ -class InputDataSourceDesc +class InDataSourceDesc { - /** - * The <tt>Logger</tt> used by the <tt>InputDataSourceDesc</tt> class and - * its instances for logging output. - */ - private static final Logger logger - = Logger.getLogger(InputDataSourceDesc.class); - /** * The constant which represents an empty array with <tt>SourceStream</tt> * element type. Explicitly defined in order to avoid unnecessary allocations. */ private static final SourceStream[] EMPTY_STREAMS = new SourceStream[0]; + /** + * The <tt>Logger</tt> used by the <tt>InDataSourceDesc</tt> class and + * its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(InDataSourceDesc.class); + /** * The indicator which determines whether the effective input * <tt>DataSource</tt> described by this instance is currently connected. @@ -60,41 +60,41 @@ class InputDataSourceDesc * The <tt>DataSource</tt> for which additional information is described by * this instance. */ - public final DataSource inputDataSource; + public final DataSource inDataSource; /** * The <tt>AudioMixingPushBufferDataSource</tt> in which the mix - * contributions of {@link #inputDataSource} are to not be included. + * contributions of {@link #inDataSource} are to not be included. */ - public final AudioMixingPushBufferDataSource outputDataSource; + public final AudioMixingPushBufferDataSource outDataSource; /** * The <tt>DataSource</tt>, if any, which transcodes the tracks of - * {@link #inputDataSource} in the output <tt>Format</tt> of the associated + * {@link #inDataSource} in the output <tt>Format</tt> of the associated * <tt>AudioMixer</tt>. */ private DataSource transcodingDataSource; /** - * Initializes a new <tt>InputDataSourceDesc</tt> instance which is to + * Initializes a new <tt>InDataSourceDesc</tt> instance which is to * describe additional information about a specific input * <tt>DataSource</tt> of an <tt>AudioMixer</tt>. Associates the specified * <tt>DataSource</tt> with the <tt>AudioMixingPushBufferDataSource</tt> in * which the mix contributions of the specified input <tt>DataSource</tt> * are to not be included. * - * @param inputDataSource a <tt>DataSourc</tt> for which additional + * @param inDataSource a <tt>DataSourc</tt> for which additional * information is to be described by the new instance - * @param outputDataSource the <tt>AudioMixingPushBufferDataSource</tt> in - * which the mix contributions of <tt>inputDataSource</tt> are to not be + * @param outDataSource the <tt>AudioMixingPushBufferDataSource</tt> in + * which the mix contributions of <tt>inDataSource</tt> are to not be * included */ - public InputDataSourceDesc( - DataSource inputDataSource, - AudioMixingPushBufferDataSource outputDataSource) + public InDataSourceDesc( + DataSource inDataSource, + AudioMixingPushBufferDataSource outDataSource) { - this.inputDataSource = inputDataSource; - this.outputDataSource = outputDataSource; + this.inDataSource = inDataSource; + this.outDataSource = outDataSource; } /** @@ -103,7 +103,7 @@ public InputDataSourceDesc( * effective input <tt>DataSource</tt> is to be asynchronously connected, * the completion of the connect procedure will be reported to the specified * <tt>AudioMixer</tt> by calling its - * {@link AudioMixer#connected(InputDataSourceDesc)}. + * {@link AudioMixer#connected(InDataSourceDesc)}. * * @param audioMixer the <tt>AudioMixer</tt> requesting the effective input * <tt>DataSource</tt> described by this instance to be connected @@ -113,12 +113,12 @@ public InputDataSourceDesc( synchronized void connect(final AudioMixer audioMixer) throws IOException { - final DataSource effectiveInputDataSource + final DataSource effectiveInDataSource = (transcodingDataSource == null) - ? inputDataSource + ? inDataSource : transcodingDataSource; - if (effectiveInputDataSource instanceof TranscodingDataSource) + if (effectiveInDataSource instanceof TranscodingDataSource) { if (connectThread == null) { @@ -129,28 +129,26 @@ public void run() { try { - audioMixer - .connect( - effectiveInputDataSource, - inputDataSource); - synchronized (InputDataSourceDesc.this) + audioMixer.connect( + effectiveInDataSource, + inDataSource); + synchronized (InDataSourceDesc.this) { connected = true; } - audioMixer.connected(InputDataSourceDesc.this); + audioMixer.connected(InDataSourceDesc.this); } catch (IOException ioex) { - logger - .error( - "Failed to connect to inputDataSource " - + MediaStreamImpl - .toString(inputDataSource), + logger.error( + "Failed to connect to inDataSource " + + MediaStreamImpl.toString( + inDataSource), ioex); } finally { - synchronized (InputDataSourceDesc.this) + synchronized (InDataSourceDesc.this) { if (connectThread == Thread.currentThread()) connectThread = null; @@ -164,7 +162,7 @@ public void run() } else { - audioMixer.connect(effectiveInputDataSource, inputDataSource); + audioMixer.connect(effectiveInDataSource, inDataSource); connected = true; } } @@ -174,18 +172,18 @@ public void run() * the input <tt>DataSource</tt> described by this instance into a specific * output <tt>Format</tt>. * - * @param outputFormat the <tt>Format</tt> in which the tracks of the input + * @param outFormat the <tt>Format</tt> in which the tracks of the input * <tt>DataSource</tt> described by this instance are to be transcoded * @return <tt>true</tt> if a new transcoding <tt>DataSource</tt> has been * created for the input <tt>DataSource</tt> described by this instance; * otherwise, <tt>false</tt> */ - synchronized boolean createTranscodingDataSource(Format outputFormat) + synchronized boolean createTranscodingDataSource(Format outFormat) { if (transcodingDataSource == null) { setTranscodingDataSource( - new TranscodingDataSource(inputDataSource, outputFormat)); + new TranscodingDataSource(inDataSource, outFormat)); return true; } else @@ -200,7 +198,7 @@ synchronized void disconnect() { if (connected) { - getEffectiveInputDataSource().disconnect(); + getEffectiveInDataSource().disconnect(); connected = false; } } @@ -218,12 +216,12 @@ synchronized void disconnect() */ public synchronized Object getControl(String controlType) { - DataSource effectiveInputDataSource = getEffectiveInputDataSource(); + DataSource effectiveInDataSource = getEffectiveInDataSource(); return - (effectiveInputDataSource == null) + (effectiveInDataSource == null) ? null - : effectiveInputDataSource.getControl(controlType); + : effectiveInDataSource.getControl(controlType); } /** @@ -235,14 +233,24 @@ public synchronized Object getControl(String controlType) * <tt>AudioMixer</tt> directly reads in order to retrieve the mix * contribution of the <tt>DataSource</tt> described by this instance */ - public synchronized DataSource getEffectiveInputDataSource() + public synchronized DataSource getEffectiveInDataSource() { return (transcodingDataSource == null) - ? inputDataSource + ? inDataSource : (connected ? transcodingDataSource : null); } + /** + * Returns this instance's <tt>inDataSource</tt> + * + * @return this instance's <tt>inDataSource</tt> + */ + public DataSource getInDataSource() + { + return inDataSource; + } + /** * Gets the <tt>SourceStream</tt>s of the effective input * <tt>DataSource</tt> described by this instance. @@ -255,18 +263,31 @@ public synchronized SourceStream[] getStreams() if (!connected) return EMPTY_STREAMS; - DataSource inputDataSource = getEffectiveInputDataSource(); + DataSource inDataSource = getEffectiveInDataSource(); - if (inputDataSource instanceof PushBufferDataSource) - return ((PushBufferDataSource) inputDataSource).getStreams(); - else if (inputDataSource instanceof PullBufferDataSource) - return ((PullBufferDataSource) inputDataSource).getStreams(); - else if (inputDataSource instanceof TranscodingDataSource) - return ((TranscodingDataSource) inputDataSource).getStreams(); + if (inDataSource instanceof PushBufferDataSource) + return ((PushBufferDataSource) inDataSource).getStreams(); + else if (inDataSource instanceof PullBufferDataSource) + return ((PullBufferDataSource) inDataSource).getStreams(); + else if (inDataSource instanceof TranscodingDataSource) + return ((TranscodingDataSource) inDataSource).getStreams(); else return null; } + /** + * Returns the <tt>TranscodingDataSource</tt> object used in this instance. + * + * @return the <tt>TranscodingDataSource</tt> object used in this instance. + */ + public TranscodingDataSource getTranscodingDataSource() + { + return + (transcodingDataSource == null) + ? null + : (TranscodingDataSource) transcodingDataSource; + } + /** * Sets the <tt>DataSource</tt>, if any, which transcodes the tracks of the * input <tt>DataSource</tt> described by this instance in the output @@ -294,7 +315,7 @@ synchronized void start() throws IOException { if (connected) - getEffectiveInputDataSource().start(); + getEffectiveInDataSource().start(); } /** @@ -308,28 +329,6 @@ synchronized void stop() throws IOException { if (connected) - getEffectiveInputDataSource().stop(); - } - - /** - * Returns this instance's <tt>inputDataSource</tt> - * - * @return this instance's <tt>inputDataSource</tt> - */ - public DataSource getInputDataSource() - { - return inputDataSource; - } - - /** - * Returns the <tt>TranscodingDataSource</tt> object used in this instance. - * - * @return the <tt>TranscodingDataSource</tt> object used in this instance. - */ - public TranscodingDataSource getTranscodingDataSource() - { - return (transcodingDataSource == null)? - null - : (TranscodingDataSource) transcodingDataSource; + getEffectiveInDataSource().stop(); } } diff --git a/src/org/jitsi/impl/neomedia/conference/InputStreamDesc.java b/src/org/jitsi/impl/neomedia/conference/InStreamDesc.java similarity index 78% rename from src/org/jitsi/impl/neomedia/conference/InputStreamDesc.java rename to src/org/jitsi/impl/neomedia/conference/InStreamDesc.java index 5cfacbb2..f2f43448 100644 --- a/src/org/jitsi/impl/neomedia/conference/InputStreamDesc.java +++ b/src/org/jitsi/impl/neomedia/conference/InStreamDesc.java @@ -24,11 +24,11 @@ * * @author Lyubomir Marinov */ -class InputStreamDesc +class InStreamDesc { /** * The <tt>Buffer</tt> into which media data is to be read from - * {@link #inputStream}. + * {@link #inStream}. */ private SoftReference<Buffer> buffer; @@ -36,13 +36,13 @@ class InputStreamDesc * The <tt>DataSource</tt> which created the <tt>SourceStream</tt> described * by this instance and additional information about it. */ - public final InputDataSourceDesc inputDataSourceDesc; + public final InDataSourceDesc inDataSourceDesc; /** * The <tt>SourceStream</tt> for which additional information is described * by this instance. */ - private SourceStream inputStream; + private SourceStream inStream; /** * The number of reads of this input stream which did not return any @@ -51,24 +51,24 @@ class InputStreamDesc long nonContributingReadCount; /** - * Initializes a new <tt>InputStreamDesc</tt> instance which is to describe + * Initializes a new <tt>InStreamDesc</tt> instance which is to describe * additional information about a specific input audio <tt>SourceStream</tt> * of an <tt>AudioMixer</tt>. Associates the specified <tt>SourceStream</tt> * with the <tt>DataSource</tt> which created it and additional information * about it. * - * @param inputStream a <tt>SourceStream</tt> for which additional + * @param inStream a <tt>SourceStream</tt> for which additional * information is to be described by the new instance - * @param inputDataSourceDesc the <tt>DataSource</tt> which created the + * @param inDataSourceDesc the <tt>DataSource</tt> which created the * <tt>SourceStream</tt> to be described by the new instance and additional * information about it */ - public InputStreamDesc( - SourceStream inputStream, - InputDataSourceDesc inputDataSourceDesc) + public InStreamDesc( + SourceStream inStream, + InDataSourceDesc inDataSourceDesc) { - this.inputStream = inputStream; - this.inputDataSourceDesc = inputDataSourceDesc; + this.inStream = inStream; + this.inDataSourceDesc = inDataSourceDesc; } /** @@ -97,9 +97,9 @@ public Buffer getBuffer(boolean create) * * @return the <tt>SourceStream</tt> described by this instance */ - public SourceStream getInputStream() + public SourceStream getInStream() { - return inputStream; + return inStream; } /** @@ -111,9 +111,9 @@ public SourceStream getInputStream() * contribution of the <tt>SourceStream</tt> described by this instance is * to not be included */ - public AudioMixingPushBufferDataSource getOutputDataSource() + public AudioMixingPushBufferDataSource getOutDataSource() { - return inputDataSourceDesc.outputDataSource; + return inDataSourceDesc.outDataSource; } /** @@ -132,17 +132,17 @@ public void setBuffer(Buffer buffer) /** * Sets the <tt>SourceStream</tt> to be described by this instance. * - * @param inputStream the <tt>SourceStream</tt> to be described by this + * @param inStream the <tt>SourceStream</tt> to be described by this * instance */ - public void setInputStream(SourceStream inputStream) + public void setInStream(SourceStream inStream) { - if (this.inputStream != inputStream) + if (this.inStream != inStream) { - this.inputStream = inputStream; + this.inStream = inStream; /* - * Since the inputStream has changed, one may argue that the Buffer + * Since the inStream has changed, one may argue that the Buffer * of the old value is not optimal for the new value. */ setBuffer(null); diff --git a/src/org/jitsi/impl/neomedia/conference/IntArrayCache.java b/src/org/jitsi/impl/neomedia/conference/IntArrayCache.java new file mode 100644 index 00000000..63a10759 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/conference/IntArrayCache.java @@ -0,0 +1,96 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.impl.neomedia.conference; + +import java.lang.ref.*; +import java.util.*; + +import javax.media.*; + +/** + * Caches <tt>int</tt> arrays for the purposes of reducing garbage collection. + * + * @author Lyubomir Marinov + */ +class IntArrayCache +{ + /** + * The cache of <tt>int</tt> arrays managed by this instance for the + * purposes of reducing garbage collection. + */ + private SoftReference<List<int[]>> intArrays; + + public synchronized int[] allocateIntArray(int minSize) + { + List<int[]> intArrays + = (this.intArrays == null) ? null : this.intArrays.get(); + + if (intArrays != null) + { + Iterator<int[]> i = intArrays.iterator(); + + while (i.hasNext()) + { + int[] intArray = i.next(); + + if (intArray.length >= minSize) + { + i.remove(); + return intArray; + } + } + } + + return new int[minSize]; + } + + public synchronized void deallocateIntArray(int[] intArray) + { + if (intArray == null) + return; + + List<int[]> intArrays; + + if ((this.intArrays == null) + || ((intArrays = this.intArrays.get()) == null)) + { + intArrays = new LinkedList<int[]>(); + this.intArrays = new SoftReference<List<int[]>>(intArrays); + } + + if (intArrays.size() != 0) + for (int[] element : intArrays) + if (element == intArray) + return; + + intArrays.add(intArray); + } + + public int[] validateIntArraySize(Buffer buffer, int newSize) + { + Object data = buffer.getData(); + int[] intArray; + + if (data instanceof int[]) + { + intArray = (int[]) data; + if (intArray.length < newSize) + { + deallocateIntArray(intArray); + intArray = null; + } + } + else + intArray = null; + if (intArray == null) + { + intArray = allocateIntArray(newSize); + buffer.setData(intArray); + } + return intArray; + } +} diff --git a/src/org/jitsi/impl/neomedia/device/AudioMediaDeviceImpl.java b/src/org/jitsi/impl/neomedia/device/AudioMediaDeviceImpl.java index 15b606e3..051367ff 100644 --- a/src/org/jitsi/impl/neomedia/device/AudioMediaDeviceImpl.java +++ b/src/org/jitsi/impl/neomedia/device/AudioMediaDeviceImpl.java @@ -146,14 +146,14 @@ protected synchronized CaptureDevice createCaptureDevice() captureDeviceSharing = createCaptureDeviceSharing(captureDevice); captureDevice - = captureDeviceSharing.createOutputDataSource(); + = captureDeviceSharing.createOutDataSource(); } } if ((captureDevice == null) && createCaptureDeviceIfNull) captureDevice = superCreateCaptureDevice(); } else - captureDevice = captureDeviceSharing.createOutputDataSource(); + captureDevice = captureDeviceSharing.createOutDataSource(); } return captureDevice; } diff --git a/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java b/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java index 31549f8c..75d10a42 100644 --- a/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java +++ b/src/org/jitsi/impl/neomedia/device/AudioMixerMediaDevice.java @@ -203,7 +203,7 @@ public void connect(DataSource captureDevice) @Override public AudioMixingPushBufferDataSource createOutputDataSource() { - return getAudioMixer().createOutputDataSource(); + return getAudioMixer().createOutDataSource(); } /** @@ -513,7 +513,7 @@ void removeInputDataSources(DataSourceFilter dataSourceFilter) AudioMixer audioMixer = this.audioMixer; if (audioMixer != null) - audioMixer.removeInputDataSources(dataSourceFilter); + audioMixer.removeInDataSources(dataSourceFilter); } /** @@ -673,7 +673,7 @@ public void addReceiveStream(ReceiveStream receiveStream) @Override protected DataSource createCaptureDevice() { - return getAudioMixer().getLocalOutputDataSource(); + return getAudioMixer().getLocalOutDataSource(); } /** @@ -1044,7 +1044,7 @@ protected void playbackDataSourceAdded(DataSource playbackDataSource) .getDataSource(); if (captureDevice instanceof AudioMixingPushBufferDataSource) ((AudioMixingPushBufferDataSource) captureDevice) - .addInputDataSource(playbackDataSource); + .addInDataSource(playbackDataSource); audioMixerMediaDeviceSession.addPlaybackDataSource( playbackDataSource); @@ -1094,7 +1094,7 @@ protected void playbackDataSourceUpdated(DataSource playbackDataSource) if (captureDevice instanceof AudioMixingPushBufferDataSource) { ((AudioMixingPushBufferDataSource) captureDevice) - .updateInputDataSource(playbackDataSource); + .updateInDataSource(playbackDataSource); } } diff --git a/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java b/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java index 4453a4e4..8edf7c74 100644 --- a/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java +++ b/src/org/jitsi/impl/neomedia/device/MediaDeviceSession.java @@ -1808,9 +1808,7 @@ public void addDTMF(DTMFInbandTone tone) DataSource captureDevice = this.captureDevice; if (captureDevice instanceof InbandDTMFDataSource) - { ((InbandDTMFDataSource) captureDevice).addDTMF(tone); - } } /** diff --git a/src/org/jitsi/service/neomedia/DTMFInbandTone.java b/src/org/jitsi/service/neomedia/DTMFInbandTone.java index 5d85db56..5475c0d2 100644 --- a/src/org/jitsi/service/neomedia/DTMFInbandTone.java +++ b/src/org/jitsi/service/neomedia/DTMFInbandTone.java @@ -6,6 +6,8 @@ */ package org.jitsi.service.neomedia; +import org.jitsi.service.protocol.*; + /** * Manages the generation of the inband DMTF signal. A signal is identified by a * value (1, 2, 3, 4, 5, 6, 7, 8, 9, *, #, A, B, C and D) and each signal is @@ -28,154 +30,148 @@ public class DTMFInbandTone /** * The first set of frequencies in Hz which composes an inband DTMF. */ - private static final double[] frequencyList1 = - new double[] - { - 697.0, 770.0, 852.0, 941.0 - }; + private static final double[] FREQUENCY_LIST_1 + = new double[] { 697.0, 770.0, 852.0, 941.0 }; /** * The second set of frequencies in Hz which composes an inband DTMF. */ - private static final double[] frequencyList2 = - new double[] - { - 1209.0, 1336.0, 1477.0, 1633.0 - }; + private static final double[] FREQUENCY_LIST_2 + = new double[] { 1209.0, 1336.0, 1477.0, 1633.0 }; /** * The "0" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_0 = new DTMFInbandTone("0", - frequencyList1[3], - frequencyList2[1]); + FREQUENCY_LIST_1[3], + FREQUENCY_LIST_2[1]); /** * The "1" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_1 = new DTMFInbandTone("1", - frequencyList1[0], - frequencyList2[0]); + FREQUENCY_LIST_1[0], + FREQUENCY_LIST_2[0]); /** * The "2" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_2 = new DTMFInbandTone("2", - frequencyList1[0], - frequencyList2[1]); + FREQUENCY_LIST_1[0], + FREQUENCY_LIST_2[1]); /** * The "3" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_3 = new DTMFInbandTone("3", - frequencyList1[0], - frequencyList2[2]); + FREQUENCY_LIST_1[0], + FREQUENCY_LIST_2[2]); /** * The "4" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_4 = new DTMFInbandTone("4", - frequencyList1[1], - frequencyList2[0]); + FREQUENCY_LIST_1[1], + FREQUENCY_LIST_2[0]); /** * The "5" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_5 = new DTMFInbandTone("5", - frequencyList1[1], - frequencyList2[1]); + FREQUENCY_LIST_1[1], + FREQUENCY_LIST_2[1]); /** * The "6" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_6 = new DTMFInbandTone("6", - frequencyList1[1], - frequencyList2[2]); + FREQUENCY_LIST_1[1], + FREQUENCY_LIST_2[2]); /** * The "7" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_7 = new DTMFInbandTone("7", - frequencyList1[2], - frequencyList2[0]); + FREQUENCY_LIST_1[2], + FREQUENCY_LIST_2[0]); /** * The "8" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_8 = new DTMFInbandTone("8", - frequencyList1[2], - frequencyList2[1]); + FREQUENCY_LIST_1[2], + FREQUENCY_LIST_2[1]); /** * The "9" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_9 = new DTMFInbandTone("9", - frequencyList1[2], - frequencyList2[2]); + FREQUENCY_LIST_1[2], + FREQUENCY_LIST_2[2]); /** * The "*" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_STAR = new DTMFInbandTone("*", - frequencyList1[3], - frequencyList2[0]); + FREQUENCY_LIST_1[3], + FREQUENCY_LIST_2[0]); /** * The "#" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_SHARP = new DTMFInbandTone("#", - frequencyList1[3], - frequencyList2[2]); + FREQUENCY_LIST_1[3], + FREQUENCY_LIST_2[2]); /** * The "A" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_A = new DTMFInbandTone("A", - frequencyList1[0], - frequencyList2[3]); + FREQUENCY_LIST_1[0], + FREQUENCY_LIST_2[3]); /** * The "B" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_B = new DTMFInbandTone("B", - frequencyList1[1], - frequencyList2[3]); + FREQUENCY_LIST_1[1], + FREQUENCY_LIST_2[3]); /** * The "C" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_C = new DTMFInbandTone("C", - frequencyList1[2], - frequencyList2[3]); + FREQUENCY_LIST_1[2], + FREQUENCY_LIST_2[3]); /** * The "D" DTMF Inband Tone. */ public static final DTMFInbandTone DTMF_INBAND_D = new DTMFInbandTone("D", - frequencyList1[3], - frequencyList2[3]); + FREQUENCY_LIST_1[3], + FREQUENCY_LIST_2[3]); /** * The default duration of an inband DTMF tone in ms. * 50 ms c.f. * http://nemesis.lonestar.org/reference/telecom/signaling/dtmf.html - * which cites the norm ANSI T1.401-1988 (but unavailable to me). - * But when testing it at 50 ms, the asterisk servers miss some DTMF tone - * impulses. Thus, set it up 150 ms. + * which cites the norm ANSI T1.401-1988. + * But when testing it at 50 ms, the Asterisk servers miss some DTMF tone + * impulses. Thus, set up to 150 ms. */ - private static final int toneDuration = 150; + private static final int TONE_DURATION = 150; /** * The default duration of an inband DTMF tone in ms. * 45 ms c.f. * http://nemesis.lonestar.org/reference/telecom/signaling/dtmf.html - * which cites the norm ANSI T1.401-1988 (but unavailable to me). - * Moreover the minimum duty cycle (signal tone + silence) for + * which cites the norm ANSI T1.401-1988. + * Moreover, the minimum duty cycle (signal tone + silence) for * ANSI-compliance shall be greater or equal to 100 ms. */ - private static final int interDigitInterval = 45; + private static final int INTER_DIGIT_INTERVAL = 45; /** * The value which identifies the current inband tone. Available values are @@ -314,48 +310,46 @@ public int getAudioSampleDiscrete( * Generates a signal sample for the current tone signal and stores it into * the byte data array. * - * @param samplingFrequency The sampling frequency (codec clock rate) in Hz - * of the stream which will encapsulate this signal. + * @param sampleRate The sampling frequency (codec clock rate) in Hz of the + * stream which will encapsulate this signal. * @param sampleSizeInBits The size of each sample (8 for a byte, 16 for a * short and 32 for an int) - * * @return The data array containing the DTMF signal. */ - public int[] getAudioSamples( - double samplingFrequency, - int sampleSizeInBits) + public int[] getAudioSamples(double sampleRate, int sampleSizeInBits) { - int sampleNumber = 0; - - int nbToneSamples = ((int) (samplingFrequency / 1000.0)) * - DTMFInbandTone.toneDuration; - int nbInterDigitSamples = ((int) (samplingFrequency / 1000.0)) * - DTMFInbandTone.interDigitInterval; - - int[] sampleData = - new int[nbInterDigitSamples + nbToneSamples + nbInterDigitSamples]; - - while(sampleNumber < nbInterDigitSamples) - { - sampleData[sampleNumber] = 0; - ++sampleNumber; - } - while(sampleNumber < nbInterDigitSamples + nbToneSamples) + /* + * TODO Rounding the sampleRate to an integer so early will lead to a + * very inaccurate or at least not quite expected number of samples. + */ + int kHz = (int) (sampleRate / 1000.0); + int nbToneSamples = kHz * DTMFInbandTone.TONE_DURATION; + int nbInterDigitSamples = kHz * DTMFInbandTone.INTER_DIGIT_INTERVAL; + + int[] samples + = new int[nbInterDigitSamples + nbToneSamples + nbInterDigitSamples]; + + /* + * The leading nbInterDigitSamples should be zeroes. They are because we + * have just allocated the array. + */ + for (int sampleNumber = nbInterDigitSamples, + endSampleNumber = nbInterDigitSamples + nbToneSamples; + sampleNumber < endSampleNumber; + sampleNumber++) { - sampleData[sampleNumber] = getAudioSampleDiscrete( - samplingFrequency, - sampleNumber, - sampleSizeInBits); - - ++sampleNumber; - } - while(sampleNumber < sampleData.length) - { - sampleData[sampleNumber] = 0; - ++sampleNumber; + samples[sampleNumber] + = getAudioSampleDiscrete( + sampleRate, + sampleNumber, + sampleSizeInBits); } + /* + * The trailing nbInterDigitSamples should be zeroes. They are because + * we have just allocated the array. + */ - return sampleData; + return samples; } /** @@ -366,58 +360,41 @@ public int[] getAudioSamples( * @return the corresponding DTMF tone which contains a value as an * identifier and two frequencies composing the inband tone. */ - public static DTMFInbandTone - mapTone(org.jitsi.service.protocol.DTMFTone tone) + public static DTMFInbandTone mapTone(DTMFTone tone) { - if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_0)) + if(tone.equals(DTMFTone.DTMF_0)) return DTMFInbandTone.DTMF_INBAND_0; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_1)) + else if(tone.equals(DTMFTone.DTMF_1)) return DTMFInbandTone.DTMF_INBAND_1; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_2)) + else if(tone.equals(DTMFTone.DTMF_2)) return DTMFInbandTone.DTMF_INBAND_2; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_3)) + else if(tone.equals(DTMFTone.DTMF_3)) return DTMFInbandTone.DTMF_INBAND_3; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_4)) + else if(tone.equals(DTMFTone.DTMF_4)) return DTMFInbandTone.DTMF_INBAND_4; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_5)) + else if(tone.equals(DTMFTone.DTMF_5)) return DTMFInbandTone.DTMF_INBAND_5; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_6)) + else if(tone.equals(DTMFTone.DTMF_6)) return DTMFInbandTone.DTMF_INBAND_6; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_7)) + else if(tone.equals(DTMFTone.DTMF_7)) return DTMFInbandTone.DTMF_INBAND_7; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_8)) + else if(tone.equals(DTMFTone.DTMF_8)) return DTMFInbandTone.DTMF_INBAND_8; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_9)) + else if(tone.equals(DTMFTone.DTMF_9)) return DTMFInbandTone.DTMF_INBAND_9; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_A)) + else if(tone.equals(DTMFTone.DTMF_A)) return DTMFInbandTone.DTMF_INBAND_A; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_B)) + else if(tone.equals(DTMFTone.DTMF_B)) return DTMFInbandTone.DTMF_INBAND_B; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_C)) + else if(tone.equals(DTMFTone.DTMF_C)) return DTMFInbandTone.DTMF_INBAND_C; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_D)) + else if(tone.equals(DTMFTone.DTMF_D)) return DTMFInbandTone.DTMF_INBAND_D; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_SHARP)) + else if(tone.equals(DTMFTone.DTMF_SHARP)) return DTMFInbandTone.DTMF_INBAND_SHARP; - else if(tone.equals( - org.jitsi.service.protocol.DTMFTone.DTMF_STAR)) + else if(tone.equals(DTMFTone.DTMF_STAR)) return DTMFInbandTone.DTMF_INBAND_STAR; - - return null; + else + return null; } } -- GitLab