From e66e19329d73132e393a941a4459d9b9ef1b85dc Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Fri, 28 Mar 2025 21:55:03 +0100 Subject: [PATCH] Improved subtitle handling --- HISTORY | 6 +++- dvbsubtitle.c | 85 +++++++++++++++++++++++++++++++++------------------ dvbsubtitle.h | 4 ++- 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/HISTORY b/HISTORY index 3d43da2a..ff09beb2 100644 --- a/HISTORY +++ b/HISTORY @@ -10090,7 +10090,7 @@ Video Disk Recorder Revision History - Added missing locks to SetMenuItem() functions. - Revised locking in cMenuSchedule and cMenuWhatsOn. -2025-03-04: +2025-03-28: - Added the "override" keyword to virtual functions reimplemented in derived classes. Plugins may want to do the same, but don't have to. @@ -10104,3 +10104,7 @@ Video Disk Recorder Revision History by Markus Ehrnsperger). - Making absolutely sure cEvent::Title() never returns NULL. APIVERSNUM is now 30007. +- Improved subtitle handling: + + Subtitles now disappear as signaled in the data stream. + + Subtitles are now kept in memory for at least 120 seconds, to have them readily + available after a short rewind during replay. diff --git a/dvbsubtitle.c b/dvbsubtitle.c index d4407628..49de924c 100644 --- a/dvbsubtitle.c +++ b/dvbsubtitle.c @@ -7,7 +7,7 @@ * Original author: Marco Schluessler * With some input from the "subtitles plugin" by Pekka Virtanen * - * $Id: dvbsubtitle.c 5.3 2025/03/02 11:03:35 kls Exp $ + * $Id: dvbsubtitle.c 5.4 2025/03/28 21:55:03 kls Exp $ */ #include "dvbsubtitle.h" @@ -1351,6 +1351,8 @@ void cDvbSubtitleBitmaps::DbgDump(int WindowWidth, int WindowHeight) // --- cDvbSubtitleConverter ------------------------------------------------- +#define SUBTITLE_RETENTION 120 // seconds + int cDvbSubtitleConverter::setupLevel = 0; cDvbSubtitleConverter::cDvbSubtitleConverter(void) @@ -1364,8 +1366,12 @@ cDvbSubtitleConverter::cDvbSubtitleConverter(void) displayHeight = windowHeight = 576; windowHorizontalOffset = 0; windowVerticalOffset = 0; + osdDeltaX = osdDeltaY = 0; + osdFactorX = osdFactorY = 1.0; + retention = SUBTITLE_RETENTION; pages = new cList; bitmaps = new cList; + current = NULL; SD.Reset(); Start(); } @@ -1390,7 +1396,7 @@ void cDvbSubtitleConverter::Reset(void) dvbSubtitleAssembler->Reset(); Lock(); pages->Clear(); - bitmaps->Clear(); + current = NULL; DELETENULL(osd); frozen = false; ddsVersionNumber = -1; @@ -1485,52 +1491,60 @@ int cDvbSubtitleConverter::Convert(const uchar *Data, int Length) #define LimitTo32Bit(n) ((n) & 0x00000000FFFFFFFFL) +static int PtsDeltaMs(int64_t a, int64_t b) +{ + return (LimitTo32Bit(a) - LimitTo32Bit(b)) / 90; // some devices only deliver 32 bits, PTS are in 1/90000s +} + void cDvbSubtitleConverter::Action(void) { int LastSetupLevel = setupLevel; - cTimeMs Timeout; while (Running()) { int WaitMs = 100; if (!frozen) { LOCK_THREAD; if (osd) { int NewSetupLevel = setupLevel; - if (Timeout.TimedOut() || LastSetupLevel != NewSetupLevel) { + if (LastSetupLevel != NewSetupLevel) { dbgoutput("closing osd
\n"); DELETENULL(osd); } LastSetupLevel = NewSetupLevel; } - for (cDvbSubtitleBitmaps *sb = bitmaps->First(); sb; sb = bitmaps->Next(sb)) { + int64_t STC = cDevice::PrimaryDevice()->GetSTC(); + if (osd && current && current->Timeout() * 1000 - PtsDeltaMs(STC, current->Pts()) <= 0) + DELETENULL(osd); + for (cDvbSubtitleBitmaps *sb = bitmaps->First(); sb; ) { // Calculate the Delta between the STC (the current timestamp of the video) // and the bitmap's PTS (the timestamp when the bitmap shall be presented). // A negative Delta means that the bitmap will be presented in the future: - int64_t STC = cDevice::PrimaryDevice()->GetSTC(); - int64_t Delta = LimitTo32Bit(STC) - LimitTo32Bit(sb->Pts()); // some devices only deliver 32 bits - if (Delta > (int64_t(1) << 31)) - Delta -= (int64_t(1) << 32); - else if (Delta < -((int64_t(1) << 31) - 1)) - Delta += (int64_t(1) << 32); - Delta /= 90; // STC and PTS are in 1/90000s - if (Delta >= 0) { // found a bitmap that shall be displayed... - if (Delta < sb->Timeout() * 1000) { // ...and has not timed out yet - if (!sb->HasBitmaps()) { - Timeout.Set(); - WaitMs = 0; - } + cDvbSubtitleBitmaps *This = NULL; + int Delta = PtsDeltaMs(STC, sb->Pts()); + if (Delta > 0 && sb == bitmaps->Last()) + Delta = 0; // avoids sticky subtitles at longer pauses + if (Delta <= 0) { // found the bitmap that shall be displayed next + if (Delta < 0) + This = bitmaps->Prev(sb); + else if (Delta == 0) + This = sb; + if (This && This != current) { + current = This; + if (!current->HasBitmaps() || current->Timeout() * 1000 - PtsDeltaMs(STC, current->Pts()) <= 0) + DELETENULL(osd); else if (AssertOsd()) { - dbgoutput("showing bitmap #%d of %d
\n", sb->Index() + 1, bitmaps->Count()); - sb->Draw(osd); - Timeout.Set(sb->Timeout() * 1000); - dbgconverter("PTS: %" PRId64 " STC: %" PRId64 " (%" PRId64 ") timeout: %d
\n", sb->Pts(), STC, Delta, sb->Timeout()); + dbgoutput("showing bitmap #%d of %d
\n", current->Index() + 1, bitmaps->Count()); + current->Draw(osd); + dbgconverter("PTS: %" PRId64 " STC: %" PRId64 " (%" PRId64 ") timeout: %d
\n", current->Pts(), STC, Delta, current->Timeout()); } } - else - WaitMs = 0; // bitmap already timed out, so try next one immediately - dbgoutput("deleting bitmap #%d of %d
\n", sb->Index() + 1, bitmaps->Count()); - bitmaps->Del(sb); break; } + cDvbSubtitleBitmaps *sbOld = sb; + sb = bitmaps->Next(sb); + if (sbOld != current) { + if (Delta > (sbOld->Timeout() + retention) * 1000) + bitmaps->Del(sbOld); + } } } cCondWait::SleepMs(WaitMs); @@ -1770,13 +1784,21 @@ void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page) return; int NumAreas; tArea *Areas = Page->GetAreas(NumAreas); - if (!Areas) - return; tArea AreaCombined = Page->CombineAreas(NumAreas, Areas); + cDvbSubtitleBitmaps *After = NULL; + for (cDvbSubtitleBitmaps *sb = bitmaps->Last(); sb; sb = bitmaps->Prev(sb)) { + int64_t Delta = PtsDeltaMs(sb->Pts(), Page->Pts()); + if (Delta == 0) + return; // we already have this one + if (Delta < 0) { + After = sb; + break; + } + } tArea AreaOsd = Page->ScaleArea(AreaCombined, osdFactorX, osdFactorY); int Bpp = 8; bool Reduced = false; - if (osd) { + if (NumAreas) { while (osd->CanHandleAreas(&AreaOsd, 1) != oeOk) { dbgoutput("CanHandleAreas: %d
\n", osd->CanHandleAreas(&AreaOsd, 1)); int HalfBpp = Bpp / 2; @@ -1792,7 +1814,10 @@ void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page) } } cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->PageState(), Page->Pts(), Page->PageTimeout(), Areas, NumAreas, osdFactorX, osdFactorY, AreaCombined, AreaOsd); - bitmaps->Add(Bitmaps); + if (After) + bitmaps->Add(Bitmaps, After); + else + bitmaps->Ins(Bitmaps); for (int i = 0; i < NumAreas; i++) { if (cSubtitleRegionRef *srr = Page->GetRegionRefByIndex(i)) { if (cSubtitleRegion *sr = Page->GetRegionById(srr->RegionId())) { diff --git a/dvbsubtitle.h b/dvbsubtitle.h index 0ff62e75..97362c41 100644 --- a/dvbsubtitle.h +++ b/dvbsubtitle.h @@ -6,7 +6,7 @@ * * Original author: Marco Schluessler * - * $Id: dvbsubtitle.h 5.1 2025/03/02 11:03:35 kls Exp $ + * $Id: dvbsubtitle.h 5.2 2025/03/28 21:55:03 kls Exp $ */ #ifndef __DVBSUBTITLE_H @@ -37,8 +37,10 @@ private: int osdDeltaY; double osdFactorX; double osdFactorY; + int retention; cList *pages; cList *bitmaps; + cDvbSubtitleBitmaps *current; cDvbSubtitlePage *GetPageById(int PageId, bool New = false); void SetOsdData(void); bool AssertOsd(void);