diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 18737734..ade4c508 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -2468,6 +2468,8 @@ Christoph Haubrich for fixing handling zero bytes in cH264Parser for implementing parsing frame rate and image size for MPEG2, H.264 and H.265 for reporting a bug in generating the index file in the cutter + for adding the frame width, height, scan type and aspect ratio of a recording to the 'F' + tag of the 'info' file Pekka Mauno for fixing cSchedule::GetFollowingEvent() in case there is currently no present diff --git a/HISTORY b/HISTORY index ad13498a..d996dc4d 100644 --- a/HISTORY +++ b/HISTORY @@ -9852,7 +9852,9 @@ Video Disk Recorder Revision History mode receiver device (thanks to Markus Ehrnsperger). - Revised support for kernel based LIRC driver (thanks to Marko Mäkelä). -2023-02-21: +2023-12-28: - Fixed broken video data streams on systems without output device when switching live channel to a different transponder while recording (reported by Markus Ehrnsperger). +- The frame width, height, scan type and apect ratio of a recording are now stored in + the 'info' file under the 'F' tag (thanks to Christoph Haubrich). diff --git a/recorder.c b/recorder.c index 30832017..2ef18407 100644 --- a/recorder.c +++ b/recorder.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recorder.c 5.4 2021/06/19 14:21:16 kls Exp $ + * $Id: recorder.c 5.5 2023/12/28 21:22:29 kls Exp $ */ #include "recorder.h" @@ -326,8 +326,12 @@ void cRecorder::Action(void) break; if (frameDetector->Synced()) { if (!InfoWritten) { - if (frameDetector->FramesPerSecond() > 0 && DoubleEqual(recordingInfo->FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(recordingInfo->FramesPerSecond(), frameDetector->FramesPerSecond())) { + if ((frameDetector->FramesPerSecond() > 0 && DoubleEqual(recordingInfo->FramesPerSecond(), DEFAULTFRAMESPERSECOND) && !DoubleEqual(recordingInfo->FramesPerSecond(), frameDetector->FramesPerSecond())) || + frameDetector->FrameWidth() != recordingInfo->FrameWidth() || + frameDetector->FrameHeight() != recordingInfo->FrameHeight() || + frameDetector->AspectRatio() != recordingInfo->AspectRatio()) { recordingInfo->SetFramesPerSecond(frameDetector->FramesPerSecond()); + recordingInfo->SetFrameParams(frameDetector->FrameWidth(), frameDetector->FrameHeight(), frameDetector->ScanType(), frameDetector->AspectRatio()); recordingInfo->Write(); LOCK_RECORDINGS_WRITE; Recordings->UpdateByName(recordingName); diff --git a/recording.c b/recording.c index c7ffb1ee..1abe185d 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 5.22 2023/02/15 14:59:25 kls Exp $ + * $Id: recording.c 5.23 2023/12/28 21:22:42 kls Exp $ */ #include "recording.h" @@ -24,7 +24,6 @@ #include "i18n.h" #include "interface.h" #include "menu.h" -#include "remux.h" #include "ringbuffer.h" #include "skins.h" #include "svdrp.h" @@ -363,6 +362,10 @@ cRecordingInfo::cRecordingInfo(const cChannel *Channel, const cEvent *Event) event = ownEvent ? ownEvent : Event; aux = NULL; framesPerSecond = DEFAULTFRAMESPERSECOND; + frameWidth = 0; + frameHeight = 0; + scanType = stUnknown; + aspectRatio = arUnknown; priority = MAXPRIORITY; lifetime = MAXLIFETIME; fileName = NULL; @@ -424,6 +427,10 @@ cRecordingInfo::cRecordingInfo(const char *FileName) aux = NULL; errors = -1; framesPerSecond = DEFAULTFRAMESPERSECOND; + frameWidth = 0; + frameHeight = 0; + scanType = stUnknown; + aspectRatio = arUnknown; priority = MAXPRIORITY; lifetime = MAXLIFETIME; fileName = strdup(cString::sprintf("%s%s", FileName, INFOFILESUFFIX)); @@ -458,6 +465,14 @@ void cRecordingInfo::SetFramesPerSecond(double FramesPerSecond) framesPerSecond = FramesPerSecond; } +void cRecordingInfo::SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio) +{ + frameWidth = FrameWidth; + frameHeight = FrameHeight; + scanType = ScanType; + aspectRatio = AspectRatio; +} + void cRecordingInfo::SetFileName(const char *FileName) { bool IsPesRecording = fileName && endswith(fileName, ".vdr"); @@ -507,7 +522,35 @@ bool cRecordingInfo::Read(FILE *f) } } break; - case 'F': framesPerSecond = atod(t); + case 'F': { + char *fpsBuf = NULL; + char scanTypeCode; + char *arBuf = NULL; + int n = sscanf(t, "%m[^ ] %hu %hu %c %m[^\n]", &fpsBuf, &frameWidth, &frameHeight, &scanTypeCode, &arBuf); + if (n >= 1) { + framesPerSecond = atod(fpsBuf); + if (n >= 4) { + scanType = stUnknown; + for (int st = stUnknown + 1; st < stMax; st++) { + if (ScanTypeChars[st] == scanTypeCode) { + scanType = eScanType(st); + break; + } + } + aspectRatio = arUnknown; + if (n == 5) { + for (int ar = arUnknown + 1; ar < arMax; ar++) { + if (strcmp(arBuf, AspectRatioTexts[ar]) == 0) { + aspectRatio = eAspectRatio(ar); + break; + } + } + } + } + } + free(fpsBuf); + free(arBuf); + } break; case 'L': lifetime = atoi(t); break; @@ -536,7 +579,10 @@ bool cRecordingInfo::Write(FILE *f, const char *Prefix) const if (channelID.Valid()) fprintf(f, "%sC %s%s%s\n", Prefix, *channelID.ToString(), channelName ? " " : "", channelName ? channelName : ""); event->Dump(f, Prefix, true); - fprintf(f, "%sF %s\n", Prefix, *dtoa(framesPerSecond, "%.10g")); + if (frameWidth > 0 && frameHeight > 0) + fprintf(f, "%sF %s %s %s %c %s\n", Prefix, *dtoa(framesPerSecond, "%.10g"), *itoa(frameWidth), *itoa(frameHeight), ScanTypeChars[scanType], AspectRatioTexts[aspectRatio]); + else + fprintf(f, "%sF %s\n", Prefix, *dtoa(framesPerSecond, "%.10g")); fprintf(f, "%sP %d\n", Prefix, priority); fprintf(f, "%sL %d\n", Prefix, lifetime); fprintf(f, "%sO %d\n", Prefix, errors); @@ -2520,8 +2566,12 @@ void cIndexFileGenerator::Action(void) if (IndexFileWritten) { cRecordingInfo RecordingInfo(recordingName); if (RecordingInfo.Read()) { - if (FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) { + if ((FrameDetector.FramesPerSecond() > 0 && !DoubleEqual(RecordingInfo.FramesPerSecond(), FrameDetector.FramesPerSecond())) || + FrameDetector.FrameWidth() != RecordingInfo.FrameWidth() || + FrameDetector.FrameHeight() != RecordingInfo.FrameHeight() || + FrameDetector.AspectRatio() != RecordingInfo.AspectRatio()) { RecordingInfo.SetFramesPerSecond(FrameDetector.FramesPerSecond()); + RecordingInfo.SetFrameParams(FrameDetector.FrameWidth(), FrameDetector.FrameHeight(), FrameDetector.ScanType(), FrameDetector.AspectRatio()); RecordingInfo.Write(); LOCK_RECORDINGS_WRITE; Recordings->UpdateByName(recordingName); diff --git a/recording.h b/recording.h index 4d7d8a6c..cea7ff6e 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.h 5.5 2021/05/23 15:03:17 kls Exp $ + * $Id: recording.h 5.6 2023/12/27 09:21:29 kls Exp $ */ #ifndef __RECORDING_H @@ -17,6 +17,7 @@ #include "thread.h" #include "timers.h" #include "tools.h" +#include "remux.h" #define FOLDERDELIMCHAR '~' @@ -69,6 +70,10 @@ private: cEvent *ownEvent; char *aux; double framesPerSecond; + uint16_t frameWidth; + uint16_t frameHeight; + eScanType scanType; + eAspectRatio aspectRatio; int priority; int lifetime; char *fileName; @@ -87,7 +92,14 @@ public: const cComponents *Components(void) const { return event->Components(); } const char *Aux(void) const { return aux; } double FramesPerSecond(void) const { return framesPerSecond; } + uint16_t FrameWidth(void) const { return frameWidth; } + uint16_t FrameHeight(void) const { return frameHeight; } + eScanType ScanType(void) const { return scanType; } + char ScanTypeChar(void) const { return ScanTypeChars[scanType]; } + eAspectRatio AspectRatio(void) const { return aspectRatio; } + const char *AspectRatioText(void) const { return AspectRatioTexts[aspectRatio]; } void SetFramesPerSecond(double FramesPerSecond); + void SetFrameParams(uint16_t FrameWidth, uint16_t FrameHeight, eScanType ScanType, eAspectRatio AspectRatio); void SetFileName(const char *FileName); int Errors(void) const { return errors; } // returns -1 if undefined void SetErrors(int Errors); diff --git a/remux.c b/remux.c index 63814bb5..d32a5d3d 100644 --- a/remux.c +++ b/remux.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.c 5.5 2022/11/30 14:38:46 kls Exp $ + * $Id: remux.c 5.6 2023/12/28 21:22:47 kls Exp $ */ #include "remux.h" @@ -1177,7 +1177,8 @@ protected: uint16_t frameWidth; uint16_t frameHeight; double framesPerSecond; - bool progressive; + eScanType scanType; + eAspectRatio aspectRatio; public: cFrameParser(void); virtual ~cFrameParser() {}; @@ -1195,7 +1196,8 @@ public: uint16_t FrameWidth(void) { return frameWidth; } uint16_t FrameHeight(void) { return frameHeight; } double FramesPerSecond(void) { return framesPerSecond; } - bool Progressive(void) { return progressive; } + eScanType ScanType(void) { return scanType; } + eAspectRatio AspectRatio(void) { return aspectRatio; } }; cFrameParser::cFrameParser(void) @@ -1207,7 +1209,8 @@ cFrameParser::cFrameParser(void) frameWidth = 0; frameHeight = 0; framesPerSecond = 0.0; - progressive = false; + scanType = stUnknown; + aspectRatio = arUnknown; } // --- cAudioParser ---------------------------------------------------------- @@ -1322,17 +1325,24 @@ int cMpeg2Parser::Parse(const uchar *Data, int Length, int Pid) uchar b = tsPayload.GetByte(); // ignoring two MSB of width and height in sequence extension frameWidth |= b >> 4; // as 12 Bit = max 4095 should be sufficient for all available MPEG2 streams frameHeight = (b & 0x0F) << 8 | tsPayload.GetByte(); - b = tsPayload.GetByte(); + b = tsPayload.GetByte(); // hi: aspect ratio info, lo: frame rate code + switch (b >> 4) { + case 1: aspectRatio = ar_1_1; break; + case 2: aspectRatio = ar_4_3; break; + case 3: aspectRatio = ar_16_9; break; + case 4: aspectRatio = ar_2_21_1; break; + default: aspectRatio = arUnknown; + } uchar frame_rate_value = b & 0x0F; if (frame_rate_value > 0 && frame_rate_value <= 8) framesPerSecond = frame_rate_table[frame_rate_value]; } else if (!seenScanType && scanner == 0x000001B5) { // Extension start code if ((tsPayload.GetByte() & 0xF0) == 0x10) { // Sequence Extension - progressive = (tsPayload.GetByte() & 0x40) != 0; + scanType = (tsPayload.GetByte() & 0x40) ? stProgressive : stInterlaced; seenScanType = true; if (debug) { - cString s = cString::sprintf("MPEG2: %d x %d%c %.2f fps", frameWidth, frameHeight, progressive ? 'p' : 'i', framesPerSecond); + cString s = cString::sprintf("MPEG2: %d x %d%c %.2f fps %s", frameWidth, frameHeight, ScanTypeChars[scanType], framesPerSecond, AspectRatioTexts[aspectRatio]); dsyslog("%s", *s); dbgframes("\n%s", *s); } @@ -1553,7 +1563,7 @@ void cH264Parser::ParseSequenceParameterSet(void) uint16_t frame_Height = 16 * (1 + GetGolombUe()); // pic_height_in_map_units_minus1 frame_mbs_only_flag = GetBit(); // frame_mbs_only_flag if (frameWidth == 0) { - progressive = frame_mbs_only_flag; + scanType = frame_mbs_only_flag ? stProgressive : stInterlaced; if (!frame_mbs_only_flag) { GetBit(); // mb_adaptive_frame_field_flag frame_Height *= 2; @@ -1584,8 +1594,11 @@ void cH264Parser::ParseSequenceParameterSet(void) if (GetBit()) { // vui_parameters_present_flag if (GetBit()) { // aspect_ratio_info_present int aspect_ratio_idc = GetBits(8); // aspect_ratio_idc - if (aspect_ratio_idc == 255) - GetBits(32); + if (aspect_ratio_idc == 255) // EXTENDED_SAR + GetBits(32); // sar_width, sar_height + else if (frameHeight >= 720 && (aspect_ratio_idc == 1 || aspect_ratio_idc == 14 || aspect_ratio_idc == 15 || aspect_ratio_idc == 16)) + aspectRatio = ar_16_9; + // implement decoding of other aspect_ratio_idc values when they are required } if (GetBit()) // overscan_info_present_flag GetBit(); // overscan_approriate_flag @@ -1606,7 +1619,7 @@ void cH264Parser::ParseSequenceParameterSet(void) } } if (debug) { - cString s = cString::sprintf("H.264: %d x %d%c %.2f fps %d Bit", frameWidth, frameHeight, progressive ? 'p':'i', framesPerSecond, bitDepth); + cString s = cString::sprintf("H.264: %d x %d%c %.2f fps %d Bit %s", frameWidth, frameHeight, ScanTypeChars[scanType], framesPerSecond, bitDepth, AspectRatioTexts[aspectRatio]); dsyslog("%s", *s); dbgframes("\n%s", *s); } @@ -1737,7 +1750,7 @@ void cH265Parser::ParseSequenceParameterSet(void) GetByte(); GetByte(); bool general_progressive_source_flag = GetBit(); // general_progressive_source_flag - progressive = general_progressive_source_flag; + scanType = general_progressive_source_flag ? stProgressive : stInterlaced; GetBit(); // general_interlaced_source_flag GetBits(6); GetByte(); @@ -1880,6 +1893,9 @@ void cH265Parser::ParseSequenceParameterSet(void) int aspect_ratio_idc = GetBits(8); // aspect_ratio_idc if (aspect_ratio_idc == 255) // EXTENDED_SAR GetBits(32); // sar_width, sar_height + else if (aspect_ratio_idc == 1 || aspect_ratio_idc == 14) + aspectRatio = ar_16_9; + // implement decoding of other aspect_ratio_idc values when they are required } if (GetBit()) // overscan_info_present_flag GetBit(); // overscan_appropriate_flag @@ -1907,7 +1923,7 @@ void cH265Parser::ParseSequenceParameterSet(void) } } if (debug) { - cString s = cString::sprintf("H.265: %d x %d%c %.2f fps %d Bit", frameWidth, frameHeight, progressive ? 'p':'i', framesPerSecond, bitDepth); + cString s = cString::sprintf("H.265: %d x %d%c %.2f fps %d Bit %s", frameWidth, frameHeight, ScanTypeChars[scanType], framesPerSecond, bitDepth, AspectRatioTexts[aspectRatio]); dsyslog("%s", *s); dbgframes("\n%s", *s); } @@ -1915,6 +1931,16 @@ void cH265Parser::ParseSequenceParameterSet(void) // --- cFrameDetector -------------------------------------------------------- +const char *ScanTypeChars = "-pi"; // index is eScanType +const char *AspectRatioTexts[] = { // index is eAspectRatio + "-", + "1:1", + "4:3", + "16:9", + "2.21:1", + NULL + }; + cFrameDetector::cFrameDetector(int Pid, int Type) { parser = NULL; @@ -1924,6 +1950,10 @@ cFrameDetector::cFrameDetector(int Pid, int Type) numPtsValues = 0; numIFrames = 0; framesPerSecond = 0; + frameWidth = 0; + frameHeight = 0; + scanType = stUnknown; + aspectRatio = arUnknown; framesInPayloadUnit = framesPerPayloadUnit = 0; scanning = false; } @@ -1991,6 +2021,10 @@ int cFrameDetector::Analyze(const uchar *Data, int Length) else { if (parser->FramesPerSecond() > 0.0) { framesPerSecond = parser->FramesPerSecond(); + frameWidth = parser->FrameWidth(); + frameHeight = parser->FrameHeight(); + scanType = parser->ScanType(); + aspectRatio = parser->AspectRatio(); synced = true; parser->SetDebug(false); } diff --git a/remux.h b/remux.h index b2b1573c..e28fb40a 100644 --- a/remux.h +++ b/remux.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: remux.h 5.2 2021/12/25 14:11:39 kls Exp $ + * $Id: remux.h 5.3 2023/12/27 09:30:42 kls Exp $ */ #ifndef __REMUX_H @@ -504,6 +504,25 @@ void PesDump(const char *Name, const u_char *Data, int Length); class cFrameParser; +enum eScanType { + stUnknown = 0, + stProgressive = 1, + stInterlaced = 2, + stMax + }; + +enum eAspectRatio { + arUnknown = 0, + ar_1_1 = 1, + ar_4_3 = 2, + ar_16_9 = 3, + ar_2_21_1 = 4, + arMax + }; + +extern const char *ScanTypeChars; +extern const char *AspectRatioTexts[]; + class cFrameDetector { private: enum { MaxPtsValues = 150 }; @@ -517,6 +536,10 @@ private: int numIFrames; bool isVideo; double framesPerSecond; + uint16_t frameWidth; + uint16_t frameHeight; + eScanType scanType; + eAspectRatio aspectRatio; int framesInPayloadUnit; int framesPerPayloadUnit; // Some broadcasters send one frame per payload unit (== 1), // while others put an entire GOP into one payload unit (> 1). @@ -547,6 +570,14 @@ public: double FramesPerSecond(void) { return framesPerSecond; } ///< Returns the number of frames per second, or 0 if this information is not ///< available. + uint16_t FrameWidth(void) { return frameWidth; } + ///< Returns the frame width, or 0 if this information is not available. + uint16_t FrameHeight(void) { return frameHeight; } + ///< Returns the frame height, or 0 if this information is not available. + eScanType ScanType(void) { return scanType; } + ///< Returns the scan type, or stUnknown if this information is not available. + eAspectRatio AspectRatio(void) { return aspectRatio; } + ///< Returns the aspect ratio, or arUnknown if this information is not available. }; #endif // __REMUX_H diff --git a/vdr.5 b/vdr.5 index 142c87c3..5f056cd1 100644 --- a/vdr.5 +++ b/vdr.5 @@ -8,7 +8,7 @@ .\" License as specified in the file COPYING that comes with the .\" vdr distribution. .\" -.\" $Id: vdr.5 5.8 2022/12/26 13:24:09 kls Exp $ +.\" $Id: vdr.5 5.9 2023/12/25 20:53:04 kls Exp $ .\" .TH vdr 5 "27 Dec 2021" "2.6" "Video Disk Recorder Files" .SH NAME @@ -814,7 +814,7 @@ characters are defined: .TS tab (|); l l. -\fBF\fR| +\fBF\fR| \fBL\fR| \fBP\fR| \fBO\fR|