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

【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

http://www.kler.cn/a/599414.html

相关文章:

  • 100天精通Python(爬虫篇)——第122天:基于selenium接管已启动的浏览器(反反爬策略)
  • python如何创建虚拟环境
  • 科技赋能,高端气膜料仓重塑储存新标准—轻空间
  • 计算机二级:基础操作题
  • CDN基本原理剖析与代码实现测试
  • CSS3:深度解析与实战应用
  • SEO监控看板搭建:基于Data Studio的实时数据可视化
  • 数据库锁机制
  • 【uni-app】tabBar使用
  • 预测蓝桥杯16届嵌入式省赛客观题
  • xLua_003 Lua访问C#
  • 【前端】 el-form-item的label由于字数多自行换行调整
  • LeetCode hot 100 每日一题(15)——48.旋转图像
  • 分布式环境下的重复请求防护:非Redis锁替代方案全解析
  • 数据不外传!通过内网穿透实现绿联NAS远程访问的安全配置方案
  • iPaaS集成平台:企业数字化转型的加速器
  • VUE2导出el-table数据为excel并且按字段分多个sheet
  • 大模型技术在商品归一和商品预测中的应用
  • Shiro框架漏洞攻略
  • AI 时代,我们需要什么样的数据库?