剑痴乎

  • 首页
  • 文章分类
    • 音视频
    • WebRTC
    • 编程之美
    • Linux
    • Windows
    • 生活点滴
    • 校园生活
  • 参考
    • API参考
    • 实用工具
    • 测试音视频
    • 文档
  • 留言板
  • 关于
剑痴乎
代码为剑,如痴如醉
  1. 首页
  2. WebRTC
  3. 正文

WebRTC研究:Encoded Transform

2021年3月28日 4151点热度 11人点赞 6条评论

前段时间看到腾讯云的分享:《WebRTC Insertable Stream 初探与 WebRTC “管道化”》。想不到国内也开始关注这个新特性了,作为一个经常关注WebRTC最新提交代码的人,去年初就关注到了WebRTC Insertable Streams相关代码提交,不过对我这样一个Native Code开发者来说,用途不大,毕竟自己也可以在源码层面实现,获取或者修改自己需要的任何数据。但是这一特性对Web开发者来说用途就大了,给了Web开发者更多的自由与操作性。

WebRTC Insertable Streams

以前WebRTC Web开发者无法操作WebRTC采集或者编码后的音视频数据,当然也无法使用自定义的音视频编解码器,很多人使用WebRTC可能只是想用到传输这一块功能。当然也有比较黑科技的做法,使用WebAssembly技术把WebRTC库编译成二进制文件给Javascript调用,不过这一做法难度颇大,很多人编译WebRTC都废了九牛二虎之力,更别说使用WebAssembly技术编译,而且自己改的不一定稳定。所以WebRTC Insertable Streams这项新特性就诞生了。
WebRTC Insertable Streams目前包含两项内容:

  • Encoded Transform:操作编码后的数据以及解码前数据(Post-encoding/Pre-decoding)
  • Mediacapture Transform:操作编码前数据以及解码后数据(Pre-encoding /Post-decoding)

WebRTC Insertable Streams这项新特性增加了些新API,让Web开发者对媒体数据可以做如下的事:

    • 允许用户不打破WebRTC正常处理pipeline,手动处理数据
  • 允许使用WASM技术进行数据处理
  • 允许使用Web Worker技术处理数据,避免阻塞主线程
  • 允许端到端间进行数据加解密,避免安全以及隐私泄露风险(不信任SFU服务器,避免中间人攻击)

根据[1],这里列举几个应用场景:

  • 视频特效
  • 视频背景移除
  • 语音处理
  • 动态控制编解码器参数
  • 各个媒体Tracks的自定义带宽分配
  • 自定义编解码器(与WebCodecs结合)

是不是很有趣?不过截止今天,WebRTC也只是实现了Encoded Transform特性,只能操作编码后的数据。所以本篇文章就从源码层面分析下目前的Encoded Transform实现。

Encoded Transform

前面说到Encoded Transform处理的是Post-encoding/Pre-decoding的媒体数据。

该特性接口定义位于FrameTransformerInterface中。

FrameTransformerInterface

看下接口定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Objects implement this interface to be notified with the transformed frame.
class TransformedFrameCallback : public rtc::RefCountInterface {
public:
  virtual void OnTransformedFrame(
      std::unique_ptr<TransformableFrameInterface> frame) = 0;
 
protected:
  ~TransformedFrameCallback() override = default;
};
 
// Transforms encoded frames. The transformed frame is sent in a callback using
// the TransformedFrameCallback interface (see above).
class FrameTransformerInterface : public rtc::RefCountInterface {
public:
  // Transforms |frame| using the implementing class' processing logic.
  virtual void Transform(
      std::unique_ptr<TransformableFrameInterface> transformable_frame) = 0;
 
  virtual void RegisterTransformedFrameCallback(
      rtc::scoped_refptr<TransformedFrameCallback>) {}
  virtual void RegisterTransformedFrameSinkCallback(
      rtc::scoped_refptr<TransformedFrameCallback>,
      uint32_t ssrc) {}
  virtual void UnregisterTransformedFrameCallback() {}
  virtual void UnregisterTransformedFrameSinkCallback(uint32_t ssrc) {}
 
protected:
  ~FrameTransformerInterface() override = default;
};

上面代码中,FrameTransformerInterface注册完TransformedFrame相关回调后,在回调TransformedFrameCallback::OnTransformedFrame获取编码后的Payload数据,调用TransformableFrameInterface接口对Payload数据进行操作:

调用GetData获取Payload数据,调用SetData设置Payload数据,我们可以通过GetData获取原始编码后的媒体数据,处理后再调用SetData达到修改媒体数据目的。当然了,也可以获取音视频的其他信息,例如音频,可以获取音频的RTP报头信息。

接下来我们以音频为例看下目前Encoded Transform相关代码实现。

音频发送端

这里先看下发送端代码流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
     采集
      ↓
3A等音频前处理
      ↓
     编码
      ↓
ChannelSend::SendData
      ↓
ChannelSend::SendRtpAudio
      ↓
    RTP打包
      ↓
   发送到网络

WebRTC对ChannelSend::SendData进行了修改,其中frame_transformer_delegate_(ChannelSendFrameTransformerDelegate)继承自TransformedFrameCallback。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int32_t ChannelSend::SendData(AudioFrameType frameType,
                              uint8_t payloadType,
                              uint32_t rtp_timestamp,
                              const uint8_t* payloadData,
                              size_t payloadSize,
                              int64_t absolute_capture_timestamp_ms) {
  RTC_DCHECK_RUN_ON(&encoder_queue_);
  rtc::ArrayView<const uint8_t> payload(payloadData, payloadSize);
  if (frame_transformer_delegate_) {
    // Asynchronously transform the payload before sending it. After the payload
    // is transformed, the delegate will call SendRtpAudio to send it.
    frame_transformer_delegate_->Transform(
        frameType, payloadType, rtp_timestamp, rtp_rtcp_->StartTimestamp(),
        payloadData, payloadSize, absolute_capture_timestamp_ms,
        rtp_rtcp_->SSRC());
    return 0;
  }
  return SendRtpAudio(frameType, payloadType, rtp_timestamp, payload,
                      absolute_capture_timestamp_ms);
}

处理完数据后通过ChannelSend::SendRtpAudio将数据返回到原来的的处理Pipeline。所以若初始化了ChannelSendFrameTransformerDelegate,处理流程变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
     采集
      ↓
3A等音频前处理
      ↓
     编码
      ↓
ChannelSend::SendData
      ↓
ChannelSendFrameTransformerDelegate::Transform
// 由我们处理Payload数据
FrameTransformerInterface::Transform
TransformableAudioFrameInterface::GetData
TransformableAudioFrameInterface::SetData
TransformedFrameCallback::OnTransformedFrame
ChannelSendFrameTransformerDelegate::SendFrame
      ↓
ChannelSend::SendRtpAudio
      ↓
    RTP打包
      ↓
   发送到网络

这里FrameTransformerInterface由我们外部自己实现。所以我们需要修改Payload数据的话需要实现FrameTransformerInterface,在FrameTransformerInterface::Transform中通过TransformableAudioFrameInterface进行获取或者修改Paylod数据,最后通过注册的回调接口返回修改的Payload数据。。

音频接收端

这里先看下接收端代码流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
  网络接收
    ↓
ChannelReceive::OnRtpPacket
    ↓
ChannelReceive::ReceivePacket
    ↓
ChannelReceive::OnReceivedPayloadData
    ↓
   NetEq
    ↓
   解码
    ↓
混音等后处理

WebRTC对ChannelReceive::ReceivePacket处理流程进行了修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void ChannelReceive::ReceivePacket(const uint8_t* packet,
                                   size_t packet_length,
                                   const RTPHeader& header) {
  const uint8_t* payload = packet + header.headerLength;
 
  rtc::ArrayView<const uint8_t> payload_data(payload, payload_data_length);
  if (frame_transformer_delegate_) {
    // Asynchronously transform the received payload. After the payload is
    // transformed, the delegate will call OnReceivedPayloadData to handle it.
    frame_transformer_delegate_->Transform(payload_data, header, remote_ssrc_);
  } else {
    OnReceivedPayloadData(payload_data, header);
  }
}

同前面发送端代码,其中frame_transformer_delegate_(ChannelReceiveFrameTransformerDelegate)也是继承自TransformedFrameCallback。最后处理完数据后通过ChannelReceive::OnReceivedPayloadData将数据返回到原来的处理Pipeline。具体代码类似发送端,就不再分析了。最后代码处理流程变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  网络接收
    ↓
ChannelReceive::OnRtpPacket
    ↓
ChannelReceive::ReceivePacket
    ↓
ChannelReceiveFrameTransformerDelegate::Transform
// 由我们处理Payload数据
FrameTransformerInterface::Transform
TransformableAudioFrameInterface::GetData
TransformableAudioFrameInterface::SetData
TransformedFrameCallback::OnTransformedFrame
ChannelReceiveFrameTransformerDelegate::ReceiveFrame
    ↓
ChannelReceive::OnReceivedPayloadData
    ↓
   NetEq
    ↓
   解码
    ↓
3A等后处理

在接收端我们也需要在外部实现FrameTransformerInterface,从而进行数据处理。

总结

本文简单介绍了Insertable Streams这个新特性,并从音频角度分析了其中一项内容的源码实现:Encoded Transform。等后续WebRTC官方更新了Mediacapture Transform实现代码,我也将做下分析。

参考

[1] webrtc-encoded-transform.https://github.com/w3c/webrtc-encoded-transform.

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
标签: WebRTC
最后更新:2024年3月15日

Jeff

管理员——代码为剑,如痴如醉

打赏 点赞
< 上一篇
下一篇 >

文章评论

  • bibo

    谢谢大佬的分享。

    2021年3月29日
    回复
  • wosshzhb

    如果自己实现了FrameTransformerInterface这个接口,那么注册的时机是在哪里?目前貌似没有webrtc的实际demo?

    2021年6月28日
    回复
    • Jeff

      @wosshzhb 这个chromium源码有相关使用,这个就一个非常简单的接口,如果你看过代码,就不会问什么时候注册了

      2021年7月5日
      回复
  • sven

    谢谢大佬的分享
    请问大佬,如果想在neteq后获取packet,native是否有现成途径获取到,还是要魔改呢?

    2021年7月29日
    回复
  • Richard

    楼主你好,图片不见了,可以更新一下嘛?

    2022年5月17日
    回复
  • Ivan

    可以在安卓中使用吗? 我尝试了这种方法,但似乎框架没有改变。 您能否给出如何进行简单转换的完整代码示例? 例如只是对帧进行异或。 谢谢你

    2022年7月20日
    回复
  • razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
    取消回复

    这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。

    版权声明

    为支持原创,创作更好的文章,未经许可,禁止任何形式的转载与抄袭,如需转载请邮件私信!本人保留所有法定权利。违者必究!

    文章目录
    • WebRTC Insertable Streams
    • Encoded Transform
      • FrameTransformerInterface
      • 音频发送端
      • 音频接收端
    • 总结
    • 参考
    最近评论
    ztt 发布于 3 周前(04月05日) 你好,想看里面的视频和图片为什么没有显示呢?需要下flash吗还是什么。
    huowa222 发布于 1 个月前(03月26日) 同问
    邱国禄 发布于 2 个月前(02月17日) Receive Delta以0.25ms为单位,reference time以64ms为单位,kDe...
    啊非 发布于 4 个月前(12月30日) 大神,请教一个问题: constexpr int kBaseScaleFactor = Tran...
    啊非 发布于 4 个月前(12月30日) reference time:3字节,表示参考时间,以64ms为单位,但是 代码里面是 Trans...
    相关文章
    • WebRTC资讯:H265支持进展
    • WebRTC研究:Audio level
    • Mac平台WebRTC编译
    • WebRTC研究:RTP时间戳的产生
    • WebRTC研究:统计参数之丢包率

    COPYRIGHT © 2024 jianchihu.net. ALL RIGHTS RESERVED.

    Theme Kratos Made By Seaton Jiang