Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VBI: support of Teletext (PoC) #2033

Merged
merged 3 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 214 additions & 90 deletions Source/MediaInfo/Multiple/File_Vbi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
#include "MediaInfo/Multiple/File_Vbi.h"
#if defined(MEDIAINFO_EIA608_YES)
#include "MediaInfo/Text/File_Eia608.h"
#endif
#if defined(MEDIAINFO_TIMECODE_YES)
#include "MediaInfo/Multiple/File_Gxf_TimeCode.h"
#endif
#endif
#if defined(MEDIAINFO_TELETEXT_YES)
#include "MediaInfo/Text/File_Teletext.h"
#endif
Expand All @@ -41,17 +41,6 @@ namespace MediaInfoLib
{
//---------------------------------------------------------------------------

//***************************************************************************
// Info
//***************************************************************************

//---------------------------------------------------------------------------
static const char* Vbi_MuxingMode[] = {
"VBI / Line 21",
"VBI / VITC",
};
static_assert(sizeof(Vbi_MuxingMode) / sizeof(Vbi_MuxingMode[0]) == VbiType_Max - 1, "");

//***************************************************************************
// Constructor/Destructor
//***************************************************************************
Expand All @@ -66,23 +55,6 @@ File_Vbi::File_Vbi()

LineNumber=0;
IsLast=false;
#if defined(MEDIAINFO_TELETEXT_YES)
Teletext_Parser=NULL;
#endif //defined(MEDIAINFO_TELETEXT_YES)
}

//---------------------------------------------------------------------------
File_Vbi::~File_Vbi()
{
#if defined(MEDIAINFO_TELETEXT_YES)
if (Teletext_Parser)
delete Teletext_Parser;
#endif //defined(MEDIAINFO_TELETEXT_YES)
}

//---------------------------------------------------------------------------
void File_Vbi::Streams_Fill()
{
}

//---------------------------------------------------------------------------
Expand All @@ -93,59 +65,63 @@ void File_Vbi::Streams_Finish()
continue;
}
Finish(Stream.second.Parser);
size_t StreamKind = Stream_General + 1;
for (; StreamKind < Stream_Max; StreamKind++) {
if (Stream.second.Parser->Count_Get((stream_t)StreamKind)) {
break;
}
}
if (StreamKind >= Stream_Max) {
}

for (auto& Stream : Streams) {
if (!Stream.second.Parser) {
continue;
}
auto Start = Count_Get((stream_t)StreamKind);
size_t Start[Stream_Max];
for (size_t StreamKind = Stream_General + 1; StreamKind < Stream_Max; StreamKind++) {
Start[StreamKind] = Count_Get((stream_t)StreamKind);
}
Merge(*Stream.second.Parser);
auto End = Count_Get((stream_t)StreamKind);
for (size_t StreamPos = Start; StreamPos < End; StreamPos++) {
auto ID = Stream.first >= 263 ? (Stream.first - 263) : Stream.first;
auto ID_String = Ztring::ToZtring(ID);
const auto& SubID = Retrieve_Const((stream_t)StreamKind, StreamPos, General_ID);
if (!SubID.empty()) {
ID_String += __T('-');
ID_String += SubID;
}
Fill((stream_t)StreamKind, StreamPos, General_ID, ID_String, true);
if (Stream.second.Type) {
Fill((stream_t)StreamKind, StreamPos, "MuxingMode", Vbi_MuxingMode[Stream.second.Type - 1]);
if (Stream.second.Type == VbiType_Vitc) {
Fill(Stream_Other, StreamPos, Other_Format, "SMPTE TC"); // TODO: in timecode parser directly
Fill(Stream_Other, StreamPos, Other_BitRate_Mode, "CBR"); // TODO: in timecode parser directly
auto ID2 = ID + 263;
auto Field2_Vitc = Streams.find(ID2);
if (Field2_Vitc != Streams.end() && Field2_Vitc->second.Type == Stream.second.Type) {
const auto& TimeCode_FirstFrame1 = Retrieve_Const(Stream_Other, StreamPos, Other_TimeCode_FirstFrame);
const auto& TimeCode_FirstFrame2 = Field2_Vitc->second.Parser->Retrieve_Const(Stream_Other, StreamPos, Other_TimeCode_FirstFrame);
if (TimeCode_FirstFrame2 != TimeCode_FirstFrame1) {
Fill(Stream_Other, StreamPos, Other_TimeCode_FirstFrame, TimeCode_FirstFrame2);
for (size_t StreamKind = Stream_General + 1; StreamKind < Stream_Max; StreamKind++) {
auto End = Count_Get((stream_t)StreamKind);
for (size_t StreamPos = Start[StreamKind]; StreamPos < End; StreamPos++) {
auto ID = Stream.first >= 313 ? (Stream.first - 313) : Stream.first >= 263 ? (Stream.first - 263) : Stream.first;
auto ID_String = Ztring::ToZtring(ID);
const auto& SubID = Retrieve_Const((stream_t)StreamKind, StreamPos, General_ID);
if (!SubID.empty()) {
ID_String += __T('-');
ID_String += SubID;
}
Fill((stream_t)StreamKind, StreamPos, General_ID, ID_String, true);
if (Stream.second.Type) {
Fill((stream_t)StreamKind, StreamPos, "MuxingMode", "VBI");
if (Stream.second.Type == VbiType_Vitc) {
Fill(Stream_Other, StreamPos, Other_Format, "SMPTE TC"); // TODO: in timecode parser directly
Fill(Stream_Other, StreamPos, Other_BitRate_Mode, "CBR"); // TODO: in timecode parser directly
auto Field2 = Streams.find(ID + 263); // NTSC
if (Field2 == Streams.end() || Field2->second.Type != Stream.second.Type) {
Field2 = Streams.find(ID + 313); //PAL
}
if (Field2 != Streams.end() && Field2->second.Type == Stream.second.Type) {
const auto& TimeCode_FirstFrame1 = Stream.second.Parser->Retrieve_Const(Stream_Other, 0, Other_TimeCode_FirstFrame);
const auto& TimeCode_FirstFrame2 = Field2->second.Parser->Retrieve_Const(Stream_Other, 0, Other_TimeCode_FirstFrame);
if (TimeCode_FirstFrame2 != TimeCode_FirstFrame1) {
Fill(Stream_Other, StreamPos, Other_TimeCode_FirstFrame, TimeCode_FirstFrame2);
}
Streams.erase(Field2);
}
}
if (Stream.second.Type == VbiType_Teletext) {
auto Field2 = Streams.find(ID + 263); // NTSC
if (Field2 == Streams.end() || Field2->second.Type != Stream.second.Type) {
Field2 = Streams.find(ID + 313); //PAL
}
if (Field2 != Streams.end() && Field2->second.Type == Stream.second.Type) {
const auto& ID_FirstFrame1 = Stream.second.Parser->Retrieve_Const(StreamKind_Last, 0, General_ID);
const auto& ID_FirstFrame2 = Field2->second.Parser->Retrieve_Const(StreamKind_Last, 0, General_ID);
if (ID_FirstFrame1 == ID_FirstFrame2) {
Streams.erase(Field2);
}
}
Streams.erase(Field2_Vitc);
}
}
}
}
}

#if defined(MEDIAINFO_TELETEXT_YES)
if (Teletext_Parser && !Teletext_Parser->Status[IsFinished] && Teletext_Parser->Status[IsAccepted])
{
Finish(Teletext_Parser);
for (size_t StreamKind=Stream_General+1; StreamKind<Stream_Max; StreamKind++)
for (size_t StreamPos=0; StreamPos<Teletext_Parser->Count_Get((stream_t)StreamKind); StreamPos++)
{
Merge(*Teletext_Parser, (stream_t)StreamKind, StreamPos, StreamPos);
Fill((stream_t)StreamKind, StreamPos, "MuxingMode", "VBI", Unlimited, true);
}
}
#endif //defined(MEDIAINFO_TELETEXT_YES)
}

//***************************************************************************
Expand Down Expand Up @@ -180,20 +156,36 @@ void File_Vbi::Read_Buffer_Continue()
}
}

//---------------------------------------------------------------------------
void File_Vbi::Read_Buffer_Unsynched()
{
for (auto& Stream : Streams) {
if (Stream.second.Parser) {
Stream.second.Parser->Open_Buffer_Unsynch();
}
}
}

//***************************************************************************
// Elements
//***************************************************************************

//---------------------------------------------------------------------------
void File_Vbi::Parse()
{
auto& Stream = Streams[LineNumber];
switch (Stream.Type) {
case VbiType_Line21: Line21(); break;
case VbiType_Vitc: Vitc(); break;
default: // Probing all
auto Stream = Streams.find(LineNumber);
if (Stream != Streams.end()) {
switch (Stream->second.Type) {
case VbiType_Line21: Line21(); break;
case VbiType_Vitc: Vitc(); break;
case VbiType_Teletext: Teletext(); break;
default:;
}
}
else {
Line21();
Vitc();
Teletext();
}
}

Expand Down Expand Up @@ -228,15 +220,17 @@ void File_Vbi::Line21()
// Is 0
if (Is1) {
// New 0
Current_Value = Value;
Is1 = false;
if (!ClockRunIn_Pos) {
ClockRunIn_0_x2 = Current_Pos_Min + Current_Pos_Max; // First Clock Run-In is found
}
ClockRunIn_Pos++;
if (ClockRunIn_Pos == 7) {
break; // The 7 Clock Run-In are found
}
Current_Value = Value;
Current_Pos_Min = i;
Current_Pos_Max = i;
Is1 = false;
}
else {
// Another 0
Expand All @@ -250,6 +244,8 @@ void File_Vbi::Line21()
else {
// New 1
Current_Value = Value;
Current_Pos_Min = i;
Current_Pos_Max = i;
Is1 = true;
}
}
Expand Down Expand Up @@ -277,7 +273,7 @@ void File_Vbi::Line21()

// Compute step between bits
Step_x12 = Current_Pos_Min + Current_Pos_Max - ClockRunIn_0_x2;
if (ClockRunIn_Pos != 7 || Step_x12 * 25 >= Buffer_Size * 12 - ClockRunIn_0_x2 * 6) {
if (ClockRunIn_Pos != 7 || Step_x12 / 12 > ClockRunIn_0_x2 || Step_x12 * 25 >= Buffer_Size * 12 - ClockRunIn_0_x2 * 6) {
return; // Clock Run-In elements are not found or not enough place for 26 elements
}
auto Get_Buffer_Index = [&](int i) { // i is up to 25
Expand Down Expand Up @@ -469,17 +465,145 @@ void File_Vbi::Vitc()
}

//---------------------------------------------------------------------------
void File_Vbi::Read_Buffer_Unsynched()
void File_Vbi::Teletext()
{
#if defined(MEDIAINFO_TELETEXT_YES)
if (Teletext_Parser)
Teletext_Parser->Open_Buffer_Unsynch();
#endif //defined(MEDIAINFO_TELETEXT_YES)
}
// Check luminance min & max
int8u Level_Min = (int8u)-1;
int8u Level_Max = 0;
for (int i = 0; i < Buffer_Size; i++) {
auto Value = Buffer[i];
Level_Min = min(Level_Min, Value);
Level_Max = max(Level_Max, Value);
}
if (Level_Max - Level_Min < 16)
return; // Not enough difference in luminance for having an actual signal
int8u Middle = (Level_Min + Level_Max) >> 1;

// Find first and last 1 bit
int8u First_Highest = 0;
size_t First_Pos = 0;
for (; First_Pos < Buffer_Size; First_Pos++) {
auto Value = Buffer[First_Pos];
if (Value <= First_Highest && First_Highest >= Middle) {
First_Pos--;
break;
}
First_Highest = Value;
}
int8u Last_Highest = 0;
size_t Last_Pos = Buffer_Size - 1;
for (; Last_Pos; Last_Pos--) {
auto Value = Buffer[Last_Pos];
if (Value <= Last_Highest && Last_Highest >= Middle) {
Last_Pos++;
break;
}
Last_Highest = Value;
}
if (Last_Pos <= First_Pos) {
return;
}

//***************************************************************************
// Helpers
//***************************************************************************
// Guess the precise position of the peak based on adjacent bytes
auto Get_PrecisePos = [&](const int8u* Buffer, size_t i) {
if (i + 1 >= Buffer_Size) {
return (float)Buffer[i];
}
if (i && Buffer[i - 1] > Buffer[i + 1]) {
auto Diff0 = Level_Max - Buffer[i - 1];
auto Diff1 = Level_Max - Buffer[i];
float Sum = Diff0 + Diff1;
float Pos = i;
if (Sum) {
Pos -= Diff1 / Sum;
}
return Pos;
}
else {
auto Diff0 = Level_Max - Buffer[i];
auto Diff1 = Level_Max - Buffer[i + 1];
float Sum = Diff0 + Diff1;
float Pos = i;
if (Sum) {
Pos += Diff0 / Sum;
}
return Pos;
}
};

// Guess precise first 1 bit and last 1 bit
auto First1BitPos = Get_PrecisePos(Buffer, First_Pos);
auto Last1BitPos = Get_PrecisePos(Buffer, Last_Pos);

// Compute step between bits
auto Step = (Last1BitPos - First1BitPos) / 357; // Most frames have first 1 bit the first clock bit and last 1 bit the checksum of 0 padding so 3rd last bit
if (Step < 1 || Step > 2 || First1BitPos + Step * 359 + 0.5 >= Buffer_Size) {
return; // Not enough place for 360 elements
}
auto Get_Value = [&](int i) { // i is up to 359
auto Idx = First1BitPos + Step * i;
auto Idx_Int = (size_t)Idx;
auto Value = Buffer[Idx_Int];
Idx_Int++;
if (Idx_Int < Buffer_Size) {
auto Diff = (float)Buffer[Idx_Int] - Value;
Idx -= (size_t)Idx;
Value += Idx * Diff;
}
return Value;
};

//auto ID = LineNumber >= 313 ? (LineNumber - 313) : LineNumber >= 263 ? (LineNumber - 263) : LineNumber;
//auto& Stream = Streams[ID];
auto& Stream = Streams[LineNumber];

// Read 45 8-bit characters
int8u Teletext_Buffer[45];
auto Dump = [&]() {
int8u Current = 0;
for (int i = 0; i < 360; i++) {
auto Value = Get_Value(i);
Current <<= 1;
if (Value >= Middle) {
Current++;
}
if ((i % 8) == 7) {
Teletext_Buffer[i / 8] = ReverseBits(Current);
}
}
};
Dump();
if (BigEndian2int24u(Teletext_Buffer) == 0x555527 && BigEndian2int32u(Teletext_Buffer + 41) == 0x20202020) {
// Store pos when Clock Run-In and Framing Code as well of last 0 padding bytes are found
// 0 = frame count, 1 = sum of first 1 bit, 2 = sum of last 1 bit
Stream.Private[0]++;
Stream.Private[1] += First1BitPos;
Stream.Private[2] += Last1BitPos;
}
else if (Stream.Private[0]) {
// Ever it is not Teletext or no last 0 padding bytes so more difficult to find the exact step, using previous values
First1BitPos = Stream.Private[1] / Stream.Private[0];
Last1BitPos = Stream.Private[2] / Stream.Private[0];
Step = (Last1BitPos - First1BitPos) / 357;
Dump();
if (BigEndian2int24u(Teletext_Buffer) != 0x555527) {
return; // Clock Run-In and Framing Code not found
}
}
else {
return; // Clock Run-In and Framing Code not found
}

// Parse the characters
if (!Stream.Parser)
{
Stream.Parser = new File_Teletext;
Stream.Type = VbiType_Teletext;
Open_Buffer_Init(Stream.Parser);
}
Open_Buffer_Continue(Stream.Parser, Teletext_Buffer, 45);
Element_Offset = Element_Size;
}

//***************************************************************************
// C++
Expand Down
Loading
Loading