DirectShow 本身是一个基于 Filter Graph 的框架,它不直接提供像 AVFoundation 或 MediaFoundation 那样高级的 API 来设置参数,设置参数的过程实际上是在 Filter Graph 中找到合适的 Filter,然后通过其接口(如 IAMStreamConfig)来调整参数。

这个过程的核心是枚举和配置 Pin。
核心概念
- Filter Graph (滤镜图): 数据流处理的管道图,采集设备(如摄像头)是一个 Filter,它有一个或多个输出 Pin。
- Pin (引脚): Filter 的连接点,输出 Pin 产出数据,输入 Pin 接收数据,一个摄像头 Filter 通常有一个或多个输出 Pin,例如一个用于视频,一个用于音频。
- IAMStreamConfig 接口: 这是设置采集参数的关键接口,它存在于许多 Filter(如摄像头 Filter、视频渲染器 Filter)上,用于配置流的格式、分辨率、帧率等。
- IPin 接口: 代表 Pin 的基本接口,用于连接、枚举媒体类型等。
- AM_MEDIA_TYPE 结构: 描述媒体流的格式信息,包括主要类型(如
MEDIATYPE_Video)、子类型(如MEDIASUBTYPE_RGB24)和格式块(formattype,如FORMAT_VideoInfo)。
设置采集参数的完整步骤
假设我们要从一个视频采集设备(摄像头)中采集视频,并设置其分辨率和帧率。
第 1 步:枚举系统设备
你需要找到系统中可用的视频采集设备,这通常通过 ICreateDevEnum (设备枚举器) 来完成。
#include <dshow.h>
#include <dshowasf.h>
#pragma comment(lib, "strmiids.lib")
// 初始化 COM 库
CoInitialize(NULL);
// 创建设备枚举器
ICreateDevEnum* pDevEnum = NULL;
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pDevEnum);
if (FAILED(hr)) { /* 错误处理 */ }
// 创建视频输入设备的类别枚举器
IEnumMoniker* pEnum = NULL;
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDevice, &pEnum, 0);
if (FAILED(hr) || pEnum == NULL) { /* 没有找到视频设备 */ }
// 枚举所有视频设备
IMoniker* pMoniker = NULL;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK) {
// 从 Moniker 获取设备名称
IPropertyBag* pPropBag = NULL;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPropBag);
if (SUCCEEDED(hr)) {
VARIANT var;
var.vt = VT_BSTR;
hr = pPropBag->Read(L"FriendlyName", &var, NULL);
if (SUCCEEDED(hr)) {
wprintf(L"Found video device: %s\n", var.bstrVal);
SysFreeString(var.bstrVal);
}
pPropBag->Release();
}
pMoniker->Release();
}
pEnum->Release();
pDevEnum->Release();
第 2 步:构建 Filter Graph
选择一个设备后,用它来创建 Filter Graph。

IGraphBuilder* pGraph = NULL;
ICaptureGraphBuilder2* pBuilder = NULL;
IBaseFilter* pVideoInputFilter = NULL;
// 创建 Filter Graph Builder
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuilder);
if (FAILED(hr)) { /* 错误处理 */ }
// 创建 Graph Builder
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&pGraph);
if (FAILED(hr)) { /* 错误处理 */ }
pBuilder->SetFiltergraph(pGraph);
// 使用上一步找到的 Moniker 创建 Filter
// (这里简化了,实际应用中应保存选中的 pMoniker)
// hr = pMoniker->BindToObject(..., IID_IBaseFilter, (void**)&pVideoInputFilter);
// 假设我们已经有了 pVideoInputFilter
// hr = pGraph->AddFilter(pVideoInputFilter, L"Video Capture Filter");
第 3 步:查找并配置 Pin(核心步骤)
这是设置参数的关键,我们需要找到视频输入 Filter 的输出 Pin,然后通过 IAMStreamConfig 接口来配置它。
// 1. 在视频输入 Filter 上查找输出 Pin
IPin* pInputPin = NULL;
hr = pVideoInputFilter->FindPin(L"Capture", &pInputPin); // "Capture" 是常见的输出 Pin 名称
if (FAILED(hr)) {
// "Capture" 不行,可以尝试枚举所有 Pin 来找到视频输出 Pin
IEnumPins* pEnumPins = NULL;
pVideoInputFilter->EnumPins(&pEnumPins);
IPin* pPin = NULL;
while (pEnumPins->Next(1, &pPin, NULL) == S_OK) {
PIN_DIRECTION dir;
pPin->QueryDirection(&dir);
if (dir == PINDIR_OUTPUT) {
// 检查这个 Pin 是否输出视频
AM_MEDIA_TYPE mt;
pPin->ConnectionMediaType(&mt);
if (mt.majortype == MEDIATYPE_Video) {
pInputPin = pPin;
break;
}
}
pPin->Release();
}
pEnumPins->Release();
}
if (!pInputPin) { /* 错误:找不到视频输出 Pin */ }
// 2. 查询 IAMStreamConfig 接口
IAMStreamConfig* pStreamConfig = NULL;
hr = pInputPin->QueryInterface(IID_IAMStreamConfig, (void**)&pStreamConfig);
if (FAILED(hr)) { /* 错误:Pin 不支持 IAMStreamConfig */ }
// 3. 获取 Pin 支持的所有媒体类型
int iCount = 0;
int iSize = 0;
hr = pStreamConfig->GetNumberOfCapabilities(&iCount, &iSize);
if (FAILED(hr)) { /* 错误处理 */ }
// 4. 遍历所有媒体类型,找到我们想要的分辨率和帧率
for (int i = 0; i < iCount; i++) {
AM_MEDIA_TYPE* pmt = NULL;
VIDEO_STREAM_CONFIG_CAPS caps;
hr = pStreamConfig->GetStreamCaps(i, &pmt, (BYTE*)&caps);
if (SUCCEEDED(hr)) {
// 检查这个媒体类型是否符合我们的要求
if (pmt->formattype == FORMAT_VideoInfo) {
VIDEOINFOHEADER* pvi = (VIDEOINFOHEADER*)pmt->pbFormat;
// 假设我们要 1920x1080 分辨率,30fps
if (pvi->bmiHeader.biWidth == 1920 && pvi->bmiHeader.biHeight == 1080) {
// 检查帧率
double AvgTimePerFrame = (double)pvi->AvgTimePerFrame / 10000000.0; // 转换为秒
if (fabs(AvgTimePerFrame - (1.0/30.0)) < 0.001) { // 允许小的浮点误差
// 找到了!设置这个媒体类型
hr = pStreamConfig->SetFormat(pmt);
if (SUCCEEDED(hr)) {
wprintf(L"Successfully set format to 1920x1080@30fps.\n");
}
// 退出循环
break;
}
}
}
DeleteMediaType(pmt); // 释放 GetStreamCaps 分配的内存
}
}
// 5. 释放接口
pStreamConfig->Release();
pInputPin->Release();
第 4 步:连接并运行 Graph
设置好参数后,就可以将 Pin 连接到渲染器(如 Video Renderer Filter)并运行 Graph 了。
// ... (添加渲染器 Filter)
IBaseFilter* pVideoRenderer = NULL;
// CoCreateInstance(CLSID_VideoRenderer, ..., &pVideoRenderer);
// pGraph->AddFilter(pVideoRenderer, L"Video Renderer");
// 连接 Pin
IPin* pOutPin = NULL; // 从 pVideoInputFilter 的 Capture Pin 获取
IPin* pInPin = NULL; // 从 pVideoRenderer 获取 In Pin
// pGraph->Connect(pOutPin, pInPin);
// 运行 Graph
IMediaControl* pControl = NULL;
pGraph->QueryInterface(IID_IMediaControl, (void**)&pControl);
hr = pControl->Run();
if (SUCCEEDED(hr)) {
// 等待或处理消息
Sleep(10000); // 运行10秒
}
pControl->Stop();
pControl->Release();
// ... (释放所有 Filter 和 Graph 接口)
常见采集参数及其设置
分辨率
如上例所示,通过遍历 VIDEOINFOHEADER 结构中的 bmiHeader.biWidth 和 bmiHeader.biHeight 来找到并设置。
帧率
帧率存储在 VIDEOINFOHEADER 的 AvgTimePerFrame 字段中,单位是 100-nanosecond units(即 1/10,000,000 秒)。
- 30fps 的
AvgTimePerFrame值是333333(即 1/30 * 10,000,000)。 - 25fps 的值是
400000。 - 计算公式:
AvgTimePerFrame = (1 / fps) * 10000000
视频格式(颜色空间)
这由 AM_MEDIA_TYPE 的 subtype 字段决定。
MEDIASUBTYPE_RGB24: 24位真彩色MEDIASUBTYPE_RGB32: 32位真彩色MEDIASUBTYPE_YUY2: YUV 4:2:2 格式(常用于视频处理)MEDIASUBTYPE_NV12: YUV 4:2:0 格式(现代摄像头常用)MEDIASUBTYPE_UYVY: 另一种 YUV 4:2:2 格式
在遍历 GetStreamCaps 时,检查 pmt->subtype 即可。
其他高级参数(如亮度、对比度、对焦)
这些参数通常通过 IAMVideoProcAmp 接口来控制,它和 IAMStreamConfig 一样,也是在 Pin 上查询。
// 在找到的视频输出 Pin (pInputPin) 上查询
IAMVideoProcAmp* pProcAmp = NULL;
hr = pInputPin->QueryInterface(IID_IAMVideoProcAmp, (void**)&pProcAmp);
if (SUCCEEDED(hr)) {
long lValue;
long Flags;
// 获取当前亮度
pProcAmp->Get(VideoProcAmp_Brightness, &lValue, &Flags);
// 设置亮度 (范围通常是 0-100)
lValue = 80; // 设置亮度为80
pProcAmp->Set(VideoProcAmp_Brightness, lValue, VideoProcAmp_Flags_Manual);
pProcAmp->Release();
}
IAMTuner 接口用于控制电视调谐器相关的参数(如频道、制式)。
重要注意事项
- 线程安全: DirectShow 操作不是线程安全的,最好在同一个线程(通常是主线程)中创建、控制和销毁 Filter Graph。
- 内存管理:
GetStreamCaps分配的AM_MEDIA_TYPE结构必须用DeleteMediaType函数释放。CoTaskMemFree适用于 COM 分配的内存,不适用于此。 - 接口释放: 所有通过
QueryInterface获取的接口,以及CoCreateInstance创建的对象,都必须在使用完毕后调用Release()释放引用计数。 - 错误处理: DirectShow 操作返回
HRESULT,必须检查每个HRESULT的值,确保操作成功,可以使用SUCCEEDED(hr)和FAILED(hr)宏。 - 现代替代方案: DirectShow 是一项较老的技术(Windows XP 时代),在 Windows 7 及更高版本,推荐使用 Media Foundation (MF),它提供了更现代、更强大的 API 和更清晰的编程模型,对于新的项目,除非有特殊兼容性要求,否则应优先考虑 Media Foundation。
在 DirectShow 中设置采集参数的流程可以概括为: 枚举设备 -> 构建Graph -> 在Filter的Output Pin上查询IAMStreamConfig接口 -> 遍历支持的媒体类型 -> 找到匹配的参数 -> 调用SetFormat应用参数。
这个过程比较繁琐,需要手动管理 Filter Graph 和 Pin 连接,但它提供了底层的控制能力。
