mirror of https://github.com/sipwise/jitsi.git
cusax-fix
parent
a34e5a9153
commit
5edc84047f
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.
@ -0,0 +1,122 @@
|
||||
package net.java.sip.communicator.impl.media.codec.video;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.AffineTransformOp;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.media.Buffer;
|
||||
import javax.media.Codec;
|
||||
import javax.media.Format;
|
||||
import javax.media.format.RGBFormat;
|
||||
import javax.media.format.VideoFormat;
|
||||
|
||||
import net.sf.fmj.media.AbstractCodec;
|
||||
import net.sf.fmj.media.util.BufferToImage;
|
||||
import net.sf.fmj.media.util.ImageToBuffer;
|
||||
|
||||
/**
|
||||
* Codec that scales images from one size to another.
|
||||
* Interestingly, cross-platform JMF does not appear to have a corresponding codec.
|
||||
* @author Ken Larson
|
||||
*
|
||||
* Original from fmj project, changed only output format sizes.
|
||||
* The sizes are those supported in h263 and h264.
|
||||
* @author Damian Minkov
|
||||
*/
|
||||
public class ImageScaler extends AbstractCodec implements Codec
|
||||
{
|
||||
// TODO: all formats supported by BufferToImage
|
||||
private final Format[] supportedInputFormats = new Format[] {
|
||||
new RGBFormat(null, -1, Format.intArray, -1.0f, 32, -1, -1, -1),
|
||||
};
|
||||
private final Format[] supportedOutputFormats = new Format[] {
|
||||
//P720
|
||||
new RGBFormat(new Dimension(720, 480), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
|
||||
//CIF4
|
||||
new RGBFormat(new Dimension(704, 576), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
|
||||
//CIF
|
||||
new RGBFormat(new Dimension(352, 288), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
|
||||
//QCIF
|
||||
new RGBFormat(new Dimension(176, 144), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
|
||||
//SQCIF
|
||||
new RGBFormat(new Dimension(128, 96), -1, Format.intArray, -1.0f, 32, -1, -1, -1),
|
||||
};
|
||||
|
||||
@Override
|
||||
public Format[] getSupportedInputFormats()
|
||||
{
|
||||
return supportedInputFormats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Format[] getSupportedOutputFormats(Format input)
|
||||
{
|
||||
// if (input == null)
|
||||
return supportedOutputFormats;
|
||||
// VideoFormat inputCast = (VideoFormat) input;
|
||||
// final Format[] result = new Format[] {
|
||||
// new RGBFormat(DIMENSION, -1, Format.intArray, -1.0f, 32, -1, -1, -1)};
|
||||
// // TODO: we have to specify the RGB, etc. in the output format.
|
||||
// return result;
|
||||
|
||||
}
|
||||
|
||||
private BufferToImage bufferToImage;
|
||||
|
||||
@Override
|
||||
public Format setInputFormat(Format format)
|
||||
{
|
||||
final VideoFormat videoFormat = (VideoFormat) format;
|
||||
if (videoFormat.getSize() == null)
|
||||
return null; // must set a size.
|
||||
|
||||
// logger.fine("FORMAT: " + MediaCGUtils.formatToStr(format));
|
||||
// TODO: check VideoFormat and compatibility
|
||||
bufferToImage = new BufferToImage((VideoFormat) format);
|
||||
return super.setInputFormat(format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int process(Buffer input, Buffer output)
|
||||
{
|
||||
if (!checkInputBuffer(input))
|
||||
{
|
||||
return BUFFER_PROCESSED_FAILED;
|
||||
}
|
||||
|
||||
if (isEOM(input))
|
||||
{
|
||||
propagateEOM(output); // TODO: what about data? can there be any?
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
final BufferedImage image = (BufferedImage) bufferToImage.createImage(input);
|
||||
|
||||
final Dimension inputSize = ((VideoFormat) inputFormat).getSize();
|
||||
final Dimension outputSize = ((VideoFormat) outputFormat).getSize();
|
||||
final double scaleX = ((double) outputSize.width) / ((double) inputSize.width);
|
||||
final double scaleY = ((double) outputSize.height) / ((double) inputSize.height);
|
||||
|
||||
final BufferedImage scaled = scale(image, scaleX, scaleY); // TODO: is the size exact? what about rounding errors?
|
||||
|
||||
// System.out.println("scaled: " + scaled.getWidth() + "x" + scaled.getHeight());
|
||||
final Buffer b = ImageToBuffer.createBuffer(scaled, ((VideoFormat) outputFormat).getFrameRate());
|
||||
output.setData(b.getData());
|
||||
output.setLength(b.getLength());
|
||||
output.setOffset(b.getOffset());
|
||||
output.setFormat(b.getFormat());
|
||||
// TODO: what about format?
|
||||
|
||||
return BUFFER_PROCESSED_OK;
|
||||
|
||||
|
||||
}
|
||||
private BufferedImage scale(BufferedImage bi, double scaleX, double scaleY)
|
||||
{
|
||||
AffineTransform tx = new AffineTransform();
|
||||
tx.scale(scaleX, scaleY);
|
||||
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
|
||||
return op.filter(bi, null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
package net.java.sip.communicator.impl.media.codec.video.h264;
|
||||
|
||||
import javax.media.*;
|
||||
|
||||
import net.java.sip.communicator.util.*;
|
||||
|
||||
/**
|
||||
* Parses H264 rtp headers and extracts the data in the format
|
||||
* the decoder expects it. RFC3984.
|
||||
*
|
||||
* @author Damian Minkov
|
||||
*/
|
||||
public class H264Parser
|
||||
{
|
||||
private final Logger logger = Logger.getLogger(H264Parser.class);
|
||||
|
||||
// allocate enough space for the incoming data
|
||||
private static final int MAX_FRAME_SIZE = 128 * 1024;
|
||||
|
||||
// every NAL starts with this start sequence
|
||||
private static final byte[] startSequence = { 0, 0, 1};
|
||||
|
||||
// the timestamp of the last received rtp packet
|
||||
private long lastTimestamp = -1;
|
||||
|
||||
// the result data is collected in this buffer
|
||||
private byte[] encodedFrame = new byte[MAX_FRAME_SIZE];
|
||||
// the size of the result data
|
||||
private int encodedFrameLen;
|
||||
|
||||
/**
|
||||
* New rtp packet is received. We push it to the parser to extract the data.
|
||||
* @param inputBuffer the data from the rtp packet
|
||||
* @return true if the result data must be passed to the decoder.
|
||||
*/
|
||||
public boolean pushRTPInput(Buffer inputBuffer)
|
||||
{
|
||||
long currentStamp = inputBuffer.getTimeStamp();
|
||||
|
||||
// the rtp marker field points that this is the last packet of
|
||||
// the received frame
|
||||
boolean hasMarker =
|
||||
(inputBuffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0;
|
||||
|
||||
// if the timestamp changes we are starting receiving a new frame
|
||||
if(!(currentStamp == lastTimestamp))
|
||||
{
|
||||
encodedFrame = new byte[MAX_FRAME_SIZE];
|
||||
encodedFrameLen = 0;
|
||||
}
|
||||
// the new frame timestamp
|
||||
lastTimestamp = currentStamp;
|
||||
|
||||
byte[] inData = (byte[]) inputBuffer.getData();
|
||||
int inputOffset = inputBuffer.getOffset();
|
||||
byte fByte = inData[inputOffset];
|
||||
int type = fByte & 0x1f;
|
||||
|
||||
// types from 1 to 23 are treated the same way
|
||||
if (type >= 1 && type <= 23)
|
||||
{
|
||||
System.arraycopy(startSequence, 0, encodedFrame, encodedFrameLen, startSequence.length);
|
||||
encodedFrameLen += startSequence.length;
|
||||
int len = inputBuffer.getLength();
|
||||
System.arraycopy(inData, inputOffset, encodedFrame, encodedFrameLen, len);
|
||||
encodedFrameLen += len;
|
||||
}
|
||||
else if (type == 24)
|
||||
{
|
||||
//return deencapsulateSTAP(inputBuffer);
|
||||
}
|
||||
else if (type == 28)
|
||||
{
|
||||
deencapsulateFU(fByte, inputBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn("Skipping unsupported NAL unit type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(hasMarker)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract data from FU packet. This are packets accross several
|
||||
* rtp packets, the fisrt has a start bit set, we store all data
|
||||
* and don't care about end bit.
|
||||
* @param nal
|
||||
* @param inputBuffer
|
||||
*/
|
||||
private void deencapsulateFU (byte nal, Buffer inputBuffer)
|
||||
{
|
||||
byte[] buf = (byte[])inputBuffer.getData();
|
||||
int len = inputBuffer.getLength();
|
||||
int offset = inputBuffer.getOffset();
|
||||
|
||||
offset++;
|
||||
len--;
|
||||
|
||||
byte fu_indicator = nal;
|
||||
byte fu_header = buf[offset];
|
||||
boolean start_bit = (fu_header >> 7) != 0;
|
||||
//boolean end_bit = ((fu_header & 0x40) >> 6) != 0;
|
||||
int nal_type = (fu_header & 0x1f);
|
||||
byte reconstructed_nal;
|
||||
//reconstruct this packet's true nal; only the data follows..
|
||||
//the original nal forbidden bit and NRI are stored in this packet's nal;
|
||||
reconstructed_nal = (byte)(fu_indicator & (byte)0xe0);
|
||||
reconstructed_nal |= nal_type;
|
||||
|
||||
// skip the fu_header...
|
||||
offset++;
|
||||
len--;
|
||||
|
||||
if(start_bit)
|
||||
{
|
||||
// copy in the start sequence, and the reconstructed nal....
|
||||
System.arraycopy(startSequence, 0,
|
||||
encodedFrame, encodedFrameLen, startSequence.length);
|
||||
encodedFrameLen += startSequence.length;
|
||||
encodedFrame[encodedFrameLen] = reconstructed_nal;
|
||||
encodedFrameLen++;
|
||||
System.arraycopy(buf, offset, encodedFrame, encodedFrameLen, len);
|
||||
encodedFrameLen += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.arraycopy(buf, offset, encodedFrame, encodedFrameLen, len);
|
||||
encodedFrameLen += len;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result data extracted from one ore more rtp packest.
|
||||
* @return the result data.
|
||||
*/
|
||||
public byte[] getEncodedFrame()
|
||||
{
|
||||
return encodedFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result data length.
|
||||
* @return the result length.
|
||||
*/
|
||||
public int getEncodedFrameLen()
|
||||
{
|
||||
return encodedFrameLen;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,271 @@
|
||||
package net.java.sip.communicator.impl.media.codec.video.h264;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.media.*;
|
||||
import javax.media.format.*;
|
||||
|
||||
import net.java.sip.communicator.util.*;
|
||||
import net.java.sip.communicator.impl.media.codec.*;
|
||||
import net.sf.ffmpeg_java.*;
|
||||
import net.sf.ffmpeg_java.AVCodecLibrary.*;
|
||||
|
||||
import com.ibm.media.codec.video.*;
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.ptr.*;
|
||||
|
||||
/**
|
||||
* Decodes incoming rtp data of type h264 and returns the result
|
||||
* frames in RGB format.
|
||||
*
|
||||
* @author Damian Minkov
|
||||
*/
|
||||
public class NativeDecoder
|
||||
extends VideoCodec
|
||||
{
|
||||
private final Logger logger = Logger.getLogger(NativeDecoder.class);
|
||||
|
||||
// Instances for the ffmpeg lib
|
||||
private AVFormatLibrary AVFORMAT;
|
||||
private AVCodecLibrary AVCODEC;
|
||||
private AVUtilLibrary AVUTIL;
|
||||
|
||||
// The codec we will use
|
||||
private AVCodec avcodec;
|
||||
private AVCodecContext avcontext;
|
||||
// The decoded data is stored in avpicture in native ffmpeg format (YUV)
|
||||
private AVFrame avpicture;
|
||||
// Used to convert decoded data to RGB
|
||||
private AVFrame frameRGB;
|
||||
|
||||
// The parser used to parse rtp content
|
||||
private H264Parser parser = new H264Parser();
|
||||
|
||||
// supported sizes by the codec
|
||||
private Dimension[] supportedSizes = new Dimension[]
|
||||
{
|
||||
//P720
|
||||
new Dimension(720, 480),
|
||||
//CIF4
|
||||
new Dimension(704, 576),
|
||||
//CIF
|
||||
new Dimension(352, 288),
|
||||
//QCIF
|
||||
new Dimension(176, 144),
|
||||
//SQCIF
|
||||
new Dimension(128, 96)
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs new h264 decoder
|
||||
*/
|
||||
public NativeDecoder()
|
||||
{
|
||||
supportedInputFormats =
|
||||
new VideoFormat[]
|
||||
{
|
||||
new VideoFormat(Constants.H264_RTP)
|
||||
};
|
||||
|
||||
defaultOutputFormats = new VideoFormat[]
|
||||
{
|
||||
new RGBFormat()
|
||||
};
|
||||
PLUGIN_NAME = "H.264 Decoder";
|
||||
|
||||
AVFORMAT = AVFormatLibrary.INSTANCE;
|
||||
AVCODEC = AVCodecLibrary.INSTANCE;
|
||||
AVUTIL = AVUtilLibrary.INSTANCE;
|
||||
|
||||
AVFORMAT.av_register_all();
|
||||
|
||||
AVCODEC.avcodec_init();
|
||||
}
|
||||
|
||||
protected Format[] getMatchingOutputFormats(Format in)
|
||||
{
|
||||
VideoFormat ivf = (VideoFormat) in;
|
||||
|
||||
supportedOutputFormats = new VideoFormat[supportedSizes.length];
|
||||
|
||||
for (int i = 0; i < supportedSizes.length; i++)
|
||||
{
|
||||
Dimension size = supportedSizes[i];
|
||||
supportedOutputFormats[i] =
|
||||
//PIX_FMT_RGB32
|
||||
new RGBFormat(
|
||||
size,
|
||||
-1,
|
||||
Format.intArray,
|
||||
ivf.getFrameRate(),
|
||||
32,
|
||||
0xFF0000,
|
||||
0xFF00,
|
||||
0xFF,
|
||||
1,
|
||||
size.width,
|
||||
Format.FALSE,
|
||||
Format.NOT_SPECIFIED);
|
||||
}
|
||||
|
||||
return supportedOutputFormats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data input format.
|
||||
*
|
||||
* @return false if the format is not supported.
|
||||
*/
|
||||
public Format setInputFormat(Format format)
|
||||
{
|
||||
if (super.setInputFormat(format) != null)
|
||||
{
|
||||
reset();
|
||||
return format;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the codec instances.
|
||||
*/
|
||||
public void open() throws ResourceUnavailableException
|
||||
{
|
||||
avcodec = AVCODEC.avcodec_find_decoder(AVCodecLibrary.CODEC_ID_H264);
|
||||
|
||||
avcontext = AVCODEC.avcodec_alloc_context();
|
||||
|
||||
if (AVCODEC.avcodec_open(avcontext, avcodec) < 0)
|
||||
throw new RuntimeException("Could not open codec ");
|
||||
|
||||
avpicture = AVCODEC.avcodec_alloc_frame();
|
||||
|
||||
avcontext.lowres = 1;
|
||||
|
||||
avcontext.workaround_bugs = 1;
|
||||
|
||||
AVUTIL.av_log_set_callback(new AVUtilLibrary.LogCallback()
|
||||
{
|
||||
|
||||
public void callback(Pointer p, int l, String fmtS, Pointer va_list)
|
||||
{
|
||||
logger.info("decoder: " + fmtS);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
frameRGB = AVCODEC.avcodec_alloc_frame();
|
||||
|
||||
opened = true;
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
if (opened)
|
||||
{
|
||||
opened = false;
|
||||
super.close();
|
||||
|
||||
AVCODEC.avcodec_close(avcontext);
|
||||
|
||||
AVUTIL.av_free(avpicture.getPointer());
|
||||
AVUTIL.av_free(avcontext.getPointer());
|
||||
}
|
||||
}
|
||||
|
||||
public int process(Buffer inputBuffer, Buffer outputBuffer)
|
||||
{
|
||||
if (!checkInputBuffer(inputBuffer))
|
||||
{
|
||||
return BUFFER_PROCESSED_FAILED;
|
||||
}
|
||||
|
||||
if (isEOM(inputBuffer) || !opened)
|
||||
{
|
||||
propagateEOM(outputBuffer);
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
if(!parser.pushRTPInput(inputBuffer))
|
||||
{
|
||||
return OUTPUT_BUFFER_NOT_FILLED;
|
||||
}
|
||||
|
||||
// fills the incoming data
|
||||
IntByReference got_picture = new IntByReference();
|
||||
Pointer encBuf = AVUTIL.av_malloc(parser.getEncodedFrameLen());
|
||||
arraycopy(parser.getEncodedFrame(), 0, encBuf, 0, parser.getEncodedFrameLen());
|
||||
|
||||
// decodes the data
|
||||
AVCODEC.avcodec_decode_video(
|
||||
avcontext, avpicture, got_picture, encBuf, parser.getEncodedFrameLen());
|
||||
|
||||
if(got_picture.getValue() == 0)
|
||||
{
|
||||
outputBuffer.setDiscard(true);
|
||||
|
||||
AVUTIL.av_free(encBuf);
|
||||
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
// convert the picture in RGB Format
|
||||
int numBytes = AVCODEC.avpicture_get_size(
|
||||
AVCodecLibrary.PIX_FMT_RGB32, avcontext.width, avcontext.height);
|
||||
Pointer buffer = AVUTIL.av_malloc(numBytes);
|
||||
|
||||
AVCODEC.avpicture_fill(
|
||||
frameRGB, buffer, AVCodecLibrary.PIX_FMT_RGB32, avcontext.width, avcontext.height);
|
||||
|
||||
// Convert the image from its native format to RGB
|
||||
AVCODEC.img_convert(frameRGB, AVCodecLibrary.PIX_FMT_RGB32,
|
||||
avpicture, avcontext.pix_fmt, avcontext.width,
|
||||
avcontext.height);
|
||||
|
||||
int[] data =
|
||||
frameRGB.data0.getIntArray(0, avcontext.height * avcontext.width);
|
||||
int[] outData = validateIntArraySize(outputBuffer, data.length);
|
||||
System.arraycopy(data, 0, outData, 0, data.length);
|
||||
|
||||
outputBuffer.setOffset(0);
|
||||
outputBuffer.setLength(outData.length);
|
||||
outputBuffer.setData(outData);
|
||||
|
||||
AVUTIL.av_free(encBuf);
|
||||
AVUTIL.av_free(buffer);
|
||||
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the given src array to the pointer
|
||||
* @param src the source
|
||||
* @param srcPos source offset
|
||||
* @param dest destination
|
||||
* @param destPos destination offset
|
||||
* @param length data length to be copied
|
||||
*/
|
||||
private void arraycopy(byte[] src, int srcPos, Pointer dest, int destPos,
|
||||
int length)
|
||||
{
|
||||
int count = 0;
|
||||
while (count < length)
|
||||
{
|
||||
dest.setByte(destPos++, src[srcPos++]);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkFormat(Format format)
|
||||
{
|
||||
if ((format.getEncoding()).equals(Constants.H264_RTP))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.checkFormat(format);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,447 @@
|
||||
package net.java.sip.communicator.impl.media.codec.video.h264;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.media.*;
|
||||
import javax.media.format.*;
|
||||
|
||||
import net.java.sip.communicator.impl.media.codec.*;
|
||||
import net.java.sip.communicator.util.*;
|
||||
import net.sf.ffmpeg_java.*;
|
||||
import net.sf.ffmpeg_java.AVCodecLibrary.*;
|
||||
|
||||
import com.sun.media.*;
|
||||
import com.sun.jna.*;
|
||||
|
||||
/**
|
||||
* Encodes supplied data and encapsulate it in rtp according
|
||||
* rfc RFC3984.
|
||||
* @author Damian Minkov
|
||||
*/
|
||||
public class NativeEncoder
|
||||
extends BasicCodec
|
||||
{
|
||||
private final Logger logger = Logger.getLogger(NativeEncoder.class);
|
||||
|
||||
// supported formats
|
||||
// private final static int P720_WIDTH = 720;
|
||||
// private final static int P720_HEIGHT = 480;
|
||||
// private final static int CIF4_WIDTH = 704;
|
||||
// private final static int CIF4_HEIGHT = 576;
|
||||
private final static int CIF_WIDTH = 352;
|
||||
private final static int CIF_HEIGHT = 288;
|
||||
// private final static int QCIF_WIDTH = 176;
|
||||
// private final static int QCIF_HEIGHT = 144;
|
||||
// private final static int SQCIF_WIDTH = 128;
|
||||
// private final static int SQCIF_HEIGHT = 96;
|
||||
|
||||
private final static String PLUGIN_NAME = "H264 Encoder";
|
||||
|
||||
private final static int DEF_WIDTH = CIF_WIDTH;
|
||||
private final static int DEF_HEIGHT = CIF_HEIGHT;
|
||||
|
||||
// without the headers
|
||||
private final static int MAX_PAYLOAD_SIZE = 1400;
|
||||
|
||||
private final static int INPUT_BUFFER_PADDING_SIZE = 8;
|
||||
|
||||
private final static Format[] defOutputFormats =
|
||||
{
|
||||
new VideoFormat(Constants.H264_RTP)
|
||||
};
|
||||
|
||||
// the frame rate we will use
|
||||
private final static int TARGET_FRAME_RATE = 15;
|
||||
|
||||
// Instances for the ffmpeg lib
|
||||
private AVFormatLibrary AVFORMAT;
|
||||
private AVCodecLibrary AVCODEC;
|
||||
private AVUtilLibrary AVUTIL;
|
||||
|
||||
// The codec we will use
|
||||
private AVCodec avcodec;
|
||||
private AVCodecContext avcontext;
|
||||
|
||||
// the encoded data is stored in avpicture
|
||||
private AVFrame avpicture;
|
||||
|
||||
// we use this buffer to supply data to encoder
|
||||
private Pointer encFrameBuffer;
|
||||
// the supplied data length
|
||||
private int encFrameLen;
|
||||
|
||||
private Pointer rawFrameBuffer;
|
||||
|
||||
// Vector to store NALs , when packaging encoded frame to rtp packets
|
||||
private Vector<byte[]> nals = new Vector<byte[]>();
|
||||
// last timestamp we processed
|
||||
private long lastTimeStamp = 0;
|
||||
// the current rtp sequence
|
||||
private int seq = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public NativeEncoder()
|
||||
{
|
||||
int strideY = DEF_WIDTH;
|
||||
int strideUV = strideY / 2;
|
||||
int offsetU = strideY * DEF_HEIGHT;
|
||||
int offsetV = offsetU + strideUV * DEF_HEIGHT / 2;
|
||||
|
||||
int inputYuvLength = (strideY + strideUV) * DEF_HEIGHT;
|
||||
float sourceFrameRate = TARGET_FRAME_RATE;
|
||||
|
||||
|
||||
inputFormats = new Format[]{
|
||||
new YUVFormat(new Dimension(DEF_WIDTH, DEF_HEIGHT),
|
||||
inputYuvLength + INPUT_BUFFER_PADDING_SIZE,
|
||||
Format.byteArray, sourceFrameRate, YUVFormat.YUV_420, strideY,
|
||||
strideUV, 0, offsetU, offsetV)
|
||||
};
|
||||
|
||||
inputFormat = null;
|
||||
outputFormat = null;
|
||||
|
||||
AVFORMAT = AVFormatLibrary.INSTANCE;
|
||||
AVCODEC = AVCodecLibrary.INSTANCE;
|
||||
AVUTIL = AVUtilLibrary.INSTANCE;
|
||||
|
||||
AVFORMAT.av_register_all();
|
||||
|
||||
AVCODEC.avcodec_init();
|
||||
}
|
||||
|
||||
private Format[] getMatchingOutputFormats(Format in)
|
||||
{
|
||||
VideoFormat videoIn = (VideoFormat) in;
|
||||
Dimension inSize = videoIn.getSize();
|
||||
|
||||
outputFormats =
|
||||
new VideoFormat[]
|
||||
{
|
||||
new VideoFormat(Constants.H264_RTP, inSize,
|
||||
Format.NOT_SPECIFIED, Format.byteArray, videoIn
|
||||
.getFrameRate()) };
|
||||
|
||||
return outputFormats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of formats supported at the output.
|
||||
*/
|
||||
public Format[] getSupportedOutputFormats(Format in)
|
||||
{
|
||||
// null input format
|
||||
if (in == null)
|
||||
return defOutputFormats;
|
||||
|
||||
// mismatch input format
|
||||
if (!(in instanceof VideoFormat) || null == matches(in, inputFormats))
|
||||
return new Format[0];
|
||||
|
||||
return getMatchingOutputFormats(in);
|
||||
}
|
||||
|
||||
public Format setInputFormat(Format in)
|
||||
{
|
||||
// mismatch input format
|
||||
if (!(in instanceof VideoFormat) || null == matches(in, inputFormats))
|
||||
return null;
|
||||
|
||||
VideoFormat videoIn = (VideoFormat) in;
|
||||
Dimension inSize = videoIn.getSize();
|
||||
|
||||
if (inSize == null)
|
||||
inSize = new Dimension(DEF_WIDTH, DEF_HEIGHT);
|
||||
|
||||
YUVFormat yuv = (YUVFormat) videoIn;
|
||||
|
||||
if (yuv.getOffsetU() > yuv.getOffsetV())
|
||||
return null;
|
||||
|
||||
int strideY = inSize.width;
|
||||
int strideUV = strideY / 2;
|
||||
int offsetU = strideY * inSize.height;
|
||||
int offsetV = offsetU + strideUV * inSize.height / 2;
|
||||
|
||||
int inputYuvLength = (strideY + strideUV) * inSize.height;
|
||||
float sourceFrameRate = videoIn.getFrameRate();
|
||||
|
||||
inputFormat =
|
||||
new YUVFormat(inSize, inputYuvLength + INPUT_BUFFER_PADDING_SIZE,
|
||||
Format.byteArray, sourceFrameRate, YUVFormat.YUV_420, strideY,
|
||||
strideUV, 0, offsetU, offsetV);
|
||||
|
||||
// Return the selected inputFormat
|
||||
return inputFormat;
|
||||
}
|
||||
|
||||
public Format setOutputFormat(Format out)
|
||||
{
|
||||
// mismatch output format
|
||||
if (!(out instanceof VideoFormat)
|
||||
|| null == matches(out, getMatchingOutputFormats(inputFormat)))
|
||||
return null;
|
||||
|
||||
VideoFormat videoOut = (VideoFormat) out;
|
||||
Dimension outSize = videoOut.getSize();
|
||||
|
||||
if (outSize == null)
|
||||
{
|
||||
Dimension inSize = ((VideoFormat) inputFormat).getSize();
|
||||
if (inSize == null)
|
||||
outSize = new Dimension(DEF_WIDTH, DEF_HEIGHT);
|
||||
else
|
||||
outSize = inSize;
|
||||
}
|
||||
|
||||
outputFormat =
|
||||
new VideoFormat(videoOut.getEncoding(),
|
||||
outSize, outSize.width * outSize.height,
|
||||
Format.byteArray, videoOut.getFrameRate());
|
||||
|
||||
// Return the selected outputFormat
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
public int process(Buffer inBuffer, Buffer outBuffer)
|
||||
{
|
||||
// if there are some nals we check and send them
|
||||
if(nals.size() > 0)
|
||||
{
|
||||
byte[] buf = nals.remove(0);
|
||||
//int type = buf[0] & 0x1f;
|
||||
// if nal is too big we must make FU packet
|
||||
if(buf.length > MAX_PAYLOAD_SIZE)
|
||||
{
|
||||
int bufOfset = 0;
|
||||
int size = buf.length;
|
||||
|
||||
int nri = buf[bufOfset] & 0x60;
|
||||
byte[] tmp = new byte[MAX_PAYLOAD_SIZE];
|
||||
tmp[0] = 28; /* FU Indicator; Type = 28 ---> FU-A */
|
||||
tmp[0] |= nri;
|
||||
tmp[1] = buf[bufOfset];
|
||||
tmp[1] |= 1 << 7;
|
||||
bufOfset += 1;
|
||||
size -= 1;
|
||||
int currentSIx = 0;
|
||||
while (size + 2 > MAX_PAYLOAD_SIZE)
|
||||
{
|
||||
System.arraycopy(buf, bufOfset, tmp, 2, MAX_PAYLOAD_SIZE - 2);
|
||||
|
||||
nals.add(currentSIx++, tmp.clone());
|
||||
|
||||
bufOfset += MAX_PAYLOAD_SIZE - 2;
|
||||
size -= MAX_PAYLOAD_SIZE - 2;
|
||||
tmp[1] &= ~(1 << 7);
|
||||
}
|
||||
|
||||
byte[] tmp2 = new byte[size + 2];
|
||||
tmp2[0] = tmp[0];
|
||||
tmp2[1] = tmp[1];
|
||||
tmp2[1] |= 1 << 6;
|
||||
System.arraycopy(buf, bufOfset, tmp2, 2, size);
|
||||
nals.add(currentSIx++, tmp2);
|
||||
|
||||
return INPUT_BUFFER_NOT_CONSUMED | OUTPUT_BUFFER_NOT_FILLED;
|
||||
}
|
||||
|
||||
// size is ok lets send it
|
||||
outBuffer.setData(buf);
|
||||
outBuffer.setLength(buf.length);
|
||||
outBuffer.setOffset(0);
|
||||
outBuffer.setTimeStamp(lastTimeStamp);
|
||||
outBuffer.setSequenceNumber(seq++);
|
||||
|
||||
// if more nals exist process them
|
||||
if(nals.size() > 0)
|
||||
{
|
||||
return BUFFER_PROCESSED_OK | INPUT_BUFFER_NOT_CONSUMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// its the last one from current frame, 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);
|
||||
}
|
||||
|
||||
if (inBuffer.getLength() < 10)
|
||||
{
|
||||
outBuffer.setDiscard(true);
|
||||
reset();
|
||||
return BUFFER_PROCESSED_OK;
|
||||
}
|
||||
|
||||
// copy data to avpicture
|
||||
rawFrameBuffer.write(0,
|
||||
(byte[])inBuffer.getData(), inBuffer.getOffset(), encFrameLen);
|
||||
|
||||
// encode data
|
||||
int encLen =
|
||||
AVCODEC.avcodec_encode_video(
|
||||
avcontext, encFrameBuffer, encFrameLen, avpicture);
|
||||
|
||||
byte[] r = encFrameBuffer.getByteArray(0, encLen);
|
||||
|
||||
// split encoded data to nals
|
||||
// we know it starts with 00 00 01
|
||||
int ix = 3;
|
||||
int prevIx = 1;
|
||||
while((ix = ff_avc_find_startcode(r, ix, r.length)) < r.length)
|
||||
{
|
||||
int len = ix - prevIx;
|
||||
byte[] b = new byte[len -4];
|
||||
System.arraycopy(r, prevIx+ 3, b, 0, len-4);
|
||||
nals.add(b);
|
||||
|
||||
prevIx = ix;
|
||||
ix = prevIx + 3;
|
||||
}
|
||||
|
||||
int len = ix - prevIx;
|
||||
byte[] b = new byte[len - 3];
|
||||
System.arraycopy(r, prevIx+ 3, b, 0, len-3);
|
||||
nals.add(b);
|
||||
|
||||
lastTimeStamp = inBuffer.getTimeStamp();
|
||||
|
||||
return INPUT_BUFFER_NOT_CONSUMED | OUTPUT_BUFFER_NOT_FILLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a NAL in the encoded data.
|
||||
* @param buff the encoded data
|
||||
* @param startIx starting index
|
||||
* @param endIx end index
|
||||
* @return starting position of the NAL
|
||||
*/
|
||||
private int ff_avc_find_startcode(byte[] buff, int startIx, int endIx)
|
||||
{
|
||||
while(startIx < (endIx - 3))
|
||||
{
|
||||
if(buff[startIx] == 0 &&
|
||||
buff[startIx + 1] == 0 &&
|
||||
buff[startIx + 2] == 1)
|
||||
return startIx;
|
||||
|
||||
startIx++;
|
||||
}
|
||||
return endIx;
|
||||
}
|
||||
|
||||
public synchronized void open() throws ResourceUnavailableException
|
||||
{
|
||||
if (!opened)
|
||||
{
|
||||
super.open();
|
||||
|
||||
if (inputFormat == null)
|
||||
throw new ResourceUnavailableException(
|
||||
"No input format selected");
|
||||
if (outputFormat == null)
|
||||
throw new ResourceUnavailableException(
|
||||
"No output format selected");
|
||||
|
||||
AVCODEC.avcodec_init();
|
||||
|
||||
avcodec = AVCODEC.avcodec_find_encoder(AVCodecLibrary.CODEC_ID_H264);
|
||||
avcontext = AVCODEC.avcodec_alloc_context();
|
||||
avpicture = AVCODEC.avcodec_alloc_frame();
|
||||
|
||||
avcontext.pix_fmt = AVFormatLibrary.PIX_FMT_YUV420P;
|
||||
avcontext.width = DEF_WIDTH;
|
||||
avcontext.height = DEF_HEIGHT;
|
||||
|
||||
avcontext.time_base = new FFMPEGLibrary.AVRational(1, TARGET_FRAME_RATE);
|
||||
|
||||
avpicture.linesize[0] = DEF_WIDTH;
|
||||
avpicture.linesize[1] = DEF_WIDTH / 2;
|
||||
avpicture.linesize[2] = DEF_WIDTH / 2;
|
||||
//avpicture.quality = (int)10;
|
||||
|
||||
avcontext.qcompress = 1;
|
||||
|
||||
//int _bitRate = 267000;
|
||||
//avcontext.bit_rate = (_bitRate * 3) >> 2; // average bit rate
|
||||
|
||||
AVUTIL.av_log_set_callback(new AVUtilLibrary.LogCallback(){
|
||||
|
||||
public void callback(Pointer p, int l, String fmtS,
|
||||
Pointer va_list)
|
||||
{
|
||||
logger.info("encoder: " + fmtS);
|
||||
}});
|
||||
|
||||
if (AVCODEC.avcodec_open(avcontext, avcodec) < 0)
|
||||
throw new RuntimeException("Could not open codec ");
|
||||
|
||||
opened = true;
|
||||
|
||||
encFrameLen = (DEF_WIDTH * DEF_HEIGHT * 3) / 2;
|
||||
|
||||
rawFrameBuffer = AVUTIL.av_malloc(encFrameLen);
|
||||
encFrameBuffer = AVUTIL.av_malloc(encFrameLen);
|
||||
|
||||
int size = DEF_WIDTH * DEF_HEIGHT;
|
||||
avpicture.data0 = rawFrameBuffer;
|
||||
avpicture.data1 = avpicture.data0.share(size);
|
||||
avpicture.data2 = avpicture.data1.share(size/4);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void close()
|
||||
{
|
||||
if (opened)
|
||||
{
|
||||
super.close();
|
||||
|
||||
AVCODEC.avcodec_close(avcontext);
|
||||
|
||||
AVUTIL.av_free(avpicture.getPointer());
|
||||
AVUTIL.av_free(avcontext.getPointer());
|
||||
|
||||
AVUTIL.av_free(rawFrameBuffer);
|
||||
AVUTIL.av_free(encFrameBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return PLUGIN_NAME;
|
||||
}
|
||||
|
||||
public java.lang.Object[] getControls()
|
||||
{
|
||||
if (controls == null)
|
||||
{
|
||||
controls =
|
||||
new Control[0];
|
||||
}
|
||||
|
||||
return (Object[]) controls;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue