/*!*************************************************************************** *! FILE NAME : camogm.c *! DESCRIPTION: Program to write captured video (and audio) to camera file system *! using Ogg container. *! Original implementation will copy package data to a buffer to use library calls? *! Copyright (C) 2007 Elphel, Inc. *! -----------------------------------------------------------------------------** *! This program is free software: you can redistribute it and/or modify *! it under the terms of the GNU General Public License as published by *! the Free Software Foundation, either version 3 of the License, or *! (at your option) any later version. *! *! This program is distributed in the hope that it will be useful, *! but WITHOUT ANY WARRANTY; without even the implied warranty of *! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *! GNU General Public License for more details. *! *! You should have received a copy of the GNU General Public License *! along with this program. If not, see . *! -----------------------------------------------------------------------------** *! *! $Log: camogm.c,v $ *! Revision 1.13 2011/04/22 22:38:30 elphel *! added "greedy" and "ignore_fps" options *! *! Revision 1.12 2010/08/16 17:10:59 elphel *! typo *! *! Revision 1.11 2010/08/08 21:14:04 elphel *! 8.0.8.38 *! *! Revision 1.10 2010/08/07 23:38:01 elphel *! fixed skipping (sometimes) frames at start recording *! *! Revision 1.9 2010/08/03 06:19:57 elphel *! more debug for debug-level>=3 *! *! Revision 1.8 2010/08/02 01:26:42 elphel *! error tracking *! *! Revision 1.7 2010/08/01 19:30:24 elphel *! new readonly parameter FRAME_SIZE and it support in the applications *! *! Revision 1.6 2010/07/18 16:59:09 elphel *! 8.0.8.31 - added parameters to camogm (one is to start at certain absolute time, helps to synchronize multiple cameras) *! *! Revision 1.5 2010/07/04 19:06:02 elphel *! moved acknowledge earlier *! *! Revision 1.4 2010/06/22 18:27:26 elphel *! bug fix *! *! Revision 1.3 2010/06/22 16:53:30 elphel *! camogm acknowledges received command by copying G_THIS_FRAME value to G_DAEMON_ERR+3 (default number for camogm) *! *! Revision 1.2 2009/02/25 17:50:02 spectr_rain *! removed deprecated dependency *! *! Revision 1.1.1.1 2008/11/27 20:04:01 elphel *! *! *! Revision 1.6 2008/11/21 01:52:53 elphel *! updated for 8.0 *! *! Revision 1.5 2008/11/20 23:21:32 elphel *! Put FIXME notes and removed parameters that are not used anymore *! *! Revision 1.4 2008/10/29 04:18:28 elphel *! v.8.0.alpha10 made a separate structure for global parameters (not related to particular frames in a frame queue) *! *! Revision 1.3 2008/10/13 16:55:53 elphel *! removed (some) obsolete P_* parameters, renamed CIRCLSEEK to LSEEK_CIRC constants (same as other similar) *! *! Revision 1.2 2008/09/07 19:48:08 elphel *! snapshot *! *! Revision 1.9 2008/04/13 21:05:19 elphel *! Fixing KML generation *! *! Revision 1.8 2008/04/11 23:09:33 elphel *! modified to handle kml generation *! *! Revision 1.7 2008/04/07 09:13:34 elphel *! Changes related to new Exif generation/processing *! *! Revision 1.6 2008/01/14 22:59:00 elphel *! 7.1.7.4 - added timelapse mode to camogm *! *! Revision 1.5 2007/12/03 08:28:45 elphel *! Multiple changes, mostly cleanup *! *! Revision 1.4 2007/11/29 00:38:57 elphel *! fixed timescale bug *! *! Revision 1.3 2007/11/19 05:07:19 elphel *! fixed 2 typos *! *! Revision 1.2 2007/11/19 03:23:21 elphel *! 7.1.5.5 Added support for *.mov files in camogm. *! *! Revision 1.1 2007/11/16 08:49:56 elphel *! Initial release of camogm - program to record video/image to the camera hard drive (or other storage) *! */ #include #include #include #include #include #include #include #include #include #include #include //#include //#include #include #include #include /*little <-> big endian ?*/ #include /* mmap */ #include #include #include #include #include // has to be before ogmstreams.h #include "ogmstreams.h" // move it to <>? //#include "camogm_exif.h" #include "camogm_ogm.h" #include "camogm_jpeg.h" #include "camogm_mov.h" #include "camogm_kml.h" #include "camogm.h" /* debug code follows */ // this will be defined in c313a.h #define SENSOR_PORTS 4 #undef GLOBALPARS #define GLOBALPARS(p, x) (globalPars[(p)][(x)-FRAMEPAR_GLOBALS]) /* end of debug code */ #define COMMAND_LOOP_DELAY 500000 //0.5sec #define DEFAULT_DEBUG_LVL 6 #define TRAILER_SIZE 0x02 #define MAP_OPTIONS MAP_FILE | MAP_PRIVATE char trailer[TRAILER_SIZE] = { 0xff, 0xd9 }; const char *exifFileNames[] = { "/dev/exif_exif0", "/dev/exif_exif1", "/dev/exif_exif2", "/dev/exif_exif3" }; const char *headFileNames[] = { "/dev/jpeghead0", "/dev/jpeghead1", "/dev/jpeghead2", "/dev/jpeghead3" }; const char *ctlFileNames[] = { "/dev/frameparsall0", "/dev/frameparsall1", "/dev/frameparsall2", "/de/framepars3" }; const char *circbufFileNames[] = {"/dev/circbuf0", "/dev/circbuf1", "/dev/circbuf2", "/dev/circbuf3" }; unsigned long * ccam_dma_buf[SENSOR_PORTS]; /* mmapped arrays */ int lastDaemonBit[SENSOR_PORTS] = {DAEMON_BIT_CAMOGM}; struct framepars_all_t *frameParsAll[SENSOR_PORTS]; struct framepars_t *framePars[SENSOR_PORTS]; unsigned long *globalPars[SENSOR_PORTS]; /// parameters that are not frame-related, their changes do not initiate any actions #define DEFAULT_DURATION 60 /*!default segment duration (seconds) */ #define DEFAULT_LENGTH 100000000 /*!default segment length (B) */ #define DEFAULT_GREEDY 0 /*!behavior for the files: 0 clean buffer, 1 - save as much as possible */ #define DEFAULT_IGNORE_FPS 0 /*!0 restartf file if fps changed, 1 - ignore variable fps (and skip less frames) */ //!Next 2 for Quicktime (mostly) #define DEFAULT_FRAMES 16384 /* Maximal number of frames in file segment (each need 4* (1 + 1/frames_per_chunk) bytes for the frame index */ #define DEFAULT_FRAMES_PER_CHUNK 10 /*second sparse index - for Quicktime fast forward */ #define DEFAULT_LENGTH 100000000 /*!default segment length (B) */ #define DEFAULT_EXIF 1 /* use Exif */ static char cmdbuf[1024]; static int cmdbufp = 0; // current input pointer in the command buffer (read from pipe) static int cmdstrt = 0; // start of the next partial command camogm_state sstate[SENSOR_PORTS]; //camogm_state * state; int debug_level; FILE* debug_file; void camogm_init(camogm_state *state, unsigned int port, char *pipe_name); int camogm_start(camogm_state *state); int camogm_stop(camogm_state *state); int camogm_reset(camogm_state *state); //! reset circbuf read pointer int camogm_debug(camogm_state *state, const char *fname); int camogm_debug_level(int d); void camogm_set_segment_duration(camogm_state *state, int sd); void camogm_set_segment_length(camogm_state *state, int sl); void camogm_set_greedy(camogm_state *state, int d); void camogm_set_ignore_fps(camogm_state *state, int d); void camogm_set_save_gp(camogm_state *state, int d); void camogm_set_prefix(camogm_state *state, const char * p); void camogm_set_exif(camogm_state *state, int d); void camogm_set_timescale(camogm_state *state, double d); //! set timescale, default=1.0 void camogm_set_frames_skip(camogm_state *state, int d); //! set number of frames to skip, if negative - seconds between frames void camogm_set_format(camogm_state *state, int d); void camogm_kml_set_enable(camogm_state *state, int d); void camogm_kml_set_horHalfFov(camogm_state *state, double dd); void camogm_kml_set_vertHalfFov(camogm_state *state, double dd); void camogm_kml_set_height_mode(camogm_state *state, int d); void camogm_kml_set_height(camogm_state *state, double dd); void camogm_kml_set_period(camogm_state *state, int d); void camogm_kml_set_near(camogm_state *state, double dd); // distance to PhotoOverlay int parse_cmd(camogm_state *state, FILE* npipe); char * getLineFromPipe(FILE* npipe); int sendImageFrame(camogm_state *state); void camogm_set_start_after_timestamp(camogm_state *state, double d); void camogm_set_max_frames(camogm_state *state, int d); void camogm_set_frames_per_chunk(camogm_state *state, int d); //!====================================================================================================== void put_uint16(void *buf, u_int16_t val) { unsigned char *tmp; tmp = (unsigned char*)buf; tmp[0] = val & 0xff; tmp[1] = (val >>= 8) & 0xff; } void put_uint32(void *buf, u_int32_t val) { unsigned char *tmp; tmp = (unsigned char*)buf; tmp[0] = val & 0xff; tmp[1] = (val >>= 8) & 0xff; tmp[2] = (val >>= 8) & 0xff; tmp[3] = (val >>= 8) & 0xff; } void put_uint64(void *buf, u_int64_t val) { unsigned char *tmp; tmp = (unsigned char*)buf; tmp[0] = val & 0xff; tmp[1] = (val >>= 8) & 0xff; tmp[2] = (val >>= 8) & 0xff; tmp[3] = (val >>= 8) & 0xff; tmp[4] = (val >>= 8) & 0xff; tmp[5] = (val >>= 8) & 0xff; tmp[6] = (val >>= 8) & 0xff; tmp[7] = (val >>= 8) & 0xff; } /** * @brief Initialize state for a particular sensor port. * @param[in] state pointer to #camogm_state structure for a particular sensor channel * @param[in] port sensor port number * @param[in] pipe_name pointer to command pipe name string * @return none */ void camogm_init(camogm_state *state, unsigned int port, char *pipe_name) { const char sserial[] = "elp0"; int * ipser = (int*)sserial; state->running = 0; // mo state->starting = 0; // mo state->vf = NULL; camogm_set_segment_duration(state, DEFAULT_DURATION); camogm_set_segment_length(state, DEFAULT_LENGTH); camogm_set_greedy(state, DEFAULT_GREEDY); camogm_set_ignore_fps(state, DEFAULT_IGNORE_FPS); camogm_set_max_frames(state, DEFAULT_FRAMES); camogm_set_frames_per_chunk(state, DEFAULT_FRAMES_PER_CHUNK); camogm_set_start_after_timestamp(state, 0.0); // start any time camogm_set_prefix(state, "\0"); camogm_set_save_gp(state, 0); camogm_reset(state); // sets state->buf_overruns =- 1 state->serialno = ipser[0]; state->last = 0; debug_file = stderr; camogm_debug_level(DEFAULT_DEBUG_LVL); strcpy(state->debug_name, "stderr"); camogm_set_timescale(state, 1.0); camogm_set_frames_skip(state, 0); // don't skip camogm_set_format(state, CAMOGM_FORMAT_OGM); state->exifSize = 0; state->exif = DEFAULT_EXIF; state->frame_lengths = NULL; state->frameno = 0; state->formats = 0; state->last_error_code = 0; // kml stuff camogm_kml_set_enable(state, 0); state->kml_file = NULL; camogm_kml_set_horHalfFov(state, 20.0); camogm_kml_set_vertHalfFov(state, 15.0); camogm_kml_set_height_mode(state, 0); camogm_kml_set_height(state, 10.0); camogm_kml_set_period(state, 2); // 2 sec camogm_kml_set_near(state, 40.0); // 40 m (distance to PhotoOverlay) state->kml_path[0] = '\0'; state->port_num = port; state->pipe_name = pipe_name; } int camogm_debug(camogm_state *state, const char * fname) { int none = 1; if (fname && strlen(fname) && strcmp(fname, "none") && strcmp(fname, "null") && strcmp(fname, "/dev/null")) none = 0; if (debug_file) { if (strcmp(state->debug_name, "stdout") && strcmp(state->debug_name, "stderr")) fclose(debug_file); debug_file = NULL; state->debug_name[0] = '\0'; } if (!none) { if (strcmp(fname, "stdout") == 0) debug_file = stdout; else if (strcmp(fname, "stderr") == 0) debug_file = stderr; else debug_file = fopen(fname, "w+"); } if (debug_file) { strncpy(state->debug_name, fname, sizeof(state->debug_name) - 1); state->debug_name[sizeof(state->debug_name) - 1] = '\0'; } return 0; } int camogm_debug_level(int d) { debug_level = d; return 0; } int camogm_start(camogm_state *state) { int timestamp_start; int rslt; int next_metadata_start, next_jpeg_len, fp; D1(fprintf(debug_file, "Starting recording\n")); double dtime_stamp; state->frameno = 0; state->timescale = state->set_timescale; //! current timescale, default 1 ///debug int * ifp = (int*)&(state->frame_params); int * ifp_this = (int*)&(state->this_frame_params); if (state->kml_enable) camogm_init_kml(); // do nothing if (state->format != state->set_format) { state->format = state->set_format; switch (state->format) { case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(); break; case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(); break; } state->formats |= 1 << (state->format); //! exit on unknown formats? } state->max_frames = state->set_max_frames; state->frames_per_chunk = state->set_frames_per_chunk; state->starting = 1; //!may be already set //! Check/set circbuf read pointer D3(fprintf(debug_file, "1: state->cirbuf_rp=0x%x\n", state->cirbuf_rp)); if ((state->cirbuf_rp < 0) || (lseek(state->fd_circ, state->cirbuf_rp, SEEK_SET) < 0) || (lseek(state->fd_circ, LSEEK_CIRC_VALID, SEEK_END) < 0 )) { D3(fprintf(debug_file, "2: state->cirbuf_rp=0x%x\n", state->cirbuf_rp)); // state->cirbuf_rp=lseek(state->fd_circ,LSEEK_CIRC_LAST,SEEK_END); /* In "greedy" mode try to save as many frames from the circbuf as possible */ state->cirbuf_rp = lseek(state->fd_circ, state->greedy ? LSEEK_CIRC_SCND : LSEEK_CIRC_LAST, SEEK_END); if (!state->ignore_fps) { // don't even try in ignore mode if (((fp = lseek(state->fd_circ, LSEEK_CIRC_PREV, SEEK_END))) >= 0) state->cirbuf_rp = fp; //!try to have 2 frames available for fps } state->buf_overruns++; //! file pointer here should match state->rp; so no need to do lseek(state->fd_circ,state->cirbuf_rp,SEEK_SET); state->buf_min = getGPValue(state->port_num, G_FREECIRCBUF); } else { if (state->buf_min > getGPValue(state->port_num, G_FREECIRCBUF)) state->buf_min = getGPValue(state->port_num, G_FREECIRCBUF); } D3(fprintf(debug_file, "3: state->cirbuf_rp=0x%x\n", state->cirbuf_rp)); D3(fprintf(debug_file, "4:lseek(state->fd_circ,LSEEK_CIRC_READY,SEEK_END)=%d\n", (int)lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END))); //! is this frame ready? if (lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END) < 0) return -CAMOGM_FRAME_NOT_READY; //! frame pointer valid, but no frames yet D3(fprintf(debug_file, "5: state->cirbuf_rp=0x%x\n", state->cirbuf_rp)); state->metadata_start = (state->cirbuf_rp) - 32; if (state->metadata_start < 0) state->metadata_start += state->circ_buff_size; ///================================== memcpy(&(state->frame_params), (unsigned long* )&ccam_dma_buf[state->port_num][state->metadata_start >> 2], 32); state->jpeg_len = state->frame_params.frame_length; //! frame_params.frame_length are now the length of bitstream if (state->frame_params.signffff != 0xffff) { D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->frame_params.signffff)); state->cirbuf_rp = -1; D1(fprintf(debug_file, "state->cirbuf_rp=0x%x\r\n", (int)state->cirbuf_rp)); D1(fprintf(debug_file, "%08x %08x %08x %08x %08x %08x %08x %08x\r\n", ifp[0], ifp[1], ifp[2], ifp[3], ifp[4], ifp[5], ifp[6], ifp[7])); return -CAMOGM_FRAME_BROKEN; } //! find location of the timestamp and copy it to the frame_params structure ///================================== timestamp_start = (state->cirbuf_rp) + ((state->jpeg_len + CCAM_MMAP_META + 3) & (~0x1f)) + 32 - CCAM_MMAP_META_SEC; //! magic shift - should index first byte of the time stamp if (timestamp_start >= state->circ_buff_size) timestamp_start -= state->circ_buff_size; memcpy(&(state->frame_params.timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][timestamp_start >> 2], 8); /// New - see if current timestamp is later than start one, if not return "CAMOGM_TOO_EARLY" reset read pointer and buffer read pointer if (state->start_after_timestamp > 0.0) { /// don't bother if it is 0 dtime_stamp = 0.000001 * state->frame_params.timestamp_usec + state->frame_params.timestamp_sec; if (dtime_stamp < state->start_after_timestamp) { state->cirbuf_rp = -1; D3(fprintf(debug_file, "Too early to start, %f < %f\n", dtime_stamp, state->start_after_timestamp)); return -CAMOGM_TOO_EARLY; } } D3(fprintf(debug_file, "6: state->cirbuf_rp=0x%x\n", state->cirbuf_rp)); //! see if next frame is available if ((lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END) < 0 ) || //! is that next frame ready? (((fp = lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END))) < 0)) { D3(fprintf(debug_file, "6a:lseek(state->fd_circ,LSEEK_CIRC_NEXT,SEEK_END)=0x%x, fp=0x%x\n", (int)lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END), (int)lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END))); lseek(state->fd_circ, state->cirbuf_rp, SEEK_SET); //!just in case - restore pointer return -CAMOGM_FRAME_NOT_READY; //! frame pointer valid, but no frames yet } next_metadata_start = fp - 32; if (next_metadata_start < 0) next_metadata_start += state->circ_buff_size; memcpy(&(state->this_frame_params), (unsigned long* )&ccam_dma_buf[state->port_num][next_metadata_start >> 2], 32); next_jpeg_len = state->this_frame_params.frame_length; //! frame_params.frame_length are now the length of bitstream if (state->this_frame_params.signffff != 0xffff) { //! should not happen ever D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->this_frame_params.signffff)); D1(fprintf(debug_file, "fp=0x%x\r\n", (int)fp)); D1(fprintf(debug_file, "%08x %08x %08x %08x %08x %08x %08x %08x\r\n", ifp_this[0], ifp_this[1], ifp_this[2], ifp_this[3], ifp_this[4], ifp_this[5], ifp_this[6], ifp_this[7])); // int * ifp = (int *) &(state->this_frame_params) ; // int * ifp_this = (int *) &(state->this_frame_params) ; state->cirbuf_rp = -1; return -CAMOGM_FRAME_BROKEN; } D3(fprintf(debug_file, "7: state->cirbuf_rp=0x%x\n", state->cirbuf_rp)); //! find location of the timestamp and copy it to the frame_params structure timestamp_start = fp + ((next_jpeg_len + CCAM_MMAP_META + 3) & (~0x1f)) + 32 - CCAM_MMAP_META_SEC; //! magic shift - should index first byte of the time stamp if (timestamp_start >= state->circ_buff_size) timestamp_start -= state->circ_buff_size; memcpy(&(state->this_frame_params.timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][timestamp_start >> 2], 8); //! verify that the essential current frame params did not change, if they did - return an error (need new file header) if (!state->ignore_fps && ((state->frame_params.width != state->this_frame_params.width) || (state->frame_params.height != state->this_frame_params.height))) { //! Advance frame pointer to the next (caller should try again) state->cirbuf_rp = fp; return -CAMOGM_FRAME_CHANGED; // no yet checking for the FPS } D3(fprintf(debug_file, "8: state->cirbuf_rp=0x%x\n", state->cirbuf_rp)); //! calcualte the frame period - time difference (in microseconds) state->frame_period = (state->this_frame_params.timestamp_usec - state->frame_params.timestamp_usec) + 1000000 * (state->this_frame_params.timestamp_sec - state->frame_params.timestamp_sec); //! correct for timelapse modes: state->frames_skip = state->set_frames_skip; if (state->frames_skip > 0) { state->frames_skip_left = 0; state->frame_period *= (state->frames_skip + 1); // state->frames_skip_left= state->set_frames_skip; } else if (state->frames_skip < 0) { state->frame_period = -(state->frames_skip); //! actual frame period will fluctuate to the nearest frame acquired (free running) state->frames_skip_left = state->frame_params.timestamp_sec; } D3(fprintf(debug_file, "9: state->frame_period=0x%x\n", state->frame_period)); state->time_unit = (ogg_int64_t)(((double)state->frame_period) * ((double)10) / ((double)state->timescale)); state->width = state->frame_params.width; state->height = state->frame_params.height; //!read JPEG header - it should stay the same for the whole file (restart new file if any parameters changed) //!rebuild JPEG header: lseek(state->fd_head, state->cirbuf_rp + 1, SEEK_END); //!+1 to avoid condition when jpeg_start==0. overloaded lseek will ignore 5 LSBs when SEEK_END state->head_size = lseek(state->fd_head, 0, SEEK_END); /// In 8.0 the header size might change for some jp4 modes if (state->head_size > JPEG_HEADER_MAXSIZE) { D0(fprintf(debug_file, "%s:%d: Too big JPEG header (%d > %d)", __FILE__, __LINE__, state->head_size, JPEG_HEADER_MAXSIZE )); return -2; } //! and read it lseek(state->fd_head, 0, 0); read(state->fd_head, state->jpegHeader, state->head_size); //! Restore read pointer to the original (now there may be no frame ready there yet) lseek(state->fd_circ, state->cirbuf_rp, SEEK_SET); //!here we are ready to initialize Ogm (or other) file switch (state->format) { case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_OGM: rslt = camogm_start_ogm(state); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_start_jpeg(state); break; case CAMOGM_FORMAT_MOV: rslt = camogm_start_mov(state); break; default: rslt = 0; // do nothing } if (rslt) { D0(fprintf(debug_file, "camogm_start() error, rslt=0x%x\n", rslt)); return rslt; } if (state->kml_enable) rslt = camogm_start_kml(state); // will turn on state->kml_used if it can if (rslt) return rslt; state->running = 1; state->starting = 0; D1(fprintf(debug_file, "Started OK\n")); return 0; } int sendImageFrame(camogm_state *state) { int rslt; unsigned char frame_packet_type = PACKET_IS_SYNCPOINT; int timestamp_start; ///debugging: // int * ifp = (int *) &(state->frame_params) ; int * ifp_this = (int*)&(state->this_frame_params); int fp; //! This is probably needed only for Quicktime (not to exceed already allocated frame index) if (state->frameno >= (state->max_frames)) { D3(fprintf(debug_file, "sendImageFrame:1: state->frameno(0x%x) >= state->max_frames(0x%x)\n", state->frameno, state->max_frames)); return -CAMOGM_FRAME_CHANGED; } //! Format changed? // D3(fprintf (debug_file,"sendImageFrame: format=%d, set_format=%d\n", state->format, state->set_format)); if (state->format != state->set_format) { D3(fprintf(debug_file, "sendImageFrame:2: state->format(0x%x) != state->set_format(0x%x)\n", state->format, state->set_format)); return -CAMOGM_FRAME_CHANGED; } //! check if file size is exceeded (assuming fopen),-CAMOGM_FRAME_CHANGED will trigger a new segment if ((state->vf) && (state->segment_length >= 0) && (ftell(state->vf) > state->segment_length)) { D3(fprintf(debug_file, "sendImageFrame:3: segment length exceeded\n")); return -CAMOGM_FRAME_CHANGED; } //!same for open if (((state->ivf) >= 0) && (state->segment_length >= 0) && (lseek(state->ivf, 0, SEEK_CUR) > state->segment_length)) { D3(fprintf(debug_file, "sendImageFrame:4: segment length exceeded\n")); return -CAMOGM_FRAME_CHANGED; } //! check the frame pointer is valid if ((fp = lseek(state->fd_circ, state->cirbuf_rp, SEEK_SET)) < 0) { D3(fprintf(debug_file, "sendImageFrame:5: invalid frame\n")); return -CAMOGM_FRAME_INVALID; //!it will probably be that allready } //! is the frame ready? if (lseek(state->fd_circ, LSEEK_CIRC_READY, SEEK_END) < 0) { D3(fprintf(debug_file, "?6,fp=0x%x ", fp)); //frame not ready, frame pointer seems valid, but not ready return -CAMOGM_FRAME_NOT_READY; //! frame pointer valid, but no frames yet } //! process skipping frames. TODO: add - skipping time between frames (or better - actual time period - use the nearest frame) instead of the frame number if ( (state->frames_skip > 0) && (state->frames_skip_left > 0 )) { //!skipping frames, not seconds. state->cirbuf_rp = lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END); //!optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers") if (state->save_gp) lseek(state->fd_circ, LSEEK_CIRC_SETP, SEEK_END); state->frames_skip_left--; D3(fprintf(debug_file, "?7 ")); //frame not ready return -CAMOGM_FRAME_NOT_READY; //! the required frame is not ready } //! Get metadata D3(fprintf(debug_file, "_1_")); state->metadata_start = state->cirbuf_rp - 32; if (state->metadata_start < 0) state->metadata_start += state->circ_buff_size; memcpy(&(state->this_frame_params), (unsigned long* )&ccam_dma_buf[state->port_num][state->metadata_start >> 2], 32); state->jpeg_len = state->this_frame_params.frame_length; //! frame_params.frame_length are now the length of bitstream if (state->this_frame_params.signffff != 0xffff) { D0(fprintf(debug_file, "%s:%d: wrong signature - %d\r\n", __FILE__, __LINE__, (int)state->this_frame_params.signffff)); D1(fprintf(debug_file, "state->cirbuf_rp=0x%x\r\n", (int)state->cirbuf_rp)); D1(fprintf(debug_file, "%08x %08x %08x %08x %08x %08x %08x %08x\r\n", ifp_this[0], ifp_this[1], ifp_this[2], ifp_this[3], ifp_this[4], ifp_this[5], ifp_this[6], ifp_this[7])); D3(fprintf(debug_file, "sendImageFrame:8: frame broken\n")); return -CAMOGM_FRAME_BROKEN; } D3(fprintf(debug_file, "_2_")); //! find location of the timestamp and copy it to the frame_params structure timestamp_start = state->cirbuf_rp + ((state->jpeg_len + CCAM_MMAP_META + 3) & (~0x1f)) + 32 - CCAM_MMAP_META_SEC; //! magic shift - should index first byte of the time stamp if (timestamp_start >= state->circ_buff_size) timestamp_start -= state->circ_buff_size; D3(fprintf(debug_file, "_3_")); memcpy(&(state->this_frame_params.timestamp_sec), (unsigned long* )&ccam_dma_buf[state->port_num][timestamp_start >> 2], 8); //! verify that the essential current frame params did not change, if they did - return an error (need new file header) if (!state->ignore_fps && ((state->frame_params.width != state->this_frame_params.width) || (state->frame_params.height != state->this_frame_params.height))) { D3(fprintf(debug_file, "sendImageFrame:9: WOI changed\n")); return -CAMOGM_FRAME_CHANGED; //! not yet checking for the FPS } //! check if file duration (in seconds) exceeded ,-CAMOGM_FRAME_CHANGED will trigger a new segment if ((state->segment_duration > 0) && ((state->this_frame_params.timestamp_sec - state->frame_params.timestamp_sec) > state->segment_duration)) { D3(fprintf(debug_file, "sendImageFrame:10: segment duration in seconds exceeded\n")); return -CAMOGM_FRAME_CHANGED; } //! check if (in timelapse mode) it is too early for the frame to be stored if ((state->frames_skip < 0) && (state->frames_skip_left > state->this_frame_params.timestamp_sec) ) { state->cirbuf_rp = lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END); //!optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers") if (state->save_gp) lseek(state->fd_circ, LSEEK_CIRC_SETP, SEEK_END); D3(fprintf(debug_file, "sendImageFrame:11: timelapse: frame will be skipped\n")); return -CAMOGM_FRAME_NOT_READY; //! the required frame is not ready } D3(fprintf(debug_file, "_4_")); if (state->exif) { D3(fprintf(debug_file, "_5_")); //! update the Exif header with the current frame metadata // updateExif(ep, state->ed, &(state->frame_params)); state->exifSize = lseek(state->fd_exif, 1, SEEK_END); // at the beginning of page 1 - position == page length // if (state->exifSize < 0) state->exifSize=0; // error from lseek; if (state->exifSize > 0) { //state->this_frame_params.meta_index lseek(state->fd_exif, state->this_frame_params.meta_index, SEEK_END); //! select meta page to use (matching frame) rslt = read(state->fd_exif, state->ed, state->exifSize); if (rslt < 0) rslt = 0; state->exifSize = rslt; } else state->exifSize = 0; } else state->exifSize = 0; D3(fprintf(debug_file, "_6_")); //! prepare a packet to be sent (a lst of memory chunks) state->chunk_index = 0; state->packetchunks[state->chunk_index ].bytes = 1; state->packetchunks[state->chunk_index++].chunk = &frame_packet_type; if (state->exif > 0) { //! insert Exif D3(fprintf(debug_file, "_7_")); state->packetchunks[state->chunk_index ].bytes = 2; state->packetchunks[state->chunk_index++].chunk = state->jpegHeader; state->packetchunks[state->chunk_index ].bytes = state->exifSize; state->packetchunks[state->chunk_index++].chunk = state->ed; state->packetchunks[state->chunk_index ].bytes = state->head_size - 2; state->packetchunks[state->chunk_index++].chunk = &(state->jpegHeader[2]); } else { D3(fprintf(debug_file, "_8_")); state->packetchunks[state->chunk_index ].bytes = state->head_size; state->packetchunks[state->chunk_index++].chunk = state->jpegHeader; } D3(fprintf(debug_file, "_9_")); /*! JPEG image data may be split in two segments (rolled over buffer end) - process both variants */ if ((state->cirbuf_rp + state->jpeg_len) > state->circ_buff_size) { //! two segments /*! copy from the beginning of the frame to the end of the buffer */ D3(fprintf(debug_file, "_10_")); state->packetchunks[state->chunk_index ].bytes = state->circ_buff_size - state->cirbuf_rp; state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp >> 2]; /*! copy from the beginning of the buffer to the end of the frame */ state->packetchunks[state->chunk_index ].bytes = state->jpeg_len - (state->circ_buff_size - state->cirbuf_rp); state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][0]; } else { // single segment D3(fprintf(debug_file, "_11_")); /*! copy from the beginning of the frame to the end of the frame (no buffer rollovers) */ state->packetchunks[state->chunk_index ].bytes = state->jpeg_len; state->packetchunks[state->chunk_index++].chunk = (unsigned char*)&ccam_dma_buf[state->port_num][state->cirbuf_rp >> 2]; } D3(fprintf(debug_file, "_12_")); state->packetchunks[state->chunk_index ].bytes = 2; state->packetchunks[state->chunk_index++].chunk = (unsigned char*)trailer; switch (state->format) { case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_OGM: rslt = camogm_frame_ogm(state); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_frame_jpeg(state); break; case CAMOGM_FORMAT_MOV: rslt = camogm_frame_mov(state); break; default: rslt = 0; // do nothing } if (rslt) { D3(fprintf(debug_file, "sendImageFrame:12: camogm_frame_***() returned %d\n", rslt)); return rslt; } if (state->kml_used) rslt = camogm_frame_kml(state); // will turn on state->kml_used if it can if (rslt) return rslt; D3(fprintf(debug_file, "_14_")); //!advance frame pointer state->frameno++; state->cirbuf_rp = lseek(state->fd_circ, LSEEK_CIRC_NEXT, SEEK_END); //!optionally save it to global read pointer (i.e. for debugging with imgsrv "/pointers") if (state->save_gp) lseek(state->fd_circ, LSEEK_CIRC_SETP, SEEK_END); D3(fprintf(debug_file, "_15_\n")); if (state->frames_skip > 0) { state->frames_skip_left = state->frames_skip; } else if (state->frames_skip < 0) { state->frames_skip_left += -(state->frames_skip); } return 0; } int camogm_stop(camogm_state *state) { int rslt = 0; if (!state->running) { if (!state->starting) { D2(fprintf(debug_file, "Recording was not running, nothing to stop\n")); } else { state->starting = 0; D1(fprintf(debug_file, "Dropping attempt to start\n")); } return 0; } D1(fprintf(debug_file, "Ending recording\n")); if (state->kml_used) camogm_end_kml(state); switch (state->format) { case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_OGM: rslt = camogm_end_ogm(state); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_end_jpeg(); break; case CAMOGM_FORMAT_MOV: rslt = camogm_end_mov(state); break; /// default: return 0; // do nothing } //! now close video file (if it is open) if (state->vf) fclose(state->vf); state->vf = NULL; if (rslt) return rslt; state->last = 1; //!state->running=0 should be output after file is finished and closed state->running = 0; state->starting = 0; return 0; } void camogm_free(camogm_state *state) { int f; //! free all file format handlers that were used //add kml when needed for (f = 0; f < 31; f++) { if (state->formats & ( 1 << (state->format))) { switch (f) { case CAMOGM_FORMAT_NONE: break; case CAMOGM_FORMAT_OGM: camogm_free_ogm(); break; case CAMOGM_FORMAT_JPEG: camogm_free_jpeg(); break; case CAMOGM_FORMAT_MOV: camogm_free_mov(); break; } } } state->formats = 0; } int camogm_reset(camogm_state *state) //! reset circbuf read pointer { state->cirbuf_rp = -1; state->buf_overruns = -1; //!first will not count return 0; } ///kml stuff void camogm_kml_set_enable(camogm_state *state, int d) { state->kml_enable = d; } void camogm_kml_set_horHalfFov(camogm_state *state, double dd) { state->kml_horHalfFov = dd; } void camogm_kml_set_vertHalfFov(camogm_state *state, double dd) { state->kml_vertHalfFov = dd; } void camogm_kml_set_height_mode(camogm_state *state, int d) { state->kml_height_mode = d; } void camogm_kml_set_height(camogm_state *state, double dd) { state->kml_height = dd; } void camogm_kml_set_period(camogm_state *state, int d) { state->kml_period = d; state->kml_last_ts = 0; state->kml_last_uts = 0; } void camogm_kml_set_near(camogm_state *state, double dd) // distance to PhotoOverlay { state->kml_near = dd; } void camogm_set_segment_duration(camogm_state *state, int sd) { state->segment_duration = sd; } void camogm_set_segment_length(camogm_state *state, int sl) { state->segment_length = sl; } void camogm_set_save_gp(camogm_state *state, int d) { state->save_gp = d; } void camogm_set_exif(camogm_state *state, int d) { state->exif = d; } void camogm_set_greedy(camogm_state *state, int d) { state->greedy = d ? 1 : 0; } void camogm_set_ignore_fps(camogm_state *state, int d) { state->ignore_fps = d ? 1 : 0; } void camogm_set_prefix(camogm_state *state, const char * p) { strncpy(state->path_prefix, p, sizeof(state->path_prefix) - 1); state->path_prefix[sizeof(state->path_prefix) - 1] = '\0'; } void camogm_set_timescale(camogm_state *state, double d) //! set timescale, default=1,000,000 { state->set_timescale = d; if ((state->running == 0) && (state->starting == 0)) { state->timescale = state->set_timescale; } } void camogm_set_frames_skip(camogm_state *state, int d) //! set frames to skip (for time lapse) { state->set_frames_skip = d; if ((state->running == 0) && (state->starting == 0)) { state->frames_skip = state->set_frames_skip; // state->frames_skip_left= state->set_fram_skip; state->frames_skip_left = 0; } } void camogm_set_format(camogm_state *state, int d) { int rslt = 0; state->set_format = d; if ((state->running == 0) && (state->starting == 0)) { state->format = state->set_format; switch (state->format) { case CAMOGM_FORMAT_NONE: rslt = 0; break; case CAMOGM_FORMAT_OGM: rslt = camogm_init_ogm(); break; case CAMOGM_FORMAT_JPEG: rslt = camogm_init_jpeg(); break; case CAMOGM_FORMAT_MOV: rslt = camogm_init_mov(); break; } if (rslt) { D0(fprintf(debug_file, "%s:%d: Error setting format to=%d\n", __FILE__, __LINE__, state->format)); } state->formats |= 1 << (state->format); } } //! needed for Quicktime - maybe something else? void camogm_set_max_frames(camogm_state *state, int d) { state->set_max_frames = d; if ((state->running == 0) && (state->starting == 0)) state->max_frames = d; } void camogm_set_frames_per_chunk(camogm_state *state, int d) { state->set_frames_per_chunk = d; if ((state->running == 0) && (state->starting == 0)) state->frames_per_chunk = d; } void camogm_set_start_after_timestamp(camogm_state *state, double d) { state->start_after_timestamp = d; } void camogm_status(camogm_state *state, char * fn, int xml) { int _len = 0; int _dur, _udur; //TODO:make it XML file FILE* f; char *_state, *_output_format, *_using_exif, *_using_global_pointer, *_compressor_state; int _b_free, _b_used, _b_size; // , save_p; int _frames_remain = 0; int _sec_remain = 0; int _frames_skip = 0; int _sec_skip = 0; char *_kml_enable, *_kml_used, *_kml_height_mode; _kml_enable = state->kml_enable ? "yes" : "no"; _kml_used = state->kml_used ? "yes" : "no"; _kml_height_mode = state->kml_height_mode ? "GPS altitude" : "map ground level"; //! 1 - actual, 0 - ground _b_free = getGPValue(state->port_num, G_FREECIRCBUF); _b_used = getGPValue(state->port_num, G_CIRCBUFSIZE) - getGPValue(state->port_num, G_FREECIRCBUF); _b_size = getGPValue(state->port_num, G_FRAME_SIZE); if (!fn) f = stdout; else if (strcmp(fn, "stdout") == 0) f = stdout; else if (strcmp(fn, "stderr") == 0) f = stderr; else { if (!((f = fopen(fn, "w")))) { D0(fprintf(debug_file, "Error opening %s\n", fn)); return; } } if (state->vf) _len = ftell(state->vf); //! for ogm else if ((state->ivf) >= 0) _len = lseek(state->ivf, 0, SEEK_CUR); //!for mov _dur = state->this_frame_params.timestamp_sec - state->frame_params.timestamp_sec; _udur = state->this_frame_params.timestamp_usec - state->frame_params.timestamp_usec; if (_udur < 0) { _dur -= 1; _udur += 1000000; } else if (_udur >= 1000000) { _dur += 1; _udur -= 1000000; } _state = state->running ? "running" : (state->starting ? "starting" : "stopped"); _output_format = state->format ? ((state->format == CAMOGM_FORMAT_OGM) ? "ogm" : ((state->format == CAMOGM_FORMAT_JPEG) ? "jpeg" : ((state->format == CAMOGM_FORMAT_MOV) ? "mov" : "other"))) : "none"; _using_exif = state->exif ? "yes" : "no"; _using_global_pointer = state->save_gp ? "yes" : "no"; _compressor_state = (getGPValue(state->port_num, P_COMPRESSOR_RUN) == 2) ? "running" : "stoppped"; if ( state->frames_skip > 0 ) { _frames_remain = state->frames_skip_left; _frames_skip = state->frames_skip; } else if ( state->frames_skip < 0 ) { _sec_remain = (state->frames_skip_left - state->this_frame_params.timestamp_sec); _sec_skip = -(state->frames_skip); } if (xml) { fprintf(f, "\n" \ "\n" \ " \"%s\"\n" \ " \"%s\"\n" \ " \"%s\"\n" \ " %d\n" \ " %d\n" \ " %f\n" \ " %d.%06d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " \"%s\"\n" \ " \"%s\"\n" \ " \"%s\"\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %f\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " %d\n" \ " \"%s\"\n" \ " %d\n" \ " \"%s\"\n" \ " \"%s\"\n" \ " \"%s\"\n" \ " \"%s\"\n" \ " \"%f\"\n" \ " \"%f\"\n" \ " \"%f\"\n" \ " \"%s\"\n" \ " \"%f\"\n" \ " %d\n" \ " %d.%06d\n" \ " \"%s\"\n" \ " \"%s\"\n" \ "\n", _state, _compressor_state, state->path, state->frameno, _b_size, state->start_after_timestamp, _dur, _udur, _len, state->frame_period, \ _frames_skip, _sec_skip, _frames_remain, _sec_remain, \ state->width, state->height, _output_format, _using_exif, \ state->path_prefix, state->segment_duration, state->segment_length, state->max_frames, state->timescale, \ state->frames_per_chunk, state->last_error_code, state->buf_overruns, state->buf_min, _b_free, _b_used, state->cirbuf_rp, \ state->debug_name, debug_level, _using_global_pointer, \ _kml_enable, _kml_used, state->kml_path, state->kml_horHalfFov, state->kml_vertHalfFov, state->kml_near, \ _kml_height_mode, state->kml_height, state->kml_period, state->kml_last_ts, state->kml_last_uts, \ state->greedy ? "yes" : "no", state->ignore_fps ? "yes" : "no"); } else { fprintf(f, "state %s\n", _state); fprintf(f, "compressor state %s\n", _compressor_state); fprintf(f, "file %s\n", state->path); fprintf(f, "frame %d\n", state->frameno); fprintf(f, "frame size %d\n", _b_size); fprintf(f, "start_after_timestamp %f\n", state->start_after_timestamp); fprintf(f, "file duration %d.%06d sec\n", _dur, _udur); fprintf(f, "file length %d B\n", _len); fprintf(f, "frame period %d (0x%x)\n", state->frame_period, state->frame_period); if ( _frames_skip > 0 ) fprintf(f, "frames to skip %d (left %d)\n", _frames_skip, _frames_remain); if ( _sec_skip < 0 ) fprintf(f, "timelapse period %d sec (remaining %d sec)\n", _sec_skip, _sec_remain); fprintf(f, "width %d (0x%x)\n", state->width, state->width); fprintf(f, "height %d (0x%x)\n", state->height, state->height); fprintf(f, "\n"); fprintf(f, "output format %s\n", _output_format); fprintf(f, "using exif %s\n", _using_exif); fprintf(f, "path prefix: %s\n", state->path_prefix); fprintf(f, "max file duration: %d sec\n", state->segment_duration); fprintf(f, "max file length: %d B\n", state->segment_length); fprintf(f, "max frames %d\n", state->max_frames); fprintf(f, "timescale %f\n", state->timescale); fprintf(f, "frames per chunk %d\n", state->frames_per_chunk); fprintf(f, "greedy %s\n", state->greedy ? "yes" : "no"); fprintf(f, "ignore fps %s\n", state->ignore_fps ? "yes" : "no"); fprintf(f, "\n"); fprintf(f, "last error code %d\n", state->last_error_code); fprintf(f, "buffer overruns %d\n", state->buf_overruns); fprintf(f, "buffer minimal %d\n", state->buf_min); fprintf(f, "buffer free %d\n", _b_free); fprintf(f, "buffer used %d\n", _b_used); fprintf(f, "circbuf_rp %d (0x%x)\n", state->cirbuf_rp, state->cirbuf_rp); fprintf(f, "\n"); fprintf(f, "debug output to %s\n", state->debug_name); fprintf(f, "debug level %d\n", debug_level); fprintf(f, "use global pointer %s\n", _using_global_pointer); fprintf(f, "\n\n"); fprintf(f, "kml_enable %s\n", _kml_enable); fprintf(f, "kml_used %s\n", _kml_used); fprintf(f, "kml_path %s\n", state->kml_path); fprintf(f, "kml_horHalfFov %f degrees\n", state->kml_horHalfFov); fprintf(f, "kml_vertHalfFov %f degrees\n", state->kml_vertHalfFov); fprintf(f, "kml_near %f m\n", state->kml_near); fprintf(f, "kml height mode %s\n", _kml_height_mode); fprintf(f, "kml_height (extra) %f m\n", state->kml_height); fprintf(f, "kml_period %d\n", state->kml_period); fprintf(f, "kml_last_ts %d.%06d\n", state->kml_last_ts, state->kml_last_uts); fprintf(f, "\n\n"); } if ((f != stdout) && (f != stderr)) fclose(f); if (state->buf_overruns >= 0) state->buf_overruns = 0; //! resets overruns after reading status , so "overruns" means since last reading status state->last_error_code = 0; //! Reset error state->buf_min = _b_free; } //! will read from pipe, return pointer to null terminated string if available, NULL otherwise char * getLineFromPipe(FILE* npipe) { int fl; char * nlp; //!remove used string if any if (cmdstrt > 0) { //!moving overlapping strings memmove(cmdbuf, &cmdbuf[cmdstrt], sizeof(cmdbuf) - cmdstrt); cmdbufp -= cmdstrt; cmdstrt = 0; } //! is there any complete string in a buffer? if (!cmdbufp) cmdbuf[cmdbufp] = 0; //!null-terminate first access (probably not needed for the static buffer // nlp= strchr(cmdbuf,'\n'); nlp = strpbrk(cmdbuf, ";\n"); if (!nlp) { //!no complete string, try to read more fl = fread(&cmdbuf[cmdbufp], 1, sizeof(cmdbuf) - cmdbufp - 1, npipe); cmdbuf[cmdbufp + fl] = 0; //! is there any complete string in a buffer after reading? // nlp= strchr(&cmdbuf[cmdbufp],'\n'); //! there were no new lines before cmdbufp nlp = strpbrk(&cmdbuf[cmdbufp], ";\n"); //! there were no new lines before cmdbufp cmdbufp += fl; //!advance pointer after pipe read } if (nlp) { //printf ("++nlp=%d\n", (int) (nlp-cmdbuf)); nlp[0] = 0; cmdstrt = nlp - cmdbuf + 1; //printf ("++cmdstrt=%d\n", cmdstrt); //printf ("cmdbuf[0]=%d, cmdbuf[1]=%d, cmdbuf[2]=%d, cmdbuf[3]=%d, \n",cmdbuf[0],cmdbuf[1],cmdbuf[2],cmdbuf[3]); for (fl = 0; cmdbuf[fl] && strchr(" \t", cmdbuf[fl]); fl++) ; //printf ("++fl=%d\n", fl); return &cmdbuf[fl]; } else { //printf ("notready: cmdbufp=%d, cmdstrt=%d\n",cmdbufp, cmdstrt); return NULL; } } // command[= \t]*args[ \t]* int parse_cmd(camogm_state *state, FILE* npipe) { char * cmd; char * args; char * argse; int d; double dd; // if (!((cmd=getLineFromPipe(npipe)))) return 0; //! nothing in the pipe //!skip empty commands while (((cmd = getLineFromPipe(npipe))) && !cmd[0]) ; if (!cmd) return 0; //! nothing in the pipe D2(fprintf(debug_file, "Got command: '%s'\n", cmd)); /// Acknowledge received command by copying frame number to per-daemon parameter #ifdef DISABLE_CODE // GLOBALPARS(state->port_num, G_DAEMON_ERR + lastDaemonBit[state->port_num]) = GLOBALPARS(state->port_num, G_THIS_FRAME); setGValue(state->port, G_DAEMON_ERR + lastDaemonBit[state->port_nun], getGValue(state->port_num, G_THIS_FRAME)); #endif /* DISABLE_CODE */ // printf ("cmd[0]=%d:%s\n",(int) cmd[0],cmd); args = strpbrk(cmd, "= \t"); //! is it just a single word command or does it have parameters? if (args) { args[0] = 0; args++; while (strchr("= \t", args[0])) args++; if (args[0]) { //! ltrim (args) for (argse = strchr(args, '\0') - 1; strchr("= \t", argse[0]); argse--) argse[0] = '\0'; } if (!args[0]) args = NULL; } //! now cmd is trimmed, arg is NULL or a pointer to trimmed command arguments if (strcmp(cmd, "start") == 0) { camogm_start(state); return 1; } else if (strcmp(cmd, "reset") == 0) { //! will reset pointer to the last acquired frame (if any) camogm_reset(state); return 2; } else if (strcmp(cmd, "stop") == 0) { camogm_stop(state); return 3; } else if (strcmp(cmd, "exit") == 0) { camogm_stop(state); camogm_free(state); exit(0); } else if (strcmp(cmd, "duration") == 0) { if (!(args) || (((d = strtol(args, NULL, 10))) <= 0)) d = DEFAULT_DURATION; camogm_set_segment_duration(state, d); return 4; } else if (strcmp(cmd, "length") == 0) { if (!(args) || (((d = strtol(args, NULL, 10))) <= 0)) d = DEFAULT_LENGTH; camogm_set_segment_length(state, d); return 5; } else if (strcmp(cmd, "prefix") == 0) { if (args) camogm_set_prefix(state, args); return 6; } else if (strcmp(cmd, "status") == 0) { camogm_status(state, args, 0); return 7; } else if (strcmp(cmd, "xstatus") == 0) { camogm_status(state, args, 1); return 7; } else if (strcmp(cmd, "save_gp") == 0) { if ((args) && (((d = strtol(args, NULL, 10))) >= 0)) camogm_set_save_gp(state, d); return 8; } else if (strcmp(cmd, "exif") == 0) { if ((args) && (((d = strtol(args, NULL, 10))) >= 0)) camogm_set_exif(state, d); return 8; } else if (strcmp(cmd, "debug") == 0) { camogm_debug(state, args); return 9; } else if (strcmp(cmd, "timescale") == 0) { dd = strtod(args, NULL); camogm_set_timescale(state, dd ? dd : 1.0); return 10; //!TODO: fix period calculation/check for frame skipping (just disable in frame skip mode?) //!TODO: add time period (system clock), not just frame skipping } else if (strcmp(cmd, "frameskip") == 0) { d = strtol(args, NULL, 10); camogm_set_frames_skip(state, d); return 11; } else if (strcmp(cmd, "timelapse") == 0) { //! period (in seconds) between stored frames d = strtol(args, NULL, 10); camogm_set_frames_skip(state, -d); return 11; } else if (strcmp(cmd, "format") == 0) { if (args) { if (strcmp(args, "none") == 0) camogm_set_format(state, 0); else if ((strcmp(args, "ogm" ) == 0) || (strcmp(args, "ogg") == 0)) camogm_set_format(state, CAMOGM_FORMAT_OGM); else if ((strcmp(args, "jpeg") == 0) || (strcmp(args, "jpg") == 0)) camogm_set_format(state, CAMOGM_FORMAT_JPEG); else if (strcmp(args, "mov" ) == 0) camogm_set_format(state, CAMOGM_FORMAT_MOV); } return 12; } else if (strcmp(cmd, "debuglev") == 0) { d = strtol(args, NULL, 10); camogm_debug_level(d ? d : 0); return 13; } else if (strcmp(cmd, "kml") == 0) { if ((args) && (((d = strtol(args, NULL, 10))) >= 0)) camogm_kml_set_enable(state, d); return 14; } else if (strcmp(cmd, "kml_hhf") == 0) { dd = strtod(args, NULL); camogm_kml_set_horHalfFov(state, dd); return 15; } else if (strcmp(cmd, "kml_vhf") == 0) { dd = strtod(args, NULL); camogm_kml_set_vertHalfFov(state, dd); return 16; } else if (strcmp(cmd, "kml_near") == 0) { dd = strtod(args, NULL); camogm_kml_set_near(state, dd); return 17; } else if (strcmp(cmd, "kml_alt") == 0) { if (args) { if (strcmp(args, "gps" ) == 0) camogm_kml_set_height_mode(state, 1); else if (strcmp(args, "ground") == 0) camogm_kml_set_height_mode(state, 0); } return 18; } else if (strcmp(cmd, "kml_height") == 0) { dd = strtod(args, NULL); camogm_kml_set_height(state, dd); return 19; } else if (strcmp(cmd, "kml_period") == 0) { d = strtol(args, NULL, 10); camogm_kml_set_period(state, d ? d : 1); return 20; } else if (strcmp(cmd, "frames_per_chunk") == 0) { d = strtol(args, NULL, 10); camogm_set_frames_per_chunk(state, d); return 21; } else if (strcmp(cmd, "max_frames") == 0) { d = strtol(args, NULL, 10); camogm_set_max_frames(state, d); return 22; } else if (strcmp(cmd, "start_after_timestamp") == 0) { dd = strtod(args, NULL); camogm_set_start_after_timestamp(state, dd); return 23; } else if (strcmp(cmd, "greedy") == 0) { dd = strtod(args, NULL); camogm_set_greedy(state, dd); return 24; } else if (strcmp(cmd, "ignore_fps") == 0) { dd = strtod(args, NULL); camogm_set_ignore_fps(state, dd); return 25; } return -1; } /** * @brief Create a list of pipe names from a single name given * * This function accepts a pipe name string provided by the user to the program and * creates a list of #SENSOR_PORTS similar names but with port number added to the end of * the name. * @param[in] pipe_name pipe name provided by user * @param[out] names a list of names with port number added * return 0 if the list was successfully created or -1 otherwise */ int create_pipe_names(const char *pipe_name, char **names) { int ret = 0; unsigned int len = strlen(pipe_name); for (int i = 0; i < SENSOR_PORTS; i++) { char *name = malloc(PATH_MAX); if (name == NULL) { ret = -1; break; } strncpy(name, pipe_name, PATH_MAX - 1); snprintf(&name[len], PATH_MAX - len - 1, "%u", i); names[i] = name; } if (ret < 0) { for (int i = 0; i < SENSOR_PORTS; i++) { free(names[i]); names[i] = NULL; } } return ret; } /** * @brief This function closes open files and deletes allocated memory. * @param[in] state pointer to #camogm_state structure for a particular sensor channel * return none */ void clean_up(camogm_state *state) { if (state->fd_exif > 0) close(state->fd_exif); if (state->fd_head > 0) close(state->fd_head); if (state->fd_circ > 0) close(state->fd_circ); if (state->fd_fparmsall) close(state->fd_fparmsall); free(state->pipe_name); state->pipe_name = NULL; } /** * @brief Main processing loop * * If recording is on, this function will check for new commands in command pipe after each frame. If recording is * turn off, it will poll command pipe each #COMMAND_LOOP_DELAYS microseconds and then sleep. * @param[in] state #camogm_state structure associated with a single port * @return normally this function loops indefinitely processing commands but will return negative exit code in case * of error and \e EXIT_SUCCESS it eventually terminates in normal way. */ int listener_loop(camogm_state *state) { FILE *cmd_file; int rslt, ret, cmd; int fp0, fp1; int process = 1; unsigned int port = state->port_num; const char *pipe_name = state->pipe_name; // open Exif header file #ifdef DISABLE_CODE state->fd_exif = open(exifFileNames[port], O_RDONLY); if (state->fd_exif < 0) { // check control OK D0(fprintf(debug_file, "Error opening %s\n", exifFileNames[port])); clean_up(state); return -1; } #endif /* DESABLE_CODE */ // open JPEG header file state->fd_head = open(headFileNames[port], O_RDWR); if (state->fd_head < 0) { // check control OK D0(fprintf(debug_file, "Error opening %s\n", headFileNames[port])); clean_up(state); return -1; } state->head_size = lseek(state->fd_head, 0, SEEK_END); if (state->head_size > JPEG_HEADER_MAXSIZE) { D0(fprintf(debug_file, "%s:%d: Too big JPEG header (%d > %d)", __FILE__, __LINE__, state->head_size, JPEG_HEADER_MAXSIZE )); clean_up(state); return -2; } // open circbuf and mmap it (once at startup) state->fd_circ = open(circbufFileNames[port], O_RDWR); if (state->fd_circ < 0) { // check control OK D0(fprintf(debug_file, "Error opening %s\n", circbufFileNames[port])); clean_up(state); return -2; } // find total buffer length (it is in defines, actually in c313a.h state->circ_buff_size = lseek(state->fd_circ, 0, SEEK_END); ccam_dma_buf[port] = (unsigned long*)mmap(0, state->circ_buff_size, PROT_READ, MAP_SHARED, state->fd_circ, 0); if ((int)ccam_dma_buf[port] == -1) { D0(fprintf(debug_file, "Error in mmap of %s\n", circbufFileNames[port])); clean_up(state); return -3; } // now open/mmap file to read sensor/compressor parameters (currently - just free memory in circbuf and compressor state) #ifdef DISABLE_CODE state->fd_fparmsall = open(ctlFileNames[port], O_RDWR); if (state->fd_fparmsall < 0) { // check control OK D0(fprintf(debug_file, "%s:%d:%s: Error opening %s\n", __FILE__, __LINE__, __FUNCTION__, ctlFileNames[port])); clean_up(state); return -2; } // now try to mmap // PROT_WRITE - only to write acknowledge frameParsAll[port] = (struct framepars_all_t*)mmap(0, sizeof(struct framepars_all_t), PROT_READ | PROT_WRITE, MAP_SHARED, state->fd_fparmsall, 0); if ((int)frameParsAll[port] == -1) { D0(fprintf(debug_file, "%s:%d:%s: Error in mmap in %s\n", __FILE__, __LINE__, __FUNCTION__, ctlFileNames[port])); clean_up(state); return -3; } framePars[port] = frameParsAll[port]->framePars; globalPars[port] = frameParsAll[port]->globalPars; #endif /* DESABLE_CODE */ // create a named pipe // always delete the pipe if it existed, start a fresh one ret = unlink(pipe_name); if (ret) { D1(fprintf(debug_file, "Unlink %s returned %d, errno=%d \n", pipe_name, ret, errno)); } ret = mkfifo(pipe_name, 0777); //EEXIST // now should not exist if (ret) { if (errno == EEXIST) { D1(fprintf(debug_file, "Named pipe %s already exists, will use it.\n", pipe_name)); } else { D0(fprintf(debug_file, "Can not create a named pipe %s, errno=%d \n", pipe_name, errno)); clean_up(state); return -4; } } // now open the pipe - will block until something will be written (or just open for writing, // reads themselves will not block) if (!((cmd_file = fopen(pipe_name, "r")))) { D0(fprintf(debug_file, "Can not open command file %s\n", pipe_name)); clean_up(state); return -5; } D0(fprintf(debug_file, "Pipe %s open for reading\n", pipe_name)); // to make sure something is sent out // enter main processing loop while (process) { // look at command queue first cmd = parse_cmd(state, cmd_file); if (cmd) { if (cmd < 0) D0(fprintf(debug_file, "Unrecognized command\n")); } else if (state->running) { // no commands in queue, started switch ((rslt = -sendImageFrame(state))) { case 0: break; // frame sent OK, nothing to do (TODO: check file length/duration) case CAMOGM_FRAME_NOT_READY: // just wait for the frame to appear at the current pointer // we'll wait for a frame, not to waste resources. But if the compressor is stopped this program will not respond to any commands // TODO - add another wait with (short) timeout? fp0 = lseek(state->fd_circ, 0, SEEK_CUR); if (fp0 < 0) { D0(fprintf(debug_file, "%s:line %d got broken frame (%d) before waiting for ready\n", __FILE__, __LINE__, fp0)); rslt = CAMOGM_FRAME_BROKEN; } else { fp1 = lseek(state->fd_circ, LSEEK_CIRC_WAIT, SEEK_END); if (fp1 < 0) { D0(fprintf(debug_file, "%s:line %d got broken frame (%d) while waiting for ready. Before that fp0=0x%x\n", __FILE__, __LINE__, fp1, fp0)); rslt = CAMOGM_FRAME_BROKEN; } else { break; } } // no break case CAMOGM_FRAME_CHANGED: // frame parameters have changed case CAMOGM_FRAME_NEXTFILE: // next file needed (need to switch to a new file (time/size exceeded limit) case CAMOGM_FRAME_INVALID: // invalid frame pointer case CAMOGM_FRAME_BROKEN: // frame broken (buffer overrun) // restart the file D3(fprintf(debug_file,"%s:line %d - sendImageFrame() returned -%d\n", __FILE__, __LINE__, rslt)); camogm_stop(state); camogm_start(state); break; case CAMOGM_FRAME_FILE_ERR: // error with file I/O case CAMOGM_FRAME_OTHER: // other errors D0(fprintf(debug_file, "%s:line %d - error=%d\n", __FILE__, __LINE__, rslt)); break; default: D0(fprintf(debug_file, "%s:line %d - should not get here (rslt=%d)\n", __FILE__, __LINE__, rslt)); clean_up(state); exit(-1); } // switch sendImageFrame() if ((rslt != 0) && (rslt != CAMOGM_FRAME_NOT_READY) && (rslt != CAMOGM_FRAME_CHANGED)) state->last_error_code = rslt; } else if (state->starting) { // no commands in queue,starting (but not started yet) // retry starting switch ((rslt = -camogm_start(state))) { case 0: break; // file started OK, nothing to do case CAMOGM_TOO_EARLY: lseek(state->fd_circ, LSEEK_CIRC_TOWP, SEEK_END); // set pointer to the frame to wait for lseek(state->fd_circ, LSEEK_CIRC_WAIT, SEEK_END); // It already passed CAMOGM_FRAME_NOT_READY, so compressor may be running already break; // no need to wait extra case CAMOGM_FRAME_NOT_READY: // just wait for the frame to appear at the current pointer // we'll wait for a frame, not to waste resources. But if the compressor is stopped this program will not respond to any commands // TODO - add another wait with (short) timeout? case CAMOGM_FRAME_CHANGED: // frame parameters have changed case CAMOGM_FRAME_NEXTFILE: case CAMOGM_FRAME_INVALID: // invalid frame pointer case CAMOGM_FRAME_BROKEN: // frame broken (buffer overrun) usleep(COMMAND_LOOP_DELAY); // it should be not too long so empty buffer will not be overrun break; case CAMOGM_FRAME_FILE_ERR: // error with file I/O case CAMOGM_FRAME_OTHER: // other errors D0(fprintf(debug_file, "%s:line %d - error=%d\n", __FILE__, __LINE__, rslt)); break; default: D0(fprintf(debug_file, "%s:line %d - should not get here (rslt=%d)\n", __FILE__, __LINE__, rslt)); clean_up(state); exit(-1); } // switch camogm_start() if ((rslt != 0) && (rslt != CAMOGM_TOO_EARLY) && (rslt != CAMOGM_FRAME_NOT_READY) && (rslt != CAMOGM_FRAME_CHANGED) ) state->last_error_code = rslt; } else { // not running, not starting usleep(COMMAND_LOOP_DELAY); // make it longer but interruptible by signals? } } // while (process) // normally, we should not be here clean_up(state); return EXIT_SUCCESS; } int main(int argc, char *argv[]) { const char usage[] = "This program allows recording of the video/images acquired by Elphel camera to the storage media.\n" \ "It is designed to run in the background and accept commands through a named pipe.\n\n" \ "Usage:\n\n" \ "%s \n\n" \ "i.e.:\n\n" \ "%s /var/state/camogm_cmd\n\n" \ "When the program is runninig you may send commands by writing strings to the command file\n" \ "(/var/state/camogm_cmd in the example above). The complete list of available commands is available\n" \ "on Elphel Wiki (http://wiki.elphel.com/index.php?title=Camogm), here is the example of usage\n" \ "from the shell prompt in the camera:\n\n" \ "echo \"status; exif=1; format=jpeg;status=/var/tmp/camogm.status\" > /var/state/camogm_cmd\n\n" \ "That will print status information on the standard output (may not be visible if the program was not\n" \ "started from the same session), set exif mode on (each frame will have the full Exif header including\n" \ "a precise time stamp), set output format to a series of individual JPEG files, and then send status\n" \ "information to a file /var/tmp/camogm.status in the camera file system.\n\n" \ "This program does not control the process of acquisition of the video/images to the camera internal\n" \ "buffer, it only retrieves that data from the buffer (waiting when needed), packages it to selected\n" \ "format and stores the result files.\n\n"; char *pipe_names[SENSOR_PORTS] = {0}; int ret; // no command line options processing yet if ((argc < 2) || (argv[1][1] == '-')) { printf(usage, argv[0], argv[0]); return EXIT_SUCCESS; } if (create_pipe_names(argv[1], pipe_names) < 0) { printf("Error: unable to allocate memory for command pipe name\n"); return EXIT_FAILURE; } // spawn a process for each sensor port for (int i = 0; i < SENSOR_PORTS; i++) { camogm_init(&sstate[i], i, pipe_names[i]); if (fork() == 0) { ret = listener_loop(&sstate[i]); exit(ret); } } return EXIT_SUCCESS; } /** * @brief Read either G_* parameter (these are 'regular' values defined by number) or P_* parameter * (it can be read for up to 6 frames ahead, but current interface only allows to read last/current value) * @param[in] GPNumber parameter number (as defined in c313a.h), G_* parameters have numbers above FRAMEPAR_GLOBALS, P_* - below) * @param[in] port sensor port number for which a parameter is needed * @return parameter value */ unsigned long getGPValue(unsigned int port, unsigned long GPNumber) { #ifdef DISABLE_CODE return (GPNumber >= FRAMEPAR_GLOBALS) ? GLOBALPARS(port, GPNumber) : framePars[port][GLOBALPARS(port, G_THIS_FRAME) & PARS_FRAMES_MASK].pars[GPNumber]; #endif /* DESABLE_CODE */ return 0; } /** * @brief Set value of the specified global (G_*) parameter * @param[in] GNumber parameter number (as defined in c313a.h) * @param[in] value value to set * @return none */ void setGValue(unsigned int port, unsigned long GNumber, unsigned long value) { GLOBALPARS(port, GNumber) = value; } /** * @brief Check if this application is enabled (by appropriate bit in P_DAEMON_EN), if not - * and wait until enabled (return false when enabled) * @param[in] daemonBit bit number to accept control in P_DAEMON_EN parameter * @return (after possible waiting) true if there was no waiting, false if there was waiting */ int waitDaemonEnabled(unsigned int port, int daemonBit) // <0 - use default { camogm_state *state = &sstate[port]; if ((daemonBit >= 0) && (daemonBit < 32)) lastDaemonBit[port] = daemonBit; unsigned long this_frame = GLOBALPARS(port, G_THIS_FRAME); // No semaphors, so it is possible to miss event and wait until the streamer will be re-enabled before sending message, // but it seems not so terrible lseek(state->fd_circ, LSEEK_DAEMON_CIRCBUF + lastDaemonBit[port], SEEK_END); /// if (this_frame == GLOBALPARS(port, G_THIS_FRAME)) return 1; return 0; } /** * @brief Check if this application is enabled (by appropriate bit in P_DAEMON_EN) * @param[in] daemonBit bit number to accept control in P_DAEMON_EN parameter * @return (after possible waiting) true if there was no waiting, false if there was waiting */ int isDaemonEnabled(unsigned int port, int daemonBit) // <0 - use default { if ((daemonBit >= 0) && (daemonBit < 32)) lastDaemonBit[port] = daemonBit; return ((framePars[port][GLOBALPARS(port, G_THIS_FRAME) & PARS_FRAMES_MASK].pars[P_DAEMON_EN] & (1 << lastDaemonBit[port])) != 0); }