1
|
1 /* Capture and store a 24 bit RGB image as a PPM file */
|
|
2 /* Copyright (c) 2000 Randall Hopper
|
|
3 *
|
|
4 * Based on a grab.c from Roger Hardiman, which was
|
|
5 * based on an original program by Mark Tinguely and Jim Lowe .
|
|
6 *
|
|
7 * All rights reserved.
|
|
8 *
|
|
9 * Redistribution and use in source and binary forms, with or without
|
|
10 * modification, are permitted provided that the following conditions
|
|
11 * are met:
|
|
12 * 1. Redistributions of source code must retain the above copyright
|
|
13 * notice, this list of conditions and the following disclaimer.
|
|
14 * 2. Redistributions in binary form must reproduce the above copyright
|
|
15 * notice, this list of conditions and the following disclaimer in the
|
|
16 * documentation and/or other materials provided with the distribution.
|
|
17 * 3. All advertising materials mentioning features or use of this software
|
|
18 * must display the following acknowledgement:
|
|
19 * This product includes software developed by Mark Tinguely and Jim Lowe
|
|
20 * 4. The name of the author may not be used to endorse or promote products
|
|
21 * derived from this software without specific prior written permission.
|
|
22 *
|
|
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
26 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
|
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
33 * POSSIBILITY OF SUCH DAMAGE.
|
|
34 */
|
|
35
|
|
36 #include <assert.h>
|
|
37 #include <stdio.h>
|
|
38 #include <unistd.h>
|
|
39 #include <string.h>
|
|
40 #include <stdlib.h>
|
|
41 #include <errno.h>
|
|
42 #include <sys/types.h>
|
|
43 #include <poll.h>
|
|
44 #include <sys/mman.h>
|
|
45 #include <sys/fcntl.h>
|
|
46 #include <sys/time.h>
|
|
47 #include <libgen.h>
|
|
48 #include <sys/soundcard.h>
|
5
|
49 #include <dev/bktr/ioctl_bt848.h>
|
|
50 #include <dev/bktr/ioctl_meteor.h>
|
1
|
51 #include <machine/param.h>
|
|
52 #include <sys/types.h>
|
|
53 #include <sys/ipc.h>
|
|
54 #include <sys/shm.h>
|
|
55 #include <X11/Xlib.h>
|
|
56 #include <X11/Xutil.h>
|
|
57 #include <X11/extensions/XShm.h>
|
|
58 #include <X11/extensions/Xvlib.h>
|
|
59 #ifndef USE_XVIMAGES
|
|
60 #include "Hermes/Hermes.h"
|
|
61 #endif
|
|
62
|
|
63 #include <lirc/lirc_client.h>
|
|
64
|
|
65 /* Some BT848/BT878 cards have gain control hardware which takes
|
|
66 1 or 2 seconds to settle. If you get a washed out / too bright
|
|
67 image, change the GRABBER_SETTLE_TIME from 0 to either 1 or 2
|
|
68 */
|
|
69 /*#define GRABBER_SETTLE_TIME 10*/
|
|
70 #define GRABBER_SETTLE_TIME 0
|
|
71
|
|
72
|
|
73 #define PAL 1
|
|
74 #define NTSC 2
|
|
75
|
|
76 /* PAL is 768 x 576. NTSC is 640 x 480 */
|
|
77 #define PAL_HEIGHT 576
|
|
78 #define NTSC_HEIGHT 480
|
|
79
|
3
|
80 int bktr_fd;
|
|
81 int tuner_fd;
|
|
82 unsigned char *bktr_buffer;
|
1
|
83 #if 1
|
3
|
84 int width = 320;
|
|
85 int height = 240;
|
1
|
86 #else
|
7
|
87 int width = 720;
|
|
88 int height = 576;
|
1
|
89 #endif
|
|
90
|
|
91 #ifndef USE_XVIMAGES
|
3
|
92 static XImage *rgb_image;
|
|
93 static Pixmap pmap;
|
1
|
94 #endif
|
|
95 static XShmSegmentInfo shminfo;
|
|
96 static Display *disp;
|
3
|
97 static Window win;
|
|
98 static Visual *vis;
|
|
99 static int scr;
|
|
100 static GC gc;
|
|
101 static int depth;
|
|
102 static Colormap cmap;
|
1
|
103 static XVisualInfo *vi;
|
3
|
104 static int bits_per_pixel;
|
1
|
105 static XvImage *yuv_image;
|
|
106 static XvAdaptorInfo *xv_adaptors;
|
3
|
107 static int xv_num_adaptors;
|
|
108 static int xv_adaptor;
|
|
109 static int xv_format_id;
|
1
|
110
|
3
|
111 static int channel;
|
1
|
112
|
|
113 #define DO_IOCTL_GERR(str) fprintf(stderr, "ioctl(%s) failed: %s\n", \
|
|
114 str, strerror(errno) )
|
7
|
115 #define DO_IOCTL_SERR(str,arg) fprintf(stderr, "ioctl(%s, %ld) failed: %s\n", \
|
1
|
116 str, (long)arg, strerror(errno) )
|
|
117 /*--------------------------------------------------------------------------*/
|
|
118
|
3
|
119 void
|
|
120 Close() {
|
8
|
121 close(tuner_fd);
|
|
122 close(bktr_fd);
|
1
|
123 }
|
|
124
|
3
|
125 void
|
|
126 Open() {
|
8
|
127 struct meteor_geomet geo;
|
|
128 int buffer_size, format, source, c;
|
|
129 char *device_name;
|
1
|
130
|
8
|
131 format = PAL; /* default value */
|
|
132 source = 1; /* default value */
|
|
133 device_name = "/dev/bktr0"; /* default value */
|
1
|
134
|
8
|
135 /* Open the Meteor or Bt848/Bt878 grabber */
|
|
136 if ((bktr_fd = open(device_name, O_RDONLY)) < 0) {
|
|
137 printf("open failed: %d\n", errno);
|
|
138 exit(1);
|
|
139 }
|
|
140 if ((tuner_fd = open("/dev/tuner0", O_RDONLY)) < 0) {
|
|
141 printf("open failed: %d\n", errno);
|
|
142 exit(1);
|
|
143 }
|
|
144 /* set up the capture type and size */
|
|
145 geo.rows = height;
|
|
146 geo.columns = width;
|
|
147 geo.frames = 1;
|
3
|
148 #ifdef USE_XVIMAGES
|
8
|
149 /* Should be YUV_12, but 422 actually gives a synced picture. Though */
|
|
150 geo.oformat = METEOR_GEO_YUV_422 | METEOR_GEO_YUV_12;
|
7
|
151
|
1
|
152 #else
|
8
|
153 geo.oformat = METEOR_GEO_RGB24;
|
1
|
154 #endif
|
|
155
|
8
|
156 /* switch from interlaced capture to single field capture if */
|
|
157 /* the grab height is less than half the normal TV height */
|
|
158 /* this gives better quality captures when the object in the TV */
|
|
159 /* picture is moving */
|
|
160 if ((format == PAL) && (height <= (PAL_HEIGHT / 2)))
|
|
161 geo.oformat |= METEOR_GEO_ODD_ONLY;
|
|
162 if ((format == NTSC) && (height <= (NTSC_HEIGHT / 2)))
|
|
163 geo.oformat |= METEOR_GEO_ODD_ONLY;
|
1
|
164
|
8
|
165 if (ioctl(bktr_fd, METEORSETGEO, &geo) < 0) {
|
|
166 printf("METEORSETGEO ioctl failed: %d\n", errno);
|
|
167 exit(1);
|
|
168 }
|
|
169 /* Select PAL or NTSC */
|
|
170 switch (format) {
|
|
171 case PAL:
|
|
172 c = METEOR_FMT_PAL;
|
|
173 break;
|
|
174 case NTSC:
|
|
175 c = METEOR_FMT_NTSC;
|
|
176 break;
|
|
177 default:
|
|
178 c = METEOR_FMT_NTSC;
|
|
179 break;
|
|
180 }
|
1
|
181
|
8
|
182 c = BT848_IFORM_F_PALBDGHI;
|
|
183 if (ioctl(bktr_fd, BT848SFMT, &c) < 0) {
|
|
184 DO_IOCTL_SERR("BT848SFMT", c);
|
|
185 return;
|
|
186 }
|
|
187 c = AUDIO_TUNER;
|
|
188 if (ioctl(tuner_fd, BT848_SAUDIO, &c) < 0) {
|
|
189 DO_IOCTL_SERR("BT848SFMT", c);
|
|
190 return;
|
|
191 }
|
|
192 c = CHNLSET_AUSTRALIA;
|
|
193 if (ioctl(tuner_fd, TVTUNER_SETTYPE, &c) < 0) {
|
|
194 DO_IOCTL_SERR("TVTUNER_SETTYPE", c);
|
|
195 return;
|
|
196 }
|
|
197 if (ioctl(tuner_fd, TVTUNER_SETCHNL, &channel) < 0) {
|
|
198 DO_IOCTL_SERR("TVTUNER_SETCHNL", channel);
|
|
199 return;
|
|
200 }
|
|
201 /* Select the Video Source */
|
|
202 /* Video In, Tuner, S-Video */
|
|
203 switch (source) {
|
|
204 case 0:
|
|
205 c = METEOR_INPUT_DEV0;
|
|
206 break;
|
|
207 case 1:
|
|
208 c = METEOR_INPUT_DEV1;
|
|
209 break;
|
|
210 case 2:
|
|
211 c = METEOR_INPUT_DEV2;
|
|
212 break;
|
|
213 case 3:
|
|
214 c = METEOR_INPUT_DEV3;
|
|
215 break;
|
|
216 default:
|
|
217 c = METEOR_INPUT_DEV0;
|
|
218 break;
|
|
219 }
|
1
|
220
|
8
|
221 printf("Input - %x\n", c);
|
|
222 if (ioctl(bktr_fd, METEORSINPUT, &c) < 0) {
|
|
223 printf("ioctl failed: %d\n", errno);
|
|
224 exit(1);
|
|
225 }
|
|
226 /* Use mmap to Map the drivers grab buffer */
|
|
227 buffer_size = width * height * 4; /* R,G,B,spare */
|
|
228 bktr_buffer = (unsigned char *)mmap((caddr_t) 0, buffer_size, PROT_READ,
|
|
229 MAP_SHARED, bktr_fd, (off_t) 0);
|
1
|
230
|
8
|
231 if (bktr_buffer == (unsigned char *)MAP_FAILED)
|
|
232 exit(1);
|
1
|
233
|
|
234
|
8
|
235 /* We may need to wait for a short time to allow the grabber */
|
|
236 /* brightness to settle down */
|
|
237 sleep(GRABBER_SETTLE_TIME);
|
1
|
238 }
|
|
239
|
|
240 /*--------------------------------------------------------------------------*/
|
|
241
|
3
|
242 void
|
|
243 Capture() {
|
8
|
244 int c;
|
3
|
245
|
8
|
246 /* Perform a single frame capture */
|
|
247 c = METEOR_CAP_SINGLE;
|
|
248 ioctl(bktr_fd, METEORCAPTUR, &c);
|
1
|
249 }
|
|
250
|
|
251 /*--------------------------------------------------------------------------*/
|
|
252
|
3
|
253 void
|
|
254 SaveImage() {
|
8
|
255 unsigned char *line_buffer;
|
|
256 int o , w, h;
|
|
257 unsigned char *p;
|
|
258 unsigned char header[30];
|
|
259 char *filename = "t.ppm" /* argv[3] */ ;
|
1
|
260
|
8
|
261 /* Create the output file */
|
|
262 if ((o = open(filename, O_WRONLY | O_CREAT, 0644)) < 0) {
|
|
263 printf("ppm open failed: %d\n", errno);
|
|
264 exit(1);
|
|
265 }
|
|
266 /* make PPM header and save to file */
|
|
267 sprintf(header, "P6\n%d\n%d\n255\n", width, height);
|
|
268 write(o, header, strlen(header));
|
1
|
269
|
8
|
270 /* save the RGB data to PPM file */
|
|
271 /* save this one line at a time */
|
|
272 line_buffer = (unsigned char *)malloc(width * 3 * sizeof(unsigned char));
|
|
273 p = bktr_buffer;
|
|
274 for (h = 0; h < height; h++) {
|
|
275 for (w = 0; w < width; w++) {
|
|
276 line_buffer[(w * 3) + 2] = *p++; /* blue */
|
|
277 line_buffer[(w * 3) + 1] = *p++; /* green */
|
|
278 line_buffer[(w * 3) + 0] = *p++; /* red */
|
|
279 p++; /* NULL byte */
|
1
|
280 }
|
8
|
281 write(o, line_buffer, width * 3);
|
|
282 }
|
|
283 close(o);
|
|
284 free(line_buffer);
|
1
|
285 }
|
|
286
|
3
|
287 void
|
|
288 X_ShowCursor(void) {
|
8
|
289 XDefineCursor(disp, win, 0);
|
1
|
290 }
|
|
291
|
3
|
292 void
|
|
293 X_HideCursor(void) {
|
8
|
294 Cursor no_ptr;
|
|
295 Pixmap bm_no;
|
|
296 XColor black , dummy;
|
|
297 Colormap colormap;
|
|
298 static unsigned char bm_no_data[] = {0, 0, 0, 0, 0, 0, 0, 0};
|
1
|
299
|
8
|
300 colormap = DefaultColormap(disp, DefaultScreen(disp));
|
|
301 XAllocNamedColor(disp, colormap, "black", &black, &dummy);
|
|
302 bm_no = XCreateBitmapFromData(disp, win, bm_no_data, 8, 8);
|
|
303 no_ptr = XCreatePixmapCursor(disp, bm_no, bm_no, &black, &black, 0, 0);
|
3
|
304
|
8
|
305 XDefineCursor(disp, win, no_ptr);
|
|
306 XFreeCursor(disp, no_ptr);
|
|
307 if (bm_no != None)
|
|
308 XFreePixmap(disp, bm_no);
|
3
|
309
|
1
|
310 }
|
|
311
|
|
312 /*
|
|
313 * Sends the EWMH fullscreen state event.
|
3
|
314 *
|
1
|
315 * action: could be on of _NET_WM_STATE_REMOVE -- remove state
|
|
316 * _NET_WM_STATE_ADD -- add state
|
|
317 * _NET_WM_STATE_TOGGLE -- toggle
|
|
318 */
|
3
|
319 #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
|
|
320 #define _NET_WM_STATE_ADD 1 /* add/set property */
|
|
321 #define _NET_WM_STATE_TOGGLE 2 /* toggle property */
|
1
|
322 void
|
|
323 vo_x11_ewmh_fullscreen(int action) {
|
8
|
324 XEvent xev;
|
1
|
325
|
8
|
326 assert(action == _NET_WM_STATE_REMOVE ||
|
|
327 action == _NET_WM_STATE_ADD || action == _NET_WM_STATE_TOGGLE);
|
1
|
328
|
|
329
|
8
|
330 /* init X event structure for _NET_WM_FULLSCREEN client msg */
|
|
331 xev.xclient.type = ClientMessage;
|
|
332 xev.xclient.serial = 0;
|
|
333 xev.xclient.send_event = True;
|
|
334 xev.xclient.message_type = XInternAtom(disp, "_NET_WM_STATE", False);
|
|
335 xev.xclient.window = win;
|
|
336 xev.xclient.format = 32;
|
|
337 xev.xclient.data.l[0] = action;
|
|
338 xev.xclient.data.l[1] = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", False);
|
|
339 xev.xclient.data.l[2] = 0;
|
|
340 xev.xclient.data.l[3] = 0;
|
|
341 xev.xclient.data.l[4] = 0;
|
1
|
342
|
8
|
343 /* finally send that damn thing */
|
|
344 if (!XSendEvent(disp, DefaultRootWindow(disp), False,
|
|
345 SubstructureRedirectMask | SubstructureNotifyMask,
|
|
346 &xev)) {
|
|
347 fprintf(stderr, "Failed to send fullscreen command\n");
|
|
348 }
|
1
|
349 }
|
|
350
|
|
351
|
3
|
352 void
|
|
353 X_Setup(int w, int h) {
|
8
|
354 XGCValues gcvals;
|
|
355 Window root;
|
|
356 XVisualInfo vinfo_pref;
|
|
357 int num_vis;
|
|
358 XPixmapFormatValues *pf;
|
|
359 int num_pf , pfi, i, j;
|
|
360 XSizeHints sz_hint;
|
5
|
361
|
8
|
362 disp = XOpenDisplay(NULL);
|
|
363 if (!disp) {
|
|
364 fprintf(stderr, "X-Error: unable to connect to display\n");
|
|
365 exit(1);
|
|
366 }
|
|
367 XSynchronize(disp, True);
|
3
|
368
|
8
|
369 scr = DefaultScreen(disp);
|
|
370 vis = DefaultVisual(disp, scr);
|
|
371 root = DefaultRootWindow(disp);
|
|
372 depth = DefaultDepth(disp, scr);
|
1
|
373
|
8
|
374 vinfo_pref.screen = scr;
|
|
375 vinfo_pref.visualid = XVisualIDFromVisual(vis);
|
|
376 vi = XGetVisualInfo(disp, VisualScreenMask | VisualIDMask,
|
|
377 &vinfo_pref, &num_vis);
|
|
378 assert(num_vis == 1);
|
3
|
379
|
8
|
380 win = XCreateSimpleWindow(disp, root, 0, 0, w, h, 0, 0, 0);
|
|
381 gc = XCreateGC(disp, win, (unsigned long)0, &gcvals);
|
|
382 XSetForeground(disp, gc, 0);
|
|
383 XSetBackground(disp, gc, 1);
|
1
|
384
|
8
|
385 XMapWindow(disp, win);
|
|
386 cmap = DefaultColormap(disp, scr);
|
5
|
387
|
8
|
388 sz_hint.flags = PAspect;
|
|
389 sz_hint.min_aspect.x = width;
|
|
390 sz_hint.min_aspect.y = height;
|
|
391 sz_hint.max_aspect.x = width;
|
|
392 sz_hint.max_aspect.y = height;
|
5
|
393
|
8
|
394 /* set min height/width to 4 to avoid off by one errors */
|
|
395 sz_hint.min_width = sz_hint.min_height = 4;
|
|
396 sz_hint.flags |= PMinSize;
|
|
397 XSetWMNormalHints(disp, win, &sz_hint);
|
5
|
398
|
8
|
399 XSync(disp, False);
|
1
|
400
|
8
|
401 /* Setup with Xv extension */
|
|
402 xv_adaptor = -1;
|
|
403 xv_format_id = -1;
|
|
404 XvQueryAdaptors(disp, root, &xv_num_adaptors, &xv_adaptors);
|
|
405 for (i = 0; i < xv_num_adaptors; i++) {
|
|
406 XvAdaptorInfo *adaptor = &xv_adaptors[i];
|
|
407 int takes_images;
|
1
|
408
|
8
|
409 takes_images = adaptor->type & (XvInputMask | XvImageMask);
|
|
410 if (takes_images) {
|
|
411 XvImageFormatValues *formats;
|
|
412 int num_formats;
|
1
|
413
|
8
|
414 formats = XvListImageFormats(disp, adaptor->base_id, &num_formats);
|
|
415 for (j = 0; j < num_formats; j++)
|
|
416 if (formats[j].type == XvYUV && formats[j].format == XvPlanar &&
|
|
417 strcmp(formats[j].guid, "YV12") == 0)
|
|
418 break;
|
|
419 if (j < num_formats) {
|
|
420 xv_adaptor = i;
|
|
421 xv_format_id = formats[j].id;
|
|
422 break;
|
|
423 }
|
1
|
424 }
|
8
|
425 }
|
|
426 assert(xv_adaptor >= 0);
|
1
|
427
|
8
|
428 /* Create an image to captured frames */
|
1
|
429 #ifdef USE_XVIMAGES
|
8
|
430 yuv_image = XvShmCreateImage(disp, xv_adaptors[xv_adaptor].base_id,
|
|
431 xv_format_id, 0, w, h, &shminfo);
|
|
432 if (!yuv_image)
|
|
433 fprintf(stderr, "X-Error: unable to create XvShm XImage\n");
|
1
|
434
|
8
|
435 shminfo.shmid = shmget(IPC_PRIVATE, yuv_image->data_size, IPC_CREAT | 0777);
|
|
436 if (shminfo.shmid == -1)
|
|
437 fprintf(stderr, "SharedMemory Error: unable to get identifier\n");
|
1
|
438
|
8
|
439 shminfo.shmaddr = yuv_image->data = shmat(shminfo.shmid, 0, 0);
|
|
440 yuv_image->data = shminfo.shmaddr;
|
1
|
441 #else
|
8
|
442 rgb_image = XShmCreateImage(disp, vis, depth, ZPixmap, NULL, &shminfo, w, h);
|
|
443 if (!rgb_image)
|
|
444 fprintf(stderr, "X-Error: unable to create XShm XImage\n");
|
1
|
445
|
8
|
446 shminfo.shmid = shmget(IPC_PRIVATE,
|
|
447 rgb_image->bytes_per_line * rgb_image->height,
|
|
448 IPC_CREAT | 0777);
|
|
449 if (shminfo.shmid == -1)
|
|
450 fprintf(stderr, "SharedMemory Error: unable to get identifier\n");
|
1
|
451
|
8
|
452 shminfo.shmaddr = rgb_image->data = shmat(shminfo.shmid, 0, 0);
|
1
|
453 #endif
|
|
454
|
8
|
455 if (!XShmAttach(disp, &shminfo))
|
|
456 fprintf(stderr, "X-Error: unable to attach XShm Shared Memory Segment\n");
|
1
|
457
|
8
|
458 /* Create a pixmap for the window background */
|
1
|
459 #ifdef OLD
|
8
|
460 pmap = XShmCreatePixmap(disp, win, shminfo.shmaddr, &shminfo, w, h, depth);
|
|
461 if (!pmap)
|
|
462 fprintf(stderr, "Unable to create pixmap\n");
|
1
|
463 #endif
|
|
464
|
8
|
465 /* Determine bits-per-pixel for pixmaps */
|
|
466 pf = XListPixmapFormats(disp, &num_pf);
|
|
467 assert(pf);
|
|
468 for (pfi = 0; pfi < num_pf; pfi++)
|
|
469 if (pf[pfi].depth == vi->depth)
|
|
470 break;
|
|
471 assert(pfi < num_pf);
|
|
472 bits_per_pixel = pf[pfi].bits_per_pixel;
|
|
473 XFree(pf);
|
1
|
474
|
|
475 #ifdef OLD
|
8
|
476 XSetWindowBackgroundPixmap(disp, win, pmap);
|
1
|
477 #endif
|
|
478 }
|
|
479
|
|
480 /*--------------------------------------------------------------------------*/
|
|
481
|
3
|
482 void
|
|
483 X_Shutdown() {
|
8
|
484 XShmDetach(disp, &shminfo);
|
5
|
485 #ifdef USE_XVIMAGES
|
8
|
486 shmctl(shminfo.shmid, IPC_RMID, 0);
|
|
487 shmdt(shminfo.shmaddr);
|
|
488 XFree(yuv_image);
|
1
|
489 #else
|
8
|
490 XDestroyImage(rgb_image);
|
1
|
491 #endif
|
8
|
492 shmdt(shminfo.shmaddr);
|
|
493 shmctl(shminfo.shmid, IPC_RMID, 0);
|
1
|
494 }
|
|
495
|
|
496
|
|
497 /*--------------------------------------------------------------------------*/
|
|
498
|
3
|
499 void
|
|
500 X_Display(void) {
|
8
|
501 int _w , _h, _d;
|
|
502 Window _dw;
|
1
|
503
|
|
504 #ifdef USE_XVIMAGES
|
8
|
505 XGetGeometry(disp, win, &_dw, &_d, &_d, &_w, &_h, &_d, &_d);
|
|
506 XvShmPutImage(disp, xv_adaptors[xv_adaptor].base_id, win,
|
|
507 gc, yuv_image, 0, 0, yuv_image->width, yuv_image->height,
|
|
508 0, 0, _w, _h, True);
|
1
|
509 #else
|
8
|
510 XShmPutImage(disp, win, gc, rgb_image, 0, 0, 0, 0,
|
|
511 rgb_image->width, rgb_image->height, False);
|
1
|
512 #endif
|
8
|
513 XSync(disp, False);
|
1
|
514 }
|
|
515
|
|
516 #define CMD_NONE 0
|
|
517 #define CMD_CHNUP 1
|
|
518 #define CMD_CHNDN 2
|
|
519 #define CMD_MUTE 3
|
|
520 #define CMD_QUIT 4
|
|
521 #define CMD_RELOAD 5
|
|
522 #define CMD_CURSOR 6
|
|
523 #define CMD_VOLDN 7
|
|
524 #define CMD_VOLUP 8
|
|
525 #define CMD_FSTOGGLE 9
|
|
526
|
|
527 /*--------------------------------------------------------------------------*/
|
|
528
|
3
|
529 int
|
|
530 main(int argc, char *argv[]) {
|
1
|
531 #ifndef USE_XVIMAGES
|
8
|
532 HermesHandle conv;
|
|
533 HermesFormat fmt_source, fmt_dest;
|
1
|
534 #endif
|
8
|
535 XEvent e;
|
|
536 char *scratch_buf, *code, *c, *mixerdev;
|
|
537 int frames, channelidx, oldchan, mute, cursor, fd, ret, cmd;
|
|
538 int exitnow, mfd, uselirc, curvol;
|
|
539 struct timeval then, now, diff, lastmove;
|
|
540 float rate;
|
|
541 KeySym key;
|
|
542 char text [255];
|
|
543 int channellist[] = {2, 7, 9, 10, 28};
|
|
544 struct lirc_config *config;
|
|
545 struct pollfd fds[1];
|
7
|
546
|
3
|
547 #define NUMCHANS (sizeof(channellist) / sizeof(channellist[0]))
|
1
|
548
|
8
|
549 channelidx = mute = cursor = 0;
|
1
|
550
|
8
|
551 oldchan = channel = channellist[channelidx];
|
1
|
552
|
|
553 #ifndef USE_XVIMAGES
|
8
|
554 if (!Hermes_Init()) {
|
|
555 printf("Couldn't initialise Hermes!\n");
|
|
556 exit(1);
|
|
557 }
|
|
558 conv = Hermes_ConverterInstance(HERMES_CONVERT_NORMAL);
|
|
559 if (!conv) {
|
|
560 printf("Could not obtain converter instance from Hermes!\n");
|
|
561 exit(1);
|
|
562 }
|
1
|
563 #endif
|
|
564
|
8
|
565 X_Setup(width, height);
|
|
566 XSelectInput(disp, win, KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);
|
1
|
567
|
8
|
568 X_HideCursor();
|
1
|
569
|
8
|
570 /* Open Capture device */
|
|
571 Open();
|
1
|
572
|
8
|
573 /* Open audio mixer */
|
|
574 mixerdev = "/dev/mixer";
|
|
575 if ((mfd = open(mixerdev, O_RDWR)) == -1)
|
|
576 fprintf(stderr, "Unable to open %s - %s\n", mixerdev, strerror(errno));
|
1
|
577
|
8
|
578 /* Talk to LIRC */
|
|
579 if ((fd = lirc_init(basename(argv[0]), 1)) == -1) {
|
|
580 fprintf(stderr, "Unable to init lirc client library\n");
|
|
581 uselirc = 0;
|
|
582 } else {
|
|
583 if (lirc_readconfig(NULL, &config, NULL) != 0) {
|
|
584 fprintf(stderr, "Unable to parse config file\n");
|
|
585 uselirc = 0;
|
|
586 } else
|
|
587 uselirc = 1;
|
|
588 fds[0].fd = fd;
|
|
589 fds[0].events = POLLRDNORM;
|
|
590 }
|
1
|
591
|
3
|
592 #ifndef USE_XVIMAGES
|
8
|
593 /* Conversion from and to formats */
|
|
594 fmt_source.indexed = 0;
|
|
595 fmt_source.bits = 32;
|
|
596 fmt_source.r = 0xff0000;
|
|
597 fmt_source.g = 0x00ff00;
|
|
598 fmt_source.b = 0x0000ff;
|
|
599 fmt_source.a = 0;
|
3
|
600
|
8
|
601 fmt_dest.indexed = 0;
|
|
602 fmt_dest.bits = bits_per_pixel;
|
|
603 fmt_dest.r = vi->red_mask;
|
|
604 fmt_dest.g = vi->green_mask;
|
|
605 fmt_dest.b = vi->blue_mask;
|
|
606 fmt_dest.a = 0;
|
3
|
607 #else
|
8
|
608 scratch_buf = malloc(width * height);
|
3
|
609 #endif
|
|
610
|
8
|
611 frames = 0;
|
|
612 gettimeofday(&then, NULL);
|
|
613 gettimeofday(&lastmove, NULL);
|
3
|
614
|
8
|
615 exitnow = 0;
|
3
|
616
|
8
|
617 /* Capture loop */
|
|
618 while (1) {
|
|
619 if (frames == 50) {
|
|
620 gettimeofday(&now, NULL);
|
|
621 timersub(&now, &then, &diff);
|
3
|
622
|
8
|
623 rate = (float)frames / (float)(diff.tv_usec / 1000000.0 + diff.tv_sec);
|
3
|
624
|
8
|
625 printf("%d frames in %.2f seconds, rate %.2f\n", frames, (float)(diff.tv_usec / 1000000.0) + diff.tv_sec, rate);
|
|
626 gettimeofday(&then, NULL);
|
|
627 frames = 0;
|
|
628 XResetScreenSaver(disp);
|
|
629 }
|
|
630 frames++;
|
1
|
631
|
8
|
632 timersub(&now, &lastmove, &diff);
|
|
633 if (((float)diff.tv_usec / 1000000.0 + (float)diff.tv_sec) > 2.0) {
|
|
634 if (cursor) {
|
|
635 X_HideCursor();
|
|
636 cursor = 0;
|
|
637 }
|
|
638 } else {
|
|
639 if (!cursor) {
|
|
640 X_ShowCursor();
|
|
641 cursor = 1;
|
|
642 }
|
|
643 }
|
3
|
644
|
8
|
645 if (XCheckMaskEvent(disp, PointerMotionMask, &e) && e.type == MotionNotify) {
|
|
646 gettimeofday(&lastmove, NULL);
|
|
647 }
|
|
648 cmd = CMD_NONE;
|
1
|
649
|
8
|
650 if (XCheckMaskEvent(disp, ButtonReleaseMask, &e)) {
|
|
651 printf("e.type = %d\n", e.type);
|
|
652 cmd = CMD_MUTE;
|
|
653 }
|
|
654 if (XCheckMaskEvent(disp, KeyReleaseMask, &e) && e.type == KeyRelease) {
|
|
655 gettimeofday(&lastmove, NULL);
|
3
|
656
|
8
|
657 XLookupString(&e.xkey, text, 255, &key, 0);
|
|
658 printf("Press - %c\n", text[0]);
|
1
|
659
|
8
|
660 switch (text[0]) {
|
|
661 case 'q':
|
|
662 cmd = CMD_QUIT;
|
|
663 break;
|
3
|
664
|
8
|
665 case '=':
|
|
666 case '+':
|
|
667 cmd = CMD_CHNUP;
|
|
668 break;
|
3
|
669
|
8
|
670 case '-':
|
|
671 cmd = CMD_CHNDN;
|
|
672 break;
|
3
|
673
|
8
|
674 case 'h':
|
|
675 cmd = CMD_CURSOR;
|
|
676 break;
|
3
|
677
|
8
|
678 case 'm':
|
|
679 cmd = CMD_MUTE;
|
|
680 break;
|
3
|
681
|
8
|
682 case 'r':
|
|
683 cmd = CMD_RELOAD;
|
|
684 break;
|
3
|
685
|
8
|
686 case ',':
|
|
687 cmd = CMD_VOLDN;
|
|
688 break;
|
3
|
689
|
8
|
690 case '.':
|
|
691 cmd = CMD_VOLUP;
|
|
692 break;
|
3
|
693
|
8
|
694 case 'f':
|
|
695 cmd = CMD_FSTOGGLE;
|
|
696 break;
|
|
697 }
|
|
698 }
|
7
|
699
|
8
|
700 /* Poll for IR events */
|
|
701 if (uselirc) {
|
|
702 fds[0].revents = 0;
|
3
|
703
|
8
|
704 while (1) {
|
7
|
705
|
8
|
706 if (poll(fds, 1, 0) == -1) {
|
|
707 fprintf(stderr, "Poll failed - %s\n", strerror(errno));
|
|
708 exit(EXIT_FAILURE);
|
|
709 }
|
|
710 if ((fds[0].revents & POLLRDNORM) != 0) {
|
|
711 fprintf(stderr, "Processing IR.. \n", lircfails);
|
3
|
712
|
8
|
713 /* Try and get an event */
|
|
714 if (lirc_nextcode(&code) == 0) {
|
|
715 if (code == NULL) {
|
|
716 continue;
|
|
717 }
|
7
|
718
|
8
|
719 /* Translate it (via the config file) */
|
|
720 while ((ret = lirc_code2char(config, code, &c)) == 0 &&
|
|
721 c != NULL) {
|
|
722 fprintf(stderr, "Got command \"%s\"\n", c);
|
1
|
723
|
8
|
724 if (!strcmp(c, "Mute"))
|
|
725 cmd = CMD_MUTE;
|
|
726 else if (!strcmp(c, "CH_UP"))
|
|
727 cmd = CMD_CHNUP;
|
|
728 else if (!strcmp(c, "CH_DOWN"))
|
|
729 cmd = CMD_CHNDN;
|
|
730 else if (!strcmp(c, "Power"))
|
|
731 cmd = CMD_QUIT;
|
|
732 else if (!strcmp(c, "VOL_UP"))
|
|
733 cmd = CMD_VOLUP;
|
|
734 else if (!strcmp(c, "VOL_DOWN"))
|
|
735 cmd = CMD_VOLDN;
|
|
736 else if (!strcmp(c, "AV/TV"))
|
|
737 cmd = CMD_FSTOGGLE;
|
7
|
738
|
8
|
739 free(code);
|
3
|
740 }
|
8
|
741 } else {
|
|
742 /* lircd probably died */
|
|
743 uselirc = 0;
|
|
744 fprintf(stderr, "Failed to communicate with LIRC - daemon exited?");
|
|
745 }
|
|
746 } else
|
|
747 break;
|
|
748 }
|
|
749 }
|
|
750 switch (cmd) {
|
|
751 case CMD_QUIT:
|
|
752 exitnow = 1;
|
|
753 break;
|
|
754
|
|
755 case CMD_CHNUP:
|
|
756 case CMD_CHNDN:
|
|
757 if (cmd == CMD_CHNUP)
|
|
758 channelidx++;
|
|
759 else
|
|
760 channelidx--;
|
|
761
|
|
762 if (channelidx < 0)
|
|
763 channelidx = NUMCHANS - 1;
|
|
764 if (channelidx > NUMCHANS - 1)
|
|
765 channelidx = 0;
|
|
766
|
|
767 channel = channellist[channelidx];
|
|
768
|
|
769 printf("Channel - %d\n", channel);
|
|
770 if (oldchan != channel) {
|
|
771 oldchan = channel;
|
|
772
|
|
773 if (ioctl(tuner_fd, TVTUNER_SETCHNL, &channel) < 0) {
|
|
774 DO_IOCTL_SERR("TVTUNER_SETCHNL", channel);
|
|
775 exit(1);
|
1
|
776 }
|
8
|
777 }
|
|
778 break;
|
3
|
779
|
8
|
780 case CMD_MUTE:
|
|
781 if (mute)
|
|
782 mute = 0;
|
|
783 else
|
|
784 mute = 1;
|
3
|
785
|
8
|
786 printf("Mute - %d\n", mute);
|
|
787 if (ioctl(tuner_fd, BT848_SAUDIO, &mute) < 0) {
|
|
788 DO_IOCTL_SERR("BT848_SAUDIO", mute);
|
|
789 exit(1);
|
|
790 }
|
|
791 break;
|
1
|
792
|
8
|
793 case CMD_CURSOR:
|
|
794 if (cursor) {
|
|
795 printf("Cursor hidden\n");
|
|
796 X_HideCursor();
|
|
797 cursor = 0;
|
|
798 } else {
|
|
799 printf("Cursor revealed\n");
|
|
800 X_ShowCursor();
|
|
801 cursor = 1;
|
|
802 }
|
|
803 break;
|
3
|
804
|
8
|
805 case CMD_RELOAD:
|
|
806 printf("Reloading\n");
|
|
807 Close();
|
|
808 Open();
|
|
809 break;
|
3
|
810
|
8
|
811 case CMD_VOLUP:
|
|
812 case CMD_VOLDN:
|
|
813 if (ioctl(mfd, MIXER_READ(SOUND_MIXER_VOLUME), &curvol) == -1) {
|
|
814 fprintf(stderr, "Unable to read current volume - %s\n", strerror(errno));
|
|
815 break;
|
|
816 }
|
|
817 curvol = curvol & 0x7f;
|
3
|
818
|
8
|
819 if (cmd == CMD_VOLUP)
|
|
820 curvol += 5;
|
|
821 else
|
|
822 curvol -= 5;
|
3
|
823
|
8
|
824 if (curvol < 0)
|
|
825 curvol = 0;
|
|
826 if (curvol > 100)
|
|
827 curvol = 100;
|
3
|
828
|
8
|
829 printf("Setting volume to %d\n", curvol);
|
|
830 curvol |= curvol << 8;
|
1
|
831
|
8
|
832 if (ioctl(mfd, MIXER_WRITE(SOUND_MIXER_VOLUME), &curvol) == -1) {
|
|
833 fprintf(stderr, "Unable to write volume - %s\n", strerror(errno));
|
|
834 break;
|
|
835 }
|
|
836 break;
|
3
|
837
|
8
|
838 case CMD_FSTOGGLE:
|
|
839 vo_x11_ewmh_fullscreen(_NET_WM_STATE_TOGGLE);
|
|
840 break;
|
3
|
841
|
8
|
842 }
|
3
|
843
|
8
|
844 Capture();
|
1
|
845
|
3
|
846 #ifdef USE_XVIMAGES
|
8
|
847 /* bktr's YUV_12 is planar W*H bytes Y, W/2*H/2 bytes U, */
|
|
848 /* W/2*H/2 bytes V. Xv's YV12 is the same with U and V */
|
|
849 /* planes reversed. */
|
|
850 {
|
|
851 int y_off , u_off, v_off;
|
|
852 y_off = 0;
|
|
853 u_off = width * height;
|
|
854 v_off = u_off + width * height / 4;
|
3
|
855
|
8
|
856 assert(yuv_image->data_size == width * height * 3 / 2);
|
|
857 memcpy(yuv_image->data, bktr_buffer, u_off - y_off);
|
|
858 memcpy(yuv_image->data + u_off, bktr_buffer + v_off, v_off - u_off);
|
|
859 memcpy(yuv_image->data + v_off, bktr_buffer + u_off, v_off - u_off);
|
|
860 }
|
3
|
861 #else
|
8
|
862 /* SaveImage(); */
|
3
|
863
|
8
|
864 Hermes_ConverterRequest(conv, &fmt_source, &fmt_dest);
|
|
865 Hermes_ConverterCopy(conv, bktr_buffer, 0, 0, width, height, width * 4,
|
|
866 rgb_image->data, 0, 0, width, height,
|
|
867 rgb_image->bytes_per_line);
|
3
|
868 #endif
|
|
869
|
8
|
870 X_Display();
|
|
871 if (exitnow) {
|
|
872 printf("quitting\n");
|
|
873 break;
|
1
|
874 }
|
8
|
875 }
|
1
|
876
|
3
|
877 #ifndef USE_XVIMAGES
|
8
|
878 Hermes_ConverterReturn(conv);
|
|
879 Hermes_Done();
|
1
|
880 #endif
|
|
881
|
8
|
882 X_Shutdown();
|
|
883 lirc_freeconfig(config);
|
|
884 lirc_deinit();
|
1
|
885
|
8
|
886 return 0;
|
1
|
887 }
|