ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/mintv/mintv.cpp
Revision: 1.4
Committed: 2003-03-07T18:17:09Z (21 years, 8 months ago) by cebix
Branch: MAIN
Changes since 1.3: +16 -8 lines
Log Message:
display width can be specified with the -w switch

File Contents

# Content
1 /*
2 * mintv.cpp - 50/60Hz video display using v4l and XVideo
3 *
4 * Written in 2003 by Christian Bauer
5 */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <getopt.h>
10 #include <errno.h>
11
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <sys/time.h>
15 #include <sys/ioctl.h>
16 #include <sys/mman.h>
17 #include <sys/shm.h>
18 #include <linux/videodev.h>
19
20 #include <X11/Xlib.h>
21 #include <X11/Xutil.h>
22 #include <X11/StringDefs.h>
23 #include <X11/Intrinsic.h>
24 #include <X11/Shell.h>
25 #include <X11/Xaw/Simple.h>
26 #include <X11/extensions/XShm.h>
27 #include <X11/extensions/Xv.h>
28 #include <X11/extensions/Xvlib.h>
29
30 static bool ntsc = false;
31
32 const int PAL_WIDTH = 384;
33 const int PAL_HEIGHT = 288;
34 const int NTSC_WIDTH = 360;
35 const int NTSC_HEIGHT = 240;
36
37 static int grab_width = -1, grab_height;
38 static int image_width, image_height;
39 static int win_width, win_height;
40
41 const int PAL_FPS = 50;
42 const int NTSC_FPS = 60;
43
44 static int fps;
45
46 static int brightness = 50, contrast = 50, color = 50;
47
48 static int channel = 1;
49
50 #define XV_FORMAT 0x32595559
51 #define BYTES_PER_PIXEL 2
52
53 #define DOUBLE_FRAME 1
54 #define LOGGING 0
55
56 #if LOGGING
57 static FILE *log;
58 #endif
59
60 static int port = -1;
61 static Display *dpy;
62 static GC gc;
63 static Atom wm;
64 static Widget app_shell, video;
65 static XShmSegmentInfo shminfo;
66 static XvImage *image[2];
67 static int fd = -1;
68
69 static void quit(Widget widget, XEvent *event, String *params, Cardinal *num_params)
70 {
71 XShmDetach(dpy, &shminfo);
72 shmdt(shminfo.shmaddr);
73
74 if (fd >= 0) {
75 video_audio au;
76 au.audio = 1;
77 ioctl(fd, VIDIOCGAUDIO, &au);
78 au.flags |= VIDEO_AUDIO_MUTE;
79 ioctl(fd, VIDIOCSAUDIO, &au);
80 close(fd);
81 }
82
83 exit(0);
84 }
85
86 static void set_bcc(void)
87 {
88 video_picture pict;
89 if (ioctl(fd, VIDIOCGPICT, &pict) < 0) {
90 fprintf(stderr, "ioctl VIDIOCGPICT: %s\n", strerror(errno));
91 exit(1);
92 }
93 pict.brightness = brightness * 65536 / 100;
94 pict.contrast = contrast * 65536 / 100;
95 pict.colour = color * 65536 / 100;
96 if (ioctl(fd, VIDIOCSPICT, &pict) < 0) {
97 fprintf(stderr, "ioctl VIDIOCSPICT: %s\n", strerror(errno));
98 exit(1);
99 }
100 }
101
102 static void inc_brightness(Widget widget, XEvent *event, String *params, Cardinal *num_params)
103 {
104 if (brightness < 100)
105 brightness++;
106 set_bcc();
107 }
108
109 static void dec_brightness(Widget widget, XEvent *event, String *params, Cardinal *num_params)
110 {
111 if (brightness > 0)
112 brightness--;
113 set_bcc();
114 }
115
116 static void reset_brightness(Widget widget, XEvent *event, String *params, Cardinal *num_params)
117 {
118 brightness = 50;
119 set_bcc();
120 }
121
122 static void inc_contrast(Widget widget, XEvent *event, String *params, Cardinal *num_params)
123 {
124 if (contrast < 100)
125 contrast++;
126 set_bcc();
127 }
128
129 static void dec_contrast(Widget widget, XEvent *event, String *params, Cardinal *num_params)
130 {
131 if (contrast > 0)
132 contrast--;
133 set_bcc();
134 }
135
136 static void reset_contrast(Widget widget, XEvent *event, String *params, Cardinal *num_params)
137 {
138 contrast = 50;
139 set_bcc();
140 }
141
142 static void inc_color(Widget widget, XEvent *event, String *params, Cardinal *num_params)
143 {
144 if (color < 100)
145 color++;
146 set_bcc();
147 }
148
149 static void dec_color(Widget widget, XEvent *event, String *params, Cardinal *num_params)
150 {
151 if (color > 0)
152 color--;
153 set_bcc();
154 }
155
156 static void reset_color(Widget widget, XEvent *event, String *params, Cardinal *num_params)
157 {
158 color = 50;
159 set_bcc();
160 }
161
162 static void set_channel(void)
163 {
164 video_channel chan;
165 chan.channel = channel;
166 if (ioctl(fd, VIDIOCGCHAN, &chan) < 0) {
167 fprintf(stderr, "ioctl VIDIOCGCHAN: %s\n", strerror(errno));
168 exit(1);
169 }
170 if (ntsc)
171 chan.norm = VIDEO_MODE_NTSC;
172 else
173 chan.norm = VIDEO_MODE_PAL;
174 if (ioctl(fd, VIDIOCSCHAN, &chan) < 0) {
175 fprintf(stderr, "ioctl VIDIOCSCHAN: %s\n", strerror(errno));
176 exit(1);
177 }
178 }
179
180 static void select_channel_1(Widget widget, XEvent *event, String *params, Cardinal *num_params)
181 {
182 channel = 1;
183 set_channel();
184 }
185
186 static void select_channel_2(Widget widget, XEvent *event, String *params, Cardinal *num_params)
187 {
188 channel = 2;
189 set_channel();
190 }
191
192 static XtActionsRec actionTable[] = {
193 {"quit", quit},
194 {"inc_brightness", inc_brightness},
195 {"dec_brightness", dec_brightness},
196 {"reset_brightness", reset_brightness},
197 {"inc_contrast", inc_contrast},
198 {"dec_contrast", dec_contrast},
199 {"reset_contrast", reset_contrast},
200 {"inc_color", inc_color},
201 {"dec_color", dec_color},
202 {"reset_color", reset_color},
203 {"select_channel_1", select_channel_1},
204 {"select_channel_2", select_channel_2}
205 };
206
207 static void resize_event(Widget widget, XtPointer client_data, XEvent *event, Boolean *d)
208 {
209 if (event->type == ConfigureNotify) {
210 win_width = event->xconfigure.width;
211 win_height = event->xconfigure.height;
212 }
213 }
214
215 static video_mbuf mbuf;
216 static void *buf = NULL;
217 static video_mmap mm[2];
218 static int bufnum = 0;
219
220 typedef long long int64;
221 typedef unsigned long long uint64;
222
223 uint64 GetTicks_usec(void)
224 {
225 struct timeval t;
226 gettimeofday(&t, NULL);
227 return (uint64)t.tv_sec * 1000000 + t.tv_usec;
228 }
229
230 void Delay_usec(unsigned usec)
231 {
232 int was_error;
233 struct timeval tv;
234
235 /* Set the timeout interval - Linux only needs to do this once */
236 tv.tv_sec = 0;
237 tv.tv_usec = usec;
238 do {
239 errno = 0;
240 was_error = select(0, NULL, NULL, NULL, &tv);
241 } while (was_error && (errno == EINTR));
242 }
243
244 uint64 frame_time = 0;
245
246 static Boolean work_proc(XtPointer client_data)
247 {
248 ioctl(fd, VIDIOCMCAPTURE, &(mm[bufnum ^ 1]));
249 ioctl(fd, VIDIOCSYNC, &bufnum);
250
251 uint64 now = GetTicks_usec();
252 #if LOGGING
253 fprintf(log, "frame_time: %Ld\n", now - frame_time);
254 #endif
255 frame_time = now;
256
257 #if DOUBLE_FRAME
258 char *src = (char *)buf + mbuf.offsets[bufnum];
259 char *dst = image[0]->data;
260 for (unsigned y=0; y<grab_height; y+=2) {
261 memcpy(dst, src, grab_width * BYTES_PER_PIXEL);
262 src += grab_width * BYTES_PER_PIXEL * 2;
263 dst += grab_width * BYTES_PER_PIXEL;
264 }
265
266 uint64 prev = GetTicks_usec();
267
268 XvShmPutImage(
269 dpy, port, XtWindow(video), gc, image[0],
270 0, 0, image_width, image_height,
271 0, 0, win_width, win_height,
272 False
273 );
274
275 src = (char *)buf + mbuf.offsets[bufnum] + grab_width * BYTES_PER_PIXEL;
276 dst = image[1]->data;
277 for (unsigned y=0; y<grab_height; y+=2) {
278 memcpy(dst, src, grab_width * BYTES_PER_PIXEL);
279 src += grab_width * BYTES_PER_PIXEL * 2;
280 dst += grab_width * BYTES_PER_PIXEL;
281 }
282
283 XSync(dpy, False);
284
285 now = GetTicks_usec();
286 uint64 elapsed = now - prev;
287 int64 delay = 1000000 / fps - (now - prev);
288 #if LOGGING
289 fprintf(log, "elapsed %Ld usec, delay %Ld usec\n", elapsed, delay);
290 #endif
291 if (delay > 0)
292 Delay_usec(delay);
293
294 XvShmPutImage(
295 dpy, port, XtWindow(video), gc, image[1],
296 0, 0, image_width, image_height,
297 0, 0, win_width, win_height,
298 False
299 );
300 XSync(dpy, False);
301 #else
302 memcpy(image[0]->data, (char *)buf + mbuf.offsets[bufnum], image[0]->data_size);
303
304 XvShmPutImage(
305 dpy, port, XtWindow(video), gc, image[0],
306 0, 0, image_width, image_height,
307 0, 0, win_width, win_height,
308 False
309 );
310 XSync(dpy, False);
311 #endif
312
313 bufnum ^= 1;
314
315 return False;
316 }
317
318 int main(int argc, char **argv)
319 {
320 static struct option long_opts[] = {
321 {"help", 0, 0, 'h'},
322 {"ntsc", 0, 0, 'n'},
323 {"port", 1, 0, 'p'},
324 {"width", 1, 0, 'w'},
325 {NULL, 0, 0, 0}
326 };
327
328 #if LOGGING
329 log = fopen("log", "w");
330 #endif
331
332 // Parse options
333 for (;;) {
334 int c;
335 if ((c = getopt_long(argc, argv, "hnp:w:", long_opts,NULL)) == -1)
336 break;
337
338 switch (c) {
339 case 0: /* Long option */
340 break;
341 case 'n':
342 ntsc = true;
343 break;
344 case 'p':
345 port = atoi(optarg);
346 break;
347 case 'w':
348 grab_width = atoi(optarg);
349 break;
350 case 'h':
351 default:
352 fprintf(stderr,
353 "50/60Hz video display application\n\n"
354 "Options:\n"
355 " -h | --help this text\n"
356 " -n | --ntsc NTSC mode\n"
357 " -p | --port n Xv output port\n"
358 " -w | --width n image width\n\n"
359 "Keyboard commands:\n"
360 " q quit\n"
361 " 1/2 select channel\n"
362 " KP 7/4/1 adjust brightness\n"
363 " KP 8/5/2 adjust contrast\n"
364 " KP 9/6/3 adjust color\n"
365 );
366 exit(1);
367 break;
368 }
369 }
370
371 // Init X11
372 XtAppContext app_context;
373 app_shell = XtAppInitialize(&app_context, "mintv", NULL, 0, &argc, argv, NULL, NULL, 0);
374 dpy = XtDisplay(app_shell);
375 XtAppAddActions(app_context,actionTable, sizeof(actionTable) / sizeof(XtActionsRec));
376 XtOverrideTranslations(app_shell, XtParseTranslationTable(
377 "<Message>WM_PROTOCOLS: quit()\n"
378 "<Key>q: quit()\n"
379 "<Key>Escape: quit()\n"
380 "<Key>1: select_channel_1()\n"
381 "<Key>2: select_channel_2()\n"
382 "<Key>KP_7: inc_brightness()\n"
383 "<Key>KP_4: reset_brightness()\n"
384 "<Key>KP_1: dec_brightness()\n"
385 "<Key>KP_8: inc_contrast()\n"
386 "<Key>KP_5: reset_contrast()\n"
387 "<Key>KP_2: dec_contrast()\n"
388 "<Key>KP_9: inc_color()\n"
389 "<Key>KP_6: reset_color()\n"
390 "<Key>KP_3: dec_color()"
391 ));
392 XtAddEventHandler(app_shell, StructureNotifyMask, True, resize_event, NULL);
393 wm = XInternAtom(XtDisplay(app_shell), "WM_DELETE_WINDOW", False);
394
395 // Xvideo available?
396 unsigned ver, rel, req, ev, err, val;
397 if (XvQueryExtension(dpy, &ver, &rel, &req, &ev, &err) != Success) {
398 fprintf(stderr, "Server doesn't support Xvideo\n");
399 exit(1);
400 }
401
402 // Yes, query available adaptors
403 unsigned num_adaptors;
404 XvAdaptorInfo *ai;
405 if (XvQueryAdaptors(dpy, DefaultRootWindow(dpy), &num_adaptors, &ai) != Success) {
406 fprintf(stderr, "XvQueryAdaptors failed\n");
407 exit(1);
408 }
409
410 // Find usable port
411 if (port < 0) {
412 for (unsigned i=0; i<num_adaptors; i++) {
413 if (ai[i].type & XvImageMask) {
414 for (unsigned p=ai[i].base_id; p<ai[i].base_id+ai[i].num_ports; p++) {
415 int num_formats;
416 XvImageFormatValues *fo = XvListImageFormats(dpy, p, &num_formats);
417 if (fo) {
418 for (unsigned j=0; j<num_formats; j++) {
419 if (fo[j].id == XV_FORMAT) {
420 port = p;
421 goto port_found;
422 }
423 }
424 XFree(fo);
425 }
426 }
427 }
428 }
429 port_found:
430 ;
431 }
432 if (port < 0) {
433 fprintf(stderr, "No suitable XVideo port found\n");
434 exit(1);
435 } else
436 printf("Using XVideo port %d\n", port);
437
438 // Set grab and window dimensions
439 if (ntsc) {
440 if (grab_width == -1)
441 grab_width = NTSC_WIDTH;
442 grab_height = NTSC_HEIGHT;
443 fps = NTSC_FPS;
444 } else {
445 if (grab_width == -1)
446 grab_width = PAL_WIDTH;
447 grab_height = PAL_HEIGHT;
448 fps = PAL_FPS;
449 }
450 image_width = grab_width;
451 image_height = grab_height;
452 win_width = DisplayWidth(dpy, DefaultScreen(dpy));
453 win_height = DisplayHeight(dpy, DefaultScreen(dpy));
454 #if DOUBLE_FRAME
455 grab_height *= 2;
456 #endif
457
458 // Open window
459 video = XtVaCreateManagedWidget("video", simpleWidgetClass, app_shell,
460 XtNwidth, win_width,
461 XtNheight, win_height,
462 NULL
463 );
464 XtRealizeWidget(app_shell);
465 XtVaSetValues(app_shell,
466 XtNtitle, "mintv",
467 NULL
468 );
469 XSetWMProtocols(XtDisplay(app_shell), XtWindow(app_shell), &wm, 1);
470 gc = XCreateGC(dpy, XtWindow(video), 0, NULL);
471
472 // Set image format
473 unsigned format = XV_FORMAT;
474
475 // Create image
476 image[0] = XvShmCreateImage(dpy, port, format, NULL, image_width, image_height, &shminfo);
477 image[1] = XvShmCreateImage(dpy, port, format, NULL, image_width, image_height, &shminfo);
478 shminfo.shmid = shmget(IPC_PRIVATE, image[0]->data_size * 2, IPC_CREAT | 0777);
479 shminfo.shmaddr = (char *)shmat(shminfo.shmid, NULL, 0);
480 shminfo.readOnly = False;
481 image[0]->data = shminfo.shmaddr;
482 image[1]->data = shminfo.shmaddr + image[0]->data_size;
483 XShmAttach(dpy, &shminfo);
484 XSync(dpy, False);
485 shmctl(shminfo.shmid, IPC_RMID, 0);
486 memset(image[0]->data, 0x80, image[0]->data_size * 2);
487
488 // Open frame grabber device
489 fd = open("/dev/video0", O_RDWR);
490 if (fd < 0) {
491 fprintf(stderr, "Can't open /dev/video0: %s\n", strerror(errno));
492 exit(1);
493 }
494
495 // Set channel
496 set_channel();
497
498 video_audio au;
499 au.audio = 1;
500 if (ioctl(fd, VIDIOCGAUDIO, &au) < 0) {
501 fprintf(stderr, "ioctl VIDIOCGAUDIO: %s\n", strerror(errno));
502 exit(1);
503 }
504 au.flags &= ~VIDEO_AUDIO_MUTE;
505 if (ioctl(fd, VIDIOCSAUDIO, &au) < 0) {
506 fprintf(stderr, "ioctl VIDIOCSAUDIO: %s\n", strerror(errno));
507 exit(1);
508 }
509
510 // Configure frame grabber
511 set_bcc();
512
513 if (ioctl(fd, VIDIOCGMBUF, &mbuf) < 0) {
514 fprintf(stderr, "ioctl VIDIOCGMBUF: %s\n", strerror(errno));
515 exit(1);
516 }
517
518 buf = mmap(NULL, mbuf.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
519 if (buf == NULL) {
520 fprintf(stderr, "mmap failed: %s\n", strerror(errno));
521 exit(1);
522 }
523
524 mm[0].frame = 0;
525 mm[0].width = grab_width;
526 mm[0].height = grab_height;
527 mm[0].format = VIDEO_PALETTE_YUV422;
528 mm[1].frame = 1;
529 mm[1].width = grab_width;
530 mm[1].height = grab_height;
531 mm[1].format = VIDEO_PALETTE_YUV422;
532 ioctl(fd, VIDIOCMCAPTURE, &(mm[bufnum]));
533
534 // Disable screen saver
535 XSetScreenSaver(dpy, 0, 0, DefaultBlanking, DefaultExposures);
536
537 // Main loop
538 XtAppAddWorkProc(app_context, work_proc, NULL);
539 XtAppMainLoop(app_context);
540
541 return 0;
542 }