Index: netcam_jpeg.c =================================================================== --- netcam_jpeg.c (revision 305) +++ netcam_jpeg.c (working copy) @@ -301,6 +301,9 @@ if (debug_level > CAMERA_VERBOSE) motion_log(-1, 0, "***new pic delay successful***"); + } else { + if (debug_level > 3) + motion_log(-1,0,"no need to wait."); } netcam->imgcnt_last = netcam->imgcnt; @@ -436,10 +439,22 @@ } ret = netcam_init_jpeg(netcam, &cinfo); - - if (ret != 0) - return ret; - + + if (ret != 0) { + if (netcam->cnt->pause != 1 ) + /* Do not flood log if detection paused */ + if (netcam->cnt->error_minute != netcam->cnt->currenttime_tm->tm_min) { + /* Only report error if have done so in this minute */ + netcam->cnt->error_minute = netcam->cnt->currenttime_tm->tm_min; + if (ret != NETCAM_NOTHING_NEW_ERROR ) + motion_log(LOG_INFO, 0, "netcam_proc_jpeg: netcam_init_jpeg returned %d",ret); + /* We do not report Nothing Nw 'errors' at all */ + } + return ret; + } + else + netcam->cnt->error_minute = -1; /* Reset the store of minute that an error occurred in */ + /* Do a sanity check on dimensions * If dimensions have changed we throw an * error message that will cause Index: motion.c =================================================================== --- motion.c (revision 305) +++ motion.c (working copy) @@ -21,6 +21,7 @@ #include "event.h" #include "picture.h" #include "rotate.h" +#include /* Forward declarations */ static int motion_init(struct context *cnt); @@ -68,6 +69,8 @@ */ volatile unsigned short int finish=0; +time_t restart_time, program_run_time; /* Times recorded for last restart and when actually started */ + /** * restart * @@ -236,6 +239,9 @@ cnt->pipe = -1; cnt->mpipe = -1; + cnt->total_diffs=0; + cnt->total_frames=0; + } /** @@ -860,6 +866,7 @@ { struct context *cnt = arg; int i, j, z = 0; + int detecting_motion = 0; time_t lastframetime = 0; int frame_buffer_size; unsigned short int ref_frame_limit = 0; @@ -882,6 +889,17 @@ int minimum_frame_time_downcounter = cnt->conf.minimum_frame_time; /* time in seconds to skip between capturing images */ int get_image = 1; /* Flag used to signal that we capture new image when we run the loop */ + /* Next variables are for statistics average fps calculation */ + float stats_frame_accum; + float stats_fps; + int stats_rolling_frame = 0; + + float rate_req; /* For netcam throttling */ + + /* Initialise the fps average data store to 0 */ + for (j=0; j< STATS_FPS_AVERAGE_CALC_TIME; j++) + cnt->stats_fps_average_data[j]=0; + /* 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. @@ -954,9 +972,12 @@ * 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); - } + /* Detect if netcam is set up but not yet running, skip resize if so */ + //if (cnt->conf.netcam_url && (cnt->netcam == NULL)) { + 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); @@ -981,9 +1002,109 @@ } else get_image = 1; + + /* Once a second Statistics actions */ + + cnt->netcam->secstart = 1; /* Signal to netcam raw fps measurement */ + cnt->netcam->dump_count = 0; + + stats_rolling_frame++; + if (stats_rolling_frame >= STATS_FPS_AVERAGE_CALC_TIME) + { + stats_rolling_frame = 0; + } + + /* Netcam throttling actions, but first test for netcam and streaming type */ + /* We use the 'raw camera fps' to control throttling, not the 'average' */ + /* as it would lead to a cause-effect loop. */ + if (cnt->conf.netcam_url && cnt->netcam->caps.streaming) { + cnt->netcam->dump_count = 0; + cnt->netcam->dump_keep = 0; + /* Work out the required fps taking into account minimum_frame_time */ + if (cnt->conf.minimum_frame_time > 0) + rate_req=1.0/cnt->conf.minimum_frame_time; + else + rate_req=cnt->conf.frame_limit; + /* Calculate the necessary interval and quantity to be dumped */ + if (cnt->netcam->cam_raw_fps > (int) (rate_req+0.25)) { + cnt->netcam->dump_qty = (int) cnt->netcam->cam_raw_fps - rate_req; + if (cnt->netcam->dump_qty > 0) + cnt->netcam->dump_interval = (float)cnt->netcam->cam_raw_fps / (float)rate_req; + else + cnt->netcam->dump_interval = 0.0; + } else { + cnt->netcam->dump_qty = 0; + cnt->netcam->dump_interval = 0.0; + } + + if (cnt->conf.setup_mode) + motion_log(LOG_INFO, 0, "throttle settings: fps req'd=%.1f last cam fps = %d dump_qty = %d dump_interval = %.1f", rate_req, cnt->netcam->cam_raw_fps, cnt->netcam->dump_qty, cnt->netcam->dump_interval); + } + + /* Still in once a second if */ + + /* Calculate average of the elements */ + stats_frame_accum = 0.0; + for (j=0; j < STATS_FPS_AVERAGE_CALC_TIME; j++) + { + stats_frame_accum += cnt->stats_fps_average_data[j]; + } + stats_fps = (float) stats_frame_accum / STATS_FPS_AVERAGE_CALC_TIME; + cnt->stats_averagefps = stats_fps; + + /* Enter the last second's frame count into the array */ + /* and add the number of frames to the tally, ... but */ + /* only if we have a good connection to camera */ + if (cnt->lost_connection == 0) { + if (cnt->conf.netcam_url) + cnt->stats_fps_average_data[stats_rolling_frame]=(float) 1000000/cnt->netcam->av_frame_time; + else + cnt->stats_fps_average_data[stats_rolling_frame]=(float) cnt->lastrate; + cnt->total_frames += (int) (cnt->stats_fps_average_data[stats_rolling_frame] + 0.5); + } + else + cnt->stats_fps_average_data[stats_rolling_frame]=0; + + + /* Once every 6 seconds Statistics actions + * Here we work out the 'motion throughput' (see below) + * and if in setup mode, report some useful information to the + * screen, ie. the average camera fps, the motion fps, and + * warn if configured fps not achieved. + */ + + if (cnt->currenttime % 6 == 0) { + + /* Find the 'Motion throughput' every 6 seconds. This is + * defined as the number of frames compared for motion + * which may be less than the fps of the camera, + * on a slow machine. This is because the Jpeg-decompress + * task, colourspace conversion, despeckle and actual + * compare can take a considerable time (in machine + * terms) and the rate of frames compared for motion + * will be less than the received fps of the camera. + * This metric is an attempt to show how much of a speed + * reduction the machine is causing. Causes of this can be: + * raw clock speed ; architecture ; optimization ; etc + * We find it every 6 secs to give better accuracy. + */ + + cnt->stats_motion_thruput = cnt->stats_motion_accum / 6.0 ; + cnt->stats_motion_accum = 0; + + if (cnt->conf.setup_mode) { + char temp[80]; + temp[0]='\0'; + for (j=0; j < STATS_FPS_AVERAGE_CALC_TIME; j++) + { + sprintf(&temp[strlen(temp)],"%.1f ", cnt->stats_fps_average_data[j]); + } + motion_log(LOG_DEBUG, 0, "Calculated average CAMERA fps as %.2f (last %d sec, data '%s').", stats_fps, STATS_FPS_AVERAGE_CALC_TIME, temp); + motion_log(LOG_DEBUG, 0, "Calculated average MOTION fps as %.2f (last 6 sec).", cnt->stats_motion_thruput); + } + } } - /* Increase the shots variable for each frame captured within this second */ cnt->shots++; @@ -1144,6 +1265,10 @@ * we go straight for the grey error image. */ ++cnt->missing_frame_counter; + + if (cnt->missing_frame_counter == 1) + motion_log(-1, 0, "SW first missing frame ... vid_return_code %d", vid_return_code); + 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); @@ -1250,6 +1375,8 @@ }else if (cnt->imgs.labelsize_max) cnt->imgs.labelsize_max = 0; /* Disable labeling if enabled */ + cnt->stats_motion_accum++; /* Tally motion-detected frames */ + } else if (!cnt->conf.setup_mode) cnt->current_image->diffs = 0; @@ -1581,6 +1708,15 @@ } /* get_image end */ + /***** MOTION LOOP - STATISTICS SECTION *****/ + + /* Tally diffs for statistics. */ + cnt->total_diffs+=cnt->current_image->diffs; + + /* Count of total frames viewed moved to 'if get_next_frame' success (above) */ + + + /***** MOTION LOOP - SNAPSHOT FEATURE SECTION *****/ /* Did we get triggered to make a snapshot from control http? Then shoot a snap @@ -1791,6 +1927,8 @@ frame_delay = required_frame_time-elapsedtime - (rolling_average - required_frame_time); if (frame_delay > 0) { + cnt->stats_frame_delay = frame_delay; /* For stats reporting */ + /* Apply delay to meet frame time */ if (frame_delay > required_frame_time) frame_delay = required_frame_time; @@ -1803,6 +1941,10 @@ /* SLEEP as defined in motion.h A safe sleep using nanosleep */ SLEEP(0, delay_time_nsec); + + } else { + /* There was no delay time needed, or more likely it was negative. */ + cnt->stats_frame_delay = 0; } } @@ -2196,6 +2338,10 @@ /* Create the TLS key for thread number. */ pthread_key_create(&tls_key_threadnr, NULL); + /* Record the actual program start time (record restart time later) */ + program_run_time=time(NULL); + restart_time=time(NULL); /* Set this too, if first time through */ + do { if (restart) { /* Handle the restart situation. Currently the approach is to @@ -2208,6 +2354,9 @@ #ifndef WITHOUT_V4L SLEEP(5,0); // maybe some cameras needs less time #endif + /* Record the most recent restart time */ + restart_time=time(NULL); + motion_startup(0, argc, argv); /* 0 = skip daemon init */ } Index: motion.h =================================================================== --- motion.h (revision 305) +++ motion.h (working copy) @@ -144,6 +144,8 @@ #define DEF_TIMESTAMP "%Y-%m-%d\\n%T" #define DEF_EVENTSTAMP "%Y%m%d%H%M%S" +#define STATS_FPS_AVERAGE_CALC_TIME 6 /* 6 secs of averaging */ + #define DEF_SNAPPATH "%v-%Y%m%d%H%M%S-snapshot" #define DEF_JPEGPATH "%v-%Y%m%d%H%M%S-%q" #define DEF_MPEGPATH "%v-%Y%m%d%H%M%S" @@ -293,6 +295,10 @@ int cap_width; int cap_height; }; + +int detecting_motion; // Moved here for access by webhttpd.c + +char my_machine_name[300]; // Used when finding machine's IP address /* these used to be global variables but now each thread will have its @@ -350,9 +356,22 @@ int lastrate; unsigned short int moved; unsigned short int pause; + int error_minute; /* Value of minute, used to avoid too-frequent error logging */ int missing_frame_counter; /* counts failed attempts to fetch picture frame from camera */ unsigned short int lost_connection; - + + unsigned long total_diffs; /* for statistics, how many pixels different so far? */ + unsigned long total_frames; /* for statistics, how many frames seen so far? */ + /* Next we reserve 1 more data store to hold the end of the 10th second */ + float stats_fps_average_data[1 + STATS_FPS_AVERAGE_CALC_TIME]; + float stats_averagefps; /* for statistics, average camera fps over time */ + float stats_motion_thruput; /* average motion fps over time */ + int stats_motion_accum; /* accumulator for above */ + long int stats_frame_delay; /* for illustration of idle time in thread */ + int v4l2_dev; + + unsigned int pid; /* for statistics, our pid. */ + #if (defined(BSD)) int tuner_dev; #endif Index: video_common.c =================================================================== --- video_common.c (revision 305) +++ video_common.c (working copy) @@ -781,6 +781,8 @@ cnt->imgs.height = height; } + cnt->v4l2_dev = dev->v4l2; + cnt->imgs.type = dev->v4l_fmt; switch (cnt->imgs.type) { Index: webhttpd.c =================================================================== --- webhttpd.c (revision 305) +++ webhttpd.c (working copy) @@ -19,6 +19,13 @@ #include #include +// Following additional includes are for get_my_ip_address +#include +#include +#include +#include +#include + pthread_mutex_t httpd_mutex; int warningkill; // This is a dummy variable use to kill warnings when not checking sscanf and similar functions @@ -1841,6 +1848,17 @@ static unsigned short int handle_get(int client_socket, const char* url, void *userdata) { struct context **cnt=userdata; + char tmpin1[] = "%Y-%m-%d %T"; /* Format string in Stats page 'Generated' and start times */ + char tmpin2[] = "%Y-%m-%d %T"; /* Format string in ConnLost line, same as on grey image */ + char tmpout[80]; /* Contains result of mystrftime with above inputs */ + struct tm tmptime; + float req_rate; /* Calculated taking into account minimum_frame_time */ + char in_event[10]; /* To hold 'No' or 'Yes' plus \0 */ + int event_timer; + int optval; + socklen_t optlen=sizeof(optval); + socklen_t len; + if (*url == '/' ){ unsigned short int i = 0; char *res=NULL; @@ -1860,6 +1878,8 @@ sprintf(res,"Thread %hu
\n", y, y); send_template(client_socket, res); } + sprintf(res,"Statistics\n"); + send_template(client_socket, res); send_template_end_client(client_socket); } else { send_template_ini_client_raw(client_socket); @@ -1869,8 +1889,327 @@ sprintf(res, "%hu\n", y); send_template_raw(client_socket, res); } + sprintf(res,"Statistics\n"); + send_template_raw(client_socket, res); } } + else if (! (strcmp (url, "/statistics/")) ) { + int y=0; + int j; + if (cnt[0]->conf.control_html_output) { + + /* HTTP OUTPUT SECTION */ + + send_template_ini_client(client_socket,ini_template); + sprintf(res,"
Motion Statistics Page
"); + send_template(client_socket, res); + if (i==1) + localtime_r(&cnt[0]->currenttime, &tmptime); + else + localtime_r(&cnt[1]->currenttime, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"
Generated at %s
",tmpout); + send_template(client_socket, res); + sprintf(res,"
"); + send_template(client_socket, res); + + localtime_r(&program_run_time, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"MotionStart='%s'
\n", tmpout); + send_template(client_socket, res); + localtime_r(&restart_time, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"LastRestart='%s'
\n
\n", tmpout); + send_template(client_socket, res); + + for (y=0; yAll
\n
\n"); + send_template(client_socket, res); + continue; /* No further fields for the 'all' line */ + } else { + sprintf(res,"Thread %d ", y, y); + send_template(client_socket, res); + } + + /* The first line of Statistics data relates to the camera and its + * connection parameters. We present first of all, the TEXT_EVENT string + * from the motion.conf file, which could be set to the camera's location. + * Then we show if it is a V4L or Netcam, if a netcam then various + * additional items are shown: Raw fps and Throttle data, Keep-Alive flag, + * Receive and Send buffer sizes. + */ + + strcpy(tmpout,cnt[y]->conf.text_event); + /* motion_log(LOG_DEBUG, 0, "webhttpd: text_event='%s'",tmpout); */ + sprintf(res," \"%s\" ",tmpout); + send_template(client_socket, res); + if (cnt[y]->conf.netcam_url) { + sprintf(res,"(Netcam) RawFps=%d ", cnt[y]->netcam->cam_raw_fps); + send_template(client_socket, res); + if (cnt[y]->netcam->caps.streaming && cnt[y]->netcam->dump_qty > 0 && cnt[y]->netcam->dump_interval > 0.0 ) { + sprintf(res,"(Throttle %.0f%%) ", 100.0/cnt[y]->netcam->dump_interval); + send_template(client_socket, res); + + } + + /* Always output the Keep-Alive state now it is committed */ + sprintf(res,"KeepAlive=%d ", cnt[y]->netcam->connect_keepalive); + send_template(client_socket, res); + + /* Get receive and transmit buffer sizes */ + if (getsockopt(cnt[y]->netcam->sock, SOL_SOCKET, SO_RCVBUF, &optval, &optlen ) <0) { + motion_log(LOG_ERR, 1, "webhttpd.c: getsockopt for receive buffer returned error."); + } else { + sprintf(res,"RecvBuf=%d ", optval); + send_template(client_socket, res); + if (getsockopt(cnt[y]->netcam->sock, SOL_SOCKET, SO_SNDBUF, &optval, &optlen ) <0) { + motion_log(LOG_ERR, 1, "webhttpd.c: getsockopt for transmit buffer returned error."); + } else { + sprintf(res,"SendBuf=%d ", optval); + send_template(client_socket, res); + } + } + + +#ifdef MOTION_V4L2 + } else if (!cnt[y]->v4l2_dev) { + sprintf(res,"(V4L1) "); + send_template(client_socket, res); + } else if (cnt[y]->v4l2_dev) { + sprintf(res,"(V4L2) "); + send_template(client_socket, res); +#endif + +#ifndef WITHOUT_V4L + } else if (cnt[y]->video_dev != -1) { + sprintf(res,"(V4L1/2) "); + send_template(client_socket, res); +#endif + } else { + sprintf(res,"(None) "); + send_template(client_socket, res); + } + sprintf(res,"
\n"); + send_template(client_socket, res); + + /* Prepare a few 'canned' strings to insert into the next line */ + if (cnt[y]->event_nr-1 == 0) /* No events yet, print a sensible time */ + strcpy(tmpout,"None"); + else + mystrftime(cnt[y], tmpout, sizeof(tmpout), tmpin1, cnt[y]->eventtime_tm, NULL, 0); + event_timer=cnt[y]->currenttime - cnt[y]->lasttime; + if (event_timer > cnt[y]->conf.gap) { + strcpy(in_event,"No"); + event_timer = 0; + } else { + strcpy(in_event,"Yes"); + } + + /* The second line contains items related to motion detection such + * as Number of Events, Time of Last Event, if we are Detecting Motion + * right now, the Gap Timer. + * These data items can help debug event timer related problems. + */ + + sprintf(res,"Thread %d Events=%d LastEvent='%s' DetectingMotion=%d Gap=%d InEvent=%s GapTimer=%d
\n", + y, y, cnt[y]->event_nr-1, tmpout, detecting_motion, cnt[y]->conf.gap, in_event, event_timer); + send_template(client_socket, res); + + /* The third line contains frame rate info, including total frames, + * average frame rate, Frame Delay timer, and last six seconds fps. + */ + + if (cnt[y]->conf.minimum_frame_time > 0) + req_rate=1.0/cnt[y]->conf.minimum_frame_time; + else + req_rate=cnt[y]->conf.frame_limit; + sprintf(res,"Thread %d Frames=%lu fps=%.2f Lastrate=%d Req.Rate=%.1f FrameDelay[ms]=%.0f fps[6s]='", + y, y, cnt[y]->total_frames, cnt[y]->stats_averagefps, cnt[y]->lastrate, req_rate, cnt[y]->stats_frame_delay/1000.0); + send_template(client_socket, res); + + for (j=0; j < STATS_FPS_AVERAGE_CALC_TIME ; j++){ + sprintf(res,"%.1f ", cnt[y]->stats_fps_average_data[j] ); + send_template(client_socket, res); + } + + sprintf(res,"'
\n"); + send_template(client_socket, res); + + /* The fourth line contains useful data: the MotionThruPut is the number of + * processed frames per second, the frame size, no. of diffs, threshold, + * and also the PID (sadly usually is 0) and the Missing Frame counter. + * Here also is the Connection Lost flag. + */ + + sprintf(res,"Thread %d MotionThruPut=%.1f Size=%dx%d Diff=%d Thresh=%d Pid=%u MissFrameCtr=%d ", + y, y, cnt[y]->stats_motion_thruput, cnt[y]->imgs.width, cnt[y]->imgs.height, + cnt[y]->current_image->diffs, cnt[y]->threshold, cnt[y]->pid, cnt[y]->missing_frame_counter); + send_template(client_socket, res); + sprintf(res,"ConnLost="); + send_template(client_socket, res); + if (cnt[y]->lost_connection) { + localtime_r(&cnt[y]->connectionlosttime, &tmptime); + mystrftime(cnt[y], tmpout, sizeof(tmpout), tmpin2, &tmptime, NULL, 0); + sprintf(res,"'%s'", tmpout); + } else { + sprintf(res,"'No'"); + } + send_template(client_socket, res); + sprintf(res,"
\n"); + send_template(client_socket, res); + + /* The fifth line contains a hyperlink to the Webcam Image, if set up */ + + if (cnt[y]->conf.webcam_port > 0) { + sprintf(res,"Thread %d Webcam Image", get_my_ip_address(), cnt[y]->conf.webcam_port, y); + } else { + sprintf(res,"Thread %d Webcam Image not configured", y); + } + send_template(client_socket, res); + sprintf(res,"

\n"); + send_template(client_socket, res); + } + sprintf(res,"
"); + send_template(client_socket, res); + sprintf(res,"<- back\n"); + send_template(client_socket, res); + send_template_end_client(client_socket); + } else { + + /* RAW OUTPUT SECTION */ + /* Raw output should now be the same as HTML output */ + send_template_ini_client_raw(client_socket); + sprintf(res,"Motion Statistics Page\n\n"); + send_template_raw(client_socket, res); + localtime_r(&cnt[1]->currenttime, &tmptime); + mystrftime(cnt[1], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"Generated at %s\n\n",tmpout); + send_template_raw(client_socket, res); + + localtime_r(&program_run_time, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"MotionStart='%s'\n", tmpout); + send_template(client_socket, res); + localtime_r(&restart_time, &tmptime); + mystrftime(cnt[0], tmpout, sizeof(tmpout), tmpin1, &tmptime, NULL, 0); + sprintf(res,"LastRestart='%s'\n\n", tmpout); + send_template(client_socket, res); + + for (y=1; yconf.text_event); + /* motion_log(LOG_DEBUG, 0, "webhttpd: text_event='%s'",tmpout); */ + sprintf(res,"%d \"%s\" ", y, tmpout); + send_template(client_socket, res); + + if (cnt[y]->conf.netcam_url) { + sprintf(res,"(Netcam) RawFps=%d ", cnt[y]->netcam->cam_raw_fps); + send_template(client_socket, res); + if (cnt[y]->netcam->caps.streaming && cnt[y]->netcam->dump_qty > 0 && cnt[y]->netcam->dump_interval > 0.0 ) { + sprintf(res,"(Throttle %.0f%%) ", 100.0/cnt[y]->netcam->dump_interval); + send_template(client_socket, res); + } +#ifdef MOTION_V4L2 + } else if (!cnt[y]->v4l2_dev) { + sprintf(res,"(V4L1) "); + send_template(client_socket, res); + } else if (cnt[y]->v4l2_dev) { + sprintf(res,"(V4L2) "); + send_template(client_socket, res); +#endif + +#ifndef WITHOUT_V4L + } else if (cnt[y]->video_dev != -1) { + sprintf(res,"(V4L1/2) "); + send_template(client_socket, res); +#endif + } else { + sprintf(res,"(None) "); + send_template(client_socket, res); + } + sprintf(res,"\n"); + send_template_raw(client_socket, res); + + /* Prepare a few 'canned' strings to insert into the next line */ + if (cnt[y]->event_nr-1 == 0) /* No events yet, print a sensible time */ + strcpy(tmpout,"None"); + else + mystrftime(cnt[y], tmpout, sizeof(tmpout), tmpin1, cnt[y]->eventtime_tm, NULL, 0); + event_timer=cnt[y]->currenttime - cnt[y]->lasttime; + if (event_timer > cnt[y]->conf.gap) { + strcpy(in_event,"No"); + event_timer = 0; + } else { + strcpy(in_event,"Yes"); + } + + /* The second line contains items related to motion detection such + * as Number of Events, Time of Last Event, if we are Detecting Motion + * right now, the Gap Timer. + * These data items can help debug event timer related problems. + */ + + sprintf(res,"%d Events=%d LastEvent='%s' DetectingMotion=%d Gap=%d InEvent=%s GapTimer=%d\n", + y, cnt[y]->event_nr-1, tmpout, detecting_motion, cnt[y]->conf.gap, in_event, event_timer); + send_template_raw(client_socket, res); + + /* The third line contains frame rate info, including total frames, + * average frame rate, Frame Delay timer, and last six seconds fps. + */ + + if (cnt[y]->conf.minimum_frame_time > 0) + req_rate=1.0/cnt[y]->conf.minimum_frame_time; + else + req_rate=cnt[y]->conf.frame_limit; + sprintf(res, "%d Frames=%lu fps=%.2f Lastrate=%d Req.Rate=%.1f FrameDelay[ms]=%.3f fps[6s]='", y, cnt[y]->total_frames, cnt[y]->stats_averagefps, cnt[y]->lastrate, req_rate, cnt[y]->stats_frame_delay/1000.0); + send_template_raw(client_socket, res); + + for (j=0; j < STATS_FPS_AVERAGE_CALC_TIME ; j++){ + sprintf(res,"%.1f ", cnt[y]->stats_fps_average_data[j] ); + send_template(client_socket, res); + } + + sprintf(res,"'\n"); + send_template(client_socket, res); + + /* The fourth line contains useful data: the MotionThruPut is the number of + * processed frames per second, the frame size, no. of diffs, threshold, + * and also the PID (sadly usually is 0) and the Missing Frame counter. + * Here also is the Connection Lost flag. + */ + + sprintf(res, "%d MotionThruPut=%.1f Size=%dx%d Diff=%d Thresh=%d Pid=%u MissFrameCntr=%d ", + y, cnt[y]->stats_motion_thruput, cnt[y]->imgs.width, cnt[y]->imgs.height, + cnt[y]->current_image->diffs, cnt[y]->threshold, cnt[y]->pid, cnt[y]->missing_frame_counter); + send_template_raw(client_socket, res); + sprintf(res,"ConnLost="); + send_template_raw(client_socket, res); + if (cnt[y]->lost_connection) { + localtime_r(&cnt[y]->connectionlosttime, &tmptime); + mystrftime(cnt[y], tmpout, sizeof(tmpout), tmpin2, &tmptime, NULL, 0); + sprintf(res,"'%s'", tmpout); + } else { + sprintf(res,"'No'"); + } + send_template_raw(client_socket, res); + sprintf(res,"\n\n"); + send_template_raw(client_socket, res); + + /* Raw format has no need of a hyperlink to webcam image */ + } + } + } else { char command[256] = {'\0'}; char slash; @@ -2371,3 +2710,92 @@ httpd_run(cnt); pthread_exit(NULL); } + +/* To get machine's IP address */ +/* This is very useful when connecting to the Statistics page from a remote */ +/* machine, as 'localhost' then refers to the browsing machine, not the one */ +/* running Motion. */ + + +char *get_my_ip_address(void) +{ +struct ifaddrs *ifa = NULL, *ifp = NULL; + +if (getifaddrs (&ifp) < 0) + { + motion_log(LOG_DEBUG, 0, "get_my_ip_address: getifaddr error, using localhost ip address."); + strcpy(my_machine_name,"127.0.0.1"); + return my_machine_name; + } + +for (ifa = ifp; ifa; ifa = ifa->ifa_next) + { + char ip[ 200 ]; + socklen_t salen; + + if (ifa->ifa_addr->sa_family == AF_INET) + salen = sizeof (struct sockaddr_in); + else if (ifa->ifa_addr->sa_family == AF_INET6) + salen = sizeof (struct sockaddr_in6); + else + continue; + + if (getnameinfo (ifa->ifa_addr, salen, + ip, sizeof (ip), NULL, 0, NI_NUMERICHOST) < 0) + { + motion_log(LOG_DEBUG, 0, "get_my_ip_address: getnameinfo error in loop."); + continue; + } + if (strncmp(ip,"127.",4)==0) + continue; /* Reject the localhost address */ + + /* Continue on to return the first non-localhost ip address */ + /* If a machine has >1 ip address it is not clear which will be used */ + strcpy(my_machine_name, ip); + return my_machine_name; + + } + +freeifaddrs (ifp); + +/* No return from within the loop - no ip address other than localhost. */ +/* So use localhost then */ +strcpy(my_machine_name,"127.0.0.1"); +return my_machine_name; +} + +char *get_my_ip_address2(void) +{ + struct hostent *he; + struct sockaddr_in sin; + //char my_machine_name[ 300 ]; // Defined in motion.h + + if (gethostname (my_machine_name, sizeof (my_machine_name))) + { + //motion_log(LOG_DEBUG, 0, "get_my_ip_address: gethostname error, using localhost ip address."); + strcpy(my_machine_name,"127.0.0.1"); + return my_machine_name; + } + + he = gethostbyname (my_machine_name); + if (he == NULL) { + if (errno == TRY_AGAIN){ + motion_log(LOG_DEBUG, 0, "get_my_ip_address: gethostbyname gave Try Again, using localhost ip address."); + } else { + motion_log(LOG_DEBUG, 0, "get_my_ip_address: gethostbyname error, using localhost ip address."); + } + strcpy(my_machine_name,"127.0.0.1"); + return my_machine_name; + } + + bzero (&sin, sizeof (struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = htons (80); + bcopy (he->h_addr_list[ 0 ], &sin.sin_addr, he->h_length); + + motion_log(LOG_DEBUG, 0, "get_my_ip_address: ip address is %s", inet_ntoa (sin.sin_addr)); + strcpy(my_machine_name,inet_ntoa (sin.sin_addr)); + + return my_machine_name; +} + Index: webhttpd.h =================================================================== --- webhttpd.h (revision 305) +++ webhttpd.h (working copy) @@ -17,5 +17,11 @@ void * motion_web_control(void *arg); void httpd_run(struct context **); +char *get_my_ip_address(void); + +extern time_t program_run_time, restart_time; + + + #endif Index: netcam.c =================================================================== --- netcam.c (revision 305) +++ netcam.c (working copy) @@ -989,6 +989,7 @@ * * * Returns: 0 for success, -1 for error + * -2 for framed dropped by throttling (non fatal) * */ static int netcam_read_html_jpeg(netcam_context_ptr netcam) @@ -1180,6 +1181,44 @@ remaining -= retval; } } + + /* read is complete - maintain the 'raw camera fps' counter */ + /* This is needed since if we are throttling, the other fps counter */ + /* is the 'throttled' figure. We need this one to calculate throttle */ + /* parameters */ + + netcam->cam_raw_fps_counter++; + if (netcam->secstart==1) { + netcam->cam_raw_fps = netcam->cam_raw_fps_counter; + //motion_log(-1,0,"throttle: raw cam fps = %d", netcam->cam_raw_fps); + netcam->cam_raw_fps_counter = 0; + netcam->secstart = 0; + } + + /* read is complete - but if we are throttling (only if streaming camera) */ + /* we work out if it's time to dump a frame */ + + if (netcam->caps.streaming && netcam->dump_qty>0 && netcam->dump_interval>0.0) { + /* Netcam throttling code */ + //motion_log(-1,0,"throttle: dump variables are dump_accum = %.1f dump_interval= %.1f.", netcam->dump_accum, netcam->dump_interval); + if (netcam->dump_count < netcam->dump_qty) { + /* Need to dump more frames in this second */ + + if (netcam->dump_accum >= 1.0) { + netcam->dump_count++; /* Dump the frame */ + netcam->dump_accum-=1.0; + //motion_log(-1,0,"throttle: new pic arrived but dumped it (dump_count = %d ).", netcam->dump_count); + return -2; /* Exit code for throttling */ + } else { + /* It is not time to dump a frame yet */ + netcam->dump_keep++; + netcam->dump_accum+=netcam->dump_interval; + } + } + /* Else drop through to process frame (in case maths incorrect) */ + //motion_log(-1,0,"throttle: new pic arrived and retained (dump_keep = %d ).", netcam->dump_keep); + } + /* * read is complete - set the current 'receiving' buffer atomically @@ -1599,8 +1638,9 @@ } } } - if (netcam->get_image(netcam) < 0) { - motion_log(LOG_ERR, 0, "Error getting jpeg image"); + if ((retval=netcam->get_image(netcam)) < 0) { + if (retval!=-2) /* -2 indicates throtting has dropped a frame so do not log error */ + motion_log(LOG_ERR, 0, "Error getting jpeg image"); /* if FTP connection, attempt to re-connect to server */ if (netcam->ftp) { close(netcam->ftp->control_file_desc); @@ -1609,7 +1649,7 @@ } } continue; - } + } /* * FIXME * Need to check whether the image was received / decoded @@ -2145,7 +2185,7 @@ } return NETCAM_NOTHING_NEW_ERROR; } - + /* * If we are controlling a non-streaming camera, we synchronize the * motion main-loop with the camera-handling thread through a signal, @@ -2156,7 +2196,7 @@ netcam->start_capture = 1; pthread_cond_signal(&netcam->cap_cond); pthread_mutex_unlock(&netcam->mutex); - } + } /* * If an error occurs in the JPEG decompression which follows this, @@ -2239,6 +2279,9 @@ /* Initialise the average frame time to the user's value */ netcam->av_frame_time = 1000000.0 / cnt->conf.frame_limit; + /* Clear the error minute */ + netcam->cnt->error_minute = -1; + /* * If a proxy has been specified, parse that URL. */ Index: netcam.h =================================================================== --- netcam.h (revision 305) +++ netcam.h (working copy) @@ -222,6 +222,15 @@ int imgcnt; /* count for # of received jpegs */ int imgcnt_last; /* remember last count to check if a new image arrived */ + + int secstart; /* Start of second marker for raw fps measurement */ + int cam_raw_fps; /* Used to determine throttling */ + int cam_raw_fps_counter; + int dump_count; /* simple count of frames dumped this fps throttle cycle */ + int dump_keep; + float dump_accum; + float dump_interval; /* interval of frames to count BEFORE dumping a frame */ + int dump_qty; /* number of frames to dump. Set to 0 to prevent. */ int error_count; /* simple count of number of errors since last good frame was received */