/*
* @(#)NativeEncoder.java 1.41 00/11/06
*
* Copyright 1998 by Sun Microsystems, Inc.,
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Sun Microsystems, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Sun.
*/
package com.sun.media.codec.video.jpeg;
import javax.media.*;
import javax.media.Format;
import javax.media.format.*;
import javax.media.control.*;
import java.awt.Dimension;
import java.awt.Component;
import com.sun.media.*;
import com.sun.media.util.*;
import com.sun.media.format.AviVideoFormat;
import com.sun.media.controls.QualityAdapter;
import javax.media.rtp.RTPHeader;
public class NativeEncoder extends BasicCodec {
// I/O formats
private RGBFormat inputFormat = null;
private VideoFormat outputFormat = null;
// Have we loaded the native library?
private static boolean loaded = false;
// Assume we can load it.
private static boolean canLoad = true;
// Pointer to native structure
private int peer = 0;
private static int QSCALE = 99;
private float quality = 0.6f;
private float prevQuality = 0;
private boolean isMotionJPEG = false;
private boolean firstFrame = true;
// default packet size of JPEG payload for RTP. can be changed
// using encoding controls ?
private int PACKET_SIZE = 960;
// current sequence number on RTP format packets.
private int currentSeq = (int) System.currentTimeMillis();
// current timestamp on RTP format packets.
private long timestamp = (long) (System.currentTimeMillis() * Math.random());
// output buffer in which encoded JPEG RTP buffer is stored
// data will be copied from this buffer into output buffer until
// entire compressed RTP_JPEG frame has been packetized into
// smaller RTP packets.
private byte[] rtp_data = null;
static Integer processLock = new Integer(1);
// next 4 varaibles used for RTP packetization
private int copyLength = PACKET_SIZE;
private boolean newframe = true;
private int current_offset = 0;
int returnVal = 0;
private boolean dropFrame = false;
private boolean minimal = false;
// default frame rate for RTP JPEG format. Should normally be
// specified when frame rate is set.
private static final int DEFAULT_FRAMERATE = 15;
/****************************************************************
* Codec Methods
****************************************************************/
// Initialize default formats.
public NativeEncoder() {
inputFormats = new RGBFormat[1];
inputFormats[0] = new RGBFormat();
outputFormats = new VideoFormat[2];
outputFormats[0] = new JPEGFormat();
outputFormats[1] = new VideoFormat(VideoFormat.MJPG);
class QCA extends QualityAdapter implements Owned {
public QCA() {
super(0.6f, 0f, 1f, true);
}
public float setQuality(float newValue) {
quality = super.setQuality(newValue);
return quality;
}
public String getName() {
return "JPEG Quality";
}
public Object getOwner() {
return NativeEncoder.this;
}
}
QualityControl qualityControl = new QCA();
FrameProcessingControl fpc = new FrameProcessingControl() {
public boolean setMinimalProcessing(boolean newMinimal) {
minimal = newMinimal;
return minimal;
}
public void setFramesBehind(float frames) {
if (frames >= 1)
dropFrame = true;
else
dropFrame = false;
}
public Component getControlComponent() {
return null;
}
public int getFramesDropped() {
return 0; ///XXX not implemented
}
};
controls = new Control[2];
controls[0] = qualityControl;
controls[1] = fpc;
}
protected Format getInputFormat() {
return inputFormat;
}
protected Format getOutputFormat() {
return outputFormat;
}
private byte [] mjpgExtraBytes = new byte[] {
44, 0, 0, 0,
24, 0, 0, 0,
0, 0, 0, 0,
2, 0, 0, 0,
8, 0, 0, 0,
2, 0, 0, 0,
1, 0, 0, 0,
};
protected float qfToQuality(float quality) {
if (quality < 0.1f)
quality = 0.1f;
if (isMotionJPEG)
return quality * (float) QSCALE;
if (quality < 0.8f) {
return quality * 1.25f * QSCALE;
} else {
return (float) QSCALE;
}
}
protected int qfToDecimation(float quality) {
if (isMotionJPEG)
return 2;
if (quality >= 0.8f) {
if (quality >= 0.90f)
return 4;
else
return 2;
} else
return 1;
}
protected int qfToType(float quality) {
switch (qfToDecimation(quality)) {
case 1:
return JPEGFormat.DEC_420;
case 2:
return JPEGFormat.DEC_422;
case 4:
return JPEGFormat.DEC_444;
}
return 1;
}
// Return supported output formats
public Format [] getSupportedOutputFormats(Format in) {
if (in == null)
return outputFormats;
// Make sure the input is RGB video format
if (!verifyInputFormat(in))
return new Format[0];
Format out [] = new Format[2];
RGBFormat rgb = (RGBFormat) in;
Dimension size = rgb.getSize();
int maxDataLength = size.width * size.height * 3;
VideoFormat jpeg = new JPEGFormat(size, maxDataLength,
Format.byteArray,
rgb.getFrameRate(),
// -- hsy for bugid 4414481
//(int) (qfToQuality(quality)),
//qfToType(quality));
Format.NOT_SPECIFIED,
Format.NOT_SPECIFIED);
VideoFormat mjpg = new AviVideoFormat(VideoFormat.MJPG,
size, maxDataLength,
Format.byteArray,
rgb.getFrameRate(),
1, 24, maxDataLength,
0, 0, 0, 0,
mjpgExtraBytes);
out[0] = jpeg;
out[1] = mjpg;
return out;
}
private boolean verifyInputFormat(Format input) {
if (!(input instanceof RGBFormat))
return false;
RGBFormat rgb = (RGBFormat) input;
if ( rgb.getDataType() != Format.byteArray ||
rgb.getBitsPerPixel() != 24 ||
rgb.getRedMask() != 3 ||
rgb.getGreenMask() != 2 ||
rgb.getBlueMask() != 1 ||
rgb.getSize() == null ||
rgb.getLineStride() < rgb.getSize().width ||
rgb.getPixelStride() != 3 )
return false;
return true;
}
public Format setInputFormat(Format input) {
if (!verifyInputFormat(input))
return null;
inputFormat = (RGBFormat) input;
if (opened) {
close();
Dimension size = inputFormat.getSize();
int maxDataLength = size.width * size.height * 3;
if (outputFormat instanceof JPEGFormat)
outputFormat = new JPEGFormat(size,
maxDataLength,
Format.byteArray,
inputFormat.getFrameRate(),
(int) qfToQuality(quality),
qfToType(quality));
else
outputFormat = new AviVideoFormat(VideoFormat.MJPG,
size, maxDataLength,
Format.byteArray,
inputFormat.getFrameRate(),
1, 24, maxDataLength,
0, 0, 0, 0,
mjpgExtraBytes);
}
return input;
}
public Format setOutputFormat(Format output) {
if (matches(output, outputFormats) == null){
return null;
}
outputFormat = (VideoFormat) output;
if (outputFormat.getEncoding().equalsIgnoreCase(VideoFormat.MJPG))
isMotionJPEG = true;
else
isMotionJPEG = false;
return output;
}
public void open() throws ResourceUnavailableException {
if (!canLoad)
throw new ResourceUnavailableException("Unable to load" +
" native JPEG converter");
// Size restriction - 8x8
Dimension size = inputFormat.getSize();
if (size == null || (size.width % 8) != 0 || (size.height % 8) != 0 ) {
Log.error("Class: " + this);
Log.error(" can only encode in sizes of multiple of 8 pixels.");
throw new ResourceUnavailableException("Unable to encode in size " + size);
}
if (!loaded) {
try {
JMFSecurityManager.loadLibrary( "jmutil");
JMFSecurityManager.loadLibrary( "jmjpeg");
loaded = true;
} catch (Throwable t) {
canLoad = false;
throw new ResourceUnavailableException("Unable to load " +
"native JPEG encoder");
}
}
if (inputFormat == null || outputFormat == null)
throw new ResourceUnavailableException("Formats not set " +
"on the JPEG encoder");
if (peer != 0)
close();
try {
peer = initJPEGEncoder(size.width, size.height,
(int) qfToQuality(quality),
qfToDecimation(quality));
firstFrame = true;
} catch (Throwable t) {
}
if (peer == 0)
throw new ResourceUnavailableException("Unable to initialize JPEG encoder");
super.open();
}
public synchronized void close() {
if (peer != 0)
freeJPEGEncoder(peer);
peer = 0;
super.close();
}
public void reset() {
// Anything to do?
}
public synchronized int process(Buffer inBuffer, Buffer outBuffer) {
Object header = null;
float changeQuality;
int changeDecimation;
Format inFormat;
Object inData;
long inBytes;
boolean flipped;
// EndOfMedia?
if (isEOM(inBuffer)) {
propagateEOM(outBuffer);
return BUFFER_PROCESSED_OK;
}
// Dropping frames?
if (minimal || dropFrame) {
outBuffer.setFlags(outBuffer.getFlags() | Buffer.FLAG_DISCARD);
return BUFFER_PROCESSED_OK;
}
inFormat = inBuffer.getFormat();
inData = getInputData(inBuffer);
inBytes = getNativeData(inData);
flipped = ((RGBFormat) inFormat).getFlipped() == Format.TRUE;
//if (outputFormat.getEncoding().equals(VideoFormat.JPEG)){
byte [] outData = (byte[]) outBuffer.getData();
if (outData == null ||
outData.length < outputFormat.getMaxDataLength()) {
outData = new byte[outputFormat.getMaxDataLength()];
outBuffer.setData(outData);
}
outBuffer.setFormat(outputFormat);
if (prevQuality != quality) {
prevQuality = quality;
changeQuality = qfToQuality(quality);
outputFormat = new JPEGFormat(outputFormat.getSize(),
outputFormat.getMaxDataLength(),
Format.byteArray,
outputFormat.getFrameRate(),
(int) qfToQuality(quality),
qfToType(quality));
close();
// Allow changing decimation only if this is the first encoded frame
if (firstFrame)
changeDecimation = qfToDecimation(quality);
else
changeDecimation = -1;
} else { // dont change quality
changeQuality = -1;
changeDecimation = -1;
}
if (peer == 0) {
try {
open();
} catch (ResourceUnavailableException re) {
return BUFFER_PROCESSED_FAILED;
}
}
Dimension size = inputFormat.getSize();
synchronized (processLock) {
returnVal =
encodeJPEG(peer,
inData,
inBytes,
size.width,
size.height,
outData,
outData.length,
(int) changeQuality,
(int) changeDecimation,
flipped);
}
firstFrame = false;
if (returnVal > 0) {
outBuffer.setLength(returnVal);
outBuffer.setOffset(0);
inBuffer.setLength(0);
outBuffer.setFlags(Buffer.FLAG_KEY_FRAME);
outBuffer.setTimeStamp(inBuffer.getTimeStamp());
outBuffer.setFormat(outputFormat);
return BUFFER_PROCESSED_OK;
}
outBuffer.setDiscard(true);
return BUFFER_PROCESSED_FAILED;
}// end of process()
public void finalize() {
close();
}
public String getName() {
return "JPEG Encoder";
}
/****************************************************************
* Native Methods
****************************************************************/
// Initializes the native encoder
// Decimation is 1, 2, or 4 for YUV 4:2:0, 4:2:2 and 4:4:4 resp.
private native int initJPEGEncoder(int width, int height,
int quality, int decimation);
/*
* Encodes the RGB data and returns the output length (positive)
* Returns zero if it couldn't encode, or a negative value to indicate
* the error.
*/
private native int encodeJPEG(int peer, Object inData, long inBytes,
int width, int height,
byte [] outData, int length, int quality,
int decimation, boolean flipped);
// Frees any native structures
private native boolean freeJPEGEncoder(int peer);
/****************************************************************
* Test Code
****************************************************************/
public static void main(String [] args) {
int width = 320;
int height = 240;
RGBFormat in = new RGBFormat(new Dimension(width, height),
width * height * 3,
Format.byteArray,
Format.NOT_SPECIFIED, // frame rate
24,
1, 2, 3, 3, width * 3,
Format.FALSE, // flipped
Format.NOT_SPECIFIED // endian
);
VideoFormat out = new VideoFormat(VideoFormat.JPEG,
new Dimension(width, height),
width * height * 3,
Format.byteArray,
// frame rate
Format.NOT_SPECIFIED
);
NativeEncoder e = new NativeEncoder();
if (e.setInputFormat(in) != null) {
if (e.setOutputFormat(out) != null) {
try {
e.open();
} catch (ResourceUnavailableException rue) {
System.err.println("Couldn't open encoder");
System.exit(0);
}
byte [] rgbData = new byte[width * height * 3];
System.err.println("Filling rgb data");
for (int i = 0; i < width * height * 3; i++)
rgbData[i] = (byte) (((i % 3) * (i % width)) % 255);
System.err.println("Encoding");
Buffer inBuffer = new Buffer();
inBuffer.setFormat(in);
inBuffer.setData(rgbData);
inBuffer.setLength(width * height * 3);
Buffer outBuffer = new Buffer();
outBuffer.setFormat(out);
int result = e.process(inBuffer, outBuffer);
System.err.println("Result = " + result);
e.close();
}
}
System.exit(0);
}
}
|