Files
motion/motion.c

2796 lines
102 KiB
C

/* motion.c
*
* Detect changes in a video stream.
* Copyright 2000 by Jeroen Vreeken (pe1rxq@amsat.org)
* This software is distributed under the GNU public license version 2
* See also the file 'COPYING'.
*
*/
#include "ffmpeg.h"
#include "motion.h"
#if (defined(BSD) && !defined(PWCBSD))
#include "video_freebsd.h"
#else
#include "video.h"
#endif /* BSD */
#include "conf.h"
#include "alg.h"
#include "track.h"
#include "event.h"
#include "picture.h"
#include "rotate.h"
/* Forward declarations */
static int motion_init(struct context *cnt);
static void motion_cleanup(struct context *cnt);
/**
* tls_key_threadnr
*
* TLS key for storing thread number in thread-local storage.
*/
pthread_key_t tls_key_threadnr;
/**
* global_lock
*
* Protects any global variables (like 'threads_running') during updates,
* to prevent problems with multiple threads updating at the same time.
*/
//pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t global_lock;
/**
* cnt_list
*
* List of context structures, one for each main Motion thread.
*/
struct context **cnt_list = NULL;
/**
* threads_running
*
* Keeps track of number of Motion threads currently running. Also used
* by 'main' to know when all threads have exited.
*/
volatile int threads_running = 0;
/*
* debug_level is for developers, normally used to control which
* types of messages get output.
*/
unsigned short int debug_level;
/* Set this when we want main to end or restart
*/
volatile unsigned short int finish = 0;
/**
* restart
*
* Differentiates between a quit and a restart. When all threads have
* finished running, 'main' checks if 'restart' is true and if so starts
* up again (instead of just quitting).
*/
unsigned short int restart = 0;
/**
* image_ring_resize
*
* This routine is called from motion_loop to resize the image precapture ringbuffer
* NOTE: This function clears all images in the old ring buffer
* Parameters:
*
* cnt Pointer to the motion context structure
* new_size The new size of the ring buffer
*
* Returns: nothing
*/
static void image_ring_resize(struct context *cnt, int new_size)
{
/* Only resize if :
* Not in an event and
* decreasing at last position in new buffer
* increasing at last position in old buffer
* e.g. at end of smallest buffer */
if (cnt->event_nr != cnt->prev_event) {
int smallest;
if (new_size < cnt->imgs.image_ring_size) { /* Decreasing */
smallest = new_size;
} else { /* Increasing */
smallest = cnt->imgs.image_ring_size;
}
if (cnt->imgs.image_ring_in == smallest - 1 || smallest == 0) {
motion_log(LOG_INFO, 0, "Resizing pre_capture buffer to %d items", new_size);
/* Create memory for new ring buffer */
struct image_data *tmp;
tmp = mymalloc(new_size * sizeof(struct image_data));
/* Copy all information from old to new
* Smallest is 0 at initial init */
if (smallest > 0)
memcpy(tmp, cnt->imgs.image_ring, sizeof(struct image_data) * smallest);
/* In the new buffers, allocate image memory */
{
int i;
for(i = smallest; i < new_size; i++) {
tmp[i].image = mymalloc(cnt->imgs.size);
memset(tmp[i].image, 0x80, cnt->imgs.size); /* initialize to grey */
}
}
/* Free the old ring */
free(cnt->imgs.image_ring);
/* Point to the new ring */
cnt->imgs.image_ring = tmp;
cnt->imgs.image_ring_size = new_size;
}
}
}
/**
* image_ring_destroy
*
* This routine is called when we want to free the ring
*
* Parameters:
*
* cnt Pointer to the motion context structure
*
* Returns: nothing
*/
static void image_ring_destroy(struct context *cnt)
{
unsigned short int i;
/* Exit if don't have any ring */
if (cnt->imgs.image_ring == NULL)
return;
/* Free all image buffers */
for (i = 0; i < cnt->imgs.image_ring_size; i++)
free(cnt->imgs.image_ring[i].image);
/* Free the ring */
free(cnt->imgs.image_ring);
cnt->imgs.image_ring = NULL;
cnt->imgs.image_ring_size = 0;
}
/**
* image_save_as_preview
*
* This routine is called when we detect motion and want to save a image in the preview buffer
*
* Parameters:
*
* cnt Pointer to the motion context structure
* img Pointer to the image_data structure we want to set as preview image
*
* Returns: nothing
*/
static void image_save_as_preview(struct context *cnt, struct image_data *img)
{
void * image;
/* Save preview image pointer */
image = cnt->imgs.preview_image.image;
/* Copy all info */
memcpy(&cnt->imgs.preview_image.image, img, sizeof(struct image_data));
/* restore image pointer */
cnt->imgs.preview_image.image = image;
/* Copy image */
memcpy(cnt->imgs.preview_image.image, img->image, cnt->imgs.size);
/* If we set output_all to yes and during the event
* there is no image with motion, diffs is 0, we are not going to save the preview event */
if (cnt->imgs.preview_image.diffs == 0)
cnt->imgs.preview_image.diffs = 1;
/* If we have locate on it is already done */
if (cnt->locate == LOCATE_PREVIEW)
alg_draw_location(&img->location, &cnt->imgs, cnt->imgs.width,
cnt->imgs.preview_image.image, LOCATE_NORMAL);
}
/**
* context_init
*
* Initializes a context struct with the default values for all the
* variables.
*
* Parameters:
*
* cnt - the context struct to destroy
*
* Returns: nothing
*/
static void context_init (struct context *cnt)
{
/*
* We first clear the entire structure to zero, then fill in any
* values which have non-zero default values. Note that this
* assumes that a NULL address pointer has a value of binary 0
* (this is also assumed at other places within the code, i.e.
* there are instances of "if (ptr)"). Just for possible future
* changes to this assumption, any pointers which are intended
* to be initialised to NULL are listed within a comment.
*/
memset(cnt, 0, sizeof(struct context));
cnt->noise = 255;
cnt->lastrate = 25;
memcpy(&cnt->track, &track_template, sizeof(struct trackoptions));
cnt->pipe = -1;
cnt->mpipe = -1;
}
/**
* context_destroy
*
* Destroys a context struct by freeing allocated memory, calling the
* appropriate cleanup functions and finally freeing the struct itself.
*
* Parameters:
*
* cnt - the context struct to destroy
*
* Returns: nothing
*/
static void context_destroy(struct context *cnt)
{
unsigned short int j;
/* Free memory allocated for config parameters */
for (j = 0; config_params[j].param_name != NULL; j++) {
if (config_params[j].copy == copy_string) {
void **val;
val = (void *)((char *)cnt+(int)config_params[j].conf_value);
if (*val) {
free(*val);
*val = NULL;
}
}
}
free(cnt);
}
/**
* sig_handler
*
* Our SIGNAL-Handler. We need this to handle alarms and external signals.
*/
static void sig_handler(int signo)
{
short int i;
switch(signo) {
case SIGALRM:
/* Somebody (maybe we ourself) wants us to make a snapshot
* This feature triggers snapshots on ALL threads that have
* snapshot_interval different from 0.
*/
if (cnt_list) {
i = -1;
while (cnt_list[++i]) {
if (cnt_list[i]->conf.snapshot_interval)
cnt_list[i]->snapshot = 1;
}
}
break;
case SIGUSR1:
/* Ouch! We have been hit from the outside! Someone wants us to
make a movie! */
if (cnt_list) {
i = -1;
while (cnt_list[++i])
cnt_list[i]->makemovie = 1;
}
break;
case SIGHUP:
restart = 1;
/* Fall through, as the value of 'restart' is the only difference
* between SIGHUP and the ones below.
*/
case SIGINT:
case SIGQUIT:
case SIGTERM:
/* Somebody wants us to quit! We should better finish the actual
movie and end up! */
if (cnt_list) {
i = -1;
while (cnt_list[++i]) {
cnt_list[i]->makemovie = 1;
cnt_list[i]->finish = 1;
/* don't restart thread when it ends,
* all threads restarts if global restart is set
*/
cnt_list[i]->restart = 0;
}
}
/* Set flag we want to quit main check threads loop
* if restart is set (above) we start up again */
finish = 1;
break;
case SIGSEGV:
exit(0);
}
}
/**
* sigchild_handler
*
* This function is a POSIX compliant replacement of the commonly used
* signal(SIGCHLD, SIG_IGN).
*/
static void sigchild_handler(int signo ATTRIBUTE_UNUSED)
{
#ifdef WNOHANG
while (waitpid(-1, NULL, WNOHANG) > 0) {};
#endif /* WNOHANG */
return;
}
/**
* motion_remove_pid
*
* This function remove the process id file ( pid file ) before motion exit.
*
*/
static void motion_remove_pid(void)
{
if ((cnt_list[0]->daemon) && (cnt_list[0]->conf.pid_file) && (restart == 0)) {
if (!unlink(cnt_list[0]->conf.pid_file))
motion_log(LOG_INFO, 0, "Removed process id file (pid file).");
else
motion_log(LOG_INFO, 1, "Error removing pid file");
}
}
/**
* motion_detected
*
* Called from 'motion_loop' when motion is detected
* Can be called when no motion if output_all is set!
*
* Parameters:
*
* cnt - current thread's context struct
* dev - video device file descriptor
* img - pointer to the captured image_data with detected motion
*/
static void motion_detected(struct context *cnt, int dev, struct image_data *img)
{
struct config *conf = &cnt->conf;
struct images *imgs = &cnt->imgs;
struct coord *location = &img->location;
/* Draw location */
if (cnt->locate == LOCATE_ON)
alg_draw_location(location, imgs, imgs->width, img->image, LOCATE_BOTH);
/* Calculate how centric motion is if configured preview center*/
if (cnt->new_img & NEWIMG_CENTER) {
unsigned int distX = abs((imgs->width/2) - location->x);
unsigned int distY = abs((imgs->height/2) - location->y);
img->cent_dist = distX*distX + distY*distY;
}
/* Do things only if we have got minimum_motion_frames */
if (img->flags & IMAGE_TRIGGER) {
/* Take action if this is a new event and we have a trigger image */
if (cnt->event_nr != cnt->prev_event) {
/* Reset prev_event number to current event and save event time
* in both time_t and struct tm format.
*/
cnt->prev_event = cnt->event_nr;
cnt->eventtime = img->timestamp;
localtime_r(&cnt->eventtime, cnt->eventtime_tm);
/* Since this is a new event we create the event_text_string used for
* the %C conversion specifier. We may already need it for
* on_motion_detected_commend so it must be done now.
*/
mystrftime(cnt, cnt->text_event_string, sizeof(cnt->text_event_string),
cnt->conf.text_event, cnt->eventtime_tm, NULL, 0);
/* EVENT_FIRSTMOTION triggers on_event_start_command and event_ffmpeg_newfile */
event(cnt, EVENT_FIRSTMOTION, img->image, NULL, NULL, &img->timestamp_tm);
if (cnt->conf.setup_mode)
motion_log(-1, 0, "Motion detected - starting event %d", cnt->event_nr);
/* always save first motion frame as preview-shot, may be changed to an other one later */
if (cnt->new_img & (NEWIMG_FIRST | NEWIMG_BEST | NEWIMG_CENTER))
image_save_as_preview(cnt, img);
}
/* EVENT_MOTION triggers event_beep and on_motion_detected_command */
event(cnt, EVENT_MOTION, NULL, NULL, NULL, &img->timestamp_tm);
}
/* Limit framerate */
if (img->shot < conf->frame_limit) {
/* If config option webcam_motion is enabled, send the latest motion detected image
* to the webcam but only if it is not the first shot within a second. This is to
* avoid double frames since we already have sent a frame to the webcam.
* We also disable this in setup_mode.
*/
if (conf->webcam_motion && !conf->setup_mode && img->shot != 1)
event(cnt, EVENT_WEBCAM, img->image, NULL, NULL, &img->timestamp_tm);
/* Save motion jpeg, if configured */
/* Output the image_out (motion) picture. */
if (conf->motion_img)
event(cnt, EVENT_IMAGEM_DETECTED, NULL, NULL, NULL, &img->timestamp_tm);
}
if (cnt->track.type)
cnt->moved = track_move(cnt, dev, location, imgs, 0);
}
/**
* process_image_ring
*
* Called from 'motion_loop' to save images / send images to movie
*
* Parameters:
*
* cnt - current thread's context struct
* max_images - Max number of images to process
* Set to IMAGE_BUFFER_FLUSH to send/save all images in buffer
*/
#define IMAGE_BUFFER_FLUSH ((unsigned int)-1)
static void process_image_ring(struct context *cnt, unsigned int max_images)
{
/* we are going to send an event, in the events there is still
* some code that use cnt->current_image
* so set it temporary to our image */
struct image_data *saved_current_image = cnt->current_image;
/* If image is flaged to be saved and not saved yet, process it */
do {
/* Check if we should save/send this image, breakout if not */
if ((cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags &
(IMAGE_SAVE | IMAGE_SAVED)) != IMAGE_SAVE)
break;
/* Set inte global cotext that we are working with this image */
cnt->current_image = &cnt->imgs.image_ring[cnt->imgs.image_ring_out];
if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].shot < cnt->conf.frame_limit) {
/* Output the picture to jpegs and ffmpeg */
event(cnt, EVENT_IMAGE_DETECTED,
cnt->imgs.image_ring[cnt->imgs.image_ring_out].image, NULL, NULL,
&cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tm);
}
/* Mark the image as saved */
cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags |= IMAGE_SAVED;
/* Store it as a preview image, only if it have motion */
if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_MOTION) {
/* Check for most significant preview-shot when output_normal=best */
if (cnt->new_img & NEWIMG_BEST) {
if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].diffs > cnt->imgs.preview_image.diffs)
image_save_as_preview(cnt, &cnt->imgs.image_ring[cnt->imgs.image_ring_out]);
}
/* Check for most significant preview-shot when output_normal=center */
if (cnt->new_img & NEWIMG_CENTER) {
if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].cent_dist < cnt->imgs.preview_image.cent_dist)
image_save_as_preview(cnt, &cnt->imgs.image_ring[cnt->imgs.image_ring_out]);
}
}
/* Increment to image after last sended */
if (++cnt->imgs.image_ring_out >= cnt->imgs.image_ring_size)
cnt->imgs.image_ring_out = 0;
if (max_images != IMAGE_BUFFER_FLUSH) {
max_images--;
/* breakout if we have done max_images */
if (max_images == 0)
break;
}
/* loop until out and in is same e.g. buffer empty */
} while (cnt->imgs.image_ring_out != cnt->imgs.image_ring_in);
/* restore global context values */
cnt->current_image = saved_current_image;
}
/**
* motion_init
*
* This routine is called from motion_loop (the main thread of the program) to do
* all of the initialisation required before starting the actual run.
*
* Parameters:
*
* cnt Pointer to the motion context structure
*
* Returns: 0 OK
* -1 Fatal error, open loopback error
* -2 Fatal error, open SQL database error
*/
static int motion_init(struct context *cnt)
{
int i;
FILE *picture;
/* Store thread number in TLS. */
pthread_setspecific(tls_key_threadnr, (void *)((unsigned long)cnt->threadnr));
cnt->currenttime_tm = mymalloc(sizeof(struct tm));
cnt->eventtime_tm = mymalloc(sizeof(struct tm));
/* Init frame time */
cnt->currenttime = time(NULL);
localtime_r(&cnt->currenttime, cnt->currenttime_tm);
cnt->smartmask_speed = 0;
/* We initialize cnt->event_nr to 1 and cnt->prev_event to 0 (not really needed) so
* that certain code below does not run until motion has been detected the first time */
cnt->event_nr = 1;
cnt->prev_event = 0;
cnt->lightswitch_framecounter = 0;
cnt->detecting_motion = 0;
cnt->makemovie = 0;
motion_log(LOG_DEBUG, 0, "Thread %d started", (unsigned long)pthread_getspecific(tls_key_threadnr));
if (!cnt->conf.filepath)
cnt->conf.filepath = strdup(".");
/* set the device settings */
cnt->video_dev = vid_start(cnt);
/* We failed to get an initial image from a camera
* So we need to guess height and width based on the config
* file options.
*/
if (cnt->video_dev < 0) {
motion_log(LOG_ERR, 0, "Could not fetch initial image from camera");
motion_log(LOG_ERR, 0, "Motion continues using width and height from config file(s)");
cnt->imgs.width = cnt->conf.width;
cnt->imgs.height = cnt->conf.height;
cnt->imgs.size = cnt->conf.width * cnt->conf.height * 3 / 2;
cnt->imgs.motionsize = cnt->conf.width * cnt->conf.height;
cnt->imgs.type = VIDEO_PALETTE_YUV420P;
}
image_ring_resize(cnt, 1); /* Create a initial precapture ring buffer with 1 frame */
cnt->imgs.ref = mymalloc(cnt->imgs.size);
cnt->imgs.out = mymalloc(cnt->imgs.size);
memset(cnt->imgs.out, 0, cnt->imgs.size);
/* contains the moving objects of ref. frame */
cnt->imgs.ref_dyn = mymalloc(cnt->imgs.motionsize * sizeof(cnt->imgs.ref_dyn));
cnt->imgs.image_virgin = mymalloc(cnt->imgs.size);
cnt->imgs.smartmask = mymalloc(cnt->imgs.motionsize);
cnt->imgs.smartmask_final = mymalloc(cnt->imgs.motionsize);
cnt->imgs.smartmask_buffer = mymalloc(cnt->imgs.motionsize * sizeof(cnt->imgs.smartmask_buffer));
cnt->imgs.labels = mymalloc(cnt->imgs.motionsize * sizeof(cnt->imgs.labels));
cnt->imgs.labelsize = mymalloc((cnt->imgs.motionsize/2+1) * sizeof(cnt->imgs.labelsize));
/* allocate buffer here for preview buffer */
cnt->imgs.preview_image.image = mymalloc(cnt->imgs.size);
/* Allocate a buffer for temp. usage in some places */
/* Only despeckle & bayer2rgb24() for now for now... */
cnt->imgs.common_buffer = mymalloc(3 * cnt->imgs.width * cnt->imgs.height);
/* Now is a good time to init rotation data. Since vid_start has been
* called, we know that we have imgs.width and imgs.height. When capturing
* from a V4L device, these are copied from the corresponding conf values
* in vid_start. When capturing from a netcam, they get set in netcam_start,
* which is called from vid_start.
*
* rotate_init will set cap_width and cap_height in cnt->rotate_data.
*/
rotate_init(cnt); /* rotate_deinit is called in main */
/* Capture first image, or we will get an alarm on start */
if (cnt->video_dev > 0) {
for (i = 0; i < 5; i++) {
if (vid_next(cnt, cnt->imgs.image_virgin) == 0)
break;
SLEEP(2,0);
}
if (i >= 5) {
memset(cnt->imgs.image_virgin, 0x80, cnt->imgs.size); /* initialize to grey */
draw_text(cnt->imgs.image_virgin, 10, 20, cnt->imgs.width,
"Error capturing first image", cnt->conf.text_double);
motion_log(LOG_ERR, 0, "Error capturing first image");
}
}
/* create a reference frame */
alg_update_reference_frame(cnt, RESET_REF_FRAME);
#ifndef WITHOUT_V4L
#if (!defined(BSD))
/* open video loopback devices if enabled */
if (cnt->conf.vidpipe) {
if (cnt->conf.setup_mode)
motion_log(-1, 0, "Opening video loopback device for normal pictures");
/* vid_startpipe should get the output dimensions */
cnt->pipe = vid_startpipe(cnt->conf.vidpipe, cnt->imgs.width, cnt->imgs.height, cnt->imgs.type);
if (cnt->pipe < 0) {
motion_log(LOG_ERR, 0, "Failed to open video loopback");
return -1;
}
}
if (cnt->conf.motionvidpipe) {
if (cnt->conf.setup_mode)
motion_log(-1, 0, "Opening video loopback device for motion pictures");
/* vid_startpipe should get the output dimensions */
cnt->mpipe = vid_startpipe(cnt->conf.motionvidpipe, cnt->imgs.width, cnt->imgs.height, cnt->imgs.type);
if (cnt->mpipe < 0) {
motion_log(LOG_ERR, 0, "Failed to open video loopback");
return -1;
}
}
#endif /* BSD */
#endif /*WITHOUT_V4L*/
#ifdef HAVE_MYSQL
if (cnt->conf.mysql_db) {
cnt->database = (MYSQL *) mymalloc(sizeof(MYSQL));
mysql_init(cnt->database);
if (!mysql_real_connect(cnt->database, cnt->conf.mysql_host, cnt->conf.mysql_user,
cnt->conf.mysql_password, cnt->conf.mysql_db, 0, NULL, 0)) {
motion_log(LOG_ERR, 0, "Cannot connect to MySQL database %s on host %s with user %s",
cnt->conf.mysql_db, cnt->conf.mysql_host, cnt->conf.mysql_user);
motion_log(LOG_ERR, 0, "MySQL error was %s", mysql_error(cnt->database));
return -2;
}
#if (defined(MYSQL_VERSION_ID)) && (MYSQL_VERSION_ID > 50012)
my_bool my_true = TRUE;
mysql_options(cnt->database,MYSQL_OPT_RECONNECT,&my_true);
#endif
}
#endif /* HAVE_MYSQL */
#ifdef HAVE_PGSQL
if (cnt->conf.pgsql_db) {
char connstring[255];
/* create the connection string.
Quote the values so we can have null values (blank)*/
snprintf(connstring, 255,
"dbname='%s' host='%s' user='%s' password='%s' port='%d'",
cnt->conf.pgsql_db, /* dbname */
(cnt->conf.pgsql_host ? cnt->conf.pgsql_host : ""), /* host (may be blank) */
(cnt->conf.pgsql_user ? cnt->conf.pgsql_user : ""), /* user (may be blank) */
(cnt->conf.pgsql_password ? cnt->conf.pgsql_password : ""), /* password (may be blank) */
cnt->conf.pgsql_port
);
cnt->database_pg = PQconnectdb(connstring);
if (PQstatus(cnt->database_pg) == CONNECTION_BAD) {
motion_log(LOG_ERR, 0, "Connection to PostgreSQL database '%s' failed: %s",
cnt->conf.pgsql_db, PQerrorMessage(cnt->database_pg));
return -2;
}
}
#endif /* HAVE_PGSQL */
#if defined(HAVE_MYSQL) || defined(HAVE_PGSQL)
/* Set the sql mask file according to the SQL config options*/
cnt->sql_mask = cnt->conf.sql_log_image * (FTYPE_IMAGE + FTYPE_IMAGE_MOTION) +
cnt->conf.sql_log_snapshot * FTYPE_IMAGE_SNAPSHOT +
cnt->conf.sql_log_mpeg * (FTYPE_MPEG + FTYPE_MPEG_MOTION) +
cnt->conf.sql_log_timelapse * FTYPE_MPEG_TIMELAPSE;
#endif /* defined(HAVE_MYSQL) || defined(HAVE_PGSQL) */
/* Load the mask file if any */
if (cnt->conf.mask_file) {
if ((picture = fopen(cnt->conf.mask_file, "r"))) {
/* NOTE: The mask is expected to have the output dimensions. I.e., the mask
* applies to the already rotated image, not the capture image. Thus, use
* width and height from imgs.
*/
cnt->imgs.mask = get_pgm(picture, cnt->imgs.width, cnt->imgs.height);
fclose(picture);
} else {
motion_log(LOG_ERR, 1, "Error opening mask file %s", cnt->conf.mask_file);
/* Try to write an empty mask file to make it easier
for the user to edit it */
put_fixed_mask(cnt, cnt->conf.mask_file);
}
if (!cnt->imgs.mask) {
motion_log(LOG_ERR, 0, "Failed to read mask image. Mask feature disabled.");
} else {
if (cnt->conf.setup_mode)
motion_log(-1, 0, "Maskfile \"%s\" loaded.",cnt->conf.mask_file);
}
} else {
cnt->imgs.mask = NULL;
}
/* Always initialize smart_mask - someone could turn it on later... */
memset(cnt->imgs.smartmask, 0, cnt->imgs.motionsize);
memset(cnt->imgs.smartmask_final, 255, cnt->imgs.motionsize);
memset(cnt->imgs.smartmask_buffer, 0, cnt->imgs.motionsize*sizeof(cnt->imgs.smartmask_buffer));
/* Set noise level */
cnt->noise = cnt->conf.noise;
/* Set threshold value */
cnt->threshold = cnt->conf.max_changes;
/* Initialize webcam server if webcam port is specified to not 0 */
if (cnt->conf.webcam_port) {
if (webcam_init(cnt) == -1) {
motion_log(LOG_ERR, 1, "Problem enabling stream server in port %d", cnt->conf.webcam_port);
cnt->finish = 1;
} else {
motion_log(LOG_DEBUG, 0, "Started stream webcam server in port %d", cnt->conf.webcam_port);
}
}
/* Prevent first few frames from triggering motion... */
cnt->moved = 8;
/* 2 sec startup delay so FPS is calculated correct */
cnt->startup_frames = cnt->conf.frame_limit * 2;
return 0;
}
/**
* motion_cleanup
*
* This routine is called from motion_loop when thread ends to
* cleanup all memory etc. that motion_init did.
*
* Parameters:
*
* cnt Pointer to the motion context structure
*
* Returns: nothing
*/
static void motion_cleanup(struct context *cnt)
{
/* Stop webcam */
event(cnt, EVENT_STOP, NULL, NULL, NULL, NULL);
if (cnt->video_dev >= 0) {
motion_log(LOG_DEBUG, 0, "Calling vid_close() from motion_cleanup");
vid_close(cnt);
}
if (cnt->imgs.out) {
free(cnt->imgs.out);
cnt->imgs.out = NULL;
}
if (cnt->imgs.ref) {
free(cnt->imgs.ref);
cnt->imgs.ref = NULL;
}
if (cnt->imgs.ref_dyn) {
free(cnt->imgs.ref_dyn);
cnt->imgs.ref_dyn = NULL;
}
if (cnt->imgs.image_virgin) {
free(cnt->imgs.image_virgin);
cnt->imgs.image_virgin = NULL;
}
if (cnt->imgs.labels) {
free(cnt->imgs.labels);
cnt->imgs.labels = NULL;
}
if (cnt->imgs.labelsize) {
free(cnt->imgs.labelsize);
cnt->imgs.labelsize = NULL;
}
if (cnt->imgs.smartmask) {
free(cnt->imgs.smartmask);
cnt->imgs.smartmask = NULL;
}
if (cnt->imgs.smartmask_final) {
free(cnt->imgs.smartmask_final);
cnt->imgs.smartmask_final = NULL;
}
if (cnt->imgs.smartmask_buffer) {
free(cnt->imgs.smartmask_buffer);
cnt->imgs.smartmask_buffer = NULL;
}
if (cnt->imgs.common_buffer) {
free(cnt->imgs.common_buffer);
cnt->imgs.common_buffer = NULL;
}
if (cnt->imgs.preview_image.image) {
free(cnt->imgs.preview_image.image);
cnt->imgs.preview_image.image = NULL;
}
image_ring_destroy(cnt); /* Cleanup the precapture ring buffer */
rotate_deinit(cnt); /* cleanup image rotation data */
if (cnt->pipe != -1) {
close(cnt->pipe);
cnt->pipe = -1;
}
if (cnt->mpipe != -1) {
close(cnt->mpipe);
cnt->mpipe = -1;
}
/* Cleanup the current time structure */
if (cnt->currenttime_tm) {
free(cnt->currenttime_tm);
cnt->currenttime_tm = NULL;
}
/* Cleanup the event time structure */
if (cnt->eventtime_tm) {
free(cnt->eventtime_tm);
cnt->eventtime_tm = NULL;
}
}
/**
* motion_loop
*
* Thread function for the motion handling threads.
*
*/
static void *motion_loop(void *arg)
{
struct context *cnt = arg;
int i, j, z = 0;
time_t lastframetime = 0;
int frame_buffer_size;
unsigned short int ref_frame_limit = 0;
int area_once = 0;
int area_minx[9], area_miny[9], area_maxx[9], area_maxy[9];
int smartmask_ratio = 0;
int smartmask_count = 20;
int smartmask_lastrate = 0;
int olddiffs = 0;
int previous_diffs = 0, previous_location_x = 0, previous_location_y = 0;
unsigned short int text_size_factor;
unsigned short int passflag = 0;
long int *rolling_average_data = NULL;
long int rolling_average_limit, required_frame_time, frame_delay, delay_time_nsec;
int rolling_frame = 0;
struct timeval tv1, tv2;
unsigned long int rolling_average, elapsedtime;
unsigned long long int timenow = 0, timebefore = 0;
/* Return code used when calling vid_next */
int vid_return_code = 0;
/* time in seconds to skip between capturing images */
int minimum_frame_time_downcounter = cnt->conf.minimum_frame_time;
/* Flag used to signal that we capture new image when we run the loop */
unsigned short int get_image = 1;
/* Next two variables are used for snapshot and timelapse feature
* time_last_frame is set to 1 so that first coming timelapse or second=0
* is acted upon.
*/
unsigned long int time_last_frame=1, time_current_frame;
cnt->running = 1;
if (motion_init(cnt) < 0)
goto err;
/* Initialize the double sized characters if needed. */
if (cnt->conf.text_double)
text_size_factor = 2;
else
text_size_factor = 1;
/* Initialize area detection */
area_minx[0] = area_minx[3] = area_minx[6] = 0;
area_miny[0] = area_miny[1] = area_miny[2] = 0;
area_minx[1] = area_minx[4] = area_minx[7] = cnt->imgs.width / 3;
area_maxx[0] = area_maxx[3] = area_maxx[6] = cnt->imgs.width / 3;
area_minx[2] = area_minx[5] = area_minx[8] = cnt->imgs.width / 3 * 2;
area_maxx[1] = area_maxx[4] = area_maxx[7] = cnt->imgs.width / 3 * 2;
area_miny[3] = area_miny[4] = area_miny[5] = cnt->imgs.height / 3;
area_maxy[0] = area_maxy[1] = area_maxy[2] = cnt->imgs.height / 3;
area_miny[6] = area_miny[7] = area_miny[8] = cnt->imgs.height / 3 * 2;
area_maxy[3] = area_maxy[4] = area_maxy[5] = cnt->imgs.height / 3 * 2;
area_maxx[2] = area_maxx[5] = area_maxx[8] = cnt->imgs.width;
area_maxy[6] = area_maxy[7] = area_maxy[8] = cnt->imgs.height;
/* Work out expected frame rate based on config setting */
if (cnt->conf.frame_limit < 2)
cnt->conf.frame_limit = 2;
required_frame_time = 1000000L / cnt->conf.frame_limit;
frame_delay = required_frame_time;
/*
* Reserve enough space for a 10 second timing history buffer. Note that,
* if there is any problem on the allocation, mymalloc does not return.
*/
rolling_average_limit = 10 * cnt->conf.frame_limit;
rolling_average_data = mymalloc(sizeof(rolling_average_data) * rolling_average_limit);
/* Preset history buffer with expected frame rate */
for (j = 0; j < rolling_average_limit; j++)
rolling_average_data[j] = required_frame_time;
/* MAIN MOTION LOOP BEGINS HERE */
/* Should go on forever... unless you bought vaporware :) */
while (!cnt->finish || cnt->makemovie) {
/***** MOTION LOOP - PREPARE FOR NEW FRAME SECTION *****/
cnt->watchdog = WATCHDOG_TMO;
/* Get current time and preserver last time for frame interval calc. */
timebefore = timenow;
gettimeofday(&tv1, NULL);
timenow = tv1.tv_usec + 1000000L * tv1.tv_sec;
/* since we don't have sanity checks done when options are set,
* this sanity check must go in the main loop :(, before pre_captures
* are attempted. */
if (cnt->conf.minimum_motion_frames < 1)
cnt->conf.minimum_motion_frames = 1;
if (cnt->conf.pre_capture < 0)
cnt->conf.pre_capture = 0;
/* Check if our buffer is still the right size
* If pre_capture or minimum_motion_frames has been changed
* via the http remote control we need to re-size the ring buffer
*/
frame_buffer_size = cnt->conf.pre_capture + cnt->conf.minimum_motion_frames;
if (cnt->imgs.image_ring_size != frame_buffer_size)
image_ring_resize(cnt, frame_buffer_size);
/* Get time for current frame */
cnt->currenttime = time(NULL);
/* localtime returns static data and is not threadsafe
* so we use localtime_r which is reentrant and threadsafe
*/
localtime_r(&cnt->currenttime, cnt->currenttime_tm);
/* If we have started on a new second we reset the shots variable
* lastrate is updated to be the number of the last frame. last rate
* is used as the ffmpeg framerate when motion is detected.
*/
if (lastframetime != cnt->currenttime) {
cnt->lastrate = cnt->shots + 1;
cnt->shots = -1;
lastframetime = cnt->currenttime;
if (cnt->conf.minimum_frame_time) {
minimum_frame_time_downcounter--;
if (minimum_frame_time_downcounter == 0)
get_image = 1;
} else {
get_image = 1;
}
}
/* Increase the shots variable for each frame captured within this second */
cnt->shots++;
if (cnt->startup_frames > 0)
cnt->startup_frames--;
if (get_image){
if (cnt->conf.minimum_frame_time) {
minimum_frame_time_downcounter = cnt->conf.minimum_frame_time;
get_image = 0;
}
/* ring_buffer_in is pointing to current pos, update before put in a new image */
if (++cnt->imgs.image_ring_in >= cnt->imgs.image_ring_size)
cnt->imgs.image_ring_in = 0;
/* Check if we have filled the ring buffer, throw away last image */
if (cnt->imgs.image_ring_in == cnt->imgs.image_ring_out) {
if (++cnt->imgs.image_ring_out >= cnt->imgs.image_ring_size)
cnt->imgs.image_ring_out = 0;
}
/* cnt->current_image points to position in ring where to store image, diffs etc. */
cnt->current_image = &cnt->imgs.image_ring[cnt->imgs.image_ring_in];
/* Init/clear current_image */
{
/* Store time with pre_captured image */
cnt->current_image->timestamp = cnt->currenttime;
localtime_r(&cnt->current_image->timestamp, &cnt->current_image->timestamp_tm);
/* Store shot number with pre_captured image */
cnt->current_image->shot = cnt->shots;
/* set diffs to 0 now, will be written after we calculated diffs in new image */
cnt->current_image->diffs = 0;
/* Set flags to 0 */
cnt->current_image->flags = 0;
cnt->current_image->cent_dist = 0;
/* Clear location data */
memset(&cnt->current_image->location, 0, sizeof(cnt->current_image->location));
cnt->current_image->total_labels = 0;
}
/***** MOTION LOOP - RETRY INITIALIZING SECTION *****/
/* If a camera is not available we keep on retrying every 10 seconds
* until it shows up.
*/
if (cnt->video_dev < 0 &&
cnt->currenttime % 10 == 0 && cnt->shots == 0) {
motion_log(LOG_ERR, 0,
"Retrying until successful connection with camera");
cnt->video_dev = vid_start(cnt);
/* if the netcam has different dimensions than in the config file
* we need to restart Motion to re-allocate all the buffers
*/
if (cnt->imgs.width != cnt->conf.width || cnt->imgs.height != cnt->conf.height) {
motion_log(LOG_ERR, 0, "Camera has finally become available");
motion_log(LOG_ERR, 0, "Camera image has different width and height "
"from what is in the config file. You should fix that");
motion_log(LOG_ERR, 0, "Restarting Motion thread to reinitialize all "
"image buffers to new picture dimensions");
cnt->conf.width = cnt->imgs.width;
cnt->conf.height = cnt->imgs.height;
/* Break out of main loop terminating thread
* watchdog will start us again */
break;
}
}
/***** MOTION LOOP - IMAGE CAPTURE SECTION *****/
/* Fetch next frame from camera
* If vid_next returns 0 all is well and we got a new picture
* Any non zero value is an error.
* 0 = OK, valid picture
* <0 = fatal error - leave the thread by breaking out of the main loop
* >0 = non fatal error - copy last image or show grey image with message
*/
if (cnt->video_dev >= 0)
vid_return_code = vid_next(cnt, cnt->current_image->image);
else
vid_return_code = 1; /* Non fatal error */
// VALID PICTURE
if (vid_return_code == 0) {
cnt->lost_connection = 0;
cnt->connectionlosttime = 0;
/* If all is well reset missing_frame_counter */
if (cnt->missing_frame_counter >= MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit)
/* If we previously logged starting a grey image, now log video re-start */
motion_log(LOG_ERR, 0, "Video signal re-acquired");
// event for re-acquired video signal can be called here
cnt->missing_frame_counter = 0;
#ifdef HAVE_FFMPEG
/* Deinterlace the image with ffmpeg, before the image is modified. */
if (cnt->conf.ffmpeg_deinterlace)
ffmpeg_deinterlace(cnt->current_image->image, cnt->imgs.width, cnt->imgs.height);
#endif
/* save the newly captured still virgin image to a buffer
* which we will not alter with text and location graphics
*/
memcpy(cnt->imgs.image_virgin, cnt->current_image->image, cnt->imgs.size);
/* If the camera is a netcam we let the camera decide the pace.
* Otherwise we will keep on adding duplicate frames.
* By resetting the timer the framerate becomes maximum the rate
* of the Netcam.
*/
if (cnt->conf.netcam_url) {
gettimeofday(&tv1, NULL);
timenow = tv1.tv_usec + 1000000L * tv1.tv_sec;
}
// FATAL ERROR - leave the thread by breaking out of the main loop
} else if (vid_return_code < 0) {
/* Fatal error - Close video device */
motion_log(LOG_ERR, 0, "Video device fatal error - Closing video device");
vid_close(cnt);
/* Use virgin image, if we are not able to open it again next loop
* a gray image with message is applied
* flag lost_connection
*/
memcpy(cnt->current_image->image, cnt->imgs.image_virgin, cnt->imgs.size);
cnt->lost_connection = 1;
/* NO FATAL ERROR -
* copy last image or show grey image with message
* flag on lost_connection if :
* vid_return_code == NETCAM_RESTART_ERROR
* cnt->video_dev < 0
* cnt->missing_frame_counter > (MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit)
*/
} else {
if (debug_level >= CAMERA_VERBOSE)
motion_log(-1, 0, "vid_return_code %d", vid_return_code);
/* Netcams that change dimensions while Motion is running will
* require that Motion restarts to reinitialize all the many
* buffers inside Motion. It will be a mess to try and recover any
* other way
*/
if (vid_return_code == NETCAM_RESTART_ERROR) {
motion_log(LOG_ERR, 0, "Restarting Motion thread to reinitialize all "
"image buffers");
/* Break out of main loop terminating thread
* watchdog will start us again
* Set lost_connection flag on */
cnt->lost_connection = 1;
break;
}
/* First missed frame - store timestamp
* Don't reset time when thread restarts*/
if (cnt->connectionlosttime == 0)
cnt->connectionlosttime = cnt->currenttime;
/* Increase missing_frame_counter
* The first MISSING_FRAMES_TIMEOUT seconds we copy previous virgin image
* After MISSING_FRAMES_TIMEOUT seconds we put a grey error image in the buffer
* If we still have not yet received the initial image from a camera
* we go straight for the grey error image.
*/
++cnt->missing_frame_counter;
if (cnt->video_dev >= 0 &&
cnt->missing_frame_counter < (MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit)) {
memcpy(cnt->current_image->image, cnt->imgs.image_virgin, cnt->imgs.size);
} else {
const char *tmpin;
char tmpout[80];
struct tm tmptime;
cnt->lost_connection = 1;
if (cnt->video_dev >= 0)
tmpin = "CONNECTION TO CAMERA LOST\\nSINCE %Y-%m-%d %T";
else
tmpin = "UNABLE TO OPEN VIDEO DEVICE\\nSINCE %Y-%m-%d %T";
localtime_r(&cnt->connectionlosttime, &tmptime);
memset(cnt->current_image->image, 0x80, cnt->imgs.size);
mystrftime(cnt, tmpout, sizeof(tmpout), tmpin, &tmptime, NULL, 0);
draw_text(cnt->current_image->image, 10, 20 * text_size_factor, cnt->imgs.width,
tmpout, cnt->conf.text_double);
/* Write error message only once */
if (cnt->missing_frame_counter == MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit) {
motion_log(LOG_ERR, 0, "Video signal lost - Adding grey image");
// Event for lost video signal can be called from here
event(cnt, EVENT_CAMERA_LOST, NULL, NULL,
NULL, cnt->currenttime_tm);
}
/* If we don't get a valid frame for a long time, try to close/reopen device
* Only try this when a device is open */
if ((cnt->video_dev > 0) &&
(cnt->missing_frame_counter == (MISSING_FRAMES_TIMEOUT * 4) * cnt->conf.frame_limit)) {
motion_log(LOG_ERR, 0, "Video signal still lost - Trying to close video device");
vid_close(cnt);
}
}
}
/***** MOTION LOOP - MOTION DETECTION SECTION *****/
/* The actual motion detection takes place in the following
* diffs is the number of pixels detected as changed
* Make a differences picture in image_out
*
* alg_diff_standard is the slower full feature motion detection algorithm
* alg_diff first calls a fast detection algorithm which only looks at a
* fraction of the pixels. If this detects possible motion alg_diff_standard
* is called.
*/
if (cnt->threshold && !cnt->pause) {
/* if we've already detected motion and we want to see if there's
* still motion, don't bother trying the fast one first. IF there's
* motion, the alg_diff will trigger alg_diff_standard
* anyway
*/
if (cnt->detecting_motion || cnt->conf.setup_mode)
cnt->current_image->diffs = alg_diff_standard(cnt, cnt->imgs.image_virgin);
else
cnt->current_image->diffs = alg_diff(cnt, cnt->imgs.image_virgin);
/* Lightswitch feature - has light intensity changed?
* This can happen due to change of light conditions or due to a sudden change of the camera
* sensitivity. If alg_lightswitch detects lightswitch we suspend motion detection the next
* 5 frames to allow the camera to settle.
* Don't check if we have lost connection, we detect "Lost signal" frame as lightswitch
*/
if (cnt->conf.lightswitch && !cnt->lost_connection) {
if (alg_lightswitch(cnt, cnt->current_image->diffs)) {
if (cnt->conf.setup_mode)
motion_log(-1, 0, "Lightswitch detected");
if (cnt->moved < 5)
cnt->moved = 5;
cnt->current_image->diffs = 0;
alg_update_reference_frame(cnt, RESET_REF_FRAME);
}
}
/* Switchfilter feature tries to detect a change in the video signal
* from one camera to the next. This is normally used in the Round
* Robin feature. The algorithm is not very safe.
* The algorithm takes a little time so we only call it when needed
* ie. when feature is enabled and diffs>threshold.
* We do not suspend motion detection like we did for lightswitch
* because with Round Robin this is controlled by roundrobin_skip.
*/
if (cnt->conf.switchfilter && cnt->current_image->diffs > cnt->threshold) {
cnt->current_image->diffs = alg_switchfilter(cnt, cnt->current_image->diffs,
cnt->current_image->image);
if (cnt->current_image->diffs <= cnt->threshold) {
cnt->current_image->diffs = 0;
if (cnt->conf.setup_mode)
motion_log(-1, 0, "Switchfilter detected");
}
}
/* Despeckle feature
* First we run (as given by the despeckle option iterations
* of erode and dilate algorithms.
* Finally we run the labelling feature.
* All this is done in the alg_despeckle code.
*/
cnt->current_image->total_labels = 0;
cnt->imgs.largest_label = 0;
olddiffs = 0;
if (cnt->conf.despeckle && cnt->current_image->diffs > 0) {
olddiffs = cnt->current_image->diffs;
cnt->current_image->diffs = alg_despeckle(cnt, olddiffs);
} else if (cnt->imgs.labelsize_max) {
cnt->imgs.labelsize_max = 0; /* Disable labeling if enabled */
}
} else if (!cnt->conf.setup_mode) {
cnt->current_image->diffs = 0;
}
/* Manipulate smart_mask sensitivity (only every smartmask_ratio seconds) */
if (cnt->smartmask_speed && (cnt->event_nr != cnt->prev_event)) {
if (!--smartmask_count) {
alg_tune_smartmask(cnt);
smartmask_count = smartmask_ratio;
}
}
/* cnt->moved is set by the tracking code when camera has been asked to move.
* When camera is moving we do not want motion to detect motion or we will
* get our camera chasing itself like crazy and we will get motion detected
* which is not really motion. So we pretend there is no motion by setting
* cnt->diffs = 0.
* We also pretend to have a moving camera when we start Motion and when light
* switch has been detected to allow camera to settle.
*/
if (cnt->moved) {
cnt->moved--;
cnt->current_image->diffs = 0;
}
/***** MOTION LOOP - TUNING SECTION *****/
/* if noise tuning was selected, do it now. but only when
* no frames have been recorded and only once per second
*/
if (cnt->conf.noise_tune && cnt->shots == 0) {
if (!cnt->detecting_motion && (cnt->current_image->diffs <= cnt->threshold))
alg_noise_tune(cnt, cnt->imgs.image_virgin);
}
/* if we are not noise tuning lets make sure that remote controlled
* changes of noise_level are used.
*/
if (!cnt->conf.noise_tune)
cnt->noise = cnt->conf.noise;
/* threshold tuning if enabled
* if we are not threshold tuning lets make sure that remote controlled
* changes of threshold are used.
*/
if (cnt->conf.threshold_tune)
alg_threshold_tune(cnt, cnt->current_image->diffs, cnt->detecting_motion);
else
cnt->threshold = cnt->conf.max_changes;
/* If motion is detected (cnt->current_image->diffs > cnt->threshold) and before we add text to the pictures
we find the center and size coordinates of the motion to be used for text overlays and later
for adding the locate rectangle */
if (cnt->current_image->diffs > cnt->threshold)
alg_locate_center_size(&cnt->imgs, cnt->imgs.width, cnt->imgs.height,
&cnt->current_image->location);
/* Update reference frame. */
/* micro-lighswitch: e.g. neighbors cat switched on the motion sensitive *
* frontdoor illumination. Updates are rate-limited to 3 per second at *
* framerates above 5fps to save CPU resources and to keep sensitivity *
* at a constant level. *
*/
ref_frame_limit++;
if (ref_frame_limit >= (cnt->lastrate / 3)) {
ref_frame_limit = 0;
if ((cnt->current_image->diffs > cnt->threshold) &&
(cnt->lightswitch_framecounter < (cnt->lastrate * 2)) && /* two seconds window */
((abs(previous_diffs - cnt->current_image->diffs)) < (previous_diffs / 15)) &&
((abs(cnt->current_image->location.x - previous_location_x)) <= (cnt->imgs.width / 150)) &&
((abs(cnt->current_image->location.y - previous_location_y)) <= (cnt->imgs.height / 150))) {
alg_update_reference_frame(cnt, RESET_REF_FRAME);
cnt->current_image->diffs = 0;
cnt->lightswitch_framecounter = 0;
if (cnt->conf.setup_mode)
motion_log(-1, 0, "micro-lightswitch!");
} else {
alg_update_reference_frame(cnt, UPDATE_REF_FRAME);
}
previous_diffs = cnt->current_image->diffs;
previous_location_x = cnt->current_image->location.x;
previous_location_y = cnt->current_image->location.y;
}
/***** MOTION LOOP - TEXT AND GRAPHICS OVERLAY SECTION *****/
/* Some overlays on top of the motion image
* Note that these now modifies the cnt->imgs.out so this buffer
* can no longer be used for motion detection features until next
* picture frame is captured.
*/
/* Smartmask overlay */
if (cnt->smartmask_speed && (cnt->conf.motion_img || cnt->conf.ffmpeg_cap_motion || cnt->conf.setup_mode))
overlay_smartmask(cnt, cnt->imgs.out);
/* Largest labels overlay */
if (cnt->imgs.largest_label && (cnt->conf.motion_img || cnt->conf.ffmpeg_cap_motion || cnt->conf.setup_mode))
overlay_largest_label(cnt, cnt->imgs.out);
/* Fixed mask overlay */
if (cnt->imgs.mask && (cnt->conf.motion_img || cnt->conf.ffmpeg_cap_motion || cnt->conf.setup_mode))
overlay_fixed_mask(cnt, cnt->imgs.out);
/* Initialize the double sized characters if needed. */
if (cnt->conf.text_double && text_size_factor == 1) {
text_size_factor = 2;
/* If text_double is set to off, then reset the scaling text_size_factor. */
} else if (!cnt->conf.text_double && text_size_factor == 2) {
text_size_factor = 1;
}
/* Add changed pixels in upper right corner of the pictures */
if (cnt->conf.text_changes) {
char tmp[15];
if (!cnt->pause)
sprintf(tmp, "%d", cnt->current_image->diffs);
else
sprintf(tmp, "-");
draw_text(cnt->current_image->image, cnt->imgs.width - 10, 10, cnt->imgs.width,
tmp, cnt->conf.text_double);
}
/* Add changed pixels to motion-images (for webcam) in setup_mode
and always overlay smartmask (not only when motion is detected) */
if (cnt->conf.setup_mode) {
char tmp[PATH_MAX];
sprintf(tmp, "D:%5d L:%3d N:%3d", cnt->current_image->diffs, cnt->current_image->total_labels, cnt->noise);
draw_text(cnt->imgs.out, cnt->imgs.width - 10, cnt->imgs.height - 30 * text_size_factor,
cnt->imgs.width, tmp, cnt->conf.text_double);
sprintf(tmp, "THREAD %d SETUP", cnt->threadnr);
draw_text(cnt->imgs.out, cnt->imgs.width - 10, cnt->imgs.height - 10 * text_size_factor,
cnt->imgs.width, tmp, cnt->conf.text_double);
}
/* Add text in lower left corner of the pictures */
if (cnt->conf.text_left) {
char tmp[PATH_MAX];
mystrftime(cnt, tmp, sizeof(tmp), cnt->conf.text_left, &cnt->current_image->timestamp_tm, NULL, 0);
draw_text(cnt->current_image->image, 10, cnt->imgs.height - 10 * text_size_factor, cnt->imgs.width,
tmp, cnt->conf.text_double);
}
/* Add text in lower right corner of the pictures */
if (cnt->conf.text_right) {
char tmp[PATH_MAX];
mystrftime(cnt, tmp, sizeof(tmp), cnt->conf.text_right, &cnt->current_image->timestamp_tm, NULL, 0);
draw_text(cnt->current_image->image, cnt->imgs.width - 10, cnt->imgs.height - 10 * text_size_factor,
cnt->imgs.width, tmp, cnt->conf.text_double);
}
/***** MOTION LOOP - ACTIONS AND EVENT CONTROL SECTION *****/
if (cnt->current_image->diffs > cnt->threshold) {
/* flag this image, it have motion */
cnt->current_image->flags |= IMAGE_MOTION;
cnt->lightswitch_framecounter++; /* micro lightswitch */
} else {
cnt->lightswitch_framecounter = 0;
}
/* If motion has been detected we take action and start saving
* pictures and movies etc by calling motion_detected().
* Is output_all enabled we always call motion_detected()
* If post_capture is enabled we also take care of this in the this
* code section.
*/
if (cnt->conf.output_all && (cnt->startup_frames == 0)) {
cnt->detecting_motion = 1;
/* Setup the postcap counter */
cnt->postcap = cnt->conf.post_capture;
cnt->current_image->flags |= (IMAGE_TRIGGER | IMAGE_SAVE);
motion_detected(cnt, cnt->video_dev, cnt->current_image);
} else if ((cnt->current_image->flags & IMAGE_MOTION) && (cnt->startup_frames == 0)) {
/* Did we detect motion (like the cat just walked in :) )?
* If so, ensure the motion is sustained if minimum_motion_frames
*/
/* Count how many frames with motion there is in the last minimum_motion_frames in precap buffer */
int frame_count = 0;
int pos = cnt->imgs.image_ring_in;
for(i = 0; i < cnt->conf.minimum_motion_frames; i++) {
if (cnt->imgs.image_ring[pos].flags & IMAGE_MOTION)
frame_count++;
if (pos == 0)
pos = cnt->imgs.image_ring_size-1;
else
pos--;
}
if (frame_count >= cnt->conf.minimum_motion_frames) {
cnt->current_image->flags |= (IMAGE_TRIGGER | IMAGE_SAVE);
cnt->detecting_motion = 1;
/* Setup the postcap counter */
cnt->postcap = cnt->conf.post_capture;
/* Mark all images in image_ring to be saved */
for(i = 0; i < cnt->imgs.image_ring_size; i++)
cnt->imgs.image_ring[i].flags |= IMAGE_SAVE;
} else if (cnt->postcap) { /* we have motion in this frame, but not enought frames for trigger. Check postcap */
cnt->current_image->flags |= (IMAGE_POSTCAP | IMAGE_SAVE);
cnt->postcap--;
} else {
cnt->current_image->flags |= IMAGE_PRECAP;
}
/* Always call motion_detected when we have a motion image */
motion_detected(cnt, cnt->video_dev, cnt->current_image);
} else if (cnt->postcap) {
/* No motion, doing postcap */
cnt->current_image->flags |= (IMAGE_POSTCAP | IMAGE_SAVE);
cnt->postcap--;
} else {
/* Done with postcap, so just have the image in the precap buffer */
cnt->current_image->flags |= IMAGE_PRECAP;
cnt->detecting_motion = 0;
}
/* Update last frame saved time, so we can end event after gap time */
if (cnt->current_image->flags & IMAGE_SAVE)
cnt->lasttime = cnt->current_image->timestamp;
/* Simple hack to recognize motion in a specific area */
/* Do we need a new coversion specifier as well?? */
if ((cnt->conf.area_detect) && (cnt->event_nr != area_once) &&
(cnt->current_image->flags & IMAGE_TRIGGER)) {
j = strlen(cnt->conf.area_detect);
for (i = 0; i < j; i++) {
z = cnt->conf.area_detect[i] - 49; /* 1 becomes 0 */
if ((z >= 0) && (z < 9)) {
if (cnt->current_image->location.x > area_minx[z] &&
cnt->current_image->location.x < area_maxx[z] &&
cnt->current_image->location.y > area_miny[z] &&
cnt->current_image->location.y < area_maxy[z]) {
event(cnt, EVENT_AREA_DETECTED, NULL, NULL,
NULL, cnt->currenttime_tm);
area_once = cnt->event_nr; /* Fire script only once per event */
if (cnt->conf.setup_mode)
motion_log(-1, 0, "Motion in area %d detected.\n", z+1);
break;
}
}
}
}
/* Is the mpeg movie to long? Then make movies
* First test for max mpegtime
*/
if (cnt->conf.maxmpegtime && cnt->event_nr == cnt->prev_event)
if (cnt->currenttime - cnt->eventtime >= cnt->conf.maxmpegtime)
cnt->makemovie = 1;
/* Now test for quiet longer than 'gap' OR make movie as decided in
* previous statement.
*/
if (((cnt->currenttime - cnt->lasttime >= cnt->conf.gap) && cnt->conf.gap > 0) || cnt->makemovie) {
if (cnt->event_nr == cnt->prev_event || cnt->makemovie) {
/* Flush image buffer */
process_image_ring(cnt, IMAGE_BUFFER_FLUSH);
/* Save preview_shot here at the end of event */
if (cnt->imgs.preview_image.diffs) {
preview_save(cnt);
cnt->imgs.preview_image.diffs = 0;
}
event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, cnt->currenttime_tm);
/* if tracking is enabled we center our camera so it does not
* point to a place where it will miss the next action
*/
if (cnt->track.type)
cnt->moved = track_center(cnt, cnt->video_dev, 0, 0, 0);
if (cnt->conf.setup_mode)
motion_log(-1, 0, "End of event %d", cnt->event_nr);
cnt->makemovie = 0;
/* Reset post capture */
cnt->postcap = 0;
/* Finally we increase the event number */
cnt->event_nr++;
cnt->lightswitch_framecounter = 0;
/* And we unset the text_event_string to avoid that buffered
* images get a timestamp from previous event.
*/
cnt->text_event_string[0] = '\0';
}
}
/* Save/send to movie some images */
process_image_ring(cnt, 2);
/***** MOTION LOOP - SETUP MODE CONSOLE OUTPUT SECTION *****/
/* If setup_mode enabled output some numbers to console */
if (cnt->conf.setup_mode) {
char msg[1024] = "\0";
char part[100];
if (cnt->conf.despeckle) {
snprintf(part, 99, "Raw changes: %5d - changes after '%s': %5d",
olddiffs, cnt->conf.despeckle, cnt->current_image->diffs);
strcat(msg, part);
if (strchr(cnt->conf.despeckle, 'l')){
sprintf(part, " - labels: %3d", cnt->current_image->total_labels);
strcat(msg, part);
}
} else {
sprintf(part, "Changes: %5d", cnt->current_image->diffs);
strcat(msg, part);
}
if (cnt->conf.noise_tune) {
sprintf(part, " - noise level: %2d", cnt->noise);
strcat(msg, part);
}
if (cnt->conf.threshold_tune) {
sprintf(part, " - threshold: %d", cnt->threshold);
strcat(msg, part);
}
motion_log(-1, 0, "%s", msg);
}
} /* get_image end */
/***** MOTION LOOP - SNAPSHOT FEATURE SECTION *****/
/* Did we get triggered to make a snapshot from control http? Then shoot a snap
* If snapshot_interval is not zero and time since epoch MOD snapshot_interval = 0 then snap
* We actually allow the time to run over the interval in case we have a delay
* from slow camera.
* Note: Negative value means SIGALRM snaps are enabled
* httpd-control snaps are always enabled.
*/
/* time_current_frame is used both for snapshot and timelapse features */
time_current_frame = cnt->currenttime;
if ((cnt->conf.snapshot_interval > 0 && cnt->shots == 0 &&
time_current_frame % cnt->conf.snapshot_interval <= time_last_frame % cnt->conf.snapshot_interval) ||
cnt->snapshot) {
event(cnt, EVENT_IMAGE_SNAPSHOT, cnt->current_image->image, NULL, NULL,
&cnt->current_image->timestamp_tm);
cnt->snapshot = 0;
}
/***** MOTION LOOP - TIMELAPSE FEATURE SECTION *****/
#ifdef HAVE_FFMPEG
if (cnt->conf.timelapse) {
/* Check to see if we should start a new timelapse file. We start one when
* we are on the first shot, and and the seconds are zero. We must use the seconds
* to prevent the timelapse file from getting reset multiple times during the minute.
*/
if (cnt->current_image->timestamp_tm.tm_min == 0 &&
(time_current_frame % 60 < time_last_frame % 60) &&
cnt->shots == 0) {
if (strcasecmp(cnt->conf.timelapse_mode,"manual") == 0) {
;/* No action */
/* If we are daily, raise timelapseend event at midnight */
} else if (strcasecmp(cnt->conf.timelapse_mode, "daily") == 0) {
if (cnt->current_image->timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL,
&cnt->current_image->timestamp_tm);
/* handle the hourly case */
} else if (strcasecmp(cnt->conf.timelapse_mode, "hourly") == 0) {
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL,
&cnt->current_image->timestamp_tm);
/* If we are weekly-sunday, raise timelapseend event at midnight on sunday */
} else if (strcasecmp(cnt->conf.timelapse_mode, "weekly-sunday") == 0) {
if (cnt->current_image->timestamp_tm.tm_wday == 0 && cnt->current_image->timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tm);
/* If we are weekly-monday, raise timelapseend event at midnight on monday */
} else if (strcasecmp(cnt->conf.timelapse_mode, "weekly-monday") == 0) {
if (cnt->current_image->timestamp_tm.tm_wday == 1 && cnt->current_image->timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tm);
/* If we are monthly, raise timelapseend event at midnight on first day of month */
} else if (strcasecmp(cnt->conf.timelapse_mode, "monthly") == 0) {
if (cnt->current_image->timestamp_tm.tm_mday == 1 && cnt->current_image->timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tm);
/* If invalid we report in syslog once and continue in manual mode */
} else {
motion_log(LOG_ERR, 0, "Invalid timelapse_mode argument '%s'",
cnt->conf.timelapse_mode);
motion_log(LOG_ERR, 0, "Defaulting to manual timelapse mode");
conf_cmdparse(&cnt, (char *)"ffmpeg_timelapse_mode",(char *)"manual");
}
}
/* If ffmpeg timelapse is enabled and time since epoch MOD ffmpeg_timelaps = 0
* add a timelapse frame to the timelapse mpeg.
*/
if (cnt->shots == 0 &&
time_current_frame % cnt->conf.timelapse <= time_last_frame % cnt->conf.timelapse)
event(cnt, EVENT_TIMELAPSE, cnt->current_image->image, NULL, NULL,
&cnt->current_image->timestamp_tm);
} else if (cnt->ffmpeg_timelapse) {
/* if timelapse mpeg is in progress but conf.timelapse is zero then close timelapse file
* This is an important feature that allows manual roll-over of timelapse file using the http
* remote control via a cron job.
*/
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, cnt->currenttime_tm);
}
#endif /* HAVE_FFMPEG */
time_last_frame = time_current_frame;
/***** MOTION LOOP - VIDEO LOOPBACK SECTION *****/
/* feed last image and motion image to video device pipes and the webcam clients
* In setup mode we send the special setup mode image to both webcam and vloopback pipe
* In normal mode we feed the latest image to vloopback device and we send
* the image to the webcam. We always send the first image in a second to the webcam.
* Other image are sent only when the config option webcam_motion is off
* The result is that with webcam_motion on the webcam stream is normally at the minimal
* 1 frame per second but the minute motion is detected the motion_detected() function
* sends all detected pictures to the webcam except the 1st per second which is already sent.
*/
if (cnt->conf.setup_mode) {
event(cnt, EVENT_IMAGE, cnt->imgs.out, NULL, &cnt->pipe, cnt->currenttime_tm);
event(cnt, EVENT_WEBCAM, cnt->imgs.out, NULL, NULL, cnt->currenttime_tm);
} else {
event(cnt, EVENT_IMAGE, cnt->current_image->image, NULL, &cnt->pipe,
&cnt->current_image->timestamp_tm);
if (!cnt->conf.webcam_motion || cnt->shots == 1)
event(cnt, EVENT_WEBCAM, cnt->current_image->image, NULL, NULL,
&cnt->current_image->timestamp_tm);
}
event(cnt, EVENT_IMAGEM, cnt->imgs.out, NULL, &cnt->mpipe, cnt->currenttime_tm);
/***** MOTION LOOP - ONCE PER SECOND PARAMETER UPDATE SECTION *****/
/* Check for some config parameter changes but only every second */
if (cnt->shots == 0){
if (strcasecmp(cnt->conf.output_normal, "on") == 0)
cnt->new_img = NEWIMG_ON;
else if (strcasecmp(cnt->conf.output_normal, "first") == 0)
cnt->new_img = NEWIMG_FIRST;
else if (strcasecmp(cnt->conf.output_normal, "best") == 0)
cnt->new_img = NEWIMG_BEST;
else if (strcasecmp(cnt->conf.output_normal, "center") == 0)
cnt->new_img = NEWIMG_CENTER;
else
cnt->new_img = NEWIMG_OFF;
if (strcasecmp(cnt->conf.locate, "on") == 0)
cnt->locate = LOCATE_ON;
else if (strcasecmp(cnt->conf.locate, "preview") == 0)
cnt->locate = LOCATE_PREVIEW;
else
cnt->locate = LOCATE_OFF;
/* Sanity check for smart_mask_speed, silly value disables smart mask */
if (cnt->conf.smart_mask_speed < 0 || cnt->conf.smart_mask_speed > 10)
cnt->conf.smart_mask_speed = 0;
/* Has someone changed smart_mask_speed or framerate? */
if (cnt->conf.smart_mask_speed != cnt->smartmask_speed || smartmask_lastrate != cnt->lastrate){
if (cnt->conf.smart_mask_speed == 0){
memset(cnt->imgs.smartmask, 0, cnt->imgs.motionsize);
memset(cnt->imgs.smartmask_final, 255, cnt->imgs.motionsize);
}
smartmask_lastrate = cnt->lastrate;
cnt->smartmask_speed = cnt->conf.smart_mask_speed;
/* Decay delay - based on smart_mask_speed (framerate independent)
This is always 5*smartmask_speed seconds */
smartmask_ratio = 5 * cnt->lastrate * (11 - cnt->smartmask_speed);
}
#if defined(HAVE_MYSQL) || defined(HAVE_PGSQL)
/* Set the sql mask file according to the SQL config options
* We update it for every frame in case the config was updated
* via remote control.
*/
cnt->sql_mask = cnt->conf.sql_log_image * (FTYPE_IMAGE + FTYPE_IMAGE_MOTION) +
cnt->conf.sql_log_snapshot * FTYPE_IMAGE_SNAPSHOT +
cnt->conf.sql_log_mpeg * (FTYPE_MPEG + FTYPE_MPEG_MOTION) +
cnt->conf.sql_log_timelapse * FTYPE_MPEG_TIMELAPSE;
#endif /* defined(HAVE_MYSQL) || defined(HAVE_PGSQL) */
}
/***** MOTION LOOP - FRAMERATE TIMING AND SLEEPING SECTION *****/
/* Work out expected frame rate based on config setting which may
have changed from http-control */
if (cnt->conf.frame_limit)
required_frame_time = 1000000L / cnt->conf.frame_limit;
else
required_frame_time = 0;
/* Get latest time to calculate time taken to process video data */
gettimeofday(&tv2, NULL);
elapsedtime = (tv2.tv_usec + 1000000L * tv2.tv_sec) - timenow;
/* Update history buffer but ignore first pass as timebefore
variable will be inaccurate
*/
if (passflag)
rolling_average_data[rolling_frame] = timenow-timebefore;
else
passflag = 1;
rolling_frame++;
if (rolling_frame >= rolling_average_limit)
rolling_frame = 0;
/* Calculate 10 second average and use deviation in delay calculation */
rolling_average = 0L;
for (j = 0; j < rolling_average_limit; j++)
rolling_average += rolling_average_data[j];
rolling_average /= rolling_average_limit;
frame_delay = required_frame_time-elapsedtime - (rolling_average - required_frame_time);
if (frame_delay > 0) {
/* Apply delay to meet frame time */
if (frame_delay > required_frame_time)
frame_delay = required_frame_time;
/* Delay time in nanoseconds for SLEEP */
delay_time_nsec = frame_delay * 1000;
if (delay_time_nsec > 999999999)
delay_time_nsec = 999999999;
/* SLEEP as defined in motion.h A safe sleep using nanosleep */
SLEEP(0, delay_time_nsec);
}
}
/* END OF MOTION MAIN LOOP
* If code continues here it is because the thread is exiting or restarting
*/
err:
if (rolling_average_data)
free(rolling_average_data);
cnt->lost_connection = 1;
motion_log(-1, 0, "Thread exiting");
motion_cleanup(cnt);
pthread_mutex_lock(&global_lock);
threads_running--;
pthread_mutex_unlock(&global_lock);
if (!cnt->restart)
cnt->watchdog=WATCHDOG_OFF;
cnt->running = 0;
cnt->finish = 0;
pthread_exit(NULL);
}
/**
* become_daemon
*
* Turns Motion into a daemon through forking. The parent process (i.e. the
* one initially calling this function) will exit inside this function, while
* control will be returned to the child process. Standard input/output are
* released properly, and the current directory is set to / in order to not
* lock up any file system.
*
* Parameters:
*
* cnt - current thread's context struct
*
* Returns: nothing
*/
static void become_daemon(void)
{
int i;
FILE *pidf = NULL;
struct sigaction sig_ign_action;
/* Setup sig_ign_action */
#ifdef SA_RESTART
sig_ign_action.sa_flags = SA_RESTART;
#else
sig_ign_action.sa_flags = 0;
#endif
sig_ign_action.sa_handler = SIG_IGN;
sigemptyset(&sig_ign_action.sa_mask);
/* fork */
if (fork()) {
motion_log(-1, 0, "Motion going to daemon mode");
exit(0);
}
/* Create the pid file if defined, if failed exit
* If we fail we report it. If we succeed we postpone the log entry till
* later when we have closed stdout. Otherwise Motion hangs in the terminal waiting
* for an enter.
*/
if (cnt_list[0]->conf.pid_file) {
pidf = fopen(cnt_list[0]->conf.pid_file, "w+");
if (pidf ) {
(void)fprintf(pidf, "%d\n", getpid());
fclose(pidf);
} else {
motion_log(LOG_ERR, 1, "Exit motion, cannot create process id file (pid file) %s",
cnt_list[0]->conf.pid_file);
exit(0);
}
}
/* changing dir to root enables people to unmount a disk
without having to stop Motion */
if (chdir("/")) {
motion_log(LOG_ERR, 1, "Could not change directory");
}
#if (defined(BSD))
setpgrp(0, getpid());
#else
setpgrp();
#endif /* BSD */
if ((i = open("/dev/tty", O_RDWR)) >= 0) {
ioctl(i, TIOCNOTTY, NULL);
close(i);
}
setsid();
i = open("/dev/null", O_RDONLY);
if (i != -1) {
dup2(i, STDIN_FILENO);
close(i);
}
i = open("/dev/null", O_WRONLY);
if (i != -1) {
dup2(i, STDOUT_FILENO);
dup2(i, STDERR_FILENO);
close(i);
}
/* Now it is safe to add the PID creation to the logs */
if (pidf )
motion_log(LOG_INFO, 0, "Created process id file %s. Process ID is %d",
cnt_list[0]->conf.pid_file, getpid());
sigaction(SIGTTOU, &sig_ign_action, NULL);
sigaction(SIGTTIN, &sig_ign_action, NULL);
sigaction(SIGTSTP, &sig_ign_action, NULL);
}
/**
* cntlist_create
*
* Sets up the 'cnt_list' variable by allocating room for (and actually
* allocating) one context struct. Also loads the configuration from
* the config file(s).
*
* Parameters:
* argc - size of argv
* argv - command-line options, passed initially from 'main'
*
* Returns: nothing
*/
static void cntlist_create(int argc, char *argv[])
{
/* cnt_list is an array of pointers to the context structures cnt for each thread.
* First we reserve room for a pointer to thread 0's context structure
* and a NULL pointer which indicates that end of the array of pointers to
* thread context structures.
*/
cnt_list = mymalloc(sizeof(struct context *) * 2);
/* Now we reserve room for thread 0's context structure and let cnt_list[0] point to it */
cnt_list[0] = mymalloc(sizeof(struct context));
/* Populate context structure with start/default values */
context_init(cnt_list[0]);
/* cnt_list[1] pointing to zero indicates no more thread context structures - they get added later */
cnt_list[1] = NULL;
/* Command line arguments are being pointed to from cnt_list[0] and we call conf_load which loads
* the config options from motion.conf, thread config files and the command line.
*/
cnt_list[0]->conf.argv = argv;
cnt_list[0]->conf.argc = argc;
cnt_list = conf_load(cnt_list);
}
/**
* motion_shutdown
*
* Responsible for performing cleanup when Motion is shut down or restarted,
* including freeing memory for all the context structs as well as for the
* context struct list itself.
*
* Parameters: none
*
* Returns: nothing
*/
static void motion_shutdown(void)
{
int i = -1;
motion_remove_pid();
while (cnt_list[++i])
context_destroy(cnt_list[i]);
free(cnt_list);
cnt_list = NULL;
#ifndef WITHOUT_V4L
vid_cleanup();
#endif
}
/**
* motion_startup
*
* Responsible for initializing stuff when Motion starts up or is restarted,
* including daemon initialization and creating the context struct list.
*
* Parameters:
*
* daemonize - non-zero to do daemon init (if the config parameters says so),
* or 0 to skip it
* argc - size of argv
* argv - command-line options, passed initially from 'main'
*
* Returns: nothing
*/
static void motion_startup(int daemonize, int argc, char *argv[])
{
/* Initialize our global mutex */
pthread_mutex_init(&global_lock, NULL);
/* Create the list of context structures and load the
* configuration.
*/
cntlist_create(argc, argv);
motion_log(LOG_INFO, 0, "Motion "VERSION" Started");
initialize_chars();
if (daemonize) {
/* If daemon mode is requested, and we're not going into setup mode,
* become daemon.
*/
if (cnt_list[0]->daemon && cnt_list[0]->conf.setup_mode == 0) {
become_daemon();
motion_log(LOG_INFO, 0, "Motion running as daemon process");
}
}
#ifndef WITHOUT_V4L
vid_init();
#endif
}
/**
* setup_signals
*
* Attaches handlers to a number of signals that Motion need to catch.
*
* Parameters: sigaction structs for signals in general and SIGCHLD.
*
* Returns: nothing
*/
static void setup_signals(struct sigaction *sig_handler_action, struct sigaction *sigchild_action)
{
#ifdef SA_NOCLDWAIT
sigchild_action->sa_flags = SA_NOCLDWAIT;
#else
sigchild_action->sa_flags = 0;
#endif
sigchild_action->sa_handler = sigchild_handler;
sigemptyset(&sigchild_action->sa_mask);
#ifdef SA_RESTART
sig_handler_action->sa_flags = SA_RESTART;
#else
sig_handler_action->sa_flags = 0;
#endif
sig_handler_action->sa_handler = sig_handler;
sigemptyset(&sig_handler_action->sa_mask);
/* Enable automatic zombie reaping */
sigaction(SIGCHLD, sigchild_action, NULL);
sigaction(SIGPIPE, sigchild_action, NULL);
sigaction(SIGALRM, sig_handler_action, NULL);
sigaction(SIGHUP, sig_handler_action, NULL);
sigaction(SIGINT, sig_handler_action, NULL);
sigaction(SIGQUIT, sig_handler_action, NULL);
sigaction(SIGTERM, sig_handler_action, NULL);
sigaction(SIGUSR1, sig_handler_action, NULL);
}
/**
* start_motion_thread
*
* Called from main when start a motion thread
*
* Parameters:
*
* cnt - Thread context pointer
* thread_attr - pointer to thread attributes
*
* Returns: nothing
*/
static void start_motion_thread(struct context *cnt, pthread_attr_t *thread_attr)
{
int i;
/* Check the webcam port number for conflicts.
* First we check for conflict with the control port.
* Second we check for that two threads does not use the same port number
* for the webcam. If a duplicate port is found the webcam feature gets disabled (port =0)
* for this thread and a warning is written to console and syslog.
*/
if (cnt->conf.webcam_port != 0) {
/* Compare against the control port. */
if (cnt_list[0]->conf.control_port == cnt->conf.webcam_port) {
motion_log(LOG_ERR, 0,
"Webcam port number %d for thread %d conflicts with the control port",
cnt->conf.webcam_port, cnt->threadnr);
motion_log(LOG_ERR, 0, "Webcam feature for thread %d is disabled.", cnt->threadnr);
cnt->conf.webcam_port = 0;
}
/* Compare against webcam ports of other threads. */
for (i = 1; cnt_list[i]; i++) {
if (cnt_list[i] == cnt)
continue;
if (cnt_list[i]->conf.webcam_port == cnt->conf.webcam_port) {
motion_log(LOG_ERR, 0,
"Webcam port number %d for thread %d conflicts with thread %d",
cnt->conf.webcam_port, cnt->threadnr, cnt_list[i]->threadnr);
motion_log(LOG_ERR, 0,
"Webcam feature for thread %d is disabled.", cnt->threadnr);
cnt->conf.webcam_port = 0;
}
}
}
/* Update how many threads we have running. This is done within a
* mutex lock to prevent multiple simultaneous updates to
* 'threads_running'.
*/
pthread_mutex_lock(&global_lock);
threads_running++;
pthread_mutex_unlock(&global_lock);
/* Set a flag that we want this thread running */
cnt->restart = 1;
/* Give the thread WATCHDOG_TMO seconds to start */
cnt->watchdog = WATCHDOG_TMO;
/* Create the actual thread. Use 'motion_loop' as the thread
* function.
*/
pthread_create(&cnt->thread_id, thread_attr, &motion_loop, cnt);
}
/**
* main
*
* Main entry point of Motion. Launches all the motion threads and contains
* the logic for starting up, restarting and cleaning up everything.
*
* Parameters:
*
* argc - size of argv
* argv - command-line options
*
* Returns: Motion exit status = 0 always
*/
int main (int argc, char **argv)
{
int i;
pthread_attr_t thread_attr;
pthread_t thread_id;
/* Setup signals and do some initialization. 1 in the call to
* 'motion_startup' means that Motion will become a daemon if so has been
* requested, and argc and argc are necessary for reading the command
* line options.
*/
struct sigaction sig_handler_action;
struct sigaction sigchild_action;
setup_signals(&sig_handler_action, &sigchild_action);
motion_startup(1, argc, argv);
#ifdef HAVE_FFMPEG
/* FFMpeg initialization is only performed if FFMpeg support was found
* and not disabled during the configure phase.
*/
ffmpeg_init();
#endif /* HAVE_FFMPEG */
/* In setup mode, Motion is very communicative towards the user, which
* allows the user to experiment with the config parameters in order to
* optimize motion detection and stuff.
*/
if (cnt_list[0]->conf.setup_mode)
motion_log(-1, 0, "Motion running in setup mode.");
/* Create and a thread attribute for the threads we spawn later on.
* PTHREAD_CREATE_DETACHED means to create threads detached, i.e.
* their termination cannot be synchronized through 'pthread_join'.
*/
pthread_attr_init(&thread_attr);
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
/* Create the TLS key for thread number. */
pthread_key_create(&tls_key_threadnr, NULL);
do {
if (restart) {
/* Handle the restart situation. Currently the approach is to
* cleanup everything, and then initialize everything again
* (including re-reading the config file(s)).
*/
motion_shutdown();
restart = 0; /* only one reset for now */
motion_log(LOG_INFO,0,"motion restarted");
#ifndef WITHOUT_V4L
SLEEP(5,0); // maybe some cameras needs less time
#endif
motion_startup(0, argc, argv); /* 0 = skip daemon init */
}
/* Start the motion threads. First 'cnt_list' item is global if 'thread'
* option is used, so start at 1 then and 0 otherwise.
*/
for (i = cnt_list[1] != NULL ? 1 : 0; cnt_list[i]; i++) {
/* If i is 0 it means no thread files and we then set the thread number to 1 */
cnt_list[i]->threadnr = i ? i : 1;
if (strcmp(cnt_list[i]->conf_filename,"") )
motion_log(LOG_INFO, 0, "Thread %d is from %s", cnt_list[i]->threadnr, cnt_list[i]->conf_filename );
if (cnt_list[0]->conf.setup_mode) {
motion_log(-1, 0, "Thread %d is device: %s input %d", cnt_list[i]->threadnr,
cnt_list[i]->conf.netcam_url ? cnt_list[i]->conf.netcam_url : cnt_list[i]->conf.video_device,
cnt_list[i]->conf.netcam_url ? -1 : cnt_list[i]->conf.input
);
}
if (cnt_list[0]->conf.setup_mode)
motion_log(LOG_ERR, 0, "Webcam port %d", cnt_list[i]->conf.webcam_port);
start_motion_thread(cnt_list[i], &thread_attr);
}
/* Create a thread for the control interface if requested. Create it
* detached and with 'motion_web_control' as the thread function.
*/
if (cnt_list[0]->conf.control_port)
pthread_create(&thread_id, &thread_attr, &motion_web_control, cnt_list);
if (cnt_list[0]->conf.setup_mode)
motion_log(-1, 0,"Waiting for threads to finish, pid: %d", getpid());
/* Crude way of waiting for all threads to finish - check the thread
* counter (because we cannot do join on the detached threads).
*/
while (1) {
SLEEP(1,0);
/* Calculate how many threads runnig or wants to run
* if zero and we want to finish, break out
*/
int motion_threads_running = 0;
for (i = (cnt_list[1] != NULL ? 1 : 0); cnt_list[i]; i++) {
if (cnt_list[i]->running || cnt_list[i]->restart)
motion_threads_running++;
}
if (((motion_threads_running == 0 ) && finish ) ||
((motion_threads_running == 0 ) && (threads_running == 0)) ){
if (debug_level >= CAMERA_DEBUG){
motion_log(LOG_INFO, 0, "DEBUG-1 threads_running %d motion_threads_running %d , finish %d",
threads_running, motion_threads_running, finish);
}
break;
}
for (i = (cnt_list[1] != NULL ? 1 : 0); cnt_list[i]; i++) {
/* Check if threads wants to be restarted */
if ((!cnt_list[i]->running) && (cnt_list[i]->restart) ) {
motion_log(LOG_INFO, 0, "Motion thread %d restart", cnt_list[i]->threadnr);
start_motion_thread(cnt_list[i], &thread_attr);
}
if (cnt_list[i]->watchdog > WATCHDOG_OFF) {
cnt_list[i]->watchdog--;
if (cnt_list[i]->watchdog == 0) {
motion_log(LOG_ERR, 0, "Thread %d - Watchdog timeout, trying to do a graceful restart",
cnt_list[i]->threadnr);
cnt_list[i]->finish = 1;
}
if (cnt_list[i]->watchdog == -60) {
motion_log(LOG_ERR, 0, "Thread %d - Watchdog timeout, did NOT restart graceful,"
"killing it!", cnt_list[i]->threadnr);
pthread_cancel(cnt_list[i]->thread_id);
pthread_mutex_lock(&global_lock);
threads_running--;
pthread_mutex_unlock(&global_lock);
motion_cleanup(cnt_list[i]);
cnt_list[i]->running = 0;
cnt_list[i]->finish = 0;
}
}
}
if (debug_level >= CAMERA_DEBUG){
motion_log(LOG_INFO, 0, "DEBUG-2 threads_running %d motion_threads_running %d , finish %d",
threads_running, motion_threads_running, finish);
}
}
/* Reset end main loop flag */
finish = 0;
if (cnt_list[0]->conf.setup_mode)
motion_log(LOG_DEBUG, 0, "Threads finished");
/* Rest for a while if we're supposed to restart. */
if (restart)
SLEEP(2,0);
} while (restart); /* loop if we're supposed to restart */
// Be sure that http control exits fine
cnt_list[0]->finish = 1;
SLEEP(1,0);
motion_log(LOG_INFO, 0, "Motion terminating");
/* Perform final cleanup. */
pthread_key_delete(tls_key_threadnr);
pthread_attr_destroy(&thread_attr);
pthread_mutex_destroy(&global_lock);
motion_shutdown();
return 0;
}
/**
* mymalloc
*
* Allocates some memory and checks if that succeeded or not. If it failed,
* do some errorlogging and bail out.
*
* NOTE: Kenneth Lavrsen changed printing of size_t types so instead of using
* conversion specifier %zd I changed it to %llu and casted the size_t
* variable to unsigned long long. The reason for this nonsense is that older
* versions of gcc like 2.95 uses %Zd and does not understand %zd. So to avoid
* this mess I used a more generic way. Long long should have enough bits for
* 64-bit machines with large memory areas.
*
* Parameters:
*
* nbytes - no. of bytes to allocate
*
* Returns: a pointer to the allocated memory
*/
void * mymalloc(size_t nbytes)
{
void *dummy = malloc(nbytes);
if (!dummy) {
motion_log(LOG_EMERG, 1, "Could not allocate %llu bytes of memory!", (unsigned long long)nbytes);
motion_remove_pid();
exit(1);
}
return dummy;
}
/**
* myrealloc
*
* Re-allocate (i.e., resize) some memory and check if that succeeded or not.
* If it failed, do some errorlogging and bail out. If the new memory size
* is 0, the memory is freed.
*
* Parameters:
*
* ptr - pointer to the memory to resize/reallocate
* size - new memory size
* desc - name of the calling function
*
* Returns: a pointer to the reallocated memory, or NULL if the memory was
* freed
*/
void *myrealloc(void *ptr, size_t size, const char *desc)
{
void *dummy = NULL;
if (size == 0) {
free(ptr);
motion_log(LOG_WARNING, 0,
"Warning! Function %s tries to resize memoryblock at %p to 0 bytes!",
desc, ptr);
} else {
dummy = realloc(ptr, size);
if (!dummy) {
motion_log(LOG_EMERG, 0,
"Could not resize memory-block at offset %p to %llu bytes (function %s)!",
ptr, (unsigned long long)size, desc);
motion_remove_pid();
exit(1);
}
}
return dummy;
}
/**
* create_path
*
* This function creates a whole path, like mkdir -p. Example paths:
* this/is/an/example/
* /this/is/an/example/
* Warning: a path *must* end with a slash!
*
* Parameters:
*
* cnt - current thread's context structure (for logging)
* path - the path to create
*
* Returns: 0 on success, -1 on failure
*/
int create_path(const char *path)
{
char *start;
mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
if (path[0] == '/')
start = strchr(path + 1, '/');
else
start = strchr(path, '/');
while (start) {
char *buffer = strdup(path);
buffer[start-path] = 0x00;
if (mkdir(buffer, mode) == -1 && errno != EEXIST) {
motion_log(LOG_ERR, 1, "Problem creating directory %s", buffer);
free(buffer);
return -1;
}
free(buffer);
start = strchr(start + 1, '/');
}
return 0;
}
/**
* myfopen
*
* This function opens a file, if that failed because of an ENOENT error
* (which is: path does not exist), the path is created and then things are
* tried again. This is faster then trying to create that path over and over
* again. If someone removes the path after it was created, myfopen will
* recreate the path automatically.
*
* Parameters:
*
* path - path to the file to open
* mode - open mode
*
* Returns: the file stream object
*/
FILE * myfopen(const char *path, const char *mode)
{
/* first, just try to open the file */
FILE *dummy = fopen(path, mode);
/* could not open file... */
if (!dummy) {
/* path did not exist? */
if (errno == ENOENT) {
/* create path for file... */
if (create_path(path) == -1)
return NULL;
/* and retry opening the file */
dummy = fopen(path, mode);
if (dummy)
return dummy;
}
/* two possibilities
* 1: there was an other error while trying to open the file for the first time
* 2: could still not open the file after the path was created
*/
motion_log(LOG_ERR, 1, "Error opening file %s with mode %s", path, mode);
return NULL;
}
return dummy;
}
/**
* mystrftime
*
* Motion-specific variant of strftime(3) that supports additional format
* specifiers in the format string.
*
* Parameters:
*
* cnt - current thread's context structure
* s - destination string
* max - max number of bytes to write
* userformat - format string
* tm - time information
* filename - string containing full path of filename
* set this to NULL if not relevant
* sqltype - Filetype as used in SQL feature, set to 0 if not relevant
*
* Returns: number of bytes written to the string s
*/
size_t mystrftime(struct context *cnt, char *s, size_t max, const char *userformat,
const struct tm *tm, const char *filename, int sqltype)
{
char formatstring[PATH_MAX] = "";
char tempstring[PATH_MAX] = "";
char *format, *tempstr;
const char *pos_userformat;
format = formatstring;
/* if mystrftime is called with userformat = NULL we return a zero length string */
if (userformat == NULL) {
*s = '\0';
return 0;
}
for (pos_userformat = userformat; *pos_userformat; ++pos_userformat) {
if (*pos_userformat == '%') {
/* Reset 'tempstr' to point to the beginning of 'tempstring',
* otherwise we will eat up tempstring if there are many
* format specifiers.
*/
tempstr = tempstring;
tempstr[0] = '\0';
switch (*++pos_userformat) {
case '\0': // end of string
--pos_userformat;
break;
case 'v': // event
sprintf(tempstr, "%02d", cnt->event_nr);
break;
case 'q': // shots
sprintf(tempstr, "%02d", cnt->current_image->shot);
break;
case 'D': // diffs
sprintf(tempstr, "%d", cnt->current_image->diffs);
break;
case 'N': // noise
sprintf(tempstr, "%d", cnt->noise);
break;
case 'i': // motion width
sprintf(tempstr, "%d", cnt->current_image->location.width);
break;
case 'J': // motion height
sprintf(tempstr, "%d", cnt->current_image->location.height);
break;
case 'K': // motion center x
sprintf(tempstr, "%d", cnt->current_image->location.x);
break;
case 'L': // motion center y
sprintf(tempstr, "%d", cnt->current_image->location.y);
break;
case 'o': // threshold
sprintf(tempstr, "%d", cnt->threshold);
break;
case 'Q': // number of labels
sprintf(tempstr, "%d", cnt->current_image->total_labels);
break;
case 't': // thread number
sprintf(tempstr, "%d",(int)(unsigned long)
pthread_getspecific(tls_key_threadnr));
break;
case 'C': // text_event
if (cnt->text_event_string && cnt->text_event_string[0])
snprintf(tempstr, PATH_MAX, "%s", cnt->text_event_string);
else
++pos_userformat;
break;
case 'f': // filename
if (filename)
snprintf(tempstr, PATH_MAX, "%s", filename);
else
++pos_userformat;
break;
case 'n': // sqltype
if (sqltype)
sprintf(tempstr, "%d", sqltype);
else
++pos_userformat;
break;
default: // Any other code is copied with the %-sign
*format++ = '%';
*format++ = *pos_userformat;
continue;
}
/* If a format specifier was found and used, copy the result from
* 'tempstr' to 'format'.
*/
if (tempstr[0]) {
while ((*format = *tempstr++) != '\0')
++format;
continue;
}
}
/* For any other character than % we just simply copy the character */
*format++ = *pos_userformat;
}
*format = '\0';
format = formatstring;
return strftime(s, max, format, tm);
}
/**
* motion_log
*
* This routine is used for printing all informational, debug or error
* messages produced by any of the other motion functions. It always
* produces a message of the form "[n] {message}", and (if the param
* 'errno_flag' is set) follows the message with the associated error
* message from the library.
*
* Parameters:
*
* level logging level for the 'syslog' function
* (-1 implies no syslog message should be produced)
* errno_flag if set, the log message should be followed by the
* error message.
* fmt the format string for producing the message
* ap variable-length argument list
*
* Returns:
* Nothing
*/
void motion_log(int level, int errno_flag, const char *fmt, ...)
{
int errno_save, n;
char buf[1024];
#if (!defined(BSD))
char msg_buf[100];
#endif
va_list ap;
int threadnr;
/* If pthread_getspecific fails (e.g., because the thread's TLS doesn't
* contain anything for thread number, it returns NULL which casts to zero,
* which is nice because that's what we want in that case.
*/
threadnr = (unsigned long)pthread_getspecific(tls_key_threadnr);
/*
* First we save the current 'error' value. This is required because
* the subsequent calls to vsnprintf could conceivably change it!
*/
errno_save = errno;
/* Prefix the message with the thread number */
n = snprintf(buf, sizeof(buf), "[%d] ", threadnr);
/* Next add the user's message */
va_start(ap, fmt);
n += vsnprintf(buf + n, sizeof(buf) - n, fmt, ap);
/* If errno_flag is set, add on the library error message */
if (errno_flag) {
strcat(buf, ": ");
n += 2;
/*
* this is bad - apparently gcc/libc wants to use the non-standard GNU
* version of strerror_r, which doesn't actually put the message into
* my buffer :-(. I have put in a 'hack' to get around this.
*/
#if (defined(BSD))
strerror_r(errno_save, buf + n, sizeof(buf) - n); /* 2 for the ': ' */
#else
strcat(buf, strerror_r(errno_save, msg_buf, sizeof(msg_buf)));
#endif
}
/* If 'level' is not negative, send the message to the syslog */
if (level >= 0)
syslog(level, buf);
/* For printing to stderr we need to add a newline */
strcat(buf, "\n");
fputs(buf, stderr);
fflush(stderr);
/* Clean up the argument list routine */
va_end(ap);
}