官方文档:https://docs.microsoft.com/zh-cn/windows/win32/directshow/directshow

创建CLR类库项目(CSharpDirectShow),编写托管的DirectShow类库,右键项目属性–>链接器–> 输入–>附加依赖项;添加静态库文件Strmiids.lib和Quartz.lib;

定义头文件CSharpDirectShow.h,包含头文件dshow.h,定义如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#pragma once

#include <dshow.h>

using namespace System;
using namespace System::Runtime::InteropServices;

public ref class DirectShow
{
public:
/// <summary>
/// 在当前线程上初始化COM库,并将并发模型标识为单线程单元(STA)
/// </summary>
/// <returns></returns>
static Boolean ComInit();
/// <summary>
/// 关闭当前线程上的COM库,卸载该线程加载的所有DLL,释放该线程维护的所有其他资源,并强制关闭该线程上的所有RPC连接
/// </summary>
static void ComUinit();
/// <summary>
/// 获取视频输入设备
/// </summary>
/// <param name="devices"></param>
/// <returns></returns>
static Int32 GetVideoInputDevices([Out]array<VideoInputDsDevice^>^% devices);
/// <summary>
/// 获取音频输入设备
/// </summary>
/// <param name="device"></param>
/// <returns></returns>
static Int32 GetAudioInputDevices([Out]array<AudioInputDsDevice^>^% device);
};

其中,初始化COM库只能在STA线程调用,控制台程序直接调用会返回失败,在Winform和WPF等应用程序应该在第一次使用时初始化COM库;
当前线程初始化COM库和关闭COM库:

1
2
3
4
5
6
7
8
9
10
Boolean DirectShow::ComInit()
{
HRESULT hr = CoInitialize(NULL);
return hr == S_OK || hr == S_FALSE;
}

void DirectShow::ComUinit()
{
CoUninitialize();
}

一、枚举视频输入设备,参考:https://docs.microsoft.com/zh-cn/windows/win32/directshow/selecting-a-capture-device

1、创建视频输入设备的枚举器IEnumMoniker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
HRESULT DirectShow::EnumerateDevices(REFGUID category, IEnumMoniker** ppEnum)
{
// 创建系统设备枚举器
ICreateDevEnum* pDevEnum;
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));

if (SUCCEEDED(hr))
{
// 为类别创建枚举数.
hr = pDevEnum->CreateClassEnumerator(category, ppEnum, 0);

if (hr == S_FALSE)
hr = VFW_E_NOT_FOUND; // 类别是空的,视为错误

pDevEnum->Release();
}
return hr;
}

Int32 DirectShow::GetVideoInputDevices([Out]array<VideoInputDsDevice^>^% devices)
{
devices = nullptr;

IEnumMoniker* pEnum;
HRESULT hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum); // 创建视频输入设备枚举器

if (FAILED(hr))
return hr;

hr = EnumerateVideoInputDevices(pEnum, devices); // 枚举视频输入设备
pEnum->Release();
return hr;
}

2、调用IEnumMoniker::Next()方法枚举设备,调用IPropertyBag::Read方法读取设备属性,以获取设备的友好名称和设备标识字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
HRESULT DirectShow::EnumerateVideoInputDevices(IEnumMoniker* pEnum, [Out]array<VideoInputDsDevice^>^% devices)
{
HRESULT hr = S_FALSE;
devices = nullptr;
List<VideoInputDsDevice^>^ list = gcnew List<VideoInputDsDevice^>();

IMoniker* pMoniker;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
{
IPropertyBag* pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
if (FAILED(hr))
{
pMoniker->Release();
continue;
}

VideoInputDsDevice^ device = gcnew VideoInputDsDevice();

VARIANT var;
VariantInit(&var);

// 获取设备友好名
hr = pPropBag->Read(L"FriendlyName", &var, 0);

if (FAILED(hr))
hr = pPropBag->Read(L"Description", &var, 0);

if (SUCCEEDED(hr))
{
device->FriendlyName = System::String(var.bstrVal).ToString();
VariantClear(&var);
}

// 获取设备Moniker名
LPOLESTR pOleDisplayName = reinterpret_cast<LPOLESTR>(CoTaskMemAlloc(MAX_MONIKER_NAME_LENGTH * 2));
hr = pMoniker->GetDisplayName(NULL, NULL, &pOleDisplayName);

if (SUCCEEDED(hr))
{
device->MonikerName = System::String(pOleDisplayName).ToString();
array<VideoParams^>^ params;
hr = EnumerateVideoParams(pMoniker, params); // 枚举设备的采集参数
if (SUCCEEDED(hr))
device->Params = params;
}

CoTaskMemFree(pOleDisplayName);

list->Add(device);
pPropBag->Release();
pMoniker->Release();
}

devices = list->ToArray();
return S_OK;
}

3、枚举视频输入设备的采集参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
HRESULT DirectShow::EnumerateVideoParams(IMoniker* pMoniker, [Out]array<VideoParams^>^% params)
{
params = nullptr;
IBaseFilter* pFilter;
HRESULT hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);

if (FAILED(hr))
return hr;

IEnumPins* pinEnum;
hr = pFilter->EnumPins(&pinEnum);

if (FAILED(hr)) {
pFilter->Release();
return hr;
}

List<VideoParams^>^ list = gcnew List<VideoParams^>();
IPin* pPins;
while (pinEnum->Next(1, &pPins, NULL) == S_OK)
{
PIN_INFO pinInfo;
hr = pPins->QueryPinInfo(&pinInfo);

if (FAILED(hr) || pinInfo.dir != PINDIR_OUTPUT)
{
pPins->Release();
continue;
}

IEnumMediaTypes* mtEnum;
hr = pPins->EnumMediaTypes(&mtEnum);

if (FAILED(hr))
{
pPins->Release();
continue;
}

AM_MEDIA_TYPE* mt;
while (mtEnum->Next(1, &mt, NULL) == S_OK)
{
VideoParams^ param = nullptr;
if (mt->formattype == FORMAT_VideoInfo)
{
VIDEOINFOHEADER* pVih = reinterpret_cast<VIDEOINFOHEADER*>(mt->pbFormat);
param = gcnew VideoParams();
param->FrameWidth = pVih->bmiHeader.biWidth;
param->FrameHeight = pVih->bmiHeader.biHeight;
param->AverageFrameRate = pVih->AvgTimePerFrame == 0 ? 0 : 10000000 / pVih->AvgTimePerFrame;
}
else if (mt->formattype == FORMAT_VideoInfo2) {
VIDEOINFOHEADER2* pVih = reinterpret_cast<VIDEOINFOHEADER2*>(mt->pbFormat);
param = gcnew VideoParams();
param->FrameWidth = pVih->bmiHeader.biWidth;
param->FrameHeight = pVih->bmiHeader.biHeight;
param->AverageFrameRate = pVih->AvgTimePerFrame == 0 ? 0 : 10000000 / pVih->AvgTimePerFrame;
}

if (param && param->AverageFrameRate > 1)
{
Boolean isExit = false;
for each (VideoParams ^ item in list)
{
if (item->FrameWidth == param->FrameWidth && item->FrameHeight == param->FrameHeight && item->AverageFrameRate == param->AverageFrameRate)
{
isExit = true;
break;
}
}
if (!isExit)
list->Add(param);
}
}
pPins->Release();
}

pFilter->Release();

params = list->ToArray();
return S_OK;
}

调用代码及结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var ret = DirectShow.GetVideoInputDevices (out VideoInputDsDevice[] videoInputDevices);

if (ret == 0) {
Console.WriteLine ("视频输入设备:");

foreach (var videoInputDevice in videoInputDevices) {
Console.WriteLine ($"{videoInputDevice.FriendlyName}\t{videoInputDevice.MonikerName}");

if (videoInputDevice.Params.Length > 0) {
Console.WriteLine ("像素宽度\t像素高度\t1秒平均帧数");

foreach (var param in videoInputDevice.Params) {
Console.WriteLine ($"{param.FrameWidth}\t{param.FrameHeight}\t{param.AverageFrameRate}");
}
}

Console.WriteLine ();
}
}

二、枚举音频输入设备,参考https://docs.microsoft.com/zh-cn/windows/win32/directshow/selecting-a-capture-device ,过程和视频一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ret = DirectShow.GetAudioInputDevices (out AudioInputDsDevice[] audioInputDevices);

if (ret == 0) {
Console.WriteLine ("音频输入设备:");

foreach (var audioInputDevice in audioInputDevices) {
Console.WriteLine ($"{audioInputDevice.FriendlyName}\t{audioInputDevice.MonikerName}");

if (audioInputDevice.Params.Length > 0) {
Console.WriteLine ("音频格式\t通道数\t采样速率\t块对齐\t位数");

foreach (var param in audioInputDevice.Params) {
Console.WriteLine ($"{param.Format}\t{param.Channels}\t{param.SampleRate}\t{param.BlockAlign}\t{param.BitsPerSample}");
}
}

Console.WriteLine ();
}
}

官方文档:https://docs.microsoft.com/zh-cn/windows/win32/directshow/directshow
参考链接:https://www.cnblogs.com/pumbaa/p/14255725.html
参考代码:https://github.com/LowPlayer/CameraCapture