UVC摄像头嵌入式Linux应用

由于是第一次接触UVC,所以内容会比较杂。文章内容多为参考整合。

参考链接:
【1】添加内核支持部分:https://blog.csdn.net/u010034969/article/details/115210890
【2】100ask-摄像头V4L2编程应用开发:http://100ask.org/pages/cfba84/#_7-1-v4l2%E7%AE%80%E4%BB%8B
【3】完整代码来源:https://download.csdn.net/download/luckywang1103/6707987

    之前在淘宝上买了个USB摄像头,最近想着在我的imx6ull开发板上应用试试,在这里做个记录。
下面是从淘宝商品详情找到的产品参数:
产品参数
附上目录:

1.相关概念

1.1 UVC

    UVC全称为USB Video Class,即:USB视频类,是一种为USB视频捕获设备定义的协议标准。UVC是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已成为USB org标准之一。支持 USB Video Class (UVC) standard 1.1可以让相机在所有的作业系统以及平台中使用(Windows, Linux, Mac etc.)。用户只需连接相机便可进行图像传输,而无需安装任何驱动程序

    简单点说,就是只要USB摄像头是UVC摄像头,那这个摄像头的驱动就遵循一个通用的格式,可以实现免驱的操作。在Linux系统中,UVC驱动的支持在Linux Kernel 2.4之后被增加到内核中。但是为了让内核识别到这款摄像头,还要告诉内核这个USB的ID是UVC设备才行。

UVC设备的PID和VID简介

    每个USB设备都有VID(Vender ID,供应商识别码)和PID(Product ID,产品识别码),两者的长度均为2Byte。PID和VID是主机识别USB设备时使用。
    主机检测到USB设备后,首先会通过USB Class查询插入的是什么设备。检测到插入设备的类型后,通过读取和检索VID和PID,主机就能知道当前连接的设备的类型,并能了解应该给这个USB设备加载什么样的驱动程序进行通信。

例如,我们插入了U盘

  1. 系统首先会检测插入USB设备的Class。
  2. U盘的Class是0x08 —— Mass Storage。那么就会按照USB大容量存储设备的方式对USB设备进行操作。
  3. 同时,系统还会查看设备的VID和PID,识别该U盘的制造商和型号,并查看是否需要加载特殊的驱动。

1.2 Linux下V4L2简介

    V4L2是Video for linux2的简称,是linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写或是MMAP。
    摄像头的设备节点在/dev/video下,如果只有一个视频设备,通过为/dev/video0,也有可能是/dev/uvcvideo。

1.3 V4L2视频采集原理

参考100ask-摄像头V4L2编程应用开发

使用V4L2采集图像,需要对收集到的图像进行如下步骤:

  1. 分配帧缓冲区
  2. 将分配到的帧缓冲区从内核空间映射到用户空间
  3. 将申请到的帧缓冲区在视频采集输入队列中排队
  4. 等待数据的到来

数据传输流程:

  1. 首先驱动程序采集一帧图像数据,并放入第一个帧缓冲区
  2. 第一个帧缓冲区存满一帧图像数据后,将该缓冲区放入视频采集输出队列,等待应用程序取出
  3. 应用程序取出图像数据后可以对图像数据进行处理或存储操作,然后将该帧缓冲区放入视频采集输入队列的尾部
  4. 驱动程序采集下一帧数据,放入第二个缓冲区,存满一帧数据后放入输出队列

循环上面几步,就可以实现循环采集。流程图如下:
在这里插入图片描述

1.4 V4L2应用程序实现流程

V4L2视频采集一般分为以下5步:

  1. 打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
  2. 申请图像帧缓冲,并进行内存映射,将这些帧缓冲从内核空间映射到用户空间,便于应用程序读取、处理图像数据;
  3. 帧缓冲进行入队操作,启动视频采集;
  4. 驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
  5. 释放资源,停止采集工作。
    在这里插入图片描述

1.4.* 一些常用的命令表示符

(1)VIDIOC_REQBUFS:分配内存;

(2)VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;

(3)VIDIOC_QUERYCAP:查询驱动功能;

(4)VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式;

(5)VIDIOC_S_FMT:设置当前驱动的视频捕获格式;

(6)VIDIOC_G_FMT:读取当前驱动的视频捕获格式;

(7)VIDIOC_TRY_FMT:验证当前驱动的显示格式;

(8)VIDIOC_CROPCAP:查询驱动的修剪功能;

(9)VIDIOC_S_CROP:设置视频信号的边框;

(10)VIDIOC_G_CROP:读取视频信号的边框;

(11)VIDIOC_QBUF:把数据从缓存中读取出来;

(12)VIDIOC_DQBUF:把数据放回缓存队列;

(13)VIDIOC_STREAMOP:开始视频显示函数;

(14)VIDIOC_STREAMOFF:结束视频显示函数;

(15)VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC;

2.环境搭建

本部分参考https://blog.csdn.net/u010034969/article/details/115210890

    首先需要在内核中添加UVC支持,为了让这个板子正常识别到这个摄像头,需要在内核中打开UVC摄像头的编译,并将摄像头的PID和VID加入到代码中。

2.1 内核添加UVC支持

2.1.1 查看PID与VID

将摄像头插入电脑USB接口,借助windows的设备管理器,查看对应摄像头的属性。并在详细信息中找到硬件ID。
UVC:PID&VID
得到其vid和pid:

  • VID:0x0C45
  • PID:0x64AB

保存PID和VID备用。

2.1.2 内核图形配置界面添加UVC设备支持

进入内核根目录,make menuconfig进入内核编译配置。打开如下几项

Device Drives
	---> Multimedia support
		---> <*>Media USB Adapters
		---> V4L platform devices
			---> <*>SoC camera support
			---> <*>platform camera support

下面直接引用参考链接【1】的图。

  1. 进入Device Drivers → Multimedia support → Media USB Adapters,找到并开启USB Video Class(UVC)如下图:
    在这里插入图片描述
    如果找不到这项,说明是相关的依赖项未打开。
    可以搜索关键词USB_VIDEO_CLASS,能看到这项的依赖项:
    在这里插入图片描述
    将所有依赖项设置为Y(编译进内核)即可。
  2. 同样,Device Drivers → Multimedia support → V4L platform devices下,开启如下两项:
    在这里插入图片描述

2.1.3 内核中增加摄像头的PID和VID

打开内核代码driver/media/usb/uvc/uvc_driver.c,找到usb_device_id结构体uvc_ids。
仿照其他摄像头的代码,添加摄像头的PID和VID:

static struct usb_device_id uvc_ids[] = {
//下面是新增的摄像头的id信息,VID是0x05A3,PID是0x9601
    {
        .match_flags = USB_DEVICE_ID_MATCH_DEVICE
        | USB_DEVICE_ID_MATCH_INT_INFO,
        .idVendor             = 0x0C45,  //摄像头的VID
        .idProduct            = 0x64AB,
        .bInterfaceClass      = USB_CLASS_VIDEO,
        .bInterfaceSubClass   = 1,
        .bInterfaceProtocol   = 0,
        .driver_info          = UVC_QUIRK_RESTRICT_FRAME_RATE
    },
//下面是原有的信息
    /* LogiLink Wireless Webcam */
    {
        .match_flags      = USB_DEVICE_ID_MATCH_DEVICE
        | USB_DEVICE_ID_MATCH_INT_INFO,
        .idVendor         = 0x0416,
        .idProduct        = 0xa91a,
        .bInterfaceClass  = USB_CLASS_VIDEO,
        .bInterfaceSubClass   = 1,
        .bInterfaceProtocol   = 0,
        .driver_info      = UVC_QUIRK_PROBE_MINMAX
    },
    //省略...
}

 
 

完成后make整个内核,并将内核烧写入硬件平台中。

启动平台后插入摄像头,如果出现如下uvc的打印信息,显示出了摄像头的PID&VID并挂在了input下,则说明添加成功。

[   55.563704] usb 2-1.3: new high-speed USB device number 3 using ci_hdrc
[   55.703344] uvcvideo: Found UVC 1.00 device Integrated_Webcam_HD (0c45:64ab)
[   55.733330] input: Integrated_Webcam_HD as /devices/platform/soc/2100000.aips-bus/2184200.usb/ci_hdrc.1/usb2/2-1/2-1.3/2-1.3:1.0/input/input4

可以通过如下指令查看新增的设备节点。

/lib/modules # ls /dev/
video0  video1

在这里新增的是/dev/video1。

2.2 环境安装

2.2.1 JPEGLIB

摄像头输出数据为MJPEG格式(怎么查看支持的输出格式在后面有写到(VIDIOC_G_FMT指令)),需要使用jpeglib库将数据转化为rbg的格式。
官网下载安装包,解压。

./configure CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld --host=arm-linux-gnueabihf --prefix=/home/book/linux_study/IMX6ULL/Project/Downloads/jpeg-9e/temp --exec-prefix=/home/book/linux_study/IMX6ULL/Project/Downloads/jpeg-9e/temp --enable-shared --enable-static

make
make install

这里的CC、LD用于指定编译器与链接器,host指定运行主机,–prefix指定安装目录,安装完成后文件都在–prefix指定的目录中,–enable-shared 选项开启动态库支持。

安装完成之后,需要把对应的头文件和库文件移植到运行环境中,通过下面的语句查看交叉编译链的库文件和头文件所在目录。

echo 'main(){}' | arm-linux-gnueabihf-gcc -E -v -

进入上面指定的目录/home/book/linux_study/IMX6ULL/Project/Downloads/jpeg-9e/temp,复制lib与include下的文件到对应的目录。

cp lib/* -rfd /home/book/linux_study/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/lib/
cp include/* /home/book/linux_study/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/../arm-linux-gnueabihf/libc/usr/include

以及同样需要复制到开发板的文件系统中,对应/usr/lib/usr/include

至此jpeglib移植完成,在对程序进行交叉编译时,需要加上链接选项--ljpeg
头文件包含:#include <jpeglib.h>,可在测试程序中包含头文件并编译检查是否移植成功。

3. 各步骤详细编程实现

V4L2的代码主要在video2lcd/video/v4l2.c文件中。

本章内容参考搬运自100ask-摄像头V4L2编程应用开发

3.1. 打开设备

devname = "/dev/video1";
if(argc==2) devname = argv[1];

int video_fd = open(devname, O_RDWR, 0); //打开摄像头设备,使用阻塞方式打开
if (video_fd <0)
{
printf(“open device error\n”);
return -1;
}

3.2. 查询设备属性

    通过VIDIOC_QUERYCAP命令来查询driver是否合乎规范。因为V4L2要求所有driver和device都支持这个ioctl。所以,通过VIDIOC_QUERYCAP命令是否成功来判断当前device和driver是否符合V4L2规范。当然,这个命令执行成功的同时还能够得到设备足够的信息,如struct v4l2_capability结构体所示内容。19~22行代码检查当前设备是否为capture设备,并检查使用内存映射还是直接读的方式获取图像数据。

1	struct v4l2_capability tV4l2Cap;
2	iError = ioctl(video_fd , VIDIOC_QUERYCAP, &tV4l2Cap);
3	memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
4	iError = ioctl(video_fd , VIDIOC_QUERYCAP, &tV4l2Cap);
5	if (iError) {
6	    DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
7	    goto err_exit;
8	}
9	
10	if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))//检测是否为capture设备
11	{
12	    DBG_PRINTF("%s is not a video capture device\n", strDevName);
13	    goto err_exit;
14	}
15	
16	if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
17	    DBG_PRINTF("%s supports streaming i/o\n", strDevName);
18	}
19	
20	if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
21	    DBG_PRINTF("%s supports read i/o\n", strDevName);
22	}

 
 

下面是结构体的具体内容

01 struct v4l2_capability
02 {
03     __u8 driver[16];       // 驱动名字
04     __u8 card[32];         // 设备名字
05     __u8 bus_info[32];     // 设备在系统中的位置
06     __u32 version;         // 驱动版本号
07     __u32 capabilities;    // 设备支持的操作
08     __u32 reserved[4];     // 保留字段
09 };

3.3. 获取视频格式

结构体v4l2_fmtdesc的内容如下,该结构体描述当前camera支持的格式信息。

01 struct v4l2_fmtdesc
02 {
03     __u32 index;               // 要查询的格式序号,应用程序设置
04     enum v4l2_buf_type type;   // 帧类型,应用程序设置
05     __u32 flags;               // 是否为压缩格式
06     __u8 description[32];      // 格式名称
07     __u32 pixelformat;         //所支持的格式
08     __u32 reserved[4];         // 保留
09 };

    使用VIDIOC_ENUM_FMT命令查询当前camera支持的所有格式。
    struct v4l2_fmtdesc结构体中index要设置,从0开始;
    enum v4l2_buf_type type也要设置,如果使用的是camera设备,则enum v4l2_buf_type type要设置为V4L2_BUF_TYPE_VIDEO_CAPTURE,因为camera是CAPTURE设备。
    结构体中的其他内容driver会填充。其中__u32 pixelformat参数在设置图像帧格式时需要使用。

struct v4l2_fmtdesc tFmtDesc;
memset(&tFmtDesc, 0, sizeof(tFmtDesc));
tFmtDesc.index = 0;
tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {//查询当前camera支持的所有格式。
    if (isSupportThisFormat(tFmtDesc.pixelformat))
    {
        ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
        break;
    }
    printf("enum fmt desc\n");
	printf("%d.%s\n",tFmtDesc.index,tFmtDesc.description);
    tFmtDesc.index++;
}//经查询,买的摄像头只支持MJPEG

3.4. 设置图像帧格式

    设置图像帧格式需要用到struct v4l2_format结构体,该结构体描述每帧图像的具体格式,包括帧类型以及图像的长、宽等信息。

01 struct v4l2_format
02 {
03     enum v4l2_buf_type type;          // 帧类型,应用程序设置
04     union fmt
05     {
06         struct v4l2_pix_format pix;   // 视频设备使用
07         structv 4l2_window win;
08         struct v4l2_vbi_format vbi;
09         struct v4l2_sliced_vbi_format sliced;
10         __u8 raw_data[200];
11     };
12 };

    struct v4l2_format结构体需要设置enum v4l2_buf_type type==和union fmt中的struct v4l2_pix_format pix

enum v4l2_buf_type type因为使用的是camera设备,camera是CAPTURE设备,所以设置成V4L2_BUF_TYPE_VIDEO_CAPTURE。
struct v4l2_pix_format pix设置一帧图像的长、宽和格式等,由于要适配LCD输出,所以长、宽设置为LCD支持的长、宽,如124~125行所示。

struct v4l2_format tV4l2Fmt;					//设置获取视频的格式
memset( &tV4l2Fmt, 0, sizeof(tV4l2Fmt));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//视频数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
tV4l2Fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//视频源的格式为JPEG或YUN4:2:2或RGB
tV4l2Fmt.fmt.pix.width = iLcdWidth;					//设置视频宽度
tV4l2Fmt.fmt.pix.height = iLcdHeight;					//设置视频高度
tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;	
if (ioctl(fd, VIDIOC_S_FMT, &tV4l2Fmt) < 0)		//使配置生效
{
	printf("set format failed\n");
	return -1;
}

设置后可通过VIDIOC_G_FMT读取格式查看是否设置成功。

3.5. 申请缓冲区

相关结构体如下,该结构体描述申请的缓冲区的基本信息。

01 struct v4l2_requestbuffers
02 {
03     __u32 count;                    // 缓冲区内缓冲帧的数目
04     enum v4l2_buf_type type;        // 缓冲帧数据格式
05     enum v4l2_memorymemory;         // 区别是内存映射还是用户指针方式
06     __u32 reserved[2];
07 };
  • 申请一个拥有四个缓冲帧的缓冲区,__u32 count为缓冲帧的数目;
  • enum v4l2_buf_type type和前文一样,同样设置成V4L2_BUF_TYPE_VIDEO_CAPTURE
  • enum v4l2_memorymemory用来区分是内存映射还是用户指针,我们使用内存映射的方式,所以设置成V4L2_MEMORY_MMAP
struct v4l2_requestbuffers tV4l2ReqBuffs;				//申请帧缓冲
/* request buffers */
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
tV4l2ReqBuffs.count = NB_BUFFER;					//4,缓存数量,即可保存的图片数量
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;			//存储类型:V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR

iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);//使配置生效
if (iError)
{
DBG_PRINTF(“Unable to allocate buffers.\n”);
goto err_exit;
}

3.6. 对申请的帧缓冲内存映射

相关结构体如下,该结构体表示一帧图像数据的基本信息,包含序号、缓冲帧长度和缓冲帧地址等信息。

01 struct v4l2_buffer
02 {
03     __u32 index;                    //buffer 序号
04     enum v4l2_buf_type type;        //buffer 类型
05     __u32 byteused;                 //buffer 中已使用的字节数
06     __u32 flags;                    // 区分是MMAP 还是USERPTR
07     enum v4l2_field field;
08     struct timeval timestamp;       // 获取第一个字节时的系统时间
09     struct v4l2_timecode timecode;
10     __u32 sequence;                 // 队列中的序号
11     enum v4l2_memory memory;        //IO 方式,被应用程序设置
12     union m
13     {
14         __u32 offset;               // 缓冲帧地址,只对MMAP 有效
15         unsigned long userptr;
16     };
17     __u32 length;                   // 缓冲帧长度
18     __u32 input;
19     __u32 reserved;
20 };

    将内核空间的帧缓冲映射到用户空间,需要两个数据接收帧缓冲的长度地址
    这里我们需要自己定义一个结构体,其中iVideoBufMaxLen接收帧缓冲的长度,pucVideBuf接收帧缓冲地址。

16 struct VideoDevice {
17     int iFd;				//设备文件句柄
18     int iPixelFormat;	//支持的像素格式
19     int iWidth;			//视频宽度与高度
20     int iHeight;
21
22     int iVideoBufCnt;	//表示缓冲区个数
23     int iVideoBufMaxLen;	//表示接收帧缓冲区长度
24     int iVideoBufCurIndex;//表示当前缓冲区索引
25     unsigned char *pucVideBuf[NB_BUFFER];	//用于存储各个接收缓冲区的地址。
26
27     /* 函数 */
28     PT_VideoOpr ptOPr;
29 };

mmap原型:

void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);

  
  
  • 1

参数具体的含义:
addr:映射起始地址,一般为NULL,让内核自动选择;
length:被映射内存块的长度;
prot:标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE;
flags:确定此内存映射能否被其他进程共享,可设置为MAP_SHARED或MAP_PRIVATE;
fd:设备文件句柄;
offset:确定映射后的内存地址

以下代码使用VIDIOC_QUERYBUF命令和mmap函数将内核空间的缓冲区映射到用户空间。

  • VIDIOC_QUERYBUF命令的使用需要参数struct v4l2_buffer结构体
    • 结构体中的typememoryindex参数需要设置
      • typememory和前文中的设置一样,分别设置成V4L2_BUF_TYPE_VIDEO_CAPTUREV4L2_MEMORY_MMAP
      • index参数表示申请的缓冲帧的标号,从0开始,包含申请的所有缓冲帧。
struct v4l2_buffer tV4l2Buf;
/* map the buffers */
for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
{
    memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    tV4l2Buf.index = i;							//申请的缓冲帧的标号
    tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Buf.memory = V4L2_MEMORY_MMAP;			//MMAP方式访问
    iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);	//把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;
    if (iError)
    {
        DBG_PRINTF("Unable to query buffer.\n");
        goto err_exit;
    }

    ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
    ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,
                                        tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
                                        tV4l2Buf.m.offset);//将内核空间的缓冲区映射到用户空间。
    if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED)
    {
        DBG_PRINTF("Unable to map buffer\n");
        goto err_exit;
    }
}

 
 

3.7. 将申请的缓冲帧放入队列,并启动数据流

使用VIDIOC_QBUF命令,将申请的缓冲帧依次放入缓冲帧输入队列,等待被图像采集设备依次填满。

/* Queue the buffers. */
for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
{
    memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    tV4l2Buf.index = i;
    tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);//把数据从缓存中读取出来;
    if (iError)
    {
        DBG_PRINTF("Unable to queue buffer.\n");
        goto err_exit;
    }
}
4

3.8. 启动捕捉图像数据

启动捕捉图像数据使用VIDIOC_STREAMON命令,当该命令执行成功后,便可以等待图像数据的到来。

/**********************************************************************
* 函数名称:V4l2StartDevice
* 功能描述:开始捕捉图像数据
* 输入参数:ptVideoDevice
* 输出参数:无
* 返 回 值:无
* 修改日期             版本号        修改人           修改内容
* -----------------------------------------------
* 2020/02/16         V1.0     zhenhua             创建
***********************************************************************/
/**********************************************************************
* 函数名称:V4l2StartDevice
* 功能描述:开始捕捉图像数据
* 输入参数:ptVideoDevice
* 输出参数:无
* 返 回 值:无
* 修改日期             版本号        修改人           修改内容
* -----------------------------------------------
* 2020/02/16         V1.0     zhenhua             创建
***********************************************************************/
static int V4l2StartDevice(PT_VideoDevice ptVideoDevice)
{
    int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
    int iError;
iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);//启动捕捉图像数据
if (iError)
{
    DBG_PRINTF("Unable to start capture.\n");
    return -1;
}
return 0;

}

3.9. 出列采集的帧缓冲,并处理图像数据,然后再将数据帧入列

    我们可以使用VIDIOC_DQBUF命令,等待缓冲帧的到来,当有缓冲帧被放入视频输出缓冲队列,我们便可以采到一帧图像。接收到图像我们可以对图像进行操作,例如保存、压缩或者LCD输出等。

/**********************************************************************
 * 函数名称:V4l2GetFrameForStreaming
 * 功能描述:从图像数据流中获取一帧图像数据
 * 输入参数:ptVideoDevice
             ptVideoBuf
 * 输出参数:无
 * 返 回 值:无
 * 修改日期             版本号        修改人           修改内容
 * -----------------------------------------------
 * 2020/02/16         V1.0     zhenhua             创建
 ***********************************************************************/
 
static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{
    struct pollfd tFds[1];
    int iRet;
    struct v4l2_buffer tV4l2Buf;

/* poll */
    tFds[0].fd     = ptVideoDevice->iFd;
    tFds[0].events = POLLIN;

  
  iRet = poll(tFds, 1, -1);
    if (iRet <= 0)
    {
        DBG_PRINTF("poll error!\n");
        return -1;
    }

    /* VIDIOC_DQBUF */
    memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);//VIDIOC_DQBUF把数据放回缓存队列;
    if (iRet < 0)
    {
        DBG_PRINTF("Unable to dequeue buffer.\n");
        return -1;
    }
    ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index;

    ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;
    ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;
    ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;
    ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \
        (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 :  \
        0;
    ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;
    ptVideoBuf->tPixelDatas.iTotalBytes   = tV4l2Buf.bytesused;
    ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index];
    return 0;
}


当我们从缓冲帧输出队列取出一个缓冲帧,取出图像数据后我们需要将缓冲帧重新放回到视频输入缓冲队列,该操作还是使用VIDIOC_QBUF命令,放回缓冲帧输入队列后继续等待被填满。

296 /**********************************************************************
297 * 函数名称:V4l2PutFrameForStreaming
298 * 功能描述:将取出的帧缓冲重新放回图像输入队列
299 * 输入参数:ptVideoDevice
300             ptVideoBuf
301 * 输出参数:无
302 * 返 回 值:无
303 * 修改日期             版本号        修改人           修改内容
304 * -----------------------------------------------
305 * 2020/02/16         V1.0     zhenhua             创建
306 ***********************************************************************/
307 static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
308 {
309     /* VIDIOC_QBUF */
310     struct v4l2_buffer tV4l2Buf;
311     int iError;
312
313     memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
314     tV4l2Buf.index  = ptVideoDevice->iVideoBufCurIndex;
315     tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
316     tV4l2Buf.memory = V4L2_MEMORY_MMAP;
317     iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf);
318     if (iError)
319     {
320         DBG_PRINTF("Unable to queue buffer.\n");
321         return -1;
322     }
323     return 0;
324 }


  
  

3.10. 停止捕捉图像数据

停止采集图像数据,首先使用VIDIOC_STREAMOFF命令,关闭捕获图像数据。同时要注意取消内存映射和关闭句柄,防止不必要的内存泄漏。代码390407行为停止捕捉图像数据命令;代码227241行为取消内存映射和关闭句柄。

380 /**********************************************************************
381 * 函数名称:V4l2StopDevice
382 * 功能描述:停止捕捉图像数据
383 * 输入参数:ptVideoDevice
384 * 输出参数:无
385 * 返 回 值:无
386 * 修改日期             版本号        修改人           修改内容
387 * -----------------------------------------------
388 * 2020/02/16         V1.0     zhenhua             创建
389 ***********************************************************************/
390 static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
391 {
392     int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
393     int iError;
394
395     iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMOFF, &iType);
396     if (iError)
397     {
398             DBG_PRINTF("Unable to stop capture.\n");
399             return -1;
400     }
401     return 0;
402 }
403
404 static int V4l2GetFormat(PT_VideoDevice ptVideoDevice)
405 {
406     return ptVideoDevice->iPixelFormat;
407 }


217 /**********************************************************************
218 * 函数名称:V4l2ExitDevice
219 * 功能描述:退出采集设备,取消帧缓冲映射和关闭句柄
220 * 输入参数:ptVideoDevice
221 * 输出参数:无
222 * 返 回 值:无
223 * 修改日期             版本号        修改人           修改内容
224 * -----------------------------------------------
225 * 2020/02/16         V1.0     zhenhua             创建
226 ***********************************************************************/
227 static int V4l2ExitDevice(PT_VideoDevice ptVideoDevice)
228 {
229     int i;
230     for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++)
231     {
232         if (ptVideoDevice->pucVideBuf[i])
233         {
234             munmap(ptVideoDevice->pucVideBuf[i], ptVideoDevice->iVideoBufMaxLen);
235             ptVideoDevice->pucVideBuf[i] = NULL;
236         }
237     }
238
239     close(ptVideoDevice->iFd);
240     return 0;
241 }


    
    

3.11 对数据进行处理

这里因为使用的摄像头仅支持mjpeg格式,所以还需要对摄像头输出的数据帧进行处理。
这一部分代码参考:

//预览采集到的图像
	while (1)
	{
		//如果把处理JPEG格式的数据和显示程序分离,把处理JPEG部分的数据作成一个新的线程,预览时会更加流畅。
		for (numBufs = 0; numBufs < req.count; numBufs++)
		{	
			if ((fd_y_file = fopen(s, "wb")) < 0)
			{
				printf("Unable to create y frame recording file\n");
				return -1;
			}

			memset(&buf, 0, sizeof(buf));
			buf.index = numBufs;
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//取得原始采集数据
			buf.memory = V4L2_MEMORY_MMAP;			//存储类型:V4L2_MEMORY_MMAP(内存映射)或V4L2_MEMORY_USERPTR(用户指针)
			if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0)		//从缓冲队列中取出数据
			{
				perror("VIDIOC_DQBUF failed.\n");
				return -1;
			}

			unsigned char *ptcur = buffers[numBufs].start;	//开始霍夫曼解码
			int i1;
			//check huffman table,these code are optional
			for (i1=0; i1<buf.bytesused; i1++)
			{
				if ((buffers[numBufs].start[i1] == 0x000000FF) && (buffers[numBufs].start[i1+1] == 0x000000C4))
				{
					break;
				}
			}
			if (i1 == buf.bytesused)
			{
				printf("huffman table don't exist! \n");
				goto next_frame;
			}
			int i;
			//SOI = Start Of Image = "FFD8", EOI = End Of Image = "FFD9"
			for (i=0; i<buf.bytesused; i++)
			{
				if ((buffers[numBufs].start[i] == 0x000000FF) && (buffers[numBufs].start[i+1] == 0x000000D8))
					break;
				ptcur++;
			}
			int imagesize = buf.bytesused - i;
			fwrite(ptcur, imagesize, 1, fd_y_file);			//开始向LCD发送数据显示采集到的图像
			fclose(fd_y_file);

			if ((infile = fopen(s, "rb")) == NULL)
			{
				fprintf(stderr, "open %s failed\n", s);
				exit(-1);
			}
			cinfo.err = jpeg_std_error(&jerr);
			
			jpeg_create_decompress(&cinfo);
		
			//导入要解压的Jpeg文件infile
			jpeg_stdio_src(&cinfo, infile);
			
			//读取jpeg文件的文件头
			jpeg_read_header(&cinfo, TRUE);
			
			//开始解压Jpeg文件,解压后将分配给scanline缓冲区,
			jpeg_start_decompress(&cinfo);

			buffer = (unsigned char *) malloc(cinfo.output_width * cinfo.output_components);
			y = 0;
			while (cinfo.output_scanline < cinfo.output_height)
			{
				jpeg_read_scanlines(&cinfo, &buffer, 1);
				if (fbdev.fb_bpp == 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(fbdev.fb_mem, fbdev.fb_width, fbdev.fb_height, x, y, color);///
					}
				}
				else if (fbdev.fb_bpp == 24||fbdev.fb_bpp == 32)
				{
					memcpy((unsigned char *)fbdev.fb_mem + y * fbdev.fb_width * fbdev.fb_bpp / 8, buffer,
							cinfo.output_width * cinfo.output_components);
				}
				y++;//下一个scanline
			}

			//完成Jpeg解码,释放Jpeg文件
			jpeg_finish_decompress(&cinfo);
			jpeg_destroy_decompress(&cinfo);

			//释放帧缓冲区
			free(buffer);

			//关闭Jpeg输入文件
			fclose(infile);
 
next_frame:
			//将取出的图像放回缓冲区
			memset(&buf, 0 ,sizeof(buf));
			buf.index = numBufs;
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			buf.memory = V4L2_MEMORY_MMAP;
			if (ioctl(fd, VIDIOC_QBUF, &buf) < 0)
			{
				printf("VIDIOC_QBUF error\n");
				return -1;
			}
		}
		
		//printf("start the next frame\n");
		if(stop_flag) 				
			break;
	}


4. 应用代码实现

由于今天改写还没完成,所以先将可以运行的代码贴上来,等后面重构成功后,贴上最终代码。

以下代码部分搬运自文件usb_camera.c

/*************************************

NAME:usb_camera.c
COPYRIGHT:www.embedsky.net

*************************************/

#include <errno.h>
#include <sys/types.h>	
#include <sys/stat.h>	
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>    
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <asm/types.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <string.h>
#include <malloc.h>
#include <linux/fb.h>
#include <jpeglib.h>
#include <jerror.h>

#include <signal.h>

typedef struct VideoBuffer
{
	unsigned char *start;
	size_t offset;
	size_t length;
} VideoBuffer;

struct fb_dev
{
	//for frame buffer
	int fb;
	void *fb_mem;	//frame buffer mmap
	int fb_width, fb_height, fb_line_len, fb_size;
	int fb_bpp;
} fbdev;

//得到framebuffer的长、宽和位宽,成功则返回0,失败返回-1 
int fb_stat(int fd)
{
	struct fb_fix_screeninfo fb_finfo;
	struct fb_var_screeninfo fb_vinfo;

	if (ioctl(fd, FBIOGET_FSCREENINFO, &fb_finfo))
	{
		perror(__func__);
		return (-1);
	}

	if (ioctl(fd, FBIOGET_VSCREENINFO, &fb_vinfo))
	{
		perror(__func__);
		return (-1);
	}

	fbdev.fb_width = fb_vinfo.xres;
	fbdev.fb_height = fb_vinfo.yres;
	fbdev.fb_bpp = fb_vinfo.bits_per_pixel;
	fbdev.fb_line_len = fb_finfo.line_length;
	fbdev.fb_size = fb_finfo.smem_len;

	return (0);
}

//转换RGB888为RGB565(因为当前LCD是采用的RGB565显示的)
unsigned short RGB888toRGB565(unsigned char red, unsigned char green, unsigned char blue)
{
	unsigned short B = (blue >> 3) & 0x001F;
	unsigned short G = ((green >> 2) << 5) & 0x07E0;
	unsigned short R = ((red >> 3) << 11) & 0xF800;

	return (unsigned short) (R | G | B);
}
//释放framebuffer的映射
int fb_munmap(void *start, size_t length)
{
	return (munmap(start, length));
}

//显示一个像素点的图像到framebuffer上
int fb_pixel(void *fbmem, int width, int height, int x, int y, unsigned short color)
{
	if ((x > width) || (y > height))
		return (-1);

	unsigned short *dst = ((unsigned short *) fbmem + y * width + x);

	*dst = color;
	return 0;
}

unsigned char stop_flag=0;
void stop_function()
{
	printf("usb camera stop\n");
	stop_flag = 1;
}

int main(int argc, char** argv)
{
	int numBufs;
	int ret;
	char *devname;
	printf("USB Camera Test\n");

	signal(SIGINT,stop_function);//安装信号处理函数  
	devname = "/dev/video1";
	if(argc==2) devname = argv[1];

	int fd = open(devname, O_RDWR, 0);	//打开摄像头设备,使用阻塞方式打开
	if (fd<0)
	{
		printf("open error\n");
		return  -1;
	}


	struct v4l2_format fmt;					//设置获取视频的格式
	memset( &fmt, 0, sizeof(fmt));
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//视频数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//视频源的格式为JPEG或YUN4:2:2或RGB
	fmt.fmt.pix.width = 800;					//设置视频宽度
	fmt.fmt.pix.height = 600;					//设置视频高度
	//fmt.fmt.pix.field = V4L2_FIELD_ANY;	
	if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0)		//使配置生效
	{
		printf("set format failed\n");
		return -1;
	}

	struct v4l2_fmtdesc fmtdesc;
	fmtdesc.index = 0;
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) != -1)
	{
		printf("enum fmt desc\n");
		printf("%d.%s\n",fmtdesc.index,fmtdesc.description);
		fmtdesc.index++;
	}//经查询,天敏minicam只支持MJPEG


//设置camera的亮度值
	struct v4l2_queryctrl queryctrl;
	struct v4l2_control control;
	memset(&queryctrl, 0, sizeof(queryctrl));
	queryctrl.id = V4L2_CID_BRIGHTNESS;
	if(-1 == ioctl(fd, VIDIOC_QUERYCTRL, &queryctrl))
	{
		if(errno != EINVAL)
		{
			perror("VIDIOC_QUERYCTRL\n");
			exit(-1);
		}
		else
		{
			printf("V4L2_CID_BRIGHTNESS is not support\n");
		}
	}
	else if(queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
	{
		printf("V4L2_CID_BRIGHTNESS is not support\n");
	}
	else
	{
		memset(&control, 0, sizeof(control));
		control.id = V4L2_CID_BRIGHTNESS;
		control.value = queryctrl.default_value;//queryctrl.default_value;
		if(-1 == ioctl(fd, VIDIOC_S_CTRL, &control))
		{
			perror("VIDIOC_S_CTRL\n");
			exit(-1);
		}
	}


	struct v4l2_requestbuffers req;				//申请帧缓冲
	memset(&req, 0, sizeof (req));
	req.count = 4;								//缓存数量,即可保存的图片数量
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
	req.memory = V4L2_MEMORY_MMAP;			//存储类型:V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
	if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1)	//使配置生效
	{
		perror("request buffer error \n");
		return -1;
	}

	VideoBuffer *buffers = calloc(req.count, sizeof(VideoBuffer));	//将已申请到的缓冲帧映射到应用程序
//	printf("sizeof(VideoBuffer) is %d\n", sizeof(VideoBuffer));
//sizeof(VideoBUffer)=12,因为start指针指向unsigned char类型数据,但是本身是4位长度
	struct v4l2_buffer buf;
	for (numBufs = 0; numBufs < req.count; numBufs++)
	{
		
		memset( &buf, 0, sizeof(buf));
		buf.index = numBufs;
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
		buf.memory = V4L2_MEMORY_MMAP;			//存储类型:V4L2_MEMORY_MMAP(内存映射)或V4L2_MEMORY_USERPTR(用户指针)				
		if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0)		//使配置生效
		{
			printf("VIDIOC_QUERYBUF error\n");
			return -1;
		}

//		printf("buf len is %d\n", sizeof(buf));
		buffers[numBufs].length = buf.length;
		buffers[numBufs].offset = (size_t) buf.m.offset;
		buffers[numBufs].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,MAP_SHARED, fd, buf.m.offset);//使用mmap函数将申请的缓存地址转换应用程序的绝对地址
#if 0
		printf("buffers.length = %d,buffers.offset = %d ,buffers.start[0] = %d\n",
					buffers[numBufs].length, buffers[numBufs].offset,buffers[numBufs].start[0]);
		printf("buf2 len is %d\n", sizeof(buffers[numBufs].start));
#endif
		if (buffers[numBufs].start == MAP_FAILED)
		{
			perror("buffers error\n");
			return -1;
		}
		if (ioctl(fd, VIDIOC_QBUF, &buf) < 0)		//放入缓存队列
		{
			printf("VIDIOC_QBUF error\n");
			return -1;
		}
	}

	enum v4l2_buf_type type;					//开始视频显示
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;		//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
	if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
	{
		printf("VIDIOC_STREAMON error\n");
		return -1;
	}

	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//数据流类型,永远都是V4L2_BUF_TYPE_VIDEO_CAPTURE
	if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0)		//读取视频源格式
	{
		printf("get format failed\n");
		return -1;
	}
	else
	{
		printf("Picture:Width = %d   Height = %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
		printf("Image size = %d\n", fmt.fmt.pix.sizeimage);
		printf("pixelformat = %d\n", fmt.fmt.pix.pixelformat);
	}

	FILE * fd_y_file = 0;
	int a=0;
	int k = 0;

	//设置显卡设备framebuffer
	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;
	FILE *infile;							//Jpeg文件的句柄
	unsigned char *buffer;

	int fb;
	char *fb_device;
	unsigned int x;
	unsigned int y;
	char s[15];
	sprintf(s, "%d.jpg", a);

	if ((fb = open("/dev/fb0", O_RDWR)) < 0)			//打开显卡设备
	{
		perror(__func__);
		return (-1);
	}

	//获取framebuffer的状态
	fb_stat(fb);							//获取显卡驱动中的长、宽和显示位宽
	printf("frame buffer: %dx%d,  %dbpp, 0x%xbyte= %d\n", 
		fbdev.fb_width, fbdev.fb_height, fbdev.fb_bpp, fbdev.fb_size, fbdev.fb_size);

	//映射framebuffer的地址
	fbdev.fb_mem = mmap (NULL, fbdev.fb_size, PROT_READ|PROT_WRITE,MAP_SHARED,fb,0);//映射显存地址
	fbdev.fb = fb;
	memset(fbdev.fb_mem ,0,fbdev.fb_size);

	//预览采集到的图像
	while (1)
	{
		//如果把处理JPEG格式的数据和显示程序分离,把处理JPEG部分的数据作成一个新的线程,预览时会更加流畅。
		for (numBufs = 0; numBufs < req.count; numBufs++)
		{	
			if ((fd_y_file = fopen(s, "wb")) < 0)
			{
				printf("Unable to create y frame recording file\n");
				return -1;
			}

			memset(&buf, 0, sizeof(buf));
			buf.index = numBufs;
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//取得原始采集数据
			buf.memory = V4L2_MEMORY_MMAP;			//存储类型:V4L2_MEMORY_MMAP(内存映射)或V4L2_MEMORY_USERPTR(用户指针)
			if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0)		//从缓冲队列中取出数据
			{
				perror("VIDIOC_DQBUF failed.\n");
				return -1;
			}

			unsigned char *ptcur = buffers[numBufs].start;	//开始霍夫曼解码
			int i1;
			//check huffman table,these code are optional
			for (i1=0; i1<buf.bytesused; i1++)
			{
				if ((buffers[numBufs].start[i1] == 0x000000FF) && (buffers[numBufs].start[i1+1] == 0x000000C4))
				{
					break;
				}
			}
			if (i1 == buf.bytesused)
			{
				printf("huffman table don't exist! \n");
				goto next_frame;
			}
			int i;
			//SOI = Start Of Image = "FFD8", EOI = End Of Image = "FFD9"
			for (i=0; i<buf.bytesused; i++)
			{
				if ((buffers[numBufs].start[i] == 0x000000FF) && (buffers[numBufs].start[i+1] == 0x000000D8))
					break;
				ptcur++;
			}
			int imagesize = buf.bytesused - i;
			fwrite(ptcur, imagesize, 1, fd_y_file);			//开始向LCD发送数据显示采集到的图像
			fclose(fd_y_file);

			if ((infile = fopen(s, "rb")) == NULL)
			{
				fprintf(stderr, "open %s failed\n", s);
				exit(-1);
			}
			cinfo.err = jpeg_std_error(&jerr);
			
			jpeg_create_decompress(&cinfo);
		
			//导入要解压的Jpeg文件infile
			jpeg_stdio_src(&cinfo, infile);
			
			//读取jpeg文件的文件头
			jpeg_read_header(&cinfo, TRUE);
			
			//开始解压Jpeg文件,解压后将分配给scanline缓冲区,
			jpeg_start_decompress(&cinfo);

			buffer = (unsigned char *) malloc(cinfo.output_width * cinfo.output_components);
			y = 0;
			while (cinfo.output_scanline < cinfo.output_height)
			{
				jpeg_read_scanlines(&cinfo, &buffer, 1);
				if (fbdev.fb_bpp == 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(fbdev.fb_mem, fbdev.fb_width, fbdev.fb_height, x, y, color);///
					}
				}
				else if (fbdev.fb_bpp == 24||fbdev.fb_bpp == 32)
				{
					memcpy((unsigned char *)fbdev.fb_mem + y * fbdev.fb_width * fbdev.fb_bpp / 8, buffer,
							cinfo.output_width * cinfo.output_components);
				}
				y++;//下一个scanline
			}

			//完成Jpeg解码,释放Jpeg文件
			jpeg_finish_decompress(&cinfo);
			jpeg_destroy_decompress(&cinfo);

			//释放帧缓冲区
			free(buffer);

			//关闭Jpeg输入文件
			fclose(infile);
 
next_frame:
			//将取出的图像放回缓冲区
			memset(&buf, 0 ,sizeof(buf));
			buf.index = numBufs;
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			buf.memory = V4L2_MEMORY_MMAP;
			if (ioctl(fd, VIDIOC_QBUF, &buf) < 0)
			{
				printf("VIDIOC_QBUF error\n");
				return -1;
			}
		}
		
		//printf("start the next frame\n");
		if(stop_flag) 				
			break;
	}
	
	printf("go out the while loop\n");
	//停止视频采集
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if(-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))
	{
		perror("VIDIOC_STREAMOFF\n");
		exit(-1);
	}	
	for (numBufs = 0; numBufs < req.count; numBufs++)
	{
		 
		munmap(buffers[numBufs].start, buffers[numBufs].length);
	}
	free(buffers);
	fb_munmap(fbdev.fb_mem, fbdev.fb_size);	//释放framebuffer映射
	close(fb);								//关闭Framebuffer设备
	close(fd);

	printf("finish the program\n");
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值