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

无ID3标签MP3:ffprobe与FFmpeg C API获取时长结果不一致

问题:缺失ID3标签的MP3文件,FFmpeg C API获取时长错误但ffprobe正常

开发基于FFmpeg C API的应用时遇到问题:有一个缺失ID3标签(以0xFFFB帧数据开头)的MP3文件,用ffprobe能正确识别时长,但自己的C++代码调用FFmpeg API得到的时长严重错误。已尝试设置scan_all_pmts参数,目前仅能通过累加AVPacket的时长得到正确结果,想了解ffprobe处理该文件的差异点。


ffprobe输出日志

[AVFormatContext @ 0000015509158a00] Opening 'OhYeah.mp3' for reading
[file @ 0000015509158fc0] Setting default whitelist 'file,crypto,data'
Probing mp3 score:1 size:2048
Probing mp3 score:25 size:4096
Probing mp3 score:51 size:8192
[mp3 @ 0000015509158a00] Format mp3 probed with size=8192 and score=51
[mp3 @ 0000015509158a00] pad 576 1418
[mp3 @ 0000015509158a00] Skipping 0 bytes of junk at 835.
[mp3 @ 0000015509158a00] Before avformat_find_stream_info() pos: 835 bytes read:97968 seeks:1 nb_streams:1
[mp3 @ 0000015509158a00] demuxer injecting skip 1105 / discard 0
[mp3float @ 0000015509162000] skip 1105 / discard 0 samples due to side data
[mp3float @ 0000015509162000] skip 1105/1152 samples
[mp3 @ 0000015509158a00] All info found
[mp3 @ 0000015509158a00] stream 0: start_time: 0.0250567 duration: 2.011429
[mp3 @ 0000015509158a00] format: start_time: 0.025057 duration: 2.011429 (estimate from stream) bitrate=259 kb/s
[mp3 @ 0000015509158a00] After avformat_find_stream_info() pos: 42819 bytes read:130400 seeks:1 frames:50
Input #0, mp3, from 'OhYeah.mp3':
  Duration: 00:00:02.01, start: 0.025057, bitrate: 259 kb/s
  Stream #0:0, 50, 1/14112000: Audio: mp3 (mp3float), 44100 Hz, stereo, fltp, 256 kb/s
      Metadata:
        encoder         : LAME3.100
[AVIOContext @ 0000015509161a80] Statistics: 130400 bytes read, 1 seeks

自定义C++代码输出日志

[AVFormatContext @ 0x236c2f0] Opening '/tmp/OhYeah.mp3' for reading
[file @ 0x236c6b0] Setting default whitelist 'file,crypto,data'
Probing mp3 score:1 size:2048
Probing mp3 score:25 size:4096
Probing mp3 score:51 size:8192
[mp3 @ 0x236c2f0] Format mp3 probed with size=8192 and score=51
[mp3 @ 0x236c2f0] invalid concatenated file detected - using bitrate for duration
[mp3 @ 0x236c2f0] pad 576 1418
[mp3 @ 0x236c2f0] Skipping 0 bytes of junk at 835.
[mp3 @ 0x236c2f0] Before avformat_find_stream_info() pos: 835 bytes read:65536 seeks:2 nb_streams:1
[mp3 @ 0x236c2f0] demuxer injecting skip 1105 / discard 0
[mp3float @ 0x2374cd0] skip 1105 / discard 0 samples due to side data
[mp3float @ 0x2374cd0] skip 1105/1152 samples
[mp3 @ 0x236c2f0] All info found
[mp3 @ 0x236c2f0] Estimating duration from bitrate, this may be inaccurate
[mp3 @ 0x236c2f0] stream 0: start_time: 0.0250567 duration: 53521.548438
[mp3 @ 0x236c2f0] format: start_time: 0.025057 duration: 53521.548438 (estimate from bit rate) bitrate=256 kb/s
[mp3 @ 0x236c2f0] format: start_time: 0.025057 duration: 53521.548438 (estimate from bit rate) bitrate=256 kb/s
[mp3 @ 0x236c2f0] After avformat_find_stream_info() pos: 42819 bytes read:97968 seeks:2 frames:50
Input #0, mp3, from '/tmp/OhYeah.mp3':
  Duration: 14:52:01.55, start: 0.025057, bitrate: 256 kb/s
  Stream #0:0, 50, 1/14112000: Audio: mp3 (mp3float), 44100 Hz, stereo, fltp, 256 kb/s
      Metadata:
        encoder         : LAME3.100
[AVIOContext @ 0x2374780] Statistics: 97968 bytes read, 2 seeks

自定义C++代码

av_log_set_level(AV_LOG_TRACE);

AVDictionary *options = nullptr;
av_dict_set(&options, "scan_all_pmts", "1", AV_DICT_MATCH_CASE);

AVFormatContext *format_context = nullptr;
int ret = avformat_open_input(&format_context, path.c_str(), nullptr, &options);
if (ret < 0)
{
    char strError[AV_ERROR_MAX_STRING_SIZE];
    av_strerror(ret, strError, sizeof(strError));
    LOG(1, "Error opening input file: %s, error %s\n", path.c_str(), strError);
    retObj.set("status", "error");
    retObj.set("error", strError);
    return retObj;
}

ret = avformat_find_stream_info(format_context, nullptr);
if (ret < 0)
{
    char strError[AV_ERROR_MAX_STRING_SIZE];
    av_strerror(ret, strError, sizeof(strError));
    LOG(1, "Error retrieving stream information: %s, error %s\n", path.c_str(), strError);
    avformat_close_input(&format_context);
    retObj.set("status", "error");
    retObj.set("error", strError);
    return retObj;
}

av_dump_format(format_context, 0, path.c_str(), 0);

当前获取正确时长的方法

// 遍历包计算时长
float duration = 0;
AVPacket pkg;
av_init_packet(&pkg);
pkg.data = nullptr;
pkg.size = 0;

while (av_read_frame(format_context, &pkg) >= 0)
{
    if (pkg.stream_index == audioStreamIndex)
    {
        duration += av_q2d(format_context->streams[audioStreamIndex]->time_base) * pkg.duration;
    }
    av_packet_unref(&pkg);
}
LOG(3, "duration: %f\n", duration);

差异分析与解决建议

核心差异点

  1. 时长估算逻辑:

    • ffprobe通过解析到的实际流帧信息估算时长(日志显示estimate from stream),结果准确。
    • 自定义代码中FFmpeg误判文件为无效拼接文件,转而用文件总大小/比特率估算时长,导致结果严重错误(日志显示invalid concatenated file detected - using bitrate for duration)。
  2. 探测阶段的读取行为:

    • ffprobe在avformat_find_stream_info()前读取了97968字节,仅1次seek;自定义代码仅读取65536字节,2次seek。更多的读取量让ffprobe能更完整地识别MP3帧结构,避免误判。
  3. 参数有效性:

    • scan_all_pmts是MPEG-TS流的专用参数,对MP3文件完全无效。ffprobe默认启用了针对音频文件的优化参数(如更大的analyzeduration和probesize),确保足够的数据分析。

解决建议

  • 替换无效参数,设置音频专用分析参数:
    将scan_all_pmts替换为analyzeduration和probesize,让FFmpeg读取更多数据来分析流信息:
    AVDictionary *options = nullptr;
    // 设置分析时长为1秒(单位:微秒)
    av_dict_set(&options, "analyzeduration", "1000000", 0);
    // 设置探测缓冲区大小为5MB(单位:字节)
    av_dict_set(&options, "probesize", "5000000", 0);
    
  • 保留累加AVPacket的方法:如果参数调整后仍有问题,遍历所有帧累加时长是最可靠的方案,虽然需要读取整个文件,但结果完全准确。

内容的提问来源于stack exchange,提问作者Spider

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:45:55