前段时间看到腾讯云的分享:《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.
文章评论
谢谢大佬的分享。
如果自己实现了FrameTransformerInterface这个接口,那么注册的时机是在哪里?目前貌似没有webrtc的实际demo?
@wosshzhb 这个chromium源码有相关使用,这个就一个非常简单的接口,如果你看过代码,就不会问什么时候注册了
谢谢大佬的分享
请问大佬,如果想在neteq后获取packet,native是否有现成途径获取到,还是要魔改呢?
楼主你好,图片不见了,可以更新一下嘛?
可以在安卓中使用吗? 我尝试了这种方法,但似乎框架没有改变。 您能否给出如何进行简单转换的完整代码示例? 例如只是对帧进行异或。 谢谢你