Qt/Linux 下的摄像头捕获(Video4L…

来源:互联网 发布:什么网络电话软件好 编辑:程序博客网 时间:2024/05/02 23:14

Qt/Linux下的摄像头捕获(Video4Linux2)

On 2009年12月17日, in C++, Linux, QT, 嵌入式开发, by 刘 凡超
10

 

文章《使用ffmpeg进行摄像头捕获》叙述了使用ffmpeg进行更好的摄像头捕获的方法,欢迎阅读

Linux下使用各种设备是一件令人兴奋的事情。在Unix的世界里,用户与硬件打交待总是简单的。最近笔者在Linux下搞了摄像头的开发,有一点感想发于此处。
Linux中操作一个设备一般都是打开(open),读取(read)和关闭(close)。使用Read的大多是一些字符型设备,然而对于显示屏或者摄像头这种字符设备而已,挨个字的读写将使得系统调用变得频繁,众所周之,系统调用对于系统而已是个不小的开销。于是有内存映射(mmap)等物,本例中将讲述在Linux下开发摄像头的一般过程以及使用Qt进行界面开发的实例。
使用mmap方式获取摄像头数据的方式过程一般为:
打开设备 -> 获取设备的信息 -> 请求设备的缓冲区-> 获得缓冲区的开始地址及大小 ->使用mmap获得进程地址空间的缓冲区起始地址 -> 读取缓冲区。


Mmap就是所谓内存映射。很多设备带有自己的数据缓冲区,或者驱动本身在内核空间中维护一片内存区域,为了让用户空间程序安全地访问,内核往往要从设备内存或者内核空间内存复制数据到用户空间。这样一来便多了复制内存这个环节,浪费了时间。因此mmap就将目标存储区域映射到一个用户空间的一片内存,这样用户进程访问这片内存时,内核将自动转换为访问这个目标存储区。这种转换往往是地址的线性变化而已(很多设备的存储空间在所谓外围总线地址空间(X86)或者总的地址空间(ARM)上都是连续的),所以不必担心其转换的效率。

 

现在开始叙述Video4Linux2的使用。

int fd = open ("/dev/video",O_RDONLY);if (fd==-1){perror ("Can't open device");return -1;} struct v4l2_format format;memset (&format,0,sizoef(format));format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (-1==ioctl(fd,VIDIOC_G_FMT,&format)){perror ("While getting format");return -2;}  char code[5];unsigned int i;for (i=0;i<4;i++) {code[i] = (format.fmt.pix.pixelformat & (0xff<<i*8))>>i*8;}code[4]=0;   struct v4l2_requestbuffers req;memset (&req,0,sizeof(req));req.count = 10;req.type    = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory  = V4L2_MEMORY_MMAP;if (-1==ioctl(fd,VIDIOC_REQBUFS,&req)){perror ("While requesting buffers");return -3;}if (req.count < 5){fprintf (stderr, "Can't get enough buffers!\n");return -4;}  struct buffer * buffers = (struct buffer *)malloc (nbuffer*sizeof(*buffers));if (!buffers){perror ("Can't allocate memory for buffers!");return -4;} struct v4l2_buffer buf;for (nbuffer=0;nbuffer<req.count;++nbuffer) {        memset (&buf,0,sizeof(buf));        buf.type             = V4L2_BUF_TYPE_VIDEO_CAPTURE;        buf.memory   = V4L2_MEMORY_MMAP;        buf.index    = nbuffer;         if (-1==ioctl(fd,VIDIOC_QUERYBUF,&buf)){                perror ("While querying buffer");                return -5;        }         buffers[nbuffer].length = buf.length;        buffers[nbuffer].start = mmap (                NULL,                 buf.length,                PROT_READ,                  MAP_SHARED,                fd,                buf.m.offset        );         if (MAP_FAILED == buffers[nbuffer].start) {                perror ("While mapping memory");                return -6;        }}  enum v4l2_buf_type type;type = V4L2_BUF_TYPE_VIDEO_CAPTURE;if (-1==ioctl(fd,VIDIOC_STREAMON,&type)){        perror ("While opening stream");        return -7;} unsigned int i;i=0;while(1) {memset (&buf,0,sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i; if (-1==ioctl(fd,VIDIOC_DQBUF,&buf)){        perror ("While getting buffer's data");        return -8;} process_image ( buffers+buf.index,buf.index ); if (-1==ioctl(fd,VIDIOC_QBUF,&buf)){        perror ("While returning buffer's data");        return -9;} i = (i+1) & nbuffer;}

这就是所有获取的过程了。至于图像的处理,则是由解码函数和Qt来处理。现在先进行一些思路的设计。设想在进行图像的转换时,必须提供一块内存区域来进行,我们当然可以在转换时使用malloc来进行动态分配,转换完成并显示后,再将它free。然而这样做对内核而言是一个不小的负担:每次为一整张图片分配内存,最少也有上百KB,如此大的分配量和释放量,很容易造成内存碎片加重内核的负担。由于仅是每转换一次才显示一次图像,所以这片用于转换的内存区域可以安全地复用,不会同时由两个线程操作之。因此在初始化时我们为每一块内存映射缓冲区分配一块内存区域作为转换用。对于MJPEG到JPEG的转换,使用相同的内存大小。代码就不在此列出了。这片假设这个内存区域的起始指针为convertion_buffers,在process_image(struct buffer * buf, int index ) 中,有

void process_image (struct buffer *buf, int index){        struct * buffer conv_buf = convertion_buffers+index;        do_image_conversion (        buf->start, buf->length,          conv_buf->start, conv_buf->length,         );                 m_pixmap -> loadFromData (conv_buf->start,conv_buf->length);                repaint (); }                 MyWidget::paintEvent (QPaintEvent * evt) {            QPainter painter(this);            painter.drawPixmap (QPoint(0,0),*m_pixmap);            QWidget::paintEvent(evt);        }

这里讲Pixmap画到了(0,0)处。

考虑的改进之处:

虽然上述程序已经可以工作了,但是有一些细节可以改进。比如图像转换之处,可能相当耗时。解决的办法之一可以考虑多线程,用一个线程进行数据的收集,每收集一帧数据便通知显示的进程。显示的进程使用一个FIFO收集数据,用一个定时器,在固定的时间到时,然后从FIFO中取出数据进行转换然后显示。两个线程互不干扰,可以更有效地利用CPU,使收集、转换和显示协调地工作。

0 0
原创粉丝点击