通過Windows DShow獲取設備名、支持的編解碼及視頻size列表實現

之前在https://blog.csdn.net/fengbingchun/article/details/102641967中介紹過通過DShow獲取Camera視頻的實現,即調用VideoCapture類。在OpenCV的VideoCapture類中並沒有提供獲取Camera設備列表、支持的編解碼類型列表及支持的video size列表接口,這裏基於已有的VideoCapture類增加對這些的實現。

獲取Camera設備名:在videoInput類中有兩個函數,一個是getDeviceCount用於獲取設備數,一個是getDeviceName用於根據索引獲取設備名。在CvCaputreCAM_DShow類中增加函數getDevicesList,實現如下,在此函數中調用videoInput中的getDeviceCount和getDeviceName。

bool CvCaptureCAM_DShow::getDevicesList(std::map<int, std::string>& devicelist) const
{
	devicelist.clear();

	for (int i = 0; i < VI.getDeviceCount(); ++i) {
		devicelist[i] = VI.getDeviceName(i);
	}

	return true;
}

增加一個對外調用的接口get_camera_names,實現如下:

bool FBC_EXPORTS get_camera_names(std::map<int, std::string>& names)
{
	VideoCapture capture(0);
	if (!capture.isOpened()) {
		fprintf(stderr, "fail to open capture\n");
		return false;
	}

	return capture.getDevicesList(names);
}

獲取Camera支持的編解碼類型和video size:這部分的實現參考了FFmpeg中的源碼libavdevice/dshow.c,並把其中的一些code移了過來。首先在videoInput類中增加一個變量infolist,用於存放設備索引名、編解碼類型、視頻size信息;增加一個函數getCodecAndVideoSize,用於實現獲取編解碼和video size;增加一個getCodecList用於供CvCaptureCAM_DShow調用獲取編解碼列表;增加一個getVideoSizeList用於供CvCaptureCAM_DShow調用獲取video size列表。CvCaptureCAM_DShow中也有對應的這兩個函數,用於供VideoCapture調用。它們的實現如下:執行完VD->streamConf->GetStreamCaps後,結構體scc中的MinOutputSize和MaxOutputSize成員變量值即是video size。通過計算結構體pmtConfig中的BITMAPINFOHEADER即可獲取codec type。

void videoInput::getCodecAndVideoSize(videoDevice *VD)
{
	infolist[VD->myID].clear();

	int iCount = 0;
	int iSize = 0;
	HRESULT hr = VD->streamConf->GetNumberOfCapabilities(&iCount, &iSize);
	if (hr != S_OK) {
		fprintf(stderr, "fail to get number of capabilities\n");
		return;
	}

	if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS)) {
		std::map<int, std::map<int, std::set<std::vector<int>>>> info;

		//For each format type RGB24 YUV2 etc
		for (int iFormat = 0; iFormat < iCount; iFormat++) {
			VIDEO_STREAM_CONFIG_CAPS scc;
			AM_MEDIA_TYPE *pmtConfig;

			hr = VD->streamConf->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
			if (SUCCEEDED(hr)){
				//his is how many diff sizes are available for the format
				int stepX = scc.OutputGranularityX;
				int stepY = scc.OutputGranularityY;

				//Don't want to get stuck in a loop
				if (stepX < 1 || stepY < 1) continue;

				std::vector<int> size_min{ scc.MinOutputSize.cx, scc.MinOutputSize.cy }, size_max{ scc.MaxOutputSize.cx, scc.MaxOutputSize.cy };

				VIDEOINFOHEADER* pVih = reinterpret_cast<VIDEOINFOHEADER*>(pmtConfig->pbFormat);
				BITMAPINFOHEADER* bih = &pVih->bmiHeader;
				const AVCodecTag *const tags[] = { avformat_get_riff_video_tags(), NULL };

				enum AVPixelFormat pix_fmt = dshow_pixfmt(bih->biCompression, bih->biBitCount);
				if (pix_fmt == AV_PIX_FMT_NONE) {
					enum AVCodecID codec_id = av_codec_get_id(tags, bih->biCompression);
					if (codec_id != AV_CODEC_ID_NONE) {
						if (codec_id == AV_CODEC_ID_MJPEG) {
							info[VD->myID][VIDEO_CODEC_TYPE_MJPEG].insert(size_min);
							info[VD->myID][VIDEO_CODEC_TYPE_MJPEG].insert(size_max);
						}
						else if (codec_id == AV_CODEC_ID_H264) {
							info[VD->myID][VIDEO_CODEC_TYPE_H264].insert(size_min);
							info[VD->myID][VIDEO_CODEC_TYPE_H264].insert(size_max);
						}
						else if (codec_id == AV_CODEC_ID_H265) {
							info[VD->myID][VIDEO_CODEC_TYPE_H265].insert(size_min);
							info[VD->myID][VIDEO_CODEC_TYPE_H265].insert(size_max);
						}
					}
				}
				else {
					info[VD->myID][VIDEO_CODEC_TYPE_RAWVIDEO].insert(size_min);
					info[VD->myID][VIDEO_CODEC_TYPE_RAWVIDEO].insert(size_max);
				}

				MyDeleteMediaType(pmtConfig);
			}
		}

		for (auto codec_id = 0; codec_id < info[VD->myID].size(); ++codec_id) {
			for (auto it = info[VD->myID][codec_id].cbegin(); it != info[VD->myID][codec_id].cend(); ++it) {
				std::string size = std::to_string((*it)[0]);
				size += "x";
				size += std::to_string((*it)[1]);
				infolist[VD->myID][codec_id].push_back(size);
			}
		}
	}
	else {
		fprintf(stderr, "fail to get codec and video size\n");
		return;
	}
}

bool videoInput::getCodecList(int device_id, std::vector<int>& codecids)
{
	codecids.clear();
	if (infolist[device_id].size() == 0) return false;

	for (auto it = infolist[device_id].cbegin(); it != infolist[device_id].cend(); ++it) {
		codecids.push_back(it->first);
	}

	return true;
}

bool videoInput::getVideoSizeList(int device_id, int codec_id, std::vector<std::string>& sizelist)
{
	if (device_id >= getDeviceCount()) return false;

	sizelist = this->infolist[device_id][codec_id];

	return true;
}

OpenCV中的VideoCapture類中雖然有接收設備名的接口,但是好像並不能運行,只能通過設備索引來獲取視頻。在一臺PC機上插入多個USB攝像頭時並不清楚哪個索引對應哪個設備名,這裏對VideoCapture(const std::string& filename)的實現進行了修改,使其可以通過輸入設備名獲取視頻,實現如下:首先通過索引0獲取到設備列表,然後根據輸入的設備名在設備列表中是否可以映射到對應的索引值,如果有匹配的,則再通過對應的索引值打開設備。

bool VideoCapture::open(const std::string& filename)
{
	if (isOpened()) release();
	open(0);
	if (!cap) return false;

	std::map<int, std::string> devices_name;
	cap->getDevicesList(devices_name);
	if (devices_name.size() == 0) return false;
	if (isOpened()) release();

	int device = -1;
	for (auto it = devices_name.cbegin(); it != devices_name.cend(); ++it) {
		if (filename.compare((*it).second) == 0)
			device = (*it).first;
	}
	if (device == -1) return false;

	return open(device);
}

測試代碼如下:

#include "fbc_cv_funset.hpp"
#include <videocapture.hpp>
#include <opencv2/opencv.hpp>

int test_get_camera_info()
{
#ifdef _MSC_VER
	std::map<int, std::string> camera_names;
	fbc::get_camera_names(camera_names);
	fprintf(stdout, "camera count: %d\n", camera_names.size());
	for (auto it = camera_names.cbegin(); it != camera_names.cend(); ++it) {
		fprintf(stdout, "camera index: %d, name: %s\n", (*it).first, (*it).second.c_str());
	}

	fbc::VideoCapture capture(0);
	if (!capture.isOpened()) {
		fprintf(stderr, "fail to open capture\n");
		return -1;
	}

	std::vector<int> codecids;
	capture.getCodecList(codecids);
	fprintf(stdout, "support codec type(video_codec_type_t: 0: h264; 1: h265; 2: mjpeg; 3: rawvideo): ");
	for (auto i = 0; i < codecids.size(); ++i) {
		fprintf(stdout, "%d  ", codecids[i]);
	}
	fprintf(stdout, "\n");

	std::vector<std::string> sizelist;
	int codec_type = fbc::VIDEO_CODEC_TYPE_MJPEG;
	capture.getVideoSizeList(codec_type, sizelist);
	fprintf(stdout, "codec type: %d, support video size list:(width*height)\n", codec_type);
	for (auto it = sizelist.cbegin(); it != sizelist.cend(); ++it) {
		fprintf(stdout, "\t%s\n", (*it).c_str());
	}

	return 0;
#else
	fprintf(stderr, "Error: only support windows platform\n");
	return -1;
#endif
}

執行結果如下:

GitHubhttps://github.com//fengbingchun/OpenCV_Test

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章