http://www.oschina.net/code/snippet_12_368?from=rss
使用video4linux2编程接口,获得笔记本摄像头影像后,用SDL显示在X Window下的情景
关键点有二:
其一,从video4linux2的编程接口笔者了解到其笔记本摄像头支持YUYV视频帧格式,显示在SDL上需要将YUYV格式转换成SDL支持的显示格式RGB。
其二,要学会使用video4linux2获得摄像头YUYV数据流的程序框架。
YUV到RGB颜色空间的转换,可阅读video4linux2的文献,网址如下:
http://www.linuxtv.org/downloads/video4linux/API/V4L2_API/spec/ch02s02.html
YUYV数据流每个字节的含义,可阅读video4linux2的文献,网址如下:
http://www.linuxtv.org/downloads/video4linux/API/V4L2_API/spec/re09.html
举个例子,4x4像素的视频帧,内存中每个字节的存放情况如下:
一个Y'对应一个视频帧的像素。
[swc目录结构]
--------------------------------------------------------------------
.
|-- Makefile
|-- book-2010-05-21
|-- opt.c
|-- opt.h
|-- screen.c
|-- screen.h
|-- swc.c
|-- video.c
`-- video.h
关键点有二:
其一,从video4linux2的编程接口笔者了解到其笔记本摄像头支持YUYV视频帧格式,显示在SDL上需要将YUYV格式转换成SDL支持的显示格式RGB。
其二,要学会使用video4linux2获得摄像头YUYV数据流的程序框架。
YUV到RGB颜色空间的转换,可阅读video4linux2的文献,网址如下:
http://www.linuxtv.org/downloads/video4linux/API/V4L2_API/spec/ch02s02.html
YUYV数据流每个字节的含义,可阅读video4linux2的文献,网址如下:
http://www.linuxtv.org/downloads/video4linux/API/V4L2_API/spec/re09.html
举个例子,4x4像素的视频帧,内存中每个字节的存放情况如下:
start + 0: Y'[00] Cb[00] Y'[01] Cr[00] Y'[02] Cb[01] Y'[03] Cr[01] start + 8: Y'[10] Cb[10] Y'[11] Cr[10] Y'[12] Cb[11] Y'[13] Cr[11] start + 16: Y'[20] Cb[20] Y'[21] Cr[20] Y'[22] Cb[21] Y'[23] Cr[21] start + 24: Y'[30] Cb[30] Y'[31] Cr[30] Y'[32] Cb[31] Y'[33] Cr[31] |
[swc目录结构]
--------------------------------------------------------------------
.
|-- Makefile
|-- book-2010-05-21
|-- opt.c
|-- opt.h
|-- screen.c
|-- screen.h
|-- swc.c
|-- video.c
`-- video.h
标签: <无>
代码片段(8)[全屏查看所有代码]
1. [代码]Makefile
01 | CC = gcc |
02 | CFLAGS = -Wall -Werror -Wcast-align -g |
03 | LDFLAGS = |
04 |
05 | SWCOBJECT = swc.o opt.o video.o screen .o |
06 |
07 | all: swc |
08 |
09 | swc: $(SWCOBJECT) |
10 | $(CC) $(LDFLAGS) `pkg-config --libs sdl` $(SWCOBJECT) -o $@ |
11 |
12 | swc.o: swc.c |
13 | $(CC) $(CFLAGS) `pkg-config --cflags sdl` -c $< |
14 |
15 | opt.o: opt.c opt.h |
16 | $(CC) $(CFLAGS) -c $< |
17 |
18 | video.o: video.c video.h |
19 | $(CC) $(CFLAGS) -c $< |
20 |
21 | screen .o: screen .c screen .h |
22 | $(CC) $(CFLAGS) `pkg-config --cflags sdl` -c $< |
23 |
24 | clean: |
25 | rm -f *.o *~ swc |
26 |
27 | .PHONY: all clean |
2. [代码]screen.h
01 | #ifndef SCREEN_H |
02 | #define SCREEN_H |
03 |
04 | #include <SDL.h> |
05 |
06 | struct rgb_surface { |
07 | SDL_Surface *surface; |
08 | unsigned int rmask; |
09 | unsigned int gmask; |
10 | unsigned int bmask; |
11 | unsigned int amask; |
12 | int width; |
13 | int height; |
14 | int bpp; |
15 | int pitch; |
16 | unsigned char *pixels; |
17 | int pixels_num; |
18 | }; |
19 |
20 | struct screen { |
21 | SDL_Surface *display; |
22 | SDL_Event event; |
23 | int width; |
24 | int height; |
25 | int bpp; |
26 | int running; |
27 | struct rgb_surface rgb; |
28 | }; |
29 |
30 | void screen_init(); |
31 | void screen_quit(); |
32 | void screen_mainloop(); |
33 |
34 | #endif |
3. [代码]opt.h
01 | #ifndef OPT_H |
02 | #define OPT_H |
03 |
04 | struct options { |
05 | int verbose; |
06 | int width; |
07 | int height; |
08 | }; |
09 |
10 | void options_init(); |
11 | void options_deal( int argc, char *argv[]); |
12 |
13 | #endif |
4. [代码]screen.c
001 | #include <stdio.h> |
002 | #include <stdlib.h> |
003 | #include <string.h> |
004 | #include "screen.h" |
005 | #include "opt.h" |
006 | #include "video.h" |
007 |
008 | extern struct options opt; |
009 | extern struct video video; |
010 | struct screen screen; |
011 |
012 | static void sdl_init(); |
013 | static void create_rgb_surface(); |
014 | static void update_rgb_surface( int index); |
015 | static void update_rgb_pixels( const void *start); |
016 | static void yuv2rgb(unsigned char Y, |
017 | unsigned char Cb, |
018 | unsigned char Cr, |
019 | int *ER, |
020 | int *EG, |
021 | int *EB); |
022 | static int clamp( double x); |
023 |
024 | static void |
025 | sdl_init() |
026 | { |
027 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTTHREAD) < 0) { |
028 | perror ( "SDL_Init" ); |
029 | exit (EXIT_FAILURE); |
030 | } |
031 | SDL_WM_SetCaption( "Simple WebCam" , NULL); |
032 | atexit (SDL_Quit); |
033 | } |
034 |
035 | static void |
036 | create_rgb_surface() |
037 | { |
038 | screen.rgb.rmask = 0x000000ff; |
039 | screen.rgb.gmask = 0x0000ff00; |
040 | screen.rgb.bmask = 0x00ff0000; |
041 | screen.rgb.amask = 0xff000000; |
042 | screen.rgb.width = screen.width; |
043 | screen.rgb.height = screen.height; |
044 | screen.rgb.bpp = screen.bpp; |
045 | screen.rgb.pitch = screen.width * 4; |
046 | screen.rgb.pixels_num = screen.width * screen.height * 4; |
047 | screen.rgb.pixels = (unsigned char *) malloc (screen.rgb.pixels_num); |
048 | memset (screen.rgb.pixels, 0, screen.rgb.pixels_num); |
049 | screen.rgb.surface = SDL_CreateRGBSurfaceFrom(screen.rgb.pixels, |
050 | screen.rgb.width, |
051 | screen.rgb.height, |
052 | screen.rgb.bpp, |
053 | screen.rgb.pitch, |
054 | screen.rgb.rmask, |
055 | screen.rgb.gmask, |
056 | screen.rgb.bmask, |
057 | screen.rgb.amask); |
058 | } |
059 |
060 | static void |
061 | update_rgb_surface( int index) |
062 | { |
063 | update_rgb_pixels(video.buffer.buf[index].start); |
064 | SDL_BlitSurface(screen.rgb.surface, NULL, screen.display, NULL); |
065 | SDL_Flip(screen.display); |
066 | } |
067 |
068 | static void |
069 | update_rgb_pixels( const void *start) |
070 | { |
071 | unsigned char *data = (unsigned char *)start; |
072 | unsigned char *pixels = screen.rgb.pixels; |
073 | int width = screen.rgb.width; |
074 | int height = screen.rgb.height; |
075 | unsigned char Y, Cr, Cb; |
076 | int r, g, b; |
077 | int x, y; |
078 | int p1, p2, p3, p4; |
079 |
080 | for (y = 0; y < height; y++) { |
081 | for (x = 0; x < width; x++) { |
082 | p1 = y * width * 2 + x * 2; |
083 | Y = data[p1]; |
084 | if (x % 2 == 0) { |
085 | p2 = y * width * 2 + (x * 2 + 1); |
086 | p3 = y * width * 2 + (x * 2 + 3); |
087 | } |
088 | else { |
089 | p2 = y * width * 2 + (x * 2 - 1); |
090 | p3 = y * width * 2 + (x * 2 + 1); |
091 | } |
092 | Cb = data[p2]; |
093 | Cr = data[p3]; |
094 | yuv2rgb(Y, Cb, Cr, &r, &g, &b); |
095 | p4 = y * width * 4 + x * 4; |
096 | pixels[p4] = r; |
097 | pixels[p4 + 1] = g; |
098 | pixels[p4 + 2] = b; |
099 | pixels[p4 + 3] = 255; |
100 | } |
101 | } |
102 | } |
103 |
104 | static void |
105 | yuv2rgb(unsigned char Y, |
106 | unsigned char Cb, |
107 | unsigned char Cr, |
108 | int *ER, |
109 | int *EG, |
110 | int *EB) |
111 | { |
112 | double y1, pb, pr, r, g, b; |
113 |
114 | y1 = (255 / 219.0) * (Y - 16); |
115 | pb = (255 / 224.0) * (Cb - 128); |
116 | pr = (255 / 224.0) * (Cr - 128); |
117 | r = 1.0 * y1 + 0 * pb + 1.402 * pr; |
118 | g = 1.0 * y1 - 0.344 * pb - 0.714 * pr; |
119 | b = 1.0 * y1 + 1.722 * pb + 0 * pr; |
120 | /* 用GDB调试了这么久终于将BUG找出来了:), 是v4l2的文档有问题 */ |
121 | /* 不应该为clamp(r * 255) */ |
122 | *ER = clamp(r); |
123 | *EG = clamp(g); |
124 | *EB = clamp(b); |
125 | } |
126 |
127 | static int |
128 | clamp( double x) |
129 | { |
130 | int r = x; |
131 | if (r < 0) |
132 | return 0; |
133 | else if (r > 255) |
134 | return 255; |
135 | else |
136 | return r; |
137 | } |
138 |
139 | void |
140 | screen_init() |
141 | { |
142 | screen.width = opt.width; |
143 | screen.height = opt.height; |
144 | screen.bpp = 32; |
145 | screen.running = 1; |
146 | screen.display = SDL_SetVideoMode(screen.width, |
147 | screen.height, |
148 | screen.bpp, |
149 | SDL_SWSURFACE | SDL_DOUBLEBUF); |
150 | if (screen.display == NULL) { |
151 | perror ( "SDL_SetVideoMode" ); |
152 | exit (EXIT_FAILURE); |
153 | } |
154 | sdl_init(); |
155 | create_rgb_surface(); |
156 | } |
157 |
158 | void |
159 | screen_quit() |
160 | { |
161 | SDL_FreeSurface(screen.display); |
162 | SDL_FreeSurface(screen.rgb.surface); |
163 | free (screen.rgb.pixels); |
164 | SDL_Quit(); |
165 | } |
166 |
167 | void |
168 | screen_mainloop() |
169 | { |
170 | int i; |
171 |
172 | for (i = 0; screen.running && i <= video.buffer.req.count; i++) { |
173 | if (i == video.buffer.req.count) { |
174 | i = 0; |
175 | } |
176 | buffer_dequeue(i); |
177 | update_rgb_surface(i); |
178 | if (SDL_PollEvent(&screen.event) == 1) { |
179 | switch (screen.event.type) { |
180 | case SDL_KEYDOWN: |
181 | switch (screen.event.key.keysym.sym) { |
182 | case SDLK_q: |
183 | puts ( "bye" ); |
184 | screen.running = 0; |
185 | break ; |
186 | default : |
187 | break ; |
188 | } |
189 | break ; |
190 | case SDL_QUIT: |
191 | screen.running = 0; |
192 | break ; |
193 | default : |
194 | break ; |
195 | } |
196 | } |
197 | buffer_enqueue(i); |
198 | } |
199 | } |
5. [代码]video.h
01 | #ifndef VIDEO_H |
02 | #define VIDEO_H |
03 |
04 | #include <linux/videodev2.h> |
05 |
06 | struct buffer { |
07 | struct v4l2_requestbuffers req; // 请求/ |
08 | struct v4l2_buffer query; // 获取/ |
09 | struct { // 缓冲/ |
10 | void *start; |
11 | size_t length; |
12 | } *buf; |
13 | }; |
14 |
15 | struct video { |
16 | int fd; |
17 | struct v4l2_format format; // 视频帧格式 |
18 | struct buffer buffer; // 视频缓冲 |
19 | }; |
20 |
21 | void video_init(); |
22 | void video_quit(); |
23 | void buffer_enqueue( int index); |
24 | void buffer_dequeue( int index); |
25 |
26 | #endif |
6. [代码]video.c
001 | #include <stdio.h> |
002 | #include <stdlib.h> |
003 | #include <string.h> |
004 | #include <sys/types.h> |
005 | #include <sys/stat.h> |
006 | #include <sys/ioctl.h> |
007 | #include <sys/mman.h> |
008 | #include <fcntl.h> |
009 | #include <unistd.h> |
010 | #include <errno.h> |
011 | #include <assert.h> |
012 | #include "video.h" |
013 | #include "opt.h" |
014 |
015 | #define BUFFER_NUM 5 |
016 |
017 | static int stream_flag = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
018 | extern struct options opt; |
019 | struct video video; |
020 |
021 | static void video_open(); |
022 | static void video_close(); |
023 | static void video_set_format(); |
024 | static void video_streamon(); |
025 | static void video_streamoff(); |
026 | static void buffer_init(); |
027 | static void buffer_free(); |
028 | static void buffer_request(); |
029 | static void buffer_mmap( int index); |
030 |
031 | static void |
032 | video_open() |
033 | { |
034 | int i, fd; |
035 | char device[13]; |
036 |
037 | for (i = 0; i < 99; i++) { |
038 | sprintf (device, "%s%d" , "/dev/video" , i); |
039 | fd = open(device, O_RDWR); |
040 | if (fd != -1) { |
041 | if (opt.verbose) { |
042 | printf ( "open %s success\n" , device); |
043 | } |
044 | break ; |
045 | } |
046 | } |
047 | if (i == 100) { |
048 | perror ( "video open fail" ); |
049 | exit (EXIT_FAILURE); |
050 | } |
051 | video.fd = fd; |
052 | } |
053 |
054 | static void |
055 | video_close() |
056 | { |
057 | close(video.fd); |
058 | } |
059 |
060 | static void |
061 | video_set_format() |
062 | { |
063 | memset (&video.format, 0, sizeof (video.format)); |
064 | video.format.type = stream_flag; |
065 | video.format.fmt.pix.width = opt.width; |
066 | video.format.fmt.pix.height = opt.height; |
067 | video.format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; |
068 | if (ioctl(video.fd, VIDIOC_S_FMT, &video.format) == -1) { |
069 | perror ( "VIDIOC_S_FORMAT" ); |
070 | exit (EXIT_FAILURE); |
071 | } |
072 | } |
073 |
074 | static void |
075 | video_streamon() |
076 | { |
077 | if (ioctl(video.fd, VIDIOC_STREAMON, &stream_flag) == -1) { |
078 | if ( errno == EINVAL) { |
079 | perror ( "streaming i/o is not support" ); |
080 | } |
081 | else { |
082 | perror ( "VIDIOC_STREAMON" ); |
083 | } |
084 | exit (EXIT_FAILURE); |
085 | } |
086 | } |
087 |
088 | static void |
089 | video_streamoff() |
090 | { |
091 | if (ioctl(video.fd, VIDIOC_STREAMOFF, &stream_flag) == -1) { |
092 | if ( errno == EINVAL) { |
093 | perror ( "streaming i/o is not support" ); |
094 | } |
095 | else { |
096 | perror ( "VIDIOC_STREAMOFF" ); |
097 | } |
098 | exit (EXIT_FAILURE); |
099 | } |
100 | } |
101 |
102 | static void |
103 | buffer_init() |
104 | { |
105 | int i; |
106 |
107 | buffer_request(); |
108 | for (i = 0; i < video.buffer.req.count; i++) { |
109 | buffer_mmap(i); |
110 | buffer_enqueue(i); |
111 | } |
112 | } |
113 |
114 | static void |
115 | buffer_free() |
116 | { |
117 | int i; |
118 |
119 | for (i = 0; i < video.buffer.req.count; i++) { |
120 | munmap(video.buffer.buf[i].start, video.buffer.buf[i].length); |
121 | } |
122 | free (video.buffer.buf); |
123 | } |
124 |
125 | static void |
126 | buffer_request() |
127 | { |
128 |
129 | memset (&video.buffer.req, 0, sizeof (video.buffer.req)); |
130 | video.buffer.req.type = stream_flag; |
131 | video.buffer.req.memory = V4L2_MEMORY_MMAP; |
132 | video.buffer.req.count = BUFFER_NUM; |
133 | /* 设置视频帧缓冲规格 */ |
134 | if (ioctl(video.fd, VIDIOC_REQBUFS, &video.buffer.req) == -1) { |
135 | if ( errno == EINVAL) { |
136 | perror ( "video capturing or mmap-streaming is not support" ); |
137 | } |
138 | else { |
139 | perror ( "VIDIOC_REQBUFS" ); |
140 | } |
141 | exit (EXIT_FAILURE); |
142 | } |
143 | if (video.buffer.req.count < BUFFER_NUM) { |
144 | perror ( "no enough buffer" ); |
145 | exit (EXIT_FAILURE); |
146 | } |
147 | video.buffer.buf = calloc (video.buffer.req.count, |
148 | sizeof (*video.buffer.buf)); |
149 | assert (video.buffer.buf != NULL); |
150 | } |
151 |
152 | static void |
153 | buffer_mmap( int index) |
154 | { |
155 | memset (&video.buffer.query, 0, sizeof (video.buffer.query)); |
156 | video.buffer.query.type = video.buffer.req.type; |
157 | video.buffer.query.memory = V4L2_MEMORY_MMAP; |
158 | video.buffer.query.index = index; |
159 | /* 视频帧缓冲映射 */ |
160 | if (ioctl(video.fd, VIDIOC_QUERYBUF, &video.buffer.query) == -1) { |
161 | perror ( "VIDIOC_QUERYBUF" ); |
162 | exit (EXIT_FAILURE); |
163 | } |
164 | video.buffer.buf[index].length = video.buffer.query.length; |
165 | video.buffer.buf[index].start = mmap(NULL, |
166 | video.buffer.query.length, |
167 | PROT_READ | PROT_WRITE, |
168 | MAP_SHARED, |
169 | video.fd, |
170 | video.buffer.query.m.offset); |
171 | if (video.buffer.buf[index].start == MAP_FAILED) { |
172 | perror ( "mmap" ); |
173 | exit (EXIT_FAILURE); |
174 | } |
175 | } |
176 |
177 | void |
178 | video_init() |
179 | { |
180 | video_open(); |
181 | video_set_format(); |
182 | buffer_init(); |
183 | video_streamon(); |
184 | } |
185 |
186 | void |
187 | video_quit() |
188 | { |
189 | video_streamoff(); |
190 | video_close(); |
191 | buffer_free(); |
192 | } |
193 |
194 | void |
195 | buffer_enqueue( int index) |
196 | { |
197 | memset (&video.buffer.query, 0, sizeof (video.buffer.query)); |
198 | video.buffer.query.type = video.buffer.req.type; |
199 | video.buffer.query.memory = V4L2_MEMORY_MMAP; |
200 | video.buffer.query.index = index; |
201 | /* 视频帧缓冲入队 */ |
202 | if (ioctl(video.fd, VIDIOC_QBUF, &video.buffer.query) == -1) { |
203 | perror ( "VIDIOC_QBUF" ); |
204 | exit (EXIT_FAILURE); |
205 | } |
206 | } |
207 |
208 | void |
209 | buffer_dequeue( int index) |
210 | { |
211 | memset (&video.buffer.query, 0, sizeof (video.buffer.query)); |
212 | video.buffer.query.type = video.buffer.req.type; |
213 | video.buffer.query.memory = V4L2_MEMORY_MMAP; |
214 | video.buffer.query.index = index; |
215 | /* 视频帧缓冲出队 */ |
216 | if (ioctl(video.fd, VIDIOC_DQBUF, &video.buffer.query) == -1) { |
217 | perror ( "VIDIOC_DQBUF" ); |
218 | exit (EXIT_FAILURE); |
219 | } |
220 | } |
7. [代码]swc.c
01 | #include <stdlib.h> |
02 | #include "opt.h" |
03 | #include "video.h" |
04 | #include "screen.h" |
05 |
06 | int |
07 | main( int argc, char *argv[]) |
08 | { |
09 | options_init(); |
10 | options_deal(argc, argv); |
11 | video_init(); |
12 | screen_init(); |
13 | screen_mainloop(); |
14 | screen_quit(); |
15 | video_quit(); |
16 | exit (EXIT_SUCCESS); |
17 | } |
8. [代码]opt.c
01 | #include <stdio.h> |
02 | #include <stdlib.h> |
03 | #include <unistd.h> |
04 | #include "opt.h" |
05 |
06 | struct options opt; |
07 |
08 | static void show_usage(); |
09 |
10 | static void |
11 | show_usage() |
12 | { |
13 | printf ("usage: swc [options]\n\ |
14 | -h 打印该帮助信息\n\ |
15 | -v 显示程序内部提示\n"); |
16 | } |
17 |
18 | void |
19 | options_init() |
20 | { |
21 | opt.verbose = 0; |
22 | opt.width = 640; |
23 | opt.height = 480; |
24 | } |
25 |
26 | void |
27 | options_deal( int argc, char *argv[]) |
28 | { |
29 | int my_opt; |
30 |
31 | while ((my_opt = getopt(argc, argv, "hv" )) != -1) { |
32 | switch (my_opt) { |
33 | case 'h' : |
34 | show_usage(); |
35 | break ; |
36 | case 'v' : |
37 | opt.verbose = 1; |
38 | break ; |
39 | default : |
40 | show_usage(); |
41 | exit (EXIT_FAILURE); |
42 | } |
43 | } |
44 | } |