WebRTC视频 02 - 视频采集类 VideoCaptureModule

news2024/11/18 23:25:13

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);
  // 获取摄像头Capability
  device_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.
  // 开始构造CaptureSinkFilter
  sink_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_);
  // 如果用户请求的能力,和我们当前设备能力不同,需要重新连接下Filter
  if (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.coloradmin.cn/o/2243063.html

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

相关文章

解决Windows远程桌面 “为安全考虑,已锁定该用户账户,原因是登录尝试或密码更改尝试过多。请稍后片刻再重试,或与系统管理员或技术支持联系“问题

当我们远程连接服务器连接不上并提示“为安全考虑&#xff0c;已锁定该用户账户&#xff0c;原因是登录尝试或密码更改尝试过多。请稍候片刻再重试&#xff0c;或与系统管理员或技术支持联系”时&#xff0c;根本原因是当前计算机远程连接时输入了过多的错误密码&#xff0c;触…

企业BI工具如何选择?主流5款BI工具多维对比

数据大爆炸时代&#xff0c;企业数据爆发式增长&#xff0c;来自产品、运营、价值链以及外部的数据都成指数级增长趋势。利用大数据分析实现精细化运营&#xff0c;驱动业务增长是企业的理想蓝图。而BI工具能够整合、分析并可视化复杂的数据集&#xff0c;帮助管理层和决策者快…

Sping全面复习

Spring框架是一个功能强大且广泛使用的Java平台&#xff0c;它通过提供全面的基础设施支持&#xff0c;使得开发人员能够轻松构建高效、可移植、易于测试的代码。Spring的核心特性包括依赖注入&#xff08;DI&#xff09;、面向切面编程&#xff08;AOP&#xff09;和事件驱动模…

深挖C++赋值

详解赋值 const int a 10; int b a;&a 0x000000b7c6afef34 {56496} &a 0x000000b7c6afef34 {10} 3. &b 0x000000b7c6afef54 {10} 总结&#xff1a; int a 10 是指在内存中&#xff08;栈&#xff09;中创建一个int &#xff08;4 byte&#xff09;大小的空间…

vue项目使用eslint+prettier管理项目格式化

代码格式化、规范化说明 使用eslintprettier进行格式化&#xff0c;vscode中需要安装插件ESLint、Prettier - Code formatter&#xff0c;且格式化程序选择为后者&#xff08;vue文件、js文件要分别设置&#xff09; 对于eslint规则&#xff0c;在格式化时不会全部自动调整&…

让空间计算触手可及,VR手套何以点石成金?

引言 如何让一位母亲与她去世的小女儿“重逢”&#xff1f;韩国MBC电视台《I Met You》节目实现了一个“不可能”心愿。 在空旷的绿幕中&#xff0c;母亲Jang Ji-sung透过VR头显&#xff0c;看到了三年前因白血病去世的女儿Nayeon。当她伸出双手&#xff0c;居然能摸到女儿的…

多模态简述

多模态学习概念 【多模态简述-哔哩哔哩】 https://b23.tv/UrUyfln 定义&#xff1a; 模态&#xff1a;事物表达或感知的方式 多模态&#xff1a;研究异构和相互连接数据的科学&#xff0c;涵盖了从原始的器官信号到抽象概念的多种模态 语音和语言是理解人物交互的关键模态&am…

RabbitMQ-死信队列(golang)

1、概念 死信&#xff08;Dead Letter&#xff09;&#xff0c;字面上可以理解为未被消费者成功消费的信息&#xff0c;正常来说&#xff0c;生产者将消息放入到队列中&#xff0c;消费者从队列获取消息&#xff0c;并进行处理&#xff0c;但是由于某种原因&#xff0c;队列中的…

第8章利用CSS制作导航菜单

8.1 水平顶部导航栏 8.1.1 简单水平导航栏的设计与实现 8.1.1.1导航栏的创建 <nav>标签是 HIML5 新增的文档结构标签&#xff0c;用于标记导航栏&#xff0c;以便后续与网站的其他内整合&#xff0c;所以常用<nav>标签在页面上创建导航栏菜单区域。 例如,在<na…

「人眼视觉不再是视频消费的唯一形式」丨智能编解码和 AI 视频生成专场回顾@RTE2024

你是否想过&#xff0c;未来你看到的电影预告片、广告&#xff0c;甚至新闻报道&#xff0c;都可能完全由 AI 生成&#xff1f; 在人工智能迅猛发展的今天&#xff0c;视频技术正经历着一场前所未有的变革。从智能编解码到虚拟数字人&#xff0c;再到 AI 驱动的视频生成&#…

C++:哈希拓展-位图

目录 一.问题导入 二.什么是位图? 2.1如何确定目标数在哪个比特位? 2.2如何存放高低位 2.3位图模拟代码实现 2.3.1如何标记一个数 2.3.2如何重置标记 2.3.3如何检查一个数是否被标记 整体代码实现 标准库的Bitset 库中的bitset的缺陷 简单应用 一.问题导入 这道…

nacos-operator在k8s集群上部署nacos-server2.4.3版本踩坑实录

文章目录 操作步骤1. 拉取仓库代码2. 安装nacos-operator3. 安装nacos-server 坑点一坑点二nacos-ui页面访问同一集群环境下微服务连接nacos地址配置待办参考文档 操作步骤 1. 拉取仓库代码 &#xff08;这一步主要用到代码中的相关yml文件&#xff0c;稍加修改用于部署容器&…

Python爬虫----python爬虫基础

一、python爬虫基础-爬虫简介 1、现实生活中实际爬虫有哪些&#xff1f; 2、什么是网络爬虫&#xff1f; 3、什么是通用爬虫和聚焦爬虫&#xff1f; 4、为什么要用python写爬虫程序 5、环境和工具 二、python爬虫基础-http协议和chrome抓包工具 1、什么是http和https协议…

从北美火到中国,大数据洞察品牌“STANLEY”的突围之路

保守直筒大头的“硬汉”外形&#xff0c;以百变颜色踩中时尚命脉&#xff0c;与各路大牌“梦幻联动”&#xff0c;不少时尚弄潮儿没能逃过其“真香”诱惑。 这就是今年以来从北美火到中国的STANLEY&#xff0c;在“巨无霸”水杯中突围出属于自己的一条路。 最近STANLEY又整活…

Java结合ElasticSearch根据查询关键字,高亮显示全文数据。

由于es高亮显示机制的问题。当全文内容过多&#xff0c;且搜索中标又少时&#xff0c;就会出现高亮结果无法覆盖全文。因此需要根据需求手动替换。 1.根据es的ik分词器获取搜索词的分词结果。 es部分&#xff1a; //中文分词解析 post /_analyze {"analyzer":"…

Python绘制雪花

文章目录 系列目录写在前面技术需求完整代码代码分析1. 代码初始化部分分析2. 雪花绘制核心逻辑分析3. 窗口保持部分分析4. 美学与几何特点总结 写在后面 系列目录 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4…

Linux性能优化之火焰图简介

Linux 火焰图&#xff08;Flame Graph&#xff09;是一种可视化工具&#xff0c;用于分析程序性能问题&#xff0c;尤其是 CPU 使用情况。它展示了程序中函数调用的层次结构和各个调用栈占用的时间比例。 以下是详细介绍&#xff0c;包括火焰图的工作原理、生成步骤和实际使用中…

Axure设计之文本编辑器制作教程

文本编辑器是一个功能强大的工具&#xff0c;允许用户在图形界面中创建和编辑文本的格式和布局&#xff0c;如字体样式、大小、颜色、对齐方式等&#xff0c;在Web端实际项目中&#xff0c;文本编辑器的使用非常频繁。以下是在Axure中模拟web端富文本编辑器&#xff0c;来制作文…

Python中的正则表达式教程

一、 正则表达式基础 1。1。概念介绍 正则表达式是用于处理字符串的强大工具,它并不是Python的一部分。 其他编程语言中也有正则表达式的概念,区别只在于不同的编程语言实现支持的语法数量不同。 它拥有自己独特的语法以及一个独立的处理引擎&#xff0c;在提供了正则表达式…

脑机接口、嵌入式 AI 、工业级 MR、空间视频和下一代 XR 浏览器丨RTE2024 空间计算和新硬件专场回顾

这一轮硬件创新由 AI 引爆&#xff0c;或许最大受益者仍是 AI&#xff0c;因为只有硬件才能为 AI 直接获取最真实世界的数据。 在人工智能与硬件融合的新时代&#xff0c;实时互动技术正迎来前所未有的创新浪潮。从嵌入式系统到混合现实&#xff0c;从空间视频到脑机接口&…