Linux摄像头(v4l2应用)——在LCD上实时显示摄像头采集JPEG数据

根前文介绍了如何使用摄像头获得摄像头采集到的一帧数据,本文将利用libjpeg库将采集到的JPEG数据转化为RGB格式,并实时显示。

Linux摄像头(v4l2应用)——获取摄像头一帧图像

一、libjpeg简介

前面使用了v4l2从摄像头获取到一帧图像,格式为JPEG,JPEG是经过压缩后的图像,如果要在LCD上显示,则需要将其解压缩得到RGB数据用于在LCD上显示,解压缩的过程就可以调用libjpeg开源库中的函数来完成解压,得到图像的RGB数据。开源库地址:http:// http://www.ijg.org/files/

下载源码移植后就可以使用了,在解码中需要用到最重要的结构体如下,struct jpeg_decompress_struct可以在libjpeg源码中jpeg.h中找到该结构体,其成员特别多,包含了jpeg图像的详细信息,同时也包含了解码后的数据信息。struct jpeg_error_mgr结构体用于错误处理,libjpeg库中默认了错误处理函数,使用时只需要定义该结构体变量,并让错误处理对象与解压对象绑定即可。

struct jpeg_decompress_struct{...};
struct jpeg_error_mgr{...};

二、解码流程与显示

jpeg的解码流程为:①创建jpeg解码对象;②指定解码数据源;③读取图像信息;④设置解码参数;⑤开始解码;⑥读取解码后的数据;⑦解码完毕;⑧释放解码对象。

为了便于方便,我直接将解码和LCD显示写在了一个函数里面,在获取到一帧数据后,直接调用该函数,就可以直接在LCD上显示。因为libjpeg在解析数据时,是按照一行一行进行解析的,所以我也是解析一行后进行转换,转换后的数据直接写入framebuffer中。

int LCD_JPEG_Show(const char *JpegData, int size)
{
	int min_hight = LCD_height, min_width = LCD_width, valid_bytes;
	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;
	cinfo.err = jpeg_std_error(&jerr);//错误处理对象与解压对象绑定
	//创建解码对象
	jpeg_create_decompress(&cinfo);
	//指定解码数据源
	jpeg_mem_src(&cinfo, JpegData, size);
	//读取图像信息
	jpeg_read_header(&cinfo, TRUE);
	//printf("jpeg图像的大小为:%d*%d\n", cinfo.image_width, cinfo.image_height);
	//设置解码参数
	cinfo.out_color_space = JCS_RGB;//可以不设置默认为RGB
	//cinfo.scale_num = 1;
	//cinfo.scale_denom = 1;设置图像缩放,scale_num/scale_denom缩放比例,默认为1
	//开始解码
	jpeg_start_decompress(&cinfo);
	
	//为缓冲区分配空间
	unsigned char*jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
	unsigned int*fb_line_buf = malloc(line_length);//每个成员4个字节和RGB888对应
	//判断图像和LCD屏那个分辨率更低
	if(cinfo.output_width < min_width)
		min_width = cinfo.output_width;
	if(cinfo.output_height < min_hight)
		min_hight = cinfo.output_height;
	//读取数据,数据按行读取
	valid_bytes = min_width * bpp / 8;//一行的有效字节数,实际写进LCD显存的一行数据大小
	unsigned char *ptr = fbbase;//指针指向显存起始地址
	while(cinfo.output_scanline < min_hight)
	{
		jpeg_read_scanlines(&cinfo, &jpeg_line_buf, 1);//每次读取一行
		//将读取到的BGR888数据转化为RGB888
		unsigned int red, green, blue;
		unsigned int color;
		for(int i = 0; i < min_width; i++)
		{
			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;
			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;
}
所遇问题
①libjpeg解码后的数据转化问题

在使用libjpeg解码后的数据是BGR888格式,即蓝色在高位,红色在低位,并且一个像素三个字节,而LCD屏幕显示则是用RGB888格式,红色在高位,同时一个像素用四个字节。

因此为了转化,先定义解码后一行的缓冲区jpeg_line_buf和准备给LCD一行的缓冲区fb_line_buf,先将红、绿、蓝提取出来再合并为一个颜色,前者是char型1个字节,后者是int型4个字节,所以用for循环一次fb_line_buf移动四个字节,恰好是下一个像素点。

②关于程序段错误和花屏

我在调试过程中会出现段错误,经过增加打印信息发现是操作framebuffer不当导致的,我在摄像头设置中设置了和LCD同样的分辨率,理论上在解码后的数据恰好能填充LCD,但是摄像头支持的分辨率不一定和LCD的分辨率一样,在数据转化时程序中的for循环判断条件按LCD_width判断时就会出现错误,会将数据填充在不该填充的位置,导致段错误或者花屏,所以这里增加了判断找到LCD和设置的摄像头的分辨率的最小值,以此为判断条件,但是导致的问题就是可能LCD屏幕上有空白。

可以通过ioctl函数来查看摄像头支持的分辨率,我的摄像头支持的分辨率如下,而我的LCD屏幕的分辨率是1024*600,而我按LCD的分辨率设置该摄像头时,会设置不成功,后期打印时显示设置的结果为800*600。所以就出现了上述问题。

三、源码及显示效果 

#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>
#include <jpeglib.h>
#include <linux/fb.h>

int fd_fb;
int screen_size;//屏幕像素大小
int LCD_width;//LCD宽度
int LCD_height;//LCD高度
unsigned char *fbbase = NULL;//LCD显存地址
unsigned long line_length;       //LCD一行的长度(字节为单位)
unsigned int bpp;    //像素深度bpp


//初始化LCD
int LCD_Init(void)
{
	struct fb_var_screeninfo var;   /* Current var */
	struct fb_fix_screeninfo fix;   /* Current fix */
	fd_fb = open("/dev/fb0", O_RDWR);
	if(fd_fb < 0)
	{
		perror("打开LCD失败");
		return -1;
	}
	//获取LCD信息
	ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);//获取屏幕可变信息
	ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix);//获取屏幕固定信息
	//LCD_width  = var.xres * var.bits_per_pixel / 8;
    //pixel_width = var.bits_per_pixel / 8;
    screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
	LCD_width = var.xres;
	LCD_height = var.yres;
	bpp = var.bits_per_pixel;
	line_length = fix.line_length;
	printf("LCD分辨率:%d %d\n",LCD_width, LCD_height);
	printf("bpp: %d\n", bpp);
	fbbase = mmap(NULL, screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);//映射
	if (fbbase == (unsigned char *)-1)
    {
        printf("can't mmap\n");
        return -1;
    }
    memset(fbbase, 0xFF, screen_size);//LCD设置为白色背景
    return 0;
}


int LCD_JPEG_Show(const char *JpegData, int size)
{
	int min_hight = LCD_height, min_width = LCD_width, valid_bytes;
	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;
	cinfo.err = jpeg_std_error(&jerr);//错误处理对象与解压对象绑定
	//创建解码对象
	jpeg_create_decompress(&cinfo);
	//指定解码数据源
	jpeg_mem_src(&cinfo, JpegData, size);
	//读取图像信息
	jpeg_read_header(&cinfo, TRUE);
	//printf("jpeg图像的大小为:%d*%d\n", cinfo.image_width, cinfo.image_height);
	//设置解码参数
	cinfo.out_color_space = JCS_RGB;//可以不设置默认为RGB
	//cinfo.scale_num = 1;
	//cinfo.scale_denom = 1;设置图像缩放,scale_num/scale_denom缩放比例,默认为1
	//开始解码
	jpeg_start_decompress(&cinfo);
	
	//为缓冲区分配空间
	unsigned char*jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
	unsigned int*fb_line_buf = malloc(line_length);//每个成员4个字节和RGB888对应
	//判断图像和LCD屏那个分辨率更低
	if(cinfo.output_width < min_width)
		min_width = cinfo.output_width;
	if(cinfo.output_height < min_hight)
		min_hight = cinfo.output_height;
	//读取数据,数据按行读取
	valid_bytes = min_width * bpp / 8;//一行的有效字节数,实际写进LCD显存的一行数据大小
	unsigned char *ptr = fbbase;
	while(cinfo.output_scanline < min_hight)
	{
		jpeg_read_scanlines(&cinfo, &jpeg_line_buf, 1);//每次读取一行
		//将读取到的BGR888数据转化为RGB888
		unsigned int red, green, blue;
		unsigned int color;  
		for(int i = 0; i < min_width; i++)
		{
			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;
			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;
}


int main(int argc, char**argv)
{
	if(argc != 2)
	{
		printf("%s </dev/video0,1...>\n", argv[0]);
		return -1;
	}
	if(LCD_Init() != 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 = LCD_width;//设置宽,设置为LCD的宽高
	vfmt.fmt.pix.height = LCD_height;//设置高
	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]);	

	//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("申请缓冲队列失败");
	}
	
	//映射,映射之前需要查询缓存信息->每个缓冲区逐个映射->将缓冲区放入队列
	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("打开设备失败");
	//unsigned char rgbdata[LCD_height*LCD_width*3];//存储解码后的RGB数据
	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("获取数据失败");

		//显示在LCD上
		LCD_JPEG_Show(mmpaddr[readbuffer.index], readbuffer.length);
		//读取数据后将缓冲区放入队列
		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;	
}

显示效果如下,LCD能实时显示摄像头拍摄的画面,但如上面所谈,因为LCD分辨率和摄像头设置分辨率不同的原因,LCD右侧有一部分空白,后续尝试下是否可以通过jpeg转RGB后再进行放大到LCD所需分辨率。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值