views:

943

answers:

1

I am writing a video application in Java by executing ffmpeg and capturing its output to standard output. I decided to use Apache Commons-Exec instead of Java's Runtime, because it seems better. However, I am have a difficult time capturing all of the output.

I thought using pipes would be the way to go, because it is a standard way of inter-process communication. However, my setup using PipedInputStream and PipedOutputStream is wrong. It seems to work, but only for the first 1042 bytes of the stream, which curiously happens to be the value of PipedInputStream.PIPE_SIZE.

I have no love affair with using pipes, but I want to avoid use disk I/O (if possible), because of speed and volume of data (a 1m 20s video at 512x384 resolution produces 690M of piped data).

Thoughts on the best solution to handle large amounts of data coming from a pipe? My code for my two classes are below. (yes, sleep is bad. Thoughts on that? wait() and notifyAll() ?)

WriteFrames.java

public class WriteFrames {
    public static void main(String[] args) {
     String commandName = "ffmpeg";
     CommandLine commandLine = new CommandLine(commandName);
     File filename = new File(args[0]);
     String[] options = new String[] { 
       "-i",
       filename.getAbsolutePath(),
       "-an",
       "-f",
       "yuv4mpegpipe",
       "-"};

     for (String s : options) {
      commandLine.addArgument(s);
     }



     PipedOutputStream output = new PipedOutputStream();
     PumpStreamHandler streamHandler = new PumpStreamHandler(output, System.err);
     DefaultExecutor executor = new DefaultExecutor();

     try {
      DataInputStream is = new DataInputStream(new PipedInputStream(output));
      YUV4MPEGPipeParser p = new YUV4MPEGPipeParser(is);
      p.start();

      executor.setStreamHandler(streamHandler);
      executor.execute(commandLine);
     } catch (IOException e) {
      e.printStackTrace();
     }

    }
}

YUV4MPEGPipeParser.java

public class YUV4MPEGPipeParser extends Thread {

    private InputStream is;
    int width, height;

    public YUV4MPEGPipeParser(InputStream is) {
     this.is = is;
    }

    public void run() {
     try {
      while (is.available() == 0) {
       Thread.sleep(100);
      }

      while (is.available() != 0) {
       // do stuff.... like write out YUV frames
      }
     } catch (IOException e) {
      e.printStackTrace();
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
}
+2  A: 

The problem is in the run method of YUV4MPEGPipeParser class. There are two successive loops. The second loop terminates immediately if there are no data currently available on the stream (e.g. all input so far was processed by parser, and ffmpeg or stream pump were not fast enough to serve some new data for it -> available() == 0 -> loop is terminated -> pump thread finishes).

Just get rid of these two loops and sleep and just perform a simple blocking read() instead of checking if any data are available for processing. There is also probably no need for wait()/notify() or even sleep() because the parser code is started on a separate thread.

You can rewrite the code of run() method like this:

public class YUV4MPEGPipeParser extends Thread {

    ...

    // optimal size of buffer for reading from pipe stream :-)
    private static final int BUFSIZE = PipedInputStream.PIPE_SIZE; 

    public void run() {
        try {
            byte buffer[] = new byte[BUFSIZE];
            int len = 0;
            while ((len = is.read(buffer, 0, BUFSIZE) != -1) {
                // we have valid data available 
                // in first 'len' bytes of 'buffer' array.

                // do stuff.... like write out YUV frames
            }
         } catch ...
     }
 }
Matej
Worked like a charm. So much easier. Thank you. I missed the fact that read() blocked.
Brian