视频流媒体中视频数据的传输占据了绝大部分的带宽,如何提升编码效率、减小带宽使用、提升画面质量,成为音视频开发者努力的重点。随着互联网、流媒体技术的发展,兼容支持H.264、H.265编码器(可减少计算的复杂性、提高压缩率,并降低编码时间)已经成为迫在眉睫的事。
EasyRTMPClient是一套稳定、易用、支持重连的RTMPClient工具,以SDK形式提供,接口调用非常简单。本篇文章我们就来谈谈在研发EasyRTMPClient过程中,我们对RTMP时间戳的处理方式。
RTMP时间戳
基本概念
RTMP消息包一共分成三种类型。一类是命令(通知)消息,一类是音频消息,一类是视频消息。在实际开发过程中,我们主要关心的是音频和视频消息包的时间戳。RTMP消息分为块头和消息头,而RTMP消息包的时间戳主要存储于消息头域中的,用三个字节以大端序来存储,如果时间戳超过0xFFFFFF时,则将消息包域的时间戳域设成0xFFFFFF,然后在消息包域和负载之间会插入四节来表示时间戳,该四字节时间戳域通常称为扩展时间戳。
RTMP时间戳形式
按adobe公司已公开的rtmp资料来看,rtmp消息包的时间戳主要有两种形式,一种称为递增模式,一种称为差值模式。
递增模式
此时间戳格式与flv文件tag的时间戳保持一致。即音频、视频时间戳基于同一个时钟来进行递增。
如:A0 -> V0 ->A1 -> A2 -> V1 ….
基于此种模块,它们的时间戳值一定满足:V1 >= A2 >= A1 >= V0 >= A0。通常,在打音频或视频时间戳时,可直接取系统时间戳即可。
差值模式
此时间戳格式,则较前一种有很大不同,主要区别是,音频和视频时间戳不在基于同一时钟来处理。无论音频还是视频消息包,每次打的时间戳都是相较于同一类消息包的前一条消息的时间戳的差值,或者可以将该值理解成为某一帧的duration值。例如,在某个流中,视频是25帧,那么此时,视频帧消息包的时间戳每次打的值基本都是在40左右(40=1000ms / 25f, 实际情况可能距该值上浮或下降几毫秒),音频每包时间戳跟视频包同理。
EasyRTMPClient中的处理
在实际的开发过程当中,我们通常无法预知对端的流是采用以上何种时间戳格式来进行处理的(当然,可以通过RTMP协商的时候进行约束)。为了减少上层开发的工作量,EasyRTMPClient在实际开发过程中,对上述两种时间戳都进行了兼容。输出统一的格式,与flv文件tag时间戳保持一致,即使用递增模式。
在RTMP中,音频包时间戳和视频包时间戳处理方法基本是一致的,因此,下面只贴出EasyRTMPClient对上述两种时间戳的视频处理代码为:
if ( 0xFFFFFF != pkt->timestamp )
{
pkt->extendTimestamp = pkt->timestamp;
}
s = pkt->extendTimestamp / 1000;
us = ( pkt->extendTimestamp % 1000 ) * 1000;
if ( 0 == easyRtmp->preTimestamp.s && 0 == easyRtmp->preTimestamp.us )
{
easyRtmp->preTimestamp.s = s;
easyRtmp->preTimestamp.us = us;
}
else
{
if ( s >= easyRtmp->preTimestamp.s && us >= easyRtmp->preTimestamp.us )
{
goto _WRITE_TIMESTAMP;
}
ms = easyRtmp->videoTimestamp.s * 1000 + easyRtmp->videoTimestamp.us / 1000;
ms += pkt->extendTimestamp;
s = ms / 1000;
us = ( ms % 1000 ) * 1000;
easyRtmp->videoTimestamp.s = s;
easyRtmp->videoTimestamp.us = us;
}
_WRITE_TIMESTAMP:
easyRtmp->preTimestamp.s = s;
easyRtmp->preTimestamp.us = us;