音视频学习之ffmpeg时间戳相关整理(时间基tbr,tbn,tbc)
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探測媒體?件格式,可以了解文件中使用的相關編碼,如下:
3.3 內部時間基AV_TIME_BASE
除以上三種時間基外,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內部函數處理,使?此時間基計算得到時間值表示的是微秒。
3.4 時間值形式轉換
===》**av_q2d()**將時間從AVRational形式轉換為double形式(分數轉double)。
===》AVRational是分數類型,double是雙精度浮點數 類型,轉換的結果單位是秒。
===》轉換前后的值基于同?時間基,僅僅是數值的表現形式不同?已。
av_q2d()實現如下:
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);3.5 時間基轉換函數
===》**av_rescale_q()**?于不同時間基的轉換,?于將時間值從?種時間基轉換為另?種時間基。
===》 將a數值由 bq時間基轉成 cq的時間基,
===》 通過返回結果獲取以cq時間基表示的新數值。
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;??av_rescale_rnd()?是計算 “a * b / c” 的值并分五種?式來取整
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) //它的作?是計算 "a * b / c" 的值并分五種?式來取整:AV_ROUND_ZERO = 0, // Round toward zero. 趨近于0, round(2.5) 為 2, ?round(-2.5) 為 -2 AV_ROUND_INF = 1, // Round away from zero. 趨遠于0 round(3.5)=4, round(-3.5)=-4 AV_ROUND_DOWN = 2, // Round toward -infinity.向負?窮??向 [-2.9, -1.2, 2.4, 5.6,7.0, 2.4] -> [-3, -2, 2, 5, 7, 2] AV_ROUND_UP = 3, // Round toward +infinity. 向正?窮??向[-2.9, -1.2, 2.4, 5.6,7.0, 2.4] -> [-2, -1, 3, 6, 7, 3] AV_ROUND_NEAR_INF = 5, // Round to nearest and halfway cases away from zero. // 四舍五?,?于0.5取值趨向0,?于0.5取值趨遠于0??av_packet_rescale_ts()?于將AVPacket中各種時間值從?種時間基轉換為另?種時間基。
void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst); //將AVPacket中各種時間值從?種時間基轉換為另?種時間基。3.6 轉封裝過程中的時間基轉換
容器中的時間基(AVStream.time_base,3.2節中的tbn)定義如下:
typedef struct AVStream {......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 ,是容器中的時間基例如:flv封裝格式的time_base為{1,1000},ts封裝格式的time_base為{1,90000}
我們編寫程序將flv封裝格式轉換為ts封裝格式,抓取原?件(flv)的前四幀顯示時間戳:
#抓取原?件(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.0800003.7 轉碼過程中的時間基轉換
編解碼器中的時間基(AVCodecContext.time_base,3.2節中的tbc)定義如下:
typedef struct AVCodecContext {......AVRational time_base;...... }//AVCodecContext.time_base是幀率(視頻幀)的倒數,每幀時間戳遞增1,那么tbc就等于幀率。 // 編碼過程中,應由?戶設置好此參數。 // 解碼過程中,此參數已過時,建議直接使?幀率倒數?作時間基。// 這?有?個問題:按照此處注釋說明,幀率為25的視頻流,tbc理應為25,但實際值卻為50,不知作何解釋?是否tbc已經過時,不具參考意義// 根據注釋中的建議, //實際使?時,在視頻解碼過程中,我們不使?AVCodecContext.time_base,??幀率倒數作時間基 // 在視頻編碼過程中,我們將AVCodecContext.time_base設置為幀率的倒數。3.7.1 視頻流
? 視頻按幀播放,所以解碼后的原始視頻幀時間基為 1/framerate。
??視頻解碼過程中的時間基轉換處理(該段沒有參考意義,packet的pts到底什么,要看實際的 情況,從av_read_frame讀取的packet,是以AVSteam->time_base,送給解碼器之前沒有必要轉成 AVCodecContext->time_base, 需要注意的是avcodec_receive_frame后以AVSteam->time_base為 單位即可。):
//視頻解碼時間基轉換處理: 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);??視頻編碼過程中的時間基轉換處理(編碼的時候frame如果以AVstream為time_base送編碼器, 則avcodec_receive_packet讀取的時候也是以轉成AVSteam->time_base,本質來講就是具體情況具體 分析,沒必要硬套流程):
//視頻編碼時間基轉換處理: 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 ?頻流
??對于?頻流也是類似的:
??===> ?如ffplay 解碼播放時就是AVSteam的time_base為基準的packet進?到編碼器,然后出來的frame再?AVSteam的 time_base講對應的pts轉成秒,
??===>但是要注意的是ffplay做了?個?較隱秘的設置:avctx- >pkt_timebase = ic->streams[stream_index]->time_base;
??===>即是對應的codeccontext??對 pkt_timebase設置和AVStream?樣的time_base。
? ?頻按采樣點播放,所以解碼后的原始?頻幀時間基為 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);轉自:音視頻學習之時間戳相關整理(時間基tbr,tbn,tbc)_yun6853992的博客-CSDN博客?
總結
以上是生活随笔為你收集整理的音视频学习之ffmpeg时间戳相关整理(时间基tbr,tbn,tbc)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 20220417在WIN10下给串口板F
- 下一篇: 谈谈代码风格