ffmpeg rtp時間戳

ffmpeg rtp時間戳

1、介紹

在ffmpeg中,每幀都會存在一個pts用來表示該幀圖像在視頻流中的位置。而在多路流(好比視頻、音頻)時,每每須要進行多媒體的同步,使得畫面和聲音同步,這時便須要使用二者的pts來作同步。那麼pts是如何計算獲得的呢,如何使用它作同步呢?html

1.1 時間基轉換

ffmpeg中時間存在一個基,能夠理解成單位,好比把1s分紅1000000等份,每一個等份就是1us,那麼1s就能夠表示成1000000;而若是把1s分紅90000等份,那麼1s的值就是90000。網絡

基的轉換,把a從b基轉到c基,計算公式爲:,好比2s以1000000爲基則是2000000,轉換成以90000爲基,則有2000000 / 1000000 * 90000 = 180000。app

ffmpeg中提供了兩個函數用於基的轉換,能夠更好地處理溢出與round問題:tcp

av_rescale(a, b, c): 時間基從c -- > b。b, c能夠直接是數字。
av_rescale_q(a, b, c): 時間基從 b --> c。 b,c須要使用AVRaional結構。ide

1.2 時間戳類型

ffmpeg中經常使用的幾個時間戳:
rtcp_ntp_timestamp: 真實時間, 絕對時間,在網絡傳輸時的時間基(1 << 32),
rtcp_timestamp: rtcp時間,通常會有一個base, 在網絡傳輸時的時間基90000
rtp_timestamp: rtp時間,和rtcp_timestamp相似,網絡時間基90000
Avpacket->pts: 經過如上計算獲得,video通常是以90000爲基svg

1.3 pts剛開始爲負值

爲何剛開始的Avpacket->pts的值是負的?
由於咱們實現時,rtcp_timestamp是使用clock_gettime()獲取當前時間,而rtp_timestamp是用的h264 buffer裏的時間, 因此rtp_timestamp < rtcp_timestamp, 而又是以rtcp_timestamp爲基準0, 因此出現了剛開始幀的pts爲負值。將rtcp_timestamp和rtp_timestamp使用相同的值,pts則從0開始。函數

2、Encode

在推流時,要將rtcp時間戳、rtp時間戳寫入到包中,以供客戶端解析,下面介紹如何將三個值寫入。測試

rtsp encode
rtsp encode

2.1 rtcp編碼時間戳:

這裏寫了兩個時間戳,這個值咱們實現的時候是以clock_gettime()獲取的時間戳,在此基礎上分別計算rtcp_ntp_time和rtcp_time:ui

rtcp_ntp_timestamp: 以爲基

rtcp_send_sr(s1, av_get_cur_time());

#define NTP_TO_RTP_FORMAT(x) av_rescale((x), INT64_C(1) << 32, 1000000)
val = NTP_TO_RTP_FORMAT(ntp_time);
*((int *)&rtcp_header[8]) = htonl(val >> 32);
*((int *)&rtcp_header[12]) = htonl(val & 0xffffffff);

last_rtcp_timestamp: 從1000000rescale到90000

咱們這邊又再加上一個隨機值base_timestamp,這個base_timestamp一次鏈接中是不變的:編碼

rtp_ts = av_rescale_q(ntp_time, (AVRational){1, 1000000},
                      s1->streams[0]->time_base) + s->base_timestamp;
*((int *)&rtcp_header[16]) = htonl(rtp_ts);

2.2 rtp編碼時間戳:

在咱們的實現中,rtp時間戳是由輸入packet的pts計算獲得,而packet.pts最開始是h264 buffer的timestamp 從1000000rescale到90000:

packet.pts = av_rescale_q(packet.pts,
				in->time_base,
				out->time_base);
				
    s->cur_timestamp = s->base_timestamp + pkt->pts;

把cur_timestamp寫入到 rtp包中:

s->timestamp = s->cur_timestamp;
    
    *((short *)&rtp_header[2]) = htons(s->seq);
    *((int *)&rtp_header[4]) = htonl(s->timestamp);
    *((int *)&rtp_header[8]) = htonl(s->ssrc);

能夠看到rtcp_time和rtp_time都是以90000以基,而rtcp_ntp_time是爲基,因此在使用rtcp_ntp_time時要注意基的轉換。

3、Decode

ffmpeg rtsp, rtp解碼主要流程:

rtsp decode
rtsp decode

3.1 解析 rtp packet:

讀取的代碼在libavofrmat/rtpdec.c --> rtp_parse_packet_internal()函數中:

seq       = AV_RB16(buf + 2);
timestamp = AV_RB32(buf + 4);
ssrc      = AV_RB32(buf + 8);

讀出的timestamp會傳入到finalize_packet中計算pts,以下方式傳入:

// now perform timestamp things....
finalize_packet(s, pkt, timestamp);

固然只有rtp_time仍是不夠的,還須要rtcp_time,在多個流中還須要rtcp_ntp_time作多個流之間的同步。

3.2 解析rtcp時間戳:

rtpdec.c --> rtcp_parse_packet()函數中:

s->last_rtcp_ntp_time  = AV_RB64(buf + 8);
s->last_rtcp_timestamp = AV_RB32(buf + 16);
if (s->first_rtcp_ntp_time == AV_NOPTS_VALUE) {
    s->first_rtcp_ntp_time = s->last_rtcp_ntp_time;
    if (!s->base_timestamp)
        s->base_timestamp = s->last_rtcp_timestamp;
    s->rtcp_ts_offset = (int32_t)(s->last_rtcp_timestamp - s->base_timestamp);
}

其中,last_rtcp_ntp_time是ntp時間戳,last_rtcp_timestamp是rtcp時間戳,這兩個值會在rtcp同步時進行更新。

第一次的時候,會執行s->first_rtcp_ntp_time = s->last_rtcp_ntp_time; ,first_rtcp_ntp_time一旦會一直保持這個值不變,後面rtcp同步的時候只會修改last_rtcp_ntp_time。

另外,s->base_timestamp = s->last_rtcp_timestamp, 這個值也會一直不變,有了這兩個基準,其它的就是要和這兩個比較,最後計算出pts。

從上面的計算方式能夠知道,rtcp_ts_offset爲0,這個值在一個流中也不會變,不過不一樣流之間或許有差異。

3.3 pts計算

av_read_frame會返回一個Avpacket對象,其中的pts變量存儲了計算後的時間戳,計算方式在rtpdec.c --> finalize_packet()函數中,以下,分兩種狀況:

1. 若是是多路(如同時包含video, audio)

咱們知道,傳入的timestamp是rtp時間戳,須要使用ntp時間作同步:

delta_timestamp = timestamp - s->last_rtcp_timestamp;
/* convert to the PTS timebase */
addend = av_rescale(s->last_rtcp_ntp_time - s->first_rtcp_ntp_time, 
            s->st->time_base.den, (uint64_t) s->st->time_base.num << 32);
pkt->pts = s->range_start_offset + s->rtcp_ts_offset + addend + delta_timestamp;

range_start_offset = 0
rtcp_ts_offset = 0
addend: 最後一次rtcp同步的ntp時間 - first_rtcp_ntp_time,至關於作了一次ntp time同步,能夠清除以前的rtp計算累積的偏差
delta_timestamp: rtp時間戳 - 最後一次rtcp同步的rtcp時間

multi stream sync
multi stream sync

測試打印:

printf("multi stream: %ld, range_start_off: %ld, rtcp_ts_offset: %ld,addend: %ld, last timestamp: %ld, timestamp: %ld, dalta: %ld, rescale: %ld\n", pkt->pts, s->range_start_offset, s->rtcp_ts_offset, addend, s->last_rtcp_timestamp, timestamp, delta_timestamp, av_rescale(pkt->pts - old_pts, 1e6, 90000));

輸出:

multi stream: 71279, range_start_off: 0, rtcp_ts_offset: 0,addend: 0, last timestamp: 3619407542, timestamp: 3619478821, dalta: 71279, rescale: 0

2. 若是是單路:

單路計算pts代碼以下,能夠看到單路不須要用到rtcp_ntp_time,只須要rtcp_time, rtp_time就能夠了:

/* unwrapped是rtp時間累加 */
s->unwrapped_timestamp += (int32_t)(timestamp - s->timestamp);
/* unwrapped時間最後要減去rtcp_base_time */
pkt->pts     = s->unwrapped_timestamp + s->range_start_offset - s->base_timestamp;

unwrapped_timestamp: 若是是第1幀,則爲第1幀的rtp_time, 以後的值是當前幀與上一幀差rtp_time逐漸累加的結果,那麼,實際上通常狀況下unwrapped_timestamp就等於當前幀的rtp_time
range_start_offset是0,
base_timestamp是rtcp解析時最初的rtcp_timestamp

single stream
single stream

測試打印:

printf("single stream: %ld, base: %u, unwrapped: %ld, range: %ld, last timestamp:%ld, timestamp: %ld\n", pkt->pts, s->base_timestamp, s->unwrapped_timestamp, s->range_start_offset, old_timestamp, timestamp);

輸出:

single stream: 6321193, base: 3079643606, unwrapped: 3085964799, range: 0, last timestamp:3085964799, timestamp: 3085964799
single stream: 6325714, base: 3079643606, unwrapped: 3085969320, range: 0, last timestamp:3085969320, timestamp: 3085969320

4、Reference

https://www.cnblogs.com/yinxiangpei/articles/3892982.html
http://www.cppblog.com/gtwdaizi/articles/65515.html

相關文章
相關標籤/搜索