WebRTC视频 04 - 视频采集类 VideoCaptureDS 中篇

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

一、前言:

前面介绍了CaptureFilter和SinkFilter,这让我想起了抗战电视剧里面的打电话,这俩Filter就像两部电话机,两部电话机得接通,得先告诉接线员,帮我接那个“三八六旅独立团”,咔一下子,接线员就把你的线怼到对端的线上了。该啥时候断,线的接头多粗多长,这就得接线员选择了。我们VideoCaptureDS当中连接俩Filter也和这个类似,这个工作交给SetCameraOutput这个函数去做了。

二、Filter和FilterGraph:

1、关系:

在这里插入图片描述

也就是说Filter得加入同一个FilterGraph由其管理,FilterGraph通过一系列逻辑操作,在两个Filter中间形成了一条通路(虚线好像不太妥,就这样吧 ,理解就行);两个Filter之间是通过Pin进行连接的,CaptureFilter的输出Pin,连接到SinkFilter的输入Pin;

2、ConnectDirect:

在 DirectShow 中,FilterGraphBuilder::ConnectDirect 方法是用于直接连接两个 DirectShow Filter的方法。这个方法允许在不经过其他中间Filter的情况下直接将一个Filter连接到另一个Filter,以建立媒体数据流路径。

下面是对 FilterGraphBuilder::ConnectDirect 方法的一般说明:

  • 语法

    HRESULT ConnectDirect(IPin *ppinOut, IPin *ppinIn, const AM_MEDIA_TYPE *pmt);
    
  • 参数

    • ppinOut:表示要连接的输出端口的 IPin 接口。
    • ppinIn:表示要连接的输入端口的 IPin 接口。
    • pmt:可选参数,表示连接的媒体类型(AM_MEDIA_TYPE 结构)。如果不提供此参数,则 DirectShow 将尝试在连接过程中自动匹配适当的媒体类型。
  • 返回值

    • 如果成功连接两个过滤器,则返回 S_OK
    • 如果连接失败,则返回相应的错误代码,比如 VFW_E_NOT_CONNECTED

所以,请注意不能使用Pin或者Filter的方法,要使用ConnectDirect方法。

3、AM_MEDIA_TYPE:

下面是 AM_MEDIA_TYPE 结构体的一般说明:

  • 结构体定义

    typedef struct _AMMediaType {
         
        GUID majortype;            // 主类型,如视频、音频等
        GUID subtype;              // 子类型,如 RGB、MPEG-2、PCM 等
        BOOL bFixedSizeSamples;    // 是否固定大小的样本
        BOOL bTemporalCompression; // 是否时间压缩
        ULONG lSampleSize;         // 样本大小
        GUID formattype;           // 格式类型,如 WaveFormatEx、VideoInfoHeader 等
        BYTE *pbFormat;            // 格式数据的指针
    } AM_MEDIA_TYPE;
    
  • 字段

    • majortype:表示媒体数据的主要类型,如视频、音频等。
    • subtype:表示媒体数据的子类型,用于更具体地描述数据的格式。
    • bFixedSizeSamples:指示数据样本是否是固定大小的。
    • bTemporalCompression:指示数据是否进行了时间压缩。
    • lSampleSize:表示数据样本的大小。
    • formattype:表示数据的格式类型,如 WaveFormatEx、VideoInfoHeader 等。
    • pbFormat:指向包含格式数据的指针。
  • 作用

    • AM_MEDIA_TYPE 结构体用于描述连接两个 DirectShow Filter时使用的媒体数据类型。通过指定主要类型、子类型和其他属性,可以确保连接的两个Filter之间传递的数据格式匹配。
  • 使用

    • 在连接 DirectShow Filter时,可以使用 AM_MEDIA_TYPE 结构体来指定连接的媒体类型,以便确保数据流的顺利传输和处理。
    • 在调用连接方法(如 ConnectDirect)时,可以将包含所需媒体类型信息的 AM_MEDIA_TYPE 结构体作为参数传递。

通过使用 AM_MEDIA_TYPE 结构体,可以在 DirectShow 中有效地描述和传递媒体数据的类型信息,帮助确保连接的Filter之间能够正确处理和渲染各种类型的音频和视频数据。

三、代码走读:

1、获得Capability:

两个Filter之间需要连接,就得协商媒体类型,就像开头说的接电话线的例子,你得根据电话的能力选合适的线,合适的插头。代码如下:

// 入参requestedCapability就是用户请求的能力
int32_t VideoCaptureDS::SetCameraOutput(const VideoCaptureCapability& requestedCapability) {
   
  // Get the best matching capability
  VideoCaptureCapability capability;
  int32_t capabilityIndex;

  // Store the new requested size
  _requestedCapability = requestedCapability;
  // Match the requested capability with the supported.
  // 根据用户请求的capability,和系统的capability,综合得出最优解(最接近的)
  if ((capabilityIndex = _dsInfo.GetBestMatchedCapability(
           _deviceUniqueId, _requestedCapability, capability)) < 0) {
   
    return -1;
  }
  // 省略后续步骤...
  return 0;
}

虽然用户请求的能力是requestedCapability,但是,我系统硬件未必支持这个能力,所以就要协商出一个最接近的capability,看下如何协商。

主要分为几大步:

  • 获取系统所有的capabilities;
  • 遍历capabilities,挑出和目标capability最接近的capability(接近的判断优先级:高度 > 宽度 > 最大帧率)
  • 最佳capability保存到resulting(出参);
  • 返回最佳capability在数组中的index;

注意,这个类和平台无关,linux和windows都这么干的!

// 根据用户请求的能力requested,结合系统支持的能力,协商出一个最接近的能力,用resulting返回
int32_t DeviceInfoImpl::GetBestMatchedCapability(
    const char* deviceUniqueIdUTF8,
    const VideoCaptureCapability& requested,
    VideoCaptureCapability& resulting) {
   
  if (!deviceUniqueIdUTF8)
    return -1;

  MutexLock lock(&_apiLock);
  if (!absl::EqualsIgnoreCase(deviceUniqueIdUTF8, absl::string_view(_lastUsedDeviceName, _lastUsedDeviceNameLength))) {
   
    // 获取系统所有的capabilities
    if (-1 == CreateCapabilityMap(deviceUniqueIdUTF8)) {
   
      return -1;
    }
  }

  int32_t bestformatIndex = -1;
  // 获取到capability的数量
  const int32_t numberOfCapabilies = static_cast<int32_t>(_captureCapabilities.size());
  // 遍历capabilities,挑出和目标capability最接近的capability(优先级:高度 > 宽度 > 最大帧率)
  for (int32_t tmp = 0; tmp < numberOfCapabilies; ++tmp)  // Loop through all capabilities
  {
   
     // 比较高度、宽度、最大帧率的部分省略...
  }

  // Copy the capability
  if (bestformatIndex < 0)
    return -1;
    
  resulting = _captureCapabilities[bestformatIndex]; // 最佳capability保存到resulting(出参)
  return bestformatIndex; // 返回最佳capability的index
}

我把最繁琐的选择最接近能力的部分删除了,线理解下主流程,就是前面我们说的那四步,接下来看看如何删掉的这部分代码;

// 根据用户请求的能力requested,结合系统支持的能力,协商出一个最接近的能力,用resulting返回
int32_t DeviceInfoImpl::GetBestMatchedCapability(
    const char* deviceUniqueIdUTF8,
    const VideoCaptureCapability& requested,
    VideoCaptureCapability& resulting) {
   
    
  // 前面的代码删除....
  int32_t bestformatIndex = -1;
  int32_t bestWidth = 0;
  int32_t bestHeight = 0;
  int32_t bestFrameRate = 0;
  VideoType bestVideoType = VideoType::kUnknown;
  // 遍历capabilities,挑出和目标capability最接近的capability(优先级:高度 > 宽度 > 最大帧率)
  for (int32_t tmp = 0; tmp < numberOfCapabilies; ++tmp) {
   
    // 获取当前的capability
    VideoCaptureCapability& capability = _captureCapabilities[tmp];
    // 计算当前capability和用户请求的capability之间的宽度、高度、帧率的差值
    const int32_t diffWidth = capability.width - requested.width;
    const int32_t diffHeight = capability.height - requested.height;
    const int32_t diffFrameRate = capability.maxFPS - requested.maxFPS;
    // 计算之前循环中选择的最优值的宽度、高度、最大帧率和用户请求的capability之间的差值
    const int32_t currentbestDiffWith = bestWidth - requested.width;
    const int32_t currentbestDiffHeight = bestHeight - requested.height;
    const int32_t currentbestDiffFrameRate = bestFrameRate - requested.maxFPS;
    // 线判断当前capability的高度是否比前面选的bestHeight更接近用于请求的高度,有两种情况:
    // 1、当前高度大于目标高度,但是更接近
    // diffHeight >= 0表示我们当前capability的height不小于用户请求的
    // diffHeight <= abs(currentbestDiffHeight)表示更接近目标能力的高度
    // 2、当前告诉小于目标高度,但是更接近
    // currentbestDiffHeight < 0表示之前选择的最优高度小于目标高度
    // diffHeight >= currentbestDiffHeight表示当前更接近目标高度
    if ((diffHeight >= 0 && diffHeight <= abs(currentbestDiffHeight))
        || (currentbestDiffHeight < 0 && diffHeight >= currentbestDiffHeight)) {
   
      // 如果当前高度差等于之前的最优高度差,那就继续比较宽度和最大帧率
      if (diffHeight == currentbestDiffHeight)  // Found best height. Care about the width)
      {
   
        // 如果高度差和之前相同,看当前宽度是否最优,和高度类似,都是当前宽度大于目标宽度,或者小于目标宽的,但是更接近
        if ((diffWidth >= 0 && diffWidth <= abs(currentbestDiffWith))
            || (currentbestDiffWith < 0 && diffWidth >= currentbestDiffWith)) {
   
          // 如果高度和宽度都和当前最优值一样
          if (diffWidth == currentbestDiffWith && diffHeight == currentbestDiffHeight)
          {
   
            // 如果高度差和宽度差都和之前相同,看当前帧率是否最优,
            if (((diffFrameRate >= 0 && diffFrameRate <= currentbestDiffFrameRate) 
                 || (currentbestDiffFrameRate < 0 && diffFrameRate >= currentbestDiffFrameRate))
            ) {
   
              // 如果帧率差值相等 或者 之前最优帧率 不小于 用户请求的帧率
              // (当前帧率和之前最优解的帧率相等,或者之前的帧率已经足够好了)
              if ((currentbestDiffFrameRate ==
                   diffFrameRate)  // Same frame rate as previous  or frame rate
                                   // allready good enough
                  || (currentbestDiffFrameRate >= 0)) {
   
                // 如果videoType和用户请求的不一致,并且不是第一次设置videoType,
                // 同时本次capability的videoType也是webrtc支持的videoType
                if (bestVideoType !
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值