/*
* @(#)NativeEncoder.java 1.26 03/04/24
*
* Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved.
*/
/*
* Licensed Materials - Property of IBM
* "Restricted Materials of IBM"
* 5648-B81
* (c) Copyright IBM Corporation 1997,1998 All Rights Reserved
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with
* IBM Corporation.
*
*/
package com.ibm.media.codec.video.h263;
import javax.media.*;
import javax.media.format.*;
import javax.media.format.*;
import com.sun.media.*;
import com.sun.media.controls.*;
import com.ibm.media.codec.video.*;
import java.awt.Dimension;
import javax.media.rtp.*;
import javax.media.control.*;
import java.awt.*;
import java.awt.event.*;
public class NativeEncoder extends VideoCodec {
////////////////////////////////////////////////////////////////////////////
// Constants
/**
* Licensed Materials - Property of IBM
* "Restricted Materials of IBM"
* 5648-B81
* (c) Copyright IBM Corporation 1997,1999 All Rights Reserved
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with
* IBM Corporation.
*
**/
public static final String a_copyright_notice="(c) Copyright IBM Corporation 1997, 1999.";
////////////////////////////////////////////////////////////////////////////
// Variables
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;
int nativeFormat=0; // native representation of format
// Variables & constants needed for the RTP
// final static int DEFAULT_RTP_MTU = 576; //XXX
// set default to 1024 - RTP size (12) - UDP size (8) - IP size (20)
final static int DEFAULT_RTP_MTU = 984;
final static int MAX_RTP_MTU = 1456;
final static int DEFAULT_MAX_OUTPUT_LENGTH = 40960; // should be ported from the native dll export
public int maxOutputLength;
public int targetOutputLength;
long timeStamp = Buffer.TIME_UNKNOWN; // change to random ?
long sequenceNumber = 0; // change to random ?
long deltaFrames = (long) (1000000.0/29.97); // in Nano Seconds
boolean useRtp = false;
Control[] controls=null;
float sourceFrameRate=0;
float targetFrameRate=0;
int minBitRate=5000;
int maxBitRate=1000000;
int useBitRate=20000;
int iFramePeriod=15;
int frameDecimation=1;
int frame2skip=0;
boolean initCompleted=false;
boolean settingsChanged = false;
boolean dropFrame = false;
boolean okToDrop = false;
EncodeControl encodeControl = null;
////////////////////////////////////////////////////////////////////////////
// Native interface
// private native boolean initNativeEncoder(int nativeFormat,boolean rtpSessionFlag);
private native boolean initNativeEncoder(int nativeFormat,
int MTUPacketSize,
float sourceFrameRate,
float targetFrameRate,
int targetBitRate,
int IFramePeriod);
/* new Init to push all controls into PUSER
nativeFormat
MTUPacketSize(0 for non RTP sessions)
sourceFrameRate
targetFrameRate
targetBitRate
IFramePeriod
*/
private native boolean setFramesBehind(int numOfFrames);
private native boolean setQuality(float quality);
private native boolean encodeFrameNative(Buffer in, Buffer out);
private native boolean closeNativeEncoder();
// java variables to be accessed by native
private int nativeData = 0;
private int prevTr =0; //previous temporal reference
private int tr; // current temporal reference
public boolean frameDone = true ; // we start a new frame
private int outputLength;
private boolean interFlag;
// Constructor
public NativeEncoder() {
/// System.out.println("[h263Encoder:Constructor]");
supportedInputFormats = new VideoFormat[] {
new YUVFormat(YUVFormat.YUV_420)
};
defaultOutputFormats = new VideoFormat[] {
new VideoFormat(VideoFormat.H263),
new VideoFormat(VideoFormat.H263_RTP)
};
PLUGIN_NAME = "H.263 Encoder";
}
public Format setInputFormat(Format format) {
YUVFormat ivf = (YUVFormat) super.setInputFormat(format);
if (ivf==null)
return null;
Dimension inSize = ivf.getSize();
if (inSize==null)
return null;
if (ivf.getOffsetU() > ivf.getOffsetV())
return null;
videoWidth = inSize.width;
videoHeight = inSize.height;
sourceFrameRate=ivf.getFrameRate();
deltaFrames = (long) (1000000.0/ivf.getFrameRate()); // in Nano Seconds
if (opened) {
VideoFormat newOut;
newOut =
new VideoFormat (
outputFormat.getEncoding(),
new Dimension(inSize),
Format.NOT_SPECIFIED,
Format.byteArray,
ivf.getFrameRate());
close();
setOutputFormat(newOut);
try {
open();
} catch (ResourceUnavailableException re) {
return null;
}
}
return format;
}
/** Hagai
* overides VideoCodec.setOutputFormat in order to init RTP variables
*/
public Format setOutputFormat(Format format) {
VideoFormat f = (VideoFormat)super.setOutputFormat(format);
if (f.getMaxDataLength() == Format.NOT_SPECIFIED) {
if (f.getEncoding().equals(VideoFormat.H263_RTP)) {
useRtp = true;
maxOutputLength = MAX_RTP_MTU;
targetOutputLength = DEFAULT_RTP_MTU;
} else {
useRtp = false;
maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
targetOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
}
f = new VideoFormat(f.getEncoding(),f.getSize(),maxOutputLength,Format.byteArray,f.getFrameRate());
targetFrameRate=f.getFrameRate();
frameDecimation= (int)(sourceFrameRate/targetFrameRate);
useBitRate = (int) ((targetFrameRate * f.getSize().width *
f.getSize().height) / 5);
f = (VideoFormat)super.setOutputFormat(f);
}
return f; // How do we define who is responsible for initiating the MTU
}
protected Format[] getMatchingOutputFormats(Format in) {
VideoFormat ivf = (VideoFormat) in;
Dimension inSize = ivf.getSize();
if (inSize==null)
return null;
videoWidth = inSize.width;
videoHeight = inSize.height;
// Amith - allow only default frame rate
supportedOutputFormats= new VideoFormat[2];
for (int i=0;i<2;) {
float useFrameRate=ivf.getFrameRate();
if (i==2)
useFrameRate /= 2.0F;
if (i==4)
useFrameRate /= 3.0F;
supportedOutputFormats[i++]=
new VideoFormat (
VideoFormat.H263,
new Dimension(inSize),
Format.NOT_SPECIFIED,
Format.byteArray,
useFrameRate);
supportedOutputFormats[i++]=
new VideoFormat ( //Hagai
VideoFormat.H263_RTP,
new Dimension(inSize),
Format.NOT_SPECIFIED,
Format.byteArray,
useFrameRate);
}
return supportedOutputFormats;
}
public void open() throws ResourceUnavailableException {
// Validate sizes here.
// Native format
//
// |FRAME FORMAT width height image_format |
// |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
// |SQCIF 128 96 0 |
// |QCIF (PAL) 176 144 1 |
// |CIF (PAL) 352 288 2 |
///===add support for non standard frame size here==
if ( (videoWidth==128) && (videoHeight==96 ) ) {
nativeFormat=0;
} else if ( (videoWidth==176) && (videoHeight==144) ) {
nativeFormat=1;
} else if ( (videoWidth==352) && (videoHeight==288) ) {
nativeFormat=2;
} else {
Log.error("Class: " + this);
Log.error(" can only encode in sizes: 128x96, 176x144, 352x288.");
throw new ResourceUnavailableException("could not load jmvh263");
}
try {
JMFSecurityManager.loadLibrary("jmutil");
JMFSecurityManager.loadLibrary("jmh263enc");
//initFrame();
// initEncoder();
super.open();
if (encodeControl != null)
encodeControl.open(controls);
return;
}
catch (Throwable e) {
System.out.println(e);
}
//System.out.println("[h263encoder::open]could not load jmvh263");
throw new ResourceUnavailableException("could not load jmvh263");
}
synchronized public void close() {
// System.out.println("[h263Encoder:close]");
if (encodeControl != null)
encodeControl.close();
closeNativeEncoder();
super.close();
}
protected void finalize() {
if (encodeControl != null) {
encodeControl.frame.dispose();
encodeControl = null;
}
}
private synchronized void closeNative() {
closeNativeEncoder();
}
public final synchronized void reset() {
// System.out.println("[h263Encoder:reset]");
initEncoder();
settingsChanged = false;
}
// called when video resize is detected, by checkFormat()
protected void videoResized() {
// System.out.println("[h263Encoder:videoResized]");
initEncoder();
}
protected void initEncoder() {
if (maxOutputLength != outputFormat.getMaxDataLength() ) {
VideoFormat f=outputFormat;
outputFormat = new VideoFormat(f.getEncoding(),f.getSize(),maxOutputLength,Format.byteArray,f.getFrameRate());
// System.out.println("of "+outputFormat);
}
/*
System.out.println("[h263Encoder:initEncoder]");
System.out.println("nativeFormat="+ nativeFormat);
System.out.println("sourceFrameRate="+ sourceFrameRate);
System.out.println("targetFrameRate="+ targetFrameRate);
System.out.println("useBitRate="+ useBitRate);
System.out.println("iFramePeriod="+ iFramePeriod);
System.out.println("maxOutputLength="+ maxOutputLength);
System.out.println("targetOutputLength="+ targetOutputLength);
System.out.println("useRTP="+useRtp);
*/
closeNative();
// System.out.println("[h263Encoder:initEncoder] : closed");
// initNativeEncoder(nativeFormat,useRtp);
if (useRtp)
initNativeEncoder(nativeFormat,
targetOutputLength, //MTUPacketSize
sourceFrameRate,
targetFrameRate,
useBitRate, //targetBitRate,
iFramePeriod );//IFramePeriod
else
initNativeEncoder(nativeFormat,
0, // "No RTP"
sourceFrameRate,//sourceFrameRate,
targetFrameRate,//targetFrameRate,
useBitRate, //targetBitRate,
iFramePeriod);//IFramePeriod
// System.out.println("[h263Encoder:initEncoder] : done");
}
synchronized public int process(Buffer inputBuffer, Buffer outputBuffer) {
if (!initCompleted) {
// System.out.println("init encoder");
initCompleted=true;
initEncoder();
}
// Drop frame when necessary.
if (okToDrop && dropFrame) {
dropFrame = false;
outputBuffer.setDiscard(true);
if (settingsChanged)
reset();
return BUFFER_PROCESSED_OK;
}
okToDrop = false;
// System.out.println("[h263Encoder:process]");
if ( frameDone==true) {
// System.out.println(frame2skip);
frame2skip++;
if (frame2skip!=frameDecimation) {
updateOutput(outputBuffer,outputFormat, 0 , 0);
// outputBuffer.setDiscard(true);
return OUTPUT_BUFFER_NOT_FILLED;
}
frame2skip=0;
if (!checkInputBuffer(inputBuffer) ) {
return BUFFER_PROCESSED_FAILED;
}
if (isEOM(inputBuffer) ) {
propagateEOM(outputBuffer);
okToDrop = true;
if (settingsChanged)
reset();
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();
if (outMaxLength < MAX_RTP_MTU)
outMaxLength = MAX_RTP_MTU;
byte[] inData =(byte[]) inputBuffer.getData();
byte[] outData = validateByteArraySize(outputBuffer,outMaxLength );
boolean ret = encodeFrameNative(inputBuffer,outputBuffer);
// if (frameDone == false)
// System.out.println("[NativeEncoder:process] tr=" +tr + " outputLength= "+ outputLength + " timeStamp=" + inputBuffer.getTimeStamp() );
if (outputLength>=0)
outputBuffer.setLength(outputLength);
// update the output buffer fileds that are needed within the RTP header.
if (useRtp) {
outputBuffer.setSequenceNumber(sequenceNumber);
sequenceNumber++;
// RTPHeader header = (RTPHeader)outputBuffer.getHeader(); // this should have been done elsewhere ??
// if (null == header)
// header = new RTPHeader();
if (true == frameDone) {
timeStamp = inputBuffer.getTimeStamp();
// The above libe should be repaced with the line bellow if there is a possibility that the input
// Buffer does not have a timeStamp
// timeStamp += ((long)((tr-prevTr)&0x000000ff))*deltaFrames;
prevTr= tr;
int flags = outputBuffer.getFlags();
flags |= Buffer.FLAG_RTP_MARKER;
outputBuffer.setFlags(flags);
}
else {
}
// No need to set the time stamp.
//outputBuffer.setTimeStamp(timeStamp);
} else { // set key frame for intra frames
int flags = outputBuffer.getFlags();
if (!interFlag)
flags |= Buffer.FLAG_KEY_FRAME;
else
flags &= ~Buffer.FLAG_KEY_FRAME;
outputBuffer.setFlags(flags);
}
// note that encoder update output length
updateOutput(outputBuffer,outputFormat, outputBuffer.getLength() , 0);
// Amith - added this because outputFormat is defined in both BasicCodec
// and VideoCodec
outputBuffer.setFormat(outputFormat);
if (true == frameDone)
if (outputLength !=-1) {
okToDrop = true;
if (settingsChanged)
reset();
return BUFFER_PROCESSED_OK;
} else {
// System.out.println("[NativeEncoder:process]Last Gob Does not fit intop MTU");
return (OUTPUT_BUFFER_NOT_FILLED);
}
else
if (outputLength ==-1) { // a gob doesn't fit in an MTU
// System.out.println("[NativeEncoder:process] A gob doesn't fit in an MTU");
return (INPUT_BUFFER_NOT_CONSUMED|OUTPUT_BUFFER_NOT_FILLED);
}
else
return INPUT_BUFFER_NOT_CONSUMED;
} // end of process
public java.lang.Object[] getControls() {
if (controls==null) {
controls=new Control[8];
controls[0]=new H263Adapter(this, false, false,false,false,false,0,1000,false);
controls[1]=new BitRateAdapter(this,useBitRate,minBitRate,maxBitRate,true);
controls[2]=new KeyFrameAdapter(this,iFramePeriod,true);
controls[3]=new QualityAdapter(this,1.0F,0.0F,1.0F,false,true);
controls[4]=new FrameRateAdapter(this,targetFrameRate,
sourceFrameRate/3,
sourceFrameRate,true);
controls[5]=new FrameProcessingAdapter(this);
controls[6]=new PacketSizeAdapter(this,targetOutputLength,true);
encodeControl = new EncodeControl(controls);
controls[7]= encodeControl;
}
return (Object[])controls;
}
}
class EncodeControl implements javax.media.Control {
Control [] controls;
Button button;
Frame frame = null;
public EncodeControl(Control [] controls) {
open(controls);
}
public void open(Control [] controls) {
this.controls = controls;
createFrame();
}
public void close() {
synchronized (this) {
controls = null;
if (frame != null) {
frame.removeAll();
frame.setVisible(false);
}
}
}
private void createFrame() {
synchronized (this) {
if (frame != null)
frame.removeAll();
else
frame = new Frame("H.263 Control");
}
frame.setLayout(new com.sun.media.controls.VFlowLayout(1));
frame.add(new Label( "H.263 Control",Label.CENTER) );
frame.add(new Label( " "));
for (int i=0;i<controls.length;i++) {
if (controls[i] != null && controls[i] != this)
frame.add(controls[i].getControlComponent() );
}
//System.out.println(c.length);
frame.pack();
frame.addWindowListener( new WindowAdapter() {
public void windowClosing(WindowEvent we) {
frame.setVisible(false);
}
} );
}
public Component getControlComponent() {
if (button == null) {
button = new Button("H.263 Encoding Controls");
button.setName("H.263 Control");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
createFrame();
frame.setVisible( true );
}
} );
}
return button;
}
}
class BitRateAdapter extends com.sun.media.controls.BitRateAdapter implements Owned{
NativeEncoder owner;
public BitRateAdapter(NativeEncoder owner,int initialBitRate, int minBitRate,
int maxBitRate, boolean settable) {
super(initialBitRate, minBitRate ,maxBitRate, settable);
this.owner=owner;
}
public java.lang.Object getOwner() {
return (Object) owner;
}
public int setBitRate(int newValue) {
owner.useBitRate=super.setBitRate(newValue);
owner.settingsChanged = true;
return owner.useBitRate;
}
}
class KeyFrameAdapter extends com.sun.media.controls.KeyFrameAdapter implements Owned{
NativeEncoder owner;
public KeyFrameAdapter(Codec owner,int preferredInterval, boolean settable) {
super(preferredInterval, settable);
this.owner=(NativeEncoder)owner;
}
public int setKeyFrameInterval(int newValue) {
owner.iFramePeriod=super.setKeyFrameInterval(newValue);
owner.settingsChanged = true;
return owner.iFramePeriod;
}
public java.lang.Object getOwner() {
return (Object) owner;
}
}
class QualityAdapter extends com.sun.media.controls.QualityAdapter implements Owned{
Codec owner;
public QualityAdapter(Codec owner,float preferred, float min, float max,boolean isTSsupported,
boolean settable) {
super( preferred, min, max, isTSsupported,settable);
this.owner=owner;
}
public java.lang.Object getOwner() {
return (Object) owner;
}
}
class FrameRateAdapter extends com.sun.media.controls.FrameRateAdapter {
public FrameRateAdapter(Object owner, float initialFrameRate, float minFrameRate,
float maxFrameRate, boolean settable) {
super( owner, initialFrameRate, minFrameRate, maxFrameRate,settable);
}
public float setFrameRate(float frameRate) {
NativeEncoder owner=(NativeEncoder)super.owner;
int skipFrames=(int)(owner.sourceFrameRate / frameRate);
float useFrameRate= owner.sourceFrameRate / skipFrames;
if (useFrameRate>max)
useFrameRate=max;
if (useFrameRate<min)
useFrameRate=min;
owner.targetFrameRate=super.setFrameRate(useFrameRate);
owner.settingsChanged = true;
return owner.targetFrameRate;
}
}
class PacketSizeAdapter extends com.sun.media.controls.PacketSizeAdapter {
public PacketSizeAdapter(Codec owner, int packetSize, boolean settable) {
super( owner, packetSize, settable);
}
public int setPacketSize(int numBytes) {
NativeEncoder owner=(NativeEncoder)super.owner;
if ((numBytes<200) ||
(numBytes>owner.MAX_RTP_MTU) ||
(!owner.useRtp) )
return owner.targetOutputLength;
// int useBytes=super.setPacketSize(numBytes); // Just returns current!
int useBytes=numBytes;
//System.out.println("[H263-RTP]new Packet size "+useBytes);
owner.targetOutputLength=useBytes;
if (owner.targetOutputLength>owner.maxOutputLength)
owner.maxOutputLength=owner.targetOutputLength;
owner.settingsChanged = true;
// set the value in the adapter so it is returned by getPacketSize
packetSize=useBytes;
return useBytes;
}
}
class FrameProcessingAdapter implements FrameProcessingControl,Owned {
NativeEncoder owner;
public FrameProcessingAdapter(NativeEncoder owner) {
this.owner=owner;
}
public boolean setMinimalProcessing(boolean newMinimal) {
// minimal = newMinimal;
// return minimal;
return true;
}
public void setFramesBehind(float frames) {
if (frames >= 1)
owner.dropFrame = true;
else
owner.dropFrame = false;
}
public int getFramesDropped() {
return 0; ///XXX not implemented
}
public Component getControlComponent() {
return new Label("Frame processing");
}
public Object getOwner() {
return (Object)owner;
}
}
|