diff options
author | Klaus Schmidinger <vdr@tvdr.de> | 2015-01-14 10:39:55 +0100 |
---|---|---|
committer | Klaus Schmidinger <vdr@tvdr.de> | 2015-01-14 10:39:55 +0100 |
commit | 7062583ab40bec2ee63e84adbbd8e3e1740729bf (patch) | |
tree | e7886025fc6714c5db0d88580401a439a2702639 /dvbsubtitle.c | |
parent | b454a0777f54d3cd256b042b446088a77415a434 (diff) | |
download | vdr-7062583ab40bec2ee63e84adbbd8e3e1740729bf.tar.gz vdr-7062583ab40bec2ee63e84adbbd8e3e1740729bf.tar.bz2 |
Added support for PGS subtitles
Diffstat (limited to 'dvbsubtitle.c')
-rw-r--r-- | dvbsubtitle.c | 280 |
1 files changed, 269 insertions, 11 deletions
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 <marco@lordzodiac.de> * With some input from the "subtitles plugin" by Pekka Virtanen <pekka.virtanen@sci.fi> * - * $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("<b>clut</b> id %d version %d<br>\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<br>\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("<b>object</b> 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<br>\n"); + while (DecodePgsCodeString(Bitmap, px, py, &bs, x, y) && !bs.IsEOF()) { + x = 0; + y++; + } + break; default: dbgpixel("unknown sub block %s %d<br>\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("<b>objectref</b> id %d type %d flag %d x %d y %d fg %d bg %d<br>\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("<b>objectrefPgs</b> id %d flag %d x %d y %d cropX %d cropY %d cropW %d cropH %d<br>\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("<b>region</b> id %d version %d clutId %d<br>\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("<b>region</b> id %d width %d height %d<br>\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("<b>regionref</b> id %d tx %d y %d<br>\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<br>\n", pageState); + } + bs.SkipBits(6); + dbgpages("<hr>\n<b>page</b> id %d version %d pts %"PRId64" timeout %d state %d<br>\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"<br>\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; - } + 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)<br>\n"); + FinishPage(page); + } + dbgsegments("PGS_PRESENTATION_SEGMENT<br>\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<br>\n"); + break; + } + case PGS_PALETTE_SEGMENT: { + dbgsegments("PGS_PALETTE_SEGMENT<br>\n"); + cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true); + clut->ParsePgs(bs); + break; + } + case PGS_OBJECT_SEGMENT: { + dbgsegments("PGS_OBJECT_SEGMENT<br>\n"); + cSubtitleObject *object = page->GetObjectById(bs.GetBits(16), true); + object->ParsePgs(bs); + break; + } + case PGS_DISPLAY_SEGMENT: { + dbgsegments("PGS_DISPLAY_SEGMENT<br>\n"); + FinishPage(page); + page->SetPending(false); + break; + } + default: + dbgsegments("*** unknown segment type: %02X<br>\n", segmentType); + return -1; + } + return bs.Length() / 8; + } + return -1; +} + void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page) { if (!AssertOsd()) |