1. 程式人生 > >Windows下用DirectShow查詢攝像頭(含解析度)和麥克風

Windows下用DirectShow查詢攝像頭(含解析度)和麥克風

        在視訊聊天、視訊會議、線上監控和視訊展臺等專案中,需要查找出本地電腦上連線的所有攝像頭,網上流傳比較多的方式是ffmpeg的方式,這種方式可以跨平臺,不同的平臺下呼叫不同的庫。這種方式在控制檯直接列印了攝像頭的資訊,無法(或者說我暫時沒找到)在記憶體中獲取,因此直接採用了DirectShow的方式,DirectShow列舉IMoniker和Ipin。因為網上的文件,不是特別詳盡,所以我寫了本文,我儘量解釋清楚,分段貼出部分程式碼,主要是要看明白並且理解,通過本文中的方式,基本可以列出電腦上的攝像頭和麥克風,以及他們的引數。

1、用ffmpeg的方式

1)ffmpeg功能強大,關於ffmpeg的詳細文件,可以去官網看看:

http://ffmpeg.org/

2)關於ffmpeg支援的裝置列表,可以參考下面的連結:

3)這種方式比較簡單,其實在Widows下還是呼叫dshow,

直接傳入“list_devices”,列出裝置列表。先看看命令列方式。

ffmpeg -list_devices true -f dshow -i dummy
上面的命令列和下面的程式碼是一個效果,看看命令列的引數和下面的程式碼的幾個引數,是不是一樣?

所以啊,如果看到命令列的例子,在寫程式碼呼叫介面時可以參考他。

//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)上面不是列出了裝置名嗎?那麼把裝置名傳入下面的函式,就可以列出該裝置支援的解析度等資訊
//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...
//裝置引數
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
//根據裝置最好的引數排序
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來查到對應的定義,比如
OUR_GUID_ENTRY(MEDIATYPE_Video,
0x73646976, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71)
所以,我簡單定義對應關係的函式,可以把GUID對映為友好的名稱。 另外,IPin->AM_MEDIA_TYPE->VIDEOINFOHEADER->BITMAPINFOHEADER https://msdn.microsoft.com/en-us/library/windows/desktop/dd373477(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/windows/desktop/dd407325(v=vs.85).aspx

https://msdn.microsoft.com/en-us/library/windows/desktop/dd318229(v=vs.85).aspx

也就是說,如果想要什麼引數,可以從上面幾個型別中找。

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)如何呼叫?
	HRESULT hrrst;
	GUID guid = CLSID_VideoInputDeviceCategory;
	hrrst = DsGetAudioVideoInputDevices(videoDeviceVec, guid);
	guid = CLSID_AudioInputDeviceCategory;
	hrrst = DsGetAudioVideoInputDevices(audioDeviceVec, guid);
參考: