【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;@CalledByNativepublic 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.rhkb.cn/news/39643.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows安装Rust环境(详细教程)

一、 安装mingw64(C语言环境) Rust默认使用的C语言依赖Visual Studio&#xff0c;但该工具占用空间大安装也较为麻烦&#xff0c;可以选用轻便的mingw64包。 1.1 安装地址 (1) 下载地址1-GitHub&#xff1a;Releases niXman/mingw-builds-binaries GitHub (2) 下载地址2-W…

英伟达黄仁勋谈人工智能趋势,首提代理式AI,后续机器人将登场

近日&#xff0c;英伟达 GTC 2025 大会主题演讲中&#xff0c;英伟达 CEO 黄仁勋再次身穿皮衣登场。黄仁勋一上来就提到了 AI 发展的未来&#xff0c;现在我们处于生成式 AI&#xff08;Generative AI&#xff09;阶段&#xff0c;但根据黄仁勋的路线图&#xff0c;我们将迈向一…

LCR 187. 破冰游戏(python3解法)

难度&#xff1a;简单 社团共有 num 位成员参与破冰游戏&#xff0c;编号为 0 ~ num-1。成员们按照编号顺序围绕圆桌而坐。社长抽取一个数字 target&#xff0c;从 0 号成员起开始计数&#xff0c;排在第 target 位的成员离开圆桌&#xff0c;且成员离开后从下一个成员开始计数…

水星(MERCURY)监控初始化的恢复和转码方法

水星(MERCURY)的安防监控恢复了很多&#xff0c;其嵌入式文件系统也一直迭代更新。做为数据恢复从业者每天处理最多的就是恢复数据&#xff0c;但是有的时候业务的需要我们不仅仅恢复出数据&#xff0c;还需要能够转码成通用的MP4类文件并要求画面和声音实现“同步”。 故障存…

基于SpringBoot的实现的客户关系管理系统(CRM)(源码+数据库)

464客户关系管理系统&#xff08;CRM&#xff09;&#xff0c;主要功能如下 【后台功能】 权限管理模块: 包括系统的登录与注册功能 用户管理模块: 基于RBAC的权限模型设计, 实现分配角色的功能功能 客户管理模块: 对客户信息进行新增 修改 删除 查看 联络信息管理模块: 对联络…

关于网络的一点知识(持续更新)

1、IP地址和子网掩码、端口号: IP地址是设备在网络上的地址,相当于一栋房子的门牌号。子网掩码相当于房子所在的街道。同一条街道的房子间是通过街道直通的,主人可以互相拜访。 举个例子,如下图所示。 说明:将两台设备的IP和子网掩码转化为二进制,然后将各自的IP地址和…

Python---数据分析(Pandas八:二维数组DataFrame数据操作一: 数据清洗,数据转换)

一、 数据清洗 1.1、 isnull() 用于检测 DataFrame 中的缺失值&#xff0c;它会返回一个相同形状的布尔型 DataFrame&#xff0c;其中每个元素表示原始 DataFrame 中相应位置的元素是否是缺失 值。 import pandas as pd import numpy as np# 创建一个包含缺失值的 DataFrame …

智能汽车图像及视频处理方案,支持视频星轨拍摄能力

美摄科技作为智能汽车图像及视频处理领域的先行者&#xff0c;正以革新性的技术引领着行业的未来发展。美摄科技智能汽车图像及视频处理方案&#xff0c;一个集高效性、智能化、画质增强于一体的创新解决方案&#xff0c;旨在重塑智能汽车图像画质的新标准&#xff0c;并支持前…

Flask接口开发--GET接口

安装Flask 1.安装命令&#xff1a; pip3 install Flask2.查看Flask版本&#xff1a; pip3 show flask如图我的Flask版本号是2.0.3 项目创建 1、在PyCharm中&#xff0c;我们点击左上方的 file&#xff0c;选择 New Project&#xff0c;创建一个Flask项目。&#xff08;Py…

应用权限组列表

文章目录 使用须知位置相机麦克风通讯录日历运动数据身体传感器图片和视频音乐和音频跨应用关联设备发现和连接剪切板文件夹文件(deprecated) 使用须知 在申请目标权限前&#xff0c;建议开发者先阅读应用权限管控概述-权限组和子权限&#xff0c;了解相关概念&#xff0c;再合…

Python为Word文档添加书签并打包成exe

背景简述 由于一些工作场景&#xff0c;需要从多个Word文档中找到出现的关键词&#xff0c;并阅读关键词的上下文内容。文件可能几十个&#xff0c;手动操作太要命了。所以python尝试处理。 目录 背景简述思路第一步、功能实现结果验证 第二步、打包成exe2-1、基础准备2-2、打…

【MYSQL】索引和事务

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 本期内容讲解 MySQL 中的索引和事务&#xff0c;在学习的过程中&#xff0c;我们需要经常问自己为什么 文章目录 1. 索…

2021年蓝桥杯第十二届CC++大学B组真题及代码

目录 1A&#xff1a;空间&#xff08;填空5分_单位转换&#xff09; 2B&#xff1a;卡片&#xff08;填空5分_模拟&#xff09; 3C&#xff1a;直线&#xff08;填空10分_数学排序&#xff09; 4D&#xff1a;货物摆放&#xff08;填空10分_质因数&#xff09; 5E&#xf…

PicGo安装与配置-Gitee图床

1、 前言 平时使用Typora写文章,上传文章到第三方平台上去都要把图片一个一个上传上去,于是我就百度了有没有什么方法可以省略这一步骤,我发现Typora可以用PicGo+Gitee图床方式,这个挺容易的,我把安装的过程在此记录下来。 PicGo是一个用于快速上传图片并获取图片 URL 链…

html css js网页制作成品——HTML+CSS+js迪奥口红网站网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

重学Java基础篇—如何优雅的删除HashMap元素

在Java中优雅地删除HashMap元素需要注意遍历时的安全性和代码的简洁性。 以下是几种推荐的方法&#xff1a; 1. 使用迭代器遍历并删除&#xff08;传统方式&#xff09; 在遍历时通过迭代器的remove() 方法删除元素&#xff0c;避免ConcurrentModificationException异常。 H…

26考研——图_图的遍历(6)

408答疑 文章目录 三、图的遍历图的遍历概述图的遍历算法的重要性图的遍历与树的遍历的区别图的遍历过程中的注意事项避免重复访问遍历算法的分类遍历结果的不唯一性 广度优先搜索广度优先搜索&#xff08;BFS&#xff09;概述BFS 的特点广度优先遍历的过程示例图遍历过程 BFS …

2025-03-24 学习记录--C/C++-PTA 习题7-6 统计大写辅音字母

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 习题7-6 统计大写辅音字母 英文辅音字母是除A、E、I、O、U以外的字母。本题要求编写程序&#xff0c;统计给…

在vitepress中使用vue组建,然后引入到markdown

在 VitePress 中&#xff0c;每个 Markdown 文件都被编译成 HTML&#xff0c;而且将其作为 Vue 单文件组件处理。这意味着可以在 Markdown 中使用任何 Vue 功能&#xff0c;包括动态模板、使用 Vue 组件或通过添加 <script> 标签为页面的 Vue 组件添加逻辑。 值得注意的…

常见中间件漏洞之一 ----【Tomcat】

中间件Tomcat介绍&#xff1a; tomcat是⼀个开源⽽且免费的jsp服务器&#xff0c;默认端⼝ : 8080&#xff0c;属于轻量级应⽤服务器。它可以实现 JavaWeb程序的装载&#xff0c;是配置JSP&#xff08;Java Server Page&#xff09;和JAVA系统必备的⼀款环境。 在历史上也披露…