FileDocCategorySizeDatePackage
DirectAudioRenderer.javaAPI DocJMF 2.1.1e16213Mon May 12 12:21:20 BST 2003com.sun.media.renderer.audio

DirectAudioRenderer.java

/*
 * @(#)DirectAudioRenderer.java	1.18 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media.renderer.audio;

import java.util.*;
import java.io.*;
import javax.media.*;
import javax.media.format.*;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;

import com.sun.media.*;
import com.sun.media.util.*;
import com.sun.media.controls.*;
import com.sun.media.renderer.audio.device.AudioOutput;

import com.sun.media.JMFSecurity;
import com.sun.media.JMFSecurityManager;
import com.ms.security.PermissionID;
import com.ms.security.PolicyEngine;


/**
 * DirectAudioRenderer to render audio to a device directly.
 * This is used in conjunction DirectAudioOutput.
 * @version
 */

public class DirectAudioRenderer extends AudioRenderer
implements Runnable, ExclusiveUse {

    static String NAME = "DirectSound Renderer";

    public static final int REQ_OPEN = 1;
    public static final int REQ_START = 2;
    public static final int REQ_STOP = 3;
    public static final int REQ_FLUSH = 4;
    public static final int REQ_DRAIN = 5;
    public static final int REQ_CLOSE = 6;
    public static final int REQ_SETGAIN = 7;
    public static final int REQ_SETMUTE = 8;
    public static final int REQ_SETRATE = 9;
    public static final int REQ_WRITE = 10;
    public static final int REQ_AVAILABLE = 11;
    public static final int REQ_TIME = 12;

    private static JMFSecurity jmfSecurity = null;
    private static boolean securityPrivelege=false;
    private Method m[] = new Method[1];
    private Class cl[] = new Class[1];
    private Object args[][] = new Object[1][0];

    private Format ulawFormat, linearFormat;
    private Codec ulawDecoder;
    private Format ulawOutputFormat;
    
    static {
	try {
	    jmfSecurity = JMFSecurityManager.getJMFSecurity();
	    securityPrivelege = true;
	} catch (SecurityException e) {
	}
    }

    public DirectAudioRenderer() {
        super();

	//
	// This line advertizes all the formats that are supported
	// by this renderer.
	// Here we assume the renderer can support all linear/PCM formats.
	//
	supportedFormats = new Format[2];
	ulawFormat = new AudioFormat(AudioFormat.ULAW);
 	linearFormat = new AudioFormat(AudioFormat.LINEAR);

        supportedFormats[0] = new AudioFormat(
                AudioFormat.LINEAR,
		AudioFormat.NOT_SPECIFIED,	// Sample rate
		AudioFormat.NOT_SPECIFIED,	// Sample size
		AudioFormat.NOT_SPECIFIED,	// # of channels
		AudioFormat.NOT_SPECIFIED,
		AudioFormat.NOT_SPECIFIED
            );
	supportedFormats[1] = ulawFormat;

	gainControl = new MCA(this);
    }

    public String getName() {
	return NAME;
    }


    public void open() throws ResourceUnavailableException {
	if (device == null && inputFormat != null) {
	    if (!initDevice(inputFormat))
		throw new ResourceUnavailableException("Cannot intialize audio device for playback");
	}
    }

    protected boolean initDevice(AudioFormat in) {
	Format newInput = in;

	// Free the old ulaw decoder if there's one.
	if (ulawDecoder != null) {
	    ulawDecoder.close();
	    ulawDecoder = null;
	}

	// Initialize a ulaw decoder if the input format is ulaw.
	Format outs[] = new Format[1];
	if (ulawFormat.matches(in)) {

	    ulawDecoder = SimpleGraphBuilder.findCodec(in, linearFormat, null, outs);
	    if (ulawDecoder != null) {
		ulawOutputFormat = newInput = outs[0];
	    } else
		return false;
	}

	devFormat = in;

	return super.initDevice((AudioFormat)newInput);
    }

    public boolean isExclusive() {
	return false;
    }

    /**
     * Close the renderer and the device.
     */
    public void close() {
        super.close();
    }

    Buffer decodeBuffer = null;

    public int processData(Buffer buffer) {
	if (!checkInput(buffer))
	    return BUFFER_PROCESSED_FAILED;

	// Process linear data
	if (ulawDecoder == null) {
	    return super.doProcessData(buffer);
	}

	// Pre-processing ulaw data, then feed it into JavaSound.
	if (decodeBuffer == null) {
	    decodeBuffer = new Buffer();
	    decodeBuffer.setFormat(ulawOutputFormat);
	}

	decodeBuffer.setLength(0);
	decodeBuffer.setOffset(0);
	decodeBuffer.setFlags(buffer.getFlags());
	decodeBuffer.setTimeStamp(buffer.getTimeStamp());
	decodeBuffer.setSequenceNumber(buffer.getSequenceNumber());

	int rc = ulawDecoder.process(buffer, decodeBuffer);

	if (rc == BUFFER_PROCESSED_OK) {
	    return super.doProcessData(decodeBuffer);
	}

	return BUFFER_PROCESSED_FAILED;
    }	


    /**
     * Create the device - DirectAudioOutput.
     */
    protected AudioOutput createDevice(AudioFormat format) {
	return (new DirectAudioOutput());
    }


    /**
     * Grab the audio device without opening it.
     */
    private static synchronized boolean grabDevice() {
	// If the device can be claimed without being opened,
	// then fill in this method.
	return true;
    }

    public void run() {
	((Runnable) device).run();
    }

    Thread chpThread() {
	Thread thread = null;
	
	if ( /*securityPrivelege && */ (jmfSecurity != null) ) {
	    String permission = null;
	    try {
		if (jmfSecurity.getName().startsWith("jmf-security")) {
		    permission = "thread";
		    jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD);
		    m[0].invoke(cl[0], args[0]);
		    
		    permission = "thread group";
		    jmfSecurity.requestPermission(m, cl, args, JMFSecurity.THREAD_GROUP);
		    m[0].invoke(cl[0], args[0]);
		} else if (jmfSecurity.getName().startsWith("internet")) {
		    PolicyEngine.checkPermission(PermissionID.THREAD);
		    PolicyEngine.assertPermission(PermissionID.THREAD);
		}
	    } catch (Throwable e) {
		if (JMFSecurityManager.DEBUG) {
		    System.err.println("Unable to get " + permission +
				       " privilege  " + e);
		}
		securityPrivelege = false;
		// TODO: Do the right thing if permissions cannot be obtained.
		// User should be notified via an event
	    }
	}

 	if ( (jmfSecurity != null) && (jmfSecurity.getName().startsWith("jdk12"))) {
	    try {
		Constructor cons = jdk12CreateThreadRunnableAction.cons;
		
		thread = (Thread) jdk12.doPrivM.invoke(
					   jdk12.ac,
					   new Object[] {
					       cons.newInstance(
							new Object[] {
							    Thread.class,
							    this
							})});
		
		thread.setName("DirectSound Request Thread");
		
		cons = jdk12PriorityAction.cons;
		jdk12.doPrivM.invoke(
				     jdk12.ac,
				     new Object[] {
 					  cons.newInstance(
 					   new Object[] {
                                               thread,
                                               new Integer(Thread.MAX_PRIORITY)
                                           })});
	    } catch (Exception e) {
		e.printStackTrace();
	    }
 	} else {
	    thread = new Thread(this);
	    thread.setName("DirectSound Request Thread");
	    thread.setPriority(Thread.MAX_PRIORITY);
 	}
	return thread;
    }


    /****************************************************************
     * INNER CLASSES
     ****************************************************************/
    
    /**
     * Set the gain for this renderer.
     */
    class MCA extends GainControlAdapter {

	AudioRenderer renderer;

	protected MCA(AudioRenderer r) {
	    super(1.0f);
	    renderer = r;
	}

	/**
	 * Muting the device.
	 */
	public void setMute(boolean mute) {
	    if (renderer != null && renderer.device != null)
		renderer.device.setMute(mute);
	    super.setMute(mute);
	}

	/**
	 * Set the gain of the device.
	 */
	public float setLevel(float g) {
	    float level = super.setLevel(g);
	    g = getDB();
	    if (renderer != null && renderer.device != null) {
		renderer.device.setGain(g);
	    }
	    return level;
	}

    }


    /**
     * native method declaraions
     */
    private native int nOpen(int rate, int sizeInBits, int channels, int bufSize);
    private native void nClose(int peer);
    private native void nPause(int peer);
    private native void nResume(int peer);
    private native void nDrain(int peer);
    private native void nFlush(int peer);
    private native void nSetGain(int peer, float g);
    private native void nSetMute(int peer, boolean m);
    private native long nGetSamplesPlayed(int peer);
    private native int nBufferAvailable(int peer);
    private native int nWrite(int peer, byte data[], int off, int len,
			      boolean swapBytes, boolean signChange);
    private native void nCheckUnderflow(int peer);
    private native boolean nSetFrequency(int peer, int frequency);

    static boolean loaded = false;
    private int nativeData = 0;		// Pointer to the native decoder.

    static {
	if (!loaded) {
	    try {
		JMFSecurityManager.loadLibrary("jmdaud");
		loaded = true;
	    } catch (UnsatisfiedLinkError e) {
		loaded = false;
	    }
	}
    }


    /**
     * This is the low-level audio device wrapper class.
     * It assumes the native code is written in a library call jmdaud.
     */
    class DirectAudioOutput implements AudioOutput, Runnable {

	private int bufSize;
	private AudioFormat format;
	private float gain;
	private boolean muted;
	private int request = 0;
	private Integer reqLock = new Integer(0);
	private boolean response = false;
	private Thread reqThread = null;
	private float rate = 1.0f;
	private boolean swapBytes = false;
	private boolean signChange = false;
	private float reqRate = 1.0f;
	private byte[] writeData;
	private int writeOffset;
	private int writeLen;
	private int writeResponse;
	private long nanoseconds;
	private int bufferAvailable;
	private boolean started = false;
	private Integer writeLock = new Integer(1);
	
	public DirectAudioOutput() {
	}

	/**
	 * Initialize the audio device to the specified format.
	 */
	public boolean initialize(AudioFormat format, int bufSize) {

	    // Native libraries cannot be loaded.
	    if (!loaded)
		return false;

	    // The higher level class (DirectAudioRenderer) has already 
	    // screen the format.  It should be one of the supported
	    // formats as advertized.
	    this.format = format;

	    // 1/32 of a second.
	    this.bufSize = (int)(format.getSampleRate() * format.getSampleSizeInBits() * format.getChannels() / 8 / 32);

	    if (reqThread == null) {
		reqThread = chpThread();
		reqThread.start();
	    }

	    if (format.getEndian() == AudioFormat.BIG_ENDIAN &&
		format.getSampleSizeInBits() == 16)
		swapBytes = true;
	    else
		swapBytes = false;
	    if (format.getSigned() == AudioFormat.SIGNED &&
		format.getSampleSizeInBits() == 8)
		signChange = true;
	    else
		signChange = false;
	    makeRequest(REQ_OPEN);
	    waitForResponse();
	    return nativeData != 0;
	}

	private void makeRequest(int request) {
	    synchronized (reqLock) {
		this.request = request;
		response = false;
		reqLock.notifyAll();
	    }
	}

	private void waitForResponse() {
	    synchronized (reqLock) {
		while (response == false) {
		    try {
			reqLock.wait(50);
		    } catch (InterruptedException ie) {
		    }
		}
	    }
	}

	public void run() {
	    boolean done = false;
	    int reqCopy;
	    //Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
	    while (!done) {
		synchronized (reqLock) {
		    try {
			if (request == 0)
			    reqLock.wait(50);
		    } catch (InterruptedException ie) {
		    }
		    reqCopy = request;
		    request = 0;
		}
		//System.err.println("reqCopy = " + reqCopy);
		switch (reqCopy) {
		case REQ_OPEN:
		    nativeData = nOpen((int)format.getSampleRate(),
				       format.getSampleSizeInBits(),
				       format.getChannels(),
				       bufSize);
		    //System.err.println("Trying to open " + nativeData);
		    break;

		case REQ_START:
		    nResume(nativeData);
		    break;
		    
		case REQ_STOP:
		    nPause(nativeData);
		    break;
		    
		case REQ_FLUSH:
		    nFlush(nativeData);
		    break;
		    
		case REQ_DRAIN:
		    nDrain(nativeData);
		    break;
		    
		case REQ_SETGAIN:
		    nSetGain(nativeData, gain);
		    break;
		case REQ_SETMUTE:
		    nSetMute(nativeData, muted);
		    break;
		    
		case REQ_SETRATE:
		    doSetRate(reqRate);
		    break;
		    
		case REQ_WRITE:
		    writeResponse = nWrite(nativeData,
					   writeData, writeOffset, writeLen,
					   swapBytes, signChange);
		    break;
		    
		case REQ_TIME:
		    {
			long samples = nGetSamplesPlayed(nativeData);
			nanoseconds = (long)(1000000L * samples /
					     format.getSampleRate()) * 1000L;
		    }
		    break;
		    
		case REQ_AVAILABLE:
		    bufferAvailable = nBufferAvailable(nativeData);
		    break;
		    
		case REQ_CLOSE:
		    synchronized (writeLock) {
			nClose(nativeData);
			nativeData = 0;
			done = true;
		    }
		    break;
		default:
		    //System.err.println("Idle");
		    if (started) {
			synchronized (writeLock) {
			    nCheckUnderflow(nativeData);
			}
		    }
		}
		if (reqCopy > 0) {
		    synchronized (reqLock) {
			response = true;
			reqLock.notifyAll();
		    }
		}
	    }
	    reqThread = null;
	}
	
	/**
	 * Close the device.
	 */
	public synchronized void dispose() {
	    if (nativeData != 0) {
		makeRequest(REQ_CLOSE);
		waitForResponse();
	    }
	}

	/**
	 * Pause the device.
	 */
	public synchronized void pause() {
	    makeRequest(REQ_STOP);
	    waitForResponse();
	    started = false;
	}


	/**
	 * Resume the device.
	 */
	public synchronized void resume() {
	    makeRequest(REQ_START);
	    waitForResponse();
	    started = true;
	}


	/**
	 * Drain the device.
	 */
	public synchronized void drain() {
	    makeRequest(REQ_DRAIN);
	    waitForResponse();
	    started = false;
	}


	/**
	 * Flush the device.
	 */
	public synchronized void flush() {
	    makeRequest(REQ_FLUSH);
	    waitForResponse();
	    started = false;
	}


	/**
	 * Retrieve the audio format.
	 */
	public AudioFormat getFormat() {
	    return format;
	}


	/**
	 * Return the amount of audio samples played so far in units of
	 * nanoseconds.  This method should return a number that's montonically
	 * increasing.  It should get the samples played so far from
	 * the audio device translate that into nanoseconds.
	 */
	public long getMediaNanoseconds() {
	    long samples = nGetSamplesPlayed(nativeData);
	    nanoseconds = (long)(1000000L * samples /
				 format.getSampleRate()) * 1000L;
	    //makeRequest(REQ_TIME);
	    //waitForResponse();
	    return nanoseconds;
	}


	/**
	 * Return the audio gain in units of db.
	 */
	public double getGain() {
	    return gain;
	}


	public synchronized void setGain(double g) {
	    gain = (float) g;
	    makeRequest(REQ_SETGAIN);
	    waitForResponse();
	}


	/**
	 * Mute/unmute the device.
	 */
	public synchronized void setMute(boolean m) {
	    muted = m;
	    makeRequest(REQ_SETMUTE);
	    waitForResponse();
	}


	/**
	 * Return the mute state of the device.
	 */
	public boolean getMute() {
	    return muted;
	}

	public float doSetRate(float r) {
	    // DirectSound currently handles only between 100 Hz and 100KHz
	    if (r * format.getSampleRate() > 100000) {
		r = (float) 100000 / (float) format.getSampleRate();
	    }

	    if (r < 0.1f)
		r = 0.1f;

	    int sampFreq = (int) (r * format.getSampleRate());
	    if (nSetFrequency(nativeData, sampFreq))
		rate = r;
	    else {
		rate = 1.0f;
		r = 1.0f;
	    }
	    return r;
	}

	/**
	 * Set the playback rate of the device.
	 */
	public synchronized float setRate(float r) {
	    reqRate = r;
	    makeRequest(REQ_SETRATE);
	    waitForResponse();
	    return rate;
	}

	/**
	 * Get the playback rate of the device.
	 */
	public float getRate() {
	    return rate;
	}

	/**
	 * Return the amount of data in bytes that can be sent to the
	 * device via the "write" call without blocking it.
	 */
	public synchronized int bufferAvailable() {
	    makeRequest(REQ_AVAILABLE);
	    waitForResponse();
	    return bufferAvailable;
	}


	/**
	 * Send the audio data to the device.  This should be a blocking call.
	 */
	public int write(byte data[], int off, int len) {
	    writeData = data;
	    writeOffset = off;
	    writeLen = len;
	    synchronized (writeLock) {
		writeResponse = nWrite(nativeData,
				       writeData, writeOffset, writeLen,
				       swapBytes, signChange);
	    }
	    //makeRequest(REQ_WRITE);
	    //waitForResponse();
	    return writeResponse;
	}
    }
}