vdr  1.7.31
dvbsubtitle.c
Go to the documentation of this file.
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 "subtitle plugin" by Pekka Virtanen <pekka.virtanen@sci.fi>
9  *
10  * $Id: dvbsubtitle.c 2.32 2012/05/08 08:17:17 kls Exp $
11  */
12 
13 
14 #include "dvbsubtitle.h"
15 #define __STDC_FORMAT_MACROS // Required for format specifiers
16 #include <inttypes.h>
17 #include "device.h"
18 #include "libsi/si.h"
19 
20 //#define FINISHPAGE_HACK
21 
22 #define PAGE_COMPOSITION_SEGMENT 0x10
23 #define REGION_COMPOSITION_SEGMENT 0x11
24 #define CLUT_DEFINITION_SEGMENT 0x12
25 #define OBJECT_DATA_SEGMENT 0x13
26 #define DISPLAY_DEFINITION_SEGMENT 0x14
27 #define DISPARITY_SIGNALING_SEGMENT 0x15 // DVB BlueBook A156
28 #define END_OF_DISPLAY_SET_SEGMENT 0x80
29 #define STUFFING_SEGMENT 0xFF
30 
31 // Set these to 'true' for debug output:
32 static bool DebugConverter = false;
33 static bool DebugSegments = false;
34 static bool DebugPages = false;
35 static bool DebugRegions = false;
36 static bool DebugObjects = false;
37 static bool DebugCluts = false;
38 
39 #define dbgconverter(a...) if (DebugConverter) fprintf(stderr, a)
40 #define dbgsegments(a...) if (DebugSegments) fprintf(stderr, a)
41 #define dbgpages(a...) if (DebugPages) fprintf(stderr, a)
42 #define dbgregions(a...) if (DebugRegions) fprintf(stderr, a)
43 #define dbgobjects(a...) if (DebugObjects) fprintf(stderr, a)
44 #define dbgcluts(a...) if (DebugCluts) fprintf(stderr, a)
45 
46 // --- cSubtitleClut ---------------------------------------------------------
47 
48 class cSubtitleClut : public cListObject {
49 private:
50  int clutId;
51  int version;
55 public:
56  cSubtitleClut(int ClutId);
57  int ClutId(void) { return clutId; }
58  int Version(void) { return version; }
59  void SetVersion(int Version) { version = Version; }
60  void SetColor(int Bpp, int Index, tColor Color);
61  const cPalette *GetPalette(int Bpp);
62  };
63 
65 :palette2(2)
66 ,palette4(4)
67 ,palette8(8)
68 {
69  int a = 0, r = 0, g = 0, b = 0;
70  clutId = ClutId;
71  version = -1;
72  // ETSI EN 300 743 10.3: 4-entry CLUT default contents
73  palette2.SetColor(0, ArgbToColor( 0, 0, 0, 0));
74  palette2.SetColor(1, ArgbToColor(255, 255, 255, 255));
75  palette2.SetColor(2, ArgbToColor(255, 0, 0, 0));
76  palette2.SetColor(3, ArgbToColor(255, 127, 127, 127));
77  // ETSI EN 300 743 10.2: 16-entry CLUT default contents
78  palette4.SetColor(0, ArgbToColor(0, 0, 0, 0));
79  for (int i = 1; i < 16; ++i) {
80  if (i < 8) {
81  r = (i & 1) ? 255 : 0;
82  g = (i & 2) ? 255 : 0;
83  b = (i & 4) ? 255 : 0;
84  }
85  else {
86  r = (i & 1) ? 127 : 0;
87  g = (i & 2) ? 127 : 0;
88  b = (i & 4) ? 127 : 0;
89  }
90  palette4.SetColor(i, ArgbToColor(255, r, g, b));
91  }
92  // ETSI EN 300 743 10.1: 256-entry CLUT default contents
93  palette8.SetColor(0, ArgbToColor(0, 0, 0, 0));
94  for (int i = 1; i < 256; ++i) {
95  if (i < 8) {
96  r = (i & 1) ? 255 : 0;
97  g = (i & 2) ? 255 : 0;
98  b = (i & 4) ? 255 : 0;
99  a = 63;
100  }
101  else {
102  switch (i & 0x88) {
103  case 0x00:
104  r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
105  g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
106  b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
107  a = 255;
108  break;
109  case 0x08:
110  r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
111  g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
112  b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
113  a = 127;
114  break;
115  case 0x80:
116  r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
117  g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
118  b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
119  a = 255;
120  break;
121  case 0x88:
122  r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
123  g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
124  b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
125  a = 255;
126  break;
127  }
128  }
129  palette8.SetColor(i, ArgbToColor(a, r, g, b));
130  }
131 }
132 
133 void cSubtitleClut::SetColor(int Bpp, int Index, tColor Color)
134 {
135  switch (Bpp) {
136  case 2: palette2.SetColor(Index, Color); break;
137  case 4: palette4.SetColor(Index, Color); break;
138  case 8: palette8.SetColor(Index, Color); break;
139  default: esyslog("ERROR: wrong Bpp in cSubtitleClut::SetColor(%d, %d, %08X)", Bpp, Index, Color);
140  }
141 }
142 
144 {
145  switch (Bpp) {
146  case 2: return &palette2;
147  case 4: return &palette4;
148  case 8: return &palette8;
149  default: esyslog("ERROR: wrong Bpp in cSubtitleClut::GetPalette(%d)", Bpp);
150  }
151  return &palette8;
152 }
153 
154 // --- cSubtitleObject -------------------------------------------------------
155 
156 class cSubtitleObject : public cListObject {
157 private:
158  int objectId;
159  int version;
165  int px;
166  int py;
168  char textData[Utf8BufSize(256)]; // number of character codes is an 8-bit field
169  void DrawLine(int x, int y, tIndex Index, int Length);
170  bool Decode2BppCodeString(cBitStream *bs, int&x, int y, const uint8_t *MapTable);
171  bool Decode4BppCodeString(cBitStream *bs, int&x, int y, const uint8_t *MapTable);
172  bool Decode8BppCodeString(cBitStream *bs, int&x, int y);
173 public:
174  cSubtitleObject(int ObjectId, cBitmap *Bitmap);
175  int ObjectId(void) { return objectId; }
176  int Version(void) { return version; }
177  int CodingMethod(void) { return codingMethod; }
180  const char *TextData(void) { return &textData[0]; }
181  int X(void) { return px; }
182  int Y(void) { return py; }
184  void DecodeCharacterString(const uchar *Data, int NumberOfCodes);
185  void DecodeSubBlock(const uchar *Data, int Length, bool Even);
191  void SetPosition(int x, int y) { px = x; py = y; }
192  void SetProviderFlag(int ProviderFlag) { providerFlag = ProviderFlag; }
193  };
194 
196 {
197  objectId = ObjectId;
198  version = -1;
199  codingMethod = -1;
200  nonModifyingColorFlag = false;
203  providerFlag = -1;
204  px = py = 0;
205  bitmap = Bitmap;
206  memset(textData, 0, sizeof(textData));
207 }
208 
209 void cSubtitleObject::DecodeCharacterString(const uchar *Data, int NumberOfCodes)
210 {
211  if (NumberOfCodes > 0) {
212  bool singleByte;
213  const uchar *from = &Data[1];
214  int len = NumberOfCodes * 2 - 1;
215  cCharSetConv conv(SI::getCharacterTable(from, len, &singleByte));
216  if (singleByte) {
217  char txt[NumberOfCodes + 1];
218  char *p = txt;
219  for (int i = 2; i < NumberOfCodes; ++i) {
220  uchar c = Data[i * 2 + 1] & 0xFF;
221  if (c == 0)
222  break;
223  if (' ' <= c && c <= '~' || c == '\n' || 0xA0 <= c)
224  *(p++) = c;
225  else if (c == 0x8A)
226  *(p++) = '\n';
227  }
228  *p = 0;
229  const char *s = conv.Convert(txt);
231  }
232  else {
233  // TODO: add proper multibyte support for "UTF-16", "EUC-KR", "GB2312", "GBK", "UTF-8"
234  }
235  }
236 }
237 
238 void cSubtitleObject::DecodeSubBlock(const uchar *Data, int Length, bool Even)
239 {
240  int x = 0;
241  int y = Even ? 0 : 1;
242  uint8_t map2to4[ 4] = { 0x00, 0x07, 0x08, 0x0F };
243  uint8_t map2to8[ 4] = { 0x00, 0x77, 0x88, 0xFF };
244  uint8_t map4to8[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
245  const uint8_t *mapTable = NULL;
246  cBitStream bs(Data, Length * 8);
247  while (!bs.IsEOF()) {
248  switch (bs.GetBits(8)) {
249  case 0x10:
250  dbgobjects("2-bit / pixel code string\n");
251  switch (bitmap->Bpp()) {
252  case 8: mapTable = map2to8; break;
253  case 4: mapTable = map2to4; break;
254  default: mapTable = NULL; break;
255  }
256  while (Decode2BppCodeString(&bs, x, y, mapTable) && !bs.IsEOF())
257  ;
258  bs.ByteAlign();
259  break;
260  case 0x11:
261  dbgobjects("4-bit / pixel code string\n");
262  switch (bitmap->Bpp()) {
263  case 8: mapTable = map4to8; break;
264  default: mapTable = NULL; break;
265  }
266  while (Decode4BppCodeString(&bs, x, y, mapTable) && !bs.IsEOF())
267  ;
268  bs.ByteAlign();
269  break;
270  case 0x12:
271  dbgobjects("8-bit / pixel code string\n");
272  while (Decode8BppCodeString(&bs, x, y) && !bs.IsEOF())
273  ;
274  break;
275  case 0x20:
276  dbgobjects("sub block 2 to 4 map\n");
277  map2to4[0] = bs.GetBits(4);
278  map2to4[1] = bs.GetBits(4);
279  map2to4[2] = bs.GetBits(4);
280  map2to4[3] = bs.GetBits(4);
281  break;
282  case 0x21:
283  dbgobjects("sub block 2 to 8 map\n");
284  for (int i = 0; i < 4; ++i)
285  map2to8[i] = bs.GetBits(8);
286  break;
287  case 0x22:
288  dbgobjects("sub block 4 to 8 map\n");
289  for (int i = 0; i < 16; ++i)
290  map4to8[i] = bs.GetBits(8);
291  break;
292  case 0xF0:
293  dbgobjects("end of object line\n");
294  x = 0;
295  y += 2;
296  break;
297  default: dbgobjects("unknown sub block %s %d\n", __FUNCTION__, __LINE__);
298  }
299  }
300 }
301 
302 void cSubtitleObject::DrawLine(int x, int y, tIndex Index, int Length)
303 {
304  if (nonModifyingColorFlag && Index == 1)
305  return;
306  x += px;
307  y += py;
308  for (int pos = x; pos < x + Length; pos++)
309  bitmap->SetIndex(pos, y, Index);
310 }
311 
312 bool cSubtitleObject::Decode2BppCodeString(cBitStream *bs, int &x, int y, const uint8_t *MapTable)
313 {
314  int rl = 0;
315  int color = 0;
316  uchar code = bs->GetBits(2);
317  if (code) {
318  color = code;
319  rl = 1;
320  }
321  else if (bs->GetBit()) { // switch_1
322  rl = bs->GetBits(3) + 3;
323  color = bs->GetBits(2);
324  }
325  else if (bs->GetBit()) // switch_2
326  rl = 1; //color 0
327  else {
328  switch (bs->GetBits(2)) { // switch_3
329  case 0:
330  return false;
331  case 1:
332  rl = 2; //color 0
333  break;
334  case 2:
335  rl = bs->GetBits(4) + 12;
336  color = bs->GetBits(2);
337  break;
338  case 3:
339  rl = bs->GetBits(8) + 29;
340  color = bs->GetBits(2);
341  break;
342  default: ;
343  }
344  }
345  if (MapTable)
346  color = MapTable[color];
347  DrawLine(x, y, color, rl);
348  x += rl;
349  return true;
350 }
351 
352 bool cSubtitleObject::Decode4BppCodeString(cBitStream *bs, int &x, int y, const uint8_t *MapTable)
353 {
354  int rl = 0;
355  int color = 0;
356  uchar code = bs->GetBits(4);
357  if (code) {
358  color = code;
359  rl = 1;
360  }
361  else if (bs->GetBit() == 0) { // switch_1
362  code = bs->GetBits(3);
363  if (code)
364  rl = code + 2; //color 0
365  else
366  return false;
367  }
368  else if (bs->GetBit() == 0) { // switch_2
369  rl = bs->GetBits(2) + 4;
370  color = bs->GetBits(4);
371  }
372  else {
373  switch (bs->GetBits(2)) { // switch_3
374  case 0: // color 0
375  rl = 1;
376  break;
377  case 1: // color 0
378  rl = 2;
379  break;
380  case 2:
381  rl = bs->GetBits(4) + 9;
382  color = bs->GetBits(4);
383  break;
384  case 3:
385  rl = bs->GetBits(8) + 25;
386  color = bs->GetBits(4);
387  break;
388  }
389  }
390  if (MapTable)
391  color = MapTable[color];
392  DrawLine(x, y, color, rl);
393  x += rl;
394  return true;
395 }
396 
398 {
399  int rl = 0;
400  int color = 0;
401  uchar code = bs->GetBits(8);
402  if (code) {
403  color = code;
404  rl = 1;
405  }
406  else if (bs->GetBit()) {
407  rl = bs->GetBits(7);
408  color = bs->GetBits(8);
409  }
410  else {
411  code = bs->GetBits(7);
412  if (code)
413  rl = code; // color 0
414  else
415  return false;
416  }
417  DrawLine(x, y, color, rl);
418  x += rl;
419  return true;
420 }
421 
422 // --- cSubtitleRegion -------------------------------------------------------
423 
424 class cSubtitleRegion : public cListObject, public cBitmap {
425 private:
426  int regionId;
427  int version;
428  int clutId;
431  int level;
434 public:
436  int RegionId(void) { return regionId; }
437  int Version(void) { return version; }
438  int ClutId(void) { return clutId; }
439  int Level(void) { return level; }
440  int Depth(void) { return Bpp(); }
441  void FillRegion(tIndex Index);
442  cSubtitleObject *GetObjectById(int ObjectId, bool New = false);
443  int HorizontalAddress(void) { return horizontalAddress; }
444  int VerticalAddress(void) { return verticalAddress; }
446  void SetClutId(int ClutId) { clutId = ClutId; }
447  void SetLevel(int Level);
448  void SetDepth(int Depth);
451  void UpdateTextData(cSubtitleClut *Clut);
452  };
453 
455 :cBitmap(1, 1, 4)
456 {
457  regionId = RegionId;
458  version = -1;
459  clutId = -1;
460  horizontalAddress = 0;
461  verticalAddress = 0;
462  level = 0;
463  lineHeight = 26; // configurable subtitling font size
464 }
465 
467 {
468  dbgregions("FillRegion %d\n", Index);
469  for (int y = 0; y < Height(); y++) {
470  for (int x = 0; x < Width(); x++)
471  SetIndex(x, y, Index);
472  }
473 }
474 
476 {
477  cSubtitleObject *result = NULL;
478  for (cSubtitleObject *so = objects.First(); so; so = objects.Next(so)) {
479  if (so->ObjectId() == ObjectId)
480  result = so;
481  }
482  if (!result && New) {
483  result = new cSubtitleObject(ObjectId, this);
484  objects.Add(result);
485  }
486  return result;
487 }
488 
490 {
491  const cPalette *palette = Clut ? Clut->GetPalette(Depth()) : NULL;
492  for (cSubtitleObject *so = objects.First(); so && palette; so = objects.Next(so)) {
493  if (Utf8StrLen(so->TextData()) > 0) {
495  cBitmap tmp(font->Width(so->TextData()), font->Height(), Depth());
496  double factor = (double)lineHeight / font->Height();
497  tmp.DrawText(0, 0, so->TextData(), palette->Color(so->ForegroundPixelCode()), palette->Color(so->BackgroundPixelCode()), font);
498  cBitmap *scaled = tmp.Scaled(factor, factor, true);
499  DrawBitmap(so->X(), so->Y(), *scaled);
500  delete scaled;
501  delete font;
502  }
503  }
504 }
505 
507 {
508  if (Level > 0 && Level < 4)
509  level = 1 << Level;
510 }
511 
513 {
514  if (Depth > 0 && Depth < 4)
515  SetBpp(1 << Depth);
516 }
517 
518 // --- cDvbSubtitlePage ------------------------------------------------------
519 
521 private:
522  int pageId;
523  int version;
524  int state;
525  int64_t pts;
526  int timeout;
528 public:
531  virtual ~cDvbSubtitlePage();
532  int PageId(void) { return pageId; }
533  int Version(void) { return version; }
534  int State(void) { return state; }
535  tArea *GetAreas(double FactorX, double FactorY);
536  cSubtitleClut *GetClutById(int ClutId, bool New = false);
537  cSubtitleObject *GetObjectById(int ObjectId);
538  cSubtitleRegion *GetRegionById(int RegionId, bool New = false);
539  int64_t Pts(void) const { return pts; }
540  int Timeout(void) { return timeout; }
542  void SetPts(int64_t Pts) { pts = Pts; }
543  void SetState(int State);
545  };
546 
548 {
549  pageId = PageId;
550  version = -1;
551  state = -1;
552  pts = 0;
553  timeout = 0;
554 }
555 
557 {
558 }
559 
560 tArea *cDvbSubtitlePage::GetAreas(double FactorX, double FactorY)
561 {
562  if (regions.Count() > 0) {
563  tArea *Areas = new tArea[regions.Count()];
564  tArea *a = Areas;
565  for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) {
566  a->x1 = int(round(FactorX * sr->HorizontalAddress()));
567  a->y1 = int(round(FactorY * sr->VerticalAddress()));
568  a->x2 = int(round(FactorX * (sr->HorizontalAddress() + sr->Width() - 1)));
569  a->y2 = int(round(FactorY * (sr->VerticalAddress() + sr->Height() - 1)));
570  a->bpp = sr->Bpp();
571  while ((a->Width() & 3) != 0)
572  a->x2++; // aligns width to a multiple of 4, so 2, 4 and 8 bpp will work
573  a++;
574  }
575  return Areas;
576  }
577  return NULL;
578 }
579 
581 {
582  cSubtitleClut *result = NULL;
583  for (cSubtitleClut *sc = cluts.First(); sc; sc = cluts.Next(sc)) {
584  if (sc->ClutId() == ClutId)
585  result = sc;
586  }
587  if (!result && New) {
588  result = new cSubtitleClut(ClutId);
589  cluts.Add(result);
590  }
591  return result;
592 }
593 
595 {
596  cSubtitleRegion *result = NULL;
597  for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) {
598  if (sr->RegionId() == RegionId)
599  result = sr;
600  }
601  if (!result && New) {
602  result = new cSubtitleRegion(RegionId);
603  regions.Add(result);
604  }
605  return result;
606 }
607 
609 {
610  cSubtitleObject *result = NULL;
611  for (cSubtitleRegion *sr = regions.First(); sr && !result; sr = regions.Next(sr))
612  result = sr->GetObjectById(ObjectId);
613  return result;
614 }
615 
617 {
618  state = State;
619  switch (state) {
620  case 0: // normal case - page update
621  dbgpages("page update\n");
622  break;
623  case 1: // acquisition point - page refresh
624  dbgpages("page refresh\n");
625  regions.Clear();
626  break;
627  case 2: // mode change - new page
628  dbgpages("new Page\n");
629  regions.Clear();
630  cluts.Clear();
631  break;
632  case 3: // reserved
633  break;
634  default: dbgpages("unknown page state (%s %d)\n", __FUNCTION__, __LINE__);
635  }
636 }
637 
638 // --- cDvbSubtitleAssembler -------------------------------------------------
639 
641 private:
643  int length;
644  int pos;
645  int size;
646  bool Realloc(int Size);
647 public:
648  cDvbSubtitleAssembler(void);
649  virtual ~cDvbSubtitleAssembler();
650  void Reset(void);
651  unsigned char *Get(int &Length);
652  void Put(const uchar *Data, int Length);
653  };
654 
656 {
657  data = NULL;
658  size = 0;
659  Reset();
660 }
661 
663 {
664  free(data);
665 }
666 
668 {
669  length = 0;
670  pos = 0;
671 }
672 
674 {
675  if (Size > size) {
676  Size = max(Size, 2048);
677  if (uchar *NewBuffer = (uchar *)realloc(data, Size)) {
678  size = Size;
679  data = NewBuffer;
680  }
681  else {
682  esyslog("ERROR: can't allocate memory for subtitle assembler");
683  length = 0;
684  size = 0;
685  free(data);
686  data = NULL;
687  return false;
688  }
689  }
690  return true;
691 }
692 
693 unsigned char *cDvbSubtitleAssembler::Get(int &Length)
694 {
695  if (length > pos + 5) {
696  Length = (data[pos + 4] << 8) + data[pos + 5] + 6;
697  if (length >= pos + Length) {
698  unsigned char *result = data + pos;
699  pos += Length;
700  return result;
701  }
702  }
703  return NULL;
704 }
705 
706 void cDvbSubtitleAssembler::Put(const uchar *Data, int Length)
707 {
708  if (Length && Realloc(length + Length)) {
709  memcpy(data + length, Data, Length);
710  length += Length;
711  }
712 }
713 
714 // --- cDvbSubtitleBitmaps ---------------------------------------------------
715 
717 private:
718  int64_t pts;
719  int timeout;
721  int numAreas;
722  double osdFactorX;
723  double osdFactorY;
725 public:
726  cDvbSubtitleBitmaps(int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY);
728  int64_t Pts(void) { return pts; }
729  int Timeout(void) { return timeout; }
730  void AddBitmap(cBitmap *Bitmap);
731  void Draw(cOsd *Osd);
732  };
733 
734 cDvbSubtitleBitmaps::cDvbSubtitleBitmaps(int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY)
735 {
736  pts = Pts;
737  timeout = Timeout;
738  areas = Areas;
739  numAreas = NumAreas;
740  osdFactorX = OsdFactorX;
741  osdFactorY = OsdFactorY;
742 }
743 
745 {
746  delete[] areas;
747  for (int i = 0; i < bitmaps.Size(); i++)
748  delete bitmaps[i];
749 }
750 
752 {
753  bitmaps.Append(Bitmap);
754 }
755 
757 {
758  bool Scale = !(DoubleEqual(osdFactorX, 1.0) && DoubleEqual(osdFactorY, 1.0));
759  bool AntiAlias = true;
760  if (Scale && osdFactorX > 1.0 || osdFactorY > 1.0) {
761  // Upscaling requires 8bpp:
762  int Bpp[MAXOSDAREAS];
763  for (int i = 0; i < numAreas; i++) {
764  Bpp[i] = areas[i].bpp;
765  areas[i].bpp = 8;
766  }
767  if (Osd->CanHandleAreas(areas, numAreas) != oeOk) {
768  for (int i = 0; i < numAreas; i++)
769  Bpp[i] = areas[i].bpp = Bpp[i];
770  AntiAlias = false;
771  }
772  }
773  if (Osd->SetAreas(areas, numAreas) == oeOk) {
774  for (int i = 0; i < bitmaps.Size(); i++) {
775  cBitmap *b = bitmaps[i];
776  if (Scale)
777  b = b->Scaled(osdFactorX, osdFactorY, AntiAlias);
778  Osd->DrawBitmap(int(round(b->X0() * osdFactorX)), int(round(b->Y0() * osdFactorY)), *b);
779  if (b != bitmaps[i])
780  delete b;
781  }
782  Osd->Flush();
783  }
784 }
785 
786 // --- cDvbSubtitleConverter -------------------------------------------------
787 
789 
791 :cThread("subtitleConverter")
792 {
794  osd = NULL;
795  frozen = false;
796  ddsVersionNumber = -1;
797  displayWidth = windowWidth = 720;
798  displayHeight = windowHeight = 576;
803  Start();
804 }
805 
807 {
808  Cancel(3);
809  delete dvbSubtitleAssembler;
810  delete osd;
811  delete bitmaps;
812  delete pages;
813 }
814 
816 {
817  setupLevel++;
818 }
819 
821 {
822  dbgconverter("Converter reset -----------------------\n");
824  Lock();
825  pages->Clear();
826  bitmaps->Clear();
827  DELETENULL(osd);
828  frozen = false;
829  ddsVersionNumber = -1;
830  displayWidth = windowWidth = 720;
831  displayHeight = windowHeight = 576;
834  Unlock();
835 }
836 
837 int cDvbSubtitleConverter::ConvertFragments(const uchar *Data, int Length)
838 {
839  if (Data && Length > 8) {
840  int PayloadOffset = PesPayloadOffset(Data);
841  int SubstreamHeaderLength = 4;
842  bool ResetSubtitleAssembler = Data[PayloadOffset + 3] == 0x00;
843 
844  // Compatibility mode for old subtitles plugin:
845  if ((Data[7] & 0x01) && (Data[PayloadOffset - 3] & 0x81) == 0x01 && Data[PayloadOffset - 2] == 0x81) {
846  PayloadOffset--;
847  SubstreamHeaderLength = 1;
848  ResetSubtitleAssembler = Data[8] >= 5;
849  }
850 
851  if (Length > PayloadOffset + SubstreamHeaderLength) {
852  int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : 0;
853  if (pts)
854  dbgconverter("Converter PTS: %"PRId64"\n", pts);
855  const uchar *data = Data + PayloadOffset + SubstreamHeaderLength; // skip substream header
856  int length = Length - PayloadOffset - SubstreamHeaderLength; // skip substream header
857  if (ResetSubtitleAssembler)
859 
860  if (length > 3) {
861  if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F)
862  dvbSubtitleAssembler->Put(data + 2, length - 2);
863  else
864  dvbSubtitleAssembler->Put(data, length);
865 
866  int Count;
867  while (true) {
868  unsigned char *b = dvbSubtitleAssembler->Get(Count);
869  if (b && b[0] == 0x0F) {
870  if (ExtractSegment(b, Count, pts) == -1)
871  break;
872  }
873  else
874  break;
875  }
876  }
877  }
878  return Length;
879  }
880  return 0;
881 }
882 
883 int cDvbSubtitleConverter::Convert(const uchar *Data, int Length)
884 {
885  if (Data && Length > 8) {
886  int PayloadOffset = PesPayloadOffset(Data);
887  if (Length > PayloadOffset) {
888  int64_t pts = PesGetPts(Data);
889  if (pts)
890  dbgconverter("Converter PTS: %"PRId64"\n", pts);
891  const uchar *data = Data + PayloadOffset;
892  int length = Length - PayloadOffset;
893  if (length > 3) {
894  if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) {
895  data += 2;
896  length -= 2;
897  }
898  const uchar *b = data;
899  while (length > 0) {
900  if (b[0] == 0x0F) {
901  int n = ExtractSegment(b, length, pts);
902  if (n < 0)
903  break;
904  b += n;
905  length -= n;
906  }
907  else
908  break;
909  }
910  }
911  }
912  return Length;
913  }
914  return 0;
915 }
916 
917 #define LimitTo32Bit(n) ((n) & 0x00000000FFFFFFFFL)
918 #define MAXDELTA 40000 // max. reasonable PTS/STC delta in ms
919 
921 {
922  int LastSetupLevel = setupLevel;
923  cTimeMs Timeout;
924  while (Running()) {
925  int WaitMs = 100;
926  if (!frozen) {
927  LOCK_THREAD;
928  if (osd) {
929  int NewSetupLevel = setupLevel;
930  if (Timeout.TimedOut() || LastSetupLevel != NewSetupLevel) {
931  DELETENULL(osd);
932  }
933  LastSetupLevel = NewSetupLevel;
934  }
935  if (cDvbSubtitleBitmaps *sb = bitmaps->First()) {
936  int64_t STC = cDevice::PrimaryDevice()->GetSTC();
937  int64_t Delta = LimitTo32Bit(sb->Pts()) - LimitTo32Bit(STC); // some devices only deliver 32 bits
938  if (Delta > (int64_t(1) << 31))
939  Delta -= (int64_t(1) << 32);
940  else if (Delta < -((int64_t(1) << 31) - 1))
941  Delta += (int64_t(1) << 32);
942  Delta /= 90; // STC and PTS are in 1/90000s
943  if (Delta <= MAXDELTA) {
944  if (Delta <= 0) {
945  dbgconverter("Got %d bitmaps, showing #%d\n", bitmaps->Count(), sb->Index() + 1);
946  if (AssertOsd()) {
947  sb->Draw(osd);
948  Timeout.Set(sb->Timeout() * 1000);
949  dbgconverter("PTS: %"PRId64" STC: %"PRId64" (%"PRId64") timeout: %d\n", sb->Pts(), cDevice::PrimaryDevice()->GetSTC(), Delta, sb->Timeout());
950  }
951  bitmaps->Del(sb);
952  }
953  else if (Delta < WaitMs)
954  WaitMs = Delta;
955  }
956  else
957  bitmaps->Del(sb);
958  }
959  }
960  cCondWait::SleepMs(WaitMs);
961  }
962 }
963 
965 {
966  int Ey, Epb, Epr;
967  int Eg, Eb, Er;
968 
969  Ey = (Y - 16);
970  Epb = (Cb - 128);
971  Epr = (Cr - 128);
972  /* ITU-R 709 */
973  Er = constrain((298 * Ey + 460 * Epr) / 256, 0, 255);
974  Eg = constrain((298 * Ey - 55 * Epb - 137 * Epr) / 256, 0, 255);
975  Eb = constrain((298 * Ey + 543 * Epb ) / 256, 0, 255);
976 
977  return (Er << 16) | (Eg << 8) | Eb;
978 }
979 
981 {
982  int OsdWidth, OsdHeight;
983  double OsdAspect;
984  int VideoWidth, VideoHeight;
985  double VideoAspect;
986  cDevice::PrimaryDevice()->GetOsdSize(OsdWidth, OsdHeight, OsdAspect);
987  cDevice::PrimaryDevice()->GetVideoSize(VideoWidth, VideoHeight, VideoAspect);
988  if (OsdWidth == displayWidth && OsdHeight == displayHeight) {
989  osdFactorX = osdFactorY = 1.0;
990  osdDeltaX = osdDeltaY = 0;
991  }
992  else {
993  osdFactorX = VideoAspect * OsdHeight / displayWidth;
994  osdFactorY = double(OsdHeight) / displayHeight;
995  osdDeltaX = (OsdWidth - displayWidth * osdFactorX) / 2;
996  osdDeltaY = (OsdHeight - displayHeight * osdFactorY) / 2;
997  }
998 }
999 
1001 {
1002  LOCK_THREAD;
1003  if (!osd) {
1004  SetOsdData();
1006  }
1007  return osd != NULL;
1008 }
1009 
1010 int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t Pts)
1011 {
1012  cBitStream bs(Data, Length * 8);
1013  if (Length > 5 && bs.GetBits(8) == 0x0F) { // sync byte
1014  int segmentType = bs.GetBits(8);
1015  if (segmentType == STUFFING_SEGMENT)
1016  return -1;
1017  int pageId = bs.GetBits(16);
1018  int segmentLength = bs.GetBits(16);
1019  if (!bs.SetLength(bs.Index() + segmentLength * 8))
1020  return -1;
1021  cDvbSubtitlePage *page = NULL;
1022  LOCK_THREAD;
1023  for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) {
1024  if (sp->PageId() == pageId) {
1025  page = sp;
1026  break;
1027  }
1028  }
1029  if (!page) {
1030  page = new cDvbSubtitlePage(pageId);
1031  pages->Add(page);
1032  dbgpages("Create SubtitlePage %d (total pages = %d)\n", pageId, pages->Count());
1033  }
1034  if (Pts)
1035  page->SetPts(Pts);
1036  switch (segmentType) {
1037  case PAGE_COMPOSITION_SEGMENT: {
1038  dbgsegments("PAGE_COMPOSITION_SEGMENT\n");
1039  int pageTimeout = bs.GetBits(8);
1040  int pageVersion = bs.GetBits(4);
1041  if (pageVersion == page->Version())
1042  break; // no update
1043  page->SetVersion(pageVersion);
1044  page->SetTimeout(pageTimeout);
1045  page->SetState(bs.GetBits(2));
1046  bs.SkipBits(2); // reserved
1047  dbgpages("Update page id %d version %d pts %"PRId64" timeout %d state %d\n", pageId, page->Version(), page->Pts(), page->Timeout(), page->State());
1048  while (!bs.IsEOF()) {
1049  cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8), true);
1050  bs.SkipBits(8); // reserved
1051  region->SetHorizontalAddress(bs.GetBits(16));
1052  region->SetVerticalAddress(bs.GetBits(16));
1053  }
1054  break;
1055  }
1057  dbgsegments("REGION_COMPOSITION_SEGMENT\n");
1058  cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8));
1059  if (!region)
1060  break;
1061  int regionVersion = bs.GetBits(4);
1062  if (regionVersion == region->Version())
1063  break; // no update
1064  region->SetVersion(regionVersion);
1065  bool regionFillFlag = bs.GetBit();
1066  bs.SkipBits(3); // reserved
1067  int regionWidth = bs.GetBits(16);
1068  if (regionWidth < 1)
1069  regionWidth = 1;
1070  int regionHeight = bs.GetBits(16);
1071  if (regionHeight < 1)
1072  regionHeight = 1;
1073  region->SetSize(regionWidth, regionHeight);
1074  region->SetLevel(bs.GetBits(3));
1075  region->SetDepth(bs.GetBits(3));
1076  bs.SkipBits(2); // reserved
1077  region->SetClutId(bs.GetBits(8));
1078  dbgregions("Region pageId %d id %d version %d fill %d width %d height %d level %d depth %d clutId %d\n", pageId, region->RegionId(), region->Version(), regionFillFlag, regionWidth, regionHeight, region->Level(), region->Depth(), region->ClutId());
1079  int region8bitPixelCode = bs.GetBits(8);
1080  int region4bitPixelCode = bs.GetBits(4);
1081  int region2bitPixelCode = bs.GetBits(2);
1082  bs.SkipBits(2); // reserved
1083  if (regionFillFlag) {
1084  switch (region->Bpp()) {
1085  case 2: region->FillRegion(region8bitPixelCode); break;
1086  case 4: region->FillRegion(region4bitPixelCode); break;
1087  case 8: region->FillRegion(region2bitPixelCode); break;
1088  default: dbgregions("unknown bpp %d (%s %d)\n", region->Bpp(), __FUNCTION__, __LINE__);
1089  }
1090  }
1091  while (!bs.IsEOF()) {
1092  cSubtitleObject *object = region->GetObjectById(bs.GetBits(16), true);
1093  int objectType = bs.GetBits(2);
1094  object->SetCodingMethod(objectType);
1095  object->SetProviderFlag(bs.GetBits(2));
1096  int objectHorizontalPosition = bs.GetBits(12);
1097  bs.SkipBits(4); // reserved
1098  int objectVerticalPosition = bs.GetBits(12);
1099  object->SetPosition(objectHorizontalPosition, objectVerticalPosition);
1100  if (objectType == 0x01 || objectType == 0x02) {
1101  object->SetForegroundPixelCode(bs.GetBits(8));
1102  object->SetBackgroundPixelCode(bs.GetBits(8));
1103  }
1104  }
1105  break;
1106  }
1107  case CLUT_DEFINITION_SEGMENT: {
1108  dbgsegments("CLUT_DEFINITION_SEGMENT\n");
1109  cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true);
1110  int clutVersion = bs.GetBits(4);
1111  if (clutVersion == clut->Version())
1112  break; // no update
1113  clut->SetVersion(clutVersion);
1114  bs.SkipBits(4); // reserved
1115  dbgcluts("Clut pageId %d id %d version %d\n", pageId, clut->ClutId(), clut->Version());
1116  while (!bs.IsEOF()) {
1117  uchar clutEntryId = bs.GetBits(8);
1118  bool entryClut2Flag = bs.GetBit();
1119  bool entryClut4Flag = bs.GetBit();
1120  bool entryClut8Flag = bs.GetBit();
1121  bs.SkipBits(4); // reserved
1122  uchar yval;
1123  uchar crval;
1124  uchar cbval;
1125  uchar tval;
1126  if (bs.GetBit()) { // full_range_flag
1127  yval = bs.GetBits(8);
1128  crval = bs.GetBits(8);
1129  cbval = bs.GetBits(8);
1130  tval = bs.GetBits(8);
1131  }
1132  else {
1133  yval = bs.GetBits(6) << 2;
1134  crval = bs.GetBits(4) << 4;
1135  cbval = bs.GetBits(4) << 4;
1136  tval = bs.GetBits(2) << 6;
1137  }
1138  tColor value = 0;
1139  if (yval) {
1140  value = yuv2rgb(yval, cbval, crval);
1141  value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24;
1142  }
1143  dbgcluts("%2d %d %d %d %08X\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value);
1144  if (entryClut2Flag)
1145  clut->SetColor(2, clutEntryId, value);
1146  if (entryClut4Flag)
1147  clut->SetColor(4, clutEntryId, value);
1148  if (entryClut8Flag)
1149  clut->SetColor(8, clutEntryId, value);
1150  }
1151  dbgcluts("\n");
1152  break;
1153  }
1154  case OBJECT_DATA_SEGMENT: {
1155  dbgsegments("OBJECT_DATA_SEGMENT\n");
1156  cSubtitleObject *object = page->GetObjectById(bs.GetBits(16));
1157  if (!object)
1158  break;
1159  int objectVersion = bs.GetBits(4);
1160  if (objectVersion == object->Version())
1161  break; // no update
1162  object->SetVersion(objectVersion);
1163  int codingMethod = bs.GetBits(2);
1164  object->SetNonModifyingColorFlag(bs.GetBit());
1165  bs.SkipBit(); // reserved
1166  dbgobjects("Object pageId %d id %d version %d method %d modify %d\n", pageId, object->ObjectId(), object->Version(), object->CodingMethod(), object->NonModifyingColorFlag());
1167  if (codingMethod == 0) { // coding of pixels
1168  int topFieldLength = bs.GetBits(16);
1169  int bottomFieldLength = bs.GetBits(16);
1170  object->DecodeSubBlock(bs.GetData(), topFieldLength, true);
1171  if (bottomFieldLength)
1172  object->DecodeSubBlock(bs.GetData() + topFieldLength, bottomFieldLength, false);
1173  else
1174  object->DecodeSubBlock(bs.GetData(), topFieldLength, false);
1175  bs.WordAlign();
1176  }
1177  else if (codingMethod == 1) { // coded as a string of characters
1178  int numberOfCodes = bs.GetBits(8);
1179  object->DecodeCharacterString(bs.GetData(), numberOfCodes);
1180  }
1181 #ifdef FINISHPAGE_HACK
1182  FinishPage(page); // flush to OSD right away
1183 #endif
1184  break;
1185  }
1187  dbgsegments("DISPLAY_DEFINITION_SEGMENT\n");
1188  int version = bs.GetBits(4);
1189  if (version != ddsVersionNumber) {
1190  bool displayWindowFlag = bs.GetBit();
1193  bs.SkipBits(3); // reserved
1194  displayWidth = windowWidth = bs.GetBits(16) + 1;
1195  displayHeight = windowHeight = bs.GetBits(16) + 1;
1196  if (displayWindowFlag) {
1197  windowHorizontalOffset = bs.GetBits(16); // displayWindowHorizontalPositionMinimum
1198  windowWidth = bs.GetBits(16) - windowHorizontalOffset + 1; // displayWindowHorizontalPositionMaximum
1199  windowVerticalOffset = bs.GetBits(16); // displayWindowVerticalPositionMinimum
1200  windowHeight = bs.GetBits(16) - windowVerticalOffset + 1; // displayWindowVerticalPositionMaximum
1201  }
1202  SetOsdData();
1203  SetupChanged();
1204  ddsVersionNumber = version;
1205  }
1206  break;
1207  }
1209  dbgsegments("DISPARITY_SIGNALING_SEGMENT\n");
1210  bs.SkipBits(4); // dss_version_number
1211  bool disparity_shift_update_sequence_page_flag = bs.GetBit();
1212  bs.SkipBits(3); // reserved
1213  bs.SkipBits(8); // page_default_disparity_shift
1214  if (disparity_shift_update_sequence_page_flag) {
1215  bs.SkipBits(8); // disparity_shift_update_sequence_length
1216  bs.SkipBits(24); // interval_duration[23..0]
1217  int division_period_count = bs.GetBits(8);
1218  for (int i = 0; i < division_period_count; ++i) {
1219  bs.SkipBits(8); // interval_count
1220  bs.SkipBits(8); // disparity_shift_update_integer_part
1221  }
1222  }
1223  while (!bs.IsEOF()) {
1224  bs.SkipBits(8); // region_id
1225  bool disparity_shift_update_sequence_region_flag = bs.GetBit();
1226  bs.SkipBits(5); // reserved
1227  int number_of_subregions_minus_1 = bs.GetBits(2);
1228  for (int i = 0; i <= number_of_subregions_minus_1; ++i) {
1229  if (number_of_subregions_minus_1 > 0) {
1230  bs.SkipBits(16); // subregion_horizontal_position
1231  bs.SkipBits(16); // subregion_width
1232  }
1233  bs.SkipBits(8); // subregion_disparity_shift_integer_part
1234  bs.SkipBits(4); // subregion_disparity_shift_fractional_part
1235  bs.SkipBits(4); // reserved
1236  if (disparity_shift_update_sequence_region_flag) {
1237  bs.SkipBits(8); // disparity_shift_update_sequence_length
1238  bs.SkipBits(24); // interval_duration[23..0]
1239  int division_period_count = bs.GetBits(8);
1240  for (int i = 0; i < division_period_count; ++i) {
1241  bs.SkipBits(8); // interval_count
1242  bs.SkipBits(8); // disparity_shift_update_integer_part
1243  }
1244  }
1245  }
1246  }
1247  break;
1248  }
1250  dbgsegments("END_OF_DISPLAY_SET_SEGMENT\n");
1251  FinishPage(page);
1252  break;
1253  }
1254  default:
1255  dbgsegments("*** unknown segment type: %02X\n", segmentType);
1256  }
1257  return bs.Length() / 8;
1258  }
1259  return -1;
1260 }
1261 
1263 {
1264  if (!AssertOsd())
1265  return;
1266  tArea *Areas = Page->GetAreas(osdFactorX, osdFactorY);
1267  int NumAreas = Page->regions.Count();
1268  int Bpp = 8;
1269  bool Reduced = false;
1270  while (osd && osd->CanHandleAreas(Areas, NumAreas) != oeOk) {
1271  int HalfBpp = Bpp / 2;
1272  if (HalfBpp >= 2) {
1273  for (int i = 0; i < NumAreas; i++) {
1274  if (Areas[i].bpp >= Bpp) {
1275  Areas[i].bpp = HalfBpp;
1276  Reduced = true;
1277  }
1278  }
1279  Bpp = HalfBpp;
1280  }
1281  else
1282  return; // unable to draw bitmaps
1283  }
1284  cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->Pts(), Page->Timeout(), Areas, NumAreas, osdFactorX, osdFactorY);
1285  bitmaps->Add(Bitmaps);
1286  for (int i = 0; i < NumAreas; i++) {
1287  cSubtitleRegion *sr = Page->regions.Get(i);
1288  cSubtitleClut *clut = Page->GetClutById(sr->ClutId());
1289  if (!clut)
1290  continue;
1291  sr->Replace(*clut->GetPalette(sr->Bpp()));
1292  sr->UpdateTextData(clut);
1293  if (Reduced) {
1294  if (sr->Bpp() != Areas[i].bpp) {
1295  if (sr->Level() <= Areas[i].bpp) {
1296  //TODO this is untested - didn't have any such subtitle stream
1297  cSubtitleClut *Clut = Page->GetClutById(sr->ClutId());
1298  if (Clut) {
1299  dbgregions("reduce region %d bpp %d level %d area bpp %d\n", sr->RegionId(), sr->Bpp(), sr->Level(), Areas[i].bpp);
1300  sr->ReduceBpp(*Clut->GetPalette(sr->Bpp()));
1301  }
1302  }
1303  else {
1304  dbgregions("condense region %d bpp %d level %d area bpp %d\n", sr->RegionId(), sr->Bpp(), sr->Level(), Areas[i].bpp);
1305  sr->ShrinkBpp(Areas[i].bpp);
1306  }
1307  }
1308  }
1309  int posX = sr->HorizontalAddress();
1310  int posY = sr->VerticalAddress();
1311  if (sr->Width() > 0 && sr->Height() > 0) {
1312  cBitmap *bm = new cBitmap(sr->Width(), sr->Height(), sr->Bpp(), posX, posY);
1313  bm->DrawBitmap(posX, posY, *sr);
1314  Bitmaps->AddBitmap(bm);
1315  }
1316  }
1317 }