/* Video file format class according the specification of the "SER format description version 3" found at https://free-astro.org/images/5/51/SER_Doc_V3b.pdf */ #include #include #include #include #include #include #include #include "ser.h" #include "videodev.h" /* * Convert v4l2 pixelformat to the corresponding ser format. * Also return depth per channel. */ int ser_convert_type_to_ser (int v4l2_fmt, int *pixeldepth, int *serformat) { if (pixeldepth == NULL || serformat == NULL) return 0; switch (v4l2_fmt) { case (V4L2_PIX_FMT_RGB24): *serformat = SER_COLORID_RGB; *pixeldepth = 8; break; case (V4L2_PIX_FMT_BGR24): *serformat = SER_COLORID_BGR; *pixeldepth = 8; break; default: return 0; } return 1; } SER::SER() { /* clear header */ memset(&header, 0, sizeof(header)); /* fill some initial data */ memcpy(header.FileID, "LUCAM-RECORDER", strlen("LUCAM-RECORDER")); header.LuID = 0; header.ColorID = SER_COLORID_RGB; header.LittleEndian = 0; // opposite meaning of the specification header.ImageWidth = 0; header.ImageHeight = 0; header.PixelDepthPerPlane = 8; header.FrameCount = 0; header.DateTime = 0; header.DateTimeUTC = 0; FileDesc = NULL; Frame = NULL; FramePointer = 0; FrameSize = 0; NumberOfPlanes = 0; BytesPerPixel = 0; Reading = 1; NumberOfTimeStamps = 0; TimeStamps = NULL; } /* Class destructor. Free resources. */ SER::~SER() { if(FileDesc) { /* Update header if necessary */ if(!Reading) { int err; int64_t offset = (int64_t)&header.FrameCount - (int64_t)&header; header.FrameCount = FramePointer; /* goto file beginning */ if((err = fseek(FileDesc, offset, SEEK_SET)) == -1) { fprintf(stderr, "Error: failed seek SER file FrameCount (%d, %s)\n", err, strerror(errno)); } else { /* write framecount in header data */ if((err = fwrite(&header.FrameCount, sizeof(header.FrameCount), 1, FileDesc)) != 1) { fprintf(stderr, "Error: failed writing SER file FrameCount (%d, %s)\n", err, strerror(errno)); } } /* goto file end */ if((err = fseek(FileDesc, 0, SEEK_END)) == -1) { fprintf(stderr, "Error: failed seek SER file end (%d, %s)\n", err, strerror(errno)); } else { /* write timestamp data */ if((err = fwrite(TimeStamps, sizeof(header.DateTime), FramePointer, FileDesc)) != FramePointer) { fprintf(stderr, "Error: failed writing SER file TimeStamps (%d, %s)\n", err, strerror(errno)); } } } /* Close file if necessary */ fclose(FileDesc); } /* Free timestamps */ if(TimeStamps) { free(TimeStamps); } /* Free frame memory if necessary */ if(Frame) { free(Frame); } } /* Sets the name of the observer. E.g. "Steffen Pohle" 40 ASCII characters {32...126 dec.}, fill unused characters with 0 dec. */ int SER::setObserver(char *name) { strncpy(header.Observer, name, sizeof(header.Observer)); return 0; } /* Sets the name of the used camera. E.g. "Svbony SV305" 40 ASCII characters {32...126 dec.}, fill unused characters with 0 dec. */ int SER::setInstrument(char *name) { strncpy(header.Instrument, name, sizeof(header.Instrument)); return 0; } /* Sets the name of the used telescope. E.g. "Sky-Watcher EVOSTAR 102" 40 ASCII characters {32...126 dec.}, fill unused characters with 0 dec. */ int SER::setTelescope(char *name) { strncpy(header.Telescope, name, sizeof(header.Telescope)); return 0; } /* Set the file name for input/output operations. If file exists, we open it for reading, otherwise we try to create the file. */ int SER::setFile(char *name) { FILE *file; /* try opening file for reading */ if((file = fopen(name, "rb")) == NULL) { /* file does probably not exist */ if((file = fopen(name, "wb")) == NULL) { /* failed to create file */ fprintf(stderr, "Error: failed to create file '%s' (%s)\n", name, strerror(errno)); return -1; } else { fprintf(stdout, "Debug: created file '%s' for writing SER file\n", name); FileDesc = file; Reading = 0; return 0; } } fprintf(stdout, "Debug: opened file '%s' for reading SER file\n", name); FileDesc = file; Reading = 1; return 0; } /* The function writes the file header of the SER file. */ int SER::writeHeader(void) { int err; /* adjust time stamps for header */ setDateTime(); if(FileDesc == NULL) { fprintf(stderr, "Error: writing header, SER file not yet specified\n"); return -1; } /* goto file beginning */ if((err = fseek(FileDesc, 0, SEEK_SET)) == -1) { fprintf(stderr, "Error: failed seek SER file beginning (%d, %s)\n", err, strerror(errno)); return -1; } /* write header data */ if((err = fwrite(&header, sizeof(header), 1, FileDesc)) != 1) { fprintf(stderr, "Error: failed writing SER header (%d, %s)\n", err, strerror(errno)); return -1; } /* reset space for timestamps */ NumberOfTimeStamps = 16; if(TimeStamps) { free(TimeStamps); } /* allocate some memory for the upcoming timestamps */ if((TimeStamps = (int64_t *)malloc(sizeof(header.DateTime) * NumberOfTimeStamps)) == NULL) { fprintf(stderr, "Error: failed to allocate %llu bytes for timestamps\n", (long long unsigned int) sizeof(header.DateTime) * (long)NumberOfTimeStamps); return -1; } FramePointer = 0; return 0; } /* The function reads the file header of the SER file. */ int SER::readHeader(void) { int err; if(FileDesc == NULL) { fprintf(stderr, "Error: reading header, SER file not yet specified\n"); return -1; } /* goto file beginning */ if((err = fseek(FileDesc, 0, SEEK_SET)) == -1) { fprintf(stderr, "Error: failed seek SER file beginning (%d, %s)\n", err, strerror(errno)); return -1; } /* read header data */ if((err = fread(&header, sizeof(header), 1, FileDesc)) != 1) { fprintf(stderr, "Error: failed reading SER header (%d, %s)\n", err, strerror(errno)); return -1; } FramePointer = 0; return 0; } /* The function apppends a single frame at the current file position. Frame size is determined by internal header data. */ int SER::appendFrame(void *data) { if(FileDesc == NULL) { fprintf(stderr, "Error: appending frame, SER file not yet specified\n"); return -1; } /* write frame data */ if(fwrite(data, FrameSize, 1, FileDesc) != 1) { fprintf(stderr, "Error: failed write SER frame (%s)\n", strerror(errno)); return -1; } /* ensure we have enough memory allocated for timestamps */ if(FramePointer >= NumberOfTimeStamps) { while(FramePointer >= NumberOfTimeStamps) NumberOfTimeStamps *= 2; if((TimeStamps = (int64_t *)realloc(TimeStamps, sizeof(header.DateTime) * NumberOfTimeStamps)) == NULL) { fprintf(stderr, "Error: failed to re-allocate %llu bytes for timestamps\n", (long long unsigned int) sizeof(header.DateTime) * (long)NumberOfTimeStamps); return -1; } } /* save timestamp for this frame */ TimeStamps[FramePointer++] = currentDateTimeUTC(); return 0; } /* Updates the internal data according changes in the header. */ void SER::updateHeaderData(void) { NumberOfPlanes = header.ColorID < SER_COLORID_RGB ? 1 : 3; BytesPerPixel = NumberOfPlanes * (header.PixelDepthPerPlane <= 8 ? 1 : 2); FrameSize = header.ImageWidth * header.ImageHeight * BytesPerPixel; } /* Sets the pixel depth in bits. */ int SER::setPixelDepth(int pixeldepth) { header.PixelDepthPerPlane = pixeldepth; updateHeaderData(); return 0; } /* Returns the pixel depth in bits. */ int SER::getPixelDepth(void) { return header.PixelDepthPerPlane; } /* Returns the number of bytes in an allocated frame. */ size_t SER::getFrameSize(void) { updateHeaderData(); return FrameSize; } /* Sets the image width. */ int SER::setWidth(int width) { header.ImageWidth = width; updateHeaderData(); return 0; } /* Gets the image width. */ int SER::getWidth(void) { return header.ImageWidth; } /* Sets the image height. */ int SER::setHeight(int height) { header.ImageHeight = height; updateHeaderData(); return 0; } /* Gets the image height. */ int SER::getHeight(void) { return header.ImageHeight; } /* Sets the color ID. */ int SER::setColorID(int id) { header.ColorID = id; updateHeaderData(); return 0; } /* Allocates memory for a single frame. Reading/writing could be done on this memory block. */ void * SER::allocFrame(void) { /* Drop old frame data. */ if(Frame) { free(Frame); Frame = NULL; } /* Try allocating new frame data. */ if((Frame = malloc(FrameSize)) == NULL) { fprintf(stderr, "Error: failed to allocate %lu bytes for frame\n", (long)FrameSize); return NULL; } return Frame; } /* The function reads a single frame at the current file position. Frame size is determined by internal header data. */ int SER::readFrame(void *data) { int err; /* read frame data */ if((err = fread(data, FrameSize, 1, FileDesc)) != 1) { fprintf(stderr, "Error: failed reading SER frame (%d, %s)\n", err, strerror(errno)); return -1; } /* goto next frame */ FramePointer++; /* reached end of file? */ if(FramePointer >= getNumberOfFrames()) { /* goto first frame beginning */ if((err = fseek(FileDesc, sizeof(header), SEEK_SET)) == -1) { fprintf(stderr, "Error: failed seeking SER file frame #%d (%d, %s)\n", 0, err, strerror(errno)); return -1; } /* start over at beginning */ FramePointer = 0; } return 0; } /* The function reads a single frame at the given file position. Frame size is determined by internal header data. */ int SER::readFrame(void *data, int frame) { int err; /* goto frame beginning */ if((err = fseek(FileDesc, sizeof(header) + frame * FrameSize, SEEK_SET)) == -1) { fprintf(stderr, "Error: failed seeking SER file frame #%d (%d, %s)\n", frame, err, strerror(errno)); return -1; } /* read frame data */ if((err = fread(data, FrameSize, 1, FileDesc)) != 1) { fprintf(stderr, "Error: failed reading SER frame (%d, %s)\n", err, strerror(errno)); return -1; } FramePointer = frame + 1; return 0; } /* Sets the number of frames in the header. */ void SER::setNumberOfFrames(int32_t frames) { header.FrameCount = frames; } /* Returns the number of frames in the header. */ int32_t SER::getNumberOfFrames(void) { return header.FrameCount; } /* Sets the current timestamps in the header. */ int64_t SER::setDateTime(void) { int64_t hundred_nano_seconds = currentDateTimeUTC(); header.DateTimeUTC = hundred_nano_seconds; header.DateTime = hundred_nano_seconds + differenceLocalUTC(); return header.DateTime; } /* Timestamps in SER ----------------- Holds IEEE 64-bit (8-byte) values that represent dates ranging from January 1 of the year 0001 through December 31 of the year 9999, and times from 12:00:00 AM (midnight) through 11:59:59.9999999 PM. Each increment represents 100 nanoseconds of elapsed time since the beginning of January 1 of the year 1 in the Gregorian calendar. The maximum value represents 100 nanoseconds before the beginning of January 1 of the year 10000. */ int64_t SER::currentDateTimeUTC(void) { int64_t hundred_nano_seconds; struct timeval current_time; /* get time since 01.01.1970 00:00 */ gettimeofday (¤t_time, NULL); hundred_nano_seconds = (62135596800L + current_time.tv_sec) * 10000000L + current_time.tv_usec * 10L; return hundred_nano_seconds; } /* Determines the difference between local time and UTC time. */ int64_t SER::differenceLocalUTC(void) { time_t abs_ts, loc_ts, gmt_ts; struct tm loc_time_info, gmt_time_info; /* Absolute time stamp.*/ time (&abs_ts); /* Now get once the local time for this time stamp, and once the GMT (UTC without summer time) time stamp.*/ localtime_r (&abs_ts, &loc_time_info); gmtime_r (&abs_ts, &gmt_time_info); /* Convert them back.*/ loc_ts = mktime (&loc_time_info); gmt_ts = mktime (&gmt_time_info); /* Unfortunately, GMT still has summer time. Get rid of it:*/ if (gmt_time_info.tm_isdst == 1) gmt_ts -= 3600; fprintf(stdout, "Debug: difference between local time and UTC is %lld hours\n", (long long int)(loc_ts - gmt_ts) / 3600); return ((int64_t)loc_ts - (int64_t)gmt_ts) * 10000000L; }