352 lines
9.5 KiB
C
352 lines
9.5 KiB
C
/* input_ptp2.c -- MJPG-streamer input plugin to stream JPG frames
|
|
from digital cameras supporting PTP2 capture
|
|
|
|
Copyright (C) 2010, Alessio Sangalli (alesan@manoweb.com)
|
|
|
|
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 3, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
|
|
|
|
#define CAMERA_CHECK_GP(res, msg){if(res != GP_OK){IPRINT(INPUT_PLUGIN_NAME " - Gphoto error, on '%s': %d - %s\n", msg, res, gp_result_as_string(res)); return 0;}}
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <pthread.h>
|
|
#include <gphoto2/gphoto2-camera.h>
|
|
#include "input_ptp2.h"
|
|
|
|
#define INPUT_PLUGIN_NAME "PTP2 input plugin"
|
|
|
|
static int plugin_id;
|
|
static pthread_t thread;
|
|
static pthread_mutex_t control_mutex;
|
|
static globals* global;
|
|
|
|
GPContext* context;
|
|
Camera* camera;
|
|
char* selected_port;
|
|
int delay;
|
|
|
|
int input_init(input_parameter *param, int id)
|
|
{
|
|
int i;
|
|
int opt;
|
|
|
|
global = param->global;
|
|
|
|
if(pthread_mutex_init(&control_mutex, NULL) != 0)
|
|
{
|
|
IPRINT(INPUT_PLUGIN_NAME "- Could not initialize mutex variable\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
control zoom_ctrl;
|
|
zoom_ctrl.group = IN_CMD_GENERIC;
|
|
zoom_ctrl.menuitems = NULL;
|
|
zoom_ctrl.value = 0.0;
|
|
zoom_ctrl.class_id = 0;
|
|
|
|
zoom_ctrl.ctrl.id = 1;
|
|
zoom_ctrl.ctrl.type = V4L2_CTRL_TYPE_INTEGER;
|
|
strcpy((char*) zoom_ctrl.ctrl.name, "Zoom");
|
|
zoom_ctrl.ctrl.minimum = 0;
|
|
zoom_ctrl.ctrl.maximum = 10;
|
|
zoom_ctrl.ctrl.step = 1;
|
|
zoom_ctrl.ctrl.default_value = 0;
|
|
zoom_ctrl.ctrl.flags = V4L2_CTRL_FLAG_SLIDER;
|
|
|
|
param->global->in[id].in_parameters = (control*) malloc((param->global->in[id].parametercount + 1) * sizeof(control));
|
|
|
|
param->global->in[id].in_parameters[param->global->in[id].parametercount] = zoom_ctrl;
|
|
param->global->in[id].parametercount++;
|
|
|
|
selected_port = NULL;
|
|
delay = 0;
|
|
|
|
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]);
|
|
}
|
|
|
|
optind = 1;
|
|
while((opt = getopt(param->argc, param->argv, "hu:d:")) != -1)
|
|
{
|
|
switch(opt)
|
|
{
|
|
case 'h':
|
|
help();
|
|
return 1;
|
|
case 'u':
|
|
delay = atoi(optarg);
|
|
break;
|
|
case 'd':
|
|
selected_port = strdup(optarg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DBG("usleep: %d\n", delay); DBG("device: %s\n", selected_port);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int input_stop(int id)
|
|
{
|
|
DBG("will cancel input thread\n");
|
|
pthread_cancel(thread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void help()
|
|
{
|
|
printf(" ---------------------------------------------------------------\n"
|
|
" Help for input plugin..: "INPUT_PLUGIN_NAME"\n"
|
|
" ---------------------------------------------------------------\n"
|
|
" The following parameters can be passed to this plugin:\n\n"
|
|
" [-h ]..........: print this help\n"
|
|
" [-u X ]........: delay between frames in us (default 0)\n"
|
|
" [-d X ]........: camera address in [usb:xxx,yyy] form; use\n"
|
|
" gphoto2 --auto-detect to get a list of\n"
|
|
" available cameras\n"
|
|
" ---------------------------------------------------------------\n");
|
|
}
|
|
|
|
int input_run(int id)
|
|
{
|
|
int res, i;
|
|
|
|
plugin_id = id;
|
|
|
|
// auto-detect algorithm
|
|
CameraAbilitiesList* al;
|
|
GPPortInfoList* il;
|
|
CameraList* list;
|
|
const char* model;
|
|
const char* port;
|
|
context = gp_context_new();
|
|
gp_abilities_list_new(&al);
|
|
gp_abilities_list_load(al, context);
|
|
gp_port_info_list_new(&il);
|
|
gp_port_info_list_load(il);
|
|
gp_list_new(&list);
|
|
gp_abilities_list_detect(al, il, list, context);
|
|
int count = gp_list_count(list);
|
|
IPRINT(INPUT_PLUGIN_NAME " - Detected %d camera(s)\n", count);
|
|
if(count == 0)
|
|
{
|
|
IPRINT(INPUT_PLUGIN_NAME " - No cameras detected.\n");
|
|
return 0;
|
|
}
|
|
GPPortInfo info;
|
|
CameraAbilities a;
|
|
int m, p;
|
|
camera = NULL;
|
|
for(i = 0; i < count; i++)
|
|
{
|
|
res = gp_list_get_name(list, i, &model);
|
|
CAMERA_CHECK_GP(res, "gp_list_get_name");
|
|
m = gp_abilities_list_lookup_model(al, model);
|
|
if(m < 0)
|
|
{
|
|
IPRINT(INPUT_PLUGIN_NAME " - Gphoto abilities_list_lookup_model Code: %d - %s\n", m, gp_result_as_string(m));
|
|
return 0;
|
|
}
|
|
res = gp_abilities_list_get_abilities(al, m, &a);
|
|
CAMERA_CHECK_GP(res, "gp_abilities_list_get_abilities");
|
|
res = gp_list_get_value(list, i, &port);
|
|
CAMERA_CHECK_GP(res, "gp_list_get_value"); DBG("Model: %s; port: %s.\n", model, port);
|
|
if(selected_port != NULL && strcmp(selected_port, port) != 0)
|
|
continue;
|
|
p = gp_port_info_list_lookup_path(il, port);
|
|
if(p < 0)
|
|
{
|
|
IPRINT(INPUT_PLUGIN_NAME " - Gphoto port_info_list_lookup_path Code: %d - %s\n", m, gp_result_as_string(m));
|
|
return 0;
|
|
}
|
|
res = gp_port_info_list_get_info(il, p, &info);
|
|
CAMERA_CHECK_GP(res, "gp_port_info_list_get_info");
|
|
|
|
res = gp_camera_new(&camera);
|
|
CAMERA_CHECK_GP(res, "gp_camera_new");
|
|
res = gp_camera_set_abilities(camera, a);
|
|
CAMERA_CHECK_GP(res, "gp_camera_set_abilities");
|
|
res = gp_camera_set_port_info(camera, info);
|
|
CAMERA_CHECK_GP(res, "gp_camera_set_port_info");
|
|
}
|
|
if(camera == NULL)
|
|
{
|
|
IPRINT("Camera %s not found, exiting.\n", selected_port);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
// cleanup
|
|
gp_list_unref(list);
|
|
gp_port_info_list_free(il);
|
|
gp_abilities_list_free(al);
|
|
|
|
// open camera and set capture on
|
|
int value = 1;
|
|
res = gp_camera_init(camera, context);
|
|
CAMERA_CHECK_GP(res, "gp_camera_init");
|
|
camera_set("capture", &value);
|
|
|
|
// starting thread
|
|
if(pthread_create(&thread, 0, capture, NULL) != 0)
|
|
{
|
|
free(global->in[id].buf);
|
|
IPRINT("could not start worker thread\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
pthread_detach(thread);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void* capture(void* arg)
|
|
{
|
|
int res;
|
|
int i = 0;
|
|
CameraFile* file;
|
|
|
|
int jpeg_buffer_size = 256 * 1024;
|
|
global->in[plugin_id].buf = malloc(jpeg_buffer_size);
|
|
if(global->in[plugin_id].buf == NULL)
|
|
{
|
|
IPRINT(INPUT_PLUGIN_NAME " - could not allocate memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
pthread_cleanup_push(cleanup, NULL);
|
|
while(!global->stop)
|
|
{
|
|
unsigned long int xsize;
|
|
const char* xdata;
|
|
pthread_mutex_lock(&control_mutex);
|
|
res = gp_file_new(&file);
|
|
CAMERA_CHECK_GP(res, "gp_file_new");
|
|
res = gp_camera_capture_preview(camera, file, context);
|
|
CAMERA_CHECK_GP(res, "gp_camera_capture_preview");
|
|
pthread_mutex_lock(&global->in[plugin_id].db);
|
|
res = gp_file_get_data_and_size(file, &xdata, &xsize);
|
|
if(xsize == 0)
|
|
{
|
|
if(i++ > 3)
|
|
{
|
|
IPRINT("Restarted too many times; giving up\n");
|
|
return NULL;
|
|
}
|
|
int value = 0;
|
|
IPRINT("Read 0 bytes from camera; restarting it\n");
|
|
camera_set("capture", &value);
|
|
sleep(3);
|
|
value = 1;
|
|
camera_set("capture", &value);
|
|
}
|
|
else
|
|
i = 0;
|
|
CAMERA_CHECK_GP(res, "gp_file_get_data_and_size");
|
|
|
|
if(jpeg_buffer_size <= xsize) {
|
|
jpeg_buffer_size = xsize + xsize * 10/100;
|
|
unsigned char *tmp_buff = realloc(global->in[plugin_id].buf,jpeg_buffer_size);
|
|
if(tmp_buff == NULL)
|
|
{
|
|
IPRINT(INPUT_PLUGIN_NAME " - could not allocate memory\n");
|
|
return NULL;
|
|
}
|
|
global->in[plugin_id].buf = tmp_buff;
|
|
}
|
|
|
|
|
|
memcpy(global->in[plugin_id].buf, xdata, xsize);
|
|
res = gp_file_unref(file);
|
|
pthread_mutex_unlock(&control_mutex);
|
|
CAMERA_CHECK_GP(res, "gp_file_unref");
|
|
global->in[plugin_id].size = xsize;
|
|
DBG("Read %d bytes from camera.\n", global->in[plugin_id].size);
|
|
pthread_cond_broadcast(&global->in[plugin_id].db_update);
|
|
pthread_mutex_unlock(&global->in[plugin_id].db);
|
|
usleep(delay);
|
|
}
|
|
pthread_cleanup_pop(1);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int camera_set(char* name, void* value)
|
|
{
|
|
int res;
|
|
|
|
CameraWidget* config_root;
|
|
CameraWidget* widget;
|
|
res = gp_camera_get_config(camera, &config_root, context);
|
|
CAMERA_CHECK_GP(res, "gp_camera_get_config");
|
|
res = gp_widget_get_child_by_name(config_root, name, &widget);
|
|
CAMERA_CHECK_GP(res, "gp_widget_get_child_by_name");
|
|
res = gp_widget_set_value(widget, value);
|
|
CAMERA_CHECK_GP(res, "gp_widget_set_value");
|
|
res = gp_camera_set_config(camera, config_root, context);
|
|
CAMERA_CHECK_GP(res, "gp_camera_set_config");
|
|
gp_widget_unref(config_root);
|
|
return 1;
|
|
}
|
|
|
|
void cleanup(void *arg)
|
|
{
|
|
int value = 0;
|
|
|
|
// TODO check to see if we have already cleaned up?
|
|
|
|
IPRINT("PTP2 capture - Cleaning up\n");
|
|
camera_set("capture", &value);
|
|
gp_camera_exit(camera, context);
|
|
gp_camera_unref(camera);
|
|
gp_context_unref(context);
|
|
free(global->in[plugin_id].buf);
|
|
}
|
|
|
|
int input_cmd(int plugin, unsigned int control_id, unsigned int group, int value)
|
|
{
|
|
int res;
|
|
int i;
|
|
DBG("Requested cmd (id: %d) for the %d plugin. Group: %d value: %d\n", control_id, plugin_id, group, value);
|
|
switch(group)
|
|
{
|
|
case IN_CMD_GENERIC:
|
|
for(i = 0; i < global->in[plugin_id].parametercount; i++)
|
|
{
|
|
if((global->in[plugin_id].in_parameters[i].ctrl.id == control_id) && (global->in[plugin_id].in_parameters[i].group == IN_CMD_GENERIC))
|
|
{
|
|
DBG("Generic control found (id: %d): %s\n", control_id, global->in[plugin_id].in_parameters[i].ctrl.name);
|
|
if(control_id == 1)
|
|
{
|
|
float z = value;
|
|
pthread_mutex_lock(&control_mutex);
|
|
res = camera_set("zoom", &z);
|
|
pthread_mutex_unlock(&control_mutex);
|
|
} DBG("New %s value: %d\n", global->in[plugin_id].in_parameters[i].ctrl.name, value);
|
|
return 0;
|
|
}
|
|
}
|
|
DBG("Requested generic control (%d) did not found\n", control_id);
|
|
return -1;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|