Files
UnrealEngine/Engine/Plugins/Media/ElectraUtil/Source/ElectraBase/Private/Utilities/UtilsMPEG.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

420 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Utilities/UtilsMPEG.h"
#include "Utilities/Utilities.h"
#include "Utilities/ElectraBitstream.h"
namespace Electra
{
namespace MPEG
{
class FMP4BitReader : public FBitstreamReader
{
public:
FMP4BitReader(const void* pData, int64 nData)
: FBitstreamReader(pData, nData)
{
}
int32 ReadMP4Length()
{
int32 Length = 0;
for (int32 i = 0; i < 4; ++i)
{
uint32 Bits = GetBits(8);
Length = (Length << 7) + (Bits & 0x7f);
if ((Bits & 0x80) == 0)
break;
}
return Length;
}
};
FESDescriptor::FESDescriptor()
: ObjectTypeID(FObjectTypeID::Unknown)
, StreamTypeID(FStreamType::Unknown)
, BufferSize(0)
, MaxBitrate(0)
, AvgBitrate(0)
, ESID(0)
, DependsOnStreamESID(0)
, StreamPriority(16)
, bDependsOnStream(false)
{
}
void FESDescriptor::SetRawData(const void* Data, int64 Size)
{
RawData.Empty();
if (Size)
{
RawData.Reserve((uint32)Size);
RawData.SetNumUninitialized((uint32)Size);
FMemory::Memcpy(RawData.GetData(), Data, Size);
}
}
const TArray<uint8>& FESDescriptor::GetRawData() const
{
return RawData;
}
const TArray<uint8>& FESDescriptor::GetCodecSpecificData() const
{
return CodecSpecificData;
}
bool FESDescriptor::Parse()
{
CodecSpecificData.Empty();
FMP4BitReader BitReader(RawData.GetData(), RawData.Num());
if (BitReader.GetBits(8) != 3)
{
return false;
}
int32 ESSize = BitReader.ReadMP4Length();
ESID = (uint16)BitReader.GetBits(16);
bDependsOnStream = BitReader.GetBits(1) != 0;
bool bURLFlag = BitReader.GetBits(1) != 0;
bool bOCRflag = BitReader.GetBits(1) != 0;
StreamPriority = (uint8)BitReader.GetBits(5);
if (bDependsOnStream)
{
DependsOnStreamESID = BitReader.GetBits(16);
}
if (bURLFlag)
{
// Skip over the URL
uint32 urlLen = BitReader.GetBits(8);
BitReader.SkipBytes(urlLen);
}
if (bOCRflag)
{
// Skip the OCR ES ID
BitReader.SkipBits(16);
}
// Parse the config descriptor
if (BitReader.GetBits(8) != 4)
{
return false;
}
int32 ConfigDescrSize = BitReader.ReadMP4Length();
ObjectTypeID = static_cast<FObjectTypeID>(BitReader.GetBits(8));
StreamTypeID = static_cast<FStreamType>(BitReader.GetBits(6));
// Skip upstream flag
BitReader.SkipBits(1);
BitReader.SkipBits(1); // this bit is reserved and must be 1, but it is sometimes incorrectly set to 0, so do not check for it being 1!
BufferSize = BitReader.GetBits(24);
MaxBitrate = BitReader.GetBits(32);
AvgBitrate = BitReader.GetBits(32);
if (ConfigDescrSize > 13)
{
// Optional codec specific descriptor
if (BitReader.GetBits(8) != 5)
{
return false;
}
int32 CodecSize = BitReader.ReadMP4Length();
CodecSpecificData.Reserve(CodecSize);
for (int32 i = 0; i < CodecSize; ++i)
{
CodecSpecificData.Push(BitReader.GetBits(8));
}
}
// SL config (we do not need it, we require it to be there though as per the standard)
if (BitReader.GetBits(8) != 6)
{
return false;
}
int32 nSLSize = BitReader.ReadMP4Length();
if (nSLSize != 1)
{
return false;
}
if (BitReader.GetBits(8) != 2)
{
return false;
}
return true;
}
bool FID3V2Metadata::Parse(const uint8* InData, int64 InDataSize)
{
if (!(InData[0] == 'I' && InData[1] == 'D' && InData[2] == '3' &&
InData[3] != 0xff && InData[4] != 0xff && InData[6] < 0x80 && InData[7] < 0x80 && InData[8] < 0x80 && InData[9] < 0x80))
{
return false;
}
auto GetID3Size = [](const uint8* InAddr) -> int32
{
return (int32) (10U + (static_cast<uint32>(InAddr[0]) << 21) + (static_cast<uint32>(InAddr[1]) << 14) + (static_cast<uint32>(InAddr[2]) << 7) + InAddr[3]);
};
auto GetTagSize = [](const uint8* InAddr) -> int32
{
return (int32) (10U + (static_cast<uint32>(InAddr[0]) << 24) + (static_cast<uint32>(InAddr[1]) << 16) + (static_cast<uint32>(InAddr[2]) << 8) + InAddr[3]);
};
if (GetID3Size(InData + 6) > InDataSize)
{
return false;
}
// "Unsynchronization" is not currently supported.
if ((InData[5] & 0x80) != 0)
{
check(!"unsynchronization not currently supported");
return false;
}
// Experimental headers are not supported
if ((InData[5] & 0x20) != 0)
{
return false;
}
int32 HeaderSize = 10;
if ((InData[5] & 0x40) != 0)
{
check(!"extended header not currently supported");
return false;
//HeaderSize += GetTagSize(InData + 10) + xxxx;
}
const uint8* InDataEnd = InData + InDataSize;
InData += HeaderSize;
auto GetString = [](const uint8* InStr, int32 NumBytes, const uint8** OutPtr=nullptr, int32* InOutEncoding=nullptr) -> FString
{
if (InStr && NumBytes > 0)
{
int32 Encoding = (!InOutEncoding || (InOutEncoding && *InOutEncoding<0)) ? *InStr++ : *InOutEncoding;
if (InOutEncoding)
{
*InOutEncoding = Encoding;
}
if (Encoding == 0)
{
// ISO 8859-1 encoding
TArray<uint8> ConvBuf;
ConvBuf.Reserve(NumBytes*2);
const uint8* SrcPtr = reinterpret_cast<const uint8*>(InStr);
int32 NumSrcChars = NumBytes - 1;
for(int32 i=0; i<NumSrcChars; ++i, ++SrcPtr)
{
if (*SrcPtr == 0x00)
{
++SrcPtr;
break;
}
// Permitted whitespaces
if (*SrcPtr == 0x0a || *SrcPtr == 0x09)
{
ConvBuf.Add(*SrcPtr);
}
else if (*SrcPtr >= 0x20 && *SrcPtr < 0x80)
{
ConvBuf.Add(*SrcPtr);
}
else if (*SrcPtr >= 0xa0)
{
// We can convert straight from ISO 8859-1 to UTF8 by doing this:
ConvBuf.Add(0xc0 | (*SrcPtr >> 6));
ConvBuf.Add(0x80 | (*SrcPtr & 0x3f));
}
}
if (OutPtr)
{
*OutPtr = SrcPtr;
}
auto Cnv = StringCast<TCHAR>(reinterpret_cast<const UTF8CHAR*>(ConvBuf.GetData()), ConvBuf.Num());
return FString::ConstructFromPtrSize(Cnv.Get(), Cnv.Length());
}
else if (Encoding == 1 && NumBytes > 1)
{
const uint8* SrcPtr = reinterpret_cast<const uint8*>(InStr);
if (OutPtr)
{
*OutPtr = SrcPtr + NumBytes;
}
int32 NumSrcChars = NumBytes - 1;
uint16 BOM = ((uint16)SrcPtr[0] << 8) | SrcPtr[1];
// Have BOM?
if (BOM == 0xfffe)
{
auto Cnv = StringCast<TCHAR>(reinterpret_cast<const UCS2CHAR*>(SrcPtr+2), NumSrcChars/2-1);
return FString::ConstructFromPtrSize(Cnv.Get(), Cnv.Length());
}
else if (BOM == 0xfeff)
{
TArray<uint8> be;
be.AddUninitialized(NumSrcChars);
for(int32 k=0; k<NumSrcChars; k+=2)
{
be[k+0] = SrcPtr[k+1];
be[k+1] = SrcPtr[k+0];
}
auto Cnv = StringCast<TCHAR>(reinterpret_cast<const UCS2CHAR*>(be.GetData()+2), NumSrcChars/2-1);
return FString::ConstructFromPtrSize(Cnv.Get(), Cnv.Length());
}
}
}
return FString();
};
while(InData+10 < InDataEnd)
{
if (((InData[0] >= 'A' && InData[0] <= 'Z') || (InData[0] >= '0' && InData[0] <= '9')) &&
((InData[1] >= 'A' && InData[1] <= 'Z') || (InData[1] >= '0' && InData[1] <= '9')) &&
((InData[2] >= 'A' && InData[2] <= 'Z') || (InData[2] >= '0' && InData[2] <= '9')) &&
((InData[3] >= 'A' && InData[3] <= 'Z') || (InData[3] >= '0' && InData[3] <= '9')))
{
uint32 FrameID = (static_cast<uint32>(InData[0]) << 24) | (static_cast<uint32>(InData[1]) << 16) | (static_cast<uint32>(InData[2]) << 8) | static_cast<uint32>(InData[3]);
int32 FrameSize = (int32)GetTagSize(InData + 4);
if (FrameSize <= 10)
{
return false;
}
const uint8* const FrameEnd = InData + FrameSize;
uint16 Flags = (InData[8] << 8) | InData[9];
// Ignore compressed, encrypted or grouped tags.
if ((Flags & 0xc0) != 0)
{
InData += FrameSize;
continue;
}
switch(FrameID)
{
default:
{
break;
}
// Track length
case Utils::Make4CC('T','L','E','N'):
{
FString ms(GetString(InData + 10, FrameSize - 10));
int64 dur = -1;
LexFromString(dur, *ms);
if (dur > 0)
{
FTimespan Duration = FTimespan::FromMilliseconds((double)dur);
FItem Item {.Value = FVariant(Duration) };
Tags.Emplace(FrameID, MoveTemp(Item));
}
break;
}
// MPEG location lookup table
case Utils::Make4CC('M','L','L','T'):
{
TArray<uint8> Blob(InData + 10, FrameSize - 10);
FItem Item {.Value = FVariant(MoveTemp(Blob)) };
Tags.Emplace(FrameID, MoveTemp(Item));
break;
}
// Cover image
case Utils::Make4CC('A','P','I','C'):
{
const uint8* NextData = InData + 10;
int32 TextEncoding = -1;
FString MimeType(GetString(NextData, FrameEnd - NextData, &NextData, &TextEncoding));
if (NextData < FrameEnd)
{
uint8 PictureType = *NextData++;
FString Description(GetString(NextData, FrameEnd - NextData, &NextData, &TextEncoding));
int32 ImageSize = FrameEnd - NextData;
TArray<uint8> PicData(NextData, ImageSize);
FItem Item {.MimeType = MoveTemp(MimeType), .Value = FVariant(MoveTemp(PicData)), .ItemType = PictureType };
Tags.Emplace(FrameID, MoveTemp(Item));
}
break;
}
// Private data
case Utils::Make4CC('P','R','I','V'):
{
int32 TextEncoding = 0;
const uint8* NextData = InData + 10;
FString Owner(GetString(NextData, FrameEnd - NextData, &NextData, &TextEncoding));
int32 PrivateSize = FrameEnd - NextData;
TArray<uint8> PrivateData(NextData, PrivateSize);
FItem& Item = PrivateItems.Emplace_GetRef();
Item.MimeType = MoveTemp(Owner);
Item.Value = FVariant(MoveTemp(PrivateData));
break;
}
// Recognized text fields
case Utils::Make4CC('T','A','L','B'):
case Utils::Make4CC('T','C','O','M'):
case Utils::Make4CC('T','C','O','N'):
case Utils::Make4CC('T','C','O','P'):
case Utils::Make4CC('T','D','A','T'):
case Utils::Make4CC('T','E','N','C'):
case Utils::Make4CC('T','E','X','T'):
case Utils::Make4CC('T','I','M','E'):
case Utils::Make4CC('T','I','T','1'):
case Utils::Make4CC('T','I','T','2'):
case Utils::Make4CC('T','I','T','3'):
case Utils::Make4CC('T','L','A','N'):
case Utils::Make4CC('T','P','E','1'):
case Utils::Make4CC('T','P','E','2'):
case Utils::Make4CC('T','P','E','3'):
case Utils::Make4CC('T','P','E','4'):
case Utils::Make4CC('T','P','O','S'):
case Utils::Make4CC('T','P','U','B'):
case Utils::Make4CC('T','R','C','K'):
case Utils::Make4CC('T','Y','E','R'):
{
FString s(GetString(InData + 10, FrameSize - 10));
if (s.Len())
{
FItem Item {.Value = FVariant(MoveTemp(s)) };
Tags.Emplace(FrameID, MoveTemp(Item));
}
break;
}
}
InData += FrameSize;
}
else
{
return InData[0] == 0;
}
}
return true;
}
bool FID3V2Metadata::HaveTag(uint32 InTag)
{
const FItem* v = Tags.Find(InTag);
return !!v;
}
bool FID3V2Metadata::GetTag(FItem& OutValue, uint32 InTag)
{
const FItem* v = Tags.Find(InTag);
if (v)
{
OutValue = *v;
}
return !!v;
}
const TMap<uint32, FID3V2Metadata::FItem>& FID3V2Metadata::GetTags() const
{
return Tags;
}
TMap<uint32, FID3V2Metadata::FItem>& FID3V2Metadata::GetTags()
{
return Tags;
}
const TArray<FID3V2Metadata::FItem>& FID3V2Metadata::GetPrivateItems() const
{
return PrivateItems;
}
} // namespace MPEG
} // namespace Electra