FileDocCategorySizeDatePackage
WavPlayer.javaAPI DocJ2ME MIDP 2.014434Thu Nov 07 12:02:26 GMT 2002com.sun.mmedia

WavPlayer.java

/*
 * @(#)WavPlayer.java	1.51 02/11/05 @(#)
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 * PROPRIETARY/CONFIDENTIAL
 * Use is subject to license terms.
 */

package com.sun.mmedia;

import javax.microedition.media.*;
import javax.microedition.media.control.*;
import com.sun.mmedia.BasicPlayer;
import java.io.IOException;


/**
 * This class implements the wav (audio/x-wav) audio player.
 */
public class WavPlayer extends BasicPlayer implements Runnable {
    /**
     * the duration of this player
     */
    private long duration = TIME_UNKNOWN;

    /** 
     * Audio format parameters: sampleRate, channel number, sample size
     */
    private int sampleRate, channels, sampleSizeInBits;

    /**
     * byte rate and alignment
     */
    private int bytesPerSecond, blockAlign;

    /**
     * Last time when the player is stopped, how many samples have been
     * played and the media time at that point 
     */
    private long lastPos, origin;

    /**
     * waveout lock obj 
     */
    private Object waveLock = new Object();
    /**
     * play thread obj
     */
    private Thread playThread;
    /**
     * a status flag indicating whether this player is started 
     */
    private boolean started;
    /**
     * a status flag indicating whether this player has been deallocated 
     */
    private boolean interrupted;
    /**
     * a play lock obj 
     */
    private Object playLock = new Object();
    /**
     * In source stream, the start position of pcm data
     */
    private long startpt;
    /**
     * where the pcm data ends in source stream 
     */
    private long endpt = Long.MAX_VALUE;
    /**
     * the pointer to native wave out data structure. 
     */
    private int peer;

    /**
     * The buffer length to read from source stream every time.
     */
    private int bufLen;
    /**
     * the data buffer read from source stream 
     */
    private byte[] buffer;
    
    /**
     * a pause lock obj 
     */
    private Object pauseLock = new Object();
    /**
     * a flag indicating if this player could be paused 
     */
    private boolean canPause = true;

    /** 
     * Open the audio device in a particular format.
     * 
     * @param sampleRate the sampleRate of the intended format
     * @param bits how many bits per sample of the intended format
     * @param channels mono or stereo
     * @return the pointer to the wave out data structure, if succeeded.
     *         non-positive number if failed.
     */
    private native int nOpen(int sampleRate, int bits, int channels); 
    
    /**
     * Pass a data buffer to native code.
     *
     * @param peer the pointer to the native wave out data structure.
     * @param data the data buffer to be written to native wave out
     * @param offset the offset in data buffer.
     * @param len how many bytes of data to be written.
     * @return the acutal number of bytes has been written.
     */
    private native int nWrite(int peer, byte[] data, int offset, int len);

    /**
     * Utility functions for wave out
     *
     * @param peer the pointer to the native wave out data structure.
     * @param code functionality code
     * @param param parameters for a particular functionality.
     * @return status.
     */
    private native int nCommon(int peer, int code, int param);
    
    /**
     * Return the content type.
     *
     * @return the wav content type.
     */
    public String getContentType() {
	chkClosed(true);
	return "audio/x-wav";
    }

    /**
     * Parse the input, realize the player
     */
    protected void doRealize() throws MediaException {
	try {
	    readHeader();
	    startpt = getStrmLoc();
	} catch (IOException e) {
	    throw new MediaException(e.getMessage());
	}
    }
    
    
    /**
     * Get the duration of the media represented by this object.
     * The value returned is the media's duration
     * when played at the default rate.
     * If the duration can't be determined (for example, the media object is
     * presenting live * video)  <CODE>getDuration</CODE> returns 
     * <CODE>TIME_UNKNWON</CODE>.
     *
     * @return A <CODE>long</CODE> object representing the duration or 
     * TIME_UNKNWON.
     */
    public long doGetDuration() {
	return duration;
    }
    
    
    /**
     * Parse the Wave audio file header.
     *
     */
    protected void readHeader() throws IOException {
	if (readInt() != 0x46464952) // RIFF
	    throw new IOException("malformed wave data");
	
	readInt();
	
	if (readInt() != 0x45564157) // WAVE
	    throw new IOException("malformed wave data");
	
	// Only the required chunks 'fmt ' and 'data' are supported.
	// There are no restrictions upon the order of the chunks within 
	// a WAVE file, with the exception that the Format chunk must precede
	// the Data chunk.
	
	// Skip all chunks until you reach the 'fmt ' chunk
	while (readInt() != 0x20746D66) { // FMT
	    int size = readInt();
	    skipStrm(size);
	}
	
	// Handle Format chunk 'fmt ', formatSize
	int fmtsize = readInt();
	if (fmtsize < 16)
	    throw new IOException("bad fmt chunk");
	fmtsize -= 16;
	
	int encoding = readShort();
	if (encoding != 0x0001) // WF_PCM
	    throw new IOException("only supports PCM");

	channels = readShort();
	sampleRate = readInt();
	bytesPerSecond = readInt();
	blockAlign = readShort();
	sampleSizeInBits = readShort();

	// bytesPerSecond and blockAlign might not accurate in the file
	// need to calculate 
	bytesPerSecond = sampleRate * channels * sampleSizeInBits / 8;
	blockAlign = channels * sampleSizeInBits / 8;

	// skip the rest of the format chunk
	if (fmtsize > 0)
	    skipStrm(fmtsize);

	// Skip all chunks until you reach the 'data' chunk
	while (readInt() != 0x61746164) { // DATA
	    int size = readInt();
	    skipStrm(size);
	}
	
	// Handle Format chunk 'data'
	long dataSize = readInt();
	endpt = getStrmLoc() + dataSize;

	if ((channels * sampleSizeInBits / 8) == blockAlign) {
	    duration = (dataSize*1000000L)/bytesPerSecond;
	} else {
	    duration = TIME_UNKNOWN;
	}

	return;
    }


    /**
     * Get the resources ready.
     */
    protected void doPrefetch() throws MediaException {
	// Open the audio device.
	synchronized (waveLock) {
	    if (peer == 0) {
		peer = nOpen(sampleRate, sampleSizeInBits, channels);
		if (peer <= 0) {
		    throw new MediaException("can't open audio device");
		}

		bufLen = nCommon(peer, 9, 0);
		buffer = new byte[bufLen];

		if (getLevel() == -1)
		    setLevel(nCommon(peer, 8, 0));
	    }
	}

	if (isMuted()) {
	    nCommon(peer, 7, 0);
	} else {
	    nCommon(peer, 7, getLevel());
	}
    }


    /**
     * Start the playback.
     * @return the status if the player has been successfully started.
     */
    protected boolean doStart() {
	if (started)
	    return true;

	started = true;

	// Start the playback loop.
	synchronized (playLock) {
	    if (playThread == null) {
		playThread = new Thread(this);
		playThread.start();
	    } else
		playLock.notifyAll();
	}
	    
	nCommon(peer, 2, 0); // RESUME
	return true;
    }


    /**
     * Stop the playback loop.
     */
    protected void doStop() {
	if (!started)
	    return;
	
	started = false;
	synchronized (pauseLock) {
	    while (!canPause)
		try {
		    pauseLock.wait();
		} catch (Exception ex) {
		}
	    nCommon(peer, 1, 0); // PAUSE
	    pauseLock.notifyAll();
	}
    }


    /**
     * Deallocate the exclusing resource.
     */
    protected void doDeallocate() {
	// Interrupt the playback loop.

	// If the playThread had not been started, we'll need
	// to explicitly close the device.
	if (state == PREFETCHED && playThread == null) {
	    nCommon(peer, 5, 0); // CLOSE
	    return;
	}

	synchronized (playLock) {
	    interrupted = true;

	    // Wake up the run loop if it was stopped.
	    playLock.notifyAll();

	    // Wait for the playback loop to completely stop before 
	    // returning.  There's a maximum wait limit set here in 
	    // case anything goes wrong.
	    if (playThread != null) {
		try {
		    playLock.wait(5000);
		} catch (Exception e) {}
	    }
	}
    }


    /**
     * Close the player.
     */
    protected void doClose() {
	// Deallocate would have been called before this.
	// So all the resources should have been released.
    }

    /**
     * The worker method to actually set player's media time.
     *
     * @param now The new media time in microseconds.
     * @return The actual media time set in microseconds.
     * @exception MediaException Thrown if an error occurs
     * while setting the media time.
     */
    protected long doSetMediaTime(long now) throws MediaException {
	long ret = now;
	try {
	    long pp = (bytesPerSecond * now / 1000000L / blockAlign) *
		blockAlign + startpt;
	    if (getState() == STARTED)
		doStop();
	    nCommon(peer, 3, 0); // FLUSH
	    lastPos = nCommon(peer, 6, 0); // SAMPLES_PLAYED
	    ret = seekStrm(pp);
	    ret = (ret-startpt) * 1000000L / bytesPerSecond;
	    origin = ret;
	    if (getState() == STARTED)
		doStart();
	} catch (Exception e) {
	    throw new MediaException(e.getMessage());
	}

	return ret;
    }

    /**
     * Gets this player's current <i>media time</i>
     * in microseconds.
     * 
     * @return The current <i>media time</i> in microseconds.
     */
    public long doGetMediaTime() {
	long pos = 0;
	long mtime;
	synchronized (waveLock) {
	    if (sampleRate == 0)
		return 0;
	    pos = nCommon(peer, 6, 0); /* SAMPLES_PLAYED */
	}

	// Media time is in micro-seconds
	mtime = ((pos - lastPos) * 1000000L) / sampleRate + origin;

	return (mtime < 0 ? 0 : mtime);

    }

    /**
     * The worker method to actually obtain the control.
     *
     * @param type the class name of the <code>Control</code>.  
     * @return <code>Control</code> for the class or interface
     * name.
     */
    protected Control doGetControl(String type) {
	if ((getState() >= REALIZED) && 
	    type.equals("javax.microedition.media.control.VolumeControl")) {
	    return this;
	}
	return null;
    }
    

    /**
     * error string 
     */
    private String errMsg = null;
    
    /**
     * Read the data from the source and write them to the waveout in native.
     * @return the status.
     */
    private boolean doProcess() {
	int len = 0, wlen = 0;
	
	try {
	    len = readBytes(buffer, 0, bufLen);
	} catch (IOException ioe) {
	    errMsg = ioe.getMessage();
	    return false;
	}

	if (len < 1) {
	    synchronized (pauseLock) {
		canPause = false;
		try {
		    while (nCommon(peer, 4, 0) != 1) // DRAIN
			pauseLock.wait(20);
		} catch (Exception ex) {}
		canPause = true;
		pauseLock.notifyAll();
	    }

	    Thread.yield();
	    started = false;
	    sendEvent(PlayerListener.END_OF_MEDIA, new Long(getMediaTime()));
	    return true;
	}

	synchronized (pauseLock) {
	    canPause = false;
	    while ((wlen = nWrite(peer, buffer, 0, len)) == 0) {
		try {
		    pauseLock.wait(16);
		} catch (Exception ex) {}
	    }
	    canPause = true;
	    pauseLock.notifyAll();
	}

	if (wlen == -1) 
	    return false;
	
	return true;
    }
    

    /**
     * Main process loop driving the media flow.
     */
    public void run() {
	boolean statusOK = true;

	while (true) {

	    while (!interrupted && started && statusOK) {
		statusOK = doProcess();
		Thread.yield();
	    }

	    synchronized (playLock) {
		if (interrupted || !statusOK)
		    break;

		try {
		    playLock.wait();
		} catch (Exception ex) {}
	    }

	} // end of while (true)

	// Close the audio device, exit safely out of the process loop.

	nCommon(peer, 3, 0); // FLUSH
	synchronized (waveLock) {
	    nCommon(peer, 5, 0); // CLOSE
	    peer = 0;
	    interrupted = started = false;
	}
	lastPos = 0;

	synchronized (playLock) {
	    playThread = null;
	    // Notify the blocking deallocate that we are done with
	    // the process loop.
	    playLock.notifyAll();
	}

	if (!statusOK) {
	    if (stream != null) {
		try {
		    stream.close();
		} catch (IOException ex) {}
	    }
	    sendEvent(PlayerListener.ERROR, errMsg);
	}
    }





    /**
     * ====================================
     * Read calls to read from SourceStream
     * ====================================
     */
    /**
     * Read bytes from source stream.
     * @param array the byte array to hold the data
     * @param offset the offset in the byte array
     * @param num the number of bytes to be read
     * @return the actual number of bytes has been read
     */
    private int readBytes(byte[] array, int offset, int num) throws 
    IOException {
	if (num == 0) {
	    return 0;
	}

	long cpos = getStrmLoc();
	long available = endpt - cpos;

	if (available <= 0) {
	    return -1;
	}
	
	if (num > available) {
	    num = (int) available;
	}

	int rem = num;
	int read = 0;

	while (rem > 0) {
	    try {
		read = readStrm(array, offset, rem);
	    } catch (IOException e) {
		return -1;
	    }
	    if (read == -1) {	// End of stream
		if (rem == num) {
		    return -1;
		} else {
		    return num - rem;
		}
	    }
	    rem -= read;
	    offset += read;
	}
	return num;
    }

    /**
     * temporary buffer
     */
    private byte [] intArray = new byte[4];

    /**
     * Read an integer from source stream
     * @return the integer read.
     * @throws IOExeption if there is an error
     */
    private int readInt() throws IOException {
	if (readBytes(intArray, 0, 4) < 4)
	    throw new IOException("malformed wave data");
	return ((intArray[3] & 0xFF) << 24) |
	    ((intArray[2] & 0xFF) << 16) |
	    ((intArray[1] & 0xFF) << 8) |
	    (intArray[0] & 0xFF);
    }

    /**
     * Read a short from source stream
     * @return the short read
     * @throws IOException if there is an error
     */
    private short readShort() throws IOException {
	if (readBytes(intArray, 0, 2) < 2)
	    throw new IOException("malformed wave data");
	return (short) (((intArray[1] & 0xFF) << 8) |
		(intArray[0] & 0xFF));
    }


    /**
     * ==========================
     * Methods for VolumeControl.
     * ==========================
     */
    /**
     * The worker method to actually obtain the control.
     *
     * @param vol the volume level to be set.  
     * @return the actual level has been set.
     */
    protected int doSetLevel(int vol) {
	// 0 <= vol <= 100
	synchronized (waveLock) {
	    if (peer > 0) {
		nCommon(peer, 7, vol); // SETVOL
	    }
	}
	return vol;
    }
}