Added support for PGS subtitles

This commit is contained in:
Klaus Schmidinger 2015-01-14 10:39:55 +01:00
parent b454a0777f
commit 7062583ab4
5 changed files with 305 additions and 14 deletions

View File

@ -3270,6 +3270,7 @@ Thomas Reufer <thomas@reufer.ch>
for fixing a possible crash in the LCARS skin
for implementing cOsd::DrawScaledBitmap()
for adding handling for DTS audio tracks to cPatPmtParser::ParsePmt()
for adding support for PGS subtitles
Eike Sauer <EikeSauer@t-online.de>
for reporting a problem with channels that need more than 5 TS packets for detecting

View File

@ -8360,3 +8360,4 @@ Video Disk Recorder Revision History
- Now handling CAT sections that consist of more than one TS packet.
- Added handling for DTS audio tracks to cPatPmtParser::ParsePmt() (thanks to
Thomas Reufer).
- Added support for PGS subtitles (thanks to Thomas Reufer).

View File

@ -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;
}
else
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())

View File

@ -6,7 +6,7 @@
*
* Original author: Marco Schluessler <marco@lordzodiac.de>
*
* $Id: dvbsubtitle.h 3.1 2013/09/06 10:53:30 kls Exp $
* $Id: dvbsubtitle.h 3.2 2015/01/14 10:01:48 kls Exp $
*/
#ifndef __DVBSUBTITLE_H
@ -43,6 +43,7 @@ private:
void SetOsdData(void);
bool AssertOsd(void);
int ExtractSegment(const uchar *Data, int Length, int64_t Pts);
int ExtractPgsSegment(const uchar *Data, int Length, int64_t Pts);
void FinishPage(cDvbSubtitlePage *Page);
public:
cDvbSubtitleConverter(void);

32
remux.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: remux.c 3.8 2015/01/14 09:28:24 kls Exp $
* $Id: remux.c 3.9 2015/01/14 09:57:09 kls Exp $
*/
#include "remux.h"
@ -853,6 +853,36 @@ void cPatPmtParser::ParsePmt(const uchar *Data, int Length)
}
}
break;
case 0x90: // PGS subtitles for BD
{
dbgpatpmt(" subtitling");
char lang[MAXLANGCODE1] = { 0 };
SI::Descriptor *d;
for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) {
switch (d->getDescriptorTag()) {
case SI::ISO639LanguageDescriptorTag: {
SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d;
dbgpatpmt(" '%s'", ld->languageCode);
strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1);
if (NumSpids < MAXSPIDS) {
spids[NumSpids] = stream.getPid();
*slangs[NumSpids] = 0;
subtitlingTypes[NumSpids] = 0;
compositionPageIds[NumSpids] = 0;
ancillaryPageIds[NumSpids] = 0;
if (updatePrimaryDevice)
cDevice::PrimaryDevice()->SetAvailableTrack(ttSubtitle, NumSpids, stream.getPid(), lang);
NumSpids++;
spids[NumSpids] = 0;
}
}
break;
default: ;
}
delete d;
}
}
break;
default: ;
}
dbgpatpmt("\n");