嵌入式Linux基于framebuffer的jpeg格式本地LCD屏显示

在基于Linux的视频监控采集系统中,摄像头采集到的一帧视频图像数据一般都是经过硬件自动压缩成jpeg格式的,然后再保存到摄像头设备的缓冲区.如果要把采集到的jpeg格式显示在本地LCD屏上,由于我们的Linux系统没有移植任何GUI系统,就要考虑以下方面:1. 将jpeg格式解压缩为位图格式,也就是jpeg解码.

2. 将解码出来的位图格式输出到本地的LCD屏上. 在Linux系统下是通过写入帧缓冲(framebuffer)来实现的.

3. framebuffer相当于为LCD设备提供一个统一的接口,对framebuffer的操控会反映到LCD显示设备上去. 如果配置Linux内核时没有找到支持本地lcd屏这种型号的驱动,那我们要自己写lcd屏驱动,然后选择静态加入内核或以模块的形式加入内核动态加载.

针对以上三点,我们逐一解决:

1. jpeg解码

先了解一下jpeg标准的编码过程:原始的一帧未压缩过的图像可以看成是RGB(红绿蓝)色彩空间上的一组向量集合,但在RGB空间是不利于数据压缩的,因此为了压缩先要把图像映射到利于压缩的YUV空间上(原因是因为人类的眼睛对于亮度差异的敏感度高于色彩变化,而YUV空间的一个基向量Y就是亮度), 这一步叫色彩空间转换.下一步可以在YUV空间上减少U(色调)和V(饱和度)的成分,也就是在亮度信息不减少的情况下移除部分色彩信息,谁叫人的眼睛对亮度的敏感优于对色彩的敏感呢.这一步叫缩减取样.下一步是将图像从色彩空间映射到频率空间,可以采用的变换方法有:离散余弦变换, 傅氏变换, 正弦变换等. 其中应用最广的是离散余弦变换(DCT).这一步是无损的,目的是为了在下一步称之为量化的过程中可以经过四舍五入删除高频量得到压缩后的矩阵.量化之后就是对这个矩阵的编码问题了.针对这个矩阵的分布特点, 采用”Z”字形的顺序扫描编排,然后进行RLE行程编码, 把大量连续重复的数据压缩.最后再用范式Huffman编码.要了解详细的过程,可以查看JPEG标准.

而解码就是以上编码的逆过程了.除非想要自己实现jpeg的编码和解码函数,我们可以不必细究这些过程,而是直接使用别人已经实现的jpeg编码解码库.在Linux平台下, 有libjpeg库, 它是完全用C语言编写的, 依照它的许可协议,可自由使用, 不是GPL协议,它可以用于商业目的.

libjpeg的6b版本有个问题,就是解码接口,它只接受文件源.打开源的函数jpeg_stdio_src(j_decompress_ptr cinfo, FILE *infile)要求解码源infile是文件.而我们希望解码的是直接来自映射内存中的数据.要解码内存流的话就要修改libjpeg的源码了,可以参考这里:~Simon_fu/?p=565目前libjpeg的最新版8c已经解决了这个接口不好的问题了,它增加了对内存流解码的支持.通过调用函数

jpeg_mem_src(&cinfo, fdmem, st.st_size);

就可以将保存在内存的jpeg格式数据作为源输入了.因此我们就用libjpeg 8c这个版本来解码.

用到的函数主要有:

初始化jpeg解压对象: /* init jpeg decompress object error handler */ cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo);绑定jpeg解压对象到输入流:/* bind jpeg decompress object to infile */#if READ_FILE// 从jpeg文件读入jpeg_stdio_src(&cinfo, infile);#elif READ_MEM// 从内存读入jpeg格式jpeg_mem_src(&cinfo, fdmem, st.st_size);#endif读取jpeg头部信息:/* read jpeg header */jpeg_read_header(&cinfo, TRUE);解压过程:/* decompress process */jpeg_start_decompress(&cinfo);

调用这个函数之后,可以得到jpeg图像的下面几个参数:

我们采用每扫描一行像素就输出到屏幕的方法的话,根据以上参数可以确定分配一行信息需要的缓冲区:

buffer = (unsigned char *)malloc(cinfo.output_width *cinfo.output_components);

总共需要扫描output_height行.

读取一行扫描数据并输出到LCD屏幕:y = 0;while (cinfo.output_scanline < cinfo.output_height) {jpeg_read_scanlines(&cinfo, &buffer, 1);if (fb_depth == 16) {// 如果显示设备色深是16位…} else if (fb_depth == 24) {// 如果显示设备色深是24位…} else if (fb_depth == 32) {// 如果显示设备色深是32位…}y++;}结束jpeg解码:/* finish decompress, destroy decompress object */jpeg_finish_decompress(&cinfo);jpeg_destroy_decompress(&cinfo);释放缓冲区:/* release memory buffer */free(buffer);2. 输出位图到LCD屏

通过framebuffer直接写屏的主要步骤有:

打开framebuffer设备:/* open framebuffer device */fbdev = fb_open("/dev/fb0");获取framebuffer设备参数:/* get status of framebuffer device */fb_stat(fbdev, &fb_width, &fb_height, &fb_depth);映射framebuffer设备到共享内存:screensize = fb_width * fb_height * fb_depth / 8;fbmem = fb_mmap(fbdev, screensize);直接对映射到那片内存进行写操作,LCD屏刷新刷新时就会反应到屏幕上去了.y = 0;while (cinfo.output_scanline < cinfo.output_height) {jpeg_read_scanlines(&cinfo, &buffer, 1);if (fb_depth == 16) {unsigned short color;for (x = 0; x < cinfo.output_width; x++) {color =RGB888toRGB565(buffer[x * 3],buffer[x * 3 + 1], buffer[x * 3 + 2]);fb_pixel(fbmem, fb_width, fb_height, x, y, color);}} else if (fb_depth == 24) {memcpy((unsigned char *) fbmem + y * fb_width * 3,buffer, cinfo.output_width * cinfo.output_components);} else if (fb_depth == 32) {// memcpy((unsigned char *) fbmem + y * fb_width * 4,// buffer, cinfo.output_width * cinfo.output_components);for (x = 0; x < cinfo.output_width; x++) {*(fbmem + y * fb_width * 4 + x * 4)= (unsigned char) buffer[x * 3 + 2];*(fbmem + y * fb_width * 4 + x * 4 + 1) = (unsigned char) buffer[x * 3 + 1];*(fbmem + y * fb_width * 4 + x * 4 + 2) = (unsigned char) buffer[x * 3 + 0];*(fbmem + y * fb_width * 4 + x * 4 + 3) = (unsigned char) 0;}}y++; // next scanline}卸载映射framebuffer的那部分内存:/* unmap framebuffer’s shared memory */fb_munmap(fbmem, screensize);关闭framebuffer设备:close(fbdev);为你的快乐而快乐的是朋友,为你的难过而难过的才是你的知己。

嵌入式Linux基于framebuffer的jpeg格式本地LCD屏显示

相关文章:

你感兴趣的文章:

标签云: