转帖地址:http://blog.csdn.net/fan2273/article/details/77653700

基于DirectShow的框架H.264 RTP Sender Filter

开发框架与环境:

1.VS2017——工具集为V120-VS2013

2.jrtplib-3.11.1 jthread-1.3.3 编译为32位版

3.程序为32位程序

4.DirectShow链路图如下(控制台为RTP发送地址与本机端口)

5.RTP拆包方式为FU-A

6.x264编码filter为ffdshow codec(本filter支持输入为H264的sample)

7.本程序开源(部分实现借鉴了许多CSDN博客与大牛的程序),请遵守开源协议

git地址:https://github.com/EthanXzhang/RTP-Sender-Filter

实现部分

有空的话应该会整理个更详细的版本,这里主要就说一下遇到的问题好了。

实现DirectShow+jrtplib的H264收发包程序,总共用时约两周,主要的精力和时间花在了RTP协议、RTP拆包与H.264

字节流处理、H.264格式的解析上。

1.H.264的NAL单元(NALU)

H.264编码实现了网络层也就是NAL,其中每一个单元(NALU)适用于网络传输,详细的这里不阐述了,捡重点的方

便大家快速理解。

编码器对原始采集视频(图像)进行处理后,输出的每一帧即是一个NALU。这里的每一帧包括编码器初始化输出的

PPS和SPS。

实际上,我们需要用RTP实现的传输,就是从输入pin中的数据,提取每一帧(NALU),对NALU进行打包发送。

H.264编解码,怎样完成?需要怎样保持数据的发送?

首先,H.264的编码,除了初始化编码器后,输出PPS和SPS外,之后的所有NALU单元,都为I-P-B帧的组合。其中,

I帧为关键帧,解码器遇到这一帧后,会清除重置解码器的预测基准(具体请参考H.264编码与运动预测模型),而P

帧和B帧则是前向预测帧和双向预测帧。H.264由于进行了运动预测,因此除了I帧外,P帧和B帧仅需要较少的bit进行

编码,从而减少传输的数据量。

既然考虑网络传输,肯定需要知道接收端/解码器,需要什么信息才能完成解码。网上一些资料说到,需要PPS和

SPS,解码器才能获得编码的信息。这部分可能是基于封装后的H.264编码视频,而不是指实时采集的H.264视频流。

这里可以告诉大家的是,解码器需要的一切信息,都在编码器定时编码输出的I帧中。也就是,接收端开启后,收到到

来的第一个I帧后,便可以开始正常的解码工作。之后遇上任何网络波动、丢包、延迟等情况,都会在下一个I帧后重

置。I帧的速率,由编码器控制。由于I帧需要较大的编码量,因此一般编码器缺省I帧速率居中,通常受输出压缩率与

质量影响。

较高的I帧速率,可以保证较好的动态分辨率,降低延迟、丢包等带来的视频模糊、拖影、花屏影响,但相对的会增

加网络传输的数据量。(通常一个I帧的数据量是P B帧的五倍以上)

如果网络丢包严重、延迟较高,有需要较好的动态分辨率和画质,可以通过提高I帧的编码速率来解决。

这一部分,简单来说,编码器输出一个个NALU,而我们不需要管这个NALU是具体什么帧,只需要对它进行RTP

拆包并发送就可以了。解码器会自动等待I帧,并开始之后的解码工作。

2.NALU的结构

无论这个NALU是PPS还是SPS,又或者I P B帧,他们都具有一样的结构。

startcode+NALU头+NALU数据。

startcode,起始码,主要是帮助解码器从数据流中分辨NALU用。startcode格式十分固定,但根据编码器规范,

可能具有两种不同的形式。

三位的0x000001与四位的0x00000001。无论哪种格式,都可以通过读0后读1判断。当遇到一个startcode后,紧接

着startcode后的数据就是该NALU的数据,直到遇到下一个startcode。

对于实时采集编码来说,由于我们使用的filter处理数据单位为Sample,每一个MediaSample中携带的数据即是一帧

数据,因此,永远都是以startcode开头,并且无需判断该NALU结束(直接使用actualsize数据长度取数据)

但是,这里有一个问题。

由于MediaSample的getPointer(BYTE
**pb)方法,获得指向内存的指针。而BYTE为unsigned char型,0x00在

char型中默认为NULL。也就是,此时返回的指针会提示指向的数据为空('0'
\0),但实际只是因为指向的第一个char

型内存单元为0x00。

此时,不要慌张,通过循环判断*pb==0x00(或NULL);pb++的方法,使指针后移,便可取到startcode后的NALU

数据。紧接着startcode,是一个字节NALU头,8位bit组成。由高位到低位依次是F(1bit)-NRI(2bit)-TYPE(5bit)。

这个部分后面的RTP拆包需要用到,因此需要保存下来(我使用了一个结构体,方便赋值)

NALU头之后,便是H.264的帧数据,这部分原封不动保留下来,直接装载到RTP包的playload中就好了。

PS:这一部分,主要可能遇到的问题是startcode的处理。由于startcode开头有2-3位的0x00,很多人使用

IMediaSample->getPointer方法会以为取得了空指针,而不断怀疑filter与Sample的问题。实际上,只要对指针pb进行

后移处理就好。(其实,我这里就被坑了3天,才反应过来)

3.RTP发包

JRTPLib使用的UDP协议进行发包。UDP协议是不可靠传送协议,因此,装载数据量大的UDP包容易被路由丢弃。

因此,根据RTP协议,通常将每一个RTP包的最大装载量限制在1400(JRTPLIB中用MAX定义为1360大小)。

RTP拆包协议主要由FU-A和FU-B两种,这里我主要使用的是FU-A拆包方式,具体的可以百度更详细的资料,不过

度展开。FU-A的好处是,可以直接使用VLC等播放器,对你的发送端进行测试,而不需要开发出接收端。

当你需要对NALU进行拆包时,假设拆了N个包,那么这里面只有两种包,即前N-1种和最后一包N。(最后一包不

同)FU-A协议,需要在每一个NALU数据前(去掉NALU header),额外添加FU indicator和FU header,各一个字节。

不需要拆包的NALU单元,直接发送即可(包括NALU头)。

FU
indicator有以下格式:
      +---------------+
      |0|1|2|3|4|5|6|7|(注意,左边为高位,右边为低位,此处0-7表示比特流的起始到终止的方向)
      +-+-+-+-+-+-+-+-+
      |F|NRI|  Type   |
      +---------------+
   FU指示字节的类型域 Type=28表示FU-A。。NRI域的值必须根据分片NAL单元的NRI域的值设置。
 
   FU header的格式如下:
      +---------------+
      |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单元荷载类型定义见下表

表1.  单元类型以及荷载结构总结
      Type  Packet      Typename                      
     ---------------------------------------------------------
      0     undefined                                   -
      1-23   NALunit    Single NAL unit packet per H.264  
      24    STAP-A     Single-time aggregation packet   
      25    STAP-B     Single-time aggregation packet   
      26    MTAP16    Multi-time aggregation packet     
      27    MTAP24    Multi-time aggregation packet     
      28     FU-A     Fragmentation unit               
      29    FU-B      Fragmentationunit                
      30-31 undefined

简单来说,FU-A的拆包方式,indicator你只需要注意TYPE设置为28,F和NRI全部取NALU的头对应的位。Header你

只需要注意TYPE取得NALU头的type,S
E R全部置0(最后一包的E置1)。

这里说明一下FU-A的工作方式。

接收端根据RTP包中头一位(可能为FU indicator或NALU header)的TYPE位,判断这个包具体是什么。

当紧接着一包的FU header E位为1时候,接收端便知道要进行组包工作。

JRTPLib通过RTP包的mark位判断是否是最后一包,进行组包(具体见代码)。

4.filter的实现

这部分没太多可说的,不过因为DirectShow
filter相关的资料现在越来越难找,因此也大概说一下遇到的问题。

本程序前后使用了CBaseRenderer、CBaseVideoRenderer、CTransformFilter实现。

其实,选用哪个filter,主要看当前filter的目的和在整个链路中的定位。

我当前使用的是Transform,作为中间filter。

实际上,我是被迫这么做的。

早期我打算使用的Renderer,继承实现doRender方法,来进行RTP发包。但由于IMediaSample的getPointer一直为

空,而我的资料又很有限。在前后换了使用Base和Video的父类,使用了pin方法Receive,仍然解决不了后,我就更换

了Transform filter来尝试实现。(这个filter的资料相对多一些)

实际上,只是因为startcode的0x00,字符指针为NULL而已。

关于filter部分,实际上需要实现的基本只有以下几个部分:

1.filter信息与注册(名字、CLSID、pin属性)

2.checktype方法(不同位置的check方法不同,用以检测pin口是否匹配,通常必须实现)

3.关键的处理方法(doRender、Transform、FillBuffer)

4.createInstance与构造函数完成初始化

5.RTP传输问题处理

进行RTP传输的时候,视频经常会出现灰蒙、抖动、花屏。

总结来说,基本就是延迟、丢包、乱序的问题。

但在本地收发测试中,丢包和延迟的问题基本不会存在,一般也不可能存在乱序的情况。

那么,为什么本地VLC测试,还会出现上述状况呢?

请使用秒表在采集端进行测试……

经过我的检测发现,是发送端的时间戳和发送频率、延迟设置的问题。

由于实时采集,一般来说,发送频率和帧率是匹配,播放端才能还原出和采集端同速的图像。

如果发送端,发送速度高于帧率,播放端接收到就会马上播放,因此时常会处于等待状态,出现延迟的情况。

如果发送端,发送速度慢与帧率,播放端则会慢速播放,而下一个I帧到来又会刷新还没播放完的P
B帧,出现卡顿的情况。

而类似画面抖动,帧间预测导致画面中动作往复、影响重叠,则是RTP包乱序,或者P
B帧跟随前面的I帧顺序不对,

也大多是因为上述问题产生的。

DirectShow的RTP发包(H264)Filter <转>的更多相关文章

  1. 最简单的基于DirectShow的示例:获取Filter信息

    ===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...

  2. (转)基于RTP的H264视频数据打包解包类

    最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...

  3. rtp传输h264

    ---恢复内容开始--- 基本概念的理解 H.264的主要目标:1.高的视频压缩比2.良好的网络亲和性 解决方案:VCL video coding layer 视频编码层NAL network abs ...

  4. 对最近的RTP和H264学习进行总结整理-04.20

    虽然还是没有搞出来,但总感觉快了哈哈(哪来的自信) 1.RTP协议接受数据 #region 1-RTP协议变量声明 RTPSession session; RTPReceiver receiver; ...

  5. 基于RTP的H264视频数据打包解包类

    from:http://blog.csdn.net/dengzikun/article/details/5807694 最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打 ...

  6. RTP封装h264

    网络抽象层单元类型 (NALU): NALU头由一个字节组成,它的语法如下: +---------------+      |0|1|2|3|4|5|6|7|      +-+-+-+-+-+-+-+ ...

  7. RTP 打包H264与AAC

    static int h264_parse(Track *tr, uint8_t *data, size_t len) { h264_priv *priv = tr->private_data; ...

  8. 【FFMPEG】基于RTP的H264视频数据打包解包类

    最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...

  9. 用实例分析H264 RTP payload

    用实例分析H264 RTP payload H264的RTP中有三种不同的基本负载(Single NAL,Non-interleaved,Interleaved) 应用程序可以使用第一个字节来识别. ...

随机推荐

  1. softmax与logistic关系

    Softmax回归模型,该模型是logistic回归模型在多分类问题上的推广,在多分类问题中,类标签  可以取两个以上的值. Softmax回归模型对于诸如MNIST手写数字分类等问题是很有用的,该问 ...

  2. hiho1602本质不同的回文子串的数量

    给定一个字符串S,请统计S的所有子串中,有多少个本质不同的回文字符串? 注意如果两个位置不同的子串满足长度相同且对应字符也都相同,则认为这两个子串本质上是相同的. Input 一个只包含小写字母的字符 ...

  3. BZOJ1066 SCOI2007 蜥蜴 【网络流-最大流】

    BZOJ1066 SCOI2007 蜥蜴 Description 在一个r行c列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外. 每行每列中相邻石柱的距离 ...

  4. BZOJ1597土地购买 【斜率优化DP】

    BZOJ1597土地购买 [斜率优化DP] Description 农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土地的长宽满足( ...

  5. NET Core项目模板

    打造自己的.NET Core项目模板 https://www.cnblogs.com/catcher1994/p/10061470.html 前言 每个人都有自己习惯的项目结构,有人的喜欢在项目里面建 ...

  6. 强大的Core Image(教你做自己的美图秀秀))

    iOS5新特性:强大的Core Image(教你做自己的美图秀秀))       iOS5给我们带来了很多很好很强大的功能和API.Core Image就是其中之一,它使我们很容易就能处理图片的各种效 ...

  7. hadoop2.x配合ZooKeeper集群环境搭建

    前期准备就不详细说了,课堂上都介绍了1.修改Linux主机名2.修改IP3.修改主机名和IP的映射关系 ######注意######如果你们公司是租用的服务器或是使用的云主机(如华为用主机.阿里云主机 ...

  8. 《DSP using MATLAB》示例 Example 6.25

    代码: % x = [-0.9, 0.81]; [y, L, B] = QCoeff(x, 3) % Unquantized parameters r = 0.9; theta = pi/3; a1 ...

  9. Hadoop HDFS 整合 上传 下载 删除

    新建一个Java项目,导入jar,新建一个测试类,编写代码实现文件操作功能: package com.bw.test; import java.io.FileInputStream; import j ...

  10. 可以随时查找的max栈和max队列——面试

    这是面试中比较常见的题目,max队列也是编程之美里的一道题 对于max的栈,有个比较简单的办法就是,每次入栈前判断栈顶元素与正在入栈的元素哪个大,哪个大就哪个入栈 对于队列,我们知道可以用两个栈来实现 ...