1. 程式人生 > >新手學習FFmpeg - 呼叫API計算關鍵幀渲染時間點

新手學習FFmpeg - 呼叫API計算關鍵幀渲染時間點

通過簡單的計算來,線上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)
        ...
    }
}