package com.ibm.media.codec.video.h263; 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 JavaDecoder extends VideoCodec { //////////////////////////////////////////////////////////////////////////// // Constants // CIF/QCIF sizes // static final int CIFW = 352; // static final int CIFH = 288; // static final int QCIFW = 176; // static final int QCIFH = 144; // static final int [] widths = {0, 128, 176, 352, 704, 1408,0,0}; // static final int [] heights = {0, 96, 144, 288, 576, 1152,0,0}; // RGB bit masks static final private int rMask = 0x000000ff; static final private int gMask = 0x0000ff00; static final private int bMask = 0x00ff0000; static final private boolean DEBUG = false; //////////////////////////////////////////////////////////////////////////// // Variables private H263Decoder javaDecoder; private FrameBuffer outputFrame; static public final int [] widths = {0, 128, 176, 352, 704, 1408,0,0}; static public final int [] heights = {0, 96, 144, 288, 576, 1152,0,0}; private int videoWidth=176; // defualt size private int videoHeight=144; private boolean FormatSizeInitFlag=false; private int payloadLength=4; // Check if the native h263peg decoder is there. If it is, disable // this renderer by return null from setInputFormat. static boolean nativeAvail = false; static { if (plugInExists("com.sun.media.codec.video.vh263.NativeDecoder", PlugInManager.CODEC)) { try { JMFSecurityManager.loadLibrary("jmutil"); JMFSecurityManager.loadLibrary("jmvh263"); nativeAvail = true; } catch (Throwable t) { } } } public JavaDecoder() { supportedInputFormats = new VideoFormat[] {new VideoFormat(VideoFormat.H263),new VideoFormat(VideoFormat.H263_RTP) }; defaultOutputFormats = new VideoFormat[] {new RGBFormat() }; PLUGIN_NAME = "H.263 Decoder"; } protected Format[] getMatchingOutputFormats(Format in) { VideoFormat ivf = (VideoFormat) in; Dimension inSize = ivf.getSize(); int maxDataLength=ivf.getMaxDataLength(); if ( (ivf.getEncoding()).equals(VideoFormat.H263_RTP) ) { supportedOutputFormats= new VideoFormat[] { new RGBFormat (new Dimension(videoWidth,videoHeight), videoWidth * videoHeight, int[].class, ivf.getFrameRate(), 32, rMask, gMask, bMask, 1,videoWidth, Format.FALSE, // flipped Format.NOT_SPECIFIED // endian ) /*, new RGBFormat (null, Format.NOT_SPECIFIED, int[].class, 32, rMask, gMask, bMask, 1,Format.NOT_SPECIFIED )*/ }; } else { supportedOutputFormats= new VideoFormat[] { new RGBFormat (new Dimension(inSize), inSize.width * inSize.height, int[].class, ivf.getFrameRate(), 32, rMask, gMask, bMask, 1,inSize.width, Format.FALSE, // flipped Format.NOT_SPECIFIED // endian ) }; } return supportedOutputFormats; } /** * Set the data input format. * @return false if the format is not supported. */ public Format setInputFormat(Format format) { if (nativeAvail) return null; if (super.setInputFormat(format) != null) { reset(); return format; } else return null; } public void open() throws ResourceUnavailableException { initDecoder(); } public void close() { javaDecoder=null; } public void reset() { initDecoder(); } // called when video resize is detected, by checkFormat() protected void videoResized() { initDecoder(); } protected void initDecoder() { javaDecoder = new H263Decoder(true); } public int process(Buffer inputBuffer, Buffer outputBuffer) { boolean rtpData = false; 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; if ( (videoWidth!=widths[s]) || (videoHeight!=heights[s]) ) { videoWidth=widths[s]; videoHeight=heights[s]; outputFormat = new RGBFormat (new Dimension(videoWidth,videoHeight), videoWidth * videoHeight, int[].class, ivf.getFrameRate(), 32, rMask, gMask, bMask, 1,videoWidth, Format.FALSE, // flipped Format.NOT_SPECIFIED // endian ); outMaxLength = videoWidth*videoHeight; if (FormatSizeInitFlag) videoResized(); // allocate a new decoder only after it was actually used } FormatSizeInitFlag=true; } if (false == FormatSizeInitFlag) { return BUFFER_PROCESSED_FAILED; } } int[] outData = validateIntArraySize(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) { if (DEBUG) 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 boolean ret = decodeData(inputBuffer,inLength,outputBuffer,rtpData); if (ret) { updateOutput(outputBuffer,outputFormat, outMaxLength, 0); return BUFFER_PROCESSED_OK; } else { if (DEBUG) System.out.println("[JavaDecoder] : returning OUTPUT_BUFFER_NOT_FILLED; "); return OUTPUT_BUFFER_NOT_FILLED; } } boolean decodeData(Buffer inputBuffer,int inputLength,Buffer outputBuffer,boolean rtpData) { int ret; int [] outData = (int [])outputBuffer.getData(); byte[] inputData = (byte [])inputBuffer.getData(); if (inputLength <= 0) { return false; } javaDecoder.initBitstream(); int inputOffset=inputBuffer.getOffset(); if (rtpData) { // RTPHeader rtpHeader= (RTPHeader) inputBuffer.getHeader(); if (DEBUG) { System.out.println("[javadecoder:decodeData] inputBuffer.getTimeStamp()=" + inputBuffer.getTimeStamp()); // System.out.println("[javadecoder:decodeData] rtpHeader.getMarker()=" + inputBuffer.getHeader().getMarker()); } ret = javaDecoder.DecodeRtpPacket(inputData,inputOffset+payloadLength,inputLength,inputData,inputOffset,inputBuffer.getTimeStamp()); if(ret == H263Decoder.H263_RC_PICTURE_FORMAT_NOT_INITED) { if (DEBUG) System.out.println("[javadecoder:decodeData] FORMAT_NOT_INITED returing false"); return false; } } else { ret = javaDecoder.DecodePicture(inputData,inputOffset,true); } if (ret == H263Decoder.H263_RC_PICTURE_FORMAT_NOT_SUPPORTED) { if (DEBUG) System.out.println("[javadecoder:decodeData] throwing exception - format is not supported "); throw new RuntimeException("Currently this picture format is not supported!"); } if (ret == H263Decoder.H263_RC_PICTURE_DONE) { int outWidth = outputFormat.getSize().width; int outHeight = outputFormat.getSize().height; outputFrame = javaDecoder.CurrentFrame; YCbCrToRGB.convert(outputFrame.Y,outputFrame.Cb,outputFrame.Cr,outData,outputFrame.width,outputFrame.height,outWidth,outHeight,255,4); return true; } else { if (DEBUG) System.out.println("[javadecoder:decodeData] ret != H263Decoder.H263_RC_PICTURE_DONE returning false"); return false; } } static public 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; } public boolean checkFormat(Format format) { if ( (format.getEncoding()).equals(VideoFormat.H263_RTP) ) { return true; } else { return super.checkFormat(format); } } }