/*
 * tclwmf.c --
 *
 *      This file contains the implementation of the "wmf" Tcl
 *      built-in command which allows to operate cameras using
 *      the Windows Media Foundation.
 *
 * Copyright (c) 2016-2019 Christian Werner <chw at ch minus werner dot de>
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#ifndef _WIN32
#error "unsupported platform"
#endif

#undef  WINVER
#define WINVER 0x0601

#include <windows.h>
#include <tk.h>
#include <string.h>

#include <shlwapi.h>

#include <initguid.h>
#undef  EXTERN_GUID
#define EXTERN_GUID DEFINE_GUID

#include <mferror.h>
#include <mfidl.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmultichar"
#include <mfapi.h>
#pragma GCC diagnostic pop
#include <mfreadwrite.h>
#include <strmif.h>

#include <jpeglib.h>
#include <setjmp.h>

#if defined(__GNUC__)

/* These UUIDs are missing in current mingw64 header files */

DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
	    0xc60ac5fe, 0x252a, 0x478f,
	    0xa0, 0xef, 0xbc, 0x8f, 0xa5, 0xf7, 0xca, 0xd3);
DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID,
	    0x8ac3587a, 0x4ae7, 0x42d8,
	    0x99, 0xe0, 0x0a, 0x60, 0x13, 0xee, 0xf9, 0x0f);
DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
	    0x60d0e559, 0x52f8, 0x4fa2,
	    0xbb, 0xce, 0xac, 0xdb, 0x34, 0xa8, 0xec, 0x1);
DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
	    0x58f0aad8, 0x22bf, 0x4f8a,
	    0xbb, 0x3d, 0xd2, 0xc4, 0x97, 0x8c, 0x6e, 0x2f);

#endif

/* These are private defs since some versions of mingw define it, some not */

DEFINE_GUID(PRIVATE_IID_IAMVideoProcAmp,
	    0xc6e13360, 0x30ac, 0x11d0,
	    0xa1, 0x8c, 0x00, 0xa0, 0xc9, 0x11, 0x89, 0x56);
DEFINE_GUID(PRIVATE_IID_IAMCameraControl,
	    0xc6e13370, 0x30ac, 0x11d0,
	    0xa1, 0x8c, 0x00, 0xa0, 0xc9, 0x11, 0x89, 0x56);

#ifndef TCL_THREADS
#error "build requires TCL_THREADS"
#endif

/*
 * RIFF/AVI structures and constants.
 */

static void inline
PUT16LE(unsigned short *p, unsigned short v)
{
    unsigned char b[2];

    b[0] = v & 0xff;
    b[1] = (v >> 8) & 0xff;
    memcpy(p, b, 2);
}

static void inline
PUT32LE(unsigned int *p, unsigned int v)
{
    unsigned char b[4];

    b[0] = v & 0xff;
    b[1] = (v >> 8) & 0xff;
    b[2] = (v >> 16) & 0xff;
    b[3] = (v >> 24) & 0xff;
    memcpy(p, b, 4);
}

struct RIFF_avih {
    unsigned int uspf;    /* us per frame. */
    unsigned int bps;     /* Data rate. */
    unsigned int res0;
    unsigned int flags;
    unsigned int nframes; /* Number of frames. */
    unsigned int res1;
    unsigned int nstreams;
    unsigned int bufsize;
    unsigned int width;
    unsigned int height;
    unsigned int scale;
    unsigned int rate;
    unsigned int start;
    unsigned int length;
};

struct RIFF_strh {
    unsigned char type[4];
    unsigned char handler[4];
    unsigned int flags;
    unsigned int priority;
    unsigned int res0;
    unsigned int scale;
    unsigned int rate;
    unsigned int start;
    unsigned int length;
    unsigned int bufsize;
    unsigned int quality;
    unsigned int samplesize;
};

struct RIFF_strf_vids {
    unsigned int size;
    unsigned int width;
    unsigned int height;
    unsigned short planes;
    unsigned short bits;
    unsigned char compr[4];
    unsigned int image_size;
    unsigned int xpels_meter;
    unsigned int ypels_meter;
    unsigned int num_colors;
    unsigned int imp_colors;
};

struct AVI_HDR {
    unsigned char riff_id[4];
    unsigned int riff_size;
    unsigned char riff_type[4];
    unsigned char hdrl_list_id[4];
    unsigned int hdrl_size;
    unsigned char hdrl_type[4];
    unsigned char avih_id[4];
    unsigned int avih_size;
    struct RIFF_avih avih;
};

struct AVIX_HDR {
    unsigned char riff_id[4];
    unsigned int riff_size;
    unsigned char riff_type[4];
    unsigned char data_list_id[4];
    unsigned int data_size;
    unsigned char data_type[4];
};

struct AVI_HDR_VIDEO {
    unsigned char strl_list_id[4];
    unsigned int strl_size;
    unsigned char strl_type[4];
    unsigned char strh_id[4];
    unsigned int strh_size;
    struct RIFF_strh strh;
    unsigned char strf_id[4];
    unsigned int strf_size;
    struct RIFF_strf_vids strf;
};

struct AVI_HDR_ODML {
    unsigned char strl_list_id[4];
    unsigned int strl_size;
    unsigned char strl_type[4];
    unsigned char strh_id[4];
    unsigned int strh_size;
    unsigned int nframes;
};

struct AVI_DATA {
    unsigned char data_list_id[4];
    unsigned int data_size;
    unsigned char data_type[4];
};

struct CHUNK_HDR {
    unsigned char id[4];
    unsigned int size;
};

struct AVI_IDX {
    unsigned char id[4];
    unsigned int flags;
    unsigned int offset;
    unsigned int size;
};

/*
 * NB: as of Tcl 8.6.5 asynchronous event handlers (Tcl_AsyncCreate())
 * are unreliable since they can break the Tcl catch command.
 * If USE_ASYNC_HANDLER is undefined, Tcl_ThreadQueueEvent() is used
 * instead and runtime behaviour should be almost identical.
 */

#undef USE_ASYNC_HANDLER

/*
 * FourCCs in little-endian.
 */

#define FOURCC_NV12 0x3231564e
#define FOURCC_YUY2 0x32595559
#define FOURCC_MJPG 0x47504a4d
#define FOURCC_RGB0 0x00000000	/* Internally used. */

/*
 * Per process info for dynamically linked WMF DLLs.
 */

static struct {
    int initialized;
    HMODULE mfplat;			/* mfplat.dll */
    HMODULE mf;				/* mf.dll */
    HMODULE mfreadwrite;		/* mfreadwrite.dll */
    HRESULT WINAPI (*startup)(ULONG, DWORD);
    HRESULT WINAPI (*shutdown)(void);
    HRESULT WINAPI (*createattributes)(IMFAttributes **, UINT32);
    HRESULT WINAPI (*enumdevicesources)(IMFAttributes *, IMFActivate ***,
					UINT32 *);
    HRESULT WINAPI (*createsourcereaderfrommediasource)(IMFMediaSource *,
							IMFAttributes *,
							IMFSourceReader **);
    HRESULT WINAPI (*getstrideforbitmapinfoheader)(DWORD, DWORD, LONG *);
} WMFM = { 0, 0, NULL, NULL, NULL };

#define MFStartup           WMFM.startup
#define MFShutdown          WMFM.shutdown
#define MFCreateAttributes  WMFM.createattributes
#define MFEnumDeviceSources WMFM.enumdevicesources
#define MFCreateSourceReaderFromMediaSource \
    WMFM.createsourcereaderfrommediasource
#define MFGetStrideForBitmapInfoHeader \
    WMFM.getstrideforbitmapinfoheader

TCL_DECLARE_MUTEX(wmfMutex)

/*
 * IMFSourceReaderCallback implementation.
 */

typedef struct {
    interface IMFSourceReaderCallback isrcb;	/* Interface, methods. */
    CRITICAL_SECTION lock;			/* Mutex. */
    int refCount;				/* Reference count. */
    int closing;				/* True during close. */
    struct WMFC *wmfc;				/* Pointer to WMF struct. */
} SourceReaderCB;

/*
 * Frame buffer.
 */

typedef struct {
    unsigned char *data;	/* Pixel buffer. */
    int length;			/* Used length of pixel buffer. */
    int maxLength;		/* Max. length of pixel buffer. */
    int ready;			/* Zero when free, positive when filled. */
    int fourcc;			/* Media format code. */
    int width, height;		/* Width and height in pixels. */
    int stride;			/* Line increment in bytes. */
} Frame;

/*
 * Media format.
 */

typedef struct {
    int index;			/* Index for activation. */
    int fourcc;			/* Four character code. */
    int width, height;		/* Width and height in pixels. */
    int stride;			/* Line increment in bytes. */
    UINT64 frameRate;		/* Rate (nominator/denominator). */
    UINT64 frameRateMin;	/* Min. rate (nominator/denominator). */
    UINT64 frameRateMax;	/* Max. rate (nominator/denominator). */
} MediaFmt;

/*
 * Control structure for device control.
 */

typedef struct {
    int code;			/* Selector. */
    int iscam;			/* Camera control, if true. */
    const char *name;		/* Name of control, lower case. */
    long min, max;		/* Minimum/maximum value. */
    long step;			/* Increment value. */
    long def;			/* Default value. */
    long caps;			/* Capabilities. */
} WCTRL;

/*
 * Recording states.
 */

#define REC_STOP	0
#define REC_RECORD	1
#define REC_PAUSE	2
#define REC_ERROR	3

/*
 * Control structure for capture.
 */

typedef struct WMFC {
    IMFMediaSource *mediaSrc;	/* Handle for device. */
    IMFSourceReader *srcReader;	/* Handle for capture. */
    Tcl_Interp *interp;		/* Interpreter for this object. */
    char devId[32];		/* Device id. */
    Tcl_DString devName;	/* Device name. */
    int cbCmdLen;               /* Initial length of callback command. */
    Tcl_DString cbCmd;          /* Callback command prefix. */
    Tcl_TimerToken cbPending;	/* Set when callback dispatched. */
    int fourcc;			/* Media format code. */
    int stride, width, height;	/* Image dimensions. */
    int mirror;			/* Image mirror flags. */
    int rotate;			/* Image rotation in degrees. */
    Tcl_WideInt counters[4];	/* Statistic counters. */
    int useFmt;			/* MediaFmt index to use. */
    int numFmts;		/* Number formats in fmts. */
    MediaFmt *fmts;		/* Array of MediaFmts or NULL. */
#ifdef USE_ASYNC_HANDLER
    Tcl_AsyncHandler async;	/* For signalling captured image. */
#else
    Tcl_ThreadId tid;		/* Thread identifier of interp. */
#endif
    Tcl_HashTable ctrl;		/* Device controls. */
    SourceReaderCB srcb;	/* Callback interface object. */
    int streamEnd;		/* Indicates capture error or EOF. */
    int frameReady;		/* Frame index with data or -1. */
    int frameQueued;		/* Next frame index to be filled. */
    Frame frame[2];		/* Frame buffers. */

    /* Info for recording to channel (file or socket) follows. */

    int rstate;			/* Recording state. */
    int ruser;			/* True, when user writes frames. */
    Tcl_Channel rchan;		/* Recording channel or NULL. */
    Tcl_DString rbdStr;		/* Frame boundary string. */
    int rrate;			/* Recording frame rate. */
    int rtv;			/* Target time for next frame. */
    int ltv;			/* Time of last frame read. */
    struct {
	Tcl_WideInt nframes;
	Tcl_WideInt nframes0;
	Tcl_WideInt totsize;
	Tcl_WideInt segsize;
	Tcl_WideInt segsize0;
	Tcl_WideInt segstart;
	int hdrsize;
	Tcl_WideInt pos0;
	int rate;
	struct AVI_HDR avi_hdr;
	struct AVI_HDR_VIDEO avi_hdrv;
	struct AVI_HDR_ODML avi_hdro;
	struct AVI_DATA avi_data;
	int idx_off;
	int curr_idx, num_idx;
	struct AVI_IDX *idx;
    } avi;			/* AVI file writer. */
} WMFC;

#ifndef USE_ASYNC_HANDLER

typedef struct {
    Tcl_Event hdr;		/* Generic event header. */
    WMFC *wmfc;			/* Pointer to control structure. */
} WMFEVT;

#endif

/*
 * Per interpreter control structure.
 */

typedef struct {
    int idCount;		/* Counter for unique handles. */
    Tcl_HashTable wmfc;		/* List of active WMFC instances. */
    int checkedTk;		/* Non-zero when Tk availability checked. */
} WMFI;

/*
 * Forward declarations.
 */

static int	CheckForTk(WMFI *wmfi, Tcl_Interp *interp);
static void	CloseAVISegment(WMFC *wmfc, int end);
static Frame *	FrameToJPEG(Frame *in, Frame *out);
static int	WriteFrame(WMFC *wmfc, Frame *frame);
static int	StartRecording(WMFC *wmfc, Tcl_Interp *interp,
				       int objc, Tcl_Obj * const objv[]);
static void	WriteAVIHeader(WMFC *wmfc, int end);
static void	FinishRecording(WMFC *wmfc);
static int	RecordFrameFromData(WMFC *wmfc, Tcl_Interp *interp,
				    int objc, Tcl_Obj * const objv[]);
static int	DataToPhoto(WMFI *wmfi, Tcl_Interp *interp,
			    int objc, Tcl_Obj * const objv[]);
#ifdef USE_ASYNC_HANDLER
static int	SignalBuffer(ClientData clientData,
			     Tcl_Interp *interp, int code);
#else
static int	SignalBuffer(Tcl_Event *evPtr, int flags);
#endif
static int	StopCapture(WMFC *wmfc, int closing);


/*
 *-------------------------------------------------------------------------
 *
 * CheckForTk --
 *
 *	Check availability of Tk. Return standard Tcl error
 *	and appropriate error message if unavailable.
 *
 *-------------------------------------------------------------------------
 */

static int
CheckForTk(WMFI *wmfi, Tcl_Interp *interp)
{
    if (wmfi->checkedTk > 0) {
	return TCL_OK;
    } else if (wmfi->checkedTk < 0) {
	Tcl_SetResult(interp, "can't find package Tk", TCL_STATIC);
	return TCL_ERROR;
    }
#ifdef USE_TK_STUBS
    if (Tk_InitStubs(interp, "8.4", 0) == NULL) {
	wmfi->checkedTk = -1;
	return TCL_ERROR;
    }
#else
    if (Tcl_PkgRequire(interp, "Tk", "8.4", 0) == NULL) {
	wmfi->checkedTk = -1;
	return TCL_ERROR;
    }
#endif
    wmfi->checkedTk = 1;
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * CloseAVISegment --
 *
 *	Recording: close current and optionally start next 2G AVI
 *	segment in output file.
 *
 *-------------------------------------------------------------------------
 */

static void
CloseAVISegment(WMFC *wmfc, int end)
{
    int toWrite = 0, written = 0;
    Tcl_WideInt pos;
    struct AVIX_HDR xhdr;
    static const struct AVIX_HDR xhdr0 = {
	{ 'R', 'I', 'F', 'F' },
	0,
	{ 'A', 'V', 'I', 'X' },
	{ 'L', 'I', 'S', 'T' },
	0,
	{ 'm', 'o', 'v', 'i' }
    };

    pos = Tcl_Seek(wmfc->rchan, 0, SEEK_CUR);
    if (wmfc->avi.totsize > wmfc->avi.segsize) {
	Tcl_Seek(wmfc->rchan, wmfc->avi.segstart, SEEK_SET);
	xhdr = xhdr0;
	PUT32LE(&xhdr.riff_size, wmfc->avi.segsize + 16);
	PUT32LE(&xhdr.data_size, wmfc->avi.segsize + 4);
	toWrite = sizeof(xhdr);
	written = Tcl_WriteRaw(wmfc->rchan, (const char *) &xhdr, toWrite);
	Tcl_Seek(wmfc->rchan, pos, SEEK_SET);
    } else {
	wmfc->avi.nframes0 = wmfc->avi.nframes;
	wmfc->avi.segsize0 = wmfc->avi.segsize;
	WriteAVIHeader(wmfc, 0);
	if (wmfc->rstate == REC_ERROR) {
	    return;
	}
    }
    wmfc->avi.segsize = 0;
    wmfc->avi.segstart = pos;
    if (!end && (written == toWrite)) {
	xhdr = xhdr0;
	toWrite = sizeof(xhdr);
	written = Tcl_WriteRaw(wmfc->rchan, (const char *) &xhdr, toWrite);
    }
    if (written != toWrite) {
	wmfc->rstate = REC_ERROR;
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * FrameToJPEG --
 *
 *	Convert frame to JPEG. Input frame must not be JPEG yet.
 *	Returns allocated and populated new frame or NULL on error.
 *
 *-------------------------------------------------------------------------
 */

struct error_mgr {
    struct jpeg_error_mgr super;
    jmp_buf jmp;
};

static void
_output_message(j_common_ptr info)
{
}

static void
_format_message(j_common_ptr info, char *buffer)
{
    buffer[0] = '\0';
}

static void
_error_exit(j_common_ptr info)
{
    struct error_mgr *jerr = (struct error_mgr *) info->err;

    (*info->err->output_message)(info);
    longjmp(jerr->jmp, 1);
}

struct _compr {
    struct jpeg_compress_struct cinfo;
    struct jpeg_destination_mgr dmgr;
    Frame *out;
};

static void
_dst_init(j_compress_ptr cinfo)
{
    struct _compr *c = (struct _compr *) cinfo;

    c->dmgr.next_output_byte = c->out->data;
    c->dmgr.free_in_buffer = c->out->maxLength;
}

static boolean
_dst_empty(j_compress_ptr cinfo)
{
    return TRUE;
}

static void
_dst_term(j_compress_ptr cinfo)
{
    struct _compr *c = (struct _compr *) cinfo;

    c->out->length = c->dmgr.next_output_byte - (JOCTET *) c->out->data;
}

static Frame *
FrameToJPEG(Frame *in, Frame *out)
{
    Frame tmpFrame;
    struct _compr cinfo;
    struct error_mgr jerr;

    if (in->fourcc == FOURCC_MJPG) {
	return NULL;
    }
    tmpFrame.data = NULL;
    if (in->fourcc == FOURCC_NV12) {
	unsigned char *yPtr, *uvPtr, *dstPtr;
	int x, y, yVal, uVal = 0, vVal = 0;

	/* convert to RGB first */
	tmpFrame = *in;
	tmpFrame.length = in->width * in->height * 3;
	tmpFrame.maxLength = tmpFrame.length;
	tmpFrame.stride = in->width * 3;
	tmpFrame.fourcc = FOURCC_RGB0;
	tmpFrame.data = attemptckalloc(tmpFrame.length);
	if (tmpFrame.data == NULL) {
	    return NULL;
	}
	for (y = 0; y < in->height; y++) {
	    yPtr = in->data + y * in->stride;
	    uvPtr = in->data + y * in->stride * in->height
		  + (y >> 1) * in->stride;
	    dstPtr = tmpFrame.data + y * tmpFrame.stride;
	    for (x = 0; x < in->width; x++) {
		int red, green, blue;

		yVal = *yPtr++;
		yVal -= 16;
		if (yVal < 0) {
		    yVal = 0;
		}
		if ((x & 1) == 0) {
		    uVal = *uvPtr++ - 128;
		    vVal = *uvPtr++ - 128;
		}
		yVal *= 1192;
		red = yVal + 1634 * vVal;
		green = yVal - 833 * vVal - 400 * uVal;
		blue = yVal + 2066 * uVal;
		if (red < 0) {
		    red = 0;
		} else if (red > 0x3ffff) {
		    red = 0x3ffff;
		}
		*dstPtr++ = red >> 10;
		if (green < 0) {
		    green = 0;
		} else if (green > 0x3ffff) {
		    green = 0x3ffff;
		}
		*dstPtr++ = green >> 10;
		if (blue < 0) {
		    blue = 0;
		} else if (blue > 0x3ffff) {
		    blue = 0x3ffff;
		}
		*dstPtr++ = blue >> 10;
	    }
	}
	in = &tmpFrame;
    } else if (in->fourcc == FOURCC_YUY2) {
	unsigned char *yPtr, *dstPtr;
	int x, y, yVal, yVal2, uVal = 0, vVal = 0;

	/* convert to RGB first */
	tmpFrame = *in;
	tmpFrame.length = in->width * in->height * 3;
	tmpFrame.maxLength = tmpFrame.length;
	tmpFrame.stride = in->width * 3;
	tmpFrame.fourcc = FOURCC_RGB0;
	tmpFrame.data = attemptckalloc(tmpFrame.length);
	if (tmpFrame.data == NULL) {
	    return NULL;
	}
	for (y = 0; y < in->height; y++) {
	    yPtr = in->data + y * in->stride;
	    dstPtr = tmpFrame.data + y * tmpFrame.stride;
	    for (x = 0; x < in->width; x++) {
		int red, green, blue;

		yVal = *yPtr++;
		yVal -= 16;
		if (yVal < 0) {
		    yVal = 0;
		}
		uVal = *yPtr++ - 128;
		yVal2 = *yPtr++;
		yVal2 -= 16;
		if (yVal2 < 0) {
		    yVal2 = 0;
		}
		vVal = *yPtr++ - 128;
		red = 298 * yVal + 409 * vVal + 128;
		green = 298 * yVal - 100 * uVal - 208 * vVal + 128;
		blue = 298 * yVal + 516 * uVal + 128;
		if (red < 0) {
		    red = 0;
		} else if (red > 0xffff) {
		    red = 0xffff;
		}
		*dstPtr++ = red >> 8;
		if (green < 0) {
		    green = 0;
		} else if (green > 0xffff) {
		    green = 0xffff;
		}
		*dstPtr++ = green >> 8;
		if (blue < 0) {
		    blue = 0;
		} else if (blue > 0xffff) {
		    blue = 0xffff;
		}
		*dstPtr++ = blue >> 8;
		red = 298 * yVal2 + 409 * vVal + 128;
		green = 298 * yVal2 - 100 * uVal - 208 * vVal + 128;
		blue = 298 * yVal2 + 516 * uVal + 128;
		if (red < 0) {
		    red = 0;
		} else if (red > 0xffff) {
		    red = 0xffff;
		}
		*dstPtr++ = red >> 8;
		if (green < 0) {
		    green = 0;
		} else if (green > 0xffff) {
		    green = 0xffff;
		}
		*dstPtr++ = green >> 8;
		if (blue < 0) {
		    blue = 0;
		} else if (blue > 0xffff) {
		    blue = 0xffff;
		}
		*dstPtr++ = blue >> 8;
	    }
	}
	in = &tmpFrame;
    } else if (in->fourcc != FOURCC_RGB0) {
	return NULL;
    }

    out->ready = 1;
    out->fourcc = FOURCC_MJPG;
    out->width = in->width;
    out->height = in->height;
    out->stride = 0;
    out->maxLength = out->width * out->height;
    out->data = attemptckalloc(out->maxLength);
    if (out->data == NULL) {
	if (tmpFrame.data != NULL) {
	    ckfree(tmpFrame.data);
	}
	return NULL;
    }

    cinfo.out = out;
    cinfo.dmgr.init_destination = _dst_init;
    cinfo.dmgr.empty_output_buffer = _dst_empty;
    cinfo.dmgr.term_destination = _dst_term;
    cinfo.cinfo.err = jpeg_std_error(&jerr.super);
    jerr.super.output_message = _output_message;
    jerr.super.format_message = _format_message;
    jerr.super.error_exit = _error_exit;

    if (setjmp(jerr.jmp)) {
	ckfree(out->data);
	if (tmpFrame.data != NULL) {
	    ckfree(tmpFrame.data);
	}
	return NULL;
    }

    jpeg_create_compress(&cinfo.cinfo);
    cinfo.cinfo.in_color_space = JCS_RGB;
    cinfo.cinfo.image_width = out->width;
    cinfo.cinfo.image_height = out->height;
    cinfo.cinfo.input_components = 3;
    jpeg_set_defaults(&cinfo.cinfo);
    cinfo.cinfo.dest = &cinfo.dmgr;
    jpeg_start_compress(&cinfo.cinfo, TRUE);

    while (cinfo.cinfo.next_scanline < cinfo.cinfo.image_height) {
	JSAMPROW row_pointer[1];

	row_pointer[0] = in->data + cinfo.cinfo.next_scanline * in->stride;
	jpeg_write_scanlines(&cinfo.cinfo, row_pointer, 1);
    }

    jpeg_finish_compress(&cinfo.cinfo);
    jpeg_destroy_compress(&cinfo.cinfo);

    if (tmpFrame.data != NULL) {
	ckfree(tmpFrame.data);
    }
    return out;
}

/*
 *-------------------------------------------------------------------------
 *
 * WriteFrame --
 *
 *	Recording: write given frame onto recording output channel.
 *	The recording frame rate may differ from the hardware frame
 *	rate. Thus, some time calculation takes place here to write
 *	a frame when time is due to the configured recording frame
 *	rate. The result is 1 if a frame was written, 0 if skipped
 *	due to timing constraints, or -1 on write error.
 *
 *-------------------------------------------------------------------------
 */

static int
WriteFrame(WMFC *wmfc, Frame *frame)
{
    int n, toWrite, written, fWritten;
    char buffer[256];
    int now, diff;
    Frame *newFrame = NULL, fBuf[1];

    if (wmfc->rchan == NULL) {
	wmfc->rstate = REC_ERROR;
    }
    now = (int) GetTickCount();
    diff = wmfc->rtv - now;
    if (diff > 0) {
	return (wmfc->rstate == REC_ERROR) ? -1 : 0;
    }
    wmfc->rtv = now;
    diff = wmfc->rtv - wmfc->ltv;
    wmfc->ltv = wmfc->rtv;

    wmfc->rtv += wmfc->rrate;
    if (frame->length == 0) {
	return 0;
    }
    if (wmfc->rstate == REC_ERROR) {
	return -1;
    }
    if (Tcl_DStringLength(&wmfc->rbdStr) > 0) {
	/*
	 * HTTP MJPEG streaming webcam mode.
	 */
	if (frame->fourcc != FOURCC_MJPG) {
	    newFrame = FrameToJPEG(frame, fBuf);
	    if (newFrame == NULL) {
		wmfc->rstate = REC_ERROR;
		return -1;
	    }
	    frame = newFrame;
	}
	n = Tcl_DStringLength(&wmfc->rbdStr);
	sprintf(buffer, "\r\nContent-type: image/jpeg\r\n"
		"Content-length: %d\r\n\r\n", (int) frame->length);
	Tcl_DStringAppend(&wmfc->rbdStr, buffer, -1);
	toWrite = Tcl_DStringLength(&wmfc->rbdStr);
	written = Tcl_WriteRaw(wmfc->rchan, Tcl_DStringValue(&wmfc->rbdStr),
			       toWrite);
	Tcl_DStringSetLength(&wmfc->rbdStr, n);
	if (written == toWrite) {
	    toWrite = frame->length;
	    written = Tcl_WriteRaw(wmfc->rchan, (const char *) frame->data,
				   toWrite);
	}
    } else {
	/*
	 * AVI file.
	 */
	int size, sizea;
	struct CHUNK_HDR hdr;
	static const struct CHUNK_HDR hdr0 = {
	    { '0', '0', 'd', 'b' },
	    0
	};

	if (frame->fourcc == FOURCC_MJPG) {
	    size = frame->length;
	} else if (memcmp(&wmfc->avi.avi_hdrv.strh.handler, "MJPG", 4) == 0) {
	    newFrame = FrameToJPEG(frame, fBuf);
	    if (newFrame == NULL) {
		wmfc->rstate = REC_ERROR;
		return -1;
	    }
	    frame = newFrame;
	    size = frame->length;
	} else {
	    size = frame->height * frame->stride;
	}
	sizea = (size + 3) & ~3;
	hdr = hdr0;
	PUT32LE(&hdr.size, sizea);
	fWritten = 0;
	toWrite = sizeof(hdr);
	written = Tcl_WriteRaw(wmfc->rchan, (const char *) &hdr, toWrite);
	if (written == toWrite) {
	    toWrite = size;
	    written = Tcl_WriteRaw(wmfc->rchan, (const char *) frame->data,
				   toWrite);
	    fWritten = written;
	}

	/* Align to next 32 bit boundary. */
	if ((written == toWrite) && (sizea > size)) {
	    static const char four0[4] = {
		0, 0, 0, 0
	    };

	    toWrite = sizea - size;
	    written = Tcl_WriteRaw(wmfc->rchan, four0, toWrite);
	}

	wmfc->avi.nframes++;
	wmfc->avi.totsize += sizea + sizeof(hdr);
	wmfc->avi.segsize += sizea + sizeof(hdr);

	if (fWritten == size) {
	    if (wmfc->avi.segsize > 0x7F000000) {
		CloseAVISegment(wmfc, 0);
		wmfc->avi.curr_idx = wmfc->avi.num_idx = 0;
		if (wmfc->avi.idx != NULL) {
		    ckfree((char *) wmfc->avi.idx);
		    wmfc->avi.idx = NULL;
		}
	    } else if (wmfc->avi.totsize == wmfc->avi.segsize) {
		/* Add index entry. */
		if (wmfc->avi.curr_idx >= wmfc->avi.num_idx) {
		    int newsize = wmfc->avi.num_idx + 512;
		    struct AVI_IDX *newidx;

		    newidx = attemptckrealloc((char *) wmfc->avi.idx,
					      newsize * sizeof(struct AVI_IDX));
		    if (newidx == NULL) {
			wmfc->avi.curr_idx = wmfc->avi.num_idx = 0;
			if (wmfc->avi.idx != NULL) {
			    ckfree((char *) wmfc->avi.idx);
			    wmfc->avi.idx = NULL;
			}
		    } else {
			wmfc->avi.num_idx = newsize;
			wmfc->avi.idx = newidx;
		    }
		}
		if (wmfc->avi.idx != NULL) {
		    struct AVI_IDX *idx = wmfc->avi.idx + wmfc->avi.curr_idx;

		    memcpy(idx->id, hdr0.id, sizeof(hdr0.id));
		    PUT32LE(&idx->flags, 0);
		    PUT32LE(&idx->offset, wmfc->avi.idx_off);
		    PUT32LE(&idx->size, sizea);
		    wmfc->avi.curr_idx++;
		    wmfc->avi.idx_off += sizea + sizeof(struct CHUNK_HDR);
		}
	    }
	}

	/* Compute average frame rate. */
	if (wmfc->avi.nframes == 0) {
	    wmfc->avi.rate = diff;
	} else {
	    wmfc->avi.rate += diff;
	    wmfc->avi.rate /= 2;
	}
    }
    if (written != toWrite) {
	wmfc->rstate = REC_ERROR;
    }
    if (newFrame != NULL) {
	ckfree(newFrame->data);
    }
    return (wmfc->rstate == REC_ERROR) ? -1 : 1;
}

/*
 *-------------------------------------------------------------------------
 *
 * StartRecording --
 *
 *	Prepare recording from given parameters (channel, mode, etc.)
 *	The channel used for writing frames is detached from the
 *	calling interpreter. Timing computations for adapting
 *	the recording frame rate are performed.
 *
 *-------------------------------------------------------------------------
 */

static int
StartRecording(WMFC *wmfc, Tcl_Interp *interp,
	       int objc, Tcl_Obj * const objv[])
{
    int i, mode, doMJPG = 0, doUser = 0;
    double rate = 0, ratef;
    const char *p, *rbdStr = NULL;
    Tcl_Channel chan = NULL, stack[2];
    Tcl_WideInt pos0 = 0;

    if (objc < 5) {
	Tcl_WrongNumArgs(interp, 2, objv, "devid start ...");
	return TCL_ERROR;
    }
    for (i = 4; i < objc; i++) {
	p = Tcl_GetString(objv[i]);
	if (strcmp(p, "-mjpeg") == 0) {
	    doMJPG++;
	} else if (strcmp(p, "-user") == 0) {
	    doMJPG++;
	    doUser++;
	} else if (strcmp(p, "-fps") == 0) {
	    if (++i >= objc) {
		Tcl_SetResult(interp, "-fps option needs a value",
			      TCL_STATIC);
		return TCL_ERROR;
	    }
	    if (Tcl_GetDoubleFromObj(interp, objv[i], &rate) != TCL_OK) {
		return TCL_ERROR;
	    }
	} else if (strcmp(p, "-boundary") == 0) {
	    if (++i >= objc) {
		Tcl_SetResult(interp, "-boundary option needs a value",
			      TCL_STATIC);
		return TCL_ERROR;
	    }
	    rbdStr = Tcl_GetString(objv[i]);
	} else if (strcmp(p, "-chan") == 0) {
	    if (++i >= objc) {
		Tcl_SetResult(interp, "-chan option needs a value",
			      TCL_STATIC);
		return TCL_ERROR;
	    }
	    chan = Tcl_GetChannel(interp, Tcl_GetString(objv[i]), &mode);
	    if (chan == NULL) {
		return TCL_ERROR;
	    }
	    if ((mode & TCL_WRITABLE) == 0) {
		Tcl_SetResult(interp, "channel is not writable",
			      TCL_STATIC);
		return TCL_ERROR;
	    }
	}
    }
    if (chan == NULL) {
	Tcl_SetResult(interp, "no channel given", TCL_STATIC);
	return TCL_ERROR;
    }
    stack[0] = Tcl_GetTopChannel(chan);
    stack[1] = Tcl_GetStackedChannel(chan);
    if (((stack[0] != NULL) && (stack[0] != chan)) || (stack[1] != NULL)) {
	Tcl_SetResult(interp, "stacked channels are not supported", TCL_STATIC);
	return TCL_ERROR;
    }
    if ((Tcl_SetChannelOption(interp, chan, "-blocking", "0") != TCL_OK) ||
	(Tcl_SetChannelOption(interp, chan, "-buffering", "none") != TCL_OK) ||
	(Tcl_SetChannelOption(interp, chan, "-translation", "binary")
	 != TCL_OK)) {
	return TCL_ERROR;
    }
    if ((rbdStr == NULL) || (strlen(rbdStr) == 0)) {
	pos0 = Tcl_Seek(chan, 0, SEEK_CUR);
	if (pos0 == (Tcl_WideInt) -1) {
	    Tcl_SetResult(interp, "not a random access channel", TCL_STATIC);
	    return TCL_ERROR;
	}
    }
    if (Tcl_DetachChannel(interp, chan) != TCL_OK) {
	Tcl_SetResult(interp, "cannot detach channel", TCL_STATIC);
	return TCL_ERROR;
    }
    FinishRecording(wmfc);
    wmfc->rchan = chan;
    ratef = (wmfc->fmts[wmfc->useFmt].frameRate >> 32);
    ratef /= (wmfc->fmts[wmfc->useFmt].frameRate & 0xffffffff);
    if ((rate > 0.0) && (rate < ratef)) {
	wmfc->rrate = rate * 1000;
    } else if (ratef <= 0) {
	wmfc->rrate = 1000;
    } else {
	wmfc->rrate = 1000 / ratef;
    }
    if ((rbdStr != NULL) && (strlen(rbdStr) > 0)) {
	Tcl_DStringAppend(&wmfc->rbdStr, rbdStr, -1);
    } else {
	int n;
	static const struct AVI_HDR avi_hdr = {
	    { 'R', 'I', 'F', 'F' },
	    0,
	    { 'A', 'V', 'I', ' ' },
	    { 'L', 'I', 'S', 'T' },
	    0,
	    { 'h', 'd', 'r', 'l' },
	    { 'a', 'v', 'i', 'h' },
	    0,
	    { }
	};
	static const struct AVI_HDR_VIDEO avi_hdrv = {
	    { 'L', 'I', 'S', 'T' },
	    0,
	    { 's', 't', 'r', 'l' },
	    { 's', 't', 'r', 'h' },
	    0,
	    {
		{ 'v', 'i', 'd', 's' }
	    },
	    { 's', 't', 'r', 'f' },
	    0,
	    { }
	};
	static const struct AVI_HDR_ODML avi_hdro = {
	    { 'L', 'I', 'S', 'T' },
	    0,
	    { 'o', 'd', 'm', 'l' },
	    { 'd', 'm', 'l', 'h' },
	    0,
	    0
	};
	static const struct AVI_DATA avi_data = {
	    { 'L', 'I', 'S', 'T' },
	    0,
	    { 'm', 'o', 'v', 'i' },
	};

	/* Setup AVI writer. */
	wmfc->avi.pos0 = pos0;
	wmfc->avi.avi_hdr = avi_hdr;
	PUT32LE(&wmfc->avi.avi_hdr.avih_size,
		sizeof(struct RIFF_avih));
	wmfc->avi.avi_hdrv = avi_hdrv;
	PUT32LE(&wmfc->avi.avi_hdrv.strl_size,
		sizeof(struct RIFF_strh) +
		sizeof(struct RIFF_strf_vids) + 20);
	PUT32LE(&wmfc->avi.avi_hdrv.strh_size,
		sizeof(struct RIFF_strh));
	PUT32LE(&wmfc->avi.avi_hdrv.strf_size,
		sizeof(struct RIFF_strf_vids));
	wmfc->avi.avi_hdro = avi_hdro;
	PUT32LE(&wmfc->avi.avi_hdro.strl_size,
		sizeof(unsigned int) + 12);
	PUT32LE(&wmfc->avi.avi_hdro.strh_size,
		sizeof(unsigned int));
	wmfc->avi.avi_data = avi_data;

	PUT32LE(&wmfc->avi.avi_hdr.avih.width, wmfc->width);
	PUT32LE(&wmfc->avi.avi_hdr.avih.height, wmfc->height);
	n = wmfc->rrate * 1000;
	PUT32LE(&wmfc->avi.avi_hdr.avih.uspf, n);
	n = 24 * n / 1000;
	n = n * wmfc->width * wmfc->height;
	PUT32LE(&wmfc->avi.avi_hdr.avih.bps, n);
	PUT32LE(&wmfc->avi.avi_hdr.avih.nstreams, 1);
	wmfc->avi.hdrsize = Tcl_WriteRaw(wmfc->rchan,
					 (const char *) &wmfc->avi.avi_hdr,
					 sizeof(wmfc->avi.avi_hdr));
	if (doMJPG) {
	    memcpy(&wmfc->avi.avi_hdrv.strh.handler, "MJPG", 4);
	    memcpy(&wmfc->avi.avi_hdrv.strf.compr, "MJPG", 4);
	} else {
	    memcpy(&wmfc->avi.avi_hdrv.strh.handler, &wmfc->fourcc, 4);
	    memcpy(&wmfc->avi.avi_hdrv.strf.compr, &wmfc->fourcc, 4);
	}
	n = wmfc->rrate * 1000;
	PUT32LE(&wmfc->avi.avi_hdrv.strh.scale, n);
	PUT32LE(&wmfc->avi.avi_hdrv.strh.rate, 1000000);
	PUT32LE(&wmfc->avi.avi_hdrv.strf.size, sizeof(wmfc->avi.avi_hdrv.strf));
	PUT32LE(&wmfc->avi.avi_hdrv.strf.width, wmfc->width);
	PUT32LE(&wmfc->avi.avi_hdrv.strf.height, wmfc->height);
	PUT16LE(&wmfc->avi.avi_hdrv.strf.planes, 1);
	PUT16LE(&wmfc->avi.avi_hdrv.strf.bits, 24);
	n = 3 * wmfc->width * wmfc->height;
	PUT32LE(&wmfc->avi.avi_hdrv.strf.image_size, n);
	wmfc->avi.hdrsize += Tcl_WriteRaw(wmfc->rchan,
					  (const char *) &wmfc->avi.avi_hdrv,
					  sizeof(wmfc->avi.avi_hdrv));
	wmfc->avi.hdrsize += Tcl_WriteRaw(wmfc->rchan,
					  (const char *) &wmfc->avi.avi_hdro,
					  sizeof(wmfc->avi.avi_hdro));
	Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_data,
		     sizeof(wmfc->avi.avi_data));
	wmfc->avi.segsize0 = 4;
	WriteAVIHeader(wmfc, 0);
	wmfc->avi.curr_idx = wmfc->avi.num_idx = 0;
	wmfc->avi.idx_off = 4;
	if (wmfc->avi.idx != NULL) {
	    ckfree((char *) wmfc->avi.idx);
	    wmfc->avi.idx = NULL;
	}
    }
    /* Reserve 1ms for processing. */
    wmfc->rrate -= 1;
    wmfc->ltv = (int) GetTickCount();
    wmfc->rtv = wmfc->ltv;
    wmfc->ruser = doUser ? 1 : 0;
#ifdef USE_ASYNC_HANDLER
    wmfc->rstate = (wmfc->async != NULL) ? REC_RECORD : REC_PAUSE;
#else
    wmfc->rstate = (wmfc->tid != NULL) ? REC_RECORD : REC_PAUSE;
#endif
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * WriteAVIHeader --
 *
 *	(Re)write AVI file header with current chunk sizes at
 *	the very begin of the AVI file.
 *
 *-------------------------------------------------------------------------
 */

static void
WriteAVIHeader(WMFC *wmfc, int end)
{
    int size, idx_size;
    Tcl_WideInt pos;

    if (end && (wmfc->avi.idx != NULL)) {
	/* Write index. */
	struct CHUNK_HDR idxh;
	static const struct CHUNK_HDR idxh0 = {
	    { 'i', 'd', 'x', '1' },
	    0
	};

	idxh = idxh0;
	idx_size = wmfc->avi.curr_idx * sizeof(struct AVI_IDX);
	PUT32LE(&idxh.size, idx_size);
	Tcl_WriteRaw(wmfc->rchan, (const char *) &idxh, sizeof(idxh));
	Tcl_WriteRaw(wmfc->rchan, (const char *) wmfc->avi.idx, idx_size);

	/* Mark index present. */
	PUT32LE(&wmfc->avi.avi_hdr.avih.flags, 0x10);
	idx_size += sizeof(struct CHUNK_HDR);
    } else {
	/* Mark index absent. */
	PUT32LE(&wmfc->avi.avi_hdr.avih.flags, 0);
	idx_size = 0;
    }

    /* For MJPG use computed average frame rate. */
    if (memcmp(&wmfc->avi.avi_hdrv.strh.handler, "MJPG", 4) == 0) {
	int n;

	n = wmfc->avi.rate * 1000;
	PUT32LE(&wmfc->avi.avi_hdr.avih.uspf, n);
	PUT32LE(&wmfc->avi.avi_hdrv.strh.scale, n);
    }
    size = wmfc->avi.hdrsize + wmfc->avi.segsize0;
    PUT32LE(&wmfc->avi.avi_hdr.riff_size, size + idx_size);
    size = wmfc->avi.hdrsize - 20;
    PUT32LE(&wmfc->avi.avi_hdr.hdrl_size, size);
    size = wmfc->avi.nframes0;
    PUT32LE(&wmfc->avi.avi_hdr.avih.nframes, size);
    PUT32LE(&wmfc->avi.avi_hdrv.strh.length, size);
    size = wmfc->avi.segsize0 + 4;
    PUT32LE(&wmfc->avi.avi_data.data_size, size);
    size = wmfc->avi.nframes;
    PUT32LE(&wmfc->avi.avi_hdro.nframes, size);

    pos = Tcl_Seek(wmfc->rchan, 0, SEEK_CUR);
    Tcl_Seek(wmfc->rchan, wmfc->avi.pos0, SEEK_SET);
    Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdr,
		 sizeof(wmfc->avi.avi_hdr));
    Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdrv,
		 sizeof(wmfc->avi.avi_hdrv));
    Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdro,
		 sizeof(wmfc->avi.avi_hdro));
    Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_data,
		 sizeof(wmfc->avi.avi_data));
    if (Tcl_Seek(wmfc->rchan, pos, SEEK_SET) == (Tcl_WideInt) -1) {
	wmfc->rstate = REC_ERROR;
    }

    if (end) {
	wmfc->avi.curr_idx = wmfc->avi.num_idx = 0;
	if (wmfc->avi.idx != NULL) {
	    ckfree((char *) wmfc->avi.idx);
	    wmfc->avi.idx = NULL;
	}
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * FinishRecording --
 *
 *	Close recording channel and release resources.
 *
 *-------------------------------------------------------------------------
 */

static void
FinishRecording(WMFC *wmfc)
{
    if ((wmfc->rchan != NULL) &&
	(Tcl_DStringLength(&wmfc->rbdStr) == 0)) {
	CloseAVISegment(wmfc, 1);
	WriteAVIHeader(wmfc, 1);
    }
    Tcl_DStringFree(&wmfc->rbdStr);
    if (wmfc->rchan != NULL) {
	Tcl_Close(NULL, wmfc->rchan);
	wmfc->rchan = NULL;
	memset(&wmfc->avi, 0, sizeof(wmfc->avi));
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * RecordFrameFromData --
 *
 *	Write single frame from byte array data.
 *
 *-------------------------------------------------------------------------
 */

static int
RecordFrameFromData(WMFC *wmfc, Tcl_Interp *interp,
		    int objc, Tcl_Obj * const objv[])
{
    int width, height, bpp, length, ret;
    unsigned char *data;
    Frame frame;

    if (objc != 8) {
	Tcl_WrongNumArgs(interp, 2, objv,
			 "devid width height bpp bytearray");
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[4], &width) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[5], &height) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[6], &bpp) != TCL_OK) {
	return TCL_ERROR;
    }
    data = Tcl_GetByteArrayFromObj(objv[7], &length);
    if ((length < width * height * bpp) || (bpp != 3) ||
	(width != wmfc->width) || (height != wmfc->height)) {
	Tcl_SetResult(interp, "incompatible frame data", TCL_STATIC);
	return TCL_ERROR;
    }
    if (!wmfc->ruser ||
	((wmfc->rstate != REC_RECORD) && (wmfc->rstate != REC_PAUSE))) {
	Tcl_SetResult(interp, "wrong recording state for frame",
		      TCL_STATIC);
	return TCL_ERROR;
    }

    frame.data = data;
    frame.length = length;
    frame.maxLength = length;
    frame.width = width;
    frame.height = height;
    frame.ready = 1;
    frame.fourcc = FOURCC_RGB0;
    frame.stride = width * bpp;
    ret = WriteFrame(wmfc, &frame);
    Tcl_SetObjResult(interp, Tcl_NewIntObj(ret));
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * DataToPhoto --
 *
 *	Put byte array data to a Tk photo image.
 *
 *-------------------------------------------------------------------------
 */

static int
DataToPhoto(WMFI *wmfi, Tcl_Interp *interp,
	    int objc, Tcl_Obj * const objv[])
{
    int width, height, bpp, length;
    int rot = 0, mirx = 0, miry = 0, mirror;
    unsigned char *data;
    Tk_PhotoHandle photo;
    char *name;
    Tk_PhotoImageBlock block;

    if (CheckForTk(wmfi, interp) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((objc < 7) || (objc > 10)) {
	Tcl_WrongNumArgs(interp, 2, objv,
			 "photo width height bpp bytearray "
			 "?rotation mirrorx mirrory?");
	return TCL_ERROR;
    }
    if (Tk_MainWindow(interp) == NULL) {
	Tcl_SetResult(interp, "application has been destroyed",
		      TCL_STATIC);
	return TCL_ERROR;
    }
    name = Tcl_GetString(objv[2]);
    photo = Tk_FindPhoto(interp, name);
    if (photo == NULL) {
	Tcl_SetObjResult(interp,
	    Tcl_ObjPrintf("can't use \"%s\": not a photo image", name));
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[3], &width) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[4], &height) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[5], &bpp) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((objc > 7) && (Tcl_GetIntFromObj(interp, objv[7], &rot) != TCL_OK)) {
	return TCL_ERROR;
    }
    if ((objc > 8) &&
        (Tcl_GetBooleanFromObj(interp, objv[8], &mirx) != TCL_OK)) {
	return TCL_ERROR;
    }
    if ((objc > 9) &&
        (Tcl_GetBooleanFromObj(interp, objv[9], &miry) != TCL_OK)) {
	return TCL_ERROR;
    }
    data = Tcl_GetByteArrayFromObj(objv[6], &length);
    if ((length < width * height * bpp) ||
	((bpp != 1) && (bpp != 3))) {
	Tcl_SetResult(interp, "unsupported data format", TCL_STATIC);
	return TCL_ERROR;
    }
    if (bpp == 1) {
	block.pixelSize = 1;
	block.offset[0] = 0;
	block.offset[1] = 0;
	block.offset[2] = 0;
	block.offset[3] = 1;
    } else {
	block.pixelSize = 3;
	block.offset[0] = 0;
	block.offset[1] = 1;
	block.offset[2] = 2;
	block.offset[3] = 4;
    }
    block.width = width;
    block.height = height;
    block.pitch = width * bpp;
    block.pixelPtr = data;
    mirror = (mirx ? 1 : 0) | (miry ? 2 : 0);
    rot = rot % 360;
    if (rot < 45) {
	rot = 0;
    } else if (rot < 135) {
	rot = 90;
    } else if (rot < 225) {
	rot = 180;
    } else if (rot < 315) {
	rot = 270;
    } else {
	rot = 0;
    }
    if ((mirror & 3) == 3) {
	rot = (rot + 180) % 360;
    }
    switch (rot) {
    case 270:	/* = 90 CW */
	block.pitch = block.pixelSize;
	block.pixelPtr += width * block.pixelSize * (height - 1);
	block.pixelSize *= -width;
	block.offset[3] = block.pixelSize + 1;	/* no alpha */
	block.width = height;
	block.height = width;
	break;
    case 180:	/* = 180 CW */
	block.pitch = -block.pitch;
	block.pixelPtr += (width * height - 1) * block.pixelSize;
	block.pixelSize = -block.pixelSize;
	block.offset[3] = block.pixelSize + 1;	/* no alpha */
	break;
    case 90:	/* = 270 CW */
	block.pitch = -block.pixelSize;
	block.pixelPtr += (width - 1) * block.pixelSize;
	block.pixelSize *= width;
	block.offset[3] = block.pixelSize + 1;	/* no alpha */
	block.width = height;
	block.height = width;
	break;
    }
    if ((mirror & 3) == 2) {
	/* mirror in X */
	block.pixelPtr += (block.width - 1) * block.pixelSize;
	block.pixelSize = -block.pixelSize;
	block.offset[3] = block.pixelSize + 1;      /* no alpha */
    }
    if ((mirror & 3) == 1) {
	/* mirror in Y */
	block.pixelPtr += block.pitch * (block.height - 1);
	block.pitch = -block.pitch;
    }
    if (Tk_PhotoExpand(interp, photo, block.width, block.height) != TCL_OK) {
	return TCL_ERROR;
    }
    if (Tk_PhotoPutBlock(interp, photo, &block, 0, 0, block.width,
			 block.height, TK_PHOTO_COMPOSITE_SET) != TCL_OK) {
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * EnumDevices --
 *
 *	Fill array of IMFActivate objects with info about
 *	devices capable of video capture. Returns number of
 *	objects in returned array.
 *
 *-------------------------------------------------------------------------
 */

static int
EnumDevices(IMFActivate ***devOut)
{
    IMFAttributes *attr = NULL;
    HRESULT hr;
    UINT32 count = 0;

    *devOut = NULL;
    hr = MFCreateAttributes(&attr, 1);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = attr->lpVtbl->SetGUID(attr,
		       &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
		       &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
    if (!SUCCEEDED(hr))	{
	goto done;
    }
    hr = MFEnumDeviceSources(attr, devOut, &count);
    if (!SUCCEEDED(hr))	{
	count = 0;
	goto done;
    }
done:
    if (attr != NULL) {
	attr->lpVtbl->Release(attr);
    }
    return count;
}

/*
 *-------------------------------------------------------------------------
 *
 * ListDevices --
 *
 *	Fill interp's result with a list with two elements
 *	per device: symbolic link name and friendly name.
 *
 *-------------------------------------------------------------------------
 */

static int
ListDevices(Tcl_Interp *interp)
{
    IMFActivate **devList = NULL, *dev;
    int i, count;
    HRESULT hr;
    Tcl_DString ds;
    Tcl_Obj *list = Tcl_NewListObj(0, NULL);

    Tcl_DStringInit(&ds);
    count = EnumDevices(&devList);
    for (i = 0; i < count; i++) {
	WCHAR *link = NULL, *name = NULL;

	dev = devList[i];
	hr = dev->lpVtbl->GetAllocatedString(dev,
		&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
		&link, NULL);
	if (!SUCCEEDED(hr)) {
	    CoTaskMemFree(link);
	    continue;
	}
	hr = dev->lpVtbl->GetAllocatedString(dev,
		&MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME,
		&name, NULL);
	if (SUCCEEDED(hr)) {
	    Tcl_WinTCharToUtf((TCHAR *) link, -1, &ds);
	    Tcl_ListObjAppendElement(NULL, list,
				     Tcl_NewStringObj(Tcl_DStringValue(&ds),
						      Tcl_DStringLength(&ds)));
	    Tcl_DStringFree(&ds);
	    Tcl_WinTCharToUtf((TCHAR *) name, -1, &ds);
	    Tcl_ListObjAppendElement(NULL, list,
				     Tcl_NewStringObj(Tcl_DStringValue(&ds),
						      Tcl_DStringLength(&ds)));
	    Tcl_DStringFree(&ds);
	}
	CoTaskMemFree(link);
	CoTaskMemFree(name);
    }
    for (i = 0; i < count; i++) {
	dev = devList[i];
	dev->lpVtbl->Release(dev);
    }
    CoTaskMemFree(devList);
    Tcl_SetObjResult(interp, list);
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * GetSource --
 *
 *	Obtain IMFMediaSource for given symbolic link name.
 *
 *-------------------------------------------------------------------------
 */

static IMFMediaSource *
GetSource(char *devName)
{
    IMFActivate **devList = NULL, *dev = NULL;
    IMFMediaSource *mediaSrc = NULL;
    int i, count;
    HRESULT hr;

    count = EnumDevices(&devList);
    for (i = 0; i < count; i++) {
	WCHAR *link = NULL;
	Tcl_DString ds;
	int rc;

	dev = devList[i];
	hr = dev->lpVtbl->GetAllocatedString(dev,
		&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
		&link, NULL);
	if (!SUCCEEDED(hr)) {
	    CoTaskMemFree(link);
	    continue;
	}
	Tcl_DStringInit(&ds);
	Tcl_WinTCharToUtf((TCHAR *) link, -1, &ds);
	rc = strcmp(Tcl_DStringValue(&ds), devName);
	Tcl_DStringFree(&ds);
	CoTaskMemFree(link);
	if (rc == 0) {
	    break;
	}
	dev = NULL;
    }
    if (dev != NULL) {
	hr = dev->lpVtbl->ActivateObject(dev, &IID_IMFMediaSource,
					 (void **) &mediaSrc);
	if (!SUCCEEDED(hr)) {
	    mediaSrc = NULL;
	}
    }
    for (i = 0; i < count; i++) {
	dev = devList[i];
	dev->lpVtbl->Release(dev);
    }
    CoTaskMemFree(devList);
    return mediaSrc;
}

/*
 *-------------------------------------------------------------------------
 *
 * IMFSourceReader implementation and its VTBL
 *
 *-------------------------------------------------------------------------
 */

static HRESULT STDMETHODCALLTYPE
SR_QueryInterface(IMFSourceReaderCallback *This, REFIID riid, void **ppvObj)
{
    if (IsEqualIID(riid, &IID_IMFSourceReaderCallback)) {
	This->lpVtbl->AddRef(This);
	*ppvObj = This;
	return S_OK;
    }
    *ppvObj = NULL;
    return E_NOINTERFACE;
}

static ULONG STDMETHODCALLTYPE
SR_AddRef(IMFSourceReaderCallback *This)
{
    SourceReaderCB *srcb = (SourceReaderCB *) This;

    srcb->refCount += 1;
    return srcb->refCount;
}

static ULONG STDMETHODCALLTYPE
SR_Release(IMFSourceReaderCallback *This)
{
    SourceReaderCB *srcb = (SourceReaderCB *) This;

    if (srcb->refCount == 0) {
	return 0;
    }
    srcb->refCount -= 1;
    return srcb->refCount;
}

static HRESULT STDMETHODCALLTYPE
SR_OnReadSample(IMFSourceReaderCallback *This, HRESULT hrStatus,
		DWORD streamIndex, DWORD streamFlags, LONGLONG ts,
		IMFSample *sample)
{
    SourceReaderCB *srcb = (SourceReaderCB *) This;
    WMFC *wmfc;
    IMFMediaBuffer *mbuf = NULL;
    int index;
    HRESULT hr = S_OK;

    if (!SUCCEEDED(hrStatus)) {
	hr = hrStatus;
    }
    EnterCriticalSection(&srcb->lock);
    wmfc = srcb->wmfc;
    if ((wmfc->frameQueued < 0) || !SUCCEEDED(hr)) {
	wmfc->streamEnd = -1;
	goto done;
    }
    if (sample == NULL) {
	if ((streamFlags & MF_SOURCE_READERF_ENDOFSTREAM) ==
	    MF_SOURCE_READERF_ENDOFSTREAM) {
	    wmfc->streamEnd = 1;
	}
	goto done;
    }
    wmfc->counters[0] += 1;
    index = wmfc->frameQueued;
    hr = sample->lpVtbl->ConvertToContiguousBuffer(sample, &mbuf);
    if (SUCCEEDED(hr)) {
	DWORD length = 0, maxLength = 0;
	BYTE *data = NULL;
	int bufok = 0;

	mbuf->lpVtbl->Lock(mbuf, &data, &maxLength, &length);
	if (wmfc->frame[index].data == NULL) {
	    wmfc->frame[index].data =
		(unsigned char *) attemptckalloc(length);
	    if (wmfc->frame[index].data != NULL) {
		wmfc->frame[index].maxLength = length;
		bufok = 1;
	    } else {
		wmfc->frame[index].maxLength = 0;
	    }
	} else if (wmfc->frame[index].maxLength >= length) {
	    bufok = 1;
	} else {
	    unsigned char *newData = (unsigned char *)
		attemptckrealloc(wmfc->frame[index].data, length);

	    if (newData != NULL) {
		wmfc->frame[index].data = newData;
		wmfc->frame[index].maxLength = length;
		bufok = 1;
	    }
	}
	if (bufok) {
	    memcpy(wmfc->frame[index].data, data, length);
	    wmfc->frame[index].length = length;
	    wmfc->frame[index].ready = 1;
	    wmfc->frame[index].fourcc = wmfc->fourcc;
	    wmfc->frame[index].width = wmfc->width;
	    wmfc->frame[index].height = wmfc->height;
	    wmfc->frame[index].stride = wmfc->stride;
	} else {
	    wmfc->frame[index].length = 0;
	    wmfc->frame[index].ready = -1;
	    wmfc->counters[2] += 1;
	    wmfc->counters[3] += 1;
	}
	mbuf->lpVtbl->Unlock(mbuf);
    }
    if (mbuf != NULL) {
	mbuf->lpVtbl->Release(mbuf);
    }
done:
#ifdef USE_ASYNC_HANDLER
    Tcl_AsyncMark(wmfc->async);
#else
    if (wmfc->tid != NULL) {
	WMFEVT *event = (WMFEVT *) ckalloc(sizeof(WMFEVT));

	event->hdr.proc = SignalBuffer;
	event->hdr.nextPtr = NULL;
	event->wmfc = wmfc;
	Tcl_ThreadQueueEvent(wmfc->tid, &event->hdr, TCL_QUEUE_TAIL);
	Tcl_ThreadAlert(wmfc->tid);
    }
#endif
    LeaveCriticalSection(&srcb->lock);
    return hr;
}

static HRESULT STDMETHODCALLTYPE
SR_OnFlush(IMFSourceReaderCallback *This, DWORD streamIndex)
{
    SourceReaderCB *srcb = (SourceReaderCB *) This;

    srcb->closing = 0;
    return S_OK;
}

static HRESULT STDMETHODCALLTYPE
SR_OnEvent(IMFSourceReaderCallback *This, DWORD streamIndex,
	   IMFMediaEvent *pEvent)
{
    return S_OK;
}

static IMFSourceReaderCallbackVtbl SourceReaderVtbl = {
    SR_QueryInterface,
    SR_AddRef,
    SR_Release,
    SR_OnReadSample,
    SR_OnFlush,
    SR_OnEvent
};

/*
 *-------------------------------------------------------------------------
 *
 * ImageCallback --
 *
 *	Called as timer handler from the (async) event handler
 *	when a new image is available or image capture was stopped
 *	due to an error.
 *
 *-------------------------------------------------------------------------
 */

static void
ImageCallback(ClientData clientData)
{
    WMFC *wmfc = (WMFC *) clientData;
    Tcl_Interp *interp = wmfc->interp;
    int ret;
#ifdef USE_ASYNC_HANDLER
    int capture = (wmfc->async != NULL);
#else
    int capture = (wmfc->tid != NULL);
#endif

    wmfc->cbPending = NULL;
    Tcl_DStringSetLength(&wmfc->cbCmd, wmfc->cbCmdLen);
    Tcl_DStringAppendElement(&wmfc->cbCmd, wmfc->devId);
    if (wmfc->streamEnd) {
	Tcl_DStringAppendElement(&wmfc->cbCmd, (wmfc->streamEnd < 0) ?
				 "error" : "eof");
    } else {
	Tcl_DStringAppendElement(&wmfc->cbCmd, capture ? "capture" : "stop");
    }
    Tcl_Preserve((ClientData) interp);
    ret = Tcl_EvalEx(interp, Tcl_DStringValue(&wmfc->cbCmd),
		     Tcl_DStringLength(&wmfc->cbCmd), TCL_EVAL_GLOBAL);
    if (ret != TCL_OK) {
	StopCapture(wmfc, 0);
	Tcl_AddErrorInfo(interp, "\n    (wmf event handler)");
	Tcl_BackgroundError(interp);
    }
    Tcl_Release((ClientData) interp);
}

/*
 *-------------------------------------------------------------------------
 *
 * SignalBuffer --
 *
 *	(Async) event handler called when an image became ready.
 *	Requests next frame and schedules timer handler
 *	for performing the Tcl callback established on open.
 *
 *-------------------------------------------------------------------------
 */

static int
#ifdef USE_ASYNC_HANDLER
SignalBuffer(ClientData clientData, Tcl_Interp *interp, int code)
#else
SignalBuffer(Tcl_Event *evPtr, int flags)
#endif
{
#ifdef USE_ASYNC_HANDLER
    WMFC *wmfc = (WMFC *) clientData;
#else
    WMFEVT *wevPtr = (WMFEVT *) evPtr;
    WMFC *wmfc = wevPtr->wmfc;
    int code = 1;	/* event was handled */
#endif
    HRESULT hr;
    int bufok = 0, dostop = 0, doread = 0;

    if (wmfc->srcReader == NULL) {
	return code;
    }
#ifndef USE_ASYNC_HANDLER
    if (wmfc->tid == NULL) {
	return code;
    }
#endif
    EnterCriticalSection(&wmfc->srcb.lock);
    if (wmfc->counters[3] > 25) {
	/* stop after consecutive memory errors */
	dostop = 1;
    } else if (wmfc->frameQueued >= 0) {
	bufok = wmfc->frame[wmfc->frameQueued].ready > 0;
	if (bufok) {
	    if ((wmfc->frameReady >= 0) &&
		(wmfc->frame[wmfc->frameReady].ready < 2)) {
		wmfc->counters[1] += 1;	/* dropped */
	    }
	    wmfc->frameReady = wmfc->frameQueued;
	    wmfc->frameQueued = wmfc->frameQueued ? 0 : 1;
	    wmfc->counters[3] = 0;	/* reset memory error counter */
	}
	wmfc->frame[wmfc->frameQueued].ready = 0;
	doread = 1;
    }
    LeaveCriticalSection(&wmfc->srcb.lock);
    if (doread) {
	hr = wmfc->srcReader->lpVtbl->ReadSample(wmfc->srcReader,
		MF_SOURCE_READER_ANY_STREAM, 0, NULL, NULL, NULL, NULL);
	if (!SUCCEEDED(hr)) {
	    dostop = 1;
	}
    }
    if (bufok && !dostop && !wmfc->ruser && (wmfc->rstate == REC_RECORD)) {
	WriteFrame(wmfc, &wmfc->frame[wmfc->frameReady]);
    }
    if (dostop) {
	wmfc->frameQueued = -1;
#ifdef USE_ASYNC_HANDLER
	Tcl_AsyncDelete(wmfc->async);
	wmfc->async = NULL;
#else
	wmfc->tid = NULL;
#endif
    }
    if (bufok || dostop) {
	if (wmfc->cbPending == NULL) {
	    wmfc->cbPending =
		Tcl_CreateTimerHandler(0, ImageCallback, (ClientData) wmfc);
	}
    }
    return code;
}

/*
 *-------------------------------------------------------------------------
 *
 * SetFormat --
 *
 *	Set media source and source reader format given index
 *	which must be an index from the MediaFmts array.
 *
 *-------------------------------------------------------------------------
 */

static HRESULT
SetFormat(IMFMediaSource *mediaSrc, IMFSourceReader *srcReader, int index)
{
    IMFPresentationDescriptor *pd = NULL;
    IMFStreamDescriptor *sd = NULL;
    IMFMediaTypeHandler *handler = NULL;
    IMFMediaType *mediaType = NULL;
    BOOL selected;
    HRESULT hr;

    hr = mediaSrc->lpVtbl->CreatePresentationDescriptor(mediaSrc, &pd);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = pd->lpVtbl->GetStreamDescriptorByIndex(pd, 0, &selected, &sd);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = sd->lpVtbl->GetMediaTypeHandler(sd, &handler);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = handler->lpVtbl->GetMediaTypeByIndex(handler, index, &mediaType);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = handler->lpVtbl->SetCurrentMediaType(handler, mediaType);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = srcReader->lpVtbl->SetCurrentMediaType(srcReader,
		MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediaType);
done:
    if (mediaType != NULL) {
	mediaType->lpVtbl->Release(mediaType);
    }
    if (handler != NULL) {
	handler->lpVtbl->Release(handler);
    }
    if (sd != NULL) {
	sd->lpVtbl->Release(sd);
    }
    if (pd != NULL) {
	pd->lpVtbl->Release(pd);
    }
    return hr;
}

/*
 *-------------------------------------------------------------------------
 *
 * GetFormatList --
 *
 *	Return allocated array of usable formats.
 *
 *-------------------------------------------------------------------------
 */

static MediaFmt *
GetFormatList(IMFMediaSource *mediaSrc, int *numFmtsRet)
{
    IMFPresentationDescriptor *pd = NULL;
    IMFStreamDescriptor *sd = NULL;
    IMFMediaTypeHandler *handler = NULL;
    IMFMediaType *mediaType = NULL;
    MediaFmt *fmts = NULL;
    BOOL selected;
    DWORD count = 0;
    int i, numFmts = 0;
    HRESULT hr;

    hr = mediaSrc->lpVtbl->CreatePresentationDescriptor(mediaSrc, &pd);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = pd->lpVtbl->GetStreamDescriptorByIndex(pd, 0, &selected, &sd);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = sd->lpVtbl->GetMediaTypeHandler(sd, &handler);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    hr = handler->lpVtbl->GetMediaTypeCount(handler, &count);
    if (!SUCCEEDED(hr)) {
	goto done;
    }
    fmts = (MediaFmt *) attemptckalloc(sizeof(MediaFmt) * count);
    if (fmts == NULL) {
	goto done;
    }
    for (i = 0; i < count; i++) {
	GUID subType = GUID_NULL;
	UINT64 wval;
	LONG lval;
	int w, h;
	BOOL isCompressed = TRUE;

	hr = handler->lpVtbl->GetMediaTypeByIndex(handler, i, &mediaType);
	if (!SUCCEEDED(hr)) {
	    goto skip;
	}
	mediaType->lpVtbl->IsCompressedFormat(mediaType, &isCompressed);
	if (isCompressed) {
	    goto skip;
	}
	hr = mediaType->lpVtbl->GetGUID(mediaType, &MF_MT_SUBTYPE, &subType);
	if (SUCCEEDED(hr) && ((subType.Data1 == FOURCC_NV12) ||
			      (subType.Data1 == FOURCC_YUY2))) {
	    fmts[numFmts].index = i;
	    fmts[numFmts].fourcc = subType.Data1;
	    wval = 0;
	    hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_SIZE,
					      &wval);
	    if (!SUCCEEDED(hr)) {
		continue;
	    }
	    w = wval >> 32;
	    h = wval;
	    if ((w == 0) || (h == 0)) {
		continue;
	    }
	    fmts[numFmts].width = w;
	    fmts[numFmts].height = h;
	    hr = MFGetStrideForBitmapInfoHeader(subType.Data1, w, &lval);
	    if (!SUCCEEDED(hr)) {
		continue;
	    }
	    fmts[numFmts].stride = lval;
	    wval = 0;
	    hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_RATE,
					      &wval);
	    if (!SUCCEEDED(hr)) {
		continue;
	    }
	    fmts[numFmts].frameRate = wval;
	    fmts[numFmts].frameRateMin = fmts[numFmts].frameRateMax = wval;
	    wval = 0;
	    hr = mediaType->lpVtbl->GetUINT64(mediaType,
					      &MF_MT_FRAME_RATE_RANGE_MIN,
					      &wval);
	    if (SUCCEEDED(hr)) {
		fmts[numFmts].frameRateMin = wval;
	    }
	    wval = 0;
	    hr = mediaType->lpVtbl->GetUINT64(mediaType,
					      &MF_MT_FRAME_RATE_RANGE_MAX,
					      &wval);
	    if (SUCCEEDED(hr)) {
		fmts[numFmts].frameRateMax = wval;
	    }
	    ++numFmts;
	}
skip:
	if (mediaType != NULL) {
	    mediaType->lpVtbl->Release(mediaType);
	    mediaType = NULL;
	}
    }
done:
    if (handler != NULL) {
	handler->lpVtbl->Release(handler);
    }
    if (sd != NULL) {
	sd->lpVtbl->Release(sd);
    }
    if (pd != NULL) {
	pd->lpVtbl->Release(pd);
    }
    if ((numFmts <= 0) && (fmts != NULL)) {
	ckfree((char *) fmts);
	fmts = NULL;
    }
    *numFmtsRet = numFmts;
    return fmts;
}

/*
 *-------------------------------------------------------------------------
 *
 * GetFormat --
 *
 *	Get information about the media format (width, height etc.).
 *
 *-------------------------------------------------------------------------
 */

static int
GetFormat(WMFC *wmfc)
{
    HRESULT hr;
    Tcl_Interp *interp = wmfc->interp;
    IMFSourceReader *srcReader = wmfc->srcReader;
    IMFMediaType *mediaType = NULL;
    GUID subType = GUID_NULL;
    LONG lval = 0;
    UINT64 wval = 0;
    UINT32 w = 0, h = 0;
    int result = TCL_ERROR;

    hr = srcReader->lpVtbl->GetCurrentMediaType(srcReader,
		MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediaType);
    if (!SUCCEEDED(hr)) {
	Tcl_SetResult(interp, "error getting media type from source reader",
		      TCL_STATIC);
	goto done;
    }
    hr = mediaType->lpVtbl->GetGUID(mediaType, &MF_MT_SUBTYPE, &subType);
    if (!SUCCEEDED(hr)) {
	Tcl_SetResult(interp, "error getting media format", TCL_STATIC);
	goto done;
    }
    if ((subType.Data1 != FOURCC_NV12) && (subType.Data1 != FOURCC_YUY2)) {
	Tcl_SetObjResult(interp,
			 Tcl_ObjPrintf("unsupported format code 0x%08lx",
				       subType.Data1));
	goto done;
    }
    wmfc->fourcc = subType.Data1;
    hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_SIZE, &wval);
    if (SUCCEEDED(hr)) {
	w = wval >> 32;
	h = wval;
    }
    if (!SUCCEEDED(hr)) {
	Tcl_SetResult(interp, "error getting frame size", TCL_STATIC);
	goto done;
    }
    hr = MFGetStrideForBitmapInfoHeader(subType.Data1, w, &lval);
    if (!SUCCEEDED(hr)) {
	Tcl_SetResult(interp, "error getting stride", TCL_STATIC);
	goto done;
    }
    wmfc->stride = lval;
    wmfc->width = w;
    wmfc->height = h;
    result = TCL_OK;
done:
    if (mediaType != NULL) {
	mediaType->lpVtbl->Release(mediaType);
    }
    return result;
}

/*
 *-------------------------------------------------------------------------
 *
 * StartCapture --
 *
 *	Start capture of frames. A media source is created if not
 *	already available. The (async) event handler is established
 *	and the initial read of the first frame is performed. Later
 *	frames are requested by the (async) event handler.
 *
 *-------------------------------------------------------------------------
 */

static int
StartCapture(WMFC *wmfc)
{
    Tcl_Interp *interp = wmfc->interp;
    IMFAttributes *attr = NULL;
    HRESULT hr;
    int doread = 0;

    if (wmfc->srcReader == NULL) {
	hr = MFCreateAttributes(&attr, 1);
	if (!SUCCEEDED(hr)) {
	    Tcl_SetResult(interp, "error creating WMF attributes", TCL_STATIC);
	    return TCL_ERROR;
	}
	wmfc->counters[0] = wmfc->counters[1] = 0;
	wmfc->counters[2] = wmfc->counters[3] = 0;
	wmfc->frame[0].ready = wmfc->frame[1].ready = 0;
	wmfc->streamEnd = 0;
	wmfc->frameReady = wmfc->frameQueued = -1;
	wmfc->srcb.isrcb.lpVtbl = &SourceReaderVtbl;
	wmfc->srcb.refCount = 1;
	wmfc->srcb.closing = 0;
	wmfc->srcb.wmfc = wmfc;
	hr = attr->lpVtbl->SetUnknown(attr,
				      &MF_SOURCE_READER_ASYNC_CALLBACK,
				      (struct IUnknown *) &wmfc->srcb);
	hr = MFCreateSourceReaderFromMediaSource(wmfc->mediaSrc, attr,
						 &wmfc->srcReader);
	if (attr != NULL) {
	    attr->lpVtbl->Release(attr);
	}
	if (!SUCCEEDED(hr)) {
	    if (wmfc->srcReader != NULL) {
		wmfc->srcReader->lpVtbl->Release(wmfc->srcReader);
		wmfc->srcReader = NULL;
	    }
	    Tcl_SetResult(interp, "error creating source reader", TCL_STATIC);
	    return TCL_ERROR;
	}
	if (wmfc->useFmt < 0) {
	    wmfc->useFmt = wmfc->fmts[0].index;
	}
	hr = SetFormat(wmfc->mediaSrc, wmfc->srcReader, wmfc->useFmt);
	if (!SUCCEEDED(hr)) {
	    wmfc->srcReader->lpVtbl->Release(wmfc->srcReader);
	    wmfc->srcReader = NULL;
	    Tcl_SetResult(interp, "error setting media format", TCL_STATIC);
	    return TCL_ERROR;
	}
	if (GetFormat(wmfc) != TCL_OK) {
	    wmfc->srcReader->lpVtbl->Release(wmfc->srcReader);
	    wmfc->srcReader = NULL;
	    return TCL_ERROR;
	}
	wmfc->cbPending = NULL;
    }
    EnterCriticalSection(&wmfc->srcb.lock);
#ifdef USE_ASYNC_HANDLER
    if (wmfc->async == NULL) {
	wmfc->async = Tcl_AsyncCreate(SignalBuffer, (ClientData) wmfc);
	doread = 1;
    }
#else
    if (wmfc->tid == NULL) {
	wmfc->tid = Tcl_GetCurrentThread();
	doread = 1;
    }
#endif
    if (doread) {
	wmfc->streamEnd = 0;
	wmfc->counters[0] = wmfc->counters[1] = 0;
	wmfc->counters[2] = wmfc->counters[3] = 0;
	wmfc->frameQueued = wmfc->frameReady ? 0 : 1;
	wmfc->frame[wmfc->frameQueued].ready = 0;
	hr = wmfc->srcReader->lpVtbl->ReadSample(wmfc->srcReader,
						 MF_SOURCE_READER_ANY_STREAM,
						 0, NULL, NULL, NULL, NULL);
	if (!SUCCEEDED(hr)) {
	    wmfc->frameQueued = -1;
#ifdef USE_ASYNC_HANDLER
	    Tcl_AsyncDelete(wmfc->async);
	    wmfc->async = NULL;
#else
	    wmfc->tid = NULL;
#endif
	}
    } else {
	hr = S_OK;
    }
    LeaveCriticalSection(&wmfc->srcb.lock);
    if (!SUCCEEDED(hr)) {
	Tcl_SetResult(interp, "error starting read from source reader",
		      TCL_STATIC);
	return TCL_ERROR;
    }
    if (wmfc->rstate == REC_PAUSE) {
	wmfc->ltv = (int) GetTickCount();
	wmfc->rtv = wmfc->ltv;
	wmfc->rstate = REC_RECORD;
    }
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * StopCapture --
 *
 *      Stop capture if running. The (async) event handler is removed and
 *	a pending call to ImageCallback is cancelled, too.
 *
 *-------------------------------------------------------------------------
 */

static int
StopCapture(WMFC *wmfc, int closing)
{
    EnterCriticalSection(&wmfc->srcb.lock);
#ifdef USE_ASYNC_HANDLER
    if (wmfc->async != NULL) {
	Tcl_AsyncDelete(wmfc->async);
	wmfc->async = NULL;
    }
#else
    wmfc->tid = NULL;
#endif
    if (wmfc->streamEnd < 0) {
	wmfc->streamEnd = 0;
    }
    wmfc->frameQueued = -1;
    if (wmfc->srcReader != NULL) {
	wmfc->srcb.closing = closing;
	wmfc->srcReader->lpVtbl->Flush(wmfc->srcReader,
				       MF_SOURCE_READER_ALL_STREAMS);
    } else {
	closing = 0;
    }
    LeaveCriticalSection(&wmfc->srcb.lock);
    Tcl_DeleteTimerHandler(wmfc->cbPending);
    wmfc->cbPending = NULL;
    if (closing) {
	int i;

	for (i = 0; i < 5; i++) {
	    if (!wmfc->srcb.closing) {
		break;
	    }
	    Sleep(20);
	}
    }
    if (wmfc->rstate == REC_RECORD) {
	wmfc->rstate = REC_PAUSE;
    }
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * InitControls --
 *
 *	Fill (or release) table with meta information about the
 *	device's controls.
 *
 *-------------------------------------------------------------------------
 */

static void
InitControls(WMFC *wmfc, int deinit)
{
    int i, isNew;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    WCTRL *wctrl;
    IAMVideoProcAmp *procAmp = NULL;
    IAMCameraControl *camCtrl = NULL;
    HRESULT hr;
    static const struct {
	int code;
	const char *name;
    } painfo[] = {
	{ VideoProcAmp_Brightness, "brightness" },
	{ VideoProcAmp_Contrast, "contrast" },
	{ VideoProcAmp_Hue, "hue" },
	{ VideoProcAmp_Saturation, "saturation" },
	{ VideoProcAmp_Sharpness, "sharpness" },
	{ VideoProcAmp_Gamma, "gamma" },
	{ VideoProcAmp_ColorEnable, "color-enable" },
	{ VideoProcAmp_WhiteBalance, "white-balance" },
	{ VideoProcAmp_BacklightCompensation, "backlight-compensation" },
	{ VideoProcAmp_Gain, "gain" }
    };
    static const struct {
	int code;
	const char *name;
    } ccinfo[] = {
	{ CameraControl_Pan, "pan" },
	{ CameraControl_Tilt, "tilt" },
	{ CameraControl_Roll, "roll" },
	{ CameraControl_Zoom, "zoom" },
	{ CameraControl_Exposure, "exposure" },
	{ CameraControl_Iris, "iris" },
	{ CameraControl_Focus, "focus" },
    };

    /* first, free up old stuff */
    hPtr = Tcl_FirstHashEntry(&wmfc->ctrl, &search);
    while (hPtr != NULL) {
	wctrl = (WCTRL *) Tcl_GetHashValue(hPtr);
	ckfree((char *) wctrl);
	hPtr = Tcl_NextHashEntry(&search);
    }
    Tcl_DeleteHashTable(&wmfc->ctrl);

    if (deinit) {
	return;
    }

    Tcl_InitHashTable(&wmfc->ctrl, TCL_STRING_KEYS);

    /* fill in new information */
    hr = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc,
		&PRIVATE_IID_IAMVideoProcAmp, (void **) &procAmp);
    if (SUCCEEDED(hr)) {
	for (i = 0; i < sizeof(painfo) / sizeof(painfo[0]); i++) {
	    long llim, ulim, step, def, caps;

	    hr = procAmp->lpVtbl->GetRange(procAmp, painfo[i].code,
					   &llim, &ulim, &step, &def, &caps);
	    if (!SUCCEEDED(hr)) {
		continue;
	    }
	    if (!(caps & VideoProcAmp_Flags_Manual)) {
		continue;
	    }
	    wctrl = (WCTRL *) ckalloc(sizeof(WCTRL));
	    memset(wctrl, 0, sizeof(WCTRL));
	    wctrl->code = painfo[i].code;
	    wctrl->iscam = 0;
	    wctrl->name = painfo[i].name;
	    wctrl->min = llim;
	    wctrl->max = ulim;
	    wctrl->step = step;
	    wctrl->def = def;
	    wctrl->caps = caps;
	    hPtr = Tcl_CreateHashEntry(&wmfc->ctrl, (ClientData) wctrl->name,
				       &isNew);
	    if (!isNew) {
		WCTRL *oldctrl = (WCTRL *) Tcl_GetHashValue(hPtr);

		ckfree((char *) oldctrl);
	    }
	    Tcl_SetHashValue(hPtr, (ClientData) wctrl);
	}
    }
    if (procAmp != NULL) {
	procAmp->lpVtbl->Release(procAmp);
    }
    hr = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc,
		&PRIVATE_IID_IAMCameraControl, (void **) &camCtrl);
    if (SUCCEEDED(hr)) {
	for (i = 0; i < sizeof(ccinfo) / sizeof(ccinfo[0]); i++) {
	    long llim, ulim, step, def, caps;

	    hr = camCtrl->lpVtbl->GetRange(camCtrl, ccinfo[i].code,
					   &llim, &ulim, &step, &def, &caps);
	    if (!SUCCEEDED(hr)) {
		continue;
	    }
	    if (!(caps & CameraControl_Flags_Manual)) {
		continue;
	    }
	    wctrl = (WCTRL *) ckalloc(sizeof(WCTRL));
	    memset(wctrl, 0, sizeof(WCTRL));
	    wctrl->code = ccinfo[i].code;
	    wctrl->iscam = 1;
	    wctrl->name = ccinfo[i].name;
	    wctrl->min = llim;
	    wctrl->max = ulim;
	    wctrl->step = step;
	    wctrl->def = def;
	    wctrl->caps = caps;
	    hPtr = Tcl_CreateHashEntry(&wmfc->ctrl, (ClientData) wctrl->name,
				       &isNew);
	    if (!isNew) {
		WCTRL *oldctrl = (WCTRL *) Tcl_GetHashValue(hPtr);

		ckfree((char *) oldctrl);
	    }
	    Tcl_SetHashValue(hPtr, (ClientData) wctrl);
	}
    }
    if (camCtrl != NULL) {
	camCtrl->lpVtbl->Release(camCtrl);
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * GetControls --
 *
 *	Read out current values of device controls and return
 *	these as list made up of key value pairs. Added meta
 *	information entries to support user interface:
 *
 *	  <name>-minimum   minimum value for bool/integer
 *	  <name>-maximum   minimum value for bool/integer
 *	  <name>-step      interval step value for integer
 *	  <name>-default   default value for bool/integer
 *	  <name>-auto      0/1 depending on manual/auto
 *
 *-------------------------------------------------------------------------
 */

static void
GetControls(WMFC *wmfc, Tcl_Obj *list)
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    Tcl_DString ds;
    IAMVideoProcAmp *procAmp = NULL;
    IAMCameraControl *camCtrl = NULL;
    HRESULT hrpa, hrcc;

    Tcl_DStringInit(&ds);
    hrpa = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc,
		&PRIVATE_IID_IAMVideoProcAmp, (void **) &procAmp);
    hrcc = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc,
		&PRIVATE_IID_IAMCameraControl, (void **) &camCtrl);
    if (SUCCEEDED(hrpa) || SUCCEEDED(hrcc)) {
	hPtr = Tcl_FirstHashEntry(&wmfc->ctrl, &search);
	while (hPtr != NULL) {
	    WCTRL *wctrl = (WCTRL *) Tcl_GetHashValue(hPtr);
	    HRESULT hr = E_INVALIDARG;
	    long val = 0, flags = 0;
	    char buffer[32];

	    if (wctrl->iscam && SUCCEEDED(hrcc)) {
		hr = camCtrl->lpVtbl->Get(camCtrl, wctrl->code, &val, &flags);
	    } else if (!wctrl->iscam && SUCCEEDED(hrpa)) {
		hr = procAmp->lpVtbl->Get(procAmp, wctrl->code, &val, &flags);
	    }
	    if (SUCCEEDED(hr)) {
		Tcl_ListObjAppendElement(NULL, list,
		    Tcl_NewStringObj(wctrl->name, -1));
		sprintf(buffer, "%ld", val);
		Tcl_ListObjAppendElement(NULL, list,
		    Tcl_NewStringObj(buffer, -1));
		if (wctrl->min != wctrl->max) {
		    Tcl_DStringSetLength(&ds, 0);
		    Tcl_DStringAppend(&ds, wctrl->name, -1);
		    Tcl_DStringAppend(&ds, "-minimum", -1);
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(Tcl_DStringValue(&ds),
					 Tcl_DStringLength(&ds)));
		    sprintf(buffer, "%ld", wctrl->min);
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(buffer, -1));
		    Tcl_DStringSetLength(&ds, 0);
		    Tcl_DStringAppend(&ds, wctrl->name, -1);
		    Tcl_DStringAppend(&ds, "-maximum", -1);
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(Tcl_DStringValue(&ds),
					 Tcl_DStringLength(&ds)));
		    sprintf(buffer, "%ld", wctrl->max);
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(buffer, -1));
		}
		Tcl_DStringSetLength(&ds, 0);
		Tcl_DStringAppend(&ds, wctrl->name, -1);
		Tcl_DStringAppend(&ds, "-default", -1);
		Tcl_ListObjAppendElement(NULL, list,
		    Tcl_NewStringObj(Tcl_DStringValue(&ds),
				     Tcl_DStringLength(&ds)));
		sprintf(buffer, "%ld", wctrl->max);
		Tcl_ListObjAppendElement(NULL, list,
		    Tcl_NewStringObj(buffer, -1));
		if (wctrl->step != 0) {
		    Tcl_DStringSetLength(&ds, 0);
		    Tcl_DStringAppend(&ds, wctrl->name, -1);
		    Tcl_DStringAppend(&ds, "-step", -1);
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(Tcl_DStringValue(&ds),
					 Tcl_DStringLength(&ds)));
		    sprintf(buffer, "%ld", wctrl->step);
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(buffer, -1));
		}
		if (wctrl->caps & VideoProcAmp_Flags_Auto) {
		    Tcl_DStringSetLength(&ds, 0);
		    Tcl_DStringAppend(&ds, wctrl->name, -1);
		    Tcl_DStringAppend(&ds, "-auto", -1);
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(Tcl_DStringValue(&ds),
					 Tcl_DStringLength(&ds)));
		    strcpy(buffer, (flags & VideoProcAmp_Flags_Auto)
			   ? "1" : "0");
		    Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(buffer, -1));
		}
	    }
	    hPtr = Tcl_NextHashEntry(&search);
	}
    }
    if (procAmp != NULL) {
	procAmp->lpVtbl->Release(procAmp);
    }
    if (camCtrl != NULL) {
	camCtrl->lpVtbl->Release(camCtrl);
    }
    Tcl_DStringFree(&ds);
}

/*
 *-------------------------------------------------------------------------
 *
 * SetControls --
 *
 *	Set device controls given list of key value pairs.
 *
 *-------------------------------------------------------------------------
 */

static void
SetControls(WMFC *wmfc, int objc, Tcl_Obj * const objv[])
{
    Tcl_HashEntry *hPtr;
    int i;
    IAMVideoProcAmp *procAmp = NULL;
    IAMCameraControl *camCtrl = NULL;
    HRESULT hrpa, hrcc;

    hrpa = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc,
		&PRIVATE_IID_IAMVideoProcAmp, (void **) &procAmp);
    hrcc = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc,
		&PRIVATE_IID_IAMCameraControl, (void **) &camCtrl);
    for (i = 0; i < objc; i += 2) {
	WCTRL *wctrl;
	long val;
	int doauto = 0;
	char *name = Tcl_GetString(objv[i]);

	hPtr = Tcl_FindHashEntry(&wmfc->ctrl, name);
	if (hPtr == NULL) {
	    int n = strlen(name);

	    if ((n > 5) && (strcmp(name + n - 5, "-auto") == 0)) {
		Tcl_DString ds;

		Tcl_DStringInit(&ds);
		Tcl_DStringAppend(&ds, name, n - 5);
		hPtr = Tcl_FindHashEntry(&wmfc->ctrl, Tcl_DStringValue(&ds));
		Tcl_DStringFree(&ds);
		doauto = 1;
	    }
	}
	if (hPtr == NULL) {
	    continue;
	}
	wctrl = (WCTRL *) Tcl_GetHashValue(hPtr);
	if (wctrl->iscam && !SUCCEEDED(hrcc)) {
	    continue;
	} else if (!wctrl->iscam && !SUCCEEDED(hrpa)) {
	    continue;
	}
	val = strtol(Tcl_GetString(objv[i + 1]), NULL, 0);
	if (doauto && wctrl->iscam) {
	    if (wctrl->caps & CameraControl_Flags_Auto) {
		camCtrl->lpVtbl->Set(camCtrl, wctrl->code, wctrl->def, val ?
				     CameraControl_Flags_Auto :
				     CameraControl_Flags_Manual);
	    }
	} else if (doauto) {
	    if (wctrl->caps & VideoProcAmp_Flags_Auto) {
		procAmp->lpVtbl->Set(procAmp, wctrl->code, wctrl->def, val ?
				     VideoProcAmp_Flags_Auto :
				     VideoProcAmp_Flags_Manual);
	    }
	} else {
	    if (val < wctrl->min) {
		val = wctrl->min;
	    } else if (val > wctrl->max) {
		val = wctrl->max;
	    }
	    if (wctrl->step > 0) {
		val = val - (val % wctrl->step);
	    }
	    if (wctrl->iscam) {
		camCtrl->lpVtbl->Set(camCtrl, wctrl->code, val,
				     CameraControl_Flags_Manual);
	    } else {
		procAmp->lpVtbl->Set(procAmp, wctrl->code, val,
				     VideoProcAmp_Flags_Manual);
	    }
	}
    }
    if (procAmp != NULL) {
	procAmp->lpVtbl->Release(procAmp);
    }
    if (camCtrl != NULL) {
	camCtrl->lpVtbl->Release(camCtrl);
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * GetImage --
 *
 *	Retrieve last captured buffer as photo image or byte array.
 *
 *-------------------------------------------------------------------------
 */

static int
GetImage(WMFI *wmfi, WMFC *wmfc, Tcl_Obj *arg)
{
    Tcl_Interp *interp = wmfc->interp;
    Tk_PhotoHandle photo = NULL;
    int fourcc, width, height, result = TCL_OK;
    unsigned char *rawPtr = NULL;
    char *name;

    if (arg != NULL) {
	if (CheckForTk(wmfi, interp) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (Tk_MainWindow(interp) == NULL) {
	    Tcl_SetResult(interp, "application has been destroyed",
			  TCL_STATIC);
	    return TCL_ERROR;
	}
	name = Tcl_GetString(arg);
	photo = Tk_FindPhoto(interp, name);
	if (photo == NULL) {
	    Tcl_SetObjResult(interp,
		Tcl_ObjPrintf("can't use \"%s\": not a photo image", name));
	    return TCL_ERROR;
	}
    }

    EnterCriticalSection(&wmfc->srcb.lock);
    if (wmfc->frameReady >= 0) {
	int stride;

	fourcc = wmfc->frame[wmfc->frameReady].fourcc;
	width = wmfc->frame[wmfc->frameReady].width;
	height = wmfc->frame[wmfc->frameReady].height;
	stride = wmfc->frame[wmfc->frameReady].stride;
	if ((width > 0) && (height > 0) && (stride > 0)) {
	    rawPtr = (unsigned char *) attemptckalloc(stride * height * 5);
	}
	if (rawPtr != NULL) {
	    int size = stride * height;

	    if (fourcc == FOURCC_NV12) {
		/* NV12: stride should be equal to width */
		size += (width * height) / 2;
	    } else {
		/* YUY2: stride should be 2 times width */
	    }
	    memcpy(rawPtr, wmfc->frame[wmfc->frameReady].data, size);
	}
	wmfc->frame[wmfc->frameReady].ready = 2;
    }
    LeaveCriticalSection(&wmfc->srcb.lock);

    if (rawPtr == NULL) {
	/* no image available */
	if (photo != NULL) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
	} else {
	    Tcl_SetResult(interp, "no image available", TCL_STATIC);
	    result = TCL_ERROR;
	}
	goto done;
    }
    if ((width <= 0) || (height <= 0)) {
	/* malformed image */
	if (photo != NULL) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(-1));
	} else {
	    Tcl_SetResult(interp, "malformed image", TCL_STATIC);
	    result = TCL_ERROR;
	}
	goto done;
    }

    if (photo != NULL) {
	Tk_PhotoImageBlock block;
	unsigned char *yPtr, *uvPtr, *dstPtr;
	int x, y, yVal, yVal2, uVal = 0, vVal = 0, rot = wmfc->rotate;

	block.pixelSize = 3;
	block.offset[0] = 0;
	block.offset[1] = 1;
	block.offset[2] = 2;
	block.offset[3] = block.pixelSize + 1;	/* no alpha */
	block.width = width;
	block.height = height;
	block.pitch = block.pixelSize * block.width;
	block.pixelPtr = rawPtr + 2 * width * height;

	if (fourcc == FOURCC_NV12) {
	    /* convert NV12 -> RGB */
	    for (y = 0; y < height; y++) {
		yPtr = rawPtr + y * width;
		uvPtr = rawPtr + width * height + (y >> 1) * width;
		dstPtr = block.pixelPtr + block.pixelSize * y * width;
		for (x = 0; x < width; x++) {
		    int red, green, blue;

		    yVal = *yPtr++;
		    yVal -= 16;
		    if (yVal < 0) {
			yVal = 0;
		    }
		    if ((x & 1) == 0) {
			uVal = *uvPtr++ - 128;
			vVal = *uvPtr++ - 128;
		    }
		    yVal *= 1192;
		    red = yVal + 1634 * vVal;
		    green = yVal - 833 * vVal - 400 * uVal;
		    blue = yVal + 2066 * uVal;
		    if (red < 0) {
			red = 0;
		    } else if (red > 0x3ffff) {
			red = 0x3ffff;
		    }
		    *dstPtr++ = red >> 10;
		    if (green < 0) {
			green = 0;
		    } else if (green > 0x3ffff) {
			green = 0x3ffff;
		    }
		    *dstPtr++ = green >> 10;
		    if (blue < 0) {
			blue = 0;
		    } else if (blue > 0x3ffff) {
			blue = 0x3ffff;
		    }
		    *dstPtr++ = blue >> 10;
		}
	    }
	} else {
	    /* convert YUY2 -> RGB */
	    for (y = 0; y < height; y++) {
		yPtr = rawPtr + y * width * 2;
		dstPtr = block.pixelPtr + block.pixelSize * y * width;
		for (x = 0; x < width; x++) {
		    int red, green, blue;

		    yVal = *yPtr++;
		    yVal -= 16;
		    if (yVal < 0) {
			yVal = 0;
		    }
		    uVal = *yPtr++ - 128;
		    yVal2 = *yPtr++;
		    yVal2 -= 16;
		    if (yVal2 < 0) {
			yVal2 = 0;
		    }
		    vVal = *yPtr++ - 128;
		    red = 298 * yVal + 409 * vVal + 128;
		    green = 298 * yVal - 100 * uVal - 208 * vVal + 128;
		    blue = 298 * yVal + 516 * uVal + 128;
		    if (red < 0) {
			red = 0;
		    } else if (red > 0xffff) {
			red = 0xffff;
		    }
		    *dstPtr++ = red >> 8;
		    if (green < 0) {
			green = 0;
		    } else if (green > 0xffff) {
			green = 0xffff;
		    }
		    *dstPtr++ = green >> 8;
		    if (blue < 0) {
			blue = 0;
		    } else if (blue > 0xffff) {
			blue = 0xffff;
		    }
		    *dstPtr++ = blue >> 8;
		    red = 298 * yVal2 + 409 * vVal + 128;
		    green = 298 * yVal2 - 100 * uVal - 208 * vVal + 128;
		    blue = 298 * yVal2 + 516 * uVal + 128;
		    if (red < 0) {
			red = 0;
		    } else if (red > 0xffff) {
			red = 0xffff;
		    }
		    *dstPtr++ = red >> 8;
		    if (green < 0) {
			green = 0;
		    } else if (green > 0xffff) {
			green = 0xffff;
		    }
		    *dstPtr++ = green >> 8;
		    if (blue < 0) {
			blue = 0;
		    } else if (blue > 0xffff) {
			blue = 0xffff;
		    }
		    *dstPtr++ = blue >> 8;
		}
	    }
	}

	if ((wmfc->mirror & 3) == 3) {
	    rot = (rot + 180) % 360;
	}

	switch (rot) {
	case 270:	/* = 90 CW */
	    block.pitch = block.pixelSize;
	    block.pixelPtr += width * block.pixelSize * (height - 1);
	    block.pixelSize *= -width;
	    block.offset[3] = block.pixelSize + 1;	/* no alpha */
	    block.width = height;
	    block.height = width;
	    break;
	case 180:	/* = 180 CW */
	    block.pitch = -block.pitch;
	    block.pixelPtr += (width * height - 1) * block.pixelSize;
	    block.pixelSize = -block.pixelSize;
	    block.offset[3] = block.pixelSize + 1;	/* no alpha */
	    break;
	case 90:	/* = 270 CW */
	    block.pitch = -block.pixelSize;
	    block.pixelPtr += (width - 1) * block.pixelSize;
	    block.pixelSize *= width;
	    block.offset[3] = block.pixelSize + 1;	/* no alpha */
	    block.width = height;
	    block.height = width;
	    break;
	}

	if ((wmfc->mirror & 3) == 2) {
	    /* mirror in X */
	    block.pixelPtr += (block.width - 1) * block.pixelSize;
	    block.pixelSize = -block.pixelSize;
	    block.offset[3] = block.pixelSize + 1;	/* no alpha */
	}
	if ((wmfc->mirror & 3) == 1) {
	    /* mirror in Y */
	    block.pixelPtr += block.pitch * (block.height - 1);
	    block.pitch = -block.pitch;
	}

	if (Tk_PhotoExpand(interp, photo, block.width, block.height)
	    != TCL_OK) {
	    result = TCL_ERROR;
	    goto done;
	}
	if (Tk_PhotoPutBlock(interp, photo, &block, 0, 0,
			     block.width, block.height,
			     TK_PHOTO_COMPOSITE_SET) != TCL_OK) {
	    result = TCL_ERROR;
	} else {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(1));
	}
    } else {
	unsigned char *pixelPtr;
	Tcl_Obj *list[4];
	unsigned char *yPtr, *uvPtr, *dstPtr;
	int x, y, yVal, yVal2, uVal = 0, vVal = 0;

	pixelPtr = rawPtr + 2 * width * height;

	if (fourcc == FOURCC_NV12) {
	    /* convert NV12 -> RGB */
	    for (y = 0; y < height; y++) {
		yPtr = rawPtr + y * width;
		uvPtr = rawPtr + width * height + (y >> 1) * width;
		dstPtr = pixelPtr + 3 * y * width;
		for (x = 0; x < width; x++) {
		    int red, green, blue;

		    yVal = *yPtr++;
		    yVal -= 16;
		    if (yVal < 0) {
			yVal = 0;
		    }
		    if ((x & 1) == 0) {
			uVal = *uvPtr++ - 128;
			vVal = *uvPtr++ - 128;
		    }
		    yVal *= 1192;
		    red = yVal + 1634 * vVal;
		    green = yVal - 833 * vVal - 400 * uVal;
		    blue = yVal + 2066 * uVal;
		    if (red < 0) {
			red = 0;
		    } else if (red > 0x3ffff) {
			red = 0x3ffff;
		    }
		    *dstPtr++ = red >> 10;
		    if (green < 0) {
			green = 0;
		    } else if (green > 0x3ffff) {
			green = 0x3ffff;
		    }
		    *dstPtr++ = green >> 10;
		    if (blue < 0) {
			blue = 0;
		    } else if (blue > 0x3ffff) {
			blue = 0x3ffff;
		    }
		    *dstPtr++ = blue >> 10;
		}
	    }
	} else {
	    /* convert YUY2 -> RGB */
	    for (y = 0; y < height; y++) {
		yPtr = rawPtr + y * width * 2;
		dstPtr = pixelPtr + 3 * y * width;
		for (x = 0; x < width / 2; x++) {
		    int red, green, blue;

		    yVal = *yPtr++;
		    yVal -= 16;
		    if (yVal < 0) {
			yVal = 0;
		    }
		    uVal = *yPtr++ - 128;
		    yVal2 = *yPtr++;
		    yVal2 -= 16;
		    if (yVal2 < 0) {
			yVal2 = 0;
		    }
		    vVal = *yPtr++ - 128;
		    red = 298 * yVal + 409 * vVal + 128;
		    green = 298 * yVal - 100 * uVal - 208 * vVal + 128;
		    blue = 298 * yVal + 516 * uVal + 128;
		    if (red < 0) {
			red = 0;
		    } else if (red > 0xffff) {
			red = 0xffff;
		    }
		    *dstPtr++ = red >> 8;
		    if (green < 0) {
			green = 0;
		    } else if (green > 0xffff) {
			green = 0xffff;
		    }
		    *dstPtr++ = green >> 8;
		    if (blue < 0) {
			blue = 0;
		    } else if (blue > 0xffff) {
			blue = 0xffff;
		    }
		    *dstPtr++ = blue >> 8;
		    red = 298 * yVal2 + 409 * vVal + 128;
		    green = 298 * yVal2 - 100 * uVal - 208 * vVal + 128;
		    blue = 298 * yVal2 + 516 * uVal + 128;
		    if (red < 0) {
			red = 0;
		    } else if (red > 0xffff) {
			red = 0xffff;
		    }
		    *dstPtr++ = red >> 8;
		    if (green < 0) {
			green = 0;
		    } else if (green > 0xffff) {
			green = 0xffff;
		    }
		    *dstPtr++ = green >> 8;
		    if (blue < 0) {
			blue = 0;
		    } else if (blue > 0xffff) {
			blue = 0xffff;
		    }
		    *dstPtr++ = blue >> 8;
		}
	    }
	}

	list[0] = Tcl_NewIntObj(width);
	list[1] = Tcl_NewIntObj(height);
	list[2] = Tcl_NewIntObj(3);
	list[3] = Tcl_NewByteArrayObj(pixelPtr, 3 * width * height);
	Tcl_SetObjResult(interp, Tcl_NewListObj(4, list));
    }
done:
    if (rawPtr != NULL) {
	ckfree((char *) rawPtr);
    }
    return result;
}

/*
 *-------------------------------------------------------------------------
 *
 * WmfObjCmdDeleted --
 *
 *	Destructor of "wmf" Tcl command. Closes all open devices and
 *	releases all resources.
 *
 *-------------------------------------------------------------------------
 */

static void
WmfObjCmdDeleted(ClientData clientData)
{
    WMFI *wmfi = (WMFI *) clientData;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch search;
    WMFC *wmfc;

    hPtr = Tcl_FirstHashEntry(&wmfi->wmfc, &search);
    while (hPtr != NULL) {
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	StopCapture(wmfc, 1);
	FinishRecording(wmfc);
	InitControls(wmfc, 1);
	if (wmfc->srcReader != NULL) {
	    wmfc->srcReader->lpVtbl->Release(wmfc->srcReader);
	}
	if (wmfc->mediaSrc != NULL) {
	    wmfc->mediaSrc->lpVtbl->Release(wmfc->mediaSrc);
	}
	if (wmfc->frame[0].data != NULL) {
	    ckfree((char *) wmfc->frame[0].data);
	}
	if (wmfc->frame[1].data != NULL) {
	    ckfree((char *) wmfc->frame[1].data);
	}
	Tcl_DStringFree(&wmfc->devName);
	Tcl_DStringFree(&wmfc->cbCmd);
	if (wmfc->fmts != NULL) {
	    ckfree((char *) wmfc->fmts);
	}
	DeleteCriticalSection(&wmfc->srcb.lock);
	ckfree((char *) wmfc);
	hPtr = Tcl_NextHashEntry(&search);
    }
    Tcl_DeleteHashTable(&wmfi->wmfc);
    ckfree((char *) wmfi);
}

/*
 *-------------------------------------------------------------------------
 *
 * WmfObjCmd --
 *
 *	"wmf" Tcl command dealing with Windows Media Foundation.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *-------------------------------------------------------------------------
 */

static int
WmfObjCmd(ClientData clientData, Tcl_Interp *interp,
	  int objc, Tcl_Obj * const objv[])
{
    WMFI *wmfi = (WMFI *) clientData;
    WMFC *wmfc;
    Tcl_HashEntry *hPtr;
    int ret = TCL_OK, command;

    static const char *cmdNames[] = {
	"close", "counters", "devices", "format", "image",
	"info",	"listformats", "mirror", "open", "orientation",
	"parameters", "record", "start", "state", "stop",
	"tophoto", NULL
    };
    enum cmdCode {
	CMD_close, CMD_counters, CMD_devices, CMD_format, CMD_image,
	CMD_info, CMD_listformats, CMD_mirror, CMD_open, CMD_orientation,
	CMD_parameters, CMD_record, CMD_start, CMD_state, CMD_stop,
	CMD_tophoto
    };
    static const char *recNames[] = {
	"frame", "pause", "resume", "start", "state", "stop", NULL
    };
    enum recCode {
	REC_frame, REC_pause, REC_resume, REC_start, REC_state, REC_stop
    };

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ...");
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], cmdNames, "option", 0,
			    &command) != TCL_OK) {
	return TCL_ERROR;
    }

    switch ((enum cmdCode) command) {

    case CMD_close: {
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
devNotFound:
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf("device \"%s\" not found",
			Tcl_GetString(objv[2])));
	    return TCL_ERROR;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	Tcl_DeleteHashEntry(hPtr);
	StopCapture(wmfc, 1);
	FinishRecording(wmfc);
	InitControls(wmfc, 1);
	if (wmfc->srcReader != NULL) {
	    wmfc->srcReader->lpVtbl->Release(wmfc->srcReader);
	}
	if (wmfc->mediaSrc != NULL) {
	    wmfc->mediaSrc->lpVtbl->Release(wmfc->mediaSrc);
	}
	if (wmfc->frame[0].data != NULL) {
	    ckfree((char *) wmfc->frame[0].data);
	}
	if (wmfc->frame[1].data != NULL) {
	    ckfree((char *) wmfc->frame[1].data);
	}
	Tcl_DStringFree(&wmfc->devName);
	Tcl_DStringFree(&wmfc->cbCmd);
	if (wmfc->fmts != NULL) {
	    ckfree((char *) wmfc->fmts);
	}
	DeleteCriticalSection(&wmfc->srcb.lock);
	ckfree((char *) wmfc);
	break;
    }

    case CMD_counters: {
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr != NULL) {
	    Tcl_Obj *r[3];

	    wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	    EnterCriticalSection(&wmfc->srcb.lock);
	    r[0] = Tcl_NewWideIntObj(wmfc->counters[0]);
	    r[1] = Tcl_NewWideIntObj(wmfc->counters[1]);
	    r[2] = Tcl_NewWideIntObj(wmfc->counters[2]);
	    LeaveCriticalSection(&wmfc->srcb.lock);
	    Tcl_SetObjResult(interp, Tcl_NewListObj(3, r));
	} else {
	    goto devNotFound;
	}
	break;
    }

    case CMD_devices: {
	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, NULL);
	    return TCL_ERROR;
	}
	ret = ListDevices(interp);
	break;
    }

    case CMD_format: {
	if ((objc < 3) || (objc > 4)) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid ?number?");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	if (objc > 3) {
	    int i, k, fmtOk = 0, isRunning = 0;

	    if (Tcl_GetIntFromObj(interp, objv[3], &k) != TCL_OK) {
		return TCL_ERROR;
	    }
#ifdef USE_ASYNC_HANDLER
	    isRunning = wmfc->async != NULL;
#else
	    isRunning = wmfc->tid != NULL;
#endif
	    if (isRunning) {
		Tcl_SetResult(interp, "capture still running", TCL_STATIC);
		return TCL_ERROR;
	    }
	    /* Stop recording due to format change. */
	    if (wmfc->rstate > REC_STOP) {
		wmfc->rstate = REC_STOP;
	    }
	    FinishRecording(wmfc);
	    for (i = 0; i < wmfc->numFmts; i++) {
		if (wmfc->fmts[i].index == k) {
		    HRESULT hr;

		    if (wmfc->srcReader == NULL) {
			wmfc->useFmt = k;
			fmtOk = 1;
			break;
		    }
		    hr = SetFormat(wmfc->mediaSrc, wmfc->srcReader, k);
		    if (SUCCEEDED(hr)) {
			if (GetFormat(wmfc) == TCL_OK) {
			    wmfc->useFmt = k;
			    fmtOk = 1;
			} else {
			    fmtOk = -1;
			}
		    } else {
			Tcl_SetObjResult(interp,
				 Tcl_ObjPrintf("set format %d failed", k));
			fmtOk = -1;
		    }
		    break;
		}
	    }
	    if (fmtOk == 0) {
		Tcl_SetObjResult(interp,
				 Tcl_ObjPrintf("format %d not found", k));
	    }
	    ret = (fmtOk <= 0) ? TCL_ERROR : TCL_OK;
	} else {
	    int i = (wmfc->useFmt < 0) ? wmfc->fmts[0].index : wmfc->useFmt;

	    Tcl_SetObjResult(interp, Tcl_NewIntObj(i));
	}
	break;
    }

    case CMD_image: {
	if ((objc < 3) || (objc > 4)) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid ?photoImage?");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	ret = GetImage(wmfi, wmfc, (objc > 3) ? objv[3] : NULL);
	break;
    }

    case CMD_info: {
	if (objc > 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "?devid?");
	    return TCL_ERROR;
	}
	if (objc == 2) {
	    Tcl_HashSearch search;
	    Tcl_Obj *list = Tcl_NewListObj(0, NULL);

	    hPtr = Tcl_FirstHashEntry(&wmfi->wmfc, &search);
	    while (hPtr != NULL) {
		wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
		Tcl_ListObjAppendElement(NULL, list,
			Tcl_NewStringObj(wmfc->devId, -1));
		hPtr = Tcl_NextHashEntry(&search);
	    }
	    Tcl_SetObjResult(interp, list);
	} else {
	    hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	    if (hPtr != NULL) {
		Tcl_Obj *r[2];

		wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
		r[0] = Tcl_NewStringObj(Tcl_DStringValue(&wmfc->devName),
					Tcl_DStringLength(&wmfc->devName));
		r[1] = Tcl_NewStringObj(Tcl_DStringValue(&wmfc->cbCmd),
					wmfc->cbCmdLen);
		Tcl_SetObjResult(interp, Tcl_NewListObj(2, r));
	    } else {
		goto devNotFound;
	    }
	}
	break;
    }

    case CMD_listformats: {
	Tcl_Obj *dict;
	int i;

	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	dict = Tcl_NewDictObj();
	for (i = 0; i < wmfc->numFmts; i++) {
	    Tcl_DString ds;
	    char buffer[64];

	    Tcl_DStringInit(&ds);
	    Tcl_DStringAppendElement(&ds, "frame-size");
	    sprintf(buffer, "%dx%d", wmfc->fmts[i].width,
		    wmfc->fmts[i].height);
	    Tcl_DStringAppendElement(&ds, buffer);
	    Tcl_DStringAppendElement(&ds, "stride");
	    sprintf(buffer, "%d", wmfc->fmts[i].stride);
	    Tcl_DStringAppendElement(&ds, buffer);
	    Tcl_DStringAppendElement(&ds, "fourcc");
	    switch (wmfc->fmts[i].fourcc) {
	    case FOURCC_NV12:
		Tcl_DStringAppendElement(&ds, "NV12");
		break;
	    case FOURCC_YUY2:
		Tcl_DStringAppendElement(&ds, "YUY2");
		break;
	    default:
		Tcl_DStringAppendElement(&ds, "????");
		break;
	    }
	    Tcl_DStringAppendElement(&ds, "frame-rate");
	    sprintf(buffer, "%d/%d", (int) (wmfc->fmts[i].frameRate >> 32),
		    (int) (wmfc->fmts[i].frameRate & 0xffffffff));
	    Tcl_DStringAppendElement(&ds, buffer);
	    Tcl_DStringAppendElement(&ds, "frame-rate-min");
	    sprintf(buffer, "%d/%d", (int) (wmfc->fmts[i].frameRateMin >> 32),
		    (int) (wmfc->fmts[i].frameRateMin & 0xffffffff));
	    Tcl_DStringAppendElement(&ds, buffer);
	    Tcl_DStringAppendElement(&ds, "frame-rate-max");
	    sprintf(buffer, "%d/%d", (int) (wmfc->fmts[i].frameRateMax >> 32),
		    (int) (wmfc->fmts[i].frameRateMax & 0xffffffff));
	    Tcl_DStringAppendElement(&ds, buffer);
	    Tcl_DictObjPut(NULL, dict, Tcl_NewIntObj(wmfc->fmts[i].index),
			   Tcl_NewStringObj(Tcl_DStringValue(&ds),
					    Tcl_DStringLength(&ds)));
	    Tcl_DStringFree(&ds);
	}
	Tcl_SetObjResult(interp, dict);
	break;
    }

    case CMD_mirror: {
	int x, y;

	if ((objc != 3) && (objc != 5)) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid ?x y?");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	if ((objc > 3) &&
	    ((Tcl_GetBooleanFromObj(interp, objv[3], &x) != TCL_OK) ||
	     (Tcl_GetBooleanFromObj(interp, objv[4], &y) != TCL_OK))) {
	    return TCL_ERROR;
	}
	if (objc > 3) {
	    wmfc->mirror = (x ? 1 : 0) | (y ? 2 : 0);
	} else {
	    Tcl_Obj *list[2];

	    list[0] = Tcl_NewBooleanObj(wmfc->mirror & 1);
	    list[1] = Tcl_NewBooleanObj(wmfc->mirror & 2);
	    Tcl_SetObjResult(interp, Tcl_NewListObj(2, list));
	}
	break;
    }

    case CMD_open: {
	char *devName;
	int isNew, numFmts = 0;
	IMFMediaSource *mediaSrc;
	Tcl_HashSearch search;
	MediaFmt *fmts = NULL;

	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "device callback");
	    return TCL_ERROR;
	}
	devName = Tcl_GetString(objv[2]);
	hPtr = Tcl_FirstHashEntry(&wmfi->wmfc, &search);
	while (hPtr != NULL) {
	    wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	    if (strcmp(Tcl_DStringValue(&wmfc->devName), devName) == 0) {
		Tcl_SetObjResult(interp,
			Tcl_ObjPrintf("\"%s\" is already opened", devName));
		return TCL_ERROR;
	    }
	    hPtr = Tcl_NextHashEntry(&search);
	}
	mediaSrc = GetSource(devName);
	if (mediaSrc == NULL) {
	    Tcl_SetObjResult(interp,
		Tcl_ObjPrintf("error opening \"%s\"", devName));
	    return TCL_ERROR;
	}
	fmts = GetFormatList(mediaSrc, &numFmts);
	if (fmts == NULL) {
	    mediaSrc->lpVtbl->Release(mediaSrc);
	    Tcl_SetObjResult(interp,
		Tcl_ObjPrintf("error getting format list for \"%s\"", devName));
	    return TCL_ERROR;
	}
	wmfc = (WMFC *) ckalloc(sizeof(WMFC));
	memset(wmfc, 0, sizeof(WMFC));
	wmfc->mediaSrc = mediaSrc;
	wmfc->srcReader = NULL;
	wmfc->streamEnd = 0;
	wmfc->frameReady = wmfc->frameQueued = -1;
	wmfc->mirror = 0;
	wmfc->rotate = 0;
	wmfc->interp = interp;
	wmfc->cbPending = NULL;
	InitializeCriticalSection(&wmfc->srcb.lock);
	Tcl_DStringInit(&wmfc->devName);
	Tcl_DStringAppend(&wmfc->devName, devName, -1);
	Tcl_DStringInit(&wmfc->cbCmd);
	Tcl_DStringAppend(&wmfc->cbCmd, Tcl_GetString(objv[3]), -1);
	wmfc->useFmt = -1;
	wmfc->numFmts = numFmts;
	wmfc->fmts = fmts;
	wmfc->cbCmdLen = Tcl_DStringLength(&wmfc->cbCmd);
	Tcl_InitHashTable(&wmfc->ctrl, TCL_STRING_KEYS);
	InitControls(wmfc, 0);
	wmfc->rstate = REC_STOP;
	wmfc->rchan = NULL;
	Tcl_DStringInit(&wmfc->rbdStr);
	sprintf(wmfc->devId, "wmfdev%d", wmfi->idCount++);
	hPtr = Tcl_CreateHashEntry(&wmfi->wmfc, wmfc->devId, &isNew);
	Tcl_SetHashValue(hPtr, (ClientData) wmfc);
	Tcl_SetObjResult(interp, Tcl_NewStringObj(wmfc->devId, -1));
	break;
    }

    case CMD_orientation: {
	if (objc > 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid ?degrees?");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	if (objc > 3) {
	    int degrees;

	    if (Tcl_GetIntFromObj(interp, objv[3], &degrees) != TCL_OK) {
		return TCL_ERROR;
	    }
	    degrees = degrees % 360;
	    if (degrees < 45) {
		wmfc->rotate = 0;
	    } else if (degrees < 135) {
		wmfc->rotate = 90;
	    } else if (degrees < 225) {
		wmfc->rotate = 180;
	    } else if (degrees < 315) {
		wmfc->rotate = 270;
	    } else {
		wmfc->rotate = 0;
	    }
	} else {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(wmfc->rotate));
	}
	break;
    }

    case CMD_parameters: {
	if ((objc < 3) || (objc % 2 == 0)) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid ?key value ...?");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	} else {
	    Tcl_Obj *list = Tcl_NewListObj(0, NULL);

	    wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	    if (objc > 3) {
		SetControls(wmfc, objc - 3, objv + 3);
	    }
	    GetControls(wmfc, list);
	    Tcl_SetObjResult(interp, list);
	}
	break;
    }

    case CMD_record: {
	if (objc < 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid cmd ...");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr != NULL) {
	    wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	} else {
	    goto devNotFound;
	}
	if (Tcl_GetIndexFromObj(interp, objv[3], recNames, "option", 0,
				&command) != TCL_OK) {
	    return TCL_ERROR;
	}
	switch ((enum recCode) command) {
	case REC_frame:
	    if (RecordFrameFromData(wmfc, interp, objc, objv) != TCL_OK) {
		return TCL_ERROR;
	    }
	    break;
	case REC_pause:
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "devid pause");
		return TCL_ERROR;
	    }
	    if (wmfc->rstate == REC_RECORD) {
		wmfc->rstate = REC_PAUSE;
	    } else if (wmfc->rstate != REC_PAUSE) {
		Tcl_SetResult(interp, "wrong recording state for pause",
			      TCL_STATIC);
		return TCL_ERROR;
	    }
	    break;
	case REC_resume:
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "devid resume");
		return TCL_ERROR;
	    }
	    if (wmfc->rstate == REC_PAUSE) {
		int isRunning;

#ifdef USE_ASYNC_HANDLER
		isRunning = (wmfc->async != NULL);
#else
		isRunning = (wmfc->tid != NULL);
#endif
		if (isRunning) {
		    wmfc->ltv = (int) GetTickCount();
		    wmfc->rtv = wmfc->ltv;
		    wmfc->rstate = REC_RECORD;
		}
	    } else if (wmfc->rstate != REC_RECORD) {
		Tcl_SetResult(interp, "wrong recording state for resume",
			      TCL_STATIC);
		return TCL_ERROR;
	    }
	    break;
	case REC_start:
	    if (StartRecording(wmfc, interp, objc, objv) != TCL_OK) {
		return TCL_ERROR;
	    }
	    break;
	case REC_state:
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "devid state");
		return TCL_ERROR;
	    }
	    switch (wmfc->rstate) {
	    default:
	    case REC_STOP:
		Tcl_SetResult(interp, "stop", TCL_STATIC);
		break;
	    case REC_RECORD:
		Tcl_SetResult(interp, "recording", TCL_STATIC);
		break;
	    case REC_PAUSE:
		Tcl_SetResult(interp, "pause", TCL_STATIC);
		break;
	    case REC_ERROR:
		Tcl_SetResult(interp, "error", TCL_STATIC);
		break;
	    }
	    break;
	case REC_stop:
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "devid stop");
		return TCL_ERROR;
	    }
	    if (wmfc->rstate > REC_STOP) {
		wmfc->rstate = REC_STOP;
	    }
	    FinishRecording(wmfc);
	    break;
	}
	break;
    }

    case CMD_start: {
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	ret = StartCapture(wmfc);
	break;
    }

    case CMD_state: {
	char *state;

	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	if (wmfc->streamEnd) {
	    state = (wmfc->streamEnd < 0) ? "error" : "eof";
	} else {
	    state =
#ifdef USE_ASYNC_HANDLER
		(wmfc->async != NULL)
#else
		(wmfc->tid != NULL)
#endif
		? "capture" : "stop";
	}
	Tcl_SetResult(interp, state, TCL_STATIC);
	break;
    }

    case CMD_stop: {
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "devid");
	    return TCL_ERROR;
	}
	hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2]));
	if (hPtr == NULL) {
	    goto devNotFound;
	}
	wmfc = (WMFC *) Tcl_GetHashValue(hPtr);
	StopCapture(wmfc, 0);
	break;
    }

    case CMD_tophoto: {
	if (DataToPhoto(wmfi, interp, objc, objv) != TCL_OK) {
	    return TCL_ERROR;
	}
	break;
    }

    }

    return ret;
}

/*
 *-------------------------------------------------------------------------
 *
 * WmfSysInit --
 *
 *	Process wide (de)initializer function, should be called
 *	once per process with init set to true to load WMF libraries
 *	and once on process termination with init set to false to
 *	unload WMF libraries.
 *
 *-------------------------------------------------------------------------
 */

static void
WmfSysInit(int init)
{
    if (init) {
	HRESULT hr;

	WMFM.mfplat = LoadLibrary("mfplat.dll");
	if (WMFM.mfplat == NULL) {
	    goto initFailed;
	}
	WMFM.mf = LoadLibrary("mf.dll");
	if (WMFM.mf == NULL) {
	    goto initFailed;
	}
	WMFM.mfreadwrite = LoadLibrary("mfreadwrite.dll");
	if (WMFM.mfreadwrite == NULL) {
	    goto initFailed;
	}
	*((FARPROC *) &WMFM.startup) =
	    GetProcAddress(WMFM.mfplat, "MFStartup");
	if (WMFM.startup == NULL) {
	    goto initFailed;
	}
	*((FARPROC *) &WMFM.shutdown) =
	    GetProcAddress(WMFM.mfplat, "MFShutdown");
	if (WMFM.shutdown == NULL) {
	    goto initFailed;
	}
	*((FARPROC *) &WMFM.createattributes) =
	    GetProcAddress(WMFM.mfplat, "MFCreateAttributes");
	if (WMFM.createattributes == NULL) {
	    goto initFailed;
	}
	*((FARPROC *) &WMFM.enumdevicesources) =
	    GetProcAddress(WMFM.mf, "MFEnumDeviceSources");
	if (WMFM.enumdevicesources == NULL) {
	    goto initFailed;
	}
	*((FARPROC *) &WMFM.createsourcereaderfrommediasource) =
	    GetProcAddress(WMFM.mfreadwrite,
			   "MFCreateSourceReaderFromMediaSource");
	if (WMFM.createsourcereaderfrommediasource == NULL) {
	    goto initFailed;
	}
	*((FARPROC *) &WMFM.getstrideforbitmapinfoheader) =
	    GetProcAddress(WMFM.mfplat,
			   "MFGetStrideForBitmapInfoHeader");
	if (WMFM.getstrideforbitmapinfoheader == NULL) {
	    goto initFailed;
	}
	hr = MFStartup(MF_VERSION, MFSTARTUP_FULL);
	if (!SUCCEEDED(hr)) {
initFailed:
	    if (WMFM.mfplat != NULL) {
		FreeLibrary(WMFM.mfplat);
		WMFM.mfplat = NULL;
	    }
	    if (WMFM.mf != NULL) {
		FreeLibrary(WMFM.mfplat);
		WMFM.mfplat = NULL;
	    }
	    if (WMFM.mfreadwrite != NULL) {
		FreeLibrary(WMFM.mfreadwrite);
		WMFM.mfreadwrite = NULL;
	    }
	    return;
	}
	hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if ((hr != S_OK) && (hr != S_FALSE)) {
	    MFShutdown();
	    goto initFailed;
	}
	WMFM.initialized = (hr == S_OK) ? 2 : 1;
    } else if (WMFM.initialized) {
	MFShutdown();
	FreeLibrary(WMFM.mfplat);
	WMFM.mfplat = NULL;
	FreeLibrary(WMFM.mf);
	WMFM.mf = NULL;
	FreeLibrary(WMFM.mfreadwrite);
	WMFM.mfreadwrite = NULL;
	if (WMFM.initialized > 1) {
	    CoUninitialize();
	}
	WMFM.initialized = 0;
    }
}

/*
 *-------------------------------------------------------------------------
 *
 * Tclwmf_Init --
 *
 *	Module initializer
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *-------------------------------------------------------------------------
 */

int
Tclwmf_Init(Tcl_Interp *interp)
{
    WMFI *wmfi;

#ifdef USE_TCL_STUBS
    if (Tcl_InitStubs(interp, "8.4", 0) == NULL) {
	return TCL_ERROR;
    }
#else
    if (Tcl_PkgRequire(interp, "Tcl", "8.4", 0) == NULL) {
	return TCL_ERROR;
    }
#endif
    if (!WMFM.initialized) {
	Tcl_MutexLock(&wmfMutex);
	WmfSysInit(1);
	if (!WMFM.initialized) {
	    Tcl_MutexUnlock(&wmfMutex);
	    Tcl_SetResult(interp, "Windows Media Foundation not available",
			  TCL_STATIC);
	    return TCL_ERROR;
	}
	Tcl_MutexUnlock(&wmfMutex);
    }
    if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION) != TCL_OK) {
	return TCL_ERROR;
    }
    wmfi = (WMFI *) ckalloc(sizeof(WMFI));
    memset(wmfi, 0, sizeof(WMFI));
    wmfi->idCount = 0;
    wmfi->checkedTk = 0;
    Tcl_InitHashTable(&wmfi->wmfc, TCL_STRING_KEYS);
    Tcl_CreateObjCommand(interp, "wmf", WmfObjCmd,
			 (ClientData) wmfi, WmfObjCmdDeleted);
    return TCL_OK;
}

/*
 *-------------------------------------------------------------------------
 *
 * DllMain --
 *
 *	Use DLL_PROCESS_DETACH reason code to unload depending
 *	Windows Media Framework libraries.
 *
 *-------------------------------------------------------------------------
 */

BOOL WINAPI
DllMain(HINSTANCE hmod, DWORD reason, PVOID unused)
{
    if (reason == DLL_PROCESS_DETACH) {
	WmfSysInit(0);
    }
    return TRUE;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * tab-width: 8
 * End:
 */
