FFmpeg 時間戳詳解

2021-02-16 字節流動
作者:葉餘

來源:https://www.cnblogs.com/leisure_chn/p/10584910.html

1. I 幀/P 幀/B 幀

I 幀:I 幀(Intra-coded picture, 幀內編碼幀常稱為關鍵幀)包含一幅完整的圖像信息,屬於幀內編碼圖像,不含運動矢量,在解碼時不需要參考其他幀圖像。

因此在 I 幀圖像處可以切換頻道,而不會導致圖像丟失或無法解碼。I 幀圖像用於阻止誤差的累積和擴散。在閉合式 GOP 中,每個 GOP 的第一個幀一定是 I 幀,且當前 GOP 的數據不會參考前後 GOP 的數據。

P 幀:P 幀(Predictive-coded picture, 預測編碼圖像幀)是幀間編碼幀,利用之前的 I 幀或 P 幀進行預測編碼。

B 幀:B 幀(Bi-directionally predicted picture, 雙向預測編碼圖像幀)是幀間編碼幀,利用之前和(或)之後的 I 幀或 P 幀進行雙向預測編碼。B 幀不可以作為參考幀。


B 幀具有更高的壓縮率,但需要更多的緩衝時間以及更高的 CPU 佔用率,因此 B 幀適合本地存儲以及視頻點播,而不適用對實時性要求較高的直播系統。

2. DTS 和 PTS

DTS(Decoding Time Stamp, 解碼時間戳),表示壓縮幀的解碼時間。


PTS(Presentation Time Stamp, 顯示時間戳),表示將壓縮幀解碼後得到的原始幀的顯示時間。


音頻中 DTS 和 PTS 是相同的視頻中由於 B 幀需要雙向預測,B 幀依賴於其前和其後的幀,因此含 B 幀的視頻解碼順序與顯示順序不同,即 DTS 與 PTS 不同

當然,不含 B 幀的視頻,其 DTS 和 PTS 是相同的。下圖以一個開放式 GOP 示意圖為例,說明視頻流的解碼順序和顯示順序。

採集順序指圖像傳感器採集原始信號得到圖像幀的順序。
編碼順序指編碼器編碼後圖像幀的順序。存儲到磁碟的本地視頻文件中圖像幀的順序與編碼順序相同。
傳輸順序指編碼後的流在網絡中傳輸過程中圖像幀的順序。
採集順序與顯示順序相同。編碼順序、傳輸順序和解碼順序相同。
以圖中「B[1]」幀為例進行說明,「B[1]」幀解碼時需要參考「I[0]」幀和「P[3]」幀,因此「P[3]」幀必須比「B[1]」幀先解碼。這就導致了解碼順序和顯示順序的不一致,後顯示的幀需要先解碼。
3. FFmpeg 中的時間基與時間戳3.1 時間基與時間戳的概念

在 FFmpeg 中,時間基(time_base)是時間戳(timestamp)的單位,時間戳值乘以時間基,可以得到實際的時刻值(以秒等為單位)。

例如,如果一個視頻幀的 dts 是 40,pts 是 160,其 time_base 是 1/1000 秒,那麼可以計算出此視頻幀的解碼時刻是 40 毫秒(40/1000),顯示時刻是 160 毫秒(160/1000)。

FFmpeg 中時間戳(pts/dts)的類型是 int64_t 類型,把一個 time_base 看作一個時鐘脈衝,則可把 dts/pts 看作時鐘脈衝的計數。


3.2 三種時間基 tbr、tbn 和 tbc

不同的封裝格式具有不同的時間基。在 FFmpeg 處理音視頻過程中的不同階段,也會採用不同的時間基。


FFmepg 中有三種時間基,命令行中 tbr、tbn 和 tbc 的列印值就是這三種時間基的倒數:


tbn:對應容器中的時間基。值是 AVStream.time_base 的倒數
tbc:對應編解碼器中的時間基。值是 AVCodecContext.time_base 的倒數
tbr:從視頻流中猜算得到,可能是幀率或場率(幀率的 2 倍)

測試文件下載(右鍵另存為):tnmil3.flv


使用 ffprobe 探測媒體文件格式,如下:

1
2
3
4
5
6
7
8
think@opensuse> ffprobe tnmil3.flv
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, flv, from 'tnmil3.flv':
Metadata:
encoder : Lavf58.20.100
Duration: 00:00:03.60, start: 0.017000, bitrate: 513 kb/s
Stream #0:0: Video: h264 (High), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 1k tbn, 50 tbc
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s

關於 tbr、tbn 和 tbc 的說明,原文如下,來自 FFmpeg 郵件列表:

There are three different time bases for time stamps in FFmpeg. The
values printed are actually reciprocals of these, i.e. 1/tbr, 1/tbn and
1/tbc.

tbn is the time base in AVStream that has come from the container, I
think. It is used for all AVStream time stamps.

tbc is the time base in AVCodecContext for the codec used for a
particular stream. It is used for all AVCodecContext and related time
stamps.

tbr is guessed from the video stream and is the value users want to see
when they look for the video frame rate, except sometimes it is twice
what one would expect because of field rate versus frame rate.


3.3 內部時間基 AV_TIME_BASE

除以上三種時間基外,FFmpeg 還有一個內部時間基 AV_TIME_BASE(以及分數形式的 AV_TIME_BASE_Q)

1
2
3
4
5
// Internal time base represented as integer
#define AV_TIME_BASE 1000000

// Internal time base represented as fractional value
#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}

AV_TIME_BASE 及 AV_TIME_BASE_Q 用於 FFmpeg 內部函數處理,使用此時間基計算得到時間值表示的是微秒。

3.4 時間值形式轉換

av_q2d()將時間從 AVRational 形式轉換為 double 形式。AVRational 是分數類型,double 是雙精度浮點數類型,轉換的結果單位是秒。

轉換前後的值基於同一時間基,僅僅是數值的表現形式不同而已。

av_q2d()實現如下:

1
2
3
4
5
6
7
8
9
/**
* Convert an AVRational to a `double`.
* @param a AVRational to convert
* @return `a` in floating-point form
* @see av_d2q()
*/
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}

av_q2d()使用方法如下:

1
2
3
4
AVStream stream;
AVPacket packet;
packet 播放時刻值:timestamp(單位秒) = packet.pts × av_q2d(stream.time_base);
packet 播放時長值:duration(單位秒) = packet.duration × av_q2d(stream.time_base);


3.5 時間基轉換函數

av_rescale_q() 用於不同時間基的轉換,用於將時間值從一種時間基轉換為另一種時間基。

1
2
3
4
5
6
7
8
9
10
/**
* Rescale a 64-bit integer by 2 rational numbers.
*
* The operation is mathematically equivalent to `a × bq / cq`.
*
* This function is equivalent to av_rescale_q_rnd() with #AV_ROUND_NEAR_INF.
*
* @see av_rescale(), av_rescale_rnd(), av_rescale_q_rnd()
*/
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;

av_packet_rescale_ts()用於將 AVPacket 中各種時間值從一種時間基轉換為另一種時間基。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Convert valid timing fields (timestamps / durations) in a packet from one
* timebase to another. Timestamps with unknown values (AV_NOPTS_VALUE) will be
* ignored.
*
* @param pkt packet on which the conversion will be performed
* @param tb_src source timebase, in which the timing fields in pkt are
* expressed
* @param tb_dst destination timebase, to which the timing fields will be
* converted
*/
void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);


3.6 轉封裝過程中的時間基轉換

容器中的時間基(AVStream.time_base,3.2 節中的 tbn)定義如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct AVStream {
.
/**
* This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented.
*
* decoding: set by libavformat
* encoding: May be set by the caller before avformat_write_header() to
* provide a hint to the muxer about the desired timebase. In
* avformat_write_header(), the muxer will overwrite this field
* with the timebase that will actually be used for the timestamps
* written into the file (which may or may not be related to the
* user-provided one, depending on the format).
*/
AVRational time_base;
.
}

AVStream.time_base 是 AVPacket 中 pts 和 dts 的時間單位,輸入流與輸出流中 time_base 按如下方式確定:


對於輸入流:打開輸入文件後,調用 avformat_find_stream_info()可獲取到每個流中的 time_base


對於輸出流:打開輸出文件後,調用 avformat_write_header()可根據輸出文件封裝格式確定每個流的 time_base 並寫入輸出文件中

不同封裝格式具有不同的時間基,在轉封裝(將一種封裝格式轉換為另一種封裝格式)過程中,時間基轉換相關代碼如下:

1
2
3
4
av_read_frame(ifmt_ctx, &pkt);
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);

下面的代碼具有和上面代碼相同的效果:

1
2
3
4
// 從輸入文件中讀取 packet
av_read_frame(ifmt_ctx, &pkt);
// 將 packet 中的各時間值從輸入流封裝格式時間基轉換到輸出流封裝格式時間基
av_packet_rescale_ts(&pkt, in_stream->time_base, out_stream->time_base);

這裡流裡的時間基in_stream->time_base和out_stream->time_base,是容器中的時間基,就是 3.2 節中的 tbn。

例如,flv 封裝格式的 time_base 為{1,1000},ts 封裝格式的 time_base 為{1,90000}


我們編寫程序將 flv 封裝格式轉換為 ts 封裝格式,抓取原文件(flv)的前四幀顯示時間戳:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
think@opensuse> ffprobe -show_frames -select_streams v tnmil3.flv | grep pkt_pts
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, flv, from 'tnmil3.flv':
Metadata:
encoder : Lavf58.20.100
Duration: 00:00:03.60, start: 0.017000, bitrate: 513 kb/s
Stream #0:0: Video: h264 (High), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 1k tbn, 50 tbc
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 128 kb/s
pkt_pts=80
pkt_pts_time=0.080000
pkt_pts=120
pkt_pts_time=0.120000
pkt_pts=160
pkt_pts_time=0.160000
pkt_pts=200
pkt_pts_time=0.200000

再抓取轉換的文件(ts)的前四幀顯示時間戳:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
think@opensuse> ffprobe -show_frames -select_streams v tnmil3.ts | grep pkt_pts
ffprobe version 4.1 Copyright (c) 2007-2018 the FFmpeg developers
Input #0, mpegts, from 'tnmil3.ts':
Duration: 00:00:03.58, start: 0.017000, bitrate: 619 kb/s
Program 1
Metadata:
service_name : Service01
service_provider: FFmpeg
Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 784x480, 25 fps, 25 tbr, 90k tbn, 50 tbc
Stream #0:1[0x101]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 44100 Hz, stereo, fltp, 127 kb/s
pkt_pts=7200
pkt_pts_time=0.080000
pkt_pts=10800
pkt_pts_time=0.120000
pkt_pts=14400
pkt_pts_time=0.160000
pkt_pts=18000
pkt_pts_time=0.200000

可以發現,對於同一個視頻幀,它們時間基(tbn)不同因此時間戳(pkt_pts)也不同,但是計算出來的時刻值(pkt_pts_time)是相同的。


看第一幀的時間戳,計算關係:80×{1,1000} == 7200×{1,90000} == 0.080000

3.7 轉碼過程中的時間基轉換

編解碼器中的時間基(AVCodecContext.time_base,3.2 節中的 tbc)定義如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef struct AVCodecContext {
.

/**
* This is the fundamental unit of time (in seconds) in terms
* of which frame timestamps are represented. For fixed-fps content,
* timebase should be 1/framerate and timestamp increments should be
* identically 1.
* This often, but not always is the inverse of the frame rate or field rate
* for video. 1/time_base is not the average frame rate if the frame rate is not
* constant.
*
* Like containers, elementary streams also can store timestamps, 1/time_base
* is the unit in which these timestamps are specified.
* As example of such codec time base see ISO/IEC 14496-2:2001(E)
* vop_time_increment_resolution and fixed_vop_rate
* (fixed_vop_rate == 0 implies that it is different from the framerate)
*
* - encoding: MUST be set by user.
* - decoding: the use of this field for decoding is deprecated.
* Use framerate instead.
*/
AVRational time_base;

.
}

上述注釋指出,AVCodecContext.time_base 是幀率(視頻幀)的倒數,每幀時間戳遞增 1,那麼 tbc 就等於幀率。編碼過程中,應由用戶設置好此參數。解碼過程中,此參數已過時,建議直接使用幀率倒數用作時間基。

這裡有一個問題:按照此處注釋說明,幀率為 25 的視頻流,tbc 理應為 25,但實際值卻為 50,不知作何解釋?是否 tbc 已經過時,不具參考意義?

根據注釋中的建議,實際使用時,在視頻解碼過程中,我們不使用 AVCodecContext.time_base,而用幀率倒數作時間基,在視頻編碼過程中,我們將 AVCodecContext.time_base 設置為幀率的倒數。

3.7.1 視頻流

視頻按幀播放,所以解碼後的原始視頻幀時間基為 1/framerate。

視頻解碼過程中的時間基轉換處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AVFormatContext *ifmt_ctx;
AVStream *in_stream;
AVCodecContext *dec_ctx;
AVPacket packet;
AVFrame *frame;

// 從輸入文件中讀取編碼幀
av_read_frame(ifmt_ctx, &packet);

// 時間基轉換
int raw_video_time_base = av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(packet, in_stream->time_base, raw_video_time_base);

// 解碼
avcodec_send_packet(dec_ctx, packet)
avcodec_receive_frame(dec_ctx, frame);

視頻編碼過程中的時間基轉換處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AVFormatContext *ofmt_ctx;
AVStream *out_stream;
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVPacket packet;
AVFrame *frame;

// 編碼
avcodec_send_frame(enc_ctx, frame);
avcodec_receive_packet(enc_ctx, packet);

// 時間基轉換
packet.stream_index = out_stream_idx;
enc_ctx->time_base = av_inv_q(dec_ctx->framerate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_base);

// 將編碼幀寫入輸出媒體文件
av_interleaved_write_frame(o_fmt_ctx, &packet);

3.7.2 音頻流

音頻按採樣點播放,所以解碼後的原始音頻幀時間基為 1/sample_rate

音頻解碼過程中的時間基轉換處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AVFormatContext *ifmt_ctx;
AVStream *in_stream;
AVCodecContext *dec_ctx;
AVPacket packet;
AVFrame *frame;

// 從輸入文件中讀取編碼幀
av_read_frame(ifmt_ctx, &packet);

// 時間基轉換
int raw_audio_time_base = av_inv_q(dec_ctx->sample_rate);
av_packet_rescale_ts(packet, in_stream->time_base, raw_audio_time_base);

// 解碼
avcodec_send_packet(dec_ctx, packet)
avcodec_receive_frame(dec_ctx, frame);

音頻編碼過程中的時間基轉換處理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
AVFormatContext *ofmt_ctx;
AVStream *out_stream;
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVPacket packet;
AVFrame *frame;

// 編碼
avcodec_send_frame(enc_ctx, frame);
avcodec_receive_packet(enc_ctx, packet);

// 時間基轉換
packet.stream_index = out_stream_idx;
enc_ctx->time_base = av_inv_q(dec_ctx->sample_rate);
av_packet_rescale_ts(&opacket, enc_ctx->time_base, out_stream->time_base);

// 將編碼幀寫入輸出媒體文件
av_interleaved_write_frame(o_fmt_ctx, &packet);


4. 參考資料

[1]. What does the output of ffmpeg mean? tbr tbn tbc etc?
[2]. 視頻編解碼基礎概念, https://www.cnblogs.com/leisure_chn/p/10285829.html
[3]. 對 ffmpeg 的時間戳的理解筆記, https://blog.csdn.net/topsluo/article/details/76239136
[4]. ffmpeg 中的時間戳與時間基, http://www.imooc.com/article/91381
[5]. ffmpeg 編解碼中涉及到的 pts 詳解, http://www.52ffmpeg.com/article/353.html
[6]. 音視頻錄入的 pts 和 dts 問題, https://blog.csdn.net/zhouyongku/article/details/38510747

相關焦點

  • 【FFmpeg】 MP4格式文件,將MPEG4的編碼方式轉為H.264
    文章目錄MPEG-4科普軟體準備代碼詳解轉換實例MPEG-4科普MPEG-4就是我們常說的mp4,它是一種網絡視頻圖像壓縮標準。MPEG-4標準目前分為27個部分,統稱為ISO/IEC14496國際標準。
  • FFmpeg常用命令行
    >ffmpeg -i test.ts -acodec copy -vcodec copy -f mp4 output.mp4//ts視頻流轉mp4ffmpeg -i test.h264 -vcodec copy -f mpegts output.ts//h264視頻轉ts視頻流ffmpeg -i test.h264
  • ffmpeg第五彈:Qt+SDL+ffmpeg視頻播放演示
    今天分享ffmepg第五彈:ffmpeg+qt+SDL的真正開發環境,就要用源碼安裝的方式去在qt裡面調用ffmpeg相關的庫;還記得之前源碼搭建創建的三個文件夾不:bin   ffmpeg_sources    ffmpeg_buildbin文件夾下是編譯得到的二進位文件
  • ffmpeg常用命令集錦
    常用命令總結一.採集命令列出攝像頭設備ffmpeg -list_devicestrue -f dshow -i dummy列出某一個攝像頭的能力ffmpeg -list_optionstrue -f dshow -i video=「Integrated Camera」指定格式播放某一個設備
  • Nginx + rtmp + ffmpeg 搭建流媒體伺服器
    client_max_body_size 500M;    #server {    #    listen 80 default;    #    return 500;    #}    include /opt/nginx-1.9.5/conf/conf.d/*.conf;}安裝 ffmpeg
  • 實戰詳細講解ffmpeg命令的使用(來自一線的經驗,視頻合併&avi轉MP4&補空白音頻【收藏下來一定用的到】)
    ffmpeg的下載地址是:ffmpeg的下載地址 安裝過程沒啥好說的,按照提示一直點下一步就行了。這裡需要說明的一點是ffmpeg安裝好之後最好在PATH中配置ffmpeg的環境變量。配置好之後在命令行中輸入ffmpeg會出現如下結果:基本概念說明比特率:指的是每秒傳送的比特(bit)數,單位是bps。
  • ffmpeg數據結構簡介
    關閉解碼器AVCodec *     avcodec_find_decoder (enum AVCodecID id)根據id查找解碼器AVCodec *     avcodec_find_decoder_by_name (const char *name)根據名字查找解碼器結語今天的內容比較空洞,都是些api的簡單解說,有點空中閣樓的感覺。ffmpe
  • python時間戳與不同時間格式的轉換
    # 引入所需模塊(python內置模塊)import timeimport datetime# 1.時間戳轉換為指定格式的時間
  • 時間戳(Timestamp)與分布式系統的排序(Ordering)
    如果系統中只有一臺機器,那麼我們當然可以使用這個方法來確定時間戳。然而,如果系統中有許多臺機器,那麼使用每臺機器自己的時間戳通常是不夠的;因為他們之間的時鐘往往是不同步的。例如,我們手機上的時間可能會和電腦的時間不一樣。不同步時鐘帶來的問題:我們用一個簡單的例子來看不能同步的時間戳會在分布式系統中帶來何種問題。
  • Python繪製時間演變圖工具匯總(附代碼)
    之前轉載了一篇使用Python製作時間演化圖的推文,後臺留言說想要代碼,但是我也沒有那篇推文的代碼。這次就把我平時用到的繪製時間動態變化圖的工具介紹一下,同時附上代碼。雷達反射率時間演變安裝圖形優化庫yum install gifscalepip install
  • 雷射雷達與相機標定的時間戳同步問題
    知圈 | 進「汽車軟體社群」,請加微信13636581676,備註軟體相機和雷射雷達之間的時間戳同步問題一直是實時跑
  • 【網絡乾貨】NTP時間同步技術詳解
    RouterA 發送一個 NTP 報文給 RouterB,該報文中帶有它離開 RouterA 時的時間戳10:00:00a.m.(T1)。2. 此 NTP 報文到達 RouterB 時, RouterB 加上到達時間戳 11:00:01a.m.(T2)。3.
  • 戳瞎你的狗眼
    戳瞎你的狗眼現在我們的文章越來越不好寫了。別問咋的。被人搶活了
  • 音視頻傳輸:RTP協議詳解和H.264打包方案
    7. timestamp時間戳:佔32位四字節,這個單位要注意是採樣率到倒數,不是真實的時間,一般要根據採樣率進行換算。這裡反應的RTP報文第一個八位組的採樣時刻,目的是為了接收端計算延遲、抖動和音視頻同步。
  • 網絡排障工具:ping 和 tracert 技術詳解
    上一章節介紹了IP報文頭的格式 ,用wireshark抓包帶你詳解下IP報文頭,今天介紹下同屬於網絡層的ICMP協議。ICMP Echo消息常用於診斷源和目的之間的網絡連通性,還可以提供其他信息,如報文往返時間等。當網絡設備無法訪問目標時,會自動發送ICMP目的不可達報文到發送端設備。
  • 投技講解丨天命串串樂——幽蘭黛爾投技詳解
    二、反擊投技詳解 1、防禦類鐮刀死士:防禦姿態雙刀死士:防禦姿態近戰泰坦(含刷漆版,也含教父3、其他持盾崩壞獸:戳地機甲死士(永夜歐米伽):突刺攻擊(收招後) 如果大家還發現有其他可以觸發反擊投技的攻擊,歡迎在評論區留言哦~
  • 臺索「斷交」戳中美國的痛點
    臺灣與南太平洋島國索羅門群島斷交不到一周時間,傳出與另一個南太」友邦」吐瓦魯的關係也有鬆動跡象,雖然吐國新總理是否延續前任總理的親臺路線還不得而知