Index: conf.c =================================================================== --- conf.c (revisión: 468) +++ conf.c (copia de trabajo) @@ -82,6 +82,7 @@ ffmpeg_output: 0, extpipe: NULL, useextpipe: 0, + smooth_video: 0, ffmpeg_output_debug: 0, ffmpeg_bps: DEF_FFMPEG_BPS, ffmpeg_vbr: DEF_FFMPEG_VBR, @@ -148,6 +149,7 @@ text_left: NULL, text_right: DEF_TIMESTAMP, text_event: DEF_EVENTSTAMP, + cameraname: "CAMERA NAME", text_double: 0, despeckle_filter: NULL, area_detect: NULL, @@ -332,6 +334,17 @@ print_int }, { + "force_framerate", + "# Force the number of frames per second set in framerate.\n" + "# This is useful for capture cards on busy system where motion\n" + "# often failed to detect the correct framerate.\n" + "# Valid range: 2-100. Default: 100 (almost no limit).", + 0, + CONF_OFFSET(force_framerate), + copy_bool, + print_bool + }, + { "minimum_frame_time", "# Minimum time in seconds between capturing picture frames from the camera.\n" "# Default: 0 = disabled - the capture rate is given by the camera framerate.\n" @@ -765,6 +778,19 @@ print_string }, { + "smooth_video", + "\n############################################################\n" + "# Normally, video is recorded as __precap+motion+postcap____\n" + "# This option changes the default behavior of the video \n" + "# capturing so that it is __precap+event-start-->event-end__ \n" + "############################################################\n" + "# Use smooth_video on to enable this feature (default: off)\n", + 0, + CONF_OFFSET(smooth_video), + copy_bool, + print_bool + }, + { "snapshot_interval", "\n############################################################\n" "# Snapshots (Traditional Periodic Webcam File Output)\n" @@ -841,6 +867,16 @@ print_bool }, { + "cameraname", + "# This option defines the value of the special event conversion specifier %e\n" + "# Default: Camera 1\n" + "# The idea is that %e can be used as part of the filename to identify a camera\n", + 0, + CONF_OFFSET(cameraname), + copy_string, + print_string + }, + { "text_event", "# This option defines the value of the special event conversion specifier %C\n" "# You can use any conversion specifier in this option except %C. Date and time\n" @@ -1038,7 +1074,7 @@ CONF_OFFSET(webcontrol_authentication), copy_string, print_string - }, + }, { "track_type", "\n############################################################\n" Index: conf.h =================================================================== --- conf.h (revisión: 468) +++ conf.h (copia de trabajo) @@ -38,7 +38,9 @@ int input; int norm; int frame_limit; + int force_framerate; int quiet; + int smooth_video; int useextpipe; /* ext_pipe on or off */ const char *extpipe; /* full command line for pipe -- must accept YUV420P images */ const char *picture_type; @@ -120,6 +122,7 @@ const char *text_left; const char *text_right; const char *text_event; + const char *cameraname; int text_double; const char *despeckle_filter; const char *area_detect; Index: motion.c =================================================================== --- motion.c (revisión: 468) +++ motion.c (copia de trabajo) @@ -418,7 +418,7 @@ } 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*/ @@ -539,49 +539,48 @@ 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); +//smoothvideo: payne 10/15/2008 +//located in two places + /* process this only if we're outputting to a movie */ + if (cnt->conf.smooth_video && (cnt->ffmpeg_output || cnt->conf.useextpipe)) { + if (cnt->prevtv.tv_sec > 0) { + time_t elapsedus = ((cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_sec - cnt->prevtv.tv_sec) * 1000000) + + (cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_usec - cnt->prevtv.tv_usec); + int myinsertnum = ((elapsedus + cnt->leftovers) / cnt->usinterval); + /* the larger loop we're in is only called to process + * motion -- there will be one frame processed later + * REGARDLESS of what we do here, so, account for it */ + cnt->leftovers += elapsedus - (cnt->usinterval*(myinsertnum+1)); -//#ifdef HAVE_FFMPEG - /* Check if we must add any "filler" frames into movie to keep up fps */ - if (cnt->imgs.image_ring[cnt->imgs.image_ring_out].shot == 0) { - /* 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 (debug_level >= CAMERA_DEBUG) { - int frames = cnt->movie_fps - (cnt->movie_last_shot + 1); - if (frames > 0) { - char tmp[15]; - motion_log(LOG_DEBUG, 0, "%s: Added %d fillerframes into movie", - __FUNCTION__, 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); + if (debug_level >= 2) + motion_log(LOG_INFO, 0, "current, prev, fps, us, interval, insertnum, leftovers: " + "%d.%06ld, %d.%06ld, %d, %d, %d, %d, %d", + cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_sec, + cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_usec, + cnt->prevtv.tv_sec, cnt->prevtv.tv_usec, cnt->movie_fps, elapsedus, + cnt->usinterval, myinsertnum, cnt->leftovers); + + + while (myinsertnum > 0) { + event(cnt, EVENT_FFMPEG_PUT, cnt->previmg, NULL, NULL, + &cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tm); + myinsertnum--; + } } + cnt->prevtv = cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv; + memcpy(cnt->previmg, cnt->imgs.image_ring[cnt->imgs.image_ring_out].image, cnt->imgs.size); } - /* 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! Properbly we got motion - no motion - motion */ - cnt->movie_last_shot = -1; + /* Output the picture to jpegs (if motion), ffmpeg and extpipe (regardless)*/ + if ((cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_MOTION) && + ! (cnt->imgs.image_ring[cnt->imgs.image_ring_out].flags & IMAGE_PRECAP)) { + 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); + } else { + 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); } - - /* 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; -//#endif } /* Mark the image as saved */ @@ -682,6 +681,10 @@ cnt->imgs.type = VIDEO_PALETTE_YUV420P; } + /* setup cnt->previmg (previous img for ffmpeg streaming) */ + cnt->previmg = mymalloc(cnt->imgs.size); + memset(cnt->previmg, 0x80, cnt->imgs.size); /* initialize to grey */ + image_ring_resize(cnt, 1); /* Create a initial precapture ring buffer with 1 frame */ cnt->imgs.ref = mymalloc(cnt->imgs.size); @@ -1019,6 +1022,12 @@ if (cnt->conf.sqlite3_db) sqlite3_close(cnt->database_sqlite3); #endif /* HAVE_SQLITE3 */ + + /* Cleanup the previmg buffer */ + if (cnt->previmg) { + free(cnt->previmg); + cnt->previmg = NULL; + } } /** @@ -1155,8 +1164,10 @@ image_ring_resize(cnt, frame_buffer_size); /* Get time for current frame */ - cnt->currenttime = time(NULL); + gettimeofday(&cnt->tv, NULL); + cnt->currenttime = cnt->tv.tv_sec; + /* localtime returns static data and is not threadsafe * so we use localtime_r which is reentrant and threadsafe */ @@ -1233,6 +1244,8 @@ /* Store time with pre_captured image */ cnt->current_image->timestamp = cnt->currenttime; + cnt->current_image->tv = cnt->tv; + localtime_r(&cnt->current_image->timestamp, &cnt->current_image->timestamp_tm); /* Store shot number with pre_captured image */ @@ -1796,12 +1809,58 @@ * images get a timestamp from previous event. */ cnt->text_event_string[0] = '\0'; + + + cnt->gapfix = 0; + if ((cnt->currenttime - cnt->lasttime < cnt->conf.event_gap) && cnt->conf.event_gap > 0) { + cnt->current_image->flags |= (IMAGE_TRIGGER); + cnt->postcap = cnt->conf.post_capture; + motion_detected(cnt, cnt->video_dev, cnt->current_image); + cnt->gapfix = 1; + } else { + /* And, finally again, we reset the prevtv structure. */ + cnt->prevtv.tv_sec = 0; + cnt->prevtv.tv_usec = 0; + } } } /* Save/send to movie some images */ process_image_ring(cnt, 2); + /* For movie realtime sync. code needed here so we don't run into issues + with high numbers of queued images, which would slow down processing */ + if (cnt->conf.smooth_video && (cnt->ffmpeg_output || (cnt->conf.useextpipe))) { + /* process this if we're outputting to a movie only.. */ + if (cnt->prevtv.tv_sec > 0) { /* only run through this block of code if we're in an event -- + minimal processing is KEY =) */ + time_t elapsedus = ((cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_sec - cnt->prevtv.tv_sec) * 1000000) + + (cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_usec - cnt->prevtv.tv_usec); + int myinsertnum = ((elapsedus + cnt->leftovers) / cnt->usinterval); + + if (myinsertnum > 0) { + cnt->leftovers += elapsedus - (cnt->usinterval*(myinsertnum)); + + if (debug_level >= 2) + motion_log(LOG_INFO, 0, "KEEPUP: current, prev, fps, us, interval, insertnum, leftovers: " + "%d.%06ld, %d.%06ld, %d, %d, %d, %d, %d", + cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_sec, + cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv.tv_usec, + cnt->prevtv.tv_sec, cnt->prevtv.tv_usec, cnt->movie_fps, elapsedus, + cnt->usinterval, myinsertnum, cnt->leftovers); + + while (myinsertnum > 0) { + event(cnt, EVENT_FFMPEG_PUT, cnt->previmg, NULL, NULL, + &cnt->imgs.image_ring[cnt->imgs.image_ring_out].timestamp_tm); + myinsertnum--; + } + + cnt->prevtv = cnt->imgs.image_ring[cnt->imgs.image_ring_out].tv; + memcpy(cnt->previmg, cnt->imgs.image_ring[cnt->imgs.image_ring_out].image, cnt->imgs.size); + } + } + } + /***** MOTION LOOP - SETUP MODE CONSOLE OUTPUT SECTION *****/ /* If CAMERA_VERBOSE enabled output some numbers to console */ @@ -2152,8 +2211,8 @@ motion_log(LOG_ERR, 1, "%s: Exit motion, cannot create process id file (pid file) %s", __FUNCTION__, cnt_list[0]->conf.pid_file); if (ptr_logfile) - myfclose(ptr_logfile); - exit(0); + myfclose(ptr_logfile); + exit(0); } } @@ -2735,7 +2794,7 @@ if (!start) motion_log(LOG_INFO, 0, "%s: creating directory %s", __FUNCTION__, buffer); - + free(buffer); } @@ -2965,6 +3024,13 @@ pthread_getspecific(tls_key_threadnr)); break; + case 'e': // cameraname + if (cnt->conf.cameraname && cnt->conf.cameraname[0]) + snprintf(tempstr, PATH_MAX, "%s", cnt->conf.cameraname); + else + ++pos_userformat; + break; + case 'C': // text_event if (cnt->text_event_string && cnt->text_event_string[0]) snprintf(tempstr, PATH_MAX, "%s", cnt->text_event_string); @@ -2992,6 +3058,26 @@ ++pos_userformat; break; + case 'u': // tv_usec -- really "us" for micro time, returns microsecond part + // ux = two decimals + // ug = gap? + switch(*++pos_userformat) { + case 's': // %us, just return microseconds (6 digits padded) + sprintf(tempstr, "%06ld",cnt->current_image->tv.tv_usec); + break; + case 'x': // %ux, just return microseconds (2 digits padded) + sprintf(tempstr, "%02ld",cnt->current_image->tv.tv_usec/4); + break; + case 'g': // %ug, gap? + sprintf(tempstr, "%d",cnt->gapfix); + break; + } + if (tempstr[0]) //if %ut or %us + break; + else + --pos_userformat; + + default: // Any other code is copied with the %-sign *format++ = '%'; *format++ = *pos_userformat; Index: motion.h =================================================================== --- motion.h (revisión: 468) +++ motion.h (copia de trabajo) @@ -88,8 +88,8 @@ while (nanosleep(&tv, &tv) == -1); \ } + #define CLEAR(x) memset(&(x), 0, sizeof(x)) - #if defined(WITHOUT_V4L) || defined(BSD) #define VIDEO_PALETTE_GREY 1 /* Linear greyscale */ @@ -223,6 +223,7 @@ struct image_data { unsigned char *image; int diffs; + struct timeval tv; time_t timestamp; /* Timestamp when image was captured */ struct tm timestamp_tm; int shot; /* Sub second timestamp count */ @@ -328,6 +329,7 @@ struct config conf; struct images imgs; + unsigned char *previmg; struct trackoptions track; struct netcam_context *netcam; struct image_data *current_image; /* Pointer to a structure where the image, diffs etc is stored */ @@ -367,6 +369,11 @@ struct tm *eventtime_tm; time_t currenttime; + struct timeval tv; /* store current time of image */ + struct timeval prevtv; /* previous pic tv.. needs to stick around */ + struct timeval prevmotiontv; /* previous pic detection tv.. needs to stick around */ + int gapfix; /* set to 1 if gap not fulfilled; used for output */ + time_t leftovers; /* leftover microseconds */ time_t lasttime; time_t eventtime; time_t connectionlosttime; /* timestamp from connection lost */ @@ -405,6 +412,8 @@ #endif int movie_fps; + time_t usinterval; /* microsecond interval, 1000000 / movie_fps */ + char newfilename[PATH_MAX]; char extpipefilename[PATH_MAX]; int movie_last_shot; Index: motion-dist.conf.in =================================================================== --- motion-dist.conf.in (revisión: 468) +++ motion-dist.conf.in (copia de trabajo) @@ -89,6 +89,12 @@ # Valid range: 2-100. Default: 100 (almost no limit). framerate 2 +# Force the number of frames per second set in framerate. +# This is useful for capture cards on busy system where motion +# often failed to detect the correct framerate. +# Valid range: 2-100. Default: 100 (almost no limit). +force_framerate 100 + # Minimum time in seconds between capturing picture frames from the camera. # Default: 0 = disabled - the capture rate is given by the camera framerate. # This option is used when you want to capture images at a rate lower than 2 per second. @@ -316,8 +322,14 @@ # Generally, use '-' for STDIN... ;extpipe mencoder -demuxer rawvideo -rawvideo w=320:h=240:i420 -ovc x264 -x264encopts bframes=4:frameref=1:subq=1:scenecut=-1:nob_adapt:threads=1:keyint=1000:8x8dct:vbv_bufsize=4000:crf=24:partitions=i8x8,i4x4:vbv_maxrate=800:no-chroma-me -vf denoise3d=16:12:48:4,pp=lb -of avi -o %f.avi - -fps %fps +############################################################ +# Normally, video is recorded as __precap+motion+postcap____ +# This option changes the default behavior of the video +# capturing so that it is __precap+event-start-->event-end__ +############################################################ +# Use smooth_video on to enable this feature (default: off) +;smooth_video - ############################################################ # Snapshots (Traditional Periodic Webcam File Output) ############################################################ @@ -362,6 +374,11 @@ # Text is placed in lower left corner ; text_left CAMERA %t +# This option defines the value of the special event conversion specifier %e +# Default: Camera 1 +# The idea is that %e can be used as part of the filename to identify a camera +;cameraname + # Draw the number of changed pixed on the images (default: off) # Will normally be set to off except when you setup and adjust the motion settings # Text is placed in upper right corner Index: event.c =================================================================== --- event.c (revisión: 468) +++ event.c (copia de trabajo) @@ -172,7 +172,7 @@ int res; char *errmsg = 0; res = sqlite3_exec(cnt->database_sqlite3, sqlquery, NULL, 0, &errmsg); - if (res != SQLITE_OK ) { + if (res != SQLITE_OK) { motion_log(LOG_ERR, 0, "%s: SQLite error was %s", __FUNCTION__, errmsg); sqlite3_free(errmsg); } @@ -467,10 +467,14 @@ if (debug_level >= CAMERA_INFO) motion_log(LOG_DEBUG, 0, "%s FPS %d", __FUNCTION__, cnt->movie_fps); - if (cnt->movie_fps > 30) + if (cnt->conf.force_framerate) + cnt->movie_fps = cnt->conf.frame_limit; + else if (cnt->movie_fps > 30) cnt->movie_fps = 30; else if (cnt->movie_fps < 2) cnt->movie_fps = 2; + + cnt->usinterval = 1000000 / cnt->movie_fps; /* less calculations are good... */ } #ifdef HAVE_FFMPEG @@ -766,7 +770,7 @@ EVENT_IMAGE_SNAPSHOT, event_image_snapshot }, -#if !defined(WITHOUT_V4L) && !defined(BSD) +#if !defined(WITHOUT_V4L) && !defined(BSD) { EVENT_IMAGE, event_vid_putpipe @@ -775,7 +779,7 @@ EVENT_IMAGEM, event_vid_putpipe }, -#endif /* !WITHOUT_V4L && !BSD */ +#endif /* !WITHOUT_V4L && !BSD */ { EVENT_STREAM, event_stream_put Index: alg.c =================================================================== --- alg.c (revisión: 468) +++ alg.c (copia de trabajo) @@ -1242,7 +1242,8 @@ * */ /* Seconds */ -#define ACCEPT_STATIC_OBJECT_TIME 10 +// -- changed from 10 to 2 +#define ACCEPT_STATIC_OBJECT_TIME 2 #define EXCLUDE_LEVEL_PERCENT 20 void alg_update_reference_frame(struct context *cnt, int action) {