当前位置:   article > 正文

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

directshow pmoniker

        在视频聊天、视频会议、在线监控和视频展台等项目中,需要查找出本地电脑上连接的所有摄像头,网上流传比较多的方式是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”,列出设备列表。先看看命令行方式。

 

ffmpeg -list_devices true -f dshow -i dummy
   
   
  • 1

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

 

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

 


   
   
  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. }
  • 1

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

 

 


 
 
  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. }
  • 1

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

 

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

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...

  
  
  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. TDeviceInfo() {
  31. Reset();
  32. }
  33. void Reset() {
  34. ParamCount = 0;
  35. }
  36. int SetResolution(int w, int h, int avgTime) {
  37. if (ParamCount >= MAX_PARAM_COUNT)
  38. return -1;
  39. for (int i = 0; i < ParamCount; i++) {
  40. if (Params[i].width == w && Params[i].height == h) {
  41. return 0;
  42. }
  43. }
  44. int insertIndex = 0;
  45. for (int i = 0; i < ParamCount; i++) {
  46. if (w > Params[i].width || h > Params[i].height) {
  47. break;
  48. else {
  49. insertIndex++;
  50. }
  51. }
  52. for (int i = ParamCount - 1; i >= insertIndex; i--) {
  53. Params[i + 1].Copy(Params[i]);
  54. }
  55. Params[insertIndex].Set(w, h, avgTime);
  56. ParamCount++;
  57. if (w > BestParam.width) {
  58. BestParam.Set(w, h, avgTime);
  59. }
  60. };
  • 1
3)列出设备
列出设备,其中有个问题就是宽字符,所以可以用W2A(头文件是#include <atlconv.h>)来转换为Char
 

  
  
  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. deviceVec.clear();
  16. // 初始化COM
  17. hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  18. if (FAILED(hr)) {
  19. printf("Init error!\n");
  20. return hr;
  21. }
  22. // 创建系统设备枚举器实例
  23. ICreateDevEnum *pSysDevEnum = NULL;
  24. hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
  25. if (FAILED(hr)){
  26. CoUninitialize();
  27. printf("Create instance error!\n");
  28. return hr;
  29. }
  30. // 获取设备类枚举器
  31. IEnumMoniker *pEnumCat = NULL;
  32. hr = pSysDevEnum->CreateClassEnumerator(guidValue, &pEnumCat, 0);
  33. if (hr != S_OK) {
  34. CoUninitialize();
  35. //pSysDevEnum->Release();
  36. return hr;
  37. }
  38. // 枚举设备名称
  39. IMoniker *pMoniker = NULL;
  40. ULONG cFetched;
  41. while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) {
  42. IPropertyBag *pPropBag;
  43. hr = pMoniker->BindToStorage(NULL, NULL, IID_IPropertyBag, (void **)&pPropBag);
  44. if ( FAILED( hr ) ) {
  45. pMoniker->Release();
  46. continue;
  47. }
  48. info.Reset();
  49. // 获取设备友好名
  50. VARIANT varName;
  51. VariantInit(&varName);
  52. hr = pPropBag->Read(L"FriendlyName", &varName, NULL);
  53. if (SUCCEEDED(hr)) {
  54. StringCchCopy(info.FriendlyName, MAX_FRIENDLY_NAME_LENGTH, varName.bstrVal);
  55. #if PRINT_DEBUG
  56. wprintf(L"Device:%s\n", info.FriendlyName);
  57. #endif
  58. // 获取设备Moniker名
  59. LPOLESTR pOleDisplayName = reinterpret_cast<LPOLESTR>(CoTaskMemAlloc(MAX_MONIKER_NAME_LENGTH * 2));
  60. if (pOleDisplayName != NULL) {
  61. hr = pMoniker->GetDisplayName(NULL, NULL, &pOleDisplayName);
  62. if (SUCCEEDED(hr)) {
  63. StringCchCopy( info.MonikerName, MAX_MONIKER_NAME_LENGTH, pOleDisplayName );
  64. //获取设备支持的分辨率
  65. DsGetOptionDevice( pMoniker, info );
  66. deviceVec.push_back( info );
  67. }
  68. CoTaskMemFree(pOleDisplayName);
  69. }
  70. }
  71. VariantClear(&varName);
  72. pPropBag->Release();
  73. pMoniker->Release();
  74. } // End for While
  75. pEnumCat->Release();
  76. pSysDevEnum->Release();
  77. CoUninitialize();
  78. std::sort( deviceVec.begin(), deviceVec.end(), SortDevice );
  79. for (int i = 0; i < deviceVec.size(); i++) {
  80. deviceVec[i].Debug();
  81. }
  82. return hr;
  • 1
3)查找设备参数
查找设备参数此处需要注意的是,返回的是GUID,GUID需要查找uuids.h来查到对应的定义,比如

  
  
  1. OUR_GUID_ENTRY(MEDIATYPE_Video,
  2. 0x73646976, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71)
  • 1
所以,我简单定义对应关系的函数,可以把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

也就是说,如果想要什么参数,可以从上面几个类型中找。


  
  
  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. int GetMajorType(GUID guid, char* buffer) {
  15. memset(buffer, 0, 256);
  16. if (guid == MEDIATYPE_Video) {
  17. snprintf(buffer, 256, "MEDIATYPE_Video");
  18. return 0;
  19. }
  20. if (guid == MEDIATYPE_Audio) {
  21. snprintf(buffer, 256, "MEDIATYPE_Audio");
  22. return 0;
  23. }
  24. if (guid == MEDIASUBTYPE_RGB24) {
  25. snprintf(buffer, 256, "MEDIATYPE_Stream");
  26. return 0;
  27. }
  28. return -1;
  29. }
  30. int GetSubType(GUID guid, char* buffer) {
  31. memset(buffer, 0, 256);
  32. if( guid == MEDIASUBTYPE_YUY2){
  33. snprintf(buffer, 256, "MEDIASUBTYPE_YUY2");
  34. return 0;
  35. }
  36. if (guid == MEDIASUBTYPE_MJPG) {
  37. snprintf(buffer, 256, "MEDIASUBTYPE_MJPG");
  38. return 0;
  39. }
  40. if (guid == MEDIASUBTYPE_RGB24) {
  41. snprintf(buffer, 256, "MEDIASUBTYPE_RGB24");
  42. return 0;
  43. }
  44. return -1;
  45. }
  46. int GetFormatType(GUID guid, char* buffer) {
  47. memset(buffer, 0, 256);
  48. if (guid == FORMAT_VideoInfo) {
  49. snprintf(buffer, 256, "FORMAT_VideoInfo");
  50. return 0;
  51. }
  52. if (guid == FORMAT_VideoInfo2) {
  53. snprintf(buffer, 256, "FORMAT_VideoInfo2");
  54. return 0;
  55. }
  56. return -1;
  57. }
  58. int DsGetOptionDevice(IMoniker* pMoniker,TDeviceInfo& info) {
  59. USES_CONVERSION;
  60. HRESULT hr = NULL;
  61. IBaseFilter *pFilter;
  62. hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);
  63. if (!pFilter) {
  64. return -1;
  65. }
  66. IEnumPins * pinEnum = NULL;
  67. IPin * pin = NULL;
  68. if (FAILED(pFilter->EnumPins(&pinEnum))) {
  69. pinEnum->Release();
  70. return -1;
  71. }
  72. pinEnum->Reset();
  73. ULONG pinFetched = 0;
  74. while (SUCCEEDED(pinEnum->Next(1, &pin, &pinFetched)) && pinFetched) {
  75. if (!pin) {
  76. continue;
  77. }
  78. PIN_INFO pinInfo;
  79. if (FAILED(pin->QueryPinInfo(&pinInfo))) {
  80. continue;
  81. }
  82. if (pinInfo.dir != PINDIR_OUTPUT) {
  83. continue;
  84. }
  85. #if PRINT_DEBUG
  86. printf("\t[Pin] Dir:Output Name %s\n", W2A(pinInfo.achName));
  87. #endif
  88. IEnumMediaTypes *mtEnum = NULL;
  89. AM_MEDIA_TYPE *mt = NULL;
  90. if (FAILED(pin->EnumMediaTypes(&mtEnum)))
  91. break;
  92. mtEnum->Reset();
  93. ULONG mtFetched = 0;
  94. while (SUCCEEDED(mtEnum->Next(1, &mt, &mtFetched)) && mtFetched) {
  95. char majorbuf[256];
  96. if ( GetMajorType(mt->majortype, majorbuf) != 0) {
  97. GuidToString(mt->majortype, majorbuf);
  98. }
  99. char subtypebuf[256];
  100. if (GetSubType(mt->subtype, subtypebuf) != 0) {
  101. GuidToString(mt->subtype, subtypebuf);
  102. }
  103. char formatbuf[256];
  104. if (GetFormatType(mt->formattype, formatbuf) != 0) {
  105. GuidToString(mt->formattype, formatbuf);
  106. }
  107. #if PRINT_DEBUG
  108. printf("\t%s\t%s\t%s", majorbuf, subtypebuf, formatbuf);
  109. #endif
  110. BITMAPINFOHEADER* bmi = NULL;
  111. int avgTime;
  112. if (mt->formattype == FORMAT_VideoInfo) {
  113. if ( mt->cbFormat >= sizeof(VIDEOINFOHEADER)){
  114. VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>( mt->pbFormat);
  115. bmi = &( pVih->bmiHeader );
  116. avgTime = pVih->AvgTimePerFrame;
  117. }
  118. } else if (mt->formattype == FORMAT_VideoInfo2) {
  119. if (mt->cbFormat >= sizeof(VIDEOINFOHEADER2)) {
  120. VIDEOINFOHEADER2* pVih = reinterpret_cast<VIDEOINFOHEADER2*>(mt->pbFormat);
  121. bmi = &(pVih->bmiHeader);
  122. avgTime = pVih->AvgTimePerFrame;
  123. }
  124. }
  125. if( bmi ){
  126. info.SetResolution(bmi->biWidth, bmi->biHeight, avgTime);
  127. #if PRINT_DEBUG
  128. printf("\t%d * %d, Bit %d\n", bmi->biWidth, bmi->biHeight, bmi->biBitCount);
  129. #endif
  130. }else {
  131. printf("\tNo find\n");
  132. }
  133. }
  134. pin->Release();
  135. }
  136. return 0;
  137. }
  • 1
4)如何调用?

  
  
  1. HRESULT hrrst;
  2. GUID guid = CLSID_VideoInputDeviceCategory;
  3. hrrst = DsGetAudioVideoInputDevices(videoDeviceVec, guid);
  4. guid = CLSID_AudioInputDeviceCategory;
  5. hrrst = DsGetAudioVideoInputDevices(audioDeviceVec, guid);
  • 1
参考:

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

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

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/742525
推荐阅读
相关标签
  

闽ICP备14008679号