JDK-6372428 : REGRESSION: playback fails after exiting from thread that has started it (Windows)
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.sound
  • Affected Version: 5.0
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows
  • CPU: x86
  • Submitted: 2006-01-13
  • Updated: 2011-01-19
  • Resolved: 2006-05-03
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other JDK 6
5.0u8 b01Fixed 6Fixed
Description
When the thread that calls SourceDataLine.start() exits, the sound will stop playing (sound card starts to click, line position increases very slow and sometimes jumps back).

Due this bug JMF (All-Java version) doesn't work with latest java releases (1.5 and above)

Code example (works with 1.4.2, fails with 1.5 and 1.6):

import java.io.*;
import javax.sound.sampled.*;
import java.net.URL;

public class Test {
    public Test() {
    }
    
    public static void main(String[] args) throws Exception {
        Test pThis = new Test();
        try {
            pThis.testPlayback();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw ex;
        }
    }
    
    final static int DATA_LENGTH        = 15;   // in seconds
    final static int PLAYTHREAD_DELAY   = 5;   // in seconds
    
    // playback test classes/routines
    
    class PlayThread extends Thread {
        SourceDataLine line;
        public PlayThread(SourceDataLine line) {
            this.line = line;
            this.setDaemon(true);
        }
        
        public void run() {
            log("PlayThread: starting...");
            line.start();
            log("PlayThread: delaying " + (PLAYTHREAD_DELAY * 1000) + "ms...");
            delay(PLAYTHREAD_DELAY * 1000);
            log("PlayThread: exiting...");
        }
    }
    
    class WriteThread extends Thread {
        SourceDataLine line;
        byte[] data;
        int pos = 0;
        public WriteThread(SourceDataLine line, byte[] data) {
            this.line = line;
            this.data = data;
            this.setDaemon(true);
        }
        
        public void run() {
            int remain = data.length;
            while (remain > 0) {
                int avail = line.available();
                if (avail > 0) {
                    if (avail > remain)
                        avail = remain;
                    int written = line.write(data, pos, avail);
                    pos += written;
                    remain -= written;
                    log("WriteThread: " + written + " bytes written");
                } else {
                    delay(100);
                }
            }
            log("WriteThread: all data has been written, draining");
            line.drain();
            log("WriteThread: stopping");
            line.stop();
            log("WriteThread: exiting");
        }
        
        public boolean isCompleted() {
            return (pos >= data.length-1);
        }
    }
    
    void testPlayback() throws Exception {
        // prepare audio data
        AudioFormat format = new AudioFormat(22050, 8, 1, false, false);
        byte[] soundData = new byte[(int) (format.getFrameRate() * format.getFrameSize() * DATA_LENGTH)];
        
        // create & open source data line
        DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
        SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info);
        //SourceDataLine line = AudioSystem.getSourceDataLine(format);
        line.open(format);
        
        // start write data thread
        new WriteThread(line, soundData).start();
        
        // start line
        new PlayThread(line).start();
        
        // monitor line
        long lineTime1 = line.getMicrosecondPosition() / 1000;
        long realTime1 = currentTimeMillis();
        while (true) {
            delay(500);
            if (!line.isActive()) {
                log("audio data played completely");
                break;
            }
            long lineTime2 = line.getMicrosecondPosition() / 1000;
            long realTime2 = currentTimeMillis();
            long dLineTime = lineTime2 - lineTime1;
            long dRealTime = realTime2 - realTime1;
            log("line pos: " + lineTime2 + "ms");
            if (dLineTime < 0) {
                throw new RuntimeException("ERROR: line position have decreased from " + lineTime1 + " to " + lineTime2);
            }
            if (dRealTime < 450) {
                // delay() has been interrupted?
                continue;
            }
            if (dLineTime < 250) {
                throw new RuntimeException("ERROR: line position increased too slow: " + dLineTime + "ms during " + dRealTime + "ms");
            }
            
            lineTime1 = lineTime2;
            realTime1 = realTime2;
        }
    }
    
    
    // helper routines
    static long currentTimeMillis() {
        //return System.nanoTime() / 1000000L;
        return System.currentTimeMillis();
    }
    static void log(String s) {
        System.out.println(s);
    }
    static void delay(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {}
    }
}

Comments
WORK AROUND don't start playback (and capturing) from short-lived threads (start it from thread that will be avile along whole playback/capturing process)
13-01-2006

EVALUATION The reason of the bug is specific DirectSound buffers behaviour in the case: http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_c/directx/htm/idirectsoundbuffer8play.asp (remark section): If the application is multithreaded, the thread that plays the buffer must continue to exist as long as the buffer is playing. Buffers created on WDM drivers stop playing when the thread is terminated. due IDirectSoundCaptureBuffer8::Start() description: http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/directx9_c/directx/htm/idirectsoundcapturebuffer8start.asp capture buffers should have the same behaviour, but test shows recording works fine in the same situation (at least with WinXP)
13-01-2006