一、RTP映射与Payload Type:动态PT与静态PT、a=rtpmap行、a=fmtp参数
各位同学,今天我们来聊一个在WebRTC媒体协商中非常核心、但也容易让人迷糊的话题——RTP映射与Payload Type。说白了,就是媒体数据在网络上传输时,怎么告诉对方“我这包数据是啥编码格式”。
我记得刚接触WebRTC那会儿,看到SDP里一堆“a=rtpmap:96 VP8/90000”这样的行,心里直犯嘀咕:为什么是96?为什么不是8?90000又是什么鬼?后来踩了不少坑,才慢慢搞明白这里面的门道。
1.1 什么是Payload Type?
Payload Type,简称PT,是RTP包头里的一个7位字段。取值范围0-127。它用来标识RTP包里面承载的媒体类型和编码格式。
你想想看,接收端收到一个RTP包,怎么知道里面是H264还是Opus?靠的就是这个PT值。所以PT的映射关系,必须在媒体协商阶段就定好。
核心要点:PT值本身只是一个数字编号,真正有意义的是这个编号对应的编码格式、采样率、声道数等信息。这些信息通过SDP中的a=rtpmap行来声明。
1.2 静态PT vs 动态PT
PT值分为两类:静态PT和动态PT。这个区分其实挺有意思的,我给大家拆开讲。
静态PT(Static Payload Type)
静态PT是IANA(互联网号码分配局)预先定义好的。范围是0-95。比如:
| PT值 | 编码格式 | 采样率 | 说明 |
|---|---|---|---|
| 0 | PCMU | 8000 | G.711 μ-law |
| 8 | PCMA | 8000 | G.711 A-law |
| 9 | G.722 | 8000 | 注意实际是16000采样 |
| 13 | CN | 8000 | 舒适噪声 |
| 26 | JPEG | 90000 | 视频 |
| 31 | H261 | 90000 | 视频 |
| 34 | H263 | 90000 | 视频 |
静态PT的好处是——不需要额外协商。双方都知道PT=0就是PCMU,PT=8就是PCMA。这在早期的VoIP系统里很常见。
注意:静态PT虽然方便,但WebRTC里几乎不用。因为WebRTC主推的编码格式(VP8、VP9、H264、Opus)都没有静态PT分配。所以必须走动态PT。
动态PT(Dynamic Payload Type)
动态PT的范围是96-127。这些值没有预定义含义,完全由媒体协商时动态分配。
举个例子,同一个VP8编码,在A会话里可能用PT=96,在B会话里可能用PT=100。完全取决于Offer/Answer双方怎么商量。
我在项目中遇到过一个问题:两个WebRTC客户端,一个把VP8映射到PT=96,另一个映射到PT=100。结果媒体流完全不通。排查了半天才发现是PT映射没对齐。嗯,从那以后我每次调试SDP都会先检查rtpmap行。
1.3 a=rtpmap行:核心映射声明
a=rtpmap行是SDP中用来声明PT映射关系的核心属性。它的格式很简单:
a=rtpmap:<payload_type> <encoding_name>/<clock_rate>[/<channels>]
来看几个实际例子:
// 音频:Opus编码,48000采样率,双声道
a=rtpmap:111 opus/48000/2
// 视频:VP8编码,90000时钟频率
a=rtpmap:96 VP8/90000
// 视频:H264编码,90000时钟频率
a=rtpmap:102 H264/90000
// 音频:ISAC编码,16000采样率,单声道
a=rtpmap:103 ISAC/16000
这里有几个关键点:
- encoding_name:编码名称,大小写敏感。WebRTC里通常大写,比如VP8、H264、OPUS(注意Opus在SDP里通常写opus小写,但实际实现中大小写都可能出现)
- clock_rate:时钟频率。音频通常是8000、16000、48000;视频固定90000
- channels:声道数,仅音频有。省略时默认为1
个人建议:写代码解析SDP时,encoding_name最好做大小写不敏感比较。我见过有的终端发"VP8",有的发"vp8",还有的发"Vp8"。统一处理能少很多bug。
1.4 a=fmtp参数:编码格式的细化配置
光有rtpmap还不够。很多编码格式需要额外的参数才能正常工作。这时候就需要a=fmtp行出场了。
格式如下:
a=fmtp:<payload_type> <parameter_string>
参数格式是key=value对,用分号分隔。不同编码的参数各不相同。
Opus的fmtp参数
a=fmtp:111 minptime=10; useinbandfec=1
常见参数:
- minptime:最小打包时长,单位毫秒。Opus支持10ms、20ms、40ms、60ms
- useinbandfec:是否使用带内FEC(前向纠错)。1表示开启
- stereo:是否立体声。1表示开启
- sprop-stereo:发送端是否支持立体声
H264的fmtp参数
a=fmtp:102 level-asymmetry-allowed=1; packetization-mode=1; profile-level-id=42e01f
这个就复杂多了。我简单说几个关键的:
- profile-level-id:H264的profile和level标识。比如42e01f表示Baseline profile,level 3.1
- packetization-mode:分包模式。0=单NALU模式,1=非交错模式(WebRTC常用)
- level-asymmetry-allowed:是否允许收发双方使用不同level
我曾经踩过的坑:有一次做H264联调,双方都声明了H264支持,但视频就是黑屏。最后发现是profile-level-id不匹配。一方是42e01f(Baseline),另一方是64001f(High profile)。虽然都叫H264,但profile不同,解码器不认。所以fmtp参数一定要仔细核对。
1.5 知识体系总览
下面这张图把RTP映射与Payload Type的核心逻辑串起来了。我建议你多看几遍,理解PT值、rtpmap、fmtp三者之间的关系。
1.6 实际SDP片段分析
来看一个完整的SDP媒体行片段,我加了注释:
m=audio 5004 UDP/TLS/RTP/SAVPF 111 103 104
// 音频媒体行,支持PT=111, 103, 104
a=rtpmap:111 opus/48000/2
// PT=111 对应 Opus编码,48kHz采样,双声道
a=fmtp:111 minptime=10; useinbandfec=1
// Opus的额外参数:最小打包10ms,开启带内FEC
a=rtpmap:103 ISAC/16000
// PT=103 对应 ISAC编码,16kHz采样,单声道
a=rtpmap:104 ISAC/32000
// PT=104 对应 ISAC编码,32kHz采样,单声道
m=video 5006 UDP/TLS/RTP/SAVPF 96 98 102
// 视频媒体行,支持PT=96, 98, 102
a=rtpmap:96 VP8/90000
// PT=96 对应 VP8编码
a=rtpmap:98 VP9/90000
// PT=98 对应 VP9编码
a=rtpmap:102 H264/90000
// PT=102 对应 H264编码
a=fmtp:102 level-asymmetry-allowed=1; packetization-mode=1; profile-level-id=42e01f
// H264的细化参数
你看,整个协商过程就是:先列出支持的PT列表,然后通过rtpmap告诉对方每个PT对应什么编码,最后用fmtp补充编码的细化参数。缺一不可。
1.7 常见问题与避坑指南
最后,我总结几个实际开发中容易踩的坑:
- PT值冲突:同一个媒体行里,不能有两个不同的编码使用同一个PT值。但不同媒体行(比如音频和视频)可以复用PT值
- rtpmap缺失:动态PT必须要有对应的rtpmap行。没有rtpmap的PT,接收端无法解码
- fmtp参数不匹配:Offer和Answer两端的fmtp参数必须兼容。比如H264的profile-level-id不一致,就会导致协商失败
- 编码名称大小写:虽然规范说大小写不敏感,但实际实现中有的终端只认大写或只认小写。建议统一大写
调试小技巧:在Chrome浏览器里打开chrome://webrtc-internals,可以看到完整的SDP Offer/Answer。我经常用这个工具来排查PT映射问题。一眼就能看出双方rtpmap是否对齐。
好了,关于RTP映射与Payload Type的核心内容就讲到这里。记住一句话:PT是编号,rtpmap是字典,fmtp是注释。三者配合,才能让媒体数据在网络上正确传输。