嵌入式linux相机(1)
本人从0开始学习linux,使用的是韦东山的教程,在跟着课程学习的情况下的所遇到的问题的总结,理论虽枯燥但是是基础。本人将前几章的内容大致学完之后,考虑到后续驱动方面得更多的开始实操,后续的内容将以韦东山教程Linux项目的内容为主,学习其中的代码并手敲。做到锻炼动手能力的同时钻研其中的理论知识点。
摘要:相机部分代码的理解
摘要关键词:相机驱动
第一个项目相关使用的命令行
bear make
ctrl+h
arm-buildroot-linux-gnueabihf-gcc
adb push video2lcd root
ls /dev/video*
mv /etc/init.d/S99myirhmi2 /root
mv /etc/init.d/S05lvgl /root
./video2lcd /dev/video1
这个项目主要是测试在屏幕上实时显示摄像头的数据。
本人出现了没有文件内容但是不影响。
实验测试效果,摄像头我是买的别的厂家的比较便宜,只要支持ubuntu,linux驱动免安装就行。
实验二相关的命令行。
bear make
ctrl+h
arm-buildroot-linux-gnueabihf-gcc
adb push video2lcd root
ls /dev/video*
mv /etc/init.d/S99myirhmi2 /root
mv /etc/init.d/S05lvgl /root
./lv_100ask_camera_demo /dev/video1
第二个项目和第一个类似,只是加了一点拍照功能。
补充实验
这个实验主要是测试摄像头所支持的格式,以及分辨率大小。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <stdio.h>
#include <string.h>
/* ./video_test </dev/video0> */int main(int argc, char **agrv)
{int fd;struct v4l2_fmtdesc fmtdesc;int fmt_index = 0;int frame_index = 0;struct v4l2_frmsizeenum fsenum;if (argc != 2){printf("Usage: %s </dev/videoX>,print detail for video device\n",agrv[0]);return -1;}/* open */fd = open(agrv[1],O_RDWR);if (fd < 0){printf("can not open %s \n",agrv[1]);return -1;}/* */while(1){/* 枚举格式 *//* 枚举这种格式所支持的帧大小*/fmtdesc.index = fmt_index; // 比如从0开始fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获"if(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != 0) break;frame_index = 0;while(1){//枚举所支持的帧大小memset(&fsenum,0,sizeof(struct v4l2_frmsizeenum));fsenum.pixel_format = fmtdesc.pixelformat;fsenum.index = frame_index;if(ioctl(fd,VIDIOC_ENUM_FRAMESIZES,&fsenum) == 0){printf("format %s %d ,framesize %d x %d \n",fmtdesc.description,fmtdesc.pixelformat,fsenum.discrete.width,fsenum.discrete.height);}else break;frame_index++;}fmt_index++;}return 0;
}
整体代码。
int main(int argc, char **agrv)
{int fd;struct v4l2_fmtdesc fmtdesc;int fmt_index = 0;int frame_index = 0;struct v4l2_frmsizeenum fsenum;
fd: 文件描述符,用于操作视频设备
fmtdesc: V4L2格式描述结构体
fmt_index: 格式索引,用于枚举所有支持的格式
frame_index: 帧大小索引,用于枚举特定格式支持的所有分辨率
fsenum: V4L2帧大小枚举结构体
这段代码中最关键的就是那两个结构体,这两个结构体是Video for Linux 2 (V4L2) API 的一部分,用于枚举(列出)视频设备(如摄像头、采集卡)所支持的视频数据格式,枚举某个特定像素格式(Pixel Format)所支持的所有帧尺寸(分辨率)。
当你编写一个程序想要从摄像头获取视频时,你需要告诉驱动程序你希望以什么样的格式接收数据(例如,是压缩的 MJPEG 还是未压缩的 YUYV,或者是 H.264?)。不同的设备支持不同的格式。
这个结构体就是用来查询设备到底支持哪些格式的。你通过一个循环,不断询问驱动程序“第 0 个格式是什么?”、“第 1 个格式是什么?”… 直到驱动程序返回一个错误(表示没有更多格式了),从而获得所有支持的格式列表。然后你选中一个格式(比如 MJPEG),再用 v4l2_frmsizeenum去查询这个格式具体支持哪些分辨率(比如 1920x1080, 1280x720, 640x480)
fmtdesc index:用途: 你要查询的格式的序号。
用法: 你从 0 开始,依次递增这个值去调用 ioctl,直到获取失败(即枚举完了所有支持的格式)。
fmtdesc type:用途: 指定你要枚举哪种类型的缓冲区的格式。
用法: 对于最常见的从摄像头捕获视频的情况,你会使用 V4L2_BUF_TYPE_VIDEO_CAPTURE。它还可以是其他类型,如 V4L2_BUF_TYPE_VIDEO_OUTPUT(用于输出)。
fsenum. index:用途: 要查询的帧尺寸的序号。
用法: 和 v4l2_fmtdesc 的 index 一样,从 0 开始递增,直到枚举完所有支持的分辨率。
fsenum. pixel_format: 用途: 这是最关键的联系字段。指定你要查询哪个格式所支持的分辨率。
用法: 这里你要填入之前用 v4l2_fmtdesc 枚举得到的某个格式的 pixelformat 值(例如 V4L2_PIX_FMT_MJPEG)。你必须先知道格式,才能查询其分辨率。
while(1){/* 枚举格式 */fmtdesc.index = fmt_index; // 比如从0开始fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获"if(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != 0) break;
枚举视频格式:
设置格式索引和类型(视频捕获)
使用VIDIOC_ENUM_FMT ioctl命令枚举设备支持的格式
如果枚举失败(返回非0),跳出循环
详细展开说一下工作步骤:
a. 查找文件描述符对应的驱动
内核根据您提供的文件描述符 fd,找到您之前打开的 /dev/video0 这个设备文件。
这个设备文件在内核中关联着一个特定的设备驱动程序——就是控制您摄像头硬件的那个 V4L2 驱动。
b. 派遣 ioctl 命令
内核发现这是一个 ioctl 调用,于是它会找到该设备驱动程序中实现的 ioctl 函数接口(通常是一个包含大量 switch-case 语句的函数),并将控制权交给它。
内核同时将 VIDIOC_ENUM_FMT 命令和您传递的用户空间内存地址 &fmtdesc 也交给驱动。
c. 驱动处理命令
驱动程序的 ioctl 函数里有一个很大的 switch (cmd) 语句,它会检查收到的命令是什么。
当它看到 cmd == VIDIOC_ENUM_FMT 时,它就明白:“啊,用户想要枚举支持的格式”。
驱动知道,对于这个命令,用户传递的参数是一个 struct v4l2_fmtdesc 类型结构的地址。
d. 安全地访问用户内存
驱动程序现在处于内核态,不能直接解引用您提供的用户空间指针 &fmtdesc,因为那是属于另一个地址空间(用户进程)的内存,直接访问会导致内核崩溃或安全漏洞。
内核会使用像 copy_from_user() 这样的函数,安全地将您填充好的 fmtdesc 结构体从用户空间复制到内核空间的一个临时副本中。这样驱动就可以安全地读取您的 index 和 type 了。
e. 执行请求的操作
驱动程序根据您传来的 index 和 type,在其内部的数据结构(通常是一个支持的格式列表)中查找第 index 个捕获格式。
找到后,驱动程序会填充那个内核空间的临时结构体副本:它将格式的 FourCC 码写入 pixelformat,将描述字符串写入 description,设置好 flags,等等。
f. 返回结果给用户
填充完成后,驱动程序再使用像 copy_to_user() 这样的函数,将已经填充好信息的结构体内核副本,安全地复制回您用户空间原来那个 fmtdesc 结构体所在的内存地址。
驱动程序的 ioctl 处理函数返回成功(0)或错误(-1)。
所以就能实现-1结束,0继续往下走。
frame_index = 0;
while(1){//枚举所支持的帧大小memset(&fsenum,0,sizeof(struct v4l2_frmsizeenum));fsenum.pixel_format = fmtdesc.pixelformat;fsenum.index = frame_index;
枚举帧大小:
重置帧索引为0
清空帧大小枚举结构体
设置像素格式和帧索引
其实我挺好奇的fsenum.pixel_format = fmtdesc.pixelformat;这个变量,但是这个变量并没有给值,那他有什么用呢? 后来问了一下D老师,确实被赋予了值,但这个赋值不是通过直接的赋值语句完成的,而是通过 ioctl 调用间接完成的。
当这个 ioctl 调用成功时,内核视频驱动程序会填充 fmtdesc 结构体的所有字段,包括 pixelformat。这就是为什么在内层循环中可以直接使用 fmtdesc.pixelformat 的原因。这个调用告诉内核:“请给我第0个视频捕获格式的信息”。
if(ioctl(fd,VIDIOC_ENUM_FRAMESIZES,&fsenum) == 0){printf("format %s %d ,framesize %d x %d \n",fmtdesc.description,fmtdesc.pixelformat,fsenum.discrete.width,fsenum.discrete.height);}else break;frame_index++;
获取并打印帧大小信息:
使用VIDIOC_ENUM_FRAMESIZES ioctl命令获取特定格式支持的帧大小
如果成功,打印格式描述、像素格式值和分辨率
如果失败,跳出内部循环
增加帧索引以枚举下一个分辨率
工作原理
设备打开:程序打开指定的视频设备文件(如/dev/video0)
格式枚举:通过循环调用VIDIOC_ENUM_FMT ioctl命令,枚举设备支持的所有视频格式
分辨率枚举:对于每个支持的格式,通过循环调用VIDIOC_ENUM_FRAMESIZES ioctl命令,枚举该格式支持的所有分辨率
信息输出:将枚举到的格式信息和分辨率信息打印到控制台
循环终止:当所有格式和分辨率都枚举完毕后,ioctl调用会返回错误,循环终止
外层循环次数 = 摄像头支持的格式数量
内层循环次数(对于每种格式)= 该格式支持的分辨率数量
book@100ask:~/test/03_video_params$ arm-buildroot-linux-gnueabihf-gcc -o video_test video_test.c
book@100ask:~/test/03_video_params$ adb push video_test root
video_test: 1 file pushed. 0.5 MB/s (8488 bytes in 0.016s)
book@100ask:~/test/03_video_params$ ./video_test /dev/video1
最终效果