mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
VDR developer version 2.1.2 is now available at ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.2.tar.bz2 A 'diff' against the previous version is available at ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.1-2.1.2.diff MD5 checksums: 4725e9cb26fea8fa3682dd30f5594a50 vdr-2.1.2.tar.bz2 869d9b298b432a505186328bcf0b53c6 vdr-2.1.1-2.1.2.diff WARNING: ======== This is a *developer* version. Even though *I* use it in my productive environment, I strongly recommend that you only use it under controlled conditions and for testing and debugging. From the HISTORY file: - Updated the Finnish OSD texts (thanks to Rolf Ahrenberg). - Fixed displaying DVB subtitles (thanks to Rolf Ahrenberg for helping to debug and understand subtitle page refreshes): + Fixed handling DVB subtitle fill region codes for 2 and 8 bpp. + Fixed handling pages without an explicit END_OF_DISPLAY_SET_SEGMENT. The FINISHPAGE_HACK is no longer necessary. + Fixed handling "page refreshes". The data is now parsed and stored closer to the DVB standard specs, introducing "object refs" and "region refs". + The debug output now goes into an HTML file named dbg-log.htm and shows the actual bitmaps (dbg-nnn.jpg) used to display the subtitles. That way it is much easier to see what's actually going on. + Fixed handling subtitles encoded as a string of characters (the very first character was always skipped). - Fixed wrong initialization of Setup.PositionerSwing (reported by Arthur Konovalov). - Updated the Estonian OSD texts (thanks to Arthur Konovalov). - Fixed cleaning up old EPG events in case no epg data file is given (reported by Dave Pickles). - Unified the internal sequence of actions when pressing the Blue and the Back key, respectively, during replay (reported by Thomas Maass). - The Yellow button in the main menu no longer acts as "Pause" if "Pause key handling" is set to "do not pause live video" (suggested by Ulf Kiener). - The code for distributing recordings over several video directories has been removed. VDR now by default assumes that the video directory is one big disk. If you absolutely need to use several separate disks to store recordings, you can write a plugin that uses the new cVideoDirectory API to implement the necessary functionality (see PLUGINS.html, section "The video directory"). You can copy the respective code from previous versions of videodir.c. IMPORTANT NOTE: If you write a plugin that implements a distributed video directory, =============== be sure to make cVideoDirectory::Rename() follow symbolic links! This functionality was never implemented in VDR and it therefore used a workaround in cutter.c. See the section marked with // XXX this can be removed once RenameVideoFile() follows symlinks in previous versions of cutter.c. + CloseVideoFile() is obsolete and has been removed. + The functions OpenVideoFile(), RenameVideoFile(), RemoveVideoFile(), VideoFileSpaceAvailable(), VideoDiskSpace(), RemoveEmptyVideoDirectories(), IsOnVideoDirectoryFileSystem() and PrefixVideoFileName() are now static members of cVideoDirectory and need to be called with the proper prefix. + The name of the video directory is now available through cVideoDirectory::Name(). - Added renaming and moving recordings and folders, editing a recording's priority and lifetime, and queueing cutting jobs (inspired by the "extrecmenu" plugin from Martin Prochnow). + The "Recording info" menu now has a new Blue button named "Edit", which opens a dialog in which several properties of the selected recording can be changed. It can be renamed or moved into another folder and its priority and lifetime can be modified (inspired by the "extrecmenu" plugin from Martin Prochnow). The new blue "Edit" button in the "Recordings" menu opens a dialog in which a folder can be renamed or moved. See MANUAL, section "Managing folders". + In the "Edit recording" menu the Yellow button ("Delete marks") allows you to delete all editing marks of the selected recording. + cCutter is no longer a static class. Cutting requests should now be invoked by calling RecordingsHandler.Add(ruCut, FileName). See the new cRecordingsHandler class in recording.h. + Cutting jobs are now placed in a queue (together with any move or copy jobs) and are processed one by one. + The new SVDRP command RENR can be used to rename a recording (suggested by Rolf Ahrenberg). + Note that in several places in the source code a "copy" operation is mentioned, however there is no user interface for this, yet. - Changed some variable names in positioner.c to match the names used in the page with the explanation on vdr-portal.de. - Updated the Italian OSD texts (thanks to Diego Pierotto). - Fixed writing group separators to channels.conf that contain a comma (reported by Eike Edener). - Now also checking the source (in addition to the transponder) when setting the system time from the TDT, which avoids problems in case devices are tuned to the same transponder on different sources, and these broadcast different time data (reported by Torsten Lang). - Changed cRecorder::Action() to use cTimeMs instead of time() to avoid problems with unjustified "video data stream broken" errors in case the system time is changed while a recording is active (reported by Torsten Lang). - Revised the section on "Learning the remote control keys" in the INSTALL file to avoid the impression that there actually is a default remote.conf file, and to not use any alphabetic keys for special functions, so that they remain available for textual input. - The function cRecordings::MBperMinute() now only takes into account recordings with less than 5 seconds per megabyte, in an attempt to filter out radio recordings (thanks to Harald Koenig). The result of this function was way off any realistic value in case there are many radio recordings in the video directory. - Added maximum signal strength value for TechniSat SkyStar 2 DVB-S rev 2.3P (thanks to Guido Cordaro). - Fixed an inconsistent behavior between opening the Recordings menu manually via the main menu and by pressing the Recordings key. In the latter case it automatically opened all sub folders to position the cursor to the last replayed recording, which is unexpected at this point (reported by Helmut Auer). You can still navigate to the last replayed recording (if any) by pressing Ok repeatedly in the Recordings menu.
1525 lines
51 KiB
C
1525 lines
51 KiB
C
/*
|
|
* dvbsubtitle.c: DVB subtitles
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* Original author: Marco Schluessler <marco@lordzodiac.de>
|
|
* With some input from the "subtitles plugin" by Pekka Virtanen <pekka.virtanen@sci.fi>
|
|
*
|
|
* $Id: dvbsubtitle.c 3.4 2013/09/07 10:39:46 kls Exp $
|
|
*/
|
|
|
|
#include "dvbsubtitle.h"
|
|
#define __STDC_FORMAT_MACROS // Required for format specifiers
|
|
#include <inttypes.h>
|
|
#include "device.h"
|
|
#include "libsi/si.h"
|
|
|
|
#define PAGE_COMPOSITION_SEGMENT 0x10
|
|
#define REGION_COMPOSITION_SEGMENT 0x11
|
|
#define CLUT_DEFINITION_SEGMENT 0x12
|
|
#define OBJECT_DATA_SEGMENT 0x13
|
|
#define DISPLAY_DEFINITION_SEGMENT 0x14
|
|
#define DISPARITY_SIGNALING_SEGMENT 0x15 // DVB BlueBook A156
|
|
#define END_OF_DISPLAY_SET_SEGMENT 0x80
|
|
#define STUFFING_SEGMENT 0xFF
|
|
|
|
// Set these to 'true' for debug output, which is written into the file dbg-log.htm
|
|
// in the current working directory. The HTML file shows the actual bitmaps (dbg-nnn.jpg)
|
|
// used to display the subtitles.
|
|
static bool DebugNormal = false; // shows pages, regions and objects
|
|
static bool DebugVerbose = false; // shows everything
|
|
static bool DebugDisplay = DebugVerbose || DebugNormal;
|
|
static bool DebugPages = DebugVerbose || DebugNormal;
|
|
static bool DebugRegions = DebugVerbose || DebugNormal;
|
|
static bool DebugObjects = DebugVerbose || DebugNormal;
|
|
static bool DebugBitmaps = DebugVerbose || DebugNormal;
|
|
static bool DebugConverter = DebugVerbose;
|
|
static bool DebugSegments = DebugVerbose;
|
|
static bool DebugPixel = DebugVerbose;
|
|
static bool DebugCluts = DebugVerbose;
|
|
static bool DebugOutput = DebugVerbose;
|
|
|
|
#define dbgdisplay(a...) if (DebugDisplay) SD.WriteHtml(a)
|
|
#define dbgpages(a...) if (DebugPages) SD.WriteHtml(a)
|
|
#define dbgregions(a...) if (DebugRegions) SD.WriteHtml(a)
|
|
#define dbgobjects(a...) if (DebugObjects) SD.WriteHtml(a)
|
|
#define dbgbitmaps(a...) if (DebugBitmaps) SD.WriteHtml(a)
|
|
#define dbgconverter(a...) if (DebugConverter) SD.WriteHtml(a)
|
|
#define dbgsegments(a...) if (DebugSegments) SD.WriteHtml(a)
|
|
#define dbgpixel(a...) if (DebugPixel) SD.WriteHtml(a)
|
|
#define dbgcluts(a...) if (DebugCluts) SD.WriteHtml(a)
|
|
#define dbgoutput(a...) if (DebugOutput) SD.WriteHtml(a)
|
|
|
|
#define DBGMAXBITMAPS 100 // debug output will be stopped after this many bitmaps
|
|
#define DBGBITMAPWIDTH 400
|
|
|
|
// --- cSubtitleDebug --------------------------------------------------------
|
|
|
|
class cSubtitleDebug {
|
|
private:
|
|
cMutex mutex;
|
|
int imgCnt;
|
|
int64_t firstPts;
|
|
bool newFile;
|
|
double factor;
|
|
public:
|
|
cSubtitleDebug(void) { Reset(); }
|
|
void Reset(void);
|
|
bool Active(void) { return imgCnt < DBGMAXBITMAPS; }
|
|
int64_t FirstPts(void) { return firstPts; }
|
|
void SetFirstPts(int64_t FirstPts) { if (firstPts < 0) firstPts = FirstPts; }
|
|
void SetFactor(double Factor) { factor = Factor; }
|
|
cString WriteJpeg(const cBitmap *Bitmap, int MaxX = 0, int MaxY = 0);
|
|
void WriteHtml(const char *Format, ...);
|
|
};
|
|
|
|
void cSubtitleDebug::Reset(void)
|
|
{
|
|
imgCnt = 0;
|
|
firstPts = -1;
|
|
newFile = true;
|
|
factor = 1.0;
|
|
}
|
|
|
|
cString cSubtitleDebug::WriteJpeg(const cBitmap *Bitmap, int MaxX, int MaxY)
|
|
{
|
|
if (!Active())
|
|
return NULL;
|
|
cMutexLock MutexLock(&mutex);
|
|
cBitmap *Scaled = Bitmap->Scaled(factor, factor, true);
|
|
int w = MaxX ? int(round(MaxX * factor)) : Scaled->Width();
|
|
int h = MaxY ? int(round(MaxY * factor)) : Scaled->Height();
|
|
uchar mem[w * h * 3];
|
|
for (int x = 0; x < w; x++) {
|
|
for (int y = 0; y < h; y++) {
|
|
tColor c = Scaled->GetColor(x, y);
|
|
int o = (y * w + x) * 3;
|
|
mem[o++] = (c & 0x00FF0000) >> 16;
|
|
mem[o++] = (c & 0x0000FF00) >> 8;
|
|
mem[o] = (c & 0x000000FF);
|
|
}
|
|
}
|
|
delete Scaled;
|
|
int Size = 0;
|
|
uchar *Jpeg = RgbToJpeg(mem, w, h, Size);
|
|
cString ImgName = cString::sprintf("dbg-%03d.jpg", imgCnt++);
|
|
int f = open(ImgName, O_WRONLY | O_CREAT, DEFFILEMODE);
|
|
if (f >= 0) {
|
|
if (write(f, Jpeg, Size) < 0)
|
|
LOG_ERROR_STR(*ImgName);
|
|
close(f);
|
|
}
|
|
free(Jpeg);
|
|
return ImgName;
|
|
}
|
|
|
|
void cSubtitleDebug::WriteHtml(const char *Format, ...)
|
|
{
|
|
if (!Active())
|
|
return;
|
|
cMutexLock MutexLock(&mutex);
|
|
if (FILE *f = fopen("dbg-log.htm", newFile ? "w" : "a")) {
|
|
va_list ap;
|
|
va_start(ap, Format);
|
|
vfprintf(f, Format, ap);
|
|
va_end(ap);
|
|
fclose(f);
|
|
newFile = false;
|
|
}
|
|
}
|
|
|
|
static cSubtitleDebug SD;
|
|
|
|
// --- cSubtitleClut ---------------------------------------------------------
|
|
|
|
class cSubtitleClut : public cListObject {
|
|
private:
|
|
int clutId;
|
|
int clutVersionNumber;
|
|
cPalette palette2;
|
|
cPalette palette4;
|
|
cPalette palette8;
|
|
tColor yuv2rgb(int Y, int Cb, int Cr);
|
|
void SetColor(int Bpp, int Index, tColor Color);
|
|
public:
|
|
cSubtitleClut(int ClutId);
|
|
void Parse(cBitStream &bs);
|
|
int ClutId(void) { return clutId; }
|
|
int ClutVersionNumber(void) { return clutVersionNumber; }
|
|
const cPalette *GetPalette(int Bpp);
|
|
};
|
|
|
|
cSubtitleClut::cSubtitleClut(int ClutId)
|
|
:palette2(2)
|
|
,palette4(4)
|
|
,palette8(8)
|
|
{
|
|
int a = 0, r = 0, g = 0, b = 0;
|
|
clutId = ClutId;
|
|
clutVersionNumber = -1;
|
|
// ETSI EN 300 743 10.3: 4-entry CLUT default contents
|
|
palette2.SetColor(0, ArgbToColor( 0, 0, 0, 0));
|
|
palette2.SetColor(1, ArgbToColor(255, 255, 255, 255));
|
|
palette2.SetColor(2, ArgbToColor(255, 0, 0, 0));
|
|
palette2.SetColor(3, ArgbToColor(255, 127, 127, 127));
|
|
// ETSI EN 300 743 10.2: 16-entry CLUT default contents
|
|
palette4.SetColor(0, ArgbToColor(0, 0, 0, 0));
|
|
for (int i = 1; i < 16; ++i) {
|
|
if (i < 8) {
|
|
r = (i & 1) ? 255 : 0;
|
|
g = (i & 2) ? 255 : 0;
|
|
b = (i & 4) ? 255 : 0;
|
|
}
|
|
else {
|
|
r = (i & 1) ? 127 : 0;
|
|
g = (i & 2) ? 127 : 0;
|
|
b = (i & 4) ? 127 : 0;
|
|
}
|
|
palette4.SetColor(i, ArgbToColor(255, r, g, b));
|
|
}
|
|
// ETSI EN 300 743 10.1: 256-entry CLUT default contents
|
|
palette8.SetColor(0, ArgbToColor(0, 0, 0, 0));
|
|
for (int i = 1; i < 256; ++i) {
|
|
if (i < 8) {
|
|
r = (i & 1) ? 255 : 0;
|
|
g = (i & 2) ? 255 : 0;
|
|
b = (i & 4) ? 255 : 0;
|
|
a = 63;
|
|
}
|
|
else {
|
|
switch (i & 0x88) {
|
|
case 0x00:
|
|
r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
|
|
g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
|
|
b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
|
|
a = 255;
|
|
break;
|
|
case 0x08:
|
|
r = ((i & 1) ? 85 : 0) + ((i & 0x10) ? 170 : 0);
|
|
g = ((i & 2) ? 85 : 0) + ((i & 0x20) ? 170 : 0);
|
|
b = ((i & 4) ? 85 : 0) + ((i & 0x40) ? 170 : 0);
|
|
a = 127;
|
|
break;
|
|
case 0x80:
|
|
r = 127 + ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
|
|
g = 127 + ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
|
|
b = 127 + ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
|
|
a = 255;
|
|
break;
|
|
case 0x88:
|
|
r = ((i & 1) ? 43 : 0) + ((i & 0x10) ? 85 : 0);
|
|
g = ((i & 2) ? 43 : 0) + ((i & 0x20) ? 85 : 0);
|
|
b = ((i & 4) ? 43 : 0) + ((i & 0x40) ? 85 : 0);
|
|
a = 255;
|
|
break;
|
|
}
|
|
}
|
|
palette8.SetColor(i, ArgbToColor(a, r, g, b));
|
|
}
|
|
}
|
|
|
|
void cSubtitleClut::Parse(cBitStream &bs)
|
|
{
|
|
int Version = bs.GetBits(4);
|
|
if (clutVersionNumber == Version)
|
|
return; // no update
|
|
clutVersionNumber = Version;
|
|
bs.SkipBits(4); // reserved
|
|
dbgcluts("<b>clut</b> id %d version %d<br>\n", clutId, clutVersionNumber);
|
|
while (!bs.IsEOF()) {
|
|
uchar clutEntryId = bs.GetBits(8);
|
|
bool entryClut2Flag = bs.GetBit();
|
|
bool entryClut4Flag = bs.GetBit();
|
|
bool entryClut8Flag = bs.GetBit();
|
|
bs.SkipBits(4); // reserved
|
|
uchar yval;
|
|
uchar crval;
|
|
uchar cbval;
|
|
uchar tval;
|
|
if (bs.GetBit()) { // full_range_flag
|
|
yval = bs.GetBits(8);
|
|
crval = bs.GetBits(8);
|
|
cbval = bs.GetBits(8);
|
|
tval = bs.GetBits(8);
|
|
}
|
|
else {
|
|
yval = bs.GetBits(6) << 2;
|
|
crval = bs.GetBits(4) << 4;
|
|
cbval = bs.GetBits(4) << 4;
|
|
tval = bs.GetBits(2) << 6;
|
|
}
|
|
tColor value = 0;
|
|
if (yval) {
|
|
value = yuv2rgb(yval, cbval, crval);
|
|
value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24;
|
|
}
|
|
dbgcluts("%2d %d %d %d %08X<br>\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value);
|
|
if (entryClut2Flag)
|
|
SetColor(2, clutEntryId, value);
|
|
if (entryClut4Flag)
|
|
SetColor(4, clutEntryId, value);
|
|
if (entryClut8Flag)
|
|
SetColor(8, clutEntryId, value);
|
|
}
|
|
}
|
|
|
|
tColor cSubtitleClut::yuv2rgb(int Y, int Cb, int Cr)
|
|
{
|
|
int Ey, Epb, Epr;
|
|
int Eg, Eb, Er;
|
|
|
|
Ey = (Y - 16);
|
|
Epb = (Cb - 128);
|
|
Epr = (Cr - 128);
|
|
/* ITU-R 709 */
|
|
Er = constrain((298 * Ey + 460 * Epr) / 256, 0, 255);
|
|
Eg = constrain((298 * Ey - 55 * Epb - 137 * Epr) / 256, 0, 255);
|
|
Eb = constrain((298 * Ey + 543 * Epb ) / 256, 0, 255);
|
|
|
|
return (Er << 16) | (Eg << 8) | Eb;
|
|
}
|
|
|
|
void cSubtitleClut::SetColor(int Bpp, int Index, tColor Color)
|
|
{
|
|
switch (Bpp) {
|
|
case 2: palette2.SetColor(Index, Color); break;
|
|
case 4: palette4.SetColor(Index, Color); break;
|
|
case 8: palette8.SetColor(Index, Color); break;
|
|
default: esyslog("ERROR: wrong Bpp in cSubtitleClut::SetColor(%d, %d, %08X)", Bpp, Index, Color);
|
|
}
|
|
}
|
|
|
|
const cPalette *cSubtitleClut::GetPalette(int Bpp)
|
|
{
|
|
switch (Bpp) {
|
|
case 2: return &palette2;
|
|
case 4: return &palette4;
|
|
case 8: return &palette8;
|
|
default: esyslog("ERROR: wrong Bpp in cSubtitleClut::GetPalette(%d)", Bpp);
|
|
}
|
|
return &palette8;
|
|
}
|
|
|
|
// --- cSubtitleObject -------------------------------------------------------
|
|
|
|
class cSubtitleObject : public cListObject {
|
|
private:
|
|
int objectId;
|
|
int objectVersionNumber;
|
|
int objectCodingMethod;
|
|
bool nonModifyingColorFlag;
|
|
int topLength;
|
|
int botLength;
|
|
uchar *topData;
|
|
uchar *botData;
|
|
char *txtData;
|
|
int lineHeight;
|
|
void DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length);
|
|
bool Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable);
|
|
bool Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y, const uint8_t *MapTable);
|
|
bool Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int&x, int y);
|
|
void DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even);
|
|
void DecodeCharacterString(const uchar *Data, int NumberOfCodes);
|
|
public:
|
|
cSubtitleObject(int ObjectId);
|
|
~cSubtitleObject();
|
|
void Parse(cBitStream &bs);
|
|
int ObjectId(void) { return objectId; }
|
|
int ObjectVersionNumber(void) { return objectVersionNumber; }
|
|
int ObjectCodingMethod(void) { return objectCodingMethod; }
|
|
bool NonModifyingColorFlag(void) { return nonModifyingColorFlag; }
|
|
void Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg);
|
|
};
|
|
|
|
cSubtitleObject::cSubtitleObject(int ObjectId)
|
|
{
|
|
objectId = ObjectId;
|
|
objectVersionNumber = -1;
|
|
objectCodingMethod = -1;
|
|
nonModifyingColorFlag = false;
|
|
topLength = 0;
|
|
botLength = 0;
|
|
topData = NULL;
|
|
botData = NULL;
|
|
txtData = NULL;
|
|
lineHeight = 26; // configurable subtitling font size?
|
|
}
|
|
|
|
cSubtitleObject::~cSubtitleObject()
|
|
{
|
|
free(topData);
|
|
free(botData);
|
|
free(txtData);
|
|
}
|
|
|
|
void cSubtitleObject::Parse(cBitStream &bs)
|
|
{
|
|
int Version = bs.GetBits(4);
|
|
if (objectVersionNumber == Version)
|
|
return; // no update
|
|
objectVersionNumber = Version;
|
|
objectCodingMethod = bs.GetBits(2);
|
|
nonModifyingColorFlag = bs.GetBit();
|
|
bs.SkipBit(); // reserved
|
|
dbgobjects("<b>object</b> id %d version %d method %d modify %d", objectId, objectVersionNumber, objectCodingMethod, nonModifyingColorFlag); // no "<br>\n" here, DecodeCharacterString() may add data
|
|
if (objectCodingMethod == 0) { // coding of pixels
|
|
topLength = bs.GetBits(16);
|
|
botLength = bs.GetBits(16);
|
|
free(topData);
|
|
if ((topData = MALLOC(uchar, topLength)) != NULL)
|
|
memcpy(topData, bs.GetData(), topLength);
|
|
else
|
|
topLength = 0;
|
|
free(botData);
|
|
if ((botData = MALLOC(uchar, botLength)) != NULL)
|
|
memcpy(botData, bs.GetData() + topLength, botLength);
|
|
else
|
|
botLength = 0;
|
|
bs.WordAlign();
|
|
}
|
|
else if (objectCodingMethod == 1) { // coded as a string of characters
|
|
int numberOfCodes = bs.GetBits(8);
|
|
DecodeCharacterString(bs.GetData(), numberOfCodes);
|
|
}
|
|
dbgobjects("<br>\n");
|
|
if (DebugObjects) {
|
|
// We can't get the actual clut here, so we use a default one. This may lead to
|
|
// funny colors, but we just want to get a rough idea of what's in the object, anyway.
|
|
cSubtitleClut Clut(0);
|
|
cBitmap b(1920, 1080, 8);
|
|
b.Replace(*Clut.GetPalette(b.Bpp()));
|
|
b.Clean();
|
|
Render(&b, 0, 0, 0, 1);
|
|
int x1, y1, x2, y2;
|
|
if (b.Dirty(x1, y1, x2, y2)) {
|
|
cString ImgName = SD.WriteJpeg(&b, x2, y2);
|
|
dbgobjects("<img src=\"%s\"><br>\n", *ImgName);
|
|
}
|
|
}
|
|
}
|
|
|
|
void cSubtitleObject::DecodeCharacterString(const uchar *Data, int NumberOfCodes)
|
|
{
|
|
// "ETSI EN 300 743 V1.3.1 (2006-11)", chapter 7.2.5 "Object data segment" specifies
|
|
// character_code to be a 16-bit index number into the character table identified
|
|
// in the subtitle_descriptor. However, the "subtitling_descriptor" <sic> according to
|
|
// "ETSI EN 300 468 V1.13.1 (2012-04)" doesn't contain a "character table identifier".
|
|
// It only contains a three letter language code, without any specification as to how
|
|
// this is related to a specific character table.
|
|
// Apparently the first "code" in textual subtitles contains the character table
|
|
// identifier, and all codes are 8-bit only. So let's first make Data a string of
|
|
// 8-bit characters:
|
|
if (NumberOfCodes > 0) {
|
|
char txt[NumberOfCodes + 1];
|
|
for (int i = 0; i < NumberOfCodes; i++)
|
|
txt[i] = Data[i * 2 + 1];
|
|
txt[NumberOfCodes] = 0;
|
|
bool singleByte;
|
|
const uchar *from = (uchar *)txt;
|
|
int len = NumberOfCodes;
|
|
const char *CharacterTable = SI::getCharacterTable(from, len, &singleByte);
|
|
dbgobjects(" table %s single %d raw '%s'", CharacterTable, singleByte, from);
|
|
cCharSetConv conv(CharacterTable, cCharSetConv::SystemCharacterTable());
|
|
const char *s = conv.Convert((const char *)from);
|
|
dbgobjects(" conv '%s'", s);
|
|
free(txtData);
|
|
txtData = strdup(s);
|
|
}
|
|
}
|
|
|
|
void cSubtitleObject::DecodeSubBlock(cBitmap *Bitmap, int px, int py, const uchar *Data, int Length, bool Even)
|
|
{
|
|
int x = 0;
|
|
int y = Even ? 0 : 1;
|
|
uint8_t map2to4[ 4] = { 0x00, 0x07, 0x08, 0x0F };
|
|
uint8_t map2to8[ 4] = { 0x00, 0x77, 0x88, 0xFF };
|
|
uint8_t map4to8[16] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
|
|
const uint8_t *mapTable = NULL;
|
|
cBitStream bs(Data, Length * 8);
|
|
while (!bs.IsEOF()) {
|
|
switch (bs.GetBits(8)) {
|
|
case 0x10:
|
|
dbgpixel("2-bit / pixel code string<br>\n");
|
|
switch (Bitmap->Bpp()) {
|
|
case 8: mapTable = map2to8; break;
|
|
case 4: mapTable = map2to4; break;
|
|
default: mapTable = NULL; break;
|
|
}
|
|
while (Decode2BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF())
|
|
;
|
|
bs.ByteAlign();
|
|
break;
|
|
case 0x11:
|
|
dbgpixel("4-bit / pixel code string<br>\n");
|
|
switch (Bitmap->Bpp()) {
|
|
case 8: mapTable = map4to8; break;
|
|
default: mapTable = NULL; break;
|
|
}
|
|
while (Decode4BppCodeString(Bitmap, px, py, &bs, x, y, mapTable) && !bs.IsEOF())
|
|
;
|
|
bs.ByteAlign();
|
|
break;
|
|
case 0x12:
|
|
dbgpixel("8-bit / pixel code string<br>\n");
|
|
while (Decode8BppCodeString(Bitmap, px, py, &bs, x, y) && !bs.IsEOF())
|
|
;
|
|
break;
|
|
case 0x20:
|
|
dbgpixel("sub block 2 to 4 map<br>\n");
|
|
for (int i = 0; i < 4; ++i)
|
|
map2to4[i] = bs.GetBits(4);
|
|
break;
|
|
case 0x21:
|
|
dbgpixel("sub block 2 to 8 map<br>\n");
|
|
for (int i = 0; i < 4; ++i)
|
|
map2to8[i] = bs.GetBits(8);
|
|
break;
|
|
case 0x22:
|
|
dbgpixel("sub block 4 to 8 map<br>\n");
|
|
for (int i = 0; i < 16; ++i)
|
|
map4to8[i] = bs.GetBits(8);
|
|
break;
|
|
case 0xF0:
|
|
dbgpixel("end of object line<br>\n");
|
|
x = 0;
|
|
y += 2;
|
|
break;
|
|
default: dbgpixel("unknown sub block %s %d<br>\n", __FUNCTION__, __LINE__);
|
|
}
|
|
}
|
|
}
|
|
|
|
void cSubtitleObject::DrawLine(cBitmap *Bitmap, int x, int y, tIndex Index, int Length)
|
|
{
|
|
if (nonModifyingColorFlag && Index == 1)
|
|
return;
|
|
for (int pos = x; pos < x + Length; pos++)
|
|
Bitmap->SetIndex(pos, y, Index);
|
|
}
|
|
|
|
bool cSubtitleObject::Decode2BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable)
|
|
{
|
|
int rl = 0;
|
|
int color = 0;
|
|
uchar code = bs->GetBits(2);
|
|
if (code) {
|
|
color = code;
|
|
rl = 1;
|
|
}
|
|
else if (bs->GetBit()) { // switch_1
|
|
rl = bs->GetBits(3) + 3;
|
|
color = bs->GetBits(2);
|
|
}
|
|
else if (bs->GetBit()) // switch_2
|
|
rl = 1; //color 0
|
|
else {
|
|
switch (bs->GetBits(2)) { // switch_3
|
|
case 0:
|
|
return false;
|
|
case 1:
|
|
rl = 2; //color 0
|
|
break;
|
|
case 2:
|
|
rl = bs->GetBits(4) + 12;
|
|
color = bs->GetBits(2);
|
|
break;
|
|
case 3:
|
|
rl = bs->GetBits(8) + 29;
|
|
color = bs->GetBits(2);
|
|
break;
|
|
default: ;
|
|
}
|
|
}
|
|
if (MapTable)
|
|
color = MapTable[color];
|
|
DrawLine(Bitmap, px + x, py + y, color, rl);
|
|
x += rl;
|
|
return true;
|
|
}
|
|
|
|
bool cSubtitleObject::Decode4BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y, const uint8_t *MapTable)
|
|
{
|
|
int rl = 0;
|
|
int color = 0;
|
|
uchar code = bs->GetBits(4);
|
|
if (code) {
|
|
color = code;
|
|
rl = 1;
|
|
}
|
|
else if (bs->GetBit() == 0) { // switch_1
|
|
code = bs->GetBits(3);
|
|
if (code)
|
|
rl = code + 2; //color 0
|
|
else
|
|
return false;
|
|
}
|
|
else if (bs->GetBit() == 0) { // switch_2
|
|
rl = bs->GetBits(2) + 4;
|
|
color = bs->GetBits(4);
|
|
}
|
|
else {
|
|
switch (bs->GetBits(2)) { // switch_3
|
|
case 0: // color 0
|
|
rl = 1;
|
|
break;
|
|
case 1: // color 0
|
|
rl = 2;
|
|
break;
|
|
case 2:
|
|
rl = bs->GetBits(4) + 9;
|
|
color = bs->GetBits(4);
|
|
break;
|
|
case 3:
|
|
rl = bs->GetBits(8) + 25;
|
|
color = bs->GetBits(4);
|
|
break;
|
|
}
|
|
}
|
|
if (MapTable)
|
|
color = MapTable[color];
|
|
DrawLine(Bitmap, px + x, py + y, color, rl);
|
|
x += rl;
|
|
return true;
|
|
}
|
|
|
|
bool cSubtitleObject::Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y)
|
|
{
|
|
int rl = 0;
|
|
int color = 0;
|
|
uchar code = bs->GetBits(8);
|
|
if (code) {
|
|
color = code;
|
|
rl = 1;
|
|
}
|
|
else if (bs->GetBit()) {
|
|
rl = bs->GetBits(7);
|
|
color = bs->GetBits(8);
|
|
}
|
|
else {
|
|
code = bs->GetBits(7);
|
|
if (code)
|
|
rl = code; // color 0
|
|
else
|
|
return false;
|
|
}
|
|
DrawLine(Bitmap, px + x, py + y, color, rl);
|
|
x += rl;
|
|
return true;
|
|
}
|
|
|
|
void cSubtitleObject::Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg)
|
|
{
|
|
if (objectCodingMethod == 0) { // coding of pixels
|
|
DecodeSubBlock(Bitmap, px, py, topData, topLength, true);
|
|
if (botLength)
|
|
DecodeSubBlock(Bitmap, px, py, botData, botLength, false);
|
|
else
|
|
DecodeSubBlock(Bitmap, px, py, topData, topLength, false);
|
|
}
|
|
else if (objectCodingMethod == 1) { // coded as a string of characters
|
|
if (txtData) {
|
|
//TODO couldn't we draw the text directly into Bitmap?
|
|
cFont *font = cFont::CreateFont(Setup.FontOsd, Setup.FontOsdSize);
|
|
cBitmap tmp(font->Width(txtData), font->Height(), Bitmap->Bpp());
|
|
double factor = (double)lineHeight / font->Height();
|
|
tmp.DrawText(0, 0, txtData, Bitmap->Color(IndexFg), Bitmap->Color(IndexBg), font);
|
|
cBitmap *scaled = tmp.Scaled(factor, factor, true);
|
|
Bitmap->DrawBitmap(px, py, *scaled);
|
|
delete scaled;
|
|
delete font;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- cSubtitleObjects ------------------------------------------------------
|
|
|
|
class cSubtitleObjects : public cList<cSubtitleObject> {
|
|
public:
|
|
cSubtitleObject *GetObjectById(int ObjectId, bool New = false);
|
|
};
|
|
|
|
cSubtitleObject *cSubtitleObjects::GetObjectById(int ObjectId, bool New)
|
|
{
|
|
for (cSubtitleObject *so = First(); so; so = Next(so)) {
|
|
if (so->ObjectId() == ObjectId)
|
|
return so;
|
|
}
|
|
if (!New)
|
|
return NULL;
|
|
cSubtitleObject *Object = new cSubtitleObject(ObjectId);
|
|
Add(Object);
|
|
return Object;
|
|
}
|
|
|
|
// --- cSubtitleObjectRef ----------------------------------------------------
|
|
|
|
class cSubtitleObjectRef : public cListObject {
|
|
private:
|
|
int objectId;
|
|
int objectType;
|
|
int objectProviderFlag;
|
|
int objectHorizontalPosition;
|
|
int objectVerticalPosition;
|
|
int foregroundPixelCode;
|
|
int backgroundPixelCode;
|
|
public:
|
|
cSubtitleObjectRef(cBitStream &bs);
|
|
int ObjectId(void) { return objectId; }
|
|
int ObjectType(void) { return objectType; }
|
|
int ObjectProviderFlag(void) { return objectProviderFlag; }
|
|
int ObjectHorizontalPosition(void) { return objectHorizontalPosition; }
|
|
int ObjectVerticalPosition(void) { return objectVerticalPosition; }
|
|
int ForegroundPixelCode(void) { return foregroundPixelCode; }
|
|
int BackgroundPixelCode(void) { return backgroundPixelCode; }
|
|
};
|
|
|
|
cSubtitleObjectRef::cSubtitleObjectRef(cBitStream &bs)
|
|
{
|
|
objectId = bs.GetBits(16);
|
|
objectType = bs.GetBits(2);
|
|
objectProviderFlag = bs.GetBits(2);
|
|
objectHorizontalPosition = bs.GetBits(12);
|
|
bs.SkipBits(4); // reserved
|
|
objectVerticalPosition = bs.GetBits(12);
|
|
if (objectType == 0x01 || objectType == 0x02) {
|
|
foregroundPixelCode = bs.GetBits(8);
|
|
backgroundPixelCode = bs.GetBits(8);
|
|
}
|
|
else {
|
|
foregroundPixelCode = 0;
|
|
backgroundPixelCode = 0;
|
|
}
|
|
dbgregions("<b>objectref</b> id %d type %d flag %d x %d y %d fg %d bg %d<br>\n", objectId, objectType, objectProviderFlag, objectHorizontalPosition, objectVerticalPosition, foregroundPixelCode, backgroundPixelCode);
|
|
}
|
|
|
|
// --- cSubtitleRegion -------------------------------------------------------
|
|
|
|
class cSubtitleRegion : public cListObject {
|
|
private:
|
|
int regionId;
|
|
int regionVersionNumber;
|
|
bool regionFillFlag;
|
|
int regionWidth;
|
|
int regionHeight;
|
|
int regionLevelOfCompatibility;
|
|
int regionDepth;
|
|
int clutId;
|
|
int region8bitPixelCode;
|
|
int region4bitPixelCode;
|
|
int region2bitPixelCode;
|
|
cList<cSubtitleObjectRef> objectRefs;
|
|
public:
|
|
cSubtitleRegion(int RegionId);
|
|
void Parse(cBitStream &bs);
|
|
int RegionId(void) { return regionId; }
|
|
int RegionVersionNumber(void) { return regionVersionNumber; }
|
|
bool RegionFillFlag(void) { return regionFillFlag; }
|
|
int RegionWidth(void) { return regionWidth; }
|
|
int RegionHeight(void) { return regionHeight; }
|
|
int RegionLevelOfCompatibility(void) { return regionLevelOfCompatibility; }
|
|
int RegionDepth(void) { return regionDepth; }
|
|
int ClutId(void) { return clutId; }
|
|
void Render(cBitmap *Bitmap, cSubtitleObjects *Objects);
|
|
};
|
|
|
|
cSubtitleRegion::cSubtitleRegion(int RegionId)
|
|
{
|
|
regionId = RegionId;
|
|
regionVersionNumber = -1;
|
|
regionFillFlag = false;
|
|
regionWidth = 0;
|
|
regionHeight = 0;
|
|
regionLevelOfCompatibility = 0;
|
|
regionDepth = 0;
|
|
clutId = -1;
|
|
region8bitPixelCode = 0;
|
|
region4bitPixelCode = 0;
|
|
region2bitPixelCode = 0;
|
|
}
|
|
|
|
void cSubtitleRegion::Parse(cBitStream &bs)
|
|
{
|
|
int Version = bs.GetBits(4);
|
|
if (regionVersionNumber == Version)
|
|
return; // no update
|
|
regionVersionNumber = Version;
|
|
regionFillFlag = bs.GetBit();
|
|
bs.SkipBits(3); // reserved
|
|
regionWidth = bs.GetBits(16);
|
|
regionHeight = bs.GetBits(16);
|
|
regionLevelOfCompatibility = 1 << bs.GetBits(3); // stored as "number of bits per pixel"
|
|
regionDepth = 1 << bs.GetBits(3); // stored as "number of bits per pixel"
|
|
bs.SkipBits(2); // reserved
|
|
clutId = bs.GetBits(8);
|
|
region8bitPixelCode = bs.GetBits(8);
|
|
region4bitPixelCode = bs.GetBits(4);
|
|
region2bitPixelCode = bs.GetBits(2);
|
|
bs.SkipBits(2); // reserved
|
|
dbgregions("<b>region</b> id %d version %d fill %d width %d height %d level %d depth %d clutId %d<br>\n", regionId, regionVersionNumber, regionFillFlag, regionWidth, regionHeight, regionLevelOfCompatibility, regionDepth, clutId);
|
|
// no objectRefs.Clear() here!
|
|
while (!bs.IsEOF())
|
|
objectRefs.Add(new cSubtitleObjectRef(bs));
|
|
}
|
|
|
|
void cSubtitleRegion::Render(cBitmap *Bitmap, cSubtitleObjects *Objects)
|
|
{
|
|
if (regionFillFlag) {
|
|
switch (Bitmap->Bpp()) {
|
|
case 2: Bitmap->Fill(region2bitPixelCode); break;
|
|
case 4: Bitmap->Fill(region4bitPixelCode); break;
|
|
case 8: Bitmap->Fill(region8bitPixelCode); break;
|
|
default: dbgregions("unknown bpp %d (%s %d)<br>\n", Bitmap->Bpp(), __FUNCTION__, __LINE__);
|
|
}
|
|
}
|
|
for (cSubtitleObjectRef *sor = objectRefs.First(); sor; sor = objectRefs.Next(sor)) {
|
|
if (cSubtitleObject *so = Objects->GetObjectById(sor->ObjectId())) {
|
|
so->Render(Bitmap, sor->ObjectHorizontalPosition(), sor->ObjectVerticalPosition(), sor->ForegroundPixelCode(), sor->BackgroundPixelCode());
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- cSubtitleRegionRef ----------------------------------------------------
|
|
|
|
class cSubtitleRegionRef : public cListObject {
|
|
private:
|
|
int regionId;
|
|
int regionHorizontalAddress;
|
|
int regionVerticalAddress;
|
|
public:
|
|
cSubtitleRegionRef(cBitStream &bs);
|
|
int RegionId(void) { return regionId; }
|
|
int RegionHorizontalAddress(void) { return regionHorizontalAddress; }
|
|
int RegionVerticalAddress(void) { return regionVerticalAddress; }
|
|
};
|
|
|
|
cSubtitleRegionRef::cSubtitleRegionRef(cBitStream &bs)
|
|
{
|
|
regionId = bs.GetBits(8);
|
|
bs.SkipBits(8); // reserved
|
|
regionHorizontalAddress = bs.GetBits(16);
|
|
regionVerticalAddress = bs.GetBits(16);
|
|
dbgpages("<b>regionref</b> id %d tx %d y %d<br>\n", regionId, regionHorizontalAddress, regionVerticalAddress);
|
|
}
|
|
|
|
// --- cDvbSubtitlePage ------------------------------------------------------
|
|
|
|
class cDvbSubtitlePage : public cListObject {
|
|
private:
|
|
int pageId;
|
|
int pageTimeout;
|
|
int pageVersionNumber;
|
|
int pageState;
|
|
int64_t pts;
|
|
bool pending;
|
|
cSubtitleObjects objects;
|
|
cList<cSubtitleClut> cluts;
|
|
cList<cSubtitleRegion> regions;
|
|
cList<cSubtitleRegionRef> regionRefs;
|
|
public:
|
|
cDvbSubtitlePage(int PageId);
|
|
void Parse(int64_t Pts, cBitStream &bs);
|
|
int PageId(void) { return pageId; }
|
|
int PageTimeout(void) { return pageTimeout; }
|
|
int PageVersionNumber(void) { return pageVersionNumber; }
|
|
int PageState(void) { return pageState; }
|
|
int64_t Pts(void) const { return pts; }
|
|
bool Pending(void) { return pending; }
|
|
cSubtitleObjects *Objects(void) { return &objects; }
|
|
tArea *GetAreas(int &NumAreas, double FactorX, double FactorY);
|
|
cSubtitleObject *GetObjectById(int ObjectId, bool New = false);
|
|
cSubtitleClut *GetClutById(int ClutId, bool New = false);
|
|
cSubtitleRegion *GetRegionById(int RegionId, bool New = false);
|
|
cSubtitleRegionRef *GetRegionRefByIndex(int RegionRefIndex) { return regionRefs.Get(RegionRefIndex); }
|
|
void SetPending(bool Pending) { pending = Pending; }
|
|
};
|
|
|
|
cDvbSubtitlePage::cDvbSubtitlePage(int PageId)
|
|
{
|
|
pageId = PageId;
|
|
pageTimeout = 0;
|
|
pageVersionNumber = -1;
|
|
pageState = -1;
|
|
pts = -1;
|
|
pending = false;
|
|
}
|
|
|
|
void cDvbSubtitlePage::Parse(int64_t Pts, cBitStream &bs)
|
|
{
|
|
if (Pts >= 0)
|
|
pts = Pts;
|
|
pageTimeout = bs.GetBits(8);
|
|
int Version = bs.GetBits(4);
|
|
if (pageVersionNumber == Version)
|
|
return; // no update
|
|
pageVersionNumber = Version;
|
|
pageState = bs.GetBits(2);
|
|
switch (pageState) {
|
|
case 0: // normal case - page update
|
|
break;
|
|
case 1: // acquisition point - page refresh
|
|
regions.Clear();
|
|
objects.Clear();
|
|
break;
|
|
case 2: // mode change - new page
|
|
regions.Clear();
|
|
cluts.Clear();
|
|
objects.Clear();
|
|
break;
|
|
case 3: // reserved
|
|
break;
|
|
default: dbgpages("unknown page state: %d<br>\n", pageState);
|
|
}
|
|
bs.SkipBits(2); // reserved
|
|
dbgpages("<hr>\n<b>page</b> id %d version %d pts %"PRId64" timeout %d state %d<br>\n", pageId, pageVersionNumber, pts, pageTimeout, pageState);
|
|
regionRefs.Clear();
|
|
while (!bs.IsEOF())
|
|
regionRefs.Add(new cSubtitleRegionRef(bs));
|
|
pending = true;
|
|
}
|
|
|
|
tArea *cDvbSubtitlePage::GetAreas(int &NumAreas, double FactorX, double FactorY)
|
|
{
|
|
if (regions.Count() > 0) {
|
|
NumAreas = regionRefs.Count();
|
|
tArea *Areas = new tArea[NumAreas];
|
|
tArea *a = Areas;
|
|
for (cSubtitleRegionRef *srr = regionRefs.First(); srr; srr = regionRefs.Next(srr)) {
|
|
if (cSubtitleRegion *sr = GetRegionById(srr->RegionId())) {
|
|
a->x1 = int(round(FactorX * srr->RegionHorizontalAddress()));
|
|
a->y1 = int(round(FactorY * srr->RegionVerticalAddress()));
|
|
a->x2 = int(round(FactorX * (srr->RegionHorizontalAddress() + sr->RegionWidth() - 1)));
|
|
a->y2 = int(round(FactorY * (srr->RegionVerticalAddress() + sr->RegionHeight() - 1)));
|
|
a->bpp = sr->RegionDepth();
|
|
while ((a->Width() & 3) != 0)
|
|
a->x2++; // aligns width to a multiple of 4, so 2, 4 and 8 bpp will work
|
|
}
|
|
else
|
|
a->x1 = a->y1 = a->x2 = a->y2 = a->bpp = 0;
|
|
a++;
|
|
}
|
|
return Areas;
|
|
}
|
|
NumAreas = 0;
|
|
return NULL;
|
|
}
|
|
|
|
cSubtitleClut *cDvbSubtitlePage::GetClutById(int ClutId, bool New)
|
|
{
|
|
for (cSubtitleClut *sc = cluts.First(); sc; sc = cluts.Next(sc)) {
|
|
if (sc->ClutId() == ClutId)
|
|
return sc;
|
|
}
|
|
if (!New)
|
|
return NULL;
|
|
cSubtitleClut *Clut = new cSubtitleClut(ClutId);
|
|
cluts.Add(Clut);
|
|
return Clut;
|
|
}
|
|
|
|
cSubtitleRegion *cDvbSubtitlePage::GetRegionById(int RegionId, bool New)
|
|
{
|
|
for (cSubtitleRegion *sr = regions.First(); sr; sr = regions.Next(sr)) {
|
|
if (sr->RegionId() == RegionId)
|
|
return sr;
|
|
}
|
|
if (!New)
|
|
return NULL;
|
|
cSubtitleRegion *Region = new cSubtitleRegion(RegionId);
|
|
regions.Add(Region);
|
|
return Region;
|
|
}
|
|
|
|
cSubtitleObject *cDvbSubtitlePage::GetObjectById(int ObjectId, bool New)
|
|
{
|
|
return objects.GetObjectById(ObjectId, New);
|
|
}
|
|
|
|
// --- cDvbSubtitleAssembler -------------------------------------------------
|
|
|
|
class cDvbSubtitleAssembler {
|
|
private:
|
|
uchar *data;
|
|
int length;
|
|
int pos;
|
|
int size;
|
|
bool Realloc(int Size);
|
|
public:
|
|
cDvbSubtitleAssembler(void);
|
|
virtual ~cDvbSubtitleAssembler();
|
|
void Reset(void);
|
|
unsigned char *Get(int &Length);
|
|
void Put(const uchar *Data, int Length);
|
|
};
|
|
|
|
cDvbSubtitleAssembler::cDvbSubtitleAssembler(void)
|
|
{
|
|
data = NULL;
|
|
size = 0;
|
|
Reset();
|
|
}
|
|
|
|
cDvbSubtitleAssembler::~cDvbSubtitleAssembler()
|
|
{
|
|
free(data);
|
|
}
|
|
|
|
void cDvbSubtitleAssembler::Reset(void)
|
|
{
|
|
length = 0;
|
|
pos = 0;
|
|
}
|
|
|
|
bool cDvbSubtitleAssembler::Realloc(int Size)
|
|
{
|
|
if (Size > size) {
|
|
Size = max(Size, 2048);
|
|
if (uchar *NewBuffer = (uchar *)realloc(data, Size)) {
|
|
size = Size;
|
|
data = NewBuffer;
|
|
}
|
|
else {
|
|
esyslog("ERROR: can't allocate memory for subtitle assembler");
|
|
length = 0;
|
|
size = 0;
|
|
free(data);
|
|
data = NULL;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
unsigned char *cDvbSubtitleAssembler::Get(int &Length)
|
|
{
|
|
if (length > pos + 5) {
|
|
Length = (data[pos + 4] << 8) + data[pos + 5] + 6;
|
|
if (length >= pos + Length) {
|
|
unsigned char *result = data + pos;
|
|
pos += Length;
|
|
return result;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void cDvbSubtitleAssembler::Put(const uchar *Data, int Length)
|
|
{
|
|
if (Length && Realloc(length + Length)) {
|
|
memcpy(data + length, Data, Length);
|
|
length += Length;
|
|
}
|
|
}
|
|
|
|
// --- cDvbSubtitleBitmaps ---------------------------------------------------
|
|
|
|
class cDvbSubtitleBitmaps : public cListObject {
|
|
private:
|
|
int state;
|
|
int64_t pts;
|
|
int timeout;
|
|
tArea *areas;
|
|
int numAreas;
|
|
double osdFactorX;
|
|
double osdFactorY;
|
|
cVector<cBitmap *> bitmaps;
|
|
public:
|
|
cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY);
|
|
~cDvbSubtitleBitmaps();
|
|
int State(void) { return state; }
|
|
int64_t Pts(void) { return pts; }
|
|
int Timeout(void) { return timeout; }
|
|
void AddBitmap(cBitmap *Bitmap);
|
|
bool HasBitmaps(void) { return bitmaps.Size(); }
|
|
void Draw(cOsd *Osd);
|
|
void DbgDump(int WindowWidth, int WindowHeight);
|
|
};
|
|
|
|
cDvbSubtitleBitmaps::cDvbSubtitleBitmaps(int State, int64_t Pts, int Timeout, tArea *Areas, int NumAreas, double OsdFactorX, double OsdFactorY)
|
|
{
|
|
state = State;
|
|
pts = Pts;
|
|
timeout = Timeout;
|
|
areas = Areas;
|
|
numAreas = NumAreas;
|
|
osdFactorX = OsdFactorX;
|
|
osdFactorY = OsdFactorY;
|
|
}
|
|
|
|
cDvbSubtitleBitmaps::~cDvbSubtitleBitmaps()
|
|
{
|
|
delete[] areas;
|
|
for (int i = 0; i < bitmaps.Size(); i++)
|
|
delete bitmaps[i];
|
|
}
|
|
|
|
void cDvbSubtitleBitmaps::AddBitmap(cBitmap *Bitmap)
|
|
{
|
|
bitmaps.Append(Bitmap);
|
|
}
|
|
|
|
void cDvbSubtitleBitmaps::Draw(cOsd *Osd)
|
|
{
|
|
bool Scale = !(DoubleEqual(osdFactorX, 1.0) && DoubleEqual(osdFactorY, 1.0));
|
|
bool AntiAlias = true;
|
|
if (Scale && osdFactorX > 1.0 || osdFactorY > 1.0) {
|
|
// Upscaling requires 8bpp:
|
|
int Bpp[MAXOSDAREAS];
|
|
for (int i = 0; i < numAreas; i++) {
|
|
Bpp[i] = areas[i].bpp;
|
|
areas[i].bpp = 8;
|
|
}
|
|
if (Osd->CanHandleAreas(areas, numAreas) != oeOk) {
|
|
for (int i = 0; i < numAreas; i++)
|
|
areas[i].bpp = Bpp[i];
|
|
AntiAlias = false;
|
|
}
|
|
}
|
|
if (State() == 0 || Osd->SetAreas(areas, numAreas) == oeOk) {
|
|
for (int i = 0; i < bitmaps.Size(); i++) {
|
|
cBitmap *b = bitmaps[i];
|
|
if (Scale)
|
|
b = b->Scaled(osdFactorX, osdFactorY, AntiAlias);
|
|
Osd->DrawBitmap(int(round(b->X0() * osdFactorX)), int(round(b->Y0() * osdFactorY)), *b);
|
|
if (b != bitmaps[i])
|
|
delete b;
|
|
}
|
|
Osd->Flush();
|
|
}
|
|
}
|
|
|
|
void cDvbSubtitleBitmaps::DbgDump(int WindowWidth, int WindowHeight)
|
|
{
|
|
if (!SD.Active())
|
|
return;
|
|
SD.SetFirstPts(Pts());
|
|
double STC = double(cDevice::PrimaryDevice()->GetSTC() - SD.FirstPts()) / 90000;
|
|
double Start = double(Pts() - SD.FirstPts()) / 90000;
|
|
double Duration = Timeout();
|
|
double End = Start + Duration;
|
|
cBitmap Bitmap(WindowWidth, WindowHeight, 8);
|
|
#define DBGBACKGROUND 0xA0A0A0
|
|
Bitmap.DrawRectangle(0, 0, WindowWidth - 1, WindowHeight - 1, DBGBACKGROUND);
|
|
for (int i = 0; i < bitmaps.Size(); i++) {
|
|
cBitmap *b = bitmaps[i];
|
|
Bitmap.DrawBitmap(b->X0(), b->Y0(), *b);
|
|
}
|
|
cString ImgName = SD.WriteJpeg(&Bitmap);
|
|
#define BORDER //" border=1"
|
|
SD.WriteHtml("<p>%s<br>", State() == 0 ? "page update" : State() == 1 ? "page refresh" : State() == 2 ? "new page" : "???");
|
|
SD.WriteHtml("<table" BORDER "><tr><td>");
|
|
SD.WriteHtml("%.2f", STC);
|
|
SD.WriteHtml("</td><td>");
|
|
SD.WriteHtml("<img src=\"%s\">", *ImgName);
|
|
SD.WriteHtml("</td><td style=\"height:100%%\"><table" BORDER " style=\"height:100%%\">");
|
|
SD.WriteHtml("<tr><td valign=top><b>%.2f</b></td></tr>", Start);
|
|
SD.WriteHtml("<tr><td valign=middle>%.2f</td></tr>", Duration);
|
|
SD.WriteHtml("<tr><td valign=bottom>%.2f</td></tr>", End);
|
|
SD.WriteHtml("</table></td>");
|
|
SD.WriteHtml("</tr></table>\n");
|
|
}
|
|
|
|
// --- cDvbSubtitleConverter -------------------------------------------------
|
|
|
|
int cDvbSubtitleConverter::setupLevel = 0;
|
|
|
|
cDvbSubtitleConverter::cDvbSubtitleConverter(void)
|
|
:cThread("subtitleConverter")
|
|
{
|
|
dvbSubtitleAssembler = new cDvbSubtitleAssembler;
|
|
osd = NULL;
|
|
frozen = false;
|
|
ddsVersionNumber = -1;
|
|
displayWidth = windowWidth = 720;
|
|
displayHeight = windowHeight = 576;
|
|
windowHorizontalOffset = 0;
|
|
windowVerticalOffset = 0;
|
|
pages = new cList<cDvbSubtitlePage>;
|
|
bitmaps = new cList<cDvbSubtitleBitmaps>;
|
|
SD.Reset();
|
|
Start();
|
|
}
|
|
|
|
cDvbSubtitleConverter::~cDvbSubtitleConverter()
|
|
{
|
|
Cancel(3);
|
|
delete dvbSubtitleAssembler;
|
|
delete osd;
|
|
delete bitmaps;
|
|
delete pages;
|
|
}
|
|
|
|
void cDvbSubtitleConverter::SetupChanged(void)
|
|
{
|
|
setupLevel++;
|
|
}
|
|
|
|
void cDvbSubtitleConverter::Reset(void)
|
|
{
|
|
dbgconverter("converter reset -----------------------<br>\n");
|
|
dvbSubtitleAssembler->Reset();
|
|
Lock();
|
|
pages->Clear();
|
|
bitmaps->Clear();
|
|
DELETENULL(osd);
|
|
frozen = false;
|
|
ddsVersionNumber = -1;
|
|
displayWidth = windowWidth = 720;
|
|
displayHeight = windowHeight = 576;
|
|
windowHorizontalOffset = 0;
|
|
windowVerticalOffset = 0;
|
|
Unlock();
|
|
}
|
|
|
|
int cDvbSubtitleConverter::ConvertFragments(const uchar *Data, int Length)
|
|
{
|
|
if (Data && Length > 8) {
|
|
int PayloadOffset = PesPayloadOffset(Data);
|
|
int SubstreamHeaderLength = 4;
|
|
bool ResetSubtitleAssembler = Data[PayloadOffset + 3] == 0x00;
|
|
|
|
// Compatibility mode for old subtitles plugin:
|
|
if ((Data[7] & 0x01) && (Data[PayloadOffset - 3] & 0x81) == 0x01 && Data[PayloadOffset - 2] == 0x81) {
|
|
PayloadOffset--;
|
|
SubstreamHeaderLength = 1;
|
|
ResetSubtitleAssembler = Data[8] >= 5;
|
|
}
|
|
|
|
if (Length > PayloadOffset + SubstreamHeaderLength) {
|
|
int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1;
|
|
if (pts >= 0)
|
|
dbgconverter("converter PTS: %"PRId64"<br>\n", pts);
|
|
const uchar *data = Data + PayloadOffset + SubstreamHeaderLength; // skip substream header
|
|
int length = Length - PayloadOffset - SubstreamHeaderLength; // skip substream header
|
|
if (ResetSubtitleAssembler)
|
|
dvbSubtitleAssembler->Reset();
|
|
|
|
if (length > 3) {
|
|
if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F)
|
|
dvbSubtitleAssembler->Put(data + 2, length - 2);
|
|
else
|
|
dvbSubtitleAssembler->Put(data, length);
|
|
|
|
int Count;
|
|
while (true) {
|
|
unsigned char *b = dvbSubtitleAssembler->Get(Count);
|
|
if (b && b[0] == 0x0F) {
|
|
if (ExtractSegment(b, Count, pts) == -1)
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Length;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int cDvbSubtitleConverter::Convert(const uchar *Data, int Length)
|
|
{
|
|
if (Data && Length > 8) {
|
|
int PayloadOffset = PesPayloadOffset(Data);
|
|
if (Length > PayloadOffset) {
|
|
int64_t pts = PesHasPts(Data) ? PesGetPts(Data) : -1;
|
|
if (pts >= 0)
|
|
dbgconverter("converter PTS: %"PRId64"<br>\n", pts);
|
|
const uchar *data = Data + PayloadOffset;
|
|
int length = Length - PayloadOffset;
|
|
if (length > 3) {
|
|
if (data[0] == 0x20 && data[1] == 0x00 && data[2] == 0x0F) {
|
|
data += 2;
|
|
length -= 2;
|
|
}
|
|
const uchar *b = data;
|
|
while (length > 0) {
|
|
if (b[0] == 0x0F) {
|
|
int n = ExtractSegment(b, length, pts);
|
|
if (n < 0)
|
|
break;
|
|
b += n;
|
|
length -= n;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return Length;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define LimitTo32Bit(n) ((n) & 0x00000000FFFFFFFFL)
|
|
|
|
void cDvbSubtitleConverter::Action(void)
|
|
{
|
|
int LastSetupLevel = setupLevel;
|
|
cTimeMs Timeout;
|
|
while (Running()) {
|
|
int WaitMs = 100;
|
|
if (!frozen) {
|
|
LOCK_THREAD;
|
|
if (osd) {
|
|
int NewSetupLevel = setupLevel;
|
|
if (Timeout.TimedOut() || LastSetupLevel != NewSetupLevel) {
|
|
dbgoutput("closing osd<br>\n");
|
|
DELETENULL(osd);
|
|
}
|
|
LastSetupLevel = NewSetupLevel;
|
|
}
|
|
for (cDvbSubtitleBitmaps *sb = bitmaps->First(); sb; sb = bitmaps->Next(sb)) {
|
|
// Calculate the Delta between the STC (the current timestamp of the video)
|
|
// and the bitmap's PTS (the timestamp when the bitmap shall be presented).
|
|
// A negative Delta means that the bitmap will be presented in the future:
|
|
int64_t STC = cDevice::PrimaryDevice()->GetSTC();
|
|
int64_t Delta = LimitTo32Bit(STC) - LimitTo32Bit(sb->Pts()); // some devices only deliver 32 bits
|
|
if (Delta > (int64_t(1) << 31))
|
|
Delta -= (int64_t(1) << 32);
|
|
else if (Delta < -((int64_t(1) << 31) - 1))
|
|
Delta += (int64_t(1) << 32);
|
|
Delta /= 90; // STC and PTS are in 1/90000s
|
|
if (Delta >= 0) { // found a bitmap that shall be displayed...
|
|
if (Delta < sb->Timeout() * 1000) { // ...and has not timed out yet
|
|
if (!sb->HasBitmaps()) {
|
|
Timeout.Set();
|
|
WaitMs = 0;
|
|
}
|
|
else if (AssertOsd()) {
|
|
dbgoutput("showing bitmap #%d of %d<br>\n", sb->Index() + 1, bitmaps->Count());
|
|
sb->Draw(osd);
|
|
Timeout.Set(sb->Timeout() * 1000);
|
|
dbgconverter("PTS: %"PRId64" STC: %"PRId64" (%"PRId64") timeout: %d<br>\n", sb->Pts(), STC, Delta, sb->Timeout());
|
|
}
|
|
}
|
|
else
|
|
WaitMs = 0; // bitmap already timed out, so try next one immediately
|
|
dbgoutput("deleting bitmap #%d of %d<br>\n", sb->Index() + 1, bitmaps->Count());
|
|
bitmaps->Del(sb);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
cCondWait::SleepMs(WaitMs);
|
|
}
|
|
}
|
|
|
|
void cDvbSubtitleConverter::SetOsdData(void)
|
|
{
|
|
int OsdWidth, OsdHeight;
|
|
double OsdAspect;
|
|
int VideoWidth, VideoHeight;
|
|
double VideoAspect;
|
|
cDevice::PrimaryDevice()->GetOsdSize(OsdWidth, OsdHeight, OsdAspect);
|
|
cDevice::PrimaryDevice()->GetVideoSize(VideoWidth, VideoHeight, VideoAspect);
|
|
if (OsdWidth == displayWidth && OsdHeight == displayHeight) {
|
|
osdFactorX = osdFactorY = 1.0;
|
|
osdDeltaX = osdDeltaY = 0;
|
|
}
|
|
else {
|
|
osdFactorX = VideoAspect * OsdHeight / displayWidth;
|
|
osdFactorY = double(OsdHeight) / displayHeight;
|
|
osdDeltaX = (OsdWidth - displayWidth * osdFactorX) / 2;
|
|
osdDeltaY = (OsdHeight - displayHeight * osdFactorY) / 2;
|
|
}
|
|
}
|
|
|
|
bool cDvbSubtitleConverter::AssertOsd(void)
|
|
{
|
|
LOCK_THREAD;
|
|
if (!osd) {
|
|
SetOsdData();
|
|
osd = cOsdProvider::NewOsd(int(round(osdFactorX * windowHorizontalOffset + osdDeltaX)), int(round(osdFactorY * windowVerticalOffset + osdDeltaY)) + Setup.SubtitleOffset, OSD_LEVEL_SUBTITLES);
|
|
}
|
|
return osd != NULL;
|
|
}
|
|
|
|
cDvbSubtitlePage *cDvbSubtitleConverter::GetPageById(int PageId, bool New)
|
|
{
|
|
for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) {
|
|
if (sp->PageId() == PageId)
|
|
return sp;
|
|
}
|
|
if (!New)
|
|
return NULL;
|
|
cDvbSubtitlePage *Page = new cDvbSubtitlePage(PageId);
|
|
pages->Add(Page);
|
|
return Page;
|
|
}
|
|
|
|
int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t Pts)
|
|
{
|
|
cBitStream bs(Data, Length * 8);
|
|
if (Length > 5 && bs.GetBits(8) == 0x0F) { // sync byte
|
|
int segmentType = bs.GetBits(8);
|
|
if (segmentType == STUFFING_SEGMENT)
|
|
return -1;
|
|
LOCK_THREAD;
|
|
cDvbSubtitlePage *page = GetPageById(bs.GetBits(16), true);
|
|
int segmentLength = bs.GetBits(16);
|
|
if (!bs.SetLength(bs.Index() + segmentLength * 8))
|
|
return -1;
|
|
switch (segmentType) {
|
|
case PAGE_COMPOSITION_SEGMENT: {
|
|
if (page->Pending()) {
|
|
dbgsegments("END_OF_DISPLAY_SET_SEGMENT (simulated)<br>\n");
|
|
FinishPage(page);
|
|
}
|
|
dbgsegments("PAGE_COMPOSITION_SEGMENT<br>\n");
|
|
page->Parse(Pts, bs);
|
|
SD.SetFactor(double(DBGBITMAPWIDTH) / windowWidth);
|
|
break;
|
|
}
|
|
case REGION_COMPOSITION_SEGMENT: {
|
|
dbgsegments("REGION_COMPOSITION_SEGMENT<br>\n");
|
|
cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8), true);
|
|
region->Parse(bs);
|
|
break;
|
|
}
|
|
case CLUT_DEFINITION_SEGMENT: {
|
|
dbgsegments("CLUT_DEFINITION_SEGMENT<br>\n");
|
|
cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true);
|
|
clut->Parse(bs);
|
|
break;
|
|
}
|
|
case OBJECT_DATA_SEGMENT: {
|
|
dbgsegments("OBJECT_DATA_SEGMENT<br>\n");
|
|
cSubtitleObject *object = page->GetObjectById(bs.GetBits(16), true);
|
|
object->Parse(bs);
|
|
break;
|
|
}
|
|
case DISPLAY_DEFINITION_SEGMENT: {
|
|
dbgsegments("DISPLAY_DEFINITION_SEGMENT<br>\n");
|
|
int version = bs.GetBits(4);
|
|
if (version != ddsVersionNumber) {
|
|
bool displayWindowFlag = bs.GetBit();
|
|
windowHorizontalOffset = 0;
|
|
windowVerticalOffset = 0;
|
|
bs.SkipBits(3); // reserved
|
|
displayWidth = windowWidth = bs.GetBits(16) + 1;
|
|
displayHeight = windowHeight = bs.GetBits(16) + 1;
|
|
if (displayWindowFlag) {
|
|
windowHorizontalOffset = bs.GetBits(16); // displayWindowHorizontalPositionMinimum
|
|
windowWidth = bs.GetBits(16) - windowHorizontalOffset + 1; // displayWindowHorizontalPositionMaximum
|
|
windowVerticalOffset = bs.GetBits(16); // displayWindowVerticalPositionMinimum
|
|
windowHeight = bs.GetBits(16) - windowVerticalOffset + 1; // displayWindowVerticalPositionMaximum
|
|
}
|
|
SetOsdData();
|
|
ddsVersionNumber = version;
|
|
dbgdisplay("<b>display</b> version %d flag %d width %d height %d ofshor %d ofsver %d<br>\n", ddsVersionNumber, displayWindowFlag, windowWidth, windowHeight, windowHorizontalOffset, windowVerticalOffset);
|
|
}
|
|
break;
|
|
}
|
|
case DISPARITY_SIGNALING_SEGMENT: {
|
|
dbgsegments("DISPARITY_SIGNALING_SEGMENT<br>\n");
|
|
bs.SkipBits(4); // dss_version_number
|
|
bool disparity_shift_update_sequence_page_flag = bs.GetBit();
|
|
bs.SkipBits(3); // reserved
|
|
bs.SkipBits(8); // page_default_disparity_shift
|
|
if (disparity_shift_update_sequence_page_flag) {
|
|
bs.SkipBits(8); // disparity_shift_update_sequence_length
|
|
bs.SkipBits(24); // interval_duration[23..0]
|
|
int division_period_count = bs.GetBits(8);
|
|
for (int i = 0; i < division_period_count; ++i) {
|
|
bs.SkipBits(8); // interval_count
|
|
bs.SkipBits(8); // disparity_shift_update_integer_part
|
|
}
|
|
}
|
|
while (!bs.IsEOF()) {
|
|
bs.SkipBits(8); // region_id
|
|
bool disparity_shift_update_sequence_region_flag = bs.GetBit();
|
|
bs.SkipBits(5); // reserved
|
|
int number_of_subregions_minus_1 = bs.GetBits(2);
|
|
for (int i = 0; i <= number_of_subregions_minus_1; ++i) {
|
|
if (number_of_subregions_minus_1 > 0) {
|
|
bs.SkipBits(16); // subregion_horizontal_position
|
|
bs.SkipBits(16); // subregion_width
|
|
}
|
|
bs.SkipBits(8); // subregion_disparity_shift_integer_part
|
|
bs.SkipBits(4); // subregion_disparity_shift_fractional_part
|
|
bs.SkipBits(4); // reserved
|
|
if (disparity_shift_update_sequence_region_flag) {
|
|
bs.SkipBits(8); // disparity_shift_update_sequence_length
|
|
bs.SkipBits(24); // interval_duration[23..0]
|
|
int division_period_count = bs.GetBits(8);
|
|
for (int i = 0; i < division_period_count; ++i) {
|
|
bs.SkipBits(8); // interval_count
|
|
bs.SkipBits(8); // disparity_shift_update_integer_part
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case END_OF_DISPLAY_SET_SEGMENT: {
|
|
dbgsegments("END_OF_DISPLAY_SET_SEGMENT<br>\n");
|
|
FinishPage(page);
|
|
page->SetPending(false);
|
|
break;
|
|
}
|
|
default:
|
|
dbgsegments("*** unknown segment type: %02X<br>\n", segmentType);
|
|
}
|
|
return bs.Length() / 8;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page)
|
|
{
|
|
if (!AssertOsd())
|
|
return;
|
|
int NumAreas;
|
|
tArea *Areas = Page->GetAreas(NumAreas, osdFactorX, osdFactorY);
|
|
int Bpp = 8;
|
|
bool Reduced = false;
|
|
while (osd && osd->CanHandleAreas(Areas, NumAreas) != oeOk) {
|
|
dbgoutput("CanHandleAreas: %d<br>\n", osd->CanHandleAreas(Areas, NumAreas));
|
|
int HalfBpp = Bpp / 2;
|
|
if (HalfBpp >= 2) {
|
|
for (int i = 0; i < NumAreas; i++) {
|
|
if (Areas[i].bpp >= Bpp) {
|
|
Areas[i].bpp = HalfBpp;
|
|
Reduced = true;
|
|
}
|
|
}
|
|
Bpp = HalfBpp;
|
|
}
|
|
else
|
|
return; // unable to draw bitmaps
|
|
}
|
|
cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->PageState(), Page->Pts(), Page->PageTimeout(), Areas, NumAreas, osdFactorX, osdFactorY);
|
|
bitmaps->Add(Bitmaps);
|
|
for (int i = 0; i < NumAreas; i++) {
|
|
if (cSubtitleRegionRef *srr = Page->GetRegionRefByIndex(i)) {
|
|
if (cSubtitleRegion *sr = Page->GetRegionById(srr->RegionId())) {
|
|
if (cSubtitleClut *clut = Page->GetClutById(sr->ClutId())) {
|
|
cBitmap *bm = new cBitmap(sr->RegionWidth(), sr->RegionHeight(), sr->RegionDepth());
|
|
bm->Replace(*clut->GetPalette(sr->RegionDepth()));
|
|
sr->Render(bm, Page->Objects());
|
|
if (Reduced) {
|
|
if (sr->RegionDepth() != Areas[i].bpp) {
|
|
if (sr->RegionLevelOfCompatibility() <= Areas[i].bpp) {
|
|
//TODO this is untested - didn't have any such subtitle stream
|
|
cSubtitleClut *Clut = Page->GetClutById(sr->ClutId());
|
|
dbgregions("reduce region %d bpp %d level %d area bpp %d<br>\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp);
|
|
bm->ReduceBpp(*Clut->GetPalette(sr->RegionDepth()));
|
|
}
|
|
else {
|
|
dbgregions("condense region %d bpp %d level %d area bpp %d<br>\n", sr->RegionId(), sr->RegionDepth(), sr->RegionLevelOfCompatibility(), Areas[i].bpp);
|
|
bm->ShrinkBpp(Areas[i].bpp);
|
|
}
|
|
}
|
|
}
|
|
bm->SetOffset(srr->RegionHorizontalAddress(), srr->RegionVerticalAddress());
|
|
Bitmaps->AddBitmap(bm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (DebugPages)
|
|
Bitmaps->DbgDump(windowWidth, windowHeight);
|
|
}
|