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 000000000..8917f80a1
--- /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 PushBufferDataSource which is also a
+ * CaptureDevice through delegation to a specific
+ * CaptureDevice.
+ *
+ * @author Lubomir Marinov
+ */
+public abstract class CaptureDeviceDelegatePushBufferDataSource
+ extends PushBufferDataSource
+ implements CaptureDevice
+{
+
+ /**
+ * The CaptureDevice this instance delegates to in order to
+ * implement its CaptureDevice functionality.
+ */
+ private final CaptureDevice captureDevice;
+
+ /**
+ * Initializes a new CaptureDeviceDelegatePushBufferDataSource
+ * instance which delegates to a specific CaptureDevice in
+ * order to implement its CaptureDevice functionality.
+ *
+ * @param captureDevice the CaptureDevice the new instance is
+ * to delegate to in order to provide its
+ * CaptureDevice 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 c4f3d2b8f..1e83bbc6f 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 @@
import java.io.*;
import java.util.*;
+
import javax.media.*;
-import javax.media.control.*;
import javax.media.protocol.*;
/**
@@ -24,8 +24,7 @@
* @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 synchronized void setMute(boolean mute)
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 MutePushBufferStream(PushBufferStream stream)
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 @@ else if (Format.shortArray.equals(dataClass))
}
}
- public void setTransferHandler(BufferTransferHandler transferHandler)
- {
- stream.setTransferHandler((transferHandler == null) ? null
- : new MuteBufferTransferHandler(transferHandler));
- }
-
- /**
- * Implements a BufferTransferHandler wrapper which doesn't
- * expose a wrapped PushBufferStream 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 BufferTransferHandler which receives the
- * actual events from the wrapped PushBufferStream.
- */
- private final BufferTransferHandler transferHandler;
-
- /**
- * Initializes a new MuteBufferTransferHandler instance
- * which is to overwrite the source PushBufferStream of a
- * specific BufferTransferHandler.
- *
- * @param transferHandler the BufferTransferHandler the new
- * instance is to overwrite the source
- * PushBufferStream 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 000000000..fd2e7ce4c
--- /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 BufferTransferHandler wrapper which doesn't
+ * expose a PushBufferStream but rather a specific substitute in order
+ * to give full control to the {@link PushBufferStream#read(Buffer)} method of
+ * the substitute.
+ *
+ * The purpose is achieved in #transferData(PushBufferStream)
+ * where the method argument stream is ignored and the substitute
+ * is used instead.
+ *
PushBufferStream to be overridden for
+ * transferHandler with the substitute of this
+ * instance.
+ */
+ private final PushBufferStream stream;
+
+ /**
+ * The PushBufferStream to override the stream of
+ * this instance for transferHandler.
+ */
+ private final PushBufferStream substitute;
+
+ /**
+ * The wrapped BufferTransferHandler which receives the
+ * actual events from the wrapped PushBufferStream.
+ */
+ private final BufferTransferHandler transferHandler;
+
+ /**
+ * Initializes a new StreamSubstituteBufferTransferHandler instance
+ * which is to overwrite the source PushBufferStream of a specific
+ * BufferTransferHandler.
+ *
+ * @param transferHandler the BufferTransferHandler the new
+ * instance is to overwrite the source PushBufferStream
+ * of
+ * @param stream the PushBufferStream to be overridden for the
+ * specified transferHandler with the specified
+ * substitute
+ * @param substitute the PushBufferStream to override the
+ * specified stream for the specified
+ * transferHandler
+ */
+ 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 000000000..93a27fd92
--- /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.
+ *
+ * The input audio streams are provided to the AudioMixer through
+ * {@link #addInputDataSource(DataSource)} in the form of input
+ * DataSources giving access to one or more input
+ * SourceStreams.
+ *
+ * The output audio stream representing the mix of the multiple input audio
+ * streams is provided by the AudioMixer in the form of a
+ * AudioMixingPushBufferDataSource giving access to a
+ * AudioMixingPushBufferStream. Such an output is obtained through
+ * {@link #createOutputDataSource()}. The AudioMixer 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.
+ *
AudioFormat in which
+ * AudioMixer, AudioMixingPushBufferDataSource and
+ * AudioMixingPushBufferStream output audio.
+ */
+ private static final AudioFormat DEFAULT_OUTPUT_FORMAT
+ = new AudioFormat(
+ AudioFormat.LINEAR,
+ 44100,
+ 16,
+ 2,
+ AudioFormat.LITTLE_ENDIAN,
+ AudioFormat.SIGNED);
+
+ /**
+ * The CaptureDevice capabilities provided by the
+ * AudioMixingPushBufferDataSources created by this
+ * AudioMixer. JMF's
+ * Manager.createMergingDataSource(DataSource[]) requires the
+ * interface implementation for audio if it is implemented for video and it
+ * is indeed the case for our use case of
+ * AudioMixingPushBufferDataSource.
+ */
+ private final CaptureDevice captureDevice;
+
+ /**
+ * The number of output AudioMixingPushBufferDataSources
+ * reading from this AudioMixer which are connected. When the
+ * value is greater than zero, this AudioMixer is connected to
+ * the input DataSources it manages.
+ */
+ private int connected;
+
+ /**
+ * The collection of input DataSources this instance reads
+ * audio data from.
+ */
+ private final ListAudioMixerPushBufferStream through which this
+ * instance pushes audio sample data to
+ * AudioMixingPushBufferStreams to be mixed.
+ */
+ private AudioMixerPushBufferStream outputStream;
+
+ /**
+ * Initializes a new AudioMixer instance. Because JMF's
+ * Manager.createMergingDataSource(DataSource[]) requires the
+ * implementation of CaptureDevice for audio if it is
+ * implemented for video and it is indeed the cause for our use case of
+ * AudioMixingPushBufferDataSource, the new
+ * AudioMixer instance provides specified
+ * CaptureDevice capabilities to the
+ * AudioMixingPushBufferDataSources it creates. The specified
+ * CaptureDevice is also added as the first input
+ * DataSource of the new instance.
+ *
+ * @param captureDevice the CaptureDevice capabilities to be
+ * provided to the AudioMixingPushBufferDataSources
+ * created by the new instance and its first input
+ * DataSource
+ */
+ public AudioMixer(CaptureDevice captureDevice)
+ {
+ this.captureDevice = captureDevice;
+
+ addInputDataSource((DataSource) captureDevice);
+ }
+
+ /**
+ * Adds a new input DataSource to the collection of input
+ * DataSources from which this instance reads audio. If the
+ * specified DataSource indeed provides audio, the respective
+ * contributions to the mix are always included.
+ *
+ * @param inputDataSource a new DataSource to input audio to
+ * this instance
+ */
+ public void addInputDataSource(DataSource inputDataSource)
+ {
+ addInputDataSource(inputDataSource, null);
+ }
+
+ /**
+ * Adds a new input DataSource to the collection of input
+ * DataSources from which this instance reads audio. If the
+ * specified DataSource indeed provides audio, the respective
+ * contributions to the mix will be excluded from the mix output provided
+ * through a specific AudioMixingPushBufferDataSource.
+ *
+ * @param inputDataSource a new DataSource to input audio to
+ * this instance
+ * @param outputDataSource the AudioMixingPushBufferDataSource
+ * to not include the audio contributions of
+ * inputDataSource 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 AudioMixer that an output
+ * AudioMixingPushBufferDataSource reading from it has been
+ * connected. The first of the many
+ * AudioMixingPushBufferDataSources reading from this
+ * AudioMixer which gets connected causes it to connect to the
+ * input DataSources it manages.
+ *
+ * @throws IOException
+ */
+ void connect()
+ throws IOException
+ {
+ if (connected == 0)
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().connect();
+
+ connected++;
+ }
+
+ /**
+ * Creates a new AudioMixingPushBufferDataSource which gives
+ * access to a single audio stream representing the mix of the audio streams
+ * input into this AudioMixer through its input
+ * DataSources. The returned
+ * AudioMixingPushBufferDataSource can also be used to include
+ * new input DataSources in this AudioMixer but
+ * have their contributions not included in the mix available through the
+ * returned AudioMixingPushBufferDataSource.
+ *
+ * @return a new AudioMixingPushBufferDataSource which gives
+ * access to a single audio stream representing the mix of the audio
+ * streams input into this AudioMixer through its input
+ * DataSources
+ */
+ public AudioMixingPushBufferDataSource createOutputDataSource()
+ {
+ return new AudioMixingPushBufferDataSource(this);
+ }
+
+ /**
+ * Creates a DataSource which attempts to transcode the tracks
+ * of a specific input DataSource into a specific output
+ * Format.
+ *
+ * @param inputDataSource
+ * the DataSource from the tracks of which data is
+ * to be read and transcoded into the specified output
+ * Format
+ * @param outputFormat
+ * the Format in which the tracks of
+ * inputDataSource are to be transcoded
+ * @return a new DataSource which attempts to transcode the
+ * tracks of inputDataSource into
+ * outputFormat
+ * @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 AudioMixer that an output
+ * AudioMixingPushBufferDataSource reading from it has been
+ * disconnected. The last of the many
+ * AudioMixingPushBufferDataSources reading from this
+ * AudioMixer which gets disconnected causes it to disconnect
+ * from the input DataSources it manages.
+ */
+ void disconnect()
+ {
+ if (connected <= 0)
+ return;
+
+ connected--;
+
+ if (connected == 0)
+ {
+ outputStream = null;
+
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().disconnect();
+ }
+ }
+
+ /**
+ * Gets the CaptureDeviceInfo of the CaptureDevice
+ * this AudioMixer provides through its output
+ * AudioMixingPushBufferDataSources.
+ *
+ * @return the CaptureDeviceInfo of the
+ * CaptureDevice this AudioMixer provides
+ * through its output AudioMixingPushBufferDataSources
+ */
+ CaptureDeviceInfo getCaptureDeviceInfo()
+ {
+ return captureDevice.getCaptureDeviceInfo();
+ }
+
+ /**
+ * Gets the content type of the data output by this AudioMixer.
+ *
+ * @return the content type of the data output by this
+ * AudioMixer
+ */
+ String getContentType()
+ {
+ return ContentDescriptor.RAW;
+ }
+
+ /**
+ * Gets the duration of each one of the output streams produced by this
+ * AudioMixer.
+ *
+ * @return the duration of each one of the output streams produced by this
+ * AudioMixer
+ */
+ 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 Format in which a specific DataSource
+ * provides stream data.
+ *
+ * @param dataSource
+ * the DataSource for which the Format
+ * in which it provides stream data is to be determined
+ * @return the Format in which the specified
+ * dataSource provides stream data if it was
+ * determined; otherwise, null
+ */
+ private static Format getFormat(DataSource dataSource)
+ {
+ FormatControl formatControl
+ = (FormatControl) dataSource.getControl(
+ FormatControl.class.getName());
+
+ return (formatControl == null) ? null : formatControl.getFormat();
+ }
+
+ /**
+ * Gets the Format in which a specific
+ * SourceStream provides data.
+ *
+ * @param stream
+ * the SourceStream for which the
+ * Format in which it provides data is to be
+ * determined
+ * @return the Format in which the specified
+ * SourceStream provides data if it was determined;
+ * otherwise, null
+ */
+ 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 FormatControls for the
+ * CaptureDevice this AudioMixer provides through
+ * its output AudioMixingPushBufferDataSources.
+ *
+ * @return an array of FormatControls for the
+ * CaptureDevice this AudioMixer provides
+ * through its output AudioMixingPushBufferDataSources
+ */
+ FormatControl[] getFormatControls()
+ {
+ return captureDevice.getFormatControls();
+ }
+
+ /**
+ * Gets the SourceStreams (in the form of
+ * InputStreamDesc) of a specific DataSource
+ * (provided in the form of InputDataSourceDesc) which produce
+ * data in a specific AudioFormat (or a matching one).
+ *
+ * @param inputDataSourceDesc
+ * the DataSource (in the form of
+ * InputDataSourceDesc) which is to be examined for
+ * SourceStreams producing data in the specified
+ * AudioFormat
+ * @param outputFormat
+ * the AudioFormat in which the collected
+ * SourceStreams are to produce data
+ * @param inputStreams
+ * the List of InputStreamDesc in which
+ * the discovered SourceStreams are to be returned
+ * @return true if SourceStreams produced by the
+ * specified input DataSource and outputing data in the
+ * specified AudioFormat were discovered and reported
+ * in inputStreams; otherwise, false
+ */
+ private boolean getInputStreamsFromInputDataSource(
+ InputDataSourceDesc inputDataSourceDesc,
+ AudioFormat outputFormat,
+ ListSourceStreams (in the form of
+ * InputStreamDesc) of the DataSources from which
+ * this AudioMixer reads data which produce data in a specific
+ * AudioFormat. When an input DataSource does not
+ * have such SourceStreams, an attempt is made to transcode its
+ * tracks so that such SourceStreams can be retrieved from it
+ * after transcoding.
+ *
+ * @param outputFormat
+ * the AudioFormat in which the retrieved
+ * SourceStreams are to produce data
+ * @return a new collection of SourceStreams (in the form of
+ * InputStreamDesc) retrieved from the input
+ * DataSources of this AudioMixer and
+ * producing data in the specified AudioFormat
+ * @throws IOException
+ */
+ private CollectionAudioFormat in which the input
+ * DataSources of this AudioMixer can produce data
+ * and which is to be the output Format of this
+ * AudioMixer.
+ *
+ * @return the AudioFormat in which the input
+ * DataSources of this AudioMixer can
+ * produce data and which is to be the output Format of
+ * this AudioMixer
+ */
+ private AudioFormat getOutputFormatFromInputDataSources()
+ {
+ // TODO Auto-generated method stub
+ return DEFAULT_OUTPUT_FORMAT;
+ }
+
+ /**
+ * Gets the AudioMixerPushBufferStream, first creating it if it
+ * does not exist already, which reads data from the input
+ * DataSources of this AudioMixer and pushes it to
+ * output AudioMixingPushBufferStreams for audio mixing.
+ *
+ * @return the AudioMixerPushBufferStream which reads data from
+ * the input DataSources of this
+ * AudioMixer and pushes it to output
+ * AudioMixingPushBufferStreams for audio mixing
+ */
+ AudioMixerPushBufferStream getOutputStream()
+ {
+ AudioFormat outputFormat = getOutputFormatFromInputDataSources();
+
+ setOutputFormatToInputDataSources(outputFormat);
+
+ CollectionFormat matches a specific
+ * Format in the sense of JMF Format matching.
+ * Since this AudioMixer and the audio mixing functionality
+ * related to it can handle varying characteristics of a certain output
+ * Format, the only requirement for the specified
+ * Formats to match is for both of them to have one and the
+ * same encoding.
+ *
+ * @param input
+ * the Format for which it is required to determine
+ * whether it matches a specific Format
+ * @param pattern
+ * the Format against which the specified
+ * input is to be matched
+ * @return true if the specified
+ * input matches the specified pattern in
+ * the sense of JMF Format matching; otherwise,
+ * false
+ */
+ 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 input 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 input at which the reading of the
+ * short integer is to start
+ * @return a short integer in the form of
+ * int 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 AudioFormat, if possible, as the output
+ * format of the input DataSources of this
+ * AudioMixer in an attempt to not have to perform explicit
+ * transcoding of the input SourceStreams.
+ *
+ * @param outputFormat
+ * the AudioFormat in which the input
+ * DataSources of this AudioMixer 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 DataSources of this AudioMixer.
+ *
+ * @throws IOException
+ */
+ void start()
+ throws IOException
+ {
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().start();
+ }
+
+ /**
+ * Stops the input DataSources of this AudioMixer.
+ *
+ * @throws IOException
+ */
+ void stop()
+ throws IOException
+ {
+ for (InputDataSourceDesc inputDataSourceDesc : inputDataSources)
+ inputDataSourceDesc.getEffectiveInputDataSource().stop();
+ }
+
+ /**
+ * Represents a PushBufferStream which reads data from the
+ * SourceStreams of the input DataSources of this
+ * AudioMixer and pushes it to
+ * AudioMixingPushBufferStreams for audio mixing.
+ */
+ class AudioMixerPushBufferStream
+ implements PushBufferStream
+ {
+
+ /**
+ * The factor which scales a short value to an int
+ * value.
+ */
+ private static final float INT_TO_SHORT_RATIO
+ = Integer.MAX_VALUE / (float) Short.MAX_VALUE;
+
+ /**
+ * The factor which scales an int value to a short
+ * value.
+ */
+ private static final float SHORT_TO_INT_RATIO
+ = Short.MAX_VALUE / (float) Integer.MAX_VALUE;
+
+ /**
+ * The SourceStreams (in the form of
+ * InputStreamDesc so that this instance can track back the
+ * AudioMixingPushBufferDataSource which outputs the mixed
+ * audio stream and determine whether the associated
+ * SourceStream is to be included into the mix) from which
+ * this instance reads its data.
+ */
+ private InputStreamDesc[] inputStreams;
+
+ /**
+ * The AudioFormat of the data this instance outputs.
+ */
+ private final AudioFormat outputFormat;
+
+ /**
+ * The AudioMixingPushBufferStreams to which this instance
+ * pushes data for audio mixing.
+ */
+ private final List outputStreams
+ = new ArrayList();
+
+ /**
+ * The BufferTransferHandler through which this instance
+ * gets notifications from its input SourceStreams 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 AudioMixerPushBufferStream instance to
+ * output data in a specific AudioFormat.
+ *
+ * @param outputFormat
+ * the AudioFormat in which the new instance is
+ * to output data
+ */
+ private AudioMixerPushBufferStream(AudioFormat outputFormat)
+ {
+ this.outputFormat = outputFormat;
+ }
+
+ /**
+ * Adds a specific AudioMixingPushBufferStream to the
+ * collection of such streams which this instance is to push the data
+ * for audio mixing it reads from its input SourceStreams.
+ *
+ * @param outputStream
+ * the AudioMixingPushBufferStream 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
+ * SourceStreams
+ */
+ 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 PushBufferStream and
+ * converts them to a specific output AudioFormat. An
+ * attempt is made to read a specific maximum number of samples from the
+ * specified PushBufferStream but the very
+ * PushBufferStream may not honor the request.
+ *
+ * @param inputStream
+ * the PushBufferStream to read from
+ * @param outputFormat
+ * the AudioFormat to which the samples read
+ * from inputStream are to converted before
+ * being returned
+ * @param sampleCount
+ * the maximum number of samples which the read operation
+ * should attempt to read from inputStream but
+ * the very inputStream 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 PullBufferStreams of
+ * this instance and converts them to a specific output
+ * AudioFormat. An attempt is made to read a specific
+ * maximum number of samples from each of the
+ * PullBufferStreams but the very
+ * PullBufferStream may not honor the request.
+ *
+ * @param outputFormat
+ * the AudioFormat in which the audio samples
+ * read from the PullBufferStreams are to be
+ * converted before being returned
+ * @param outputSampleCount
+ * the maximum number of audio samples to be read from each
+ * of the PullBufferStreams but the very
+ * PullBufferStream 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 PullBufferStreams 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 PushBufferStreams of
+ * this instance and converts them to a specific output
+ * AudioFormat.
+ *
+ * @param outputFormat
+ * the AudioFormat in which the audio samples
+ * read from the PushBufferStreams 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 PushBufferStreams 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 AudioMixingPushBufferStream from the
+ * collection of such streams which this instance pushes the data for
+ * audio mixing it reads from its input SourceStreams.
+ *
+ * @param outputStream
+ * the AudioMixingPushBufferStream to be removed
+ * from the collection of such streams which this instance
+ * pushes the data for audio mixing it reads from its input
+ * SourceStreams
+ */
+ 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
+ * AudioMixingPushBufferStream for audio mixing. Audio
+ * samples read from input DataSources which the
+ * AudioMixingPushBufferDataSource owner of the specified
+ * AudioMixingPushBufferStream has specified to not be
+ * included in the output mix are not pushed to the
+ * AudioMixingPushBufferStream.
+ *
+ * @param outputStream
+ * the AudioMixingPushBufferStream to push the
+ * specified set of audio samples to
+ * @param inputSamples
+ * the set of audio samples to be pushed to
+ * outputStream for audio mixing
+ * @param maxInputSampleCount
+ * the maximum number of audio samples available in
+ * inputSamples
+ */
+ 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 SourceStreams (in the form of
+ * InputStreamDesc) from which this instance is to read
+ * audio samples and push them to the
+ * AudioMixingPushBufferStreams for audio mixing.
+ *
+ * @param inputStreams
+ * the SourceStreams (in the form of
+ * InputStreamDesc) from which this instance is
+ * to read audio samples and push them to the
+ * AudioMixingPushBufferStreams for audio mixing
+ */
+ private void setInputStreams(Collection 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 BufferTransferHandler to a specific
+ * collection of SourceStreams (in the form of
+ * InputStreamDesc) abstracting the differences among the
+ * various types of SourceStreams.
+ *
+ * @param inputStreams
+ * the input SourceStreams to which the
+ * specified BufferTransferHandler is to be set
+ * @param transferHandler
+ * the BufferTransferHandler to be set to the
+ * specified inputStreams
+ */
+ 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 SourceStreams of this
+ * instance and pushes them to its output
+ * AudioMixingPushBufferStreams 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
+ * DataSource of an AudioMixer so that the
+ * AudioMixer can, for example, quickly discover the output
+ * AudioMixingPushBufferDataSource in the mix of which the
+ * contribution of the DataSource is to not be included.
+ */
+ private static class InputDataSourceDesc
+ {
+
+ /**
+ * The DataSource for which additional information is
+ * described by this instance.
+ */
+ public final DataSource inputDataSource;
+
+ /**
+ * The AudioMixingPushBufferDataSource in which the
+ * mix contributions of the DataSource described by this
+ * instance are to not be included.
+ */
+ public final AudioMixingPushBufferDataSource outputDataSource;
+
+ /**
+ * The DataSource, if any, which transcodes the tracks of
+ * inputDataSource in the output Format of the
+ * associated AudioMixer.
+ */
+ private DataSource transcodingDataSource;
+
+ /**
+ * Initializes a new InputDataSourceDesc instance which is
+ * to describe additional information about a specific input
+ * DataSource of an AudioMixer. Associates the
+ * specified DataSource with the
+ * AudioMixingPushBufferDataSource in which the mix
+ * contributions of the specified input DataSource are to
+ * not be included.
+ *
+ * @param inputDataSource
+ * a DataSourc for which additional information
+ * is to be described by the new instance
+ * @param outputDataSource
+ * the AudioMixingPushBufferDataSource in which
+ * the mix contributions of inputDataSource are
+ * to not be included
+ */
+ public InputDataSourceDesc(
+ DataSource inputDataSource,
+ AudioMixingPushBufferDataSource outputDataSource)
+ {
+ this.inputDataSource = inputDataSource;
+ this.outputDataSource = outputDataSource;
+ }
+
+ /**
+ * Gets the actual DataSource from which the associated
+ * AudioMixer directly reads in order to retrieve the mix
+ * contribution of the DataSource described by this
+ * instance.
+ *
+ * @return the actual DataSource from which the associated
+ * AudioMixer directly reads in order to retrieve
+ * the mix contribution of the DataSource described
+ * by this instance
+ */
+ public DataSource getEffectiveInputDataSource()
+ {
+ return
+ (transcodingDataSource == null)
+ ? inputDataSource
+ : transcodingDataSource;
+ }
+
+ /**
+ * Sets the DataSource, if any, which transcodes the tracks
+ * of the input DataSource described by this instance in
+ * the output Format of the associated
+ * AudioMixer.
+ *
+ * @param transcodingDataSource
+ * the DataSource which transcodes the tracks of
+ * the input DataSource described by this
+ * instance in the output Format of the
+ * associated AudioMixer
+ */
+ public void setTranscodingDataSource(DataSource transcodingDataSource)
+ {
+ this.transcodingDataSource = transcodingDataSource;
+ }
+ }
+
+ /**
+ * Describes additional information about a specific input audio
+ * SourceStream of an AudioMixer so that the
+ * AudioMixer can, for example, quickly discover the output
+ * AudioMixingPushBufferDataSource in the mix of which the
+ * contribution of the SourceStream is to not be included.
+ */
+ private static class InputStreamDesc
+ {
+
+ /**
+ * The DataSource which created the
+ * SourceStream described by this instance and additional
+ * information about it.
+ */
+ private final InputDataSourceDesc inputDataSourceDesc;
+
+ /**
+ * The SourceStream for which additional information is
+ * described by this instance.
+ */
+ private SourceStream inputStream;
+
+ /**
+ * Initializes a new InputStreamDesc instance which is to
+ * describe additional information about a specific input audio
+ * SourceStream of an AudioMixer. Associates
+ * the specified SourceStream with the
+ * DataSource which created it and additional information
+ * about it.
+ *
+ * @param inputStream
+ * a SourceStream for which additional
+ * information is to be described by the new instance
+ * @param inputDataSourceDesc
+ * the DataSource which created the
+ * SourceStream 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 SourceStream described by this instance
+ *
+ * @return the SourceStream described by this instance
+ */
+ public SourceStream getInputStream()
+ {
+ return inputStream;
+ }
+
+ /**
+ * Gets the AudioMixingPushBufferDataSource in which the
+ * mix contribution of the SourceStream described by this
+ * instance is to not be included.
+ *
+ * @return the AudioMixingPushBufferDataSource in which the
+ * mix contribution of the SourceStream described
+ * by this instance is to not be included
+ */
+ public AudioMixingPushBufferDataSource getOutputDataSource()
+ {
+ return inputDataSourceDesc.outputDataSource;
+ }
+
+ /**
+ * Sets the SourceStream to be described by this instance
+ *
+ * @param inputStream
+ * the SourceStream 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 000000000..faa7072a2
--- /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 PushBufferDataSource which provides a single
+ * PushBufferStream containing the result of the audio mixing of
+ * DataSources.
+ *
+ * @author Lubomir Marinov
+ */
+public class AudioMixingPushBufferDataSource
+ extends PushBufferDataSource
+ implements CaptureDevice
+{
+
+ /**
+ * The AudioMixer performing the audio mixing, managing the
+ * input DataSources and pushing the data of this output
+ * PushBufferDataSource.
+ */
+ private final AudioMixer audioMixer;
+
+ /**
+ * The indicator which determines whether this DataSource is
+ * connected.
+ */
+ private boolean connected;
+
+ /**
+ * The one and only PushBufferStream this
+ * PushBufferDataSource provides to its clients and containing
+ * the result of the audio mixing performed by audioMixer.
+ */
+ private AudioMixingPushBufferStream outputStream;
+
+ /**
+ * The indicator which determines whether this DataSource is
+ * started.
+ */
+ private boolean started;
+
+ /**
+ * Initializes a new AudioMixingPushBufferDataSource instance
+ * which gives access to the result of the audio mixing performed by a
+ * specific AudioMixer.
+ *
+ * @param audioMixer the AudioMixer performing audio mixing,
+ * managing the input DataSources and pushing the
+ * data of the new output PushBufferDataSource
+ */
+ public AudioMixingPushBufferDataSource(AudioMixer audioMixer)
+ {
+ this.audioMixer = audioMixer;
+ }
+
+ /**
+ * Adds a new input DataSource to be mixed by the associated
+ * AudioMixer of this instance and to not have its audio
+ * contributions included in the mixing output represented by this
+ * DataSource.
+ *
+ * @param inputDataSource a DataSource to be added for mixing
+ * to the AudioMixer associate with this instance
+ * and to not have its audio contributions included in the mixing
+ * output represented by this DataSource
+ */
+ 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 000000000..ad28c1da1
--- /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 PushBufferStream containing the result of the audio
+ * mixing of DataSources.
+ *
+ * @author Lubomir Marinov
+ */
+public class AudioMixingPushBufferStream
+ implements PushBufferStream
+{
+
+ /**
+ * The AudioMixer.AudioMixerPushBufferStream which reads data
+ * from the input DataSources and pushes it to this instance to
+ * be mixed.
+ */
+ private final AudioMixer.AudioMixerPushBufferStream audioMixerStream;
+
+ /**
+ * The AudioMixingPushBufferDataSource which created and owns
+ * this instance and defines the input data which is to not be mixed in the
+ * output of this PushBufferStream.
+ */
+ private final AudioMixingPushBufferDataSource dataSource;
+
+ /**
+ * The collection of input audio samples still not mixed and read through
+ * this AudioMixingPushBufferStream.
+ */
+ private int[][] inputSamples;
+
+ /**
+ * The maximum number of per-stream audio samples available through
+ * inputSamples.
+ */
+ private int maxInputSampleCount;
+
+ /**
+ * The BufferTransferHandler through which this
+ * PushBufferStream notifies its clients that new data is
+ * available for reading.
+ */
+ private BufferTransferHandler transferHandler;
+
+ /**
+ * Initializes a new AudioMixingPushBufferStream mixing the
+ * input data of a specific
+ * AudioMixer.AudioMixerPushBufferStream and excluding from the
+ * mix the audio contributions of a specific
+ * AudioMixingPushBufferDataSource.
+ *
+ * @param audioMixerStream the
+ * AudioMixer.AudioMixerPushBufferStream reading
+ * data from input DataSources and to push it to the
+ * new AudioMixingPushBufferStream
+ * @param dataSource the AudioMixingPushBufferDataSource 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 AudioMixingPushBufferDataSource which created and
+ * owns this instance and defines the input data which is to not be mixed in
+ * the output of this PushBufferStream.
+ *
+ * @return the AudioMixingPushBufferDataSource which created
+ * and owns this instance and defines the input data which is to not
+ * be mixed in the output of this PushBufferStream
+ */
+ 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
+ * AudioFormat.
+ *
+ * @param outputFormat the AudioFormat of which to get the
+ * maximum possible value for an audio sample
+ * @return the maximum possible value for an audio sample of the specified
+ * AudioFormat
+ * @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
+ * AudioFormat.
+ *
+ * @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 AudioFormat 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 inputSamples
+ */
+ 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 output 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 output 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 000000000..3f86ff2ae
--- /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 SourceStreams, usually
+ * ones reading data in arrays of bytes and not in Buffers, to
+ * SourceStreams reading data in Buffers. An example
+ * use is creating a PushBufferStream representation of a PushSourceStream.
+ *
+ * @author Lubomir Marinov
+ */
+public abstract class BufferStreamAdapter
+ implements SourceStream
+{
+
+ /**
+ * The Format of this stream to be reported through the output
+ * Buffer this instance reads data into.
+ */
+ private final Format format;
+
+ /**
+ * The SourceStream being adapted by this instance.
+ */
+ protected final T stream;
+
+ /**
+ * Initializes a new BufferStreamAdapter which is to adapt a
+ * specific SourceStream into a SourceStream with
+ * a specific Format.
+ *
+ * @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 Format of the data this stream provides.
+ *
+ * @return the Format of the data this stream provides
+ */
+ public Format getFormat()
+ {
+ return format;
+ }
+
+ /**
+ * Reads byte data from this stream into a specific Buffer
+ * which is to use a specific array of bytes for its data.
+ *
+ * @param buffer the Buffer to read byte data into from this
+ * instance
+ * @param bytes the array of bytes to read data into from this
+ * instance and to be set as the data of the specified
+ * buffer
+ * @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
+ * bytes starting the storing at a specific offset and reading
+ * at most a specific number of bytes.
+ *
+ * @param buffer the array of bytes into which the data read
+ * from this stream is to be written
+ * @param offset the offset in the specified buffer at which
+ * writing data read from this stream should start
+ * @param length the maximum number of bytes to be written into the
+ * specified buffer
+ * @return the number of bytes read from this stream and written into the
+ * specified buffer
+ * @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 000000000..c29324f59
--- /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 PushBufferStream a certain maximum number
+ * of data units (e.g. bytes, shorts, ints) even if the
+ * PushBufferStream itself pushes a larger number of data units.
+ *
+ * An example use of this functionality is pacing a
+ * PushBufferStream which pushes more data units in a single step
+ * than a CaptureDevice. When these two undergo audio mixing, the
+ * different numbers of per-push data units will cause the
+ * PushBufferStream "play" itself faster than the
+ * CaptureDevice.
+ *
+ *
+ * @author Lubomir Marinov
+ */
+public class CachingPushBufferStream
+ implements PushBufferStream
+{
+
+ /**
+ * The Buffer in which this instance stores the data it reads
+ * from the wrapped PushBufferStream and from which it reads in
+ * chunks later on when its #read(Buffer) method is called.
+ */
+ private Buffer cache;
+
+ /**
+ * The last IOException this stream has received from the
+ * #read(Buffer) method of the wrapped stream and to be thrown
+ * by this stream on the earliest call of its #read(Buffer)
+ * method.
+ */
+ private IOException readException;
+
+ /**
+ * The PushBufferStream 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 CachingPushBufferStream instance which is
+ * to pace the number of per-push data units a specific
+ * PushBufferStream provides.
+ *
+ * @param stream the PushBufferStream 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 Buffer provided to the
+ * #read(Buffer) 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 Buffer (if such data is
+ * available) and writes the read data into a specific output
+ * Buffer. The input Buffer will be modified to
+ * reflect the number of read data units. If the output Buffer
+ * has allocated an array for storing the read data and the type of this
+ * array matches that of the input Buffer, it will be used and
+ * thus the output Buffer may control the maximum number of
+ * data units to be read into it.
+ *
+ * @param input the Buffer to read data from
+ * @param output the Buffer into which to write the data read
+ * from the specified input
+ * @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 000000000..f167bfb6d
--- /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 PullBufferStream which reads its data from a
+ * specific PullSourceStream.
+ *
+ * @author Lubomir Marinov
+ */
+public class PullBufferStreamAdapter
+ extends BufferStreamAdapter
+ implements PullBufferStream
+{
+
+ /**
+ * Initializes a new PullBufferStreamAdapter instance which
+ * reads its data from a specific PullSourceStream with a
+ * specific Format
+ *
+ * @param stream the PullSourceStream the new instance is to
+ * read its data from
+ * @param format the Format of the specified input
+ * stream 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
+ * Format.
+ *
+ * @param format the Format to determine the frame size in
+ * bytes of
+ * @return the frame size measured in bytes defined by the specified
+ * Format
+ */
+ 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 000000000..a3369c547
--- /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 PushBufferStream which reads its data from a
+ * specific PushSourceStream.
+ *
+ * @author Lubomir Marinov
+ */
+public class PushBufferStreamAdapter
+ extends BufferStreamAdapter
+ implements PushBufferStream
+{
+
+ /**
+ * Initializes a new PushBufferStreamAdapter instance which
+ * reads its data from a specific PushSourceStream with a
+ * specific Format
+ *
+ * @param stream the PushSourceStream the new instance is to
+ * read its data from
+ * @param format the Format of the specified input
+ * stream 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 000000000..2a73e65d7
--- /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 DataSource which transcodes the tracks of a
+ * specific input DataSource into a specific output
+ * Format. The transcoding is attempted only for tracks which
+ * actually support it for the specified output Format.
+ *
+ * @author Lubomir Marinov
+ */
+public class TranscodingDataSource
+ extends DataSource
+{
+
+ /**
+ * The DataSource which has its tracks transcoded by this
+ * instance.
+ */
+ private final DataSource inputDataSource;
+
+ /**
+ * The DataSource which contains the transcoded tracks of
+ * inputDataSource and which is wrapped by this instance. It is
+ * the output of transcodingProcessor.
+ */
+ private DataSource outputDataSource;
+
+ /**
+ * The Format in which the tracks of
+ * inputDataSource are transcoded.
+ */
+ private final Format outputFormat;
+
+ /**
+ * The Processor which carries out the actual transcoding of
+ * the tracks of inputDataSource.
+ */
+ private Processor transcodingProcessor;
+
+ /**
+ * Initializes a new TranscodingDataSource instance to
+ * transcode the tracks of a specific DataSource into a
+ * specific output Format.
+ *
+ * @param inputDataSource the DataSource which is to have its
+ * tracks transcoded in a specific outptu Format
+ * @param outputFormat the Format in which the new instance is
+ * to transcode the tracks of inputDataSource
+ */
+ 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 DataSource
+ * of this instance in the output Format of this instance.
+ *
+ * @return an array of SourceStreams 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();
+ }
+ }
+}