一、前言
本篇打算,记录一篇,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

应用篇,LCD实时显示图像或图片(三)&spm=1001.2101.3001.5002&articleId=134584632&d=1&t=3&u=49484fd1e0cb4a2eafdbd53a962377b3)
2174

被折叠的 条评论
为什么被折叠?



