--- /dev/null
+/*
+ Copyright 2008 Mark Calderbank
+
+ This file is part of VOMP.
+
+ VOMP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ VOMP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with VOMP; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+/* Sections of code and general methods adopted from VDR version 1.6.0
+ * dvbsubtitle.c (GPL version 2 or later)
+ * Original author: Marco Schluessler <marco@lordzodiac.de>
+ * With some input from the "subtitle plugin"
+ * by Pekka Virtanen <pekka.virtanen@sci.fi>
+*/
+
+/* Reference ETSI EN 300 743 V1.3.1 (2006-11) */
+
+#include "dvbsubtitles.h"
+#include "demuxer.h"
+#include "osdreceiver.h"
+#include "video.h"
+#include "log.h"
+
+DVBSubtitleCLUT::DVBSubtitleCLUT()
+: version(0xFF),
+ palette2(2),
+ palette4(4),
+ palette8(8)
+{
+ for (int i = 0; i < 4; ++i) palette2.setColour(i, defaultPalette2[i]);
+ for (int i = 0; i < 16; ++i) palette4.setColour(i, defaultPalette4[i]);
+ for (int i = 0; i < 256; ++i) palette8.setColour(i, defaultPalette8[i]);
+}
+
+const ULONG DVBSubtitleCLUT::defaultPalette2[4] = {
+ 0x00000000, 0xFFFFFFFF, 0xFF000000, 0xFF808080
+};
+
+const ULONG DVBSubtitleCLUT::defaultPalette4[16] = {
+ 0x00000000, 0xFFFF0000, 0xFF00FF00, 0xFFFFFF00,
+ 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFFFFFFFF,
+ 0xFF000000, 0xFF800000, 0xFF008000, 0xFF808000,
+ 0xFF000080, 0xFF800080, 0xFF008080, 0xFF808080
+};
+
+const ULONG DVBSubtitleCLUT::defaultPalette8[256] = {
+ 0x00000000, 0x40FF0000, 0x4000FF00, 0x40FFFF00, // 0000 0000
+ 0x400000FF, 0x40FF00FF, 0x4000FFFF, 0x40FFFFFF, // 0000 0100
+ 0x80000000, 0x80550000, 0x80005500, 0x80555500, // 0000 1000
+ 0x80000055, 0x80550055, 0x80005555, 0x80555555, // 0000 1100
+ 0xFFAA0000, 0xFFFF0000, 0xFFAA5500, 0xFFFF5500, // 0001 0000
+ 0xFFAA0055, 0xFFFF0055, 0xFFAA5555, 0xFFFF5555, // 0001 0100
+ 0x80AA0000, 0x80FF0000, 0x80AA5500, 0x80FF5500, // 0001 1000
+ 0x80AA0055, 0x80FF0055, 0x80AA5555, 0x80FF5555, // 0001 1100
+ 0xFF00AA00, 0xFF55AA00, 0xFF00FF00, 0xFF55FF00, // 0010 0000
+ 0xFF00AA55, 0xFF55AA55, 0xFF00FF55, 0xFF55FF55, // 0010 0100
+ 0x8000AA00, 0x8055AA00, 0x8000FF00, 0x8055FF00, // 0010 1000
+ 0x8000AA55, 0x8055AA55, 0x8000FF55, 0x8055FF55, // 0010 1100
+ 0xFFAAAA00, 0xFFFFAA00, 0xFFAAFF00, 0xFFFFFF00, // 0011 0000
+ 0xFFAAAA55, 0xFFFFAA55, 0xFFAAFF55, 0xFFFFFF55, // 0011 0100
+ 0x80AAAA00, 0x80FFAA00, 0x80AAFF00, 0x80FFFF00, // 0011 1000
+ 0x80AAAA55, 0x80FFAA55, 0x80AAFF55, 0x80FFFF55, // 0011 1100
+ 0xFF0000AA, 0xFF5500AA, 0xFF0055AA, 0xFF5555AA, // 0100 0000
+ 0xFF0000FF, 0xFF5500FF, 0xFF0055FF, 0xFF5555FF, // 0100 0100
+ 0x800000AA, 0x805500AA, 0x800055AA, 0x805555AA, // 0100 1000
+ 0x800000FF, 0x805500FF, 0x800055FF, 0x805555FF, // 0100 1100
+ 0xFFAA00AA, 0xFFFF00AA, 0xFFAA55AA, 0xFFFF55AA, // 0101 0000
+ 0xFFAA00FF, 0xFFFF00FF, 0xFFAA55FF, 0xFFFF55FF, // 0101 0100
+ 0x80AA00AA, 0x80FF00AA, 0x80AA55AA, 0x80FF55AA, // 0101 1000
+ 0x80AA00FF, 0x80FF00FF, 0x80AA55FF, 0x80FF55FF, // 0101 1100
+ 0xFF00AAAA, 0xFF55AAAA, 0xFF00FFAA, 0xFF55FFAA, // 0110 0000
+ 0xFF00AAFF, 0xFF55AAFF, 0xFF00FFFF, 0xFF55FFFF, // 0110 0100
+ 0x8000AAAA, 0x8055AAAA, 0x8000FFAA, 0x8055FFAA, // 0110 1000
+ 0x8000AAFF, 0x8055AAFF, 0x8000FFFF, 0x8055FFFF, // 0110 1100
+ 0xFFAAAAAA, 0xFFFFAAAA, 0xFFAAFFAA, 0xFFFFFFAA, // 0111 0000
+ 0xFFAAAAFF, 0xFFFFAAFF, 0xFFAAFFFF, 0xFFFFFFFF, // 0111 0100
+ 0x80AAAAAA, 0x80FFAAAA, 0x80AAFFAA, 0x80FFFFAA, // 0111 1000
+ 0x80AAAAFF, 0x80FFAAFF, 0x80AAFFFF, 0x80FFFFFF, // 0111 1100
+ 0xFF808080, 0xFFAA8080, 0xFF80AA80, 0xFFAAAA80, // 1000 0000
+ 0xFF8080AA, 0xFFAA80AA, 0xFF80AAAA, 0xFFAAAAAA, // 1000 0100
+ 0xFF000000, 0xFF2A0000, 0xFF002A00, 0xFF2A2A00, // 1000 1000
+ 0xFF00002A, 0xFF2A002A, 0xFF002A2A, 0xFF2A2A2A, // 1000 1100
+ 0xFFD58080, 0xFFFF8080, 0xFFD5AA80, 0xFFFFAA80, // 1001 0000
+ 0xFFD580AA, 0xFFFF80AA, 0xFFD5AAAA, 0xFFFFAAAA, // 1001 0100
+ 0xFF550000, 0xFF7F0000, 0xFF552A00, 0xFF7F2A00, // 1001 1000
+ 0xFF55002A, 0xFF7F002A, 0xFF552A2A, 0xFF7F2A2A, // 1001 1100
+ 0xFF80D580, 0xFFAAD580, 0xFF80FF80, 0xFFAAFF80, // 1010 0000
+ 0xFF80D5AA, 0xFFAAD5AA, 0xFF80FFAA, 0xFFAAFFAA, // 1010 0100
+ 0xFF005500, 0xFF2A5500, 0xFF007F00, 0xFF2A7F00, // 1010 1000
+ 0xFF00552A, 0xFF2A552A, 0xFF007F2A, 0xFF2A7F2A, // 1010 1100
+ 0xFFD5D580, 0xFFFFD580, 0xFFD5FF80, 0xFFFFFF80, // 1011 0000
+ 0xFFD5D5AA, 0xFFFFD5AA, 0xFFD5FFAA, 0xFFFFFFAA, // 1011 0100
+ 0xFF555500, 0xFF7F5500, 0xFF557F00, 0xFF7F7F00, // 1011 1000
+ 0xFF55552A, 0xFF7F552A, 0xFF557F2A, 0xFF7F7F2A, // 1011 1100
+ 0xFF8080D5, 0xFFAA80D5, 0xFF80AAD5, 0xFFAAAAD5, // 1100 0000
+ 0xFF8080FF, 0xFFAA80FF, 0xFF80AAFF, 0xFFAAAAFF, // 1100 0100
+ 0xFF000055, 0xFF2A0055, 0xFF002A55, 0xFF2A2A55, // 1100 1000
+ 0xFF00007F, 0xFF2A007F, 0xFF002A7F, 0xFF2A2A7F, // 1100 1100
+ 0xFFD580D5, 0xFFFF80D5, 0xFFD5AAD5, 0xFFFFAAD5, // 1101 0000
+ 0xFFD580FF, 0xFFFF80FF, 0xFFD5AAFF, 0xFFFFAAFF, // 1101 0100
+ 0xFF550055, 0xFF7F0055, 0xFF552A55, 0xFF7F2A55, // 1101 1000
+ 0xFF55007F, 0xFF7F007F, 0xFF552A7F, 0xFF7F2A7F, // 1101 1100
+ 0xFF80D5D5, 0xFFAAD5D5, 0xFF80FFD5, 0xFFAAFFD5, // 1110 0000
+ 0xFF80D5FF, 0xFFAAD5FF, 0xFF80FFFF, 0xFFAAFFFF, // 1110 0100
+ 0xFF005555, 0xFF2A5555, 0xFF007F55, 0xFF2A7F55, // 1110 1000
+ 0xFF00557F, 0xFF2A557F, 0xFF007F7F, 0xFF2A7F7F, // 1110 1100
+ 0xFFD5D5D5, 0xFFFFD5D5, 0xFFD5FFD5, 0xFFFFFFD5, // 1111 0000
+ 0xFFD5D5FF, 0xFFFFD5FF, 0xFFD5FFFF, 0xFFFFFFFF, // 1111 0100
+ 0xFF555555, 0xFF7F5555, 0xFF557F55, 0xFF7F7F55, // 1111 1000
+ 0xFF55557F, 0xFF7F557F, 0xFF557F7F, 0xFF7F7F7F, // 1111 1100
+};
+
+void DVBSubtitleCLUT::setEntry(int bpp, UINT index, UCHAR Y, UCHAR Cr, UCHAR Cb, UCHAR T)
+{
+ UCHAR Y2 = Y;
+ UCHAR A;
+ if (Y2 == 0) { Y2 = 16; A = 0; } else A = 255 - T;
+ switch (bpp)
+ {
+ case 2: palette2.setYCrCbA(index, Y2, Cr, Cb, A); break;
+ case 4: palette4.setYCrCbA(index, Y2, Cr, Cb, A); break;
+ case 8: palette8.setYCrCbA(index, Y2, Cr, Cb, A); break;
+ }
+}
+
+const Palette& DVBSubtitleCLUT::getPalette(int bpp) const
+{
+ switch (bpp)
+ {
+ case 2: return palette2; break;
+ case 4: return palette4; break;
+ default: return palette8; break;
+ }
+}
+
+DVBSubtitleRegion::DVBSubtitleRegion()
+: version(0xFF),
+ level(0),
+ CLUTid(0)
+{}
+
+void DVBSubtitleRegion::setDepth(UINT d)
+{
+ if (d < 4) palette.setBpp(1 << d);
+}
+
+DVBSubtitlePage::DVBSubtitlePage()
+: pts(0),
+ dirty(false),
+ version(0xFF),
+ timeout(0),
+ state(0),
+ serviceAcquired(false)
+{}
+
+void DVBSubtitlePage::setState(UCHAR s)
+{
+ state = s;
+ if (state == 1)
+ serviceAcquired = true;
+ if (state == 2) // Mode change
+ {
+ serviceAcquired = true;
+ dirty = true;
+ regions.clear();
+ currentDisplay.clear();
+ pendingDisplay.clear();
+ CLUTs.clear();
+ }
+}
+
+void DVBSubtitlePage::updateRegionPalettes(const DVBSubtitleCLUT& clut)
+{
+ RegionMap::iterator region;
+ for (region = regions.begin(); region != regions.end(); ++region)
+ if (region->second.CLUTid == clut.CLUTid)
+ region->second.palette = clut.getPalette(region->second.palette.getBpp());
+}
+
+class DVBSubtitleObject
+{
+ private:
+ UINT objectID;
+ UCHAR nonModColourFlag;
+ struct RegionRef
+ {
+ DVBSubtitleRegion* region;
+ UINT horizontalPosition;
+ UINT verticalPosition;
+ };
+ typedef std::vector<struct RegionRef> UseList;
+ UseList useList;
+ UCHAR map2to4[4];
+ UCHAR map2to8[4];
+ UCHAR map4to8[16];
+ void initMapTables();
+ UCHAR* currentMapTable;
+ UINT bitPos;
+ void drawLine(const RegionRef& use, UINT x, UINT y, UCHAR colour, UINT len);
+ UCHAR get2Bits(const UCHAR* data, UINT& index);
+ UCHAR get4Bits(const UCHAR* data, UINT& index);
+ bool decode2BppCodeString(const UCHAR* data, UINT& index, const struct RegionRef& use, UINT& x, UINT y);
+ bool decode4BppCodeString(const UCHAR* data, UINT& index, const struct RegionRef& use, UINT& x, UINT y);
+ bool decode8BppCodeString(const UCHAR* data, UINT& index, const struct RegionRef& use, UINT& x, UINT y);
+ public:
+ DVBSubtitleObject(UINT id = 0);
+ void setNonModColourFlag(UCHAR flag) { nonModColourFlag = flag; }
+ void findWhereUsed(DVBSubtitlePage&);
+ void decodeSubBlock(const UCHAR* data, UINT length, bool even);
+};
+
+DVBSubtitleObject::DVBSubtitleObject(UINT id)
+: objectID(id),
+ nonModColourFlag(0),
+ currentMapTable(NULL),
+ bitPos(0)
+{
+ initMapTables();
+}
+
+void DVBSubtitleObject::initMapTables()
+{
+ map2to4[0] = 0x0; map2to4[1] = 0x7; map2to4[2] = 0x8; map2to4[3] = 0xF;
+ map2to8[0] = 0x00; map2to8[1] = 0x77; map2to8[2] = 0x88; map2to8[3] = 0xFF;
+ map4to8[0] = 0x00; map4to8[1] = 0x11; map4to8[2] = 0x22; map4to8[3] = 0x33;
+ map4to8[4] = 0x44; map4to8[5] = 0x55; map4to8[6] = 0x66; map4to8[7] = 0x77;
+ map4to8[8] = 0x88; map4to8[9] = 0x99; map4to8[10]= 0xAA; map4to8[11]= 0xBB;
+ map4to8[12]= 0xCC; map4to8[13]= 0xDD; map4to8[14]= 0xEE; map4to8[15]= 0xFF;
+}
+
+void DVBSubtitleObject::findWhereUsed(DVBSubtitlePage& page)
+{
+ useList.clear();
+ DVBSubtitlePage::RegionMap::iterator i;
+ for (i = page.regions.begin(); i != page.regions.end(); ++i)
+ {
+ DVBSubtitleRegion::ObjectList::iterator j;
+ for (j = i->second.objects.begin(); j != i->second.objects.end(); ++j)
+ {
+ if (j->objectID == objectID)
+ {
+ struct RegionRef usage;
+ usage.region = &(i->second);
+ usage.horizontalPosition = j->horizontalPosition;
+ usage.verticalPosition = j->verticalPosition;
+ useList.push_back(usage);
+ }
+ }
+ }
+}
+
+void DVBSubtitleObject::drawLine(const RegionRef& use, UINT x, UINT y,
+ UCHAR colour, UINT len)
+{
+ if (nonModColourFlag && colour == 1) return;
+ x += use.horizontalPosition;
+ y += use.verticalPosition;
+ for (UINT pos = x; pos < x + len; ++pos)
+ use.region->setIndex(pos, y, colour);
+}
+
+UCHAR DVBSubtitleObject::get2Bits(const UCHAR* data, UINT& index)
+{
+ UCHAR result = data[index];
+ if (!bitPos)
+ {
+ index++;
+ bitPos = 8;
+ }
+ bitPos -= 2;
+ return (result >> bitPos) & 0x03;
+}
+
+UCHAR DVBSubtitleObject::get4Bits(const UCHAR* data, UINT& index)
+{
+ UCHAR result = data[index];
+ if (!bitPos)
+ {
+ index++;
+ bitPos = 4;
+ }
+ else
+ {
+ result >>= 4;
+ bitPos -= 4;
+ }
+ return result & 0x0F;
+}
+
+bool DVBSubtitleObject::decode2BppCodeString(const UCHAR* data, UINT& index, const struct RegionRef& use, UINT& x, UINT y)
+{
+ UINT rl = 0, colour = 0;
+ UCHAR code = get2Bits(data, index);
+ if (code)
+ {
+ colour = code;
+ rl = 1;
+ }
+ else
+ {
+ code = get2Bits(data, index);
+ if (code & 2) // Switch 1
+ {
+ rl = ((code & 1) << 2) + get2Bits(data, index) + 3;
+ colour = get2Bits(data, index);
+ }
+ else if (code & 1) rl = 1; // Colour 0
+ else
+ {
+ code = get2Bits(data, index);
+ switch (code)
+ {
+ case 0:
+ return false; // end of 2-bit/pixel_code_string
+ case 1:
+ rl = 2; // Colour 0
+ break;
+ case 2:
+ rl = (get2Bits(data, index) << 2) + get2Bits(data, index) + 12;
+ colour = get2Bits(data, index);
+ break;
+ case 3:
+ rl = (get2Bits(data, index) << 6) + (get2Bits(data, index) << 4) +
+ (get2Bits(data, index) << 2) + get2Bits(data, index) + 29;
+ colour = get2Bits(data, index);
+ break;
+ }
+ }
+ }
+ drawLine(use, x, y, colour, rl);
+ x += rl;
+ return true;
+}
+
+bool DVBSubtitleObject::decode4BppCodeString(const UCHAR* data, UINT& index, const struct RegionRef& use, UINT& x, UINT y)
+{
+ UINT rl = 0, colour = 0;
+ UCHAR code = get4Bits(data, index);
+ if (code)
+ {
+ colour = code;
+ rl = 1;
+ }
+ else
+ {
+ code = get4Bits(data, index);
+ if (code & 8) // Switch 1
+ {
+ if (code & 4) // Switch 2
+ {
+ switch (code & 3) // Switch 3
+ {
+ case 0: // Colour 0
+ rl = 1;
+ break;
+ case 1: // Colour 0
+ rl = 2;
+ break;
+ case 2:
+ rl = get4Bits(data, index) + 9;
+ colour = get4Bits(data, index);
+ break;
+ case 3:
+ rl = (get4Bits(data, index) << 4) + get4Bits(data, index) + 25;
+ colour = get4Bits(data, index);
+ break;
+ }
+ }
+ else
+ {
+ rl = (code & 3) + 4;
+ colour = get4Bits(data, index);
+ }
+ }
+ else
+ {
+ if (!code) return false; // end of 4-bit/pixel_code_string
+ rl = code + 2; // Colour 0
+ }
+ }
+ drawLine(use, x, y, colour, rl);
+ x += rl;
+ return true;
+}
+
+bool DVBSubtitleObject::decode8BppCodeString(const UCHAR* data, UINT& index, const struct RegionRef& use, UINT& x, UINT y)
+{
+ UINT rl = 0, colour = 0;
+ UCHAR code = data[index++];
+ if (code)
+ {
+ colour = code;
+ rl = 1;
+ }
+ else
+ {
+ code = data[index++];
+ rl = code & 0x7F;
+ if (code & 0x80)
+ colour = data[index++];
+ else if (!rl)
+ return false; // else colour 0
+ }
+ drawLine(use, x, y, colour, rl);
+ x += rl;
+ return true;
+}
+
+void DVBSubtitleObject::decodeSubBlock(const UCHAR* data, UINT length, bool even)
+{
+ UseList::const_iterator use;
+ for (use = useList.begin(); use != useList.end(); ++use)
+ {
+ UINT x = 0; UINT y = even ? 0 : 1; UINT index = 0;
+ while (index < length)
+ {
+ switch (data[index++])
+ {
+ case 0x10:
+ switch (use->region->palette.getBpp())
+ {
+ case 8: currentMapTable = map2to8; break;
+ case 4: currentMapTable = map2to4; break;
+ default: currentMapTable = NULL;
+ }
+ bitPos = 8;
+ while (decode2BppCodeString(data, index, *use,x,y) && index < length);
+ if (!bitPos) index++;
+ break;
+ case 0x11:
+ if (use->region->palette.getBpp() == 8)
+ currentMapTable = map4to8;
+ else
+ currentMapTable = NULL;
+ bitPos = 4;
+ while (decode4BppCodeString(data, index, *use,x,y) && index < length);
+ if (!bitPos) index++;
+ break;
+ case 0x12:
+ while (decode8BppCodeString(data, index, *use,x,y) && index < length);
+ break;
+ case 0x20:
+ map2to4[0] = data[index] >> 4;
+ map2to4[1] = data[index++] & 0x0F;
+ map2to4[2] = data[index] >> 4;
+ map2to4[3] = data[index++] & 0x0F;
+ break;
+ case 0x21:
+ for (int i = 0; i < 4; i++) map2to8[i] = data[index++];
+ break;
+ case 0x22:
+ for (int i = 0; i < 16; i++) map4to8[i] = data[index++];
+ break;
+ case 0xF0:
+ x = 0; y += 2;
+ break;
+ }
+ }
+ }
+}
+
+static ULLONG PTSDifference(ULLONG pts1, ULLONG pts2)
+{
+ // Assume pts1, pts2 < 2^33; calculate pts1 - pts2
+ if (pts1 > pts2)
+ return pts1 - pts2;
+ else
+ return (1LL<<33) + pts1 - pts2;
+}
+
+DVBSubtitles::DVBSubtitles(OSDReceiver* tosd)
+: osd(tosd),
+ pageOnDisplay(65536),
+ running(false),
+ showing(false),
+ threadNudged(false)
+{
+#ifndef WIN32
+ pthread_mutex_init(&input_mutex, NULL);
+ pthread_mutex_init(&output_mutex, NULL);
+#else
+ input_mutex=CreateMutex(NULL,FALSE,NULL);
+ output_mutex=CreateMutex(NULL,FALSE,NULL);
+#endif
+}
+
+void DVBSubtitles::put(const PESPacket& packet)
+{
+ lockInput();
+ if (running)
+ {
+ if (packet.getPTS() != PESPacket::PTS_INVALID)
+ {
+ worklist.push_back(packet);
+ nudge();
+ }
+ }
+ unlockInput();
+}
+
+bool DVBSubtitles::decodePacket(const PESPacket& packet)
+{
+ const std::vector<UCHAR>& data = packet.getData();
+ UINT packetSize = packet.getSize();
+ if (packetSize < 9) return false;
+ UINT segmentOffset = data[8] + 11;
+ while (packetSize > segmentOffset)
+ {
+ if (data[segmentOffset] == 0xFF) return true; // 'End of PES' marker
+ if (data[segmentOffset] != 0x0F) return false; // Out of sync
+ if (packetSize <= segmentOffset + 5) return false; // Packet truncated
+ UCHAR segmentType = data[segmentOffset + 1];
+ UINT pageID = (data[segmentOffset + 2] << 8)
+ + data[segmentOffset + 3];
+ UINT segmentLength = (data[segmentOffset + 4] << 8)
+ + data[segmentOffset + 5];
+ if (pageOnDisplay == 65536) pageOnDisplay = pageID;
+ if (pageOnDisplay != pageID) return false;
+ if (packetSize <= segmentOffset + 5 + segmentLength) return false;
+ const UCHAR* segmentData = &data[segmentOffset + 6];
+ pages[pageID]; // Create this page if it doesn't exist
+ PageMap::iterator pageEntry = pages.find(pageID);
+ if (pageEntry == pages.end()) return false;
+ DVBSubtitlePage& page = pageEntry->second;
+ if (packet.getPTS() != PESPacket::PTS_INVALID) page.pts = packet.getPTS();
+ switch (segmentType)
+ {
+ case 0x10: // Page composition
+ {
+ if (segmentLength < 2) break;
+ page.setState((segmentData[1] & 0x0C) >> 2);
+ if (!page.serviceAcquired) break;
+ UCHAR pageVersion = (segmentData[1] & 0xF0) >> 4;
+ if (pageVersion == page.version) break; // No update
+ page.version = pageVersion;
+ page.timeout = segmentData[0];
+ page.pendingDisplay.clear();
+ UINT parsePos = 2;
+ while (segmentLength > parsePos + 5)
+ {
+ UCHAR regionID = segmentData[parsePos];
+ page.regions[regionID]; // Create the region if it doesn't exist
+ DVBSubtitlePage::Coords coords;
+ coords.x = (segmentData[parsePos + 2] << 8) +
+ segmentData[parsePos + 3];
+ coords.y = (segmentData[parsePos + 4] << 8) +
+ segmentData[parsePos + 5];
+ page.pendingDisplay[regionID] = coords;
+ parsePos += 6;
+ }
+ break;
+ }
+ case 0x11: // Region composition
+ {
+ if (segmentLength < 10) break;
+ UCHAR regionID = segmentData[0];
+ page.regions[regionID]; // Create the region if it doesn't exist
+ DVBSubtitlePage::RegionMap::iterator regionEntry;
+ regionEntry = page.regions.find(regionID);
+ if (regionEntry == page.regions.end()) return false;
+ DVBSubtitleRegion& region = regionEntry->second;
+ UCHAR regionVersion = (segmentData[1] & 0xF0) >> 4;
+ if (regionVersion == region.version) break; // No update
+ region.version = regionVersion;
+ bool regionFillFlag = (segmentData[1] & 0x08) >> 3;
+ UINT regionWidth = (segmentData[2] << 8) + segmentData[3];
+ UINT regionHeight = (segmentData[4] << 8) + segmentData[5];
+ region.setSize(regionWidth, regionHeight);
+ region.level = (segmentData[6] & 0xE0) >> 5;
+ region.setDepth((segmentData[6] & 0x1C) >> 2);
+ region.CLUTid = segmentData[7];
+ page.CLUTs[region.CLUTid]; // Create the CLUT if it doesn't exist
+ DVBSubtitlePage::CLUTMap::iterator clut;
+ clut = page.CLUTs.find(region.CLUTid);
+ if (clut == page.CLUTs.end()) return false;
+ region.palette = clut->second.getPalette(region.palette.getBpp());
+ if (regionFillFlag) switch (region.palette.getBpp())
+ {
+ case 2: region.setAllIndices((segmentData[9] & 0x0C) >> 2); break;
+ case 4: region.setAllIndices((segmentData[9] & 0xF0) >> 4); break;
+ case 8: region.setAllIndices(segmentData[8]); break;
+ }
+ region.objects.clear();
+ UINT parsePos = 10;
+ while (segmentLength > parsePos + 5)
+ {
+ UINT objectID = (segmentData[parsePos] << 8) +
+ segmentData[parsePos + 1];
+ DVBSubtitleRegion::ObjectRef objref;
+ objref.objectID = objectID;
+ UINT objectType = (segmentData[parsePos + 2] & 0xC0) >> 6;
+ objref.horizontalPosition = ((segmentData[parsePos + 2] & 0x0F) << 8) +
+ segmentData[parsePos + 3];
+ objref.verticalPosition = ((segmentData[parsePos + 4] & 0x0F) << 8) +
+ segmentData[parsePos + 5];
+ if (objectType == 0) region.objects.push_back(objref);
+ parsePos += 6;
+ }
+ break; // case 0x11
+ }
+ case 0x12: // CLUT definition
+ {
+ if (segmentLength < 2) break;
+ UCHAR CLUTid = segmentData[0];
+ DVBSubtitlePage::CLUTMap::iterator clutEntry;
+ clutEntry = page.CLUTs.find(CLUTid);
+ if (clutEntry == page.CLUTs.end()) return false;
+ DVBSubtitleCLUT& clut = clutEntry->second;
+ clut.CLUTid = CLUTid;
+ UCHAR clutVersion = (segmentData[1] & 0xF0) >> 4;
+ if (clutVersion == clut.version) break; // No update
+ clut.version = clutVersion;
+ UINT parsePos = 2;
+ while (segmentLength > parsePos + 3)
+ {
+ UCHAR clutEntryID = segmentData[parsePos];
+ bool fullRangeFlag = segmentData[parsePos + 1] & 0x01;
+ UCHAR Y, Cr, Cb, T;
+ if (fullRangeFlag)
+ {
+ if (segmentLength <= parsePos + 5) break;
+ Y = segmentData[parsePos + 2];
+ Cr = segmentData[parsePos + 3];
+ Cb = segmentData[parsePos + 4];
+ T = segmentData[parsePos + 5];
+ }
+ else
+ {
+ Y = segmentData[parsePos + 2] & 0xFC;
+ Cr = (segmentData[parsePos + 2] & 0x03) << 6;
+ Cr |= (segmentData[parsePos + 3] & 0xC0) >> 2;
+ Cb = (segmentData[parsePos + 3] & 0x3C) << 2;
+ T = (segmentData[parsePos + 3] & 0x03) << 6;
+ }
+ UCHAR entryFlags = segmentData[parsePos + 1];
+ if (entryFlags & 0x80) clut.setEntry(2, clutEntryID, Y,Cr,Cb,T);
+ if (entryFlags & 0x40) clut.setEntry(4, clutEntryID, Y,Cr,Cb,T);
+ if (entryFlags & 0x20) clut.setEntry(8, clutEntryID, Y,Cr,Cb,T);
+ parsePos += fullRangeFlag ? 6 : 4;
+ }
+ page.updateRegionPalettes(clut);
+ break; // case 0x12
+ }
+ case 0x13: // Object data
+ {
+ if (segmentLength < 3) break;
+ UINT objectID = (segmentData[0] << 8) + segmentData[1];
+ DVBSubtitleObject object(objectID);
+ object.findWhereUsed(page);
+ UCHAR codingMethod = (segmentData[2] & 0x0C) >> 2;
+ object.setNonModColourFlag(segmentData[2] & 0x01);
+ if (codingMethod == 0x00) // Coding of pixels
+ {
+ if (segmentLength < 7) break;
+ UINT topFieldLen = (segmentData[3] << 8) + segmentData[4];
+ UINT bottomFieldLen = (segmentData[5] << 8) + segmentData[6];
+ if (segmentLength < 7 + topFieldLen + bottomFieldLen) break;
+ object.decodeSubBlock(segmentData + 7, topFieldLen, true);
+ if (bottomFieldLen)
+ object.decodeSubBlock(segmentData + 7 + topFieldLen,
+ bottomFieldLen, false);
+ else
+ object.decodeSubBlock(segmentData + 7, topFieldLen, false);
+ }
+ else if (codingMethod == 0x01) // Coded as a string of characters
+ ; // TODO
+ break;
+ }
+ case 0x14: // Display definition
+ break; // TODO: Ignore now and assume 720x576 per ETSI EN 300 743 V1.2.1
+ case 0x80: // End of display set
+ finishPage(page);
+ page.dirty = false;
+ page.currentDisplay = page.pendingDisplay;
+ break;
+ }
+ segmentOffset += (segmentLength + 6);
+ }
+ return false; // Fall through from while loop: we ran out of data
+}
+
+void DVBSubtitles::finishPage(const DVBSubtitlePage& page)
+{
+ if (page.dirty)
+ {
+ osd->clearOSD();
+ }
+
+ DVBSubtitlePage::DisplayMap::const_iterator i,j;
+ for (i = page.currentDisplay.begin(); i != page.currentDisplay.end(); ++i)
+ { // For each currently displayed region, if the region has been
+ // moved or removed, blank out its previous screen position
+ j = page.pendingDisplay.find(i->first);
+ if (j == page.pendingDisplay.end() ||
+ j->second.x != i->second.x ||
+ j->second.y != i->second.y)
+ {
+ DVBSubtitlePage::RegionMap::const_iterator region_iter;
+ region_iter = page.regions.find(i->first);
+ if (region_iter == page.regions.end()) continue;
+ Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Clear region %d", i->first);
+ osd->clearOSDArea(i->second.x, i->second.y,
+ region_iter->second.getWidth(), region_iter->second.getHeight());
+ }
+ }
+
+ for (i = page.pendingDisplay.begin(); i != page.pendingDisplay.end(); ++i)
+ { // Display each pending region
+ DVBSubtitlePage::RegionMap::const_iterator region_iter;
+ region_iter = page.regions.find(i->first);
+ if (region_iter == page.regions.end()) continue;
+ Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Display region %d", i->first);
+ osd->drawOSDBitmap(i->second.x, i->second.y, region_iter->second);
+ }
+}
+
+void DVBSubtitles::lockInput()
+{
+#ifndef WIN32
+ pthread_mutex_lock(&input_mutex);
+#else
+ WaitForSingleObject(input_mutex, INFINITE);
+#endif
+}
+
+void DVBSubtitles::unlockInput()
+{
+#ifndef WIN32
+ pthread_mutex_unlock(&input_mutex);
+#else
+ ReleaseMutex(input_mutex);
+#endif
+}
+
+void DVBSubtitles::lockOutput()
+{
+#ifndef WIN32
+ pthread_mutex_lock(&output_mutex);
+#else
+ WaitForSingleObject(output_mutex, INFINITE);
+#endif
+}
+
+void DVBSubtitles::unlockOutput()
+{
+#ifndef WIN32
+ pthread_mutex_unlock(&output_mutex);
+#else
+ ReleaseMutex(output_mutex);
+#endif
+}
+
+int DVBSubtitles::start()
+{
+ lockInput();
+ running = true;
+ unlockInput();
+ return threadStart();
+}
+
+void DVBSubtitles::stop()
+{
+ threadStop();
+ lockInput();
+ running = false;
+ worklist.clear();
+ unlockInput();
+ lockOutput();
+ PageMap::const_iterator pageEntry = pages.find(pageOnDisplay);
+ if (pageEntry != pages.end())
+ {
+ const DVBSubtitlePage& page = pageEntry->second;
+ DVBSubtitlePage::DisplayMap::const_iterator i;
+ for (i = page.currentDisplay.begin(); i != page.currentDisplay.end(); ++i)
+ {
+ DVBSubtitlePage::RegionMap::const_iterator region_iter;
+ region_iter = page.regions.find(i->first);
+ if (region_iter == page.regions.end()) continue;
+ osd->clearOSDArea(i->second.x, i->second.y,
+ region_iter->second.getWidth(), region_iter->second.getHeight());
+ }
+ }
+ pages.clear();
+ pageOnDisplay = 65536;
+ unlockOutput();
+ threadNudged = false;
+}
+
+void DVBSubtitles::show()
+{
+ lockOutput();
+ showing = true;
+ unlockOutput();
+}
+
+void DVBSubtitles::hide()
+{
+ lockOutput();
+ showing = false;
+ PageMap::const_iterator pageEntry = pages.find(pageOnDisplay);
+ if (pageEntry != pages.end())
+ {
+ const DVBSubtitlePage& page = pageEntry->second;
+ DVBSubtitlePage::DisplayMap::const_iterator i;
+ for (i = page.currentDisplay.begin(); i != page.currentDisplay.end(); ++i)
+ {
+ DVBSubtitlePage::RegionMap::const_iterator region_iter;
+ region_iter = page.regions.find(i->first);
+ if (region_iter == page.regions.end()) continue;
+ osd->clearOSDArea(i->second.x, i->second.y,
+ region_iter->second.getWidth(), region_iter->second.getHeight());
+ }
+ }
+ pages.clear();
+ pageOnDisplay = 65536;
+ unlockOutput();
+}
+
+void DVBSubtitles::nudge()
+{
+ pthread_mutex_lock(&threadCondMutex);
+ threadNudged = true;
+ pthread_cond_signal(&threadCond);
+ pthread_mutex_unlock(&threadCondMutex);
+}
+
+void DVBSubtitles::threadMethod()
+{
+ struct timespec sleeptime;
+ sleeptime.tv_sec = 0;
+ sleeptime.tv_nsec = 0;
+ while (1)
+ {
+ threadCheckExit();
+ threadLock();
+ if (!threadNudged)
+ { // We have done the current work and no more has arrived. Sleep.
+ if (sleeptime.tv_sec == 0 && sleeptime.tv_nsec == 0)
+ {
+ Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Sleeping until nudged.");
+ threadWaitForSignal();
+ }
+ else
+ {
+ Log::getInstance()->log("SUBTITLES", Log::DEBUG, "Sleeping for %d and %d", sleeptime.tv_sec, sleeptime.tv_nsec);
+ struct timespec targetTime;
+#ifndef WIN32
+ clock_gettime(CLOCK_REALTIME, &targetTime);
+#else
+ SYSTEMTIME systime;
+ __int64 filetime;
+ __int64 test;
+ GetSystemTime(&systime);
+ SystemTimeToFileTime(&systime,(FILETIME*)&filetime);
+ targetTime.tv_sec=(filetime-WINDOWS_TIME_BASE_OFFSET)/(10*1000*1000);
+ targetTime.tv_nsec=((filetime-WINDOWS_TIME_BASE_OFFSET)%(10*1000*1000))*100;
+#endif
+ targetTime.tv_nsec += sleeptime.tv_nsec;
+ if (targetTime.tv_nsec > 999999999)
+ {
+ targetTime.tv_sec += 1;
+ targetTime.tv_nsec -= 1000000000;
+ }
+ targetTime.tv_sec += sleeptime.tv_sec;
+ threadWaitForSignalTimed(&targetTime);
+ }
+ }
+ threadNudged = false;
+ threadUnlock();
+ bool finished = false;
+ ULLONG nowPTS, pktPTS;
+ while (!finished)
+ {
+ threadCheckExit();
+ finished = true;
+ pktPTS = PESPacket::PTS_INVALID;
+ lockInput();
+ if (!worklist.empty())
+ pktPTS = worklist.front().getPTS();
+ unlockInput();
+ if (pktPTS != PESPacket::PTS_INVALID)
+ { // An entry exists in the work list
+ nowPTS = Video::getInstance()->getCurrentTimestamp();
+ if (PTSDifference(nowPTS, pktPTS) < 2*90000 ||
+ PTSDifference(pktPTS, nowPTS) < 4000)
+ { // It is due for processing or discarding.
+ finished = false;
+ lockInput();
+ if (!worklist.empty())
+ {
+ PESPacket packet = worklist.front();
+ worklist.pop_front();
+ unlockInput();
+ lockOutput();
+ if (showing) decodePacket(packet);
+ unlockOutput();
+ }
+ else unlockInput();
+ }
+ else if (PTSDifference(pktPTS, nowPTS) >= 60*90000)
+ { // Seems like a bad or very old entry. Get rid of it.
+ finished = false;
+ lockInput();
+ if (!worklist.empty()) worklist.pop_front();
+ unlockInput();
+ }
+ }
+ }
+ // Calculate how long to sleep for
+ sleeptime.tv_sec = 0;
+ sleeptime.tv_nsec = 0;
+ if (pktPTS != PESPacket::PTS_INVALID)
+ { // A future action exists
+ nowPTS = Video::getInstance()->getCurrentTimestamp();
+ ULLONG diff = PTSDifference(pktPTS, nowPTS);
+ sleeptime.tv_sec = diff / 90000;
+ sleeptime.tv_nsec = (long)(diff % 90000) * 10000L / 90000L * 100000L;
+ if (sleeptime.tv_sec > 60)
+ { // We have a problem. An action so far in the future should have
+ // been culled. Probably the action is already due and PTSDifference
+ // wrapped around. Therefore we sleep for a minimal time instead.
+ sleeptime.tv_sec = 0;
+ sleeptime.tv_nsec = 1;
+ }
+ }
+ }
+}