From 388782cb44dd7977137422bd93266895a1154cb1 Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Tue, 7 Jul 2026 22:15:43 +0900 Subject: [PATCH 1/8] Add MMT/TLV container parser MMT (MPEG Media Transport, ISO/IEC 23008-1) framed in TLV packets (ARIB STD-B32), the container used by Japanese ISDB-S3 BS/CS 4K/8K broadcasts. Maps the MPT asset list to HEVC, AAC and TTML streams; surfaces the present MH-EIT event as a Menu chapter (name, description, schedule), the MH-SDT service name, the video/audio component descriptors, and the scramble state. Everything comes from the clear signaling, so it also works on scrambled files. Structured like File_MpegTs: File_MmtTlv handles TLV, compressed-IP and MMTP framing plus fragment reassembly, then hands complete signaling tables to File_Mmt (tables) and File_Mmt_Descriptors (descriptors) via Open_Buffer, and creates a child ES parser per asset by asset_type. Parsing uses the standard Get_/Skip_/BS_ helpers throughout, so it produces a full trace. Signed-off-by: Rainbaby --- Project/CMake/CMakeLists.txt | 3 + Source/MediaInfo/File__MultipleParsing.cpp | 6 + Source/MediaInfo/MediaInfo_File.cpp | 11 +- Source/MediaInfo/Multiple/File_Mmt.cpp | 461 +++++ Source/MediaInfo/Multiple/File_Mmt.h | 167 ++ Source/MediaInfo/Multiple/File_MmtTlv.cpp | 1614 +++++++++++++++++ Source/MediaInfo/Multiple/File_MmtTlv.h | 194 ++ .../Multiple/File_Mmt_Descriptors.cpp | 556 ++++++ .../MediaInfo/Multiple/File_Mmt_Descriptors.h | 60 + Source/MediaInfo/Setup.h | 3 + 10 files changed, 3074 insertions(+), 1 deletion(-) create mode 100644 Source/MediaInfo/Multiple/File_Mmt.cpp create mode 100644 Source/MediaInfo/Multiple/File_Mmt.h create mode 100644 Source/MediaInfo/Multiple/File_MmtTlv.cpp create mode 100644 Source/MediaInfo/Multiple/File_MmtTlv.h create mode 100644 Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp create mode 100644 Source/MediaInfo/Multiple/File_Mmt_Descriptors.h diff --git a/Project/CMake/CMakeLists.txt b/Project/CMake/CMakeLists.txt index 25471e0e4..f8d2e5fc7 100644 --- a/Project/CMake/CMakeLists.txt +++ b/Project/CMake/CMakeLists.txt @@ -284,6 +284,9 @@ set(MediaInfoLib_SRCS ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_Lxf.cpp ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_Mk.cpp ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_MiXml.cpp + ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_MmtTlv.cpp + ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_Mmt.cpp + ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_Mmt_Descriptors.cpp ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_Mpeg4.cpp ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_Mpeg4_Descriptors.cpp ${MediaInfoLib_SOURCES_PATH}/MediaInfo/Multiple/File_Mpeg4_Elements.cpp diff --git a/Source/MediaInfo/File__MultipleParsing.cpp b/Source/MediaInfo/File__MultipleParsing.cpp index a050f1afd..b734d0d54 100644 --- a/Source/MediaInfo/File__MultipleParsing.cpp +++ b/Source/MediaInfo/File__MultipleParsing.cpp @@ -86,6 +86,9 @@ #if defined(MEDIAINFO_MIXML_YES) #include "MediaInfo/Multiple/File_MiXml.h" #endif +#if defined(MEDIAINFO_MMTTLV_YES) + #include "MediaInfo/Multiple/File_MmtTlv.h" +#endif #if defined(MEDIAINFO_MPEG4_YES) #include "MediaInfo/Multiple/File_Mpeg4.h" #endif @@ -535,6 +538,9 @@ File__MultipleParsing::File__MultipleParsing() #if defined(MEDIAINFO_MIXML_YES) Parser.push_back(new File_MiXml()); #endif + #if defined(MEDIAINFO_MMTTLV_YES) + Parser.push_back(new File_MmtTlv()); + #endif #if defined(MEDIAINFO_MPEG4_YES) Parser.push_back(new File_Mpeg4()); #endif diff --git a/Source/MediaInfo/MediaInfo_File.cpp b/Source/MediaInfo/MediaInfo_File.cpp index 73507d3de..d50040352 100644 --- a/Source/MediaInfo/MediaInfo_File.cpp +++ b/Source/MediaInfo/MediaInfo_File.cpp @@ -100,6 +100,9 @@ #if defined(MEDIAINFO_MIXML_YES) #include "MediaInfo/Multiple/File_MiXml.h" #endif +#if defined(MEDIAINFO_MMTTLV_YES) + #include "MediaInfo/Multiple/File_MmtTlv.h" +#endif #if defined(MEDIAINFO_MPEG4_YES) #include "MediaInfo/Multiple/File_Mpeg4.h" #endif @@ -548,6 +551,9 @@ static File__Analyze* SelectFromExtension(const String& Parser) #if defined(MEDIAINFO_MK_YES) if (Parser==__T("Mk")) return new File_Mk(); #endif + #if defined(MEDIAINFO_MMTTLV_YES) + if (Parser==__T("MmtTlv")) return new File_MmtTlv(); + #endif #if defined(MEDIAINFO_MPEG4_YES) if (Parser==__T("Mpeg4")) return new File_Mpeg4(); #endif @@ -978,6 +984,9 @@ int MediaInfo_Internal::ListFormats(const String &File_Name) #if defined(MEDIAINFO_MIXML_YES) SAFE_DELETE(Info); Info=new File_MiXml(); if (((Reader_File*)Reader)->Format_Test_PerParser(this, File_Name)>0) return 1; #endif + #if defined(MEDIAINFO_MMTTLV_YES) + SAFE_DELETE(Info); Info=new File_MmtTlv(); if (((Reader_File*)Reader)->Format_Test_PerParser(this, File_Name)>0) return 1; + #endif #if defined(MEDIAINFO_MPEG4_YES) SAFE_DELETE(Info); Info=new File_Mpeg4(); if (((Reader_File*)Reader)->Format_Test_PerParser(this, File_Name)>0) return 1; #endif @@ -1348,7 +1357,7 @@ int MediaInfo_Internal::ListFormats(const String &File_Name) bool MediaInfo_Internal::LibraryIsModified () { #if defined(MEDIAINFO_MULTI_NO) || defined(MEDIAINFO_VIDEO_NO) || defined(MEDIAINFO_AUDIO_NO) || defined(MEDIAINFO_TEXT_NO) || defined(MEDIAINFO_IMAGE_NO) || defined(MEDIAINFO_ARCHIVE_NO) \ - || defined(MEDIAINFO_BDAV_NO) || defined(MEDIAINFO_MK_NO) || defined(MEDIAINFO_OGG_NO) || defined(MEDIAINFO_RIFF_NO) || defined(MEDIAINFO_MPEG4_NO) || defined(MEDIAINFO_MPEGPS_NO) || defined(MEDIAINFO_MPEGTS_NO) || defined(MEDIAINFO_DXW_NO) || defined(MEDIAINFO_FLV_NO) || defined(MEDIAINFO_GXF_NO) || defined(MEDIAINFO_HDSF4M_NO) || defined(MEDIAINFO_HLS_NO) || defined(MEDIAINFO_ISM_NO) || defined(MEDIAINFO_IVF_NO) || defined(MEDIAINFO_LXF_NO) || defined(MEDIAINFO_SWF_NO) || defined(MEDIAINFO_MXF_NO) || defined(MEDIAINFO_NSV_NO) || defined(MEDIAINFO_NUT_NO) || defined(MEDIAINFO_WM_NO) || defined(MEDIAINFO_WTV_NO) || defined(MEDIAINFO_QT_NO) || defined(MEDIAINFO_RM_NO) || defined(MEDIAINFO_DVDIF_NO) || defined(MEDIAINFO_DVDV_NO) || defined(MEDIAINFO_AAF_NO) || defined(MEDIAINFO_CDXA_NO) || defined(MEDIAINFO_DPG_NO) || defined(MEDIAINFO_TSP_NO) \ + || defined(MEDIAINFO_BDAV_NO) || defined(MEDIAINFO_MK_NO) || defined(MEDIAINFO_OGG_NO) || defined(MEDIAINFO_RIFF_NO) || defined(MEDIAINFO_MPEG4_NO) || defined(MEDIAINFO_MPEGPS_NO) || defined(MEDIAINFO_MPEGTS_NO) || defined(MEDIAINFO_MMTTLV_NO) || defined(MEDIAINFO_DXW_NO) || defined(MEDIAINFO_FLV_NO) || defined(MEDIAINFO_GXF_NO) || defined(MEDIAINFO_HDSF4M_NO) || defined(MEDIAINFO_HLS_NO) || defined(MEDIAINFO_ISM_NO) || defined(MEDIAINFO_IVF_NO) || defined(MEDIAINFO_LXF_NO) || defined(MEDIAINFO_SWF_NO) || defined(MEDIAINFO_MXF_NO) || defined(MEDIAINFO_NSV_NO) || defined(MEDIAINFO_NUT_NO) || defined(MEDIAINFO_WM_NO) || defined(MEDIAINFO_WTV_NO) || defined(MEDIAINFO_QT_NO) || defined(MEDIAINFO_RM_NO) || defined(MEDIAINFO_DVDIF_NO) || defined(MEDIAINFO_DVDV_NO) || defined(MEDIAINFO_AAF_NO) || defined(MEDIAINFO_CDXA_NO) || defined(MEDIAINFO_DPG_NO) || defined(MEDIAINFO_TSP_NO) \ || defined(MEDIAINFO_APV_NO) || defined(MEDIAINFO_AV1_NO) || defined(MEDIAINFO_AV2_NO) || defined(MEDIAINFO_AVC_NO) || defined(MEDIAINFO_AVS3V_NO) || defined(MEDIAINFO_AVSV_NO) || defined(MEDIAINFO_HEVC_NO) || defined(MEDIAINFO_MPEG4V_NO) || defined(MEDIAINFO_MPEGV_NO) || defined(MEDIAINFO_FLIC_NO) || defined(MEDIAINFO_THEORA_NO) || defined(MEDIAINFO_Y4M_NO) \ || defined(MEDIAINFO_AC3_NO) || defined(MEDIAINFO_AC4_NO) || defined(MEDIAINFO_ADIF_NO) || defined(MEDIAINFO_ADTS_NO) || defined(MEDIAINFO_SMPTEST0337_NO) || defined(MEDIAINFO_AMR_NO) || defined(MEDIAINFO_DTS_NO) || defined(MEDIAINFO_DOLBYE_NO) || defined(MEDIAINFO_FLAC_NO) || defined(MEDIAINFO_IAMF_NO) || defined(MEDIAINFO_APE_NO) || defined(MEDIAINFO_MPC_NO) || defined(MEDIAINFO_MPCSV8_NO) || defined(MEDIAINFO_MPEGA_NO) || defined(MEDIAINFO_OPENMG_NO) || defined(MEDIAINFO_TWINVQ_NO) || defined(MEDIAINFO_XM_NO) || defined(MEDIAINFO_MOD_NO) || defined(MEDIAINFO_S3M_NO) || defined(MEDIAINFO_IT_NO) || defined(MEDIAINFO_SPEEX_NO) || defined(MEDIAINFO_TAK_NO) || defined(MEDIAINFO_PS2A_NO) \ || defined(MEDIAINFO_CMML_NO) || defined(MEDIAINFO_KATE_NO) || defined(MEDIAINFO_PGS_NO) || defined(MEDIAINFO_OTHERTEXT_NO) \ diff --git a/Source/MediaInfo/Multiple/File_Mmt.cpp b/Source/MediaInfo/Multiple/File_Mmt.cpp new file mode 100644 index 000000000..bd999f694 --- /dev/null +++ b/Source/MediaInfo/Multiple/File_Mmt.cpp @@ -0,0 +1,461 @@ +/* Copyright (c) MediaArea.net SARL. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license that can + * be found in the License.html file in the root of the source tree. + */ + +//--------------------------------------------------------------------------- +#include "MediaInfo/PreComp.h" +#ifdef __BORLANDC__ + #pragma hdrstop +#endif +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/Setup.h" +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#if defined(MEDIAINFO_MMTTLV_YES) +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/Multiple/File_Mmt.h" +#include "MediaInfo/Multiple/File_Mmt_Descriptors.h" +using namespace ZenLib; +//--------------------------------------------------------------------------- + +namespace MediaInfoLib +{ + +//*************************************************************************** +// Local helpers (temporary; consolidated into a shared header when the +// MPT/MH-EIT tables migrate here from File_MmtTlv) +//*************************************************************************** + +namespace +{ + const int8u TABLE_MPT = 0x20; // MPT: table_id(8) version(8) length(16) + + //MMT-SI table_id -> name (ARIB STD-B60 Table 4-8); "" if unrecognized. + const char* Mmt_Table_Name(int8u table_id) + { + switch (table_id) + { + case 0x00 : return "PA"; + case 0x01 : return "MPI (subset 0)"; + case 0x10 : return "MPI (complete)"; + case 0x20 : return "MPT"; + case 0x21 : return "CRI"; + case 0x22 : return "DCI"; + case 0x80 : return "PLT"; + case 0x81 : return "LCT"; + case 0x86 : return "CAT (MH)"; + case 0x8B : return "MH-EIT"; + case 0x9C : return "MH-AIT"; + case 0x9D : return "MH-BIT"; + case 0x9E : return "MH-SDTT"; + case 0x9F : return "MH-SDT"; + case 0xA0 : return "MH-SDT (other stream)"; + case 0xA1 : return "MH-TOT"; + case 0xA2 : return "MH-CDT"; + case 0xA3 : return "DDM Table"; + case 0xA4 : return "DAM Table"; + case 0xA5 : return "DCC Table"; + case 0xA6 : return "EMT"; + case 0xA7 : return "MH-DIT"; + case 0xA8 : return "MH-SIT"; + default : break; + } + if (table_id >= 0x02 && table_id <= 0x0F) return "MPI (subset)"; + if (table_id >= 0x11 && table_id <= 0x1F) return "MPT (subset)"; + if (table_id >= 0x82 && table_id <= 0x83) return "ECM"; + if (table_id >= 0x84 && table_id <= 0x85) return "EMM"; + if (table_id >= 0x87 && table_id <= 0x88) return "DCM"; + if (table_id >= 0x89 && table_id <= 0x8A) return "DMM"; + if (table_id >= 0x8C && table_id <= 0x9B) return "MH-EIT (schedule)"; + return ""; + } + + //running_status meaning (ARIB STD-B60 Table 7-19); "" if not annotated. + const char* Mmt_running_status(int8u c) + { + switch (c) + { + case 1 : return "In non-operation"; + case 2 : return "It will start within several seconds"; + case 3 : return "Out of operation"; + case 4 : return "In operation"; + default: return ""; + } + } +} + +//*************************************************************************** +// Constructor/Destructor +//*************************************************************************** + +//--------------------------------------------------------------------------- +File_Mmt::File_Mmt() +{ + Complete_Stream = NULL; +} + +//*************************************************************************** +// Buffer - Global +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_Mmt::FileHeader_Parse() +{ + Accept(); +} + +//*************************************************************************** +// Buffer - Per element (one signaling table) +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_Mmt::Header_Parse() +{ + //Frame one table. MPT carries a 16-bit length after an 8-bit version; the + //M2 section tables (MH-EIT/SDT/TOT) carry the 12-bit section_length. + int8u table_id; + Get_B1 (table_id, "table_id"); + int64u Size; + if (table_id == TABLE_MPT) + { + int16u length; + Get_B1 (Table_Version, "version"); + Get_B2 (length, "length"); + Size = (int64u)4 + length; + } + else + { + int16u section_length; + BS_Begin(); + Skip_SB( "section_syntax_indicator"); + Skip_SB( "reserved_future_use"); + Skip_S1(2, "reserved"); + Get_S2 (12, section_length, "section_length"); + BS_End(); + Size = (int64u)3 + section_length; + } + if (Element_Size && Size > Element_Size) + Size = Element_Size; // a truncated final table: parse against what we have + Header_Fill_Size(Size); + Header_Fill_Code(table_id, Ztring().From_Number(table_id, 16)); +} + +//--------------------------------------------------------------------------- +void File_Mmt::Data_Parse() +{ + switch (Element_Code) + { + case 0x20 : Element_Name("MPT"); Mpt(); break; // asset -> codec map + case 0x8B : Element_Name("MH-EIT"); MhEit(); break; // present event + case 0x9F : Element_Name("MH-SDT"); MhSdt(); break; // service (channel) name + case 0xA1 : Element_Name("MH-TOT"); MhTot(); break; // current JST clock + default : + { + //Named-but-unparsed: recognized in the trace, body left opaque. + const char* Name = Mmt_Table_Name((int8u)Element_Code); + if (Name[0]) + Element_Name(Name); + Skip_XX(Element_Size - Element_Offset, "Data"); + } + break; + } +} + +//*************************************************************************** +// Tables +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_Mmt::MhTot() +{ + //JST_time (40): MJD(16) + BCD HHMMSS(24), then a descriptor loop and a CRC + //we do not verify. + if (Element_Size - Element_Offset < 5) + { + Skip_XX(Element_Size - Element_Offset, "Data"); + return; + } + int16u mjd; + int32u hms; + Get_B2 (mjd, "MJD"); + Get_B3 (hms, "JST_time (BCD)"); + if (Element_Size - Element_Offset >= 2) + { + int16u dll; + BS_Begin(); + Skip_S1(4, "reserved"); + Get_S2 (12, dll, "descriptors_loop_length"); + BS_End(); + if (Element_Offset + dll > Element_Size) + dll = (int16u)(Element_Size - Element_Offset); + if (dll) + Skip_XX(dll, "descriptors"); + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "CRC32"); + + int64s utc = Mmt_DateTime_To_Seconds(mjd, hms); // JST + if (utc < 0 || !Complete_Stream) + return; + Complete_Stream->TotUtc = utc - Mmt_JST_Offset_Seconds; + Complete_Stream->TotSeen = true; +} + +//--------------------------------------------------------------------------- +void File_Mmt::Mpt() +{ + //MPT body: MPT_mode(8) MMT_package_id_length(8) id MPT_descriptors_length(16) + //descriptors number_of_assets(8), then per asset: identifier_type(8) + //asset_id_scheme(32) asset_id_length(8) id asset_type(32,LE) clock_flag(8) + //location_count(8) locations asset_descriptors_length(16) descriptors. + if (!Complete_Stream || Element_Size - Element_Offset < 2) + return; + int8u pkg_id_len, number_of_assets; + int16u mpt_desc_len; + BS_Begin(); + Skip_S1(6, "reserved"); + Skip_S1(2, "MPT_mode"); + BS_End(); + Get_B1 (pkg_id_len, "MMT_package_id_length"); + if (Element_Offset + pkg_id_len > Element_Size) return; + Skip_XX(pkg_id_len, "MMT_package_id"); + if (Element_Size - Element_Offset < 2) return; + Get_B2 (mpt_desc_len, "MPT_descriptors_length"); + if (Element_Offset + mpt_desc_len > Element_Size) return; + if (mpt_desc_len) + { + //Package-level descriptors: not extracted, but named in the trace. + File_Mmt_Descriptors Desc; + Desc.Complete_Stream = Complete_Stream; + Open_Buffer_Init(&Desc); + Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, mpt_desc_len); + Open_Buffer_Finalize(&Desc); + Skip_XX(mpt_desc_len, "MPT_descriptors"); + } + if (Element_Size - Element_Offset < 1) return; + Get_B1 (number_of_assets, "number_of_assets"); + + Complete_Stream->TransferLast = 0; //newest wins within this MPT + std::vector Found; + for (int i = 0; i < number_of_assets && Element_Offset < Element_Size; ++i) + { + //A mid-asset shortfall stops the loop with prior assets intact. + Element_Begin1("asset"); + if (Element_Size - Element_Offset < 6) { Element_End0(); break; } + int8u asset_id_len, location_count; + int32u asset_type; + Skip_B1( "identifier_type"); + Skip_B4( "asset_id_scheme"); + Get_B1 (asset_id_len, "asset_id_length"); + if (Element_Offset + asset_id_len > Element_Size) { Element_End0(); break; } + Skip_XX(asset_id_len, "asset_id"); + if (Element_Size - Element_Offset < 4) { Element_End0(); break; } + Get_L4 (asset_type, "asset_type"); //FourCC (little-endian) + if (Element_Size - Element_Offset < 1) { Element_End0(); break; } + BS_Begin(); + Skip_S1(7, "reserved"); + Skip_SB( "asset_clock_relation_flag"); + BS_End(); + if (Element_Size - Element_Offset < 1) { Element_End0(); break; } + Get_B1 (location_count, "location_count"); + + int16u packet_id = 0; + bool packet_id_set = false; + for (int j = 0; j < location_count && Element_Offset < Element_Size; ++j) + { + int8u location_type; + Get_B1 (location_type, "location_type"); + size_t loc_len; + switch (location_type) + { + case 0x00: loc_len = 2; break; //packet_id(16) + case 0x01: loc_len = 4 + 2 + 2; break; //ipv4 (approx) + case 0x02: loc_len = 16 + 16 + 2; break; //ipv6 (approx) + case 0x05: loc_len = 2 + 2 + 2; break; //url-ish (best-effort) + default: loc_len = 0; break; + } + if (Element_Offset + loc_len > Element_Size) + break; + if (location_type == 0x00 && !packet_id_set) + { + Get_B2 (packet_id, "packet_id"); + packet_id_set = true; + if (loc_len > 2) + Skip_XX(loc_len - 2, "location"); + } + else if (loc_len) + Skip_XX(loc_len, "location"); + } + + if (Element_Size - Element_Offset < 2) { Element_End0(); break; } + int16u asset_desc_len; + Get_B2 (asset_desc_len, "asset_descriptors_length"); + if (Element_Offset + asset_desc_len > Element_Size) { Element_End0(); break; } + + asset a; + a.Type = asset_type; + a.PacketId = packet_id; + if (asset_desc_len) + { + File_Mmt_Descriptors Desc; + Desc.Complete_Stream = Complete_Stream; + Desc.CurrentAsset = &a; + Open_Buffer_Init(&Desc); + Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, asset_desc_len); + Open_Buffer_Finalize(&Desc); + Skip_XX(asset_desc_len, "asset_descriptors"); + } + Found.push_back(a); + Element_End0(); + } + + Complete_Stream->Assets = Found; + Complete_Stream->MptVersion = Table_Version; + Complete_Stream->MptValid = true; +} + +//--------------------------------------------------------------------------- +void File_Mmt::MhEit() +{ + //The present event leads section 0 (current_next==1). The container keeps + //the boundary-window/hop logic; here we only extract fields. + if (!Complete_Stream || Element_Size - Element_Offset < 11) + return; + int16u service_id, tlv_stream_id; + int8u section_number; + bool current_next_indicator; + Get_B2 (service_id, "service_id"); + BS_Begin(); + Skip_S1(2, "reserved"); + Skip_S1(5, "version_number"); + Get_SB (current_next_indicator, "current_next_indicator"); + BS_End(); + Get_B1 (section_number, "section_number"); + Skip_B1( "last_section_number"); + Get_B2 (tlv_stream_id, "TLV_stream_id"); + Skip_B2( "original_network_id"); + Skip_B1( "segment_last_section_number"); + Skip_B1( "last_table_id"); + Complete_Stream->EitSvcId = service_id; + Complete_Stream->EitSvcIdFound = true; + Complete_Stream->TlvStreamId = tlv_stream_id; + + if (section_number != 0 || !current_next_indicator) + return; + + int64u End = Element_Size >= 4 ? Element_Size - 4 : Element_Size; // drop CRC32 + if (Element_Offset + 12 > End) + return; + Element_Begin1("event"); + int16u event_id, start_date, dll; + int32u start_time, duration; + Get_B2 (event_id, "event_id"); + Get_B2 (start_date, "start_time (MJD)"); + Get_B3 (start_time, "start_time (BCD HHMMSS)"); + Get_B3 (duration, "duration (BCD HHMMSS)"); + int8u running_status; + BS_Begin(); + Get_S1 (3, running_status, "running_status"); Param_Info1(Mmt_running_status(running_status)); + Skip_SB( "free_CA_mode"); + Get_S2 (12, dll, "descriptors_loop_length"); + BS_End(); + int16u desc_loop_len = dll; + if (Element_Offset + desc_loop_len > End) + desc_loop_len = (int16u)(End - Element_Offset); + if (desc_loop_len) + { + File_Mmt_Descriptors Desc; + Desc.Complete_Stream = Complete_Stream; //short_event writes name/text into mmt_stream + Open_Buffer_Init(&Desc); + Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, desc_loop_len); + Open_Buffer_Finalize(&Desc); + Skip_XX(desc_loop_len, "descriptors"); + } + Element_End0(); + + Complete_Stream->EitEventId = event_id; + Complete_Stream->EitStartDate = start_date; + Complete_Stream->EitStartTime = start_time; + Complete_Stream->EitDuration = duration; + Complete_Stream->EitParsed = true; +} + +//--------------------------------------------------------------------------- +void File_Mmt::MhSdt() +{ + //SDT header (8 bytes) then a services loop, then a trailing CRC32. The + //service is matched to the present-EIT service_id when the container knows + //it, else the first service with a name wins. + if (Element_Size - Element_Offset < 8 + 4) + { + Skip_XX(Element_Size - Element_Offset, "Data"); + return; + } + int16u tlv_stream_id; + Get_B2 (tlv_stream_id, "TLV_stream_id"); + BS_Begin(); + Skip_S1(2, "reserved"); + Skip_S1(5, "version_number"); + Skip_SB( "current_next_indicator"); + BS_End(); + Skip_B1( "section_number"); + Skip_B1( "last_section_number"); + Skip_B2( "original_network_id"); + Skip_B1( "reserved_future_use"); + if (Complete_Stream) + Complete_Stream->TlvStreamId = tlv_stream_id; + + int64u End = Element_Size >= 4 ? Element_Size - 4 : Element_Size; // exclude CRC32 + while (Element_Offset + 5 <= End) + { + Element_Begin1("service"); + int16u service_id, dll_word; + Get_B2 (service_id, "service_id"); + int8u running_status; + BS_Begin(); + Skip_S1(3, "reserved_future_use"); + Skip_S1(3, "EIT_user_defined_flags"); + Skip_SB( "EIT_schedule_flag"); + Skip_SB( "EIT_present_following_flag"); + Get_S1 (3, running_status, "running_status"); Param_Info1(Mmt_running_status(running_status)); + Skip_SB( "free_CA_mode"); + Get_S2 (12, dll_word, "descriptors_loop_length"); + BS_End(); + int16u desc_len = dll_word; + if (Element_Offset + desc_len > End) + desc_len = (int16u)(End - Element_Offset); + + bool take = Complete_Stream && (Complete_Stream->EitServiceIdFound + ? (service_id == Complete_Stream->EitServiceId) : true); + if (take && desc_len) + { + File_Mmt_Descriptors Desc; + Desc.Complete_Stream = Complete_Stream; + Open_Buffer_Init(&Desc); + Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, desc_len); + Open_Buffer_Finalize(&Desc); + } + if (desc_len) + Skip_XX(desc_len, "descriptors"); + Element_End0(); + + if (take && Complete_Stream && !Complete_Stream->ServiceName.empty()) + { + Complete_Stream->SdtFound = true; + break; + } + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "CRC32"); +} + +} //NameSpace + +#endif //MEDIAINFO_MMTTLV_YES diff --git a/Source/MediaInfo/Multiple/File_Mmt.h b/Source/MediaInfo/Multiple/File_Mmt.h new file mode 100644 index 000000000..3da7bae6e --- /dev/null +++ b/Source/MediaInfo/Multiple/File_Mmt.h @@ -0,0 +1,167 @@ +/* Copyright (c) MediaArea.net SARL. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license that can + * be found in the License.html file in the root of the source tree. + */ + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// MMT signaling tables (MPT / MH-EIT / MH-SDT / MH-TOT). Fed a reassembled +// signaling message by File_MmtTlv; parses the tables and writes results into +// the shared mmt_stream. Analogous to File_Mpeg_Psi for MPEG-TS PSI. +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//--------------------------------------------------------------------------- +#ifndef MediaInfo_File_MmtH +#define MediaInfo_File_MmtH +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/File__Analyze.h" +#include +//--------------------------------------------------------------------------- + +namespace MediaInfoLib +{ + +//*************************************************************************** +// Shared time helpers (used by both the container and the signaling parser). +//*************************************************************************** +const int64s Mmt_JST_Offset_Seconds = 9 * 3600; + +inline int Mmt_Bcd2(int8u b) { return (b >> 4) * 10 + (b & 0x0F); } + +//MJD + 24-bit BCD HHMMSS -> seconds since the Unix epoch (in the table's own +//time zone; the caller removes the JST offset). +inline int64s Mmt_DateTime_To_Seconds(int16u mjd, int32u bcd_hhmmss) +{ + if (mjd == 0 || mjd == 0xFFFF) + return -1; + int64s days = (int64s)mjd - 40587; + int hh = Mmt_Bcd2((int8u)(bcd_hhmmss >> 16)); + int mm = Mmt_Bcd2((int8u)(bcd_hhmmss >> 8)); + int ss = Mmt_Bcd2((int8u)(bcd_hhmmss)); + return days * 86400 + hh * 3600 + mm * 60 + ss; +} + +//*************************************************************************** +// One media/data asset from the MPT asset list, with its decoded descriptors. +//*************************************************************************** +struct asset +{ + int32u Type; //asset_type FourCC (little-endian as read) + int16u PacketId; //location_type 0x00 packet_id, else 0 + bool Superimpose; //stpp: superimposed text (true) vs subtitle (false) + //MH-audio-component descriptor (0x8014), MP4A assets only: + Ztring Language; + Ztring Title; //component description (e.g. main audio / commentary) + bool MainComponent; //-> Default track + int8u Handicapped; //audio_for_handicapped: 0b01 = VI commentary + int8u AudioMode; //component_type & 0x1F (0=none) + int8u SamplingCode; //sampling_rate 3-bit code (0=none) + //Video_Component_Descriptor (0x8010): + int8u VideoResolution; //0=none, 6=2160, 7=4320 + int8u VideoAspect; + int8u VideoScan; //0=interlaced, 1=progressive, 0xFF=none + int8u VideoFrameRate; + //Asset_Group_Descriptor (0x8000): main+backup of a stream share GroupId; level 0 = default. + int GroupId; + int8u SelectionLevel; + asset() : Type(0), PacketId(0), Superimpose(false), + MainComponent(false), Handicapped(0), + AudioMode(0), SamplingCode(0), + VideoResolution(0), VideoAspect(0), VideoScan(0xFF), VideoFrameRate(0), + GroupId(-1), SelectionLevel(0) {} +}; + +//*************************************************************************** +// Shared state the container (File_MmtTlv) owns and the signaling parser +// writes into - analogous to File_Mpeg_Psi's complete_stream. Grows a field +// per migrated table. +//*************************************************************************** +struct mmt_stream +{ + //MH-TOT: authoritative JST clock, as UTC seconds (-1 = not seen). + int64s TotUtc; + bool TotSeen; + + //TLV stream id (the multiplex id, MPEG-TS transport_stream_id analog), from the MH-SDT/MH-EIT + //section header. -1 = not seen. + int32s TlvStreamId; //out + + //MH-SDT: service (channel) name + provider, matched to the present-EIT + //service_id when the container knows it (in), else the first service. + int16u EitServiceId; //in + bool EitServiceIdFound; //in + Ztring ServiceName; //out + Ztring Provider; //out + Ztring ServiceType; //out (decoded MH-service_descriptor service_type) + bool SdtFound; //out + + //MPT: asset list (parsed here, committed by the container), plus the newest + //0x8010 transfer nibble in the table (the container applies the phase gate). + std::vector Assets; + int MptVersion; + int8u TransferLast; //out (0 = none) + bool MptValid; //out + //Video MPU presentation-time span in this MPT, microseconds (-1 = none). In the clear, so it + //works for scrambled video; the container keeps the global min/max. + int64s PtsMinUs; //out + int64s PtsMaxUs; //out + + //MH-EIT present event (parsed here; the container keeps the boundary/hop logic). + bool EitSvcIdFound; //out + int16u EitSvcId; //out + bool EitParsed; //out: a present-section first event was extracted + int16u EitEventId; //out + int16u EitStartDate; //out (MJD) + int32u EitStartTime; //out (BCD HHMMSS) + int32u EitDuration; //out (BCD HHMMSS) + Ztring EitName; //out + Ztring EitText; //out + Ztring EitLanguage; //out + + mmt_stream() + : TotUtc(-1), TotSeen(false), TlvStreamId(-1), + EitServiceId(0), EitServiceIdFound(false), SdtFound(false), + MptVersion(-1), TransferLast(0), MptValid(false), + PtsMinUs(-1), PtsMaxUs(-1), + EitSvcIdFound(false), EitSvcId(0), EitParsed(false), + EitEventId(0), EitStartDate(0), EitStartTime(0), EitDuration(0) + {} +}; + +//*************************************************************************** +// Class File_Mmt +//*************************************************************************** + +class File_Mmt : public File__Analyze +{ +public : + //In - set by the container before feeding a table buffer. + mmt_stream* Complete_Stream; + + File_Mmt(); + +private : + //Buffer - Global + void FileHeader_Parse() override; + + //Buffer - Per element (one signaling table) + void Header_Parse() override; + void Data_Parse() override; + + //Header_Parse stashes the table version byte for Mpt(). + int8u Table_Version; + + //Tables + void Mpt(); + void MhEit(); + void MhTot(); + void MhSdt(); +}; + +} //NameSpace + +#endif diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.cpp b/Source/MediaInfo/Multiple/File_MmtTlv.cpp new file mode 100644 index 000000000..aa2e2472d --- /dev/null +++ b/Source/MediaInfo/Multiple/File_MmtTlv.cpp @@ -0,0 +1,1614 @@ +/* Copyright (c) MediaArea.net SARL. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license that can + * be found in the License.html file in the root of the source tree. + */ + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// MMT protocol over TLV packets (MMT/TLV): an MMTP stream (MPEG Media +// Transport, ISO/IEC 23008-1) framed in TLV packets (ARIB STD-B32) to form a +// stored container. Japanese ISDB-S3 BS/CS 4K/8K broadcasting. +// +// Per-element synchronization re-syncs a stream that does not begin on a +// TLV sync byte. +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//--------------------------------------------------------------------------- +// Pre-compilation +#include "MediaInfo/PreComp.h" +#ifdef __BORLANDC__ + #pragma hdrstop +#endif +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/Setup.h" +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#if defined(MEDIAINFO_MMTTLV_YES) +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/Multiple/File_MmtTlv.h" +#include "MediaInfo/Multiple/File_Mmt.h" +#include "MediaInfo/TimeCode.h" //Date_MJD(), Time_BCD() +#if defined(MEDIAINFO_HEVC_YES) + #include "MediaInfo/Video/File_Hevc.h" +#endif +#if defined(MEDIAINFO_AAC_YES) + #include "MediaInfo/Audio/File_Aac.h" +#endif +//--------------------------------------------------------------------------- + +namespace MediaInfoLib +{ + +// Shared VUI strings (File_Mpegv.cpp) so descriptor and ES paths agree. +extern const char* Mpegv_colour_primaries(int8u colour_primaries); +extern const char* Mpegv_transfer_characteristics(int8u transfer_characteristics); +extern const char* Mpegv_matrix_coefficients(int8u matrix_coefficients); +// Shared AAC channel strings (File_Aac_Main.cpp), keyed by MPEG-4 channel_configuration. +extern int8u Aac_Channels_Get(int8u ChannelLayout); +extern std::string Aac_ChannelConfiguration_GetString(int8u ChannelLayout); +extern std::string Aac_ChannelConfiguration2_GetString(int8u ChannelLayout); +extern std::string Aac_ChannelLayout_GetString(int8u ChannelLayout, bool IsMpegh3da, bool IsTip); + +//*************************************************************************** +// Constants +//*************************************************************************** + +namespace +{ + // TLV packet types + const int8u TLV_HEADER_BYTE = 0x7F; + const int8u TLV_IPV4_PACKET = 0x01; + const int8u TLV_IPV6_PACKET = 0x02; + const int8u TLV_HEADER_COMPRESSED_IP_PACKET = 0x03; + const int8u TLV_TRANSMISSION_CONTROL_PACKET = 0xFE; + const int8u TLV_NULL_PACKET = 0xFF; + + // Header-compression context identifiers + const int8u CID_PARTIAL_IPV4_PARTIAL_UDP = 0x20; + const int8u CID_IPV4_HEADER = 0x21; + const int8u CID_PARTIAL_IPV6_PARTIAL_UDP = 0x60; + const int8u CID_NO_COMPRESSED_HEADER = 0x61; + + // Compressed-IP partial-header skip lengths: the compressed form + // drops the first 4 UDP and first 2 IPv6 bytes. + const size_t PARTIAL_UDP_HEADER_LENGTH = 8 - 4; // 4 + const size_t PARTIAL_IPV6_HEADER_LENGTH = 40 - 2; // 38 + + // MMTP payload types + const int8u MMTP_PAYLOAD_MPU = 0x00; + const int8u MMTP_PAYLOAD_SIGNALING = 0x02; + + // Signaling message ids + const int16u MSG_PA_MESSAGE = 0x0000; + const int16u MSG_M2_SECTION = 0x8000; + + // Table ids + const int8u TABLE_MPT = 0x20; // MMT Package Table + const int8u TABLE_ECM_0 = 0x82; // ECM (CAS active) + const int8u TABLE_ECM_1 = 0x83; // ECM (alt) + const int8u TABLE_MH_EIT = 0x8B; // MH-EIT[p/f] + const int8u TABLE_MH_SDT = 0x9F; // MH-SDT (service name) + const int8u TABLE_MH_TOT = 0xA1; // MH-TOT (current JST) + + // Descriptor tags + const int16u DESC_MH_SHORT_EVENT = 0xF001; + + // asset_type FourCCs, read little-endian + const int32u ASSET_HEV1 = 0x31766568; // 'hev1' (VPS/SPS/PPS in the MFU) + const int32u ASSET_HVC1 = 0x31637668; // 'hvc1' (VPS/SPS/PPS in MPU metadata) + const int32u ASSET_MP4A = 0x6134706D; // 'mp4a' + const int32u ASSET_STPP = 0x70707473; // 'stpp' + + // Stop scanning after this many packets, so a multi-GB file is not read + // end-to-end when the present EIT never arrives. + const int64u GIVE_UP_AFTER_PACKETS = 200000; + + // Feeding stops on SPS/ASC, so this only bounds a no-parse stream. Sized for + // 8K, whose sparse RAPs can put the first in-band SPS tens of MB in. + const int64u MEDIA_BYTES_BUDGET = 64 * 1024 * 1024; + + // A scrambled PID feeds nothing decodable; give up after this many MPUs so + // the probe (and tail) still finish. + const int64u SCRAMBLED_GIVEUP_MPU = 64; + // Bytes fed that count as readable (not encrypted). Clear HEVC feeds 100s of + // KB; a scrambled payload ~0, a spurious AAC accept a few hundred. + const int64u READABLE_MIN_BYTES = 4096; + + const int64u BOUNDARY_HOP_BYTES = 8 * 1024 * 1024; + const int BOUNDARY_MAX_HOPS = 8; + // Present event with under this long left at "now" is the previous program's + // tail, not the stream's main program. EPG times are minute-granular. + const int64s BOUNDARY_GUARD_SECONDS = 60; + + // Signaling near an event's start is unsettled (observed to settle within + // this); scan this long past the start, newest wins. Also the no-EIT give-up. + const int64s BOUNDARY_WINDOW_SECONDS = 75; + + // First clock this far before the present-event start = not this event's boundary. + // EPG times are minute-granular. + const int64s BOUNDARY_MISALIGN_SECONDS = 60; + + + const int64u TAIL_PROBE_BYTES = 4 * 1024 * 1024; + + // The boundary window has passed for this committed start: the values (and the p/f + // itself) are stable. Unknown timing is the caller's decision. + inline bool boundary_window_passed(int64s eit_start, int64s now_first, int64s now_utc) + { + if (eit_start < 0 || now_first < 0 || now_utc < 0) + return false; + int64s since_start = now_first - eit_start; + if (since_start < -BOUNDARY_MISALIGN_SECONDS || since_start > BOUNDARY_WINDOW_SECONDS) + return true; + return now_utc - eit_start >= BOUNDARY_WINDOW_SECONDS; + } + + // MH-SDT recurs less often than MPT/EIT, so scan this far past core + // completion to catch it. + const int64u SDT_GRACE_PACKETS = 20000; + + // A recording that begins at its program start has the present event's start + // ~= the first clock, so the boundary window would only settle after ~75 s of + // stream (hundreds of MB at 8K). The present event is already captured at core + // completion and the tail probe fixes the duration, so cap the settle wait: + // finalize this far past core completion even if the window has not passed. + const int64u SETTLE_GRACE_PACKETS = 5000; + + const int64s NTP_UNIX_EPOCH_DELTA = 2208988800LL; + + // Bounds checked by caller. + inline int16u RB16(const int8u* p) { return ((int16u)p[0] << 8) | p[1]; } + inline int32u RB24(const int8u* p) + { return ((int32u)p[0] << 16) | ((int32u)p[1] << 8) | p[2]; } + inline int32u RB32(const int8u* p) + { return ((int32u)p[0] << 24) | ((int32u)p[1] << 16) | ((int32u)p[2] << 8) | p[3]; } + inline int32u RL32(const int8u* p) + { return ((int32u)p[3] << 24) | ((int32u)p[2] << 16) | ((int32u)p[1] << 8) | p[0]; } + inline int64u RB64(const int8u* p) + { return ((int64u)RB32(p) << 32) | RB32(p + 4); } + + // NTP 32.32 -> us. The epoch offset cancels in a duration, so it is not + // subtracted. + inline int64s Ntp32_32_To_Us(int64u ntp) + { + int64u sec = ntp >> 32; + int64u frac = ntp & 0xFFFFFFFFULL; + return (int64s)(sec * 1000000ULL + (frac * 1000000ULL) / 0x100000000ULL); + } + + inline bool Is_Tlv_Type(int8u t) + { + return t == TLV_IPV4_PACKET || t == TLV_IPV6_PACKET + || t == TLV_HEADER_COMPRESSED_IP_PACKET + || t == TLV_TRANSMISSION_CONTROL_PACKET || t == TLV_NULL_PACKET; + } + + // Video_Component_Descriptor geometry (0x8010, STD-B60). + inline int VideoResolutionHeight(int8u code) + { + switch (code) + { + case 1: return 180; case 2: return 240; case 3: return 480; + case 4: return 720; case 5: return 1080; case 6: return 2160; case 7: return 4320; + default: return 0; + } + } + inline float64 VideoFrameRateFps(int8u code) + { + switch (code) + { + case 1: return 15; case 2: return 24000.0 / 1001; + case 3: return 24; case 4: return 25; + case 5: return 30000.0 / 1001; case 6: return 30; + case 7: return 50; case 8: return 60000.0 / 1001; + case 9: return 60; case 10: return 100; + case 11: return 120000.0 / 1001; case 12: return 120; + default: return 0; + } + } + + // MH-audio-component sampling_rate 3-bit code -> Hz. 0/4 reserved. + inline int32u AudioSamplingRate(int8u code) + { + switch (code) + { + case 0x1: return 16000; + case 0x2: return 22050; + case 0x3: return 24000; + case 0x5: return 32000; + case 0x6: return 44100; + case 0x7: return 48000; + default: return 0; + } + } + + // MH-audio-component audio_mode (ARIB STD-B60 Table 7-60) -> MPEG-4 channel_configuration, + // so the shared Aac_* strings apply. 0 = leave to the ES parser. + inline int8u AudioModeToAacConfig(int8u mode) + { + switch (mode) + { + case 0x01: return 1; // 1/0 mono + case 0x02: return 8; // 1/0+1/0 dual mono + case 0x03: return 2; // 2/0 stereo + case 0x05: return 3; // 3/0 + case 0x07: return 4; // 3/1 + case 0x08: return 5; // 3/2 (5.0) + case 0x09: return 6; // 3/2+LFE (5.1) + case 0x0C: return 7; // 5/2.1 (7.1) + case 0x11: return 13; // 22.2 + default: return 0; + } + } + +} + +//*************************************************************************** +// Constructor/Destructor +//*************************************************************************** + +//--------------------------------------------------------------------------- +File_MmtTlv::File_MmtTlv() +: File__Analyze() +{ + //Configuration + MustSynchronize = true; + + //Init + Eit_Present_Found = false; + Eit_EventId = 0; + Eit_StartDate = 0; + Eit_StartTime = 0; + Eit_Duration = 0; + Eit_ServiceId = 0; + Eit_ServiceId_Found = false; + Mpt_Found = false; + Mpt_AssetCount = -1; + Mpt_Version = -1; + Eit_Start_Utc = -1; + Transfer_Last = 0xFF; + Sdt_Found = false; + Tlv_Stream_Id = -1; + Ecm_Seen = false; + Packets_Since_Accept = 0; + Core_Done_At = (int64u)-1; + Media_Done_Utc = -1; + Media_Probe_Done = false; + Media_Bytes = 0; + Now_Utc = -1; + Tot_Seen = false; + Now_First = -1; + Now_Last = -1; + Pts_First_Us = -1; + Pts_Last_Us = -1; + Pts_Last_At_Tail = -1; + Eit_Boundary_Hops = 0; + Phase = Phase_Scan; +} + +//*************************************************************************** +// Streams management +//*************************************************************************** + +//--------------------------------------------------------------------------- +bool File_MmtTlv::PidEncrypted(int16u PacketId) const +{ + if (!PacketId || !ScrambledPids.count(PacketId)) + return false; + //Bytes fed, not the accept flag: clear HEVC can finish on the byte budget + //without IsAccepted, and File_Aac can accept a garbage LOAS frame. A readable + //PID was descrambled in place, not encrypted. + std::map::const_iterator It = MediaParsers.find(PacketId); + if (It != MediaParsers.end()) + { + if (It->second.Fed >= READABLE_MIN_BYTES) + return false; + } + else + { + //No ES parser here (e.g. subtitle): readable iff any A/V parser was. + for (std::map::const_iterator J = MediaParsers.begin(); + J != MediaParsers.end(); ++J) + if (J->second.Fed >= READABLE_MIN_BYTES) + return false; + } + //Gate on ECM: before it, a descrambled file's still-scrambled lead-in is not + //yet encrypted. + return Ecm_Seen; +} + +//--------------------------------------------------------------------------- +void File_MmtTlv::Streams_Fill() +{ + //Format identification. Only General_Format is set here; the extensions and the + //descriptive info come from the format database (Format.csv -> MediaInfo_Config_Automatic). + Fill(Stream_General, 0, General_Format, "MMT/TLV"); + + //TLV stream id (transport_stream_id analog), as File_MpegTs exposes it: numeric + hex string. + if (Tlv_Stream_Id >= 0) + { + Fill(Stream_General, 0, General_ID, Tlv_Stream_Id, 10, true); + Fill(Stream_General, 0, General_ID_String, Get_Hex_ID((int32u)Tlv_Stream_Id), true); + } + + //A/V/subtitle streams from the MPT asset list. + //The Asset_Group_Descriptor (STD-B60 7.4.3.1) groups the main and robust-backup versions of a + //stream under one group_identification (rain fade); selection_level 0 is the default. Expose + //the group as the AlternateGroup and Default the level-0 member. Absent it GroupId is -1, so + //Default falls back to the main_component flag alone. + for (size_t i = 0; i < Assets.size(); ++i) + { + const asset& A = Assets[i]; + switch (A.Type) + { + case ASSET_HEV1: + case ASSET_HVC1: + Stream_Prepare(Stream_Video); + Fill(Stream_Video, StreamPos_Last, Video_Format, "HEVC"); + Fill(Stream_Video, StreamPos_Last, Video_CodecID, A.Type == ASSET_HVC1 ? "hvc1" : "hev1"); + if (A.PacketId) + { + Fill(Stream_Video, StreamPos_Last, Video_ID, A.PacketId, 10); + Fill(Stream_Video, StreamPos_Last, Video_ID_String, Get_Hex_ID(A.PacketId), true); // MMT packet_id is hex-native + } + if (PidEncrypted(A.PacketId)) + Fill(Stream_Video, StreamPos_Last, "Encryption", "Encrypted"); + if (A.GroupId >= 0) + { + Fill(Stream_Video, StreamPos_Last, Video_AlternateGroup, A.GroupId); + if (A.SelectionLevel == 0) + Fill(Stream_Video, StreamPos_Last, Video_Default, "Yes"); + } + { + std::map::iterator It = MediaParsers.find(A.PacketId); + if (It != MediaParsers.end()) + { + It->second.StreamPos = StreamPos_Last; + //Seed geometry from the descriptor; the ES overrides it at finish. + int h = VideoResolutionHeight(A.VideoResolution); + if (h) + { + It->second.DescHeight = h; + if (A.VideoAspect == 1) + It->second.DescWidth = h * 4 / 3; + else if (A.VideoAspect == 2 || A.VideoAspect == 3) + It->second.DescWidth = h * 16 / 9; + } + It->second.DescFrameRate = VideoFrameRateFps(A.VideoFrameRate); + It->second.DescScan = A.VideoScan; + } + } + break; + case ASSET_MP4A: + Stream_Prepare(Stream_Audio); + Fill(Stream_Audio, StreamPos_Last, Audio_Format, "AAC"); + Fill(Stream_Audio, StreamPos_Last, Audio_CodecID, "mp4a"); + if (A.PacketId) + { + Fill(Stream_Audio, StreamPos_Last, Audio_ID, A.PacketId, 10); + Fill(Stream_Audio, StreamPos_Last, Audio_ID_String, Get_Hex_ID(A.PacketId), true); + } + //From the MH-audio-component descriptor (0x8014). + if (!A.Language.empty()) + Fill(Stream_Audio, StreamPos_Last, Audio_Language, A.Language); + if (!A.Title.empty()) + Fill(Stream_Audio, StreamPos_Last, Audio_Title, A.Title); + if (A.GroupId >= 0) + Fill(Stream_Audio, StreamPos_Last, Audio_AlternateGroup, A.GroupId); + if (A.MainComponent && A.SelectionLevel == 0) + Fill(Stream_Audio, StreamPos_Last, Audio_Default, "Yes"); + //audio_for_handicapped 0b01 = audio description. Only the VI case maps cleanly. + if (A.Handicapped == 0x01) + { + Fill(Stream_Audio, StreamPos_Last, Audio_ServiceKind, "VI"); + Fill(Stream_Audio, StreamPos_Last, Audio_ServiceKind_String, "Visually Impaired"); + } + //Seed channels from the descriptor (shared Aac_* strings); the ES overrides upward. + { + int8u Cfg = AudioModeToAacConfig(A.AudioMode); + int DescCh = Aac_Channels_Get(Cfg); + if (DescCh) + { + Fill(Stream_Audio, StreamPos_Last, Audio_Channel_s_, DescCh); + Ztring Pos; Pos.From_UTF8(Aac_ChannelConfiguration_GetString(Cfg)); + if (!Pos.empty()) + Fill(Stream_Audio, StreamPos_Last, Audio_ChannelPositions, Pos); + Ztring Pos2; Pos2.From_UTF8(Aac_ChannelConfiguration2_GetString(Cfg)); + if (!Pos2.empty()) + Fill(Stream_Audio, StreamPos_Last, Audio_ChannelPositions_String2, Pos2); + Ztring Lay; Lay.From_UTF8(Aac_ChannelLayout_GetString(Cfg, false, false)); + if (!Lay.empty()) + Fill(Stream_Audio, StreamPos_Last, Audio_ChannelLayout, Lay); + } + //Encrypted: no ES detail, so the descriptor is all we have. + if (PidEncrypted(A.PacketId)) + { + Fill(Stream_Audio, StreamPos_Last, "Encryption", "Encrypted"); + int32u SamplingRate = AudioSamplingRate(A.SamplingCode); + if (SamplingRate) + Fill(Stream_Audio, StreamPos_Last, Audio_SamplingRate, SamplingRate); + } + { + std::map::iterator It = MediaParsers.find(A.PacketId); + if (It != MediaParsers.end()) + { + It->second.StreamPos = StreamPos_Last; + It->second.DescCh = DescCh; + It->second.DescCfg = Cfg; + } + } + } + break; + case ASSET_STPP: + Stream_Prepare(Stream_Text); + Fill(Stream_Text, StreamPos_Last, Text_Format, "TTML"); + Fill(Stream_Text, StreamPos_Last, Text_CodecID, "stpp"); + if (A.PacketId) + { + Fill(Stream_Text, StreamPos_Last, Text_ID, A.PacketId, 10); + Fill(Stream_Text, StreamPos_Last, Text_ID_String, Get_Hex_ID(A.PacketId), true); + } + Fill(Stream_Text, StreamPos_Last, Text_Format_Profile, + A.Superimpose ? "Superimpose" : "Subtitle"); + if (!A.Language.empty()) //ISO_639 from the MH-data-component descriptor (0x8020) + Fill(Stream_Text, StreamPos_Last, Text_Language, A.Language); + if (PidEncrypted(A.PacketId)) + Fill(Stream_Text, StreamPos_Last, "Encryption", "Encrypted"); + break; + default: + break; + } + } + + if (Sdt_Found && !Sdt_ServiceName.empty()) + { + Fill(Stream_General, 0, General_ServiceName, Sdt_ServiceName); + if (!Sdt_Provider.empty()) + Fill(Stream_General, 0, General_ServiceProvider, Sdt_Provider); + if (!Sdt_ServiceType.empty()) + Fill(Stream_General, 0, General_ServiceType, Sdt_ServiceType); + } + + //Present event -> a Menu chapter, as File_MpegTs renders DVB/ATSC EPG: the + //entry key is the scheduled start (UTC), the value a "/"-delimited record + //(name / text / content / rating / duration / running_status). + if (Eit_Present_Found) + { + Stream_Prepare(Stream_Menu); + Fill(Stream_Menu, StreamPos_Last, Menu_ID, Eit_EventId, 10); + Fill(Stream_Menu, StreamPos_Last, Menu_ID_String, Get_Hex_ID(Eit_EventId), true); + if (!Eit_EventName.empty()) + Fill(Stream_General, 0, General_Title, Eit_EventName); + if (!Eit_Language.empty()) + Fill(Stream_Menu, StreamPos_Last, Menu_Language, Eit_Language); + if (Sdt_Found && !Sdt_ServiceName.empty()) + Fill(Stream_Menu, StreamPos_Last, Menu_ServiceName, Sdt_ServiceName); + + int64s Start = Mmt_DateTime_To_Seconds(Eit_StartDate, Eit_StartTime); // JST + if (Start >= 0) + { + Ztring Time = Ztring().Date_From_Seconds_1970(Start - Mmt_JST_Offset_Seconds); + Time.FindAndReplace(__T("UTC "), __T("")); + Time += __T(" UTC"); + Ztring Dur; Dur.From_UTF8(Time_BCD(Eit_Duration).c_str()); + Ztring Event = Eit_EventName + __T(" / ") + Eit_EventText + __T(" / / / ") + Dur + __T(" / "); + Fill(Stream_Menu, StreamPos_Last, Menu_Chapters_Pos_Begin, Count_Get(Stream_Menu, StreamPos_Last), 10, true); + Fill(Stream_Menu, StreamPos_Last, Time.To_UTF8().c_str(), Event, true); + Fill(Stream_Menu, StreamPos_Last, Menu_Chapters_Pos_End, Count_Get(Stream_Menu, StreamPos_Last), 10, true); + } + } +} + +//--------------------------------------------------------------------------- +void File_MmtTlv::Streams_Finish() +{ + for (std::map::iterator It = MediaParsers.begin(); + It != MediaParsers.end(); ++It) + { + media_parser& M = It->second; + if (!M.Parser) + continue; + Open_Buffer_Finalize(M.Parser); + //Non-erasing: keep the container-level Format/CodecID/ID, add codec detail. + Merge(*M.Parser, M.StreamKind, 0, M.StreamPos, false); + //Keep the descriptor channels when the decoded ES read fewer (a boundary transient). + if (M.StreamKind == Stream_Audio && M.DescCh) + { + int64u EsCh = Retrieve_Const(Stream_Audio, M.StreamPos, Audio_Channel_s_).To_int64u(); + if (EsCh < (int64u)M.DescCh) + { + Fill(Stream_Audio, M.StreamPos, Audio_Channel_s_, (int64u)M.DescCh, 10, true); + Ztring Pos; Pos.From_UTF8(Aac_ChannelConfiguration_GetString(M.DescCfg)); + if (!Pos.empty()) + Fill(Stream_Audio, M.StreamPos, Audio_ChannelPositions, Pos, true); + Ztring Pos2; Pos2.From_UTF8(Aac_ChannelConfiguration2_GetString(M.DescCfg)); + if (!Pos2.empty()) + Fill(Stream_Audio, M.StreamPos, Audio_ChannelPositions_String2, Pos2, true); + Ztring Lay; Lay.From_UTF8(Aac_ChannelLayout_GetString(M.DescCfg, false, false)); + if (!Lay.empty()) + Fill(Stream_Audio, M.StreamPos, Audio_ChannelLayout, Lay, true); + } + } + //From 0x8010: transfer overrides the HEVC VUI; geometry and colour fill the ES's gaps. + if (M.StreamKind == Stream_Video) + { + if (Transfer_Last >= 1 && Transfer_Last < 8) + { + //Descriptor -> VUI code (STD-B60 Table 7-51). The newest descriptor wins over + //the ES: a head-bounded probe may only have decoded the opening segment's SPS. + int8u vui = Transfer_Last == 5 ? 18 : Transfer_Last == 4 ? 16 : Transfer_Last == 3 ? 14 : Transfer_Last == 2 ? 11 : 1; + Fill(Stream_Video, M.StreamPos, Video_transfer_characteristics, Mpegv_transfer_characteristics(vui), Unlimited, true, true); + if (Transfer_Last == 3 || Transfer_Last == 4 || Transfer_Last == 5) + { + if (Retrieve_Const(Stream_Video, M.StreamPos, Video_colour_primaries).empty()) + Fill(Stream_Video, M.StreamPos, Video_colour_primaries, Mpegv_colour_primaries(9)); + if (Retrieve_Const(Stream_Video, M.StreamPos, Video_matrix_coefficients).empty()) + Fill(Stream_Video, M.StreamPos, Video_matrix_coefficients, Mpegv_matrix_coefficients(9)); + } + } + //Prefer the ES; fill only the gaps it left. + if (M.DescHeight && Retrieve_Const(Stream_Video, M.StreamPos, Video_Height).empty()) + { + Fill(Stream_Video, M.StreamPos, Video_Height, M.DescHeight); + if (M.DescWidth) + Fill(Stream_Video, M.StreamPos, Video_Width, M.DescWidth); + } + if (M.DescFrameRate > 0 && Retrieve_Const(Stream_Video, M.StreamPos, Video_FrameRate).empty()) + Fill(Stream_Video, M.StreamPos, Video_FrameRate, M.DescFrameRate, 3); + if (M.DescScan != 0xFF && Retrieve_Const(Stream_Video, M.StreamPos, Video_ScanType).empty()) + Fill(Stream_Video, M.StreamPos, Video_ScanType, M.DescScan ? "Progressive" : "Interlaced"); + } + delete M.Parser; + M.Parser = NULL; + } + MediaParsers.clear(); + + //Stream start wall clock (UTC), the same field File_MpegTs uses. + if (Now_First >= 0) + Fill(Stream_General, 0, General_Duration_Start, + Ztring().Date_From_Seconds_1970(Now_First)); + + //Stream span, distinct from the Menu Duration (scheduled event length). Prefer the + //MPU-PTS span (sub-second), but when the NTP clock ran materially longer the tail PTS was + //never read - a scrambled service capture whose end blocks don't decode, or an early + //give-up - so the whole-second clock is the true span, not the truncated PTS. + float64 pts_span_ms = (Pts_First_Us >= 0 && Pts_Last_Us > Pts_First_Us) + ? (float64)(Pts_Last_Us - Pts_First_Us) / 1000.0 : -1; // us -> ms + float64 ntp_span_ms = (Now_First >= 0 && Now_Last > Now_First) + ? (float64)(Now_Last - Now_First) * 1000.0 : -1; // s -> ms + float64 span_ms; + if (pts_span_ms > 0 && (ntp_span_ms < 0 || ntp_span_ms <= pts_span_ms + 2000.0)) + span_ms = pts_span_ms; + else + span_ms = ntp_span_ms; + if (span_ms > 0) + { + //Float ms keeps the sub-second precision. + Fill(Stream_General, 0, General_Duration, span_ms, 3); + //Same span per A/V track -> correct per-stream and overall bitrate. + for (size_t Pos = 0; Pos < Count_Get(Stream_Video); ++Pos) + Fill(Stream_Video, Pos, Video_Duration, span_ms, 3); + for (size_t Pos = 0; Pos < Count_Get(Stream_Audio); ++Pos) + Fill(Stream_Audio, Pos, Audio_Duration, span_ms, 3); + //End clock = start + span, so Start/Duration/End stay consistent (the clock + //is whole-second while the span is sub-second PTS, so the last raw clock can + //differ by a second or two). + if (Now_First >= 0) + Fill(Stream_General, 0, General_Duration_End, + Ztring().Date_From_Seconds_1970(Now_First + (int64s)(span_ms / 1000.0 + 0.5))); + } +} + +//*************************************************************************** +// Buffer - File header (format detection / probe) +//*************************************************************************** + +//--------------------------------------------------------------------------- +bool File_MmtTlv::FileHeader_Begin() +{ + //Scan up to 100 TLV packets, require recognized + //compressed-IP / NULL packets. Resync byte-wise (a stream may not start on a + //sync byte). + if (Buffer_Size < 188) + return false; + + size_t i = 0; + int processed = 0; + int recognized = 0; + + while (i + 4 < Buffer_Size && processed < 100) + { + if (Buffer[i] != TLV_HEADER_BYTE) + { + ++i; + continue; + } + ++processed; + + int8u packet_type = Buffer[i + 1]; + int16u data_length = RB16(Buffer + i + 2); + i += 4; + + if (packet_type == TLV_HEADER_COMPRESSED_IP_PACKET) + { + if (data_length >= 3 && i + 2 < Buffer_Size) + { + switch (Buffer[i + 2]) + { + case CID_PARTIAL_IPV4_PARTIAL_UDP: + case CID_IPV4_HEADER: + case CID_PARTIAL_IPV6_PARTIAL_UDP: + case CID_NO_COMPRESSED_HEADER: + ++recognized; + } + } + i += data_length; + } + else if (packet_type == TLV_NULL_PACKET) + { + bool AllFF = true; + for (size_t j = i; j < i + data_length && j < Buffer_Size; ++j) + if (Buffer[j] != 0xFF) { AllFF = false; break; } + if (AllFF) + ++recognized; + i += data_length; + } + else if (packet_type == TLV_IPV6_PACKET || packet_type == TLV_IPV4_PACKET) + { + i += data_length; + } + // else: unknown byte after a false 0x7F; keep scanning byte-wise. + } + + //Ratio test rejects files that merely contain a stray 0x7F. + if (processed >= 4 && recognized > 0 + && recognized * 100 / (processed < 10 ? 10 : processed) >= 25) + { + Accept("MmtTlv"); + return true; + } + + //Not enough confidence yet. A stream may begin with a long non-TLV region; + //keep requesting data until a bounded search budget is spent, then give up. + const int64u DetectionBudget = 512 * 1024; // bytes from start of file + if (File_Offset + Buffer_Size < DetectionBudget && File_Offset + Buffer_Size < File_Size) + return false; //Wait for more data + + Reject("MmtTlv"); + return false; +} + +//*************************************************************************** +// Buffer - Synchro +//*************************************************************************** + +//--------------------------------------------------------------------------- +bool File_MmtTlv::Synchronize() +{ + //Sync byte followed by a known packet type. + while (Buffer_Offset + 4 <= Buffer_Size + && !(Buffer[Buffer_Offset] == TLV_HEADER_BYTE + && Is_Tlv_Type(Buffer[Buffer_Offset + 1]))) + ++Buffer_Offset; + + if (Buffer_Offset + 4 > Buffer_Size) + return false; //Need more data + + return true; +} + +//--------------------------------------------------------------------------- +bool File_MmtTlv::Synched_Test() +{ + if (Buffer_Offset + 4 > Buffer_Size) + return false; + + if (!(Buffer[Buffer_Offset] == TLV_HEADER_BYTE + && Is_Tlv_Type(Buffer[Buffer_Offset + 1]))) + Synched = false; + + return true; +} + +//*************************************************************************** +// Buffer - Per element (one TLV packet) +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_MmtTlv::Header_Parse() +{ + //TLV header: sync(8)=0x7F packet_type(8) data_length(16). + int8u packet_type; + int16u data_length; + Skip_B1( "sync (0x7F)"); + Get_B1 (packet_type, "packet_type"); + Get_B2 (data_length, "data_length"); + + Header_Fill_Size((int64u)4 + data_length); + Header_Fill_Code(packet_type, "TLV packet"); +} + +//--------------------------------------------------------------------------- +void File_MmtTlv::Data_Parse() +{ + ++Packets_Since_Accept; + + //Tail phase: near EOF for the last PTS and clock. Only signaling matters. + if (Phase == Phase_Tail) + { + if (Element_Code == TLV_HEADER_COMPRESSED_IP_PACKET) + Parse_CompressedIp(); + else if (Element_Code == TLV_IPV6_PACKET) + Parse_Ntp(); + Skip_XX(Element_Size - Element_Offset, "Data"); + //With PTS, wait for a fresher one from the tail rather than stopping on + //the earlier clock; else fall back to a distinct last "now". + bool HavePts = (Pts_First_Us >= 0); + bool GotTailPts = (Pts_Last_Us > Pts_Last_At_Tail); + bool Done = HavePts ? GotTailPts : (Now_Last > Now_First); + if (Done || Packets_Since_Accept >= GIVE_UP_AFTER_PACKETS) + { + Phase = Phase_Done; + Finish(); + } + return; + } + + // Settled once the boundary window has passed (from the later of the first clock and the + // present-event start); a file opened mid-program, or with no EPG/clock, is already settled. + auto probe_settled = [&]() -> bool { + if (Eit_Start_Utc < 0 || Now_First < 0 || Now_Utc < 0) + return true; + return boundary_window_passed(Eit_Start_Utc, Now_First, Now_Utc); + }; + bool Settled = probe_settled(); + switch ((int8u)Element_Code) + { + case TLV_HEADER_COMPRESSED_IP_PACKET: + if (!(Mpt_Found && Eit_Present_Found && Media_Probe_Done && Sdt_Found && Settled)) + Parse_CompressedIp(); + break; + case TLV_IPV6_PACKET: + //Advance the clock from NTP until MH-TOT takes over; an NTP-only stream must not freeze. + if (!Tot_Seen) + Parse_Ntp(); + break; + default: + break; + } + + Skip_XX(Element_Size - Element_Offset, "Data"); + + //Probe done once every A/V parser has finished or the budget is spent. + if (Mpt_Found && !Media_Probe_Done) + { + bool AllDone = true; + for (std::map::iterator It = MediaParsers.begin(); + It != MediaParsers.end(); ++It) + if (!It->second.Done) + { + AllDone = false; + break; + } + if (AllDone || Media_Bytes >= MEDIA_BYTES_BUDGET) + Media_Probe_Done = true; + } + if (Mpt_Found && MediaParsers.empty()) + Media_Probe_Done = true; + + //Note when the media probe finished with a clock in hand, to time-box the EIT wait below. + if (Mpt_Found && Media_Probe_Done && Now_Utc >= 0 && Media_Done_Utc < 0) + Media_Done_Utc = Now_Utc; + + //Grace past core completion for the less-frequent MH-SDT. + bool CoreComplete = (Mpt_Found && Eit_Present_Found && Media_Probe_Done); + if (CoreComplete && Core_Done_At == (int64u)-1) + Core_Done_At = Packets_Since_Accept; + + // Recompute: this packet's EIT/TOT may have set the start or advanced the clock. + Settled = probe_settled(); + bool SettledOrTimeout = Settled + || (Core_Done_At != (int64u)-1 + && Packets_Since_Accept - Core_Done_At >= SETTLE_GRACE_PACKETS); + bool ScanComplete = CoreComplete && SettledOrTimeout + && (Sdt_Found + || (Core_Done_At != (int64u)-1 + && Packets_Since_Accept - Core_Done_At >= SDT_GRACE_PACKETS)); + + // No EIT within the boundary window of the media probe (a raw service capture): + // stop the forward scan instead of reading on to the give-up or EOF. + bool NoEpgDone = !ScanComplete && Mpt_Found && Media_Probe_Done && !Eit_Present_Found + && Media_Done_Utc >= 0 && Now_Utc >= 0 + && Now_Utc - Media_Done_Utc >= BOUNDARY_WINDOW_SECONDS; + + // Forward scan spent its packet budget before the boundary window could settle - a scrambled, + // NULL-padded capture whose packet count outruns its timeline (the stuffing inflates the count + // while the clock barely advances). Finalize like a completion rather than reading on to EOF. + bool GaveUp = (Packets_Since_Accept >= GIVE_UP_AFTER_PACKETS); + + if (ScanComplete || NoEpgDone || GaveUp) + { + // A give-up or NoEpgDone stopped short of the end, so the tail is unread; a normal + // completion only needs the tail when the forward scan has not reached it yet. The tail + // probe is what fixes the span: it advances the clock (and PTS, when readable) to the true + // end, so a give-up no longer clamps Duration to the boundary window. + bool TailUnread = Now_First >= 0 && File_Size != (int64u)-1 && File_Size > TAIL_PROBE_BYTES + && ((NoEpgDone || GaveUp) || File_Offset + Buffer_Offset < File_Size - TAIL_PROBE_BYTES); + if (TailUnread) + { + Phase = Phase_Tail; + Pts_Last_At_Tail = Pts_Last_Us; //require a fresher PTS from the tail + Packets_Since_Accept = 0; //tail has its own budget + GoToFromEnd(TAIL_PROBE_BYTES, "MmtTlv"); + return; + } + Finish(); + } +} + +//*************************************************************************** +// Compressed-IP TLV payload -> strip the (partial) IPv6+UDP headers -> MMTP. +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_MmtTlv::Parse_CompressedIp() +{ + if (Element_Size - Element_Offset < 3) + return; + int8u header_type; + Element_Begin1("Compressed IP"); + BS_Begin(); + Skip_S2(12, "context_id"); + Skip_S1( 4, "sequence_number"); + BS_End(); + Get_B1 (header_type, "CID_header_type"); + switch (header_type) + { + case CID_PARTIAL_IPV6_PARTIAL_UDP: + if (Element_Size - Element_Offset < PARTIAL_IPV6_HEADER_LENGTH + PARTIAL_UDP_HEADER_LENGTH) + { Element_End0(); return; } + Skip_XX(PARTIAL_IPV6_HEADER_LENGTH, "partial IPv6 header"); + Skip_XX(PARTIAL_UDP_HEADER_LENGTH, "partial UDP header"); + break; + case CID_NO_COMPRESSED_HEADER: + break; + default: + Element_End0(); + return; // IPv4 variants not carried by MMT signaling here + } + Element_End0(); + + Parse_Mmtp(); +} + +//--------------------------------------------------------------------------- +// MMTP packet header layout. +void File_MmtTlv::Parse_Mmtp() +{ + if (Element_Size - Element_Offset < 12) + return; + + bool packet_counter_flag, extension_flag; + int8u payload_type; + int16u packet_id; + int32u seq_num; + + Element_Begin1("MMTP"); + BS_Begin(); + Skip_S1(2, "version"); + Get_SB ( packet_counter_flag, "packet_counter_flag"); + Skip_S1(2, "FEC_type"); + Skip_S1(1, "reserved"); + Get_SB ( extension_flag, "extension_flag"); + Skip_SB( "RAP_flag"); + Skip_S1(2, "reserved"); + Get_S1 (6, payload_type, "payload_type"); + BS_End(); + Get_B2 (packet_id, "packet_id"); + Skip_B4( "distribute_timestamp"); + Get_B4 (seq_num, "packet_sequence_number"); + if (packet_counter_flag) + { + if (Element_Size - Element_Offset < 4) { Element_End0(); return; } + Skip_B4( "packet_counter"); + } + if (extension_flag) + { + if (Element_Size - Element_Offset < 4) { Element_End0(); return; } + int16u ext_len; + Skip_B2( "extension_header_type"); + Get_B2 (ext_len, "extension_header_length"); + if (Element_Size - Element_Offset < ext_len) { Element_End0(); return; } + //Sub-header chain: (sub_type&0x7FFF)==1 is the scramble sub-header; data[0] bits 4-3 = + //encryption_flag (>=2 means scrambled). Peeked so the PID flag matches the byte pass. + { + const int8u* e = Buffer + Buffer_Offset + (size_t)Element_Offset; + size_t en = ext_len; + while (en >= 4) + { + int16u sub_type = RB16(e) & 0x7FFF; + int16u sub_len = RB16(e + 2); + if ((size_t)4 + sub_len > en) break; + if (sub_type == 0x0001 && sub_len >= 1 && ((e[4] >> 3) & 0x03) >= 2) + ScrambledPids.insert(packet_id); + e += 4 + sub_len; en -= 4 + sub_len; + } + } + Skip_XX(ext_len, "extension_header"); + } + Element_End0(); + + //Payload handed to the still-byte-based inner parsers at the current cursor; Data_Parse trails + //with Skip_XX(Element_Size - Element_Offset) to consume it. + const int8u* P = Buffer + Buffer_Offset + (size_t)Element_Offset; + size_t N = (size_t)(Element_Size - Element_Offset); + if (payload_type == MMTP_PAYLOAD_SIGNALING) + Parse_SignalingMessages(packet_id, seq_num, P, N); + else if (payload_type == MMTP_PAYLOAD_MPU && !Media_Probe_Done) + { + std::map::iterator It = MediaParsers.find(packet_id); + if (It != MediaParsers.end() && !It->second.Done) + { + //The scramble flag can't gate feeding: a stream descrambled in place keeps it set. So + //always probe; encrypted payload just fails the MFU/NAL checks. Give up on a scrambled + //PID feeding nothing so the probe (and tail) finish. + ++It->second.MpuSeen; + Parse_Mpu(packet_id, seq_num, P, N); + if (ScrambledPids.count(packet_id) + && It->second.Fed < READABLE_MIN_BYTES + && It->second.MpuSeen >= SCRAMBLED_GIVEUP_MPU) + It->second.Done = true; + } + } +} + +//*************************************************************************** +// MPU (media) -> MFU data units -> per-asset HEVC/AAC child parser. +// MPU payload -> MFU data units (timed and non-timed). +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_MmtTlv::Parse_Mpu(int16u packet_id, int32u seq_num, const int8u* Data, size_t Size) +{ + (void)Data; (void)Size; // parse at the element cursor (== Data/Size handed by Parse_Mmtp) + if (Element_Size - Element_Offset < 8) + return; + + Element_Begin1("MPU"); + int16u length; + Get_B2 (length, "MPU_payload_length"); + if (length != Element_Size - Element_Offset) // must fill the MMTP payload + { + Element_End0(); + return; + } + int8u fragment_type, fragmentation_indicator; + bool timed_flag, aggregation_flag; + BS_Begin(); + Get_S1 (4, fragment_type, "fragment_type"); + Get_SB ( timed_flag, "timed_flag"); + Get_S1 (2, fragmentation_indicator, "fragmentation_indicator"); + Get_SB ( aggregation_flag, "aggregation_flag"); + BS_End(); + Skip_B1( "fragment_counter"); + Skip_B4( "MPU_sequence_number"); + + if ((aggregation_flag && fragmentation_indicator != 0) || fragment_type != 2 /*MFU*/) + { + Element_End0(); + return; + } + + // Per-DU header before each fragment: + // timed -> movie_fragment_seq(32) sample(32) offset(32) prio(8) dep(8) + // !timed -> item_id(32) + const int64u du_header = timed_flag ? (32 + 32 + 32 + 8 + 8) / 8 : 32 / 8; + + if (aggregation_flag) + { + // Run of length-prefixed COMPLETE DUs. + while (Element_Size - Element_Offset >= 2) + { + int16u du_len; + Get_B2 (du_len, "data_unit_length"); + if (du_len > Element_Size - Element_Offset) + break; + if (du_len >= du_header) + Feed_DataUnit(packet_id, + Buffer + Buffer_Offset + (size_t)Element_Offset + (size_t)du_header, + du_len - (size_t)du_header); + Skip_XX(du_len, "data_unit"); + } + Element_End0(); + return; + } + + if (Element_Size - Element_Offset < du_header) + { + Element_End0(); + return; + } + Skip_XX(du_header, "data_unit_header"); + + // The fragment payload is reassembled across MPUs (buffer-based, like the signaling + // fragments); the trailing Skip_XX in Data_Parse consumes it from the cursor. + const int8u* p = Buffer + Buffer_Offset + (size_t)Element_Offset; + size_t n = (size_t)(Element_Size - Element_Offset); + Element_End0(); + + fragment_assembler& Ass = MfuAssemblers[packet_id]; + + //A seq jump or first-ever sighting drops any partial. + if (Ass.State == fragment_assembler::Init) + Ass.State = fragment_assembler::Skip; + else if (seq_num != Ass.LastSeq + 1) + { + Ass.Data.clear(); + Ass.State = fragment_assembler::Skip; + } + Ass.LastSeq = seq_num; + + switch (fragmentation_indicator) + { + case 0: // NOT_FRAGMENTED (complete DU) + Ass.State = fragment_assembler::NotStarted; + Ass.Data.clear(); + Feed_DataUnit(packet_id, p, n); + break; + case 1: // FIRST + Ass.State = fragment_assembler::InFragment; + Ass.Data.assign(p, p + n); + break; + case 2: // MIDDLE + if (Ass.State == fragment_assembler::InFragment) + Ass.Data.insert(Ass.Data.end(), p, p + n); + break; + case 3: // LAST + if (Ass.State == fragment_assembler::InFragment) + { + Ass.Data.insert(Ass.Data.end(), p, p + n); + if (!Ass.Data.empty()) + Feed_DataUnit(packet_id, &Ass.Data[0], Ass.Data.size()); + } + Ass.Data.clear(); + Ass.State = fragment_assembler::NotStarted; + break; + default: + break; + } +} + +//--------------------------------------------------------------------------- +// Child ES parser factory. asset_type (STD-B60 MPT) -> MediaInfo parser + stream +// kind + DU framing. STD-B60 signals HEVC video (hev1) and MPEG-4 AAC audio +// (mp4a); stpp (TTML) and aapp carry no ES parser. A newly assigned asset_type +// is a single added case. +bool File_MmtTlv::Create_MediaParser(int32u asset_type, media_parser& M) +{ + switch (asset_type) + { + case ASSET_HEV1: + case ASSET_HVC1: // same File_Hevc path; for hvc1 the MFU carries slices without the + // parameter sets (those are in the MPU metadata), but geometry/colour + // still come from the 0x8010 video-component descriptor + #if defined(MEDIAINFO_HEVC_YES) + { + File_Hevc* Parser = new File_Hevc; + Parser->FrameIsAlwaysComplete = true; + M.Parser = Parser; + M.StreamKind = Stream_Video; + M.Framing = Es_Nal; + Open_Buffer_Init(Parser); + return true; + } + #else + return false; + #endif + case ASSET_MP4A: + #if defined(MEDIAINFO_AAC_YES) + { + File_Aac* Parser = new File_Aac; + Parser->Mode = File_Aac::Mode_LATM; + M.Parser = Parser; + M.StreamKind = Stream_Audio; + M.Framing = Es_Loas; + Open_Buffer_Init(Parser); + return true; + } + #else + return false; + #endif + default: + return false; // stpp/aapp and any un-assigned asset_type: no ES parser + } +} + +//--------------------------------------------------------------------------- +// A COMPLETE data unit, framed for the child parser (Annex-B / LOAS) and fed. +void File_MmtTlv::Feed_DataUnit(int16u packet_id, const int8u* Data, size_t Size) +{ + std::map::iterator It = MediaParsers.find(packet_id); + if (It == MediaParsers.end()) + return; + media_parser& M = It->second; + if (M.Done || !M.Parser) + return; + + std::vector Frame; + if (M.Framing == Es_Loas) + { + // The DU is the LATM payload; prepend a LOAS sync header. + if (Size == 0 || (Size >> 13)) + return; + Frame.reserve(3 + Size); + Frame.push_back(0x56); + Frame.push_back((int8u)(0xE0 | (Size >> 8))); + Frame.push_back((int8u)(Size & 0xFF)); + Frame.insert(Frame.end(), Data, Data + Size); + } + else + { + // The DU is be32 length + NAL; emit Annex-B. + if (Size < 4) + return; + int32u nal_size = RB32(Data); + const int8u* nal = Data + 4; + size_t nal_n = Size - 4; + if (nal_size != nal_n) + return; + Frame.reserve(3 + nal_n); + Frame.push_back(0x00); + Frame.push_back(0x00); + Frame.push_back(0x01); + Frame.insert(Frame.end(), nal, nal + nal_n); + } + + Open_Buffer_Continue(M.Parser, &Frame[0], Frame.size()); + M.Fed += Frame.size(); + Media_Bytes += Frame.size(); + + if (M.Parser->Status[IsAccepted] || M.Parser->Status[IsFinished]) + M.Done = true; +} + +//--------------------------------------------------------------------------- +// Reassembly keyed by packet_id; aggregation and fragmentation are mutually +// exclusive. A completed message goes to Parse_SignalingMessage. +void File_MmtTlv::Parse_SignalingMessages(int16u packet_id, int32u seq_num, const int8u* Data, size_t Size) +{ + if (Size < 2) + return; + + int8u b = Data[0]; + int fragmentation_ind = b >> 6; // 0 complete, 1 first, 2 middle, 3 last + bool length_extension = (b >> 1) & 1; + bool aggregation = b & 1; + + const int8u* p = Data + 2; // fragmentation byte + fragment counter byte + size_t n = Size - 2; + + fragment_assembler& Ass = Assemblers[packet_id]; + + //A seq jump or first-ever sighting drops any partial; a complete/first + //fragment recovers the run. + if (Ass.State == fragment_assembler::Init) + Ass.State = fragment_assembler::Skip; + else if (seq_num != Ass.LastSeq + 1) + { + Ass.Data.clear(); + Ass.State = fragment_assembler::Skip; + } + Ass.LastSeq = seq_num; + + if (!aggregation) + { + switch (fragmentation_ind) + { + case 0: // NOT_FRAGMENTED (complete) + Ass.State = fragment_assembler::NotStarted; + Ass.Data.clear(); + Parse_SignalingMessage(p, n); + break; + case 1: // FIRST + Ass.State = fragment_assembler::InFragment; + Ass.Data.assign(p, p + n); + break; + case 2: // MIDDLE + if (Ass.State == fragment_assembler::InFragment) + Ass.Data.insert(Ass.Data.end(), p, p + n); + break; + case 3: // LAST + if (Ass.State == fragment_assembler::InFragment) + { + Ass.Data.insert(Ass.Data.end(), p, p + n); + if (!Ass.Data.empty()) + Parse_SignalingMessage(&Ass.Data[0], Ass.Data.size()); + } + Ass.Data.clear(); + Ass.State = fragment_assembler::NotStarted; + break; + default: + break; + } + return; + } + + //Aggregated: length-prefixed COMPLETE messages. + if (fragmentation_ind != 0) + return; + Ass.State = fragment_assembler::NotStarted; + Ass.Data.clear(); + while (n > 0) + { + int32u length; + if (length_extension) + { + if (n < 4) return; + length = RB32(p); p += 4; n -= 4; + } + else + { + if (n < 2) return; + length = RB16(p); p += 2; n -= 2; + } + if (length > n) return; + Parse_SignalingMessage(p, (size_t)length); + p += length; n -= length; + } +} + +//--------------------------------------------------------------------------- +// PA message (0x0000, a table list) or M2 section (0x8000, one table). +void File_MmtTlv::Parse_SignalingMessage(const int8u* Data, size_t Size) +{ + if (Size < 4) + return; + int16u msg_id = RB16(Data); + + if (msg_id == MSG_PA_MESSAGE) + { + // id(16) version(8) length(32) + if (Size < 7) return; + int32u length = RB32(Data + 3); + const int8u* body = Data + 7; + if (length > Size - 7) length = (int32u)(Size - 7); + size_t n = length; + + if (n < 1) return; + int8u num_of_tables = body[0]; + const int8u* p = body + 1; n -= 1; + // per-table index: table_id(8) table_version(8) table_length(16) + for (int i = 0; i < num_of_tables && n >= 4; ++i) { p += 4; n -= 4; } + // Concatenated tables. Length lives in each table header, width by table: + // - MPT (0x20): table_id(8) version(8) length(16) -> 4+len + // - M2 section tables: table_id(8) + 16-bit, low 12 = section_length + // -> 3+section_length + while (n >= 4) + { + int8u tid = p[0]; + size_t consumed; + if (tid == TABLE_MPT) + consumed = (size_t)4 + RB16(p + 2); + else + consumed = (size_t)3 + (RB16(p + 1) & 0x0FFF); + if (consumed < 4) + break; + //Parse a truncated final table against the bytes we have. + size_t take = consumed > n ? n : consumed; + Parse_Table(p, take); + if (consumed > n) + break; + p += consumed; n -= consumed; + } + } + else if (msg_id == MSG_M2_SECTION) + { + // id(16) version(8) length(16) + if (Size < 5) return; + int16u length = RB16(Data + 3); + const int8u* body = Data + 5; + if (length > Size - 5) length = (int16u)(Size - 5); + Parse_Table(body, length); + } +} + +//--------------------------------------------------------------------------- +void File_MmtTlv::Parse_Table(const int8u* Data, size_t Size) +{ + if (Size < 1) + return; + switch (Data[0]) + { + case TABLE_MPT: + { + //The asset list + video MPU-PTS are parsed in File_Mmt; the container commits them here. + mmt_stream Stream; + File_Mmt Signaling; + Signaling.Complete_Stream = &Stream; + Open_Buffer_Init(&Signaling); + Open_Buffer_Continue(&Signaling, Data, Size); + Open_Buffer_Finalize(&Signaling); + + //Timestamps come from every MPT (start + tail probe); keep the global min/max. + if (Stream.PtsMinUs >= 0 && (Pts_First_Us < 0 || Stream.PtsMinUs < Pts_First_Us)) + Pts_First_Us = Stream.PtsMinUs; + if (Stream.PtsMaxUs >= 0 && (Pts_Last_Us < 0 || Stream.PtsMaxUs > Pts_Last_Us)) + Pts_Last_Us = Stream.PtsMaxUs; + + if (!Stream.MptValid) + break; + + //Transfer (0x8010): newest head-scan value wins; the tail probe's MPT is the next + //program's, so it does not vote. + if (Phase != Phase_Tail && Stream.TransferLast >= 1 && Stream.TransferLast < 8) + Transfer_Last = Stream.TransferLast; + + //The MPT version identifies the current package. A change replaces the track set + //(dropping what the new program no longer carries); within a version keep the fullest, + //as the asset list builds up after a tune-in. The tail probe is a different program + //near EOF, so skip it. + std::vector& Found = Stream.Assets; + int Media = 0; + for (size_t i = 0; i < Found.size(); ++i) + if (Found[i].Type == ASSET_HEV1 || Found[i].Type == ASSET_HVC1 || Found[i].Type == ASSET_MP4A || Found[i].Type == ASSET_STPP) + ++Media; + bool NewVersion = (Stream.MptVersion != Mpt_Version); + if (Phase == Phase_Scan && Media && (NewVersion || Media > Mpt_AssetCount)) + { + if (NewVersion) + { + //Drop the previous package's parsers; its packet_ids and count may differ. + for (std::map::iterator It = MediaParsers.begin(); + It != MediaParsers.end(); ++It) + delete It->second.Parser; + MediaParsers.clear(); + Mpt_Version = Stream.MptVersion; + Media_Probe_Done = false; + Core_Done_At = (int64u)-1; + Media_Done_Utc = -1; + } + Mpt_AssetCount = Media; + Assets = Found; + Mpt_Found = true; + + //A child ES parser per A/V asset, keyed by packet_id (Text needs none). The + //factory maps asset_type -> parser + framing, so a new codec is one added row. + for (size_t i = 0; i < Assets.size(); ++i) + { + const asset& A = Assets[i]; + if (!A.PacketId || MediaParsers.count(A.PacketId)) + continue; + media_parser M; + if (Create_MediaParser(A.Type, M)) + MediaParsers[A.PacketId] = M; + } + } + break; + } + case TABLE_MH_EIT: + { + //The p/f is itself a boundary-window value: re-read until the window passes, so a flip + //re-commits and re-anchors (the first EIT can precede the first clock). + if (Eit_Present_Found && boundary_window_passed(Eit_Start_Utc, Now_First, Now_Utc)) + break; + + //Field extraction is in File_Mmt; the boundary/hop logic stays here (it seeks the file). + mmt_stream Stream; + File_Mmt Signaling; + Signaling.Complete_Stream = &Stream; + Open_Buffer_Init(&Signaling); + Open_Buffer_Continue(&Signaling, Data, Size); + Open_Buffer_Finalize(&Signaling); + + if (Stream.EitSvcIdFound && !Eit_ServiceId_Found) // matched later against MH-SDT + { + Eit_ServiceId = Stream.EitSvcId; + Eit_ServiceId_Found = true; + } + if (Stream.TlvStreamId >= 0 && Tlv_Stream_Id < 0) + Tlv_Stream_Id = Stream.TlvStreamId; + if (!Stream.EitParsed) + break; + + //If this present event has already ended at "now", it is the previous program's tail + //(the stream started on a boundary): hop forward and re-scan for the one whose window + //contains "now". + bool hopped = false; + if (Now_Utc >= 0) + { + int64s start_s = Mmt_DateTime_To_Seconds(Stream.EitStartDate, Stream.EitStartTime); // JST + if (start_s >= 0) + { + start_s -= Mmt_JST_Offset_Seconds; // -> UTC, to compare with Now_Utc + Eit_Start_Utc = start_s; + int dur_h = Mmt_Bcd2((int8u)(Stream.EitDuration >> 16)); + int dur_m = Mmt_Bcd2((int8u)(Stream.EitDuration >> 8)); + int dur_s = Mmt_Bcd2((int8u)(Stream.EitDuration)); + int64s dur = dur_h * 3600 + dur_m * 60 + dur_s; + int64s end_s = start_s + dur; + if (dur > 0 && end_s <= Now_Utc + BOUNDARY_GUARD_SECONDS + && Eit_Boundary_Hops < BOUNDARY_MAX_HOPS) + { + //The boundary may reconfigure the A/V, so drop the prior program's probe + //and re-derive after the hop. + for (std::map::iterator It = MediaParsers.begin(); + It != MediaParsers.end(); ++It) + delete It->second.Parser; + MediaParsers.clear(); + MfuAssemblers.clear(); + Assets.clear(); + Mpt_Found = false; + Mpt_AssetCount = -1; + Transfer_Last = 0xFF; + Media_Probe_Done = false; + Media_Bytes = 0; + Core_Done_At = (int64u)-1; // core re-derives at new pos + Media_Done_Utc = -1; + + ++Eit_Boundary_Hops; + int64u here = File_Offset + Buffer_Offset; + //Size the hop to land just past the event's end, from the byte rate + //observed so far; a fixed nudge cannot cross a minute-long tail. + int64u hop = BOUNDARY_HOP_BYTES; + if (Now_Utc > Now_First && end_s + 2 > Now_Utc) + { + int64u rate = here / (int64u)(Now_Utc - Now_First); + if (rate > 0 && (int64u)(end_s + 2 - Now_Utc) > BOUNDARY_HOP_BYTES / rate) + hop = rate * (int64u)(end_s + 2 - Now_Utc); + } + int64u target = here + hop; + if (File_Size != (int64u)-1 && target >= File_Size) + target = here + BOUNDARY_HOP_BYTES / 4; // small final nudge + GoTo(target, "MmtTlv"); + hopped = true; + } + } + } + if (hopped) + break; + + Eit_EventId = Stream.EitEventId; + Eit_StartDate = Stream.EitStartDate; + Eit_StartTime = Stream.EitStartTime; + Eit_Duration = Stream.EitDuration; + Eit_Language = Stream.EitLanguage; + Eit_EventName = Stream.EitName; + Eit_EventText = Stream.EitText; + Eit_Present_Found = true; // present event only + break; + } + case TABLE_MH_SDT: + { + //Migrated to File_Mmt (+ File_Mmt_Descriptors for the loop). The container passes the + //present-EIT service_id in and keeps the once-only latch on the recovered name. + if (Sdt_Found) + break; + mmt_stream Stream; + Stream.EitServiceId = Eit_ServiceId; + Stream.EitServiceIdFound = Eit_ServiceId_Found; + File_Mmt Signaling; + Signaling.Complete_Stream = &Stream; + Open_Buffer_Init(&Signaling); + Open_Buffer_Continue(&Signaling, Data, Size); + Open_Buffer_Finalize(&Signaling); + if (Stream.SdtFound) + { + Sdt_Found = true; + Sdt_ServiceName = Stream.ServiceName; + Sdt_Provider = Stream.Provider; + Sdt_ServiceType = Stream.ServiceType; + } + if (Stream.TlvStreamId >= 0 && Tlv_Stream_Id < 0) + Tlv_Stream_Id = Stream.TlvStreamId; + break; + } + case TABLE_MH_TOT: + { + //Migrated to the File_Mmt signaling sub-parser (idiomatic, traced). The container + //keeps its authoritative clock; the sub-parser only reports the decoded MH-TOT time. + mmt_stream Stream; + File_Mmt Signaling; + Signaling.Complete_Stream = &Stream; + Open_Buffer_Init(&Signaling); + Open_Buffer_Continue(&Signaling, Data, Size); + Open_Buffer_Finalize(&Signaling); + if (Stream.TotSeen) + { + Tot_Seen = true; + Now_Utc = Stream.TotUtc; // authoritative, overrides NTP + Note_StreamNow(Stream.TotUtc); + } + break; + } + case TABLE_ECM_0: // presence = CAS active; not parsed + case TABLE_ECM_1: Ecm_Seen = true; // and name it in the trace, below + default: + #if MEDIAINFO_TRACE + //Not parsed; route through the signaling sub-parser only for the + //trace, so recognized B60 tables (Table 4-8) show by name. + if (Trace_Activated) + { + mmt_stream Stream; + File_Mmt Signaling; + Signaling.Complete_Stream = &Stream; + Open_Buffer_Init(&Signaling); + Open_Buffer_Continue(&Signaling, Data, Size); + Open_Buffer_Finalize(&Signaling); + } + #endif //MEDIAINFO_TRACE + break; + } +} + +//*************************************************************************** +// MH-TOT: current wall clock (JST), in the clear. +//*************************************************************************** + + +//*************************************************************************** +// Fallback clock: the NTP transmit timestamp in the IPv6/UDP time-distribution +// packet, used until MH-TOT arrives. Only a coarse "now" is needed. +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_MmtTlv::Parse_Ntp() +{ + const int64u IPV6_HEADER = 40; + const int64u UDP_HEADER = 8; + const int64u NTP_MIN = 48; + if (Element_Size - Element_Offset < IPV6_HEADER + UDP_HEADER + NTP_MIN) + return; + // Gate before parsing: IPv6 (version 6, next_header UDP=17) carrying a plausible NTP payload + // (non-zero mode, sane stratum). Peeked so a non-NTP datagram leaves no half-trace. + const int8u* P = Buffer + Buffer_Offset + (size_t)Element_Offset; + const int8u* Ntp = P + IPV6_HEADER + UDP_HEADER; + if ((P[0] >> 4) != 6 || P[6] != 17 || (Ntp[0] & 0x07) == 0 || Ntp[1] > 15) + return; + + int32u ntp_secs; + Element_Begin1("IPv6/UDP/NTP"); + Skip_XX(IPV6_HEADER, "IPv6 header"); + Skip_XX(UDP_HEADER, "UDP header"); + Element_Begin1("NTP"); + Skip_B1( "LI/VN/Mode"); + Skip_B1( "Stratum"); + Skip_B1( "Poll"); + Skip_B1( "Precision"); + Skip_B4( "Root Delay"); + Skip_B4( "Root Dispersion"); + Skip_B4( "Reference ID"); + Skip_XX(8, "Reference Timestamp"); + Skip_XX(8, "Origin Timestamp"); + Skip_XX(8, "Receive Timestamp"); + Get_B4 (ntp_secs, "Transmit Timestamp (seconds)"); + Skip_B4( "Transmit Timestamp (fraction)"); + Element_End0(); + Element_End0(); + + if (ntp_secs == 0) + return; + int64s utc = (int64s)ntp_secs - NTP_UNIX_EPOCH_DELTA; + if (!Tot_Seen) + Now_Utc = utc; // provisional until MH-TOT + Note_StreamNow(utc); +} + +//--------------------------------------------------------------------------- +void File_MmtTlv::Note_StreamNow(int64s Utc) +{ + if (Now_First < 0 || Utc < Now_First) + Now_First = Utc; + if (Now_Last < 0 || Utc > Now_Last) + Now_Last = Utc; +} + +} //NameSpace + +#endif //MEDIAINFO_MMTTLV_YES diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.h b/Source/MediaInfo/Multiple/File_MmtTlv.h new file mode 100644 index 000000000..77b2204b4 --- /dev/null +++ b/Source/MediaInfo/Multiple/File_MmtTlv.h @@ -0,0 +1,194 @@ +/* Copyright (c) MediaArea.net SARL. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license that can + * be found in the License.html file in the root of the source tree. + */ + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// MMT protocol over TLV packets (MMT/TLV): an MMTP stream (MPEG Media +// Transport, ISO/IEC 23008-1) framed in TLV packets (ARIB STD-B32). Japanese +// ISDB-S3 BS/CS 4K/8K. Surfaces A/V/subtitle streams (MPT asset list) and, +// under Stream_Menu, the present event as a chapter (name, description, +// scheduled start and duration; MH-EIT[p/f], table_id 0x8B). +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//--------------------------------------------------------------------------- +#ifndef MediaInfo_File_MmtTlvH +#define MediaInfo_File_MmtTlvH +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/File__Analyze.h" +#include "MediaInfo/Multiple/File_Mmt.h" //asset, mmt_stream (shared with the signaling sub-parser) +#include +#include +#include +//--------------------------------------------------------------------------- + +namespace MediaInfoLib +{ + +class File__Analyze; + +//*************************************************************************** +// Class File_MmtTlv +//*************************************************************************** + +class File_MmtTlv : public File__Analyze +{ +public : + File_MmtTlv(); + +private : + //Streams management + void Streams_Fill() override; + void Streams_Finish() override; + + //Buffer - File header (probe) + bool FileHeader_Begin() override; + + //Buffer - Synchro (per-TLV-packet synchronization; gives free resync) + bool Synchronize() override; + bool Synched_Test() override; + + //Buffer - Per element (one TLV packet) + void Header_Parse() override; + void Data_Parse() override; + + //Payload parsers (operate on an in-memory byte range; see .cpp) + void Parse_CompressedIp(); + void Parse_Mmtp(); + void Parse_SignalingMessages(int16u packet_id, int32u seq_num, const int8u* Data, size_t Size); + void Parse_SignalingMessage(const int8u* Data, size_t Size); + void Parse_Table(const int8u* Data, size_t Size); + void Parse_Ntp(); // IPv6/UDP -> NTP fallback clock + void Note_StreamNow(int64s Utc); // record earliest/latest "now" + bool PidEncrypted(int16u PacketId) const; + + void Parse_Mpu(int16u packet_id, int32u seq_num, const int8u* Data, size_t Size); + void Feed_DataUnit(int16u packet_id, const int8u* Data, size_t Size); + + //Child ES parser factory: maps an MPT asset_type (STD-B60) to a MediaInfo + //parser + stream kind + DU framing. Returns false for a type with no ES + //parser (stpp/aapp today; a newly assigned asset_type is one added row). + struct media_parser; + bool Create_MediaParser(int32u asset_type, media_parser& M); + + //FIRST/MIDDLE/LAST fragments accumulated per packet_id with sequence-number + //continuity. The MPT is fragmented across MMTP packets, so without this no + //A/V/subtitle streams appear. + struct fragment_assembler + { + enum state_t { Init, NotStarted, InFragment, Skip }; + state_t State; + int32u LastSeq; + std::vector Data; + fragment_assembler() : State(Init), LastSeq(0) {} + }; + std::map Assemblers; + + //As Assemblers, but the completed DU is framed and fed to a child ES parser + //rather than dispatched as a table. + std::map MfuAssemblers; + + //asset struct now lives in File_Mmt.h (shared with the signaling sub-parser). + std::vector Assets; + + //Data-unit framing of the child ES: HEVC emits Annex-B NAL, AAC LATM emits + //LOAS. Keyed off asset_type at parser creation, so a new codec is one row. + enum es_framing { Es_Nal, Es_Loas }; + + //Child ES parser per A/V asset, keyed by packet_id, Merge()d onto the + //MPT-created stream at finish. + struct media_parser + { + File__Analyze* Parser; //owned; deleted in Streams_Finish + stream_t StreamKind; + size_t StreamPos; //as created in Streams_Fill + es_framing Framing; //how Feed_DataUnit frames a DU for the parser + bool Done; + int64u Fed; //validated bytes fed (readability signal) + int64u MpuSeen; //MPU packets routed here (scramble give-up) + int DescCh; //audio-component channel count + int8u DescCfg; //MPEG-4 channel_configuration for the shared Aac_* strings + //Video geometry from 0x8010; fills the ES's gaps at finish. + int DescWidth, DescHeight; + float64 DescFrameRate; + int8u DescScan; //0xFF=none + media_parser() : Parser(NULL), StreamKind(Stream_Video), StreamPos(0), + Framing(Es_Nal), Done(false), Fed(0), MpuSeen(0), + DescCh(0), DescCfg(0), + DescWidth(0), DescHeight(0), DescFrameRate(0), DescScan(0xFF) {} + }; + std::map MediaParsers; + bool Media_Probe_Done; + int64u Media_Bytes; //global probe budget + + //PIDs flagged scrambled in the MMTP scramble sub-header. + std::set ScrambledPids; + + //An ECM (0x82/0x83) was seen -> CAS active. A scrambled PID counts as + //encrypted only after this: a descrambled file's pre-ECM lead-in is still + //scrambled but not encrypted. + bool Ecm_Seen; + + bool Eit_Present_Found; + Ztring Eit_EventName; + Ztring Eit_EventText; //short event description + int16u Eit_ServiceId; //matches the SDT service + bool Eit_ServiceId_Found; + int16u Eit_EventId; + int16u Eit_StartDate; //MJD + int32u Eit_StartTime; //24-bit BCD HHMMSS + int64s Eit_Start_Utc; //present event start, UTC (settle grace) + int32u Eit_Duration; //24-bit BCD HHMMSS + Ztring Eit_Language; + + bool Mpt_Found; + int Mpt_AssetCount; //media assets in the committed MPT version (fullest within it) + int Mpt_Version; //committed MPT version; a change replaces the track set + int8u Transfer_Last; //newest 0x8010 transfer code (STD-B60 7-51); 0xFF = none seen + + //Per-channel, so it survives an EIT boundary re-scan (unlike the MPT). + bool Sdt_Found; + Ztring Sdt_ServiceName; + Ztring Sdt_Provider; + Ztring Sdt_ServiceType; + + //TLV stream id (transport_stream_id analog) from the MH-SDT/MH-EIT header. -1 = not seen. + int32s Tlv_Stream_Id; + + //Stream wall clock, JST seconds since the Unix epoch. MH-TOT preferred, NTP + //transmit timestamp fallback. -1 = not seen. + bool Tot_Seen; //authoritative MH-TOT clock seen; until then NTP advances it + int64s Now_Utc; //latest, drives the boundary check + int64s Now_First; + int64s Now_Last; //incl. tail probe + + //Min/max video MPU presentation time, us. In the clear, so it works for + //scrambled video. -1 = none seen. + int64s Pts_First_Us; + int64s Pts_Last_Us; + int64s Pts_Last_At_Tail; //snapshot on entering the tail probe + + //Bounded so a bad stream is not walked forever. + int Eit_Boundary_Hops; + + enum probe_phase { Phase_Scan, Phase_Tail, Phase_Done }; + probe_phase Phase; + + //Bound on scanning past accept, so a huge file is not read end-to-end when + //the present EIT never appears. + int64u Packets_Since_Accept; + + //When the core (MPT + present EIT + media probe) completed; a bounded grace + //after it catches the less-frequent MH-SDT. -1 = not yet. + int64u Core_Done_At; + int64s Media_Done_Utc; //clock when MPT+media first done, to time-box the EIT wait +}; + +} //NameSpace + +#endif diff --git a/Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp new file mode 100644 index 000000000..fd3a10dae --- /dev/null +++ b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp @@ -0,0 +1,556 @@ +/* Copyright (c) MediaArea.net SARL. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license that can + * be found in the License.html file in the root of the source tree. + */ + +//--------------------------------------------------------------------------- +#include "MediaInfo/PreComp.h" +#ifdef __BORLANDC__ + #pragma hdrstop +#endif +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/Setup.h" +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#if defined(MEDIAINFO_MMTTLV_YES) +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/Multiple/File_Mmt_Descriptors.h" +using namespace ZenLib; +//--------------------------------------------------------------------------- + +namespace MediaInfoLib +{ + +//*************************************************************************** +// Descriptor tag -> name, using the exact B60 syntax-structure identifiers +// (STD-B60 Table 4-10 and the descriptor bodies); "" if unrecognized. +//*************************************************************************** + +namespace +{ + const char* Mmt_Descriptor_Name(int16u tag) + { + switch (tag) + { + case 0x0000 : return "CRI_Descriptor"; + case 0x0001 : return "MPU_Timestamp_Descriptor"; + case 0x0002 : return "Dependency_Descriptor"; + case 0x0003 : return "GFDT_Descriptor"; + case 0x8000 : return "Asset_Group_Descriptor"; + case 0x8001 : return "Event_Package_Descriptor"; + case 0x8002 : return "Background_Color_Descriptor"; + case 0x8003 : return "MPU_Presentation_Region_Descriptor"; + case 0x8004 : return "Access_Control_Descriptor"; + case 0x8005 : return "Scrambler_Descriptor"; + case 0x8006 : return "Message_Authentication_Method_Descriptor"; + case 0x8007 : return "Emergency_Information_Descriptor"; + case 0x8008 : return "MH-MPEG-4_Audio_Descriptor"; + case 0x8009 : return "MH-MPEG-4_Audio_Extension_Descriptor"; + case 0x800A : return "MH-HEVC_Descriptor"; + case 0x800C : return "MH-Event_Group_Descriptor"; + case 0x800D : return "MH-Service_List_Descriptor"; + case 0x8010 : return "Video_Component_Descriptor"; + case 0x8011 : return "MH-Stream_Identifier_Descriptor"; + case 0x8012 : return "MH-Content_Descriptor"; + case 0x8013 : return "MH-Parental_Rating_Descriptor"; + case 0x8014 : return "MH-Audio_Component_Descriptor"; + case 0x8015 : return "MH-Target_Region_Descriptor"; + case 0x8016 : return "MH-Series_Descriptor"; + case 0x8017 : return "MH-SI_Parameter_Descriptor"; + case 0x8018 : return "MH-Broadcaster_Name_Descriptor"; + case 0x8019 : return "MH-Service_Descriptor"; + case 0x801A : return "IP_Data_Flow_Descriptor"; + case 0x801B : return "MH-CA_Startup_Descriptor"; + case 0x801C : return "MH-Type_Descriptor"; + case 0x801D : return "MH-Info_Descriptor"; + case 0x801E : return "MH-Expire_Descriptor"; + case 0x801F : return "MH-Compression_Type_Descriptor"; + case 0x8020 : return "MH-Data_Component_Descriptor"; + case 0x8021 : return "UTC-NPT_Reference_Descriptor"; + case 0x8023 : return "MH-Local_Time_Offset_Descriptor"; + case 0x8024 : return "MH-Component_Group_Descriptor"; + case 0x8025 : return "MH-Logo_Transmission_Descriptor"; + case 0x8026 : return "MPU_Extended_Timestamp_Descriptor"; + case 0x8027 : return "MPU_Download_Content_Descriptor"; + case 0x8028 : return "MH-Network_Download_Content_Descriptor"; + case 0x8029 : return "MH-Application_Descriptor"; + case 0x802A : return "MH-Transport_Protocol_Descriptor"; + case 0x802B : return "MH-Simple_Application_Location_Descriptor"; + case 0x802C : return "MH-Application_Boundary_and_Permission_Descriptor"; + case 0x802D : return "MH-Autostart_Priority_Descriptor"; + case 0x802E : return "MH-Cache_Control_Info_Descriptor"; + case 0x802F : return "MH-Randomized_Latency_Descriptor"; + case 0x8030 : return "Linked_PU_Descriptor"; + case 0x8031 : return "Locked_Cache_Descriptor"; + case 0x8032 : return "Unlocked_Cache_Descriptor"; + case 0x8033 : return "MH-DL_Protection_Descriptor"; + case 0x8034 : return "Application_Service_Descriptor"; + case 0x8035 : return "MPU_Node_Descriptor"; + case 0x8036 : return "PU_Structure_Descriptor"; + case 0x8037 : return "MH-Hierarchy_Descriptor"; + case 0x8038 : return "Content_Copy_Control_Descriptor"; + case 0x8039 : return "Content_Usage_Control_Descriptor"; + case 0x803A : return "MH-External_Application_Control_Descriptor"; + case 0x803B : return "MH-Playback_Application_Descriptor"; + case 0x803C : return "MH-Simple_Playback_Application_Location_Descriptor"; + case 0x803D : return "MH-Application_Expiration_Descriptor"; + case 0x803E : return "Related_Broadcaster_Descriptor"; + case 0x803F : return "Multimedia_Service_Info_Descriptor"; + case 0x8040 : return "Emergency_News_Descriptor"; + case 0x8041 : return "MH-CA_Contract_Info_Descriptor"; + case 0x8042 : return "MH-CA_Service_Descriptor"; + case 0xF000 : return "MH-Linkage_Descriptor"; + case 0xF001 : return "MH-Short_Event_Descriptor"; + case 0xF002 : return "MH-Extended_Event_Descriptor"; + case 0xF003 : return "Event_Message_Descriptor"; + case 0xF004 : return "MH-stuffing_descriptor"; + case 0xF005 : return "MH-broadcast_id_descriptor"; + case 0xF006 : return "MH-network_identification_descriptor"; + default : return ""; + } + } + + //Coded-field meanings (ARIB STD-B60); "" leaves the field with no annotation. + const char* Mmt_video_resolution(int8u c) + { + switch (c) + { + case 1 : return "180"; + case 2 : return "240"; + case 3 : return "480 (525)"; + case 4 : return "720 (750)"; + case 5 : return "1080 (1125)"; + case 6 : return "2160"; + case 7 : return "4320"; + default: return ""; + } + } + const char* Mmt_video_aspect_ratio(int8u c) + { + switch (c) + { + case 1 : return "4:3"; + case 2 : return "16:9 with pan vector"; + case 3 : return "16:9 without pan vector"; + case 4 : return "> 16:9"; + default: return ""; + } + } + const char* Mmt_video_frame_rate(int8u c) + { + switch (c) + { + case 1 : return "15"; + case 2 : return "24/1.001"; + case 3 : return "24"; + case 4 : return "25"; + case 5 : return "30/1.001"; + case 6 : return "30"; + case 7 : return "50"; + case 8 : return "60/1.001"; + case 9 : return "60"; + case 10: return "100"; + case 11: return "120/1.001"; + case 12: return "120"; + default: return ""; + } + } + const char* Mmt_video_transfer(int8u c) + { + switch (c) + { + case 1 : return "BT.709"; + case 2 : return "IEC 61966-2-4"; + case 3 : return "BT.2020"; + case 4 : return "BT.2100 PQ"; + case 5 : return "BT.2100 HLG"; + default: return ""; + } + } + const char* Mmt_stream_content(int8u c) + { + switch (c) + { + case 2 : return "Sound (system not specified)"; + case 3 : return "MPEG-4 AAC"; + case 4 : return "MPEG-4 ALS"; + default: return ""; + } + } + const char* Mmt_audio_mode(int8u c) //component_type bits 4-0 (Table 7-60) + { + switch (c & 0x1F) + { + case 0x01: return "1/0 mode (single mono)"; + case 0x02: return "1/0 + 1/0 mode (dual mono)"; + case 0x03: return "2/0 mode (stereo)"; + case 0x04: return "2/1 mode"; + case 0x05: return "3/0 mode"; + case 0x06: return "2/2 mode"; + case 0x07: return "3/1 mode"; + case 0x08: return "3/2 mode"; + case 0x09: return "3/2 + LFE mode (3/2.1 mode)"; + case 0x0A: return "3/3.1 mode"; + case 0x0B: return "2/0/0-2/0/2-0.1 mode"; + case 0x0C: return "5/2.1 mode"; + case 0x0D: return "3/2/2.1 mode"; + case 0x0E: return "2/0/0-3/0/2-0.1 mode"; + case 0x0F: return "0/2/0-3/0/2-0.1 mode"; + case 0x10: return "2/0/0-3/2/3-0.2 mode"; + case 0x11: return "3/3/3-5/2/3-3/0/0.2 mode"; + default : return ""; + } + } + const char* Mmt_sampling_rate(int8u c) + { + switch (c) + { + case 1 : return "16 kHz"; + case 2 : return "22.05 kHz"; + case 3 : return "24 kHz"; + case 5 : return "32 kHz"; + case 6 : return "44.1 kHz"; + case 7 : return "48 kHz"; + default: return ""; + } + } + const char* Mmt_data_component_id(int16u c) + { + switch (c) + { + case 0x0020: return "Caption (2nd generation)"; + case 0x0021: return "Multimedia (2nd generation)"; + default : return ""; + } + } + const char* Mmt_service_type(int8u c) + { + switch (c) + { + case 0x01: return "Digital TV service"; + case 0x02: return "Digital audio service"; + case 0xA1: return "Special video service"; + case 0xA4: return "Engineering service"; + case 0xC0: return "Data service"; + case 0xC1: return "Storage type service using TLV"; + case 0xC2: return "Multimedia service"; + default : return ""; + } + } +} + +//*************************************************************************** +// Constructor/Destructor +//*************************************************************************** + +//--------------------------------------------------------------------------- +File_Mmt_Descriptors::File_Mmt_Descriptors() +{ + Complete_Stream = NULL; + CurrentAsset = NULL; +} + +//*************************************************************************** +// Buffer - Global +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::FileHeader_Parse() +{ + Accept(); +} + +//*************************************************************************** +// Buffer - Per element (one descriptor) +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Header_Parse() +{ + //MMT descriptor: 16-bit tag. The length field is 16-bit for the 0xF000-0xFFFF + //tags (MH-EIT short/extended event) and 8-bit below that (STD-B60 tag table). + int16u descriptor_tag; + Get_B2 (descriptor_tag, "descriptor_tag"); + if (descriptor_tag >= 0xF000) + { + int16u descriptor_length; + Get_B2 (descriptor_length, "descriptor_length"); + Header_Fill_Size((int64u)4 + descriptor_length); + } + else + { + int8u descriptor_length; + Get_B1 (descriptor_length, "descriptor_length"); + Header_Fill_Size((int64u)3 + descriptor_length); + } + Header_Fill_Code(descriptor_tag, Ztring().From_Number(descriptor_tag, 16)); +} + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Data_Parse() +{ + switch (Element_Code) + { + case 0x0001 : Element_Name("MPU_Timestamp_Descriptor"); Descriptor_0001(); break; + case 0x8000 : Element_Name("Asset_Group_Descriptor"); Descriptor_8000(); break; + case 0x8010 : Element_Name("Video_Component_Descriptor"); Descriptor_8010(); break; + case 0x8014 : Element_Name("MH-Audio_Component_Descriptor"); Descriptor_8014(); break; + case 0x8019 : Element_Name("MH-Service_Descriptor"); Descriptor_8019(); break; + case 0x8020 : Element_Name("MH-Data_Component_Descriptor"); Descriptor_8020(); break; + case 0xF001 : Element_Name("MH-Short_Event_Descriptor"); Descriptor_F001(); break; + default : + { + const char* Name = Mmt_Descriptor_Name((int16u)Element_Code); + if (Name[0]) + Element_Name(Name); + Skip_XX(Element_Size - Element_Offset, "Data"); + } + break; + } +} + +//*************************************************************************** +// Descriptors +//*************************************************************************** + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Descriptor_0001() +{ + //Repeated { mpu_sequence_number(32), presentation_time(64, NTP 32.32) }. Only + //the video (HEVC) asset's timestamps define the stream span. + if (!Complete_Stream || !CurrentAsset + || (CurrentAsset->Type != 0x31766568 /*'hev1'*/ && CurrentAsset->Type != 0x31637668 /*'hvc1'*/)) + { + Skip_XX(Element_Size - Element_Offset, "Data"); + return; + } + while (Element_Size - Element_Offset >= 12) + { + int32u seconds, fraction; + Skip_B4( "mpu_sequence_number"); + Get_B4 (seconds, "presentation_time (seconds)"); + Get_B4 (fraction, "presentation_time (fraction)"); + int64s us = (int64s)((int64u)seconds * 1000000ULL + + ((int64u)fraction * 1000000ULL) / 0x100000000ULL); + if (Complete_Stream->PtsMinUs < 0 || us < Complete_Stream->PtsMinUs) Complete_Stream->PtsMinUs = us; + if (Complete_Stream->PtsMaxUs < 0 || us > Complete_Stream->PtsMaxUs) Complete_Stream->PtsMaxUs = us; + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "Data"); +} + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Descriptor_8000() +{ + //group_identification(8) selection_level(8): main + rain-fade backup of a + //stream share the group; selection_level 0 is the default member. + if (Element_Size - Element_Offset >= 2 && CurrentAsset && CurrentAsset->GroupId < 0) + { + int8u group_id, selection_level; + Get_B1 (group_id, "group_identification"); + Get_B1 (selection_level, "selection_level"); + CurrentAsset->GroupId = group_id; + CurrentAsset->SelectionLevel = selection_level; + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "Data"); +} + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Descriptor_8010() +{ + if (Element_Size - Element_Offset < 2 || !CurrentAsset) + { + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "Data"); + return; + } + int8u video_resolution, video_aspect_ratio, video_scan_flag, video_frame_rate; + BS_Begin(); + Get_S1 (4, video_resolution, "video_resolution"); Param_Info1(Mmt_video_resolution(video_resolution)); + Get_S1 (4, video_aspect_ratio, "video_aspect_ratio"); Param_Info1(Mmt_video_aspect_ratio(video_aspect_ratio)); + Get_S1 (1, video_scan_flag, "video_scan_flag"); Param_Info1(video_scan_flag ? "Progressive" : "Interlaced"); + Skip_S1(2, "reserved"); + Get_S1 (5, video_frame_rate, "video_frame_rate"); Param_Info1(Mmt_video_frame_rate(video_frame_rate)); + BS_End(); + CurrentAsset->VideoResolution = video_resolution; + CurrentAsset->VideoAspect = video_aspect_ratio; + CurrentAsset->VideoScan = video_scan_flag; + CurrentAsset->VideoFrameRate = video_frame_rate; + if (Element_Size - Element_Offset >= 3) + { + int8u transfer; + Skip_B2( "component_tag"); + BS_Begin(); + Get_S1 (4, transfer, "video_transfer_characteristics"); Param_Info1(Mmt_video_transfer(transfer)); + Skip_S1(4, "reserved"); + BS_End(); + if (Complete_Stream && transfer >= 1 && transfer < 8) + Complete_Stream->TransferLast = transfer; // container applies the phase gate + } + if (Element_Size - Element_Offset >= 3) + { + Ztring Language; + Get_UTF8(3, Language, "ISO_639_language_code"); + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "text"); +} + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Descriptor_8014() +{ + //component_type packs the handicapped mode (bits 6-5) and audio_mode (bits + //4-0); the flags byte carries ES_multi, main_component and the sampling_rate + //code, then the 3-byte language (a second when ES_multi) and the UTF-8 text. + if (Element_Size - Element_Offset < 10 || !CurrentAsset) + { + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "Data"); + return; + } + int8u component_type, sampling_rate; + bool es_multi, main_component; + int8u stream_content; + BS_Begin(); + Skip_S1(4, "reserved_future_use"); + Get_S1 (4, stream_content, "stream_content"); Param_Info1(Mmt_stream_content(stream_content)); + BS_End(); + Get_B1 (component_type, "component_type"); Param_Info1(Mmt_audio_mode(component_type)); + Skip_B2( "component_tag"); + Skip_B1( "stream_type"); + Skip_B1( "simulcast_group_tag"); + BS_Begin(); + Get_SB (es_multi, "ES_multi_lingual_flag"); + Get_SB (main_component, "main_component_flag"); + Skip_S1(2, "quality_indicator"); + Get_S1 (3, sampling_rate, "sampling_rate"); Param_Info1(Mmt_sampling_rate(sampling_rate)); + Skip_SB( "reserved_future_use"); + BS_End(); + CurrentAsset->Handicapped = (component_type >> 5) & 0x03; + CurrentAsset->AudioMode = component_type & 0x1F; + CurrentAsset->MainComponent = main_component; + CurrentAsset->SamplingCode = sampling_rate; + Ztring Language; + Get_UTF8(3, Language, "ISO_639_language_code"); + CurrentAsset->Language = Language; + if (es_multi && Element_Size - Element_Offset >= 3) + Skip_UTF8(3, "ISO_639_language_code_2"); + if (Element_Offset < Element_Size) + { + Ztring Text; + Get_UTF8(Element_Size - Element_Offset, Text, "text"); + CurrentAsset->Title = Text; + } +} + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Descriptor_8020() +{ + //data_component_id 0x0020 = caption; the Additional_Arib_Subtitle_Info (STD-B60 Table 9-4) + //carries the ISO_639_language_code, then a type byte whose bits 7-6 are 01 = superimposed + //text, 00 = subtitle. + if (Element_Size - Element_Offset >= 8 && CurrentAsset) + { + int16u data_component_id; + Get_B2 (data_component_id, "data_component_id"); Param_Info1(Mmt_data_component_id(data_component_id)); + if (data_component_id == 0x0020) + { + Ztring Language; + int8u subtitle_type; + Skip_XX(2, "Additional_Arib_Subtitle_Info"); + Get_UTF8(3, Language, "ISO_639_language_code"); + BS_Begin(); + Get_S1 (2, subtitle_type, "subtitle_type"); + Skip_S1(6, "reserved"); + BS_End(); + CurrentAsset->Language = Language; + CurrentAsset->Superimpose = subtitle_type == 0x01; + } + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "Data"); +} + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Descriptor_F001() +{ + //ISO_639_language(24) event_name_length(8) event_name text_length(16) text. + //Decrypted MMT SI text is UTF-8 (no ARIB STD-B24 decode). text_length is + //16-bit here - an 8-bit read would hit the high 0x00 and yield empty text. + if (Element_Size - Element_Offset < 4 || !Complete_Stream) + { + Skip_XX(Element_Size - Element_Offset, "Data"); + return; + } + Ztring Language, Name, Text; + int8u event_name_length; + Get_UTF8(3, Language, "ISO_639_language_code"); + Complete_Stream->EitLanguage = Language; + Get_B1 (event_name_length, "event_name_length"); + if (event_name_length && Element_Offset + event_name_length <= Element_Size) + { + Get_UTF8(event_name_length, Name, "event_name"); + Complete_Stream->EitName = Name; + } + if (Element_Offset + 2 <= Element_Size) + { + int16u text_length; + Get_B2 (text_length, "text_length"); + if (text_length && Element_Offset + text_length <= Element_Size) + { + Get_UTF8(text_length, Text, "text"); + Complete_Stream->EitText = Text; + } + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "Data"); +} + +//--------------------------------------------------------------------------- +void File_Mmt_Descriptors::Descriptor_8019() +{ + //service_type(8) provider_name_length(8) provider(UTF-8) + //service_name_length(8) service_name(UTF-8) + if (Element_Size - Element_Offset < 2) + { + Skip_XX(Element_Size - Element_Offset, "Data"); + return; + } + Ztring Provider, ServiceName; + int8u service_type, provider_name_length, service_name_length; + Get_B1 (service_type, "service_type"); Param_Info1(Mmt_service_type(service_type)); + Get_B1 (provider_name_length, "provider_name_length"); + if (provider_name_length && Element_Offset + provider_name_length <= Element_Size) + Get_UTF8(provider_name_length, Provider, "service_provider_name"); + //A malformed provider length leaves the rest to the trailing skip. + if (Element_Offset < Element_Size) + { + Get_B1 (service_name_length, "service_name_length"); + if (service_name_length && Element_Offset + service_name_length <= Element_Size) + Get_UTF8(service_name_length, ServiceName, "service_name"); + } + if (Element_Offset < Element_Size) + Skip_XX(Element_Size - Element_Offset, "Data"); + + if (Complete_Stream) + { + if (!Provider.empty()) + Complete_Stream->Provider = Provider; + if (!ServiceName.empty()) + Complete_Stream->ServiceName = ServiceName; + const char* Type = Mmt_service_type(service_type); + if (Type[0]) + Complete_Stream->ServiceType = Ztring().From_UTF8(Type); + } +} + +} //NameSpace + +#endif //MEDIAINFO_MMTTLV_YES diff --git a/Source/MediaInfo/Multiple/File_Mmt_Descriptors.h b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.h new file mode 100644 index 000000000..e4e6f3bc3 --- /dev/null +++ b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.h @@ -0,0 +1,60 @@ +/* Copyright (c) MediaArea.net SARL. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license that can + * be found in the License.html file in the root of the source tree. + */ + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// +// ARIB MMT descriptors (STD-B60), carried in the MMT signaling tables. Fed a +// descriptor loop by File_Mmt; writes the decoded fields into the shared +// mmt_stream. Analogous to File_Mpeg_Descriptors for MPEG-TS. +// +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//--------------------------------------------------------------------------- +#ifndef MediaInfo_File_Mmt_DescriptorsH +#define MediaInfo_File_Mmt_DescriptorsH +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +#include "MediaInfo/Multiple/File_Mmt.h" //mmt_stream +//--------------------------------------------------------------------------- + +namespace MediaInfoLib +{ + +//*************************************************************************** +// Class File_Mmt_Descriptors +//*************************************************************************** + +class File_Mmt_Descriptors : public File__Analyze +{ +public : + //In - set by File_Mmt before feeding a descriptor loop. + mmt_stream* Complete_Stream; + asset* CurrentAsset; //the MPT asset being built (NULL for non-asset loops) + + File_Mmt_Descriptors(); + +private : + //Buffer - Global + void FileHeader_Parse() override; + + //Buffer - Per element (one descriptor) + void Header_Parse() override; + void Data_Parse() override; + + //Descriptors + void Descriptor_0001(); //MPU_Timestamp_Descriptor: video presentation-time span + void Descriptor_8000(); //Asset_Group_Descriptor + void Descriptor_8010(); //Video_Component_Descriptor + void Descriptor_8014(); //MH-Audio_Component_Descriptor + void Descriptor_8019(); //MH-Service_Descriptor: provider + service name + void Descriptor_8020(); //MH-Data_Component_Descriptor: superimpose vs subtitle + void Descriptor_F001(); //MH-Short_Event_Descriptor: event name + text +}; + +} //NameSpace + +#endif diff --git a/Source/MediaInfo/Setup.h b/Source/MediaInfo/Setup.h index f21a2be88..c65c80c15 100644 --- a/Source/MediaInfo/Setup.h +++ b/Source/MediaInfo/Setup.h @@ -537,6 +537,9 @@ #if !defined(MEDIAINFO_MULTI_NO) && !defined(MEDIAINFO_MIXML_NO) && !defined(MEDIAINFO_MIXML_YES) #define MEDIAINFO_MIXML_YES #endif +#if !defined(MEDIAINFO_MULTI_NO) && !defined(MEDIAINFO_MMTTLV_NO) && !defined(MEDIAINFO_MMTTLV_YES) + #define MEDIAINFO_MMTTLV_YES +#endif #if !defined(MEDIAINFO_MULTI_NO) && !defined(MEDIAINFO_MPEG4_NO) && !defined(MEDIAINFO_MPEG4_YES) #define MEDIAINFO_MPEG4_YES #endif From 87ac94f9ed42a6a47ab401771df9c902ce9df69c Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Tue, 7 Jul 2026 22:16:03 +0900 Subject: [PATCH 2/8] MMT/TLV: register the format in the database Adds the MMT/TLV row (category Multiple, extension mmts, description) to Format.csv and the generated MediaInfo_Config_Automatic.cpp, so the format info and extensions come from the database rather than being filled by the parser. Signed-off-by: Rainbaby --- Source/MediaInfo/MediaInfo_Config_Automatic.cpp | 1 + Source/Resource/Text/DataBase/Format.csv | 1 + 2 files changed, 2 insertions(+) diff --git a/Source/MediaInfo/MediaInfo_Config_Automatic.cpp b/Source/MediaInfo/MediaInfo_Config_Automatic.cpp index f713d62d7..946a6fc33 100644 --- a/Source/MediaInfo/MediaInfo_Config_Automatic.cpp +++ b/Source/MediaInfo/MediaInfo_Config_Automatic.cpp @@ -1418,6 +1418,7 @@ void MediaInfo_Config_Format (InfoMap &Info) "IVF;;;M;Ivf;;ivf\n" "LXF;;;M;Lxf;;lxf;video/lxf\n" "Matroska;;;M;Mk;;mkv mk3d mka mks;;https://matroska.org/downloads/windows.html\n" + "MMT/TLV;;;M;MmtTlv;MMT (MPEG Media Transport) over TLV, ARIB STD-B32/B60;mmts\n" "MPEG-PS;;;M;MpegPs;;mpeg mpg m2p vob vro pss evo;video/MP2P\n" "MPEG-TS;;;M;MpegTs;;ts m2t m2s m4t m4s tmf ts tp trp ty;video/MP2T\n" "MPEG-4;;;M;Mpeg4;;braw mov mp4 m4v m4a m4b m4p m4r 3ga 3gpa 3gpp 3gp 3gpp2 3g2 k3g jpm jpx mqv ismv isma ismt f4a f4b f4v;video/mp4\n" diff --git a/Source/Resource/Text/DataBase/Format.csv b/Source/Resource/Text/DataBase/Format.csv index 41e383e0d..25d02d0e0 100644 --- a/Source/Resource/Text/DataBase/Format.csv +++ b/Source/Resource/Text/DataBase/Format.csv @@ -25,6 +25,7 @@ ISM;Internet Streaming Media;;M;Ism;;ism;;; IVF;;;M;Ivf;;ivf;;; LXF;;;M;Lxf;;lxf;video/lxf;; Matroska;;;M;Mk;;mkv mk3d mka mks;;https://matroska.org/downloads/windows.html; +MMT/TLV;;;M;MmtTlv;MMT (MPEG Media Transport) over TLV, ARIB STD-B32/B60;mmts;;; MPEG-PS;;;M;MpegPs;;mpeg mpg m2p vob vro pss evo;video/MP2P;; MPEG-TS;;;M;MpegTs;;ts m2t m2s m4t m4s tmf ts tp trp ty;video/MP2T;; MPEG-4;;;M;Mpeg4;;braw mov mp4 m4v m4a m4b m4p m4r 3ga 3gpa 3gpp 3gp 3gpp2 3g2 k3g jpm jpx mqv ismv isma ismt f4a f4b f4v;video/mp4;; From 89b179de11dcb2bafc724978decf87305e052be8 Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Tue, 7 Jul 2026 22:16:03 +0900 Subject: [PATCH 3/8] MMT/TLV: add to the autotools, CMake, MSVC and Qt build systems Registers File_MmtTlv, File_Mmt and File_Mmt_Descriptors in Makefile.am, configure.ac (--disable-mmttlv, MEDIAINFO_MMTTLV_NO/_YES), the CMake feature list, the MSVC 2022 and 2026 projects (vcxproj, filters, UWP) and the Qt project. Signed-off-by: Rainbaby --- Project/CMake/MediaInfoSetupOptions.cmake | 1 + Project/GNU/Library/Makefile.am | 3 +++ Project/GNU/Library/configure.ac | 3 +++ Project/MSVC2022/Library/MediaInfoLib.vcxproj | 6 ++++++ .../Library/MediaInfoLib.vcxproj.filters | 18 ++++++++++++++++++ .../MSVC2022/Library/MediaInfoLib_UWP.vcxproj | 6 ++++++ Project/MSVC2026/Library/MediaInfoLib.vcxproj | 6 ++++++ .../Library/MediaInfoLib.vcxproj.filters | 18 ++++++++++++++++++ .../MSVC2026/Library/MediaInfoLib_UWP.vcxproj | 6 ++++++ Project/Qt/MediaInfoLib.pro | 6 ++++++ 10 files changed, 73 insertions(+) diff --git a/Project/CMake/MediaInfoSetupOptions.cmake b/Project/CMake/MediaInfoSetupOptions.cmake index 3de878692..395e0e9c8 100644 --- a/Project/CMake/MediaInfoSetupOptions.cmake +++ b/Project/CMake/MediaInfoSetupOptions.cmake @@ -133,6 +133,7 @@ set(MEDIAINFO_FEATURE_LIST MINIMAL MIXML MK + MMTTLV MOD MPC MPCSV8 diff --git a/Project/GNU/Library/Makefile.am b/Project/GNU/Library/Makefile.am index 6064e681c..8c7da5eed 100644 --- a/Project/GNU/Library/Makefile.am +++ b/Project/GNU/Library/Makefile.am @@ -153,6 +153,9 @@ lib@MediaInfoLib_LibName@_la_SOURCES = \ ../../../Source/MediaInfo/Multiple/File_Lxf.cpp \ ../../../Source/MediaInfo/Multiple/File_Mk.cpp \ ../../../Source/MediaInfo/Multiple/File_MiXml.cpp \ + ../../../Source/MediaInfo/Multiple/File_MmtTlv.cpp \ + ../../../Source/MediaInfo/Multiple/File_Mmt.cpp \ + ../../../Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp \ ../../../Source/MediaInfo/Multiple/File_Mpeg4.cpp \ ../../../Source/MediaInfo/Multiple/File_Mpeg4_Descriptors.cpp \ ../../../Source/MediaInfo/Multiple/File_Mpeg4_Elements.cpp \ diff --git a/Project/GNU/Library/configure.ac b/Project/GNU/Library/configure.ac index f38660a92..10c522769 100644 --- a/Project/GNU/Library/configure.ac +++ b/Project/GNU/Library/configure.ac @@ -153,6 +153,7 @@ AC_ARG_ENABLE(hdsf4m, AS_HELP_STRING([--disable-hdsf4m], [Disable Multiple - H AC_ARG_ENABLE(hls, AS_HELP_STRING([--disable-hls], [Disable Multiple - HLS]), MediaInfoHls=$enableval, MediaInfoHls=depend) AC_ARG_ENABLE(mk, AS_HELP_STRING([--disable-mk], [Disable Multiple - Matroska]), MediaInfoMk=$enableval, MediaInfoMk=depend) AC_ARG_ENABLE(mixml, AS_HELP_STRING([--disable-mixml], [Disable Multiple - MIXML]), MediaInfoMiXml=$enableval, MediaInfoMiXml=depend) +AC_ARG_ENABLE(mmttlv, AS_HELP_STRING([--disable-mmttlv], [Disable Multiple - MMT/TLV (ARIB MMT over TLV)]), MediaInfoMmtTlv=$enableval, MediaInfoMmtTlv=depend) AC_ARG_ENABLE(mpeg4, AS_HELP_STRING([--disable-mpeg4], [Disable Multiple - MPEG-4 based formats (MOV, AAC...)]), MediaInfoMpeg4=$enableval, MediaInfoMpeg4=depend) AC_ARG_ENABLE(mpegps, AS_HELP_STRING([--disable-mpegps], [Disable Multiple - MPEG-PS]), MediaInfoMpegPs=$enableval, MediaInfoMpegPs=depend) AC_ARG_ENABLE(mpegts, AS_HELP_STRING([--disable-mpegts], [Disable Multiple - MPEG-TS]), MediaInfoMpegTs=$enableval, MediaInfoMpegTs=depend) @@ -335,6 +336,7 @@ if test "$MediaInfohdsF4m" = "no"; then AC_DEFINE(MEDIAINFO_HDSF4M_NO) fi; if if test "$MediaInfoHls" = "no"; then AC_DEFINE(MEDIAINFO_HLS_NO) fi; if test "$MediaInfoHls" = "yes"; then AC_DEFINE(MEDIAINFO_HLS_YES) fi if test "$MediaInfoMk" = "no"; then AC_DEFINE(MEDIAINFO_MK_NO) fi; if test "$MediaInfoMk" = "yes"; then AC_DEFINE(MEDIAINFO_MK_YES) fi if test "$MediaInfoMiXml" = "no"; then AC_DEFINE(MEDIAINFO_MIXML_NO) fi; if test "$MediaInfoMiXml" = "yes"; then AC_DEFINE(MEDIAINFO_MIXML_YES) fi +if test "$MediaInfoMmtTlv" = "no"; then AC_DEFINE(MEDIAINFO_MMTTLV_NO) fi; if test "$MediaInfoMmtTlv" = "yes"; then AC_DEFINE(MEDIAINFO_MMTTLV_YES) fi if test "$MediaInfoMpeg4" = "no"; then AC_DEFINE(MEDIAINFO_MPEG4_NO) fi; if test "$MediaInfoMpeg4" = "yes"; then AC_DEFINE(MEDIAINFO_MPEG4_YES) fi if test "$MediaInfoMpegPs" = "no"; then AC_DEFINE(MEDIAINFO_MPEGPS_NO) fi; if test "$MediaInfoMpegPs" = "yes"; then AC_DEFINE(MEDIAINFO_MPEGPS_YES) fi if test "$MediaInfoMpegTs" = "no"; then AC_DEFINE(MEDIAINFO_MPEGTS_NO) fi; if test "$MediaInfoMpegTs" = "yes"; then AC_DEFINE(MEDIAINFO_MPEGTS_YES) fi @@ -1178,6 +1180,7 @@ Mcho "Hls " "$MediaInfoHls" Mcho "Flv " "$MediaInfoFlv" Mcho "Mk " "$MediaInfoMk" Mcho "MiXml " "$MediaInfoMiXml" +Mcho "MmtTlv " "$MediaInfoMmtTlv" Mcho "Mpeg4 " "$MediaInfoMpeg4" Mcho "MpegPs " "$MediaInfoMpegPs" Mcho "MpegTs " "$MediaInfoMpegTs" diff --git a/Project/MSVC2022/Library/MediaInfoLib.vcxproj b/Project/MSVC2022/Library/MediaInfoLib.vcxproj index dd43ca489..99b708ac7 100644 --- a/Project/MSVC2022/Library/MediaInfoLib.vcxproj +++ b/Project/MSVC2022/Library/MediaInfoLib.vcxproj @@ -582,6 +582,9 @@ + + + @@ -932,6 +935,9 @@ + + + diff --git a/Project/MSVC2022/Library/MediaInfoLib.vcxproj.filters b/Project/MSVC2022/Library/MediaInfoLib.vcxproj.filters index 91ae5180a..2ad6775d4 100644 --- a/Project/MSVC2022/Library/MediaInfoLib.vcxproj.filters +++ b/Project/MSVC2022/Library/MediaInfoLib.vcxproj.filters @@ -353,6 +353,15 @@ Source Files\Multiple + + Source Files\Multiple + + + Source Files\Multiple + + + Source Files\Multiple + Source Files\Multiple @@ -1108,6 +1117,15 @@ Header Files\Multiple + + Header Files\Multiple + + + Header Files\Multiple + + + Header Files\Multiple + Header Files\Multiple diff --git a/Project/MSVC2022/Library/MediaInfoLib_UWP.vcxproj b/Project/MSVC2022/Library/MediaInfoLib_UWP.vcxproj index 3fbdf131b..abd1b119a 100644 --- a/Project/MSVC2022/Library/MediaInfoLib_UWP.vcxproj +++ b/Project/MSVC2022/Library/MediaInfoLib_UWP.vcxproj @@ -387,6 +387,9 @@ + + + @@ -670,6 +673,9 @@ + + + diff --git a/Project/MSVC2026/Library/MediaInfoLib.vcxproj b/Project/MSVC2026/Library/MediaInfoLib.vcxproj index cc1030814..582c6dff5 100644 --- a/Project/MSVC2026/Library/MediaInfoLib.vcxproj +++ b/Project/MSVC2026/Library/MediaInfoLib.vcxproj @@ -582,6 +582,9 @@ + + + @@ -932,6 +935,9 @@ + + + diff --git a/Project/MSVC2026/Library/MediaInfoLib.vcxproj.filters b/Project/MSVC2026/Library/MediaInfoLib.vcxproj.filters index b31bcdf19..b9d04122a 100644 --- a/Project/MSVC2026/Library/MediaInfoLib.vcxproj.filters +++ b/Project/MSVC2026/Library/MediaInfoLib.vcxproj.filters @@ -353,6 +353,15 @@ Source Files\Multiple + + Source Files\Multiple + + + Source Files\Multiple + + + Source Files\Multiple + Source Files\Multiple @@ -1108,6 +1117,15 @@ Header Files\Multiple + + Header Files\Multiple + + + Header Files\Multiple + + + Header Files\Multiple + Header Files\Multiple diff --git a/Project/MSVC2026/Library/MediaInfoLib_UWP.vcxproj b/Project/MSVC2026/Library/MediaInfoLib_UWP.vcxproj index dab1f867d..42250f387 100644 --- a/Project/MSVC2026/Library/MediaInfoLib_UWP.vcxproj +++ b/Project/MSVC2026/Library/MediaInfoLib_UWP.vcxproj @@ -387,6 +387,9 @@ + + + @@ -670,6 +673,9 @@ + + + diff --git a/Project/Qt/MediaInfoLib.pro b/Project/Qt/MediaInfoLib.pro index b87409002..12ee81f63 100644 --- a/Project/Qt/MediaInfoLib.pro +++ b/Project/Qt/MediaInfoLib.pro @@ -176,6 +176,9 @@ HEADERS += \ ../../Source/MediaInfo/Multiple/File_Ivf.h \ ../../Source/MediaInfo/Multiple/File_Lxf.h \ ../../Source/MediaInfo/Multiple/File_Mk.h \ + ../../Source/MediaInfo/Multiple/File_MmtTlv.h \ + ../../Source/MediaInfo/Multiple/File_Mmt.h \ + ../../Source/MediaInfo/Multiple/File_Mmt_Descriptors.h \ ../../Source/MediaInfo/Multiple/File_Mpeg4.h \ ../../Source/MediaInfo/Multiple/File_Mpeg4_Descriptors.h \ ../../Source/MediaInfo/Multiple/File_Mpeg4_TimeCode.h \ @@ -426,6 +429,9 @@ SOURCES += \ ../../Source/MediaInfo/Multiple/File_Lxf.cpp \ ../../Source/MediaInfo/Multiple/File_Mk.cpp \ ../../Source/MediaInfo/Multiple/File_MiXml.cpp \ + ../../Source/MediaInfo/Multiple/File_MmtTlv.cpp \ + ../../Source/MediaInfo/Multiple/File_Mmt.cpp \ + ../../Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp \ ../../Source/MediaInfo/Multiple/File_Mpeg4.cpp \ ../../Source/MediaInfo/Multiple/File_Mpeg4_Descriptors.cpp \ ../../Source/MediaInfo/Multiple/File_Mpeg4_Elements.cpp \ From cbb3c9cea47483eef99835aeab840c9e0ed2da82 Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Thu, 9 Jul 2026 17:09:29 +0900 Subject: [PATCH 4/8] MMT/TLV: review follow-up Signed-off-by: Rainbaby --- Source/MediaInfo/Multiple/File_Mmt.cpp | 35 ++--- Source/MediaInfo/Multiple/File_Mmt.h | 35 ++--- Source/MediaInfo/Multiple/File_MmtTlv.cpp | 124 ++++++------------ Source/MediaInfo/Multiple/File_MmtTlv.h | 94 +++++++------ .../Multiple/File_Mmt_Descriptors.cpp | 11 -- .../MediaInfo/Multiple/File_Mmt_Descriptors.h | 6 +- 6 files changed, 116 insertions(+), 189 deletions(-) diff --git a/Source/MediaInfo/Multiple/File_Mmt.cpp b/Source/MediaInfo/Multiple/File_Mmt.cpp index bd999f694..6d2427aaa 100644 --- a/Source/MediaInfo/Multiple/File_Mmt.cpp +++ b/Source/MediaInfo/Multiple/File_Mmt.cpp @@ -91,16 +91,6 @@ namespace } } -//*************************************************************************** -// Constructor/Destructor -//*************************************************************************** - -//--------------------------------------------------------------------------- -File_Mmt::File_Mmt() -{ - Complete_Stream = NULL; -} - //*************************************************************************** // Buffer - Global //*************************************************************************** @@ -234,10 +224,10 @@ void File_Mmt::Mpt() //Package-level descriptors: not extracted, but named in the trace. File_Mmt_Descriptors Desc; Desc.Complete_Stream = Complete_Stream; + Element_Begin1("MPT_descriptors"); Open_Buffer_Init(&Desc); - Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, mpt_desc_len); - Open_Buffer_Finalize(&Desc); - Skip_XX(mpt_desc_len, "MPT_descriptors"); + Open_Buffer_Continue(&Desc, mpt_desc_len); + Element_End0(); } if (Element_Size - Element_Offset < 1) return; Get_B1 (number_of_assets, "number_of_assets"); @@ -307,10 +297,10 @@ void File_Mmt::Mpt() File_Mmt_Descriptors Desc; Desc.Complete_Stream = Complete_Stream; Desc.CurrentAsset = &a; + Element_Begin1("asset_descriptors"); Open_Buffer_Init(&Desc); - Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, asset_desc_len); - Open_Buffer_Finalize(&Desc); - Skip_XX(asset_desc_len, "asset_descriptors"); + Open_Buffer_Continue(&Desc, asset_desc_len); + Element_End0(); } Found.push_back(a); Element_End0(); @@ -373,10 +363,10 @@ void File_Mmt::MhEit() { File_Mmt_Descriptors Desc; Desc.Complete_Stream = Complete_Stream; //short_event writes name/text into mmt_stream + Element_Begin1("descriptors"); Open_Buffer_Init(&Desc); - Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, desc_loop_len); - Open_Buffer_Finalize(&Desc); - Skip_XX(desc_loop_len, "descriptors"); + Open_Buffer_Continue(&Desc, desc_loop_len); + Element_End0(); } Element_End0(); @@ -438,11 +428,12 @@ void File_Mmt::MhSdt() { File_Mmt_Descriptors Desc; Desc.Complete_Stream = Complete_Stream; + Element_Begin1("descriptors"); Open_Buffer_Init(&Desc); - Open_Buffer_Continue(&Desc, Buffer + Buffer_Offset + (size_t)Element_Offset, desc_len); - Open_Buffer_Finalize(&Desc); + Open_Buffer_Continue(&Desc, desc_len); + Element_End0(); } - if (desc_len) + else if (desc_len) Skip_XX(desc_len, "descriptors"); Element_End0(); diff --git a/Source/MediaInfo/Multiple/File_Mmt.h b/Source/MediaInfo/Multiple/File_Mmt.h index 3da7bae6e..86c1eae4a 100644 --- a/Source/MediaInfo/Multiple/File_Mmt.h +++ b/Source/MediaInfo/Multiple/File_Mmt.h @@ -50,29 +50,24 @@ inline int64s Mmt_DateTime_To_Seconds(int16u mjd, int32u bcd_hhmmss) //*************************************************************************** struct asset { - int32u Type; //asset_type FourCC (little-endian as read) - int16u PacketId; //location_type 0x00 packet_id, else 0 - bool Superimpose; //stpp: superimposed text (true) vs subtitle (false) + int32u Type = 0; //asset_type FourCC (little-endian as read) + int16u PacketId = 0; //location_type 0x00 packet_id, else 0 + bool Superimpose = false; //stpp: superimposed text (true) vs subtitle (false) //MH-audio-component descriptor (0x8014), MP4A assets only: Ztring Language; Ztring Title; //component description (e.g. main audio / commentary) - bool MainComponent; //-> Default track - int8u Handicapped; //audio_for_handicapped: 0b01 = VI commentary - int8u AudioMode; //component_type & 0x1F (0=none) - int8u SamplingCode; //sampling_rate 3-bit code (0=none) + bool MainComponent = false; //-> Default track + int8u Handicapped = 0; //audio_for_handicapped: 0b01 = VI commentary + int8u AudioMode = 0; //component_type & 0x1F (0=none) + int8u SamplingCode = 0; //sampling_rate 3-bit code (0=none) //Video_Component_Descriptor (0x8010): - int8u VideoResolution; //0=none, 6=2160, 7=4320 - int8u VideoAspect; - int8u VideoScan; //0=interlaced, 1=progressive, 0xFF=none - int8u VideoFrameRate; + int8u VideoResolution = 0; //0=none, 6=2160, 7=4320 + int8u VideoAspect = 0; + int8u VideoScan = 0xFF; //0=interlaced, 1=progressive, 0xFF=none + int8u VideoFrameRate = 0; //Asset_Group_Descriptor (0x8000): main+backup of a stream share GroupId; level 0 = default. - int GroupId; - int8u SelectionLevel; - asset() : Type(0), PacketId(0), Superimpose(false), - MainComponent(false), Handicapped(0), - AudioMode(0), SamplingCode(0), - VideoResolution(0), VideoAspect(0), VideoScan(0xFF), VideoFrameRate(0), - GroupId(-1), SelectionLevel(0) {} + int GroupId = -1; + int8u SelectionLevel = 0; }; //*************************************************************************** @@ -140,9 +135,7 @@ class File_Mmt : public File__Analyze { public : //In - set by the container before feeding a table buffer. - mmt_stream* Complete_Stream; - - File_Mmt(); + mmt_stream* Complete_Stream = NULL; private : //Buffer - Global diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.cpp b/Source/MediaInfo/Multiple/File_MmtTlv.cpp index aa2e2472d..4eb6bbcca 100644 --- a/Source/MediaInfo/Multiple/File_MmtTlv.cpp +++ b/Source/MediaInfo/Multiple/File_MmtTlv.cpp @@ -163,17 +163,6 @@ namespace const int64s NTP_UNIX_EPOCH_DELTA = 2208988800LL; - // Bounds checked by caller. - inline int16u RB16(const int8u* p) { return ((int16u)p[0] << 8) | p[1]; } - inline int32u RB24(const int8u* p) - { return ((int32u)p[0] << 16) | ((int32u)p[1] << 8) | p[2]; } - inline int32u RB32(const int8u* p) - { return ((int32u)p[0] << 24) | ((int32u)p[1] << 16) | ((int32u)p[2] << 8) | p[3]; } - inline int32u RL32(const int8u* p) - { return ((int32u)p[3] << 24) | ((int32u)p[2] << 16) | ((int32u)p[1] << 8) | p[0]; } - inline int64u RB64(const int8u* p) - { return ((int64u)RB32(p) << 32) | RB32(p + 4); } - // NTP 32.32 -> us. The epoch offset cancels in a duration, so it is not // subtracted. inline int64s Ntp32_32_To_Us(int64u ntp) @@ -260,37 +249,6 @@ File_MmtTlv::File_MmtTlv() { //Configuration MustSynchronize = true; - - //Init - Eit_Present_Found = false; - Eit_EventId = 0; - Eit_StartDate = 0; - Eit_StartTime = 0; - Eit_Duration = 0; - Eit_ServiceId = 0; - Eit_ServiceId_Found = false; - Mpt_Found = false; - Mpt_AssetCount = -1; - Mpt_Version = -1; - Eit_Start_Utc = -1; - Transfer_Last = 0xFF; - Sdt_Found = false; - Tlv_Stream_Id = -1; - Ecm_Seen = false; - Packets_Since_Accept = 0; - Core_Done_At = (int64u)-1; - Media_Done_Utc = -1; - Media_Probe_Done = false; - Media_Bytes = 0; - Now_Utc = -1; - Tot_Seen = false; - Now_First = -1; - Now_Last = -1; - Pts_First_Us = -1; - Pts_Last_Us = -1; - Pts_Last_At_Tail = -1; - Eit_Boundary_Hops = 0; - Phase = Phase_Scan; } //*************************************************************************** @@ -515,7 +473,7 @@ void File_MmtTlv::Streams_Finish() media_parser& M = It->second; if (!M.Parser) continue; - Open_Buffer_Finalize(M.Parser); + Open_Buffer_Finalize(M.Parser.get()); //Non-erasing: keep the container-level Format/CodecID/ID, add codec detail. Merge(*M.Parser, M.StreamKind, 0, M.StreamPos, false); //Keep the descriptor channels when the decoded ES read fewer (a boundary transient). @@ -565,8 +523,6 @@ void File_MmtTlv::Streams_Finish() if (M.DescScan != 0xFF && Retrieve_Const(Stream_Video, M.StreamPos, Video_ScanType).empty()) Fill(Stream_Video, M.StreamPos, Video_ScanType, M.DescScan ? "Progressive" : "Interlaced"); } - delete M.Parser; - M.Parser = NULL; } MediaParsers.clear(); @@ -633,7 +589,7 @@ bool File_MmtTlv::FileHeader_Begin() ++processed; int8u packet_type = Buffer[i + 1]; - int16u data_length = RB16(Buffer + i + 2); + int16u data_length = BigEndian2int16u(Buffer + i + 2); i += 4; if (packet_type == TLV_HEADER_COMPRESSED_IP_PACKET) @@ -926,31 +882,41 @@ void File_MmtTlv::Parse_Mmtp() Skip_B2( "extension_header_type"); Get_B2 (ext_len, "extension_header_length"); if (Element_Size - Element_Offset < ext_len) { Element_End0(); return; } - //Sub-header chain: (sub_type&0x7FFF)==1 is the scramble sub-header; data[0] bits 4-3 = - //encryption_flag (>=2 means scrambled). Peeked so the PID flag matches the byte pass. + //Sub-header chain: (type & 0x7FFF)==1 is the scramble sub-header, whose first byte + //carries the encryption_flag in bits 4-3 (>=2 means scrambled). + int64u ext_end = Element_Offset + ext_len; + while (Element_Offset + 4 <= ext_end) { - const int8u* e = Buffer + Buffer_Offset + (size_t)Element_Offset; - size_t en = ext_len; - while (en >= 4) + int16u sub_type, sub_len; + Get_B2 (sub_type, "header_extension_type"); + Get_B2 (sub_len, "header_extension_length"); + if (Element_Offset + sub_len > ext_end) + break; + if ((sub_type & 0x7FFF) == 0x0001 && sub_len >= 1) { - int16u sub_type = RB16(e) & 0x7FFF; - int16u sub_len = RB16(e + 2); - if ((size_t)4 + sub_len > en) break; - if (sub_type == 0x0001 && sub_len >= 1 && ((e[4] >> 3) & 0x03) >= 2) + int8u scrambling; + Get_B1 (scrambling, "scrambling_control"); + if (((scrambling >> 3) & 0x03) >= 2) ScrambledPids.insert(packet_id); - e += 4 + sub_len; en -= 4 + sub_len; + if (sub_len > 1) + Skip_XX(sub_len - 1, "header_extension_data"); } + else if (sub_len) + Skip_XX(sub_len, "header_extension_data"); } - Skip_XX(ext_len, "extension_header"); + if (Element_Offset < ext_end) + Skip_XX(ext_end - Element_Offset, "header_extension_data"); } Element_End0(); - //Payload handed to the still-byte-based inner parsers at the current cursor; Data_Parse trails - //with Skip_XX(Element_Size - Element_Offset) to consume it. - const int8u* P = Buffer + Buffer_Offset + (size_t)Element_Offset; - size_t N = (size_t)(Element_Size - Element_Offset); if (payload_type == MMTP_PAYLOAD_SIGNALING) + { + //The reassembler concatenates fragment payloads across packets, so it works on a raw + //buffer rather than the per-element cursor. + const int8u* P = Buffer + Buffer_Offset + (size_t)Element_Offset; + size_t N = (size_t)(Element_Size - Element_Offset); Parse_SignalingMessages(packet_id, seq_num, P, N); + } else if (payload_type == MMTP_PAYLOAD_MPU && !Media_Probe_Done) { std::map::iterator It = MediaParsers.find(packet_id); @@ -960,7 +926,7 @@ void File_MmtTlv::Parse_Mmtp() //always probe; encrypted payload just fails the MFU/NAL checks. Give up on a scrambled //PID feeding nothing so the probe (and tail) finish. ++It->second.MpuSeen; - Parse_Mpu(packet_id, seq_num, P, N); + Parse_Mpu(packet_id, seq_num); if (ScrambledPids.count(packet_id) && It->second.Fed < READABLE_MIN_BYTES && It->second.MpuSeen >= SCRAMBLED_GIVEUP_MPU) @@ -975,9 +941,9 @@ void File_MmtTlv::Parse_Mmtp() //*************************************************************************** //--------------------------------------------------------------------------- -void File_MmtTlv::Parse_Mpu(int16u packet_id, int32u seq_num, const int8u* Data, size_t Size) +void File_MmtTlv::Parse_Mpu(int16u packet_id, int32u seq_num) { - (void)Data; (void)Size; // parse at the element cursor (== Data/Size handed by Parse_Mmtp) + //Parses at the element cursor, which Parse_Mmtp left at the MPU payload. if (Element_Size - Element_Offset < 8) return; @@ -1102,7 +1068,7 @@ bool File_MmtTlv::Create_MediaParser(int32u asset_type, media_parser& M) { File_Hevc* Parser = new File_Hevc; Parser->FrameIsAlwaysComplete = true; - M.Parser = Parser; + M.Parser.reset(Parser); M.StreamKind = Stream_Video; M.Framing = Es_Nal; Open_Buffer_Init(Parser); @@ -1116,7 +1082,7 @@ bool File_MmtTlv::Create_MediaParser(int32u asset_type, media_parser& M) { File_Aac* Parser = new File_Aac; Parser->Mode = File_Aac::Mode_LATM; - M.Parser = Parser; + M.Parser.reset(Parser); M.StreamKind = Stream_Audio; M.Framing = Es_Loas; Open_Buffer_Init(Parser); @@ -1158,7 +1124,7 @@ void File_MmtTlv::Feed_DataUnit(int16u packet_id, const int8u* Data, size_t Size // The DU is be32 length + NAL; emit Annex-B. if (Size < 4) return; - int32u nal_size = RB32(Data); + int32u nal_size = BigEndian2int32u(Data); const int8u* nal = Data + 4; size_t nal_n = Size - 4; if (nal_size != nal_n) @@ -1170,7 +1136,7 @@ void File_MmtTlv::Feed_DataUnit(int16u packet_id, const int8u* Data, size_t Size Frame.insert(Frame.end(), nal, nal + nal_n); } - Open_Buffer_Continue(M.Parser, &Frame[0], Frame.size()); + Open_Buffer_Continue(M.Parser.get(), &Frame[0], Frame.size()); M.Fed += Frame.size(); Media_Bytes += Frame.size(); @@ -1251,12 +1217,12 @@ void File_MmtTlv::Parse_SignalingMessages(int16u packet_id, int32u seq_num, cons if (length_extension) { if (n < 4) return; - length = RB32(p); p += 4; n -= 4; + length = BigEndian2int32u(p); p += 4; n -= 4; } else { if (n < 2) return; - length = RB16(p); p += 2; n -= 2; + length = BigEndian2int16u(p); p += 2; n -= 2; } if (length > n) return; Parse_SignalingMessage(p, (size_t)length); @@ -1270,13 +1236,13 @@ void File_MmtTlv::Parse_SignalingMessage(const int8u* Data, size_t Size) { if (Size < 4) return; - int16u msg_id = RB16(Data); + int16u msg_id = BigEndian2int16u(Data); if (msg_id == MSG_PA_MESSAGE) { // id(16) version(8) length(32) if (Size < 7) return; - int32u length = RB32(Data + 3); + int32u length = BigEndian2int32u(Data + 3); const int8u* body = Data + 7; if (length > Size - 7) length = (int32u)(Size - 7); size_t n = length; @@ -1295,9 +1261,9 @@ void File_MmtTlv::Parse_SignalingMessage(const int8u* Data, size_t Size) int8u tid = p[0]; size_t consumed; if (tid == TABLE_MPT) - consumed = (size_t)4 + RB16(p + 2); + consumed = (size_t)4 + BigEndian2int16u(p + 2); else - consumed = (size_t)3 + (RB16(p + 1) & 0x0FFF); + consumed = (size_t)3 + (BigEndian2int16u(p + 1) & 0x0FFF); if (consumed < 4) break; //Parse a truncated final table against the bytes we have. @@ -1312,7 +1278,7 @@ void File_MmtTlv::Parse_SignalingMessage(const int8u* Data, size_t Size) { // id(16) version(8) length(16) if (Size < 5) return; - int16u length = RB16(Data + 3); + int16u length = BigEndian2int16u(Data + 3); const int8u* body = Data + 5; if (length > Size - 5) length = (int16u)(Size - 5); Parse_Table(body, length); @@ -1365,9 +1331,6 @@ void File_MmtTlv::Parse_Table(const int8u* Data, size_t Size) if (NewVersion) { //Drop the previous package's parsers; its packet_ids and count may differ. - for (std::map::iterator It = MediaParsers.begin(); - It != MediaParsers.end(); ++It) - delete It->second.Parser; MediaParsers.clear(); Mpt_Version = Stream.MptVersion; Media_Probe_Done = false; @@ -1387,7 +1350,7 @@ void File_MmtTlv::Parse_Table(const int8u* Data, size_t Size) continue; media_parser M; if (Create_MediaParser(A.Type, M)) - MediaParsers[A.PacketId] = M; + MediaParsers[A.PacketId] = std::move(M); } } break; @@ -1438,9 +1401,6 @@ void File_MmtTlv::Parse_Table(const int8u* Data, size_t Size) { //The boundary may reconfigure the A/V, so drop the prior program's probe //and re-derive after the hop. - for (std::map::iterator It = MediaParsers.begin(); - It != MediaParsers.end(); ++It) - delete It->second.Parser; MediaParsers.clear(); MfuAssemblers.clear(); Assets.clear(); diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.h b/Source/MediaInfo/Multiple/File_MmtTlv.h index 77b2204b4..ef71a2ed8 100644 --- a/Source/MediaInfo/Multiple/File_MmtTlv.h +++ b/Source/MediaInfo/Multiple/File_MmtTlv.h @@ -25,6 +25,7 @@ #include #include #include +#include //--------------------------------------------------------------------------- namespace MediaInfoLib @@ -67,7 +68,7 @@ private : void Note_StreamNow(int64s Utc); // record earliest/latest "now" bool PidEncrypted(int16u PacketId) const; - void Parse_Mpu(int16u packet_id, int32u seq_num, const int8u* Data, size_t Size); + void Parse_Mpu(int16u packet_id, int32u seq_num); void Feed_DataUnit(int16u packet_id, const int8u* Data, size_t Size); //Child ES parser factory: maps an MPT asset_type (STD-B60) to a MediaInfo @@ -82,10 +83,9 @@ private : struct fragment_assembler { enum state_t { Init, NotStarted, InFragment, Skip }; - state_t State; - int32u LastSeq; + state_t State = Init; + int32u LastSeq = 0; std::vector Data; - fragment_assembler() : State(Init), LastSeq(0) {} }; std::map Assemblers; @@ -104,27 +104,23 @@ private : //MPT-created stream at finish. struct media_parser { - File__Analyze* Parser; //owned; deleted in Streams_Finish - stream_t StreamKind; - size_t StreamPos; //as created in Streams_Fill - es_framing Framing; //how Feed_DataUnit frames a DU for the parser - bool Done; - int64u Fed; //validated bytes fed (readability signal) - int64u MpuSeen; //MPU packets routed here (scramble give-up) - int DescCh; //audio-component channel count - int8u DescCfg; //MPEG-4 channel_configuration for the shared Aac_* strings + std::unique_ptr Parser; //owned; freed with the map entry + stream_t StreamKind = Stream_Video; + size_t StreamPos = 0; //as created in Streams_Fill + es_framing Framing = Es_Nal; //how Feed_DataUnit frames a DU for the parser + bool Done = false; + int64u Fed = 0; //validated bytes fed (readability signal) + int64u MpuSeen = 0; //MPU packets routed here (scramble give-up) + int DescCh = 0; //audio-component channel count + int8u DescCfg = 0; //MPEG-4 channel_configuration for the shared Aac_* strings //Video geometry from 0x8010; fills the ES's gaps at finish. - int DescWidth, DescHeight; - float64 DescFrameRate; - int8u DescScan; //0xFF=none - media_parser() : Parser(NULL), StreamKind(Stream_Video), StreamPos(0), - Framing(Es_Nal), Done(false), Fed(0), MpuSeen(0), - DescCh(0), DescCfg(0), - DescWidth(0), DescHeight(0), DescFrameRate(0), DescScan(0xFF) {} + int DescWidth = 0, DescHeight = 0; + float64 DescFrameRate = 0; + int8u DescScan = 0xFF; //0xFF=none }; std::map MediaParsers; - bool Media_Probe_Done; - int64u Media_Bytes; //global probe budget + bool Media_Probe_Done = false; + int64u Media_Bytes = 0; //global probe budget //PIDs flagged scrambled in the MMTP scramble sub-header. std::set ScrambledPids; @@ -132,61 +128,61 @@ private : //An ECM (0x82/0x83) was seen -> CAS active. A scrambled PID counts as //encrypted only after this: a descrambled file's pre-ECM lead-in is still //scrambled but not encrypted. - bool Ecm_Seen; + bool Ecm_Seen = false; - bool Eit_Present_Found; + bool Eit_Present_Found = false; Ztring Eit_EventName; Ztring Eit_EventText; //short event description - int16u Eit_ServiceId; //matches the SDT service - bool Eit_ServiceId_Found; - int16u Eit_EventId; - int16u Eit_StartDate; //MJD - int32u Eit_StartTime; //24-bit BCD HHMMSS - int64s Eit_Start_Utc; //present event start, UTC (settle grace) - int32u Eit_Duration; //24-bit BCD HHMMSS + int16u Eit_ServiceId = 0; //matches the SDT service + bool Eit_ServiceId_Found = false; + int16u Eit_EventId = 0; + int16u Eit_StartDate = 0; //MJD + int32u Eit_StartTime = 0; //24-bit BCD HHMMSS + int64s Eit_Start_Utc = -1; //present event start, UTC (settle grace) + int32u Eit_Duration = 0; //24-bit BCD HHMMSS Ztring Eit_Language; - bool Mpt_Found; - int Mpt_AssetCount; //media assets in the committed MPT version (fullest within it) - int Mpt_Version; //committed MPT version; a change replaces the track set - int8u Transfer_Last; //newest 0x8010 transfer code (STD-B60 7-51); 0xFF = none seen + bool Mpt_Found = false; + int Mpt_AssetCount = -1; //media assets in the committed MPT version (fullest within it) + int Mpt_Version = -1; //committed MPT version; a change replaces the track set + int8u Transfer_Last = 0xFF; //newest 0x8010 transfer code (STD-B60 7-51); 0xFF = none seen //Per-channel, so it survives an EIT boundary re-scan (unlike the MPT). - bool Sdt_Found; + bool Sdt_Found = false; Ztring Sdt_ServiceName; Ztring Sdt_Provider; Ztring Sdt_ServiceType; //TLV stream id (transport_stream_id analog) from the MH-SDT/MH-EIT header. -1 = not seen. - int32s Tlv_Stream_Id; + int32s Tlv_Stream_Id = -1; //Stream wall clock, JST seconds since the Unix epoch. MH-TOT preferred, NTP //transmit timestamp fallback. -1 = not seen. - bool Tot_Seen; //authoritative MH-TOT clock seen; until then NTP advances it - int64s Now_Utc; //latest, drives the boundary check - int64s Now_First; - int64s Now_Last; //incl. tail probe + bool Tot_Seen = false; //authoritative MH-TOT clock seen; until then NTP advances it + int64s Now_Utc = -1; //latest, drives the boundary check + int64s Now_First = -1; + int64s Now_Last = -1; //incl. tail probe //Min/max video MPU presentation time, us. In the clear, so it works for //scrambled video. -1 = none seen. - int64s Pts_First_Us; - int64s Pts_Last_Us; - int64s Pts_Last_At_Tail; //snapshot on entering the tail probe + int64s Pts_First_Us = -1; + int64s Pts_Last_Us = -1; + int64s Pts_Last_At_Tail = -1; //snapshot on entering the tail probe //Bounded so a bad stream is not walked forever. - int Eit_Boundary_Hops; + int Eit_Boundary_Hops = 0; enum probe_phase { Phase_Scan, Phase_Tail, Phase_Done }; - probe_phase Phase; + probe_phase Phase = Phase_Scan; //Bound on scanning past accept, so a huge file is not read end-to-end when //the present EIT never appears. - int64u Packets_Since_Accept; + int64u Packets_Since_Accept = 0; //When the core (MPT + present EIT + media probe) completed; a bounded grace //after it catches the less-frequent MH-SDT. -1 = not yet. - int64u Core_Done_At; - int64s Media_Done_Utc; //clock when MPT+media first done, to time-box the EIT wait + int64u Core_Done_At = (int64u)-1; + int64s Media_Done_Utc = -1; //clock when MPT+media first done, to time-box the EIT wait }; } //NameSpace diff --git a/Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp index fd3a10dae..bbf1a7d29 100644 --- a/Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp +++ b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.cpp @@ -245,17 +245,6 @@ namespace } } -//*************************************************************************** -// Constructor/Destructor -//*************************************************************************** - -//--------------------------------------------------------------------------- -File_Mmt_Descriptors::File_Mmt_Descriptors() -{ - Complete_Stream = NULL; - CurrentAsset = NULL; -} - //*************************************************************************** // Buffer - Global //*************************************************************************** diff --git a/Source/MediaInfo/Multiple/File_Mmt_Descriptors.h b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.h index e4e6f3bc3..c196de0dd 100644 --- a/Source/MediaInfo/Multiple/File_Mmt_Descriptors.h +++ b/Source/MediaInfo/Multiple/File_Mmt_Descriptors.h @@ -32,10 +32,8 @@ class File_Mmt_Descriptors : public File__Analyze { public : //In - set by File_Mmt before feeding a descriptor loop. - mmt_stream* Complete_Stream; - asset* CurrentAsset; //the MPT asset being built (NULL for non-asset loops) - - File_Mmt_Descriptors(); + mmt_stream* Complete_Stream = NULL; + asset* CurrentAsset = NULL; //the MPT asset being built (NULL for non-asset loops) private : //Buffer - Global From a2e37ff9124612c35bf4f6492718a0f5eebb88df Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Thu, 9 Jul 2026 18:11:20 +0900 Subject: [PATCH 5/8] MMT/TLV: decide the present-event boundary only with a clock The forward scan could commit the present event and finalize before the first NTP/TOT arrived, so a recording that opens a few seconds before a program boundary froze on the outgoing program (e.g. a 1-minute PR spot) and missed the target program's assets. Leave the event uncommitted until a clock is in hand, so the boundary hop always runs and the scan cannot finalize early. Signed-off-by: Rainbaby --- Source/MediaInfo/Multiple/File_MmtTlv.cpp | 86 ++++++++++++----------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.cpp b/Source/MediaInfo/Multiple/File_MmtTlv.cpp index 4eb6bbcca..dcd7c3a4b 100644 --- a/Source/MediaInfo/Multiple/File_MmtTlv.cpp +++ b/Source/MediaInfo/Multiple/File_MmtTlv.cpp @@ -1380,55 +1380,59 @@ void File_MmtTlv::Parse_Table(const int8u* Data, size_t Size) if (!Stream.EitParsed) break; + //The boundary decision below compares the present event's window to the wall clock, so + //it needs one. Do not assume the first EIT arrives before or after the first NTP/TOT: + //without a clock, leave the present event uncommitted (Eit_Present_Found stays false, so + //the forward scan cannot finalize) and re-decide on a later EIT once the clock is in hand. + if (Now_Utc < 0) + break; + //If this present event has already ended at "now", it is the previous program's tail //(the stream started on a boundary): hop forward and re-scan for the one whose window //contains "now". bool hopped = false; - if (Now_Utc >= 0) + int64s start_s = Mmt_DateTime_To_Seconds(Stream.EitStartDate, Stream.EitStartTime); // JST + if (start_s >= 0) { - int64s start_s = Mmt_DateTime_To_Seconds(Stream.EitStartDate, Stream.EitStartTime); // JST - if (start_s >= 0) + start_s -= Mmt_JST_Offset_Seconds; // -> UTC, to compare with Now_Utc + Eit_Start_Utc = start_s; + int dur_h = Mmt_Bcd2((int8u)(Stream.EitDuration >> 16)); + int dur_m = Mmt_Bcd2((int8u)(Stream.EitDuration >> 8)); + int dur_s = Mmt_Bcd2((int8u)(Stream.EitDuration)); + int64s dur = dur_h * 3600 + dur_m * 60 + dur_s; + int64s end_s = start_s + dur; + if (dur > 0 && end_s <= Now_Utc + BOUNDARY_GUARD_SECONDS + && Eit_Boundary_Hops < BOUNDARY_MAX_HOPS) { - start_s -= Mmt_JST_Offset_Seconds; // -> UTC, to compare with Now_Utc - Eit_Start_Utc = start_s; - int dur_h = Mmt_Bcd2((int8u)(Stream.EitDuration >> 16)); - int dur_m = Mmt_Bcd2((int8u)(Stream.EitDuration >> 8)); - int dur_s = Mmt_Bcd2((int8u)(Stream.EitDuration)); - int64s dur = dur_h * 3600 + dur_m * 60 + dur_s; - int64s end_s = start_s + dur; - if (dur > 0 && end_s <= Now_Utc + BOUNDARY_GUARD_SECONDS - && Eit_Boundary_Hops < BOUNDARY_MAX_HOPS) + //The boundary may reconfigure the A/V, so drop the prior program's probe + //and re-derive after the hop. + MediaParsers.clear(); + MfuAssemblers.clear(); + Assets.clear(); + Mpt_Found = false; + Mpt_AssetCount = -1; + Transfer_Last = 0xFF; + Media_Probe_Done = false; + Media_Bytes = 0; + Core_Done_At = (int64u)-1; // core re-derives at new pos + Media_Done_Utc = -1; + + ++Eit_Boundary_Hops; + int64u here = File_Offset + Buffer_Offset; + //Size the hop to land just past the event's end, from the byte rate + //observed so far; a fixed nudge cannot cross a minute-long tail. + int64u hop = BOUNDARY_HOP_BYTES; + if (Now_Utc > Now_First && end_s + 2 > Now_Utc) { - //The boundary may reconfigure the A/V, so drop the prior program's probe - //and re-derive after the hop. - MediaParsers.clear(); - MfuAssemblers.clear(); - Assets.clear(); - Mpt_Found = false; - Mpt_AssetCount = -1; - Transfer_Last = 0xFF; - Media_Probe_Done = false; - Media_Bytes = 0; - Core_Done_At = (int64u)-1; // core re-derives at new pos - Media_Done_Utc = -1; - - ++Eit_Boundary_Hops; - int64u here = File_Offset + Buffer_Offset; - //Size the hop to land just past the event's end, from the byte rate - //observed so far; a fixed nudge cannot cross a minute-long tail. - int64u hop = BOUNDARY_HOP_BYTES; - if (Now_Utc > Now_First && end_s + 2 > Now_Utc) - { - int64u rate = here / (int64u)(Now_Utc - Now_First); - if (rate > 0 && (int64u)(end_s + 2 - Now_Utc) > BOUNDARY_HOP_BYTES / rate) - hop = rate * (int64u)(end_s + 2 - Now_Utc); - } - int64u target = here + hop; - if (File_Size != (int64u)-1 && target >= File_Size) - target = here + BOUNDARY_HOP_BYTES / 4; // small final nudge - GoTo(target, "MmtTlv"); - hopped = true; + int64u rate = here / (int64u)(Now_Utc - Now_First); + if (rate > 0 && (int64u)(end_s + 2 - Now_Utc) > BOUNDARY_HOP_BYTES / rate) + hop = rate * (int64u)(end_s + 2 - Now_Utc); } + int64u target = here + hop; + if (File_Size != (int64u)-1 && target >= File_Size) + target = here + BOUNDARY_HOP_BYTES / 4; // small final nudge + GoTo(target, "MmtTlv"); + hopped = true; } } if (hopped) From 97d569dd522904ab5f65b12cc26a4fc7cd6baf80 Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Fri, 10 Jul 2026 06:11:40 +0900 Subject: [PATCH 6/8] MMT/TLV: review follow-up Signed-off-by: Rainbaby --- Source/MediaInfo/Multiple/File_MmtTlv.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.cpp b/Source/MediaInfo/Multiple/File_MmtTlv.cpp index dcd7c3a4b..1fb29ca71 100644 --- a/Source/MediaInfo/Multiple/File_MmtTlv.cpp +++ b/Source/MediaInfo/Multiple/File_MmtTlv.cpp @@ -1320,7 +1320,7 @@ void File_MmtTlv::Parse_Table(const int8u* Data, size_t Size) //(dropping what the new program no longer carries); within a version keep the fullest, //as the asset list builds up after a tune-in. The tail probe is a different program //near EOF, so skip it. - std::vector& Found = Stream.Assets; + const std::vector& Found = Stream.Assets; int Media = 0; for (size_t i = 0; i < Found.size(); ++i) if (Found[i].Type == ASSET_HEV1 || Found[i].Type == ASSET_HVC1 || Found[i].Type == ASSET_MP4A || Found[i].Type == ASSET_STPP) @@ -1493,6 +1493,7 @@ void File_MmtTlv::Parse_Table(const int8u* Data, size_t Size) } case TABLE_ECM_0: // presence = CAS active; not parsed case TABLE_ECM_1: Ecm_Seen = true; // and name it in the trace, below + [[fallthrough]]; default: #if MEDIAINFO_TRACE //Not parsed; route through the signaling sub-parser only for the From 533beec8564c6bf47271d4e8ccfc7a08465e2365 Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Tue, 14 Jul 2026 00:33:03 +0900 Subject: [PATCH 7/8] MMT/TLV: trace fragmented NAL access units at their real file offset A fragmented NAL was reassembled and fed to the child at the last fragment's offset, so it was traced past the MPU the bytes came from. Feed each fragment as it arrives (FrameIsAlwaysComplete=false, MustAdaptSubOffsets=true) so it is traced where it lives. AAC keeps reassembling first, since its LOAS length header is only known once the whole unit is in hand. Signed-off-by: Rainbaby --- Source/MediaInfo/Multiple/File_MmtTlv.cpp | 72 +++++++++++++++++++++-- Source/MediaInfo/Multiple/File_MmtTlv.h | 1 + 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.cpp b/Source/MediaInfo/Multiple/File_MmtTlv.cpp index 1fb29ca71..a2e0bd568 100644 --- a/Source/MediaInfo/Multiple/File_MmtTlv.cpp +++ b/Source/MediaInfo/Multiple/File_MmtTlv.cpp @@ -1021,6 +1021,11 @@ void File_MmtTlv::Parse_Mpu(int16u packet_id, int32u seq_num) } Ass.LastSeq = seq_num; + // HEVC is fed fragment-by-fragment, so each fragment is parsed at its real file offset. AAC is + // not: its LOAS length header is unknown until the whole DU is reassembled. + std::map::iterator MpIt = MediaParsers.find(packet_id); + bool nal = (MpIt != MediaParsers.end() && MpIt->second.Framing == Es_Nal); + switch (fragmentation_indicator) { case 0: // NOT_FRAGMENTED (complete DU) @@ -1030,18 +1035,31 @@ void File_MmtTlv::Parse_Mpu(int16u packet_id, int32u seq_num) break; case 1: // FIRST Ass.State = fragment_assembler::InFragment; - Ass.Data.assign(p, p + n); + if (nal) + Feed_NalFragment(packet_id, p, n, true); + else + Ass.Data.assign(p, p + n); break; case 2: // MIDDLE if (Ass.State == fragment_assembler::InFragment) - Ass.Data.insert(Ass.Data.end(), p, p + n); + { + if (nal) + Feed_NalFragment(packet_id, p, n, false); + else + Ass.Data.insert(Ass.Data.end(), p, p + n); + } break; case 3: // LAST if (Ass.State == fragment_assembler::InFragment) { - Ass.Data.insert(Ass.Data.end(), p, p + n); - if (!Ass.Data.empty()) - Feed_DataUnit(packet_id, &Ass.Data[0], Ass.Data.size()); + if (nal) + Feed_NalFragment(packet_id, p, n, false); + else + { + Ass.Data.insert(Ass.Data.end(), p, p + n); + if (!Ass.Data.empty()) + Feed_DataUnit(packet_id, &Ass.Data[0], Ass.Data.size()); + } } Ass.Data.clear(); Ass.State = fragment_assembler::NotStarted; @@ -1067,7 +1085,8 @@ bool File_MmtTlv::Create_MediaParser(int32u asset_type, media_parser& M) #if defined(MEDIAINFO_HEVC_YES) { File_Hevc* Parser = new File_Hevc; - Parser->FrameIsAlwaysComplete = true; + Parser->FrameIsAlwaysComplete = false; // a NAL spans several MFU fragments; stream them + Parser->MustAdaptSubOffsets = true; // trace each fragment at its real file offset M.Parser.reset(Parser); M.StreamKind = Stream_Video; M.Framing = Es_Nal; @@ -1144,6 +1163,47 @@ void File_MmtTlv::Feed_DataUnit(int16u packet_id, const int8u* Data, size_t Size M.Done = true; } +//--------------------------------------------------------------------------- +// Feeds one MFU fragment of a NAL to the child at the current element cursor, so its trace lands +// at the fragment's real file offset (MustAdaptSubOffsets re-aligns the child between feeds). The +// first fragment's leading be32 NAL length becomes the Annex-B start code; the rest is fed raw. +void File_MmtTlv::Feed_NalFragment(int16u packet_id, const int8u* Data, size_t Size, bool First) +{ + std::map::iterator It = MediaParsers.find(packet_id); + if (It == MediaParsers.end()) + return; + media_parser& M = It->second; + if (M.Done || !M.Parser) + return; + + std::vector Frame; + if (First) + { + if (Size < 4) // be32 length prefix + return; + Frame.reserve(3 + (Size - 4)); + Frame.push_back(0x00); + Frame.push_back(0x00); + Frame.push_back(0x01); + Frame.insert(Frame.end(), Data + 4, Data + Size); + } + else + { + if (!Size) + return; + Frame.assign(Data, Data + Size); + } + if (Frame.empty()) + return; + + Open_Buffer_Continue(M.Parser.get(), &Frame[0], Frame.size()); + M.Fed += Frame.size(); + Media_Bytes += Frame.size(); + + if (M.Parser->Status[IsAccepted] || M.Parser->Status[IsFinished]) + M.Done = true; +} + //--------------------------------------------------------------------------- // Reassembly keyed by packet_id; aggregation and fragmentation are mutually // exclusive. A completed message goes to Parse_SignalingMessage. diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.h b/Source/MediaInfo/Multiple/File_MmtTlv.h index ef71a2ed8..b7d853274 100644 --- a/Source/MediaInfo/Multiple/File_MmtTlv.h +++ b/Source/MediaInfo/Multiple/File_MmtTlv.h @@ -70,6 +70,7 @@ private : void Parse_Mpu(int16u packet_id, int32u seq_num); void Feed_DataUnit(int16u packet_id, const int8u* Data, size_t Size); + void Feed_NalFragment(int16u packet_id, const int8u* Data, size_t Size, bool First); //Child ES parser factory: maps an MPT asset_type (STD-B60) to a MediaInfo //parser + stream kind + DU framing. Returns false for a type with no ES From e62f133dea1e01b5f9e0a09afafa6e684d2bcc77 Mon Sep 17 00:00:00 2001 From: Rainbaby Date: Tue, 14 Jul 2026 00:33:04 +0900 Subject: [PATCH 8/8] MMT/TLV: label the MPU payload and fragmentation indicator in the trace The MPU line gave no hint what its payload was, so the child ES appeared with nothing tying it to a codec. Name it from the MPT asset_type (4CC plus codec, so an unmapped type still shows its 4CC), and spell out fragmentation_indicator. Trace only. Signed-off-by: Rainbaby --- Source/MediaInfo/Multiple/File_MmtTlv.cpp | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Source/MediaInfo/Multiple/File_MmtTlv.cpp b/Source/MediaInfo/Multiple/File_MmtTlv.cpp index a2e0bd568..84a95af30 100644 --- a/Source/MediaInfo/Multiple/File_MmtTlv.cpp +++ b/Source/MediaInfo/Multiple/File_MmtTlv.cpp @@ -106,6 +106,41 @@ namespace const int32u ASSET_MP4A = 0x6134706D; // 'mp4a' const int32u ASSET_STPP = 0x70707473; // 'stpp' + // asset_type -> trace label for the MPU line: the printable 4CC (as the MPT declared it) plus a + // friendly codec name when known. An un-mapped/future asset_type still shows its 4CC, so the MPU + // payload is never an unlabelled block. + inline Ztring Mmt_AssetLabel(int32u asset_type) + { + Ztring Label; // asset_type was read little-endian, so byte i spells 4CC character i + for (int i = 0; i < 4; ++i) + { + int8u c = (int8u)(asset_type >> (8 * i)); + Label += (c >= 0x20 && c < 0x7F) ? (Char)c : (Char)__T('.'); + } + switch (asset_type) + { + case ASSET_HEV1: + case ASSET_HVC1: Label += __T(" (HEVC)"); break; + case ASSET_MP4A: Label += __T(" (AAC)"); break; + case ASSET_STPP: Label += __T(" (TTML)"); break; + default:; + } + return Label; + } + + // MMTP MPU fragmentation_indicator (ISO/IEC 23008-1): where this fragment sits in its data unit. + inline const char* Mmt_fragmentation_indicator(int8u i) + { + switch (i) + { + case 0: return "Not fragmented"; + case 1: return "First"; + case 2: return "Middle"; + case 3: return "Last"; + default: return ""; + } + } + // Stop scanning after this many packets, so a multi-GB file is not read // end-to-end when the present EIT never arrives. const int64u GIVE_UP_AFTER_PACKETS = 200000; @@ -961,6 +996,7 @@ void File_MmtTlv::Parse_Mpu(int16u packet_id, int32u seq_num) Get_S1 (4, fragment_type, "fragment_type"); Get_SB ( timed_flag, "timed_flag"); Get_S1 (2, fragmentation_indicator, "fragmentation_indicator"); + Param_Info1(Mmt_fragmentation_indicator(fragmentation_indicator)); Get_SB ( aggregation_flag, "aggregation_flag"); BS_End(); Skip_B1( "fragment_counter"); @@ -972,6 +1008,16 @@ void File_MmtTlv::Parse_Mpu(int16u packet_id, int32u seq_num) return; } + // Name the MFU payload on the MPU line (hev1/mp4a/... + codec), so the child ES that follows is + // not an unlabelled block. Sourced from the MPT's asset_type, so a type with no ES parser (or a + // future one) still shows its 4CC. + for (size_t i = 0; i < Assets.size(); ++i) + if (Assets[i].PacketId == packet_id) + { + Element_Info1(Mmt_AssetLabel(Assets[i].Type)); + break; + } + // Per-DU header before each fragment: // timed -> movie_fragment_seq(32) sample(32) offset(32) prio(8) dep(8) // !timed -> item_id(32)