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