/*    motion.c
 *
 *    Detect changes in a video stream.
 *    Copyright 2000 by Jeroen Vreeken (pe1rxq@amsat.org)
 *    This software is distributed under the GNU public license version 2
 *    See also the file 'COPYING'.
 *
 */
#include "ffmpeg.h"
#include "motion.h"

#if (defined(BSD) && !defined(PWCBSD))
#include "video_freebsd.h"
#else
#include "video.h"
#endif /* BSD */

#include "conf.h"
#include "alg.h"
#include "track.h"
#include "event.h"
#include "picture.h"
#include "rotate.h"

/* Forward declarations */
static int motion_init(struct context *cnt);
static void motion_cleanup(struct context *cnt);
static void setup_signals(struct sigaction *, struct sigaction *);


/**
 * 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;

/**
 * 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, "%s: Resizing pre_capture buffer to %d items",
                       new_size);

            /* Create memory for new ring buffer */
            struct image_data *tmp;
            tmp = mymalloc(new_size * sizeof(struct image_data));

            /*
             * Copy all information from old to new
             * Smallest is 0 at initial init
             */
            if (smallest > 0)
                memcpy(tmp, cnt->imgs.image_ring, sizeof(struct image_data) * smallest);


            /* In the new buffers, allocate image memory */
            {
                int i;
                for(i = smallest; i < new_size; i++) {
                    tmp[i].image = mymalloc(cnt->imgs.size);
                    memset(tmp[i].image, 0x80, cnt->imgs.size);  /* initialize to grey */
                }
            }

            /* Free the old ring */
            free(cnt->imgs.image_ring);

            /* Point to the new ring */
            cnt->imgs.image_ring = tmp;
            cnt->current_image = NULL;

            cnt->imgs.image_ring_size = new_size;
        }
    }
}

/**
 * image_ring_destroy
 *
 * This routine is called when we want to free the ring
 *
 * Parameters:
 *
 *      cnt      Pointer to the motion context structure
 *
 * Returns:     nothing
 */
static void image_ring_destroy(struct context *cnt)
{
    int i;

    /* Exit if don't have any ring */
    if (cnt->imgs.image_ring == NULL)
        return;

    /* Free all image buffers */
    for (i = 0; i < cnt->imgs.image_ring_size; i++)
        free(cnt->imgs.image_ring[i].image);


    /* Free the ring */
    free(cnt->imgs.image_ring);

    cnt->imgs.image_ring = NULL;
    cnt->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;
    /* Copy all info */
    memcpy(&cnt->imgs.preview_image.image, img, sizeof(struct image_data));
    /* restore image pointer */
    cnt->imgs.preview_image.image = image;

    /* Copy image */
    memcpy(cnt->imgs.preview_image.image, img->image, cnt->imgs.size);

    /*
     * If we set output_all to yes and during the event
     * there is no image with motion, diffs is 0, we are not going to save the preview event
     */
    if (cnt->imgs.preview_image.diffs == 0)
        cnt->imgs.preview_image.diffs = 1;

    /* 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,
                              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,
                                  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,
                              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,
                                  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;

    switch(signo) {
    case SIGALRM:
        /*
         * Somebody (maybe we ourself) wants us to make a snapshot
         * This feature triggers snapshots on ALL threads that have
         * snapshot_interval different from 0.
         */
        if (cnt_list) {
            i = -1;
            while (cnt_list[++i]) {
                if (cnt_list[i]->conf.snapshot_interval)
                    cnt_list[i]->snapshot = 1;

            }
        }
        break;
    case SIGUSR1:
        /*
         * Ouch! We have been hit from the outside! Someone wants us to
         * make a movie!
         */
        if (cnt_list) {
            i = -1;
            while (cnt_list[++i])
                cnt_list[i]->makemovie = 1;
        }
        break;
    case SIGHUP:
        restart = 1;
        /*
         * Fall through, as the value of 'restart' is the only difference
         * between SIGHUP and the ones below.
         */
    case SIGINT:
    case SIGQUIT:
    case SIGTERM:
        /*
         * Somebody wants us to quit! We should better finish the actual
         * movie and end up!
         */
        if (cnt_list) {
            i = -1;
            while (cnt_list[++i]) {
                cnt_list[i]->makemovie = 1;
                cnt_list[i]->finish = 1;
                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;
}

/**
 * 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, "%s: Removed process id file (pid file).");
        else
            MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "%s: Error removing pid file");
    }

    if (ptr_logfile) {
        MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Closing logfile (%s).",
                   cnt_list[0]->conf.log_file);
        myfclose(ptr_logfile);
        set_log_mode(LOGMODE_SYSLOG);
        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, 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, LOCATE_REDBOX,
                                  LOCATE_BOTH, cnt->process_thisframe);
        } else if (cnt->locate_motion_style == LOCATE_CROSS) {
            alg_draw_location(location, imgs, imgs->width, img->image, 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, 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;
            localtime_r(&cnt->eventtime, cnt->eventtime_tm);

            /*
             * Since this is a new event we create the event_text_string used for
             * the %C conversion specifier. We may already need it for
             * on_motion_detected_commend so it must be done now.
             */
            mystrftime(cnt, cnt->text_event_string, sizeof(cnt->text_event_string),
                       cnt->conf.text_event, cnt->eventtime_tm, NULL, 0);

            /* EVENT_FIRSTMOTION triggers on_event_start_command and event_ffmpeg_newfile */
            event(cnt, EVENT_FIRSTMOTION, img->image, NULL, NULL, &img->timestamp_tm);

            MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Motion detected - starting event %d",
                       cnt->event_nr);

            /* always save first motion frame as preview-shot, may be changed to an other one later */
            if (cnt->new_img & (NEWIMG_FIRST | NEWIMG_BEST | NEWIMG_CENTER))
                image_save_as_preview(cnt, img);

        }

        /* EVENT_MOTION triggers event_beep and on_motion_detected_command */
        event(cnt, EVENT_MOTION, NULL, NULL, NULL, &img->timestamp_tm);
    }

    /* Limit framerate */
    if (img->shot < conf->frame_limit) {
        /*
         * If config option 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->image, NULL, NULL, &img->timestamp_tm);

        /*
         * Save motion jpeg, if configured
         * Output the image_out (motion) picture.
         */
        if (conf->motion_img)
            event(cnt, EVENT_IMAGEM_DETECTED, NULL, NULL, NULL, &img->timestamp_tm);
    }

    /* if 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
 */
#define IMAGE_BUFFER_FLUSH ((unsigned int)-1)
static void process_image_ring(struct context *cnt, unsigned int max_images)
{
    /*
     * We are going to send an event, in the events there is still
     * some code that use cnt->current_image
     * so set it temporary to our image
     */
    struct image_data *saved_current_image = cnt->current_image;

    /* If image is flaged to be saved and not saved yet, process it */
    do {
        /* Check if we should save/send this image, breakout if not */
        if ((cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & (IMAGE_SAVE | IMAGE_SAVED)) != IMAGE_SAVE)
            break;

        /* Set inte global cotext that we are working with this image */
        cnt->current_image = &cnt->imgs.image_ring[cnt->imgs.image_ring_out];

        if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].shot < cnt->conf.frame_limit) {
            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_tm, NULL, 0);
                draw_text(cnt->imgs.image_ring[cnt->imgs.image_ring_out].image, 10, 20,
                          cnt->imgs.width, tmp, cnt->conf.text_double);
                draw_text(cnt->imgs.image_ring[cnt->imgs.image_ring_out].image, 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].image, NULL, NULL,
                  &cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tm);

            /*
             * 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) &&
#ifdef HAVE_FFMPEG
                (cnt->ffmpeg_output || (cnt->conf.useextpipe && cnt->extpipe))) {
#else
                (cnt->conf.useextpipe && cnt->extpipe)) {
#endif
                /*
                 * 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[15];
                            MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "%s: Added %d fillerframes into movie",
                                       frames);
                            sprintf(tmp, "Fillerframes %d", frames);
                            draw_text(cnt->imgs.image_ring[cnt->imgs.image_ring_out].image, 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].image, NULL, NULL,
                              &cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tm);

                        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 have 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;
}

/**
 * motion_init
 *
 * This routine is called from motion_loop (the main thread of the program) to do
 * all of the initialisation required before starting the actual run.
 *
 * Parameters:
 *
 *      cnt     Pointer to the motion context structure
 *
 * Returns:     0 OK
 *             -1 Fatal error, open loopback error
 *             -2 Fatal error, open SQL database error
 *             -3 Fatal error, image dimensions are not modulo 8
 */
static int motion_init(struct context *cnt)
{
    FILE *picture;

    /* Store thread number in TLS. */
    pthread_setspecific(tls_key_threadnr, (void *)((unsigned long)cnt->threadnr));

    cnt->currenttime_tm = mymalloc(sizeof(struct tm));
    cnt->eventtime_tm = mymalloc(sizeof(struct tm));
    /* Init frame time */
    cnt->currenttime = time(NULL);
    localtime_r(&cnt->currenttime, cnt->currenttime_tm);

    cnt->smartmask_speed = 0;

    /*
     * We initialize cnt->event_nr to 1 and cnt->prev_event to 0 (not really needed) so
     * that certain code below does not run until motion has been detected the first time
     */
    cnt->event_nr = 1;
    cnt->prev_event = 0;
    cnt->lightswitch_framecounter = 0;
    cnt->detecting_motion = 0;
    cnt->makemovie = 0;

    MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Camera %d started: motion detection %s",
               cnt->conf.camera_id, cnt->pause ? "Disabled":"Enabled");

    if (!cnt->conf.filepath)
        cnt->conf.filepath = mystrdup(".");

    /* 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, "%s: Could not fetch initial image from camera "
                   "Motion continues using width and height from config file(s)");
        cnt->imgs.width = cnt->conf.width;
        cnt->imgs.height = cnt->conf.height;
        cnt->imgs.size = cnt->conf.width * cnt->conf.height * 3 / 2;
        cnt->imgs.motionsize = cnt->conf.width * cnt->conf.height;
        cnt->imgs.type = VIDEO_PALETTE_YUV420P;
    } else if (cnt->video_dev == -2) {
        MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "%s: Could not fetch initial image from camera "
                   "Motion only supports width and height modulo 8");
        return -3;
    }

    image_ring_resize(cnt, 1); /* Create a initial precapture ring buffer with 1 frame */

    cnt->imgs.ref = mymalloc(cnt->imgs.size);
    cnt->imgs.out = mymalloc(cnt->imgs.size);

    /* contains the moving objects of ref. frame */
    cnt->imgs.ref_dyn = mymalloc(cnt->imgs.motionsize * sizeof(*cnt->imgs.ref_dyn));
    cnt->imgs.image_virgin = mymalloc(cnt->imgs.size);
    cnt->imgs.smartmask = mymalloc(cnt->imgs.motionsize);
    cnt->imgs.smartmask_final = mymalloc(cnt->imgs.motionsize);
    cnt->imgs.smartmask_buffer = mymalloc(cnt->imgs.motionsize * sizeof(*cnt->imgs.smartmask_buffer));
    cnt->imgs.labels = mymalloc(cnt->imgs.motionsize * sizeof(*cnt->imgs.labels));
    cnt->imgs.labelsize = mymalloc((cnt->imgs.motionsize/2+1) * sizeof(*cnt->imgs.labelsize));

    /* Set output picture type */
    if (!strcmp(cnt->conf.picture_type, "ppm"))
        cnt->imgs.picture_type = IMAGE_TYPE_PPM;
    else
        cnt->imgs.picture_type = IMAGE_TYPE_JPEG;

    /* allocate buffer here for preview buffer */
    cnt->imgs.preview_image.image = mymalloc(cnt->imgs.size);

    /*
     * Allocate a buffer for temp. usage in some places
     * Only despeckle & bayer2rgb24() for now for now...
     */
    cnt->imgs.common_buffer = mymalloc(3 * cnt->imgs.width * cnt->imgs.height);

    /*
     * Now is a good time to init rotation data. Since vid_start has been
     * called, we know that we have imgs.width and imgs.height. When capturing
     * from a V4L device, these are copied from the corresponding conf values
     * in vid_start. When capturing from a netcam, they get set in netcam_start,
     * which is called from vid_start.
     *
     * rotate_init will set cap_width and cap_height in cnt->rotate_data.
     */
    rotate_init(cnt); /* rotate_deinit is called in main */

    /* Capture first image, or we will get an alarm on start */
    if (cnt->video_dev > 0) {
        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, 0x80, cnt->imgs.size);       /* initialize to grey */
            draw_text(cnt->imgs.image_virgin, 10, 20, cnt->imgs.width,
                      "Error capturing first image", cnt->conf.text_double);
            MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "%s: Error capturing first image");
        }
    }

    /* create a reference frame */
    alg_update_reference_frame(cnt, RESET_REF_FRAME);

#if defined(HAVE_LINUX_VIDEODEV_H) && !defined(WITHOUT_V4L) && !defined(BSD)
    /* open video loopback devices if enabled */
    if (cnt->conf.vidpipe) {
        MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Opening video loopback device for normal pictures");

        /* vid_startpipe should get the output dimensions */
        cnt->pipe = vid_startpipe(cnt->conf.vidpipe, cnt->imgs.width, cnt->imgs.height, cnt->imgs.type);

        if (cnt->pipe < 0) {
            MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "%s: Failed to open video loopback for normal pictures");
            return -1;
        }
    }

    if (cnt->conf.motionvidpipe) {
        MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Opening video loopback device for motion pictures");

        /* vid_startpipe should get the output dimensions */
        cnt->mpipe = vid_startpipe(cnt->conf.motionvidpipe, cnt->imgs.width, cnt->imgs.height, cnt->imgs.type);

        if (cnt->mpipe < 0) {
            MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "%s: Failed to open video loopback for motion pictures");
            return -1;
        }
    }
#endif /* !WITHOUT_V4L && !BSD */

#if defined(HAVE_MYSQL) || defined(HAVE_PGSQL) || defined(HAVE_SQLITE3)
    if (cnt->conf.database_type) {
        MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "%s: 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, "%s: SQLite3 using shared handle");
    } else if ((!strcmp(cnt->conf.database_type, "sqlite3")) && cnt->conf.database_dbname) {
        MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "%s: 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, "%s: 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, "%s: 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, "%s: 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 = 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, "%s: 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, "%s: 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, "%s: 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, "%s: 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, "%s: Failed to read mask image. Mask feature disabled.");
        } else {
            MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "%s: Maskfile \"%s\" loaded.",
                       cnt->conf.mask_file);
        }
    } else {
        cnt->imgs.mask = NULL;
    }

    /* Always initialize smart_mask - someone could turn it on later... */
    memset(cnt->imgs.smartmask, 0, cnt->imgs.motionsize);
    memset(cnt->imgs.smartmask_final, 255, cnt->imgs.motionsize);
    memset(cnt->imgs.smartmask_buffer, 0, cnt->imgs.motionsize * sizeof(*cnt->imgs.smartmask_buffer));

    /* Set noise level */
    cnt->noise = cnt->conf.noise;

    /* Set threshold value */
    cnt->threshold = cnt->conf.max_changes;

    /* Initialize stream server if stream port is specified to not 0 */
    if (cnt->conf.stream_port) {
        if (stream_init(cnt) == -1) {
            MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "%s: Problem enabling motion-stream server in port %d",
                       cnt->conf.stream_port);
            cnt->finish = 1;
        } else {
            MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Started motion-stream server on port %d (auth %s)",
                       cnt->conf.stream_port, cnt->conf.stream_auth_method ? "Enabled":"Disabled");
        }
    }

    /* Prevent first few frames from triggering motion... */
    cnt->moved = 8;
    /* 2 sec startup delay so FPS is calculated correct */
    cnt->startup_frames = cnt->conf.frame_limit * 2;

    return 0;
}

/**
 * motion_cleanup
 *
 * This routine is called from motion_loop when thread ends to
 * cleanup all memory etc. that motion_init did.
 *
 * Parameters:
 *
 *      cnt     Pointer to the motion context structure
 *
 * Returns:     nothing
 */
static void motion_cleanup(struct context *cnt)
{
    /* Stop stream */
    event(cnt, EVENT_STOP, NULL, NULL, NULL, NULL);

#ifdef HAVE_FFMPEG
    event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, NULL);
    event(cnt, EVENT_ENDMOTION, NULL, NULL, NULL, NULL);
#endif // HAVE_FFMPEG

    if (cnt->video_dev >= 0) {
        MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "%s: Calling vid_close() from motion_cleanup");
        vid_close(cnt);
    }

    free(cnt->imgs.out);
    cnt->imgs.out = NULL;

    free(cnt->imgs.ref);
    cnt->imgs.ref = NULL;

    free(cnt->imgs.ref_dyn);
    cnt->imgs.ref_dyn = NULL;

    free(cnt->imgs.image_virgin);
    cnt->imgs.image_virgin = NULL;

    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;

    free(cnt->imgs.common_buffer);
    cnt->imgs.common_buffer = NULL;

    free(cnt->imgs.preview_image.image);
    cnt->imgs.preview_image.image = NULL;

    image_ring_destroy(cnt); /* Cleanup the precapture ring buffer */

    rotate_deinit(cnt); /* cleanup image rotation data */

    if (cnt->pipe != -1) {
        close(cnt->pipe);
        cnt->pipe = -1;
    }

    if (cnt->mpipe != -1) {
        close(cnt->mpipe);
        cnt->mpipe = -1;
    }

    /* Cleanup the current time structure */
    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);
        }
#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 */
    }
}

/**
 * motion_loop
 *
 *   Thread function for the motion handling threads.
 *
 */
static void *motion_loop(void *arg)
{
    struct context *cnt = arg;
    int i, j, z = 0;
    time_t lastframetime = 0;
    int frame_buffer_size;
    unsigned int rate_limit = 0;
    int area_once = 0;
    int area_minx[9], area_miny[9], area_maxx[9], area_maxy[9];
    int smartmask_ratio = 0;
    int smartmask_count = 20;
    unsigned int smartmask_lastrate = 0;
    int olddiffs = 0;
    int previous_diffs = 0, previous_location_x = 0, previous_location_y = 0;
    unsigned int text_size_factor;
    unsigned int passflag = 0;
    long int *rolling_average_data = NULL;
    long int rolling_average_limit, required_frame_time, frame_delay, delay_time_nsec;
    int rolling_frame = 0;
    struct timeval tv1, tv2;
    unsigned long int rolling_average, elapsedtime;
    unsigned long long int timenow = 0, timebefore = 0;
    int vid_return_code = 0;        /* Return code used when calling vid_next */
    int minimum_frame_time_downcounter = cnt->conf.minimum_frame_time; /* time in seconds to skip between capturing images */
    unsigned int get_image = 1;    /* Flag used to signal that we capture new image when we run the loop */
    struct image_data *old_image;

#ifdef HAVE_PTHREAD_SETNAME_NP
    {
        char tname[16];
        snprintf(tname, sizeof(tname), "ml%d%s%s",
                 cnt->threadnr,
                 cnt->conf.camera_name ? ":" : "",
                 cnt->conf.camera_name ? cnt->conf.camera_name : "");
        pthread_setname_np(pthread_self(), tname);
    }
#endif

    /*
     * Next two variables are used for snapshot and timelapse feature
     * time_last_frame is set to 1 so that first coming timelapse or second = 0
     * is acted upon.
     */
    unsigned long int time_last_frame = 1, time_current_frame;

    if (motion_init(cnt) < 0)
        goto err;


    /* Initialize the double sized characters if needed. */
    if (cnt->conf.text_double)
        text_size_factor = 2;
    else
        text_size_factor = 1;

    /* Initialize area detection */
    area_minx[0] = area_minx[3] = area_minx[6] = 0;
    area_miny[0] = area_miny[1] = area_miny[2] = 0;

    area_minx[1] = area_minx[4] = area_minx[7] = cnt->imgs.width / 3;
    area_maxx[0] = area_maxx[3] = area_maxx[6] = cnt->imgs.width / 3;

    area_minx[2] = area_minx[5] = area_minx[8] = cnt->imgs.width / 3 * 2;
    area_maxx[1] = area_maxx[4] = area_maxx[7] = cnt->imgs.width / 3 * 2;

    area_miny[3] = area_miny[4] = area_miny[5] = cnt->imgs.height / 3;
    area_maxy[0] = area_maxy[1] = area_maxy[2] = cnt->imgs.height / 3;

    area_miny[6] = area_miny[7] = area_miny[8] = cnt->imgs.height / 3 * 2;
    area_maxy[3] = area_maxy[4] = area_maxy[5] = cnt->imgs.height / 3 * 2;

    area_maxx[2] = area_maxx[5] = area_maxx[8] = cnt->imgs.width;
    area_maxy[6] = area_maxy[7] = area_maxy[8] = cnt->imgs.height;

    /* Work out expected frame rate based on config setting */
    if (cnt->conf.frame_limit < 2)
        cnt->conf.frame_limit = 2;

    required_frame_time = 1000000L / cnt->conf.frame_limit;

    frame_delay = required_frame_time;

    /*
     * Reserve enough space for a 10 second timing history buffer. Note that,
     * if there is any problem on the allocation, mymalloc does not return.
     */
    rolling_average_limit = 10 * cnt->conf.frame_limit;
    rolling_average_data = mymalloc(sizeof(rolling_average_data) * rolling_average_limit);

    /* Preset history buffer with expected frame rate */
    for (j = 0; j < rolling_average_limit; j++)
        rolling_average_data[j] = required_frame_time;


    if (cnt->track.type)
        cnt->moved = track_center(cnt, cnt->video_dev, 0, 0, 0);

#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);
#endif

    /*
     * MAIN MOTION LOOP BEGINS HERE
     * Should go on forever... unless you bought vaporware :)
     */

    while (!cnt->finish || cnt->makemovie) {

    /***** MOTION LOOP - PREPARE FOR NEW FRAME SECTION *****/
        cnt->watchdog = WATCHDOG_TMO;

        /* Get current time and preserver last time for frame interval calc. */
        timebefore = timenow;
        gettimeofday(&tv1, NULL);
        timenow = tv1.tv_usec + 1000000L * tv1.tv_sec;

        /*
         * Calculate detection rate limit. Above 5fps we limit the detection
         * rate to 3fps to reduce load at higher framerates.
         */
        cnt->process_thisframe = 0;
        rate_limit++;
        if (rate_limit >= (cnt->lastrate / 3)) {
            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 (lastframetime != cnt->currenttime) {
            cnt->lastrate = cnt->shots + 1;
            cnt->shots = -1;
            lastframetime = cnt->currenttime;

            if (cnt->conf.minimum_frame_time) {
                minimum_frame_time_downcounter--;
                if (minimum_frame_time_downcounter == 0)
                    get_image = 1;
            } else {
                get_image = 1;
            }
        }


        /* Increase the shots variable for each frame captured within this second */
        cnt->shots++;

        if (cnt->startup_frames > 0)
            cnt->startup_frames--;

        if (get_image) {
            if (cnt->conf.minimum_frame_time) {
                minimum_frame_time_downcounter = cnt->conf.minimum_frame_time;
                get_image = 0;
            }

            /* ring_buffer_in is pointing to current pos, update before put in a new image */
            if (++cnt->imgs.image_ring_in >= cnt->imgs.image_ring_size)
                cnt->imgs.image_ring_in = 0;

            /* Check if we have filled the ring buffer, throw away last image */
            if (cnt->imgs.image_ring_in == cnt->imgs.image_ring_out) {
                if (++cnt->imgs.image_ring_out >= cnt->imgs.image_ring_size)
                    cnt->imgs.image_ring_out = 0;
            }

            /* cnt->current_image points to position in ring where to store image, diffs etc. */
            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 = old_image->timestamp;
                cnt->current_image->timestamp_tm = old_image->timestamp_tm;
                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 */
            cnt->current_image->timestamp = cnt->currenttime;
            localtime_r(&cnt->current_image->timestamp, &cnt->current_image->timestamp_tm);

            /* Store shot number with pre_captured image */
            cnt->current_image->shot = cnt->shots;

        /***** MOTION LOOP - RETRY INITIALIZING SECTION *****/
            /*
             * If a camera is not available we keep on retrying every 10 seconds
             * until it shows up.
             */
            if (cnt->video_dev < 0 &&
                cnt->currenttime % 10 == 0 && cnt->shots == 0) {
                MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO,
                           "%s: 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, "%s: 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
                     */
                    break;
                }
            }


        /***** MOTION LOOP - IMAGE CAPTURE SECTION *****/

            /*
             * Fetch next frame from camera
             * If vid_next returns 0 all is well and we got a new picture
             * Any non zero value is an error.
             * 0 = OK, valid picture
             * <0 = fatal error - leave the thread by breaking out of the main loop
             * >0 = non fatal error - copy last image or show grey image with message
             */
            if (cnt->video_dev >= 0)
                vid_return_code = vid_next(cnt, cnt->current_image->image);
            else
                vid_return_code = 1; /* Non fatal error */

            // VALID PICTURE
            if (vid_return_code == 0) {
                cnt->lost_connection = 0;
                cnt->connectionlosttime = 0;

                /* If all is well reset missing_frame_counter */
                if (cnt->missing_frame_counter >= MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit) {
                    /* If we previously logged starting a grey image, now log video re-start */
                    MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Video signal re-acquired");
                    // event for re-acquired video signal can be called here
                }
                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, cnt->current_image->image, cnt->imgs.size);

                /*
                 * If the camera is a netcam we let the camera decide the pace.
                 * Otherwise we will keep on adding duplicate frames.
                 * By resetting the timer the framerate becomes maximum the rate
                 * of the Netcam.
                 */
                if (cnt->conf.netcam_url) {
                    gettimeofday(&tv1, NULL);
                    timenow = tv1.tv_usec + 1000000L * tv1.tv_sec;
                }
            // FATAL ERROR - leave the thread by breaking out of the main loop
            } else if (vid_return_code < 0) {
                /* Fatal error - Close video device */
                MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "%s: Video device fatal error - Closing video device");
                vid_close(cnt);
                /*
                 * Use virgin image, if we are not able to open it again next loop
                 * a gray image with message is applied
                 * flag lost_connection
                 */
                memcpy(cnt->current_image->image, cnt->imgs.image_virgin, cnt->imgs.size);
                cnt->lost_connection = 1;
            /* NO FATAL ERROR -
            *        copy last image or show grey image with message
            *        flag on lost_connection if :
            *               vid_return_code == NETCAM_RESTART_ERROR
            *        cnt->video_dev < 0
            *        cnt->missing_frame_counter > (MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit)
            */
            } else {

                MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "%s: 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, "%s: Restarting Motion thread to reinitialize all "
                               "image buffers");
                    /*
                     * Break out of main loop terminating thread
                     * watchdog will start us again
                     * Set lost_connection flag on
                     */

                    cnt->lost_connection = 1;
                    break;
                }

                /*
                 * First missed frame - store timestamp
                 * Don't reset time when thread restarts
                 */
                if (cnt->connectionlosttime == 0)
                    cnt->connectionlosttime = cnt->currenttime;

                /*
                 * Increase missing_frame_counter
                 * The first MISSING_FRAMES_TIMEOUT seconds we copy previous virgin image
                 * After MISSING_FRAMES_TIMEOUT seconds we put a grey error image in the buffer
                 * If we still have not yet received the initial image from a camera
                 * we go straight for the grey error image.
                 */
                ++cnt->missing_frame_counter;

                if (cnt->video_dev >= 0 &&
                    cnt->missing_frame_counter < (MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit)) {
                    memcpy(cnt->current_image->image, cnt->imgs.image_virgin, cnt->imgs.size);
                } else {
                    const char *tmpin;
                    char tmpout[80];
                    struct tm tmptime;
                    cnt->lost_connection = 1;

                    if (cnt->video_dev >= 0)
                        tmpin = "CONNECTION TO CAMERA LOST\\nSINCE %Y-%m-%d %T";
                    else
                        tmpin = "UNABLE TO OPEN VIDEO DEVICE\\nSINCE %Y-%m-%d %T";

                    localtime_r(&cnt->connectionlosttime, &tmptime);
                    memset(cnt->current_image->image, 0x80, cnt->imgs.size);
                    mystrftime(cnt, tmpout, sizeof(tmpout), tmpin, &tmptime, NULL, 0);
                    draw_text(cnt->current_image->image, 10, 20 * text_size_factor, cnt->imgs.width,
                              tmpout, cnt->conf.text_double);

                    /* Write error message only once */
                    if (cnt->missing_frame_counter == MISSING_FRAMES_TIMEOUT * cnt->conf.frame_limit) {
                        MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Video signal lost - Adding grey image");
                        // Event for lost video signal can be called from here
                        event(cnt, EVENT_CAMERA_LOST, NULL, NULL,
                              NULL, cnt->currenttime_tm);
                    }

                    /*
                     * If we don't get a valid frame for a long time, try to close/reopen device
                     * Only try this when a device is open
                     */
                    if ((cnt->video_dev > 0) &&
                        (cnt->missing_frame_counter == (MISSING_FRAMES_TIMEOUT * 4) * cnt->conf.frame_limit)) {
                        MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "%s: Video signal still lost - "
                                   "Trying to close video device");
                        vid_close(cnt);
                    }
                }
            }

        /***** MOTION LOOP - MOTION DETECTION SECTION *****/

            /*
             * The actual motion detection takes place in the following
             * diffs is the number of pixels detected as changed
             * Make a differences picture in image_out
             *
             * alg_diff_standard is the slower full feature motion detection algorithm
             * alg_diff first calls a fast detection algorithm which only looks at a
             * fraction of the pixels. If this detects possible motion alg_diff_standard
             * is called.
             */
            if (cnt->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);
                    else
                        cnt->current_image->diffs = alg_diff(cnt, cnt->imgs.image_virgin);

                    /* Lightswitch feature - has light intensity changed?
                     * This can happen due to change of light conditions or due to a sudden change of the camera
                     * sensitivity. If alg_lightswitch detects lightswitch we suspend motion detection the next
                     * 5 frames to allow the camera to settle.
                     * Don't check if we have lost connection, we detect "Lost signal" frame as lightswitch
                     */
                    if (cnt->conf.lightswitch > 1 && !cnt->lost_connection) {
                        if (alg_lightswitch(cnt, cnt->current_image->diffs)) {
                            MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "%s: Lightswitch detected");

                            if (cnt->moved < 5)
                                cnt->moved = 5;

                            cnt->current_image->diffs = 0;
                            alg_update_reference_frame(cnt, RESET_REF_FRAME);
                        }
                    }

                    /*
                     * Switchfilter feature tries to detect a change in the video signal
                     * from one camera to the next. This is normally used in the Round
                     * Robin feature. The algorithm is not very safe.
                     * The algorithm takes a little time so we only call it when needed
                     * ie. when feature is enabled and diffs>threshold.
                     * We do not suspend motion detection like we did for lightswitch
                     * because with Round Robin this is controlled by roundrobin_skip.
                     */
                    if (cnt->conf.switchfilter && cnt->current_image->diffs > cnt->threshold) {
                        cnt->current_image->diffs = alg_switchfilter(cnt, cnt->current_image->diffs,
                                                                     cnt->current_image->image);

                        if (cnt->current_image->diffs <= cnt->threshold) {
                            cnt->current_image->diffs = 0;

                            MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "%s: 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;
                    olddiffs = 0;

                    if (cnt->conf.despeckle_filter && cnt->current_image->diffs > 0) {
                        olddiffs = cnt->current_image->diffs;
                        cnt->current_image->diffs = alg_despeckle(cnt, olddiffs);
                    } else if (cnt->imgs.labelsize_max) {
                        cnt->imgs.labelsize_max = 0; /* Disable labeling if enabled */
                    }

                } else if (!cnt->conf.setup_mode) {
                    cnt->current_image->diffs = 0;
                }
            }

            /* Manipulate smart_mask sensitivity (only every smartmask_ratio seconds) */
            if ((cnt->smartmask_speed && (cnt->event_nr != cnt->prev_event)) &&
                (!--smartmask_count)) {
                alg_tune_smartmask(cnt);
                smartmask_count = smartmask_ratio;
            }

            /*
             * cnt->moved is set by the tracking code when camera has been asked to move.
             * When camera is moving we do not want motion to detect motion or we will
             * get our camera chasing itself like crazy and we will get motion detected
             * which is not really motion. So we pretend there is no motion by setting
             * cnt->diffs = 0.
             * We also pretend to have a moving camera when we start Motion and when light
             * switch has been detected to allow camera to settle.
             */
            if (cnt->moved) {
                cnt->moved--;
                cnt->current_image->diffs = 0;
            }

        /***** MOTION LOOP - TUNING SECTION *****/

            /*
             * If noise tuning was selected, do it now. but only when
             * no frames have been recorded and only once per second
             */
            if ((cnt->conf.noise_tune && cnt->shots == 0) &&
                 (!cnt->detecting_motion && (cnt->current_image->diffs <= cnt->threshold)))
                alg_noise_tune(cnt, cnt->imgs.image_virgin);


            /*
             * If we are not noise tuning lets make sure that remote controlled
             * changes of noise_level are used.
             */
            if (cnt->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(previous_diffs - cnt->current_image->diffs)) < (previous_diffs / 15)) &&
                    /* center of motion in about the same place ? */
                    ((abs(cnt->current_image->location.x - previous_location_x)) <= (cnt->imgs.width / 150)) &&
                    ((abs(cnt->current_image->location.y - previous_location_y)) <= (cnt->imgs.height / 150))) {
                    alg_update_reference_frame(cnt, RESET_REF_FRAME);
                    cnt->current_image->diffs = 0;
                    cnt->lightswitch_framecounter = 0;

                    MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "%s: micro-lightswitch!");
                } else {
                    alg_update_reference_frame(cnt, UPDATE_REF_FRAME);
                }

                previous_diffs = cnt->current_image->diffs;
                previous_location_x = cnt->current_image->location.x;
                previous_location_y = cnt->current_image->location.y;
            }

        /***** MOTION LOOP - TEXT AND GRAPHICS OVERLAY SECTION *****/

            /*
             * Some overlays on top of the motion image
             * Note that these now modifies the cnt->imgs.out so this buffer
             * can no longer be used for motion detection features until next
             * picture frame is captured.
             */

            /* Smartmask overlay */
            if (cnt->smartmask_speed && (cnt->conf.motion_img || cnt->conf.ffmpeg_output_debug ||
                cnt->conf.setup_mode))
                overlay_smartmask(cnt, cnt->imgs.out);

            /* Largest labels overlay */
            if (cnt->imgs.largest_label && (cnt->conf.motion_img || cnt->conf.ffmpeg_output_debug ||
                cnt->conf.setup_mode))
                overlay_largest_label(cnt, cnt->imgs.out);

            /* Fixed mask overlay */
            if (cnt->imgs.mask && (cnt->conf.motion_img || cnt->conf.ffmpeg_output_debug ||
                cnt->conf.setup_mode))
                overlay_fixed_mask(cnt, cnt->imgs.out);

            /* Initialize the double sized characters if needed. */
            if (cnt->conf.text_double && text_size_factor == 1) {
                text_size_factor = 2;
            /* If text_double is set to off, then reset the scaling text_size_factor. */
            } else if (!cnt->conf.text_double && text_size_factor == 2) {
                text_size_factor = 1;
            }

            /* Add changed pixels in upper right corner of the pictures */
            if (cnt->conf.text_changes) {
                char tmp[15];

                if (!cnt->pause)
                    sprintf(tmp, "%d", cnt->current_image->diffs);
                else
                    sprintf(tmp, "-");

                draw_text(cnt->current_image->image, cnt->imgs.width - 10, 10,
                          cnt->imgs.width, tmp, cnt->conf.text_double);
            }

            /*
             * Add changed pixels to motion-images (for stream) in setup_mode
             * and always overlay smartmask (not only when motion is detected)
             */
            if (cnt->conf.setup_mode) {
                char tmp[PATH_MAX];
                sprintf(tmp, "D:%5d L:%3d N:%3d", cnt->current_image->diffs,
                        cnt->current_image->total_labels, cnt->noise);
                draw_text(cnt->imgs.out, cnt->imgs.width - 10, cnt->imgs.height - 30 * text_size_factor,
                          cnt->imgs.width, tmp, cnt->conf.text_double);
                sprintf(tmp, "THREAD %d SETUP", cnt->threadnr);
                draw_text(cnt->imgs.out, cnt->imgs.width - 10, cnt->imgs.height - 10 * text_size_factor,
                          cnt->imgs.width, tmp, cnt->conf.text_double);
            }

            /* Add text in lower left corner of the pictures */
            if (cnt->conf.text_left) {
                char tmp[PATH_MAX];
                mystrftime(cnt, tmp, sizeof(tmp), cnt->conf.text_left,
                           &cnt->current_image->timestamp_tm, NULL, 0);
                draw_text(cnt->current_image->image, 10, cnt->imgs.height - 10 * text_size_factor,
                          cnt->imgs.width, tmp, cnt->conf.text_double);
            }

            /* Add text in lower right corner of the pictures */
            if (cnt->conf.text_right) {
                char tmp[PATH_MAX];
                mystrftime(cnt, tmp, sizeof(tmp), cnt->conf.text_right,
                           &cnt->current_image->timestamp_tm, NULL, 0);
                draw_text(cnt->current_image->image, cnt->imgs.width - 10,
                          cnt->imgs.height - 10 * text_size_factor,
                          cnt->imgs.width, tmp, cnt->conf.text_double);
            }


        /***** MOTION LOOP - ACTIONS AND EVENT CONTROL SECTION *****/

            if (cnt->current_image->diffs > cnt->threshold) {
                /* flag this image, it have motion */
                cnt->current_image->flags |= IMAGE_MOTION;
                cnt->lightswitch_framecounter++; /* micro lightswitch */
            } else {
                cnt->lightswitch_framecounter = 0;
            }

            /*
             * If motion has been detected we take action and start saving
             * pictures and movies etc by calling motion_detected().
             * Is 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;
                MOTION_LOG(INF, TYPE_ALL, NO_ERRNO, "%s: Emulating motion");
#ifdef HAVE_FFMPEG
                if (cnt->ffmpeg_output || (cnt->conf.useextpipe && cnt->extpipe)) {
#else
                if (cnt->conf.useextpipe && cnt->extpipe) {
#endif
                    /* Setup the postcap counter */
                    cnt->postcap = cnt->conf.post_capture;
                    MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "%s: (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 (i = 0; i < cnt->conf.minimum_motion_frames; i++) {

                    if (cnt->imgs.image_ring[pos].flags & IMAGE_MOTION)
                        frame_count++;

                    if (pos == 0)
                        pos = cnt->imgs.image_ring_size-1;
                    else
                        pos--;
                }

                if (frame_count >= cnt->conf.minimum_motion_frames) {

                    cnt->current_image->flags |= (IMAGE_TRIGGER | IMAGE_SAVE);
                    cnt->detecting_motion = 1;

                    /* Setup the postcap counter */
                    cnt->postcap = cnt->conf.post_capture;
                    MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "%s: Setup post capture %d",
                               cnt->postcap);

                    /* Mark all images in image_ring to be saved */
                    for (i = 0; i < cnt->imgs.image_ring_size; i++)
                        cnt->imgs.image_ring[i].flags |= IMAGE_SAVE;

                } else if ((cnt->postcap) &&
#ifdef HAVE_FFMPEG
                           (cnt->ffmpeg_output || (cnt->conf.useextpipe && cnt->extpipe))) {
#else
                           (cnt->conf.useextpipe && cnt->extpipe)) {
#endif
                   /* 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, "%s: 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) &&
#ifdef HAVE_FFMPEG
                      (cnt->ffmpeg_output || (cnt->conf.useextpipe && cnt->extpipe))) {
#else
                      (cnt->conf.useextpipe && cnt->extpipe)) {
#endif
                /* No motion, doing postcap */
                cnt->current_image->flags |= (IMAGE_POSTCAP | IMAGE_SAVE);
                cnt->postcap--;
                MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "%s: 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;


            /*
             * Simple hack to recognize motion in a specific area
             * Do we need a new coversion specifier as well??
             */
            if ((cnt->conf.area_detect) && (cnt->event_nr != area_once) &&
                (cnt->current_image->flags & IMAGE_TRIGGER)) {
                j = strlen(cnt->conf.area_detect);

                for (i = 0; i < j; i++) {
                    z = cnt->conf.area_detect[i] - 49; /* 1 becomes 0 */
                    if ((z >= 0) && (z < 9)) {
                        if (cnt->current_image->location.x > area_minx[z] &&
                            cnt->current_image->location.x < area_maxx[z] &&
                            cnt->current_image->location.y > area_miny[z] &&
                            cnt->current_image->location.y < area_maxy[z]) {
                            event(cnt, EVENT_AREA_DETECTED, NULL, NULL,
                                  NULL, cnt->currenttime_tm);
                            area_once = cnt->event_nr; /* Fire script only once per event */

                            MOTION_LOG(DBG, TYPE_ALL, NO_ERRNO, "%s: Motion in area %d detected.",
                                       z + 1);
                            break;
                        }
                    }
                }
            }

            /*
             * 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->currenttime_tm);

                    /*
                     * If tracking is enabled we center our camera so it does not
                     * point to a place where it will miss the next action
                     */
                    if (cnt->track.type)
                        cnt->moved = track_center(cnt, cnt->video_dev, 0, 0, 0);

                    MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: End of event %d",
                               cnt->event_nr);

                    cnt->makemovie = 0;
                    /* Reset post capture */
                    cnt->postcap = 0;

                    /* Finally we increase the event number */
                    cnt->event_nr++;
                    cnt->lightswitch_framecounter = 0;

                    /*
                     * And we unset the text_event_string to avoid that buffered
                     * images get a timestamp from previous event.
                     */
                    cnt->text_event_string[0] = '\0';
                }
            }

            /* Save/send to movie some images */
            process_image_ring(cnt, 2);

        /***** MOTION LOOP - SETUP MODE CONSOLE OUTPUT SECTION *****/

            /* If 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",
                             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: %s", msg);
            }

        } /* get_image end */

    /***** MOTION LOOP - SNAPSHOT FEATURE SECTION *****/

        /*
         * Did we get triggered to make a snapshot from control http? Then shoot a snap
         * If snapshot_interval is not zero and time since epoch MOD snapshot_interval = 0 then snap
         * We actually allow the time to run over the interval in case we have a delay
         * from slow camera.
         * Note: Negative value means SIGALRM snaps are enabled
         * httpd-control snaps are always enabled.
         */

        /* time_current_frame is used both for snapshot and timelapse features */
        time_current_frame = cnt->currenttime;

        if ((cnt->conf.snapshot_interval > 0 && cnt->shots == 0 &&
             time_current_frame % cnt->conf.snapshot_interval <= time_last_frame % cnt->conf.snapshot_interval) ||
             cnt->snapshot) {
            event(cnt, EVENT_IMAGE_SNAPSHOT, cnt->current_image->image, NULL, NULL, &cnt->current_image->timestamp_tm);
            cnt->snapshot = 0;
        }


    /***** MOTION LOOP - TIMELAPSE FEATURE SECTION *****/

#ifdef HAVE_FFMPEG

        if (cnt->conf.timelapse) {

            /*
             * Check to see if we should start a new timelapse file. We start one when
             * we are on the first shot, and and the seconds are zero. We must use the seconds
             * to prevent the timelapse file from getting reset multiple times during the minute.
             */
            if (cnt->current_image->timestamp_tm.tm_min == 0 &&
                (time_current_frame % 60 < time_last_frame % 60) &&
                cnt->shots == 0) {

                if (strcasecmp(cnt->conf.timelapse_mode, "manual") == 0) {
                    ;/* No action */

                /* If we are daily, raise timelapseend event at midnight */
                } else if (strcasecmp(cnt->conf.timelapse_mode, "daily") == 0) {
                    if (cnt->current_image->timestamp_tm.tm_hour == 0)
                        event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tm);

                /* handle the hourly case */
                } else if (strcasecmp(cnt->conf.timelapse_mode, "hourly") == 0) {
                    event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, &cnt->current_image->timestamp_tm);

                /* If we are weekly-sunday, raise timelapseend event at midnight on sunday */
                } else if (strcasecmp(cnt->conf.timelapse_mode, "weekly-sunday") == 0) {
                    if (cnt->current_image->timestamp_tm.tm_wday == 0 &&
                        cnt->current_image->timestamp_tm.tm_hour == 0)
                        event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL,
                              &cnt->current_image->timestamp_tm);
                /* If we are weekly-monday, raise timelapseend event at midnight on monday */
                } else if (strcasecmp(cnt->conf.timelapse_mode, "weekly-monday") == 0) {
                    if (cnt->current_image->timestamp_tm.tm_wday == 1 &&
                        cnt->current_image->timestamp_tm.tm_hour == 0)
                        event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL,
                              &cnt->current_image->timestamp_tm);
                /* If we are monthly, raise timelapseend event at midnight on first day of month */
                } else if (strcasecmp(cnt->conf.timelapse_mode, "monthly") == 0) {
                    if (cnt->current_image->timestamp_tm.tm_mday == 1 &&
                        cnt->current_image->timestamp_tm.tm_hour == 0)
                        event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL,
                              &cnt->current_image->timestamp_tm);
                /* If invalid we report in syslog once and continue in manual mode */
                } else {
                    MOTION_LOG(ERR, TYPE_ALL, NO_ERRNO, "%s: 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 && time_current_frame % cnt->conf.timelapse <=
                time_last_frame % cnt->conf.timelapse)
                event(cnt, EVENT_TIMELAPSE, cnt->current_image->image, NULL, NULL,
                      &cnt->current_image->timestamp_tm);
        } else if (cnt->ffmpeg_timelapse) {
        /*
         * If timelapse movie is in progress but conf.timelapse is zero then close timelapse file
         * This is an important feature that allows manual roll-over of timelapse file using the http
         * remote control via a cron job.
         */
            event(cnt, EVENT_TIMELAPSEEND, NULL, NULL, NULL, cnt->currenttime_tm);
        }

#endif /* HAVE_FFMPEG */

        time_last_frame = time_current_frame;


    /***** MOTION LOOP - VIDEO LOOPBACK SECTION *****/

        /*
         * Feed last image and motion image to video device pipes and the 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.out, NULL, &cnt->pipe, cnt->currenttime_tm);
            event(cnt, EVENT_STREAM, cnt->imgs.out, NULL, NULL, cnt->currenttime_tm);
#ifdef HAVE_SDL
            if (cnt_list[0]->conf.sdl_threadnr == cnt->threadnr)
                event(cnt, EVENT_SDL_PUT, cnt->imgs.out, NULL, NULL, cnt->currenttime_tm);
#endif
        } else {
            event(cnt, EVENT_IMAGE, cnt->current_image->image, NULL,
                  &cnt->pipe, &cnt->current_image->timestamp_tm);

            if (!cnt->conf.stream_motion || cnt->shots == 1)
                event(cnt, EVENT_STREAM, cnt->current_image->image, NULL, NULL,
                      &cnt->current_image->timestamp_tm);
#ifdef HAVE_SDL
            if (cnt_list[0]->conf.sdl_threadnr == cnt->threadnr)
                event(cnt, EVENT_SDL_PUT, cnt->current_image->image, NULL, NULL,
                      &cnt->current_image->timestamp_tm);
#endif
        }

        event(cnt, EVENT_IMAGEM, cnt->imgs.out, NULL, &cnt->mpipe, cnt->currenttime_tm);


    /***** MOTION LOOP - ONCE PER SECOND PARAMETER UPDATE SECTION *****/

        /* Check for some config parameter changes but only every second */
        if (cnt->shots == 0) {
            if (strcasecmp(cnt->conf.output_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 ||
                smartmask_lastrate != cnt->lastrate) {
                if (cnt->conf.smart_mask_speed == 0) {
                    memset(cnt->imgs.smartmask, 0, cnt->imgs.motionsize);
                    memset(cnt->imgs.smartmask_final, 255, cnt->imgs.motionsize);
                }

                smartmask_lastrate = cnt->lastrate;
                cnt->smartmask_speed = cnt->conf.smart_mask_speed;
                /*
                 * Decay delay - based on smart_mask_speed (framerate independent)
                 * This is always 5*smartmask_speed seconds
                 */
                smartmask_ratio = 5 * cnt->lastrate * (11 - cnt->smartmask_speed);
            }

#if defined(HAVE_MYSQL) || defined(HAVE_PGSQL) || 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) */

        }


    /***** MOTION LOOP - FRAMERATE TIMING AND SLEEPING SECTION *****/


        /*
         * Work out expected frame rate based on config setting which may
         * have changed from http-control
         */
        if (cnt->conf.frame_limit)
            required_frame_time = 1000000L / cnt->conf.frame_limit;
        else
            required_frame_time = 0;

        /* Get latest time to calculate time taken to process video data */
        gettimeofday(&tv2, NULL);
        elapsedtime = (tv2.tv_usec + 1000000L * tv2.tv_sec) - timenow;

        /*
         * Update history buffer but ignore first pass as timebefore
         * variable will be inaccurate
         */
        if (passflag)
            rolling_average_data[rolling_frame] = timenow-timebefore;
        else
            passflag = 1;

        rolling_frame++;
        if (rolling_frame >= rolling_average_limit)
            rolling_frame = 0;

        /* Calculate 10 second average and use deviation in delay calculation */
        rolling_average = 0L;

        for (j = 0; j < rolling_average_limit; j++)
            rolling_average += rolling_average_data[j];

        rolling_average /= rolling_average_limit;
        frame_delay = required_frame_time-elapsedtime - (rolling_average - required_frame_time);

        if (frame_delay > 0) {
            /* Apply delay to meet frame time */
            if (frame_delay > required_frame_time)
                frame_delay = required_frame_time;

            /* Delay time in nanoseconds for SLEEP */
            delay_time_nsec = frame_delay * 1000;

            if (delay_time_nsec > 999999999)
                delay_time_nsec = 999999999;

            /* SLEEP as defined in motion.h  A safe sleep using nanosleep */
            SLEEP(0, delay_time_nsec);
        }
    }

    /*
     * END OF MOTION MAIN LOOP
     * If code continues here it is because the thread is exiting or restarting
     */
err:
    free(rolling_average_data);

    cnt->lost_connection = 1;
    MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: 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, "%s: 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, "%s: 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, "%s: Could not change directory");


#if (defined(BSD))
    setpgrp(0, getpid());
#else
    setpgrp();
#endif /* BSD */


    if ((i = open("/dev/tty", O_RDWR)) >= 0) {
        ioctl(i, TIOCNOTTY, NULL);
        close(i);
    }

    setsid();
    i = open("/dev/null", O_RDONLY);

    if (i != -1) {
        dup2(i, STDIN_FILENO);
        close(i);
    }

    i = open("/dev/null", O_WRONLY);

    if (i != -1) {
        dup2(i, STDOUT_FILENO);
        dup2(i, STDERR_FILENO);
        close(i);
    }

    /* Now it is safe to add the PID creation to the logs */
    if (pidf)
        MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Created process id file %s. Process ID is %d",
                   cnt_list[0]->conf.pid_file, getpid());

    sigaction(SIGTTOU, &sig_ign_action, NULL);
    sigaction(SIGTTIN, &sig_ign_action, NULL);
    sigaction(SIGTSTP, &sig_ign_action, NULL);
}

/**
 * cntlist_create
 *
 *   Sets up the 'cnt_list' variable by allocating room for (and actually
 *   allocating) one context struct. Also loads the configuration from
 *   the config file(s).
 *
 * Parameters:
 *   argc - size of argv
 *   argv - command-line options, passed initially from 'main'
 *
 * Returns: nothing
 */
static void cntlist_create(int argc, char *argv[])
{
    /*
     * cnt_list is an array of pointers to the context structures cnt for each thread.
     * First we reserve room for a pointer to thread 0's context structure
     * and a NULL pointer which indicates that end of the array of pointers to
     * thread context structures.
     */
    cnt_list = mymalloc(sizeof(struct context *) * 2);

    /* Now we reserve room for thread 0's context structure and let cnt_list[0] point to it */
    cnt_list[0] = mymalloc(sizeof(struct context));

    /* Populate context structure with start/default values */
    context_init(cnt_list[0]);

    /* cnt_list[1] pointing to zero indicates no more thread context structures - they get added later */
    cnt_list[1] = NULL;

    /*
     * Command line arguments are being pointed to from cnt_list[0] and we call conf_load which loads
     * the config options from motion.conf, thread config files and the command line.
     */
    cnt_list[0]->conf.argv = argv;
    cnt_list[0]->conf.argc = argc;
    cnt_list = conf_load(cnt_list);
}


/**
 * motion_shutdown
 *
 *   Responsible for performing cleanup when Motion is shut down or restarted,
 *   including freeing memory for all the context structs as well as for the
 *   context struct list itself.
 *
 * Parameters: none
 *
 * Returns:    nothing
 */
static void motion_shutdown(void)
{
    int i = -1;

    motion_remove_pid();

    while (cnt_list[++i])
        context_destroy(cnt_list[i]);

    free(cnt_list);
    cnt_list = NULL;
#ifndef WITHOUT_V4L
    vid_cleanup();
#endif
}

/**
 * motion_startup
 *
 *   Responsible for initializing stuff when Motion starts up or is restarted,
 *   including daemon initialization and creating the context struct list.
 *
 * Parameters:
 *
 *   daemonize - non-zero to do daemon init (if the config parameters says so),
 *               or 0 to skip it
 *   argc      - size of argv
 *   argv      - command-line options, passed initially from 'main'
 *
 * Returns: nothing
 */
static void motion_startup(int daemonize, int argc, char *argv[])
{
    /* Initialize our global mutex */
    pthread_mutex_init(&global_lock, NULL);

    /*
     * Create the list of context structures and load the
     * configuration.
     */
    cntlist_create(argc, argv);

    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, "%s: 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);

#ifdef HAVE_SDL
     MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Motion "VERSION" Started with SDL support");
#else
     MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Motion "VERSION" Started");
#endif

    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, "%s: Logging to file (%s)",
                       cnt_list[0]->conf.log_file);
            set_log_mode(LOGMODE_FILE);
        } else {
            MOTION_LOG(EMG, TYPE_ALL, SHOW_ERRNO, "%s: Exit motion, cannot create log file %s",
                       cnt_list[0]->conf.log_file);
            exit(0);
        }
    } else {
        MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: 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, "%s: Using default log type (%s)",
                   get_log_type_str(cnt_list[0]->log_type));
    }

    MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: 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);

    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, "%s: Motion running as daemon process");
        }
    }

#ifndef WITHOUT_V4L
    vid_init();
#endif
}

/**
 * setup_signals
 *
 *   Attaches handlers to a number of signals that Motion need to catch.
 *
 * Parameters: sigaction structs for signals in general and SIGCHLD.
 *
 * Returns:    nothing
 */
static void setup_signals(struct sigaction *sig_handler_action, struct sigaction *sigchild_action)
{
#ifdef SA_NOCLDWAIT
    sigchild_action->sa_flags = SA_NOCLDWAIT;
#else
    sigchild_action->sa_flags = 0;
#endif
    sigchild_action->sa_handler = sigchild_handler;
    sigemptyset(&sigchild_action->sa_mask);
#ifdef SA_RESTART
    sig_handler_action->sa_flags = SA_RESTART;
#else
    sig_handler_action->sa_flags = 0;
#endif
    sig_handler_action->sa_handler = sig_handler;
    sigemptyset(&sig_handler_action->sa_mask);

    /* Enable automatic zombie reaping */
    sigaction(SIGCHLD, sigchild_action, NULL);
    sigaction(SIGPIPE, sigchild_action, NULL);
    sigaction(SIGALRM, sig_handler_action, NULL);
    sigaction(SIGHUP, sig_handler_action, NULL);
    sigaction(SIGINT, sig_handler_action, NULL);
    sigaction(SIGQUIT, sig_handler_action, NULL);
    sigaction(SIGTERM, sig_handler_action, NULL);
    sigaction(SIGUSR1, sig_handler_action, NULL);

    /* 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);
}

/**
 * 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,
                       "%s: 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, "%s: 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,
                           "%s: 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,
                           "%s: 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;

    /*
     * Setup signals and do some initialization. 1 in the call to
     * 'motion_startup' means that Motion will become a daemon if so has been
     * requested, and argc and argc are necessary for reading the command
     * line options.
     */
    struct sigaction sig_handler_action;
    struct sigaction sigchild_action;
    setup_signals(&sig_handler_action, &sigchild_action);

    motion_startup(1, argc, argv);

#ifdef HAVE_FFMPEG
    /*
     * FFMpeg initialization is only performed if FFMpeg support was found
     * and not disabled during the configure phase.
     */
    ffmpeg_init();
#endif /* HAVE_FFMPEG */
#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, "%s: 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, "%s: SQLite3 is threadsafe");
            MOTION_LOG(NTC, TYPE_DB, NO_ERRNO, "%s: 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, "%s: 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, "%s: 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, "%s: 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, "%s: 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, "%s: Restarting motion.");
            motion_shutdown();
            restart = 0; /* only one reset for now */
#ifndef WITHOUT_V4L
            SLEEP(5, 0); // maybe some cameras needs less time
#endif
            motion_startup(0, argc, argv); /* 0 = skip daemon init */
            MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "%s: 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, "%s: Camera %d is from %s",
                           cnt_list[i]->conf.camera_id, cnt_list[i]->conf_filename);
            }

            MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Camera %d is device: %s input %d",
                       cnt_list[i]->conf.camera_id, cnt_list[i]->conf.netcam_url ?
                       cnt_list[i]->conf.netcam_url : cnt_list[i]->conf.video_device,
                       cnt_list[i]->conf.netcam_url ? -1 : cnt_list[i]->conf.input);

            MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: Stream port %d",
                       cnt_list[i]->conf.stream_port);
#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);
        }

#ifdef HAVE_SDL
        if (cnt_list[0]->conf.sdl_threadnr > 0)
            sdl_start(cnt_list[cnt_list[1] != NULL ? cnt_list[0]->conf.sdl_threadnr : 0]->conf.width,
                      cnt_list[cnt_list[1] != NULL ? cnt_list[0]->conf.sdl_threadnr : 0]->conf.height);
#endif

        /*
         * 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, "%s: 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, "%s: 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, "%s: 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, "%s: 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, "%s: 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, "%s: 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, "%s: 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, "%s: 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 */

#ifdef HAVE_SDL
    sdl_stop();
#endif
#ifdef HAVE_FFMPEG
    ffmpeg_finalise();
#endif /* HAVE_FFMPEG */

    // Be sure that http control exits fine
    cnt_list[0]->webcontrol_finish = 1;
    SLEEP(1, 0);
    MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: 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, "%s: 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,
                   "%s: 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,
                       "%s: 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, "%s: Problem creating directory %s",
                       buffer);
            free(buffer);
            return -1;
        }

        start = strchr(start + 1, '/');

        if (!start)
            MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "%s: 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, "%s: 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, "%s: Error closing file");

    return rval;
}

/**
 * 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 tm *tm, const char *filename, int sqltype)
{
    char formatstring[PATH_MAX] = "";
    char tempstring[PATH_MAX] = "";
    char *format, *tempstr;
    const char *pos_userformat;
    int width;

    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 && 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 '$': // 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, tm);
}

