簡介
音視頻同步的實(shí)現(xiàn)可以有兩種方式
- 視頻同步到音頻
- 音頻同步到視頻
我們這里選擇第一種撕贞,因?yàn)橐纛l播放的頻率是SDL控制的蝌矛,我們沒法改變,所以使用視頻同步到音頻磅废。
實(shí)現(xiàn)音視頻同步
在videoplayer.h
先定義兩個(gè)全局變量锅棕,用于記錄音頻時(shí)鐘和視頻時(shí)鐘
/** 音頻時(shí)鐘,當(dāng)前音頻包對應(yīng)的時(shí)間值 */
double _aTime = 0;
/** 視頻時(shí)鐘淌山,當(dāng)前視頻包對應(yīng)的時(shí)間值 */
double _vTime = 0;
在音頻處理類videoplayer_audio.cpp
里保存音頻時(shí)鐘
// 保存音頻時(shí)鐘
if (pkt.pts != AV_NOPTS_VALUE) {
_aTime = av_q2d(_aStream->time_base) *pkt.pts;
}
在videoplayer_video.cpp
里的解碼視頻方法decodeVideo
里記錄視頻時(shí)鐘并進(jìn)行音畫同步
// 視頻時(shí)鐘
if (pkt.dts != AV_NOPTS_VALUE) {
_vTime = av_q2d(_vStream->time_base) * pkt.dts;
}
// 如果視頻包過早被解碼出來裸燎,那就需要等待對應(yīng)的音頻時(shí)鐘到達(dá)
while (_vTime > _aTime && _state == Playing) {
SDL_Delay(5);
}
處理沒有音頻的播放
上面音視頻同步是一個(gè)視頻文件里有音頻的情況下可以正常使用,但是如果一個(gè)視頻文件沒有音頻是不能正常使用泼疑。
將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){// 有音頻
// 如果視頻包過早被解碼出來德绿,那就需要等待對應(yīng)的音頻時(shí)鐘到達(dá)
while (_vTime > _aTime && _state == Playing) {
SDL_Delay(5);
}
}
更新界面播放的進(jìn)度條
因?yàn)槲覀円粢曨l同步是根據(jù)音頻為基準(zhǔn)的,所以我們可以在音頻獲取音頻時(shí)鐘的地方退渗,把音頻時(shí)鐘發(fā)送到界面里進(jìn)行更新移稳。
在videoplayer.cpp
中給外界提供獲取當(dāng)前的播放時(shí)刻的方法
/** 當(dāng)前的播放時(shí)刻(單位是秒) */
int VideoPlayer::getTime(){
return round(_aTime);// round四舍五入的意思
}
在提供一個(gè)信號(hào)量用于給外界發(fā)送消息
void timeChanged(VideoPlayer *player);
然后在videoplayer_audio.cpp
里的獲取音頻當(dāng)前時(shí)刻的地方,發(fā)送信號(hào)給外界
// 保存音頻時(shí)鐘
if (pkt.pts != AV_NOPTS_VALUE) {
_aTime = av_q2d(_aStream->time_base) *pkt.pts;
// 通知外界:播放時(shí)間點(diǎn)發(fā)生了改變
emit timeChanged(this);
}
在界面類mainwindow.cpp
里注冊當(dāng)前播放時(shí)刻的監(jiān)聽
connect(_player, &VideoPlayer::timeChanged,
this, &MainWindow::onPlayerTimeChanged);
void MainWindow::onPlayerTimeChanged(VideoPlayer *player) {
ui->currentSlider->setValue(player->getTime());
}
處理切換音視頻時(shí)還保留上一個(gè)視頻的最后一幀畫面
我們在播放一個(gè)視頻文件時(shí)会油,點(diǎn)擊停止后在去播放另外一個(gè)視頻文件个粱,會(huì)出現(xiàn)上一個(gè)視頻的最后一幀畫面。
我們點(diǎn)擊停止后翻翩,會(huì)發(fā)出停止的信號(hào)出來都许,而我們的VideoWidget
類里也有出來狀態(tài)改變的監(jiān)聽,那么還是會(huì)出現(xiàn)上一個(gè)視頻的最后一幀畫面呢嫂冻?
// 狀態(tài)改變的監(jiān)聽
void VideoWidget::onPlayerStateChanged(VideoPlayer *player) {
if (player->getState() != VideoPlayer::Stopped) return;
freeImage();
update();
}
這是因?yàn)?code>VideoWidget類里onPlayerFrameDecoded
方法是在主線程胶征,而發(fā)射它的信號(hào)(frameDecoded
)是在子線程中,所以有多線程的情況,就會(huì)導(dǎo)致出現(xiàn)這種情況桨仿,現(xiàn)在我們需要在onPlayerFrameDecoded
方法里添加判斷
void VideoWidget::onPlayerFrameDecoded(VideoPlayer *player,
uint8_t *data,
VideoPlayer::VideoSwsSpec &spec) {
if (player->getState() == VideoPlayer::Stopped) return;
......
}