/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// IDEA:
// Use java.util.concurrent.Semaphore,
// to improve this class's thread safety.
/**
* A Mixer which provides direct access to audio devices
*
* @author Florian Bomers
*/
// CONSTANTS
// INSTANCE VARIABLES
/** number of opened lines */
/** number of started lines */
// CONSTRUCTOR
// pass in Line.Info, mixer, controls
super(portMixerInfo, // Mixer.Info
null, // Control[]
null, // Line.Info[] sourceLineInfo
null); // Line.Info[] targetLineInfo
// source lines
if (srcLineInfo != null) {
// SourcedataLine
// Clip
32, // arbitrary minimum buffer size
} else {
}
// TargetDataLine
if (dstLineInfo != null) {
} else {
}
}
synchronized(formats) {
isSource /* true:SourceDataLine/Clip, false:TargetDataLine */,
formats);
int formatArraySize = size;
for (int i = 0; i < size; i++) {
hardwareFormatArray[i] = format;
if ((isSigned || isUnsigned)) {
// will insert a magically converted format here
}
}
int formatArrayIndex = 0;
for (int i = 0; i < size; i++) {
// add convenience formats (automatic conversion)
if (bits == 8) {
// add the other signed'ness for 8-bit
if (isSigned) {
format.isBigEndian());
}
else if (isUnsigned) {
format.isBigEndian());
}
// add the other endian'ness for more than 8-bit
!format.isBigEndian());
}
//System.out.println("Adding "+v.get(v.size()-1));
}
}
}
// todo: find out more about the buffer size ?
if (formatArray != null) {
32, // arbitrary minimum buffer size
}
return null;
}
// ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS
}
// if a format is specified by the info class passed in, use it.
// otherwise use a format from fullInfo.
}
// use the default format
lineFormat = null;
} else {
// use the last format specified in the line.info object passed
// in by the app
// if something is not specified, use default format
lineFormat = null;
}
}
}
}
}
}
}
// if it's not supported at all, return 0.
return 0;
}
// DirectAudioDevices should mix !
return getMaxSimulLines();
}
return 0;
}
}
protected void implClose() {
}
protected void implStart() {
}
protected void implStop() {
}
// IMPLEMENTATION HELPERS
int getMixerIndex() {
}
int getDeviceID() {
}
int getMaxSimulLines() {
}
private static void addFormat(Vector v, int bits, int frameSizeInBytes, int channels, float sampleRate,
switch (encoding) {
case PCM:
break;
case ULAW:
if (bits != 8) {
if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ULAW, but bitsPerSample="+bits);
}
break;
case ALAW:
if (bits != 8) {
if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ALAW, but bitsPerSample="+bits);
}
break;
}
if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with unknown encoding: "+encoding);
return;
}
if (frameSizeInBytes <= 0) {
if (channels > 0) {
} else {
}
}
}
// if this is PCM_SIGNED and 16-bit or higher, then try with endian-ness magic
}
// if this is PCM and 8-bit, then try with signed-ness magic
}
return null;
}
// INNER CLASSES
/**
* Private inner class for the DataLine.Info objects
* adds a little magic for the isFormatSupported so
* that the automagic conversion of endianness and sign
* does not show up in the formats array.
* I.e. the formats array contains only the formats
* that are really supported by the hardware,
* but isFormatSupported() also returns true
* for formats with wrong endianness.
*/
this.hardwareFormats = hardwareFormatArray;
}
return true;
}
}
return false;
}
/*public boolean isFormatSupported(AudioFormat format) {
* return isFormatSupportedInHardware(format)
* || isFormatSupportedInHardware(getSignOrEndianChangedFormat(format));
*}
*/
return hardwareFormats;
}
}
/**
* Private inner class as base class for direct lines
*/
protected final int mixerIndex;
protected final int deviceID;
protected long id;
protected int waitTime;
protected volatile boolean flushing = false;
protected volatile long bytePosition;
protected volatile boolean drained = false; // set to true when drain function returns, set to false in write()
protected boolean monitoring = false;
// is set to the framesize
// Guards all native calls.
// CONSTRUCTOR
int bufferSize,
int mixerIndex,
int deviceID,
boolean isSource) {
this.mixerIndex = mixerIndex;
}
// ABSTRACT METHOD IMPLEMENTATIONS
// ABSTRACT LINE / DATALINE
// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
// check for record permission
if (!isSource) {
}
}
}
}
}
/* set up controls */
if (isSource) {
// no controls for non-PCM formats */
}
// no support for more than 2 channels or more than 16 bits
} else {
} else {
/* to keep compatibility with apps that rely on
* MixerSourceLine's PanControl
*/
}
}
}
/* some magic to account for not-supported endianness or signed-ness */
// apparently, the new format can be used.
+softwareConversionSize+":");
}
}
}
// align buffer to full frames
if (id == 0) {
// TODO: nicer error messages...
throw new LineUnavailableException(
}
if (this.bufferSize < 1) {
// this is an error!
this.bufferSize = bufferSize;
}
// wait time = 1/4 of buffer time
if (waitTime < 10) {
waitTime = 1;
}
else if (waitTime > 1000) {
// we have seen large buffer sizes!
// never wait for more than a second
waitTime = 1000;
}
bytePosition = 0;
stoppedWritten = false;
doIO = false;
calcVolume();
}
void implStart() {
// check for record permission
if (!isSource) {
}
synchronized (lockNative)
{
}
// check for monitoring/servicing
if (monitoring) {
getEventDispatcher().addLineMonitor(this);
}
doIO = true;
// need to set Active and Started
// note: the current API always requires that
// Started and Active are set at the same time...
if (isSource && stoppedWritten) {
setStarted(true);
setActive(true);
}
}
void implStop() {
// check for record permission
if (!isSource) {
}
if (monitoring) {
getEventDispatcher().removeLineMonitor(this);
monitoring = false;
}
synchronized (lockNative) {
}
// wake up any waiting threads
synchronized(lock) {
// need to set doIO to false before notifying the
// cannot be used
doIO = false;
}
setActive(false);
setStarted(false);
stoppedWritten = false;
}
void implClose() {
// check for record permission
if (!isSource) {
}
// be sure to remove this monitor
if (monitoring) {
getEventDispatcher().removeLineMonitor(this);
monitoring = false;
}
doIO = false;
id = 0;
synchronized (lockNative) {
}
bytePosition = 0;
}
// METHOD OVERRIDES
public int available() {
if (id == 0) {
return 0;
}
int a;
synchronized (lockNative) {
}
return a;
}
public void drain() {
noService = true;
// additional safeguard against draining forever
// this occured on Solaris 8 x86, probably due to a bug
// in the audio driver
int counter = 0;
long startPos = getLongFramePosition();
boolean posChanged = false;
while (!drained) {
synchronized (lockNative) {
break;
}
// check every now and then for a new position
long thisFramePos = getLongFramePosition();
// when some time elapsed, check that the frame position
// really changed
if (!posChanged) {
break;
}
posChanged = false;
}
}
counter++;
synchronized(lock) {
try {
} catch (InterruptedException ie) {}
}
}
drained = true;
}
noService = false;
}
public void flush() {
if (id != 0) {
flushing = true;
synchronized(lock) {
}
synchronized (lockNative) {
if (id != 0) {
// then flush native buffers
}
}
drained = true;
}
}
// replacement for getFramePosition (see AbstractDataLine)
public long getLongFramePosition() {
long pos;
synchronized (lockNative) {
}
// hack because ALSA sometimes reports wrong framepos
if (pos < 0) {
pos = 0;
}
}
/*
* write() belongs into SourceDataLine and Clip,
* so define it here and make it accessible by
* declaring the respective interfaces with DirectSDL and DirectClip
*/
flushing = false;
if (len == 0) {
return 0;
}
if (len < 0) {
}
throw new IllegalArgumentException("illegal request to write "
+"non-integral number of frames ("
+len+" bytes, "
}
if (off < 0) {
throw new ArrayIndexOutOfBoundsException(off);
}
throw new ArrayIndexOutOfBoundsException(b.length);
}
// this is not exactly correct... would be nicer
// if the native sub system sent a callback when IO really starts
setActive(true);
setStarted(true);
}
int written = 0;
while (!flushing) {
int thisWritten;
synchronized (lockNative) {
if (thisWritten < 0) {
// error in native layer
break;
}
if (thisWritten > 0) {
drained = false;
}
}
len -= thisWritten;
written += thisWritten;
off += thisWritten;
synchronized (lock) {
try {
} catch (InterruptedException ie) {}
}
} else {
break;
}
}
stoppedWritten = true;
}
return written;
}
protected boolean requiresServicing() {
}
// called from event dispatcher for lines that need servicing
public void checkLine() {
synchronized (lockNative) {
if (monitoring
&& doIO
&& id != 0
&& !flushing
&& !noService) {
}
}
}
private void calcVolume() {
return;
}
if (muteControl.getValue()) {
leftGain = 0.0f;
rightGain = 0.0f;
return;
}
// trivial case: only use gain
} else {
// need to combine gain and balance
if (bal < 0.0f) {
// left
} else {
}
}
}
/////////////////// CONTROLS /////////////////////////////
private Gain() {
-1,
0.0f,
"dB", "Minimum", "", "Maximum");
}
// adjust value within range ?? spec says IllegalArgumentException
//newValue = Math.min(newValue, getMaximum());
//newValue = Math.max(newValue, getMinimum());
// if no exception, commit to our new gain
calcVolume();
}
float getLinearGain() {
return linearGain;
}
} // class Gain
private Mute() {
}
calcVolume();
}
} // class Mute
private Balance() {
"", "Left", "Center", "Right");
}
calcVolume();
}
}
} // class Balance
private Pan() {
"", "Left", "Center", "Right");
}
calcVolume();
}
}
} // class Pan
} // class DirectDL
/**
* Private inner class representing a SourceDataLine
*/
implements SourceDataLine {
// CONSTRUCTOR
int bufferSize,
}
}
/**
* Private inner class representing a TargetDataLine
*/
implements TargetDataLine {
// CONSTRUCTOR
int bufferSize,
}
// METHOD OVERRIDES
flushing = false;
if (len == 0) {
return 0;
}
if (len < 0) {
}
throw new IllegalArgumentException("illegal request to read "
+"non-integral number of frames ("
+len+" bytes, "
}
if (off < 0) {
throw new ArrayIndexOutOfBoundsException(off);
}
throw new ArrayIndexOutOfBoundsException(b.length);
}
// this is not exactly correct... would be nicer
// if the native sub system sent a callback when IO really starts
setActive(true);
setStarted(true);
}
int read = 0;
int thisRead;
synchronized (lockNative) {
if (thisRead < 0) {
// error in native layer
break;
}
bytePosition += thisRead;
if (thisRead > 0) {
drained = false;
}
}
if (len > 0) {
synchronized(lock) {
try {
} catch (InterruptedException ie) {}
}
} else {
break;
}
}
if (flushing) {
read = 0;
}
return read;
}
}
/**
* Private inner class representing a Clip
* This clip is realized in software only
*/
private int m_lengthInFrames;
private int loopCount;
private int loopStartFrame;
// auto closing clip support
private boolean autoclosing = false;
// CONSTRUCTOR
int bufferSize,
}
// CLIP METHODS
throws LineUnavailableException {
// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
byte[] newData = new byte[bufferSize];
}
// this method does not copy the data array
throws LineUnavailableException {
// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
synchronized (mixer) {
if (isOpen()) {
" and frame lengh of " + getFrameLength());
} else {
// if the line is not currently open, try to open it with this format and buffer size
this.m_lengthInFrames = frameLength;
// initialize loop selection with full range
bytePosition = 0;
clipBytePosition = 0;
loopStartFrame = 0;
try {
// use DirectDL's open method to open it
} catch (LineUnavailableException lue) {
throw lue;
} catch (IllegalArgumentException iae) {
throw iae;
}
// if we got this far, we can instanciate the thread
"Direct Clip", // name
true, // daemon
priority, // priority
false); // doStart
// cannot start in createThread, because the thread
// uses the "thread" variable as indicator if it should
// continue to run
}
}
if (isAutoClosing()) {
getEventDispatcher().autoClosingClipOpened(this);
}
}
// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
synchronized (mixer) {
byte[] streamData = null;
if (isOpen()) {
" and frame lengh of " + getFrameLength());
}
int bytesRead = 0;
// read the data from the stream into an array in one fell swoop.
streamData = new byte[arraysize];
int bytesRemaining = arraysize;
int thisRead = 0;
if (thisRead > 0) {
}
else if (thisRead == 0) {
}
}
} else {
// read data from the stream until we reach the end of the stream
// we use a slightly modified version of ByteArrayOutputStream
// to get direct access to the byte array (we don't want a new array
// to be allocated)
int MAX_READ_LIMIT = 16384;
byte tmp[] = new byte[MAX_READ_LIMIT];
int thisRead = 0;
while (thisRead >= 0) {
if (thisRead > 0) {
}
else if (thisRead == 0) {
}
} // while
}
// now try to open the device
} // synchronized
}
public int getFrameLength() {
return m_lengthInFrames;
}
public long getMicrosecondLength() {
}
if (frames < 0) {
frames = 0;
}
else if (frames >= getFrameLength()) {
frames = getFrameLength();
}
if (doIO) {
} else {
newFramePosition = -1;
}
// fix for failing test050
// $$fb although getFramePosition should return the number of rendered
// frames, it is intuitive that setFramePosition will modify that
// value.
// cease currently playing buffer
flush();
// set new native position (if necessary)
// this must come after the flush!
synchronized (lockNative) {
}
+" doIO="+doIO
+" newFramePosition="+newFramePosition
+" clipBytePosition="+clipBytePosition
+" bytePosition="+bytePosition
+" getLongFramePosition()="+getLongFramePosition());
}
// replacement for getFramePosition (see AbstractDataLine)
public long getLongFramePosition() {
/* $$fb
* this would be intuitive, but the definition of getFramePosition
* is the number of frames rendered since opening the device...
* That also means that setFramePosition() means something very
* different from getFramePosition() for Clip.
*/
// take into account the case that a new position was set...
//if (!doIO && newFramePosition >= 0) {
//return newFramePosition;
//}
return super.getLongFramePosition();
}
setFramePosition((int) frames);
}
}
if (end >= getFrameLength()) {
}
if (end == -1) {
if (end < 0) {
end = 0;
}
}
// if the end position is less than the start position, throw IllegalArgumentException
}
// slight race condition with the run() method, but not a big problem
loopEndFrame = end;
}
// note: when count reaches 0, it means that the entire clip
// will be played, i.e. it will play past the loop end point
start();
}
// ABSTRACT METHOD IMPLEMENTATIONS
// ABSTRACT LINE
// only if audioData wasn't set in a calling open(format, byte[], frameSize)
// this call is allowed.
throw new IllegalArgumentException("illegal call to open() in interface Clip");
}
}
void implClose() {
// dispose of thread
doIO = false;
// wake up the thread if it's in wait()
synchronized(lock) {
}
// wait for the thread to terminate itself,
// but max. 2 seconds. Must not be synchronized!
try {
} catch (InterruptedException ie) {}
}
super.implClose();
// remove audioData reference and hand it over to gc
newFramePosition = -1;
// remove this instance from the list of auto closing clips
getEventDispatcher().autoClosingClipClosed(this);
}
void implStart() {
super.implStart();
}
void implStop() {
super.implStop();
// reset loopCount field so that playback will be normal with
// next call to start()
loopCount = 0;
}
// main playback loop
public void run() {
// doIO is volatile, but we could check it, then get
// pre-empted while another thread changes doIO and notifies,
// before we wait (so we sleep in wait forever).
synchronized(lock) {
if (!doIO) {
try {
} catch(InterruptedException ie) {}
}
}
while (doIO) {
if (newFramePosition >= 0) {
newFramePosition = -1;
}
}
if (toWriteBytes > getBufferSize()) {
}
// make sure nobody called setFramePosition, or stop() during the write() call
// since endFrame is the last frame to be played,
// framePos is after endFrame when all frames, including framePos,
// are played.
// at end of playback. If looping is on, loop back to the beginning.
if (loopCount != LOOP_CONTINUOUSLY) {
loopCount--;
}
} else {
// no looping, stop playback
if (Printer.debug) Printer.debug(" doIO="+doIO+" written="+written+" clipBytePosition="+clipBytePosition);
drain();
stop();
}
}
}
}
}
}
// AUTO CLOSING CLIP SUPPORT
/* $$mp 2003-10-01
The following two methods are common between this class and
MixerClip. They should be moved to a base class, together
with the instance variable 'autoclosing'. */
public boolean isAutoClosing() {
return autoclosing;
}
if (value != autoclosing) {
if (isOpen()) {
if (value) {
getEventDispatcher().autoClosingClipOpened(this);
} else {
getEventDispatcher().autoClosingClipClosed(this);
}
}
autoclosing = value;
}
}
protected boolean requiresServicing() {
// no need for servicing for Clips
return false;
}
} // DirectClip
/*
* private inner class representing a ByteArrayOutputStream
* which allows retrieval of the internal array
*/
DirectBAOS() {
super();
}
public byte[] getInternalBuffer() {
return buf;
}
} // class DirectBAOS
int encoding,
float sampleRate,
int sampleSizeInBits,
int frameSize,
int channels,
boolean signed,
boolean bigEndian,
int bufferSize) throws LineUnavailableException;
// returns if the native implementation needs regular calls to nService()
// called in irregular intervals
}