ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PHP8怎么配置ffmpeg硬件加速转码

PHP8怎么配置ffmpeg硬件加速转码 前言给视频转码加硬件加速几乎每个做媒体处理的团队都试过。结果通常分成两类一类是“参数加上去了但完全没变快”另一类是“报了一堆看不懂的错”Cannot load libcuda.so.1、No device available for decoder、Failed to initialise VAAPI connectionImpossible to convert between the formats supported by the filter graph 0 input from stream 0:0转码成功但画面全绿、花屏或者播到一半卡住容器里/dev/dri/renderD128权限拒绝花了半天配好实测发现转码时间几乎没变。这些问题几乎都来自同一个误解“硬件加速”在 ffmpeg 里不是一个开关而是解码、滤镜、编码三个互相独立的环节。只打开其中一个另外两个还在 CPU 上跑瓶颈根本没被挪走而三个都打开时又会遇到硬件帧hardware frame在内存与显存之间的搬运问题。本文以 PHP 8 为运行环境示例代码需要 PHP 8.0 及以上讲清楚三件事怎么探测机器上到底有哪些硬件能力、怎么按能力生成正确的 ffmpeg 参数、以及在 Docker 里怎么把设备透传给容器。文中不会给出任何“提速 N 倍”的数字——硬件加速的收益与显卡、驱动、编码器版本、分辨率强相关只有在你自己的机器上实测才有意义本文会给出可自测的脚本。一、三个环节解码、滤镜、编码先把概念拆开后面的所有参数都会变得可解释环节作用典型参数不开启时的后果硬件解码把视频流解码成硬件帧-hwaccel cuda、-hwaccel vaapi、-hwaccel qsv解码仍占 CPU硬件滤镜/缩放在显存里缩放、转换像素格式scale_cuda、scale_vaapi、hwupload/hwdownload需要把帧搬回内存再处理硬件编码用专用单元做编码-c:v h264_nvenc、h264_vaapi、h264_qsv编码仍占 CPU通常是最大瓶颈最常见的错误就是“解码加速了编码没有”-hwaccel cuda只影响解码如果-c:v仍然是libx264整体耗时的绝大部分还在 CPU 上于是“配了个寂寞”。三条探测命令建议在任何优化开始之前先跑一遍# 本机 ffmpeg 支持哪些加速方式 ffmpeg -hide_banner -hwaccels # 有哪些硬件编码器 ffmpeg -hide_banner -encoders | grep -E nvenc|vaapi|qsv|videotoolbox # 有哪些硬件滤镜 ffmpeg -hide_banner -filters | grep -E scale_cuda|scale_vaapi|scale_qsv|hwupload|hwdownload-hwaccels有输出、-encoders里没有对应的硬件编码器说明这份 ffmpeg 构建里没带上编码支持只能换构建跟 PHP 一点关系都没有。二、各平台的设备与参数组合不同硬件平台用的参数完全不一样下面这张表是配置时的对照依据平台设备节点指定硬件设备硬件编码器示例NVIDIANVENC/NVDEC/dev/nvidia*无需显式指定h264_nvenc、hevc_nvencIntel Quick SyncQSV/dev/dri/renderD128-init_hw_device qsv或由驱动自动选择h264_qsv、hevc_qsvIntel/AMDVAAPI/dev/dri/renderD128-vaapi_device /dev/dri/renderD128h264_vaapi、hevc_vaapimacOS系统框架无需指定h264_videotoolboxWindows驱动层无需指定h264_qsv、h264_amf以 NVIDIA 为例一条完整的“解码 缩放 编码”全硬件链路是这样的ffmpeg -hide_banner -y \ -hwaccel cuda -hwaccel_output_format cuda \ -i input.mp4 \ -vf scale_cuda1280:720 \ -c:v h264_nvenc -preset p4 -cq 26 \ -c:a aac -b:a 128k \ output.mp4-hwaccel_output_format cuda这一项很关键它让解码结果留在显存里后续的scale_cuda才能直接在显存中处理。如果不写解码出来的帧会被下载到内存滤镜链又试图和 CUDA 帧对接于是出现那句经典的Impossible to convert between the formats。VAAPI 的写法要注意选项位置ffmpeg -hide_banner -y \ -vaapi_device /dev/dri/renderD128 \ -hwaccel vaapi -hwaccel_output_format vaapi \ -i input.mp4 \ -vf scale_vaapiw1280:h720 \ -c:v h264_vaapi -qp 26 \ output.mp4-vaapi_device是一个“设备级”选项必须写在-i之前。把它放到输出参数里会得到Unknown option或者干脆被当成全局参数忽略最后以上面的连接失败告终。三、硬件加速什么时候有用什么时候没用这一节不给数字只讲原理因为原理能帮你自己判断该不该上硬件。硬件编码器的设计目标是吞吐不是压缩率。专用编码单元并行度极高能在功耗很低的情况下实时编码多路视频但它的码率控制与运动搜索策略远不如 x264/x265 的 slow 档细致。因此在“同码率下要比体积”的场景硬件编码往往是劣势在“同画质下要比速度/功耗/并发路数”的场景它才有优势。帧在内存与显存之间的搬运是隐性成本。一次hwdownload/hwupload就是一次全帧拷贝4K 帧的拷贝量相当可观。这也是为什么“硬件解码 软件滤镜 软件编码”这种混搭常常比纯软件还慢——多了一次往返搬运却没省下最耗时的编码。什么时候值得上硬件加速高分辨率素材的批量转码、需要同时处理多路视频、对延迟敏感的直播转码、以及 CPU 已经很紧张但显卡空闲的机器。什么时候别折腾追求最小体积的归档、低分辨率素材编解码本身就不慢、以及没有专用硬件的服务器。是否真的有收益用下面这种最少依赖的方式自测准备一段 30 秒的样片一份用纯软件参数、一份用硬件参数各跑一次并记录time输出同时比较产物大小和肉眼画质。只有三项都可接受硬件加速才算划算。四、PHP 侧探测能力并生成参数PHP 代码要做的是“按机器能力生成正确的命令”而不是写死一套参数。下面是一个可直接运行的完整示例PHP 8.0php hw_transcode.php input.mp4 output.mp4?php declare(strict_types1); // 运行环境PHP 8.0 final class HwTranscoder { public function __construct( private string $ffmpeg ffmpeg, private string $vaapiDevice /dev/dri/renderD128, ) {} /** return array{code:int,stdout:string,stderr:string} */ public function run(array $args, int $timeoutSeconds 3600): array { $descriptors [0 [pipe, r], 1 [pipe, w], 2 [pipe, w]]; $proc proc_open($args, $descriptors, $pipes); if (!is_resource($proc)) { throw new RuntimeException(proc_open 失败); } fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $stdout ; $stderr ; $deadline microtime(true) $timeoutSeconds; while (true) { if (microtime(true) $deadline) { proc_terminate($proc, 9); throw new RuntimeException(转码超时); } $read []; if (!feof($pipes[1])) { $read[] $pipes[1]; } if (!feof($pipes[2])) { $read[] $pipes[2]; } if ($read []) { break; } $w []; $e []; if (stream_select($read, $w, $e, 1) false) { break; } foreach ($read as $stream) { $chunk fread($stream, 65536); if ($chunk false || $chunk ) { continue; } if ($stream $pipes[1]) { $stdout . $chunk; } else { $stderr . $chunk; } } } fclose($pipes[1]); fclose($pipes[2]); return [code proc_close($proc), stdout $stdout, stderr $stderr]; } /** 探测本机 ffmpeg 的硬件能力 */ public function capabilities(): array { $hwaccels $this-run([$this-ffmpeg, -hide_banner, -hwaccels], 20)[stdout]; $encoders $this-run([$this-ffmpeg, -hide_banner, -encoders], 20)[stdout]; $has static fn (string $haystack, string $needle): bool $needle ! str_contains($haystack, $needle); return [ cuda $has($hwaccels, cuda) $has($encoders, h264_nvenc), vaapi $has($hwaccels, vaapi) $has($encoders, h264_vaapi), qsv $has($hwaccels, qsv) $has($encoders, h264_qsv), device is_readable($this-vaapiDevice), ]; } /** * 按可用能力生成参数都不可用时退回软件编码 * return array{args:string[], mode:string} */ public function buildArgs(string $input, string $output, string $quality balanced): array { $caps $this-capabilities(); $args [$this-ffmpeg, -hide_banner, -nostdin, -y]; if ($caps[cuda]) { $mode nvenc; $args [...$args, -hwaccel, cuda, -hwaccel_output_format, cuda, -i, $input, -vf, scale_cuda1280:720, -c:v, h264_nvenc, -preset, p4, -cq, 26]; } elseif ($caps[vaapi] $caps[device]) { $mode vaapi; $args [...$args, -vaapi_device, $this-vaapiDevice, // 必须在 -i 之前 -hwaccel, vaapi, -hwaccel_output_format, vaapi, -i, $input, -vf, scale_vaapiw1280:h720, -c:v, h264_vaapi, -qp, 26]; } elseif ($caps[qsv]) { $mode qsv; $args [...$args, -hwaccel, qsv, -i, $input, -vf, scale_qsv1280:720, -c:v, h264_qsv, -global_quality, 26]; } else { $mode software; $args [...$args, -i, $input, -vf, scale1280:720, -c:v, libx264, -preset, medium, -crf, 23]; } // 硬件编码器普遍更依赖码率上限需要“高压缩率”时应改用软件 x265 if ($quality smallest $mode ! software) { $mode . note; } return [ args [...$args, -c:a, aac, -b:a, 128k, -loglevel, warning, -stats, $output], mode $mode, ]; } } // ---- 使用 ---- $input $argv[1] ?? null; $output $argv[2] ?? null; if ($input null || $output null) { fwrite(STDERR, 用法: php hw_transcode.php 输入 输出\n); exit(1); } $tr new HwTranscoder(); printf(能力探测: %s\n, json_encode($tr-capabilities(), JSON_UNESCAPED_UNICODE)); $job $tr-buildArgs($input, $output); printf(使用模式: %s\n命令: %s\n, $job[mode], implode( , $job[args])); $t0 microtime(true); $result $tr-run($job[args], 7200); $elapsed microtime(true) - $t0; if ($result[code] ! 0) { fwrite(STDERR, 转码失败: . substr(trim($result[stderr]), -500) . \n); exit(2); } printf(完成耗时 %.1f 秒\n, $elapsed); // ffmpeg 的告警写在 stderr画面异常通常会在这里留下线索 if (trim($result[stderr]) ! ) { fwrite(STDERR, ffmpeg 告警:\n . trim($result[stderr]) . \n); }注意capabilities()的判定逻辑-hwaccels里出现cuda并且编码器列表里有h264_nvenc才算可用。只满足一半时强行加参数就会得到本文开头那些初始化失败的错误。把探测结果打印出来比猜测快得多。运行输出形如能力探测: {cuda:false,vaapi:true,qsv:false,device:true} 使用模式: vaapi 完成耗时 实测值 秒这个耗时数字就是你自己机器上最有价值的对比数据把buildArgs()里的硬件分支临时换成软件分支再跑一次同样的输入两次数值相比才是真实的收益。五、Docker 里的设备透传容器里跑硬件加速关键不是 ffmpeg而是设备节点有没有进容器# VAAPI / QSV映射 DRI 设备 docker run --rm \ --device /dev/dri:/dev/dri \ --group-add $(getent group render | cut -d: -f3) \ -v $PWD:/app -w /app \ your-php-image php hw_transcode.php input.mp4 output.mp4 # NVIDIA需要宿主机装好驱动与 nvidia-container-toolkit docker run --rm --gpus all your-php-image ffmpeg -hide_banner -hwaccels两个细节一是只映射/dev/dri还不够容器内进程要对renderD128有读写权限通常需要补上 render 组的 GID--group-add二是 NVIDIA 场景下宿主机驱动版本与 ffmpeg 编译时使用的头文件版本需要匹配否则容器里的报错会是Cannot load libcuda.so.1或者驱动 API 版本不兼容——这类问题必须先在宿主机上用裸 ffmpeg 验证通过再进容器排查。常见坑点1. 只加-hwaccel编码器还是软件❌-hwaccel cuda -c:v libx264以为整体加速了 ✅ 同时指定硬件编码器-c:v h264_nvenc否则瓶颈仍在 CPU2. 少了-hwaccel_output_format❌-hwaccel cuda -vf scale_cuda1280:720报格式无法转换 ✅ 加上-hwaccel_output_format cuda让帧留在显存或显式加hwdownload再走软件滤镜3.-vaapi_device位置写错❌ 把-vaapi_device放在-i之后或输出参数区 ✅ 它必须在-i之前属于设备级选项4. 容器没透传设备或权限不足❌ 容器里直接跑报Failed to initialise VAAPI connection✅--device /dev/dri:/dev/dri必要时--group-add加上 render 组 GID5. 期望硬件编码同时做到“最小体积”❌ 用h264_nvenc追求比 x264 slow 更小的文件 ✅ 体积优先就用软件 x265 慢档硬件编码的强项是吞吐与并发6. 不校验产物❌ 退出码为 0 就认为成功结果视频花屏 ✅ 检查-loglevel warning输出的stderr并对产物抽帧或校验时长7. 在容器里用-gpu之类的想当然参数❌ 凭印象加-gpu 0这种选项 ✅ 用ffmpeg -h encoderh264_nvenc查看该编码器真正支持的选项名8. 忽略驱动与构建版本匹配❌ 宿主机驱动很旧却用新版本头文件编译的 ffmpeg ✅ 先在宿主机裸环境跑通 1 秒样片再进容器缩小排查范围总结环节配置要点常见错误能力探测ffmpeg -hwaccels-encoders双条件判断只看其中一个就下结论解码-hwaccel xxx-hwaccel_output_format漏掉输出格式滤镜链断裂滤镜scale_cuda/scale_vaapi与硬件帧配套硬件帧混软件滤镜多一次拷贝编码明确写出h264_nvenc/h264_vaapi/h264_qsv只加速解码编码仍是 libx264设备容器--device 组权限NVIDIA 用--gpus all设备没透传容器内初始化失败选型吞吐优先用硬件体积优先用软件用错场景白折腾验证同素材跑两种配置对比耗时、体积、画质凭感觉判断“变快了”硬件加速的正确定位是“把特定环节的负载从 CPU 挪到专用单元”而不是“一键加速”。配之前先探测能力配之后先验证产物再拿同一条素材自己跑一遍对比——这三个步骤做完你得到的结论一定比任何一篇参数教程都更适合自己的机器。
返回列表