diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f49c0ab0..e8a7652b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1117,6 +1117,7 @@ Rolf Ahrenberg for making the Audio and Subtitles options available through the Green and Yellow keys in the Setup/DVB menu for making the Recordings menu display the length (in hours:minutes) of each recording + for fixing handling DVB subtitles and implementing decoding textual DVB subtitles Ralf Klueber for reporting a bug in cutting a recording if there is only a single editing mark diff --git a/HISTORY b/HISTORY index 03705783..4e0c3e4d 100644 --- a/HISTORY +++ b/HISTORY @@ -6743,7 +6743,7 @@ Video Disk Recorder Revision History extends over TS packet boundaries is now done by locally skipping TS packets in cFrameDetector. -2011-09-11: Version 1.7.22 +2011-09-18: Version 1.7.22 - Fixed scaling subtitles in case the primary device's GetVideoSize() function doesn't return actual values (thanks to Luca Olivetti). @@ -6758,3 +6758,5 @@ Video Disk Recorder Revision History - The configuration file name has been changed from "unicable.conf" to "scr.conf". - Updated sources.conf (thanks to Arthur Konovalov). - The SVDRP command LSTC now also accepts channel IDs (thanks to Dominic Evans). +- Fixed handling DVB subtitles and implemented decoding textual DVB subtitles (thanks + to Rolf Ahrenberg). diff --git a/dvbsubtitle.c b/dvbsubtitle.c index 7b95fc3a..272950fa 100644 --- a/dvbsubtitle.c +++ b/dvbsubtitle.c @@ -7,7 +7,7 @@ * Original author: Marco Schlüßler * With some input from the "subtitle plugin" by Pekka Virtanen * - * $Id: dvbsubtitle.c 2.19 2011/09/10 09:43:40 kls Exp $ + * $Id: dvbsubtitle.c 2.20 2011/09/18 11:23:15 kls Exp $ */ @@ -15,13 +15,18 @@ #define __STDC_FORMAT_MACROS // Required for format specifiers #include #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 END_OF_DISPLAY_SET_SEGMENT 0x80 +//#define FINISHPAGE_HACK + +#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: static bool DebugConverter = false; @@ -61,8 +66,68 @@ cSubtitleClut::cSubtitleClut(int ClutId) ,palette4(4) ,palette8(8) { + int a = 0, r = 0, g = 0, b = 0; clutId = ClutId; version = -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::SetColor(int Bpp, int Index, tColor Color) @@ -94,29 +159,33 @@ private: int version; int codingMethod; bool nonModifyingColorFlag; - int nibblePos; - uchar backgroundColor; - uchar foregroundColor; + uchar backgroundPixelCode; + uchar foregroundPixelCode; int providerFlag; int px; int py; cBitmap *bitmap; + char textData[Utf8BufSize(256)]; // number of character codes is an 8-bit field void DrawLine(int x, int y, tIndex Index, int Length); - uchar Get2Bits(const uchar *Data, int &Index); - uchar Get4Bits(const uchar *Data, int &Index); - bool Decode2BppCodeString(const uchar *Data, int &Index, int&x, int y); - bool Decode4BppCodeString(const uchar *Data, int &Index, int&x, int y); - bool Decode8BppCodeString(const uchar *Data, int &Index, int&x, int y); + bool Decode2BppCodeString(cBitStream *bs, int&x, int y, const uint8_t *MapTable); + bool Decode4BppCodeString(cBitStream *bs, int&x, int y, const uint8_t *MapTable); + bool Decode8BppCodeString(cBitStream *bs, int&x, int y); public: cSubtitleObject(int ObjectId, cBitmap *Bitmap); int ObjectId(void) { return objectId; } int Version(void) { return version; } int CodingMethod(void) { return codingMethod; } + uchar BackgroundPixelCode(void) { return backgroundPixelCode; } + uchar ForegroundPixelCode(void) { return foregroundPixelCode; } + const char *TextData(void) { return &textData[0]; } + int X(void) { return px; } + int Y(void) { return py; } bool NonModifyingColorFlag(void) { return nonModifyingColorFlag; } + void DecodeCharacterString(const uchar *Data, int NumberOfCodes); void DecodeSubBlock(const uchar *Data, int Length, bool Even); void SetVersion(int Version) { version = Version; } - void SetBackgroundColor(uchar BackgroundColor) { backgroundColor = BackgroundColor; } - void SetForegroundColor(uchar ForegroundColor) { foregroundColor = ForegroundColor; } + void SetBackgroundPixelCode(uchar BackgroundPixelCode) { backgroundPixelCode = BackgroundPixelCode; } + void SetForegroundPixelCode(uchar ForegroundPixelCode) { foregroundPixelCode = ForegroundPixelCode; } void SetNonModifyingColorFlag(bool NonModifyingColorFlag) { nonModifyingColorFlag = NonModifyingColorFlag; } void SetCodingMethod(int CodingMethod) { codingMethod = CodingMethod; } void SetPosition(int x, int y) { px = x; py = y; } @@ -129,59 +198,105 @@ cSubtitleObject::cSubtitleObject(int ObjectId, cBitmap *Bitmap) version = -1; codingMethod = -1; nonModifyingColorFlag = false; - nibblePos = 0; - backgroundColor = 0; - foregroundColor = 0; + backgroundPixelCode = 0; + foregroundPixelCode = 0; providerFlag = -1; px = py = 0; bitmap = Bitmap; + memset(textData, 0, sizeof(textData)); +} + +void cSubtitleObject::DecodeCharacterString(const uchar *Data, int NumberOfCodes) +{ + if (NumberOfCodes > 0) { + bool singleByte; + const uchar *from = &Data[1]; + int len = NumberOfCodes * 2 - 1; + cCharSetConv conv(SI::getCharacterTable(from, len, &singleByte)); + if (singleByte) { + char txt[NumberOfCodes + 1]; + char *p = txt; + for (int i = 2; i < NumberOfCodes; ++i) { + char c = Data[i * 2 + 1] & 0xFF; + if (c == 0) + break; + if (' ' <= c && c <= '~' || c == '\n' || 0xA0 <= c) + *(p++) = c; + else if (c == 0x8A) + *(p++) = '\n'; + } + *p = 0; + const char *s = conv.Convert(txt); + Utf8Strn0Cpy(textData, s, Utf8StrLen(s)); + } + else { + // TODO: add proper multibyte support for "UTF-16", "EUC-KR", "GB2312", "GBK", "UTF-8" + } + } } void cSubtitleObject::DecodeSubBlock(const uchar *Data, int Length, bool Even) { int x = 0; int y = Even ? 0 : 1; - for (int index = 0; index < Length; ) { - switch (Data[index++]) { - case 0x10: { - nibblePos = 8; - while (Decode2BppCodeString(Data, index, x, y) && index < Length) - ; - if (!nibblePos) - index++; - break; - } - case 0x11: { - nibblePos = 4; - while (Decode4BppCodeString(Data, index, x, y) && index < Length) - ; - if (!nibblePos) - index++; - break; - } - case 0x12: - while (Decode8BppCodeString(Data, index, x, y) && index < Length) - ; - break; - case 0x20: //TODO - dbgobjects("sub block 2 to 4 map\n"); - index += 4; - break; - case 0x21: //TODO - dbgobjects("sub block 2 to 8 map\n"); - index += 4; - break; - case 0x22: //TODO - dbgobjects("sub block 4 to 8 map\n"); - index += 16; - break; - case 0xF0: - x = 0; - y += 2; - break; - default: dbgobjects("unknown sub block %s %d\n", __FUNCTION__, __LINE__); + 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: + dbgobjects("2-bit / pixel code string\n"); + switch (bitmap->Bpp()) { + case 8: mapTable = map2to8; break; + case 4: mapTable = map2to4; break; + default: mapTable = NULL; break; + } + while (Decode2BppCodeString(&bs, x, y, mapTable) && !bs.IsEOF()) + ; + bs.ByteAlign(); + break; + case 0x11: + dbgobjects("4-bit / pixel code string\n"); + switch (bitmap->Bpp()) { + case 8: mapTable = map4to8; break; + default: mapTable = NULL; break; + } + while (Decode4BppCodeString(&bs, x, y, mapTable) && !bs.IsEOF()) + ; + bs.ByteAlign(); + break; + case 0x12: + dbgobjects("8-bit / pixel code string\n"); + while (Decode8BppCodeString(&bs, x, y) && !bs.IsEOF()) + ; + break; + case 0x20: + dbgobjects("sub block 2 to 4 map\n"); + map2to4[0] = bs.GetBits(4); + map2to4[1] = bs.GetBits(4); + map2to4[2] = bs.GetBits(4); + map2to4[3] = bs.GetBits(4); + break; + case 0x21: + dbgobjects("sub block 2 to 8 map\n"); + for (int i = 0; i < 4; ++i) + map2to8[i] = bs.GetBits(8); + break; + case 0x22: + dbgobjects("sub block 4 to 8 map\n"); + for (int i = 0; i < 16; ++i) + map4to8[i] = bs.GetBits(8); + break; + case 0xF0: + dbgobjects("end of object line\n"); + x = 0; + y += 2; + break; + default: dbgobjects("unknown sub block %s %d\n", __FUNCTION__, __LINE__); + } } - } } void cSubtitleObject::DrawLine(int x, int y, tIndex Index, int Length) @@ -194,136 +309,110 @@ void cSubtitleObject::DrawLine(int x, int y, tIndex Index, int Length) bitmap->SetIndex(pos, y, Index); } -uchar cSubtitleObject::Get2Bits(const uchar *Data, int &Index) -{ - uchar result = Data[Index]; - if (!nibblePos) { - Index++; - nibblePos = 8; - } - nibblePos -= 2; - return (result >> nibblePos) & 0x03; -} - -uchar cSubtitleObject::Get4Bits(const uchar *Data, int &Index) -{ - uchar result = Data[Index]; - if (!nibblePos) { - Index++; - nibblePos = 4; - } - else { - result >>= 4; - nibblePos -= 4; - } - return result & 0x0F; -} - -bool cSubtitleObject::Decode2BppCodeString(const uchar *Data, int &Index, int &x, int y) +bool cSubtitleObject::Decode2BppCodeString(cBitStream *bs, int &x, int y, const uint8_t *MapTable) { int rl = 0; int color = 0; - uchar code = Get2Bits(Data, Index); + uchar code = bs->GetBits(2); if (code) { color = code; rl = 1; } - else { - code = Get2Bits(Data, Index); - if (code & 2) { // switch_1 - rl = ((code & 1) << 2) + Get2Bits(Data, Index) + 3; - color = Get2Bits(Data, Index); - } - else if (code & 1) - rl = 1; //color 0 - else { - code = Get2Bits(Data, Index); - switch (code & 3) { //switch_3 - case 0: - return false; - case 1: - rl = 2; //color 0 - break; - case 2: - rl = (Get2Bits(Data, Index) << 2) + Get2Bits(Data, Index) + 12; - color = Get2Bits(Data, Index); - break; - case 3: - rl = (Get2Bits(Data, Index) << 6) + (Get2Bits(Data, Index) << 4) + (Get2Bits(Data, Index) << 2) + Get2Bits(Data, Index) + 29; - color = Get2Bits(Data, Index); - break; - default: ; - } - } + 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(x, y, color, rl); x += rl; return true; } -bool cSubtitleObject::Decode4BppCodeString(const uchar *Data, int &Index, int &x, int y) +bool cSubtitleObject::Decode4BppCodeString(cBitStream *bs, int &x, int y, const uint8_t *MapTable) { int rl = 0; int color = 0; - uchar code = Get4Bits(Data, Index); + uchar code = bs->GetBits(4); if (code) { color = 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: // color 0 - rl = 1; - break; - case 1: // color 0 - rl = 2; - break; - case 2: - rl = Get4Bits(Data, Index) + 9; - color = Get4Bits(Data, Index); - break; - case 3: - rl = (Get4Bits(Data, Index) << 4) + Get4Bits(Data, Index) + 25; - color = Get4Bits(Data, Index); - break; - default: ; - } - } - else { - rl = (code & 3) + 4; - color = Get4Bits(Data, Index); - } - } - else { // color 0 - if (!code) - return false; - rl = code + 2; - } + 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(x, y, color, rl); x += rl; return true; } -bool cSubtitleObject::Decode8BppCodeString(const uchar *Data, int &Index, int &x, int y) +bool cSubtitleObject::Decode8BppCodeString(cBitStream *bs, int &x, int y) { int rl = 0; int color = 0; - uchar code = Data[Index++]; + 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 = Data[Index++]; - rl = code & 0x63; - if (code & 0x80) - color = Data[Index++]; - else if (!rl) - return false; //else color 0 + code = bs->GetBits(7); + if (code) + rl = code; // color 0 + else + return false; } DrawLine(x, y, color, rl); x += rl; @@ -340,6 +429,7 @@ private: int horizontalAddress; int verticalAddress; int level; + int lineHeight; cList objects; public: cSubtitleRegion(int RegionId); @@ -358,6 +448,7 @@ public: void SetDepth(int Depth); void SetHorizontalAddress(int HorizontalAddress) { horizontalAddress = HorizontalAddress; } void SetVerticalAddress(int VerticalAddress) { verticalAddress = VerticalAddress; } + void UpdateTextData(cSubtitleClut *Clut); }; cSubtitleRegion::cSubtitleRegion(int RegionId) @@ -369,6 +460,7 @@ cSubtitleRegion::cSubtitleRegion(int RegionId) horizontalAddress = 0; verticalAddress = 0; level = 0; + lineHeight = 26; // configurable subtitling font size } void cSubtitleRegion::FillRegion(tIndex Index) @@ -394,6 +486,22 @@ cSubtitleObject *cSubtitleRegion::GetObjectById(int ObjectId, bool New) return result; } +void cSubtitleRegion::UpdateTextData(cSubtitleClut *Clut) +{ + const cPalette *palette = Clut ? Clut->GetPalette(Depth()) : NULL; + for (cSubtitleObject *so = objects.First(); so && palette; so = objects.Next(so)) { + if (Utf8StrLen(so->TextData()) > 0) { + const cFont *font = cFont::GetFont(fontOsd); + cBitmap *tmp = new cBitmap(font->Width(so->TextData()), font->Height(), Depth()); + double factor = (double)lineHeight / font->Height(); + tmp->DrawText(0, 0, so->TextData(), palette->Color(so->ForegroundPixelCode()), palette->Color(so->BackgroundPixelCode()), font); + tmp = tmp->Scaled(factor, factor, true); + DrawBitmap(so->X(), so->Y(), *tmp); + DELETENULL(tmp); + } + } +} + void cSubtitleRegion::SetLevel(int Level) { if (Level > 0 && Level < 4) @@ -907,12 +1015,15 @@ bool cDvbSubtitleConverter::AssertOsd(void) int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t Pts) { - if (Length > 5 && Data[0] == 0x0F) { - int segmentLength = (Data[4] << 8) + Data[5] + 6; - if (segmentLength > Length) + 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; + int pageId = bs.GetBits(16); + int segmentLength = bs.GetBits(16); + if (!bs.SetLength(bs.Index() + segmentLength * 8)) return -1; - int segmentType = Data[1]; - int pageId = (Data[2] << 8) + Data[3]; cDvbSubtitlePage *page = NULL; LOCK_THREAD; for (cDvbSubtitlePage *sp = pages->First(); sp; sp = pages->Next(sp)) { @@ -931,154 +1042,170 @@ int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t switch (segmentType) { case PAGE_COMPOSITION_SEGMENT: { dbgsegments("PAGE_COMPOSITION_SEGMENT\n"); - int pageVersion = (Data[6 + 1] & 0xF0) >> 4; + int pageTimeout = bs.GetBits(8); + int pageVersion = bs.GetBits(4); if (pageVersion == page->Version()) break; // no update page->SetVersion(pageVersion); - page->SetTimeout(Data[6]); - page->SetState((Data[6 + 1] & 0x0C) >> 2); + page->SetTimeout(pageTimeout); + page->SetState(bs.GetBits(2)); page->regions.Clear(); + bs.SkipBits(2); // reserved dbgpages("Update page id %d version %d pts %"PRId64" timeout %d state %d\n", pageId, page->Version(), page->Pts(), page->Timeout(), page->State()); - for (int i = 6 + 2; i < segmentLength; i += 6) { - cSubtitleRegion *region = page->GetRegionById(Data[i], true); - region->SetHorizontalAddress((Data[i + 2] << 8) + Data[i + 3]); - region->SetVerticalAddress((Data[i + 4] << 8) + Data[i + 5]); - } + while (!bs.IsEOF()) { + cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8), true); + bs.SkipBits(8); // reserved + region->SetHorizontalAddress(bs.GetBits(16)); + region->SetVerticalAddress(bs.GetBits(16)); + } break; } case REGION_COMPOSITION_SEGMENT: { dbgsegments("REGION_COMPOSITION_SEGMENT\n"); - cSubtitleRegion *region = page->GetRegionById(Data[6]); + cSubtitleRegion *region = page->GetRegionById(bs.GetBits(8)); if (!region) break; - int regionVersion = (Data[6 + 1] & 0xF0) >> 4; + int regionVersion = bs.GetBits(4); if (regionVersion == region->Version()) break; // no update region->SetVersion(regionVersion); - bool regionFillFlag = (Data[6 + 1] & 0x08) >> 3; - int regionWidth = (Data[6 + 2] << 8) | Data[6 + 3]; + bool regionFillFlag = bs.GetBit(); + bs.SkipBits(3); // reserved + int regionWidth = bs.GetBits(16); if (regionWidth < 1) regionWidth = 1; - int regionHeight = (Data[6 + 4] << 8) | Data[6 + 5]; + int regionHeight = bs.GetBits(16); if (regionHeight < 1) regionHeight = 1; region->SetSize(regionWidth, regionHeight); - region->SetLevel((Data[6 + 6] & 0xE0) >> 5); - region->SetDepth((Data[6 + 6] & 0x1C) >> 2); - region->SetClutId(Data[6 + 7]); + region->SetLevel(bs.GetBits(3)); + region->SetDepth(bs.GetBits(3)); + bs.SkipBits(2); // reserved + region->SetClutId(bs.GetBits(8)); dbgregions("Region pageId %d id %d version %d fill %d width %d height %d level %d depth %d clutId %d\n", pageId, region->RegionId(), region->Version(), regionFillFlag, regionWidth, regionHeight, region->Level(), region->Depth(), region->ClutId()); + int region8bitPixelCode = bs.GetBits(8); + int region4bitPixelCode = bs.GetBits(4); + int region2bitPixelCode = bs.GetBits(2); + bs.SkipBits(2); // reserved if (regionFillFlag) { switch (region->Bpp()) { - case 2: region->FillRegion((Data[6 + 9] & 0x0C) >> 2); break; - case 4: region->FillRegion((Data[6 + 9] & 0xF0) >> 4); break; - case 8: region->FillRegion(Data[6 + 8]); break; + case 2: region->FillRegion(region8bitPixelCode); break; + case 4: region->FillRegion(region4bitPixelCode); break; + case 8: region->FillRegion(region2bitPixelCode); break; default: dbgregions("unknown bpp %d (%s %d)\n", region->Bpp(), __FUNCTION__, __LINE__); } } - for (int i = 6 + 10; i < segmentLength; i += 6) { - cSubtitleObject *object = region->GetObjectById((Data[i] << 8) | Data[i + 1], true); - int objectType = (Data[i + 2] & 0xC0) >> 6; - object->SetCodingMethod(objectType); - object->SetProviderFlag((Data[i + 2] & 0x30) >> 4); - int objectHorizontalPosition = ((Data[i + 2] & 0x0F) << 8) | Data[i + 3]; - int objectVerticalPosition = ((Data[i + 4] & 0x0F) << 8) | Data[i + 5]; - object->SetPosition(objectHorizontalPosition, objectVerticalPosition); - if (objectType == 0x01 || objectType == 0x02) { - object->SetForegroundColor(Data[i + 6]); - object->SetBackgroundColor(Data[i + 7]); - i += 2; - } - } + while (!bs.IsEOF()) { + cSubtitleObject *object = region->GetObjectById(bs.GetBits(16), true); + int objectType = bs.GetBits(2); + object->SetCodingMethod(objectType); + object->SetProviderFlag(bs.GetBits(2)); + int objectHorizontalPosition = bs.GetBits(12); + bs.SkipBits(4); // reserved + int objectVerticalPosition = bs.GetBits(12); + object->SetPosition(objectHorizontalPosition, objectVerticalPosition); + if (objectType == 0x01 || objectType == 0x02) { + object->SetForegroundPixelCode(bs.GetBits(8)); + object->SetBackgroundPixelCode(bs.GetBits(8)); + } + } break; } case CLUT_DEFINITION_SEGMENT: { dbgsegments("CLUT_DEFINITION_SEGMENT\n"); - cSubtitleClut *clut = page->GetClutById(Data[6], true); - int clutVersion = (Data[6 + 1] & 0xF0) >> 4; + cSubtitleClut *clut = page->GetClutById(bs.GetBits(8), true); + int clutVersion = bs.GetBits(4); if (clutVersion == clut->Version()) break; // no update clut->SetVersion(clutVersion); + bs.SkipBits(4); // reserved dbgcluts("Clut pageId %d id %d version %d\n", pageId, clut->ClutId(), clut->Version()); - for (int i = 6 + 2; i < segmentLength; i += 2) { - uchar clutEntryId = Data[i]; - bool fullRangeFlag = Data[i + 1] & 1; - uchar yval; - uchar crval; - uchar cbval; - uchar tval; - if (fullRangeFlag) { - yval = Data[i + 2]; - crval = Data[i + 3]; - cbval = Data[i + 4]; - tval = Data[i + 5]; - } - else { - yval = Data[i + 2] & 0xFC; - crval = (Data[i + 2] & 0x03) << 6; - crval |= (Data[i + 3] & 0xC0) >> 2; - cbval = (Data[i + 3] & 0x3C) << 2; - tval = (Data[i + 3] & 0x03) << 6; - } - tColor value = 0; - if (yval) { - value = yuv2rgb(yval, cbval, crval); - value |= ((10 - (clutEntryId ? Setup.SubtitleFgTransparency : Setup.SubtitleBgTransparency)) * (255 - tval) / 10) << 24; - } - int EntryFlags = Data[i + 1]; - dbgcluts("%2d %d %d %d %08X\n", clutEntryId, (EntryFlags & 0x80) ? 2 : 0, (EntryFlags & 0x40) ? 4 : 0, (EntryFlags & 0x20) ? 8 : 0, value); - if ((EntryFlags & 0x80) != 0) - clut->SetColor(2, clutEntryId, value); - if ((EntryFlags & 0x40) != 0) - clut->SetColor(4, clutEntryId, value); - if ((EntryFlags & 0x20) != 0) - clut->SetColor(8, clutEntryId, value); - i += fullRangeFlag ? 4 : 2; - } + 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\n", clutEntryId, entryClut2Flag ? 2 : 0, entryClut4Flag ? 4 : 0, entryClut8Flag ? 8 : 0, value); + if (entryClut2Flag) + clut->SetColor(2, clutEntryId, value); + if (entryClut4Flag) + clut->SetColor(4, clutEntryId, value); + if (entryClut8Flag) + clut->SetColor(8, clutEntryId, value); + } dbgcluts("\n"); page->UpdateRegionPalette(clut); break; } case OBJECT_DATA_SEGMENT: { dbgsegments("OBJECT_DATA_SEGMENT\n"); - cSubtitleObject *object = page->GetObjectById((Data[6] << 8) | Data[6 + 1]); + cSubtitleObject *object = page->GetObjectById(bs.GetBits(16)); if (!object) break; - int objectVersion = (Data[6 + 2] & 0xF0) >> 4; + int objectVersion = bs.GetBits(4); if (objectVersion == object->Version()) break; // no update object->SetVersion(objectVersion); - int codingMethod = (Data[6 + 2] & 0x0C) >> 2; - object->SetNonModifyingColorFlag(Data[6 + 2] & 0x01); + int codingMethod = bs.GetBits(2); + object->SetNonModifyingColorFlag(bs.GetBit()); + bs.SkipBit(); // reserved dbgobjects("Object pageId %d id %d version %d method %d modify %d\n", pageId, object->ObjectId(), object->Version(), object->CodingMethod(), object->NonModifyingColorFlag()); if (codingMethod == 0) { // coding of pixels - int i = 6 + 3; - int topFieldLength = (Data[i] << 8) | Data[i + 1]; - int bottomFieldLength = (Data[i + 2] << 8) | Data[i + 3]; - object->DecodeSubBlock(Data + i + 4, topFieldLength, true); + int topFieldLength = bs.GetBits(16); + int bottomFieldLength = bs.GetBits(16); + object->DecodeSubBlock(bs.GetData(), topFieldLength, true); if (bottomFieldLength) - object->DecodeSubBlock(Data + i + 4 + topFieldLength, bottomFieldLength, false); + object->DecodeSubBlock(bs.GetData() + topFieldLength, bottomFieldLength, false); else - object->DecodeSubBlock(Data + i + 4, topFieldLength, false); + object->DecodeSubBlock(bs.GetData(), topFieldLength, false); + bs.WordAlign(); } else if (codingMethod == 1) { // coded as a string of characters - //TODO implement textual subtitles + int numberOfCodes = bs.GetBits(8); + object->DecodeCharacterString(bs.GetData(), numberOfCodes); } +#ifdef FINISHPAGE_HACK + FinishPage(page); // flush to OSD right away +#endif break; } case DISPLAY_DEFINITION_SEGMENT: { dbgsegments("DISPLAY_DEFINITION_SEGMENT\n"); - int version = (Data[6] & 0xF0) >> 4; + int version = bs.GetBits(4); if (version != ddsVersionNumber) { - int displayWindowFlag = (Data[6] & 0x08) >> 3; + bool displayWindowFlag = bs.GetBit(); windowHorizontalOffset = 0; windowVerticalOffset = 0; - displayWidth = windowWidth = ((Data[7] << 8) | Data[8]) + 1; - displayHeight = windowHeight = ((Data[9] << 8) | Data[10]) + 1; - if (displayWindowFlag) { - windowHorizontalOffset = (Data[11] << 8) | Data[12]; // displayWindowHorizontalPositionMinimum - windowWidth = ((Data[13] << 8) | Data[14]) - windowHorizontalOffset + 1; // displayWindowHorizontalPositionMaximum - windowVerticalOffset = (Data[15] << 8) | Data[16]; // displayWindowVerticalPositionMinimum - windowHeight = ((Data[17] << 8) | Data[18]) - windowVerticalOffset + 1; // displayWindowVerticalPositionMaximum + 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(); SetupChanged(); @@ -1086,15 +1213,56 @@ int cDvbSubtitleConverter::ExtractSegment(const uchar *Data, int Length, int64_t } break; } + case DISPARITY_SIGNALING_SEGMENT: { + dbgsegments("DISPARITY_SIGNALING_SEGMENT\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\n"); FinishPage(page); - } break; + } default: dbgsegments("*** unknown segment type: %02X\n", segmentType); } - return segmentLength; + return bs.Length() / 8; } return -1; } @@ -1143,6 +1311,7 @@ void cDvbSubtitleConverter::FinishPage(cDvbSubtitlePage *Page) cDvbSubtitleBitmaps *Bitmaps = new cDvbSubtitleBitmaps(Page->Pts(), Page->Timeout(), Areas, NumAreas, osdFactorX, osdFactorY); bitmaps->Add(Bitmaps); for (cSubtitleRegion *sr = Page->regions.First(); sr; sr = Page->regions.Next(sr)) { + sr->UpdateTextData(Page->GetClutById(sr->ClutId())); int posX = sr->HorizontalAddress(); int posY = sr->VerticalAddress(); if (sr->Width() > 0 && sr->Height() > 0) { diff --git a/tools.c b/tools.c index c9a3a44e..35693821 100644 --- a/tools.c +++ b/tools.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 2.17 2011/08/15 13:35:23 kls Exp $ + * $Id: tools.c 2.18 2011/09/18 11:22:21 kls Exp $ */ #include "tools.h" @@ -1196,6 +1196,47 @@ const char *cBase64Encoder::NextLine(void) return NULL; } +// --- cBitStream ------------------------------------------------------------ + +int cBitStream::GetBit(void) +{ + if (index >= length) + return 1; + int r = (data[index >> 3] >> (7 - (index & 7))) & 1; + ++index; + return r; +} + +uint32_t cBitStream::GetBits(int n) +{ + uint32_t r = 0; + while (n--) + r |= GetBit() << n; + return r; +} + +void cBitStream::ByteAlign(void) +{ + int n = index % 8; + if (n > 0) + SkipBits(8 - n); +} + +void cBitStream::WordAlign(void) +{ + int n = index % 16; + if (n > 0) + SkipBits(16 - n); +} + +bool cBitStream::SetLength(int Length) +{ + if (Length > length) + return false; + length = Length; + return true; +} + // --- cReadLine ------------------------------------------------------------- cReadLine::cReadLine(void) diff --git a/tools.h b/tools.h index 3d13c191..bcc6f171 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 2.11 2011/08/15 14:13:42 kls Exp $ + * $Id: tools.h 2.12 2011/09/18 11:21:23 kls Exp $ */ #ifndef __TOOLS_H @@ -266,6 +266,28 @@ public: ///< is called, or until the object is destroyed. }; +class cBitStream { +private: + const uint8_t *data; + int length; // in bits + int index; // in bits +public: + cBitStream(const uint8_t *Data, int Length) : data(Data), length(Length), index(0) {} + ~cBitStream() {} + int GetBit(void); + uint32_t GetBits(int n); + void ByteAlign(void); + void WordAlign(void); + bool SetLength(int Length); + void SkipBits(int n) { index += n; } + void SkipBit(void) { SkipBits(1); } + bool IsEOF(void) const { return index >= length; } + void Reset(void) { index = 0; } + int Length(void) const { return length; } + int Index(void) const { return (IsEOF() ? length : index); } + const uint8_t *GetData(void) const { return (IsEOF() ? NULL : data + (index / 8)); } + }; + class cTimeMs { private: uint64_t begin;