1/*
2 * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.media.sound;
27
28import java.io.ByteArrayOutputStream;
29import java.io.IOException;
30import java.util.Vector;
31
32import javax.sound.sampled.AudioFormat;
33import javax.sound.sampled.AudioInputStream;
34import javax.sound.sampled.AudioSystem;
35import javax.sound.sampled.BooleanControl;
36import javax.sound.sampled.Clip;
37import javax.sound.sampled.Control;
38import javax.sound.sampled.DataLine;
39import javax.sound.sampled.FloatControl;
40import javax.sound.sampled.Line;
41import javax.sound.sampled.LineUnavailableException;
42import javax.sound.sampled.SourceDataLine;
43import javax.sound.sampled.TargetDataLine;
44
45// IDEA:
46// Use java.util.concurrent.Semaphore,
47// java.util.concurrent.locks.ReentrantLock and other new classes/methods
48// to improve this class's thread safety.
49
50/**
51 * A Mixer which provides direct access to audio devices.
52 *
53 * @author Florian Bomers
54 */
55final class DirectAudioDevice extends AbstractMixer {
56
57    private static final int CLIP_BUFFER_TIME = 1000; // in milliseconds
58
59    private static final int DEFAULT_LINE_BUFFER_TIME = 500; // in milliseconds
60
61    DirectAudioDevice(DirectAudioDeviceProvider.DirectAudioDeviceInfo portMixerInfo) {
62        // pass in Line.Info, mixer, controls
63        super(portMixerInfo,              // Mixer.Info
64              null,                       // Control[]
65              null,                       // Line.Info[] sourceLineInfo
66              null);                      // Line.Info[] targetLineInfo
67
68        if (Printer.trace) Printer.trace(">> DirectAudioDevice: constructor");
69
70        // source lines
71        DirectDLI srcLineInfo = createDataLineInfo(true);
72        if (srcLineInfo != null) {
73            sourceLineInfo = new Line.Info[2];
74            // SourcedataLine
75            sourceLineInfo[0] = srcLineInfo;
76            // Clip
77            sourceLineInfo[1] = new DirectDLI(Clip.class, srcLineInfo.getFormats(),
78                                              srcLineInfo.getHardwareFormats(),
79                                              32, // arbitrary minimum buffer size
80                                              AudioSystem.NOT_SPECIFIED);
81        } else {
82            sourceLineInfo = new Line.Info[0];
83        }
84
85        // TargetDataLine
86        DataLine.Info dstLineInfo = createDataLineInfo(false);
87        if (dstLineInfo != null) {
88            targetLineInfo = new Line.Info[1];
89            targetLineInfo[0] = dstLineInfo;
90        } else {
91            targetLineInfo = new Line.Info[0];
92        }
93        if (Printer.trace) Printer.trace("<< DirectAudioDevice: constructor completed");
94    }
95
96    private DirectDLI createDataLineInfo(boolean isSource) {
97        Vector<AudioFormat> formats = new Vector<>();
98        AudioFormat[] hardwareFormatArray = null;
99        AudioFormat[] formatArray = null;
100
101        synchronized(formats) {
102            nGetFormats(getMixerIndex(), getDeviceID(),
103                        isSource /* true:SourceDataLine/Clip, false:TargetDataLine */,
104                        formats);
105            if (formats.size() > 0) {
106                int size = formats.size();
107                int formatArraySize = size;
108                hardwareFormatArray = new AudioFormat[size];
109                for (int i = 0; i < size; i++) {
110                    AudioFormat format = formats.elementAt(i);
111                    hardwareFormatArray[i] = format;
112                    int bits = format.getSampleSizeInBits();
113                    boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
114                    boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);
115                    if ((isSigned || isUnsigned)) {
116                        // will insert a magically converted format here
117                        formatArraySize++;
118                    }
119                }
120                formatArray = new AudioFormat[formatArraySize];
121                int formatArrayIndex = 0;
122                for (int i = 0; i < size; i++) {
123                    AudioFormat format = hardwareFormatArray[i];
124                    formatArray[formatArrayIndex++] = format;
125                    int bits = format.getSampleSizeInBits();
126                    boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
127                    boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);
128                    // add convenience formats (automatic conversion)
129                    if (bits == 8) {
130                        // add the other signed'ness for 8-bit
131                        if (isSigned) {
132                            formatArray[formatArrayIndex++] =
133                                new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED,
134                                    format.getSampleRate(), bits, format.getChannels(),
135                                    format.getFrameSize(), format.getSampleRate(),
136                                    format.isBigEndian());
137                        }
138                        else if (isUnsigned) {
139                            formatArray[formatArrayIndex++] =
140                                new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
141                                    format.getSampleRate(), bits, format.getChannels(),
142                                    format.getFrameSize(), format.getSampleRate(),
143                                    format.isBigEndian());
144                        }
145                    } else if (bits > 8 && (isSigned || isUnsigned)) {
146                        // add the other endian'ness for more than 8-bit
147                        formatArray[formatArrayIndex++] =
148                            new AudioFormat(format.getEncoding(),
149                                              format.getSampleRate(), bits,
150                                              format.getChannels(),
151                                              format.getFrameSize(),
152                                              format.getSampleRate(),
153                                              !format.isBigEndian());
154                    }
155                    //System.out.println("Adding "+v.get(v.size()-1));
156                }
157            }
158        }
159        // todo: find out more about the buffer size ?
160        if (formatArray != null) {
161            return new DirectDLI(isSource?SourceDataLine.class:TargetDataLine.class,
162                                 formatArray, hardwareFormatArray,
163                                 32, // arbitrary minimum buffer size
164                                 AudioSystem.NOT_SPECIFIED);
165        }
166        return null;
167    }
168
169    // ABSTRACT MIXER: ABSTRACT METHOD IMPLEMENTATIONS
170
171    @Override
172    public Line getLine(Line.Info info) throws LineUnavailableException {
173        Line.Info fullInfo = getLineInfo(info);
174        if (fullInfo == null) {
175            throw new IllegalArgumentException("Line unsupported: " + info);
176        }
177        if (fullInfo instanceof DataLine.Info) {
178
179            DataLine.Info dataLineInfo = (DataLine.Info)fullInfo;
180            AudioFormat lineFormat;
181            int lineBufferSize = AudioSystem.NOT_SPECIFIED;
182
183            // if a format is specified by the info class passed in, use it.
184            // otherwise use a format from fullInfo.
185
186            AudioFormat[] supportedFormats = null;
187
188            if (info instanceof DataLine.Info) {
189                supportedFormats = ((DataLine.Info)info).getFormats();
190                lineBufferSize = ((DataLine.Info)info).getMaxBufferSize();
191            }
192
193            if ((supportedFormats == null) || (supportedFormats.length == 0)) {
194                // use the default format
195                lineFormat = null;
196            } else {
197                // use the last format specified in the line.info object passed
198                // in by the app
199                lineFormat = supportedFormats[supportedFormats.length-1];
200
201                // if something is not specified, use default format
202                if (!Toolkit.isFullySpecifiedPCMFormat(lineFormat)) {
203                    lineFormat = null;
204                }
205            }
206
207            if (dataLineInfo.getLineClass().isAssignableFrom(DirectSDL.class)) {
208                return new DirectSDL(dataLineInfo, lineFormat, lineBufferSize, this);
209            }
210            if (dataLineInfo.getLineClass().isAssignableFrom(DirectClip.class)) {
211                return new DirectClip(dataLineInfo, lineFormat, lineBufferSize, this);
212            }
213            if (dataLineInfo.getLineClass().isAssignableFrom(DirectTDL.class)) {
214                return new DirectTDL(dataLineInfo, lineFormat, lineBufferSize, this);
215            }
216        }
217        throw new IllegalArgumentException("Line unsupported: " + info);
218    }
219
220    @Override
221    public int getMaxLines(Line.Info info) {
222        Line.Info fullInfo = getLineInfo(info);
223
224        // if it's not supported at all, return 0.
225        if (fullInfo == null) {
226            return 0;
227        }
228
229        if (fullInfo instanceof DataLine.Info) {
230            // DirectAudioDevices should mix !
231            return getMaxSimulLines();
232        }
233
234        return 0;
235    }
236
237    @Override
238    protected void implOpen() throws LineUnavailableException {
239        if (Printer.trace) Printer.trace("DirectAudioDevice: implOpen - void method");
240    }
241
242    @Override
243    protected void implClose() {
244        if (Printer.trace) Printer.trace("DirectAudioDevice: implClose - void method");
245    }
246
247    @Override
248    protected void implStart() {
249        if (Printer.trace) Printer.trace("DirectAudioDevice: implStart - void method");
250    }
251
252    @Override
253    protected void implStop() {
254        if (Printer.trace) Printer.trace("DirectAudioDevice: implStop - void method");
255    }
256
257    int getMixerIndex() {
258        return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getIndex();
259    }
260
261    int getDeviceID() {
262        return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getDeviceID();
263    }
264
265    int getMaxSimulLines() {
266        return ((DirectAudioDeviceProvider.DirectAudioDeviceInfo) getMixerInfo()).getMaxSimulLines();
267    }
268
269    private static void addFormat(Vector<AudioFormat> v, int bits, int frameSizeInBytes, int channels, float sampleRate,
270                                  int encoding, boolean signed, boolean bigEndian) {
271        AudioFormat.Encoding enc = null;
272        switch (encoding) {
273        case PCM:
274            enc = signed?AudioFormat.Encoding.PCM_SIGNED:AudioFormat.Encoding.PCM_UNSIGNED;
275            break;
276        case ULAW:
277            enc = AudioFormat.Encoding.ULAW;
278            if (bits != 8) {
279                if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ULAW, but bitsPerSample="+bits);
280                bits = 8; frameSizeInBytes = channels;
281            }
282            break;
283        case ALAW:
284            enc = AudioFormat.Encoding.ALAW;
285            if (bits != 8) {
286                if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with ALAW, but bitsPerSample="+bits);
287                bits = 8; frameSizeInBytes = channels;
288            }
289            break;
290        }
291        if (enc==null) {
292            if (Printer.err) Printer.err("DirectAudioDevice.addFormat called with unknown encoding: "+encoding);
293            return;
294        }
295        if (frameSizeInBytes <= 0) {
296            if (channels > 0) {
297                frameSizeInBytes = ((bits + 7) / 8) * channels;
298            } else {
299                frameSizeInBytes = AudioSystem.NOT_SPECIFIED;
300            }
301        }
302        v.add(new AudioFormat(enc, sampleRate, bits, channels, frameSizeInBytes, sampleRate, bigEndian));
303    }
304
305    protected static AudioFormat getSignOrEndianChangedFormat(AudioFormat format) {
306        boolean isSigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED);
307        boolean isUnsigned = format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED);
308        if (format.getSampleSizeInBits() > 8 && isSigned) {
309            // if this is PCM_SIGNED and 16-bit or higher, then try with endian-ness magic
310            return new AudioFormat(format.getEncoding(),
311                                   format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),
312                                   format.getFrameSize(), format.getFrameRate(), !format.isBigEndian());
313        }
314        else if (format.getSampleSizeInBits() == 8 && (isSigned || isUnsigned)) {
315            // if this is PCM and 8-bit, then try with signed-ness magic
316            return new AudioFormat(isSigned?AudioFormat.Encoding.PCM_UNSIGNED:AudioFormat.Encoding.PCM_SIGNED,
317                                   format.getSampleRate(), format.getSampleSizeInBits(), format.getChannels(),
318                                   format.getFrameSize(), format.getFrameRate(), format.isBigEndian());
319        }
320        return null;
321    }
322
323    /**
324     * Private inner class for the DataLine.Info objects
325     * adds a little magic for the isFormatSupported so
326     * that the automagic conversion of endianness and sign
327     * does not show up in the formats array.
328     * I.e. the formats array contains only the formats
329     * that are really supported by the hardware,
330     * but isFormatSupported() also returns true
331     * for formats with wrong endianness.
332     */
333    private static final class DirectDLI extends DataLine.Info {
334        final AudioFormat[] hardwareFormats;
335
336        private DirectDLI(Class<?> clazz, AudioFormat[] formatArray,
337                          AudioFormat[] hardwareFormatArray,
338                          int minBuffer, int maxBuffer) {
339            super(clazz, formatArray, minBuffer, maxBuffer);
340            this.hardwareFormats = hardwareFormatArray;
341        }
342
343        public boolean isFormatSupportedInHardware(AudioFormat format) {
344            if (format == null) return false;
345            for (int i = 0; i < hardwareFormats.length; i++) {
346                if (format.matches(hardwareFormats[i])) {
347                    return true;
348                }
349            }
350            return false;
351        }
352
353        /*public boolean isFormatSupported(AudioFormat format) {
354         *   return isFormatSupportedInHardware(format)
355         *      || isFormatSupportedInHardware(getSignOrEndianChangedFormat(format));
356         *}
357         */
358
359         private AudioFormat[] getHardwareFormats() {
360             return hardwareFormats;
361         }
362    }
363
364    /**
365     * Private inner class as base class for direct lines.
366     */
367    private static class DirectDL extends AbstractDataLine implements EventDispatcher.LineMonitor {
368        protected final int mixerIndex;
369        protected final int deviceID;
370        protected long id;
371        protected int waitTime;
372        protected volatile boolean flushing = false;
373        protected final boolean isSource;         // true for SourceDataLine, false for TargetDataLine
374        protected volatile long bytePosition;
375        protected volatile boolean doIO = false;     // true in between start() and stop() calls
376        protected volatile boolean stoppedWritten = false; // true if a write occurred in stopped state
377        protected volatile boolean drained = false; // set to true when drain function returns, set to false in write()
378        protected boolean monitoring = false;
379
380        // if native needs to manually swap samples/convert sign, this
381        // is set to the framesize
382        protected int softwareConversionSize = 0;
383        protected AudioFormat hardwareFormat;
384
385        private final Gain gainControl = new Gain();
386        private final Mute muteControl = new Mute();
387        private final Balance balanceControl = new Balance();
388        private final Pan panControl = new Pan();
389        private float leftGain, rightGain;
390        protected volatile boolean noService = false; // do not run the nService method
391
392        // Guards all native calls.
393        protected final Object lockNative = new Object();
394
395        protected DirectDL(DataLine.Info info,
396                           DirectAudioDevice mixer,
397                           AudioFormat format,
398                           int bufferSize,
399                           int mixerIndex,
400                           int deviceID,
401                           boolean isSource) {
402            super(info, mixer, null, format, bufferSize);
403            if (Printer.trace) Printer.trace("DirectDL CONSTRUCTOR: info: " + info);
404            this.mixerIndex = mixerIndex;
405            this.deviceID = deviceID;
406            this.waitTime = 10; // 10 milliseconds default wait time
407            this.isSource = isSource;
408
409        }
410
411        @Override
412        void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {
413            if (Printer.trace) Printer.trace(">> DirectDL: implOpen("+format+", "+bufferSize+" bytes)");
414
415            // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
416            Toolkit.isFullySpecifiedAudioFormat(format);
417
418            // check for record permission
419            if (!isSource) {
420                JSSecurityManager.checkRecordPermission();
421            }
422            int encoding = PCM;
423            if (format.getEncoding().equals(AudioFormat.Encoding.ULAW)) {
424                encoding = ULAW;
425            }
426            else if (format.getEncoding().equals(AudioFormat.Encoding.ALAW)) {
427                encoding = ALAW;
428            }
429
430            if (bufferSize <= AudioSystem.NOT_SPECIFIED) {
431                bufferSize = (int) Toolkit.millis2bytes(format, DEFAULT_LINE_BUFFER_TIME);
432            }
433
434            DirectDLI ddli = null;
435            if (info instanceof DirectDLI) {
436                ddli = (DirectDLI) info;
437            }
438
439            /* set up controls */
440            if (isSource) {
441                if (!format.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED)
442                    && !format.getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)) {
443                    // no controls for non-PCM formats */
444                    controls = new Control[0];
445                }
446                else if (format.getChannels() > 2
447                         || format.getSampleSizeInBits() > 16) {
448                    // no support for more than 2 channels or more than 16 bits
449                    controls = new Control[0];
450                } else {
451                    if (format.getChannels() == 1) {
452                        controls = new Control[2];
453                    } else {
454                        controls = new Control[4];
455                        controls[2] = balanceControl;
456                        /* to keep compatibility with apps that rely on
457                         * MixerSourceLine's PanControl
458                         */
459                        controls[3] = panControl;
460                    }
461                    controls[0] = gainControl;
462                    controls[1] = muteControl;
463                }
464            }
465            if (Printer.debug) Printer.debug("DirectAudioDevice: got "+controls.length+" controls.");
466
467            hardwareFormat = format;
468
469            /* some magic to account for not-supported endianness or signed-ness */
470            softwareConversionSize = 0;
471            if (ddli != null && !ddli.isFormatSupportedInHardware(format)) {
472                AudioFormat newFormat = getSignOrEndianChangedFormat(format);
473                if (ddli.isFormatSupportedInHardware(newFormat)) {
474                    // apparently, the new format can be used.
475                    hardwareFormat = newFormat;
476                    // So do endian/sign conversion in software
477                    softwareConversionSize = format.getFrameSize() / format.getChannels();
478                    if (Printer.debug) {
479                        Printer.debug("DirectAudioDevice: softwareConversionSize "
480                                      +softwareConversionSize+":");
481                        Printer.debug("  from "+format);
482                        Printer.debug("  to   "+newFormat);
483                    }
484                }
485            }
486
487            // align buffer to full frames
488            bufferSize = ( bufferSize / format.getFrameSize()) * format.getFrameSize();
489
490            id = nOpen(mixerIndex, deviceID, isSource,
491                    encoding,
492                    hardwareFormat.getSampleRate(),
493                    hardwareFormat.getSampleSizeInBits(),
494                    hardwareFormat.getFrameSize(),
495                    hardwareFormat.getChannels(),
496                    hardwareFormat.getEncoding().equals(
497                        AudioFormat.Encoding.PCM_SIGNED),
498                    hardwareFormat.isBigEndian(),
499                    bufferSize);
500
501            if (id == 0) {
502                // TODO: nicer error messages...
503                throw new LineUnavailableException(
504                        "line with format "+format+" not supported.");
505            }
506
507            this.bufferSize = nGetBufferSize(id, isSource);
508            if (this.bufferSize < 1) {
509                // this is an error!
510                this.bufferSize = bufferSize;
511            }
512            this.format = format;
513            // wait time = 1/4 of buffer time
514            waitTime = (int) Toolkit.bytes2millis(format, this.bufferSize) / 4;
515            if (waitTime < 10) {
516                waitTime = 1;
517            }
518            else if (waitTime > 1000) {
519                // we have seen large buffer sizes!
520                // never wait for more than a second
521                waitTime = 1000;
522            }
523            bytePosition = 0;
524            stoppedWritten = false;
525            doIO = false;
526            calcVolume();
527
528            if (Printer.trace) Printer.trace("<< DirectDL: implOpen() succeeded");
529        }
530
531        @Override
532        void implStart() {
533            if (Printer.trace) Printer.trace(" >> DirectDL: implStart()");
534
535            // check for record permission
536            if (!isSource) {
537                JSSecurityManager.checkRecordPermission();
538            }
539
540            synchronized (lockNative)
541            {
542                nStart(id, isSource);
543            }
544            // check for monitoring/servicing
545            monitoring = requiresServicing();
546            if (monitoring) {
547                getEventDispatcher().addLineMonitor(this);
548            }
549
550            doIO = true;
551
552            // need to set Active and Started
553            // note: the current API always requires that
554            //       Started and Active are set at the same time...
555            if (isSource && stoppedWritten) {
556                setStarted(true);
557                setActive(true);
558            }
559
560            if (Printer.trace) Printer.trace("<< DirectDL: implStart() succeeded");
561        }
562
563        @Override
564        void implStop() {
565            if (Printer.trace) Printer.trace(">> DirectDL: implStop()");
566
567            // check for record permission
568            if (!isSource) {
569                JSSecurityManager.checkRecordPermission();
570            }
571
572            if (monitoring) {
573                getEventDispatcher().removeLineMonitor(this);
574                monitoring = false;
575            }
576            synchronized (lockNative) {
577                nStop(id, isSource);
578            }
579            // wake up any waiting threads
580            synchronized(lock) {
581                // need to set doIO to false before notifying the
582                // read/write thread, that's why isStartedRunning()
583                // cannot be used
584                doIO = false;
585                lock.notifyAll();
586            }
587            setActive(false);
588            setStarted(false);
589            stoppedWritten = false;
590
591            if (Printer.trace) Printer.trace(" << DirectDL: implStop() succeeded");
592        }
593
594        @Override
595        void implClose() {
596            if (Printer.trace) Printer.trace(">> DirectDL: implClose()");
597
598            // check for record permission
599            if (!isSource) {
600                JSSecurityManager.checkRecordPermission();
601            }
602
603            // be sure to remove this monitor
604            if (monitoring) {
605                getEventDispatcher().removeLineMonitor(this);
606                monitoring = false;
607            }
608
609            doIO = false;
610            long oldID = id;
611            id = 0;
612            synchronized (lockNative) {
613                nClose(oldID, isSource);
614            }
615            bytePosition = 0;
616            softwareConversionSize = 0;
617            if (Printer.trace) Printer.trace("<< DirectDL: implClose() succeeded");
618        }
619
620        @Override
621        public int available() {
622            if (id == 0) {
623                return 0;
624            }
625            int a;
626            synchronized (lockNative) {
627                a = nAvailable(id, isSource);
628            }
629            return a;
630        }
631
632        @Override
633        public void drain() {
634            noService = true;
635            // additional safeguard against draining forever
636            // this occurred on Solaris 8 x86, probably due to a bug
637            // in the audio driver
638            int counter = 0;
639            long startPos = getLongFramePosition();
640            boolean posChanged = false;
641            while (!drained) {
642                synchronized (lockNative) {
643                    if ((id == 0) || (!doIO) || !nIsStillDraining(id, isSource))
644                        break;
645                }
646                // check every now and then for a new position
647                if ((counter % 5) == 4) {
648                    long thisFramePos = getLongFramePosition();
649                    posChanged = posChanged | (thisFramePos != startPos);
650                    if ((counter % 50) > 45) {
651                        // when some time elapsed, check that the frame position
652                        // really changed
653                        if (!posChanged) {
654                            if (Printer.err) Printer.err("Native reports isDraining, but frame position does not increase!");
655                            break;
656                        }
657                        posChanged = false;
658                        startPos = thisFramePos;
659                    }
660                }
661                counter++;
662                synchronized(lock) {
663                    try {
664                        lock.wait(10);
665                    } catch (InterruptedException ie) {}
666                }
667            }
668
669            if (doIO && id != 0) {
670                drained = true;
671            }
672            noService = false;
673        }
674
675        @Override
676        public void flush() {
677            if (id != 0) {
678                // first stop ongoing read/write method
679                flushing = true;
680                synchronized(lock) {
681                    lock.notifyAll();
682                }
683                synchronized (lockNative) {
684                    if (id != 0) {
685                        // then flush native buffers
686                        nFlush(id, isSource);
687                    }
688                }
689                drained = true;
690            }
691        }
692
693        // replacement for getFramePosition (see AbstractDataLine)
694        @Override
695        public long getLongFramePosition() {
696            long pos;
697            synchronized (lockNative) {
698                pos = nGetBytePosition(id, isSource, bytePosition);
699            }
700            // hack because ALSA sometimes reports wrong framepos
701            if (pos < 0) {
702                if (Printer.debug) Printer.debug("DirectLine.getLongFramePosition: Native reported pos="
703                                                 +pos+"! is changed to 0. byteposition="+bytePosition);
704                pos = 0;
705            }
706            return (pos / getFormat().getFrameSize());
707        }
708
709        /*
710         * write() belongs into SourceDataLine and Clip,
711         * so define it here and make it accessible by
712         * declaring the respective interfaces with DirectSDL and DirectClip
713         */
714        public int write(byte[] b, int off, int len) {
715            flushing = false;
716            if (len == 0) {
717                return 0;
718            }
719            if (len < 0) {
720                throw new IllegalArgumentException("illegal len: "+len);
721            }
722            if (len % getFormat().getFrameSize() != 0) {
723                throw new IllegalArgumentException("illegal request to write "
724                                                   +"non-integral number of frames ("
725                                                   +len+" bytes, "
726                                                   +"frameSize = "+getFormat().getFrameSize()+" bytes)");
727            }
728            if (off < 0) {
729                throw new ArrayIndexOutOfBoundsException(off);
730            }
731            if ((long)off + (long)len > (long)b.length) {
732                throw new ArrayIndexOutOfBoundsException(b.length);
733            }
734
735            if (!isActive() && doIO) {
736                // this is not exactly correct... would be nicer
737                // if the native sub system sent a callback when IO really starts
738                setActive(true);
739                setStarted(true);
740            }
741            int written = 0;
742            while (!flushing) {
743                int thisWritten;
744                synchronized (lockNative) {
745                    thisWritten = nWrite(id, b, off, len,
746                            softwareConversionSize,
747                            leftGain, rightGain);
748                    if (thisWritten < 0) {
749                        // error in native layer
750                        break;
751                    }
752                    bytePosition += thisWritten;
753                    if (thisWritten > 0) {
754                        drained = false;
755                    }
756                }
757                len -= thisWritten;
758                written += thisWritten;
759                if (doIO && len > 0) {
760                    off += thisWritten;
761                    synchronized (lock) {
762                        try {
763                            lock.wait(waitTime);
764                        } catch (InterruptedException ie) {}
765                    }
766                } else {
767                    break;
768                }
769            }
770            if (written > 0 && !doIO) {
771                stoppedWritten = true;
772            }
773            return written;
774        }
775
776        protected boolean requiresServicing() {
777            return nRequiresServicing(id, isSource);
778        }
779
780        // called from event dispatcher for lines that need servicing
781        @Override
782        public void checkLine() {
783            synchronized (lockNative) {
784                if (monitoring
785                        && doIO
786                        && id != 0
787                        && !flushing
788                        && !noService) {
789                    nService(id, isSource);
790                }
791            }
792        }
793
794        private void calcVolume() {
795            if (getFormat() == null) {
796                return;
797            }
798            if (muteControl.getValue()) {
799                leftGain = 0.0f;
800                rightGain = 0.0f;
801                return;
802            }
803            float gain = gainControl.getLinearGain();
804            if (getFormat().getChannels() == 1) {
805                // trivial case: only use gain
806                leftGain = gain;
807                rightGain = gain;
808            } else {
809                // need to combine gain and balance
810                float bal = balanceControl.getValue();
811                if (bal < 0.0f) {
812                    // left
813                    leftGain = gain;
814                    rightGain = gain * (bal + 1.0f);
815                } else {
816                    leftGain = gain * (1.0f - bal);
817                    rightGain = gain;
818                }
819            }
820        }
821
822        /////////////////// CONTROLS /////////////////////////////
823
824        protected final class Gain extends FloatControl {
825
826            private float linearGain = 1.0f;
827
828            private Gain() {
829
830                super(FloatControl.Type.MASTER_GAIN,
831                      Toolkit.linearToDB(0.0f),
832                      Toolkit.linearToDB(2.0f),
833                      Math.abs(Toolkit.linearToDB(1.0f)-Toolkit.linearToDB(0.0f))/128.0f,
834                      -1,
835                      0.0f,
836                      "dB", "Minimum", "", "Maximum");
837            }
838
839            @Override
840            public void setValue(float newValue) {
841                // adjust value within range ?? spec says IllegalArgumentException
842                //newValue = Math.min(newValue, getMaximum());
843                //newValue = Math.max(newValue, getMinimum());
844
845                float newLinearGain = Toolkit.dBToLinear(newValue);
846                super.setValue(Toolkit.linearToDB(newLinearGain));
847                // if no exception, commit to our new gain
848                linearGain = newLinearGain;
849                calcVolume();
850            }
851
852            float getLinearGain() {
853                return linearGain;
854            }
855        } // class Gain
856
857        private final class Mute extends BooleanControl {
858
859            private Mute() {
860                super(BooleanControl.Type.MUTE, false, "True", "False");
861            }
862
863            @Override
864            public void setValue(boolean newValue) {
865                super.setValue(newValue);
866                calcVolume();
867            }
868        }  // class Mute
869
870        private final class Balance extends FloatControl {
871
872            private Balance() {
873                super(FloatControl.Type.BALANCE, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,
874                      "", "Left", "Center", "Right");
875            }
876
877            @Override
878            public void setValue(float newValue) {
879                setValueImpl(newValue);
880                panControl.setValueImpl(newValue);
881                calcVolume();
882            }
883
884            void setValueImpl(float newValue) {
885                super.setValue(newValue);
886            }
887
888        } // class Balance
889
890        private final class Pan extends FloatControl {
891
892            private Pan() {
893                super(FloatControl.Type.PAN, -1.0f, 1.0f, (1.0f / 128.0f), -1, 0.0f,
894                      "", "Left", "Center", "Right");
895            }
896
897            @Override
898            public void setValue(float newValue) {
899                setValueImpl(newValue);
900                balanceControl.setValueImpl(newValue);
901                calcVolume();
902            }
903            void setValueImpl(float newValue) {
904                super.setValue(newValue);
905            }
906        } // class Pan
907    } // class DirectDL
908
909    /**
910     * Private inner class representing a SourceDataLine.
911     */
912    private static final class DirectSDL extends DirectDL
913            implements SourceDataLine {
914
915        private DirectSDL(DataLine.Info info,
916                          AudioFormat format,
917                          int bufferSize,
918                          DirectAudioDevice mixer) {
919            super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
920            if (Printer.trace) Printer.trace("DirectSDL CONSTRUCTOR: completed");
921        }
922
923    }
924
925    /**
926     * Private inner class representing a TargetDataLine.
927     */
928    private static final class DirectTDL extends DirectDL
929            implements TargetDataLine {
930
931        private DirectTDL(DataLine.Info info,
932                          AudioFormat format,
933                          int bufferSize,
934                          DirectAudioDevice mixer) {
935            super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), false);
936            if (Printer.trace) Printer.trace("DirectTDL CONSTRUCTOR: completed");
937        }
938
939        @Override
940        public int read(byte[] b, int off, int len) {
941            flushing = false;
942            if (len == 0) {
943                return 0;
944            }
945            if (len < 0) {
946                throw new IllegalArgumentException("illegal len: "+len);
947            }
948            if (len % getFormat().getFrameSize() != 0) {
949                throw new IllegalArgumentException("illegal request to read "
950                                                   +"non-integral number of frames ("
951                                                   +len+" bytes, "
952                                                   +"frameSize = "+getFormat().getFrameSize()+" bytes)");
953            }
954            if (off < 0) {
955                throw new ArrayIndexOutOfBoundsException(off);
956            }
957            if ((long)off + (long)len > (long)b.length) {
958                throw new ArrayIndexOutOfBoundsException(b.length);
959            }
960            if (!isActive() && doIO) {
961                // this is not exactly correct... would be nicer
962                // if the native sub system sent a callback when IO really starts
963                setActive(true);
964                setStarted(true);
965            }
966            int read = 0;
967            while (doIO && !flushing) {
968                int thisRead;
969                synchronized (lockNative) {
970                    thisRead = nRead(id, b, off, len, softwareConversionSize);
971                    if (thisRead < 0) {
972                        // error in native layer
973                        break;
974                    }
975                    bytePosition += thisRead;
976                    if (thisRead > 0) {
977                        drained = false;
978                    }
979                }
980                len -= thisRead;
981                read += thisRead;
982                if (len > 0) {
983                    off += thisRead;
984                    synchronized(lock) {
985                        try {
986                            lock.wait(waitTime);
987                        } catch (InterruptedException ie) {}
988                    }
989                } else {
990                    break;
991                }
992            }
993            if (flushing) {
994                read = 0;
995            }
996            return read;
997        }
998
999    }
1000
1001    /**
1002     * Private inner class representing a Clip
1003     * This clip is realized in software only
1004     */
1005    private static final class DirectClip extends DirectDL
1006            implements Clip, Runnable, AutoClosingClip {
1007
1008        private volatile Thread thread;
1009        private volatile byte[] audioData = null;
1010        private volatile int frameSize;         // size of one frame in bytes
1011        private volatile int m_lengthInFrames;
1012        private volatile int loopCount;
1013        private volatile int clipBytePosition;   // index in the audioData array at current playback
1014        private volatile int newFramePosition;   // set in setFramePosition()
1015        private volatile int loopStartFrame;
1016        private volatile int loopEndFrame;      // the last sample included in the loop
1017
1018        // auto closing clip support
1019        private boolean autoclosing = false;
1020
1021        private DirectClip(DataLine.Info info,
1022                           AudioFormat format,
1023                           int bufferSize,
1024                           DirectAudioDevice mixer) {
1025            super(info, mixer, format, bufferSize, mixer.getMixerIndex(), mixer.getDeviceID(), true);
1026            if (Printer.trace) Printer.trace("DirectClip CONSTRUCTOR: completed");
1027        }
1028
1029        // CLIP METHODS
1030
1031        @Override
1032        public void open(AudioFormat format, byte[] data, int offset, int bufferSize)
1033            throws LineUnavailableException {
1034
1035            // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1036            Toolkit.isFullySpecifiedAudioFormat(format);
1037            Toolkit.validateBuffer(format.getFrameSize(), bufferSize);
1038
1039            byte[] newData = new byte[bufferSize];
1040            System.arraycopy(data, offset, newData, 0, bufferSize);
1041            open(format, newData, bufferSize / format.getFrameSize());
1042        }
1043
1044        // this method does not copy the data array
1045        private void open(AudioFormat format, byte[] data, int frameLength)
1046            throws LineUnavailableException {
1047
1048            // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1049            Toolkit.isFullySpecifiedAudioFormat(format);
1050
1051            synchronized (mixer) {
1052                if (Printer.trace) Printer.trace("> DirectClip.open(format, data, frameLength)");
1053                if (Printer.debug) Printer.debug("   data="+((data==null)?"null":""+data.length+" bytes"));
1054                if (Printer.debug) Printer.debug("   frameLength="+frameLength);
1055
1056                if (isOpen()) {
1057                    throw new IllegalStateException("Clip is already open with format " + getFormat() +
1058                                                    " and frame lengh of " + getFrameLength());
1059                } else {
1060                    // if the line is not currently open, try to open it with this format and buffer size
1061                    this.audioData = data;
1062                    this.frameSize = format.getFrameSize();
1063                    this.m_lengthInFrames = frameLength;
1064                    // initialize loop selection with full range
1065                    bytePosition = 0;
1066                    clipBytePosition = 0;
1067                    newFramePosition = -1; // means: do not set to a new readFramePos
1068                    loopStartFrame = 0;
1069                    loopEndFrame = frameLength - 1;
1070                    loopCount = 0; // means: play the clip irrespective of loop points from beginning to end
1071
1072                    try {
1073                        // use DirectDL's open method to open it
1074                        open(format, (int) Toolkit.millis2bytes(format, CLIP_BUFFER_TIME)); // one second buffer
1075                    } catch (LineUnavailableException lue) {
1076                        audioData = null;
1077                        throw lue;
1078                    } catch (IllegalArgumentException iae) {
1079                        audioData = null;
1080                        throw iae;
1081                    }
1082
1083                    // if we got this far, we can instanciate the thread
1084                    int priority = Thread.NORM_PRIORITY
1085                        + (Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) / 3;
1086                    thread = JSSecurityManager.createThread(this,
1087                                                            "Direct Clip", // name
1088                                                            true,     // daemon
1089                                                            priority, // priority
1090                                                            false);  // doStart
1091                    // cannot start in createThread, because the thread
1092                    // uses the "thread" variable as indicator if it should
1093                    // continue to run
1094                    thread.start();
1095                }
1096            }
1097            if (isAutoClosing()) {
1098                getEventDispatcher().autoClosingClipOpened(this);
1099            }
1100            if (Printer.trace) Printer.trace("< DirectClip.open completed");
1101        }
1102
1103        @Override
1104        public void open(AudioInputStream stream) throws LineUnavailableException, IOException {
1105
1106            // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
1107            Toolkit.isFullySpecifiedAudioFormat(format);
1108
1109            synchronized (mixer) {
1110                if (Printer.trace) Printer.trace("> DirectClip.open(stream)");
1111                byte[] streamData = null;
1112
1113                if (isOpen()) {
1114                    throw new IllegalStateException("Clip is already open with format " + getFormat() +
1115                                                    " and frame lengh of " + getFrameLength());
1116                }
1117                int lengthInFrames = (int)stream.getFrameLength();
1118                if (Printer.debug) Printer.debug("DirectClip: open(AIS): lengthInFrames: " + lengthInFrames);
1119
1120                int bytesRead = 0;
1121                if (lengthInFrames != AudioSystem.NOT_SPECIFIED) {
1122                    // read the data from the stream into an array in one fell swoop.
1123                    int arraysize = lengthInFrames * stream.getFormat().getFrameSize();
1124                    streamData = new byte[arraysize];
1125
1126                    int bytesRemaining = arraysize;
1127                    int thisRead = 0;
1128                    while (bytesRemaining > 0 && thisRead >= 0) {
1129                        thisRead = stream.read(streamData, bytesRead, bytesRemaining);
1130                        if (thisRead > 0) {
1131                            bytesRead += thisRead;
1132                            bytesRemaining -= thisRead;
1133                        }
1134                        else if (thisRead == 0) {
1135                            Thread.yield();
1136                        }
1137                    }
1138                } else {
1139                    // read data from the stream until we reach the end of the stream
1140                    // we use a slightly modified version of ByteArrayOutputStream
1141                    // to get direct access to the byte array (we don't want a new array
1142                    // to be allocated)
1143                    int MAX_READ_LIMIT = 16384;
1144                    DirectBAOS dbaos  = new DirectBAOS();
1145                    byte tmp[] = new byte[MAX_READ_LIMIT];
1146                    int thisRead = 0;
1147                    while (thisRead >= 0) {
1148                        thisRead = stream.read(tmp, 0, tmp.length);
1149                        if (thisRead > 0) {
1150                            dbaos.write(tmp, 0, thisRead);
1151                            bytesRead += thisRead;
1152                        }
1153                        else if (thisRead == 0) {
1154                            Thread.yield();
1155                        }
1156                    } // while
1157                    streamData = dbaos.getInternalBuffer();
1158                }
1159                lengthInFrames = bytesRead / stream.getFormat().getFrameSize();
1160
1161                if (Printer.debug) Printer.debug("Read to end of stream. lengthInFrames: " + lengthInFrames);
1162
1163                // now try to open the device
1164                open(stream.getFormat(), streamData, lengthInFrames);
1165
1166                if (Printer.trace) Printer.trace("< DirectClip.open(stream) succeeded");
1167            } // synchronized
1168        }
1169
1170        @Override
1171        public int getFrameLength() {
1172            return m_lengthInFrames;
1173        }
1174
1175        @Override
1176        public long getMicrosecondLength() {
1177            return Toolkit.frames2micros(getFormat(), getFrameLength());
1178        }
1179
1180        @Override
1181        public void setFramePosition(int frames) {
1182            if (Printer.trace) Printer.trace("> DirectClip: setFramePosition: " + frames);
1183
1184            if (frames < 0) {
1185                frames = 0;
1186            }
1187            else if (frames >= getFrameLength()) {
1188                frames = getFrameLength();
1189            }
1190            if (doIO) {
1191                newFramePosition = frames;
1192            } else {
1193                clipBytePosition = frames * frameSize;
1194                newFramePosition = -1;
1195            }
1196            // fix for failing test050
1197            // $$fb although getFramePosition should return the number of rendered
1198            // frames, it is intuitive that setFramePosition will modify that
1199            // value.
1200            bytePosition = frames * frameSize;
1201
1202            // cease currently playing buffer
1203            flush();
1204
1205            // set new native position (if necessary)
1206            // this must come after the flush!
1207            synchronized (lockNative) {
1208                nSetBytePosition(id, isSource, frames * frameSize);
1209            }
1210
1211            if (Printer.debug) Printer.debug("  DirectClip.setFramePosition: "
1212                                             +" doIO="+doIO
1213                                             +" newFramePosition="+newFramePosition
1214                                             +" clipBytePosition="+clipBytePosition
1215                                             +" bytePosition="+bytePosition
1216                                             +" getLongFramePosition()="+getLongFramePosition());
1217            if (Printer.trace) Printer.trace("< DirectClip: setFramePosition");
1218        }
1219
1220        // replacement for getFramePosition (see AbstractDataLine)
1221        @Override
1222        public long getLongFramePosition() {
1223            /* $$fb
1224             * this would be intuitive, but the definition of getFramePosition
1225             * is the number of frames rendered since opening the device...
1226             * That also means that setFramePosition() means something very
1227             * different from getFramePosition() for Clip.
1228             */
1229            // take into account the case that a new position was set...
1230            //if (!doIO && newFramePosition >= 0) {
1231            //return newFramePosition;
1232            //}
1233            return super.getLongFramePosition();
1234        }
1235
1236        @Override
1237        public synchronized void setMicrosecondPosition(long microseconds) {
1238            if (Printer.trace) Printer.trace("> DirectClip: setMicrosecondPosition: " + microseconds);
1239
1240            long frames = Toolkit.micros2frames(getFormat(), microseconds);
1241            setFramePosition((int) frames);
1242
1243            if (Printer.trace) Printer.trace("< DirectClip: setMicrosecondPosition succeeded");
1244        }
1245
1246        @Override
1247        public void setLoopPoints(int start, int end) {
1248            if (Printer.trace) Printer.trace("> DirectClip: setLoopPoints: start: " + start + " end: " + end);
1249
1250            if (start < 0 || start >= getFrameLength()) {
1251                throw new IllegalArgumentException("illegal value for start: "+start);
1252            }
1253            if (end >= getFrameLength()) {
1254                throw new IllegalArgumentException("illegal value for end: "+end);
1255            }
1256
1257            if (end == -1) {
1258                end = getFrameLength() - 1;
1259                if (end < 0) {
1260                    end = 0;
1261                }
1262            }
1263
1264            // if the end position is less than the start position, throw IllegalArgumentException
1265            if (end < start) {
1266                throw new IllegalArgumentException("End position " + end + "  preceeds start position " + start);
1267            }
1268
1269            // slight race condition with the run() method, but not a big problem
1270            loopStartFrame = start;
1271            loopEndFrame = end;
1272
1273            if (Printer.trace) Printer.trace("  loopStart: " + loopStartFrame + " loopEnd: " + loopEndFrame);
1274            if (Printer.trace) Printer.trace("< DirectClip: setLoopPoints completed");
1275        }
1276
1277        @Override
1278        public void loop(int count) {
1279            // note: when count reaches 0, it means that the entire clip
1280            // will be played, i.e. it will play past the loop end point
1281            loopCount = count;
1282            start();
1283        }
1284
1285        @Override
1286        void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException {
1287            // only if audioData wasn't set in a calling open(format, byte[], frameSize)
1288            // this call is allowed.
1289            if (audioData == null) {
1290                throw new IllegalArgumentException("illegal call to open() in interface Clip");
1291            }
1292            super.implOpen(format, bufferSize);
1293        }
1294
1295        @Override
1296        void implClose() {
1297            if (Printer.trace) Printer.trace(">> DirectClip: implClose()");
1298
1299            // dispose of thread
1300            Thread oldThread = thread;
1301            thread = null;
1302            doIO = false;
1303            if (oldThread != null) {
1304                // wake up the thread if it's in wait()
1305                synchronized(lock) {
1306                    lock.notifyAll();
1307                }
1308                // wait for the thread to terminate itself,
1309                // but max. 2 seconds. Must not be synchronized!
1310                try {
1311                    oldThread.join(2000);
1312                } catch (InterruptedException ie) {}
1313            }
1314            super.implClose();
1315            // remove audioData reference and hand it over to gc
1316            audioData = null;
1317            newFramePosition = -1;
1318
1319            // remove this instance from the list of auto closing clips
1320            getEventDispatcher().autoClosingClipClosed(this);
1321
1322            if (Printer.trace) Printer.trace("<< DirectClip: implClose() succeeded");
1323        }
1324
1325        @Override
1326        void implStart() {
1327            if (Printer.trace) Printer.trace("> DirectClip: implStart()");
1328            super.implStart();
1329            if (Printer.trace) Printer.trace("< DirectClip: implStart() succeeded");
1330        }
1331
1332        @Override
1333        void implStop() {
1334            if (Printer.trace) Printer.trace(">> DirectClip: implStop()");
1335
1336            super.implStop();
1337            // reset loopCount field so that playback will be normal with
1338            // next call to start()
1339            loopCount = 0;
1340
1341            if (Printer.trace) Printer.trace("<< DirectClip: implStop() succeeded");
1342        }
1343
1344        // main playback loop
1345        @Override
1346        public void run() {
1347            if (Printer.trace) Printer.trace(">>> DirectClip: run() threadID="+Thread.currentThread().getId());
1348            Thread curThread = Thread.currentThread();
1349            while (thread == curThread) {
1350                // doIO is volatile, but we could check it, then get
1351                // pre-empted while another thread changes doIO and notifies,
1352                // before we wait (so we sleep in wait forever).
1353                synchronized(lock) {
1354                    while (!doIO && thread == curThread) {
1355                        try {
1356                            lock.wait();
1357                        } catch (InterruptedException ignored) {
1358                        }
1359                    }
1360                }
1361                while (doIO && thread == curThread) {
1362                    if (newFramePosition >= 0) {
1363                        clipBytePosition = newFramePosition * frameSize;
1364                        newFramePosition = -1;
1365                    }
1366                    int endFrame = getFrameLength() - 1;
1367                    if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1368                        endFrame = loopEndFrame;
1369                    }
1370                    long framePos = (clipBytePosition / frameSize);
1371                    int toWriteFrames = (int) (endFrame - framePos + 1);
1372                    int toWriteBytes = toWriteFrames * frameSize;
1373                    if (toWriteBytes > getBufferSize()) {
1374                        toWriteBytes = Toolkit.align(getBufferSize(), frameSize);
1375                    }
1376                    int written = write(audioData, clipBytePosition, toWriteBytes); // increases bytePosition
1377                    clipBytePosition += written;
1378                    // make sure nobody called setFramePosition, or stop() during the write() call
1379                    if (doIO && newFramePosition < 0 && written >= 0) {
1380                        framePos = clipBytePosition / frameSize;
1381                        // since endFrame is the last frame to be played,
1382                        // framePos is after endFrame when all frames, including framePos,
1383                        // are played.
1384                        if (framePos > endFrame) {
1385                            // at end of playback. If looping is on, loop back to the beginning.
1386                            if (loopCount > 0 || loopCount == LOOP_CONTINUOUSLY) {
1387                                if (loopCount != LOOP_CONTINUOUSLY) {
1388                                    loopCount--;
1389                                }
1390                                newFramePosition = loopStartFrame;
1391                            } else {
1392                                // no looping, stop playback
1393                                if (Printer.debug) Printer.debug("stop clip in run() loop:");
1394                                if (Printer.debug) Printer.debug("  doIO="+doIO+" written="+written+" clipBytePosition="+clipBytePosition);
1395                                if (Printer.debug) Printer.debug("  framePos="+framePos+" endFrame="+endFrame);
1396                                drain();
1397                                stop();
1398                            }
1399                        }
1400                    }
1401                }
1402            }
1403            if (Printer.trace) Printer.trace("<<< DirectClip: run() threadID="+Thread.currentThread().getId());
1404        }
1405
1406        // AUTO CLOSING CLIP SUPPORT
1407
1408        /* $$mp 2003-10-01
1409           The following two methods are common between this class and
1410           MixerClip. They should be moved to a base class, together
1411           with the instance variable 'autoclosing'. */
1412
1413        @Override
1414        public boolean isAutoClosing() {
1415            return autoclosing;
1416        }
1417
1418        @Override
1419        public void setAutoClosing(boolean value) {
1420            if (value != autoclosing) {
1421                if (isOpen()) {
1422                    if (value) {
1423                        getEventDispatcher().autoClosingClipOpened(this);
1424                    } else {
1425                        getEventDispatcher().autoClosingClipClosed(this);
1426                    }
1427                }
1428                autoclosing = value;
1429            }
1430        }
1431
1432        @Override
1433        protected boolean requiresServicing() {
1434            // no need for servicing for Clips
1435            return false;
1436        }
1437
1438    } // DirectClip
1439
1440    /*
1441     * private inner class representing a ByteArrayOutputStream
1442     * which allows retrieval of the internal array
1443     */
1444    private static class DirectBAOS extends ByteArrayOutputStream {
1445        DirectBAOS() {
1446            super();
1447        }
1448
1449        public byte[] getInternalBuffer() {
1450            return buf;
1451        }
1452
1453    } // class DirectBAOS
1454
1455    @SuppressWarnings("rawtypes")
1456    private static native void nGetFormats(int mixerIndex, int deviceID,
1457                                           boolean isSource, Vector formats);
1458
1459    private static native long nOpen(int mixerIndex, int deviceID, boolean isSource,
1460                                     int encoding,
1461                                     float sampleRate,
1462                                     int sampleSizeInBits,
1463                                     int frameSize,
1464                                     int channels,
1465                                     boolean signed,
1466                                     boolean bigEndian,
1467                                     int bufferSize) throws LineUnavailableException;
1468    private static native void nStart(long id, boolean isSource);
1469    private static native void nStop(long id, boolean isSource);
1470    private static native void nClose(long id, boolean isSource);
1471    private static native int nWrite(long id, byte[] b, int off, int len, int conversionSize,
1472                                     float volLeft, float volRight);
1473    private static native int nRead(long id, byte[] b, int off, int len, int conversionSize);
1474    private static native int nGetBufferSize(long id, boolean isSource);
1475    private static native boolean nIsStillDraining(long id, boolean isSource);
1476    private static native void nFlush(long id, boolean isSource);
1477    private static native int nAvailable(long id, boolean isSource);
1478    // javaPos is number of bytes read/written in Java layer
1479    private static native long nGetBytePosition(long id, boolean isSource, long javaPos);
1480    private static native void nSetBytePosition(long id, boolean isSource, long pos);
1481
1482    // returns if the native implementation needs regular calls to nService()
1483    private static native boolean nRequiresServicing(long id, boolean isSource);
1484    // called in irregular intervals
1485    private static native void nService(long id, boolean isSource);
1486}
1487