United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-6372428 : REGRESSION: playback fails after exiting from thread that has started it (Windows)

Details
Type:
Bug
Submit Date:
2006-01-13
Status:
Resolved
Updated Date:
2011-01-19
Project Name:
JDK
Resolved Date:
2006-05-03
Component:
client-libs
OS:
windows
Sub-Component:
javax.sound
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
5.0
Fixed Versions:
5.0u8 (b01)

Related Reports
Backport:

Sub Tasks

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)
                                     
2006-01-13
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)
                                     
2006-01-13



Hardware and Software, Engineered to Work Together