931 lines
32 KiB
C
931 lines
32 KiB
C
/*******************************************************************************
|
|
# Linux-UVC streaming input-plugin for MJPG-streamer #
|
|
# #
|
|
# This package work with the Logitech UVC based webcams with the mjpeg feature #
|
|
# #
|
|
# Copyright (C) 2005 2006 Laurent Pinchart && Michel Xhaard #
|
|
# 2007 Lucas van Staden #
|
|
# 2007 Tom Stöveken #
|
|
# #
|
|
# This program is free software; you can redistribute it and/or modify #
|
|
# it under the terms of the GNU General Public License as published by #
|
|
# the Free Software Foundation; either version 2 of the License, or #
|
|
# (at your option) any later version. #
|
|
# #
|
|
# This program is distributed in the hope that it will be useful, #
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
|
# GNU General Public License for more details. #
|
|
# #
|
|
# You should have received a copy of the GNU General Public License #
|
|
# along with this program; if not, write to the Free Software #
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
|
# #
|
|
*******************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/time.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <getopt.h>
|
|
#include <pthread.h>
|
|
#include <syslog.h>
|
|
|
|
#include <linux/types.h> /* for videodev2.h */
|
|
#include <linux/videodev2.h>
|
|
|
|
#include "../../utils.h"
|
|
#include "v4l2uvc.h" // this header will includes the ../../mjpg_streamer.h
|
|
|
|
#ifndef NO_LIBJPEG
|
|
#include "jpeg_utils.h"
|
|
#include "huffman.h"
|
|
#endif
|
|
|
|
#include "dynctrl.h"
|
|
|
|
//#include "uvcvideo.h"
|
|
|
|
#define INPUT_PLUGIN_NAME "UVC webcam grabber"
|
|
|
|
static const struct {
|
|
const char *string;
|
|
const v4l2_std_id vstd;
|
|
} norms[] = {
|
|
{ "UNKNOWN", V4L2_STD_UNKNOWN },
|
|
{ "PAL", V4L2_STD_PAL },
|
|
{ "NTSC", V4L2_STD_NTSC },
|
|
{ "SECAM", V4L2_STD_SECAM }
|
|
};
|
|
|
|
/* private functions and variables to this plugin */
|
|
static globals *pglobal;
|
|
static unsigned int minimum_size = 0;
|
|
static int dynctrls = 1;
|
|
static unsigned int every = 1;
|
|
static int wantTimestamp = 0;
|
|
static struct timeval timestamp;
|
|
static int softfps = -1;
|
|
static unsigned int timeout = 5;
|
|
static unsigned int dv_timings = 0;
|
|
|
|
static const struct {
|
|
const char * k;
|
|
const int v;
|
|
} exposures[] = {
|
|
{ "auto", V4L2_EXPOSURE_AUTO },
|
|
{ "shutter-priority", V4L2_EXPOSURE_SHUTTER_PRIORITY },
|
|
{ "aperature-priority", V4L2_EXPOSURE_APERTURE_PRIORITY }
|
|
};
|
|
|
|
static const struct {
|
|
const char * k;
|
|
const int v;
|
|
} power_line[] = {
|
|
{ "disabled", V4L2_CID_POWER_LINE_FREQUENCY_DISABLED },
|
|
{ "50hz", V4L2_CID_POWER_LINE_FREQUENCY_50HZ },
|
|
{ "60hz", V4L2_CID_POWER_LINE_FREQUENCY_60HZ },
|
|
{ "auto", V4L2_CID_POWER_LINE_FREQUENCY_AUTO }
|
|
};
|
|
|
|
void *cam_thread(void *);
|
|
void cam_cleanup(void *);
|
|
void help(void);
|
|
int input_cmd(int plugin, unsigned int control, unsigned int group, int value, char *value_string);
|
|
|
|
const char *get_name_by_tvnorm(v4l2_std_id vstd) {
|
|
int i;
|
|
for (i=0;i<sizeof(norms)/sizeof(norms[0]);i++) {
|
|
if (vstd == norms[i].vstd) {
|
|
return norms[i].string;
|
|
}
|
|
}
|
|
return norms[0].string;
|
|
}
|
|
|
|
static context_settings * init_settings() {
|
|
context_settings *settings;
|
|
|
|
settings = calloc(1, sizeof(context_settings));
|
|
if (settings == NULL) {
|
|
IPRINT("error allocating context");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
settings->quality = 80;
|
|
return settings;
|
|
}
|
|
|
|
|
|
/*** plugin interface functions ***/
|
|
/******************************************************************************
|
|
Description.: This function initializes the plugin. It parses the commandline-
|
|
parameter and stores the default and parsed values in the
|
|
appropriate variables.
|
|
Input Value.: param contains among others the command-line string
|
|
Return Value: 0 if everything is fine
|
|
1 if "--help" was triggered, in this case the calling programm
|
|
should stop running and leave.
|
|
******************************************************************************/
|
|
int input_init(input_parameter *param, int id)
|
|
{
|
|
char *dev = "/dev/video0", *s;
|
|
int width = 640, height = 480, fps = -1, format = V4L2_PIX_FMT_MJPEG, i;
|
|
v4l2_std_id tvnorm = V4L2_STD_UNKNOWN;
|
|
context *pctx;
|
|
context_settings *settings;
|
|
|
|
pctx = calloc(1, sizeof(context));
|
|
if (pctx == NULL) {
|
|
IPRINT("error allocating context");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
settings = pctx->init_settings = init_settings();
|
|
pglobal = param->global;
|
|
pglobal->in[id].context = pctx;
|
|
|
|
/* initialize the mutes variable */
|
|
if(pthread_mutex_init(&pctx->controls_mutex, NULL) != 0) {
|
|
IPRINT("could not initialize mutex variable\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
param->argv[0] = INPUT_PLUGIN_NAME;
|
|
|
|
/* show all parameters for DBG purposes */
|
|
for(i = 0; i < param->argc; i++) {
|
|
DBG("argv[%d]=%s\n", i, param->argv[i]);
|
|
}
|
|
|
|
/* parse the parameters */
|
|
reset_getopt();
|
|
while(1) {
|
|
int option_index = 0, c = 0;
|
|
static struct option long_options[] = {
|
|
{"h", no_argument, 0, 0
|
|
},
|
|
{"help", no_argument, 0, 0},
|
|
{"d", required_argument, 0, 0},
|
|
{"device", required_argument, 0, 0},
|
|
{"r", required_argument, 0, 0},
|
|
{"resolution", required_argument, 0, 0},
|
|
{"f", required_argument, 0, 0},
|
|
{"fps", required_argument, 0, 0},
|
|
{"y", no_argument, 0, 0},
|
|
{"yuv", no_argument, 0, 0},
|
|
{"u", no_argument, 0, 0},
|
|
{"uyvy", no_argument, 0, 0},
|
|
{"q", required_argument, 0, 0},
|
|
{"quality", required_argument, 0, 0},
|
|
{"m", required_argument, 0, 0},
|
|
{"minimum_size", required_argument, 0, 0},
|
|
{"n", no_argument, 0, 0},
|
|
{"no_dynctrl", no_argument, 0, 0},
|
|
{"l", required_argument, 0, 0},
|
|
{"led", required_argument, 0, 0},
|
|
{"fourcc", required_argument, 0, 0},
|
|
{"t", required_argument, 0, 0 },
|
|
{"tvnorm", required_argument, 0, 0 },
|
|
{"e", required_argument, 0, 0},
|
|
{"every_frame", required_argument, 0, 0},
|
|
{"sh", required_argument, 0, 0},
|
|
{"co", required_argument, 0, 0},
|
|
{"br", required_argument, 0, 0},
|
|
{"sa", required_argument, 0, 0},
|
|
{"wb", required_argument, 0, 0},
|
|
{"ex", required_argument, 0, 0},
|
|
{"bk", required_argument, 0, 0},
|
|
{"rot", required_argument, 0, 0},
|
|
{"hf", required_argument, 0, 0},
|
|
{"vf", required_argument, 0, 0},
|
|
{"pl", required_argument, 0, 0},
|
|
{"gain", required_argument, 0, 0},
|
|
{"cagc", required_argument, 0, 0},
|
|
{"cb", required_argument, 0, 0},
|
|
{"timestamp", no_argument, 0, 0},
|
|
{"softfps", required_argument, 0, 0},
|
|
{"timeout", required_argument, 0, 0},
|
|
{"dv_timings", no_argument, 0, 0},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
/* parsing all parameters according to the list above is sufficent */
|
|
c = getopt_long_only(param->argc, param->argv, "", long_options, &option_index);
|
|
|
|
/* no more options to parse */
|
|
if(c == -1) break;
|
|
|
|
/* unrecognized option */
|
|
if(c == '?') {
|
|
help();
|
|
return 1;
|
|
}
|
|
|
|
/* dispatch the given options */
|
|
switch(option_index) {
|
|
/* h, help */
|
|
case 0:
|
|
case 1:
|
|
DBG("case 0,1\n");
|
|
help();
|
|
return 1;
|
|
break;
|
|
|
|
/* d, device */
|
|
case 2:
|
|
case 3:
|
|
DBG("case 2,3\n");
|
|
dev = realpath(optarg, NULL);
|
|
break;
|
|
|
|
/* r, resolution */
|
|
case 4:
|
|
case 5:
|
|
DBG("case 4,5\n");
|
|
parse_resolution_opt(optarg, &width, &height);
|
|
break;
|
|
|
|
/* f, fps */
|
|
case 6:
|
|
case 7:
|
|
DBG("case 6,7\n");
|
|
fps = atoi(optarg);
|
|
break;
|
|
|
|
/* y, yuv */
|
|
#ifndef NO_LIBJPEG
|
|
case 8:
|
|
case 9:
|
|
DBG("case 8,9\n");
|
|
format = V4L2_PIX_FMT_YUYV;
|
|
break;
|
|
#endif
|
|
/* u, uyvy */
|
|
#ifndef NO_LIBJPEG
|
|
case 10:
|
|
case 11:
|
|
DBG("case 10,11\n");
|
|
format = V4L2_PIX_FMT_UYVY;
|
|
break;
|
|
#endif
|
|
/* q, quality */
|
|
#ifndef NO_LIBJPEG
|
|
case 12:
|
|
OPTION_INT(13, quality)
|
|
settings->quality = MIN(MAX(settings->quality, 0), 100);
|
|
break;
|
|
#endif
|
|
/* m, minimum_size */
|
|
case 14:
|
|
case 15:
|
|
DBG("case 14,15\n");
|
|
minimum_size = MAX(atoi(optarg), 0);
|
|
break;
|
|
|
|
/* n, no_dynctrl */
|
|
case 16:
|
|
case 17:
|
|
DBG("case 16,17\n");
|
|
dynctrls = 0;
|
|
break;
|
|
|
|
/* l, led */
|
|
case 18:
|
|
case 19:/*
|
|
DBG("case 18,19\n");
|
|
if ( strcmp("on", optarg) == 0 ) {
|
|
led = IN_CMD_LED_ON;
|
|
} else if ( strcmp("off", optarg) == 0 ) {
|
|
led = IN_CMD_LED_OFF;
|
|
} else if ( strcmp("auto", optarg) == 0 ) {
|
|
led = IN_CMD_LED_AUTO;
|
|
} else if ( strcmp("blink", optarg) == 0 ) {
|
|
led = IN_CMD_LED_BLINK;
|
|
}*/
|
|
break;
|
|
/* fourcc */
|
|
#ifndef NO_LIBJPEG
|
|
case 20:
|
|
DBG("case 20\n");
|
|
if (strcmp(optarg, "RGB24") == 0) {
|
|
format = V4L2_PIX_FMT_RGB24;
|
|
} else if (strcmp(optarg, "RGBP") == 0) {
|
|
format = V4L2_PIX_FMT_RGB565;
|
|
} else {
|
|
fprintf(stderr," i: FOURCC codec '%s' not supported\n", optarg);
|
|
}
|
|
break;
|
|
#endif
|
|
/* t, tvnorm */
|
|
case 21:
|
|
case 22:
|
|
DBG("case 21,22\n");
|
|
if (strcasecmp("pal",optarg) == 0 ) {
|
|
tvnorm = V4L2_STD_PAL;
|
|
} else if ( strcasecmp("ntsc",optarg) == 0 ) {
|
|
tvnorm = V4L2_STD_NTSC;
|
|
} else if ( strcasecmp("secam",optarg) == 0 ) {
|
|
tvnorm = V4L2_STD_SECAM;
|
|
}
|
|
break;
|
|
case 23:
|
|
/* e, every */
|
|
case 24:
|
|
DBG("case 24\n");
|
|
every = MAX(atoi(optarg), 1);
|
|
break;
|
|
|
|
/* options */
|
|
OPTION_INT(25, sh)
|
|
break;
|
|
OPTION_INT(26, co)
|
|
break;
|
|
OPTION_INT_AUTO(27, br)
|
|
break;
|
|
OPTION_INT(28, sa)
|
|
break;
|
|
OPTION_INT_AUTO(29, wb)
|
|
break;
|
|
OPTION_MULTI_OR_INT(30, ex_auto, V4L2_EXPOSURE_MANUAL, ex, exposures)
|
|
break;
|
|
OPTION_INT(31, bk)
|
|
break;
|
|
OPTION_INT(32, rot)
|
|
break;
|
|
OPTION_BOOL(33, hf)
|
|
break;
|
|
OPTION_BOOL(34, vf)
|
|
break;
|
|
OPTION_MULTI(35, pl, power_line)
|
|
break;
|
|
OPTION_INT_AUTO(36, gain)
|
|
break;
|
|
OPTION_INT_AUTO(37, cagc)
|
|
break;
|
|
OPTION_INT_AUTO(38, cb)
|
|
break;
|
|
case 39:
|
|
wantTimestamp = 1;
|
|
break;
|
|
case 40:
|
|
softfps = atoi(optarg);
|
|
break;
|
|
case 41:
|
|
DBG("case 41\n");
|
|
timeout = MAX(atoi(optarg), 1);
|
|
break;
|
|
case 42:
|
|
DBG("case 42\n");
|
|
dv_timings = 1;
|
|
break;
|
|
default:
|
|
DBG("default case\n");
|
|
help();
|
|
return 1;
|
|
}
|
|
}
|
|
DBG("input id: %d\n", id);
|
|
pctx->id = id;
|
|
pctx->pglobal = param->global;
|
|
|
|
/* allocate webcam datastructure */
|
|
pctx->videoIn = calloc(1, sizeof(struct vdIn));
|
|
if(pctx->videoIn == NULL) {
|
|
IPRINT("not enough memory for videoIn\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* display the parsed values */
|
|
IPRINT("Using V4L2 device.: %s\n", dev);
|
|
IPRINT("Desired Resolution: %i x %i\n", width, height);
|
|
IPRINT("Frames Per Second.: %i\n", fps);
|
|
char *fmtString = NULL;
|
|
switch (format) {
|
|
case V4L2_PIX_FMT_MJPEG:
|
|
// Fall-through intentional
|
|
case V4L2_PIX_FMT_JPEG:
|
|
fmtString = "JPEG";
|
|
break;
|
|
#ifndef NO_LIBJPG
|
|
case V4L2_PIX_FMT_YUYV:
|
|
fmtString = "YUYV";
|
|
break;
|
|
case V4L2_PIX_FMT_UYVY:
|
|
fmtString = "UYVY";
|
|
break;
|
|
case V4L2_PIX_FMT_RGB24:
|
|
fmtString = "RGB24";
|
|
break;
|
|
case V4L2_PIX_FMT_RGB565:
|
|
fmtString = "RGB565";
|
|
break;
|
|
#endif
|
|
default:
|
|
fmtString = "Unknown format";
|
|
}
|
|
|
|
IPRINT("Format............: %s\n", fmtString);
|
|
#ifndef NO_LIBJPEG
|
|
if(format != V4L2_PIX_FMT_MJPEG && format != V4L2_PIX_FMT_JPEG)
|
|
IPRINT("JPEG Quality......: %d\n", settings->quality);
|
|
#endif
|
|
|
|
if (tvnorm != V4L2_STD_UNKNOWN) {
|
|
IPRINT("TV-Norm...........: %s\n", get_name_by_tvnorm(tvnorm));
|
|
} else {
|
|
IPRINT("TV-Norm...........: DEFAULT\n");
|
|
}
|
|
|
|
DBG("vdIn pn: %d\n", id);
|
|
/* open video device and prepare data structure */
|
|
pctx->videoIn->dv_timings = dv_timings;
|
|
if(init_videoIn(pctx->videoIn, dev, width, height, fps, format, 1, pctx->pglobal, id, tvnorm) < 0) {
|
|
IPRINT("init_VideoIn failed\n");
|
|
closelog();
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (softfps > 0) {
|
|
IPRINT("Framedrop FPS.....: %d\n", softfps);
|
|
}
|
|
|
|
/*
|
|
* recent linux-uvc driver (revision > ~#125) requires to use dynctrls
|
|
* for pan/tilt/focus/...
|
|
* dynctrls must get initialized
|
|
*/
|
|
if(dynctrls)
|
|
initDynCtrls(pctx->videoIn->fd);
|
|
|
|
enumerateControls(pctx->videoIn, pctx->pglobal, id); // enumerate V4L2 controls after UVC extended mapping
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
Description.: Stops the execution of worker thread
|
|
Input Value.: -
|
|
Return Value: always 0
|
|
******************************************************************************/
|
|
int input_stop(int id)
|
|
{
|
|
input * in = &pglobal->in[id];
|
|
context *pctx = (context*)in->context;
|
|
|
|
DBG("will cancel camera thread #%02d\n", id);
|
|
pthread_cancel(pctx->threadID);
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
Description.: spins of a worker thread
|
|
Input Value.: -
|
|
Return Value: always 0
|
|
******************************************************************************/
|
|
int input_run(int id)
|
|
{
|
|
input * in = &pglobal->in[id];
|
|
context *pctx = (context*)in->context;
|
|
|
|
in->buf = malloc(pctx->videoIn->framesizeIn);
|
|
if(in->buf == NULL) {
|
|
fprintf(stderr, "could not allocate memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
DBG("launching camera thread #%02d\n", id);
|
|
/* create thread and pass context to thread function */
|
|
pthread_create(&(pctx->threadID), NULL, cam_thread, in);
|
|
pthread_detach(pctx->threadID);
|
|
return 0;
|
|
}
|
|
|
|
/*** private functions for this plugin below ***/
|
|
/******************************************************************************
|
|
Description.: print a help message to stderr
|
|
Input Value.: -
|
|
Return Value: -
|
|
******************************************************************************/
|
|
void help(void)
|
|
{
|
|
int i;
|
|
|
|
fprintf(stderr, " ---------------------------------------------------------------\n" \
|
|
" Help for input plugin..: "INPUT_PLUGIN_NAME"\n" \
|
|
" ---------------------------------------------------------------\n" \
|
|
" The following parameters can be passed to this plugin:\n\n" \
|
|
" [-d | --device ].......: video device to open (your camera)\n" \
|
|
" [-r | --resolution ]...: the resolution of the video device,\n" \
|
|
" can be one of the following strings:\n" \
|
|
" ");
|
|
|
|
resolutions_help(" ");
|
|
|
|
fprintf(stderr,
|
|
" [-f | --fps ]..........: frames per second\n" \
|
|
" (camera may coerce to different value)\n" \
|
|
" [-q | --quality ] .....: set quality of JPEG encoding\n" \
|
|
" [-m | --minimum_size ].: drop frames smaller then this limit, useful\n" \
|
|
" if the webcam produces small-sized garbage frames\n" \
|
|
" may happen under low light conditions\n" \
|
|
" [-e | --every_frame ]..: drop all frames except numbered\n" \
|
|
" [-n | --no_dynctrl ]...: do not initalize dynctrls of Linux-UVC driver\n" \
|
|
" [-l | --led ]..........: switch the LED \"on\", \"off\", let it \"blink\" or leave\n" \
|
|
" it up to the driver using the value \"auto\"\n" \
|
|
" [-t | --tvnorm ] ......: set TV-Norm pal, ntsc or secam\n" \
|
|
" [-u | --uyvy ] ........: Use UYVY format, default: MJPEG (uses more cpu power)\n" \
|
|
" [-y | --yuv ] ........: Use YUV format, default: MJPEG (uses more cpu power)\n" \
|
|
" [-fourcc ] ............: Use FOURCC codec 'argopt', \n" \
|
|
" currently supported codecs are: RGB24, RGBP \n" \
|
|
" [-timestamp ]..........: Populate frame timestamp with system time\n" \
|
|
" [-softfps] ............: Drop frames to try and achieve this fps\n" \
|
|
" set your camera to its maximum fps to avoid stuttering\n" \
|
|
" [-timeout] ............: Timeout for device querying (seconds)\n" \
|
|
" [-dv_timings] .........: Enable DV timings queriyng and events processing\n" \
|
|
" ---------------------------------------------------------------\n");
|
|
|
|
fprintf(stderr, "\n"\
|
|
" Optional parameters (may not be supported by all cameras):\n\n"\
|
|
" [-br ].................: Set image brightness (auto or integer)\n"\
|
|
" [-co ].................: Set image contrast (integer)\n"\
|
|
" [-sh ].................: Set image sharpness (integer)\n"\
|
|
" [-sa ].................: Set image saturation (integer)\n"\
|
|
" [-cb ].................: Set color balance (auto or integer)\n"\
|
|
" [-wb ].................: Set white balance (auto or integer)\n"\
|
|
" [-ex ].................: Set exposure (auto, shutter-priority, aperature-priority, or integer)\n"\
|
|
" [-bk ].................: Set backlight compensation (integer)\n"\
|
|
" [-rot ]................: Set image rotation (0-359)\n"\
|
|
" [-hf ].................: Set horizontal flip (true/false)\n"\
|
|
" [-vf ].................: Set vertical flip (true/false)\n"\
|
|
" [-pl ].................: Set power line filter (disabled, 50hz, 60hz, auto)\n"\
|
|
" [-gain ]...............: Set gain (auto or integer)\n"\
|
|
" [-cagc ]...............: Set chroma gain control (auto or integer)\n"\
|
|
" ---------------------------------------------------------------\n\n"\
|
|
);
|
|
}
|
|
|
|
/******************************************************************************
|
|
Description.: this thread worker grabs a frame and copies it to the global buffer
|
|
Input Value.: unused
|
|
Return Value: unused, always NULL
|
|
******************************************************************************/
|
|
void *cam_thread(void *arg)
|
|
{
|
|
input * in = (input*)arg;
|
|
context *pcontext = (context*)in->context;
|
|
context_settings *settings = pcontext->init_settings;
|
|
|
|
unsigned int every_count = 0;
|
|
int quality = settings->quality;
|
|
|
|
/* set cleanup handler to cleanup allocated resources */
|
|
pthread_cleanup_push(cam_cleanup, in);
|
|
|
|
#define V4L_OPT_SET(vid, var, desc) \
|
|
if (input_cmd(pcontext->id, vid, IN_CMD_V4L2, settings->var, NULL) != 0) {\
|
|
fprintf(stderr, "Failed to set " desc "\n"); \
|
|
} else { \
|
|
printf(" i: %-18s: %d\n", desc, settings->var); \
|
|
}
|
|
|
|
#define V4L_INT_OPT(vid, var, desc) \
|
|
if (settings->var##_set) { \
|
|
V4L_OPT_SET(vid, var, desc) \
|
|
}
|
|
|
|
/* V4L options */
|
|
V4L_INT_OPT(V4L2_CID_SHARPNESS, sh, "sharpness")
|
|
V4L_INT_OPT(V4L2_CID_CONTRAST, co, "contrast")
|
|
V4L_INT_OPT(V4L2_CID_SATURATION, sa, "saturation")
|
|
V4L_INT_OPT(V4L2_CID_BACKLIGHT_COMPENSATION, bk, "backlight compensation")
|
|
V4L_INT_OPT(V4L2_CID_ROTATE, rot, "rotation")
|
|
V4L_INT_OPT(V4L2_CID_HFLIP, hf, "hflip")
|
|
V4L_INT_OPT(V4L2_CID_VFLIP, vf, "vflip")
|
|
V4L_INT_OPT(V4L2_CID_VFLIP, pl, "power line filter")
|
|
|
|
if (settings->br_set) {
|
|
V4L_OPT_SET(V4L2_CID_AUTOBRIGHTNESS, br_auto, "auto brightness mode")
|
|
|
|
if (settings->br_auto == 0) {
|
|
V4L_OPT_SET(V4L2_CID_BRIGHTNESS, br, "brightness")
|
|
}
|
|
}
|
|
|
|
if (settings->wb_set) {
|
|
V4L_OPT_SET(V4L2_CID_AUTO_WHITE_BALANCE, wb_auto, "auto white balance mode")
|
|
|
|
if (settings->wb_auto == 0) {
|
|
V4L_OPT_SET(V4L2_CID_WHITE_BALANCE_TEMPERATURE, wb, "white balance temperature")
|
|
}
|
|
}
|
|
|
|
if (settings->ex_set) {
|
|
V4L_OPT_SET(V4L2_CID_EXPOSURE_AUTO, ex_auto, "exposure mode")
|
|
if (settings->ex_auto == V4L2_EXPOSURE_MANUAL) {
|
|
V4L_OPT_SET(V4L2_CID_EXPOSURE_ABSOLUTE, ex, "absolute exposure")
|
|
}
|
|
}
|
|
|
|
if (settings->gain_set) {
|
|
V4L_OPT_SET(V4L2_CID_AUTOGAIN, gain_auto, "auto gain mode")
|
|
|
|
if (settings->gain_auto == 0) {
|
|
V4L_OPT_SET(V4L2_CID_GAIN, gain, "gain")
|
|
}
|
|
}
|
|
|
|
if (settings->cagc_set) {
|
|
V4L_OPT_SET(V4L2_CID_AUTO_WHITE_BALANCE, cagc_auto, "chroma gain mode")
|
|
|
|
if (settings->cagc_auto == 0) {
|
|
V4L_OPT_SET(V4L2_CID_WHITE_BALANCE_TEMPERATURE, cagc, "chroma gain")
|
|
}
|
|
}
|
|
|
|
if (settings->cb_set) {
|
|
V4L_OPT_SET(V4L2_CID_HUE_AUTO, cb_auto, "color balance mode")
|
|
|
|
if (settings->cb_auto == 0) {
|
|
V4L_OPT_SET(V4L2_CID_HUE, cagc, "color balance")
|
|
}
|
|
}
|
|
|
|
free(settings);
|
|
settings = NULL;
|
|
pcontext->init_settings = NULL;
|
|
|
|
if (softfps > 0) {
|
|
pcontext->videoIn->soft_framedrop = 1;
|
|
pcontext->videoIn->frame_period_time = 1000/softfps;
|
|
}
|
|
|
|
if (video_enable(pcontext->videoIn)) {
|
|
IPRINT("Can\'t enable video in first time\n");
|
|
goto endloop;
|
|
}
|
|
|
|
while(!pglobal->stop) {
|
|
while(pcontext->videoIn->streamingState == STREAMING_PAUSED) {
|
|
usleep(1); // maybe not the best way so FIXME
|
|
}
|
|
|
|
fd_set rd_fds; // for capture
|
|
fd_set ex_fds; // for capture
|
|
fd_set wr_fds; // for output
|
|
|
|
FD_ZERO(&rd_fds);
|
|
FD_SET(pcontext->videoIn->fd, &rd_fds);
|
|
|
|
FD_ZERO(&ex_fds);
|
|
FD_SET(pcontext->videoIn->fd, &ex_fds);
|
|
|
|
FD_ZERO(&wr_fds);
|
|
FD_SET(pcontext->videoIn->fd, &wr_fds);
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = timeout;
|
|
tv.tv_usec = 0;
|
|
|
|
int sel = select(pcontext->videoIn->fd + 1, &rd_fds, &wr_fds, &ex_fds, &tv);
|
|
DBG("select() = %d\n", sel);
|
|
|
|
if (sel < 0) {
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
perror("select() error");
|
|
goto endloop;
|
|
} else if (sel == 0) {
|
|
IPRINT("select() timeout\n");
|
|
if (dv_timings) {
|
|
if (setResolution(pcontext->videoIn, pcontext->videoIn->width, pcontext->videoIn->height) < 0) {
|
|
goto endloop;
|
|
}
|
|
continue;
|
|
} else {
|
|
goto endloop;
|
|
}
|
|
}
|
|
|
|
if (FD_ISSET(pcontext->videoIn->fd, &rd_fds)) {
|
|
DBG("Grabbing a frame...\n");
|
|
/* grab a frame */
|
|
if(uvcGrab(pcontext->videoIn) < 0) {
|
|
IPRINT("Error grabbing frames\n");
|
|
goto endloop;
|
|
}
|
|
|
|
if ( every_count < every - 1 ) {
|
|
DBG("dropping %d frame for every=%d\n", every_count + 1, every);
|
|
++every_count;
|
|
goto other_select_handlers;
|
|
} else {
|
|
every_count = 0;
|
|
}
|
|
|
|
//DBG("received frame of size: %d from plugin: %d\n", pcontext->videoIn->tmpbytesused, pcontext->id);
|
|
|
|
/*
|
|
* Workaround for broken, corrupted frames:
|
|
* Under low light conditions corrupted frames may get captured.
|
|
* The good thing is such frames are quite small compared to the regular pictures.
|
|
* For example a VGA (640x480) webcam picture is normally >= 8kByte large,
|
|
* corrupted frames are smaller.
|
|
*/
|
|
if(pcontext->videoIn->tmpbytesused < minimum_size) {
|
|
DBG("dropping too small frame, assuming it as broken\n");
|
|
goto other_select_handlers;
|
|
}
|
|
|
|
// Overwrite timestamp (e.g. where camera is providing 0 values)
|
|
// Do it here so that this timestamp can be used in frameskipping
|
|
if(wantTimestamp)
|
|
{
|
|
gettimeofday(×tamp, NULL);
|
|
pcontext->videoIn->tmptimestamp = timestamp;
|
|
}
|
|
|
|
// use software frame dropping on low fps
|
|
if (pcontext->videoIn->soft_framedrop == 1) {
|
|
unsigned long last = pglobal->in[pcontext->id].timestamp.tv_sec * 1000 +
|
|
(pglobal->in[pcontext->id].timestamp.tv_usec/1000); // convert to ms
|
|
unsigned long current = pcontext->videoIn->tmptimestamp.tv_sec * 1000 +
|
|
pcontext->videoIn->tmptimestamp.tv_usec/1000; // convert to ms
|
|
|
|
// if the requested time did not esplashed skip the frame
|
|
if ((current - last) < pcontext->videoIn->frame_period_time) {
|
|
DBG("Last frame taken %d ms ago so drop it\n", (current - last));
|
|
goto other_select_handlers;
|
|
}
|
|
DBG("Lagg: %ld\n", (current - last) - pcontext->videoIn->frame_period_time);
|
|
}
|
|
|
|
/* copy JPG picture to global buffer */
|
|
pthread_mutex_lock(&pglobal->in[pcontext->id].db);
|
|
|
|
/*
|
|
* If capturing in YUV mode convert to JPEG now.
|
|
* This compression requires many CPU cycles, so try to avoid YUV format.
|
|
* Getting JPEGs straight from the webcam, is one of the major advantages of
|
|
* Linux-UVC compatible devices.
|
|
*/
|
|
#ifndef NO_LIBJPEG
|
|
if ((pcontext->videoIn->formatIn == V4L2_PIX_FMT_YUYV) ||
|
|
(pcontext->videoIn->formatIn == V4L2_PIX_FMT_UYVY) ||
|
|
(pcontext->videoIn->formatIn == V4L2_PIX_FMT_RGB24) ||
|
|
(pcontext->videoIn->formatIn == V4L2_PIX_FMT_RGB565) ) {
|
|
DBG("compressing frame from input: %d\n", (int)pcontext->id);
|
|
pglobal->in[pcontext->id].size = compress_image_to_jpeg(pcontext->videoIn, pglobal->in[pcontext->id].buf, pcontext->videoIn->framesizeIn, quality);
|
|
/* copy this frame's timestamp to user space */
|
|
pglobal->in[pcontext->id].timestamp = pcontext->videoIn->tmptimestamp;
|
|
} else {
|
|
#endif
|
|
DBG("copying frame from input: %d\n", (int)pcontext->id);
|
|
pglobal->in[pcontext->id].size = memcpy_picture(pglobal->in[pcontext->id].buf, pcontext->videoIn->tmpbuffer, pcontext->videoIn->tmpbytesused);
|
|
/* copy this frame's timestamp to user space */
|
|
pglobal->in[pcontext->id].timestamp = pcontext->videoIn->tmptimestamp;
|
|
#ifndef NO_LIBJPEG
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
/* motion detection can be done just by comparing the picture size, but it is not very accurate!! */
|
|
if((prev_size - global->size)*(prev_size - global->size) > 4 * 1024 * 1024) {
|
|
DBG("motion detected (delta: %d kB)\n", (prev_size - global->size) / 1024);
|
|
}
|
|
prev_size = global->size;
|
|
#endif
|
|
|
|
/* signal fresh_frame */
|
|
pthread_cond_broadcast(&pglobal->in[pcontext->id].db_update);
|
|
pthread_mutex_unlock(&pglobal->in[pcontext->id].db);
|
|
}
|
|
|
|
other_select_handlers:
|
|
|
|
if (dv_timings) {
|
|
if (FD_ISSET(pcontext->videoIn->fd, &wr_fds)) {
|
|
IPRINT("Writing?!\n");
|
|
}
|
|
|
|
if (FD_ISSET(pcontext->videoIn->fd, &ex_fds)) {
|
|
IPRINT("FD exception\n");
|
|
if (video_handle_event(pcontext->videoIn) < 0) {
|
|
goto endloop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
endloop:
|
|
|
|
DBG("leaving input thread, calling cleanup function now\n");
|
|
pthread_cleanup_pop(1);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/******************************************************************************
|
|
Description.:
|
|
Input Value.:
|
|
Return Value:
|
|
******************************************************************************/
|
|
void cam_cleanup(void *arg)
|
|
{
|
|
input * in = (input*)arg;
|
|
context *pctx = (context*)in->context;
|
|
|
|
IPRINT("cleaning up resources allocated by input thread\n");
|
|
|
|
if (pctx->videoIn != NULL) {
|
|
close_v4l2(pctx->videoIn);
|
|
free(pctx->videoIn->tmpbuffer);
|
|
free(pctx->videoIn);
|
|
pctx->videoIn = NULL;
|
|
}
|
|
|
|
free(in->buf);
|
|
in->buf = NULL;
|
|
in->size = 0;
|
|
}
|
|
|
|
/******************************************************************************
|
|
Description.: process commands, allows to set v4l2 controls
|
|
Input Value.: * control specifies the selected v4l2 control's id
|
|
see struct v4l2_queryctr in the videodev2.h
|
|
* value is used for control that make use of a parameter.
|
|
Return Value: depends in the command, for most cases 0 means no errors and
|
|
-1 signals an error. This is just rule of thumb, not more!
|
|
******************************************************************************/
|
|
int input_cmd(int plugin_number, unsigned int control_id, unsigned int group, int value, char *value_string)
|
|
{
|
|
input * in = &pglobal->in[plugin_number];
|
|
context *pctx = (context*)in->context;
|
|
|
|
int ret = -1;
|
|
int i = 0;
|
|
DBG("Requested cmd (id: %d) for the %d plugin. Group: %d value: %d\n", control_id, plugin_number, group, value);
|
|
switch(group) {
|
|
case IN_CMD_GENERIC: {
|
|
int i;
|
|
for (i = 0; i<in->parametercount; i++) {
|
|
if ((in->in_parameters[i].ctrl.id == control_id) &&
|
|
(in->in_parameters[i].group == IN_CMD_GENERIC)){
|
|
DBG("Generic control found (id: %d): %s\n", control_id, in->in_parameters[i].ctrl.name);
|
|
DBG("New %s value: %d\n", in->in_parameters[i].ctrl.name, value);
|
|
return 0;
|
|
}
|
|
}
|
|
DBG("Requested generic control (%d) did not found\n", control_id);
|
|
return -1;
|
|
} break;
|
|
case IN_CMD_V4L2: {
|
|
ret = v4l2SetControl(pctx->videoIn, control_id, value, plugin_number, pglobal);
|
|
if(ret == 0) {
|
|
in->in_parameters[i].value = value;
|
|
} else {
|
|
DBG("v4l2SetControl failed: %d\n", ret);
|
|
}
|
|
return ret;
|
|
} break;
|
|
case IN_CMD_RESOLUTION: {
|
|
// the value points to the current formats nth resolution
|
|
if(value > (in->in_formats[in->currentFormat].resolutionCount - 1)) {
|
|
DBG("The value is out of range");
|
|
return -1;
|
|
}
|
|
int height = in->in_formats[in->currentFormat].supportedResolutions[value].height;
|
|
int width = in->in_formats[in->currentFormat].supportedResolutions[value].width;
|
|
ret = setResolution(pctx->videoIn, width, height);
|
|
if(ret == 0) {
|
|
in->in_formats[in->currentFormat].currentResolution = value;
|
|
}
|
|
return ret;
|
|
} break;
|
|
case IN_CMD_JPEG_QUALITY:
|
|
if((value >= 0) && (value < 101)) {
|
|
in->jpegcomp.quality = value;
|
|
if(IOCTL_VIDEO(pctx->videoIn->fd, VIDIOC_S_JPEGCOMP, &in->jpegcomp) != EINVAL) {
|
|
DBG("JPEG quality is set to %d\n", value);
|
|
ret = 0;
|
|
} else {
|
|
DBG("Setting the JPEG quality is not supported\n");
|
|
}
|
|
} else {
|
|
DBG("Quality is out of range\n");
|
|
}
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|