summaryrefslogtreecommitdiff
path: root/dvbsubtitle.c
diff options
context:
space:
mode:
authorKlaus Schmidinger <vdr@tvdr.de>2015-01-14 10:39:55 +0100
committerKlaus Schmidinger <vdr@tvdr.de>2015-01-14 10:39:55 +0100
commit7062583ab40bec2ee63e84adbbd8e3e1740729bf (patch)
treee7886025fc6714c5db0d88580401a439a2702639 /dvbsubtitle.c
parentb454a0777f54d3cd256b042b446088a77415a434 (diff)
downloadvdr-7062583ab40bec2ee63e84adbbd8e3e1740729bf.tar.gz
vdr-7062583ab40bec2ee63e84adbbd8e3e1740729bf.tar.bz2
Added support for PGS subtitles
Diffstat (limited to 'dvbsubtitle.c')
-rw-r--r--dvbsubtitle.c280
1 files changed, 269 insertions, 11 deletions
diff --git a/dvbsubtitle.c b/dvbsubtitle.c
index d1fefdcb..4bf613b9 100644
--- a/dvbsubtitle.c
+++ b/dvbsubtitle.c
@@ -7,7 +7,7 @@
* 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.7 2015/01/09 11:56:25 kls Exp $
+ * $Id: dvbsubtitle.c 3.8 2015/01/14 10:30:50 kls Exp $
*/
#include "dvbsubtitle.h"
@@ -25,6 +25,12 @@
#define END_OF_DISPLAY_SET_SEGMENT 0x80
#define STUFFING_SEGMENT 0xFF
+#define PGS_PALETTE_SEGMENT 0x14
+#define PGS_OBJECT_SEGMENT 0x15
+#define PGS_PRESENTATION_SEGMENT 0x16
+#define PGS_WINDOW_SEGMENT 0x17
+#define PGS_DISPLAY_SEGMENT 0x80
+
// 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.
@@ -146,6 +152,7 @@ private:
public:
cSubtitleClut(int ClutId);
void Parse(cBitStream &bs);
+ void ParsePgs(cBitStream &bs);
int ClutId(void) { return clutId; }
int ClutVersionNumber(void) { return clutVersionNumber; }
const cPalette *GetPalette(int Bpp);
@@ -267,6 +274,31 @@ void cSubtitleClut::Parse(cBitStream &bs)
}
}
+void cSubtitleClut::ParsePgs(cBitStream &bs)
+{
+ int Version = bs.GetBits(8);
+ if (clutVersionNumber == Version)
+ return; // no update
+ clutVersionNumber = Version;
+ dbgcluts("<b>clut</b> id %d version %d<br>\n", clutId, clutVersionNumber);
+ for (int i = 0; i < 256; ++i)
+ SetColor(8, i, ArgbToColor(0, 0, 0, 0));
+ while (!bs.IsEOF()) {
+ uchar clutEntryId = bs.GetBits(8);
+ uchar yval = bs.GetBits(8);
+ uchar crval = bs.GetBits(8);
+ uchar cbval = bs.GetBits(8);
+ uchar tval = bs.GetBits(8);
+ tColor value = 0;
+ if (yval) {
+ value = yuv2rgb(yval, cbval, crval);
+ value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * tval / 10) << 24;
+ }
+ dbgcluts("%2d %08X<br>\n", clutEntryId, value);
+ SetColor(8, clutEntryId, value);
+ }
+}
+
tColor cSubtitleClut::yuv2rgb(int Y, int Cb, int Cr)
{
int Ey, Epb, Epr;
@@ -314,6 +346,7 @@ private:
bool nonModifyingColorFlag;
int topLength;
int botLength;
+ int topIndex;
uchar *topData;
uchar *botData;
char *txtData;
@@ -322,12 +355,14 @@ private:
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);
+ bool DecodePgsCodeString(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);
+ void ParsePgs(cBitStream &bs);
int ObjectId(void) { return objectId; }
int ObjectVersionNumber(void) { return objectVersionNumber; }
int ObjectCodingMethod(void) { return objectCodingMethod; }
@@ -343,6 +378,7 @@ cSubtitleObject::cSubtitleObject(int ObjectId)
nonModifyingColorFlag = false;
topLength = 0;
botLength = 0;
+ topIndex = 0;
topData = NULL;
botData = NULL;
txtData = NULL;
@@ -404,6 +440,29 @@ void cSubtitleObject::Parse(cBitStream &bs)
}
}
+void cSubtitleObject::ParsePgs(cBitStream &bs)
+{
+ int Version = bs.GetBits(8);
+ if (objectVersionNumber == Version)
+ return; // no update
+ objectVersionNumber = Version;
+ objectCodingMethod = 0;
+ int sequenceDescriptor = bs.GetBits(8);
+ if (!(sequenceDescriptor & 0x80) && topData != NULL) {
+ memcpy(topData + topIndex, bs.GetData(), (bs.Length() - bs.Index()) / 8);
+ topIndex += (bs.Length() - bs.Index()) / 8;
+ return;
+ }
+ topLength = bs.GetBits(24) - 4 + 1; // exclude width / height, add sub block type
+ bs.SkipBits(32);
+ if ((topData = MALLOC(uchar, topLength)) != NULL) {
+ topData[topIndex++] = 0xFF; // PGS end of line
+ memcpy(topData + 1, bs.GetData(), (bs.Length() - bs.Index()) / 8);
+ topIndex += (bs.Length() - bs.Index()) / 8 + 1;
+ }
+ dbgobjects("<b>object</b> id %d version %d method %d modify %d", objectId, objectVersionNumber, objectCodingMethod, nonModifyingColorFlag);
+}
+
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
@@ -490,6 +549,13 @@ void cSubtitleObject::DecodeSubBlock(cBitmap *Bitmap, int px, int py, const ucha
x = 0;
y += 2;
break;
+ case 0xFF:
+ dbgpixel("PGS code string, including EOLs<br>\n");
+ while (DecodePgsCodeString(Bitmap, px, py, &bs, x, y) && !bs.IsEOF()) {
+ x = 0;
+ y++;
+ }
+ break;
default: dbgpixel("unknown sub block %s %d<br>\n", __FUNCTION__, __LINE__);
}
}
@@ -613,6 +679,28 @@ bool cSubtitleObject::Decode8BppCodeString(cBitmap *Bitmap, int px, int py, cBit
return true;
}
+bool cSubtitleObject::DecodePgsCodeString(cBitmap *Bitmap, int px, int py, cBitStream *bs, int &x, int y)
+{
+ while (!bs->IsEOF()) {
+ int color = bs->GetBits(8);
+ int rl = 1;
+ if (!color) {
+ int flags = bs->GetBits(8);
+ rl = flags & 0x3f;
+ if (flags & 0x40)
+ rl = (rl << 8) + bs->GetBits(8);
+ color = flags & 0x80 ? bs->GetBits(8) : 0;
+ }
+ if (rl > 0) {
+ DrawLine(Bitmap, px + x, py + y, color, rl);
+ x += rl;
+ }
+ else if (!rl)
+ return true;
+ }
+ return false;
+}
+
void cSubtitleObject::Render(cBitmap *Bitmap, int px, int py, tIndex IndexFg, tIndex IndexBg)
{
if (objectCodingMethod == 0) { // coding of pixels
@@ -660,7 +748,7 @@ cSubtitleObject *cSubtitleObjects::GetObjectById(int ObjectId, bool New)
// --- cSubtitleObjectRef ----------------------------------------------------
class cSubtitleObjectRef : public cListObject {
-private:
+protected:
int objectId;
int objectType;
int objectProviderFlag;
@@ -669,6 +757,7 @@ private:
int foregroundPixelCode;
int backgroundPixelCode;
public:
+ cSubtitleObjectRef(void);
cSubtitleObjectRef(cBitStream &bs);
int ObjectId(void) { return objectId; }
int ObjectType(void) { return objectType; }
@@ -679,6 +768,17 @@ public:
int BackgroundPixelCode(void) { return backgroundPixelCode; }
};
+cSubtitleObjectRef::cSubtitleObjectRef(void)
+{
+ objectId = 0;
+ objectType = 0;
+ objectProviderFlag = 0;
+ objectHorizontalPosition = 0;
+ objectVerticalPosition = 0;
+ foregroundPixelCode = 0;
+ backgroundPixelCode = 0;
+}
+
cSubtitleObjectRef::cSubtitleObjectRef(cBitStream &bs)
{
objectId = bs.GetBits(16);
@@ -698,6 +798,38 @@ cSubtitleObjectRef::cSubtitleObjectRef(cBitStream &bs)
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);
}
+// --- cSubtitleObjectRefPgs - PGS variant of cSubtitleObjectRef -------------
+
+class cSubtitleObjectRefPgs : public cSubtitleObjectRef {
+private:
+ int windowId;
+ int compositionFlag;
+ int cropX;
+ int cropY;
+ int cropW;
+ int cropH;
+public:
+ cSubtitleObjectRefPgs(cBitStream &bs);
+};
+
+cSubtitleObjectRefPgs::cSubtitleObjectRefPgs(cBitStream &bs)
+:cSubtitleObjectRef()
+{
+ objectId = bs.GetBits(16);
+ windowId = bs.GetBits(8);
+ compositionFlag = bs.GetBits(8);
+ bs.SkipBits(32); // skip absolute position, object is aligned to region
+ if ((compositionFlag & 0x80) != 0) {
+ cropX = bs.GetBits(16);
+ cropY = bs.GetBits(16);
+ cropW = bs.GetBits(16);
+ cropH = bs.GetBits(16);
+ }
+ else
+ cropX = cropY = cropW = cropH = 0;
+ dbgregions("<b>objectrefPgs</b> id %d flag %d x %d y %d cropX %d cropY %d cropW %d cropH %d<br>\n", objectId, compositionFlag, objectHorizontalPosition, objectVerticalPosition, cropX, cropY, cropW, cropH);
+}
+
// --- cSubtitleRegion -------------------------------------------------------
class cSubtitleRegion : public cListObject {
@@ -717,6 +849,8 @@ private:
public:
cSubtitleRegion(int RegionId);
void Parse(cBitStream &bs);
+ void ParsePgs(cBitStream &bs);
+ void SetDimensions(int Width, int Height);
int RegionId(void) { return regionId; }
int RegionVersionNumber(void) { return regionVersionNumber; }
bool RegionFillFlag(void) { return regionFillFlag; }
@@ -769,6 +903,24 @@ void cSubtitleRegion::Parse(cBitStream &bs)
objectRefs.Add(new cSubtitleObjectRef(bs));
}
+void cSubtitleRegion::ParsePgs(cBitStream &bs)
+{
+ regionDepth = 8;
+ bs.SkipBits(8); // skip palette update flag
+ clutId = bs.GetBits(8);
+ dbgregions("<b>region</b> id %d version %d clutId %d<br>\n", regionId, regionVersionNumber, clutId);
+ int objects = bs.GetBits(8);
+ while (objects--)
+ objectRefs.Add(new cSubtitleObjectRefPgs(bs));
+}
+
+void cSubtitleRegion::SetDimensions(int Width, int Height)
+{
+ regionWidth = Width;
+ regionHeight = Height;
+ dbgregions("<b>region</b> id %d width %d height %d<br>\n", regionId, regionWidth, regionHeight);
+}
+
void cSubtitleRegion::Render(cBitmap *Bitmap, cSubtitleObjects *Objects)
{
if (regionFillFlag) {
@@ -794,12 +946,20 @@ private:
int regionHorizontalAddress;
int regionVerticalAddress;
public:
+ cSubtitleRegionRef(int id, int x, int y);
cSubtitleRegionRef(cBitStream &bs);
int RegionId(void) { return regionId; }
int RegionHorizontalAddress(void) { return regionHorizontalAddress; }
int RegionVerticalAddress(void) { return regionVerticalAddress; }
};
+cSubtitleRegionRef::cSubtitleRegionRef(int id, int x, int y)
+{
+ regionId = id;
+ regionHorizontalAddress = x;
+ regionVerticalAddress = y;
+ dbgpages("<b>regionref</b> id %d tx %d y %d<br>\n", regionId, regionHorizontalAddress, regionVerticalAddress);
+}
cSubtitleRegionRef::cSubtitleRegionRef(cBitStream &bs)
{
regionId = bs.GetBits(8);
@@ -826,6 +986,7 @@ private:
public:
cDvbSubtitlePage(int PageId);
void Parse(int64_t Pts, cBitStream &bs);
+ void ParsePgs(int64_t Pts, cBitStream &bs);
int PageId(void) { return pageId; }
int PageTimeout(void) { return pageTimeout; }
int PageVersionNumber(void) { return pageVersionNumber; }
@@ -838,6 +999,7 @@ public:
cSubtitleClut *GetClutById(int ClutId, bool New = false);
cSubtitleRegion *GetRegionById(int RegionId, bool New = false);
cSubtitleRegionRef *GetRegionRefByIndex(int RegionRefIndex) { return regionRefs.Get(RegionRefIndex); }
+ void AddRegionRef(cSubtitleRegionRef *rf) { regionRefs.Add(rf); }
void SetPending(bool Pending) { pending = Pending; }
};
@@ -887,6 +1049,35 @@ void cDvbSubtitlePage::Parse(int64_t Pts, cBitStream &bs)
pending = true;
}
+void cDvbSubtitlePage::ParsePgs(int64_t Pts, cBitStream &bs)
+{
+ if (Pts >= 0)
+ pts = Pts;
+ pageTimeout = 60000;
+ int Version = bs.GetBits(16);
+ if (pageVersionNumber == Version)
+ return;
+ pageVersionNumber = Version;
+ pageState = bs.GetBits(2);
+ switch (pageState) {
+ case 0: // normal case - page update
+ regions.Clear();
+ break;
+ case 1: // acquisition point - page refresh
+ case 2: // epoch start - new page
+ case 3: // epoch continue - new page
+ regions.Clear();
+ cluts.Clear();
+ objects.Clear();
+ break;
+ default: dbgpages("unknown page state: %d<br>\n", pageState);
+ }
+ bs.SkipBits(6);
+ 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();
+ pending = true;
+}
+
tArea *cDvbSubtitlePage::GetAreas(int &NumAreas, double FactorX, double FactorY)
{
if (regions.Count() > 0) {
@@ -1232,22 +1423,24 @@ int cDvbSubtitleConverter::Convert(const uchar *Data, int Length)
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) {
+ if (length > 0) {
+ if (length > 2 && 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;
- }
+ if (b[0] == STUFFING_SEGMENT)
+ break;
+ int n;
+ if (b[0] == 0x0F)
+ n = ExtractSegment(b, length, pts);
else
+ n = ExtractPgsSegment(b, length, pts);
+ if (n < 0)
break;
+ b += n;
+ length -= n;
}
}
}
@@ -1473,6 +1666,71 @@ int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t
return -1;
}
+int cDvbSubtitleConverter::ExtractPgsSegment(const uchar *Data, int Length, int64_t Pts)
+{
+ cBitStream bs(Data, Length * 8);
+ if (Length >= 3) {
+ int segmentType = bs.GetBits(8);
+ int segmentLength = bs.GetBits(16);
+ if (!bs.SetLength(bs.Index() + segmentLength * 8))
+ return -1;
+ LOCK_THREAD;
+ cDvbSubtitlePage *page = GetPageById(0, true);
+ switch (segmentType) {
+ case PGS_PRESENTATION_SEGMENT: {
+ if (page->Pending()) {
+ dbgsegments("PGS_DISPLAY_SEGMENT (simulated)<br>\n");
+ FinishPage(page);
+ }
+ dbgsegments("PGS_PRESENTATION_SEGMENT<br>\n");
+ displayWidth = windowWidth = bs.GetBits(16);
+ displayHeight = windowHeight = bs.GetBits(16);
+ bs.SkipBits(8);
+ page->ParsePgs(Pts, bs);
+ SD.SetFactor(double(DBGBITMAPWIDTH) / windowWidth);
+ cSubtitleRegion *region = page->GetRegionById(0, true);
+ region->ParsePgs(bs);
+ break;
+ }
+ case PGS_WINDOW_SEGMENT: {
+ bs.SkipBits(16);
+ int regionHorizontalAddress = bs.GetBits(16);
+ int regionVerticalAddress = bs.GetBits(16);
+ int regionWidth = bs.GetBits(16);
+ int regionHeight = bs.GetBits(16);
+ cSubtitleRegion *region = page->GetRegionById(0, true);
+ region->SetDimensions(regionWidth, regionHeight);
+ page->AddRegionRef(new cSubtitleRegionRef(0, regionHorizontalAddress, regionVerticalAddress));
+ dbgsegments("PGS_WINDOW_SEGMENT<br>\n");
+ break;
+ }
+ case PGS_PALETTE_SEGMENT: {
+ dbgsegments("PGS_PALETTE_SEGMENT<br>\n");
+ cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true);
+ clut->ParsePgs(bs);
+ break;
+ }
+ case PGS_OBJECT_SEGMENT: {
+ dbgsegments("PGS_OBJECT_SEGMENT<br>\n");
+ cSubtitleObject *object = page->GetObjectById(bs.GetBits(16), true);
+ object->ParsePgs(bs);
+ break;
+ }
+ case PGS_DISPLAY_SEGMENT: {
+ dbgsegments("PGS_DISPLAY_SEGMENT<br>\n");
+ FinishPage(page);
+ page->SetPending(false);
+ break;
+ }
+ default:
+ dbgsegments("*** unknown segment type: %02X<br>\n", segmentType);
+ return -1;
+ }
+ return bs.Length() / 8;
+ }
+ return -1;
+}
+
void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page)
{
if (!AssertOsd())