DirectShow的RTP发包(H264)Filter <转>
转帖地址: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 <转>的更多相关文章
- 最简单的基于DirectShow的示例:获取Filter信息
===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...
- (转)基于RTP的H264视频数据打包解包类
最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...
- rtp传输h264
---恢复内容开始--- 基本概念的理解 H.264的主要目标:1.高的视频压缩比2.良好的网络亲和性 解决方案:VCL video coding layer 视频编码层NAL network abs ...
- 对最近的RTP和H264学习进行总结整理-04.20
虽然还是没有搞出来,但总感觉快了哈哈(哪来的自信) 1.RTP协议接受数据 #region 1-RTP协议变量声明 RTPSession session; RTPReceiver receiver; ...
- 基于RTP的H264视频数据打包解包类
from:http://blog.csdn.net/dengzikun/article/details/5807694 最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打 ...
- RTP封装h264
网络抽象层单元类型 (NALU): NALU头由一个字节组成,它的语法如下: +---------------+ |0|1|2|3|4|5|6|7| +-+-+-+-+-+-+-+ ...
- RTP 打包H264与AAC
static int h264_parse(Track *tr, uint8_t *data, size_t len) { h264_priv *priv = tr->private_data; ...
- 【FFMPEG】基于RTP的H264视频数据打包解包类
最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...
- 用实例分析H264 RTP payload
用实例分析H264 RTP payload H264的RTP中有三种不同的基本负载(Single NAL,Non-interleaved,Interleaved) 应用程序可以使用第一个字节来识别. ...
随机推荐
- softmax与logistic关系
Softmax回归模型,该模型是logistic回归模型在多分类问题上的推广,在多分类问题中,类标签 可以取两个以上的值. Softmax回归模型对于诸如MNIST手写数字分类等问题是很有用的,该问 ...
- hiho1602本质不同的回文子串的数量
给定一个字符串S,请统计S的所有子串中,有多少个本质不同的回文字符串? 注意如果两个位置不同的子串满足长度相同且对应字符也都相同,则认为这两个子串本质上是相同的. Input 一个只包含小写字母的字符 ...
- BZOJ1066 SCOI2007 蜥蜴 【网络流-最大流】
BZOJ1066 SCOI2007 蜥蜴 Description 在一个r行c列的网格地图中有一些高度不同的石柱,一些石柱上站着一些蜥蜴,你的任务是让尽量多的蜥蜴逃到边界外. 每行每列中相邻石柱的距离 ...
- BZOJ1597土地购买 【斜率优化DP】
BZOJ1597土地购买 [斜率优化DP] Description 农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土地的长宽满足( ...
- NET Core项目模板
打造自己的.NET Core项目模板 https://www.cnblogs.com/catcher1994/p/10061470.html 前言 每个人都有自己习惯的项目结构,有人的喜欢在项目里面建 ...
- 强大的Core Image(教你做自己的美图秀秀))
iOS5新特性:强大的Core Image(教你做自己的美图秀秀)) iOS5给我们带来了很多很好很强大的功能和API.Core Image就是其中之一,它使我们很容易就能处理图片的各种效 ...
- hadoop2.x配合ZooKeeper集群环境搭建
前期准备就不详细说了,课堂上都介绍了1.修改Linux主机名2.修改IP3.修改主机名和IP的映射关系 ######注意######如果你们公司是租用的服务器或是使用的云主机(如华为用主机.阿里云主机 ...
- 《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 ...
- Hadoop HDFS 整合 上传 下载 删除
新建一个Java项目,导入jar,新建一个测试类,编写代码实现文件操作功能: package com.bw.test; import java.io.FileInputStream; import j ...
- 可以随时查找的max栈和max队列——面试
这是面试中比较常见的题目,max队列也是编程之美里的一道题 对于max的栈,有个比较简单的办法就是,每次入栈前判断栈顶元素与正在入栈的元素哪个大,哪个大就哪个入栈 对于队列,我们知道可以用两个栈来实现 ...