一个未压缩的视频数据有多大?我们举个例子计算一下。
在今天的网络环境下,这么大的数据量是不可能进行实时传输的,我们需要对视频进行有效压缩,这是对视频(或音频) 进行编解码的主要目的。
WebRTC 规范对音视频编码有强制要求,对于视频,要求所有兼容 WebRTC 的浏览器必须支持 VP8 和 AVC/H.264 视频编码,当然除了这两种编码格式之外,浏览器也可以选择支持其他编码格式;对于音频,要求必须支持 Opus 和 G.711 PCM 编码格式。
当前主流浏览器对 WebRTC 编码格式的支持情况如下表所示。
| WebRTC 编码格式 | |||
| 编码格式 | 媒体类型 | 兼容浏览器 | 说明 |
| VP8 | 视频 | Chrome、Edge、Firefox、Safari(12.1+) | WebRTC 规范 |
| H.264 | 视频 | Chrome(52+)、Edge、 Firefox、 Safari | WebRTC 规范 |
| VP9 | 视频 | Chrome(48+)、Firefox | |
| AV1 | 视频 | Chrome(70+)、Firefox(67+) | |
| Opus | 音频 | Chrome、 Firefox、Safari、 Edge | WebRTC 规范 |
| G.711 PCM | 音频 | Chrome、Firefox、Safari | WebRTC 规范 |
AV1专门为 Web 技术设计,拥有比 VP9 更高的压缩率,但由于 AV1 技术尚未成熟目前不具备产品化条件,我们就不做过多介绍了。
VP8 (Video Processor 8)由 On2 公司开发,谷歌收购 On2 后,将 VP8 开源。从压缩率和视频质量方面看,VP8 与 H264 很相近。
VP9 (Video Processor 9)是谷歌在 VP8 的基础上研发的优化版,拥有比 VP8 更好的性能和视频质量,与 H.265 更相近。
VP8/VP9都是开源且免费的技术,没有版权相关的问题(H.265 有版权问题),可以放心使用。
通常将视频格式WebM与VP8/VP9结合使用。注意,因为目前Safari浏览器不支持VP9也不支持WebM,所以如果要使用VP9,需要考虑为 Safari提供其他编码方案。
H.264是在MPEG-4技术的基础上建立起来的编码格式,又名为AVC(Advanced VideoCoding),目前被广泛应用于各种流媒体。
H.264 的使用非常灵活,通过更改配置可以胜任不同场景,如配置 Constrained BaselineProfile(CBP)使用了较低的带宽,适合用于视频会议和移动网络;Main Profile 适用于标准的视频内容;而 High Profile 则适用于高清蓝光 DVD 视频。
| H.264 配置表 | |
| 配置 | 16进制编码值 |
| Constrained Baseline Profile (CBP) | 42 |
| Baseline Profile (BP) | 42 |
| Main Profile (MP) | 4D |
| High Profile (HiP) | 64 |
WebRTC规范要求必须支持 Constrained Baseline(CB) 配置,以便应用于低延时的视频会议场景。SDP 在参数 profile-level-id 指定 H.264 配置(profile)和级别(level),形式如下。
profile-level-id=42e01f
其中42 对应着 Constrained BaselineProfile 配置。
参数 packetization-mode 用于指明封包模式,取值说明如下。
注意,WebRTC 两端的编码格式和相应的配置参数都需要保持一致,否则建立连接过程会失败。
Opus是WebRTC主要使用的音频编码格式,具备很好的灵活性和可扩展性,可用于语音、音乐播放等复杂的音频场景。
Opus支持多种压缩算法,甚至可以在同一个音频内应用多个压缩算法,编码器可灵活设置码率、带宽、算法压缩等参数。
Opus完全开源,支持码率范围为6~510kbps,支持最多255个音频通道,最大采样率 48kHz,延时范围为5~66.5ms,可用于 MP4/WebM/MPEG-TS 封装格式。
G.711 PCM 是一种实现简单,兼容性好的音频编码格式,支持码率64kbps,支持采样率 8kHz,通常用于向下兼容。
推荐在应用中采用WebRTC规范支持的编码格式,如果采用非推荐的编码格式,需要认真考虑回退方案。
不同的编码格式在浏览器兼容性、占用带宽、能耗等方面存在明显差异,我们可以根据应用程序的特点灵活选择。
(1)音频
即使是在网络环境不好的情况下,使用Opus的窄带模式仍然可以保证较好的通话质量。如果希望应用程序能提供较好的兼容性,可以考虑使用G.711编码格式提供较好的通话质量。
(2)视频
选择视频编码通常需要考虑如下因素。
① 许可条款
VP8/VP9 是完全免费的,但是H.264可能会有潜在的专利费用,不过对于大多数Web开发者来讲,目前无须担心费用问题,H.264的专利所有人表示只会向编解码软件收费。
② 耗能
尤其是在iOS和iPadOS平台,支持硬件编码的H.264更加省电。出于兼容性考虑Safari12.1以后的版本支持 VP8,但是很遗憾,没有提供硬件编码支持。
③ 性能
从终端用户角度来看,VP8与H.264的性能差不多,可以同等应用到WebRTC应用中。
④ 使用复杂度
因为H.264涉及较多的配置和参数,所以使用复杂度较高,而VP8则相对简单很多。
WebRTC 使用 RTCRtpCodecCapability 描述当前平台支持的编码格式。RTCRtpCodecCapability 的定义如下面的代码清单所示。
//RTCRtpCodecCapabiity的定义
dictionary RTCRtpCodecCapability {
required DOMString mimeType;
required unsigned long clockRate;
unsigned short channels;
DOMString sdpFmtpLine;
};
| RTCRtpCodecCapability 属性说明 | |
| 属性 | 说明 |
| mimeType | MIME媒体类型 |
| clockRate | 时钟频率 |
| channels | 最大通道数 |
| sdpFmtpLine | SDP与该编码格式对应的a=fmtp行信息 |
WebRTC 使用 RTCRtpCodecParameters 描述当前使用的编码格式。RTCRtpCodecParameters 的定义如下面的代码清单所示。
//RTCRtpCodecParameters的定义
dictionary RTCRtpCodecParameters {
required octet payloadType;
required DOMstring mimeType;
required unsigned long clockRate;
unsigned short channels;
DOMString sdpFmtpLine;
};
| RTCRtpCodecParameters 属性说明 | |
| 属性 | 说明 |
| payloadType | RTP载荷类型,用于标识该编码格式。我们在RTP协议相关内容里介绍过载荷类型 |
| mimeType | MIME 媒体类型 |
| clockRate | 时钟频率 |
| channels | 最大通道数 |
| sdpFmtpLine | SDP与该编码格式对应的a=fmtp行信息 |
WebRTC 使用 RTCRtpEncodingParameters 描述编码格式使用到的编码参数。RTCRtpEncodingParameters 的定义如下面的代码清单所示。
//RTCRtpEncodingParameters的定义
dictionary RTCRtpEncodingParameters : RTCRtpCodingParameters {
boolean active = true;
unsigned long maxBitrate;
double scaleResolutionDownBy;
};
dictionary RTCRtpCodingParameters {
DOMString rid;
};
| RTCRtpEncodingParameters 属性说明 | |
| 属性 | 说明 |
| active | 编码是否处于活跃状态,true表示活跃,false表示不活跃 |
| maxBitrate | 媒体流的最大码率,单位是 bps |
| scaleResolutionDownBy | 指示将视频内容发送给对等端之前,如何缩减视频尺寸。比如,该值取2,则高和宽都除以2,结果只发送原尺寸的1/4;该值取1,则尺寸保持不变,默认值为1 |
| rid | RTP id,rid 头扩展 |
由于不同平台支持的编码格式不同,所以在为WebRTC应用编码之前,要先获取当前平台支持的编码格式列表。
使用静态方法 RTCRtpSender.getCapabilities() 或者 RTCRtpReceiver.getCapabilities() 可以获得当前平台支持的编码格式列表,方法的参数是媒体类型,如下面的代码清单所示。
codecList = RCRtpSender.getCapabilities("video" ).codecs;
codecList 是一个 RTCRtpCodecCapability 对象数组,每个对象描述一个编码格式。 在Chrome83中执行上述方法,codecList的结果如下面的代码清单所示。
//RTCRtpSender.getCapabilities()输出
0:{clockRate:90000,mimeType:"video/VP8"}
1:{clockRate:90000,mimeType:"video/rtx"}
2:{clockRate:90000,mimeType:"video/VP9",sdpFmtpLine:"profile-id=0"}
3:(clockRate: 90000,mimeType:"video/vp9",sdpFmtpLine: "profile-id=2"}
4:(clockRate: 90000,mimeType:"video/H264",sdpFmtpLine: "level-asymmetry-
allowed=l;packetization-mode=l;profile-level-id=42001f"}
5:{clockRate:90000,mimeType:"video/H264",sdpFmtpLine:"level-asymmetry-
allowed=l;packetization-mode=0;profile-level-id=42001f"}
6:{clockRate:90000,mimeType:"video/H264",sdpFmtpLine:"level-asymmetry-
allowed=l;packetization-mode=l;profile-level-id=42e01f" }
7:(clockRate: 90000,mimeType:"video/H264",sdpFmtpLine:"level-asymmetry-
allowed=l;packetization-mode=0;profile-level-id=42e01f"}
8:(clockRate: 90000,mimeType:"video/H264",sdpFmtpLine:"level-asymmetry-
allowed=1;packetization-mode=l;profile-level-id=4d0032"}
9:(clockRate: 90000,mimeType:"video/H264", sdprmtprine: "level-asymmetry-
allowed=1;packetization-mode=l;profile-level-id-640032"}
10:{clockRate:90000,mimeType:"video/red"}
11:(clockRate:90000, mimerype: "video/ulpfea"}
在输出结果中,"video/rtx"是重传入口,"video/red"是冗余编码入口,"video/ulpfec"是错误重定向入口,它们都不是可用的编码格式。
获取当前平台支持的编码格式列表后,如何指定编码格式呢?这时候只须调整一下列表顺序,把我们想要的编码格式放在最前面,然后再告诉WebRTC就可以了。
下面的代码清单针对每个媒体轨道应用编码格式mimeType,该示例展示了如何获取当前浏览器的视频编码能力,并将与mimeType匹配的编码能力优先级调整为最高(数组最前面),然后调用 setCodecPreferences 进行设置。
//指定编码格式示例
function changeVideoCodec(mimeType) {
const transceivers = pc.getTransceivers();
transceivers.forEach(transceiver => {
const kind = transceiver.sender.track.kind;
let sendCodecs = RTCRtpSender.getCapabilities(kind).codecs;
let recvCodecs = RTCRtpReceiver.getCapabilities(kind).codecs;
if (kind === "video") {
sendCodecs = preferCodec(sendCodecs,mimeType);
recvCodecs = preferCodec(recvCodecs,mimeType);
transceiver.setCodecPreferences([...sendCodecs, ...recvCodecs]);
}
});
pc.onnegotiationneeded();
}
function preferCodec(codecs, mimeType) {
let otherCodecs = [];
let sortedCodecs = [];
codecs.forEach(codec => {
if (codec.mimeType === mimeType) {
sortedCodecs.push(codec);
} else {
otherCodecs.push(codec);
}
});
return sortedCodecs.concat(otherCodecs);
}
我们给 WebRTC 提供了包含多个编码格式的数组,并按照优先级做了排序,WebRTC 从最高优先级开始,遍历这个数组,逐一尝试应用编码格式,如果不支持,则尝试下一个。
这时就产生了一个问题,WebRTC最终使用了哪个编码格式呢?要回答这个问题就要使用 getParameters() 方法进行査询,如下面的代码清单所示。
//查询当前使用的编码格式
const senders = peerConnection.getSenders();
senders.forEach((sender) => {
if(sender.track.kind === "video") {
codecList = sender.getParameters().codecs;
return;
}
});
上面的代码清单中的codecList是一个包含 RTCRtpCodecParameters 的数组,代表了当前使用的编码格式。