1 /*
   2  * Copyright (c) 2006, 2017, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import javax.sound.sampled.AudioFormat;
  25 import javax.sound.sampled.AudioSystem;
  26 import javax.sound.sampled.DataLine;
  27 import javax.sound.sampled.LineUnavailableException;
  28 import javax.sound.sampled.SourceDataLine;
  29 import javax.sound.sampled.TargetDataLine;
  30 
  31 /*
  32  * @test
  33  * @bug 6372428
  34  * @summary playback and capture doesn't interrupt after terminating thread that
  35  *          calls start()
  36  * @run main bug6372428
  37  */
  38 public class bug6372428 {
  39     public bug6372428() {
  40     }
  41 
  42     public static void main(final String[] args) {
  43         bug6372428 pThis = new bug6372428();
  44         boolean failed1 = false;
  45         boolean failed2 = false;
  46         log("");
  47         log("****************************************************************");
  48         log("*** Playback Test");
  49         log("****************************************************************");
  50         log("");
  51         try {
  52             pThis.testPlayback();
  53         } catch (IllegalArgumentException | LineUnavailableException e) {
  54             System.out.println("Playback test is not applicable. Skipped");
  55         } catch (Exception ex) {
  56             ex.printStackTrace();
  57             failed1 = true;
  58         }
  59         log("");
  60         log("");
  61         log("****************************************************************");
  62         log("*** Capture Test");
  63         log("****************************************************************");
  64         log("");
  65         try {
  66             pThis.testRecord();
  67         } catch (IllegalArgumentException | LineUnavailableException e) {
  68             System.out.println("Record test is not applicable. Skipped");
  69         } catch (Exception ex) {
  70             ex.printStackTrace();
  71             failed2 = true;
  72         }
  73         log("");
  74         log("");
  75         log("****************************************************************");
  76         if (failed1 || failed2) {
  77             String s = "";
  78             if (failed1 && failed2)
  79                 s = "playback and capture";
  80             else if (failed1)
  81                 s = "playback only";
  82             else
  83                 s = "capture only";
  84             throw new RuntimeException("Test FAILED (" + s + ")");
  85         }
  86         log("*** All tests passed successfully.");
  87     }
  88 
  89     final static int DATA_LENGTH        = 15;   // in seconds
  90     final static int PLAYTHREAD_DELAY   = 5;   // in seconds
  91 
  92     // playback test classes/routines
  93 
  94     class PlayThread extends Thread {
  95         SourceDataLine line;
  96         public PlayThread(SourceDataLine line) {
  97             this.line = line;
  98             this.setDaemon(true);
  99         }
 100 
 101         public void run() {
 102             log("PlayThread: starting...");
 103             line.start();
 104             log("PlayThread: delaying " + (PLAYTHREAD_DELAY * 1000) + "ms...");
 105             delay(PLAYTHREAD_DELAY * 1000);
 106             log("PlayThread: exiting...");
 107         }
 108     }
 109 
 110     class WriteThread extends Thread {
 111         SourceDataLine line;
 112         byte[] data;
 113         volatile int remaining;
 114         volatile boolean stopRequested = false;
 115         public WriteThread(SourceDataLine line, byte[] data) {
 116             this.line = line;
 117             this.data = data;
 118             remaining = data.length;
 119             this.setDaemon(true);
 120         }
 121 
 122         public void run() {
 123             while (remaining > 0 && !stopRequested) {
 124                 int avail = line.available();
 125                 if (avail > 0) {
 126                     if (avail > remaining)
 127                         avail = remaining;
 128                     int written = line.write(data, data.length - remaining, avail);
 129                     remaining -= written;
 130                     log("WriteThread: " + written + " bytes written");
 131                 } else {
 132                     delay(100);
 133                 }
 134             }
 135             if (remaining == 0) {
 136                 log("WriteThread: all data has been written, draining");
 137                 line.drain();
 138             } else {
 139                 log("WriteThread: stop requested");
 140             }
 141             log("WriteThread: stopping");
 142             line.stop();
 143             log("WriteThread: exiting");
 144         }
 145 
 146         public boolean isCompleted() {
 147             return (remaining <= 0);
 148         }
 149 
 150         public void requestStop() {
 151             stopRequested = true;
 152         }
 153     }
 154 
 155     void testPlayback() throws LineUnavailableException {
 156         // prepare audio data
 157         AudioFormat format = new AudioFormat(22050, 8, 1, false, false);
 158         byte[] soundData = new byte[(int) (format.getFrameRate() * format.getFrameSize() * DATA_LENGTH)];
 159 
 160         // create & open source data line
 161         //SourceDataLine line = AudioSystem.getSourceDataLine(format);
 162         DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
 163         SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info);
 164 
 165         line.open(format);
 166 
 167         // start write data thread
 168         WriteThread p1 = new WriteThread(line, soundData);
 169         p1.start();
 170 
 171         // start line
 172         PlayThread p2 = new PlayThread(line);
 173         p2.start();
 174 
 175         // monitor line
 176         long lineTime1 = line.getMicrosecondPosition() / 1000;
 177         long realTime1 = currentTimeMillis();
 178         while (true) {
 179             delay(500);
 180             if (!line.isActive()) {
 181                 log("audio data played completely");
 182                 break;
 183             }
 184             long lineTime2 = line.getMicrosecondPosition() / 1000;
 185             long realTime2 = currentTimeMillis();
 186             long dLineTime = lineTime2 - lineTime1;
 187             long dRealTime = realTime2 - realTime1;
 188             log("line pos: " + lineTime2 + "ms" + ", thread is " + (p2.isAlive() ? "alive" : "DIED"));
 189             if (dLineTime < 0) {
 190                 throw new RuntimeException("ERROR: line position have decreased from " + lineTime1 + " to " + lineTime2);
 191             }
 192             if (dRealTime < 450) {
 193                 // delay() has been interrupted?
 194                 continue;
 195             }
 196             lineTime1 = lineTime2;
 197             realTime1 = realTime2;
 198         }
 199     }
 200 
 201 
 202     // recording test classes/routines
 203 
 204     class RecordThread extends Thread {
 205         TargetDataLine line;
 206         public RecordThread(TargetDataLine line) {
 207             this.line = line;
 208             this.setDaemon(true);
 209         }
 210 
 211         public void run() {
 212             log("RecordThread: starting...");
 213             line.start();
 214             log("RecordThread: delaying " + (PLAYTHREAD_DELAY * 1000) + "ms...");
 215             delay(PLAYTHREAD_DELAY * 1000);
 216             log("RecordThread: exiting...");
 217         }
 218     }
 219 
 220     class ReadThread extends Thread {
 221         TargetDataLine line;
 222         byte[] data;
 223         volatile int remaining;
 224         public ReadThread(TargetDataLine line, byte[] data) {
 225             this.line = line;
 226             this.data = data;
 227             remaining = data.length;
 228             this.setDaemon(true);
 229         }
 230 
 231         public void run() {
 232             log("ReadThread: buffer size is " + data.length + " bytes");
 233             delay(200);
 234             while ((remaining > 0) && line.isOpen()) {
 235                 int avail = line.available();
 236                 if (avail > 0) {
 237                     if (avail > remaining)
 238                         avail = remaining;
 239                     int read = line.read(data, data.length - remaining, avail);
 240                     remaining -= read;
 241                     log("ReadThread: " + read + " bytes read");
 242                 } else {
 243                     delay(100);
 244                 }
 245                 if (remaining <= 0) {
 246                     log("ReadThread: record buffer is full, exiting");
 247                     break;
 248                 }
 249             }
 250             if (remaining > 0) {
 251                 log("ReadThread: line has been stopped, exiting");
 252             }
 253         }
 254 
 255         public int getCount() {
 256             return data.length - remaining;
 257         }
 258         public boolean isCompleted() {
 259             return (remaining <= 0);
 260         }
 261     }
 262 
 263     void testRecord() throws LineUnavailableException {
 264         // prepare audio data
 265         AudioFormat format = new AudioFormat(22050, 8, 1, false, false);
 266 
 267         // create & open target data line
 268         //TargetDataLine line = AudioSystem.getTargetDataLine(format);
 269         DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
 270         TargetDataLine line = (TargetDataLine)AudioSystem.getLine(info);
 271 
 272         line.open(format);
 273 
 274         // start read data thread
 275         byte[] data = new byte[(int) (format.getFrameRate() * format.getFrameSize() * DATA_LENGTH)];
 276         ReadThread p1 = new ReadThread(line, data);
 277         p1.start();
 278 
 279         // start line
 280         //new RecordThread(line).start();
 281         RecordThread p2 = new RecordThread(line);
 282         p2.start();
 283 
 284         // monitor line
 285         long endTime = currentTimeMillis() + DATA_LENGTH * 1000;
 286 
 287         long realTime1 = currentTimeMillis();
 288         long lineTime1 = line.getMicrosecondPosition() / 1000;
 289 
 290         while (realTime1 < endTime && !p1.isCompleted()) {
 291             delay(100);
 292             long lineTime2 = line.getMicrosecondPosition() / 1000;
 293             long realTime2 = currentTimeMillis();
 294             long dLineTime = lineTime2 - lineTime1;
 295             long dRealTime = realTime2 - realTime1;
 296             log("line pos: " + lineTime2 + "ms" + ", thread is " + (p2.isAlive() ? "alive" : "DIED"));
 297             if (dLineTime < 0) {
 298                 line.stop();
 299                 line.close();
 300                 throw new RuntimeException("ERROR: line position have decreased from " + lineTime1 + " to " + lineTime2);
 301             }
 302             if (dRealTime < 450) {
 303                 // delay() has been interrupted?
 304                 continue;
 305             }
 306             lineTime1 = lineTime2;
 307             realTime1 = realTime2;
 308         }
 309         log("stopping line...");
 310         line.stop();
 311         line.close();
 312 
 313         /*
 314         log("");
 315         log("");
 316         log("");
 317         log("recording completed, delaying 5 sec");
 318         log("recorded " + p1.getCount() + " bytes, " + DATA_LENGTH + " seconds: " + (p1.getCount() * 8 / DATA_LENGTH) + " bit/sec");
 319         log("");
 320         log("");
 321         log("");
 322         delay(5000);
 323         log("starting playing...");
 324         playRecorded(format, data);
 325         */
 326     }
 327 
 328     void playRecorded(AudioFormat format, byte[] data) throws Exception {
 329         //SourceDataLine line = AudioSystem.getSourceDataLine(format);
 330         DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
 331         SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info);
 332 
 333         line.open();
 334         line.start();
 335 
 336         int remaining = data.length;
 337         while (remaining > 0) {
 338             int avail = line.available();
 339             if (avail > 0) {
 340                 if (avail > remaining)
 341                     avail = remaining;
 342                 int written = line.write(data, data.length - remaining, avail);
 343                 remaining -= written;
 344                 log("Playing: " + written + " bytes written");
 345             } else {
 346                 delay(100);
 347             }
 348         }
 349 
 350         line.drain();
 351         line.stop();
 352     }
 353 
 354     // helper routines
 355     static long startTime = currentTimeMillis();
 356     static long currentTimeMillis() {
 357         //return System.nanoTime() / 1000000L;
 358         return System.currentTimeMillis();
 359     }
 360     static void log(String s) {
 361         long time = currentTimeMillis() - startTime;
 362         long ms = time % 1000;
 363         time /= 1000;
 364         long sec = time % 60;
 365         time /= 60;
 366         long min = time % 60;
 367         time /= 60;
 368         System.out.println(""
 369             + (time < 10 ? "0" : "") + time
 370             + ":" + (min < 10 ? "0" : "") + min
 371             + ":" + (sec < 10 ? "0" : "") + sec
 372             + "." + (ms < 10 ? "00" : (ms < 100 ? "0" : "")) + ms
 373             + " (" + Thread.currentThread().getName() + ") " + s);
 374     }
 375     static void delay(int millis) {
 376         try {
 377             Thread.sleep(millis);
 378         } catch (InterruptedException e) {}
 379     }
 380 }