All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jitsi.impl.neomedia.conference.AudioMixer Maven / Gradle / Ivy

/*
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jitsi.impl.neomedia.conference;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

import javax.media.*;
import javax.media.Controls;
import javax.media.control.*;
import javax.media.format.*;
import javax.media.protocol.*;

import org.jitsi.impl.neomedia.*;
import org.jitsi.impl.neomedia.control.*;
import org.jitsi.impl.neomedia.device.*;
import org.jitsi.impl.neomedia.protocol.*;
import org.jitsi.util.*;
import org.jitsi.utils.logging.*;

/**
 * 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 #addInDataSource(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 #createOutDataSource()}. 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. * * @author Lyubomir Marinov */ public class AudioMixer { /** * The default output AudioFormat in which AudioMixer, * AudioMixingPushBufferDataSource and * AudioMixingPushBufferStream output audio. */ private static final AudioFormat DEFAULT_OUTPUT_FORMAT = new AudioFormat( AudioFormat.LINEAR, 8000, 16, 1, AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED); /** * The Logger used by the AudioMixer class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(AudioMixer.class); /** * 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; } /** * The BufferControl of this instance and, respectively, its * AudioMixingPushBufferDataSources. */ private BufferControl bufferControl; /** * 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. */ protected 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 List inDataSources = new ArrayList(); /** * The AudioMixingPushBufferDataSource which contains the mix of * inDataSources excluding captureDevice and is thus * meant for playback on the local peer in a call. */ private final AudioMixingPushBufferDataSource localOutDataSource; /** * The output AudioMixerPushBufferStream through which this * instance pushes audio sample data to * AudioMixingPushBufferStreams to be mixed. */ private AudioMixerPushBufferStream outStream; /** * The number of output AudioMixingPushBufferDataSources reading * from this AudioMixer which are started. When the value is * greater than zero, this AudioMixer is started and so are the * input DataSources it manages. */ private int started; /** * The greatest generation with which * {@link #start(AudioMixerPushBufferStream, long)} or * {@link #stop(AudioMixerPushBufferStream, long)} has been invoked. */ private long startedGeneration; /** * 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) { /* * AudioMixer provides PushBufferDataSources so it needs a way to push * them. It does the pushing by using the pushes of its CaptureDevice * i.e. it has to be a PushBufferDataSource. */ if (captureDevice instanceof PullBufferDataSource) { captureDevice = new PushBufferDataSourceAdapter( (PullBufferDataSource) captureDevice); } // Try to enable tracing on captureDevice. if (logger.isTraceEnabled()) { captureDevice = MediaDeviceImpl.createTracingCaptureDevice( captureDevice, logger); } this.captureDevice = captureDevice; this.localOutDataSource = createOutDataSource(); addInDataSource( (DataSource) this.captureDevice, this.localOutDataSource); } /** * 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 inDataSource a new DataSource to input audio to this * instance */ public void addInDataSource(DataSource inDataSource) { addInDataSource(inDataSource, 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 inDataSource a new DataSource to input audio to this * instance * @param outDataSource the AudioMixingPushBufferDataSource to * not include the audio contributions of inDataSource in the * mix it outputs */ void addInDataSource( DataSource inDataSource, AudioMixingPushBufferDataSource outDataSource) { if (inDataSource == null) throw new NullPointerException("inDataSource"); synchronized (inDataSources) { for (InDataSourceDesc inDataSourceDesc : inDataSources) if (inDataSource.equals(inDataSourceDesc.inDataSource)) throw new IllegalArgumentException("inDataSource"); InDataSourceDesc inDataSourceDesc = new InDataSourceDesc( inDataSource, outDataSource); boolean added = inDataSources.add(inDataSourceDesc); if (added) { if (logger.isTraceEnabled()) { logger.trace( "Added input DataSource with hashCode " + inDataSource.hashCode()); } /* * If the other inDataSources have already been connected, * connect to the new one as well. */ if (connected > 0) { try { inDataSourceDesc.connect(this); } catch (IOException ioex) { throw new UndeclaredThrowableException(ioex); } } // Update outStream with any new inStreams. if (outStream != null) getOutStream(); /* * If the other inDataSources have been started, start the * new one as well. */ if (started > 0) { try { inDataSourceDesc.start(); } catch (IOException ioe) { throw new UndeclaredThrowableException(ioe); } } } } } /** * 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 if input/output error occurred */ void connect() throws IOException { synchronized (inDataSources) { if (connected == 0) { for (InDataSourceDesc inDataSourceDesc : inDataSources) try { inDataSourceDesc.connect(this); } catch (IOException ioe) { logger.error( "Failed to connect to inDataSource " + MediaStreamImpl.toString( inDataSourceDesc.inDataSource), ioe); throw ioe; } /* * Since the media of the input streams is to be mixed, their * bufferLengths have to be equal. After a DataSource is * connected, its BufferControl is available and its * bufferLength may change so make sure that the bufferLengths * of the input streams are equal. */ if (outStream != null) outStream.equalizeInStreamBufferLength(); } connected++; } } /** * Connects to a specific DataSource which this AudioMixer * will read audio from. The specified DataSource is known to exist * because of a specific DataSource added as an input to this * instance i.e. it may be an actual input DataSource added to this * instance or a DataSource transcoding an input * DataSource added to this instance. * * @param dataSource the DataSource to connect to * @param inDataSource the DataSource which is the cause for * dataSource to exist in this AudioMixer * @throws IOException if anything wrong happens while connecting to * dataSource */ protected void connect(DataSource dataSource, DataSource inDataSource) throws IOException { dataSource.connect(); } /** * Notifies this AudioMixer that a specific input * DataSource has finished its connecting procedure. Primarily * meant for input DataSource which have their connecting executed * in a separate thread as are, for example, input DataSources * which are being transcoded. * * @param inDataSource the InDataSourceDesc of the input * DataSource which has finished its connecting procedure * @throws IOException if anything wrong happens while including * inDataSource into the mix */ void connected(InDataSourceDesc inDataSource) throws IOException { synchronized (inDataSources) { if (inDataSources.contains(inDataSource) && (connected > 0)) { if (started > 0) inDataSource.start(); if (outStream != null) getOutStream(); } } } /** * Creates a new InStreamDesc instance which is to describe a * specific input SourceStream originating from a specific input * DataSource given by its InDataSourceDesc. * * @param inStream the input SourceStream to be described by the * new instance * @param inDataSourceDesc the input DataSource given by its * InDataSourceDesc to be described by the new instance * @return a new InStreamDesc instance which describes the * specified input SourceStream and DataSource */ private InStreamDesc createInStreamDesc( SourceStream inStream, InDataSourceDesc inDataSourceDesc) { return new InStreamDesc(inStream, inDataSourceDesc); } /** * 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 createOutDataSource() { return new AudioMixingPushBufferDataSource(this); } /** * Creates a DataSource which attempts to transcode the tracks of a * specific input DataSource into a specific output * Format. * * @param inDataSourceDesc the InDataSourceDesc describing * the input DataSource to be transcoded into the specified output * Format and to receive the transcoding DataSource * @param outFormat the Format in which the tracks of the input * DataSource are to be transcoded * @return true if a new transcoding DataSource has been * created for the input DataSource described by * inDataSourceDesc; otherwise, false * @throws IOException if an error occurs while creating the transcoding * DataSource, connecting to it or staring it */ private boolean createTranscodingDataSource( InDataSourceDesc inDataSourceDesc, Format outFormat) throws IOException { if (inDataSourceDesc.createTranscodingDataSource(outFormat)) { if (connected > 0) inDataSourceDesc.connect(this); if (started > 0) inDataSourceDesc.start(); return true; } else return false; } /** * 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() { synchronized (inDataSources) { if (connected <= 0) return; connected--; if (connected == 0) { for (InDataSourceDesc inDataSourceDesc : inDataSources) inDataSourceDesc.disconnect(); /* * XXX Make the outStream to release the inStreams. * Otherwise, the PushBufferStream ones which have been wrapped * into CachingPushBufferStream may remain waiting. */ outStream.setInStreams(null); outStream = null; startedGeneration = 0; } } } /** * Gets the BufferControl of this instance and, respectively, its * AudioMixingPushBufferDataSources. * * @return the BufferControl of this instance and, respectively, * its AudioMixingPushBufferDataSources if such a control is * available for the CaptureDevice of this instance; otherwise, * null */ BufferControl getBufferControl() { if ((bufferControl == null) && (captureDevice instanceof Controls)) { BufferControl captureDeviceBufferControl = (BufferControl) ((Controls) captureDevice).getControl( BufferControl.class.getName()); if (captureDeviceBufferControl != null) bufferControl = new ReadOnlyBufferControlDelegate( captureDeviceBufferControl); } return bufferControl; } /** * 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() { return ((DataSource) captureDevice).getDuration(); } /** * Gets an InStreamDesc from a specific existing list of * InStreamDescs which describes a specific * SourceStream. If such an InStreamDesc does not * exist, returns null. * * @param inStream the SourceStream to locate an * InStreamDesc for in existingInStreamDescs * @param existingInStreamDescs the list of existing * InStreamDescs in which an InStreamDesc for * inStream is to be located * @return an InStreamDesc from * existingInStreamDescs which describes inStream if * such an InStreamDesc exists; otherwise, null */ private InStreamDesc getExistingInStreamDesc( SourceStream inStream, InStreamDesc[] existingInStreamDescs) { if (existingInStreamDescs == null) return null; for (InStreamDesc existingInStreamDesc : existingInStreamDescs) { SourceStream existingInStream = existingInStreamDesc.getInStream(); if (existingInStream == inStream) return existingInStreamDesc; if ((existingInStream instanceof BufferStreamAdapter) && (((BufferStreamAdapter) existingInStream).getStream() == inStream)) return existingInStreamDesc; if ((existingInStream instanceof CachingPushBufferStream) && (((CachingPushBufferStream) existingInStream).getStream() == inStream)) return existingInStreamDesc; } return null; } /** * 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() { /* * Setting the format of the captureDevice once we've started using it * is likely to wreak havoc so disable it. */ FormatControl[] formatControls = captureDevice.getFormatControls(); if (!OSUtils.IS_ANDROID && (formatControls != null)) { for (int i = 0; i < formatControls.length; i++) { formatControls[i] = new ReadOnlyFormatControlDelegate(formatControls[i]); } } return formatControls; } /** * Gets the SourceStreams (in the form of InStreamDesc) * of a specific DataSource (provided in the form of * InDataSourceDesc) which produce data in a specific * AudioFormat (or a matching one). * * @param inDataSourceDesc the DataSource (in the form of * InDataSourceDesc) which is to be examined for * SourceStreams producing data in the specified * AudioFormat * @param outFormat the AudioFormat in which the collected * SourceStreams are to produce data * @param existingInStreams the InStreamDesc instances which * already exist and which are used to avoid creating multiple * InStreamDescs for input SourceStreams which already * have ones * @param inStreams the List of InStreamDesc in * which the discovered SourceStreams are to be returned * @return true if SourceStreams produced by the specified * input DataSource and outputting data in the specified * AudioFormat were discovered and reported in * inStreams; otherwise, false */ private boolean getInStreamsFromInDataSource( InDataSourceDesc inDataSourceDesc, AudioFormat outFormat, InStreamDesc[] existingInStreams, List inStreams) { SourceStream[] inDataSourceStreams = inDataSourceDesc.getStreams(); if (inDataSourceStreams != null) { boolean added = false; for (SourceStream inStream : inDataSourceStreams) { Format inFormat = getFormat(inStream); if ((inFormat != null) && matches(inFormat, outFormat)) { InStreamDesc inStreamDesc = getExistingInStreamDesc(inStream, existingInStreams); if (inStreamDesc == null) inStreamDesc = createInStreamDesc(inStream, inDataSourceDesc); if (inStreams.add(inStreamDesc)) added = true; } } return added; } DataSource inDataSource = inDataSourceDesc.getEffectiveInDataSource(); if (inDataSource == null) return false; Format inFormat = getFormat(inDataSource); if ((inFormat != null) && !matches(inFormat, outFormat)) { if (inDataSource instanceof PushDataSource) { for (PushSourceStream inStream : ((PushDataSource) inDataSource).getStreams()) { InStreamDesc inStreamDesc = getExistingInStreamDesc(inStream, existingInStreams); if (inStreamDesc == null) inStreamDesc = createInStreamDesc( new PushBufferStreamAdapter( inStream, inFormat), inDataSourceDesc); inStreams.add(inStreamDesc); } return true; } if (inDataSource instanceof PullDataSource) { for (PullSourceStream inStream : ((PullDataSource) inDataSource).getStreams()) { InStreamDesc inStreamDesc = getExistingInStreamDesc(inStream, existingInStreams); if (inStreamDesc == null) inStreamDesc = createInStreamDesc( new PullBufferStreamAdapter( inStream, inFormat), inDataSourceDesc); inStreams.add(inStreamDesc); } return true; } } return false; } /** * Gets the SourceStreams (in the form of InStreamDesc) * 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 outFormat the AudioFormat in which the retrieved * SourceStreams are to produce data * @param existingInStreams the SourceStreams which are already * known to this AudioMixer * @return a new collection of SourceStreams (in the form of * InStreamDesc) retrieved from the input DataSources * of this AudioMixer and producing data in the specified * AudioFormat * @throws IOException if anything wrong goes while retrieving the input * SourceStreams from the input DataSources */ private Collection getInStreamsFromInDataSources( AudioFormat outFormat, InStreamDesc[] existingInStreams) throws IOException { List inStreams = new ArrayList(); synchronized (inDataSources) { for (InDataSourceDesc inDataSourceDesc : inDataSources) { boolean got = getInStreamsFromInDataSource( inDataSourceDesc, outFormat, existingInStreams, inStreams); if (!got && createTranscodingDataSource( inDataSourceDesc, outFormat)) getInStreamsFromInDataSource( inDataSourceDesc, outFormat, existingInStreams, inStreams); } } return inStreams; } /** * Gets the AudioMixingPushBufferDataSource containing the mix of * all input DataSources excluding the CaptureDevice of * this AudioMixer and is thus meant for playback on the local peer * in a call. * * @return the AudioMixingPushBufferDataSource containing the mix * of all input DataSources excluding the CaptureDevice of * this AudioMixer and is thus meant for playback on the local peer * in a call */ public AudioMixingPushBufferDataSource getLocalOutDataSource() { return localOutDataSource; } /** * Gets the AudioFormat 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 getOutFormatFromInDataSources() { String formatControlType = FormatControl.class.getName(); AudioFormat outFormat = null; synchronized (inDataSources) { for (InDataSourceDesc inDataSource : inDataSources) { DataSource effectiveInDataSource = inDataSource.getEffectiveInDataSource(); if (effectiveInDataSource == null) continue; FormatControl formatControl = (FormatControl) effectiveInDataSource.getControl(formatControlType); if (formatControl != null) { AudioFormat format = (AudioFormat) formatControl.getFormat(); if (format != null) { // SIGNED int signed = format.getSigned(); if ((AudioFormat.SIGNED == signed) || (Format.NOT_SPECIFIED == signed)) { // LITTLE_ENDIAN int endian = format.getEndian(); if ((AudioFormat.LITTLE_ENDIAN == endian) || (Format.NOT_SPECIFIED == endian)) { outFormat = format; break; } } } } } } if (outFormat == null) outFormat = DEFAULT_OUTPUT_FORMAT; if (logger.isTraceEnabled()) { logger.trace( "Determined outFormat of AudioMixer from inDataSources" + " to be " + outFormat); } return outFormat; } /** * 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 getOutStream() { synchronized (inDataSources) { AudioFormat outFormat = (outStream == null) ? getOutFormatFromInDataSources() : outStream.getFormat(); setOutFormatToInDataSources(outFormat); Collection inStreams; try { inStreams = getInStreamsFromInDataSources( outFormat, (outStream == null) ? null : outStream.getInStreams()); } catch (IOException ioex) { throw new UndeclaredThrowableException(ioex); } if (outStream == null) { outStream = new AudioMixerPushBufferStream(this, outFormat); startedGeneration = 0; } outStream.setInStreams(inStreams); return outStream; } } /** * Searches this object's inDataSources for one that matches * inDataSource, and returns it's associated * TranscodingDataSource. Currently this is only used when * the MediaStream needs access to the codec chain used to * playback one of it's ReceiveStreams. * * @param inDataSource the DataSource to search for. * @return The TranscodingDataSource associated with * inDataSource, if we can find one, null otherwise. */ public TranscodingDataSource getTranscodingDataSource( DataSource inDataSource) { for (InDataSourceDesc inDataSourceDesc : inDataSources) { DataSource ourDataSource = inDataSourceDesc.getInDataSource(); if (ourDataSource == inDataSource) return inDataSourceDesc.getTranscodingDataSource(); else if (ourDataSource instanceof ReceiveStreamPushBufferDataSource) { // Sometimes the inDataSource has come to AudioMixer wrapped in // a ReceiveStreamPushBufferDataSource. We consider it to match. if (((ReceiveStreamPushBufferDataSource) ourDataSource) .getDataSource() == inDataSource) return inDataSourceDesc.getTranscodingDataSource(); } } return null; } /** * Determines whether a specific Format 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 media from a specific PushBufferStream which belongs to * a specific DataSource into a specific output Buffer. * Allows extenders to tap into the reading and monitor and customize it. * * @param stream the PushBufferStream to read media from and known * to belong to the specified DataSOurce * @param buffer the output Buffer in which the media read from the * specified stream is to be written so that it gets returned to * the caller * @param dataSource the DataSource from which stream * originated * @throws IOException if anything wrong happens while reading from the * specified stream */ protected void read( PushBufferStream stream, Buffer buffer, DataSource dataSource) throws IOException { stream.read(buffer); } /** * Removes DataSources accepted by a specific * DataSourceFilter from the list of input DataSources of * this AudioMixer from which it reads audio to be mixed. * * @param dataSourceFilter the DataSourceFilter which selects the * DataSources to be removed from the list of input * DataSources of this AudioMixer from which it reads * audio to be mixed */ public void removeInDataSources(DataSourceFilter dataSourceFilter) { synchronized (inDataSources) { Iterator inDataSourceIter = inDataSources.iterator(); boolean removed = false; while (inDataSourceIter.hasNext()) { InDataSourceDesc inDsDesc = inDataSourceIter.next(); if (dataSourceFilter.accept(inDsDesc.getInDataSource())) { inDataSourceIter.remove(); removed = true; try { inDsDesc.stop(); inDsDesc.disconnect(); } catch(IOException ex) { logger.error("Failed to stop DataSource", ex); } } } if (removed && (outStream != null)) getOutStream(); } } /** * 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 outFormat the AudioFormat in which the input * DataSources of this AudioMixer are to be instructed to * output */ private void setOutFormatToInDataSources(AudioFormat outFormat) { String formatControlType = FormatControl.class.getName(); synchronized (inDataSources) { for (InDataSourceDesc inDataSourceDesc : inDataSources) { FormatControl formatControl = (FormatControl) inDataSourceDesc.getControl(formatControlType); if (formatControl != null) { Format inFormat = formatControl.getFormat(); if ((inFormat == null) || !matches(inFormat, outFormat)) { Format setFormat = formatControl.setFormat(outFormat); if (setFormat == null) logger.error( "Failed to set format of inDataSource to " + outFormat); else if (setFormat != outFormat) logger.warn( "Failed to change format of inDataSource" + " from " + setFormat + " to " + outFormat); else if (logger.isTraceEnabled()) logger.trace( "Set format of inDataSource to " + setFormat); } } } } } /** * Starts the input DataSources of this AudioMixer. * * @param outStream the AudioMixerPushBufferStream which requests * this AudioMixer to start. If outStream is the current * one and only AudioMixerPushBufferStream of this * AudioMixer, this AudioMixer starts if it hasn't started * yet. Otherwise, the request is ignored. * @param generation a value generated by outStream indicating the * order of the invocations of the start and stop methods * performed by outStream allowing it to execute the said methods * outside synchronized blocks for the purposes of reducing deadlock risks * @throws IOException if any of the input DataSources of this * AudioMixer throws such an exception while attempting to start it */ void start(AudioMixerPushBufferStream outStream, long generation) throws IOException { synchronized (inDataSources) { /* * AudioMixer has only one outStream at a time and only its current * outStream knows when it has to start (and stop). */ if (this.outStream != outStream) return; /* * The notion of generations was introduced in order to allow * outStream to invoke the start and stop methods outside * synchronized blocks. The generation value always increases in a * synchronized block. */ if (startedGeneration < generation) startedGeneration = generation; else return; if (started == 0) { for (InDataSourceDesc inDataSourceDesc : inDataSources) inDataSourceDesc.start(); } started++; } } /** * Stops the input DataSources of this AudioMixer. * * @param outStream the AudioMixerPushBufferStream which requests * this AudioMixer to stop. If outStream is the current * one and only AudioMixerPushBufferStream of this * AudioMixer, this AudioMixer stops. Otherwise, the * request is ignored. * @param generation a value generated by outStream indicating the * order of the invocations of the start and stop methods * performed by outStream allowing it to execute the said methods * outside synchronized blocks for the purposes of reducing deadlock risks * @throws IOException if any of the input DataSources of this * AudioMixer throws such an exception while attempting to stop it */ void stop(AudioMixerPushBufferStream outStream, long generation) throws IOException { synchronized (inDataSources) { /* * AudioMixer has only one outStream at a time and only its current * outStream knows when it has to stop (and start). */ if (this.outStream != outStream) return; /* * The notion of generations was introduced in order to allow * outStream to invoke the start and stop methods outside * synchronized blocks. The generation value always increases in a * synchronized block. */ if (startedGeneration < generation) startedGeneration = generation; else return; if (started <= 0) return; started--; if (started == 0) { for (InDataSourceDesc inDataSourceDesc : inDataSources) inDataSourceDesc.stop(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy