ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/mintv/mintv.cpp
Revision: 1.8
Committed: 2005-10-02T11:57:30Z (19 years, 1 month ago) by cebix
Branch: MAIN
CVS Tags: HEAD
Changes since 1.7: +3 -3 lines
Log Message:
stylistic fixes

File Contents

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