aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip
diff options
context:
space:
mode:
authorLyubomir Marinov <lyubomir.marinov@jitsi.org>2009-06-09 09:09:43 +0000
committerLyubomir Marinov <lyubomir.marinov@jitsi.org>2009-06-09 09:09:43 +0000
commit8f4466385c7453ba3490c4cd74e7f6d534cf02fa (patch)
treec37a1f1b7e49211ae91753383f2649b72aa02386 /src/net/java/sip
parentba1a912b028aa12c2ed6578630ebb6e59b1e1f13 (diff)
downloadjitsi-8f4466385c7453ba3490c4cd74e7f6d534cf02fa.zip
jitsi-8f4466385c7453ba3490c4cd74e7f6d534cf02fa.tar.gz
jitsi-8f4466385c7453ba3490c4cd74e7f6d534cf02fa.tar.bz2
Represents the first take at implementing autio mixing for the purposes of conferencing. The functionality in question is still not being used and is to be employed later when the other conferencing bits are added.
Diffstat (limited to 'src/net/java/sip')
-rw-r--r--src/net/java/sip/communicator/impl/media/CaptureDeviceDelegatePushBufferDataSource.java70
-rw-r--r--src/net/java/sip/communicator/impl/media/MutePushBufferDataSource.java152
-rw-r--r--src/net/java/sip/communicator/impl/media/StreamSubstituteBufferTransferHandler.java84
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/AudioMixer.java1561
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferDataSource.java239
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferStream.java384
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/BufferStreamAdapter.java152
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/CachingPushBufferStream.java329
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/PullBufferStreamAdapter.java120
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/PushBufferStreamAdapter.java75
-rw-r--r--src/net/java/sip/communicator/impl/media/conference/TranscodingDataSource.java280
11 files changed, 3381 insertions, 65 deletions
diff --git a/src/net/java/sip/communicator/impl/media/CaptureDeviceDelegatePushBufferDataSource.java b/src/net/java/sip/communicator/impl/media/CaptureDeviceDelegatePushBufferDataSource.java
new file mode 100644
index 0000000..8917f80
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/CaptureDeviceDelegatePushBufferDataSource.java
@@ -0,0 +1,70 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media;
+
+import javax.media.*;
+import javax.media.control.*;
+import javax.media.protocol.*;
+
+/**
+ * Represents a <code>PushBufferDataSource</code> which is also a
+ * <code>CaptureDevice</code> through delegation to a specific
+ * <code>CaptureDevice</code>.
+ *
+ * @author Lubomir Marinov
+ */
+public abstract class CaptureDeviceDelegatePushBufferDataSource
+ extends PushBufferDataSource
+ implements CaptureDevice
+{
+
+ /**
+ * The <code>CaptureDevice</code> this instance delegates to in order to
+ * implement its <code>CaptureDevice</code> functionality.
+ */
+ private final CaptureDevice captureDevice;
+
+ /**
+ * Initializes a new <code>CaptureDeviceDelegatePushBufferDataSource</code>
+ * instance which delegates to a specific <code>CaptureDevice</code> in
+ * order to implement its <code>CaptureDevice</code> functionality.
+ *
+ * @param captureDevice the <code>CaptureDevice</code> the new instance is
+ * to delegate to in order to provide its
+ * <code>CaptureDevice</code> functionality
+ */
+ public CaptureDeviceDelegatePushBufferDataSource(
+ CaptureDevice captureDevice)
+ {
+ this.captureDevice = captureDevice;
+ }
+
+ /*
+ * Implements CaptureDevice#getCaptureDeviceInfo(). Delegates to the wrapped
+ * CaptureDevice if available; otherwise, returns null.
+ */
+ public CaptureDeviceInfo getCaptureDeviceInfo()
+ {
+ return
+ (captureDevice != null)
+ ? captureDevice.getCaptureDeviceInfo()
+ : null;
+ }
+
+ /*
+ * Implements CaptureDevice#getFormatControls(). Delegates to the wrapped
+ * CaptureDevice if available; otherwise, returns an empty array of
+ * FormatControl.
+ */
+ public FormatControl[] getFormatControls()
+ {
+ return
+ (captureDevice != null)
+ ? captureDevice.getFormatControls()
+ : new FormatControl[0];
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/MutePushBufferDataSource.java b/src/net/java/sip/communicator/impl/media/MutePushBufferDataSource.java
index c4f3d2b..1e83bbc 100644
--- a/src/net/java/sip/communicator/impl/media/MutePushBufferDataSource.java
+++ b/src/net/java/sip/communicator/impl/media/MutePushBufferDataSource.java
@@ -8,8 +8,8 @@ package net.java.sip.communicator.impl.media;
import java.io.*;
import java.util.*;
+
import javax.media.*;
-import javax.media.control.*;
import javax.media.protocol.*;
/**
@@ -24,8 +24,7 @@ import javax.media.protocol.*;
* @author Lubomir Marinov
*/
public class MutePushBufferDataSource
- extends PushBufferDataSource
- implements CaptureDevice
+ extends CaptureDeviceDelegatePushBufferDataSource
{
/**
@@ -47,62 +46,73 @@ public class MutePushBufferDataSource
*/
public MutePushBufferDataSource(PushBufferDataSource dataSource)
{
+ super(
+ (dataSource instanceof CaptureDevice)
+ ? (CaptureDevice) dataSource
+ : null);
+
this.dataSource = dataSource;
}
+ /*
+ * Implements DataSource#connect(). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public void connect() throws IOException
{
dataSource.connect();
}
+ /*
+ * Implements DataSource#disconnect(). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public void disconnect()
{
dataSource.disconnect();
}
- public CaptureDeviceInfo getCaptureDeviceInfo()
- {
- CaptureDeviceInfo captureDeviceInfo;
-
- if (dataSource instanceof CaptureDevice)
- captureDeviceInfo =
- ((CaptureDevice) dataSource).getCaptureDeviceInfo();
- else
- captureDeviceInfo = null;
- return captureDeviceInfo;
- }
-
+ /*
+ * Implements DataSource#getContentType(). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public String getContentType()
{
return dataSource.getContentType();
}
+ /*
+ * Implements DataSource#getControl(String). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public Object getControl(String controlType)
{
return dataSource.getControl(controlType);
}
+ /*
+ * Implements DataSource#getControls(). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public Object[] getControls()
{
return dataSource.getControls();
}
+ /*
+ * Implements DataSource#getDuration(). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public Time getDuration()
{
return dataSource.getDuration();
}
- public FormatControl[] getFormatControls()
- {
- FormatControl[] formatControls;
-
- if (dataSource instanceof CaptureDevice)
- formatControls = ((CaptureDevice) dataSource).getFormatControls();
- else
- formatControls = new FormatControl[0];
- return formatControls;
- }
-
+ /*
+ * Implements PushBufferDataSource#getStreams(). Wraps the streams of the
+ * wrapped PushBufferDataSource into MutePushBufferStream instances in order
+ * to provide mute support to them.
+ */
public PushBufferStream[] getStreams()
{
PushBufferStream[] streams = dataSource.getStreams();
@@ -136,11 +146,19 @@ public class MutePushBufferDataSource
this.mute = mute;
}
+ /*
+ * Implements DataSource#start(). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public void start() throws IOException
{
dataSource.start();
}
+ /*
+ * Implements DataSource#stop(). Delegates to the wrapped
+ * PushBufferDataSource.
+ */
public void stop() throws IOException
{
dataSource.stop();
@@ -171,36 +189,65 @@ public class MutePushBufferDataSource
this.stream = stream;
}
+ /*
+ * Implements SourceStream#getContentDescriptor(). Delegates to the
+ * wrapped PushBufferStream.
+ */
public ContentDescriptor getContentDescriptor()
{
return stream.getContentDescriptor();
}
+ /*
+ * Implements SourceStream#getContentLength(). Delegates to the wrapped
+ * PushBufferStream.
+ */
public long getContentLength()
{
return stream.getContentLength();
}
+ /*
+ * Implements Controls#getControl(String). Delegates to the wrapped
+ * PushBufferStream.
+ */
public Object getControl(String controlType)
{
return stream.getControl(controlType);
}
+ /*
+ * Implements Controls#getControls(). Delegates to the wrapped
+ * PushBufferStream.
+ */
public Object[] getControls()
{
return stream.getControls();
}
+ /*
+ * Implements PushBufferStream#getFormat(). Delegates to the wrapped
+ * PushBufferStream.
+ */
public Format getFormat()
{
return stream.getFormat();
}
+ /*
+ * Implements SourceStream#endOfStream(). Delegates to the wrapped
+ * PushBufferStream.
+ */
public boolean endOfStream()
{
return stream.endOfStream();
}
+ /*
+ * Implements PushBufferStream#read(Buffer). If this instance is muted
+ * (through its owning MutePushBufferDataSource), overwrites the data
+ * read from the wrapped PushBufferStream with silence data.
+ */
public void read(Buffer buffer) throws IOException
{
stream.read(buffer);
@@ -229,47 +276,22 @@ public class MutePushBufferDataSource
}
}
- public void setTransferHandler(BufferTransferHandler transferHandler)
- {
- stream.setTransferHandler((transferHandler == null) ? null
- : new MuteBufferTransferHandler(transferHandler));
- }
-
- /**
- * Implements a <tt>BufferTransferHandler</tt> wrapper which doesn't
- * expose a wrapped <tt>PushBufferStream</tt> but rather its wrapper in
- * order to give full control to the
- * {@link PushBufferStream#read(Buffer)} method of the wrapper.
+ /*
+ * Implements PushBufferStream#setTransferHandler(BufferTransferHandler).
+ * Sets up the hiding of the wrapped PushBufferStream from the specified
+ * transferHandler and thus gives this MutePushBufferStream full control
+ * when the transferHandler in question starts calling to the stream
+ * given to it in BufferTransferHandler#transferData(PushBufferStream).
*/
- public class MuteBufferTransferHandler
- implements BufferTransferHandler
+ public void setTransferHandler(BufferTransferHandler transferHandler)
{
-
- /**
- * The wrapped <tt>BufferTransferHandler</tt> which receives the
- * actual events from the wrapped <tt>PushBufferStream</tt>.
- */
- private final BufferTransferHandler transferHandler;
-
- /**
- * Initializes a new <tt>MuteBufferTransferHandler</tt> instance
- * which is to overwrite the source <tt>PushBufferStream</tt> of a
- * specific <tt>BufferTransferHandler</tt>.
- *
- * @param transferHandler the <tt>BufferTransferHandler</tt> the new
- * instance is to overwrite the source
- * <tt>PushBufferStream</tt> of
- */
- public MuteBufferTransferHandler(
- BufferTransferHandler transferHandler)
- {
- this.transferHandler = transferHandler;
- }
-
- public void transferData(PushBufferStream stream)
- {
- transferHandler.transferData(MutePushBufferStream.this);
- }
+ stream.setTransferHandler(
+ (transferHandler == null)
+ ? null
+ : new StreamSubstituteBufferTransferHandler(
+ transferHandler,
+ stream,
+ this));
}
}
}
diff --git a/src/net/java/sip/communicator/impl/media/StreamSubstituteBufferTransferHandler.java b/src/net/java/sip/communicator/impl/media/StreamSubstituteBufferTransferHandler.java
new file mode 100644
index 0000000..fd2e7ce
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/StreamSubstituteBufferTransferHandler.java
@@ -0,0 +1,84 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media;
+
+import javax.media.*;
+import javax.media.protocol.*;
+
+/**
+ * Implements a <tt>BufferTransferHandler</tt> wrapper which doesn't
+ * expose a <tt>PushBufferStream</tt> but rather a specific substitute in order
+ * to give full control to the {@link PushBufferStream#read(Buffer)} method of
+ * the substitute.
+ * <p>
+ * The purpose is achieved in <code>#transferData(PushBufferStream)</code>
+ * where the method argument <code>stream</code> is ignored and the substitute
+ * is used instead.
+ * </p>
+ *
+ * @author Lubomir Marinov
+ */
+public class StreamSubstituteBufferTransferHandler
+ implements BufferTransferHandler
+{
+
+ /**
+ * The <code>PushBufferStream</code> to be overridden for
+ * <code>transferHandler</code> with the <code>substitute</code> of this
+ * instance.
+ */
+ private final PushBufferStream stream;
+
+ /**
+ * The <code>PushBufferStream</code> to override the <code>stream</code> of
+ * this instance for <code>transferHandler</code>.
+ */
+ private final PushBufferStream substitute;
+
+ /**
+ * The wrapped <tt>BufferTransferHandler</tt> which receives the
+ * actual events from the wrapped <tt>PushBufferStream</tt>.
+ */
+ private final BufferTransferHandler transferHandler;
+
+ /**
+ * Initializes a new <tt>StreamSubstituteBufferTransferHandler</tt> instance
+ * which is to overwrite the source <tt>PushBufferStream</tt> of a specific
+ * <tt>BufferTransferHandler</tt>.
+ *
+ * @param transferHandler the <tt>BufferTransferHandler</tt> the new
+ * instance is to overwrite the source <tt>PushBufferStream</tt>
+ * of
+ * @param stream the <code>PushBufferStream</code> to be overridden for the
+ * specified <code>transferHandler</code> with the specified
+ * <code>substitute</code>
+ * @param substitute the <code>PushBufferStream</code> to override the
+ * specified <code>stream</code> for the specified
+ * <code>transferHandler</code>
+ */
+ public StreamSubstituteBufferTransferHandler(
+ BufferTransferHandler transferHandler,
+ PushBufferStream stream,
+ PushBufferStream substitute)
+ {
+ this.transferHandler = transferHandler;
+ this.stream = stream;
+ this.substitute = substitute;
+ }
+
+ /*
+ * Implements BufferTransferHandler#transferData(PushBufferStream). Puts in
+ * place the essence of the StreamSubstituteBufferTransferHandler class
+ * which is to report to the transferHandler from the same PushBufferStream
+ * to which it was set so that the substitute can gain full control.
+ */
+ public void transferData(PushBufferStream stream)
+ {
+ transferHandler.transferData(
+ (stream == this.stream) ? substitute : stream);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/AudioMixer.java b/src/net/java/sip/communicator/impl/media/conference/AudioMixer.java
new file mode 100644
index 0000000..93a27fd
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/AudioMixer.java
@@ -0,0 +1,1561 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.media.*;
+import javax.media.control.*;
+import javax.media.format.*;
+import javax.media.protocol.*;
+
+import net.java.sip.communicator.impl.media.*;
+
+/**
+ * Represents an audio mixer which manages the mixing of multiple audio streams
+ * i.e. it is able to output a single audio stream which contains the audio of
+ * multiple input audio streams.
+ * <p>
+ * The input audio streams are provided to the <code>AudioMixer</code> through
+ * {@link #addInputDataSource(DataSource)} in the form of input
+ * <code>DataSource</code>s giving access to one or more input
+ * <code>SourceStreams</code>.
+ * </p>
+ * <p>
+ * The output audio stream representing the mix of the multiple input audio
+ * streams is provided by the <code>AudioMixer</code> in the form of a
+ * <code>AudioMixingPushBufferDataSource</code> giving access to a
+ * <code>AudioMixingPushBufferStream</code>. Such an output is obtained through
+ * {@link #createOutputDataSource()}. The <code>AudioMixer</code> 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
+ */
+public class AudioMixer
+{
+
+ /**
+ * The default output <code>AudioFormat</code> in which
+ * <code>AudioMixer</code>, <code>AudioMixingPushBufferDataSource</code> and
+ * <code>AudioMixingPushBufferStream</code> output audio.
+ */
+ private static final AudioFormat DEFAULT_OUTPUT_FORMAT
+ = new AudioFormat(
+ AudioFormat.LINEAR,
+ 44100,
+ 16,
+ 2,
+ AudioFormat.LITTLE_ENDIAN,
+ AudioFormat.SIGNED);
+
+ /**
+ * The <code>CaptureDevice</code> capabilities provided by the
+ * <code>AudioMixingPushBufferDataSource</code>s created by this
+ * <code>AudioMixer</code>. JMF's
+ * <code>Manager.createMergingDataSource(DataSource[])</code> requires the
+ * interface implementation for audio if it is implemented for video and it
+ * is indeed the case for our use case of
+ * <code>AudioMixingPushBufferDataSource</code>.
+ */
+ private final CaptureDevice captureDevice;
+
+ /**
+ * The number of output <code>AudioMixingPushBufferDataSource</code>s
+ * reading from this <code>AudioMixer</code> which are connected. When the
+ * value is greater than zero, this <code>AudioMixer</code> is connected to
+ * the input <code>DataSource</code>s it manages.
+ */
+ private int connected;
+
+ /**
+ * The collection of input <code>DataSource</code>s this instance reads
+ * audio data from.
+ */
+ private final List<InputDataSourceDesc> inputDataSources
+ = new ArrayList<InputDataSourceDesc>();
+
+ /**
+ * The output <code>AudioMixerPushBufferStream</code> through which this
+ * instance pushes audio sample data to
+ * <code>AudioMixingPushBufferStream</code>s to be mixed.
+ */
+ private AudioMixerPushBufferStream outputStream;
+
+ /**
+ * Initializes a new <code>AudioMixer</code> instance. Because JMF's
+ * <code>Manager.createMergingDataSource(DataSource[])</code> requires the
+ * implementation of <code>CaptureDevice</code> for audio if it is
+ * implemented for video and it is indeed the cause for our use case of
+ * <code>AudioMixingPushBufferDataSource</code>, the new
+ * <code>AudioMixer</code> instance provides specified
+ * <code>CaptureDevice</code> capabilities to the
+ * <code>AudioMixingPushBufferDataSource</code>s it creates. The specified
+ * <code>CaptureDevice</code> is also added as the first input
+ * <code>DataSource</code> of the new instance.
+ *
+ * @param captureDevice the <code>CaptureDevice</code> capabilities to be
+ * provided to the <code>AudioMixingPushBufferDataSource</code>s
+ * created by the new instance and its first input
+ * <code>DataSource</code>
+ */
+ public AudioMixer(CaptureDevice captureDevice)
+ {
+ this.captureDevice = captureDevice;
+
+ addInputDataSource((DataSource) captureDevice);
+ }
+
+ /**
+ * Adds a new input <code>DataSource</code> to the collection of input
+ * <code>DataSource</code>s from which this instance reads audio. If the
+ * specified <code>DataSource</code> indeed provides audio, the respective
+ * contributions to the mix are always included.
+ *
+ * @param inputDataSource a new <code>DataSource</code> to input audio to
+ * this instance
+ */
+ public void addInputDataSource(DataSource inputDataSource)
+ {
+ addInputDataSource(inputDataSource, null);
+ }
+
+ /**
+ * Adds a new input <code>DataSource</code> to the collection of input
+ * <code>DataSource</code>s from which this instance reads audio. If the
+ * specified <code>DataSource</code> indeed provides audio, the respective
+ * contributions to the mix will be excluded from the mix output provided
+ * through a specific <code>AudioMixingPushBufferDataSource</code>.
+ *
+ * @param inputDataSource a new <code>DataSource</code> to input audio to
+ * this instance
+ * @param outputDataSource the <code>AudioMixingPushBufferDataSource</code>
+ * to not include the audio contributions of
+ * <code>inputDataSource</code> in the mix it outputs
+ */
+ void addInputDataSource(
+ DataSource inputDataSource,
+ AudioMixingPushBufferDataSource outputDataSource)
+ {
+ if (inputDataSource == null)
+ throw new IllegalArgumentException("inputDataSource");
+
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ if (inputDataSource.equals(inputDataSourceDesc.inputDataSource))
+ throw new IllegalArgumentException("inputDataSource");
+
+ inputDataSources.add(
+ new InputDataSourceDesc(
+ inputDataSource,
+ outputDataSource));
+ }
+
+ /**
+ * Notifies this <code>AudioMixer</code> that an output
+ * <code>AudioMixingPushBufferDataSource</code> reading from it has been
+ * connected. The first of the many
+ * <code>AudioMixingPushBufferDataSource</code>s reading from this
+ * <code>AudioMixer</code> which gets connected causes it to connect to the
+ * input <code>DataSource</code>s it manages.
+ *
+ * @throws IOException
+ */
+ void connect()
+ throws IOException
+ {
+ if (connected == 0)
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().connect();
+
+ connected++;
+ }
+
+ /**
+ * Creates a new <code>AudioMixingPushBufferDataSource</code> which gives
+ * access to a single audio stream representing the mix of the audio streams
+ * input into this <code>AudioMixer</code> through its input
+ * <code>DataSource</code>s. The returned
+ * <code>AudioMixingPushBufferDataSource</code> can also be used to include
+ * new input <code>DataSources</code> in this <code>AudioMixer</code> but
+ * have their contributions not included in the mix available through the
+ * returned <code>AudioMixingPushBufferDataSource</code>.
+ *
+ * @return a new <code>AudioMixingPushBufferDataSource</code> which gives
+ * access to a single audio stream representing the mix of the audio
+ * streams input into this <code>AudioMixer</code> through its input
+ * <code>DataSource</code>s
+ */
+ public AudioMixingPushBufferDataSource createOutputDataSource()
+ {
+ return new AudioMixingPushBufferDataSource(this);
+ }
+
+ /**
+ * Creates a <code>DataSource</code> which attempts to transcode the tracks
+ * of a specific input <code>DataSource</code> into a specific output
+ * <code>Format</code>.
+ *
+ * @param inputDataSource
+ * the <code>DataSource</code> from the tracks of which data is
+ * to be read and transcoded into the specified output
+ * <code>Format</code>
+ * @param outputFormat
+ * the <code>Format</code> in which the tracks of
+ * <code>inputDataSource</code> are to be transcoded
+ * @return a new <code>DataSource</code> which attempts to transcode the
+ * tracks of <code>inputDataSource</code> into
+ * <code>outputFormat</code>
+ * @throws IOException
+ */
+ private DataSource createTranscodingDataSource(
+ DataSource inputDataSource,
+ Format outputFormat)
+ throws IOException
+ {
+ TranscodingDataSource transcodingDataSource;
+
+ if (inputDataSource instanceof TranscodingDataSource)
+ transcodingDataSource = null;
+ else
+ {
+ transcodingDataSource
+ = new TranscodingDataSource(inputDataSource, outputFormat);
+
+ if (connected > 0)
+ transcodingDataSource.connect();
+ }
+ return transcodingDataSource;
+ }
+
+ /**
+ * Notifies this <code>AudioMixer</code> that an output
+ * <code>AudioMixingPushBufferDataSource</code> reading from it has been
+ * disconnected. The last of the many
+ * <code>AudioMixingPushBufferDataSource</code>s reading from this
+ * <code>AudioMixer</code> which gets disconnected causes it to disconnect
+ * from the input <code>DataSource</code>s it manages.
+ */
+ void disconnect()
+ {
+ if (connected <= 0)
+ return;
+
+ connected--;
+
+ if (connected == 0)
+ {
+ outputStream = null;
+
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().disconnect();
+ }
+ }
+
+ /**
+ * Gets the <code>CaptureDeviceInfo</code> of the <code>CaptureDevice</code>
+ * this <code>AudioMixer</code> provides through its output
+ * <code>AudioMixingPushBufferDataSource</code>s.
+ *
+ * @return the <code>CaptureDeviceInfo</code> of the
+ * <code>CaptureDevice</code> this <code>AudioMixer</code> provides
+ * through its output <code>AudioMixingPushBufferDataSource</code>s
+ */
+ CaptureDeviceInfo getCaptureDeviceInfo()
+ {
+ return captureDevice.getCaptureDeviceInfo();
+ }
+
+ /**
+ * Gets the content type of the data output by this <code>AudioMixer</code>.
+ *
+ * @return the content type of the data output by this
+ * <code>AudioMixer</code>
+ */
+ String getContentType()
+ {
+ return ContentDescriptor.RAW;
+ }
+
+ /**
+ * Gets the duration of each one of the output streams produced by this
+ * <code>AudioMixer</code>.
+ *
+ * @return the duration of each one of the output streams produced by this
+ * <code>AudioMixer</code>
+ */
+ Time getDuration()
+ {
+ Time duration = null;
+
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ {
+ Time inputDuration
+ = inputDataSourceDesc
+ .getEffectiveInputDataSource().getDuration();
+
+ if (Duration.DURATION_UNBOUNDED.equals(inputDuration)
+ || Duration.DURATION_UNKNOWN.equals(inputDuration))
+ return inputDuration;
+
+ if ((duration == null)
+ || (duration.getNanoseconds() < inputDuration.getNanoseconds()))
+ duration = inputDuration;
+ }
+ return (duration == null) ? Duration.DURATION_UNKNOWN : duration;
+ }
+
+ /**
+ * Gets the <code>Format</code> in which a specific <code>DataSource</code>
+ * provides stream data.
+ *
+ * @param dataSource
+ * the <code>DataSource</code> for which the <code>Format</code>
+ * in which it provides stream data is to be determined
+ * @return the <code>Format</code> in which the specified
+ * <code>dataSource</code> 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 <code>Format</code> in which a specific
+ * <code>SourceStream</code> provides data.
+ *
+ * @param stream
+ * the <code>SourceStream</code> for which the
+ * <code>Format</code> in which it provides data is to be
+ * determined
+ * @return the <code>Format</code> in which the specified
+ * <code>SourceStream</code> 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;
+ }
+
+ /**
+ * Gets an array of <code>FormatControl</code>s for the
+ * <code>CaptureDevice</code> this <code>AudioMixer</code> provides through
+ * its output <code>AudioMixingPushBufferDataSource</code>s.
+ *
+ * @return an array of <code>FormatControl</code>s for the
+ * <code>CaptureDevice</code> this <code>AudioMixer</code> provides
+ * through its output <code>AudioMixingPushBufferDataSource</code>s
+ */
+ FormatControl[] getFormatControls()
+ {
+ return captureDevice.getFormatControls();
+ }
+
+ /**
+ * Gets the <code>SourceStream</code>s (in the form of
+ * <code>InputStreamDesc</code>) of a specific <code>DataSource</code>
+ * (provided in the form of <code>InputDataSourceDesc</code>) which produce
+ * data in a specific <code>AudioFormat</code> (or a matching one).
+ *
+ * @param inputDataSourceDesc
+ * the <code>DataSource</code> (in the form of
+ * <code>InputDataSourceDesc</code>) which is to be examined for
+ * <code>SourceStreams</code> producing data in the specified
+ * <code>AudioFormat</code>
+ * @param outputFormat
+ * the <code>AudioFormat</code> in which the collected
+ * <code>SourceStream</code>s are to produce data
+ * @param inputStreams
+ * the <code>List</code> of <code>InputStreamDesc</code> in which
+ * the discovered <code>SourceStream</code>s are to be returned
+ * @return <tt>true</tt> if <code>SourceStream</code>s produced by the
+ * specified input <code>DataSource</code> and outputing data in the
+ * specified <code>AudioFormat</code> were discovered and reported
+ * in <code>inputStreams</code>; otherwise, <tt>false</tt>
+ */
+ private boolean getInputStreamsFromInputDataSource(
+ InputDataSourceDesc inputDataSourceDesc,
+ AudioFormat outputFormat,
+ List<InputStreamDesc> inputStreams)
+ {
+ DataSource inputDataSource
+ = inputDataSourceDesc.getEffectiveInputDataSource();
+ SourceStream[] inputDataSourceStreams;
+
+ if (inputDataSource instanceof PushBufferDataSource)
+ inputDataSourceStreams
+ = ((PushBufferDataSource) inputDataSource).getStreams();
+ else if (inputDataSource instanceof PullBufferDataSource)
+ inputDataSourceStreams
+ = ((PullBufferDataSource) inputDataSource).getStreams();
+ else if (inputDataSource instanceof TranscodingDataSource)
+ inputDataSourceStreams
+ = ((TranscodingDataSource) inputDataSource).getStreams();
+ else
+ inputDataSourceStreams = null;
+
+ if (inputDataSourceStreams != null)
+ {
+ boolean added = false;
+
+ for (SourceStream inputStream : inputDataSourceStreams)
+ {
+ Format inputFormat = getFormat(inputStream);
+
+ if ((inputFormat != null)
+ && matches(inputFormat, outputFormat)
+ && inputStreams.add(
+ new InputStreamDesc(
+ inputStream,
+ inputDataSourceDesc)))
+ added = true;
+ }
+ return added;
+ }
+
+ Format inputFormat = getFormat(inputDataSource);
+
+ if ((inputFormat != null) && !matches(inputFormat, outputFormat))
+ {
+ if (inputDataSource instanceof PushDataSource)
+ {
+ for (PushSourceStream inputStream
+ : ((PushDataSource) inputDataSource).getStreams())
+ inputStreams.add(
+ new InputStreamDesc(
+ new PushBufferStreamAdapter(
+ inputStream,
+ inputFormat),
+ inputDataSourceDesc));
+ return true;
+ }
+ if (inputDataSource instanceof PullDataSource)
+ {
+ for (PullSourceStream inputStream
+ : ((PullDataSource) inputDataSource).getStreams())
+ inputStreams.add(
+ new InputStreamDesc(
+ new PullBufferStreamAdapter(
+ inputStream,
+ inputFormat),
+ inputDataSourceDesc));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the <code>SourceStream</code>s (in the form of
+ * <code>InputStreamDesc</code>) of the <code>DataSource</code>s from which
+ * this <code>AudioMixer</code> reads data which produce data in a specific
+ * <code>AudioFormat</code>. When an input <code>DataSource</code> does not
+ * have such <code>SourceStream</code>s, an attempt is made to transcode its
+ * tracks so that such <code>SourceStream</code>s can be retrieved from it
+ * after transcoding.
+ *
+ * @param outputFormat
+ * the <code>AudioFormat</code> in which the retrieved
+ * <code>SourceStream</code>s are to produce data
+ * @return a new collection of <code>SourceStream</code>s (in the form of
+ * <code>InputStreamDesc</code>) retrieved from the input
+ * <code>DataSource</code>s of this <code>AudioMixer</code> and
+ * producing data in the specified <code>AudioFormat</code>
+ * @throws IOException
+ */
+ private Collection<InputStreamDesc> getInputStreamsFromInputDataSources(
+ AudioFormat outputFormat)
+ throws IOException
+ {
+ List<InputStreamDesc> inputStreams = new ArrayList<InputStreamDesc>();
+
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ {
+ boolean got
+ = getInputStreamsFromInputDataSource(
+ inputDataSourceDesc,
+ outputFormat,
+ inputStreams);
+
+ if (!got)
+ {
+ DataSource transcodingDataSource
+ = createTranscodingDataSource(
+ inputDataSourceDesc.getEffectiveInputDataSource(),
+ outputFormat);
+
+ if (transcodingDataSource != null)
+ {
+ inputDataSourceDesc.setTranscodingDataSource(
+ transcodingDataSource);
+
+ getInputStreamsFromInputDataSource(
+ inputDataSourceDesc,
+ outputFormat,
+ inputStreams);
+ }
+ }
+ }
+ return inputStreams;
+ }
+
+ /**
+ * Gets the <code>AudioFormat</code> in which the input
+ * <code>DataSource</code>s of this <code>AudioMixer</code> can produce data
+ * and which is to be the output <code>Format</code> of this
+ * <code>AudioMixer</code>.
+ *
+ * @return the <code>AudioFormat</code> in which the input
+ * <code>DataSource</code>s of this <code>AudioMixer</code> can
+ * produce data and which is to be the output <code>Format</code> of
+ * this <code>AudioMixer</code>
+ */
+ private AudioFormat getOutputFormatFromInputDataSources()
+ {
+ // TODO Auto-generated method stub
+ return DEFAULT_OUTPUT_FORMAT;
+ }
+
+ /**
+ * Gets the <code>AudioMixerPushBufferStream</code>, first creating it if it
+ * does not exist already, which reads data from the input
+ * <code>DataSource</code>s of this <code>AudioMixer</code> and pushes it to
+ * output <code>AudioMixingPushBufferStream</code>s for audio mixing.
+ *
+ * @return the <code>AudioMixerPushBufferStream</code> which reads data from
+ * the input <code>DataSource</code>s of this
+ * <code>AudioMixer</code> and pushes it to output
+ * <code>AudioMixingPushBufferStream</code>s for audio mixing
+ */
+ AudioMixerPushBufferStream getOutputStream()
+ {
+ AudioFormat outputFormat = getOutputFormatFromInputDataSources();
+
+ setOutputFormatToInputDataSources(outputFormat);
+
+ Collection<InputStreamDesc> inputStreams;
+
+ try
+ {
+ inputStreams = getInputStreamsFromInputDataSources(outputFormat);
+ }
+ catch (IOException ex)
+ {
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ if (inputStreams.size() <= 0)
+ outputStream = null;
+ else
+ {
+ if (outputStream == null)
+ outputStream = new AudioMixerPushBufferStream(outputFormat);
+ outputStream.setInputStreams(inputStreams);
+ }
+ return outputStream;
+ }
+
+ /**
+ * Determines whether a specific <code>Format</code> matches a specific
+ * <code>Format</code> in the sense of JMF <code>Format</code> matching.
+ * Since this <code>AudioMixer</code> and the audio mixing functionality
+ * related to it can handle varying characteristics of a certain output
+ * <code>Format</code>, the only requirement for the specified
+ * <code>Format</code>s to match is for both of them to have one and the
+ * same encoding.
+ *
+ * @param input
+ * the <code>Format</code> for which it is required to determine
+ * whether it matches a specific <code>Format</code>
+ * @param pattern
+ * the <code>Format</code> against which the specified
+ * <code>input</code> is to be matched
+ * @return <tt>true</tt> if the specified
+ * <code>input<code> matches the specified <code>pattern</code> in
+ * the sense of JMF <code>Format</code> matching; otherwise,
+ * <tt>false</tt>
+ */
+ private boolean matches(Format input, AudioFormat pattern)
+ {
+ return
+ ((input instanceof AudioFormat) && input.isSameEncoding(pattern));
+ }
+
+ /**
+ * 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 <code>input</code> 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);
+ }
+
+ /**
+ * 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 <code>input</code> at which the reading of the
+ * short integer is to start
+ * @return a short integer in the form of
+ * <tt>int</code> read from the specified series of bytes starting at the specified offset in it
+ */
+ private static int readShort(byte[] input, int inputOffset)
+ {
+ return
+ (short)
+ ((input[inputOffset + 1] << 8)
+ | (input[inputOffset] & 0x00FF));
+ }
+
+ /**
+ * Sets a specific <code>AudioFormat</code>, if possible, as the output
+ * format of the input <code>DataSource</code>s of this
+ * <code>AudioMixer</code> in an attempt to not have to perform explicit
+ * transcoding of the input <code>SourceStream</code>s.
+ *
+ * @param outputFormat
+ * the <code>AudioFormat</code> in which the input
+ * <code>DataSource</code>s of this <code>AudioMixer</code> are
+ * to be instructed to output
+ */
+ private void setOutputFormatToInputDataSources(AudioFormat outputFormat)
+ {
+ String formatControlType = FormatControl.class.getName();
+
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ {
+ DataSource inputDataSource
+ = inputDataSourceDesc.getEffectiveInputDataSource();
+ FormatControl formatControl
+ = (FormatControl) inputDataSource.getControl(formatControlType);
+
+ if (formatControl != null)
+ {
+ Format inputFormat = formatControl.getFormat();
+
+ if ((inputFormat == null)
+ || !matches(inputFormat, outputFormat))
+ formatControl.setFormat(outputFormat);
+ }
+ }
+ }
+
+ /**
+ * Starts the input <code>DataSource</code>s of this <code>AudioMixer</code>.
+ *
+ * @throws IOException
+ */
+ void start()
+ throws IOException
+ {
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().start();
+ }
+
+ /**
+ * Stops the input <code>DataSource</code>s of this <code>AudioMixer</code>.
+ *
+ * @throws IOException
+ */
+ void stop()
+ throws IOException
+ {
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().stop();
+ }
+
+ /**
+ * Represents a <code>PushBufferStream</code> which reads data from the
+ * <code>SourceStream</code>s of the input <code>DataSource</code>s of this
+ * <code>AudioMixer</code> and pushes it to
+ * <code>AudioMixingPushBufferStream</code>s for audio mixing.
+ */
+ class AudioMixerPushBufferStream
+ implements PushBufferStream
+ {
+
+ /**
+ * The factor which scales a <tt>short</tt> value to an <tt>int</tt>
+ * value.
+ */
+ private static final float INT_TO_SHORT_RATIO
+ = Integer.MAX_VALUE / (float) Short.MAX_VALUE;
+
+ /**
+ * The factor which scales an <tt>int</tt> value to a <tt>short</tt>
+ * value.
+ */
+ private static final float SHORT_TO_INT_RATIO
+ = Short.MAX_VALUE / (float) Integer.MAX_VALUE;
+
+ /**
+ * The <code>SourceStream</code>s (in the form of
+ * <code>InputStreamDesc</code> so that this instance can track back the
+ * <code>AudioMixingPushBufferDataSource</code> which outputs the mixed
+ * audio stream and determine whether the associated
+ * <code>SourceStream</code> is to be included into the mix) from which
+ * this instance reads its data.
+ */
+ private InputStreamDesc[] inputStreams;
+
+ /**
+ * The <code>AudioFormat</code> of the data this instance outputs.
+ */
+ private final AudioFormat outputFormat;
+
+ /**
+ * The <code>AudioMixingPushBufferStream</code>s to which this instance
+ * pushes data for audio mixing.
+ */
+ private final List<AudioMixingPushBufferStream> outputStreams
+ = new ArrayList<AudioMixingPushBufferStream>();
+
+ /**
+ * The <code>BufferTransferHandler</code> through which this instance
+ * gets notifications from its input <code>SourceStream</code>s that new
+ * data is available for audio mixing.
+ */
+ private final BufferTransferHandler transferHandler
+ = new BufferTransferHandler()
+ {
+ public void transferData(PushBufferStream stream)
+ {
+ AudioMixerPushBufferStream.this.transferData();
+ }
+ };
+
+ /**
+ * Initializes a new <code>AudioMixerPushBufferStream</code> instance to
+ * output data in a specific <code>AudioFormat</code>.
+ *
+ * @param outputFormat
+ * the <code>AudioFormat</code> in which the new instance is
+ * to output data
+ */
+ private AudioMixerPushBufferStream(AudioFormat outputFormat)
+ {
+ this.outputFormat = outputFormat;
+ }
+
+ /**
+ * Adds a specific <code>AudioMixingPushBufferStream</code> to the
+ * collection of such streams which this instance is to push the data
+ * for audio mixing it reads from its input <code>SourceStream</code>s.
+ *
+ * @param outputStream
+ * the <code>AudioMixingPushBufferStream</code> to be added
+ * to the collection of such streams which this instance is
+ * to push the data for audio mixing it reads from its input
+ * <code>SourceStream</code>s
+ */
+ void addOutputStream(AudioMixingPushBufferStream outputStream)
+ {
+ if (outputStream == null)
+ throw new IllegalArgumentException("outputStream");
+
+ synchronized (outputStreams)
+ {
+ if (!outputStreams.contains(outputStream))
+ outputStreams.add(outputStream);
+ }
+ }
+
+ /*
+ * Implements SourceStream#endOfStream(). Delegates to the input
+ * SourceStreams of this instance.
+ */
+ public boolean endOfStream()
+ {
+ if (inputStreams != null)
+ for (InputStreamDesc inputStreamDesc : inputStreams)
+ if (!inputStreamDesc.getInputStream().endOfStream())
+ return false;
+ return true;
+ }
+
+ /*
+ * Implements SourceStream#getContentDescriptor(). Returns a
+ * ContentDescriptor which describes the content type of this instance.
+ */
+ public ContentDescriptor getContentDescriptor()
+ {
+ return
+ new ContentDescriptor(AudioMixer.this.getContentType());
+ }
+
+ /*
+ * Implements SourceStream#getContentLength(). Delegates to the input
+ * SourceStreams of this instance.
+ */
+ public long getContentLength()
+ {
+ long contentLength = 0;
+
+ if (inputStreams != null)
+ for (InputStreamDesc inputStreamDesc : inputStreams)
+ {
+ long inputContentLength
+ = inputStreamDesc.getInputStream().getContentLength();
+
+ if (LENGTH_UNKNOWN == inputContentLength)
+ return LENGTH_UNKNOWN;
+ if (contentLength < inputContentLength)
+ contentLength = inputContentLength;
+ }
+ return contentLength;
+ }
+
+ /*
+ * Implements Controls#getControl(String). Does nothing.
+ */
+ public Object getControl(String controlType)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /*
+ * Implements Controls#getControls(). Does nothing.
+ */
+ public Object[] getControls()
+ {
+ // TODO Auto-generated method stub
+ return new Object[0];
+ }
+
+ /*
+ * Implements PushBufferStream#getFormat(). Returns the output
+ * AudioFormat in which this instance was configured to output its data.
+ */
+ public AudioFormat getFormat()
+ {
+ return outputFormat;
+ }
+
+ /*
+ * Implements PushBufferStream#read(Buffer). Reads audio samples from
+ * the input SourceStreams of this instance in various formats, converts
+ * the read audio samples to one and the same format and pushes them to
+ * the output AudioMixingPushBufferStreams for the very audio mixing.
+ */
+ public void read(Buffer buffer)
+ throws IOException
+ {
+ int inputStreamCount
+ = (inputStreams == null) ? 0 : inputStreams.length;
+
+ if (inputStreamCount <= 0)
+ return;
+
+ AudioFormat outputFormat = getFormat();
+ int[][] inputSamples = new int[inputStreamCount][];
+ int maxInputSampleCount;
+
+ try
+ {
+ maxInputSampleCount
+ = readInputPushBufferStreams(outputFormat, inputSamples);
+ }
+ catch (UnsupportedFormatException ufex)
+ {
+ IOException ioex = new IOException();
+ ioex.initCause(ufex);
+ throw ioex;
+ }
+
+ maxInputSampleCount
+ = Math.max(
+ maxInputSampleCount,
+ readInputPullBufferStreams(
+ outputFormat,
+ maxInputSampleCount,
+ inputSamples));
+
+ buffer.setData(inputSamples);
+ buffer.setLength(maxInputSampleCount);
+ }
+
+ /**
+ * Reads audio samples from a specific <code>PushBufferStream</code> and
+ * converts them to a specific output <code>AudioFormat</code>. An
+ * attempt is made to read a specific maximum number of samples from the
+ * specified <code>PushBufferStream</code> but the very
+ * <code>PushBufferStream</code> may not honor the request.
+ *
+ * @param inputStream
+ * the <code>PushBufferStream</code> to read from
+ * @param outputFormat
+ * the <code>AudioFormat</code> to which the samples read
+ * from <code>inputStream</code> are to converted before
+ * being returned
+ * @param sampleCount
+ * the maximum number of samples which the read operation
+ * should attempt to read from <code>inputStream</code> but
+ * the very <code>inputStream</code> may not honor the
+ * request
+ * @return
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ private int[] read(
+ PushBufferStream inputStream,
+ AudioFormat outputFormat,
+ int sampleCount)
+ throws IOException,
+ UnsupportedFormatException
+ {
+ Buffer buffer = new Buffer();
+
+ if (sampleCount != 0)
+ {
+ AudioFormat inputFormat = (AudioFormat) inputStream.getFormat();
+ Class<?> inputDataType = inputFormat.getDataType();
+
+ if (Format.byteArray.equals(inputDataType))
+ {
+ buffer.setData(
+ new byte[
+ sampleCount
+ * (inputFormat.getSampleSizeInBits() / 8)]);
+ buffer.setLength(0);
+ buffer.setOffset(0);
+ }
+ else
+ throw
+ new UnsupportedFormatException(
+ "!Format.getDataType().equals(byte[].class)",
+ inputFormat);
+ }
+
+ inputStream.read(buffer);
+
+ int inputLength = buffer.getLength();
+
+ if (inputLength <= 0)
+ return null;
+
+ AudioFormat inputFormat = (AudioFormat) buffer.getFormat();
+
+ if (inputFormat.getSigned() != AudioFormat.SIGNED)
+ throw
+ new UnsupportedFormatException(
+ "AudioFormat.getSigned()",
+ inputFormat);
+ if (inputFormat.getChannels() != outputFormat.getChannels())
+ throw
+ new UnsupportedFormatException(
+ "AudioFormat.getChannels()",
+ inputFormat);
+
+ Object inputData = buffer.getData();
+
+ if (inputData instanceof byte[])
+ {
+ byte[] inputSamples = (byte[]) inputData;
+ int[] outputSamples;
+ int outputSampleSizeInBits = outputFormat.getSampleSizeInBits();
+
+ switch (inputFormat.getSampleSizeInBits())
+ {
+ case 16:
+ outputSamples = new int[inputSamples.length / 2];
+ for (int i = 0; i < outputSamples.length; i++)
+ {
+ int sample = readShort(inputSamples, i * 2);
+
+ switch (outputSampleSizeInBits)
+ {
+ case 16:
+ break;
+ case 32:
+ sample = Math.round(sample * INT_TO_SHORT_RATIO);
+ break;
+ case 8:
+ case 24:
+ default:
+ throw
+ new UnsupportedFormatException(
+ "AudioFormat.getSampleSizeInBits()",
+ outputFormat);
+ }
+
+ outputSamples[i] = sample;
+ }
+ return outputSamples;
+ case 32:
+ outputSamples = new int[inputSamples.length / 4];
+ for (int i = 0; i < outputSamples.length; i++)
+ {
+ int sample = readInt(inputSamples, i * 4);
+
+ switch (outputSampleSizeInBits)
+ {
+ case 16:
+ sample = Math.round(sample * SHORT_TO_INT_RATIO);
+ break;
+ case 32:
+ break;
+ case 8:
+ case 24:
+ default:
+ throw
+ new UnsupportedFormatException(
+ "AudioFormat.getSampleSizeInBits()",
+ outputFormat);
+ }
+
+ outputSamples[i] = sample;
+ }
+ return outputSamples;
+ case 8:
+ case 24:
+ default:
+ throw
+ new UnsupportedFormatException(
+ "AudioFormat.getSampleSizeInBits()",
+ inputFormat);
+ }
+ }
+ else if (inputData instanceof short[])
+ {
+ throw
+ new UnsupportedFormatException(
+ "Format.getDataType().equals(short[].class)",
+ inputFormat);
+ }
+ else if (inputData instanceof int[])
+ {
+ throw
+ new UnsupportedFormatException(
+ "Format.getDataType().equals(int[].class)",
+ inputFormat);
+ }
+ return null;
+ }
+
+ /**
+ * Reads audio samples from the input <code>PullBufferStream</code>s of
+ * this instance and converts them to a specific output
+ * <code>AudioFormat</code>. An attempt is made to read a specific
+ * maximum number of samples from each of the
+ * <code>PullBufferStream</code>s but the very
+ * <code>PullBufferStream</code> may not honor the request.
+ *
+ * @param outputFormat
+ * the <code>AudioFormat</code> in which the audio samples
+ * read from the <code>PullBufferStream</code>s are to be
+ * converted before being returned
+ * @param outputSampleCount
+ * the maximum number of audio samples to be read from each
+ * of the <code>PullBufferStream</code>s but the very
+ * <code>PullBufferStream</code> may not honor the request
+ * @param inputSamples
+ * 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 <code>PullBufferStream</code>s of this instance
+ * @throws IOException
+ */
+ private int readInputPullBufferStreams(
+ AudioFormat outputFormat,
+ int outputSampleCount,
+ int[][] inputSamples)
+ throws IOException
+ {
+ int maxInputSampleCount = 0;
+
+ for (InputStreamDesc inputStreamDesc : inputStreams)
+ if (inputStreamDesc.getInputStream() instanceof PullBufferStream)
+ throw
+ new UnsupportedOperationException(
+ AudioMixerPushBufferStream.class.getSimpleName()
+ + ".readInputPullBufferStreams(AudioFormat,int,int[][])");
+ return maxInputSampleCount;
+ }
+
+ /**
+ * Reads audio samples from the input <code>PushBufferStream</code>s of
+ * this instance and converts them to a specific output
+ * <code>AudioFormat</code>.
+ *
+ * @param outputFormat
+ * the <code>AudioFormat</code> in which the audio samples
+ * read from the <code>PushBufferStream</code>s are to be
+ * converted before being returned
+ * @param inputSamples
+ * 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 <code>PushBufferStream</code>s of this instance
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ private int readInputPushBufferStreams(
+ AudioFormat outputFormat,
+ int[][] inputSamples)
+ throws IOException,
+ UnsupportedFormatException
+ {
+ int maxInputSampleCount = 0;
+
+ for (int i = 0; i < inputStreams.length; i++)
+ {
+ SourceStream inputStream = inputStreams[i].getInputStream();
+
+ if (inputStream instanceof PushBufferStream)
+ {
+ int[] inputStreamSamples
+ = read(
+ (PushBufferStream) inputStream,
+ outputFormat,
+ maxInputSampleCount);
+
+ if (inputStreamSamples != null)
+ {
+ int inputStreamSampleCount = inputStreamSamples.length;
+
+ if (inputStreamSampleCount != 0)
+ {
+ inputSamples[i] = inputStreamSamples;
+
+ if (maxInputSampleCount < inputStreamSampleCount)
+ maxInputSampleCount = inputStreamSampleCount;
+ }
+ }
+ }
+ }
+ return maxInputSampleCount;
+ }
+
+ /**
+ * Removes a specific <code>AudioMixingPushBufferStream</code> from the
+ * collection of such streams which this instance pushes the data for
+ * audio mixing it reads from its input <code>SourceStream</code>s.
+ *
+ * @param outputStream
+ * the <code>AudioMixingPushBufferStream</code> to be removed
+ * from the collection of such streams which this instance
+ * pushes the data for audio mixing it reads from its input
+ * <code>SourceStream</code>s
+ */
+ void removeOutputStream(AudioMixingPushBufferStream outputStream)
+ {
+ synchronized (outputStreams)
+ {
+ if (outputStream != null)
+ outputStreams.remove(outputStream);
+ }
+ }
+
+ /**
+ * Pushes a copy of a specific set of input audio samples to a specific
+ * <code>AudioMixingPushBufferStream</code> for audio mixing. Audio
+ * samples read from input <code>DataSource</code>s which the
+ * <code>AudioMixingPushBufferDataSource</code> owner of the specified
+ * <code>AudioMixingPushBufferStream</code> has specified to not be
+ * included in the output mix are not pushed to the
+ * <code>AudioMixingPushBufferStream</code>.
+ *
+ * @param outputStream
+ * the <code>AudioMixingPushBufferStream</code> to push the
+ * specified set of audio samples to
+ * @param inputSamples
+ * the set of audio samples to be pushed to
+ * <code>outputStream</code> for audio mixing
+ * @param maxInputSampleCount
+ * the maximum number of audio samples available in
+ * <code>inputSamples</code>
+ */
+ private void setInputSamples(
+ AudioMixingPushBufferStream outputStream,
+ int[][] inputSamples,
+ int maxInputSampleCount)
+ {
+ inputSamples = inputSamples.clone();
+
+ AudioMixingPushBufferDataSource outputDataSource
+ = outputStream.getDataSource();
+
+ for (int i = 0; i < inputSamples.length; i++)
+ {
+ InputStreamDesc inputStreamDesc = inputStreams[i];
+
+ if (outputDataSource.equals(
+ inputStreamDesc.getOutputDataSource()))
+ inputSamples[i] = null;
+ }
+
+ outputStream.setInputSamples(inputSamples, maxInputSampleCount);
+ }
+
+ /**
+ * Sets the <code>SourceStream</code>s (in the form of
+ * <code>InputStreamDesc</code>) from which this instance is to read
+ * audio samples and push them to the
+ * <code>AudioMixingPushBufferStream</code>s for audio mixing.
+ *
+ * @param inputStreams
+ * the <code>SourceStream</code>s (in the form of
+ * <code>InputStreamDesc</code>) from which this instance is
+ * to read audio samples and push them to the
+ * <code>AudioMixingPushBufferStream</code>s for audio mixing
+ */
+ private void setInputStreams(Collection<InputStreamDesc> inputStreams)
+ {
+ InputStreamDesc[] oldValue = this.inputStreams;
+ InputStreamDesc[] newValue
+ = inputStreams.toArray(
+ new InputStreamDesc[inputStreams.size()]);
+ boolean valueIsChanged = !Arrays.equals(oldValue, newValue);
+
+ if (valueIsChanged)
+ setTransferHandler(oldValue, null);
+ this.inputStreams = newValue;
+ if (valueIsChanged)
+ {
+ boolean skippedForTransferHandler = false;
+
+ for (InputStreamDesc inputStreamDesc : newValue)
+ {
+ SourceStream inputStream = inputStreamDesc.getInputStream();
+
+ if (inputStream instanceof PushBufferStream)
+ {
+ if (!skippedForTransferHandler)
+ skippedForTransferHandler = true;
+ else if (!(inputStream instanceof CachingPushBufferStream))
+ inputStreamDesc.setInputStream(
+ new CachingPushBufferStream(
+ (PushBufferStream) inputStream));
+ }
+ }
+
+ setTransferHandler(newValue, transferHandler);
+ }
+ }
+
+ /*
+ * Implements PushBufferStream#setTransferHandler(BufferTransferHandler).
+ * Because this instance pushes data to multiple output
+ * AudioMixingPushBufferStreams, a single BufferTransferHandler is not
+ * sufficient and thus this method is unsupported.
+ */
+ public void setTransferHandler(BufferTransferHandler transferHandler)
+ {
+ throw
+ new UnsupportedOperationException(
+ AudioMixerPushBufferStream.class.getSimpleName()
+ + ".setTransferHandler(BufferTransferHandler)");
+ }
+
+ /**
+ * Sets a specific <code>BufferTransferHandler</code> to a specific
+ * collection of <code>SourceStream</code>s (in the form of
+ * <code>InputStreamDesc</code>) abstracting the differences among the
+ * various types of <code>SourceStream</code>s.
+ *
+ * @param inputStreams
+ * the input <code>SourceStream</code>s to which the
+ * specified <code>BufferTransferHandler</code> is to be set
+ * @param transferHandler
+ * the <code>BufferTransferHandler</code> to be set to the
+ * specified <code>inputStreams</code>
+ */
+ private void setTransferHandler(
+ InputStreamDesc[] inputStreams,
+ BufferTransferHandler transferHandler)
+ {
+ if ((inputStreams == null) || (inputStreams.length <= 0))
+ return;
+
+ boolean transferHandlerIsSet = false;
+
+ for (InputStreamDesc inputStreamDesc : inputStreams)
+ {
+ SourceStream inputStream = inputStreamDesc.getInputStream();
+
+ if (inputStream instanceof PushBufferStream)
+ {
+ BufferTransferHandler inputStreamTransferHandler;
+ PushBufferStream inputPushBufferStream
+ = (PushBufferStream) inputStream;
+
+ if (transferHandler == null)
+ inputStreamTransferHandler = null;
+ else if (transferHandlerIsSet)
+ inputStreamTransferHandler
+ = new BufferTransferHandler()
+ {
+ public void transferData(
+ PushBufferStream stream)
+ {
+ /*
+ * Do nothing because we don't want
+ * the associated PushBufferStream
+ * to cause the transfer of data
+ * from this
+ * AudioMixerPushBufferStream.
+ */
+ }
+ };
+ else
+ inputStreamTransferHandler
+ = new StreamSubstituteBufferTransferHandler(
+ transferHandler,
+ inputPushBufferStream,
+ this);
+
+ inputPushBufferStream.setTransferHandler(
+ inputStreamTransferHandler);
+
+ transferHandlerIsSet = true;
+ }
+ }
+ }
+
+ /**
+ * Reads audio samples from the input <code>SourceStream</code>s of this
+ * instance and pushes them to its output
+ * <code>AudioMixingPushBufferStream</code>s for audio mixing.
+ */
+ protected void transferData()
+ {
+ Buffer buffer = new Buffer();
+
+ try
+ {
+ read(buffer);
+ }
+ catch (IOException ex)
+ {
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ int[][] inputSamples = (int[][]) buffer.getData();
+ int maxInputSampleCount = buffer.getLength();
+
+ if ((inputSamples == null)
+ || (inputSamples.length == 0)
+ || (maxInputSampleCount <= 0))
+ return;
+
+ AudioMixingPushBufferStream[] outputStreams;
+
+ synchronized (this.outputStreams)
+ {
+ outputStreams
+ = this.outputStreams.toArray(
+ new AudioMixingPushBufferStream[
+ this.outputStreams.size()]);
+ }
+ for (AudioMixingPushBufferStream outputStream : outputStreams)
+ setInputSamples(outputStream, inputSamples, maxInputSampleCount);
+ }
+ }
+
+ /**
+ * Describes additional information about a specific input
+ * <code>DataSource</code> of an <code>AudioMixer</code> so that the
+ * <code>AudioMixer</code> can, for example, quickly discover the output
+ * <code>AudioMixingPushBufferDataSource</code> in the mix of which the
+ * contribution of the <code>DataSource</code> is to not be included.
+ */
+ private static class InputDataSourceDesc
+ {
+
+ /**
+ * The <code>DataSource</code> for which additional information is
+ * described by this instance.
+ */
+ public final DataSource inputDataSource;
+
+ /**
+ * The <code>AudioMixingPushBufferDataSource</code> in which the
+ * mix contributions of the <code>DataSource</code> described by this
+ * instance are to not be included.
+ */
+ public final AudioMixingPushBufferDataSource outputDataSource;
+
+ /**
+ * The <code>DataSource</code>, if any, which transcodes the tracks of
+ * <code>inputDataSource</code> in the output <code>Format</code> of the
+ * associated <code>AudioMixer</code>.
+ */
+ private DataSource transcodingDataSource;
+
+ /**
+ * Initializes a new <code>InputDataSourceDesc</code> instance which is
+ * to describe additional information about a specific input
+ * <code>DataSource</code> of an <code>AudioMixer</code>. Associates the
+ * specified <code>DataSource</code> with the
+ * <code>AudioMixingPushBufferDataSource</code> in which the mix
+ * contributions of the specified input <code>DataSource</code> are to
+ * not be included.
+ *
+ * @param inputDataSource
+ * a <code>DataSourc</code> for which additional information
+ * is to be described by the new instance
+ * @param outputDataSource
+ * the <code>AudioMixingPushBufferDataSource</code> in which
+ * the mix contributions of <code>inputDataSource</code> are
+ * to not be included
+ */
+ public InputDataSourceDesc(
+ DataSource inputDataSource,
+ AudioMixingPushBufferDataSource outputDataSource)
+ {
+ this.inputDataSource = inputDataSource;
+ this.outputDataSource = outputDataSource;
+ }
+
+ /**
+ * Gets the actual <code>DataSource</code> from which the associated
+ * <code>AudioMixer</code> directly reads in order to retrieve the mix
+ * contribution of the <code>DataSource</code> described by this
+ * instance.
+ *
+ * @return the actual <code>DataSource</code> from which the associated
+ * <code>AudioMixer</code> directly reads in order to retrieve
+ * the mix contribution of the <code>DataSource</code> described
+ * by this instance
+ */
+ public DataSource getEffectiveInputDataSource()
+ {
+ return
+ (transcodingDataSource == null)
+ ? inputDataSource
+ : transcodingDataSource;
+ }
+
+ /**
+ * Sets the <code>DataSource</code>, if any, which transcodes the tracks
+ * of the input <code>DataSource</code> described by this instance in
+ * the output <code>Format</code> of the associated
+ * <code>AudioMixer</code>.
+ *
+ * @param transcodingDataSource
+ * the <code>DataSource</code> which transcodes the tracks of
+ * the input <code>DataSource</code> described by this
+ * instance in the output <code>Format</code> of the
+ * associated <code>AudioMixer</code>
+ */
+ public void setTranscodingDataSource(DataSource transcodingDataSource)
+ {
+ this.transcodingDataSource = transcodingDataSource;
+ }
+ }
+
+ /**
+ * Describes additional information about a specific input audio
+ * <code>SourceStream</code> of an <code>AudioMixer</code> so that the
+ * <code>AudioMixer</code> can, for example, quickly discover the output
+ * <code>AudioMixingPushBufferDataSource</code> in the mix of which the
+ * contribution of the <code>SourceStream</code> is to not be included.
+ */
+ private static class InputStreamDesc
+ {
+
+ /**
+ * The <code>DataSource</code> which created the
+ * <code>SourceStream</code> described by this instance and additional
+ * information about it.
+ */
+ private final InputDataSourceDesc inputDataSourceDesc;
+
+ /**
+ * The <code>SourceStream</code> for which additional information is
+ * described by this instance.
+ */
+ private SourceStream inputStream;
+
+ /**
+ * Initializes a new <code>InputStreamDesc</code> instance which is to
+ * describe additional information about a specific input audio
+ * <code>SourceStream</code> of an <code>AudioMixer</code>. Associates
+ * the specified <code>SourceStream</code> with the
+ * <code>DataSource</code> which created it and additional information
+ * about it.
+ *
+ * @param inputStream
+ * a <code>SourceStream</code> for which additional
+ * information is to be described by the new instance
+ * @param inputDataSourceDesc
+ * the <code>DataSource</code> which created the
+ * <code>SourceStream</code> to be described by the new
+ * instance and additional information about it
+ */
+ public InputStreamDesc(
+ SourceStream inputStream,
+ InputDataSourceDesc inputDataSourceDesc)
+ {
+ this.inputStream = inputStream;
+ this.inputDataSourceDesc = inputDataSourceDesc;
+ }
+
+ /**
+ * Gets the <code>SourceStream</code> described by this instance
+ *
+ * @return the <code>SourceStream</code> described by this instance
+ */
+ public SourceStream getInputStream()
+ {
+ return inputStream;
+ }
+
+ /**
+ * Gets the <code>AudioMixingPushBufferDataSource</code> in which the
+ * mix contribution of the <code>SourceStream</code> described by this
+ * instance is to not be included.
+ *
+ * @return the <code>AudioMixingPushBufferDataSource</code> in which the
+ * mix contribution of the <code>SourceStream</code> described
+ * by this instance is to not be included
+ */
+ public AudioMixingPushBufferDataSource getOutputDataSource()
+ {
+ return inputDataSourceDesc.outputDataSource;
+ }
+
+ /**
+ * Sets the <code>SourceStream</code> to be described by this instance
+ *
+ * @param inputStream
+ * the <code>SourceStream</code> to be described by this
+ * instance
+ */
+ public void setInputStream(SourceStream inputStream)
+ {
+ this.inputStream = inputStream;
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferDataSource.java b/src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferDataSource.java
new file mode 100644
index 0000000..faa7072
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferDataSource.java
@@ -0,0 +1,239 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+import javax.media.*;
+import javax.media.control.*;
+import javax.media.protocol.*;
+
+/**
+ * Represents a <code>PushBufferDataSource</code> which provides a single
+ * <code>PushBufferStream</code> containing the result of the audio mixing of
+ * <code>DataSource</code>s.
+ *
+ * @author Lubomir Marinov
+ */
+public class AudioMixingPushBufferDataSource
+ extends PushBufferDataSource
+ implements CaptureDevice
+{
+
+ /**
+ * The <code>AudioMixer</code> performing the audio mixing, managing the
+ * input <code>DataSource</code>s and pushing the data of this output
+ * <code>PushBufferDataSource</code>.
+ */
+ private final AudioMixer audioMixer;
+
+ /**
+ * The indicator which determines whether this <code>DataSource</code> is
+ * connected.
+ */
+ private boolean connected;
+
+ /**
+ * The one and only <code>PushBufferStream</code> this
+ * <code>PushBufferDataSource</code> provides to its clients and containing
+ * the result of the audio mixing performed by <code>audioMixer</code>.
+ */
+ private AudioMixingPushBufferStream outputStream;
+
+ /**
+ * The indicator which determines whether this <code>DataSource</code> is
+ * started.
+ */
+ private boolean started;
+
+ /**
+ * Initializes a new <code>AudioMixingPushBufferDataSource</code> instance
+ * which gives access to the result of the audio mixing performed by a
+ * specific <code>AudioMixer</code>.
+ *
+ * @param audioMixer the <code>AudioMixer</code> performing audio mixing,
+ * managing the input <code>DataSource</code>s and pushing the
+ * data of the new output <code>PushBufferDataSource</code>
+ */
+ public AudioMixingPushBufferDataSource(AudioMixer audioMixer)
+ {
+ this.audioMixer = audioMixer;
+ }
+
+ /**
+ * Adds a new input <code>DataSource</code> to be mixed by the associated
+ * <code>AudioMixer</code> of this instance and to not have its audio
+ * contributions included in the mixing output represented by this
+ * <code>DataSource</code>.
+ *
+ * @param inputDataSource a <code>DataSource</code> to be added for mixing
+ * to the <code>AudioMixer</code> associate with this instance
+ * and to not have its audio contributions included in the mixing
+ * output represented by this <code>DataSource</code>
+ */
+ public void addInputDataSource(DataSource inputDataSource)
+ {
+ audioMixer.addInputDataSource(inputDataSource, this);
+ }
+
+ /*
+ * Implements DataSource#connect(). Lets the AudioMixer know that one of its
+ * output PushBufferDataSources has been connected and marks this DataSource
+ * as connected.
+ */
+ public void connect()
+ throws IOException
+ {
+ if (!connected)
+ {
+ audioMixer.connect();
+ connected = true;
+ }
+ }
+
+ /*
+ * Implements DataSource#disconnect(). Marks this DataSource as disconnected
+ * and notifies the AudioMixer that one of its output PushBufferDataSources
+ * has been disconnected.
+ */
+ public void disconnect()
+ {
+ try
+ {
+ stop();
+ }
+ catch (IOException ex)
+ {
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ if (connected)
+ {
+ outputStream = null;
+ connected = false;
+
+ audioMixer.disconnect();
+ }
+ }
+
+ /*
+ * Implements CaptureDevice#getCaptureDeviceInfo(). Delegates to the
+ * associated AudioMixer because it knows which CaptureDevice is being
+ * wrapped.
+ */
+ public CaptureDeviceInfo getCaptureDeviceInfo()
+ {
+ return audioMixer.getCaptureDeviceInfo();
+ }
+
+ /*
+ * Implements DataSource#getContentType(). Delegates to the associated
+ * AudioMixer because it manages the inputs and knows their characteristics.
+ */
+ public String getContentType()
+ {
+ return audioMixer.getContentType();
+ }
+
+ /*
+ * Implements DataSource#getControl(String). Does nothing.
+ */
+ public Object getControl(String controlType)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /*
+ * Implements DataSource#getControls(). Does nothing.
+ */
+ public Object[] getControls()
+ {
+ // TODO Auto-generated method stub
+ return new Object[0];
+ }
+
+ /*
+ * Implements DataSource#getDuration(). Delegates to the associated
+ * AudioMixer because it manages the inputs and knows their characteristics.
+ */
+ public Time getDuration()
+ {
+ return audioMixer.getDuration();
+ }
+
+ /*
+ * Implements CaptureDevice#getFormatControls(). Delegates to the associated
+ * AudioMixer because it knows which CaptureDevice is being wrapped.
+ */
+ public FormatControl[] getFormatControls()
+ {
+ return audioMixer.getFormatControls();
+ }
+
+ /*
+ * Implements PushBufferDataSource#getStreams(). Gets a PushBufferStream
+ * which reads data from the associated AudioMixer and mixes it.
+ */
+ public PushBufferStream[] getStreams()
+ {
+ if (outputStream == null)
+ {
+ AudioMixer.AudioMixerPushBufferStream audioMixerOutputStream
+ = audioMixer.getOutputStream();
+
+ if (audioMixerOutputStream != null)
+ {
+ outputStream
+ = new AudioMixingPushBufferStream(
+ audioMixerOutputStream,
+ this);
+ if (started)
+ outputStream.start();
+ }
+ }
+ return
+ (outputStream == null)
+ ? new PushBufferStream[0]
+ : new PushBufferStream[] { outputStream };
+ }
+
+ /*
+ * Implements DataSource#start(). Starts the output PushBufferStream of
+ * this DataSource (if it exists) and notifies the AudioMixer that one of
+ * its output PushBufferDataSources has been started.
+ */
+ public void start()
+ throws IOException
+ {
+ if (!started)
+ {
+ if (outputStream != null)
+ outputStream.start();
+ audioMixer.start();
+ started = true;
+ }
+ }
+
+ /*
+ * Implements DataSource#stop(). Notifies the AudioMixer that one of its
+ * output PushBufferDataSources has been stopped and stops the output
+ * PushBufferStream of this DataSource (if it exists).
+ */
+ public void stop()
+ throws IOException
+ {
+ if (started)
+ {
+ audioMixer.stop();
+ if (outputStream != null)
+ outputStream.stop();
+ started = false;
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferStream.java b/src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferStream.java
new file mode 100644
index 0000000..ad28c1d
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/AudioMixingPushBufferStream.java
@@ -0,0 +1,384 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+
+import javax.media.*;
+import javax.media.format.*;
+import javax.media.protocol.*;
+
+/**
+ * Represents a <code>PushBufferStream</code> containing the result of the audio
+ * mixing of <code>DataSource</code>s.
+ *
+ * @author Lubomir Marinov
+ */
+public class AudioMixingPushBufferStream
+ implements PushBufferStream
+{
+
+ /**
+ * The <code>AudioMixer.AudioMixerPushBufferStream</code> which reads data
+ * from the input <code>DataSource</code>s and pushes it to this instance to
+ * be mixed.
+ */
+ private final AudioMixer.AudioMixerPushBufferStream audioMixerStream;
+
+ /**
+ * The <code>AudioMixingPushBufferDataSource</code> which created and owns
+ * this instance and defines the input data which is to not be mixed in the
+ * output of this <code>PushBufferStream</code>.
+ */
+ private final AudioMixingPushBufferDataSource dataSource;
+
+ /**
+ * The collection of input audio samples still not mixed and read through
+ * this <code>AudioMixingPushBufferStream</code>.
+ */
+ private int[][] inputSamples;
+
+ /**
+ * The maximum number of per-stream audio samples available through
+ * <code>inputSamples</code>.
+ */
+ private int maxInputSampleCount;
+
+ /**
+ * The <code>BufferTransferHandler</code> through which this
+ * <code>PushBufferStream</code> notifies its clients that new data is
+ * available for reading.
+ */
+ private BufferTransferHandler transferHandler;
+
+ /**
+ * Initializes a new <code>AudioMixingPushBufferStream</code> mixing the
+ * input data of a specific
+ * <code>AudioMixer.AudioMixerPushBufferStream</code> and excluding from the
+ * mix the audio contributions of a specific
+ * <code>AudioMixingPushBufferDataSource</code>.
+ *
+ * @param audioMixerStream the
+ * <code>AudioMixer.AudioMixerPushBufferStream</code> reading
+ * data from input <code>DataSource</code>s and to push it to the
+ * new <code>AudioMixingPushBufferStream</code>
+ * @param dataSource the <code>AudioMixingPushBufferDataSource</code> which
+ * has requested the initialization of the new instance and which
+ * defines the input data to not be mixed in the output of the
+ * new instance
+ */
+ public AudioMixingPushBufferStream(
+ AudioMixer.AudioMixerPushBufferStream audioMixerStream,
+ AudioMixingPushBufferDataSource dataSource)
+ {
+ this.audioMixerStream = audioMixerStream;
+ this.dataSource = dataSource;
+ }
+
+ /*
+ * Implements SourceStream#endOfStream(). Delegates to the wrapped
+ * AudioMixer.AudioMixerPushBufferStream.
+ */
+ public boolean endOfStream()
+ {
+ /*
+ * TODO If the inputSamples 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();
+ }
+
+ /*
+ * Implements SourceStream#getContentDescriptor(). Delegates to the wrapped
+ * AudioMixer.AudioMixerPushBufferStream.
+ */
+ public ContentDescriptor getContentDescriptor()
+ {
+ return audioMixerStream.getContentDescriptor();
+ }
+
+ /*
+ * Implements SourceStream#getContentLength(). Delegates to the wrapped
+ * AudioMixer.AudioMixerPushBufferStream.
+ */
+ public long getContentLength()
+ {
+ return audioMixerStream.getContentLength();
+ }
+
+ /*
+ * Implements Controls#getControl(String). Does nothing.
+ */
+ public Object getControl(String controlType)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ /*
+ * Implements Controls#getControls(). Does nothing.
+ */
+ public Object[] getControls()
+ {
+ // TODO Auto-generated method stub
+ return new Object[0];
+ }
+
+ /**
+ * Gets the <code>AudioMixingPushBufferDataSource</code> which created and
+ * owns this instance and defines the input data which is to not be mixed in
+ * the output of this <code>PushBufferStream</code>.
+ *
+ * @return the <code>AudioMixingPushBufferDataSource</code> which created
+ * and owns this instance and defines the input data which is to not
+ * be mixed in the output of this <code>PushBufferStream</code>
+ */
+ public AudioMixingPushBufferDataSource getDataSource()
+ {
+ return dataSource;
+ }
+
+ /*
+ * Implements PushBufferStream#getFormat(). Delegates to the wrapped
+ * AudioMixer.AudioMixerPushBufferStream.
+ */
+ public AudioFormat getFormat()
+ {
+ return audioMixerStream.getFormat();
+ }
+
+ /**
+ * Gets the maximum possible value for an audio sample of a specific
+ * <code>AudioFormat</code>.
+ *
+ * @param outputFormat the <code>AudioFormat</code> of which to get the
+ * maximum possible value for an audio sample
+ * @return the maximum possible value for an audio sample of the specified
+ * <code>AudioFormat</code>
+ * @throws UnsupportedFormatException
+ */
+ 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
+ * <code>AudioFormat</code>.
+ *
+ * @param inputSamples the collection of audio sample sets to be mixed into
+ * one audio sample set in the sense of audio mixing
+ * @param outputFormat the <code>AudioFormat</code> in which the resulting
+ * mix audio sample set is to be produced
+ * @param outputSampleCount 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)
+ {
+ int[] outputSamples = new int[outputSampleCount];
+ int maxOutputSample;
+
+ try
+ {
+ maxOutputSample = getMaxOutputSample(outputFormat);
+ }
+ catch (UnsupportedFormatException ufex)
+ {
+ throw new UnsupportedOperationException(ufex);
+ }
+
+ for (int[] inputStreamSamples : inputSamples)
+ {
+
+ if (inputStreamSamples == null)
+ continue;
+
+ int inputStreamSampleCount = inputStreamSamples.length;
+
+ if (inputStreamSampleCount <= 0)
+ continue;
+
+ for (int i = 0; i < inputStreamSampleCount; i++)
+ {
+ int inputStreamSample = inputStreamSamples[i];
+ int outputSample = outputSamples[i];
+
+ outputSamples[i]
+ = inputStreamSample
+ + outputSample
+ - Math.round(
+ inputStreamSample
+ * (outputSample
+ / (float) maxOutputSample));
+ }
+ }
+ return outputSamples;
+ }
+
+ /*
+ * Implements PushBufferStream#read(Buffer). If inputSamples are available,
+ * mixes them and writes them to the specified Buffer performing the
+ * necessary data type conversions.
+ */
+ public void read(Buffer buffer)
+ throws IOException
+ {
+ int[][] inputSamples = this.inputSamples;
+ int maxInputSampleCount = this.maxInputSampleCount;
+
+ this.inputSamples = null;
+ this.maxInputSampleCount = 0;
+
+ if ((inputSamples == null)
+ || (inputSamples.length == 0)
+ || (maxInputSampleCount <= 0))
+ return;
+
+ AudioFormat outputFormat = getFormat();
+ int[] outputSamples
+ = mix(inputSamples, outputFormat, maxInputSampleCount);
+
+ Class<?> outputDataType = outputFormat.getDataType();
+
+ if (Format.byteArray.equals(outputDataType))
+ {
+ byte[] outputData;
+
+ switch (outputFormat.getSampleSizeInBits())
+ {
+ case 16:
+ outputData = new byte[outputSamples.length * 2];
+ for (int i = 0; i < outputSamples.length; i++)
+ writeShort(outputSamples[i], outputData, i * 2);
+ break;
+ case 32:
+ outputData = new byte[outputSamples.length * 4];
+ for (int i = 0; i < outputSamples.length; i++)
+ writeInt(outputSamples[i], outputData, i * 4);
+ break;
+ case 8:
+ case 24:
+ default:
+ throw
+ new UnsupportedOperationException(
+ "AudioMixingPushBufferStream.read(Buffer)");
+ }
+
+ buffer.setData(outputData);
+ buffer.setFormat(outputFormat);
+ buffer.setLength(outputData.length);
+ buffer.setOffset(0);
+ }
+ else
+ throw
+ new UnsupportedOperationException(
+ "AudioMixingPushBufferStream.read(Buffer)");
+ }
+
+ /**
+ * Sets the collection of audio sample sets to be mixed in the sense of
+ * 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
+ * this stream when data is read from it
+ * @param maxInputSampleCount the maximum number of per-stream audio samples
+ * available through <code>inputSamples</code>
+ */
+ void setInputSamples(int[][] inputSamples, int maxInputSampleCount)
+ {
+ this.inputSamples = inputSamples;
+ this.maxInputSampleCount = maxInputSampleCount;
+
+ if (transferHandler != null)
+ transferHandler.transferData(this);
+ }
+
+ /*
+ * Implements PushBufferStream#setTransferHandler(BufferTransferHandler).
+ */
+ public void setTransferHandler(BufferTransferHandler transferHandler)
+ {
+ this.transferHandler = transferHandler;
+ }
+
+ /**
+ * Starts the pushing of data out of this stream.
+ */
+ void start()
+ {
+ audioMixerStream.addOutputStream(this);
+ }
+
+ /**
+ * Stops the pushing of data out of this stream.
+ */
+ void stop()
+ {
+ audioMixerStream.removeOutputStream(this);
+ }
+
+ /**
+ * 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 <code>output</code> 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);
+ }
+
+ /**
+ * Converts a short 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 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 integer to a series of bytes
+ * @param outputOffset the offset in <code>output</code> at which the
+ * writing of the result of the conversion is to be started
+ */
+ private static void writeShort(int input, byte[] output, int outputOffset)
+ {
+ output[outputOffset] = (byte) (input & 0xFF);
+ output[outputOffset + 1] = (byte) (input >> 8);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/BufferStreamAdapter.java b/src/net/java/sip/communicator/impl/media/conference/BufferStreamAdapter.java
new file mode 100644
index 0000000..3f86ff2
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/BufferStreamAdapter.java
@@ -0,0 +1,152 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+
+import javax.media.*;
+import javax.media.protocol.*;
+
+/**
+ * Represents a base class for adapters of <code>SourceStream</code>s, usually
+ * ones reading data in arrays of bytes and not in <code>Buffer</code>s, to
+ * <code>SourceStream</code>s reading data in <code>Buffer</code>s. An example
+ * use is creating a PushBufferStream representation of a PushSourceStream.
+ *
+ * @author Lubomir Marinov
+ */
+public abstract class BufferStreamAdapter<T extends SourceStream>
+ implements SourceStream
+{
+
+ /**
+ * The <code>Format</code> of this stream to be reported through the output
+ * <code>Buffer</code> this instance reads data into.
+ */
+ private final Format format;
+
+ /**
+ * The <code>SourceStream</code> being adapted by this instance.
+ */
+ protected final T stream;
+
+ /**
+ * Initializes a new <code>BufferStreamAdapter</code> which is to adapt a
+ * specific <code>SourceStream</code> into a <code>SourceStream</code> with
+ * a specific <code>Format</code>.
+ *
+ * @param stream
+ * @param format
+ */
+ public BufferStreamAdapter(T stream, Format format)
+ {
+ this.stream = stream;
+ this.format = format;
+ }
+
+ /*
+ * Implements SourceStream#endOfStream(). Delegates to the wrapped
+ * SourceStream.
+ */
+ public boolean endOfStream()
+ {
+ return stream.endOfStream();
+ }
+
+ /*
+ * Implements SourceStream#getContentDescriptor(). Delegates to the wrapped
+ * SourceStream.
+ */
+ public ContentDescriptor getContentDescriptor()
+ {
+ return stream.getContentDescriptor();
+ }
+
+ /*
+ * Implements SourceStream#getContentLength(). Delegates to the wrapped
+ * SourceStream.
+ */
+ public long getContentLength()
+ {
+ return stream.getContentLength();
+ }
+
+ /*
+ * Implements Controls#getControl(String). Delegates to the wrapped
+ * SourceStream.
+ */
+ public Object getControl(String controlType)
+ {
+ return stream.getControl(controlType);
+ }
+
+ /*
+ * Implements Controls#getControls(). Delegates to the wrapped SourceStream.
+ */
+ public Object[] getControls()
+ {
+ return stream.getControls();
+ }
+
+ /**
+ * Gets the <code>Format</code> of the data this stream provides.
+ *
+ * @return the <code>Format</code> of the data this stream provides
+ */
+ public Format getFormat()
+ {
+ return format;
+ }
+
+ /**
+ * Reads byte data from this stream into a specific <code>Buffer</code>
+ * which is to use a specific array of bytes for its data.
+ *
+ * @param buffer the <code>Buffer</code> to read byte data into from this
+ * instance
+ * @param bytes the array of <code>byte</code>s to read data into from this
+ * instance and to be set as the data of the specified
+ * <code>buffer</code>
+ * @throws IOException
+ */
+ protected void read(Buffer buffer, byte[] bytes)
+ throws IOException
+ {
+ int offset = 0;
+ int numberOfBytesRead = read(bytes, offset, bytes.length);
+
+ if (numberOfBytesRead > -1)
+ {
+ buffer.setData(bytes);
+ buffer.setOffset(offset);
+ buffer.setLength(numberOfBytesRead);
+
+ Format format = getFormat();
+
+ if (format != null)
+ buffer.setFormat(format);
+ }
+ }
+
+ /**
+ * Reads byte data from this stream into a specific array of
+ * <code>byte</code>s starting the storing at a specific offset and reading
+ * at most a specific number of bytes.
+ *
+ * @param buffer the array of <code>byte</code>s into which the data read
+ * from this stream is to be written
+ * @param offset the offset in the specified <code>buffer</code> at which
+ * writing data read from this stream should start
+ * @param length the maximum number of bytes to be written into the
+ * specified <code>buffer</code>
+ * @return the number of bytes read from this stream and written into the
+ * specified <code>buffer</code>
+ * @throws IOException
+ */
+ protected abstract int read(byte[] buffer, int offset, int length)
+ throws IOException;
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/CachingPushBufferStream.java b/src/net/java/sip/communicator/impl/media/conference/CachingPushBufferStream.java
new file mode 100644
index 0000000..c29324f
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/CachingPushBufferStream.java
@@ -0,0 +1,329 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+
+import javax.media.*;
+import javax.media.format.*;
+import javax.media.protocol.*;
+
+import net.java.sip.communicator.impl.media.*;
+
+/**
+ * Enables reading from a <code>PushBufferStream</code> a certain maximum number
+ * of data units (e.g. bytes, shorts, ints) even if the
+ * <code>PushBufferStream</code> itself pushes a larger number of data units.
+ * <p>
+ * An example use of this functionality is pacing a
+ * <code>PushBufferStream</code> which pushes more data units in a single step
+ * than a <code>CaptureDevice</code>. When these two undergo audio mixing, the
+ * different numbers of per-push data units will cause the
+ * <code>PushBufferStream</code> "play" itself faster than the
+ * <code>CaptureDevice</code>.
+ * </p>
+ *
+ * @author Lubomir Marinov
+ */
+public class CachingPushBufferStream
+ implements PushBufferStream
+{
+
+ /**
+ * The <code>Buffer</code> in which this instance stores the data it reads
+ * from the wrapped <code>PushBufferStream</code> and from which it reads in
+ * chunks later on when its <code>#read(Buffer)</code> method is called.
+ */
+ private Buffer cache;
+
+ /**
+ * The last <code>IOException</code> this stream has received from the
+ * <code>#read(Buffer)</code> method of the wrapped stream and to be thrown
+ * by this stream on the earliest call of its <code>#read(Buffer)</code>
+ * method.
+ */
+ private IOException readException;
+
+ /**
+ * The <code>PushBufferStream</code> being paced by this instance with
+ * respect to the maximum number of data units it provides in a single push.
+ */
+ private final PushBufferStream stream;
+
+ /**
+ * Initializes a new <code>CachingPushBufferStream</code> instance which is
+ * to pace the number of per-push data units a specific
+ * <code>PushBufferStream</code> provides.
+ *
+ * @param stream the <code>PushBufferStream</code> to be paced with respect
+ * to the number of per-push data units it provides
+ */
+ public CachingPushBufferStream(PushBufferStream stream)
+ {
+ this.stream = stream;
+ }
+
+ /*
+ * Implements SourceStream#endOfStream(). Delegates to the wrapped
+ * PushBufferStream when the cache of this instance is fully read;
+ * otherwise, returns false.
+ */
+ public boolean endOfStream()
+ {
+ /*
+ * TODO If the cache is still not exhausted, don't report the end of
+ * this stream even if the wrapped stream has reached its end.
+ */
+ return stream.endOfStream();
+ }
+
+ /*
+ * Implements SourceStream#getContentDescriptor(). Delegates to the wrapped
+ * PushBufferStream.
+ */
+ public ContentDescriptor getContentDescriptor()
+ {
+ return stream.getContentDescriptor();
+ }
+
+ /*
+ * Implements SourceStream#getContentLength(). Delegates to the wrapped
+ * PushBufferStream.
+ */
+ public long getContentLength()
+ {
+ return stream.getContentLength();
+ }
+
+ /*
+ * Implements Controls#getControl(String). Delegates to the wrapped
+ * PushBufferStream.
+ */
+ public Object getControl(String controlType)
+ {
+ return stream.getControl(controlType);
+ }
+
+ /*
+ * Implements Controls#getControls(). Delegates to the wrapped
+ * PushBufferStream.
+ */
+ public Object[] getControls()
+ {
+ return stream.getControls();
+ }
+
+ /*
+ * Implements PushBufferStream#getFormat(). Delegates to the wrapped
+ * PushBufferStream.
+ */
+ public Format getFormat()
+ {
+ return stream.getFormat();
+ }
+
+ /**
+ * Gets the object this instance uses for synchronization of the operations
+ * (such as reading from the wrapped stream into the cache of this instance
+ * and reading out of the cache into the <code>Buffer</code> provided to the
+ * <code>#read(Buffer)</code> method of this instance) it performs in
+ * various threads.
+ *
+ * @return the object this instance uses for synchronization of the
+ * operations it performs in various threads
+ */
+ private Object getSyncRoot()
+ {
+ return this;
+ }
+
+ /*
+ * Implements PushBufferStream#read(Buffer). If an IOException has been
+ * thrown by the wrapped stream when data was last read from it, re-throws
+ * it. If there is no such exception, reads from the cache of this instance.
+ */
+ public void read(Buffer buffer)
+ throws IOException
+ {
+ Object syncRoot = getSyncRoot();
+
+ synchronized (syncRoot)
+ {
+ if (readException != null)
+ {
+ IOException ex = readException;
+ readException = null;
+ throw ex;
+ }
+
+ if (cache != null)
+ {
+ try
+ {
+ read(cache, buffer);
+ }
+ catch (UnsupportedFormatException ufex)
+ {
+ IOException ioex = new IOException();
+ ioex.initCause(ufex);
+ throw ioex;
+ }
+
+ int cacheLength = cache.getLength();
+
+ if ((cacheLength <= 0)
+ || (cacheLength <= cache.getOffset())
+ || (cache.getData() == null))
+ {
+ cache = null;
+ syncRoot.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * Reads data from a specific input <code>Buffer</code> (if such data is
+ * available) and writes the read data into a specific output
+ * <code>Buffer</code>. The input <code>Buffer</code> will be modified to
+ * reflect the number of read data units. If the output <code>Buffer</code>
+ * has allocated an array for storing the read data and the type of this
+ * array matches that of the input <code>Buffer</code>, it will be used and
+ * thus the output <code>Buffer</code> may control the maximum number of
+ * data units to be read into it.
+ *
+ * @param input the <code>Buffer</code> to read data from
+ * @param output the <code>Buffer</code> into which to write the data read
+ * from the specified <code>input</code>
+ * @throws IOException
+ * @throws UnsupportedFormatException
+ */
+ private void read(Buffer input, Buffer output)
+ throws IOException,
+ UnsupportedFormatException
+ {
+ Object outputData = output.getData();
+
+ if (outputData != null)
+ {
+ Object inputData = input.getData();
+
+ if (inputData == null)
+ {
+ output.setFormat(input.getFormat());
+ output.setLength(0);
+ return;
+ }
+
+ Class<?> dataType = outputData.getClass();
+
+ if (inputData.getClass().equals(dataType)
+ && dataType.equals(byte[].class))
+ {
+ byte[] outputBytes = (byte[]) outputData;
+ int outputLength
+ = Math.min(input.getLength(), outputBytes.length);
+
+ System.arraycopy(
+ (byte[]) inputData,
+ input.getOffset(),
+ outputBytes,
+ output.getOffset(),
+ outputLength);
+
+ output.setData(outputBytes);
+ output.setFormat(input.getFormat());
+ output.setLength(outputLength);
+
+ input.setLength(input.getLength() - outputLength);
+ input.setOffset(input.getOffset() + outputLength);
+ return;
+ }
+ }
+
+ output.copy(input);
+
+ int outputLength = output.getLength();
+
+ input.setLength(input.getLength() - outputLength);
+ input.setOffset(input.getOffset() + outputLength);
+ }
+
+ /*
+ * Implements PushBufferStream#setTransferHandler(BufferTransferHandler).
+ * Delegates to the wrapped PushBufferStream but wraps the specified
+ * BufferTransferHandler in order to intercept the calls to
+ * BufferTransferHandler#transferData(PushBufferStream) and read data from
+ * the wrapped PushBufferStream into the cache during the calls in question.
+ */
+ public void setTransferHandler(BufferTransferHandler transferHandler)
+ {
+ stream.setTransferHandler(
+ (transferHandler == null)
+ ? null
+ : new StreamSubstituteBufferTransferHandler(
+ transferHandler,
+ stream,
+ this)
+ {
+ public void transferData(PushBufferStream stream)
+ {
+ if (CachingPushBufferStream.this.stream
+ == stream)
+ CachingPushBufferStream.this.transferData();
+
+ super.transferData(stream);
+ }
+ });
+ }
+
+ /**
+ * Reads data from the wrapped/input PushBufferStream into the cache of this
+ * stream if the cache is empty. If the cache is not empty, blocks the
+ * calling thread until the cache is emptied and data is read from the
+ * wrapped PushBufferStream into the cache.
+ */
+ protected void transferData()
+ {
+ Object syncRoot = getSyncRoot();
+
+ synchronized (syncRoot)
+ {
+ boolean interrupted = false;
+
+ try
+ {
+ while (cache != null)
+ try
+ {
+ syncRoot.wait();
+ }
+ catch (InterruptedException ex)
+ {
+ interrupted = true;
+ }
+ }
+ finally
+ {
+ if (interrupted)
+ Thread.currentThread().interrupt();
+ }
+
+ cache = new Buffer();
+
+ try
+ {
+ stream.read(cache);
+ readException = null;
+ }
+ catch (IOException ex)
+ {
+ readException = ex;
+ }
+ }
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/PullBufferStreamAdapter.java b/src/net/java/sip/communicator/impl/media/conference/PullBufferStreamAdapter.java
new file mode 100644
index 0000000..f167bfb
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/PullBufferStreamAdapter.java
@@ -0,0 +1,120 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+
+import javax.media.*;
+import javax.media.format.*;
+import javax.media.protocol.*;
+
+/**
+ * Represents a <code>PullBufferStream</code> which reads its data from a
+ * specific <code>PullSourceStream</code>.
+ *
+ * @author Lubomir Marinov
+ */
+public class PullBufferStreamAdapter
+ extends BufferStreamAdapter<PullSourceStream>
+ implements PullBufferStream
+{
+
+ /**
+ * Initializes a new <code>PullBufferStreamAdapter</code> instance which
+ * reads its data from a specific <code>PullSourceStream</code> with a
+ * specific <code>Format</code>
+ *
+ * @param stream the <code>PullSourceStream</code> the new instance is to
+ * read its data from
+ * @param format the <code>Format</code> of the specified input
+ * <code>stream</code> and of the new instance
+ */
+ public PullBufferStreamAdapter(PullSourceStream stream, Format format)
+ {
+ super(stream, format);
+ }
+
+ /**
+ * Gets the frame size measured in bytes defined by a specific
+ * <code>Format</code>.
+ *
+ * @param format the <code>Format</code> to determine the frame size in
+ * bytes of
+ * @return the frame size measured in bytes defined by the specified
+ * <code>Format</code>
+ */
+ private static int getFrameSizeInBytes(Format format)
+ {
+ AudioFormat audioFormat = (AudioFormat) format;
+ int frameSizeInBits = audioFormat.getFrameSizeInBits();
+
+ if (frameSizeInBits <= 0)
+ return
+ (audioFormat.getSampleSizeInBits() / 8)
+ * audioFormat.getChannels();
+ return (frameSizeInBits <= 8) ? 1 : (frameSizeInBits / 8);
+ }
+
+ /*
+ * Implements PullBufferStream#read(Buffer). Delegates to the wrapped
+ * PullSourceStream by either allocating a new byte[] buffer or using the
+ * existing one in the specified Buffer.
+ */
+ public void read(Buffer buffer)
+ throws IOException
+ {
+ Object data = buffer.getData();
+ byte[] bytes = null;
+
+ if (data != null)
+ {
+ if (data instanceof byte[])
+ bytes = (byte[]) data;
+ else if (data instanceof short[])
+ {
+ short[] shorts = (short[]) data;
+
+ bytes = new byte[2 * shorts.length];
+ }
+ else if (data instanceof int[])
+ {
+ int[] ints = (int[]) data;
+
+ bytes = new byte[4 * ints.length];
+ }
+ }
+ if (bytes == null)
+ {
+ int frameSizeInBytes = getFrameSizeInBytes(getFormat());
+
+ bytes
+ = new byte[
+ 1024 * ((frameSizeInBytes <= 0) ? 4 : frameSizeInBytes)];
+ }
+
+ read(buffer, bytes);
+ }
+
+ /*
+ * Implements BufferStreamAdapter#read(byte[], int, int). Delegates to the
+ * wrapped PullSourceStream.
+ */
+ protected int read(byte[] buffer, int offset, int length)
+ throws IOException
+ {
+ return stream.read(buffer, offset, length);
+ }
+
+ /*
+ * Implements PullBufferStream#willReadBlock(). Delegates to the wrapped
+ * PullSourceStream.
+ */
+ public boolean willReadBlock()
+ {
+ return stream.willReadBlock();
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/PushBufferStreamAdapter.java b/src/net/java/sip/communicator/impl/media/conference/PushBufferStreamAdapter.java
new file mode 100644
index 0000000..a3369c5
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/PushBufferStreamAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+
+import javax.media.*;
+import javax.media.protocol.*;
+
+/**
+ * Represents a <code>PushBufferStream</code> which reads its data from a
+ * specific <code>PushSourceStream</code>.
+ *
+ * @author Lubomir Marinov
+ */
+public class PushBufferStreamAdapter
+ extends BufferStreamAdapter<PushSourceStream>
+ implements PushBufferStream
+{
+
+ /**
+ * Initializes a new <code>PushBufferStreamAdapter</code> instance which
+ * reads its data from a specific <code>PushSourceStream</code> with a
+ * specific <code>Format</code>
+ *
+ * @param stream the <code>PushSourceStream</code> the new instance is to
+ * read its data from
+ * @param format the <code>Format</code> of the specified input
+ * <code>stream</code> and of the new instance
+ */
+ public PushBufferStreamAdapter(PushSourceStream stream, Format format)
+ {
+ super(stream, format);
+ }
+
+ /*
+ * Implements PushBufferStream#read(Buffer). Delegates to the wrapped
+ * PushSourceStream by allocating a new byte[] buffer of size equal to
+ * PushSourceStream#getMinimumTransferSize().
+ */
+ public void read(Buffer buffer)
+ throws IOException
+ {
+ read(buffer, new byte[stream.getMinimumTransferSize()]);
+ }
+
+ /*
+ * Implements BufferStreamAdapter#read(byte[], int, int). Delegates to the
+ * wrapped PushSourceStream.
+ */
+ protected int read(byte[] buffer, int offset, int length)
+ throws IOException
+ {
+ return stream.read(buffer, offset, length);
+ }
+
+ /*
+ * Implements PushBufferStream#setTransferHandler(BufferTransferHandler).
+ * Delegates to the wrapped PushSourceStream by translating the specified
+ * BufferTransferHandler to a SourceTransferHandler.
+ */
+ public void setTransferHandler(final BufferTransferHandler transferHandler)
+ {
+ stream.setTransferHandler(new SourceTransferHandler()
+ {
+ public void transferData(PushSourceStream stream) {
+ transferHandler.transferData(PushBufferStreamAdapter.this);
+ }
+ });
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/media/conference/TranscodingDataSource.java b/src/net/java/sip/communicator/impl/media/conference/TranscodingDataSource.java
new file mode 100644
index 0000000..2a73e65
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/media/conference/TranscodingDataSource.java
@@ -0,0 +1,280 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.sip.communicator.impl.media.conference;
+
+import java.io.*;
+import java.lang.reflect.*;
+
+import javax.media.*;
+import javax.media.control.*;
+import javax.media.format.*;
+import javax.media.protocol.*;
+
+import net.java.sip.communicator.impl.media.*;
+
+/**
+ * Represents a <code>DataSource</code> which transcodes the tracks of a
+ * specific input <code>DataSource</code> into a specific output
+ * <code>Format</code>. The transcoding is attempted only for tracks which
+ * actually support it for the specified output <code>Format</code>.
+ *
+ * @author Lubomir Marinov
+ */
+public class TranscodingDataSource
+ extends DataSource
+{
+
+ /**
+ * The <code>DataSource</code> which has its tracks transcoded by this
+ * instance.
+ */
+ private final DataSource inputDataSource;
+
+ /**
+ * The <code>DataSource</code> which contains the transcoded tracks of
+ * <code>inputDataSource</code> and which is wrapped by this instance. It is
+ * the output of <code>transcodingProcessor</code>.
+ */
+ private DataSource outputDataSource;
+
+ /**
+ * The <code>Format</code> in which the tracks of
+ * <code>inputDataSource</code> are transcoded.
+ */
+ private final Format outputFormat;
+
+ /**
+ * The <code>Processor</code> which carries out the actual transcoding of
+ * the tracks of <code>inputDataSource</code>.
+ */
+ private Processor transcodingProcessor;
+
+ /**
+ * Initializes a new <code>TranscodingDataSource</code> instance to
+ * transcode the tracks of a specific <code>DataSource</code> into a
+ * specific output <code>Format</code>.
+ *
+ * @param inputDataSource the <code>DataSource</code> which is to have its
+ * tracks transcoded in a specific outptu <code>Format</code>
+ * @param outputFormat the <code>Format</code> in which the new instance is
+ * to transcode the tracks of <code>inputDataSource</code>
+ */
+ public TranscodingDataSource(
+ DataSource inputDataSource,
+ Format outputFormat)
+ {
+ super(inputDataSource.getLocator());
+
+ this.inputDataSource = inputDataSource;
+ this.outputFormat = outputFormat;
+ }
+
+ /*
+ * Implements DataSource#connect(). Sets up the very transcoding process and
+ * just does not start it i.e. creates a Processor on the inputDataSource,
+ * sets outputFormat on its tracks (which support a Format compatible with
+ * outputFormat) and connects to its output DataSource.
+ */
+ public void connect()
+ throws IOException
+ {
+ if (outputDataSource != null)
+ return;
+
+ Processor processor;
+
+ try
+ {
+ processor = Manager.createProcessor(inputDataSource);
+ }
+ catch (NoProcessorException npex)
+ {
+ IOException ioex = new IOException();
+ ioex.initCause(npex);
+ throw ioex;
+ }
+
+ ProcessorUtility processorUtility = new ProcessorUtility();
+
+ if (!processorUtility.waitForState(processor, Processor.Configured))
+ throw new IOException("Couldn't configure transcoding processor.");
+
+ TrackControl[] trackControls = processor.getTrackControls();
+
+ if (trackControls != null)
+ for (TrackControl trackControl : trackControls)
+ {
+ Format trackFormat = trackControl.getFormat();
+
+ /*
+ * XXX We only care about AudioFormat here and we assume
+ * outputFormat is of such type because it is in our current and
+ * only use case of TranscodingDataSource
+ */
+ if ((trackFormat instanceof AudioFormat)
+ && !trackFormat.matches(outputFormat))
+ {
+ Format[] supportedTrackFormats
+ = trackControl.getSupportedFormats();
+
+ if (supportedTrackFormats != null)
+ for (Format supportedTrackFormat
+ : supportedTrackFormats)
+ if (supportedTrackFormat.matches(outputFormat))
+ {
+ Format intersectionFormat
+ = supportedTrackFormat.intersects(
+ outputFormat);
+
+ if (intersectionFormat != null)
+ {
+ trackControl.setFormat(intersectionFormat);
+ break;
+ }
+ }
+ }
+ }
+
+ if (!processorUtility.waitForState(processor, Processor.Realized))
+ throw new IOException("Couldn't realize transcoding processor.");
+
+ DataSource outputDataSource = processor.getDataOutput();
+ outputDataSource.connect();
+
+ transcodingProcessor = processor;
+ this.outputDataSource = outputDataSource;
+ }
+
+ /*
+ * Implements DataSource#disconnect(). Stops and undoes the whole setup of
+ * the very transcoding process i.e. disconnects from the output DataSource
+ * of the transcodingProcessor and disposes of the transcodingProcessor.
+ */
+ public void disconnect()
+ {
+ if (outputDataSource == null)
+ return;
+
+ try
+ {
+ stop();
+ }
+ catch (IOException ioex)
+ {
+ throw new UndeclaredThrowableException(ioex);
+ }
+
+ outputDataSource.disconnect();
+
+ transcodingProcessor.deallocate();
+ transcodingProcessor.close();
+ transcodingProcessor = null;
+
+ outputDataSource = null;
+ }
+
+ /*
+ * Implements DataSource#getContentType(). Delegates to the actual output of
+ * the transcoding.
+ */
+ public String getContentType()
+ {
+ return
+ (outputDataSource == null)
+ ? null
+ : outputDataSource.getContentType();
+ }
+
+ /*
+ * Implements DataSource#getControl(String). Delegates to the actual output
+ * of the transcoding.
+ */
+ public Object getControl(String controlType)
+ {
+ /*
+ * The Javadoc of DataSource#getControl(String) says it's an error to
+ * call the method without being connected and by that time we should
+ * have the outputDataSource.
+ */
+ return outputDataSource.getControl(controlType);
+ }
+
+ /*
+ * Implements DataSource#getControls(). Delegates to the actual output of
+ * the transcoding.
+ */
+ public Object[] getControls()
+ {
+ return
+ (outputDataSource == null)
+ ? new Object[0]
+ : outputDataSource.getControls();
+ }
+
+ /*
+ * Implements DataSource#getDuration(). Delegates to the actual output of
+ * the transcoding.
+ */
+ public Time getDuration()
+ {
+ return
+ (outputDataSource == null)
+ ? DURATION_UNKNOWN
+ : outputDataSource.getDuration();
+ }
+
+ /**
+ * Gets the output streams that this instance provides. Some of them may be
+ * the result of transcoding the tracks of the input <code>DataSource</code>
+ * of this instance in the output <code>Format</code> of this instance.
+ *
+ * @return an array of <code>SourceStream</code>s which represents the
+ * collection of output streams that this instance provides
+ */
+ public SourceStream[] getStreams()
+ {
+ if (outputDataSource instanceof PushBufferDataSource)
+ return ((PushBufferDataSource) outputDataSource).getStreams();
+ if (outputDataSource instanceof PullBufferDataSource)
+ return ((PullBufferDataSource) outputDataSource).getStreams();
+ if (outputDataSource instanceof PushDataSource)
+ return ((PushDataSource) outputDataSource).getStreams();
+ if (outputDataSource instanceof PullDataSource)
+ return ((PullDataSource) outputDataSource).getStreams();
+ return new SourceStream[0];
+ }
+
+ /*
+ * Implements DataSource#start(). Starts the actual transcoding process
+ * already set up with #connect().
+ */
+ public void start()
+ throws IOException
+ {
+ /*
+ * The Javadoc of DataSource#start() says it's an error to call the
+ * method without being connected and by that time we should have the
+ * outputDataSource.
+ */
+ outputDataSource.start();
+ transcodingProcessor.start();
+ }
+
+ /*
+ * Implements DataSource#stop(). Stops the actual transcoding process if it
+ * has already been set up with #connect().
+ */
+ public void stop()
+ throws IOException
+ {
+ if (outputDataSource != null)
+ {
+ transcodingProcessor.stop();
+ outputDataSource.stop();
+ }
+ }
+}