开发环境: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.打开设备。
- /* Example: Open device */
- video_fd = open(VIDEO_DEV, O_RDWR, 0);
- if (video_fd < 0) {
- perror("Open camera device");
- exit(EXIT_FAILURE);
- }
2.获取设备信息,查看是否有相应功能(VIDIOC_QUERYCAP)。
- /* Example: Query capabilities */
- struct v4l2_capability cap;
- if (ioctl(video_fd, VIDIOC_QUERYCAP, &cap) == -1) {
- perror("VIDIOC_QUERYCAP");
- exit(EXIT_FAILURE);
- }
- printf("Information about camera capabilities:\n");
- printf("\tDriver name: %s\n", cap.driver);
- printf("\tCard name: %s\n", cap.card);
- printf("\tBus info: %s\n", cap.bus_info);
- printf("\tDriver version:%u.%u.%u\n", (cap.version>>16)&0xff,
- (cap.version>>8)&0xff, cap.version&0xff);
- printf("\tCapabilities: %x\n", cap.capabilities);
- if (0 == cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
- fprintf(stderr, "Can't support video capture!\n");
- exit(EXIT_FAILURE);
- }
- if (0 == cap.capabilities & V4L2_CAP_STREAMING) {
- fprintf(stderr, "Can't supports streaming I/O method\n");
- exit(EXIT_FAILURE);
- }
3.获取视频帧格式(VIDIOC_G_FMT)。
- /* Example: Get the data format */
- struct v4l2_format format;
- memset(&format, 0, sizeof(format));
- format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (ioctl(video_fd, VIDIOC_G_FMT, &format) < 0) {
- perror("VIDIOC_G_FMT");
- exit(EXIT_FAILURE);
- }
4.设置视频帧格式(VIDIOC_S_FMT)。
- /* Example: Set the data format */
- format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
- if (ioctl(video_fd, VIDIOC_S_FMT, &format) < 0) {
- perror("VIDIOC_S_FMT");
- exit(EXIT_FAILURE);
- }
5.向驱动申请帧缓冲,申请用户空间物理内存,并将申请到的帧缓冲映射到用户空间的物理内存。把申请到的帧缓冲全部入队列,以便存放采集到的数据(VIDIOC_REQBUFS, VIDIOC_QUERYBUF,VIDIOC_QBUF)。
- /* Example: Mapping buffers */
- struct v4l2_requestbuffers reqbuf;
- struct {
- void *start;
- size_t length;
- } *buffers;
- unsigned int i;
- memset (&reqbuf, 0, sizeof (reqbuf));
- reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- reqbuf.memory = V4L2_MEMORY_MMAP;
- reqbuf.count = 20;
- if (-1 == ioctl (fd, VIDIOC_REQBUFS, &reqbuf)) {
- if (errno == EINVAL)
- printf ("Video capturing or mmap-streaming is not supported\n");
- else
- perror ("VIDIOC_REQBUFS");
- exit (EXIT_FAILURE);
- }
- /* We want at least five buffers. */
- if (reqbuf.count < 5) {
- /* You may need to free the buffers here. */
- printf ("Not enough buffer memory\n");
- exit (EXIT_FAILURE);
- }
- buffers = calloc (reqbuf.count, sizeof (*buffers));
- assert (buffers != NULL);
- for (i = 0; i < reqbuf.count; i++) {
- struct v4l2_buffer buffer;
- memset (&buffer, 0, sizeof (buffer));
- buffer.type = reqbuf.type;
- buffer.memory = V4L2_MEMORY_MMAP;
- buffer.index = i;
- if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buffer)) {
- perror ("VIDIOC_QUERYBUF");
- exit (EXIT_FAILURE);
- }
- buffers[i].length = buffer.length; /* remember for munmap() */
- buffers[i].start = mmap (NULL, buffer.length,
- PROT_READ | PROT_WRITE, /* recommended */
- MAP_SHARED, /* recommended */
- fd, buffer.m.offset);
- if (MAP_FAILED == buffers[i].start) {
- /* If you do not exit here you should unmap() and free()
- the buffers mapped so far. */
- perror ("mmap");
- exit (EXIT_FAILURE);
- }
- if (-1 == ioctl (fd, VIDIOC_QBUF, &buffer)) {
- perror ("VIDIOC_QBUF");
- exit (EXIT_FAILURE);
- }
- }
6.开始视频采集(VIDIOC_STREAMON)。
- /* Example: Start streaming I/O */
- enum v4l2_buf_type type;
- type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (-1 == ioctl(video_fd, VIDIOC_STREAMON, &type)) {
- perror("VIDIOC_STREAMON");
- exit(EXIT_FAILURE);
- }
7.采集图像并处理(VIDIOC_DQBUF,VIDIOC_QBUF)。
- /* Example: Capture and process image */
- int i,j,imagesize;
- while(1) {
- /* Dequeue buffer*/
- memset(&buf, 0, sizeof(buf));
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- if (ioctl(video_fd, VIDIOC_DQBUF, &buf) < 0) {
- perror("VIDIOC_DQBUF");
- exit(EXIT_FAILURE);
- }
- /* Check buffman table, these code are optional */
- for (j=0; j<buf.bytesused; j++) {
- if ((buffers[buf.index].start[j] == 0x000000FF)
- && (buffers[buf.index].start[j+1] == 0x000000C4)) {
- printf("huffman table finded! \nFFC4 = %d \n", j);
- break;
- }
- }
- if (j == buf.bytesused)
- printf("huffman table don't exist! \n");
- /* SOI = Start Of Image = "FFD8", EOI = End Of Image = "FFD9"
- Find the start of JPEG image */
- for (i=0; i<buf.bytesused; i++) {
- if ((buffers[buf.index].start[i] == 0x000000FF)
- && (buffers[buf.index].start[i+1] == 0x000000D8)) {
- break;
- }
- }
- imagesize = buf.bytesused - i;
- /* Process image */
- process_jpeg_image(&buffers[buf.index].start[i], imagesize);
-
- /* Queue buffer*/
- if (ioctl(video_fd, VIDIOC_QBUF, &buf) < 0) {
- perror("VIDIOC_QBUF");
- exit(EXIT_FAILURE);
- }
- }
8.停止视频采集(VIDIOC_STREAMOFF)。
- /* Example: Stop streaming I/O */
- type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (-1 == ioctl(video_fd, VIDIOC_STREAMOFF, &type)) {
- perror("VIDIOC_STREAMOFF");
- exit(EXIT_FAILURE);
- }
9.释放内存,关闭设备
- /* Example: Cleanup and close device*/
- for (i = 0; i < reqbuf.count; i++)
- munmap (buffers[i].start, buffers[i].length);
- free(buffers);
- 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);
首先定义两个结构体:
- struct jpeg_decompress_struct cinfo;
- struct jpeg_error_mgr jerr;
解码流程如下:
1.初始化解压缩对象对应的结构体cinfo。
- /* Step 1: allocate and initialize JPEG decompression object */
- /* init jpeg decompress object error handler */
- cinfo.err = jpeg_std_error(&jerr);
- jpeg_create_decompress(&cinfo);
2.指定JPEG数据源。这里的代码传递的参数是上一小节通过v4l2 API采集到的数据帧对应的数据。
- /* Step 2: specify data source (eg, a file) */
- jpeg_mem_src(&cinfo, &buffers[buf.index].start[i], imagesize);
3.读取JPEG数据头。
- /* Step 3: read file parameters with jpeg_read_header() */
- (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控制输出图像的大小。
- /* Step 4: set parameters for decompression(optional) */
- /* output 1/2 scale image */
- cinfo.scale_num = 1;
- cinfo.scale_denom = 2;
5.开始解压缩
- /* Step 5: Start decompressor */
- (void) jpeg_start_decompress(&cinfo);
6.读取数据并处理,在这里是将读出来的RGB888数据转换成RGB565,方便复制到RGB565LCD对应的framebuffer中,或者这里直接写到framebuffer中。
- /* Step 6: while (scan lines remain to be read) */
- /* jpeg_read_scanlines(...); */
- unsigned char *jpeg_buf = (unsigned char *) malloc(cinfo.output_width *
- cinfo.output_components);
- int x = 0;
- int y = 0;
- while (cinfo.output_scanline < cinfo.output_height) {
- (void)jpeg_read_scanlines(&cinfo, &jpeg_buf, 1);
- for (x = 0; x < cinfo.output_width; x++) {
- rgb565_buf[x + y * fb.width] = RGB888_TO_RGB565(jpeg_buf[x * 3],\
- jpeg_buf[x * 3 + 1], jpeg_buf[x * 3 + 2]);
- }
- y++; // next scanline
- }
7.完成解压缩,并释放解压缩对象相应结构体cinfo。
- /* Step 7: Finish decompression and Release JPEG decompression object*/
- (void) jpeg_finish_decompress(&cinfo);
- jpeg_destroy_decompress(&cinfo);