ffmpeg rtp时间戳

ffmpeg  c 

一、介绍

在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

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

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

测试打印:

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

测试打印:

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

ffmpeg rtp时间戳的更多相关文章

  1. (转)关于RTP时间戳及多媒体通信同步的问题

    下载 多媒体通信同步方法,主要有时间戳同步法.同步标记法.多路复用同步法三种.下面主要讨论时间戳同步法,特别是 RTP 时间戳同步.内容包括 RTP 媒体间同步的实现,为什么需要 RTCP 的 NTP ...

  2. RTP 时间戳的处理

    RTP 时间戳的处理   在RTP传输音频数据时,一般选定逻辑时间戳速率与采样速率相同, 但是在传输视频数据时,必须使时间戳速率大于每帧的一个滴答(这样才能使图像回放更为平滑--<用TCP/IP ...

  3. RTP时间戳

    http://xingyunbaijunwei.blog.163.com/blog/static/7653806720126121014111/ ——————————————————————————— ...

  4. 多媒体开之之rtp 时间戳和负载类型介绍

    (1)时间戳 (2)负载类型 (3)rtp 包头 (1)时间戳 有三个 一个实时间单位 timestamp_increse=(unsigned int)(90000.0 / framerate); / ...

  5. ffmpeg 按时间戳读取文件 -re

    ffmpeg读取文件有两种方式:一种是直接读取,文件被迅速读完;一种是按时间戳读取.一般都是按时间戳读取文件, 命令行加入-re,表示按时间戳读取文件,在ffmpeg_opt.c 中可以看到re对应的 ...

  6. ffmpeg rtp rtmp udp 推流命令

    推组播 组播地址指的范围是224.0.0.0—239.255.255.255 ffmpeg -re -i chunwan.h264 -vcodec mpeg2video -f mpeg2video u ...

  7. ffmpeg修复时间戳

    ffmpeg -re -i e:/media/baifa.mp4 -filter_complex -hls_wrap -hls_time d:/demo/hls/cctv13/playlist.m3u ...

  8. 关于RTP时间戳及多媒体通信同步的问题(转)

    文章转载自:罗索实验室 [http://www.rosoo.net/a/201101/10776.html]

  9. 【FFMPEG】谈谈RTP传输中的负载类型和时间戳

    谈谈RTP传输中的负载类型和时间戳 最近被RTP的负载类型和时间戳搞郁闷了,一个问题调试了近一周,终于圆满解决,回头看看,发现其实主要原因还是自己没有真正地搞清楚RTP协议中负载类型和时间戳的含义.虽 ...

随机推荐

  1. [K/3Cloud]屏蔽页签的关闭按钮

    如图,屏蔽企业门户的X关闭按钮. this.View.GetControl<TabControl>("FMainTab").SetItemCloseable(1, fa ...

  2. 解决使用myeclipse电脑卡的问题

    1. 原因:myeclipse会自动更新,因此会占用大量内存 2. 解决方法: (1)window->Perferences->General->Startup and Shutdo ...

  3. memory management in oracle 11G R2

    When we talking about memory management in Oracle, we are refering to SGA and PGA. The management me ...

  4. iOS xmpp协议实现聊天之openfire的服务端配置(二)

    本篇主要说一下怎样利用命令行来正确配置MySql. 首先打开终端: 1.为mysql起一个别名 alias mysql=/usr/local/mysql/bin/mysql 2.创建mysql的管理员 ...

  5. redis 主从备份自动切换+java代码实现类

    转载:http://blog.csdn.net/qq_23430789/article/details/52185706 目录(?)[-] redis-0sentinel实例之间的通讯端口 maste ...

  6. 喷水装置(一)(南阳oj6)(简单贪心)

    喷水装置(一) 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描写叙述 现有一块草坪,长为20米.宽为2米.要在横中心线上放置半径为Ri的喷水装置.每一个喷水装置的效果都会让 ...

  7. FFmpeg总结(三)AV系列结构体之AVCodecContext

    位置: 描写叙述:主要扩展API的结构体 New fields can be added to the end with minor version bumps. Removal, reorderin ...

  8. HDU 3008 DP

    基础DP题 打BOSS  BOSS和自己都有100点血.玩家先手 每回合能够选择施放技能攻击(耗蓝,共n种)或者普通攻击(不耗蓝,伤害为1),BOSS每回合会攻击自己q点血,每回合自己会恢复t点法力 ...

  9. wpf 禁用启用webbroswer右键菜单

    //禁用脚本错误等类似的窗口信息 this.webBrowser1.ScriptErrorsSuppressed = true; //禁用右键菜单 this.webBrowser1.IsWebBrow ...

  10. mybatis Generator生成代码及使用方式(转载)

    转载自:http://www.cnblogs.com/fengzheng/p/5889312.html 文章很棒,很不错,转了.