使用FFmpeg libavfilter acrossfade仅获首个输入部分输出的问题排查
问题:FFmpeg acrossfade滤镜合并音频块输出不完整
我尝试通过acrossfade滤镜合并两个原始音频块(格式为s16、采样率48000、单声道),但输出仅包含第一个音频块减去淡入淡出样本数的内容,无法得到完整的合并结果。
相关实现代码如下:
滤镜节点创建
avfilter_graph_create_filter(&mediaIn_1, avfilter_get_by_name("abuffer"), "MediaIn_1", "sample_rate=48000:sample_fmt=s16:channel_layout=mono", NULL, graph); avfilter_graph_create_filter(&mediaIn_2, avfilter_get_by_name("abuffer"), "MediaIn_2", "sample_rate=48000:sample_fmt=s16:channel_layout=mono", NULL, graph); avfilter_graph_create_filter(&mediaOut, avfilter_get_by_name("abuffersink"), "MediaOut", NULL, NULL, graph); avfilter_graph_create_filter(&crossfade, avfilter_get_by_name("acrossfade"), "crossfade", "nb_samples=6000:c1=tri:c2=tri", NULL, graph);
滤镜图链接
avfilter_link(mediaIn_1, 0, crossfade, 0); avfilter_link(mediaIn_2, 0, crossfade, 1); avfilter_link(crossfade, 0, mediaOut, 0); avfilter_graph_config(graph, NULL);
音频帧创建(以第一个块为例)
frame1 = av_frame_alloc(); frame1->format = AV_SAMPLE_FMT_S16; frame1->nb_samples = buf1sz / 2; frame1->sample_rate = 48000; frame1->ch_layout.order = AV_CHANNEL_ORDER_NATIVE; frame1->ch_layout.nb_channels = 1; frame1->ch_layout.u.mask = AV_CH_LAYOUT_MONO; frame1->ch_layout.opaque = NULL; frame1->pts = 0; frame1->duration = buf1sz / 2; frame1->time_base.num = 1; frame1->time_base.den = 48000; av_frame_get_buffer(frame1, 0); memcpy(frame1->buf[0]->data, buf1, buf1sz);
帧发送与输出获取
// 发送第一个音频块 av_buffersrc_add_frame_flags(mediaIn_1, frame1, 0); av_buffersrc_add_frame_flags(mediaIn_1, NULL, AV_BUFFERSRC_FLAG_PUSH); // 发送第二个音频块 av_buffersrc_add_frame_flags(mediaIn_2, frame2, 0); av_buffersrc_add_frame_flags(mediaIn_2, NULL, AV_BUFFERSRC_FLAG_PUSH); // 获取输出帧 oframe = av_frame_alloc(); av_buffersink_get_frame_flags(mediaOut, oframe, 0); // ...处理帧 av_frame_unref(oframe);
输出帧仅包含frame1->nb_samples - 6000个样本,再次调用av_buffersink_get_frame_flags返回AVERROR_EOF,无法获取第二个音频块的内容。
解决方案
问题出在双输入滤镜的帧发送时机和EOF信号的处理逻辑上,acrossfade需要第一个输入的结尾和第二个输入的开头重叠,当前的发送方式会导致滤镜提前终止。
1. 核心错误原因
- 你先给第一个输入发送完所有帧并标记EOF,此时acrossfade会判定第一个输入已结束,直接输出第一个音频块减去交叉淡入长度的内容后就终止流程,不会等待第二个输入的帧。
- acrossfade的工作逻辑是:需要第一个输入的最后
nb_samples个样本和第二个输入的前nb_samples个样本进行混合,因此必须保证第二个输入的帧在第一个输入的EOF信号发送前就进入滤镜队列。
2. 具体修正步骤
步骤一:调整帧发送顺序,延迟发送EOF
不要先给第一个输入发送NULL帧标记EOF,而是先发送第一个音频块的帧,紧接着发送第二个音频块的帧,最后再给两个输入依次发送EOF信号:
// 先发送第一个音频块的帧 av_buffersrc_add_frame_flags(mediaIn_1, frame1, 0); // 接着发送第二个音频块的帧 av_buffersrc_add_frame_flags(mediaIn_2, frame2, 0); // 先给第一个输入发送EOF av_buffersrc_add_frame_flags(mediaIn_1, NULL, AV_BUFFERSRC_FLAG_PUSH); // 再给第二个输入发送EOF av_buffersrc_add_frame_flags(mediaIn_2, NULL, AV_BUFFERSRC_FLAG_PUSH);
步骤二:循环获取输出帧,直到返回EOF
当前只调用了一次av_buffersink_get_frame_flags,但滤镜可能会分多次输出帧,需要循环获取直到返回AVERROR_EOF:
oframe = av_frame_alloc(); int ret; while ((ret = av_buffersink_get_frame_flags(mediaOut, oframe, 0)) >= 0) { // 处理输出帧,比如保存或处理样本 // ... av_frame_unref(oframe); } // 检查是否是正常结束 if (ret != AVERROR_EOF) { // 处理错误 } av_frame_free(&oframe);
步骤三:确保帧的PTS和时间线对齐
acrossfade依赖时间线来对齐两个输入,需要将第二个输入的PTS设置为第一个输入的总时长减去交叉淡入的样本数,这样滤镜才能正确计算重叠区域:
// 计算第一个音频块的总样本数(即总时长对应的样本量) int64_t frame1_total_samples = frame1->nb_samples; // 第二个帧的PTS设置为第一个帧的总样本数减去交叉淡入样本数 frame2->pts = frame1_total_samples - 6000; frame2->time_base = frame1->time_base; // 保持时间基准一致
3. 修正后效果
调整后,acrossfade会先缓存第一个输入的最后6000个样本,接着接收第二个输入的前6000个样本进行混合,最终输出内容为:第一个输入的非重叠部分 + 混合过渡部分 + 第二个输入的非重叠部分,总样本数为frame1->nb_samples + frame2->nb_samples - 6000,符合预期。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

