#include #include #include #include #include #include #include #include #include #include // #include #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #define PROCESS_STATS 1 #endif #ifndef DEFAULT_HOST #define DEFAULT_HOST "10.0.0.44" #endif #undef SIG_ACTIONS #ifndef DEFAULT_PATH /* "/cgi-bin/Stream?Video webcamPWD=RootCookie00000" */ #define DEFAULT_PATH "/cgi-bin/Stream?Video" #endif static char cookie[MAXPATHLEN]; static char auth[MAXPATHLEN]; static int debug; static int no_write_image; static unsigned int connect_count; // static int image_count; static unsigned int image_data_count; static unsigned int image_errors; static int image_rec; static int frate_mod; static unsigned int re_read; static unsigned int re_wrt; typedef struct nc_cam { // int finish; int frate; unsigned int frate_nano_delay; unsigned int frate_timer; unsigned int frate_imgcnt; char connect_host[MAXHOSTNAMELEN]; int connect_port; char connect_request[MAXPATHLEN]; unsigned int imgcnt; int sock; struct timeval timeout; unsigned int width; unsigned int height; char *image_buff; size_t image_buff_size; size_t content_length; struct timeval image_time;; } nc_cam; #define IMAGE_BUF_SIZE 4096 #define FRATE_NANO_DELAY 99999999 #define IMAGE_READ_TIMEOUT 3 #define IMAGE_TIMEOUT 7 #define DEFAULT_FRAMERATE 10 char *init_url = "http://10.1.1.44/ImageSet.html"; char init_data[64]; /* IMG_S_M = 01600120 03200240 06400480 */ #define IMAGE_SIZE "03200240" /* IMG_S_C = (low)50 (med)70 (high)85 */ #define IMAGE_QUALITY "50" /* IMG_S_O = 50 60 000 */ #define IMAGE_FREQ "000" static int nc_connect_to_cam(struct nc_cam *); static int nc_read_header(struct nc_cam *); static int nc_send_image(struct nc_cam *); static int nc_adjust_frate(struct nc_cam *); #ifdef PROCESS_STATS static void procstats(struct nc_cam *); #endif #ifdef SIG_ACTIONS static void sig_action(int); #endif struct nc_cam *init(int, char **); int nc_cam_start(struct nc_cam *); int nc_cam_next(struct nc_cam *); void nc_cam_cleanup(struct nc_cam *); /* The macro below defines a version of sleep using nanosleep * If a signal such as SIG_CHLD interrupts the sleep we just continue sleeping */ #define SLEEP(seconds, nanoseconds) { \ struct timespec tv; \ tv.tv_sec = (seconds); \ tv.tv_nsec = (nanoseconds); \ while (nanosleep(&tv, &tv) == -1); \ } int main(int argc, char *argv[]) { struct timeval tv; int k; int start_time; struct nc_cam *nc_netcam; // syslog(LOG_INFO, "nc800: started (ac=%d) (ppid=%d)", argc, getppid()); nc_netcam = init(argc, argv); nc_cam_start(nc_netcam); gettimeofday(&tv, NULL); start_time = tv.tv_sec; nc_netcam->frate_timer=tv.tv_sec; while(1) { if (nc_cam_next(nc_netcam) < 0) { syslog(LOG_ERR, "nc_cam_next [%d] returned error %d (%m)", nc_netcam->imgcnt, errno); while(nc_connect_to_cam(nc_netcam) < 0) { SLEEP(1,0); } errno=0; continue; } nc_send_image(nc_netcam); SLEEP(0, nc_netcam->frate_nano_delay); if(debug) { fputc('\n', stderr); fflush(stderr); } #ifdef PROCESS_STATS /* print stats ever 15 sec */ if( nc_netcam->imgcnt % ( nc_netcam->frate * 15 ) == 0 ) { procstats(nc_netcam); } #endif /* adjust to desired frame rate every n frames or seconds */ if( ((nc_netcam->imgcnt % frate_mod) == 20) || (nc_netcam->image_time.tv_sec - nc_netcam->frate_timer) > ( frate_mod / nc_netcam->frate) ) { nc_adjust_frate(nc_netcam); } if(nc_netcam->imgcnt > 300 && debug) break; } gettimeofday(&tv, NULL); k = tv.tv_sec - start_time; if(debug) fprintf(stderr, "Run Totals: %d sec\t%d frames\t%d K data\n\t%d frate\t%d drate\n" "%d max_size", k, nc_netcam->imgcnt, (image_data_count / 1000), nc_netcam->imgcnt/k, (image_data_count / 1000 )/k, nc_netcam->image_buff_size ); nc_cam_cleanup(nc_netcam); syslog(LOG_INFO, "nc800: run completed cleaning up"); return(0); } void parseurl(char *url, char *urlhost, int *urlport, char *urlpath) { char *s,*t; int i; /* take care of protocol type */ if (strncmp(url,"http://",7)){ if (debug) fprintf(stderr,"URL '%s' ambiguous. Assuming protocol http.\n", url); } else url += 7; s = strchr(url,'/'); if (!s) s = url +strlen(url); /* check for port */ t = strchr(url,':'); if (t && tsock = -1; init_nc_cnt->image_buff = (char *) malloc(IMAGE_BUF_SIZE); init_nc_cnt->image_buff_size = IMAGE_BUF_SIZE; setlinebuf(stderr); setlinebuf(stdout); openlog("motion", LOG_PID | LOG_NDELAY, LOG_DAEMON); syslog(LOG_INFO, "nc800: started (ac=%d) (ppid=%d, pgrp=%d, sid=%d)", ac, getppid(), getpgrp(), getsid(0) ); while ((i = getopt(ac, av, "h:p:udr:")) != -1) { // syslog(LOG_ERR, "nc800: getopt c=%c optarg=%s\n", i, optarg); switch (i) { case 'r': image_rec = strtol(optarg, (char **)NULL, 10); break; case 'f': init_nc_cnt->frate = strtol(optarg, (char **)NULL, 10); break; case 'c': strncpy(cookie, optarg, sizeof(cookie)); break; case 'a': strncpy(auth, optarg, MAXPATHLEN); break; case 'd': debug++; break; } } ac -= optind; av += optind; if ( ac > 0 && !strncmp(av[0], "http:", 5) ) { parseurl(av[0], init_nc_cnt->connect_host, &init_nc_cnt->connect_port, init_nc_cnt->connect_request); } else if (debug) { fprintf(stderr, "nc800: ac=%d\n", ac); fprintf(stderr, "nc800: av[0]=%s\n", av[0]); } if(isatty(STDOUT_FILENO) || debug) { no_write_image++; } else { // 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); } } if (init_nc_cnt->frate == 0) { init_nc_cnt->frate = DEFAULT_FRAMERATE; } init_nc_cnt->frate_nano_delay = 999999999/init_nc_cnt->frate; frate_mod=30; #ifdef SIG_ACTIONS for ( i = 0 ; i < (sizeof(sigs)/sizeof(*sigs)) ; i++) { signal(sigs[i], &sig_action); } #endif snprintf(init_data, sizeof(init_data), "IMG_S_M=%s&IMG_S_C=%s&IMG_S_O=%s&submit=APPLY", IMAGE_SIZE, IMAGE_QUALITY, IMAGE_FREQ); if( !*init_nc_cnt->connect_host ) strcpy(init_nc_cnt->connect_host, DEFAULT_HOST); if ( !*init_nc_cnt->connect_request ) strcpy(init_nc_cnt->connect_request, DEFAULT_PATH); if ( !cookie ) strcpy(cookie, "webcamPWD=RootCookie00000"); if ( init_nc_cnt->connect_port == 0 ) init_nc_cnt->connect_port=80; if(debug) fprintf(stderr, "nc800: host=%s\tport=%d\trequest=%s\n", init_nc_cnt->connect_host, init_nc_cnt->connect_port, init_nc_cnt->connect_request); // greet our client; s = "HTTP/1.0 200 OK\r\n" "Server: Web Server/1.0\r\n" "Content-Type: multipart/x-mixed-replace;boundary=--IPCamBoundary--\r\n" "MIME-version: 1.0\r\n" "Pragma: no-cache\r\n" "Cache-Control: no-cache\r\n" "Expires: 01 Jan 1970 00:00:00 GMT\r\n" "\r\n"; errno = 0; write(1, s, strlen(s)); return(init_nc_cnt); } /* * HEADER: 20 bytes / 10 16bit shorts * 0 = 10794 chars "**" * 1 = 4286 image size * 2 = 0 NULL ? * 3 = 320 image width * 4 = 240 image height * 5 = 70 image compression * 6 = 0 NULL ? * 7 = 0 NULL ? * 8 = 0 NULL ? * 9 = 0 NULL ? * */ static int nc_read_header(struct nc_cam *nc_cam) { u_short headerbuf[32]; int i, j; char *p; j=20; p = (char *) headerbuf; do { errno=0; if ( (i = read(nc_cam->sock, p, j)) == -1) { if(errno == EAGAIN) continue; if(debug) fprintf(stderr, "nc_read_header: i=%d, errno=%d\n", i, errno); syslog(LOG_ERR, "nc800: nc_read_header: i=%d, errno=%d (%m)", i, errno); return(-1); } else if ( i == 0 ) { syslog(LOG_ERR, "nc800: nc_cam_next: EOF: i=%d, errno=%d (%m)", i, errno); return(-2); } j -= i; p += i; } while(j); /* test headsr */ if ( *headerbuf != 10794 || headerbuf[6] || headerbuf[7] || headerbuf[8] || headerbuf[9] ) { syslog(LOG_ERR, "nc800: nc_read_header: invalid NC header"); return(-1); } nc_cam->content_length = headerbuf[1]; nc_cam->width = headerbuf[3]; nc_cam->height = headerbuf[4]; if(debug > 1) fprintf(stderr, "img %d/%d(%d):\timage_length=%d, image_width=%d image_height=%d\n", nc_cam->imgcnt, image_errors, connect_count, nc_cam->content_length, nc_cam->width, nc_cam->height); return 0; } static int nc_connect_to_cam(struct nc_cam *cam_cnt) { char buf[256]; int i, j; // struct timeval tv; int soc_type = SOCK_STREAM; struct sockaddr_in sock_out; struct hostent *hp; connect_count++; cam_cnt->imgcnt=0; if(debug) fprintf(stderr, "nc_connect_to_cam [#%d]\n", connect_count); if(cam_cnt->sock > 0) { close(cam_cnt->sock); cam_cnt->sock = -1; } #ifdef PROCESS_STATS setproctitle("nc800: connecting to %s", cam_cnt->connect_host); #endif cam_cnt->sock = socket(PF_INET, soc_type, 0); if (cam_cnt->sock == -1) { syslog(LOG_ERR,"nc800: error getting socket: %d (%m)\n", errno); return -1; } hp = gethostbyname(cam_cnt->connect_host); if (!hp) { syslog(LOG_ERR,"nc800: unknown host: %s\n", cam_cnt->connect_host); return -1; } memcpy(&sock_out.sin_addr, hp->h_addr, hp->h_length); sock_out.sin_port = htons(cam_cnt->connect_port); sock_out.sin_family = PF_INET; if (connect(cam_cnt->sock,(struct sockaddr *)&sock_out,sizeof(sock_out))) { close(cam_cnt->sock); cam_cnt->sock=-1; if(debug) { fprintf(stderr,"failed to connect to %s - %s\n", cam_cnt->connect_host, strerror(errno)); } syslog(LOG_ERR, "nc800: failed to connect to %s : %d (%m)\n", cam_cnt->connect_host, errno); #ifdef PROCESS_STATS setproctitle("nc800: failed to connect to %s", cam_cnt->connect_host); #endif return -1; } #ifdef PROCESS_STATS setproctitle("connection established to %s", cam_cnt->connect_host); #endif syslog(LOG_INFO, "nc800: nc_connect_to_cam %s [#%d] sock=%d\n", cam_cnt->connect_host, connect_count, cam_cnt->sock); i=1; errno=0; if(setsockopt(cam_cnt->sock, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i))) { syslog(LOG_ERR, "nc800: setsockopt SO_KEEPALIVE failed : %d (%m)\n", errno); } errno=0; cam_cnt->timeout.tv_usec = 0; /* 0 Microseconds */ cam_cnt->timeout.tv_sec = IMAGE_READ_TIMEOUT; /* 2 Secs Timeout */ if(setsockopt(cam_cnt->sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&cam_cnt->timeout,sizeof(struct timeval))) { syslog(LOG_ERR, "nc800: setsockopt SO_RCVTIMEO failed : %d (%m)\n", errno); } errno=0; j = sizeof(i); if (getsockopt(cam_cnt->sock, SOL_SOCKET, SO_RCVBUF, &i, (socklen_t *) &j)) { syslog(LOG_ERR, "nc800: getsockopt SO_RCVBUF failed : %d\n", errno); if(debug) perror("getsockopt"); } else { if (debug) fprintf(stderr, "setsockopt SO_RCVBUF = %d\n", i); if ( i < cam_cnt->image_buff_size ) { errno=0; i = cam_cnt->image_buff_size; if (setsockopt(cam_cnt->sock, SOL_SOCKET, SO_RCVBUF, &i, sizeof(i))) { syslog(LOG_ERR, "nc800: setsockopt SO_RCVBUF %d failed : %d (%m)\n", i, errno); } else if (debug) { fprintf(stderr, "setsockopt SO_RCVBUF = %d\n", i); } } } if(debug > 1) fprintf(stderr, "*** nc_connect_to_cam success webcam_fd=%d\n", cam_cnt->sock); /* nc_set_image_param(cam_cnt); */ sprintf(buf, "GET %s HTTP/1.0\r\n", cam_cnt->connect_request); if (*cookie) { strcat(buf, "Cookie: "); strcat(buf, cookie); strcat(buf, "\r\n"); } strcat(buf, "\r\n"); errno=0; i = write(cam_cnt->sock, buf, strlen(buf)); if (i < 1) { if(debug) perror("write"); return(-1); } if(debug > 1) { fprintf(stderr, "connect_to_cam wrote %d\n", i); fputs(buf, stderr); } /* mark last image timestamp as now so 1st image gets a chance */ gettimeofday(&cam_cnt->image_time, NULL); return 0; } int nc_cam_start(struct nc_cam *nc_ptr) { if (nc_connect_to_cam(nc_ptr) < 0) { perror("connect"); } return(0); } /* int nc_set_image_param(struct nc_cam *nc_ptr) { char buf[256]; int i, j; char p; j = sprintf(buf, "POST %s HTTP/1.0\r\nContent-Length: %d\r\nCookie: %s\r\n\r\n%s" init_url, strlen(init_data), cookie, init_data); p=buf; j=i; do( errno=0; if ( (i = write(1, p, j)) < 0) { return(0); } j -= i; p += i; while(j); read(nc_cnt_ptr->sock, p, j)) < 0) } */ int nc_cam_next(struct nc_cam *nc_cnt_ptr) { int i,j,k;; char *p; i=0; nc_cnt_ptr->content_length=0; if (nc_read_header(nc_cnt_ptr) < 0) { syslog(LOG_ERR, "nc800: bad header_read\n"); #ifdef PROCESS_STATS setproctitle("bad header_read"); #endif return(-1); } if (nc_cnt_ptr->content_length > nc_cnt_ptr->image_buff_size ) { int s; s = nc_cnt_ptr->content_length + (nc_cnt_ptr->content_length / 10); if(debug) syslog(LOG_INFO, "realloc image_buff from %d to %d(%d)\n", nc_cnt_ptr->image_buff_size, s, nc_cnt_ptr->content_length); nc_cnt_ptr->image_buff = realloc(nc_cnt_ptr->image_buff, s); nc_cnt_ptr->image_buff_size = s; s *= 10; // setsockopt(nc_cnt_ptr->sock, SOL_SOCKET, SO_RCVBUF, &s, sizeof(s)); } if(debug > 1) { fprintf(stderr, "reading %d\n", nc_cnt_ptr->content_length); fflush(stderr); } p = nc_cnt_ptr->image_buff; j = nc_cnt_ptr->content_length; k = 0; do { errno=0; if ( (i = read(nc_cnt_ptr->sock, p, j)) < 0) { if(debug > 1) fputc('X', stderr); if ( k++ > 5 ) { struct timeval ltv; gettimeofday(<v, NULL); // check if it has been longer then IMAGE_TIMEOUT since our last sucessful // image read. if ( (ltv.tv_sec - nc_cnt_ptr->image_time.tv_sec) > IMAGE_TIMEOUT ){ syslog(LOG_ERR, "nc800: nc_cam_next IMAGE_TIMEOUT %ds, errno=%d (%m)", IMAGE_TIMEOUT, errno); return(-4); } k=0; } if(errno == EAGAIN) { if(debug) syslog(LOG_ERR, "nc800: bad image read (timeout?), img=%d, %d of %d\n", nc_cnt_ptr->imgcnt, j, nc_cnt_ptr->content_length); SLEEP(0,10000000); // .010 a sec re_read++; continue; } if(debug) fprintf(stderr, "nc_cam_next: i=%d, errno=%d\n", i, errno); syslog(LOG_ERR, "nc800: nc_cam_next: i=%d, errno=%d (%m)", i, errno); return(-2); } else if ( i == 0 ) { syslog(LOG_ERR, "nc800: nc_cam_next: EOF: i=%d, errno=%d (%m)", i, errno); return(-2); } else { k=0; if(debug > 1) fputc('+', stderr); } j -= i; p += i; } while(j); /* now we got a image we should do a simple check that * it is what we want ( a jpeg image ) */ if ( (char) *nc_cnt_ptr->image_buff != (char) 0xff || (char) nc_cnt_ptr->image_buff[1] != (char) 0xd8 || (char) nc_cnt_ptr->image_buff[2] != (char) 0xff || (char) nc_cnt_ptr->image_buff[nc_cnt_ptr->content_length -2] != (char) 0xff || (char) nc_cnt_ptr->image_buff[nc_cnt_ptr->content_length -1] != (char) 0xd9 ) { image_errors++; return(-2); } else { if(debug > 2) fprintf(stderr, "Jpeg marker ok\n"); } gettimeofday(&nc_cnt_ptr->image_time, NULL); return(0); } static int nc_send_image(struct nc_cam *nc_ptr) { int i, j; char *p; char buffy[256]; i = snprintf(buffy, sizeof(buffy), "--IPCamBoundary--\r\n" "ETag: image_%06d\r\n" "Content-Type: image/jpeg\r\n" "Content-Length: %d\r\n" "\r\n", nc_ptr->imgcnt, nc_ptr->content_length); j=i; p=buffy; do { errno=0; if ( (i = write(1, p, j)) < 0) { if(debug) { fprintf(stderr, "nc_send_image header: i=%d, errno=%d\n", i, errno); perror("write header (nc_send_image)"); } syslog(LOG_ERR, "nc800: nc_send_image header: i=%d, errno=%d (%m), exiting", i, errno); nc_cam_cleanup(nc_ptr); exit(1); } j -= i; p += i; if (j) re_wrt++; } while(j); nc_ptr->imgcnt++; image_data_count += nc_ptr->content_length + 20; // fprintf(stderr, "\nimage %d = %hhx %hhx -- %hhx %hhx\n", // nc_ptr->imgcnt, (char) *nc_ptr->image_buff, (char ) nc_ptr->image_buff[1], // (char) nc_ptr->image_buff[nc_ptr->content_length -1], // (char) nc_ptr->image_buff[nc_ptr->content_length -2]); if(image_rec > 0) { int fdi; snprintf(buffy, sizeof(buffy), "img%02d.jpeg", (nc_ptr->imgcnt % image_rec)); fdi = creat(buffy, 0644); if ( fdi < 1) { perror("open"); } else { errno = 0; i = write(fdi, nc_ptr->image_buff, nc_ptr->content_length); fprintf(stderr, "%d written to %s (%d)\n", i, buffy, fdi); close(fdi); } } if(no_write_image==0) { j=nc_ptr->content_length; p=nc_ptr->image_buff; do { errno = 0; if ( (i = write(1, p, j)) < 0) { if(debug) { fprintf(stderr, "nc_send_image: i=%d, errno=%d\n", i, errno); perror("write (nc_send_image)"); } syslog(LOG_ERR, "nc800: nc_send_image image: i=%d, errno=%d (%m), exiting", i, errno); nc_cam_cleanup(nc_ptr); exit(1); } j -= i; p += i; if (j) re_wrt++; } while(j); } return(0); } #ifdef PROCESS_STATS static void procstats(struct nc_cam *nc_ptr) { int k, c, r; struct timeval tv; gettimeofday(&tv, NULL); k = tv.tv_sec - nc_ptr->frate_timer; c = nc_ptr->imgcnt - nc_ptr->frate_imgcnt; r = c/k; gettimeofday(&tv, NULL); setproctitle("%d sec %d=frms %d=err %d=conn %dK=data" " %d/%d=frate %d=drate, %d=rds %d=wrts", k, nc_ptr->imgcnt, image_errors, connect_count, (image_data_count / 1000), r,nc_ptr->frate, (image_data_count / 1000 )/k, re_read, re_wrt); return; } #endif static int nc_adjust_frate(struct nc_cam *nc_ptr) { int k, c,r,d; float f; struct timeval tv; gettimeofday(&tv, NULL); k = tv.tv_sec - nc_ptr->frate_timer; c = nc_ptr->imgcnt - nc_ptr->frate_imgcnt; r = c/k; syslog(LOG_INFO, "nc800: %d sec %d(%d) frms %d err %d conn %dK data," " %d/%d frate, %d drate, %d re-rds, %d re-wrts", k, c, nc_ptr->imgcnt, image_errors, connect_count, (image_data_count / 1000), r,nc_ptr->frate, (image_data_count / 1000 )/k, re_read, re_wrt); if(abs(r - nc_ptr->frate) <= 1) { frate_mod = (1200 * nc_ptr->frate); // check again in 10 min } else { frate_mod = (300 * nc_ptr->frate); // 5 min } if ((nc_ptr->frate - r) > 3 && nc_ptr->frate_nano_delay < 1000){ syslog(LOG_INFO, "nc800: %d - %d > 3 and %d < 1000", r,nc_ptr->frate, nc_ptr->frate_nano_delay); return(0); } f = (float) c / ((float) nc_ptr->frate * k) ; d = (int) (nc_ptr->frate_nano_delay * f) ; if( d > 999999999) { d = 999999999; } else if ( d < 99999 ) { d = 99999; } syslog(LOG_INFO, "nc800: frate_nano_delay: %d, new: %d, adjust: %.4f, mod: %d\n", nc_ptr->frate_nano_delay, d, f, frate_mod); nc_ptr->frate_nano_delay = d; nc_ptr->frate_timer = tv.tv_sec; nc_ptr->frate_imgcnt = nc_ptr->imgcnt; return(0); } #ifdef SIG_ACTIONS static void sig_action(int sig) { char *s; int quit; switch(sig) { case SIGBUS: case SIGFPE: case SIGHUP: case SIGILL: case SIGINT: case SIGPIPE: case SIGQUIT: case SIGSEGV: case SIGTERM: case SIGXCPU: case SIGXFSZ: quit=1; break; default: quit=0; break; } s = strsignal(sig); syslog(LOG_INFO, "nc800: Signal %s (%d) received -- %s\n", s, sig, (quit ? "exiting" : " ") ); fprintf(stderr, "nc800: Signal %s (%d) received -- %s\n", s, sig, (quit ? "exiting" : " ") ); if(quit) exit(0); return; } #endif void nc_cam_cleanup(struct nc_cam *nc_cam_ptr) { if(nc_cam_ptr) { free(nc_cam_ptr->image_buff); free(nc_cam_ptr); } return; } int mywrit(int fd, void *b, size_t sz) { int j, i; char *p; j=sz; p=b; do { errno=0; if ( (i = write(fd, p, j)) < 0) { return(i); } j -= i; p += i; if (j) re_wrt++; } while(j); return(sz); } int myread(int fd, void *b, size_t sz) { int j, i; char *p; j=sz; p = (char *) b; do { errno=0; if ( (i = read(fd, &p, j)) < 0) { if(errno == EAGAIN) { SLEEP(0, 9999); continue; } if(debug) fprintf(stderr, "myread: i=%d, errno=%d\n", i, errno); return(i); } else if ( i == 0 ) { /* syslog(LOG_ERR, "nc800: nc_cam_next: EOF: i=%d, errno=%d (%m)", i, errno); */ return(0); } j -= i; p += i; } while(j); return(sz); }