Path: blob/master/src/java.desktop/share/classes/com/sun/media/sound/DirectAudioDevice.java
41161 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.AudioFormat;32import javax.sound.sampled.AudioInputStream;33import javax.sound.sampled.AudioSystem;34import javax.sound.sampled.BooleanControl;35import javax.sound.sampled.Clip;36import javax.sound.sampled.Control;37import javax.sound.sampled.DataLine;38import javax.sound.sampled.FloatControl;39import javax.sound.sampled.Line;40import javax.sound.sampled.LineUnavailableException;41import javax.sound.sampled.SourceDataLine;42import javax.sound.sampled.TargetDataLine;4344// IDEA:45// Use java.util.concurrent.Semaphore,46// java.util.concurrent.locks.ReentrantLock and other new classes/methods47// to improve this class's thread safety.4849/**50* A Mixer which provides direct access to audio devices.51*52* @author Florian Bomers53*/54final class DirectAudioDevice extends AbstractMixer {5556private static final int CLIP_BUFFER_TIME = 1000; // in milliseconds5758private static final int DEFAULT_LINE_BUFFER_TIME = 500; // in milliseconds5960DirectAudioDevice(DirectAudioDeviceProvider.DirectAudioDeviceInfo portMixerInfo) {61// pass in Line.Info, mixer, controls62super(portMixerInfo, // Mixer.Info63null, // Control[]64null, // Line.Info[] sourceLineInfo65null); // Line.Info[] targetLineInfo66// source lines67DirectDLI srcLineInfo = createDataLineInfo(true);68if (srcLineInfo != null) {69sourceLineInfo = new Line.Info[2];70// SourcedataLine71sourceLineInfo[0] = srcLineInfo;72// Clip73sourceLineInfo[1] = new DirectDLI(Clip.class, srcLineInfo.getFormats(),74srcLineInfo.getHardwareFormats(),7532, // arbitrary minimum buffer size76AudioSystem.NOT_SPECIFIED);77} else {78sourceLineInfo = new Line.Info[0];79}8081// TargetDataLine82DataLine.Info dstLineInfo = createDataLineInfo(false);83if (dstLineInfo != null) {84targetLineInfo = new Line.Info[1];85targetLineInfo[0] = dstLineInfo;86} else {87targetLineInfo = new Line.Info[0];88}89}9091private DirectDLI createDataLineInfo(boolean isSource) {92Vector<AudioFormat> formats = new Vector<>();93AudioFormat[] hardwareFormatArray = null;94AudioFormat[] formatArray = null;9596synchronized(formats) {97nGetFormats(getMixerIndex(), getDeviceID(),98isSource /* true:SourceDataLine/Clip, false:TargetDataLine */,99formats);100if (formats.size() > 0) {101int size = formats.size();102int formatArraySize = size;103hardwareFormatArray = new AudioFormat[size];104for (int i = 0; i < size; i++) {105AudioFormat format = formats.elementAt(i);106hardwareFormatArray[i] = format;107int bits = format.getSampleSizeInBits();108boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);109boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);110if ((isSigned || isUnsigned)) {111// will insert a magically converted format here112formatArraySize++;113}114}115formatArray = new AudioFormat[formatArraySize];116int formatArrayIndex = 0;117for (int i = 0; i < size; i++) {118AudioFormat format = hardwareFormatArray[i];119formatArray[formatArrayIndex++] = format;120int bits = format.getSampleSizeInBits();121boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);122boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);123// add convenience formats (automatic conversion)124if (bits == 8) {125// add the other signed'ness for 8-bit126if (isSigned) {127formatArray[formatArrayIndex++] =128new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,129format.getSampleRate(), bits, format.getChannels(),130format.getFrameSize(), format.getSampleRate(),131format.isBigEndian());132}133else if (isUnsigned) {134formatArray[formatArrayIndex++] =135new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,136format.getSampleRate(), bits, format.getChannels(),137format.getFrameSize(), format.getSampleRate(),138format.isBigEndian());139}140} else if (bits > 8 && (isSigned || isUnsigned)) {141// add the other endian'ness for more than 8-bit142formatArray[formatArrayIndex++] =143new AudioFormat(format.getEncoding(),144format.getSampleRate(), bits,145format.getChannels(),146format.getFrameSize(),147format.getSampleRate(),148!format.isBigEndian());149}150//System.out.println("Adding "+v.get(v.size()-1));151}152}153}154// todo: find out more about the buffer size ?155if (formatArray != null) {156return new DirectDLI(isSource?SourceDataLine.class:TargetDataLine.class,157formatArray, hardwareFormatArray,15832, // arbitrary minimum buffer size159AudioSystem.NOT_SPECIFIED);160}161return null;162}163164// ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS165166@Override167public Line getLine(Line.Info info) throws LineUnavailableException {168Line.Info fullInfo = getLineInfo(info);169if (fullInfo == null) {170throw new IllegalArgumentException("Line unsupported: " + info);171}172if (fullInfo instanceof DataLine.Info) {173174DataLine.Info dataLineInfo = (DataLine.Info)fullInfo;175AudioFormat lineFormat;176int lineBufferSize = AudioSystem.NOT_SPECIFIED;177178// if a format is specified by the info class passed in, use it.179// otherwise use a format from fullInfo.180181AudioFormat[] supportedFormats = null;182183if (info instanceof DataLine.Info) {184supportedFormats = ((DataLine.Info)info).getFormats();185lineBufferSize = ((DataLine.Info)info).getMaxBufferSize();186}187188if ((supportedFormats == null) || (supportedFormats.length == 0)) {189// use the default format190lineFormat = null;191} else {192// use the last format specified in the line.info object passed193// in by the app194lineFormat = supportedFormats[supportedFormats.length-1];195196// if something is not specified, use default format197if (!Toolkit.isFullySpecifiedPCMFormat(lineFormat)) {198lineFormat = null;199}200}201202if (dataLineInfo.getLineClass().isAssignableFrom(DirectSDL.class)) {203return new DirectSDL(dataLineInfo, lineFormat, lineBufferSize, this);204}205if (dataLineInfo.getLineClass().isAssignableFrom(DirectClip.class)) {206return new DirectClip(dataLineInfo, lineFormat, lineBufferSize, this);207}208if (dataLineInfo.getLineClass().isAssignableFrom(DirectTDL.class)) {209return new DirectTDL(dataLineInfo, lineFormat, lineBufferSize, this);210}211}212throw new IllegalArgumentException("Line unsupported: " + info);213}214215@Override216public int getMaxLines(Line.Info info) {217Line.Info fullInfo = getLineInfo(info);218219// if it's not supported at all, return 0.220if (fullInfo == null) {221return 0;222}223224if (fullInfo instanceof DataLine.Info) {225// DirectAudioDevices should mix !226return getMaxSimulLines();227}228229return 0;230}231232@Override233protected void implOpen() throws LineUnavailableException {234}235236@Override237protected void implClose() {238}239240@Override241protected void implStart() {242}243244@Override245protected void implStop() {246}247248int getMixerIndex() {249return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getIndex();250}251252int getDeviceID() {253return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getDeviceID();254}255256int getMaxSimulLines() {257return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getMaxSimulLines();258}259260private static void addFormat(Vector<AudioFormat> v, int bits, int frameSizeInBytes, int channels, float sampleRate,261int encoding, boolean signed, boolean bigEndian) {262AudioFormat.Encoding enc = null;263switch (encoding) {264case PCM:265enc = signed?AudioFormat.Encoding.PCM_SIGNED:AudioFormat.Encoding.PCM_UNSIGNED;266break;267case ULAW:268enc = AudioFormat.Encoding.ULAW;269if (bits != 8) {270if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ULAW, but bitsPerSample="+bits);271bits = 8; frameSizeInBytes = channels;272}273break;274case ALAW:275enc = AudioFormat.Encoding.ALAW;276if (bits != 8) {277if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ALAW, but bitsPerSample="+bits);278bits = 8; frameSizeInBytes = channels;279}280break;281}282if (enc==null) {283if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with unknown encoding: "+encoding);284return;285}286if (frameSizeInBytes <= 0) {287if (channels > 0) {288frameSizeInBytes = ((bits + 7) / 8) * channels;289} else {290frameSizeInBytes = AudioSystem.NOT_SPECIFIED;291}292}293v.add(new AudioFormat(enc, sampleRate, bits, channels, frameSizeInBytes, sampleRate, bigEndian));294}295296protected static AudioFormat getSignOrEndianChangedFormat(AudioFormat format) {297boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);298boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);299if (format.getSampleSizeInBits() > 8 && isSigned) {300// if this is PCM_SIGNED and 16-bit or higher, then try with endian-ness magic301return new AudioFormat(format.getEncoding(),302format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),303format.getFrameSize(), format.getFrameRate(), !format.isBigEndian());304}305else if (format.getSampleSizeInBits() == 8 && (isSigned || isUnsigned)) {306// if this is PCM and 8-bit, then try with signed-ness magic307return new AudioFormat(isSigned?AudioFormat.Encoding.PCM_UNSIGNED:AudioFormat.Encoding.PCM_SIGNED,308format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),309format.getFrameSize(), format.getFrameRate(), format.isBigEndian());310}311return null;312}313314/**315* Private inner class for the DataLine.Info objects316* adds a little magic for the isFormatSupported so317* that the automagic conversion of endianness and sign318* does not show up in the formats array.319* I.e. the formats array contains only the formats320* that are really supported by the hardware,321* but isFormatSupported() also returns true322* for formats with wrong endianness.323*/324private static final class DirectDLI extends DataLine.Info {325final AudioFormat[] hardwareFormats;326327private DirectDLI(Class<?> clazz, AudioFormat[] formatArray,328AudioFormat[] hardwareFormatArray,329int minBuffer, int maxBuffer) {330super(clazz, formatArray, minBuffer, maxBuffer);331this.hardwareFormats = hardwareFormatArray;332}333334public boolean isFormatSupportedInHardware(AudioFormat format) {335if (format == null) return false;336for (int i = 0; i < hardwareFormats.length; i++) {337if (format.matches(hardwareFormats[i])) {338return true;339}340}341return false;342}343344/*public boolean isFormatSupported(AudioFormat format) {345* return isFormatSupportedInHardware(format)346* || isFormatSupportedInHardware(getSignOrEndianChangedFormat(format));347*}348*/349350private AudioFormat[] getHardwareFormats() {351return hardwareFormats;352}353}354355/**356* Private inner class as base class for direct lines.357*/358private static class DirectDL extends AbstractDataLine implements EventDispatcher.LineMonitor {359protected final int mixerIndex;360protected final int deviceID;361protected long id;362protected int waitTime;363protected volatile boolean flushing = false;364protected final boolean isSource; // true for SourceDataLine, false for TargetDataLine365protected volatile long bytePosition;366protected volatile boolean doIO = false; // true in between start() and stop() calls367protected volatile boolean stoppedWritten = false; // true if a write occurred in stopped state368protected volatile boolean drained = false; // set to true when drain function returns, set to false in write()369protected boolean monitoring = false;370371// if native needs to manually swap samples/convert sign, this372// is set to the framesize373protected int softwareConversionSize = 0;374protected AudioFormat hardwareFormat;375376private final Gain gainControl = new Gain();377private final Mute muteControl = new Mute();378private final Balance balanceControl = new Balance();379private final Pan panControl = new Pan();380private float leftGain, rightGain;381protected volatile boolean noService = false; // do not run the nService method382383// Guards all native calls.384protected final Object lockNative = new Object();385386protected DirectDL(DataLine.Info info,387DirectAudioDevice mixer,388AudioFormat format,389int bufferSize,390int mixerIndex,391int deviceID,392boolean isSource) {393super(info, mixer, null, format, bufferSize);394this.mixerIndex = mixerIndex;395this.deviceID = deviceID;396this.waitTime = 10; // 10 milliseconds default wait time397this.isSource = isSource;398399}400401@Override402void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {403// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions404Toolkit.isFullySpecifiedAudioFormat(format);405406// check for record permission407if (!isSource) {408JSSecurityManager.checkRecordPermission();409}410int encoding = PCM;411if (format.getEncoding().equals(AudioFormat.Encoding.ULAW)) {412encoding = ULAW;413}414else if (format.getEncoding().equals(AudioFormat.Encoding.ALAW)) {415encoding = ALAW;416}417418if (bufferSize <= AudioSystem.NOT_SPECIFIED) {419bufferSize = (int) Toolkit.millis2bytes(format, DEFAULT_LINE_BUFFER_TIME);420}421422DirectDLI ddli = null;423if (info instanceof DirectDLI) {424ddli = (DirectDLI) info;425}426427/* set up controls */428if (isSource) {429if (!format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)430&& !format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {431// no controls for non-PCM formats */432controls = new Control[0];433}434else if (format.getChannels() > 2435|| format.getSampleSizeInBits() > 16) {436// no support for more than 2 channels or more than 16 bits437controls = new Control[0];438} else {439if (format.getChannels() == 1) {440controls = new Control[2];441} else {442controls = new Control[4];443controls[2] = balanceControl;444/* to keep compatibility with apps that rely on445* MixerSourceLine's PanControl446*/447controls[3] = panControl;448}449controls[0] = gainControl;450controls[1] = muteControl;451}452}453hardwareFormat = format;454455/* some magic to account for not-supported endianness or signed-ness */456softwareConversionSize = 0;457if (ddli != null && !ddli.isFormatSupportedInHardware(format)) {458AudioFormat newFormat = getSignOrEndianChangedFormat(format);459if (ddli.isFormatSupportedInHardware(newFormat)) {460// apparently, the new format can be used.461hardwareFormat = newFormat;462// So do endian/sign conversion in software463softwareConversionSize = format.getFrameSize() / format.getChannels();464}465}466467// align buffer to full frames468bufferSize = ( bufferSize / format.getFrameSize()) * format.getFrameSize();469470id = nOpen(mixerIndex, deviceID, isSource,471encoding,472hardwareFormat.getSampleRate(),473hardwareFormat.getSampleSizeInBits(),474hardwareFormat.getFrameSize(),475hardwareFormat.getChannels(),476hardwareFormat.getEncoding().equals(477AudioFormat.Encoding.PCM_SIGNED),478hardwareFormat.isBigEndian(),479bufferSize);480481if (id == 0) {482// TODO: nicer error messages...483throw new LineUnavailableException(484"line with format "+format+" not supported.");485}486487this.bufferSize = nGetBufferSize(id, isSource);488if (this.bufferSize < 1) {489// this is an error!490this.bufferSize = bufferSize;491}492this.format = format;493// wait time = 1/4 of buffer time494waitTime = (int) Toolkit.bytes2millis(format, this.bufferSize) / 4;495if (waitTime < 10) {496waitTime = 1;497}498else if (waitTime > 1000) {499// we have seen large buffer sizes!500// never wait for more than a second501waitTime = 1000;502}503bytePosition = 0;504stoppedWritten = false;505doIO = false;506calcVolume();507}508509@Override510void implStart() {511// check for record permission512if (!isSource) {513JSSecurityManager.checkRecordPermission();514}515516synchronized (lockNative)517{518nStart(id, isSource);519}520// check for monitoring/servicing521monitoring = requiresServicing();522if (monitoring) {523getEventDispatcher().addLineMonitor(this);524}525526synchronized(lock) {527doIO = true;528// need to set Active and Started529// note: the current API always requires that530// Started and Active are set at the same time...531if (isSource && stoppedWritten) {532setStarted(true);533setActive(true);534}535}536}537538@Override539void implStop() {540// check for record permission541if (!isSource) {542JSSecurityManager.checkRecordPermission();543}544545if (monitoring) {546getEventDispatcher().removeLineMonitor(this);547monitoring = false;548}549synchronized (lockNative) {550nStop(id, isSource);551}552// wake up any waiting threads553synchronized(lock) {554// need to set doIO to false before notifying the555// read/write thread, that's why isStartedRunning()556// cannot be used557doIO = false;558setActive(false);559setStarted(false);560lock.notifyAll();561}562stoppedWritten = false;563}564565@Override566void implClose() {567// check for record permission568if (!isSource) {569JSSecurityManager.checkRecordPermission();570}571572// be sure to remove this monitor573if (monitoring) {574getEventDispatcher().removeLineMonitor(this);575monitoring = false;576}577578doIO = false;579long oldID = id;580id = 0;581synchronized (lockNative) {582nClose(oldID, isSource);583}584bytePosition = 0;585softwareConversionSize = 0;586}587588@Override589public int available() {590if (id == 0) {591return 0;592}593int a;594synchronized (lockNative) {595a = nAvailable(id, isSource);596}597return a;598}599600@Override601public void drain() {602noService = true;603// additional safeguard against draining forever604// this occurred on Solaris 8 x86, probably due to a bug605// in the audio driver606int counter = 0;607long startPos = getLongFramePosition();608boolean posChanged = false;609while (!drained) {610synchronized (lockNative) {611if ((id == 0) || (!doIO) || !nIsStillDraining(id, isSource))612break;613}614// check every now and then for a new position615if ((counter % 5) == 4) {616long thisFramePos = getLongFramePosition();617posChanged = posChanged | (thisFramePos != startPos);618if ((counter % 50) > 45) {619// when some time elapsed, check that the frame position620// really changed621if (!posChanged) {622if (Printer.err) Printer.err("Native reports isDraining, but frame position does not increase!");623break;624}625posChanged = false;626startPos = thisFramePos;627}628}629counter++;630synchronized(lock) {631try {632lock.wait(10);633} catch (InterruptedException ie) {}634}635}636637if (doIO && id != 0) {638drained = true;639}640noService = false;641}642643@Override644public void flush() {645if (id != 0) {646// first stop ongoing read/write method647flushing = true;648synchronized(lock) {649lock.notifyAll();650}651synchronized (lockNative) {652if (id != 0) {653// then flush native buffers654nFlush(id, isSource);655}656}657drained = true;658}659}660661// replacement for getFramePosition (see AbstractDataLine)662@Override663public long getLongFramePosition() {664long pos;665synchronized (lockNative) {666pos = nGetBytePosition(id, isSource, bytePosition);667}668// hack because ALSA sometimes reports wrong framepos669if (pos < 0) {670pos = 0;671}672return (pos / getFormat().getFrameSize());673}674675/*676* write() belongs into SourceDataLine and Clip,677* so define it here and make it accessible by678* declaring the respective interfaces with DirectSDL and DirectClip679*/680public int write(byte[] b, int off, int len) {681flushing = false;682if (len == 0) {683return 0;684}685if (len < 0) {686throw new IllegalArgumentException("illegal len: "+len);687}688if (len % getFormat().getFrameSize() != 0) {689throw new IllegalArgumentException("illegal request to write "690+"non-integral number of frames ("691+len+" bytes, "692+"frameSize = "+getFormat().getFrameSize()+" bytes)");693}694if (off < 0) {695throw new ArrayIndexOutOfBoundsException(off);696}697if ((long)off + (long)len > (long)b.length) {698throw new ArrayIndexOutOfBoundsException(b.length);699}700synchronized(lock) {701if (!isActive() && doIO) {702// this is not exactly correct... would be nicer703// if the native sub system sent a callback when IO really704// starts705setActive(true);706setStarted(true);707}708}709int written = 0;710while (!flushing) {711int thisWritten;712synchronized (lockNative) {713thisWritten = nWrite(id, b, off, len,714softwareConversionSize,715leftGain, rightGain);716if (thisWritten < 0) {717// error in native layer718break;719}720bytePosition += thisWritten;721if (thisWritten > 0) {722drained = false;723}724}725len -= thisWritten;726written += thisWritten;727if (doIO && len > 0) {728off += thisWritten;729synchronized (lock) {730try {731lock.wait(waitTime);732} catch (InterruptedException ie) {}733}734} else {735break;736}737}738if (written > 0 && !doIO) {739stoppedWritten = true;740}741return written;742}743744protected boolean requiresServicing() {745return nRequiresServicing(id, isSource);746}747748// called from event dispatcher for lines that need servicing749@Override750public void checkLine() {751synchronized (lockNative) {752if (monitoring753&& doIO754&& id != 0755&& !flushing756&& !noService) {757nService(id, isSource);758}759}760}761762private void calcVolume() {763if (getFormat() == null) {764return;765}766if (muteControl.getValue()) {767leftGain = 0.0f;768rightGain = 0.0f;769return;770}771float gain = gainControl.getLinearGain();772if (getFormat().getChannels() == 1) {773// trivial case: only use gain774leftGain = gain;775rightGain = gain;776} else {777// need to combine gain and balance778float bal = balanceControl.getValue();779if (bal < 0.0f) {780// left781leftGain = gain;782rightGain = gain * (bal + 1.0f);783} else {784leftGain = gain * (1.0f - bal);785rightGain = gain;786}787}788}789790/////////////////// CONTROLS /////////////////////////////791792protected final class Gain extends FloatControl {793794private float linearGain = 1.0f;795796private Gain() {797798super(FloatControl.Type.MASTER_GAIN,799Toolkit.linearToDB(0.0f),800Toolkit.linearToDB(2.0f),801Math.abs(Toolkit.linearToDB(1.0f)-Toolkit.linearToDB(0.0f))/128.0f,802-1,8030.0f,804"dB", "Minimum", "", "Maximum");805}806807@Override808public void setValue(float newValue) {809// adjust value within range ?? spec says IllegalArgumentException810//newValue = Math.min(newValue, getMaximum());811//newValue = Math.max(newValue, getMinimum());812813float newLinearGain = Toolkit.dBToLinear(newValue);814super.setValue(Toolkit.linearToDB(newLinearGain));815// if no exception, commit to our new gain816linearGain = newLinearGain;817calcVolume();818}819820float getLinearGain() {821return linearGain;822}823} // class Gain824825private final class Mute extends BooleanControl {826827private Mute() {828super(BooleanControl.Type.MUTE, false, "True", "False");829}830831@Override832public void setValue(boolean newValue) {833super.setValue(newValue);834calcVolume();835}836} // class Mute837838private final class Balance extends FloatControl {839840private Balance() {841super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,842"", "Left", "Center", "Right");843}844845@Override846public void setValue(float newValue) {847setValueImpl(newValue);848panControl.setValueImpl(newValue);849calcVolume();850}851852void setValueImpl(float newValue) {853super.setValue(newValue);854}855856} // class Balance857858private final class Pan extends FloatControl {859860private Pan() {861super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,862"", "Left", "Center", "Right");863}864865@Override866public void setValue(float newValue) {867setValueImpl(newValue);868balanceControl.setValueImpl(newValue);869calcVolume();870}871void setValueImpl(float newValue) {872super.setValue(newValue);873}874} // class Pan875} // class DirectDL876877/**878* Private inner class representing a SourceDataLine.879*/880private static final class DirectSDL extends DirectDL881implements SourceDataLine {882883private DirectSDL(DataLine.Info info,884AudioFormat format,885int bufferSize,886DirectAudioDevice mixer) {887super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);888}889}890891/**892* Private inner class representing a TargetDataLine.893*/894private static final class DirectTDL extends DirectDL895implements TargetDataLine {896897private DirectTDL(DataLine.Info info,898AudioFormat format,899int bufferSize,900DirectAudioDevice mixer) {901super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), false);902}903904@Override905public int read(byte[] b, int off, int len) {906flushing = false;907if (len == 0) {908return 0;909}910if (len < 0) {911throw new IllegalArgumentException("illegal len: "+len);912}913if (len % getFormat().getFrameSize() != 0) {914throw new IllegalArgumentException("illegal request to read "915+"non-integral number of frames ("916+len+" bytes, "917+"frameSize = "+getFormat().getFrameSize()+" bytes)");918}919if (off < 0) {920throw new ArrayIndexOutOfBoundsException(off);921}922if ((long)off + (long)len > (long)b.length) {923throw new ArrayIndexOutOfBoundsException(b.length);924}925synchronized(lock) {926if (!isActive() && doIO) {927// this is not exactly correct... would be nicer928// if the native sub system sent a callback when IO really929// starts930setActive(true);931setStarted(true);932}933}934int read = 0;935while (doIO && !flushing) {936int thisRead;937synchronized (lockNative) {938thisRead = nRead(id, b, off, len, softwareConversionSize);939if (thisRead < 0) {940// error in native layer941break;942}943bytePosition += thisRead;944if (thisRead > 0) {945drained = false;946}947}948len -= thisRead;949read += thisRead;950if (len > 0) {951off += thisRead;952synchronized(lock) {953try {954lock.wait(waitTime);955} catch (InterruptedException ie) {}956}957} else {958break;959}960}961if (flushing) {962read = 0;963}964return read;965}966967}968969/**970* Private inner class representing a Clip971* This clip is realized in software only972*/973private static final class DirectClip extends DirectDL974implements Clip, Runnable, AutoClosingClip {975976private volatile Thread thread;977private volatile byte[] audioData = null;978private volatile int frameSize; // size of one frame in bytes979private volatile int m_lengthInFrames;980private volatile int loopCount;981private volatile int clipBytePosition; // index in the audioData array at current playback982private volatile int newFramePosition; // set in setFramePosition()983private volatile int loopStartFrame;984private volatile int loopEndFrame; // the last sample included in the loop985986// auto closing clip support987private boolean autoclosing = false;988989private DirectClip(DataLine.Info info,990AudioFormat format,991int bufferSize,992DirectAudioDevice mixer) {993super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);994}995996// CLIP METHODS997998@Override999public void open(AudioFormat format, byte[] data, int offset, int bufferSize)1000throws LineUnavailableException {10011002// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions1003Toolkit.isFullySpecifiedAudioFormat(format);1004Toolkit.validateBuffer(format.getFrameSize(), bufferSize);10051006byte[] newData = new byte[bufferSize];1007System.arraycopy(data, offset, newData, 0, bufferSize);1008open(format, newData, bufferSize / format.getFrameSize());1009}10101011// this method does not copy the data array1012private void open(AudioFormat format, byte[] data, int frameLength)1013throws LineUnavailableException {10141015// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions1016Toolkit.isFullySpecifiedAudioFormat(format);10171018synchronized (mixer) {1019if (isOpen()) {1020throw new IllegalStateException("Clip is already open with format " + getFormat() +1021" and frame lengh of " + getFrameLength());1022} else {1023// if the line is not currently open, try to open it with this format and buffer size1024this.audioData = data;1025this.frameSize = format.getFrameSize();1026this.m_lengthInFrames = frameLength;1027// initialize loop selection with full range1028bytePosition = 0;1029clipBytePosition = 0;1030newFramePosition = -1; // means: do not set to a new readFramePos1031loopStartFrame = 0;1032loopEndFrame = frameLength - 1;1033loopCount = 0; // means: play the clip irrespective of loop points from beginning to end10341035try {1036// use DirectDL's open method to open it1037open(format, (int) Toolkit.millis2bytes(format, CLIP_BUFFER_TIME)); // one second buffer1038} catch (LineUnavailableException lue) {1039audioData = null;1040throw lue;1041} catch (IllegalArgumentException iae) {1042audioData = null;1043throw iae;1044}10451046// if we got this far, we can instanciate the thread1047int priority = Thread.NORM_PRIORITY1048+ (Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 3;1049thread = JSSecurityManager.createThread(this,1050"Direct Clip", // name1051true, // daemon1052priority, // priority1053false); // doStart1054// cannot start in createThread, because the thread1055// uses the "thread" variable as indicator if it should1056// continue to run1057thread.start();1058}1059}1060if (isAutoClosing()) {1061getEventDispatcher().autoClosingClipOpened(this);1062}1063}10641065@Override1066public void open(AudioInputStream stream) throws LineUnavailableException, IOException {10671068// $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions1069Toolkit.isFullySpecifiedAudioFormat(stream.getFormat());10701071synchronized (mixer) {1072byte[] streamData = null;10731074if (isOpen()) {1075throw new IllegalStateException("Clip is already open with format " + getFormat() +1076" and frame lengh of " + getFrameLength());1077}1078int lengthInFrames = (int)stream.getFrameLength();1079int bytesRead = 0;1080int frameSize = stream.getFormat().getFrameSize();1081if (lengthInFrames != AudioSystem.NOT_SPECIFIED) {1082// read the data from the stream into an array in one fell swoop.1083int arraysize = lengthInFrames * frameSize;1084if (arraysize < 0) {1085throw new IllegalArgumentException("Audio data < 0");1086}1087try {1088streamData = new byte[arraysize];1089} catch (OutOfMemoryError e) {1090throw new IOException("Audio data is too big");1091}1092int bytesRemaining = arraysize;1093int thisRead = 0;1094while (bytesRemaining > 0 && thisRead >= 0) {1095thisRead = stream.read(streamData, bytesRead, bytesRemaining);1096if (thisRead > 0) {1097bytesRead += thisRead;1098bytesRemaining -= thisRead;1099}1100else if (thisRead == 0) {1101Thread.yield();1102}1103}1104} else {1105// read data from the stream until we reach the end of the stream1106// we use a slightly modified version of ByteArrayOutputStream1107// to get direct access to the byte array (we don't want a new array1108// to be allocated)1109int maxReadLimit = Math.max(16384, frameSize);1110DirectBAOS dbaos = new DirectBAOS();1111byte[] tmp;1112try {1113tmp = new byte[maxReadLimit];1114} catch (OutOfMemoryError e) {1115throw new IOException("Audio data is too big");1116}1117int thisRead = 0;1118while (thisRead >= 0) {1119thisRead = stream.read(tmp, 0, tmp.length);1120if (thisRead > 0) {1121dbaos.write(tmp, 0, thisRead);1122bytesRead += thisRead;1123}1124else if (thisRead == 0) {1125Thread.yield();1126}1127} // while1128streamData = dbaos.getInternalBuffer();1129}1130lengthInFrames = bytesRead / frameSize;11311132// now try to open the device1133open(stream.getFormat(), streamData, lengthInFrames);1134} // synchronized1135}11361137@Override1138public int getFrameLength() {1139return m_lengthInFrames;1140}11411142@Override1143public long getMicrosecondLength() {1144return Toolkit.frames2micros(getFormat(), getFrameLength());1145}11461147@Override1148public void setFramePosition(int frames) {1149if (frames < 0) {1150frames = 0;1151}1152else if (frames >= getFrameLength()) {1153frames = getFrameLength();1154}1155if (doIO) {1156newFramePosition = frames;1157} else {1158clipBytePosition = frames * frameSize;1159newFramePosition = -1;1160}1161// fix for failing test0501162// $$fb although getFramePosition should return the number of rendered1163// frames, it is intuitive that setFramePosition will modify that1164// value.1165bytePosition = frames * frameSize;11661167// cease currently playing buffer1168flush();11691170// set new native position (if necessary)1171// this must come after the flush!1172synchronized (lockNative) {1173nSetBytePosition(id, isSource, frames * frameSize);1174}1175}11761177// replacement for getFramePosition (see AbstractDataLine)1178@Override1179public long getLongFramePosition() {1180/* $$fb1181* this would be intuitive, but the definition of getFramePosition1182* is the number of frames rendered since opening the device...1183* That also means that setFramePosition() means something very1184* different from getFramePosition() for Clip.1185*/1186// take into account the case that a new position was set...1187//if (!doIO && newFramePosition >= 0) {1188//return newFramePosition;1189//}1190return super.getLongFramePosition();1191}11921193@Override1194public synchronized void setMicrosecondPosition(long microseconds) {1195long frames = Toolkit.micros2frames(getFormat(), microseconds);1196setFramePosition((int) frames);1197}11981199@Override1200public void setLoopPoints(int start, int end) {1201if (start < 0 || start >= getFrameLength()) {1202throw new IllegalArgumentException("illegal value for start: "+start);1203}1204if (end >= getFrameLength()) {1205throw new IllegalArgumentException("illegal value for end: "+end);1206}12071208if (end == -1) {1209end = getFrameLength() - 1;1210if (end < 0) {1211end = 0;1212}1213}12141215// if the end position is less than the start position, throw IllegalArgumentException1216if (end < start) {1217throw new IllegalArgumentException("End position " + end + " preceeds start position " + start);1218}12191220// slight race condition with the run() method, but not a big problem1221loopStartFrame = start;1222loopEndFrame = end;1223}12241225@Override1226public void loop(int count) {1227// note: when count reaches 0, it means that the entire clip1228// will be played, i.e. it will play past the loop end point1229loopCount = count;1230start();1231}12321233@Override1234void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {1235// only if audioData wasn't set in a calling open(format, byte[], frameSize)1236// this call is allowed.1237if (audioData == null) {1238throw new IllegalArgumentException("illegal call to open() in interface Clip");1239}1240super.implOpen(format, bufferSize);1241}12421243@Override1244void implClose() {1245// dispose of thread1246Thread oldThread = thread;1247thread = null;1248doIO = false;1249if (oldThread != null) {1250// wake up the thread if it's in wait()1251synchronized(lock) {1252lock.notifyAll();1253}1254// wait for the thread to terminate itself,1255// but max. 2 seconds. Must not be synchronized!1256try {1257oldThread.join(2000);1258} catch (InterruptedException ie) {}1259}1260super.implClose();1261// remove audioData reference and hand it over to gc1262audioData = null;1263newFramePosition = -1;12641265// remove this instance from the list of auto closing clips1266getEventDispatcher().autoClosingClipClosed(this);1267}12681269@Override1270void implStart() {1271super.implStart();1272}12731274@Override1275void implStop() {1276super.implStop();1277// reset loopCount field so that playback will be normal with1278// next call to start()1279loopCount = 0;1280}12811282// main playback loop1283@Override1284public void run() {1285Thread curThread = Thread.currentThread();1286while (thread == curThread) {1287// doIO is volatile, but we could check it, then get1288// pre-empted while another thread changes doIO and notifies,1289// before we wait (so we sleep in wait forever).1290synchronized(lock) {1291while (!doIO && thread == curThread) {1292try {1293lock.wait();1294} catch (InterruptedException ignored) {1295}1296}1297}1298while (doIO && thread == curThread) {1299if (newFramePosition >= 0) {1300clipBytePosition = newFramePosition * frameSize;1301newFramePosition = -1;1302}1303int endFrame = getFrameLength() - 1;1304if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {1305endFrame = loopEndFrame;1306}1307long framePos = (clipBytePosition / frameSize);1308int toWriteFrames = (int) (endFrame - framePos + 1);1309int toWriteBytes = toWriteFrames * frameSize;1310if (toWriteBytes > getBufferSize()) {1311toWriteBytes = Toolkit.align(getBufferSize(), frameSize);1312}1313int written = write(audioData, clipBytePosition, toWriteBytes); // increases bytePosition1314clipBytePosition += written;1315// make sure nobody called setFramePosition, or stop() during the write() call1316if (doIO && newFramePosition < 0 && written >= 0) {1317framePos = clipBytePosition / frameSize;1318// since endFrame is the last frame to be played,1319// framePos is after endFrame when all frames, including framePos,1320// are played.1321if (framePos > endFrame) {1322// at end of playback. If looping is on, loop back to the beginning.1323if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {1324if (loopCount != LOOP_CONTINUOUSLY) {1325loopCount--;1326}1327newFramePosition = loopStartFrame;1328} else {1329// no looping, stop playback1330drain();1331stop();1332}1333}1334}1335}1336}1337}13381339// AUTO CLOSING CLIP SUPPORT13401341/* $$mp 2003-10-011342The following two methods are common between this class and1343MixerClip. They should be moved to a base class, together1344with the instance variable 'autoclosing'. */13451346@Override1347public boolean isAutoClosing() {1348return autoclosing;1349}13501351@Override1352public void setAutoClosing(boolean value) {1353if (value != autoclosing) {1354if (isOpen()) {1355if (value) {1356getEventDispatcher().autoClosingClipOpened(this);1357} else {1358getEventDispatcher().autoClosingClipClosed(this);1359}1360}1361autoclosing = value;1362}1363}13641365@Override1366protected boolean requiresServicing() {1367// no need for servicing for Clips1368return false;1369}13701371} // DirectClip13721373/*1374* private inner class representing a ByteArrayOutputStream1375* which allows retrieval of the internal array1376*/1377private static class DirectBAOS extends ByteArrayOutputStream {1378DirectBAOS() {1379super();1380}13811382public byte[] getInternalBuffer() {1383return buf;1384}13851386} // class DirectBAOS13871388@SuppressWarnings("rawtypes")1389private static native void nGetFormats(int mixerIndex, int deviceID,1390boolean isSource, Vector formats);13911392private static native long nOpen(int mixerIndex, int deviceID, boolean isSource,1393int encoding,1394float sampleRate,1395int sampleSizeInBits,1396int frameSize,1397int channels,1398boolean signed,1399boolean bigEndian,1400int bufferSize) throws LineUnavailableException;1401private static native void nStart(long id, boolean isSource);1402private static native void nStop(long id, boolean isSource);1403private static native void nClose(long id, boolean isSource);1404private static native int nWrite(long id, byte[] b, int off, int len, int conversionSize,1405float volLeft, float volRight);1406private static native int nRead(long id, byte[] b, int off, int len, int conversionSize);1407private static native int nGetBufferSize(long id, boolean isSource);1408private static native boolean nIsStillDraining(long id, boolean isSource);1409private static native void nFlush(long id, boolean isSource);1410private static native int nAvailable(long id, boolean isSource);1411// javaPos is number of bytes read/written in Java layer1412private static native long nGetBytePosition(long id, boolean isSource, long javaPos);1413private static native void nSetBytePosition(long id, boolean isSource, long pos);14141415// returns if the native implementation needs regular calls to nService()1416private static native boolean nRequiresServicing(long id, boolean isSource);1417// called in irregular intervals1418private static native void nService(long id, boolean isSource);1419}142014211422