Files
motion/motion.c
Mr-DaveDev eef702b3d7 Fix builds on musl based systems
* Fix pthread_setname_np detection

Commit 6617c6f2c8 replaced
AC_LINK_IFELSE with AC_COMPILE_IFELSE. This has broken the
pthread_setname_np detection as compilation will always succeed even if
pthread_setname_np is not available (if the function is not found, a
simple warning will be displayed in config.log).

The correct fix is to put back AC_LINK_IFELSE with -pthread in LIBS
otherwise compilation will fail on toolchain without pthread_setname_np.

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>

* Check for pthread_getname_np

On some toolchains (like musl), pthread_setname_np is available but not
pthread_getname_np so add this check in configure.ac

Signed-off-by: Fabrice Fontaine <fontaine.fabrice@gmail.com>

* Revision for detection of XSI vs GNU variants of strerror
2017-12-09 15:42:16 -07:00

3802 lines
136 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 "motion.h"
#include "ffmpeg.h"
#include "video_common.h"
#include "video_v4l2.h"
#include "video_loopback.h"
#include "conf.h"
#include "alg.h"
#include "track.h"
#include "event.h"
#include "picture.h"
#include "rotate.h"
#define IMAGE_BUFFER_FLUSH ((unsigned int)-1)
/**
* 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;
/* Set this when we want main to end or restart */
volatile unsigned int finish = 0;
/* Log file used instead of stderr and syslog */
FILE *ptr_logfile = NULL;
/**
* 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 int restart = 0;
static void imagepkt_init(struct image_data *img_data){
#ifdef HAVE_FFMPEG
/* Initialize av packets for ffmpeg_pass through */
av_init_packet(&img_data->packet_norm);
img_data->packet_norm.data = NULL;
img_data->packet_norm.size = 0;
av_init_packet(&img_data->packet_high);
img_data->packet_high.data = NULL;
img_data->packet_high.size = 0;
return;
#else /* No FFmpeg/Libav */
/* Stop compiler warnings */
if (img_data->packet_norm) img_data->packet_norm = 0;
return;
#endif /* End #ifdef HAVE_FFMPEG */
}
static void imagepkt_deinit(struct image_data *img_data){
#ifdef HAVE_FFMPEG
/* free the av packets for ffmpeg_pass through */
my_packet_unref(img_data->packet_norm);
my_packet_unref(img_data->packet_high);
return;
#else /* No FFmpeg/Libav */
/* Stop compiler warnings */
if (img_data->packet_norm) img_data->packet_norm = 1;
return;
#endif /* End #ifdef HAVE_FFMPEG */
}
/**
* 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(NTC, TYPE_ALL, NO_ERRNO, "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_norm = mymalloc(cnt->imgs.size_norm);
memset(tmp[i].image_norm, 0x80, cnt->imgs.size_norm); /* initialize to grey */
if (cnt->imgs.size_high > 0){
tmp[i].image_high = mymalloc(cnt->imgs.size_high);
memset(tmp[i].image_high, 0x80, cnt->imgs.size_high);
}
imagepkt_init(&tmp[i]);
}
}
/* Free the old ring */
free(cnt->imgs.image_ring);
/* Point to the new ring */
cnt->imgs.image_ring = tmp;
cnt->current_image = NULL;
cnt->imgs.image_ring_size = new_size;
cnt->imgs.image_ring_in = 0;
cnt->imgs.image_ring_out = 0;
}
}
}
/**
* 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)
{
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_norm);
if (cnt->imgs.size_high >0 ) free(cnt->imgs.image_ring[i].image_high);
imagepkt_deinit(&cnt->imgs.image_ring[i]);
}
/* Free the ring */
free(cnt->imgs.image_ring);
cnt->imgs.image_ring = NULL;
cnt->current_image = NULL;
cnt->imgs.image_ring_size = 0;
}
/**
* image_save_as_preview
*
* This routine is called when we detect motion and want to save an 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_norm;
/* Copy all info */
memcpy(&cnt->imgs.preview_image.image_norm, img, sizeof(struct image_data));
/* restore image pointer */
cnt->imgs.preview_image.image_norm = image;
/* Copy image */
memcpy(cnt->imgs.preview_image.image_norm, img->image_norm, cnt->imgs.size_norm);
/*
* 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;
/* draw locate box here when mode = LOCATE_PREVIEW */
if (cnt->locate_motion_mode == LOCATE_PREVIEW) {
if (cnt->locate_motion_style == LOCATE_BOX) {
alg_draw_location(&img->location, &cnt->imgs, cnt->imgs.width, cnt->imgs.preview_image.image_norm,
LOCATE_BOX, LOCATE_NORMAL, cnt->process_thisframe);
} else if (cnt->locate_motion_style == LOCATE_REDBOX) {
alg_draw_red_location(&img->location, &cnt->imgs, cnt->imgs.width, cnt->imgs.preview_image.image_norm,
LOCATE_REDBOX, LOCATE_NORMAL, cnt->process_thisframe);
} else if (cnt->locate_motion_style == LOCATE_CROSS) {
alg_draw_location(&img->location, &cnt->imgs, cnt->imgs.width, cnt->imgs.preview_image.image_norm,
LOCATE_CROSS, LOCATE_NORMAL, cnt->process_thisframe);
} else if (cnt->locate_motion_style == LOCATE_REDCROSS) {
alg_draw_red_location(&img->location, &cnt->imgs, cnt->imgs.width, cnt->imgs.preview_image.image_norm,
LOCATE_REDCROSS, LOCATE_NORMAL, cnt->process_thisframe);
}
}
}
/**
* 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 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)
{
int i;
/*The FALLTHROUGH is a special comment required by compiler. Do not edit it*/
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.
*/
/*FALLTHROUGH*/
case SIGINT:
/*FALLTHROUGH*/
case SIGQUIT:
/*FALLTHROUGH*/
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;
cnt_list[i]->webcontrol_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);
case SIGVTALRM:
printf("SIGVTALRM went off\n");
break;
}
}
/**
* 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;
}
/**
* 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);
/* use SIGVTALRM as a way to break out of the ioctl, don't restart */
sig_handler_action->sa_flags = 0;
sigaction(SIGVTALRM, sig_handler_action, NULL);
}
static void setup_signals_BSD(struct context *cnt){
#ifdef __OpenBSD__
/*
* FIXMARK
* Fixes zombie issue on OpenBSD 4.6
*/
struct sigaction sig_handler_action;
struct sigaction sigchild_action;
setup_signals(&sig_handler_action, &sigchild_action);
#else
/* Kill compiler warnings */
cnt->log_level = cnt->log_level;
#endif
}
/**
* 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(NTC, TYPE_ALL, NO_ERRNO, "Removed process id file (pid file).");
else
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Error removing pid file");
}
if (ptr_logfile) {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Closing logfile (%s).",
cnt_list[0]->conf.log_file);
myfclose(ptr_logfile);
set_log_mode(LOGMODE_NONE);
ptr_logfile = NULL;
}
}
/**
* motion_detected
*
* Called from 'motion_loop' when motion is detected
* Can be called when no motion if emulate_motion 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_motion_mode == LOCATE_ON) {
if (cnt->locate_motion_style == LOCATE_BOX) {
alg_draw_location(location, imgs, imgs->width, img->image_norm, LOCATE_BOX,
LOCATE_BOTH, cnt->process_thisframe);
} else if (cnt->locate_motion_style == LOCATE_REDBOX) {
alg_draw_red_location(location, imgs, imgs->width, img->image_norm, LOCATE_REDBOX,
LOCATE_BOTH, cnt->process_thisframe);
} else if (cnt->locate_motion_style == LOCATE_CROSS) {
alg_draw_location(location, imgs, imgs->width, img->image_norm, LOCATE_CROSS,
LOCATE_BOTH, cnt->process_thisframe);
} else if (cnt->locate_motion_style == LOCATE_REDCROSS) {
alg_draw_red_location(location, imgs, imgs->width, img->image_norm, LOCATE_REDCROSS,
LOCATE_BOTH, cnt->process_thisframe);
}
}
/* 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_tv.tv_sec;
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, &img->timestamp_tv, NULL, 0);
/* EVENT_FIRSTMOTION triggers on_event_start_command and event_ffmpeg_newfile */
event(cnt, EVENT_FIRSTMOTION, img, NULL, NULL,
&cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tv);
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "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_tv);
}
/* Limit framerate */
if (img->shot < conf->frame_limit) {
/*
* If config option stream_motion is enabled, send the latest motion detected image
* to the stream 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 stream.
* We also disable this in setup_mode.
*/
if (conf->stream_motion && !conf->setup_mode && img->shot != 1)
event(cnt, EVENT_STREAM, img, NULL, NULL, &img->timestamp_tv);
/*
* 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_tv);
}
/* if track enabled and auto track on */
if (cnt->track.type && cnt->track.active)
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
*/
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 */
assert(cnt->imgs.image_ring_out < cnt->imgs.image_ring_size);
if ((cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & (IMAGE_SAVE | IMAGE_SAVED)) != IMAGE_SAVE)
break;
/* Set inte global context 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) {
if (cnt->log_level >= DBG) {
char tmp[32];
const char *t;
if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_TRIGGER)
t = "Trigger";
else if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_MOTION)
t = "Motion";
else if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_PRECAP)
t = "Precap";
else if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_POSTCAP)
t = "Postcap";
else
t = "Other";
mystrftime(cnt, tmp, sizeof(tmp), "%H%M%S-%q",
&cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tv, NULL, 0);
draw_text(cnt->imgs.image_ring[cnt->imgs.image_ring_out].image_norm, 10, 20,
cnt->imgs.width, tmp, cnt->conf.text_double);
draw_text(cnt->imgs.image_ring[cnt->imgs.image_ring_out].image_norm, 10, 30,
cnt->imgs.width, t, cnt->conf.text_double);
}
/* Output the picture to jpegs and ffmpeg */
event(cnt, EVENT_IMAGE_DETECTED,
&cnt->imgs.image_ring[cnt->imgs.image_ring_out], NULL, NULL,
&cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tv);
/*
* Check if we must add any "filler" frames into movie to keep up fps
* Only if we are recording videos ( ffmpeg or extenal pipe )
* While the overall elapsed time might be correct, if there are
* many duplicated frames, say 10 fps, 5 duplicated, the video will
* look like it is frozen every second for half a second.
*/
if (!cnt->conf.ffmpeg_duplicate_frames) {
/* don't duplicate frames */
} else if ((cnt->imgs.image_ring[cnt->imgs.image_ring_out].shot == 0) &&
(cnt->ffmpeg_output || (cnt->conf.useextpipe && cnt->extpipe))) {
/*
* movie_last_shoot is -1 when file is created,
* we don't know how many frames there is in first sec
*/
if (cnt->movie_last_shot >= 0) {
if (cnt_list[0]->log_level >= DBG) {
int frames = cnt->movie_fps - (cnt->movie_last_shot + 1);
if (frames > 0) {
char tmp[25];
MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "Added %d fillerframes into movie",
frames);
sprintf(tmp, "Fillerframes %d", frames);
draw_text(cnt->imgs.image_ring[cnt->imgs.image_ring_out].image_norm, 10, 40,
cnt->imgs.width, tmp, cnt->conf.text_double);
}
}
/* Check how many frames it was last sec */
while ((cnt->movie_last_shot + 1) < cnt->movie_fps) {
/* Add a filler frame into encoder */
event(cnt, EVENT_FFMPEG_PUT,
&cnt->imgs.image_ring[cnt->imgs.image_ring_out], NULL, NULL,
&cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tv);
cnt->movie_last_shot++;
}
}
cnt->movie_last_shot = 0;
} else if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].shot != (cnt->movie_last_shot + 1)) {
/* We are out of sync! Propably we got motion - no motion - motion */
cnt->movie_last_shot = -1;
}
/*
* Save last shot added to movie
* only when we not are within first sec
*/
if (cnt->movie_last_shot >= 0)
cnt->movie_last_shot = cnt->imgs.image_ring[cnt->imgs.image_ring_out].shot;
}
/* 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 has motion */
if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_MOTION) {
/* Check for most significant preview-shot when output_pictures=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_pictures=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;
}
static int init_camera_type(struct context *cnt){
cnt->camera_type = CAMERA_TYPE_UNKNOWN;
#ifdef HAVE_MMAL
if (cnt->conf.mmalcam_name) {
cnt->camera_type = CAMERA_TYPE_MMAL;
return 0;
}
#endif // HAVE_MMAL
if (cnt->conf.netcam_url) {
if ((strncmp(cnt->conf.netcam_url,"mjpeg",5) == 0) ||
(strncmp(cnt->conf.netcam_url,"v4l2" ,4) == 0) ||
(strncmp(cnt->conf.netcam_url,"rtmp" ,4) == 0) ||
(strncmp(cnt->conf.netcam_url,"rtsp" ,4) == 0)) {
cnt->camera_type = CAMERA_TYPE_RTSP;
} else {
cnt->camera_type = CAMERA_TYPE_NETCAM;
}
return 0;
}
#ifdef HAVE_BKTR
if (strncmp(cnt->conf.video_device,"/dev/bktr",9) == 0) {
cnt->camera_type = CAMERA_TYPE_BKTR;
return 0;
}
#endif // HAVE_BKTR
#ifdef HAVE_V4L2
if (cnt->conf.video_device) {
cnt->camera_type = CAMERA_TYPE_V4L2;
return 0;
}
#endif // HAVE_V4L2
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Unable to determine camera type (MMAL, Netcam, V4L2, BKTR)");
return -1;
}
static void init_mask_privacy(struct context *cnt){
int indxrow, indxcol;
int start_cr, offset_cb, start_cb;
int y_index, uv_index;
int indx_img, indx_max; /* Counter and max for norm/high */
int indx_width, indx_height;
unsigned char *img_temp, *img_temp_uv;
FILE *picture;
/* Load the privacy file if any */
cnt->imgs.mask_privacy = NULL;
cnt->imgs.mask_privacy_uv = NULL;
cnt->imgs.mask_privacy_high = NULL;
cnt->imgs.mask_privacy_high_uv = NULL;
if (cnt->conf.mask_privacy) {
if ((picture = myfopen(cnt->conf.mask_privacy, "r"))) {
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Opening privacy mask file");
/*
* 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_privacy = get_pgm(picture, cnt->imgs.width, cnt->imgs.height);
/* We only need the "or" mask for the U & V chrominance area. */
cnt->imgs.mask_privacy_uv = mymalloc((cnt->imgs.height * cnt->imgs.width) / 2);
if (cnt->imgs.size_high > 0){
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Opening high resolution privacy mask file");
rewind(picture);
cnt->imgs.mask_privacy_high = get_pgm(picture, cnt->imgs.width_high, cnt->imgs.height_high);
cnt->imgs.mask_privacy_high_uv = mymalloc((cnt->imgs.height_high * cnt->imgs.width_high) / 2);
}
myfclose(picture);
} else {
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Error opening mask file %s",
cnt->conf.mask_privacy);
/* Try to write an empty mask file to make it easier for the user to edit it */
put_fixed_mask(cnt, cnt->conf.mask_privacy);
}
if (!cnt->imgs.mask_privacy) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Failed to read mask privacy image. Mask privacy feature disabled.");
} else {
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Mask privacy file \"%s\" loaded.", cnt->conf.mask_privacy);
indx_img = 1;
indx_max = 1;
if (cnt->imgs.size_high > 0) indx_max = 2;
while (indx_img <= indx_max){
if (indx_img == 1){
start_cr = (cnt->imgs.height * cnt->imgs.width);
offset_cb = ((cnt->imgs.height * cnt->imgs.width)/4);
start_cb = start_cr + offset_cb;
indx_width = cnt->imgs.width;
indx_height = cnt->imgs.height;
img_temp = cnt->imgs.mask_privacy;
img_temp_uv = cnt->imgs.mask_privacy_uv;
} else {
start_cr = (cnt->imgs.height_high * cnt->imgs.width_high);
offset_cb = ((cnt->imgs.height_high * cnt->imgs.width_high)/4);
start_cb = start_cr + offset_cb;
indx_width = cnt->imgs.width_high;
indx_height = cnt->imgs.height_high;
img_temp = cnt->imgs.mask_privacy_high;
img_temp_uv = cnt->imgs.mask_privacy_high_uv;
}
for (indxrow = 0; indxrow < indx_height; indxrow++) {
for (indxcol = 0; indxcol < indx_width; indxcol++) {
y_index = indxcol + (indxrow * indx_width);
if (img_temp[y_index] == 0xff) {
if ((indxcol % 2 == 0) && (indxrow % 2 == 0) ){
uv_index = (indxcol/2) + ((indxrow * indx_width)/4);
img_temp[start_cr + uv_index] = 0xff;
img_temp[start_cb + uv_index] = 0xff;
img_temp_uv[uv_index] = 0x00;
img_temp_uv[offset_cb + uv_index] = 0x00;
}
} else {
img_temp[y_index] = 0x00;
if ((indxcol % 2 == 0) && (indxrow % 2 == 0) ){
uv_index = (indxcol/2) + ((indxrow * indx_width)/4);
img_temp[start_cr + uv_index] = 0x00;
img_temp[start_cb + uv_index] = 0x00;
img_temp_uv[uv_index] = 0x80;
img_temp_uv[offset_cb + uv_index] = 0x80;
}
}
}
}
indx_img++;
}
}
}
}
/**
* motion_init
*
* This routine is called from motion_loop (the main thread of the program) to do
* all of the initialization 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
* -3 Fatal error, image dimensions are not modulo 8
*/
static int motion_init(struct context *cnt)
{
FILE *picture;
int indx;
util_threadname_set("ml",cnt->threadnr,cnt->conf.camera_name);
/* 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;
/* Make sure to default the high res to zero */
cnt->imgs.width_high = 0;
cnt->imgs.height_high = 0;
cnt->imgs.size_high = 0;
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Camera %d started: motion detection %s",
cnt->conf.camera_id, cnt->pause ? "Disabled":"Enabled");
if (!cnt->conf.filepath)
cnt->conf.filepath = mystrdup(".");
if (init_camera_type(cnt) != 0 ) return -3;
if ((cnt->camera_type != CAMERA_TYPE_RTSP) &&
(cnt->conf.ffmpeg_passthrough)) {
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Pass-through processing disabled.");
cnt->conf.ffmpeg_passthrough = 0;
}
if ((cnt->conf.height == 0) || (cnt->conf.width == 0)) {
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Invalid configuration dimensions %dx%d",cnt->conf.height,cnt->conf.width);
cnt->conf.height = DEF_HEIGHT;
cnt->conf.width = DEF_WIDTH;
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Using default dimensions %dx%d",cnt->conf.height,cnt->conf.width);
}
/* 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 == -1) {
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Could not fetch initial image from camera ");
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "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_norm = cnt->conf.width * cnt->conf.height * 3 / 2;
cnt->imgs.motionsize = cnt->conf.width * cnt->conf.height;
cnt->imgs.type = VIDEO_PALETTE_YUV420P;
} else if (cnt->video_dev == -2) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Could not fetch initial image from camera ");
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Motion only supports width and height modulo 8");
return -3;
}
/* We set size_high here so that it can be used in the retry function to determine whether
* we need to break and reallocate buffers
*/
cnt->imgs.size_high = (cnt->imgs.width_high * cnt->imgs.height_high * 3) / 2;
image_ring_resize(cnt, 1); /* Create a initial precapture ring buffer with 1 frame */
cnt->imgs.ref = mymalloc(cnt->imgs.size_norm);
cnt->imgs.img_motion.image_norm = mymalloc(cnt->imgs.size_norm);
/* contains the moving objects of ref. frame */
cnt->imgs.ref_dyn = mymalloc(cnt->imgs.motionsize * sizeof(*cnt->imgs.ref_dyn));
cnt->imgs.image_virgin.image_norm = mymalloc(cnt->imgs.size_norm);
imagepkt_init(&cnt->imgs.image_virgin);
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));
/* Set output picture type */
if (!strcmp(cnt->conf.picture_type, "ppm"))
cnt->imgs.picture_type = IMAGE_TYPE_PPM;
else if (!strcmp(cnt->conf.picture_type, "webp")) {
#ifdef HAVE_WEBP
cnt->imgs.picture_type = IMAGE_TYPE_WEBP;
#else
/* Fallback to jpeg if webp was selected in the config file, but the support for it was not compiled in */
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "webp image format is not available, failing back to jpeg");
cnt->imgs.picture_type = IMAGE_TYPE_JPEG;
#endif /* HAVE_WEBP */
}
else
cnt->imgs.picture_type = IMAGE_TYPE_JPEG;
/* allocate buffer here for preview buffer */
cnt->imgs.preview_image.image_norm = mymalloc(cnt->imgs.size_norm);
/*
* 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) {
int i;
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.image_norm, 0x80, cnt->imgs.size_norm); /* initialize to grey */
draw_text(cnt->imgs.image_virgin.image_norm, 10, 20, cnt->imgs.width,
"Error capturing first image", cnt->conf.text_double);
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Error capturing first image");
}
}
/* create a reference frame */
alg_update_reference_frame(cnt, RESET_REF_FRAME);
#if defined(HAVE_V4L2) && !defined(__FreeBSD__)
/* open video loopback devices if enabled */
if (cnt->conf.vidpipe) {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Opening video loopback device for normal pictures");
/* vid_startpipe should get the output dimensions */
cnt->pipe = vlp_startpipe(cnt->conf.vidpipe, cnt->imgs.width, cnt->imgs.height);
if (cnt->pipe < 0) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Failed to open video loopback for normal pictures");
return -1;
}
}
if (cnt->conf.motionvidpipe) {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Opening video loopback device for motion pictures");
/* vid_startpipe should get the output dimensions */
cnt->mpipe = vlp_startpipe(cnt->conf.motionvidpipe, cnt->imgs.width, cnt->imgs.height);
if (cnt->mpipe < 0) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Failed to open video loopback for motion pictures");
return -1;
}
}
#endif /* HAVE_V4L2 && !__FreeBSD__ */
#if defined(HAVE_MYSQL) || defined(HAVE_PGSQL) || defined(HAVE_SQLITE3)
if (cnt->conf.database_type) {
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "Database backend %s",
cnt->conf.database_type);
#ifdef HAVE_SQLITE3
/* if database_sqlite3 is NULL then we are using a non threaded version of
* sqlite3 and will need a seperate connection for each thread */
if (cnt->database_sqlite3) {
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "SQLite3 using shared handle");
} else if ((!strcmp(cnt->conf.database_type, "sqlite3")) && cnt->conf.database_dbname) {
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "SQLite3 Database filename %s",
cnt->conf.database_dbname);
if (sqlite3_open(cnt->conf.database_dbname, &cnt->database_sqlite3) != SQLITE_OK) {
MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, "Can't open database %s : %s",
cnt->conf.database_dbname, sqlite3_errmsg(cnt->database_sqlite3));
sqlite3_close(cnt->database_sqlite3);
exit(1);
}
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "database_busy_timeout %d msec",
cnt->conf.database_busy_timeout);
if (sqlite3_busy_timeout(cnt->database_sqlite3, cnt->conf.database_busy_timeout) != SQLITE_OK)
MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, "database_busy_timeout failed %s",
sqlite3_errmsg(cnt->database_sqlite3));
}
#endif /* HAVE_SQLITE3 */
#ifdef HAVE_MYSQL
if ((!strcmp(cnt->conf.database_type, "mysql")) && (cnt->conf.database_dbname)) {
// close database to be sure that we are not leaking
mysql_close(cnt->database);
cnt->database_event_id = 0;
cnt->database = mymalloc(sizeof(MYSQL));
mysql_init(cnt->database);
if (!mysql_real_connect(cnt->database, cnt->conf.database_host, cnt->conf.database_user,
cnt->conf.database_password, cnt->conf.database_dbname, 0, NULL, 0)) {
MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, "Cannot connect to MySQL database %s on host %s with user %s",
cnt->conf.database_dbname, cnt->conf.database_host,
cnt->conf.database_user);
MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, "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 ((!strcmp(cnt->conf.database_type, "postgresql")) && (cnt->conf.database_dbname)) {
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.database_dbname, /* dbname */
(cnt->conf.database_host ? cnt->conf.database_host : ""), /* host (may be blank) */
(cnt->conf.database_user ? cnt->conf.database_user : ""), /* user (may be blank) */
(cnt->conf.database_password ? cnt->conf.database_password : ""), /* password (may be blank) */
cnt->conf.database_port
);
cnt->database_pg = PQconnectdb(connstring);
if (PQstatus(cnt->database_pg) == CONNECTION_BAD) {
MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, "Connection to PostgreSQL database '%s' failed: %s",
cnt->conf.database_dbname, PQerrorMessage(cnt->database_pg));
return -2;
}
}
#endif /* 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_movie * (FTYPE_MPEG + FTYPE_MPEG_MOTION) +
cnt->conf.sql_log_timelapse * FTYPE_MPEG_TIMELAPSE;
}
#endif /* defined(HAVE_MYSQL) || defined(HAVE_PGSQL) || defined(HAVE_SQLITE3) */
/* Load the mask file if any */
if (cnt->conf.mask_file) {
if ((picture = myfopen(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);
myfclose(picture);
} else {
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "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(ERR, TYPE_ALL, NO_ERRNO, "Failed to read mask image. Mask feature disabled.");
} else {
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Maskfile \"%s\" loaded.",
cnt->conf.mask_file);
}
} else {
cnt->imgs.mask = NULL;
}
init_mask_privacy(cnt);
/* 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 stream server if stream port is specified to not 0 */
if (cnt->conf.stream_port) {
if (stream_init (&(cnt->stream), cnt->conf.stream_port, cnt->conf.stream_localhost,
cnt->conf.ipv6_enabled) == -1) {
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Problem enabling motion-stream server in port %d",
cnt->conf.stream_port);
cnt->conf.stream_port = 0;
cnt->finish = 1;
} else {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Started motion-stream server on port %d (auth %s)",
cnt->conf.stream_port, cnt->conf.stream_auth_method ? "Enabled":"Disabled");
}
}
/* Initialize 50% scaled substream server if substream port is specified to not 0
But only if dimensions are 8-modulo after scaling. Otherwise disable substream */
if (cnt->conf.substream_port){
if ((cnt->conf.width / 2) % 8 == 0 && (cnt->conf.height / 2) % 8 == 0
&& cnt->imgs.type == VIDEO_PALETTE_YUV420P){
if (stream_init (&(cnt->substream), cnt->conf.substream_port, cnt->conf.stream_localhost,
cnt->conf.ipv6_enabled) == -1) {
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Problem enabling motion-substream server in port %d",
cnt->conf.substream_port);
cnt->conf.substream_port = 0;
cnt->finish = 1;
} else {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Started motion-substream server on port %d (auth %s)",
cnt->conf.substream_port, cnt->conf.stream_auth_method ? "Enabled":"Disabled");
}
}
else
{
/* TODO support GRAY scale */
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Subtream does not support GRAY, and original resolution must be modulo of 16");
cnt->conf.substream_port = 0;
}
}
/* Prevent first few frames from triggering motion... */
cnt->moved = 8;
/* Initialize the double sized characters if needed. */
if (cnt->conf.text_double)
cnt->text_size_factor = 2;
else
cnt->text_size_factor = 1;
/* Work out expected frame rate based on config setting */
if (cnt->conf.frame_limit < 2)
cnt->conf.frame_limit = 2;
/* 2 sec startup delay so FPS is calculated correct */
cnt->startup_frames = (cnt->conf.frame_limit * 2) + cnt->conf.pre_capture + cnt->conf.minimum_motion_frames;
cnt->required_frame_time = 1000000L / cnt->conf.frame_limit;
cnt->frame_delay = cnt->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.
*/
cnt->rolling_average_data = NULL;
cnt->rolling_average_limit = 10 * cnt->conf.frame_limit;
cnt->rolling_average_data = mymalloc(sizeof(cnt->rolling_average_data) * cnt->rolling_average_limit);
/* Preset history buffer with expected frame rate */
for (indx = 0; indx < cnt->rolling_average_limit; indx++)
cnt->rolling_average_data[indx] = cnt->required_frame_time;
if (cnt->track.type)
cnt->moved = track_center(cnt, cnt->video_dev, 0, 0, 0);
setup_signals_BSD(cnt);
/* Initialize area detection */
cnt->area_minx[0] = cnt->area_minx[3] = cnt->area_minx[6] = 0;
cnt->area_miny[0] = cnt->area_miny[1] = cnt->area_miny[2] = 0;
cnt->area_minx[1] = cnt->area_minx[4] = cnt->area_minx[7] = cnt->imgs.width / 3;
cnt->area_maxx[0] = cnt->area_maxx[3] = cnt->area_maxx[6] = cnt->imgs.width / 3;
cnt->area_minx[2] = cnt->area_minx[5] = cnt->area_minx[8] = cnt->imgs.width / 3 * 2;
cnt->area_maxx[1] = cnt->area_maxx[4] = cnt->area_maxx[7] = cnt->imgs.width / 3 * 2;
cnt->area_miny[3] = cnt->area_miny[4] = cnt->area_miny[5] = cnt->imgs.height / 3;
cnt->area_maxy[0] = cnt->area_maxy[1] = cnt->area_maxy[2] = cnt->imgs.height / 3;
cnt->area_miny[6] = cnt->area_miny[7] = cnt->area_miny[8] = cnt->imgs.height / 3 * 2;
cnt->area_maxy[3] = cnt->area_maxy[4] = cnt->area_maxy[5] = cnt->imgs.height / 3 * 2;
cnt->area_maxx[2] = cnt->area_maxx[5] = cnt->area_maxx[8] = cnt->imgs.width;
cnt->area_maxy[6] = cnt->area_maxy[7] = cnt->area_maxy[8] = cnt->imgs.height;
cnt->areadetect_eventnbr = 0;
cnt->timenow = 0;
cnt->timebefore = 0;
cnt->rate_limit = 0;
cnt->lastframetime = 0;
cnt->minimum_frame_time_downcounter = cnt->conf.minimum_frame_time;
cnt->get_image = 1;
cnt->olddiffs = 0;
cnt->smartmask_ratio = 0;
cnt->smartmask_count = 20;
cnt->previous_diffs = 0;
cnt->previous_location_x = 0;
cnt->previous_location_y = 0;
cnt->time_last_frame = 1;
cnt->time_current_frame = 0;
cnt->smartmask_lastrate = 0;
cnt->passflag = 0; //only purpose to flag first frame
cnt->rolling_frame = 0;
if (cnt->conf.emulate_motion) {
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Emulating motion");
}
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 stream */
event(cnt, EVENT_STOP, NULL, NULL, NULL, NULL);
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, NULL);
event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, NULL);
if (cnt->video_dev >= 0) {
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Calling vid_close() from motion_cleanup");
vid_close(cnt);
}
free(cnt->imgs.img_motion.image_norm);
cnt->imgs.img_motion.image_norm = NULL;
free(cnt->imgs.ref);
cnt->imgs.ref = NULL;
free(cnt->imgs.ref_dyn);
cnt->imgs.ref_dyn = NULL;
free(cnt->imgs.image_virgin.image_norm);
cnt->imgs.image_virgin.image_norm = NULL;
imagepkt_deinit(&cnt->imgs.image_virgin);
free(cnt->imgs.labels);
cnt->imgs.labels = NULL;
free(cnt->imgs.labelsize);
cnt->imgs.labelsize = NULL;
free(cnt->imgs.smartmask);
cnt->imgs.smartmask = NULL;
free(cnt->imgs.smartmask_final);
cnt->imgs.smartmask_final = NULL;
free(cnt->imgs.smartmask_buffer);
cnt->imgs.smartmask_buffer = NULL;
if (cnt->imgs.mask) free(cnt->imgs.mask);
cnt->imgs.mask = NULL;
if (cnt->imgs.mask_privacy) free(cnt->imgs.mask_privacy);
cnt->imgs.mask_privacy = NULL;
if (cnt->imgs.mask_privacy_uv) free(cnt->imgs.mask_privacy_uv);
cnt->imgs.mask_privacy_uv = NULL;
if (cnt->imgs.mask_privacy_high) free(cnt->imgs.mask_privacy_high);
cnt->imgs.mask_privacy_high = NULL;
if (cnt->imgs.mask_privacy_high_uv) free(cnt->imgs.mask_privacy_high_uv);
cnt->imgs.mask_privacy_high_uv = NULL;
free(cnt->imgs.common_buffer);
cnt->imgs.common_buffer = NULL;
free(cnt->imgs.preview_image.image_norm);
cnt->imgs.preview_image.image_norm = 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 */
free(cnt->currenttime_tm);
cnt->currenttime_tm = NULL;
/* Cleanup the event time structure */
free(cnt->eventtime_tm);
cnt->eventtime_tm = NULL;
if (cnt->conf.database_type) {
#ifdef HAVE_MYSQL
if ( (!strcmp(cnt->conf.database_type, "mysql")) && (cnt->conf.database_dbname)) {
mysql_close(cnt->database);
cnt->database_event_id = 0;
}
#endif /* HAVE_MYSQL */
#ifdef HAVE_PGSQL
if ((!strcmp(cnt->conf.database_type, "postgresql")) && (cnt->conf.database_dbname)) {
PQfinish(cnt->database_pg);
}
#endif /* HAVE_PGSQL */
#ifdef HAVE_SQLITE3
/* Close the SQLite database */
if ((!strcmp(cnt->conf.database_type, "sqlite3")) && (cnt->conf.database_dbname)) {
sqlite3_close(cnt->database_sqlite3);
}
#endif /* HAVE_SQLITE3 */
}
}
static void mlp_mask_privacy(struct context *cnt){
if (cnt->imgs.mask_privacy == NULL) return;
/*
* This function uses long operations to process 4 (32 bit) or 8 (64 bit)
* bytes at a time, providing a significant boost in performance.
* Then a trailer loop takes care of any remaining bytes.
*/
unsigned char *image;
const unsigned char *mask;
const unsigned char *maskuv;
int index_y;
int index_crcb;
int increment;
int indx_img; /* Counter for how many images we need to apply the mask to */
int indx_max; /* 1 if we are only doing norm, 2 if we are doing both norm and high */
indx_img = 1;
indx_max = 1;
if (cnt->imgs.size_high > 0) indx_max = 2;
increment = sizeof(unsigned long);
while (indx_img <= indx_max){
if (indx_img == 1) {
/* Normal Resolution */
index_y = cnt->imgs.height * cnt->imgs.width;
image = cnt->current_image->image_norm;
mask = cnt->imgs.mask_privacy;
index_crcb = cnt->imgs.size_norm - index_y;
maskuv = cnt->imgs.mask_privacy_uv;
} else {
/* High Resolution */
index_y = cnt->imgs.height_high * cnt->imgs.width_high;
image = cnt->current_image->image_high;
mask = cnt->imgs.mask_privacy_high;
index_crcb = cnt->imgs.size_high - index_y;
maskuv = cnt->imgs.mask_privacy_high_uv;
}
while (index_y >= increment) {
*((unsigned long *)image) &= *((unsigned long *)mask);
image += increment;
mask += increment;
index_y -= increment;
}
while (--index_y >= 0) {
*(image++) &= *(mask++);
}
/* Mask chrominance. */
while (index_crcb >= increment) {
index_crcb -= increment;
/*
* Replace the masked bytes with 0x080. This is done using two masks:
* the normal privacy mask is used to clear the masked bits, the
* "or" privacy mask is used to write 0x80. The benefit of that method
* is that we process 4 or 8 bytes in just two operations.
*/
*((unsigned long *)image) &= *((unsigned long *)mask);
mask += increment;
*((unsigned long *)image) |= *((unsigned long *)maskuv);
maskuv += increment;
image += increment;
}
while (--index_crcb >= 0) {
if (*(mask++) == 0x00) *image = 0x80; // Mask last remaining bytes.
image += 1;
}
indx_img++;
}
}
static void mlp_areadetect(struct context *cnt){
int i, j, z = 0;
/*
* 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 != cnt->areadetect_eventnbr) &&
(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; /* characters are stored as ascii 48-57 (0-9) */
if ((z >= 0) && (z < 9)) {
if (cnt->current_image->location.x > cnt->area_minx[z] &&
cnt->current_image->location.x < cnt->area_maxx[z] &&
cnt->current_image->location.y > cnt->area_miny[z] &&
cnt->current_image->location.y < cnt->area_maxy[z]) {
event(cnt, EVENT_AREA_DETECTED, NULL, NULL, NULL, &cnt->current_image->timestamp_tv);
cnt->areadetect_eventnbr = cnt->event_nr; /* Fire script only once per event */
MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "Motion in area %d detected.", z + 1);
break;
}
}
}
}
}
static void mlp_prepare(struct context *cnt){
int frame_buffer_size;
struct timeval tv1;
/***** MOTION LOOP - PREPARE FOR NEW FRAME SECTION *****/
cnt->watchdog = WATCHDOG_TMO;
/* Get current time and preserver last time for frame interval calc. */
/* This may be better at the end of the loop or moving the part in
* the end doing elapsed time calc in here
*/
cnt->timebefore = cnt->timenow;
gettimeofday(&tv1, NULL);
cnt->timenow = tv1.tv_usec + 1000000L * tv1.tv_sec;
/*
* Calculate detection rate limit. Above 5fps we limit the detection
* rate to 3fps to reduce load at higher framerates.
*/
cnt->process_thisframe = 0;
cnt->rate_limit++;
if (cnt->rate_limit >= (cnt->lastrate / 3)) {
cnt->rate_limit = 0;
cnt->process_thisframe = 1;
}
/*
* 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 (cnt->lastframetime != cnt->currenttime) {
cnt->lastrate = cnt->shots + 1;
cnt->shots = -1;
cnt->lastframetime = cnt->currenttime;
if (cnt->conf.minimum_frame_time) {
cnt->minimum_frame_time_downcounter--;
if (cnt->minimum_frame_time_downcounter == 0)
cnt->get_image = 1;
} else {
cnt->get_image = 1;
}
}
/* Increase the shots variable for each frame captured within this second */
cnt->shots++;
if (cnt->startup_frames > 0)
cnt->startup_frames--;
}
static void mlp_resetimages(struct context *cnt){
struct image_data *old_image;
if (cnt->conf.minimum_frame_time) {
cnt->minimum_frame_time_downcounter = cnt->conf.minimum_frame_time;
cnt->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. */
old_image = cnt->current_image;
cnt->current_image = &cnt->imgs.image_ring[cnt->imgs.image_ring_in];
/* Init/clear current_image */
if (cnt->process_thisframe) {
/* 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;
} else if (cnt->current_image && old_image) {
/* not processing this frame: save some important values for next image */
cnt->current_image->diffs = old_image->diffs;
cnt->current_image->timestamp_tv = old_image->timestamp_tv;
cnt->current_image->shot = old_image->shot;
cnt->current_image->cent_dist = old_image->cent_dist;
cnt->current_image->flags = old_image->flags & (~IMAGE_SAVED);
cnt->current_image->location = old_image->location;
cnt->current_image->total_labels = old_image->total_labels;
}
/* Store time with pre_captured image */
gettimeofday(&cnt->current_image->timestamp_tv, NULL);
/* Store shot number with pre_captured image */
cnt->current_image->shot = cnt->shots;
}
static int mlp_retry(struct context *cnt){
/*
* If a camera is not available we keep on retrying every 10 seconds
* until it shows up.
*/
int size_high;
if (cnt->video_dev < 0 &&
cnt->currenttime % 10 == 0 && cnt->shots == 0) {
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "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(NTC, TYPE_ALL, NO_ERRNO, "Camera has finally become available\n"
"Camera image has different width and height"
"from what is in the config file. You should fix that\n"
"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
*/
return 1;
}
/*
* For high res, we check the size of buffer to determine whether to break out
* the init_motion function allocated the buffer for high using the cnt->imgs.size_high
* and the vid_start ONLY re-populates the height/width so we can check the size here.
*/
size_high = (cnt->imgs.width_high * cnt->imgs.height_high * 3) / 2;
if (cnt->imgs.size_high != size_high) return 1;
}
return 0;
}
static int mlp_capture(struct context *cnt){
const char *tmpin;
char tmpout[80];
int vid_return_code = 0; /* Return code used when calling vid_next */
struct timeval tv1;
/***** 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);
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(NTC, TYPE_ALL, NO_ERRNO, "Video signal re-acquired");
// event for re-acquired video signal can be called here
event(cnt, EVENT_CAMERA_FOUND, NULL, NULL, NULL, NULL);
}
cnt->missing_frame_counter = 0;
/*
* 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.image_norm, cnt->current_image->image_norm, cnt->imgs.size_norm);
mlp_mask_privacy(cnt);
/*
* 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);
cnt->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(ERR, TYPE_ALL, NO_ERRNO, "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_norm, cnt->imgs.image_virgin.image_norm, cnt->imgs.size_norm);
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 {
//MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "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(NTC, TYPE_ALL, NO_ERRNO, "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;
return 1;
}
/*
* 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_norm, cnt->imgs.image_virgin.image_norm, cnt->imgs.size_norm);
} else {
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";
tv1.tv_sec=cnt->connectionlosttime;
tv1.tv_usec = 0;
memset(cnt->current_image->image_norm, 0x80, cnt->imgs.size_norm);
mystrftime(cnt, tmpout, sizeof(tmpout), tmpin, &tv1, NULL, 0);
draw_text(cnt->current_image->image_norm, 10, 20 * cnt->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(NTC, TYPE_ALL, NO_ERRNO, "Video signal lost - Adding grey image");
// Event for lost video signal can be called from here
event(cnt, EVENT_CAMERA_LOST, NULL, NULL, NULL, &tv1);
}
/*
* 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(ERR, TYPE_ALL, NO_ERRNO, "Video signal still lost - "
"Trying to close video device");
vid_close(cnt);
}
}
}
return 0;
}
static void mlp_detection(struct context *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->process_thisframe) {
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.image_norm);
else
cnt->current_image->diffs = alg_diff(cnt, cnt->imgs.image_virgin.image_norm);
/* 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 > 1 && !cnt->lost_connection) {
if (alg_lightswitch(cnt, cnt->current_image->diffs)) {
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "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_norm);
if (cnt->current_image->diffs <= cnt->threshold) {
cnt->current_image->diffs = 0;
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "Switchfilter detected");
}
}
/*
* Despeckle feature
* First we run (as given by the despeckle_filter 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;
cnt->olddiffs = 0;
if (cnt->conf.despeckle_filter && cnt->current_image->diffs > 0) {
cnt->olddiffs = cnt->current_image->diffs;
cnt->current_image->diffs = alg_despeckle(cnt, 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;
}
}
//TODO: This section needs investigation for purpose, cause and effect
/* Manipulate smart_mask sensitivity (only every smartmask_ratio seconds) */
if ((cnt->smartmask_speed && (cnt->event_nr != cnt->prev_event)) &&
(!--cnt->smartmask_count)) {
alg_tune_smartmask(cnt);
cnt->smartmask_count = cnt->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;
}
}
static void mlp_tuning(struct context *cnt){
/***** 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) &&
(!cnt->detecting_motion && (cnt->current_image->diffs <= cnt->threshold)))
alg_noise_tune(cnt, cnt->imgs.image_virgin.image_norm);
/*
* If we are not noise tuning lets make sure that remote controlled
* changes of noise_level are used.
*/
if (cnt->process_thisframe) {
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: trying to auto-detect lightswitch events.
* 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.
*/
if ((cnt->current_image->diffs > cnt->threshold) && (cnt->conf.lightswitch == 1) &&
(cnt->lightswitch_framecounter < (cnt->lastrate * 2)) && /* two seconds window only */
/* number of changed pixels almost the same in two consecutive frames and */
((abs(cnt->previous_diffs - cnt->current_image->diffs)) < (cnt->previous_diffs / 15)) &&
/* center of motion in about the same place ? */
((abs(cnt->current_image->location.x - cnt->previous_location_x)) <= (cnt->imgs.width / 150)) &&
((abs(cnt->current_image->location.y - cnt->previous_location_y)) <= (cnt->imgs.height / 150))) {
alg_update_reference_frame(cnt, RESET_REF_FRAME);
cnt->current_image->diffs = 0;
cnt->lightswitch_framecounter = 0;
MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "micro-lightswitch!");
} else {
alg_update_reference_frame(cnt, UPDATE_REF_FRAME);
}
cnt->previous_diffs = cnt->current_image->diffs;
cnt->previous_location_x = cnt->current_image->location.x;
cnt->previous_location_y = cnt->current_image->location.y;
}
}
static void mlp_overlay(struct context *cnt){
char tmp[PATH_MAX];
/***** 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_output_debug ||
cnt->conf.setup_mode))
overlay_smartmask(cnt, cnt->imgs.img_motion.image_norm);
/* Largest labels overlay */
if (cnt->imgs.largest_label && (cnt->conf.motion_img || cnt->conf.ffmpeg_output_debug ||
cnt->conf.setup_mode))
overlay_largest_label(cnt, cnt->imgs.img_motion.image_norm);
/* Fixed mask overlay */
if (cnt->imgs.mask && (cnt->conf.motion_img || cnt->conf.ffmpeg_output_debug ||
cnt->conf.setup_mode))
overlay_fixed_mask(cnt, cnt->imgs.img_motion.image_norm);
/* Initialize the double sized characters if needed. */
if (cnt->conf.text_double && cnt->text_size_factor == 1) {
cnt->text_size_factor = 2;
/* If text_double is set to off, then reset the scaling text_size_factor. */
} else if (!cnt->conf.text_double && cnt->text_size_factor == 2) {
cnt->text_size_factor = 1;
}
/* Add changed pixels in upper right corner of the pictures */
if (cnt->conf.text_changes) {
if (!cnt->pause)
sprintf(tmp, "%d", cnt->current_image->diffs);
else
sprintf(tmp, "-");
draw_text(cnt->current_image->image_norm, cnt->imgs.width - 10, 10,
cnt->imgs.width, tmp, cnt->conf.text_double);
}
/*
* Add changed pixels to motion-images (for stream) in setup_mode
* and always overlay smartmask (not only when motion is detected)
*/
if (cnt->conf.setup_mode) {
sprintf(tmp, "D:%5d L:%3d N:%3d", cnt->current_image->diffs,
cnt->current_image->total_labels, cnt->noise);
draw_text(cnt->imgs.img_motion.image_norm, cnt->imgs.width - 10, cnt->imgs.height - 30 * cnt->text_size_factor,
cnt->imgs.width, tmp, cnt->conf.text_double);
sprintf(tmp, "THREAD %d SETUP", cnt->threadnr);
draw_text(cnt->imgs.img_motion.image_norm, cnt->imgs.width - 10, cnt->imgs.height - 10 * cnt->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) {
mystrftime(cnt, tmp, sizeof(tmp), cnt->conf.text_left,
&cnt->current_image->timestamp_tv, NULL, 0);
draw_text(cnt->current_image->image_norm, 10, cnt->imgs.height - 10 * cnt->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) {
mystrftime(cnt, tmp, sizeof(tmp), cnt->conf.text_right,
&cnt->current_image->timestamp_tv, NULL, 0);
draw_text(cnt->current_image->image_norm, cnt->imgs.width - 10,
cnt->imgs.height - 10 * cnt->text_size_factor,
cnt->imgs.width, tmp, cnt->conf.text_double);
}
}
static void mlp_actions(struct context *cnt){
int indx;
/***** 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 emulate_motion 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.emulate_motion && (cnt->startup_frames == 0)) {
cnt->detecting_motion = 1;
if (cnt->conf.post_capture > 0) {
/* Setup the postcap counter */
cnt->postcap = cnt->conf.post_capture;
// MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "(Em) Init post capture %d", cnt->postcap);
}
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 (indx = 0; indx < cnt->conf.minimum_motion_frames; indx++) {
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);
/* If we were previously detecting motion, started a movie, then got
* no motion then we reset the start movie time so that we do not
* get a pause in the movie.
*/
if ( (cnt->detecting_motion == 0) && (cnt->ffmpeg_output != NULL) )
ffmpeg_reset_movie_start_time(cnt->ffmpeg_output, &cnt->current_image->timestamp_tv);
cnt->detecting_motion = 1;
/* Setup the postcap counter */
cnt->postcap = cnt->conf.post_capture;
//MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "Setup post capture %d", cnt->postcap);
/* Mark all images in image_ring to be saved */
for (indx = 0; indx < cnt->imgs.image_ring_size; indx++)
cnt->imgs.image_ring[indx].flags |= IMAGE_SAVE;
} else if (cnt->postcap > 0) {
/* we have motion in this frame, but not enought frames for trigger. Check postcap */
cnt->current_image->flags |= (IMAGE_POSTCAP | IMAGE_SAVE);
cnt->postcap--;
//MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "post capture %d", 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 > 0) {
/* No motion, doing postcap */
cnt->current_image->flags |= (IMAGE_POSTCAP | IMAGE_SAVE);
cnt->postcap--;
//MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "post capture %d", cnt->postcap);
} else {
/* Done with postcap, so just have the image in the precap buffer */
cnt->current_image->flags |= IMAGE_PRECAP;
/* gapless movie feature */
if ((cnt->conf.event_gap == 0) && (cnt->detecting_motion == 1))
cnt->makemovie = 1;
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_tv.tv_sec;
mlp_areadetect(cnt);
/*
* Is the movie too long? Then make movies
* First test for max_movie_time
*/
if ((cnt->conf.max_movie_time && cnt->event_nr == cnt->prev_event) &&
(cnt->currenttime - cnt->eventtime >= cnt->conf.max_movie_time))
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.event_gap) && cnt->conf.event_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->current_image->timestamp_tv);
/*
* 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);
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "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);
}
static void mlp_setupmode(struct context *cnt){
/***** MOTION LOOP - SETUP MODE CONSOLE OUTPUT SECTION *****/
/* If CAMERA_VERBOSE enabled output some numbers to console */
if (cnt->conf.setup_mode) {
char msg[1024] = "\0";
char part[100];
if (cnt->conf.despeckle_filter) {
snprintf(part, 99, "Raw changes: %5d - changes after '%s': %5d",
cnt->olddiffs, cnt->conf.despeckle_filter, cnt->current_image->diffs);
strcat(msg, part);
if (strchr(cnt->conf.despeckle_filter, '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(INF, TYPE_ALL, NO_ERRNO, "%s", msg);
}
}
static void mlp_snapshot(struct context *cnt){
/***** 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 */
cnt->time_current_frame = cnt->currenttime;
if ((cnt->conf.snapshot_interval > 0 && cnt->shots == 0 &&
cnt->time_current_frame % cnt->conf.snapshot_interval <= cnt->time_last_frame % cnt->conf.snapshot_interval) ||
cnt->snapshot) {
event(cnt, EVENT_IMAGE_SNAPSHOT, cnt->current_image, NULL, NULL, &cnt->current_image->timestamp_tv);
cnt->snapshot = 0;
}
}
static void mlp_timelapse(struct context *cnt){
struct tm timestamp_tm;
if (cnt->conf.timelapse_interval) {
localtime_r(&cnt->current_image->timestamp_tv.tv_sec, &timestamp_tm);
/*
* 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 (timestamp_tm.tm_min == 0 &&
(cnt->time_current_frame % 60 < cnt->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 (timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tv);
/* handle the hourly case */
} else if (strcasecmp(cnt->conf.timelapse_mode, "hourly") == 0) {
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tv);
/* If we are weekly-sunday, raise timelapseend event at midnight on sunday */
} else if (strcasecmp(cnt->conf.timelapse_mode, "weekly-sunday") == 0) {
if (timestamp_tm.tm_wday == 0 &&
timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tv);
/* If we are weekly-monday, raise timelapseend event at midnight on monday */
} else if (strcasecmp(cnt->conf.timelapse_mode, "weekly-monday") == 0) {
if (timestamp_tm.tm_wday == 1 &&
timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tv);
/* If we are monthly, raise timelapseend event at midnight on first day of month */
} else if (strcasecmp(cnt->conf.timelapse_mode, "monthly") == 0) {
if (timestamp_tm.tm_mday == 1 &&
timestamp_tm.tm_hour == 0)
event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tv);
/* If invalid we report in syslog once and continue in manual mode */
} else {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Invalid timelapse_mode argument '%s'",
cnt->conf.timelapse_mode);
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "%:s 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 movie.
*/
if (cnt->shots == 0 && cnt->time_current_frame % cnt->conf.timelapse_interval <=
cnt->time_last_frame % cnt->conf.timelapse_interval) {
event(cnt, EVENT_TIMELAPSE, cnt->current_image, NULL, NULL,
&cnt->current_image->timestamp_tv);
}
} else if (cnt->ffmpeg_timelapse) {
/*
* If timelapse movie is in progress but conf.timelapse_interval 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->current_image->timestamp_tv);
}
cnt->time_last_frame = cnt->time_current_frame;
}
static void mlp_loopback(struct context *cnt){
/*
* Feed last image and motion image to video device pipes and the stream clients
* In setup mode we send the special setup mode image to both stream and vloopback pipe
* In normal mode we feed the latest image to vloopback device and we send
* the image to the stream. We always send the first image in a second to the stream.
* Other image are sent only when the config option stream_motion is off
* The result is that with stream_motion on the stream 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 stream except the 1st per second which is already sent.
*/
if (cnt->conf.setup_mode) {
event(cnt, EVENT_IMAGE, &cnt->imgs.img_motion, NULL, &cnt->pipe, &cnt->current_image->timestamp_tv);
event(cnt, EVENT_STREAM, &cnt->imgs.img_motion, NULL, NULL, &cnt->current_image->timestamp_tv);
} else {
event(cnt, EVENT_IMAGE, cnt->current_image, NULL,
&cnt->pipe, &cnt->current_image->timestamp_tv);
if (!cnt->conf.stream_motion || cnt->shots == 1)
event(cnt, EVENT_STREAM, cnt->current_image, NULL, NULL,
&cnt->current_image->timestamp_tv);
}
event(cnt, EVENT_IMAGEM, &cnt->imgs.img_motion, NULL, &cnt->mpipe, &cnt->current_image->timestamp_tv);
}
static void mlp_parmsupdate(struct context *cnt){
/***** 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_pictures, "on") == 0)
cnt->new_img = NEWIMG_ON;
else if (strcasecmp(cnt->conf.output_pictures, "first") == 0)
cnt->new_img = NEWIMG_FIRST;
else if (strcasecmp(cnt->conf.output_pictures, "best") == 0)
cnt->new_img = NEWIMG_BEST;
else if (strcasecmp(cnt->conf.output_pictures, "center") == 0)
cnt->new_img = NEWIMG_CENTER;
else
cnt->new_img = NEWIMG_OFF;
if (strcasecmp(cnt->conf.locate_motion_mode, "on") == 0)
cnt->locate_motion_mode = LOCATE_ON;
else if (strcasecmp(cnt->conf.locate_motion_mode, "preview") == 0)
cnt->locate_motion_mode = LOCATE_PREVIEW;
else
cnt->locate_motion_mode = LOCATE_OFF;
if (strcasecmp(cnt->conf.locate_motion_style, "box") == 0)
cnt->locate_motion_style = LOCATE_BOX;
else if (strcasecmp(cnt->conf.locate_motion_style, "redbox") == 0)
cnt->locate_motion_style = LOCATE_REDBOX;
else if (strcasecmp(cnt->conf.locate_motion_style, "cross") == 0)
cnt->locate_motion_style = LOCATE_CROSS;
else if (strcasecmp(cnt->conf.locate_motion_style, "redcross") == 0)
cnt->locate_motion_style = LOCATE_REDCROSS;
else
cnt->locate_motion_style = LOCATE_BOX;
/* 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 ||
cnt->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);
}
cnt->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
*/
cnt->smartmask_ratio = 5 * cnt->lastrate * (11 - cnt->smartmask_speed);
}
#if defined(HAVE_MYSQL) || defined(HAVE_PGSQL) || defined(HAVE_SQLITE3)
/*
* 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_movie * (FTYPE_MPEG + FTYPE_MPEG_MOTION) +
cnt->conf.sql_log_timelapse * FTYPE_MPEG_TIMELAPSE;
#endif /* defined(HAVE_MYSQL) || defined(HAVE_PGSQL) || defined(HAVE_SQLITE3) */
}
}
static void mlp_frametiming(struct context *cnt){
int indx;
struct timeval tv2;
unsigned long int elapsedtime; //TODO: Need to evaluate logic for needing this.
long int delay_time_nsec;
/***** 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)
cnt->required_frame_time = 1000000L / cnt->conf.frame_limit;
else
cnt->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) - cnt->timenow;
/*
* Update history buffer but ignore first pass as timebefore
* variable will be inaccurate
*/
if (cnt->passflag)
cnt->rolling_average_data[cnt->rolling_frame] = cnt->timenow - cnt->timebefore;
else
cnt->passflag = 1;
cnt->rolling_frame++;
if (cnt->rolling_frame >= cnt->rolling_average_limit)
cnt->rolling_frame = 0;
/* Calculate 10 second average and use deviation in delay calculation */
cnt->rolling_average = 0L;
for (indx = 0; indx < cnt->rolling_average_limit; indx++)
cnt->rolling_average += cnt->rolling_average_data[indx];
cnt->rolling_average /= cnt->rolling_average_limit;
cnt->frame_delay = cnt->required_frame_time - elapsedtime - (cnt->rolling_average - cnt->required_frame_time);
if (cnt->frame_delay > 0) {
/* Apply delay to meet frame time */
if (cnt->frame_delay > cnt->required_frame_time)
cnt->frame_delay = cnt->required_frame_time;
/* Delay time in nanoseconds for SLEEP */
delay_time_nsec = cnt->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);
}
}
/**
* motion_loop
*
* Thread function for the motion handling threads.
*
*/
static void *motion_loop(void *arg)
{
struct context *cnt = arg;
if (motion_init(cnt) < 0) goto err;
while (!cnt->finish || cnt->makemovie) {
mlp_prepare(cnt);
if (cnt->get_image) {
mlp_resetimages(cnt);
if (mlp_retry(cnt) == 1) break;
if (mlp_capture(cnt) == 1) break;
mlp_detection(cnt);
mlp_tuning(cnt);
mlp_overlay(cnt);
mlp_actions(cnt);
mlp_setupmode(cnt);
}
mlp_snapshot(cnt);
mlp_timelapse(cnt);
mlp_loopback(cnt);
mlp_parmsupdate(cnt);
mlp_frametiming(cnt);
}
err:
free(cnt->rolling_average_data);
cnt->lost_connection = 1;
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "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(NTC, TYPE_ALL, NO_ERRNO, "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 = myfopen(cnt_list[0]->conf.pid_file, "w+");
if (pidf) {
(void)fprintf(pidf, "%d\n", getpid());
myfclose(pidf);
} else {
MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, "Exit motion, cannot create process"
" id file (pid file) %s", cnt_list[0]->conf.pid_file);
if (ptr_logfile)
myfclose(ptr_logfile);
exit(0);
}
}
/*
* Changing dir to root enables people to unmount a disk
* without having to stop Motion
*/
if (chdir("/"))
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Could not change directory");
#if (defined(BSD) && !defined(__APPLE__))
setpgrp(0, getpid());
#else
setpgrp();
#endif
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(NTC, TYPE_ALL, NO_ERRNO, "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;
vid_mutex_destroy();
}
/**
* 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);
if ((cnt_list[0]->conf.log_level > ALL) ||
(cnt_list[0]->conf.log_level == 0)) {
cnt_list[0]->conf.log_level = LEVEL_DEFAULT;
cnt_list[0]->log_level = cnt_list[0]->conf.log_level;
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Using default log level (%s) (%d)",
get_log_level_str(cnt_list[0]->log_level), SHOW_LEVEL_VALUE(cnt_list[0]->log_level));
} else {
cnt_list[0]->log_level = cnt_list[0]->conf.log_level - 1; // Let's make syslog compatible
}
//set_log_level(cnt_list[0]->log_level);
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Motion "VERSION" Started");
if ((cnt_list[0]->conf.log_file) && (strncmp(cnt_list[0]->conf.log_file, "syslog", 6))) {
set_log_mode(LOGMODE_FILE);
ptr_logfile = set_logfile(cnt_list[0]->conf.log_file);
if (ptr_logfile) {
set_log_mode(LOGMODE_SYSLOG);
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Logging to file (%s)",
cnt_list[0]->conf.log_file);
set_log_mode(LOGMODE_FILE);
} else {
MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, "Exit motion, cannot create log file %s",
cnt_list[0]->conf.log_file);
exit(0);
}
} else {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Logging to syslog");
}
if ((cnt_list[0]->conf.log_type_str == NULL) ||
!(cnt_list[0]->log_type = get_log_type(cnt_list[0]->conf.log_type_str))) {
cnt_list[0]->log_type = TYPE_DEFAULT;
cnt_list[0]->conf.log_type_str = mystrcpy(cnt_list[0]->conf.log_type_str, "ALL");
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Using default log type (%s)",
get_log_type_str(cnt_list[0]->log_type));
}
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Using log type (%s) log level (%s)",
get_log_type_str(cnt_list[0]->log_type), get_log_level_str(cnt_list[0]->log_level));
set_log_level(cnt_list[0]->log_level);
set_log_type(cnt_list[0]->log_type);
conf_output_parms(cnt_list);
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(NTC, TYPE_ALL, NO_ERRNO, "Motion running as daemon process");
}
}
vid_mutex_init();
}
/**
* 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 stream 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 stream. If a duplicate port is found the stream feature gets disabled (port = 0)
* for this thread and a warning is written to console and syslog.
*/
if (cnt->conf.stream_port != 0) {
/* Compare against the control port. */
if (cnt_list[0]->conf.webcontrol_port == cnt->conf.stream_port) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO,
"Stream port number %d for thread %d conflicts with the control port",
cnt->conf.stream_port, cnt->threadnr);
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Stream feature for thread %d is disabled.",
cnt->threadnr);
cnt->conf.stream_port = 0;
}
/* Compare against stream ports of other threads. */
for (i = 1; cnt_list[i]; i++) {
if (cnt_list[i] == cnt)
continue;
if (cnt_list[i]->conf.stream_port == cnt->conf.stream_port) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO,
"Stream port number %d for thread %d conflicts with thread %d",
cnt->conf.stream_port, cnt->threadnr, cnt_list[i]->threadnr);
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,
"Stream feature for thread %d is disabled.",
cnt->threadnr);
cnt->conf.stream_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 to start */
cnt->watchdog = WATCHDOG_TMO;
/* Flag it as running outside of the thread, otherwise if the main loop
* checked if it is was running before the thread set it to 1, it would
* start another thread for this device. */
cnt->running = 1;
/*
* Create the actual thread. Use 'motion_loop' as the thread
* function.
*/
if (pthread_create(&cnt->thread_id, thread_attr, &motion_loop, cnt)) {
/* thread create failed, undo running state */
cnt->running = 0;
pthread_mutex_lock(&global_lock);
threads_running--;
pthread_mutex_unlock(&global_lock);
}
}
/**
* 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;
char service[6];
/*
* 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);
ffmpeg_global_init();
#ifdef HAVE_MYSQL
if (mysql_library_init(0, NULL, NULL)) {
fprintf(stderr, "could not initialize MySQL library\n");
exit(1);
}
#endif /* HAVE_MYSQL */
#ifdef HAVE_SQLITE3
/* database_sqlite3 == NULL if not changed causes each thread to creat their own
* sqlite3 connection this will only happens when using a non-threaded sqlite version */
cnt_list[0]->database_sqlite3=NULL;
if (cnt_list[0]->conf.database_type && ((!strcmp(cnt_list[0]->conf.database_type, "sqlite3")) && cnt_list[0]->conf.database_dbname)) {
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "SQLite3 Database filename %s",
cnt_list[0]->conf.database_dbname);
int thread_safe = sqlite3_threadsafe();
if (thread_safe > 0) {
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "SQLite3 is threadsafe");
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "SQLite3 serialized %s",
(sqlite3_config(SQLITE_CONFIG_SERIALIZED)?"FAILED":"SUCCESS"));
if (sqlite3_open( cnt_list[0]->conf.database_dbname, &cnt_list[0]->database_sqlite3) != SQLITE_OK) {
MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, "Can't open database %s : %s",
cnt_list[0]->conf.database_dbname, sqlite3_errmsg( cnt_list[0]->database_sqlite3));
sqlite3_close( cnt_list[0]->database_sqlite3);
exit(1);
}
MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "database_busy_timeout %d msec",
cnt_list[0]->conf.database_busy_timeout);
if (sqlite3_busy_timeout( cnt_list[0]->database_sqlite3, cnt_list[0]->conf.database_busy_timeout) != SQLITE_OK)
MOTION_LOG(ERR, TYPE_DB, NO_ERRNO, "database_busy_timeout failed %s",
sqlite3_errmsg( cnt_list[0]->database_sqlite3));
}
}
#endif /* HAVE_SQLITE3 */
/*
* 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(NTC, TYPE_ALL, NO_ERRNO, "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_LOG(WRN, TYPE_ALL, NO_ERRNO, "Restarting motion.");
motion_shutdown();
restart = 0; /* only one reset for now */
SLEEP(5, 0); // maybe some cameras needs less time
motion_startup(0, argc, argv); /* 0 = skip daemon init */
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Motion restarted");
}
/*
* 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;
/* camera_id is not defined in the config, then the camera id needs to be generated.
* the load order will generate a # that will become the camera_id
*/
cnt_list[i]->conf.camera_id = cnt_list[i]->conf.camera_id ? cnt_list[i]->conf.camera_id: i;
if (strcmp(cnt_list[i]->conf_filename, ""))
{
cnt_list[i]->conf_filename[sizeof(cnt_list[i]->conf_filename) - 1] = '\0';
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Camera ID: %d is from %s",
cnt_list[i]->conf.camera_id, cnt_list[i]->conf_filename);
}
if (cnt_list[i]->conf.netcam_url){
snprintf(service,6,"%s",cnt_list[i]->conf.netcam_url);
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Camera ID: %d Camera Name: %s Service: %s"
,cnt_list[i]->conf.camera_id, cnt_list[i]->conf.camera_name,service);
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Stream port %d",
cnt_list[i]->conf.stream_port);
} else {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Camera ID: %d Camera Name: %s Device: %s"
,cnt_list[i]->conf.camera_id, cnt_list[i]->conf.camera_name,cnt_list[i]->conf.video_device);
}
#ifdef HAVE_SQLITE
/* this is done to share the seralized handle
* and supress creation of new handles in the threads */
cnt_list[i]->database_sqlite3=cnt_list[0]->database_sqlite3;
#endif
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.webcontrol_port) {
pthread_mutex_lock(&global_lock);
threads_running++;
/* set outside the loop to avoid thread set vs main thread check */
cnt_list[0]->webcontrol_running = 1;
pthread_mutex_unlock(&global_lock);
if (pthread_create(&thread_id, &thread_attr, &motion_web_control,
cnt_list)) {
/* thread create failed, undo running state */
pthread_mutex_lock(&global_lock);
threads_running--;
cnt_list[0]->webcontrol_running = 0;
pthread_mutex_unlock(&global_lock);
}
}
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "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 are running 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 (cnt_list[0]->conf.webcontrol_port &&
cnt_list[0]->webcontrol_running)
motion_threads_running++;
if (((motion_threads_running == 0) && finish) ||
((motion_threads_running == 0) && (threads_running == 0))) {
MOTION_LOG(ALL, TYPE_ALL, NO_ERRNO, "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(NTC, TYPE_ALL, NO_ERRNO, "Motion thread %d restart",
cnt_list[i]->threadnr);
start_motion_thread(cnt_list[i], &thread_attr);
}
if (cnt_list[i]->watchdog > WATCHDOG_OFF) {
if (cnt_list[i]->watchdog == WATCHDOG_KILL) {
/* if 0 then it finally did clean up (and will restart without any further action here)
* kill(, 0) == ESRCH means the thread is no longer running
* if it is no longer running with running set, then cleanup here so it can restart
*/
if(cnt_list[i]->running && pthread_kill(cnt_list[i]->thread_id, 0) == ESRCH) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "cleaning Thread %d", cnt_list[i]->threadnr);
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;
} else {
/* keep sending signals so it doesn't get stuck in a blocking call */
pthread_kill(cnt_list[i]->thread_id, SIGVTALRM);
}
} else {
cnt_list[i]->watchdog--;
if (cnt_list[i]->watchdog == 0) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Thread %d - Watchdog timeout, trying to do "
"a graceful restart", cnt_list[i]->threadnr);
cnt_list[i]->finish = 1;
}
if (cnt_list[i]->watchdog == WATCHDOG_KILL) {
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "Thread %d - Watchdog timeout, did NOT restart graceful, "
"killing it!", cnt_list[i]->threadnr);
/* The problem is pthread_cancel might just wake up the thread so it runs to completion
* or it might not. In either case don't rip the carpet out under it by
* doing motion_cleanup until it no longer is running.
*/
pthread_cancel(cnt_list[i]->thread_id);
}
}
}
}
MOTION_LOG(ALL, TYPE_ALL, NO_ERRNO, "DEBUG-2 threads_running %d motion_threads_running %d finish %d",
threads_running, motion_threads_running, finish);
}
/* Reset end main loop flag */
finish = 0;
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "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 */
ffmpeg_global_deinit();
#ifdef HAVE_MYSQL
/* We started it up at the beginning of this function so we now need to clean up */
mysql_library_end();
#endif /* HAVE_MYSQL */
// Be sure that http control exits fine
cnt_list[0]->webcontrol_finish = 1;
SLEEP(1, 0);
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "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 = calloc(nbytes, 1);
if (!dummy) {
MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, "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(WRN, TYPE_ALL, NO_ERRNO,
"Warning! Function %s tries to resize memoryblock at %p to 0 bytes!",
desc, ptr);
} else {
dummy = realloc(ptr, size);
if (!dummy) {
MOTION_LOG(EMG, TYPE_ALL, NO_ERRNO,
"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 = mystrdup(path);
buffer[start-path] = 0x00;
if (mkdir(buffer, mode) == -1 && errno != EEXIST) {
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Problem creating directory %s",
buffer);
free(buffer);
return -1;
}
start = strchr(start + 1, '/');
if (!start)
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "creating directory %s", buffer);
free(buffer);
}
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);
if (dummy) return dummy;
/* could not open file... */
/* 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) {
/*
* 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(ERR, TYPE_ALL, SHOW_ERRNO, "Error opening file %s with mode %s",
path, mode);
return NULL;
}
return dummy;
}
/**
* myfclose
*
* Motion-specific variant of fclose()
*
* Returns: fclose() return value
*/
int myfclose(FILE* fh)
{
int rval = fclose(fh);
if (rval != 0)
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Error closing file");
return rval;
}
/**
* mystrftime_long
*
* Motion-specific long form of format specifiers.
*
* Parameters:
*
* cnt - current thread's context structure.
* width - width associated with the format specifier.
* word - beginning of the format specifier's word.
* l - length of the format specifier's word.
* out - output buffer where to store the result. Size: PATH_MAX.
*
* This is called if a format specifier with the format below was found:
*
* % { word }
*
* As a special edge case, an incomplete format at the end of the string
* is processed as well:
*
* % { word \0
*
* Any valid format specified width is supported, e.g. "%12{host}".
*
* The following specifier keywords are currently supported:
*
* host Replaced with the name of the local machine (see gethostname(2)).
* fps Equivalent to %fps.
*/
static void mystrftime_long (const struct context *cnt,
int width, const char *word, int l, char *out)
{
#define SPECIFIERWORD(k) ((strlen(k)==l) && (!strncmp (k, word, l)))
if (SPECIFIERWORD("host")) {
char host[PATH_MAX];
gethostname (host, PATH_MAX);
host[PATH_MAX-1] = 0; // see man page for gethostname.
snprintf (out, PATH_MAX, "%*s", width, host);
return;
}
if (SPECIFIERWORD("fps")) {
sprintf(out, "%*d", width, cnt->movie_fps);
return;
}
if (SPECIFIERWORD("dbeventid")) {
sprintf(out, "%*llu", width, cnt->database_event_id);
return;
}
// Not a valid modifier keyword. Log the error and ignore.
MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO,
"invalid format specifier keyword %*.*s", l, l, word);
// Do not let the output buffer empty, or else where to restart the
// interpretation of the user string will become dependent to far too
// many conditions. Maybe change loop to "if (*pos_userformat == '%') {
// ...} __else__ ..."?
out[0] = '~'; out[1] = 0;
}
/**
* 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(const struct context *cnt, char *s, size_t max, const char *userformat,
const struct timeval *tv1, const char *filename, int sqltype)
{
char formatstring[PATH_MAX] = "";
char tempstring[PATH_MAX] = "";
char *format, *tempstr;
const char *pos_userformat;
int width;
struct tm timestamp_tm;
localtime_r(&tv1->tv_sec, &timestamp_tm);
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';
width = 0;
while ('0' <= pos_userformat[1] && pos_userformat[1] <= '9') {
width *= 10;
width += pos_userformat[1] - '0';
++pos_userformat;
}
switch (*++pos_userformat) {
case '\0': // end of string
--pos_userformat;
break;
case 'v': // event
sprintf(tempstr, "%0*d", width ? width : 2, cnt->event_nr);
break;
case 'q': // shots
sprintf(tempstr, "%0*d", width ? width : 2,
cnt->current_image->shot);
break;
case 'D': // diffs
sprintf(tempstr, "%*d", width, cnt->current_image->diffs);
break;
case 'N': // noise
sprintf(tempstr, "%*d", width, cnt->noise);
break;
case 'i': // motion width
sprintf(tempstr, "%*d", width,
cnt->current_image->location.width);
break;
case 'J': // motion height
sprintf(tempstr, "%*d", width,
cnt->current_image->location.height);
break;
case 'K': // motion center x
sprintf(tempstr, "%*d", width, cnt->current_image->location.x);
break;
case 'L': // motion center y
sprintf(tempstr, "%*d", width, cnt->current_image->location.y);
break;
case 'o': // threshold
sprintf(tempstr, "%*d", width, cnt->threshold);
break;
case 'Q': // number of labels
sprintf(tempstr, "%*d", width,
cnt->current_image->total_labels);
break;
case 't': // camera id
sprintf(tempstr, "%*d", width, cnt->conf.camera_id);
break;
case 'C': // text_event
if (cnt->text_event_string[0])
snprintf(tempstr, PATH_MAX, "%*s", width,
cnt->text_event_string);
else
++pos_userformat;
break;
case 'w': // picture width
sprintf(tempstr, "%*d", width, cnt->imgs.width);
break;
case 'h': // picture height
sprintf(tempstr, "%*d", width, cnt->imgs.height);
break;
case 'f': // filename -- or %fps
if ((*(pos_userformat+1) == 'p') && (*(pos_userformat+2) == 's')) {
sprintf(tempstr, "%*d", width, cnt->movie_fps);
pos_userformat += 2;
break;
}
if (filename)
snprintf(tempstr, PATH_MAX, "%*s", width, filename);
else
++pos_userformat;
break;
case 'n': // sqltype
if (sqltype)
sprintf(tempstr, "%*d", width, sqltype);
else
++pos_userformat;
break;
case '{': // long format specifier word.
{
const char *word = ++pos_userformat;
while ((*pos_userformat != '}') && (*pos_userformat != 0))
++pos_userformat;
mystrftime_long (cnt, width, word, (int)(pos_userformat-word), tempstr);
if (*pos_userformat == '\0') --pos_userformat;
}
break;
case '$': // thread name
if (cnt->conf.camera_name && cnt->conf.camera_name[0])
snprintf(tempstr, PATH_MAX, "%s", cnt->conf.camera_name);
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, &timestamp_tm);
}
/* This is a temporary location for these util functions. All the generic utility
* functions will be collected here and ultimately moved into a new common "util" module
*/
void util_threadname_set(const char *abbr, int threadnbr, const char *threadname){
/* When the abbreviation is sent in as null, that means we are being
* provided a fully filled out thread name (usually obtained from a
* previously called get_threadname so we set it without additional
* formatting.
*/
char tname[16];
if (abbr != NULL){
snprintf(tname, sizeof(tname), "%s%d%s%s",abbr,threadnbr,
threadname ? ":" : "",
threadname ? threadname : "");
} else {
snprintf(tname, sizeof(tname), "%s",threadname);
}
#ifdef __APPLE__
pthread_setname_np(tname);
#elif defined(BSD)
pthread_set_name_np(pthread_self(), tname);
#elif HAVE_PTHREAD_SETNAME_NP
pthread_setname_np(pthread_self(), tname);
#else
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "Unable to set thread name %s", tname);
#endif
}
void util_threadname_get(char *threadname){
#if ((!defined(BSD) && HAVE_PTHREAD_GETNAME_NP) || defined(__APPLE__))
char currname[16];
pthread_getname_np(pthread_self(), currname, sizeof(currname));
snprintf(threadname, sizeof(currname), "%s",currname);
#else
snprintf(threadname, 8, "%s","Unknown");
#endif
}
int util_check_passthrough(struct context *cnt){
#if (HAVE_FFMPEG && LIBAVFORMAT_VERSION_MAJOR < 55)
if (cnt->conf.ffmpeg_passthrough)
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "FFMPEG version too old. Disabling pass-through processing.");
return 0;
#else
if (cnt->conf.ffmpeg_passthrough){
/* Disable passthrough until functional */
//MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "pass-through enabled.");
//return 1;
MOTION_LOG(INF, TYPE_NETCAM, NO_ERRNO, "pass-through disabled.");
return 0;
} else {
return 0;
}
#endif
}