2 Replies Latest reply: Feb 12, 2012 11:00 AM by 829497 RSS

    How to use TargetDataLine.drain()?

    829497
      Hey there,

      I have a question regarding a specific problem. I need to give some code to explain:
      public class Recorder {
          private final TargetDataLine line;
          private final ByteArrayOutputStream data;
          
      
          public Recorder(AudioFormat format) throws LineUnavailableException {
              DataLine.Info lineInfo = new DataLine.Info( TargetDataLine.class,
                                                          format);
              line = (TargetDataLine)AudioSystem.getLine(lineInfo);
              data = new ByteArrayOutputStream();
          }
          
      
          public void start() throws LineUnavailableException {
              line.open();
              line.start();
      
      
              new Thread() {
                  @Override
                  public void run() {
                      byte[] buffer = new byte[line.getFormat().getFrameSize()];
                      while(line.isOpen()) {
                          if(0 != line.read(buffer, 0, buffer.length)) {
                              System.out.println("Writing data.");
                              try {
                                  data.write(buffer);
                              }
                              catch(IOException ex) {
                              }
                          }
                          else {
                              System.out.println("NOT writing data.");
                          }
                      }
                  }
              }.start();
          }
          
          
          public void stop() {
              line.stop();
              System.out.println("Available after stop(): " + line.available());
              line.drain();
              System.out.println("Available after drain(): " + line.available());
              line.close();
              System.out.println("Available after close(): " + line.available());
              stopLineWatch();
          }
      }
      And here's the output that the code produces when start() and the stop() is invoked:
      Writing data.
      Writing data.
      Writing data.
      Available after stop(): 11024
      Available after drain(): 11024
      Available after close(): 0
      NOT writing data.

      Now here's the problem:
      I don't want my Recorder class to end recording prematurely when Recorder.stop() is invoked. So the recording loop actually stops when the line gets closed so the line is supposed to be closed when no more data is available. Therefor I first stop the line to avoid that the lines internal buffer goes on being filled with more data. Then I invoke drain(), expecting that I will be able to go on reading from the line until there's no more data in its internal buffer. Unfortunately I am not able to go on reading from the line at all since read() always returns 0 after stop() has been invoked. I also expected drain() to block while there's more data to be read from the line. But drain() seems not to block at all as available returns a big number of leftover bytes and line.stop() is invoked immediately after line.drain().
      So: How can I use drain() correctly to get all remaining data from the line?

      Thanks in advance!
        • 1. Re: How to use TargetDataLine.drain()?
          Phil Freihofner
          Instead of using "line.isOpen()" as your while condition, create a boolean, such as "running". Then you are free to stop the while loop via an event prior to and independent of the line.stop() by setting running = false.

          The Java Tutorial kinda sorta talks about this, noting helpfully that an example has not been provided, in the third to last paragraph of the section "Reading the Data from the TargetDataLine".

          [They use while(!stopped) and set stopped to true. Same diff.]

          http://docs.oracle.com/javase/tutorial/sound/capturing.html
          • 2. Re: How to use TargetDataLine.drain()?
            829497
            Thank you for answering and providing the link.

            I think the code in the link is a good example for capturing sound and I'm sure that using the boolean as loop condition is a good aproach. I decided to adapt that aproach to my code. But the info about how to use the drain() method is still missing, even though it is mentioned on the page you provided.

            I think this paragraph might be helpful:
            As with a source data line, it's possible to drain or flush a target data line. For example, if you're recording the input to a file, you'll probably want to invoke the drain method when the user clicks a Stop button. The drain method will cause the mixer's remaining data to get delivered to the target data line's buffer. If you don't drain the data, the captured sound might seem to be truncated prematurely at the end.
            It makes me consider two things:
            1. Recording to file is a special case that will lead to a special behavior of the drain() method. (Just an assumption, needs to be confirmed or rebuted)
            2. The drain() method behaves different from what I expected it. The JavaDoc says about flush(): "Drains queued data from the line by continuing data I/O until the data line's internal buffer has been emptied." I find this description is very different from the description on the web page. Which one is right?