文章目录▼CloseOpen
- 第一步:先把FFmpeg和PHP的“桥”搭好——环境配置别再踩坑
- 第二步:PHP调用FFmpeg的核心——写对命令+处理异步,再也不超时
- 先记牢FFmpeg的切片命令
- PHP怎么调用这个命令?
- 大视频切片不超时:异步处理是关键
- 方法1:命令后面加&,后台运行
- 方法2:用队列系统,更可靠
- 切片后的细节:前端能播放才是关键
- 为什么PHP调用FFmpeg时提示“ffmpeg不是内部或外部命令”?
- 大视频切片时PHP脚本老是超时怎么办?
- 切片生成的.m3u8文件前端无法播放,可能是什么原因?
- PHP调用FFmpeg时有哪些安全注意事项?
-i input.mp4
:指定输入视频文件(比如用户上传的文件);-c:v libx264
:视频编码器用H.264(最常用,兼容性好);-c:a aac
:音频编码器用AAC(浏览器支持);-hls_time 10
:每片10秒(可以改5秒,切片越小加载越快,但文件越多);-hls_list_size 0
:保留所有切片(不删旧文件,适合长期存储);output.m3u8
:输出的索引文件,前端要访问的就是它。
我去年帮朋友的在线教育平台做过这套流程,从环境配置到代码落地,踩了不少坑——比如Linux装FFmpeg时版本太老报错,Windows没加环境变量导致PHP找不到命令,调用时没处理异步让用户等10分钟……现在整理成能直接抄的步骤,连我那只会写CRUD的实习生都跟着做出来了,今天就掰开揉碎给你讲。
第一步:先把FFmpeg和PHP的“桥”搭好——环境配置别再踩坑
视频切片的前提是FFmpeg能正常运行,再加上PHP能调用它。这一步看着简单,但我见过80%的新手栽在这里——要么FFmpeg装错版本,要么PHP权限不够。
先讲FFmpeg安装,不同系统差别挺大,我整理了个对比表(直接抄就行):
系统 | 安装方式 | 关键注意点 |
---|---|---|
Windows | 下载静态编译版+环境变量 | 选ffmpeg-release-essentials.zip ,解压后把bin 目录加进系统Path |
Linux(CentOS) | 源码编译/包管理器 | 别用yum 装旧版本,需先装x264-devel 等依赖 |
Mac | brew install ffmpeg |
加sudo 解决权限问题,需先装Homebrew |
我当初在Linux上踩的坑特经典:一开始用yum install ffmpeg
装了个3.4版本,结果切片时老是报错“不支持的H.264编码”——后来查FFmpeg官网才知道,旧版本默认没开libx264
编码器,得源码编译。编译时要先装依赖:yum install -y gcc yasm x264-devel libmp3lame-devel
,再解压FFmpeg源码,运行./configure enable-gpl enable-libx264 enable-libmp3lame
,最后make -j4
(4核CPU加速)、make install
,这才解决问题。
Windows更坑:朋友一开始下载了FFmpeg的动态编译版,解压后没加环境变量,PHP调用时直接报“ffmpeg不是内部或外部命令”——调了三小时代码,最后才发现是环境变量没配置。记住:Windows一定要把FFmpeg的bin
目录(比如D:ffmpegbin
)加到系统Path里,然后重启命令行和PHP服务!
PHP这边还要注意开启exec函数——很多服务器默认禁用了exec
、shell_exec
这些执行系统命令的函数,因为安全问题。打开php.ini
,找到disable_functions
这行,把里面的exec
、shell_exec
删掉,然后重启PHP-FPM(Nginx用户)或Apache。
但要强调:别用root用户运行PHP!朋友平台一开始用root用户跑PHP-FPM,结果被黑客上传了个恶意脚本,调用FFmpeg删了一堆文件——后来改成www-data
用户,给FFmpeg执行权限(chmod +x /usr/local/bin/ffmpeg
),这样就算被攻击,权限也有限,安全多了。
第二步:PHP调用FFmpeg的核心——写对命令+处理异步,再也不超时
环境搭好后,接下来就是怎么用PHP让FFmpeg干活。先讲清楚视频切片的原理:比如常用的HLS切片,就是把大视频拆成10秒左右的.ts
小文件,再生成一个.m3u8
索引文件——前端只要加载.m3u8
,就能按顺序播放.ts
文件,实现“秒开”效果。
先记牢FFmpeg的切片命令
FFmpeg切片HLS的基础命令长这样:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -hls_time 10 -hls_list_size 0 output.m3u8
我用大白话解释每个参数:
比如你要切一个test.mp4
视频,生成的output.m3u8
会包含所有.ts
文件的路径,像这样:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.000000,
output0.ts
#EXTINF:10.000000,
output1.ts
...
PHP怎么调用这个命令?
用exec
函数就行,但要注意捕获错误信息——我之前用exec
时没加2>&1
,结果报错信息全没出来,排查了半天。正确的写法是:
// 用户上传的视频路径(假设存在$_FILES['video'])
$inputPath = $_FILES['video']['tmp_name'];
// 切片后的输出目录(要放在Web根目录,比如public/hls/)
$outputDir = __DIR__ . '/public/hls/';
// 确保目录存在
if (!is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
// 输出的m3u8文件名(用时间戳避免重复)
$m3u8Name = time() . '.m3u8';
$outputPath = $outputDir . $m3u8Name;
// 构造FFmpeg命令
$command = "ffmpeg -i {$inputPath} -c:v libx264 -c:a aac -hls_time 10 -hls_list_size 0 {$outputPath} 2>&1";
// 调用FFmpeg并捕获输出
exec($command, $outputLines, $status);
// 处理结果
if ($status !== 0) {
// 执行失败,打印错误信息
echo "切片失败:" . implode("n", $outputLines);
} else {
// 成功,返回m3u8的URL(前端用这个播放)
$m3u8Url = 'http://你的域名/hls/' . $m3u8Name;
echo "切片成功,播放地址:{$m3u8Url}";
}
解释下:
2>&1
:把标准错误输出(stderr)重定向到标准输出(stdout),这样$outputLines
里能拿到所有错误信息——比如输入文件不存在会报“ No such file or directory”,编码器没装对会报“ Unsupported codec”,一看就懂。 $status
:返回值0代表成功,非0代表失败——这是判断切片是否完成的关键。 大视频切片不超时:异步处理是关键
要是你切的是1G以上的大视频,同步调用FFmpeg会让PHP脚本超时——朋友平台一开始就是这样,用户上传1G视频后,页面等了10分钟还没反应,直接504错误。
解决办法有两个:后台运行或队列处理。
方法1:命令后面加&,后台运行
把命令改成这样:
$command .= ' &';
比如:
$command = "ffmpeg -i {$inputPath} ... {$outputPath} > /path/to/hls.log 2>&1 &";
&
会让FFmpeg在后台运行,exec立即返回,不用等切片完成。但要注意:
> /path/to/hls.log
),方便排查错误; ps aux | grep ffmpeg
看进程是否在运行,或者写个脚本定时检查日志。 方法2:用队列系统,更可靠
后台运行虽然简单,但不好管理——比如进程挂了没人知道,或者多个任务同时运行占满CPU。朋友平台后来用了Redis队列,流程是:
video_slice_queue
队列里; swoole
,或直接写个循环脚本),每10秒从队列里取一个任务; Worker脚本的简单示例:
// 连接Redis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while (true) {
// 从队列左边取任务(阻塞式,直到有任务)
$task = $redis->lPop('video_slice_queue');
if (!$task) {
// 没任务,等10秒再查
sleep(10);
continue;
}
// 解析任务(假设任务是JSON字符串)
$taskData = json_decode($task, true);
$inputPath = $taskData['input_path'];
$outputPath = $taskData['output_path'];
$userId = $taskData['user_id'];
// 构造FFmpeg命令
$command = "ffmpeg -i {$inputPath} -c:v libx264 -c:a aac -hls_time 10 -hls_list_size 0 {$outputPath} 2>&1";
// 执行命令
exec($command, $outputLines, $status);
// 更新数据库状态
$pdo = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
$statusText = $status === 0 ? '完成' '失败';
$sql = "UPDATE video SET status = ?, error = ? WHERE user_id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$statusText, implode("n", $outputLines), $userId]);
echo "处理任务完成,用户ID:{$userId},状态:{$statusText}n";
}
这样处理的好处是:任务可监控、可重试——比如Worker挂了,重启后能继续处理队列里的任务;要是某个任务失败,还能把它重新扔回队列重试。
切片后的细节:前端能播放才是关键
切片完成后,还要注意文件路径和权限:
.m3u8
和.ts
文件要放在Web根目录下(比如public/hls/
),这样前端才能通过URL访问(比如http://yourdomain.com/hls/1620000000.m3u8
); .ts
文件的权限要设为644
(chmod 644 *.ts
),不然浏览器会报403错误; .m3u8
里的.ts
路径要对——比如你的.m3u8
放在hls
目录下,里面的.ts
路径应该是相对路径(比如16200000000.ts
),别写绝对路径,不然前端会404。 我把这些步骤整理成了一个demo,里面有完整的PHP上传+切片脚本,需要的话可以找我要。你要是按这个方法试了,不管成没成,都欢迎回来留个言——要是成了,我替你高兴;要是没成,把错误信息贴出来,我帮你看看哪里出问题。毕竟踩过的坑,不想让你再踩一遍~
先说最关键的一点——绝对别用root用户运行PHP服务!我去年帮朋友的在线教育平台踩过这个大坑:他们一开始图方便,用root用户跑PHP-FPM,结果有个黑客上传了个伪装成视频的恶意脚本,调用FFmpeg删了半个服务器的课程视频。后来换成www-data用户(Nginx默认的低权限用户),就算再被攻击,权限也只限于网站的public目录,不会波及整个系统。你想啊,root是系统“超级管理员”,手里攥着所有权限,PHP脚本要是被劫持,等于把整个服务器的“大门钥匙”拱手让人,这种险真的不能冒。
再说说PHP的系统函数和FFmpeg的权限——得“掐着腰”管严点。很多服务器默认禁用了exec、shell_exec这些执行系统命令的函数,因为安全,但咱们调用FFmpeg必须用这俩——那你就把其他没用的函数“砍”了,比如system、passthru、proc_open,只留exec和shell_exec。这样就算有人想搞事,能用的“工具”也少了一半。还有FFmpeg的执行权限,别嫌麻烦要给对:Linux下用chmod +x /usr/local/bin/ffmpeg
(给执行权限),但千万别给777(谁都能改)——要是FFmpeg的二进制文件被篡改,那调用它的PHP脚本等于“带毒运行”,后果比漏个洞还严重。
最后得防着用户给你“埋雷”——一定要过滤用户上传的文件路径!比如说,有人故意传个叫“video; rm -rf / .mp4”的文件,要是你直接把这个文件名塞进FFmpeg命令里,等于帮黑客执行了“删除整个系统”的命令。我朋友之前没做过滤,用户传了个带分号的文件名,结果FFmpeg执行时顺带跑了个ls -l
,虽然没丢数据,但吓出一身冷汗。后来他们的解决办法特实在:文件名只留字母、数字、下划线和点,其他字符(比如分号、引号、空格、斜杠)全替换成下划线。你想,就算用户想搞事,特殊字符都被“无害化”了,命令注入的路子自然就被堵死了。
还有个容易忽略的点——别把FFmpeg的输出日志往网站目录扔!我见过有人把FFmpeg的日志存在public目录下,结果日志里泄露了服务器路径、视频原文件位置这些敏感信息。最好把日志存在非Web访问目录,比如/var/log/ffmpeg/,权限设为600(只有所有者能读),这样就算日志被爬,也拿不到有用的东西。
为什么PHP调用FFmpeg时提示“ffmpeg不是内部或外部命令”?
这种情况大多是FFmpeg的环境变量未配置或配置错误。Windows系统需将FFmpeg的bin目录(如D:ffmpegbin)添加到系统Path中,然后重启PHP服务;Linux/Mac系统需确认FFmpeg安装路径(如/usr/local/bin/ffmpeg)已加入系统PATH,或在PHP代码中直接使用FFmpeg的绝对路径(如exec(‘/usr/local/bin/ffmpeg -i …’))。
大视频切片时PHP脚本老是超时怎么办?
大视频同步处理会导致PHP超时, 用异步方式解决:一是在FFmpeg命令末尾加&(如ffmpeg -i input.mp4 … &),让进程后台运行;二是用队列系统(如Redis队列),将切片任务存入队列,再用Worker进程后台处理,避免前端长时间等待。
切片生成的.m3u8文件前端无法播放,可能是什么原因?
先检查.m3u8文件内的.ts路径是否正确(应为相对路径,如output0.ts,而非绝对路径);再确认.ts文件的权限是否为644(确保浏览器能正常访问);最后检查FFmpeg编码参数,需使用libx264(视频)和aac(音频)编码器,避免使用浏览器不支持的编码格式。
PHP调用FFmpeg时有哪些安全注意事项?
不要用root用户运行PHP服务, 用www-data或nginx等低权限用户;禁用不必要的系统函数(仅保留exec、shell_exec用于调用FFmpeg);限制FFmpeg的执行权限(如chmod +x /usr/local/bin/ffmpeg);对用户上传的视频文件路径进行过滤,避免注入恶意命令(如将文件名中的特殊字符替换为下划线)。