一、H264传输封包格式的2个概念

(1)组包模式(Packetization Modes)

RFC3984中定义了3种组包模式:单NALU模式(Single Nal Unit Mode)、非交错模式(Non-interleaved Mode)和交错模式(Interleaved Mode)。

“单NALU模式”:NALU封包在传输过程中必须是整包传输,不可以分包(指应用层的分包,并非指传输层)。而且NALU必须是严格按照解码顺序传输,也就是说,假设1s中连续的24帧分别标记为:frame1,frame2...,frame24,则传输必须严格按frame1,frame2...,frame24这个顺序传输。

“非交错模式”:NALU必须是严格按照解码顺序传输,也就是说,假设1s中连续的24帧分别标记为:frame1,frame2...,frame24,则传输必须严格按frame1,frame2...,frame24这个顺序传输。该模式可以分包(指应用层的分包,并非指传输层)。

“交错模式”:NALU可以不按照解码顺序传输,也就是说,假设1s中连续的24帧分别标记为:frame1,frame2...,frame24,则传输顺序可以是frame15,frame7,frame9...。该模式可以分包(指应用层的分包,并非指传输层)。

(2)封包类型(Packet Type)

RFC3984中定义了7种封包类型:Nal Unit, STAP-A, STAP-B, MTAP16, MTAP24, FU-A, FU-B。

这些类型分别对应着不同的传输属性(如支持应用层的“大包分小包”、“小包组大包”)。其中比较常见的是FU-A(Fragmentation Units A)这种类型。

“组包模式”和“封包类型”一起规定了H264的传输格式,但它们之间也非随意组合的,具体如下图:

二、SDP中的组包模式

H264传输的组包模式在SDP中被指定,下图是截取的一段SDP内容

其中“packetization-mode=1”即规定了H264的组包模式。3种组包模式分别对应编号0,1,2(见RFC3984),1表示“非交错模式”。

三、本地NALU和传输中NALU

(1)本地NALU

现在我们本地有一个NALU,大小为3000字节,如下图。

其中NALU Header分别由,1bit禁止位,2bit权限位,和5bit类型位。

其中type的有效值为1-12,分别代表了NALU的不同类型,数值0禁用,13-31保留(type由5bit表示,范围为0-31)。

(2)传输中的NALU

假设“组包模式”=“非交错模式”,“封包类型”=“FU-A”,并且将上述所述的包拆分成了3个进行传输,我们来举例说明传输中的NALU和本地的NALU的区别。

以上就是按顺序传输到客户端的3个封包。与本地NALU不同的是,NALU Header的type不再是1-12,而是28(28表示FU-A传输格式,见RFC3984),真正的NALU的type被包含在FU-A Header中。

FU-A Header的格式如下

S(Start):起始包指示位,即当传输的是第1个NALU分包时,该位置1。上图中Pack 1该位会被置位;

E(End):结束宝指示位,即当传输的是最后1个NALU分包时,该位置1。上图中Pack 3该位会被置位;

R(Reserved):保留位,忽略之。

Type:NALU类型,即原来在NALU Header中的Type。

当客户端收到这3个分包时,便可以将其还原成本地NALU的格式了。

四、源码分析

在nalu_types_h264.cpp中,首先分析函数:

size_t FU_A::CopyData(uint8_t * buf, uint8_t * data, size_t size)

它的作用是将data中的数据复制到buf中,一共复制size个字节,返回实际复制的字节数。其中buf为用户的缓冲区,data为rtp接收的数据。

 size_t FU_A::CopyData(uint8_t * buf, uint8_t * data, size_t size)
{
size_t CopySize = ;
if(!buf || !data) return ; StartFlag = IsPacketStart(data);
EndFlag = IsPacketEnd(data); uint8_t NALUHeader = ;
NALUHeader = (uint8_t)(
ParseNALUHeader_F(data) |
ParseNALUHeader_NRI(data) |
ParseNALUHeader_Type(data)
); if(StartFlag) { // NALU start code size
buf[] = ; buf[] = ; buf[] = ; buf[] = ;
CopySize += ;
memcpy(buf + CopySize, &NALUHeader, sizeof(NALUHeader));
CopySize += sizeof(NALUHeader);
}
const int FU_A_HeaderSize = ;
memcpy(buf + CopySize, data + FU_A_HeaderSize, size - FU_A_HeaderSize);
CopySize += size - FU_A_HeaderSize; return CopySize;
}

仔细看一下源码,我们会发现该函数先解析data的前2个字节(IsPacketStart、IsPacketEnd、ParseNALUHeader_F、ParseNALUHeader_NRI和ParseNALUHeader_Type,源码如下),如果该数据为NALU的第1个RTP分包,则在其最前面添加{0,0,0,1},以标注NALU的开头。

 bool FU_A::IsPacketStart(const uint8_t * rtp_payload)
{
if(!IsPacketThisType(rtp_payload)) return false; uint8_t PacketS_Mask = 0x80; // binary:1000_0000 return (rtp_payload[] & PacketS_Mask);
} bool FU_A::IsPacketEnd(const uint8_t * rtp_payload)
{
if(!IsPacketThisType(rtp_payload)) return false; uint8_t PacketE_Mask = 0x40; // binary:0100_0000 return (rtp_payload[] & PacketE_Mask);
} uint16_t FU_A::ParseNALUHeader_F(const uint8_t * rtp_payload)
{
if(!rtp_payload) return FU_A_ERR;
if(FU_A_ID != (rtp_payload[] & FU_A_ID)) return FU_A_ERR; uint16_t NALUHeader_F_Mask = 0x0080; // binary: 1000_0000 // "F" at the byte of rtp_payload[0]
return (rtp_payload[] & NALUHeader_F_Mask);
} uint16_t FU_A::ParseNALUHeader_NRI(const uint8_t * rtp_payload)
{
if(!rtp_payload) return FU_A_ERR;
if(FU_A_ID != (rtp_payload[] & FU_A_ID)) return FU_A_ERR; uint16_t NALUHeader_NRI_Mask = 0x0060; // binary: 0110_0000 // "NRI" at the byte of rtp_payload[0]
return (rtp_payload[] & NALUHeader_NRI_Mask); } uint16_t FU_A::ParseNALUHeader_Type(const uint8_t * rtp_payload)
{
if(!rtp_payload) return FU_A_ERR;
if(FU_A_ID != (rtp_payload[] & FU_A_ID)) return FU_A_ERR; uint16_t NALUHeader_Type_Mask = 0x001F; // binary: 0001_1111 // "Type" at the byte of rtp_payload[0]
return (rtp_payload[] & NALUHeader_Type_Mask);
}

上一篇                 回目录                 下一篇

一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(七)RTP音视频传输解析层之H264传输格式的更多相关文章

  1. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(五)用户接口层之提取媒体流数据

    当RTSP客户端向RTSP服务端发送完PLAY命令后,RTSP服务端就会另外开启UDP端口(SDP协商定义的端口)发送RTP媒体流数据包.这些数据包之间会间隔一段时间(毫秒级)陆续被发送到RTSP客户 ...

  2. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(二)用户接口层之RtspClient类及其构造函数

    RtspClient类是myRTSPClient函数库所有特性集中实现的地方. 主要为用户提供: 1. RTSP协议通信接口函数,如DoOPTIONS(): 2. RTSP账号.密码设置函数,如Set ...

  3. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(四)用户接口层之处理SDP报文

    当RTSP客户端向RTSP服务端发送DESCRIBE命令时,服务端理应当回复一条SDP报文. 该SDP报文中包含RTSP服务端的基本信息.所能提供的音视频媒体类型以及相应的负载能力,以下是一段SDP示 ...

  4. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(三)用户接口层之RTSP命令

    截至版本1.2.3,myRtspClient函数库共支持以下6个RTSP命令: (1)OPTIONS (2)DESCRIBE (3)SETUP (4)PLAY (5)PAUSE (6)TEARDOWN ...

  5. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用ffmpeg解码视频

    一.概述 myRTSPClient(RTSPClient)获取音视频数据之后,接下来的工作便是将音视频数据交给解码器去解码(ffmpeg),ffmpeg解码之后于是便有了呈现在终端用户(USER)面前 ...

  6. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(一)概览

    myRTSPClient主要可以分成3个部分: 1. RTSPClient用户接口层: 2. RTP 音视频传输解析层: 3. RTP传输层. "RTSPClient用户接口层": ...

  7. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(六)RTP音视频传输解析层之音视频数据传输格式

    一.差异 本地音视频数据格式和用来传输的音视频数据格式存在些许差异,由于音视频数据流到达客户端时,需要考虑数据流的数据边界.分包.组包顺序等问题,所以传输中的音视频数据往往会多一些字节. 举个例子,有 ...

  8. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(三)一个简单的rtsp播放器

    该篇内容简单的将前两篇内容组合在一起,创建了2个线程,分别播放音频和视频. int main(int argc, char * argv[]) { RtspClient Client; pthread ...

  9. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(十)使用JRTPLIB传输RTP数据

    myRtspClient通过简单修改JRTPLIB的官方例程作为其RTP传输层实现.因为JRTPLIB使用的是CMAKE编译工具,这就是为什么编译myRtspClient时需要预装CMAKE. 该部分 ...

随机推荐

  1. day_5.27py

    生成器:send()  next() send 和next都可以把生成器向下走,但是send可以传入个参数 ''' 周末继续py 下周回学校过郭星辰生日,还得回来再复查一下 2018-5-27 16: ...

  2. 网络通信协议八之(传输层)TCP协议详解

    传输层协议 分段是为了提高传输效率,封装是指给每个数据段添加一个编号 端到端的传输是逻辑上的端到端,并不是真正意义上的发送方某层与接收方某层之间的传输 IP协议只是保证数据报文发送到目的地,为主机之间 ...

  3. Flask web开发之路一

    之前学过一段时间的flask,感觉还是挺好用的,自己的专利挖掘项目也想这个web框架来搭建,于是重新开始基础学习 环境:win10,python3.6,pycharm2017,虚拟环境virtuale ...

  4. C语言指针赋值前的指向问题

    以下代码运行会得到什么结果? #include<stdio.h> int main() { int *k;//定义一个指针变量 *k=100;#给指针变量所指的内存赋值 printf(&q ...

  5. HDLM for AIX安装

    HDLM for AIX安装 1)    安装ODM补丁包 补丁包在软件介质光盘的\HDLM_AIX\AIX_ODM\HTC_ODM下面:HTCODM3.tar 解压:#tar -xvf HCODM3 ...

  6. 省一行是一行:在if语句中使用C# 7.0的模式匹配

    C# 7.0的模式匹配(Pattern Mathing)不仅可以节省代码,而且可以让代码更流畅(Fluent),今天又在实际开发中体会了一下. 不用模式匹配的代码,需要先获取返回值,然后用if进行判断 ...

  7. Codeforces 777C - Alyona and Spreadsheet - [DP]

    题目链接:http://codeforces.com/problemset/problem/777/C 题意: 给定 $n \times m$ 的一个数字表格,给定 $k$ 次查询,要你回答是否存在某 ...

  8. [No0000F4]C# 枚举(Enum)

    枚举是一组命名整型常量.枚举类型是使用 enum 关键字声明的. C# 枚举是值数据类型.换句话说,枚举包含自己的值,且不能继承或传递继承. 声明 enum 变量 声明枚举的一般语法: enum &l ...

  9. Appium入门(6)__appium-desktop安装

    部分摘自:http://www.testclass.net/appium/appium-base-desktop/ Appium-Server主要用来监听移动设备,然后将不同编程语言编写的 appiu ...

  10. Qt网络模块如何使用(表格)

    1.网络模块介绍 类名 说明 中文 QAbstractNetworkCache The interface for cache implementations 缓存实现的接口 QNetworkCach ...