V4L2应用程序开发实战:枚举摄像头所有支持的格式和分辨率
V4L2应用程序开发实战枚举摄像头所有支持的格式和分辨率这节课我们只做一件事用手把手的方式从零写出一个完整的 V4L2 程序它能列出你的摄像头设备所有支持的像素格式比如 YUYV、MJPEG以及每种格式下的所有分辨率比如 640x480、1280x720。我会带你一步步写代码、编译、传到开发板并运行同时讲解每个关键知识点。你不需要有多个摄像头一个就够。写完这个程序你就掌握了 V4L2 中最常用的查询接口以后写任何摄像头应用都心里有底。 这节课你将收获什么技能点重要性说明编写完整的 V4L2 枚举程序⭐⭐⭐ 必须掌握你以后开发摄像头应用第一步几乎都是先查摄像头能力理解VIDIOC_ENUM_FMT和VIDIOC_ENUM_FRAMESIZES⭐⭐⭐ 必须掌握面试常问实际项目必备交叉编译主机 → ARM 开发板⭐⭐⭐ 必须掌握嵌入式开发的标配流程使用 ADB 上传并运行程序⭐⭐ 重点掌握调试嵌入式程序最快的方法调试常见错误权限、设备节点、工具链⭐⭐ 重点掌握实际开发中一定会遇到文章目录V4L2应用程序开发实战枚举摄像头所有支持的格式和分辨率 这节课你将收获什么一、准备工作1.1 你需要什么1.2 验证摄像头设备二、编写完整代码可直接复制三、在 Ubuntu 主机上先本地测试可选四、交叉编译生成开发板能运行的程序4.1 设置工具链路径4.2 交叉编译五、将程序传到开发板并运行5.1 连接 ADB5.2 上传程序5.3 进入开发板 shell 并运行5.4 查看输出六、逐行讲解代码中的关键点面试/考试重点6.1 为什么需要 memset 清零结构体6.2 VIDIOC_ENUM_FMT 的 index 从 0 开始递增6.3 VIDIOC_ENUM_FRAMESIZES 需要传入 pixel_format6.4 fsenum.type 的作用七、常见错误与解决方法八、必须搞懂的 5 个核心问题问题1为什么每次循环都要用 memset 清零问题2VIDIOC_ENUM_FMT 的 index 为什么从 0 开始递增它何时停止问题3为什么 VIDIOC_ENUM_FRAMESIZES 必须放在内层循环问题4分辨率的“离散”和“连续”是什么意思分别怎么处理问题5如果枚举过程中 ioctl 返回 -1 且 errno 不是 EINVAL代表什么九、面试官提问环节必背第1问V4L2 中如何枚举摄像头支持的像素格式请说明使用的 ioctl 和关键数据结构。第2问在枚举完一种格式后如何知道该格式支持哪些分辨率需要用到什么 ioctl为什么这个 ioctl 必须放在格式枚举的循环内部第3问v4l2_frmsizeenum 结构体中的 type 字段有哪些取值分别代表什么第4问枚举过程中ioctl 返回 -1 是否一定表示错误如何正确判断枚举结束第5问为什么要先用 memset 清零 V4L2 结构体不清零会有什么后果第6问请画出或描述枚举所有格式及分辨率的程序流程图。十、总结一、准备工作1.1 你需要什么一台Ubuntu 虚拟机或主机装有交叉编译工具链我们使用百问网 T113 开发板的工具链。一块6ull 开发板已烧录 Tina Linux 系统并通过 USB OTG 线连接到电脑ADB 能识别。一个USB 摄像头或开发板自带的摄像头接口插入开发板后会出现/dev/video0或/dev/video1。如果你还没有交叉编译工具链不用担心我会给出路径和设置方法。1.2 验证摄像头设备在开发板上通过adb shell或串口执行bashls /dev/video*如果显示/dev/video0或/dev/video1说明摄像头已经被驱动识别。二、编写完整代码可直接复制下面是一个完整的 C 程序它会打开指定的视频设备枚举所有格式和分辨率然后打印出来。代码里写了详尽的注释即使你没学过 V4L2也能看懂每一步在做什么。创建一个文件video_enum.cc#include stdio.h #include stdlib.h #include string.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include linux/videodev2.h int main(int argc, char **argv) { int fd; struct v4l2_fmtdesc fmtdesc; struct v4l2_frmsizeenum fsenum; int fmt_index 0; int frame_index; /* 1. 检查命令行参数 */ if (argc ! 2) { fprintf(stderr, Usage: %s video_device\n, argv[0]); fprintf(stderr, Example: %s /dev/video0\n, argv[0]); return 1; } /* 2. 打开设备 */ fd open(argv[1], O_RDWR); if (fd 0) { perror(open); return 1; } printf(Device: %s\n, argv[1]); printf(\n); /* 3. 循环枚举所有像素格式 */ while (1) { memset(fmtdesc, 0, sizeof(fmtdesc)); fmtdesc.index fmt_index; fmtdesc.type V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_ENUM_FMT, fmtdesc) 0) { break; // 枚举结束 } /* 打印格式信息 */ printf(Format %d: %s (fourcc0x%08x)\n, fmt_index, fmtdesc.description, fmtdesc.pixelformat); /* 4. 对于当前格式枚举所有支持的分辨率 */ frame_index 0; while (1) { memset(fsenum, 0, sizeof(fsenum)); fsenum.index frame_index; fsenum.pixel_format fmtdesc.pixelformat; if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, fsenum) 0) { break; // 该格式的分辨率枚举结束 } /* 只处理离散分辨率绝大多数摄像头都是离散的 */ if (fsenum.type V4L2_FRMSIZE_TYPE_DISCRETE) { printf( %d x %d\n, fsenum.discrete.width, fsenum.discrete.height); } else if (fsenum.type V4L2_FRMSIZE_TYPE_STEPWISE) { /* 连续范围极少见简单打印范围 */ printf( continuous: %d..%d x %d..%d\n, fsenum.stepwise.min_width, fsenum.stepwise.max_width, fsenum.stepwise.min_height, fsenum.stepwise.max_height); } frame_index; } fmt_index; printf(\n); // 格式之间空一行 } close(fd); return 0; }代码核心逻辑打开设备文件。用VIDIOC_ENUM_FMT依次查询每种像素格式直到返回错误。对每一种格式用VIDIOC_ENUM_FRAMESIZES查询它支持的分辨率直到返回错误。打印出每种格式的名称、fourcc 码以及所有分辨率。三、在 Ubuntu 主机上先本地测试可选为了确保代码逻辑正确你可以在 Ubuntu 主机上插一个 USB 摄像头或者使用虚拟机里的虚拟摄像头先用本地 gcc 编译测试。bashsudo apt install gcc # 如果没有安装 gcc -o video_enum video_enum.c ./video_enum /dev/video0如果主机上有摄像头你会看到类似输出textDevice: /dev/video0 Format 0: YUYV 4:2:2 (fourcc0x56595559) 640 x 480 800 x 600 1280 x 720 Format 1: MJPEG (fourcc0x47504a4d) 640 x 480 1280 x 720如果主机没有摄像头可以跳过这一步直接交叉编译到开发板上运行。四、交叉编译生成开发板能运行的程序我们使用 T113 开发板的交叉编译工具链。工具链在 Tina-SDK 的prebuilt目录下。4.1 设置工具链路径假设你的 Tina-SDK 放在/home/ubuntu/tina-d1-h则执行bashexport PATH$PATH:/home/ubuntu/tina-d1-h/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/bin为了确认是否设置成功输入basharm-buildroot-linux-gnueabihf-gcc --version应该能看到版本信息。如果没有请检查路径是否正确你的用户名和 SDK 目录可能不同。4.2 交叉编译basharm-buildroot-linux-gnueabihf-gcc -o video_enum video_enum.c编译成功后用file命令查看bashfile video_enum输出应该包含ELF 32-bit LSB executable, ARM说明是 ARM 架构的可执行文件。五、将程序传到开发板并运行5.1 连接 ADB用 USB 线连接开发板的OTG 口到电脑。在 Ubuntu 终端输入bashadb devices如果看到类似20080411 device的设备说明连接成功。如果显示no permissions可以尝试sudo adb kill-server后重新插拔。5.2 上传程序bashadb push video_enum /root/5.3 进入开发板 shell 并运行bashadb shell cd /root chmod x video_enum # 如果没有可执行权限 ./video_enum /dev/video0如果摄像头是video1就改成/dev/video1。5.4 查看输出你应该会看到类似下面的输出根据你的摄像头支持情况可能不同textDevice: /dev/video0 Format 0: YUYV 4:2:2 (fourcc0x56595559) 640 x 480 160 x 120 320 x 240 352 x 288 800 x 600 1280 x 720 1280 x 1024 Format 1: Motion-JPEG (fourcc0x47504a4d) 640 x 480 160 x 120 320 x 240 352 x 288 800 x 600 1280 x 720 1280 x 1024恭喜你已经成功列出了摄像头的所有能力。六、逐行讲解代码中的关键点面试/考试重点6.1 为什么需要memset清零结构体cmemset(fmtdesc, 0, sizeof(fmtdesc));V4L2 的结构体中有很多保留字段内核要求应用程序把它们初始化为 0否则 ioctl 可能失败或行为异常。这是一个好习惯。6.2VIDIOC_ENUM_FMT的 index 从 0 开始递增内核维护了一个格式列表你每次传入一个 index它返回第 index 个格式的信息。当传入的 index 超过最后一个时ioctl 返回 -1我们跳出循环。6.3VIDIOC_ENUM_FRAMESIZES需要传入pixel_format只有先知道格式才能查询该格式的分辨率。这也是为什么要在内层循环中的原因。6.4fsenum.type的作用V4L2_FRMSIZE_TYPE_DISCRETE离散分辨率直接读取discrete.width/height。V4L2_FRMSIZE_TYPE_STEPWISE连续范围如最小 32x32 到最大 1920x1080步长 8。这种摄像头非常少见我们简单打印范围即可。七、常见错误与解决方法错误现象原因解决办法open: Permission denied普通用户无权限访问设备开发板上用 root 用户adb shell 默认 root或chmod 666 /dev/video0VIDIOC_ENUM_FMT: Invalid argument传入了错误的type值确保type V4L2_BUF_TYPE_VIDEO_CAPTURE交叉编译时command not foundPATH 未设置重新执行export PATH...并确认路径正确ADB 推文件失败device offlineUSB 线松动或驱动没加载拔插 USB 线重新运行adb devices开发板执行后没有任何输出摄像头设备节点可能不是 video0先用ls /dev/video*查看用正确的节点名八、必须搞懂的 5 个核心问题问题1为什么每次循环都要用memset清零答V4L2 的结构体中包含reserved保留字段。内核要求这些字段必须为 0否则可能被视为要求启用尚未定义的功能导致 ioctl 失败或行为异常。清零是一个好习惯避免栈上的随机值污染。问题2VIDIOC_ENUM_FMT的index为什么从 0 开始递增它何时停止答内核内部维护一个格式的列表index就是数组下标。从 0 开始每成功一次就加 1直到内核返回 -1且errno为EINVAL。这表示已经枚举完所有格式。这种模式在 V4L2 中非常普遍枚举帧率、控制项等也类似。问题3为什么VIDIOC_ENUM_FRAMESIZES必须放在内层循环答该 ioctl 需要输入参数pixel_format即具体的格式 fourcc。这个 fourcc 只能从外层VIDIOC_ENUM_FMT获得。没有外层枚举你无法知道要查哪种格式的分辨率。因此它必须嵌套在格式循环内部。问题4分辨率的“离散”和“连续”是什么意思分别怎么处理答离散摄像头只支持固定的几个分辨率如 640x480、1280x720。直接读取fsenum.discrete.width/height。连续摄像头支持一段范围内的任何分辨率并给出步长。例如最小 16x16最大 1920x1080步长 8x8。应用程序可以选择任意满足步长倍数的分辨率。此类摄像头极少见通常出现在工业领域。问题5如果枚举过程中 ioctl 返回 -1 且errno不是EINVAL代表什么答真正的硬件错误或驱动异常。例如EIOI/O 错误、ENOMEM内存不足。在这种情况下不应继续循环应打印错误并退出。九、面试官提问环节必背第1问V4L2 中如何枚举摄像头支持的像素格式请说明使用的 ioctl 和关键数据结构。参考答案使用VIDIOC_ENUM_FMT命令配合结构体struct v4l2_fmtdesc。设置fmtdesc.type V4L2_BUF_TYPE_VIDEO_CAPTURE然后从index 0开始递增调用ioctl直到返回 -1。每次成功时fmtdesc.pixelformat是格式的 FourCC 码fmtdesc.description是可读字符串。第2问在枚举完一种格式后如何知道该格式支持哪些分辨率需要用到什么 ioctl为什么这个 ioctl 必须放在格式枚举的循环内部参考答案使用VIDIOC_ENUM_FRAMESIZES结构体为struct v4l2_frmsizeenum。必须传入fsenum.pixel_format即从格式枚举得到的 FourCC所以必须在格式枚举的内层循环中调用。内核根据该 FourCC 返回该格式支持的分辨率列表。第3问v4l2_frmsizeenum结构体中的type字段有哪些取值分别代表什么参考答案V4L2_FRMSIZE_TYPE_DISCRETE摄像头支持离散的固定分辨率分辨率信息在fsenum.discrete中。V4L2_FRMSIZE_TYPE_STEPWISE摄像头支持连续范围分辨率信息在fsenum.stepwise中包括最小、最大和步长。绝大多数消费级摄像头都是前一种。第4问枚举过程中ioctl返回 -1 是否一定表示错误如何正确判断枚举结束参考答案不是。当index超过最后一个条目时ioctl返回 -1 并且errno被设置为EINVAL这是正常的枚举结束。真正的错误应检查errno是否为其他值如EIO、ENOMEM。简单示例中通常只判断返回值 0 就退出严谨的程序应区分EINVAL和真实错误。第5问为什么要先用memset清零 V4L2 结构体不清零会有什么后果参考答案V4L2 结构体中有reserved保留字段内核要求这些字段必须为 0。如果不清零栈上的随机值会被内核读取可能被解释为尚未定义的扩展参数导致 ioctl 失败或产生不可预知的行为。清零是一种防御性编程确保兼容性。第6问请画出或描述枚举所有格式及分辨率的程序流程图。参考答案text开始 │ ├─ 打开 /dev/videoX │ ├─ fmt_index 0 │ ├─ while (1) { │ └─ 调用 ioctl( VIDIOC_ENUM_FMT ) │ ├─ 失败 → 跳出外层循环 │ └─ 成功 → 打印格式信息 │ frame_index 0 │ while (1) { │ 调用 ioctl( VIDIOC_ENUM_FRAMESIZES ) │ ├─ 失败 → 跳出内层循环 │ └─ 成功 → 打印分辨率 │ frame_index │ } │ fmt_index │ } │ └─ 关闭设备十、总结这一节我们完成了一个非常实用的 V4L2 程序它不采集图像却能帮你彻底摸清摄像头的“底细”。在真实项目中你会经常先运行类似程序来确认摄像头能力然后再写采集代码。你必须掌握的核心VIDIOC_ENUM_FMT和VIDIOC_ENUM_FRAMESIZES的用法。交叉编译的完整流程设置 PATH、编译、file 验证。ADB 部署与运行。下次当你拿到一个新的摄像头时别忘了先运行video_enum /dev/videoX看看它到底支持什么。 你已经迈出了 V4L2 应用开发的一大步下一个目标写一个可以实时显示摄像头画面的程序结合 LCD 显示。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2588127.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!