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

早些時候給出了在Windows下通過dshow獲取視頻設備信息的實現,包括獲取視頻設備名、獲取每種視頻設備支持的編解碼格式列表、每種編解碼格式支持的video size列表,見:https://blog.csdn.net/fengbingchun/article/details/102806822

下面給出在Linux下通過v4l2獲取這些信息的實現,代碼如下:

#include "funset.hpp"
#include <string.h>
#include <assert.h>
#include <iostream>
#include <algorithm>
#include <set>

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <dirent.h>

#include "v4l2_common.hpp"

namespace {

//static const __u32 v4l2_pixel_format_map[4] = {875967048, 0, 1196444237, 1448695129};
static const __u32 v4l2_pixel_format_map[] = {V4L2_PIX_FMT_H264, 0, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_YUYV};

int v4l2_is_v4l_dev(const char *name)
{
	return !strncmp(name, "video", 5) ||
		!strncmp(name, "radio", 5) ||
		!strncmp(name, "vbi", 3) ||
		!strncmp(name, "v4l-subdev", 10);
}

int device_open(const char* device_path)
{
	int fd = open(device_path, O_RDWR, 0);
	if (fd < 0) {
		fprintf(stderr, "Error: cannot open video device %s\n", device_path);
		goto fail;
	}

	struct v4l2_capability cap;
	if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
		fprintf(stderr, "Error: cam_info: can't open device: %s\n", device_path);
		goto fail;
	}

	if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
		fprintf(stderr, "Error: Not a video capture device\n");
		goto fail;
	}

	if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        	fprintf(stderr, "Error: The device does not support the streaming I/O method.\n");
		goto fail;
	}

	return fd;

fail:
	close(fd);
	return -1;
}

const struct fmt_map ff_fmt_conversion_table[] = {
	//ff_fmt              codec_id              v4l2_fmt
	{ AV_PIX_FMT_YUV420P, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_YUV420  },
	{ AV_PIX_FMT_YUV420P, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_YVU420  },
	{ AV_PIX_FMT_YUV422P, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_YUV422P },
	{ AV_PIX_FMT_YUYV422, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_YUYV    },
	{ AV_PIX_FMT_UYVY422, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_UYVY    },
	{ AV_PIX_FMT_YUV411P, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_YUV411P },
	{ AV_PIX_FMT_YUV410P, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_YUV410  },
	{ AV_PIX_FMT_YUV410P, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_YVU410  },
	{ AV_PIX_FMT_RGB555LE,AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_RGB555  },
	{ AV_PIX_FMT_RGB555BE,AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_RGB555X },
	{ AV_PIX_FMT_RGB565LE,AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_RGB565  },
	{ AV_PIX_FMT_RGB565BE,AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_RGB565X },
	{ AV_PIX_FMT_BGR24,   AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_BGR24   },
	{ AV_PIX_FMT_RGB24,   AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_RGB24   },
#ifdef V4L2_PIX_FMT_XBGR32
	{ AV_PIX_FMT_BGR0,    AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_XBGR32  },
	{ AV_PIX_FMT_0RGB,    AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_XRGB32  },
	{ AV_PIX_FMT_BGRA,    AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_ABGR32  },
	{ AV_PIX_FMT_ARGB,    AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_ARGB32  },
#endif
	{ AV_PIX_FMT_BGR0,    AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_BGR32   },
	{ AV_PIX_FMT_0RGB,    AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_RGB32   },
	{ AV_PIX_FMT_GRAY8,   AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_GREY    },
#ifdef V4L2_PIX_FMT_Y16
	{ AV_PIX_FMT_GRAY16LE,AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_Y16     },
#endif
	{ AV_PIX_FMT_NV12,    AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_NV12    },
	{ AV_PIX_FMT_NONE,    AV_CODEC_ID_MJPEG,    V4L2_PIX_FMT_MJPEG   },
	{ AV_PIX_FMT_NONE,    AV_CODEC_ID_MJPEG,    V4L2_PIX_FMT_JPEG    },
#ifdef V4L2_PIX_FMT_H264
	{ AV_PIX_FMT_NONE,    AV_CODEC_ID_H264,     V4L2_PIX_FMT_H264    },
#endif
#ifdef V4L2_PIX_FMT_MPEG4
	{ AV_PIX_FMT_NONE,    AV_CODEC_ID_MPEG4,    V4L2_PIX_FMT_MPEG4   },
#endif
#ifdef V4L2_PIX_FMT_CPIA1
	{ AV_PIX_FMT_NONE,    AV_CODEC_ID_CPIA,     V4L2_PIX_FMT_CPIA1   },
#endif
#ifdef V4L2_PIX_FMT_SRGGB8
	{ AV_PIX_FMT_BAYER_BGGR8, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_SBGGR8 },
	{ AV_PIX_FMT_BAYER_GBRG8, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_SGBRG8 },
	{ AV_PIX_FMT_BAYER_GRBG8, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_SGRBG8 },
	{ AV_PIX_FMT_BAYER_RGGB8, AV_CODEC_ID_RAWVIDEO, V4L2_PIX_FMT_SRGGB8 },
#endif
	{ AV_PIX_FMT_NONE,    AV_CODEC_ID_NONE,     0                    },
};

enum AVCodecID ff_fmt_v4l2codec(uint32_t v4l2_fmt)
{
	for (int i = 0; ff_fmt_conversion_table[i].codec_id != AV_CODEC_ID_NONE; i++) {
		if (ff_fmt_conversion_table[i].v4l2_fmt == v4l2_fmt) {
			return ff_fmt_conversion_table[i].codec_id;
		}
	}

	return AV_CODEC_ID_NONE;
}

enum AVPixelFormat ff_fmt_v4l2ff(uint32_t v4l2_fmt, enum AVCodecID codec_id)
{
	for (int i = 0; ff_fmt_conversion_table[i].codec_id != AV_CODEC_ID_NONE; i++) {
		if (ff_fmt_conversion_table[i].v4l2_fmt == v4l2_fmt &&
			ff_fmt_conversion_table[i].codec_id == codec_id) {
			return ff_fmt_conversion_table[i].ff_fmt;
		}
	}

	return AV_PIX_FMT_NONE;
}

}; // namespace

int test_v4l2_get_device_list(std::map<std::string, std::string>& device_list)
{
	device_list.clear();

	const char* dir_name = "/dev";
	DIR* dir = opendir(dir_name);
	if (!dir) {
		fprintf(stderr, "Error: couldn't open the directory: %s\n", dir_name);
		return -1;
	}

	struct dirent* entry = nullptr;
	int fd;

	while ((entry = readdir(dir))) {
		char device_name[256];
		if (!v4l2_is_v4l_dev(entry->d_name))
			continue;
		
		snprintf(device_name, sizeof(device_name), "/dev/%s", entry->d_name);
		if ((fd = device_open(device_name)) < 0)
			continue;

		struct v4l2_capability cap;
		if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
			fprintf(stderr, "Error: cam_info: can't open device: %s\n", device_name);
			goto fail;
		}

		device_list[device_name] = reinterpret_cast<char*>(cap.card);

		close(fd);
		continue;

		fail:
			if (fd >= 0) close(fd);
			break;
	}

	closedir(dir);
	return 0;
}

int test_v4l2_get_codec_type_list(const std::string& device_name, std::vector<int>& codec_list)
{
	codec_list.clear();

	int fd = device_open(device_name.c_str());
	if (fd < 0) {
		fprintf(stderr, "Error: fail to open device: %s\n", device_name.c_str());
		return -1;
	}

	struct v4l2_capability cap;
	if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
		fprintf(stderr, "Error: cam_info: can't open device: %s\n", device_name.c_str());
		return -1;
	}

	struct v4l2_fmtdesc vfd;
	vfd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vfd.index = 0;
	
	while(!ioctl(fd, VIDIOC_ENUM_FMT, &vfd)) {
		enum AVCodecID codec_id = ff_fmt_v4l2codec(vfd.pixelformat);
		enum AVPixelFormat pix_fmt = ff_fmt_v4l2ff(vfd.pixelformat, codec_id);

		vfd.index++;

		if (!(vfd.flags & V4L2_FMT_FLAG_COMPRESSED)) {
			if (pix_fmt != AV_PIX_FMT_NONE)
				codec_list.emplace_back(VIDEO_CODEC_TYPE_RAWVIDEO);
		} else if (vfd.flags & V4L2_FMT_FLAG_COMPRESSED) {
			if (codec_id == AV_CODEC_ID_MJPEG)
				codec_list.emplace_back(VIDEO_CODEC_TYPE_MJPEG);
			else if (codec_id == AV_CODEC_ID_H264)
				codec_list.emplace_back(VIDEO_CODEC_TYPE_H264);
			else if (codec_id == AV_CODEC_ID_H265)
				codec_list.emplace_back(VIDEO_CODEC_TYPE_H265);
			else
				fprintf(stdout, "WARNING: support codec type: %d\n", codec_id);
		}
	}

	std::sort(codec_list.begin(), codec_list.end());
	close(fd);
	return 0;
}

int test_v4l2_get_video_size_list(const std::string& device_name, int codec_type, std::vector<std::string>& size_list)
{
	size_list.clear();
	if (codec_type < 0 || codec_type > 3) return -1;

	int fd = device_open(device_name.c_str());
	if (fd < 0) {
		fprintf(stderr, "Error: fail to open device: %s\n", device_name.c_str());
		return -1;
	}

	struct v4l2_capability cap;
	if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
		fprintf(stderr, "Error: cam_info: can't open device: %s\n", device_name.c_str());
		return -1;
	}

	struct v4l2_frmsizeenum vfse;
	vfse.pixel_format = v4l2_pixel_format_map[codec_type];
	vfse.index = 0;

	std::set<std::vector<unsigned int>> list;
	while(!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &vfse)) {
		switch (vfse.type) {
			case V4L2_FRMSIZE_TYPE_DISCRETE:
			list.insert({vfse.discrete.width, vfse.discrete.height});
			break;
		}
		vfse.index++;
   	}

	for (auto it = list.cbegin(); it != list.cend(); ++it) {
		std::string str = std::to_string((*it)[0]);
		str +="x";
		str += std::to_string((*it)[1]);
		size_list.emplace_back(str);
	}

	close(fd);
	return 0;
}

int test_v4l2_get_video_device_info()
{
	std::map<std::string, std::string> device_list;
	test_v4l2_get_device_list(device_list);
	fprintf(stdout, "device count: %d\n", device_list.size());
	for (auto it = device_list.cbegin(); it != device_list.cend(); ++it) {
		fprintf(stdout, "device name: %s, description: %s\n", (*it).first.c_str(), (*it).second.c_str());

		std::vector<int> codec_list;
		test_v4l2_get_codec_type_list((*it).first, codec_list);
		for (auto it2 = codec_list.cbegin(); it2 != codec_list.cend(); ++it2) {
			fprintf(stdout, " support codec type(0: h264; 1: h265; 2: mjpeg; 3: rawvideo):%d\n", (*it2));

			std::vector<std::string> size_list;
			test_v4l2_get_video_size_list((*it).first, (*it2), size_list);
			fprintf(stdout, "  support video size(width*height):\n");
			for (auto it3 = size_list.cbegin(); it3 != size_list.cend(); ++it3) {
				fprintf(stdout, "   %s\n", (*it3).c_str());
			}
		}
	}

	return 0;
}

以上代碼參考了ffmpeg中libavdevice/v4l2.c的實現。

獲取視頻設備:在Linux下是通過遍歷/dev目錄下的所有文件來查找視頻設備的,遍歷目錄是通過調用opendir和readdir函數,首先找出子目錄名是video、radio、vbi、v4l-subden的目錄。然後通過open函數打開此設備,如果打開成功並且通過調用ioctl函數獲取結構體v4l2_capability內容,判斷其成員capabilities屬於V4L2_CAP_VIDEO_CAPTURE和V4L2_CAP_STREAMING,則最終判斷此名字爲視頻設備。

獲取支持的編解碼格式列表:通過ioctl函數獲取結構體v4l2_fmtdesc內容,主要通過v4l2_fmtdesc中的成員pixelformat來判斷支持的編解碼格式,這裏有個映射,見ff_fmt_v4l2codec函數,即每個v4l2_fmtdesc.pixelformat對應ffmpeg中的AVPixelFormat、AVCodecID。注意:test_v4l2_get_codec_type_list函數中vfd.index=0,如果沒有此語句,在同時存在多個視頻設備時會獲取不到後面設備的編解碼格式列表。

獲取video size列表:通過ioctl函數獲取結構體v4l2_frmsizeenum內容,然後成員discrete.width和discrete.height即爲支持的video size,通過vfse.index++循環獲取支持的列表。注意:test_v4l2_get_video_size_list函數中的vfse.index=0,如果沒有此語句,在同時存在多個視頻設備時會獲取不到後面設備的video size列表。

以上測試代碼執行結果如下:

GitHubhttps://github.com/fengbingchun/OpenCV_Test

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