您的位置:首页 > 其它

Windows下用DirectShow查找摄像头(含分辨率)和麦克风

2017-01-03 13:38 375 查看
在视频聊天、视频会议、在线监控和视频展台等项目中,需要查找出本地电脑上连接的所有摄像头,网上流传比较多的方式是ffmpeg的方式,这种方式可以跨平台,不同的平台下调用不同的库。这种方式在控制台直接打印了摄像头的信息,无法(或者说我暂时没找到)在内存中获取,因此直接采用了DirectShow的方式,DirectShow枚举IMoniker和Ipin。因为网上的文档,不是特别详尽,所以我写了本文,我尽量解释清楚,分段贴出部分代码,主要是要看明白并且理解,通过本文中的方式,基本可以列出电脑上的摄像头和麦克风,以及他们的参数。

在另外一篇文章中,介绍了如何利用获取的设备信息播放和编解码:《MFC中如何利用ffmpeg和SDL2.0多线程多窗口播放摄像头的视频


1、用ffmpeg的方式

1)ffmpeg功能强大,关于ffmpeg的详细文档,可以去官网看看:http://ffmpeg.org/

直接静态库或者动态库好了,不怕麻烦的可以下载开发版:https://ffmpeg.zeranoe.com/builds/

2)关于ffmpeg支持的设备列表,可以参考下面的链接:

http://www.ffmpeg.org/ffmpeg-devices.html

3)这种方式比较简单,其实在Widows下还是调用dshow,

直接传入“list_devices”,列出设备列表。先看看命令行方式。

[cpp] view
plain copy







ffmpeg -list_devices true -f dshow -i dummy

上面的命令行和下面的代码是一个效果,看看命令行的参数和下面的代码的几个参数,是不是一样?

所以啊,如果看到命令行的例子,在写代码调用接口时可以参考他。

[cpp] view
plain copy







//Show directshow device

void show_dshow_device() {

AVFormatContext *pFormatCtx = avformat_alloc_context();

AVDictionary* options = NULL;

av_dict_set(&options, "list_devices", "true", 0);

AVInputFormat *iformat = av_find_input_format("dshow");

avformat_open_input(&pFormatCtx, "video=dummy", iformat, &options);

}

4)上面不是列出了设备名吗?那么把设备名传入下面的函数,就可以列出该设备支持的分辨率等信息

[cpp] view
plain copy







//Show device options

void show_dshow_device_option(const char* cameraName) {

AVFormatContext *pFormatCtx = avformat_alloc_context();

AVDictionary* options = NULL;

av_dict_set(&options, "list_options", "true", 0);

AVInputFormat *iformat = av_find_input_format("dshow");

char buffer[128];

sprintf(buffer, "video=%s", cameraName);

avformat_open_input(&pFormatCtx, buffer, iformat, &options);

}

为什么要列出分辨率的信息呢?因为如果你需要更改摄像头的分辨率,必须是该设备支持的分辨率,否则打开就会失败。

上面已经说了,这种方式可以列出来,但是内存中不好获取。


2、直接使用DirectShow的方式

DirectShow的方式,也不是那么麻烦,重点是搞清楚其机制

1)重要的数据结构IMoniker和IPin,前者是设备,后者是支持参数。

举个例子:IMoniker指摄像头,IPin里面指设备支持的分辨率,比如1024*768

另外,相对应的是枚举器:IEnumMoniker和IEnumPins,就是循环枚举IMoniker和IPin的。

2)先定义一个结构体,存储遍历之后的设备和参数

摄像头1:参数:1280*960,1024*768...

摄像头2:参数:1920*1440,1600*1200...

[cpp] view
plain copy







//设备参数

struct TDeviceParam {

int width; //分辨率宽

int height; //分辨率高

int avgTimePerFrame; //每帧的时间

TDeviceParam BestParam; //最好的参数

TDeviceParam() {

Reset();

}

void Reset() {

width = 0;

height = 0;

avgTimePerFrame = 1;

}

void Set(int w, int h, int avgTime) {

width = w;

height = h;

avgTimePerFrame = avgTime;

}

void Copy(TDeviceParam& param) {

Set(param.width, param.height, param.avgTimePerFrame);

}

};

//设备信息

struct TDeviceInfo {

WCHAR FriendlyName[MAX_FRIENDLY_NAME_LENGTH]; // 设备友好名

WCHAR MonikerName[MAX_MONIKER_NAME_LENGTH]; // 设备Moniker名

int ParamCount; // 参数数量

TDeviceParam Params[MAX_PARAM_COUNT]; // 支持的分辨率

TDeviceInfo() {

Reset();

}

void Reset() {

ParamCount = 0;

}

int SetResolution(int w, int h, int avgTime) {

if (ParamCount >= MAX_PARAM_COUNT)

return -1;

for (int i = 0; i < ParamCount; i++) {

if (Params[i].width == w && Params[i].height == h) {

return 0;

}

}

int insertIndex = 0;

for (int i = 0; i < ParamCount; i++) {

if (w > Params[i].width || h > Params[i].height) {

break;

}

else {

insertIndex++;

}

}

for (int i = ParamCount - 1; i >= insertIndex; i--) {

Params[i + 1].Copy(Params[i]);

}

Params[insertIndex].Set(w, h, avgTime);

ParamCount++;

if (w > BestParam.width) {

BestParam.Set(w, h, avgTime);

}

};

3)列出设备

列出设备,其中有个问题就是宽字符,所以可以用W2A(头文件是#include <atlconv.h>)来转换为Char

[cpp] view
plain copy







//根据设备最好的参数排序

bool SortDevice(const TDeviceInfo& device1, const TDeviceInfo& device2) {

<span style="white-space:pre"> </span>if (device1.BestParam.width > device2.BestParam.width)

<span style="white-space:pre"> </span>return true;

<span style="white-space:pre"> </span>return false;

}

//guidValue:

//CLSID_AudioInputDeviceCategory:获取音频输入设备列表

//CLSID_VideoInputDeviceCategory:获取视频输入设备列表

HRESULT DsGetAudioVideoInputDevices(std::vector<TDeviceInfo>& deviceVec, REFGUID guidValue)

{

TDeviceInfo info;

HRESULT hr;

// 初始化

deviceVec.clear();

// 初始化COM

hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

if (FAILED(hr)) {

printf("Init error!\n");

return hr;

}

// 创建系统设备枚举器实例

ICreateDevEnum *pSysDevEnum = NULL;

hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);

if (FAILED(hr)){

CoUninitialize();

printf("Create instance error!\n");

return hr;

}

// 获取设备类枚举器

IEnumMoniker *pEnumCat = NULL;

hr = pSysDevEnum->CreateClassEnumerator(guidValue, &pEnumCat, 0);

if (hr != S_OK) {

CoUninitialize();

//pSysDevEnum->Release();

return hr;

}

// 枚举设备名称

IMoniker *pMoniker = NULL;

ULONG cFetched;

while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) {

IPropertyBag *pPropBag;

hr = pMoniker->BindToStorage(NULL, NULL, IID_IPropertyBag, (void **)&pPropBag);

if ( FAILED( hr ) ) {

pMoniker->Release();

continue;

}

info.Reset();

// 获取设备友好名

VARIANT varName;

VariantInit(&varName);

hr = pPropBag->Read(L"FriendlyName", &varName, NULL);

if (SUCCEEDED(hr)) {

StringCchCopy(info.FriendlyName, MAX_FRIENDLY_NAME_LENGTH, varName.bstrVal);

#if PRINT_DEBUG

wprintf(L"Device:%s\n", info.FriendlyName);

#endif

// 获取设备Moniker名

LPOLESTR pOleDisplayName = reinterpret_cast<LPOLESTR>(CoTaskMemAlloc(MAX_MONIKER_NAME_LENGTH * 2));

if (pOleDisplayName != NULL) {

hr = pMoniker->GetDisplayName(NULL, NULL, &pOleDisplayName);

if (SUCCEEDED(hr)) {

StringCchCopy( info.MonikerName, MAX_MONIKER_NAME_LENGTH, pOleDisplayName );

//获取设备支持的分辨率

DsGetOptionDevice( pMoniker, info );

deviceVec.push_back( info );

}

CoTaskMemFree(pOleDisplayName);

}

}

VariantClear(&varName);

pPropBag->Release();

pMoniker->Release();

} // End for While

pEnumCat->Release();

pSysDevEnum->Release();

CoUninitialize();

std::sort( deviceVec.begin(), deviceVec.end(), SortDevice );

for (int i = 0; i < deviceVec.size(); i++) {

deviceVec[i].Debug();

}

return hr;

}

3)查找设备参数

查找设备参数此处需要注意的是,返回的是GUID,GUID需要查找uuids.h来查到对应的定义,比如

[cpp] view
plain copy







OUR_GUID_ENTRY(MEDIATYPE_Video,

0x73646976, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71)

所以,我简单定义对应关系的函数,可以把GUID映射为友好的名称。

另外,IPin->AM_MEDIA_TYPE->VIDEOINFOHEADER->BITMAPINFOHEADER

AM_MEDIA_TYPE类型请参考
https://msdn.microsoft.com/en-us/library/windows/desktop/dd373477(v=vs.85).aspx
VIDEOINFOHEADER的类型请参考
https://msdn.microsoft.com/en-us/library/windows/desktop/dd407325(v=vs.85).aspx
BITMAPINFOHEADER的类型请参考
https://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx
也就是说,如果想要什么参数,可以从上面几个类型中找。

[cpp] view
plain copy







int GuidToString(const GUID &guid, char* buffer){

int buf_len = 64;

snprintf(

buffer,

buf_len,

"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",

guid.Data1, guid.Data2, guid.Data3,

guid.Data4[0], guid.Data4[1],

guid.Data4[2], guid.Data4[3],

guid.Data4[4], guid.Data4[5],

guid.Data4[6], guid.Data4[7]);

return 0;

}

int GetMajorType(GUID guid, char* buffer) {

memset(buffer, 0, 256);

if (guid == MEDIATYPE_Video) {

snprintf(buffer, 256, "MEDIATYPE_Video");

return 0;

}

if (guid == MEDIATYPE_Audio) {

snprintf(buffer, 256, "MEDIATYPE_Audio");

return 0;

}

if (guid == MEDIASUBTYPE_RGB24) {

snprintf(buffer, 256, "MEDIATYPE_Stream");

return 0;

}

return -1;

}

int GetSubType(GUID guid, char* buffer) {

memset(buffer, 0, 256);

if( guid == MEDIASUBTYPE_YUY2){

snprintf(buffer, 256, "MEDIASUBTYPE_YUY2");

return 0;

}

if (guid == MEDIASUBTYPE_MJPG) {

snprintf(buffer, 256, "MEDIASUBTYPE_MJPG");

return 0;

}

if (guid == MEDIASUBTYPE_RGB24) {

snprintf(buffer, 256, "MEDIASUBTYPE_RGB24");

return 0;

}

return -1;

}

int GetFormatType(GUID guid, char* buffer) {

memset(buffer, 0, 256);

if (guid == FORMAT_VideoInfo) {

snprintf(buffer, 256, "FORMAT_VideoInfo");

return 0;

}

if (guid == FORMAT_VideoInfo2) {

snprintf(buffer, 256, "FORMAT_VideoInfo2");

return 0;

}

return -1;

}

int DsGetOptionDevice(IMoniker* pMoniker,TDeviceInfo& info) {

USES_CONVERSION;

HRESULT hr = NULL;

IBaseFilter *pFilter;

hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);

if (!pFilter) {

return -1;

}

IEnumPins * pinEnum = NULL;

IPin * pin = NULL;

if (FAILED(pFilter->EnumPins(&pinEnum))) {

pinEnum->Release();

return -1;

}

pinEnum->Reset();

ULONG pinFetched = 0;

while (SUCCEEDED(pinEnum->Next(1, &pin, &pinFetched)) && pinFetched) {

if (!pin) {

continue;

}

PIN_INFO pinInfo;

if (FAILED(pin->QueryPinInfo(&pinInfo))) {

continue;

}

if (pinInfo.dir != PINDIR_OUTPUT) {

continue;

}

#if PRINT_DEBUG

printf("\t[Pin] Dir:Output Name %s\n", W2A(pinInfo.achName));

#endif

IEnumMediaTypes *mtEnum = NULL;

AM_MEDIA_TYPE *mt = NULL;

if (FAILED(pin->EnumMediaTypes(&mtEnum)))

break;

mtEnum->Reset();

ULONG mtFetched = 0;

while (SUCCEEDED(mtEnum->Next(1, &mt, &mtFetched)) && mtFetched) {

char majorbuf[256];

if ( GetMajorType(mt->majortype, majorbuf) != 0) {

GuidToString(mt->majortype, majorbuf);

}

char subtypebuf[256];

if (GetSubType(mt->subtype, subtypebuf) != 0) {

GuidToString(mt->subtype, subtypebuf);

}

char formatbuf[256];

if (GetFormatType(mt->formattype, formatbuf) != 0) {

GuidToString(mt->formattype, formatbuf);

}

#if PRINT_DEBUG

printf("\t%s\t%s\t%s", majorbuf, subtypebuf, formatbuf);

#endif

BITMAPINFOHEADER* bmi = NULL;

int avgTime;

if (mt->formattype == FORMAT_VideoInfo) {

if ( mt->cbFormat >= sizeof(VIDEOINFOHEADER)){

VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>( mt->pbFormat);

bmi = &( pVih->bmiHeader );

avgTime = pVih->AvgTimePerFrame;

}

} else if (mt->formattype == FORMAT_VideoInfo2) {

if (mt->cbFormat >= sizeof(VIDEOINFOHEADER2)) {

VIDEOINFOHEADER2* pVih = reinterpret_cast<VIDEOINFOHEADER2*>(mt->pbFormat);

bmi = &(pVih->bmiHeader);

avgTime = pVih->AvgTimePerFrame;

}

}

if( bmi ){

info.SetResolution(bmi->biWidth, bmi->biHeight, avgTime);

#if PRINT_DEBUG

printf("\t%d * %d, Bit %d\n", bmi->biWidth, bmi->biHeight, bmi->biBitCount);

#endif

}else {

printf("\tNo find\n");

}

}

pin->Release();

}

return 0;

}

4)如何调用?

[cpp] view
plain copy







HRESULT hrrst;

GUID guid = CLSID_VideoInputDeviceCategory;

hrrst = DsGetAudioVideoInputDevices(videoDeviceVec, guid);

guid = CLSID_AudioInputDeviceCategory;

hrrst = DsGetAudioVideoInputDevices(audioDeviceVec, guid);

参考:

http://blog.csdn.net/leixiaohua1020/article/details/42649379

http://blog.csdn.net/jhqin/article/details/5929796
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: