Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/com/sun/media/sound/DirectAudioDevice.java
38924 views
/*1* Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.sun.media.sound;2627import java.io.ByteArrayOutputStream;28import java.io.IOException;29import java.util.Vector;3031import javax.sound.sampled.*;3233// IDEA:34// Use java.util.concurrent.Semaphore,35// java.util.concurrent.locks.ReentrantLock and other new classes/methods36// to improve this class's thread safety.373839/**40* A Mixer which provides direct access to audio devices41*42* @author Florian Bomers43*/44final class DirectAudioDevice extends AbstractMixer {4546// CONSTANTS47private static final int CLIP_BUFFER_TIME = 1000; // in milliseconds4849private static final int DEFAULT_LINE_BUFFER_TIME = 500; // in milliseconds5051// INSTANCE VARIABLES5253/** number of opened lines */54private int deviceCountOpened = 0;5556/** number of started lines */57private int deviceCountStarted = 0;5859// CONSTRUCTOR60DirectAudioDevice(DirectAudioDeviceProvider.DirectAudioDeviceInfo portMixerInfo) {61// pass in Line.Info, mixer, controls62super(portMixerInfo, // Mixer.Info63null, // Control[]64null, // Line.Info[] sourceLineInfo65null); // Line.Info[] targetLineInfo6667if (Printer.trace) Printer.trace(">> DirectAudioDevice: constructor");6869// source lines70DirectDLI srcLineInfo = createDataLineInfo(true);71if (srcLineInfo != null) {72sourceLineInfo = new Line.Info[2];73// SourcedataLine74sourceLineInfo[0] = srcLineInfo;75// Clip76sourceLineInfo[1] = new DirectDLI(Clip.class, srcLineInfo.getFormats(),77srcLineInfo.getHardwareFormats(),7832, // arbitrary minimum buffer size79AudioSystem.NOT_SPECIFIED);80} else {81sourceLineInfo = new Line.Info[0];82}8384// TargetDataLine85DataLine.Info dstLineInfo = createDataLineInfo(false);86if (dstLineInfo != null) {87targetLineInfo = new Line.Info[1];88targetLineInfo[0] = dstLineInfo;89} else {90targetLineInfo = new Line.Info[0];91}92if (Printer.trace) Printer.trace("<< DirectAudioDevice: constructor completed");93}9495private DirectDLI createDataLineInfo(boolean isSource) {96Vector formats = new Vector();97AudioFormat[] hardwareFormatArray = null;98AudioFormat[] formatArray = null;99100synchronized(formats) {101nGetFormats(getMixerIndex(), getDeviceID(),102isSource /* true:SourceDataLine/Clip, false:TargetDataLine */,103formats);104if (formats.size() > 0) {105int size = formats.size();106int formatArraySize = size;107hardwareFormatArray = new AudioFormat[size];108for (int i = 0; i < size; i++) {109AudioFormat format = (AudioFormat)formats.elementAt(i);110hardwareFormatArray[i] = format;111int bits = format.getSampleSizeInBits();112boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);113boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);114if ((isSigned || isUnsigned)) {115// will insert a magically converted format here116formatArraySize++;117}118}119formatArray = new AudioFormat[formatArraySize];120int formatArrayIndex = 0;121for (int i = 0; i < size; i++) {122AudioFormat format = hardwareFormatArray[i];123formatArray[formatArrayIndex++] = format;124int bits = format.getSampleSizeInBits();125boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);126boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);127// add convenience formats (automatic conversion)128if (bits == 8) {129// add the other signed'ness for 8-bit130if (isSigned) {131formatArray[formatArrayIndex++] =132new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,133format.getSampleRate(), bits, format.getChannels(),134format.getFrameSize(), format.getSampleRate(),135format.isBigEndian());136}137else if (isUnsigned) {138formatArray[formatArrayIndex++] =139new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,140format.getSampleRate(), bits, format.getChannels(),141format.getFrameSize(), format.getSampleRate(),142format.isBigEndian());143}144} else if (bits > 8 && (isSigned || isUnsigned)) {145// add the other endian'ness for more than 8-bit146formatArray[formatArrayIndex++] =147new AudioFormat(format.getEncoding(),148format.getSampleRate(), bits,149format.getChannels(),150format.getFrameSize(),151format.getSampleRate(),152!format.isBigEndian());153}154//System.out.println("Adding "+v.get(v.size()-1));155}156}157}158// todo: find out more about the buffer size ?159if (formatArray != null) {160return new DirectDLI(isSource?SourceDataLine.class:TargetDataLine.class,161formatArray, hardwareFormatArray,16232, // arbitrary minimum buffer size163AudioSystem.NOT_SPECIFIED);164}165return null;166}167168// ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS169170public Line getLine(Line.Info info) throws LineUnavailableException {171Line.Info fullInfo = getLineInfo(info);172if (fullInfo == null) {173throw new IllegalArgumentException("Line unsupported: " + info);174}175if (fullInfo instanceof DataLine.Info) {176177DataLine.Info dataLineInfo = (DataLine.Info)fullInfo;178AudioFormat lineFormat;179int lineBufferSize = AudioSystem.NOT_SPECIFIED;180181// if a format is specified by the info class passed in, use it.182// otherwise use a format from fullInfo.183184AudioFormat[] supportedFormats = null;185186if (info instanceof DataLine.Info) {187supportedFormats = ((DataLine.Info)info).getFormats();188lineBufferSize = ((DataLine.Info)info).getMaxBufferSize();189}190191if ((supportedFormats == null) || (supportedFormats.length == 0)) {192// use the default format193lineFormat = null;194} else {195// use the last format specified in the line.info object passed196// in by the app197lineFormat = supportedFormats[supportedFormats.length-1];198199// if something is not specified, use default format200if (!Toolkit.isFullySpecifiedPCMFormat(lineFormat)) {201lineFormat = null;202}203}204205if (dataLineInfo.getLineClass().isAssignableFrom(DirectSDL.class)) {206return new DirectSDL(dataLineInfo, lineFormat, lineBufferSize, this);207}208if (dataLineInfo.getLineClass().isAssignableFrom(DirectClip.class)) {209return new DirectClip(dataLineInfo, lineFormat, lineBufferSize, this);210}211if (dataLineInfo.getLineClass().isAssignableFrom(DirectTDL.class)) {212return new DirectTDL(dataLineInfo, lineFormat, lineBufferSize, this);213}214}215throw new IllegalArgumentException("Line unsupported: " + info);216}217218219public int getMaxLines(Line.Info info) {220Line.Info fullInfo = getLineInfo(info);221222// if it's not supported at all, return 0.223if (fullInfo == null) {224return 0;225}226227if (fullInfo instanceof DataLine.Info) {228// DirectAudioDevices should mix !229return getMaxSimulLines();230}231232return 0;233}234235236protected void implOpen() throws LineUnavailableException {237if (Printer.trace) Printer.trace("DirectAudioDevice: implOpen - void method");238}239240protected void implClose() {241if (Printer.trace) Printer.trace("DirectAudioDevice: implClose - void method");242}243244protected void implStart() {245if (Printer.trace) Printer.trace("DirectAudioDevice: implStart - void method");246}247248protected void implStop() {249if (Printer.trace) Printer.trace("DirectAudioDevice: implStop - void method");250}251252253// IMPLEMENTATION HELPERS254255int getMixerIndex() {256return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getIndex();257}258259int getDeviceID() {260return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getDeviceID();261}262263int getMaxSimulLines() {264return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getMaxSimulLines();265}266267private static void addFormat(Vector v, int bits, int frameSizeInBytes, int channels, float sampleRate,268int encoding, boolean signed, boolean bigEndian) {269AudioFormat.Encoding enc = null;270switch (encoding) {271case PCM:272enc = signed?AudioFormat.Encoding.PCM_SIGNED:AudioFormat.Encoding.PCM_UNSIGNED;273break;274case ULAW:275enc = AudioFormat.Encoding.ULAW;276if (bits != 8) {277if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ULAW, but bitsPerSample="+bits);278bits = 8; frameSizeInBytes = channels;279}280break;281case ALAW:282enc = AudioFormat.Encoding.ALAW;283if (bits != 8) {284if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ALAW, but bitsPerSample="+bits);285bits = 8; frameSizeInBytes = channels;286}287break;288}289if (enc==null) {290if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with unknown encoding: "+encoding);291return;292}293if (frameSizeInBytes <= 0) {294if (channels > 0) {295frameSizeInBytes = ((bits + 7) / 8) * channels;296} else {297frameSizeInBytes = AudioSystem.NOT_SPECIFIED;298}299}300v.add(new AudioFormat(enc, sampleRate, bits, channels, frameSizeInBytes, sampleRate, bigEndian));301}302303protected static AudioFormat getSignOrEndianChangedFormat(AudioFormat format) {304boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);305boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);306if (format.getSampleSizeInBits() > 8 && isSigned) {307// if this is PCM_SIGNED and 16-bit or higher, then try with endian-ness magic308return new AudioFormat(format.getEncoding(),309format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),310format.getFrameSize(), format.getFrameRate(), !format.isBigEndian());311}312else if (format.getSampleSizeInBits() == 8 && (isSigned || isUnsigned)) {313// if this is PCM and 8-bit, then try with signed-ness magic314return new AudioFormat(isSigned?AudioFormat.Encoding.PCM_UNSIGNED:AudioFormat.Encoding.PCM_SIGNED,315format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),316format.getFrameSize(), format.getFrameRate(), format.isBigEndian());317}318return null;319}320321322323324// INNER CLASSES325326327/**328* Private inner class for the DataLine.Info objects329* adds a little magic for the isFormatSupported so330* that the automagic conversion of endianness and sign331* does not show up in the formats array.332* I.e. the formats array contains only the formats333* that are really supported by the hardware,334* but isFormatSupported() also returns true335* for formats with wrong endianness.336*/337private static final class DirectDLI extends DataLine.Info {338final AudioFormat[] hardwareFormats;339340private DirectDLI(Class clazz, AudioFormat[] formatArray,341AudioFormat[] hardwareFormatArray,342int minBuffer, int maxBuffer) {343super(clazz, formatArray, minBuffer, maxBuffer);344this.hardwareFormats = hardwareFormatArray;345}346347public boolean isFormatSupportedInHardware(AudioFormat format) {348if (format == null) return false;349for (int i = 0; i < hardwareFormats.length; i++) {350if (format.matches(hardwareFormats[i])) {351return true;352}353}354return false;355}356357/*public boolean isFormatSupported(AudioFormat format) {358* return isFormatSupportedInHardware(format)359* || isFormatSupportedInHardware(getSignOrEndianChangedFormat(format));360*}361*/362363private AudioFormat[] getHardwareFormats() {364return hardwareFormats;365}366}367368/**369* Private inner class as base class for direct lines370*/371private static class DirectDL extends AbstractDataLine implements EventDispatcher.LineMonitor {372protected final int mixerIndex;373protected final int deviceID;374protected long id;375protected int waitTime;376protected volatile boolean flushing = false;377protected final boolean isSource; // true for SourceDataLine, false for TargetDataLine378protected volatile long bytePosition;379protected volatile boolean doIO = false; // true in between start() and stop() calls380protected volatile boolean stoppedWritten = false; // true if a write occurred in stopped state381protected volatile boolean drained = false; // set to true when drain function returns, set to false in write()382protected boolean monitoring = false;383384// if native needs to manually swap samples/convert sign, this385// is set to the framesize386protected int softwareConversionSize = 0;387protected AudioFormat hardwareFormat;388389private final Gain gainControl = new Gain();390private final Mute muteControl = new Mute();391private final Balance balanceControl = new Balance();392private final Pan panControl = new Pan();393private float leftGain, rightGain;394protected volatile boolean noService = false; // do not run the nService method395396// Guards all native calls.397protected final Object lockNative = new Object();398399// CONSTRUCTOR400protected DirectDL(DataLine.Info info,401DirectAudioDevice mixer,402AudioFormat format,403int bufferSize,404int mixerIndex,405int deviceID,406boolean isSource) {407super(info, mixer, null, format, bufferSize);408if (Printer.trace) Printer.trace("DirectDL CONSTRUCTOR: info: " + info);409this.mixerIndex = mixerIndex;410this.deviceID = deviceID;411this.waitTime = 10; // 10 milliseconds default wait time412this.isSource = isSource;413414}415416417// ABSTRACT METHOD IMPLEMENTATIONS418419// ABSTRACT LINE / DATALINE420421void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {422if (Printer.trace) Printer.trace(">> DirectDL: implOpen("+format+", "+bufferSize+" bytes)");423424// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions425Toolkit.isFullySpecifiedAudioFormat(format);426427// check for record permission428if (!isSource) {429JSSecurityManager.checkRecordPermission();430}431int encoding = PCM;432if (format.getEncoding().equals(AudioFormat.Encoding.ULAW)) {433encoding = ULAW;434}435else if (format.getEncoding().equals(AudioFormat.Encoding.ALAW)) {436encoding = ALAW;437}438439if (bufferSize <= AudioSystem.NOT_SPECIFIED) {440bufferSize = (int) Toolkit.millis2bytes(format, DEFAULT_LINE_BUFFER_TIME);441}442443DirectDLI ddli = null;444if (info instanceof DirectDLI) {445ddli = (DirectDLI) info;446}447448/* set up controls */449if (isSource) {450if (!format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)451&& !format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {452// no controls for non-PCM formats */453controls = new Control[0];454}455else if (format.getChannels() > 2456|| format.getSampleSizeInBits() > 16) {457// no support for more than 2 channels or more than 16 bits458controls = new Control[0];459} else {460if (format.getChannels() == 1) {461controls = new Control[2];462} else {463controls = new Control[4];464controls[2] = balanceControl;465/* to keep compatibility with apps that rely on466* MixerSourceLine's PanControl467*/468controls[3] = panControl;469}470controls[0] = gainControl;471controls[1] = muteControl;472}473}474if (Printer.debug) Printer.debug("DirectAudioDevice: got "+controls.length+" controls.");475476hardwareFormat = format;477478/* some magic to account for not-supported endianness or signed-ness */479softwareConversionSize = 0;480if (ddli != null && !ddli.isFormatSupportedInHardware(format)) {481AudioFormat newFormat = getSignOrEndianChangedFormat(format);482if (ddli.isFormatSupportedInHardware(newFormat)) {483// apparently, the new format can be used.484hardwareFormat = newFormat;485// So do endian/sign conversion in software486softwareConversionSize = format.getFrameSize() / format.getChannels();487if (Printer.debug) {488Printer.debug("DirectAudioDevice: softwareConversionSize "489+softwareConversionSize+":");490Printer.debug(" from "+format);491Printer.debug(" to "+newFormat);492}493}494}495496// align buffer to full frames497bufferSize = ((int) bufferSize / format.getFrameSize()) * format.getFrameSize();498499id = nOpen(mixerIndex, deviceID, isSource,500encoding,501hardwareFormat.getSampleRate(),502hardwareFormat.getSampleSizeInBits(),503hardwareFormat.getFrameSize(),504hardwareFormat.getChannels(),505hardwareFormat.getEncoding().equals(506AudioFormat.Encoding.PCM_SIGNED),507hardwareFormat.isBigEndian(),508bufferSize);509510if (id == 0) {511// TODO: nicer error messages...512throw new LineUnavailableException(513"line with format "+format+" not supported.");514}515516this.bufferSize = nGetBufferSize(id, isSource);517if (this.bufferSize < 1) {518// this is an error!519this.bufferSize = bufferSize;520}521this.format = format;522// wait time = 1/4 of buffer time523waitTime = (int) Toolkit.bytes2millis(format, this.bufferSize) / 4;524if (waitTime < 10) {525waitTime = 1;526}527else if (waitTime > 1000) {528// we have seen large buffer sizes!529// never wait for more than a second530waitTime = 1000;531}532bytePosition = 0;533stoppedWritten = false;534doIO = false;535calcVolume();536537if (Printer.trace) Printer.trace("<< DirectDL: implOpen() succeeded");538}539540541void implStart() {542if (Printer.trace) Printer.trace(" >> DirectDL: implStart()");543544// check for record permission545if (!isSource) {546JSSecurityManager.checkRecordPermission();547}548549synchronized (lockNative)550{551nStart(id, isSource);552}553// check for monitoring/servicing554monitoring = requiresServicing();555if (monitoring) {556getEventDispatcher().addLineMonitor(this);557}558559synchronized(lock) {560doIO = true;561// need to set Active and Started562// note: the current API always requires that563// Started and Active are set at the same time...564if (isSource && stoppedWritten) {565setStarted(true);566setActive(true);567}568}569570if (Printer.trace) Printer.trace("<< DirectDL: implStart() succeeded");571}572573void implStop() {574if (Printer.trace) Printer.trace(">> DirectDL: implStop()");575576// check for record permission577if (!isSource) {578JSSecurityManager.checkRecordPermission();579}580581if (monitoring) {582getEventDispatcher().removeLineMonitor(this);583monitoring = false;584}585synchronized (lockNative) {586nStop(id, isSource);587}588// wake up any waiting threads589synchronized(lock) {590// need to set doIO to false before notifying the591// read/write thread, that's why isStartedRunning()592// cannot be used593doIO = false;594setActive(false);595setStarted(false);596lock.notifyAll();597}598stoppedWritten = false;599600if (Printer.trace) Printer.trace(" << DirectDL: implStop() succeeded");601}602603void implClose() {604if (Printer.trace) Printer.trace(">> DirectDL: implClose()");605606// check for record permission607if (!isSource) {608JSSecurityManager.checkRecordPermission();609}610611// be sure to remove this monitor612if (monitoring) {613getEventDispatcher().removeLineMonitor(this);614monitoring = false;615}616617doIO = false;618long oldID = id;619id = 0;620synchronized (lockNative) {621nClose(oldID, isSource);622}623bytePosition = 0;624softwareConversionSize = 0;625if (Printer.trace) Printer.trace("<< DirectDL: implClose() succeeded");626}627628// METHOD OVERRIDES629630public int available() {631if (id == 0) {632return 0;633}634int a;635synchronized (lockNative) {636a = nAvailable(id, isSource);637}638return a;639}640641642public void drain() {643noService = true;644// additional safeguard against draining forever645// this occurred on Solaris 8 x86, probably due to a bug646// in the audio driver647int counter = 0;648long startPos = getLongFramePosition();649boolean posChanged = false;650while (!drained) {651synchronized (lockNative) {652if ((id == 0) || (!doIO) || !nIsStillDraining(id, isSource))653break;654}655// check every now and then for a new position656if ((counter % 5) == 4) {657long thisFramePos = getLongFramePosition();658posChanged = posChanged | (thisFramePos != startPos);659if ((counter % 50) > 45) {660// when some time elapsed, check that the frame position661// really changed662if (!posChanged) {663if (Printer.err) Printer.err("Native reports isDraining, but frame position does not increase!");664break;665}666posChanged = false;667startPos = thisFramePos;668}669}670counter++;671synchronized(lock) {672try {673lock.wait(10);674} catch (InterruptedException ie) {}675}676}677678if (doIO && id != 0) {679drained = true;680}681noService = false;682}683684public void flush() {685if (id != 0) {686// first stop ongoing read/write method687flushing = true;688synchronized(lock) {689lock.notifyAll();690}691synchronized (lockNative) {692if (id != 0) {693// then flush native buffers694nFlush(id, isSource);695}696}697drained = true;698}699}700701// replacement for getFramePosition (see AbstractDataLine)702public long getLongFramePosition() {703long pos;704synchronized (lockNative) {705pos = nGetBytePosition(id, isSource, bytePosition);706}707// hack because ALSA sometimes reports wrong framepos708if (pos < 0) {709if (Printer.debug) Printer.debug("DirectLine.getLongFramePosition: Native reported pos="710+pos+"! is changed to 0. byteposition="+bytePosition);711pos = 0;712}713return (pos / getFormat().getFrameSize());714}715716717/*718* write() belongs into SourceDataLine and Clip,719* so define it here and make it accessible by720* declaring the respective interfaces with DirectSDL and DirectClip721*/722public int write(byte[] b, int off, int len) {723flushing = false;724if (len == 0) {725return 0;726}727if (len < 0) {728throw new IllegalArgumentException("illegal len: "+len);729}730if (len % getFormat().getFrameSize() != 0) {731throw new IllegalArgumentException("illegal request to write "732+"non-integral number of frames ("733+len+" bytes, "734+"frameSize = "+getFormat().getFrameSize()+" bytes)");735}736if (off < 0) {737throw new ArrayIndexOutOfBoundsException(off);738}739if ((long)off + (long)len > (long)b.length) {740throw new ArrayIndexOutOfBoundsException(b.length);741}742synchronized(lock) {743if (!isActive() && doIO) {744// this is not exactly correct... would be nicer745// if the native sub system sent a callback when IO really746// starts747setActive(true);748setStarted(true);749}750}751int written = 0;752while (!flushing) {753int thisWritten;754synchronized (lockNative) {755thisWritten = nWrite(id, b, off, len,756softwareConversionSize,757leftGain, rightGain);758if (thisWritten < 0) {759// error in native layer760break;761}762bytePosition += thisWritten;763if (thisWritten > 0) {764drained = false;765}766}767len -= thisWritten;768written += thisWritten;769if (doIO && len > 0) {770off += thisWritten;771synchronized (lock) {772try {773lock.wait(waitTime);774} catch (InterruptedException ie) {}775}776} else {777break;778}779}780if (written > 0 && !doIO) {781stoppedWritten = true;782}783return written;784}785786protected boolean requiresServicing() {787return nRequiresServicing(id, isSource);788}789790// called from event dispatcher for lines that need servicing791public void checkLine() {792synchronized (lockNative) {793if (monitoring794&& doIO795&& id != 0796&& !flushing797&& !noService) {798nService(id, isSource);799}800}801}802803private void calcVolume() {804if (getFormat() == null) {805return;806}807if (muteControl.getValue()) {808leftGain = 0.0f;809rightGain = 0.0f;810return;811}812float gain = gainControl.getLinearGain();813if (getFormat().getChannels() == 1) {814// trivial case: only use gain815leftGain = gain;816rightGain = gain;817} else {818// need to combine gain and balance819float bal = balanceControl.getValue();820if (bal < 0.0f) {821// left822leftGain = gain;823rightGain = gain * (bal + 1.0f);824} else {825leftGain = gain * (1.0f - bal);826rightGain = gain;827}828}829}830831832/////////////////// CONTROLS /////////////////////////////833834protected final class Gain extends FloatControl {835836private float linearGain = 1.0f;837838private Gain() {839840super(FloatControl.Type.MASTER_GAIN,841Toolkit.linearToDB(0.0f),842Toolkit.linearToDB(2.0f),843Math.abs(Toolkit.linearToDB(1.0f)-Toolkit.linearToDB(0.0f))/128.0f,844-1,8450.0f,846"dB", "Minimum", "", "Maximum");847}848849public void setValue(float newValue) {850// adjust value within range ?? spec says IllegalArgumentException851//newValue = Math.min(newValue, getMaximum());852//newValue = Math.max(newValue, getMinimum());853854float newLinearGain = Toolkit.dBToLinear(newValue);855super.setValue(Toolkit.linearToDB(newLinearGain));856// if no exception, commit to our new gain857linearGain = newLinearGain;858calcVolume();859}860861float getLinearGain() {862return linearGain;863}864} // class Gain865866867private final class Mute extends BooleanControl {868869private Mute() {870super(BooleanControl.Type.MUTE, false, "True", "False");871}872873public void setValue(boolean newValue) {874super.setValue(newValue);875calcVolume();876}877} // class Mute878879private final class Balance extends FloatControl {880881private Balance() {882super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,883"", "Left", "Center", "Right");884}885886public void setValue(float newValue) {887setValueImpl(newValue);888panControl.setValueImpl(newValue);889calcVolume();890}891892void setValueImpl(float newValue) {893super.setValue(newValue);894}895896} // class Balance897898private final class Pan extends FloatControl {899900private Pan() {901super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,902"", "Left", "Center", "Right");903}904905public void setValue(float newValue) {906setValueImpl(newValue);907balanceControl.setValueImpl(newValue);908calcVolume();909}910void setValueImpl(float newValue) {911super.setValue(newValue);912}913} // class Pan914915916917} // class DirectDL918919920/**921* Private inner class representing a SourceDataLine922*/923private static final class DirectSDL extends DirectDL924implements SourceDataLine {925926// CONSTRUCTOR927private DirectSDL(DataLine.Info info,928AudioFormat format,929int bufferSize,930DirectAudioDevice mixer) {931super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);932if (Printer.trace) Printer.trace("DirectSDL CONSTRUCTOR: completed");933}934935}936937/**938* Private inner class representing a TargetDataLine939*/940private static final class DirectTDL extends DirectDL941implements TargetDataLine {942943// CONSTRUCTOR944private DirectTDL(DataLine.Info info,945AudioFormat format,946int bufferSize,947DirectAudioDevice mixer) {948super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), false);949if (Printer.trace) Printer.trace("DirectTDL CONSTRUCTOR: completed");950}951952// METHOD OVERRIDES953954public int read(byte[] b, int off, int len) {955flushing = false;956if (len == 0) {957return 0;958}959if (len < 0) {960throw new IllegalArgumentException("illegal len: "+len);961}962if (len % getFormat().getFrameSize() != 0) {963throw new IllegalArgumentException("illegal request to read "964+"non-integral number of frames ("965+len+" bytes, "966+"frameSize = "+getFormat().getFrameSize()+" bytes)");967}968if (off < 0) {969throw new ArrayIndexOutOfBoundsException(off);970}971if ((long)off + (long)len > (long)b.length) {972throw new ArrayIndexOutOfBoundsException(b.length);973}974synchronized(lock) {975if (!isActive() && doIO) {976// this is not exactly correct... would be nicer977// if the native sub system sent a callback when IO really978// starts979setActive(true);980setStarted(true);981}982}983int read = 0;984while (doIO && !flushing) {985int thisRead;986synchronized (lockNative) {987thisRead = nRead(id, b, off, len, softwareConversionSize);988if (thisRead < 0) {989// error in native layer990break;991}992bytePosition += thisRead;993if (thisRead > 0) {994drained = false;995}996}997len -= thisRead;998read += thisRead;999if (len > 0) {1000off += thisRead;1001synchronized(lock) {1002try {1003lock.wait(waitTime);1004} catch (InterruptedException ie) {}1005}1006} else {1007break;1008}1009}1010if (flushing) {1011read = 0;1012}1013return read;1014}10151016}10171018/**1019* Private inner class representing a Clip1020* This clip is realized in software only1021*/1022private static final class DirectClip extends DirectDL1023implements Clip, Runnable, AutoClosingClip {10241025private volatile Thread thread;1026private volatile byte[] audioData = null;1027private volatile int frameSize; // size of one frame in bytes1028private volatile int m_lengthInFrames;1029private volatile int loopCount;1030private volatile int clipBytePosition; // index in the audioData array at current playback1031private volatile int newFramePosition; // set in setFramePosition()1032private volatile int loopStartFrame;1033private volatile int loopEndFrame; // the last sample included in the loop10341035// auto closing clip support1036private boolean autoclosing = false;10371038// CONSTRUCTOR1039private DirectClip(DataLine.Info info,1040AudioFormat format,1041int bufferSize,1042DirectAudioDevice mixer) {1043super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);1044if (Printer.trace) Printer.trace("DirectClip CONSTRUCTOR: completed");1045}10461047// CLIP METHODS10481049public void open(AudioFormat format, byte[] data, int offset, int bufferSize)1050throws LineUnavailableException {10511052// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions1053Toolkit.isFullySpecifiedAudioFormat(format);10541055byte[] newData = new byte[bufferSize];1056System.arraycopy(data, offset, newData, 0, bufferSize);1057open(format, newData, bufferSize / format.getFrameSize());1058}10591060// this method does not copy the data array1061private void open(AudioFormat format, byte[] data, int frameLength)1062throws LineUnavailableException {10631064// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions1065Toolkit.isFullySpecifiedAudioFormat(format);10661067synchronized (mixer) {1068if (Printer.trace) Printer.trace("> DirectClip.open(format, data, frameLength)");1069if (Printer.debug) Printer.debug(" data="+((data==null)?"null":""+data.length+" bytes"));1070if (Printer.debug) Printer.debug(" frameLength="+frameLength);10711072if (isOpen()) {1073throw new IllegalStateException("Clip is already open with format " + getFormat() +1074" and frame lengh of " + getFrameLength());1075} else {1076// if the line is not currently open, try to open it with this format and buffer size1077this.audioData = data;1078this.frameSize = format.getFrameSize();1079this.m_lengthInFrames = frameLength;1080// initialize loop selection with full range1081bytePosition = 0;1082clipBytePosition = 0;1083newFramePosition = -1; // means: do not set to a new readFramePos1084loopStartFrame = 0;1085loopEndFrame = frameLength - 1;1086loopCount = 0; // means: play the clip irrespective of loop points from beginning to end10871088try {1089// use DirectDL's open method to open it1090open(format, (int) Toolkit.millis2bytes(format, CLIP_BUFFER_TIME)); // one second buffer1091} catch (LineUnavailableException lue) {1092audioData = null;1093throw lue;1094} catch (IllegalArgumentException iae) {1095audioData = null;1096throw iae;1097}10981099// if we got this far, we can instanciate the thread1100int priority = Thread.NORM_PRIORITY1101+ (Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 3;1102thread = JSSecurityManager.createThread(this,1103"Direct Clip", // name1104true, // daemon1105priority, // priority1106false); // doStart1107// cannot start in createThread, because the thread1108// uses the "thread" variable as indicator if it should1109// continue to run1110thread.start();1111}1112}1113if (isAutoClosing()) {1114getEventDispatcher().autoClosingClipOpened(this);1115}1116if (Printer.trace) Printer.trace("< DirectClip.open completed");1117}111811191120public void open(AudioInputStream stream) throws LineUnavailableException, IOException {11211122// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions1123Toolkit.isFullySpecifiedAudioFormat(stream.getFormat());11241125synchronized (mixer) {1126if (Printer.trace) Printer.trace("> DirectClip.open(stream)");1127byte[] streamData = null;11281129if (isOpen()) {1130throw new IllegalStateException("Clip is already open with format " + getFormat() +1131" and frame lengh of " + getFrameLength());1132}1133int lengthInFrames = (int)stream.getFrameLength();1134if (Printer.debug) Printer.debug("DirectClip: open(AIS): lengthInFrames: " + lengthInFrames);11351136int bytesRead = 0;1137int frameSize = stream.getFormat().getFrameSize();1138if (lengthInFrames != AudioSystem.NOT_SPECIFIED) {1139// read the data from the stream into an array in one fell swoop.1140int arraysize = lengthInFrames * frameSize;1141if (arraysize < 0) {1142throw new IllegalArgumentException("Audio data < 0");1143}1144try {1145streamData = new byte[arraysize];1146} catch (OutOfMemoryError e) {1147throw new IOException("Audio data is too big");1148}1149int bytesRemaining = arraysize;1150int thisRead = 0;1151while (bytesRemaining > 0 && thisRead >= 0) {1152thisRead = stream.read(streamData, bytesRead, bytesRemaining);1153if (thisRead > 0) {1154bytesRead += thisRead;1155bytesRemaining -= thisRead;1156}1157else if (thisRead == 0) {1158Thread.yield();1159}1160}1161} else {1162// read data from the stream until we reach the end of the stream1163// we use a slightly modified version of ByteArrayOutputStream1164// to get direct access to the byte array (we don't want a new array1165// to be allocated)1166int maxReadLimit = Math.max(16384, frameSize);1167DirectBAOS dbaos = new DirectBAOS();1168byte[] tmp;1169try {1170tmp = new byte[maxReadLimit];1171} catch (OutOfMemoryError e) {1172throw new IOException("Audio data is too big");1173}1174int thisRead = 0;1175while (thisRead >= 0) {1176thisRead = stream.read(tmp, 0, tmp.length);1177if (thisRead > 0) {1178dbaos.write(tmp, 0, thisRead);1179bytesRead += thisRead;1180}1181else if (thisRead == 0) {1182Thread.yield();1183}1184} // while1185streamData = dbaos.getInternalBuffer();1186}1187lengthInFrames = bytesRead / frameSize;11881189if (Printer.debug) Printer.debug("Read to end of stream. lengthInFrames: " + lengthInFrames);11901191// now try to open the device1192open(stream.getFormat(), streamData, lengthInFrames);11931194if (Printer.trace) Printer.trace("< DirectClip.open(stream) succeeded");1195} // synchronized1196}119711981199public int getFrameLength() {1200return m_lengthInFrames;1201}120212031204public long getMicrosecondLength() {1205return Toolkit.frames2micros(getFormat(), getFrameLength());1206}120712081209public void setFramePosition(int frames) {1210if (Printer.trace) Printer.trace("> DirectClip: setFramePosition: " + frames);12111212if (frames < 0) {1213frames = 0;1214}1215else if (frames >= getFrameLength()) {1216frames = getFrameLength();1217}1218if (doIO) {1219newFramePosition = frames;1220} else {1221clipBytePosition = frames * frameSize;1222newFramePosition = -1;1223}1224// fix for failing test0501225// $$fb although getFramePosition should return the number of rendered1226// frames, it is intuitive that setFramePosition will modify that1227// value.1228bytePosition = frames * frameSize;12291230// cease currently playing buffer1231flush();12321233// set new native position (if necessary)1234// this must come after the flush!1235synchronized (lockNative) {1236nSetBytePosition(id, isSource, frames * frameSize);1237}12381239if (Printer.debug) Printer.debug(" DirectClip.setFramePosition: "1240+" doIO="+doIO1241+" newFramePosition="+newFramePosition1242+" clipBytePosition="+clipBytePosition1243+" bytePosition="+bytePosition1244+" getLongFramePosition()="+getLongFramePosition());1245if (Printer.trace) Printer.trace("< DirectClip: setFramePosition");1246}12471248// replacement for getFramePosition (see AbstractDataLine)1249public long getLongFramePosition() {1250/* $$fb1251* this would be intuitive, but the definition of getFramePosition1252* is the number of frames rendered since opening the device...1253* That also means that setFramePosition() means something very1254* different from getFramePosition() for Clip.1255*/1256// take into account the case that a new position was set...1257//if (!doIO && newFramePosition >= 0) {1258//return newFramePosition;1259//}1260return super.getLongFramePosition();1261}126212631264public synchronized void setMicrosecondPosition(long microseconds) {1265if (Printer.trace) Printer.trace("> DirectClip: setMicrosecondPosition: " + microseconds);12661267long frames = Toolkit.micros2frames(getFormat(), microseconds);1268setFramePosition((int) frames);12691270if (Printer.trace) Printer.trace("< DirectClip: setMicrosecondPosition succeeded");1271}12721273public void setLoopPoints(int start, int end) {1274if (Printer.trace) Printer.trace("> DirectClip: setLoopPoints: start: " + start + " end: " + end);12751276if (start < 0 || start >= getFrameLength()) {1277throw new IllegalArgumentException("illegal value for start: "+start);1278}1279if (end >= getFrameLength()) {1280throw new IllegalArgumentException("illegal value for end: "+end);1281}12821283if (end == -1) {1284end = getFrameLength() - 1;1285if (end < 0) {1286end = 0;1287}1288}12891290// if the end position is less than the start position, throw IllegalArgumentException1291if (end < start) {1292throw new IllegalArgumentException("End position " + end + " preceeds start position " + start);1293}12941295// slight race condition with the run() method, but not a big problem1296loopStartFrame = start;1297loopEndFrame = end;12981299if (Printer.trace) Printer.trace(" loopStart: " + loopStartFrame + " loopEnd: " + loopEndFrame);1300if (Printer.trace) Printer.trace("< DirectClip: setLoopPoints completed");1301}130213031304public void loop(int count) {1305// note: when count reaches 0, it means that the entire clip1306// will be played, i.e. it will play past the loop end point1307loopCount = count;1308start();1309}13101311// ABSTRACT METHOD IMPLEMENTATIONS13121313// ABSTRACT LINE13141315void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {1316// only if audioData wasn't set in a calling open(format, byte[], frameSize)1317// this call is allowed.1318if (audioData == null) {1319throw new IllegalArgumentException("illegal call to open() in interface Clip");1320}1321super.implOpen(format, bufferSize);1322}13231324void implClose() {1325if (Printer.trace) Printer.trace(">> DirectClip: implClose()");13261327// dispose of thread1328Thread oldThread = thread;1329thread = null;1330doIO = false;1331if (oldThread != null) {1332// wake up the thread if it's in wait()1333synchronized(lock) {1334lock.notifyAll();1335}1336// wait for the thread to terminate itself,1337// but max. 2 seconds. Must not be synchronized!1338try {1339oldThread.join(2000);1340} catch (InterruptedException ie) {}1341}1342super.implClose();1343// remove audioData reference and hand it over to gc1344audioData = null;1345newFramePosition = -1;13461347// remove this instance from the list of auto closing clips1348getEventDispatcher().autoClosingClipClosed(this);13491350if (Printer.trace) Printer.trace("<< DirectClip: implClose() succeeded");1351}135213531354void implStart() {1355if (Printer.trace) Printer.trace("> DirectClip: implStart()");1356super.implStart();1357if (Printer.trace) Printer.trace("< DirectClip: implStart() succeeded");1358}13591360void implStop() {1361if (Printer.trace) Printer.trace(">> DirectClip: implStop()");13621363super.implStop();1364// reset loopCount field so that playback will be normal with1365// next call to start()1366loopCount = 0;13671368if (Printer.trace) Printer.trace("<< DirectClip: implStop() succeeded");1369}137013711372// main playback loop1373public void run() {1374if (Printer.trace) Printer.trace(">>> DirectClip: run() threadID="+Thread.currentThread().getId());1375Thread curThread = Thread.currentThread();1376while (thread == curThread) {1377// doIO is volatile, but we could check it, then get1378// pre-empted while another thread changes doIO and notifies,1379// before we wait (so we sleep in wait forever).1380synchronized(lock) {1381while (!doIO && thread == curThread) {1382try {1383lock.wait();1384} catch (InterruptedException ignored) {1385}1386}1387}1388while (doIO && thread == curThread) {1389if (newFramePosition >= 0) {1390clipBytePosition = newFramePosition * frameSize;1391newFramePosition = -1;1392}1393int endFrame = getFrameLength() - 1;1394if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {1395endFrame = loopEndFrame;1396}1397long framePos = (clipBytePosition / frameSize);1398int toWriteFrames = (int) (endFrame - framePos + 1);1399int toWriteBytes = toWriteFrames * frameSize;1400if (toWriteBytes > getBufferSize()) {1401toWriteBytes = Toolkit.align(getBufferSize(), frameSize);1402}1403int written = write(audioData, (int) clipBytePosition, toWriteBytes); // increases bytePosition1404clipBytePosition += written;1405// make sure nobody called setFramePosition, or stop() during the write() call1406if (doIO && newFramePosition < 0 && written >= 0) {1407framePos = clipBytePosition / frameSize;1408// since endFrame is the last frame to be played,1409// framePos is after endFrame when all frames, including framePos,1410// are played.1411if (framePos > endFrame) {1412// at end of playback. If looping is on, loop back to the beginning.1413if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {1414if (loopCount != LOOP_CONTINUOUSLY) {1415loopCount--;1416}1417newFramePosition = loopStartFrame;1418} else {1419// no looping, stop playback1420if (Printer.debug) Printer.debug("stop clip in run() loop:");1421if (Printer.debug) Printer.debug(" doIO="+doIO+" written="+written+" clipBytePosition="+clipBytePosition);1422if (Printer.debug) Printer.debug(" framePos="+framePos+" endFrame="+endFrame);1423drain();1424stop();1425}1426}1427}1428}1429}1430if (Printer.trace) Printer.trace("<<< DirectClip: run() threadID="+Thread.currentThread().getId());1431}14321433// AUTO CLOSING CLIP SUPPORT14341435/* $$mp 2003-10-011436The following two methods are common between this class and1437MixerClip. They should be moved to a base class, together1438with the instance variable 'autoclosing'. */14391440public boolean isAutoClosing() {1441return autoclosing;1442}14431444public void setAutoClosing(boolean value) {1445if (value != autoclosing) {1446if (isOpen()) {1447if (value) {1448getEventDispatcher().autoClosingClipOpened(this);1449} else {1450getEventDispatcher().autoClosingClipClosed(this);1451}1452}1453autoclosing = value;1454}1455}14561457protected boolean requiresServicing() {1458// no need for servicing for Clips1459return false;1460}14611462} // DirectClip14631464/*1465* private inner class representing a ByteArrayOutputStream1466* which allows retrieval of the internal array1467*/1468private static class DirectBAOS extends ByteArrayOutputStream {1469DirectBAOS() {1470super();1471}14721473public byte[] getInternalBuffer() {1474return buf;1475}14761477} // class DirectBAOS147814791480private static native void nGetFormats(int mixerIndex, int deviceID,1481boolean isSource, Vector formats);14821483private static native long nOpen(int mixerIndex, int deviceID, boolean isSource,1484int encoding,1485float sampleRate,1486int sampleSizeInBits,1487int frameSize,1488int channels,1489boolean signed,1490boolean bigEndian,1491int bufferSize) throws LineUnavailableException;1492private static native void nStart(long id, boolean isSource);1493private static native void nStop(long id, boolean isSource);1494private static native void nClose(long id, boolean isSource);1495private static native int nWrite(long id, byte[] b, int off, int len, int conversionSize,1496float volLeft, float volRight);1497private static native int nRead(long id, byte[] b, int off, int len, int conversionSize);1498private static native int nGetBufferSize(long id, boolean isSource);1499private static native boolean nIsStillDraining(long id, boolean isSource);1500private static native void nFlush(long id, boolean isSource);1501private static native int nAvailable(long id, boolean isSource);1502// javaPos is number of bytes read/written in Java layer1503private static native long nGetBytePosition(long id, boolean isSource, long javaPos);1504private static native void nSetBytePosition(long id, boolean isSource, long pos);15051506// returns if the native implementation needs regular calls to nService()1507private static native boolean nRequiresServicing(long id, boolean isSource);1508// called in irregular intervals1509private static native void nService(long id, boolean isSource);15101511}151215131514