日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言?ffmpeg與sdl實現播放視頻同時同步時鐘詳解_C 語言

作者:CodeOfCC ? 更新時間: 2022-11-14 編程語言

前言

視頻的時鐘同步有時是很難理解的,甚至知道了理論并不能確保實現,需要通過實踐獲取各種參數以及具體的實現邏輯。本文將介紹一些視頻時鐘同步的具體實現方式。

一、直接延時

我們播放視頻是可以直接延時的,這種方式比較不準確,但是也算是一種初級的方法。

1、根據幀率延時

每渲染一幀都進行一個固定的延時,這個延時的時間是通過幀率計算得來。

//獲取視頻幀率
AVRational framerate = play->formatContext->streams[video->decoder.streamIndex]->avg_frame_rate;
//根據幀率計算出一幀延時
double duration = (double)framerate.num / framerate.den;
//顯示視頻
略
//延時
av_usleep(duration* 1000000);

2、根據duration延時

每渲染一幀根據其duration進行延時,這個duration在視頻的封裝格式中通常會包含。

//獲取當前幀的持續時間,下列方法有可能會無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計算duartion?;蛘咦约簩崿F多幀估算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);

二、同步到時鐘

注:這一部分講的只有視頻時鐘同步不涉及音頻。

上面的簡單延時一定程度可以滿足播放需求,但也有問題,不能確保時間的準確性。尤其是解復用解碼也會消耗時間,單純的固定延時會導致累計延時。這個時候我們就需要一個時鐘來校準每一幀的播放時間。

1、同步到絕對時鐘

比較簡單的方式是按絕對的方式同步到時鐘,即視頻有多長,開始之后一定是按照系統時間的長度播放完成,如果視頻慢了則會不斷丟幀,直到追上時間。

定義一個視頻起始時間,在播放循環之外的地方。

//視頻起始時間,單位為秒
double videoStartTime=0;

播放循環中進行時鐘同步。下列代碼的frame為解碼的AVFrame。

//以下變量時間單位為s	
//當前時間
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;
//視頻幀的持續時間,下列方法有可能會無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計算duartion。
double duration = frame->pkt_duration * (double)timebase.num / timebase.den;
//大于閾值,修正時間,時鐘和視頻幀偏差超過0.1s時重新設置起點時間。
if (diff > 0.1)
{
	videoStartTime = av_gettime_relative() / 1000000.0 - pts;
	currentTime = pts;
	diff = 0;
}
//時間早了延時
if (diff < 0)
{
    //小于閾值,修正延時,避免延時過大導致程序卡死
    if (diff< -0.1)
    {
	    diff =-0.1;
    }
	av_usleep(-diff * 1000000);
	currentTime = av_gettime_relative() / 1000000.0 -videoStartTime;
	diff = currentTime - pts;
}
//時間晚了丟幀,duration為一幀的持續時間,在一個duration內是正常時間,加一個duration作為閾值來判斷丟幀。
if (diff > 2 * duration)
{
	av_frame_unref(frame);
	av_frame_free(&frame);
	//此處返回即不渲染,進行丟幀。也可以渲染追幀。
	return;
}
//顯示視頻
略

2、同步到視頻時鐘

同步到視頻時鐘就是,按照視頻播放的pts為基準,每次渲染的時候都根據當前幀的pts更新視頻時鐘。與上面的差距只是多了最底部一行時鐘更新代碼。

//更新視頻時鐘
videoStartTime = av_gettime_relative() / 1000000.0 - pts;

因為與上一節代碼基本一致,所以不做具體說明,直接參考上一節說明即可。

//以下變量時間單位為s	
//當前時間
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;
//視頻幀的持續時間,下列方法有可能會無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計算duartion。
double duration = frame->pkt_duration * (double)timebase.num / timebase.den;
//大于閾值,修正時間,時鐘和視頻幀偏差超過0.1s時重新設置起點時間。
if (diff > 0.1)
{
	videoStartTime = av_gettime_relative() / 1000000.0 - pts;
	currentTime = pts;
	diff = 0;
}
//時間早了延時
if (diff < 0)
{
    //小于閾值,修正延時,避免延時過大導致程序卡死
    if (diff< -0.1)
    {
	    diff =-0.1;
    }
	av_usleep(-diff * 1000000);
	currentTime = av_gettime_relative() / 1000000.0 - videoStartTime;
	diff = currentTime - pts;
}
//時間晚了丟幀,duration為一幀的持續時間,在一個duration內是正常時間,加一個duration作為閾值來判斷丟幀。
if (diff > 2 * duration)
{
	av_frame_unref(frame);
	av_frame_free(&frame);
	//此處返回即不渲染,進行丟幀。也可以渲染追幀。
	return;
}
//更新視頻時鐘
videoStartTime = av_gettime_relative() / 1000000.0 - pts;
//顯示視頻
略

三、同步到音頻

1、音頻時鐘的計算

要同步到音頻我們首先得計算音頻時鐘,通過音頻播放的數據長度可以計算出pts。

定義兩個變量,音頻的pts,以及音頻時鐘的起始時間startTime。

//下列變量單位為秒
double audioPts=0;
double audioStartTime=0;

在sdl的音頻播放回調中計算音頻時鐘。其中spec為SDL_AudioSpec是SDL_OpenAudioDevice的第四個參數。

//音頻設備播放回調
static void play_audio_callback(void* userdata, uint8_t* stream, int len) {
    //寫入設備的音頻數據長度
    int dataSize;
    //將數據拷貝到stream
    略
    //計算音頻時鐘
    if (dataSize > 0)
	{
	  //計算當前pts
      audioPts+=(double) (dataSize)*/ (spec.freq * av_get_bytes_per_sample(forceFormat) * spec.channels);
      //更新音頻時鐘
      audioStartTime= = av_gettime_relative() / 1000000.0 -audioPts;
    }
}

2、同步到音頻時鐘

有了音頻時鐘后,我們需要將視頻同步到音頻,在二、2的基礎上加入同步邏輯即可。

//同步到音頻	
double avDiff = 0;
avDiff = videoStartTime - audioStartTime;
diff += avDiff;

完整代碼

//以下變量時間單位為s	
//當前時間
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;
//視頻幀的持續時間,下列方法有可能會無法獲取duration,還有其他方法獲取duration這里不具體說明,比如ffplay通過緩存多幀,來計算duartion。
double duration = frame->pkt_duration * (double)timebase.num / timebase.den;
//同步到音頻	
double avDiff = 0;
avDiff = videoStartTime - audioStartTime;
diff += avDiff;
//大于閾值,修正時間,時鐘和視頻幀偏差超過0.1s時重新設置起點時間。
if (diff > 0.1)
{
	videoStartTime = av_gettime_relative() / 1000000.0 - pts;
	currentTime = pts;
	diff = 0;
}
//時間早了延時
if (diff < 0)
{
    //小于閾值,修正延時,避免延時過大導致程序卡死
    if (diff< -0.1)
    {
	    diff =-0.1;
    }
	av_usleep(-diff * 1000000);
	currentTime = av_gettime_relative() / 1000000.0 - videoStartTime;
	diff = currentTime - pts;
}
//時間晚了丟幀,duration為一幀的持續時間,在一個duration內是正常時間,加一個duration作為閾值來判斷丟幀。
if (diff > 2 * duration)
{
	av_frame_unref(frame);
	av_frame_free(&frame);
	//此處返回即不渲染,進行丟幀。也可以渲染追幀。
	return;
}
//更新視頻時鐘
videoStartTime = av_gettime_relative() / 1000000.0 - pts;
//顯示視頻
略

總結

就是今天要講的內容,本文簡單介紹了幾種視頻時鐘同步的方法,不算特別難,但是在網上查找的資料比較少。可以參考的ffplay的實現也有點復雜,本文的實現部分借鑒了ffplay。本文實現的時鐘同步還是可以繼續優化的,比如用pid進行動態控制。以及duration的計算可以細化調整。

原文鏈接:https://blog.csdn.net/u013113678/article/details/126449924

欄目分類
最近更新