構建視頻上傳轉碼系統需解決:大檔案上傳限制、FFmpeg 靜態編譯支援 H.264、PHP 安全調用外部程式、非同步處理架構。本文提供從環境搭建到生產部署的完整方案。
; php.ini 關鍵參數
file_uploads = On
upload_tmp_dir = /tmp/php_uploads ; 獨立目錄,權限 700 www-data
upload_max_filesize = 2G ; 單檔上限
post_max_size = 2G ; POST 總上限 > upload_max_filesize
max_execution_time = 7200 ; 2小時,轉碼極耗時
max_input_time = 7200 ; 接收上傳資料時間
memory_limit = 512M ; PHP 記憶體,轉碼由外部程式處理不佔此限
; 關閉輸出緩衝避免大檔案記憶體暴增
output_buffering = Off
Nginx 同步調整:
# nginx.conf http 區塊
client_max_body_size 2G;
proxy_read_timeout 7200s;
proxy_send_timeout 7200s;
# CentOS/RHEL
yum install -y gcc gcc-c++ make automake autoconf libtool \
nasm yasm pkgconfig zlib-devel bzip2-devel \
libX11-devel freetype-devel fontconfig-devel
# Ubuntu/Debian
apt-get update && apt-get install -y build-essential pkg-config \
nasm yasm libz-dev libbz2-dev libx11-dev libfreetype6-dev libfontconfig1-dev
cd /usr/local/src
git clone --branch stable --depth 1 https://code.videolan.org/videolan/x264.git
cd x264
./configure --prefix=/usr/local --enable-shared --enable-pic --disable-cli
make -j$(nproc)
make install
ldconfig
cd /usr/local/src
git clone --depth 1 https://git.ffmpeg.org/ffmpeg.git
cd ffmpeg
./configure \
--prefix=/usr/local \
--enable-gpl \
--enable-nonfree \
--enable-libx264 \
--enable-shared \
--disable-static \
--disable-doc \
--disable-debug \
--enable-pthreads \
--enable-libfreetype \
--enable-libfontconfig \
--extra-cflags="-I/usr/local/include" \
--extra-ldflags="-L/usr/local/lib" \
--extra-libs="-lpthread -lm -ldl"
make -j$(nproc)
make install
ldconfig
# 驗證
ffmpeg -version
# 輸出應包含 --enable-libx264 --enable-gpl
ffmpeg -encoders | grep libx264
# 應顯示 libx264 可用
; php.ini
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
; ↑ 生產環境建議全禁用,改用 Symfony Process 或獨立 Worker 進程
更安全架構:PHP 僅寫入任務隊列(Redis/RabbitMQ),獨立 Worker 進程(PHP CLI/Go/Python)消費隊列調用 FFmpeg,完全隔離 Web 進程。
<?php
function transcodeToMp4(string $input, string $output): bool {
// 輸入驗證:防止命令注入
$input = escapeshellarg(realpath($input));
$output = escapeshellarg($output);
// FFmpeg 參數詳解:
// -y 覆蓋輸出
// -i 輸入檔
// -c:v libx264 視頻編碼 H.264
// -preset medium 壓縮速度/品質平衡
// -crf 23 品質常數 (18-28 越小越好)
// -c:a aac 音頻編碼 AAC
// -b:a 128k 音頻碼率
// -movflags +faststart 移動 moov atom 到開頭,支援網頁邊下邊播
$cmd = "ffmpeg -y -i {$input} -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 128k -movflags +faststart {$output} 2>&1";
$outputLines = [];
$returnCode = 0;
exec($cmd, $outputLines, $returnCode);
if ($returnCode !== 0) {
error_log("FFmpeg 轉碼失敗: " . implode("\n", $outputLines));
return false;
}
return true;
}
// 使用
$ok = transcodeToMp4('/uploads/raw/video.mov', '/uploads/mp4/video.mp4');
?>
<?php
// Web 端:接收上傳 → 存檔 → 寫入 Redis 隊列 → 立即返回任務 ID
$taskId = bin2hex(random_bytes(16));
$task = [
'id' => $taskId,
'input' => $savedPath,
'output' => "/processed/{$taskId}.mp4",
'status' => 'pending',
'created_at' => time(),
];
$redis->lPush('video_transcode_queue', json_encode($task));
echo json_encode(['task_id' => $taskId, 'status' => 'pending']);
// Worker 端 (cli/worker.php):長期運行進程消費隊列
while (true) {
$json = $redis->rPop('video_transcode_queue');
if (!$json) { sleep(1); continue; }
$task = json_decode($json, true);
$redis->hSet("task:{$task['id']}", 'status', 'processing');
$ok = transcodeToMp4($task['input'], $task['output']);
$redis->hSet("task:{$task['id']}", [
'status' => $ok ? 'done' : 'failed',
'finished_at' => time(),
]);
// 可觸發 Webhook/推送通知前端
}
?>
PHPSESSID 參數,PHP 端 session_id($_POST['PHPSESSID']); session_start(); 恢復。| 問題 | 解決 |
|---|---|
| 轉碼極慢/CPU 100% | 調整 -preset ultrafast/superfast 犧牲壓縮率換速度;限制 FFmpeg 執行緒 -threads 2 |
| 輸出 MP4 無法邊下邊播 | 必須加 -movflags +faststart 或轉碼後執行 qt-faststart |
| 音視頻不同步 | 加 -async 1 或 -vsync 1;檢查源檔案時間基 |
| 大檔案轉碼中斷 | Worker 進程監控磁碟空間、記憶體;支援斷點續傳需分片轉碼(高階) |
| 權限錯誤 Permission denied | 確保 www-data 對上傳/輸出目錄有 rwx;FFmpeg 二進制可執行 |
遵循「大檔上傳配置 → FFmpeg 靜態編譯 H.264 → 隊列非同步轉碼 → 安全調用隔離」四步驟,即可構建穩定、可擴展的視頻轉碼服務。