3. 音视频播放方案
1. 视频直播支持
中继:画面先传到云服务器,再转发到手机
LAN:局域网内(同1路由器),手机可以观看摄像头视频。
UPnP:摄像头向路由器申请打洞,从而实现P2P直连,手机可以直接通过互联网找到摄像头并拉流。
在2025年,很多物联网安全专家建议谨慎开启UPnP。因为如果摄像头存在漏洞,开启了UPnP的端口可能会被公网上的恶高脚本扫描到,导致摄像头画面被直播到国外网站上。因此,现在的趋势是:倾向于使用基于云端的P2P打洞(如阿里云隧道、WebRTC(Web实时通信)技术),而非依赖路由器的UPnP,从而在不开启UPnP的情况下,也能实现远程低延迟直连。
1.1 GOP结构:由一个I-帧和若干个P-帧和B-帧组成的连续帧序列则称为一个GOP(Group of Pictures)。编码器会将视频编码为多个连续的GOP。
i帧:关键帧,是一种不需要其他参考帧即可编码的视频帧,视频总是以i帧开头,预览功能就是i帧。
p帧:P-帧会分析出参考帧中总是不变的部分,只有变化的部分会被编码,P-帧需要参考帧,所以对传输错误的容忍度较低。
B帧:帧间预测,需要同时使用之前的 i 帧 或 p 帧 和 之后的 i 帧 或 p 帧 进行编码,记录前后参考帧的差别,压缩比更高。但B帧现任增加了编码复杂度,且对延迟要求较高,所以在实时通话领域通常不使用B帧。
参考帧:它是一个功能角色,指"当前正在被其他帧依赖的帧",关键帧一定是参考帧(因为别人都依赖它),但参考帧不一定是关键帧----有些P帧(预测帧)虽然体积小,但如果后续帧依赖它,它也是参考帧。
IDR帧:就是关键帧。
1.2 MTU最大传输单元:指网络链路层能够传输的最大数据包大小(单位Byte),如果超过这个值数据包就会被分片(IP分片)
加入MTU是1500Byte,RTP应用层生成1个2000Byte的数据包交给IP层发送,这时IP层会自动切成两个IP分片(1500Byte+500Byte)
这样会导致1.丢包率翻倍,2.性能开销大,3.如果路由器不支持分片,直接丢包导致流媒体中断。
不分片的情况:1920x1080的帧,编码后大约30k,MTU1500Byte,直接交给IP层,会被分成20多个IP分片,网络稍微抖动就花屏。
分片的情况:1920x1080的帧,编码后大约30k,MTU1500Byte,切分成多个小于1500的RTP分片(FU-A模式),这样每个RTP分片在IP层都小于MTU,不会额外分片,如有丢包可申请重传,不至于整帧报废。
1.3 NALU、RTP、IP这三个什么关系?
NALU位于应用层,视频压缩后的数据单元,包含SPS/PPS/i帧/p帧等信息。
RTP属于传输层,给NALU加上序列号和时问戳,实现实时传输和播放控制。
UDP位于传输层,RTP包前加端口号,实现进程到进程的通信。
IP位于网络层,给UDP包加上源地址IP和目的IP地址,实现主机到主机的路由和寻址。
1.3.1 摄像头发送1帧H.264视频时,数据逐层打包过程如下
1.原始视频数据(YUV数据)---->H264编码器压缩成NALU
2.NALU交给RTP层,加上RTP头,形成RTP包
3.RTP包交给UDP层,加上UDP头(8字节,包含源端口和目的端口),形成UDP数据包
4.UDP数据包交给IP层,加上IP头(通常20字节,包含源IP和目的IP),形成IP数据包。
5.IP数据包交给数据链路层(以太网),加上MAC头,通过网卡发出去
1.3.2实例:假设发送1个IDR帧(关键帧)30KB,超过MTU(1500字节);
1.(应用层 - H.264编码器):产生1个NALU(类型=5,表示IDR帧),原始数据30KB。
2.(应用层 - RTP打包器):发现这个NALU > 1400字节(预留MTU安全值),于是启动FU-A分片模式,把它切成20个RTP包。
每个RTP包都有相同的SSRC和时间戳,但序列号一次递增(比如100~119)
第一个包标记S=1(起始),第20个包标记E=1(结束)且M位=1(帧结束标记)
3.(传输层 - UDP):这20个RTP包分别被加上UDP头(比如源端口1234,目的端口5678),变成20个UDP数据报。
4.(网络层 - IP):这20个UDP数据报分别被加上IP头(比如源IP=192.168.1.100,目的IP=8.8.8.8),变成20个IP数据包。
5.在网络线缆上传输的,就是这20个独立的IP数据包,他们可能走不通的路由到达目的地,顺序可能打乱(但没关系,接收端会根据RTP序列号重新排序)
2.RTP(实时传输协议)简述:
摄像头视频流,OTT通话这些在网络上跑的数据包,都是基于RTP封装的。
TCP,确保对方收到数据,再发第二包,如果丢了一包,必须重发,速度慢,不适合直播。
UDP,一次发送多包,不管顺序,不管是否丢包,速度极快,但视频传输不能接受丢包,而且包的顺序会乱。
RTP,给每包数据价格顺序和时间码,然后用UDP一次性发送,对方收到后按顺序进行罗列,丢的包标记出来。
RTP不保证数据一定到达,它只是给每个RTP包打上序列号,对方收到1、2、4,可以立刻发送请求重传3
RTP打上时间戳(Timestamp):记录这个RTP包里的数据应该在什么时候被播放。比如摄像头采集了一帧画面,时间戳是00:00:01.000,那么播放器就必须在那一毫秒把这帧显示出来,这是保证音画同步的关键。
2.1 RTP数据包结构介绍
bit0-1 V版本号,固定为2
bit2 P填充位,0表示padding不存在
bit3 X扩展位,1表示extension header 和 extension data
bit4-7 CC CSRC计数器,表示CSRC标识符的数量,一个CSRC的长度是4字节。用于表示对当前RTP数据包的生成或内容由贡献的源。
bit8 M标记位,表示一个RTP数据包是否是1个媒体帧的最后1个分片。
bit9-15 PT负载类型,表示RTP数据包的负载格式,96表示H.264视频
bit6-31 sequence number序列号,用于表示RTP数据包的顺序,用于检测数据包丢失,重建数据包顺序以及时间戳节后达到同步媒体流的目的。
bit32- timestamp时间戳,用于同步媒体流
bit SSRC同步源标识符,用于标识发送源。表示当前的音频数据来自哪个发言者。
bit CSRC
bit extension header
bit extension data
bit payload 负载数据,包含实际的媒体数据。
bit padding 用于填充字节,满足字节对齐的需要。
3.音频处理方式
1.音频数据PCM格式未用RTP封装,直接放入播放队列,不用RTP处理。
2.音频入队调度算法(缓存队列、播放队列的双缓冲方案)
播放队列空,PCMdata入缓存队列,缓存队列满则全部入播放队列。
播放队列满,PCMdata丢弃
播放队列有空间(不空不满),PCMdata入播放队列。
4.摄像头视频直播与音频播放线程处理
thread1 H264视频处理线程
NALU = GM.GetVideoData() //取1帧H264数据
FragmentH264(NALU) //对H264进行分片处理
AddRTPHeader(H264FU) //对H264分片进行RTP封装
send(H264RTP) //发送H264 RTP包
thread2 音频处理(采集、播放、EC(回声消除)、AAC、RTP发送)线程
EC:属于音频预处理环节,实时通话中,你的声音从对方扬声器传出,又被对方麦克风重新采集,传回给你,就形成了回声,EC技术的核心就是通过算法识别并抑制这种回声,保证通话质量,主要有自适应滤波器和非线性处理模块。
AAC高级音频编码:属于音频编码/压缩环节,作用是在保证音质的前提下,将采集到的大容量PCM音频数据压缩成更小的AAC数据包,以便于后续通过RTP进行高效的网络传输。
AudioData = audioQueue.Pop() //队列取音频数据
GM.WriteSPL(AudioData) //播放
ECProcess(AudioData, RecordData) //
AACEncode() //
AddRTPHeader(AACFrame) //
send(AACRTP) //
thread3 RTSP收包线程
TCP流 读数据
提取RTP包(0x24 + chan_num)
RTP解析
音频数据插入播放队列
5.摄像头视频数据采集流程
H.264视频在存储和传输时以NALU(网络抽象层单元)组织数据
1.NALU是H.264/AVC视频编码标准中,视频压缩后的数据单元,包含SPS/PPS/I帧/P帧等原始码流。
原始视频经过编码压缩后,会生成一串原始二进制数据(RBSP),但这段数据在网络上传输时,容易出错且不方便定位,所以H.264标准开始规定给这段原始数据加个包装盒NALU。
2.NALU的物理组成:头部(1字节) + 载荷(payload)
3.NALU头部详解:
bit0 禁止位,必须为0,如果为1说明传输发生错误,丢弃该包。
bit1~2 值越大说明越重要,网络拥堵时可以丢弃不重要的包。
bit3~7 NALU类型,核心字段,记录包裹内容:
NALU常见类型:
7 SPS序列参数集 包含分辨率、帧率、编码档次
9 PPS图像参数集 包含编码细节
5 IDR帧(关键帧) 解码器收到后,清空所有记忆,独立解码出完整画面
1 非IDR的I/P/B 承载普通图像的条带数据,解码时需要参考其他包裹。
6 SEI(补充增强信息) 额外信息如时间码、字母
9 分隔符 表示1个图像帧的开始,方便解码器定位。
4. 为什么有NALU?
1.找到帧边界,NALU通过特定起始码作为“包裹分隔符”。
2.应对丢包,如果一个几十K的i帧传输时丢了1bit,整个帧就废了,NALU可以将i帧分片成多个小的NALU,如果丢了可以重传该NALU。
5.总结:H.264编码器产生的SPS、PPS、I帧、P帧等数据,都必须套上NALU这个"格式外壳",并贴上类型标签(头部),才能被RTP协议安全、高效的发出去。
6. H.264视频由一个个视频序列组成,每个序列的第1帧图像必须为IDR(即时解码刷新),IDR是i帧,其余可以为I、P、B Slice,IDR周期为2秒,即每2秒必须发出1帧i帧,其余为P帧。
1.解码器看到IDR帧时,会清空之前所有的参考帧缓存,从头开始解码。
2.每2秒必须出1帧i帧,如果IDR间隔太长,视频压缩率会很高,但一旦网络丢包导致p帧解不出来,花屏会长达10秒,间隔太短导致i帧数据量大,最终占用大量带宽。
3.25fps,周期2秒,意思每50帧有1个i帧,其余49帧为p帧,好处是网络抖动时2秒就能恢复清晰画面。
7. 每个序列的图像数据在传输前,需传输SPS(序列参数集,其定义了Profile、Level、图像分辨率、帧率等参数) 和 PPS(图像参数集,其定义了编码相关参数,如初始量化参数,参考帧数量等),告知对端如何解码。
8. H.264使用RTP传输时,根据RTP3984定义,采样时钟设置为90K,25fps,每帧时间戳增量为3600个单位。
90k是RTP传输的标准时间单位,90k是25fps、30fps、50fps的公倍数,保证时间戳都是整数。
时间戳增量计算:时钟90000Hz,帧率25fps,那么每1帧时90000/25 =3600,也就是每发送1帧,RTP头里的时间戳就固定增加3600,接收端根据递增的3600就能精准以25fps的速度还原视频播放节奏。
9.视频画面数据采集流程总结
1.采集端:摄像头采集到原始图像。
2.编码策略:编码器强制每50帧(2秒)生成1个带 I Slice的IDR帧,中间生成P帧,并附带生成对应的SPS/PPS参数。
3.打包(RTP):将编码好的NALU(H.264数据单元)打包进RTP包,并给这个包打上时间戳(如果是25fps,此帧时间戳比上一帧大3600)
4.传输:发送端先发SPS/PPS,再发IDR帧,最后发P帧
5.接收端:收到数据后,通过SPS/PPS初始化解码器,根据时间戳3600的步进值控制播放速度,一旦收到IDR帧,立即刷新显示画面,丢弃之前的错误参考帧。
10.H.264视频的RTP传输
RTP(RCF3984)有三种H.264的NALU传输方式
1个NALU以1个RTP包发送(单包发送)。
多个NALU以1个RTP包发送(聚合包发送),分单时间戳和多时间戳等四种方式。
1个NALU分多个RTP包发送(分片包发送),分FU-A(不带DON信息)和FU-B(带DON信息)
注意:SPS和PPS由于单元字节较少(一般为几十字节),以单包方式发送(未聚合)
I Slice 和 P Slice字节数较多,一般以分片方式(FU-A)发送,注:720P的 I 帧约65KB,P帧在10KB左右,大小有波动。
最后一个分片包,RTP头Mark标志位帧边界
分片包的RTP序列号增长,但时间戳相同。
SPS和PPS的时间戳设置为相邻 I 帧的时间戳
11. 所有音视频的播放都由VLC处理
VLC的核心解码库,是一个无界面的SDK(C/C++库),提供了完整的音视频解码、渲染、流媒体播放能力。
APP通过掉了LibVLC的API,将音视频流交给它解码并输出到屏幕和扬声器。

浙公网安备 33010602011771号