WebRTC音频 04 - 关键类
WebRTC音频01 - 设备管理
WebRTC音频 02 - Windows平台设备管理
WebRTC音频 03 - 实时通信框架
WebRTC音频 04 - 关键类(本文)
一、前言:
在WebRTC音频代码阅读过程中,我们发现有很多关键的类比较抽象,搞不清楚会导致代码阅读一脸懵逼。比如PeerConnection、Call、AudioState、Channel、Stream,本文就尽力介绍下。
二、关键类关系图:
- 一个PeerConnection拥有一个Call,来管理和对端的会话;可以看出Call里面主要负责创建一些收发的Stream;
- 但是PeerConnectionFactory是全局唯一的;
- 同时CompositeMediaEngine属于PeerConnectonFactory,因此,MediaEngine也是唯一的;
- 因此WebRtcVoiceEngine也是唯一的,里面的AudioState也是全局唯一,来处理众多PeerConnection中的Call过来的收发流;
三、PeerConnection:
1、职责:
可以理解成一个Socket Plus,比如我们有一个Mesh架构,里面有三个终端C1\C2\C3,他们互相之间都可以P2P通信。当同时加入一个会议中,那么,C1需要创建两个PeerConnection,一个负责和C2通信,另外一个负责和C3通信;
由于PeerConnection位于核心层最上面,需要和session层的API进行交互,因此,它首先得实现 PeerConnectionInterface API ;
PeerConnection单独负责的有:
-
管理会话状态机(信令状态)。
-
创建和初始化较低级别的对象,如 PortAllocator 和 BaseChannels;
-
拥有和管理 RtpSender/RtpReceiver 和跟踪对象的生命周期;
-
跟踪当前和待处理的本地/远程会话描述;
共同负责的有:
-
解析和解释 SDP;
-
根据当前状态生成Offer和Answer;
-
ICE 状态机;
-
成统计数据;
2、创建时机:
开始呼叫的时候会要做最主要的三件事(可以参考Conductor::InitializePeerConnection):
-
CreatePeerConnectionFactory(创建PeerConnection工厂对象);
-
CreatePeerConnection(就创建Peerconntion了);
-
最后AddTracks(添加Track到PeerConnection中,track后面会介绍,理解成一条音频或者视频流即可);
上面这些步骤在介绍音频架构的文章中都有出现。可以看看代码:
bool Conductor::InitializePeerConnection() {// 这函数前四个参数如果你想使用自己定义的线程函数,那么就传入,否则就使用的是webrtc内部的默认函数peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(nullptr /* network_thread */, nullptr /* worker_thread */,nullptr /* signaling_thread */, nullptr /* default_adm */,webrtc::CreateBuiltinAudioEncoderFactory(),webrtc::CreateBuiltinAudioDecoderFactory(),webrtc::CreateBuiltinVideoEncoderFactory(),webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,nullptr /* audio_processing */);// 创建PeerConnection对象if (!CreatePeerConnection(/*dtls=*/true)) {main_wnd_->MessageBox("Error", "CreatePeerConnection failed", true);DeletePeerConnection();}// 添加track到PeerConnection中AddTracks();return peer_connection_ != nullptr; }
上面的CreatePeerConnection就会创建出来PeerConnection;
四、Call:
1、职责:
Call代表着和某一个终端的会话,管理者通话的整体流程和状态;一个Call对象可以包含多个发送/接收流,且这些流对应同一个远端端点,并共享码率估计。具体职责如下:
在WebRtc内部职责:
- 创建/销毁 AudioReceiveStream、AudioSendStream;
- 创建/销毁 VideoSendStream、VideoReceiveStream;
开放给上层应用的功能:(通过PeerConnection开放)
- 发送码率设置(包含最大码率、最小码率、初始码率,初始码率作为编码器的初始参数以及带宽估计的先验值);
- 提供获取传输统计数据途径(包含估算的可用发送带宽、估算的可用接收带宽、平滑发送引入的延迟、RTT估计值、累计的最大填充bit);
- 提供获取所有发送的数据包回调;
- 另外其还持有PacketReceiver对象,因此,所有接收到RTP/RTCP数据包,也将经过Call。
2、创建时机:
Call的创建还是和大多数WebRtc模块一块,通过工厂模式创建,先创建工厂,再创建自己;
Factory创建:
// 文件路径:.\api\create_peerconnection_factory.cc
rtc::scoped_refptr<PeerConnectionFactoryInterface> CreatePeerConnectionFactory(//...) {// 具体参数都打包,为了方便向下传递PeerConnectionFactoryDependencies dependencies;dependencies.call_factory = CreateCallFactory(); // 创建了CallFactory// ...
}
看到创建了call_factory并放入dependencies,继续往下传给后续流程。
创建Call对象:
在CreatePeerConnection -> CreatePeerConnectionOrError 处创建
RTCErrorOr<rtc::scoped_refptr<PeerConnectionInterface>>
PeerConnectionFactory::CreatePeerConnectionOrError(const PeerConnectionInterface::RTCConfiguration& configuration,PeerConnectionDependencies dependencies) {// ... std::unique_ptr<Call> call = worker_thread()->Invoke<std::unique_ptr<Call>>(RTC_FROM_HERE,[this, &event_log] { return CreateCall_w(event_log.get()); }); // 创建了Call// 创建PeerConnectionauto result = PeerConnection::Create(context_, options_, std::move(event_log),std::move(call), configuration,std::move(dependencies));return result_proxy;
}
可以看出在工作线程通过调用CreateCall_w创建了一个call对象,并在PeerConnection创建的时候传给PeerConnection,这样,以后PeerConnecton就持有了Call;
看看CreateCall_w:
// 文件路径:pc\peer_connection_factory.cc
std::unique_ptr<Call> PeerConnectionFactory::CreateCall_w(RtcEventLog* event_log) {// 前面主要是设置一些收发流相关的参数,省略.// 调用工厂类创建具体Call对象return std::unique_ptr<Call>(context_->call_factory()->CreateCall(call_config));
}
然后就是调用Factory创建Call:
// 文件路径:call\call_factory.cc
Call* CallFactory::CreateCall(const Call::Config& config) {RTC_DCHECK_RUN_ON(&call_thread_);absl::optional<webrtc::BuiltInNetworkBehaviorConfig> send_degradation_config =ParseDegradationConfig(true);absl::optional<webrtc::BuiltInNetworkBehaviorConfig>receive_degradation_config = ParseDegradationConfig(false);if (send_degradation_config || receive_degradation_config) {return new DegradedCall(std::unique_ptr<Call>(Call::Create(config)),send_degradation_config, receive_degradation_config,config.task_queue_factory);}if (!module_thread_) {module_thread_ = SharedModuleThread::Create(ProcessThread::Create("SharedModThread"), [this]() {RTC_DCHECK_RUN_ON(&call_thread_);module_thread_ = nullptr;});}// 调用Call的静态Create方法创建return Call::Create(config, module_thread_);
}
这样Call对象就被创建出来了。
五、AudioState:
1、职责:
前面的WebRtcVoiceEngine里面有个AudioState成员变量,这个变量非常重要,主要负责管理AudioTranport模块(里面又有AudioMixer和AudioProcessing)和Adm模块,AudioState有两个,Call模块有一个,引擎模块AudioEngine里面也有一个,Call模块里面的主要定义一些接口,并且创建AudioEngine里面的Call。
小结一下:
- AudioState并不是管理音频状态,实际可以理解成一个音频上下文;
- AudioState主要管理两个模块Adm和AudioTransport;
- 其中adm主要管理音频硬件的;
- 其中AudioTransport主要通过Mixer和Processing模块来进行混音和3A处理;
2、定义:
// 文件路径:audio\audio_state.h
class AudioState : public webrtc::AudioState {public:// ...AudioProcessing* audio_processing() override;AudioTransport* audio_transport() override;void SetPlayout(bool enabled) override;void SetRecording(bool enabled) override;void SetStereoChannelSwapping(bool enable) override;AudioDeviceModule* audio_device_module() {RTC_DCHECK(config_.audio_device_module);return config_.audio_device_module.get();}void AddReceivingStream(webrtc::AudioReceiveStream* stream);void RemoveReceivingStream(webrtc::AudioReceiveStream* stream);void AddSendingStream(webrtc::AudioSendStream* stream,int sample_rate_hz,size_t num_channels);void RemoveSendingStream(webrtc::AudioSendStream* stream);private:// Transports mixed audio from the mixer to the audio device and// recorded audio to the sending streams.AudioTransportImpl audio_transport_;// Null audio poller is used to continue polling the audio streams if audio// playout is disabled so that audio processing still happens and the audio// stats are still updated.std::unique_ptr<NullAudioPoller> null_audio_poller_;std::unordered_set<webrtc::AudioReceiveStream*> receiving_streams_;struct StreamProperties {int sample_rate_hz = 0;size_t num_channels = 0;};std::map<webrtc::AudioSendStream*, StreamProperties> sending_streams_;
};
看得出,主要是对Stream做一些创建、删除等操作;
-
AddReceiveingStream:将要处理的音频接收流添加到AudioState中;
-
AddSendingStream:将要处理的音频发送流添加到AudioState中;
-
AudioTransportImpl:
- RecordedDataIsAvailable:拿到录制后的数据;
- NeedMorePlayData:向扬声器喂更多的数据;
-
发现WebRtcVoiceEngine持有AudioState,并且还持有ADM、Mixer、AudioProcessing(主要进行3A处理),但是,这哥仨的状态维护是AudioState来完成的(WebRtcVoiceEngine是这哥仨它爹,负责生了这仨,AudioState是这哥仨部门经理,负责管理派活);
-
AudioState持有AudioDeviceModule,就说明AudioState是底层硬件设备和上层应用之间的桥梁,上层应用想控制底层设备的采集与播放,必须通过AudioState;AudioState再控制AudioDeviceModule对硬件进行操作;
3、创建时机:
调用栈如下:
WebRtcVoiceEngine::Init()
CompositeMediaEngine::Init()
ChannelManager::Init()
ConnectionContext::Create()
PeerConnectionFactory::Create()
CreateModularPeerConnectionFactory()
CreatePeerConnectionFactory()
也就是说在音频引擎的初始化函数里面创建的,看看源代码(非关键代码已经删除)
// 文件路径:media\engine\webrtc_voice_engine.cc
void WebRtcVoiceEngine::Init() {// Set up AudioState.{webrtc::AudioState::Config config;if (audio_mixer_) {config.audio_mixer = audio_mixer_;} else {config.audio_mixer = webrtc::AudioMixerImpl::Create();}config.audio_processing = apm_;config.audio_device_module = adm_;if (audio_frame_processor_)config.async_audio_processing_factory =new rtc::RefCountedObject<webrtc::AsyncAudioProcessing::Factory>(*audio_frame_processor_, *task_queue_factory_);audio_state_ = webrtc::AudioState::Create(config);}
}
// 文件路径:audio\audio_state.cc
rtc::scoped_refptr<AudioState> AudioState::Create(const AudioState::Config& config) {// 创建一个使用引用计数管理的AudioState对象return new rtc::RefCountedObject<internal::AudioState>(config);
}
备注:AudioState创建的对象使用智能指针管理的,不明白WebRTC智能指针的,可以看看我的另外一篇文章:WebRTC基本类 - 智能指针(RefCountedObject和scoped_refptr)-CSDN博客
4、Call和AudioState的关系:
前面介绍了Call代表一个通话,管理整个通话的流程和状态。里面提供了Audio/Video的Send/Receive相关的Stream。但是刚才看到AudioState里面也有一些Add/Remove Stream相关操作,他俩之间什么关系呢?其实Call主要是建立连接、与远端通信、处理媒体数据流、处理通话状态等。但是自己并不能操作到硬件,而AudioState又持有adm指针(config_.audio_device_module),因此Call需要调用AudioState的方法来完成具体的音频数据的采集、处理、播放等动作。
六、Stream和Channel:
前面分析媒体协商的时候我说过,WebRtc含有很多种Channel、Stream,非常混乱,搞不清楚会导致阅读代码n脸懵逼。因此,我们必须要弄懂它:
1、API层:
我们从API层(Web API和Native API都一样)看到的有Stream和Track两个概念,其中Track就表示一条媒体源,比如音视频会议中,一路音频是一个auidioTrack,一路视频又是一个videoTrack。而Stream就是将音频Track和视频Track打包起来,作为一路流(MediaStream)。
2、媒体引擎层:
引擎层又有Stream和Channel两个概念,比如WebRtcVoiceMediaChannel和WebRtcAudioSendStream。由于引擎层一个Channel其实就代表的和一个编解码器的连接,因此,需要分类管理,Audio和Audio放一起,Video和Video放一起。而这个Channel中包含的音频、视频它又叫做stream,比如AudioSendStream、AudioReceiveStream打包起来叫做VoiceMediaChannel。
基本相当于引擎层的Channel对应API层的Track。
3、Call层:
Call层作为PeerConnecton的得力干将,主要负责将这个业务给拉起来。它里面也有Stream和Channel的概念,和引擎层相反,是Stream里面包含Channel(我猜这么设计的原因是,Call层偏重于业务的概念,我一条业务流只能包含一个方向的一种媒体流),这又要分为音频和视频了:
- 音频:
- Stream是有方向的,要么是Send,要么是Receive方向;
- Stream里面包含Channel,Channel也是有方向的;
- 视频:
- 只有Stream,没有Channel的概念,并且Stream也是有方向的。要么是Send,要么是Receive;
4、小结:
引擎层是将audio和video分类管理,一个Channel中包含多个stream,可以是send也可以是recv。Call层的音频Stream中有Channel,但是,视频的Stream中并没有Channel。
关注公众号,和你分享优质资源:
相关文章:

WebRTC音频 04 - 关键类
WebRTC音频01 - 设备管理 WebRTC音频 02 - Windows平台设备管理 WebRTC音频 03 - 实时通信框架 WebRTC音频 04 - 关键类(本文) 一、前言: 在WebRTC音频代码阅读过程中,我们发现有很多关键的类比较抽象,搞不清楚会导致代码阅读一脸懵逼。比如…...

Elasticsearch:Redact(编辑) processor
Redact 处理器使用 Grok 规则引擎来隐藏输入文档中与给定 Grok 模式匹配的文本。该处理器可用于隐藏个人身份信息 (Personal Identifying Information - PII),方法是将其配置为检测已知模式,例如电子邮件或 IP 地址。与 Grok 模式匹配的文本将被替换为可…...
O2OA结合备份脚本和定时任务进行数据库的备份,我们以MySQL数据库为例
概述 系统运行一段时间后,可能发生各种情况导致数据丢失,如硬件故障、人为错误、软件错误、病毒攻击等。定期备份可以帮助您保护数据免受这些风险的影响,以便在需要时能够恢复数据。 O2OA应用本身可以通过dump配置每天自定备份数据ÿ…...
Python自动化办公:批量提取PDF中的表格到Excel
在现代办公环境中,处理大量的PDF文件并提取其中的表格数据是一项常见而繁琐的任务。手动复制粘贴不仅耗时耗力,还容易出错。Python作为一种功能强大的编程语言,提供了丰富的工具包,可以高效地解决这一问题。本文将介绍如何使用Pyt…...

selenium有多个frame页时的操作方法(5)
之前文章我们提到,在webdriver.WebDriver类有一个switch_to方法,通过switch_to.frame()可以切换到不同的frame页然后才再定位某个元素做一些输入/点击等操作。 比如下面这个测试网站有2个frame页:http://www.sahitest.com/demo/framesTest.h…...

谷歌外链的周期性维护!
外链建设不是一次性的工作,它需要长期的维护和更新,才能持续为网站带来稳定的流量和SEO提升。很多网站在初期通过短期内大规模获取外链的方式,确实能看到排名的提升,但这种方法往往难以维持长期的效果。谷歌更喜欢自然、持续增长的…...
CATIA软件许可管理最佳实践
在当今的工程设计领域,CATIA软件已成为众多企业不可或缺的工具。然而,随着软件使用的广泛普及,许可管理变得尤为关键。本文将为您探讨CATIA软件许可管理的最佳实践,助您在确保合规性的同时,实现成本效益的最大化。 一、…...

大华智能云网关注册管理平台 SQL注入漏洞复现(CNVD-2024-38747)
0x01 产品简介 大华智能云网关注册管理平台是一款专为解决社会面视频资源接入问题而设计的高效、便捷的管理工具,平台凭借其高效接入、灵活部署、安全保障、兼容性和便捷管理等特点,成为了解决社会面视频资源接入问题的优选方案。该平台不仅提高了联网效率,降低了联网成本,…...

什么是思维导图,手把手教你做经典思维导图
在信息爆炸的时代,如何高效整理思绪、激发创意、提升学习效率成为了我们共同面临的挑战。思维导图,这一源自脑科学的思维工具,以其直观、灵活的特点,成为了众多学习者、管理者和创意人士的得力助手。今天,就让我们一起…...

使用GSEA读‘gmt文件‘时最后一行未遂问题解决
最近工作中,使用GSEA网站自定义库下载的gmt文件用函数读取的时候报错: 这种问题在文本文件读取中经常出现,往往因为最后一行未留出/n,也就是最后一行没有换行留出空行。 可以使用notepad打开gmt文件: 发现果然最后一行…...

C++中vector常用函数总结
一,vector vector可以理解为一个边长数组,可以存储不同的类型,int ,double,char,结构体等。 也可以才能出STL标准容器,如set,string,vector等 二,构造函数 vector(size_t n,T val) …...

手撕数据结构 —— 队列(C语言讲解)
目录 1.什么是队列 2.如何实现队列 3.队列的实现 Queue.h中接口总览 具体实现 结构的定义 初始化 销毁 入队列 出队列 取队头元素 取队尾元素 判断是否为空 获取队列的大小 4.完整代码附录 Queue.h Queue.c 1.什么是队列 队列是一种特殊的线性表࿰…...

Java知识巩固(五)
目录 基本数据类型 基本类型和包装类型的区别? 自动装箱与拆箱了解吗?原理是什么? 为什么浮点数运算的时候回邮精度丢失的风险? 如何解决浮点数运算的精度丢失问题? 超过 long 整型的数据应该如何表示? 基本数据…...
C# 中 yield关键字的使用
yield return有以下优点: 每次迭代时生成一个值,并且在下次迭代时继续从上次离开的地方开始。 延迟执行:只有在实际需要时才会生成下一个值,这对于处理大量数据非常有用。 节省内存:不需要一次性将所有数据加载到内存中…...
YoloDotNet 的基本使用方法详解
文章目录 一、创建项目与引用库二、模型加载与初始化三、图像数据的处理与输入四、目标检测结果的获取与解析五、性能优化与参数调整一、创建项目与引用库 在使用 YoloDotNet 之前,首先需要在开发环境中创建一个新的项目。可以选择使用 Visual Studio 等开发工具,创建一个 C#…...
0x12 Dapr Dashboard configurations 未授权访问漏洞 CVE-2022-38817
参考: Dapr Dashboard configurations 未授权访问漏洞 CVE-2022-38817 | PeiQi文库 (wgpsec.org)免责声明 欢迎访问我的博客。以下内容仅供教育和信息用途: 合法性:我不支持或鼓励非法活动。请确保遵守法律法规。信息准确性:尽管我尽力提供准确的信息,但不保证其完全准确…...
Android activity 启动流程
Android activity 启动流程 本文主要记录下acitivty的启动流程. 1: Activity 我们都知道启动activity调用方法: startActivity(Intent intent)startActivity(Intent intent, Nullable Bundle options)startActivityForResult(RequiresPermission Intent intent, int reques…...

使用 Go 语言实现 WebSocket的核心逻辑
文章目录 WebSocket 简介时序图核心逻辑Client 结构与功能创建新客户端消息读取逻辑 (ReadPump)发送消息逻辑 (Send)客户端管理器 (ClientManager)WebSocket 处理器处理心跳与长连接 总结 本文将基于 Go 语言,通过使用 gorilla/websocket 库来实现一个简单的聊天应用…...

Linux下的杀毒软件介绍
Linux下的杀毒软件介绍 一、Linux杀毒软件的基本概念和作用二、Linux杀毒软件的选择三、Linux杀毒软件推荐四、Linux杀毒软件对应用进程的影响五、结论在当今数字化和网络化的环境中,保护计算机系统的安全至关重要。尽管Linux操作系统因其开源、稳定且相对安全的特性而较少受到…...
JSONP详解
JSONP(JSON with Padding)是一种非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过JavaScript callback的形式实现跨域访问。以下是对JSONP的详细解释: 一、JSONP的背景与原理 背景: 由于浏…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...