RTMP推流中的视频分辨率改变处理
问题
视频旋转时(分辨率发生改变时), 腾讯播放器不旋转
分析
RTMP协议只对数据进行转发, 音视频的封包是基于FLV进行打包的
RTMP 的 VideoTag 根据 AVCPacketType 字段的不同,主要分为两种类型:
AVC sequence header(AVC 配置类型)
AVCPacketType = 0x00
用于发送 SPS/PPS 等解码器配置信息,通常在推流开始时发送一次,作为初始化解码器的关键数据。
AVC NALU(AVC 帧数据类型)
AVCPacketType = 0x01
用于传输实际的 H.264 视频帧数据,如 I 帧(关键帧)或 P/B 帧(非关键帧)。
但在实际传输中, RTMP 的 VideoTag是可以包含多个 NALU 单元的,具体取决于封装格式和打包方式:
l 情况一:AVCC 格式(常见于 RTMP/MP4)
每个 NALU 前有 长度字段(如 4 字节大端整数),表示该 NALU 的大小。
一个 VideoTag(即一帧数据)可以连续包含多个 NALU,例如:
SPS
PPS
IDR slice
SEI
这些 NALU 按顺序依次拼接,形成完整的视频帧数据。
l 情况二:Annex-B 格式(裸流或 TS)
每个 NALU 以起始码 0x00 00 01 或 0x00 00 00 01 分隔。
多个 NALU 可以直接连续出现在同一个数据包中,无需额外封装结构。
当分辨率发生改变时,播放器可以从新的AVC sequence header感知,或者从AVC NALU感知,但前提是包含AVC NALU包含SPS、PPS
原有使用我司封装的Librtmp推流
数据用的是Annex-B格式; AVC sequence header只发送了一次
如图所示,在开始推流时第一个数据包中,
1) 有AVC Sequence Header, SPS内容为67 64 00 1f ac d9 40 50...
2) AVC NAUL 是Annex-B 格式, SPS内容为67 64 00 16 ac b4 05 01...
在该抓包数据中,搜索再无AVC Sequence Header, 即使SPS发生改变时:
发现AVC Sequence Header中的SPS和AVC NAUL中额SPS内容不一致呢?
抓取另一个包,如下图所示:
1) 有AVC Sequence Header, SPS内容为67 64 00 1f ac d9 40 50...
2) AVC NAUL 是Annex-B 格式, SPS内容为67 64 00 33 ac b4 02 20 1e ...
不难猜测,AVC Sequence Header中SPS的内容,应该是封装库中,SPS的内容写死了,而未取参数传递的值。
播放器播放效果分析
VLC能够正确播放,且能够旋转, 这是因为VLC能够识别Annex-B格式,解析其中的SPS
腾讯播放器旋转不了,是因为其无法识别Annex-B格式, 只能从唯一的一个AVC Sequence Header来感知解码参数,当分辨率发生改变时也不变;
腾讯播放器播放竖屏视频源,开始就显示为横屏, 是因为AVC Sequence Header中的SPS被写死,未使用传递的SPS, 猜测由于使用Annex-B各种中包含了正确的SPS,所以解码器被重新初始化,解码未报错,但是画布的长宽是从AVC Sequence Header中获取的(1920*1080),所以是横屏
VLC能够播放, 阿里播放器能够播放, 腾讯最后一个免费的播放器不兼容
使用FFMPEG推流
VideoTag数据使用AVCC格式,且包含SPS; AVC sequence header只发送了一次
VideoTag采用Annex-B格式,包含SPS,且AVC Sequence Header中的SPS的内容一致 67 64 00 33 ac b4 02 20 1e 34
以下当分辨率发生改变时,AVC Sequence Header也未再发送
播放器播放效果分析
VLC能够正确播放,因为能解析AVCC格式,且其中包含SPS,能够再次初始化
腾讯播放器能够正确播放,因为能解析AVCC格式,且其中包含SPS,能够再次初始化
SRS-Librtmp
VideoTag数据使用AVCC格式,且包含SPS; AVC sequence header多次发送
开始时发送了一次AVC Sequence Header, 注意,后面没有紧跟I帧数据
下一个包才有I帧
在SPS发生改变时,再次发送AVC Sequence Hader
当分辨率发生改变时,腾讯播放器能够正常播放,但VLC仍会花屏
分析:
VLC 在接收 RTMP 流时,必须把 H.264 解码参数(SPS/PPS)先拿到才能正确解码后面的帧。在收到第一次AVC Sequence Header 时,flv_packet() 会调用 flv_get_extradata() 把 SPS/PPS 拷进 fmt->p_extra,随后 decoder_New() 用这份 extradata 初始化 H.264 解码器。
解码器一旦建立,后面再出现的 AVCPacketType == 0 包就被忽略了。
因此即使你中途把 SPS/PPS 改成别的参数,VLC 仍然用旧的 extradata 去解码,于是出现花屏/解码错误。
•
为什么 RTMP/FLV 不自动补发 SPS/PPS?
RTMP 协议本身只是字节管道,它不缓存或重传解码配置。FLV 规范规定:
• AVCPacketType=0 的那一条(AVC sequence header)必须在每一路视频流的最前面发一次,后续只发 AVCPacketType=1 的裸 NALU。
客户端(包括 VLC)通常只在这一条里解析 SPS/PPS,之后就不再解析了。因此,后面如果 SPS/PPS 缺席,客户端不会回头去找。 新的腾讯播放器,ffplay会再次取AVE Sequence Header,来更新SPS/PPS
解决方式(任选其一)
在推流最开始发一条 AVC sequence header(AVCPacketType=0),里面放 SPS/PPS;以后每一帧都用 AVCPacketType=1,只发 IDR/P/B NALU。
这样 VLC 一定能在首条拿到解码参数。
可以在每一个关键帧之前把 SPS/PPS 再发一次:
[VideoTagHeader: 0x17 0x01 00 00 00]
[NALU1: SPS]
[NALU2: PPS]
[NALU3: IDR]
验证结果符合预期:在 RTMP 生态中,"每个 0x01 携带 SPS" 是实际可行的动态分辨率切换方案,而"重发 0x00" 存在兼容性问题。

浙公网安备 33010602011771号