diff options
Diffstat (limited to 'zbar/video/vfw.c')
-rw-r--r-- | zbar/video/vfw.c | 511 |
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); +} |