summaryrefslogtreecommitdiffstats
path: root/zbar/video/vfw.c
diff options
context:
space:
mode:
Diffstat (limited to 'zbar/video/vfw.c')
-rw-r--r--zbar/video/vfw.c511
1 files changed, 511 insertions, 0 deletions
diff --git a/zbar/video/vfw.c b/zbar/video/vfw.c
new file mode 100644
index 0000000..a9e050d
--- /dev/null
+++ b/zbar/video/vfw.c
@@ -0,0 +1,511 @@
+/*------------------------------------------------------------------------
+ * Copyright 2009 (c) Jeff Brown <spadix@users.sourceforge.net>
+ *
+ * This file is part of the ZBar Bar Code Reader.
+ *
+ * The ZBar Bar Code Reader is free software; you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * The ZBar Bar Code Reader is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser Public License
+ * along with the ZBar Bar Code Reader; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ *
+ * http://sourceforge.net/projects/zbar
+ *------------------------------------------------------------------------*/
+
+#include "thread.h"
+#include "video.h"
+
+#include <vfw.h>
+
+#include <assert.h>
+
+#define MAX_DRIVERS 10
+#define MAX_NAME 128
+
+#define BIH_FMT \
+ "%ldx%ld @%dbpp (%lx) cmp=%.4s(%08lx) res=%ldx%ld clr=%ld/%ld (%lx)"
+#define BIH_FIELDS(bih) \
+ (bih)->biWidth, (bih)->biHeight, (bih)->biBitCount, (bih)->biSizeImage, \
+ (char *)&(bih)->biCompression, (bih)->biCompression, \
+ (bih)->biXPelsPerMeter, (bih)->biYPelsPerMeter, (bih)->biClrImportant, \
+ (bih)->biClrUsed, (bih)->biSize
+
+struct video_state_s {
+ zbar_thread_t thread; /* capture message pump */
+ HANDLE captured;
+ HWND hwnd; /* vfw interface */
+ HANDLE notify; /* capture thread status change */
+ int bi_size; /* size of bih */
+ BITMAPINFOHEADER *bih; /* video format details */
+ zbar_image_t *image; /* currently capturing frame */
+};
+
+static const uint32_t vfw_formats[] = {
+ /* planar YUV formats */
+ fourcc('I', '4', '2', '0'),
+ /* FIXME YU12 is IYUV in windows */
+ fourcc('Y', 'V', '1', '2'),
+ /* FIXME IMC[1-4]? */
+
+ /* planar Y + packed UV plane */
+ fourcc('N', 'V', '1', '2'),
+
+ /* packed YUV formats */
+ fourcc('U', 'Y', 'V', 'Y'), fourcc('Y', 'U', 'Y', '2'), /* FIXME add YVYU */
+ /* FIXME AYUV? Y411? Y41P? */
+
+ /* packed rgb formats */
+ fourcc('B', 'G', 'R', '3'), fourcc('B', 'G', 'R', '4'),
+
+ fourcc('Y', 'V', 'U', '9'),
+
+ /* basic grayscale format */
+ fourcc('G', 'R', 'E', 'Y'), fourcc('Y', '8', '0', '0'),
+
+ /* compressed formats */
+ fourcc('J', 'P', 'E', 'G'),
+
+ /* terminator */
+ 0
+};
+
+#define VFW_NUM_FORMATS (sizeof(vfw_formats) / sizeof(uint32_t))
+
+static ZTHREAD vfw_capture_thread(void *arg)
+{
+ MSG msg;
+ int rc = 0;
+
+ zbar_video_t *vdo = arg;
+ video_state_t *state = vdo->state;
+ zbar_thread_t *thr = &state->thread;
+
+ state->hwnd = capCreateCaptureWindow(NULL, WS_POPUP, 0, 0, 1, 1, NULL, 0);
+ if (!state->hwnd)
+ goto done;
+
+ _zbar_mutex_lock(&vdo->qlock);
+ _zbar_thread_init(thr);
+ zprintf(4, "spawned vfw capture thread (thr=%04lx)\n", _zbar_thread_self());
+
+ while (thr->started && rc >= 0 && rc <= 1) {
+ _zbar_mutex_unlock(&vdo->qlock);
+
+ rc = MsgWaitForMultipleObjects(1, &thr->notify, 0, INFINITE,
+ QS_ALLINPUT);
+ if (rc == 1)
+ while (PeekMessage(&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE))
+ if (rc > 0) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ _zbar_mutex_lock(&vdo->qlock);
+ }
+
+done:
+ thr->running = 0;
+ _zbar_event_trigger(&thr->activity);
+ _zbar_mutex_unlock(&vdo->qlock);
+ return (0);
+}
+
+static LRESULT CALLBACK vfw_stream_cb(HWND hwnd, VIDEOHDR *hdr)
+{
+ zbar_video_t *vdo;
+ zbar_image_t *img;
+
+ if (!hwnd || !hdr)
+ return (0);
+ vdo = (void *)capGetUserData(hwnd);
+
+ _zbar_mutex_lock(&vdo->qlock);
+ img = vdo->state->image;
+ if (!img) {
+ _zbar_mutex_lock(&vdo->qlock);
+ img = video_dq_image(vdo);
+ }
+ if (img) {
+ img->data = hdr->lpData;
+ img->datalen = hdr->dwBufferLength;
+ vdo->state->image = img;
+ SetEvent(vdo->state->captured);
+ }
+ _zbar_mutex_unlock(&vdo->qlock);
+
+ return (1);
+}
+
+static LRESULT CALLBACK vfw_error_cb(HWND hwnd, int errid, const char *errmsg)
+{
+ zbar_video_t *vdo;
+ if (!hwnd)
+ return (0);
+ vdo = (void *)capGetUserData(hwnd);
+ zprintf(2, "id=%d msg=%s\n", errid, errmsg);
+ _zbar_mutex_lock(&vdo->qlock);
+ vdo->state->image = NULL;
+ SetEvent(vdo->state->captured);
+ _zbar_mutex_unlock(&vdo->qlock);
+ return (1);
+}
+
+static int vfw_nq(zbar_video_t *vdo, zbar_image_t *img)
+{
+ img->data = NULL;
+ img->datalen = 0;
+ return (video_nq_image(vdo, img));
+}
+
+/// Platform dependent part of #zbar_video_next_image, which blocks
+/// until an image is available.
+/** Must be called with video lock held and returns
+ * with the lock released.
+ * <p>Waits for the image from `vdo->state->image`. If available,
+ * this field is nulled. Releases the lock temporarily when waiting for
+ * the `vdo->state->captured` signal. */
+static zbar_image_t *vfw_dq(zbar_video_t *vdo)
+{
+ zbar_image_t *img = vdo->state->image;
+ if (!img) {
+ int rc;
+ _zbar_mutex_unlock(&vdo->qlock);
+ rc = WaitForSingleObject(vdo->state->captured, INFINITE);
+ // note: until we get the lock again the grabber thread might
+ // already provide the next sample (which is fine)
+ _zbar_mutex_lock(&vdo->qlock);
+
+ switch (rc) {
+ case WAIT_OBJECT_0:
+ img = vdo->state->image;
+ break;
+ case WAIT_ABANDONED:
+ err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "event handle abandoned");
+ break;
+ case WAIT_FAILED:
+ err_capture(vdo, SEV_ERROR, ZBAR_ERR_WINAPI, __func__,
+ "Waiting for image failed");
+ break;
+ }
+ }
+
+ vdo->state->image = NULL;
+ ResetEvent(vdo->state->captured);
+
+ video_unlock(vdo);
+ return (img);
+}
+
+static int vfw_start(zbar_video_t *vdo)
+{
+ ResetEvent(vdo->state->captured);
+
+ if (!capCaptureSequenceNoFile(vdo->state->hwnd))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "starting video stream"));
+ return (0);
+}
+
+static int vfw_stop(zbar_video_t *vdo)
+{
+ video_state_t *state = vdo->state;
+ if (!capCaptureAbort(state->hwnd))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "stopping video stream"));
+
+ _zbar_mutex_lock(&vdo->qlock);
+ if (state->image)
+ state->image = NULL;
+ SetEvent(state->captured);
+ _zbar_mutex_unlock(&vdo->qlock);
+ return (0);
+}
+
+static int vfw_set_format(zbar_video_t *vdo, uint32_t fmt)
+{
+ BITMAPINFOHEADER *bih;
+ const zbar_format_def_t *fmtdef = _zbar_format_lookup(fmt);
+ if (!fmtdef->format)
+ return (err_capture_int(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "unsupported vfw format: %x", fmt));
+
+ bih = vdo->state->bih;
+ assert(bih);
+ bih->biWidth = vdo->width;
+ bih->biHeight = vdo->height;
+ switch (fmtdef->group) {
+ case ZBAR_FMT_GRAY:
+ bih->biBitCount = 8;
+ break;
+ case ZBAR_FMT_YUV_PLANAR:
+ case ZBAR_FMT_YUV_PACKED:
+ case ZBAR_FMT_YUV_NV:
+ bih->biBitCount =
+ 8 + (16 >> (fmtdef->p.yuv.xsub2 + fmtdef->p.yuv.ysub2));
+ break;
+ case ZBAR_FMT_RGB_PACKED:
+ bih->biBitCount = fmtdef->p.rgb.bpp * 8;
+ break;
+ default:
+ bih->biBitCount = 0;
+ }
+ bih->biClrUsed = bih->biClrImportant = 0;
+ bih->biCompression = fmt;
+
+ zprintf(8, "setting format: %.4s(%08x) " BIH_FMT "\n", (char *)&fmt, fmt,
+ BIH_FIELDS(bih));
+
+ if (!capSetVideoFormat(vdo->state->hwnd, bih, vdo->state->bi_size))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "setting video format"));
+
+ if (!capGetVideoFormat(vdo->state->hwnd, bih, vdo->state->bi_size))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "getting video format"));
+
+ if (bih->biCompression != fmt)
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "video format set ignored"));
+
+ vdo->format = fmt;
+ vdo->width = bih->biWidth;
+ vdo->height = bih->biHeight;
+ vdo->datalen = bih->biSizeImage;
+
+ zprintf(4, "set new format: %.4s(%08x) " BIH_FMT "\n", (char *)&fmt, fmt,
+ BIH_FIELDS(bih));
+ return (0);
+}
+
+static int vfw_init(zbar_video_t *vdo, uint32_t fmt)
+{
+ HWND hwnd;
+ CAPTUREPARMS cp;
+
+ if (vfw_set_format(vdo, fmt))
+ return (-1);
+
+ hwnd = vdo->state->hwnd;
+ if (!capCaptureGetSetup(hwnd, &cp, sizeof(cp)))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_WINAPI, __func__,
+ "retrieving capture parameters"));
+
+ cp.dwRequestMicroSecPerFrame = 33333;
+ cp.fMakeUserHitOKToCapture = 0;
+ cp.wPercentDropForError = 90;
+ cp.fYield = 1;
+ cp.wNumVideoRequested = vdo->num_images;
+ cp.fCaptureAudio = 0;
+ cp.vKeyAbort = 0;
+ cp.fAbortLeftMouse = 0;
+ cp.fAbortRightMouse = 0;
+ cp.fLimitEnabled = 0;
+
+ if (!capCaptureSetSetup(hwnd, &cp, sizeof(cp)))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_WINAPI, __func__,
+ "setting capture parameters"));
+
+ if (!capCaptureGetSetup(hwnd, &cp, sizeof(cp)))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_WINAPI, __func__,
+ "checking capture parameters"));
+
+ /* ignore errors since we skipped checking fHasOverlay */
+ capOverlay(hwnd, 0);
+
+ if (!capPreview(hwnd, 0) || !capPreviewScale(hwnd, 0))
+ err_capture(vdo, SEV_WARNING, ZBAR_ERR_WINAPI, __func__,
+ "disabling preview");
+
+ if (!capSetCallbackOnVideoStream(hwnd, vfw_stream_cb) ||
+ !capSetCallbackOnError(hwnd, vfw_error_cb))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_BUSY, __func__,
+ "setting capture callbacks"));
+
+ vdo->num_images = cp.wNumVideoRequested;
+ vdo->iomode = VIDEO_MMAP; /* driver provides "locked" buffers */
+
+ zprintf(3, "initialized video capture: %d buffers %ldms/frame\n",
+ vdo->num_images, cp.dwRequestMicroSecPerFrame);
+
+ return (0);
+}
+
+static int vfw_cleanup(zbar_video_t *vdo)
+{
+ /* close open device */
+ video_state_t *state = vdo->state;
+ /* NB this has to go here so the thread can pump messages during cleanup */
+ capDriverDisconnect(state->hwnd);
+ DestroyWindow(state->hwnd);
+ state->hwnd = NULL;
+ _zbar_thread_stop(&state->thread, &vdo->qlock);
+
+ if (state->captured) {
+ CloseHandle(state->captured);
+ state->captured = NULL;
+ }
+ return (0);
+}
+
+static int vfw_probe_format(zbar_video_t *vdo, uint32_t fmt)
+{
+ BITMAPINFOHEADER *bih;
+ const zbar_format_def_t *fmtdef = _zbar_format_lookup(fmt);
+ if (!fmtdef)
+ return (0);
+
+ zprintf(4, " trying %.4s(%08x)...\n", (char *)&fmt, fmt);
+ bih = vdo->state->bih;
+ bih->biWidth = vdo->width;
+ bih->biHeight = vdo->height;
+ switch (fmtdef->group) {
+ case ZBAR_FMT_GRAY:
+ bih->biBitCount = 8;
+ break;
+ case ZBAR_FMT_YUV_PLANAR:
+ case ZBAR_FMT_YUV_PACKED:
+ case ZBAR_FMT_YUV_NV:
+ bih->biBitCount =
+ 8 + (16 >> (fmtdef->p.yuv.xsub2 + fmtdef->p.yuv.ysub2));
+ break;
+ case ZBAR_FMT_RGB_PACKED:
+ bih->biBitCount = fmtdef->p.rgb.bpp * 8;
+ break;
+ default:
+ bih->biBitCount = 0;
+ }
+ bih->biCompression = fmt;
+
+ if (!capSetVideoFormat(vdo->state->hwnd, bih, vdo->state->bi_size)) {
+ zprintf(4, "\tno (set fails)\n");
+ return (0);
+ }
+
+ if (!capGetVideoFormat(vdo->state->hwnd, bih, vdo->state->bi_size))
+ return (0 /*FIXME error...*/);
+
+ zprintf(6, "\tactual: " BIH_FMT "\n", BIH_FIELDS(bih));
+
+ if (bih->biCompression != fmt) {
+ zprintf(4, "\tno (set ignored)\n");
+ return (0);
+ }
+
+ zprintf(4, "\tyes\n");
+ return (1);
+}
+
+static int vfw_probe(zbar_video_t *vdo)
+{
+ BITMAPINFOHEADER *bih;
+ int n = 0;
+ const uint32_t *fmt;
+
+ video_state_t *state = vdo->state;
+ state->bi_size = capGetVideoFormatSize(state->hwnd);
+ bih = state->bih = realloc(state->bih, state->bi_size);
+ /* FIXME check OOM */
+
+ if (!capSetUserData(state->hwnd, (LONG)vdo) || !state->bi_size || !bih ||
+ !capGetVideoFormat(state->hwnd, bih, state->bi_size))
+ return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "setting up video capture"));
+
+ zprintf(3, "initial format: " BIH_FMT " (bisz=%x)\n", BIH_FIELDS(bih),
+ state->bi_size);
+
+ if (!vdo->width || !vdo->height) {
+ vdo->width = bih->biWidth;
+ vdo->height = bih->biHeight;
+ }
+ vdo->datalen = bih->biSizeImage;
+
+ zprintf(2, "probing supported formats:\n");
+ vdo->formats = calloc(VFW_NUM_FORMATS, sizeof(uint32_t));
+
+ for (fmt = vfw_formats; *fmt; fmt++)
+ if (vfw_probe_format(vdo, *fmt))
+ vdo->formats[n++] = *fmt;
+
+ vdo->formats = realloc(vdo->formats, (n + 1) * sizeof(uint32_t));
+
+ vdo->width = bih->biWidth;
+ vdo->height = bih->biHeight;
+ vdo->intf = VIDEO_VFW;
+ vdo->init = vfw_init;
+ vdo->start = vfw_start;
+ vdo->stop = vfw_stop;
+ vdo->cleanup = vfw_cleanup;
+ vdo->nq = vfw_nq;
+ vdo->dq = vfw_dq;
+ return (0);
+}
+
+int _zbar_video_open(zbar_video_t *vdo, const char *dev)
+{
+ int reqid = -1;
+ char name[MAX_NAME], desc[MAX_NAME];
+ int devid;
+
+ video_state_t *state = vdo->state;
+ if (!state)
+ state = vdo->state = calloc(1, sizeof(video_state_t));
+
+ if ((!strncmp(dev, "/dev/video", 10) ||
+ !strncmp(dev, "\\dev\\video", 10)) &&
+ dev[10] >= '0' && dev[10] <= '9' && !dev[11])
+ reqid = dev[10] - '0';
+ else if (strlen(dev) == 1 && dev[0] >= '0' && dev[0] <= '9')
+ reqid = dev[0] - '0';
+
+ zprintf(6, "searching for camera: %s (%d)\n", dev, reqid);
+ for (devid = 0; devid < MAX_DRIVERS; devid++) {
+ if (!capGetDriverDescription(devid, name, MAX_NAME, desc, MAX_NAME)) {
+ /* FIXME TBD error */
+ zprintf(6, " [%d] not found...\n", devid);
+ continue;
+ }
+ zprintf(6, " [%d] %.100s - %.100s\n", devid, name, desc);
+ if ((reqid >= 0) ? devid == reqid : !strncmp(dev, name, MAX_NAME))
+ break;
+ }
+ if (devid >= MAX_DRIVERS)
+ return (err_capture_str(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "video device not found '%s'", dev));
+
+ if (!state->captured)
+ state->captured = CreateEvent(NULL, TRUE, FALSE, NULL);
+ else
+ ResetEvent(state->captured);
+
+ if (_zbar_thread_start(&state->thread, vfw_capture_thread, vdo, NULL))
+ return (-1);
+
+ /* FIXME error */
+ assert(state->hwnd);
+
+ if (!capDriverConnect(state->hwnd, devid)) {
+ _zbar_thread_stop(&state->thread, NULL);
+ return (err_capture_str(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__,
+ "failed to connect to camera '%s'", dev));
+ }
+
+ zprintf(1, "opened camera: %.60s (%d) (thr=%04lx)\n", name, devid,
+ _zbar_thread_self());
+
+ if (vfw_probe(vdo)) {
+ _zbar_thread_stop(&state->thread, NULL);
+ return (-1);
+ }
+ return (0);
+}