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

    在视频聊天、视频会议、在线监控和视频展台等项目中,需要查找出本地电脑上连接的所有摄像头,网上流传比较多的方式是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
  在CODE上查看代码片 派生到我的代码片
  1. ffmpeg -list_devices true -f dshow -i dummy  
上面的命令行和下面的代码是一个效果,看看命令行的参数和下面的代码的几个参数,是不是一样?

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

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //Show directshow device  
  2. void show_dshow_device() {  
  3.     AVFormatContext *pFormatCtx = avformat_alloc_context();  
  4.     AVDictionary* options = NULL;  
  5.     av_dict_set(&options, "list_devices""true", 0);  
  6.     AVInputFormat *iformat = av_find_input_format("dshow");           
  7.     avformat_open_input(&pFormatCtx, "video=dummy", iformat, &options);   
  8. }  
4)上面不是列出了设备名吗?那么把设备名传入下面的函数,就可以列出该设备支持的分辨率等信息

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //Show device options  
  2. void show_dshow_device_option(const char* cameraName) {  
  3.     AVFormatContext *pFormatCtx = avformat_alloc_context();  
  4.     AVDictionary* options = NULL;  
  5.     av_dict_set(&options, "list_options""true", 0);  
  6.     AVInputFormat *iformat = av_find_input_format("dshow");  
  7.     char buffer[128];  
  8.     sprintf(buffer, "video=%s", cameraName);  
  9.     avformat_open_input(&pFormatCtx, buffer, iformat, &options);  
  10. }  
为什么要列出分辨率的信息呢?因为如果你需要更改摄像头的分辨率,必须是该设备支持的分辨率,否则打开就会失败。

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

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
  在CODE上查看代码片 派生到我的代码片
  1. //设备参数  
  2. struct TDeviceParam {  
  3.     int width;              //分辨率宽  
  4.     int height;             //分辨率高  
  5.     int avgTimePerFrame;                    //每帧的时间  
  6.         TDeviceParam BestParam;                 //最好的参数  
  7.     TDeviceParam() {  
  8.         Reset();  
  9.     }  
  10.     void Reset() {  
  11.         width = 0;  
  12.         height = 0;  
  13.         avgTimePerFrame = 1;  
  14.     }  
  15.     void Set(int w, int h, int avgTime) {  
  16.         width = w;  
  17.         height = h;  
  18.         avgTimePerFrame = avgTime;  
  19.     }  
  20.     void Copy(TDeviceParam& param) {  
  21.         Set(param.width, param.height, param.avgTimePerFrame);  
  22.     }  
  23. };  
  24. //设备信息  
  25. struct TDeviceInfo {  
  26.     WCHAR FriendlyName[MAX_FRIENDLY_NAME_LENGTH];   // 设备友好名    
  27.     WCHAR MonikerName[MAX_MONIKER_NAME_LENGTH];     // 设备Moniker名  
  28.     int ParamCount;                 // 参数数量  
  29.     TDeviceParam Params[MAX_PARAM_COUNT];       // 支持的分辨率  
  30.   
  31.     TDeviceInfo() {  
  32.         Reset();  
  33.     }  
  34.     void Reset() {  
  35.         ParamCount = 0;  
  36.     }  
  37.     int SetResolution(int w, int h, int avgTime) {  
  38.         if (ParamCount >= MAX_PARAM_COUNT)  
  39.             return -1;  
  40.         for (int i = 0; i < ParamCount; i++) {  
  41.             if (Params[i].width == w && Params[i].height == h) {  
  42.                 return 0;  
  43.             }  
  44.         }  
  45.         int insertIndex = 0;  
  46.         for (int i = 0; i < ParamCount; i++) {  
  47.             if (w > Params[i].width || h > Params[i].height) {  
  48.                 break;  
  49.             }  
  50.             else {  
  51.                 insertIndex++;  
  52.             }  
  53.         }  
  54.         for (int i = ParamCount - 1; i >= insertIndex; i--) {  
  55.             Params[i + 1].Copy(Params[i]);  
  56.         }  
  57.         Params[insertIndex].Set(w, h, avgTime);  
  58.         ParamCount++;  
  59.             if (w > BestParam.width) {  
  60.                         BestParam.Set(w, h, avgTime);  
  61.             }  
  62. };  
3)列出设备
列出设备,其中有个问题就是宽字符,所以可以用W2A(头文件是#include <atlconv.h>)来转换为Char

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. //根据设备最好的参数排序  
  2. bool SortDevice(const TDeviceInfo& device1, const TDeviceInfo& device2) {  
  3. <span style="white-space:pre">    </span>if (device1.BestParam.width > device2.BestParam.width)  
  4. <span style="white-space:pre">        </span>return true;  
  5. <span style="white-space:pre">    </span>return false;  
  6. }  
  7. //guidValue:  
  8. //CLSID_AudioInputDeviceCategory:获取音频输入设备列表  
  9. //CLSID_VideoInputDeviceCategory:获取视频输入设备列表  
  10. HRESULT DsGetAudioVideoInputDevices(std::vector<TDeviceInfo>& deviceVec, REFGUID guidValue)  
  11. {  
  12.     TDeviceInfo info;  
  13.     HRESULT hr;  
  14.   
  15.     // 初始化    
  16.     deviceVec.clear();  
  17.   
  18.     // 初始化COM    
  19.     hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);  
  20.     if (FAILED(hr)) {  
  21.         printf("Init error!\n");  
  22.         return hr;  
  23.     }  
  24.     // 创建系统设备枚举器实例    
  25.     ICreateDevEnum *pSysDevEnum = NULL;  
  26.     hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);  
  27.     if (FAILED(hr)){  
  28.         CoUninitialize();  
  29.         printf("Create instance error!\n");  
  30.         return hr;  
  31.     }  
  32.     // 获取设备类枚举器    
  33.     IEnumMoniker *pEnumCat = NULL;  
  34.     hr = pSysDevEnum->CreateClassEnumerator(guidValue, &pEnumCat, 0);  
  35.     if (hr != S_OK) {  
  36.         CoUninitialize();  
  37.         //pSysDevEnum->Release();  
  38.         return hr;  
  39.     }  
  40.   
  41.     // 枚举设备名称    
  42.     IMoniker *pMoniker = NULL;  
  43.     ULONG cFetched;  
  44.     while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) {  
  45.         IPropertyBag *pPropBag;  
  46.         hr = pMoniker->BindToStorage(NULL, NULL, IID_IPropertyBag, (void **)&pPropBag);  
  47.         if ( FAILED( hr ) ) {  
  48.             pMoniker->Release();  
  49.             continue;  
  50.         }         
  51.         info.Reset();  
  52.         // 获取设备友好名    
  53.         VARIANT varName;  
  54.         VariantInit(&varName);  
  55.         hr = pPropBag->Read(L"FriendlyName", &varName, NULL);  
  56.         if (SUCCEEDED(hr)) {  
  57.             StringCchCopy(info.FriendlyName, MAX_FRIENDLY_NAME_LENGTH, varName.bstrVal);  
  58. #if PRINT_DEBUG  
  59.             wprintf(L"Device:%s\n", info.FriendlyName);  
  60. #endif  
  61.             // 获取设备Moniker名    
  62.             LPOLESTR pOleDisplayName = reinterpret_cast<LPOLESTR>(CoTaskMemAlloc(MAX_MONIKER_NAME_LENGTH * 2));  
  63.             if (pOleDisplayName != NULL) {  
  64.                 hr = pMoniker->GetDisplayName(NULL, NULL, &pOleDisplayName);  
  65.                 if (SUCCEEDED(hr)) {  
  66.                     StringCchCopy( info.MonikerName, MAX_MONIKER_NAME_LENGTH, pOleDisplayName );  
  67.                     //获取设备支持的分辨率  
  68.                     DsGetOptionDevice( pMoniker, info );  
  69.                     deviceVec.push_back( info );  
  70.                 }  
  71.                 CoTaskMemFree(pOleDisplayName);  
  72.             }  
  73.         }         
  74.         VariantClear(&varName);  
  75.         pPropBag->Release();           
  76.         pMoniker->Release();  
  77.     } // End for While    
  78.   
  79.     pEnumCat->Release();  
  80.     pSysDevEnum->Release();  
  81.     CoUninitialize();  
  82.   
  83.     std::sort( deviceVec.begin(), deviceVec.end(), SortDevice );  
  84.     for (int i = 0; i < deviceVec.size(); i++) {  
  85.         deviceVec[i].Debug();  
  86.     }  
  87.     return hr;  
  88. }  
3)查找设备参数
查找设备参数此处需要注意的是,返回的是GUID,GUID需要查找uuids.h来查到对应的定义,比如
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. OUR_GUID_ENTRY(MEDIATYPE_Video,  
  2. 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
  在CODE上查看代码片 派生到我的代码片
  1. int GuidToString(const GUID &guid, char* buffer){  
  2.     int buf_len = 64;  
  3.     snprintf(  
  4.         buffer,  
  5.         buf_len,  
  6.         "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",  
  7.         guid.Data1, guid.Data2, guid.Data3,  
  8.         guid.Data4[0], guid.Data4[1],  
  9.         guid.Data4[2], guid.Data4[3],  
  10.         guid.Data4[4], guid.Data4[5],  
  11.         guid.Data4[6], guid.Data4[7]);  
  12.     return 0;  
  13. }  
  14.   
  15. int GetMajorType(GUID guid, char* buffer) {  
  16.     memset(buffer, 0, 256);  
  17.     if (guid == MEDIATYPE_Video) {  
  18.         snprintf(buffer, 256, "MEDIATYPE_Video");  
  19.         return 0;  
  20.     }  
  21.     if (guid == MEDIATYPE_Audio) {  
  22.         snprintf(buffer, 256, "MEDIATYPE_Audio");  
  23.         return 0;  
  24.     }  
  25.     if (guid == MEDIASUBTYPE_RGB24) {  
  26.         snprintf(buffer, 256, "MEDIATYPE_Stream");  
  27.         return 0;  
  28.     }  
  29.     return -1;  
  30. }  
  31.   
  32. int GetSubType(GUID guid, char* buffer) {  
  33.     memset(buffer, 0, 256);  
  34.     if( guid == MEDIASUBTYPE_YUY2){  
  35.         snprintf(buffer, 256, "MEDIASUBTYPE_YUY2");  
  36.         return 0;  
  37.     }  
  38.     if (guid == MEDIASUBTYPE_MJPG) {  
  39.         snprintf(buffer, 256, "MEDIASUBTYPE_MJPG");  
  40.         return 0;  
  41.     }  
  42.     if (guid == MEDIASUBTYPE_RGB24) {  
  43.         snprintf(buffer, 256, "MEDIASUBTYPE_RGB24");  
  44.         return 0;  
  45.     }  
  46.     return -1;  
  47. }  
  48.   
  49. int GetFormatType(GUID guid, char* buffer) {  
  50.     memset(buffer, 0, 256);  
  51.     if (guid == FORMAT_VideoInfo) {  
  52.         snprintf(buffer, 256, "FORMAT_VideoInfo");  
  53.         return 0;  
  54.     }  
  55.     if (guid == FORMAT_VideoInfo2) {  
  56.         snprintf(buffer, 256, "FORMAT_VideoInfo2");  
  57.         return 0;  
  58.     }  
  59.     return -1;  
  60. }  
  61.   
  62. int DsGetOptionDevice(IMoniker* pMoniker,TDeviceInfo& info) {  
  63.     USES_CONVERSION;  
  64.     HRESULT hr = NULL;  
  65.     IBaseFilter *pFilter;  
  66.     hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);  
  67.     if (!pFilter) {  
  68.         return -1;  
  69.     }  
  70.     IEnumPins * pinEnum = NULL;  
  71.     IPin * pin = NULL;  
  72.     if (FAILED(pFilter->EnumPins(&pinEnum))) {  
  73.         pinEnum->Release();  
  74.         return -1;  
  75.     }  
  76.     pinEnum->Reset();  
  77.     ULONG pinFetched = 0;  
  78.     while (SUCCEEDED(pinEnum->Next(1, &pin, &pinFetched)) && pinFetched) {  
  79.         if (!pin) {  
  80.             continue;  
  81.         }  
  82.         PIN_INFO pinInfo;  
  83.         if (FAILED(pin->QueryPinInfo(&pinInfo))) {  
  84.             continue;  
  85.         }  
  86.         if (pinInfo.dir != PINDIR_OUTPUT) {  
  87.             continue;  
  88.         }  
  89. #if PRINT_DEBUG  
  90.         printf("\t[Pin] Dir:Output Name %s\n", W2A(pinInfo.achName));  
  91. #endif  
  92.       
  93.         IEnumMediaTypes *mtEnum = NULL;  
  94.         AM_MEDIA_TYPE   *mt = NULL;  
  95.         if (FAILED(pin->EnumMediaTypes(&mtEnum)))  
  96.             break;  
  97.         mtEnum->Reset();  
  98.         ULONG mtFetched = 0;  
  99.         while (SUCCEEDED(mtEnum->Next(1, &mt, &mtFetched)) && mtFetched) {  
  100.             char majorbuf[256];           
  101.             if ( GetMajorType(mt->majortype, majorbuf) != 0) {  
  102.                 GuidToString(mt->majortype, majorbuf);  
  103.             }  
  104.             char subtypebuf[256];  
  105.             if (GetSubType(mt->subtype, subtypebuf) != 0) {  
  106.                 GuidToString(mt->subtype, subtypebuf);  
  107.             }                     
  108.             char formatbuf[256];  
  109.             if (GetFormatType(mt->formattype, formatbuf) != 0) {  
  110.                 GuidToString(mt->formattype, formatbuf);               
  111.             }  
  112. #if PRINT_DEBUG  
  113.             printf("\t%s\t%s\t%s", majorbuf, subtypebuf, formatbuf);  
  114. #endif  
  115.             BITMAPINFOHEADER* bmi = NULL;  
  116.             int avgTime;  
  117.             if (mt->formattype == FORMAT_VideoInfo) {  
  118.                 if ( mt->cbFormat >= sizeof(VIDEOINFOHEADER)){                      
  119.                     VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>( mt->pbFormat);  
  120.                     bmi = &( pVih->bmiHeader );  
  121.                     avgTime = pVih->AvgTimePerFrame;  
  122.                 }  
  123.             } else if (mt->formattype == FORMAT_VideoInfo2) {  
  124.                 if (mt->cbFormat >= sizeof(VIDEOINFOHEADER2)) {  
  125.                     VIDEOINFOHEADER2* pVih = reinterpret_cast<VIDEOINFOHEADER2*>(mt->pbFormat);  
  126.                     bmi = &(pVih->bmiHeader);  
  127.                     avgTime = pVih->AvgTimePerFrame;  
  128.                 }  
  129.             }  
  130.             if( bmi ){  
  131.                 info.SetResolution(bmi->biWidth, bmi->biHeight, avgTime);  
  132. #if PRINT_DEBUG  
  133.                 printf("\t%d * %d, Bit %d\n", bmi->biWidth, bmi->biHeight, bmi->biBitCount);  
  134. #endif        
  135.             }else {  
  136.                 printf("\tNo find\n");  
  137.             }             
  138.         }  
  139.         pin->Release();  
  140.     }  
  141.     return 0;  
  142. }  
4)如何调用?
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. HRESULT hrrst;  
  2. GUID guid = CLSID_VideoInputDeviceCategory;  
  3. hrrst = DsGetAudioVideoInputDevices(videoDeviceVec, guid);  
  4. guid = CLSID_AudioInputDeviceCategory;  
  5. hrrst = DsGetAudioVideoInputDevices(audioDeviceVec, guid);  
参考:

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

http://blog.csdn.net/jhqin/article/details/5929796

猜你喜欢

转载自blog.csdn.net/voidluffy/article/details/53993711