Davin lin 2024-11-08 10:36 采纳率: 0%
浏览 62

qt+ffmpeg播放器,qaudiooutput倍速播放有杂音

qt+ffmpeg播放器倍速播放有杂音

该项目基于 qt+ffmpeg,QAudioOutput 模块进行音频,1.0 倍的速度是正常的,改倍速虽然速度正常修改,但会有电流杂音,我检查了采样率是匹配的以及缓冲区大小是足够的,速度的实现主要是修改时间戳和采样率,如何解决呢?

读文件时音频的逻辑

    if (audioStream >= 0)
    {
        ///查找音频解码器
        aCodecCtx = pFormatCtx->streams[audioStream]->codec;
        aCodec = avcodec_find_decoder(aCodecCtx->codec_id);

        if (aCodec == NULL)
        {
            fprintf(stderr, "ACodec not found.\n");
            audioStream = -1;
        }
        else
        {
            ///打开音频解码器
            if (avcodec_open2(aCodecCtx, aCodec, nullptr) < 0)
            {
                fprintf(stderr, "Could not open audio codec.\n");
                doOpenVideoFileFailed();
                goto end;
            }

            ///解码音频相关
            aFrame = av_frame_alloc();


            //重采样设置选项-----------------------------------------------------------start
            aFrame_ReSample = nullptr;

            //frame->16bit 44100 PCM 统一音频采样格式与采样率
            swrCtx = nullptr;

            //输入的声道布局
            int in_ch_layout;

            //输出的声道布局
            /*out_ch_layout = av_get_default_channel_layout(audio_tgt_channels); ///AV_CH_LAYOUT_STEREO

            out_ch_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;*/

            /// 这里音频播放使用了固定的参数
            /// 强制将音频重采样成44100 双声道  AV_SAMPLE_FMT_S16
            /// SDL播放中也是用了同样的播放参数
            //重采样设置选项----------------
            //输入的采样格式
            in_sample_fmt = aCodecCtx->sample_fmt;
            //输出的采样格式 16bit PCM
            out_sample_fmt = AV_SAMPLE_FMT_S16;
            //输入的采样率
            in_sample_rate = aCodecCtx->sample_rate;
            //输入的声道布局
            in_ch_layout = aCodecCtx->channel_layout;

            //输出的采样率
//            out_sample_rate = 44100;
            out_sample_rate = static_cast<int>(aCodecCtx->sample_rate * mSpeed);// 默认与输入采样率相同,也可以强制为44100Hz

            //out_sample_rate = aCodecCtx->sample_rate;

            //输出的声道布局
            // 输出的声道数(固定为双声道)
            audio_tgt_channels = aCodecCtx->channels; ///av_get_channel_layout_nb_channels(out_ch_layout);
            out_ch_layout = av_get_default_channel_layout(audio_tgt_channels); ///AV_CH_LAYOUT_STEREO

            out_ch_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;

            
            /// wav/wmv 文件获取到的aCodecCtx->channel_layout为0会导致后面的初始化失败,因此这里需要加个判断。
            if (in_ch_layout <= 0)
            {
                in_ch_layout = av_get_default_channel_layout(aCodecCtx->channels);
            }

            swrCtx = swr_alloc_set_opts(nullptr, out_ch_layout, out_sample_fmt, out_sample_rate,
                                                 in_ch_layout, in_sample_fmt, in_sample_rate, 0, nullptr);

            /** Open the resampler with the specified parameters. */
            int ret = swr_init(swrCtx);
            if (ret < 0)
            {
                char buff[128]={0};
                av_strerror(ret, buff, 128);

                fprintf(stderr, "Could not open resample context %s\n", buff);
                swr_free(&swrCtx);
                swrCtx = nullptr;
                doOpenVideoFileFailed();
                goto end;
            }


            mAudioStream = pFormatCtx->streams[audioStream];

            ///创建一个线程专门用来解码音频
            int code = openAudio();
            if (code == 0) {
                std::thread([&](VideoPlayer *pointer)
                {
                    pointer->decodeAudioThread();

                }, this).detach();
            }

        }

    }

处理音频帧逻辑

void VideoPlayer::decodeAudioThread()
{
    mIsAudioThreadFinished = false;
    double last_audio_clock = 0;

    while (!mIsQuit) {
        if (mIsPause) {
            QThread::msleep(10);  // 短暂休眠避免占用 CPU
            continue;
        }

        // 解码音频帧
        int audio_data_size = decodeAudioFrame1();

        // 如果解码出错或没有数据,播放静音
        if (audio_data_size <= 0) {
            memset(audio_buf, 0, 1024);
            audio_data_size = 1024;
        }

        // 如果需要静音处理
        if (mIsMute || mIsNeedPause) {
            memset(audio_buf, 0, audio_data_size);
        }
        else {
            PcmVolumeControl::RaiseVolume((char*)audio_buf, audio_data_size, 1, mVolume); // 调节音量
        }
        if (audioDevice) {
            qint64 bytesWritten = 0;
            while (bytesWritten < audio_data_size) {
                qint64 written = audioDevice->write((const char*)audio_buf + bytesWritten, audio_data_size - bytesWritten);

                if (written == -1) {
                    qDebug() << "Error writing audio data";
                    break;
                }
                else if (written == 0) {
                    QThread::msleep(1);  // 避免过度等待,降低 msleep
                }
                else {
                    bytesWritten += written;
                }
            }
        }

        // 短暂休眠,防止占用过多 CPU
        QThread::msleep(5);
    }

    mIsAudioThreadFinished = true;
}

int VideoPlayer::decodeAudioFrame1(bool isBlock)
{
    int audioBufferSize = 0;

    while (1)
    {
        if (mIsQuit) {
            mIsAudioThreadFinished = true;
            clearAudioQueue();
            break;
        }

        if (mIsPause) {
            break;
        }

        mConditon_Audio->Lock();

        if (mAudioPacktList.size() <= 0) {
            if (isBlock) {
                mConditon_Audio->Wait();
            }
            else {
                mConditon_Audio->Unlock();
                break;
            }
        }

        AVPacket packet = mAudioPacktList.front();
        mAudioPacktList.pop_front();
        mConditon_Audio->Unlock();

        AVPacket* pkt = &packet;

        if (pkt->pts != AV_NOPTS_VALUE)
        {
            audio_clock = av_q2d(mAudioStream->time_base) * pkt->pts;
        }
        audio_clock /= mSpeed;
        //收到这个数据 说明刚刚执行过跳转 现在需要把解码器的数据 清除一下
        if (strcmp((char*)pkt->data, FLUSH_DATA) == 0)
        {
            avcodec_flush_buffers(mAudioStream->codec);
            av_packet_unref(pkt);
            continue;
        }

        if (seek_flag_audio)
        {
            //发生了跳转 则跳过关键帧到目的时间的这几帧
            if (audio_clock < seek_time)
            {
                av_packet_unref(pkt);
                continue;
            }
            else
            {
                seek_flag_audio = 0;
            }
        }

        // 发送数据包到解码器
        if (avcodec_send_packet(aCodecCtx, &packet) != 0) {
            av_packet_unref(&packet);
            continue;
        }

        // 接收解码后的帧
        int ret = avcodec_receive_frame(aCodecCtx, aFrame);

        av_packet_unref(&packet);

        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            continue; // 数据不足,继续解码
        }
        else if (ret < 0) {
            break; // 解码错误
        }

        // 处理解码后的帧
        if (aFrame->format != AV_SAMPLE_FMT_S16 || aFrame->sample_rate != out_sample_rate || aFrame->channels != audio_tgt_channels) {
            // 重采样音频数据为 QAudioOutput 支持的格式
            int nb_samples = av_rescale_rnd(swr_get_delay(swrCtx, out_sample_rate) + aFrame->nb_samples, out_sample_rate, in_sample_rate, AV_ROUND_UP);

            // 初始化重采样帧
            if (!aFrame_ReSample || aFrame_ReSample->nb_samples != nb_samples) {
                if (aFrame_ReSample) {
                    av_frame_free(&aFrame_ReSample);
                }
                aFrame_ReSample = av_frame_alloc();

                aFrame_ReSample->format = out_sample_fmt;
                aFrame_ReSample->channel_layout = out_ch_layout;
                aFrame_ReSample->sample_rate = out_sample_rate;
                aFrame_ReSample->nb_samples = nb_samples;

                int ret = av_samples_fill_arrays(aFrame_ReSample->data, aFrame_ReSample->linesize, audio_buf, audio_tgt_channels, aFrame_ReSample->nb_samples, out_sample_fmt, 0);
                if (ret < 0) {
                    fprintf(stderr, "Error allocating an audio buffer\n");
                    continue;
                }
            }


            // 使用 swr_convert 进行重采样
            int len2 = swr_convert(swrCtx, aFrame_ReSample->data, aFrame_ReSample->nb_samples, (const uint8_t**)aFrame->data, aFrame->nb_samples);

            //audioBufferSize = av_samples_get_buffer_size(nullptr, audio_tgt_channels, len2, out_sample_fmt, 1);
            audioBufferSize = len2 * audio_tgt_channels * av_get_bytes_per_sample(out_sample_fmt);
            //memcpy(audio_buf, aFrame_ReSample->data[0], audioBufferSize);

        }
        else {
            // 如果解码的帧已经是目标格式,直接处理
            int unpadded_linesize = aFrame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)aFrame->format);
            audioBufferSize = unpadded_linesize;

            memcpy(audio_buf, aFrame->data[0], audioBufferSize);
        }
        // 根据倍速调整缓冲区大小
        audioBufferSize = static_cast<int>(audioBufferSize / mSpeed);
        break; // 成功解码一帧音频,退出循环
    }

    return audioBufferSize;
}

  • 写回答

1条回答 默认 最新

  • 越重天 新星创作者: Java技术领域 2024-11-08 22:09
    关注

    本答案参考 多次询问 ChatGPT-3.5 后选择的最佳回答,若是满意,还请采纳,谢谢

    1. 可能的原因分析
      • 重采样问题
        • 虽然设置了重采样的参数,但倍速播放时可能会使重采样算法在处理音频数据时出现问题。例如,当倍速改变时,时间相关的处理在重采样过程中可能不够精确。在swr_alloc_set_optsswr_convert函数的使用过程中,倍速可能影响了输入输出采样率、声道布局等参数的协同工作。
        • 代码中swrCtx = swr_alloc_set_opts(nullptr, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, nullptr);这里设置了重采样的参数,倍速改变时可能导致这些参数之间的关系不再最优。
      • 音频缓冲区处理问题
        • 在倍速播放时,音频缓冲区的填充和清空逻辑可能受到影响。从decodeAudioFrame1函数中可以看到,audioBufferSize = static_cast<int>(audioBufferSize / mSpeed);这样根据倍速调整缓冲区大小的操作可能存在问题。如果倍速较大,可能会导致缓冲区数据不足或者数据处理不及时,从而产生杂音。
        • decodeAudioThread函数中,while (!mIsQuit)循环内对音频数据的处理可能没有很好地适应倍速播放的情况。例如,将音频数据写入QAudioOutput设备时,audioDevice->write操作在倍速下可能会出现数据传输不稳定的情况。
      • 时间戳处理问题
        • 尽管检查了采样率匹配,但时间戳的处理在倍速播放下可能仍然不准确。在decodeAudioFrame1函数中,audio_clock /= mSpeed;这样简单地根据倍速调整时间戳可能不够完善。例如,如果在音频流中有一些特殊的时间标记或者同步信息,这种简单的除法可能会破坏音频的同步性,导致杂音。
    2. 解决方案建议
      • 优化重采样过程
        • 重新审视重采样的参数设置。可以尝试在倍速改变时动态调整重采样的参数,而不仅仅是改变采样率。例如,根据倍速调整swr_alloc_set_opts中的其他参数,如声道布局或者一些内部算法相关的参数(如果有可调整的)。
        • 检查swr_convert函数的使用。确保在倍速播放下,输入和输出的数据格式、采样率、声道布局等参数的转换是准确的。可以参考FFmpeg的官方文档,查看是否有针对倍速播放的特殊处理建议。
      • 改进音频缓冲区管理
        • 重新设计音频缓冲区的大小调整逻辑。不仅仅是简单地根据倍速进行除法操作,可以根据音频数据的实际情况,如音频的帧率、时长等因素,更合理地计算缓冲区大小。
        • decodeAudioThread函数中,优化将音频数据写入设备的逻辑。可以增加一些错误处理和数据完整性检查,确保在倍速播放下数据能够稳定地传输到QAudioOutput设备。例如,在audioDevice->write操作前后增加更多的状态检查和错误处理机制。
      • 精确处理时间戳
        • 建立更精确的时间戳处理机制。除了简单的除法操作,可以根据音频流中的关键帧、同步点等信息来调整时间戳。例如,可以在音频流中查找特定的同步标记,根据这些标记和倍速来调整时间戳,而不是仅仅依赖于简单的数学计算。
        • decodeAudioFrame1函数中,对时间戳的处理可以考虑音频流的整体结构,例如,根据音频帧的序号、音频流的起始时间等信息来综合调整时间戳,以确保音频在倍速播放下的同步性。
    评论

报告相同问题?

问题事件

  • 创建了问题 11月8日