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