/*
* tcluvc.c --
*
* This file contains the implementation of the "uvc" Tcl built-in
* command which allows to operate cameras using libuvc.
*
* Copyright (c) 2016-24 Christian Werner <chw at ch minus werner dot de>
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include <tk.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <sched.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <dlfcn.h>
#include <libusb-1.0/libusb.h>
#include <libusb-1.0/libusb_dl.h>
#include <libuvc/libuvc.h>
#include <libuvc/libuvc_internal.h>
#ifdef HAVE_LIBUDEV
#include <libudev.h>
#endif
#if defined(ANDROID) && !defined(__TERMUX__)
#define LIBUSB_SO "libusb.so"
#endif
#if defined(linux) && !defined(ANDROID) && !defined(__TERMUX__)
#define LIBUSB_SO "libusb-1.0.so.0"
#endif
#ifdef __FreeBSD__
#define LIBUSB_SO "libusb.so.3"
#endif
#ifdef __OpenBSD__
#define LIBUSB_SO "libusb-1.0.so"
#endif
#ifdef __sun
#define LIBUSB_SO "libusb-1.0.so.0"
#endif
#ifdef __APPLE__
#define LIBUSB_SO "libusb-1.0.dylib"
#endif
#ifdef __HAIKU__
#define LIBUSB_SO "libusb-1.0.so.0"
#endif
#ifdef __TERMUX__
#define LIBUSB_SO "libusb-1.0.so"
#endif
#ifndef LIBUSB_SO
#error LIBUSB_SO unknown on this platform
#endif
#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
/*
* Structure for UVC control item.
*/
#define CTRL_HAS_MIN 0x0001
#define CTRL_HAS_MAX 0x0002
#define CTRL_HAS_RES 0x0004
#define CTRL_HAS_DEF 0x0008
typedef struct {
int code; /* Selector mask and selector, see below. */
const char *name; /* Name of control, lower case. */
int type; /* Item length in bytes. */
int count; /* Number of items. */
int flags; /* See CTRL_HAS_* masks above. */
unsigned char cur[32]; /* Current value of control. */
unsigned char min[32]; /* Minimum value, read on open. */
unsigned char max[32]; /* Maximum value, read on open. */
unsigned char res[32]; /* Resolution, read on open. */
unsigned char def[16]; /* Default value, read on open. */
} UCTRL;
/*
* Structure for UVC frame format item.
*/
typedef struct {
int width, height; /* Frame width, height in pixels. */
int fps; /* Default frame rate. */
int iscomp; /* Compressed format. */
int bpp; /* Bits per pixel for uncompressed formats. */
char fourcc[4]; /* Four character code for format. */
short fpsList[32]; /* List of supported frame rates. */
Tcl_DString str; /* Textual representation. */
} UFMT;
/*
* Recording states.
*/
#define REC_STOP 0
#define REC_RECPRI 1
#define REC_RECORD 2
#define REC_PAUSEPRI 3
#define REC_PAUSE 4
#define REC_ERROR 5
/*
* Control structure for libuvc capture.
*/
typedef struct {
int running; /* Greater than zero when acquiring. */
uvc_context_t *ctx; /* libuvc context. */
uvc_device_t *dev; /* UVC device. */
uvc_device_handle_t *devh; /* UVC device handle. */
uvc_frame_t *frame; /* Last captured frame or NULL. */
Tcl_Interp *interp; /* Interpreter for this object. */
#ifdef USE_ASYNC_HANDLER
Tcl_AsyncHandler async; /* Async handler signaled by callback. */
#else
Tcl_ThreadId tid; /* Thread identifier of interp. */
int numev; /* Number events queued. */
int idle; /* FrameReady() in do-when-idle. */
#endif
int mirror; /* Image mirror flags. */
int rotate; /* Image rotation in degrees. */
int width; /* Requested width. */
int height; /* Requested height. */
int conv; /* When true, convert early. */
int fps; /* Frames per second. */
int usefmt; /* Current UVC format index. */
int iscomp; /* Compressed format. */
int greyshift; /* For GRAY16 to GRAY8 conversion. */
Tcl_HashTable ctrl; /* UVC controls. */
Tcl_HashTable fmts; /* UVC formats. */
char devId[32]; /* Device id. */
Tcl_DString devName; /* Device name. */
int cbCmdLen; /* Initial length of callback command. */
Tcl_DString cbCmd; /* Callback command prefix. */
Tcl_WideInt counters[3]; /* Statistic counters. */
/* 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. */
struct timeval rrate; /* Recording frame rate. */
struct timeval rtv; /* Target time for next frame. */
struct timeval ltv; /* Time of last frame read. */
Tcl_Mutex rmutex; /* Recording mutex. */
struct {
Tcl_WideInt nframes;
Tcl_WideInt nframes0;
Tcl_WideInt totsize;
Tcl_WideInt segsize;
Tcl_WideInt segsize0;
Tcl_WideInt segstart;
int hdrsize;
Tcl_WideInt pos0;
struct timeval 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. */
} TUVC;
#ifndef USE_ASYNC_HANDLER
typedef struct {
Tcl_Event hdr; /* Generic event header. */
TUVC *tuvc; /* Pointer to control structure. */
} TUEVT;
#endif
/*
* Per interpreter control structure.
*/
typedef struct {
int idCount;
uvc_context_t *ctx; /* libuvc context. */
int checkedTk; /* Non-zero when Tk availability
* checked. */
Tcl_HashTable tuvcc; /* List of active TUVC instances. */
Tcl_Encoding enc; /* UTF-8 encoding. */
#ifdef HAVE_LIBUDEV
Tcl_Interp *interp; /* Interpreter for this object. */
int devsNeedRefresh; /* True when list needs refresh. */
Tcl_HashTable devs; /* List of devices (udev). */
int cbCmdLen; /* Init. length of callback command. */
Tcl_DString cbCmd; /* Callback command prefix. */
struct udev *udev; /* udev instance. */
struct udev_monitor *udevMon; /* udev monitor. */
#endif
} TUVCI;
/*
* UVC controls.
*/
#define UVC_SELECTOR 0xFF0000
#define UVC_SELECTOR_CT 0x010000
#define UVC_SELECTOR_PU 0x020000
#define UVC_SELECTOR_SU 0x030000
static struct {
int code;
const char *name;
int type;
int count;
} const UvcCtrlInfo[] = {
{ UVC_CT_SCANNING_MODE_CONTROL | UVC_SELECTOR_CT,
"scanning-mode", 1, 1 },
{ UVC_CT_AE_MODE_CONTROL | UVC_SELECTOR_CT,
"ae-mode", 1, 1 },
{ UVC_CT_AE_PRIORITY_CONTROL | UVC_SELECTOR_CT,
"ae-priority", 1, 1 },
{ UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL | UVC_SELECTOR_CT,
"exposure-time-abs", 4, 1 },
{ UVC_CT_EXPOSURE_TIME_RELATIVE_CONTROL | UVC_SELECTOR_CT,
"exposure-time-rel", 1, 1 },
{ UVC_CT_FOCUS_ABSOLUTE_CONTROL | UVC_SELECTOR_CT,
"focus-abs", 2, 1 },
{ UVC_CT_FOCUS_RELATIVE_CONTROL | UVC_SELECTOR_CT,
"focus-rel", 2, 2 },
{ UVC_CT_FOCUS_SIMPLE_CONTROL | UVC_SELECTOR_CT,
"focus-simple", 1, 1 },
{ UVC_CT_FOCUS_AUTO_CONTROL | UVC_SELECTOR_CT,
"focus-auto", 1, 1 },
{ UVC_CT_IRIS_ABSOLUTE_CONTROL | UVC_SELECTOR_CT,
"iris-abs", 2, 1},
{ UVC_CT_IRIS_RELATIVE_CONTROL | UVC_SELECTOR_CT,
"iris-rel", 1, 1 },
{ UVC_CT_ZOOM_ABSOLUTE_CONTROL | UVC_SELECTOR_CT,
"zoom-abs", 2, 1},
{ UVC_CT_ZOOM_RELATIVE_CONTROL | UVC_SELECTOR_CT,
"zoom-rel", 1, 3 },
{ UVC_CT_PANTILT_ABSOLUTE_CONTROL | UVC_SELECTOR_CT,
"pantilt-abs", 4, 2 },
{ UVC_CT_PANTILT_RELATIVE_CONTROL | UVC_SELECTOR_CT,
"pantilt-rel", 1, 4 },
{ UVC_CT_ROLL_ABSOLUTE_CONTROL | UVC_SELECTOR_CT,
"roll-abs", 2, 1 },
{ UVC_CT_ROLL_RELATIVE_CONTROL | UVC_SELECTOR_CT,
"roll-rel", 1, 2 },
{ UVC_CT_PRIVACY_CONTROL | UVC_SELECTOR_CT,
"privacy", 1, 1 },
{ UVC_CT_DIGITAL_WINDOW_CONTROL | UVC_SELECTOR_CT,
"digital-window", 2, 6 },
{ UVC_CT_REGION_OF_INTEREST_CONTROL | UVC_SELECTOR_CT,
"roi", 2, 5 },
{ UVC_PU_BACKLIGHT_COMPENSATION_CONTROL | UVC_SELECTOR_PU,
"backlight-compensation", 2, 1 },
{ UVC_PU_BRIGHTNESS_CONTROL | UVC_SELECTOR_PU,
"brightness", 2, 1 },
{ UVC_PU_CONTRAST_CONTROL | UVC_SELECTOR_PU,
"contrast", 2, 1 },
{ UVC_PU_CONTRAST_AUTO_CONTROL | UVC_SELECTOR_PU,
"contrast-auto", 1, 1 },
{ UVC_PU_GAIN_CONTROL | UVC_SELECTOR_PU,
"gain", 2, 1 },
{ UVC_PU_POWER_LINE_FREQUENCY_CONTROL | UVC_SELECTOR_PU,
"power-line-frequency", 1, 1 },
{ UVC_PU_HUE_CONTROL | UVC_SELECTOR_PU,
"hue", 2, 1 },
{ UVC_PU_HUE_AUTO_CONTROL | UVC_SELECTOR_PU,
"hue-auto", 1, 1 },
{ UVC_PU_SATURATION_CONTROL | UVC_SELECTOR_PU,
"saturation", 2, 1 },
{ UVC_PU_SHARPNESS_CONTROL | UVC_SELECTOR_PU,
"sharpness", 2, 1 },
{ UVC_PU_GAMMA_CONTROL | UVC_SELECTOR_PU,
"gamma", 2, 1 },
{ UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL | UVC_SELECTOR_PU,
"white-balance-temperature", 2, 1 },
{ UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL | UVC_SELECTOR_PU,
"white-balance-temperature-auto", 1, 1 },
{ UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL | UVC_SELECTOR_PU,
"white-balance-component", 2, 2 },
{ UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL | UVC_SELECTOR_PU,
"white-balance-component-auto", 1, 1 },
{ UVC_PU_DIGITAL_MULTIPLIER_CONTROL | UVC_SELECTOR_PU,
"digital-multiplier", 2, 1 },
{ UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL | UVC_SELECTOR_PU,
"digital-multiplier-limit", 2, 1 },
{ UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL | UVC_SELECTOR_PU,
"analog-video-standard", 1, 1 },
{ UVC_PU_ANALOG_LOCK_STATUS_CONTROL | UVC_SELECTOR_PU,
"analog-lock-status", 1, 1 },
{ UVC_SU_INPUT_SELECT_CONTROL | UVC_SELECTOR_SU,
"input-select", 1, 1 }
};
/*
* Mutex and flag used during initialization etc.
*/
TCL_DECLARE_MUTEX(uvcMutex)
static int uvcInitialized = 0;
static int tip609 = 0;
/*
* Stuff for dynamic linking libusb-1.0.so.0
*/
static void *libusb = NULL;
struct libusb_dl libusb_dl = { 0 };
#ifdef HAVE_LIBUDEV
/*
* Stuff for dynamic linking libudev.so.
*/
static void *libudev = NULL;
typedef const char *(*fn_device_get_action)(struct udev_device *);
typedef const char *(*fn_device_get_devnode)(struct udev_device *);
typedef const char *(*fn_device_get_property_value)
(struct udev_device *, const char *);
typedef const char *(*fn_device_get_sysattr_value)
(struct udev_device *, const char *);
typedef struct udev_device *(*fn_device_new_from_syspath)
(struct udev *, const char *);
typedef void (*fn_device_unref)(struct udev_device *);
typedef int (*fn_monitor_get_fd)(struct udev_monitor *);
typedef struct udev_device *(*fn_monitor_receive_device)
(struct udev_monitor *);
typedef void (*fn_monitor_unref)(struct udev_monitor *);
typedef int (*fn_monitor_enable_receiving)(struct udev_monitor *);
typedef struct udev *(*fn_new)(void);
typedef void (*fn_unref)(struct udev *);
typedef int (*fn_monitor_filter_add_match_subsystem_devtype)
(struct udev_monitor *, const char *, const char *);
typedef struct udev_monitor *(*fn_monitor_new_from_netlink)
(struct udev *, const char *);
typedef struct udev_enumerate *(*fn_enumerate_new)(struct udev *);
typedef int (*fn_enumerate_add_match_subsystem)
(struct udev_enumerate *, const char *);
typedef struct udev_list_entry *(*fn_enumerate_get_list_entry)
(struct udev_enumerate *);
typedef int (*fn_enumerate_scan_devices)(struct udev_enumerate *);
typedef void (*fn_enumerate_unref)(struct udev_enumerate *);
typedef const char *(*fn_list_entry_get_name)(struct udev_list_entry *);
typedef struct udev_list_entry *(*fn_list_entry_get_next)
(struct udev_list_entry *);
static struct {
fn_device_get_action device_get_action;
fn_device_get_devnode device_get_devnode;
fn_device_get_property_value device_get_property_value;
fn_device_get_sysattr_value device_get_sysattr_value;
fn_device_new_from_syspath device_new_from_syspath;
fn_device_unref device_unref;
fn_monitor_get_fd monitor_get_fd;
fn_monitor_receive_device monitor_receive_device;
fn_monitor_unref monitor_unref;
fn_monitor_enable_receiving monitor_enable_receiving;
fn_new new;
fn_unref unref;
fn_monitor_filter_add_match_subsystem_devtype
monitor_filter_add_match_subsystem_devtype;
fn_monitor_new_from_netlink monitor_new_from_netlink;
fn_enumerate_new enumerate_new;
fn_enumerate_add_match_subsystem enumerate_add_match_subsystem;
fn_enumerate_get_list_entry enumerate_get_list_entry;
fn_enumerate_scan_devices enumerate_scan_devices;
fn_enumerate_unref enumerate_unref;
fn_list_entry_get_name list_entry_get_name;
fn_list_entry_get_next list_entry_get_next;
} udev_dl;
#define udev_device_get_action udev_dl.device_get_action
#define udev_device_get_devnode udev_dl.device_get_devnode
#define udev_device_get_property_value udev_dl.device_get_property_value
#define udev_device_get_sysattr_value udev_dl.device_get_sysattr_value
#define udev_device_new_from_syspath udev_dl.device_new_from_syspath
#define udev_device_unref udev_dl.device_unref
#define udev_monitor_get_fd udev_dl.monitor_get_fd
#define udev_monitor_receive_device udev_dl.monitor_receive_device
#define udev_monitor_unref udev_dl.monitor_unref
#define udev_new udev_dl.new
#define udev_unref udev_dl.unref
#define udev_monitor_enable_receiving udev_dl.monitor_enable_receiving
#define udev_monitor_filter_add_match_subsystem_devtype \
udev_dl.monitor_filter_add_match_subsystem_devtype
#define udev_monitor_new_from_netlink udev_dl.monitor_new_from_netlink
#define udev_enumerate_new udev_dl.enumerate_new
#define udev_enumerate_add_match_subsystem \
udev_dl.enumerate_add_match_subsystem
#define udev_enumerate_get_list_entry udev_dl.enumerate_get_list_entry
#define udev_enumerate_scan_devices udev_dl.enumerate_scan_devices
#define udev_enumerate_unref udev_dl.enumerate_unref
#define udev_list_entry_get_name udev_dl.list_entry_get_name
#define udev_list_entry_get_next udev_dl.list_entry_get_next
#endif
/*
* Forward declarations.
*/
static int CheckForTk(TUVCI *tuvci, Tcl_Interp *interp);
static void CloseAVISegment(TUVC *tuvc, int end);
static uvc_frame_t * FrameToJPEG(uvc_frame_t *in, int greyshift);
static int WriteFrame(TUVC *tuvc, uvc_frame_t *frame);
static int StartRecording(TUVC *tuvc, Tcl_Interp *interp,
int objc, Tcl_Obj * const objv[]);
static void WriteAVIHeader(TUVC *tuvc, int end);
static void FinishRecording(TUVC *tuvc, int lock, int final);
static int RecordFrameFromData(TUVC *tuvc, Tcl_Interp *interp,
int objc, Tcl_Obj * const objv[]);
static int DataToPhoto(TUVCI *tuvci, Tcl_Interp *interp,
int objc, Tcl_Obj * const objv[]);
static void FrameCallback(uvc_frame_t *frame, void *arg);
static void FrameReady(ClientData clientData);
#ifdef USE_ASYNC_HANDLER
static int FrameReady0(ClientData clientData, Tcl_Interp *interp,
int code);
#else
static int FrameReady0(Tcl_Event *evPtr, int flags);
#endif
static int StopCapture(TUVC *tuvc);
static int StartCapture(TUVC *tuvc);
static int GetImage(TUVCI *tuvci, TUVC *tuvc, Tcl_Obj *arg);
static void InitControls(TUVC *tuvc);
static void GetControls(TUVC *tuvc, Tcl_Obj *list);
static void PrintVal(UCTRL *uctrl, unsigned char *data,
Tcl_DString *dsPtr, Tcl_Obj *list);
static int SetControls(TUVC *tuvc, int objc,
Tcl_Obj * const objv[]);
static void UvcObjCmdDeleted(ClientData clientData);
static int UvcObjCmd(ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj * const objv[]);
#ifdef HAVE_LIBUDEV
static char * UdevUVCName(TUVCI *tuvci, struct udev_device *dev,
Tcl_DString *dsPtr, Tcl_DString *ds2Ptr);
static void DecodeProp(TUVCI *tuvci, Tcl_DString *dsPtr,
const char *val);
static void UdevMonitor(ClientData clientData, int mask);
static void UdevScan(TUVCI *tuvci, struct udev_enumerate *udevEnum);
#endif
#ifdef HAVE_LIBUDEV
/*
*-------------------------------------------------------------------------
*
* UdevUVCName, DecodeProp --
*
* Given udev_device pointer make UVC name in caller
* provided Tcl_DString(s). Returns UVC name as string or NULL.
*
*-------------------------------------------------------------------------
*/
static char *
UdevUVCName(TUVCI *tuvci, struct udev_device *dev,
Tcl_DString *dsPtr, Tcl_DString *ds2Ptr)
{
const char *val, *busStr, *devStr;
int idVendor, idProduct;
char buffer[128];
val = udev_device_get_property_value(dev, "ID_USB_INTERFACES");
if ((val == NULL) || (strstr(val, ":0e02") == NULL)) {
/* not an UVC device */
return NULL;
}
val = udev_device_get_property_value(dev, "ID_VENDOR_ID");
if ((val == NULL) || (sscanf(val, "%x", &idVendor) != 1)) {
return NULL;
}
val = udev_device_get_property_value(dev, "ID_MODEL_ID");
if ((val == NULL) || (sscanf(val, "%x", &idProduct) != 1)) {
return NULL;
}
busStr = udev_device_get_sysattr_value(dev, "busnum");
devStr = udev_device_get_sysattr_value(dev, "devnum");
if ((busStr != NULL) && (devStr != NULL)) {
sprintf(buffer, "%04X:%04X:%s.%s", idVendor, idProduct,
busStr, devStr);
} else {
sprintf(buffer, "%04X:%04X", idVendor, idProduct);
}
Tcl_DStringAppend(dsPtr, buffer, -1);
if (ds2Ptr != NULL) {
val = udev_device_get_property_value(dev, "ID_VENDOR_ENC");
if (val != NULL) {
DecodeProp(tuvci, ds2Ptr, val);
}
Tcl_DStringAppend(ds2Ptr, "\0", 1);
val = udev_device_get_property_value(dev, "ID_MODEL_ENC");
if (val != NULL) {
DecodeProp(tuvci, ds2Ptr, val);
}
}
return Tcl_DStringValue(dsPtr);
}
static void
DecodeProp(TUVCI *tuvci, Tcl_DString *dsPtr, const char *val)
{
Tcl_DString raw, enc;
Tcl_DStringInit(&raw);
while (*val != '\0') {
if ((val[0] == '\\') && (val[1] == 'x')) {
char buf[4];
int ch = 0;
strncpy(buf, val + 2, 2);
buf[2] = '\0';
sscanf(buf, "%x", &ch);
if (ch <= 0) {
ch = '?';
}
buf[3] = ch;
Tcl_DStringAppend(&raw, buf + 3, 1);
val += strlen(buf) + 2;
} else {
Tcl_DStringAppend(&raw, val, 1);
++val;
}
}
Tcl_ExternalToUtfDString(tuvci->enc, Tcl_DStringValue(&raw),
Tcl_DStringLength(&raw), &enc);
Tcl_DStringFree(&raw);
Tcl_DStringAppend(dsPtr, Tcl_DStringValue(&enc),
Tcl_DStringLength(&enc));
Tcl_DStringFree(&enc);
}
/*
*-------------------------------------------------------------------------
*
* UdevMonitor --
*
* File handler for udev events. Depending on plug/unplug
* events, the table of devices is updated and the listen
* callback command is invoked.
*
*-------------------------------------------------------------------------
*/
static void
UdevMonitor(ClientData clientData, int mask)
{
TUVCI *tuvci = (TUVCI *) clientData;
Tcl_Interp *interp = tuvci->interp;
struct udev_device *dev;
Tcl_HashEntry *hPtr;
const char *action;
Tcl_DString ds;
char *devName = NULL;
int isNew;
if (!(mask & TCL_READABLE)) {
return;
}
dev = udev_monitor_receive_device(tuvci->udevMon);
if (dev == NULL) {
return;
}
action = udev_device_get_action(dev);
Tcl_DStringInit(&ds);
if (strcmp(action, "add") == 0) {
Tcl_DString *dsPtr;
dsPtr = (Tcl_DString *) ckalloc(sizeof(Tcl_DString));
Tcl_DStringInit(dsPtr);
devName = UdevUVCName(tuvci, dev, &ds, dsPtr);
if (devName != NULL) {
hPtr = Tcl_CreateHashEntry(&tuvci->devs,
(ClientData) devName, &isNew);
if (!isNew) {
action = NULL;
Tcl_DStringFree((Tcl_DString *) Tcl_GetHashValue(hPtr));
ckfree((char *) Tcl_GetHashValue(hPtr));
}
Tcl_SetHashValue(hPtr, (ClientData) dsPtr);
} else {
Tcl_DStringFree(dsPtr);
ckfree((char *) dsPtr);
action = NULL;
}
} else if (strcmp(action, "remove") == 0) {
devName = UdevUVCName(tuvci, dev, &ds, NULL);
hPtr = NULL;
if (devName != NULL) {
hPtr = Tcl_FindHashEntry(&tuvci->devs, (ClientData) devName);
}
if (hPtr == NULL) {
struct udev_enumerate *udevEnum;
struct udev_list_entry *item;
Tcl_DString ds2;
Tcl_HashTable avail;
Tcl_HashSearch search;
int found = 0;
/*
* Sync the table the long way...
*/
udevEnum = udev_enumerate_new(tuvci->udev);
if (udevEnum == NULL) {
action = NULL;
goto docb;
}
Tcl_InitHashTable(&avail, TCL_STRING_KEYS);
Tcl_DStringInit(&ds2);
udev_enumerate_add_match_subsystem(udevEnum, "usb");
udev_enumerate_scan_devices(udevEnum);
item = udev_enumerate_get_list_entry(udevEnum);
while (item != NULL) {
struct udev_device *dev2 = udev_device_new_from_syspath(
tuvci->udev, udev_list_entry_get_name(item));
if (dev2 != NULL) {
Tcl_DStringSetLength(&ds2, 0);
devName = UdevUVCName(tuvci, dev2, &ds2, NULL);
if (devName != NULL) {
hPtr = Tcl_FindHashEntry(&tuvci->devs,
(ClientData) devName);
if (hPtr != NULL) {
Tcl_CreateHashEntry(&avail, (ClientData) devName,
&isNew);
}
}
udev_device_unref(dev2);
}
item = udev_list_entry_get_next(item);
}
udev_enumerate_unref(udevEnum);
Tcl_DStringFree(&ds2);
hPtr = Tcl_FirstHashEntry(&tuvci->devs, &search);
while (hPtr != NULL) {
devName = (char *) Tcl_GetHashKey(&tuvci->devs, hPtr);
if (Tcl_FindHashEntry(&avail, (ClientData) devName) == NULL) {
/*
* This should be the/an orphaned entry.
*/
Tcl_DStringSetLength(&ds, 0);
Tcl_DStringAppend(&ds, devName, -1);
devName = Tcl_DStringValue(&ds);
found = 1;
/*
* Remove the entry, finally.
*/
Tcl_DStringFree((Tcl_DString *) Tcl_GetHashValue(hPtr));
ckfree((char *) Tcl_GetHashValue(hPtr));
Tcl_DeleteHashEntry(hPtr);
break;
}
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&avail);
if (!found) {
action = NULL;
}
/*
* Flag the table to be refreshed later.
*/
tuvci->devsNeedRefresh = 1;
} else {
Tcl_DStringFree((Tcl_DString *) Tcl_GetHashValue(hPtr));
ckfree((char *) Tcl_GetHashValue(hPtr));
Tcl_DeleteHashEntry(hPtr);
}
} else {
action = NULL;
}
docb:
if ((tuvci->cbCmdLen > 0) && (action != NULL) &&
(interp != NULL) && !Tcl_InterpDeleted(interp)) {
int ret;
Tcl_DStringSetLength(&tuvci->cbCmd, tuvci->cbCmdLen);
Tcl_DStringAppendElement(&tuvci->cbCmd, action);
if (devName == NULL) {
devName = "";
}
Tcl_DStringAppendElement(&tuvci->cbCmd, devName);
Tcl_Preserve((ClientData) interp);
ret = Tcl_EvalEx(interp, Tcl_DStringValue(&tuvci->cbCmd),
Tcl_DStringLength(&tuvci->cbCmd), TCL_EVAL_GLOBAL);
if (ret != TCL_OK) {
Tcl_AddErrorInfo(interp, "\n (uvc udev monitor)");
Tcl_BackgroundException(interp, ret);
}
Tcl_Release((ClientData) interp);
}
Tcl_DStringFree(&ds);
udev_device_unref(dev);
}
/*
*-------------------------------------------------------------------------
*
* UdevScan --
*
* Refresh device list using udev.
*
*-------------------------------------------------------------------------
*/
static void
UdevScan(TUVCI *tuvci, struct udev_enumerate *udevEnum)
{
struct udev_list_entry *item;
struct udev_device *dev;
Tcl_HashEntry *hPtr;
Tcl_DString ds;
char *devName;
int isNew, needFree = 0;
tuvci->devsNeedRefresh = 0;
if (udevEnum == NULL) {
Tcl_HashSearch search;
udevEnum = udev_enumerate_new(tuvci->udev);
if (udevEnum == NULL) {
return;
}
needFree = 1;
hPtr = Tcl_FirstHashEntry(&tuvci->devs, &search);
while (hPtr != NULL) {
Tcl_DStringFree((Tcl_DString *) Tcl_GetHashValue(hPtr));
ckfree((char *) Tcl_GetHashValue(hPtr));
Tcl_DeleteHashEntry(hPtr);
hPtr = Tcl_NextHashEntry(&search);
}
}
Tcl_DStringInit(&ds);
udev_enumerate_add_match_subsystem(udevEnum, "usb");
udev_enumerate_scan_devices(udevEnum);
item = udev_enumerate_get_list_entry(udevEnum);
while (item != NULL) {
dev = udev_device_new_from_syspath(tuvci->udev,
udev_list_entry_get_name(item));
if (dev != NULL) {
Tcl_DString *dsPtr;
Tcl_DStringSetLength(&ds, 0);
dsPtr = (Tcl_DString *) ckalloc(sizeof(Tcl_DString));
Tcl_DStringInit(dsPtr);
devName = UdevUVCName(tuvci, dev, &ds, dsPtr);
if (devName != NULL) {
hPtr = Tcl_CreateHashEntry(&tuvci->devs,
(ClientData) devName, &isNew);
if (!isNew) {
Tcl_DStringFree((Tcl_DString *) Tcl_GetHashValue(hPtr));
ckfree((char *) Tcl_GetHashValue(hPtr));
}
Tcl_SetHashValue(hPtr, (ClientData) dsPtr);
} else {
Tcl_DStringFree(dsPtr);
ckfree((char *) dsPtr);
}
udev_device_unref(dev);
}
item = udev_list_entry_get_next(item);
}
if (needFree) {
udev_enumerate_unref(udevEnum);
}
Tcl_DStringFree(&ds);
}
#endif
/*
*-------------------------------------------------------------------------
*
* CheckForTk --
*
* Check availability of Tk. Return standard Tcl error
* and appropriate error message if unavailable.
*
*-------------------------------------------------------------------------
*/
static int
CheckForTk(TUVCI *tuvci, Tcl_Interp *interp)
{
if (tuvci->checkedTk > 0) {
return TCL_OK;
} else if (tuvci->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) {
tuvci->checkedTk = -1;
return TCL_ERROR;
}
#else
if (Tcl_PkgRequire(interp, "Tk", "8.4", 0) == NULL) {
tuvci->checkedTk = -1;
return TCL_ERROR;
}
#endif
tuvci->checkedTk = 1;
return TCL_OK;
}
/*
*-------------------------------------------------------------------------
*
* CloseAVISegment --
*
* Recording: close current and optionally start next 2G AVI
* segment in output file.
*
*-------------------------------------------------------------------------
*/
static void
CloseAVISegment(TUVC *tuvc, 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(tuvc->rchan, 0, SEEK_CUR);
if (tuvc->avi.totsize > tuvc->avi.segsize) {
Tcl_Seek(tuvc->rchan, tuvc->avi.segstart, SEEK_SET);
xhdr = xhdr0;
PUT32LE(&xhdr.riff_size, tuvc->avi.segsize + 16);
PUT32LE(&xhdr.data_size, tuvc->avi.segsize + 4);
toWrite = sizeof(xhdr);
written = Tcl_WriteRaw(tuvc->rchan, (const char *) &xhdr, toWrite);
Tcl_Seek(tuvc->rchan, pos, SEEK_SET);
} else {
tuvc->avi.nframes0 = tuvc->avi.nframes;
tuvc->avi.segsize0 = tuvc->avi.segsize;
WriteAVIHeader(tuvc, 0);
if (tuvc->rstate == REC_ERROR) {
return;
}
}
tuvc->avi.segsize = 0;
tuvc->avi.segstart = pos;
if (!end && (written == toWrite)) {
xhdr = xhdr0;
toWrite = sizeof(xhdr);
written = Tcl_WriteRaw(tuvc->rchan, (const char *) &xhdr, toWrite);
}
if (written != toWrite) {
tuvc->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.
*
*-------------------------------------------------------------------------
*/
static uvc_frame_t *
FrameToJPEG(uvc_frame_t *in, int greyshift)
{
uvc_frame_t *out, *tmpFrame = in;
uvc_error_t uret;
if (in->frame_format == UVC_FRAME_FORMAT_MJPEG) {
return NULL;
}
if (in->frame_format == UVC_FRAME_FORMAT_GRAY16) {
tmpFrame = uvc_allocate_frame(in->width * in->height);
if (tmpFrame == NULL) {
return NULL;
}
uret = uvc_gray16to8(in, tmpFrame, greyshift);
if (uret) {
uvc_free_frame(tmpFrame);
return NULL;
}
} else if ((in->frame_format != UVC_FRAME_FORMAT_RGB) &&
(in->frame_format != UVC_FRAME_FORMAT_GRAY8)) {
tmpFrame = uvc_allocate_frame(in->width * in->height * 3);
if (tmpFrame == NULL) {
return NULL;
}
uret = uvc_any2rgb(in, tmpFrame);
if (uret) {
uvc_free_frame(tmpFrame);
return NULL;
}
}
out = uvc_allocate_frame(tmpFrame->data_bytes);
if (out == NULL) {
if (tmpFrame != in) {
uvc_free_frame(tmpFrame);
}
return NULL;
}
uret = uvc_rgb2mjpeg(tmpFrame, out);
if (tmpFrame != in) {
uvc_free_frame(tmpFrame);
}
if (uret) {
uvc_free_frame(out);
return NULL;
}
return out;
}
/*
*-------------------------------------------------------------------------
*
* WriteFrame --
*
* Recording: write given frame onto recording output channel.
* When called from the libuvc thread, the TUVC.rmutex should
* have been acquired by the caller to ensure the output
* channel stays valid. 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(TUVC *tuvc, uvc_frame_t *frame)
{
int n, toWrite, written, fWritten;
char buffer[256];
struct timeval now, diff;
uvc_frame_t *newFrame = NULL;
if (tuvc->rchan == NULL) {
tuvc->rstate = REC_ERROR;
}
gettimeofday(&now, NULL);
diff.tv_sec = now.tv_sec - frame->capture_time.tv_sec;
diff.tv_usec = now.tv_usec - frame->capture_time.tv_usec;
if (diff.tv_usec < 0) {
diff.tv_sec -= 1;
diff.tv_usec += 1000000;
}
if ((diff.tv_sec > 0) || ((diff.tv_sec == 0) && (diff.tv_usec > 0))) {
now = frame->capture_time;
} else {
/* Clock went back. */
}
diff.tv_sec = tuvc->rtv.tv_sec - now.tv_sec;
diff.tv_usec = tuvc->rtv.tv_usec - now.tv_usec;
if (diff.tv_usec < 0) {
diff.tv_sec -= 1;
diff.tv_usec += 1000000;
}
if ((diff.tv_sec > 0) || ((diff.tv_sec == 0) && (diff.tv_usec > 0))) {
return (tuvc->rstate == REC_ERROR) ? -1 : 0;
}
tuvc->rtv = now;
diff.tv_sec = tuvc->rtv.tv_sec - tuvc->ltv.tv_sec;
diff.tv_usec = tuvc->rtv.tv_usec - tuvc->ltv.tv_usec;
if (diff.tv_usec < 0) {
diff.tv_sec -= 1;
diff.tv_usec += 1000000;
}
tuvc->ltv = tuvc->rtv;
tuvc->rtv.tv_sec += tuvc->rrate.tv_sec;
tuvc->rtv.tv_usec += tuvc->rrate.tv_usec;
if (tuvc->rtv.tv_usec > 1000000) {
tuvc->rtv.tv_sec += 1;
tuvc->rtv.tv_usec -= 1000000;
}
if (frame->data_bytes == 0) {
return 0;
}
if (tuvc->rstate == REC_ERROR) {
return -1;
}
if (Tcl_DStringLength(&tuvc->rbdStr) > 0) {
/*
* HTTP MJPEG streaming webcam mode.
*/
if (frame->frame_format != UVC_FRAME_FORMAT_MJPEG) {
newFrame = FrameToJPEG(frame, tuvc->greyshift);
if (newFrame == NULL) {
tuvc->rstate = REC_ERROR;
return -1;
}
frame = newFrame;
}
n = Tcl_DStringLength(&tuvc->rbdStr);
sprintf(buffer, "\r\nContent-type: image/jpeg\r\n"
"Content-length: %d\r\n\r\n", (int) frame->data_bytes);
Tcl_DStringAppend(&tuvc->rbdStr, buffer, -1);
toWrite = Tcl_DStringLength(&tuvc->rbdStr);
written = Tcl_WriteRaw(tuvc->rchan, Tcl_DStringValue(&tuvc->rbdStr),
toWrite);
Tcl_DStringSetLength(&tuvc->rbdStr, n);
if (written == toWrite) {
toWrite = frame->data_bytes;
written = Tcl_WriteRaw(tuvc->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->frame_format == UVC_FRAME_FORMAT_MJPEG) {
size = frame->data_bytes;
} else if (memcmp(&tuvc->avi.avi_hdrv.strh.handler, "MJPG", 4) == 0) {
newFrame = FrameToJPEG(frame, tuvc->greyshift);
if (newFrame == NULL) {
tuvc->rstate = REC_ERROR;
return -1;
}
frame = newFrame;
size = frame->data_bytes;
} else {
size = frame->height * frame->step;
}
sizea = (size + 3) & ~3;
hdr = hdr0;
PUT32LE(&hdr.size, sizea);
fWritten = 0;
toWrite = sizeof(hdr);
written = Tcl_WriteRaw(tuvc->rchan, (const char *) &hdr, toWrite);
if (written == toWrite) {
toWrite = size;
written = Tcl_WriteRaw(tuvc->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(tuvc->rchan, four0, toWrite);
}
tuvc->avi.nframes++;
tuvc->avi.totsize += sizea + sizeof(hdr);
tuvc->avi.segsize += sizea + sizeof(hdr);
if (fWritten == size) {
if (tuvc->avi.segsize > 0x7F000000) {
CloseAVISegment(tuvc, 0);
tuvc->avi.curr_idx = tuvc->avi.num_idx = 0;
if (tuvc->avi.idx != NULL) {
ckfree((char *) tuvc->avi.idx);
tuvc->avi.idx = NULL;
}
} else if (tuvc->avi.totsize == tuvc->avi.segsize) {
/* Add index entry. */
if (tuvc->avi.curr_idx >= tuvc->avi.num_idx) {
int newsize = tuvc->avi.num_idx + 512;
struct AVI_IDX *newidx;
newidx = attemptckrealloc((char *) tuvc->avi.idx,
newsize * sizeof(struct AVI_IDX));
if (newidx == NULL) {
tuvc->avi.curr_idx = tuvc->avi.num_idx = 0;
if (tuvc->avi.idx != NULL) {
ckfree((char *) tuvc->avi.idx);
tuvc->avi.idx = NULL;
}
} else {
tuvc->avi.num_idx = newsize;
tuvc->avi.idx = newidx;
}
}
if (tuvc->avi.idx != NULL) {
struct AVI_IDX *idx = tuvc->avi.idx + tuvc->avi.curr_idx;
memcpy(idx->id, hdr0.id, sizeof(hdr0.id));
PUT32LE(&idx->flags, 0);
PUT32LE(&idx->offset, tuvc->avi.idx_off);
PUT32LE(&idx->size, sizea);
tuvc->avi.curr_idx++;
tuvc->avi.idx_off += sizea + sizeof(struct CHUNK_HDR);
}
}
}
/* Compute average frame rate. */
if (tuvc->avi.nframes == 0) {
tuvc->avi.rate = diff;
} else {
tuvc->avi.rate.tv_sec += diff.tv_sec;
tuvc->avi.rate.tv_sec /= 2;
tuvc->avi.rate.tv_usec += diff.tv_usec;
tuvc->avi.rate.tv_usec /= 2;
}
}
if (written != toWrite) {
tuvc->rstate = REC_ERROR;
}
if (newFrame != NULL) {
uvc_free_frame(newFrame);
}
return (tuvc->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 and thus can be used by the libuvc
* thread. However, proper guarding against race conditions
* is required using the UVC.rmutex when writing a frame and
* when closing the channel. Timing computations for adapting
* the recording frame rate are performed.
*
*-------------------------------------------------------------------------
*/
static int
StartRecording(TUVC *tuvc, Tcl_Interp *interp,
int objc, Tcl_Obj * const objv[])
{
int i, mode, doMJPG = 0, doUser = 0;
double rate = 0;
const char *p, *rbdStr = NULL;
Tcl_Channel chan = NULL, stack[2];
Tcl_HashEntry *hPtr;
long li;
UFMT *ufmt;
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;
}
}
}
li = tuvc->usefmt;
hPtr = Tcl_FindHashEntry(&tuvc->fmts, (ClientData) li);
if (hPtr == NULL) {
Tcl_SetResult(interp, "unsupported format", TCL_STATIC);
return TCL_ERROR;
}
ufmt = (UFMT *) Tcl_GetHashValue(hPtr);
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;
}
Tcl_MutexLock(&tuvc->rmutex);
FinishRecording(tuvc, 0, 0);
tuvc->rchan = chan;
if ((rate > 0.0) && (rate < tuvc->fps)) {
tuvc->rrate.tv_sec = 1.0 / rate;
tuvc->rrate.tv_usec = 1000000.0 / rate;
} else if (tuvc->fps <= 0) {
tuvc->rrate.tv_sec = 1;
tuvc->rrate.tv_usec = 0;
} else {
tuvc->rrate.tv_sec = 1 / tuvc->fps;
tuvc->rrate.tv_usec = 1000000 / tuvc->fps;
}
if ((rbdStr != NULL) && (strlen(rbdStr) > 0)) {
Tcl_DStringAppend(&tuvc->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. */
tuvc->avi.pos0 = pos0;
tuvc->avi.avi_hdr = avi_hdr;
PUT32LE(&tuvc->avi.avi_hdr.avih_size,
sizeof(struct RIFF_avih));
tuvc->avi.avi_hdrv = avi_hdrv;
PUT32LE(&tuvc->avi.avi_hdrv.strl_size,
sizeof(struct RIFF_strh) +
sizeof(struct RIFF_strf_vids) + 20);
PUT32LE(&tuvc->avi.avi_hdrv.strh_size,
sizeof(struct RIFF_strh));
PUT32LE(&tuvc->avi.avi_hdrv.strf_size,
sizeof(struct RIFF_strf_vids));
tuvc->avi.avi_hdro = avi_hdro;
PUT32LE(&tuvc->avi.avi_hdro.strl_size,
sizeof(unsigned int) + 12);
PUT32LE(&tuvc->avi.avi_hdro.strh_size,
sizeof(unsigned int));
tuvc->avi.avi_data = avi_data;
PUT32LE(&tuvc->avi.avi_hdr.avih.width, ufmt->width);
PUT32LE(&tuvc->avi.avi_hdr.avih.height, ufmt->height);
n = tuvc->rrate.tv_sec * 1000000 + tuvc->rrate.tv_usec;
PUT32LE(&tuvc->avi.avi_hdr.avih.uspf, n);
if (ufmt->iscomp || doMJPG) {
n = 24 * n / 1000;
} else {
n = ufmt->bpp * n / 1000;
}
n = n * ufmt->width * ufmt->height;
PUT32LE(&tuvc->avi.avi_hdr.avih.bps, n);
PUT32LE(&tuvc->avi.avi_hdr.avih.nstreams, 1);
tuvc->avi.hdrsize = Tcl_WriteRaw(tuvc->rchan,
(const char *) &tuvc->avi.avi_hdr,
sizeof(tuvc->avi.avi_hdr));
if (ufmt->iscomp || doMJPG) {
memcpy(&tuvc->avi.avi_hdrv.strh.handler, "MJPG", 4);
memcpy(&tuvc->avi.avi_hdrv.strf.compr, "MJPG", 4);
} else {
memcpy(&tuvc->avi.avi_hdrv.strh.handler, ufmt->fourcc, 4);
memcpy(&tuvc->avi.avi_hdrv.strf.compr, ufmt->fourcc, 4);
}
n = tuvc->rrate.tv_sec * 1000000 + tuvc->rrate.tv_usec;
PUT32LE(&tuvc->avi.avi_hdrv.strh.scale, n);
PUT32LE(&tuvc->avi.avi_hdrv.strh.rate, 1000000);
PUT32LE(&tuvc->avi.avi_hdrv.strf.size, sizeof(tuvc->avi.avi_hdrv.strf));
PUT32LE(&tuvc->avi.avi_hdrv.strf.width, ufmt->width);
PUT32LE(&tuvc->avi.avi_hdrv.strf.height, ufmt->height);
PUT16LE(&tuvc->avi.avi_hdrv.strf.planes, 1);
PUT16LE(&tuvc->avi.avi_hdrv.strf.bits, ufmt->bpp);
n = ufmt->bpp * ufmt->width * ufmt->height;
PUT32LE(&tuvc->avi.avi_hdrv.strf.image_size, n);
tuvc->avi.hdrsize += Tcl_WriteRaw(tuvc->rchan,
(const char *) &tuvc->avi.avi_hdrv,
sizeof(tuvc->avi.avi_hdrv));
tuvc->avi.hdrsize += Tcl_WriteRaw(tuvc->rchan,
(const char *) &tuvc->avi.avi_hdro,
sizeof(tuvc->avi.avi_hdro));
Tcl_WriteRaw(tuvc->rchan, (const char *) &tuvc->avi.avi_data,
sizeof(tuvc->avi.avi_data));
tuvc->avi.segsize0 = 4;
WriteAVIHeader(tuvc, 0);
tuvc->avi.curr_idx = tuvc->avi.num_idx = 0;
tuvc->avi.idx_off = 4;
if (tuvc->avi.idx != NULL) {
ckfree((char *) tuvc->avi.idx);
tuvc->avi.idx = NULL;
}
}
/* Reserve 500us for processing. */
tuvc->rrate.tv_usec -= 500;
if (tuvc->rrate.tv_usec < 0) {
tuvc->rrate.tv_sec -= 1;
tuvc->rrate.tv_usec += 1000000;
}
gettimeofday(&tuvc->ltv, NULL);
tuvc->rtv = tuvc->ltv;
if (doUser) {
tuvc->ruser = 1;
tuvc->rstate = tuvc->running ? REC_RECORD : REC_PAUSE;
} else {
tuvc->ruser = 0;
if (tuvc->running) {
tuvc->rstate = tuvc->conv ? REC_RECPRI : REC_RECORD;
} else {
tuvc->rstate = tuvc->conv ? REC_PAUSEPRI : REC_PAUSE;
}
}
Tcl_MutexUnlock(&tuvc->rmutex);
return TCL_OK;
}
/*
*-------------------------------------------------------------------------
*
* WriteAVIHeader --
*
* (Re)write AVI file header with current chunk sizes at
* the very begin of the AVI file.
*
*-------------------------------------------------------------------------
*/
static void
WriteAVIHeader(TUVC *tuvc, int end)
{
int size, idx_size;
Tcl_WideInt pos;
if (end && (tuvc->avi.idx != NULL)) {
/* Write index. */
struct CHUNK_HDR idxh;
static const struct CHUNK_HDR idxh0 = {
{ 'i', 'd', 'x', '1' },
0
};
idxh = idxh0;
idx_size = tuvc->avi.curr_idx * sizeof(struct AVI_IDX);
PUT32LE(&idxh.size, idx_size);
Tcl_WriteRaw(tuvc->rchan, (const char *) &idxh, sizeof(idxh));
Tcl_WriteRaw(tuvc->rchan, (const char *) tuvc->avi.idx, idx_size);
/* Mark index present. */
PUT32LE(&tuvc->avi.avi_hdr.avih.flags, 0x10);
idx_size += sizeof(struct CHUNK_HDR);
} else {
/* Mark index absent. */
PUT32LE(&tuvc->avi.avi_hdr.avih.flags, 0);
idx_size = 0;
}
/* For MJPG use computed average frame rate. */
if (memcmp(&tuvc->avi.avi_hdrv.strh.handler, "MJPG", 4) == 0) {
int n;
n = tuvc->avi.rate.tv_sec * 1000000 + tuvc->avi.rate.tv_usec;
PUT32LE(&tuvc->avi.avi_hdr.avih.uspf, n);
PUT32LE(&tuvc->avi.avi_hdrv.strh.scale, n);
}
size = tuvc->avi.hdrsize + tuvc->avi.segsize0;
PUT32LE(&tuvc->avi.avi_hdr.riff_size, size + idx_size);
size = tuvc->avi.hdrsize - 20;
PUT32LE(&tuvc->avi.avi_hdr.hdrl_size, size);
size = tuvc->avi.nframes0;
PUT32LE(&tuvc->avi.avi_hdr.avih.nframes, size);
PUT32LE(&tuvc->avi.avi_hdrv.strh.length, size);
size = tuvc->avi.segsize0 + 4;
PUT32LE(&tuvc->avi.avi_data.data_size, size);
size = tuvc->avi.nframes;
PUT32LE(&tuvc->avi.avi_hdro.nframes, size);
pos = Tcl_Seek(tuvc->rchan, 0, SEEK_CUR);
Tcl_Seek(tuvc->rchan, tuvc->avi.pos0, SEEK_SET);
Tcl_WriteRaw(tuvc->rchan, (const char *) &tuvc->avi.avi_hdr,
sizeof(tuvc->avi.avi_hdr));
Tcl_WriteRaw(tuvc->rchan, (const char *) &tuvc->avi.avi_hdrv,
sizeof(tuvc->avi.avi_hdrv));
Tcl_WriteRaw(tuvc->rchan, (const char *) &tuvc->avi.avi_hdro,
sizeof(tuvc->avi.avi_hdro));
Tcl_WriteRaw(tuvc->rchan, (const char *) &tuvc->avi.avi_data,
sizeof(tuvc->avi.avi_data));
if (Tcl_Seek(tuvc->rchan, pos, SEEK_SET) == (Tcl_WideInt) -1) {
tuvc->rstate = REC_ERROR;
}
if (end) {
tuvc->avi.curr_idx = tuvc->avi.num_idx = 0;
if (tuvc->avi.idx != NULL) {
ckfree((char *) tuvc->avi.idx);
tuvc->avi.idx = NULL;
}
}
}
/*
*-------------------------------------------------------------------------
*
* FinishRecording --
*
* Close recording channel and release resources. Must be
* called from the thread which opened the UVC device.
* Optionally TUVC.rmutex is locked, optionally it is
* finalized when the UVC device gets closed.
*
*-------------------------------------------------------------------------
*/
static void
FinishRecording(TUVC *tuvc, int lock, int final)
{
if (lock) {
Tcl_MutexLock(&tuvc->rmutex);
}
if ((tuvc->rchan != NULL) &&
(Tcl_DStringLength(&tuvc->rbdStr) == 0)) {
CloseAVISegment(tuvc, 1);
WriteAVIHeader(tuvc, 1);
}
Tcl_DStringFree(&tuvc->rbdStr);
if (tuvc->rchan != NULL) {
Tcl_Close(NULL, tuvc->rchan);
tuvc->rchan = NULL;
memset(&tuvc->avi, 0, sizeof(tuvc->avi));
}
if (lock) {
Tcl_MutexUnlock(&tuvc->rmutex);
}
if (final) {
Tcl_MutexFinalize(&tuvc->rmutex);
}
}
/*
*-------------------------------------------------------------------------
*
* RecordFrameFromData --
*
* Write single frame from byte array data.
*
*-------------------------------------------------------------------------
*/
static int
RecordFrameFromData(TUVC *tuvc, Tcl_Interp *interp,
int objc, Tcl_Obj * const objv[])
{
int width, height, bpp, length, ret;
unsigned char *data;
long li;
Tcl_HashEntry *hPtr;
UFMT *ufmt;
uvc_frame_t *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);
li = tuvc->usefmt;
hPtr = Tcl_FindHashEntry(&tuvc->fmts, li);
if (hPtr == NULL) {
Tcl_SetResult(interp, "unsupported format", TCL_STATIC);
return TCL_ERROR;
}
ufmt = (UFMT *) Tcl_GetHashValue(hPtr);
if ((length < width * height * bpp) ||
(width != ufmt->width) || (height != ufmt->height)) {
Tcl_SetResult(interp, "incompatible frame data", TCL_STATIC);
return TCL_ERROR;
}
if (!tuvc->ruser ||
((tuvc->rstate != REC_RECORD) && (tuvc->rstate != REC_PAUSE))) {
Tcl_SetResult(interp, "wrong recording state for frame",
TCL_STATIC);
return TCL_ERROR;
}
frame = uvc_allocate_frame(0);
if (frame == NULL) {
Tcl_SetResult(interp, "out of memory", TCL_STATIC);
return TCL_ERROR;
}
frame->library_owns_data = 0;
frame->width = width;
frame->height = height;
frame->step = width * bpp;
frame->sequence = 0;
frame->source = 0;
frame->data = data;
frame->data_bytes = length;
switch (bpp) {
case 1:
frame->frame_format = UVC_FRAME_FORMAT_GRAY8;
break;
case 2:
frame->frame_format = UVC_FRAME_FORMAT_GRAY16;
break;
default:
frame->frame_format = UVC_FRAME_FORMAT_RGB;
break;
}
gettimeofday(&frame->capture_time, NULL);
ret = WriteFrame(tuvc, frame);
uvc_free_frame(frame);
Tcl_SetObjResult(interp, Tcl_NewIntObj(ret));
return TCL_OK;
}
/*
*-------------------------------------------------------------------------
*
* DataToPhoto --
*
* Put byte array data to a Tk photo image.
*
*-------------------------------------------------------------------------
*/
static int
DataToPhoto(TUVCI *tuvci, 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(tuvci, 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;
}
/*
*-------------------------------------------------------------------------
*
* FrameCallback --
*
* Invoked by a internal libuvc thread to indicate a frame
* ready to be processed further. Frame is converted to RGB
* and the interpreter associated with the UVC device woken
* up by marking a Tcl_AsyncHandler or by queuing an event.
*
*-------------------------------------------------------------------------
*/
static void
FrameCallback(uvc_frame_t *frame, void *arg)
{
TUVC *tuvc = (TUVC *) arg;
uvc_frame_t *newFrame;
uvc_error_t uret;
#ifndef USE_ASYNC_HANDLER
TUEVT *event;
#endif
#ifdef USE_ASYNC_HANDLER
if (tuvc->async == NULL) {
/* should never happen */
return;
}
#else
if (tuvc->tid == NULL) {
/* should never happen */
return;
}
#endif
if (tuvc->rstate == REC_RECPRI) {
Tcl_MutexLock(&tuvc->rmutex);
WriteFrame(tuvc, frame);
Tcl_MutexUnlock(&tuvc->rmutex);
}
if (tuvc->conv && (frame->frame_format != UVC_FRAME_FORMAT_GRAY8) &&
(frame->frame_format != UVC_FRAME_FORMAT_RGB)) {
if (frame->frame_format == UVC_FRAME_FORMAT_GRAY16) {
newFrame = uvc_allocate_frame(frame->data_bytes / 2);
if (newFrame == NULL) {
return;
}
uret = uvc_gray16to8(frame, newFrame, tuvc->greyshift);
} else {
newFrame = uvc_allocate_frame(frame->data_bytes);
if (newFrame == NULL) {
return;
}
if (frame->frame_format == UVC_FRAME_FORMAT_MJPEG) {
uret = uvc_mjpeg2rgb(frame, newFrame);
} else {
uret = uvc_any2rgb(frame, newFrame);
}
}
if (uret) {
uvc_free_frame(newFrame);
return;
}
} else {
newFrame = frame;
}
Tcl_MutexLock(&uvcMutex);
if (tuvc->frame != NULL) {
uvc_frame_t *oldFrame = tuvc->frame;
tuvc->frame = newFrame;
newFrame = oldFrame;
tuvc->counters[2] += 1; /* frame dropped */
} else {
tuvc->frame = newFrame;
newFrame = NULL;
}
tuvc->counters[0] += 1;
#ifdef USE_ASYNC_HANDLER
Tcl_AsyncMark(tuvc->async);
#else
if ((tuvc->tid != NULL) && (tuvc->numev == 0)) {
event = (TUEVT *) ckalloc(sizeof(TUEVT));
event->hdr.proc = FrameReady0;
event->hdr.nextPtr = NULL;
event->tuvc = tuvc;
if (tip609) {
/* TCL_QUEUE_TAIL_ALERT_IF_EMPTY */
Tcl_ThreadQueueEvent(tuvc->tid, &event->hdr, TCL_QUEUE_TAIL | 4);
} else {
Tcl_ThreadQueueEvent(tuvc->tid, &event->hdr, TCL_QUEUE_TAIL);
Tcl_ThreadAlert(tuvc->tid);
}
tuvc->numev++;
}
#endif
Tcl_MutexUnlock(&uvcMutex);
if (newFrame != NULL) {
uvc_free_frame(newFrame);
}
}
/*
*-------------------------------------------------------------------------
*
* FrameReady, FrameReady0 --
*
* A frame became ready. FrameReady is a do-when-idle handler
* triggered by FrameReady0 which is a Tcl_AsyncHandler or an
* event callback function triggered by the FrameCallback function
* which runs in a different libuvc controlled thread. The
* top-level function invokes the Tcl callback procedure which
* then can retrieve the frame buffer using the "uvc image ..."
* command.
*
*-------------------------------------------------------------------------
*/
static void
FrameReady(ClientData clientData)
{
TUVC *tuvc = (TUVC *) clientData;
Tcl_Interp *interp = tuvc->interp;
int ret;
#ifndef USE_ASYNC_HANDLER
Tcl_MutexLock(&uvcMutex);
if (tuvc->idle) {
tuvc->numev = 0;
}
Tcl_MutexUnlock(&uvcMutex);
#endif
if (!tuvc->ruser && (tuvc->rstate == REC_RECORD)) {
uvc_frame_t *frame;
Tcl_MutexLock(&uvcMutex);
frame = tuvc->frame;
tuvc->frame = NULL;
Tcl_MutexUnlock(&uvcMutex);
if (frame != NULL) {
WriteFrame(tuvc, frame);
}
Tcl_MutexLock(&uvcMutex);
if ((frame != NULL) && (tuvc->frame == NULL)) {
/* Put back last frame */
tuvc->frame = frame;
frame = NULL;
}
Tcl_MutexUnlock(&uvcMutex);
if (frame != NULL) {
uvc_free_frame(frame);
}
}
if (tuvc->frame == NULL) {
/* should never happen */
return;
}
Tcl_DStringSetLength(&tuvc->cbCmd, tuvc->cbCmdLen);
Tcl_DStringAppendElement(&tuvc->cbCmd, tuvc->devId);
Tcl_Preserve((ClientData) interp);
ret = Tcl_EvalEx(interp, Tcl_DStringValue(&tuvc->cbCmd),
Tcl_DStringLength(&tuvc->cbCmd), TCL_EVAL_GLOBAL);
if (ret != TCL_OK) {
Tcl_AddErrorInfo(interp, "\n (uvc event handler)");
Tcl_BackgroundException(interp, ret);
StopCapture(tuvc);
}
Tcl_Release((ClientData) interp);
}
#ifdef USE_ASYNC_HANDLER
static int
FrameReady0(ClientData clientData, Tcl_Interp *interp, int code)
{
Tcl_CancelIdleCall(FrameReady, clientData);
Tcl_DoWhenIdle(FrameReady, clientData);
return code;
}
#else
static int
FrameReady0(Tcl_Event *evPtr, int flags)
{
TUEVT *tevPtr = (TUEVT *) evPtr;
TUVC *tuvc = tevPtr->tuvc;
int doit = 0;
Tcl_MutexLock(&uvcMutex);
if (tuvc->tid != NULL) {
if (!tuvc->idle) {
tuvc->numev--;
}
doit = 1;
} else {
tuvc->numev = 0;
}
Tcl_MutexUnlock(&uvcMutex);
if (doit) {
if (tuvc->idle) {
Tcl_CancelIdleCall(FrameReady, (ClientData) tuvc);
Tcl_DoWhenIdle(FrameReady, (ClientData) tuvc);
} else {
FrameReady((ClientData) tuvc);
}
}
return 1;
}
#endif
/*
*-------------------------------------------------------------------------
*
* StopCapture --
*
* Stop capture if running. UVC streaming is turned off and
* the async handler removed. A pending idle call to FrameReady
* is cancelled, too. If using the Tcl event queue instead of
* async notification, only the thread id to which further
* events would be queued has to be cleared.
*
*-------------------------------------------------------------------------
*/
static int
StopCapture(TUVC *tuvc)
{
if (tuvc->running > 0) {
uvc_stop_streaming(tuvc->devh);
#ifdef USE_ASYNC_HANDLER
Tcl_AsyncDelete(tuvc->async);
tuvc->async = NULL;
#else
tuvc->tid = NULL;
#endif
Tcl_CancelIdleCall(FrameReady, (ClientData) tuvc);
tuvc->running = 0;
if (tuvc->rstate == REC_RECPRI) {
tuvc->rstate = REC_PAUSEPRI;
} else if (tuvc->rstate == REC_RECORD) {
tuvc->rstate = REC_PAUSE;
}
}
return TCL_OK;
}
/*
*-------------------------------------------------------------------------
*
* StartCapture --
*
* Setup image acquisition:
* - set capture format and size
* - estabilsh async handler for FrameReady callback
* - start UVC streaming
*
*-------------------------------------------------------------------------
*/
static int
StartCapture(TUVC *tuvc)
{
Tcl_Interp *interp = tuvc->interp;
uvc_stream_ctrl_t ctrl;
uvc_error_t uret;
int i;
static const struct {
enum uvc_frame_format fmt;
int iscomp;
} tryfmts[] = {
{ UVC_FRAME_FORMAT_MJPEG, 1 },
{ UVC_FRAME_FORMAT_YUYV, 0 },
{ UVC_FRAME_FORMAT_UYVY, 0 },
{ UVC_FRAME_FORMAT_GRAY16, 0 },
{ UVC_FRAME_FORMAT_GRAY8, 0 },
{ UVC_FRAME_FORMAT_RGB, 0 }
};
if (tuvc->running > 0) {
return TCL_OK;
}
/* set format/size */
uret = UVC_ERROR_INVALID_MODE;
for (i = 0; i < sizeof(tryfmts) / sizeof(tryfmts[0]); i++) {
if (!tuvc->iscomp && tryfmts[i].iscomp) {
continue;
}
uret = uvc_get_stream_ctrl_format_size(tuvc->devh, &ctrl,
tryfmts[i].fmt,
tuvc->width, tuvc->height,
tuvc->fps);
if (uret == UVC_SUCCESS) {
break;
}
i++;
}
if (uret < 0) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error setting format: %s",
uvc_strerror(uret)));
return TCL_ERROR;
}
/* start capture */
tuvc->running = 1;
tuvc->counters[0] = tuvc->counters[1] = tuvc->counters[2] = 0;
#ifdef USE_ASYNC_HANDLER
tuvc->async = Tcl_AsyncCreate(FrameReady0, (ClientData) tuvc);
#else
tuvc->tid = Tcl_GetCurrentThread();
tuvc->numev = 0;
#endif
uret = uvc_start_streaming(tuvc->devh, &ctrl, FrameCallback, tuvc, 0);
if (uret < 0) {
tuvc->running = 0;
#ifdef USE_ASYNC_HANDLER
Tcl_AsyncDelete(tuvc->async);
tuvc->async = NULL;
#else
tuvc->tid = NULL;
#endif
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error starting streaming: %s",
uvc_strerror(uret)));
return TCL_ERROR;
}
if (tuvc->rstate == REC_PAUSEPRI) {
gettimeofday(&tuvc->ltv, NULL);
tuvc->rtv = tuvc->ltv;
tuvc->rstate = REC_RECPRI;
} else if (tuvc->rstate == REC_PAUSE) {
gettimeofday(&tuvc->ltv, NULL);
tuvc->rtv = tuvc->ltv;
tuvc->rstate = REC_RECORD;
}
return TCL_OK;
}
/*
*-------------------------------------------------------------------------
*
* GetImage --
*
* Retrieve last captured frame as photo image or byte array.
*
*-------------------------------------------------------------------------
*/
static int
GetImage(TUVCI *tuvci, TUVC *tuvc, Tcl_Obj *arg)
{
Tcl_Interp *interp = tuvc->interp;
uvc_frame_t *frame;
Tk_PhotoHandle photo = NULL;
int result = TCL_OK, done = 0;
char *name;
if (arg != NULL) {
if (CheckForTk(tuvci, tuvc->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;
}
}
/* Temporarily take out last frame. */
Tcl_MutexLock(&uvcMutex);
frame = tuvc->frame;
tuvc->frame = NULL;
Tcl_MutexUnlock(&uvcMutex);
if (frame == NULL) {
/* no image available */
noImage:
if (photo != NULL) {
Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
} else {
Tcl_SetResult(interp, "no image available", TCL_STATIC);
result = TCL_ERROR;
}
goto done;
}
if ((photo == NULL) && (frame->frame_format == UVC_FRAME_FORMAT_GRAY16)) {
goto doByteArray;
}
if ((frame->frame_format != UVC_FRAME_FORMAT_RGB) &&
(frame->frame_format != UVC_FRAME_FORMAT_GRAY8)) {
uvc_frame_t *newFrame;
uvc_error_t uret;
int frameSize;
switch (frame->frame_format) {
case UVC_FRAME_FORMAT_YUYV:
case UVC_FRAME_FORMAT_UYVY:
case UVC_FRAME_FORMAT_MJPEG:
frameSize = frame->width * frame->height * 3;
break;
case UVC_FRAME_FORMAT_GRAY16:
frameSize = frame->width * frame->height;
break;
default:
goto noImage;
}
newFrame = uvc_allocate_frame(frameSize);
if (newFrame == NULL) {
goto noImage;
}
switch (frame->frame_format) {
case UVC_FRAME_FORMAT_YUYV:
uret = uvc_yuyv2rgb(frame, newFrame);
break;
case UVC_FRAME_FORMAT_UYVY:
uret = uvc_uyvy2rgb(frame, newFrame);
break;
case UVC_FRAME_FORMAT_MJPEG:
uret = uvc_mjpeg2rgb(frame, newFrame);
break;
case UVC_FRAME_FORMAT_GRAY16:
uret = uvc_gray16to8(frame, newFrame, tuvc->greyshift);
break;
default:
uret = UVC_ERROR_NOT_SUPPORTED;
break;
}
if (uret) {
uvc_free_frame(newFrame);
goto noImage;
}
uvc_free_frame(frame);
frame = newFrame;
}
if (photo != NULL) {
Tk_PhotoImageBlock block;
int rot = tuvc->rotate;
int width = frame->width;
int height = frame->height;
if (frame->frame_format == UVC_FRAME_FORMAT_GRAY8) {
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 = frame->step;
block.pixelPtr = frame->data;
if ((tuvc->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 ((tuvc->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 ((tuvc->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));
done = 1;
}
}
doByteArray:
if (photo == NULL) {
unsigned char *rawPtr;
int rawSize;
Tcl_Obj *list[4];
list[0] = Tcl_NewIntObj(frame->width);
list[1] = Tcl_NewIntObj(frame->height);
if (frame->frame_format == UVC_FRAME_FORMAT_GRAY16) {
rawSize = frame->width * frame->height * 2;
list[2] = Tcl_NewIntObj(2);
} else if (frame->frame_format == UVC_FRAME_FORMAT_GRAY8) {
rawSize = frame->width * frame->height;
list[2] = Tcl_NewIntObj(1);
} else {
rawSize = frame->width * frame->height * 3;
list[2] = Tcl_NewIntObj(3);
}
rawPtr = frame->data;
list[3] = Tcl_NewByteArrayObj(rawPtr, rawSize);
Tcl_SetObjResult(interp, Tcl_NewListObj(4, list));
done = 1;
}
done:
Tcl_MutexLock(&uvcMutex);
if ((frame != NULL) && (tuvc->frame == NULL)) {
/* Put back last frame */
tuvc->frame = frame;
frame = NULL;
}
if (done) {
tuvc->counters[1] += 1;
}
Tcl_MutexUnlock(&uvcMutex);
if (frame != NULL) {
uvc_free_frame(frame);
}
return result;
}
/*
*-------------------------------------------------------------------------
*
* InitControls --
*
* Fill (or release) TUVC controls with meta information about
* the device's controls.
*
*-------------------------------------------------------------------------
*/
static void
InitControls(TUVC *tuvc)
{
int i, isNew;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
UCTRL *uctrl;
UFMT *ufmt, *ufmt0 = NULL;
/* first, free up old stuff */
hPtr = Tcl_FirstHashEntry(&tuvc->ctrl, &search);
while (hPtr != NULL) {
uctrl = (UCTRL *) Tcl_GetHashValue(hPtr);
ckfree((char *) uctrl);
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&tuvc->ctrl);
Tcl_InitHashTable(&tuvc->ctrl, TCL_STRING_KEYS);
hPtr = Tcl_FirstHashEntry(&tuvc->fmts, &search);
while (hPtr != NULL) {
ufmt = (UFMT *) Tcl_GetHashValue(hPtr);
Tcl_DStringFree(&ufmt->str);
ckfree((char *) ufmt);
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&tuvc->fmts);
Tcl_InitHashTable(&tuvc->fmts, TCL_ONE_WORD_KEYS);
/* done, when there's no opened device */
if (tuvc->devh == NULL) {
return;
}
/* fill in new information */
for (i = 0; i < sizeof(UvcCtrlInfo) / sizeof(UvcCtrlInfo[0]); i++) {
uvc_error_t uret;
int index, len;
switch (UvcCtrlInfo[i].code & UVC_SELECTOR) {
case UVC_SELECTOR_CT:
if (uvc_get_camera_terminal(tuvc->devh) == NULL) {
continue;
}
index = (uvc_get_camera_terminal(tuvc->devh)->bTerminalID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
case UVC_SELECTOR_PU:
if (uvc_get_processing_units(tuvc->devh) == NULL) {
continue;
}
index = (uvc_get_processing_units(tuvc->devh)->bUnitID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
case UVC_SELECTOR_SU:
if (uvc_get_selector_units(tuvc->devh) == NULL) {
continue;
}
index = (uvc_get_selector_units(tuvc->devh)->bUnitID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
default:
continue;
}
uctrl = (UCTRL *) ckalloc(sizeof(UCTRL));
memset(uctrl, 0, sizeof(UCTRL));
uctrl->code = UvcCtrlInfo[i].code;
uctrl->name = UvcCtrlInfo[i].name;
uctrl->type = UvcCtrlInfo[i].type;
uctrl->count = UvcCtrlInfo[i].count;
len = uctrl->type * uctrl->count;
uret = libusb_control_transfer(tuvc->devh->usb_devh, 0xa1, UVC_GET_CUR,
(uctrl->code << 8) & 0xFF00, index,
uctrl->cur, len, 0);
if (uret != len) {
ckfree((char *) uctrl);
continue;
}
uret = libusb_control_transfer(tuvc->devh->usb_devh, 0xa1, UVC_GET_MIN,
(uctrl->code << 8) & 0xFF00, index,
uctrl->min, len, 0);
if (uret == len) {
uctrl->flags |= CTRL_HAS_MIN;
}
uret = libusb_control_transfer(tuvc->devh->usb_devh, 0xa1, UVC_GET_MAX,
(uctrl->code << 8) & 0xFF00, index,
uctrl->max, len, 0);
if (uret == len) {
uctrl->flags |= CTRL_HAS_MAX;
}
uret = libusb_control_transfer(tuvc->devh->usb_devh, 0xa1, UVC_GET_RES,
(uctrl->code << 8) & 0xFF00, index,
uctrl->res, len, 0);
if (uret == len) {
uctrl->flags |= CTRL_HAS_RES;
}
uret = libusb_control_transfer(tuvc->devh->usb_devh, 0xa1, UVC_GET_DEF,
(uctrl->code << 8) & 0xFF00, index,
uctrl->def, len, 0);
if (uret == len) {
uctrl->flags |= CTRL_HAS_DEF;
}
hPtr =
Tcl_CreateHashEntry(&tuvc->ctrl, (ClientData) uctrl->name, &isNew);
if (!isNew) {
UCTRL *oldctrl = (UCTRL *) Tcl_GetHashValue(hPtr);
ckfree((char *) oldctrl);
}
Tcl_SetHashValue(hPtr, (ClientData) uctrl);
}
/* format table: frame-size, frame-rate, etc. */
if (tuvc->devh->info != NULL) {
int k;
long index = 0;
uvc_streaming_interface_t *sif;
uvc_format_desc_t *fm;
uvc_frame_desc_t *fd;
char buffer[64];
/*
* First indices of table are uncompressed formats,
* then MJPEG formats (if any) follow.
*/
for (k = 0; k < 2; k++) {
for (sif = tuvc->devh->info->stream_ifs;
sif != NULL; sif = sif->next) {
for (fm = sif->format_descs; fm != NULL; fm = fm->next) {
if (k == 0 &&
fm->bDescriptorSubtype != UVC_VS_FORMAT_UNCOMPRESSED) {
continue;
} else if (k &&
fm->bDescriptorSubtype != UVC_VS_FORMAT_MJPEG) {
continue;
}
for (fd = fm->frame_descs; fd != NULL; fd = fd->next) {
int r;
ufmt = (UFMT *) ckalloc(sizeof(UFMT));
ufmt->width = fd->wWidth;
ufmt->height = fd->wHeight;
ufmt->bpp = k ? 24 : fm->bBitsPerPixel;
memcpy(&ufmt->fourcc, &fm->fourccFormat, 4);
memset(ufmt->fpsList, 0, sizeof(ufmt->fpsList));
Tcl_DStringInit(&ufmt->str);
Tcl_DStringAppendElement(&ufmt->str, "frame-size");
sprintf(buffer, "%dx%d", ufmt->width, ufmt->height);
Tcl_DStringAppendElement(&ufmt->str, buffer);
ufmt->fps = 10000000 / fd->dwDefaultFrameInterval;
Tcl_DStringAppendElement(&ufmt->str, "frame-rate");
sprintf(buffer, "%d", ufmt->fps);
Tcl_DStringAppendElement(&ufmt->str, buffer);
if (fd->intervals) {
uint32_t *ip = fd->intervals;
Tcl_DStringAppendElement(&ufmt->str,
"frame-rate-values");
Tcl_DStringStartSublist(&ufmt->str);
i = 0;
while (*ip) {
r = 10000000 / *ip;
ufmt->fpsList[i++] = r;
sprintf(buffer, "%d", r);
Tcl_DStringAppendElement(&ufmt->str, buffer);
++ip;
}
Tcl_DStringEndSublist(&ufmt->str);
} else {
Tcl_DStringAppendElement(&ufmt->str,
"frame-rate-min");
r = 10000000 / fd->dwMinFrameInterval;
sprintf(buffer, "%d", r);
Tcl_DStringAppendElement(&ufmt->str, buffer);
Tcl_DStringAppendElement(&ufmt->str,
"frame-rate-max");
r = 10000000 / fd->dwMaxFrameInterval;
sprintf(buffer, "%d", r);
Tcl_DStringAppendElement(&ufmt->str, buffer);
}
ufmt->iscomp = (k > 0);
Tcl_DStringAppendElement(&ufmt->str, "mjpeg");
Tcl_DStringAppendElement(&ufmt->str,
ufmt->iscomp ? "1" : "0");
hPtr = Tcl_CreateHashEntry(&tuvc->fmts,
(ClientData) index, &isNew);
if (!isNew) {
UFMT *uold = (UFMT *) Tcl_GetHashValue(hPtr);
if (uold == ufmt0) {
ufmt0 = ufmt;
}
Tcl_DStringFree(&uold->str);
ckfree((char *) uold);
}
Tcl_SetHashValue(hPtr, (ClientData) ufmt);
if (ufmt0 == NULL) {
ufmt0 = ufmt;
}
index++;
}
}
}
}
if (ufmt0 != NULL) {
tuvc->width = ufmt0->width;
tuvc->height = ufmt0->height;
tuvc->fps = ufmt0->fps;
tuvc->usefmt = 0;
tuvc->iscomp = ufmt0->iscomp;
}
}
}
/*
*-------------------------------------------------------------------------
*
* GetControls, PrintVal --
*
* Read out current values of device controls and return
* these as list made up of key value pairs. Added meta
* information entries to support user interface:
*
* <name>-values comma separated list of menu choices
* <name>-minimum minimum value for bool/integer
* <name>-maximum minimum value for bool/integer
* <name>-step interval step value for integer
* <name>-default default value for bool/integer
*
*-------------------------------------------------------------------------
*/
static void
GetControls(TUVC *tuvc, Tcl_Obj *list)
{
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
Tcl_DString ds;
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj("update-mode", -1));
#ifdef USE_ASYNC_HANDLER
Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj("0", 1));
#else
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(tuvc->idle ? "1" : "0", 1));
#endif
Tcl_DStringInit(&ds);
hPtr = Tcl_FirstHashEntry(&tuvc->ctrl, &search);
while (hPtr != NULL) {
UCTRL *uctrl = (UCTRL *) Tcl_GetHashValue(hPtr);
int index = -1;
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj((char *) uctrl->name, -1));
switch (uctrl->code & UVC_SELECTOR) {
case UVC_SELECTOR_CT:
index = (uvc_get_camera_terminal(tuvc->devh)->bTerminalID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
case UVC_SELECTOR_PU:
index = (uvc_get_processing_units(tuvc->devh)->bUnitID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
case UVC_SELECTOR_SU:
index = (uvc_get_selector_units(tuvc->devh)->bUnitID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
}
if (index != -1) {
int len = uctrl->type * uctrl->count;
libusb_control_transfer(tuvc->devh->usb_devh, 0xa1, UVC_GET_CUR,
(uctrl->code << 8) & 0xFF00, index,
uctrl->cur, len, 0);
}
PrintVal(uctrl, uctrl->cur, &ds, list);
if (uctrl->flags & CTRL_HAS_MIN) {
Tcl_DStringSetLength(&ds, 0);
Tcl_DStringAppend(&ds, uctrl->name, -1);
Tcl_DStringAppend(&ds, "-minimum", -1);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(Tcl_DStringValue(&ds),
Tcl_DStringLength(&ds)));
PrintVal(uctrl, uctrl->min, &ds, list);
}
if (uctrl->flags & CTRL_HAS_MAX) {
Tcl_DStringSetLength(&ds, 0);
Tcl_DStringAppend(&ds, uctrl->name, -1);
Tcl_DStringAppend(&ds, "-maximum", -1);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(Tcl_DStringValue(&ds),
Tcl_DStringLength(&ds)));
PrintVal(uctrl, uctrl->max, &ds, list);
}
if (uctrl->flags & CTRL_HAS_RES) {
Tcl_DStringSetLength(&ds, 0);
Tcl_DStringAppend(&ds, uctrl->name, -1);
Tcl_DStringAppend(&ds, "-step", -1);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(Tcl_DStringValue(&ds),
Tcl_DStringLength(&ds)));
PrintVal(uctrl, uctrl->res, &ds, list);
}
if (uctrl->flags & CTRL_HAS_DEF) {
Tcl_DStringSetLength(&ds, 0);
Tcl_DStringAppend(&ds, uctrl->name, -1);
Tcl_DStringAppend(&ds, "-default", -1);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(Tcl_DStringValue(&ds),
Tcl_DStringLength(&ds)));
PrintVal(uctrl, uctrl->def, &ds, list);
}
hPtr = Tcl_NextHashEntry(&search);
}
}
static void
PrintVal(UCTRL *uctrl, unsigned char *data, Tcl_DString *dsPtr, Tcl_Obj *list)
{
int i;
unsigned char *dp = data;
char buffer[64];
Tcl_DStringSetLength(dsPtr, 0);
for (i = 0; i < uctrl->count; i++) {
int v = 0;
switch (uctrl->type) {
case 1:
v = dp[0];
dp += uctrl->type;
break;
case 2:
v = dp[0] | (dp[1] << 8);
dp += uctrl->type;
break;
case 4:
v = dp[0] | (dp[1] << 8) | (dp[2] << 16) | (dp[3] << 24);
dp += uctrl->type;
break;
}
sprintf(buffer, "%s%d", (i == 0) ? "" : ",", v);
Tcl_DStringAppend(dsPtr, buffer, -1);
}
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(Tcl_DStringValue(dsPtr),
Tcl_DStringLength(dsPtr)));
}
/*
*-------------------------------------------------------------------------
*
* SetControls --
*
* Set device controls given list of key value pairs.
*
*-------------------------------------------------------------------------
*/
static int
SetControls(TUVC *tuvc, int objc, Tcl_Obj * const objv[])
{
Tcl_Interp *interp = tuvc->interp;
Tcl_HashEntry *hPtr;
int i, k, n;
for (i = 0; i < objc; i += 2) {
UCTRL *uctrl;
char *name, *valStr;
name = Tcl_GetString(objv[i]);
if (strcmp(name, "update-mode") == 0) {
#ifndef USE_ASYNC_HANDLER
int flag;
if (Tcl_GetBooleanFromObj(NULL, objv[i + 1], &flag) == TCL_OK) {
if (flag != tuvc->idle) {
Tcl_CancelIdleCall(FrameReady, (ClientData) tuvc);
Tcl_MutexLock(&uvcMutex);
tuvc->numev = 0;
tuvc->idle = flag;
Tcl_MutexUnlock(&uvcMutex);
}
}
#endif
continue;
}
hPtr = Tcl_FindHashEntry(&tuvc->ctrl, name);
if (hPtr == NULL) {
continue;
}
uctrl = (UCTRL *) Tcl_GetHashValue(hPtr);
valStr = Tcl_GetString(objv[i + 1]);
for (k = n = 0; k < uctrl->count; k++) {
long lv;
char *endPtr = NULL;
lv = strtol(valStr, &endPtr, 0);
switch (uctrl->type) {
case 1:
uctrl->cur[n++] = lv;
break;
case 2:
uctrl->cur[n++] = lv;
uctrl->cur[n++] = lv >> 8;
break;
case 4:
uctrl->cur[n++] = lv;
uctrl->cur[n++] = lv >> 8;
uctrl->cur[n++] = lv >> 16;
uctrl->cur[n++] = lv >> 24;
break;
}
if ((endPtr == NULL) || (*endPtr != ',')) {
break;
}
valStr = endPtr + 1;
}
k = -1;
switch (uctrl->code & UVC_SELECTOR) {
case UVC_SELECTOR_CT:
k = (uvc_get_camera_terminal(tuvc->devh)->bTerminalID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
case UVC_SELECTOR_PU:
k = (uvc_get_processing_units(tuvc->devh)->bUnitID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
case UVC_SELECTOR_SU:
k = (uvc_get_selector_units(tuvc->devh)->bUnitID << 8) |
tuvc->devh->info->ctrl_if.bInterfaceNumber;
break;
}
if (k != -1) {
uvc_error_t uret;
n = uctrl->type * uctrl->count;
uret = libusb_control_transfer(tuvc->devh->usb_devh, 0x21,
UVC_SET_CUR,
(uctrl->code << 8) & 0xFF00, k,
uctrl->cur, n, 0);
if (uret < 0) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error setting \"%s\": %s",
Tcl_GetString(objv[i]),
uvc_strerror(uret)));
return TCL_ERROR;
} else if (uret != n) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error setting \"%s\": short write",
Tcl_GetString(objv[i])));
return TCL_ERROR;
}
}
}
return TCL_OK;
}
/*
*-------------------------------------------------------------------------
*
* UvcObjCmdDeleted --
*
* Destructor of "uvc" Tcl command. Closes all open devices and
* releases all resources.
*
*-------------------------------------------------------------------------
*/
static void
UvcObjCmdDeleted(ClientData clientData)
{
TUVCI *tuvci = (TUVCI *) clientData;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
TUVC *tuvc;
hPtr = Tcl_FirstHashEntry(&tuvci->tuvcc, &search);
while (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
StopCapture(tuvc);
uvc_close(tuvc->devh);
tuvc->devh = NULL;
uvc_unref_device(tuvc->dev);
tuvc->dev = NULL;
uvc_exit(tuvc->ctx);
tuvc->ctx = NULL;
Tcl_DStringFree(&tuvc->devName);
Tcl_DStringFree(&tuvc->cbCmd);
FinishRecording(tuvc, 1, 1);
InitControls(tuvc);
ckfree((char *) tuvc);
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&tuvci->tuvcc);
if (tuvci->ctx != NULL) {
uvc_exit(tuvci->ctx);
}
#ifdef HAVE_LIBUDEV
tuvci->interp = NULL;
Tcl_DStringFree(&tuvci->cbCmd);
hPtr = Tcl_FirstHashEntry(&tuvci->devs, &search);
while (hPtr != NULL) {
Tcl_DStringFree((Tcl_DString *) Tcl_GetHashValue(hPtr));
ckfree((char *) Tcl_GetHashValue(hPtr));
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_DeleteHashTable(&tuvci->devs);
if (tuvci->udevMon != NULL) {
Tcl_DeleteFileHandler(udev_monitor_get_fd(tuvci->udevMon));
udev_monitor_unref(tuvci->udevMon);
tuvci->udevMon = NULL;
}
if (tuvci->udev != NULL) {
udev_unref(tuvci->udev);
tuvci->udev = NULL;
}
#endif
Tcl_FreeEncoding(tuvci->enc);
ckfree((char *) tuvci);
}
/*
*-------------------------------------------------------------------------
*
* UvcObjCmd --
*
* "uvc" Tcl command dealing with libuvc devices.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*-------------------------------------------------------------------------
*/
static int
UvcObjCmd(ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj * const objv[])
{
TUVCI *tuvci = (TUVCI *) clientData;
TUVC *tuvc;
Tcl_HashEntry *hPtr;
int ret = TCL_OK, command;
uvc_error_t uret;
static const char *cmdNames[] = {
"close", "convmode", "counters", "devices",
"format", "greyshift", "image", "info", "listen",
"listformats", "mbcopy", "mcopy", "mirror", "open",
"orientation", "parameters", "record", "start",
"state", "stop", "tophoto", NULL
};
enum cmdCode {
CMD_close, CMD_convmode, CMD_counters, CMD_devices,
CMD_format, CMD_greyshift, CMD_image, CMD_info, CMD_listen,
CMD_listformats, CMD_mbcopy, CMD_mcopy, CMD_mirror, CMD_open,
CMD_orientation, CMD_parameters, CMD_record, CMD_start,
CMD_state, CMD_stop, CMD_tophoto
};
static const char *recNames[] = {
"frame", "pause", "resume", "start", "state", "stop", NULL
};
enum recCode {
REC_frame, REC_pause, REC_resume, REC_start, REC_state, REC_stop
};
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "option ...");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObj(interp, objv[1], cmdNames, "option", 0,
&command) != TCL_OK) {
return TCL_ERROR;
}
switch ((enum cmdCode) command) {
case CMD_close:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "devid");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
Tcl_DeleteHashEntry(hPtr);
StopCapture(tuvc);
uvc_close(tuvc->devh);
tuvc->devh = NULL;
uvc_unref_device(tuvc->dev);
tuvc->dev = NULL;
uvc_exit(tuvc->ctx);
tuvc->ctx = NULL;
Tcl_DStringFree(&tuvc->devName);
Tcl_DStringFree(&tuvc->cbCmd);
FinishRecording(tuvc, 1, 1);
InitControls(tuvc);
ckfree((char *) tuvc);
} else {
devNotFound:
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("device \"%s\" not found",
Tcl_GetString(objv[2])));
ret = TCL_ERROR;
}
break;
case CMD_convmode:
if (objc != 3 && objc != 4) {
Tcl_WrongNumArgs(interp, 2, objv, "devid ?flag?");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
if (objc > 3) {
int conv;
if (Tcl_GetBooleanFromObj(interp, objv[3], &conv) != TCL_OK) {
return TCL_ERROR;
}
if (tuvc->conv != conv) {
FinishRecording(tuvc, 1, 0);
}
tuvc->conv = conv;
} else {
Tcl_SetBooleanObj(Tcl_GetObjResult(interp), tuvc->conv);
}
} else {
goto devNotFound;
}
break;
case CMD_counters:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "devid");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
Tcl_Obj *r[3];
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
r[0] = Tcl_NewWideIntObj(tuvc->counters[0]);
r[1] = Tcl_NewWideIntObj(tuvc->counters[1]);
r[2] = Tcl_NewWideIntObj(tuvc->counters[2]);
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;
}
if (tuvci->ctx == NULL) {
Tcl_SetResult(interp, "libuvc not initialized", TCL_STATIC);
return TCL_ERROR;
#ifdef HAVE_LIBUDEV
} else if (tuvci->udevMon != NULL) {
Tcl_Obj *list = Tcl_NewListObj(0, NULL);
Tcl_HashSearch search;
if (tuvci->devsNeedRefresh) {
UdevScan(tuvci, NULL);
}
hPtr = Tcl_FirstHashEntry(&tuvci->devs, &search);
while (hPtr != NULL) {
char *p = (char *) Tcl_GetHashKey(&tuvci->devs, hPtr);
Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(p, -1));
p = Tcl_DStringValue((Tcl_DString *) Tcl_GetHashValue(hPtr));
Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(p, -1));
p += strlen(p) + 1;
Tcl_ListObjAppendElement(NULL, list, Tcl_NewStringObj(p, -1));
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_SetObjResult(interp, list);
#endif
} else {
uvc_device_t **devlist = NULL;
int i;
Tcl_Obj *list;
uret = uvc_get_device_list(tuvci->ctx, &devlist);
if (uret < 0) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error getting devices: %s",
uvc_strerror(uret)));
return TCL_ERROR;
}
list = Tcl_NewListObj(0, NULL);
for (i = 0; (devlist != NULL) && (devlist[i] != NULL); i++) {
uvc_device_descriptor_t *desc = NULL;
char *p, buffer[128];
Tcl_DString ds;
if (uvc_get_device_descriptor(devlist[i], &desc) < 0) {
continue;
}
Tcl_DStringInit(&ds);
sprintf(buffer, "%04X:%04X:%d.%d",
desc->idVendor, desc->idProduct,
uvc_get_bus_number(devlist[i]),
uvc_get_device_address(devlist[i]));
Tcl_DStringAppend(&ds, buffer, -1);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(Tcl_DStringValue(&ds),
Tcl_DStringLength(&ds)));
Tcl_DStringFree(&ds);
if (desc->manufacturer != NULL) {
p = Tcl_ExternalToUtfDString(tuvci->enc,
desc->manufacturer, -1, &ds);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(p, Tcl_DStringLength(&ds)));
Tcl_DStringFree(&ds);
} else {
Tcl_ListObjAppendElement(NULL, list, Tcl_NewObj());
}
if (desc->product != NULL) {
p = Tcl_ExternalToUtfDString(tuvci->enc,
desc->product, -1, &ds);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(p, Tcl_DStringLength(&ds)));
Tcl_DStringFree(&ds);
} else {
Tcl_ListObjAppendElement(NULL, list, Tcl_NewObj());
}
uvc_free_device_descriptor(desc);
}
uvc_free_device_list(devlist, 1);
Tcl_SetObjResult(interp, list);
}
break;
case CMD_format:
if ((objc < 3) || (objc > 5)) {
Tcl_WrongNumArgs(interp, 2, objv, "devid ?fmt ?fps??");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr == NULL) {
goto devNotFound;
}
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
if (objc > 3) {
UFMT *ufmt;
int k, fps = 0;
long lk;
if (Tcl_GetIntFromObj(interp, objv[3], &k) != TCL_OK) {
return TCL_ERROR;
}
if ((objc > 4) &&
(Tcl_GetIntFromObj(interp, objv[4], &fps) != TCL_OK)) {
return TCL_ERROR;
}
lk = k;
hPtr = Tcl_FindHashEntry(&tuvc->fmts, (ClientData) lk);
if (hPtr == NULL) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("format %d not found", k));
return TCL_ERROR;
}
if (tuvc->running) {
Tcl_SetResult(interp, "capture still running", TCL_STATIC);
return TCL_ERROR;
}
/* Stop recording due to format change. */
if (tuvc->rstate > REC_STOP) {
tuvc->rstate = REC_STOP;
}
FinishRecording(tuvc, 1, 0);
/* Set new format. */
ufmt = (UFMT *) Tcl_GetHashValue(hPtr);
tuvc->width = ufmt->width;;
tuvc->height = ufmt->height;
tuvc->usefmt = k;
tuvc->fps = ufmt->fps;
tuvc->iscomp = ufmt->iscomp;
if ((fps > 0) && (ufmt->fpsList[0] > 0)) {
k = 0;
while (ufmt->fpsList[k] > fps) {
k++;
}
if (ufmt->fpsList[k] > 0) {
tuvc->fps = ufmt->fpsList[k];
} else if (--k >= 0) {
tuvc->fps = ufmt->fpsList[k];
}
}
} else {
Tcl_Obj *list[2];
list[0] = Tcl_NewIntObj(tuvc->usefmt);
list[1] = Tcl_NewIntObj(tuvc->fps);
Tcl_SetObjResult(interp, Tcl_NewListObj(2, list));
}
break;
case CMD_greyshift:
if (objc != 3 && objc != 4) {
Tcl_WrongNumArgs(interp, 2, objv, "devid ?shift?");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
if (objc > 3) {
int shift;
if (Tcl_GetIntFromObj(interp, objv[3], &shift) != TCL_OK) {
return TCL_ERROR;
}
tuvc->greyshift = shift;
} else {
Tcl_SetIntObj(Tcl_GetObjResult(interp), tuvc->greyshift);
}
} else {
goto devNotFound;
}
break;
case CMD_image:
if ((objc < 3) || (objc > 4)) {
Tcl_WrongNumArgs(interp, 2, objv, "devid ?photoImage?");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
ret = GetImage(tuvci, tuvc, (objc > 3) ? objv[3] : NULL);
} else {
goto devNotFound;
}
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(&tuvci->tuvcc, &search);
while (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
Tcl_ListObjAppendElement(NULL, list,
Tcl_NewStringObj(tuvc->devId, -1));
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_SetObjResult(interp, list);
} else {
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
Tcl_Obj *r[2];
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
r[0] = Tcl_NewStringObj(Tcl_DStringValue(&tuvc->devName), -1);
Tcl_DStringSetLength(&tuvc->cbCmd, tuvc->cbCmdLen);
r[1] = Tcl_NewStringObj(Tcl_DStringValue(&tuvc->cbCmd), -1);
Tcl_SetObjResult(interp, Tcl_NewListObj(2, r));
} else {
goto devNotFound;
}
}
break;
case CMD_listen:
if (objc > 3) {
Tcl_WrongNumArgs(interp, 2, objv, "?cmd?");
return TCL_ERROR;
}
#ifdef HAVE_LIBUDEV
if (tuvci->udevMon != NULL) {
if (objc == 2) {
Tcl_DStringSetLength(&tuvci->cbCmd, tuvci->cbCmdLen);
Tcl_SetObjResult(interp,
Tcl_NewStringObj(Tcl_DStringValue(&tuvci->cbCmd),
Tcl_DStringLength(&tuvci->cbCmd)));
} else {
Tcl_DStringSetLength(&tuvci->cbCmd, 0);
Tcl_DStringAppend(&tuvci->cbCmd, Tcl_GetString(objv[2]), -1);
tuvci->cbCmdLen = Tcl_DStringLength(&tuvci->cbCmd);
}
}
#endif
break;
case CMD_listformats:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "devid");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr == NULL) {
goto devNotFound;
} else {
Tcl_HashSearch search;
Tcl_Obj *dict;
UFMT *ufmt;
long lk;
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
dict = Tcl_NewDictObj();
hPtr = Tcl_FirstHashEntry(&tuvc->fmts, &search);
while (hPtr != NULL) {
ufmt = (UFMT *) Tcl_GetHashValue(hPtr);
lk = (long) Tcl_GetHashKey(&tuvc->fmts, hPtr);
Tcl_DictObjPut(NULL, dict, Tcl_NewIntObj((int) lk),
Tcl_NewStringObj(Tcl_DStringValue(&ufmt->str),
Tcl_DStringLength(&ufmt->str)));
hPtr = Tcl_NextHashEntry(&search);
}
Tcl_SetObjResult(interp, dict);
}
break;
case CMD_mbcopy: {
int mask0, mask, i, srcLen, dstLen;
unsigned char *src, *dst;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "bytearray1 bytearray2 mask");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[4], &mask0) != TCL_OK) {
return TCL_ERROR;
}
dst = Tcl_GetByteArrayFromObj(objv[2], &dstLen);
src = Tcl_GetByteArrayFromObj(objv[3], &srcLen);
if ((srcLen != dstLen) || (srcLen % 3)) {
Tcl_SetResult(interp, "incompatible bytearrays", TCL_STATIC);
return TCL_ERROR;
}
mask = (mask0 >> 16) & 0xff; /* red */
if (mask != 0) {
for (i = 0; i < srcLen; i += 3) {
dst[i] = (dst[i] & (~mask)) | (src[i] & mask);
}
}
mask = (mask0 >> 8) & 0xff; /* green */
if (mask != 0) {
for (i = 1; i < srcLen; i += 3) {
dst[i] = (dst[i] & (~mask)) | (src[i] & mask);
}
}
mask = mask0 & 0xff; /* blue */
if (mask != 0) {
for (i = 2; i < srcLen; i += 3) {
dst[i] = (dst[i] & (~mask)) | (src[i] & mask);
}
}
break;
}
case CMD_mcopy: {
char *name;
Tk_PhotoHandle ph1, ph2;
int mask0, mask, nops = 0, x, y;
Tk_PhotoImageBlock block1, block2;
unsigned char *src, *dst;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "photo1 photo2 mask");
return TCL_ERROR;
}
if (CheckForTk(tuvci, interp) != TCL_OK) {
return TCL_ERROR;
}
name = Tcl_GetString(objv[2]);
ph1 = Tk_FindPhoto(interp, name);
if (ph1 == NULL) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("can't use \"%s\": not a photo image", name));
return TCL_ERROR;
}
name = Tcl_GetString(objv[3]);
ph2 = Tk_FindPhoto(interp, name);
if (ph2 == NULL) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("can't use \"%s\": not a photo image", name));
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[4], &mask0) != TCL_OK) {
return TCL_ERROR;
}
Tk_PhotoGetImage(ph1, &block1);
Tk_PhotoGetImage(ph2, &block2);
if ((block1.width != block2.width) ||
(block1.height != block2.height) ||
(block1.pixelSize != block2.pixelSize) ||
(block1.pixelSize != 4)) {
Tcl_SetResult(interp, "incompatible photo images", TCL_STATIC);
return TCL_ERROR;
}
mask = (mask0 >> 24) & 0xff; /* alpha */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[3];
src += block2.offset[3];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
mask = (mask0 >> 16) & 0xff; /* red */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[0];
src += block2.offset[0];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
mask = (mask0 >> 8) & 0xff; /* green */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[1];
src += block2.offset[1];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
mask = mask0 & 0xff; /* blue */
if (mask != 0) {
for (y = 0; y < block1.height; y++) {
dst = block1.pixelPtr + y * block1.pitch;
src = block2.pixelPtr + y * block2.pitch;
dst += block1.offset[2];
src += block2.offset[2];
for (x = 0; x < block1.width; x++) {
*dst = (*dst & (~mask)) | (*src & mask);
dst += block1.pixelSize;
src += block2.pixelSize;
}
}
++nops;
}
if (nops) {
ret = Tk_PhotoPutBlock(interp, ph1, &block1, 0, 0,
block1.width, block1.height,
TK_PHOTO_COMPOSITE_SET);
}
break;
}
case CMD_mirror: {
int x, y;
if ((objc != 3) && (objc != 5)) {
Tcl_WrongNumArgs(interp, 2, objv, "devid ?x y?");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr == NULL) {
goto devNotFound;
}
tuvc = (TUVC *) 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) {
tuvc->mirror = (x ? 1 : 0) | (y ? 2 : 0);
} else {
Tcl_Obj *list[2];
list[0] = Tcl_NewBooleanObj(tuvc->mirror & 1);
list[1] = Tcl_NewBooleanObj(tuvc->mirror & 2);
Tcl_SetObjResult(interp, Tcl_NewListObj(2, list));
}
break;
}
case CMD_open: {
char *devName, *p;
uvc_context_t *ctx;
uvc_device_t *dev;
uvc_device_descriptor_t *desc;
uvc_device_handle_t *devh;
int vid = 0, pid = 0, isNew;
int bd[2], *bdp = NULL;
if (objc != 4) {
Tcl_WrongNumArgs(interp, 2, objv, "device callback");
return TCL_ERROR;
}
uvc_init(&ctx, NULL);
if (ctx == NULL) {
Tcl_SetResult(interp, "libuvc not initialized", TCL_STATIC);
return TCL_ERROR;
}
devName = Tcl_GetString(objv[2]);
sscanf(devName, "%x:%x", &vid, &pid);
p = strchr(devName, ':');
if (p != NULL) {
p = strchr(p + 1, ':');
if (p != NULL) {
sscanf(p + 1, "%d.%d", bd, bd + 1);
bdp = bd;
}
}
uret = uvc_find_device_bd(ctx, &dev, vid, pid, bdp);
if (uret < 0) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error while searching \"%s\": %s",
devName, uvc_strerror(uret)));
uvc_exit(ctx);
return TCL_ERROR;
}
if (uvc_get_device_descriptor(dev, &desc) < 0) {
uvc_unref_device(dev);
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error while getting descriptor for \"%s\": %s",
devName, uvc_strerror(uret)));
uvc_exit(ctx);
return TCL_ERROR;
}
uret = uvc_open(dev, &devh);
if (uret < 0) {
uvc_free_device_descriptor(desc);
uvc_unref_device(dev);
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error while opening \"%s\": %s",
devName, uvc_strerror(uret)));
uvc_exit(ctx);
return TCL_ERROR;
}
tuvc = (TUVC *) ckalloc(sizeof(TUVC));
memset(tuvc, 0, sizeof(TUVC));
tuvc->ctx = ctx;
tuvc->dev = dev;
tuvc->devh = devh;
tuvc->mirror = 0;
tuvc->rotate = 0;
tuvc->width = 640;
tuvc->height = 480;
tuvc->conv = 1;
tuvc->greyshift = 4; /* preset for 12 bit sensors */
tuvc->fps = 30;
tuvc->interp = interp;
#ifdef USE_ASYNC_HANDLER
tuvc->async = NULL;
#else
tuvc->tid = NULL;
tuvc->numev = 0;
tuvc->idle = 0;
#endif
tuvc->running = 0;
Tcl_DStringInit(&tuvc->devName);
Tcl_DStringSetLength(&tuvc->devName, 128);
p = Tcl_DStringValue(&tuvc->devName);
sprintf(p, "%04X:%04X:%d.%d", desc->idVendor, desc->idProduct,
uvc_get_bus_number(dev),
uvc_get_device_address(dev));
Tcl_DStringSetLength(&tuvc->devName, strlen(p));
Tcl_DStringInit(&tuvc->cbCmd);
Tcl_DStringAppend(&tuvc->cbCmd, Tcl_GetString(objv[3]), -1);
tuvc->cbCmdLen = Tcl_DStringLength(&tuvc->cbCmd);
sprintf(tuvc->devId, "uvc%d", tuvci->idCount++);
Tcl_InitHashTable(&tuvc->ctrl, TCL_STRING_KEYS);
Tcl_InitHashTable(&tuvc->fmts, TCL_ONE_WORD_KEYS);
hPtr = Tcl_CreateHashEntry(&tuvci->tuvcc, tuvc->devId, &isNew);
Tcl_SetHashValue(hPtr, (ClientData) tuvc);
Tcl_SetObjResult(interp, Tcl_NewStringObj(tuvc->devId, -1));
uvc_free_device_descriptor(desc);
InitControls(tuvc);
tuvc->rstate = REC_STOP;
tuvc->rchan = NULL;
Tcl_DStringInit(&tuvc->rbdStr);
Tcl_MutexLock(&tuvc->rmutex);
Tcl_MutexUnlock(&tuvc->rmutex);
break;
}
case CMD_orientation: {
if (objc > 4) {
Tcl_WrongNumArgs(interp, 2, objv, "devid ?degrees?");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr == NULL) {
goto devNotFound;
}
tuvc = (TUVC *) 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) {
tuvc->rotate = 0;
} else if (degrees < 135) {
tuvc->rotate = 90;
} else if (degrees < 225) {
tuvc->rotate = 180;
} else if (degrees < 315) {
tuvc->rotate = 270;
} else {
tuvc->rotate = 0;
}
} else {
Tcl_SetObjResult(interp, Tcl_NewIntObj(tuvc->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(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
if (objc == 3) {
Tcl_Obj *list = Tcl_NewListObj(0, NULL);
GetControls(tuvc, list);
Tcl_SetObjResult(interp, list);
} else {
ret = SetControls(tuvc, objc - 3, objv + 3);
if (ret == TCL_OK) {
Tcl_Obj *list = Tcl_NewListObj(0, NULL);
GetControls(tuvc, list);
Tcl_SetObjResult(interp, list);
}
}
} else {
goto devNotFound;
}
break;
case CMD_record:
if (objc < 4) {
Tcl_WrongNumArgs(interp, 2, objv, "devid cmd ...");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) 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(tuvc, 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 (tuvc->rstate == REC_RECPRI) {
tuvc->rstate = REC_PAUSEPRI;
} else if (tuvc->rstate == REC_RECORD) {
tuvc->rstate = REC_PAUSE;
} else if ((tuvc->rstate != REC_PAUSEPRI) &&
(tuvc->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 (tuvc->rstate == REC_PAUSEPRI) {
if (tuvc->running) {
gettimeofday(&tuvc->ltv, NULL);
tuvc->rtv = tuvc->ltv;
tuvc->rstate = REC_RECPRI;
}
} else if (tuvc->rstate == REC_PAUSE) {
if (tuvc->running) {
gettimeofday(&tuvc->ltv, NULL);
tuvc->rtv = tuvc->ltv;
tuvc->rstate = REC_RECORD;
}
} else if ((tuvc->rstate != REC_RECPRI) &&
(tuvc->rstate != REC_RECORD)) {
Tcl_SetResult(interp, "wrong recording state for resume",
TCL_STATIC);
return TCL_ERROR;
}
break;
case REC_start:
if (StartRecording(tuvc, 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 (tuvc->rstate) {
default:
case REC_STOP:
Tcl_SetResult(interp, "stop", TCL_STATIC);
break;
case REC_RECPRI:
case REC_RECORD:
Tcl_SetResult(interp, "recording", TCL_STATIC);
break;
case REC_PAUSEPRI:
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 (tuvc->rstate > REC_STOP) {
tuvc->rstate = REC_STOP;
}
FinishRecording(tuvc, 1, 0);
break;
}
break;
case CMD_start:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "devid");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
ret = StartCapture(tuvc);
} else {
goto devNotFound;
}
break;
case CMD_state:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "devid");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
Tcl_SetResult(interp, (tuvc->running < 0) ? "error" :
(tuvc->running ? "capture" : "stopped"),
TCL_STATIC);
} else {
goto devNotFound;
}
break;
case CMD_stop:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "devid");
return TCL_ERROR;
}
hPtr = Tcl_FindHashEntry(&tuvci->tuvcc, Tcl_GetString(objv[2]));
if (hPtr != NULL) {
tuvc = (TUVC *) Tcl_GetHashValue(hPtr);
ret = StopCapture(tuvc);
} else {
goto devNotFound;
}
break;
case CMD_tophoto:
if (DataToPhoto(tuvci, interp, objc, objv) != TCL_OK) {
return TCL_ERROR;
}
break;
}
return ret;
}
/*
*-------------------------------------------------------------------------
*
* Tcluvc_Init --
*
* Module initializer:
* - require Tcl/Tk infrastructure
* - dynamic link libusb
* - (optional) setup udev for plug/unplug events
* - initialize module data structures
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*-------------------------------------------------------------------------
*/
int
Tcluvc_Init(Tcl_Interp *interp)
{
uvc_context_t *ctx = NULL;
uvc_error_t uret;
TUVCI *tuvci;
#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 (!uvcInitialized) {
#if defined(ANDROID) && !defined(__TERMUX__)
Tcl_DString ds;
const char *path;
#endif
int major = 0, minor = 0;
const char *val;
Tcl_MutexLock(&uvcMutex);
if (uvcInitialized) {
Tcl_MutexUnlock(&uvcMutex);
goto doInit;
}
if ((Tcl_EvalEx(interp, "::tcl::pkgconfig get threaded", -1, 0)
!= TCL_OK) ||
(Tcl_GetStringResult(interp)[0] != '1')) {
uvcInitialized = -1;
goto doInit;
}
Tcl_ResetResult(interp);
Tcl_GetVersion(&major, &minor, NULL, NULL);
if ((major > 8) || ((major == 8) && (minor > 6))) {
tip609 = 1;
} else {
val = Tcl_GetVar2(interp, "tcl_platform", "tip609",
TCL_GLOBAL_ONLY);
if ((val != NULL) && *val && (*val != '0')) {
tip609 = 1;
}
}
/* dynamic link libusb */
(void) dlerror();
#if defined(ANDROID) && !defined(__TERMUX__)
Tcl_DStringInit(&ds);
path = getenv("INTERNAL_STORAGE");
if (path != NULL) {
char *end;
Tcl_DStringAppend(&ds, path, -1);
end = strrchr(Tcl_DStringValue(&ds), '/');
if (end == NULL) {
Tcl_DStringAppend(&ds, "/../lib/", -1);
} else {
Tcl_DStringSetLength(&ds, end - Tcl_DStringValue(&ds));
Tcl_DStringAppend(&ds, "/lib/", -1);
}
}
Tcl_DStringAppend(&ds, LIBUSB_SO, -1);
libusb = dlopen(Tcl_DStringValue(&ds), RTLD_NOW | RTLD_GLOBAL);
Tcl_DStringFree(&ds);
if (libusb == NULL) {
(void) dlerror();
libusb = dlopen(LIBUSB_SO, RTLD_NOW | RTLD_GLOBAL);
}
#else
libusb = dlopen(LIBUSB_SO, RTLD_NOW);
#endif
if (libusb == NULL) {
libusbError:
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("unable to link " LIBUSB_SO ": %s",
dlerror()));
if (libusb != NULL) {
dlclose(libusb);
libusb = NULL;
}
Tcl_MutexUnlock(&uvcMutex);
return TCL_ERROR;
}
#define USBDLSYM(name) \
libusb_dl.name = (fn_libusb_ ## name) dlsym(libusb, "libusb_" #name); \
if (libusb_dl.name == NULL) goto libusbError
#define USBDLSYM_NO_CHECK(name) \
libusb_dl.name = (fn_libusb_ ## name) dlsym(libusb, "libusb_" #name);
USBDLSYM(alloc_transfer);
USBDLSYM(attach_kernel_driver);
USBDLSYM(cancel_transfer);
USBDLSYM(claim_interface);
USBDLSYM(close);
USBDLSYM(control_transfer);
USBDLSYM(detach_kernel_driver);
USBDLSYM(exit);
USBDLSYM(free_config_descriptor);
USBDLSYM(free_device_list);
USBDLSYM(free_transfer);
USBDLSYM(get_bus_number);
USBDLSYM(get_config_descriptor);
USBDLSYM(get_device_address);
USBDLSYM(get_device_descriptor);
USBDLSYM(get_device_list);
USBDLSYM(get_string_descriptor_ascii);
USBDLSYM(handle_events);
USBDLSYM(init);
USBDLSYM(open);
USBDLSYM(ref_device);
USBDLSYM(release_interface);
USBDLSYM(set_interface_alt_setting);
USBDLSYM(submit_transfer);
USBDLSYM(unref_device);
USBDLSYM(clear_halt);
#ifdef __TERMUX__
USBDLSYM(wrap_sys_device);
#endif
USBDLSYM_NO_CHECK(handle_events_completed);
#undef USBDLSYM
#ifdef HAVE_LIBUDEV
/* dynamic link libudev */
libudev = dlopen("libudev.so.1", RTLD_NOW);
if (libudev == NULL) {
libudev = dlopen("libudev.so.0", RTLD_NOW);
if (libudev == NULL) {
goto libudevEnd;
libudevError:
dlclose(libudev);
libudev = NULL;
goto libudevEnd;
}
}
#define UDEVDLSYM(name) \
udev_dl.name = (fn_ ## name) dlsym(libudev, "udev_" #name); \
if (udev_dl.name == NULL) goto libudevError
UDEVDLSYM(device_get_action);
UDEVDLSYM(device_get_devnode);
UDEVDLSYM(device_get_property_value);
UDEVDLSYM(device_get_sysattr_value);
UDEVDLSYM(device_new_from_syspath);
UDEVDLSYM(device_unref);
UDEVDLSYM(monitor_get_fd);
UDEVDLSYM(monitor_receive_device);
UDEVDLSYM(monitor_unref);
UDEVDLSYM(new);
UDEVDLSYM(unref);
UDEVDLSYM(monitor_enable_receiving);
UDEVDLSYM(monitor_filter_add_match_subsystem_devtype);
UDEVDLSYM(monitor_new_from_netlink);
UDEVDLSYM(enumerate_new);
UDEVDLSYM(enumerate_add_match_subsystem);
UDEVDLSYM(enumerate_get_list_entry);
UDEVDLSYM(enumerate_scan_devices);
UDEVDLSYM(enumerate_unref);
UDEVDLSYM(list_entry_get_name);
UDEVDLSYM(list_entry_get_next);
#undef UDEVDLSYM
libudevEnd:
;
#endif /* HAVE_LIBUDEV */
uvcInitialized = 1;
Tcl_MutexUnlock(&uvcMutex);
}
doInit:
if (uvcInitialized < 0) {
Tcl_SetResult(interp, "thread support unavailable", TCL_STATIC);
return TCL_ERROR;
}
uret = uvc_init(&ctx, NULL);
if (uret < 0) {
Tcl_SetObjResult(interp,
Tcl_ObjPrintf("error initializing libuvc: %s (%d)",
uvc_strerror(uret), uret));
return TCL_ERROR;
}
if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION) != TCL_OK) {
uvc_exit(ctx);
return TCL_ERROR;
}
tuvci = (TUVCI *) ckalloc(sizeof(TUVCI));
memset(tuvci, 0, sizeof(TUVCI));
tuvci->idCount = 0;
tuvci->checkedTk = 0;
tuvci->ctx = ctx;
tuvci->enc = Tcl_GetEncoding(NULL, "utf-8");
Tcl_InitHashTable(&tuvci->tuvcc, TCL_STRING_KEYS);
#ifdef HAVE_LIBUDEV
/* setup udev */
tuvci->interp = interp;
Tcl_InitHashTable(&tuvci->devs, TCL_STRING_KEYS);
Tcl_DStringInit(&tuvci->cbCmd);
tuvci->cbCmdLen = 0;
tuvci->udev = (libudev == NULL) ? NULL : udev_new();
if (tuvci->udev != NULL) {
tuvci->udevMon = udev_monitor_new_from_netlink(tuvci->udev, "udev");
if (tuvci->udevMon == NULL) {
udev_unref(tuvci->udev);
tuvci->udev = NULL;
}
}
if (tuvci->udevMon != NULL) {
struct udev_enumerate *udevEnum;
/* watch "usb" subsystem */
udev_monitor_filter_add_match_subsystem_devtype(tuvci->udevMon,
"usb", NULL);
udev_monitor_enable_receiving(tuvci->udevMon);
Tcl_CreateFileHandler(udev_monitor_get_fd(tuvci->udevMon),
TCL_READABLE, UdevMonitor, (ClientData) tuvci);
/* initial device scan */
udevEnum = udev_enumerate_new(tuvci->udev);
if (udevEnum == NULL) {
/* trouble ahead... */
Tcl_DeleteFileHandler(udev_monitor_get_fd(tuvci->udevMon));
udev_monitor_unref(tuvci->udevMon);
tuvci->udevMon = NULL;
udev_unref(tuvci->udev);
tuvci->udev = NULL;
} else {
UdevScan(tuvci, udevEnum);
udev_enumerate_unref(udevEnum);
}
}
#endif /* HAVE_LIBUDEV */
Tcl_CreateObjCommand(interp, "uvc", UvcObjCmd,
(ClientData) tuvci, UvcObjCmdDeleted);
return TCL_OK;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* tab-width: 8
* End:
*/