C語言 ffmpeg與sdl實(shí)現(xiàn)播放視頻同時(shí)同步時(shí)鐘詳解
前言
視頻的時(shí)鐘同步有時(shí)是很難理解的,甚至知道了理論并不能確保實(shí)現(xiàn),需要通過實(shí)踐獲取各種參數(shù)以及具體的實(shí)現(xiàn)邏輯。本文將介紹一些視頻時(shí)鐘同步的具體實(shí)現(xiàn)方式。
一、直接延時(shí)
我們播放視頻是可以直接延時(shí)的,這種方式比較不準(zhǔn)確,但是也算是一種初級(jí)的方法。
1、根據(jù)幀率延時(shí)
每渲染一幀都進(jìn)行一個(gè)固定的延時(shí),這個(gè)延時(shí)的時(shí)間是通過幀率計(jì)算得來。
//獲取視頻幀率 AVRational framerate = play->formatContext->streams[video->decoder.streamIndex]->avg_frame_rate; //根據(jù)幀率計(jì)算出一幀延時(shí) double duration = (double)framerate.num / framerate.den; //顯示視頻 略 //延時(shí) av_usleep(duration* 1000000);
2、根據(jù)duration延時(shí)
每渲染一幀根據(jù)其duration進(jìn)行延時(shí),這個(gè)duration在視頻的封裝格式中通常會(huì)包含。
//獲取當(dāng)前幀的持續(xù)時(shí)間,下列方法有可能會(huì)無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計(jì)算duartion?;蛘咦约簩?shí)現(xiàn)多幀估算duration。 AVRational timebase = play->formatContext->streams[video->decoder.streamIndex]->time_base; double duration = frame->pkt_duration * (double)timebase.num / timebase.den; //顯示視頻 略 //延時(shí) av_usleep(duration* 1000000);
二、同步到時(shí)鐘
注:這一部分講的只有視頻時(shí)鐘同步不涉及音頻。
上面的簡(jiǎn)單延時(shí)一定程度可以滿足播放需求,但也有問題,不能確保時(shí)間的準(zhǔn)確性。尤其是解復(fù)用解碼也會(huì)消耗時(shí)間,單純的固定延時(shí)會(huì)導(dǎo)致累計(jì)延時(shí)。這個(gè)時(shí)候我們就需要一個(gè)時(shí)鐘來校準(zhǔn)每一幀的播放時(shí)間。
1、同步到絕對(duì)時(shí)鐘
比較簡(jiǎn)單的方式是按絕對(duì)的方式同步到時(shí)鐘,即視頻有多長(zhǎng),開始之后一定是按照系統(tǒng)時(shí)間的長(zhǎng)度播放完成,如果視頻慢了則會(huì)不斷丟幀,直到追上時(shí)間。
定義一個(gè)視頻起始時(shí)間,在播放循環(huán)之外的地方。
//視頻起始時(shí)間,單位為秒 double videoStartTime=0;
播放循環(huán)中進(jìn)行時(shí)鐘同步。下列代碼的frame為解碼的AVFrame。
//以下變量時(shí)間單位為s //當(dāng)前時(shí)間 double currentTime = av_gettime_relative() / 1000000.0 - videoStartTime; //視頻幀的時(shí)間 double pts = frame->pts * (double)timebase.num / timebase.den; //計(jì)算時(shí)間差,大于0則late,小于0則early。 double diff = currentTime - pts; //視頻幀的持續(xù)時(shí)間,下列方法有可能會(huì)無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計(jì)算duartion。 double duration = frame->pkt_duration * (double)timebase.num / timebase.den; //大于閾值,修正時(shí)間,時(shí)鐘和視頻幀偏差超過0.1s時(shí)重新設(shè)置起點(diǎn)時(shí)間。 if (diff > 0.1) { videoStartTime = av_gettime_relative() / 1000000.0 - pts; currentTime = pts; diff = 0; } //時(shí)間早了延時(shí) if (diff < 0) { //小于閾值,修正延時(shí),避免延時(shí)過大導(dǎo)致程序卡死 if (diff< -0.1) { diff =-0.1; } av_usleep(-diff * 1000000); currentTime = av_gettime_relative() / 1000000.0 -videoStartTime; diff = currentTime - pts; } //時(shí)間晚了丟幀,duration為一幀的持續(xù)時(shí)間,在一個(gè)duration內(nèi)是正常時(shí)間,加一個(gè)duration作為閾值來判斷丟幀。 if (diff > 2 * duration) { av_frame_unref(frame); av_frame_free(&frame); //此處返回即不渲染,進(jìn)行丟幀。也可以渲染追幀。 return; } //顯示視頻 略
2、同步到視頻時(shí)鐘
同步到視頻時(shí)鐘就是,按照視頻播放的pts為基準(zhǔn),每次渲染的時(shí)候都根據(jù)當(dāng)前幀的pts更新視頻時(shí)鐘。與上面的差距只是多了最底部一行時(shí)鐘更新代碼。
//更新視頻時(shí)鐘 videoStartTime = av_gettime_relative() / 1000000.0 - pts;
因?yàn)榕c上一節(jié)代碼基本一致,所以不做具體說明,直接參考上一節(jié)說明即可。
//以下變量時(shí)間單位為s //當(dāng)前時(shí)間 double currentTime = av_gettime_relative() / 1000000.0 - videoStartTime; //視頻幀的時(shí)間 double pts = frame->pts * (double)timebase.num / timebase.den; //計(jì)算時(shí)間差,大于0則late,小于0則early。 double diff = currentTime - pts; //視頻幀的持續(xù)時(shí)間,下列方法有可能會(huì)無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計(jì)算duartion。 double duration = frame->pkt_duration * (double)timebase.num / timebase.den; //大于閾值,修正時(shí)間,時(shí)鐘和視頻幀偏差超過0.1s時(shí)重新設(shè)置起點(diǎn)時(shí)間。 if (diff > 0.1) { videoStartTime = av_gettime_relative() / 1000000.0 - pts; currentTime = pts; diff = 0; } //時(shí)間早了延時(shí) if (diff < 0) { //小于閾值,修正延時(shí),避免延時(shí)過大導(dǎo)致程序卡死 if (diff< -0.1) { diff =-0.1; } av_usleep(-diff * 1000000); currentTime = av_gettime_relative() / 1000000.0 - videoStartTime; diff = currentTime - pts; } //時(shí)間晚了丟幀,duration為一幀的持續(xù)時(shí)間,在一個(gè)duration內(nèi)是正常時(shí)間,加一個(gè)duration作為閾值來判斷丟幀。 if (diff > 2 * duration) { av_frame_unref(frame); av_frame_free(&frame); //此處返回即不渲染,進(jìn)行丟幀。也可以渲染追幀。 return; } //更新視頻時(shí)鐘 videoStartTime = av_gettime_relative() / 1000000.0 - pts; //顯示視頻 略
三、同步到音頻
1、音頻時(shí)鐘的計(jì)算
要同步到音頻我們首先得計(jì)算音頻時(shí)鐘,通過音頻播放的數(shù)據(jù)長(zhǎng)度可以計(jì)算出pts。
定義兩個(gè)變量,音頻的pts,以及音頻時(shí)鐘的起始時(shí)間startTime。
//下列變量單位為秒 double audioPts=0; double audioStartTime=0;
在sdl的音頻播放回調(diào)中計(jì)算音頻時(shí)鐘。其中spec為SDL_AudioSpec是SDL_OpenAudioDevice的第四個(gè)參數(shù)。
//音頻設(shè)備播放回調(diào) static void play_audio_callback(void* userdata, uint8_t* stream, int len) { //寫入設(shè)備的音頻數(shù)據(jù)長(zhǎng)度 int dataSize; //將數(shù)據(jù)拷貝到stream 略 //計(jì)算音頻時(shí)鐘 if (dataSize > 0) { //計(jì)算當(dāng)前pts audioPts+=(double) (dataSize)*/ (spec.freq * av_get_bytes_per_sample(forceFormat) * spec.channels); //更新音頻時(shí)鐘 audioStartTime= = av_gettime_relative() / 1000000.0 -audioPts; } }
2、同步到音頻時(shí)鐘
有了音頻時(shí)鐘后,我們需要將視頻同步到音頻,在二、2的基礎(chǔ)上加入同步邏輯即可。
//同步到音頻 double avDiff = 0; avDiff = videoStartTime - audioStartTime; diff += avDiff;
完整代碼
//以下變量時(shí)間單位為s //當(dāng)前時(shí)間 double currentTime = av_gettime_relative() / 1000000.0 - videoStartTime; //視頻幀的時(shí)間 double pts = frame->pts * (double)timebase.num / timebase.den; //計(jì)算時(shí)間差,大于0則late,小于0則early。 double diff = currentTime - pts; //視頻幀的持續(xù)時(shí)間,下列方法有可能會(huì)無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計(jì)算duartion。 double duration = frame->pkt_duration * (double)timebase.num / timebase.den; //同步到音頻 double avDiff = 0; avDiff = videoStartTime - audioStartTime; diff += avDiff; //大于閾值,修正時(shí)間,時(shí)鐘和視頻幀偏差超過0.1s時(shí)重新設(shè)置起點(diǎn)時(shí)間。 if (diff > 0.1) { videoStartTime = av_gettime_relative() / 1000000.0 - pts; currentTime = pts; diff = 0; } //時(shí)間早了延時(shí) if (diff < 0) { //小于閾值,修正延時(shí),避免延時(shí)過大導(dǎo)致程序卡死 if (diff< -0.1) { diff =-0.1; } av_usleep(-diff * 1000000); currentTime = av_gettime_relative() / 1000000.0 - videoStartTime; diff = currentTime - pts; } //時(shí)間晚了丟幀,duration為一幀的持續(xù)時(shí)間,在一個(gè)duration內(nèi)是正常時(shí)間,加一個(gè)duration作為閾值來判斷丟幀。 if (diff > 2 * duration) { av_frame_unref(frame); av_frame_free(&frame); //此處返回即不渲染,進(jìn)行丟幀。也可以渲染追幀。 return; } //更新視頻時(shí)鐘 videoStartTime = av_gettime_relative() / 1000000.0 - pts; //顯示視頻 略
總結(jié)
就是今天要講的內(nèi)容,本文簡(jiǎn)單介紹了幾種視頻時(shí)鐘同步的方法,不算特別難,但是在網(wǎng)上查找的資料比較少??梢詤⒖嫉膄fplay的實(shí)現(xiàn)也有點(diǎn)復(fù)雜,本文的實(shí)現(xiàn)部分借鑒了ffplay。本文實(shí)現(xiàn)的時(shí)鐘同步還是可以繼續(xù)優(yōu)化的,比如用pid進(jìn)行動(dòng)態(tài)控制。以及duration的計(jì)算可以細(xì)化調(diào)整。
到此這篇關(guān)于C語言 ffmpeg與sdl實(shí)現(xiàn)播放視頻同時(shí)同步時(shí)鐘詳解的文章就介紹到這了,更多相關(guān)C語言 ffmpeg與sdl內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中Copy-Swap實(shí)現(xiàn)拷貝交換
本文主要介紹了C++中Copy-Swap實(shí)現(xiàn)拷貝交換,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07C++連接mysql的方法(直接調(diào)用C-API)
首先安裝mysql,點(diǎn)完全安裝,才能在在安裝目錄include找到相應(yīng)的頭文件,注意,是完全安裝,需要的朋友可以參考下2017-06-06C語言數(shù)據(jù)類型與sizeof關(guān)鍵字
這篇文章主要介紹了C語言數(shù)據(jù)類型與sizeof關(guān)鍵字,C語言的數(shù)據(jù)類型包括基本類型、構(gòu)造類型、指針類型以及空類型,下文更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-04-04C語言實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)停車場(chǎng)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11