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


Linux下使用各种设备是一件令人兴奋的事情。在Unix的世界里,用户与硬件打交待总是简单的。最近笔者在Linux下搞了摄像头的开发,有一点感想发于此处。

Linux中操作一个设备一般都是打开(open),读取(read)和关闭(close)。使用Read的大多是一些字符型设备,然而对于显示屏 或者摄像头这种字符设备而已,挨个字的读写将使得系统调用变得频繁,众所周之,系统调用对于系统而已是个不小的开销。于是有内存映射(mmap)等物,本 例中将讲述在Linux下开发摄像头的一般过程以及使用Qt进行界面开发的实例。

使用mmap方式获取摄像头数据的方式过程一般为:

打开设备 -> 获取设备的信息 -> 请求设备的缓冲区 -> 获得缓冲区的开始地址及大小 -> 使用mmap获得进程地址空间的缓冲区起始地址 -> 读取缓冲区。

 

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

现在开始叙述Video4Linux2的使用。

复制代码
  1 /* 打开设备并进行错误检查 */
  2 
  3 int fd = open ("/dev/video",O_RDONLY);
  4 
  5 if (fd==-1){
  6 
  7 perror ("Can't open device");
  8 
  9 return -1;
 10 
 11 }
 12 
 13  
 14 
 15 /* 查询设备的输出格式 */
 16 
 17 struct v4l2_format format;
 18 
 19 memset (&format,0,sizoef(format));
 20 
 21 format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 22 
 23 if (-1==ioctl(fd,VIDIOC_G_FMT,&format)){
 24 
 25 perror ("While getting format");
 26 
 27 return -2;
 28 
 29 }
 30 
 31  
 32 
 33 /* 
 34 
 35  * 这里要将struct v4l2_format结构体置零,然后将
 36 
 37  * format.type设定为V4L2_BUF_TYPE_VIDEO_CAPTURE,
 38 
 39  * 这样在进行 VIDIOC_G_FMT 的ioctl时,驱动就会知
 40 
 41  * 道是在捕获视频的情形下获取格式的内容。
 42 
 43  * 成功返回后,format就含有捕获视频的尺寸大小及格
 44 
 45  * 式。格式存储在 format.fmt.pix.pixelformat这个32
 46 
 47  * 位的无符号整数中,共四个字节,以小头序存储。这里
 48 
 49  * 介绍一种获取的方法。
 50 
 51  */
 52 
 53  
 54 
 55 char code[5];
 56 
 57 unsigned int i;
 58 
 59 for (i=0;i<4;i++) {
 60 
 61 code[i] = (format.fmt.pix.pixelformat & (0xff<<i*8))>>i*8;
 62 
 63 }
 64 
 65 code[4]=0;
 66 
 67  
 68 
 69 /* 现在的code是一个以/0结束的字符串。很多摄像头都是以格式MJPG输出视频的。
 70 
 71  * MJPG是Motion JPEG的缩写,其实就是一些没填霍夫曼表的JPEG图片。
 72 
 73  */
 74 
 75  
 76 
 77 /* 请求一定数量的缓冲区。
 78 
 79  * 但是不一定能请求到那么多。据体还得看返回的数量
 80 
 81  */ 
 82 
 83 struct v4l2_requestbuffers req;
 84 
 85 memset (&req,0,sizeof(req));
 86 
 87 req.count = 10;
 88 
 89 req.type    = V4L2_BUF_TYPE_VIDEO_CAPTURE;
 90 
 91 req.memory  = V4L2_MEMORY_MMAP;
 92 
 93 if (-1==ioctl(fd,VIDIOC_REQBUFS,&req)){
 94 
 95 perror ("While requesting buffers");
 96 
 97 return -3;
 98 
 99 }
100 
101 if (req.count < 5){
102 
103 fprintf (stderr, "Can't get enough buffers!/n");
104 
105 return -4;
106 
107 }
108 
109  
110 
111 /* 这里请求了10块缓存区,并将其类型设为MMAP型。 */
112 
113  
114 
115 /* 获取缓冲区的信息 
116 
117  * 在操作之前,我们必须要能记录下我们
118 
119  * 申请的缓存区,并在最后使用munmap释放它们
120 
121  * 这里使用结构体
122 
123  * struct buffer {
124 
125  *      void * start; 
126 
127  *   ssize_t length;
128 
129  * } 以及buffer数量
130 
131  * static int nbuffer
132 
133  * 来表示
134 
135  */
136 
137 struct buffer * buffers = (struct buffer *)malloc (nbuffer*sizeof(*buffers));
138 
139 if (!buffers){
140 
141 perror ("Can't allocate memory for buffers!");
142 
143 return -4;
144 
145 }
146 
147  
148 
149 struct v4l2_buffer buf;
150 
151 for (nbuffer=0;nbuffer<req.count;++nbuffer) {
152 
153 memset (&buf,0,sizeof(buf));
154 
155 buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
156 
157 buf.memory= V4L2_MEMORY_MMAP;
158 
159 buf.index  = nbuffer;
160 
161  
162 
163 if (-1==ioctl(fd,VIDIOC_QUERYBUF,&buf)){
164 
165 perror ("While querying buffer");
166 
167 return -5;
168 
169 }
170 
171  
172 
173 buffers[nbuffer].length = buf.length;
174 
175 buffers[nbuffer].start = mmap (
176 
177 NULL, 
178 
179 buf.length,
180 
181 PROT_READ,  /* 官方文档说要加上PROT_WRITE,但加上会出错 */
182 
183 MAP_SHARED,
184 
185 fd,
186 
187 buf.m.offset
188 
189 );
190 
191  
192 
193 if (MAP_FAILED == buffers[nbuffer].start) {
194 
195 perror ("While mapping memory");
196 
197 return -6;
198 
199 }
200 
201 }
202 
203  
204 
205 /*这个循环完成后,所有缓存区都保存在
206 
207  *了buffers这个数组里了,完了就再将它们munmap即可。
208 
209  */
210 
211  
212 
213 /* 打开视频捕获 */
214 
215 enum v4l2_buf_type type;
216 
217 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
218 
219 if (-1==ioctl(fd,VIDIOC_STREAMON,&type)){
220 
221 perror ("While opening stream");
222 
223 return -7;
224 
225 }
226 
227  
228 
229 /* 与内核交换缓冲区 */
230 
231 unsigned int i;
232 
233 i=0;
234 
235 while(1) {
236 
237 memset (&buf,0,sizeof(buf));
238 
239 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
240 
241 buf.memory = V4L2_MEMORY_MMAP;
242 
243 buf.index = i;
244 
245  
246 
247 if (-1==ioctl(fd,VIDIOC_DQBUF,&buf)){
248 
249 perror ("While getting buffer's data");
250 
251 return -8;
252 
253 }
254 
255  
256 
257 /* 现在就得到了一片缓冲区的数据,发送处理 */
258 
259 process_image ( buffers+buf.index,buf.index );
260 
261  
262 
263 /* 将缓冲区交还给内核 */
264 
265 if (-1==ioctl(fd,VIDIOC_QBUF,&buf)){
266 
267 perror ("While returning buffer's data");
268 
269 return -9;
270 
271 }
272 
273  
274 
275 i = (i+1) & nbuffer;
276 
277 }
复制代码

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

复制代码
 1 void process_image (struct buffer *buf, int index){
 2 
 3 struct * buffer conv_buf = convertion_buffers+index;
 4 
 5 do_image_conversion (
 6 
 7 buf->start, buf->length,  /* 要转换的区域 */
 8 
 9 conv_buf->start, conv_buf->length, /* 保存转换数据的区域 */
10 
11 );
12 
13  
14 
15 /* 现在就可以把数据取出并交给QPixmap处理 
16 
17 * 要在一个QWidget里作图,必须重载paintEvent 
18 
19 * 函数并用QPainter作画。然而paintEvent
20 
21 * 是由事件驱动层调用的,我们不能手工,
22 
23 * 所以在我们自己的的重载类里要保存一个全局 
24 
25 * 的QPixmap。这里设为 QPixmap * m_pixmap
26 
27 */
28 
29 m_pixmap -> loadFromData (conv_buf->start,conv_buf->length);
30 
31 /* 立即安排一次重绘事件 */
32 
33 repaint (); 
34 
35 }
36 
37  
38 
39 /* 重载的paintEvent示例 */
40 
41 MyWidget::paintEvent (QPaintEvent * evt) {
42 
43    QPainter painter(this);
44 
45    painter.drawPixmap (QPoint(0,0),*m_pixmap);
46 
47    QWidget::paintEvent(evt);
48 
49 }
复制代码

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

考虑的改进之处:

 

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

VN:F [1.9.6_1107]


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值