You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 05:17:11