/*
* @(#)DirectSoundStream.java 1.8 02/08/21
*
* Copyright (c) 1996-2002 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.media.protocol.dsound;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.io.IOException;
import javax.media.*;
import javax.media.protocol.*;
import javax.media.control.*;
import javax.media.format.AudioFormat;
import com.sun.media.Log;
import com.sun.media.CircularBuffer;
import com.sun.media.protocol.*;
import com.sun.media.util.LoopThread;
import com.sun.media.util.Arch;
import com.sun.media.util.jdk12;
import com.sun.media.ui.AudioFormatChooser;
import com.sun.media.JMFSecurity;
import com.sun.media.JMFSecurityManager;
import com.ms.security.PermissionID;
import com.ms.security.PolicyEngine;
/**
* DirectSound capture stream.
*/
public class DirectSoundStream extends BasicSourceStream
implements PushBufferStream {
DataSource dsource;
DSound dSound = null;
AudioFormat format;
AudioFormat devFormat;
boolean reconnect = false;
int bufSize;
BufferTransferHandler transferHandler;
boolean started = false;
AudioFormatChooser afc;
BufferControl bc;
CircularBuffer cb = new CircularBuffer(1);
PushThread pushThread = null;
//PortControl portControl = null;
// Defaults if nothing is specified.
static int DefRate = 44100;
static int DefBits = 16;
static int DefChannels = 2;
static int DefSigned = AudioFormat.SIGNED;
static int DefEndian = AudioFormat.LITTLE_ENDIAN;
static int OtherEndian = AudioFormat.BIG_ENDIAN;
static javax.media.Format supported[];
private static JMFSecurity jmfSecurity = null;
private static boolean securityPrivelege=false;
private Method mSecurity[] = new Method[1];
private Class clSecurity[] = new Class[1];
private Object argsSecurity[][] = new Object[1][0];
static {
try {
jmfSecurity = JMFSecurityManager.getJMFSecurity();
securityPrivelege = true;
} catch (SecurityException e) {
}
}
public DirectSoundStream(DataSource ds) {
super(new ContentDescriptor(ContentDescriptor.RAW), LENGTH_UNKNOWN);
dsource = ds;
bc = new BC(this);
controls = new javax.media.Control[2];
controls[0] = new FC(this);
controls[1] = bc;
CaptureDeviceInfo cdi = CaptureDeviceManager.getDevice(ds.NAME);
supported = cdi.getFormats();
}
/**
* Parse the javasound media locator which specifies the format.
* A valid media locator is of the form:
* javasound://[rate]/[sizeInBits]/[channels]/[big|little]/[signed|unsigned]
*/
static public Format parseLocator(MediaLocator ml) {
int rate, bits, channels, endian, signed;
String rateStr = null, bitsStr = null, channelsStr = null;
String endianStr = null, signedStr = null;
// Parser the media locator to extract the requested format.
String remainder = ml.getRemainder();
if (remainder != null && remainder.length() > 0) {
while (remainder.length() > 1 && remainder.charAt(0) == '/')
remainder = remainder.substring(1);
// Now see if there's a sample rate specified.
int off = remainder.indexOf('/');
if (off == -1) {
if (!remainder.equals(""))
rateStr = remainder;
} else {
rateStr = remainder.substring(0, off);
remainder = remainder.substring(off + 1);
// Now see if there's a sample size specified
off = remainder.indexOf('/');
if (off == -1) {
if (!remainder.equals(""))
bitsStr = remainder;
} else {
bitsStr = remainder.substring(0, off);
remainder = remainder.substring(off + 1);
// Now see if there's a channels specified
off = remainder.indexOf('/');
if (off == -1) {
if (!remainder.equals(""))
channelsStr = remainder;
} else {
channelsStr = remainder.substring(0, off);
remainder = remainder.substring(off + 1);
// Now see if there's endian specified.
off = remainder.indexOf('/');
if (off == -1) {
if (!remainder.equals(""))
endianStr = remainder;
} else {
endianStr = remainder.substring(0, off);
if (!remainder.equals(""))
signedStr = remainder.substring(off + 1);
}
}
}
}
}
// Sample Rate
rate = DefRate;
if (rateStr != null) {
try {
Integer integer = Integer.valueOf(rateStr);
if (integer != null)
rate = integer.intValue();
} catch (Throwable t) { }
// Range check.
if (rate <= 0 || rate > 96000) {
Log.warning("DSound capture: unsupported sample rate: " + rate);
rate = DefRate;
Log.warning(" defaults to: " + rate);
}
}
// Sample Size
bits = DefBits;
if (bitsStr != null) {
try {
Integer integer = Integer.valueOf(bitsStr);
if (integer != null)
bits = integer.intValue();
} catch (Throwable t) { }
// Range check.
if (bits != 8 && bits != 16) {
Log.warning("DSound capture: unsupported sample size: " + bits);
bits = DefBits;
Log.warning(" defaults to: " + bits);
}
}
// # of channels
channels = DefChannels;
if (channelsStr != null) {
try {
Integer integer = Integer.valueOf(channelsStr);
if (integer != null)
channels = integer.intValue();
} catch (Throwable t) { }
// Range check.
if (channels != 1 && channels != 2) {
Log.warning("DSound capture: unsupported # of channels: " + channels);
channels = DefChannels;
Log.warning(" defaults to: " + channels);
}
}
// Endian
endian = DefEndian;
if (endianStr != null) {
if (endianStr.equalsIgnoreCase("big"))
endian = AudioFormat.BIG_ENDIAN;
else if (endianStr.equalsIgnoreCase("little"))
endian = AudioFormat.LITTLE_ENDIAN;
else {
Log.warning("DSound capture: unsupported endianess: " + endianStr);
Log.warning(" defaults to: big endian");
}
}
// Signed
signed = DefSigned;
if (signedStr != null) {
if (signedStr.equalsIgnoreCase("signed"))
signed = AudioFormat.SIGNED;
else if (signedStr.equalsIgnoreCase("unsigned"))
signed = AudioFormat.UNSIGNED;
else {
Log.warning("DSound capture: unsupported signedness: " + signedStr);
Log.warning(" defaults to: signed");
}
}
AudioFormat fmt;
fmt = new AudioFormat(AudioFormat.LINEAR,
rate, bits, channels, endian, signed);
return fmt;
}
/**
* Set the capture format.
*/
public Format setFormat(javax.media.Format fmt) {
if (started) {
Log.warning("Cannot change audio capture format after started.");
return format;
}
if (fmt == null)
return format;
javax.media.Format f = null;
for (int i = 0; i < supported.length; i++) {
if (fmt.matches(supported[i]) &&
(f = fmt.intersects(supported[i])) != null) {
break;
}
}
if (f == null)
return format;
try {
if (devFormat != null) {
if (!devFormat.matches(f) && !DSound.isOpen()) {
// Can't change format if DirectSound is already opened
// for rendering.
//System.err.println("The capture format has changed.");
format = (AudioFormat)f;
disconnect();
connect();
}
} else {
format = (AudioFormat)f;
connect();
}
} catch (IOException e) {
return null;
}
if (afc != null)
afc.setCurrentFormat(format);
return format;
}
public boolean isConnected() {
return devFormat != null;
}
/**
* Connect to the device. It in turn calls openDev to do the
* real work.
*/
public void connect() throws IOException {
// Return if it's already connected.
if (isConnected())
return;
if (DSound.isOpen()) {
Log.warning("DSound is already opened for rendering. Will capture at the default format.");
format = null;
}
openDev();
if (pushThread == null) {
if ( /*securityPrivelege && */ (jmfSecurity != null) ) {
String permission = null;
try {
if (jmfSecurity.getName().startsWith("jmf-security")) {
permission = "thread";
jmfSecurity.requestPermission(mSecurity, clSecurity, argsSecurity,
JMFSecurity.THREAD);
mSecurity[0].invoke(clSecurity[0], argsSecurity[0]);
permission = "thread group";
jmfSecurity.requestPermission(mSecurity, clSecurity, argsSecurity,
JMFSecurity.THREAD_GROUP);
mSecurity[0].invoke(clSecurity[0], argsSecurity[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 = jdk12CreateThreadAction.cons;
pushThread = (PushThread) jdk12.doPrivM.invoke(
jdk12.ac,
new Object[] {
cons.newInstance(
new Object[] {
PushThread.class,
})});
} catch (Exception e) {
// System.out.println("Exception: creating pushThread");
}
} else {
pushThread = new PushThread();
}
pushThread.setSourceStream(this);
}
if (reconnect)
Log.comment("Capture buffer size: " + bufSize);
devFormat = format;
reconnect = false;
}
/**
* Open the capture device.
*/
void openDev() throws IOException {
if (format != null) {
int frameSize = (format.getSampleSizeInBits() * format.getChannels())/8;
if (frameSize == 0)
frameSize = 1;
bufSize = (int)(format.getSampleRate() * frameSize *
bc.getBufferLength() / 1000);
} else {
}
if (!DSound.isFormatSupported(format, bufSize))
throw new IOException("Cannot open audio device for input.");
try {
dSound = new DSoundST(format, bufSize);
dSound.open();
format = dSound.getFormat();
bufSize = dSound.getBufferSize();
} catch (Exception e) {
Log.error("Cannot open audio device for input: " + e);
throw new IOException(e.getMessage());
}
}
public void disconnect() {
if (dSound == null)
return;
/*
dataLine.drain();
*/
dSound.stop();
dSound.close();
dSound = null;
devFormat = null;
if (pushThread != null) {
pushThread.kill();
pushThread = null;
}
}
/**
* Start capturing.
*/
public void start() throws IOException {
if (dSound == null)
throw new IOException("A DirectSound input channel cannot be opened.");
if (started)
return;
// Check if the GUI control has specified a new format.
if (afc != null) {
Format f;
if ((f = afc.getFormat()) != null && !f.matches(format)) {
if (setFormat(f) == null) {
//System.err.println("The chosen format is not supported.");
}
}
afc.setEnabled(false);
}
// Disconnect the source if the reconnect flag is on.
if (reconnect)
disconnect();
// Connect the source if it's not already connected.
if (!isConnected())
connect();
// Flush the old data.
synchronized (cb) {
while (cb.canRead()) {
cb.read();
cb.readReport();
}
cb.notifyAll();
}
dSound.flush();
dSound.start();
pushThread.start();
started = true;
}
/**
* Stop the capture.
*/
public void stop() throws IOException {
if (!started)
return;
pushThread.pause();
if (dSound != null)
dSound.stop();
started = false;
if (afc != null && !DSound.isOpen())
afc.setEnabled(true);
}
public Format getFormat() {
return format;
}
public java.lang.Object[] getControls() {
return controls;
}
static public Format[] getSupportedFormats() {
return supported;
}
public void setTransferHandler(BufferTransferHandler th) {
transferHandler = th;
}
public boolean willReadBlock() {
return !started;
}
public void read(Buffer in) {
Buffer buffer;
Object data;
synchronized (cb) {
while (!cb.canRead()) {
try {
cb.wait();
} catch (Exception e) {}
}
buffer = cb.read();
}
// Swap data with the input buffer and my own buffer.
data = in.getData();
in.copy(buffer);
buffer.setData(data);
synchronized (cb) {
cb.readReport();
cb.notify();
}
}
/****************************************************************
* INNER CLASS
****************************************************************/
/**
* Format control
*/
class FC implements FormatControl, Owned {
DirectSoundStream jsss;
public FC(DirectSoundStream jsss) {
this.jsss = jsss;
}
public Object getOwner() {
return dsource;
}
public Format getFormat() {
return format;
}
public Format setFormat(Format fmt) {
return jsss.setFormat(fmt);
}
public Format [] getSupportedFormats() {
return supported;
}
public boolean isEnabled() {
return true;
}
public void setEnabled(boolean enabled) {
}
public java.awt.Component getControlComponent() {
if (afc == null) {
afc = new AudioFormatChooser(supported, format);
afc.setName("DirectSound");
if (started || dSound == null || DSound.isOpen())
afc.setEnabled(false);
}
return afc;
}
}
static int DefaultMinBufferSize = 16; // millisecs.
static int DefaultMaxBufferSize = 4000; // millisecs.
long bufLenReq = 50; // 1/20 sec.
/**
* BufferControl for the renderer.
*/
class BC implements BufferControl, Owned {
DirectSoundStream jsss;
BC(DirectSoundStream js) {
jsss = js;
}
public long getBufferLength() {
return bufLenReq;
}
public long setBufferLength(long time) {
if (time < DefaultMinBufferSize)
bufLenReq = DefaultMinBufferSize;
else if (time > DefaultMaxBufferSize)
bufLenReq = DefaultMaxBufferSize;
else
bufLenReq = time;
Log.comment("Capture buffer length set: " + bufLenReq);
reconnect = true;
return bufLenReq;
}
public long getMinimumThreshold() {
return 0;
}
public long setMinimumThreshold(long time) {
return 0;
}
public void setEnabledThreshold(boolean b) {
}
public boolean getEnabledThreshold() {
return false;
}
public java.awt.Component getControlComponent() {
return null;
}
public Object getOwner() {
return dsource;
}
}
}
/**
* The thread that pushes the data.
*/
/**
* This class used to be an inner class, which is the correct thing to do.
* Changed it to a package private class because of jdk1.2 security.
* For jdk1.2 and above applets, PushThread is created in a
* privileged block using jdk12CreateThreadAction. jdk12CreateThreadAction
* class is unable to create and instantiate an inner class
* in DirectSoundStream class
*/
class PushThread extends LoopThread {
private DirectSoundStream sourceStream;
private SystemTimeBase systemTimeBase = new SystemTimeBase();
private long seqNo = 0;
public PushThread() {
setName("DirectSound PushThread");
}
void setSourceStream(DirectSoundStream ss) {
sourceStream = ss;
}
protected boolean process() {
Buffer buffer;
byte data[];
int len;
CircularBuffer cb = sourceStream.cb;
BufferTransferHandler transferHandler = sourceStream.transferHandler;
synchronized (cb) {
while (!cb.canWrite()) {
try {
cb.wait();
} catch (Exception e) {}
}
buffer = cb.getEmptyBuffer();
}
if (buffer.getData() instanceof byte[])
data = (byte[])buffer.getData();
else
data = null;
if (data == null || data.length < sourceStream.bufSize) {
data = new byte[sourceStream.bufSize];
buffer.setData(data);
}
len = sourceStream.dSound.read(data, 0, sourceStream.bufSize);
//System.err.println("dsound.read " + len);
buffer.setOffset(0);
buffer.setLength(len);
buffer.setFormat(sourceStream.format);
buffer.setFlags(buffer.FLAG_SYSTEM_TIME | buffer.FLAG_LIVE_DATA);
buffer.setSequenceNumber(seqNo++);
if (len < 1) {
buffer.setFlags(buffer.FLAG_DISCARD);
}
// We are assuming very little capture latency.
buffer.setTimeStamp(systemTimeBase.getNanoseconds());
synchronized (cb) {
cb.writeReport();
cb.notify();
if (transferHandler != null && len > 0)
transferHandler.transferData(sourceStream);
}
return true;
}
}
|