Java FFmpeg Integration Using Standard Input and Output
This article demonstrates how to build a Java application that interfaces directly with the FFmpeg binary using redirected standard input (stdin) and standard output (stdout). You will learn how to launch FFmpeg as an external process, stream raw data into its input stream, and capture the processed media stream directly in your Java code without relying on temporary disk files.
The Core Concept: FFmpeg Piping
FFmpeg supports reading from standard input and writing to standard
output using the pipe: protocol. * To read input from
stdin, use -i pipe:0 (or simply -i -). * To
write output to stdout, specify pipe:1 (or simply
-) as the output path.
When piping output, you must also specify a container format using
the -f flag (e.g., -f mp3 or
-f mpegts), because FFmpeg cannot auto-detect the output
format from a pipe filename.
Setting Up ProcessBuilder in Java
To run FFmpeg from Java, use java.lang.ProcessBuilder.
It allows you to configure the environment, arguments, and standard I/O
redirection.
Managing the Error Stream (stderr)
FFmpeg writes its status updates, progress, and error logs to the
standard error stream (stderr). If you do not read from
stderr, the operating system buffer may fill up, causing
the FFmpeg process to hang indefinitely. The easiest way to handle this
in Java is to redirect stderr to your Java application’s
standard error console using Redirect.INHERIT.
Complete Java Implementation
Below is a complete, working Java class that reads an input audio file, pipes it into FFmpeg to convert it to MP3 format, and writes the output MP3 data directly to a file on disk.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class FFmpegStreamer {
public static void main(String[] args) {
// Define the FFmpeg command
// This command takes input from stdin (pipe:0) and outputs MP3 to stdout (pipe:1)
String[] ffmpegCommand = {
"ffmpeg",
"-y", // Overwrite output files without asking
"-i", "pipe:0", // Read input from standard input
"-f", "mp3", // Force MP3 output format
"pipe:1" // Write output to standard output
};
ProcessBuilder pb = new ProcessBuilder(ffmpegCommand);
// Redirect stderr to parent process console to prevent hanging and see FFmpeg logs
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
try {
Process process = pb.start();
// Create threads to handle input and output simultaneously to avoid deadlocks
Thread writerThread = new Thread(() -> writeInputToFFmpeg(process.getOutputStream()));
Thread readerThread = new Thread(() -> readOutputFromFFmpeg(process.getInputStream()));
writerThread.start();
readerThread.start();
// Wait for I/O operations to complete
writerThread.join();
readerThread.join();
// Wait for FFmpeg process to terminate
int exitCode = process.waitFor();
System.out.println("FFmpeg process exited with code: " + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
private static void writeInputToFFmpeg(OutputStream ffmpegStdin) {
// Read a source audio file and write it to FFmpeg's standard input
try (InputStream fileInput = new FileInputStream("input_raw.wav");
OutputStream os = ffmpegStdin) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fileInput.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush(); // Ensure all data is sent
} catch (IOException e) {
System.err.println("Error writing to FFmpeg stdin: " + e.getMessage());
}
}
private static void readOutputFromFFmpeg(InputStream ffmpegStdout) {
// Read processed MP3 data from FFmpeg's standard output and save it to a file
try (InputStream is = ffmpegStdout;
FileOutputStream fileOutput = new FileOutputStream("output_processed.mp3")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fileOutput.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
System.err.println("Error reading from FFmpeg stdout: " + e.getMessage());
}
}
}Critical Best Practices
- Multithreading: Always perform reading from stdout and writing to stdin in separate concurrent threads (or using asynchronous NIO). If you write synchronously and wait to read, the process will deadlock when the internal OS pipe buffers fill up.
- Explicit Closing: Always close the output stream
connected to FFmpeg’s stdin (
process.getOutputStream()) when you are finished writing. This sends an EOF (End of File) signal to FFmpeg, letting it know that no more data is coming so it can finish processing and exit. - Container Limitations: Some formats (like MP4)
require a seekable output file to write metadata headers (the “moov
atom”) at the beginning of the file. Because standard stdout is a
non-seekable stream, you must configure FFmpeg to use fragmented output
(e.g.,
-movflags frag_keyframe+empty_moovfor MP4) or use inherently streamable formats like MPEG-TS, MP3, or FLAC.