From b6553ec07696ae63d05a7c6723b0413fab3b2e16 Mon Sep 17 00:00:00 2001 From: Mark Calderbank Date: Sat, 29 Nov 2008 23:31:35 +0000 Subject: [PATCH] DVB Subtitles --- bitmap.cc | 125 +++++++ bitmap.h | 73 ++++ boxx.cc | 8 +- boxx.h | 3 + demuxer.cc | 3 +- demuxer.h | 4 +- demuxervdr.cc | 74 +++- demuxervdr.h | 9 +- dvbsubtitles.cc | 938 ++++++++++++++++++++++++++++++++++++++++++++++++ dvbsubtitles.h | 135 +++++++ objects.mk | 3 +- osdreceiver.h | 33 ++ player.cc | 42 ++- player.h | 8 +- surface.h | 3 + surfacemvp.cc | 62 ++++ surfacemvp.h | 1 + surfacewin.cc | 9 + surfacewin.h | 1 + vvideorec.cc | 27 +- vvideorec.h | 8 +- 21 files changed, 1545 insertions(+), 24 deletions(-) create mode 100644 bitmap.cc create mode 100644 bitmap.h create mode 100644 dvbsubtitles.cc create mode 100644 dvbsubtitles.h create mode 100644 osdreceiver.h diff --git a/bitmap.cc b/bitmap.cc new file mode 100644 index 0000000..29e62b6 --- /dev/null +++ b/bitmap.cc @@ -0,0 +1,125 @@ +/* + 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. +*/ +#include "bitmap.h" + +Palette::Palette(UCHAR tBpp) +{ + numColours = 0; + setBpp(tBpp); +} + +void Palette::argb2yrba(ULONG argb, UCHAR& y, UCHAR& cr, UCHAR& cb, UCHAR& a) +{ + a = ((argb & 0xFF000000) >> 24); + int r = (argb & 0x00FF0000) >> 16; + int g = (argb & 0x0000FF00) >> 8; + int b = (argb & 0x000000FF); + y = (1052*r + 2065*g + 401*b + 4096*16 + 2048) / 4096; + cr = (1799*r - 1508*g - 291*b + 4096*128 + 2048) / 4096; + cb = (-608*r - 1191*g + 1799*b + 4096*128 + 2048) / 4096; +} + +ULONG Palette::yrba2argb(UCHAR y, UCHAR cr, UCHAR cb, UCHAR a) +{ + int r, g, b; + r = (4769*(y-16) + 6537*(cr-128) + 2048) / 4096; + g = (4769*(y-16) - 3329*(cr-128) - 1604*(cb-128) + 2048) / 4096; + b = (4769*(y-16) + 8263*(cb-128) + 2048) / 4096; + if (r < 0) r = 0; if (r > 255) r = 255; + if (g < 0) g = 0; if (g > 255) g = 255; + if (b < 0) b = 0; if (b > 255) b = 255; + return (a << 24) + (r << 16) + (g << 8) + b; +} + +void Palette::setBpp(UCHAR tBpp) +{ + bpp = tBpp; + if (bpp > MAX_DEPTH) bpp = MAX_DEPTH; + maxColours = 1 << bpp; + if (numColours > maxColours) numColours = maxColours; + colour.resize(maxColours,0xFF000000); + Y.resize(maxColours,16); + Cr.resize(maxColours,128); + Cb.resize(maxColours,128); + A.resize(maxColours,255); +} + +void Palette::setColour(UCHAR index, ULONG tColour) +{ + if (index >= maxColours) return; + if (index >= numColours) numColours = index + 1; + colour[index] = tColour; + argb2yrba(tColour, Y[index], Cr[index], Cb[index], A[index]); +} + +void Palette::setYCrCbA(UCHAR index, UCHAR tY, UCHAR tCr, UCHAR tCb, UCHAR tA) +{ + if (index >= maxColours) return; + if (index >= numColours) numColours = index + 1; + Y[index] = tY; Cr[index] = tCr; Cb[index] = tCb; A[index] = tA; + colour[index] = yrba2argb(tY, tCr, tCb, tA); +} + +Bitmap::Bitmap(UINT tWidth, UINT tHeight, UCHAR tBpp) +{ + setSize(tWidth, tHeight); + palette.setBpp(tBpp); +} + +void Bitmap::setSize(UINT tWidth, UINT tHeight) +{ + if (width == tWidth && height == tHeight) return; + width = tWidth; height = tHeight; + if (width == 0 || height == 0) + bitmap.clear(); + else + bitmap.assign(width * height, 0); +} + +bool Bitmap::setIndex(UINT x, UINT y, UCHAR index) +{ + if (x <= width && y <= height) + { + bitmap[x + y*width] = index; + return true; + } + else return false; +} + +UCHAR Bitmap::getIndex(UINT x, UINT y) const +{ + if (x > width || y > height) + return 0; + else + return bitmap[x + y*width]; +} + +ULONG Bitmap::getColour(UINT x, UINT y) const +{ + if (x > width || y > height) + return 0; + else + return palette.getColour(bitmap[x + y*width]); +} + +void Bitmap::setAllIndices(UCHAR index) +{ + bitmap.assign(width * height, index); +} diff --git a/bitmap.h b/bitmap.h new file mode 100644 index 0000000..654775f --- /dev/null +++ b/bitmap.h @@ -0,0 +1,73 @@ +/* + 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. +*/ +#ifndef BITMAP_H +#define BITMAP_H + +#include "defines.h" +#include + +class Palette +{ + public: + Palette(UCHAR tBpp = 8); + UCHAR getBpp() const { return bpp; } + void reset() { numColours = 0; } + void setBpp(UCHAR tBpp); + ULONG getColour(UCHAR index) const { + return index < maxColours ? colour[index] : 0; } + void setColour(UCHAR index, ULONG tColour); + void setYCrCbA(UCHAR index, UCHAR tY, UCHAR tCr, UCHAR tCb, UCHAR tA); + const std::vector& getColourVector() const { return colour; } + const std::vector& getYVector() const { return Y; } + const std::vector& getCrVector() const { return Cr; } + const std::vector& getCbVector() const { return Cb; } + const std::vector& getAVector() const { return A; } + private: + const static UINT MAX_DEPTH = 8; + std::vector colour; + std::vector Y; + std::vector Cr; + std::vector Cb; + std::vector A; + UCHAR bpp; + UINT maxColours, numColours; + void argb2yrba(ULONG argb, UCHAR& y, UCHAR& cr, UCHAR& cb, UCHAR& a); + ULONG yrba2argb(UCHAR y, UCHAR cr, UCHAR cb, UCHAR a); +}; + +class Bitmap +{ + private: + std::vector bitmap; + UINT width, height; + public: + Bitmap(UINT tWidth = 0, UINT tHeight = 0, UCHAR tBpp = 8); + Palette palette; + UINT getWidth() const { return width; } + UINT getHeight() const { return height; } + UCHAR getIndex(UINT x, UINT y) const; + ULONG getColour(UINT x, UINT y) const; + const std::vector & rawData() const { return bitmap; } + void setSize(UINT tWidth, UINT tHeight); + bool setIndex(UINT x, UINT y, UCHAR index); + void setAllIndices(UCHAR index); +}; + +#endif diff --git a/boxx.cc b/boxx.cc index c46af63..af6ce48 100644 --- a/boxx.cc +++ b/boxx.cc @@ -19,7 +19,7 @@ */ #include "boxx.h" - +#include "bitmap.h" #include "log.h" char Boxx::numBoxxes = 0; @@ -360,6 +360,12 @@ void Boxx::drawPixel(UINT x, UINT y, const Colour& colour) } } +void Boxx::drawBitmap(UINT x, UINT y, const Bitmap& bm) +{ + if (parent) parent->drawBitmap(area.x + x, area.y + y, bm); + else surface->drawBitmap(x, y, bm); +} + void Boxx::startFastDraw() { if (parent) parent->startFastDraw(); diff --git a/boxx.h b/boxx.h index f9da2e1..439fa54 100644 --- a/boxx.h +++ b/boxx.h @@ -36,6 +36,8 @@ using namespace std; #include "surfacemvp.h" #endif +class Bitmap; + class Boxx { public: @@ -101,6 +103,7 @@ class Boxx void drawTextRJ(const char* text, int x, int y, const Colour& colour); void drawTextCentre(const char* text, int x, int y, const Colour& colour); void drawPixel(UINT x, UINT y, const Colour& colour); + void drawBitmap(UINT x, UINT y, const Bitmap& bm); /* This is for system which need a locking of the drawing surface to speed up drawing */ void startFastDraw(); diff --git a/demuxer.cc b/demuxer.cc index b75d256..931f33f 100644 --- a/demuxer.cc +++ b/demuxer.cc @@ -159,7 +159,7 @@ Demuxer* Demuxer::getInstance() return instance; } -int Demuxer::init(Callback* tcallback, DrainTarget* audio, DrainTarget* video, ULONG demuxMemoryV, ULONG demuxMemoryA) +int Demuxer::init(Callback* tcallback, DrainTarget* audio, DrainTarget* video, ULONG demuxMemoryV, ULONG demuxMemoryA, DVBSubtitles* tsubtitles) { if (!initted) { @@ -175,6 +175,7 @@ int Demuxer::init(Callback* tcallback, DrainTarget* audio, DrainTarget* video, U reset(); initted = true; + subtitles = tsubtitles; callback = tcallback; return 1; } diff --git a/demuxer.h b/demuxer.h index 7994b96..ebfccaa 100644 --- a/demuxer.h +++ b/demuxer.h @@ -34,6 +34,7 @@ however, no code was copied verbatim. #include "stream.h" #include "defines.h" +class DVBSubtitles; class Callback; class DrainTarget; @@ -75,7 +76,7 @@ class Demuxer Demuxer(); virtual ~Demuxer(); static Demuxer* getInstance(); - int init(Callback* callback, DrainTarget* audio, DrainTarget* video, ULONG demuxMemoryV, ULONG demuxMemoryA); + int init(Callback* callback, DrainTarget* audio, DrainTarget* video, ULONG demuxMemoryV, ULONG demuxMemoryA, DVBSubtitles* tsubtitles = NULL); virtual void reset(); virtual void flush(); void flushAudio(); @@ -162,6 +163,7 @@ class Demuxer static Demuxer* instance; Stream videostream; Stream audiostream; + DVBSubtitles* subtitles; int shutdown(); bool initted; bool vid_seeking; diff --git a/demuxervdr.cc b/demuxervdr.cc index 0a69cf3..4ff9698 100644 --- a/demuxervdr.cc +++ b/demuxervdr.cc @@ -1,5 +1,5 @@ /* - Copyright 2005-2007 Mark Calderbank + Copyright 2005-2008 Mark Calderbank This file is part of VOMP. @@ -14,13 +14,13 @@ 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. + along with VOMP; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "demuxervdr.h" - #include "video.h" +#include "dvbsubtitles.h" #include "log.h" #ifndef WIN32 @@ -56,6 +56,7 @@ DemuxerVDR::DemuxerVDR() { frameCounting = false; packetCounting = false; + subtitlePacketPosition = 0; } void DemuxerVDR::reset() @@ -70,6 +71,7 @@ void DemuxerVDR::flush() { state = 0; submitting = false; + subtitlePacketPosition = 0; Demuxer::flush(); } @@ -288,10 +290,6 @@ ULONG DemuxerVDR::getFrameNumFromPTS(ULLONG pts) pts_map.erase(iter, pts_map.end()); } pts_map_mutex.Unlock(); - if (total > 1 && actual == 1) -Log::getInstance()->log("Demuxer", Log::DEBUG, "DELETED REFERENCES"); - if (actual > 1) -Log::getInstance()->log("Demuxer", Log::DEBUG, "STILL USING OLD REF"); if (difference == (1LL<<33)) return 0; // We cannot make sense of the pts @@ -299,9 +297,67 @@ Log::getInstance()->log("Demuxer", Log::DEBUG, "STILL USING OLD REF"); return ref_frame + difference * Video::getInstance()->getFPS() / 90000; } +void DemuxerVDR::dealWithSubtitlePacket() +{ + const std::vector& data = packet.getData(); + UINT packetSize = packet.getSize(); + if (packetSize < 9) return; + UINT payloadOffset = data[8] + 9; + if (packetSize < payloadOffset + 5) return; + if (data[payloadOffset + 3] == 0x00) + { // Not a continuation packet + if (packetSize < payloadOffset + 7) return; + subtitlePacket.init(data[3]); + subtitlePacket.write(&data[6], payloadOffset - 6); + subtitlePacket.write(&data[payloadOffset+4], packetSize-payloadOffset-4); + subtitlePacketPosition = payloadOffset + 2; + } + else + { // Continuation packet + if (subtitlePacketPosition == 0) return; + subtitlePacket.write(&data[payloadOffset+4], packetSize-payloadOffset-4); + } + + const std::vector& sub_data = subtitlePacket.getData(); + UINT subSize = subtitlePacket.getSize(); + while (subtitlePacketPosition < subSize) + { + if (sub_data[subtitlePacketPosition] == 0xFF) + { + subtitles->put(subtitlePacket); + subtitlePacketPosition = 0; // Wait for next non-continuation packet + break; + } + if (sub_data[subtitlePacketPosition] != 0x0F) + { + subtitlePacketPosition = 0; // Wait for next non-continuation packet + break; + } + if (subSize < subtitlePacketPosition + 6) break; + UINT segmentLength = (sub_data[subtitlePacketPosition + 4] << 8) + + sub_data[subtitlePacketPosition + 5]; + subtitlePacketPosition += segmentLength + 6; + } +} + void DemuxerVDR::parseVDRPacketDetails() { - parsePacketDetails(packet); + if (packet.getPacketType() == PESTYPE_PRIVATE_1 && packet.getSize() > 8) + { + UINT payload_begin = packet[8] + 9; + if (packet.getSize() > payload_begin) + { + UCHAR substream_type = packet[payload_begin] & 0xF0; + if (substream_type == 0x20) // Subtitles + { + dealWithSubtitlePacket(); + } + else // Not subtitles + parsePacketDetails(packet); + } + } + else // Not a private packet*/ + parsePacketDetails(packet); if (packetCounting && packet.getPacketType() >= PESTYPE_AUD0 && packet.getPacketType() <= PESTYPE_AUDMAX) diff --git a/demuxervdr.h b/demuxervdr.h index d64e9e5..5efa71a 100644 --- a/demuxervdr.h +++ b/demuxervdr.h @@ -1,5 +1,5 @@ /* - Copyright 2005-2007 Mark Calderbank + Copyright 2005-2008 Mark Calderbank This file is part of VOMP. @@ -14,8 +14,8 @@ 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. + along with VOMP; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DEMUXERVDR_H @@ -45,6 +45,8 @@ class DemuxerVDR : public Demuxer bool submitting; int packetLength; PESPacket packet; + PESPacket subtitlePacket; + UINT subtitlePacketPosition; ULONG frameNumber, packetNumber; bool frameCounting, packetCounting; @@ -54,6 +56,7 @@ class DemuxerVDR : public Demuxer Mutex pts_map_mutex; void parseVDRPacketDetails(); + void dealWithSubtitlePacket(); }; #endif diff --git a/dvbsubtitles.cc b/dvbsubtitles.cc new file mode 100644 index 0000000..32836d9 --- /dev/null +++ b/dvbsubtitles.cc @@ -0,0 +1,938 @@ +/* + 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 + * With some input from the "subtitle plugin" + * by Pekka Virtanen +*/ + +/* 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 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& 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; + } + } + } +} diff --git a/dvbsubtitles.h b/dvbsubtitles.h new file mode 100644 index 0000000..71531c7 --- /dev/null +++ b/dvbsubtitles.h @@ -0,0 +1,135 @@ +/* + 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. +*/ + +#ifndef DVBSUBTITLES_H +#define DVBSUBTITLES_H + +#ifdef WIN32 +#include "threadwin.h" +#else +#include "threadp.h" +#endif +#include "bitmap.h" +#include "demuxer.h" +#include +#include +#include +#include + +class OSDReceiver; + +class DVBSubtitleCLUT +{ + public: + DVBSubtitleCLUT(); + UCHAR CLUTid; + UCHAR version; + void setEntry(int bpp, UINT index, UCHAR Y, UCHAR Cr, UCHAR Cb, UCHAR T); + const Palette& getPalette(int bpp) const; + private: + Palette palette2; + Palette palette4; + Palette palette8; + const static ULONG defaultPalette2[4]; + const static ULONG defaultPalette4[16]; + const static ULONG defaultPalette8[256]; +}; + +class DVBSubtitleRegion : public Bitmap +{ + public: + DVBSubtitleRegion(); + UCHAR version; + UCHAR level; + UCHAR CLUTid; + void setDepth(UINT); + struct ObjectRef + { + UINT objectID; + UINT horizontalPosition; + UINT verticalPosition; + }; + typedef std::vector ObjectList; + ObjectList objects; +}; + +class DVBSubtitlePage +{ + public: + DVBSubtitlePage(); + ULLONG pts; + bool dirty; + UCHAR version; + UINT timeout; + UCHAR state; + bool serviceAcquired; + typedef std::map CLUTMap; + CLUTMap CLUTs; + typedef std::map RegionMap; + RegionMap regions; + struct Coords { UINT x; UINT y; }; + typedef std::map DisplayMap; + DisplayMap currentDisplay; + DisplayMap pendingDisplay; + + void setState(UCHAR s); + void updateRegionPalettes(const DVBSubtitleCLUT&); +}; + +class DVBSubtitles : public Thread_TYPE +{ + public: + DVBSubtitles(OSDReceiver* tosd = NULL); + ~DVBSubtitles() {} + void put(const PESPacket& packet); + int start(); + void stop(); + void show(); + void hide(); + + private: + OSDReceiver* osd; + std::deque worklist; + typedef std::map PageMap; + PageMap pages; + UINT pageOnDisplay; + bool decodePacket(const PESPacket&); + void finishPage(const DVBSubtitlePage&); + + bool running; + bool showing; + bool threadNudged; + void nudge(); + void lockInput(); + void unlockInput(); + void lockOutput(); + void unlockOutput(); + void threadMethod(); + void threadPostStopCleanup() {}; +#ifndef WIN32 + pthread_mutex_t input_mutex; + pthread_mutex_t output_mutex; +#else + HANDLE input_mutex; + HANDLE output_mutex; +#endif +}; + +#endif diff --git a/objects.mk b/objects.mk index 5fb9fcb..150d416 100644 --- a/objects.mk +++ b/objects.mk @@ -19,4 +19,5 @@ OBJECTS1 = command.o log.o tcp.o dsock.o thread.o timers.o i18n.o mutex.o \ eventdispatcher.o vdrrequestpacket.o vdrresponsepacket.o \ vvideolivetv.o vsleeptimer.o \ playerlivetv.o playerliveradio.o \ - wprogressbar.o + wprogressbar.o \ + bitmap.o dvbsubtitles.o diff --git a/osdreceiver.h b/osdreceiver.h new file mode 100644 index 0000000..4fb350d --- /dev/null +++ b/osdreceiver.h @@ -0,0 +1,33 @@ +/* + 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. +*/ +#ifndef OSDRECEIVER_H +#define OSDRECEIVER_H + +class Bitmap; + +class OSDReceiver +{ + public: + virtual void drawOSDBitmap(UINT posX, UINT posY, const Bitmap&)=0; + virtual void clearOSD()=0; + virtual void clearOSDArea(UINT posX, UINT posY, UINT width, UINT height)=0; +}; + +#endif diff --git a/player.cc b/player.cc index db3e01a..774cda9 100644 --- a/player.cc +++ b/player.cc @@ -29,16 +29,19 @@ #include "messagequeue.h" #include "remote.h" #include "message.h" +#include "dvbsubtitles.h" +#include "osdreceiver.h" #define USER_RESPONSE_TIME 500 // Milliseconds // ----------------------------------- Called from outside, one offs or info funcs -Player::Player(MessageQueue* tmessageQueue, void* tmessageReceiver) +Player::Player(MessageQueue* tmessageQueue, void* tmessageReceiver, OSDReceiver* tosdReceiver) : vfeed(this), afeed(this) { messageQueue = tmessageQueue; messageReceiver = tmessageReceiver; + osdReceiver = tosdReceiver; audio = Audio::getInstance(); video = Video::getInstance(); logger = Log::getInstance(); @@ -50,6 +53,8 @@ Player::Player(MessageQueue* tmessageQueue, void* tmessageReceiver) state = S_STOP; ifactor = 4; + subtitlesShowing = false; + videoStartup = false; threadBuffer = NULL; @@ -74,8 +79,10 @@ int Player::init() demuxer = new DemuxerVDR(); if (!demuxer) return 0; + subtitles = new DVBSubtitles(osdReceiver); + if (!subtitles) return 0; - if (!demuxer->init(this, audio, video, 2097152, 524288)) + if (!demuxer->init(this, audio, video, 2097152, 524288, subtitles)) { logger->log("Player", Log::ERR, "Demuxer failed to init"); shutdown(); @@ -101,6 +108,8 @@ int Player::shutdown() delete demuxer; demuxer = NULL; + delete subtitles; + subtitles = NULL; #ifdef WIN32 CloseHandle(mutex); @@ -168,6 +177,21 @@ void Player::setAudioChannel(int newChannel, int type) demuxer->setAudioChannel(newChannel); } +bool Player::toggleSubtitles() +{ + if (!subtitlesShowing) + { + subtitlesShowing = true; + subtitles->show(); + } + else + { + subtitlesShowing = false; + subtitles->hide(); + } + return subtitlesShowing; +} + // ----------------------------------- Externally called events void Player::play() @@ -264,7 +288,7 @@ void Player::fastBackward() void Player::jumpToPercent(double percent) { lock(); - logger->log("Player", Log::DEBUG, "JUMP TO %i%%", percent); + logger->log("Player", Log::DEBUG, "JUMP TO %f%%", percent); ULONG newFrame = (ULONG)(percent * lengthFrames / 100); switchState(S_JUMP, newFrame); // unLock(); - let thread unlock this @@ -347,6 +371,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) threadStop(); vfeed.stop(); afeed.stop(); + subtitles->stop(); demuxer->flush(); state = S_FFWD; threadStart(); @@ -359,6 +384,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) threadStop(); vfeed.stop(); afeed.stop(); + subtitles->stop(); demuxer->flush(); state = S_FBWD; threadStart(); @@ -368,6 +394,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) { vfeed.stop(); afeed.stop(); + subtitles->stop(); threadStop(); video->stop(); video->blank(); @@ -389,6 +416,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) threadStop(); vfeed.stop(); afeed.stop(); + subtitles->stop(); demuxer->flush(); state = S_PAUSE_I; video->reset(); @@ -423,6 +451,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) audio->systemMuteOn(); vfeed.stop(); afeed.stop(); + subtitles->stop(); threadStop(); video->unPause(); audio->unPause(); @@ -436,6 +465,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) audio->systemMuteOn(); vfeed.stop(); afeed.stop(); + subtitles->stop(); threadStop(); video->unPause(); audio->unPause(); @@ -447,6 +477,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) { vfeed.stop(); afeed.stop(); + subtitles->stop(); threadStop(); video->stop(); video->blank(); @@ -473,6 +504,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) threadStop(); vfeed.stop(); afeed.stop(); + subtitles->stop(); demuxer->flush(); state = S_PAUSE_I; video->reset(); @@ -680,6 +712,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) logger->log("Player", Log::DEBUG, "Immediate play"); afeed.start(); vfeed.start(); + subtitles->start(); video->sync(); audio->sync(); audio->play(); @@ -748,6 +781,7 @@ void Player::restartAtFrame(ULONG newFrame) { vfeed.stop(); afeed.stop(); + subtitles->stop(); threadStop(); video->stop(); video->reset(); @@ -760,6 +794,7 @@ void Player::restartAtFrame(ULONG newFrame) videoStartup = true; afeed.start(); vfeed.start(); + subtitles->start(); threadStart(); audio->play(); video->sync(); @@ -909,6 +944,7 @@ void Player::threadMethod() videoStartup = true; afeed.start(); vfeed.start(); + subtitles->start(); audio->play(); audio->sync(); audio->systemMuteOff(); diff --git a/player.h b/player.h index 51af591..b009956 100644 --- a/player.h +++ b/player.h @@ -45,11 +45,13 @@ class Video; class VDR; class Log; class Demuxer; +class OSDReceiver; +class DVBSubtitles; class Player : public Thread_TYPE, public Callback { public: - Player(MessageQueue* messageQueue, void* messageReceiver); + Player(MessageQueue* messageQueue, void* messageReceiver, OSDReceiver* osdReceiver); virtual ~Player(); int init(); @@ -58,6 +60,7 @@ class Player : public Thread_TYPE, public Callback void setLengthBytes(ULLONG length); void setLengthFrames(ULONG length); void setAudioChannel(int newChannel, int type); + bool toggleSubtitles(); void play(); void stop(); @@ -118,12 +121,15 @@ class Player : public Thread_TYPE, public Callback void restartAtFrame(ULONG newFrame); void restartAtFramePI(ULONG newFrame); + bool subtitlesShowing; MessageQueue* messageQueue; void* messageReceiver; + OSDReceiver* osdReceiver; Log* logger; Audio* audio; Video* video; Demuxer* demuxer; + DVBSubtitles* subtitles; VDR* vdr; VFeed vfeed; AFeed afeed; diff --git a/surface.h b/surface.h index 8564004..1a72511 100644 --- a/surface.h +++ b/surface.h @@ -39,6 +39,8 @@ extern osd_font_t font_CaslonRoman_1_25; extern osd_font_t font_helvB24; extern osd_font_t font_helvB18; +class Bitmap; + class Surface { public: @@ -61,6 +63,7 @@ class Surface virtual void drawPixel(int x, int y, unsigned int c)=0; virtual void drawHorzLine(int x1, int x2, int y, unsigned int c)=0; virtual void drawVertLine(int x, int y1, int y2, unsigned int c)=0; + virtual void drawBitmap(int x, int y, const Bitmap& bm)=0; virtual int updateToScreen(int sx, int sy, int w, int h, int dx, int dy)=0; virtual void readPixel(int x, int y, unsigned char* r, unsigned char* g, unsigned char* b)=0; virtual void screenShot(char* fileName)=0; diff --git a/surfacemvp.cc b/surfacemvp.cc index e7d8f5d..e946ffd 100644 --- a/surfacemvp.cc +++ b/surfacemvp.cc @@ -22,6 +22,7 @@ #include "surfacemvp.h" #include "osd.h" +#include "bitmap.h" #include "log.h" SurfaceMVP::SurfaceMVP(int id) @@ -264,6 +265,67 @@ void SurfaceMVP::drawVertLine(int x, int y1, int y2, unsigned int c) fillblt(x, y1, 1, y2-y1, c); } +void SurfaceMVP::drawBitmap(int x, int y, const Bitmap& bm) +{ + UINT bmw = bm.getWidth(); UINT bmh = bm.getHeight(); + if (bmw == 0 || bmh == 0) return; + if ((x >= (int)surface.sfc.width) || (y >= (int)surface.sfc.height)) return; + int remainder = (surface.sfc.width % 4); + UINT line; + if (remainder == 0) + line = surface.sfc.width; + else + line = surface.sfc.width + (4 - remainder); + const std::vector& bmdata = bm.rawData(); + const std::vector& Y = bm.palette.getYVector(); + const std::vector& Cr = bm.palette.getCrVector(); + const std::vector& Cb = bm.palette.getCbVector(); + const std::vector& A = bm.palette.getAVector(); + UINT b_offset = 0; + UINT s_offset = x + y*line; + UINT plotWidth = bmw; + UINT plotHeight = bmh; + if (x + plotWidth - 1 > surface.sfc.width) + plotWidth = surface.sfc.width - x + 1; + if (y + plotHeight - 1 > surface.sfc.height) + plotHeight = surface.sfc.height - y + 1; + for (UINT j = 0; j < plotHeight; ++j) + { + UINT i = 0; + if (x & 1) // odd x - need to plot first column separately + { + UCHAR index = bmdata[b_offset]; + *(surface.base[0] + s_offset) = Y[index]; + *(surface.base[1] + s_offset - 1) = Cb[index]; + *(surface.base[1] + s_offset) = Cr[index]; + *(surface.base[2] + s_offset) = A[index]; + i = 1; + } + // Now, plot pairs of pixels with averaged chroma values + while (i < plotWidth - 1) + { + UCHAR index1 = bmdata[b_offset + i]; + UCHAR index2 = bmdata[b_offset + i + 1]; + *(surface.base[0] + s_offset + i) = Y[index1]; + *(surface.base[0] + s_offset + i + 1) = Y[index2]; + *(surface.base[1] + s_offset + i) = (Cb[index1] + Cb[index2]) / 2; + *(surface.base[1] + s_offset + i + 1) = (Cr[index1] + Cr[index2]) / 2; + *(surface.base[2] + s_offset + i) = A[index1]; + *(surface.base[2] + s_offset + i + 1) = A[index2]; + i += 2; + } + if (i == plotWidth - 1) // One column left to do + { + UCHAR index = bmdata[b_offset + i]; + *(surface.base[0] + s_offset + i) = Y[index]; + *(surface.base[1] + s_offset + i) = Cb[index]; + *(surface.base[1] + s_offset + i + 1) = Cr[index]; + *(surface.base[2] + s_offset + i) = A[index]; + } + s_offset += line; + b_offset += bmw; + } +} /* surface update to screen needs: source x distance into this surface diff --git a/surfacemvp.h b/surfacemvp.h index 3e4bb87..f71908c 100644 --- a/surfacemvp.h +++ b/surfacemvp.h @@ -164,6 +164,7 @@ class SurfaceMVP : public Surface void drawPixel(int x, int y, unsigned int c); void drawHorzLine(int x1, int x2, int y, unsigned int c); void drawVertLine(int x, int y1, int y2, unsigned int c); + void drawBitmap(int x, int y, const Bitmap& bm); int updateToScreen(int sx, int sy, int w, int h, int dx, int dy); void readPixel(int x, int y, unsigned char* r, unsigned char* g, unsigned char* b); void screenShot(char* fileName); diff --git a/surfacewin.cc b/surfacewin.cc index 2707bd7..6b52b73 100644 --- a/surfacewin.cc +++ b/surfacewin.cc @@ -20,6 +20,7 @@ #include "surfacewin.h" #include "osdwin.h" +#include "bitmap.h" #include "log.h" SurfaceWin::SurfaceWin(int id) @@ -215,6 +216,14 @@ void SurfaceWin::drawVertLine(int x, int y1, int y2, unsigned int c) fillblt(x, y1, 1, y2-y1, c); } +void SurfaceWin::drawBitmap(int x, int y, const Bitmap& bm) +{ + // Temporary code? Draw one pixel at a time using drawPixel() + for (UINT j = 0; j < bm.getHeight(); ++j) + for (UINT i = 0; i < bm.getWidth(); ++i) + drawPixel(x+i, y+j, bm.getColour(i,j)); +} + int SurfaceWin::updateToScreen(int sx, int sy, int w, int h, int dx, int dy) // FIXME new, replace others with this FIXME { WaitForSingleObject(event,INFINITE); //since this might be called before surface diff --git a/surfacewin.h b/surfacewin.h index baa62aa..40f8736 100644 --- a/surfacewin.h +++ b/surfacewin.h @@ -42,6 +42,7 @@ class SurfaceWin : public Surface void drawPixel(int x, int y, unsigned int c); void drawHorzLine(int x1, int x2, int y, unsigned int c); void drawVertLine(int x, int y1, int y2, unsigned int c); + void drawBitmap(int x, int y, const Bitmap& bm); int updateToScreen(int sx, int sy, int w, int h, int dx, int dy); void readPixel(int x, int y, unsigned char* r, unsigned char* g, unsigned char* b); void screenShot(char* fileName); diff --git a/vvideorec.cc b/vvideorec.cc index e9c0b21..308d756 100644 --- a/vvideorec.cc +++ b/vvideorec.cc @@ -35,6 +35,7 @@ #include "boxstack.h" #include "vinfo.h" #include "i18n.h" +#include "bitmap.h" VVideoRec::VVideoRec(Recording* rec) { @@ -45,7 +46,7 @@ VVideoRec::VVideoRec(Recording* rec) vas = NULL; vsummary = NULL; - player = new Player(Command::getInstance(), this); + player = new Player(Command::getInstance(), this, this); player->init(); videoMode = video->getMode(); @@ -93,8 +94,6 @@ VVideoRec::VVideoRec(Recording* rec) clocksRegion.y = barRegion.y + 12; clocksRegion.w = 170; clocksRegion.h = surface->getFontHeight(); - - // barBlue.set(0, 0, 150, 150); barBlue.set(0, 0, 0, 128); @@ -368,6 +367,7 @@ int VVideoRec::handleCommand(int command) case Remote::EIGHT: player->jumpToPercent(80); doBar(0); return 2; case Remote::NINE: player->jumpToPercent(90); doBar(0); return 2; + case Remote::RECORD: player->toggleSubtitles(); return 2; #ifdef DEV // case Remote::RED: // { @@ -864,3 +864,24 @@ void VVideoRec::removeSummary() } } +void VVideoRec::drawOSDBitmap(UINT posX, UINT posY, const Bitmap& bm) +{ + drawBitmap(posX, posY, bm); + Region r; + r.x = posX; r.y = posY; r.w = bm.getWidth(); r.h = bm.getHeight(); + boxstack->update(this, &r); +} + +void VVideoRec::clearOSD() +{ + rectangle(area, transparent); + boxstack->update(this, &area); +} + +void VVideoRec::clearOSDArea(UINT posX, UINT posY, UINT width, UINT height) +{ + Region r; + r.x = posX; r.y = posY; r.w = width; r.h = height; + rectangle(r, transparent); + boxstack->update(this, &r); +} diff --git a/vvideorec.h b/vvideorec.h index 36f5231..8c89b74 100644 --- a/vvideorec.h +++ b/vvideorec.h @@ -28,6 +28,7 @@ #include "wwss.h" #include "region.h" #include "colour.h" +#include "osdreceiver.h" class VDR; class Video; @@ -38,10 +39,11 @@ class VAudioSelector; class Message; class BoxStack; class VInfo; +class Bitmap; //#include "vepg.h" // for testing EPG in NTSC with a NTSC test video -class VVideoRec : public Boxx, public TimerReceiver +class VVideoRec : public Boxx, public TimerReceiver, public OSDReceiver { public: VVideoRec(Recording* rec); @@ -53,6 +55,10 @@ class VVideoRec : public Boxx, public TimerReceiver void timercall(int clientReference); void processMessage(Message* m); + void drawOSDBitmap(UINT posX, UINT posY, const Bitmap&); + void clearOSD(); + void clearOSDArea(UINT posX, UINT posY, UINT width, UINT height); + private: BoxStack* boxstack; VDR* vdr; -- 2.39.5