探究部分 DVD 格式只有画面没有声音的问题
一、问题现象
FSPlayer 在播放某 DVD 格式文件时,视频正常但音频完全无声。排除解码器问题后,日志显示:
1
2
[FSPlayer] Could not find codec parameters for stream 2 (Audio: pcm_dvd, 0 channels): unspecified sample format
Consider increasing the value for the 'analyzeduration' (5000000) and 'probesize' (10000000) options
即使调高 probesize 和 analyzeduration,探测阶段依然无法获取音频参数。
二、根因分析:In-band 参数机制
pcm_dvd 编码器具有特殊性质:其音频参数(声道数、采样率、采样格式)并非静态存储在容器头部,而是以 In-band(带内) 方式嵌入在每个音频 Packet 的私有头部中。
这意味着 avformat_find_stream_info 在探测阶段可能读取不到足够的 Packet 来解析这些参数,导致音频流被”误判死刑”——av_find_best_stream 直接返回 -1,认为没有可用音频流。
三、解决方案:双阶段修复策略
阶段一:探测阶段的 Fallback 机制
当 av_find_best_stream 找不到音频流时,手动扫描所有流,为 In-band 参数类型的编码器提供 Fallback:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (!ffp->audio_disable) {
st_index[AVMEDIA_TYPE_AUDIO] =
av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
st_index[AVMEDIA_TYPE_AUDIO],
st_index[AVMEDIA_TYPE_VIDEO],
NULL, 0);
// Fallback:手动扫描 In-band 参数类型的音频流
if (st_index[AVMEDIA_TYPE_AUDIO] < 0) {
for (int fbs_i = 0; fbs_i < (int)ic->nb_streams; fbs_i++) {
AVStream *fbs_st = ic->streams[fbs_i];
if (fbs_st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
enum AVCodecID codec_id = fbs_st->codecpar->codec_id;
int is_inband_param_codec =
codec_id == AV_CODEC_ID_PCM_DVD ||
codec_id == AV_CODEC_ID_PCM_S16BE_PLANAR ||
codec_id == AV_CODEC_ID_ADPCM_G722 ||
codec_id == AV_CODEC_ID_ADPCM_G726;
if (avcodec_find_decoder(codec_id) != NULL && is_inband_param_codec) {
av_log(NULL, AV_LOG_WARNING,
"[Audio] av_find_best_stream returned -1 (incomplete codec params),"
" fallback to stream %d codec=%s\n",
fbs_i, avcodec_get_name(fbs_st->codecpar->codec_id));
st_index[AVMEDIA_TYPE_AUDIO] = fbs_i;
break;
}
}
}
}
}
阶段二:解码阶段的安全默认值
探测阶段未获取到参数,因此在 avcodec_open2 后,仅为 In-band 参数类型的解码器设置安全默认值,确保解码线程能正常启动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int is_inband_param_codec =
avctx->codec_id == AV_CODEC_ID_PCM_DVD ||
avctx->codec_id == AV_CODEC_ID_PCM_S16BE_PLANAR ||
avctx->codec_id == AV_CODEC_ID_ADPCM_G722 ||
avctx->codec_id == AV_CODEC_ID_ADPCM_G726;
if (is_inband_param_codec) {
if (avctx->ch_layout.nb_channels == 0) {
av_log(NULL, AV_LOG_WARNING,
"[Audio] codec '%s' reports 0 channels, defaulting to stereo\n",
avcodec_get_name(avctx->codec_id));
av_channel_layout_uninit(&avctx->ch_layout);
av_channel_layout_default(&avctx->ch_layout, 2);
}
if (avctx->sample_rate == 0) {
av_log(NULL, AV_LOG_WARNING,
"[Audio] codec '%s' reports 0 sample_rate, defaulting to 48000 Hz\n",
avcodec_get_name(avctx->codec_id));
avctx->sample_rate = 48000;
}
if (avctx->sample_fmt == AV_SAMPLE_FMT_NONE) {
av_log(NULL, AV_LOG_WARNING,
"[Audio] codec '%s' reports no sample_fmt, defaulting to s16\n",
avcodec_get_name(avctx->codec_id));
avctx->sample_fmt = AV_SAMPLE_FMT_S16;
}
}
四、关键限制:为什么必须限定编码器类型?
上述 is_inband_param_codec 检查至关重要。 若将此逻辑扩大到非 In-band 编码器(如 AAC),会导致灾难性后果:
1
2
3
4
5
* thread #32, name = 'ff_audio_dec', stop reason = EXC_BAD_ACCESS (code=1, address=0xff0)
frame #0: 0x000000010632170c FSPlayer`ff_vector_fmul_window_neon + 96
frame #1: 0x0000000105f42614 FSPlayer`imdct_and_windowing + 176
...
frame #7: 0x000000010601b5e8 FSPlayer`avcodec_send_packet + 176
崩溃原因:AAC 等解码器内部状态高度敏感。若外部强行修改 sample_fmt 为不匹配值,其 NEON 优化指令在进行数学运算(如 fmul)时会访问非法内存,直接导致 EXC_BAD_ACCESS。
五、解码后的真值修正
通过 LLDB 调试验证,解码器会在运行时自动修正参数:
1
2
3
4
5
6
# avcodec_send_packet 阶段(手动设置的占位值)
(lldb) p d->avctx->sample_fmt -> AV_SAMPLE_FMT_FLT
# avcodec_receive_frame 成功后(解码器解析的真值)
(lldb) p d->avctx->sample_fmt -> AV_SAMPLE_FMT_S16
(lldb) p frame->format -> 1 (即 AV_SAMPLE_FMT_S16)
因此,播放器在音频线程中持续监测 AVFrame 参数。一旦发现与”预判值”不符,立即重新配置 SwrContext,确保输出到音频渲染器的数据格式统一。
六、经验总结
处理底层编解码问题,必须遵守 “精准修复” 原则:
| 原则 | 说明 |
|---|---|
| 不要扩大化修复 | 解码器内部状态敏感,外部干预 sample_fmt 极易引发底层崩溃(如 NEON 指令异常) |
| 区分参数来源 | 明确哪些是探测阶段确定,哪些是解码阶段确定。pcm_dvd 的”占位值”仅用于打通链路,真实渲染配置必须以 AVFrame 为准 |
| 运行时动态适配 | 建立从解码到渲染的反馈机制,根据实际输出帧动态调整重采样器 |
此次修复使 FSPlayer 完美兼容了 DVD-ISO 等特殊格式,同时避免了引入新的稳定性风险。