mjpg-streamer/plugins/input_opencv/input_opencv.cpp

478 lines
15 KiB
C++

/*******************************************************************************
# #
# OpenCV input plugin #
# Copyright (C) 2016 Dustin Spicuzza #
# #
# 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; version 2 of the License. #
# #
# 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 <getopt.h>
#include <dlfcn.h>
#include <pthread.h>
#include "input_opencv.h"
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
/* private functions and variables to this plugin */
static globals *pglobal;
typedef struct {
char *filter_args;
int fps_set, fps,
quality_set, quality,
co_set, co,
br_set, br,
sa_set, sa,
gain_set, gain,
ex_set, ex;
} context_settings;
// filter functions
typedef bool (*filter_init_fn)(const char * args, void** filter_ctx);
typedef Mat (*filter_init_frame_fn)(void* filter_ctx);
typedef void (*filter_process_fn)(void* filter_ctx, Mat &src, Mat &dst);
typedef void (*filter_free_fn)(void* filter_ctx);
typedef struct {
pthread_t worker;
VideoCapture capture;
context_settings *init_settings;
void* filter_handle;
void* filter_ctx;
filter_init_fn filter_init;
filter_init_frame_fn filter_init_frame;
filter_process_fn filter_process;
filter_free_fn filter_free;
} context;
void *worker_thread(void *);
void worker_cleanup(void *);
#define INPUT_PLUGIN_NAME "OpenCV Input plugin"
static char plugin_name[] = INPUT_PLUGIN_NAME;
static void null_filter(void* filter_ctx, Mat &src, Mat &dst) {
dst = src;
}
static void help() {
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" \
" [-q | --quality ] .....: set quality of JPEG encoding\n" \
" ---------------------------------------------------------------\n" \
" Optional parameters (may not be supported by all cameras):\n\n"
" [-br ].................: Set image brightness (integer)\n"\
" [-co ].................: Set image contrast (integer)\n"\
" [-sh ].................: Set image sharpness (integer)\n"\
" [-sa ].................: Set image saturation (integer)\n"\
" [-ex ].................: Set exposure (off, or integer)\n"\
" [-gain ]...............: Set gain (integer)\n"
" ---------------------------------------------------------------\n" \
" Optional filter plugin:\n" \
" [ -filter ]............: filter plugin .so\n" \
" [ -fargs ].............: filter plugin arguments\n" \
" ---------------------------------------------------------------\n\n"\
);
}
static context_settings* init_settings() {
context_settings *settings;
settings = (context_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.: parse input parameters
Input Value.: param contains the command line string and a pointer to globals
Return Value: 0 if everything is ok
******************************************************************************/
int input_init(input_parameter *param, int plugin_no)
{
const char * device = "default";
const char *filter = NULL, *filter_args = "";
int width = 640, height = 480, i, device_idx;
input * in;
context *pctx;
context_settings *settings;
pctx = new context();
settings = pctx->init_settings = init_settings();
pglobal = param->global;
in = &pglobal->in[plugin_no];
in->context = pctx;
param->argv[0] = 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},
{"q", required_argument, 0, 0},
{"quality", required_argument, 0, 0},
{"co", required_argument, 0, 0},
{"br", required_argument, 0, 0},
{"sa", required_argument, 0, 0},
{"gain", required_argument, 0, 0},
{"ex", required_argument, 0, 0},
{"filter", required_argument, 0, 0},
{"fargs", required_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:
help();
return 1;
/* d, device */
case 2:
case 3:
device = optarg;
break;
/* r, resolution */
case 4:
case 5:
DBG("case 4,5\n");
parse_resolution_opt(optarg, &width, &height);
break;
/* f, fps */
case 6:
OPTION_INT(7, fps)
break;
/* q, quality */
case 8:
OPTION_INT(9, quality)
settings->quality = MIN(MAX(settings->quality, 0), 100);
break;
OPTION_INT(10, co)
break;
OPTION_INT(11, br)
break;
OPTION_INT(12, sa)
break;
OPTION_INT(13, gain)
break;
OPTION_INT(14, ex)
break;
/* filter */
case 15:
filter = optarg;
break;
/* fargs */
case 16:
filter_args = optarg;
break;
default:
help();
return 1;
}
}
IPRINT("device........... : %s\n", device);
IPRINT("Desired Resolution: %i x %i\n", width, height);
// need to allocate a VideoCapture object: default device is 0
try {
if (!strcasecmp(device, "default")) {
pctx->capture.open(0);
} else if (sscanf(device, "%d", &device_idx) == 1) {
pctx->capture.open(device_idx);
} else {
pctx->capture.open(device);
}
} catch (Exception e) {
IPRINT("VideoCapture::open() failed: %s\n", e.what());
goto fatal_error;
}
// validate that isOpened is true
if (!pctx->capture.isOpened()) {
IPRINT("VideoCapture::open() failed\n");
goto fatal_error;
}
pctx->capture.set(CAP_PROP_FRAME_WIDTH, width);
pctx->capture.set(CAP_PROP_FRAME_HEIGHT, height);
if (settings->fps_set)
pctx->capture.set(CAP_PROP_FPS, settings->fps);
/* filter stuff goes here */
if (filter != NULL) {
IPRINT("filter........... : %s\n", filter);
IPRINT("filter args ..... : %s\n", filter_args);
pctx->filter_handle = dlopen(filter, RTLD_LAZY | RTLD_GLOBAL);
if(!pctx->filter_handle) {
LOG("ERROR: could not find input plugin\n");
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror());
goto fatal_error;
}
pctx->filter_init = (filter_init_fn)dlsym(pctx->filter_handle, "filter_init");
if (pctx->filter_init == NULL) {
LOG("ERROR: %s\n", dlerror());
goto fatal_error;
}
pctx->filter_process = (filter_process_fn)dlsym(pctx->filter_handle, "filter_process");
if (pctx->filter_process == NULL) {
LOG("ERROR: %s\n", dlerror());
goto fatal_error;
}
pctx->filter_free = (filter_free_fn)dlsym(pctx->filter_handle, "filter_free");
if (pctx->filter_free == NULL) {
LOG("ERROR: %s\n", dlerror());
goto fatal_error;
}
// optional functions
pctx->filter_init_frame = (filter_init_frame_fn)dlsym(pctx->filter_handle, "filter_init_frame");
// initialize it
if (!pctx->filter_init(filter_args, &pctx->filter_ctx)) {
goto fatal_error;
}
} else {
pctx->filter_handle = NULL;
pctx->filter_ctx = NULL;
pctx->filter_process = null_filter;
pctx->filter_free = NULL;
}
return 0;
fatal_error:
worker_cleanup(in);
closelog();
exit(EXIT_FAILURE);
}
/******************************************************************************
Description.: stops the execution of the worker thread
Input Value.: -
Return Value: 0
******************************************************************************/
int input_stop(int id)
{
input * in = &pglobal->in[id];
context *pctx = (context*)in->context;
if (pctx != NULL) {
DBG("will cancel input thread\n");
pthread_cancel(pctx->worker);
}
return 0;
}
/******************************************************************************
Description.: starts the worker thread and allocates memory
Input Value.: -
Return Value: 0
******************************************************************************/
int input_run(int id)
{
input * in = &pglobal->in[id];
context *pctx = (context*)in->context;
in->buf = NULL;
in->size = 0;
if(pthread_create(&pctx->worker, 0, worker_thread, in) != 0) {
worker_cleanup(in);
fprintf(stderr, "could not start worker thread\n");
exit(EXIT_FAILURE);
}
pthread_detach(pctx->worker);
return 0;
}
void *worker_thread(void *arg)
{
input * in = (input*)arg;
context *pctx = (context*)in->context;
context_settings *settings = (context_settings*)pctx->init_settings;
/* set cleanup handler to cleanup allocated resources */
pthread_cleanup_push(worker_cleanup, arg);
/* set VideoCapture options */
#define CVOPT_OPT(prop, var, desc) \
if (!pctx->capture.set(prop, settings->var)) {\
IPRINT("%-18s: %d\n", desc, settings->var); \
} else {\
fprintf(stderr, "Failed to set " desc "\n"); \
}
#define CVOPT_SET(prop, var, desc) \
if (settings->var##_set) { \
CVOPT_OPT(prop, var,desc) \
}
CVOPT_SET(CAP_PROP_FPS, fps, "frames per second")
CVOPT_SET(CAP_PROP_BRIGHTNESS, co, "contrast")
CVOPT_SET(CAP_PROP_CONTRAST, br, "brightness")
CVOPT_SET(CAP_PROP_SATURATION, sa, "saturation")
CVOPT_SET(CAP_PROP_GAIN, gain, "gain")
CVOPT_SET(CAP_PROP_EXPOSURE, ex, "exposure")
/* setup imencode options */
vector<int> compression_params;
compression_params.push_back(CV_IMWRITE_JPEG_QUALITY);
compression_params.push_back(settings->quality); // 1-100
free(settings);
pctx->init_settings = NULL;
settings = NULL;
Mat src, dst;
vector<uchar> jpeg_buffer;
// this exists so that the numpy allocator can assign a custom allocator to
// the mat, so that it doesn't need to copy the data each time
if (pctx->filter_init_frame != NULL)
src = pctx->filter_init_frame(pctx->filter_ctx);
while (!pglobal->stop) {
if (!pctx->capture.read(src))
break; // TODO
// call the filter function
pctx->filter_process(pctx->filter_ctx, src, dst);
/* copy JPG picture to global buffer */
pthread_mutex_lock(&in->db);
// take whatever Mat it returns, and write it to jpeg buffer
imencode(".jpg", dst, jpeg_buffer, compression_params);
// TODO: what to do if imencode returns an error?
// std::vector is guaranteed to be contiguous
in->buf = &jpeg_buffer[0];
in->size = jpeg_buffer.size();
/* signal fresh_frame */
pthread_cond_broadcast(&in->db_update);
pthread_mutex_unlock(&in->db);
}
IPRINT("leaving input thread, calling cleanup function now\n");
pthread_cleanup_pop(1);
return NULL;
}
/******************************************************************************
Description.: this functions cleans up allocated resources
Input Value.: arg is unused
Return Value: -
******************************************************************************/
void worker_cleanup(void *arg)
{
input * in = (input*)arg;
if (in->context != NULL) {
context *pctx = (context*)in->context;
if (pctx->filter_free != NULL && pctx->filter_ctx != NULL) {
pctx->filter_free(pctx->filter_ctx);
pctx->filter_free = NULL;
}
if (pctx->filter_handle != NULL) {
dlclose(pctx->filter_handle);
pctx->filter_handle = NULL;
}
delete pctx;
in->context = NULL;
}
}