PHP环境下用ffmpeg/sox转pcm_s16le为MP3及实现断点续播问询
一、PHP无临时文件转MP3方案(基于FFmpeg)
直接通过管道实现WAV/PCM到MP3的实时转码,无需生成中间临时文件,核心是利用PHP的proc_open与FFmpeg的标准输入输出交互:
实现思路
将拆分后带WAV头的音频内容(或原始PCM数据)通过管道传给FFmpeg,FFmpeg实时编码为MP3后输出到标准输出,PHP直接将输出内容返回给客户端或写入存储。
代码示例
// 假设$wavContent是拆分后带44字节WAV头的音频内容(可从内存或文件读取) $wavContent = file_get_contents('path/to/split_track.wav'); // FFmpeg命令:从标准输入读WAV,输出320kbps无ID3v2头的MP3到标准输出 $cmd = 'ffmpeg -i pipe:0 -codec:a libmp3lame -b:a 320k -f mp3 -id3v2_version 0 pipe:1'; // 建立进程管道 $descriptorspec = [ 0 => ['pipe', 'r'], // 标准输入:向FFmpeg传入WAV数据 1 => ['pipe', 'w'], // 标准输出:接收FFmpeg生成的MP3数据 2 => ['pipe', 'w'] // 标准错误:捕获FFmpeg调试日志 ]; $process = proc_open($cmd, $descriptorspec, $pipes); if (is_resource($process)) { // 写入WAV数据到FFmpeg标准输入 fwrite($pipes[0], $wavContent); fclose($pipes[0]); // 读取MP3输出内容 $mp3Content = stream_get_contents($pipes[1]); fclose($pipes[1]); // 读取错误日志(可选,用于调试) $errorLog = stream_get_contents($pipes[2]); fclose($pipes[2]); // 关闭进程 proc_close($process); // 返回给浏览器直接播放 header('Content-Type: audio/mpeg'); header('Content-Length: ' . strlen($mp3Content)); echo $mp3Content; }
如果直接处理原始pcm_s16le数据(CD标准:44100Hz、双声道),只需修改FFmpeg命令:
ffmpeg -f s16le -ar 44100 -ac 2 -i pipe:0 -codec:a libmp3lame -b:a 320k -f mp3 -id3v2_version 0 pipe:1
二、MP3部分请求(跳过播放)实现
要支持HTTP范围请求(即跳过播放无需加载完整文件),需解决三个核心问题:
1. 制作可Seek的MP3结构
MP3没有单独的全局文件头,每个音频帧自带帧头(包含同步字、码率、采样率等解码信息)。要实现范围请求,需保证:
- 移除开头的ID3v2标签(ID3v2会占据文件开头可变长度字节,导致帧起始位置偏移),用
-id3v2_version 0参数让FFmpeg不生成ID3v2头; - 若需元数据,可使用ID3v1标签(位于文件末尾,不影响帧起始位置)。
这样MP3文件的开头就是第一个音频帧,客户端请求任意字节范围时,只要从完整帧的起始位置返回,播放器即可正常解码。
2. 确定MP3文件长度
- 预先转码场景:直接用PHP的
filesize($mp3FilePath)获取总字节数; - 实时转码场景:先计算原始PCM的总时长,再根据目标码率推导MP3总字节数:
PCM时长公式:时长(秒) = PCM总字节数 / (采样率 * 声道数 * 2)(16bit采样,每个样本占2字节)
MP3总字节数公式:总字节数 = (目标码率(bps) / 8) * 时长(秒)
3. PCM转无头部MP3的FFmpeg命令
直接将pcm_s16le数据通过管道传给FFmpeg,指定输入格式并输出无ID3v2头的MP3:
ffmpeg -f s16le -ar 44100 -ac 2 -i pipe:0 -codec:a libmp3lame -b:a 320k -f mp3 -id3v2_version 0 pipe:1
参数说明:
-f s16le:指定输入为16bit小端PCM格式;-ar 44100:设置采样率为CD标准的44100Hz;-ac 2:设置双声道;-id3v2_version 0:禁用ID3v2头;pipe:0/pipe:1:分别对应标准输入和输出。
范围请求的HTTP实现
服务器需处理客户端的Range请求头,核心逻辑如下:
$mp3Path = 'path/to/target.mp3'; $fileSize = filesize($mp3Path); if (isset($_SERVER['HTTP_RANGE'])) { preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches); $start = intval($matches[1]); $end = isset($matches[2]) ? intval($matches[2]) : $fileSize - 1; // 验证请求范围合法性 if ($start >= $fileSize) { header('HTTP/1.1 416 Requested Range Not Satisfiable'); header('Content-Range: bytes */' . $fileSize); exit; } $length = $end - $start + 1; // 返回部分内容响应头 header('HTTP/1.1 206 Partial Content'); header('Content-Type: audio/mpeg'); header('Content-Length: ' . $length); header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize); header('Accept-Ranges: bytes'); // 读取并输出指定范围的MP3数据 $fp = fopen($mp3Path, 'rb'); fseek($fp, $start); echo fread($fp, $length); fclose($fp); } else { // 返回完整文件 header('Content-Type: audio/mpeg'); header('Content-Length: ' . $fileSize); header('Accept-Ranges: bytes'); readfile($mp3Path); }
内容的提问来源于stack exchange,提问作者Loïc
相关产品推荐
相关产品推荐

