必须异步处理:上传接口仅校验、存原始文件、写任务记录;转码由独立 Worker 执行,命令须含 -ac 1 -ar 16000 -acodec pcm_s16le,并用 ffprobe 预检格式、which ffmpeg 动态获取路径,避免阻塞与静默失败。

上传完立刻执行转码,但别用 exec() 硬等
PHP 接收音频文件后直接调用 ffmpeg 转码,看似简单,实际容易卡住 Web 请求或丢错误。关键不是“能不能转”,而是“谁来转、怎么知道转完了、失败了怎么反馈”。exec() 在默认配置下会阻塞整个 HTTP 响应,而一个 5 分钟的 MP3 转 WAV 可能超时(max_execution_time=30 是常见值),导致前端以为上传失败。
- 必须把转码扔进异步队列:用 Redis + Laravel Horizon、或最简方案——写入数据库
audio_tasks表并起个独立 PHP Worker 脚本轮询 - 上传接口只做三件事:校验文件类型(
$_FILES['audio']['type']和finfo_file()双检)、存原始文件到public/uploads/raw/、插入任务记录(含status='pending') - 不要在上传控制器里调
shell_exec("ffmpeg -i ...")—— 即使加了@抑制错误,stderr 也根本看不到,出错只能查空日志
ffmpeg 转码命令必须带 -ac 1 -ar 16000 -acodec pcm_s16le
这不是可选项。主流语音识别 API(腾讯云 ASR、阿里云 ASR、Whisper)都明确要求输入是单声道、16kHz 采样率、PCM 编码的 WAV。漏掉任一参数,大概率返回 Invalid audio encoding 或识别准确率暴跌。
-
-ac 1:强制转单声道,多声道(如立体声 MP3)会导致 ASR 模型崩溃或只识别左声道 -
-ar 16000:采样率必须是 16000,8kHz 或 44.1kHz 都会被拒绝 -
-acodec pcm_s16le:WAV 容器里必须是小端 PCM,不是 MP3、AAC 或其他编码封装 - 完整命令示例:
ffmpeg -i /path/to/input.mp3 -ac 1 -ar 16000 -acodec pcm_s16le /path/to/output.wav -y,末尾-y防止交互式覆盖确认
转码前先用 ffprobe 拿时长和编码信息
用户上传的可能是 AMR(微信录音)、M4A(iOS 录音)、甚至 OPUS(WebRTC),这些格式 FFmpeg 能转,但不检查就硬跑会浪费资源。比如 AMR 文件若没装 libopencore-amrnb,ffmpeg 直接报 Unknown encoder 'libopencore-amrnb' 并退出,而你根本不知道缺什么依赖。
- 先跑:
ffprobe -v quiet -show_entries format=duration -of default=nw=1 input.amr,确认能否读取时长;失败则说明格式不受支持或缺少解码器 - 再跑:
ffprobe -v quiet -show_entries stream=codec_name,codec_type -of csv=p=0 input.m4a,看是否含audio,aac或audio,opus - 对 AMR、OPUS 这类冷门格式,建议提前拦截并返回明确提示:“暂不支持 AMR 格式,请用录音 App 导出为 MP3 或 M4A”
PHP 调用时别写死 /usr/bin/ffmpeg 路径
服务器上 ffmpeg 可能在 /usr/local/bin/ffmpeg(源码编译)、/opt/ffmpeg/bin/ffmpeg(Docker 容器),甚至 Windows 上是 C:\ffmpeg\bin\ffmpeg.exe。硬编码路径会让代码在测试环境跑通、上线就报 Command not found。
立即学习“PHP免费学习笔记(深入)”;
- 用
exec('which ffmpeg 2>/dev/null', $out, $code)动态获取路径,$out[0]就是真实位置 - 如果
$code !== 0,说明系统找不到 ffmpeg,此时该立刻中断流程并记录告警,而不是继续往下走 - 某些云主机(如阿里云轻量应用服务器)默认禁用
exec类函数,需检查php.ini中disable_functions是否包含它;若被禁,只能改用proc_open()或换部署方式



















