1. 程式人生 > 其它 >FFmpeg開發——深入理解pts,dts和timebase

FFmpeg開發——深入理解pts,dts和timebase

概述

本文將以具體視訊播放器開發過程中遇到的具體問題,來系統地闡釋pts,dts和timebase的概念。

1.時間基

FFmpeg開發中,經常會遇到結構體中有time_base這個成員,通過標頭檔案檢視他的型別是AVRational

typedef struct AVRational{
    int num; ///< numerator
    int den; ///< denominator
} AVRational;

那麼AVRational到底表示了什麼呢?

AVRational這個結構標識一個分數,num為分子,den為分母。實際上time_base的意思就是時間的刻度。

如果把1秒分為25等份,你可以理解就是一把尺,那麼每一格表示的就是1/25秒。此時的time_base={1,25}。如果你是把1秒分成90000份,每一個刻度就是1/90000秒,此時的time_base={1,90000}。所謂時間基表示的就是每個刻度是多少秒。
那麼,在刻度為1/25的體系下的time=5,轉換成在刻度為1/90000體系下的時間time為(5x1/25)/(1/90000) = 3600*5=18000

正是由於不同的封裝格式,timebase是不一樣的。另外,整個轉碼過程,不同的資料狀態對應的時間基也不一致。所以在實際開發過程中,存在著大量時間基的轉換。

2.PTS和DTS

PTS:Presentation Time Stamp。PTS 主要用於度量解碼後的視訊幀什麼時候被顯示出來。
DTS

:Decode Time Stamp。DTS 主要是標識讀入記憶體中的Bit流在什麼時候開始送入解碼器中進行解碼。

雖然 DTS、PTS 是用於指導播放端的行為,但它們是在編碼的時候由編碼器生成的。當視訊流中沒有 B 幀時,通常 DTS 和 PTS 的順序是一致的。但如果有 B 幀時,解碼順序和播放順序不一致了。

來看一個具體的例子,利用雷神做的videoeye視訊碼流分析軟體,來對視訊檔案進行分析,這個檔案是mp4格式的,可以看到視訊碼流PTS在遞增,這就是我們看到畫面的順序,但是碼流順序並不是遞增的,這裡的碼流順序可理解為解碼的順序,也就是DTS表示的意思,先解I幀,再解P幀,依次解中間的B幀

而音訊的解碼順序就是我們依次聽到的順序,PTS和DTS相等


怎麼理解PTS數值表達的含義呢,如果有某一幀,假設它是第10秒開始顯示。那麼它的pts是多少呢。是10?還是10s?還是兩者都不是。

這就引出了pts和dts的值到底代表了什麼含義這個問題

pts和dts的值指的是佔多少個時間刻度(佔多少個格子)。它的單位不是秒,而是時間刻度。只有pts與time_base兩者結合在一起,才能表達出具體的時間是多少。好比我只告訴你,某個物體的長度佔某一把尺上的20個刻度。但是我不告訴你,每個刻度是多少釐米,你仍然無法知道物體的長度。pts 就是這樣的東西,pts(佔了多少個時間刻度) ,time_base(每個時間刻度是多少秒) ,而幀的顯示時間戳 = pts(佔了多少個時間刻度) * time_base(每個時間刻度是多少秒)。

2.一些開發過程中時間基轉換的場景

1.計算視訊總時長

AVFormatContext *ifmt_ctx = NULL;
avformat_open_input(&ifmt_ctx, filename, NULL, NULL);
int totalMs;//視訊總毫秒數
totalMs = ifmt_ctx->duration / (AV_TIME_BASE / 1000);

2.根據PTS求出一幀在視訊中對應的秒數位置

double sec = enc_pkt.pts * av_q2d(ofmt_ctx->streams[stream_index]->time_base);

3.ffmpeg內部的時間戳與標準的時間轉換方法

//timestamp為ffmpeg內部時間戳,time為正常時間戳,單位為秒
timestamp = AV_TIME_BASE * time
time = AV_TIME_BASE_Q * timestamp

AV_TIME_BASE這個巨集為1000000,由此我們可以發現ffmpeg內部時間戳是以微秒(μs)為單位的

4.當需要把視訊Seek到N秒的時候

//pos單位毫秒
double pos;
seekPos = ifmtctx->streams[videoStream]->duration * pos;
av_seek_frame(ifmtctx, videoStream, seekPos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME);