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
结构体作为参数传递。
- 在连接 DirectShow Filter时,可以使用
通过使用 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 !