/* * tclwmf.c -- * * This file contains the implementation of the "wmf" Tcl * built-in command which allows to operate cameras using * the Windows Media Foundation. * * Copyright (c) 2016-2019 Christian Werner * * 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 #include #include #include #include #undef EXTERN_GUID #define EXTERN_GUID DEFINE_GUID #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmultichar" #include #pragma GCC diagnostic pop #include #include #include #include #if defined(__GNUC__) /* These UUIDs are missing in current mingw64 header files */ DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, 0xc60ac5fe, 0x252a, 0x478f, 0xa0, 0xef, 0xbc, 0x8f, 0xa5, 0xf7, 0xca, 0xd3); DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID, 0x8ac3587a, 0x4ae7, 0x42d8, 0x99, 0xe0, 0x0a, 0x60, 0x13, 0xee, 0xf9, 0x0f); DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, 0x60d0e559, 0x52f8, 0x4fa2, 0xbb, 0xce, 0xac, 0xdb, 0x34, 0xa8, 0xec, 0x1); DEFINE_GUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, 0x58f0aad8, 0x22bf, 0x4f8a, 0xbb, 0x3d, 0xd2, 0xc4, 0x97, 0x8c, 0x6e, 0x2f); #endif /* These are private defs since some versions of mingw define it, some not */ DEFINE_GUID(PRIVATE_IID_IAMVideoProcAmp, 0xc6e13360, 0x30ac, 0x11d0, 0xa1, 0x8c, 0x00, 0xa0, 0xc9, 0x11, 0x89, 0x56); DEFINE_GUID(PRIVATE_IID_IAMCameraControl, 0xc6e13370, 0x30ac, 0x11d0, 0xa1, 0x8c, 0x00, 0xa0, 0xc9, 0x11, 0x89, 0x56); #ifndef TCL_THREADS #error "build requires TCL_THREADS" #endif /* * RIFF/AVI structures and constants. */ static void inline PUT16LE(unsigned short *p, unsigned short v) { unsigned char b[2]; b[0] = v & 0xff; b[1] = (v >> 8) & 0xff; memcpy(p, b, 2); } static void inline PUT32LE(unsigned int *p, unsigned int v) { unsigned char b[4]; b[0] = v & 0xff; b[1] = (v >> 8) & 0xff; b[2] = (v >> 16) & 0xff; b[3] = (v >> 24) & 0xff; memcpy(p, b, 4); } struct RIFF_avih { unsigned int uspf; /* us per frame. */ unsigned int bps; /* Data rate. */ unsigned int res0; unsigned int flags; unsigned int nframes; /* Number of frames. */ unsigned int res1; unsigned int nstreams; unsigned int bufsize; unsigned int width; unsigned int height; unsigned int scale; unsigned int rate; unsigned int start; unsigned int length; }; struct RIFF_strh { unsigned char type[4]; unsigned char handler[4]; unsigned int flags; unsigned int priority; unsigned int res0; unsigned int scale; unsigned int rate; unsigned int start; unsigned int length; unsigned int bufsize; unsigned int quality; unsigned int samplesize; }; struct RIFF_strf_vids { unsigned int size; unsigned int width; unsigned int height; unsigned short planes; unsigned short bits; unsigned char compr[4]; unsigned int image_size; unsigned int xpels_meter; unsigned int ypels_meter; unsigned int num_colors; unsigned int imp_colors; }; struct AVI_HDR { unsigned char riff_id[4]; unsigned int riff_size; unsigned char riff_type[4]; unsigned char hdrl_list_id[4]; unsigned int hdrl_size; unsigned char hdrl_type[4]; unsigned char avih_id[4]; unsigned int avih_size; struct RIFF_avih avih; }; struct AVIX_HDR { unsigned char riff_id[4]; unsigned int riff_size; unsigned char riff_type[4]; unsigned char data_list_id[4]; unsigned int data_size; unsigned char data_type[4]; }; struct AVI_HDR_VIDEO { unsigned char strl_list_id[4]; unsigned int strl_size; unsigned char strl_type[4]; unsigned char strh_id[4]; unsigned int strh_size; struct RIFF_strh strh; unsigned char strf_id[4]; unsigned int strf_size; struct RIFF_strf_vids strf; }; struct AVI_HDR_ODML { unsigned char strl_list_id[4]; unsigned int strl_size; unsigned char strl_type[4]; unsigned char strh_id[4]; unsigned int strh_size; unsigned int nframes; }; struct AVI_DATA { unsigned char data_list_id[4]; unsigned int data_size; unsigned char data_type[4]; }; struct CHUNK_HDR { unsigned char id[4]; unsigned int size; }; struct AVI_IDX { unsigned char id[4]; unsigned int flags; unsigned int offset; unsigned int size; }; /* * NB: as of Tcl 8.6.5 asynchronous event handlers (Tcl_AsyncCreate()) * are unreliable since they can break the Tcl catch command. * If USE_ASYNC_HANDLER is undefined, Tcl_ThreadQueueEvent() is used * instead and runtime behaviour should be almost identical. */ #undef USE_ASYNC_HANDLER /* * FourCCs in little-endian. */ #define FOURCC_NV12 0x3231564e #define FOURCC_YUY2 0x32595559 #define FOURCC_MJPG 0x47504a4d #define FOURCC_RGB0 0x00000000 /* Internally used. */ /* * Per process info for dynamically linked WMF DLLs. */ static struct { int initialized; HMODULE mfplat; /* mfplat.dll */ HMODULE mf; /* mf.dll */ HMODULE mfreadwrite; /* mfreadwrite.dll */ HRESULT WINAPI (*startup)(ULONG, DWORD); HRESULT WINAPI (*shutdown)(void); HRESULT WINAPI (*createattributes)(IMFAttributes **, UINT32); HRESULT WINAPI (*enumdevicesources)(IMFAttributes *, IMFActivate ***, UINT32 *); HRESULT WINAPI (*createsourcereaderfrommediasource)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **); HRESULT WINAPI (*getstrideforbitmapinfoheader)(DWORD, DWORD, LONG *); } WMFM = { 0, 0, NULL, NULL, NULL }; #define MFStartup WMFM.startup #define MFShutdown WMFM.shutdown #define MFCreateAttributes WMFM.createattributes #define MFEnumDeviceSources WMFM.enumdevicesources #define MFCreateSourceReaderFromMediaSource \ WMFM.createsourcereaderfrommediasource #define MFGetStrideForBitmapInfoHeader \ WMFM.getstrideforbitmapinfoheader TCL_DECLARE_MUTEX(wmfMutex) /* * IMFSourceReaderCallback implementation. */ typedef struct { interface IMFSourceReaderCallback isrcb; /* Interface, methods. */ CRITICAL_SECTION lock; /* Mutex. */ int refCount; /* Reference count. */ int closing; /* True during close. */ struct WMFC *wmfc; /* Pointer to WMF struct. */ } SourceReaderCB; /* * Frame buffer. */ typedef struct { unsigned char *data; /* Pixel buffer. */ int length; /* Used length of pixel buffer. */ int maxLength; /* Max. length of pixel buffer. */ int ready; /* Zero when free, positive when filled. */ int fourcc; /* Media format code. */ int width, height; /* Width and height in pixels. */ int stride; /* Line increment in bytes. */ } Frame; /* * Media format. */ typedef struct { int index; /* Index for activation. */ int fourcc; /* Four character code. */ int width, height; /* Width and height in pixels. */ int stride; /* Line increment in bytes. */ UINT64 frameRate; /* Rate (nominator/denominator). */ UINT64 frameRateMin; /* Min. rate (nominator/denominator). */ UINT64 frameRateMax; /* Max. rate (nominator/denominator). */ } MediaFmt; /* * Control structure for device control. */ typedef struct { int code; /* Selector. */ int iscam; /* Camera control, if true. */ const char *name; /* Name of control, lower case. */ long min, max; /* Minimum/maximum value. */ long step; /* Increment value. */ long def; /* Default value. */ long caps; /* Capabilities. */ } WCTRL; /* * Recording states. */ #define REC_STOP 0 #define REC_RECORD 1 #define REC_PAUSE 2 #define REC_ERROR 3 /* * Control structure for capture. */ typedef struct WMFC { IMFMediaSource *mediaSrc; /* Handle for device. */ IMFSourceReader *srcReader; /* Handle for capture. */ Tcl_Interp *interp; /* Interpreter for this object. */ char devId[32]; /* Device id. */ Tcl_DString devName; /* Device name. */ int cbCmdLen; /* Initial length of callback command. */ Tcl_DString cbCmd; /* Callback command prefix. */ Tcl_TimerToken cbPending; /* Set when callback dispatched. */ int fourcc; /* Media format code. */ int stride, width, height; /* Image dimensions. */ int mirror; /* Image mirror flags. */ int rotate; /* Image rotation in degrees. */ Tcl_WideInt counters[4]; /* Statistic counters. */ int useFmt; /* MediaFmt index to use. */ int numFmts; /* Number formats in fmts. */ MediaFmt *fmts; /* Array of MediaFmts or NULL. */ #ifdef USE_ASYNC_HANDLER Tcl_AsyncHandler async; /* For signalling captured image. */ #else Tcl_ThreadId tid; /* Thread identifier of interp. */ #endif Tcl_HashTable ctrl; /* Device controls. */ SourceReaderCB srcb; /* Callback interface object. */ int streamEnd; /* Indicates capture error or EOF. */ int frameReady; /* Frame index with data or -1. */ int frameQueued; /* Next frame index to be filled. */ Frame frame[2]; /* Frame buffers. */ /* Info for recording to channel (file or socket) follows. */ int rstate; /* Recording state. */ int ruser; /* True, when user writes frames. */ Tcl_Channel rchan; /* Recording channel or NULL. */ Tcl_DString rbdStr; /* Frame boundary string. */ int rrate; /* Recording frame rate. */ int rtv; /* Target time for next frame. */ int ltv; /* Time of last frame read. */ struct { Tcl_WideInt nframes; Tcl_WideInt nframes0; Tcl_WideInt totsize; Tcl_WideInt segsize; Tcl_WideInt segsize0; Tcl_WideInt segstart; int hdrsize; Tcl_WideInt pos0; int rate; struct AVI_HDR avi_hdr; struct AVI_HDR_VIDEO avi_hdrv; struct AVI_HDR_ODML avi_hdro; struct AVI_DATA avi_data; int idx_off; int curr_idx, num_idx; struct AVI_IDX *idx; } avi; /* AVI file writer. */ } WMFC; #ifndef USE_ASYNC_HANDLER typedef struct { Tcl_Event hdr; /* Generic event header. */ WMFC *wmfc; /* Pointer to control structure. */ } WMFEVT; #endif /* * Per interpreter control structure. */ typedef struct { int idCount; /* Counter for unique handles. */ Tcl_HashTable wmfc; /* List of active WMFC instances. */ int checkedTk; /* Non-zero when Tk availability checked. */ } WMFI; /* * Forward declarations. */ static int CheckForTk(WMFI *wmfi, Tcl_Interp *interp); static void CloseAVISegment(WMFC *wmfc, int end); static Frame * FrameToJPEG(Frame *in, Frame *out); static int WriteFrame(WMFC *wmfc, Frame *frame); static int StartRecording(WMFC *wmfc, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]); static void WriteAVIHeader(WMFC *wmfc, int end); static void FinishRecording(WMFC *wmfc); static int RecordFrameFromData(WMFC *wmfc, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]); static int DataToPhoto(WMFI *wmfi, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]); #ifdef USE_ASYNC_HANDLER static int SignalBuffer(ClientData clientData, Tcl_Interp *interp, int code); #else static int SignalBuffer(Tcl_Event *evPtr, int flags); #endif static int StopCapture(WMFC *wmfc, int closing); /* *------------------------------------------------------------------------- * * CheckForTk -- * * Check availability of Tk. Return standard Tcl error * and appropriate error message if unavailable. * *------------------------------------------------------------------------- */ static int CheckForTk(WMFI *wmfi, Tcl_Interp *interp) { if (wmfi->checkedTk > 0) { return TCL_OK; } else if (wmfi->checkedTk < 0) { Tcl_SetResult(interp, "can't find package Tk", TCL_STATIC); return TCL_ERROR; } #ifdef USE_TK_STUBS if (Tk_InitStubs(interp, "8.4", 0) == NULL) { wmfi->checkedTk = -1; return TCL_ERROR; } #else if (Tcl_PkgRequire(interp, "Tk", "8.4", 0) == NULL) { wmfi->checkedTk = -1; return TCL_ERROR; } #endif wmfi->checkedTk = 1; return TCL_OK; } /* *------------------------------------------------------------------------- * * CloseAVISegment -- * * Recording: close current and optionally start next 2G AVI * segment in output file. * *------------------------------------------------------------------------- */ static void CloseAVISegment(WMFC *wmfc, int end) { int toWrite = 0, written = 0; Tcl_WideInt pos; struct AVIX_HDR xhdr; static const struct AVIX_HDR xhdr0 = { { 'R', 'I', 'F', 'F' }, 0, { 'A', 'V', 'I', 'X' }, { 'L', 'I', 'S', 'T' }, 0, { 'm', 'o', 'v', 'i' } }; pos = Tcl_Seek(wmfc->rchan, 0, SEEK_CUR); if (wmfc->avi.totsize > wmfc->avi.segsize) { Tcl_Seek(wmfc->rchan, wmfc->avi.segstart, SEEK_SET); xhdr = xhdr0; PUT32LE(&xhdr.riff_size, wmfc->avi.segsize + 16); PUT32LE(&xhdr.data_size, wmfc->avi.segsize + 4); toWrite = sizeof(xhdr); written = Tcl_WriteRaw(wmfc->rchan, (const char *) &xhdr, toWrite); Tcl_Seek(wmfc->rchan, pos, SEEK_SET); } else { wmfc->avi.nframes0 = wmfc->avi.nframes; wmfc->avi.segsize0 = wmfc->avi.segsize; WriteAVIHeader(wmfc, 0); if (wmfc->rstate == REC_ERROR) { return; } } wmfc->avi.segsize = 0; wmfc->avi.segstart = pos; if (!end && (written == toWrite)) { xhdr = xhdr0; toWrite = sizeof(xhdr); written = Tcl_WriteRaw(wmfc->rchan, (const char *) &xhdr, toWrite); } if (written != toWrite) { wmfc->rstate = REC_ERROR; } } /* *------------------------------------------------------------------------- * * FrameToJPEG -- * * Convert frame to JPEG. Input frame must not be JPEG yet. * Returns allocated and populated new frame or NULL on error. * *------------------------------------------------------------------------- */ struct error_mgr { struct jpeg_error_mgr super; jmp_buf jmp; }; static void _output_message(j_common_ptr info) { } static void _format_message(j_common_ptr info, char *buffer) { buffer[0] = '\0'; } static void _error_exit(j_common_ptr info) { struct error_mgr *jerr = (struct error_mgr *) info->err; (*info->err->output_message)(info); longjmp(jerr->jmp, 1); } struct _compr { struct jpeg_compress_struct cinfo; struct jpeg_destination_mgr dmgr; Frame *out; }; static void _dst_init(j_compress_ptr cinfo) { struct _compr *c = (struct _compr *) cinfo; c->dmgr.next_output_byte = c->out->data; c->dmgr.free_in_buffer = c->out->maxLength; } static boolean _dst_empty(j_compress_ptr cinfo) { return TRUE; } static void _dst_term(j_compress_ptr cinfo) { struct _compr *c = (struct _compr *) cinfo; c->out->length = c->dmgr.next_output_byte - (JOCTET *) c->out->data; } static Frame * FrameToJPEG(Frame *in, Frame *out) { Frame tmpFrame; struct _compr cinfo; struct error_mgr jerr; if (in->fourcc == FOURCC_MJPG) { return NULL; } tmpFrame.data = NULL; if (in->fourcc == FOURCC_NV12) { unsigned char *yPtr, *uvPtr, *dstPtr; int x, y, yVal, uVal = 0, vVal = 0; /* convert to RGB first */ tmpFrame = *in; tmpFrame.length = in->width * in->height * 3; tmpFrame.maxLength = tmpFrame.length; tmpFrame.stride = in->width * 3; tmpFrame.fourcc = FOURCC_RGB0; tmpFrame.data = attemptckalloc(tmpFrame.length); if (tmpFrame.data == NULL) { return NULL; } for (y = 0; y < in->height; y++) { yPtr = in->data + y * in->stride; uvPtr = in->data + y * in->stride * in->height + (y >> 1) * in->stride; dstPtr = tmpFrame.data + y * tmpFrame.stride; for (x = 0; x < in->width; x++) { int red, green, blue; yVal = *yPtr++; yVal -= 16; if (yVal < 0) { yVal = 0; } if ((x & 1) == 0) { uVal = *uvPtr++ - 128; vVal = *uvPtr++ - 128; } yVal *= 1192; red = yVal + 1634 * vVal; green = yVal - 833 * vVal - 400 * uVal; blue = yVal + 2066 * uVal; if (red < 0) { red = 0; } else if (red > 0x3ffff) { red = 0x3ffff; } *dstPtr++ = red >> 10; if (green < 0) { green = 0; } else if (green > 0x3ffff) { green = 0x3ffff; } *dstPtr++ = green >> 10; if (blue < 0) { blue = 0; } else if (blue > 0x3ffff) { blue = 0x3ffff; } *dstPtr++ = blue >> 10; } } in = &tmpFrame; } else if (in->fourcc == FOURCC_YUY2) { unsigned char *yPtr, *dstPtr; int x, y, yVal, yVal2, uVal = 0, vVal = 0; /* convert to RGB first */ tmpFrame = *in; tmpFrame.length = in->width * in->height * 3; tmpFrame.maxLength = tmpFrame.length; tmpFrame.stride = in->width * 3; tmpFrame.fourcc = FOURCC_RGB0; tmpFrame.data = attemptckalloc(tmpFrame.length); if (tmpFrame.data == NULL) { return NULL; } for (y = 0; y < in->height; y++) { yPtr = in->data + y * in->stride; dstPtr = tmpFrame.data + y * tmpFrame.stride; for (x = 0; x < in->width; x++) { int red, green, blue; yVal = *yPtr++; yVal -= 16; if (yVal < 0) { yVal = 0; } uVal = *yPtr++ - 128; yVal2 = *yPtr++; yVal2 -= 16; if (yVal2 < 0) { yVal2 = 0; } vVal = *yPtr++ - 128; red = 298 * yVal + 409 * vVal + 128; green = 298 * yVal - 100 * uVal - 208 * vVal + 128; blue = 298 * yVal + 516 * uVal + 128; if (red < 0) { red = 0; } else if (red > 0xffff) { red = 0xffff; } *dstPtr++ = red >> 8; if (green < 0) { green = 0; } else if (green > 0xffff) { green = 0xffff; } *dstPtr++ = green >> 8; if (blue < 0) { blue = 0; } else if (blue > 0xffff) { blue = 0xffff; } *dstPtr++ = blue >> 8; red = 298 * yVal2 + 409 * vVal + 128; green = 298 * yVal2 - 100 * uVal - 208 * vVal + 128; blue = 298 * yVal2 + 516 * uVal + 128; if (red < 0) { red = 0; } else if (red > 0xffff) { red = 0xffff; } *dstPtr++ = red >> 8; if (green < 0) { green = 0; } else if (green > 0xffff) { green = 0xffff; } *dstPtr++ = green >> 8; if (blue < 0) { blue = 0; } else if (blue > 0xffff) { blue = 0xffff; } *dstPtr++ = blue >> 8; } } in = &tmpFrame; } else if (in->fourcc != FOURCC_RGB0) { return NULL; } out->ready = 1; out->fourcc = FOURCC_MJPG; out->width = in->width; out->height = in->height; out->stride = 0; out->maxLength = out->width * out->height; out->data = attemptckalloc(out->maxLength); if (out->data == NULL) { if (tmpFrame.data != NULL) { ckfree(tmpFrame.data); } return NULL; } cinfo.out = out; cinfo.dmgr.init_destination = _dst_init; cinfo.dmgr.empty_output_buffer = _dst_empty; cinfo.dmgr.term_destination = _dst_term; cinfo.cinfo.err = jpeg_std_error(&jerr.super); jerr.super.output_message = _output_message; jerr.super.format_message = _format_message; jerr.super.error_exit = _error_exit; if (setjmp(jerr.jmp)) { ckfree(out->data); if (tmpFrame.data != NULL) { ckfree(tmpFrame.data); } return NULL; } jpeg_create_compress(&cinfo.cinfo); cinfo.cinfo.in_color_space = JCS_RGB; cinfo.cinfo.image_width = out->width; cinfo.cinfo.image_height = out->height; cinfo.cinfo.input_components = 3; jpeg_set_defaults(&cinfo.cinfo); cinfo.cinfo.dest = &cinfo.dmgr; jpeg_start_compress(&cinfo.cinfo, TRUE); while (cinfo.cinfo.next_scanline < cinfo.cinfo.image_height) { JSAMPROW row_pointer[1]; row_pointer[0] = in->data + cinfo.cinfo.next_scanline * in->stride; jpeg_write_scanlines(&cinfo.cinfo, row_pointer, 1); } jpeg_finish_compress(&cinfo.cinfo); jpeg_destroy_compress(&cinfo.cinfo); if (tmpFrame.data != NULL) { ckfree(tmpFrame.data); } return out; } /* *------------------------------------------------------------------------- * * WriteFrame -- * * Recording: write given frame onto recording output channel. * The recording frame rate may differ from the hardware frame * rate. Thus, some time calculation takes place here to write * a frame when time is due to the configured recording frame * rate. The result is 1 if a frame was written, 0 if skipped * due to timing constraints, or -1 on write error. * *------------------------------------------------------------------------- */ static int WriteFrame(WMFC *wmfc, Frame *frame) { int n, toWrite, written, fWritten; char buffer[256]; int now, diff; Frame *newFrame = NULL, fBuf[1]; if (wmfc->rchan == NULL) { wmfc->rstate = REC_ERROR; } now = (int) GetTickCount(); diff = wmfc->rtv - now; if (diff > 0) { return (wmfc->rstate == REC_ERROR) ? -1 : 0; } wmfc->rtv = now; diff = wmfc->rtv - wmfc->ltv; wmfc->ltv = wmfc->rtv; wmfc->rtv += wmfc->rrate; if (frame->length == 0) { return 0; } if (wmfc->rstate == REC_ERROR) { return -1; } if (Tcl_DStringLength(&wmfc->rbdStr) > 0) { /* * HTTP MJPEG streaming webcam mode. */ if (frame->fourcc != FOURCC_MJPG) { newFrame = FrameToJPEG(frame, fBuf); if (newFrame == NULL) { wmfc->rstate = REC_ERROR; return -1; } frame = newFrame; } n = Tcl_DStringLength(&wmfc->rbdStr); sprintf(buffer, "\r\nContent-type: image/jpeg\r\n" "Content-length: %d\r\n\r\n", (int) frame->length); Tcl_DStringAppend(&wmfc->rbdStr, buffer, -1); toWrite = Tcl_DStringLength(&wmfc->rbdStr); written = Tcl_WriteRaw(wmfc->rchan, Tcl_DStringValue(&wmfc->rbdStr), toWrite); Tcl_DStringSetLength(&wmfc->rbdStr, n); if (written == toWrite) { toWrite = frame->length; written = Tcl_WriteRaw(wmfc->rchan, (const char *) frame->data, toWrite); } } else { /* * AVI file. */ int size, sizea; struct CHUNK_HDR hdr; static const struct CHUNK_HDR hdr0 = { { '0', '0', 'd', 'b' }, 0 }; if (frame->fourcc == FOURCC_MJPG) { size = frame->length; } else if (memcmp(&wmfc->avi.avi_hdrv.strh.handler, "MJPG", 4) == 0) { newFrame = FrameToJPEG(frame, fBuf); if (newFrame == NULL) { wmfc->rstate = REC_ERROR; return -1; } frame = newFrame; size = frame->length; } else { size = frame->height * frame->stride; } sizea = (size + 3) & ~3; hdr = hdr0; PUT32LE(&hdr.size, sizea); fWritten = 0; toWrite = sizeof(hdr); written = Tcl_WriteRaw(wmfc->rchan, (const char *) &hdr, toWrite); if (written == toWrite) { toWrite = size; written = Tcl_WriteRaw(wmfc->rchan, (const char *) frame->data, toWrite); fWritten = written; } /* Align to next 32 bit boundary. */ if ((written == toWrite) && (sizea > size)) { static const char four0[4] = { 0, 0, 0, 0 }; toWrite = sizea - size; written = Tcl_WriteRaw(wmfc->rchan, four0, toWrite); } wmfc->avi.nframes++; wmfc->avi.totsize += sizea + sizeof(hdr); wmfc->avi.segsize += sizea + sizeof(hdr); if (fWritten == size) { if (wmfc->avi.segsize > 0x7F000000) { CloseAVISegment(wmfc, 0); wmfc->avi.curr_idx = wmfc->avi.num_idx = 0; if (wmfc->avi.idx != NULL) { ckfree((char *) wmfc->avi.idx); wmfc->avi.idx = NULL; } } else if (wmfc->avi.totsize == wmfc->avi.segsize) { /* Add index entry. */ if (wmfc->avi.curr_idx >= wmfc->avi.num_idx) { int newsize = wmfc->avi.num_idx + 512; struct AVI_IDX *newidx; newidx = attemptckrealloc((char *) wmfc->avi.idx, newsize * sizeof(struct AVI_IDX)); if (newidx == NULL) { wmfc->avi.curr_idx = wmfc->avi.num_idx = 0; if (wmfc->avi.idx != NULL) { ckfree((char *) wmfc->avi.idx); wmfc->avi.idx = NULL; } } else { wmfc->avi.num_idx = newsize; wmfc->avi.idx = newidx; } } if (wmfc->avi.idx != NULL) { struct AVI_IDX *idx = wmfc->avi.idx + wmfc->avi.curr_idx; memcpy(idx->id, hdr0.id, sizeof(hdr0.id)); PUT32LE(&idx->flags, 0); PUT32LE(&idx->offset, wmfc->avi.idx_off); PUT32LE(&idx->size, sizea); wmfc->avi.curr_idx++; wmfc->avi.idx_off += sizea + sizeof(struct CHUNK_HDR); } } } /* Compute average frame rate. */ if (wmfc->avi.nframes == 0) { wmfc->avi.rate = diff; } else { wmfc->avi.rate += diff; wmfc->avi.rate /= 2; } } if (written != toWrite) { wmfc->rstate = REC_ERROR; } if (newFrame != NULL) { ckfree(newFrame->data); } return (wmfc->rstate == REC_ERROR) ? -1 : 1; } /* *------------------------------------------------------------------------- * * StartRecording -- * * Prepare recording from given parameters (channel, mode, etc.) * The channel used for writing frames is detached from the * calling interpreter. Timing computations for adapting * the recording frame rate are performed. * *------------------------------------------------------------------------- */ static int StartRecording(WMFC *wmfc, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { int i, mode, doMJPG = 0, doUser = 0; double rate = 0, ratef; const char *p, *rbdStr = NULL; Tcl_Channel chan = NULL, stack[2]; Tcl_WideInt pos0 = 0; if (objc < 5) { Tcl_WrongNumArgs(interp, 2, objv, "devid start ..."); return TCL_ERROR; } for (i = 4; i < objc; i++) { p = Tcl_GetString(objv[i]); if (strcmp(p, "-mjpeg") == 0) { doMJPG++; } else if (strcmp(p, "-user") == 0) { doMJPG++; doUser++; } else if (strcmp(p, "-fps") == 0) { if (++i >= objc) { Tcl_SetResult(interp, "-fps option needs a value", TCL_STATIC); return TCL_ERROR; } if (Tcl_GetDoubleFromObj(interp, objv[i], &rate) != TCL_OK) { return TCL_ERROR; } } else if (strcmp(p, "-boundary") == 0) { if (++i >= objc) { Tcl_SetResult(interp, "-boundary option needs a value", TCL_STATIC); return TCL_ERROR; } rbdStr = Tcl_GetString(objv[i]); } else if (strcmp(p, "-chan") == 0) { if (++i >= objc) { Tcl_SetResult(interp, "-chan option needs a value", TCL_STATIC); return TCL_ERROR; } chan = Tcl_GetChannel(interp, Tcl_GetString(objv[i]), &mode); if (chan == NULL) { return TCL_ERROR; } if ((mode & TCL_WRITABLE) == 0) { Tcl_SetResult(interp, "channel is not writable", TCL_STATIC); return TCL_ERROR; } } } if (chan == NULL) { Tcl_SetResult(interp, "no channel given", TCL_STATIC); return TCL_ERROR; } stack[0] = Tcl_GetTopChannel(chan); stack[1] = Tcl_GetStackedChannel(chan); if (((stack[0] != NULL) && (stack[0] != chan)) || (stack[1] != NULL)) { Tcl_SetResult(interp, "stacked channels are not supported", TCL_STATIC); return TCL_ERROR; } if ((Tcl_SetChannelOption(interp, chan, "-blocking", "0") != TCL_OK) || (Tcl_SetChannelOption(interp, chan, "-buffering", "none") != TCL_OK) || (Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK)) { return TCL_ERROR; } if ((rbdStr == NULL) || (strlen(rbdStr) == 0)) { pos0 = Tcl_Seek(chan, 0, SEEK_CUR); if (pos0 == (Tcl_WideInt) -1) { Tcl_SetResult(interp, "not a random access channel", TCL_STATIC); return TCL_ERROR; } } if (Tcl_DetachChannel(interp, chan) != TCL_OK) { Tcl_SetResult(interp, "cannot detach channel", TCL_STATIC); return TCL_ERROR; } FinishRecording(wmfc); wmfc->rchan = chan; ratef = (wmfc->fmts[wmfc->useFmt].frameRate >> 32); ratef /= (wmfc->fmts[wmfc->useFmt].frameRate & 0xffffffff); if ((rate > 0.0) && (rate < ratef)) { wmfc->rrate = rate * 1000; } else if (ratef <= 0) { wmfc->rrate = 1000; } else { wmfc->rrate = 1000 / ratef; } if ((rbdStr != NULL) && (strlen(rbdStr) > 0)) { Tcl_DStringAppend(&wmfc->rbdStr, rbdStr, -1); } else { int n; static const struct AVI_HDR avi_hdr = { { 'R', 'I', 'F', 'F' }, 0, { 'A', 'V', 'I', ' ' }, { 'L', 'I', 'S', 'T' }, 0, { 'h', 'd', 'r', 'l' }, { 'a', 'v', 'i', 'h' }, 0, { } }; static const struct AVI_HDR_VIDEO avi_hdrv = { { 'L', 'I', 'S', 'T' }, 0, { 's', 't', 'r', 'l' }, { 's', 't', 'r', 'h' }, 0, { { 'v', 'i', 'd', 's' } }, { 's', 't', 'r', 'f' }, 0, { } }; static const struct AVI_HDR_ODML avi_hdro = { { 'L', 'I', 'S', 'T' }, 0, { 'o', 'd', 'm', 'l' }, { 'd', 'm', 'l', 'h' }, 0, 0 }; static const struct AVI_DATA avi_data = { { 'L', 'I', 'S', 'T' }, 0, { 'm', 'o', 'v', 'i' }, }; /* Setup AVI writer. */ wmfc->avi.pos0 = pos0; wmfc->avi.avi_hdr = avi_hdr; PUT32LE(&wmfc->avi.avi_hdr.avih_size, sizeof(struct RIFF_avih)); wmfc->avi.avi_hdrv = avi_hdrv; PUT32LE(&wmfc->avi.avi_hdrv.strl_size, sizeof(struct RIFF_strh) + sizeof(struct RIFF_strf_vids) + 20); PUT32LE(&wmfc->avi.avi_hdrv.strh_size, sizeof(struct RIFF_strh)); PUT32LE(&wmfc->avi.avi_hdrv.strf_size, sizeof(struct RIFF_strf_vids)); wmfc->avi.avi_hdro = avi_hdro; PUT32LE(&wmfc->avi.avi_hdro.strl_size, sizeof(unsigned int) + 12); PUT32LE(&wmfc->avi.avi_hdro.strh_size, sizeof(unsigned int)); wmfc->avi.avi_data = avi_data; PUT32LE(&wmfc->avi.avi_hdr.avih.width, wmfc->width); PUT32LE(&wmfc->avi.avi_hdr.avih.height, wmfc->height); n = wmfc->rrate * 1000; PUT32LE(&wmfc->avi.avi_hdr.avih.uspf, n); n = 24 * n / 1000; n = n * wmfc->width * wmfc->height; PUT32LE(&wmfc->avi.avi_hdr.avih.bps, n); PUT32LE(&wmfc->avi.avi_hdr.avih.nstreams, 1); wmfc->avi.hdrsize = Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdr, sizeof(wmfc->avi.avi_hdr)); if (doMJPG) { memcpy(&wmfc->avi.avi_hdrv.strh.handler, "MJPG", 4); memcpy(&wmfc->avi.avi_hdrv.strf.compr, "MJPG", 4); } else { memcpy(&wmfc->avi.avi_hdrv.strh.handler, &wmfc->fourcc, 4); memcpy(&wmfc->avi.avi_hdrv.strf.compr, &wmfc->fourcc, 4); } n = wmfc->rrate * 1000; PUT32LE(&wmfc->avi.avi_hdrv.strh.scale, n); PUT32LE(&wmfc->avi.avi_hdrv.strh.rate, 1000000); PUT32LE(&wmfc->avi.avi_hdrv.strf.size, sizeof(wmfc->avi.avi_hdrv.strf)); PUT32LE(&wmfc->avi.avi_hdrv.strf.width, wmfc->width); PUT32LE(&wmfc->avi.avi_hdrv.strf.height, wmfc->height); PUT16LE(&wmfc->avi.avi_hdrv.strf.planes, 1); PUT16LE(&wmfc->avi.avi_hdrv.strf.bits, 24); n = 3 * wmfc->width * wmfc->height; PUT32LE(&wmfc->avi.avi_hdrv.strf.image_size, n); wmfc->avi.hdrsize += Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdrv, sizeof(wmfc->avi.avi_hdrv)); wmfc->avi.hdrsize += Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdro, sizeof(wmfc->avi.avi_hdro)); Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_data, sizeof(wmfc->avi.avi_data)); wmfc->avi.segsize0 = 4; WriteAVIHeader(wmfc, 0); wmfc->avi.curr_idx = wmfc->avi.num_idx = 0; wmfc->avi.idx_off = 4; if (wmfc->avi.idx != NULL) { ckfree((char *) wmfc->avi.idx); wmfc->avi.idx = NULL; } } /* Reserve 1ms for processing. */ wmfc->rrate -= 1; wmfc->ltv = (int) GetTickCount(); wmfc->rtv = wmfc->ltv; wmfc->ruser = doUser ? 1 : 0; #ifdef USE_ASYNC_HANDLER wmfc->rstate = (wmfc->async != NULL) ? REC_RECORD : REC_PAUSE; #else wmfc->rstate = (wmfc->tid != NULL) ? REC_RECORD : REC_PAUSE; #endif return TCL_OK; } /* *------------------------------------------------------------------------- * * WriteAVIHeader -- * * (Re)write AVI file header with current chunk sizes at * the very begin of the AVI file. * *------------------------------------------------------------------------- */ static void WriteAVIHeader(WMFC *wmfc, int end) { int size, idx_size; Tcl_WideInt pos; if (end && (wmfc->avi.idx != NULL)) { /* Write index. */ struct CHUNK_HDR idxh; static const struct CHUNK_HDR idxh0 = { { 'i', 'd', 'x', '1' }, 0 }; idxh = idxh0; idx_size = wmfc->avi.curr_idx * sizeof(struct AVI_IDX); PUT32LE(&idxh.size, idx_size); Tcl_WriteRaw(wmfc->rchan, (const char *) &idxh, sizeof(idxh)); Tcl_WriteRaw(wmfc->rchan, (const char *) wmfc->avi.idx, idx_size); /* Mark index present. */ PUT32LE(&wmfc->avi.avi_hdr.avih.flags, 0x10); idx_size += sizeof(struct CHUNK_HDR); } else { /* Mark index absent. */ PUT32LE(&wmfc->avi.avi_hdr.avih.flags, 0); idx_size = 0; } /* For MJPG use computed average frame rate. */ if (memcmp(&wmfc->avi.avi_hdrv.strh.handler, "MJPG", 4) == 0) { int n; n = wmfc->avi.rate * 1000; PUT32LE(&wmfc->avi.avi_hdr.avih.uspf, n); PUT32LE(&wmfc->avi.avi_hdrv.strh.scale, n); } size = wmfc->avi.hdrsize + wmfc->avi.segsize0; PUT32LE(&wmfc->avi.avi_hdr.riff_size, size + idx_size); size = wmfc->avi.hdrsize - 20; PUT32LE(&wmfc->avi.avi_hdr.hdrl_size, size); size = wmfc->avi.nframes0; PUT32LE(&wmfc->avi.avi_hdr.avih.nframes, size); PUT32LE(&wmfc->avi.avi_hdrv.strh.length, size); size = wmfc->avi.segsize0 + 4; PUT32LE(&wmfc->avi.avi_data.data_size, size); size = wmfc->avi.nframes; PUT32LE(&wmfc->avi.avi_hdro.nframes, size); pos = Tcl_Seek(wmfc->rchan, 0, SEEK_CUR); Tcl_Seek(wmfc->rchan, wmfc->avi.pos0, SEEK_SET); Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdr, sizeof(wmfc->avi.avi_hdr)); Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdrv, sizeof(wmfc->avi.avi_hdrv)); Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_hdro, sizeof(wmfc->avi.avi_hdro)); Tcl_WriteRaw(wmfc->rchan, (const char *) &wmfc->avi.avi_data, sizeof(wmfc->avi.avi_data)); if (Tcl_Seek(wmfc->rchan, pos, SEEK_SET) == (Tcl_WideInt) -1) { wmfc->rstate = REC_ERROR; } if (end) { wmfc->avi.curr_idx = wmfc->avi.num_idx = 0; if (wmfc->avi.idx != NULL) { ckfree((char *) wmfc->avi.idx); wmfc->avi.idx = NULL; } } } /* *------------------------------------------------------------------------- * * FinishRecording -- * * Close recording channel and release resources. * *------------------------------------------------------------------------- */ static void FinishRecording(WMFC *wmfc) { if ((wmfc->rchan != NULL) && (Tcl_DStringLength(&wmfc->rbdStr) == 0)) { CloseAVISegment(wmfc, 1); WriteAVIHeader(wmfc, 1); } Tcl_DStringFree(&wmfc->rbdStr); if (wmfc->rchan != NULL) { Tcl_Close(NULL, wmfc->rchan); wmfc->rchan = NULL; memset(&wmfc->avi, 0, sizeof(wmfc->avi)); } } /* *------------------------------------------------------------------------- * * RecordFrameFromData -- * * Write single frame from byte array data. * *------------------------------------------------------------------------- */ static int RecordFrameFromData(WMFC *wmfc, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { int width, height, bpp, length, ret; unsigned char *data; Frame frame; if (objc != 8) { Tcl_WrongNumArgs(interp, 2, objv, "devid width height bpp bytearray"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[4], &width) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[5], &height) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[6], &bpp) != TCL_OK) { return TCL_ERROR; } data = Tcl_GetByteArrayFromObj(objv[7], &length); if ((length < width * height * bpp) || (bpp != 3) || (width != wmfc->width) || (height != wmfc->height)) { Tcl_SetResult(interp, "incompatible frame data", TCL_STATIC); return TCL_ERROR; } if (!wmfc->ruser || ((wmfc->rstate != REC_RECORD) && (wmfc->rstate != REC_PAUSE))) { Tcl_SetResult(interp, "wrong recording state for frame", TCL_STATIC); return TCL_ERROR; } frame.data = data; frame.length = length; frame.maxLength = length; frame.width = width; frame.height = height; frame.ready = 1; frame.fourcc = FOURCC_RGB0; frame.stride = width * bpp; ret = WriteFrame(wmfc, &frame); Tcl_SetObjResult(interp, Tcl_NewIntObj(ret)); return TCL_OK; } /* *------------------------------------------------------------------------- * * DataToPhoto -- * * Put byte array data to a Tk photo image. * *------------------------------------------------------------------------- */ static int DataToPhoto(WMFI *wmfi, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { int width, height, bpp, length; int rot = 0, mirx = 0, miry = 0, mirror; unsigned char *data; Tk_PhotoHandle photo; char *name; Tk_PhotoImageBlock block; if (CheckForTk(wmfi, interp) != TCL_OK) { return TCL_ERROR; } if ((objc < 7) || (objc > 10)) { Tcl_WrongNumArgs(interp, 2, objv, "photo width height bpp bytearray " "?rotation mirrorx mirrory?"); return TCL_ERROR; } if (Tk_MainWindow(interp) == NULL) { Tcl_SetResult(interp, "application has been destroyed", TCL_STATIC); return TCL_ERROR; } name = Tcl_GetString(objv[2]); photo = Tk_FindPhoto(interp, name); if (photo == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("can't use \"%s\": not a photo image", name)); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[3], &width) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[4], &height) != TCL_OK) { return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[5], &bpp) != TCL_OK) { return TCL_ERROR; } if ((objc > 7) && (Tcl_GetIntFromObj(interp, objv[7], &rot) != TCL_OK)) { return TCL_ERROR; } if ((objc > 8) && (Tcl_GetBooleanFromObj(interp, objv[8], &mirx) != TCL_OK)) { return TCL_ERROR; } if ((objc > 9) && (Tcl_GetBooleanFromObj(interp, objv[9], &miry) != TCL_OK)) { return TCL_ERROR; } data = Tcl_GetByteArrayFromObj(objv[6], &length); if ((length < width * height * bpp) || ((bpp != 1) && (bpp != 3))) { Tcl_SetResult(interp, "unsupported data format", TCL_STATIC); return TCL_ERROR; } if (bpp == 1) { block.pixelSize = 1; block.offset[0] = 0; block.offset[1] = 0; block.offset[2] = 0; block.offset[3] = 1; } else { block.pixelSize = 3; block.offset[0] = 0; block.offset[1] = 1; block.offset[2] = 2; block.offset[3] = 4; } block.width = width; block.height = height; block.pitch = width * bpp; block.pixelPtr = data; mirror = (mirx ? 1 : 0) | (miry ? 2 : 0); rot = rot % 360; if (rot < 45) { rot = 0; } else if (rot < 135) { rot = 90; } else if (rot < 225) { rot = 180; } else if (rot < 315) { rot = 270; } else { rot = 0; } if ((mirror & 3) == 3) { rot = (rot + 180) % 360; } switch (rot) { case 270: /* = 90 CW */ block.pitch = block.pixelSize; block.pixelPtr += width * block.pixelSize * (height - 1); block.pixelSize *= -width; block.offset[3] = block.pixelSize + 1; /* no alpha */ block.width = height; block.height = width; break; case 180: /* = 180 CW */ block.pitch = -block.pitch; block.pixelPtr += (width * height - 1) * block.pixelSize; block.pixelSize = -block.pixelSize; block.offset[3] = block.pixelSize + 1; /* no alpha */ break; case 90: /* = 270 CW */ block.pitch = -block.pixelSize; block.pixelPtr += (width - 1) * block.pixelSize; block.pixelSize *= width; block.offset[3] = block.pixelSize + 1; /* no alpha */ block.width = height; block.height = width; break; } if ((mirror & 3) == 2) { /* mirror in X */ block.pixelPtr += (block.width - 1) * block.pixelSize; block.pixelSize = -block.pixelSize; block.offset[3] = block.pixelSize + 1; /* no alpha */ } if ((mirror & 3) == 1) { /* mirror in Y */ block.pixelPtr += block.pitch * (block.height - 1); block.pitch = -block.pitch; } if (Tk_PhotoExpand(interp, photo, block.width, block.height) != TCL_OK) { return TCL_ERROR; } if (Tk_PhotoPutBlock(interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET) != TCL_OK) { return TCL_ERROR; } return TCL_OK; } /* *------------------------------------------------------------------------- * * EnumDevices -- * * Fill array of IMFActivate objects with info about * devices capable of video capture. Returns number of * objects in returned array. * *------------------------------------------------------------------------- */ static int EnumDevices(IMFActivate ***devOut) { IMFAttributes *attr = NULL; HRESULT hr; UINT32 count = 0; *devOut = NULL; hr = MFCreateAttributes(&attr, 1); if (!SUCCEEDED(hr)) { goto done; } hr = attr->lpVtbl->SetGUID(attr, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); if (!SUCCEEDED(hr)) { goto done; } hr = MFEnumDeviceSources(attr, devOut, &count); if (!SUCCEEDED(hr)) { count = 0; goto done; } done: if (attr != NULL) { attr->lpVtbl->Release(attr); } return count; } /* *------------------------------------------------------------------------- * * ListDevices -- * * Fill interp's result with a list with two elements * per device: symbolic link name and friendly name. * *------------------------------------------------------------------------- */ static int ListDevices(Tcl_Interp *interp) { IMFActivate **devList = NULL, *dev; int i, count; HRESULT hr; Tcl_DString ds; Tcl_Obj *list = Tcl_NewListObj(0, NULL); Tcl_DStringInit(&ds); count = EnumDevices(&devList); for (i = 0; i < count; i++) { WCHAR *link = NULL, *name = NULL; dev = devList[i]; hr = dev->lpVtbl->GetAllocatedString(dev, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &link, NULL); if (!SUCCEEDED(hr)) { CoTaskMemFree(link); continue; } hr = dev->lpVtbl->GetAllocatedString(dev, &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, NULL); if (SUCCEEDED(hr)) { Tcl_WinTCharToUtf((TCHAR *) link, -1, &ds); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); Tcl_DStringFree(&ds); Tcl_WinTCharToUtf((TCHAR *) name, -1, &ds); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); Tcl_DStringFree(&ds); } CoTaskMemFree(link); CoTaskMemFree(name); } for (i = 0; i < count; i++) { dev = devList[i]; dev->lpVtbl->Release(dev); } CoTaskMemFree(devList); Tcl_SetObjResult(interp, list); return TCL_OK; } /* *------------------------------------------------------------------------- * * GetSource -- * * Obtain IMFMediaSource for given symbolic link name. * *------------------------------------------------------------------------- */ static IMFMediaSource * GetSource(char *devName) { IMFActivate **devList = NULL, *dev = NULL; IMFMediaSource *mediaSrc = NULL; int i, count; HRESULT hr; count = EnumDevices(&devList); for (i = 0; i < count; i++) { WCHAR *link = NULL; Tcl_DString ds; int rc; dev = devList[i]; hr = dev->lpVtbl->GetAllocatedString(dev, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &link, NULL); if (!SUCCEEDED(hr)) { CoTaskMemFree(link); continue; } Tcl_DStringInit(&ds); Tcl_WinTCharToUtf((TCHAR *) link, -1, &ds); rc = strcmp(Tcl_DStringValue(&ds), devName); Tcl_DStringFree(&ds); CoTaskMemFree(link); if (rc == 0) { break; } dev = NULL; } if (dev != NULL) { hr = dev->lpVtbl->ActivateObject(dev, &IID_IMFMediaSource, (void **) &mediaSrc); if (!SUCCEEDED(hr)) { mediaSrc = NULL; } } for (i = 0; i < count; i++) { dev = devList[i]; dev->lpVtbl->Release(dev); } CoTaskMemFree(devList); return mediaSrc; } /* *------------------------------------------------------------------------- * * IMFSourceReader implementation and its VTBL * *------------------------------------------------------------------------- */ static HRESULT STDMETHODCALLTYPE SR_QueryInterface(IMFSourceReaderCallback *This, REFIID riid, void **ppvObj) { if (IsEqualIID(riid, &IID_IMFSourceReaderCallback)) { This->lpVtbl->AddRef(This); *ppvObj = This; return S_OK; } *ppvObj = NULL; return E_NOINTERFACE; } static ULONG STDMETHODCALLTYPE SR_AddRef(IMFSourceReaderCallback *This) { SourceReaderCB *srcb = (SourceReaderCB *) This; srcb->refCount += 1; return srcb->refCount; } static ULONG STDMETHODCALLTYPE SR_Release(IMFSourceReaderCallback *This) { SourceReaderCB *srcb = (SourceReaderCB *) This; if (srcb->refCount == 0) { return 0; } srcb->refCount -= 1; return srcb->refCount; } static HRESULT STDMETHODCALLTYPE SR_OnReadSample(IMFSourceReaderCallback *This, HRESULT hrStatus, DWORD streamIndex, DWORD streamFlags, LONGLONG ts, IMFSample *sample) { SourceReaderCB *srcb = (SourceReaderCB *) This; WMFC *wmfc; IMFMediaBuffer *mbuf = NULL; int index; HRESULT hr = S_OK; if (!SUCCEEDED(hrStatus)) { hr = hrStatus; } EnterCriticalSection(&srcb->lock); wmfc = srcb->wmfc; if ((wmfc->frameQueued < 0) || !SUCCEEDED(hr)) { wmfc->streamEnd = -1; goto done; } if (sample == NULL) { if ((streamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) { wmfc->streamEnd = 1; } goto done; } wmfc->counters[0] += 1; index = wmfc->frameQueued; hr = sample->lpVtbl->ConvertToContiguousBuffer(sample, &mbuf); if (SUCCEEDED(hr)) { DWORD length = 0, maxLength = 0; BYTE *data = NULL; int bufok = 0; mbuf->lpVtbl->Lock(mbuf, &data, &maxLength, &length); if (wmfc->frame[index].data == NULL) { wmfc->frame[index].data = (unsigned char *) attemptckalloc(length); if (wmfc->frame[index].data != NULL) { wmfc->frame[index].maxLength = length; bufok = 1; } else { wmfc->frame[index].maxLength = 0; } } else if (wmfc->frame[index].maxLength >= length) { bufok = 1; } else { unsigned char *newData = (unsigned char *) attemptckrealloc(wmfc->frame[index].data, length); if (newData != NULL) { wmfc->frame[index].data = newData; wmfc->frame[index].maxLength = length; bufok = 1; } } if (bufok) { memcpy(wmfc->frame[index].data, data, length); wmfc->frame[index].length = length; wmfc->frame[index].ready = 1; wmfc->frame[index].fourcc = wmfc->fourcc; wmfc->frame[index].width = wmfc->width; wmfc->frame[index].height = wmfc->height; wmfc->frame[index].stride = wmfc->stride; } else { wmfc->frame[index].length = 0; wmfc->frame[index].ready = -1; wmfc->counters[2] += 1; wmfc->counters[3] += 1; } mbuf->lpVtbl->Unlock(mbuf); } if (mbuf != NULL) { mbuf->lpVtbl->Release(mbuf); } done: #ifdef USE_ASYNC_HANDLER Tcl_AsyncMark(wmfc->async); #else if (wmfc->tid != NULL) { WMFEVT *event = (WMFEVT *) ckalloc(sizeof(WMFEVT)); event->hdr.proc = SignalBuffer; event->hdr.nextPtr = NULL; event->wmfc = wmfc; Tcl_ThreadQueueEvent(wmfc->tid, &event->hdr, TCL_QUEUE_TAIL); Tcl_ThreadAlert(wmfc->tid); } #endif LeaveCriticalSection(&srcb->lock); return hr; } static HRESULT STDMETHODCALLTYPE SR_OnFlush(IMFSourceReaderCallback *This, DWORD streamIndex) { SourceReaderCB *srcb = (SourceReaderCB *) This; srcb->closing = 0; return S_OK; } static HRESULT STDMETHODCALLTYPE SR_OnEvent(IMFSourceReaderCallback *This, DWORD streamIndex, IMFMediaEvent *pEvent) { return S_OK; } static IMFSourceReaderCallbackVtbl SourceReaderVtbl = { SR_QueryInterface, SR_AddRef, SR_Release, SR_OnReadSample, SR_OnFlush, SR_OnEvent }; /* *------------------------------------------------------------------------- * * ImageCallback -- * * Called as timer handler from the (async) event handler * when a new image is available or image capture was stopped * due to an error. * *------------------------------------------------------------------------- */ static void ImageCallback(ClientData clientData) { WMFC *wmfc = (WMFC *) clientData; Tcl_Interp *interp = wmfc->interp; int ret; #ifdef USE_ASYNC_HANDLER int capture = (wmfc->async != NULL); #else int capture = (wmfc->tid != NULL); #endif wmfc->cbPending = NULL; Tcl_DStringSetLength(&wmfc->cbCmd, wmfc->cbCmdLen); Tcl_DStringAppendElement(&wmfc->cbCmd, wmfc->devId); if (wmfc->streamEnd) { Tcl_DStringAppendElement(&wmfc->cbCmd, (wmfc->streamEnd < 0) ? "error" : "eof"); } else { Tcl_DStringAppendElement(&wmfc->cbCmd, capture ? "capture" : "stop"); } Tcl_Preserve((ClientData) interp); ret = Tcl_EvalEx(interp, Tcl_DStringValue(&wmfc->cbCmd), Tcl_DStringLength(&wmfc->cbCmd), TCL_EVAL_GLOBAL); if (ret != TCL_OK) { StopCapture(wmfc, 0); Tcl_AddErrorInfo(interp, "\n (wmf event handler)"); Tcl_BackgroundError(interp); } Tcl_Release((ClientData) interp); } /* *------------------------------------------------------------------------- * * SignalBuffer -- * * (Async) event handler called when an image became ready. * Requests next frame and schedules timer handler * for performing the Tcl callback established on open. * *------------------------------------------------------------------------- */ static int #ifdef USE_ASYNC_HANDLER SignalBuffer(ClientData clientData, Tcl_Interp *interp, int code) #else SignalBuffer(Tcl_Event *evPtr, int flags) #endif { #ifdef USE_ASYNC_HANDLER WMFC *wmfc = (WMFC *) clientData; #else WMFEVT *wevPtr = (WMFEVT *) evPtr; WMFC *wmfc = wevPtr->wmfc; int code = 1; /* event was handled */ #endif HRESULT hr; int bufok = 0, dostop = 0, doread = 0; if (wmfc->srcReader == NULL) { return code; } #ifndef USE_ASYNC_HANDLER if (wmfc->tid == NULL) { return code; } #endif EnterCriticalSection(&wmfc->srcb.lock); if (wmfc->counters[3] > 25) { /* stop after consecutive memory errors */ dostop = 1; } else if (wmfc->frameQueued >= 0) { bufok = wmfc->frame[wmfc->frameQueued].ready > 0; if (bufok) { if ((wmfc->frameReady >= 0) && (wmfc->frame[wmfc->frameReady].ready < 2)) { wmfc->counters[1] += 1; /* dropped */ } wmfc->frameReady = wmfc->frameQueued; wmfc->frameQueued = wmfc->frameQueued ? 0 : 1; wmfc->counters[3] = 0; /* reset memory error counter */ } wmfc->frame[wmfc->frameQueued].ready = 0; doread = 1; } LeaveCriticalSection(&wmfc->srcb.lock); if (doread) { hr = wmfc->srcReader->lpVtbl->ReadSample(wmfc->srcReader, MF_SOURCE_READER_ANY_STREAM, 0, NULL, NULL, NULL, NULL); if (!SUCCEEDED(hr)) { dostop = 1; } } if (bufok && !dostop && !wmfc->ruser && (wmfc->rstate == REC_RECORD)) { WriteFrame(wmfc, &wmfc->frame[wmfc->frameReady]); } if (dostop) { wmfc->frameQueued = -1; #ifdef USE_ASYNC_HANDLER Tcl_AsyncDelete(wmfc->async); wmfc->async = NULL; #else wmfc->tid = NULL; #endif } if (bufok || dostop) { if (wmfc->cbPending == NULL) { wmfc->cbPending = Tcl_CreateTimerHandler(0, ImageCallback, (ClientData) wmfc); } } return code; } /* *------------------------------------------------------------------------- * * SetFormat -- * * Set media source and source reader format given index * which must be an index from the MediaFmts array. * *------------------------------------------------------------------------- */ static HRESULT SetFormat(IMFMediaSource *mediaSrc, IMFSourceReader *srcReader, int index) { IMFPresentationDescriptor *pd = NULL; IMFStreamDescriptor *sd = NULL; IMFMediaTypeHandler *handler = NULL; IMFMediaType *mediaType = NULL; BOOL selected; HRESULT hr; hr = mediaSrc->lpVtbl->CreatePresentationDescriptor(mediaSrc, &pd); if (!SUCCEEDED(hr)) { goto done; } hr = pd->lpVtbl->GetStreamDescriptorByIndex(pd, 0, &selected, &sd); if (!SUCCEEDED(hr)) { goto done; } hr = sd->lpVtbl->GetMediaTypeHandler(sd, &handler); if (!SUCCEEDED(hr)) { goto done; } hr = handler->lpVtbl->GetMediaTypeByIndex(handler, index, &mediaType); if (!SUCCEEDED(hr)) { goto done; } hr = handler->lpVtbl->SetCurrentMediaType(handler, mediaType); if (!SUCCEEDED(hr)) { goto done; } hr = srcReader->lpVtbl->SetCurrentMediaType(srcReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediaType); done: if (mediaType != NULL) { mediaType->lpVtbl->Release(mediaType); } if (handler != NULL) { handler->lpVtbl->Release(handler); } if (sd != NULL) { sd->lpVtbl->Release(sd); } if (pd != NULL) { pd->lpVtbl->Release(pd); } return hr; } /* *------------------------------------------------------------------------- * * GetFormatList -- * * Return allocated array of usable formats. * *------------------------------------------------------------------------- */ static MediaFmt * GetFormatList(IMFMediaSource *mediaSrc, int *numFmtsRet) { IMFPresentationDescriptor *pd = NULL; IMFStreamDescriptor *sd = NULL; IMFMediaTypeHandler *handler = NULL; IMFMediaType *mediaType = NULL; MediaFmt *fmts = NULL; BOOL selected; DWORD count = 0; int i, numFmts = 0; HRESULT hr; hr = mediaSrc->lpVtbl->CreatePresentationDescriptor(mediaSrc, &pd); if (!SUCCEEDED(hr)) { goto done; } hr = pd->lpVtbl->GetStreamDescriptorByIndex(pd, 0, &selected, &sd); if (!SUCCEEDED(hr)) { goto done; } hr = sd->lpVtbl->GetMediaTypeHandler(sd, &handler); if (!SUCCEEDED(hr)) { goto done; } hr = handler->lpVtbl->GetMediaTypeCount(handler, &count); if (!SUCCEEDED(hr)) { goto done; } fmts = (MediaFmt *) attemptckalloc(sizeof(MediaFmt) * count); if (fmts == NULL) { goto done; } for (i = 0; i < count; i++) { GUID subType = GUID_NULL; UINT64 wval; LONG lval; int w, h; BOOL isCompressed = TRUE; hr = handler->lpVtbl->GetMediaTypeByIndex(handler, i, &mediaType); if (!SUCCEEDED(hr)) { goto skip; } mediaType->lpVtbl->IsCompressedFormat(mediaType, &isCompressed); if (isCompressed) { goto skip; } hr = mediaType->lpVtbl->GetGUID(mediaType, &MF_MT_SUBTYPE, &subType); if (SUCCEEDED(hr) && ((subType.Data1 == FOURCC_NV12) || (subType.Data1 == FOURCC_YUY2))) { fmts[numFmts].index = i; fmts[numFmts].fourcc = subType.Data1; wval = 0; hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_SIZE, &wval); if (!SUCCEEDED(hr)) { continue; } w = wval >> 32; h = wval; if ((w == 0) || (h == 0)) { continue; } fmts[numFmts].width = w; fmts[numFmts].height = h; hr = MFGetStrideForBitmapInfoHeader(subType.Data1, w, &lval); if (!SUCCEEDED(hr)) { continue; } fmts[numFmts].stride = lval; wval = 0; hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_RATE, &wval); if (!SUCCEEDED(hr)) { continue; } fmts[numFmts].frameRate = wval; fmts[numFmts].frameRateMin = fmts[numFmts].frameRateMax = wval; wval = 0; hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_RATE_RANGE_MIN, &wval); if (SUCCEEDED(hr)) { fmts[numFmts].frameRateMin = wval; } wval = 0; hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_RATE_RANGE_MAX, &wval); if (SUCCEEDED(hr)) { fmts[numFmts].frameRateMax = wval; } ++numFmts; } skip: if (mediaType != NULL) { mediaType->lpVtbl->Release(mediaType); mediaType = NULL; } } done: if (handler != NULL) { handler->lpVtbl->Release(handler); } if (sd != NULL) { sd->lpVtbl->Release(sd); } if (pd != NULL) { pd->lpVtbl->Release(pd); } if ((numFmts <= 0) && (fmts != NULL)) { ckfree((char *) fmts); fmts = NULL; } *numFmtsRet = numFmts; return fmts; } /* *------------------------------------------------------------------------- * * GetFormat -- * * Get information about the media format (width, height etc.). * *------------------------------------------------------------------------- */ static int GetFormat(WMFC *wmfc) { HRESULT hr; Tcl_Interp *interp = wmfc->interp; IMFSourceReader *srcReader = wmfc->srcReader; IMFMediaType *mediaType = NULL; GUID subType = GUID_NULL; LONG lval = 0; UINT64 wval = 0; UINT32 w = 0, h = 0; int result = TCL_ERROR; hr = srcReader->lpVtbl->GetCurrentMediaType(srcReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediaType); if (!SUCCEEDED(hr)) { Tcl_SetResult(interp, "error getting media type from source reader", TCL_STATIC); goto done; } hr = mediaType->lpVtbl->GetGUID(mediaType, &MF_MT_SUBTYPE, &subType); if (!SUCCEEDED(hr)) { Tcl_SetResult(interp, "error getting media format", TCL_STATIC); goto done; } if ((subType.Data1 != FOURCC_NV12) && (subType.Data1 != FOURCC_YUY2)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("unsupported format code 0x%08lx", subType.Data1)); goto done; } wmfc->fourcc = subType.Data1; hr = mediaType->lpVtbl->GetUINT64(mediaType, &MF_MT_FRAME_SIZE, &wval); if (SUCCEEDED(hr)) { w = wval >> 32; h = wval; } if (!SUCCEEDED(hr)) { Tcl_SetResult(interp, "error getting frame size", TCL_STATIC); goto done; } hr = MFGetStrideForBitmapInfoHeader(subType.Data1, w, &lval); if (!SUCCEEDED(hr)) { Tcl_SetResult(interp, "error getting stride", TCL_STATIC); goto done; } wmfc->stride = lval; wmfc->width = w; wmfc->height = h; result = TCL_OK; done: if (mediaType != NULL) { mediaType->lpVtbl->Release(mediaType); } return result; } /* *------------------------------------------------------------------------- * * StartCapture -- * * Start capture of frames. A media source is created if not * already available. The (async) event handler is established * and the initial read of the first frame is performed. Later * frames are requested by the (async) event handler. * *------------------------------------------------------------------------- */ static int StartCapture(WMFC *wmfc) { Tcl_Interp *interp = wmfc->interp; IMFAttributes *attr = NULL; HRESULT hr; int doread = 0; if (wmfc->srcReader == NULL) { hr = MFCreateAttributes(&attr, 1); if (!SUCCEEDED(hr)) { Tcl_SetResult(interp, "error creating WMF attributes", TCL_STATIC); return TCL_ERROR; } wmfc->counters[0] = wmfc->counters[1] = 0; wmfc->counters[2] = wmfc->counters[3] = 0; wmfc->frame[0].ready = wmfc->frame[1].ready = 0; wmfc->streamEnd = 0; wmfc->frameReady = wmfc->frameQueued = -1; wmfc->srcb.isrcb.lpVtbl = &SourceReaderVtbl; wmfc->srcb.refCount = 1; wmfc->srcb.closing = 0; wmfc->srcb.wmfc = wmfc; hr = attr->lpVtbl->SetUnknown(attr, &MF_SOURCE_READER_ASYNC_CALLBACK, (struct IUnknown *) &wmfc->srcb); hr = MFCreateSourceReaderFromMediaSource(wmfc->mediaSrc, attr, &wmfc->srcReader); if (attr != NULL) { attr->lpVtbl->Release(attr); } if (!SUCCEEDED(hr)) { if (wmfc->srcReader != NULL) { wmfc->srcReader->lpVtbl->Release(wmfc->srcReader); wmfc->srcReader = NULL; } Tcl_SetResult(interp, "error creating source reader", TCL_STATIC); return TCL_ERROR; } if (wmfc->useFmt < 0) { wmfc->useFmt = wmfc->fmts[0].index; } hr = SetFormat(wmfc->mediaSrc, wmfc->srcReader, wmfc->useFmt); if (!SUCCEEDED(hr)) { wmfc->srcReader->lpVtbl->Release(wmfc->srcReader); wmfc->srcReader = NULL; Tcl_SetResult(interp, "error setting media format", TCL_STATIC); return TCL_ERROR; } if (GetFormat(wmfc) != TCL_OK) { wmfc->srcReader->lpVtbl->Release(wmfc->srcReader); wmfc->srcReader = NULL; return TCL_ERROR; } wmfc->cbPending = NULL; } EnterCriticalSection(&wmfc->srcb.lock); #ifdef USE_ASYNC_HANDLER if (wmfc->async == NULL) { wmfc->async = Tcl_AsyncCreate(SignalBuffer, (ClientData) wmfc); doread = 1; } #else if (wmfc->tid == NULL) { wmfc->tid = Tcl_GetCurrentThread(); doread = 1; } #endif if (doread) { wmfc->streamEnd = 0; wmfc->counters[0] = wmfc->counters[1] = 0; wmfc->counters[2] = wmfc->counters[3] = 0; wmfc->frameQueued = wmfc->frameReady ? 0 : 1; wmfc->frame[wmfc->frameQueued].ready = 0; hr = wmfc->srcReader->lpVtbl->ReadSample(wmfc->srcReader, MF_SOURCE_READER_ANY_STREAM, 0, NULL, NULL, NULL, NULL); if (!SUCCEEDED(hr)) { wmfc->frameQueued = -1; #ifdef USE_ASYNC_HANDLER Tcl_AsyncDelete(wmfc->async); wmfc->async = NULL; #else wmfc->tid = NULL; #endif } } else { hr = S_OK; } LeaveCriticalSection(&wmfc->srcb.lock); if (!SUCCEEDED(hr)) { Tcl_SetResult(interp, "error starting read from source reader", TCL_STATIC); return TCL_ERROR; } if (wmfc->rstate == REC_PAUSE) { wmfc->ltv = (int) GetTickCount(); wmfc->rtv = wmfc->ltv; wmfc->rstate = REC_RECORD; } return TCL_OK; } /* *------------------------------------------------------------------------- * * StopCapture -- * * Stop capture if running. The (async) event handler is removed and * a pending call to ImageCallback is cancelled, too. * *------------------------------------------------------------------------- */ static int StopCapture(WMFC *wmfc, int closing) { EnterCriticalSection(&wmfc->srcb.lock); #ifdef USE_ASYNC_HANDLER if (wmfc->async != NULL) { Tcl_AsyncDelete(wmfc->async); wmfc->async = NULL; } #else wmfc->tid = NULL; #endif if (wmfc->streamEnd < 0) { wmfc->streamEnd = 0; } wmfc->frameQueued = -1; if (wmfc->srcReader != NULL) { wmfc->srcb.closing = closing; wmfc->srcReader->lpVtbl->Flush(wmfc->srcReader, MF_SOURCE_READER_ALL_STREAMS); } else { closing = 0; } LeaveCriticalSection(&wmfc->srcb.lock); Tcl_DeleteTimerHandler(wmfc->cbPending); wmfc->cbPending = NULL; if (closing) { int i; for (i = 0; i < 5; i++) { if (!wmfc->srcb.closing) { break; } Sleep(20); } } if (wmfc->rstate == REC_RECORD) { wmfc->rstate = REC_PAUSE; } return TCL_OK; } /* *------------------------------------------------------------------------- * * InitControls -- * * Fill (or release) table with meta information about the * device's controls. * *------------------------------------------------------------------------- */ static void InitControls(WMFC *wmfc, int deinit) { int i, isNew; Tcl_HashEntry *hPtr; Tcl_HashSearch search; WCTRL *wctrl; IAMVideoProcAmp *procAmp = NULL; IAMCameraControl *camCtrl = NULL; HRESULT hr; static const struct { int code; const char *name; } painfo[] = { { VideoProcAmp_Brightness, "brightness" }, { VideoProcAmp_Contrast, "contrast" }, { VideoProcAmp_Hue, "hue" }, { VideoProcAmp_Saturation, "saturation" }, { VideoProcAmp_Sharpness, "sharpness" }, { VideoProcAmp_Gamma, "gamma" }, { VideoProcAmp_ColorEnable, "color-enable" }, { VideoProcAmp_WhiteBalance, "white-balance" }, { VideoProcAmp_BacklightCompensation, "backlight-compensation" }, { VideoProcAmp_Gain, "gain" } }; static const struct { int code; const char *name; } ccinfo[] = { { CameraControl_Pan, "pan" }, { CameraControl_Tilt, "tilt" }, { CameraControl_Roll, "roll" }, { CameraControl_Zoom, "zoom" }, { CameraControl_Exposure, "exposure" }, { CameraControl_Iris, "iris" }, { CameraControl_Focus, "focus" }, }; /* first, free up old stuff */ hPtr = Tcl_FirstHashEntry(&wmfc->ctrl, &search); while (hPtr != NULL) { wctrl = (WCTRL *) Tcl_GetHashValue(hPtr); ckfree((char *) wctrl); hPtr = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(&wmfc->ctrl); if (deinit) { return; } Tcl_InitHashTable(&wmfc->ctrl, TCL_STRING_KEYS); /* fill in new information */ hr = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc, &PRIVATE_IID_IAMVideoProcAmp, (void **) &procAmp); if (SUCCEEDED(hr)) { for (i = 0; i < sizeof(painfo) / sizeof(painfo[0]); i++) { long llim, ulim, step, def, caps; hr = procAmp->lpVtbl->GetRange(procAmp, painfo[i].code, &llim, &ulim, &step, &def, &caps); if (!SUCCEEDED(hr)) { continue; } if (!(caps & VideoProcAmp_Flags_Manual)) { continue; } wctrl = (WCTRL *) ckalloc(sizeof(WCTRL)); memset(wctrl, 0, sizeof(WCTRL)); wctrl->code = painfo[i].code; wctrl->iscam = 0; wctrl->name = painfo[i].name; wctrl->min = llim; wctrl->max = ulim; wctrl->step = step; wctrl->def = def; wctrl->caps = caps; hPtr = Tcl_CreateHashEntry(&wmfc->ctrl, (ClientData) wctrl->name, &isNew); if (!isNew) { WCTRL *oldctrl = (WCTRL *) Tcl_GetHashValue(hPtr); ckfree((char *) oldctrl); } Tcl_SetHashValue(hPtr, (ClientData) wctrl); } } if (procAmp != NULL) { procAmp->lpVtbl->Release(procAmp); } hr = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc, &PRIVATE_IID_IAMCameraControl, (void **) &camCtrl); if (SUCCEEDED(hr)) { for (i = 0; i < sizeof(ccinfo) / sizeof(ccinfo[0]); i++) { long llim, ulim, step, def, caps; hr = camCtrl->lpVtbl->GetRange(camCtrl, ccinfo[i].code, &llim, &ulim, &step, &def, &caps); if (!SUCCEEDED(hr)) { continue; } if (!(caps & CameraControl_Flags_Manual)) { continue; } wctrl = (WCTRL *) ckalloc(sizeof(WCTRL)); memset(wctrl, 0, sizeof(WCTRL)); wctrl->code = ccinfo[i].code; wctrl->iscam = 1; wctrl->name = ccinfo[i].name; wctrl->min = llim; wctrl->max = ulim; wctrl->step = step; wctrl->def = def; wctrl->caps = caps; hPtr = Tcl_CreateHashEntry(&wmfc->ctrl, (ClientData) wctrl->name, &isNew); if (!isNew) { WCTRL *oldctrl = (WCTRL *) Tcl_GetHashValue(hPtr); ckfree((char *) oldctrl); } Tcl_SetHashValue(hPtr, (ClientData) wctrl); } } if (camCtrl != NULL) { camCtrl->lpVtbl->Release(camCtrl); } } /* *------------------------------------------------------------------------- * * GetControls -- * * Read out current values of device controls and return * these as list made up of key value pairs. Added meta * information entries to support user interface: * * -minimum minimum value for bool/integer * -maximum minimum value for bool/integer * -step interval step value for integer * -default default value for bool/integer * -auto 0/1 depending on manual/auto * *------------------------------------------------------------------------- */ static void GetControls(WMFC *wmfc, Tcl_Obj *list) { Tcl_HashEntry *hPtr; Tcl_HashSearch search; Tcl_DString ds; IAMVideoProcAmp *procAmp = NULL; IAMCameraControl *camCtrl = NULL; HRESULT hrpa, hrcc; Tcl_DStringInit(&ds); hrpa = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc, &PRIVATE_IID_IAMVideoProcAmp, (void **) &procAmp); hrcc = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc, &PRIVATE_IID_IAMCameraControl, (void **) &camCtrl); if (SUCCEEDED(hrpa) || SUCCEEDED(hrcc)) { hPtr = Tcl_FirstHashEntry(&wmfc->ctrl, &search); while (hPtr != NULL) { WCTRL *wctrl = (WCTRL *) Tcl_GetHashValue(hPtr); HRESULT hr = E_INVALIDARG; long val = 0, flags = 0; char buffer[32]; if (wctrl->iscam && SUCCEEDED(hrcc)) { hr = camCtrl->lpVtbl->Get(camCtrl, wctrl->code, &val, &flags); } else if (!wctrl->iscam && SUCCEEDED(hrpa)) { hr = procAmp->lpVtbl->Get(procAmp, wctrl->code, &val, &flags); } if (SUCCEEDED(hr)) { Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(wctrl->name, -1)); sprintf(buffer, "%ld", val); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(buffer, -1)); if (wctrl->min != wctrl->max) { Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, wctrl->name, -1); Tcl_DStringAppend(&ds, "-minimum", -1); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); sprintf(buffer, "%ld", wctrl->min); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(buffer, -1)); Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, wctrl->name, -1); Tcl_DStringAppend(&ds, "-maximum", -1); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); sprintf(buffer, "%ld", wctrl->max); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(buffer, -1)); } Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, wctrl->name, -1); Tcl_DStringAppend(&ds, "-default", -1); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); sprintf(buffer, "%ld", wctrl->max); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(buffer, -1)); if (wctrl->step != 0) { Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, wctrl->name, -1); Tcl_DStringAppend(&ds, "-step", -1); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); sprintf(buffer, "%ld", wctrl->step); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(buffer, -1)); } if (wctrl->caps & VideoProcAmp_Flags_Auto) { Tcl_DStringSetLength(&ds, 0); Tcl_DStringAppend(&ds, wctrl->name, -1); Tcl_DStringAppend(&ds, "-auto", -1); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); strcpy(buffer, (flags & VideoProcAmp_Flags_Auto) ? "1" : "0"); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(buffer, -1)); } } hPtr = Tcl_NextHashEntry(&search); } } if (procAmp != NULL) { procAmp->lpVtbl->Release(procAmp); } if (camCtrl != NULL) { camCtrl->lpVtbl->Release(camCtrl); } Tcl_DStringFree(&ds); } /* *------------------------------------------------------------------------- * * SetControls -- * * Set device controls given list of key value pairs. * *------------------------------------------------------------------------- */ static void SetControls(WMFC *wmfc, int objc, Tcl_Obj * const objv[]) { Tcl_HashEntry *hPtr; int i; IAMVideoProcAmp *procAmp = NULL; IAMCameraControl *camCtrl = NULL; HRESULT hrpa, hrcc; hrpa = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc, &PRIVATE_IID_IAMVideoProcAmp, (void **) &procAmp); hrcc = wmfc->mediaSrc->lpVtbl->QueryInterface(wmfc->mediaSrc, &PRIVATE_IID_IAMCameraControl, (void **) &camCtrl); for (i = 0; i < objc; i += 2) { WCTRL *wctrl; long val; int doauto = 0; char *name = Tcl_GetString(objv[i]); hPtr = Tcl_FindHashEntry(&wmfc->ctrl, name); if (hPtr == NULL) { int n = strlen(name); if ((n > 5) && (strcmp(name + n - 5, "-auto") == 0)) { Tcl_DString ds; Tcl_DStringInit(&ds); Tcl_DStringAppend(&ds, name, n - 5); hPtr = Tcl_FindHashEntry(&wmfc->ctrl, Tcl_DStringValue(&ds)); Tcl_DStringFree(&ds); doauto = 1; } } if (hPtr == NULL) { continue; } wctrl = (WCTRL *) Tcl_GetHashValue(hPtr); if (wctrl->iscam && !SUCCEEDED(hrcc)) { continue; } else if (!wctrl->iscam && !SUCCEEDED(hrpa)) { continue; } val = strtol(Tcl_GetString(objv[i + 1]), NULL, 0); if (doauto && wctrl->iscam) { if (wctrl->caps & CameraControl_Flags_Auto) { camCtrl->lpVtbl->Set(camCtrl, wctrl->code, wctrl->def, val ? CameraControl_Flags_Auto : CameraControl_Flags_Manual); } } else if (doauto) { if (wctrl->caps & VideoProcAmp_Flags_Auto) { procAmp->lpVtbl->Set(procAmp, wctrl->code, wctrl->def, val ? VideoProcAmp_Flags_Auto : VideoProcAmp_Flags_Manual); } } else { if (val < wctrl->min) { val = wctrl->min; } else if (val > wctrl->max) { val = wctrl->max; } if (wctrl->step > 0) { val = val - (val % wctrl->step); } if (wctrl->iscam) { camCtrl->lpVtbl->Set(camCtrl, wctrl->code, val, CameraControl_Flags_Manual); } else { procAmp->lpVtbl->Set(procAmp, wctrl->code, val, VideoProcAmp_Flags_Manual); } } } if (procAmp != NULL) { procAmp->lpVtbl->Release(procAmp); } if (camCtrl != NULL) { camCtrl->lpVtbl->Release(camCtrl); } } /* *------------------------------------------------------------------------- * * GetImage -- * * Retrieve last captured buffer as photo image or byte array. * *------------------------------------------------------------------------- */ static int GetImage(WMFI *wmfi, WMFC *wmfc, Tcl_Obj *arg) { Tcl_Interp *interp = wmfc->interp; Tk_PhotoHandle photo = NULL; int fourcc, width, height, result = TCL_OK; unsigned char *rawPtr = NULL; char *name; if (arg != NULL) { if (CheckForTk(wmfi, interp) != TCL_OK) { return TCL_ERROR; } if (Tk_MainWindow(interp) == NULL) { Tcl_SetResult(interp, "application has been destroyed", TCL_STATIC); return TCL_ERROR; } name = Tcl_GetString(arg); photo = Tk_FindPhoto(interp, name); if (photo == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("can't use \"%s\": not a photo image", name)); return TCL_ERROR; } } EnterCriticalSection(&wmfc->srcb.lock); if (wmfc->frameReady >= 0) { int stride; fourcc = wmfc->frame[wmfc->frameReady].fourcc; width = wmfc->frame[wmfc->frameReady].width; height = wmfc->frame[wmfc->frameReady].height; stride = wmfc->frame[wmfc->frameReady].stride; if ((width > 0) && (height > 0) && (stride > 0)) { rawPtr = (unsigned char *) attemptckalloc(stride * height * 5); } if (rawPtr != NULL) { int size = stride * height; if (fourcc == FOURCC_NV12) { /* NV12: stride should be equal to width */ size += (width * height) / 2; } else { /* YUY2: stride should be 2 times width */ } memcpy(rawPtr, wmfc->frame[wmfc->frameReady].data, size); } wmfc->frame[wmfc->frameReady].ready = 2; } LeaveCriticalSection(&wmfc->srcb.lock); if (rawPtr == NULL) { /* no image available */ if (photo != NULL) { Tcl_SetObjResult(interp, Tcl_NewIntObj(0)); } else { Tcl_SetResult(interp, "no image available", TCL_STATIC); result = TCL_ERROR; } goto done; } if ((width <= 0) || (height <= 0)) { /* malformed image */ if (photo != NULL) { Tcl_SetObjResult(interp, Tcl_NewIntObj(-1)); } else { Tcl_SetResult(interp, "malformed image", TCL_STATIC); result = TCL_ERROR; } goto done; } if (photo != NULL) { Tk_PhotoImageBlock block; unsigned char *yPtr, *uvPtr, *dstPtr; int x, y, yVal, yVal2, uVal = 0, vVal = 0, rot = wmfc->rotate; block.pixelSize = 3; block.offset[0] = 0; block.offset[1] = 1; block.offset[2] = 2; block.offset[3] = block.pixelSize + 1; /* no alpha */ block.width = width; block.height = height; block.pitch = block.pixelSize * block.width; block.pixelPtr = rawPtr + 2 * width * height; if (fourcc == FOURCC_NV12) { /* convert NV12 -> RGB */ for (y = 0; y < height; y++) { yPtr = rawPtr + y * width; uvPtr = rawPtr + width * height + (y >> 1) * width; dstPtr = block.pixelPtr + block.pixelSize * y * width; for (x = 0; x < width; x++) { int red, green, blue; yVal = *yPtr++; yVal -= 16; if (yVal < 0) { yVal = 0; } if ((x & 1) == 0) { uVal = *uvPtr++ - 128; vVal = *uvPtr++ - 128; } yVal *= 1192; red = yVal + 1634 * vVal; green = yVal - 833 * vVal - 400 * uVal; blue = yVal + 2066 * uVal; if (red < 0) { red = 0; } else if (red > 0x3ffff) { red = 0x3ffff; } *dstPtr++ = red >> 10; if (green < 0) { green = 0; } else if (green > 0x3ffff) { green = 0x3ffff; } *dstPtr++ = green >> 10; if (blue < 0) { blue = 0; } else if (blue > 0x3ffff) { blue = 0x3ffff; } *dstPtr++ = blue >> 10; } } } else { /* convert YUY2 -> RGB */ for (y = 0; y < height; y++) { yPtr = rawPtr + y * width * 2; dstPtr = block.pixelPtr + block.pixelSize * y * width; for (x = 0; x < width; x++) { int red, green, blue; yVal = *yPtr++; yVal -= 16; if (yVal < 0) { yVal = 0; } uVal = *yPtr++ - 128; yVal2 = *yPtr++; yVal2 -= 16; if (yVal2 < 0) { yVal2 = 0; } vVal = *yPtr++ - 128; red = 298 * yVal + 409 * vVal + 128; green = 298 * yVal - 100 * uVal - 208 * vVal + 128; blue = 298 * yVal + 516 * uVal + 128; if (red < 0) { red = 0; } else if (red > 0xffff) { red = 0xffff; } *dstPtr++ = red >> 8; if (green < 0) { green = 0; } else if (green > 0xffff) { green = 0xffff; } *dstPtr++ = green >> 8; if (blue < 0) { blue = 0; } else if (blue > 0xffff) { blue = 0xffff; } *dstPtr++ = blue >> 8; red = 298 * yVal2 + 409 * vVal + 128; green = 298 * yVal2 - 100 * uVal - 208 * vVal + 128; blue = 298 * yVal2 + 516 * uVal + 128; if (red < 0) { red = 0; } else if (red > 0xffff) { red = 0xffff; } *dstPtr++ = red >> 8; if (green < 0) { green = 0; } else if (green > 0xffff) { green = 0xffff; } *dstPtr++ = green >> 8; if (blue < 0) { blue = 0; } else if (blue > 0xffff) { blue = 0xffff; } *dstPtr++ = blue >> 8; } } } if ((wmfc->mirror & 3) == 3) { rot = (rot + 180) % 360; } switch (rot) { case 270: /* = 90 CW */ block.pitch = block.pixelSize; block.pixelPtr += width * block.pixelSize * (height - 1); block.pixelSize *= -width; block.offset[3] = block.pixelSize + 1; /* no alpha */ block.width = height; block.height = width; break; case 180: /* = 180 CW */ block.pitch = -block.pitch; block.pixelPtr += (width * height - 1) * block.pixelSize; block.pixelSize = -block.pixelSize; block.offset[3] = block.pixelSize + 1; /* no alpha */ break; case 90: /* = 270 CW */ block.pitch = -block.pixelSize; block.pixelPtr += (width - 1) * block.pixelSize; block.pixelSize *= width; block.offset[3] = block.pixelSize + 1; /* no alpha */ block.width = height; block.height = width; break; } if ((wmfc->mirror & 3) == 2) { /* mirror in X */ block.pixelPtr += (block.width - 1) * block.pixelSize; block.pixelSize = -block.pixelSize; block.offset[3] = block.pixelSize + 1; /* no alpha */ } if ((wmfc->mirror & 3) == 1) { /* mirror in Y */ block.pixelPtr += block.pitch * (block.height - 1); block.pitch = -block.pitch; } if (Tk_PhotoExpand(interp, photo, block.width, block.height) != TCL_OK) { result = TCL_ERROR; goto done; } if (Tk_PhotoPutBlock(interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET) != TCL_OK) { result = TCL_ERROR; } else { Tcl_SetObjResult(interp, Tcl_NewIntObj(1)); } } else { unsigned char *pixelPtr; Tcl_Obj *list[4]; unsigned char *yPtr, *uvPtr, *dstPtr; int x, y, yVal, yVal2, uVal = 0, vVal = 0; pixelPtr = rawPtr + 2 * width * height; if (fourcc == FOURCC_NV12) { /* convert NV12 -> RGB */ for (y = 0; y < height; y++) { yPtr = rawPtr + y * width; uvPtr = rawPtr + width * height + (y >> 1) * width; dstPtr = pixelPtr + 3 * y * width; for (x = 0; x < width; x++) { int red, green, blue; yVal = *yPtr++; yVal -= 16; if (yVal < 0) { yVal = 0; } if ((x & 1) == 0) { uVal = *uvPtr++ - 128; vVal = *uvPtr++ - 128; } yVal *= 1192; red = yVal + 1634 * vVal; green = yVal - 833 * vVal - 400 * uVal; blue = yVal + 2066 * uVal; if (red < 0) { red = 0; } else if (red > 0x3ffff) { red = 0x3ffff; } *dstPtr++ = red >> 10; if (green < 0) { green = 0; } else if (green > 0x3ffff) { green = 0x3ffff; } *dstPtr++ = green >> 10; if (blue < 0) { blue = 0; } else if (blue > 0x3ffff) { blue = 0x3ffff; } *dstPtr++ = blue >> 10; } } } else { /* convert YUY2 -> RGB */ for (y = 0; y < height; y++) { yPtr = rawPtr + y * width * 2; dstPtr = pixelPtr + 3 * y * width; for (x = 0; x < width / 2; x++) { int red, green, blue; yVal = *yPtr++; yVal -= 16; if (yVal < 0) { yVal = 0; } uVal = *yPtr++ - 128; yVal2 = *yPtr++; yVal2 -= 16; if (yVal2 < 0) { yVal2 = 0; } vVal = *yPtr++ - 128; red = 298 * yVal + 409 * vVal + 128; green = 298 * yVal - 100 * uVal - 208 * vVal + 128; blue = 298 * yVal + 516 * uVal + 128; if (red < 0) { red = 0; } else if (red > 0xffff) { red = 0xffff; } *dstPtr++ = red >> 8; if (green < 0) { green = 0; } else if (green > 0xffff) { green = 0xffff; } *dstPtr++ = green >> 8; if (blue < 0) { blue = 0; } else if (blue > 0xffff) { blue = 0xffff; } *dstPtr++ = blue >> 8; red = 298 * yVal2 + 409 * vVal + 128; green = 298 * yVal2 - 100 * uVal - 208 * vVal + 128; blue = 298 * yVal2 + 516 * uVal + 128; if (red < 0) { red = 0; } else if (red > 0xffff) { red = 0xffff; } *dstPtr++ = red >> 8; if (green < 0) { green = 0; } else if (green > 0xffff) { green = 0xffff; } *dstPtr++ = green >> 8; if (blue < 0) { blue = 0; } else if (blue > 0xffff) { blue = 0xffff; } *dstPtr++ = blue >> 8; } } } list[0] = Tcl_NewIntObj(width); list[1] = Tcl_NewIntObj(height); list[2] = Tcl_NewIntObj(3); list[3] = Tcl_NewByteArrayObj(pixelPtr, 3 * width * height); Tcl_SetObjResult(interp, Tcl_NewListObj(4, list)); } done: if (rawPtr != NULL) { ckfree((char *) rawPtr); } return result; } /* *------------------------------------------------------------------------- * * WmfObjCmdDeleted -- * * Destructor of "wmf" Tcl command. Closes all open devices and * releases all resources. * *------------------------------------------------------------------------- */ static void WmfObjCmdDeleted(ClientData clientData) { WMFI *wmfi = (WMFI *) clientData; Tcl_HashEntry *hPtr; Tcl_HashSearch search; WMFC *wmfc; hPtr = Tcl_FirstHashEntry(&wmfi->wmfc, &search); while (hPtr != NULL) { wmfc = (WMFC *) Tcl_GetHashValue(hPtr); StopCapture(wmfc, 1); FinishRecording(wmfc); InitControls(wmfc, 1); if (wmfc->srcReader != NULL) { wmfc->srcReader->lpVtbl->Release(wmfc->srcReader); } if (wmfc->mediaSrc != NULL) { wmfc->mediaSrc->lpVtbl->Release(wmfc->mediaSrc); } if (wmfc->frame[0].data != NULL) { ckfree((char *) wmfc->frame[0].data); } if (wmfc->frame[1].data != NULL) { ckfree((char *) wmfc->frame[1].data); } Tcl_DStringFree(&wmfc->devName); Tcl_DStringFree(&wmfc->cbCmd); if (wmfc->fmts != NULL) { ckfree((char *) wmfc->fmts); } DeleteCriticalSection(&wmfc->srcb.lock); ckfree((char *) wmfc); hPtr = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(&wmfi->wmfc); ckfree((char *) wmfi); } /* *------------------------------------------------------------------------- * * WmfObjCmd -- * * "wmf" Tcl command dealing with Windows Media Foundation. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *------------------------------------------------------------------------- */ static int WmfObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj * const objv[]) { WMFI *wmfi = (WMFI *) clientData; WMFC *wmfc; Tcl_HashEntry *hPtr; int ret = TCL_OK, command; static const char *cmdNames[] = { "close", "counters", "devices", "format", "image", "info", "listformats", "mirror", "open", "orientation", "parameters", "record", "start", "state", "stop", "tophoto", NULL }; enum cmdCode { CMD_close, CMD_counters, CMD_devices, CMD_format, CMD_image, CMD_info, CMD_listformats, CMD_mirror, CMD_open, CMD_orientation, CMD_parameters, CMD_record, CMD_start, CMD_state, CMD_stop, CMD_tophoto }; static const char *recNames[] = { "frame", "pause", "resume", "start", "state", "stop", NULL }; enum recCode { REC_frame, REC_pause, REC_resume, REC_start, REC_state, REC_stop }; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ..."); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], cmdNames, "option", 0, &command) != TCL_OK) { return TCL_ERROR; } switch ((enum cmdCode) command) { case CMD_close: { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "devid"); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr == NULL) { devNotFound: Tcl_SetObjResult(interp, Tcl_ObjPrintf("device \"%s\" not found", Tcl_GetString(objv[2]))); return TCL_ERROR; } wmfc = (WMFC *) Tcl_GetHashValue(hPtr); Tcl_DeleteHashEntry(hPtr); StopCapture(wmfc, 1); FinishRecording(wmfc); InitControls(wmfc, 1); if (wmfc->srcReader != NULL) { wmfc->srcReader->lpVtbl->Release(wmfc->srcReader); } if (wmfc->mediaSrc != NULL) { wmfc->mediaSrc->lpVtbl->Release(wmfc->mediaSrc); } if (wmfc->frame[0].data != NULL) { ckfree((char *) wmfc->frame[0].data); } if (wmfc->frame[1].data != NULL) { ckfree((char *) wmfc->frame[1].data); } Tcl_DStringFree(&wmfc->devName); Tcl_DStringFree(&wmfc->cbCmd); if (wmfc->fmts != NULL) { ckfree((char *) wmfc->fmts); } DeleteCriticalSection(&wmfc->srcb.lock); ckfree((char *) wmfc); break; } case CMD_counters: { if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "devid"); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr != NULL) { Tcl_Obj *r[3]; wmfc = (WMFC *) Tcl_GetHashValue(hPtr); EnterCriticalSection(&wmfc->srcb.lock); r[0] = Tcl_NewWideIntObj(wmfc->counters[0]); r[1] = Tcl_NewWideIntObj(wmfc->counters[1]); r[2] = Tcl_NewWideIntObj(wmfc->counters[2]); LeaveCriticalSection(&wmfc->srcb.lock); Tcl_SetObjResult(interp, Tcl_NewListObj(3, r)); } else { goto devNotFound; } break; } case CMD_devices: { if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); return TCL_ERROR; } ret = ListDevices(interp); break; } case CMD_format: { if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 2, objv, "devid ?number?"); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr == NULL) { goto devNotFound; } wmfc = (WMFC *) Tcl_GetHashValue(hPtr); if (objc > 3) { int i, k, fmtOk = 0, isRunning = 0; if (Tcl_GetIntFromObj(interp, objv[3], &k) != TCL_OK) { return TCL_ERROR; } #ifdef USE_ASYNC_HANDLER isRunning = wmfc->async != NULL; #else isRunning = wmfc->tid != NULL; #endif if (isRunning) { Tcl_SetResult(interp, "capture still running", TCL_STATIC); return TCL_ERROR; } /* Stop recording due to format change. */ if (wmfc->rstate > REC_STOP) { wmfc->rstate = REC_STOP; } FinishRecording(wmfc); for (i = 0; i < wmfc->numFmts; i++) { if (wmfc->fmts[i].index == k) { HRESULT hr; if (wmfc->srcReader == NULL) { wmfc->useFmt = k; fmtOk = 1; break; } hr = SetFormat(wmfc->mediaSrc, wmfc->srcReader, k); if (SUCCEEDED(hr)) { if (GetFormat(wmfc) == TCL_OK) { wmfc->useFmt = k; fmtOk = 1; } else { fmtOk = -1; } } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf("set format %d failed", k)); fmtOk = -1; } break; } } if (fmtOk == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("format %d not found", k)); } ret = (fmtOk <= 0) ? TCL_ERROR : TCL_OK; } else { int i = (wmfc->useFmt < 0) ? wmfc->fmts[0].index : wmfc->useFmt; Tcl_SetObjResult(interp, Tcl_NewIntObj(i)); } break; } case CMD_image: { if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 2, objv, "devid ?photoImage?"); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr == NULL) { goto devNotFound; } wmfc = (WMFC *) Tcl_GetHashValue(hPtr); ret = GetImage(wmfi, wmfc, (objc > 3) ? objv[3] : NULL); break; } case CMD_info: { if (objc > 3) { Tcl_WrongNumArgs(interp, 2, objv, "?devid?"); return TCL_ERROR; } if (objc == 2) { Tcl_HashSearch search; Tcl_Obj *list = Tcl_NewListObj(0, NULL); hPtr = Tcl_FirstHashEntry(&wmfi->wmfc, &search); while (hPtr != NULL) { wmfc = (WMFC *) Tcl_GetHashValue(hPtr); Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(wmfc->devId, -1)); hPtr = Tcl_NextHashEntry(&search); } Tcl_SetObjResult(interp, list); } else { hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr != NULL) { Tcl_Obj *r[2]; wmfc = (WMFC *) Tcl_GetHashValue(hPtr); r[0] = Tcl_NewStringObj(Tcl_DStringValue(&wmfc->devName), Tcl_DStringLength(&wmfc->devName)); r[1] = Tcl_NewStringObj(Tcl_DStringValue(&wmfc->cbCmd), wmfc->cbCmdLen); Tcl_SetObjResult(interp, Tcl_NewListObj(2, r)); } else { goto devNotFound; } } break; } case CMD_listformats: { Tcl_Obj *dict; int i; if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "devid"); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr == NULL) { goto devNotFound; } wmfc = (WMFC *) Tcl_GetHashValue(hPtr); dict = Tcl_NewDictObj(); for (i = 0; i < wmfc->numFmts; i++) { Tcl_DString ds; char buffer[64]; Tcl_DStringInit(&ds); Tcl_DStringAppendElement(&ds, "frame-size"); sprintf(buffer, "%dx%d", wmfc->fmts[i].width, wmfc->fmts[i].height); Tcl_DStringAppendElement(&ds, buffer); Tcl_DStringAppendElement(&ds, "stride"); sprintf(buffer, "%d", wmfc->fmts[i].stride); Tcl_DStringAppendElement(&ds, buffer); Tcl_DStringAppendElement(&ds, "fourcc"); switch (wmfc->fmts[i].fourcc) { case FOURCC_NV12: Tcl_DStringAppendElement(&ds, "NV12"); break; case FOURCC_YUY2: Tcl_DStringAppendElement(&ds, "YUY2"); break; default: Tcl_DStringAppendElement(&ds, "????"); break; } Tcl_DStringAppendElement(&ds, "frame-rate"); sprintf(buffer, "%d/%d", (int) (wmfc->fmts[i].frameRate >> 32), (int) (wmfc->fmts[i].frameRate & 0xffffffff)); Tcl_DStringAppendElement(&ds, buffer); Tcl_DStringAppendElement(&ds, "frame-rate-min"); sprintf(buffer, "%d/%d", (int) (wmfc->fmts[i].frameRateMin >> 32), (int) (wmfc->fmts[i].frameRateMin & 0xffffffff)); Tcl_DStringAppendElement(&ds, buffer); Tcl_DStringAppendElement(&ds, "frame-rate-max"); sprintf(buffer, "%d/%d", (int) (wmfc->fmts[i].frameRateMax >> 32), (int) (wmfc->fmts[i].frameRateMax & 0xffffffff)); Tcl_DStringAppendElement(&ds, buffer); Tcl_DictObjPut(NULL, dict, Tcl_NewIntObj(wmfc->fmts[i].index), Tcl_NewStringObj(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); Tcl_DStringFree(&ds); } Tcl_SetObjResult(interp, dict); break; } case CMD_mirror: { int x, y; if ((objc != 3) && (objc != 5)) { Tcl_WrongNumArgs(interp, 2, objv, "devid ?x y?"); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr == NULL) { goto devNotFound; } wmfc = (WMFC *) Tcl_GetHashValue(hPtr); if ((objc > 3) && ((Tcl_GetBooleanFromObj(interp, objv[3], &x) != TCL_OK) || (Tcl_GetBooleanFromObj(interp, objv[4], &y) != TCL_OK))) { return TCL_ERROR; } if (objc > 3) { wmfc->mirror = (x ? 1 : 0) | (y ? 2 : 0); } else { Tcl_Obj *list[2]; list[0] = Tcl_NewBooleanObj(wmfc->mirror & 1); list[1] = Tcl_NewBooleanObj(wmfc->mirror & 2); Tcl_SetObjResult(interp, Tcl_NewListObj(2, list)); } break; } case CMD_open: { char *devName; int isNew, numFmts = 0; IMFMediaSource *mediaSrc; Tcl_HashSearch search; MediaFmt *fmts = NULL; if (objc != 4) { Tcl_WrongNumArgs(interp, 2, objv, "device callback"); return TCL_ERROR; } devName = Tcl_GetString(objv[2]); hPtr = Tcl_FirstHashEntry(&wmfi->wmfc, &search); while (hPtr != NULL) { wmfc = (WMFC *) Tcl_GetHashValue(hPtr); if (strcmp(Tcl_DStringValue(&wmfc->devName), devName) == 0) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("\"%s\" is already opened", devName)); return TCL_ERROR; } hPtr = Tcl_NextHashEntry(&search); } mediaSrc = GetSource(devName); if (mediaSrc == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf("error opening \"%s\"", devName)); return TCL_ERROR; } fmts = GetFormatList(mediaSrc, &numFmts); if (fmts == NULL) { mediaSrc->lpVtbl->Release(mediaSrc); Tcl_SetObjResult(interp, Tcl_ObjPrintf("error getting format list for \"%s\"", devName)); return TCL_ERROR; } wmfc = (WMFC *) ckalloc(sizeof(WMFC)); memset(wmfc, 0, sizeof(WMFC)); wmfc->mediaSrc = mediaSrc; wmfc->srcReader = NULL; wmfc->streamEnd = 0; wmfc->frameReady = wmfc->frameQueued = -1; wmfc->mirror = 0; wmfc->rotate = 0; wmfc->interp = interp; wmfc->cbPending = NULL; InitializeCriticalSection(&wmfc->srcb.lock); Tcl_DStringInit(&wmfc->devName); Tcl_DStringAppend(&wmfc->devName, devName, -1); Tcl_DStringInit(&wmfc->cbCmd); Tcl_DStringAppend(&wmfc->cbCmd, Tcl_GetString(objv[3]), -1); wmfc->useFmt = -1; wmfc->numFmts = numFmts; wmfc->fmts = fmts; wmfc->cbCmdLen = Tcl_DStringLength(&wmfc->cbCmd); Tcl_InitHashTable(&wmfc->ctrl, TCL_STRING_KEYS); InitControls(wmfc, 0); wmfc->rstate = REC_STOP; wmfc->rchan = NULL; Tcl_DStringInit(&wmfc->rbdStr); sprintf(wmfc->devId, "wmfdev%d", wmfi->idCount++); hPtr = Tcl_CreateHashEntry(&wmfi->wmfc, wmfc->devId, &isNew); Tcl_SetHashValue(hPtr, (ClientData) wmfc); Tcl_SetObjResult(interp, Tcl_NewStringObj(wmfc->devId, -1)); break; } case CMD_orientation: { if (objc > 4) { Tcl_WrongNumArgs(interp, 2, objv, "devid ?degrees?"); return TCL_ERROR; } hPtr = Tcl_FindHashEntry(&wmfi->wmfc, Tcl_GetString(objv[2])); if (hPtr == NULL) { goto devNotFound; } wmfc = (WMFC *) Tcl_GetHashValue(hPtr); if (objc > 3) { int degrees; if (Tcl_GetIntFromObj(interp, objv[3], °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. * *------------------------------------------------------------------------- */ int Tclwmf_Init(Tcl_Interp *interp) { WMFI *wmfi; #ifdef USE_TCL_STUBS if (Tcl_InitStubs(interp, "8.4", 0) == NULL) { return TCL_ERROR; } #else if (Tcl_PkgRequire(interp, "Tcl", "8.4", 0) == NULL) { return TCL_ERROR; } #endif if (!WMFM.initialized) { Tcl_MutexLock(&wmfMutex); WmfSysInit(1); if (!WMFM.initialized) { Tcl_MutexUnlock(&wmfMutex); Tcl_SetResult(interp, "Windows Media Foundation not available", TCL_STATIC); return TCL_ERROR; } Tcl_MutexUnlock(&wmfMutex); } if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION) != TCL_OK) { return TCL_ERROR; } wmfi = (WMFI *) ckalloc(sizeof(WMFI)); memset(wmfi, 0, sizeof(WMFI)); wmfi->idCount = 0; wmfi->checkedTk = 0; Tcl_InitHashTable(&wmfi->wmfc, TCL_STRING_KEYS); Tcl_CreateObjCommand(interp, "wmf", WmfObjCmd, (ClientData) wmfi, WmfObjCmdDeleted); return TCL_OK; } /* *------------------------------------------------------------------------- * * DllMain -- * * Use DLL_PROCESS_DETACH reason code to unload depending * Windows Media Framework libraries. * *------------------------------------------------------------------------- */ BOOL WINAPI DllMain(HINSTANCE hmod, DWORD reason, PVOID unused) { if (reason == DLL_PROCESS_DETACH) { WmfSysInit(0); } return TRUE; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 * End: */