zoukankan      html  css  js  c++  java
  • ffmpeg rtp时间戳

    一、介绍

    在ffmpeg中,每帧都会存在一个pts用来表示该帧图像在视频流中的位置。而在多路流(比如视频、音频)时,往往需要进行多媒体的同步,使得画面和声音同步,这时便需要使用两者的pts来做同步。那么pts是如何计算得到的呢,如何使用它做同步呢?

    1.1 时间基转换

    ffmpeg中时间存在一个基,可以理解成单位,比如把1s分成1000000等份,每个等份就是1us,那么1s就可以表示成1000000;而如果把1s分成90000等份,那么1s的值就是90000。

    基的转换,把a从b基转到c基,计算公式为:,比如2s以1000000为基则是2000000,转换成以90000为基,则有2000000 / 1000000 * 90000 = 180000。

    ffmpeg中提供了两个函数用于基的转换,可以更好地处理溢出与round问题:

    av_rescale(a, b, c): 时间基从c -- > b。b, c可以直接是数字。
    av_rescale_q(a, b, c): 时间基从 b --> c。 b,c需要使用AVRaional结构。

    1.2 时间戳类型

    ffmpeg中常用的几个时间戳:
    rtcp_ntp_timestamp: 真实时间, 绝对时间,在网络传输时的时间基(1 << 32),
    rtcp_timestamp: rtcp时间,一般会有一个base, 在网络传输时的时间基90000
    rtp_timestamp: rtp时间,和rtcp_timestamp类似,网络时间基90000
    Avpacket->pts: 通过如上计算得到,video一般是以90000为基

    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开始。

    二、Encode

    在推流时,要将rtcp时间戳、rtp时间戳写入到包中,以供客户端解析,下面介绍如何将三个值写入。

    rtsp encode
    rtsp encode

    2.1 rtcp编码时间戳:

    这里写了两个时间戳,这个值我们实现的时候是以clock_gettime()获取的时间戳,在此基础上分别计算rtcp_ntp_time和rtcp_time:

    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时要注意基的转换。

    三、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
    

    四、Reference

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

  • 相关阅读:
    autocomplete自动完成搜索提示仿google提示效果
    实现子元素相对于父元素左右居中
    javascript 事件知识集锦
    让 IE9 以下的浏览器支持 Media Queries
    「2013124」Cadence ic5141 installation on CentOS 5.5 x86_64 (limited to personal use)
    「2013420」SciPy, Numerical Python, matplotlib, Enthought Canopy Express
    「2013324」ClipSync, Youdao Note, GNote
    「2013124」XDMCP Configuration for Remote Access to Linux Desktop
    「2013115」Pomodoro, Convert Multiple CD ISO to One DVD ISO HowTo.
    「2013123」CentOS 5.5 x86_64 Installation and Configuration (for Univ. Labs)
  • 原文地址:https://www.cnblogs.com/lidabo/p/15791617.html
Copyright © 2011-2022 走看看