当前位置: 首页 > news >正文

V4L2应用程序开发实战:枚举摄像头所有支持的格式和分辨率

V4L2应用程序开发实战:枚举摄像头所有支持的格式和分辨率

这节课我们只做一件事:用手把手的方式,从零写出一个完整的 V4L2 程序,它能列出你的摄像头设备所有支持的像素格式(比如 YUYV、MJPEG)以及每种格式下的所有分辨率(比如 640x480、1280x720)。
我会带你一步步写代码、编译、传到开发板并运行,同时讲解每个关键知识点。你不需要有多个摄像头,一个就够。写完这个程序,你就掌握了 V4L2 中最常用的查询接口,以后写任何摄像头应用都心里有底。


🎯 这节课你将收获什么?

技能点重要性说明
编写完整的 V4L2 枚举程序⭐⭐⭐ 必须掌握你以后开发摄像头应用,第一步几乎都是先查摄像头能力
理解VIDIOC_ENUM_FMTVIDIOC_ENUM_FRAMESIZES⭐⭐⭐ 必须掌握面试常问,实际项目必备
交叉编译(主机 → ARM 开发板)⭐⭐⭐ 必须掌握嵌入式开发的标配流程
使用 ADB 上传并运行程序⭐⭐ 重点掌握调试嵌入式程序最快的方法
调试常见错误(权限、设备节点、工具链)⭐⭐ 重点掌握实际开发中一定会遇到

文章目录

  • V4L2应用程序开发实战:枚举摄像头所有支持的格式和分辨率
    • 🎯 这节课你将收获什么?
    • 一、准备工作
      • 1.1 你需要什么?
      • 1.2 验证摄像头设备
    • 二、编写完整代码(可直接复制)
    • 三、在 Ubuntu 主机上先本地测试(可选)
    • 四、交叉编译:生成开发板能运行的程序
      • 4.1 设置工具链路径
      • 4.2 交叉编译
    • 五、将程序传到开发板并运行
      • 5.1 连接 ADB
      • 5.2 上传程序
      • 5.3 进入开发板 shell 并运行
      • 5.4 查看输出
    • 六、逐行讲解代码中的关键点(面试/考试重点)
      • 6.1 为什么需要 `memset` 清零结构体?
      • 6.2 `VIDIOC_ENUM_FMT` 的 index 从 0 开始递增
      • 6.3 `VIDIOC_ENUM_FRAMESIZES` 需要传入 `pixel_format`
      • 6.4 `fsenum.type` 的作用
    • 七、常见错误与解决方法
    • 八、必须搞懂的 5 个核心问题
      • 问题1:为什么每次循环都要用 `memset` 清零?
      • 问题2:`VIDIOC_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或串口)执行:

bash

ls /dev/video*

如果显示/dev/video0/dev/video1,说明摄像头已经被驱动识别。


二、编写完整代码(可直接复制)

下面是一个完整的 C 程序,它会打开指定的视频设备,枚举所有格式和分辨率,然后打印出来。代码里写了详尽的注释,即使你没学过 V4L2,也能看懂每一步在做什么。

创建一个文件video_enum.c

c

#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 (fourcc=0x%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; }

代码核心逻辑

  1. 打开设备文件。
  2. VIDIOC_ENUM_FMT依次查询每种像素格式,直到返回错误。
  3. 对每一种格式,用VIDIOC_ENUM_FRAMESIZES查询它支持的分辨率,直到返回错误。
  4. 打印出每种格式的名称、fourcc 码以及所有分辨率。

三、在 Ubuntu 主机上先本地测试(可选)

为了确保代码逻辑正确,你可以在 Ubuntu 主机上插一个 USB 摄像头(或者使用虚拟机里的虚拟摄像头),先用本地 gcc 编译测试。

bash

sudo apt install gcc # 如果没有安装 gcc -o video_enum video_enum.c ./video_enum /dev/video0

如果主机上有摄像头,你会看到类似输出:

text

Device: /dev/video0 ======================================== Format 0: YUYV 4:2:2 (fourcc=0x56595559) 640 x 480 800 x 600 1280 x 720 Format 1: MJPEG (fourcc=0x47504a4d) 640 x 480 1280 x 720

如果主机没有摄像头,可以跳过这一步,直接交叉编译到开发板上运行。


四、交叉编译:生成开发板能运行的程序

我们使用 T113 开发板的交叉编译工具链。工具链在 Tina-SDK 的prebuilt目录下。

4.1 设置工具链路径

假设你的 Tina-SDK 放在/home/ubuntu/tina-d1-h,则执行:

bash

export PATH=$PATH:/home/ubuntu/tina-d1-h/prebuilt/gcc/linux-x86/arm/toolchain-sunxi-musl/toolchain/bin

为了确认是否设置成功,输入:

bash

arm-buildroot-linux-gnueabihf-gcc --version

应该能看到版本信息。如果没有,请检查路径是否正确(你的用户名和 SDK 目录可能不同)。

4.2 交叉编译

bash

arm-buildroot-linux-gnueabihf-gcc -o video_enum video_enum.c

编译成功后,用file命令查看:

bash

file video_enum

输出应该包含ELF 32-bit LSB executable, ARM,说明是 ARM 架构的可执行文件。


五、将程序传到开发板并运行

5.1 连接 ADB

  • 用 USB 线连接开发板的OTG 口到电脑。
  • 在 Ubuntu 终端输入:

bash

adb devices

如果看到类似20080411 device的设备,说明连接成功。
如果显示no permissions,可以尝试sudo adb kill-server后重新插拔。

5.2 上传程序

bash

adb push video_enum /root/

5.3 进入开发板 shell 并运行

bash

adb shell cd /root chmod +x video_enum # 如果没有可执行权限 ./video_enum /dev/video0

如果摄像头是video1,就改成/dev/video1

5.4 查看输出

你应该会看到类似下面的输出(根据你的摄像头支持情况可能不同):

text

Device: /dev/video0 ======================================== Format 0: YUYV 4:2:2 (fourcc=0x56595559) 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 (fourcc=0x47504a4d) 640 x 480 160 x 120 320 x 240 352 x 288 800 x 600 1280 x 720 1280 x 1024

恭喜!你已经成功列出了摄像头的所有能力。


六、逐行讲解代码中的关键点(面试/考试重点)

6.1 为什么需要memset清零结构体?

c

memset(&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/video0
VIDIOC_ENUM_FMT: Invalid argument传入了错误的type确保type = V4L2_BUF_TYPE_VIDEO_CAPTURE
交叉编译时command not foundPATH 未设置重新执行export PATH=...,并确认路径正确
ADB 推文件失败(device offline)USB 线松动或驱动没加载拔插 USB 线,重新运行adb devices
开发板执行后没有任何输出摄像头设备节点可能不是 video0先用ls /dev/video*查看,用正确的节点名

八、必须搞懂的 5 个核心问题

问题1:为什么每次循环都要用memset清零?

:V4L2 的结构体中包含reserved保留字段。内核要求这些字段必须为 0,否则可能被视为要求启用尚未定义的功能,导致 ioctl 失败或行为异常。清零是一个好习惯,避免栈上的随机值污染。

问题2:VIDIOC_ENUM_FMTindex为什么从 0 开始递增?它何时停止?

:内核内部维护一个格式的列表,index就是数组下标。从 0 开始,每成功一次就加 1,直到内核返回 -1(且errnoEINVAL)。这表示已经枚举完所有格式。这种模式在 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,代表什么?

:真正的硬件错误或驱动异常。例如EIO(I/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是否为其他值(如EIOENOMEM)。简单示例中通常只判断返回值 < 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_FMTVIDIOC_ENUM_FRAMESIZES的用法。
  • 交叉编译的完整流程(设置 PATH、编译、file 验证)。
  • ADB 部署与运行。

下次当你拿到一个新的摄像头时,别忘了先运行video_enum /dev/videoX,看看它到底支持什么。

🚀 你已经迈出了 V4L2 应用开发的一大步!下一个目标:写一个可以实时显示摄像头画面的程序(结合 LCD 显示)。

http://www.jsqmd.com/news/763396/

相关文章:

  • 哈尔滨区域厂房搭建实力商家排行:5家硬核企业盘点 - 奔跑123
  • 旅游高倍防水防晒霜,夏日出游7款高倍防晒巨靠谱 - 全网最美
  • 2026年江苏面粉加工设备与豆类脱皮机选购指南——源头厂家直供方案对标 - 年度推荐企业名录
  • 黄金变现就现在!抚州福正美上门高价秒结 - 福正美黄金回收
  • Yo‘City:基于多智能体的3D城市动态生成框架解析
  • Win11开发者新姿势:把WSL2变成你的专属局域网开发服务器,支持SSH和SFTP(含防火墙与端口转发详解)
  • Navicat Premium 12 永久使用办法
  • 黄金变现就现在!保定福正美上门高价秒结 - 福正美黄金回收
  • 别再只盯着代码了!用SkyEye仿真ARINC429总线,手把手搭建飞控襟翼测试环境
  • 2026年山东断桥铝门窗与系统阳光房选购指南:峰睿门窗等五大品牌深度横评 - 年度推荐企业名录
  • 黄金变现就现在!广州福正美上门高价秒结 - 福正美黄金回收
  • 2026年山西精准获客、太原短视频代运营与晋中手机号定向推广深度指南:如何用新思域科技破解中小企业获客成本高、转化效率低的困局 - 企业名录优选推荐
  • 如何快速将图像转为C代码?image_to_c工具的完整使用指南
  • 终极网盘直链下载解决方案:告别限速,轻松获取9大平台高速下载链接
  • 高价引流见面砍?常德福正美偏要报价即到手价 - 福正美黄金回收
  • 不会晒黑的防晒霜推荐,一用就惊艳!4款防晒透亮到哭 - 全网最美
  • Nerve:轻量级服务感知探针,统一监控HTTP/TCP/命令检查
  • 2026年电池测试箱品牌Top10深研:为何宾德、爱斯佩克、热测与本土力量值得关注? - 品牌推荐大师1
  • BetterNCM安装器:3个步骤解锁网易云音乐隐藏潜力
  • 2026年江苏面粉加工设备采购指南:5大品牌深度横评与源头厂家直供方案 - 年度推荐企业名录
  • 基于AI多因子模型的黄金价格回升分析:避险情绪扰动与美元回落下的结构性修复
  • Go 切片核心:子切片详解(下篇)
  • 为Cursor AI助手集成本地语音输入:基于Whisper与WebGPU的离线语音识别方案
  • 安全回收携程卡,为什么大家都选喵权益? - 喵权益卡劵助手
  • 钢管护帽采购指南:华蒴在管道包装、汽轮电机的保护应用观察 - 品牌推荐大师
  • React自定义光标库use-custom-cursor:从原理到实战的完整指南
  • 深入解析admineral/Reactor:事件驱动自动化引擎的设计与实战
  • 2026年山东断桥铝门窗与系统阳光房定制指南:隔音隔热防水防盗全解 - 年度推荐企业名录
  • C2H编译器技术:硬件加速器自动生成与优化实践
  • 5步轻松实现微信聊天记录永久保存:WeChatMsg完整免费解决方案