Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does PHP hang after writing 4096 bytes to a process started with proc_open?

For anyone wondering, after leaving it all for a couple hours it now works perfectly.

I'm trying to pass a video file to VLC using PHP as a proof of concept for an upcoming project proposal for someone.

I've managed to show it works by creating a file < 4KB (Gray for 10 seconds) and testing my script but I'm curious as to the reason why this is happening in the first place.

Here's an example script to see what I mean:

$filepath = 'Path/to/your/video';
$vlcpath = 'Path/to/your/VLC executable';

$descriptorspec = array(
    0 => array("pipe", "r"),  // stdin
    1 => array("pipe", "w"),  // stdout
    2 => array("pipe", "w")   // stderr
);

$vlc = proc_open($vlcpath . ' -', $descriptorspec, $pipes, null, null, ['bypass_shell' => true]);

$file = fopen($filepath, 'r');
stream_copy_to_stream($file, $pipes[0]);
fclose($file);
proc_close($vlc);

I'm on Windows 10 and using PHP 5.5.31. I've seen a few bug reports on the PHP site about this kind of thing but they suggest the latest version has fixed it. I don't quite understand the concepts of blocking a stream but I've already tried PHP v7.0.3 to no avail.

I'm running this script using the command line: php file.php

like image 918
Mitchell O'Sullivan Avatar asked Feb 28 '16 13:02

Mitchell O'Sullivan


1 Answers

I ran into the exact same issue trying to do WAV to MP3 conversion using LAME on Windows and was unable to find a workable solution.

I tried dozens of things including blocking/non-blocking writes, writing small (< 1k) chunks of data, sleeping and trying to write but it never was able to write all data. About as much as I could ever write before it failing was around 40kb (failure being fwrite would always return 0 and never write more data to the stream, no matter how long I waited; regardless of the sizes of the chunks written before. I even tried waiting seconds between writes and they would always succeed to about 30-40kb and never write more).

Ultimately I gave up and luckily LAME could read input from a file instead of STDIN, so I just opted to write the data to a temp file, call LAME, and remove the temp file.

Here's the relevant code:

// file descriptors for reading and writing to the Lame process
$descriptors = array(
        0 => array('pipe', 'r'), // stdin
        1 => array('pipe', 'w'), // stdout
        2 => array('pipe', 'a'), // stderr
);
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    // workaround for Windows conversion
    // writing to STDIN seems to hang indefinitely after writing approximately 0xC400 bytes
    $wavinput = tempnam(sys_get_temp_dir(), 'wav');
    if (!$wavinput) {
        throw new Exception('Failed to create temporary file for WAV to MP3 conversion');
    }
    file_put_contents($wavinput, $data);
    $size = 0;
} else {
    $wavinput = '-'; // stdin
}
// Mono, variable bit rate, 32 kHz sampling rate, read WAV from stdin, write MP3 to stdout
$cmd  = sprintf("%s -m m -v -b 32 %s -", self::$lame_binary_path, $wavinput);
$proc = proc_open($cmd, $descriptors, $pipes);
if (!is_resource($proc)) {
    throw new Exception('Failed to open process for MP3 encoding');
}
stream_set_blocking($pipes[0], 0); // set stdin to be non-blocking
for ($written = 0; $written < $size; $written += $len) {
    // write to stdin until all WAV data is written
    $len = fwrite($pipes[0], substr($data, $written, 0x20000));
    if ($len === 0) {
        // fwrite wrote no data, make sure process is still alive, otherwise wait for it to process
        $status = proc_get_status($proc);
        if ($status['running'] === false) break;
        usleep(25000);
    } else if ($written < $size) {
        // couldn't write all data, small pause and try again
        usleep(10000);
    } else if ($len === false) {
        // fwrite failed, should not happen
        break;
    }
}
fclose($pipes[0]);
$data = stream_get_contents($pipes[1]);
$err  = trim(stream_get_contents($pipes[2]));
fclose($pipes[1]);
fclose($pipes[2]);
$return = proc_close($proc);
if ($wavinput != '-') unlink($wavinput); // delete temp file on Windows
if ($return !== 0) {
    throw new Exception("Failed to convert WAV to MP3.  Shell returned ({$return}): {$err}");
} else if ($written < $size) {
    throw new Exception('Failed to convert WAV to MP3.  Failed to write all data to encoder');
}
return $data;
like image 170
drew010 Avatar answered Oct 05 '22 05:10

drew010