C++基于FFmpeg的音视频编解码程序音频失真问题求助
FFmpeg解复用/复用程序音频失真问题修复
核心问题分析
- 编码器参数重复重置:每次调用
write_audio_frame时重复设置编码器bit_rate、sample_rate等参数,会打乱编码器内部状态,引发编码异常。编码器参数应在初始化阶段一次性配置完成。 - AVFrame浅拷贝与数据篡改:
set_audio_frame中直接浅拷贝AVFrame,get_audio_frame中修改原始音频数据的指针,导致后续帧复用被破坏的数据,产生杂音。 - 重采样逻辑错误:
av_rescale_rnd使用相同采样率计算,无实际转换意义;- 未使用
swr_convert返回的实际输出样本数,导致帧大小不匹配; - 重采样上下文(
swr_ctx)未正确初始化,格式/参数不匹配引发转换失真。
- PTS管理混乱:手动生成的
pts与输入帧原始PTS冲突,未正确转换时间基准。
修复后的代码实现
1. 修正输入帧保存逻辑(set_audio_frame)
void video_encoder::set_audio_frame(AVFrame* audio, AVCodecContext* c_ctx) { // 释放旧帧资源,避免内存泄漏 av_frame_unref(&audio_data); // 引用输入帧,实现深拷贝语义 av_frame_ref(&audio_data, audio); // 保存输入编码上下文参数(仅用于编码器初始化) input_sample_fmt = *c_ctx; }
2. 修正帧获取逻辑(get_audio_frame)
AVFrame* video_encoder::get_audio_frame(OutputStream *ost) { // 替换为实际输入帧队列的判断逻辑,示例中保留原时长判断 if (av_compare_ts(audio_data.pts, input_sample_fmt.time_base, STREAM_DURATION, (AVRational){ 1, 1 }) > 0) return NULL; // 创建临时帧,避免修改原始输入数据 AVFrame *frame = av_frame_alloc(); if (!frame) return NULL; av_frame_ref(frame, &audio_data); #if !__AUDIO_ENABLED // 仅在测试模式下生成正弦波音频,不修改原始输入数据 int j, i, v; int16_t *q = (int16_t*)frame->data[0]; for (j = 0; j < frame->nb_samples; j++) { v = (int)(sin(ost->t) * 10000); for (i = 0; i < ost->enc->channels; i++) *q++ = v; ost->t += ost->tincr; ost->tincr += ost->tincr2; } #endif return frame; }
3. 重构编码器初始化与写帧逻辑
// 编码器初始化:仅在启动时调用一次 void video_encoder::init_audio_encoder(OutputStream *ost) { #if __AUDIO_ENABLED AVCodecContext *c = ost->enc; // 从输入参数配置编码器 c->bit_rate = input_sample_fmt.bit_rate; c->sample_rate = input_sample_fmt.sample_rate; c->time_base = av_inv_q((AVRational){1, c->sample_rate}); // 标准音频时间基准:1/采样率 c->sample_fmt = input_sample_fmt.sample_fmt; c->channel_layout = input_sample_fmt.channel_layout; c->channels = av_get_channel_layout_nb_channels(c->channel_layout); // 打开编码器 int ret = avcodec_open2(c, NULL, NULL); if (ret < 0) { fprintf(stderr, "Failed to open audio encoder\n"); exit(1); } // 初始化重采样上下文(仅当输入与编码器参数不匹配时需要) ost->swr_ctx = swr_alloc_set_opts(NULL, c->channel_layout, c->sample_fmt, c->sample_rate, input_sample_fmt.channel_layout, input_sample_fmt.sample_fmt, input_sample_fmt.sample_rate, 0, NULL); if (!ost->swr_ctx || swr_init(ost->swr_ctx) < 0) { fprintf(stderr, "Failed to initialize audio resampler\n"); exit(1); } #endif } // 写音频帧逻辑 int video_encoder::write_audio_frame(AVFormatContext *oc, OutputStream *ost) { AVCodecContext *c = ost->enc; AVFrame *frame = get_audio_frame(ost); int ret; if (!frame) { // 发送空帧,通知编码器结束编码 ret = avcodec_send_frame(c, NULL); while (ret >= 0) { ret = avcodec_receive_packet(c, ost->tmp_pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) { fprintf(stderr, "Error receiving audio packet\n"); exit(1); } write_frame(oc, c, ost->st, NULL, ost->tmp_pkt); av_packet_unref(ost->tmp_pkt); } return 0; } // 执行重采样(如果上下文存在) if (ost->swr_ctx) { int dst_nb_samples = av_rescale_rnd(frame->nb_samples, c->sample_rate, input_sample_fmt.sample_rate, AV_ROUND_UP); // 确保输出帧可写 ret = av_frame_make_writable(ost->frame); if (ret < 0) { av_frame_free(&frame); exit(1); } // 转换音频格式 ret = swr_convert(ost->swr_ctx, ost->frame->data, dst_nb_samples, (const uint8_t **)frame->data, frame->nb_samples); if (ret < 0) { fprintf(stderr, "Error converting audio samples\n"); av_frame_free(&frame); exit(1); } // 更新输出帧参数 av_frame_unref(frame); frame = ost->frame; frame->nb_samples = ret; frame->channel_layout = c->channel_layout; frame->sample_rate = c->sample_rate; frame->sample_fmt = c->sample_fmt; } // 转换输入帧PTS到编码器时间基准 frame->pts = av_rescale_q(frame->pts, input_sample_fmt.time_base, c->time_base); // 发送帧到编码器 ret = avcodec_send_frame(c, frame); if (ret < 0) { fprintf(stderr, "Error sending audio frame to encoder\n"); av_frame_free(&frame); exit(1); } // 接收编码后的数据包并写入输出文件 while (ret >= 0) { ret = avcodec_receive_packet(c, ost->tmp_pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; if (ret < 0) { fprintf(stderr, "Error receiving audio packet\n"); av_frame_free(&frame); exit(1); } write_frame(oc, c, ost->st, NULL, ost->tmp_pkt); av_packet_unref(ost->tmp_pkt); } av_frame_free(&frame); return 0; }
额外注意事项
- 输入帧队列管理:需实现输入音频帧的缓存队列,
get_audio_frame从队列中取帧,避免复用单一帧导致数据覆盖。 - 资源释放:程序退出或编码器停止时,调用
av_frame_free、avcodec_close、swr_free等函数释放资源,防止内存泄漏。 - 重采样参数验证:确保
swr_ctx的输入/输出参数与实际音频格式完全匹配,包括采样格式、通道数、采样率,参数不匹配会直接导致失真。
内容的提问来源于stack exchange,提问作者Turgut
相关产品推荐
相关产品推荐

