Interfacing PHP with FFmpeg via Standard IO
Interfacing PHP with the FFmpeg binary via standard input, output,
and error streams (I/O) allows you to process media files dynamically in
memory without writing temporary files to disk. This article provides a
straightforward guide on using PHP’s proc_open function to
pass video data directly to FFmpeg via stdin, retrieve the
processed output through stdout, and capture diagnostic
logs using stderr.
1. Understanding the FFmpeg Pipeline
To redirect standard I/O streams, you must instruct FFmpeg to read from and write to the system pipes instead of physical files.
- Input: Use
pipe:0or-as the input filename to tell FFmpeg to read from standard input (stdin). - Output: Use
pipe:1or-as the output filename to write to standard output (stdout). - Format: When streaming via stdout, you must
explicitly define a container format using the
-fflag (e.g.,-f mp4,-f mpegts), as FFmpeg cannot guess the format from a pipe’s file extension. For MP4 streams, you must also use the-movflags frag_keyframe+empty_moovoption to make the output streamable.
2. Setting Up
proc_open in PHP
PHP’s proc_open execution function allows fine-grained
control over execution pipes. You define a descriptor specification
array to map PHP file pointers to FFmpeg’s standard streams.
$descriptors = [
0 => ['pipe', 'r'], // stdin (write to FFmpeg)
1 => ['pipe', 'w'], // stdout (read from FFmpeg)
2 => ['pipe', 'w'] // stderr (read FFmpeg errors/logs)
];3. Implementation Code
Below is a complete, non-blocking PHP script that reads a source video file, streams it into FFmpeg to convert it, and captures the output.
<?php
// Define the FFmpeg command
// This example converts an incoming stream to an MP4 with streaming-compatible flags
$cmd = 'ffmpeg -i pipe:0 -f mp4 -movflags frag_keyframe+empty_moov pipe:1';
$descriptors = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'] // stderr
];
$process = proc_open($cmd, $descriptors, $pipes);
if (is_resource($process)) {
// Set all stream pipes to non-blocking mode to prevent deadlocks
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
// Load sample input data (e.g., a local video file)
$inputData = file_get_contents('input.avi');
$inputLength = strlen($inputData);
$bytesWritten = 0;
$outputData = '';
$errorData = '';
// Loop until the process finishes and all buffers are cleared
while (true) {
$read = [$pipes[1], $pipes[2]];
$write = ($bytesWritten < $inputLength) ? [$pipes[0]] : null;
$except = null;
// Wait for activity on the streams
if (stream_select($read, $write, $except, 1) === false) {
break;
}
// Write chunk of input data to FFmpeg's stdin
if ($write && in_array($pipes[0], $write)) {
$chunk = substr($inputData, $bytesWritten, 65536);
$written = fwrite($pipes[0], $chunk);
if ($written > 0) {
$bytesWritten += $written;
}
// Close stdin once all input data has been sent
if ($bytesWritten >= $inputLength) {
fclose($pipes[0]);
}
}
// Read chunk of output data from FFmpeg's stdout
if (in_array($pipes[1], $read)) {
$chunk = fread($pipes[1], 65536);
if ($chunk !== false) {
$outputData .= $chunk;
}
}
// Read chunk of logs/errors from FFmpeg's stderr
if (in_array($pipes[2], $read)) {
$chunk = fread($pipes[2], 65536);
if ($chunk !== false) {
$errorData .= $chunk;
}
}
// Terminate loop if the process has stopped
$status = proc_get_status($process);
if (!$status['running'] && feof($pipes[1]) && feof($pipes[2])) {
break;
}
}
// Clean up remaining open pipes and close the process
if (is_resource($pipes[0])) fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($process);
if ($exitCode === 0) {
// Save the processed standard output data to a file
file_put_contents('output.mp4', $outputData);
echo "Conversion successful!";
} else {
echo "Error during conversion (Exit code: $exitCode):\n";
echo $errorData;
}
}4. Key Considerations
- Deadlock Prevention: Do not write all data to
stdinbefore reading fromstdout. FFmpeg’s internal output buffers will fill up, causing the process to block indefinitely. Always usestream_selectto read and write concurrently. - Memory Limits: Storing entire video files in PHP
variables (
$inputDataand$outputData) consumes substantial system memory. For large files, stream the data in chunks from a file pointer or socket instead of storing the entire payload in RAM. - Binary Paths: Ensure the
ffmpegbinary is in your system’s PATH environmental variable, or provide the absolute path (e.g.,/usr/bin/ffmpegorC:\ffmpeg\bin\ffmpeg.exe) in the command string.