Chinaunix首页 | 论坛 | 博客
  • 博客访问: 248452
  • 博文数量: 37
  • 博客积分: 837
  • 博客等级: 准尉
  • 技术积分: 566
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-26 17:36
文章分类

全部博文(37)

文章存档

2012年(31)

2011年(6)

我的朋友

分类: LINUX

2012-12-05 19:52:13

开发环境Windows 7旗舰版,VMware7.1.2,Fedora9

编译环境:arm-linux-gcc 4.4.3

libjpeg版本:8d

开发板:micro2440

开发板内核版本:2.6.32.2

实现功能:

使用v4l2提供的API实现USB摄像头图像的采集,通过ligjpeg库解码采集到的MPJEG格式的视频流,在LCD上实时显示。由于摄像头只支持MJPEG格式,本文只介绍针对MJPEG格式的视频解码。

MJPEG,MJPG,JPEG,JPG扫盲:

MJPG是MJPEG的缩写,MJPEG(Motion JPEG:Motion Joint Photographic Experts Group)是一种视频压缩格式,其中每一帧图像分别使用JPEG编码。JPEG是一种针对图片广泛使用的有损压缩格式,文件后缀名一般采用.jpg或.jpeg。常说的JPG图片其实就是JPEG格式的图片。

准备工作:

构建8d版本的libjpeg交叉编译环境并移植该版本的动态库到micro2440。

在网上搜索相关资料介绍的libjpeg库大都是版本6,原本开发板移植的lib下也有版本6的动态链接库。但是版本6不支持内存jpeg图像的解压缩,需要修改源码或者自己封装函数来实现,比较麻烦。后来发现新版本提供了支持内存图像的解压缩函数jpeg_mem_src,于是第一步工作先搞定libjpeg。

源码下载地址:

下载,解压缩到当前工作目录:

# tar xvzf 

# cd jpeg-8d

交叉编译三步曲:

# ./configure --build=arm --host=arm-linux 

--prefix=/usr/local/arm/libjpeg/

# make

# make install

复制生成的动态链接库到micro2440文件系统的lib库下:

# cp -r /usr/local/arm/libjpeg/lib/libjpeg.so* /home/nfs/rootfs/lib

/home/nfs/rootfs是micro2440 nfs文件系统的根目录。

编译源码中的例程:

# arm-linux-gcc example.c -o example -I /usr/local/arm/libjpeg/include -L /usr/local/arm/libjpeg/lib -ljpeg

觉得编译程序不方便可以把生成的include下的头文件,以及lib下的库文件复制到交叉编译工具arm-linux-gcc相应的目录下。这样只需要编译时候加入-ljpeg选项即可。

v4l2 API采集USB摄像头图像:

v4l2 API函数有很多,网上有很多相关资料,也可以下载v4l2.pdf学习。为了清晰明了,这里只介绍最简单的采集流程所需要用到的函数。对摄像头设备的操作几乎都是使用ioctl函数完成,通过传递不同的参数VIDIOC_XXXX,实现不同的功能。

采集图像流程如下:

1.打开设备。


点击(此处)折叠或打开

  1. /* Example: Open device */
  2. video_fd = open(VIDEO_DEV, O_RDWR, 0);
  3. if (video_fd < 0) {
  4.     perror("Open camera device");
  5.     exit(EXIT_FAILURE);
  6. }


2.获取设备信息,查看是否有相应功能(VIDIOC_QUERYCAP)。


点击(此处)折叠或打开

  1. /* Example: Query capabilities */
  2. struct v4l2_capability cap;
  3. if (ioctl(video_fd, VIDIOC_QUERYCAP, &cap) == -1) {
  4.     perror("VIDIOC_QUERYCAP");
  5.     exit(EXIT_FAILURE);
  6. }
  7. printf("Information about camera capabilities:\n");
  8. printf("\tDriver name: %s\n", cap.driver);
  9. printf("\tCard name: %s\n", cap.card);
  10. printf("\tBus info: %s\n", cap.bus_info);
  11. printf("\tDriver version:%u.%u.%u\n", (cap.version>>16)&0xff,
  12.             (cap.version>>8)&0xff, cap.version&0xff);
  13. printf("\tCapabilities: %x\n", cap.capabilities);
  14. if (0 == cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
  15.     fprintf(stderr, "Can't support video capture!\n");
  16.     exit(EXIT_FAILURE);
  17. }
  18. if (0 == cap.capabilities & V4L2_CAP_STREAMING) {
  19.     fprintf(stderr, "Can't supports streaming I/O method\n");
  20.     exit(EXIT_FAILURE);
  21. }


3.获取视频帧格式(VIDIOC_G_FMT)。


点击(此处)折叠或打开

  1. /* Example: Get the data format */
  2. struct v4l2_format format;
  3. memset(&format, 0, sizeof(format));
  4. format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  5. if (ioctl(video_fd, VIDIOC_G_FMT, &format) < 0) {
  6.     perror("VIDIOC_G_FMT");
  7.     exit(EXIT_FAILURE);
  8. }


4.设置视频帧格式(VIDIOC_S_FMT)。


点击(此处)折叠或打开

  1. /* Example: Set the data format */
  2. format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
  3. if (ioctl(video_fd, VIDIOC_S_FMT, &format) < 0) {
  4.     perror("VIDIOC_S_FMT");
  5.     exit(EXIT_FAILURE);
  6. }


5.向驱动申请帧缓冲,申请用户空间物理内存,并将申请到的帧缓冲映射到用户空间的物理内存。把申请到的帧缓冲全部入队列,以便存放采集到的数据(VIDIOC_REQBUFSVIDIOC_QUERYBUF,VIDIOC_QBUF)。


点击(此处)折叠或打开

  1. /* Example: Mapping buffers */
  2. struct v4l2_requestbuffers reqbuf;
  3. struct {
  4. void *start;
  5. size_t length;
  6. } *buffers;
  7. unsigned int i;
  8. memset (&reqbuf, 0, sizeof (reqbuf));
  9. reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  10. reqbuf.memory = V4L2_MEMORY_MMAP;
  11. reqbuf.count = 20;
  12. if (-1 == ioctl (fd, VIDIOC_REQBUFS, &reqbuf)) {
  13.   if (errno == EINVAL)
  14.   printf ("Video capturing or mmap-streaming is not supported\n");
  15.   else
  16.   perror ("VIDIOC_REQBUFS");
  17.   exit (EXIT_FAILURE);
  18. }
  19. /* We want at least five buffers. */
  20. if (reqbuf.count < 5) {
  21.   /* You may need to free the buffers here. */
  22.   printf ("Not enough buffer memory\n");
  23.   exit (EXIT_FAILURE);
  24. }
  25. buffers = calloc (reqbuf.count, sizeof (*buffers));
  26. assert (buffers != NULL);
  27. for (i = 0; i < reqbuf.count; i++) {
  28.   struct v4l2_buffer buffer;
  29.   memset (&buffer, 0, sizeof (buffer));
  30.   buffer.type = reqbuf.type;
  31.   buffer.memory = V4L2_MEMORY_MMAP;
  32.   buffer.index = i;
  33.   if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buffer)) {
  34.   perror ("VIDIOC_QUERYBUF");
  35.   exit (EXIT_FAILURE);
  36.   }
  37.   buffers[i].length = buffer.length; /* remember for munmap() */
  38.   buffers[i].start = mmap (NULL, buffer.length,
  39.   PROT_READ | PROT_WRITE, /* recommended */
  40.   MAP_SHARED, /* recommended */
  41.   fd, buffer.m.offset);
  42.   if (MAP_FAILED == buffers[i].start) {
  43.   /* If you do not exit here you should unmap() and free()
  44.   the buffers mapped so far. */
  45.   perror ("mmap");
  46.   exit (EXIT_FAILURE);
  47.   }
  48.   if (-1 == ioctl (fd, VIDIOC_QBUF, &buffer)) {
  49.   perror ("VIDIOC_QBUF");
  50.   exit (EXIT_FAILURE);
  51.   }
  52. }


6.开始视频采集(VIDIOC_STREAMON)。


点击(此处)折叠或打开

  1. /* Example: Start streaming I/O */
  2. enum v4l2_buf_type type;
  3. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  4. if (-1 == ioctl(video_fd, VIDIOC_STREAMON, &type)) {
  5.     perror("VIDIOC_STREAMON");
  6.     exit(EXIT_FAILURE);
  7. }


7.采集图像并处理(VIDIOC_DQBUF,VIDIOC_QBUF)


点击(此处)折叠或打开

  1. /* Example: Capture and process image */
  2. int i,j,imagesize;
  3. while(1) {
  4.   /* Dequeue buffer*/
  5.     memset(&buf, 0, sizeof(buf));
  6.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  7.     buf.memory = V4L2_MEMORY_MMAP;
  8.     if (ioctl(video_fd, VIDIOC_DQBUF, &buf) < 0) {
  9.         perror("VIDIOC_DQBUF");
  10.         exit(EXIT_FAILURE);
  11.     }

  12.   /* Check buffman table, these code are optional */
  13.     for (j=0; j<buf.bytesused; j++) {
  14.         if ((buffers[buf.index].start[j] == 0x000000FF)
  15.                 && (buffers[buf.index].start[j+1] == 0x000000C4)) {
  16.             printf("huffman table finded! \nFFC4 = %d \n", j);
  17.             break;
  18.         }
  19.     }
  20.     if (j == buf.bytesused)
  21.         printf("huffman table don't exist! \n");
  22.   /* SOI = Start Of Image = "FFD8", EOI = End Of Image = "FFD9"
  23.   Find the start of JPEG image */
  24.     for (i=0; i<buf.bytesused; i++) {
  25.         if ((buffers[buf.index].start[i] == 0x000000FF)
  26.                 && (buffers[buf.index].start[i+1] == 0x000000D8)) {
  27.             break;
  28.         }
  29.     }
  30.     imagesize = buf.bytesused - i;
  31.   /* Process image */
  32.   process_jpeg_image(&buffers[buf.index].start[i], imagesize);
  33.   
  34.   /* Queue buffer*/
  35.   if (ioctl(video_fd, VIDIOC_QBUF, &buf) < 0) {
  36.         perror("VIDIOC_QBUF");
  37.         exit(EXIT_FAILURE);
  38.     }
  39. }


8.停止视频采集(VIDIOC_STREAMOFF)。


点击(此处)折叠或打开

  1. /* Example: Stop streaming I/O */
  2. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  3. if (-1 == ioctl(video_fd, VIDIOC_STREAMOFF, &type)) {
  4.     perror("VIDIOC_STREAMOFF");
  5.     exit(EXIT_FAILURE);
  6. }


9.释放内存,关闭设备


点击(此处)折叠或打开

  1. /* Example: Cleanup and close device*/
  2. for (i = 0; i < reqbuf.count; i++)
  3.   munmap (buffers[i].start, buffers[i].length);
  4. free(buffers);
  5. close(fd);


libjpeg解码内存中的JPEG帧:

本节解压缩代码以及写数据到LCD framebuffer的整个流程,对应于上一小节第七步中的虚构出来的函数process_jpeg_image

libjpeg源码中有example.c程序可供参考。解码JPEG文件的流程跟解码内存中JPEG数据的流程大致相同,唯一的区别是指定数据源的函数不同。

解码JPEG文件使用函数:

void jpeg_stdio_src (j_decompress_ptr cinfo, FILE * infile);

解码内存中JPEG数据使用函数:

void jpeg_mem_src (j_decompress_ptr cinfo,unsigned char * inbuffer,  unsigned long insize);

首先定义两个结构体:


  1. struct jpeg_decompress_struct cinfo;
  2. struct jpeg_error_mgr jerr;

解码流程如下:

1.初始化解压缩对象对应的结构体cinfo。


点击(此处)折叠或打开

  1. /* Step 1: allocate and initialize JPEG decompression object */
  2. /* init jpeg decompress object error handler */
  3. cinfo.err = jpeg_std_error(&jerr);
  4. jpeg_create_decompress(&cinfo);


2.指定JPEG数据源。这里的代码传递的参数是上一小节通过v4l2 API采集到的数据帧对应的数据。


点击(此处)折叠或打开

  1. /* Step 2: specify data source (eg, a file) */
  2. jpeg_mem_src(&cinfo, &buffers[buf.index].start[i], imagesize);


3.读取JPEG数据头。


点击(此处)折叠或打开

  1. /* Step 3: read file parameters with jpeg_read_header() */
  2. (void) jpeg_read_header(&cinfo, TRUE);


4.设置解压缩参数。如果不修改采用jpeg_read_header()给的默认设置。结构体cinfo的成员scale_num和scale_denom控制输出图像的缩放比例,支持1/2、1/4、1/8三种格式。image_width和image_height控制输出图像的大小。


点击(此处)折叠或打开

  1. /* Step 4: set parameters for decompression(optional) */
  2. /* output 1/2 scale image */
  3. cinfo.scale_num = 1;
  4. cinfo.scale_denom = 2;


5.开始解压缩


点击(此处)折叠或打开

  1. /* Step 5: Start decompressor */
  2. (void) jpeg_start_decompress(&cinfo);

6.读取数据并处理,在这里是将读出来的RGB888数据转换成RGB565,方便复制到RGB565LCD对应的framebuffer中,或者这里直接写到framebuffer中。


点击(此处)折叠或打开

  1. /* Step 6: while (scan lines remain to be read) */
  2. /*         jpeg_read_scanlines(...); */
  3. unsigned char *jpeg_buf = (unsigned char *) malloc(cinfo.output_width *
  4.                 cinfo.output_components);
  5. int x = 0;
  6. int y = 0;
  7. while (cinfo.output_scanline < cinfo.output_height) {
  8.     (void)jpeg_read_scanlines(&cinfo, &jpeg_buf, 1);
  9.     for (x = 0; x < cinfo.output_width; x++) {
  10.          rgb565_buf[x + y * fb.width] = RGB888_TO_RGB565(jpeg_buf[x * 3],\
  11.                         jpeg_buf[x * 3 + 1], jpeg_buf[x * 3 + 2]);
  12.     }
  13.     y++;    // next scanline
  14. }


7.完成解压缩,并释放解压缩对象相应结构体cinfo。


点击(此处)折叠或打开

  1. /* Step 7: Finish decompression and Release JPEG decompression object*/
  2. (void) jpeg_finish_decompress(&cinfo);
  3. jpeg_destroy_decompress(&cinfo);

阅读(5400) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~