早些時候給出了在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列表。
以上測試代碼執行結果如下: