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 capabilityVideoCaptureCapability 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))) {// 获取系统所有的capabilitiesif (-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 capabilityif (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) {// 获取当前的capabilityVideoCaptureCapability& 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支持的videoTypeif (bestVideoType != requested.videoType &&requested.videoType != VideoType::kUnknown &&(capability.videoType == requested.videoType ||capability.videoType == VideoType::kI420 ||capability.videoType == VideoType::kYUY2 ||capability.videoType == VideoType::kYV12)) {// 设置当前capability的videoType 和 capability 索引为最佳(也就是当前capability是最佳的)bestVideoType = capability.videoType;bestformatIndex = tmp;}// If width height and frame rate is full filled we can use the// camera for encoding if it is supported.// 如果高度、宽度和用户请求的一样,并且当前帧率不低于请求最大帧率,那么当前就是最佳capabilityif (capability.height == requested.height &&capability.width == requested.width &&capability.maxFPS >= requested.maxFPS) {bestformatIndex = tmp;}} else  // Better frame rate{// 本次帧率差 和 之前最优解的帧率差 不相等,并且之前最优解的 最大帧率比用户请求的小。// 那么我们当前的capability就是最优的bestWidth = capability.width;bestHeight = capability.height;bestFrameRate = capability.maxFPS;bestVideoType = capability.videoType;bestformatIndex = tmp;}}} else  // Better width than previously{// 如果宽度差更小,那么当前就是最capabilitybestWidth = capability.width;bestHeight = capability.height;bestFrameRate = capability.maxFPS;bestVideoType = capability.videoType;bestformatIndex = tmp;}}     // else width no good} else  // Better height{ // 如果高度差更小,那么当前就是最优capabilitybestWidth = capability.width;bestHeight = capability.height;bestFrameRate = capability.maxFPS;bestVideoType = capability.videoType;bestformatIndex = tmp;}}  // else height not good}    // end for// Copy the capabilityif (bestformatIndex < 0)return -1;resulting = _captureCapabilities[bestformatIndex]; // 最佳capability保存到resulting(出参)return bestformatIndex; // 返回最佳capability的index
}

配合注释其实最容易理解了,就是找出高度最接近的,如果高度和目标高度接近,我就是最优capability,如果高度和别人一样接近,那么就再判断宽度和帧率。

上面提到的获取系统所有能力的接口CreateCapabilityMap,我们windows平台就是使用DirectShow那些接口获取的,可以看看:

int32_t DeviceInfoDS::CreateCapabilityMap(const char* deviceUniqueIdUTF8)
{// Reset old capability list_captureCapabilities.clear();const int32_t deviceUniqueIdUTF8Length =(int32_t)strlen((char*)deviceUniqueIdUTF8);if (deviceUniqueIdUTF8Length > kVideoCaptureUniqueNameLength) {RTC_LOG(LS_INFO) << "Device name too long";return -1;}RTC_LOG(LS_INFO) << "CreateCapabilityMap called for device "<< deviceUniqueIdUTF8;char productId[kVideoCaptureProductIdLength];// 这儿个captureDevice就是CapatureFilterIBaseFilter* captureDevice = DeviceInfoDS::GetDeviceFilter(deviceUniqueIdUTF8, productId, kVideoCaptureProductIdLength);if (!captureDevice)return -1;// 获取输出引脚IPin* outputCapturePin = GetOutputPin(captureDevice, GUID_NULL);if (!outputCapturePin) {RTC_LOG(LS_INFO) << "Failed to get capture device output pin";RELEASE_AND_CLEAR(captureDevice);return -1;}IAMExtDevice* extDevice = NULL;// 获取IID_IAMExtDevice接口到extDevice// IAMExtDevice 接口是用来操作外部设备的扩展接口之一,它提供了一些方法和属性,比如,// 控制设备的特定功能、设置参数或获取设备状态信息等HRESULT hr =captureDevice->QueryInterface(IID_IAMExtDevice, (void**)&extDevice);// 判断我们现在使用的是不是一个外设if (SUCCEEDED(hr) && extDevice) {RTC_LOG(LS_INFO) << "This is an external device";extDevice->Release();}// 使用输出Pin生成一个新的接口IID_IAMStreamConfig到streamConfig// 这个接口获取/设置设备的能力IAMStreamConfig* streamConfig = NULL;hr = outputCapturePin->QueryInterface(IID_IAMStreamConfig,(void**)&streamConfig);if (FAILED(hr)) {RTC_LOG(LS_INFO) << "Failed to get IID_IAMStreamConfig interface ""from capture device";return -1;}// this  gets the FPSIAMVideoControl* videoControlConfig = NULL;// 再获取CaptureFilter的IID_IAMVideoControl接口到videoControlConfig/*** 视频格式控制:允许应用程序控制视频设备的格式,如帧率、分辨率、像素格式等。* 摄像头控制:提供了对摄像头属性的访问,比如调整亮度、对比度、色调、饱和度等。* 相机参数设置:可以通过该接口设置摄像头的参数,如曝光时间、白平衡、对焦等。* 视频流控制:允许控制视频流的开始、停止、暂停等操作。* 镜头控制:对于某些视频设备,可能还包括对镜头控制的支持,如变焦、焦距等。*/HRESULT hrVC = captureDevice->QueryInterface(IID_IAMVideoControl,(void**)&videoControlConfig);if (FAILED(hrVC)) {RTC_LOG(LS_INFO) << "IID_IAMVideoControl Interface NOT SUPPORTED";}AM_MEDIA_TYPE* pmt = NULL;VIDEO_STREAM_CONFIG_CAPS caps;int count, size;// 获取所有的Capabilitieshr = streamConfig->GetNumberOfCapabilities(&count, &size);if (FAILED(hr)) {RTC_LOG(LS_INFO) << "Failed to GetNumberOfCapabilities";RELEASE_AND_CLEAR(videoControlConfig);RELEASE_AND_CLEAR(streamConfig);RELEASE_AND_CLEAR(outputCapturePin);RELEASE_AND_CLEAR(captureDevice);return -1;}// Check if the device support formattype == FORMAT_VideoInfo2 and// FORMAT_VideoInfo. Prefer FORMAT_VideoInfo since some cameras (ZureCam) has// been seen having problem with MJPEG and FORMAT_VideoInfo2 Interlace flag is// only supported in FORMAT_VideoInfo2// 遍历所有的capabilities,看每一种capability的格式类型是什么,// FORMAT_VideoInfo2是比较新的,老设备基本都还是supportFORMAT_VideoInfobool supportFORMAT_VideoInfo2 = false;bool supportFORMAT_VideoInfo = false;bool foundInterlacedFormat = false;GUID preferedVideoFormat = FORMAT_VideoInfo;for (int32_t tmp = 0; tmp < count; ++tmp) {hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps));if (hr == S_OK) {if (pmt->majortype == MEDIATYPE_Video &&pmt->formattype == FORMAT_VideoInfo2) {RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2";supportFORMAT_VideoInfo2 = true;VIDEOINFOHEADER2* h =reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat);assert(h);foundInterlacedFormat |=h->dwInterlaceFlags &(AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly);}if (pmt->majortype == MEDIATYPE_Video &&pmt->formattype == FORMAT_VideoInfo) {RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2";supportFORMAT_VideoInfo = true;}}}// 如果支持videoInfo2if (supportFORMAT_VideoInfo2) {// 如果也支持videoInfo格式,并且没有交错格式(foundInterlacedFormat一种老的格式,现在都是逐行扫描)// 那么就使用videoInfoif (supportFORMAT_VideoInfo && !foundInterlacedFormat) {preferedVideoFormat = FORMAT_VideoInfo;} else {// 否则,使用VideoInfo2preferedVideoFormat = FORMAT_VideoInfo2;}}// 遍历所有的capabilitiesfor (int32_t tmp = 0; tmp < count; ++tmp) {// 获取当前的capability,同时获取媒体类型(pointer media type)hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps));if (hr != S_OK) {RTC_LOG(LS_INFO) << "Failed to GetStreamCaps";RELEASE_AND_CLEAR(videoControlConfig);RELEASE_AND_CLEAR(streamConfig);RELEASE_AND_CLEAR(outputCapturePin);RELEASE_AND_CLEAR(captureDevice);return -1;}// 如果我们获取的媒体类型是Video,并且格式和我们之前首选的格式一致if (pmt->majortype == MEDIATYPE_Video &&pmt->formattype == preferedVideoFormat) {VideoCaptureCapabilityWindows capability;int64_t avgTimePerFrame = 0;// 如果是videoInfo格式,将能力赋值给capability变量if (pmt->formattype == FORMAT_VideoInfo) {VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat);assert(h);capability.directShowCapabilityIndex = tmp; // 当前capability在capabilities中的indexcapability.width = h->bmiHeader.biWidth; // 宽capability.height = h->bmiHeader.biHeight; // 高avgTimePerFrame = h->AvgTimePerFrame; // 每帧的平均时间}// 如果是videoInfo2格式,同理if (pmt->formattype == FORMAT_VideoInfo2) {VIDEOINFOHEADER2* h =reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat);assert(h);capability.directShowCapabilityIndex = tmp;capability.width = h->bmiHeader.biWidth;capability.height = h->bmiHeader.biHeight;capability.interlaced =h->dwInterlaceFlags &(AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly);avgTimePerFrame = h->AvgTimePerFrame;}// 如果之前IMediaControl接口获取成功了if (hrVC == S_OK) {LONGLONG* frameDurationList; // 帧率LONGLONG maxFPS; // 最大帧率long listSize; // 帧率列表大小SIZE size; // 帧图像大小(宽和高)size.cx = capability.width;size.cy = capability.height;// GetMaxAvailableFrameRate doesn't return max frame rate always// eg: Logitech Notebook. This may be due to a bug in that API// because GetFrameRateList array is reversed in the above camera. So// a util method written. Can't assume the first value will return// the max fps.// 获取设备所支持的所有帧率(引脚,capability index,帧图像大小(宽和高),帧率列表大小,帧率列表)hrVC = videoControlConfig->GetFrameRateList(outputCapturePin, tmp, size, &listSize, &frameDurationList);// On some odd cameras, you may get a 0 for duration.// GetMaxOfFrameArray returns the lowest duration (highest FPS)if (hrVC == S_OK && listSize > 0 &&0 != (maxFPS = GetMaxOfFrameArray(frameDurationList, listSize))) { // 获取最大值帧率(微妙)capability.maxFPS = static_cast<int>(10000000 / maxFPS); // 转换帧率单位,微妙->秒capability.supportFrameRateControl = true;} else  // use existing method{RTC_LOG(LS_INFO) << "GetMaxAvailableFrameRate NOT SUPPORTED";if (avgTimePerFrame > 0)capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame);elsecapability.maxFPS = 0;}} else  // use existing method in case IAMVideoControl is not supported{if (avgTimePerFrame > 0)capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame);elsecapability.maxFPS = 0;}// can't switch MEDIATYPE :~(// 转换下mediaType,从用户的枚举到设置被枚举if (pmt->subtype == MEDIASUBTYPE_I420) {capability.videoType = VideoType::kI420;} else if (pmt->subtype == MEDIASUBTYPE_IYUV) {capability.videoType = VideoType::kIYUV;} else if (pmt->subtype == MEDIASUBTYPE_RGB24) {capability.videoType = VideoType::kRGB24;} else if (pmt->subtype == MEDIASUBTYPE_YUY2) {capability.videoType = VideoType::kYUY2;} else if (pmt->subtype == MEDIASUBTYPE_RGB565) {capability.videoType = VideoType::kRGB565;} else if (pmt->subtype == MEDIASUBTYPE_MJPG) {capability.videoType = VideoType::kMJPEG;} else if (pmt->subtype == MEDIASUBTYPE_dvsl ||pmt->subtype == MEDIASUBTYPE_dvsd ||pmt->subtype ==MEDIASUBTYPE_dvhd)  // If this is an external DV camera{capability.videoType =VideoType::kYUY2;  // MS DV filter seems to create this type} else if (pmt->subtype ==MEDIASUBTYPE_UYVY)  // Seen used by Declink capture cards{capability.videoType = VideoType::kUYVY;} else if (pmt->subtype ==MEDIASUBTYPE_HDYC)  // Seen used by Declink capture cards. Uses// BT. 709 color. Not entiry correct to use// UYVY. http://en.wikipedia.org/wiki/YCbCr{RTC_LOG(LS_INFO) << "Device support HDYC.";capability.videoType = VideoType::kUYVY;} else {WCHAR strGuid[39];StringFromGUID2(pmt->subtype, strGuid, 39);RTC_LOG(LS_WARNING)<< "Device support unknown media type " << strGuid << ", width "<< capability.width << ", height " << capability.height;continue;}// 已经设置好了一个capability,存到下面这俩变量当中_captureCapabilities.push_back(capability);_captureCapabilitiesWindows.push_back(capability);RTC_LOG(LS_INFO) << "Camera capability, width:" << capability.width<< " height:" << capability.height<< " type:" << static_cast<int>(capability.videoType)<< " fps:" << capability.maxFPS;}FreeMediaType(pmt);pmt = NULL;}RELEASE_AND_CLEAR(streamConfig);RELEASE_AND_CLEAR(videoControlConfig);RELEASE_AND_CLEAR(outputCapturePin);RELEASE_AND_CLEAR(captureDevice);  // Release the capture device// Store the new used device name_lastUsedDeviceNameLength = deviceUniqueIdUTF8Length;_lastUsedDeviceName =(char*)realloc(_lastUsedDeviceName, _lastUsedDeviceNameLength + 1);memcpy(_lastUsedDeviceName, deviceUniqueIdUTF8,_lastUsedDeviceNameLength + 1);RTC_LOG(LS_INFO) << "CreateCapabilityMap " << _captureCapabilities.size();// 返回capabilities中capability个数return static_cast<int32_t>(_captureCapabilities.size());
}

其实核心函数就是调用了GetNumberOfCapabilities(),之所以这么又臭又长,是因为存在版本的兼容,以及一些格式的转换,都是低级垒砖代码,毫无技术含量,甚至是反面教材,兼容性太差。还想吐槽,算了。

但是,你要注意看刚才的代码,所有能力都存到两个数组中了,要记住,后面会用。

  // 已经设置好了一个capability,存到下面这俩变量当中_captureCapabilities.push_back(capability);_captureCapabilitiesWindows.push_back(capability);

2、微调Capability:

继续回到VideoCaptureDS::SetCameraOutput,看获得了最接近的能力之后,后面怎么处理。

int32_t VideoCaptureDS::SetCameraOutput(const VideoCaptureCapability& requestedCapability) {// Reduce the frame rate if possible.// 如果我们获得的最大帧率比用户请求的大,那么减小一点,使用用户请求的最大帧率即可// 否则,就使用30帧(比如用户设置了一个十万八千帧和你闹着玩)if (capability.maxFPS > requestedCapability.maxFPS) {capability.maxFPS = requestedCapability.maxFPS;} else if (capability.maxFPS <= 0) {capability.maxFPS = 30;}// Convert it to the windows capability index since they are not nexessary// the sameVideoCaptureCapabilityWindows windowsCapability;// 上面对capability的帧率做了修改,这儿保存一份我们之前获得的最优capablity到windowsCapabilityif (_dsInfo.GetWindowsCapability(capabilityIndex, windowsCapability) != 0) {return -1;}IAMStreamConfig* streamConfig = NULL;AM_MEDIA_TYPE* pmt = NULL;VIDEO_STREAM_CONFIG_CAPS caps;// 获取IID_IAMStreamConfig接口HRESULT hr = _outputCapturePin->QueryInterface(IID_IAMStreamConfig,(void**)&streamConfig);if (hr) {RTC_LOG(LS_INFO) << "Can't get the Capture format settings.";return -1;}// Get the windows capability from the capture devicebool isDVCamera = false;// 从硬件设备获取capabilities和pmt(媒体类型),然后,微调_dsInfo.GetWindowsCapability的值hr = streamConfig->GetStreamCaps(windowsCapability.directShowCapabilityIndex,&pmt, reinterpret_cast<BYTE*>(&caps));if (hr == S_OK) {if (pmt->formattype == FORMAT_VideoInfo2) {VIDEOINFOHEADER2* h = reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat);if (capability.maxFPS > 0 && windowsCapability.supportFrameRateControl) {h->AvgTimePerFrame = REFERENCE_TIME(10000000.0 / capability.maxFPS);}} else {VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat);if (capability.maxFPS > 0 && windowsCapability.supportFrameRateControl) {// 设置硬件每帧平均时间h->AvgTimePerFrame = REFERENCE_TIME(10000000.0 / capability.maxFPS);}}
}
  • 根据用户请求的帧率,丢获取的capability进行帧率微调;
  • 保存了一份原始的,没有经过微调的capability;
  • 从硬件设备获取capabilities和pmt(媒体类型),然后,微调_dsInfo.GetWindowsCapability的值;
  • 我们目前还都没有使用FORMAT_VideoInfo2,还用的是FORMAT_VideoInfo,因此,设置每帧平均时间;

3、设置SinkFilter输入Pin:

将GetWindowsCapability获取的capability设置给SinkFilter的inputPin。里面两个重要成员变量:

  1. requested_capability_这个用户请求的capability会被更新(使用前面我们获取的capability);
  2. resulting_capability_就是存储的最终结果;(只是构造对象,还没设置);
int32_t VideoCaptureDS::SetCameraOutput(const VideoCaptureCapability& requestedCapability) {// Set the sink filter to request this capability// 将前面获取到的capabilities,设置给sinkFilter的input pin,// 看sinkFilter是否支持captureFilter设置的capabilitysink_filter_->SetRequestedCapability(capability);
}
// 之前存储的是用户期望的capability(也就是requested_capability_)
// 现在根据系统的capability来调整capability,得到结果resulting_capability_
// 其实只是构造了对象resulting_capability_,具体的值还要交给下一轮去处理
HRESULT CaptureInputPin::SetRequestedCapability(const VideoCaptureCapability& capability) {RTC_DCHECK_RUN_ON(&main_checker_);RTC_DCHECK(Filter()->IsStopped());requested_capability_ = capability;resulting_capability_ = VideoCaptureCapability();return S_OK;
}

4、设置硬件capability:

就是告诉硬件我们最终选择的capability;

int32_t VideoCaptureDS::SetCameraOutput(const VideoCaptureCapability& requestedCapability) {  // Order the capture device to use this capability// 根据用户设置的和硬件支持的,校正出最终的capabilityhr += streamConfig->SetFormat(pmt);
}

5、连接两个Filter:

入口:

int32_t VideoCaptureDS::SetCameraOutput(const VideoCaptureCapability& requestedCapability) {    // 调用FilterGraph的方法进行两个Filter的连接// 最后一个参数NULL说明我们不校验媒体类型,直接将两个pin连接起来即可hr = _graphBuilder->ConnectDirect(_outputCapturePin, _inputSendPin, NULL);
}

里面会进行媒体类型协商,Allocator的协商;

  1. 媒体类型协商:主要由ReceiveConnection函数完成,让输入pin决定我们是否要接受这个连接;(里面会判断媒体类型我们是否可以接受);
  2. Allocator的协商;
    1. 第一步:看是否需要什么特殊条件,webrtc直接返回了,表示没特殊条件;
    2. 第二步:调用输入Pin的GetAllocator来获取一个分配器;最终由输出Pin决定是否要使用这个Allocator;
    3. 第三步:pin选择好之后通过NotifyAllocator通知输入Pin到底我们选择了哪个Allocator;

上面执行完 _graphBuilder->ConnectDirect 之后,里面就会自动调用CaptureInputPin::ReceiveConnection,看输入pin是否接受这个连接。

看代码:

/*** 检查这个接收pin(也就是input pin)是否可以接收(比如媒体格式什么的是否符合这个filter pin的预期)* @param connector: 上一个filter的输出pin* @param media_type: 上一个filter所支持的媒体类型*/
STDMETHODIMP CaptureInputPin::ReceiveConnection(IPin* connector,const AM_MEDIA_TYPE* media_type) {RTC_DCHECK_RUN_ON(&main_checker_);RTC_DCHECK(Filter()->IsStopped());// 不为空说明之前和其他pin已经连接了if (receive_pin_) {RTC_DCHECK(false);return VFW_E_ALREADY_CONNECTED;}// 检查上一个filter的pin是不是输出pinHRESULT hr = CheckDirection(connector);if (FAILED(hr))return hr;// 将media_type转换为capabilityif (!TranslateMediaTypeToVideoCaptureCapability(media_type,&resulting_capability_))return VFW_E_TYPE_NOT_ACCEPTED;// Complete the connection// 因为之前枚举类型传入的null,表示我们sink来什么类型就接收什么类型,不用校验receive_pin_ = connector;ResetMediaType(&media_type_);CopyMediaType(&media_type_, media_type);return S_OK;
}
  • 确保这个pin没有和别的pin相连,不允许被抢占;
  • 因为我们自己是输入pin,因此,和我们相连的必须是输出pin;
  • 同时,发现竟然对sink类型没有做什么校验;

上面medi_type转换为capability的方法如下:

// Returns true if the media type is supported, false otherwise.
// For supported types, the |capability| will be populated accordingly.
// 将media_type转换为capability
bool TranslateMediaTypeToVideoCaptureCapability(const AM_MEDIA_TYPE* media_type,VideoCaptureCapability* capability) {RTC_DCHECK(capability);if (!media_type || media_type->majortype != MEDIATYPE_Video ||!media_type->pbFormat) {return false;}const BITMAPINFOHEADER* bih = nullptr;if (media_type->formattype == FORMAT_VideoInfo) {bih = &reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat)->bmiHeader;} else if (media_type->formattype != FORMAT_VideoInfo2) {bih = &reinterpret_cast<VIDEOINFOHEADER2*>(media_type->pbFormat)->bmiHeader;} else {return false;}RTC_LOG(LS_INFO) << "TranslateMediaTypeToVideoCaptureCapability width:"<< bih->biWidth << " height:" << bih->biHeight<< " Compression:0x" << rtc::ToHex(bih->biCompression);// 转换媒体子类型,比如用户请求的是MEDIASUBTYPE_RGB24,那么我们摄像头就按照VideoType::kRGB24采集const GUID& sub_type = media_type->subtype;if (sub_type == MEDIASUBTYPE_MJPG &&bih->biCompression == MAKEFOURCC('M', 'J', 'P', 'G')) {capability->videoType = VideoType::kMJPEG;} else if (sub_type == MEDIASUBTYPE_I420 &&bih->biCompression == MAKEFOURCC('I', '4', '2', '0')) {capability->videoType = VideoType::kI420;} else if (sub_type == MEDIASUBTYPE_YUY2 &&bih->biCompression == MAKEFOURCC('Y', 'U', 'Y', '2')) {capability->videoType = VideoType::kYUY2;} else if (sub_type == MEDIASUBTYPE_UYVY &&bih->biCompression == MAKEFOURCC('U', 'Y', 'V', 'Y')) {capability->videoType = VideoType::kUYVY;} else if (sub_type == MEDIASUBTYPE_HDYC) {capability->videoType = VideoType::kUYVY;} else if (sub_type == MEDIASUBTYPE_RGB24 && bih->biCompression == BI_RGB) {capability->videoType = VideoType::kRGB24;} else {return false;}// Store the incoming width and height// 存储传进来的宽和高capability->width = bih->biWidth;// Store the incoming height,// for RGB24 we assume the frame to be upside down// 如果存储RGB24,由于图像是YUV,需要将高度转换成负值if (sub_type == MEDIASUBTYPE_RGB24 && bih->biHeight > 0) {capability->height = -(bih->biHeight);} else {capability->height = abs(bih->biHeight);}return true;
}

此时,已经连接好了Filter,接下来就是协商分配器了。

6、协商Allocator:

步骤如下:

  • 首先获取分配器属性:

    /*** 获取分配器的属性(由于我们对分配器没有特殊要求,这儿是null)*/
    STDMETHODIMP CaptureInputPin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES* props) {return E_NOTIMPL;
    }
    
  • 获取分配器:

    /*** 获取分配器*/
    STDMETHODIMP CaptureInputPin::GetAllocator(IMemAllocator** allocator) {RTC_DCHECK_RUN_ON(&main_checker_);if (allocator_ == nullptr) {// 调用CLSID_MemoryAllocator类的IID_IMemAllocator接口,最终分配好allocatorHRESULT hr = CoCreateInstance(CLSID_MemoryAllocator, 0,CLSCTX_INPROC_SERVER, IID_IMemAllocator,reinterpret_cast<void**>(allocator));if (FAILED(hr))return hr;allocator_.swap(allocator); // allocator设置给成员变量}*allocator = allocator_; // 设置出参allocator_->AddRef(); // 增加引用计数return S_OK;
    }
    
  • 至此,输入pin就已经创建好了allocator;

    /*** 通知输入pin,选择了哪个Allocator*/
    STDMETHODIMP CaptureInputPin::NotifyAllocator(IMemAllocator* allocator,BOOL read_only) {RTC_DCHECK_RUN_ON(&main_checker_);allocator_.swap(&allocator);if (allocator_)allocator_->AddRef();if (allocator)allocator->Release();return S_OK;
    }
    

四、总结:

本节主要讲了怎么将两个Filter进行连接,连接之后就可以进行数据采集了。

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

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

相关文章

【弱监督视频异常检测】2024-ESWA-基于扩散的弱监督视频异常检测常态预训练

2024-ESWA-Diffusion-based normality pre-training for weakly supervised video anomaly detection 基于扩散的弱监督视频异常检测常态预训练摘要1. 引言2. 相关工作3. 方法论3.1. 使用扩散自动编码器进行常态学习3.2. 全局-局部特征编码器3.2.1 局部块3.2.2 全局块3.2.3 协同…

ONLYOFFICE8.2版本测评,团队协作的办公软件

文章目录 引言ONLYOFFICE产品简介功能与特点1. 实时协作2. 兼容性3. 模板库4. 评论和修订5. 安全性 体验与测评功能测试 邀请用户使用项目介绍结尾了解更多 引言 在数字化办公的浪潮中&#xff0c;效率和协作成为了工作的核心。ONLYOFFICE作为一个强大的办公套件&#xff0c;正…

Day18 Nim游戏

你和你的朋友&#xff0c;两个人一起玩 Nim 游戏&#xff1a; 桌子上有一堆石头。 你们轮流进行自己的回合&#xff0c; 你作为先手 。 每一回合&#xff0c;轮到的人拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。 假设你们每一步都是最优解。请编写一个函数&#xff…

【论文复现】STM32设计的物联网智能鱼缸

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀STM32设计的物联网智能鱼缸 【1】项目功能介绍【2】设计需求总结【3】项目硬件模块组成 1.2 设计思路【1】整体设计思路【2】ESP8266工作模式…

3D意识(3D Awareness)浅析

一、简介 3D意识&#xff08;3D Awareness&#xff09;主要是指视觉基础模型&#xff08;visual foundation models&#xff09;对于3D结构的意识或感知能力&#xff0c;即这些模型在处理2D图像时是否能够理解和表示出图像中物体或场景的3D结构&#xff0c;其具体体现在编码场景…

day-83 最少翻转次数使二进制矩阵回文 II

思路 关键在于1的个数要为4的倍数&#xff0c;首先镜像的四个位置肯定一定为4的倍数&#xff0c;如果行和列为奇数则需要单独考虑&#xff0c;如果行和列皆为奇数&#xff0c;那么中心的那个数一定为0 解题过程 再单独考虑如果行和列为奇数&#xff0c;具体参考灵神。如果diff…

算法沉淀一:双指针

目录 前言&#xff1a; 双指针介绍 对撞指针 快慢指针 题目练习 1.移动零 2.复写零 3.快乐数 4.盛水最多的容器 5.有效三角形的个数 6.和为s的两个数 7.三数之和 8.四数之和 前言&#xff1a; 此章节介绍一些算法&#xff0c;主要从leetcode上的题来讲解&#xff…

《InsCode AI IDE:编程新时代的引领者》

《InsCode AI IDE&#xff1a;编程新时代的引领者》 一、InsCode AI IDE 的诞生与亮相二、独特功能与优势&#xff08;一&#xff09;智能编程体验&#xff08;二&#xff09;多语言支持与功能迭代 三、实际应用与案例&#xff08;一&#xff09;游戏开发案例&#xff08;二&am…

GitLab 如何降级?

本分分享 GitLab 降级的流程和注意事项。极狐GitLab 为 GitLab 的中文发行版&#xff0c;本文以私有化部署的极狐GitLab 为例来演示整个过程。 【极狐GitLab 推出 GitLab 老旧版本的专业升级服务【https://dl.gitlab.cn/cm33bsfv】&#xff0c;可以让 12.x、13.x、14.x、15.x …

【动手学电机驱动】 STM32-FOC(7)MCSDK Pilot 上位机控制与调试

STM32-FOC&#xff08;1&#xff09;STM32 电机控制的软件开发环境 STM32-FOC&#xff08;2&#xff09;STM32 导入和创建项目 STM32-FOC&#xff08;3&#xff09;STM32 三路互补 PWM 输出 STM32-FOC&#xff08;4&#xff09;IHM03 电机控制套件介绍 STM32-FOC&#xff08;5&…

IDEA2024:右下角显示内存

使用场景&#xff1a; 实时知晓idea内存使用情况 解决方案: 开启内存显示 View -> Apperance -> Status Bar Widgets -> Memory Indicator 效果如下&#xff1a;

2024140读书笔记|《作家榜名著:生如夏花·泰戈尔经典诗选》——你从世界的生命的溪流浮泛而下,终于停泊在我的心头

2024140读书笔记|《作家榜名著&#xff1a;生如夏花泰戈尔经典诗选》——你从世界的生命的溪流浮泛而下&#xff0c;终于停泊在我的心头 《作家榜名著&#xff1a;生如夏花泰戈尔经典诗选》[印]泰戈尔&#xff0c;郑振铎译&#xff0c;泰戈尔的诗有的清丽&#xff0c;有的童真&…

c# 调用c++ 的dll 出现找不到函数入口点

今天在调用一个设备的dll文件时遇到了一点波折&#xff0c;因为多c 不熟悉&#xff0c;调用过程张出现了找不到函数入口点&#xff0c;一般我们使用c# 调用c 文件&#xff0c;还是比较简单。 [DllImport("AtnDll2.dll",CharSet CharSet.Ansi)]public static extern …

Python_爬虫3_Requests库网络爬虫实战(5个实例)

目录 实例1&#xff1a;京东商品页面的爬取 实例2&#xff1a;亚马逊商品页面的爬取 实例3&#xff1a;百度360搜索关键词提交 实例4&#xff1a;网络图片的爬取和存储 实例5&#xff1a;IP地址归地的自动查询 实例1&#xff1a;京东商品页面的爬取 import requests url …

WebSocket协议在Java中的整合

1. 常见的消息推送方式 2.WebSocket API 3.基于WebSocket的实战&#xff08;实时聊天室&#xff09; 这里以解析后端代码为主&#xff0c;前端不作为重点&#xff0c;若想复现项目&#xff0c;请从作者的仓库中拉取代码 WebSocket-chatRoom: 基于WebSocket协议实现一个简单的…

蓝桥杯每日真题 - 第15天

题目&#xff1a;&#xff08;钟表&#xff09; 题目描述&#xff08;13届 C&C B组B题&#xff09; 解题思路&#xff1a; 理解钟表指针的运动&#xff1a; 秒针每分钟转一圈&#xff0c;即每秒转6度。 分针每小时转一圈&#xff0c;即每分钟转6度。 时针每12小时转一圈…

在 Node.js 中解决极验验证码:使用 Puppeteer 自动化

近年来&#xff0c;极验验证码在区分真实用户和自动化系统方面越来越先进&#xff0c;使其成为网页抓取和自动化的重大障碍。如果您正在使用 Node.js 并致力于在自动化流程中解决极验验证码&#xff0c;那么使用 Puppeteer 是一种有效的方法。Puppeteer 提供了一个高级 API 来控…

centos7 升级openssl 与升级openssh 安装卸载 telnet-server

前言&#xff1a; 服务器被安全扫描&#xff0c;扫出了漏洞需要修复&#xff0c;根据提示将openssh升级为9.8p1的版本&#xff0c;同时需要升级openssl&#xff0c;但是升级openssh可能会导致ssh连接失败&#xff0c;从而无法继续操作&#xff0c;特别是远程机房尤为危险&#…

PETR/PETRv2/StreamPETR论文阅读

1. PETR PETR网络结构如下&#xff0c;主要包括image-backbone&#xff0c;3D Coordinates Generator&#xff0c;3D Position Encoder&#xff0c;transformer Decoder四个模块。 把N 个视角的图像输入到骨干网络中以提取 2D 多视图特征。在 3D 坐标生成器中&#xff0c;首先…

若点集A=B则A必能恒等变换地变为B=A这一几何常识推翻直线(平面)公理

黄小宁 关键词&#xff1a;“更无理”复数 复平面z各点z的对应点z1的全体是z1面。z面平移变为z1面就使x轴⊂z面沿本身平移变为ux1轴。R可几何化为R轴&#xff0c;R轴可沿本身平移变为R′轴&#xff0c;R′轴可沿本身平移变为R″轴&#xff0c;...。直线公理和平面公理使几百年…