USB摄像头驱动的移植



1、USB摄像头的配置

UVC,全称为:USB video class 或USB video device class,是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,目前已成为USB org标准之一。

如今的主流操作系统(如Windows XP SP2 and later, Linux 2.4.6 and later, MacOS 10.5 and later)都已提供UVC设备驱动,因此符合UVC规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用。使用UVC技术的包括摄像头、数码相机、类比影像转换器、电视棒及静态影像相机等设备

下面就开始配置UVC的linux驱动。

make menuconfig
 
Device Drivers --->
     <*>Multimedia support --->
       [*] Cameras/video grabbers support
       [*] Media usb adapters---->  
 USBvideo class (uvc)
                    [*] uvc input events device support
<*>GSPCA basedwebcams
                          
 
Device Drivers --->
  [*]usb support
       [*] usb announce new device 
       <*>  usb gadget support à
               < *> usb gadget drivers(usb webcam gadget)

 

由于摄像头是台湾沛成科技(ippassion http://www.ipassion.com.tw/)生产的iP2977 ,其供应商号和产品号分别为1b3b  2977

但是内核通过的驱动中平没有匹配该型号的驱动于是我们应该修改一下内核代码(参考该公布公司的文档How to build upUVC Driver on Linux_1.1.pdf):

在内核源码的drivers/media/usb/uvc/UVC_driver.c文件中找到static struct
usb_device_id uvc_ids[].在该数组中添加:

/*iPassion USB Web Camera */
 { .match_flags = USB_DEVICE_ID_MATCH_DEVICE
 | USB_DEVICE_ID_MATCH_INT_INFO,
 .idVendor = 0x1B3B,
 .idProduct = 0x2977,/*If you use iP2970, then type "0x2970" */
 .bInterfaceClass = USB_CLASS_VIDEO,
 .bInterfaceSubClass = 1,
 .bInterfaceProtocol = 0,
 .driver_info = UVC_QUIRK_PROBE_MINMAX
 | UVC_QUIRK_IGNORE_SELECTOR_UNIT},

为了增加相容性)在文件 drivers/media/usb/uvc / uvc_video.c中,找到

static intuvc_video_decode_start 函数:
 static int uvc_video_decode_start(struct uvc_streaming *stream,
 struct uvc_buffer *buf, const __u8 *data, int len)
 {
static __u8 fid; // 修改成 static类型
 /* Sanity checks:
 * - packet must be at least 2 bytes long
 * - bHeaderLength value must be at least 2 bytes (see above)
 * - bHeaderLength value can't be larger than the packet size.
 */
 if (len < 2 || data[0] < 2 || data[0] > len)
 return -EINVAL;
 /* Skip payloads marked with the error bit ("error frames"). */
 if (data[1] & UVC_STREAM_ERR) {
 uvc_trace(UVC_TRACE_FRAME, "Dropping payload (error bit "
 "set).\n");
 return -ENODATA;
 }
if ( len >= 16 ) // have data in buffer
 {if ( (data[12]==0xFF && data[13]==0xD8&& data[14]==0xFF) ||
(data[12]==0xD8 && data[13]==0xFF&& data[14]==0xC4))
{
if(stream->last_fid)
fid &= ~UVC_STREAM_FID;
else
fid |= UVC_STREAM_FID;
}
}
//fid= data[1] & UVC_STREAM_FID;
//=================================================================================
 /* Increase the sequence number regardless of any buffer states, so
 * that discontinuous sequence numbers always indicate lost frames.
 */
 if (stream->last_fid != fid)
 stream->sequence++;……………………..
 }


然后再在该文件中找到static voiduvc_video_decode_data

static void uvc_video_decode_data(struct uvc_streaming*stream,
 struct uvc_buffer *buf, const __u8 *data, int len)
 {
 struct uvc_video_queue *queue = &stream->queue;
 unsigned int maxlen, nbytes;
 void *mem;
 if (len <= 0)
 return;
 /* Copy the video data to the buffer. */
 maxlen = buf->buf.length - buf->buf.bytesused;
 mem = queue->mem + buf->buf.m.offset + buf->buf.bytesused;
 nbytes = min((unsigned int)len, maxlen);
 memcpy(mem, data, nbytes);
 buf->buf.bytesused += nbytes;
unsigned char *point_mem;static unsigned char *mem_temp = NULL;
static unsigned int nArrayTemp_Size =1000;
 if(mem_temp== NULL){
mem_temp = kmalloc(nArrayTemp_Size,GFP_KERNEL);
}
else if(nArrayTemp_Size <= nbytes){ 
kfree(mem_temp);
nArrayTemp_Size += 500;
kmalloc(nArrayTemp_Size, GFP_KERNEL);
}
memset(mem_temp, 0x00, nArrayTemp_Size);
point_mem = (unsigned char *)mem;
if( *(point_mem) == 0xD8 &&*(point_mem + 1) == 0xFF && *(point_mem + 2) == 0xC4){
memcpy( mem_temp + 1, point_mem, nbytes);
mem_temp[0] = 0xFF;
memcpy( point_mem, mem_temp, nbytes + 1);
}
//
 ============================================================================================
 /* Complete the current frame if the buffer size was exceeded. */
 if (len > maxlen) {
 uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n");
 buf->state = UVC_BUF_STATE_READY;
 }
 }

然后编译下载到板子上面.

启动板子后,插上USB摄像头,会自动的提示如下内容:

usb 1-1: newfull-speed USB device number 3 using s3c2410-ohci

usb 1-1: New USBdevice found, idVendor=1b3b, idProduct=2977

usb 1-1: New USBdevice strings: Mfr=0, Product=0, SerialNumber=0

uvcvideo: FoundUVC 1.00 device <unnamed> (1b3b:2977)

input: UVCCamera (1b3b:2977) as /devices/platform/s3c2410-ohci/usb1/1-1/1-1:1.0/input/input3

生成的设备文件为/dev/videon(n=1,2,3……….n)

2、摄像头数据的获取

对于摄像头数据的获取主要参考驱动代码里面的ioctrl函数,为了使代码更容易理解,下面写出处理打大概流程。下面给出读取步骤(有些步骤或者顺序并不是必须的,但是按照下面的步骤总是可以的):

⑴打开摄像头设备文件 open

// 用非阻塞模式打开摄像头设备

int fd_video;

fd_video = open(“/dev/video0″, O_RDWR |O_NONBLOCK, 0);

// 如果用阻塞模式打开摄像头设备,上述代码变为:

 fd_video= open(”/dev/video0″, O_RDWR, 0);

关于阻塞模式和非阻塞模式

应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。

⑵获取本摄像头支持的图像数据编码信息  VIDIOC_QUERYCAP

structv4l2_capability cap; 

ret =ioctl(fd_video, VIDIOC_QUERYCAP, &cap); 

其中获取的信息有:

struct v4l2_capability {
      __u8      driver[16];  驱动模块的名字
      __u8      card[32];  芯片的信息
      __u8      bus_info[32]; 所用总线的名字
      __u32   version;  内核版本
      __u32    capabilities;  描述该物理设备所支持的功能
      __u32    device_caps;通过特殊的扩展该物理设备所支持的功能
      __u32    reserved[3];保留
};

一般情况下可能会支持的功能有:

V4L2_CAP_VIDEO_CAPTURE  是一个视频捕捉设备

V4L2_CAP_STREAMING      具有数据流控制模式

V4L2_CAP_DEVICE_CAPS

 

⑶获取所支持捕捉格式          VIDIOC_ENUM_FMT

struct v4l2_fmtdesc fmtd;
    fmtd.index= 0;
    fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
    while ((ret = ioctl(fd_video,VIDIOC_ENUM_FMT, &fmtd)) == 0) 
          fmtd.index++;

在fmt.pixelformat 和fmt.description记录了该摄像头所支持的格式。

视频格式比较多,一般情况下有:

V4L2_PIX_FMT_MJPEG   V4L2_PIX_FMT_JPEG
         V4L2_PIX_FMT_YUYV   V4L2_PIX_FMT_YVU420       V4L2_PIX_FMT_RGB32

⑷设置视频格式

struct v4l2_format fmt;  
    memset(&fmt, 0, sizeof(fmt));  
    fmt.type                =V4L2_BUF_TYPE_VIDEO_CAPTURE;  //类型要写这个
    fmt.fmt.pix.width       = 480; //设置摄像头输出宽度和高度,真是输出要小于等于该设置
    fmt.fmt.pix.height      = 272; 
    fmt.fmt.pix.pixelformat =V4L2_PIX_FMT_MJPEG;  //设置输出格式,因摄像头而异
    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;//设置域输出格式依赖摄像头  
    ret = ioctl(fd_video, VIDIOC_S_FMT,&fmt); //使设置生效

⑸获取视频格式

因为摄像头的硬件设置的差异,前面设置的摄像头的参数,对于摄像头来数部分参数起到的是参考作用,比如像素的宽度高度等等,此时就需要读取摄像头的视频格式来确定摄像头的真实参数。

ret =ioctl(fd_video, VIDIOC_G_FMT, &fmt);

fmt.fmt.pix.pixelformat  视频的编码格式

fmt.type 类型

fmt.fmt.pix.width输出视频像素宽度

fmt.fmt.pix.height输出视频像素高度

fmt.fmt.pix.field视频域输出方式 

fmt.fmt.pix.sizeimage  输出一张图的字节数 

fmt.fmt.pix.colorspace  输出的色彩空间比如0XRR 0XGG 0XBB就是8byte

 

⑹请求分配缓存

structv4l2_requestbuffers reqbuf;  
reqbuf.count =4;  //请求分配内存的个数,真实的申请的获得内存并不一定是4个
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  //类型还是捕获
reqbuf.memory = V4L2_MEMORY_MMAP; 设置为 memory map模式
ret = ioctl(fd_video , VIDIOC_REQBUFS, &reqbuf);//申请4个缓冲

 

⑹获取内存空间及其假如内存队列

structv4l2_buffer buf;  
for (i = 0; i< reqbuf.count; i++)  //循环获取指定书目的内存空间 
    {  
        buf.index = i;  
        buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;  //类型还是捕获
        buf.memory = V4L2_MEMORY_MMAP; //获取的内省还是内存映射
        //获取内存空间  
        ret = ioctl(fd_video , VIDIOC_QUERYBUF,&buf); //获取内存的大小为buf.length
/*获取内存的类型为V4L2_MEMORY_MMAP分配的是实实在在的物理内存,*/ 
       //获取后需要进行map
      进行mmap
……………………………
ret = ioctl(fd_video , VIDIOC_QBUF, &buf);//将获取的内存放到内存队列,以便获取图像数据
       }

⑺开始传输视频流数据

  enum v4l2_buf_type type =V4L2_BUF_TYPE_VIDEO_CAPTURE; 

VIDIOC_STREAMON,&type); //图像信息不断的存放在内存队列中

⑻对数据流数据的处理VIDIOC_DQBUF

首先要队列中的内存取出来一个

VIDIOC_DQBUF,& buf); //内存空间出队列 

    接下来就是对buf中的数据进行处理,根据要求显示或者发送出去 

    。。。。。。。。。。。。。

  处理完毕后就可以把内存放到内存队列中以便循环接收摄像头的图像数据。

     ret = ioctl(fd_video, VIDIOC_QBUF,&buf);  // 内存重新入队列

 

 

3、jpeglib的安装

要把MJPEG进行其他类型的转换的话还需要安装jpeglib

libjpeg是一个用C语言编写广泛使用的JPEG解码、JPEG编码和其他的JPEG功能的实现的库这个库由独立JPEG工作组维护,其最新版本为9a。

首先需要下载源码包,

下载地址:http://www.ijg.org/

选择版本下载 jpegsrc.v9a.tar.gz 

将下载的源码包拷贝到ubuntu中,解压进入源码吧执行安装命令

首先要进行配置,可以执行:

chmod +x ./configure

./configure --prefix=/work/tools/jpeg-9a --host=arm-linuxCC=arm-linux-2440-gcc --enable-shared

其中—prefix指向的是安装目录, CC表示交叉编译器的名字

然后执行:

make

make installl

此时会在你指定的—prefix生成一个安装的库等文件.

在安装文件中会生成下面的四个文件夹:

bin  include lib  share

include 下面就是编程需要的头文件,有以下四个文件:

jconfig.h jerror.h  jmorecfg.h  jpeglib.h

lib  是需要的库文件,其目录下有一下5个库:

libjpeg.a  libjpeg.la libjpeg.so  libjpeg.so.9  libjpeg.so.9.1.0

当选择是动态编译文件的时候,板子需要使用libjpeg.so.9.1.0,因此需要把该库拷贝到板子的目录下,在运行的时候,程序是寻找的libjpeg.so.9

因此我们在刚才—prefix指定的安装目录下面执行拷贝命令:

cp ./lib/libjpeg.so.9.1.0 /work/root/lib/ libjpeg.so.9

4、jpeglib使用步骤

JPEG(英文全称:Joint Photographic Experts Group)压缩技术可以说是所有图像压缩技术的基础。它适合静态图像的压缩,直接处理整个画面,压缩倍数为20-80倍,分辨率没有选择的余地。所以要等到整个压缩档案传输完成才开始进行解压缩成影像画面,而这样的方式造成传输一个高解析画面时须耗时数十秒甚至数分钟。

而MJPEG(Motion JPEG)是在JPEG基础发展起来的动态图像压缩技术,它只单独的对某一帧进行压缩,而基本不考虑视频流中不同帧之间的变化。使得可获取清晰度很高的视频图像,而且可灵活设置每路的视频清晰度和压缩帧数。

为了完整的描述一张jpeg图片及其设置图片的参数等信息,libjpeg使用struct jpeg_decompress_struc来表示。

下面给出使用libjpeg进行解码的程序操作步骤。在给出步骤之前,有一点是不可避免要特殊处理的----错误处理。在进行图片处理解码编码的时候,出错在所难免,libjpeg提供了一个默认的错误处理函数,但是该错误处理函数的作用是退出程序,这不是我们想要的结构。还好,libjpeg提供了一让用户自己去定义出错处理函数的机制,我们要重写该处理函数,我们重写错误函数就是释放掉应该被释放的内存然后跳出解码编码函数,开始下一轮解码与编码。其出错处理流程如下:

首先应该定义出出错处理函数:

struct my_error_mgr {  //定义出错处理的结构体
    struct jpeg_error_mgr pub;    /* "public" fields */  
    jmp_buf setjmp_buffer;    /* for return to caller */  
};  
typedef struct my_error_mgr my_error_ptr;  
 
my_error_ptr jerr;
cinfo.err = jpeg_std_error(&jerr.pub);  
jerr.pub.error_exit = my_error_exit;  //指定出错处理函数
if (setjmp(jerr.setjmp_buffer)) {  //这是错误函数的跳转返回点,
    jpeg_destroy_decompress(&cinfo);  //能进入该if,则表明出错。
    fclose(infile);  
    return -1;  
}  
void my_error_exit (j_common_ptr cinfo)  //这是错误处理函数
{  
    my_error_ptr myerr = (my_error_ptr) cinfo->err;  
    (*cinfo->err->output_message) (cinfo);  
 //调转到setjmp函数处
}

接下来开始给出解码流程:

⑴给出错误处理

my_error_ptr jerr;

cinfo.err = jpeg_std_error(&jerr.pub);  

jerr.pub.error_exit = my_error_exit;  //指定出错处理函数

(完整代码参考上面的出错处理)

⑵首先初始化解码对象:

jpeg_decompress_structcinfo;//顶一个解码对象

jpeg_create_decompress(&cinfo);//获取解码对象

⑶将要解码的数据放到cinfo中:

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

第一个参数cinfo是定义的jpeg_decompress_struct结构体

第二个参数inbuffer是定义的指向要解码的数据内存的首地址

第三个参数insize是定义的要解码数据的总的字节数

比如jpeg_mem_src ( &cinfo,buffer,bufSize);

⑷读取jpeg文件的头信息:

jpeg_read_header(&cinfo, TRUE); //第三个参数是1就可以,用来获取jpeg有文件信息

⑸设置解码参数:

这是设置的参数主要是一些缩放(scale)比例信息,和输出的格式信息等

对于缩放信息的设置可直接设置:

cinfo.scale_num =设置放大比例

cinfo.scale_denom =设置缩小比例。scale_num /scale_denom是真实的放大倍数

对于输出颜色信息的设置:

 对于输出颜色的设置可以参考jdcolor.c文件中的jinit_color_deconverter函数。其主要的枚举有:

typedef enum {  
    JCS_UNKNOWN,        /* error/unspecified */  
    JCS_GRAYSCALE,        /* monochrome */  
    JCS_RGB,        /* red/green/blue */  
    JCS_YCbCr,        /* Y/Cb/Cr (also known as YUV) */  
    JCS_CMYK,        /* C/M/Y/K */  
    JCS_YCCK,        /* Y/Cb/Cr/K */  
    #ifdef ANDROID_RGB  
         JCS_RGBA_8888,  /* red/green/blue/alpha */  
        JCS_RGB_565     /* red/green/blue in 565 format */  
    #endif  
 } J_COLOR_SPACE;

假如不做任何的设置其默认的输出就是 RGB(24位每个颜色占有8位bit)

其设置方法是:

cinfo.color_transform=想要设置的枚举变量

jinit_color_deconverter(&cinfo) ;//使设置有效

⑹设置开始解码:

 jpeg_start_decompress(&cinfo);//开始对cinfo绑定的数据进行解码

⑺读取解码数据

  linjpeg支持一行一行的读取数据,其读取数据的代码为:

while (cinfo.output_scanline < cinfo.output_height)//按行循环
      {
0xRR, 0xGG, 0xBB */
               (void) jpeg_read_scanlines(&cinfo, &decodeBuffer, 1);//把一行解码数据放到decodeBuffer中
             /*****************
           这是应该有保存处理函数
           decodeBuffer 里面的数据是0xRR,0xGG, 0xBB 
           这一行数据一共有:cinfo.output_width * cinfo.output_components字节长度
            。。。。。。。。。。
***********************************/
      }

比如要把解码后的数据转化为RGB565格式的数据,则可以执行

 则可以分别舍弃decodeBuffer(0xRR,0xGG, 0xBB)的位于低位的数据

比如:

dwRed   = dwRed >> 3;
                      dwGreen =dwGreen >> 2;
                      dwBlue  = dwBlue >> 3;
                      dwColor =(dwRed << 11) | (dwGreen << 5) | (dwBlue);
                      *pwDstDatas16bpp= dwColor; //转化为RGB565

⑻结束解码及其释放解码对象

jpeg_finish_decompress(&cinfo);//解码结束

jpeg_destroy_decompress(&cinfo); //释放解码对象