在"WebRTC的现状与未来"(https://webrtchacks.com/webrtc-today-tomorrow-bernard-aboba-qa/)这篇文章中讲述了WebRTC要带来的一些新的特性, 这里我们重点探索一下WebRTC Insertable Streams
。
WebRTC Insertable Streams
提供了让用户操作WebRTC编码后数据的能力,最新的规范在这里 https://w3c.github.io/webrtc-encoded-transform/,目前已经改名叫做WebRTC Encoded Transform。 我们先看下WebRTC的视频处理流程:
发送流程:
接收流程:
WebRTC Insertable Streams
可以让我们在发送流程中的S2和S3之间,接受流程的R3和R4之间加入处理编码后的数据的能力, 起初是为了端到端加密而设计, 但他的使用场景确可以进一步的拓展。
WebRTC Insertable Streams 在Chrome M82版本中引入,但一直是实验状态,可以在Chrome Canary版本中进行体验。基本用法如下
初始化PeerConnection的时候需要加上特殊参数:
var pc = new RTCPeerConnection({
encodedInsertableStreams: true,
});
上行RTCRtpSender 创建EncodedStreams:
let transceiver = await pc.addTransceiver(stream.getVideoTracks()[0], {
direction: "sendonly",
streams: [stream],
});
setupSenderTransform(transceiver.sender);
function setupSenderTransform(sender) {
console.log('sender kind=%s', sender.track.kind);
const senderStreams = sender.createEncodedStreams();
const readableStream = senderStreams.readableStream;
const writableStream = senderStreams.writableStream;
const transformStream = new TransformStream({
transform: encodeFunction,
});
readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream);
}
function encodeFunction(chunk, controller) {
const tmp = new DataView(chunk.data);
if (tmp.getUint32(0) == 1) { // h264 start code '0001'
console.log("h264 =======")
}
const newData = new ArrayBuffer(chunk.data.byteLength + 4);
const newView = new DataView(newData);
let metadata = new ArrayBuffer(4);
let metaView = new DataView(metadata);
metaView.setUint32(0, frames++);
const data = new Uint8Array(newData);
data.set(new Uint8Array(chunk.data));
data.set(new Uint8Array(metadata), chunk.data.byteLength);
chunk.data = newData;
controller.enqueue(chunk);
console.log("Send frame index ===", frames);
}
下行RTCRtpReceiver 创建 EncodedStreams:
const transceiver = await pc.addTransceiver("video", {
direction: "recvonly",
});
setupReceiverTransform(transceiver.receiver);
function setupReceiverTransform(receiver) {
console.log('receiver kind=%s', receiver.track.kind);
const receiverStreams = receiver.createEncodedStreams();
const readableStream = receiverStreams.readableStream;
const writableStream = receiverStreams.writableStream;
const transformStream = new TransformStream({
transform: decodeFunction,
});
readableStream
.pipeThrough(transformStream)
.pipeTo(writableStream);
}
function decodeFunction(chunk, controller) {
const view = new DataView(chunk.data);
//last 4 bytes
const count = view.getUint32(chunk.data.byteLength - 4);
chunk.data = chunk.data.slice(0, chunk.data.byteLength - 4);
controller.enqueue(chunk);
console.log("Receive frame index ===", count);
}
在体验完WebRTC Insertable Streams
之后让我想到的一个词是"管道化", WebRTC的音视频的采集,前处理,后处理,编解码,渲染都可以不再依赖WebRTC的默认实现, 你可以自己实现采集逻辑,使用自己的编码器方案,最后喂给WebRTC编码好的音视频数据。 WebRTC可以只用来做网络传输,重传,FEC, JitterBuffer,NetEQ,然后回调出来远端的音视频数据。WebRTC本身的协议栈可以只当做传输通道来用, 这将极大的扩展WebRTC的使用场景。
这个是WebRTC Insertable Streams
本来设计出来要支持的场景,但端到端加密会为服务端的录制,以及跟现有的直播基础架构互通造成很大的困扰,目前看在国内的服务商不会太跟进端到端加密, 对于海外的场景端到端加密却是一个基本项。
我们可以在编码后的数据中添加一些meta信息和音视频帧一起发送,在接收端收到音视频帧的时候再把这些meta信息拿出来。
教育场景的白板同步是一个很适合的场景,可以弥补在Web中无法使用SEI的遗憾。
钢琴教学场景中按键信息和音视频完全同步。
VR/AR场景中需要随着音视频同步的摄像头信息,坐标信息等。
远程音视频控制场景中也可以把控制信令打包进音视频信息中。
WebRTC通话场景中,尤其是经过服务端多跳中转的场景,我们很难去探测端到端的延迟, 这个对我们的数据上报造成很大的困扰。 我们可以在发送端将绝对时间戳打包进帧信息中,在整个链路透传,在播放端把绝对时间戳拿出来进行统计全链路的延迟。
WebRTC Insertable Streams 可以让我们自定义采集和编码, 这样的话我们可以绕过WebRTC原本的限制,用WebAudio 采集音频加入自己的降噪, 回声消除的算法, 甚至增加变音的效果,然后再交给WebRTC传输。 同样视频 可以增加自己的采集和编码逻辑, 比如可以对视频增加美颜滤镜, 使用自己优化过的编码器,增加区域编码等。 渲染环节也可以增加渲染逻辑, 比如增加视频边框, 视频叠加等特效。
第五条应该是第四条的延伸,在web中我们没法关闭WebRTC的APM模块,这就导致我们采集的音频都要经过APM模块的处理, APM模块会对非人声部分进行过滤,对音乐是非常不友好的。 基于我们可以自定义采集音频以及编码,我们只需要把高音质的音乐自己做编码然后通过WebRTC Insertable Streams
的方式喂给WebRTC,我们就可以绕过APM模块的处理,让WebRTC具体传输高品质音乐的能力。(该设想理论上行,但未做进一步验证, 有兴趣的伙伴可以来验证一下。)
上面的几个场景是我立即能想到的, 相信行业内会有各种各样的创新用法出现, 非常期待看到WebRTC Insertable Streams普及之后那些创新的玩法。
WebRTC Insertable Streams
让我们可以对编码后的音视频的数据进行修改, 但WebRTC 在发送数据的时候是通过RTP来打包的,而RTP打包的时候对码流数据的格式是有要求的,这样就造成你不可能任意的对编码的数据修改,比如H264的码流数据需要以“0001”开始, 如果你修改这个startbit很明显会破坏RTP的分包逻辑,导致传输失败。 所以增加meta信息并不是可以随便加的, 不能破坏WebRTC本身的RTP打包逻辑。 比如H264的场景下,我们可以在整帧数据后面加上自己的一些自定数据。 在播放侧按照相反的逻辑再解析出来。我实现了一个WebRTC Insertable stream demo, 服务端使用medooze-media-server, 推流端会把当前视频帧的index打包进编码后的数据帧, 经过服务器的中转,在拉流端把当前视频帧的index解析出来,并打印到console中, 感兴趣的同学可以自己试验一下,项目地址在 https://github.com/notedit/webrtc-insertable-stream-play