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