/*------------------------------------------------------------------------
 *  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, &currentmt);
    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, &currentmt);
	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, &currentmt);
    CHECK_COM_ERROR(hr, "couldn't query current camera format", return -1)

    if (!dshow_has_vih(currentmt)) {
	char *formattype = get_clsid_string(&currentmt->formattype);
	char *subtype	 = get_clsid_string(&currentmt->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 *)&current_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;
}
