|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.sun.media.sound; |
|
|
|
import javax.sound.sampled.AudioFormat; |
|
import javax.sound.sampled.AudioSystem; |
|
import javax.sound.sampled.Control; |
|
import javax.sound.sampled.DataLine; |
|
import javax.sound.sampled.LineEvent; |
|
import javax.sound.sampled.LineUnavailableException; |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract class AbstractDataLine extends AbstractLine implements DataLine { |
|
|
|
// DEFAULTS |
|
|
|
|
|
private final AudioFormat defaultFormat; |
|
|
|
|
|
private final int defaultBufferSize; |
|
|
|
|
|
protected final Object lock = new Object(); |
|
|
|
// STATE |
|
|
|
|
|
protected AudioFormat format; |
|
|
|
|
|
protected int bufferSize; |
|
|
|
private volatile boolean running; |
|
private volatile boolean started; |
|
private volatile boolean active; |
|
|
|
|
|
|
|
*/ |
|
protected AbstractDataLine(DataLine.Info info, AbstractMixer mixer, Control[] controls) { |
|
this(info, mixer, controls, null, AudioSystem.NOT_SPECIFIED); |
|
} |
|
|
|
|
|
|
|
*/ |
|
protected AbstractDataLine(DataLine.Info info, AbstractMixer mixer, Control[] controls, AudioFormat format, int bufferSize) { |
|
|
|
super(info, mixer, controls); |
|
|
|
|
|
if (format != null) { |
|
defaultFormat = format; |
|
} else { |
|
|
|
defaultFormat = new AudioFormat(44100.0f, 16, 2, true, Platform.isBigEndian()); |
|
} |
|
if (bufferSize > 0) { |
|
defaultBufferSize = bufferSize; |
|
} else { |
|
|
|
defaultBufferSize = ((int) (defaultFormat.getFrameRate() / 2)) * defaultFormat.getFrameSize(); |
|
} |
|
|
|
|
|
this.format = defaultFormat; |
|
this.bufferSize = defaultBufferSize; |
|
} |
|
|
|
|
|
// DATA LINE METHODS |
|
|
|
public final void open(AudioFormat format, int bufferSize) throws LineUnavailableException { |
|
|
|
synchronized (mixer) { |
|
if (Printer.trace) Printer.trace("> AbstractDataLine.open(format, bufferSize) (class: "+getClass().getName()); |
|
|
|
|
|
if (!isOpen()) { |
|
// make sure that the format is specified correctly |
|
|
|
Toolkit.isFullySpecifiedAudioFormat(format); |
|
|
|
if (Printer.debug) Printer.debug(" need to open the mixer..."); |
|
// reserve mixer resources for this line |
|
|
|
mixer.open(this); |
|
|
|
try { |
|
|
|
implOpen(format, bufferSize); |
|
|
|
|
|
setOpen(true); |
|
|
|
} catch (LineUnavailableException e) { |
|
|
|
mixer.close(this); |
|
throw e; |
|
} |
|
} else { |
|
if (Printer.debug) Printer.debug(" dataline already open"); |
|
|
|
// if the line is already open and the requested format differs from the |
|
// current settings, throw an IllegalStateException |
|
|
|
if (!format.matches(getFormat())) { |
|
throw new IllegalStateException("Line is already open with format " + getFormat() + |
|
" and bufferSize " + getBufferSize()); |
|
} |
|
|
|
if (bufferSize > 0) { |
|
setBufferSize(bufferSize); |
|
} |
|
} |
|
|
|
if (Printer.trace) Printer.trace("< AbstractDataLine.open(format, bufferSize) completed"); |
|
} |
|
} |
|
|
|
|
|
public final void open(AudioFormat format) throws LineUnavailableException { |
|
open(format, AudioSystem.NOT_SPECIFIED); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public int available() { |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public void drain() { |
|
if (Printer.trace) Printer.trace("AbstractDataLine: drain"); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public void flush() { |
|
if (Printer.trace) Printer.trace("AbstractDataLine: flush"); |
|
} |
|
|
|
|
|
public final void start() { |
|
|
|
synchronized(mixer) { |
|
if (Printer.trace) Printer.trace("> "+getClass().getName()+".start() - AbstractDataLine"); |
|
|
|
|
|
if (isOpen()) { |
|
|
|
if (!isStartedRunning()) { |
|
mixer.start(this); |
|
implStart(); |
|
running = true; |
|
} |
|
} |
|
} |
|
|
|
synchronized(lock) { |
|
lock.notifyAll(); |
|
} |
|
|
|
if (Printer.trace) Printer.trace("< "+getClass().getName()+".start() - AbstractDataLine"); |
|
} |
|
|
|
|
|
public final void stop() { |
|
|
|
|
|
synchronized(mixer) { |
|
if (Printer.trace) Printer.trace("> "+getClass().getName()+".stop() - AbstractDataLine"); |
|
|
|
|
|
if (isOpen()) { |
|
|
|
if (isStartedRunning()) { |
|
|
|
implStop(); |
|
mixer.stop(this); |
|
|
|
running = false; |
|
|
|
|
|
if (started && (!isActive())) { |
|
setStarted(false); |
|
} |
|
} |
|
} |
|
} |
|
|
|
synchronized(lock) { |
|
lock.notifyAll(); |
|
} |
|
|
|
if (Printer.trace) Printer.trace("< "+getClass().getName()+".stop() - AbstractDataLine"); |
|
} |
|
|
|
// $$jb: 12.10.99: The official API for this is isRunning(). |
|
// Per the denied RFE 4297981, |
|
// the change to isStarted() is technically an unapproved API change. |
|
// The 'started' variable is false when playback of data stops. |
|
// It is changed throughout the implementation with setStarted(). |
|
// This state is what should be returned by isRunning() in the API. |
|
// Note that the 'running' variable is true between calls to |
|
// start() and stop(). This state is accessed now through the |
|
// isStartedRunning() method, defined below. I have not changed |
|
// the variable names at this point, since 'running' is accessed |
|
// in MixerSourceLine and MixerClip, and I want to touch as little |
|
// code as possible to change isStarted() back to isRunning(). |
|
|
|
public final boolean isRunning() { |
|
return started; |
|
} |
|
|
|
public final boolean isActive() { |
|
return active; |
|
} |
|
|
|
|
|
public final long getMicrosecondPosition() { |
|
|
|
long microseconds = getLongFramePosition(); |
|
if (microseconds != AudioSystem.NOT_SPECIFIED) { |
|
microseconds = Toolkit.frames2micros(getFormat(), microseconds); |
|
} |
|
return microseconds; |
|
} |
|
|
|
|
|
public final AudioFormat getFormat() { |
|
return format; |
|
} |
|
|
|
|
|
public final int getBufferSize() { |
|
return bufferSize; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public final int setBufferSize(int newSize) { |
|
return getBufferSize(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public final float getLevel() { |
|
return (float)AudioSystem.NOT_SPECIFIED; |
|
} |
|
|
|
|
|
// HELPER METHODS |
|
|
|
/** |
|
* running is true after start is called and before stop is called, |
|
* regardless of whether data is actually being presented. |
|
*/ |
|
// $$jb: 12.10.99: calling this method isRunning() conflicts with |
|
// the official API that was once called isStarted(). Since we |
|
// use this method throughout the implementation, I am renaming |
|
// it to isStartedRunning(). This is part of backing out the |
|
// change denied in RFE 4297981. |
|
|
|
final boolean isStartedRunning() { |
|
return running; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
final void setActive(boolean active) { |
|
|
|
if (Printer.trace) Printer.trace("> AbstractDataLine: setActive(" + active + ")"); |
|
|
|
//boolean sendEvents = false; |
|
//long position = getLongFramePosition(); |
|
|
|
synchronized (this) { |
|
|
|
//if (Printer.debug) Printer.debug(" AbstractDataLine: setActive: this.active: " + this.active); |
|
//if (Printer.debug) Printer.debug(" active: " + active); |
|
|
|
if (this.active != active) { |
|
this.active = active; |
|
//sendEvents = true; |
|
} |
|
} |
|
|
|
//if (Printer.debug) Printer.debug(" this.active: " + this.active); |
|
//if (Printer.debug) Printer.debug(" sendEvents: " + sendEvents); |
|
|
|
|
|
// $$kk: 11.19.99: take ACTIVE / INACTIVE / EOM events out; |
|
// putting them in is technically an API change. |
|
// do not generate ACTIVE / INACTIVE events for now |
|
// if (sendEvents) { |
|
// |
|
// if (active) { |
|
// sendEvents(new LineEvent(this, LineEvent.Type.ACTIVE, position)); |
|
// } else { |
|
// sendEvents(new LineEvent(this, LineEvent.Type.INACTIVE, position)); |
|
// } |
|
//} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
final void setStarted(boolean started) { |
|
|
|
if (Printer.trace) Printer.trace("> AbstractDataLine: setStarted(" + started + ")"); |
|
|
|
boolean sendEvents = false; |
|
long position = getLongFramePosition(); |
|
|
|
synchronized (this) { |
|
|
|
//if (Printer.debug) Printer.debug(" AbstractDataLine: setStarted: this.started: " + this.started); |
|
//if (Printer.debug) Printer.debug(" started: " + started); |
|
|
|
if (this.started != started) { |
|
this.started = started; |
|
sendEvents = true; |
|
} |
|
} |
|
|
|
//if (Printer.debug) Printer.debug(" this.started: " + this.started); |
|
//if (Printer.debug) Printer.debug(" sendEvents: " + sendEvents); |
|
|
|
if (sendEvents) { |
|
|
|
if (started) { |
|
sendEvents(new LineEvent(this, LineEvent.Type.START, position)); |
|
} else { |
|
sendEvents(new LineEvent(this, LineEvent.Type.STOP, position)); |
|
} |
|
} |
|
if (Printer.trace) Printer.trace("< AbstractDataLine: setStarted completed"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final void setEOM() { |
|
|
|
if (Printer.trace) Printer.trace("> AbstractDataLine: setEOM()"); |
|
//$$fb 2002-04-21: sometimes, 2 STOP events are generated. |
|
|
|
setStarted(false); |
|
if (Printer.trace) Printer.trace("< AbstractDataLine: setEOM() completed"); |
|
} |
|
|
|
|
|
|
|
|
|
// OVERRIDES OF ABSTRACT LINE METHODS |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final void open() throws LineUnavailableException { |
|
|
|
if (Printer.trace) Printer.trace("> "+getClass().getName()+".open() - AbstractDataLine"); |
|
|
|
|
|
open(format, bufferSize); |
|
if (Printer.trace) Printer.trace("< "+getClass().getName()+".open() - AbstractDataLine"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final void close() { |
|
|
|
synchronized (mixer) { |
|
if (Printer.trace) Printer.trace("> "+getClass().getName()+".close() - in AbstractDataLine."); |
|
|
|
if (isOpen()) { |
|
|
|
|
|
stop(); |
|
|
|
|
|
setOpen(false); |
|
|
|
|
|
implClose(); |
|
|
|
|
|
mixer.close(this); |
|
|
|
|
|
format = defaultFormat; |
|
bufferSize = defaultBufferSize; |
|
} |
|
} |
|
if (Printer.trace) Printer.trace("< "+getClass().getName()+".close() - in AbstractDataLine"); |
|
} |
|
|
|
|
|
// IMPLEMENTATIONS OF ABSTRACT LINE ABSTRACE METHODS |
|
|
|
|
|
// ABSTRACT METHODS |
|
|
|
abstract void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException; |
|
abstract void implClose(); |
|
|
|
abstract void implStart(); |
|
abstract void implStop(); |
|
} |