mirror of https://github.com/sipwise/jitsi.git
parent
c14ffbdffb
commit
a5dadec391
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.
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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.h263p;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.media.*;
|
||||
import javax.media.format.*;
|
||||
|
||||
import net.java.sip.communicator.impl.neomedia.codec.*;
|
||||
import net.java.sip.communicator.util.Logger;
|
||||
import net.sf.fmj.media.*;
|
||||
|
||||
/**
|
||||
* Depacketizes H.263+ RTP packets in in accord with RFC 4529 "RTP Payload
|
||||
* Format for ITU-T Rec. H.263 Video".
|
||||
*
|
||||
* @author Sebastien Vincent
|
||||
* @author Lubomir Marinov
|
||||
*/
|
||||
public class DePacketizer
|
||||
extends AbstractCodecExt
|
||||
{
|
||||
/**
|
||||
* The <tt>Logger</tt> used by the <tt>DePacketizer</tt> class and its
|
||||
* instances for logging output.
|
||||
*/
|
||||
private static final Logger logger = Logger.getLogger(DePacketizer.class);
|
||||
|
||||
/**
|
||||
* The size of the padding at the end of the output data of this
|
||||
* <tt>DePacketizer</tt> expected by the H.263+ decoder.
|
||||
*/
|
||||
private final int outputPaddingSize = FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE;
|
||||
|
||||
/**
|
||||
* Keeps track of last (input) sequence number in order to avoid
|
||||
* inconsistent data.
|
||||
*/
|
||||
private long lastSequenceNumber = -1;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether incomplete buffer packets are
|
||||
* output from the H.263+ <tt>DePacketizer</tt> to the decoder.
|
||||
*/
|
||||
private static final boolean OUTPUT_INCOMPLETE_BUFFER = true;
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>DePacketizer</tt> instance which is to depacketize
|
||||
* H.263+ RTP packet.
|
||||
*/
|
||||
public DePacketizer()
|
||||
{
|
||||
super(
|
||||
"H263+ DePacketizer",
|
||||
VideoFormat.class,
|
||||
new VideoFormat[] { new VideoFormat(Constants.H263P) });
|
||||
|
||||
inputFormats
|
||||
= new VideoFormat[] { new VideoFormat(Constants.H263P_RTP) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the <tt>Codec</tt>.
|
||||
*/
|
||||
protected 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
|
||||
* @see AbstractCodecExt#doOpen()
|
||||
*/
|
||||
protected void doOpen()
|
||||
throws ResourceUnavailableException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes (depacketizes) a buffer.
|
||||
*
|
||||
* @param inBuffer input buffer
|
||||
* @param outBuffer output buffer
|
||||
* @return <tt>BUFFER_PROCESSED_OK</tt> if buffer has been successfully
|
||||
* processed
|
||||
*/
|
||||
protected int doProcess(Buffer inBuffer, Buffer outBuffer)
|
||||
{
|
||||
long sequenceNumber = inBuffer.getSequenceNumber();
|
||||
|
||||
if ((lastSequenceNumber != -1)
|
||||
&& ((sequenceNumber - lastSequenceNumber) != 1))
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
/* maybe we lost a frame somewhere or the sequence number reach
|
||||
* its maximum number
|
||||
*/
|
||||
if (logger.isTraceEnabled())
|
||||
logger.trace(
|
||||
"Dropped RTP packets upto sequenceNumber "
|
||||
+ lastSequenceNumber
|
||||
+ " and continuing with sequenceNumber "
|
||||
+ sequenceNumber);
|
||||
|
||||
ret = reset(outBuffer);
|
||||
|
||||
if ((ret & OUTPUT_BUFFER_NOT_FILLED) == 0)
|
||||
{
|
||||
lastSequenceNumber = -1;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
lastSequenceNumber = sequenceNumber;
|
||||
|
||||
byte[] in = (byte[]) inBuffer.getData();
|
||||
int inLength = inBuffer.getLength();
|
||||
int inOffset = inBuffer.getOffset();
|
||||
int outOffset = outBuffer.getOffset();
|
||||
|
||||
if(inLength < 3)
|
||||
{
|
||||
return BUFFER_PROCESSED_FAILED;
|
||||
}
|
||||
|
||||
boolean pBit = ((in[inOffset] & 0x04) > 0);
|
||||
boolean vBit = ((in[inOffset] & 0x02) > 0);;
|
||||
int plen = ((in[inOffset] & 0x01) << 5) +
|
||||
((in[inOffset + 1] & 0xF8) >> 3);
|
||||
int dataLength = inLength - plen - (vBit ? 1 : 0) - (pBit ? 0 : 2);
|
||||
|
||||
byte out[] = validateByteArraySize(outBuffer, outOffset + dataLength +
|
||||
outputPaddingSize);
|
||||
|
||||
if(pBit)
|
||||
{
|
||||
out[0] = 0x00;
|
||||
out[1] = 0x00;
|
||||
}
|
||||
|
||||
if(vBit)
|
||||
{
|
||||
/* ignore VRC */
|
||||
}
|
||||
|
||||
if(plen > 0)
|
||||
{
|
||||
if(logger.isInfoEnabled())
|
||||
{
|
||||
logger.info("Extra picture header present PLEN=" + plen);
|
||||
}
|
||||
}
|
||||
|
||||
System.arraycopy(in, inOffset + 2 + (vBit ? 1 : 0) + plen,
|
||||
out, outOffset + (pBit ? 2 : 0), dataLength - (pBit ? 2 : 0));
|
||||
|
||||
padOutput(out, outOffset + dataLength);
|
||||
|
||||
outBuffer.setLength(outOffset + dataLength);
|
||||
outBuffer.setSequenceNumber(sequenceNumber);
|
||||
|
||||
/*
|
||||
* The RTP marker bit is set for the very last packet of the access unit
|
||||
* indicated by the RTP time stamp to allow an efficient playout buffer
|
||||
* handling. Consequently, we have to output it as well.
|
||||
*/
|
||||
if ((inBuffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0)
|
||||
{
|
||||
outBuffer.setFlags(outBuffer.getFlags() | Buffer.FLAG_RTP_MARKER);
|
||||
outBuffer.setOffset(0);
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
outBuffer.setOffset(outOffset + dataLength);
|
||||
return OUTPUT_BUFFER_NOT_FILLED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends {@link #outputPaddingSize} number of bytes to <tt>out</tt>
|
||||
* beginning at index <tt>outOffset</tt>. The specified <tt>out</tt> is
|
||||
* expected to be large enough to accommodate the mentioned number of bytes.
|
||||
*
|
||||
* @param out the buffer in which <tt>outputPaddingSize</tt> number of bytes
|
||||
* are to be written
|
||||
* @param outOffset the index in <tt>outOffset</tt> at which the writing of
|
||||
* <tt>outputPaddingSize</tt> number of bytes is to begin
|
||||
*/
|
||||
private void padOutput(byte[] out, int outOffset)
|
||||
{
|
||||
Arrays.fill(out, outOffset, outOffset + outputPaddingSize, (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the states of this <tt>DePacketizer</tt> and a specific output
|
||||
* <tt>Buffer</tt> so that they are ready to have this <tt>DePacketizer</tt>
|
||||
* process input RTP payloads.
|
||||
*
|
||||
* @param outBuffer the output <tt>Buffer</tt> to be reset
|
||||
* @return the flags such as <tt>BUFFER_PROCESSED_OK</tt> and
|
||||
* <tt>OUTPUT_BUFFER_NOT_FILLED</tt> to be returned by
|
||||
* {@link #process(Buffer, Buffer)}
|
||||
*/
|
||||
private int reset(Buffer outBuffer)
|
||||
{
|
||||
if (OUTPUT_INCOMPLETE_BUFFER && outBuffer.getLength() > 0)
|
||||
{
|
||||
Object outData = outBuffer.getData();
|
||||
|
||||
if (outData instanceof byte[])
|
||||
{
|
||||
return (BUFFER_PROCESSED_OK | INPUT_BUFFER_NOT_CONSUMED);
|
||||
}
|
||||
}
|
||||
|
||||
outBuffer.setLength(0);
|
||||
return OUTPUT_BUFFER_NOT_FILLED;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,349 @@
|
||||
/*
|
||||
* 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.h263p;
|
||||
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* Implements a H.263+ decoder.
|
||||
*
|
||||
* @author Sebastien Vincent
|
||||
* @author Lubomir Marinov
|
||||
*/
|
||||
public class JNIDecoder
|
||||
extends AbstractCodec
|
||||
{
|
||||
/**
|
||||
* Plugin name.
|
||||
*/
|
||||
private static final String PLUGIN_NAME = "H.263+ Decoder";
|
||||
|
||||
/**
|
||||
* The default output <tt>VideoFormat</tt>.
|
||||
*/
|
||||
private static final VideoFormat[] DEFAULT_OUTPUT_FORMATS
|
||||
= new VideoFormat[] { new AVFrameFormat() };
|
||||
|
||||
/**
|
||||
* Array of output <tt>VideoFormat</tt>s.
|
||||
*/
|
||||
private final VideoFormat[] outputFormats;
|
||||
|
||||
/**
|
||||
* If decoder has got a picture.
|
||||
*/
|
||||
private final boolean[] got_picture = new boolean[1];
|
||||
|
||||
/**
|
||||
* The codec context native pointer we will use.
|
||||
*/
|
||||
private long avcontext = 0;
|
||||
|
||||
/**
|
||||
* The decoded data is stored in avpicture in native ffmpeg format (YUV).
|
||||
*/
|
||||
private long avframe = 0;
|
||||
|
||||
/**
|
||||
* 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 = 0;
|
||||
|
||||
/**
|
||||
* 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 = 0;
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>JNIDecoder</tt> instance which is to decode H.263+
|
||||
* encoded data into frames in YUV format.
|
||||
*/
|
||||
public JNIDecoder()
|
||||
{
|
||||
inputFormats
|
||||
= new VideoFormat[] { new VideoFormat(Constants.H263P) };
|
||||
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 H263P_RTP
|
||||
*/
|
||||
public boolean checkFormat(Format format)
|
||||
{
|
||||
return format.getEncoding().equals(Constants.H263P_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.263+ 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;
|
||||
|
||||
/* from ffmpeg -formats output: "For example, the h263 decoder
|
||||
* corresponds to the h263 and h263p encoders". That's why we use
|
||||
* CODEC_ID_H263 for the decoder side (instead of CODEC_ID_H263P).
|
||||
*/
|
||||
long avcodec = FFmpeg.avcodec_find_decoder(FFmpeg.CODEC_ID_H263);
|
||||
|
||||
avcontext = FFmpeg.avcodec_alloc_context();
|
||||
FFmpeg.avcodeccontext_set_workaround_bugs(avcontext,
|
||||
FFmpeg.FF_BUG_AUTODETECT);
|
||||
|
||||
if (FFmpeg.avcodec_open(avcontext, avcodec) < 0)
|
||||
throw new RuntimeException("Could not open codec CODEC_ID_H263");
|
||||
|
||||
avframe = FFmpeg.avcodec_alloc_frame();
|
||||
|
||||
opened = true;
|
||||
super.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes H.263+ 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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,463 @@
|
||||
/*
|
||||
* 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.h263p;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.media.*;
|
||||
import javax.media.format.*;
|
||||
|
||||
import net.java.sip.communicator.impl.neomedia.codec.*;
|
||||
import net.sf.fmj.media.*;
|
||||
|
||||
/**
|
||||
* Implements a H.263+ encoder.
|
||||
*
|
||||
* @author Sebastien Vincent
|
||||
* @author Lubomir Marinov
|
||||
*/
|
||||
public class JNIEncoder
|
||||
extends AbstractCodec
|
||||
{
|
||||
/**
|
||||
* The frame rate to be assumed by <tt>JNIEncoder</tt> instance in the
|
||||
* absence of any other frame rate indication.
|
||||
*/
|
||||
private static final int DEFAULT_FRAME_RATE = 30;
|
||||
|
||||
/**
|
||||
* Default output formats.
|
||||
*/
|
||||
private static final Format[] DEFAULT_OUTPUT_FORMATS
|
||||
= { new VideoFormat(Constants.H263P) };
|
||||
|
||||
/**
|
||||
* Key frame every 300 frames.
|
||||
*/
|
||||
private static final int IFRAME_INTERVAL = 300;
|
||||
|
||||
/**
|
||||
* Name of the code.
|
||||
*/
|
||||
private static final String PLUGIN_NAME = "H.263+ Encoder";
|
||||
|
||||
/**
|
||||
* The codec we will use.
|
||||
*/
|
||||
private long avcontext = 0;
|
||||
|
||||
/**
|
||||
* The encoded data is stored in avpicture.
|
||||
*/
|
||||
private long avframe = 0;
|
||||
|
||||
/**
|
||||
* We use this buffer to supply data to encoder.
|
||||
*/
|
||||
private byte[] encFrameBuffer = null;
|
||||
|
||||
/**
|
||||
* The supplied data length.
|
||||
*/
|
||||
private int encFrameLen = 0;
|
||||
|
||||
/**
|
||||
* The raw frame buffer.
|
||||
*/
|
||||
private long rawFrameBuffer = 0;
|
||||
|
||||
/**
|
||||
* Next interval for an automatic keyframe.
|
||||
*/
|
||||
private int framesSinceLastIFrame = IFRAME_INTERVAL + 1;
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>JNIEncoder</tt> instance.
|
||||
*/
|
||||
public JNIEncoder()
|
||||
{
|
||||
inputFormats
|
||||
= new Format[]
|
||||
{
|
||||
new YUVFormat(
|
||||
null,
|
||||
Format.NOT_SPECIFIED,
|
||||
Format.byteArray,
|
||||
DEFAULT_FRAME_RATE,
|
||||
YUVFormat.YUV_420,
|
||||
Format.NOT_SPECIFIED, Format.NOT_SPECIFIED,
|
||||
0, Format.NOT_SPECIFIED, Format.NOT_SPECIFIED)
|
||||
};
|
||||
|
||||
inputFormat = null;
|
||||
outputFormat = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this <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;
|
||||
FFmpeg.av_free(rawFrameBuffer);
|
||||
rawFrameBuffer = 0;
|
||||
|
||||
encFrameBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the matching output formats for a specific format.
|
||||
*
|
||||
* @param in input format
|
||||
* @return array for formats matching input format
|
||||
*/
|
||||
private Format[] getMatchingOutputFormats(Format in)
|
||||
{
|
||||
VideoFormat videoIn = (VideoFormat) in;
|
||||
|
||||
return
|
||||
new VideoFormat[]
|
||||
{
|
||||
new VideoFormat(
|
||||
Constants.H263P,
|
||||
videoIn.getSize(),
|
||||
Format.NOT_SPECIFIED,
|
||||
Format.byteArray,
|
||||
videoIn.getFrameRate())
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this <tt>Codec</tt>.
|
||||
*
|
||||
* @return codec name
|
||||
*/
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return PLUGIN_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of formats supported at the output.
|
||||
*
|
||||
* @param in input <tt>Format</tt> to determine corresponding output
|
||||
* <tt>Format/tt>s
|
||||
* @return array of formats supported at output
|
||||
*/
|
||||
public Format[] getSupportedOutputFormats(Format in)
|
||||
{
|
||||
// null input format
|
||||
if (in == null)
|
||||
return DEFAULT_OUTPUT_FORMATS;
|
||||
|
||||
// mismatch input format
|
||||
if (!(in instanceof VideoFormat)
|
||||
|| (null == AbstractCodecExt.matches(in, inputFormats)))
|
||||
return new Format[0];
|
||||
|
||||
return getMatchingOutputFormats(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens this <tt>Codec</tt>.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void open()
|
||||
throws ResourceUnavailableException
|
||||
{
|
||||
if (opened)
|
||||
return;
|
||||
|
||||
if (inputFormat == null)
|
||||
throw new ResourceUnavailableException("No input format selected");
|
||||
if (outputFormat == null)
|
||||
throw new ResourceUnavailableException("No output format selected");
|
||||
|
||||
VideoFormat outputVideoFormat = (VideoFormat) outputFormat;
|
||||
Dimension size = outputVideoFormat.getSize();
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
long avcodec = FFmpeg.avcodec_find_encoder(FFmpeg.CODEC_ID_H263P);
|
||||
|
||||
avcontext = FFmpeg.avcodec_alloc_context();
|
||||
|
||||
System.out.println("transmit " + width + "x" + height);
|
||||
FFmpeg.avcodeccontext_set_pix_fmt(avcontext, FFmpeg.PIX_FMT_YUV420P);
|
||||
FFmpeg.avcodeccontext_set_size(avcontext, width, height);
|
||||
FFmpeg.avcodeccontext_set_qcompress(avcontext, 0.6f);
|
||||
|
||||
int bitRate = 256000;
|
||||
int frameRate = (int) outputVideoFormat.getFrameRate();
|
||||
|
||||
if (frameRate == Format.NOT_SPECIFIED)
|
||||
frameRate = DEFAULT_FRAME_RATE;
|
||||
|
||||
// average bit rate
|
||||
FFmpeg.avcodeccontext_set_bit_rate(avcontext, bitRate);
|
||||
FFmpeg.avcodeccontext_set_bit_rate_tolerance(avcontext,
|
||||
bitRate / (frameRate - 1));
|
||||
//FFmpeg.avcodeccontext_set_rc_max_rate(avcontext, bitRate);
|
||||
//FFmpeg.avcodeccontext_set_sample_aspect_ratio(avcontext, 0, 0);
|
||||
|
||||
// time_base should be 1 / frame rate
|
||||
FFmpeg.avcodeccontext_set_time_base(avcontext, 1, frameRate);
|
||||
//FFmpeg.avcodeccontext_set_quantizer(avcontext, 10, 51, 4);
|
||||
|
||||
FFmpeg.avcodeccontext_set_mb_decision(avcontext,
|
||||
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_add_flags(avcontext,
|
||||
FFmpeg.CODEC_FLAG_AC_PRED);
|
||||
FFmpeg.avcodeccontext_add_flags(avcontext,
|
||||
FFmpeg.CODEC_FLAG_H263P_UMV);
|
||||
FFmpeg.avcodeccontext_add_flags(avcontext,
|
||||
FFmpeg.CODEC_FLAG_H263P_SLICE_STRUCT);
|
||||
|
||||
FFmpeg.avcodeccontext_set_me_method(avcontext, 6);
|
||||
FFmpeg.avcodeccontext_set_me_subpel_quality(avcontext, 2);
|
||||
FFmpeg.avcodeccontext_set_me_range(avcontext, 18);
|
||||
FFmpeg.avcodeccontext_set_me_cmp(avcontext, FFmpeg.FF_CMP_CHROMA);
|
||||
FFmpeg.avcodeccontext_set_scenechange_threshold(avcontext, 40);
|
||||
|
||||
// Constant quality mode (also known as constant ratefactor)
|
||||
//FFmpeg.avcodeccontext_set_crf(avcontext, 0);
|
||||
//FFmpeg.avcodeccontext_set_rc_buffer_size(avcontext, 0);
|
||||
FFmpeg.avcodeccontext_set_gop_size(avcontext, IFRAME_INTERVAL);
|
||||
//FFmpeg.avcodeccontext_set_i_quant_factor(avcontext, 1f / 1.4f);
|
||||
|
||||
//FFmpeg.avcodeccontext_set_refs(avcontext, 2);
|
||||
//FFmpeg.avcodeccontext_set_trellis(avcontext, 2);
|
||||
|
||||
if (FFmpeg.avcodec_open(avcontext, avcodec) < 0)
|
||||
{
|
||||
throw
|
||||
new ResourceUnavailableException(
|
||||
"Could not open codec. (size= "
|
||||
+ width + "x" + height
|
||||
+ ")");
|
||||
}
|
||||
|
||||
encFrameLen = (width * height * 3) / 2;
|
||||
|
||||
rawFrameBuffer = FFmpeg.av_malloc(encFrameLen);
|
||||
|
||||
avframe = FFmpeg.avcodec_alloc_frame();
|
||||
|
||||
int sizeInBytes = width * height;
|
||||
|
||||
FFmpeg.avframe_set_data(
|
||||
avframe,
|
||||
rawFrameBuffer,
|
||||
sizeInBytes,
|
||||
sizeInBytes / 4);
|
||||
FFmpeg.avframe_set_linesize(avframe, width, width / 2, width / 2);
|
||||
|
||||
encFrameBuffer = new byte[encFrameLen];
|
||||
|
||||
opened = true;
|
||||
|
||||
super.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes/encodes a buffer.
|
||||
*
|
||||
* @param inBuffer input buffer
|
||||
* @param outBuffer output buffer
|
||||
* @return <tt>BUFFER_PROCESSED_OK</tt> if buffer has been successfully
|
||||
* processed
|
||||
*/
|
||||
public synchronized int process(Buffer inBuffer, Buffer outBuffer)
|
||||
{
|
||||
if (isEOM(inBuffer))
|
||||
{
|
||||
propagateEOM(outBuffer);
|
||||
reset();
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
if (inBuffer.isDiscard())
|
||||
{
|
||||
outBuffer.setDiscard(true);
|
||||
reset();
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
Format inFormat = inBuffer.getFormat();
|
||||
|
||||
if ((inFormat != inputFormat) && !inFormat.matches(inputFormat))
|
||||
setInputFormat(inFormat);
|
||||
|
||||
if (inBuffer.getLength() < 3)
|
||||
{
|
||||
outBuffer.setDiscard(true);
|
||||
reset();
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
// copy data to avframe
|
||||
FFmpeg.memcpy(
|
||||
rawFrameBuffer,
|
||||
(byte[]) inBuffer.getData(), inBuffer.getOffset(),
|
||||
encFrameLen);
|
||||
|
||||
if (framesSinceLastIFrame >= IFRAME_INTERVAL)
|
||||
{
|
||||
FFmpeg.avframe_set_key_frame(avframe, true);
|
||||
framesSinceLastIFrame = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
framesSinceLastIFrame++;
|
||||
FFmpeg.avframe_set_key_frame(avframe, false);
|
||||
}
|
||||
|
||||
// encode data
|
||||
int encLen
|
||||
= FFmpeg.avcodec_encode_video(
|
||||
avcontext,
|
||||
encFrameBuffer, encFrameLen,
|
||||
avframe);
|
||||
|
||||
/*
|
||||
* Do not always allocate a new data array for outBuffer, try to reuse
|
||||
* the existing one if it is suitable.
|
||||
*/
|
||||
Object outData = outBuffer.getData();
|
||||
byte[] out;
|
||||
|
||||
if (outData instanceof byte[])
|
||||
{
|
||||
out = (byte[]) outData;
|
||||
if (out.length < encLen)
|
||||
out = null;
|
||||
}
|
||||
else
|
||||
out = null;
|
||||
if (out == null)
|
||||
out = new byte[encLen];
|
||||
|
||||
System.arraycopy(encFrameBuffer, 0, out, 0, encLen);
|
||||
|
||||
outBuffer.setData(out);
|
||||
outBuffer.setLength(encLen);
|
||||
outBuffer.setOffset(0);
|
||||
outBuffer.setTimeStamp(inBuffer.getTimeStamp());
|
||||
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input format.
|
||||
*
|
||||
* @param in format to set
|
||||
* @return format
|
||||
*/
|
||||
@Override
|
||||
public Format setInputFormat(Format in)
|
||||
{
|
||||
// mismatch input format
|
||||
if (!(in instanceof VideoFormat)
|
||||
|| (null == AbstractCodecExt.matches(in, inputFormats)))
|
||||
return null;
|
||||
|
||||
YUVFormat yuv = (YUVFormat) in;
|
||||
|
||||
if (yuv.getOffsetU() > yuv.getOffsetV())
|
||||
return null;
|
||||
|
||||
Dimension size = yuv.getSize();
|
||||
|
||||
if (size == null)
|
||||
size = new Dimension(Constants.VIDEO_WIDTH, Constants.VIDEO_HEIGHT);
|
||||
|
||||
int strideY = size.width;
|
||||
int strideUV = strideY / 2;
|
||||
int offsetU = strideY * size.height;
|
||||
int offsetV = offsetU + strideUV * size.height / 2;
|
||||
|
||||
int yuvMaxDataLength = (strideY + strideUV) * size.height;
|
||||
|
||||
inputFormat
|
||||
= new YUVFormat(
|
||||
size,
|
||||
yuvMaxDataLength + FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE,
|
||||
Format.byteArray,
|
||||
yuv.getFrameRate(),
|
||||
YUVFormat.YUV_420,
|
||||
strideY, strideUV,
|
||||
0, offsetU, offsetV);
|
||||
|
||||
// Return the selected inputFormat
|
||||
return inputFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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)
|
||||
{
|
||||
// mismatch output format
|
||||
if (!(out instanceof VideoFormat)
|
||||
|| (null
|
||||
== AbstractCodecExt.matches(
|
||||
out,
|
||||
getMatchingOutputFormats(inputFormat))))
|
||||
return null;
|
||||
|
||||
VideoFormat videoOut = (VideoFormat) out;
|
||||
Dimension outSize = videoOut.getSize();
|
||||
|
||||
if (outSize == null)
|
||||
{
|
||||
Dimension inSize = ((VideoFormat) inputFormat).getSize();
|
||||
|
||||
outSize
|
||||
= (inSize == null)
|
||||
? new Dimension(
|
||||
Constants.VIDEO_WIDTH,
|
||||
Constants.VIDEO_HEIGHT)
|
||||
: inSize;
|
||||
}
|
||||
|
||||
outputFormat
|
||||
= new VideoFormat(
|
||||
videoOut.getEncoding(),
|
||||
outSize,
|
||||
Format.NOT_SPECIFIED,
|
||||
Format.byteArray,
|
||||
videoOut.getFrameRate());
|
||||
|
||||
// Return the selected outputFormat
|
||||
return outputFormat;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,419 @@
|
||||
/*
|
||||
* 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.h263p;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List; // disambiguation
|
||||
|
||||
import javax.media.*;
|
||||
import javax.media.format.*;
|
||||
|
||||
import net.java.sip.communicator.impl.neomedia.codec.*;
|
||||
import net.sf.fmj.media.*;
|
||||
|
||||
/**
|
||||
* Packetizes H.263+ encoded data into RTP packets in accord with RFC 4529
|
||||
* "RTP Payload Format for ITU-T Rec. H.263 Video".
|
||||
*
|
||||
* @author Sebastien Vincent
|
||||
* @author Lubomir Marinov
|
||||
*/
|
||||
public class Packetizer
|
||||
extends AbstractPacketizer
|
||||
{
|
||||
/**
|
||||
* Array of default output formats.
|
||||
*/
|
||||
private static final Format[] DEFAULT_OUTPUT_FORMATS
|
||||
= { new VideoFormat(Constants.H263P_RTP) };
|
||||
|
||||
/**
|
||||
* Maximum payload size without the headers.
|
||||
*/
|
||||
public static final int MAX_PAYLOAD_SIZE = 1024;
|
||||
|
||||
/**
|
||||
* Name of the plugin.
|
||||
*/
|
||||
private static final String PLUGIN_NAME = "H263+ Packetizer";
|
||||
|
||||
/**
|
||||
* The sequence number of the next RTP packet to be output by this
|
||||
* <tt>Packetizer</tt>.
|
||||
*/
|
||||
private int sequenceNumber = 0;
|
||||
|
||||
/**
|
||||
* The list of H263+ "Start code" video packets to be sent as payload in RTP
|
||||
* packets.
|
||||
*/
|
||||
private final List<byte[]> videoPkts = new LinkedList<byte[]>();
|
||||
|
||||
/**
|
||||
* The timeStamp of the RTP packets in which H263+ packets are to be sent.
|
||||
*/
|
||||
private long timeStamp = 0;
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>Packetizer</tt> instance which is to packetize
|
||||
* H.263+ encoded data into RTP packets in accord with
|
||||
* RFC 4529 "RTP Payload Format for ITU-T Rec. H.263 Video".
|
||||
*/
|
||||
public Packetizer()
|
||||
{
|
||||
inputFormats = new Format[] { new VideoFormat(Constants.H263P) };
|
||||
|
||||
inputFormat = null;
|
||||
outputFormat = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matching output formats for a specific format.
|
||||
*
|
||||
* @param in input format
|
||||
* @return array for formats matching input format
|
||||
*/
|
||||
private Format[] getMatchingOutputFormats(Format in)
|
||||
{
|
||||
VideoFormat videoIn = (VideoFormat) in;
|
||||
Dimension inSize = videoIn.getSize();
|
||||
|
||||
return
|
||||
new VideoFormat[]
|
||||
{
|
||||
new VideoFormat(
|
||||
Constants.H263P_RTP,
|
||||
inSize,
|
||||
Format.NOT_SPECIFIED,
|
||||
Format.byteArray,
|
||||
videoIn.getFrameRate())
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get codec name.
|
||||
*
|
||||
* @return codec name
|
||||
*/
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return PLUGIN_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of formats supported at the output.
|
||||
* @param in input <tt>Format</tt> to determine corresponding output
|
||||
* <tt>Format/tt>s
|
||||
* @return array of formats supported at output
|
||||
*/
|
||||
public Format[] getSupportedOutputFormats(Format in)
|
||||
{
|
||||
// null input format
|
||||
if (in == null)
|
||||
return DEFAULT_OUTPUT_FORMATS;
|
||||
|
||||
// mismatch input format
|
||||
if (!(in instanceof VideoFormat)
|
||||
|| (null == AbstractCodecExt.matches(in, inputFormats)))
|
||||
{
|
||||
return new Format[0];
|
||||
}
|
||||
|
||||
return getMatchingOutputFormats(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open this <tt>Packetizer</tt>.
|
||||
*
|
||||
* @throws ResourceUnavailableException if something goes wrong during
|
||||
* initialization of the Packetizer.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void open()
|
||||
throws ResourceUnavailableException
|
||||
{
|
||||
if (!opened)
|
||||
{
|
||||
videoPkts.clear();
|
||||
sequenceNumber = 0;
|
||||
|
||||
super.open();
|
||||
opened = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close this <tt>Packetizer</tt>.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close()
|
||||
{
|
||||
if (opened)
|
||||
{
|
||||
videoPkts.clear();
|
||||
opened = false;
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input format.
|
||||
*
|
||||
* @param in format to set
|
||||
* @return format
|
||||
*/
|
||||
@Override
|
||||
public Format setInputFormat(Format in)
|
||||
{
|
||||
/*
|
||||
* Return null if the specified input Format is incompatible with this
|
||||
* Packetizer.
|
||||
*/
|
||||
if (!(in instanceof VideoFormat)
|
||||
|| null == AbstractCodecExt.matches(in, inputFormats))
|
||||
return null;
|
||||
|
||||
inputFormat = in;
|
||||
return 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
|
||||
* 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)
|
||||
{
|
||||
/*
|
||||
* Return null if the specified output Format is incompatible with this
|
||||
* Packetizer.
|
||||
*/
|
||||
if (!(out instanceof VideoFormat)
|
||||
|| (null
|
||||
== AbstractCodecExt.matches(
|
||||
out,
|
||||
getMatchingOutputFormats(inputFormat))))
|
||||
return null;
|
||||
|
||||
VideoFormat videoOut = (VideoFormat) out;
|
||||
Dimension outSize = videoOut.getSize();
|
||||
|
||||
if (outSize == null)
|
||||
{
|
||||
Dimension inSize = ((VideoFormat) inputFormat).getSize();
|
||||
|
||||
outSize
|
||||
= (inSize == null)
|
||||
? new Dimension(
|
||||
Constants.VIDEO_WIDTH,
|
||||
Constants.VIDEO_HEIGHT)
|
||||
: inSize;
|
||||
}
|
||||
|
||||
outputFormat
|
||||
= new VideoFormat(
|
||||
videoOut.getEncoding(),
|
||||
outSize,
|
||||
outSize.width * outSize.height,
|
||||
Format.byteArray,
|
||||
videoOut.getFrameRate());
|
||||
|
||||
// Return the outputFormat which is actually set.
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes (packetize) a buffer.
|
||||
*
|
||||
* @param inBuffer input buffer
|
||||
* @param outBuffer output buffer
|
||||
* @return <tt>BUFFER_PROCESSED_OK</tt> if buffer has been successfully
|
||||
* processed
|
||||
*/
|
||||
@Override
|
||||
public int process(Buffer inBuffer, Buffer outBuffer)
|
||||
{
|
||||
int inLength = inBuffer.getLength();
|
||||
byte inData[] = (byte[])inBuffer.getData();
|
||||
int inOffset = inBuffer.getOffset();
|
||||
boolean pktAdded = false;
|
||||
|
||||
if (videoPkts.size() > 0)
|
||||
{
|
||||
byte[] pktData = videoPkts.remove(0);
|
||||
|
||||
// Send the packet.
|
||||
outBuffer.setData(pktData);
|
||||
outBuffer.setLength(pktData.length);
|
||||
outBuffer.setOffset(0);
|
||||
outBuffer.setTimeStamp(timeStamp);
|
||||
outBuffer.setSequenceNumber(sequenceNumber++);
|
||||
|
||||
// If there are other packets, send them as well.
|
||||
if(videoPkts.size() > 0)
|
||||
{
|
||||
return (BUFFER_PROCESSED_OK | INPUT_BUFFER_NOT_CONSUMED);
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's the last packet of the current frame so mark it.
|
||||
outBuffer.setFlags(
|
||||
outBuffer.getFlags() | Buffer.FLAG_RTP_MARKER);
|
||||
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (isEOM(inBuffer))
|
||||
{
|
||||
propagateEOM(outBuffer);
|
||||
reset();
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
if (inBuffer.isDiscard())
|
||||
{
|
||||
outBuffer.setDiscard(true);
|
||||
reset();
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
Format inFormat = inBuffer.getFormat();
|
||||
|
||||
if ((inFormat != inputFormat) && !inFormat.matches(inputFormat))
|
||||
setInputFormat(inFormat);
|
||||
|
||||
int endIndex = inOffset + inLength;
|
||||
int beginIndex = findStartcode(inData, inOffset, endIndex);
|
||||
|
||||
if (beginIndex < endIndex)
|
||||
{
|
||||
for (int nextBeginIndex;
|
||||
beginIndex < endIndex;
|
||||
beginIndex = nextBeginIndex + 3)
|
||||
{
|
||||
nextBeginIndex = findStartcode(inData, beginIndex + 3,
|
||||
endIndex);
|
||||
int length = nextBeginIndex - beginIndex;
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
pktAdded
|
||||
= packetize(inData, beginIndex, length)
|
||||
|| pktAdded;
|
||||
beginIndex += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timeStamp = inBuffer.getTimeStamp();
|
||||
|
||||
if(pktAdded)
|
||||
{
|
||||
return process(inBuffer, outBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* first frame is not a synchronization point, discard ?*/
|
||||
return BUFFER_PROCESSED_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packetizes H.263+ encoded data so that it becomes ready to be sent as the
|
||||
* payload of RTP packets.
|
||||
*
|
||||
* @param data the bytes which contain the H.263+ encoded data to be
|
||||
* packetized
|
||||
* @param offset the offset of H.263+ encoded data to be packetized begins
|
||||
* @param length the length of the H.263+ encoded data starting at offset
|
||||
* @return <tt>true</tt> if at least one RTP packet payload has been
|
||||
* packetized i.e. prepared for sending; otherwise, <tt>false</tt>
|
||||
*/
|
||||
private boolean packetize(byte[] data, int offset, int length)
|
||||
{
|
||||
boolean pktAdded = false;
|
||||
|
||||
while(length > 0)
|
||||
{
|
||||
boolean isPsc = false;
|
||||
int pos = 0;
|
||||
int maxPayloadLength = MAX_PAYLOAD_SIZE;
|
||||
byte pkt[] = null;
|
||||
int payloadLength = 0;
|
||||
|
||||
/* is we are at synchronization point (PSC, GSBC, EOS, EOSBS) */
|
||||
if(data.length > 3 && data[offset] == 0x00 &&
|
||||
data[offset + 1] == 0x00)
|
||||
{
|
||||
isPsc = true;
|
||||
pos = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxPayloadLength -= 2;
|
||||
}
|
||||
|
||||
if(length > maxPayloadLength)
|
||||
{
|
||||
payloadLength = maxPayloadLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
payloadLength = length;
|
||||
}
|
||||
|
||||
pkt = new byte[payloadLength + (isPsc ? 0 : 2)];
|
||||
|
||||
/* add H263+ payload header */
|
||||
/* no VRC and no extra picture header */
|
||||
pkt[0] = (byte)(isPsc ? 0x04 : 0x00);
|
||||
pkt[1] = 0x00;
|
||||
|
||||
System.arraycopy(data, offset + pos, pkt, 2, payloadLength - pos);
|
||||
pktAdded = videoPkts.add(pkt) || pktAdded;
|
||||
|
||||
offset += payloadLength;
|
||||
length -= payloadLength;
|
||||
}
|
||||
|
||||
return pktAdded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index in <tt>byteStream</tt> at which a Picture Start code
|
||||
* begins.
|
||||
*
|
||||
* @param byteStream the H.263+ encoded byte stream
|
||||
* @param beginIndex the inclusive index in <tt>byteStream</tt> at which the
|
||||
* search is to begin
|
||||
* @param endIndex the exclusive index in <tt>byteStream</tt> at which the
|
||||
* search is to end
|
||||
* @return the index in <tt>byteStream</tt> at which the Picture Start code
|
||||
* begins, otherwise, <tt>endIndex</tt>
|
||||
*/
|
||||
private static int findStartcode(byte[] byteStream, int beginIndex,
|
||||
int endIndex)
|
||||
{
|
||||
for (; beginIndex < (endIndex - 3); beginIndex++)
|
||||
if((byteStream[beginIndex] == 0)
|
||||
&& (byteStream[beginIndex + 1] == 0)
|
||||
&& ((byteStream[beginIndex + 2] & (byte)0x80) == -128))
|
||||
{
|
||||
return beginIndex;
|
||||
}
|
||||
return endIndex;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue