Mercurial > ~darius > hgwebdir.cgi > simpletv
view simpletv.c @ 6:f0cbbe964629
XShmDetach() both cases.
author | darius |
---|---|
date | Tue, 28 Jun 2005 13:14:03 +0000 |
parents | 8b6b46a1261d |
children | 210d197c77f9 |
line wrap: on
line source
/* Capture and store a 24 bit RGB image as a PPM file */ /* Copyright (c) 2000 Randall Hopper * * Based on a grab.c from Roger Hardiman, which was * based on an original program by Mark Tinguely and Jim Lowe . * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Mark Tinguely and Jim Lowe * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <poll.h> #include <sys/mman.h> #include <sys/fcntl.h> #include <sys/time.h> #include <libgen.h> #include <sys/soundcard.h> #include <dev/bktr/ioctl_bt848.h> #include <dev/bktr/ioctl_meteor.h> #include <machine/param.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/extensions/XShm.h> #include <X11/extensions/Xvlib.h> #ifndef USE_XVIMAGES #include "Hermes/Hermes.h" #endif #include <lirc/lirc_client.h> /* Some BT848/BT878 cards have gain control hardware which takes 1 or 2 seconds to settle. If you get a washed out / too bright image, change the GRABBER_SETTLE_TIME from 0 to either 1 or 2 */ /*#define GRABBER_SETTLE_TIME 10*/ #define GRABBER_SETTLE_TIME 0 #define PAL 1 #define NTSC 2 /* PAL is 768 x 576. NTSC is 640 x 480 */ #define PAL_HEIGHT 576 #define NTSC_HEIGHT 480 int bktr_fd; int tuner_fd; unsigned char *bktr_buffer; #if 1 int width = 320; int height = 240; #else int width = 640; int height = 480; #endif #ifndef USE_XVIMAGES static XImage *rgb_image; static Pixmap pmap; #endif static XShmSegmentInfo shminfo; static Display *disp; static Window win; static Visual *vis; static int scr; static GC gc; static int depth; static Colormap cmap; static XVisualInfo *vi; static int bits_per_pixel; static XvImage *yuv_image; static XvAdaptorInfo *xv_adaptors; static int xv_num_adaptors; static int xv_adaptor; static int xv_format_id; static int channel; #define DO_IOCTL_GERR(str) fprintf(stderr, "ioctl(%s) failed: %s\n", \ str, strerror(errno) ) #define DO_IOCTL_SERR(str,arg) fprintf(stderr, "ioctl(%s, %ld) failed: %s\n",\ str, (long)arg, strerror(errno) ) /*--------------------------------------------------------------------------*/ void Close() { close(tuner_fd); close(bktr_fd); } void Open() { struct meteor_geomet geo; int buffer_size, format, source, c; char *device_name; format = PAL; /* default value */ source = 1; /* default value */ device_name = "/dev/bktr0"; /* default value */ /* Open the Meteor or Bt848/Bt878 grabber */ if ((bktr_fd = open(device_name, O_RDONLY)) < 0) { printf("open failed: %d\n", errno); exit(1); } if ((tuner_fd = open("/dev/tuner0", O_RDONLY)) < 0) { printf("open failed: %d\n", errno); exit(1); } /* set up the capture type and size */ geo.rows = height; geo.columns = width; geo.frames = 1; #ifdef USE_XVIMAGES /* Should be YUV_12, but 422 actually gives a synced picture. Though */ geo.oformat = METEOR_GEO_YUV_422 | METEOR_GEO_YUV_12; #else geo.oformat = METEOR_GEO_RGB24; #endif /* switch from interlaced capture to single field capture if */ /* the grab height is less than half the normal TV height */ /* this gives better quality captures when the object in the TV */ /* picture is moving */ if ((format == PAL) && (height <= (PAL_HEIGHT / 2))) geo.oformat |= METEOR_GEO_ODD_ONLY; if ((format == NTSC) && (height <= (NTSC_HEIGHT / 2))) geo.oformat |= METEOR_GEO_ODD_ONLY; if (ioctl(bktr_fd, METEORSETGEO, &geo) < 0) { printf("METEORSETGEO ioctl failed: %d\n", errno); exit(1); } /* Select PAL or NTSC */ switch (format) { case PAL: c = METEOR_FMT_PAL; break; case NTSC: c = METEOR_FMT_NTSC; break; default: c = METEOR_FMT_NTSC; break; } c = BT848_IFORM_F_PALBDGHI; if (ioctl(bktr_fd, BT848SFMT, &c) < 0) { DO_IOCTL_SERR("BT848SFMT", c); return; } c = AUDIO_TUNER; if (ioctl(tuner_fd, BT848_SAUDIO, &c) < 0) { DO_IOCTL_SERR("BT848SFMT", c); return; } c = CHNLSET_AUSTRALIA; if (ioctl(tuner_fd, TVTUNER_SETTYPE, &c) < 0) { DO_IOCTL_SERR("TVTUNER_SETTYPE", c); return; } if (ioctl(tuner_fd, TVTUNER_SETCHNL, &channel) < 0) { DO_IOCTL_SERR("TVTUNER_SETCHNL", channel); return; } /* Select the Video Source */ /* Video In, Tuner, S-Video */ switch (source) { case 0: c = METEOR_INPUT_DEV0; break; case 1: c = METEOR_INPUT_DEV1; break; case 2: c = METEOR_INPUT_DEV2; break; case 3: c = METEOR_INPUT_DEV3; break; default: c = METEOR_INPUT_DEV0; break; } printf("Input - %x\n", c); if (ioctl(bktr_fd, METEORSINPUT, &c) < 0) { printf("ioctl failed: %d\n", errno); exit(1); } /* Use mmap to Map the drivers grab buffer */ buffer_size = width * height * 4; /* R,G,B,spare */ bktr_buffer = (unsigned char *)mmap((caddr_t) 0, buffer_size, PROT_READ, MAP_SHARED, bktr_fd, (off_t) 0); if (bktr_buffer == (unsigned char *)MAP_FAILED) exit(1); /* We may need to wait for a short time to allow the grabber */ /* brightness to settle down */ sleep(GRABBER_SETTLE_TIME); } /*--------------------------------------------------------------------------*/ void Capture() { int c; /* Perform a single frame capture */ c = METEOR_CAP_SINGLE; ioctl(bktr_fd, METEORCAPTUR, &c); } /*--------------------------------------------------------------------------*/ void SaveImage() { unsigned char *line_buffer; int o , w, h; unsigned char *p; unsigned char header[30]; char *filename = "t.ppm" /* argv[3] */ ; /* Create the output file */ if ((o = open(filename, O_WRONLY | O_CREAT, 0644)) < 0) { printf("ppm open failed: %d\n", errno); exit(1); } /* make PPM header and save to file */ sprintf(header, "P6\n%d\n%d\n255\n", width, height); write(o, header, strlen(header)); /* save the RGB data to PPM file */ /* save this one line at a time */ line_buffer = (unsigned char *)malloc(width * 3 * sizeof(unsigned char)); p = bktr_buffer; for (h = 0; h < height; h++) { for (w = 0; w < width; w++) { line_buffer[(w * 3) + 2] = *p++; /* blue */ line_buffer[(w * 3) + 1] = *p++; /* green */ line_buffer[(w * 3) + 0] = *p++; /* red */ p++; /* NULL byte */ } write(o, line_buffer, width * 3); } close(o); free(line_buffer); } void X_ShowCursor(void) { XDefineCursor(disp, win, 0); } void X_HideCursor(void) { Cursor no_ptr; Pixmap bm_no; XColor black , dummy; Colormap colormap; static unsigned char bm_no_data[] = {0, 0, 0, 0, 0, 0, 0, 0}; colormap = DefaultColormap(disp, DefaultScreen(disp)); XAllocNamedColor(disp, colormap, "black", &black, &dummy); bm_no = XCreateBitmapFromData(disp, win, bm_no_data, 8, 8); no_ptr = XCreatePixmapCursor(disp, bm_no, bm_no, &black, &black, 0, 0); XDefineCursor(disp, win, no_ptr); XFreeCursor(disp, no_ptr); if (bm_no != None) XFreePixmap(disp, bm_no); } /* * Sends the EWMH fullscreen state event. * * action: could be on of _NET_WM_STATE_REMOVE -- remove state * _NET_WM_STATE_ADD -- add state * _NET_WM_STATE_TOGGLE -- toggle */ #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ #define _NET_WM_STATE_ADD 1 /* add/set property */ #define _NET_WM_STATE_TOGGLE 2 /* toggle property */ void vo_x11_ewmh_fullscreen(int action) { XEvent xev; assert(action == _NET_WM_STATE_REMOVE || action == _NET_WM_STATE_ADD || action == _NET_WM_STATE_TOGGLE); /* init X event structure for _NET_WM_FULLSCREEN client msg */ xev.xclient.type = ClientMessage; xev.xclient.serial = 0; xev.xclient.send_event = True; xev.xclient.message_type = XInternAtom(disp, "_NET_WM_STATE", False); xev.xclient.window = win; xev.xclient.format = 32; xev.xclient.data.l[0] = action; xev.xclient.data.l[1] = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", False); xev.xclient.data.l[2] = 0; xev.xclient.data.l[3] = 0; xev.xclient.data.l[4] = 0; /* finally send that damn thing */ if (!XSendEvent(disp, DefaultRootWindow(disp), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev)) { fprintf(stderr, "Failed to send fullscreen command\n"); } } void X_Setup(int w, int h) { XGCValues gcvals; Window root; XVisualInfo vinfo_pref; int num_vis; XPixmapFormatValues *pf; int num_pf , pfi, i, j; XSizeHints sz_hint; disp = XOpenDisplay(NULL); if (!disp) { fprintf(stderr, "X-Error: unable to connect to display\n"); exit(1); } XSynchronize(disp, True); scr = DefaultScreen(disp); vis = DefaultVisual(disp, scr); root = DefaultRootWindow(disp); depth = DefaultDepth(disp, scr); vinfo_pref.screen = scr; vinfo_pref.visualid = XVisualIDFromVisual(vis); vi = XGetVisualInfo(disp, VisualScreenMask | VisualIDMask, &vinfo_pref, &num_vis); assert(num_vis == 1); win = XCreateSimpleWindow(disp, root, 0, 0, w, h, 0, 0, 0); gc = XCreateGC(disp, win, (unsigned long)0, &gcvals); XSetForeground(disp, gc, 0); XSetBackground(disp, gc, 1); XMapWindow(disp, win); cmap = DefaultColormap(disp, scr); sz_hint.flags = PAspect; sz_hint.min_aspect.x = width; sz_hint.min_aspect.y = height; sz_hint.max_aspect.x = width; sz_hint.max_aspect.y = height; /* set min height/width to 4 to avoid off by one errors */ sz_hint.min_width = sz_hint.min_height = 4; sz_hint.flags |= PMinSize; XSetWMNormalHints(disp, win, &sz_hint); XSync(disp, False); /* Setup with Xv extension */ xv_adaptor = -1; xv_format_id = -1; XvQueryAdaptors(disp, root, &xv_num_adaptors, &xv_adaptors); for (i = 0; i < xv_num_adaptors; i++) { XvAdaptorInfo *adaptor = &xv_adaptors[i]; int takes_images; takes_images = adaptor->type & (XvInputMask | XvImageMask); if (takes_images) { XvImageFormatValues *formats; int num_formats; formats = XvListImageFormats(disp, adaptor->base_id, &num_formats); for (j = 0; j < num_formats; j++) if (formats[j].type == XvYUV && formats[j].format == XvPlanar && strcmp(formats[j].guid, "YV12") == 0) break; if (j < num_formats) { xv_adaptor = i; xv_format_id = formats[j].id; break; } } } assert(xv_adaptor >= 0); /* Create an image to captured frames */ #ifdef USE_XVIMAGES yuv_image = XvShmCreateImage(disp, xv_adaptors[xv_adaptor].base_id, xv_format_id, 0, w, h, &shminfo); if (!yuv_image) fprintf(stderr, "X-Error: unable to create XvShm XImage\n"); shminfo.shmid = shmget(IPC_PRIVATE, yuv_image->data_size, IPC_CREAT | 0777); if (shminfo.shmid == -1) fprintf(stderr, "SharedMemory Error: unable to get identifier\n"); shminfo.shmaddr = yuv_image->data = shmat(shminfo.shmid, 0, 0); yuv_image->data = shminfo.shmaddr; #else rgb_image = XShmCreateImage(disp, vis, depth, ZPixmap, NULL, &shminfo, w, h); if (!rgb_image) fprintf(stderr, "X-Error: unable to create XShm XImage\n"); shminfo.shmid = shmget(IPC_PRIVATE, rgb_image->bytes_per_line * rgb_image->height, IPC_CREAT | 0777); if (shminfo.shmid == -1) fprintf(stderr, "SharedMemory Error: unable to get identifier\n"); shminfo.shmaddr = rgb_image->data = shmat(shminfo.shmid, 0, 0); #endif if (!XShmAttach(disp, &shminfo)) fprintf(stderr, "X-Error: unable to attach XShm Shared Memory Segment\n"); /* Create a pixmap for the window background */ #ifdef OLD pmap = XShmCreatePixmap(disp, win, shminfo.shmaddr, &shminfo, w, h, depth); if (!pmap) fprintf(stderr, "Unable to create pixmap\n"); #endif /* Determine bits-per-pixel for pixmaps */ pf = XListPixmapFormats(disp, &num_pf); assert(pf); for (pfi = 0; pfi < num_pf; pfi++) if (pf[pfi].depth == vi->depth) break; assert(pfi < num_pf); bits_per_pixel = pf[pfi].bits_per_pixel; XFree(pf); #ifdef OLD XSetWindowBackgroundPixmap(disp, win, pmap); #endif } /*--------------------------------------------------------------------------*/ void X_Shutdown() { XShmDetach(disp, &shminfo); #ifdef USE_XVIMAGES shmctl(shminfo.shmid, IPC_RMID, 0); shmdt(shminfo.shmaddr); XFree(yuv_image); #else XDestroyImage(rgb_image); #endif shmdt(shminfo.shmaddr); shmctl(shminfo.shmid, IPC_RMID, 0); } /*--------------------------------------------------------------------------*/ void X_Display(void) { int _w , _h, _d; Window _dw; #ifdef USE_XVIMAGES XGetGeometry(disp, win, &_dw, &_d, &_d, &_w, &_h, &_d, &_d); XvShmPutImage(disp, xv_adaptors[xv_adaptor].base_id, win, gc, yuv_image, 0, 0, yuv_image->width, yuv_image->height, 0, 0, _w, _h, True); #else XShmPutImage(disp, win, gc, rgb_image, 0, 0, 0, 0, rgb_image->width, rgb_image->height, False); #endif XSync(disp, False); } #define CMD_NONE 0 #define CMD_CHNUP 1 #define CMD_CHNDN 2 #define CMD_MUTE 3 #define CMD_QUIT 4 #define CMD_RELOAD 5 #define CMD_CURSOR 6 #define CMD_VOLDN 7 #define CMD_VOLUP 8 #define CMD_FSTOGGLE 9 /*--------------------------------------------------------------------------*/ int main(int argc, char *argv[]) { #ifndef USE_XVIMAGES HermesHandle conv; HermesFormat fmt_source, fmt_dest; #endif XEvent e; char *scratch_buf, *code, *c, *mixerdev; int frames , channelidx, oldchan, mute, cursor, fd, ret, cmd; int exitnow , mfd, uselirc, curvol; struct timeval then, now, diff, lastmove; float rate; KeySym key; char text [255]; int channellist[] = {2, 7, 9, 10, 28}; struct lirc_config *config; struct pollfd fds[1]; #define NUMCHANS (sizeof(channellist) / sizeof(channellist[0])) channelidx = mute = cursor = 0; oldchan = channel = channellist[channelidx]; #ifndef USE_XVIMAGES if (!Hermes_Init()) { printf("Couldn't initialise Hermes!\n"); exit(1); } conv = Hermes_ConverterInstance(HERMES_CONVERT_NORMAL); if (!conv) { printf("Could not obtain converter instance from Hermes!\n"); exit(1); } #endif X_Setup(width, height); XSelectInput(disp, win, KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); X_HideCursor(); /* Open Capture device */ Open(); /* Open audio mixer */ mixerdev = "/dev/mixer"; if ((mfd = open(mixerdev, O_RDWR)) == -1) fprintf(stderr, "Unable to open %s - %s\n", mixerdev, strerror(errno)); /* Talk to LIRC */ if ((fd = lirc_init(basename(argv[0]), 1)) == -1) { fprintf(stderr, "Unable to init lirc client library\n"); uselirc = 0; } else { if (lirc_readconfig(NULL, &config, NULL) != 0) { fprintf(stderr, "Unable to parse config file\n"); uselirc = 0; } else uselirc = 1; fds[0].fd = fd; fds[0].events = POLLRDNORM; } #ifndef USE_XVIMAGES /* Conversion from and to formats */ fmt_source.indexed = 0; fmt_source.bits = 32; fmt_source.r = 0xff0000; fmt_source.g = 0x00ff00; fmt_source.b = 0x0000ff; fmt_source.a = 0; fmt_dest.indexed = 0; fmt_dest.bits = bits_per_pixel; fmt_dest.r = vi->red_mask; fmt_dest.g = vi->green_mask; fmt_dest.b = vi->blue_mask; fmt_dest.a = 0; #else scratch_buf = malloc(width * height); #endif frames = 0; gettimeofday(&then, NULL); gettimeofday(&lastmove, NULL); exitnow = 0; /* Capture loop */ while (1) { if (frames == 50) { gettimeofday(&now, NULL); timersub(&now, &then, &diff); rate = (float)frames / (float)(diff.tv_usec / 1000000.0 + diff.tv_sec); printf("%d frames in %.2f seconds, rate %.2f\n", frames, (float)(diff.tv_usec / 1000000.0) + diff.tv_sec, rate); gettimeofday(&then, NULL); frames = 0; XResetScreenSaver(disp); } frames++; timersub(&now, &lastmove, &diff); if (((float)diff.tv_usec / 1000000.0 + (float)diff.tv_sec) > 2.0) { if (cursor) { X_HideCursor(); cursor = 0; } } else { if (!cursor) { X_ShowCursor(); cursor = 1; } } if (XCheckMaskEvent(disp, PointerMotionMask, &e) && e.type == MotionNotify) { gettimeofday(&lastmove, NULL); } cmd = CMD_NONE; if (XCheckMaskEvent(disp, ButtonReleaseMask, &e)) { printf("e.type = %d\n", e.type); cmd = CMD_MUTE; } if (XCheckMaskEvent(disp, KeyReleaseMask, &e) && e.type == KeyRelease) { gettimeofday(&lastmove, NULL); XLookupString(&e.xkey, text, 255, &key, 0); printf("Press - %c\n", text[0]); switch (text[0]) { case 'q': cmd = CMD_QUIT; break; case '=': case '+': cmd = CMD_CHNUP; break; case '-': cmd = CMD_CHNDN; break; case 'h': cmd = CMD_CURSOR; break; case 'm': cmd = CMD_MUTE; break; case 'r': cmd = CMD_RELOAD; break; case ',': cmd = CMD_VOLDN; break; case '.': cmd = CMD_VOLUP; break; case 'f': cmd = CMD_FSTOGGLE; break; } } /* Poll for IR events */ if (uselirc) { fds[0].revents = 0; while (1) { if (poll(fds, 1, 0) == -1) { fprintf(stderr, "Poll failed - %s\n", strerror(errno)); exit(EXIT_FAILURE); } if ((fds[0].revents & POLLRDNORM) != 0) { fprintf(stderr, "Processing IR..\n"); if (lirc_nextcode(&code) == 0) { if (code == NULL) continue; while ((ret = lirc_code2char(config, code, &c)) == 0 && c != NULL) { fprintf(stderr, "Got command \"%s\"\n", c); if (!strcmp(c, "Mute")) cmd = CMD_MUTE; else if (!strcmp(c, "CH_UP")) cmd = CMD_CHNUP; else if (!strcmp(c, "CH_DOWN")) cmd = CMD_CHNDN; else if (!strcmp(c, "Power")) cmd = CMD_QUIT; else if (!strcmp(c, "VOL_UP")) cmd = CMD_VOLUP; else if (!strcmp(c, "VOL_DOWN")) cmd = CMD_VOLDN; else if (!strcmp(c, "AV/TV")) cmd = CMD_FSTOGGLE; free(code); } } } else break; } } switch (cmd) { case CMD_QUIT: exitnow = 1; break; case CMD_CHNUP: case CMD_CHNDN: if (cmd == CMD_CHNUP) channelidx++; else channelidx--; if (channelidx < 0) channelidx = NUMCHANS - 1; if (channelidx > NUMCHANS - 1) channelidx = 0; channel = channellist[channelidx]; printf("Channel - %d\n", channel); if (oldchan != channel) { oldchan = channel; if (ioctl(tuner_fd, TVTUNER_SETCHNL, &channel) < 0) { DO_IOCTL_SERR("TVTUNER_SETCHNL", channel); exit(1); } } break; case CMD_MUTE: if (mute) mute = 0; else mute = 1; printf("Mute - %d\n", mute); if (ioctl(tuner_fd, BT848_SAUDIO, &mute) < 0) { DO_IOCTL_SERR("BT848_SAUDIO", mute); exit(1); } break; case CMD_CURSOR: if (cursor) { printf("Cursor hidden\n"); X_HideCursor(); cursor = 0; } else { printf("Cursor revealed\n"); X_ShowCursor(); cursor = 1; } break; case CMD_RELOAD: printf("Reloading\n"); Close(); Open(); break; case CMD_VOLUP: case CMD_VOLDN: if (ioctl(mfd, MIXER_READ(SOUND_MIXER_VOLUME), &curvol) == -1) { fprintf(stderr, "Unable to read current volume - %s\n", strerror(errno)); break; } curvol = curvol & 0x7f; if (cmd == CMD_VOLUP) curvol += 5; else curvol -= 5; if (curvol < 0) curvol = 0; if (curvol > 100) curvol = 100; printf("Setting volume to %d\n", curvol); curvol |= curvol << 8; if (ioctl(mfd, MIXER_WRITE(SOUND_MIXER_VOLUME), &curvol) == -1) { fprintf(stderr, "Unable to write volume - %s\n", strerror(errno)); break; } break; case CMD_FSTOGGLE: vo_x11_ewmh_fullscreen(_NET_WM_STATE_TOGGLE); break; } Capture(); #ifdef USE_XVIMAGES /* bktr's YUV_12 is planar W*H bytes Y, W/2*H/2 bytes U, */ /* W/2*H/2 bytes V. Xv's YV12 is the same with U and V */ /* planes reversed. */ { int y_off , u_off, v_off; y_off = 0; u_off = width * height; v_off = u_off + width * height / 4; assert(yuv_image->data_size == width * height * 3 / 2); memcpy(yuv_image->data, bktr_buffer, u_off - y_off); memcpy(yuv_image->data + u_off, bktr_buffer + v_off, v_off - u_off); memcpy(yuv_image->data + v_off, bktr_buffer + u_off, v_off - u_off); } #else /* SaveImage(); */ Hermes_ConverterRequest(conv, &fmt_source, &fmt_dest); Hermes_ConverterCopy(conv, bktr_buffer, 0, 0, width, height, width * 4, rgb_image->data, 0, 0, width, height, rgb_image->bytes_per_line); #endif X_Display(); if (exitnow) { printf("quitting\n"); break; } } #ifndef USE_XVIMAGES Hermes_ConverterReturn(conv); Hermes_Done(); #endif X_Shutdown(); lirc_freeconfig(config); lirc_deinit(); return 0; }