一. 概述
用於將視頻移動到指定的關鍵幀位置。
二. 函數說明
/**
* 移動視頻到指定的關鍵幀位置
*
* @param s 輸入媒體的上下文
* @param stream_index seek的流索引。就是seek時究竟是以音頻流索引還是以視頻流索引為基准進行seek
* @param timestamp 起始位置的時間戳,單位是AVStream.time_base
* @param flags seek的具體策略
* @return >= 0 on success
*/
int av_seek_frame(AVFromatContext *s, int stream_index, int64_t timestmap, int flags);
第二個參數
timestamp
的詳細說明:這里的
timestamp
代表的是想要移動到的起始位置的時間戳
,注意這里是起始位置的時間戳
,不是起始位置的秒數
! 通俗地講,它就是起始位置的pts
,因此一個10s的視頻,你想移動到5s的位置,直接傳5是不對的。在 FFmpeg 中,時間戳(timestamp)
的單位是時間基數(time_base)
,時間戳
值乘以時間基
,可以得到實際的時刻值
(以秒等為單位)。例如,如果一個視頻幀的dts
是 40,pts
是 160,其time_base
是1/1000
秒,那么可以計算出此視頻幀的解碼時刻是40 毫秒(40/1000)
,顯示時刻是160 毫秒(160/1000)
。FFmpeg 中時間戳(pts/dts)的類型是int64_t
類型,如果把一個time_base
看作一個時鍾脈沖,那么 dts/pts 則可以看作是時鍾脈沖的計數。第四個參數
flags
的詳細說明:該參數一共有以下四種具體取值:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward #define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes #define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes #define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame number
下面分別對其進行詳細說明:
一. AVSEEK_FLAG_BACKWARD
實際業務中有這樣一個場景:用戶經常會通過拖動視頻底部進度條的方式來跳轉到某一幀的位置,那這個時候如何確定當前幀的具體位置呢?有一種辦法就是計算當前拖動位置的百分比,然后根據當前視頻的總時長,乘以該百分比,得到當前跳轉位置的時間,然后根據該時間進行seek操作。
比方說總時長是1000ms, 那如果用戶拖動到中間的位置,那就應該seek到500ms的位置,這個時候把500ms傳入到上面的函數中進行seek就可以了。可這樣有一個問題,你把500ms傳入到上面的視頻幀當中,是否真的有pts為500ms的視頻幀呢?很難,有可能根本就沒有pts為500ms的視頻幀,這個時候可能有498,也有可能有501的,那到底是取498的呢?還是取501的呢?這個時候就要有一套策略,
AVSEEK_FLAG_BACKWARD
這個FLAG就相當於標識往后走,也就是找pts為501的視頻幀。二. AVSEEK_FLAG_BYTE
這種對應的另外一種場景:假設我想移動視頻到中間的位置,但是當前視頻文件卻沒有索引, 不過我卻知道這個視頻文件的大小(1M), 那這個時候要移動視頻到中間位置的話,其實就是應該對應在500KB左右的位置,
AVSEEK_FLAG_BYTE
這個FLAG就相當於是根據字節數來移動它的位置。三. AVSEEK_FLAG_ANY
移動視頻到任意幀的位置。也就是說,seek到的位置可能是關鍵幀,也可能是非關鍵幀,注意:如果移動到的是非關鍵幀,這個時候解碼可能會失敗(因為少了前面的關鍵幀做參考),出現的后果是可能會造成花屏。
四. AVSEEK_FLAG_FRAME
移動視頻到關鍵幀位置。與上面的FLAG不同,這種情況下會強制移動到關鍵幀位置,比如你要移動的位置是500ms, 但是在500ms沒有關鍵幀,但是在前面400ms的位置有關鍵幀,它就會移到400ms的位置,這樣的話畫面就能夠正常顯示。
注意這種策略也有一定的問題,假設當前視頻的GOP是100幀,這100幀的時長是4s, 那這樣的話拖動到這4s的任意位置,畫面可能都不會改變(由於設置了移動到關鍵幀,而一個GOP中只有第一幀才是關鍵幀),這樣會給人一個錯覺——移動失敗了,這點需要注意。