Linux嵌入式相机 — 项目总结

main函数执行流程

  • 1、初始化触摸屏

    Touch_screen_Init();
    
    struct tsdev *ts = NULL;
    ts = ts_setup(NULL, 0);    //以阻塞打开
    
  • 2、初始化 LCD

    LCD_Init(void);   
    

    通过 ioctl 函数获取 LCD 的固定参数、可变参数,得到分辨率、bpp、一行的长度(以字节为单位),将显存映射到内存上,得到 framebuffer 的首地址,使用 memset 函数将这块区域全部设置为 1,即将LCD设置为白色背景

    struct fb_var_screeninfo var;   /* Current var */
    struct fb_fix_screeninfo fix;   /* Current fix */
    ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);//获取屏幕可变信息
    ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix);//获取屏幕固定信息
    ...
    unsigned char* fbbase;
    fbbase = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);   // 映射 screen_size:整块显存的大小
    memset(fbbase, 0xFF, screem_size);
    
  • 3、打开摄像头

    int fd = open("/dev/video1", O_RDWR);
    
  • 4、将背景图片1(带有拍照、相册按钮)显示在 LCD 上

    LCD_Show_JPEG(background1);
    

    这个图片的分辨率是 1024 * 600(板子的屏幕的分辨率也是 1024 * 600)

  • 5、将图片放入相册

    将指定目录(/home/)中已有的图片(.jpg格式)加入双向链表中,image_count 为目前图片名称最大索引

    image_count = xiangce_Init();
    
  • 6、遍历链表

    jpeg_list_printf(void)
    

    遍历双向链表(不包含虚拟头节点),打印所有照片的名字

  • 7、设置摄像头的采集格式

    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//选择视频抓取
    // 一定是 800 * 600(600等于LCD的高度,8800于LCD的宽度是为了显示相机的按钮)
    vfmt.fmt.pix.width  = 800;//设置宽
    vfmt.fmt.pix.height = 600;//设置高
    vfmt.fmt.pix.pixelformat =  V4L2_PIX_FMT_MJPEG;//设置视频采集像素格式int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);// VIDIOC_S_FMT:设置捕获格式
    if(ret < 0)
    {perror("设置采集格式错误");
    }
    // 判断是否设置成功
    memset(&vfmt, 0, sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);	
    if(ret < 0)
    {perror("读取采集格式失败");
    }
    printf("设置分辨率width = %d\n", vfmt.fmt.pix.width);
    printf("设置分辨率height = %d\n", vfmt.fmt.pix.height);
    unsigned char *p = (unsigned char*)&vfmt.fmt.pix.pixelformat;
    printf("pixelformat = %c%c%c%c\n", p[0],p[1],p[2],p[3]);
    
  • 8、申请缓冲队列

    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count  = 4;	//申请4个缓冲区
    reqbuffer.memory = V4L2_MEMORY_MMAP;	//采用内存映射的方式
    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
    if(ret < 0)
    {perror("申请缓冲队列失败");
    }
    
  • 9、映射

    struct v4l2_buffer mapbuffer;
    unsigned char *mmpaddr[4];   //用于存储映射后的首地址
    unsigned int  addr_length[4];//存储映射后空间的大小
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//初始化type
    for(int i = 0; i < 4; i++)
    {mapbuffer.index = i;ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);	//查询缓存信息if(ret < 0)perror("查询缓存队列失败");mmpaddr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);//mapbuffer.m.offset映射文件的偏移量addr_length[i] = mapbuffer.length;//放入队列ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);   // 将用户空间的视频缓冲区送入内核中if(ret < 0)perror("放入队列失败");
    }
    
  • 10、打开设备(启动视频流传输)

    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if(ret < 0)
    {perror("打开设备失败");
    }
    
  • 11、创建线程读取触摸屏输入

    pthread_t pthread_read;
    pthread_create(&pthread_read, NULL, start_read, NULL);
    

    线程函数:

    如果点击触摸屏,并且触摸点在 “ 拍照 ” “ 相册 ” 按钮范围内,则会记录触摸点的坐标 read_x read_y(全局变量)

    void *start_read(void *arg)
    {int x = 0, y =0;while(start_read_flag)     // 初始化为1,当整个程序要退出时,设为0,退出线程{printf("线程\n");read_touchscreen(&x, &y);     // 阻塞读取触点坐标//拍照if(x  > 800 && x < 1000 && y > 0 && y < 600){printf("chuli\n");pthread_mutex_lock(&mutex);read_x = x;read_y = y;pthread_mutex_unlock(&mutex);}printf("readx = %d, ready = %d", read_x, read_y);}return NULL;
    }
    

    read_touchscreen 函数:

    ts_read 的默认读取是以阻塞的方式

    int read_touchscreen(int *x, int *y)
    {struct ts_sample samp;// ts_setup 以阻塞的方式打开了触摸屏设备,所以 ts_read 是阻塞读取if (ts_read(ts, &samp, 1) < 0)   // 1:代表对一个触摸点的采集数{perror("ts_read error");ts_close(ts);return -1;}*x = samp.x;*y = samp.y;printf("anxia : %d %d", samp.x, samp.y);return 0;
    }
    
  • 12、进入 while 循环,提取摄像头数据,并在LCD上显示同时实现拍照、相册功能

    	while(1){//从队列中提取一帧数据struct v4l2_buffer readbuffer;readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);     //从缓冲队列获取一帧数据(出队列)//出队列后得到缓存的索引index,得到对应缓存映射的地址mmpaddr[readbuffer.index]if(ret < 0)perror("获取数据失败");if(read_x > 850 && read_x < 1000 && read_y > 0 && read_y < 600){if(read_x > 850 && read_x < 1000 && read_y > 130 && read_y < 210){printf("paizhao\n");char newname[20] = {0};image_count++;sprintf(newname,"/home/%d.jpg", image_count);FILE *file = fopen(newname, "w+");//建立文件用于保存一帧数据if(NULL == file)printf("拍照失败");int res_w = fwrite(mmpaddr[readbuffer.index], readbuffer.length, 1, file);fclose(file);jpeg_list_insert(image_list, newname);   // 将图片加入双向链表中printf("new_image:%s %d\n", newname, image_count);sleep(1);}else if(read_x  > 850 && read_x < 1000 && read_y > 390 && read_y < 470){start_xiangce();				}pthread_mutex_lock(&mutex);read_x = 0;read_y = 0;pthread_mutex_unlock(&mutex);}//打开相册//显示在LCD上LCD_JPEG_Show(mmpaddr[readbuffer.index], readbuffer.length);//读取数据后将缓冲区放入队列ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);if(ret < 0)perror("放入队列失败");}
    
  • 13、程序终止前的收尾工作

    //关闭设备(停止视频流传输)
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    if(ret < 0)perror("关闭设备失败");
    //取消映射
    for(int i = 0; i < 4; i++)munmap(mmpaddr[i], addr_length[i]);start_read_flag = 0;	//结束触摸屏监听线程
    ts_close(ts);
    close(fd);
    close(fd_fb);
    return 0;
    

封装的一些大型函数

1、在 LCD 上显示 jpeg 图片

指定 JPEG 文件显示在LCD上,传入 jpeg 文件路径

int LCD_Show_JPEG(const char *jpeg_path)
//指定JPEG文件显示在LCD上,传入jpeg文件路径
int LCD_Show_JPEG(const char *jpeg_path)
{FILE *jpeg_file = NULL;int min_hight = LCD_height, min_width = LCD_width, valid_bytes;struct jpeg_decompress_struct cinfo;struct jpeg_error_mgr jerr;//1. 错误处理对象与解码对象绑定cinfo.err = jpeg_std_error(&jerr);  //2. 打开.jpeg图像文件jpeg_file = fopen(jpeg_path, "r");	//只读方式打开//3. 创建解码对象jpeg_create_decompress(&cinfo);//4. 指定解码数据源jpeg_stdio_src(&cinfo, jpeg_file);//5. 读取图像信息jpeg_read_header(&cinfo, TRUE);//6. 设置解码参数cinfo.out_color_space = JCS_RGB;  //可以不设置默认为RGB//cinfo.scale_num = 1;//cinfo.scale_denom = 1;设置图像缩放,scale_num/scale_denom缩放比例,默认为1//7. 开始解码jpeg_start_decompress(&cinfo);//8. 为缓冲区分配空间//   cinfo.output_components:每个像素所占的字节数     cinfo.output_width:每一行的像素个数//   保存解压出来的一行数据unsigned char* jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);    // 图片每一行像素所占的字节数//   将解压出来的数据 BGR 转换成 RGB 进行保存unsigned int*  fb_line_buf   = malloc(line_length);     // LCD一行的宽度(以字节为单位)   每个成员4个字节刚好和RGB888对应//判断图像和LCD屏那个分辨率更低if(cinfo.output_width < min_width)min_width = cinfo.output_width;    // 图片的宽度和LCD宽度之间取最小的if(cinfo.output_height < min_hight)    min_hight = cinfo.output_height;   // 图片的高度和LCD高度之间取最小的//9. 读取数据,数据按行读取valid_bytes = min_width * bpp / 8;   // 一行的有效字节数,实际写进LCD显存的一行数据大小unsigned char *ptr = fbbase;         // framebuffer的起始地址while(cinfo.output_scanline < min_hight)       //  cinfo.output_scanline 当前已经处理的行的数量{jpeg_read_scanlines(&cinfo, &jpeg_line_buf, 1);   // 每次读取一行(对 jpeg 图片的解码是一行一行进行的)//将读取到的BGR888数据转化为RGB888unsigned int red, green, blue;unsigned int color;  for(int i = 0; i < min_width; i++)    // 当图片的宽度大于LCD宽度时,在LCD上只会显示LCD宽度的图片{red   = jpeg_line_buf[i*3];green = jpeg_line_buf[i*3+1];blue  = jpeg_line_buf[i*3+2];color = red<<16 | green << 8 | blue;    // 将 BGR 数据转换为 RGB 数据 fb_line_buf[i] = color;}memcpy(ptr, fb_line_buf, valid_bytes);ptr += LCD_width*bpp/8;}//完成解码jpeg_finish_decompress(&cinfo);//销毁解码对象jpeg_destroy_decompress(&cinfo);//释放内存free(jpeg_line_buf);free(fb_line_buf);return 1;
}

关键点:

  • 解码流程:

    • 1、错误处理对象与解码对象的绑定
    • 2、打开 .jpeg 图片文件(获得文件句柄)
    • 3、创建解码对象
    • 4、指定解码数据源(将解码对象与文件句柄进行绑定)
    • 5、读取图像信息
    • 6、设置解码参数(缩放比、获取像素信息的BPP格式,默认为 BGR 格式)
    • 7、开始解码
    • 8、为缓冲区分配空间(一行像素数据的大小)( cinfo.output_components * cinfo.output_width )
    • 9、读取数据,数据按行进行读取
  • 读取数据的行数:

    首先在 LCD y 分辨率和像素的宽度之间取得最小值 min_hight

    while( cinfo.output_scanline < min_hight ) cinfo.output_scanline :当前已经处理行的数量

  • BGR 到 RGB 数据的转化:

    RBG 像素数据,一个像素占 4 个字节

    unsigned int* fb_line_buf = malloc ( line_length ); // 也是分配一行(像素个数为 LCD 的 x 分辨率)

    在读取到一行的像素数据后进行转化,但是转化的像素个数为 LCD x 分辨率 与 图像宽度之间的最小值 min_width

    for ( int i = 0; i < min_width; i++ )

  • 将 RGB 数据写入 framebuffer 中:

    valid_bytes = min_width * bpp / 8;

    memcpy ( ptr, fb_line_buf, valid_bytes ); (每转化一行,就将这一行的像素数据写入framebuffer中)

2、对图片链表进行操作的一系列函数

为了实现相册中图片的切换功能,将图片统一放置在一个指定目录下,定义一个双向链表,每一项对应一张图片

struct jpeg_node{char name[30];				//图像名struct jpeg_node *next; //下一张struct jpeg_node *pre;	//上一张
};

1、初始化链表

定义一个虚拟头节点(方便节点的插入,其它并没有什么作用)

struct jpeg_node *jpeg_list_Init(void)
{struct jpeg_node* jpeg_head = malloc(sizeof(struct jpeg_node));strcpy(jpeg_head->name, background2);   // 将背景图片2作为链表的头节点(但是这个头节点相当与是虚拟头节点)jpeg_head ->pre = jpeg_head;jpeg_head ->next = jpeg_head;return jpeg_head;
}

2、插入一个新节点(头插法)

void jpeg_list_insert(struct jpeg_node *jpeg_list, char *name)
{struct jpeg_node *newnode = malloc(sizeof(struct jpeg_node));strcpy(newnode->name, name);struct jpeg_node *p = jpeg_list->next;//头插法jpeg_list->next = newnode;newnode->pre = jpeg_list;newnode->next = p;p->pre = newnode;printf("放入链表成功\n");p = NULL;
}

3、遍历整个链表(打印所有图片的名字,除了虚拟头节点)

void jpeg_list_printf(void)
{struct jpeg_node* pnext = image_list->next;while(pnext != image_list){printf("%s\n", pnext->name);pnext = pnext->next;}pnext = NULL;
}

3、将已有照片放入相册

返回目前照片名称最大索引

int xiangce_Init(void)
int xiangce_Init(void)
{image_list = jpeg_list_Init();  // 初始化链表DIR *dp = opendir("/home/");	// 打开home目录struct dirent *pdir;char *temp = NULL;char name[15];int total = 0;//遍历目录, 当遍历结束时返回NULLwhile(pdir = readdir(dp))	{if(pdir->d_type == DT_REG)				//判断是否为普通文件{if(strstr(pdir->d_name, ".jpg"))	//判断是否为jpg文件{char newname[20] = {0};sprintf(newname,"/home/%s", pdir->d_name);   // 获取文件的绝对路径作为文件名jpeg_list_insert(image_list, newname);       // 将该文件名称插入链表中bzero(name,15);                              // 将 name 数组中的前 15 个字符全部清为 0 strcpy(name, pdir->d_name);temp = strtok(name, ".");total = atoi(temp) > total ? atoi(temp) : total;   // atoi:将字符串转转换为整数   得到名称索引最大的那个}}}temp = NULL;return total;
}

关键点:

  • 名称索引:

    指定目录中的图片名称均为:1.jpg 2.jpg 3.jpg …

    获取当前已有照片的最大名称索引,下一次记录照片时,照片名为最大名称索引 + 1

  • 目录文件的遍历:

    opendir 函数用于打开指定的目录

    readdir 函数获取目录中的文件(在 while 循环中依次获取目录中的文件直至为 NULL)

    readdir 获取到的文件结构体中有属性表明了文件的类型、文件名等等

  • strstr

    char *strstr(const char *haystack, const char *needle)
    

    该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null

  • bzero

    void bzero(void *s, int n);
    

    将内存块的前 n 个字节清零

  • strtok

    char *strtok(char *str, const char *delim)
    

    该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针

    示例:

    #include <string.h>
    #include <stdio.h>int main () {char str[80] = "This is - www.runoob.com - website";const char s[2] = "-";char *token;/* 获取第一个子字符串 */token = strtok(str, s);/* 继续获取其他的子字符串 */while( token != NULL ) {printf( "%s\n", token );token = strtok(NULL, s);}return(0);
    }
    

    结果:

    This is www.runoob.com website
    
  • atoi

    int atoi(const char *str)
    

    将字符串转换为整形数据

4、打开相册

start_xiangce();
void start_xiangce(void)
{struct jpeg_node *curr_image = image_list; //->next;//指向第一张图片    LCD_Show_JPEG(background2);LCD_Show_JPEG(curr_image->name);   // 虚拟头节点的内容设置为背景图片2的名字(绝对路径)int pre_x = 0, pre_y = 0;while(1){	if(pre_x != read_x && pre_y != read_y){if(read_x>850 && read_x<1000 && read_y>260 &&read_y<340)	//下一张{printf("下一张\n");curr_image = curr_image->next;printf("current image name :%s\n", curr_image->name);}if(read_x>850 && read_x<1000 && read_y>0 && read_y<80)	//上一张{curr_image = curr_image->pre;printf("上一张\n");printf("current image name :%s\n", curr_image->name);}}pre_x = read_x;pre_y = read_y;if(curr_image == image_list)    // 去除背景图片2的循环curr_image = image_list->next;LCD_Show_JPEG(curr_image->name);	if(read_x>850 && read_x<1000 && read_y>520 && read_y<600)	//返回{LCD_Show_JPEG(background1);printf("返回\n");break;}}
}

V4L2框架

1、打开设备

当摄像头设备插入电脑后:

image-20240913101420489

int fd = open("/dev/video1",O_RDWR);

2、获得所支持的格式

使用 ioctl ( 文件描述符,命令,与命令对应的结构体 ) 来获取摄像头的格式

  • 命令

    image-20240913101559983

  • 关于 v4l2_fmtdesc 结构体

    命令使用:VIDIOC_ENUM_FMT(获取当前驱动支持的视频格式)

    struct v4l2_fmtdesc {__u32		    index;             /* Format number      */__u32		    type;              /* enum v4l2_buf_type */__u32           flags;__u8		    description[32];   /* Description string */__u32		    pixelformat;       /* Format fourcc      */__u32		    reserved[4];
    };
    

    该结构体定义如上,因为v4l2不单单针对摄像头,所以使用前需要对type成员进行初始化,v4l2_buf_type 这个枚举总共有13个成员,这里选择1,即视频抓取

    image-20240913101729576

    在用代码读取过程中,因为支持多种格式,所以用while循环读取支持的格式

  • 关于 v4l2_capability 结构体

    命令使用:VIDIOC_QUERYCAP(检查当前视频设备支持的标准)

    struct v4l2_capability
    {u8 driver[16]; // 驱动名字u8 card[32]; // 设备名字u8 bus_info[32]; // 设备在系统中的位置u32 version;// 驱动版本号u32 capabilities;// 设备支持的操作u32 reserved[4]; // 保留字段
    };
    

    除了使用 v4l2_fmtdesc 结构体获取像素格式,还可以通过 v4l2_capability 结构体来获取设备的功能,主要看capabilities成员,其是否支持视频捕捉(V4L2_CAP_VIDEO_CAPTURE)、以及是否支持流读写(V4L2_CAP_STREAMING)

使用示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
int main(int argc, char**argv)
{if(argc != 2){printf("%s </dev/video0,1...>\n", argv[0]);return -1;}//打开摄像头设备int fd = open(argv[1], O_RDWR);if(fd < 0){perror("打开设备失败");return -1;}//获取摄像头支持格式,使用ioctl函数int ioctl(int fd, unsigned long request, ...);struct v4l2_fmtdesc    v4fmt;struct v4l2_capability cap;v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//选择视频抓取int i = 0;while(1){v4fmt.index = i;i++;int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);if(ret < 0){perror("获取格式失败");break;}printf("index = %d\n", v4fmt.index);printf("flags = %d\n", v4fmt.flags);printf("descrrption = %s\n", v4fmt.description);unsigned char *p = (unsigned char*)&v4fmt.pixelformat;printf("pixelformat = %c%c%c%c\n", p[0],p[1],p[2],p[3]);printf("reserved = %d\n", v4fmt.reserved[0]);}int ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);if(ret < 0)perror("获取功能失败");printf("drivers:%s\n", cap.driver);//读取驱动名字if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)printf("%s 支持视频捕获\n", argv[1]);if(cap.capabilities & V4L2_CAP_STREAMING)printf("%s 支持流读写\n", argv[1]);close(fd);return 0;	
}

结果:

image-20240913102444276

3、配置摄像头

设置视频的采集格式,定义 v4l2_format 结构体变量,然后通过结构体 v4l2_pix_format 来设置采集的高、宽以及像素格式(YUYV),设置之后,可以采用打印的方式来查看是否设置成功

struct v4l2_format {__u32	 type;union {struct v4l2_pix_format		pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */struct v4l2_pix_format_mplane	pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */struct v4l2_window		win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */struct v4l2_vbi_format		vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */struct v4l2_sliced_vbi_format	sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */struct v4l2_sdr_format		sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */__u8	raw_data[200];                   /* user-defined */} fmt;
};struct v4l2_pix_format {__u32         		width;__u32			height;__u32			pixelformat;__u32			field;		/* enum v4l2_field */__u32            	bytesperline;	/* for padding, zero if unused */__u32          		sizeimage;__u32			colorspace;	/* enum v4l2_colorspace */__u32			priv;		/* private data, depends on pixelformat */__u32			flags;		/* format flags (V4L2_PIX_FMT_FLAG_*) */__u32			ycbcr_enc;	/* enum v4l2_ycbcr_encoding */__u32			quantization;	/* enum v4l2_quantization */__u32			xfer_func;	/* enum v4l2_xfer_func */
};

代码示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
int main(void) 
{int fd = open("/dev/video0",O_RDWR);   //根据自己的摄像头设备节点打开if (fd < 0){perror("打开设备失败");return -1;}//设置摄像头 ioctl(文件描述符,命令,与命令对应的结构体)struct v4l2_format vfmt;vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集vfmt.fmt.pix.width  = 640;                //设置摄像头采集参数,不可以任意设置vfmt.fmt.pix.height = 480;vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YYUV; //设置视频采集格式 ,根据上一步测得,注意格式有yyuv和yuyv不要搞混int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt); // 设置格式命令  VIDIOC_S_FMT:设置当前驱动的频捕捉格式if (ret < 0){perror("设置格式失败1");}memset(&vfmt,0,sizeof(vfmt));vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd,VIDIOC_G_FMTL:du'qu,&vfmt);   // VIDIOC_G_FMTL:读取当前驱动的频捕捉格式if (ret < 0){perror("设置格式失败2");}if(vfmt.fmt.pix.width == 640 && vfmt.fmt.pix.height == 480 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV){printf("设置成功!");}else{printf("设置失败3");}close(fd);return 0;
}

结果:

image-20240913103145233

4、向内核申请帧缓冲队列并映射

V4L2读取数据时有两种方式,第一种是用read读取(调用read函数),第二种是用流(streaming)读取,在第二步上已经获取到我的设备支持流读写,为了提高效率采用流读写,流读写就是在内核中维护一个缓存队列,然后再映射到用户空间,应用层直接读取队列中的数据

步骤为:申请缓冲区->逐个查询申请到的缓冲区->逐个映射->逐个放入队列中

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
int main(int argc, char**argv)
{if(argc != 2){printf("%s </dev/video0,1...>\n", argv[0]);return -1;}//打开摄像头设备int fd = open(argv[1], O_RDWR);if(fd < 0){perror("打开设备失败");return -1;}//设置摄像头采集格式struct v4l2_format vfmt;vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//选择视频抓取vfmt.fmt.pix.width = 640;  //设置宽,不能随意设置vfmt.fmt.pix.height = 480; //设置高vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;  //设置视频采集格式int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);      // VIDIOC_S_FMT:设置捕获格式if(ret < 0){perror("设置采集格式错误");}memset(&vfmt, 0, sizeof(vfmt));vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);	if(ret < 0){perror("读取采集格式失败");}printf("width = %d\n", vfmt.fmt.pix.width);printf("width = %d\n", vfmt.fmt.pix.height);unsigned char *p = (unsigned char*)&vfmt.fmt.pix.pixelformat;printf("pixelformat = %c%c%c%c\n", p[0],p[1],p[2],p[3]);	//申请缓冲队列struct v4l2_requestbuffers reqbuffer;reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuffer.count = 4;	                //申请4个缓冲区reqbuffer.memory = V4L2_MEMORY_MMAP;	//采用内存映射的方式ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);    // VIDIOC_REQBUFS: 申请缓冲区队列if(ret < 0){perror("申请缓冲队列失败");}//映射,映射之前需要查询缓存信息->每个缓冲区逐个映射->将缓冲区放入队列struct v4l2_buffer mapbuffer;unsigned char *mmpaddr[4];                      //用于存储映射后的首地址mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;   //初始化typefor(int i = 0; i < 4; i++){mapbuffer.index = i;ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);	//查询缓存信息   VIDIOC_QUERYBUF:将数据缓存转换为物理地址if(ret < 0)perror("查询缓存队列失败");mmpaddr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);    //mapbuffer.m.offset映射文件的偏移量//放入队列ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);   // 把数据从缓存中读取出来if(ret < 0)perror("放入队列失败");}close(fd);return 0;	
}

5、采集视频

做完前面的设置就可以进行采集数据,打开设备->读取数据->关闭设备->释放映射。

读取数据的本质就是从上一个步骤中映射的队列中取出数据,取出数据后需要将该缓冲区放入队列中,以便于再去采集下一帧数据。

为了便于查看,在设置采集格式时,选择MJPEG格式,用fopen函数建立一个my.jpg文件,用fwrite函数保存读到的一帧数据。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
int main(int argc, char**argv)
{if(argc != 2){printf("%s </dev/video0,1...>\n", argv[0]);return -1;}//1.打开摄像头设备int fd = open(argv[1], O_RDWR);if(fd < 0){perror("打开设备失败");return -1;}//2.设置摄像头采集格式struct v4l2_format vfmt;vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//选择视频抓取vfmt.fmt.pix.width  = 640;//设置宽,不能随意设置vfmt.fmt.pix.height = 480;//设置高vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//设置视频采集像素格式int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);// VIDIOC_S_FMT:设置捕获格式if(ret < 0){perror("设置采集格式错误");}memset(&vfmt, 0, sizeof(vfmt));vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);	if(ret < 0){perror("读取采集格式失败");}printf("width = %d\n", vfmt.fmt.pix.width);printf("width = %d\n", vfmt.fmt.pix.height);unsigned char *p = (unsigned char*)&vfmt.fmt.pix.pixelformat;printf("pixelformat = %c%c%c%c\n", p[0],p[1],p[2],p[3]);	//4.申请缓冲队列struct v4l2_requestbuffers reqbuffer;reqbuffer.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuffer.count = 4;	//申请4个缓冲区reqbuffer.memory = V4L2_MEMORY_MMAP;	//采用内存映射的方式ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);   // VIDIOC_REQBUFS: 申请缓冲区队列if(ret < 0){perror("申请缓冲队列失败");}//映射,映射之前需要查询缓存信息->每个缓冲区逐个映射->将缓冲区放入队列struct v4l2_buffer mapbuffer;unsigned char *mmpaddr[4];     //用于存储映射后的首地址unsigned int addr_length[4];   //存储映射后空间的大小mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;     //初始化type  视频捕捉for(int i = 0; i < 4; i++){mapbuffer.index = i;ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);	//查询缓存信息if(ret < 0)perror("查询缓存队列失败");mmpaddr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);//mapbuffer.m.offset映射文件的偏移量addr_length[i] = mapbuffer.length;//放入队列ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);if(ret < 0)perror("放入队列失败");}//打开设备int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_STREAMON, &type);   if(ret < 0)perror("打开设备失败");//从队列中提取一帧数据struct v4l2_buffer readbuffer;readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);        //从缓冲队列获取一帧数据(出队列)//出队列后得到缓存的索引index,得到对应缓存映射的地址mmpaddr[readbuffer.index]if(ret < 0)perror("获取数据失败");FILE *file = fopen("my.jpg", "w+");//建立文件用于保存一帧数据fwrite(mmpaddr[readbuffer.index], readbuffer.length, 1, file);fclose(file);//读取数据后将缓冲区放入队列ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);if(ret < 0)perror("放入队列失败");//关闭设备ret = ioctl(fd, VIDIOC_STREAMOFF, &type);if(ret < 0)perror("关闭设备失败");//取消映射for(int i = 0; i < 4; i++)munmap(mmpaddr[i], addr_length[i]);close(fd);return 0;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/143478.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【MATLAB源码-第225期】基于matlab的计算器GUI设计仿真,能够实现基础运算,三角函数以及幂运算

操作环境&#xff1a; MATLAB 2022a 1、算法描述 界面布局 计算器界面的主要元素分为几大部分&#xff1a;显示屏、功能按钮、数字按钮和操作符按钮。 显示屏 显示屏&#xff08;Edit Text&#xff09;&#xff1a;位于界面顶部中央&#xff0c;用于显示用户输入的表达式和…

【激励广告带来的广告收入与用户留存率的双重提升】

激励广告带来的广告收入与用户留存率的双重提升 ) 随着移动应用市场的竞争加剧&#xff0c;如何通过广告变现成为众多开发者关注的焦点。其中&#xff0c;激励广告&#xff08;Rewarded Ads&#xff09;凭借其用户友好、互动性强等特点&#xff0c;逐渐成为开发者的首选。那些…

Java——Static与final修饰的变量与方法(总结)

前言&#xff1a; Java语法学过一遍之后&#xff0c;我相信大多数和我一样脑瓜子嗡嗡的&#xff0c;甚至有点乱了&#xff0c;这时候应该自己把之前的能总结的&#xff0c;或者不熟悉的都要总结一遍&#xff0c;以便于后期的学习&#xff01;&#xff01; static修饰的成员变量…

[附源码]SpringBoot+VUE+Java实现人脸识别系统

今天带来一款优秀的项目&#xff1a;java人脸识别系统源码 。 系统采用的流行的前后端分离结构&#xff0c;内含功能包括 “人脸数数据录入”&#xff0c;“人脸管理”&#xff0c;“摄像头识别” 如果您有任何问题&#xff0c;也请联系小编&#xff0c;小编是经验丰富的程序员…

数码好物抢先看!2024有什么好用又实惠的好物推荐!

在数字科技日新月异的今天&#xff0c;各种数码好物层出不穷&#xff0c;它们以其先进的技术、创新的功能以及不断提升的性能&#xff0c;为我们的生活带来了极大的便利和乐趣。对于消费者来说&#xff0c;在众多的数码产品中挑选出好用又实惠的好物&#xff0c;无疑是一件既令…

Spring Controller

服务器控制 响应架构 Spring Boot 内集成了 Tomcat 服务器&#xff0c;也可以外接 Tomcat 服务器。通过控制层接收浏览器的 URL 请求进行操作并返回数据。 底层和浏览器的信息交互仍旧由 servlet 完成&#xff0c;服务器整体架构如下&#xff1a; Server&#xff1a; Tomcat…

电机知识总结

一.直流无刷电机&#xff08;BLDC&#xff09; 27N30P指有27个槽&#xff0c;30的极数&#xff0c;它的极对数&#xff1a;30/215,所以是15对极。 N必须是3的倍数&#xff0c;P必须是偶数&#xff0c; 电角度是电气特性&#xff0c;机械角度是空间特性&#xff0c;必须指明是谁…

Selenium等待机制:理解并应用显式等待与隐式等待,解决页面加载慢的问题

目录 引言 等待机制的重要性 显式等待&#xff08;Explicit Wait&#xff09; 原理 应用方式 代码示例 优点与缺点 隐式等待&#xff08;Implicit Wait&#xff09; 原理 应用方式 代码示例 优点与缺点 解决页面加载慢的问题 1. 合理设置等待时间 2. 优先使用显…

数据三维可视化技术的应用场景

数据三维可视化技术作为一种强大的工具&#xff0c;已经在各个领域展现出了巨大的应用潜力。它不仅提供了直观、生动的数据展示方式&#xff0c;还让用户能够更深入地理解数据间的关联和趋势。下面将探讨数据三维可视化技术的应用范围及其在不同领域中的重要性。 数据三维可视化…

控价服务如何判断高低

在当今竞争激烈的市场环境中&#xff0c;品牌控价成为企业发展的关键一环。许多品牌选择与第三方控价公司合作&#xff0c;借助其专业的电商价格监测系统&#xff0c;既能节省人力成本&#xff0c;又能获得高质量的服务。然而&#xff0c;如何判断第三方控价服务系统的优劣呢&a…

VirtualBox7.1.0 安装 Ubuntu22.04.5 虚拟机

环境 &#xff08;1&#xff09;宿主机系统&#xff1a;Windows10 &#xff08;2&#xff09;虚拟机软件&#xff1a;VirtualBox7.1.0 &#xff08;3&#xff09;虚拟机系统&#xff1a;Ubuntu 22.04.5 LTS (Jammy Jellyfish) 步骤 &#xff08;1&#xff09;第一步 &…

2024年最新版TypeScript学习笔记——泛型、接口、枚举、自定义类型等知识点

今天带来的是来自尚硅谷禹神2024年8月最新的TS课程的学习笔记&#xff0c;不得不说禹神讲的是真的超级棒&#xff01; 文章目录 TS入门JS中的困扰静态类型检查编译TS命令行编译自动化编译 类型检查变量和函数类型检查字面量类型检查 类型推断类型声明声明对象类型声明函数类型…

个人驾校预约管理系统设计与实现

个人驾校预约管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装个人驾校预约管理系统软件…

3.js - THREE.CubeTextureLoader() 添加环境纹理,以创建立方体贴图

使用 THREE.CubeTextureLoader() 添加环境纹理&#xff0c;以创建立方体贴图 不使用 THREE.CubeTextureLoader() 的时候 源码 import * as THREE from three import { OrbitControls } from three/examples/jsm/controls/OrbitControls import { RGBELoader } from three/exam…

SHAP 模型可视化 + 参数搜索策略在轴承故障诊断中的应用

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Python轴承故障诊断入门教学-CSDN博客 Python轴承故障诊断 (13)基于故障信号特征提取的超强机器学习识别模型-CSDN博客 Python轴承故障诊断 (14)高创新故障识别模型-CSDN…

【鸿蒙】HarmonyOS NEXT开发快速入门教程之ArkTS语法装饰器(上)

文章目录 前言一、ArkTS基本介绍1、 ArkTS组成2、组件参数和属性2.1、区分参数和属性的含义2.2、父子组件嵌套 二、装饰器语法1.State2.Prop3.Link4.Watch5.Provide和Consume6.Observed和ObjectLink代码示例&#xff1a;示例1&#xff1a;&#xff08;不使用Observed和ObjectLi…

新媒体运营

一、新媒体运营的概念 1.新媒体 2.新媒体运营的五大方向 用户运营 产品运营 。。。 二、新媒体的岗位职责及要求 三、新媒体平台

数仓工具:datax

datax可以理解为sqoop的优化版&#xff0c; 速度比sqoop快 因为sqoop底层是map任务&#xff0c;而datax底层是基于内存 DataX 是一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定…

群晖NAS使用Docker本地部署网页版Ubuntu系统并实现无公网IP远程访问

文章目录 前言1. 下载Docker-Webtop镜像2. 运行Docker-Webtop镜像3. 本地访问网页版Linux系统4. 群晖NAS安装Cpolar工具5. 配置异地访问Linux系统6. 异地远程访问Linux系统7. 固定异地访问的公网地址 前言 本文旨在详细介绍如何在群晖NAS部署docker-webtop&#xff0c;并结合c…

性能问题的典型特征有哪些?

各位好&#xff0c;我是 道普云 一站式云测试SaaS平台。一个在软件测试道路上不断折腾十余年的萌新。 欢迎关注我的专栏和我的主页 道普云 文章内容具有一定门槛&#xff0c;建议先赞再收藏慢慢学习&#xff0c;有不懂的问题欢迎私聊我。 &#xff08;双击屏幕有新大陆&…