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;
}