PHP调用FFmpeg后台运行后如何获取执行结果及方案选型
咱们一步步来解决你的问题,结合Windows和Linux跨平台的需求,给你实用的方案:
1. proc_open是不是解决初始阻塞问题的最佳方案?
proc_open确实是PHP中处理子进程的优质选择,但不是唯一方案——它的优势在于能精细控制子进程的输入/输出流、环境变量,适合需要对进程做深度管控的场景;如果只是单纯想解除阻塞,Linux下用exec($cmd . ' &')、Windows下用exec('start /B ' . $cmd)也能实现。
但结合你后续需要获取执行状态的需求,proc_open的可控性更强,能更好地封装跨平台的启动逻辑,所以用它来实现后台执行是很合适的。
2. 如何在不阻塞脚本的前提下获取FFmpeg执行结果?
核心思路是异步监控:主脚本启动FFmpeg后立即返回,通过记录进程ID(PID)和退出码到临时文件,后续通过轮询或定时任务检查状态。以下是跨平台的实现代码:
// 定义跨平台配置 $ffmpegPath = PHP_OS_FAMILY === 'Windows' ? 'D:\FFMPEG\bin\ffmpeg.exe' : '/usr/bin/ffmpeg'; $statusFile = __DIR__ . "/ffmpeg_status_{uniqid()}.txt"; $logFile = __DIR__ . "/ffmpeg_log_{uniqid()}.txt"; /** * 启动后台FFmpeg任务,返回状态文件路径 */ function startFFmpegBackground($videoCode, $outputFile, $statusFile, $logFile) { global $ffmpegPath; $m3u8Url = "https://linkplaylist/{$videoCode}.m3u8"; $cmd = []; if (PHP_OS_FAMILY === 'Windows') { // Windows:用start /B后台执行,记录PID和退出码到状态文件 $cmd = sprintf( 'cmd.exe /c start /B "" %s -y -i %s -map 0:1 -map 0:2 -acodec copy -bsf:a aac_adtstoasc -vcodec copy %s 1>>%s 2>&1 & echo %%ERRORLEVEL%% > %s & echo %%PID%% >> %s', escapeshellarg($ffmpegPath), escapeshellarg($m3u8Url), escapeshellarg($outputFile), escapeshellarg($logFile), escapeshellarg($statusFile), escapeshellarg($statusFile) ); } else { // Linux:用nohup后台执行,记录PID和退出码到状态文件 $cmd = sprintf( 'bash -c "nohup %s -y -i %s -map 0:1 -map 0:2 -acodec copy -bsf:a aac_adtstoasc -vcodec copy %s 1>>%s 2>&1 & echo $! > %s && wait $! && echo $? >> %s"', escapeshellarg($ffmpegPath), escapeshellarg($m3u8Url), escapeshellarg($outputFile), escapeshellarg($logFile), escapeshellarg($statusFile), escapeshellarg($statusFile) ); } // 启动进程并立即关闭管道,不阻塞脚本 $proc = proc_open($cmd, [], $pipes); if ($proc) { proc_close($proc); return $statusFile; } return false; } /** * 检查FFmpeg任务状态 */ function checkFFmpegStatus($statusFile) { if (!file_exists($statusFile)) { return ['status' => 'unknown', 'error' => '状态文件丢失']; } $content = trim(file_get_contents($statusFile)); $lines = explode("\n", $content); if (PHP_OS_FAMILY === 'Windows') { $exitCode = isset($lines[0]) ? intval($lines[0]) : null; $pid = isset($lines[1]) ? intval($lines[1]) : null; // 检查进程是否存活 $isRunning = false; if ($pid) { exec("tasklist /FI \"PID eq $pid\"", $output); $isRunning = count($output) > 1; } if (!$isRunning && $exitCode !== null) { return [ 'status' => 'completed', 'exit_code' => $exitCode, 'success' => $exitCode === 0 ]; } else { return ['status' => 'running', 'pid' => $pid]; } } else { $pid = isset($lines[0]) ? intval($lines[0]) : null; $exitCode = isset($lines[1]) ? intval($lines[1]) : null; // 检查进程是否存活 $isRunning = false; if ($pid) { exec("ps -p $pid", $output); $isRunning = count($output) > 1; } if (!$isRunning && $exitCode !== null) { return [ 'status' => 'completed', 'exit_code' => $exitCode, 'success' => $exitCode === 0 ]; } else { return ['status' => 'running', 'pid' => $pid]; } } } // ------------------------------ // 使用示例 // ------------------------------ // 1. 启动后台下载任务 $statusFile = startFFmpegBackground('football', 'football.mp4', $statusFile, $logFile); // 2. 前端可通过AJAX轮询以下接口获取状态 // $status = checkFFmpegStatus($statusFile); // echo json_encode($status);
关键说明:
- 启动任务时,将FFmpeg的PID和执行完成后的退出码写入临时状态文件;
- 主脚本启动进程后立即返回,不会阻塞页面;
- 通过
checkFFmpegStatus函数,可随时查询任务是否在运行、执行是否成功; - 记得定期清理临时状态文件和日志文件,避免磁盘占用;
- 所有路径和参数都用
escapeshellarg转义,避免命令注入风险。
内容的提问来源于stack exchange,提问作者Bruno Andrade
相关产品推荐
相关产品推荐

