
org.jitsi.impl.neomedia.protocol.RewritablePushBufferDataSource 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.protocol;
import java.io.*;
import java.nio.*;
import java.util.*;
import javax.media.*;
import javax.media.Buffer;
import javax.media.format.*;
import javax.media.protocol.*;
import org.jitsi.impl.neomedia.control.*;
import org.jitsi.service.neomedia.*;
/**
* Implements a PushBufferDataSource wrapper which provides mute
* support for the wrapped instance.
*
* Because the class wouldn't work for our use case without it,
* CaptureDevice is implemented and is being delegated to the wrapped
* DataSource (if it supports the interface in question).
*
* @author Lyubomir Marinov
*/
public class RewritablePushBufferDataSource
extends PushBufferDataSourceDelegate
implements MuteDataSource,
InbandDTMFDataSource
{
/**
* The indicator which determines whether this DataSource is mute.
*/
private boolean mute;
/**
* The tones to send via inband DTMF, if not empty.
*/
private final LinkedList tones
= new LinkedList();
/**
* Initializes a new RewritablePushBufferDataSource instance which
* is to provide mute support for a specific PushBufferDataSource.
*
* @param dataSource the PushBufferDataSource the new instance is
* to provide mute support for
*/
public RewritablePushBufferDataSource(PushBufferDataSource dataSource)
{
super(dataSource);
}
/**
* {@inheritDoc}
*
* Overrides the super implementation to include the type hierarchy of the
* very wrapped dataSource instance into the search for the
* specified controlType.
*/
@Override
public Object getControl(String controlType)
{
if (InbandDTMFDataSource.class.getName().equals(controlType)
|| MuteDataSource.class.getName().equals(controlType))
{
return this;
}
else
{
/*
* The super implements a delegate so we can be sure that it
* delegates the invocation of Controls#getControl(String) to the
* wrapped dataSource.
*/
return AbstractControls.queryInterface(dataSource, controlType);
}
}
/**
* Implements {@link PushBufferDataSource#getStreams()}. Wraps the streams
* of the wrapped PushBufferDataSource into
* MutePushBufferStream instances in order to provide mute support
* to them.
*
* @return an array of PushBufferStream instances with enabled mute
* support
*/
@Override
public PushBufferStream[] getStreams()
{
PushBufferStream[] streams = dataSource.getStreams();
if (streams != null)
{
for (int streamIndex = 0;
streamIndex < streams.length;
streamIndex++)
{
PushBufferStream stream = streams[streamIndex];
if (stream != null)
streams[streamIndex] = new MutePushBufferStream(stream);
}
}
return streams;
}
/**
* Determines whether this DataSource is mute.
*
* @return true if this DataSource is mute; otherwise,
* false
*/
public synchronized boolean isMute()
{
return mute;
}
/**
* Replaces the media data contained in a specific Buffer with a
* compatible representation of silence.
*
* @param buffer the Buffer the data contained in which is to be
* replaced with silence
*/
public static void mute(Buffer buffer)
{
Object data = buffer.getData();
if (data != null)
{
Class> dataClass = data.getClass();
final int fromIndex = buffer.getOffset();
final int toIndex = fromIndex + buffer.getLength();
if (Format.byteArray.equals(dataClass))
Arrays.fill((byte[]) data, fromIndex, toIndex, (byte) 0);
else if (Format.intArray.equals(dataClass))
Arrays.fill((int[]) data, fromIndex, toIndex, 0);
else if (Format.shortArray.equals(dataClass))
Arrays.fill((short[]) data, fromIndex, toIndex, (short) 0);
buffer.setData(data);
}
}
/**
* Sets the mute state of this DataSource.
*
* @param mute true to mute this DataSource; otherwise,
* false
*/
public synchronized void setMute(boolean mute)
{
this.mute = mute;
}
/**
* Adds a new inband DTMF tone to send.
*
* @param tone the DTMF tone to send.
*/
public void addDTMF(DTMFInbandTone tone)
{
this.tones.add(tone);
}
/**
* Determines whether this DataSource sends a DTMF tone.
*
* @return true if this DataSource is sending a DTMF tone;
* otherwise, false.
*/
public boolean isSendingDTMF()
{
return !this.tones.isEmpty();
}
/**
* Replaces the media data contained in a specific Buffer with an
* inband DTMF tone signal.
*
* @param buffer the Buffer the data contained in which is to be
* replaced with the DTMF tone
* @param tone the DMFTTone to send via inband DTMF signal.
*/
public static void sendDTMF(
Buffer buffer,
DTMFInbandTone tone)
{
Object data = buffer.getData();
Format format;
// Send the inband DTMF tone only if the buffer contains audio data.
if ((data != null)
&& ((format = buffer.getFormat()) instanceof AudioFormat))
{
AudioFormat audioFormat = (AudioFormat) format;
int sampleSizeInBits = audioFormat.getSampleSizeInBits();
// Generates the inband DTMF signal.
short[] samples
= tone.getAudioSamples(
audioFormat.getSampleRate(),
sampleSizeInBits);
int fromIndex = buffer.getOffset();
int toIndex = fromIndex + samples.length * (sampleSizeInBits / 8);
ByteBuffer newData = ByteBuffer.allocate(toIndex);
// Prepares newData to be endian compliant with original buffer
// data.
newData.order(
(audioFormat.getEndian() == AudioFormat.BIG_ENDIAN)
? ByteOrder.BIG_ENDIAN
: ByteOrder.LITTLE_ENDIAN);
// Keeps data unchanged if stored before the original buffer offset
// index. Takes care of original data array type (byte, short or
// int).
Class> dataType = data.getClass();
if (Format.byteArray.equals(dataType))
{
newData.put((byte[]) data, 0, fromIndex);
}
else if (Format.shortArray.equals(dataType))
{
short[] shortData = (short[]) data;
for(int i = 0; i < fromIndex; ++i)
newData.putShort(shortData[i]);
}
else if (Format.intArray.equals(dataType))
{
int[] intData = (int[]) data;
for(int i = 0; i < fromIndex; ++i)
newData.putInt(intData[i]);
}
// Copies inband DTMF singal into newData. Takes care of audio
// format data type (byte, short or int).
switch (sampleSizeInBits)
{
case 8:
for(int i = 0; i < samples.length; ++i)
newData.put((byte) samples[i]);
break;
case 16:
for(int i = 0; i < samples.length; ++i)
newData.putShort(samples[i]);
break;
case 24:
case 32:
default:
throw new IllegalArgumentException(
"buffer.format.sampleSizeInBits must be either 8 or 16"
+ ", not " + sampleSizeInBits);
}
// Copies newData up to date into the original buffer.
// Takes care of original data array type (byte, short or int).
if (Format.byteArray.equals(dataType))
buffer.setData(newData.array());
else if (Format.shortArray.equals(dataType))
buffer.setData(newData.asShortBuffer().array());
else if (Format.intArray.equals(dataType))
buffer.setData(newData.asIntBuffer().array());
// Updates the buffer length.
buffer.setLength(toIndex - fromIndex);
}
}
/**
* Implements a PushBufferStream wrapper which provides mute
* support for the wrapped instance.
*/
private class MutePushBufferStream
extends SourceStreamDelegate
implements PushBufferStream
{
/**
* Initializes a new MutePushBufferStream instance which is to
* provide mute support to a specific PushBufferStream.
*
* @param stream the PushBufferStream the new instance is to
* provide mute support to
*/
public MutePushBufferStream(PushBufferStream stream)
{
super(stream);
}
/**
* Implements {@link PushBufferStream#getFormat()}. Delegates to the
* wrapped PushBufferStream.
*
* @return the Format of the wrapped PushBufferStream
*/
public Format getFormat()
{
return stream.getFormat();
}
/**
* Implements {@link PushBufferStream#read(Buffer)}. If this instance is
* muted (through its owning RewritablePushBufferDataSource),
* overwrites the data read from the wrapped PushBufferStream
* with silence data.
*
* @param buffer a Buffer in which the read data is to be
* returned to the caller
* @throws IOException if reading from the wrapped
* PushBufferStream fails
*/
public void read(Buffer buffer)
throws IOException
{
stream.read(buffer);
if (isSendingDTMF())
sendDTMF(buffer, tones.poll());
else if (isMute())
mute(buffer);
}
/**
* Implements
* {@link 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).
*
* @param transferHandler a BufferTransferHandler to be
* notified by this instance when data is available for reading from it
*/
public void setTransferHandler(BufferTransferHandler transferHandler)
{
stream.setTransferHandler(
(transferHandler == null)
? null
: new StreamSubstituteBufferTransferHandler(
transferHandler,
stream,
this));
}
}
}