/* * dvbsubtitle.c: DVB subtitles * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * * Original author: Marco Schluessler * With some input from the "subtitles plugin" by Pekka Virtanen * * $Id: dvbsubtitle.c 5.2 2022/12/06 16:57:01 kls Exp $ */ #include "dvbsubtitle.h" #define __STDC_FORMAT_MACROS // Required for format specifiers #include #include "device.h" #include "libsi/si.h" #define PAGE_COMPOSITION_SEGMENT 0x10 #define REGION_COMPOSITION_SEGMENT 0x11 #define CLUT_DEFINITION_SEGMENT 0x12 #define OBJECT_DATA_SEGMENT 0x13 #define DISPLAY_DEFINITION_SEGMENT 0x14 #define DISPARITY_SIGNALING_SEGMENT 0x15 // DVB BlueBook A156 #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. static bool DebugNormal = false; // shows pages, regions and objects static bool DebugVerbose = false; // shows everything static bool DebugDisplay = DebugVerbose || DebugNormal; static bool DebugPages = DebugVerbose || DebugNormal; static bool DebugRegions = DebugVerbose || DebugNormal; static bool DebugObjects = DebugVerbose || DebugNormal; static bool DebugConverter = DebugVerbose; static bool DebugSegments = DebugVerbose; static bool DebugPixel = DebugVerbose; static bool DebugCluts = DebugVerbose; static bool DebugOutput = DebugVerbose; #define dbgdisplay(a...) if (DebugDisplay) SD.WriteHtml(a) #define dbgpages(a...) if (DebugPages) SD.WriteHtml(a) #define dbgregions(a...) if (DebugRegions) SD.WriteHtml(a) #define dbgobjects(a...) if (DebugObjects) SD.WriteHtml(a) #define dbgconverter(a...) if (DebugConverter) SD.WriteHtml(a) #define dbgsegments(a...) if (DebugSegments) SD.WriteHtml(a) #define dbgpixel(a...) if (DebugPixel) SD.WriteHtml(a) #define dbgcluts(a...) if (DebugCluts) SD.WriteHtml(a) #define dbgoutput(a...) if (DebugOutput) SD.WriteHtml(a) #define DBGMAXBITMAPS 100 // debug output will be stopped after this many bitmaps #define DBGBITMAPWIDTH 400 #define FIX_SUBTITLE_VERSION_BROADCASTER_STUPIDITY // some don't properly handle version numbers, which renders them useless because subtitles are not displayed // --- cSubtitleDebug -------------------------------------------------------- class cSubtitleDebug { private: cMutex mutex; int imgCnt; int64_t firstPts; bool newFile; double factor; public: cSubtitleDebug(void) { Reset(); } void Reset(void); bool Active(void) { return imgCnt < DBGMAXBITMAPS; } int64_t FirstPts(void) { return firstPts; } void SetFirstPts(int64_t FirstPts) { if (firstPts < 0) firstPts = FirstPts; } void SetFactor(double Factor) { factor = Factor; } cString WriteJpeg(const cBitmap *Bitmap, int MaxX = 0, int MaxY = 0); void WriteHtml(const char *Format, ...); }; void cSubtitleDebug::Reset(void) { imgCnt = 0; firstPts = -1; newFile = true; factor = 1.0; } cString cSubtitleDebug::WriteJpeg(const cBitmap *Bitmap, int MaxX, int MaxY) { if (!Active()) return NULL; cMutexLock MutexLock(&mutex); cBitmap *Scaled = Bitmap->Scaled(factor, factor, true); int w = MaxX ? int(round(MaxX * factor)) : Scaled->Width(); int h = MaxY ? int(round(MaxY * factor)) : Scaled->Height(); uchar mem[w * h * 3]; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { tColor c = Scaled->GetColor(x, y); int o = (y * w + x) * 3; mem[o++] = (c & 0x00FF0000) >> 16; mem[o++] = (c & 0x0000FF00) >> 8; mem[o] = (c & 0x000000FF); } } delete Scaled; int Size = 0; uchar *Jpeg = RgbToJpeg(mem, w, h, Size); cString ImgName = cString::sprintf("dbg-%03d.jpg", imgCnt++); int f = open(ImgName, O_WRONLY | O_CREAT, DEFFILEMODE); if (f >= 0) { if (write(f, Jpeg, Size) < 0) LOG_ERROR_STR(*ImgName); close(f); } free(Jpeg); return ImgName; } void cSubtitleDebug::WriteHtml(const char *Format, ...) { if (!Active()) return; cMutexLock MutexLock(&mutex); if (FILE *f = fopen("dbg-log.htm", newFile ? "w" : "a")) { va_list ap; va_start(ap, Format); vfprintf(f, Format, ap); va_end(ap); fclose(f); newFile = false; } } static cSubtitleDebug SD; // --- cSubtitleClut --------------------------------------------------------- class cSubtitleClut : public cListObject { private: int clutId; int clutVersionNumber; cPalette palette2; cPalette palette4; cPalette palette8; tColor yuv2rgb(int Y, int Cb, int Cr); void SetColor(int Bpp, int Index, tColor Color); 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); }; cSubtitleClut::cSubtitleClut(int ClutId) :palette2(2) ,palette4(4) ,palette8(8) { int a = 0, r = 0, g = 0, b = 0; clutId = ClutId; clutVersionNumber = -1; // ETSI EN 300 743 10.3: 4-entry CLUT default contents palette2.SetColor(0, ArgbToColor( 0, 0, 0, 0)); palette2.SetColor(1, ArgbToColor(255, 255, 255, 255)); palette2.SetColor(2, ArgbToColor(255, 0, 0, 0)); palette2.SetColor(3, ArgbToColor(255, 127, 127, 127)); // ETSI EN 300 743 10.2: 16-entry CLUT default contents palette4.SetColor(0, ArgbToColor(0, 0, 0, 0)); for (int i = 1; i < 16; ++i) { if (i < 8) { r = (i & 1) ? 255 : 0; g = (i & 2) ? 255 : 0; b = (i & 4) ? 255 : 0; } else { r = (i & 1) ? 127 : 0; g = (i & 2) ? 127 : 0; b = (i & 4) ? 127 : 0; } palette4.SetColor(i, ArgbToColor(255, r, g, b)); } // ETSI EN 300 743 10.1: 256-entry CLUT default contents palette8.SetColor(0, ArgbToColor(0, 0, 0, 0)); for (int i = 1; i < 256; ++i) { if (i < 8) { r = (i & 1) ? 255 : 0; g = (i & 2) ? 255 : 0; b = (i & 4) ? 255 : 0; a = 63; } else { switch (i & 0x88) { case 0x00: r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0); g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0); b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0); a = 255; break; case 0x08: r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0); g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0); b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0); a = 127; break; case 0x80: r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0); g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0); b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0); a = 255; break; case 0x88: r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0); g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0); b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0); a = 255; break; } } palette8.SetColor(i, ArgbToColor(a, r, g, b)); } } void cSubtitleClut::Parse(cBitStream &bs) { int Version = bs.GetBits(4); #ifndef FIX_SUBTITLE_VERSION_BROADCASTER_STUPIDITY if (clutVersionNumber == Version) return; // no update #endif clutVersionNumber = Version; bs.SkipBits(4); // reserved dbgcluts("clut id %d version %d
\n", clutId, clutVersionNumber); while (!bs.IsEOF()) { uchar clutEntryId = bs.GetBits(8); bool entryClut2Flag = bs.GetBit(); bool entryClut4Flag = bs.GetBit(); bool entryClut8Flag = bs.GetBit(); bs.SkipBits(4); // reserved uchar yval; uchar crval; uchar cbval; uchar tval; if (bs.GetBit()) { // full_range_flag yval = bs.GetBits(8); crval = bs.GetBits(8); cbval = bs.GetBits(8); tval = bs.GetBits(8); } else { yval = bs.GetBits(6) << 2; crval = bs.GetBits(4) << 4; cbval = bs.GetBits(4) << 4; tval = bs.GetBits(2) << 6; } tColor value = 0; if (yval) { value = yuv2rgb(yval, cbval, crval); value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24; } dbgcluts("%2d %d %d %d %08X
\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value); if (entryClut2Flag) SetColor(2, clutEntryId, value); if (entryClut4Flag) SetColor(4, clutEntryId, value); if (entryClut8Flag) SetColor(8, clutEntryId, value); } } 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; int Eg, Eb, Er; Ey = (Y - 16); Epb = (Cb - 128); Epr = (Cr - 128); /* ITU-R 709 */ Er = constrain((298 * Ey + 460 * Epr) / 256, 0, 255); Eg = constrain((298 * Ey - 55 * Epb - 137 * Epr) / 256, 0, 255); Eb = constrain((298 * Ey + 543 * Epb ) / 256, 0, 255); return (Er << 16) | (Eg << 8) | Eb; } void cSubtitleClut::SetColor(int Bpp, int Index, tColor Color) { switch (Bpp) { case 2: palette2.SetColor(Index, Color); break; case 4: palette4.SetColor(Index, Color); break; case 8: palette8.SetColor(Index, Color); break; default: esyslog("ERROR: wrong Bpp in cSubtitleClut::SetColor(%d, %d, %08X)", Bpp, Index, Color); } } const cPalette *cSubtitleClut::GetPalette(int Bpp) { switch (Bpp) { case 2: return &palette2; case 4: return &palette4; case 8: return &palette8; default: esyslog("ERROR: wrong Bpp in cSubtitleClut::GetPalette(%d)", Bpp); } return &palette8; } // --- cSubtitleObject ------------------------------------------------------- class cSubtitleObject : public cListObject { private: int objectId; int objectVersionNumber; int objectCodingMethod; bool nonModifyingColorFlag; int topLength; int botLength; int topIndex; uchar *topData; uchar *botData; char *txtData; int lineHeight; void DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length); 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; } bool NonModifyingColorFlag(void) { return nonModifyingColorFlag; } void Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg); }; cSubtitleObject::cSubtitleObject(int ObjectId) { objectId = ObjectId; objectVersionNumber = -1; objectCodingMethod = -1; nonModifyingColorFlag = false; topLength = 0; botLength = 0; topIndex = 0; topData = NULL; botData = NULL; txtData = NULL; lineHeight = 26; // configurable subtitling font size? } cSubtitleObject::~cSubtitleObject() { free(topData); free(botData); free(txtData); } void cSubtitleObject::Parse(cBitStream &bs) { int Version = bs.GetBits(4); #ifndef FIX_SUBTITLE_VERSION_BROADCASTER_STUPIDITY if (objectVersionNumber == Version) return; // no update #endif objectVersionNumber = Version; objectCodingMethod = bs.GetBits(2); nonModifyingColorFlag = bs.GetBit(); bs.SkipBit(); // reserved dbgobjects("object id %d version %d method %d modify %d", objectId, objectVersionNumber, objectCodingMethod, nonModifyingColorFlag); // no "
\n" here, DecodeCharacterString() may add data if (objectCodingMethod == 0) { // coding of pixels topLength = bs.GetBits(16); botLength = bs.GetBits(16); free(topData); if ((topData = MALLOC(uchar, topLength)) != NULL) memcpy(topData, bs.GetData(), topLength); else topLength = 0; free(botData); if ((botData = MALLOC(uchar, botLength)) != NULL) memcpy(botData, bs.GetData() + topLength, botLength); else botLength = 0; bs.WordAlign(); } else if (objectCodingMethod == 1) { // coded as a string of characters int numberOfCodes = bs.GetBits(8); DecodeCharacterString(bs.GetData(), numberOfCodes); } dbgobjects("
\n"); if (DebugObjects) { // We can't get the actual clut here, so we use a default one. This may lead to // funny colors, but we just want to get a rough idea of what's in the object, anyway. cSubtitleClut Clut(0); cBitmap b(1920, 1080, 8); b.Replace(*Clut.GetPalette(b.Bpp())); b.Clean(); Render(&b, 0, 0, 0, 1); int x1, y1, x2, y2; if (b.Dirty(x1, y1, x2, y2)) { cString ImgName = SD.WriteJpeg(&b, x2, y2); dbgobjects("
\n", *ImgName); } } } 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 // character_code to be a 16-bit index number into the character table identified // in the subtitle_descriptor. However, the "subtitling_descriptor" according to // "ETSI EN 300 468 V1.13.1 (2012-04)" doesn't contain a "character table identifier". // It only contains a three letter language code, without any specification as to how // this is related to a specific character table. // Apparently the first "code" in textual subtitles contains the character table // identifier, and all codes are 8-bit only. So let's first make Data a string of // 8-bit characters: if (NumberOfCodes > 0) { char txt[NumberOfCodes + 1]; for (int i = 0; i < NumberOfCodes; i++) txt[i] = Data[i * 2 + 1]; txt[NumberOfCodes] = 0; const uchar *from = (uchar *)txt; int len = NumberOfCodes; const char *CharacterTable = SI::getCharacterTable(from, len); dbgobjects(" table %s raw '%s'", CharacterTable, from); cCharSetConv conv(CharacterTable, cCharSetConv::SystemCharacterTable()); const char *s = conv.Convert((const char *)from); dbgobjects(" conv '%s'", s); free(txtData); txtData = strdup(s); } } void cSubtitleObject::DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even) { int x = 0; int y = Even ? 0 : 1; uint8_t map2to4[ 4] = { 0x00, 0x07, 0x08, 0x0F }; uint8_t map2to8[ 4] = { 0x00, 0x77, 0x88, 0xFF }; uint8_t map4to8[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; const uint8_t *mapTable = NULL; cBitStream bs(Data, Length * 8); while (!bs.IsEOF()) { switch (bs.GetBits(8)) { case 0x10: dbgpixel("2-bit / pixel code string
\n"); switch (Bitmap->Bpp()) { case 8: mapTable = map2to8; break; case 4: mapTable = map2to4; break; default: mapTable = NULL; break; } while (Decode2BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF()) ; bs.ByteAlign(); break; case 0x11: dbgpixel("4-bit / pixel code string
\n"); switch (Bitmap->Bpp()) { case 8: mapTable = map4to8; break; default: mapTable = NULL; break; } while (Decode4BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF()) ; bs.ByteAlign(); break; case 0x12: dbgpixel("8-bit / pixel code string
\n"); while (Decode8BppCodeString(Bitmap, px, py, &bs, x, y) && !bs.IsEOF()) ; break; case 0x20: dbgpixel("sub block 2 to 4 map
\n"); for (int i = 0; i < 4; ++i) map2to4[i] = bs.GetBits(4); break; case 0x21: dbgpixel("sub block 2 to 8 map
\n"); for (int i = 0; i < 4; ++i) map2to8[i] = bs.GetBits(8); break; case 0x22: dbgpixel("sub block 4 to 8 map
\n"); for (int i = 0; i < 16; ++i) map4to8[i] = bs.GetBits(8); break; case 0xF0: dbgpixel("end of object line
\n"); 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__); } } } void cSubtitleObject::DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length) { if (nonModifyingColorFlag && Index == 1) return; for (int pos = x; pos < x + Length; pos++) Bitmap->SetIndex(pos, y, Index); } bool cSubtitleObject::Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable) { int rl = 0; int color = 0; uchar code = bs->GetBits(2); if (code) { color = code; rl = 1; } else if (bs->GetBit()) { // switch_1 rl = bs->GetBits(3) + 3; color = bs->GetBits(2); } else if (bs->GetBit()) // switch_2 rl = 1; //color 0 else { switch (bs->GetBits(2)) { // switch_3 case 0: return false; case 1: rl = 2; //color 0 break; case 2: rl = bs->GetBits(4) + 12; color = bs->GetBits(2); break; case 3: rl = bs->GetBits(8) + 29; color = bs->GetBits(2); break; default: ; } } if (MapTable) color = MapTable[color]; DrawLine(Bitmap, px + x, py + y, color, rl); x += rl; return true; } bool cSubtitleObject::Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable) { int rl = 0; int color = 0; uchar code = bs->GetBits(4); if (code) { color = code; rl = 1; } else if (bs->GetBit() == 0) { // switch_1 code = bs->GetBits(3); if (code) rl = code + 2; //color 0 else return false; } else if (bs->GetBit() == 0) { // switch_2 rl = bs->GetBits(2) + 4; color = bs->GetBits(4); } else { switch (bs->GetBits(2)) { // switch_3 case 0: // color 0 rl = 1; break; case 1: // color 0 rl = 2; break; case 2: rl = bs->GetBits(4) + 9; color = bs->GetBits(4); break; case 3: rl = bs->GetBits(8) + 25; color = bs->GetBits(4); break; } } if (MapTable) color = MapTable[color]; DrawLine(Bitmap, px + x, py + y, color, rl); x += rl; return true; } bool cSubtitleObject::Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y) { int rl = 0; int color = 0; uchar code = bs->GetBits(8); if (code) { color = code; rl = 1; } else if (bs->GetBit()) { rl = bs->GetBits(7); color = bs->GetBits(8); } else { code = bs->GetBits(7); if (code) rl = code; // color 0 else return false; } DrawLine(Bitmap, px + x, py + y, color, rl); x += rl; 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 DecodeSubBlock(Bitmap, px, py, topData, topLength, true); if (botLength) DecodeSubBlock(Bitmap, px, py, botData, botLength, false); else DecodeSubBlock(Bitmap, px, py, topData, topLength, false); } else if (objectCodingMethod == 1) { // coded as a string of characters if (txtData) { //TODO couldn't we draw the text directly into Bitmap? cFont *font = cFont::CreateFont(Setup.FontOsd, Setup.FontOsdSize); cBitmap tmp(font->Width(txtData), font->Height(), Bitmap->Bpp()); double factor = (double)lineHeight / font->Height(); tmp.DrawText(0, 0, txtData, Bitmap->Color(IndexFg), Bitmap->Color(IndexBg), font); cBitmap *scaled = tmp.Scaled(factor, factor, true); Bitmap->DrawBitmap(px, py, *scaled); delete scaled; delete font; } } } // --- cSubtitleObjects ------------------------------------------------------ class cSubtitleObjects : public cList { public: cSubtitleObject *GetObjectById(int ObjectId, bool New = false); }; cSubtitleObject *cSubtitleObjects::GetObjectById(int ObjectId, bool New) { for (cSubtitleObject *so = First(); so; so = Next(so)) { if (so->ObjectId() == ObjectId) return so; } if (!New) return NULL; cSubtitleObject *Object = new cSubtitleObject(ObjectId); Add(Object); return Object; } // --- cSubtitleObjectRef ---------------------------------------------------- class cSubtitleObjectRef : public cListObject { protected: int objectId; int objectType; int objectProviderFlag; int objectHorizontalPosition; int objectVerticalPosition; int foregroundPixelCode; int backgroundPixelCode; public: cSubtitleObjectRef(void); cSubtitleObjectRef(cBitStream &bs); int ObjectId(void) { return objectId; } int ObjectType(void) { return objectType; } int ObjectProviderFlag(void) { return objectProviderFlag; } int ObjectHorizontalPosition(void) { return objectHorizontalPosition; } int ObjectVerticalPosition(void) { return objectVerticalPosition; } int ForegroundPixelCode(void) { return foregroundPixelCode; } 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); objectType = bs.GetBits(2); objectProviderFlag = bs.GetBits(2); objectHorizontalPosition = bs.GetBits(12); bs.SkipBits(4); // reserved objectVerticalPosition = bs.GetBits(12); if (objectType == 0x01 || objectType == 0x02) { foregroundPixelCode = bs.GetBits(8); backgroundPixelCode = bs.GetBits(8); } else { foregroundPixelCode = 0; backgroundPixelCode = 0; } 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 { private: int regionId; int regionVersionNumber; bool regionFillFlag; int regionWidth; int regionHeight; int regionLevelOfCompatibility; int regionDepth; int clutId; int region8bitPixelCode; int region4bitPixelCode; int region2bitPixelCode; cList objectRefs; 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; } int RegionWidth(void) { return regionWidth; } int RegionHeight(void) { return regionHeight; } int RegionLevelOfCompatibility(void) { return regionLevelOfCompatibility; } int RegionDepth(void) { return regionDepth; } int ClutId(void) { return clutId; } void Render(cBitmap *Bitmap, cSubtitleObjects *Objects); }; cSubtitleRegion::cSubtitleRegion(int RegionId) { regionId = RegionId; regionVersionNumber = -1; regionFillFlag = false; regionWidth = 0; regionHeight = 0; regionLevelOfCompatibility = 0; regionDepth = 0; clutId = -1; region8bitPixelCode = 0; region4bitPixelCode = 0; region2bitPixelCode = 0; } void cSubtitleRegion::Parse(cBitStream &bs) { int Version = bs.GetBits(4); #ifndef FIX_SUBTITLE_VERSION_BROADCASTER_STUPIDITY if (regionVersionNumber == Version) return; // no update #endif regionVersionNumber = Version; regionFillFlag = bs.GetBit(); bs.SkipBits(3); // reserved regionWidth = bs.GetBits(16); regionHeight = bs.GetBits(16); regionLevelOfCompatibility = 1 << bs.GetBits(3); // stored as "number of bits per pixel" regionDepth = 1 << bs.GetBits(3); // stored as "number of bits per pixel" bs.SkipBits(2); // reserved clutId = bs.GetBits(8); region8bitPixelCode = bs.GetBits(8); region4bitPixelCode = bs.GetBits(4); region2bitPixelCode = bs.GetBits(2); bs.SkipBits(2); // reserved dbgregions("region id %d version %d fill %d width %d height %d level %d depth %d clutId %d
\n", regionId, regionVersionNumber, regionFillFlag, regionWidth, regionHeight, regionLevelOfCompatibility, regionDepth, clutId); // no objectRefs.Clear() here! while (!bs.IsEOF()) 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) { switch (Bitmap->Bpp()) { case 2: Bitmap->Fill(region2bitPixelCode); break; case 4: Bitmap->Fill(region4bitPixelCode); break; case 8: Bitmap->Fill(region8bitPixelCode); break; default: dbgregions("unknown bpp %d (%s %d)
\n", Bitmap->Bpp(), __FUNCTION__, __LINE__); } } for (cSubtitleObjectRef *sor = objectRefs.First(); sor; sor = objectRefs.Next(sor)) { if (cSubtitleObject *so = Objects->GetObjectById(sor->ObjectId())) { so->Render(Bitmap, sor->ObjectHorizontalPosition(), sor->ObjectVerticalPosition(), sor->ForegroundPixelCode(), sor->BackgroundPixelCode()); } } } // --- cSubtitleRegionRef ---------------------------------------------------- class cSubtitleRegionRef : public cListObject { private: int regionId; 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); bs.SkipBits(8); // reserved regionHorizontalAddress = bs.GetBits(16); regionVerticalAddress = bs.GetBits(16); dbgpages("regionref id %d tx %d y %d
\n", regionId, regionHorizontalAddress, regionVerticalAddress); } // --- cDvbSubtitlePage ------------------------------------------------------ class cDvbSubtitlePage : public cListObject { private: int pageId; int pageTimeout; int pageVersionNumber; int pageState; int64_t pts; bool pending; cSubtitleObjects objects; cList cluts; cList regions; cList regionRefs; 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; } int PageState(void) { return pageState; } int64_t Pts(void) const { return pts; } bool Pending(void) { return pending; } cSubtitleObjects *Objects(void) { return &objects; } tArea *GetAreas(int &NumAreas); tArea CombineAreas(int NumAreas, const tArea *Areas); tArea ScaleArea(const tArea &Area, double FactorX, double FactorY); cSubtitleObject *GetObjectById(int ObjectId, bool New = false); 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; } }; cDvbSubtitlePage::cDvbSubtitlePage(int PageId) { pageId = PageId; pageTimeout = 0; pageVersionNumber = -1; pageState = -1; pts = -1; pending = false; } void cDvbSubtitlePage::Parse(int64_t Pts, cBitStream &bs) { if (Pts >= 0) pts = Pts; pageTimeout = bs.GetBits(8); int Version = bs.GetBits(4); #ifndef FIX_SUBTITLE_VERSION_BROADCASTER_STUPIDITY if (pageVersionNumber == Version) return; // no update #endif pageVersionNumber = Version; pageState = bs.GetBits(2); switch (pageState) { case 0: // normal case - page update break; case 1: // acquisition point - page refresh regions.Clear(); objects.Clear(); break; case 2: // mode change - new page regions.Clear(); cluts.Clear(); objects.Clear(); break; case 3: // reserved break; default: dbgpages("unknown page state: %d
\n", pageState); } bs.SkipBits(2); // reserved dbgpages("
\npage id %d version %d pts %" PRId64 " timeout %d state %d
\n", pageId, pageVersionNumber, pts, pageTimeout, pageState); regionRefs.Clear(); while (!bs.IsEOF()) regionRefs.Add(new cSubtitleRegionRef(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) { if (regions.Count() > 0) { NumAreas = regionRefs.Count(); tArea *Areas = new tArea[NumAreas]; tArea *a = Areas; for (cSubtitleRegionRef *srr = regionRefs.First(); srr; srr = regionRefs.Next(srr)) { if (cSubtitleRegion *sr = GetRegionById(srr->RegionId())) { a->x1 = srr->RegionHorizontalAddress(); a->y1 = srr->RegionVerticalAddress(); a->x2 = srr->RegionHorizontalAddress() + sr->RegionWidth() - 1; a->y2 = srr->RegionVerticalAddress() + sr->RegionHeight() - 1; a->bpp = sr->RegionDepth(); } else a->x1 = a->y1 = a->x2 = a->y2 = a->bpp = 0; a++; } return Areas; } NumAreas = 0; return NULL; } tArea cDvbSubtitlePage::CombineAreas(int NumAreas, const tArea *Areas) { tArea a; a.x1 = INT_MAX; a.x2 = INT_MIN; a.y1 = INT_MAX; a.y2 = INT_MIN; a.bpp = 1; for (int i = 0; i < NumAreas; i++) { a.x1 = min(a.x1, Areas[i].x1); a.x2 = max(a.x2, Areas[i].x2); a.y1 = min(a.y1, Areas[i].y1); a.y2 = max(a.y2, Areas[i].y2); a.bpp = max(a.bpp, Areas[i].bpp); } return a; } tArea cDvbSubtitlePage::ScaleArea(const tArea &Area, double FactorX, double FactorY) { tArea a; a.x1 = int(round(FactorX * Area.x1) ); a.x2 = int(round(FactorX * Area.x2) - 1); a.y1 = int(round(FactorY * Area.y1) ); a.y2 = int(round(FactorY * Area.y2) - 1); a.bpp = Area.bpp; while ((a.Width() & 3) != 0) a.x2++; // aligns width to a multiple of 4, so 2, 4 and 8 bpp will work return a; } cSubtitleClut *cDvbSubtitlePage::GetClutById(int ClutId, bool New) { for (cSubtitleClut *sc = cluts.First(); sc; sc = cluts.Next(sc)) { if (sc->ClutId() == ClutId) return sc; } if (!New) return NULL; cSubtitleClut *Clut = new cSubtitleClut(ClutId); cluts.Add(Clut); return Clut; } cSubtitleRegion *cDvbSubtitlePage::GetRegionById(int RegionId, bool New) { for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) { if (sr->RegionId() == RegionId) return sr; } if (!New) return NULL; cSubtitleRegion *Region = new cSubtitleRegion(RegionId); regions.Add(Region); return Region; } cSubtitleObject *cDvbSubtitlePage::GetObjectById(int ObjectId, bool New) { return objects.GetObjectById(ObjectId, New); } // --- cDvbSubtitleAssembler ------------------------------------------------- class cDvbSubtitleAssembler { private: uchar *data; int length; int pos; int size; bool Realloc(int Size); public: cDvbSubtitleAssembler(void); virtual ~cDvbSubtitleAssembler(); void Reset(void); unsigned char *Get(int &Length); void Put(const uchar *Data, int Length); }; cDvbSubtitleAssembler::cDvbSubtitleAssembler(void) { data = NULL; size = 0; Reset(); } cDvbSubtitleAssembler::~cDvbSubtitleAssembler() { free(data); } void cDvbSubtitleAssembler::Reset(void) { length = 0; pos = 0; } bool cDvbSubtitleAssembler::Realloc(int Size) { if (Size > size) { Size = max(Size, 2048); if (uchar *NewBuffer = (uchar *)realloc(data, Size)) { size = Size; data = NewBuffer; } else { esyslog("ERROR: can't allocate memory for subtitle assembler"); length = 0; size = 0; free(data); data = NULL; return false; } } return true; } unsigned char *cDvbSubtitleAssembler::Get(int &Length) { if (length > pos + 5) { Length = (data[pos + 4] << 8) + data[pos + 5] + 6; if (length >= pos + Length) { unsigned char *result = data + pos; pos += Length; return result; } } return NULL; } void cDvbSubtitleAssembler::Put(const uchar *Data, int Length) { if (Length && Realloc(length + Length)) { memcpy(data + length, Data, Length); length += Length; } } // --- cDvbSubtitleBitmaps --------------------------------------------------- class cDvbSubtitleBitmaps : public cListObject { private: int state; int64_t pts; int timeout; tArea *areas; int numAreas; tArea areaCombined; tArea areaOsd; double osdFactorX; double osdFactorY; cVector bitmaps; public: cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY, tArea &AreaCombined, tArea &AreaOsd); ~cDvbSubtitleBitmaps(); int State(void) { return state; } int64_t Pts(void) { return pts; } int Timeout(void) { return timeout; } void AddBitmap(cBitmap *Bitmap); bool HasBitmaps(void) { return bitmaps.Size(); } void Draw(cOsd *Osd); void DbgDump(int WindowWidth, int WindowHeight); }; cDvbSubtitleBitmaps::cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY, tArea &AreaCombined, tArea &AreaOsd) { state = State; pts = Pts; timeout = Timeout; areas = Areas; numAreas = NumAreas; areaCombined = AreaCombined; areaOsd = AreaOsd; osdFactorX = OsdFactorX; osdFactorY = OsdFactorY; } cDvbSubtitleBitmaps::~cDvbSubtitleBitmaps() { delete[] areas; for (int i = 0; i < bitmaps.Size(); i++) delete bitmaps[i]; } void cDvbSubtitleBitmaps::AddBitmap(cBitmap *Bitmap) { bitmaps.Append(Bitmap); } void cDvbSubtitleBitmaps::Draw(cOsd *Osd) { bool Scale = !(DoubleEqual(osdFactorX, 1.0) && DoubleEqual(osdFactorY, 1.0)); bool AntiAlias = Setup.AntiAlias; if (Scale && osdFactorX > 1.0 || osdFactorY > 1.0) { // Upscaling requires 8bpp: int Bpp = areaOsd.bpp; areaOsd.bpp = 8; if (Osd->CanHandleAreas(&areaOsd, 1) != oeOk) { areaOsd.bpp = Bpp; AntiAlias = false; } } if (State() == 0 || Osd->SetAreas(&areaOsd, 1) == oeOk) { cBitmap combined(areaCombined.Width(), areaCombined.Height(), areaCombined.bpp); combined.SetOffset(areaCombined.x1, areaCombined.y1); for (int i = 0; i < bitmaps.Size(); i++) { // merge bitmaps into combined cBitmap *b = bitmaps[i]; combined.DrawBitmap(b->X0(), b->Y0(), *b); } Osd->DrawScaledBitmap(int(round(combined.X0() * osdFactorX)), int(round(combined.Y0() * osdFactorY)), combined, osdFactorX, osdFactorY, AntiAlias); Osd->Flush(); } } void cDvbSubtitleBitmaps::DbgDump(int WindowWidth, int WindowHeight) { if (!SD.Active()) return; SD.SetFirstPts(Pts()); double STC = double(cDevice::PrimaryDevice()->GetSTC() - SD.FirstPts()) / 90000; double Start = double(Pts() - SD.FirstPts()) / 90000; double Duration = Timeout(); double End = Start + Duration; cBitmap Bitmap(WindowWidth, WindowHeight, 8); #define DBGBACKGROUND 0xA0A0A0 Bitmap.DrawRectangle(0, 0, WindowWidth - 1, WindowHeight - 1, DBGBACKGROUND); for (int i = 0; i < bitmaps.Size(); i++) { cBitmap *b = bitmaps[i]; Bitmap.DrawBitmap(b->X0(), b->Y0(), *b); } cString ImgName = SD.WriteJpeg(&Bitmap); #define BORDER //" border=1" SD.WriteHtml("

%s
", State() == 0 ? "page update" : State() == 1 ? "page refresh" : State() == 2 ? "new page" : "???"); SD.WriteHtml(""); SD.WriteHtml("%.2f", STC); SD.WriteHtml(""); SD.WriteHtml("", *ImgName); SD.WriteHtml(""); SD.WriteHtml("%.2f", Start); SD.WriteHtml("%.2f", Duration); SD.WriteHtml("%.2f", End); SD.WriteHtml(""); SD.WriteHtml("\n"); } // --- cDvbSubtitleConverter ------------------------------------------------- int cDvbSubtitleConverter::setupLevel = 0; cDvbSubtitleConverter::cDvbSubtitleConverter(void) :cThread("subtitle converter") { dvbSubtitleAssembler = new cDvbSubtitleAssembler; osd = NULL; frozen = false; ddsVersionNumber = -1; displayWidth = windowWidth = 720; displayHeight = windowHeight = 576; windowHorizontalOffset = 0; windowVerticalOffset = 0; pages = new cList; bitmaps = new cList; SD.Reset(); Start(); } cDvbSubtitleConverter::~cDvbSubtitleConverter() { Cancel(3); delete dvbSubtitleAssembler; delete osd; delete bitmaps; delete pages; } void cDvbSubtitleConverter::SetupChanged(void) { setupLevel++; } void cDvbSubtitleConverter::Reset(void) { dbgconverter("converter reset -----------------------
\n"); dvbSubtitleAssembler->Reset(); Lock(); pages->Clear(); bitmaps->Clear(); DELETENULL(osd); frozen = false; ddsVersionNumber = -1; displayWidth = windowWidth = 720; displayHeight = windowHeight = 576; windowHorizontalOffset = 0; windowVerticalOffset = 0; Unlock(); } int cDvbSubtitleConverter::ConvertFragments(const uchar *Data, int Length) { if (Data && Length > 8) { int PayloadOffset = PesPayloadOffset(Data); int SubstreamHeaderLength = 4; bool ResetSubtitleAssembler = Data[PayloadOffset + 3] == 0x00; // Compatibility mode for old subtitles plugin: if ((Data[7] & 0x01) && (Data[PayloadOffset - 3] & 0x81) == 0x01 && Data[PayloadOffset - 2] == 0x81) { PayloadOffset--; SubstreamHeaderLength = 1; ResetSubtitleAssembler = Data[8] >= 5; } if (Length > PayloadOffset + SubstreamHeaderLength) { int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1; if (pts >= 0) dbgconverter("converter PTS: %" PRId64 "
\n", pts); const uchar *data = Data + PayloadOffset + SubstreamHeaderLength; // skip substream header int length = Length - PayloadOffset - SubstreamHeaderLength; // skip substream header if (ResetSubtitleAssembler) dvbSubtitleAssembler->Reset(); if (length > 3) { if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) dvbSubtitleAssembler->Put(data + 2, length - 2); else dvbSubtitleAssembler->Put(data, length); int Count; while (true) { unsigned char *b = dvbSubtitleAssembler->Get(Count); if (b && b[0] == 0x0F) { if (ExtractSegment(b, Count, pts) == -1) break; } else break; } } } return Length; } return 0; } int cDvbSubtitleConverter::Convert(const uchar *Data, int Length) { if (Data && Length > 8) { int PayloadOffset = PesPayloadOffset(Data); if (Length > PayloadOffset) { int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1; if (pts >= 0) dbgconverter("converter PTS: %" PRId64 "
\n", pts); const uchar *data = Data + PayloadOffset; int length = Length - PayloadOffset; 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] == 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; } } } return Length; } return 0; } #define LimitTo32Bit(n) ((n) & 0x00000000FFFFFFFFL) 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) { dbgoutput("closing osd
\n"); DELETENULL(osd); } LastSetupLevel = NewSetupLevel; } for (cDvbSubtitleBitmaps *sb = bitmaps->First(); sb; sb = bitmaps->Next(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; } 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()); } } 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; } } } cCondWait::SleepMs(WaitMs); } } void cDvbSubtitleConverter::SetOsdData(void) { int OsdWidth, OsdHeight; double OsdAspect; int VideoWidth, VideoHeight; double VideoAspect; cDevice::PrimaryDevice()->GetOsdSize(OsdWidth, OsdHeight, OsdAspect); cDevice::PrimaryDevice()->GetVideoSize(VideoWidth, VideoHeight, VideoAspect); if (OsdWidth == displayWidth && OsdHeight == displayHeight) { osdFactorX = osdFactorY = 1.0; osdDeltaX = osdDeltaY = 0; } else { osdFactorX = osdFactorY = min(double(OsdWidth) / displayWidth, double(OsdHeight) / displayHeight); osdDeltaX = (OsdWidth - displayWidth * osdFactorX) / 2; osdDeltaY = (OsdHeight - displayHeight * osdFactorY) / 2; } } bool cDvbSubtitleConverter::AssertOsd(void) { LOCK_THREAD; if (!osd) { SetOsdData(); osd = cOsdProvider::NewOsd(int(round(osdFactorX * windowHorizontalOffset + osdDeltaX)), int(round(osdFactorY * windowVerticalOffset + osdDeltaY)) + Setup.SubtitleOffset, OSD_LEVEL_SUBTITLES); } return osd != NULL; } cDvbSubtitlePage *cDvbSubtitleConverter::GetPageById(int PageId, bool New) { for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) { if (sp->PageId() == PageId) return sp; } if (!New) return NULL; cDvbSubtitlePage *Page = new cDvbSubtitlePage(PageId); pages->Add(Page); return Page; } int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t Pts) { cBitStream bs(Data, Length * 8); if (Length > 5 && bs.GetBits(8) == 0x0F) { // sync byte int segmentType = bs.GetBits(8); if (segmentType == STUFFING_SEGMENT) return -1; LOCK_THREAD; cDvbSubtitlePage *page = GetPageById(bs.GetBits(16), true); int segmentLength = bs.GetBits(16); if (!bs.SetLength(bs.Index() + segmentLength * 8)) return -1; switch (segmentType) { case PAGE_COMPOSITION_SEGMENT: { if (page->Pending()) { dbgsegments("END_OF_DISPLAY_SET_SEGMENT (simulated)
\n"); FinishPage(page); } dbgsegments("PAGE_COMPOSITION_SEGMENT
\n"); page->Parse(Pts, bs); SD.SetFactor(double(DBGBITMAPWIDTH) / windowWidth); break; } case REGION_COMPOSITION_SEGMENT: { dbgsegments("REGION_COMPOSITION_SEGMENT
\n"); cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8), true); region->Parse(bs); break; } case CLUT_DEFINITION_SEGMENT: { dbgsegments("CLUT_DEFINITION_SEGMENT
\n"); cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true); clut->Parse(bs); break; } case OBJECT_DATA_SEGMENT: { dbgsegments("OBJECT_DATA_SEGMENT
\n"); cSubtitleObject *object = page->GetObjectById(bs.GetBits(16), true); object->Parse(bs); break; } case DISPLAY_DEFINITION_SEGMENT: { dbgsegments("DISPLAY_DEFINITION_SEGMENT
\n"); int version = bs.GetBits(4); #ifndef FIX_SUBTITLE_VERSION_BROADCASTER_STUPIDITY if (version == ddsVersionNumber) break; // no update #endif bool displayWindowFlag = bs.GetBit(); windowHorizontalOffset = 0; windowVerticalOffset = 0; bs.SkipBits(3); // reserved displayWidth = windowWidth = bs.GetBits(16) + 1; displayHeight = windowHeight = bs.GetBits(16) + 1; if (displayWindowFlag) { windowHorizontalOffset = bs.GetBits(16); // displayWindowHorizontalPositionMinimum windowWidth = bs.GetBits(16) - windowHorizontalOffset + 1; // displayWindowHorizontalPositionMaximum windowVerticalOffset = bs.GetBits(16); // displayWindowVerticalPositionMinimum windowHeight = bs.GetBits(16) - windowVerticalOffset + 1; // displayWindowVerticalPositionMaximum } SetOsdData(); ddsVersionNumber = version; dbgdisplay("display version %d flag %d width %d height %d ofshor %d ofsver %d
\n", ddsVersionNumber, displayWindowFlag, windowWidth, windowHeight, windowHorizontalOffset, windowVerticalOffset); break; } case DISPARITY_SIGNALING_SEGMENT: { dbgsegments("DISPARITY_SIGNALING_SEGMENT
\n"); bs.SkipBits(4); // dss_version_number bool disparity_shift_update_sequence_page_flag = bs.GetBit(); bs.SkipBits(3); // reserved bs.SkipBits(8); // page_default_disparity_shift if (disparity_shift_update_sequence_page_flag) { bs.SkipBits(8); // disparity_shift_update_sequence_length bs.SkipBits(24); // interval_duration[23..0] int division_period_count = bs.GetBits(8); for (int i = 0; i < division_period_count; ++i) { bs.SkipBits(8); // interval_count bs.SkipBits(8); // disparity_shift_update_integer_part } } while (!bs.IsEOF()) { bs.SkipBits(8); // region_id bool disparity_shift_update_sequence_region_flag = bs.GetBit(); bs.SkipBits(5); // reserved int number_of_subregions_minus_1 = bs.GetBits(2); for (int i = 0; i <= number_of_subregions_minus_1; ++i) { if (number_of_subregions_minus_1 > 0) { bs.SkipBits(16); // subregion_horizontal_position bs.SkipBits(16); // subregion_width } bs.SkipBits(8); // subregion_disparity_shift_integer_part bs.SkipBits(4); // subregion_disparity_shift_fractional_part bs.SkipBits(4); // reserved if (disparity_shift_update_sequence_region_flag) { bs.SkipBits(8); // disparity_shift_update_sequence_length bs.SkipBits(24); // interval_duration[23..0] int division_period_count = bs.GetBits(8); for (int i = 0; i < division_period_count; ++i) { bs.SkipBits(8); // interval_count bs.SkipBits(8); // disparity_shift_update_integer_part } } } } break; } case END_OF_DISPLAY_SET_SEGMENT: { dbgsegments("END_OF_DISPLAY_SET_SEGMENT
\n"); FinishPage(page); page->SetPending(false); break; } default: dbgsegments("*** unknown segment type: %02X
\n", segmentType); } return bs.Length() / 8; } 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()) return; int NumAreas; tArea *Areas = Page->GetAreas(NumAreas); if (!Areas) return; tArea AreaCombined = Page->CombineAreas(NumAreas, Areas); tArea AreaOsd = Page->ScaleArea(AreaCombined, osdFactorX, osdFactorY); int Bpp = 8; bool Reduced = false; if (osd) { while (osd->CanHandleAreas(&AreaOsd, 1) != oeOk) { dbgoutput("CanHandleAreas: %d
\n", osd->CanHandleAreas(&AreaOsd, 1)); int HalfBpp = Bpp / 2; if (HalfBpp >= 2) { if (AreaOsd.bpp >= Bpp) { AreaOsd.bpp = HalfBpp; Reduced = true; } Bpp = HalfBpp; } else return; // unable to draw bitmaps } } cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->PageState(), Page->Pts(), Page->PageTimeout(), Areas, NumAreas, osdFactorX, osdFactorY, AreaCombined, AreaOsd); bitmaps->Add(Bitmaps); for (int i = 0; i < NumAreas; i++) { if (cSubtitleRegionRef *srr = Page->GetRegionRefByIndex(i)) { if (cSubtitleRegion *sr = Page->GetRegionById(srr->RegionId())) { if (cSubtitleClut *clut = Page->GetClutById(sr->ClutId())) { cBitmap *bm = new cBitmap(sr->RegionWidth(), sr->RegionHeight(), sr->RegionDepth()); bm->Replace(*clut->GetPalette(sr->RegionDepth())); sr->Render(bm, Page->Objects()); if (Reduced) { if (sr->RegionDepth() != Areas[i].bpp) { if (sr->RegionLevelOfCompatibility() <= Areas[i].bpp) { //TODO this is untested - didn't have any such subtitle stream cSubtitleClut *Clut = Page->GetClutById(sr->ClutId()); dbgregions("reduce region %d bpp %d level %d area bpp %d
\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp); bm->ReduceBpp(*Clut->GetPalette(sr->RegionDepth())); } else { dbgregions("condense region %d bpp %d level %d area bpp %d
\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp); bm->ShrinkBpp(Areas[i].bpp); } } } bm->SetOffset(srr->RegionHorizontalAddress(), srr->RegionVerticalAddress()); Bitmaps->AddBitmap(bm); } } } } if (DebugPages) Bitmaps->DbgDump(windowWidth, windowHeight); }