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