File: | dvbsubtitle.c |
Location: | line 377, column 9 |
Description: | Null pointer passed as an argument to a 'nonnull' parameter |
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 | } |