/*
* @(#)NativeDecoder.java 1.17 02/08/21
*
* Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.media.codec.video.vh263;
import javax.media.*;
import javax.media.format.*;
import javax.media.format.*;
import com.sun.media.*;
import com.ibm.media.codec.video.*;
import java.awt.Dimension;
import javax.media.rtp.*;
public class NativeDecoder extends VideoCodec {
////////////////////////////////////////////////////////////////////////////
// Native methods
private static native boolean initNativeDecoderClass();
private native boolean initNativeDecoder(int width,int height);
private native boolean decodeFrameNative(Buffer in, Buffer out);
private native boolean decodePacketNative(byte[] in,int inputOffset,int inputLength, byte[] out,byte[] payloadHeader,int payloadOffset,int sync, int h263_1998);
private native boolean closeNativeDecoder();
////////////////////////////////////////////////////////////////////////////
// Variables
//// start: the following variables are accessed by the native code
/** pointer to the native structure of H263 decoder **/
private int nativeData;
/** pointer to the native structure of the picture descriptor **/
private int pictureDesc;
private int PBFrameCap = 1;
private int bsStart;
private int nextGOB;
private int pendingFrame = 0;
private static int MAX_SEQ = 65535;
private long prevSeq = -1;
/*
* Generate debug messages
*/
private static final boolean debug = false;
//// end: the following variables are accessed by the native code
static final int [] widths = {0, 128, 176, 352, 704, 1408,0,0};
static final int [] heights = {0, 96, 144, 288, 576, 1152,0,0};
private int videoWidth=176; // defualt size
private int videoHeight=144;
//private int videoWidth=352; // defualt size
//private int videoHeight=288;
private boolean FormatSizeInitFlag=false;
private int payloadLength=4;
public NativeDecoder() {
supportedInputFormats = new VideoFormat[] {
new VideoFormat(VideoFormat.H263),
new VideoFormat(VideoFormat.H263_RTP),
new VideoFormat(VideoFormat.H263_1998_RTP)
};
defaultOutputFormats = new VideoFormat[] {new YUVFormat() };
PLUGIN_NAME = "H.263 Decoder";
}
protected Format[] getMatchingOutputFormats(Format in) {
VideoFormat ivf = (VideoFormat) in;
Dimension inSize = ivf.getSize();
int inMaxDataLength=ivf.getMaxDataLength();
int outNumOfPixels;
if ( ( (ivf.getEncoding()).equals(VideoFormat.H263_RTP) )
|| ( (ivf.getEncoding()).equals(VideoFormat.H263_1998_RTP) ) ) {
if (inSize == null)
outNumOfPixels = videoWidth * videoHeight;
else {
outNumOfPixels = inSize.width * inSize.height;
videoWidth = inSize.width;
videoHeight = inSize.height;
}
outNumOfPixels=videoWidth*videoHeight;
supportedOutputFormats= new VideoFormat[] {
new YUVFormat (
new Dimension(videoWidth,videoHeight),
(outNumOfPixels)+ ((outNumOfPixels) >> 1),
Format.byteArray,
ivf.getFrameRate(),
YUVFormat.YUV_420 ,
videoWidth,
videoWidth>>1,
0,
outNumOfPixels,
outNumOfPixels+(outNumOfPixels >>2)
)};
}
else {
Dimension outSize=movieSizeTo263Size(inSize);
outNumOfPixels=outSize.width*outSize.height;
videoWidth=outSize.width;
videoHeight=outSize.height;
supportedOutputFormats= new VideoFormat[] {
new YUVFormat (
inSize,
(outNumOfPixels)+ ((outNumOfPixels) >> 1),
Format.byteArray,
ivf.getFrameRate(),
YUVFormat.YUV_420 ,
outSize.width,
outSize.width>>1,
0,
outNumOfPixels,
outNumOfPixels+(outNumOfPixels >>2)
) };
}
return supportedOutputFormats;
}
public Format setInputFormat(Format input) {
Format ret = super.setInputFormat(input);
if (ret == null)
return null;
if (opened) {
close();
try {
setOutputFormat(getMatchingOutputFormats(input)[0]);
open();
} catch (Exception e) {
return null;
}
}
return ret;
}
public void open() throws ResourceUnavailableException {
try {
JMFSecurityManager.loadLibrary("jmutil");
JMFSecurityManager.loadLibrary("jmvh263");
initNativeDecoderClass();
initDecoder();
super.open();
return;
} catch (Throwable e) {
//System.out.println(e);
}
throw new ResourceUnavailableException("could not load jmvh263");
}
public void close() {
closeNativeDecoder();
super.close();
}
public void reset() {
//we do not initialize the decoder as the first frame is key frame
}
// called when video resize is detected, by checkFormat()
protected void videoResized() {
initDecoder();
}
protected void initDecoder() {
closeNativeDecoder(); // close the decoder if it was openned
initNativeDecoder(videoWidth,videoHeight);
}
public int process(Buffer inputBuffer, Buffer outputBuffer) {
boolean rtpData = false;
boolean rtp1998Data = false;
boolean ret=false;
RTPHeader rtpHeader=null;
int newWidth=videoWidth;
int newHeight=videoHeight;
if (!checkInputBuffer(inputBuffer) ) {
return BUFFER_PROCESSED_FAILED;
}
if (isEOM(inputBuffer) ) {
propagateEOM(outputBuffer);
return BUFFER_PROCESSED_OK;
}
VideoFormat ivf=(VideoFormat) inputBuffer.getFormat();
int inLength=inputBuffer.getLength();
int inMaxLength=ivf.getMaxDataLength();
int outMaxLength=outputFormat.getMaxDataLength();
int inputOffset=inputBuffer.getOffset();
byte[] inData =(byte[]) inputBuffer.getData();
if ( (ivf.getEncoding()).equals(VideoFormat.H263_RTP) ) {
rtpData = true;
payloadLength=getPayloadHeaderLength(inData,inputOffset);
if ( (inData[inputOffset+payloadLength] == 0) && (inData[inputOffset+payloadLength+1] == 0) && ((inData[inputOffset+payloadLength+2] & 0xfc) == 0x80)) {
int s = (inData[inputOffset+payloadLength+4] >> 2) & 0x7;
newWidth=widths[s];
newHeight=heights[s];
FormatSizeInitFlag=true;
}
}
else if ( (ivf.getEncoding()).equals(VideoFormat.H263_1998_RTP) ) {
rtp1998Data = true;
int s = -1;
int picOffset = -1;
if (getSequenceDiff(prevSeq, inputBuffer.getSequenceNumber()) != 1) {
boolean discard = ((inData[inputOffset] & 0x04) == 0);
// Not part of the continuation, report lost packet
if (debug) {
System.err.println(
"NativeDecoder: Sequence out of order, expecting "
+ (prevSeq+1) + " got "
+ inputBuffer.getSequenceNumber()
+ (discard ? " discard packet" : ""));
}
if (discard)
return OUTPUT_BUFFER_NOT_FILLED;
}
prevSeq = inputBuffer.getSequenceNumber();
payloadLength=get1998PayloadHeaderLength(inData,inputOffset);
if (payloadLength > 5) {
// Use PIC header in payload header
if ( ((inData[inputOffset] & 0x02) == 0x02)
&& ((inData[inputOffset+3] & 0xfc) == 0x80)) {
picOffset = inputOffset + 3;
} else if ((inData[inputOffset+2] & 0xfc) == 0x80) {
picOffset = inputOffset + 2;
}
} else if ( ((inData[inputOffset] & 0x04) == 0x04)
&& ((inData[inputOffset+payloadLength] & 0xfc) == 0x80)) {
picOffset = inputOffset + payloadLength;
}
if (picOffset >= 0) {
s = (inData[picOffset+2] >> 2) & 0x7;
if (s == 7) {
// Extended PTYPE, picture size is in the extension
// if UFEP = 001
if (((inData[picOffset+3] >> 1) & 0x07) == 1) {
s = ((inData[picOffset+3] << 2) & 0x04) |
((inData[picOffset+4] >> 6) & 0x03);
} else {
s = -1; // picture type not present
}
}
}
if (s >= 0) {
newWidth=widths[s];
newHeight=heights[s];
FormatSizeInitFlag=true;
}
}
if ( (videoWidth!=newWidth) || (videoHeight!=newHeight) ) {
videoWidth=newWidth;
videoHeight=newHeight;
int outNumOfPixels=videoWidth*videoHeight;
outputFormat = new YUVFormat (
new Dimension(videoWidth,videoHeight),
(outNumOfPixels)+ ((outNumOfPixels) >> 1),
Format.byteArray,
ivf.getFrameRate(),
YUVFormat.YUV_420 ,
videoWidth,
videoWidth>>1,
0,
outNumOfPixels,
outNumOfPixels+(outNumOfPixels >>2)
);
outMaxLength = outputFormat.getMaxDataLength();
// outMaxLength = videoWidth * videoHeight;
videoResized();
FormatSizeInitFlag=true;
}
if ( (rtpData || rtp1998Data) && !FormatSizeInitFlag) {
return BUFFER_PROCESSED_FAILED;
}
byte[] outData = validateByteArraySize(outputBuffer,outMaxLength );
/*
* <PATCH> check for insufficient input:
* The decoder might read up to 8 additional bytes before checking for EOS
* It does not bother the native code, but can cause ArrayOutOfBounds
* in Java code
*/
if ( (inLength+8+inputOffset)>inData.length) {
//System.out.println("allocating more data for H.263");
int newLength=(inLength > inMaxLength) ? inLength : inMaxLength;
byte[] tempArray=new byte[inputOffset+newLength+8];
System.arraycopy(inData,0,tempArray,0,inLength+inputOffset);
inData=tempArray;
inputBuffer.setData(tempArray);
//inputBuffer.setOffset(0);
}
/*
* <PATCH> pad input with EOS
*/
inData[inputOffset+inLength] = 0;
inData[inputOffset+inLength+1] = 0;
inData[inputOffset+inLength+2]= (byte) 0xfc;
inLength += 3;
inputBuffer.setLength(inLength);
if (rtpData) {
inLength-=payloadLength; // this is the length of the bitstream
//rtpHeader= (RTPHeader) inputBuffer.getHeader();
int marker = 0;
if ((inputBuffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0)
marker = 1;
ret = decodePacketNative(inData,inputOffset+payloadLength,inLength,outData,inData,inputOffset,marker,0);
} else
if (rtp1998Data) {
inLength-=payloadLength; // this is the length of the bitstream
//rtpHeader= (RTPHeader) inputBuffer.getHeader();
int marker = 0;
if ((inputBuffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0) {
marker = 1;
} else {
inLength -= 3; // skip the EOS pad
}
ret = decodePacketNative(inData,inputOffset+payloadLength,inLength,outData,inData,inputOffset,marker,1);
}
else {
ret = decodeFrameNative(inputBuffer, outputBuffer);
}
if (ret) {
updateOutput(outputBuffer,outputFormat, outMaxLength, 0);
outputBuffer.setFormat(outputFormat);
return BUFFER_PROCESSED_OK;
}
else {
return OUTPUT_BUFFER_NOT_FILLED;
}
}
private Dimension movieSizeTo263Size(Dimension movieSize) {
int width = (movieSize.width + 0xf) & 0xfffffff0;
int height = (movieSize.height + 0xf) & 0xfffffff0;
int formatCap = (width * height) >> 8;
/* transform the number of macroblocks into format*/
if (formatCap <= 48)
return new Dimension(128,96); // SQCIF
if (formatCap <= 99)
return new Dimension(176,144); // QCIF
if (formatCap <= 396)
return new Dimension(352,288); // CIF
if (formatCap <= 1584)
return new Dimension(704,576); // 4CIF
if (formatCap <= 6336)
return new Dimension(1408,1152); // 16CIF
return new Dimension(0,0); // ERROR
}
private int getPayloadHeaderLength(byte[] input,int offset) {
int l = 0;
byte b = input[offset];
if ( (b & 0x80) != 0) { //mode B or C
if ((b & 0x40) != 0) //mode C
l = 12;
else //mode B
l = 8;
} else { //mode A
l = 4;
}
return l;
}
static public int get1998PayloadHeaderLength(byte[] input,int offset) {
int l = 2 + ((input[offset]&0x01) << 5) | ((input[offset+1]&0xf8) >> 3);
if ( (input[offset] & 0x02) != 0) { // Video Redundancy present
l++;
}
return l;
}
public boolean checkFormat(Format format) {
if ( (format.getEncoding()).equals(VideoFormat.H263_RTP) ) {
return true;
}
else if ( (format.getEncoding()).equals(VideoFormat.H263_1998_RTP) ) {
return true;
}
else {
return super.checkFormat(format);
}
}
/*
* the RTP sequence number is unsigned 16 bit counter that
* wraps around. Allow for the case where it has wrapped.
* @param p sequence number of the suspected previous packet
* @param c sequence number of the current (or next) packet
* @return int difference in sequence numbers
*/
private int getSequenceDiff(long p, long c) {
if (c > p)
return (int) (c - p);
if (c == p)
return 0;
if (p > MAX_SEQ - 100 && c < 100) {
// Allow for the case where sequence number has wrapped.
return (int) ((MAX_SEQ - p) + c + 1);
}
return (int) (c - p);
}
}
|