/*
* 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-2023 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;
int tip609;
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)
#ifndef TCL_QUEUE_TAIL_ALERT_IF_EMPTY
#define TCL_QUEUE_TAIL_ALERT_IF_EMPTY (TCL_QUEUE_TAIL | 4)
#endif
/*
* 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 ConvertFromJPEG(unsigned char *in, int inlen,
unsigned char *out, int grey, int width,
int height);
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, ConvertFromJPEG --
*
* FrameToJPEG converts a frame to JPEG. The input frame must not
* be in JPEG yet. Returns allocated and populated new frame or NULL
* on error.
*
* ConvertFromJPEG converts JPEG data to a RGB or greyscale buffer.
*
*-------------------------------------------------------------------------
*/
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;
}
/*
* ISO/IEC 10918-1:1993(E) K.3.3.
* Default Huffman tables used by MJPEG UVC devices
* which don't specify a Huffman table in the JPEG stream.
*/
static const unsigned char dc_lumi_len[] = {
0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
};
static const unsigned char dc_lumi_val[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
};
static const unsigned char dc_chromi_len[] = {
0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
};
static const unsigned char dc_chromi_val[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
};
static const unsigned char ac_lumi_len[] = {
0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d
};
static const unsigned char ac_lumi_val[] = {
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21,
0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71,
0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1,
0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72,
0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93,
0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
};
static const unsigned char ac_chromi_len[] = {
0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77
};
static const unsigned char ac_chromi_val[] = {
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31,
0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1,
0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1,
0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18,
0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa
};
#define COPY_HUFF_TBL(dinfo, tbl, name) \
do { \
if (dinfo->tbl == NULL) { \
dinfo->tbl = jpeg_alloc_huff_table((j_common_ptr) dinfo); \
} \
memcpy(dinfo->tbl->bits, name##_len, sizeof (name##_len)); \
memset(dinfo->tbl->huffval, 0, sizeof (dinfo->tbl->huffval)); \
memcpy(dinfo->tbl->huffval, name##_val, sizeof (name##_val)); \
} while (0)
static inline void
_insert_huff_tbls(j_decompress_ptr dinfo)
{
COPY_HUFF_TBL(dinfo, dc_huff_tbl_ptrs[0], dc_lumi);
COPY_HUFF_TBL(dinfo, dc_huff_tbl_ptrs[1], dc_chromi);
COPY_HUFF_TBL(dinfo, ac_huff_tbl_ptrs[0], ac_lumi);
COPY_HUFF_TBL(dinfo, ac_huff_tbl_ptrs[1], ac_chromi);
}
#undef COPY_HUFF_TBL
static int
ConvertFromJPEG(unsigned char *in, int inlen, unsigned char *out,
int grey, int width, int height)
{
struct jpeg_decompress_struct dinfo;
struct error_mgr jerr;
size_t nlines = 0;
dinfo.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)) {
goto failed;
}
jpeg_create_decompress(&dinfo);
jpeg_mem_src(&dinfo, in, inlen);
jpeg_read_header(&dinfo, TRUE);
if (dinfo.dc_huff_tbl_ptrs[0] == NULL) {
_insert_huff_tbls(&dinfo);
}
dinfo.out_color_space = grey ? JCS_GRAYSCALE : JCS_RGB;
dinfo.dct_method = JDCT_IFAST;
jpeg_start_decompress(&dinfo);
while ((dinfo.output_scanline < dinfo.output_height) &&
(dinfo.output_scanline < height)) {
unsigned char *buf[1];
int n;
buf[0] = out + nlines * width * (grey ? 1 : 3);
n = jpeg_read_scanlines(&dinfo, buf, 1);
nlines += n;
}
jpeg_finish_decompress(&dinfo);
jpeg_destroy_decompress(&dinfo);
return 1;
failed:
jpeg_destroy_decompress(&dinfo);
if (nlines > 0) {
height -= nlines;
out += width * nlines * (grey ? 1 : 3);
}
if (height > 0) {
memset(out, 0, width * height * (grey ? 1 : 3));
}
return 0;
}
/*
*-------------------------------------------------------------------------
*
* 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;
if (WMFM.tip609) {
Tcl_ThreadQueueEvent(wmfc->tid, &event->hdr,
TCL_QUEUE_TAIL_ALERT_IF_EMPTY);
} else {
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_BackgroundException(interp, ret);
}
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) ||
(subType.Data1 == FOURCC_MJPG))) {
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) &&
(subType.Data1 != FOURCC_MJPG)) {
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, int grey, Tcl_Obj *arg)
{
Tcl_Interp *interp = wmfc->interp;
Tk_PhotoHandle photo = NULL;
int fourcc, width, height, size, 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)) {
size = stride * height * (grey ? 3 : 5);
if (fourcc == FOURCC_MJPG) {
stride = width;
size = (wmfc->frame[wmfc->frameReady].length + 31) & ~31;
size += stride * height * (grey ? 1 : 3);
}
rawPtr = (unsigned char *)
attemptckalloc(stride * height * (grey ? 3 : 5));
}
if (rawPtr != NULL) {
size = stride * height;
if (fourcc == FOURCC_MJPG) {
size = wmfc->frame[wmfc->frameReady].length;
} else 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;
if (grey) {
block.pixelSize = 1;
block.offset[0] = 0;
block.offset[1] = 0;
block.offset[2] = 0;
} else {
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;
if (fourcc == FOURCC_MJPG) {
block.pixelPtr = rawPtr + ((size + 31) & ~31);
} else {
block.pixelPtr = rawPtr + 2 * width * height;
}
if (fourcc == FOURCC_MJPG) {
ConvertFromJPEG(rawPtr, size, block.pixelPtr, grey, width, height);
} else if (fourcc == FOURCC_NV12) {
/* convert NV12 -> RGB or grey */
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;
if (grey) {
*dstPtr++ = *yPtr++;
continue;
}
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 or grey */
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;
if (grey) {
*dstPtr++ = *yPtr++;
continue;
}
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;
if (fourcc == FOURCC_MJPG) {
pixelPtr = rawPtr + ((size + 31) & ~31);
} else {
pixelPtr = rawPtr + 2 * width * height;
}
if (fourcc == FOURCC_MJPG) {
ConvertFromJPEG(rawPtr, size, pixelPtr, grey, width, height);
} else if (fourcc == FOURCC_NV12) {
/* convert NV12 -> RGB or grey */
for (y = 0; y < height; y++) {
yPtr = rawPtr + y * width;
uvPtr = rawPtr + width * height + (y >> 1) * width;
dstPtr = pixelPtr + (grey ? 1 : 3) * y * width;
for (x = 0; x < width; x++) {
int red, green, blue;
if (grey) {
*dstPtr++ = *yPtr++;
continue;
}
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 or grey */
for (y = 0; y < height; y++) {
yPtr = rawPtr + y * width * 2;
dstPtr = pixelPtr + (grey ? 1 : 3) * y * width;
for (x = 0; x < width / 2; x++) {
int red, green, blue;
if (grey) {
*dstPtr++ = *yPtr++;
continue;
}
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(grey ? 1 : 3);
list[3] = Tcl_NewByteArrayObj(pixelPtr,
(grey ? 1 : 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", "greyimage",
"image", "info", "listformats", "mbcopy", "mcopy",
"mirror", "open", "orientation", "parameters",
"record", "start", "state", "stop", "tophoto", NULL
};
enum cmdCode {
CMD_close, CMD_counters, CMD_devices, CMD_format, CMD_greyimage,
CMD_image, CMD_info, CMD_listformats, CMD_mbcopy, CMD_mcopy,
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_greyimage:
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, ((enum cmdCode) command == CMD_greyimage),
(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;
case FOURCC_MJPG:
Tcl_DStringAppendElement(&ds, "MJPG");
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_mbcopy: {
int mask0, mask, i, srcLen, dstLen;
unsigned char *src, *dst;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "bytearray1 bytearray2 mask");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[4], &mask0) != TCL_OK) {
return TCL_ERROR;
}
dst = Tcl_GetByteArrayFromObj(objv[2], &dstLen);
src = Tcl_GetByteArrayFromObj(objv[3], &srcLen);
if ((srcLen != dstLen) || (srcLen % 3)) {
Tcl_SetResult(interp, "incompatible bytearrays", TCL_STATIC);
return TCL_ERROR;
}
mask = (mask0 >> 16) & 0xff; /* red */
if (mask != 0) {
for (i = 0; i < srcLen; i += 3) {
dst[i] = (dst[i] & (~mask)) | (src[i] & mask);
}
}
mask = (mask0 >> 8) & 0xff; /* green */
if (mask != 0) {
for (i = 1; i < srcLen; i += 3) {
dst[i] = (dst[i] & (~mask)) | (src[i] & mask);
}
}
mask = mask0 & 0xff; /* blue */
if (mask != 0) {
for (i = 2; i < srcLen; i += 3) {
dst[i] = (dst[i] & (~mask)) | (src[i] & mask);
}
}
break;
}
case CMD_mcopy: {
char *name;
Tk_PhotoHandle ph1, ph2;
int mask0, mask, nops = 0, x, y;
Tk_PhotoImageBlock block1, block2;
unsigned char *src, *dst;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "photo1 photo2 mask");
return TCL_ERROR;
}
if (CheckForTk(wmfi, interp) != TCL_OK) {
return TCL_ERROR;
}
name = Tcl_GetString(objv[2]);
ph1 = Tk_FindPhoto(interp, name);
if (ph1 == NULL) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("can't use \"%s\": not a photo image", name));
return TCL_ERROR;
}
name = Tcl_GetString(objv[3]);
ph2 = Tk_FindPhoto(interp, name);
if (ph2 == NULL) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("can't use \"%s\": not a photo image", name));
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[4], &mask0) != TCL_OK) {
return TCL_ERROR;
}
Tk_PhotoGetImage(ph1, &block1);
Tk_PhotoGetImage(ph2, &block2);
if ((block1.width != block2.width) ||
(block1.height != block2.height) ||
(block1.pixelSize != block2.pixelSize) ||
(block1.pixelSize != 4)) {
Tcl_SetResult(interp, "incompatible photo images", TCL_STATIC);
return TCL_ERROR;
}
mask = (mask0 >> 24) & 0xff; /* alpha */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[3];
src += block2.offset[3];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
mask = (mask0 >> 16) & 0xff; /* red */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[0];
src += block2.offset[0];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
mask = (mask0 >> 8) & 0xff; /* green */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[1];
src += block2.offset[1];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
mask = mask0 & 0xff; /* blue */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[2];
src += block2.offset[2];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
if (nops) {
ret = Tk_PhotoPutBlock(interp, ph1, &block1, 0, 0,
block1.width, block1.height,
TK_PHOTO_COMPOSITE_SET);
}
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], °rees) != 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.
*
*-------------------------------------------------------------------------
*/
DLLEXPORT 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) {
int major = 0, minor = 0;
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_GetVersion(&major, &minor, NULL, NULL);
if ((major > 8) || ((major == 8) && (minor > 6))) {
WMFM.tip609 = 1;
} else {
const char *val =
Tcl_GetVar2(interp, "tcl_platform", "tip609", TCL_GLOBAL_ONLY);
if ((val != NULL) && *val && (*val != '0')) {
WMFM.tip609 = 1;
}
}
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;
}
/*
*-------------------------------------------------------------------------
*
* Tclwmf_Unload --
*
* Tear down Windows Media Framework libraries on unloading
* of this module.
*
*-------------------------------------------------------------------------
*/
DLLEXPORT int
Tclwmf_Unload(Tcl_Interp *interp, int flags)
{
Tcl_MutexLock(&wmfMutex);
if (WMFM.initialized) {
WmfSysInit(0);
}
Tcl_MutexUnlock(&wmfMutex);
return TCL_OK;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* tab-width: 8
* End:
*/