因为项目中,ipcam的视频编码方式主要是基于H.264,因此ipcam出来的H.264码流会按照协议rfc3984来打包,mediastream2中收到rtp recv filter的数据后,必须先根据rfc3984协议来进行解析,解析成nalu单元才能丢给ffmpeg来进行解码,本文就对h.264 rtp包的解析工作进行分析。

/*process incoming rtp data and output NALUs, whenever possible*/
void rfc3984_unpack(Rfc3984Context *ctx, mblk_t *im, MSQueue *out){
uint8_t type=nal_header_get_type(im->b_rptr);
uint8_t *p;
int marker = mblk_get_marker_info(im);
uint32_t ts=mblk_get_timestamp_info(im);

if (ctx->last_ts!=ts){
/*a new frame is arriving, in case the marker bit was not set in previous frame, output it now*/
/* unless this is a FU-A (workarond some other apps bugs)*/
ctx->last_ts=ts;
if (ctx->m==NULL){
while(!ms_queue_empty(&ctx->q)){
ms_queue_put(out,ms_queue_get(&ctx->q));
}
}
}

if (im->b_cont) msgpullup(im,-1);

if (type==TYPE_STAP_A){
/*split into nalus*/
uint16_t sz;
uint8_t *buf=(uint8_t*)&sz;
mblk_t *nal;

ms_debug("Receiving STAP-A");
for(p=im->b_rptr+1;p<im->b_wptr;){
buf[0]=p[0];
buf[1]=p[1];
sz=ntohs(sz);
nal=dupb(im);
p+=2;
nal->b_rptr=p;
p+=sz;
nal->b_wptr=p;
if (p>im->b_wptr){
ms_error("Malformed STAP-A packet");
freemsg(nal);
break;
}
ms_queue_put(&ctx->q,nal);
}
freemsg(im);
}else if (type==TYPE_FU_A){
mblk_t *o=aggregate_fua(ctx,im);
ms_debug("Receiving FU-A");
if (o) ms_queue_put(&ctx->q,o);
}else{
if (ctx->m){
/*discontinued FU-A, purge it*/
freemsg(ctx->m);
ctx->m=NULL;
}
/*single nal unit*/
ms_debug("Receiving single NAL");
ms_queue_put(&ctx->q,im);
}

if (marker){
ctx->last_ts=ts;
ms_debug("Marker bit set");
/*end of frame, output everything*/
while(!ms_queue_empty(&ctx->q)){
ms_queue_put(out,ms_queue_get(&ctx->q));
}
}
}

函数rfc3984_unpack就是根据rfc3984协议对h264的rtp包进行解析,以便得到nalu单元。

对于一个原始的 H.264 NALU 单元常由 [Start Code] [NALU Header] [NALU Payload] 三部分组成, 其中 Start Code 用于标示这是一个NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", 占4个字节,NALU 头仅一个字节, 其后都是 NALU 单元内容,但在传输的时候,Start code字段被去掉了,在解码的时候,需要重新添加进去。

函数的入参im是指输入的rtp payload,注意是rtp有效负载,已经去掉了12字节的rtp头。

uint8_t type=nal_header_get_type(im->b_rptr);

首先获取nal的type字段,负载中的第一个字节便是nal单元类型,nal单元类型的结构图如下:

+---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

NAL单元类型字节部件的语义在H.264规范中制定, 简要描述如下.

F: 1 bit
      forbidden_zero_bit.  H.264规范声明设置为1指示语法违例。

NRI: 2 bits
      nal_ref_idc.  00值指示NAL单元的不用于帧间图像预测的重构参考图像。这样的NAL单元可以被丢弃而不用冒参考
      图像完整性的风险。大于0的值指示NAL单元的解码要求维护参考图像的完整性。

Type: 5 bits
      nal_unit_type.

类型对应的表如下:

Type   Packet    Type name                        Section
      ---------------------------------------------------------
      0      undefined                                    -
      1-23   NAL unit  Single NAL unit packet per H.264   5.6
      24     STAP-A    Single-time aggregation packet     5.7.1
      25     STAP-B    Single-time aggregation packet     5.7.1
      26     MTAP16    Multi-time aggregation packet      5.7.2
      27     MTAP24    Multi-time aggregation packet      5.7.2
      28     FU-A      Fragmentation unit                 5.8
      29     FU-B      Fragmentation unit                 5.8
      30-31  undefined

如果type为1-23,表示单一封装模式,表示该负载里面只包含一个NALU

0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |F|NRI|  type   |                                               |
      +-+-+-+-+-+-+-+-+                                               |
      |                                                               |
      |               Bytes 2..n of a Single NAL unit                 |
      |                                                               |
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     单个NAL单元包的RTP荷载格式

这种情况下,没什么好说的,直接将RTP Payload提取出来就表示一个NALU,因为RTP Payload存放的是一个完整的NALU(去掉了4字节的起始码)。这种方式,rtp头部中的marker字段都置为1,标示是最后一个包。

-

如果type为TYPE_STAP_A(24),组合模式风暴,表示该负载里面只包含多个NALU

0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                          RTP Header                           |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |STAP-A NAL HDR |         NALU 1 Size           | NALU 1 HDR    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                         NALU 1 Data                           |
      :                                                               :
      +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |               | NALU 2 Size                   | NALU 2 HDR    |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                         NALU 2 Data                           |
      :                                                               :
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

在提取nalu的时候必须去掉第一个字节,第一个字节是nal的类型字段(即STAP-A NAL HDR字段),不是nalu的部分,紧接着nal的类型字段后面的是一个2个字节的nalu长度字段(NALU1 Size),这个长度表示后面一个完整nalu的长度,提取nalu的时候,需要丢掉这2个字节的长度字段,依次处理每一个nalu。在这种情况下,要注意计算该RTP Payload里包含的完整nalu的个数。

如果type为TYPE_FU_A(28),表示这是一个分片包,该rtp包里面只包含一个nalu的分片,需要将多个rtp包的nalu分片重新组合起来形成一个完整的nalu,这种情况适合于当nalu很大,大小超过mtu,不适合单独作为一个RTP包来发送,因此需要分散到多个rtp包来发送。

0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      | FU indicator  |   FU header   |                               |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
      |                                                               |
      |                         FU payload                            |
      |                                                               |
      |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                               :...OPTIONAL RTP padding        |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

RTP payload format for FU-A

The FU indicator octet has the following format:

+---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+

The FU header has the following format:

+---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+

FU指示字节有以下格式:
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+
   FU指示字节的类型域的28,29表示FU-A和FU-B。
   FU头的格式如下:
      +---------------+
      |0|1|2|3|4|5|6|7|
      +-+-+-+-+-+-+-+-+
      |S|E|R|  Type   |
      +---------------+

S: 1 bit
      当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
   E: 1 bit
      当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的
      FU荷载不是分片NAL单元的最后分片,结束位设置为0。
   R: 1 bit
      保留位必须设置为0,接收者必须忽略该位。

Type: 5 bits
      NAL单元荷载类型

解析这种包,首先要获取FU头中的S和E字段,用来指示是否是nalu头或者尾。当为头的时候,表示一个nalu的开始,这个时候要还原该nalu的nal头,还原方法是将FU指示字段的前三位加FU头的后5位组成

nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f),见nal_header_init(im->b_rptr,nri,type)这个函数实现。

去掉FU指示器和FU头字段,将还原的nalu头放在nalu的开始。

如果不是头,则直接加入到之前重构的nalu后面,必须要去掉fu指示器字段和fu头,fu指示器和fu头不属于NALU的一部分。

在这种情况下,包含最后一个分片的rtp的marker字段置为1,其它置为0

至此,主要的三种基于rtp的H.264包都解析成功了,在丢给ffmpeg解码器进行解码的时候,必须是以nalu的形式数据作为输入,在有些情况下,需要重新在每个nalu的前面添加4个字节的起始码,有些情况下不需要。如果是字节流进行解析的话,必须要加入起始码,否则解码器无法定位每个nalu;如果是一个一个nalu丢给解码器进行解码,则不需要添加起始码。

基于android手机实时监控ipcam视频之三:H.264的RTP打包解析相关推荐

  1. 基于android手机实时监控ipcam视频之一:RTSP

    我以前做过一个这样的项目,基于android实现手机实时监控ipcam,ipcam厂商提供控件,该控件安装以后,在IE上面输入ipcam的ip地址,就可以实时查看ipcam的图像,这实时视频是通过HT ...

  2. 基于android手机实时监控ipcam视频之二:mediastream2

    在项目中用到了mediastream2,mediastream2是一个框架,引擎,它驱动系统的整个流程,从接收rtp媒体流并解析到媒体解码到显示到android手机屏幕上,都是由mediastream ...

  3. 基于H.264的RTP打包原理和FU-A分片实例分析

    1. H.264码流结构   H.264编码规范从逻辑上划分为视频编码层(VCL)和网络提取层(NAL).   VCL数据是由编码器直接输出的原始数据比特串(SODB),它表示图像被压缩后的编码比特流 ...

  4. 基于ffmpeg+SDL 实时播放摄像头视频

    基于ffmpeg+SDL 实时播放摄像头视频 基本流程 udp接收rtp数据流接收一帧数据后,转换为NAL单元送去解码 (这里特别说明一下,我本次用的接口是支持从连续数据流中自动分割出一个个NAL的, ...

  5. 基于android公交车线路查询论文文献,基于Android手机的实时公交查询系统设计与实现...

    龙源期刊网 http://doc.xuehai.net 基于Android手机的实时公交查询系统设计与实现 作者:郭宏昌 来源:<物联网技术>2015年第11期 摘要:为了提高城市公交的智 ...

  6. Android手机实时视频监控

    最近手机安装了一个"千里眼"和千里眼家居远程监控的应用,对里面的实时监控交通路口的状态有点好奇.以是使用相应的方法反编译看看她的原理. 对于软件"千里眼",经过 ...

  7. 基于Android 移动端的网络视频探索系统【100010403】

    基于移动端的网络视频探索系统 1 引言 1.1 研究背景 智能手机用户在 2015 年占全:人数比例超过百分之十,在 2016 年的时候手机用户超过 20 亿,中国占百分之三十左右.现如今,全:的智能 ...

  8. C++基于OpenCV实现实时监控和运动检测记录

    基于OpenCV实现实时监控并通过运动检测记录视频 一.课程介绍 1. 课程来源 课程使用的操作系统为 Ubuntu 14.04,OpenCV 版本为OpenCV 2.4.13.1,你可以在这里查看该 ...

  9. 安卓设备门禁识别开发_基于android手机的视频通话门禁控制系统

    龙源期刊网 http://www.qikan.com.cn 基于 android 手机的视频通话门禁控制系统 作者:陆海 李登辉 来源:<科教导刊 · 电子版> 2017 年第 34 期 ...

最新文章

  1. Java中的JSON
  2. 右键 Dos在这里 删除
  3. Collection集合的三种初始化方法
  4. 易天光通信ETU 25G SFP28光模块规格参数
  5. 进程的挂起以及可重入函数
  6. Linq找不到行或行已更改
  7. mysql索引技术_MySQL索引类型
  8. RuntimeError: [enforce fail at inline_container.cc:145] . PytorchStreamReader failed reading zip arc
  9. 约翰诺曼超级计算机研究中心,第433章 拉泽尔松教授的决定_学霸的黑科技系统_晨星LL作品_du00...
  10. git配置远程仓库,同时配置github、gitee、gitlab,完美解决方案
  11. 什么是事件冒泡?如何阻止事件冒泡?
  12. 《天天数学》连载37:二月六日
  13. MethodInterceptor拦截器
  14. [转载] python字符串数组字典_Python:字符串、列表、元组、字典
  15. C语言有限域的构造,有限域(3)——多项式环的商环构造有限域
  16. 木马分析(控制分析)实验
  17. kaldi的安装使用
  18. 学习verilog的经典好教材与资料
  19. python链家数据分析统计服_Python数据分析实战-链家北京二手房价分析
  20. 《安全评估报告》7条回答范例

热门文章

  1. Linux系统中VI或VIM输入时小键盘无法使用
  2. segment routing详解十一问
  3. nrf52832 蓝牙组网_nrf52832 蓝牙开发
  4. 程序员的真实工资是多少?
  5. [BJOI2019] 排兵布阵
  6. html怎么改变网页整体的大小,html设置浏览器大小
  7. 模拟自动售卖机售卖3、5、8元饮料投币业务
  8. 浅谈第四层交换机技术及应用
  9. web3d-手机产品展示_onePlus6
  10. Linux等保三级整改