Qt/Linux 下的摄像头捕获(Video4Linux2)
Linux下使用各種設(shè)備是一件令人興奮的事情。在Unix的世界里,用戶(hù)與硬件打交待總是簡(jiǎn)單的。最近筆者在Linux下搞了攝像頭的開(kāi)發(fā),有一點(diǎn)感想發(fā)于此處。
Linux中操作一個(gè)設(shè)備一般都是打開(kāi)(open),讀取(read)和關(guān)閉(close)。使用Read的大多是一些字符型設(shè)備,然而對(duì)于顯示屏 或者攝像頭這種字符設(shè)備而已,挨個(gè)字的讀寫(xiě)將使得系統(tǒng)調(diào)用變得頻繁,眾所周之,系統(tǒng)調(diào)用對(duì)于系統(tǒng)而已是個(gè)不小的開(kāi)銷(xiāo)。于是有內(nèi)存映射(mmap)等物,本 例中將講述在Linux下開(kāi)發(fā)攝像頭的一般過(guò)程以及使用Qt進(jìn)行界面開(kāi)發(fā)的實(shí)例。
使用mmap方式獲取攝像頭數(shù)據(jù)的方式過(guò)程一般為:
打開(kāi)設(shè)備 -> 獲取設(shè)備的信息 -> 請(qǐng)求設(shè)備的緩沖區(qū) -> 獲得緩沖區(qū)的開(kāi)始地址及大小 -> 使用mmap獲得進(jìn)程地址空間的緩沖區(qū)起始地址 -> 讀取緩沖區(qū)。
?
Mmap就是所謂內(nèi)存映射。很多設(shè)備帶有自己的數(shù)據(jù)緩沖區(qū),或者驅(qū)動(dòng)本身在內(nèi)核空間中維護(hù)一片內(nèi)存區(qū)域,為了讓用戶(hù)空間程序安全地訪問(wèn),內(nèi)核往往要 從設(shè)備內(nèi)存或者內(nèi)核空間內(nèi)存復(fù)制數(shù)據(jù)到用戶(hù)空間。這樣一來(lái)便多了復(fù)制內(nèi)存這個(gè)環(huán)節(jié),浪費(fèi)了時(shí)間。因此mmap就將目標(biāo)存儲(chǔ)區(qū)域映射到一個(gè)用戶(hù)空間的一片內(nèi) 存,這樣用戶(hù)進(jìn)程訪問(wèn)這片內(nèi)存時(shí),內(nèi)核將自動(dòng)轉(zhuǎn)換為訪問(wèn)這個(gè)目標(biāo)存儲(chǔ)區(qū)。這種轉(zhuǎn)換往往是地址的線性變化而已(很多設(shè)備的存儲(chǔ)空間在所謂外圍總線地址空間 (X86)或者總的地址空間(ARM)上都是連續(xù)的),所以不必?fù)?dān)心其轉(zhuǎn)換的效率。
現(xiàn)在開(kāi)始敘述Video4Linux2的使用。
1 /* 打開(kāi)設(shè)備并進(jìn)行錯(cuò)誤檢查 */
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 /* 查詢(xún)?cè)O(shè)備的輸出格式 */
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結(jié)構(gòu)體置零,然后將
36
37 * format.type設(shè)定為V4L2_BUF_TYPE_VIDEO_CAPTURE,
38
39 * 這樣在進(jìn)行 VIDIOC_G_FMT 的ioctl時(shí),驅(qū)動(dòng)就會(huì)知
40
41 * 道是在捕獲視頻的情形下獲取格式的內(nèi)容。
42
43 * 成功返回后,format就含有捕獲視頻的尺寸大小及格
44
45 * 式。格式存儲(chǔ)在 format.fmt.pix.pixelformat這個(gè)32
46
47 * 位的無(wú)符號(hào)整數(shù)中,共四個(gè)字節(jié),以小頭序存儲(chǔ)。這里
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 /* 現(xiàn)在的code是一個(gè)以/0結(jié)束的字符串。很多攝像頭都是以格式MJPG輸出視頻的。
70
71 * MJPG是Motion JPEG的縮寫(xiě),其實(shí)就是一些沒(méi)填霍夫曼表的JPEG圖片。
72
73 */
74
75
76
77 /* 請(qǐng)求一定數(shù)量的緩沖區(qū)。
78
79 * 但是不一定能請(qǐng)求到那么多。據(jù)體還得看返回的數(shù)量
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 /* 這里請(qǐng)求了10塊緩存區(qū),并將其類(lèi)型設(shè)為MMAP型。 */
112
113
114
115 /* 獲取緩沖區(qū)的信息
116
117 * 在操作之前,我們必須要能記錄下我們
118
119 * 申請(qǐng)的緩存區(qū),并在最后使用munmap釋放它們
120
121 * 這里使用結(jié)構(gòu)體
122
123 * struct buffer {
124
125 * void * start;
126
127 * ssize_t length;
128
129 * } 以及buffer數(shù)量
130
131 * static int nbuffer
132
133 * 來(lái)表示
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, /* 官方文檔說(shuō)要加上PROT_WRITE,但加上會(huì)出錯(cuò) */
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 /*這個(gè)循環(huán)完成后,所有緩存區(qū)都保存在
206
207 *了buffers這個(gè)數(shù)組里了,完了就再將它們munmap即可。
208
209 */
210
211
212
213 /* 打開(kāi)視頻捕獲 */
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 /* 與內(nèi)核交換緩沖區(qū) */
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 /* 現(xiàn)在就得到了一片緩沖區(qū)的數(shù)據(jù),發(fā)送處理 */
258
259 process_image ( buffers+buf.index,buf.index );
260
261
262
263 /* 將緩沖區(qū)交還給內(nèi)核 */
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 }
這就是所有獲取的過(guò)程了。至于圖像的處理,則是由解碼函數(shù)和Qt來(lái)處理。現(xiàn)在先進(jìn)行一些思路的設(shè)計(jì)。設(shè)想在進(jìn)行圖像的轉(zhuǎn)換時(shí),必須提供一塊內(nèi)存區(qū)域 來(lái)進(jìn)行,我們當(dāng)然可以在轉(zhuǎn)換時(shí)使用malloc來(lái)進(jìn)行動(dòng)態(tài)分配,轉(zhuǎn)換完成并顯示后,再將它free。然而這樣做對(duì)內(nèi)核而言是一個(gè)不小的負(fù)擔(dān):每次為一整張 圖片分配內(nèi)存,最少也有上百KB,如此大的分配量和釋放量,很容易造成內(nèi)存碎片加重內(nèi)核的負(fù)擔(dān)。由于僅是每轉(zhuǎn)換一次才顯示一次圖像,所以這片用于轉(zhuǎn)換的內(nèi) 存區(qū)域可以安全地復(fù)用,不會(huì)同時(shí)由兩個(gè)線程操作之。因此在初始化時(shí)我們?yōu)槊恳粔K內(nèi)存映射緩沖區(qū)分配一塊內(nèi)存區(qū)域作為轉(zhuǎn)換用。對(duì)于MJPEG到JPEG的轉(zhuǎn) 換,使用相同的內(nèi)存大小。代碼就不在此列出了。這片假設(shè)這個(gè)內(nèi)存區(qū)域的起始指針為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, /* 要轉(zhuǎn)換的區(qū)域 */
8
9 conv_buf->start, conv_buf->length, /* 保存轉(zhuǎn)換數(shù)據(jù)的區(qū)域 */
10
11 );
12
13
14
15 /* 現(xiàn)在就可以把數(shù)據(jù)取出并交給QPixmap處理
16
17 * 要在一個(gè)QWidget里作圖,必須重載paintEvent
18
19 * 函數(shù)并用QPainter作畫(huà)。然而paintEvent
20
21 * 是由事件驅(qū)動(dòng)層調(diào)用的,我們不能手工,
22
23 * 所以在我們自己的的重載類(lèi)里要保存一個(gè)全局
24
25 * 的QPixmap。這里設(shè)為 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畫(huà)到了(0,0)處。
考慮的改進(jìn)之處:
?
雖然上述程序已經(jīng)可以工作了,但是有一些細(xì)節(jié)可以改進(jìn)。比如圖像轉(zhuǎn)換之處,可能相當(dāng)耗時(shí)。解決的辦法之一可以考慮多線程,用一個(gè)線程進(jìn)行數(shù)據(jù)的收 集,每收集一幀數(shù)據(jù)便通知顯示的進(jìn)程。顯示的進(jìn)程使用一個(gè)FIFO收集數(shù)據(jù),用一個(gè)定時(shí)器,在固定的時(shí)間到時(shí),然后從FIFO中取出數(shù)據(jù)進(jìn)行轉(zhuǎn)換然后顯 示。兩個(gè)線程互不干擾,可以更有效地利用CPU,使收集、轉(zhuǎn)換和顯示協(xié)調(diào)地工作。
VN:F [1.9.6_1107]
轉(zhuǎn)載于:https://www.cnblogs.com/elect-fans/archive/2011/12/06/2408701.html
總結(jié)
以上是生活随笔為你收集整理的Qt/Linux 下的摄像头捕获(Video4Linux2)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 求一个qq网名女生闺密二人!
- 下一篇: 门套多少钱啊?