mirror of https://github.com/sipwise/jitsi.git
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue