遇到的问题:

使用wireshark对成功及失败的rtmp包进行分析,发现成功的第一个videoData是有数据的,但是失败的第一个videoData是没有数据的。

成功:

wireshark tcp无法解析rtp包_rtmp

失败:

wireshark tcp无法解析rtp包_网络_02

是否是这里有问题?

期间学习参考以下博客:

手撕rtmp协议细节 ,这个系列讲的很好也很基础

rtmp协议是一个应用层协议,基于tcp,所以有三次握手,在tcp建立后在进行rtmp协议层次的握手

握手的过程主要完成了两个工作,一是对rtmp的版本进行校验,二是发送了一些随机数据,用于网络状况的检测

Rtmp协议握手完成之后,就可以进行数据交互了,但交换的数据格式需要一个组织的标准,发送端按照该标准进行数据的组装,接收方按照该标准进行数据的拆解,这样才能完成通信

  • rtmp header
  • rtmp body

我们来先看看createStream消息,RTMP客户端发送此消息到服务端,创建一个逻辑通道,用于消息通信。音频、视频、元数据均通过createStream创建的数据通道进行交互,而releaseStream与createStream相对应,为什么有的时候会在createStream之前先来一次releaseStream呢?这个问题也困扰了我,让我一度怀疑是否是这里的问题这就像我们很多的服务实现中,先进行一次stop,然后再进行start一样。因为我们每次开启新的流程,并不能确保之前的流程是否正常走完,是否出现了异常情况,异常的情况是否已经处理等等,所以,做一个类似于恢复初始状态的操作,releaseStream就是这个作用。

wireshark tcp无法解析rtp包_音视频_03

像上面的这个图,把很多个rtmp包放在一起,要点开才能看到具体有哪些,不点看就看不到createStream。

wireshark tcp无法解析rtp包_ffmpeg_04

这里面能看到有一个publish,这个就是我们推流需要使用到的了,如果是有拉流的流程那么还应该有一个play。

Rtmp中video包介绍

RTMP直播推流 - 简书 (jianshu.com)

h264手动添加sps和pps到AVCodecContext->extradata

H264–4--H264编码

大数据]FFmepg AV_CODEC_FLAG_GLOBAL_HEADER问题描述

于是就在这些文章中搜索,在这篇文章RTMP直播推流 - 简书 (jianshu.com)找到了同样的问题!!!

原来在调用avformat_write_header之前,就要把sps跟pps传入

首先是sps和pps的简介:

Sequence Parameter Sets (SPS) 和Picture Parameter Set (PPS)

SPS 对于H264而言,就是编码后的第一帧,如果是读取的H264文件,就是第一个帧界定符和第二个帧界定符之间的数据的长度是4。

PPS 就是编码后的第二帧,如果是读取的H264文件,就是第二帧界定符和第三帧界定符中间的数据长度不固定。

在nalu的header中有规定67后面是sps,68后面是pps,具体height,width这些在哪里用什么值表示这里就不多说了,可以在网上找到相关的内容,如果对这里比较了解,可以直接手写sps_pps数组(强烈不推荐)

AMF编码格式是adobe的一种编码格式

然后是问题的表象:

b站播放器日志在一开始就出现了video track parse error 并且后面又出现了firstFrame timeout,从这里可以判断应该是第一个videoData就出现了问题,并且播放器信息里面没有该有的信息,说明播放器没有获取到应该有的视频数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

wireshark tcp无法解析rtp包_ffmpeg_05

经过和斗鱼及虎牙的对比,发现无法播放这种现象和播放器也有关系,有些播放器不挑,比如斗鱼的播放器它就不管你第一个是不是有sps和pps信息,就很奇怪,更奇怪的是虎牙,它接收了流并且在网络中保持请求数据,但是!它放不出来,不像b站,播放器解析错误就一顿重复请求、跨域请求。

wireshark tcp无法解析rtp包_音视频_06

大数据]FFmepg AV_CODEC_FLAG_GLOBAL_HEADER问题描述,这篇文章中说到,默认情况下AVCodecContext extradata在探测码流(avformat_find_stream_info) 会自动将SPS,PPS填充到extradata字符串中,通过avcodec_parameters_to_context函数,将codecpar内容传递给AVCodecContext,这可能也是一种做法,但是并不适用于我们的场景。

从以上这篇文章也可以看到,第一个视频帧也就是AVC sequence header非常重要,它是客户端解码的必需部分。

b站的播放器解码不出来就是这个原因。

上面这篇文章说可以通过side_data来更新extradata,他也尝试过修改更新AVCodecContext未果。可以尝试一下

上面这个方案不行,修改side_data是修改的packet的数据,我们这个是在pollAVPacket之前进行的操作,是AVFormat_write_header的操作,这个方法适用于中途修改sps和pps设置。
尝试将sps_pps信息写入stream->codecpar->extraData中:

在avFormat_write_header之前将sps_pps信息写入extraData,还是不行,虽然有数据在videoData的body里面,但是缺少一些信息,并且手写sps_pps不现实,不具备可移植性只能用于测试。

缺少的信息:

wireshark tcp无法解析rtp包_音视频_07

在这篇文章中也提到为AVC时,后续的数据格式为| AVCPacketType(8)| CompostionTime(24) | Data | AVCPacketType为00时代表数据类型为AVCSequence Header AVCPacketType为01时代表数据类型为AVC NALU 于是知道17后面的4个00分别为AVCPacketType和CompostionTime,也从ffmpeg中的源码中找到剩余的字节的解释。

链接:https://www.jianshu.com/p/e90184fe94f9

因为需要在AVFormat_write_header之前,更在发送rtmp包之前就要填入sps和pps信息,所以经过了以下尝试:

  • 首先在stream上修改flags |= AV_CODEC_FLAG_GLOBAL_HEADER;发现并没有用;
  • 然后手动写入sps_pps也不是个好办法,因为如上所说在sps和pps前面还会有一些信息;
  • 最后通过在编码器初始化的时候给编码器的ctx加上flags |= AV_CODEC_FLAG_GLOBAL_HEADER;,这样在没有数据进入编码器的时候(即初始化后)我们才能够获取到编码器的sps_pps信息;

但是设置了这个参数后会影响到之后的数据,即之后的I帧不再带有sps和pps信息,由于flv格式只需要用第一个Video Tag的sps和pps信息便可以生成AVCC(AVCDecoderConfigurationRecord),所以后面的I帧没有sps和pps对其没有影响,问题得以解决。

总结:

分析问题要从基础出发,尤其是关于一些底层的知识或者通信相关的知识,可以看有关于协议或者规范的官方文档,取其次也可以看看一些对其进行分析的博客或者文章,通过对流程的梳理来找到问题的所在,这是定位问题。

然后解决问题也要理清思路,对于该问题而言重要的点就在于怎样让第一个videoData有sps和pps数据,并且能够自动获取到正确的数据,对于这种情况而言应该要从源码入手,虽然了解到需要对AVCodecContext进行设置flags,但是如果对流程不够熟悉,可能并不能找到适当的位置进行设置,所以对于解决问题不仅要从多方去寻求解释,从多个角度去验证,更要研读源码熟悉流程。