ffmpeg播放器23(音频播放杂音修复)

116 阅读4分钟

1背景

上一篇提到 目前全是杂音,下次再找哪里出的问题。这一篇开始找分析问题。

2步骤

2.1 播放设备检查---无效

一开始以为是参数的问题,修改了下面的各种参数,以及更换设备名称都没效果

// 2. 设置期望的音频参数
    SDL_AudioSpec desiredSpec;
    SDL_zero(desiredSpec);

    // 使用音频帧中的参数(这些应该在解码时设置)
    desiredSpec.freq = m_sampleRate;      // 采样率 (例如44100)
    desiredSpec.channels = m_channels;    // 声道数 (例如2)
    desiredSpec.format = m_format;        // 样本格式 (例如AUDIO_S16)
    desiredSpec.samples = 1024;           // 音频缓冲区大小(建议2048或4096)
    desiredSpec.callback = audioCallback; // 回调函数
    desiredSpec.userdata = this;          // 传递实例指针

    // 3. 打开音频设备
    m_audioDeviceID = SDL_OpenAudioDevice(
        nullptr,      // 使用默认设备
        0,            // 0=输出设备,1=输入设备
        &desiredSpec,
        &m_obtainedSpec,
        SDL_AUDIO_ALLOW_FREQUENCY_CHANGE |
            SDL_AUDIO_ALLOW_CHANNELS_CHANGE
        );

    if (m_audioDeviceID == 0) {
        qCritical() << "Failed to open audio device:" << SDL_GetError();
        return false;
    }

    // 4. 检查实际获得的音频参数
    if (desiredSpec.format != m_obtainedSpec.format) {
        qWarning() << "Using different audio format:"
                   << "Desired:" << SDL_GetPixelFormatName(desiredSpec.format)
                   << "Obtained:" << SDL_GetPixelFormatName(m_obtainedSpec.format);
    }

    if (desiredSpec.channels != m_obtainedSpec.channels) {
        qWarning() << "Using different channel count:"
                   << "Desired:" << (int)desiredSpec.channels
                   << "Obtained:" << (int)m_obtainedSpec.channels;
    }

    if (desiredSpec.freq != m_obtainedSpec.freq) {
        qWarning() << "Using different sample rate:"
                   << "Desired:" << desiredSpec.freq
                   << "Obtained:" << m_obtainedSpec.freq;
    }

    // 5. 保存实际获得的参数
    m_sampleRate = m_obtainedSpec.freq;
    m_channels = m_obtainedSpec.channels;
    m_format = m_obtainedSpec.format;

    m_outSampleFmt = convertSDLToFFmpegFormat(m_format);

    // 6. 开始播放
    SDL_PauseAudioDevice(m_audioDeviceID, 0);

    qDebug() << "SDL2 audio initialized:"
             << "Format:" << m_format
             << "Sample rate:" << m_sampleRate
             << "Channels:" << m_channels
             << "Buffer size:" << m_obtainedSpec.samples;

2.2 重采样检查---无效

上面的播放设备没问题,就开始检查重采样

void SDL2AudioWnd::processAudioFrame(AVFrame *inputFrame)
{

    AVChannelLayout in_ch_layout;
    av_channel_layout_copy(&in_ch_layout, &inputFrame->ch_layout);

    // 2. 设置输出声道布局(立体声)
    AVChannelLayout out_ch_layout;
    av_channel_layout_default(&out_ch_layout, m_channels);  // 2表示立体声


    // 2. 检查是否需要重采样
    bool needsResample =
        (inputFrame->sample_rate != this->m_sampleRate) ||
        !av_channel_layout_compare(&in_ch_layout, &out_ch_layout) ||
        (inputFrame->format != m_outSampleFmt);

    // 3. 创建或更新重采样上下文(如果需要)
    if (!m_swrCtx && needsResample) {
        m_swrCtx = swr_alloc();

        // 设置重采样参数(兼容FFmpeg 5.0+)
        av_opt_set_chlayout(m_swrCtx, "in_chlayout", &in_ch_layout, 0);
        av_opt_set_chlayout(m_swrCtx, "out_chlayout", &out_ch_layout, 0);
        av_opt_set_int(m_swrCtx, "in_sample_rate", inputFrame->sample_rate, 0);
        av_opt_set_int(m_swrCtx, "out_sample_rate", m_sampleRate, 0);
        av_opt_set_sample_fmt(m_swrCtx, "in_sample_fmt", static_cast<AVSampleFormat>(inputFrame->format), 0);
        av_opt_set_sample_fmt(m_swrCtx, "out_sample_fmt",m_outSampleFmt, 0);

        int ret=swr_init(m_swrCtx);
        // 初始化重采样器
        if (ret < 0) {
            qWarning() << "Failed to initialize resampler";
            swr_free(&m_swrCtx);
            return;
        }
    }

    // 4. 创建输出帧
    AVFrame* outputFrame = av_frame_alloc();
    outputFrame->sample_rate = this->m_sampleRate;
    outputFrame->format = m_outSampleFmt;

    // 设置输出声道布局
    av_channel_layout_copy(&outputFrame->ch_layout, &out_ch_layout);

    // 5. 计算输出样本数
    int64_t delay = swr_get_delay(m_swrCtx, inputFrame->sample_rate);
    int64_t out_samples = av_rescale_rnd(
        delay + inputFrame->nb_samples,
       m_sampleRate,
        inputFrame->sample_rate,
        AV_ROUND_UP
        );
    outputFrame->nb_samples = (int)out_samples;

    // 6. 分配输出帧缓冲区
    int ret = av_frame_get_buffer(outputFrame, 0);
    if (ret < 0) {
        qWarning() << "Failed to allocate output frame buffer";
        av_frame_free(&outputFrame);
        return;
    }

    // 7. 执行重采样
    ret = swr_convert_frame(m_swrCtx, outputFrame, inputFrame);
    if (ret < 0) {
        char errbuf[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(ret, errbuf, sizeof(errbuf));
        qWarning() << "Resampling failed:" << errbuf;
        av_frame_free(&outputFrame);
        return;
    }

    addAudioFrame(outputFrame);
}

这里重点修改了重采样参数,打断点,依旧没有任何效果。检查了视频帧的格式,输出的格式。

2.3 音频帧存储问题---无效

上面的没解决之后,尝试ffmpeg解析方面去检查问题

    while (!this->quit) {
        av_init_packet(packet);

        int ret=av_read_frame(this->fmt_ctx,this->packet);

        if(ret!=0){
            if(!this->quit){

            }
            av_packet_unref(this->packet);
            return;
        }
        if (packet->stream_index == audio_stream_index) {
            ret=avcodec_send_packet(this->audio_codec_ctx,this->packet);
            if(ret!=0){
                av_packet_unref(this->packet);
                return;
            }

            ret= avcodec_receive_frame(this->audio_codec_ctx,this->frame);
            if(ret!=0){

            }else{
                this->audio_push_frame(this->frame);

                break;
            }
        }
    }

这里打了断点,检查了上面的参数设置,取消refcounted_frames都没任何效果

    if(audio_codec_ctx->codec_type==AVMEDIA_TYPE_VIDEO||
        audio_codec_ctx->codec_type==AVMEDIA_TYPE_AUDIO){
        av_dict_set(&audio_codec_opts,"refcounted_frames","1",0);
    }

2.4 检查播放---最终原因

image.png

  1. 在 addAudioFrame 中,我们使用 frame->buf[0] 来获取数据。但是,AVFrame 的数据存储在 data 指针数组中,而不是 bufbuf 是用于引用计数的,如果没有启用引用计数,那么 buf 可能为空,而数据在 data 中。我们应该使用 frame->data[0]
  2. 我们只考虑了平面格式(planar)吗?在 addAudioFrame 中,我们假设音频是交错的(interleaved),因为我们只取 frame->data[0]。实际上,如果格式是平面的,那么每个声道的数据在 data[0]data[1] 等。而重采样输出如果是交错的,那么所有数据都在 data[0] 中。

2.5 代码分支

github.com/xcyxiner/xp…

总分支 github.com/xcyxiner/xp…

未完待续