Linux驱动,之camera的驱动v4L2(video for linux two)应用篇,LCD实时显示图像或图片(三)

一、前言

本篇打算,记录一篇,LCD实时显示图像或图片的文章。

这段话比较有意义,我放到前面。

做视频采集的都知道,我们会设计一个双向的队列,用于读写视频,暂时保存数据,才不会卡顿,在内核需要申请一个队列,
映射到用户空间使用。原理是在内核申请了一个DMA,DMA直接把摄像头的数据,从v4l2这一套提供的机制,有个bufer吧,
DMA直接把bufer里的数据搬到了内存,不需要cpu参数,提供了cpu使用率,然后呢,我们需要获取到这个内存地址,获取数据,
但是内核是不会给应用程序直接操作内核空间的地址,只能通过系统调用才能访问内核空间,所以需要mmap映射这个地址到
用户空间,就可以直接读取这个队列了,我现在自己也想明白了,为啥这么玩,用户空间和内核空间需要隔离,这是内核管理内存的机制,
虽然操作的实际是同一个地址,不可能不是同一内存地址,如果不是,就需要搬运一次数据。在LCD,codex,以太网,都有这么一个映射,不这么映射,数据量不知道有多大,效率低的没法玩。

本人有非常丰富的音视频采集开发,图像显示,GUI,开发经验,对摄像头到lcd显示,或通过网络传输到上位机,流媒体比较熟悉。

之前写过一篇博客lcd驱动,显示原理,以及显示各种格式(yuv,rgb,jpeg,png)图片,结合上一篇文章,v4l2采集图像,我们将采集的
摄像头图像流,不进行编码压缩,直接封装成各种格式的图片,可以通过触摸去播放图片,或直接显示实时图像到lcd上。
后面有时间,会继续写一篇qt开发,移植qt到LCD显示屏,这样就有gui图形界面了,后面会继续写一篇移植4g模块的文章,将视频流进行编码压缩,发送到Windows,自己写的qt上位机播放,保存为mp4,或其他格式的文件。可以实时监控,如果有缘人,看到,需要做这么一个项目,可以找我合作。

要在lcd显示图像,要使用lcd的frambufer,v4l2框架,假设lcd可以工作了,可以参考我另一篇文章,lcd显示的设置,这里我们直接往显存写数据就行了,就可以显示图像了。假设你的设备已经插入了usb摄像头,或调通了其他接口的摄像头,/dev/video这个设备是有的。
在这里插入图片描述

原文链接:https://blog.csdn.net/HRad7282/article/details/142334615
我对他进行了改进,,他的例子只能是个玩具,不能用实际项目,我自己完全可以写出来,但是不想从0写,有现成的,就不造轮子了。

在这里插入图片描述

初始化v42

1、设置摄像头的采集格式

struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//选择视频抓取
vfmt.fmt.pix.width  = 800;//设置宽        // 一定是 800 * 600(600等于LCD的高度,8800于LCD的宽度是为了显示相机的按钮)
vfmt.fmt.pix.height = 600;//设置高
vfmt.fmt.pix.pixelformat =  V4L2_PIX_FMT_MJPEG;  //设置视频采集像素格式,假设采集的是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]);

2、申请缓冲队列、映射到用户空间

做视频采集的都知道,我们会设计一个双向的队列,用于读写视频,暂时保存数据,才不会卡顿,在内核需要申请一个队列,
映射到用户空间使用。原理是在内核申请了一个DMA,DMA直接把摄像头的数据,从v4l2这一套提供的机制,有个bufer吧,
DMA直接把bufer里的数据搬到了内存,不需要cpu参数,提供了cpu使用率,然后呢,我们需要获取到这个内存地址,获取数据,
但是内核是不会给应用程序直接操作内核空间的地址,只能通过系统调用才能访问内核空间,所以需要mmap映射这个地址到
用户空间,就可以直接读取这个队列了,我现在自己也想明白了,为啥这么玩,用户空间和内核空间需要隔离,这是内核管理内存的机制,虽然操作的实际是同一个地址。

在这里插入图片描述

在这里插入图片描述

申请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);
if(ret < 0)
{
   
   
	perror("申请缓冲队列失败");
}

映射

unsigned char *mmpaddr[4];       //用于存储映射后的首地址
unsigned int  addr_length[4];    //存储映射后空间的大小

struct v4l2_buffer mapbuffer;
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//初始化type

for(int i = 0; i < 4; i++)
{
   
   
	mapbuffer.index = i;
	ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);	//查询前面申请的,内核缓存信息,返回写入到这个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映射文件的偏移量,将内核的队列映射到用户空间,返回地址  
	                                                        //给mmpaddr
	addr_length[i] = mapbuffer.length;
	
	
	ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);   // 将用户空间的视频缓冲区,映射后的信息,写入送入内核中队列
	if(ret < 0)
		perror("放入队列失败");
}

3、启动视频流传输

int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);  //摄像头开始采集数据,并会写入到内核循环队列中,没有及时读走的,会覆盖
if(ret < 0)
{
   
   
	perror("打开设备失败");
}

二、写一个读取帧图像,显示视频的线程。

在这里插入图片描述

其实我在做海思项目时,有vi,vo,vi可以直接绑定到vo,vo就是设置好了fb,lcd,绑定其实是直接将显存地址,给到vi,vi从用户空间的队列,读出数据直接写到到显存地址,就不用搬运数据了,hisi这一块,肯定是封装了,但是原理大概就是这样子。

pthread_t _read_video_frame;
pthread_create(&_read_video_frame, NULL, pthread_read_video_frame, NULL);



void pthread_read_video_frame()
{
   
   

	struct v4l2_buffer buffer;
	buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//从队列中提取一帧数据
	
	while(1)
	{
   
   
		这里需要加一个select监控fd,是否有数据这个博主没做,所以,我说这是个玩具,怎么可能这么玩,30帧,30ms才来一次数据,
		这里会一直在运行,别的线程得到时间就少。
		select(fd,,,,,)//这里自己调试一下,我不想写了
		if(fd >0)//有数据
		{
   
   

		//下发命令,从缓冲队列获取一帧数据(出队列)出队列后得到缓存的索引index, 得到对应缓存映射的地址mmpaddr[readbuffer.index]
		ret = ioctl(fd, VIDIOC_DQBUF, &buffer);     
		if(ret < 0)
			perror("获取数据失败");
		
		//显示在LCD上
		LCD_JPEG_Show(mmpaddr[readbuffer.index], readbuffer.length);//直接lcd显示,但是采集的流是JPEG格式,需要转换成rgb,
																	//因为lcd是rgb,才能显示。还可以yuv,但是需要自己转换一下。
	  
	  	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);

			read_x = 0;
			read_y = 0;	
		}
	 }
	
	//读取数据后将缓冲区放入队列
	ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
	if(ret < 0)
		perror("放入队列失败");	
	}

上面我么提到的,jpeg格式图片,如果显示,解码,就需要移植库,封装一个函数出来,我前面有讲过,在lcd篇。
在这里插入图片描述

// 测试显示rgb格式,假设我们采集的rgb图片,就可以直接播放了。下面这个只是lcd画图的例子,不同格式,需要不同修改。本篇的目的不在于此。

void fb_draw_rgb_picutre(void)
{
   
   
	const unsigned char
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值