| File: | dvbsubtitle.c |
| Location: | line 997, column 16 |
| Description: | Array access (via field 'data') results in a null pointer dereference |
| 1 | /* | |||
| 2 | * dvbsubtitle.c: DVB subtitles | |||
| 3 | * | |||
| 4 | * See the main source file 'vdr.c' for copyright information and | |||
| 5 | * how to reach the author. | |||
| 6 | * | |||
| 7 | * Original author: Marco Schluessler <marco@lordzodiac.de> | |||
| 8 | * With some input from the "subtitles plugin" by Pekka Virtanen <pekka.virtanen@sci.fi> | |||
| 9 | * | |||
| 10 | * $Id: dvbsubtitle.c 3.4 2013/09/07 10:39:46 kls Exp $ | |||
| 11 | */ | |||
| 12 | ||||
| 13 | #include "dvbsubtitle.h" | |||
| 14 | #define __STDC_FORMAT_MACROS // Required for format specifiers | |||
| 15 | #include <inttypes.h> | |||
| 16 | #include "device.h" | |||
| 17 | #include "libsi/si.h" | |||
| 18 | ||||
| 19 | #define PAGE_COMPOSITION_SEGMENT0x10 0x10 | |||
| 20 | #define REGION_COMPOSITION_SEGMENT0x11 0x11 | |||
| 21 | #define CLUT_DEFINITION_SEGMENT0x12 0x12 | |||
| 22 | #define OBJECT_DATA_SEGMENT0x13 0x13 | |||
| 23 | #define DISPLAY_DEFINITION_SEGMENT0x14 0x14 | |||
| 24 | #define DISPARITY_SIGNALING_SEGMENT0x15 0x15 // DVB BlueBook A156 | |||
| 25 | #define END_OF_DISPLAY_SET_SEGMENT0x80 0x80 | |||
| 26 | #define STUFFING_SEGMENT0xFF 0xFF | |||
| 27 | ||||
| 28 | // Set these to 'true' for debug output, which is written into the file dbg-log.htm | |||
| 29 | // in the current working directory. The HTML file shows the actual bitmaps (dbg-nnn.jpg) | |||
| 30 | // used to display the subtitles. | |||
| 31 | static bool DebugNormal = false; // shows pages, regions and objects | |||
| 32 | static bool DebugVerbose = false; // shows everything | |||
| 33 | static bool DebugDisplay = DebugVerbose || DebugNormal; | |||
| 34 | static bool DebugPages = DebugVerbose || DebugNormal; | |||
| 35 | static bool DebugRegions = DebugVerbose || DebugNormal; | |||
| 36 | static bool DebugObjects = DebugVerbose || DebugNormal; | |||
| 37 | static bool DebugBitmaps = DebugVerbose || DebugNormal; | |||
| 38 | static bool DebugConverter = DebugVerbose; | |||
| 39 | static bool DebugSegments = DebugVerbose; | |||
| 40 | static bool DebugPixel = DebugVerbose; | |||
| 41 | static bool DebugCluts = DebugVerbose; | |||
| 42 | static bool DebugOutput = DebugVerbose; | |||
| 43 | ||||
| 44 | #define dbgdisplay(a...)if (DebugDisplay) SD.WriteHtml(a...) if (DebugDisplay) SD.WriteHtml(a) | |||
| 45 | #define dbgpages(a...)if (DebugPages) SD.WriteHtml(a...) if (DebugPages) SD.WriteHtml(a) | |||
| 46 | #define dbgregions(a...)if (DebugRegions) SD.WriteHtml(a...) if (DebugRegions) SD.WriteHtml(a) | |||
| 47 | #define dbgobjects(a...)if (DebugObjects) SD.WriteHtml(a...) if (DebugObjects) SD.WriteHtml(a) | |||
| 48 | #define dbgbitmaps(a...)if (DebugBitmaps) SD.WriteHtml(a...) if (DebugBitmaps) SD.WriteHtml(a) | |||
| 49 | #define dbgconverter(a...)if (DebugConverter) SD.WriteHtml(a...) if (DebugConverter) SD.WriteHtml(a) | |||
| 50 | #define dbgsegments(a...)if (DebugSegments) SD.WriteHtml(a...) if (DebugSegments) SD.WriteHtml(a) | |||
| 51 | #define dbgpixel(a...)if (DebugPixel) SD.WriteHtml(a...) if (DebugPixel) SD.WriteHtml(a) | |||
| 52 | #define dbgcluts(a...)if (DebugCluts) SD.WriteHtml(a...) if (DebugCluts) SD.WriteHtml(a) | |||
| 53 | #define dbgoutput(a...)if (DebugOutput) SD.WriteHtml(a...) if (DebugOutput) SD.WriteHtml(a) | |||
| 54 | ||||
| 55 | #define DBGMAXBITMAPS100 100 // debug output will be stopped after this many bitmaps | |||
| 56 | #define DBGBITMAPWIDTH400 400 | |||
| 57 | ||||
| 58 | // --- cSubtitleDebug -------------------------------------------------------- | |||
| 59 | ||||
| 60 | class cSubtitleDebug { | |||
| 61 | private: | |||
| 62 | cMutex mutex; | |||
| 63 | int imgCnt; | |||
| 64 | int64_t firstPts; | |||
| 65 | bool newFile; | |||
| 66 | double factor; | |||
| 67 | public: | |||
| 68 | cSubtitleDebug(void) { Reset(); } | |||
| 69 | void Reset(void); | |||
| 70 | bool Active(void) { return imgCnt < DBGMAXBITMAPS100; } | |||
| 71 | int64_t FirstPts(void) { return firstPts; } | |||
| 72 | void SetFirstPts(int64_t FirstPts) { if (firstPts < 0) firstPts = FirstPts; } | |||
| 73 | void SetFactor(double Factor) { factor = Factor; } | |||
| 74 | cString WriteJpeg(const cBitmap *Bitmap, int MaxX = 0, int MaxY = 0); | |||
| 75 | void WriteHtml(const char *Format, ...); | |||
| 76 | }; | |||
| 77 | ||||
| 78 | void cSubtitleDebug::Reset(void) | |||
| 79 | { | |||
| 80 | imgCnt = 0; | |||
| 81 | firstPts = -1; | |||
| 82 | newFile = true; | |||
| 83 | factor = 1.0; | |||
| 84 | } | |||
| 85 | ||||
| 86 | cString cSubtitleDebug::WriteJpeg(const cBitmap *Bitmap, int MaxX, int MaxY) | |||
| 87 | { | |||
| 88 | if (!Active()) | |||
| 89 | return NULL__null; | |||
| 90 | cMutexLock MutexLock(&mutex); | |||
| 91 | cBitmap *Scaled = Bitmap->Scaled(factor, factor, true); | |||
| 92 | int w = MaxX ? int(round(MaxX * factor)) : Scaled->Width(); | |||
| 93 | int h = MaxY ? int(round(MaxY * factor)) : Scaled->Height(); | |||
| 94 | uchar mem[w * h * 3]; | |||
| 95 | for (int x = 0; x < w; x++) { | |||
| 96 | for (int y = 0; y < h; y++) { | |||
| 97 | tColor c = Scaled->GetColor(x, y); | |||
| 98 | int o = (y * w + x) * 3; | |||
| 99 | mem[o++] = (c & 0x00FF0000) >> 16; | |||
| 100 | mem[o++] = (c & 0x0000FF00) >> 8; | |||
| 101 | mem[o] = (c & 0x000000FF); | |||
| 102 | } | |||
| 103 | } | |||
| 104 | delete Scaled; | |||
| 105 | int Size = 0; | |||
| 106 | uchar *Jpeg = RgbToJpeg(mem, w, h, Size); | |||
| 107 | cString ImgName = cString::sprintf("dbg-%03d.jpg", imgCnt++); | |||
| 108 | int f = open(ImgName, O_WRONLY01 | O_CREAT0100, DEFFILEMODE(0400|0200|(0400 >> 3)|(0200 >> 3)|((0400 >> 3) >> 3)|((0200 >> 3) >> 3))); | |||
| 109 | if (f >= 0) { | |||
| 110 | if (write(f, Jpeg, Size) < 0) | |||
| 111 | LOG_ERROR_STR(*ImgName)void( (SysLogLevel > 0) ? syslog_with_tid(3, "ERROR (%s,%d): %s: %m" , "dvbsubtitle.c", 111, *ImgName) : void() ); | |||
| 112 | close(f); | |||
| 113 | } | |||
| 114 | free(Jpeg); | |||
| 115 | return ImgName; | |||
| 116 | } | |||
| 117 | ||||
| 118 | void cSubtitleDebug::WriteHtml(const char *Format, ...) | |||
| 119 | { | |||
| 120 | if (!Active()) | |||
| 121 | return; | |||
| 122 | cMutexLock MutexLock(&mutex); | |||
| 123 | if (FILE *f = fopen("dbg-log.htm", newFile ? "w" : "a")) { | |||
| 124 | va_list ap; | |||
| 125 | va_start(ap, Format)__builtin_va_start(ap, Format); | |||
| 126 | vfprintf(f, Format, ap); | |||
| 127 | va_end(ap)__builtin_va_end(ap); | |||
| 128 | fclose(f); | |||
| 129 | newFile = false; | |||
| 130 | } | |||
| 131 | } | |||
| 132 | ||||
| 133 | static cSubtitleDebug SD; | |||
| 134 | ||||
| 135 | // --- cSubtitleClut --------------------------------------------------------- | |||
| 136 | ||||
| 137 | class cSubtitleClut : public cListObject { | |||
| 138 | private: | |||
| 139 | int clutId; | |||
| 140 | int clutVersionNumber; | |||
| 141 | cPalette palette2; | |||
| 142 | cPalette palette4; | |||
| 143 | cPalette palette8; | |||
| 144 | tColor yuv2rgb(int Y, int Cb, int Cr); | |||
| 145 | void SetColor(int Bpp, int Index, tColor Color); | |||
| 146 | public: | |||
| 147 | cSubtitleClut(int ClutId); | |||
| 148 | void Parse(cBitStream &bs); | |||
| 149 | int ClutId(void) { return clutId; } | |||
| 150 | int ClutVersionNumber(void) { return clutVersionNumber; } | |||
| 151 | const cPalette *GetPalette(int Bpp); | |||
| 152 | }; | |||
| 153 | ||||
| 154 | cSubtitleClut::cSubtitleClut(int ClutId) | |||
| 155 | :palette2(2) | |||
| 156 | ,palette4(4) | |||
| 157 | ,palette8(8) | |||
| 158 | { | |||
| 159 | int a = 0, r = 0, g = 0, b = 0; | |||
| 160 | clutId = ClutId; | |||
| 161 | clutVersionNumber = -1; | |||
| 162 | // ETSI EN 300 743 10.3: 4-entry CLUT default contents | |||
| 163 | palette2.SetColor(0, ArgbToColor( 0, 0, 0, 0)); | |||
| 164 | palette2.SetColor(1, ArgbToColor(255, 255, 255, 255)); | |||
| 165 | palette2.SetColor(2, ArgbToColor(255, 0, 0, 0)); | |||
| 166 | palette2.SetColor(3, ArgbToColor(255, 127, 127, 127)); | |||
| 167 | // ETSI EN 300 743 10.2: 16-entry CLUT default contents | |||
| 168 | palette4.SetColor(0, ArgbToColor(0, 0, 0, 0)); | |||
| 169 | for (int i = 1; i < 16; ++i) { | |||
| 170 | if (i < 8) { | |||
| 171 | r = (i & 1) ? 255 : 0; | |||
| 172 | g = (i & 2) ? 255 : 0; | |||
| 173 | b = (i & 4) ? 255 : 0; | |||
| 174 | } | |||
| 175 | else { | |||
| 176 | r = (i & 1) ? 127 : 0; | |||
| 177 | g = (i & 2) ? 127 : 0; | |||
| 178 | b = (i & 4) ? 127 : 0; | |||
| 179 | } | |||
| 180 | palette4.SetColor(i, ArgbToColor(255, r, g, b)); | |||
| 181 | } | |||
| 182 | // ETSI EN 300 743 10.1: 256-entry CLUT default contents | |||
| 183 | palette8.SetColor(0, ArgbToColor(0, 0, 0, 0)); | |||
| 184 | for (int i = 1; i < 256; ++i) { | |||
| 185 | if (i < 8) { | |||
| 186 | r = (i & 1) ? 255 : 0; | |||
| 187 | g = (i & 2) ? 255 : 0; | |||
| 188 | b = (i & 4) ? 255 : 0; | |||
| 189 | a = 63; | |||
| 190 | } | |||
| 191 | else { | |||
| 192 | switch (i & 0x88) { | |||
| 193 | case 0x00: | |||
| 194 | r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0); | |||
| 195 | g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0); | |||
| 196 | b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0); | |||
| 197 | a = 255; | |||
| 198 | break; | |||
| 199 | case 0x08: | |||
| 200 | r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0); | |||
| 201 | g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0); | |||
| 202 | b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0); | |||
| 203 | a = 127; | |||
| 204 | break; | |||
| 205 | case 0x80: | |||
| 206 | r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0); | |||
| 207 | g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0); | |||
| 208 | b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0); | |||
| 209 | a = 255; | |||
| 210 | break; | |||
| 211 | case 0x88: | |||
| 212 | r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0); | |||
| 213 | g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0); | |||
| 214 | b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0); | |||
| 215 | a = 255; | |||
| 216 | break; | |||
| 217 | } | |||
| 218 | } | |||
| 219 | palette8.SetColor(i, ArgbToColor(a, r, g, b)); | |||
| 220 | } | |||
| 221 | } | |||
| 222 | ||||
| 223 | void cSubtitleClut::Parse(cBitStream &bs) | |||
| 224 | { | |||
| 225 | int Version = bs.GetBits(4); | |||
| 226 | if (clutVersionNumber == Version) | |||
| 227 | return; // no update | |||
| 228 | clutVersionNumber = Version; | |||
| 229 | bs.SkipBits(4); // reserved | |||
| 230 | dbgcluts("<b>clut</b> id %d version %d<br>\n", clutId, clutVersionNumber)if (DebugCluts) SD.WriteHtml("<b>clut</b> id %d version %d<br>\n" , clutId, clutVersionNumber); | |||
| 231 | while (!bs.IsEOF()) { | |||
| 232 | uchar clutEntryId = bs.GetBits(8); | |||
| 233 | bool entryClut2Flag = bs.GetBit(); | |||
| 234 | bool entryClut4Flag = bs.GetBit(); | |||
| 235 | bool entryClut8Flag = bs.GetBit(); | |||
| 236 | bs.SkipBits(4); // reserved | |||
| 237 | uchar yval; | |||
| 238 | uchar crval; | |||
| 239 | uchar cbval; | |||
| 240 | uchar tval; | |||
| 241 | if (bs.GetBit()) { // full_range_flag | |||
| 242 | yval = bs.GetBits(8); | |||
| 243 | crval = bs.GetBits(8); | |||
| 244 | cbval = bs.GetBits(8); | |||
| 245 | tval = bs.GetBits(8); | |||
| 246 | } | |||
| 247 | else { | |||
| 248 | yval = bs.GetBits(6) << 2; | |||
| 249 | crval = bs.GetBits(4) << 4; | |||
| 250 | cbval = bs.GetBits(4) << 4; | |||
| 251 | tval = bs.GetBits(2) << 6; | |||
| 252 | } | |||
| 253 | tColor value = 0; | |||
| 254 | if (yval) { | |||
| 255 | value = yuv2rgb(yval, cbval, crval); | |||
| 256 | value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24; | |||
| 257 | } | |||
| 258 | dbgcluts("%2d %d %d %d %08X<br>\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value)if (DebugCluts) SD.WriteHtml("%2d %d %d %d %08X<br>\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value); | |||
| 259 | if (entryClut2Flag) | |||
| 260 | SetColor(2, clutEntryId, value); | |||
| 261 | if (entryClut4Flag) | |||
| 262 | SetColor(4, clutEntryId, value); | |||
| 263 | if (entryClut8Flag) | |||
| 264 | SetColor(8, clutEntryId, value); | |||
| 265 | } | |||
| 266 | } | |||
| 267 | ||||
| 268 | tColor cSubtitleClut::yuv2rgb(int Y, int Cb, int Cr) | |||
| 269 | { | |||
| 270 | int Ey, Epb, Epr; | |||
| 271 | int Eg, Eb, Er; | |||
| 272 | ||||
| 273 | Ey = (Y - 16); | |||
| 274 | Epb = (Cb - 128); | |||
| 275 | Epr = (Cr - 128); | |||
| 276 | /* ITU-R 709 */ | |||
| 277 | Er = constrain((298 * Ey + 460 * Epr) / 256, 0, 255); | |||
| 278 | Eg = constrain((298 * Ey - 55 * Epb - 137 * Epr) / 256, 0, 255); | |||
| 279 | Eb = constrain((298 * Ey + 543 * Epb ) / 256, 0, 255); | |||
| 280 | ||||
| 281 | return (Er << 16) | (Eg << 8) | Eb; | |||
| 282 | } | |||
| 283 | ||||
| 284 | void cSubtitleClut::SetColor(int Bpp, int Index, tColor Color) | |||
| 285 | { | |||
| 286 | switch (Bpp) { | |||
| 287 | case 2: palette2.SetColor(Index, Color); break; | |||
| 288 | case 4: palette4.SetColor(Index, Color); break; | |||
| 289 | case 8: palette8.SetColor(Index, Color); break; | |||
| 290 | default: esyslog("ERROR: wrong Bpp in cSubtitleClut::SetColor(%d, %d, %08X)", Bpp, Index, Color)void( (SysLogLevel > 0) ? syslog_with_tid(3, "ERROR: wrong Bpp in cSubtitleClut::SetColor(%d, %d, %08X)" , Bpp, Index, Color) : void() ); | |||
| 291 | } | |||
| 292 | } | |||
| 293 | ||||
| 294 | const cPalette *cSubtitleClut::GetPalette(int Bpp) | |||
| 295 | { | |||
| 296 | switch (Bpp) { | |||
| 297 | case 2: return &palette2; | |||
| 298 | case 4: return &palette4; | |||
| 299 | case 8: return &palette8; | |||
| 300 | default: esyslog("ERROR: wrong Bpp in cSubtitleClut::GetPalette(%d)", Bpp)void( (SysLogLevel > 0) ? syslog_with_tid(3, "ERROR: wrong Bpp in cSubtitleClut::GetPalette(%d)" , Bpp) : void() ); | |||
| 301 | } | |||
| 302 | return &palette8; | |||
| 303 | } | |||
| 304 | ||||
| 305 | // --- cSubtitleObject ------------------------------------------------------- | |||
| 306 | ||||
| 307 | class cSubtitleObject : public cListObject { | |||
| 308 | private: | |||
| 309 | int objectId; | |||
| 310 | int objectVersionNumber; | |||
| 311 | int objectCodingMethod; | |||
| 312 | bool nonModifyingColorFlag; | |||
| 313 | int topLength; | |||
| 314 | int botLength; | |||
| 315 | uchar *topData; | |||
| 316 | uchar *botData; | |||
| 317 | char *txtData; | |||
| 318 | int lineHeight; | |||
| 319 | void DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length); | |||
| 320 | bool Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable); | |||
| 321 | bool Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable); | |||
| 322 | bool Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y); | |||
| 323 | void DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even); | |||
| 324 | void DecodeCharacterString(const uchar *Data, int NumberOfCodes); | |||
| 325 | public: | |||
| 326 | cSubtitleObject(int ObjectId); | |||
| 327 | ~cSubtitleObject(); | |||
| 328 | void Parse(cBitStream &bs); | |||
| 329 | int ObjectId(void) { return objectId; } | |||
| 330 | int ObjectVersionNumber(void) { return objectVersionNumber; } | |||
| 331 | int ObjectCodingMethod(void) { return objectCodingMethod; } | |||
| 332 | bool NonModifyingColorFlag(void) { return nonModifyingColorFlag; } | |||
| 333 | void Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg); | |||
| 334 | }; | |||
| 335 | ||||
| 336 | cSubtitleObject::cSubtitleObject(int ObjectId) | |||
| 337 | { | |||
| 338 | objectId = ObjectId; | |||
| 339 | objectVersionNumber = -1; | |||
| 340 | objectCodingMethod = -1; | |||
| 341 | nonModifyingColorFlag = false; | |||
| 342 | topLength = 0; | |||
| 343 | botLength = 0; | |||
| 344 | topData = NULL__null; | |||
| 345 | botData = NULL__null; | |||
| 346 | txtData = NULL__null; | |||
| 347 | lineHeight = 26; // configurable subtitling font size? | |||
| 348 | } | |||
| 349 | ||||
| 350 | cSubtitleObject::~cSubtitleObject() | |||
| 351 | { | |||
| 352 | free(topData); | |||
| 353 | free(botData); | |||
| 354 | free(txtData); | |||
| 355 | } | |||
| 356 | ||||
| 357 | void cSubtitleObject::Parse(cBitStream &bs) | |||
| 358 | { | |||
| 359 | int Version = bs.GetBits(4); | |||
| 360 | if (objectVersionNumber == Version) | |||
| 361 | return; // no update | |||
| 362 | objectVersionNumber = Version; | |||
| 363 | objectCodingMethod = bs.GetBits(2); | |||
| 364 | nonModifyingColorFlag = bs.GetBit(); | |||
| 365 | bs.SkipBit(); // reserved | |||
| 366 | dbgobjects("<b>object</b> id %d version %d method %d modify %d", objectId, objectVersionNumber, objectCodingMethod, nonModifyingColorFlag)if (DebugObjects) SD.WriteHtml("<b>object</b> id %d version %d method %d modify %d" , objectId, objectVersionNumber, objectCodingMethod, nonModifyingColorFlag ); // no "<br>\n" here, DecodeCharacterString() may add data | |||
| 367 | if (objectCodingMethod == 0) { // coding of pixels | |||
| 368 | topLength = bs.GetBits(16); | |||
| 369 | botLength = bs.GetBits(16); | |||
| 370 | free(topData); | |||
| 371 | if ((topData = MALLOC(uchar, topLength)(uchar *)malloc(sizeof(uchar) * (topLength))) != NULL__null) | |||
| 372 | memcpy(topData, bs.GetData(), topLength); | |||
| 373 | else | |||
| 374 | topLength = 0; | |||
| 375 | free(botData); | |||
| 376 | if ((botData = MALLOC(uchar, botLength)(uchar *)malloc(sizeof(uchar) * (botLength))) != NULL__null) | |||
| 377 | memcpy(botData, bs.GetData() + topLength, botLength); | |||
| 378 | else | |||
| 379 | botLength = 0; | |||
| 380 | bs.WordAlign(); | |||
| 381 | } | |||
| 382 | else if (objectCodingMethod == 1) { // coded as a string of characters | |||
| 383 | int numberOfCodes = bs.GetBits(8); | |||
| 384 | DecodeCharacterString(bs.GetData(), numberOfCodes); | |||
| 385 | } | |||
| 386 | dbgobjects("<br>\n")if (DebugObjects) SD.WriteHtml("<br>\n"); | |||
| 387 | if (DebugObjects) { | |||
| 388 | // We can't get the actual clut here, so we use a default one. This may lead to | |||
| 389 | // funny colors, but we just want to get a rough idea of what's in the object, anyway. | |||
| 390 | cSubtitleClut Clut(0); | |||
| 391 | cBitmap b(1920, 1080, 8); | |||
| 392 | b.Replace(*Clut.GetPalette(b.Bpp())); | |||
| 393 | b.Clean(); | |||
| 394 | Render(&b, 0, 0, 0, 1); | |||
| 395 | int x1, y1, x2, y2; | |||
| 396 | if (b.Dirty(x1, y1, x2, y2)) { | |||
| 397 | cString ImgName = SD.WriteJpeg(&b, x2, y2); | |||
| 398 | dbgobjects("<img src=\"%s\"><br>\n", *ImgName)if (DebugObjects) SD.WriteHtml("<img src=\"%s\"><br>\n" , *ImgName); | |||
| 399 | } | |||
| 400 | } | |||
| 401 | } | |||
| 402 | ||||
| 403 | void cSubtitleObject::DecodeCharacterString(const uchar *Data, int NumberOfCodes) | |||
| 404 | { | |||
| 405 | // "ETSI EN 300 743 V1.3.1 (2006-11)", chapter 7.2.5 "Object data segment" specifies | |||
| 406 | // character_code to be a 16-bit index number into the character table identified | |||
| 407 | // in the subtitle_descriptor. However, the "subtitling_descriptor" <sic> according to | |||
| 408 | // "ETSI EN 300 468 V1.13.1 (2012-04)" doesn't contain a "character table identifier". | |||
| 409 | // It only contains a three letter language code, without any specification as to how | |||
| 410 | // this is related to a specific character table. | |||
| 411 | // Apparently the first "code" in textual subtitles contains the character table | |||
| 412 | // identifier, and all codes are 8-bit only. So let's first make Data a string of | |||
| 413 | // 8-bit characters: | |||
| 414 | if (NumberOfCodes > 0) { | |||
| 415 | char txt[NumberOfCodes + 1]; | |||
| 416 | for (int i = 0; i < NumberOfCodes; i++) | |||
| 417 | txt[i] = Data[i * 2 + 1]; | |||
| 418 | txt[NumberOfCodes] = 0; | |||
| 419 | bool singleByte; | |||
| 420 | const uchar *from = (uchar *)txt; | |||
| 421 | int len = NumberOfCodes; | |||
| 422 | const char *CharacterTable = SI::getCharacterTable(from, len, &singleByte); | |||
| 423 | dbgobjects(" table %s single %d raw '%s'", CharacterTable, singleByte, from)if (DebugObjects) SD.WriteHtml(" table %s single %d raw '%s'" , CharacterTable, singleByte, from); | |||
| 424 | cCharSetConv conv(CharacterTable, cCharSetConv::SystemCharacterTable()); | |||
| 425 | const char *s = conv.Convert((const char *)from); | |||
| 426 | dbgobjects(" conv '%s'", s)if (DebugObjects) SD.WriteHtml(" conv '%s'", s); | |||
| 427 | free(txtData); | |||
| 428 | txtData = strdup(s); | |||
| 429 | } | |||
| 430 | } | |||
| 431 | ||||
| 432 | void cSubtitleObject::DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even) | |||
| 433 | { | |||
| 434 | int x = 0; | |||
| 435 | int y = Even ? 0 : 1; | |||
| 436 | uint8_t map2to4[ 4] = { 0x00, 0x07, 0x08, 0x0F }; | |||
| 437 | uint8_t map2to8[ 4] = { 0x00, 0x77, 0x88, 0xFF }; | |||
| 438 | uint8_t map4to8[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; | |||
| 439 | const uint8_t *mapTable = NULL__null; | |||
| 440 | cBitStream bs(Data, Length * 8); | |||
| 441 | while (!bs.IsEOF()) { | |||
| 442 | switch (bs.GetBits(8)) { | |||
| 443 | case 0x10: | |||
| 444 | dbgpixel("2-bit / pixel code string<br>\n")if (DebugPixel) SD.WriteHtml("2-bit / pixel code string<br>\n" ); | |||
| 445 | switch (Bitmap->Bpp()) { | |||
| 446 | case 8: mapTable = map2to8; break; | |||
| 447 | case 4: mapTable = map2to4; break; | |||
| 448 | default: mapTable = NULL__null; break; | |||
| 449 | } | |||
| 450 | while (Decode2BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF()) | |||
| 451 | ; | |||
| 452 | bs.ByteAlign(); | |||
| 453 | break; | |||
| 454 | case 0x11: | |||
| 455 | dbgpixel("4-bit / pixel code string<br>\n")if (DebugPixel) SD.WriteHtml("4-bit / pixel code string<br>\n" ); | |||
| 456 | switch (Bitmap->Bpp()) { | |||
| 457 | case 8: mapTable = map4to8; break; | |||
| 458 | default: mapTable = NULL__null; break; | |||
| 459 | } | |||
| 460 | while (Decode4BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF()) | |||
| 461 | ; | |||
| 462 | bs.ByteAlign(); | |||
| 463 | break; | |||
| 464 | case 0x12: | |||
| 465 | dbgpixel("8-bit / pixel code string<br>\n")if (DebugPixel) SD.WriteHtml("8-bit / pixel code string<br>\n" ); | |||
| 466 | while (Decode8BppCodeString(Bitmap, px, py, &bs, x, y) && !bs.IsEOF()) | |||
| 467 | ; | |||
| 468 | break; | |||
| 469 | case 0x20: | |||
| 470 | dbgpixel("sub block 2 to 4 map<br>\n")if (DebugPixel) SD.WriteHtml("sub block 2 to 4 map<br>\n" ); | |||
| 471 | for (int i = 0; i < 4; ++i) | |||
| 472 | map2to4[i] = bs.GetBits(4); | |||
| 473 | break; | |||
| 474 | case 0x21: | |||
| 475 | dbgpixel("sub block 2 to 8 map<br>\n")if (DebugPixel) SD.WriteHtml("sub block 2 to 8 map<br>\n" ); | |||
| 476 | for (int i = 0; i < 4; ++i) | |||
| 477 | map2to8[i] = bs.GetBits(8); | |||
| 478 | break; | |||
| 479 | case 0x22: | |||
| 480 | dbgpixel("sub block 4 to 8 map<br>\n")if (DebugPixel) SD.WriteHtml("sub block 4 to 8 map<br>\n" ); | |||
| 481 | for (int i = 0; i < 16; ++i) | |||
| 482 | map4to8[i] = bs.GetBits(8); | |||
| 483 | break; | |||
| 484 | case 0xF0: | |||
| 485 | dbgpixel("end of object line<br>\n")if (DebugPixel) SD.WriteHtml("end of object line<br>\n" ); | |||
| 486 | x = 0; | |||
| 487 | y += 2; | |||
| 488 | break; | |||
| 489 | default: dbgpixel("unknown sub block %s %d<br>\n", __FUNCTION__, __LINE__)if (DebugPixel) SD.WriteHtml("unknown sub block %s %d<br>\n" , __FUNCTION__, 489); | |||
| 490 | } | |||
| 491 | } | |||
| 492 | } | |||
| 493 | ||||
| 494 | void cSubtitleObject::DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length) | |||
| 495 | { | |||
| 496 | if (nonModifyingColorFlag && Index == 1) | |||
| 497 | return; | |||
| 498 | for (int pos = x; pos < x + Length; pos++) | |||
| 499 | Bitmap->SetIndex(pos, y, Index); | |||
| 500 | } | |||
| 501 | ||||
| 502 | bool cSubtitleObject::Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable) | |||
| 503 | { | |||
| 504 | int rl = 0; | |||
| 505 | int color = 0; | |||
| 506 | uchar code = bs->GetBits(2); | |||
| 507 | if (code) { | |||
| 508 | color = code; | |||
| 509 | rl = 1; | |||
| 510 | } | |||
| 511 | else if (bs->GetBit()) { // switch_1 | |||
| 512 | rl = bs->GetBits(3) + 3; | |||
| 513 | color = bs->GetBits(2); | |||
| 514 | } | |||
| 515 | else if (bs->GetBit()) // switch_2 | |||
| 516 | rl = 1; //color 0 | |||
| 517 | else { | |||
| 518 | switch (bs->GetBits(2)) { // switch_3 | |||
| 519 | case 0: | |||
| 520 | return false; | |||
| 521 | case 1: | |||
| 522 | rl = 2; //color 0 | |||
| 523 | break; | |||
| 524 | case 2: | |||
| 525 | rl = bs->GetBits(4) + 12; | |||
| 526 | color = bs->GetBits(2); | |||
| 527 | break; | |||
| 528 | case 3: | |||
| 529 | rl = bs->GetBits(8) + 29; | |||
| 530 | color = bs->GetBits(2); | |||
| 531 | break; | |||
| 532 | default: ; | |||
| 533 | } | |||
| 534 | } | |||
| 535 | if (MapTable) | |||
| 536 | color = MapTable[color]; | |||
| 537 | DrawLine(Bitmap, px + x, py + y, color, rl); | |||
| 538 | x += rl; | |||
| 539 | return true; | |||
| 540 | } | |||
| 541 | ||||
| 542 | bool cSubtitleObject::Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable) | |||
| 543 | { | |||
| 544 | int rl = 0; | |||
| 545 | int color = 0; | |||
| 546 | uchar code = bs->GetBits(4); | |||
| 547 | if (code) { | |||
| 548 | color = code; | |||
| 549 | rl = 1; | |||
| 550 | } | |||
| 551 | else if (bs->GetBit() == 0) { // switch_1 | |||
| 552 | code = bs->GetBits(3); | |||
| 553 | if (code) | |||
| 554 | rl = code + 2; //color 0 | |||
| 555 | else | |||
| 556 | return false; | |||
| 557 | } | |||
| 558 | else if (bs->GetBit() == 0) { // switch_2 | |||
| 559 | rl = bs->GetBits(2) + 4; | |||
| 560 | color = bs->GetBits(4); | |||
| 561 | } | |||
| 562 | else { | |||
| 563 | switch (bs->GetBits(2)) { // switch_3 | |||
| 564 | case 0: // color 0 | |||
| 565 | rl = 1; | |||
| 566 | break; | |||
| 567 | case 1: // color 0 | |||
| 568 | rl = 2; | |||
| 569 | break; | |||
| 570 | case 2: | |||
| 571 | rl = bs->GetBits(4) + 9; | |||
| 572 | color = bs->GetBits(4); | |||
| 573 | break; | |||
| 574 | case 3: | |||
| 575 | rl = bs->GetBits(8) + 25; | |||
| 576 | color = bs->GetBits(4); | |||
| 577 | break; | |||
| 578 | } | |||
| 579 | } | |||
| 580 | if (MapTable) | |||
| 581 | color = MapTable[color]; | |||
| 582 | DrawLine(Bitmap, px + x, py + y, color, rl); | |||
| 583 | x += rl; | |||
| 584 | return true; | |||
| 585 | } | |||
| 586 | ||||
| 587 | bool cSubtitleObject::Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y) | |||
| 588 | { | |||
| 589 | int rl = 0; | |||
| 590 | int color = 0; | |||
| 591 | uchar code = bs->GetBits(8); | |||
| 592 | if (code) { | |||
| 593 | color = code; | |||
| 594 | rl = 1; | |||
| 595 | } | |||
| 596 | else if (bs->GetBit()) { | |||
| 597 | rl = bs->GetBits(7); | |||
| 598 | color = bs->GetBits(8); | |||
| 599 | } | |||
| 600 | else { | |||
| 601 | code = bs->GetBits(7); | |||
| 602 | if (code) | |||
| 603 | rl = code; // color 0 | |||
| 604 | else | |||
| 605 | return false; | |||
| 606 | } | |||
| 607 | DrawLine(Bitmap, px + x, py + y, color, rl); | |||
| 608 | x += rl; | |||
| 609 | return true; | |||
| 610 | } | |||
| 611 | ||||
| 612 | void cSubtitleObject::Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg) | |||
| 613 | { | |||
| 614 | if (objectCodingMethod == 0) { // coding of pixels | |||
| 615 | DecodeSubBlock(Bitmap, px, py, topData, topLength, true); | |||
| 616 | if (botLength) | |||
| 617 | DecodeSubBlock(Bitmap, px, py, botData, botLength, false); | |||
| 618 | else | |||
| 619 | DecodeSubBlock(Bitmap, px, py, topData, topLength, false); | |||
| 620 | } | |||
| 621 | else if (objectCodingMethod == 1) { // coded as a string of characters | |||
| 622 | if (txtData) { | |||
| 623 | //TODO couldn't we draw the text directly into Bitmap? | |||
| 624 | cFont *font = cFont::CreateFont(Setup.FontOsd, Setup.FontOsdSize); | |||
| 625 | cBitmap tmp(font->Width(txtData), font->Height(), Bitmap->Bpp()); | |||
| 626 | double factor = (double)lineHeight / font->Height(); | |||
| 627 | tmp.DrawText(0, 0, txtData, Bitmap->Color(IndexFg), Bitmap->Color(IndexBg), font); | |||
| 628 | cBitmap *scaled = tmp.Scaled(factor, factor, true); | |||
| 629 | Bitmap->DrawBitmap(px, py, *scaled); | |||
| 630 | delete scaled; | |||
| 631 | delete font; | |||
| 632 | } | |||
| 633 | } | |||
| 634 | } | |||
| 635 | ||||
| 636 | // --- cSubtitleObjects ------------------------------------------------------ | |||
| 637 | ||||
| 638 | class cSubtitleObjects : public cList<cSubtitleObject> { | |||
| 639 | public: | |||
| 640 | cSubtitleObject *GetObjectById(int ObjectId, bool New = false); | |||
| 641 | }; | |||
| 642 | ||||
| 643 | cSubtitleObject *cSubtitleObjects::GetObjectById(int ObjectId, bool New) | |||
| 644 | { | |||
| 645 | for (cSubtitleObject *so = First(); so; so = Next(so)) { | |||
| 646 | if (so->ObjectId() == ObjectId) | |||
| 647 | return so; | |||
| 648 | } | |||
| 649 | if (!New) | |||
| 650 | return NULL__null; | |||
| 651 | cSubtitleObject *Object = new cSubtitleObject(ObjectId); | |||
| 652 | Add(Object); | |||
| 653 | return Object; | |||
| 654 | } | |||
| 655 | ||||
| 656 | // --- cSubtitleObjectRef ---------------------------------------------------- | |||
| 657 | ||||
| 658 | class cSubtitleObjectRef : public cListObject { | |||
| 659 | private: | |||
| 660 | int objectId; | |||
| 661 | int objectType; | |||
| 662 | int objectProviderFlag; | |||
| 663 | int objectHorizontalPosition; | |||
| 664 | int objectVerticalPosition; | |||
| 665 | int foregroundPixelCode; | |||
| 666 | int backgroundPixelCode; | |||
| 667 | public: | |||
| 668 | cSubtitleObjectRef(cBitStream &bs); | |||
| 669 | int ObjectId(void) { return objectId; } | |||
| 670 | int ObjectType(void) { return objectType; } | |||
| 671 | int ObjectProviderFlag(void) { return objectProviderFlag; } | |||
| 672 | int ObjectHorizontalPosition(void) { return objectHorizontalPosition; } | |||
| 673 | int ObjectVerticalPosition(void) { return objectVerticalPosition; } | |||
| 674 | int ForegroundPixelCode(void) { return foregroundPixelCode; } | |||
| 675 | int BackgroundPixelCode(void) { return backgroundPixelCode; } | |||
| 676 | }; | |||
| 677 | ||||
| 678 | cSubtitleObjectRef::cSubtitleObjectRef(cBitStream &bs) | |||
| 679 | { | |||
| 680 | objectId = bs.GetBits(16); | |||
| 681 | objectType = bs.GetBits(2); | |||
| 682 | objectProviderFlag = bs.GetBits(2); | |||
| 683 | objectHorizontalPosition = bs.GetBits(12); | |||
| 684 | bs.SkipBits(4); // reserved | |||
| 685 | objectVerticalPosition = bs.GetBits(12); | |||
| 686 | if (objectType == 0x01 || objectType == 0x02) { | |||
| 687 | foregroundPixelCode = bs.GetBits(8); | |||
| 688 | backgroundPixelCode = bs.GetBits(8); | |||
| 689 | } | |||
| 690 | else { | |||
| 691 | foregroundPixelCode = 0; | |||
| 692 | backgroundPixelCode = 0; | |||
| 693 | } | |||
| 694 | 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)if (DebugRegions) SD.WriteHtml("<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 ); | |||
| 695 | } | |||
| 696 | ||||
| 697 | // --- cSubtitleRegion ------------------------------------------------------- | |||
| 698 | ||||
| 699 | class cSubtitleRegion : public cListObject { | |||
| 700 | private: | |||
| 701 | int regionId; | |||
| 702 | int regionVersionNumber; | |||
| 703 | bool regionFillFlag; | |||
| 704 | int regionWidth; | |||
| 705 | int regionHeight; | |||
| 706 | int regionLevelOfCompatibility; | |||
| 707 | int regionDepth; | |||
| 708 | int clutId; | |||
| 709 | int region8bitPixelCode; | |||
| 710 | int region4bitPixelCode; | |||
| 711 | int region2bitPixelCode; | |||
| 712 | cList<cSubtitleObjectRef> objectRefs; | |||
| 713 | public: | |||
| 714 | cSubtitleRegion(int RegionId); | |||
| 715 | void Parse(cBitStream &bs); | |||
| 716 | int RegionId(void) { return regionId; } | |||
| 717 | int RegionVersionNumber(void) { return regionVersionNumber; } | |||
| 718 | bool RegionFillFlag(void) { return regionFillFlag; } | |||
| 719 | int RegionWidth(void) { return regionWidth; } | |||
| 720 | int RegionHeight(void) { return regionHeight; } | |||
| 721 | int RegionLevelOfCompatibility(void) { return regionLevelOfCompatibility; } | |||
| 722 | int RegionDepth(void) { return regionDepth; } | |||
| 723 | int ClutId(void) { return clutId; } | |||
| 724 | void Render(cBitmap *Bitmap, cSubtitleObjects *Objects); | |||
| 725 | }; | |||
| 726 | ||||
| 727 | cSubtitleRegion::cSubtitleRegion(int RegionId) | |||
| 728 | { | |||
| 729 | regionId = RegionId; | |||
| 730 | regionVersionNumber = -1; | |||
| 731 | regionFillFlag = false; | |||
| 732 | regionWidth = 0; | |||
| 733 | regionHeight = 0; | |||
| 734 | regionLevelOfCompatibility = 0; | |||
| 735 | regionDepth = 0; | |||
| 736 | clutId = -1; | |||
| 737 | region8bitPixelCode = 0; | |||
| 738 | region4bitPixelCode = 0; | |||
| 739 | region2bitPixelCode = 0; | |||
| 740 | } | |||
| 741 | ||||
| 742 | void cSubtitleRegion::Parse(cBitStream &bs) | |||
| 743 | { | |||
| 744 | int Version = bs.GetBits(4); | |||
| 745 | if (regionVersionNumber == Version) | |||
| 746 | return; // no update | |||
| 747 | regionVersionNumber = Version; | |||
| 748 | regionFillFlag = bs.GetBit(); | |||
| 749 | bs.SkipBits(3); // reserved | |||
| 750 | regionWidth = bs.GetBits(16); | |||
| 751 | regionHeight = bs.GetBits(16); | |||
| 752 | regionLevelOfCompatibility = 1 << bs.GetBits(3); // stored as "number of bits per pixel" | |||
| 753 | regionDepth = 1 << bs.GetBits(3); // stored as "number of bits per pixel" | |||
| 754 | bs.SkipBits(2); // reserved | |||
| 755 | clutId = bs.GetBits(8); | |||
| 756 | region8bitPixelCode = bs.GetBits(8); | |||
| 757 | region4bitPixelCode = bs.GetBits(4); | |||
| 758 | region2bitPixelCode = bs.GetBits(2); | |||
| 759 | bs.SkipBits(2); // reserved | |||
| 760 | dbgregions("<b>region</b> id %d version %d fill %d width %d height %d level %d depth %d clutId %d<br>\n", regionId, regionVersionNumber, regionFillFlag, regionWidth, regionHeight, regionLevelOfCompatibility, regionDepth, clutId)if (DebugRegions) SD.WriteHtml("<b>region</b> id %d version %d fill %d width %d height %d level %d depth %d clutId %d<br>\n" , regionId, regionVersionNumber, regionFillFlag, regionWidth, regionHeight, regionLevelOfCompatibility, regionDepth, clutId ); | |||
| 761 | // no objectRefs.Clear() here! | |||
| 762 | while (!bs.IsEOF()) | |||
| 763 | objectRefs.Add(new cSubtitleObjectRef(bs)); | |||
| 764 | } | |||
| 765 | ||||
| 766 | void cSubtitleRegion::Render(cBitmap *Bitmap, cSubtitleObjects *Objects) | |||
| 767 | { | |||
| 768 | if (regionFillFlag) { | |||
| 769 | switch (Bitmap->Bpp()) { | |||
| 770 | case 2: Bitmap->Fill(region2bitPixelCode); break; | |||
| 771 | case 4: Bitmap->Fill(region4bitPixelCode); break; | |||
| 772 | case 8: Bitmap->Fill(region8bitPixelCode); break; | |||
| 773 | default: dbgregions("unknown bpp %d (%s %d)<br>\n", Bitmap->Bpp(), __FUNCTION__, __LINE__)if (DebugRegions) SD.WriteHtml("unknown bpp %d (%s %d)<br>\n" , Bitmap->Bpp(), __FUNCTION__, 773); | |||
| 774 | } | |||
| 775 | } | |||
| 776 | for (cSubtitleObjectRef *sor = objectRefs.First(); sor; sor = objectRefs.Next(sor)) { | |||
| 777 | if (cSubtitleObject *so = Objects->GetObjectById(sor->ObjectId())) { | |||
| 778 | so->Render(Bitmap, sor->ObjectHorizontalPosition(), sor->ObjectVerticalPosition(), sor->ForegroundPixelCode(), sor->BackgroundPixelCode()); | |||
| 779 | } | |||
| 780 | } | |||
| 781 | } | |||
| 782 | ||||
| 783 | // --- cSubtitleRegionRef ---------------------------------------------------- | |||
| 784 | ||||
| 785 | class cSubtitleRegionRef : public cListObject { | |||
| 786 | private: | |||
| 787 | int regionId; | |||
| 788 | int regionHorizontalAddress; | |||
| 789 | int regionVerticalAddress; | |||
| 790 | public: | |||
| 791 | cSubtitleRegionRef(cBitStream &bs); | |||
| 792 | int RegionId(void) { return regionId; } | |||
| 793 | int RegionHorizontalAddress(void) { return regionHorizontalAddress; } | |||
| 794 | int RegionVerticalAddress(void) { return regionVerticalAddress; } | |||
| 795 | }; | |||
| 796 | ||||
| 797 | cSubtitleRegionRef::cSubtitleRegionRef(cBitStream &bs) | |||
| 798 | { | |||
| 799 | regionId = bs.GetBits(8); | |||
| 800 | bs.SkipBits(8); // reserved | |||
| 801 | regionHorizontalAddress = bs.GetBits(16); | |||
| 802 | regionVerticalAddress = bs.GetBits(16); | |||
| 803 | dbgpages("<b>regionref</b> id %d tx %d y %d<br>\n", regionId, regionHorizontalAddress, regionVerticalAddress)if (DebugPages) SD.WriteHtml("<b>regionref</b> id %d tx %d y %d<br>\n" , regionId, regionHorizontalAddress, regionVerticalAddress); | |||
| 804 | } | |||
| 805 | ||||
| 806 | // --- cDvbSubtitlePage ------------------------------------------------------ | |||
| 807 | ||||
| 808 | class cDvbSubtitlePage : public cListObject { | |||
| 809 | private: | |||
| 810 | int pageId; | |||
| 811 | int pageTimeout; | |||
| 812 | int pageVersionNumber; | |||
| 813 | int pageState; | |||
| 814 | int64_t pts; | |||
| 815 | bool pending; | |||
| 816 | cSubtitleObjects objects; | |||
| 817 | cList<cSubtitleClut> cluts; | |||
| 818 | cList<cSubtitleRegion> regions; | |||
| 819 | cList<cSubtitleRegionRef> regionRefs; | |||
| 820 | public: | |||
| 821 | cDvbSubtitlePage(int PageId); | |||
| 822 | void Parse(int64_t Pts, cBitStream &bs); | |||
| 823 | int PageId(void) { return pageId; } | |||
| 824 | int PageTimeout(void) { return pageTimeout; } | |||
| 825 | int PageVersionNumber(void) { return pageVersionNumber; } | |||
| 826 | int PageState(void) { return pageState; } | |||
| 827 | int64_t Pts(void) const { return pts; } | |||
| 828 | bool Pending(void) { return pending; } | |||
| 829 | cSubtitleObjects *Objects(void) { return &objects; } | |||
| 830 | tArea *GetAreas(int &NumAreas, double FactorX, double FactorY); | |||
| 831 | cSubtitleObject *GetObjectById(int ObjectId, bool New = false); | |||
| 832 | cSubtitleClut *GetClutById(int ClutId, bool New = false); | |||
| 833 | cSubtitleRegion *GetRegionById(int RegionId, bool New = false); | |||
| 834 | cSubtitleRegionRef *GetRegionRefByIndex(int RegionRefIndex) { return regionRefs.Get(RegionRefIndex); } | |||
| 835 | void SetPending(bool Pending) { pending = Pending; } | |||
| 836 | }; | |||
| 837 | ||||
| 838 | cDvbSubtitlePage::cDvbSubtitlePage(int PageId) | |||
| 839 | { | |||
| 840 | pageId = PageId; | |||
| 841 | pageTimeout = 0; | |||
| 842 | pageVersionNumber = -1; | |||
| 843 | pageState = -1; | |||
| 844 | pts = -1; | |||
| 845 | pending = false; | |||
| 846 | } | |||
| 847 | ||||
| 848 | void cDvbSubtitlePage::Parse(int64_t Pts, cBitStream &bs) | |||
| 849 | { | |||
| 850 | if (Pts >= 0) | |||
| 851 | pts = Pts; | |||
| 852 | pageTimeout = bs.GetBits(8); | |||
| 853 | int Version = bs.GetBits(4); | |||
| 854 | if (pageVersionNumber == Version) | |||
| 855 | return; // no update | |||
| 856 | pageVersionNumber = Version; | |||
| 857 | pageState = bs.GetBits(2); | |||
| 858 | switch (pageState) { | |||
| 859 | case 0: // normal case - page update | |||
| 860 | break; | |||
| 861 | case 1: // acquisition point - page refresh | |||
| 862 | regions.Clear(); | |||
| 863 | objects.Clear(); | |||
| 864 | break; | |||
| 865 | case 2: // mode change - new page | |||
| 866 | regions.Clear(); | |||
| 867 | cluts.Clear(); | |||
| 868 | objects.Clear(); | |||
| 869 | break; | |||
| 870 | case 3: // reserved | |||
| 871 | break; | |||
| 872 | default: dbgpages("unknown page state: %d<br>\n", pageState)if (DebugPages) SD.WriteHtml("unknown page state: %d<br>\n" , pageState); | |||
| 873 | } | |||
| 874 | bs.SkipBits(2); // reserved | |||
| 875 | dbgpages("<hr>\n<b>page</b> id %d version %d pts %"PRId64" timeout %d state %d<br>\n", pageId, pageVersionNumber, pts, pageTimeout, pageState)if (DebugPages) SD.WriteHtml("<hr>\n<b>page</b> id %d version %d pts %" "l" "d"" timeout %d state %d<br>\n", pageId, pageVersionNumber , pts, pageTimeout, pageState); | |||
| 876 | regionRefs.Clear(); | |||
| 877 | while (!bs.IsEOF()) | |||
| 878 | regionRefs.Add(new cSubtitleRegionRef(bs)); | |||
| 879 | pending = true; | |||
| 880 | } | |||
| 881 | ||||
| 882 | tArea *cDvbSubtitlePage::GetAreas(int &NumAreas, double FactorX, double FactorY) | |||
| 883 | { | |||
| 884 | if (regions.Count() > 0) { | |||
| 885 | NumAreas = regionRefs.Count(); | |||
| 886 | tArea *Areas = new tArea[NumAreas]; | |||
| 887 | tArea *a = Areas; | |||
| 888 | for (cSubtitleRegionRef *srr = regionRefs.First(); srr; srr = regionRefs.Next(srr)) { | |||
| 889 | if (cSubtitleRegion *sr = GetRegionById(srr->RegionId())) { | |||
| 890 | a->x1 = int(round(FactorX * srr->RegionHorizontalAddress())); | |||
| 891 | a->y1 = int(round(FactorY * srr->RegionVerticalAddress())); | |||
| 892 | a->x2 = int(round(FactorX * (srr->RegionHorizontalAddress() + sr->RegionWidth() - 1))); | |||
| 893 | a->y2 = int(round(FactorY * (srr->RegionVerticalAddress() + sr->RegionHeight() - 1))); | |||
| 894 | a->bpp = sr->RegionDepth(); | |||
| 895 | while ((a->Width() & 3) != 0) | |||
| 896 | a->x2++; // aligns width to a multiple of 4, so 2, 4 and 8 bpp will work | |||
| 897 | } | |||
| 898 | else | |||
| 899 | a->x1 = a->y1 = a->x2 = a->y2 = a->bpp = 0; | |||
| 900 | a++; | |||
| 901 | } | |||
| 902 | return Areas; | |||
| 903 | } | |||
| 904 | NumAreas = 0; | |||
| 905 | return NULL__null; | |||
| 906 | } | |||
| 907 | ||||
| 908 | cSubtitleClut *cDvbSubtitlePage::GetClutById(int ClutId, bool New) | |||
| 909 | { | |||
| 910 | for (cSubtitleClut *sc = cluts.First(); sc; sc = cluts.Next(sc)) { | |||
| 911 | if (sc->ClutId() == ClutId) | |||
| 912 | return sc; | |||
| 913 | } | |||
| 914 | if (!New) | |||
| 915 | return NULL__null; | |||
| 916 | cSubtitleClut *Clut = new cSubtitleClut(ClutId); | |||
| 917 | cluts.Add(Clut); | |||
| 918 | return Clut; | |||
| 919 | } | |||
| 920 | ||||
| 921 | cSubtitleRegion *cDvbSubtitlePage::GetRegionById(int RegionId, bool New) | |||
| 922 | { | |||
| 923 | for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) { | |||
| 924 | if (sr->RegionId() == RegionId) | |||
| 925 | return sr; | |||
| 926 | } | |||
| 927 | if (!New) | |||
| 928 | return NULL__null; | |||
| 929 | cSubtitleRegion *Region = new cSubtitleRegion(RegionId); | |||
| 930 | regions.Add(Region); | |||
| 931 | return Region; | |||
| 932 | } | |||
| 933 | ||||
| 934 | cSubtitleObject *cDvbSubtitlePage::GetObjectById(int ObjectId, bool New) | |||
| 935 | { | |||
| 936 | return objects.GetObjectById(ObjectId, New); | |||
| 937 | } | |||
| 938 | ||||
| 939 | // --- cDvbSubtitleAssembler ------------------------------------------------- | |||
| 940 | ||||
| 941 | class cDvbSubtitleAssembler { | |||
| 942 | private: | |||
| 943 | uchar *data; | |||
| 944 | int length; | |||
| 945 | int pos; | |||
| 946 | int size; | |||
| 947 | bool Realloc(int Size); | |||
| 948 | public: | |||
| 949 | cDvbSubtitleAssembler(void); | |||
| 950 | virtual ~cDvbSubtitleAssembler(); | |||
| 951 | void Reset(void); | |||
| 952 | unsigned char *Get(int &Length); | |||
| 953 | void Put(const uchar *Data, int Length); | |||
| 954 | }; | |||
| 955 | ||||
| 956 | cDvbSubtitleAssembler::cDvbSubtitleAssembler(void) | |||
| 957 | { | |||
| 958 | data = NULL__null; | |||
| 959 | size = 0; | |||
| 960 | Reset(); | |||
| 961 | } | |||
| 962 | ||||
| 963 | cDvbSubtitleAssembler::~cDvbSubtitleAssembler() | |||
| 964 | { | |||
| 965 | free(data); | |||
| 966 | } | |||
| 967 | ||||
| 968 | void cDvbSubtitleAssembler::Reset(void) | |||
| 969 | { | |||
| 970 | length = 0; | |||
| 971 | pos = 0; | |||
| 972 | } | |||
| 973 | ||||
| 974 | bool cDvbSubtitleAssembler::Realloc(int Size) | |||
| 975 | { | |||
| 976 | if (Size > size) { | |||
| 977 | Size = max(Size, 2048); | |||
| 978 | if (uchar *NewBuffer = (uchar *)realloc(data, Size)) { | |||
| 979 | size = Size; | |||
| 980 | data = NewBuffer; | |||
| 981 | } | |||
| 982 | else { | |||
| 983 | esyslog("ERROR: can't allocate memory for subtitle assembler")void( (SysLogLevel > 0) ? syslog_with_tid(3, "ERROR: can't allocate memory for subtitle assembler" ) : void() ); | |||
| 984 | length = 0; | |||
| 985 | size = 0; | |||
| 986 | free(data); | |||
| 987 | data = NULL__null; | |||
| 988 | return false; | |||
| 989 | } | |||
| 990 | } | |||
| 991 | return true; | |||
| 992 | } | |||
| 993 | ||||
| 994 | unsigned char *cDvbSubtitleAssembler::Get(int &Length) | |||
| 995 | { | |||
| 996 | if (length > pos + 5) { | |||
| 997 | Length = (data[pos + 4] << 8) + data[pos + 5] + 6; | |||
| ||||
| 998 | if (length >= pos + Length) { | |||
| 999 | unsigned char *result = data + pos; | |||
| 1000 | pos += Length; | |||
| 1001 | return result; | |||
| 1002 | } | |||
| 1003 | } | |||
| 1004 | return NULL__null; | |||
| 1005 | } | |||
| 1006 | ||||
| 1007 | void cDvbSubtitleAssembler::Put(const uchar *Data, int Length) | |||
| 1008 | { | |||
| 1009 | if (Length && Realloc(length + Length)) { | |||
| 1010 | memcpy(data + length, Data, Length); | |||
| 1011 | length += Length; | |||
| 1012 | } | |||
| 1013 | } | |||
| 1014 | ||||
| 1015 | // --- cDvbSubtitleBitmaps --------------------------------------------------- | |||
| 1016 | ||||
| 1017 | class cDvbSubtitleBitmaps : public cListObject { | |||
| 1018 | private: | |||
| 1019 | int state; | |||
| 1020 | int64_t pts; | |||
| 1021 | int timeout; | |||
| 1022 | tArea *areas; | |||
| 1023 | int numAreas; | |||
| 1024 | double osdFactorX; | |||
| 1025 | double osdFactorY; | |||
| 1026 | cVector<cBitmap *> bitmaps; | |||
| 1027 | public: | |||
| 1028 | cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY); | |||
| 1029 | ~cDvbSubtitleBitmaps(); | |||
| 1030 | int State(void) { return state; } | |||
| 1031 | int64_t Pts(void) { return pts; } | |||
| 1032 | int Timeout(void) { return timeout; } | |||
| 1033 | void AddBitmap(cBitmap *Bitmap); | |||
| 1034 | bool HasBitmaps(void) { return bitmaps.Size(); } | |||
| 1035 | void Draw(cOsd *Osd); | |||
| 1036 | void DbgDump(int WindowWidth, int WindowHeight); | |||
| 1037 | }; | |||
| 1038 | ||||
| 1039 | cDvbSubtitleBitmaps::cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY) | |||
| 1040 | { | |||
| 1041 | state = State; | |||
| 1042 | pts = Pts; | |||
| 1043 | timeout = Timeout; | |||
| 1044 | areas = Areas; | |||
| 1045 | numAreas = NumAreas; | |||
| 1046 | osdFactorX = OsdFactorX; | |||
| 1047 | osdFactorY = OsdFactorY; | |||
| 1048 | } | |||
| 1049 | ||||
| 1050 | cDvbSubtitleBitmaps::~cDvbSubtitleBitmaps() | |||
| 1051 | { | |||
| 1052 | delete[] areas; | |||
| 1053 | for (int i = 0; i < bitmaps.Size(); i++) | |||
| 1054 | delete bitmaps[i]; | |||
| 1055 | } | |||
| 1056 | ||||
| 1057 | void cDvbSubtitleBitmaps::AddBitmap(cBitmap *Bitmap) | |||
| 1058 | { | |||
| 1059 | bitmaps.Append(Bitmap); | |||
| 1060 | } | |||
| 1061 | ||||
| 1062 | void cDvbSubtitleBitmaps::Draw(cOsd *Osd) | |||
| 1063 | { | |||
| 1064 | bool Scale = !(DoubleEqual(osdFactorX, 1.0) && DoubleEqual(osdFactorY, 1.0)); | |||
| 1065 | bool AntiAlias = true; | |||
| 1066 | if (Scale && osdFactorX > 1.0 || osdFactorY > 1.0) { | |||
| 1067 | // Upscaling requires 8bpp: | |||
| 1068 | int Bpp[MAXOSDAREAS16]; | |||
| 1069 | for (int i = 0; i < numAreas; i++) { | |||
| 1070 | Bpp[i] = areas[i].bpp; | |||
| 1071 | areas[i].bpp = 8; | |||
| 1072 | } | |||
| 1073 | if (Osd->CanHandleAreas(areas, numAreas) != oeOk) { | |||
| 1074 | for (int i = 0; i < numAreas; i++) | |||
| 1075 | areas[i].bpp = Bpp[i]; | |||
| 1076 | AntiAlias = false; | |||
| 1077 | } | |||
| 1078 | } | |||
| 1079 | if (State() == 0 || Osd->SetAreas(areas, numAreas) == oeOk) { | |||
| 1080 | for (int i = 0; i < bitmaps.Size(); i++) { | |||
| 1081 | cBitmap *b = bitmaps[i]; | |||
| 1082 | if (Scale) | |||
| 1083 | b = b->Scaled(osdFactorX, osdFactorY, AntiAlias); | |||
| 1084 | Osd->DrawBitmap(int(round(b->X0() * osdFactorX)), int(round(b->Y0() * osdFactorY)), *b); | |||
| 1085 | if (b != bitmaps[i]) | |||
| 1086 | delete b; | |||
| 1087 | } | |||
| 1088 | Osd->Flush(); | |||
| 1089 | } | |||
| 1090 | } | |||
| 1091 | ||||
| 1092 | void cDvbSubtitleBitmaps::DbgDump(int WindowWidth, int WindowHeight) | |||
| 1093 | { | |||
| 1094 | if (!SD.Active()) | |||
| 1095 | return; | |||
| 1096 | SD.SetFirstPts(Pts()); | |||
| 1097 | double STC = double(cDevice::PrimaryDevice()->GetSTC() - SD.FirstPts()) / 90000; | |||
| 1098 | double Start = double(Pts() - SD.FirstPts()) / 90000; | |||
| 1099 | double Duration = Timeout(); | |||
| 1100 | double End = Start + Duration; | |||
| 1101 | cBitmap Bitmap(WindowWidth, WindowHeight, 8); | |||
| 1102 | #define DBGBACKGROUND0xA0A0A0 0xA0A0A0 | |||
| 1103 | Bitmap.DrawRectangle(0, 0, WindowWidth - 1, WindowHeight - 1, DBGBACKGROUND0xA0A0A0); | |||
| 1104 | for (int i = 0; i < bitmaps.Size(); i++) { | |||
| 1105 | cBitmap *b = bitmaps[i]; | |||
| 1106 | Bitmap.DrawBitmap(b->X0(), b->Y0(), *b); | |||
| 1107 | } | |||
| 1108 | cString ImgName = SD.WriteJpeg(&Bitmap); | |||
| 1109 | #define BORDER //" border=1" | |||
| 1110 | SD.WriteHtml("<p>%s<br>", State() == 0 ? "page update" : State() == 1 ? "page refresh" : State() == 2 ? "new page" : "???"); | |||
| 1111 | SD.WriteHtml("<table" BORDER "><tr><td>"); | |||
| 1112 | SD.WriteHtml("%.2f", STC); | |||
| 1113 | SD.WriteHtml("</td><td>"); | |||
| 1114 | SD.WriteHtml("<img src=\"%s\">", *ImgName); | |||
| 1115 | SD.WriteHtml("</td><td style=\"height:100%%\"><table" BORDER " style=\"height:100%%\">"); | |||
| 1116 | SD.WriteHtml("<tr><td valign=top><b>%.2f</b></td></tr>", Start); | |||
| 1117 | SD.WriteHtml("<tr><td valign=middle>%.2f</td></tr>", Duration); | |||
| 1118 | SD.WriteHtml("<tr><td valign=bottom>%.2f</td></tr>", End); | |||
| 1119 | SD.WriteHtml("</table></td>"); | |||
| 1120 | SD.WriteHtml("</tr></table>\n"); | |||
| 1121 | } | |||
| 1122 | ||||
| 1123 | // --- cDvbSubtitleConverter ------------------------------------------------- | |||
| 1124 | ||||
| 1125 | int cDvbSubtitleConverter::setupLevel = 0; | |||
| 1126 | ||||
| 1127 | cDvbSubtitleConverter::cDvbSubtitleConverter(void) | |||
| 1128 | :cThread("subtitleConverter") | |||
| 1129 | { | |||
| 1130 | dvbSubtitleAssembler = new cDvbSubtitleAssembler; | |||
| 1131 | osd = NULL__null; | |||
| 1132 | frozen = false; | |||
| 1133 | ddsVersionNumber = -1; | |||
| 1134 | displayWidth = windowWidth = 720; | |||
| 1135 | displayHeight = windowHeight = 576; | |||
| 1136 | windowHorizontalOffset = 0; | |||
| 1137 | windowVerticalOffset = 0; | |||
| 1138 | pages = new cList<cDvbSubtitlePage>; | |||
| 1139 | bitmaps = new cList<cDvbSubtitleBitmaps>; | |||
| 1140 | SD.Reset(); | |||
| 1141 | Start(); | |||
| 1142 | } | |||
| 1143 | ||||
| 1144 | cDvbSubtitleConverter::~cDvbSubtitleConverter() | |||
| 1145 | { | |||
| 1146 | Cancel(3); | |||
| 1147 | delete dvbSubtitleAssembler; | |||
| 1148 | delete osd; | |||
| 1149 | delete bitmaps; | |||
| 1150 | delete pages; | |||
| 1151 | } | |||
| 1152 | ||||
| 1153 | void cDvbSubtitleConverter::SetupChanged(void) | |||
| 1154 | { | |||
| 1155 | setupLevel++; | |||
| 1156 | } | |||
| 1157 | ||||
| 1158 | void cDvbSubtitleConverter::Reset(void) | |||
| 1159 | { | |||
| 1160 | dbgconverter("converter reset -----------------------<br>\n")if (DebugConverter) SD.WriteHtml("converter reset -----------------------<br>\n" ); | |||
| 1161 | dvbSubtitleAssembler->Reset(); | |||
| 1162 | Lock(); | |||
| 1163 | pages->Clear(); | |||
| 1164 | bitmaps->Clear(); | |||
| 1165 | DELETENULL(osd); | |||
| 1166 | frozen = false; | |||
| 1167 | ddsVersionNumber = -1; | |||
| 1168 | displayWidth = windowWidth = 720; | |||
| 1169 | displayHeight = windowHeight = 576; | |||
| 1170 | windowHorizontalOffset = 0; | |||
| 1171 | windowVerticalOffset = 0; | |||
| 1172 | Unlock(); | |||
| 1173 | } | |||
| 1174 | ||||
| 1175 | int cDvbSubtitleConverter::ConvertFragments(const uchar *Data, int Length) | |||
| 1176 | { | |||
| 1177 | if (Data && Length > 8) { | |||
| ||||
| 1178 | int PayloadOffset = PesPayloadOffset(Data); | |||
| 1179 | int SubstreamHeaderLength = 4; | |||
| 1180 | bool ResetSubtitleAssembler = Data[PayloadOffset + 3] == 0x00; | |||
| 1181 | ||||
| 1182 | // Compatibility mode for old subtitles plugin: | |||
| 1183 | if ((Data[7] & 0x01) && (Data[PayloadOffset - 3] & 0x81) == 0x01 && Data[PayloadOffset - 2] == 0x81) { | |||
| 1184 | PayloadOffset--; | |||
| 1185 | SubstreamHeaderLength = 1; | |||
| 1186 | ResetSubtitleAssembler = Data[8] >= 5; | |||
| 1187 | } | |||
| 1188 | ||||
| 1189 | if (Length > PayloadOffset + SubstreamHeaderLength) { | |||
| 1190 | int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1; | |||
| 1191 | if (pts >= 0) | |||
| 1192 | dbgconverter("converter PTS: %"PRId64"<br>\n", pts)if (DebugConverter) SD.WriteHtml("converter PTS: %""l" "d""<br>\n" , pts); | |||
| 1193 | const uchar *data = Data + PayloadOffset + SubstreamHeaderLength; // skip substream header | |||
| 1194 | int length = Length - PayloadOffset - SubstreamHeaderLength; // skip substream header | |||
| 1195 | if (ResetSubtitleAssembler) | |||
| 1196 | dvbSubtitleAssembler->Reset(); | |||
| 1197 | ||||
| 1198 | if (length > 3) { | |||
| 1199 | if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) | |||
| 1200 | dvbSubtitleAssembler->Put(data + 2, length - 2); | |||
| 1201 | else | |||
| 1202 | dvbSubtitleAssembler->Put(data, length); | |||
| 1203 | ||||
| 1204 | int Count; | |||
| 1205 | while (true) { | |||
| 1206 | unsigned char *b = dvbSubtitleAssembler->Get(Count); | |||
| 1207 | if (b && b[0] == 0x0F) { | |||
| 1208 | if (ExtractSegment(b, Count, pts) == -1) | |||
| 1209 | break; | |||
| 1210 | } | |||
| 1211 | else | |||
| 1212 | break; | |||
| 1213 | } | |||
| 1214 | } | |||
| 1215 | } | |||
| 1216 | return Length; | |||
| 1217 | } | |||
| 1218 | return 0; | |||
| 1219 | } | |||
| 1220 | ||||
| 1221 | int cDvbSubtitleConverter::Convert(const uchar *Data, int Length) | |||
| 1222 | { | |||
| 1223 | if (Data && Length > 8) { | |||
| 1224 | int PayloadOffset = PesPayloadOffset(Data); | |||
| 1225 | if (Length > PayloadOffset) { | |||
| 1226 | int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1; | |||
| 1227 | if (pts >= 0) | |||
| 1228 | dbgconverter("converter PTS: %"PRId64"<br>\n", pts)if (DebugConverter) SD.WriteHtml("converter PTS: %""l" "d""<br>\n" , pts); | |||
| 1229 | const uchar *data = Data + PayloadOffset; | |||
| 1230 | int length = Length - PayloadOffset; | |||
| 1231 | if (length > 3) { | |||
| 1232 | if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) { | |||
| 1233 | data += 2; | |||
| 1234 | length -= 2; | |||
| 1235 | } | |||
| 1236 | const uchar *b = data; | |||
| 1237 | while (length > 0) { | |||
| 1238 | if (b[0] == 0x0F) { | |||
| 1239 | int n = ExtractSegment(b, length, pts); | |||
| 1240 | if (n < 0) | |||
| 1241 | break; | |||
| 1242 | b += n; | |||
| 1243 | length -= n; | |||
| 1244 | } | |||
| 1245 | else | |||
| 1246 | break; | |||
| 1247 | } | |||
| 1248 | } | |||
| 1249 | } | |||
| 1250 | return Length; | |||
| 1251 | } | |||
| 1252 | return 0; | |||
| 1253 | } | |||
| 1254 | ||||
| 1255 | #define LimitTo32Bit(n)((n) & 0x00000000FFFFFFFFL) ((n) & 0x00000000FFFFFFFFL) | |||
| 1256 | ||||
| 1257 | void cDvbSubtitleConverter::Action(void) | |||
| 1258 | { | |||
| 1259 | int LastSetupLevel = setupLevel; | |||
| 1260 | cTimeMs Timeout; | |||
| 1261 | while (Running()) { | |||
| 1262 | int WaitMs = 100; | |||
| 1263 | if (!frozen) { | |||
| 1264 | LOCK_THREADcThreadLock ThreadLock(this); | |||
| 1265 | if (osd) { | |||
| 1266 | int NewSetupLevel = setupLevel; | |||
| 1267 | if (Timeout.TimedOut() || LastSetupLevel != NewSetupLevel) { | |||
| 1268 | dbgoutput("closing osd<br>\n")if (DebugOutput) SD.WriteHtml("closing osd<br>\n"); | |||
| 1269 | DELETENULL(osd); | |||
| 1270 | } | |||
| 1271 | LastSetupLevel = NewSetupLevel; | |||
| 1272 | } | |||
| 1273 | for (cDvbSubtitleBitmaps *sb = bitmaps->First(); sb; sb = bitmaps->Next(sb)) { | |||
| 1274 | // Calculate the Delta between the STC (the current timestamp of the video) | |||
| 1275 | // and the bitmap's PTS (the timestamp when the bitmap shall be presented). | |||
| 1276 | // A negative Delta means that the bitmap will be presented in the future: | |||
| 1277 | int64_t STC = cDevice::PrimaryDevice()->GetSTC(); | |||
| 1278 | int64_t Delta = LimitTo32Bit(STC)((STC) & 0x00000000FFFFFFFFL) - LimitTo32Bit(sb->Pts())((sb->Pts()) & 0x00000000FFFFFFFFL); // some devices only deliver 32 bits | |||
| 1279 | if (Delta > (int64_t(1) << 31)) | |||
| 1280 | Delta -= (int64_t(1) << 32); | |||
| 1281 | else if (Delta < -((int64_t(1) << 31) - 1)) | |||
| 1282 | Delta += (int64_t(1) << 32); | |||
| 1283 | Delta /= 90; // STC and PTS are in 1/90000s | |||
| 1284 | if (Delta >= 0) { // found a bitmap that shall be displayed... | |||
| 1285 | if (Delta < sb->Timeout() * 1000) { // ...and has not timed out yet | |||
| 1286 | if (!sb->HasBitmaps()) { | |||
| 1287 | Timeout.Set(); | |||
| 1288 | WaitMs = 0; | |||
| 1289 | } | |||
| 1290 | else if (AssertOsd()) { | |||
| 1291 | dbgoutput("showing bitmap #%d of %d<br>\n", sb->Index() + 1, bitmaps->Count())if (DebugOutput) SD.WriteHtml("showing bitmap #%d of %d<br>\n" , sb->Index() + 1, bitmaps->Count()); | |||
| 1292 | sb->Draw(osd); | |||
| 1293 | Timeout.Set(sb->Timeout() * 1000); | |||
| 1294 | dbgconverter("PTS: %"PRId64" STC: %"PRId64" (%"PRId64") timeout: %d<br>\n", sb->Pts(), STC, Delta, sb->Timeout())if (DebugConverter) SD.WriteHtml("PTS: %""l" "d"" STC: %""l" "d"" (%""l" "d"") timeout: %d<br>\n", sb->Pts(), STC , Delta, sb->Timeout()); | |||
| 1295 | } | |||
| 1296 | } | |||
| 1297 | else | |||
| 1298 | WaitMs = 0; // bitmap already timed out, so try next one immediately | |||
| 1299 | dbgoutput("deleting bitmap #%d of %d<br>\n", sb->Index() + 1, bitmaps->Count())if (DebugOutput) SD.WriteHtml("deleting bitmap #%d of %d<br>\n" , sb->Index() + 1, bitmaps->Count()); | |||
| 1300 | bitmaps->Del(sb); | |||
| 1301 | break; | |||
| 1302 | } | |||
| 1303 | } | |||
| 1304 | } | |||
| 1305 | cCondWait::SleepMs(WaitMs); | |||
| 1306 | } | |||
| 1307 | } | |||
| 1308 | ||||
| 1309 | void cDvbSubtitleConverter::SetOsdData(void) | |||
| 1310 | { | |||
| 1311 | int OsdWidth, OsdHeight; | |||
| 1312 | double OsdAspect; | |||
| 1313 | int VideoWidth, VideoHeight; | |||
| 1314 | double VideoAspect; | |||
| 1315 | cDevice::PrimaryDevice()->GetOsdSize(OsdWidth, OsdHeight, OsdAspect); | |||
| 1316 | cDevice::PrimaryDevice()->GetVideoSize(VideoWidth, VideoHeight, VideoAspect); | |||
| 1317 | if (OsdWidth == displayWidth && OsdHeight == displayHeight) { | |||
| 1318 | osdFactorX = osdFactorY = 1.0; | |||
| 1319 | osdDeltaX = osdDeltaY = 0; | |||
| 1320 | } | |||
| 1321 | else { | |||
| 1322 | osdFactorX = VideoAspect * OsdHeight / displayWidth; | |||
| 1323 | osdFactorY = double(OsdHeight) / displayHeight; | |||
| 1324 | osdDeltaX = (OsdWidth - displayWidth * osdFactorX) / 2; | |||
| 1325 | osdDeltaY = (OsdHeight - displayHeight * osdFactorY) / 2; | |||
| 1326 | } | |||
| 1327 | } | |||
| 1328 | ||||
| 1329 | bool cDvbSubtitleConverter::AssertOsd(void) | |||
| 1330 | { | |||
| 1331 | LOCK_THREADcThreadLock ThreadLock(this); | |||
| 1332 | if (!osd) { | |||
| 1333 | SetOsdData(); | |||
| 1334 | osd = cOsdProvider::NewOsd(int(round(osdFactorX * windowHorizontalOffset + osdDeltaX)), int(round(osdFactorY * windowVerticalOffset + osdDeltaY)) + Setup.SubtitleOffset, OSD_LEVEL_SUBTITLES10); | |||
| 1335 | } | |||
| 1336 | return osd != NULL__null; | |||
| 1337 | } | |||
| 1338 | ||||
| 1339 | cDvbSubtitlePage *cDvbSubtitleConverter::GetPageById(int PageId, bool New) | |||
| 1340 | { | |||
| 1341 | for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) { | |||
| 1342 | if (sp->PageId() == PageId) | |||
| 1343 | return sp; | |||
| 1344 | } | |||
| 1345 | if (!New) | |||
| 1346 | return NULL__null; | |||
| 1347 | cDvbSubtitlePage *Page = new cDvbSubtitlePage(PageId); | |||
| 1348 | pages->Add(Page); | |||
| 1349 | return Page; | |||
| 1350 | } | |||
| 1351 | ||||
| 1352 | int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t Pts) | |||
| 1353 | { | |||
| 1354 | cBitStream bs(Data, Length * 8); | |||
| 1355 | if (Length > 5 && bs.GetBits(8) == 0x0F) { // sync byte | |||
| 1356 | int segmentType = bs.GetBits(8); | |||
| 1357 | if (segmentType == STUFFING_SEGMENT0xFF) | |||
| 1358 | return -1; | |||
| 1359 | LOCK_THREADcThreadLock ThreadLock(this); | |||
| 1360 | cDvbSubtitlePage *page = GetPageById(bs.GetBits(16), true); | |||
| 1361 | int segmentLength = bs.GetBits(16); | |||
| 1362 | if (!bs.SetLength(bs.Index() + segmentLength * 8)) | |||
| 1363 | return -1; | |||
| 1364 | switch (segmentType) { | |||
| 1365 | case PAGE_COMPOSITION_SEGMENT0x10: { | |||
| 1366 | if (page->Pending()) { | |||
| 1367 | dbgsegments("END_OF_DISPLAY_SET_SEGMENT (simulated)<br>\n")if (DebugSegments) SD.WriteHtml("END_OF_DISPLAY_SET_SEGMENT (simulated)<br>\n" ); | |||
| 1368 | FinishPage(page); | |||
| 1369 | } | |||
| 1370 | dbgsegments("PAGE_COMPOSITION_SEGMENT<br>\n")if (DebugSegments) SD.WriteHtml("PAGE_COMPOSITION_SEGMENT<br>\n" ); | |||
| 1371 | page->Parse(Pts, bs); | |||
| 1372 | SD.SetFactor(double(DBGBITMAPWIDTH400) / windowWidth); | |||
| 1373 | break; | |||
| 1374 | } | |||
| 1375 | case REGION_COMPOSITION_SEGMENT0x11: { | |||
| 1376 | dbgsegments("REGION_COMPOSITION_SEGMENT<br>\n")if (DebugSegments) SD.WriteHtml("REGION_COMPOSITION_SEGMENT<br>\n" ); | |||
| 1377 | cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8), true); | |||
| 1378 | region->Parse(bs); | |||
| 1379 | break; | |||
| 1380 | } | |||
| 1381 | case CLUT_DEFINITION_SEGMENT0x12: { | |||
| 1382 | dbgsegments("CLUT_DEFINITION_SEGMENT<br>\n")if (DebugSegments) SD.WriteHtml("CLUT_DEFINITION_SEGMENT<br>\n" ); | |||
| 1383 | cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true); | |||
| 1384 | clut->Parse(bs); | |||
| 1385 | break; | |||
| 1386 | } | |||
| 1387 | case OBJECT_DATA_SEGMENT0x13: { | |||
| 1388 | dbgsegments("OBJECT_DATA_SEGMENT<br>\n")if (DebugSegments) SD.WriteHtml("OBJECT_DATA_SEGMENT<br>\n" ); | |||
| 1389 | cSubtitleObject *object = page->GetObjectById(bs.GetBits(16), true); | |||
| 1390 | object->Parse(bs); | |||
| 1391 | break; | |||
| 1392 | } | |||
| 1393 | case DISPLAY_DEFINITION_SEGMENT0x14: { | |||
| 1394 | dbgsegments("DISPLAY_DEFINITION_SEGMENT<br>\n")if (DebugSegments) SD.WriteHtml("DISPLAY_DEFINITION_SEGMENT<br>\n" ); | |||
| 1395 | int version = bs.GetBits(4); | |||
| 1396 | if (version != ddsVersionNumber) { | |||
| 1397 | bool displayWindowFlag = bs.GetBit(); | |||
| 1398 | windowHorizontalOffset = 0; | |||
| 1399 | windowVerticalOffset = 0; | |||
| 1400 | bs.SkipBits(3); // reserved | |||
| 1401 | displayWidth = windowWidth = bs.GetBits(16) + 1; | |||
| 1402 | displayHeight = windowHeight = bs.GetBits(16) + 1; | |||
| 1403 | if (displayWindowFlag) { | |||
| 1404 | windowHorizontalOffset = bs.GetBits(16); // displayWindowHorizontalPositionMinimum | |||
| 1405 | windowWidth = bs.GetBits(16) - windowHorizontalOffset + 1; // displayWindowHorizontalPositionMaximum | |||
| 1406 | windowVerticalOffset = bs.GetBits(16); // displayWindowVerticalPositionMinimum | |||
| 1407 | windowHeight = bs.GetBits(16) - windowVerticalOffset + 1; // displayWindowVerticalPositionMaximum | |||
| 1408 | } | |||
| 1409 | SetOsdData(); | |||
| 1410 | ddsVersionNumber = version; | |||
| 1411 | dbgdisplay("<b>display</b> version %d flag %d width %d height %d ofshor %d ofsver %d<br>\n", ddsVersionNumber, displayWindowFlag, windowWidth, windowHeight, windowHorizontalOffset, windowVerticalOffset)if (DebugDisplay) SD.WriteHtml("<b>display</b> version %d flag %d width %d height %d ofshor %d ofsver %d<br>\n" , ddsVersionNumber, displayWindowFlag, windowWidth, windowHeight , windowHorizontalOffset, windowVerticalOffset); | |||
| 1412 | } | |||
| 1413 | break; | |||
| 1414 | } | |||
| 1415 | case DISPARITY_SIGNALING_SEGMENT0x15: { | |||
| 1416 | dbgsegments("DISPARITY_SIGNALING_SEGMENT<br>\n")if (DebugSegments) SD.WriteHtml("DISPARITY_SIGNALING_SEGMENT<br>\n" ); | |||
| 1417 | bs.SkipBits(4); // dss_version_number | |||
| 1418 | bool disparity_shift_update_sequence_page_flag = bs.GetBit(); | |||
| 1419 | bs.SkipBits(3); // reserved | |||
| 1420 | bs.SkipBits(8); // page_default_disparity_shift | |||
| 1421 | if (disparity_shift_update_sequence_page_flag) { | |||
| 1422 | bs.SkipBits(8); // disparity_shift_update_sequence_length | |||
| 1423 | bs.SkipBits(24); // interval_duration[23..0] | |||
| 1424 | int division_period_count = bs.GetBits(8); | |||
| 1425 | for (int i = 0; i < division_period_count; ++i) { | |||
| 1426 | bs.SkipBits(8); // interval_count | |||
| 1427 | bs.SkipBits(8); // disparity_shift_update_integer_part | |||
| 1428 | } | |||
| 1429 | } | |||
| 1430 | while (!bs.IsEOF()) { | |||
| 1431 | bs.SkipBits(8); // region_id | |||
| 1432 | bool disparity_shift_update_sequence_region_flag = bs.GetBit(); | |||
| 1433 | bs.SkipBits(5); // reserved | |||
| 1434 | int number_of_subregions_minus_1 = bs.GetBits(2); | |||
| 1435 | for (int i = 0; i <= number_of_subregions_minus_1; ++i) { | |||
| 1436 | if (number_of_subregions_minus_1 > 0) { | |||
| 1437 | bs.SkipBits(16); // subregion_horizontal_position | |||
| 1438 | bs.SkipBits(16); // subregion_width | |||
| 1439 | } | |||
| 1440 | bs.SkipBits(8); // subregion_disparity_shift_integer_part | |||
| 1441 | bs.SkipBits(4); // subregion_disparity_shift_fractional_part | |||
| 1442 | bs.SkipBits(4); // reserved | |||
| 1443 | if (disparity_shift_update_sequence_region_flag) { | |||
| 1444 | bs.SkipBits(8); // disparity_shift_update_sequence_length | |||
| 1445 | bs.SkipBits(24); // interval_duration[23..0] | |||
| 1446 | int division_period_count = bs.GetBits(8); | |||
| 1447 | for (int i = 0; i < division_period_count; ++i) { | |||
| 1448 | bs.SkipBits(8); // interval_count | |||
| 1449 | bs.SkipBits(8); // disparity_shift_update_integer_part | |||
| 1450 | } | |||
| 1451 | } | |||
| 1452 | } | |||
| 1453 | } | |||
| 1454 | break; | |||
| 1455 | } | |||
| 1456 | case END_OF_DISPLAY_SET_SEGMENT0x80: { | |||
| 1457 | dbgsegments("END_OF_DISPLAY_SET_SEGMENT<br>\n")if (DebugSegments) SD.WriteHtml("END_OF_DISPLAY_SET_SEGMENT<br>\n" ); | |||
| 1458 | FinishPage(page); | |||
| 1459 | page->SetPending(false); | |||
| 1460 | break; | |||
| 1461 | } | |||
| 1462 | default: | |||
| 1463 | dbgsegments("*** unknown segment type: %02X<br>\n", segmentType)if (DebugSegments) SD.WriteHtml("*** unknown segment type: %02X<br>\n" , segmentType); | |||
| 1464 | } | |||
| 1465 | return bs.Length() / 8; | |||
| 1466 | } | |||
| 1467 | return -1; | |||
| 1468 | } | |||
| 1469 | ||||
| 1470 | void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page) | |||
| 1471 | { | |||
| 1472 | if (!AssertOsd()) | |||
| 1473 | return; | |||
| 1474 | int NumAreas; | |||
| 1475 | tArea *Areas = Page->GetAreas(NumAreas, osdFactorX, osdFactorY); | |||
| 1476 | int Bpp = 8; | |||
| 1477 | bool Reduced = false; | |||
| 1478 | while (osd && osd->CanHandleAreas(Areas, NumAreas) != oeOk) { | |||
| 1479 | dbgoutput("CanHandleAreas: %d<br>\n", osd->CanHandleAreas(Areas, NumAreas))if (DebugOutput) SD.WriteHtml("CanHandleAreas: %d<br>\n" , osd->CanHandleAreas(Areas, NumAreas)); | |||
| 1480 | int HalfBpp = Bpp / 2; | |||
| 1481 | if (HalfBpp >= 2) { | |||
| 1482 | for (int i = 0; i < NumAreas; i++) { | |||
| 1483 | if (Areas[i].bpp >= Bpp) { | |||
| 1484 | Areas[i].bpp = HalfBpp; | |||
| 1485 | Reduced = true; | |||
| 1486 | } | |||
| 1487 | } | |||
| 1488 | Bpp = HalfBpp; | |||
| 1489 | } | |||
| 1490 | else | |||
| 1491 | return; // unable to draw bitmaps | |||
| 1492 | } | |||
| 1493 | cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->PageState(), Page->Pts(), Page->PageTimeout(), Areas, NumAreas, osdFactorX, osdFactorY); | |||
| 1494 | bitmaps->Add(Bitmaps); | |||
| 1495 | for (int i = 0; i < NumAreas; i++) { | |||
| 1496 | if (cSubtitleRegionRef *srr = Page->GetRegionRefByIndex(i)) { | |||
| 1497 | if (cSubtitleRegion *sr = Page->GetRegionById(srr->RegionId())) { | |||
| 1498 | if (cSubtitleClut *clut = Page->GetClutById(sr->ClutId())) { | |||
| 1499 | cBitmap *bm = new cBitmap(sr->RegionWidth(), sr->RegionHeight(), sr->RegionDepth()); | |||
| 1500 | bm->Replace(*clut->GetPalette(sr->RegionDepth())); | |||
| 1501 | sr->Render(bm, Page->Objects()); | |||
| 1502 | if (Reduced) { | |||
| 1503 | if (sr->RegionDepth() != Areas[i].bpp) { | |||
| 1504 | if (sr->RegionLevelOfCompatibility() <= Areas[i].bpp) { | |||
| 1505 | //TODO this is untested - didn't have any such subtitle stream | |||
| 1506 | cSubtitleClut *Clut = Page->GetClutById(sr->ClutId()); | |||
| 1507 | dbgregions("reduce region %d bpp %d level %d area bpp %d<br>\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp)if (DebugRegions) SD.WriteHtml("reduce region %d bpp %d level %d area bpp %d<br>\n" , sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility (), Areas[i].bpp); | |||
| 1508 | bm->ReduceBpp(*Clut->GetPalette(sr->RegionDepth())); | |||
| 1509 | } | |||
| 1510 | else { | |||
| 1511 | dbgregions("condense region %d bpp %d level %d area bpp %d<br>\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp)if (DebugRegions) SD.WriteHtml("condense region %d bpp %d level %d area bpp %d<br>\n" , sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility (), Areas[i].bpp); | |||
| 1512 | bm->ShrinkBpp(Areas[i].bpp); | |||
| 1513 | } | |||
| 1514 | } | |||
| 1515 | } | |||
| 1516 | bm->SetOffset(srr->RegionHorizontalAddress(), srr->RegionVerticalAddress()); | |||
| 1517 | Bitmaps->AddBitmap(bm); | |||
| 1518 | } | |||
| 1519 | } | |||
| 1520 | } | |||
| 1521 | } | |||
| 1522 | if (DebugPages) | |||
| 1523 | Bitmaps->DbgDump(windowWidth, windowHeight); | |||
| 1524 | } |