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í)鐘同步不涉及音頻。
上面的簡單延時(shí)一定程度可以滿足播放需求,但也有問題,不能確保時(shí)間的準(zhǔn)確性。尤其是解復(fù)用解碼也會(huì)消耗時(shí)間,單純的固定延時(shí)會(huì)導(dǎo)致累計(jì)延時(shí)。這個(gè)時(shí)候我們就需要一個(gè)時(shí)鐘來校準(zhǔn)每一幀的播放時(shí)間。
1、同步到絕對時(shí)鐘
比較簡單的方式是按絕對的方式同步到時(shí)鐘,即視頻有多長,開始之后一定是按照系統(tǒng)時(shí)間的長度播放完成,如果視頻慢了則會(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ù)長度可以計(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ù)長度
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)容,本文簡單介紹了幾種視頻時(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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中Copy-Swap實(shí)現(xiàn)拷貝交換
本文主要介紹了C++中Copy-Swap實(shí)現(xiàn)拷貝交換,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
C++連接mysql的方法(直接調(diào)用C-API)
首先安裝mysql,點(diǎn)完全安裝,才能在在安裝目錄include找到相應(yīng)的頭文件,注意,是完全安裝,需要的朋友可以參考下2017-06-06
C語言數(shù)據(jù)類型與sizeof關(guān)鍵字
這篇文章主要介紹了C語言數(shù)據(jù)類型與sizeof關(guān)鍵字,C語言的數(shù)據(jù)類型包括基本類型、構(gòu)造類型、指針類型以及空類型,下文更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-04-04

