1/*
2 * Copyright (c) 1999, 2016, 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 javax.sound.sampled.AudioFormat;
29import javax.sound.sampled.AudioSystem;
30import javax.sound.sampled.Control;
31import javax.sound.sampled.DataLine;
32import javax.sound.sampled.LineEvent;
33import javax.sound.sampled.LineUnavailableException;
34
35/**
36 * AbstractDataLine
37 *
38 * @author Kara Kytle
39 */
40abstract class AbstractDataLine extends AbstractLine implements DataLine {
41
42    // DEFAULTS
43
44    // default format
45    private final AudioFormat defaultFormat;
46
47    // default buffer size in bytes
48    private final int defaultBufferSize;
49
50    // the lock for synchronization
51    protected final Object lock = new Object();
52
53    // STATE
54
55    // current format
56    protected AudioFormat format;
57
58    // current buffer size in bytes
59    protected int bufferSize;
60
61    private volatile boolean running;
62    private volatile boolean started;
63    private volatile boolean active;
64
65    /**
66     * Constructs a new AbstractLine.
67     */
68    protected AbstractDataLine(DataLine.Info info, AbstractMixer mixer, Control[] controls) {
69        this(info, mixer, controls, null, AudioSystem.NOT_SPECIFIED);
70    }
71
72    /**
73     * Constructs a new AbstractLine.
74     */
75    protected AbstractDataLine(DataLine.Info info, AbstractMixer mixer, Control[] controls, AudioFormat format, int bufferSize) {
76
77        super(info, mixer, controls);
78
79        // record the default values
80        if (format != null) {
81            defaultFormat = format;
82        } else {
83            // default CD-quality
84            defaultFormat = new AudioFormat(44100.0f, 16, 2, true, Platform.isBigEndian());
85        }
86        if (bufferSize > 0) {
87            defaultBufferSize = bufferSize;
88        } else {
89            // 0.5 seconds buffer
90            defaultBufferSize = ((int) (defaultFormat.getFrameRate() / 2)) * defaultFormat.getFrameSize();
91        }
92
93        // set the initial values to the defaults
94        this.format = defaultFormat;
95        this.bufferSize = defaultBufferSize;
96    }
97
98
99    // DATA LINE METHODS
100
101    public final void open(AudioFormat format, int bufferSize) throws LineUnavailableException {
102        //$$fb 2001-10-09: Bug #4517739: avoiding deadlock by synchronizing to mixer !
103        synchronized (mixer) {
104            if (Printer.trace) Printer.trace("> AbstractDataLine.open(format, bufferSize) (class: "+getClass().getName());
105
106            // if the line is not currently open, try to open it with this format and buffer size
107            if (!isOpen()) {
108                // make sure that the format is specified correctly
109                // $$fb part of fix for 4679187: Clip.open() throws unexpected Exceptions
110                Toolkit.isFullySpecifiedAudioFormat(format);
111
112                if (Printer.debug) Printer.debug("  need to open the mixer...");
113                // reserve mixer resources for this line
114                //mixer.open(this, format, bufferSize);
115                mixer.open(this);
116
117                try {
118                    // open the data line.  may throw LineUnavailableException.
119                    implOpen(format, bufferSize);
120
121                    // if we succeeded, set the open state to true and send events
122                    setOpen(true);
123
124                } catch (LineUnavailableException e) {
125                    // release mixer resources for this line and then throw the exception
126                    mixer.close(this);
127                    throw e;
128                }
129            } else {
130                if (Printer.debug) Printer.debug("  dataline already open");
131
132                // if the line is already open and the requested format differs from the
133                // current settings, throw an IllegalStateException
134                //$$fb 2002-04-02: fix for 4661602: Buffersize is checked when re-opening line
135                if (!format.matches(getFormat())) {
136                    throw new IllegalStateException("Line is already open with format " + getFormat() +
137                                                    " and bufferSize " + getBufferSize());
138                }
139                //$$fb 2002-07-26: allow changing the buffersize of already open lines
140                if (bufferSize > 0) {
141                    setBufferSize(bufferSize);
142                }
143            }
144
145            if (Printer.trace) Printer.trace("< AbstractDataLine.open(format, bufferSize) completed");
146        }
147    }
148
149    public final void open(AudioFormat format) throws LineUnavailableException {
150        open(format, AudioSystem.NOT_SPECIFIED);
151    }
152
153    /**
154     * This implementation always returns 0.
155     */
156    @Override
157    public int available() {
158        return 0;
159    }
160
161    /**
162     * This implementation does nothing.
163     */
164    @Override
165    public void drain() {
166        if (Printer.trace) Printer.trace("AbstractDataLine: drain");
167    }
168
169    /**
170     * This implementation does nothing.
171     */
172    @Override
173    public void flush() {
174        if (Printer.trace) Printer.trace("AbstractDataLine: flush");
175    }
176
177    @Override
178    public final void start() {
179        //$$fb 2001-10-09: Bug #4517739: avoiding deadlock by synchronizing to mixer !
180        synchronized(mixer) {
181            if (Printer.trace) Printer.trace("> "+getClass().getName()+".start() - AbstractDataLine");
182
183            // $$kk: 06.06.99: if not open, this doesn't work....???
184            if (isOpen()) {
185
186                if (!isStartedRunning()) {
187                    mixer.start(this);
188                    implStart();
189                    running = true;
190                }
191            }
192        }
193
194        synchronized(lock) {
195            lock.notifyAll();
196        }
197
198        if (Printer.trace) Printer.trace("< "+getClass().getName()+".start() - AbstractDataLine");
199    }
200
201    @Override
202    public final void stop() {
203
204        //$$fb 2001-10-09: Bug #4517739: avoiding deadlock by synchronizing to mixer !
205        synchronized(mixer) {
206            if (Printer.trace) Printer.trace("> "+getClass().getName()+".stop() - AbstractDataLine");
207
208            // $$kk: 06.06.99: if not open, this doesn't work.
209            if (isOpen()) {
210
211                if (isStartedRunning()) {
212
213                    implStop();
214                    mixer.stop(this);
215
216                    running = false;
217
218                    // $$kk: 11.10.99: this is not exactly correct, but will probably work
219                    if (started && (!isActive())) {
220                        setStarted(false);
221                    }
222                }
223            }
224        }
225
226        synchronized(lock) {
227            lock.notifyAll();
228        }
229
230        if (Printer.trace) Printer.trace("< "+getClass().getName()+".stop() - AbstractDataLine");
231    }
232
233    // $$jb: 12.10.99: The official API for this is isRunning().
234    // Per the denied RFE 4297981,
235    // the change to isStarted() is technically an unapproved API change.
236    // The 'started' variable is false when playback of data stops.
237    // It is changed throughout the implementation with setStarted().
238    // This state is what should be returned by isRunning() in the API.
239    // Note that the 'running' variable is true between calls to
240    // start() and stop().  This state is accessed now through the
241    // isStartedRunning() method, defined below.  I have not changed
242    // the variable names at this point, since 'running' is accessed
243    // in MixerSourceLine and MixerClip, and I want to touch as little
244    // code as possible to change isStarted() back to isRunning().
245
246    @Override
247    public final boolean isRunning() {
248        return started;
249    }
250
251    @Override
252    public final boolean isActive() {
253        return active;
254    }
255
256    @Override
257    public final long getMicrosecondPosition() {
258
259        long microseconds = getLongFramePosition();
260        if (microseconds != AudioSystem.NOT_SPECIFIED) {
261            microseconds = Toolkit.frames2micros(getFormat(), microseconds);
262        }
263        return microseconds;
264    }
265
266    @Override
267    public final AudioFormat getFormat() {
268        return format;
269    }
270
271    @Override
272    public final int getBufferSize() {
273        return bufferSize;
274    }
275
276    /**
277     * This implementation does NOT change the buffer size
278     */
279    public final int setBufferSize(int newSize) {
280        return getBufferSize();
281    }
282
283    /**
284     * This implementation returns AudioSystem.NOT_SPECIFIED.
285     */
286    @Override
287    public final float getLevel() {
288        return (float)AudioSystem.NOT_SPECIFIED;
289    }
290
291    // HELPER METHODS
292
293    /**
294     * running is true after start is called and before stop is called,
295     * regardless of whether data is actually being presented.
296     */
297    // $$jb: 12.10.99: calling this method isRunning() conflicts with
298    // the official API that was once called isStarted().  Since we
299    // use this method throughout the implementation, I am renaming
300    // it to isStartedRunning().  This is part of backing out the
301    // change denied in RFE 4297981.
302
303    final boolean isStartedRunning() {
304        return running;
305    }
306
307    /**
308     * This method sets the active state and generates
309     * events if it changes.
310     */
311    final void setActive(boolean active) {
312
313        if (Printer.trace) Printer.trace("> AbstractDataLine: setActive(" + active + ")");
314
315        //boolean sendEvents = false;
316        //long position = getLongFramePosition();
317
318        synchronized (this) {
319
320            if (this.active != active) {
321                this.active = active;
322                //sendEvents = true;
323            }
324        }
325
326        // $$kk: 11.19.99: take ACTIVE / INACTIVE / EOM events out;
327        // putting them in is technically an API change.
328        // do not generate ACTIVE / INACTIVE events for now
329        // if (sendEvents) {
330        //
331        //      if (active) {
332        //              sendEvents(new LineEvent(this, LineEvent.Type.ACTIVE, position));
333        //      } else {
334        //              sendEvents(new LineEvent(this, LineEvent.Type.INACTIVE, position));
335        //      }
336        //}
337    }
338
339    /**
340     * This method sets the started state and generates
341     * events if it changes.
342     */
343    final void setStarted(boolean started) {
344
345        if (Printer.trace) Printer.trace("> AbstractDataLine: setStarted(" + started + ")");
346
347        boolean sendEvents = false;
348        long position = getLongFramePosition();
349
350        synchronized (this) {
351
352            if (this.started != started) {
353                this.started = started;
354                sendEvents = true;
355            }
356        }
357
358        if (sendEvents) {
359
360            if (started) {
361                sendEvents(new LineEvent(this, LineEvent.Type.START, position));
362            } else {
363                sendEvents(new LineEvent(this, LineEvent.Type.STOP, position));
364            }
365        }
366        if (Printer.trace) Printer.trace("< AbstractDataLine: setStarted completed");
367    }
368
369    /**
370     * This method generates a STOP event and sets the started state to false.
371     * It is here for historic reasons when an EOM event existed.
372     */
373    final void setEOM() {
374
375        if (Printer.trace) Printer.trace("> AbstractDataLine: setEOM()");
376        //$$fb 2002-04-21: sometimes, 2 STOP events are generated.
377        // better use setStarted() to send STOP event.
378        setStarted(false);
379        if (Printer.trace) Printer.trace("< AbstractDataLine: setEOM() completed");
380    }
381
382    // OVERRIDES OF ABSTRACT LINE METHODS
383
384    /**
385     * Try to open the line with the current format and buffer size values.
386     * If the line is not open, these will be the defaults.  If the
387     * line is open, this should return quietly because the values
388     * requested will match the current ones.
389     */
390    @Override
391    public final void open() throws LineUnavailableException {
392
393        if (Printer.trace) Printer.trace("> "+getClass().getName()+".open() - AbstractDataLine");
394
395        // this may throw a LineUnavailableException.
396        open(format, bufferSize);
397        if (Printer.trace) Printer.trace("< "+getClass().getName()+".open() - AbstractDataLine");
398    }
399
400    /**
401     * This should also stop the line.  The closed line should not be running or active.
402     * After we close the line, we reset the format and buffer size to the defaults.
403     */
404    @Override
405    public final void close() {
406        //$$fb 2001-10-09: Bug #4517739: avoiding deadlock by synchronizing to mixer !
407        synchronized (mixer) {
408            if (Printer.trace) Printer.trace("> "+getClass().getName()+".close() - in AbstractDataLine.");
409
410            if (isOpen()) {
411
412                // stop
413                stop();
414
415                // set the open state to false and send events
416                setOpen(false);
417
418                // close resources for this line
419                implClose();
420
421                // release mixer resources for this line
422                mixer.close(this);
423
424                // reset format and buffer size to the defaults
425                format = defaultFormat;
426                bufferSize = defaultBufferSize;
427            }
428        }
429        if (Printer.trace) Printer.trace("< "+getClass().getName()+".close() - in AbstractDataLine");
430    }
431
432    abstract void implOpen(AudioFormat format, int bufferSize) throws LineUnavailableException;
433    abstract void implClose();
434
435    abstract void implStart();
436    abstract void implStop();
437}
438