通过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

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