解决FFmpeg播放RTSP推送的H265码流报错问题

1、背景介绍

RTSP(Real Time Streaming Protocol),实时流传输协议,是TCP/IP协议体系中的一个应用层协议。

RTP(Real-time Transport Protocol)实时传输协议,是一个网络传输协议,它位于传输层,但通常运行在UDP协议之上。

去年我写过两篇关于使用RTSP推送H265码流并拉流显示的文章。实现HEVC码流RTSP推流并显示假如我想编码HEVC码流时就显示视频画面。前者是使用RTSP+RTP推送本地的H265码流并使用VLC软件拉流显示。而后者是使用x265编码器,在编码过程中使用RTSP+RTP推流并显示。

我录了一段视频,使用RTSP推送本地H265视频,再用VLC软件拉流显示,效果如下:

,时长00:39

但是大家或许都知道,目前视频播放器(包括流媒体)大多基于FFmpeg的框架实现,更具体一点,就是基于FFplay内核,比如B站的ijkplayer开源项目,也是在FFplay基础上做了适用于移动端的封装和优化。

所以,仅仅使用VLC软件成功拉取H265流和显示还不够,最好再用FFmpeg的FFplay工具拉取RTSP推送的H265码流并播放显示。

但是当我进行这个测试的时候,出现了下面的错误:

可以看到主要报错是:

illegal temporal ID in RTP/HEVC packet

但是比较奇怪的一点是,同一个码流的RTSP地址,我使用VLC软件网络串流,播放的视频画面却是正常的

今天就先来尝试解决一下,FFplay拉流播放失败的这个小bug。

2、问题定位

我在网上搜了一下,全网(包括使用Google搜索)搜到唯一相关的问题讨论是:

不过好像对我没什么用,为什么这么说呢?

我测试用的FFplay,是在FFmpeg官网下载的Release版本exe文件。修改播放端FFplay源码不太现实,而且FFplay既然报了错,也有它的逻辑和规则,强制去掉判断不合理吧。不过上图所说的推流端给ID字段赋值的方法,倒是可以试试。那这个报错的ID到底是什么呢?

【学习地址】: FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击 1079654574 加群领取哦~

其实第一眼看到temporal ID,大致猜到可能是和H265的nuh_temporal_id_plus1(NALU 头里)相关。

至于是不是真的是这样,还得去FFmpeg的源码里面跟踪查看,打印"Illegal temporal ID in RTP/HEVC packet"错误提示的代码逻辑究竟是什么?

首先得去FFmpeg官网或者Github下载源码,由于我是将FFmpeg的源码加到了Source Insight软件中,所以可以直接在此软件里面搜"Illegal temporal ID in RTP/HEVC packet",得到结果如下:

也就是说,在rtpdec_hevc.c文件中的185行的hevc_handle_packet函数中有这个打印。再仔细一瞅,

可以看到,FFmpeg源码在解析使用RTP封装的H265码流时,如果判断出tid值是0,它就会打印"Illegal temporal ID in RTP/HEVC packet"错误提示。

那此处tid的值是什么呢?

    nal_type =  (buf[0] >> 1) & 0x3f;    lid  = ((buf[0] << 5) & 0x20) | ((buf[1] >> 3) & 0x1f);    tid  =   buf[1] & 0x07;

FFmpeg代码这里的buf[0]和buf[1],实际就对应的是H265码流的NALU Header的2个字节。而buf[1]&0x07处理,即获取NALU Header第二个字节的低3位值,也就是上面猜测的那个nuh_temporal_id_plus1语法元素的值

为什么在FFmpeg的源码里面,一定要去检查nuh_temporal_id_plus1不能为0呢?

在ITU-T的H265白皮书里面可以找到答案:

也就是说,在H265标准里面已经明确规定了这个值不能是0,因而FFmpeg源码做的检查是肯定合理没问题的。所以,在网上查到的那个解决方案里面说去掉客户端里面的判断,这明显是一个错误的处理方法。所以说,网上公开的资料鱼龙混杂,大家平时要小心求证。

我们回过头来再想一下,本地H265码流文件,推送到RTSP服务器,肯定需要对其进行RTP封装过程。既然FFmpeg现在检测到我们推送的RTP包里面nuh_temporal_id_plus1这个值有问题,那就有两个怀疑点。

要么是本地H265码流本身的NALU Header就有问题,要么是在做RTP封装时出现了问题

至于我们测试用的H265码流本身,NALU头里面这个值是否就是0,只要用码流分析软件查看便知,结果如下:

显然这个H265码流文件的NALUHeader里面的nuh_temporal_id_plus1值是1,并不是0。

那么就只能是RTP封装H265码流时存在问题

3、问题解决

不知大家之前是否了解过RTP封装视频原理,实际上RTP封装H265裸流的过程,还是比较简单的。RTP协议的头部是12个字节,然后裸流H265作为RTP的负载。H265裸流作为RTP负载的具体规则,可以参考标准:

https://www.rfc-editor.org/rfc/pdfrfc/rfc7798.txt.pdf

在学习了RTP封装H265码流以后,接下来就去排查自己的RTSP推流H265视频文代码。最终发现在H265Source.cpp文件的HandleFrame函数里面存在逻辑问题。

在RTP协议封装H265视频,获取每个NALU数据时,传入的形参frame_buf和frame_size其实都是包含了NALU 起始码数据和长度的。而按照rfc7798标准,在对H265码流进行RTP封装时,需要跳过起始码,如下图右边所示。

上图是将一个NALU打成一个RTP包的情况,对于一个NALU的实际数据长度,超过最大值MAX_RTP_PAYLOAD_SIZE(这里定义的是1420字节)的情况也需要跳过起始码,如下图所示:

如果不跳过起始码,frame_buf[1]获取的NALU Header里面的tid值就会是0,因为H265码流起始码(0x00 00 00 01)第二个字节值肯定是0。做了代码修改以后,最后再次使用FFplay来获取RTSP的H265码流效果如下:

,时长00:37

可以看到代码修改后,就可以使用FFplay软件播放RTSP推送的本地H265视频了,但是感觉要比VLC软件拉流播放效果卡一些(对比开头的那个视频)

在上面这个视频里面,进行FFplay拉流时,运行了一个脚本ffplay_rtsp.bat,它里面内容是:

.\ffplay.exe -window_title codec2022_test -x 1280 -y 720 -rtsp_transport tcp "rtsp://127.0.0.1:554/codec2021"

其中-window_title codec2022_test的意思是:指定FFplay播放窗口名为codec2022_test,-x 1280 -y 720表示指定FFplay播放器窗口分辨率。而rtsp://127.0.0.1:554/codec2021是我自己设置的RTSP码流地址。

4、小结

以上就是我解决FFplay拉流显示,用RTSP推流本地H265视频报错的一个过程。通过对问题的分析,并层层深入源代码,最终成功用FFplay拉流显示H265视频。希望对你有用。

原文链接:解决FFmpeg播放RTSP推送的H265码流报错问题

猜你喜欢

转载自blog.csdn.net/irainsa/article/details/129846364