当前位置: 首页 > news >正文

【AndroidRTC-11】如何理解webrtc的Source、TrackSink

Android-RTC系列软重启,改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性,方便形成肌肉记忆。同时不分种类、不分难易程度,在线征集问题切入点。

问题1:如何理解VideoSource、VideoTrack&VideoSink三者的关系?它们只能是1v1v1的关系吗?

问题2:有前三者,还有一个MediaStream,这又有什么作用?

答1:在WebRTC中,VideoSource、VideoSink和VideoTrack三者构成了视频数据的生产、传输和消费链路,其关系可概括如下:

组件角色功能
VideoSource数据生产者生成原始视频帧(如摄像头、屏幕捕获、文件解码器)。
VideoTrack数据通道与管理者将VideoSource的数据封装为媒体轨道,管理数据的传输和分发。
VideoSink数据消费者接收并处理视频帧(如渲染到屏幕、编码发送、保存到文件等)。

三者构成 ​​“1:N:M”​ 的拓扑结构:

1个VideoTrack 必须关联 ​1个VideoSource强制一对一,VideoTrack与VideoSource必须一一绑定,无法跨源管理。

1个VideoTrack 可以分发到 ​多个VideoSink灵活一对多,VideoTrack可分发到多个VideoSink,支持复杂业务场景。

1个VideoSource 可以被 ​多个VideoTrack 共享,多源共存方案,通过创建多个 VideoTrack 实现多源混合,并通过 MediaStream 统一管理。

代码逻辑示例:

**(1) 创建VideoTrack并绑定Source**

// 创建摄像头VideoSource
rtc::scoped_refptr<VideoCaptureModule> capture_module = ...;
std::unique_ptr<VideoSource> video_source(new VideoSource(capture_module));

// 创建VideoTrack并绑定Source
rtc::scoped_refptr<VideoTrackInterface> video_track =
    peer_connection_factory->CreateVideoTrack("camera_track", video_source.get());

**(2) 添加VideoSink渲染画面**

class VideoRenderer : public rtc::VideoSinkInterface<VideoFrame> {
public:
    void OnFrame(const VideoFrame& frame) override {
        // 渲染帧到屏幕
        RenderFrameToScreen(frame);
    }
};

// 将渲染器注册为VideoSink
VideoRenderer renderer;
video_track->AddOrUpdateSink(&renderer, rtc::VideoSinkWants());


// 移除VideoSink
video_track->RemoveSink(&renderer);

以上基础知识来自 腾讯元宝版的DeepSeek

至于第二个问题,不太好说明白。但基于从问题出发,我们还是看看MediaStream有什么内容。

/** Java wrapper for a C++ MediaStreamInterface. */
public class MediaStream {
  private static final String TAG = "MediaStream";

  public final List<AudioTrack> audioTracks = new ArrayList<>();
  public final List<VideoTrack> videoTracks = new ArrayList<>();
  public final List<VideoTrack> preservedVideoTracks = new ArrayList<>();
  private long nativeStream;

  @CalledByNative
  public MediaStream(long nativeStream) {
    this.nativeStream = nativeStream;
  }
}

代码很简单,就是三个Track列表,看到一句关键的注释 Java wrapper for a C++ MediaStreamInterface. 我们不妨再去看看MediaStreamInterface。

// C++ version of https://www.w3.org/TR/mediacapture-streams/#mediastream.
//
// A major difference is that remote audio/video tracks (received by a
// PeerConnection/RtpReceiver) are not synchronized simply by adding them to
// the same stream; a session description with the correct "a=msid" attributes
// must be pushed down.
//
// Thus, this interface acts as simply a container for tracks.
class MediaStreamInterface : public webrtc::RefCountInterface,
                             public NotifierInterface {
    ... ...
}

注释翻译:https://www.w3.org/TR/mediacapture-streams/#mediastream.的C++版本实现。一个主要区别是,远程音频/视频轨道(由PeerConnection的RtpReceiver接收)不是简单地通过将它们添加到同一流中来同步的;必须向下推送具有正确“a=msid”属性的会话描述。因此,此接口仅充当轨道的容器。

大致意思应该是,MediaStreamInterface这个类只是一个简单的包装器,把同一(msid)会话的音频视频轨包装在一起。

我们再深挖一下MediaStream的引用地方,也就是pc/peer_connection.cc

看到这就很关键的 RTC_CHECK( ! IsUnifiedPlan()),原来这个MediaStream是旧标准的接口,这下就好理解了。

再提 PlanB and UnifiedPlan

在前一篇文章中,我们简单提过《PlanB and UnifiedPlan》 其核心差异体现在媒体流(Track)的表示方式、m-line(媒体行)数量、SSRC关联逻辑等方面。

PlanB 和 UnifiedPlan 其实就是 WebRTC 在多路媒体源(multi media source)场景下的两种不同的 SDP 协商方式。如果引入 Stream 和 Track 的概念,那么一个 Stream 可能包含 AudioTrack 和 VideoTrack,当有多路 Stream 时,就会有更多的 Track,如果每一个 Track 唯一对应一个自己的 M 描述,那么这就是 UnifiedPlan,如果每一个 M line 描述了多个 Track(track id),那么这就是 Plan B。

Plan B的SDP片段:同一行 m-line下两个SSRC流都用着VP8编码参数。

m=video 9 UDP/TLS/RTP/SAVPF 96 97  
a=ssrc:1234 cname:stream1  
a=ssrc:5678 cname:stream2  
a=sendrecv
a=rtpmap:96 VP8/90000  
a=fmtp:96 max-fs=12288;max-fr=60  

Unified Plan的SDP片段:每个m-line独立配置编码格式,通过mid标识不同Track。

m=video 9 UDP/TLS/RTP/SAVPF 96
a=sendonly  
a=mid:video1  
a=rtpmap:96 VP8/90000  

m=video 9 UDP/TLS/RTP/SAVPF 97  
a=recvonly
a=mid:video2  
a=rtpmap:97 H264/90000  

Note: 当只有一路音频流和一路视频流时,Plan B 和 UnifiedPlan 的格式是相互兼容的。

Note: 如何快速判断is_unified_plan_?直接看m=video/m=audio的行数吧。

 借用大佬的两张图直观分析。

sdp - planb

sdp - unified plan

相关文章:

  • QML指示控件:ScrollBar与ScrollIndicator
  • 【江协科技STM32】Unix时间戳(学习笔记)
  • java 设置操作系统编码、jvm平台编码和日志文件编码都为UTF-8的操作方式
  • AI Agent开发大全第八课-Stable Diffusion 3的本地安装全步骤
  • FreeRTOS学习(九):中断管理
  • Android Compose框架的值动画(animateTo、animateDpAsState)(二十二)
  • 【MySQL】~/.my.cnf文件
  • 深入探讨MySQL数据库备份与恢复:策略与实践
  • EasyUI数据表格中嵌入下拉框
  • 【c++】【STL】unordered_set 底层实现总结
  • Spring Boot整合SSE实现消息推送:跨域问题解决与前后端联调实战
  • Siri接入DeepSeek快捷指令
  • matlab 模拟 闪烁体探测器全能峰
  • 计算机复试面试
  • 【软考网工-理论篇】第六章 网络安全
  • 工业物联网的范式革命:从“云边“ 到“边边” 协的技术跃迁
  • npm打包时出现ENOTFOUND registry.nlark.com
  • 【XPipe】一款好用的SSH工具
  • linux常用指令(6)
  • C# 打印模板设计-ACTIVEX打印控件-多模板加载
  • 我的科学观|张峥:AI快速迭代,我们更需学会如何与科技共处
  • 下任美联储主席热门人选沃什:美联储犯下“系统性错误”,未能控制一代人以来最严重的通胀
  • 五一假期“热潮”来袭,计划南下的小伙伴注意了
  • 应勇:以法治力量服务黄河流域生态保护和高质量发展
  • 最高法知识产权法庭:6年来新收涉外案件年均增长23.2%
  • 摩根士丹利基金雷志勇:AI带来的产业演进仍在继续,看好三大景气领域