WebRTC视频 02 - 视频采集类 VideoCaptureModule

WebRTC视频 01 - 视频采集整体架构
WebRTC视频 02 - 视频采集类 VideoCaptureModule(本文)
WebRTC视频 03 - 视频采集类 VideoCaptureDS 上篇
WebRTC视频 04 - 视频采集类 VideoCaptureDS 中篇
WebRTC视频 05 - 视频采集类 VideoCaptureDS 下篇

一、前言:

上一篇文章介绍了webrtc视频采集整体架构,分析了几个关键类的类关系,以及如何通过这几个类建立视频采集链路。主要在软件层面进行了分析,本节着重分析下如何进行设备层操作的。总体负责操作设备层的类叫做VideoCaptureModule(简称VCM),Windows平台负责具体操作硬件的组件是DirectShow。

二、DirectShow:

1、简介:

DirectShow是一种由微软开发的多媒体框架,主要用于Windows平台上处理和操控流媒体数据。该技术提供了一组API,支持音频和视频的捕获、处理、转换和播放。下面是对DirectShow的一些主要特性的介绍:

  1. 模块化结构
    • DirectShow基于过滤器(filter)的架构,每个过滤器执行特定的任务,如源读取、数据解析、编码、解码、渲染等。过滤器之间通过引脚(pin)连接,形成一个可定制的处理链。
  2. 多种格式支持
    • 支持多种媒体格式,包括AVI、MPEG、ASF、WAV、MP3等。这使得开发者可以构建能够处理多种媒体格式的应用程序。
  3. 实时流媒体处理
    • 能够处理实时数据流,非常适合用于视频会议、视频广播等需要实时处理的场景。
  4. 灵活性和可扩展性
    • 开发者可以创建自定义过滤器,以支持新的数据格式或实现新的处理算法。此外,还可以通过编程接口动态构建和操控过滤器图(filter graph)。
  5. 系统集成性
    • 作为Windows媒体框架的一部分,DirectShow与Windows操作系统和DirectX集成良好,能够利用硬件加速功能以提高性能。
  6. 应用领域
    • DirectShow被广泛应用于视频编辑软件、播放软件、流媒体服务以及各种需要音频视频处理的应用中。

尽管DirectShow功能强大,但随着Windows平台的发展,微软推出了更新的多媒体框架,如Windows Media Foundation,以提供更现代化的功能和更好的性能。在Windows 10及更高版本中,建议使用新的框架进行开发。

2、Filter

Filter 是DirectShow架构的基本单元,每个Filter执行特定的媒体数据处理功能。Filter可以分为以下几类:

  • 源过滤器(Source Filter):负责从文件、设备或网络读取媒体数据。
  • 变换过滤器(Transform Filter):用于对流媒体数据进行处理和转换,如编解码、特效处理等。
  • 渲染过滤器(Renderer Filter):负责将媒体数据输出到设备,如显示到屏幕或播放到音频设备。

每个Filter通常有一个或多个输入和输出连接点,称为Pins,用于连接其他过滤器。

3、Filter Graph

Filter Graph 是一组按特定顺序连接的Filters,它定义了媒体流的处理路线。Filter Graph是DirectShow的核心,处理步骤如下:

  • 定义要使用的Filters及其顺序。
  • 连接Filters之间的Pins以建立媒体流的传输路径。
  • 控制媒体流的运行状态,如开始、停止、暂停等。

Filter Graph的设计允许灵活地构建不同的媒体处理链条,以满足不同的应用需求。

4、Filter Graph Manager

Filter Graph Manager 是负责创建和管理Filter Graph的组件。它提供的主要功能包括:

  • 自动构建和连接Filters来形成一个完整的Graph。
  • 管理Graph的状态与控制,如启动、暂停、停止图中所有Filters。
  • 提供接口供应用程序访问和操作Filter Graph。

通过Filter Graph Manager,开发者可以更容易地管理Filter Graph的生命周期和处理流程。

5、Pin

Pin 是Filters之间的数据连接点,用于传递媒体数据流。Pin分为两类:

  • 输入Pin:接收来自上游过滤器的数据。
  • 输出Pin:发送数据到下游过滤器。

Pins之间的连接称为“pin connection”,数据在Filter Graph中通过Pins在不同的Filters之间传递。Pins还会协商数据格式和流类型,以确保兼容性。

6、工作过程

在DirectShow的工作过程中,首先创建Filter Graph,然后通过Filter Graph Manager将合适的Filters添加到Graph,并通过Pins连接这些Filters,形成一个完整的处理链。当播放或采集媒体时,媒体数据会按照预定义的过程通过各类Filters处理,最终输出到用户设定的目标设备或存储介质中。

这种模块化和灵活性使DirectShow成为构建复杂媒体应用的强大工具。

三、核心类图:

在这里插入图片描述

上面类图的中最核心的就是VideoCaptureModule和DeviceInfo这两个接口类,这两个接口类的子类对象在什么时候创建的呢?如果还记得上一篇文章的代码,就是在VcmCapturer::Init()中利用VideoCaptureFactory创建的。

  • DeviceInfo:主要存储采集设备的相关信息;比如,采集设备的数量,采集设备的能力(分辨率、帧率等),采集设备的方向;
  • 上面两个重要的类都是接口类,在Windows平台下,实现者分别是VideoCaptureDS和DeviceInfoDS(其中DS就是DirectShow的缩写);
  • VideoCaptureModule方法包括开始和停止采集,以及一个注册回调的接口,采集到数据之后,通过这个回调上传给上层。
  • VideoCaptureDS成员:
    • _dsInfo:就是DeviceInfoDS。
    • captureFilter:主要负责视频的采集;
    • graphBuilder:用于构造FilterGraph;
    • mediaControl:用于控制FilterGraph什么时候开启,什么时候停止;
    • sink_filter:使用captureFilter采集的数据,最终输出到Sink当中;

四、采集步骤:

在这里插入图片描述

具体函数在VcmCapturer::Init当中:

bool VcmCapturer::Init(size_t width,size_t height,size_t target_fps,size_t capture_device_index) {// 创建 DeviceInfo 对象std::unique_ptr<VideoCaptureModule::DeviceInfo> device_info(VideoCaptureFactory::CreateDeviceInfo());char device_name[256];char unique_name[256];if (device_info->GetDeviceName(static_cast<uint32_t>(capture_device_index),device_name, sizeof(device_name), unique_name,sizeof(unique_name)) != 0) {Destroy();return false;}// 创建 VideoCapture 对象vcm_ = webrtc::VideoCaptureFactory::Create(unique_name);if (!vcm_) {return false;}vcm_->RegisterCaptureDataCallback(this);// 获取摄像头Capabilitydevice_info->GetCapability(vcm_->CurrentDeviceName(), 0, capability_);capability_.width = static_cast<int32_t>(width);capability_.height = static_cast<int32_t>(height);capability_.maxFPS = static_cast<int32_t>(target_fps);capability_.videoType = VideoType::kI420;// 通知 VideoCapture 开始采集if (vcm_->StartCapture(capability_) != 0) {Destroy();return false;}RTC_CHECK(vcm_->CaptureStarted());return true;
}

注意创建DeviceInfo和VideoCapture的具体对象,都是通过VideoCaptureFactory完成的,等会儿会详细分析。

VcmCaptureer::Init方法主要做了六件事:

  • 创建并获取DeviceInfo;
  • 创建并获取VideoCaptureModule,也就是真正的视频采集模块;
  • 然后就将VcmCapturer自己注册到VideoCaptureModule中来接收采集的数据;
  • 再就是通过DeviceInfo::GetCapability获取设备的能力,分辨率是多少,帧率是多少,使用的视频类型是什么。获取到这个能力之后,在初始化函数会修改这个能力为用户想要的能力。
  • 然后通过StartCapture开始根据capability采集视频数据。
  • 最后,通过CaptureStarted检测设备状态是否为已经采集状态。

下面看看这个非常关键的VCM类做了什么。

五、VideoCaptureModule:

1、采集通道搭建:

从前面的类图可知VideoCaptureModule只是一个接口类,它的实现类为VideoCaptureImpl,屏蔽了平台差异,不同平台又有自己的实现,比如Linux为VideoCaptureModuleV4L2,Windows平台为VideoCaptureDS,我是在Windows平台运行的,所以重点关注VideoCaptureDS。

我们前面说了VideoCaptureModule对象是通过VideoCaptureFactory::创建的:

  vcm_ = webrtc::VideoCaptureFactory::Create(unique_name);

我们进去看看:

rtc::scoped_refptr<VideoCaptureModule> VideoCaptureFactory::Create(const char* deviceUniqueIdUTF8) {
#if defined(WEBRTC_ANDROID) || defined(WEBRTC_MAC)return nullptr;
#else// linux 和 windows 平台走这儿return videocapturemodule::VideoCaptureImpl::Create(deviceUniqueIdUTF8);
#endif
}

发现转手调用了VideoCaptureImpl里面的方法,再进去看看:

rtc::scoped_refptr<VideoCaptureModule> VideoCaptureImpl::Create(const char* device_id) {if (device_id == nullptr)return nullptr;// TODO(tommi): Use Media Foundation implementation for Vista and up.// 创建了VideoCaptureDS对象,并进行初始化rtc::scoped_refptr<VideoCaptureDS> capture(new rtc::RefCountedObject<VideoCaptureDS>());if (capture->Init(device_id) != 0) {return nullptr;}return capture;
}

发现里面直接new了一个VideoCaptureDS对象,并进行初始化。

然后分析下VideoCaptureDS::Init函数;这里面有很多是和前面介绍的DirectShow相关的,具体代码:

int32_t VideoCaptureDS::Init(const char* deviceUniqueIdUTF8) {const int32_t nameLength = (int32_t)strlen((char*)deviceUniqueIdUTF8);if (nameLength > kVideoCaptureUniqueNameLength)return -1;// Store the device name_deviceUniqueId = new (std::nothrow) char[nameLength + 1];memcpy(_deviceUniqueId, deviceUniqueIdUTF8, nameLength + 1);if (_dsInfo.Init() != 0)return -1;// 构造CaptureFilter_captureFilter = _dsInfo.GetDeviceFilter(deviceUniqueIdUTF8);if (!_captureFilter) {RTC_LOG(LS_INFO) << "Failed to create capture filter.";return -1;}// Get the interface for DirectShow's GraphBuilder// 创建FilterGraph,并返回IGraphBuilder接口HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void**)&_graphBuilder);if (FAILED(hr)) {RTC_LOG(LS_INFO) << "Failed to create graph builder.";return -1;}// 获取IMediaControl接口,用于控制数据的流转hr = _graphBuilder->QueryInterface(IID_IMediaControl, (void**)&_mediaControl);if (FAILED(hr)) {RTC_LOG(LS_INFO) << "Failed to create media control builder.";return -1;}// 将前面构造好的CaptureFilter添加到FilterGraph当中hr = _graphBuilder->AddFilter(_captureFilter, CAPTURE_FILTER_NAME);if (FAILED(hr)) {RTC_LOG(LS_INFO) << "Failed to add the capture device to the graph.";return -1;}// 获取CaptureFilter的输出Pin_outputCapturePin = GetOutputPin(_captureFilter, PIN_CATEGORY_CAPTURE);if (!_outputCapturePin) {RTC_LOG(LS_INFO) << "Failed to get output capture pin";return -1;}// Create the sink filte used for receiving Captured frames.// 开始构造CaptureSinkFiltersink_filter_ = new ComRefCount<CaptureSinkFilter>(this);// 将CaptureSinkFilter加入到GraphicBuilder当中hr = _graphBuilder->AddFilter(sink_filter_, SINK_FILTER_NAME);if (FAILED(hr)) {RTC_LOG(LS_INFO) << "Failed to add the send filter to the graph.";return -1;}// 获取SinkFilter的输入pin_inputSendPin = GetInputPin(sink_filter_);if (!_inputSendPin) {RTC_LOG(LS_INFO) << "Failed to get input send pin";return -1;}// Temporary connect here.// This is done so that no one else can use the capture device.// 将两个filter的pin连接起来if (SetCameraOutput(_requestedCapability) != 0) {return -1;}// 先暂停,因为此时还没有数据过来hr = _mediaControl->Pause();if (FAILED(hr)) {RTC_LOG(LS_INFO)<< "Failed to Pause the Capture device. Is it already occupied? " << hr;return -1;}RTC_LOG(LS_INFO) << "Capture device '" << deviceUniqueIdUTF8<< "' initialized.";return 0;
}

我虽然每一步都写了注释,还是小结下:

  • DeviceInfo::Init准备好设备需要的环境;
  • 获取用于采集视频数据的CaptureFilter;
  • 接下来就是创建一个IGraphBuilder,这个接口主要做了两件事:
    • 创建IMediaControl接口,这个主要是控制采集的,比如,开始采集,停止采集等;
    • AddFilter将之前获取到的CaptureFilter加入到FilterGraph当中,后面再给FilterGraph添加一个Sink,就可以获取CaptureFilter采集到的数据了;
  • GetOutputPin获取CaptureFilter的输出引脚;
  • 接下来就是通过CaptureSinkFilter创建SinkFilter了;
  • 然后通过IGraphBuilder::AddFilter将SinkFilter添加到GraphFilter当中;
  • 然后获取SinkFilter的输入引脚,通过GetInputPin;
  • 通过SetCameraOutput将CaptureFilter的输出引脚和SinkFilter的输入引脚进行连接;数据就可以源源不断的从capture->sink。代码中其实就是就VideoCaptureDS作为入参传给SinkFilter;这样就一层层传给上层。
  • 调用meidaControl->Pause暂停数据采集,因为这时候我们还不需要数据采集,什么时候开始呢?就是VcmCapture的CaptureStarted调用之后。

至此,数据通道就已经建立好了,通道启动之后,数据流向就是这样:CaptureFilter->SinkFilter->VideoCaptureDS->VcmCapture->上层应用。

2、开始采集:

就是VcmCapturer::Init最后一步 vcm_->StartCapture(capability_) 开始,启动采集了。

int32_t VideoCaptureDS::StartCapture(const VideoCaptureCapability& capability) {MutexLock lock(&api_lock_);// 如果用户请求的能力,和我们当前设备能力不同,需要重新连接下Filterif (capability != _requestedCapability) {DisconnectGraph();if (SetCameraOutput(capability) != 0) {return -1;}}// 通过IMediaControl接口通知底层开始采集HRESULT hr = _mediaControl->Run();if (FAILED(hr)) {RTC_LOG(LS_INFO) << "Failed to start the Capture device.";return -1;}return 0;
}

六、总结:

本文主要说明了应用层如何通过VideoCaptureModule去控制DirectShow进行视频采集的,主要是创建了FilterGraph,并往里添加了输入CaptureFilter、输出SinkFilter两个Filter,最后通过SetCameraOutput将CaptureFilter的输出pin和SinkFilter的输入pin连起来,并通过IMediaControl接口启动采集。

后续再写一篇分析下和Filter相关的这几个步骤里面都是怎么做的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/60911.shtml

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

相关文章

POI实现根据PPTX模板渲染PPT

目录 1、前言 2、了解pptx文件结构 3、POI组件 3.1、引入依赖 3.2、常见的类 3.3、实现原理 3.4、关键代码片段 3.4.1、获取ppt实例 3.4.2、获取每页幻灯片 3.4.3、循环遍历幻灯片处理 3.4.3.1、文本 3.4.3.2、饼图 3.4.3.3、柱状图 3.4.3.4、表格 3.4.3.5、本地…

sqli-labs靶场17-20关(每日四关)持续更新!!!

Less-17 打开靶场&#xff0c;发现页面比之前多了一行字 翻译过来就是&#xff0c;密码重置&#xff0c;大家肯定会想到&#xff0c;自己平时在日常生活中怎么密码重置&#xff0c;肯定是输入自己的用户名&#xff0c;输入旧密码&#xff0c;输入新密码就可以了&#xff0c;但…

node.js下载安装步骤整理

>> 进入node.js下载页面下载 | Node.js 中文网 >>点击 全部安装包 >>删除网址node后面部分&#xff0c;只保留如图所示部分&#xff0c;回车 >>点击进入v11.0.0/版本 >>点击下载node-v11.0.0-win-x64.zip(电脑是windows 64位操作系统适用) >…

ThinkServer SR658H V2服务器BMC做raid与装系统

目录 前提准备 一. 给磁盘做raid 二. 安装系统 前提准备 磁盘和系统BMC地址都已经准备好&#xff0c;可正常使用。 例&#xff1a; 设备BMC地址&#xff1a;10.99.240.196 一. 给磁盘做raid 要求&#xff1a; 1. 将两个894G的磁盘做成raid1 2. 将两块14902G的磁盘各自做…

SpringBoot配置类

在Spring Boot中&#xff0c;配置类是一种特殊的类&#xff0c;用于定义和配置Spring应用程序的各种组件、服务和属性。这些配置类通常使用Java注解来声明&#xff0c;并且可以通过Spring的依赖注入机制来管理和使用。 Spring 容器初始化时会加载被Component、Service、Reposi…

SpringBoot教程(二十五) | SpringBoot配置多个数据源

SpringBoot教程&#xff08;二十五&#xff09; | SpringBoot配置多个数据源 前言方式一&#xff1a;使用dynamic-datasource-spring-boot-starter引入maven依赖配置数据源动态切换数据源实战 方式二&#xff1a;使用AbstractRoutingDataSource1. 创建数据源枚举类2. 创建数据源…

ZooKeeper单机、集群模式搭建教程

单点配置 ZooKeeper在启动的时候&#xff0c;默认会读取/conf/zoo.cfg配置文件&#xff0c;该文件缺失会报错。因此&#xff0c;我们需要在将容器/conf/挂载出来&#xff0c;在制定的目录下&#xff0c;添加zoo.cfg文件。 zoo.cfg logback.xml 配置文件的信息可以从二进制包…

【大数据学习 | HBASE高级】hbase-phoenix 与二次索引应用

1. hbase-phoenix的应用 1.1 概述&#xff1a; 上面我们学会了hbase的操作和原理&#xff0c;以及外部集成的mr的计算方式&#xff0c;但是我们在使用hbase的时候&#xff0c;有的时候我们要直接操作hbase做部分数据的查询和插入&#xff0c;这种原生的方式操作在工作过程中还…

拆解测试显示Mac Mini (2024)固态硬盘并未锁定 互换硬盘后仍可使用

此前已经有维修达人尝试将 Mac Mini (2024) 固态硬盘上的 NAND 闪存拆下并替换实现扩容&#xff0c;例如可以从 256GB 扩容到 2TB。虽然接口类似于 NVMe M.2 SSD 但直接安装普通硬盘是无效的&#xff0c;苹果仍然通过某种机制检测硬盘是否能够兼容。 不过知名拆解网站 iFixit 的…

主界面获取个人信息客户端方

主界面获取个人信息客户端方 前言 上一集我们完成了websocket身份验证的内容&#xff0c;那么这一集开始我们将要配合MockServer来完成主界面获取个人信息的内容。 需求分析 我们这边是完成客户端那方的内容&#xff0c;当客户端登录成功之后&#xff0c;我们就要从服务器获…

Spring整合Redis

前言 在Spring项目中整合Redis&#xff0c;能显著提升数据缓存、分布式锁、会话管理等操作的效率。Jedis作为轻量级的Java Redis客户端&#xff0c;搭配Spring Data Redis模块&#xff0c;能够简化Redis的连接和数据操作&#xff0c;实现更高性能的读写与灵活的缓存管理。本文…

爬虫——Requests库的使用

在爬虫开发中&#xff0c;HTTP请求是与服务器进行交互的关键操作。通过发送HTTP请求&#xff0c;爬虫可以获取目标网页或接口的数据&#xff0c;而有效地处理请求和响应是爬虫能够高效且稳定运行的基础。Requests库作为Python中最常用的HTTP请求库&#xff0c;因其简洁、易用和…

LinkedHashMap实现LRU

LRU 环境&#xff1a;JDK11 最近接触LRU(Least Recently Used)&#xff0c;即最近最少使用&#xff0c;也称淘汰算法&#xff0c;在JDK中LinkedHashMap有相关实现 LRU的LinkedHashMap实现 LinkedHashMap继承HashMap。所以内存的存储结构和HashMap一样&#xff0c;但是LinkedH…

IDEA部署AI代写插件

前言 Hello大家好&#xff0c;当下是AI盛行的时代&#xff0c;好多好多东西在AI大模型的趋势下都变得非常的简单。 比如之前想画一幅风景画得先去采风&#xff0c;然后写实什么的&#xff0c;现在你只需描述出你想要的效果AI就能够根据你的描述在几分钟之内画出一幅你想要的风景…

27-压力测试

测试目标 & 测试数据 ● 测试目标 ○ 测试集群的读写性能 / 做集群容量规划 ○ 对 ES 配置参数进行修改&#xff0c;评估优化效果 ○ 修改 Mapping 和 Setting&#xff0c;对数据建模进行优化&#xff0c;并测试评估性能改进 ○ 测试 ES 新版本&#xff0c;结合实际场…

4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明

4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明 文章目录 4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明前言1. Ribbon 介绍1.1 LB(Load Balance 负载均衡) 2. Ribbon 原理2.2 Ribbon 机制 3. Spring Cloud Ribbon 实现负载均衡算法-应用实例4. 总结&#x…

vue3【实战】切换全屏【组件封装】FullScreen.vue

效果预览 原理解析 使用 vueUse 里的 useFullscreen() 实现 代码实现 技术方案 vue3 vite UnoCSS vueUse 组件封装 src/components/FullScreen.vue <template><component:is"tag"click"toggle":class"[!isFullscreen ? i-ep:full-sc…

docker:基于Dockerfile镜像制作完整案例

目录 摘要目录结构介绍起始目录package目录target目录sh目录init.sh脚本start.sh脚本stop.sh脚本restart.sh脚本 config目录 步骤1、编写dockerfilescript.sh脚本 2、构件镜像查看镜像 3、保存镜像到本地服务器4、复制镜像文件到指定目录&#xff0c;并执行init.sh脚本5、查看挂…

微澜:用 OceanBase 搭建基于知识图谱的实时资讯流的应用实践

本文作者&#xff1a; 北京深鉴智源科技有限公司架构师 郑荣凯 本文整理自北京深鉴智源科技有限公司架构师郑荣凯&#xff0c;在《深入浅出 OceanBase 第四期》的分享。 知识图谱是一项综合性的系统工程&#xff0c;需要在在各种应用场景中向用户展示经过分页的一度关系。 微…

消息中间件分类

消息中间件&#xff08;Message Middleware&#xff09;是一种在分布式系统中实现跨平台、跨应用通信的软件架构。它基于消息传递机制&#xff0c;允许不同系统、不同编程语言的应用之间进行异步通信。 常见的消息中间件类型包括&#xff1a; 1. JMS&#xff08;Java Message S…