, O6 Y6 O' h% K' U. a- C/ c 这一节将一边介绍v4l的使用方法,一边建立一套简单的函数,应该说是一套很基本的函数,它完成很基本的够能但足够展示如何使用v4l。这些函数可以用来被其他程序使用,封装基本的v4l功能。本文只介绍一些和摄像头相关的编程方法,并且是最基础和最简单的,所以一些内容并没有介绍,一些与其他视频设备(如视频采集卡)和音频设备有关的内容也没有介绍,本人也不是很理解这方面的内容。 3 `8 V9 O0 H7 Y" J+ w" Z/ C: f, S+ \$ C" N! [1 d
这里先给出接下来将要开发出来函数的一个总览。' C% \" r8 S0 c8 R, Z" ?
0 Q5 w2 E" Y, o8 n4 v) A相关结构体和函数的定义我们就放到一个名为v4l.h的文件中,相关函数的编写就放在一个名为v4l.c的文件中把。 * T6 K; v# k1 G " u; ?3 T" }* h' m1 v0 b对于这个函数库共有如下的定义(也就是大体v4l.h中的内容): ( M: S+ F4 U3 P2 R7 l! o- \. P6 v t8 w7 [+ x* x- Y }7 b2 ]' T1 B
; ?# ^% }4 U9 p5 e 1: #ifndef _V4L_H_ : l* v% Z7 p* W 2: #define _V4L_H_ 0 J+ \3 [# \6 a3 U0 n4 K. t$ ^ T 3: #include <sys/types.h> / M/ l4 H, C. r- ] 4: #include <linux/videodev.h> //使用v4l必须包含的头文件 % b+ `8 C y D5 ]: |这个头文件可以在/usr/include/linux下找到,里面包含了对v4l各种结构的定义,以及各种ioctl的使用方法,所以在下文中有关v4l的相关结构体并不做详细的介绍,可以参看此文件就会得到你想要的内容。 8 W. [! s2 B. ?6 L0 Y) w# Y& W* @% n' O& T8 ?- S
下面是定义的结构体,和相关函数,突然给出这么多的代码很唐突,不过随着一点点解释条理就会很清晰了。 6 i8 P% U5 A8 ?" O2 y: @" ^& S/ ?. N& k) }( |2 u& `1 ?3 o
9 b! a" |" \, J1 x 1: struct _v4l_struct $ S6 D" K* @ k9 q0 b) `/ C 2: ! z3 A; n- [0 c. _3 K5 q# ` 3: {0 g3 B& r3 \% j6 G. W5 m
4: . |: z; ?2 Q$ h
5: int fd;//保存打开视频文件的设备描述符 ! T) F/ y, E: b/ I. j. y 6: ) M2 w1 N' \1 f' M& B H1 A 7: struct video_capability capability;//该结构及下面的结构为v4l所定义可在上述头文件中找到! \( P0 ^; X& c K8 Q
8: 8 M# m- H3 Y- F# |/ l w, h 9: struct video_picture picture; * A4 v" O5 O( j8 G 10: 2 ^# J4 @) Q: G- b- K' t 11: struct video_mmap mmap;: a3 w: q) [, C: O
12: ( g. x W# o; d$ u0 _0 L! C; o 13: struct video_mbuf mbuf;0 x3 P* G9 ~ }5 R
14: " I3 i3 ~2 _3 d( C3 O 15: unsigned char *map;//用于指向图像数据的指针 ! |$ [( L5 z# Y% |$ m& G 16: * P! x; t! L3 Q3 d1 y& T/ a 17: int frame_current;8 b7 i7 _3 T7 x" @! ]+ K6 y
18: 1 D, S2 Y" B M& ?, b# K( f 19: int frame_using[VIDEO_MAXFRAME];//这两个变量用于双缓冲在后面介绍。# L7 m) v- u) N; D$ h
20: & o! g! E! }$ ^" I6 `! p 21: }; ) G/ S' y2 n* Y0 y3 q+ w 22: $ J. k5 A/ R0 t ` 23: typedef struct _v4l_struct v4l_device;: J" K& z2 b7 ?4 E& h/ `) h% p1 ]: o
//上面的定义的结构体,有的文中章有定义channel的变量,但对于摄像头来说设置这个变量意义不大通常只有一个channel,本文不是为了写出一个大而全且成熟的函数库,只是为了介绍如何使用v4l,再加上本人水平也有限,能够给读者一个路线我就很知足了,所以并没有设置这个变量同时与channel相关的函数也没有给出。 ; N: T4 z* s2 w+ |- t ! g$ h/ p; J: r6 U9 | 1: extern int v4l_open(char *, v4l_device *); # W7 D) V6 d( L5 b1 {% C5 s; | 2: % p5 `5 d# o5 b4 j7 k 3: extern int v4l_close(v4l_device *);) I$ O( `8 ~, h
4: 7 F# r( ]+ B; x: J( ^ 5: extern int v4l_get_capability(v4l_device *); l. z7 j# m( i+ y" i( `% f
6: ; \. R' {% p2 j4 v4 ^" r 7: extern int v4l_get_picture(v4l_device *); 9 _ }+ F) X: B; {# a2 @0 f! g! o6 @ 8: 0 X/ m" y( r, g. Q) g
9: extern int v4l_get_mbuf(v4l_device *);( o- Q. q9 x/ Y @ p
10: # b2 Q. P n7 F; g. L2 o/ p" _ 11: extern int v4l_set_picture(v4l_device *, int, int, int, int, int,); ^" B' q( W4 v. S# r( H+ k8 f( i 12: * o, U. ]( m8 C/ o5 B; Z! k! H) c
13: extern int v4l_grab_picture(v4l_device *, unsigned int); * ^& m, V) }7 l3 P) L! [ 14: 5 I( N i9 z9 E# n5 \ 15: extern int v4l_mmap_init(v4l_device *); 8 n, {' _6 ?! C& R, _7 v# L3 X 16: 0 n4 K8 i1 J/ Z; B
17: extern int v4l_grab_init(v4l_device *, int, int);' |8 Z8 X5 D$ f
18: 5 c4 X- m ?" c! q1 j 19: extern int v4l_grab_frame(v4l_device *, int); % y, s7 ^9 Q4 I o7 |/ p0 ^ 20: . A9 K) A/ p! ~' W3 t1 v; v 21: extern int v4l_grab_sync(v4l_device *);* e, i6 p( I; n9 Q
" Q9 @: U7 ]7 I. g2 n
上述函数会在下文中逐渐完成,功能也会逐渐介绍,虽然现在看起来没什么感觉只能从函数名上依稀体会它的功能,或许看起来很烦,不过看完下文就会好了。 ) [0 H2 K0 A8 }8 W( C& o+ c / r& k R7 P: v; j. c7 I
9 _/ U! i, U4 q# l3 ^4 l- A前面已经说过使用v4l视频编程的流程和对文件操作并没有什么本质的不同,大概的流程如下: 3 b" k! s; v M5 y8 r; j8 v2 U! R5 Q- c9 o7 r
1.打开视频设备(通常是/dev/video0) & p; _% ]0 w+ @6 H4 {& U) i! n5 d- `) V) F
2.获得设备信息。, G5 X$ `/ ~* }1 E- p8 w! ]
- L# i) S3 v9 C- }; C% E% o
3.根据需要更改设备的相关设置。 . x! L- @8 K; e' h8 k+ g) ^ 2 ?/ c4 y! t0 d; V 4.获得采集到的图像数据(在这里v4l提供了两种方式,直接通过打开的设备读取数据,使用mmap内存映射的方式获取数据)。4 P B% G; |7 P/ P8 [# b* M; X" d
% Q" N; W1 S+ t! Z0 G 5.对采集到的数据进行操作(如显示到屏幕,图像处理,存储成图片文件)。 2 }* F2 R s! i/ _ ' E& P* |+ l' _, u 6.关闭视频设备。! I% n [4 e/ N9 f
! Y- J: a% s& q: O. |
知道了流程之后,我们就需要根据流程完成相应的函数。 7 p1 F$ u* N6 g* X) P. g; Z1 m; i. P! L6 R* A o
( t: _0 E$ k3 ~ 2 R h" g6 C1 Y) k- A+ y. j9 n+ o那么我们首先完成第1步打开视频设备,需要完成int v4l_open(char *, v4l_device *);2 m# J8 G* v5 W0 L& k
% ]: t6 q5 a3 S9 ~7 x
具体的函数如下' n5 o8 B d3 k L8 |5 n2 ]$ }
+ N1 _: U" \3 c + f4 [; q Q* D$ a 1: #define DEFAULT_DEVICE “/dev/video0” ) Z: S' H3 _, h9 |7 k 2: ) _4 S, D/ Q% j# _' a& y* ` 3: int v4l_open(char *dev , v4l_device *vd)' d, X" F2 N) S. q/ t
4: |( S, m# w( _' C o4 A 5: {8 C @9 F& k/ ` `* t5 l6 z
6: / K; ` a( v! x3 ?
7: if(!dev)dev= DEFAULT_DEVICE;* a6 I* g' Q9 N5 o3 ^) r
8: . ?) V: Y0 q' f+ B 9: if((vd-fd=open(dev,O_RDWR))<0){perror(“v4l_open:”);return -1;}! y0 Z6 r7 n' {6 C
10: 6 Y- M& G5 L I0 V9 Y( N# ]7 x# \9 \
11: if(v4l_get_capability(vd))return -1; c& @3 T% s( m
12: 0 \% J T0 C( x5 u# g. w" |
13: if(v4l_get_picture(vd))return -1;//这两个函数就是即将要完成的获取设备信息的函数) Q T D, Q8 b* [' Q6 s' i- J
14: % j t, s4 A* @2 E$ @3 q 15: return 0; g! Q7 o$ y" O
16: ; y9 b% F; D* K+ m# h; ~% W& T 17: }4 r% q, ]+ t0 h t# h/ a2 o/ c
同样对于第6步也十分简单,就是int v4l_close(v4l_device *);的作用。/ k, _! E" {0 ^: v4 M
( Y7 M$ ^& Z# r9 f% B
函数如下: ' V* F! u' y- e4 H9 h' Q" f, { `( A* Z( ]( L3 v' Y3 G: z
: F. l. o M2 k% f 1: int v4l_close(v4l_device *vd) % W) S. q* C3 R% C4 {; d1 s8 K 2: - C5 @# |) [$ m9 s9 @ 3: {close(vd->fd);return 0;}* y+ s6 r+ g; j1 [( F/ C
现在我们完成第2步中获得设备信息的任务,下面先给出函数在对函数作出相应的说明。$ N! A J1 n1 \ h! q* `" G
% ~3 G, N- W$ Z' `* g% K9 s3 U( E, x9 `
1: int v4l_get_capability(v4l_device *vd)3 @* N! \8 ^, H( f$ `
2: 0 S) j5 e* p" | 3: { 9 S- e4 o7 ^. w; X) t2 M# m5 ]4 t
4: % b4 w4 ?6 k) V% @
5: if (ioctl(vd->fd, VIDIOCGCAP, &(vd->capability)) < 0) { + P b' h# U8 Y, W# x$ s 6: , W0 q6 U5 O4 h: p 7: perror("v4l_get_capability:"); ' F5 y/ ~- I* V1 A7 v# h1 p# K4 \
8: ' X6 B- x* e8 z0 Y3 n 9: return -1; & m* H" @3 ~8 K
10: ! {: A" V1 m8 t$ a" k 11: } 1 Z3 t0 u' i8 S% g 12: ) t& l8 l' J2 m7 v# i3 [
13: return 0; - N* O& L& v3 M f5 Q# E
14: . E/ m" P% u. r/ ~ 15: } 1 W2 z" y: ^; N/ g/ ~ 16: 4 z6 \1 C9 M9 m: e* d 17: ( e0 u: B. N+ q! D1 e; p. |* w
18: 8 j# D, ^6 z! F% P/ O+ B- P* n 19: int v4l_get_picture(v4l_device *vd) 8 B$ y" y* X/ g' W: H 20: ) z8 ~: ?9 Y9 x; n# u h& c w
21: { % H2 H, T y# r1 k+ @ a 22: 7 Y0 m4 n& S$ s* q 23: if (ioctl(vd->fd, VIDIOCGPICT, &(vd->picture)) < 0) { 9 N/ o. B) x* y, { 24: L% B% i( M M0 {) H 25: perror("v4l_get_picture:"); 5 T! Y1 i; H+ G
26: 4 j; E0 J7 E" N4 E& L& N
27: return -1; + c y- N2 j" {& D6 M
28: , c+ [8 D3 I' ^) C# M" m2 m 29: } 4 ]3 r( ?* R2 Q& f/ [, j& N 30: 5 |1 n$ T* ^6 t: H! Z4 x/ y( ~# R
31: return 0; * i; v6 \# Z1 a8 \8 R9 q
32: 2 t; }- Z$ t! C; I 33: } / |+ X* B, |" ]" d- |; l! U, l. _对于以上两个函数我们不熟悉的地方可有vd->capability和vd->picture两个结构体,和这两个函数中最主要的语句ioctl。对于ioctl的行为它是由驱动程序提供和定义的,在这里当然是由v4l所定义的,其中宏VIDIOCGCAP和VIDIOCGPICT的分别表示获得视频设备的capability和picture。对于其他的宏功能定义可以在你的Linux系统中的/usr/include/linux/videodev.h中找到,这个头文件也包含了capability和picture的定义。例如:4 n3 U- h$ g! C5 F9 | H
& C- Z7 m1 t# Q" Y! g5 t
struct video_capability ! p1 z% m; Y; j3 y 4 r& L& U& E% k5 V) V+ `& U& A{ ! y( L/ a5 r0 I k1 X `+ G) s' Z6 Y$ |& D; c# y$ m; {+ `$ |7 S
char name[32];2 U2 w& Q$ o3 n. o; G0 V1 n, @6 E
( U$ \1 }7 {! n! D# ?9 x+ A
int type; 8 ]3 l* P* i+ M5 Q7 a , O+ L3 E" T: G# x int channels; /* Num channels */ & q) A; ^0 q) f1 ]# ?5 z2 N' k3 j 2 \. ^- s# j3 Y! I7 h% ` int audios; /* Num audio devices */ 7 H7 u0 ?! r- r" r/ [$ J4 g 0 r# G2 a4 m8 h( Q q) k. q( B1 O int maxwidth; /* Supported width */9 |, Q7 t. _ q v) z
5 c# \" ^' a2 a3 y* I; R int maxheight; /* And height */) C: H1 V: ^/ Y3 ~' [9 h& S" J% _3 U
5 Q# F5 l! \/ f0 \/ @
int minwidth; /* Supported width */ . h0 U+ n% w5 M4 ~% z3 ~5 i1 _5 Y9 ~$ a. F0 _& C4 B' W) W4 x0 m2 E( c
int minheight; /* And height */& D1 i' c# i% a0 \
/ O* v. F) E4 x3 @# H
};capability结构它包括了视频设备的名称,频道数,音频设备数,支持的最大最小宽度和高度等信息。 $ T i( s; H- b ; A5 o' ?! b( Y( X+ V. ~1 ?struct video_picture * ^, m% [' g6 N& ? & g0 d. F% t& K& E% I{ + A% e) K% Q( t$ k0 p; S( ~! o# `. i. N% @% x h {4 z) F& p; y
__u16 brightness; 1 k4 D9 [; V3 t 5 J3 t& z. D: b* m8 ]' l" V3 U, ? __u16 hue; , m: N! l5 l Z# s' ^ m0 X' f0 A/ h7 b0 Z* d/ u5 [ e
__u16 colour;, G: Z( H# F/ [# X$ ~9 V4 Z' i* p3 h* M' |
/ M4 b4 V2 U( Z i
__u16 contrast;8 f6 {' g' a% a
6 K }% ]- d" ~ c K2 ~
__u16 whiteness; /* Black and white only */ ) ~( a+ r: ?- v1 W. {! C 8 J: L. [" |/ t% u( Z __u16 depth; /* Capture depth */ & g: S# E b# H 3 [, @5 p2 p8 k. P. h' J6 Z; n __u16 palette; /* Palette in use */( q5 Y# o& O. f$ R4 S
% v# }! J& Q& R7 u# {
}picture结构包括了亮度,对比度,色深,调色板等等信息。头文件里还列出了palette相关的值,这里并没有给出。 # Q. L* z/ \- k6 \1 E9 N, }1 [% S ( W, ~; ~, U' n$ F+ H 了解了以上也就了解了这两个简单函数的作用,现在我们已经获取到了相关视频设备的capabilty和picture属性。/ ~2 D! l& h3 x3 \* q
* n! t% r9 b _ P0 |. i. z % [: j! t# x% ?3 ?5 H ! m C6 `, |1 F7 O; a mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必在调用read(),write()等操作。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时访问进程B对共享内存中数据的更新,反之亦然。* w1 {# ?+ q% i9 y
& G* L+ T( p. ?; Y C; L 采用共享内存通信的一个显而易见的好处是减少I/O操作提高读取效率,因为使用mmap后进程可以直接读取内存而不需要任何数据的拷贝。6 N) U9 ?1 [3 B, Q& m# V
" u' C/ m8 w6 G! L; x3 pmmap的函数原型如下 . N" r# J1 c- q" q' c/ ]7 H$ t" n* v5 v, E
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) W+ }+ q) {6 {* c6 N/ s) t3 o7 f" \# f
addr:共享内存的起始地址,一般设为0,表示由系统分配。* F; {' \- Z, F1 z, H1 n; U4 j
+ j1 l* d: w+ h5 _: T* I
len:指定映射内存的大小。在我们这里,该值为摄像头mbuf结构体的size值,即图像数据的总大小。% G. s2 H% m! ]
! _5 T: t9 }, v" Nport:指定共享内存的访问权限 PROT_READ(可读),PROT_WRITE(可写)) ~4 T4 C9 E1 \5 p6 Y. x
: T' i( w. g X# [9 H
flags:一般设置为MAP_SHARED % p9 ]( e; \9 ^! J, y" I- M& K6 S5 g" z; D* s
fd:同享文件的文件描述符。: t) ?" G* F4 x5 k$ J" }3 o s K
, P) @8 U m' C7 }* Y ; T! Q! ?! u) f" R' ^0 h' K7 f! ?
4 b) E$ }4 B1 t介绍完了mmap的使用,就可以介绍上文中定义的函数extern int v4l_mmap_init(v4l_device *);了。先给出这个函数的代码,再做说明。: d, x5 B6 |. Q7 e5 i
1 \$ x! s( s4 W4 G: h6 L9 A
/ {+ F: W5 x9 V( F3 p% k# h8 S
1: int v4l_mmap_init(v4l_device *vd) & f9 A5 P, T- F9 ^3 J+ n 2: 4 x- X% F4 S& h3 ~
3: { ' y2 Y: L \; _7 `* |+ ]
4: 3 ^9 r7 B) N& W& P# T! v
5: if (v4l_get_mbuf(vd) < 0) ( U2 J" Q4 d j8 n
6: 3 d/ G; v- b+ V5 c
7: return -1; * ], k2 I8 n. n1 ~7 l9 a6 H 8: - S; @6 M5 B r& A0 x& }
9: if ((vd->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0) { O: w* [+ D& d2 V 10: 1 K8 |" O, [9 F4 [ 11: perror("v4l_mmap_init:mmap"); ! t0 Z# ?2 X% C5 R' k, Q 12: + {) u. W2 ]- o
13: return -1; ) V- v* ?6 G1 l5 k. Z7 @
14: + }: |! n0 J h- }" J9 F
15: } ! u- m f8 ^2 ?" r6 G
16: 1 {, s4 U& l3 T& Y" O 17: return 0; 8 }7 F+ J4 Y" G; P7 N1 h- o; ~
18: ' E! ]9 B: V( Q, f
19: } ' U+ _! U: V0 s4 H( U5 t这个函数首先使用v4l_get_mbuf(vd)获得一个摄像头重要的参数,就是需要映射内存的大小,即vd->mbuf.size,然后调用mmap,当我们在编程是调用v4l_mmap_init后,vd.map指针所指向的内存空间即为我们将要采集的图像数据。 : L9 h& j& T' Y4 @% X9 F+ V/ L+ @3 q+ B
) |( A* `+ W1 q: o
, A' O& Y0 J9 Q
获得图像前的初始化工作v4l_grab_init();该函数十分简单直接粘上去,其中将。vd->frame_using[0]和vd->frame_using[1]都设为FALSE,表示两帧的截取都没有开始。 : j5 B( N1 U1 ?3 r$ _ 6 F# w) X5 w+ T) `% Z F% D. ` 1: 1 N' R9 ^1 B* Z
2: . ~" ?. f% `! j- ]
3: int v4l_grab_init(v4l_device *vd, int width, int height) 0 X4 i ?; f# T8 l" h 4: 0 ?: R6 l) }/ n7 i+ A0 H/ v0 g! s
5: { + i0 J0 i6 l! S/ [; `+ t
6: % q, M) e* K+ V0 W
7: vd->mmap.width = width; * x; A; G# p% m& V8 `, z* b 8: : m( G* w1 Q4 H- e$ M* u 9: vd->mmap.height = height; % v' G# s& Q2 P5 H/ |0 V 10: & b0 g3 ^+ w* {. j- O7 A# Y 11: vd->mmap.format = vd->picture.palette; 3 [) P7 F( U- j- z" }) l8 ]0 l
12: " \4 ]# S0 k, L- K
13: vd->frame_current = 0; 4 M2 ]" R1 [0 F! B
14: . _% G* z% t s% @; _1 i* w+ O
15: vd->frame_using[0] = FALSE; # c- k( G ~& @+ H, ]# X# Y6 G E 16: ~/ K! ^% B/ I7 G, r! ?$ ?
17: vd->frame_using[1] = FALSE; 3 Q3 N- {9 f7 K0 z3 K9 l
18: 6 H: `% K2 K3 ] 19: : [' d* k8 q5 |, _1 s- H7 B
20: . h* S2 ]2 i: x3 Q( \, Y
21: return v4l_grab_frame(vd, 0); 3 G" d0 L9 T3 j5 S3 l2 {5 @
22: ' g% B; e! z0 s5 n' x4 N
23: } : j: L* M! w" y; Y 24: : L. ?: a, Y9 o w: N( ] 25: 5 P0 @+ z( q% j, z真正获得图像的函数extern int v4l_grab_frame(v4l_device *, int); , k- v; t, V# d* T I 3 H K' v1 E! _' ~: {' j6 I, @3 S7 n+ F" S
1: int v4l_grab_frame(v4l_device *vd, int frame) 8 r% J" U5 n) G i+ z
2: 6 |5 V( Y b" \( s1 t) l7 q, z& N# l 3: { $ i) Y) m* V0 w1 R1 Y% D
4: 7 y% P& V5 m- v: z, u1 r; d 5: if (vd->frame_using[frame]) { + j: S* h* c! }; R$ a
6: ; U; r9 ~ Y2 `0 @0 m
7: fprintf(stderr, "v4l_grab_frame: frame %d is already used.\n", frame); G; T1 F+ b" s+ H# ^ W2 v9 m 8: 6 I7 W0 z( p5 x! ]! S
9: return -1; : V" w3 Z8 f) v
10: 4 B+ j! Q& `& b) f/ V7 O 11: } 9 E$ T1 W) x7 d0 h
12: & b# o8 A* F. M
13: . j, e2 ^4 L& i, p W. s/ l
14: ' n& P" c/ V3 i; N0 J W
15: vd->mmap.frame = frame; ) c: g( E$ r" u7 Y0 c% B 16: $ T( o" z, e3 u) h
17: if (ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0) { 8 {7 j# u4 ?) `& q: v, }
18: * X/ Y- V* p- R q0 | 19: perror("v4l_grab_frame"); # V4 e/ H# m- f0 i5 D3 Q
20: # ?" s" p7 x3 L1 Y
21: return -1; 5 s2 z+ E4 P" ?! e 22: - \* o5 h2 r f1 {' H. f3 a
23: } * K* c! H K i) B 24: ! o0 F7 v# M6 ?/ i
25: vd->frame_using[frame] = TRUE; 7 W% c" T$ x- c! Z% A& m! X, d$ | 26: & ]/ {) r2 d P- v z3 [ 27: vd->frame_current = frame; ( m. Y2 a1 e" i4 w2 }
28: . c6 Y; m; v( q0 `
29: return 0; 3 k/ m; ^8 j/ S* `% b' p
30: 0 w# v2 `$ E. ]/ Z- I2 O
31: } ( ] W! E- T. [; o! }' a' ?* X
读到这里,应该觉得这个函数也是相当的简单。最关键的一步即为调用ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)),调用后相应的图像就已经获取完毕。其他的代码是为了完成双缓冲就是截取两帧图像用的,可以自己理解下。 9 r; \' R: w4 E* h. W 0 z4 x9 G2 k+ h4 Q. A 在截取图像后还要进行同步操作,就是调用extern int v4l_grab_sync(v4l_device *);函数,该函数如下 ' y1 h- H: e# Q* j , r# S0 E# \2 V* p, B . R( m4 K/ f6 a& y- L2 @, r 1: int v4l_grab_sync(v4l_device *vd) ( Q! b- y; x/ e; }* i s 2: ( M, s/ `0 \8 t6 g 3: { - F+ _4 I" l- m& Q( E1 Z1 o: d
4: ( K+ L& b1 x: y: L- ?* b
5: if (ioctl(vd->fd, VIDIOCSYNC, &(vd->frame_current)) < 0) { C: w4 N" r0 ?! L 6: p( `; G0 `4 D% ]2 }$ Y
7: perror("v4l_grab_sync"); & C# ?. z/ ^: S! f! {! e4 p7 A: k% N
8: 4 q/ u4 |3 ]5 x2 s; {" ] 9: } & p0 J% X9 O9 ]$ F9 x& U" {) f
10: . }( T) g0 |- ?/ s4 t& F# u! o
11: vd->frame_using[vd->frame_current] = FALSE; $ O r( F* @% F+ R
12: 6 b( w5 M# z+ w- I0 O1 R 13: return 0; - h* I% W, t3 T; x
14: & o5 O8 j6 p: s- a: w 15: } 3 o% h2 G! y8 L& D0 N0 [该函数返回0说明你想要获取的图像帧已经获取完毕。3 _1 k$ F' l. R6 z4 y
# s1 v8 O f* x$ r8 F . t' p3 i2 Y/ Z! h8 g5 L/ C
$ R( m% Y! Q# S, V( o
图像存在了哪里? $ C+ v3 c6 S( |. ^& c9 w" }7 j6 J( G4 q/ D* w6 g& z) W- b$ q- d
最终我们使用v4l的目的是为了获取设备中的图像,那么图像存在哪里?从上面的文章可以知道,vd.map指针所指就是你要获得的第一帧图像。图像的位置,存在vd.map+vd.mbuf.offsets[vd.frame_current]处。其中vd.frame_current=0,即为第一帧的位置,vd.frame_current=1,为第二帧的位置。 $ t0 S6 C! M' M* d& k. O% l, U7 c7 a7 }3 O
9 r+ s$ A( h0 D
% J0 T6 J/ X; W5 l$ l4 \7 r2 o; I
2 上述v4l库使用的方法 V M2 K. P9 Z$ i7 Y5 @: z4 c, K 4 \" g& {7 ] |给出了上述的一些代码,这里用一些简单的代码表明如何来使用它。上文中已经说过将相关结构体和函数的定义放到一个名为v4l.h的文件中,相关函数的编写放在一个名为v4l.c的文件。 9 ]# w: p4 }, G% E' B% L ! Q! w$ F; x+ M% m( s现在我们要使用它们。9 }) h9 u9 L) X: q, r3 c
9 I$ y) t( s+ |( M/ ~
使用的方法很简单,你创建一个.c文件,假设叫test.c吧,那么test.c如下% M% x+ `% E' G( \$ y
f# e: a' x% O+ n! I) g , j' i8 S. f! p5 S( P7 \& f, B- J 1: //test.c $ N# q7 @3 C6 u! N4 ? 2: ( @: i8 ~3 E' K u
3: include “v4l.h” . ~; e% Y; [- v9 N2 m 4: 1 ^2 L0 |, O7 g. x4 H& ^ 5: ...6 X# G! ~' G, A. @) g5 c
6: + g; ?1 J$ d# l& i 7: v4l_device vd;, D) q N7 y& ^
8: % _) u4 t: F% O! K% m5 j
9: ; C1 T \/ D0 |5 N" E! |9 A A( r
10: 6 y ]* O( Y+ h" S 11: void main() ( w' c) L6 @5 q* I 12: ! e1 a, W# @/ J5 q 13: {6 Z5 t& c9 ~: E
14: - K9 q% Q. ]+ O. r! G |% [( ^
15: v4l_open(DEFAULT_DEVICE,&vd); 2 B+ H, D+ o& R. g. y3 N4 { 16: . G2 Z; L( A* w1 t" ? 17: v4l_mmap_init(&vd); 1 e% N1 G3 {/ X/ a, k3 F 18: % ]# d6 Q5 _( y+ g
19: v4l_grab_init(&vd,320,240); - i# k* G( A: O5 h" }0 D! ~ 20: ! S! w2 E! [4 u+ p( ]
21: v4l_grab_sync(&vd);//此时就已经获得了一帧的图像,存在vd.map中 . {, u, e2 o0 _- c* d% f 22: + |) ^4 h+ f) W+ }1 B3 B% c 23: while(1) / z$ v: j4 V8 a+ l8 Z+ k 24: 7 q" O8 i S) a6 P% F& N% Y 25: { . \/ C! E1 T$ Q' w3 o0 ?3 P+ N 26: + i7 w; [8 t$ j6 J$ e5 ` 27: vd.frame_current ^= 1; 9 ]& v3 r+ r: r" I" d5 b
28: ( j$ W! X* q9 e M1 A 29: v4l_grab_frame(&vd, vd.frame_current);6 A" _7 P$ z* F F. m0 L8 u5 F% |
30: 1 k/ X! w% [( l0 `' ? L
31: v4l_grab_sync(&vd); 2 J' |! N* l" H" \" F! o 32: 5 i- F) S: N: }0 } 33: 图像处理函数(vd.map+vd. vd.map+vd.mbuf.offsets[vd.frame_current]); - \ ?5 b9 v! T/ I' ~ 34: + z* X+ Y% m8 V4 B
35: //循环采集,调用你设计的图像处理函数来处理图像5 h: R8 W/ l0 z3 @& ?
36: ) \3 J+ _1 Z# S9 ~: c
37: //其中vd.map+vd. vd.map+vd.mbuf.offsets[vd.frame_current]就是图像所在位置。 9 \- z$ K5 V6 W1 X. w 38: , H6 j7 b1 Z# U! o5 m
39: }/ f# V4 D$ v: L5 P8 ?+ p1 {9 ^
40: 6 `) N3 V9 Q( W! [, d% P- { n: U 41: }9 ]+ K6 j) M6 X9 K3 o n
42: , W) A% T4 }/ F# G" _0 |$ A* T
43: / }/ L: E' w/ V, s4 {) s* j
x j) U& w9 I! R( z7 E2 Q; ~; j7 i7 Z * x3 B* h! ?) f7 \3 有关获取的图像的一些问题& }$ n! ?( n) L2 Z2 M ], m, b2 g9 l
# W) [0 U4 A) V& p$ R( q% l9 z& f
问:我获取到的图像究竟长什么样? $ a" \( C# c! d& Y 8 h2 O: H2 x5 @6 p8 u( @) w: R/ B答:每个摄像头获取的图像数据的格式可能都不尽相同,可以通过picture. palette获得。获得的图像有黑白的,有yuv格式的,RGB格式的,也有直接为jpeg格式的。你要根据实际情况,和你的需要对图像进行处理。比如常见的,如果你要在嵌入式的LCD上显示假设LCD是RGB24的,但是你获得图像是YUV格式的那么你就将他转换为RGB24的。具体的转换方法可以上网查找,也可参考前面提到过的effectTV中的相关代码。 ; Y: j7 M3 y% ]; \% t3 z$ v) w ( s) }) ^# C5 t5 K: G 5 h/ L2 ^( F: z: {+ q$ y0 T- r: e8 B% u% }: @$ z- [2 n
问:如何显示图像或将图像保存?" j2 O& T$ J y* X, R
2 A/ u* S4 G+ N e, ~答:假设你采集到的图像为RGB24格式的,我接触过的可以使用SDL库显示(网络上很流行的叫spcaview的软件就是这样的,不过它将图像数据压缩为jpeg的格式后显示,这个软件也被经常的移植到一些嵌入式平台使用,如ARM的)。当然也可以使用嵌入式linux的Framebuffer直接写屏显示。将图像保存可以用libjpeg将其保存为jpeg图片直接存储,相关的使用方法可以上网查找。也可以使用一些视频编码,将其编码保存(我希望学习一下相关的技术因为我对这方面一点不懂,如果你有一些资料可以推荐给我看,我十分想看一看)。% Z, T! f' Y" P1 R
5 h. c1 @4 K3 F- Q$ C , P- t- X" q+ _) L2 N( E% i7 I