網站首頁 編程語言 正文
前言
視頻的時鐘同步有時是很難理解的,甚至知道了理論并不能確保實現,需要通過實踐獲取各種參數以及具體的實現邏輯。本文將介紹一些視頻時鐘同步的具體實現方式。
一、直接延時
我們播放視頻是可以直接延時的,這種方式比較不準確,但是也算是一種初級的方法。
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
相關推薦
- 2023-03-22 一文帶你學會Python?Flask框架設置響應頭_python
- 2023-05-21 Golang?flag包的具體使用_Golang
- 2022-04-06 如何用C#實現SAGA分布式事務_C#教程
- 2022-10-31 ViewPager實現圖片切換效果_Android
- 2022-08-26 Python中def()函數的實戰練習題_python
- 2022-12-13 Android?Loop機制中Looper與handler詳細分析_Android
- 2022-02-23 MAC清除所有的mobileprovision
- 2022-05-15 C++中類的轉換函數你了解嗎_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支