diff options
Diffstat (limited to 'zbar/video/dshow.c')
-rw-r--r-- | zbar/video/dshow.c | 1334 |
1 files changed, 1334 insertions, 0 deletions
diff --git a/zbar/video/dshow.c b/zbar/video/dshow.c new file mode 100644 index 0000000..d304dfe --- /dev/null +++ b/zbar/video/dshow.c @@ -0,0 +1,1334 @@ +/*------------------------------------------------------------------------ + * Copyright 2012 (c) Klaus Triendl <klaus@triendl.eu> + * Copyright 2012 (c) Jarek Czekalski <jarekczek@poczta.onet.pl> + * + * 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/zbarw + *------------------------------------------------------------------------*/ + +#include "config.h" + +#include <assert.h> +#include <objbase.h> +#include <strmif.h> +#include <control.h> + +#include <qedit.h> + +#include <amvideo.h> // include after ddraw.h has been included from any dshow header + +#include <initguid.h> + +#include "misc.h" +#include "thread.h" +#include "video.h" + +#define ZBAR_DEFINE_STATIC_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, \ + b8) \ + static const GUID name; \ + static const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; + +// define a special guid that can be used for fourcc formats +// 00000000-0000-0010-8000-00AA00389B71 == MEDIASUBTYPE_FOURCC_PLACEHOLDER +ZBAR_DEFINE_STATIC_GUID(MEDIASUBTYPE_FOURCC_PLACEHOLDER, 0x00000000, 0x0000, + 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); + +#define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + ZBAR_DEFINE_STATIC_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8); + +#include <uuids.h> + +DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x46); +DEFINE_GUID(IID_ISampleGrabber, 0x6b652fff, 0x11fe, 0x4fce, 0x92, 0xad, 0x02, + 0x66, 0xb5, 0xd7, 0xc7, 0x8f); +DEFINE_GUID(IID_ISampleGrabberCB, 0x0579154a, 0x2b53, 0x4994, 0xb0, 0xd0, 0xe7, + 0x73, 0x14, 0x8e, 0xff, 0x85); +DEFINE_GUID(IID_IBaseFilter, 0x56a86895, 0x0ad4, 0x11ce, 0xb0, 0x3a, 0x00, 0x20, + 0xaf, 0x0b, 0xa7, 0x70); +DEFINE_GUID(IID_ICreateDevEnum, 0x29840822, 0x5b84, 0x11d0, 0xbd, 0x3b, 0x00, + 0xa0, 0xc9, 0x11, 0xce, 0x86); +DEFINE_GUID(IID_IGraphBuilder, 0x56a868a9, 0x0ad4, 0x11ce, 0xb0, 0x3a, 0x00, + 0x20, 0xaf, 0x0b, 0xa7, 0x70); +DEFINE_GUID(IID_IMediaControl, 0x56a868b1, 0x0ad4, 0x11ce, 0xb0, 0x3a, 0x00, + 0x20, 0xaf, 0x0b, 0xa7, 0x70); +DEFINE_GUID(IID_IPropertyBag, 0x55272a00, 0x42cb, 0x11ce, 0x81, 0x35, 0x00, + 0xaa, 0x00, 0x4b, 0xb8, 0x51); +DEFINE_GUID(IID_IAMStreamConfig, 0xc6e13340, 0x30ac, 0x11d0, 0xa1, 0x8c, 0x00, + 0xa0, 0xc9, 0x11, 0x89, 0x56); +DEFINE_GUID(IID_ICaptureGraphBuilder2, 0x93e5a4e0, 0x2d50, 0x11d2, 0xab, 0xfa, + 0x00, 0xa0, 0xc9, 0xc6, 0xe3, 0x8d); +DEFINE_GUID(CLSID_NullRenderer, 0xc1f400a4, 0x3f08, 0x11d3, 0x9f, 0x0b, 0x00, + 0x60, 0x08, 0x03, 0x9e, 0x37); +DEFINE_GUID(CLSID_SampleGrabber, 0xc1f400a0, 0x3f08, 0x11d3, 0x9f, 0x0b, 0x00, + 0x60, 0x08, 0x03, 0x9e, 0x37); + +#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 + +// taken from Capturing an Image winapi sample +#define BMP_SIZE(cx, cy, bitsPerPix) \ + ((((cx) * (bitsPerPix) + 31) / 32) * 4 * (cy)) + +#define COM_SAFE_RELEASE(ppIface) \ + if (*ppIface) \ + (*ppIface)->lpVtbl->Release(*ppIface) + +#define CHECK_COM_ERROR(hr, msg, stmt) \ + if (FAILED(hr)) { \ + LPSTR sysmsg = NULL; \ + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | \ + FORMAT_MESSAGE_FROM_SYSTEM | \ + FORMAT_MESSAGE_IGNORE_INSERTS, \ + NULL, hr, \ + 0 /* MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) */, \ + (LPSTR)&sysmsg, 0, NULL); \ + zprintf(6, "%s, hresult: 0x%lx %s", msg, hr, sysmsg); \ + LocalFree(sysmsg); \ + stmt; \ + } + +static const struct uuid_desc_s { + const GUID *guid; + const char *name; +} known_uuids[] = { +#define OUR_GUID_ENTRY(m_name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + { &m_name, #m_name }, +#include <ksuuids.h> + { NULL, NULL } +}; + +static const REFERENCE_TIME _100ns_unit = 1 * 1000 * 1000 * 1000 / 100; + +// format to which we convert MJPG streams +static const REFGUID mjpg_conversion_mediatype = &MEDIASUBTYPE_RGB32; +static const int mjpg_conversion_fmt = fourcc('B', 'G', 'R', '4'); +static const int mjpg_conversion_fmt_bpp = 32; + +static long grabbed_count = 0; + +// Destroy (Release) the format block for a media type. +static void DestroyMediaType(AM_MEDIA_TYPE *mt) +{ + if (mt->cbFormat != 0) + CoTaskMemFree(mt->pbFormat); + + if (mt->pUnk) + IUnknown_Release(mt->pUnk); +} + +// Destroy the format block for a media type and free the media type +static void DeleteMediaType(AM_MEDIA_TYPE *mt) +{ + if (!mt) + return; + DestroyMediaType(mt); + CoTaskMemFree(mt); +} + +static void make_fourcc_subtype(GUID *subtype, uint32_t fmt) +{ + *subtype = MEDIASUBTYPE_FOURCC_PLACEHOLDER; + subtype->Data1 = fmt; +} + +static int dshow_is_fourcc_guid(REFGUID subtype) +{ + // make up a fourcc guid in spe + GUID clsid; + make_fourcc_subtype(&clsid, subtype->Data1); + + return IsEqualGUID(subtype, &clsid); +} + +/// Checks whether the given AM_MEDIA_TYPE contains a +/// VIDEOINFOHEADER block. +static inline int dshow_has_vih(AM_MEDIA_TYPE *mt) +{ + // documentation for AM_MEDIA_TYPE emphasizes to do a thorough check + int isvih = (IsEqualGUID(&mt->formattype, &FORMAT_VideoInfo) && + mt->cbFormat >= sizeof(VIDEOINFOHEADER) && mt->pbFormat); + return isvih; +} + +/// Access the BITMAPINFOHEADER in the given AM_MEDIA_TYPE, +/// access is non-const +static inline BITMAPINFOHEADER *dshow_access_bih(AM_MEDIA_TYPE *mt) +{ + VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)mt->pbFormat; + return &vih->bmiHeader; +} + +/// Access the BITMAPINFOHEADER in the given AM_MEDIA_TYPE, +/// access is const +static inline const BITMAPINFOHEADER *dshow_caccess_bih(const AM_MEDIA_TYPE *mt) +{ + VIDEOINFOHEADER *vih = (VIDEOINFOHEADER *)mt->pbFormat; + return &vih->bmiHeader; +} + +/// Flips the image vertically copying it from srcBuf to img. +/** @param bpp Bits Per Pixel */ +static void flip_vert(zbar_image_t *const img, void *const srcBuf, int bpp) +{ + BYTE *dst, *src; + int i, n; + + // The formula below works only if bpp%8==0 + long bytesPerLine = 1L * img->width * bpp / 8; + assert(img->datalen >= img->height * bytesPerLine); + dst = (BYTE *)img->data; + src = ((BYTE *)srcBuf) + (img->height - 1) * bytesPerLine; + for (i = 0, n = img->height; i < n; i++) { + memcpy(dst, src, bytesPerLine); + dst += bytesPerLine; + src -= bytesPerLine; + } + assert(src + bytesPerLine == srcBuf); +} + +/// Internal format information +struct int_format_s { + uint32_t fourcc; + /// index for IAMStreamConfig::GetStreamCaps + /// @note compression in bih structure may differ from one used by zbar + int idx_caps; + resolution_list_t resolutions; +}; +typedef struct int_format_s int_format_t; + +struct video_state_s { + zbar_thread_t thread; /* capture message pump */ + HANDLE captured; + HANDLE notify; /* capture thread status change */ + int bi_size; /* size of bih */ + BITMAPINFOHEADER *bih; /* video format details of grabbed samples; + format might be among fourcc or BI_RGB */ + int do_flip_bitmap; /* whether uncompressed bitmap images are + bottom-up and need to be vertically + flipped */ + zbar_image_t *image; /* current capturing frame */ + + IGraphBuilder *graph; /* dshow graph manager */ + IMediaControl *mediacontrol; /* dshow graph control */ + IBaseFilter *camera; /* dshow source filter */ + ISampleGrabber *samplegrabber; /* dshow intermediate filter */ + IBaseFilter *grabberbase; /* samplegrabber's IBaseFilter interface */ + IBaseFilter *nullrenderer; + ICaptureGraphBuilder2 *builder; + IAMStreamConfig *camstreamconfig; /* dshow stream configuration interface */ + int caps_size; /* length of stream config caps */ + /// 0 terminated list of supported internal formats. + /** The size of this + * array matches the size of {@link zbar_video_s#formats} array and + * the consecutive entries correspond to each other, making a mapping + * between internal (camera) formats and zbar formats + * (presented to zbar processor). */ + int_format_t *int_formats; + resolution_t def_resolution; /* initial resolution read + from the camera */ +}; + +static void dshow_destroy_video_state_t(video_state_t *state) +{ + COM_SAFE_RELEASE(&state->camstreamconfig); + COM_SAFE_RELEASE(&state->builder); + COM_SAFE_RELEASE(&state->nullrenderer); + COM_SAFE_RELEASE(&state->grabberbase); + COM_SAFE_RELEASE(&state->samplegrabber); + COM_SAFE_RELEASE(&state->camera); + COM_SAFE_RELEASE(&state->mediacontrol); + COM_SAFE_RELEASE(&state->graph); + + if (state->captured) + CloseHandle(state->captured); + + free(state->bih); + + if (state->int_formats) { + int_format_t *fmt; + for (fmt = state->int_formats; !is_struct_null(fmt); fmt++) { + resolution_list_cleanup(&fmt->resolutions); + } + } + free(state->int_formats); +} + +/// Returns the index of the given format in formats array. +/** If not found, returns -1. The array is constructed like + * <code>vdo->formats</code>, with the terminating null. */ +static int get_format_index(uint32_t *fmts, uint32_t fmt0) +{ + uint32_t *fmt; + int i = 0; + for (fmt = fmts; *fmt; fmt++) { + if (*fmt == fmt0) + break; + i++; + } + if (*fmt) + return i; + else + return -1; +} + +/// Returns the index of the given format in internal formats array. +/** If not found, returns -1. The array is constructed like + * <code>vdo->state->int_formats</code>, with the terminating zeroed + * element. */ +/** @param fmt0 A fourcc format code */ +static int get_int_format_index(int_format_t *fmts, uint32_t fmt0) +{ + int_format_t *fmt; + int i = 0; + for (fmt = fmts; !is_struct_null(fmt); fmt++) { + if (fmt->fourcc == fmt0) + break; + i++; + } + if (!is_struct_null(fmt)) + return i; + else + return -1; +} + +/// Gets the fourcc code for the given media type. +/** This is necessary because of non-standard coding of video + * formats by Windows, for example BGR3/4 = BI_RGB . */ +static uint32_t get_fourcc_for_mt(const AM_MEDIA_TYPE *mt) +{ + if IsEqualGUID (&mt->subtype, &MEDIASUBTYPE_RGB24) + return fourcc('B', 'G', 'R', '3'); + else if IsEqualGUID (&mt->subtype, &MEDIASUBTYPE_RGB32) + return fourcc('B', 'G', 'R', '4'); + else if (dshow_is_fourcc_guid(&mt->subtype)) + return mt->subtype.Data1; + else + return 0; +} + +/// Dumps the mapping of internal and external formats, if the debug level +/// is sufficient. +static void dump_formats(zbar_video_t *vdo) +{ + video_state_t *state = vdo->state; + uint32_t *fmt; + int_format_t *int_fmt; + zprintf(8, "Detected formats: (internal) / (translated for zbar)\n"); + fmt = vdo->formats; + int_fmt = state->int_formats; + while (*fmt) { + zprintf(8, " %.4s / %.4s, resolutions: %lu\n", + (char *)&int_fmt->fourcc, (char *)fmt, + int_fmt->resolutions.cnt); + fmt++; + int_fmt++; + } +} + +/// Allocates a string containing given guid. +/** If it's among known CLSID's, its name is returned. + * Returned string should be freed with `free`. */ +static char *get_clsid_string(REFGUID guid) +{ + char *msg = NULL; + + // first try to determine guid name from the list + int i; + for (i = 0; known_uuids[i].name; i++) { + if (IsEqualGUID(known_uuids[i].guid, guid)) { + msg = malloc(strlen(known_uuids[i].name) + 1); + strcpy(msg, known_uuids[i].name); + break; + } + } + + // if that failed, give the ugly string + if (msg == NULL) { + LPOLESTR str = L"ERROR"; + HRESULT hr = StringFromCLSID(guid, &str); + int c = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + msg = malloc(c); + WideCharToMultiByte(CP_UTF8, 0, str, -1, msg, c, NULL, NULL); + if (!FAILED(hr)) + CoTaskMemFree(str); + } + + return msg; +} + +/// Maps internal format MJPG (if found) to a format known to +/// zbar. +/** The conversion will be done by dshow mjpeg decompressor + * before passing the image to zbar. */ +static void prepare_mjpg_format_mapping(zbar_video_t *vdo) +{ + int iMjpgConv; + video_state_t *state = vdo->state; + /// The format we will convert MJPG to + uint32_t fmtConv = mjpg_conversion_fmt; + int iMjpg = + get_int_format_index(state->int_formats, fourcc('M', 'J', 'P', 'G')); + if (iMjpg < 0) + return; + assert(vdo->formats[iMjpg] == fourcc('M', 'J', 'P', 'G')); + + // If we already have fmtConv, it will lead to duplicating it in + // external formats. + // We can't leave it this way, because when zbar wants to use fmtConv + // we must have only one internal format for that. + // It's better to drop MJPG then, as it's a compressed format and + // we prefer better quality images. + + // The index of fmtConv before mapping mjpg to fmtConv + iMjpgConv = get_int_format_index(state->int_formats, fmtConv); + + if (iMjpgConv >= 0) { + // remove the iMjpgConv entry by moving the following entries by 1 + int i; + for (i = iMjpgConv; vdo->formats[i]; i++) { + vdo->formats[i] = vdo->formats[i + 1]; + state->int_formats[i] = state->int_formats[i + 1]; + } + // The number of formats is reduced by 1 now, but to realloc just + // to save 2 times 4 bytes? Too much fuss. + } else { + vdo->formats[iMjpg] = fmtConv; + } +} + +/// sample grabber callback implementation (derived from ISampleGrabberCB) +typedef struct zbar_samplegrabber_cb { + // baseclass + const struct ISampleGrabberCB _; + // COM refcount + ULONG refcount; + + zbar_video_t *vdo; + +} zbar_samplegrabber_cb; + +// ISampleGrabber methods (implementation below) + +HRESULT __stdcall zbar_samplegrabber_cb_QueryInterface(ISampleGrabberCB *_This, + REFIID riid, + void **ppvObject); +ULONG __stdcall zbar_samplegrabber_cb_AddRef(ISampleGrabberCB *_This); +ULONG __stdcall zbar_samplegrabber_cb_Release(ISampleGrabberCB *_This); +HRESULT __stdcall zbar_samplegrabber_cb_SampleCB(ISampleGrabberCB *_This, + double sampletime, + IMediaSample *sample); +// note: original MS version expects a long for the buffer length, wine version a LONG +//HRESULT __stdcall zbar_samplegrabber_cb_BufferCB(ISampleGrabberCB* _This, double sampletime, BYTE* buffer, long bufferlen); +HRESULT __stdcall zbar_samplegrabber_cb_BufferCB(ISampleGrabberCB *_This, + double sampletime, + BYTE *buffer, LONG bufferlen); + +static struct ISampleGrabberCBVtbl SampleGrabberCBVtbl = { + &zbar_samplegrabber_cb_QueryInterface, &zbar_samplegrabber_cb_AddRef, + &zbar_samplegrabber_cb_Release, &zbar_samplegrabber_cb_SampleCB, + &zbar_samplegrabber_cb_BufferCB +}; + +static zbar_samplegrabber_cb *new_zbar_samplegrabber_cb(zbar_video_t *vdo) +{ + // allocate memory + zbar_samplegrabber_cb *o = calloc(1, sizeof(zbar_samplegrabber_cb)); + + // construct parent + ISampleGrabberCB *base = (ISampleGrabberCB *)o; + base->lpVtbl = &SampleGrabberCBVtbl; + + // construct object + o->refcount = 1; + o->vdo = vdo; + + return o; +} + +static void delete_zbar_samplegrabber_cb(zbar_samplegrabber_cb *o) +{ + zprintf(16, "thr=%04lx\n", _zbar_thread_self()); + + // no destruction necessary + + free(o); +} + +HRESULT __stdcall zbar_samplegrabber_cb_QueryInterface(ISampleGrabberCB *_This, + REFIID riid, + void **ppvObject) +{ + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISampleGrabberCB)) { + *ppvObject = _This; + return NOERROR; + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } +} + +ULONG __stdcall zbar_samplegrabber_cb_AddRef(ISampleGrabberCB *_This) +{ + zbar_samplegrabber_cb *This = (zbar_samplegrabber_cb *)_This; + + return ++This->refcount; +} + +ULONG __stdcall zbar_samplegrabber_cb_Release(ISampleGrabberCB *_This) +{ + zbar_samplegrabber_cb *This = (zbar_samplegrabber_cb *)_This; + + ULONG refcnt = --This->refcount; + if (!refcnt) + delete_zbar_samplegrabber_cb(This); + + return refcnt; +} + +HRESULT __stdcall zbar_samplegrabber_cb_SampleCB(ISampleGrabberCB *_This, + double sampletime, + IMediaSample *sample) +{ + return E_NOTIMPL; +} + +//HRESULT __stdcall zbar_samplegrabber_cb_BufferCB(ISampleGrabberCB* _This, double sampletime, BYTE* buffer, long bufferlen) +HRESULT __stdcall zbar_samplegrabber_cb_BufferCB(ISampleGrabberCB *_This, + double sampletime, + BYTE *buffer, LONG bufferlen) +{ + zbar_samplegrabber_cb *This; + zbar_video_t *vdo; + zbar_image_t *img; + if (!buffer || !bufferlen) + return S_OK; + + grabbed_count++; + This = (zbar_samplegrabber_cb *)_This; + vdo = This->vdo; + + _zbar_mutex_lock(&vdo->qlock); + + zprintf(16, "got sample no %ld: %p (%ld), thr=%04lx\n", grabbed_count, + buffer, bufferlen, _zbar_thread_self()); + + img = vdo->state->image; + if (!img) { + _zbar_mutex_lock(&vdo->qlock); + img = video_dq_image(vdo); + // note: video_dq_image() has unlocked the mutex + } + if (img) { + zprintf(16, "copying into img: %p (srcidx: %d, data: %p, len: %ld)\n", + img, img->srcidx, img->data, img->datalen); + + assert(img->datalen == bufferlen); + + // The image needs to be copied now. Usually memcpy is ok, + // but in case of MJPG the picture is upside-down. + if (vdo->state->do_flip_bitmap) + flip_vert(img, buffer, vdo->state->bih->biBitCount); + else + memcpy((void *)img->data, buffer, img->datalen); + + vdo->state->image = img; + SetEvent(vdo->state->captured); + } + + _zbar_mutex_unlock(&vdo->qlock); + + return S_OK; +} + +static ZTHREAD dshow_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; + + _zbar_mutex_lock(&vdo->qlock); + + _zbar_thread_init(thr); + zprintf(4, "spawned dshow 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 int dshow_nq(zbar_video_t *vdo, zbar_image_t *img) +{ + zprintf(16, "img: %p (srcidx: %d, data: %p, len: %ld), thr=%04lx\n", img, + img->srcidx, img->data, img->datalen, _zbar_thread_self()); + 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 *dshow_dq(zbar_video_t *vdo) +{ + zbar_image_t *img = vdo->state->image; + if (!img) { + DWORD 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; + } + } + + zprintf(16, "img: %p (srcidx: %d, data: %p, len: %ld), thr=%04lx\n", img, + img ? img->srcidx : 0, img ? img->data : NULL, + img ? img->datalen : 0, _zbar_thread_self()); + + vdo->state->image = NULL; + ResetEvent(vdo->state->captured); + video_unlock(vdo); + + return img; +} + +static int dshow_start(zbar_video_t *vdo) +{ + HRESULT hr; + video_state_t *state = vdo->state; + ResetEvent(state->captured); + + zprintf(16, "thr=%04lx\n", _zbar_thread_self()); + + hr = IMediaControl_Run(state->mediacontrol); + CHECK_COM_ERROR(hr, "couldn't start video stream", (void)0); + if (FAILED(hr)) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "starting video stream")); + return 0; +} + +static int dshow_stop(zbar_video_t *vdo) +{ + HRESULT hr; + video_state_t *state = vdo->state; + + zprintf(16, "thr=%04lx\n", _zbar_thread_self()); + + hr = IMediaControl_Stop(state->mediacontrol); + CHECK_COM_ERROR(hr, "couldn't stop video stream", (void)0); + if (FAILED(hr)) + 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 dshow_set_format(zbar_video_t *vdo, uint32_t fmt) +{ + int rc = 0; // return code + video_state_t *state; + int_format_t *int_fmt; + BYTE *caps; + AM_MEDIA_TYPE *mt = NULL, *currentmt = NULL; + HRESULT hr; + BITMAPINFOHEADER *bih; + + const zbar_format_def_t *fmtdef = _zbar_format_lookup(fmt); + int fmt_ind = get_format_index(vdo->formats, fmt); + if (!fmtdef->format || fmt_ind < 0) + return (err_capture_int(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "unsupported vfw format: %x", fmt)); + + state = vdo->state; + int_fmt = &state->int_formats[fmt_ind]; + + // prepare media type structure as read from GetStreamCaps + caps = malloc(state->caps_size); + if (!caps) + err_capture(vdo, SEV_FATAL, ZBAR_ERR_NOMEM, __func__, ""); + + hr = IAMStreamConfig_GetStreamCaps(state->camstreamconfig, + int_fmt->idx_caps, &mt, caps); + free(caps); + CHECK_COM_ERROR(hr, "querying chosen stream caps failed", goto cleanup) + bih = dshow_access_bih(mt); + + // then, adjust the format + if (!vdo->width || !vdo->height) { + // video size not requested, take camera default + resolution_t resolution = vdo->state->def_resolution; + // the initial resolution may be not suitable for the current format + get_closest_resolution(&resolution, &int_fmt->resolutions); + vdo->width = resolution.cx; + vdo->height = resolution.cy; + } + bih->biWidth = vdo->width; + bih->biHeight = vdo->height; + + zprintf(4, "setting camera format: %.4s(%08x) " BIH_FMT "\n", + (char *)&int_fmt->fourcc, int_fmt->fourcc, BIH_FIELDS(bih)); + + hr = IAMStreamConfig_SetFormat(state->camstreamconfig, mt); + CHECK_COM_ERROR(hr, "setting camera format failed", goto cleanup) + + // re-read format, image data size might have changed + hr = IAMStreamConfig_GetFormat(state->camstreamconfig, ¤tmt); + CHECK_COM_ERROR(hr, "queried currentmt", goto cleanup); + + bih = dshow_access_bih(currentmt); + + if (get_fourcc_for_mt(currentmt) != int_fmt->fourcc) { + rc = err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "video format set ignored"); + goto cleanup; + } + + vdo->format = fmt; + vdo->width = bih->biWidth; + vdo->height = bih->biHeight; + vdo->datalen = bih->biSizeImage; + + // datalen was set based on the internal format, but sometimes it's + // different from the format reported to zbar processor + if (vdo->formats[fmt_ind] != state->int_formats[fmt_ind].fourcc) { + // See prepare_mjpg_format_mapping for possible differences + // between internal and zbar format. + vdo->datalen = + BMP_SIZE(vdo->width, vdo->height, mjpg_conversion_fmt_bpp); + } + + zprintf(4, "set camera format: %.4s(%08x) " BIH_FMT "\n", + (char *)&int_fmt->fourcc, int_fmt->fourcc, BIH_FIELDS(bih)); + +cleanup: + DeleteMediaType(mt); + DeleteMediaType(currentmt); + + if (FAILED(hr)) + return err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "setting dshow format failed"); + else + // note: if an error happened it was already captured and reported + return rc; +} + +static int dshow_init(zbar_video_t *vdo, uint32_t fmt) +{ + HRESULT hr; + video_state_t *state; + int fmt_ind; + ISampleGrabberCB *grabbercb; + REFERENCE_TIME avgtime_perframe; + + if (dshow_set_format(vdo, fmt)) + return -1; + + state = vdo->state; + fmt_ind = get_format_index(vdo->formats, fmt); + + // install sample grabber callback + grabbercb = (ISampleGrabberCB *)new_zbar_samplegrabber_cb(vdo); + hr = ISampleGrabber_SetCallback(vdo->state->samplegrabber, grabbercb, 1); + ISampleGrabberCB_Release(grabbercb); + if (FAILED(hr)) + return (err_capture(vdo, SEV_ERROR, ZBAR_ERR_BUSY, __func__, + "setting capture callbacks")); + + // set up directshow graph + + // special handling for MJPG streams: + // we use the stock mjpeg decompressor filter + if (state->int_formats[fmt_ind].fourcc == fourcc('M', 'J', 'P', 'G')) { + IBaseFilter *mjpgdecompressor = NULL; + AM_MEDIA_TYPE conv_mt = { 0 }; + + hr = CoCreateInstance(&CLSID_MjpegDec, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void **)&mjpgdecompressor); + CHECK_COM_ERROR(hr, "failed to create mjpeg decompressor filter", + (void)0) + if (FAILED(hr)) { + return err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "failed to create mjpeg decompressor filter"); + } + + // add mjpeg decompressor to graph + hr = IGraphBuilder_AddFilter(state->graph, mjpgdecompressor, + L"MJPEG decompressor"); + CHECK_COM_ERROR(hr, "adding MJPEG decompressor", goto mjpg_cleanup) + + // explicitly convert MJPG to RGB32 + // (sample grabber will only accept this format) + // + // note: the mjpeg decompressor's output seems to be RGB32 (BGR4) + // by default. + // This fact has been a decisive factor to choosing the mapping + // (BGR4 [zbar input] -> MJPG [camera output]). + // Because zbar requests BGR4 we have to ensure that we really do + // provide it. + conv_mt.majortype = MEDIATYPE_Video; + conv_mt.subtype = *mjpg_conversion_mediatype; + conv_mt.formattype = FORMAT_VideoInfo; + hr = ISampleGrabber_SetMediaType(state->samplegrabber, &conv_mt); + CHECK_COM_ERROR(hr, "setting mjpg conversion media type", + goto mjpg_cleanup) + // no need to destroy conv_mt + + hr = ICaptureGraphBuilder2_RenderStream( + state->builder, &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, + (IUnknown *)state->camera, NULL, mjpgdecompressor); + CHECK_COM_ERROR(hr, "rendering filter graph 1", goto mjpg_cleanup) + + hr = ICaptureGraphBuilder2_RenderStream(state->builder, NULL, + &MEDIATYPE_Video, + (IUnknown *)mjpgdecompressor, + state->grabberbase, + state->nullrenderer); + CHECK_COM_ERROR(hr, "rendering filter graph 2", goto mjpg_cleanup) + + mjpg_cleanup: + IBaseFilter_Release(mjpgdecompressor); + + if (FAILED(hr)) { + return err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "rendering filter graph failed"); + } + } else { + // ensure (again) that the sample grabber gets only + // video media types with VIDEOINFOHEADER + AM_MEDIA_TYPE grab_mt = { 0 }; + grab_mt.majortype = MEDIATYPE_Video; + grab_mt.formattype = FORMAT_VideoInfo; + hr = ISampleGrabber_SetMediaType(state->samplegrabber, &grab_mt); + CHECK_COM_ERROR(hr, "setting sample grabber media type", + goto render_cleanup) + // no need to destroy grab_mt + + hr = ICaptureGraphBuilder2_RenderStream( + state->builder, &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, + (IUnknown *)state->camera, state->grabberbase, state->nullrenderer); + CHECK_COM_ERROR(hr, "rendering filter graph", goto render_cleanup) + + render_cleanup: + if (FAILED(hr)) { + return err_capture(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "rendering filter graph failed"); + } + } + + // scope: after the graph is built (and the pins connected) we query the + // final media type from sample grabber's input pin; + { + AM_MEDIA_TYPE input_mt = { 0 }; + const BITMAPINFOHEADER *bih; + + hr = ISampleGrabber_GetConnectedMediaType(state->samplegrabber, + &input_mt); + CHECK_COM_ERROR(hr, + "couldn't query input media type from sample grabber", + goto cleanup1) + + assert(dshow_has_vih(&input_mt)); + bih = dshow_caccess_bih(&input_mt); + + // adjust state->bih + state->bi_size = bih->biSize; + state->bih = realloc(state->bih, state->bi_size); + memcpy(state->bih, bih, state->bi_size); + + if (bih->biCompression == BI_RGB) { + // check bitmap orientation for native BI_RGB: + // positive biHeight: bottom-up bitmap -> flip + // negative biHeight: top-down bitmap + state->do_flip_bitmap = (bih->biHeight > 0); + } + + cleanup1: + DestroyMediaType(&input_mt); + + if (FAILED(hr)) + return -1; + } + + // query camera stream parameters; + avgtime_perframe = 0; + + // scope: query avgtime_perframe from camera + { + AM_MEDIA_TYPE *currentmt = NULL; + VIDEOINFOHEADER *vih; + hr = IAMStreamConfig_GetFormat(state->camstreamconfig, ¤tmt); + CHECK_COM_ERROR(hr, "querying camera format failed", goto cleanup2) + + assert(dshow_has_vih(currentmt)); + vih = (VIDEOINFOHEADER *)currentmt->pbFormat; + avgtime_perframe = vih->AvgTimePerFrame; + + cleanup2: + DeleteMediaType(currentmt); + } + + // directshow keeps ownership of the image passed to the callback, + // hence, keeping a pointer to the original data (iomode VIDEO_MMAP) is a bad idea + // in a multi-threaded environment. + // real case szenario on a windows 7 tablet with intel atom: + // with vdo->iomode=VIDEO_MMAP and vdo->num_images=1: + // 1) directshow graph provides a sample, sets the data pointer of vdo->state->img + // 2) dshow_dq (called from proc_video_thread()/zbar_video_next_image()) fetches the image and makes a copy of it + // 3) directshow graph provides the next sample, sets the data pointer of vdo->state->img + // 4) dshow_nq (called from ) resets the data pointer of vdo->state->img (nullptr) + // 5) dshow_dq returns vdo->state->img without data (nullptr) + // + // now, we could deal with this special case, but zbar_video_next_image() makes a copy of the sample anyway when + // vdo->num_images==1 (thus VIDEO_MMAP won't save us anything); therefore rather use image buffers provided + // by zbar (see video_init_images()) + vdo->iomode = VIDEO_USERPTR; + // keep zbar's default + //vdo->num_images = ZBAR_VIDEO_IMAGES_MAX; + + // note: vdo->format and vdo->datalen have been set accordingly in dshow_set_format() + + zprintf(3, "initialized video capture: %.4s(%08x), %" PRId64 " frames/s\n", + (char *)&fmt, fmt, _100ns_unit / avgtime_perframe); + + return 0; +} + +static int dshow_cleanup(zbar_video_t *vdo) +{ + video_state_t *state; + zprintf(16, "thr=%04lx\n", _zbar_thread_self()); + + /* close open device */ + state = vdo->state; + + _zbar_thread_stop(&state->thread, &vdo->qlock); + + dshow_destroy_video_state_t(state); + free(state); + vdo->state = NULL; + + CoUninitialize(); + + return 0; +} + +static int dshow_determine_formats(zbar_video_t *vdo) +{ + video_state_t *state = vdo->state; + BYTE *caps; + int n = 0, i; + + // collect formats + int resolutions; + HRESULT hr = IAMStreamConfig_GetNumberOfCapabilities(state->camstreamconfig, + &resolutions, + &state->caps_size); + CHECK_COM_ERROR(hr, "couldn't query camera capabilities", return -1) + + zprintf( + 6, + "number of formats/resolutions supported by the camera as reported by directshow: %d\n", + resolutions); + zprintf(6, "list of those with a VIDEOINFOHEADER:\n"); + + vdo->formats = calloc(resolutions + 1, sizeof(uint32_t)); + state->int_formats = calloc(resolutions + 1, sizeof(int_format_t)); + + // this is actually a VIDEO_STREAM_CONFIG_CAPS structure, which is mostly deprecated anyway, + // so we just reserve enough buffer but treat it as opaque otherwise + caps = malloc(state->caps_size); + for (i = 0; i < resolutions; ++i) { + AM_MEDIA_TYPE *mt; + int is_supported; + + HRESULT hr = + IAMStreamConfig_GetStreamCaps(state->camstreamconfig, i, &mt, caps); + CHECK_COM_ERROR(hr, "querying stream capability failed", continue) + is_supported = 0; + + if (dshow_has_vih(mt)) { + uint32_t fmt; + const BITMAPINFOHEADER *bih = dshow_caccess_bih(mt); + + zprintf(6, BIH_FMT "\n", BIH_FIELDS(bih)); + fmt = get_fourcc_for_mt(mt); + // This is actually a check if the format is recognized. + // TODO: Check if the format is really supported by zbar. + is_supported = (fmt != 0); + + if (is_supported) { + resolution_t resolution; + int j; + + // first search for existing fourcc format + for (j = 0; j < n; ++j) { + if (state->int_formats[i].fourcc == fmt) + break; + } + // push back if not found + if (j == n) { + state->int_formats[n].fourcc = fmt; + state->int_formats[n].idx_caps = i; + resolution_list_init(&state->int_formats[n].resolutions); + vdo->formats[n] = fmt; + ++n; + } + + resolution.cx = bih->biWidth; + resolution.cy = bih->biHeight; + resolution_list_add(&state->int_formats[j].resolutions, + &resolution); + } + } + // note: other format types could be possible, e.g. VIDEOINFOHEADER2 ... + + if (!is_supported) { + char *formattype = get_clsid_string(&mt->formattype); + char *subtype = get_clsid_string(&mt->subtype); + zprintf(6, "unsupported format: %s / %s\n", formattype, subtype); + free(formattype); + free(subtype); + } + + DeleteMediaType(mt); + } + free(caps); + + zprintf(6, "number of supported fourcc formats: %d\n", n); + + vdo->formats = realloc(vdo->formats, (n + 1) * sizeof(uint32_t)); + state->int_formats = + realloc(state->int_formats, (n + 1) * sizeof(int_format_t)); + prepare_mjpg_format_mapping(vdo); + dump_formats(vdo); + + if (n == 0) + return -1; + + return 0; +} + +static int dshow_probe(zbar_video_t *vdo) +{ + video_state_t *state; + AM_MEDIA_TYPE *currentmt; + HRESULT hr; + if (dshow_determine_formats(vdo)) + return -1; + + state = vdo->state; + + // query current format + + hr = IAMStreamConfig_GetFormat(state->camstreamconfig, ¤tmt); + CHECK_COM_ERROR(hr, "couldn't query current camera format", return -1) + + if (!dshow_has_vih(currentmt)) { + char *formattype = get_clsid_string(¤tmt->formattype); + char *subtype = get_clsid_string(¤tmt->subtype); + zprintf(1, + "encountered unsupported initial format, " + "no VIDEOINFOHEADER video format type: %s / %s\n", + formattype, subtype); + free(formattype); + free(subtype); + + // if we cannot read the current resolution, we need sane defaults + vdo->state->def_resolution.cx = 640; + vdo->state->def_resolution.cy = 480; + } else { + const BITMAPINFOHEADER *current_bih = dshow_caccess_bih(currentmt); + assert(current_bih); + zprintf(3, "initial format: %.4s(%08lx) " BIH_FMT "\n", + (char *)¤t_bih->biCompression, current_bih->biCompression, + BIH_FIELDS(current_bih)); + + // store initial size + vdo->state->def_resolution.cx = current_bih->biWidth; + vdo->state->def_resolution.cy = current_bih->biHeight; + } + + DeleteMediaType(currentmt); + + vdo->intf = VIDEO_DSHOW; + vdo->init = dshow_init; + vdo->start = dshow_start; + vdo->stop = dshow_stop; + vdo->cleanup = dshow_cleanup; + vdo->nq = dshow_nq; + vdo->dq = dshow_dq; + + return 0; +} + +// search camera by index or by device path +static IBaseFilter *dshow_search_camera(const char *dev) +{ + IBaseFilter *camera = NULL; + ICreateDevEnum *devenumerator = NULL; + IEnumMoniker *enummoniker = NULL; + HRESULT hr; + BSTR wdev; + int docontinue; + int devid; + + int reqid = -1; + 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 (#%d): %s\n", reqid, dev); + + hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, + &IID_ICreateDevEnum, (void **)&devenumerator); + CHECK_COM_ERROR(hr, "failed to create system device enumerator", goto done) + + hr = ICreateDevEnum_CreateClassEnumerator(devenumerator, + &CLSID_VideoInputDeviceCategory, + &enummoniker, 0); + CHECK_COM_ERROR(hr, "failed to create enumerator moniker", goto done) + + if (hr != S_OK) { + zprintf(6, "no video devices available"); + goto done; + } + + // turn device name (the GUID) from char to wide char + wdev = SysAllocStringLen(NULL, strlen(dev)); + if (!wdev) + goto done; + MultiByteToWideChar(CP_UTF8, 0, dev, -1, wdev, strlen(dev) + 1); + + // Go through and find capture device + for (devid = 0, docontinue = 1; docontinue; ++devid) { + IMoniker *moniker = NULL; + IPropertyBag *propbag = NULL; + VARIANT devpath_variant; + VARIANT friendlyname_variant; + + hr = IEnumMoniker_Next(enummoniker, 1, &moniker, NULL); + // end of monikers + if (hr != S_OK) + break; + + VariantInit(&devpath_variant); + VariantInit(&friendlyname_variant); + + hr = IMoniker_BindToStorage(moniker, NULL, NULL, &IID_IPropertyBag, + (void **)&propbag); + CHECK_COM_ERROR(hr, "failed to get property bag from moniker", + goto breakout) + + hr = IPropertyBag_Read(propbag, L"DevicePath", &devpath_variant, NULL); + CHECK_COM_ERROR(hr, "failed to read DevicePath from camera device", + goto breakout) + hr = IPropertyBag_Read(propbag, L"FriendlyName", &friendlyname_variant, + NULL); + CHECK_COM_ERROR(hr, "failed to read FriendlyName from camera device", + goto breakout) + + if ((reqid >= 0) ? devid == reqid : + !wcscmp(wdev, V_BSTR(&devpath_variant))) { + // create camera from moniker + hr = IMoniker_BindToObject(moniker, NULL, NULL, &IID_IBaseFilter, + (void **)&camera); + CHECK_COM_ERROR(hr, "failed to get camera device", goto breakout) + } + + if (camera) { + zwprintf(1, L"using camera #%d: %s (%s)\n", devid, + V_BSTR(&friendlyname_variant), V_BSTR(&devpath_variant)); + goto breakout; + } else + goto cleanup; + + breakout: + docontinue = 0; + cleanup: + VariantClear(&friendlyname_variant); + VariantClear(&devpath_variant); + COM_SAFE_RELEASE(&propbag); + COM_SAFE_RELEASE(&moniker); + } + SysFreeString(wdev); + +done: + COM_SAFE_RELEASE(&enummoniker); + COM_SAFE_RELEASE(&devenumerator); + + if (!camera) + zprintf(6, "no camera found\n"); + + return camera; +} + +int _zbar_video_open(zbar_video_t *vdo, const char *dev) +{ + HRESULT hr; + // assume failure + int ret = -1; + + video_state_t *state; + state = vdo->state = calloc(1, sizeof(video_state_t)); + + state->camera = dshow_search_camera(dev); + if (!state->camera) + goto done; + + if (!state->captured) + // create manual reset event + state->captured = CreateEvent(NULL, TRUE, FALSE, NULL); + else + ResetEvent(state->captured); + + if (_zbar_thread_start(&state->thread, dshow_capture_thread, vdo, NULL)) + return -1; + + // create filter graph instance + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, + &IID_IGraphBuilder, (void **)&state->graph); + CHECK_COM_ERROR(hr, "graph builder creation", goto done) + + // query media control from filter graph + hr = IGraphBuilder_QueryInterface(state->graph, &IID_IMediaControl, + (void **)&state->mediacontrol); + CHECK_COM_ERROR(hr, "querying media control", goto done) + + // create sample grabber instance + hr = CoCreateInstance(&CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, + &IID_ISampleGrabber, (void **)&state->samplegrabber); + CHECK_COM_ERROR(hr, "samplegrabber creation", goto done) + + // query base filter interface from sample grabber + hr = ISampleGrabber_QueryInterface(state->samplegrabber, &IID_IBaseFilter, + (void **)&state->grabberbase); + CHECK_COM_ERROR(hr, "grabberbase query", goto done) + + // capture graph without preview window + hr = CoCreateInstance(&CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void **)&state->nullrenderer); + CHECK_COM_ERROR(hr, "null renderer creation", goto done) + + // add camera to graph + hr = IGraphBuilder_AddFilter(state->graph, state->camera, L"Camera"); + CHECK_COM_ERROR(hr, "adding camera", goto done) + + // add sample grabber to graph + hr = IGraphBuilder_AddFilter(state->graph, state->grabberbase, + L"Sample Grabber"); + CHECK_COM_ERROR(hr, "adding samplegrabber", goto done) + + // add nullrenderer to graph + hr = IGraphBuilder_AddFilter(state->graph, state->nullrenderer, + L"Null Renderer"); + CHECK_COM_ERROR(hr, "adding null renderer", goto done) + + // Create the Capture Graph Builder. + hr = CoCreateInstance(&CLSID_CaptureGraphBuilder2, NULL, + CLSCTX_INPROC_SERVER, &IID_ICaptureGraphBuilder2, + (void **)&state->builder); + CHECK_COM_ERROR(hr, "capturegraph builder creation", goto done) + + // tell graph builder about the filter graph + hr = ICaptureGraphBuilder2_SetFiltergraph(state->builder, state->graph); + CHECK_COM_ERROR(hr, "setting filtergraph", goto done) + + // TODO: + // finding the streamconfig interface on the camera's preview output pin (specifying PIN_CATEGORY_PREVIEW, MEDIATYPE_Video) + // should work according to the documentation but it doesn't. + // Because devices may have separate pins for capture and preview or have a video port pin (PIN_CATEGORY_VIDEOPORT) + // instead of a preview pin, I do hope that we get the streamconfig interface for the correct pin + hr = ICaptureGraphBuilder2_FindInterface( + state->builder, &LOOK_DOWNSTREAM_ONLY, NULL, state->camera, + &IID_IAMStreamConfig, (void **)&state->camstreamconfig); + CHECK_COM_ERROR(hr, "querying camera's streamconfig interface", goto done) + + if (dshow_probe(vdo)) + goto done; + + // success + ret = 0; + +done: + if (ret) { + if (state->camera) + _zbar_thread_stop(&state->thread, NULL); + dshow_destroy_video_state_t(state); + free(state); + vdo->state = NULL; + CoUninitialize(); + + return err_capture_str(vdo, SEV_ERROR, ZBAR_ERR_INVALID, __func__, + "failed to connect to camera '%s'", dev); + } + + return ret; +} |