summaryrefslogtreecommitdiff
path: root/app/video/video.c
diff options
context:
space:
mode:
authorJacob Meuser <jakemsr@cvs.openbsd.org>2010-07-23 11:33:54 +0000
committerJacob Meuser <jakemsr@cvs.openbsd.org>2010-07-23 11:33:54 +0000
commitcbc89f0ded19cfb299889474a4610ffd3e519627 (patch)
tree164ff33c46e5f5f845f6e7563fbf4de042ca8a5a /app/video/video.c
parent2f63f14b9e8a6e56c2397bf294d99ac8720e78b7 (diff)
import video(1), an application for recording and/or displaying
images from a video(4) device ok matthieu, todd; prodding from deraadt
Diffstat (limited to 'app/video/video.c')
-rw-r--r--app/video/video.c1554
1 files changed, 1554 insertions, 0 deletions
diff --git a/app/video/video.c b/app/video/video.c
new file mode 100644
index 000000000..05a08ca86
--- /dev/null
+++ b/app/video/video.c
@@ -0,0 +1,1554 @@
+/*
+ * Copyright (c) 2010 Jacob Meuser <jakemsr@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/videoio.h>
+#include <sys/time.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xvlib.h>
+#include <X11/Xatom.h>
+
+/* Xv(3) adaptor properties */
+struct xv_adap {
+ char name[128];
+ int nattr;
+ unsigned int adap_index;
+ XvPortID base_port;
+ int nports;
+#define MAX_FMTS 8
+ int fmts[MAX_FMTS];
+ int nfmts;
+ int cur_fmt;
+ int max_width;
+ int max_height;
+};
+
+/* X(7) display properties */
+struct xdsp {
+ Display *dpy;
+ Window window;
+ Window rwin;
+ XEvent event;
+ GC gc;
+ Atom wmdelwin;
+ XvPortID port;
+ XvImage *xv_image;
+#define MAX_ADAPS 8
+ struct xv_adap adaps[MAX_ADAPS];
+ int nadaps;
+ int cur_adap;
+ int width;
+ int height;
+ int saved_x;
+ int saved_y;
+ int saved_w;
+ int saved_h;
+ int max_width;
+ int max_height;
+ int screen_id;
+};
+
+/* video(4) controls */
+struct dev_ctrls {
+ char *name;
+ int supported;
+ int id;
+ int def;
+ int min;
+ int max;
+ int step;
+ int cur;
+} ctrls[] = {
+#define CTRL_BRIGHTNESS 0
+ { "brightness", 0, V4L2_CID_BRIGHTNESS, 0, 0, 0, 0, 0 },
+#define CTRL_CONTRAST 1
+ { "contrast", 0, V4L2_CID_CONTRAST, 0, 0, 0, 0, 0 },
+#define CTRL_SATURATION 2
+ { "saturation", 0, V4L2_CID_SATURATION, 0, 0, 0, 0, 0 },
+#define CTRL_HUE 3
+ { "hue", 0, V4L2_CID_HUE, 0, 0, 0, 0, 0 },
+#define CTRL_GAIN 4
+ { "gain", 0, V4L2_CID_GAIN, 0, 0, 0, 0, 0 },
+#define CTRL_LAST 5
+ { NULL, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+/* frame dimensions */
+struct dim {
+ int w;
+ int h;
+};
+
+/* video(4) device properties */
+struct dev {
+ char path[FILENAME_MAX];
+ int fd;
+#define MAX_DSZS 16
+ struct dim sizes[MAX_DSZS];
+ int nsizes;
+ int buf_type;
+};
+
+/* video encodingss */
+struct encodings {
+ char *name;
+ int bpp;
+ int xv_id;
+ int dev_id;
+#define SW_DEV 0x1
+#define SW_XV 0x2
+#define SW_MASK (SW_DEV | SW_XV)
+ int flags;
+} encs[] = {
+#define ENC_YUY2 0
+ { "yuy2", 16, -1, -1, 0 },
+#define ENC_UYVY 1
+ { "uyvy", 16, -1, -1, 0 },
+#define ENC_LAST 2
+ { NULL, 0, 0, 0, 0 }
+};
+
+struct video {
+ struct xdsp xdsp;
+ struct dev dev;
+ uint8_t *frame_buffer;
+ size_t frame_bufsz;
+ uint8_t *conv_buffer;
+ size_t conv_bufsz;
+ char iofile[FILENAME_MAX];
+ int iofile_fd;
+ char *sz_str;
+#define CONV_SWAP 0x1
+ int conv_type;
+ int enc;
+ int full_screen;
+ int width;
+ int height;
+ int bpf;
+ int fps;
+#define M_IN_DEV 0x1
+#define M_OUT_XV 0x2
+#define M_IN_FILE 0x4
+#define M_OUT_FILE 0x8
+ int mode;
+ int verbose;
+};
+
+int xv_get_info(struct video *);
+int xv_sel_adap(struct video *);
+void xv_dump_info(struct video *);
+int xv_init(struct video *);
+void resize_window(struct video *, int);
+void display_event(struct video *);
+
+int dev_check_caps(struct video *);
+int dev_get_encs(struct video *);
+int dev_get_sizes(struct video *);
+int dev_get_ctrls(struct video *);
+void dev_dump_info(struct video *);
+int dev_init(struct video *);
+void dev_set_ctrl(struct video *, int, int);
+void dev_reset_ctrls(struct video *);
+
+int parse_size(struct video *);
+int choose_size(struct video *);
+int choose_enc(struct video *);
+int setup(struct video *);
+void cleanup(struct video *, int);
+int poll_input(struct video *);
+int grab_frame(struct video *);
+int stream(struct video *);
+
+void got_frame(int);
+void got_shutdown(int);
+int find_enc(char *);
+void usage(void);
+
+volatile sig_atomic_t play, shutdown, hold, wout;
+extern char *__progname;
+
+void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: %s [-v] [-a adaptor] [-e encoding] [-f file]\n"
+ " [-i input] [-o output] [-O output] [-r rate]\n"
+ " [-s size]\n",
+ __progname);
+}
+
+int
+find_enc(char *name)
+{
+ int i;
+
+ for (i = 0; i < ENC_LAST; i++)
+ if (!strcmp(encs[i].name, name))
+ break;
+ return i;
+}
+
+int
+xv_get_info(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ XvImageFormatValues *xvformats;
+ XvAdaptorInfo *ainfo;
+ XvEncodingInfo *xv_encs;
+ struct xv_adap *adap;
+ unsigned int nenc, p;
+ int num_xvformats, nadaps, i, j, ret;
+ char fmtName[5];
+
+ if ((x->dpy = XOpenDisplay(NULL)) == NULL) {
+ warnx("cannot open display %s", XDisplayName(NULL));
+ return 0;
+ }
+ x->rwin = DefaultRootWindow(x->dpy);
+ x->screen_id = DefaultScreen(x->dpy);
+ x->max_width = XDisplayWidth(x->dpy, x->screen_id);
+ x->max_height = XDisplayHeight(x->dpy, x->screen_id);
+
+ ret = XvQueryExtension(x->dpy, &p, &p, &p, &p, &p);
+ if (ret != Success) {
+ warnx("Xv not available");
+ return 0;
+ }
+
+ ret = XvQueryAdaptors(x->dpy, x->rwin, &nadaps, &ainfo);
+ if (ret != Success) {
+ warnx("no Xv adaptors present");
+ return 0;
+ }
+ x->nadaps = 0;
+ for (i = 0; i < nadaps; i++) {
+ if (!(ainfo[i].type & XvInputMask) ||
+ !(ainfo[i].type & XvImageMask)) {
+ continue;
+ }
+ ret = XvQueryEncodings(x->dpy, ainfo[i].base_id,
+ &nenc, &xv_encs);
+ if (ret != Success) {
+ if (vid->verbose > 2)
+ warnx("adaptor %d XvQueryEncodings failed", i);
+ continue;
+ }
+ adap = &x->adaps[x->nadaps];
+ adap->adap_index = i;
+ adap->base_port = ainfo[i].base_id;
+ adap->nports = ainfo[i].num_ports;
+ if (adap->nports == 0) {
+ if (vid->verbose > 2)
+ warnx("adaptor %d xv ports == 0", i);
+ continue;
+ }
+ strlcpy(adap->name, ainfo[i].name, sizeof(adap->name));
+ for (j = 0; j < nenc; j++) {
+ if (!strcmp(xv_encs[j].name, "XV_IMAGE")) {
+ if (xv_encs[j].width > adap->max_width &&
+ xv_encs[j].height > adap->max_height) {
+ adap->max_width = xv_encs[j].width;
+ adap->max_height = xv_encs[j].height;
+ }
+ }
+ }
+ if (xv_encs != NULL)
+ XvFreeEncodingInfo(xv_encs);
+
+ xvformats = XvListImageFormats(x->dpy, ainfo[i].base_id,
+ &num_xvformats);
+ adap->nfmts = 0;
+ for (j = 0; j < num_xvformats; j++) {
+ snprintf(fmtName, sizeof(fmtName), "%c%c%c%c",
+ xvformats[j].id & 0xff,
+ (xvformats[j].id >> 8) & 0xff,
+ (xvformats[j].id >> 16) & 0xff,
+ (xvformats[j].id >> 24) & 0xff);
+ if (!strcmp(fmtName, "YUY2")) {
+ encs[ENC_YUY2].xv_id = xvformats[j].id;
+ adap->fmts[adap->nfmts++] = xvformats[j].id;
+ } else if (!strcmp(fmtName, "UYVY")) {
+ encs[ENC_UYVY].xv_id = xvformats[j].id;
+ adap->fmts[adap->nfmts++] = xvformats[j].id;
+ }
+ if (adap->nfmts >= MAX_FMTS)
+ break;
+ }
+ if (xvformats != NULL)
+ XFree(xvformats);
+ if (adap->nfmts == 0) {
+ if (vid->verbose > 2) {
+ warnx("adaptor %d has no usable encodings",
+ adap->adap_index);
+ }
+ continue;
+ }
+ if (++x->nadaps >= MAX_ADAPS)
+ break;
+ }
+ XvFreeAdaptorInfo(ainfo);
+
+ return 1;
+}
+
+int
+xv_sel_adap(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ struct xv_adap *adap;
+ XvAttribute *attr;
+ Atom atom;
+ int i, j, ret, nattr;
+
+ /* At this point x->cur_adap is the index of the adaptor according
+ * to it's listing by the X server. But after this point, x->cur_adap
+ * will be the index in x->adaps[].
+ */
+ if (x->cur_adap != -1) {
+ for (i = 0; i < x->nadaps; i++) {
+ if (x->adaps[i].adap_index == x->cur_adap)
+ break;
+ }
+ if (i >= x->nadaps) {
+ warnx("Xv adaptor '%d' does not exist", x->cur_adap);
+ return 0;
+ }
+ x->cur_adap = i;
+ adap = &x->adaps[i];
+ for (i = 0; i < adap->nfmts; i++) {
+ if (adap->fmts[i] == encs[vid->enc].xv_id)
+ break;
+ }
+ if (i >= adap->nfmts) {
+ warnx("Xv adaptor '%d' doesn't support %s",
+ x->adaps[x->cur_adap].adap_index,
+ encs[vid->enc].name);
+ return 0;
+ }
+ }
+ for (i = 0; i < x->nadaps && x->cur_adap == -1; i++) {
+ adap = &x->adaps[i];
+ for (j = 0; j < adap->nfmts; j++) {
+ if (adap->fmts[j] == encs[vid->enc].xv_id) {
+ x->cur_adap = i;
+ break;
+ }
+ }
+ }
+ if (x->cur_adap == -1) {
+ warnx("no usable Xv adaptor found");
+ return 0;
+ }
+
+ adap = &x->adaps[x->cur_adap];
+ x->port = adap->base_port;
+ /* adap->nports will always be > 0, see xv_get_info() */
+ while (x->port < adap->base_port + adap->nports &&
+ (ret = XvGrabPort(x->dpy, x->port, CurrentTime)) != Success)
+ x->port++;
+ if (ret != Success) {
+ warnx("adaptor %d could not find usable Xv port",
+ x->adaps[x->cur_adap].adap_index);
+ return 0;
+ }
+
+ atom = None;
+ attr = XvQueryPortAttributes(x->dpy, x->port, &nattr);
+ for (i = 0; attr && i < nattr; i++) {
+ if (!strcmp(attr[i].name, "XV_AUTOPAINT_COLORKEY")) {
+ atom = XInternAtom(x->dpy, "XV_AUTOPAINT_COLORKEY",
+ False);
+ break;
+ } else if (!strcmp(attr[i].name, "XV_AUTOPAINT_COLOURKEY")) {
+ atom = XInternAtom(x->dpy, "XV_AUTOPAINT_COLOURKEY",
+ False);
+ break;
+ }
+ }
+ if (attr)
+ XFree(attr);
+ if (atom != None) {
+ ret = XvSetPortAttribute(x->dpy, x->port, atom, 1);
+ if (ret != Success && vid->verbose > 2)
+ warnx("could not enable autopaint_colorkey");
+ }
+
+ if (x->max_width > adap->max_width)
+ x->max_width = adap->max_width;
+ if (x->max_height > adap->max_height)
+ x->max_height = adap->max_height;
+
+ return 1;
+}
+
+void
+xv_dump_info(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ int i, j;
+
+ fprintf(stderr, "Xv adaptor %d, %s:\n",
+ x->adaps[x->cur_adap].adap_index, x->adaps[x->cur_adap].name);
+
+ fprintf(stderr, " encodings: ");
+ for (i = 0, j = 0; i < ENC_LAST; i++) {
+ if (encs[i].xv_id != -1 && !(encs[i].flags & SW_XV)) {
+ if (j)
+ fprintf(stderr, ", ");
+ fprintf(stderr, "%s", encs[i].name);
+ j++;
+ }
+ }
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, " max size: %dx%d\n", x->max_width, x->max_height);
+}
+
+int
+xv_init(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ XTextProperty WinName;
+ XSizeHints szhints;
+ XWMHints wmhints;
+ char *name;
+
+ x->width = vid->width;
+ x->height = vid->height;
+
+ x->window = XCreateSimpleWindow(x->dpy, x->rwin, 0, 0, x->width,
+ x->height, 0, XWhitePixel(x->dpy, x->screen_id),
+ XBlackPixel(x->dpy, x->screen_id));
+
+ szhints.flags = PSize | PMaxSize | PMinSize;
+ szhints.width = x->width;
+ szhints.height = x->height;
+ szhints.max_width = x->max_width;
+ szhints.max_height = x->max_height;
+ szhints.min_width = 160;
+ szhints.min_height = 120;
+
+ wmhints.flags = InputHint | StateHint;
+ wmhints.input = True;
+ wmhints.initial_state = NormalState;
+
+ name = __progname;
+ XStringListToTextProperty(&name, 1, &WinName);
+ XSetWMProperties(x->dpy, x->window, &WinName, &WinName, NULL, 0,
+ &szhints, &wmhints, NULL);
+
+ XSelectInput(x->dpy, x->window,
+ KeyPressMask | ButtonPressMask | StructureNotifyMask);
+
+ x->wmdelwin = XInternAtom(x->dpy, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(x->dpy, x->window, &x->wmdelwin, 1);
+
+ x->gc = XCreateGC(x->dpy, x->window, 0, NULL);
+
+ XMapRaised(x->dpy, x->window);
+
+ resize_window(vid, 0);
+
+ x->xv_image = XvCreateImage(x->dpy, x->port, encs[vid->enc].xv_id,
+ vid->frame_buffer, vid->width, vid->height);
+
+ return 1;
+}
+
+void
+resize_window(struct video *vid, int fullscreen)
+{
+ struct xdsp *x = &vid->xdsp;
+ XWindowAttributes winatt;
+ Window junk;
+ int new_width;
+ int new_height;
+ int new_x;
+ int new_y;
+
+ if (fullscreen == 1) {
+ if (vid->full_screen == 1) {
+ new_width = x->saved_w;
+ new_height = x->saved_h;
+ new_x = x->saved_x;
+ new_y = x->saved_y;
+ vid->full_screen = 0;
+ } else {
+ new_width = x->max_width;
+ new_height = x->max_height;
+ new_x = 0;
+ new_y = 0;
+ vid->full_screen = 1;
+ }
+ x->width = new_width;
+ x->height = new_height;
+ XMoveResizeWindow(x->dpy, x->window, new_x, new_y,
+ new_width, new_height);
+ } else if (!vid->full_screen) {
+ XGetWindowAttributes(x->dpy, x->window, &winatt);
+ XTranslateCoordinates(x->dpy, x->window, x->rwin,
+ -winatt.x, -winatt.y, &new_x, &new_y, &junk);
+ x->saved_w = x->width = winatt.width;
+ x->saved_h = x->height = winatt.height;
+ x->saved_x = new_x;
+ x->saved_y = new_y;
+ XResizeWindow(x->dpy, x->window, x->width, x->height);
+ }
+ XSync(x->dpy, False);
+ XSync(x->dpy, True);
+}
+
+void
+display_event(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ char str;
+
+ if (XPending(x->dpy)) {
+ XNextEvent(x->dpy, &x->event);
+ switch (x->event.type) {
+ case KeyPress:
+ if (vid->verbose > 2)
+ warnx("got KeyPress event");
+ XLookupString(&x->event.xkey, &str, 1, NULL, NULL);
+ switch (str) {
+ case 'B':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_BRIGHTNESS, 1);
+ break;
+ case 'b':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_BRIGHTNESS, -1);
+ break;
+ case 'C':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_CONTRAST, 1);
+ break;
+ case 'c':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_CONTRAST, -1);
+ break;
+ case 'f':
+ resize_window(vid, 1);
+ break;
+ case 'G':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_GAIN, 1);
+ break;
+ case 'g':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_GAIN, -1);
+ break;
+ case 'H':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_HUE, 1);
+ break;
+ case 'h':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_HUE, -1);
+ break;
+ case 'O':
+ if (!wout && vid->verbose > 0)
+ fprintf(stderr, "starting output\n");
+ wout = 1;
+ break;
+ case 'o':
+ if (wout && vid->verbose > 0)
+ fprintf(stderr, "stopping output\n");
+ wout = 0;
+ break;
+ case 'p':
+ hold = !hold;
+ break;
+ case 'q':
+ shutdown = 1;
+ break;
+ case 'r':
+ if (vid->mode & M_IN_DEV)
+ dev_reset_ctrls(vid);
+ break;
+ case 'S':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_SATURATION, 1);
+ break;
+ case 's':
+ if (vid->mode & M_IN_DEV)
+ dev_set_ctrl(vid, CTRL_SATURATION, -1);
+ break;
+ default:
+ break;
+ }
+ break;
+ case ClientMessage:
+ if (vid->verbose > 2)
+ warnx("got ClientMessage event");
+ if (x->event.xclient.data.l[0] == x->wmdelwin) {
+ shutdown = 1;
+ break;
+ }
+ break;
+ case ConfigureNotify:
+ if (vid->verbose > 2)
+ warnx("got ConfigureNotify event");
+ resize_window(vid, 0);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int
+dev_check_caps(struct video *vid)
+{
+ struct dev *d = &vid->dev;
+ struct v4l2_capability cap;
+
+ if ((d->fd = open(d->path, O_RDWR, 0)) < 0) {
+ warn("%s", d->path);
+ return 0;
+ }
+
+ if (ioctl(d->fd, VIDIOC_QUERYCAP, &cap) < 0) {
+ warn("VIDIOC_QUERYCAP");
+ return 0;
+ }
+ if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
+ warnx("%s is not a capture device", d->path);
+ return 0;
+ }
+ if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
+ warnx("%s does not support read(2)", d->path);
+ return 0;
+ }
+ d->buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ return 1;
+}
+
+int
+dev_get_encs(struct video *vid)
+{
+ struct dev *d = &vid->dev;
+ struct v4l2_fmtdesc fmtdesc;
+ int i;
+
+ fmtdesc.index = 0;
+ fmtdesc.type = d->buf_type;
+ while (ioctl(d->fd, VIDIOC_ENUM_FMT, &fmtdesc) >= 0) {
+ if (!strcmp(fmtdesc.description, "YUYV")) {
+ i = find_enc("yuy2");
+ if (i < ENC_LAST)
+ encs[i].dev_id = fmtdesc.pixelformat;
+ }
+ if (!strcmp(fmtdesc.description, "UYVY")) {
+ i = find_enc("uyvy");
+ if (i < ENC_LAST)
+ encs[i].dev_id = fmtdesc.pixelformat;
+ }
+ fmtdesc.index++;
+ }
+ for (i = 0; encs[i].name; i++) {
+ if (encs[i].dev_id != -1)
+ break;
+ }
+ if (i >= ENC_LAST) {
+ warnx("%s has no usable YUV encodings", d->path);
+ return 0;
+ }
+ if (encs[ENC_YUY2].dev_id == -1 &&
+ encs[ENC_UYVY].dev_id != -1) {
+ encs[ENC_YUY2].dev_id = encs[ENC_UYVY].dev_id;
+ encs[ENC_YUY2].flags |= SW_DEV;
+ }
+ if (encs[ENC_UYVY].dev_id == -1 &&
+ encs[ENC_YUY2].dev_id != -1) {
+ encs[ENC_UYVY].dev_id = encs[ENC_YUY2].dev_id;
+ encs[ENC_UYVY].flags |= SW_DEV;
+ }
+
+ return 1;
+}
+
+int
+dev_get_sizes(struct video *vid)
+{
+ struct dev *d = &vid->dev;
+ struct v4l2_frmsizeenum fsize;
+ struct dim sizes[MAX_DSZS];
+ int i, j, k, nsizes, tmp_w, tmp_h, step_w, step_h;
+
+ nsizes = 0;
+ fsize.index = 0;
+ fsize.pixel_format = encs[vid->enc].dev_id;
+ while (ioctl(d->fd, VIDIOC_ENUM_FRAMESIZES, &fsize) == 0) {
+ switch (fsize.type) {
+ case V4L2_FRMSIZE_TYPE_DISCRETE:
+ sizes[nsizes].w = fsize.discrete.width;
+ sizes[nsizes].h = fsize.discrete.height;
+ nsizes++;
+ break;
+ case V4L2_FRMSIZE_TYPE_CONTINUOUS:
+ step_w = (((fsize.stepwise.max_width -
+ fsize.stepwise.min_width) / MAX_DSZS) + 15) & ~15;
+ step_h = (((fsize.stepwise.max_height -
+ fsize.stepwise.min_height) / MAX_DSZS) + 15) & ~15;
+ for (tmp_w = fsize.stepwise.min_width,
+ tmp_h = fsize.stepwise.min_height;
+ tmp_w <= fsize.stepwise.max_width &&
+ tmp_h <= fsize.stepwise.max_height;
+ tmp_w += step_w, tmp_h += step_h) {
+ sizes[nsizes].w = tmp_w;
+ sizes[nsizes].h = tmp_h;
+ if (++nsizes >= MAX_DSZS)
+ break;
+ }
+ break;
+ case V4L2_FRMSIZE_TYPE_STEPWISE:
+ step_w = (((fsize.stepwise.max_width -
+ fsize.stepwise.min_width) / MAX_DSZS) +
+ fsize.stepwise.step_width - 1) &
+ ~(fsize.stepwise.step_width - 1);
+ step_h = (((fsize.stepwise.max_height -
+ fsize.stepwise.min_height) / MAX_DSZS) +
+ fsize.stepwise.step_height - 1) &
+ ~(fsize.stepwise.step_height - 1);
+ for (tmp_w = fsize.stepwise.min_width,
+ tmp_h = fsize.stepwise.min_height;
+ tmp_w <= fsize.stepwise.max_width &&
+ tmp_h <= fsize.stepwise.max_height;
+ tmp_w += step_w, tmp_h += step_h) {
+ sizes[nsizes].w = tmp_w;
+ sizes[nsizes].h = tmp_h;
+ if (++nsizes >= MAX_DSZS)
+ break;
+ }
+ break;
+ }
+ if (nsizes >= MAX_DSZS)
+ break;
+ fsize.index++;
+ }
+ if (nsizes == 0) {
+ warnx("%s doesn't have any video frame sizes", d->path);
+ return 0;
+ }
+
+ /* insert sort increasing based first on width then height */
+ d->sizes[0].w = sizes[0].w;
+ d->sizes[0].h = sizes[0].h;
+ d->nsizes = 1;
+ for (i = 0; i < nsizes; i++) {
+ for (j = 0; j < d->nsizes; j++) {
+ if (sizes[i].w < d->sizes[j].w)
+ break;
+ if (sizes[i].w == d->sizes[j].h) {
+ if (sizes[i].h < d->sizes[j].h)
+ break;
+ }
+ }
+ if (j < d->nsizes) {
+ for (k = d->nsizes; k > j; k--) {
+ d->sizes[k].w = d->sizes[k - 1].w;
+ d->sizes[k].h = d->sizes[k - 1].h;
+ }
+ }
+ d->sizes[j].w = sizes[i].w;
+ d->sizes[j].h = sizes[i].h;
+ d->nsizes++;
+ }
+
+ return 1;
+}
+
+int
+dev_get_ctrls(struct video *vid)
+{
+ struct dev *d = &vid->dev;
+ struct v4l2_queryctrl qctl;
+ struct v4l2_control control;
+ int i;
+
+ for (i = 0; i < CTRL_LAST; i++) {
+ bzero(&qctl, sizeof(struct v4l2_queryctrl));
+ qctl.id = ctrls[i].id;
+ if (ioctl(d->fd, VIDIOC_QUERYCTRL, &qctl) == -1) {
+ if (errno == EINVAL)
+ continue;
+ warn("VIDIOC_QUERYCTL");
+ return 0;
+ }
+ if (qctl.flags & V4L2_CTRL_FLAG_DISABLED)
+ continue;
+ if (qctl.type == V4L2_CTRL_TYPE_MENU)
+ continue;
+ ctrls[i].def = qctl.default_value;
+ ctrls[i].min = qctl.minimum;
+ ctrls[i].max = qctl.maximum;
+ ctrls[i].step = qctl.step;
+
+ bzero(&qctl, sizeof(struct v4l2_queryctrl));
+ control.id = ctrls[i].id;
+ if (ioctl(d->fd, VIDIOC_G_CTRL, &control) != 0) {
+ warn("VIDIOC_G_CTRL");
+ continue;
+ }
+ ctrls[i].cur = control.value;
+ ctrls[i].supported = 1;
+ }
+
+ return 1;
+}
+
+void
+dev_set_ctrl(struct video *vid, int ctrl, int change)
+{
+ struct dev *d = &vid->dev;
+ struct v4l2_control control;
+ int val;
+
+ if (ctrl < 0 || ctrl >= CTRL_LAST) {
+ warnx("invalid control");
+ return;
+ }
+ if (!ctrls[ctrl].supported) {
+ warnx("control %s not supported by %s",
+ ctrls[ctrl].name, d->path);
+ return;
+ }
+ val = ctrls[ctrl].cur + ctrls[ctrl].step * change;
+ if (val > ctrls[ctrl].max)
+ val = ctrls[ctrl].max;
+ else if (val < ctrls[ctrl].min)
+ val = ctrls[ctrl].min;
+ control.id = ctrls[ctrl].id;
+ control.value = val;
+ if (ioctl(d->fd, VIDIOC_S_CTRL, &control) != 0) {
+ warn("VIDIOC_S_CTRL");
+ return;
+ }
+ control.id = ctrls[ctrl].id;
+ if (ioctl(d->fd, VIDIOC_G_CTRL, &control) != 0) {
+ warn("VIDIOC_G_CTRL");
+ return;
+ }
+ ctrls[ctrl].cur = control.value;
+ if (vid->verbose > 0)
+ fprintf(stderr, "%s now %d\n", ctrls[ctrl].name,
+ ctrls[ctrl].cur);
+}
+
+void
+dev_reset_ctrls(struct video *vid)
+{
+ struct dev *d = &vid->dev;
+ struct v4l2_control control;
+ int i;
+
+ for (i = 0; i < CTRL_LAST; i++) {
+ if (!ctrls[i].supported)
+ continue;
+ control.id = ctrls[i].id;
+ control.value = ctrls[i].def;
+ if (ioctl(d->fd, VIDIOC_S_CTRL, &control) != 0)
+ warn("VIDIOC_S_CTRL(%s)", ctrls[i].name);
+ control.id = ctrls[i].id;
+ if (ioctl(d->fd, VIDIOC_G_CTRL, &control) != 0)
+ warn("VIDIOC_G_CTRL(%s)", ctrls[i].name);
+ ctrls[i].cur = control.value;
+ if (vid->verbose > 0)
+ fprintf(stderr, "%s now %d\n", ctrls[i].name,
+ ctrls[i].cur);
+ }
+}
+
+void
+dev_dump_info(struct video *vid)
+{
+ struct dev *d = &vid->dev;
+ int i, j;
+
+ fprintf(stderr, "video device %s:\n", d->path);
+
+ fprintf(stderr, " encodings: ");
+ for (i = 0, j = 0; i < ENC_LAST; i++) {
+ if (encs[i].dev_id != -1 && !(encs[i].flags & SW_DEV)) {
+ if (j)
+ fprintf(stderr, ", ");
+ fprintf(stderr, "%s", encs[i].name);
+ j++;
+ }
+ }
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, " sizes: ");
+ for (i = 0; i < d->nsizes; i++) {
+ if (i)
+ fprintf(stderr, ", ");
+ fprintf(stderr, "%dx%d", d->sizes[i].w, d->sizes[i].h);
+ }
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, " controls: ");
+ for (i = 0, j = 0; i < CTRL_LAST; i++) {
+ if (ctrls[i].supported) {
+ if (j)
+ fprintf(stderr, ", ");
+ fprintf(stderr, "%s", ctrls[i].name);
+ j++;
+ }
+ }
+ fprintf(stderr, "\n");
+}
+
+int
+dev_init(struct video *vid)
+{
+ struct dev *d = &vid->dev;
+ struct v4l2_format fmt;
+
+ bzero(&fmt, sizeof(struct v4l2_format));
+ fmt.type = d->buf_type;
+ fmt.fmt.pix.width = vid->width;
+ fmt.fmt.pix.height = vid->height;
+ fmt.fmt.pix.pixelformat = encs[vid->enc].dev_id;
+ fmt.fmt.pix.field = V4L2_FIELD_ANY;
+ if (ioctl(d->fd, VIDIOC_S_FMT, &fmt) < 0) {
+ warn("VIDIOC_S_FMT");
+ return 0;
+ }
+ if (fmt.fmt.pix.width != vid->width ||
+ fmt.fmt.pix.height != vid->height) {
+ warnx("%s: returned size not as requested", d->path);
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+parse_size(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ char **dimp, *dims[2];
+ const char *errstr;
+ size_t dimx;
+
+ if (!vid->sz_str) {
+ vid->width = 640;
+ vid->height = 480;
+ return 1;
+ }
+
+ if (!strcmp(vid->sz_str, "full") ||
+ !strcmp(vid->sz_str, "half")) {
+ if (!(vid->mode & M_IN_DEV) || !(vid->mode & M_OUT_XV)) {
+ warnx("size '%s' not valid for this mode", vid->sz_str);
+ return 0;
+ }
+ vid->width = x->max_width * 2 / 3;
+ vid->height = x->max_height * 2 / 3;
+ return 1;
+ }
+
+ dimx = strcspn(vid->sz_str, "x");
+ for (dimp = dims; dimp < &dims[2] &&
+ (*dimp = strsep(&vid->sz_str, "x")) != NULL; ) {
+ if (**dimp != '\0')
+ dimp++;
+ }
+ if (dimx > 0) {
+ if (dims[0] != '\0') {
+ vid->width = strtonum(dims[0], 0, 4096, &errstr);
+ if (errstr != NULL) {
+ warnx("width '%s' is %s", dims[0], errstr);
+ return 0;
+ }
+ }
+ if (dims[1] != '\0') {
+ vid->height = strtonum(dims[1], 0, 4096, &errstr);
+ if (errstr != NULL) {
+ warnx("height '%s' is %s", dims[1], errstr);
+ return 0;
+ }
+ }
+ } else if (dims[0] != '\0') {
+ vid->height = strtonum(dims[0], 0, 4096, &errstr);
+ if (errstr != NULL) {
+ warnx("height '%s' is %s", dims[0], errstr);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int
+choose_size(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ struct dev *d = &vid->dev;
+ int i;
+
+ if (vid->height && !vid->width)
+ vid->width = vid->height * 4 / 3;
+ else if (vid->width && !vid->height)
+ vid->height = vid->width * 3 / 4;
+
+ if (vid->mode & M_OUT_XV) {
+ if (vid->width > x->max_width)
+ vid->width = x->max_width;
+ if (vid->height > x->max_height)
+ vid->height = x->max_height;
+ }
+ if (vid->mode & M_IN_DEV) {
+ i = 0;
+ while (d->sizes[i].h <= vid->height &&
+ d->sizes[i].w <= vid->width)
+ i++;
+ if (i > 0 && (d->sizes[i].h > vid->height ||
+ d->sizes[i].w > vid->width))
+ i--;
+ vid->width = d->sizes[i].w;
+ vid->height = d->sizes[i].h;
+ }
+
+ return 1;
+}
+
+int
+choose_enc(struct video *vid)
+{
+ int i;
+
+ if (vid->enc < 0) {
+ for (i = 0; vid->enc < 0 && i < ENC_LAST; i++) {
+ if ((vid->mode & M_IN_DEV) && (vid->mode & M_OUT_XV)) {
+ if (encs[i].dev_id != -1 &&
+ encs[i].xv_id != -1 &&
+ (encs[i].flags & SW_MASK) == 0)
+ vid->enc = i;
+ } else if (vid->mode & M_IN_DEV) {
+ if (encs[i].dev_id != -1 &&
+ (encs[i].flags & SW_MASK) == 0)
+ vid->enc = i;
+ } else if (vid->mode & M_OUT_XV) {
+ if (encs[i].xv_id != -1 &&
+ (encs[i].flags & SW_MASK) == 0)
+ vid->enc = i;
+ }
+ }
+ for (i = 0; vid->enc < 0 && i < ENC_LAST; i++) {
+ if ((vid->mode & M_IN_DEV) && (vid->mode & M_OUT_XV)) {
+ if (encs[i].dev_id != -1 &&
+ encs[i].xv_id != -1)
+ vid->enc = i;
+ } else if (vid->mode & M_IN_DEV) {
+ if (encs[i].dev_id != -1)
+ vid->enc = i;
+ } else if (vid->mode & M_OUT_XV) {
+ if (encs[i].xv_id != -1)
+ vid->enc = i;
+ }
+ }
+ }
+ if (vid->enc < 0) {
+ warnx("could not find a usable encoding");
+ return 0;
+ }
+ if ((vid->mode & M_IN_DEV) && encs[vid->enc].dev_id == -1) {
+ warnx("device %s can't supply %s", vid->dev.path,
+ encs[vid->enc].name);
+ return 0;
+ }
+ if ((vid->mode & M_OUT_XV) && encs[vid->enc].xv_id == -1) {
+ warnx("Xv adaptor %d can't display %s",
+ vid->xdsp.adaps[vid->xdsp.cur_adap].adap_index,
+ encs[vid->enc].name);
+ return 0;
+ }
+ if (((vid->mode & M_IN_DEV) &&
+ (encs[vid->enc].flags & SW_MASK) == SW_DEV) ||
+ ((vid->mode & M_OUT_XV) &&
+ (encs[vid->enc].flags & SW_MASK) == SW_XV))
+ vid->conv_type = CONV_SWAP;
+
+ return 1;
+}
+
+int
+setup(struct video *vid)
+{
+ if (vid->mode & M_IN_FILE) {
+ if (!strcmp(vid->iofile, "-"))
+ vid->iofile_fd = STDIN_FILENO;
+ else
+ vid->iofile_fd = open(vid->iofile, O_RDONLY, 0);
+ } else if (vid->mode & M_OUT_FILE) {
+ if (!strcmp(vid->iofile, "-"))
+ vid->iofile_fd = STDOUT_FILENO;
+ else
+ vid->iofile_fd = open(vid->iofile,
+ O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ }
+ if (vid->mode & (M_IN_FILE | M_OUT_FILE)) {
+ if (vid->iofile_fd < 0) {
+ warn("%s", vid->iofile);
+ return 0;
+ }
+ }
+
+ if ((vid->mode & M_OUT_XV) && !xv_get_info(vid))
+ return 0;
+
+ if ((vid->mode & M_IN_DEV) &&
+ (!dev_check_caps(vid) || !dev_get_encs(vid)))
+ return 0;
+
+ if (!choose_enc(vid))
+ return 0;
+
+ if ((vid->mode & M_OUT_XV) && !xv_sel_adap(vid))
+ return 0;
+
+ if ((vid->mode & M_IN_DEV) &&
+ (!dev_get_sizes(vid) || !dev_get_ctrls(vid)))
+ return 0;
+
+ if (!parse_size(vid) || !choose_size(vid))
+ return 0;
+
+ vid->bpf = vid->width * vid->height * encs[vid->enc].bpp / NBBY;
+
+ if (vid->verbose > 0) {
+ if (vid->mode & M_IN_DEV)
+ dev_dump_info(vid);
+ if (vid->mode & M_OUT_XV)
+ xv_dump_info(vid);
+ fprintf(stderr, "using %s encoding\n", encs[vid->enc].name);
+ fprintf(stderr, "using frame size %dx%d (%d bytes)\n",
+ vid->width, vid->height, vid->bpf);
+ }
+
+ if ((vid->frame_buffer = calloc(1, vid->bpf)) == NULL) {
+ warn("frame_buffer");
+ return 0;
+ }
+
+ if (vid->conv_type) {
+ if (vid->conv_type == CONV_SWAP) {
+ vid->conv_bufsz = vid->bpf;
+ } else {
+ warnx("invalid conversion type");
+ return 0;
+ }
+ if ((vid->conv_buffer = calloc(1, vid->conv_bufsz)) == NULL) {
+ warn("conv_buffer");
+ return 0;
+ }
+ }
+
+ if ((vid->mode & M_IN_DEV) && !dev_init(vid))
+ return 0;
+
+ if ((vid->mode & M_OUT_XV) && !xv_init(vid))
+ return 0;
+
+ if (vid->sz_str && !strcmp(vid->sz_str, "full"))
+ resize_window(vid, 1);
+
+ return 1;
+}
+
+int
+poll_input(struct video *vid)
+{
+ struct pollfd pfds[1];
+ int ret;
+
+ pfds[0].fd = (vid->mode & M_IN_FILE) ? vid->iofile_fd : vid->dev.fd;
+ pfds[0].events = POLLIN;
+
+ ret = poll(pfds, 1, 0);
+ if (ret != 1)
+ return ret;
+
+ if (pfds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
+ return pfds[0].revents;
+
+ if (!(pfds[0].revents & POLLIN))
+ return 0;
+
+ return 1;
+}
+
+int
+grab_frame(struct video *vid)
+{
+ int fd, todo, done, ret;
+
+ if (vid->mode & M_IN_FILE)
+ fd = vid->iofile_fd;
+ else
+ fd = vid->dev.fd;
+
+ done = 0;
+ todo = vid->bpf;
+ while (todo > 0) {
+ ret = read(fd, vid->frame_buffer + done, todo);
+ if (ret <= 0) {
+ done = ret;
+ break;
+ }
+ if (ret < todo) {
+ /* video(4) doesn't support partial read() */
+ if (vid->mode & M_IN_DEV) {
+ done = 0;
+ break;
+ }
+ }
+ todo -= ret;
+ done += ret;
+ }
+
+ if (done != vid->bpf) {
+ if (done == 0 && (vid->mode & M_IN_FILE)) {
+ if (vid->verbose > 1)
+ warnx("%s: EOF", vid->iofile);
+ return 255;
+ }
+ warn("%s", (vid->mode & M_IN_DEV) ?
+ vid->dev.path : vid->iofile);
+ return 0;
+ }
+
+ return 1;
+}
+
+void
+got_frame(int s)
+{
+ play = 1;
+}
+
+void
+got_shutdown(int s)
+{
+ shutdown = 1;
+}
+
+int
+stream(struct video *vid)
+{
+ struct xdsp *x = &vid->xdsp;
+ struct timeval tp_start, tp_now, tp_run;
+ struct itimerval frit;
+ double run_time;
+ uint8_t *src;
+ long frames_played = -1, frames_grabbed = 0, fus = 50000;
+ int sequence = 20, ret, err, todo, done;
+
+ if (vid->fps)
+ fus = 1000000 / vid->fps;
+
+ if (vid->fps) {
+ timerclear(&frit.it_value);
+ timerclear(&frit.it_interval);
+ if (vid->fps == 1) {
+ frit.it_value.tv_sec = 1;
+ frit.it_interval.tv_sec = 1;
+ } else {
+ frit.it_value.tv_usec = fus;
+ frit.it_interval.tv_usec = fus;
+ }
+ signal(SIGALRM, got_frame);
+ if (setitimer(ITIMER_REAL, &frit, NULL) == -1) {
+ warn("setitimer");
+ return 0;
+ }
+ }
+ signal(SIGHUP, got_shutdown);
+ signal(SIGINT, got_shutdown);
+ signal(SIGKILL, got_shutdown);
+ signal(SIGTERM, got_shutdown);
+ signal(SIGPIPE, got_shutdown);
+
+ while (!shutdown) {
+ err = 0;
+ ret = poll_input(vid);
+ if (ret == 1) {
+ if (!vid->fps)
+ play = 1;
+ if ((vid->mode & M_IN_DEV) ||
+ frames_grabbed - 1 == frames_played) {
+ ret = grab_frame(vid);
+ if (ret == 1)
+ frames_grabbed++;
+ else if (ret == 255)
+ break;
+ else if (ret < 0)
+ err++;
+ }
+ } else if (ret < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ err++;
+ warn("poll");
+ } else if (ret & (POLLERR | POLLHUP | POLLNVAL)) {
+ if (!strcmp(vid->iofile, "-") && (ret & POLLHUP))
+ break;
+ err++;
+ warnx("poll error, revents=0x%x", ret);
+ }
+ if (err)
+ return 0;
+
+ if (vid->mode & M_OUT_XV)
+ display_event(vid);
+ if (shutdown)
+ break;
+ if (frames_grabbed < 1)
+ play = 0;
+ if (hold || !play) {
+ usleep(fus / 5);
+ continue;
+ }
+ play = 0;
+
+ src = vid->frame_buffer;
+ if (vid->conv_type == CONV_SWAP) {
+ swab(src, vid->conv_buffer, vid->bpf);
+ src = vid->conv_buffer;
+ }
+
+ if ((vid->mode & M_OUT_FILE) && wout) {
+ done = 0;
+ todo = vid->bpf;
+ while (todo > 0) {
+ ret = write(vid->iofile_fd, src + done, todo);
+ if (ret == -1) {
+ if (!strcmp(vid->iofile, "-") &&
+ (errno == EPIPE))
+ break;
+ if (errno != EAGAIN && errno != EINTR) {
+ warn("write %s", vid->iofile);
+ return 0;
+ }
+ }
+ done += ret;
+ todo -= ret;
+ }
+ }
+ if (vid->mode & M_OUT_XV) {
+ x->xv_image->data = src;
+ ret = XvPutImage(x->dpy, x->port, x->window, x->gc,
+ x->xv_image, 0, 0, vid->width, vid->height,
+ 0, 0, x->width, x->height);
+ if (ret != Success) {
+ warn("XvPutImage");
+ return 0;
+ }
+ }
+ frames_played++;
+
+ if (frames_played == 0)
+ gettimeofday(&tp_start, NULL);
+
+ if (vid->verbose > 1 && frames_played > 0 &&
+ (frames_played) % sequence == 0) {
+ gettimeofday(&tp_now, NULL);
+ timersub(&tp_now, &tp_start, &tp_run);
+ run_time = tp_run.tv_sec +
+ (double)tp_run.tv_usec / 1000000;
+ fprintf(stderr, "frames: %08ld, seconds: "
+ "%09.2f, fps: %08.5f\r", frames_played,
+ run_time, ((double)frames_played) / run_time);
+ fflush(stderr);
+ }
+ }
+ gettimeofday(&tp_now, NULL);
+
+ if (vid->fps) {
+ timerclear(&frit.it_value);
+ timerclear(&frit.it_interval);
+ if (setitimer(ITIMER_REAL, &frit, NULL) == -1 &&
+ vid->verbose > 0)
+ warn("setitimer");
+ signal(SIGALRM, SIG_DFL);
+ }
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGKILL, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+
+ if (vid->verbose > 1)
+ fprintf(stderr, "\n");
+
+ if (vid->verbose > 0) {
+ timersub(&tp_now, &tp_start, &tp_run);
+ run_time = tp_run.tv_sec + (double)tp_run.tv_usec / 1000000;
+ fprintf(stderr, "run time: %f seconds\n", run_time);
+ fprintf(stderr, "frames grabbed: %ld\n", frames_grabbed);
+ fprintf(stderr, "frames played: %ld\n", frames_played + 1);
+ fprintf(stderr, "played fps: %f\n", ((double)frames_played) / run_time);
+ }
+
+ return 1;
+}
+
+__dead void
+cleanup(struct video *vid, int excode)
+{
+ if (vid->xdsp.xv_image != NULL)
+ XFree(vid->xdsp.xv_image);
+
+ if (vid->xdsp.gc != NULL)
+ XFreeGC(vid->xdsp.dpy, vid->xdsp.gc);
+
+ if (vid->xdsp.window != 0)
+ XDestroyWindow(vid->xdsp.dpy, vid->xdsp.window);
+
+ if (vid->xdsp.port != 0)
+ XvUngrabPort(vid->xdsp.dpy, vid->xdsp.port, CurrentTime);
+
+ if (vid->xdsp.dpy != NULL)
+ XCloseDisplay(vid->xdsp.dpy);
+
+ if (vid->dev.fd >= 0)
+ close(vid->dev.fd);
+
+ if (vid->iofile_fd >= 0)
+ close(vid->iofile_fd);
+
+ if (vid->frame_buffer != NULL)
+ free(vid->frame_buffer);
+
+ if (vid->conv_buffer != NULL)
+ free(vid->conv_buffer);
+
+ if (vid->sz_str != NULL)
+ free(vid->sz_str);
+
+ exit(excode);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct video vid;
+ struct dev *d = &vid.dev;
+ struct xdsp *x = &vid.xdsp;
+ const char *errstr;
+ int ch, err = 0;
+
+ bzero(&vid, sizeof(struct video));
+
+ snprintf(d->path, sizeof(d->path), "/dev/video");
+ x->cur_adap = -1;
+ vid.dev.fd = vid.iofile_fd = -1;
+ vid.mode = M_IN_DEV | M_OUT_XV;
+ wout = 1;
+
+ while ((ch = getopt(argc, argv, "va:e:f:i:O:o:r:s:")) != -1) {
+ switch (ch) {
+ case 'a':
+ x->cur_adap = strtonum(optarg, 0, 4, &errstr);
+ if (errstr != NULL) {
+ warnx("Xv adaptor '%s' is %s", optarg, errstr);
+ err++;
+ }
+ break;
+ case 'e':
+ vid.enc = find_enc(optarg);
+ if (vid.enc >= ENC_LAST) {
+ warnx("encoding '%s' is invalid", optarg);
+ err++;
+ }
+ break;
+ case 'f':
+ snprintf(d->path, sizeof(d->path), optarg);
+ break;
+ case 'i':
+ if (vid.mode & (M_IN_FILE | M_OUT_FILE)) {
+ warnx("only one input or ouput file allowed");
+ err++;
+ } else {
+ vid.mode = (vid.mode & ~M_IN_DEV) | M_IN_FILE;
+ snprintf(vid.iofile, sizeof(vid.iofile),
+ optarg);
+ }
+ break;
+ case 'o':
+ case 'O':
+ if (vid.mode & (M_IN_FILE | M_OUT_FILE)) {
+ warnx("only one input or ouput file allowed");
+ err++;
+ } else {
+ vid.mode |= M_OUT_FILE;
+ if (ch != 'O')
+ vid.mode &= ~M_OUT_XV;
+ snprintf(vid.iofile, sizeof(vid.iofile),
+ optarg);
+ }
+ break;
+ case 'r':
+ vid.fps = strtonum(optarg, 1, 100, &errstr);
+ if (errstr != NULL) {
+ warnx("frame rate '%s' is %s", optarg, errstr);
+ err++;
+ }
+ break;
+ case 's':
+ vid.sz_str = strdup(optarg);
+ break;
+ case 'v':
+ vid.verbose++;
+ break;
+ default:
+ err++;
+ break;
+ }
+ if (err > 0)
+ break;
+ }
+ if (err > 0) {
+ usage();
+ cleanup(&vid, 1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (!setup(&vid))
+ cleanup(&vid, 1);
+
+ if (!stream(&vid))
+ cleanup(&vid, 1);
+
+ cleanup(&vid, 0);
+}