From cbc89f0ded19cfb299889474a4610ffd3e519627 Mon Sep 17 00:00:00 2001 From: Jacob Meuser Date: Fri, 23 Jul 2010 11:33:54 +0000 Subject: import video(1), an application for recording and/or displaying images from a video(4) device ok matthieu, todd; prodding from deraadt --- app/video/video.c | 1554 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1554 insertions(+) create mode 100644 app/video/video.c (limited to 'app/video/video.c') 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 + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* 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); +} -- cgit v1.2.3