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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 15:48:48