/*!*************************************************************************** *! FILE NAME : camogm_mov.c *! DESCRIPTION: Provides writing to file compatible with Apple Quicktime(R) for camogm *!TODO: Nothing yet here, will be added ASAP *! 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_mov.c,v $ *! 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.4 2008/04/11 23:09:33 elphel *! modified to handle kml generation *! *! Revision 1.3 2007/11/19 17:00:20 elphel *! removed wrong dependency *! *! 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:57 elphel *! Initial release of camogm - program to record video/image to the camera hard drive (or other storage) *! */ //!Not all are needed, just copied from the camogm.c #include #include #include #include #include #include #include #include #include #include //#include //#include #include #include #include /*little <-> big endian ?*/ #include /* mmap */ #include #include #include #include // has to be before ogmstreams.h #include "ogmstreams.h" // move it to <>? #include "camogm_mov.h" #define QUICKTIME_MIN_HEADER 0x300 // Quicktime header length (w/o index tables) enough to accomodate // static data . //! for the parser const char hexStr[] = "0123456789abcdef"; const char qtSourceFileName[] = "/etc/qt_source"; char comStr[1024]; int width = 1280; int height = 1024; int nframes = 100; int sample_dur = 80; int samplesPerChunk = 10; int framesize = 80000; int timescale = 600; int * sizes; // array of frame sizes int iPos; //!position in the string "iFile" //int oPos; //!position in the string "oFile" int ofd; // output file descriptor (file opened by the caller) int iFileLen; char * q_template = NULL; long headerSize = 0; const char *iFile = NULL; int quicktime_template_parser(camogm_state *state, const char * i_iFile, //! now - string containing header template int i_ofd, //!output file descriptor (opened) int i_width, // width in pixels int i_height, int i_nframes, int i_sample_dur, int i_samplesPerChunk, int i_framesize, int i_timescale, int * i_sizes, int data_start // put zero if the header is written before data (no gap) ); void putBigEndian(unsigned long d, int l); int parse_special(void); int parse(camogm_state *state, int top); //! called first time format is changed to this one (only once) recording is stopped //! read frame template from the file if it is not done yet int camogm_init_mov(void) { FILE* qt_header; int size; if ((qt_header = fopen(qtSourceFileName, "r")) == NULL) { D0(fprintf(debug_file, "Error opening Quicktime header template %s for reading\n", qtSourceFileName)); return -CAMOGM_FRAME_FILE_ERR; } fseek(qt_header, 0, SEEK_END); size = ftell(qt_header); //malloc(4*state->max_frames); if (!((q_template = malloc(size + 1)))) { D0(fprintf(debug_file, "Could not allocate %d bytes of memory for Quicktime header template\n", (size + 1))); fclose(qt_header); return -CAMOGM_FRAME_MALLOC; } fseek(qt_header, 0, SEEK_SET); //rewind if (fread(q_template, size, 1, qt_header) < 1) { D0(fprintf(debug_file, "Could not read %d bytes of Quicktime header template from %s\n", (size + 1), qtSourceFileName)); free(q_template); q_template = NULL; fclose(qt_header); return -CAMOGM_FRAME_FILE_ERR; } q_template[size] = 0; return 0; } void camogm_free_mov(void) { if (q_template) { free(q_template); q_template = NULL; } } int camogm_start_mov(camogm_state *state) { //! allocate memory for the frame index table if (!((state->frame_lengths = malloc(4 * state->max_frames)))) return -CAMOGM_FRAME_MALLOC; //! open file for writing sprintf(state->path, "%s%010ld_%06ld.mov", state->path_prefix, state->frame_params.timestamp_sec, state->frame_params.timestamp_usec); if (((state->ivf = open(state->path, O_RDWR | O_CREAT, 0777))) < 0) { D0(fprintf(debug_file, "Error opening %s for writing, returned %d, errno=%d\n", state->path, state->ivf, errno)); return -CAMOGM_FRAME_FILE_ERR; } //!skip header (plus extra) //! Quicktime (and else?) - frame data start (0xff 0xd8...) state->frame_data_start = QUICKTIME_MIN_HEADER + 16 + 4 * (state->max_frames) + ( 4 * (state->max_frames)) / (state->frames_per_chunk); // 8 bytes for "skip" tag lseek(state->ivf, state->frame_data_start, SEEK_SET); return 0; } int camogm_frame_mov(camogm_state *state) { int i, j; ssize_t iovlen, l; struct iovec chunks_iovec[7]; l = 0; for (i = 0; i < (state->chunk_index) - 1; i++) { chunks_iovec[i].iov_base = state->packetchunks[i + 1].chunk; chunks_iovec[i].iov_len = state->packetchunks[i + 1].bytes; l += chunks_iovec[i].iov_len; } iovlen = writev(state->ivf, chunks_iovec, (state->chunk_index) - 1); if (iovlen < l) { j = errno; D0(fprintf(debug_file, "writev error %d (returned %d, expected %d, file descriptor %d, chn %d)\n", j, iovlen, l, state->ivf, state->port_num)); perror(strerror(j)); close(state->ivf); state->ivf = -1; return -CAMOGM_FRAME_FILE_ERR; } state->frame_lengths[state->frameno] = l; return 0; } /** * @brief Move to the start of the file and insert generated header * @param[in] state pointer to the #camogm_state structure for current sensor port * @return this function is always successful and returns 0 */ int camogm_end_mov(camogm_state *state) { off_t l/*,he; unsigned char mdat_tag[8]; unsigned char skip_tag[]="\0\0\0\0skip"*/; timescale = 10000; //! frame period measured in 1/10000 of a second? //! that was in old code. If that works - try to switch to microseconds l = lseek(state->ivf, 0, SEEK_CUR) - (state->frame_data_start) + 8; //!4-byte length+"mdat" // lseek(state->ivf, state->frame_data_start, SEEK_SET); // fill in the header in the beginning of the file lseek(state->ivf, 0, SEEK_SET); quicktime_template_parser(state, q_template, //! now - string containing header template state->ivf, //!output file descriptor (opened) state->width, //! width in pixels state->height, state->frameno, state->frame_period / (1000000 / timescale), state->frames_per_chunk, 0, //!frame size - will look in the table (int)((float)timescale / (state->timescale)), state->frame_lengths, //! array of frame lengths to build an index state->frame_data_start ); #if 0 //! now we need to overwrite last mdat tag in the header to the skip the gap, instead of the length 'mdat //! put length 'skip length 'mdat he = lseek(state->ivf, 0, SEEK_CUR); // just after the original header end l = state->frame_data_start - he; //! should be >= D4(fprintf(debug_file, "Remaining gap between Quicktime header and the data is %d (it should be>=8) \n", (int)l)); lseek(state->ivf, he - 8, SEEK_SET); read(state->ivf, mdat_tag, 8); //!read calculated length+'mdat' tag lseek(state->ivf, state->frame_data_start - 8, SEEK_SET); write(state->ivf, mdat_tag, 8); skip_tag[0] = (l >> 24) & 0xff; skip_tag[1] = (l >> 16) & 0xff; skip_tag[2] = (l >> 8) & 0xff; skip_tag[3] = (l ) & 0xff; lseek(state->ivf, he - 8, SEEK_SET); write(state->ivf, skip_tag, 8); #endif close(state->ivf); state->ivf = -1; //! free memory used for index if (state->frame_lengths) { free(state->frame_lengths); state->frame_lengths = NULL; } return 0; } /** * @brief Starts with the input file pointer just after the opening "{", * and output file - at the beginning of it's output * on exit - input pointer - after closing "}", output after it's output * @param[in] d * @param[in] l * @return none */ void putBigEndian(unsigned long d, int l) { unsigned char od[4]; od[3] = d; od[2] = d >> 8; od[1] = d >> 16; od[0] = d >> 24; if (l) write(ofd, &od[4 - l], l); // oPos+=l; } //!temporary replacement for fgets to read from string char * sfgets(char * str, int size, const char * stream, int * pos) { int l; const char * eol = strchr(&stream[*pos], '\n'); if (!eol) eol = stream + (strlen(stream) - 1); //!pointer to last before \0 l = (eol - stream) - (*pos); // if (l >= size) eol=stream+ (*pos+size-1); if (l >= size) l = size - 1; memcpy(str, &stream[*pos], l); str[l] = '\0'; *pos += l; return str; } int parse_special(void) { time_t ltime; int n, j, l; char str[256]; char c; int i = 0; int gap; // while (((c=fgetc(infile))!=0x20) && (c!=0x09) && (c!=0x0a) && (c!=0x0d) && (c!=0x0) && (i<255) && ( feof(infile) == 0 )) str[i++]=c; while (((c = iFile[iPos++]) != 0x20) && (c != 0x09) && (c != 0x0a) && (c != 0x0d) && (c != 0x0) && (i < 255) && ( iPos < iFileLen )) str[i++] = c; str[i] = 0; D4(fprintf(debug_file, "parse_special, str=!%s\n", str)); if (strcmp(str, "mdata") == 0) { putBigEndian(headerSize, 4); return 0; } // will put zeroes on pass 1 if (strcmp(str, "height") == 0) { putBigEndian(height, 2); return 0; } if (strcmp(str, "width") == 0) { putBigEndian(width, 2); return 0; } if (strcmp(str, "width") == 0) { putBigEndian(width, 2); return 0; } if (strcmp(str, "nframes") == 0) { putBigEndian(nframes, 4); return 0; } if (strcmp(str, "timescale") == 0) { putBigEndian(timescale, 4); return 0; } if (strcmp(str, "duration") == 0) { putBigEndian(nframes * sample_dur, 4); return 0; } if (strcmp(str, "frame_duration") == 0) { putBigEndian(sample_dur, 4); return 0; } if (strcmp(str, "samples_chunk") == 0) { putBigEndian(samplesPerChunk, 4); return 0; } // will put zeroes on pass 1 if (strcmp(str, "sample_sizes") == 0) { if (sizes != NULL) for (i = 0; i < nframes; i++) putBigEndian(sizes[i], 4); else for (i = 0; i < nframes; i++) putBigEndian(framesize, 4); return 0; } if (strcmp(str, "chunk_offsets") == 0) { n = (nframes - 1) / samplesPerChunk + 1; putBigEndian(n, 4); if (sizes != NULL) { l = 0; j = 0; for (i = 0; i < nframes; i++) { if (j == 0) putBigEndian(headerSize + l, 4); j++; if (j >= samplesPerChunk) j = 0; l += sizes[i]; } } else for (i = 0; i < n; i++) putBigEndian(headerSize + framesize * samplesPerChunk * i, 4); return 0; } //! a hack - invlude length'skip if data position (header size is known and there is a gap) if (strcmp(str, "data_size") == 0) { gap = headerSize - lseek(ofd, 0, SEEK_CUR) - 8; if (gap > 0) { //!it should be exactly 0 if there is no gap or >8 if there is D4(fprintf(debug_file, "Inserting a skip tag to compensate for a gap (%d bytes) between the header and the frame data\n", gap)); if (gap < 8) { D0(fprintf(debug_file, "not enough room to insret 'skip' tag - %d (need 8)\n", gap)); return -1; } D4(fprintf(debug_file, "writing hex %x, %x bytes\n", gap, 4)); putBigEndian(gap, 4); D4(fprintf(debug_file, "writing string <%s>\n", "skip")); write(ofd, "skip", 4); lseek(ofd, gap - 8, SEEK_CUR); //! lseek over the gap and proceed as before } if (sizes != NULL) { l = 0; for (i = 0; i < nframes; i++) l += sizes[i]; D4(fprintf(debug_file, "writing hex %x, %x bytes\n", l, 4)); putBigEndian(l, 4); } else putBigEndian(nframes * framesize, 4); return 0; } if (strcmp(str, "time") == 0) { time(<ime); ltime += 2082801600; // 1970->1904// 31,557,600 seconds/year putBigEndian(ltime, 4); return 0; } return -1; } int parse(camogm_state *state, int top) // if top - will not include length { long out_start, out_end; char c; unsigned long d, l; char * cp; D4(fprintf(debug_file, "parse(%x)\n", top)); // c=fgetc(infile); c = iFile[iPos++]; D5(fprintf(debug_file, "%c", c)); // out_start=ftell (outfile); // out_start=oPos; // out_start=oPos=lseek(ofd,0,SEEK_CUR); out_start = lseek(ofd, 0, SEEK_CUR); if (!top) putBigEndian(0, 4); // while (( feof(infile) == 0 ) && (c!='}')) { while (( iPos < iFileLen ) && (c != '}')) { // skip white spaces strchr if ((c != ' ') && (c != 0x9) && (c != 0xa) && (c != 0xd)) { if (c == '!') { if (parse_special() < 0) return -1; } // children atoms else if (c == '{') { if (parse(state, 0) < 0) return -1; // skip comments // } else if (c=='#') fgets( comStr, sizeof(comStr), infile); } else if (c == '#') sfgets( comStr, sizeof(comStr), iFile, &iPos); else if (c == '\'') { // fgets ( comStr, sizeof(comStr), infile); sfgets( comStr, sizeof(comStr), iFile, &iPos); if ((cp = strchr(comStr, 0x0a)) != NULL) cp[0] = 0; if ((cp = strchr(comStr, 0x0d)) != NULL) cp[0] = 0; if ((cp = strchr(comStr, '#')) != NULL) cp[0] = 0; cp = comStr + strlen(comStr) - 1; while ((cp > comStr) && ((cp[0] == 0x20) || (cp[0] == 0x09))) cp--; cp[1] = 0; // fwrite (comStr,1, strlen(comStr),outfile); // memcpy(&oFile[oPos],comStr,strlen(comStr)); write(ofd, comStr, strlen(comStr)); D4(fprintf(debug_file, "writing string <%s>\n", comStr)); } else if (strchr(hexStr, c)) { d = 0; l = 1; do { d = (d << 4) + (strchr(hexStr, c) - hexStr); l++; } while (( iPos < iFileLen ) && (l <= 8) && (strchr(hexStr, (c = iFile[iPos++]))) ); l = (l) >> 1; putBigEndian(d, l); D4(fprintf(debug_file, "writing hex %lx, %lx bytes\n", d, l)); } else if ((c == '}')) { break; } else { return -1; } } // c=fgetc(infile); c = iFile[iPos++]; } // fread fseek ftell if (!top) { // out_end=ftell (outfile); // out_end=oPos; out_end = lseek(ofd, 0, SEEK_CUR); // fseek (outfile,out_start,SEEK_SET); // oPos=out_start; lseek(ofd, out_start, SEEK_SET); putBigEndian((out_end - out_start), 4); // fseek (outfile,out_end,SEEK_SET); // oPos=out_end; lseek(state->ivf, out_end, SEEK_SET); } return 0; } int quicktime_template_parser( camogm_state *state, const char * i_iFile, //! now - string containing header template int i_ofd, //!output file descriptor (opened) int i_width, // width in pixels int i_height, int i_nframes, int i_sample_dur, int i_samplesPerChunk, int i_framesize, int i_timescale, int * i_sizes, int data_start // zero if dfata is not written yet (will be no gap) ) { iFile = i_iFile; width = i_width; height = i_height; nframes = i_nframes; sample_dur = i_sample_dur; samplesPerChunk = i_samplesPerChunk; framesize = i_framesize; timescale = i_timescale; sizes = i_sizes; iPos = 0; //!position in the string "iFile" ofd = i_ofd; iFileLen = strlen(iFile); lseek(ofd, 0, SEEK_SET); if (data_start) headerSize = data_start; // int iFileLen=strlen(inFIle); D3(fprintf(debug_file, "PASS I\n")); // while ( feof(infile) == 0 ) parse(1); // pass 1 while ( iPos < iFileLen ) parse(state, 1); // pass 1 // headerSize=ftell (outfile); // fseek (outfile,0,SEEK_SET); // rewind for pass 2 // fseek (infile, 0,SEEK_SET); // // headerSize=oPos; if (!headerSize) headerSize = lseek(ofd, 0, SEEK_CUR); iPos = 0; // oPos=0; lseek(ofd, 0, SEEK_SET); D3(fprintf(debug_file, "PASS II\n")); // while ( feof(infile) == 0 ) parse(1); // pass 2 while ( iPos < iFileLen ) parse(state, 1); // pass 2 //fclose (infile); //fclose (outfile); // oFile[oPos]='\0'; return 0; }