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

1

9

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

Leboeuf answered 28/2, 2016 at 13:16 Comment(0)
S
3

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;
Salta answered 28/2, 2016 at 20:38 Comment(1)
It's strange behaviour indeed. I was also able to get it to work using a file in the $descriptorspec for proc_open as well. I cannot understand why my exact some code is actually working perfectly fine now either. It would be really unideal to write to a temp file in my case because we're going to encrypt the video file, decrypt it on the fly and send it to VLC's stdin. It won't necessarily be a PHP file for distribution but as I said just a proof of concept.Appose

© 2022 - 2024 — McMap. All rights reserved.