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 }