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;
}}; // namespaceint 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);elsefprintf(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列表。
以上测试代码执行结果如下:
GitHub:https://github.com/fengbingchun/OpenCV_Test
总结
以上是生活随笔为你收集整理的Linux下通过v4l2获取视频设备名、支持的编解码及视频size列表实现的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 通过libjpeg-turbo实现对jp
- 下一篇: Effective STL 50条有效使