本文爲做者原創,轉載請註明出處:http://www.javashuo.com/article/p-aeuhlfsy-dh.htmlhtml
I幀:I幀(Intra-coded picture, 幀內編碼幀,常稱爲關鍵幀)包含一幅完整的圖像信息,屬於幀內編碼圖像,不含運動矢量,在解碼時不須要參考其餘幀圖像。所以在I幀圖像處能夠切換頻道,而不會致使圖像丟失或沒法解碼。I幀圖像用於阻止偏差的累積和擴散。在閉合式GOP中,每一個GOP的第一個幀必定是I幀,且當前GOP的數據不會參考先後GOP的數據。git
P幀:P幀(Predictive-coded picture, 預測編碼圖像幀)是幀間編碼幀,利用以前的I幀或P幀進行預測編碼。github
B幀:B幀(Bi-directionally predicted picture, 雙向預測編碼圖像幀)是幀間編碼幀,利用以前和(或)以後的I幀或P幀進行雙向預測編碼。B幀不能夠做爲參考幀。
B幀具備更高的壓縮率,但須要更多的緩衝時間以及更高的CPU佔用率,所以B幀適合本地存儲以及視頻點播,而不適用對實時性要求較高的直播系統。express
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]」幀先解碼。這就致使瞭解碼順序和顯示順序的不一致,後顯示的幀須要先解碼。網絡
在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看做時鐘脈衝的計數。ide
不一樣的封裝格式具備不一樣的時間基。在FFmpeg處理音視頻過程當中的不一樣階段,也會採用不一樣的時間基。
FFmepg中有三種時間基,命令行中tbr、tbn和tbc的打印值就是這三種時間基的倒數:
tbn:對應容器中的時間基。值是AVStream.time_base的倒數
tbc:對應編解碼器中的時間基。值是AVCodecContext.time_base的倒數
tbr:從視頻流中猜算獲得,多是幀率或場率(幀率的2倍)函數
測試文件下載(右鍵另存爲):tnmil3.flv
使用ffprobe探測媒體文件格式,以下:測試
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郵件列表:ui
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.thistbn 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.
除以上三種時間基外,FFmpeg還有一個內部時間基AV_TIME_BASE(以及分數形式的AV_TIME_BASE_Q)
// 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內部函數處理,使用此時間基計算獲得時間值表示的是微秒。
av_q2d()將時間從AVRational形式轉換爲double形式。AVRational是分數類型,double是雙精度浮點數類型,轉換的結果單位是秒。轉換先後的值基於同一時間基,僅僅是數值的表現形式不一樣而已。
av_q2d()實現以下:
/** * 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()使用方法以下:
AVStream stream; AVPacket packet; packet播放時刻值:timestamp(單位秒) = packet.pts × av_q2d(stream.time_base); packet播放時長值:duration(單位秒) = packet.duration × av_q2d(stream.time_base);
av_rescale_q()用於不一樣時間基的轉換,用於將時間值從一種時間基轉換爲另外一種時間基。
/** * 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中各類時間值從一種時間基轉換爲另外一種時間基。
/** * 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);
容器中的時間基(AVStream.time_base,3.2節中的tbn)定義以下:
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並寫入輸出文件中
不一樣封裝格式具備不一樣的時間基,在轉封裝(將一種封裝格式轉換爲另外一種封裝格式)過程當中,時間基轉換相關代碼以下:
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);
下面的代碼具備和上面代碼相同的效果:
// 從輸入文件中讀取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)的前四幀顯示時間戳:
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)的前四幀顯示時間戳:
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
編解碼器中的時間基(AVCodecContext.time_base,3.2節中的tbc)定義以下:
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設置爲幀率的倒數。
視頻按幀播放,因此解碼後的原始視頻幀時間基爲 1/framerate。
視頻解碼過程當中的時間基轉換處理:
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);
視頻編碼過程當中的時間基轉換處理:
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);
音頻按採樣點播放,因此解碼後的原始音頻幀時間基爲 1/sample_rate
音頻解碼過程當中的時間基轉換處理:
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);
音頻編碼過程當中的時間基轉換處理:
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);
[1]. What does the output of ffmpeg mean? tbr tbn tbc etc?
[2]. 視頻編解碼基礎概念, http://www.javashuo.com/article/p-kfylstit-o.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問題, http://www.javashuo.com/article/p-sfpidpug-ko.html
2019-03-16 V1.0 初稿 2019-03-23 V1.1 增長3.7節