From 7062583ab40bec2ee63e84adbbd8e3e1740729bf Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Wed, 14 Jan 2015 10:39:55 +0100 Subject: [PATCH] Added support for PGS subtitles --- CONTRIBUTORS | 1 + HISTORY | 1 + dvbsubtitle.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++--- dvbsubtitle.h | 3 +- remux.c | 32 +++++- 5 files changed, 305 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 3242d4a1..cb32846c 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3270,6 +3270,7 @@ Thomas Reufer for fixing a possible crash in the LCARS skin for implementing cOsd::DrawScaledBitmap() for adding handling for DTS audio tracks to cPatPmtParser::ParsePmt() + for adding support for PGS subtitles Eike Sauer for reporting a problem with channels that need more than 5 TS packets for detecting diff --git a/HISTORY b/HISTORY index edbe1f09..ed153e38 100644 --- a/HISTORY +++ b/HISTORY @@ -8360,3 +8360,4 @@ Video Disk Recorder Revision History - Now handling CAT sections that consist of more than one TS packet. - Added handling for DTS audio tracks to cPatPmtParser::ParsePmt() (thanks to Thomas Reufer). +- Added support for PGS subtitles (thanks to Thomas Reufer). diff --git a/dvbsubtitle.c b/dvbsubtitle.c index d1fefdcb..4bf613b9 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 3.7 2015/01/09 11:56:25 kls Exp $ + * $Id: dvbsubtitle.c 3.8 2015/01/14 10:30:50 kls Exp $ */ #include "dvbsubtitle.h" @@ -25,6 +25,12 @@ #define END_OF_DISPLAY_SET_SEGMENT 0x80 #define STUFFING_SEGMENT 0xFF +#define PGS_PALETTE_SEGMENT 0x14 +#define PGS_OBJECT_SEGMENT 0x15 +#define PGS_PRESENTATION_SEGMENT 0x16 +#define PGS_WINDOW_SEGMENT 0x17 +#define PGS_DISPLAY_SEGMENT 0x80 + // Set these to 'true' for debug output, which is written into the file dbg-log.htm // in the current working directory. The HTML file shows the actual bitmaps (dbg-nnn.jpg) // used to display the subtitles. @@ -146,6 +152,7 @@ private: public: cSubtitleClut(int ClutId); void Parse(cBitStream &bs); + void ParsePgs(cBitStream &bs); int ClutId(void) { return clutId; } int ClutVersionNumber(void) { return clutVersionNumber; } const cPalette *GetPalette(int Bpp); @@ -267,6 +274,31 @@ void cSubtitleClut::Parse(cBitStream &bs) } } +void cSubtitleClut::ParsePgs(cBitStream &bs) +{ + int Version = bs.GetBits(8); + if (clutVersionNumber == Version) + return; // no update + clutVersionNumber = Version; + dbgcluts("clut id %d version %d
\n", clutId, clutVersionNumber); + for (int i = 0; i < 256; ++i) + SetColor(8, i, ArgbToColor(0, 0, 0, 0)); + while (!bs.IsEOF()) { + uchar clutEntryId = bs.GetBits(8); + uchar yval = bs.GetBits(8); + uchar crval = bs.GetBits(8); + uchar cbval = bs.GetBits(8); + uchar tval = bs.GetBits(8); + tColor value = 0; + if (yval) { + value = yuv2rgb(yval, cbval, crval); + value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * tval / 10) << 24; + } + dbgcluts("%2d %08X
\n", clutEntryId, value); + SetColor(8, clutEntryId, value); + } +} + tColor cSubtitleClut::yuv2rgb(int Y, int Cb, int Cr) { int Ey, Epb, Epr; @@ -314,6 +346,7 @@ private: bool nonModifyingColorFlag; int topLength; int botLength; + int topIndex; uchar *topData; uchar *botData; char *txtData; @@ -322,12 +355,14 @@ private: bool Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable); bool Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable); bool Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y); + bool DecodePgsCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y); void DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even); void DecodeCharacterString(const uchar *Data, int NumberOfCodes); public: cSubtitleObject(int ObjectId); ~cSubtitleObject(); void Parse(cBitStream &bs); + void ParsePgs(cBitStream &bs); int ObjectId(void) { return objectId; } int ObjectVersionNumber(void) { return objectVersionNumber; } int ObjectCodingMethod(void) { return objectCodingMethod; } @@ -343,6 +378,7 @@ cSubtitleObject::cSubtitleObject(int ObjectId) nonModifyingColorFlag = false; topLength = 0; botLength = 0; + topIndex = 0; topData = NULL; botData = NULL; txtData = NULL; @@ -404,6 +440,29 @@ void cSubtitleObject::Parse(cBitStream &bs) } } +void cSubtitleObject::ParsePgs(cBitStream &bs) +{ + int Version = bs.GetBits(8); + if (objectVersionNumber == Version) + return; // no update + objectVersionNumber = Version; + objectCodingMethod = 0; + int sequenceDescriptor = bs.GetBits(8); + if (!(sequenceDescriptor & 0x80) && topData != NULL) { + memcpy(topData + topIndex, bs.GetData(), (bs.Length() - bs.Index()) / 8); + topIndex += (bs.Length() - bs.Index()) / 8; + return; + } + topLength = bs.GetBits(24) - 4 + 1; // exclude width / height, add sub block type + bs.SkipBits(32); + if ((topData = MALLOC(uchar, topLength)) != NULL) { + topData[topIndex++] = 0xFF; // PGS end of line + memcpy(topData + 1, bs.GetData(), (bs.Length() - bs.Index()) / 8); + topIndex += (bs.Length() - bs.Index()) / 8 + 1; + } + dbgobjects("object id %d version %d method %d modify %d", objectId, objectVersionNumber, objectCodingMethod, nonModifyingColorFlag); +} + void cSubtitleObject::DecodeCharacterString(const uchar *Data, int NumberOfCodes) { // "ETSI EN 300 743 V1.3.1 (2006-11)", chapter 7.2.5 "Object data segment" specifies @@ -490,6 +549,13 @@ void cSubtitleObject::DecodeSubBlock(cBitmap *Bitmap, int px, int py, const ucha x = 0; y += 2; break; + case 0xFF: + dbgpixel("PGS code string, including EOLs
\n"); + while (DecodePgsCodeString(Bitmap, px, py, &bs, x, y) && !bs.IsEOF()) { + x = 0; + y++; + } + break; default: dbgpixel("unknown sub block %s %d
\n", __FUNCTION__, __LINE__); } } @@ -613,6 +679,28 @@ bool cSubtitleObject::Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBit return true; } +bool cSubtitleObject::DecodePgsCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y) +{ + while (!bs->IsEOF()) { + int color = bs->GetBits(8); + int rl = 1; + if (!color) { + int flags = bs->GetBits(8); + rl = flags & 0x3f; + if (flags & 0x40) + rl = (rl << 8) + bs->GetBits(8); + color = flags & 0x80 ? bs->GetBits(8) : 0; + } + if (rl > 0) { + DrawLine(Bitmap, px + x, py + y, color, rl); + x += rl; + } + else if (!rl) + return true; + } + return false; +} + void cSubtitleObject::Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg) { if (objectCodingMethod == 0) { // coding of pixels @@ -660,7 +748,7 @@ cSubtitleObject *cSubtitleObjects::GetObjectById(int ObjectId, bool New) // --- cSubtitleObjectRef ---------------------------------------------------- class cSubtitleObjectRef : public cListObject { -private: +protected: int objectId; int objectType; int objectProviderFlag; @@ -669,6 +757,7 @@ private: int foregroundPixelCode; int backgroundPixelCode; public: + cSubtitleObjectRef(void); cSubtitleObjectRef(cBitStream &bs); int ObjectId(void) { return objectId; } int ObjectType(void) { return objectType; } @@ -679,6 +768,17 @@ public: int BackgroundPixelCode(void) { return backgroundPixelCode; } }; +cSubtitleObjectRef::cSubtitleObjectRef(void) +{ + objectId = 0; + objectType = 0; + objectProviderFlag = 0; + objectHorizontalPosition = 0; + objectVerticalPosition = 0; + foregroundPixelCode = 0; + backgroundPixelCode = 0; +} + cSubtitleObjectRef::cSubtitleObjectRef(cBitStream &bs) { objectId = bs.GetBits(16); @@ -698,6 +798,38 @@ cSubtitleObjectRef::cSubtitleObjectRef(cBitStream &bs) dbgregions("objectref id %d type %d flag %d x %d y %d fg %d bg %d
\n", objectId, objectType, objectProviderFlag, objectHorizontalPosition, objectVerticalPosition, foregroundPixelCode, backgroundPixelCode); } +// --- cSubtitleObjectRefPgs - PGS variant of cSubtitleObjectRef ------------- + +class cSubtitleObjectRefPgs : public cSubtitleObjectRef { +private: + int windowId; + int compositionFlag; + int cropX; + int cropY; + int cropW; + int cropH; +public: + cSubtitleObjectRefPgs(cBitStream &bs); +}; + +cSubtitleObjectRefPgs::cSubtitleObjectRefPgs(cBitStream &bs) +:cSubtitleObjectRef() +{ + objectId = bs.GetBits(16); + windowId = bs.GetBits(8); + compositionFlag = bs.GetBits(8); + bs.SkipBits(32); // skip absolute position, object is aligned to region + if ((compositionFlag & 0x80) != 0) { + cropX = bs.GetBits(16); + cropY = bs.GetBits(16); + cropW = bs.GetBits(16); + cropH = bs.GetBits(16); + } + else + cropX = cropY = cropW = cropH = 0; + dbgregions("objectrefPgs id %d flag %d x %d y %d cropX %d cropY %d cropW %d cropH %d
\n", objectId, compositionFlag, objectHorizontalPosition, objectVerticalPosition, cropX, cropY, cropW, cropH); +} + // --- cSubtitleRegion ------------------------------------------------------- class cSubtitleRegion : public cListObject { @@ -717,6 +849,8 @@ private: public: cSubtitleRegion(int RegionId); void Parse(cBitStream &bs); + void ParsePgs(cBitStream &bs); + void SetDimensions(int Width, int Height); int RegionId(void) { return regionId; } int RegionVersionNumber(void) { return regionVersionNumber; } bool RegionFillFlag(void) { return regionFillFlag; } @@ -769,6 +903,24 @@ void cSubtitleRegion::Parse(cBitStream &bs) objectRefs.Add(new cSubtitleObjectRef(bs)); } +void cSubtitleRegion::ParsePgs(cBitStream &bs) +{ + regionDepth = 8; + bs.SkipBits(8); // skip palette update flag + clutId = bs.GetBits(8); + dbgregions("region id %d version %d clutId %d
\n", regionId, regionVersionNumber, clutId); + int objects = bs.GetBits(8); + while (objects--) + objectRefs.Add(new cSubtitleObjectRefPgs(bs)); +} + +void cSubtitleRegion::SetDimensions(int Width, int Height) +{ + regionWidth = Width; + regionHeight = Height; + dbgregions("region id %d width %d height %d
\n", regionId, regionWidth, regionHeight); +} + void cSubtitleRegion::Render(cBitmap *Bitmap, cSubtitleObjects *Objects) { if (regionFillFlag) { @@ -794,12 +946,20 @@ private: int regionHorizontalAddress; int regionVerticalAddress; public: + cSubtitleRegionRef(int id, int x, int y); cSubtitleRegionRef(cBitStream &bs); int RegionId(void) { return regionId; } int RegionHorizontalAddress(void) { return regionHorizontalAddress; } int RegionVerticalAddress(void) { return regionVerticalAddress; } }; +cSubtitleRegionRef::cSubtitleRegionRef(int id, int x, int y) +{ + regionId = id; + regionHorizontalAddress = x; + regionVerticalAddress = y; + dbgpages("regionref id %d tx %d y %d
\n", regionId, regionHorizontalAddress, regionVerticalAddress); +} cSubtitleRegionRef::cSubtitleRegionRef(cBitStream &bs) { regionId = bs.GetBits(8); @@ -826,6 +986,7 @@ private: public: cDvbSubtitlePage(int PageId); void Parse(int64_t Pts, cBitStream &bs); + void ParsePgs(int64_t Pts, cBitStream &bs); int PageId(void) { return pageId; } int PageTimeout(void) { return pageTimeout; } int PageVersionNumber(void) { return pageVersionNumber; } @@ -838,6 +999,7 @@ public: cSubtitleClut *GetClutById(int ClutId, bool New = false); cSubtitleRegion *GetRegionById(int RegionId, bool New = false); cSubtitleRegionRef *GetRegionRefByIndex(int RegionRefIndex) { return regionRefs.Get(RegionRefIndex); } + void AddRegionRef(cSubtitleRegionRef *rf) { regionRefs.Add(rf); } void SetPending(bool Pending) { pending = Pending; } }; @@ -887,6 +1049,35 @@ void cDvbSubtitlePage::Parse(int64_t Pts, cBitStream &bs) pending = true; } +void cDvbSubtitlePage::ParsePgs(int64_t Pts, cBitStream &bs) +{ + if (Pts >= 0) + pts = Pts; + pageTimeout = 60000; + int Version = bs.GetBits(16); + if (pageVersionNumber == Version) + return; + pageVersionNumber = Version; + pageState = bs.GetBits(2); + switch (pageState) { + case 0: // normal case - page update + regions.Clear(); + break; + case 1: // acquisition point - page refresh + case 2: // epoch start - new page + case 3: // epoch continue - new page + regions.Clear(); + cluts.Clear(); + objects.Clear(); + break; + default: dbgpages("unknown page state: %d
\n", pageState); + } + bs.SkipBits(6); + dbgpages("
\npage id %d version %d pts %"PRId64" timeout %d state %d
\n", pageId, pageVersionNumber, pts, pageTimeout, pageState); + regionRefs.Clear(); + pending = true; +} + tArea *cDvbSubtitlePage::GetAreas(int &NumAreas, double FactorX, double FactorY) { if (regions.Count() > 0) { @@ -1232,22 +1423,24 @@ int cDvbSubtitleConverter::Convert(const uchar *Data, int Length) dbgconverter("converter PTS: %"PRId64"
\n", pts); const uchar *data = Data + PayloadOffset; int length = Length - PayloadOffset; - if (length > 3) { - if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) { + if (length > 0) { + if (length > 2 && data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) { data += 2; length -= 2; } const uchar *b = data; while (length > 0) { - if (b[0] == 0x0F) { - int n = ExtractSegment(b, length, pts); - if (n < 0) - break; - b += n; - length -= n; - } - else + if (b[0] == STUFFING_SEGMENT) break; + int n; + if (b[0] == 0x0F) + n = ExtractSegment(b, length, pts); + else + n = ExtractPgsSegment(b, length, pts); + if (n < 0) + break; + b += n; + length -= n; } } } @@ -1473,6 +1666,71 @@ int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t return -1; } +int cDvbSubtitleConverter::ExtractPgsSegment(const uchar *Data, int Length, int64_t Pts) +{ + cBitStream bs(Data, Length * 8); + if (Length >= 3) { + int segmentType = bs.GetBits(8); + int segmentLength = bs.GetBits(16); + if (!bs.SetLength(bs.Index() + segmentLength * 8)) + return -1; + LOCK_THREAD; + cDvbSubtitlePage *page = GetPageById(0, true); + switch (segmentType) { + case PGS_PRESENTATION_SEGMENT: { + if (page->Pending()) { + dbgsegments("PGS_DISPLAY_SEGMENT (simulated)
\n"); + FinishPage(page); + } + dbgsegments("PGS_PRESENTATION_SEGMENT
\n"); + displayWidth = windowWidth = bs.GetBits(16); + displayHeight = windowHeight = bs.GetBits(16); + bs.SkipBits(8); + page->ParsePgs(Pts, bs); + SD.SetFactor(double(DBGBITMAPWIDTH) / windowWidth); + cSubtitleRegion *region = page->GetRegionById(0, true); + region->ParsePgs(bs); + break; + } + case PGS_WINDOW_SEGMENT: { + bs.SkipBits(16); + int regionHorizontalAddress = bs.GetBits(16); + int regionVerticalAddress = bs.GetBits(16); + int regionWidth = bs.GetBits(16); + int regionHeight = bs.GetBits(16); + cSubtitleRegion *region = page->GetRegionById(0, true); + region->SetDimensions(regionWidth, regionHeight); + page->AddRegionRef(new cSubtitleRegionRef(0, regionHorizontalAddress, regionVerticalAddress)); + dbgsegments("PGS_WINDOW_SEGMENT
\n"); + break; + } + case PGS_PALETTE_SEGMENT: { + dbgsegments("PGS_PALETTE_SEGMENT
\n"); + cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true); + clut->ParsePgs(bs); + break; + } + case PGS_OBJECT_SEGMENT: { + dbgsegments("PGS_OBJECT_SEGMENT
\n"); + cSubtitleObject *object = page->GetObjectById(bs.GetBits(16), true); + object->ParsePgs(bs); + break; + } + case PGS_DISPLAY_SEGMENT: { + dbgsegments("PGS_DISPLAY_SEGMENT
\n"); + FinishPage(page); + page->SetPending(false); + break; + } + default: + dbgsegments("*** unknown segment type: %02X
\n", segmentType); + return -1; + } + return bs.Length() / 8; + } + return -1; +} + void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page) { if (!AssertOsd()) diff --git a/dvbsubtitle.h b/dvbsubtitle.h index 13574337..f178b61c 100644 --- a/dvbsubtitle.h +++ b/dvbsubtitle.h @@ -6,7 +6,7 @@ * * Original author: Marco Schluessler * - * $Id: dvbsubtitle.h 3.1 2013/09/06 10:53:30 kls Exp $ + * $Id: dvbsubtitle.h 3.2 2015/01/14 10:01:48 kls Exp $ */ #ifndef __DVBSUBTITLE_H @@ -43,6 +43,7 @@ private: void SetOsdData(void); bool AssertOsd(void); int ExtractSegment(const uchar *Data, int Length, int64_t Pts); + int ExtractPgsSegment(const uchar *Data, int Length, int64_t Pts); void FinishPage(cDvbSubtitlePage *Page); public: cDvbSubtitleConverter(void); diff --git a/remux.c b/remux.c index 16ae1352..23e83877 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 3.8 2015/01/14 09:28:24 kls Exp $ + * $Id: remux.c 3.9 2015/01/14 09:57:09 kls Exp $ */ #include "remux.h" @@ -853,6 +853,36 @@ void cPatPmtParser::ParsePmt(const uchar *Data, int Length) } } break; + case 0x90: // PGS subtitles for BD + { + dbgpatpmt(" subtitling"); + char lang[MAXLANGCODE1] = { 0 }; + SI::Descriptor *d; + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { + switch (d->getDescriptorTag()) { + case SI::ISO639LanguageDescriptorTag: { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; + dbgpatpmt(" '%s'", ld->languageCode); + strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); + if (NumSpids < MAXSPIDS) { + spids[NumSpids] = stream.getPid(); + *slangs[NumSpids] = 0; + subtitlingTypes[NumSpids] = 0; + compositionPageIds[NumSpids] = 0; + ancillaryPageIds[NumSpids] = 0; + if (updatePrimaryDevice) + cDevice::PrimaryDevice()->SetAvailableTrack(ttSubtitle, NumSpids, stream.getPid(), lang); + NumSpids++; + spids[NumSpids] = 0; + } + } + break; + default: ; + } + delete d; + } + } + break; default: ; } dbgpatpmt("\n");