34_音视频播放器_音视频同步
简介
音视频同步的实现可以有两种方式
- 视频同步到音频
- 音频同步到视频
我们这里选择第一种,因为音频播放的频率是SDL控制的,我们没法改变,所以使用视频同步到音频。
实现音视频同步
在videoplayer.h
先定义两个全局变量,用于记录音频时钟和视频时钟
/** 音频时钟,当前音频包对应的时间值 */ double _aTime = 0; /** 视频时钟,当前视频包对应的时间值 */ double _vTime = 0;
在音频处理类videoplayer_audio.cpp
里保存音频时钟
// 保存音频时钟 if (pkt.pts != AV_NOPTS_VALUE) { _aTime = av_q2d(_aStream->time_base) *pkt.pts; }
在videoplayer_video.cpp
里的解码视频方法decodeVideo
里记录视频时钟并进行音画同步
// 视频时钟 if (pkt.dts != AV_NOPTS_VALUE) { _vTime = av_q2d(_vStream->time_base) * pkt.dts; } // 如果视频包过早被解码出来,那就需要等待对应的音频时钟到达 while (_vTime > _aTime && _state == Playing) { SDL_Delay(5); }
处理没有音频的播放
上面音视频同步是一个视频文件里有音频的情况下可以正常使用,但是如果一个视频文件没有音频是不能正常使用。
将videoplayer.cpp
的readFile
方法里的局部变量判断是否有音频和视频弄成成员变量:
// videoplayer.h /** 是否有音频流 */ bool _hasAudio = false; /** 是否有视频流 */ bool _hasVideo = false; // videoplayer.cpp // 初始化音频信息 _hasAudio = initAudioInfo() >= 0; // 初始化视频信息 _hasVideo = initVideoInfo() >= 0; if (!_hasAudio && !_hasVideo) { emit playFailed(this); free(); return; }
在videoplayer_video.cpp
中音画同步的地方添加是否有音频判断
if(_hasAudio){// 有音频 // 如果视频包过早被解码出来,那就需要等待对应的音频时钟到达 while (_vTime > _aTime && _state == Playing) { SDL_Delay(5); } }
更新界面播放的进度条
因为我们音视频同步是根据音频为基准的,所以我们可以在音频获取音频时钟的地方,把音频时钟发送到界面里进行更新。
在videoplayer.cpp
中给外界提供获取当前的播放时刻的方法
/** 当前的播放时刻(单位是秒) */ int VideoPlayer::getTime(){ return round(_aTime);// round四舍五入的意思 }
在提供一个信号量用于给外界发送消息
void timeChanged(VideoPlayer *player);
然后在videoplayer_audio.cpp
里的获取音频当前时刻的地方,发送信号给外界
// 保存音频时钟 if (pkt.pts != AV_NOPTS_VALUE) { _aTime = av_q2d(_aStream->time_base) *pkt.pts; // 通知外界:播放时间点发生了改变 emit timeChanged(this); }
在界面类mainwindow.cpp
里注册当前播放时刻的监听
connect(_player, &VideoPlayer::timeChanged, this, &MainWindow::onPlayerTimeChanged); void MainWindow::onPlayerTimeChanged(VideoPlayer *player) { ui->currentSlider->setValue(player->getTime()); }
处理切换音视频时还保留上一个视频的最后一帧画面
我们在播放一个视频文件时,点击停止后在去播放另外一个视频文件,会出现上一个视频的最后一帧画面。
我们点击停止后,会发出停止的信号出来,而我们的VideoWidget
类里也有出来状态改变的监听,那么还是会出现上一个视频的最后一帧画面呢?
// 状态改变的监听 void VideoWidget::onPlayerStateChanged(VideoPlayer *player) { if (player->getState() != VideoPlayer::Stopped) return; freeImage(); update(); }
这是因为VideoWidget
类里onPlayerFrameDecoded
方法是在主线程,而发射它的信号(frameDecoded
)是在子线程中,所以有多线程的情况,就会导致出现这种情况,现在我们需要在onPlayerFrameDecoded
方法里添加判断
void VideoWidget::onPlayerFrameDecoded(VideoPlayer *player, uint8_t *data, VideoPlayer::VideoSwsSpec &spec) { if (player->getState() == VideoPlayer::Stopped) return; ...... }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!