2 Copyright 2008 Mark Calderbank, 2020 Chris Tallon
4 This file is part of VOMP.
6 VOMP is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 VOMP is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with VOMP. If not, see <https://www.gnu.org/licenses/>.
19 /* Sections of code and general methods adopted from VDR version 1.6.0
20 * dvbsubtitle.c (GPL version 2 or later)
21 * Original author: Marco Schluessler <marco@lordzodiac.de>
22 * With some input from the "subtitle plugin"
23 * by Pekka Virtanen <pekka.virtanen@sci.fi>
26 /* Reference ETSI EN 300 743 V1.3.1 (2006-11) */
39 #include "osdreceiver.h"
43 #include "dvbsubtitles.h"
45 static const char* TAG = "DVBSubtitles";
47 DVBSubtitleCLUT::DVBSubtitleCLUT()
53 for (int i = 0; i < 4; ++i) palette2.setColour(i, defaultPalette2[i]);
54 for (int i = 0; i < 16; ++i) palette4.setColour(i, defaultPalette4[i]);
55 for (int i = 0; i < 256; ++i) palette8.setColour(i, defaultPalette8[i]);
58 const u4 DVBSubtitleCLUT::defaultPalette2[4] = {
59 0x00000000, 0xFFFFFFFF, 0xFF000000, 0xFF808080
62 const u4 DVBSubtitleCLUT::defaultPalette4[16] = {
63 0x00000000, 0xFFFF0000, 0xFF00FF00, 0xFFFFFF00,
64 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFFFFFFFF,
65 0xFF000000, 0xFF800000, 0xFF008000, 0xFF808000,
66 0xFF000080, 0xFF800080, 0xFF008080, 0xFF808080
69 const u4 DVBSubtitleCLUT::defaultPalette8[256] = {
70 0x00000000, 0x40FF0000, 0x4000FF00, 0x40FFFF00, // 0000 0000
71 0x400000FF, 0x40FF00FF, 0x4000FFFF, 0x40FFFFFF, // 0000 0100
72 0x80000000, 0x80550000, 0x80005500, 0x80555500, // 0000 1000
73 0x80000055, 0x80550055, 0x80005555, 0x80555555, // 0000 1100
74 0xFFAA0000, 0xFFFF0000, 0xFFAA5500, 0xFFFF5500, // 0001 0000
75 0xFFAA0055, 0xFFFF0055, 0xFFAA5555, 0xFFFF5555, // 0001 0100
76 0x80AA0000, 0x80FF0000, 0x80AA5500, 0x80FF5500, // 0001 1000
77 0x80AA0055, 0x80FF0055, 0x80AA5555, 0x80FF5555, // 0001 1100
78 0xFF00AA00, 0xFF55AA00, 0xFF00FF00, 0xFF55FF00, // 0010 0000
79 0xFF00AA55, 0xFF55AA55, 0xFF00FF55, 0xFF55FF55, // 0010 0100
80 0x8000AA00, 0x8055AA00, 0x8000FF00, 0x8055FF00, // 0010 1000
81 0x8000AA55, 0x8055AA55, 0x8000FF55, 0x8055FF55, // 0010 1100
82 0xFFAAAA00, 0xFFFFAA00, 0xFFAAFF00, 0xFFFFFF00, // 0011 0000
83 0xFFAAAA55, 0xFFFFAA55, 0xFFAAFF55, 0xFFFFFF55, // 0011 0100
84 0x80AAAA00, 0x80FFAA00, 0x80AAFF00, 0x80FFFF00, // 0011 1000
85 0x80AAAA55, 0x80FFAA55, 0x80AAFF55, 0x80FFFF55, // 0011 1100
86 0xFF0000AA, 0xFF5500AA, 0xFF0055AA, 0xFF5555AA, // 0100 0000
87 0xFF0000FF, 0xFF5500FF, 0xFF0055FF, 0xFF5555FF, // 0100 0100
88 0x800000AA, 0x805500AA, 0x800055AA, 0x805555AA, // 0100 1000
89 0x800000FF, 0x805500FF, 0x800055FF, 0x805555FF, // 0100 1100
90 0xFFAA00AA, 0xFFFF00AA, 0xFFAA55AA, 0xFFFF55AA, // 0101 0000
91 0xFFAA00FF, 0xFFFF00FF, 0xFFAA55FF, 0xFFFF55FF, // 0101 0100
92 0x80AA00AA, 0x80FF00AA, 0x80AA55AA, 0x80FF55AA, // 0101 1000
93 0x80AA00FF, 0x80FF00FF, 0x80AA55FF, 0x80FF55FF, // 0101 1100
94 0xFF00AAAA, 0xFF55AAAA, 0xFF00FFAA, 0xFF55FFAA, // 0110 0000
95 0xFF00AAFF, 0xFF55AAFF, 0xFF00FFFF, 0xFF55FFFF, // 0110 0100
96 0x8000AAAA, 0x8055AAAA, 0x8000FFAA, 0x8055FFAA, // 0110 1000
97 0x8000AAFF, 0x8055AAFF, 0x8000FFFF, 0x8055FFFF, // 0110 1100
98 0xFFAAAAAA, 0xFFFFAAAA, 0xFFAAFFAA, 0xFFFFFFAA, // 0111 0000
99 0xFFAAAAFF, 0xFFFFAAFF, 0xFFAAFFFF, 0xFFFFFFFF, // 0111 0100
100 0x80AAAAAA, 0x80FFAAAA, 0x80AAFFAA, 0x80FFFFAA, // 0111 1000
101 0x80AAAAFF, 0x80FFAAFF, 0x80AAFFFF, 0x80FFFFFF, // 0111 1100
102 0xFF808080, 0xFFAA8080, 0xFF80AA80, 0xFFAAAA80, // 1000 0000
103 0xFF8080AA, 0xFFAA80AA, 0xFF80AAAA, 0xFFAAAAAA, // 1000 0100
104 0xFF000000, 0xFF2A0000, 0xFF002A00, 0xFF2A2A00, // 1000 1000
105 0xFF00002A, 0xFF2A002A, 0xFF002A2A, 0xFF2A2A2A, // 1000 1100
106 0xFFD58080, 0xFFFF8080, 0xFFD5AA80, 0xFFFFAA80, // 1001 0000
107 0xFFD580AA, 0xFFFF80AA, 0xFFD5AAAA, 0xFFFFAAAA, // 1001 0100
108 0xFF550000, 0xFF7F0000, 0xFF552A00, 0xFF7F2A00, // 1001 1000
109 0xFF55002A, 0xFF7F002A, 0xFF552A2A, 0xFF7F2A2A, // 1001 1100
110 0xFF80D580, 0xFFAAD580, 0xFF80FF80, 0xFFAAFF80, // 1010 0000
111 0xFF80D5AA, 0xFFAAD5AA, 0xFF80FFAA, 0xFFAAFFAA, // 1010 0100
112 0xFF005500, 0xFF2A5500, 0xFF007F00, 0xFF2A7F00, // 1010 1000
113 0xFF00552A, 0xFF2A552A, 0xFF007F2A, 0xFF2A7F2A, // 1010 1100
114 0xFFD5D580, 0xFFFFD580, 0xFFD5FF80, 0xFFFFFF80, // 1011 0000
115 0xFFD5D5AA, 0xFFFFD5AA, 0xFFD5FFAA, 0xFFFFFFAA, // 1011 0100
116 0xFF555500, 0xFF7F5500, 0xFF557F00, 0xFF7F7F00, // 1011 1000
117 0xFF55552A, 0xFF7F552A, 0xFF557F2A, 0xFF7F7F2A, // 1011 1100
118 0xFF8080D5, 0xFFAA80D5, 0xFF80AAD5, 0xFFAAAAD5, // 1100 0000
119 0xFF8080FF, 0xFFAA80FF, 0xFF80AAFF, 0xFFAAAAFF, // 1100 0100
120 0xFF000055, 0xFF2A0055, 0xFF002A55, 0xFF2A2A55, // 1100 1000
121 0xFF00007F, 0xFF2A007F, 0xFF002A7F, 0xFF2A2A7F, // 1100 1100
122 0xFFD580D5, 0xFFFF80D5, 0xFFD5AAD5, 0xFFFFAAD5, // 1101 0000
123 0xFFD580FF, 0xFFFF80FF, 0xFFD5AAFF, 0xFFFFAAFF, // 1101 0100
124 0xFF550055, 0xFF7F0055, 0xFF552A55, 0xFF7F2A55, // 1101 1000
125 0xFF55007F, 0xFF7F007F, 0xFF552A7F, 0xFF7F2A7F, // 1101 1100
126 0xFF80D5D5, 0xFFAAD5D5, 0xFF80FFD5, 0xFFAAFFD5, // 1110 0000
127 0xFF80D5FF, 0xFFAAD5FF, 0xFF80FFFF, 0xFFAAFFFF, // 1110 0100
128 0xFF005555, 0xFF2A5555, 0xFF007F55, 0xFF2A7F55, // 1110 1000
129 0xFF00557F, 0xFF2A557F, 0xFF007F7F, 0xFF2A7F7F, // 1110 1100
130 0xFFD5D5D5, 0xFFFFD5D5, 0xFFD5FFD5, 0xFFFFFFD5, // 1111 0000
131 0xFFD5D5FF, 0xFFFFD5FF, 0xFFD5FFFF, 0xFFFFFFFF, // 1111 0100
132 0xFF555555, 0xFF7F5555, 0xFF557F55, 0xFF7F7F55, // 1111 1000
133 0xFF55557F, 0xFF7F557F, 0xFF557F7F, 0xFF7F7F7F, // 1111 1100
136 void DVBSubtitleCLUT::setEntry(int bpp, u4 index, u1 Y, u1 Cr, u1 Cb, u1 T)
140 if (Y2 == 0) { Y2 = 16; A = 0; } else A = 255 - T;
143 case 2: palette2.setYCrCbA(index, Y2, Cr, Cb, A); break;
144 case 4: palette4.setYCrCbA(index, Y2, Cr, Cb, A); break;
145 case 8: palette8.setYCrCbA(index, Y2, Cr, Cb, A); break;
149 const Palette& DVBSubtitleCLUT::getPalette(int bpp) const
153 case 2: return palette2; break;
154 case 4: return palette4; break;
155 default: return palette8; break;
159 DVBSubtitleRegion::DVBSubtitleRegion()
165 void DVBSubtitleRegion::setDepth(u4 d)
167 if (d < 4) palette.setBpp(1 << d);
170 DVBSubtitlePage::DVBSubtitlePage()
176 serviceAcquired(false)
179 void DVBSubtitlePage::setState(u1 s)
182 if (state == 1) // Acquisition point - page refresh
183 if (!serviceAcquired) // Service not acquired lets treat this as mode change
185 if (state == 2) // Mode change - new epoch
187 serviceAcquired = true;
190 currentDisplay.clear();
191 pendingDisplay.clear();
196 void DVBSubtitlePage::updateRegionPalettes(const DVBSubtitleCLUT& clut)
198 RegionMap::iterator region;
199 for (region = regions.begin(); region != regions.end(); ++region)
200 if (region->second.CLUTid == clut.CLUTid)
201 region->second.palette = clut.getPalette(region->second.palette.getBpp());
204 DVBSubtitleDisplayDefinition::DVBSubtitleDisplayDefinition()
210 class DVBSubtitleObject
217 DVBSubtitleRegion* region;
218 u4 horizontalPosition;
221 typedef std::vector<struct RegionRef> UseList;
226 void initMapTables();
229 void drawLine(const RegionRef& use, u4 x, u4 y, u1 colour, u4 len);
230 u1 get2Bits(const u1* data, u4& index);
231 u1 get4Bits(const u1* data, u4& index);
232 bool decode2BppCodeString(const u1* data, u4& index, const struct RegionRef& use, u4& x, u4 y);
233 bool decode4BppCodeString(const u1* data, u4& index, const struct RegionRef& use, u4& x, u4 y);
234 bool decode8BppCodeString(const u1* data, u4& index, const struct RegionRef& use, u4& x, u4 y);
236 DVBSubtitleObject(u4 id = 0);
237 void setNonModColourFlag(u1 flag) { nonModColourFlag = flag; }
238 void findWhereUsed(DVBSubtitlePage&);
239 void decodeSubBlock(const u1* data, u4 length, bool even);
242 DVBSubtitleObject::DVBSubtitleObject(u4 id)
245 currentMapTable(NULL),
251 void DVBSubtitleObject::initMapTables()
253 map2to4[0] = 0x0; map2to4[1] = 0x7; map2to4[2] = 0x8; map2to4[3] = 0xF;
254 map2to8[0] = 0x00; map2to8[1] = 0x77; map2to8[2] = 0x88; map2to8[3] = 0xFF;
255 map4to8[0] = 0x00; map4to8[1] = 0x11; map4to8[2] = 0x22; map4to8[3] = 0x33;
256 map4to8[4] = 0x44; map4to8[5] = 0x55; map4to8[6] = 0x66; map4to8[7] = 0x77;
257 map4to8[8] = 0x88; map4to8[9] = 0x99; map4to8[10]= 0xAA; map4to8[11]= 0xBB;
258 map4to8[12]= 0xCC; map4to8[13]= 0xDD; map4to8[14]= 0xEE; map4to8[15]= 0xFF;
261 void DVBSubtitleObject::findWhereUsed(DVBSubtitlePage& page)
264 DVBSubtitlePage::RegionMap::iterator i;
265 for (i = page.regions.begin(); i != page.regions.end(); ++i)
267 DVBSubtitleRegion::ObjectList::iterator j;
268 for (j = i->second.objects.begin(); j != i->second.objects.end(); ++j)
270 if (j->objectID == objectID)
272 struct RegionRef usage;
273 usage.region = &(i->second);
274 usage.horizontalPosition = j->horizontalPosition;
275 usage.verticalPosition = j->verticalPosition;
276 useList.push_back(usage);
282 void DVBSubtitleObject::drawLine(const RegionRef& use, u4 x, u4 y,
285 if (nonModColourFlag && colour == 1) return;
286 x += use.horizontalPosition;
287 y += use.verticalPosition;
288 for (u4 pos = x; pos < x + len; ++pos)
289 use.region->setIndex(pos, y, colour);
292 u1 DVBSubtitleObject::get2Bits(const u1* data, u4& index)
294 u1 result = data[index];
301 return (result >> bitPos) & 0x03;
304 u1 DVBSubtitleObject::get4Bits(const u1* data, u4& index)
306 u1 result = data[index];
317 return result & 0x0F;
320 bool DVBSubtitleObject::decode2BppCodeString(const u1* data, u4& index, const struct RegionRef& use, u4& x, u4 y)
322 u4 rl = 0, colour = 0;
323 u1 code = get2Bits(data, index);
331 code = get2Bits(data, index);
332 if (code & 2) // Switch 1
334 rl = ((code & 1) << 2) + get2Bits(data, index) + 3;
335 colour = get2Bits(data, index);
337 else if (code & 1) rl = 1; // Colour 0
340 code = get2Bits(data, index);
344 return false; // end of 2-bit/pixel_code_string
349 rl = (get2Bits(data, index) << 2) + get2Bits(data, index) + 12;
350 colour = get2Bits(data, index);
353 rl = (get2Bits(data, index) << 6) + (get2Bits(data, index) << 4) +
354 (get2Bits(data, index) << 2) + get2Bits(data, index) + 29;
355 colour = get2Bits(data, index);
360 drawLine(use, x, y, colour, rl);
365 bool DVBSubtitleObject::decode4BppCodeString(const u1* data, u4& index, const struct RegionRef& use, u4& x, u4 y)
367 u4 rl = 0, colour = 0;
368 u1 code = get4Bits(data, index);
376 code = get4Bits(data, index);
377 if (code & 8) // Switch 1
379 if (code & 4) // Switch 2
381 switch (code & 3) // Switch 3
390 rl = get4Bits(data, index) + 9;
391 colour = get4Bits(data, index);
394 rl = (get4Bits(data, index) << 4) + get4Bits(data, index) + 25;
395 colour = get4Bits(data, index);
402 colour = get4Bits(data, index);
407 if (!code) return false; // end of 4-bit/pixel_code_string
408 rl = code + 2; // Colour 0
411 drawLine(use, x, y, colour, rl);
416 bool DVBSubtitleObject::decode8BppCodeString(const u1* data, u4& index, const struct RegionRef& use, u4& x, u4 y)
418 u4 rl = 0, colour = 0;
419 u1 code = data[index++];
427 code = data[index++];
430 colour = data[index++];
432 return false; // else colour 0
434 drawLine(use, x, y, colour, rl);
439 void DVBSubtitleObject::decodeSubBlock(const u1* data, u4 length, bool even)
441 UseList::const_iterator use;
442 for (use = useList.begin(); use != useList.end(); ++use)
444 u4 x = 0; u4 y = even ? 0 : 1; u4 index = 0;
445 while (index < length)
447 switch (data[index++])
450 switch (use->region->palette.getBpp())
452 case 8: currentMapTable = map2to8; break;
453 case 4: currentMapTable = map2to4; break;
454 default: currentMapTable = NULL;
457 while (decode2BppCodeString(data, index, *use,x,y) && index < length);
458 if (!bitPos) index++;
461 if (use->region->palette.getBpp() == 8)
462 currentMapTable = map4to8;
464 currentMapTable = NULL;
466 while (decode4BppCodeString(data, index, *use,x,y) && index < length);
467 if (!bitPos) index++;
470 while (decode8BppCodeString(data, index, *use,x,y) && index < length);
473 map2to4[0] = data[index] >> 4;
474 map2to4[1] = data[index++] & 0x0F;
475 map2to4[2] = data[index] >> 4;
476 map2to4[3] = data[index++] & 0x0F;
479 for (int i = 0; i < 4; i++) map2to8[i] = data[index++];
482 for (int i = 0; i < 16; i++) map4to8[i] = data[index++];
492 static u8 PTSDifference(u8 pts1, u8 pts2)
494 // Assume pts1, pts2 < 2^33; calculate pts1 - pts2
498 return (1LL<<33) + pts1 - pts2;
501 static i8 PTSDifference2(u8 now, u8 next)
503 // If next is a bit in the future, return value is small positive
504 // If next is a bit in the past, return value is small negative
505 // If PTS has wrapped then now will be massive, next will be very small
506 // The return value will be very large negative
508 return (i8)next - (i8)now;
511 DVBSubtitles::DVBSubtitles(OSDReceiver* tosd)
513 pageOnDisplay(65536),
519 void DVBSubtitles::put(const PESPacket& packet)
521 // Player object play feed thread comes here, via the demuxer
522 // input_mutex only used in put() here, start(), stop() and threadMethod()
523 // So, roll old Thread::mutex/cond into input_mutex?
528 if (packet.getPTS() != PESPacket::PTS_INVALID)
530 worklist.push_back(packet);
531 signalRecalcWLTO = true;
532 LogNT::getInstance()->debug(TAG, "Received packet: PTS: {}, size: {}, type: {}", packet.getPTS(), packet.getSize(), packet.getPacketType());
535 dvbsCond.notify_one();
539 LogNT::getInstance()->debug(TAG, "PUT DROPPING INVALID PACKET: PTS: {}, size: {}, type: {}", packet.getPTS(), packet.getSize(), packet.getPacketType());
545 // FIXME dvbsubsdebug
546 u8 nowPTS = Video::getInstance()->getCurrentTimestamp();
547 fprintf(DBG, "\033[H\033[2J");
548 fprintf(DBG, "Now: %s %llu\n", tp2str(std::chrono::system_clock::now()).c_str(), nowPTS);
549 for (auto p : worklist)
551 i8 diff = PTSDifference2(nowPTS, p.getPTS());
552 fprintf(DBG, "packet PTS %llu SIZE %i\tTIME %s \tDIFF %lli\n", p.getPTS(), p.getSize(),
553 tp2str(std::chrono::system_clock::now() + std::chrono::milliseconds(diff / 90)).c_str(), diff);
558 input_mutex.unlock();
561 bool DVBSubtitles::decodePacket(const PESPacket& packet)
563 const u1* data = packet.getData();
564 u4 packetSize = packet.getSize();
565 if (packetSize < 9) return false;
566 u4 segmentOffset = data[8] + 11;
567 while (packetSize > segmentOffset)
569 if (data[segmentOffset] == 0xFF) return true; // 'End of PES' marker
570 if (data[segmentOffset] != 0x0F) return false; // Out of sync
571 if (packetSize <= segmentOffset + 5) return false; // Packet truncated
572 u1 segmentType = data[segmentOffset + 1];
573 u4 pageID = (data[segmentOffset + 2] << 8)
574 + data[segmentOffset + 3];
575 u4 segmentLength = (data[segmentOffset + 4] << 8)
576 + data[segmentOffset + 5];
577 if (pageOnDisplay == 65536) pageOnDisplay = pageID;
578 if (pageOnDisplay != pageID) return false;
579 if (packetSize <= segmentOffset + 5 + segmentLength) return false;
580 const u1* segmentData = &data[segmentOffset + 6];
581 pages[pageID]; // Create this page if it doesn't exist
582 PageMap::iterator pageEntry = pages.find(pageID);
583 if (pageEntry == pages.end()) return false;
584 DVBSubtitlePage& page = pageEntry->second;
585 if (packet.getPTS() != PESPacket::PTS_INVALID) page.pts = packet.getPTS();
588 case 0x10: // Page composition
590 if (segmentLength < 2) break;
591 page.setState((segmentData[1] & 0x0C) >> 2);
592 if (!page.serviceAcquired) break;
593 u1 pageVersion = (segmentData[1] & 0xF0) >> 4;
594 if (pageVersion == page.version) break; // No update
595 page.version = pageVersion;
596 page.timeout = segmentData[0];
597 page.pendingDisplay.clear();
599 while (segmentLength > parsePos + 5)
601 u1 regionID = segmentData[parsePos];
602 page.regions[regionID]; // Create the region if it doesn't exist
603 DVBSubtitlePage::Coords coords;
604 coords.x = (segmentData[parsePos + 2] << 8) +
605 segmentData[parsePos + 3];
606 coords.y = (segmentData[parsePos + 4] << 8) +
607 segmentData[parsePos + 5];
608 page.pendingDisplay[regionID] = coords;
613 case 0x11: // Region composition
615 if (segmentLength < 10) break;
616 u1 regionID = segmentData[0];
617 page.regions[regionID]; // Create the region if it doesn't exist
618 DVBSubtitlePage::RegionMap::iterator regionEntry;
619 regionEntry = page.regions.find(regionID);
620 if (regionEntry == page.regions.end()) return false;
621 DVBSubtitleRegion& region = regionEntry->second;
622 u1 regionVersion = (segmentData[1] & 0xF0) >> 4;
623 if (regionVersion == region.version) break; // No update
624 region.version = regionVersion;
625 bool regionFillFlag = (segmentData[1] & 0x08) >> 3;
626 u4 regionWidth = (segmentData[2] << 8) + segmentData[3];
627 u4 regionHeight = (segmentData[4] << 8) + segmentData[5];
628 region.setSize(regionWidth, regionHeight);
629 region.level = (segmentData[6] & 0xE0) >> 5;
630 region.setDepth((segmentData[6] & 0x1C) >> 2);
631 region.CLUTid = segmentData[7];
632 page.CLUTs[region.CLUTid]; // Create the CLUT if it doesn't exist
633 DVBSubtitlePage::CLUTMap::iterator clut;
634 clut = page.CLUTs.find(region.CLUTid);
635 if (clut == page.CLUTs.end()) return false;
636 region.palette = clut->second.getPalette(region.palette.getBpp());
637 if (regionFillFlag) switch (region.palette.getBpp())
639 case 2: region.setAllIndices((segmentData[9] & 0x0C) >> 2); break;
640 case 4: region.setAllIndices((segmentData[9] & 0xF0) >> 4); break;
641 case 8: region.setAllIndices(segmentData[8]); break;
643 region.objects.clear();
645 while (segmentLength > parsePos + 5)
647 u4 objectID = (segmentData[parsePos] << 8) +
648 segmentData[parsePos + 1];
649 DVBSubtitleRegion::ObjectRef objref;
650 objref.objectID = objectID;
651 u4 objectType = (segmentData[parsePos + 2] & 0xC0) >> 6;
652 objref.horizontalPosition = ((segmentData[parsePos + 2] & 0x0F) << 8) +
653 segmentData[parsePos + 3];
654 objref.verticalPosition = ((segmentData[parsePos + 4] & 0x0F) << 8) +
655 segmentData[parsePos + 5];
656 if (objectType == 0) region.objects.push_back(objref);
661 case 0x12: // CLUT definition
663 if (segmentLength < 2) break;
664 u1 CLUTid = segmentData[0];
665 DVBSubtitlePage::CLUTMap::iterator clutEntry;
666 clutEntry = page.CLUTs.find(CLUTid);
667 if (clutEntry == page.CLUTs.end()) return false;
668 DVBSubtitleCLUT& clut = clutEntry->second;
669 clut.CLUTid = CLUTid;
670 u1 clutVersion = (segmentData[1] & 0xF0) >> 4;
671 if (clutVersion == clut.version) break; // No update
672 clut.version = clutVersion;
674 while (segmentLength > parsePos + 3)
676 u1 clutEntryID = segmentData[parsePos];
677 bool fullRangeFlag = segmentData[parsePos + 1] & 0x01;
681 if (segmentLength <= parsePos + 5) break;
682 Y = segmentData[parsePos + 2];
683 Cr = segmentData[parsePos + 3];
684 Cb = segmentData[parsePos + 4];
685 T = segmentData[parsePos + 5];
689 Y = segmentData[parsePos + 2] & 0xFC;
690 Cr = (segmentData[parsePos + 2] & 0x03) << 6;
691 Cr |= (segmentData[parsePos + 3] & 0xC0) >> 2;
692 Cb = (segmentData[parsePos + 3] & 0x3C) << 2;
693 T = (segmentData[parsePos + 3] & 0x03) << 6;
695 u1 entryFlags = segmentData[parsePos + 1];
696 if (entryFlags & 0x80) clut.setEntry(2, clutEntryID, Y,Cr,Cb,T);
697 if (entryFlags & 0x40) clut.setEntry(4, clutEntryID, Y,Cr,Cb,T);
698 if (entryFlags & 0x20) clut.setEntry(8, clutEntryID, Y,Cr,Cb,T);
699 parsePos += fullRangeFlag ? 6 : 4;
701 page.updateRegionPalettes(clut);
704 case 0x13: // Object data
706 if (segmentLength < 3) break;
707 u4 objectID = (segmentData[0] << 8) + segmentData[1];
708 DVBSubtitleObject object(objectID);
709 object.findWhereUsed(page);
710 u1 codingMethod = (segmentData[2] & 0x0C) >> 2;
711 object.setNonModColourFlag(segmentData[2] & 0x01);
712 if (codingMethod == 0x00) // Coding of pixels
714 if (segmentLength < 7) break;
715 u4 topFieldLen = (segmentData[3] << 8) + segmentData[4];
716 u4 bottomFieldLen = (segmentData[5] << 8) + segmentData[6];
717 if (segmentLength < 7 + topFieldLen + bottomFieldLen) break;
718 object.decodeSubBlock(segmentData + 7, topFieldLen, true);
720 object.decodeSubBlock(segmentData + 7 + topFieldLen,
721 bottomFieldLen, false);
723 object.decodeSubBlock(segmentData + 7, topFieldLen, false);
725 else if (codingMethod == 0x01) // Coded as a string of characters
731 case 0x14: {// Display definition
732 if (segmentLength<5) break;
733 u4 ddsversion=(segmentData[0]&0xf0)>>4;
734 if (ddsversion==dds.version) break; // no update ncessary
735 dds.version=ddsversion;
736 dds.displaywindow=!(!(segmentData[0]&0x08));
737 dds.framewidth=(segmentData[1] << 8) + segmentData[2];
738 dds.frameheight=(segmentData[3] << 8) + segmentData[4];
739 if (segmentLength<13) break;
740 if (dds.displaywindow) {
741 dds.windowx=(segmentData[4] << 8) + segmentData[5];
742 dds.windoww=(segmentData[6] << 8) + segmentData[7]-dds.windowx;
743 dds.windowy=(segmentData[8] << 8) + segmentData[9];
744 dds.windowh=(segmentData[10] << 8) + segmentData[11]-dds.windowy;
748 dds.windoww=dds.framewidth;
749 dds.windowh=dds.frameheight;
753 case 0x80: // End of display set
756 page.currentDisplay = page.pendingDisplay;
759 segmentOffset += (segmentLength + 6);
761 return false; // Fall through from while loop: we ran out of data
764 void DVBSubtitles::finishPage(const DVBSubtitlePage& page)
766 if (page.dirty && !osdMenuShowing)
771 DVBSubtitlePage::DisplayMap::const_iterator i,j;
772 for (i = page.currentDisplay.begin(); i != page.currentDisplay.end(); ++i)
773 { // For each currently displayed region, if the region has been
774 // moved or removed, blank out its previous screen position
775 j = page.pendingDisplay.find(i->first);
776 if (j == page.pendingDisplay.end() ||
777 j->second.x != i->second.x ||
778 j->second.y != i->second.y)
780 DVBSubtitlePage::RegionMap::const_iterator region_iter;
781 region_iter = page.regions.find(i->first);
782 if (region_iter == page.regions.end()) continue;
783 LogNT::getInstance()->debug(TAG, "Clear region {}", i->first);
784 if (!osdMenuShowing) osd->clearOSDArea(i->second.x, i->second.y,
785 region_iter->second.getWidth(), region_iter->second.getHeight(),dds);
789 for (i = page.pendingDisplay.begin(); i != page.pendingDisplay.end(); ++i)
790 { // Display each pending region
791 DVBSubtitlePage::RegionMap::const_iterator region_iter;
792 region_iter = page.regions.find(i->first);
793 if (region_iter == page.regions.end()) continue;
794 LogNT::getInstance()->debug(TAG, "Display region {}", i->first);
795 if (!osdMenuShowing) osd->drawOSDBitmap(i->second.x, i->second.y,
796 region_iter->second,dds);
798 // after displaying regions set the page timeout timer
800 subtitleTimeoutPoint = std::chrono::system_clock::now() + std::chrono::seconds(page.timeout);
801 subtitleTimeoutPointActive = true;
803 LogNT::getInstance()->debug(TAG, "SubtitleTimeout {}", page.timeout);
806 void DVBSubtitles::start()
808 LogNT::getInstance()->debug(TAG, "subs start");
812 DBG = fopen("dfifo", "a");
814 fprintf(DBG, "\033[H\033[2J");
821 dds=DVBSubtitleDisplayDefinition();
825 dvbsThread = std::thread([this] { threadMethod(); });
827 input_mutex.unlock();
830 void DVBSubtitles::stop()
832 LogNT::getInstance()->debug(TAG, "subs stop");
837 LogNT::getInstance()->error(TAG, "STOP called, already dead!");
838 input_mutex.unlock();
842 running = false; // disables put()
845 dvbsCond.notify_one();
846 input_mutex.unlock();
850 input_mutex.unlock();
852 clearDisplayedPages();
860 void DVBSubtitles::show()
862 LogNT::getInstance()->debug(TAG, "subs show");
866 output_mutex.unlock();
869 void DVBSubtitles::hide()
871 LogNT::getInstance()->debug(TAG, "subs hide");
874 clearDisplayedPages();
877 void DVBSubtitles::clearDisplayedPages()
881 PageMap::const_iterator pageEntry = pages.find(pageOnDisplay);
882 if (pageEntry != pages.end())
884 const DVBSubtitlePage& page = pageEntry->second;
885 DVBSubtitlePage::DisplayMap::const_iterator i;
886 for (i = page.currentDisplay.begin(); i != page.currentDisplay.end(); ++i)
888 DVBSubtitlePage::RegionMap::const_iterator region_iter;
889 region_iter = page.regions.find(i->first);
890 if (region_iter == page.regions.end()) continue;
891 if (!osdMenuShowing) osd->clearOSDArea(i->second.x, i->second.y,
892 region_iter->second.getWidth(), region_iter->second.getHeight(),dds);
896 pageOnDisplay = 65536;
898 output_mutex.unlock();
901 void DVBSubtitles::pause()
903 LogNT::getInstance()->debug(TAG, "subs pause");
908 LogNT::getInstance()->error(TAG, "pause called, already dead!");
909 input_mutex.unlock();
915 dvbsCond.notify_one();
916 input_mutex.unlock();
919 void DVBSubtitles::unPause()
921 LogNT::getInstance()->debug(TAG, "subs unpause");
926 LogNT::getInstance()->error(TAG, "pause called, already dead!");
927 input_mutex.unlock();
932 signalRecalcWLTO = true;
933 dvbsCond.notify_one();
934 input_mutex.unlock();
939 #pragma warning(disable : 4146)
942 void DVBSubtitles::threadMethod()
944 std::unique_lock<std::mutex> ul(input_mutex); // Lock here, force start() to return before this does anything
949 std::chrono::time_point<std::chrono::system_clock> worklistTimeoutPoint;
950 bool worklistTimeoutPointActive{};
955 // FIXME dvbsubsdebug
956 u8 nowPTS = Video::getInstance()->getCurrentTimestamp();
957 fprintf(DBG, "\033[H\033[2J");
958 fprintf(DBG, "Now: %s %llu\n", tp2str(std::chrono::system_clock::now()).c_str(), nowPTS);
959 for (auto p : worklist)
961 i8 diff = PTSDifference2(nowPTS, p.getPTS());
962 fprintf(DBG, "packet PTS %llu SIZE %i\tTIME %s \tDIFF %lli\n", p.getPTS(), p.getSize(),
963 tp2str(std::chrono::system_clock::now() + std::chrono::milliseconds(diff / 90)).c_str(), diff);
970 LogNT::getInstance()->info(TAG, "Thread exiting");
974 else if (signalPause)
977 subtitleTimeoutPointActive = false;
978 worklistTimeoutPointActive = false;
980 else if (signalRecalcWLTO) // once per incoming packet, and other times
982 signalRecalcWLTO = false;
986 subtitleTimeoutPointActive = false;
987 worklistTimeoutPointActive = false;
992 u8 nowPTS = Video::getInstance()->getCurrentTimestamp();
997 // Video is not started yet. DVBSub packet has come in. Don't set worklistTimeoutPoint,
998 // just store the packet and allow next signal to start things off
999 LogNT::getInstance()->debug(TAG, "signalRecalcWLTO but Video PTS == 0");
1001 else if (worklist.size()) // It is possible to be called to recalc when there are no packets
1003 worklistTimeoutPointActive = true;
1004 LogNT::getInstance()->debug(TAG, "Calc: Num packets available: {}", worklist.size());
1006 u8 pktPTS = worklist.front().getPTS();
1007 i8 diff = PTSDifference2(nowPTS, pktPTS);
1008 LogNT::getInstance()->debug(TAG, "Calc'd new worklistTimeoutPoint. pktPTS {} nowPTS {} diff {}", pktPTS, nowPTS, diff);
1009 diff /= 90; // convert diff to ms (PTS difference is in 1/90000s)
1011 if ((diff > 0) && (diff < (60 * 1000))) // Packet is in the next minute
1013 worklistTimeoutPoint = std::chrono::system_clock::now() + std::chrono::milliseconds(diff);
1014 LogNT::getInstance()->debug(TAG, "Calc'd new worklistTimeoutPoint 1 {}", tp2str(worklistTimeoutPoint));
1016 else if ((diff <= 0) && (diff > -(60 * 1000))) // Packet is in the past minute
1018 worklistTimeoutPoint = std::chrono::system_clock::now();
1019 LogNT::getInstance()->debug(TAG, "Problem packet 1");
1022 waitExpireWL = true;
1025 else if (diff < 95300000)
1029 u8 newDelay = (1LL<<33) - pktPTS + nowPTS;
1032 worklistTimeoutPoint = std::chrono::system_clock::now() + std::chrono::milliseconds(newDelay);
1033 LogNT::getInstance()->debug(TAG, "Calc'd new worklistTimeoutPoint 2 {}", tp2str(worklistTimeoutPoint));
1037 // We have a problem. An action so far in the future should have
1038 // been culled. Probably the action is already due and PTSDifference
1039 // wrapped around. Therefore we sleep for a minimal time instead.
1041 // FIXME check if this still works
1042 worklistTimeoutPoint = std::chrono::system_clock::now();
1043 LogNT::getInstance()->debug(TAG, "Problem packet");
1046 waitExpireWL = true;
1052 else if (waitExpireST) // do real work - subtitletimeout
1054 LogNT::getInstance()->debug(TAG, "Subtitle timeout occurred");
1055 waitExpireST = false;
1056 subtitleTimeoutPointActive = false;
1057 output_mutex.lock();
1058 if (showing && !osdMenuShowing) osd->clearOSD();
1059 output_mutex.unlock();
1061 else if (waitExpireWL) // do real work - worklist - could be multiple packets to do
1063 LogNT::getInstance()->debug(TAG, "Something to do");
1065 waitExpireWL = false;
1066 worklistTimeoutPointActive = false;
1068 // u8 nowPTS = Video::getInstance()->getCurrentTimestamp();
1070 // Guaranteed to be at least one packet in the worklist
1071 PESPacket packet = worklist.front();
1072 //u8 pktPTS = worklist.front().getPTS();
1073 u8 pktPTS = packet.getPTS();
1077 worklist.pop_front();
1078 LogNT::getInstance()->debug(TAG, "Consumed packet with PTS {}", pktPTS);
1081 output_mutex.lock();
1082 if (showing) decodePacket(packet);
1083 output_mutex.unlock();
1085 // Repeat this loop only if there is another packet and it's the same PTS as this one
1086 // Not sure this ever happens.
1087 if (worklist.size() && (worklist.front().getPTS() == pktPTS))
1089 packet = worklist.front();
1098 if (worklist.size())
1100 signalRecalcWLTO = true;
1101 continue; // restart the loop and force a recalc
1108 if (PTSDifference(nowPTS, pktPTS) < 2*90000 // need this? we should be here exactly on time
1109 || PTSDifference(pktPTS, nowPTS) < 4000)
1110 { // It is due for processing or discarding.
1111 worklist.pop_front();
1113 output_mutex.lock();
1114 if (showing) decodePacket(packet);
1115 output_mutex.unlock();
1117 else if (PTSDifference(pktPTS, nowPTS) >= 60*90000)
1118 { // Seems like a bad or very old entry. Get rid of it.
1120 worklist.pop_front();
1125 // Loop has exited. Either worklist is now empty or the next packet doesn't match the PTSDifference ranges
1133 // 1. sleep until timeout
1134 // 2. sleep until next worklist
1135 // 3. sleep until signalled
1137 if (!subtitleTimeoutPointActive && !worklistTimeoutPointActive)
1139 LogNT::getInstance()->debug(TAG, "No WL or STT. Infinite wait");
1140 dvbsCond.wait(ul); // Wait until signalled
1142 else if (subtitleTimeoutPointActive)
1144 if (worklistTimeoutPointActive && (worklistTimeoutPoint < subtitleTimeoutPoint))
1146 LogNT::getInstance()->debug(TAG, "WaitFor: WL");
1147 if (dvbsCond.wait_until(ul, worklistTimeoutPoint) == std::cv_status::timeout) waitExpireWL = true;
1151 LogNT::getInstance()->debug(TAG, "WaitFor: ST");
1152 if (dvbsCond.wait_until(ul, subtitleTimeoutPoint) == std::cv_status::timeout) waitExpireST = true;
1157 LogNT::getInstance()->debug(TAG, "WaitFor: WL");
1158 if (dvbsCond.wait_until(ul, worklistTimeoutPoint) == std::cv_status::timeout) waitExpireWL = true;
1163 struct timespec sleeptime;
1164 sleeptime.tv_sec = 0;
1165 sleeptime.tv_nsec = 0;
1166 uint64_t wakeup = 0;
1167 timeout_clear=false;
1170 if (subtitleTimeoutPointActive && (subtitleTimeoutPoint < std::chrono::system_clock::now())) // do we have a subtitle timeout
1172 subtitleTimeoutPointActive = false;
1175 // if (SubtitleTimeout.TimedOut()) // do we have a subtitle timeout
1177 output_mutex.lock();
1178 if (showing && !osdMenuShowing) {
1179 if (!timeout_clear) {
1180 osd->clearOSD(); // if we have the timeout, lets clear the OSD
1184 output_mutex.unlock();
1186 else if (showing) // if not lets check when will we have it
1188 // Get milliseconds until the timeout
1189 wakeup = std::chrono::duration_cast<std::chrono::milliseconds>(subtitleTimeoutPoint - std::chrono::system_clock::now()).count();
1190 LogNT::getInstance()->debug(TAG, "A1: {}", wakeup);
1193 wakeup = -(SubtitleTimeout.Elapsed());
1195 LogNT::getInstance()->debug(TAG, "A2: {}", wakeup);
1197 if (wakeup > 0 && sleeptime.tv_nsec == 0 && sleeptime.tv_sec == 0)
1198 // We are not done, we still have a Subtitle Timeout!
1200 sleeptime.tv_sec = (int)(wakeup / 1000);
1201 sleeptime.tv_nsec = (long)(wakeup % 1000) * 10000L / 1000L * 100000L;
1202 timeout_clear=false;
1207 //if (dvbsThreadStop) return;
1213 { // We have done the current work and no more has arrived. Sleep.
1214 if (sleeptime.tv_sec == 0 && sleeptime.tv_nsec == 0)
1216 LogNT::getInstance()->debug(TAG, "Sleeping until nudged.");
1217 threadWaitForSignal();
1221 LogNT::getInstance()->debug(TAG, "Sleeping for {} and {}", sleeptime.tv_sec, sleeptime.tv_nsec);
1222 struct timespec targetTime;
1224 getClockRealTime(&targetTime);
1226 targetTime.tv_nsec += sleeptime.tv_nsec;
1227 if (targetTime.tv_nsec > 999999999)
1229 targetTime.tv_sec += 1;
1230 targetTime.tv_nsec -= 1000000000;
1232 targetTime.tv_sec += sleeptime.tv_sec;
1233 threadWaitForSignalTimed(&targetTime);
1236 threadNudged = false;
1238 bool finished = false;
1245 pktPTS = PESPacket::PTS_INVALID;
1247 if (!worklist.empty())
1248 pktPTS = worklist.front().getPTS();
1249 input_mutex.unlock();
1250 if (pktPTS != PESPacket::PTS_INVALID)
1251 { // An entry exists in the work list
1252 nowPTS = Video::getInstance()->getCurrentTimestamp();
1253 if (PTSDifference(nowPTS, pktPTS) < 2*90000 ||
1254 PTSDifference(pktPTS, nowPTS) < 4000)
1255 { // It is due for processing or discarding.
1258 if (!worklist.empty())
1260 PESPacket packet = worklist.front();
1261 worklist.pop_front();
1262 input_mutex.unlock();
1263 output_mutex.lock();
1264 if (showing) decodePacket(packet);
1265 output_mutex.unlock();
1267 else input_mutex.unlock();
1269 else if (PTSDifference(pktPTS, nowPTS) >= 60*90000)
1270 { // Seems like a bad or very old entry. Get rid of it.
1273 if (!worklist.empty()) worklist.pop_front();
1274 input_mutex.unlock();
1278 // Calculate how long to sleep for
1279 wait = SUBTITLE_TIMEOUT_MS; // Max sleep while checking subtitle timeout
1280 sleeptime.tv_sec = 0;
1281 sleeptime.tv_nsec = 0;
1282 if (pktPTS != PESPacket::PTS_INVALID)
1283 { // A future action exists
1284 nowPTS = Video::getInstance()->getCurrentTimestamp();
1285 u8 diff = PTSDifference(pktPTS, nowPTS);
1286 diff /= 90; // convert diff in ms as PTS difference in 1/90000s
1287 if (diff < 60 * 1000)
1289 if (diff < (u8)wait)
1291 sleeptime.tv_nsec = (long)(wait % 1000) * 10000L / 1000L * 100000L;
1294 { // We have a problem. An action so far in the future should have
1295 // been culled. Probably the action is already due and PTSDifference
1296 // wrapped around. Therefore we sleep for a minimal time instead.
1297 sleeptime.tv_nsec = 1;
1305 void DVBSubtitles::setOSDMenuVisibility(bool visible)
1307 output_mutex.lock();
1308 osdMenuShowing = visible;
1309 output_mutex.unlock();