FileDocCategorySizeDatePackage
NativeDecoder.javaAPI DocJMF 2.1.1e14047Mon May 12 12:21:20 BST 2003com.sun.media.codec.video.vcm

NativeDecoder.java

/*
 * @(#)NativeDecoder.java	1.47 03/04/24
 *
 * Copyright (c) 1996-2003 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media.codec.video.vcm;

import javax.media.*;
import javax.media.control.*;
import javax.media.Format;
import javax.media.format.VideoFormat;
import javax.media.format.RGBFormat;
import javax.media.format.YUVFormat;

import com.sun.media.format.AviVideoFormat;
import com.sun.media.*;
import com.sun.media.util.*;
import com.sun.media.vfw.*;
import java.util.Vector;
import java.awt.*;

public final class NativeDecoder extends BasicCodec
implements com.sun.media.util.DynamicPlugIn {
    
    private static boolean loaded = false;
    static {
	com.sun.media.JMFSecurityManager.loadLibrary("jmvcm");
	loaded = true;
    }

    static Integer processLock = new Integer(1);

    protected VideoFormat inputFormat = null;
    protected VideoFormat outputFormat = null;

    /*
    protected String [] knownEncodings = {//VideoFormat.CINEPAK,
					  "msvc", "cram",
					  "mrle", "vdec",
					  "vgpx",
					  //"rgb",
					  "i263", "i420",
					  "yuy2", "yvu9", "y411",
					  //"m263",
					  //VideoFormat.INDEO32,
					  VideoFormat.INDEO41,
					  VideoFormat.INDEO50};
    */
    
    private static String [] supportedEncodings = null;

    private int vcmHandle = 0;

    private boolean debug = false;

    private BitMapInfo biIn = null;
    private BitMapInfo biOut = null;
    private boolean dropFrame = false;
    private boolean keepOutputRef = false;
    private NBA outputRef = null;

    public NativeDecoder() {
	// Workaround for Netscape bug, where if the 
	// Class.forName fails the first time, because
	// says native libraries couldn't be loaded,
	// it succeeds the second time and
	// throws an error later.
	if (!loaded) {
	    throw new UnsatisfiedLinkError("Cannot load native libraries");
	}
	inputFormats = new VideoFormat[1];
	inputFormats[0] = new VideoFormat(null);

	outputFormats = new VideoFormat[1];
	outputFormats[0] = new RGBFormat();
    }

    protected Format getInputFormat() {
	return inputFormat;
    }

    protected Format getOutputFormat() {
	return outputFormat;
    }

    private static synchronized void querySupportedEncodings() {
	ICInfo icinfo = new ICInfo();
	BitMapInfo biOut = new BitMapInfo();
	int i = 0;
	Vector listEncoding = new Vector();
	
	while (true) {
	    icinfo = new ICInfo();
	    boolean result = VCM.icInfoEnum("vidc", i, icinfo);
	    if (!result || i > 30)
		break;
	    else {
		if (!(icinfo.fccHandler == null ||
		      icinfo.fccHandler.equalsIgnoreCase(VideoFormat.MJPG) ||
		      icinfo.fccHandler.equalsIgnoreCase(VideoFormat.CINEPAK)
		      )) {
		    int handle = VCM.icOpen("vidc", icinfo.fccHandler, VCM.ICMODE_DECOMPRESS);
		    if (handle != 0) {
			if (icinfo.fccHandler.equalsIgnoreCase("ir41")) // hack
			    icinfo.fccHandler = "IV41";
			if (icinfo.fccHandler.equalsIgnoreCase("ir50")) // hack
			    icinfo.fccHandler = "IV50";
			if (icinfo.fccHandler.equalsIgnoreCase("ir32")) // hack
			    icinfo.fccHandler = "IV32";
			listEncoding.addElement(icinfo.fccHandler.toUpperCase());
			VCM.icClose(handle);
		    }
		}
	    }
	    i++;
	}
	listEncoding.addElement("YVU9");
	supportedEncodings = new String[listEncoding.size()];
	for (i = 0; i < supportedEncodings.length; i++) {
	    supportedEncodings[i] = (String) listEncoding.elementAt(i);
	}
    }
    
    public Format [] getSupportedInputFormats() {
	if (supportedEncodings == null) {
	    querySupportedEncodings();
	}
	inputFormats = new VideoFormat[supportedEncodings.length];
	for (int i = 0; i < supportedEncodings.length; i++) {
	    inputFormats[i] = new VideoFormat(supportedEncodings[i]);
	}

	return inputFormats;
    }

    public Format [] getSupportedOutputFormats(Format in) {
	if (in == null)
	    return outputFormats;
	
	if (matches(in, inputFormats) == null)
	    return new Format[0];

	VideoFormat video = (VideoFormat) in;
	Dimension size = video.getSize();
	if (size == null)
	    size = new Dimension(320, 240);
	RGBFormat [] tryFormats = new RGBFormat[] {
	    new RGBFormat(size, size.width * size.height * 3,
			  Format.byteArray,
			  video.getFrameRate(), // frame rate
			  24,
			  3, 2, 1,
			  3,
			  size.width * 3,
			  Format.TRUE,
			  Format.NOT_SPECIFIED), // endian

	    new RGBFormat(size, size.width * size.height * 3,
			  Format.byteArray,
			  video.getFrameRate(), // frame rate
			  24,
			  3, 2, 1,
			  3,
			  size.width * 3,
			  Format.FALSE, // flipped
			  Format.NOT_SPECIFIED), // endian

	    new RGBFormat(size, size.width * size.height,
			  Format.shortArray,
			  video.getFrameRate(), // frame rate
			  16,
			  0x07C00, 0x003e0, 0x0001F,
			  1, size.width,
			  Format.FALSE, // flipped
			  Format.NOT_SPECIFIED), // endian

	    new RGBFormat(size, size.width * size.height,
			  Format.shortArray,
			  video.getFrameRate(), // frame rate
			  16,
			  0x07C00, 0x003e0, 0x0001F,
			  1, size.width,
			  Format.TRUE,
			  Format.NOT_SPECIFIED) // endian


	};
	if (debug) System.err.println("Input format = " +
				      new BitMapInfo((VideoFormat)video));
	Vector supportedOuts = new Vector();
	int handle = VCM.icLocate("vidc", "RGB",
				  new BitMapInfo((VideoFormat) video),
				  null,
				  VCM.ICMODE_DECOMPRESS);
	if (handle != 0) {
	    for (int enum = 0; enum < tryFormats.length; enum++) {
		if ( VCM.icDecompressBegin(handle,
					   new BitMapInfo((VideoFormat) video),
					   new BitMapInfo(tryFormats[enum])) ) {
		    supportedOuts.addElement(tryFormats[enum]);
		    if (debug) System.err.println("VCM " + video.getEncoding() + " supports " + tryFormats[enum]);
		    VCM.icDecompressEnd(handle);
		} else {
		}
	    }
	    VCM.icClose(handle);
	}

	Format [] outs = new Format[supportedOuts.size()];
	for (int i = 0; i < outs.length; i++) {
	    outs[i] = (Format) supportedOuts.elementAt(i);
	}
	
	//if (debug) System.err.println("VCM.getSupportedOutputFormats: " + rgb);

	return outs;
    }
    
    public Format setInputFormat(Format in) {
	if (supportedEncodings == null) {
	    querySupportedEncodings();
	}
	String encoding = in.getEncoding();
	if (  encoding.equalsIgnoreCase(VideoFormat.CINEPAK) ||
	      encoding.equalsIgnoreCase(VideoFormat.MJPG) ||
	      encoding.equalsIgnoreCase("RGB")                  )
	    return null;

	if ( encoding.equalsIgnoreCase("mpg4") ||
	     encoding.equalsIgnoreCase("mp42") ||
	     encoding.equalsIgnoreCase("mp43") ||
	     encoding.equalsIgnoreCase("iv32")  )
	    keepOutputRef = true;
	else
	    keepOutputRef = false;
	
	if (in instanceof VideoFormat) {
	    if (matches(in, inputFormats) != null) {
		inputFormat = (VideoFormat) in;

		if (opened) {
		    try {
			close();
			Format [] ofs =
			    getSupportedOutputFormats(inputFormat);
			if (ofs.length < 1)
			    return null;
			else if (ofs.length == 1)
			    outputFormat = (RGBFormat) ofs[0];
			else {
			    for (int i = 0; i < ofs.length; i++) {
				if (((RGBFormat)outputFormat).getBitsPerPixel() ==
				    ((RGBFormat)ofs[i]).getBitsPerPixel() &&
				    ((RGBFormat)outputFormat).getFlipped() ==
				    ((RGBFormat)ofs[i]).getFlipped()) {
				    outputFormat = (VideoFormat) ofs[i];
				    break;
				}
			    }
			}
			open();
		    } catch (Exception e) {
			return null;
		    }
		}
		return in;
	    }
	}

	return null;
    }

    public Format setOutputFormat(Format out) {
	if (out instanceof RGBFormat) {
	    outputFormat = (RGBFormat) out;
	    if (debug) System.err.println("VCM.setOutputFormat : "  + outputFormat);
	    return out;
	}
	
	return null;
    }

    public void open() throws ResourceUnavailableException {

	if (inputFormat == null || outputFormat == null)
	    throw new ResourceUnavailableException("Formats not set!");

	Dimension size = inputFormat.getSize();
	//Object bi = inputFormat.getExtraData();
	Object bi = null;
	String inFourCC = inputFormat.getEncoding();

	biOut = new BitMapInfo();

	// Translate from AviVideoFormat to BitMapInfo
	if (inputFormat instanceof AviVideoFormat) {
	    bi = new BitMapInfo(inputFormat);
	    if (debug) System.err.println("VCM.open, BMI = " + bi);
	}


	if (bi != null && bi instanceof BitMapInfo) {
	    biIn = (BitMapInfo) bi;
	} else
	    biIn = new BitMapInfo(inFourCC, size.width, size.height);

	String cramHack = inFourCC;
	if (cramHack.equalsIgnoreCase("cram"))
	    cramHack = "msvc";

	vcmHandle = VCM.icLocate("vidc", "RGB",
				 biIn, null,
				 VCM.ICMODE_DECOMPRESS);

	if (debug) System.err.println("open.vcmHandle for " + inFourCC + " = " + vcmHandle);
	
	if (vcmHandle == 0)
	    throw new ResourceUnavailableException("Could not open VCM driver");

	BitMapInfo testOut = new BitMapInfo();
	//VCM.icDecompressGetFormat(vcmHandle, biIn, testOut);

	if (debug) System.err.println("open.preferred out = " + testOut);
	// biOut = testOut;

	
	// new trial
	biOut = new BitMapInfo(outputFormat);
	if (debug) System.err.println("open.requested out = " + biOut);
	
	boolean tryFlip = true;
	boolean result;
	
	result = VCM.icDecompressBegin(vcmHandle, biIn, biOut);

	if (result == false) {
	    if (debug) System.err.println("VCM.open : Could not set i/o format");
	    close();
	    throw new ResourceUnavailableException("Formats not supported");
	}
	if (debug) System.err.println("VCM.open success!");
	super.open();
    }

    public void close() {
	if (vcmHandle != 0)
	    VCM.icClose(vcmHandle);
	vcmHandle = 0;
	super.close();
    }

    public void reset() {
	if (vcmHandle != 0) {
	    VCM.icDecompressEnd(vcmHandle);
	    VCM.icDecompressBegin(vcmHandle, biIn, biOut);
	}
    }

    public int process(Buffer inBuffer, Buffer outBuffer) {
	if (isEOM(inBuffer)) {
	    propagateEOM(outBuffer);
	    return BUFFER_PROCESSED_OK;
	}

	Format outf = outBuffer.getFormat();
	outBuffer.setFormat(outputFormat);
	Object outBuf = validateData(outBuffer, 0, true);
	int bufType = 1;

	if (outputFormat.getDataType() == Format.shortArray) {
	    bufType = 2;
	}

	Object inBuf = getInputData(inBuffer);
	long flags = 0;
	int size = inBuffer.getLength();
	if (size < 2)
	    return BUFFER_PROCESSED_FAILED;
	Object header = inBuffer.getHeader();
	if (header != null && header instanceof Integer) {
	    flags = ((Integer)header).intValue();
	}

	if (dropFrame)
	    flags |= 0x20000000L;

	biIn.biSizeImage = size;
	// biOut.biHeight = -outputFormat.getSize().height;
	int result;
	synchronized (processLock) {
	    long outBytes = getNativeData(outBuf);
	    long inBytes = getNativeData(inBuf);
	    if (!keepOutputRef) {
		result = VCM.icDecompress(vcmHandle, flags,
					  biIn, inBuf, inBytes, biOut, outBuf, outBytes,
					  bufType);
	    } else {
		if (outputRef == null)
		    outputRef = new NBA(byte[].class, outputFormat.getMaxDataLength());
		result = VCM.icDecompress(vcmHandle, flags, biIn, inBuf, inBytes,
					  biOut, null,
					  outputRef.getNativeData(), bufType);
		if (outBytes != 0) {
		    outputRef.copyTo((NBA)outBuf);
		} else {
		    outputRef.copyTo((byte[])outBuf);
		}
	    }
	}
	if (dropFrame)
	    outBuffer.setDiscard(true);
	else
	    outBuffer.setLength(outputFormat.getMaxDataLength());

	if (result < 0)
	    return BUFFER_PROCESSED_FAILED;
	else
	    return BUFFER_PROCESSED_OK;
    }

    private Control [] controls = null;
    private DC dc = null;

    public Object [] getControls() {
	if (dc == null) {
	    dc = new DC();
	    controls = new Control[1];
	    controls[0] = dc;
	}

	return controls;
    }

    public String getName() {
	return "VCM Decoder";
    }

    void dropNextFrame(boolean on) {
	dropFrame = on;
    }

    public Format [] getBaseInputFormats() {
	Format [] formats = new Format[1];

	formats[0] = new VideoFormat(null);
	return formats;
    }

    public Format [] getBaseOutputFormats() {
	Format [] formats = new Format[1];
	formats[0] = new RGBFormat();
	return formats;
    }
    
    /****************************************************************
     * Decoder Control
     ****************************************************************/
    
    class DC implements FrameProcessingControl, QualityControl {

	public Component getControlComponent() {
	    return null;
	}
	
	public boolean setMinimalProcessing(boolean on) {
	    dropNextFrame(true);
	    return true;
	}
	
	/**
	 * Informs the codec that it is behind by some number of frames
	 * and that it needs to either speed up by dropping quality or by
	 * dropping frames as it sees fit. The value <code>framesBehind</code>
	 * can either be positive, zero or negative. A negative value indicates
	 * that the codec is ahead by that many frames and can possibly improve
	 * quality if its not at maximum. This method needs to be called before a
	 * call to <code>process</code>. The value is remembered by the codec
	 * until it is explicitly changed again.
	 */
	public void setFramesBehind(float framesBehind) {
	    if (framesBehind > 0)
		dropNextFrame(true);
	    else
		dropNextFrame(false);
	}

        public int getFramesDropped() {
            return 0;       ///XXX not implemented
        }

	/**
	 * Set the quality for the decoding or encoding. This value may have
	 * different
	 * effects depending on the type of compression. A higher quality
	 * setting will result in better quality of the resulting bits, e.g.
	 * better image quality for video.  There is usually a tradeoff between
	 * CPU usage and the quality; in that higher quality requires higher
	 * CPU usage.  This value is
	 * only a hint and the codec can choose to ignore it. The actual value
	 * that was set is returned<p>.
	 * It should be in the range of 0.0 to 1.0.
	 * @see #getQuality
	 */
	public float setQuality(float quality) {
	    return 1.0f;
	}

	/**
	 * Returns the current value of the compression quality parameter.
	 */
	public float getQuality() {
	    return 1.0f;
	}

	/**
	 * Return the default compression quality recommended for
	 * this codec.
	 */
	public float getPreferredQuality() {
	    return 1.0f;
	}

	public boolean isTemporalSpatialTradeoffSupported() {
	    return true;
	}
    }
}