oRTP 是一个 RTP (Real-time Transport Protocol (RFC 3550)) 协议的库实现,它完全以 C 语言来实现,因此方便应用于各种不同的平台。本文分享用 oRTP 发送,以 Android 的 MediaCodec 编码出来的原始 H.264 码流,又称裸流的方法。
H.264 码流
MediaCode 以 H.264 编码格式编码之后的视频,是由一个一个的NALU组成的。他们的结构如下图所示。
其中每个 NALU 之间通过 startcode(起始码)进行分隔。起始码分成两种,一种是 0x000001(3Byte),另一种是 0x00000001(4Byte)。NALU 中,起始码之后,是 NALU 的类型字节,它用于描述这个 NALU 中数据的类型,NALU 的重要性等。H.264 视频流的 meta 信息等也被封装为 NALU,并以特定的类型标识 ,如 SPS 和 PPS 等描述视频流分辨率、码率等特性的信息。NALU 类型字节格式如下:
|
|
NALU 类型字节中各个字段的语义,在 ITU 的 H.264规范 中有清晰地定义,这里给出它们的简要说明:
F:1 位
forbidden_zero_bit。H.264 规范声明,值为 1 时表示语法违规。也就是数据包损坏。NRI:2 位
nal_ref_idc。这个字段用于描述该 NALU 的重要性。值为 00 表示 NALU 的内容不被用于重建图像预测的参考图像。这种 NALU 可以被丢弃而不危及参考图像的完整性。大于 00 的值表示需要解码该 NALU 来维护参考图像的完整性。Type:5 位
nal_unit_type。这个组件指定 NALU 载荷的类型,在 H.264 的表 7-1 中定义。具体的类型定义如下:
对于 NALU 的起始码,如果它对应的 Slice 为一帧的开始就用0x00000001,否则就用 0x000001。H.264 码流解析的步骤就是首先从码流中搜索 0x000001 和 0x00000001,分离出 NALU;然后再分析NALU的各个字段。在 MediaCodec API 的输出中,通常都是一帧的图像被编码为一个 NALU。不同类型的 Meta 信息也会被编码为不同的 NALU,如 SPS,PPS 等。
H.264 码流的分辨率大小不同,NALU 的类型不同等因素,导致 NALU 有着各种不同的大小。RTP 协议通常为了更高的实时性,而会选择用 UDP 作为传输层协议。但 UDP 包的大小受限于 IP 的 MTU,也就是 UDP 包加上 IP 头不能超过 IP 层的 MTU 值大小。因此在用 RTP 传输 H.264 码流时,需要适配 RTP 包的大小限制。
NALU 适配 RTP 包大小,需要分为多种情况来处理:
- NALU 非常小,多个 NALU 可以放在一个 RTP 包中传输,为了不浪费传输能力,通常需要把它们聚合在一个包中传输。
- NALU 大小与 RTP 包大小限制在同一量级,一个 RTP 包中可以放一个 NALU,但不能放多个。
- NALU 比较大,分辨率较高的视频,比如 1080P 的视频,编码出来的一帧图像可能在近 10 KB 到一两百 KB 之间,这种就需要把一个 NALU 放进多个 RTP 包中传输。
在用 RTP 传输 H.264 码流时,会为每个载荷加上一到两个字节的 RTP载荷头部,用于区分前面提到的这多种不同的情况。关于具体的 H.264 视频的 RTP 载荷格式,可以参考 H.264 视频的 RTP 载荷格式 一文,或者 IETF 的 RFC6184。
然后来看使用 ortp 发送原始 H.264 码流的方法。
oRTP 源码下载
首先需要下载 oRTP 的源码,下载地址如下:
可以通过 Git 下载最新版本的源码:
还可以下载不同发布版本打包的 .tar.gz
包:
最新版为 ortp-0.27.0.tar.gz。
oRTP 这个项目已经针对 Android 做了移植。得到源码之后,可以在
ortp/build/android
目录下找到 Android.mk
文件,可以借助于这个文件,将 oRTP 的代码集成进自己的 JNI 代码或 Android 的代码库中。
使用 ortp 发送原始 H.264 码流
使用 ortp 发送原始 H.264 码流主要需要两步,首先是初始化 RtpSession:
|
|
RTP 的使用模式,通常是接收者先 listen 在特定的端口上,然后发送者向该端口发送数据。rtp_session_set_remote_addr()
用于设置码流的接收端地址。
需要特别说明的一点是载荷类型的设置,rtp_session_set_payload_type(session, Y_PLOAD_TYPE);
,这里传入了 96
。载荷类型用于描述某种特定载荷的一些特性,如 MimeType、时钟频率、比特率等。在 RFC3551 RTP Profile for Audio and Video Conferences with Minimal Control 中定义了为具体的载荷类型分配的载荷类型编号。
在 oRTP 中,预定义了许多载荷类型的描述,如 H.263,PCMU8000,H.264 等(在文件 ortp/src/avprofile.c
中):
此外,还定义了一个表,基于 RFC3551 建立了载荷类型编号与载荷类型之间的映射关系,具体是在文件 ortp/src/avprofile.c
中的 av_profile_init()
函数里:
|
|
av_profile_init()
函数在 ortp 库初始化时会被调用到:
为 RtpSession 设置的载荷类型对数据收发的过程有一定的影响。
通过 oRTP 收发数据时,需要为其传入用户时间戳,oRTP 会根据为 RtpSession 设置的载荷类型找到描述载荷类型的PayloadType,并根据 PayloadType 的时钟频率和用户时间戳,计算出数据收发的时间间隔。以此实现用户对数据收发频率的控制。如(ortp/src/rtpsession.c
):
在 RFC3551 中,载荷类型 96 是动态映射的类型,通常由特定的应用字节决定。如在 oRTP 中,这个类型是被映射为 T140 的,但也常将 96 映射到 H.264。为了让我们前面设置的载荷类型能够正常工作,还需要修改 oRTP 的源码 ortp/src/avprofile.c
中的 av_profile_init()
函数,把如下这一行
改为
初始化了 RtpSession 之后,就可以发送 H.264 裸流了:
这里根据 RTP 载荷格式的规范,将 NALU 转为 RTP 的载荷,并发送。需要特别说明的是,从 MediaCodec 拿到的第一个 Buffer,其内容通常像下面这样:
|
|
其中包含了类型分别为 SPS 和 PPS 的两个 NALU。要发送这块 Buffer,可以按照 H.264 的 RTP 载荷格式规范中描述的,单时间聚合包的格式来发送,或者拆分为两个 RTP 包来发送。
RTP 是一个用于流媒体传输的协议,而不是流媒体方案。要想使 RTP 在实际的项目中用起来,当然还是有许多其它工作要做的。
参考文档
视音频数据处理入门:H.264视频码流解析
ORTP移植到Hi3518e,h.264封包rtp发送
打赏
Done。