V4L2驱动的移植与应用(一)

时间:2022-01-27 04:00:29

http://disanji.net/2011/08/27/v4l2%E9%A9%B1%E5%8A%A8%E7%9A%84%E7%A7%BB%E6%A4%8D%E4%B8%8E%E5%BA%94%E7%94%A8%EF%BC%88%E4%B8%80%EF%BC%89/


V4L2(video for linux) 可以支持多种设备,它可以有以下5种接口:

1、视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.下面也是着重讲解这种应用;

2、视频输出接口(video output interface):可以驱动计算机的外围视频图像设备——像可以输出电视信号格式的设备;

3、直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU;

4、视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号;

5、收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流;

V4L2驱动的主要功能是使程序有发现设备的能力和操作设备.它主要是用过一系列的回调函数来实现这些功能.像设置高频头的频率,帧频,视频压缩格式和图像像参数等等.
一、V4L2的移植

V4L2提供了三种不同的API来传输外围设备和用户空间的数据。下面就vivi(drivers/media/video/vivi.c)来讲解一个V4L2驱动的编写。注意它是一个虚拟的设备驱动,没有与实际的硬件打交道。
1、分析几个重要数据结构:
vivi.c包含头文件v4l2-device.h和v4l2-ioctl.h,其中v4l2-device.h中包含了v4l2-subdev.h,v4l2-subdev.h中又包含了v4l2-common.h,v4l2-common.h中包含了v4l2-dev.h。

在v4l2-dev.h中定义了结构体video_device和v4l2_file_operations;

在v4l2-ioctl.h中定义了结构体v4l2_ioctl_ops;

在v4l2-device.h中定义了结构体v4l2_device;

1) vivi_fops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static const struct v4l2_file_operations vivi_fops = {
 
       .owner           = THIS_MODULE,
 
       .open           = vivi_open,
 
       .release        = vivi_close,
 
       .read           = vivi_read,
 
       .poll        = vivi_poll,
 
       .ioctl          = video_ioctl2, /* V4L2 ioctl handler */
 
       .mmap           = vivi_mmap,
 
};

2) vivi_ioctl_ops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
 
       .vidioc_querycap      = vidioc_querycap,
 
       .vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
 
       .vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
 
       .vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
 
       .vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
 
       .vidioc_reqbufs       = vidioc_reqbufs,
 
       .vidioc_querybuf      = vidioc_querybuf,
 
       .vidioc_qbuf          = vidioc_qbuf,
 
       .vidioc_dqbuf         = vidioc_dqbuf,
 
       .vidioc_s_std         = vidioc_s_std,
 
       .vidioc_enum_input    = vidioc_enum_input,
 
       .vidioc_g_input       = vidioc_g_input,
 
       .vidioc_s_input       = vidioc_s_input,
 
       .vidioc_queryctrl     = vidioc_queryctrl,
 
       .vidioc_g_ctrl        = vidioc_g_ctrl,
 
       .vidioc_s_ctrl        = vidioc_s_ctrl,
 
       .vidioc_streamon      = vidioc_streamon,
 
       .vidioc_streamoff     = vidioc_streamoff,
 
#ifdef CONFIG_VIDEO_V4L1_COMPAT
 
       .vidiocgmbuf          = vidiocgmbuf,
 
#endif
 
};

3) vivi_template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static struct video_device vivi_template = {
 
       .name             = "vivi",
 
       .fops           = &vivi_fops,
 
       .ioctl_ops       = &vivi_ioctl_ops,
 
       .minor            = -1,
 
       .release    = video_device_release,
 
 
 
       .tvnorms              = V4L2_STD_525_60,
 
       .current_norm         = V4L2_STD_NTSC_M,
 
};

其中函数vivi_xxx和vidioc_xxx都是在vivi.c中实现的。如果要基于某个硬件来实现V4L2的接口,那这些函数就需要调用硬件的驱动去实现。

4) vivi_dev

1
2
3
4
5
6
7
8
9
10
11
12
struct vivi_dev {
    struct list_head           vivi_devlist; //内核双向链表,在内核数据结构里有描述
    struct semaphore           lock;   //信号量,防止竞态访问
    int                        users;  //用户数量计数
    /* various device info */
    unsigned int               resources;
    struct video_device        video_dev; //这个成员是这个结构的核心,用面向对象的话来说就是基类
    struct vivi_dmaqueue       vidq;       //DMA队列
    /* Several counters */
    int                        h,m,s,us,jiffies;   //定时器定义
    char                       timestr[13];     //其它一些资源变量.
};

像这样变义的结构在Linux C 中很普遍,这也是利用C来实现面向对象编程的强大方法。建立这个结构对象之后,所有的操作都是基于这个结构,或者这个结构派生出的来的其它结构。
5) vivi_fh

1
2
3
4
5
6
7
8
9
10
struct vivi_fh {
    struct vivi_dev            *dev;
 
    /* video capture */
    struct vivi_fmt            *fmt;
    unsigned int               width,height;
    struct videobuf_queue      vb_vidq;
 
    enum v4l2_buf_type         type;
};

这个结构即是vivi_dev结构的更深层次封装,基于那个结构加入了更多的描述信息,如视频制式、视频画面大小、视频缓冲队列等等。在open的时候,会把这个结构赋给file结构中的private_data域。在释放设备时注销.其它的像ioctl,mmap,read,write等等都会用到这个结构,其实整个模块的编写的cdev差不多。只是视频设备的基类是video_device,而字符设备的基类是cdev而已。

2、数据传输方式:
在设备与应用程序之间有三种数据传输方式:
1)read与write这种方式,它像其它设备驱动一样,但是这种方式很慢,对于数据视频流不能满足其要求;

2)直接的内存访问,可以通过其映射方式来传输(IO数据流,交换指向缓冲区指针的方法);这是视频设备通常用的方法,采用mmap()的方法,即有内核空间里开辟内存,再在程序里把这部分的内存映射到程序空间。如果有设备内存,即直接映射到设备的内核,这种性能更高。
3)异步IO口访问,但是这种方法在V4L2模块中还没有实现。(重要:需要确认)

vivi中的mmap是利用第二种方法来实现的,这也是视频设备常用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int
vivi_mmap(struct file *file, struct vm_area_struct * vma)
{
    struct vivi_fh *fh = file->private_data;
    int ret;
 
    dprintk (1,"mmap called, vma=0x%08lx/n",(unsigned long)vma);
 
    ret=videobuf_mmap_mapper(&fh->vb_vidq, vma);
 
    dprintk (1,"vma start=0x%08lx, size=%ld, ret=%d/n",
        (unsigned long)vma->vm_start,
        (unsigned long)vma->vm_end-(unsigned long)vma->vm_start,
        ret);
 
    return ret;
}

videobuf_mmap_mapper(&fh->vb_vidq, vma); 这个核心函数把设备的I/O内存或者设备内存映射到系统为它开辟的虚拟内存。

3、操控设备的实现: ioctl

1
2
3
4
static int vivi_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    return video_usercopy(inode, file, cmd, arg, vivi_do_ioctl);  
}

vivi_do_ioctl 这个函数里调用一些命令来设备V4L2模块中的一些结构参数来改变或者获取设备的


二、V4L2的应用

下面简单介绍一下V4L2驱动的应用流程。
1、 视频采集的基本流程
一般的,视频采集都有如下流程:
2、 打开视频设备
在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备:

1
2
3
4
5
// 用非阻塞模式打开摄像头设备
int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
// 如果用阻塞模式打开摄像头设备,上述代码变为:
//cameraFd = open("/dev/video0", O_RDWR, 0);

关于阻塞模式和非阻塞模式:应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。

3、 设定属性及采集方式

打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:

1
extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;

__fd:设备的ID,例如刚才用open函数打开视频通道后返回的cameraFd;

__request:具体的命令标志符。

在进行V4L2开发中,一般会用到以下的命令标志符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
VIDIOC_REQBUFS:分配内存
 
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
 
VIDIOC_QUERYCAP:查询驱动功能
 
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
 
VIDIOC_S_FMT:设置当前驱动的频捕获格式
 
VIDIOC_G_FMT:读取当前驱动的频捕获格式
 
VIDIOC_TRY_FMT:验证当前驱动的显示格式
 
VIDIOC_CROPCAP:查询驱动的修剪能力
 
VIDIOC_S_CROP:设置视频信号的边框
 
VIDIOC_G_CROP:读取视频信号的边框
 
VIDIOC_QBUF:把数据从缓存中读取出来
 
VIDIOC_DQBUF:把数据放回缓存队列
 
VIDIOC_STREAMON:开始视频显示函数
 
VIDIOC_STREAMOFF:结束视频显示函数
 
VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。

这些IO调用,有些是必须的,有些是可选择的。

4、 检查当前视频设备支持的标准

在亚洲,一般使用PAL(720X576)制式的摄像头,而欧洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD来检测:

1
2
3
4
5
6
7
8
9
10
v4l2_std_id std;
do {
  ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
    case V4L2_STD_NTSC:
        //……
    case V4L2_STD_PAL:
        //……
}

5、 设置视频捕获格式

当检测完视频设备支持的标准后,还需要设定视频捕获格式:

1
2
3
4
5
6
7
8
9
10
struct v4l2_format    fmt;
memset ( &fmt, 0, sizeof(fmt) );
fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width       = 720;
fmt.fmt.pix.height      = 576;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
  return -1;
}

v4l2_format结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct v4l2_format
{
    enum v4l2_buf_type type;    // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
    union
    {
        struct v4l2_pix_format    pix; 
        struct v4l2_window        win; 
        struct v4l2_vbi_format    vbi; 
        __u8    raw_data[200];         
    } fmt;
};
struct v4l2_pix_format
{
    __u32                   width;         // 宽,必须是16的倍数
    __u32                   height;        // 高,必须是16的倍数
    __u32                   pixelformat;   // 视频数据存储类型,例如是YUV4:2:2还是RGB
    enum v4l2_field         field;
    __u32                   bytesperline;   
    __u32                   sizeimage;
    enum v4l2_colorspace    colorspace;
    __u32                   priv;      
};

6、 分配内存

接下来可以为视频捕获分配内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct v4l2_requestbuffers  req;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
  return -1;
}
 
v4l2_requestbuffers定义如下:
 
struct v4l2_requestbuffers
{
    __u32               count;  // 缓存数量,也就是说在缓存队列里保持多少张照片
    enum v4l2_buf_type  type;   // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
    enum v4l2_memory    memory; // V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR
    __u32               reserved[2];
};

7、 获取并记录缓存的物理空间

使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
typedef struct VideoBuffer {
    void   *start;
    size_t  length;
} VideoBuffer;
 
VideoBuffer*          buffers = calloc( req.count, sizeof(*buffers) );
struct v4l2_buffer    buf;
 
for (numBufs = 0; numBufs < req.count; numBufs++) {
    memset( &buf, 0, sizeof(buf) );
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = numBufs;
    // 读取缓存
    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
        return -1;
    }
 
    buffers[numBufs].length = buf.length;
    // 转换成相对地址
    buffers[numBufs].start = mmap(NULL, buf.length,
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        fd, buf.m.offset);
 
    if (buffers[numBufs].start == MAP_FAILED) {
        return -1;
    }
 
    // 放入缓存队列
    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
        return -1;
    }
}

8、 关于视频采集方式

操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是 供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。

一共有三种视频采集方式:

1)使用read、write方式:直接使用 read 和 write 函数进行读写。这种方式最简单,但是这种方式会在 用户空间和内核空间不断拷贝数据 ,同时在用户空间和内核空间占用 了 大量内存,效率不高。

2)内存映射方式(mmap):把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。

3)用户指针模式:内存由用户空间的应用程序分配,并把地址传递到内核中的驱动程序,然后由 v4l2 驱动程序直接将数据填充到用户空间的内存中。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。

第一种方式效率是最低的,后面两种方法都能提高执行的效率,但是对于mmap 方式,文档中有这样一句描述 –Remember the buffers are allocated in physical memory, as opposed to virtual memory which can be swapped out to disk. Applications should free the buffers as soon as possible with the munmap () function .(使用mmap方法的时候,buffers相当于是在内核空间中分配的,这种情况下,这些buffer是不能被交换到虚拟内存中,虽然这种方法不怎么影响读写效率,但是它一直占用着内核空间中的内存,当系统的内存有限的时候,如果同时运行有大量的进程,则对系统的整体性能会有一定的影响。)

所以,对于三种视频采集方式的选择,推荐的顺序是 userptr 、 mmap 、 read-write 。当使用 mmap 或 userptr 方式的时候,有一个环形缓冲队列的概念,这个队列中,有 n 个 buffer ,驱动程序采集到的视频帧数据,就是存储在每个 buffer 中。在每次用 VIDIOC_DQBUF 取出一个 buffer ,并且处理完数据后,一定要用 VIDIOC_QBUF 将这个 buffer 再次放回到环形缓冲队列中。环形缓冲队列,也使得这两种视频采集方式的效率高于直接 read/write 。

9、 处理采集数据

V4L2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的 视频数据缓存送出,并重新采集一张视频数据。这个过程需要用到两个ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;
 
//读取缓存
if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)
{
    return -1;
}
//…………视频处理算法
//重新放入缓存队列
if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) {
 
    return -1;
}

10、 关闭视频设备

使用close函数关闭一个视频设备

1
close(cameraFd)

三、V4L2的demo
capture.c是官方示例程序。
capture.c 程序中的 process_image 函数:
capture.c 程序主要是用来演示怎样使用 v4l2 接口,并没有对采集到的视频帧数据做任何实际的处理,仅仅用 process_image 函数表示了处理图像的代码位置。

process_image 函数只有一个参数,就是存储视频帧的内存的地址指针,但是在真正的应用中,通常还需要知道该指针指向的数据的大小。

因此可以修改函数,改成 void process_image ( const void * p, int len ) ,但是每次调用 process_image 的时候,第 2 个参数该传递什么值?

考虑到程序中对 buffer 的定义

1
2
3
4
5
struct buffer {
 
  void * start;
 
  size_t length};

如果将 buffer.length 作为第 2 个参数传递到修改后的 process_image 函数中,这样做是不正确的。 process_image 需要的第二个参数应该是每帧图像的大小,仔细阅读代码后会发现, buffer.length 并不一定就等于图像帧的大小。 (buffer 的大小,还需要考虑其他的一些因素,比如内存对齐等 )。

capture.c只是一个示例程序,仅仅是演示怎样使用v4l2中最基本的接口。尤其是在main函数中的那几个函数调用,表明了在使用v4l2时的最基本的一个流程,包括 open_device,init_device,start_capturing,mainloop,stop_capturing,uninit_device,close_device。在写程序的时候,可以充分的利用这几个基本模块,把他们分散在不同的代码位置上,灵活的调用,有兴趣的可以看一下gstreamer中v4l2src的源代码或者其他的大型程序的相关部分。

总之一句话,capture.c仅仅是一个演示程序,不要局限于它的代码结构,要灵活的使用。

下面是capture.c的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
#include <stdio.h>  
 
#include <stdlib.h>  
 
#include <string.h>  
 
#include <assert.h>  
 
#include <getopt.h>             /* getopt_long() */  
 
#include <fcntl.h>              /* low-level i/o */  
 
#include <unistd.h>  
 
#include <errno.h>  
 
#include <malloc.h>  
 
#include <sys/stat.h>  
 
#include <sys/types.h>  
 
#include <sys/time.h>  
 
#include <sys/mman.h>  
 
#include <sys/ioctl.h>  
 
#include <asm/types.h>          /* for videodev2.h */  
 
#include <linux/videodev2.h>  
 
#define CLEAR(x) memset (&(x), 0, sizeof (x))  
 
typedef enum {  
 
    IO_METHOD_READ, IO_METHOD_MMAP, IO_METHOD_USERPTR,  
 
} io_method;  
 
struct buffer {  
 
    void * start;  
 
    size_t length;//buffer's length is different from cap_image_size  
 
};  
 
static char * dev_name = NULL;  
 
static io_method io = IO_METHOD_MMAP;//IO_METHOD_READ;//IO_METHOD_MMAP;  
 
static int fd = -1;  
 
struct buffer * buffers = NULL;  
 
static unsigned int n_buffers = 0;  
 
static FILE * outf = 0;  
 
static unsigned int cap_image_size = 0;//to keep the real image size!!  
 
//////////////////////////////////////////  
 
static void errno_exit(const char * s) {  
 
    fprintf(stderr, "%s error %d, %s/n", s, errno, strerror(errno));  
 
    exit(EXIT_FAILURE);  
 
}  
 
static int xioctl(int fd, int request, void * arg) {  
 
    int r;  
 
    do  
 
        r = ioctl(fd, request, arg);  
 
    while (-1 == r && EINTR == errno);  
 
    return r;  
 
}  
 
static void process_image(const void * p, int len) {  
 
    //  static char[115200] Outbuff ;  
 
    fputc('.', stdout);  
 
    if (len > 0) {  
 
        fputc('.', stdout);  
 
        fwrite(p, 1, len, outf);  
 
    }  
 
    fflush(stdout);  
 
}  
 
static int read_frame(void) {  
 
    struct v4l2_buffer buf;  
 
    unsigned int i;  
 
    switch (io) {  
 
    case IO_METHOD_READ:  
 
        if (-1 == read(fd, buffers[0].start, buffers[0].length)) {  
 
            switch (errno) {  
 
            case EAGAIN:  
 
                return 0;  
 
            case EIO:  
 
                /* Could ignore EIO, see spec. */  
 
                /* fall through */  
 
            default:  
 
                errno_exit("read");  
 
            }  
 
        }  
 
        //      printf("length = %d/r", buffers[0].length);  
 
        //      process_image(buffers[0].start, buffers[0].length);  
 
        printf("image_size = %d,/t IO_METHOD_READ buffer.length=%d/r",  
 
                cap_image_size, buffers[0].length);  
 
        process_image(buffers[0].start, cap_image_size);  
 
        break;  
 
    case IO_METHOD_MMAP:  
 
        CLEAR (buf);  
 
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
        buf.memory = V4L2_MEMORY_MMAP;  
 
        if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {  
 
            switch (errno) {  
 
            case EAGAIN:  
 
                return 0;  
 
            case EIO:  
 
                /* Could ignore EIO, see spec. */  
 
                /* fall through */  
 
            default:  
 
                errno_exit("VIDIOC_DQBUF");  
 
            }  
 
        }  
 
        assert(buf.index < n_buffers);  
 
        //      printf("length = %d/r", buffers[buf.index].length);  
 
        //      process_image(buffers[buf.index].start, buffers[buf.index].length);  
 
        printf("image_size = %d,/t IO_METHOD_MMAP buffer.length=%d/r",  
 
                cap_image_size, buffers[0].length);  
 
        process_image(buffers[0].start, cap_image_size);  
 
        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))  
 
            errno_exit("VIDIOC_QBUF");  
 
        break;  
 
    case IO_METHOD_USERPTR:  
 
        CLEAR (buf);  
 
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
        buf.memory = V4L2_MEMORY_USERPTR;  
 
        if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {  
 
            switch (errno) {  
 
            case EAGAIN:  
 
                return 0;  
 
            case EIO:  
 
                /* Could ignore EIO, see spec. */  
 
                /* fall through */  
 
            default:  
 
                errno_exit("VIDIOC_DQBUF");  
 
            }  
 
        }  
 
        for (i = 0; i < n_buffers; ++i)  
 
            if (buf.m.userptr == (unsigned long) buffers[i].start && buf.length  
 
                    == buffers[i].length)  
 
                break;  
 
        assert(i < n_buffers);  
 
        //      printf("length = %d/r", buffers[i].length);  
 
        //      process_image((void *) buf.m.userptr, buffers[i].length);  
 
        printf("image_size = %d,/t IO_METHOD_USERPTR buffer.length=%d/r",  
 
                cap_image_size, buffers[0].length);  
 
        process_image(buffers[0].start, cap_image_size);  
 
        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))  
 
            errno_exit("VIDIOC_QBUF");  
 
        break;  
 
    }  
 
    return 1;  
 
}  
 
static void mainloop(void) {  
 
    unsigned int count;  
 
    count = 100;  
 
    while (count-- > 0) {  
 
        for (;;) {  
 
            fd_set fds;  
 
            struct timeval tv;  
 
            int r;  
 
            FD_ZERO(&fds);  
 
            FD_SET(fd, &fds);  
 
            /* Timeout. */  
 
            tv.tv_sec = 2;  
 
            tv.tv_usec = 0;  
 
            r = select(fd + 1, &fds, NULL, NULL, &tv);  
 
            if (-1 == r) {  
 
                if (EINTR == errno)  
 
                    continue;  
 
                errno_exit("select");  
 
            }  
 
            if (0 == r) {  
 
                fprintf(stderr, "select timeout/n");  
 
                exit(EXIT_FAILURE);  
 
            }  
 
            if (read_frame())  
 
                break;  
 
            /* EAGAIN - continue select loop. */  
 
        }  
 
    }  
 
}  
 
static void stop_capturing(void) {  
 
    enum v4l2_buf_type type;  
 
    switch (io) {  
 
    case IO_METHOD_READ:  
 
        /* Nothing to do. */  
 
        break;  
 
    case IO_METHOD_MMAP:  
 
    case IO_METHOD_USERPTR:  
 
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
        if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))  
 
            errno_exit("VIDIOC_STREAMOFF");  
 
        break;  
 
    }  
 
}  
 
static void start_capturing(void) {  
 
    unsigned int i;  
 
    enum v4l2_buf_type type;  
 
    switch (io) {  
 
    case IO_METHOD_READ:  
 
        /* Nothing to do. */  
 
        break;  
 
    case IO_METHOD_MMAP:  
 
        for (i = 0; i < n_buffers; ++i) {  
 
            struct v4l2_buffer buf;  
 
            CLEAR (buf);  
 
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
            buf.memory = V4L2_MEMORY_MMAP;  
 
            buf.index = i;  
 
            if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))  
 
                errno_exit("VIDIOC_QBUF");  
 
        }  
 
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
        if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))  
 
            errno_exit("VIDIOC_STREAMON");  
 
        break;  
 
    case IO_METHOD_USERPTR:  
 
        for (i = 0; i < n_buffers; ++i) {  
 
            struct v4l2_buffer buf;  
 
            CLEAR (buf);  
 
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
            buf.memory = V4L2_MEMORY_USERPTR;  
 
            buf.index = i;  
 
            buf.m.userptr = (unsigned long) buffers[i].start;  
 
            buf.length = buffers[i].length;  
 
            if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))  
 
                errno_exit("VIDIOC_QBUF");  
 
        }  
 
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
        if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))  
 
            errno_exit("VIDIOC_STREAMON");  
 
        break;  
 
    }  
 
}  
 
static void uninit_device(void) {  
 
    unsigned int i;  
 
    switch (io) {  
 
    case IO_METHOD_READ:  
 
        free(buffers[0].start);  
 
        break;  
 
    case IO_METHOD_MMAP:  
 
        for (i = 0; i < n_buffers; ++i)  
 
            if (-1 == munmap(buffers[i].start, buffers[i].length))  
 
                errno_exit("munmap");  
 
        break;  
 
    case IO_METHOD_USERPTR:  
 
        for (i = 0; i < n_buffers; ++i)  
 
            free(buffers[i].start);  
 
        break;  
 
    }  
 
    free(buffers);  
 
}  
 
static void init_read(unsigned int buffer_size) {  
 
    buffers = calloc(1, sizeof(*buffers));  
 
    if (!buffers) {  
 
        fprintf(stderr, "Out of memory/n");  
 
        exit(EXIT_FAILURE);  
 
    }  
 
    buffers[0].length = buffer_size;  
 
    buffers[0].start = malloc(buffer_size);  
 
    if (!buffers[0].start) {  
 
        fprintf(stderr, "Out of memory/n");  
 
        exit(EXIT_FAILURE);  
 
    }  
 
}  
 
static void init_mmap(void) {  
 
    struct v4l2_requestbuffers req;  
 
    CLEAR (req);  
 
    req.count = 4;  
 
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
    req.memory = V4L2_MEMORY_MMAP;  
 
    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {  
 
        if (EINVAL == errno) {  
 
            fprintf(stderr, "%s does not support "  
 
                "memory mapping/n", dev_name);  
 
            exit(EXIT_FAILURE);  
 
        } else {  
 
            errno_exit("VIDIOC_REQBUFS");  
 
        }  
 
    }  
 
    if (req.count < 2) {  
 
        fprintf(stderr, "Insufficient buffer memory on %s/n", dev_name);  
 
        exit(EXIT_FAILURE);  
 
    }  
 
    buffers = calloc(req.count, sizeof(*buffers));  
 
    if (!buffers) {  
 
        fprintf(stderr, "Out of memory/n");  
 
        exit(EXIT_FAILURE);  
 
    }  
 
    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {  
 
        struct v4l2_buffer buf;  
 
        CLEAR (buf);  
 
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
        buf.memory = V4L2_MEMORY_MMAP;  
 
        buf.index = n_buffers;  
 
        if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))  
 
            errno_exit("VIDIOC_QUERYBUF");  
 
        buffers[n_buffers].length = buf.length;  
 
        buffers[n_buffers].start = mmap(NULL /* start anywhere */, buf.length,  
 
                PROT_READ | PROT_WRITE /* required */,  
 
                MAP_SHARED /* recommended */, fd, buf.m.offset);  
 
        if (MAP_FAILED == buffers[n_buffers].start)  
 
            errno_exit("mmap");  
 
    }  
 
}  
 
static void init_userp(unsigned int buffer_size) {  
 
    struct v4l2_requestbuffers req;  
 
    unsigned int page_size;  
 
    page_size = getpagesize();  
 
    buffer_size = (buffer_size + page_size - 1) & ~(page_size - 1);  
 
    CLEAR (req);  
 
    req.count = 4;  
 
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
    req.memory = V4L2_MEMORY_USERPTR;  
 
    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {  
 
        if (EINVAL == errno) {  
 
            fprintf(stderr, "%s does not support "  
 
                "user pointer i/o/n", dev_name);  
 
            exit(EXIT_FAILURE);  
 
        } else {  
 
            errno_exit("VIDIOC_REQBUFS");  
 
        }  
 
    }  
 
    buffers = calloc(4, sizeof(*buffers));  
 
    if (!buffers) {  
 
        fprintf(stderr, "Out of memory/n");  
 
        exit(EXIT_FAILURE);  
 
    }  
 
    for (n_buffers = 0; n_buffers < 4; ++n_buffers) {  
 
        buffers[n_buffers].length = buffer_size;  
 
        buffers[n_buffers].start = memalign(/* boundary */page_size,  
 
                buffer_size);  
 
        if (!buffers[n_buffers].start) {  
 
            fprintf(stderr, "Out of memory/n");  
 
            exit(EXIT_FAILURE);  
 
        }  
 
    }  
 
}  
 
static void init_device(void) {  
 
    struct v4l2_capability cap;  
 
    struct v4l2_cropcap cropcap;  
 
    struct v4l2_crop crop;  
 
    struct v4l2_format fmt;  
 
    unsigned int min;  
 
    if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {  
 
        if (EINVAL == errno) {  
 
            fprintf(stderr, "%s is no V4L2 device/n", dev_name);  
 
            exit(EXIT_FAILURE);  
 
        } else {  
 
            errno_exit("VIDIOC_QUERYCAP");  
 
        }  
 
    }  
 
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {  
 
        fprintf(stderr, "%s is no video capture device/n", dev_name);  
 
        exit(EXIT_FAILURE);  
 
    }  
 
    switch (io) {  
 
    case IO_METHOD_READ:  
 
        if (!(cap.capabilities & V4L2_CAP_READWRITE)) {  
 
            fprintf(stderr, "%s does not support read i/o/n", dev_name);  
 
            exit(EXIT_FAILURE);  
 
        }  
 
        break;  
 
    case IO_METHOD_MMAP:  
 
    case IO_METHOD_USERPTR:  
 
        if (!(cap.capabilities & V4L2_CAP_STREAMING)) {  
 
            fprintf(stderr, "%s does not support streaming i/o/n", dev_name);  
 
            exit(EXIT_FAILURE);  
 
        }  
 
        break;  
 
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
 //////not all capture support crop!!!!!!!  
 
    /* Select video input, video standard and tune here. */  
 
    printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");  
 
    CLEAR (cropcap);  
 
    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
    if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) {  
 
        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
#ifndef CROP_BY_JACK  
 
        crop.c = cropcap.defrect; /* reset to default */  
 
#else  
 
        crop.c.left = cropcap.defrect.left;  
 
        crop.c.top = cropcap.defrect.top;  
 
        crop.c.width = 352;  
 
        crop.c.height = 288;  
 
#endif  
 
        printf("----->has ability to crop!!/n");  
 
        printf("cropcap.defrect = (%d, %d, %d, %d)/n", cropcap.defrect.left,  
 
                cropcap.defrect.top, cropcap.defrect.width,  
 
                cropcap.defrect.height);  
 
        if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) {  
 
            switch (errno) {  
 
            case EINVAL:  
 
                /* Cropping not supported. */  
 
                break;  
 
            default:  
 
                /* Errors ignored. */  
 
                break;  
 
            }  
 
            printf("-----!!but crop to (%d, %d, %d, %d) Failed!!/n",  
 
                    crop.c.left, crop.c.top, crop.c.width, crop.c.height);  
 
        } else {  
 
            printf("----->sussess crop to (%d, %d, %d, %d)/n", crop.c.left,  
 
                    crop.c.top, crop.c.width, crop.c.height);  
 
        }  
 
    } else {  
 
        /* Errors ignored. */  
 
        printf("!! has no ability to crop!!/n");  
 
    }  
 
    printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");  
 
    printf("/n");  
 
    ////////////crop finished!  
 
    //////////set the format  
 
    CLEAR (fmt);  
 
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
 
    fmt.fmt.pix.width = 640;  
 
    fmt.fmt.pix.height = 480;  
 
    //V4L2_PIX_FMT_YVU420, V4L2_PIX_FMT_YUV420 — Planar formats with 1/2 horizontal and vertical chroma resolution, also known as YUV 4:2:0  
 
    //V4L2_PIX_FMT_YUYV — Packed format with 1/2 horizontal chroma resolution, also known as YUV 4:2:2  
 
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YUV420;//V4L2_PIX_FMT_YUYV;  
 
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;  
 
    {  
 
        printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");  
 
        printf("=====will set fmt to (%d, %d)--", fmt.fmt.pix.width,  
 
                fmt.fmt.pix.height);  
 
        if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {  
 
            printf("V4L2_PIX_FMT_YUYV/n");  
 
        } else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) {  
 
            printf("V4L2_PIX_FMT_YUV420/n");  
 
        } else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12) {  
 
            printf("V4L2_PIX_FMT_NV12/n");  
 
        }  
 
    }  
 
    if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))  
 
        errno_exit("VIDIOC_S_FMT");  
 
    {  
 
        printf("=====after set fmt/n");  
 
        printf("    fmt.fmt.pix.width = %d/n", fmt.fmt.pix.width);  
 
        printf("    fmt.fmt.pix.height = %d/n", fmt.fmt.pix.height);  
 
        printf("    fmt.fmt.pix.sizeimage = %d/n", fmt.fmt.pix.sizeimage);  
 
        cap_image_size = fmt.fmt.pix.sizeimage;  
 
        printf("    fmt.fmt.pix.bytesperline = %d/n", fmt.fmt.pix.bytesperline);  
 
        printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");  
 
        printf("/n");  
 
    }  
 
    cap_image_size = fmt.fmt.pix.sizeimage;  
 
    /* Note VIDIOC_S_FMT may change width and height. */  
 
    printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");  
 
    /* Buggy driver paranoia. */  
 
    min = fmt.fmt.pix.width * 2;  
 
    if (fmt.fmt.pix.bytesperline < min)  
 
        fmt.fmt.pix.bytesperline = min;  
 
    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;  
 
    if (fmt.fmt.pix.sizeimage < min)  
 
        fmt.fmt.pix.sizeimage = min;  
 
    printf("After Buggy driver paranoia/n");  
 
    printf("    >>fmt.fmt.pix.sizeimage = %d/n", fmt.fmt.pix.sizeimage);  
 
    printf("    >>fmt.fmt.pix.bytesperline = %d/n", fmt.fmt.pix.bytesperline);  
 
    printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-/n");  
 
    printf("/n");  
 
    switch (io) {  
 
    case IO_METHOD_READ:  
 
        init_read(fmt.fmt.pix.sizeimage);  
 
        break;  
 
    case IO_METHOD_MMAP:  
 
        init_mmap();  
 
        break;  
 
    case IO_METHOD_USERPTR:  
 
        init_userp(fmt.fmt.pix.sizeimage);  
 
        break;  
 
    }  
 
}  
 
static void close_device(void) {  
 
    if (-1 == close(fd))  
 
        errno_exit("close");  
 
    fd = -1;  
 
}  
 
static void open_device(void) {  
 
    struct stat st;  
 
    if (-1 == stat(dev_name, &st)) {  
 
        fprintf(stderr, "Cannot identify '%s': %d, %s/n", dev_name, errno,  
 
                strerror(errno));  
 
        exit(EXIT_FAILURE);  
 
    }  
 
    if (!S_ISCHR(st.st_mode)) {  
 
        fprintf(stderr, "%s is no device/n", dev_name);  
 
        exit(EXIT_FAILURE);  
 
    }  
 
    fd = open(dev_name, O_RDWR /* required */| O_NONBLOCK, 0);  
 
    if (-1 == fd) {  
 
        fprintf(stderr, "Cannot open '%s': %d, %s/n", dev_name, errno,  
 
                strerror(errno));  
 
        exit(EXIT_FAILURE);  
 
    }  
 
}  
 
static void usage(FILE * fp, int argc, char ** argv) {  
 
    fprintf(fp, "Usage: %s [options]/n/n"  
 
        "Options:/n"  
 
        "-d | --device name   Video device name [/dev/video0]/n"  
 
        "-h | --help          Print this message/n"  
 
        "-m | --mmap          Use memory mapped buffers/n"  
 
        "-r | --read          Use read() calls/n"  
 
        "-u | --userp         Use application allocated buffers/n"  
 
        "", argv[0]);  
 
}  
 
static const char short_options[] = "d:hmru";  
 
static const struct option long_options[] = { { "device", required_argument,  
 
        NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { "mmap", no_argument,  
 
        NULL, 'm' }, { "read", no_argument, NULL, 'r' }, { "userp",  
 
        no_argument, NULL, 'u' }, { 0, 0, 0, 0 } };  
 
int main(int argc, char ** argv) {  
 
    dev_name = "/dev/video0";  
 
    outf = fopen("out.yuv", "wb");  
 
    for (;;) {  
 
        int index;  
 
        int c;  
 
        c = getopt_long(argc, argv, short_options, long_options, &index);  
 
        if (-1 == c)  
 
            break;  
 
        switch (c) {  
 
        case 0: /* getopt_long() flag */  
 
            break;  
 
        case 'd':  
 
            dev_name = optarg;  
 
            break;  
 
        case 'h':  
 
            usage(stdout, argc, argv);  
 
            exit(EXIT_SUCCESS);  
 
        case 'm':  
 
            io = IO_METHOD_MMAP;  
 
            break;  
 
        case 'r':  
 
            io = IO_METHOD_READ;  
 
            break;  
 
        case 'u':  
 
            io = IO_METHOD_USERPTR;  
 
            break;  
 
        default:  
 
            usage(stderr, argc, argv);  
 
            exit(EXIT_FAILURE);  
 
        }  
 
    }  
 
    open_device();  
 
    init_device();  
 
    start_capturing();  
 
    mainloop();  
 
    printf("/n");  
 
    stop_capturing();  
 
    fclose(outf);  
 
    uninit_device();  
 
    close_device();  
 
    exit(EXIT_SUCCESS);  
 
    return 0;  
 
}
本系列文章:

相关文章