TPAC 2019 小会之 WebCodecs 提案

TPAC 2019 小会之 WebCodecs 提案

一年一度的 W3C TPAC 大会在日本福冈如期举行,有幸和同事一起代表奇虎360参加了该会议。今年的 TPAC 中,中国区成员在会上展示了小程序白皮书以及小程序在中国的应用场景以及其前景。除此之外还有很多有意思的提案,今天我来为大家介绍一下 WICG 中的 WebCodecs。

什么是 WebCodecs

WebCodecs 的目的是在 Web 环境下提供高效的途径使用内置的软件或硬件编解码器进行媒体的编解码

WebCodecs 想要解决的问题

在一些现有的 Web API 中,他们内部都通过使用媒体编解码器达成其API的功能:

  • HTMLMediaElement 以及 MSE
  • WebAudio
  • MediaRecorder
  • WebRTC

但这些API都没有提供一些灵活的配置去控制所使用的编解码器,导致在某些场景下无法很好地使用这些API。

  • WebAudio: WebAudio 提供了解码媒体文件到PCM数据的能力,但前提是传入的数据是一个完整的文件,这样就没有了流式输入的能力。同时它并不提供进度信息,也不提供编码支持。
  • MediaRecorder:MediaRecorder 允许将含有视频和音频的 MediaStream 进行编码,但对于一些关键参数无法进行控制,例如比特率和带有编解码字符串的 MIME 类型。除了对实时数据的编码外不支持对其他形式数据的编码。MediaRecorder 在输出数据前有一段缓冲,这对于低延迟的场景来说不合适。对于需要使用自有容器格式的场景也不合适,MediaRecorder 输出的数据是已经使用容器包装。对于一些行为也无法预测,例如编码速度过慢时会发生什么事情。对于一些普通场景是可用的,但却缺少了很多的功能。
  • WebRTC:WebRTC 的 PeerConnection 允许编码和解码RTP流,同时与其他 WebRTC 和 MediaStream API 有着高度的耦合性,除此之外没有其他的使用场景。同时它的过程也比较不透明,JavaScript 无法访问编码好的数据。
  • HTMLMediaElement 和 MSE:两者都可以实时解码媒体数据,但对于音视频的输出有着比较小的灵活性。对于解码速度,唯一能够控制解码速度的是通过 playbackRate,但这会对音频产生影响,没办法决定是否需要提前进行解码。并且输入其中进行解码的数据流必须使用特定的容器格式。

正因为如此,一些应用会自己使用 JavaScript 或者 WebAssembly 去实现自己的编解码器,同时这样做也带来了一些问题:

  • 需要额外下载这些编解码器的代码,虽然浏览器中已经有一些格式的解码能力
  • 性能下降
  • 耗电量的提升

WebCodecs 的目标

  1. 流式数据的可用性:提供能够操作任何流式数据的能力,不仅仅是存储在内存中的数据,甚至是通过网络传输或是存储设备上的内容。
  2. 时钟控制:通过使用特定的时钟域控制编解码器来获得时移控制的能力。
  3. 高效性:通过权衡浏览器、系统、硬件解码器在宿主中的可用状况进行选择,使得编解码过程更加高效。同时允许编解码在主线程之外运行。
  4. 可扩展性:与其他 API 可以有很好的结合,例如 Stream、WebTransport 以及 WebAssembly。
  5. 恢复能力:在出现如网速问题、资源缺失导致掉帧时的恢复能力。
  6. 灵活性:做到能覆盖任意使用场景。
  7. 对等性:在编解码两种功能下使用相似的模式。

但以下这些功能不是 WebCodecs 所要做的事情

  1. 对音视频流的混合封装以及文件容器操作的API
  2. 提供在JS或WebAssembly中编写编解码器的能力

WebCodecs 的主要应用场景

  • 低延迟的直播(<3s 延迟)
  • 云端游戏
  • 直播流上传
  • 非实时的编码、解码、转码,例如进行本地文件编辑
  • 端对端加密
  • 控制缓冲行为
  • 将多路输入的媒体流编码成一路

WebCodecs 提出的解决方案

WebCodecs 基于 WHATWG 中的 Streams、TransformStreams 以及 MediaStreamTracks 进行实现。

EncodedAudioPackets 和 EncodedVideoFrames 将会提供编码器特定的编码数据,这样可以将数据保存起来或传输出去。

DecodedAudioPackets 和 DecodedVideoFrames 将会提供解码器特定的解码数据,这样可以将解码后的数据输送到其他 API 中,例如输送到 MediaStreamTracks 中。

通过 AudioTrackReader 将一个 MediaStreamTrack 转换为 DecodedAudioPacket 类型的 ReadableStream

通过 AudioEncoder 对象将 DecodedAudioPacket 编码为 EncodedAudioPacket

通过 AudioTrackWriter 将 DecodedAudioPacket 转换为 MediaStreamTrack

对于视频来说也是有同样的一组对象和接口。

由于 WebCodecs 的使用与容器无关,我们就可以很灵活的操作这些数据。在 WebCodecs 的 IDL 中,我们可以看到,首先我们需要从服务端获取相应编码好的数据流,例如一条音频。按照现有的 JavaScript API,要播放音视频,我们需要输送 MediaStream,MediaStream 由多个 MediaStreamTrack 组成,在 WebCodecs 中,我们可以通过 TrackWriter 将解码后的数据转换成 MediaStreamTrack。要生成 MediaStreamTrack,我们需要往 TrackWriter 写入 DecodedPacket 或 DecodedFrame,这些数据则通过 Decoder 对象输出。要输出这些内容,我们只要往 Decoder 里面写入编码好的数据即可。

代码例子

简单的解码例子

// 新建一个 OPUS 解码器实例
const audioDecoder = new AudioDecoder({ codec: 'opus'});
// 新建一个音频的 TrackWriter
const audioTrackWriter = new AudioTrackWriter();
// 将 TrackWriter 输出的 MediaStreamTrack 写入 MediaStream 中
document.querySelector('audio').srcObject = new MediaStream([audioTrackWriter.track]);

axios({
  method: 'get',
  url: 'http://player.com/audio-stream',
  responseType: 'stream'
}).then((response) => {
  // 将编码好的数据写入 Decoder 中解码成 DecodedPacket
  // 再将 DecodedPacket 写入 TrackWriter 中生成 MediaStreamTrack
  response.data.pipe(audioDecoder).pipe(audioTrackWriter.writable)
})

简单的直播编码例子

navigator.mediaDevices.getUserMedia({
  audio: true
}).then(stream => {
  // 将数据转换为 ReadableStream
  const audioStream = (new AudioTrackReader(stream)).readable;
  // 新建一个编码器,并设定一个比特率
  const audioEncoder = new AudioEncoder({
    codec: 'opus',
    settings: {
      targetBitRate: 60_000
    }
  });
  // 将媒体流写入编码器中最后交给发送方法上传到云端
  audioStream.pipe(audioEncoder).pipe(axios)
})

对于转码,我们只要将数据写入特定的 Decoder 中再写入 Encoder 即可完成转码的需求。

对于其他的应用场景,使用 WebCodecs 也有很大的优势。例如 IPTV 中的 SAP(二级音轨) 功能,目前的实现中,切换之后会导致整个视频的刷新。使用 WebCodecs API 之后,我们只需要等待 SAP 的音轨准备好之后,写入 TrackWriter 中即可无缝切换。

其他问题

在TPAC的小会中同时也提到了其他的问题:

隐私问题

编解码器的时钟信息以及编解码器的自身限制、编解码器的能力可以作为生成指纹信息的地方。

小结

对于本提案,还在非常早期的阶段,对于提案中的设想对未来在 Web 中的媒体处理变得更加丰富和高效,同时对于现有的问题也会有很好的解决。