通過簡單的計算來,線上I幀在視頻中出現的時間點。 完整代碼請參考 https://andy-zhangtao.github.io/ffmpeg-examples/
名詞解釋
首先需要明確以下名詞概念:
- I/P/B 幀(具體差異請參看 https://www.jianshu.com/p/18af03556431 )
I幀: 內部編碼幀(關鍵幀)
P幀: 前向預測幀(根據I幀計算差值)
B幀: 雙向預測幀(根據I幀和P幀計算差值) - PTS: 幀顯示的時間刻度(在哪個時間點顯示此幀)
- DTS: 幀解碼的時間刻度(在哪個時間點解碼此幀)
- Timestamp: 幀在視頻內部的時間戳
- Time_base: 視頻表示時間的"刻度"
處理流程
視頻內沒有絕對時間,只有相對時間(相對視頻起始位置)。例如在播放器中看到的時間進度條"00:00:05"表示的是當前看到的幀是在相對起始時間點(00:00:00)解碼並渲染的。
而"00:00:05"只是為了讓用戶方便理解而展現出來的,在視頻內部則是使用時間戳來保存的,"00:00:05"可能相對的時間戳則是"5000000µs"(不考慮四舍五入)。
那么時間戳又是怎么計算出來的呢?此時就需要通過PTS和Time_base來配合計算。
首先來看Time_base。 Time_base好比一把尺子,上面標滿了刻度,例如(1,60)表示時間刻度就是1/60,每個時間單位就是1/60秒。 如果是(1,1000)就表示每個時間單位是1微秒。
上面說到pts是顯示的時間刻度, 也就是占用了多少時間刻度。 換算成大白話就是pts占用了多少個刻度,而time_base表示每個刻度是多長。
然而這有什么用呢?Time_base最重要的作用是用來統一”時間節奏"的。 例如視頻A編碼時采用1/1000的time_base,則某個幀的pts保存為465000。 當對視頻A進行解碼時,換成了1/9000的time_base,此時時間刻度不一致了,就需要通過pts*encode_time_base來換算成解碼時的timestamp,這樣才能保證正確解碼。
編碼實現
上面是理論介紹,下面來看如何通過代碼來計算timestamp和換算成time.
這次只需要顯示每幀的pts,time_base,time因此不需要初始化output, 只要初始化input即可。
初始化輸入源
按照前幾篇介紹的初始化思路,只需要按照打開文件
->判斷視頻流
->初始化解碼器
這樣的步驟就可以了。
+------------------------+ +-------------------------+
| avformat_open_input | ------------>|avformat_find_stream_info|
+------------------------+ +-------------------------+
|
|
|
\|/
+-----------------------------+ +-------------------------+
|avcodec_parameters_to_context| <---------| avcodec_find_decoder |
+-----------------------------+ +-------------------------+
avcodec_parameters_to_context
尤其需要關注,這個函數會根據輸入源的編碼信息來初始化用戶指定的編碼上下文。如果編碼信息不匹配或者設置錯誤時,會出現莫名的解碼錯誤。一般調用這個函數后,大多數的解碼錯誤都能消失。
計算Time
time_base是一個struct
typedef struct AVRational{
int num; ///< Numerator
int den; ///< Denominator
} AVRational;
num 表示的是分子,den表示分母。 對於time_base來說num就是1,den表示每一秒等分了多少份。 上面說過通過pts*time_base
就可以得出時間戳,所以需要計算出每個時間刻度具體代表多少,所以通過av_q2d
得出每個刻度具體值。
在循環讀入解碼后的幀數據之后,可以直接通過iframe->pts
來讀取當前幀的pts值,然后再乘以刻度值就可以得出當前時間戳iframe->pts * av_q2d(_time_base)
。
偽代碼如下:
while av_read_frame {
avcodec_send_packet
...
while avcodec_receive_frame {
...
iframe->pts * av_q2d(_time_base)
...
}
}