Rebuilds the JNI FFmpeg library with newer versions of its dependencies.

cusax-fix
Lyubomir Marinov 15 years ago
parent 982e9984cb
commit 7653173e1f

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -35,18 +35,15 @@ When building the x64 library on Windows x86, add --host=x86_64-w64-mingw32 to
the configure line.
./configure --enable-win32thread
make
- FreeBSD
patch -Np1 -i x264-01-freebsd.patch
./configure --enable-pic
gmake
- Linux, Mac OS X
./configure --enable-pic
make
3. ffmpeg
@ -75,35 +72,11 @@ Add --target-os=mingw32, --enable-memalign-hack, --disable-pthreads and
x64:
Add --arch=amd64 and --enable-cross-compile to the configure line used for x86.
mingw32-make
- Linux, FreeBSD
- FreeBSD, Linux, Mac OS X
Add --enable-pic and --enable-pthreads.
- Mac OS X
Note: Remove from the configure script the option to add -mdynamic-no-pic unless
building for the i386 architecture. For i386, remove "--enable-pic" from the
following command.
./configure \
--enable-pic \
--enable-static --disable-shared --shlibdir=. \
--disable-encoders --disable-decoders --disable-muxers --disable-demuxers \
--disable-parsers --disable-bsfs --disable-protocols --disable-devices \
--disable-network \
--enable-libx264 --enable-gpl --enable-parser=h264 --enable-encoder=libx264 \
--enable-decoder=h264 --enable-muxer=h264 --enable-demuxer=h264 \
--enable-parser=h263 --enable-encoder=h263p \
--enable-decoder=h263 --enable-muxer=h263 --enable-demuxer=h263 \
--enable-libmp3lame --enable-encoder=libmp3lame \
--extra-ldflags="-fPIC -L$MP3LAME_HOME/libmp3lame/.libs -L$X264_HOME" --extra-cflags="-fPIC -I$MP3LAME_HOME/include -I$X264_HOME" \
--disable-debug \
--disable-ffserver --disable-ffprobe --disable-ffplay --disable-ffmpeg \
--enable-pthreads
4. libffmpeg
4. jnffmpeg
Define the environment variable JAVA_HOME so that the JNI headers can be found.
Change the current directory to src\native and run "ant ffmpeg" passing it

@ -411,16 +411,12 @@ public static void enableDesktopSharing(Call call, boolean enable)
else if (showDesktopSharingWarning())
{
MediaService mediaService = GuiActivator.getMediaService();
List<MediaDevice> desktopDevices = mediaService.getDevices(
MediaType.VIDEO, MediaUseCase.DESKTOP);
List<MediaDevice> desktopDevices
= mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP);
int deviceNumber = desktopDevices.size();
if (deviceNumber == 1)
{
enableDesktopSharing(call, null, enable);
}
else if (deviceNumber > 1)
{
SelectScreenDialog selectDialog
@ -517,7 +513,6 @@ private static void enableDesktopSharing(Call call,
OperationSetDesktopSharingServer desktopOpSet
= call.getProtocolProvider().getOperationSet(
OperationSetDesktopSharingServer.class);
boolean enableSucceeded = false;
// This shouldn't happen at this stage, because we disable the button

@ -9,11 +9,11 @@
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.List; // disambiguation
import javax.media.*;
import javax.media.control.*;
import javax.media.format.RGBFormat;
import javax.media.format.*;
import javax.media.protocol.*;
import javax.swing.*;
@ -22,7 +22,6 @@
import net.java.sip.communicator.impl.neomedia.device.*;
import net.java.sip.communicator.impl.neomedia.format.*;
import net.java.sip.communicator.impl.neomedia.protocol.*;
import net.java.sip.communicator.impl.neomedia.videoflip.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.device.*;
import net.java.sip.communicator.service.neomedia.format.*;
@ -788,20 +787,12 @@ private static void controllerUpdateForPreview(ControllerEvent event,
Codec codecs[] = null;
SwScaler scaler = new SwScaler();
if(locator.getProtocol().equals(
ImageStreamingAuto.LOCATOR_PROTOCOL))
{
// do not flip desktop
codecs = new Codec[1];
codecs[0] = scaler;
}
// do not flip desktop
if (ImageStreamingAuto.LOCATOR_PROTOCOL.equals(
locator.getProtocol()))
codecs = new Codec[] { scaler };
else
{
VideoFlipEffect flipEffect = new VideoFlipEffect();
codecs = new Codec[2];
codecs[0] = flipEffect;
codecs[1] = scaler;
}
codecs = new Codec[] { new HFlip(), scaler };
trackControl.setCodecChain(codecs);
break;
@ -1136,26 +1127,23 @@ public Point getOriginForDesktopStreamingDevice(MediaDevice mediaDevice)
CaptureDeviceInfo devInfo = dev.getCaptureDeviceInfo();
MediaLocator locator = devInfo.getLocator();
if(!locator.getProtocol().equals(ImageStreamingAuto.LOCATOR_PROTOCOL))
if(!ImageStreamingAuto.LOCATOR_PROTOCOL.equals(locator.getProtocol()))
return null;
String remainder = locator.getRemainder();
String split[] = remainder.split(",");
int index = 0;
if(split != null && split.length > 1)
{
index = Integer.parseInt(split[0]);
}
else
{
index = Integer.parseInt(remainder);
}
int index
= Integer.parseInt(
((split != null) && (split.length > 1))
? split[0]
: remainder);
ScreenDevice devs[] = ScreenDeviceImpl.getAvailableScreenDevice();
if(devs.length - 1 >= index)
if (devs.length - 1 >= index)
{
Rectangle r = ((ScreenDeviceImpl)devs[index]).getBounds();
return new Point(r.x, r.y);
}

@ -402,8 +402,9 @@ public void addDynamicRTPPayloadType(
dynamicRTPPayloadTypes.put(Byte.valueOf(rtpPayloadType), format);
if (rtpManager != null)
rtpManager
.addFormat(mediaFormatImpl.getFormat(), rtpPayloadType);
rtpManager.addFormat(
mediaFormatImpl.getFormat(),
rtpPayloadType);
}
}
@ -1068,10 +1069,14 @@ private RTPManager getRTPManager()
//sure you check that the line below still works.
rtpManager.initialize(rtpConnector);
//JMF inits the local SSRC upon initialize(RTPConnector) so now's
//the time to ask:
//as JMF keep the ssrc in int value, we cast here from
//signed int to unsigned
/*
* JMF initializes the local SSRC upon #initialize(RTPConnector) so
* now's the time to ask.
*/
/*
* As JMF keeps the SSRC as a signed int value, convert it to
* unsigned.
*/
setLocalSourceID(
((RTPSessionMgr) rtpManager).getLocalSSRC() & 0xFFFFFFFFL);
}
@ -1205,8 +1210,9 @@ protected void registerCustomCodecFormats(RTPManager rtpManager)
= (MediaFormatImpl<? extends Format>)
dynamicRTPPayloadType.getValue();
rtpManager.addFormat( mediaFormatImpl.getFormat(),
dynamicRTPPayloadType.getKey());
rtpManager.addFormat(
mediaFormatImpl.getFormat(),
dynamicRTPPayloadType.getKey());
}
}
}
@ -1430,25 +1436,27 @@ public void setDirection(MediaDirection direction)
*/
public void setFormat(MediaFormat format)
{
if(logger.isTraceEnabled())
logger.trace("Changing format of stream " + hashCode()
// getDeviceSession().getFormat() will activate
// device in order to get its format
//+ " from:" + getDeviceSession().getFormat()
+ " to:" + format);
if (logger.isTraceEnabled())
logger.trace(
"Changing format of stream " + hashCode()
+ " from: " + getDeviceSession().getFormat()
+ " to: " + format);
setAdvancedAttributes(format.getAdvancedAttributes());
handleAttributes(format.getAdvancedAttributes());
handleAttributes(format.getFormatParameters());
handleAttributes(format, format.getAdvancedAttributes());
handleAttributes(format, format.getFormatParameters());
getDeviceSession().setFormat(format);
}
/**
* Handles attributes contained in <tt>MediaFormat</tt>.
*
* @param attrs the attributes list to handle
* @param format the <tt>MediaFormat</tt> to handle the attributes of
* @param attrs the attributes <tt>Map</tt> to handle
*/
protected void handleAttributes(Map<String, String> attrs)
protected void handleAttributes(
MediaFormat format,
Map<String, String> attrs)
{
}
@ -1623,9 +1631,9 @@ private void startReceiveStreams()
= receiveStream.getDataSource();
/*
* For an unknown reason, the stream DataSource can be
* null at the end of the Call after re-INVITEs have
* been handled.
* For an unknown reason, the stream DataSource can be null
* at the end of the Call after re-INVITEs have been
* handled.
*/
if (receiveStreamDataSource != null)
receiveStreamDataSource.start();
@ -1852,17 +1860,16 @@ private void stopReceiveStreams()
= receiveStream.getDataSource();
/*
* For an unknown reason, the stream DataSource can be
* null at the end of the Call after re-INVITEs have
* been handled.
* For an unknown reason, the stream DataSource can be null
* at the end of the Call after re-INVITEs have been
* handled.
*/
if (receiveStreamDataSource != null)
receiveStreamDataSource.stop();
}
catch (IOException ioex)
{
logger.warn("Failed to stop stream " + receiveStream,
ioex);
logger.warn("Failed to stop stream " + receiveStream, ioex);
}
}
}
@ -1980,8 +1987,7 @@ public static String toString(DataSource dataSource)
/**
* Notifies this <tt>ReceiveStreamListener</tt> that the <tt>RTPManager</tt>
* it is registered with has generated an event related to a
* <tt>ReceiveStream</tt>.
* it is registered with has generated an event related to a <tt>ReceiveStream</tt>.
*
* @param event the <tt>ReceiveStreamEvent</tt> which specifies the
* <tt>ReceiveStream</tt> that is the cause of the event and the very type
@ -2067,13 +2073,9 @@ else if (event instanceof TimeoutEvent)
*/
public void update(SendStreamEvent event)
{
if(event instanceof NewSendStreamEvent)
{
if(event.getSendStream().getSSRC() != this.localSourceID)
{
setLocalSourceID(event.getSendStream().getSSRC());
}
}
if ((event instanceof NewSendStreamEvent)
&& (event.getSendStream().getSSRC() != this.localSourceID))
setLocalSourceID(event.getSendStream().getSSRC());
}
/**
@ -2129,12 +2131,15 @@ public void update(RemoteEvent remoteEvent)
if(numberOfReceivedSenderReports%4 != 1)
return;
StringBuilder buff =
new StringBuilder(StatisticsEngine.RTP_STAT_PREFIX);
StringBuilder buff
= new StringBuilder(StatisticsEngine.RTP_STAT_PREFIX);
MediaFormat format = getFormat();
buff.append("Received a report for ")
.append(getFormat() != null ?
getFormat().getMediaType().toString() : "")
.append(
(format == null)
? ""
: format.getMediaType().toString())
.append(" stream SSRC:")
.append(getLocalSourceID())
.append(" [packet count:")

@ -23,7 +23,7 @@
* Implements static utility methods used by media classes.
*
* @author Emil Ivov
* @author Lubomir Marinov
* @author Lyubomir Marinov
*/
public class MediaUtils
{
@ -70,8 +70,9 @@ public class MediaUtils
AudioFormat.ULAW_RTP,
8000);
/* some codecs depend on JMF native libraries which are only available
* on Windows 32-bit and Linux 32-bit
/*
* Some codecs depend on JMF native libraries which are only available
* on 32-bit Linux and 32-bit Windows.
*/
if(OSUtils.IS_LINUX32 || OSUtils.IS_WINDOWS32)
{
@ -174,12 +175,13 @@ public class MediaUtils
/* H264 */
Map<String, String> h264FormatParams
= new HashMap<String, String>();
String packetizationMode = "packetization-mode";
Map<String, String> h264AdvancedAttributes
= new HashMap<String, String>();
h264FormatParams.put("packetization-mode", "1");
h264FormatParams.put(packetizationMode, "1");
/*
* Disable PLI since we use periodic intra-refresh feature of
* Disable PLI since we use the periodic intra-refresh feature of
* FFmpeg/x264.
*/
//h264AdvancedAttributes.put("rtcp-fb", "nack pli");
@ -190,6 +192,7 @@ public class MediaUtils
h264AdvancedAttributes.put("imageattr", createImageAttr(null, res));
// packetization-mode=1
addMediaFormats(
MediaFormat.RTP_PAYLOAD_TYPE_UNKNOWN,
"H264",
@ -206,10 +209,7 @@ public class MediaUtils
// maximum resolution we can receive is the size of our screen device
if(res != null)
{
h263FormatParams.put("CUSTOM", res.width + "," + res.height + ",2");
}
h263FormatParams.put("VGA", "2");
h263FormatParams.put("CIF", "1");
h263FormatParams.put("QCIF", "1");
@ -293,36 +293,37 @@ private static void addMediaFormats(
= new ArrayList<MediaFormat>(clockRateCount);
if (clockRateCount > 0)
{
for (double clockRate : clockRates)
{
Format format;
switch (mediaType)
{
case AUDIO:
format = new AudioFormat(jmfEncoding);
break;
case VIDEO:
format = new VideoFormat(jmfEncoding);
break;
default:
throw new IllegalArgumentException("mediaType");
case AUDIO:
format = new AudioFormat(jmfEncoding);
break;
case VIDEO:
format
= new ParameterizedVideoFormat(
jmfEncoding,
formatParameters);
break;
default:
throw new IllegalArgumentException("mediaType");
}
if (format != null)
{
MediaFormat mediaFormat
= MediaFormatImpl
.createInstance(
format,
clockRate,
formatParameters,
advancedAttributes);
if (mediaFormat != null)
mediaFormats.add(mediaFormat);
}
MediaFormat mediaFormat
= MediaFormatImpl.createInstance(
format,
clockRate,
formatParameters,
advancedAttributes);
if (mediaFormat != null)
mediaFormats.add(mediaFormat);
}
}
else
{
Format format;
@ -330,30 +331,32 @@ private static void addMediaFormats(
switch (mediaType)
{
case AUDIO:
AudioFormat audioFormat = new AudioFormat(jmfEncoding);
case AUDIO:
AudioFormat audioFormat = new AudioFormat(jmfEncoding);
format = audioFormat;
clockRate = audioFormat.getSampleRate();
break;
case VIDEO:
format = new VideoFormat(jmfEncoding);
clockRate = VideoMediaFormatImpl.DEFAULT_CLOCK_RATE;
break;
default:
throw new IllegalArgumentException("mediaType");
format = audioFormat;
clockRate = audioFormat.getSampleRate();
break;
case VIDEO:
format
= new ParameterizedVideoFormat(
jmfEncoding,
formatParameters);
clockRate = VideoMediaFormatImpl.DEFAULT_CLOCK_RATE;
break;
default:
throw new IllegalArgumentException("mediaType");
}
if (format != null)
{
MediaFormat mediaFormat
= MediaFormatImpl
.createInstance(format, clockRate, formatParameters,
advancedAttributes);
MediaFormat mediaFormat
= MediaFormatImpl.createInstance(
format,
clockRate,
formatParameters,
advancedAttributes);
if (mediaFormat != null)
mediaFormats.add(mediaFormat);
}
if (mediaFormat != null)
mediaFormats.add(mediaFormat);
}
if (mediaFormats.size() > 0)
@ -361,13 +364,11 @@ private static void addMediaFormats(
if (MediaFormat.RTP_PAYLOAD_TYPE_UNKNOWN == rtpPayloadType)
rtpPayloadTypelessMediaFormats.addAll(mediaFormats);
else
rtpPayloadTypeStrToMediaFormats
.put(
rtpPayloadTypeStrToMediaFormats.put(
Byte.toString(rtpPayloadType),
mediaFormats.toArray(EMPTY_MEDIA_FORMATS));
jmfEncodingToEncodings
.put(
jmfEncodingToEncodings.put(
((MediaFormatImpl<? extends Format>) mediaFormats.get(0))
.getJMFEncoding(),
encoding);

@ -20,6 +20,7 @@
import net.java.sip.communicator.impl.neomedia.transform.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.device.*;
import net.java.sip.communicator.service.neomedia.format.*;
import net.java.sip.communicator.service.neomedia.event.*;
import net.java.sip.communicator.util.*;
@ -28,7 +29,7 @@
* <tt>VideoMediaStream</tt>.
*
* @author Lyubomir Marinov
* @author Sébastien Vincent
* @author Sebastien Vincent
*/
public class VideoMediaStreamImpl
extends MediaStreamImpl
@ -279,12 +280,12 @@ protected void configureDataOutputStream(
{
super.configureDataOutputStream(dataOutputStream);
int maxBandwidth =
NeomediaActivator.getMediaServiceImpl().getDeviceConfiguration()
.getVideoMaxBandwidth();
int maxBandwidth
= NeomediaActivator.getMediaServiceImpl().getDeviceConfiguration()
.getVideoMaxBandwidth();
//maximum one packet for X milliseconds(the settings are for one second)
dataOutputStream.setMaxPacketsPerMillis(1, 1000/maxBandwidth);
dataOutputStream.setMaxPacketsPerMillis(1, 1000 / maxBandwidth);
}
/**
@ -353,8 +354,8 @@ protected void deviceSessionChanged(
if ((oldValue instanceof VideoMediaDeviceSession)
&& (deviceSessionVideoListener != null))
{
((VideoMediaDeviceSession) oldValue)
.removeVideoListener(deviceSessionVideoListener);
((VideoMediaDeviceSession) oldValue).removeVideoListener(
deviceSessionVideoListener);
}
if (newValue instanceof VideoMediaDeviceSession)
{
@ -551,9 +552,13 @@ protected void rtpConnectorChanged(
/**
* Handles attributes contained in <tt>MediaFormat</tt>.
*
* @param attrs the attributes list to handle
* @param format the <tt>MediaFormat</tt> to handle the attributes of
* @param attrs the attributes <tt>Map</tt> to handle
*/
protected void handleAttributes(Map<String, String> attrs)
@Override
protected void handleAttributes(
MediaFormat format,
Map<String, String> attrs)
{
/* walk through attributes and see if we recognized something
* we support
@ -565,9 +570,10 @@ protected void handleAttributes(Map<String, String> attrs)
String key = mapEntry.getKey();
String value = mapEntry.getValue();
if(key.equals("rtcp-fb") && value.equals("nack pli"))
if(key.equals("rtcp-fb"))
{
usePLI = true;
if (value.equals("nack pli"))
usePLI = true;
}
else if(key.equals("imageattr"))
{
@ -624,9 +630,7 @@ else if(key.equals("CUSTOM"))
String args[] = value.split(",");
if(args.length < 3)
{
continue;
}
try
{
@ -656,7 +660,7 @@ else if(key.equals("CUSTOM"))
* @param imgattr send/recv resolution string
* @return maximum resolution array (first element is send, second one is
* recv). Elements could be null if image attribute is not present or if
* resoluion is a wildcard.
* resolution is a wildcard.
*/
public static java.awt.Dimension[] parseSendRecvResolution(String imgattr)
{

@ -1,259 +1,259 @@
/*
* 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.neomedia.codec;
import javax.media.*;
import net.sf.fmj.media.*;
/**
* Extends FMJ's <tt>AbstractCodec</tt> to make it even easier to implement a
* <tt>Codec</tt>.
*
* @author Lyubomir Marinov
*/
public abstract class AbstractCodecExt
extends AbstractCodec
{
private final Class<? extends Format> formatClass;
/**
* The name of this <tt>PlugIn</tt>.
*/
private final String name;
private final Format[] supportedOutputFormats;
/**
* Initializes a new <tt>AbstractCodecExt</tt> instance with a specific
* <tt>PlugIn</tt> name, a specific <tt>Class</tt> of input and output
* <tt>Format</tt>s and a specific list of <tt>Format</tt>s supported as
* output.
*
* @param name the <tt>PlugIn</tt> name of the new instance
* @param formatClass the <tt>Class</tt> of input and output
* <tt>Format</tt>s supported by the new instance
* @param supportedOutputFormats the list of <tt>Format</tt>s supported by
* the new instance as output
*/
protected AbstractCodecExt(
String name,
Class<? extends Format> formatClass,
Format[] supportedOutputFormats)
{
this.formatClass = formatClass;
this.name = name;
this.supportedOutputFormats = supportedOutputFormats;
/*
* An Effect is a Codec that does not modify the Format of the data, it
* modifies the contents.
*/
if (this instanceof Effect)
inputFormats = this.supportedOutputFormats;
}
@Override
public void close()
{
if (!opened)
return;
doClose();
opened = false;
super.close();
}
protected void discardOutputBuffer(Buffer outputBuffer)
{
outputBuffer.setDiscard(true);
}
protected abstract void doClose();
/**
* Opens this <tt>Codec</tt> and acquires the resources that it needs to
* operate. A call to {@link PlugIn#open()} on this instance will result in
* a call to <tt>doOpen</tt> only if {@link AbstractCodec#opened} is
* <tt>false</tt>. All required input and/or output formats are assumed to
* have been set on this <tt>Codec</tt> before <tt>doOpen</tt> is called.
*
* @throws ResourceUnavailableException if any of the resources that this
* <tt>Codec</tt> needs to operate cannot be acquired
*/
protected abstract void doOpen()
throws ResourceUnavailableException;
protected abstract int doProcess(Buffer inputBuffer, Buffer outputBuffer);
protected Format[] getMatchingOutputFormats(Format inputFormat)
{
/*
* An Effect is a Codec that does not modify the Format of the data, it
* modifies the contents.
*/
if (this instanceof Effect)
return new Format[] { inputFormat };
if (supportedOutputFormats != null)
return supportedOutputFormats.clone();
return new Format[0];
}
@Override
public String getName()
{
return (name == null) ? super.getName() : name;
}
/**
* Implements {AbstractCodec#getSupportedOutputFormats(Format)}.
*
* @param inputFormat
* @return
* @see AbstractCodec#getSupportedOutputFormats(Format)
*/
public Format[] getSupportedOutputFormats(Format inputFormat)
{
if (inputFormat == null)
return supportedOutputFormats;
if (!formatClass.isInstance(inputFormat)
|| (matches(inputFormat, inputFormats) == null))
return new Format[0];
return getMatchingOutputFormats(inputFormat);
}
/**
* Utility to perform format matching.
*/
public static Format matches(Format in, Format outs[])
{
for (Format out : outs)
if (in.matches(out))
return out;
return null;
}
/**
* Opens this <tt>PlugIn</tt> software or hardware component and acquires
* the resources that it needs to operate. All required input and/or output
* formats have to be set on this <tt>PlugIn</tt> before <tt>open</tt> is
* called. Buffers should not be passed into this <tt>PlugIn</tt> without
* first calling <tt>open</tt>.
*
* @throws ResourceUnavailableException if any of the resources that this
* <tt>PlugIn</tt> needs to operate cannot be acquired
* @see AbstractPlugIn#open()
*/
@Override
public void open()
throws ResourceUnavailableException
{
if (opened)
return;
doOpen();
opened = true;
super.open();
}
/**
* Implements AbstractCodec#process(Buffer, Buffer).
*
* @param inputBuffer
* @param outputBuffer
* @return
* @see AbstractCodec#process(Buffer, Buffer)
*/
public int process(Buffer inputBuffer, Buffer outputBuffer)
{
if (!checkInputBuffer(inputBuffer))
return BUFFER_PROCESSED_FAILED;
if (isEOM(inputBuffer))
{
propagateEOM(outputBuffer);
return BUFFER_PROCESSED_OK;
}
if (inputBuffer.isDiscard())
{
discardOutputBuffer(outputBuffer);
return BUFFER_PROCESSED_OK;
}
return doProcess(inputBuffer, outputBuffer);
}
@Override
public Format setInputFormat(Format format)
{
if (!formatClass.isInstance(format)
|| (matches(format, inputFormats) == null))
return null;
return super.setInputFormat(format);
}
@Override
public Format setOutputFormat(Format format)
{
if (!formatClass.isInstance(format)
|| (matches(format, getMatchingOutputFormats(inputFormat))
== null))
return null;
return super.setOutputFormat(format);
}
/**
* Updates the <tt>format</tt>, <tt>length</tt> and <tt>offset</tt> of a
* specific output <tt>Buffer</tt> to specific values.
*
* @param outputBuffer the output <tt>Buffer</tt> to update the properties
* of
* @param format the <tt>Format</tt> to set on <tt>outputBuffer</tt>
* @param length the length to set on <tt>outputBuffer</tt>
* @param offset the offset to set on <tt>outputBuffer</tt>
*/
protected void updateOutput(
Buffer outputBuffer,
Format format, int length, int offset)
{
outputBuffer.setFormat(format);
outputBuffer.setLength(length);
outputBuffer.setOffset(offset);
}
protected byte[] validateByteArraySize(Buffer buffer, int newSize)
{
Object data = buffer.getData();
byte[] newBytes;
if (data instanceof byte[])
{
byte[] bytes = (byte[]) data;
if (bytes.length >= newSize)
return bytes;
newBytes = new byte[newSize];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
}
else
{
newBytes = new byte[newSize];
buffer.setLength(0);
buffer.setOffset(0);
}
buffer.setData(newBytes);
return newBytes;
}
}
/*
* 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.neomedia.codec;
import javax.media.*;
import net.sf.fmj.media.*;
/**
* Extends FMJ's <tt>AbstractCodec</tt> to make it even easier to implement a
* <tt>Codec</tt>.
*
* @author Lyubomir Marinov
*/
public abstract class AbstractCodecExt
extends AbstractCodec
{
private final Class<? extends Format> formatClass;
/**
* The name of this <tt>PlugIn</tt>.
*/
private final String name;
private final Format[] supportedOutputFormats;
/**
* Initializes a new <tt>AbstractCodecExt</tt> instance with a specific
* <tt>PlugIn</tt> name, a specific <tt>Class</tt> of input and output
* <tt>Format</tt>s and a specific list of <tt>Format</tt>s supported as
* output.
*
* @param name the <tt>PlugIn</tt> name of the new instance
* @param formatClass the <tt>Class</tt> of input and output
* <tt>Format</tt>s supported by the new instance
* @param supportedOutputFormats the list of <tt>Format</tt>s supported by
* the new instance as output
*/
protected AbstractCodecExt(
String name,
Class<? extends Format> formatClass,
Format[] supportedOutputFormats)
{
this.formatClass = formatClass;
this.name = name;
this.supportedOutputFormats = supportedOutputFormats;
/*
* An Effect is a Codec that does not modify the Format of the data, it
* modifies the contents.
*/
if (this instanceof Effect)
inputFormats = this.supportedOutputFormats;
}
@Override
public void close()
{
if (!opened)
return;
doClose();
opened = false;
super.close();
}
protected void discardOutputBuffer(Buffer outputBuffer)
{
outputBuffer.setDiscard(true);
}
protected abstract void doClose();
/**
* Opens this <tt>Codec</tt> and acquires the resources that it needs to
* operate. A call to {@link PlugIn#open()} on this instance will result in
* a call to <tt>doOpen</tt> only if {@link AbstractCodec#opened} is
* <tt>false</tt>. All required input and/or output formats are assumed to
* have been set on this <tt>Codec</tt> before <tt>doOpen</tt> is called.
*
* @throws ResourceUnavailableException if any of the resources that this
* <tt>Codec</tt> needs to operate cannot be acquired
*/
protected abstract void doOpen()
throws ResourceUnavailableException;
protected abstract int doProcess(Buffer inputBuffer, Buffer outputBuffer);
protected Format[] getMatchingOutputFormats(Format inputFormat)
{
/*
* An Effect is a Codec that does not modify the Format of the data, it
* modifies the contents.
*/
if (this instanceof Effect)
return new Format[] { inputFormat };
if (supportedOutputFormats != null)
return supportedOutputFormats.clone();
return new Format[0];
}
@Override
public String getName()
{
return (name == null) ? super.getName() : name;
}
/**
* Implements {@link AbstractCodec#getSupportedOutputFormats(Format)}.
*
* @param inputFormat
* @return
* @see AbstractCodec#getSupportedOutputFormats(Format)
*/
public Format[] getSupportedOutputFormats(Format inputFormat)
{
if (inputFormat == null)
return supportedOutputFormats;
if (!formatClass.isInstance(inputFormat)
|| (matches(inputFormat, inputFormats) == null))
return new Format[0];
return getMatchingOutputFormats(inputFormat);
}
/**
* Utility to perform format matching.
*/
public static Format matches(Format in, Format outs[])
{
for (Format out : outs)
if (in.matches(out))
return out;
return null;
}
/**
* Opens this <tt>PlugIn</tt> software or hardware component and acquires
* the resources that it needs to operate. All required input and/or output
* formats have to be set on this <tt>PlugIn</tt> before <tt>open</tt> is
* called. Buffers should not be passed into this <tt>PlugIn</tt> without
* first calling <tt>open</tt>.
*
* @throws ResourceUnavailableException if any of the resources that this
* <tt>PlugIn</tt> needs to operate cannot be acquired
* @see AbstractPlugIn#open()
*/
@Override
public void open()
throws ResourceUnavailableException
{
if (opened)
return;
doOpen();
opened = true;
super.open();
}
/**
* Implements AbstractCodec#process(Buffer, Buffer).
*
* @param inputBuffer
* @param outputBuffer
* @return
* @see AbstractCodec#process(Buffer, Buffer)
*/
public int process(Buffer inputBuffer, Buffer outputBuffer)
{
if (!checkInputBuffer(inputBuffer))
return BUFFER_PROCESSED_FAILED;
if (isEOM(inputBuffer))
{
propagateEOM(outputBuffer);
return BUFFER_PROCESSED_OK;
}
if (inputBuffer.isDiscard())
{
discardOutputBuffer(outputBuffer);
return BUFFER_PROCESSED_OK;
}
return doProcess(inputBuffer, outputBuffer);
}
@Override
public Format setInputFormat(Format format)
{
if (!formatClass.isInstance(format)
|| (matches(format, inputFormats) == null))
return null;
return super.setInputFormat(format);
}
@Override
public Format setOutputFormat(Format format)
{
if (!formatClass.isInstance(format)
|| (matches(format, getMatchingOutputFormats(inputFormat))
== null))
return null;
return super.setOutputFormat(format);
}
/**
* Updates the <tt>format</tt>, <tt>length</tt> and <tt>offset</tt> of a
* specific output <tt>Buffer</tt> to specific values.
*
* @param outputBuffer the output <tt>Buffer</tt> to update the properties
* of
* @param format the <tt>Format</tt> to set on <tt>outputBuffer</tt>
* @param length the length to set on <tt>outputBuffer</tt>
* @param offset the offset to set on <tt>outputBuffer</tt>
*/
protected void updateOutput(
Buffer outputBuffer,
Format format, int length, int offset)
{
outputBuffer.setFormat(format);
outputBuffer.setLength(length);
outputBuffer.setOffset(offset);
}
protected byte[] validateByteArraySize(Buffer buffer, int newSize)
{
Object data = buffer.getData();
byte[] newBytes;
if (data instanceof byte[])
{
byte[] bytes = (byte[]) data;
if (bytes.length >= newSize)
return bytes;
newBytes = new byte[newSize];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
}
else
{
newBytes = new byte[newSize];
buffer.setLength(0);
buffer.setOffset(0);
}
buffer.setData(newBytes);
return newBytes;
}
}

@ -135,6 +135,7 @@ protected void copy(Format f)
AVFrameFormat avFrameFormat = (AVFrameFormat) f;
pixFmt = avFrameFormat.pixFmt;
devicePixFmt = avFrameFormat.devicePixFmt;
}
}
@ -181,6 +182,12 @@ public int getDevicePixFmt()
return devicePixFmt;
}
@Override
public int hashCode()
{
return super.hashCode() + pixFmt;
}
/**
* Finds the attributes shared by two matching <tt>Format</tt>s. If the
* specified <tt>Format</tt> does not match this one, the result is
@ -195,15 +202,17 @@ public Format intersects(Format format)
{
Format intersection = super.intersects(format);
if (intersection == null)
return null;
AVFrameFormat avFrameFormatIntersection = (AVFrameFormat) intersection;
if (intersection != null)
{
AVFrameFormat avFrameFormatIntersection
= (AVFrameFormat) intersection;
avFrameFormatIntersection.pixFmt
= ((pixFmt == NOT_SPECIFIED) && (format instanceof AVFrameFormat))
? ((AVFrameFormat) format).pixFmt
: pixFmt;
avFrameFormatIntersection.pixFmt
= ((pixFmt == NOT_SPECIFIED)
&& (format instanceof AVFrameFormat))
? ((AVFrameFormat) format).pixFmt
: pixFmt;
}
return intersection;
}
@ -219,16 +228,30 @@ public Format intersects(Format format)
@Override
public boolean matches(Format format)
{
if (!super.matches(format))
return false;
if (!(format instanceof AVFrameFormat))
return true;
boolean matches;
AVFrameFormat avFrameFormat = (AVFrameFormat) format;
if (super.matches(format))
{
if (format instanceof AVFrameFormat)
{
AVFrameFormat avFrameFormat = (AVFrameFormat) format;
return
(pixFmt == NOT_SPECIFIED
|| (avFrameFormat.pixFmt == NOT_SPECIFIED)
|| (pixFmt == avFrameFormat.pixFmt));
matches
= ((pixFmt == NOT_SPECIFIED)
|| (avFrameFormat.pixFmt == NOT_SPECIFIED)
|| (pixFmt == avFrameFormat.pixFmt));
}
else
matches = true;
}
else
matches = false;
return matches;
}
@Override
public String toString()
{
return super.toString() + ", pixFmt= " + pixFmt;
}
}

@ -0,0 +1,347 @@
/*
* 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.neomedia.codec.video;
import java.awt.*;
import javax.media.*;
import net.java.sip.communicator.impl.neomedia.codec.*;
import net.java.sip.communicator.util.*;
/**
* Implements a video <tt>Effect</tt> which horizontally flips
* <tt>AVFrame</tt>s.
*
* @author Sebastien Vincent
* @author Lyubomir Marinov
*/
public class HFlip
extends AbstractCodecExt
implements Effect
{
/**
* The <tt>Logger</tt> used by the <tt>HFlip</tt> class and its instances
* for logging output.
*/
private static final Logger logger = Logger.getLogger(HFlip.class);
/**
* The list of <tt>Format</tt>s supported by <tt>HFlip</tt> instances as
* input and output.
*/
private static final Format[] SUPPORTED_FORMATS
= new Format[] { new AVFrameFormat() };
/**
* The name of the FFmpeg ffsink video source <tt>AVFilter</tt> used by
* <tt>HFlip</tt>.
*/
private static final String VSINK_FFSINK_NAME = "nullsink";
/**
* The name of the FFmpeg buffer video source <tt>AVFilter</tt> used by
* <tt>HFlip</tt>.
*/
private static final String VSRC_BUFFER_NAME = "buffer";
/**
* The pointer to the <tt>AVFilterContext</tt> in {@link #graph} of the
* FFmpeg video source with the name {@link #VSRC_BUFFER_NAME}.
*/
private long buffer;
/**
* The pointer to the <tt>AVFilterContext</tt> in {@link #graph} of the
* FFmpeg video sink with the name {@link #VSINK_FFSINK_NAME}.
*/
private long ffsink;
/**
* The pointer to the <tt>AVFilterGraph</tt> instance which contains the
* FFmpeg hflip filter represented by this <tt>Effect</tt>.
*/
private long graph;
/**
* The height of {@link #graph}.
*/
private int height;
/**
* The pointer to the <tt>AVFilterBufferRef</tt> instance represented as an
* <tt>AVFrame</tt> by {@link #outputFrame}.
*/
private long outputFilterBufferRef;
/**
* The pointer to the <tt>AVFrame</tt> instance which is the output (data)
* of this <tt>Effect</tt>.
*/
private long outputFrame;
/**
* The FFmpeg pixel format of {@link #graph}.
*/
private int pixFmt = FFmpeg.PIX_FMT_NONE;
/**
* The width of {@link #graph}.
*/
private int width;
/**
* Initializes a new <tt>HFlip</tt> instance.
*/
public HFlip()
{
super("FFmpeg HFlip Filter", AVFrameFormat.class, SUPPORTED_FORMATS);
}
/**
* Closes this <tt>Effect</tt>.
*
* @see AbstractCodecExt#doClose()
*/
protected synchronized void doClose()
{
try
{
if (outputFrame != 0)
{
FFmpeg.av_free(outputFrame);
outputFrame = 0;
}
}
finally
{
reset();
}
}
/**
* Opens this <tt>Effect</tt>.
*
* @throws ResourceUnavailableException if any of the required resource
* cannot be allocated
* @see AbstractCodecExt#doOpen()
*/
protected synchronized void doOpen()
throws ResourceUnavailableException
{
outputFrame = FFmpeg.avcodec_alloc_frame();
if (outputFrame == 0)
{
String reason = "avcodec_alloc_frame: " + outputFrame;
logger.error(reason);
throw new ResourceUnavailableException(reason);
}
}
/**
* Performs the media processing defined by this <tt>Effect</tt>.
*
* @param inputBuffer the <tt>Buffer</tt> that contains the media data to be
* processed
* @param outputBuffer the <tt>Buffer</tt> in which to store the processed
* media data
* @return <tt>BUFFER_PROCESSED_OK</tt> if the processing is successful
* @see AbstractCodecExt#doProcess(Buffer, Buffer)
*/
protected synchronized int doProcess(
Buffer inputBuffer,
Buffer outputBuffer)
{
/*
* A new frame is about to be output so the old frame is no longer
* necessary.
*/
if (outputFilterBufferRef != 0)
{
FFmpeg.avfilter_unref_buffer(outputFilterBufferRef);
outputFilterBufferRef = 0;
}
/*
* Make sure the graph is configured with the current Format i.e. size
* and pixFmt.
*/
AVFrameFormat format = (AVFrameFormat) inputBuffer.getFormat();
Dimension size = format.getSize();
int pixFmt = format.getPixFmt();
if ((this.width != size.width)
|| (this.height != size.height)
|| (this.pixFmt != pixFmt))
reset();
if (graph == 0)
{
String errorReason = null;
int error = 0;
long buffer = 0;
long ffsink = 0;
graph = FFmpeg.avfilter_graph_alloc();
if (graph == 0)
errorReason = "avfilter_graph_alloc";
else
{
String filters
= VSRC_BUFFER_NAME + "=" + size.width + ":" + size.height
+ ":" + pixFmt + ":1:1000000:1:1,hflip,"
+ VSINK_FFSINK_NAME;
long log_ctx = 0;
error
= FFmpeg.avfilter_graph_parse(
graph,
filters,
0, 0,
log_ctx);
if (error == 0)
{
/*
* Unfortunately, the name of an AVFilterContext created by
* avfilter_graph_parse is not the name of the AVFilter.
*/
String parsedFilterNameFormat = "Parsed filter %1$d %2$s";
buffer
= FFmpeg.avfilter_graph_get_filter(
graph,
String.format(
parsedFilterNameFormat,
0,
VSRC_BUFFER_NAME));
if (buffer == 0)
{
errorReason
= "avfilter_graph_get_filter: " + VSRC_BUFFER_NAME;
}
else
{
ffsink
= FFmpeg.avfilter_graph_get_filter(
graph,
String.format(
parsedFilterNameFormat,
2,
VSINK_FFSINK_NAME));
if (ffsink == 0)
{
errorReason
= "avfilter_graph_get_filter: "
+ VSINK_FFSINK_NAME;
}
else
{
error
= FFmpeg.avfilter_graph_config(graph, log_ctx);
if (error != 0)
errorReason = "avfilter_graph_config";
}
}
}
else
errorReason = "avfilter_graph_parse";
if ((errorReason != null) || (error != 0))
{
FFmpeg.avfilter_graph_free(graph);
graph = 0;
}
}
if (graph == 0)
{
if (errorReason != null)
{
if (error == 0)
logger.error(errorReason);
else
logger.error(errorReason + ": " + error);
}
return BUFFER_PROCESSED_FAILED;
}
else
{
this.width = size.width;
this.height = size.height;
this.pixFmt = pixFmt;
this.buffer = buffer;
this.ffsink = ffsink;
}
}
/*
* The graph is configured for the current Format, apply its filters to
* the inputFrame.
*/
long inputFrame = ((AVFrame) inputBuffer.getData()).getPtr();
outputFilterBufferRef
= FFmpeg.get_filtered_video_frame(
inputFrame, this.width, this.height, this.pixFmt,
buffer,
ffsink,
outputFrame);
if(outputFilterBufferRef == 0)
{
/*
* If get_filtered_video_frame fails, it is likely to fail for any
* frame. Consequently, printing that it has failed will result in a
* lot of repeating logging output. Since the failure in question
* will be visible in the UI anyway, just debug it.
*/
if (logger.isDebugEnabled())
logger.debug("get_filtered_video_frame");
return BUFFER_PROCESSED_FAILED;
}
Object out = outputBuffer.getData();
if (!(out instanceof AVFrame)
|| (((AVFrame) out).getPtr() != outputFrame))
{
outputBuffer.setData(new AVFrame(outputFrame));
}
outputBuffer.setDiscard(inputBuffer.isDiscard());
outputBuffer.setDuration(inputBuffer.getDuration());
outputBuffer.setEOM(inputBuffer.isEOM());
outputBuffer.setFlags(inputBuffer.getFlags());
outputBuffer.setFormat(format);
outputBuffer.setHeader(inputBuffer.getHeader());
outputBuffer.setLength(inputBuffer.getLength());
outputBuffer.setSequenceNumber(inputBuffer.getSequenceNumber());
outputBuffer.setTimeStamp(inputBuffer.getTimeStamp());
return BUFFER_PROCESSED_OK;
}
/**
* Resets the state of this <tt>PlugIn</tt>.
*/
@Override
public synchronized void reset()
{
if (outputFilterBufferRef != 0)
{
FFmpeg.avfilter_unref_buffer(outputFilterBufferRef);
outputFilterBufferRef = 0;
}
if (graph != 0)
{
FFmpeg.avfilter_graph_free(graph);
graph = 0;
width = 0;
height = 0;
pixFmt = FFmpeg.PIX_FMT_NONE;
buffer = 0;
ffsink = 0;
}
}
}

@ -20,7 +20,7 @@
* Implements <tt>Codec</tt> to represent a depacketizer of H.264 RTP packets
* into NAL units.
*
* @author Lubomir Marinov
* @author Lyubomir Marinov
* @author Damian Minkov
*/
public class DePacketizer
@ -83,7 +83,7 @@ public class DePacketizer
private long lastSequenceNumber = -1;
/**
* The timestamp of the last received RTP packet.
* The time stamp of the last received RTP packet.
*/
private long lastTimeStamp = -1;
@ -117,31 +117,31 @@ public class DePacketizer
private boolean usePLI = false;
/**
* Last timestamp of keyframe request.
*
* This is used to retransmit keyframe request in case previous get lost
* and codec doesn't receive keyframe.
* The time stamp of the last keyframe request. Used to retransmit a
* keyframe request in case a previous one gets lost and this
* <tt>DePacketizer</tt> does not receive a keyframe.
*/
private long lastPLIRequestTime = -1;
/**
* Last timestamp of received keyframe.
* The time stamp of the last received keyframe.
*/
private long lastReceivedKeyframeTime = -1;
/**
* If <tt>Codec</tt> has detected frames lost.
* The indicator which determines whether this <tt>DePacketizer</tt> has
* detected lost frames.
*/
private boolean missFrame = false;
private boolean lostFrame = false;
/**
* State of PLISendThread.
* The indicator which determines whether {@link #pliSendThread} is running.
*/
private boolean isPLIThreadRunning = false;
private boolean pliSendThreadIsRunning = false;
/**
* Thread that will monitor time between PLI request, keyframes received
* and will send or not a PLI.
* The <tt>Thread</tt> which monitors the time between PLI request,
* keyframes received and sends a PLI.
*/
private PLISendThread pliSendThread = new PLISendThread();
@ -156,8 +156,18 @@ public DePacketizer()
VideoFormat.class,
new VideoFormat[] { new VideoFormat(Constants.H264) });
inputFormats
= new VideoFormat[] { new VideoFormat(Constants.H264_RTP) };
List<Format> inputFormats = new ArrayList<Format>();
inputFormats.add(new VideoFormat(Constants.H264_RTP));
/*
* Apart from the generic Constants.H264_RTP VideoFormat, add the
* possible respective ParameterizedVideoFormats because
* ParameterizedVideoFormat will not match every VideoFormat due to the
* fact that a missing packetization-mode format parameter is
* interpreted as it having a value of 0.
*/
Collections.addAll(inputFormats, Packetizer.SUPPORTED_OUTPUT_FORMATS);
this.inputFormats = inputFormats.toArray(new Format[inputFormats.size()]);
}
/**
@ -201,7 +211,7 @@ private int dePacketizeFUA(
if(keyframe)
{
/* keyframe received */
missFrame = false;
lostFrame = false;
lastReceivedKeyframeTime = System.currentTimeMillis();
}
@ -308,7 +318,7 @@ private int dePacketizeSingleNALUnitPacket(
if(nal_unit_type == 5)
{
/* keyframe received */
missFrame = false;
lostFrame = false;
lastReceivedKeyframeTime = System.currentTimeMillis();
}
@ -321,7 +331,7 @@ private int dePacketizeSingleNALUnitPacket(
protected void doClose()
{
/* will end PLISendThread loop */
isPLIThreadRunning = false;
pliSendThreadIsRunning = false;
pliSendThread = null;
}
@ -343,6 +353,7 @@ protected void doOpen()
lastSequenceNumber = -1;
lastTimeStamp = -1;
}
/**
* Processes (depacketizes) a buffer.
*
@ -382,12 +393,12 @@ protected int doProcess(Buffer inBuffer, Buffer outBuffer)
/* detection of missed frames */
if(usePLI)
{
missFrame = true;
lostFrame = true;
/* if the PLI thread is not started, start it! */
if(!isPLIThreadRunning)
if(!pliSendThreadIsRunning)
{
isPLIThreadRunning = true;
pliSendThreadIsRunning = true;
pliSendThread.start();
}
}
@ -574,35 +585,39 @@ public void setRtcpFeedbackPLI(boolean use)
}
/**
* Thread that will handle Picture Loss Indication (PLI) transmission.
* Note that PLI will be sent with a delay if recently we received a
* Represents a <tt>Thread<tt> which is to handle Picture Loss Indication
* (PLI) transmission.
* <p>
* <b>Note</b>: PLI will be sent with a delay if we have recently received a
* keyframe or sent a PLI.
* </p>
*
* @author Sebastien Vincent
*/
private class PLISendThread extends Thread
private class PLISendThread
extends Thread
{
/**
* Represents the entry point of <tt>PLISendThread</tt>.
* Implements the entry point of this <tt>PLISendThread</tt>.
*/
@Override
public void run()
{
while(isPLIThreadRunning)
while(pliSendThreadIsRunning)
{
long time = System.currentTimeMillis();
if(missFrame)
if(lostFrame)
{
/* we keep some delay with the latest keyframe */
if(time > (lastReceivedKeyframeTime +
PLI_KEYFRAME_INTERVAL))
if(time >
(lastReceivedKeyframeTime + PLI_KEYFRAME_INTERVAL))
{
/* we keep delay between latest PLI to not spam sender
* with a lot of PLI request
*/
if(lastPLIRequestTime == -1 ||
time > lastPLIRequestTime + PLI_INTERVAL)
if((lastPLIRequestTime == -1) ||
(time > lastPLIRequestTime + PLI_INTERVAL))
{
lastPLIRequestTime = time;
sendRTCPFeedbackPLI();
@ -614,7 +629,7 @@ public void run()
{
Thread.sleep(100);
}
catch(Exception e)
catch (InterruptedException ie)
{
}
}

@ -1,349 +1,357 @@
/*
* 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.neomedia.codec.video.h264;
import java.awt.*;
import javax.media.*;
import javax.media.format.*;
import net.java.sip.communicator.impl.neomedia.codec.*;
import net.java.sip.communicator.impl.neomedia.codec.video.*;
import net.sf.fmj.media.*;
/**
* Decodes H.264 NAL units and returns the resulting frames as FFmpeg
* <tt>AVFrame</tt>s (i.e. in YUV format).
*
* @author Damian Minkov
* @author Lubomir Marinov
* @author Sebastien Vincent
*/
public class JNIDecoder
extends AbstractCodec
{
/**
* Plugin name.
*/
private static final String PLUGIN_NAME = "H.264 Decoder";
/**
* The default output <tt>VideoFormat</tt>.
*/
private static final VideoFormat[] DEFAULT_OUTPUT_FORMATS
= new VideoFormat[] { new AVFrameFormat() };
/**
* The codec context native pointer we will use.
*/
private long avcontext;
/**
* The decoded data is stored in avpicture in native ffmpeg format (YUV).
*/
private long avframe;
/**
* If decoder has got a picture.
*/
private final boolean[] got_picture = new boolean[1];
/**
* The last known height of {@link #avcontext} i.e. the video output by this
* <tt>JNIDecoder</tt>. Used to detect changes in the output size.
*/
private int height;
/**
* Array of output <tt>VideoFormat</tt>s.
*/
private final VideoFormat[] outputFormats;
/**
* The last known width of {@link #avcontext} i.e. the video output by this
* <tt>JNIDecoder</tt>. Used to detect changes in the output size.
*/
private int width;
/**
* Initializes a new <tt>JNIDecoder</tt> instance which is to decode H.264
* NAL units into frames in YUV format.
*/
public JNIDecoder()
{
inputFormats
= new VideoFormat[] { new VideoFormat(Constants.H264) };
outputFormats
= new VideoFormat[]
{
new AVFrameFormat(
new Dimension(
Constants.VIDEO_WIDTH,
Constants.VIDEO_HEIGHT),
ensureFrameRate(Format.NOT_SPECIFIED),
FFmpeg.PIX_FMT_YUV420P,
Format.NOT_SPECIFIED)
};
Dimension outputSize = outputFormats[0].getSize();
width = outputSize.width;
height = outputSize.height;
}
/**
* Check <tt>Format</tt>.
*
* @param format <tt>Format</tt> to check
* @return true if <tt>Format</tt> is H264_RTP
*/
public boolean checkFormat(Format format)
{
return format.getEncoding().equals(Constants.H264_RTP);
}
/**
* Close <tt>Codec</tt>.
*/
@Override
public synchronized void close()
{
if (opened)
{
opened = false;
super.close();
FFmpeg.avcodec_close(avcontext);
FFmpeg.av_free(avcontext);
avcontext = 0;
FFmpeg.av_free(avframe);
avframe = 0;
}
}
/**
* Ensure frame rate.
*
* @param frameRate frame rate
* @return frame rate
*/
private float ensureFrameRate(float frameRate)
{
return frameRate;
}
/**
* Get matching outputs for a specified input <tt>Format</tt>.
*
* @param in input <tt>Format</tt>
* @return array of matching outputs or null if there are no matching
* outputs.
*/
protected Format[] getMatchingOutputFormats(Format in)
{
VideoFormat ivf = (VideoFormat) in;
Dimension inSize = ivf.getSize();
Dimension outSize;
// return the default size/currently decoder and encoder
// set to transmit/receive at this size
if (inSize == null)
{
VideoFormat ovf = outputFormats[0];
if (ovf == null)
return null;
else
outSize = ovf.getSize();
}
else
outSize = inSize; // Output in same size as input.
return
new Format[]
{
new AVFrameFormat(
outSize,
ensureFrameRate(ivf.getFrameRate()),
FFmpeg.PIX_FMT_YUV420P,
Format.NOT_SPECIFIED)
};
}
/**
* Get plugin name.
*
* @return "H.264 Decoder"
*/
@Override
public String getName()
{
return PLUGIN_NAME;
}
/**
* Get all supported output <tt>Format</tt>s.
*
* @param in input <tt>Format</tt> to determine corresponding output
* <tt>Format/tt>s
* @return array of supported <tt>Format</tt>
*/
public Format[] getSupportedOutputFormats(Format in)
{
if (in == null)
return DEFAULT_OUTPUT_FORMATS;
// mismatch input format
if (!(in instanceof VideoFormat)
|| (AbstractCodecExt.matches(in, inputFormats) == null))
return new Format[0];
// match input format
return getMatchingOutputFormats(in);
}
/**
* Inits the codec instances.
*
* @throws ResourceUnavailableException if codec initialization failed
*/
@Override
public synchronized void open()
throws ResourceUnavailableException
{
if (opened)
return;
long avcodec = FFmpeg.avcodec_find_decoder(FFmpeg.CODEC_ID_H264);
avcontext = FFmpeg.avcodec_alloc_context();
FFmpeg.avcodeccontext_set_workaround_bugs(avcontext,
FFmpeg.FF_BUG_AUTODETECT);
/* allow to pass incomplete frame to decoder */
FFmpeg.avcodeccontext_add_flags2(avcontext, FFmpeg.CODEC_FLAG2_CHUNKS);
if (FFmpeg.avcodec_open(avcontext, avcodec) < 0)
throw new RuntimeException("Could not open codec CODEC_ID_H264");
avframe = FFmpeg.avcodec_alloc_frame();
opened = true;
super.open();
}
/**
* Decodes H.264 media data read from a specific input <tt>Buffer</tt> into
* a specific output <tt>Buffer</tt>.
*
* @param inBuffer input <tt>Buffer</tt>
* @param outBuffer output <tt>Buffer</tt>
* @return <tt>BUFFER_PROCESSED_OK</tt> if <tt>inBuffer</tt> has been
* successfully processed
*/
public synchronized int process(Buffer inBuffer, Buffer outBuffer)
{
if (!checkInputBuffer(inBuffer))
return BUFFER_PROCESSED_FAILED;
if (isEOM(inBuffer) || !opened)
{
propagateEOM(outBuffer);
return BUFFER_PROCESSED_OK;
}
if (inBuffer.isDiscard())
{
outBuffer.setDiscard(true);
return BUFFER_PROCESSED_OK;
}
// Ask FFmpeg to decode.
got_picture[0] = false;
// TODO Take into account the offset of inputBuffer.
FFmpeg.avcodec_decode_video(
avcontext,
avframe,
got_picture,
(byte[]) inBuffer.getData(), inBuffer.getLength());
if (!got_picture[0])
{
outBuffer.setDiscard(true);
return BUFFER_PROCESSED_OK;
}
// format
int width = FFmpeg.avcodeccontext_get_width(avcontext);
int height = FFmpeg.avcodeccontext_get_height(avcontext);
if ((width > 0)
&& (height > 0)
&& ((this.width != width) || (this.height != height)))
{
this.width = width;
this.height = height;
// Output in same size and frame rate as input.
Dimension outSize = new Dimension(this.width, this.height);
VideoFormat inFormat = (VideoFormat) inBuffer.getFormat();
float outFrameRate = ensureFrameRate(inFormat.getFrameRate());
outputFormat
= new AVFrameFormat(
outSize,
outFrameRate,
FFmpeg.PIX_FMT_YUV420P,
Format.NOT_SPECIFIED);
}
outBuffer.setFormat(outputFormat);
// data
Object out = outBuffer.getData();
if (!(out instanceof AVFrame) || (((AVFrame) out).getPtr() != avframe))
outBuffer.setData(new AVFrame(avframe));
// timeStamp
long pts = FFmpeg.AV_NOPTS_VALUE; // TODO avframe_get_pts(avframe);
if (pts == FFmpeg.AV_NOPTS_VALUE)
outBuffer.setTimeStamp(Buffer.TIME_UNKNOWN);
else
{
outBuffer.setTimeStamp(pts);
int outFlags = outBuffer.getFlags();
outFlags |= Buffer.FLAG_RELATIVE_TIME;
outFlags &= ~(Buffer.FLAG_RTP_TIME | Buffer.FLAG_SYSTEM_TIME);
outBuffer.setFlags(outFlags);
}
return BUFFER_PROCESSED_OK;
}
/**
* Sets the <tt>Format</tt> of the media data to be input for processing in
* this <tt>Codec</tt>.
*
* @param format the <tt>Format</tt> of the media data to be input for
* processing in this <tt>Codec</tt>
* @return the <tt>Format</tt> of the media data to be input for processing
* in this <tt>Codec</tt> if <tt>format</tt> is compatible with this
* <tt>Codec</tt>; otherwise, <tt>null</tt>
*/
@Override
public Format setInputFormat(Format format)
{
Format setFormat = super.setInputFormat(format);
if (setFormat != null)
reset();
return setFormat;
}
}
/*
* 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.neomedia.codec.video.h264;
import java.awt.*;
import javax.media.*;
import javax.media.format.*;
import net.java.sip.communicator.impl.neomedia.codec.*;
import net.java.sip.communicator.impl.neomedia.codec.video.*;
import net.sf.fmj.media.*;
/**
* Decodes H.264 NAL units and returns the resulting frames as FFmpeg
* <tt>AVFrame</tt>s (i.e. in YUV format).
*
* @author Damian Minkov
* @author Lyubomir Marinov
* @author Sebastien Vincent
*/
public class JNIDecoder
extends AbstractCodec
{
/**
* Plugin name.
*/
private static final String PLUGIN_NAME = "H.264 Decoder";
/**
* The default output <tt>VideoFormat</tt>.
*/
private static final VideoFormat[] DEFAULT_OUTPUT_FORMATS
= new VideoFormat[] { new AVFrameFormat() };
/**
* The codec context native pointer we will use.
*/
private long avctx;
/**
* The decoded data is stored in avpicture in native ffmpeg format (YUV).
*/
private long avframe;
/**
* If decoder has got a picture.
*/
private final boolean[] got_picture = new boolean[1];
/**
* The last known height of {@link #avctx} i.e. the video output by this
* <tt>JNIDecoder</tt>. Used to detect changes in the output size.
*/
private int height;
/**
* Array of output <tt>VideoFormat</tt>s.
*/
private final VideoFormat[] outputFormats;
/**
* The last known width of {@link #avctx} i.e. the video output by this
* <tt>JNIDecoder</tt>. Used to detect changes in the output size.
*/
private int width;
/**
* Initializes a new <tt>JNIDecoder</tt> instance which is to decode H.264
* NAL units into frames in YUV format.
*/
public JNIDecoder()
{
inputFormats
= new VideoFormat[] { new VideoFormat(Constants.H264) };
outputFormats
= new VideoFormat[]
{
new AVFrameFormat(
new Dimension(
Constants.VIDEO_WIDTH,
Constants.VIDEO_HEIGHT),
ensureFrameRate(Format.NOT_SPECIFIED),
FFmpeg.PIX_FMT_YUV420P,
Format.NOT_SPECIFIED)
};
Dimension outputSize = outputFormats[0].getSize();
width = outputSize.width;
height = outputSize.height;
}
/**
* Check <tt>Format</tt>.
*
* @param format <tt>Format</tt> to check
* @return true if <tt>Format</tt> is H264_RTP
*/
public boolean checkFormat(Format format)
{
return format.getEncoding().equals(Constants.H264_RTP);
}
/**
* Close <tt>Codec</tt>.
*/
@Override
public synchronized void close()
{
if (opened)
{
opened = false;
super.close();
FFmpeg.avcodec_close(avctx);
FFmpeg.av_free(avctx);
avctx = 0;
FFmpeg.av_free(avframe);
avframe = 0;
}
}
/**
* Ensure frame rate.
*
* @param frameRate frame rate
* @return frame rate
*/
private float ensureFrameRate(float frameRate)
{
return frameRate;
}
/**
* Get matching outputs for a specified input <tt>Format</tt>.
*
* @param in input <tt>Format</tt>
* @return array of matching outputs or null if there are no matching
* outputs.
*/
protected Format[] getMatchingOutputFormats(Format in)
{
VideoFormat ivf = (VideoFormat) in;
Dimension inSize = ivf.getSize();
Dimension outSize;
// return the default size/currently decoder and encoder
// set to transmit/receive at this size
if (inSize == null)
{
VideoFormat ovf = outputFormats[0];
if (ovf == null)
return null;
else
outSize = ovf.getSize();
}
else
outSize = inSize; // Output in same size as input.
return
new Format[]
{
new AVFrameFormat(
outSize,
ensureFrameRate(ivf.getFrameRate()),
FFmpeg.PIX_FMT_YUV420P,
Format.NOT_SPECIFIED)
};
}
/**
* Get plugin name.
*
* @return "H.264 Decoder"
*/
@Override
public String getName()
{
return PLUGIN_NAME;
}
/**
* Get all supported output <tt>Format</tt>s.
*
* @param inputFormat input <tt>Format</tt> to determine corresponding
* output <tt>Format/tt>s
* @return an array of supported output <tt>Format</tt>s
*/
public Format[] getSupportedOutputFormats(Format inputFormat)
{
Format[] supportedOutputFormats;
if (inputFormat == null)
supportedOutputFormats = DEFAULT_OUTPUT_FORMATS;
else
{
// mismatch input format
if (!(inputFormat instanceof VideoFormat)
|| (AbstractCodecExt.matches(inputFormat, inputFormats)
== null))
supportedOutputFormats = new Format[0];
else
{
// match input format
supportedOutputFormats = getMatchingOutputFormats(inputFormat);
}
}
return supportedOutputFormats;
}
/**
* Inits the codec instances.
*
* @throws ResourceUnavailableException if codec initialization failed
*/
@Override
public synchronized void open()
throws ResourceUnavailableException
{
if (opened)
return;
long avcodec = FFmpeg.avcodec_find_decoder(FFmpeg.CODEC_ID_H264);
avctx = FFmpeg.avcodec_alloc_context();
FFmpeg.avcodeccontext_set_workaround_bugs(avctx,
FFmpeg.FF_BUG_AUTODETECT);
/* allow to pass incomplete frame to decoder */
FFmpeg.avcodeccontext_add_flags2(avctx,
FFmpeg.CODEC_FLAG2_CHUNKS);
if (FFmpeg.avcodec_open(avctx, avcodec) < 0)
throw new RuntimeException("Could not open codec CODEC_ID_H264");
avframe = FFmpeg.avcodec_alloc_frame();
opened = true;
super.open();
}
/**
* Decodes H.264 media data read from a specific input <tt>Buffer</tt> into
* a specific output <tt>Buffer</tt>.
*
* @param inBuffer input <tt>Buffer</tt>
* @param outBuffer output <tt>Buffer</tt>
* @return <tt>BUFFER_PROCESSED_OK</tt> if <tt>inBuffer</tt> has been
* successfully processed
*/
public synchronized int process(Buffer inBuffer, Buffer outBuffer)
{
if (!checkInputBuffer(inBuffer))
return BUFFER_PROCESSED_FAILED;
if (isEOM(inBuffer) || !opened)
{
propagateEOM(outBuffer);
return BUFFER_PROCESSED_OK;
}
if (inBuffer.isDiscard())
{
outBuffer.setDiscard(true);
return BUFFER_PROCESSED_OK;
}
// Ask FFmpeg to decode.
got_picture[0] = false;
// TODO Take into account the offset of inputBuffer.
FFmpeg.avcodec_decode_video(
avctx,
avframe,
got_picture,
(byte[]) inBuffer.getData(), inBuffer.getLength());
if (!got_picture[0])
{
outBuffer.setDiscard(true);
return BUFFER_PROCESSED_OK;
}
// format
int width = FFmpeg.avcodeccontext_get_width(avctx);
int height = FFmpeg.avcodeccontext_get_height(avctx);
if ((width > 0)
&& (height > 0)
&& ((this.width != width) || (this.height != height)))
{
this.width = width;
this.height = height;
// Output in same size and frame rate as input.
Dimension outSize = new Dimension(this.width, this.height);
VideoFormat inFormat = (VideoFormat) inBuffer.getFormat();
float outFrameRate = ensureFrameRate(inFormat.getFrameRate());
outputFormat
= new AVFrameFormat(
outSize,
outFrameRate,
FFmpeg.PIX_FMT_YUV420P,
Format.NOT_SPECIFIED);
}
outBuffer.setFormat(outputFormat);
// data
Object out = outBuffer.getData();
if (!(out instanceof AVFrame) || (((AVFrame) out).getPtr() != avframe))
outBuffer.setData(new AVFrame(avframe));
// timeStamp
long pts = FFmpeg.AV_NOPTS_VALUE; // TODO avframe_get_pts(avframe);
if (pts == FFmpeg.AV_NOPTS_VALUE)
outBuffer.setTimeStamp(Buffer.TIME_UNKNOWN);
else
{
outBuffer.setTimeStamp(pts);
int outFlags = outBuffer.getFlags();
outFlags |= Buffer.FLAG_RELATIVE_TIME;
outFlags &= ~(Buffer.FLAG_RTP_TIME | Buffer.FLAG_SYSTEM_TIME);
outBuffer.setFlags(outFlags);
}
return BUFFER_PROCESSED_OK;
}
/**
* Sets the <tt>Format</tt> of the media data to be input for processing in
* this <tt>Codec</tt>.
*
* @param format the <tt>Format</tt> of the media data to be input for
* processing in this <tt>Codec</tt>
* @return the <tt>Format</tt> of the media data to be input for processing
* in this <tt>Codec</tt> if <tt>format</tt> is compatible with this
* <tt>Codec</tt>; otherwise, <tt>null</tt>
*/
@Override
public Format setInputFormat(Format format)
{
Format setFormat = super.setInputFormat(format);
if (setFormat != null)
reset();
return setFormat;
}
}

@ -7,20 +7,21 @@
package net.java.sip.communicator.impl.neomedia.codec.video.h264;
import java.awt.*;
import java.util.*;
import javax.media.*;
import javax.media.format.*;
import net.java.sip.communicator.service.neomedia.event.*;
import net.java.sip.communicator.impl.neomedia.codec.*;
import net.java.sip.communicator.impl.neomedia.format.*;
import net.java.sip.communicator.service.neomedia.event.*;
import net.sf.fmj.media.*;
/**
* Implements a H.264 encoder.
*
* @author Damian Minkov
* @author Lubomir Marinov
* @author Lyubomir Marinov
* @author Sebastien Vincent
*/
public class JNIEncoder
@ -35,15 +36,15 @@ public class JNIEncoder
private static final int DEFAULT_FRAME_RATE = 15;
/**
* Default output formats.
* Key frame every 150 frames.
*/
private static final Format[] DEFAULT_OUTPUT_FORMATS
= { new VideoFormat(Constants.H264) };
private static final int IFRAME_INTERVAL = 150;
/**
* Key frame every 150 frames.
* The name of the format parameter which specifies the packetization mode
* of H.264 RTP payload.
*/
private static final int IFRAME_INTERVAL = 150;
public static final String PACKETIZATION_MODE_FMTP = "packetization-mode";
/**
* Minimum interval between two PLI request processing (in milliseconds).
@ -55,10 +56,24 @@ public class JNIEncoder
*/
private static final String PLUGIN_NAME = "H.264 Encoder";
/**
* The list of <tt>Formats</tt> supported by <tt>JNIEncoder</tt> instances
* as output.
*/
static final Format[] SUPPORTED_OUTPUT_FORMATS
= {
new ParameterizedVideoFormat(
Constants.H264,
PACKETIZATION_MODE_FMTP, "0"),
new ParameterizedVideoFormat(
Constants.H264,
PACKETIZATION_MODE_FMTP, "1")
};
/**
* The codec we will use.
*/
private long avcontext;
private long avctx;
/**
* The encoded data is stored in avpicture.
@ -101,8 +116,8 @@ public class JNIEncoder
* manage to decode the first keyframe and must wait for the next periodic
* intra refresh to display the video to the user.
*
* Temporary solution for this probolem: send the two first frames as
* keyframe to display video stream.
* Temporary solution for this problem: send the two first frames as
* keyframes to display video stream.
*/
private boolean secondKeyFrame = true;
@ -139,9 +154,9 @@ public synchronized void close()
opened = false;
super.close();
FFmpeg.avcodec_close(avcontext);
FFmpeg.av_free(avcontext);
avcontext = 0;
FFmpeg.avcodec_close(avctx);
FFmpeg.av_free(avctx);
avctx = 0;
FFmpeg.av_free(avframe);
avframe = 0;
@ -152,6 +167,38 @@ public synchronized void close()
}
}
/**
* Event fired when RTCP feedback message is received.
*
* @param event <tt>RTCPFeedbackEvent</tt>
*/
public void feedbackReceived(RTCPFeedbackEvent event)
{
/*
* If RTCP message is a Picture Loss Indication (PLI) or a
* Full Intra-frame Request (FIR) the encoder will force the next frame
* to be a keyframe.
*/
if (event.getPayloadType() == RTCPFeedbackEvent.PT_PS)
{
switch (event.getFeedbackMessageType())
{
case RTCPFeedbackEvent.FMT_PLI:
case RTCPFeedbackEvent.FMT_FIR:
if (System.currentTimeMillis()
> (lastKeyframeRequestTime + PLI_INTERVAL))
{
lastKeyframeRequestTime = System.currentTimeMillis();
// Disable PLI.
//forceKeyFrame = true;
}
break;
default:
break;
}
}
}
/**
* Gets the matching output formats for a specific format.
*
@ -163,14 +210,17 @@ private Format[] getMatchingOutputFormats(Format in)
VideoFormat videoIn = (VideoFormat) in;
return
new VideoFormat[]
new Format[]
{
new VideoFormat(
new ParameterizedVideoFormat(
Constants.H264,
videoIn.getSize(),
Format.NOT_SPECIFIED,
Format.byteArray,
videoIn.getFrameRate())
videoIn.getFrameRate(),
ParameterizedVideoFormat.toMap(
PACKETIZATION_MODE_FMTP,
"1"))
};
}
@ -196,7 +246,7 @@ public Format[] getSupportedOutputFormats(Format in)
{
// null input format
if (in == null)
return DEFAULT_OUTPUT_FORMATS;
return SUPPORTED_OUTPUT_FORMATS;
// mismatch input format
if (!(in instanceof VideoFormat)
@ -228,12 +278,12 @@ public synchronized void open()
long avcodec = FFmpeg.avcodec_find_encoder(FFmpeg.CODEC_ID_H264);
avcontext = FFmpeg.avcodec_alloc_context();
avctx = FFmpeg.avcodec_alloc_context();
FFmpeg.avcodeccontext_set_pix_fmt(avcontext, FFmpeg.PIX_FMT_YUV420P);
FFmpeg.avcodeccontext_set_size(avcontext, width, height);
FFmpeg.avcodeccontext_set_pix_fmt(avctx, FFmpeg.PIX_FMT_YUV420P);
FFmpeg.avcodeccontext_set_size(avctx, width, height);
FFmpeg.avcodeccontext_set_qcompress(avcontext, 0.6f);
FFmpeg.avcodeccontext_set_qcompress(avctx, 0.6f);
int bitRate = 128000;
int frameRate = (int) outputVideoFormat.getFrameRate();
@ -242,58 +292,52 @@ public synchronized void open()
frameRate = DEFAULT_FRAME_RATE;
// average bit rate
FFmpeg.avcodeccontext_set_bit_rate(avcontext, bitRate);
FFmpeg.avcodeccontext_set_bit_rate(avctx, bitRate);
// so to be 1 in x264
FFmpeg.avcodeccontext_set_bit_rate_tolerance(avcontext, (bitRate /
FFmpeg.avcodeccontext_set_bit_rate_tolerance(avctx, (bitRate /
frameRate));
FFmpeg.avcodeccontext_set_rc_max_rate(avcontext, bitRate);
FFmpeg.avcodeccontext_set_sample_aspect_ratio(avcontext, 0, 0);
FFmpeg.avcodeccontext_set_thread_count(avcontext, 1);
FFmpeg.avcodeccontext_set_rc_max_rate(avctx, bitRate);
FFmpeg.avcodeccontext_set_sample_aspect_ratio(avctx, 0, 0);
FFmpeg.avcodeccontext_set_thread_count(avctx, 1);
// time_base should be 1 / frame rate
FFmpeg.avcodeccontext_set_time_base(avcontext, 1, frameRate);
FFmpeg.avcodeccontext_set_ticks_per_frame(avcontext, 2);
FFmpeg.avcodeccontext_set_quantizer(avcontext, 30, 31, 4);
FFmpeg.avcodeccontext_set_time_base(avctx, 1, frameRate);
FFmpeg.avcodeccontext_set_ticks_per_frame(avctx, 2);
FFmpeg.avcodeccontext_set_quantizer(avctx, 30, 31, 4);
// avcontext.chromaoffset = -2;
// avctx.chromaoffset = -2;
FFmpeg.avcodeccontext_add_partitions(avcontext, 0x111);
FFmpeg.avcodeccontext_add_partitions(avctx, 0x111);
// X264_PART_I4X4 0x001
// X264_PART_P8X8 0x010
// X264_PART_B8X8 0x100
FFmpeg.avcodeccontext_set_mb_decision(avcontext,
FFmpeg.avcodeccontext_set_mb_decision(avctx,
FFmpeg.FF_MB_DECISION_SIMPLE);
FFmpeg.avcodeccontext_set_rc_eq(avcontext, "blurCplx^(1-qComp)");
FFmpeg.avcodeccontext_add_flags(avcontext,
FFmpeg.CODEC_FLAG_LOOP_FILTER);
FFmpeg.avcodeccontext_set_me_method(avcontext, 7);
FFmpeg.avcodeccontext_set_me_subpel_quality(avcontext, 2);
FFmpeg.avcodeccontext_set_me_range(avcontext, 16);
FFmpeg.avcodeccontext_set_me_cmp(avcontext, FFmpeg.FF_CMP_CHROMA);
FFmpeg.avcodeccontext_set_scenechange_threshold(avcontext, 40);
FFmpeg.avcodeccontext_set_rc_eq(avctx, "blurCplx^(1-qComp)");
FFmpeg.avcodeccontext_add_flags(avctx,
FFmpeg.CODEC_FLAG_LOOP_FILTER);
FFmpeg.avcodeccontext_add_flags2(avctx,
FFmpeg.CODEC_FLAG2_INTRA_REFRESH);
FFmpeg.avcodeccontext_set_me_method(avctx, 7);
FFmpeg.avcodeccontext_set_me_subpel_quality(avctx, 2);
FFmpeg.avcodeccontext_set_me_range(avctx, 16);
FFmpeg.avcodeccontext_set_me_cmp(avctx, FFmpeg.FF_CMP_CHROMA);
FFmpeg.avcodeccontext_set_scenechange_threshold(avctx, 40);
// Constant quality mode (also known as constant ratefactor)
FFmpeg.avcodeccontext_set_crf(avcontext, 0);
FFmpeg.avcodeccontext_set_rc_buffer_size(avcontext, 10);
FFmpeg.avcodeccontext_set_gop_size(avcontext, IFRAME_INTERVAL);
FFmpeg.avcodeccontext_set_i_quant_factor(avcontext, 1f / 1.4f);
FFmpeg.avcodeccontext_set_crf(avctx, 0);
FFmpeg.avcodeccontext_set_rc_buffer_size(avctx, 10);
FFmpeg.avcodeccontext_set_gop_size(avctx, IFRAME_INTERVAL);
FFmpeg.avcodeccontext_set_i_quant_factor(avctx, 1f / 1.4f);
FFmpeg.avcodeccontext_set_refs(avcontext, 1);
//FFmpeg.avcodeccontext_set_trellis(avcontext, 2);
FFmpeg.avcodeccontext_set_refs(avctx, 1);
//FFmpeg.avcodeccontext_set_trellis(avctx, 2);
/*
* AVCodecContext's rtp_payload_size is supposed to try to provide an
* alternative to the "Fragmentation Units (FUs)" created by Packetizer
* but the former does not seem to be guaranteed and the video looks
* better at the time of this writing when only Packetizer takes care of
* ensuring MAX_PAYLOAD_SIZE.
*/
//FFmpeg.avcodeccontext_set_rtp_payload_size(avcontext,
// Packetizer.MAX_PAYLOAD_SIZE);
FFmpeg.avcodeccontext_set_keyint_min(avctx, 0);
if (FFmpeg.avcodec_open(avcontext, avcodec) < 0)
if (FFmpeg.avcodec_open(avctx, avcodec) < 0)
{
throw
new ResourceUnavailableException(
@ -305,7 +349,6 @@ public synchronized void open()
encFrameLen = (width * height * 3) / 2;
rawFrameBuffer = FFmpeg.av_malloc(encFrameLen);
avframe = FFmpeg.avcodec_alloc_frame();
int sizeInBytes = width * height;
@ -389,7 +432,7 @@ public synchronized int process(Buffer inBuffer, Buffer outBuffer)
// encode data
int encLen
= FFmpeg.avcodec_encode_video(
avcontext,
avctx,
encFrameBuffer, encFrameLen,
avframe);
@ -417,22 +460,6 @@ public synchronized int process(Buffer inBuffer, Buffer outBuffer)
outBuffer.setLength(encLen);
outBuffer.setOffset(0);
outBuffer.setTimeStamp(inBuffer.getTimeStamp());
/*
// flags
int inFlags = inBuffer.getFlags();
int outFlags = outBuffer.getFlags();
if ((inFlags & Buffer.FLAG_LIVE_DATA) != 0)
outFlags |= Buffer.FLAG_LIVE_DATA;
if ((inFlags & Buffer.FLAG_RELATIVE_TIME) != 0)
outFlags |= Buffer.FLAG_RELATIVE_TIME;
if ((inFlags & Buffer.FLAG_RTP_TIME) != 0)
outFlags |= Buffer.FLAG_RTP_TIME;
if ((inFlags & Buffer.FLAG_SYSTEM_TIME) != 0)
outFlags |= Buffer.FLAG_SYSTEM_TIME;
outBuffer.setFlags(outFlags);
*/
return BUFFER_PROCESSED_OK;
}
@ -517,46 +544,24 @@ public Format setOutputFormat(Format out)
: inSize;
}
Map<String, String> fmtps = null;
if (out instanceof ParameterizedVideoFormat)
fmtps = ((ParameterizedVideoFormat) out).getFormatParameters();
if (fmtps == null)
fmtps = new HashMap<String, String>();
fmtps.put(PACKETIZATION_MODE_FMTP, "1");
outputFormat
= new VideoFormat(
= new ParameterizedVideoFormat(
videoOut.getEncoding(),
outSize,
Format.NOT_SPECIFIED,
Format.byteArray,
videoOut.getFrameRate());
videoOut.getFrameRate(),
fmtps);
// Return the selected outputFormat
return outputFormat;
}
/**
* Event fired when RTCP feedback message is received.
*
* @param event <tt>RTCPFeedbackEvent</tt>
*/
public void feedbackReceived(RTCPFeedbackEvent event)
{
/* if RTCP message is a Picture Loss Indication (PLI)
* or a Full Intra-frame Request (FIR) the encoder will
* force the next frame to be a keyframe
*/
if(event.getPayloadType() == RTCPFeedbackEvent.PT_PS)
{
switch(event.getFeedbackMessageType())
{
case RTCPFeedbackEvent.FMT_PLI:
case RTCPFeedbackEvent.FMT_FIR:
if(System.currentTimeMillis() > lastKeyframeRequestTime
+ PLI_INTERVAL)
{
lastKeyframeRequestTime = System.currentTimeMillis();
/* disable PLI */
//forceKeyFrame = true;
}
break;
default:
break;
}
}
}
}

@ -12,6 +12,7 @@
import javax.media.*;
import javax.media.format.*;
import net.java.sip.communicator.impl.neomedia.format.*;
import net.java.sip.communicator.impl.neomedia.codec.*;
import net.sf.fmj.media.*;
@ -20,17 +21,11 @@
* 3984 "RTP Payload Format for H.264 Video".
*
* @author Damian Minkov
* @author Lubomir Marinov
* @author Lyubomir Marinov
*/
public class Packetizer
extends AbstractPacketizer
{
/**
* Array of default output formats.
*/
private static final Format[] DEFAULT_OUTPUT_FORMATS
= { new VideoFormat(Constants.H264_RTP) };
/**
* Maximum payload size without the headers.
*/
@ -41,6 +36,20 @@ public class Packetizer
*/
private static final String PLUGIN_NAME = "H264 Packetizer";
/**
* The <tt>Formats</tt> supported by <tt>Packetizer</tt> instances as
* output.
*/
static final Format[] SUPPORTED_OUTPUT_FORMATS
= {
new ParameterizedVideoFormat(
Constants.H264_RTP,
JNIEncoder.PACKETIZATION_MODE_FMTP, "0"),
new ParameterizedVideoFormat(
Constants.H264_RTP,
JNIEncoder.PACKETIZATION_MODE_FMTP, "1")
};
/**
* The list of NAL units to be sent as payload in RTP packets.
*/
@ -64,7 +73,7 @@ public class Packetizer
*/
public Packetizer()
{
inputFormats = new Format[] { new VideoFormat(Constants.H264) };
inputFormats = JNIEncoder.SUPPORTED_OUTPUT_FORMATS;
inputFormat = null;
outputFormat = null;
@ -112,31 +121,37 @@ private static int ff_avc_find_startcode(
}
/**
* Get the matching output formats for a specific format.
* Gets the output formats matching a specific input format.
*
* @param in input format
* @return array for formats matching input format
* @param input the input format to get the matching output formats for
* @return an array of output formats matching the specified input format
*/
private Format[] getMatchingOutputFormats(Format in)
private Format[] getMatchingOutputFormats(Format input)
{
VideoFormat videoIn = (VideoFormat) in;
Dimension inSize = videoIn.getSize();
VideoFormat videoInput = (VideoFormat) input;
Dimension size = videoInput.getSize();
float frameRate = videoInput.getFrameRate();
String packetizationMode = getPacketizationMode(input);
return
new VideoFormat[]
new Format[]
{
new VideoFormat(
new ParameterizedVideoFormat(
Constants.H264_RTP,
inSize,
size,
Format.NOT_SPECIFIED,
Format.byteArray,
videoIn.getFrameRate())
frameRate,
ParameterizedVideoFormat.toMap(
JNIEncoder.PACKETIZATION_MODE_FMTP,
packetizationMode))
};
}
/**
* Get codec name.
* Gets the name of this <tt>PlugIn</tt>.
*
* @return codec name
* @return the name of this <tt>PlugIn</tt>
*/
@Override
public String getName()
@ -144,6 +159,28 @@ public String getName()
return PLUGIN_NAME;
}
/**
* Gets the value of the <tt>packetization-mode</tt> format parameter
* assigned by a specific <tt>Format</tt>.
*
* @param format the <tt>Format</tt> which assigns a value to the
* <tt>packetization-mode</tt> format parameter
* @return the value of the <tt>packetization-mode</tt> format parameter
* assigned by the specified <tt>format</tt>
*/
private String getPacketizationMode(Format format)
{
String packetizationMode = null;
if (format instanceof ParameterizedVideoFormat)
packetizationMode
= ((ParameterizedVideoFormat) format).getFormatParameter(
JNIEncoder.PACKETIZATION_MODE_FMTP);
if (packetizationMode == null)
packetizationMode = "0";
return packetizationMode;
}
/**
* Return the list of formats supported at the output.
* @param in input <tt>Format</tt> to determine corresponding output
@ -154,7 +191,7 @@ public Format[] getSupportedOutputFormats(Format in)
{
// null input format
if (in == null)
return DEFAULT_OUTPUT_FORMATS;
return SUPPORTED_OUTPUT_FORMATS;
// mismatch input format
if (!(in instanceof VideoFormat)
@ -298,20 +335,6 @@ public int process(Buffer inBuffer, Buffer outBuffer)
outBuffer.setTimeStamp(nalsTimeStamp);
outBuffer.setSequenceNumber(sequenceNumber++);
// flags
// int inFlags = inBuffer.getFlags();
// int outFlags = outBuffer.getFlags();
// if ((inFlags & Buffer.FLAG_LIVE_DATA) != 0)
// outFlags |= Buffer.FLAG_LIVE_DATA;
// if ((inFlags & Buffer.FLAG_RELATIVE_TIME) != 0)
// outFlags |= Buffer.FLAG_RELATIVE_TIME;
// if ((inFlags & Buffer.FLAG_RTP_TIME) != 0)
// outFlags |= Buffer.FLAG_RTP_TIME;
// if ((inFlags & Buffer.FLAG_SYSTEM_TIME) != 0)
// outFlags |= Buffer.FLAG_SYSTEM_TIME;
// outBuffer.setFlags(outFlags);
// If there are other NALs, send them as well.
if(nals.size() > 0)
return (BUFFER_PROCESSED_OK | INPUT_BUFFER_NOT_CONSUMED);
@ -319,7 +342,7 @@ public int process(Buffer inBuffer, Buffer outBuffer)
{
// It's the last NAL of the current frame so mark it.
outBuffer.setFlags(
outBuffer.getFlags() | Buffer.FLAG_RTP_MARKER);
outBuffer.getFlags() | Buffer.FLAG_RTP_MARKER);
return BUFFER_PROCESSED_OK;
}
}
@ -377,8 +400,7 @@ public int process(Buffer inBuffer, Buffer outBuffer)
&& ((nextBeginIndex
= ff_avc_find_startcode(
inData,
beginIndex,
endIndex))
beginIndex, endIndex))
<= endIndex);
beginIndex = nextBeginIndex + 3)
{
@ -427,48 +449,60 @@ public Format setInputFormat(Format in)
* Sets the <tt>Format</tt> in which this <tt>Codec</tt> is to output media
* data.
*
* @param out the <tt>Format</tt> in which this <tt>Codec</tt> is to
* @param output the <tt>Format</tt> in which this <tt>Codec</tt> is to
* output media data
* @return the <tt>Format</tt> in which this <tt>Codec</tt> is currently
* configured to output media data or <tt>null</tt> if <tt>format</tt> was
* found to be incompatible with this <tt>Codec</tt>
*/
@Override
public Format setOutputFormat(Format out)
public Format setOutputFormat(Format output)
{
/*
* Return null if the specified output Format is incompatible with this
* Packetizer.
*/
if (!(out instanceof VideoFormat)
if (!(output instanceof VideoFormat)
|| (null
== AbstractCodecExt.matches(
out,
output,
getMatchingOutputFormats(inputFormat))))
return null;
VideoFormat videoOut = (VideoFormat) out;
Dimension outSize = videoOut.getSize();
VideoFormat videoOutput = (VideoFormat) output;
Dimension outputSize = videoOutput.getSize();
if (outSize == null)
if (outputSize == null)
{
Dimension inSize = ((VideoFormat) inputFormat).getSize();
Dimension inputSize = ((VideoFormat) inputFormat).getSize();
outSize
= (inSize == null)
outputSize
= (inputSize == null)
? new Dimension(
Constants.VIDEO_WIDTH,
Constants.VIDEO_HEIGHT)
: inSize;
: inputSize;
}
Map<String, String> fmtps = null;
if (output instanceof ParameterizedVideoFormat)
fmtps = ((ParameterizedVideoFormat) output).getFormatParameters();
if (fmtps == null)
fmtps = new HashMap<String, String>();
if (fmtps.get(JNIEncoder.PACKETIZATION_MODE_FMTP) == null)
fmtps.put(
JNIEncoder.PACKETIZATION_MODE_FMTP,
getPacketizationMode(inputFormat));
outputFormat
= new VideoFormat(
videoOut.getEncoding(),
outSize,
outSize.width * outSize.height,
= new ParameterizedVideoFormat(
videoOutput.getEncoding(),
outputSize,
outputSize.width * outputSize.height,
Format.byteArray,
videoOut.getFrameRate());
videoOutput.getFrameRate(),
fmtps);
// Return the outputFormat which is actually set.
return outputFormat;

@ -134,8 +134,8 @@ CaptureDevice createCaptureDevice()
{
if(captureDevice instanceof AbstractPullBufferCaptureDevice)
{
((AbstractPullBufferCaptureDevice)captureDevice).
setCaptureDeviceInfo(captureDeviceInfo);
((AbstractPullBufferCaptureDevice)captureDevice)
.setCaptureDeviceInfo(captureDeviceInfo);
}
// Try to enable tracing on captureDevice.

@ -28,7 +28,7 @@
* Represents the use of a specific <tt>MediaDevice</tt> by a
* <tt>MediaStream</tt>.
*
* @author Lubomir Marinov
* @author Lyubomir Marinov
* @author Damian Minkov
* @author Emil Ivov
*/
@ -88,7 +88,7 @@ public class MediaDeviceSession
* {@link #setFormat(MediaFormat)} and to be set as the output format of
* {@link #processor}.
*/
protected Format format;
private MediaFormatImpl<? extends Format> format;
/**
* The indicator which determines whether this <tt>MediaDeviceSession</tt>
@ -738,7 +738,7 @@ public Format getProcessorFormat()
? MediaType.VIDEO
: MediaType.AUDIO;
if(mediaType.equals(type))
if (mediaType.equals(type))
return jmfFormat;
}
}
@ -752,20 +752,28 @@ public Format getProcessorFormat()
* @return the <tt>MediaFormat</tt> in which this instance captures media
* from its associated <tt>MediaDevice</tt>
*/
public MediaFormat getFormat()
public MediaFormatImpl<? extends Format> getFormat()
{
// if processor is not present we will not query for format
// and return the locally stored one.
Format jmfFormat = (processor == null) ?
this.format : getProcessorFormat();
if(jmfFormat != null)
/*
* If the Format of the processor is different than the format of this
* MediaDeviceSession, we'll likely run into unexpected issues so debug
* whether there are such cases.
*/
if (logger.isDebugEnabled() && (processor != null))
{
MediaFormat format
= MediaFormatImpl.createInstance(jmfFormat);
return format;
Format processorFormat = getProcessorFormat();
Format format
= (this.format == null) ? null : this.format.getFormat();
boolean processorFormatEqualsFormat
= (processorFormat == null)
? (format == null)
: processorFormat.equals(format);
if (!processorFormatEqualsFormat)
logger.debug("processorFormat != format");
}
return null;
return format;
}
/**
@ -1277,9 +1285,7 @@ public void setContentDescriptor(ContentDescriptor contentDescriptor)
*/
public void setFormat(MediaFormat format)
{
MediaType mediaType = getMediaType();
if (!mediaType.equals(format.getMediaType()))
if (!getMediaType().equals(format.getMediaType()))
throw new IllegalArgumentException("format");
/*
@ -1290,7 +1296,14 @@ public void setFormat(MediaFormat format)
MediaFormatImpl<? extends Format> mediaFormatImpl
= (MediaFormatImpl<? extends Format>) format;
this.format = mediaFormatImpl.getFormat();
this.format = mediaFormatImpl;
if (logger.isTraceEnabled())
{
logger.trace(
"Set format " + this.format
+ " on "
+ getClass().getSimpleName() + " " + hashCode());
}
/*
* If the processor is after Configured, setting a different format will
@ -1305,7 +1318,8 @@ public void setFormat(MediaFormat format)
setProcessorFormat(processor, this.format);
else if (processorIsPrematurelyClosed
|| ((processorState > Processor.Configured)
&& !this.format.equals(getProcessorFormat())))
&& !this.format.getFormat().equals(
getProcessorFormat())))
{
setProcessor(null);
}
@ -1313,17 +1327,21 @@ else if (processorIsPrematurelyClosed
}
/**
* Sets the JMF <tt>Format</tt> in which a specific <tt>Processor</tt>
* Sets the <tt>MediaFormatImpl</tt> in which a specific <tt>Processor</tt>
* producing media to be streamed to the remote peer is to output.
*
* @param processor the <tt>Processor</tt> to set the output <tt>Format</tt>
* of
* @param format the JMF <tt>Format</tt> to set to <tt>processor</tt>
*/
protected void setProcessorFormat(Processor processor, Format format)
* @param processor the <tt>Processor</tt> to set the output
* <tt>MediaFormatImpl</tt> of
* @param mediaFormat the <tt>MediaFormatImpl</tt> to set on
* <tt>processor</tt>
*/
protected void setProcessorFormat(
Processor processor,
MediaFormatImpl<? extends Format> mediaFormat)
{
TrackControl[] trackControls = processor.getTrackControls();
MediaType mediaType = getMediaType();
Format format = mediaFormat.getFormat();
for (int trackIndex = 0;
trackIndex < trackControls.length;
@ -1393,7 +1411,9 @@ protected void setProcessorFormat(Processor processor, Format format)
else if (!supportedFormat.equals(trackControl.getFormat()))
{
Format setFormat
= setProcessorFormat(trackControl, supportedFormat);
= setProcessorFormat(
trackControl,
mediaFormat, supportedFormat);
if (setFormat == null)
logger
@ -1427,16 +1447,26 @@ else if (logger.isTraceEnabled())
}
/**
* Sets the JMF <tt>Format</tt> of a specific <tt>TrackControl</tt> of the
* <tt>Processor</tt> which produces the media to be streamed by this
* Sets the <tt>MediaFormatImpl</tt> of a specific <tt>TrackControl</tt> of
* the <tt>Processor</tt> which produces the media to be streamed by this
* <tt>MediaDeviceSession</tt> to the remote peer. Allows extenders to
* override the set procedure and to detect when the JMF <tt>Format</tt> of
* the specified <tt>TrackControl</tt> changes.
*
* @param trackControl the <tt>TrackControl</tt> to set the JMF
* <tt>Format</tt> of
* @param mediaFormat the <tt>MediaFormatImpl</tt> to be set on the
* specified <tt>TrackControl</tt>. Though <tt>mediaFormat</tt> encapsulates
* a JMF <tt>Format</tt>, <tt>format</tt> is to be set on the specified
* <tt>trackControl</tt> because it may be more specific. In any case, the
* two JMF <tt>Format</tt>s match. The <tt>MediaFormatImpl</tt> is provided
* anyway because it carries additional information such as format
* parameters.
* @param format the JMF <tt>Format</tt> to be set on the specified
* <tt>TrackControl</tt>
* <tt>TrackControl</tt>. Though <tt>mediaFormat</tt> encapsulates a JMF
* <tt>Format</tt>, the specified <tt>format</tt> is to be set on the
* specified <tt>trackControl</tt> because it may be more specific than the
* JMF <tt>Format</tt> of the <tt>mediaFormat</tt>
* @return the JMF <tt>Format</tt> set on <tt>TrackControl</tt> after the
* attempt to set the specified <tt>format</tt> or <tt>null</tt> if the
* specified <tt>format</tt> was found to be incompatible with
@ -1444,6 +1474,7 @@ else if (logger.isTraceEnabled())
*/
protected Format setProcessorFormat(
TrackControl trackControl,
MediaFormatImpl<? extends Format> mediaFormat,
Format format)
{
return trackControl.setFormat(format);

@ -18,8 +18,8 @@
import net.java.sip.communicator.impl.neomedia.*;
import net.java.sip.communicator.impl.neomedia.codec.video.*;
import net.java.sip.communicator.impl.neomedia.codec.video.h264.*;
import net.java.sip.communicator.impl.neomedia.format.*;
import net.java.sip.communicator.impl.neomedia.transform.*;
import net.java.sip.communicator.impl.neomedia.videoflip.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.event.*;
import net.java.sip.communicator.service.resources.*;
@ -34,6 +34,7 @@
public class VideoMediaDeviceSession
extends MediaDeviceSession
{
/**
* The <tt>Logger</tt> used by the <tt>VideoMediaDeviceSession</tt> class
* and its instances for logging output.
@ -188,7 +189,6 @@ protected DataSource createCaptureDevice()
if ((maxSupportedFrameRate > 0)
&& (frameRate > maxSupportedFrameRate))
frameRate = maxSupportedFrameRate;
if(frameRate > 0)
frameRateControl.setFrameRate(frameRate);
}
@ -421,11 +421,8 @@ private void controllerUpdateForCreateLocalVisualComponent(
{
for (TrackControl trackControl : trackControls)
{
VideoFlipEffect flipEffect = new VideoFlipEffect();
SwScaler scaler = new SwScaler();
trackControl.setCodecChain(
new Codec[] {flipEffect, scaler});
new Codec[] { new HFlip(), new SwScaler() });
break;
}
}
@ -788,31 +785,33 @@ protected void playerConfigureComplete(final Processor player)
* For H.264, we will use RTCP feedback. For example, to
* tell the sender that we've missed a frame.
*/
if(format.getEncoding().equals("h264/rtp") && usePLI)
if (usePLI
&& "h264/rtp".equalsIgnoreCase(
getFormat().getJMFEncoding()))
{
DePacketizer depack = new DePacketizer();
depack.setRtcpFeedbackPLI(usePLI);
DePacketizer depacketizer = new DePacketizer();
depacketizer.setRtcpFeedbackPLI(usePLI);
try
{
depack.setConnector(rtpConnector.
getControlOutputStream());
depacketizer.setConnector(
rtpConnector.getControlOutputStream());
}
catch(Exception e)
{
logger.error("Error cannot get RTCP output stream",
logger.error(
"Error cannot get RTCP output stream",
e);
}
depacketizer.setSSRC(localSSRC, remoteSSRC);
depack.setSSRC(localSSRC, remoteSSRC);
trackControl.setCodecChain(new Codec[] {
depack, playerScaler});
trackControl.setCodecChain(
new Codec[] { depacketizer, playerScaler });
}
else
{
trackControl.setCodecChain(new Codec[] {playerScaler});
trackControl.setCodecChain(
new Codec[] { playerScaler });
}
break;
}
@ -1123,28 +1122,31 @@ public void setRemoteSSRC(long remoteSSRC)
}
/**
* Sets the JMF <tt>Format</tt> in which a specific <tt>Processor</tt>
* Sets the <tt>MediaFormatImpl</tt> in which a specific <tt>Processor</tt>
* producing media to be streamed to the remote peer is to output.
*
* @param processor the <tt>Processor</tt> to set the output <tt>Format</tt>
* of
* @param format the JMF <tt>Format</tt> to set to <tt>processor</tt>
* @see MediaDeviceSession#setProcessorFormat(Processor, Format)
* @param processor the <tt>Processor</tt> to set the output
* <tt>MediaFormatImpl</tt> of
* @param mediaFormat the <tt>MediaFormatImpl</tt> to set on
* <tt>processor</tt>
* @see MediaDeviceSession#setProcessorFormat(Processor, MediaFormatImpl)
*/
@Override
protected void setProcessorFormat(Processor processor, Format format)
protected void setProcessorFormat(
Processor processor,
MediaFormatImpl<? extends Format> mediaFormat)
{
if(format.getEncoding().equals("h263-1998/rtp"))
Format format = mediaFormat.getFormat();
if ("h263-1998/rtp".equalsIgnoreCase(format.getEncoding()))
{
/* if no output size has been defined, it means that no SDP's fmtp
* has been found with QCIF, CIF, VGA or CUSTOM elements
*
* Let's choose QCIF size (176x144)
/*
* If no output size has been defined, then no SDP fmtp has been
* found with QCIF, CIF, VGA or CUSTOM parameters. Let's default to
* QCIF (176x144).
*/
if(outputSize == null)
{
if (outputSize == null)
outputSize = new Dimension(176, 144);
}
}
/*
@ -1152,7 +1154,7 @@ protected void setProcessorFormat(Processor processor, Format format)
* recreate the object. Also check whether capture device can output
* such a size.
*/
if((outputSize != null)
if ((outputSize != null)
&& (outputSize.width > 0)
&& (outputSize.height > 0))
{
@ -1179,39 +1181,52 @@ protected void setProcessorFormat(Processor processor, Format format)
else
outputSize = null;
super.setProcessorFormat(processor, format);
super.setProcessorFormat(processor, mediaFormat);
}
/**
* Sets the JMF <tt>Format</tt> of a specific <tt>TrackControl</tt> of the
* <tt>Processor</tt> which produces the media to be streamed by this
* Sets the <tt>MediaFormatImpl</tt> of a specific <tt>TrackControl</tt> of
* the <tt>Processor</tt> which produces the media to be streamed by this
* <tt>MediaDeviceSession</tt> to the remote peer. Allows extenders to
* override the set procedure and to detect when the JMF <tt>Format</tt> of
* the specified <tt>TrackControl</tt> changes.
*
* @param trackControl the <tt>TrackControl</tt> to set the JMF
* <tt>Format</tt> of
* @param mediaFormat the <tt>MediaFormatImpl</tt> to be set on the
* specified <tt>TrackControl</tt>. Though <tt>mediaFormat</tt> encapsulates
* a JMF <tt>Format</tt>, <tt>format</tt> is to be set on the specified
* <tt>trackControl</tt> because it may be more specific. In any case, the
* two JMF <tt>Format</tt>s match. The <tt>MediaFormatImpl</tt> is provided
* anyway because it carries additional information such as format
* parameters.
* @param format the JMF <tt>Format</tt> to be set on the specified
* <tt>TrackControl</tt>
* <tt>TrackControl</tt>. Though <tt>mediaFormat</tt> encapsulates a JMF
* <tt>Format</tt>, the specified <tt>format</tt> is to be set on the
* specified <tt>trackControl</tt> because it may be more specific than the
* JMF <tt>Format</tt> of the <tt>mediaFormat</tt>
* @return the JMF <tt>Format</tt> set on <tt>TrackControl</tt> after the
* attempt to set the specified <tt>format</tt> or <tt>null</tt> if the
* attempt to set the specified <tt>mediaFormat</tt> or <tt>null</tt> if the
* specified <tt>format</tt> was found to be incompatible with
* <tt>trackControl</tt>
* @see MediaDeviceSession#setProcessorFormat(TrackControl, Format)
* @see MediaDeviceSession#setProcessorFormat(TrackControl, MediaFormatImpl,
* Format)
*/
@Override
protected Format setProcessorFormat(
TrackControl trackControl,
MediaFormatImpl<? extends Format> mediaFormat,
Format format)
{
JNIEncoder encoder = null;
SwScaler scaler = null;
int codecCount = 0;
/* For H.264 we will monitor RTCP feedback. For example, if we receive a
/*
* For H.264 we will monitor RTCP feedback. For example, if we receive a
* PLI/FIR message, we will send a keyframe.
*/
if(format.getEncoding().equals("h264/rtp") && usePLI)
if (usePLI && "h264/rtp".equalsIgnoreCase(format.getEncoding()))
{
encoder = new JNIEncoder();
@ -1222,14 +1237,15 @@ protected Format setProcessorFormat(
rtpConnector.getControlInputStream())
.addRTCPFeedbackListener(encoder);
}
catch(Exception e)
catch (Exception e)
{
logger.error("Error cannot get RTCP input stream", e);
}
codecCount++;
}
if(outputSize != null)
if (outputSize != null)
{
/* We have been explicitly told to use a specified output size so
* create a custom SwScaler that will scale and convert color spaces
@ -1265,7 +1281,7 @@ protected Format setProcessorFormat(
}
}
return super.setProcessorFormat(trackControl, format);
return super.setProcessorFormat(trackControl, mediaFormat, format);
}
/**

@ -12,23 +12,25 @@
import net.java.sip.communicator.impl.neomedia.*;
import net.java.sip.communicator.impl.neomedia.codec.*;
import net.java.sip.communicator.service.neomedia.MediaType;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.format.*;
import net.java.sip.communicator.util.*;
/**
* Implements <tt>MediaFormatFactory</tt> for the JMF <tt>Format</tt> types.
*
* @author Lubomir Marinov
* @author Lyubomir Marinov
*/
public class MediaFormatFactoryImpl
implements MediaFormatFactory
{
/**
* Our class logger.
* The <tt>Logger</tt> used by the <tt>MediaFormatFactoryImpl</tt> class and
* its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(MediaFormatFactoryImpl.class);
/**
* Creates an unknown <tt>MediaFormat</tt>.
*
@ -39,14 +41,14 @@ public MediaFormat createUnknownMediaFormat(MediaType type)
{
Format unknown = null;
/*
* FIXME Why is a VideoFormat instance created for MediaType.AUDIO and
* an AudioFormat instance for MediaType.VIDEO?
*/
if(type.equals(MediaType.AUDIO))
{
unknown = new VideoFormat("unknown");
}
else if(type.equals(MediaType.VIDEO))
{
unknown = new AudioFormat("unknown");
}
return MediaFormatImpl.createInstance(unknown);
}
@ -88,11 +90,10 @@ public MediaFormat createMediaFormat(String encoding)
*/
public MediaFormat createMediaFormat(byte rtpPayloadType)
{
/*
* We know which are the MediaFormat instance with the specified
* We know which are the MediaFormat instances with the specified
* rtpPayloadType but we cannot directly return them because they do not
* reflect the user configuration with respect to being enabled and
* reflect the user's configuration with respect to being enabled and
* disabled.
*/
for (MediaFormat rtpPayloadTypeMediaFormat
@ -126,9 +127,7 @@ public MediaFormat createMediaFormat(byte rtpPayloadType)
* <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt>
* @see MediaFormatFactory#createMediaFormat(String, double)
*/
public MediaFormat createMediaFormat(
String encoding,
double clockRate)
public MediaFormat createMediaFormat(String encoding, double clockRate)
{
return createMediaFormat(encoding, clockRate, 1);
}
@ -145,7 +144,7 @@ public MediaFormat createMediaFormat(
* <tt>MediaFormat</tt> for
* @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt>
* for
* @param channels the number of availabe channels (1 for mono, 2 for
* @param channels the number of available channels (1 for mono, 2 for
* stereo) if it makes sense for the <tt>MediaFormat</tt> with the specified
* <tt>encoding</tt>; otherwise, ignored
* @return a <tt>MediaFormat</tt> with the specified <tt>encoding</tt>,
@ -156,22 +155,30 @@ public MediaFormat createMediaFormat(
* @see MediaFormatFactory#createMediaFormat(String, double, int)
*/
public MediaFormat createMediaFormat(
String encoding,
double clockRate,
int channels)
String encoding, double clockRate, int channels)
{
for (MediaFormat format : getSupportedMediaFormats(encoding, clockRate))
if ((CHANNELS_NOT_SPECIFIED != channels)
&& (format instanceof AudioMediaFormat))
{
AudioMediaFormat audioFormat = (AudioMediaFormat) format;
int audioFormatChannels = audioFormat.getChannels();
return createMediaFormat(encoding, clockRate, channels, null);
}
if ((CHANNELS_NOT_SPECIFIED == audioFormatChannels)
|| (audioFormatChannels == channels))
return audioFormat;
} else
private MediaFormat createMediaFormat(
String encoding, double clockRate, int channels,
Map<String, String> fmtps)
{
for (MediaFormat format : getSupportedMediaFormats(encoding, clockRate))
{
/*
* The mediaType, encoding and clockRate properties are sure to
* match because format is the result of the search for encoding and
* clockRate. We just want to make sure that the channels and the
* format parameters match.
*/
if (AbstractMediaStream.matches(
format,
format.getMediaType(),
format.getEncoding(), format.getClockRate(), channels,
fmtps))
return format;
}
return null;
}
@ -195,13 +202,16 @@ public MediaFormat createMediaFormat(
* otherwise, <tt>null</tt>
* @see MediaFormatFactory#createMediaFormat(String, double, Map, Map)
*/
public MediaFormat createMediaFormat( String encoding,
double clockRate,
Map<String, String> formatParams,
Map<String, String> advancedParams)
public MediaFormat createMediaFormat(
String encoding, double clockRate,
Map<String, String> formatParams,
Map<String, String> advancedParams)
{
return createMediaFormat( encoding, clockRate, 1, formatParams,
advancedParams);
return
createMediaFormat(
encoding, clockRate, 1,
formatParams,
advancedParams);
}
/**
@ -215,7 +225,7 @@ public MediaFormat createMediaFormat( String encoding,
* <tt>MediaFormat</tt> for
* @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt>
* for
* @param channels the number of availabe channels (1 for mono, 2 for
* @param channels the number of available channels (1 for mono, 2 for
* stereo) if it makes sense for the <tt>MediaFormat</tt> with the specified
* <tt>encoding</tt>; otherwise, ignored
* @param formatParams any codec specific parameters which have been
@ -229,42 +239,31 @@ public MediaFormat createMediaFormat( String encoding,
* <tt>MediaFormatFactory</tt>; otherwise, <tt>null</tt>
* @see MediaFormatFactory#createMediaFormat(String, double, int, Map, Map)
*/
public MediaFormat createMediaFormat(String encoding,
double clockRate,
int channels,
Map<String, String> formatParams,
Map<String, String> advancedParams)
public MediaFormat createMediaFormat(
String encoding, double clockRate, int channels,
Map<String, String> formatParams,
Map<String, String> advancedParams)
{
MediaFormat mediaFormat
= createMediaFormat(encoding, clockRate, channels);
Map<String, String> formatParameters
= new HashMap<String, String>();
Map<String, String> advancedParameters
= new HashMap<String, String>();
boolean recreate = false;
= createMediaFormat(encoding, clockRate, channels, formatParams);
if ((mediaFormat != null)
&& (formatParams != null)
&& !formatParams.isEmpty())
{
recreate = true;
//formatParameters.putAll(mediaFormat.getFormatParameters());
formatParameters.putAll(formatParams);
}
if (mediaFormat == null)
return null;
if(mediaFormat != null && (advancedParams != null ) &&
!advancedParams.isEmpty())
{
recreate = true;
/*
* MediaFormatImpl is immutable so if the caller wants to change the
* format parameters and/or the advanced attributes, we'll have to
* create a new MediaFormatImpl.
*/
Map<String, String> formatParameters = null;
Map<String, String> advancedParameters = null;
if(advancedParams != null)
{
advancedParameters.putAll(advancedParams);
}
}
if ((formatParams != null) && !formatParams.isEmpty())
formatParameters = formatParams;
if ((advancedParams != null ) && !advancedParams.isEmpty())
advancedParameters = advancedParams;
if(recreate)
if ((formatParameters != null) || (advancedParameters != null))
{
switch (mediaFormat.getMediaType())
{
@ -278,12 +277,23 @@ public MediaFormat createMediaFormat(String encoding,
VideoMediaFormatImpl videoMediaFormatImpl
= (VideoMediaFormatImpl) mediaFormat;
/*
* If the format of VideoMediaFormatImpl is
* a ParameterizedVideoFormat, it's possible for the format
* parameters of that ParameterizedVideoFormat and of the new
* VideoMediaFormatImpl (to be created) to be out of sync. While
* it's not technically perfect, it should be practically safe
* for the format parameters which distinguish VideoFormats with
* the same encoding and clock rate because mediaFormat has
* already been created in sync with formatParams (with respect
* to the format parameters which distinguish VideoFormats with
* the same encoding and clock rate).
*/
mediaFormat
= new VideoMediaFormatImpl(
videoMediaFormatImpl.getFormat(),
videoMediaFormatImpl.getClockRate(),
formatParameters,
advancedParameters);
formatParameters, advancedParameters);
break;
default:
mediaFormat = null;
@ -312,7 +322,7 @@ public MediaFormat createMediaFormat(String encoding,
* <tt>MediaFormat</tt> for; <tt>null</tt>
* @param clockRate the clock rate in Hz to create a <tt>MediaFormat</tt>
* for
* @param channels the number of availabe channels (1 for mono, 2 for
* @param channels the number of available channels (1 for mono, 2 for
* stereo) if it makes sense for the <tt>MediaFormat</tt> with the specified
* <tt>encoding</tt>; otherwise, ignored
* @param formatParams any codec specific parameters which have been
@ -327,9 +337,7 @@ public MediaFormat createMediaFormat(String encoding,
*/
public MediaFormat createMediaFormat(
byte rtpPayloadType,
String encoding,
double clockRate,
int channels,
String encoding, double clockRate, int channels,
Map<String, String> formatParams,
Map<String, String> advancedParams)
{
@ -374,8 +382,11 @@ public MediaFormat createMediaFormat(
}
}
return createMediaFormat(encoding, clockRate, channels, formatParams,
advancedParams);
return
createMediaFormat(
encoding, clockRate, channels,
formatParams,
advancedParams);
}
/**
@ -397,28 +408,37 @@ public MediaFormat createMediaFormat(
*/
private List<MediaFormat> getMatchingMediaFormats(
MediaFormat[] mediaFormats,
String encoding,
double clockRate)
String encoding, double clockRate)
{
// As per RFC 3551.4.5.2, because of a mistake in RFC 1890 and for
// b-ward compatibility, G.722 should always be announced as 8000 even
// though it is wideband. So, if someone is looking for G722/16000
// then: Forgive them, for they know not what they do!
if("G722".equals(encoding) && clockRate == 16000)
/*
* XXX Use String#equalsIgnoreCase(String) because some clients transmit
* some of the codecs starting with capital letters.
*/
/*
* As per RFC 3551.4.5.2, because of a mistake in RFC 1890 and for
* backward compatibility, G.722 should always be announced as 8000 even
* though it is wideband. So, if someone is looking for G722/16000,
* then: Forgive them, for they know not what they do!
*/
if("G722".equalsIgnoreCase(encoding) && (16000 == clockRate))
{
logger.info("Suppressing erroneous 16000 announcement for G.722");
clockRate = 8000;
if (logger.isInfoEnabled())
logger.info("Suppressing erroneous 16000 announcement for G.722");
}
List<MediaFormat> supportedMediaFormats = new ArrayList<MediaFormat>();
// uses equalsIgnoreCase, as some clients transmit some of the codecs
// starting with capital letters
for (MediaFormat mediaFormat : mediaFormats)
{
if (mediaFormat.getEncoding().equalsIgnoreCase(encoding)
&& ((CLOCK_RATE_NOT_SPECIFIED == clockRate)
|| (mediaFormat.getClockRate() == clockRate)))
{
supportedMediaFormats.add(mediaFormat);
}
}
return supportedMediaFormats;
}
@ -438,24 +458,23 @@ private List<MediaFormat> getMatchingMediaFormats(
* and having the specified encoding and, optionally, clock rate
*/
private List<MediaFormat> getSupportedMediaFormats(
String encoding,
double clockRate)
String encoding, double clockRate)
{
EncodingConfiguration encodingConfiguration
= NeomediaActivator
.getMediaServiceImpl().getEncodingConfiguration();
= NeomediaActivator.getMediaServiceImpl()
.getEncodingConfiguration();
List<MediaFormat> supportedMediaFormats
= getMatchingMediaFormats(
encodingConfiguration
.getSupportedEncodings(MediaType.AUDIO),
encodingConfiguration.getSupportedEncodings(
MediaType.AUDIO),
encoding,
clockRate);
if (supportedMediaFormats.isEmpty())
supportedMediaFormats
= getMatchingMediaFormats(
encodingConfiguration
.getSupportedEncodings(MediaType.VIDEO),
encodingConfiguration.getSupportedEncodings(
MediaType.VIDEO),
encoding,
clockRate);
return supportedMediaFormats;

@ -20,7 +20,7 @@
*
* @param <T> the type of the wrapped <tt>Format</tt>
*
* @author Lubomir Marinov
* @author Lyubomir Marinov
*/
public abstract class MediaFormatImpl<T extends Format>
implements MediaFormat
@ -32,7 +32,7 @@ public abstract class MediaFormatImpl<T extends Format>
* received via SIP/SDP or XMPP/Jingle. Explicitly defined in order to
* reduce unnecessary allocations.
*/
private static final Map<String, String> EMPTY_FORMAT_PARAMETERS
static final Map<String, String> EMPTY_FORMAT_PARAMETERS
= Collections.emptyMap();
/**
@ -112,38 +112,72 @@ public static MediaFormatImpl<? extends Format> createInstance(
* number of parameters and assign them equal values. Since the values are
* <tt>String</tt>s, presumes that a value of <tt>null</tt> is equal to the
* empty <tt>String</tt>.
* <p>
* The two <tt>Map</tt> instances of format parameters to be checked for
* equality are presumed to be modifiable in the sense that if the lack of a
* format parameter in a given <tt>Map</tt> is equivalent to it having a
* specific value, an association of the format parameter to the value in
* question may be added to or removed from the respective <tt>Map</tt>
* instance for the purposes of determining equality.
* </p>
*
* @param formatParameters1 the first set of format parameters to be tested
* for equality
* @param formatParameters2 the second set of format parameters to be tested
* for equality
* @param fmtps1 the first set of format parameters to be tested for
* equality
* @param fmtps2 the second set of format parameters to be tested for
* equality
* @return <tt>true</tt> if the specified sets of format parameters are
* equal; <tt>false</tt>, otherwise
*/
static boolean formatParametersAreEqual(
Map<String, String> formatParameters1,
Map<String, String> formatParameters2)
protected boolean formatParametersAreEqual(
Map<String, String> fmtps1,
Map<String, String> fmtps2)
{
if (formatParameters1 == null)
return
(formatParameters2 == null)
|| formatParameters2.isEmpty();
if (formatParameters2 == null)
return
(formatParameters1 == null)
|| formatParameters1.isEmpty();
if (formatParameters1.size() == formatParameters2.size())
return formatParametersAreEqual(getEncoding(), fmtps1, fmtps2);
}
/**
* Determines whether a specific set of format parameters is equal to
* another set of format parameters in the sense that they define an equal
* number of parameters and assign them equal values. Since the values are
* <tt>String</tt>s, presumes that a value of <tt>null</tt> is equal to the
* empty <tt>String</tt>.
* <p>
* The two <tt>Map</tt> instances of format parameters to be checked for
* equality are presumed to be modifiable in the sense that if the lack of a
* format parameter in a given <tt>Map</tt> is equivalent to it having a
* specific value, an association of the format parameter to the value in
* question may be added to or removed from the respective <tt>Map</tt>
* instance for the purposes of determining equality.
* </p>
*
* @param encoding the encoding (name) related to the two sets of format
* parameters to be tested for equality
* @param fmtps1 the first set of format parameters to be tested for
* equality
* @param fmtps2 the second set of format parameters to be tested for
* equality
* @return <tt>true</tt> if the specified sets of format parameters are
* equal; <tt>false</tt>, otherwise
*/
public static boolean formatParametersAreEqual(
String encoding,
Map<String, String> fmtps1, Map<String, String> fmtps2)
{
if (fmtps1 == null)
return (fmtps2 == null) || fmtps2.isEmpty();
if (fmtps2 == null)
return (fmtps1 == null) || fmtps1.isEmpty();
if (fmtps1.size() == fmtps2.size())
{
for (Map.Entry<String, String> formatParameter1
: formatParameters1.entrySet())
for (Map.Entry<String, String> fmtp1 : fmtps1.entrySet())
{
String key1 = formatParameter1.getKey();
String key1 = fmtp1.getKey();
if (!formatParameters2.containsKey(key1))
if (!fmtps2.containsKey(key1))
return false;
String value1 = formatParameter1.getValue();
String value2 = formatParameters2.get(key1);
String value1 = fmtp1.getValue();
String value2 = fmtps2.get(key1);
/*
* Since the values are strings, allow null to be equal to the
@ -163,6 +197,19 @@ else if (!value1.equals(value2))
return false;
}
/**
* {@inheritDoc}
* <p>
* The default implementation of <tt>MediaFormatImpl</tt> always returns
* <tt>true</tt> because format parameters in general do not cause the
* distinction of payload types.
* </p>
*/
public boolean formatParametersMatch(Map<String, String> fmtps)
{
return true;
}
/**
* The JMF <tt>Format</tt> this instance wraps and provides an
* implementation of <tt>MediaFormat</tt> for.
@ -205,7 +252,9 @@ protected MediaFormatImpl(T format)
* @param advancedParameters any parameters that have been
* received via SIP/SDP or XMPP/Jingle
*/
protected MediaFormatImpl(T format, Map<String, String> formatParameters,
protected MediaFormatImpl(
T format,
Map<String, String> formatParameters,
Map<String, String> advancedParameters)
{
if (format == null)
@ -372,7 +421,7 @@ public int hashCode()
public String getClockRateString()
{
double clockRate = getClockRate();
long clockRateL = (long)clockRate;
long clockRateL = (long) clockRate;
if (clockRateL == clockRate)
return Long.toString(clockRateL);

@ -0,0 +1,240 @@
/*
* 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.neomedia.format;
import java.awt.*;
import java.util.*;
import javax.media.*;
import javax.media.format.*;
/**
* Implements a <tt>VideoFormat</tt> with format parameters (like
* {@link VideoMediaFormatImpl}) (some of) which (could) distinguish payload
* types.
*
* @author Lyubomir Marinov
*/
public class ParameterizedVideoFormat
extends VideoFormat
{
/**
* The format parameters of this <tt>ParameterizedVideoFormat</tt> instance.
*/
private Map<String, String> fmtps;
public ParameterizedVideoFormat(
String encoding,
Dimension size,
int maxDataLength,
Class dataType,
float frameRate,
Map<String, String> fmtps)
{
super(encoding, size, maxDataLength, dataType, frameRate);
this.fmtps
= ((fmtps == null) || fmtps.isEmpty())
? MediaFormatImpl.EMPTY_FORMAT_PARAMETERS
: new HashMap<String, String>(fmtps);
}
/**
* Initializes a new <tt>ParameterizedVideoFormat</tt> with a specific
* encoding and a specific set of format parameters.
*
* @param encoding the encoding of the new instance
* @param fmtps the format parameters of the new instance
*/
public ParameterizedVideoFormat(String encoding, Map<String, String> fmtps)
{
super(encoding);
this.fmtps
= ((fmtps == null) || fmtps.isEmpty())
? MediaFormatImpl.EMPTY_FORMAT_PARAMETERS
: new HashMap<String, String>(fmtps);
}
/**
* Initializes a new <tt>ParameterizedVideoFormat</tt> with a specific
* encoding and a specific set of format parameters.
*
* @param encoding the encoding of the new instance
* @param fmtps the format parameters of the new instance in the form of an
* array of <tt>String</tt>s in which the key and the value of an
* association are expressed as consecutive elements.
*/
public ParameterizedVideoFormat(String encoding, String... fmtps)
{
this(encoding, toMap(fmtps));
}
/**
* Initializes a new <tt>ParameterizedVideoFormat</tt> instance which has
* the same properties as this instance.
*
* @return a new <tt>ParameterizedVideoFormat</tt> instance which has the
* same properties as this instance
*/
@Override
public Object clone()
{
ParameterizedVideoFormat f
= new ParameterizedVideoFormat(
getEncoding(),
getSize(),
getMaxDataLength(),
getDataType(),
getFrameRate(),
/*
* The formatParameters will be copied by
* ParameterizedVideoFormat#copy(Format) bellow.
*/
null);
f.copy(this);
return f;
}
/**
* Copies the properties of the specified <tt>Format</tt> into this
* instance.
*
* @param f the <tt>Format</tt> the properties of which are to be copied
* into this instance
*/
@Override
protected void copy(Format f)
{
super.copy(f);
if (f instanceof ParameterizedVideoFormat)
{
ParameterizedVideoFormat pvf = (ParameterizedVideoFormat) f;
Map<String, String> pvfFmtps = pvf.getFormatParameters();
fmtps
= ((pvfFmtps == null) || pvfFmtps.isEmpty())
? MediaFormatImpl.EMPTY_FORMAT_PARAMETERS
: new HashMap<String, String>(pvfFmtps);
}
}
/**
* Determines whether a specific <tt>Object</tt> represents a value that is
* equal to the value represented by this instance.
*
* @param obj the <tt>Object</tt> to be determined whether it represents a
* value that is equal to the value represented by this instance
* @return <tt>true</tt> if the specified <tt>obj</tt> represents a value
* that is equal to the value represented by this instance; otherwise,
* <tt>false</tt>
*/
@Override
public boolean equals(Object obj)
{
if (!super.equals(obj))
return false;
Map<String, String> objFmtps = null;
if (obj instanceof ParameterizedVideoFormat)
objFmtps = ((ParameterizedVideoFormat) obj).getFormatParameters();
return
VideoMediaFormatImpl.formatParametersAreEqual(
getEncoding(),
getFormatParameters(), objFmtps);
}
public String getFormatParameter(String name)
{
return fmtps.get(name);
}
public Map<String, String> getFormatParameters()
{
return new HashMap<String, String>(fmtps);
}
/**
* Finds the attributes shared by two matching <tt>Format</tt>s. If the
* specified <tt>Format</tt> does not match this one, the result is
* undefined.
*
* @param format the matching <tt>Format</tt> to intersect with this one
* @return a <tt>Format</tt> with its attributes set to the attributes
* common to this instance and the specified <tt>format</tt>
*/
@Override
public Format intersects(Format format)
{
Format intersection = super.intersects(format);
if (intersection == null)
return null;
((ParameterizedVideoFormat) intersection).fmtps
= fmtps.isEmpty()
? MediaFormatImpl.EMPTY_FORMAT_PARAMETERS
: getFormatParameters();
return intersection;
}
/**
* Determines whether a specific format matches this instance i.e. whether
* their attributes match according to the definition of "match" given by
* {@link Format#matches(Format)}.
*
* @param format the <tt>Format</tt> to compare to this instance
* @return <tt>true</tt> if the specified <tt>format</tt> matches this one;
* otherwise, <tt>false</tt>
*/
@Override
public boolean matches(Format format)
{
if (!super.matches(format))
return false;
Map<String, String> formatFmtps = null;
if (format instanceof ParameterizedVideoFormat)
formatFmtps
= ((ParameterizedVideoFormat) format).getFormatParameters();
return
VideoMediaFormatImpl.formatParametersMatch(
getEncoding(),
getFormatParameters(), formatFmtps);
}
/**
* Initializes a new <tt>Map</tt> from an array in which the key and the
* value of an association are expressed as consecutive elements.
*
* @param <T> the very type of the keys and the values to be associated in
* the new <tt>Map</tt>
* @param entries the associations to be created in the new <tt>Map</tt>
* where the key and value of an association are expressed as consecutive
* elements
* @return a new <tt>Map</tt> with the associations specified by
* <tt>entries</tt>
*/
public static <T> Map<T, T> toMap(T... entries)
{
Map<T, T> map;
if ((entries == null) || (entries.length == 0))
map = null;
else
{
map = new HashMap<T, T>();
for (int i = 0; i < entries.length; i++)
map.put(entries[i++], entries[i]);
}
return map;
}
}

@ -17,7 +17,7 @@
/**
* Implements <tt>VideoMediaFormat</tt> for the JMF <tt>VideoFormat</tt>.
*
* @author Lubomir Marinov
* @author Lyubomir Marinov
*/
public class VideoMediaFormatImpl
extends MediaFormatImpl<VideoFormat>
@ -150,6 +150,88 @@ public boolean equals(Object mediaFormat)
return (clockRate == videoMediaFormatImplClockRate);
}
/**
* {@inheritDoc}
*
* @see MediaFormatImpl#formatParametersAreEqual(Map, Map)
*/
@Override
protected boolean formatParametersAreEqual(
Map<String, String> fmtps1,
Map<String, String> fmtps2)
{
return formatParametersAreEqual(getEncoding(), fmtps1, fmtps2);
}
/**
* Determines whether a specific set of format parameters is equal to
* another set of format parameters in the sense that they define an equal
* number of parameters and assign them equal values. Since the values are
* <tt>String</tt>s, presumes that a value of <tt>null</tt> is equal to the
* empty <tt>String</tt>.
* <p>
* The two <tt>Map</tt> instances of format parameters to be checked for
* equality are presumed to be modifiable in the sense that if the lack of a
* format parameter in a given <tt>Map</tt> is equivalent to it having a
* specific value, an association of the format parameter to the value in
* question may be added to or removed from the respective <tt>Map</tt>
* instance for the purposes of determining equality.
* </p>
*
* @param encoding the encoding (name) related to the two sets of format
* parameters to be tested for equality
* @param fmtps1 the first set of format parameters to be tested for
* equality
* @param fmtps2 the second set of format parameters to be tested for
* equality
* @return <tt>true</tt> if the specified sets of format parameters are
* equal; <tt>false</tt>, otherwise
*/
public static boolean formatParametersAreEqual(
String encoding,
Map<String, String> fmtps1, Map<String, String> fmtps2)
{
return
MediaFormatImpl.formatParametersAreEqual(encoding, fmtps1, fmtps2);
}
/**
* Determines whether the format parameters of this <tt>MediaFormat</tt>
* match a specific set of format parameters.
*
* @param fmtps the set of format parameters to match to the format
* parameters of this <tt>MediaFormat</tt>
* @return <tt>true</tt> if this <tt>MediaFormat</tt> considers
* <tt>fmtps</tt> matching its format parameters; otherwise, <tt>false</tt>
*/
@Override
public boolean formatParametersMatch(Map<String, String> fmtps)
{
return
formatParametersMatch(getEncoding(), getFormatParameters(), fmtps)
&& super.formatParametersMatch(fmtps);
}
/**
* Determines whether two sets of format parameters match in the context of
* a specific encoding.
*
* @param encoding the encoding (name) related to the two sets of format
* parameters to be matched.
* @param fmtps1 the first set of format parameters which is to be matched
* against <tt>fmtps2</tt>
* @param fmtps2 the second set of format parameters which is to be matched
* against <tt>fmtps1</tt>
* @return <tt>true</tt> if the two sets of format parameters match in the
* context of the specified <tt>encoding</tt>; otherwise, <tt>false</tt>
*/
public static boolean formatParametersMatch(
String encoding,
Map<String, String> fmtps1 , Map<String, String> fmtps2)
{
return true;
}
/**
* Gets the clock rate associated with this <tt>MediaFormat</tt>.
*

@ -65,6 +65,12 @@ public abstract class AbstractBufferCaptureDevice
*/
private FormatControl[] formatControls;
/**
* The <tt>FrameRateControl</tt>s of this
* <tt>AbstractBufferCaptureDevice</tt>.
*/
private FrameRateControl[] frameRateControls;
/**
* The indicator which determines whether the transfer of media data from
* this <tt>DataSource</tt> has been started.
@ -205,6 +211,20 @@ protected FormatControl[] createFormatControls()
: new FormatControl[] { formatControl };
}
/**
* Creates a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractBufferCaptureDevice</tt>.
*
* @return a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractBufferCaptureDevice</tt>
*/
protected FrameRateControl createFrameRateControl()
{
return null;
}
/**
* Create a new <tt>AbstractBufferStream</tt> which is to be at a specific
* zero-based index in the list of streams of this
@ -269,19 +289,27 @@ final void defaultDoStop()
final Object[] defaultGetControls()
{
FormatControl[] formatControls = internalGetFormatControls();
FrameRateControl[] frameRateControls = internalGetFrameRateControls();
if ((formatControls == null) || (formatControls.length == 0))
if (((formatControls == null) || (formatControls.length == 0))
&& ((frameRateControls == null)
|| (frameRateControls.length == 0)))
return ControlsAdapter.EMPTY_CONTROLS;
else
{
Object[] controls = new Object[formatControls.length];
Object[] controls
= new Object[formatControls.length + frameRateControls.length];
int offset = 0;
System.arraycopy(
formatControls,
0,
controls,
0,
formatControls, 0,
controls, offset,
formatControls.length);
offset += formatControls.length;
System.arraycopy(
frameRateControls, 0,
controls, offset,
frameRateControls.length);
return controls;
}
}
@ -636,6 +664,30 @@ private synchronized FormatControl[] internalGetFormatControls()
return formatControls;
}
/**
* Gets an array of <tt>FrameRateControl</tt> instances which can be used to
* get and/or set the output frame rate of this
* <tt>AbstractBufferCaptureDevice</tt>.
*
* @return an array of <tt>FrameRateControl</tt> instances which can be used
* to get and/or set the output frame rate of this
* <tt>AbstractBufferCaptureDevice</tt>.
*/
private synchronized FrameRateControl[] internalGetFrameRateControls()
{
if (frameRateControls == null)
{
FrameRateControl frameRateControl = createFrameRateControl();
// Don't try to create the FrameRateControl more than once.
frameRateControls
= (frameRateControl == null)
? new FrameRateControl[0]
: new FrameRateControl[] { frameRateControl };
}
return frameRateControls;
}
/**
* Attempts to set the <tt>Format</tt> to be reported by the
* <tt>FormatControl</tt> of a <tt>PushBufferStream</tt> at a specific

@ -35,72 +35,80 @@ public abstract class AbstractPullBufferCaptureDevice
*/
private final AbstractBufferCaptureDevice<AbstractPullBufferStream> impl
= new AbstractBufferCaptureDevice<AbstractPullBufferStream>()
{
protected AbstractPullBufferStream createStream(
int streamIndex,
FormatControl formatControl)
{
return
AbstractPullBufferCaptureDevice.this.createStream(
streamIndex,
formatControl);
}
protected void doConnect()
throws IOException
{
AbstractPullBufferCaptureDevice.this.doConnect();
}
protected void doDisconnect()
{
AbstractPullBufferCaptureDevice.this.doDisconnect();
}
protected void doStart()
throws IOException
{
AbstractPullBufferCaptureDevice.this.doStart();
}
protected void doStop()
throws IOException
{
AbstractPullBufferCaptureDevice.this.doStop();
}
public CaptureDeviceInfo getCaptureDeviceInfo()
{
return
AbstractPullBufferCaptureDevice.this
.getCaptureDeviceInfo();
}
protected Format getFormat(int streamIndex, Format oldValue)
{
return
AbstractPullBufferCaptureDevice.this.getFormat(
streamIndex,
oldValue);
}
protected Format[] getSupportedFormats(int streamIndex)
{
return
AbstractPullBufferCaptureDevice.this
.getSupportedFormats(streamIndex);
}
protected Format setFormat(
int streamIndex,
Format oldValue, Format newValue)
{
return
AbstractPullBufferCaptureDevice.this.setFormat(
streamIndex,
oldValue, newValue);
}
};
{
@Override
protected FrameRateControl createFrameRateControl()
{
return
AbstractPullBufferCaptureDevice.this
.createFrameRateControl();
}
protected AbstractPullBufferStream createStream(
int streamIndex,
FormatControl formatControl)
{
return
AbstractPullBufferCaptureDevice.this.createStream(
streamIndex,
formatControl);
}
protected void doConnect()
throws IOException
{
AbstractPullBufferCaptureDevice.this.doConnect();
}
protected void doDisconnect()
{
AbstractPullBufferCaptureDevice.this.doDisconnect();
}
protected void doStart()
throws IOException
{
AbstractPullBufferCaptureDevice.this.doStart();
}
protected void doStop()
throws IOException
{
AbstractPullBufferCaptureDevice.this.doStop();
}
public CaptureDeviceInfo getCaptureDeviceInfo()
{
return
AbstractPullBufferCaptureDevice.this
.getCaptureDeviceInfo();
}
protected Format getFormat(int streamIndex, Format oldValue)
{
return
AbstractPullBufferCaptureDevice.this.getFormat(
streamIndex,
oldValue);
}
protected Format[] getSupportedFormats(int streamIndex)
{
return
AbstractPullBufferCaptureDevice.this
.getSupportedFormats(streamIndex);
}
protected Format setFormat(
int streamIndex,
Format oldValue, Format newValue)
{
return
AbstractPullBufferCaptureDevice.this.setFormat(
streamIndex,
oldValue, newValue);
}
};
/**
* Initializes a new <tt>AbstractPullBufferCaptureDevice</tt> instance.
@ -134,6 +142,20 @@ public void connect()
impl.connect();
}
/**
* Creates a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractPullBufferCaptureDevice</tt>.
*
* @return a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractPullBufferCaptureDevice</tt>
*/
protected FrameRateControl createFrameRateControl()
{
return null;
}
/**
* Creates a new <tt>PullBufferStream</tt> which is to be at a specific
* zero-based index in the list of streams of this

@ -6,6 +6,7 @@
*/
package net.java.sip.communicator.impl.neomedia.jmfext.media.protocol;
import javax.media.*;
import javax.media.control.*;
import javax.media.protocol.*;
@ -39,7 +40,7 @@ protected AbstractPullBufferStream(
}
/**
* Determines if read will block.
* Determines if {@link #read(Buffer)} will block.
*
* @return <tt>true</tt> if read block, <tt>false</tt> otherwise
*/

@ -29,87 +29,93 @@ public abstract class AbstractPushBufferCaptureDevice
*/
private final AbstractBufferCaptureDevice<AbstractPushBufferStream> impl
= new AbstractBufferCaptureDevice<AbstractPushBufferStream>()
{
protected AbstractPushBufferStream createStream(
int streamIndex,
FormatControl formatControl)
{
return
AbstractPushBufferCaptureDevice.this.createStream(
streamIndex,
formatControl);
}
protected void doConnect()
throws IOException
{
AbstractPushBufferCaptureDevice.this.doConnect();
}
protected void doDisconnect()
{
AbstractPushBufferCaptureDevice.this.doDisconnect();
}
protected void doStart()
throws IOException
{
AbstractPushBufferCaptureDevice.this.doStart();
}
protected void doStop()
throws IOException
{
AbstractPushBufferCaptureDevice.this.doStop();
}
public CaptureDeviceInfo getCaptureDeviceInfo()
{
return
AbstractPushBufferCaptureDevice.this
.getCaptureDeviceInfo();
}
/**
* Overrides
* {@link AbstractBufferCaptureDevice#getControls()} to add
* controls specific to this
* <tt>AbstractPushBufferCaptureDevice</tt>.
*
* {@inheritDoc}
*/
@Override
public Object[] getControls()
{
return
AbstractPushBufferCaptureDevice.this.getControls();
}
protected Format getFormat(int streamIndex, Format oldValue)
{
return
AbstractPushBufferCaptureDevice.this.getFormat(
streamIndex,
oldValue);
}
protected Format[] getSupportedFormats(int streamIndex)
{
return
AbstractPushBufferCaptureDevice.this
.getSupportedFormats(streamIndex);
}
protected Format setFormat(
int streamIndex,
Format oldValue, Format newValue)
{
return
AbstractPushBufferCaptureDevice.this.setFormat(
streamIndex,
oldValue, newValue);
}
};
{
@Override
protected FrameRateControl createFrameRateControl()
{
return
AbstractPushBufferCaptureDevice.this
.createFrameRateControl();
}
protected AbstractPushBufferStream createStream(
int streamIndex,
FormatControl formatControl)
{
return
AbstractPushBufferCaptureDevice.this.createStream(
streamIndex,
formatControl);
}
protected void doConnect()
throws IOException
{
AbstractPushBufferCaptureDevice.this.doConnect();
}
protected void doDisconnect()
{
AbstractPushBufferCaptureDevice.this.doDisconnect();
}
protected void doStart()
throws IOException
{
AbstractPushBufferCaptureDevice.this.doStart();
}
protected void doStop()
throws IOException
{
AbstractPushBufferCaptureDevice.this.doStop();
}
public CaptureDeviceInfo getCaptureDeviceInfo()
{
return
AbstractPushBufferCaptureDevice.this
.getCaptureDeviceInfo();
}
/**
* Overrides {@link AbstractBufferCaptureDevice#getControls()} to
* add controls specific to this
* <tt>AbstractPushBufferCaptureDevice</tt>.
*
* {@inheritDoc}
*/
@Override
public Object[] getControls()
{
return AbstractPushBufferCaptureDevice.this.getControls();
}
protected Format getFormat(int streamIndex, Format oldValue)
{
return
AbstractPushBufferCaptureDevice.this.getFormat(
streamIndex,
oldValue);
}
protected Format[] getSupportedFormats(int streamIndex)
{
return
AbstractPushBufferCaptureDevice.this
.getSupportedFormats(streamIndex);
}
protected Format setFormat(
int streamIndex,
Format oldValue, Format newValue)
{
return
AbstractPushBufferCaptureDevice.this.setFormat(
streamIndex,
oldValue, newValue);
}
};
/**
* Initializes a new <tt>AbstractPushBufferCaptureDevice</tt> instance.
@ -143,6 +149,20 @@ public void connect()
impl.connect();
}
/**
* Creates a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractPushBufferCaptureDevice</tt>.
*
* @return a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractPushBufferCaptureDevice</tt>
*/
protected FrameRateControl createFrameRateControl()
{
return null;
}
/**
* Create a new <tt>PushBufferStream</tt> which is to be at a specific
* zero-based index in the list of streams of this

@ -0,0 +1,79 @@
/*
* Jitsi, 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.neomedia.jmfext.media.protocol;
import javax.media.*;
import javax.media.control.*;
import net.java.sip.communicator.impl.neomedia.control.*;
/**
* Provides a base implementation of <tt>PullBufferDataSource</tt> and
* <tt>CaptureDevice</tt> for the purposes of video in order to facilitate
* implementers by taking care of boilerplate in the most common cases.
*
* @author Lyubomir Marinov
*/
public abstract class AbstractVideoPullBufferCaptureDevice
extends AbstractPullBufferCaptureDevice
{
/**
* Initializes a new <tt>AbstractVideoPullBufferCaptureDevice</tt> instance.
*/
protected AbstractVideoPullBufferCaptureDevice()
{
}
/**
* Initializes a new <tt>AbstractVideoPullBufferCaptureDevice</tt> instance
* from a specific <tt>MediaLocator</tt>.
*
* @param locator the <tt>MediaLocator</tt> to create the new instance from
*/
protected AbstractVideoPullBufferCaptureDevice(MediaLocator locator)
{
super(locator);
}
/**
* Creates a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractVideoPullBufferCaptureDevice</tt>.
*
* @return a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractVideoPullBufferCaptureDevice</tt>
* @see AbstractPullBufferCaptureDevice#createFrameRateControl()
*/
@Override
protected FrameRateControl createFrameRateControl()
{
return
new FrameRateControlAdapter()
{
/**
* The output frame rate of this
* <tt>AbstractVideoPullBufferCaptureDevice</tt>.
*/
private float frameRate = -1;
@Override
public float getFrameRate()
{
return frameRate;
}
@Override
public float setFrameRate(float frameRate)
{
this.frameRate = frameRate;
return this.frameRate;
}
};
}
}

@ -0,0 +1,182 @@
/*
* Jitsi, 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.neomedia.jmfext.media.protocol;
import java.io.*;
import javax.media.*;
import javax.media.control.*;
import javax.media.protocol.*;
/**
* Provides a base implementation of <tt>PullBufferStream</tt> for video in
* order to facilitate implementers by taking care of boilerplate in the most
* common cases.
*
* @author Lyubomir Marinov
*/
public abstract class AbstractVideoPullBufferStream
extends AbstractPullBufferStream
{
/**
* The output frame rate of this <tt>AbstractVideoPullBufferStream</tt>
* which has been specified by {@link #frameRateControl} and depending on
* which {@link #minimumVideoFrameInterval} has been calculated.
*/
private float frameRate;
/**
* The <tt>FrameRateControl</tt> which gets and sets the output frame rate
* of this <tt>AbstractVideoPullBufferStream</tt>.
*/
private FrameRateControl frameRateControl;
/**
* The minimum interval in milliseconds between consecutive video frames
* i.e. the reverse of {@link #frameRate}.
*/
private long minimumVideoFrameInterval;
/**
* Initializes a new <tt>AbstractVideoPullBufferStream</tt> instance which
* is to have its <tt>Format</tt>-related information abstracted by a
* specific <tt>FormatControl</tt>.
*
* @param dataSource the <tt>PullBufferDataSource</tt> which is creating the
* new instance so that it becomes one of its <tt>streams</tt>
* @param formatControl the <tt>FormatControl</tt> which is to abstract the
* <tt>Format</tt>-related information of the new instance
*/
protected AbstractVideoPullBufferStream(
PullBufferDataSource dataSource,
FormatControl formatControl)
{
super(dataSource, formatControl);
}
/**
* Blocks and reads into a <tt>Buffer</tt> from this
* <tt>PullBufferStream</tt>.
*
* @param buffer the <tt>Buffer</tt> this <tt>PullBufferStream</tt> is to
* read into
* @throws IOException if an I/O error occurs while this
* <tt>PullBufferStream</tt> reads into the specified <tt>Buffer</tt>
*/
protected abstract void doRead(Buffer buffer)
throws IOException;
/**
* Blocks and reads into a <tt>Buffer</tt> from this
* <tt>PullBufferStream</tt>.
*
* @param buffer the <tt>Buffer</tt> this <tt>PullBufferStream</tt> is to
* read into
* @throws IOException if an I/O error occurs while this
* <tt>PullBufferStream</tt> reads into the specified <tt>Buffer</tt>
*/
public void read(Buffer buffer)
throws IOException
{
FrameRateControl frameRateControl = this.frameRateControl;
if (frameRateControl != null)
{
float frameRate = frameRateControl.getFrameRate();
if (frameRate > 0)
{
if (this.frameRate != frameRate)
{
minimumVideoFrameInterval = (long) (1000 / frameRate);
this.frameRate = frameRate;
}
if (minimumVideoFrameInterval > 0)
{
long startTime = System.currentTimeMillis();
doRead(buffer);
if (!buffer.isDiscard())
{
boolean interrupted = false;
while (true)
{
// Sleep to respect the frame rate as much as possible.
long sleep
= minimumVideoFrameInterval
- (System.currentTimeMillis() - startTime);
if (sleep > 0)
{
try
{
Thread.sleep(sleep);
}
catch (InterruptedException ie)
{
interrupted = true;
}
}
else
{
// Yield a little bit to not use all the whole CPU.
Thread.yield();
break;
}
}
if (interrupted)
Thread.currentThread().interrupt();
}
// We've executed #doRead(Buffer).
return;
}
}
}
// If there is no frame rate to be respected, just #doRead(Buffer).
doRead(buffer);
}
/**
* Starts the transfer of media data from this
* <tt>AbstractBufferStream</tt>.
*
* @throws IOException if anything goes wrong while starting the transfer of
* media data from this <tt>AbstractBufferStream</tt>
* @see AbstractBufferStream#start()
*/
@Override
public void start()
throws IOException
{
super.start();
frameRateControl
= (FrameRateControl)
dataSource.getControl(FrameRateControl.class.getName());
}
/**
* Stops the transfer of media data from this <tt>AbstractBufferStream</tt>.
*
* @throws IOException if anything goes wrong while stopping the transfer of
* media data from this <tt>AbstractBufferStream</tt>
* @see AbstractBufferStream#stop()
*/
@Override
public void stop()
throws IOException
{
super.stop();
frameRateControl = null;
}
}

@ -6,8 +6,6 @@
*/
package net.java.sip.communicator.impl.neomedia.jmfext.media.protocol;
import java.util.*;
import javax.media.*;
import javax.media.control.*;
@ -21,11 +19,6 @@
public abstract class AbstractVideoPushBufferCaptureDevice
extends AbstractPushBufferCaptureDevice
{
/**
* The <tt>FrameRateControl</tt>s of this
* <tt>AbstractVideoPushBufferCaptureDevice</tt>.
*/
private FrameRateControl[] frameRateControls;
/**
* Initializes a new <tt>AbstractVideoPushBufferCaptureDevice</tt> instance.
@ -53,47 +46,11 @@ protected AbstractVideoPushBufferCaptureDevice(MediaLocator locator)
* @return a new <tt>FrameRateControl</tt> instance which is to allow the
* getting and setting of the frame rate of this
* <tt>AbstractVideoPushBufferCaptureDevice</tt>
* @see AbstractPushBufferCaptureDevice#createFrameRateControl()
*/
@Override
protected FrameRateControl createFrameRateControl()
{
return null;
}
/**
* Implements {@link javax.media.protocol.DataSource#getControls()}. Gets
* the controls available for this instance.
*
* @return an array of <tt>Object</tt>s which represent the controls
* available for this instance
*/
@Override
public Object[] getControls()
{
List<Object> controls = new ArrayList<Object>();
Collections.addAll(controls, super.getControls());
/*
* Add any FrameRateControl that this AbstractPushBufferCaptureDevice
* may want to have.
*/
synchronized (this)
{
if (frameRateControls == null)
{
FrameRateControl frameRateControl = createFrameRateControl();
// Don't try to create the FrameRateControl more than once.
if (frameRateControl == null)
frameRateControls = new FrameRateControl[0];
else
frameRateControls
= new FrameRateControl[] { frameRateControl };
}
if (frameRateControls != null)
Collections.addAll(controls, frameRateControls);
}
return controls.toArray();
}
}

@ -23,7 +23,6 @@ public class DataSource
extends PushBufferDataSource
{
protected boolean started = false;
protected String contentType = "raw";
protected boolean connected = false;
protected Time duration = DURATION_UNKNOWN;
protected AlsaStream [] streams = null;
@ -37,18 +36,15 @@ public DataSource()
}
/**
* Tell we are a raw datasource
* Gets the type of the content made available by this <tt>DataSource</tt>
* (i.e. raw).
*
* @return "raw"
* @return the type of the content made available by this
* <tt>DataSource</tt> (i.e. raw)
*/
public String getContentType()
{
if (!connected)
{
System.err.println("Error: DataSource not connected");
return null;
}
return contentType;
return ContentDescriptor.RAW;
}
/**

@ -20,7 +20,7 @@
* @author Damian Minkov
*/
public class DataSource
extends AbstractPullBufferCaptureDevice
extends AbstractVideoPullBufferCaptureDevice
{
/**
* Stream created.

@ -28,7 +28,7 @@
* @author Damian Minkov
*/
public class ImageStream
extends AbstractPullBufferStream
extends AbstractVideoPullBufferStream
{
/**
* The <tt>Logger</tt>
@ -85,20 +85,18 @@ public class ImageStream
}
/**
* Block and read a buffer from the stream.
* Blocks and reads into a <tt>Buffer</tt> from this
* <tt>PullBufferStream</tt>.
*
* @param buffer the <tt>Buffer</tt> to read captured media into
* @throws IOException if an error occurs while reading.
* @param buffer the <tt>Buffer</tt> this <tt>PullBufferStream</tt> is to
* read into
* @throws IOException if an I/O error occurs while this
* <tt>PullBufferStream</tt> reads into the specified <tt>Buffer</tt>
* @see AbstractVideoPullBufferStream#doRead(Buffer)
*/
public void read(Buffer buffer)
protected void doRead(Buffer buffer)
throws IOException
{
//System.out.println(System.currentTimeMillis());
long begin = System.currentTimeMillis();
/* maximum time allowed for a capture to respect frame rate */
long maxTime = 1000 / 10;
int wait = 0;
/*
* Determine the Format in which we're expected to output. We cannot
* rely on the Format always being specified in the Buffer because it is
@ -116,8 +114,9 @@ public void read(Buffer buffer)
if(bufferFormat instanceof AVFrameFormat)
{
/* native transfert: we keep data in native memory rather
* than Java Heap until we reach SwScaler
/*
* Native transfer: we keep data in native memory rather than the
* Java heap until we reach SwScaler.
*/
Object dataAv = buffer.getData();
AVFrame bufferFrame = null;
@ -184,29 +183,6 @@ public void read(Buffer buffer)
buffer.setSequenceNumber(seqNo);
buffer.setFlags(Buffer.FLAG_SYSTEM_TIME | Buffer.FLAG_LIVE_DATA);
seqNo++;
wait = (int)(maxTime - (System.currentTimeMillis() - begin));
try
{
/* sleep to respect as much as possible the
* frame rate
*/
if(wait > 0)
{
Thread.sleep(wait);
}
else
{
/* yield a little bit to not use all the
* CPU
*/
Thread.yield();
}
}
catch(Exception e)
{
}
}
/**

@ -144,6 +144,7 @@ protected Format doGetFormat()
* @throws IOException if anything goes wrong while reading media data from
* this <tt>PullBufferStream</tt> into the specified <tt>buffer</tt>
*/
@Override
public void read(Buffer buffer)
throws IOException
{

@ -22,7 +22,7 @@
* @author Lyubomir Marinov
*/
public class DataSource
extends AbstractPullBufferCaptureDevice
extends AbstractVideoPullBufferCaptureDevice
{
/**
* The map of Video for Linux Two API Specification pixel formats to FFmpeg

@ -25,7 +25,7 @@
* @author Lyubomir Marinov
*/
public class Video4Linux2Stream
extends AbstractPullBufferStream
extends AbstractVideoPullBufferStream
{
/**
* The pool of <tt>ByteBuffer</tt>s this instances is using to transfer the
@ -188,6 +188,124 @@ protected Format doGetFormat()
return format;
}
/**
* Reads media data from this <tt>PullBufferStream</tt> into a specific
* <tt>Buffer</tt> with blocking.
*
* @param buffer the <tt>Buffer</tt> in which media data is to be read from
* this <tt>PullBufferStream</tt>
* @throws IOException if anything goes wrong while reading media data from
* this <tt>PullBufferStream</tt> into the specified <tt>buffer</tt>
* @see AbstractVideoPullBufferStream#doRead(Buffer)
*/
protected void doRead(Buffer buffer)
throws IOException
{
Format format = buffer.getFormat();
if (!(format instanceof AVFrameFormat))
format = null;
if (format == null)
{
format = getFormat();
if (format != null)
buffer.setFormat(format);
}
if(startInRead)
{
startInRead = false;
long v4l2_buf_type
= Video4Linux2.v4l2_buf_type_alloc(
Video4Linux2.V4L2_BUF_TYPE_VIDEO_CAPTURE);
if (0 == v4l2_buf_type)
throw new OutOfMemoryError("v4l2_buf_type_alloc");
try
{
if (Video4Linux2.ioctl(fd, Video4Linux2.VIDIOC_STREAMON,
v4l2_buf_type) == -1)
{
throw new IOException("ioctl: request= VIDIOC_STREAMON");
}
}
finally
{
Video4Linux2.free(v4l2_buf_type);
}
}
if (Video4Linux2.ioctl(fd, Video4Linux2.VIDIOC_DQBUF, v4l2_buffer)
== -1)
throw new IOException("ioctl: request= VIDIOC_DQBUF");
long timeStamp = System.nanoTime();
try
{
ByteBuffer data = null;
int index = Video4Linux2.v4l2_buffer_getIndex(v4l2_buffer);
long mmap = mmaps[index];
int bytesused = Video4Linux2.v4l2_buffer_getBytesused(v4l2_buffer);
if(nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_MJPEG ||
nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_JPEG)
{
/* initialize FFmpeg's MJPEG decoder if not already done */
if(mjpeg_context == 0)
{
long avcodec = FFmpeg.avcodec_find_decoder(
FFmpeg.CODEC_ID_MJPEG);
mjpeg_context = FFmpeg.avcodec_alloc_context();
FFmpeg.avcodeccontext_set_workaround_bugs(mjpeg_context,
FFmpeg.FF_BUG_AUTODETECT);
if (FFmpeg.avcodec_open(mjpeg_context, avcodec) < 0)
{
throw new RuntimeException("" +
"Could not open codec CODEC_ID_MJPEG");
}
avframe = FFmpeg.avcodec_alloc_frame();
}
if(FFmpeg.avcodec_decode_video(mjpeg_context, avframe, mmap,
bytesused) != -1)
{
Object out = buffer.getData();
if (!(out instanceof AVFrame) ||
(((AVFrame) out).getPtr() != avframe))
{
buffer.setData(new AVFrame(avframe));
}
}
}
else
{
data = byteBufferPool.getFreeBuffer(bytesused);
if (data != null)
{
Video4Linux2.memcpy(data.ptr, mmap, bytesused);
}
data.setLength(bytesused);
FinalizableAVFrame.read(buffer, format, data, byteBufferPool);
}
}
finally
{
if (Video4Linux2.ioctl(fd, Video4Linux2.VIDIOC_QBUF, v4l2_buffer)
== -1)
throw new IOException("ioctl: request= VIDIOC_QBUF");
}
buffer.setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME);
buffer.setTimeStamp(timeStamp);
}
/**
* Gets the <tt>Format</tt> of the media data captured by the Video for
* Linux Two API Specification device represented by the <tt>fd</tt> of this
@ -422,124 +540,6 @@ private void negotiateFdInputMethod()
}
}
/**
* Reads media data from this <tt>PullBufferStream</tt> into a specific
* <tt>Buffer</tt> with blocking.
*
* @param buffer the <tt>Buffer</tt> in which media data is to be read from
* this <tt>PullBufferStream</tt>
* @throws IOException if anything goes wrong while reading media data from
* this <tt>PullBufferStream</tt> into the specified <tt>buffer</tt>
*/
public void read(Buffer buffer)
throws IOException
{
Format format = buffer.getFormat();
if (!(format instanceof AVFrameFormat))
format = null;
if (format == null)
{
format = getFormat();
if (format != null)
buffer.setFormat(format);
}
if(startInRead)
{
startInRead = false;
long v4l2_buf_type
= Video4Linux2.v4l2_buf_type_alloc(
Video4Linux2.V4L2_BUF_TYPE_VIDEO_CAPTURE);
if (0 == v4l2_buf_type)
throw new OutOfMemoryError("v4l2_buf_type_alloc");
try
{
if (Video4Linux2.ioctl(fd, Video4Linux2.VIDIOC_STREAMON,
v4l2_buf_type) == -1)
{
throw new IOException("ioctl: request= VIDIOC_STREAMON");
}
}
finally
{
Video4Linux2.free(v4l2_buf_type);
}
}
if (Video4Linux2.ioctl(fd, Video4Linux2.VIDIOC_DQBUF, v4l2_buffer)
== -1)
throw new IOException("ioctl: request= VIDIOC_DQBUF");
long timeStamp = System.nanoTime();
try
{
ByteBuffer data = null;
int index = Video4Linux2.v4l2_buffer_getIndex(v4l2_buffer);
long mmap = mmaps[index];
int bytesused = Video4Linux2.v4l2_buffer_getBytesused(v4l2_buffer);
if(nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_MJPEG ||
nativePixelFormat == Video4Linux2.V4L2_PIX_FMT_JPEG)
{
/* initialize FFmpeg's MJPEG decoder if not already done */
if(mjpeg_context == 0)
{
long avcodec = FFmpeg.avcodec_find_decoder(
FFmpeg.CODEC_ID_MJPEG);
mjpeg_context = FFmpeg.avcodec_alloc_context();
FFmpeg.avcodeccontext_set_workaround_bugs(mjpeg_context,
FFmpeg.FF_BUG_AUTODETECT);
if (FFmpeg.avcodec_open(mjpeg_context, avcodec) < 0)
{
throw new RuntimeException("" +
"Could not open codec CODEC_ID_MJPEG");
}
avframe = FFmpeg.avcodec_alloc_frame();
}
if(FFmpeg.avcodec_decode_video(mjpeg_context, avframe, mmap,
bytesused) != -1)
{
Object out = buffer.getData();
if (!(out instanceof AVFrame) ||
(((AVFrame) out).getPtr() != avframe))
{
buffer.setData(new AVFrame(avframe));
}
}
}
else
{
data = byteBufferPool.getFreeBuffer(bytesused);
if (data != null)
{
Video4Linux2.memcpy(data.ptr, mmap, bytesused);
}
data.setLength(bytesused);
FinalizableAVFrame.read(buffer, format, data, byteBufferPool);
}
}
finally
{
if (Video4Linux2.ioctl(fd, Video4Linux2.VIDIOC_QBUF, v4l2_buffer)
== -1)
throw new IOException("ioctl: request= VIDIOC_QBUF");
}
buffer.setFlags(Buffer.FLAG_LIVE_DATA | Buffer.FLAG_SYSTEM_TIME);
buffer.setTimeStamp(timeStamp);
}
/**
* Sets the file descriptor of the Video for Linux Two API Specification
* device which is to be read through this <tt>PullBufferStream</tt>.

@ -1,265 +0,0 @@
/*
* 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.neomedia.videoflip;
import javax.media.*;
import javax.media.format.*;
import net.java.sip.communicator.impl.neomedia.control.*;
import net.java.sip.communicator.impl.neomedia.codec.*;
import net.java.sip.communicator.impl.neomedia.codec.video.*;
import net.java.sip.communicator.util.*;
/**
* JMF Effect to flip local video horizontally.
*
* @author Sebastien Vincent
*/
public class VideoFlipEffect
extends ControlsAdapter
implements Effect
{
/**
* The <tt>Logger</tt> used by the <tt>SwScaler</tt> class and its instances
* for logging output.
*/
private static final Logger logger = Logger
.getLogger(VideoFlipEffect.class);
/**
* List of supported input formats.
*/
private final static Format DEFAULT_INPUT_FORMATS[]
= new Format[]
{
new AVFrameFormat(),
};
/**
* List of supported output formats.
*/
private final static Format DEFAULT_OUTPUT_FORMATS[]
= new Format[]
{
new AVFrameFormat(),
};
/**
* Name of the effect.
*/
private static final String PLUGIN_NAME = "Local video flip Effect";
/**
* Input Format
*/
private VideoFormat inputFormat = null;
/**
* Output Format
*/
private VideoFormat outputFormat = null;
/**
* AVFilterGraph reference.
*/
private long graph = 0;
/**
* AVInputStream reference.
*/
private long inputstream = 0;
/**
* AVFrame reference.
*/
private long avframe = 0;
/**
* If the filters are configured.
*/
private boolean configured = false;
/**
* Constructor.
*/
public VideoFlipEffect()
{
}
/**
* Lists all of the input formats that this codec accepts.
*
* @return An array that contains the supported input <tt>Formats</tt>.
*/
public Format[] getSupportedInputFormats()
{
return DEFAULT_INPUT_FORMATS;
}
/**
* Lists the output formats that this codec can generate.
*
* @param input The <tt>Format</tt> of the data to be used as input to the
* plug-in.
* @return An array that contains the supported output <tt>Formats</tt>.
*/
public Format[] getSupportedOutputFormats(Format input)
{
if(input == null)
{
return DEFAULT_OUTPUT_FORMATS;
}
return new Format[]
{
(Format)input.clone(),
};
}
/**
* Sets the format of the data to be input to this codec.
*
* @param format The <tt>Format</tt> to be set.
* @return The <tt>Format</tt> that was set.
*/
public Format setInputFormat(Format format)
{
inputFormat = (VideoFormat)format;
return inputFormat;
}
/**
* Sets the format for the data this codec outputs.
*
* @param format The <tt>Format</tt> to be set.
* @return The <tt>Format</tt> that was set.
*/
public Format setOutputFormat(Format format)
{
outputFormat = (VideoFormat)format;
return outputFormat;
}
/**
* Opens this effect.
*
* @throws ResourceUnavailableException If all of the required resources
* cannot be acquired.
*/
public synchronized void open() throws ResourceUnavailableException
{
graph = FFmpeg.avfilter_alloc_filtergraph();
inputstream = FFmpeg.avfilter_alloc_inputstream();
avframe = FFmpeg.avcodec_alloc_frame();
}
/**
* Closes this effect.
*/
public synchronized void close()
{
if(graph != 0)
{
FFmpeg.avfilter_free_filtergraph(graph);
}
if(inputstream != 0)
{
FFmpeg.avfilter_free_inputstream(inputstream);
}
if(avframe != 0)
{
FFmpeg.av_free(avframe);
avframe = 0;
}
configured = false;
}
/**
* Gets the name of this plug-in as a human-readable string.
*
* @return A <tt>String</tt> that contains the descriptive name of the
* plug-in.
*/
public String getName()
{
return PLUGIN_NAME;
}
/**
* Performs the media processing defined by this codec.
*
* @param inputBuffer The <tt>Buffer</tt> that contains the media data to be
* processed.
* @param outputBuffer The <tt>Buffer</tt> in which to store the processed
* media data.
* @return <tt>BUFFER_PROCESSED_OK</tt> if the processing is successful.
* @see PlugIn
*/
public synchronized int process(Buffer inputBuffer, Buffer outputBuffer)
{
int ret = 0;
if(inputBuffer.getFormat() instanceof AVFrameFormat)
{
if(!configured)
{
AVFrameFormat format = (AVFrameFormat)inputBuffer.getFormat();
if(FFmpeg.avfilter_configure_filters("hflip", inputstream,
format.getPixFmt(), format.getSize().width,
format.getSize().height, graph) != 0)
{
if(logger.isDebugEnabled())
{
logger.debug(
"Failed to configure libavfilter's filters");
}
return BUFFER_PROCESSED_FAILED;
}
configured = true;
}
AVFrame frame = (AVFrame)inputBuffer.getData();
FFmpeg.av_vsrc_buffer_add_frame(inputstream, frame.getPtr());
if(FFmpeg.av_get_filtered_video_frame(inputstream, avframe) != -1)
{
Object out = outputBuffer.getData();
if (!(out instanceof AVFrame) ||
(((AVFrame) out).getPtr() != avframe))
{
outputBuffer.setData(new AVFrame(avframe));
}
}
}
if(ret == BUFFER_PROCESSED_OK)
{
outputBuffer.setLength(inputBuffer.getLength());
outputBuffer.setFormat(inputBuffer.getFormat());
outputBuffer.setHeader(inputBuffer.getHeader());
outputBuffer.setSequenceNumber(inputBuffer.getSequenceNumber());
outputBuffer.setTimeStamp(inputBuffer.getTimeStamp());
outputBuffer.setFlags(inputBuffer.getFlags());
outputBuffer.setDiscard(inputBuffer.isDiscard());
outputBuffer.setEOM(inputBuffer.isEOM());
outputBuffer.setDuration(inputBuffer.getDuration());
}
return ret;
}
/**
* Resets effect state.
*/
public void reset()
{
}
}

@ -1127,8 +1127,11 @@ private void sendReInvite(String sdpOffer)
try
{
// Content-Type
invite.setContent(sdpOffer, getProtocolProvider().getHeaderFactory()
.createContentTypeHeader("application", "sdp"));
invite.setContent(
sdpOffer,
getProtocolProvider()
.getHeaderFactory()
.createContentTypeHeader("application", "sdp"));
/*
* If the local peer represented by the Call of this CallPeer is
@ -1140,12 +1143,14 @@ private void sendReInvite(String sdpOffer)
catch (ParseException ex)
{
ProtocolProviderServiceSipImpl.throwOperationFailedException(
"Failed to parse SDP offer for the new invite.",
OperationFailedException.INTERNAL_ERROR, ex, logger);
"Failed to parse SDP offer for the new invite.",
OperationFailedException.INTERNAL_ERROR,
ex,
logger);
}
getProtocolProvider().sendInDialogRequest(
getJainSipProvider(), invite, dialog);
getJainSipProvider(), invite, dialog);
}
/**
@ -1173,8 +1178,9 @@ public void invite()
.getHeaderFactory()
.createContentTypeHeader("application", "sdp");
invite
.setContent(getMediaHandler().createOffer(), contentTypeHeader);
invite.setContent(
getMediaHandler().createOffer(),
contentTypeHeader);
/*
* If the local peer represented by the Call of this CallPeer is
@ -1190,8 +1196,10 @@ public void invite()
catch (Exception ex)
{
ProtocolProviderServiceSipImpl.throwOperationFailedException(
"An error occurred while sending invite request",
OperationFailedException.NETWORK_FAILURE, ex, logger);
"An error occurred while sending invite request",
OperationFailedException.NETWORK_FAILURE,
ex,
logger);
}
}

@ -199,9 +199,11 @@ public CallPeerSipImpl invite(Address calleeAddress,
public void reInvite() throws OperationFailedException
{
Iterator<CallPeerSipImpl> peers = getCallPeers();
while (peers.hasNext())
{
CallPeerSipImpl peer = peers.next();
peer.sendReInvite();
}
}

@ -241,15 +241,18 @@ public void setLocalVideoAllowed(Call call,
boolean allowed)
throws OperationFailedException
{
((CallSipImpl)call).setVideoDevice(mediaDevice);
((CallSipImpl)call).setLocalVideoAllowed(allowed, MediaUseCase.DESKTOP);
size = (((VideoMediaFormat)((CallSipImpl)call).
getDefaultDevice(MediaType.VIDEO).
getFormat()).getSize());
CallSipImpl sipCall = (CallSipImpl) call;
sipCall.setVideoDevice(mediaDevice);
sipCall.setLocalVideoAllowed(allowed, MediaUseCase.DESKTOP);
size
= (((VideoMediaFormat)
sipCall.getDefaultDevice(MediaType.VIDEO).getFormat())
.getSize());
origin = getOriginForMediaDevice(mediaDevice);
/* reinvite all peers */
((CallSipImpl)call).reInvite();
sipCall.reInvite();
}
/**

@ -7,6 +7,7 @@
package net.java.sip.communicator.service.neomedia;
import java.beans.*;
import java.util.*;
import net.java.sip.communicator.service.neomedia.format.*;
@ -14,7 +15,7 @@
* Abstract base implementation of <tt>MediaStream</tt> to ease the
* implementation of the interface.
*
* @author Lubomir Marinov
* @author Lyubomir Marinov
*/
public abstract class AbstractMediaStream
implements MediaStream
@ -80,6 +81,8 @@ protected void firePropertyChange(
* @param encoding the encoding we are looking for.
* @param clockRate the clock rate that we'd like the format to have.
* @param channels the number of channels that expect to find in this format
* @param formatParameters the format parameters expected to match these of
* the specified <tt>format</tt>
* @return <tt>true</tt> if the specified <tt>format</tt> has specific
* values for its properties <tt>mediaType</tt>, <tt>encoding</tt>,
* <tt>clockRate</tt> and <tt>channels</tt>; otherwise, <tt>false</tt>
@ -87,9 +90,8 @@ protected void firePropertyChange(
public static boolean matches(
MediaFormat format,
MediaType mediaType,
String encoding,
double clockRate,
int channels)
String encoding, double clockRate, int channels,
Map<String, String> formatParameters)
{
// mediaType
// encoding
@ -121,7 +123,8 @@ public static boolean matches(
return false;
}
return true;
// formatParameters
return format.formatParametersMatch(formatParameters);
}
/**

@ -20,6 +20,7 @@
* or video formats are likely to add other methods.
*
* @author Emil Ivov
* @author Lyubomir Marinov
*/
public interface MediaFormat
{
@ -80,17 +81,27 @@ public interface MediaFormat
/**
* Determines whether this <tt>MediaFormat</tt> is equal to
* <tt>mediaFormat</tt>, or in other words that they have the same encoding
* and attributes (e.g. refreshRate, channels, etc.).
* <tt>mediaFormat</tt> i.e. they have the same encoding, clock rate, format
* parameters, advanced attributes, etc.
*
* @param mediaFormat The <tt>MediaFormat</tt> that we would like to compare
* with the current one.
* @param mediaFormat the <tt>MediaFormat</tt> to compare to this instance
* @return <tt>true</tt> if <tt>mediaFormat</tt> is equal to this format and
* <tt>false</tt> otherwise.
*/
@Override
public boolean equals(Object mediaFormat);
/**
* Determines whether the format parameters of this <tt>MediaFormat</tt>
* match a specific set of format parameters.
*
* @param fmtps the set of format parameters to match to the format
* parameters of this <tt>MediaFormat</tt>
* @return <tt>true</tt> if this <tt>MediaFormat</tt> considers
* <tt>fmtps</tt> matching its format parameters; otherwise, <tt>false</tt>
*/
public boolean formatParametersMatch(Map<String, String> fmtps);
/**
* Returns a <tt>Map</tt> containing advanced parameters specific to this
* particular <tt>MediaFormat</tt>. The parameters returned here are meant

@ -1280,8 +1280,8 @@ private void registerAudioLevelListeners(AudioMediaStream audioStream)
*/
private void registerDynamicPTsWithStream(MediaStream stream)
{
for ( Map.Entry<MediaFormat, Byte> mapEntry
: dynamicPayloadTypes.getMappings().entrySet())
for (Map.Entry<MediaFormat, Byte> mapEntry
: dynamicPayloadTypes.getMappings().entrySet())
{
byte pt = mapEntry.getValue();
MediaFormat fmt = mapEntry.getKey();
@ -1402,12 +1402,14 @@ protected MediaFormat findMediaFormat(
= MediaType.AUDIO.equals(mediaType)
? ((AudioMediaFormat) format).getChannels()
: MediaFormatFactory.CHANNELS_NOT_SPECIFIED;
Map<String, String> formatParameters = format.getFormatParameters();
for(MediaFormat match : formats)
{
if (AbstractMediaStream.matches(
match,
mediaType, encoding, clockRate, channels))
mediaType,
encoding, clockRate, channels, formatParameters))
return match;
}
return null;

@ -7,7 +7,6 @@
package net.java.sip.communicator.service.protocol.media;
import java.util.*;
import java.util.Map.*;
import net.java.sip.communicator.service.neomedia.*;
import net.java.sip.communicator.service.neomedia.format.*;
@ -37,11 +36,11 @@ public class DynamicPayloadTypeRegistry
private byte nextDynamicPayloadType = MediaFormat.MIN_DYNAMIC_PAYLOAD_TYPE;
/**
* A table mapping <tt>MediaFormat</tt> instances to the dynamic payload
* type number they have obtained for the lifetime of this registry.
* The mappings of <tt>MediaFormat</tt> instances to the dynamic payload
* type numbers they have obtained for the lifetime of this registry.
*/
private Map<MediaFormat, Byte> payloadTypeMappings
= new Hashtable<MediaFormat, Byte>();
private final Map<MediaFormat, Byte> payloadTypeMappings
= new HashMap<MediaFormat, Byte>();
/**
* Returns the dynamic payload type that has been allocated for
@ -50,7 +49,7 @@ public class DynamicPayloadTypeRegistry
* primarily during generation of SDP descriptions.
*
* @param format the <tt>MediaFormat</tt> instance that we'd like to obtain
* a payload type number for..
* a payload type number for.
*
* @return the (possibly newly allocated) payload type number corresponding
* to the specified <tt>format</tt> instance for the lifetime of the media
@ -70,13 +69,15 @@ public byte obtainPayloadTypeNumber(MediaFormat format)
? ((AudioMediaFormat) format).getChannels()
: MediaFormatFactory.CHANNELS_NOT_SPECIFIED;
Byte payloadType = null;
Map<String, String> formatParameters
= format.getFormatParameters();
for (Map.Entry<MediaFormat, Byte> payloadTypeMapping
: payloadTypeMappings.entrySet())
{
if (AbstractMediaStream.matches(
payloadTypeMapping.getKey(),
mediaType, encoding, clockRate, channels))
mediaType, encoding, clockRate, channels, formatParameters))
{
payloadType = payloadTypeMapping.getValue();
break;
@ -121,8 +122,9 @@ public byte obtainPayloadTypeNumber(MediaFormat format)
*/
private Byte getPreferredDynamicPayloadType(MediaFormat format)
{
return ProtocolMediaActivator.getMediaService()
.getDynamicPayloadTypePreferences().get(format);
return
ProtocolMediaActivator.getMediaService()
.getDynamicPayloadTypePreferences().get(format);
}
/**
@ -147,16 +149,18 @@ public void addMapping(MediaFormat format, byte payloadType)
if(alreadyMappedFmt != null)
{
throw new IllegalArgumentException(payloadType
+ " has already been allocated to " + alreadyMappedFmt);
throw new IllegalArgumentException(
payloadType + " has already been allocated to "
+ alreadyMappedFmt);
}
if( payloadType < MediaFormat.MIN_DYNAMIC_PAYLOAD_TYPE)
{
throw new IllegalArgumentException(payloadType
+ " is not a valid dynamic payload type number."
+ " (must be between " + MediaFormat.MIN_DYNAMIC_PAYLOAD_TYPE
+ " and " + MediaFormat.MAX_DYNAMIC_PAYLOAD_TYPE);
throw new IllegalArgumentException(
payloadType + " is not a valid dynamic payload type number."
+ " (must be between "
+ MediaFormat.MIN_DYNAMIC_PAYLOAD_TYPE + " and "
+ MediaFormat.MAX_DYNAMIC_PAYLOAD_TYPE);
}
payloadTypeMappings.put(format, Byte.valueOf(payloadType));
@ -203,19 +207,21 @@ private byte nextPayloadTypeNumber()
if (nextDynamicPayloadType < 0)
{
throw new IllegalStateException(
"Impossible to allocate more than the already 32 mapped "
+"dynamic payload type numbers");
"Impossible to allocate more than the already 32 "
+ "mapped dynamic payload type numbers");
}
byte payloadType = nextDynamicPayloadType++;
if(findFormat(payloadType) == null
&& findFormatWithPreference(payloadType) == null)
if ((findFormat(payloadType) == null)
&& (findFormatWithPreference(payloadType) == null))
return payloadType;
//if we get here then that means that the number we obtained by
//incrementing our PT counter was already occupied (probably by an
//incoming SDP). continue bravely and get the next free one.
/*
* If we get here, then that means that the number we obtained by
* incrementing our PT counter was already occupied (probably by an
* incoming SDP). Continue bravely and get the next free one.
*/
}
}
@ -234,14 +240,13 @@ && findFormatWithPreference(payloadType) == null)
*/
private MediaFormat findFormatWithPreference(Byte payloadTypePreference)
{
for(Entry<MediaFormat, Byte> entry
: ProtocolMediaActivator.getMediaService()
.getDynamicPayloadTypePreferences().entrySet())
for(Map.Entry<MediaFormat, Byte> entry
: ProtocolMediaActivator.getMediaService()
.getDynamicPayloadTypePreferences().entrySet())
{
if(entry.getValue() == payloadTypePreference)
return entry.getKey();
}
return null;
}
@ -252,6 +257,6 @@ private MediaFormat findFormatWithPreference(Byte payloadTypePreference)
*/
public Map<MediaFormat, Byte> getMappings()
{
return new Hashtable<MediaFormat, Byte>(payloadTypeMappings);
return new HashMap<MediaFormat, Byte>(payloadTypeMappings);
}
}

@ -49,8 +49,10 @@ public void run()
catch(Throwable err)
{
//we don't have logging here so dump to stderr
System.err.println("Warning: Failed to register our ARG handler.");
System.err.println("We won't be able to handle command line arguments.");
System.err.println(
"Warning: Failed to register our command line argument"
+ " handler. We won't be able to handle command line"
+ " arguments.");
err.printStackTrace();
}

Loading…
Cancel
Save