https://github.com/zytc2009/BigTeam_learning
分类: C/C++
2010-12-27 10:14:43
现在开始叙述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;
}
/*
* 这里要将struct v4l2_format结构体置零,然后将
* format.type设定为V4L2_BUF_TYPE_VIDEO_CAPTURE,
* 这样在进行 VIDIOC_G_FMT 的ioctl时,驱动就会知
* 道是在捕获视频的情形下获取格式的内容。
* 成功返回后,format就含有捕获视频的尺寸大小及格
* 式。格式存储在 format.fmt.pix.pixelformat这个32
* 位的无符号整数中,共四个字节,以小头序存储。这里
* 介绍一种获取的方法。
*/
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;
/* 现在的code是一个以\0结束的字符串。很多摄像头都是以格式MJPG输出视频的。
* MJPG是Motion JPEG的缩写,其实就是一些没填霍夫曼表的JPEG图片。
*/
/* 请求一定数量的缓冲区。
* 但是不一定能请求到那么多。据体还得看返回的数量
*/
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;
}
/* 这里请求了10块缓存区,并将其类型设为MMAP型。 */
/* 获取缓冲区的信息
* 在操作之前,我们必须要能记录下我们
* 申请的缓存区,并在最后使用munmap释放它们
* 这里使用结构体
* struct buffer {
* void * start;
* ssize_t length;
* } 以及buffer数量
* static int nbuffer
* 来表示
*/
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, /* 官方文档说要加上PROT_WRITE,但加上会出错 */
MAP_SHARED,
fd,
buf.m.offset
);
if (MAP_FAILED == buffers[nbuffer].start) {
perror ("While mapping memory");
return -6;
}
}
/*这个循环完成后,所有缓存区都保存在
*了buffers这个数组里了,完了就再将它们munmap即可。
*/
/* 打开视频捕获 */
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, /* 保存转换数据的区域 */
);
/* 现在就可以把数据取出并交给QPixmap处理
* 要在一个QWidget里作图,必须重载paintEvent
* 函数并用QPainter作画。然而paintEvent
* 是由事件驱动层调用的,我们不能手工,
* 所以在我们自己的的重载类里要保存一个全局
* 的QPixmap。这里设为 QPixmap * m_pixmap
*/
m_pixmap -> loadFromData (conv_buf->start,conv_buf->length);
/* 立即安排一次重绘事件 */
repaint ();
}
/* 重载的paintEvent示例 */
MyWidget::paintEvent (QPaintEvent * evt) {
QPainter painter(this);
painter.drawPixmap (QPoint(0,0),*m_pixmap);
QWidget::paintEvent(evt);
}
这里讲Pixmap画到了(0,0)处。
虽然上述程序已经可以工作了,但是有一些细节可以改进。比如图像转换之处,可能相当耗时。解决的办法之一可以考虑多线程,用一个线程进行数据的收集,每收 集一帧数据便通知显示的进程。显示的进程使用一个FIFO收集数据,用一个定时器,在固定的时间到时,然后从FIFO中取出数据进行转换然后显示。两个线 程互不干扰,可以更有效地利用CPU,使收集、转换和显示协调地工作。