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