From 864da468c08440402f4e91649f8c50c4fcbaf18f Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Tue, 22 Apr 2025 17:01:54 -0700 Subject: [PATCH 01/11] add MP4GetMovieIndTrackNALUnitLength API setup unit test --- IsoLib/libisomediafile/src/MP4Movies.c | 27 +++++++++ IsoLib/libisomediafile/src/MP4Movies.h | 10 ++++ test/test_01_simple.cpp | 2 +- test/test_t35.cpp | 82 ++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 test/test_t35.cpp diff --git a/IsoLib/libisomediafile/src/MP4Movies.c b/IsoLib/libisomediafile/src/MP4Movies.c index 26f00f11..778832e6 100644 --- a/IsoLib/libisomediafile/src/MP4Movies.c +++ b/IsoLib/libisomediafile/src/MP4Movies.c @@ -818,6 +818,33 @@ MP4GetMovieIndTrackSampleEntryType(MP4Movie theMovie, u32 idx, u32 *SEType) return err; } +MP4_EXTERN(MP4Err) +MP4GetMovieIndTrackNALUnitLength(MP4Movie theMovie, u32 idx, u32 *naluLength) +{ + MP4Err err; + MP4Track trak; + MP4TrackReader reader; + MP4Handle sampleEntryH; + + MP4NewHandle(0, &sampleEntryH); + + err = MP4GetMovieIndTrack(theMovie, idx, &trak); + if(err) goto bail; + + err = MP4CreateTrackReader(trak, &reader); + if(err) goto bail; + + err = MP4TrackReaderGetCurrentSampleDescription(reader, sampleEntryH); + if(err) goto bail; + + err = ISOGetNALUnitLength(sampleEntryH, naluLength); + +bail: + TEST_RETURN(err); + MP4DisposeHandle(sampleEntryH); + return err; +} + MP4_EXTERN(MP4Err) MP4GetMovieTrack(MP4Movie theMovie, u32 trackID, MP4Track *outTrack) { diff --git a/IsoLib/libisomediafile/src/MP4Movies.h b/IsoLib/libisomediafile/src/MP4Movies.h index 04383604..9416039b 100644 --- a/IsoLib/libisomediafile/src/MP4Movies.h +++ b/IsoLib/libisomediafile/src/MP4Movies.h @@ -764,6 +764,16 @@ extern "C" */ MP4_EXTERN(MP4Err) MP4GetMovieIndTrackSampleEntryType(MP4Movie theMovie, u32 idx, u32 *SEType); + /** + * @brief Get number of bytes that is used to signal the length of a NAL unit. + * + * @note This function only returns the NALU length of the first sample entry. + * @param theMovie input movie object + * @param idx index of the track ranges between 1 and the number of tracks in theMovie. + * @param naluLength [out] number of bytes to signal NAL unit length. + */ + MP4_EXTERN(MP4Err) MP4GetMovieIndTrackNALUnitLength(MP4Movie theMovie, u32 idx, u32 *naluLength); + /* MP4_EXTERN ( MP4Err ) MP4GetMovieInitialBIFSTrack( MP4Movie theMovie, MP4Track *outBIFSTrack ); diff --git a/test/test_01_simple.cpp b/test/test_01_simple.cpp index 9fe09686..a85d2bc6 100644 --- a/test/test_01_simple.cpp +++ b/test/test_01_simple.cpp @@ -28,7 +28,7 @@ #include const std::string strDataPath = TESTDATA_PATH; -const std::string strTestFile = strDataPath + +"/isobmff/01_simple.mp4"; +const std::string strTestFile = strDataPath + "/isobmff/01_simple.mp4"; // isobmff stuff ISOMovie cMovieBox; diff --git a/test/test_t35.cpp b/test/test_t35.cpp new file mode 100644 index 00000000..61e5b9a8 --- /dev/null +++ b/test/test_t35.cpp @@ -0,0 +1,82 @@ +/** + * @file test_t35.cpp + * @author Dimitri Podborski + * @brief Perform checks on T.35 + * @version 0.1 + * @date 2025-04-18 + * + * @copyright This software module was originally developed by Apple Computer, Inc. in the course of + * development of MPEG-4. This software module is an implementation of a part of one or more MPEG-4 + * tools as specified by MPEG-4. ISO/IEC gives users of MPEG-4 free license to this software module + * or modifications thereof for use in hardware or software products claiming conformance to MPEG-4. + * Those intending to use this software module in hardware or software products are advised that its + * use may infringe existing patents. The original developer of this software module and his/her + * company, the subsequent editors and their companies, and ISO/IEC have no liability for use of + * this software module or modifications thereof in an implementation. Copyright is not released for + * non MPEG-4 conforming products. Apple Computer, Inc. retains full right to use the code for its + * own purpose, assign or donate the code to a third party and to inhibit third parties from using + * the code for non MPEG-4 conforming products. This copyright notice must be included in all copies + * or derivative works. Copyright (c) 1999. + * + */ + +// fix +// mdat size correction +// 5796 - 85 = 5711 (0x164F) + +// first sample size correction +// 1839 - 85 = 1754 +// 0x07 0x2F -> 0x06 0xDA + +#include +#include "test_helpers.h" +#include "testdataPath.h" +#include + +const std::string strDataPath = TESTDATA_PATH; +const std::string strTestFile = strDataPath + "/isobmff/hvc1_hdr10plus_original.mp4"; + +/** + * @brief Starting point for this testing case + * + */ +TEST_CASE("T35") +{ + std::string strT35marking = "test_t35_marking.mp4"; + + /** + * @brief Read a file with T.35 NAL Units and create a sample group that is used for marking purposes only + * + * create Defragmented file with NORMAL SampleToGroupBox + * create Defragmented file with COMPACT SampleToGroupBox + * create Defragmented file with AUTO SampleToGroupBox (smaller size is used automatically) + * + */ + SECTION("Check defragmentation of sample groups") + { + MP4Err err; + MP4Movie moov; + MP4Track trak; + MP4Media media; + + err = MP4OpenMovieFile(&moov, strTestFile.c_str(), MP4OpenMovieDebug); + err = MP4GetMovieIndTrack(moov, 1, &trak); + err = MP4GetTrackMedia(trak, &media); + + u32 codecType = 0; + u32 nalUnitLength = 0; + err = MP4GetMovieIndTrackSampleEntryType(moov, 1, &codecType); + err = MP4GetMovieIndTrackNALUnitLength(moov, 1, &nalUnitLength); + REQUIRE(codecType == ISOHEVCSampleEntryAtomType); + CHECK(nalUnitLength == 4); + + u32 sampleCnt = 0; + err = MP4GetMediaSampleCount(media, &sampleCnt); + CHECK(30 == sampleCnt); + + // TBD iterate through samples and look through T.35 SEI NAL Units + // we need to build up a table that will contain + // T.35 data and the number of sample the same data was detected in. For example data_blob1 in sample 1,2,5. data_blob2 in samples 3,4,5,6 etc. + + } +} From f6e0ab13fbc0cfaedb955cabf813eabd9482fbd0 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Mon, 28 Apr 2025 14:06:23 -0700 Subject: [PATCH 02/11] ISOAddT35GroupDescription and other things --- IsoLib/libisomediafile/src/MP4Atoms.h | 3 +- IsoLib/libisomediafile/src/MP4Media.c | 37 +++++ IsoLib/libisomediafile/src/MP4Movies.h | 11 ++ IsoLib/libisomediafile/src/SampleTableAtom.c | 4 + test/test_data.h | 7 + test/test_helpers.h | 117 +++++--------- test/test_t35.cpp | 152 +++++++++++++++---- 7 files changed, 226 insertions(+), 105 deletions(-) diff --git a/IsoLib/libisomediafile/src/MP4Atoms.h b/IsoLib/libisomediafile/src/MP4Atoms.h index 1e2d15c3..94bdbaff 100644 --- a/IsoLib/libisomediafile/src/MP4Atoms.h +++ b/IsoLib/libisomediafile/src/MP4Atoms.h @@ -169,7 +169,8 @@ enum MP4MetadataLocaleBoxType = MP4_FOUR_CHAR_CODE('l', 'o', 'c', 'a'), MP4MetadataSetupBoxType = MP4_FOUR_CHAR_CODE('s', 'e', 't', 'u'), MP4GroupsListBoxType = MP4_FOUR_CHAR_CODE('g', 'r', 'p', 'l'), - MP4AlternativeEntityGroup = MP4_FOUR_CHAR_CODE('a', 'l', 't', 'r') + MP4AlternativeEntityGroup = MP4_FOUR_CHAR_CODE('a', 'l', 't', 'r'), + MP4T35SampleGroupEntry = MP4_FOUR_CHAR_CODE('i', 't', '3', '5') }; diff --git a/IsoLib/libisomediafile/src/MP4Media.c b/IsoLib/libisomediafile/src/MP4Media.c index 2e9212fd..8481cf18 100644 --- a/IsoLib/libisomediafile/src/MP4Media.c +++ b/IsoLib/libisomediafile/src/MP4Media.c @@ -177,7 +177,42 @@ ISOAddGroupDescription(MP4Media media, u32 groupType, MP4Handle description, u32 bail: TEST_RETURN(err); + return err; +} + +MP4_EXTERN(MP4Err) +ISOAddT35GroupDescription(MP4Media media, MP4Handle itu_t_t35_data, u32 complete_message_flag, u32 *index) +{ + MP4Err err; + MP4MediaAtomPtr mdia; + MP4Handle description = NULL; + MP4Handle prefix = NULL; + + if(media == NULL || itu_t_t35_data == NULL) + { + BAILWITHERROR(MP4BadParamErr); + } + mdia = (MP4MediaAtomPtr)media; + + err = MP4NewHandle(1, &prefix); + if (err) goto bail; + (*prefix)[0] = (complete_message_flag ? 0x80 : 0x00); + + err = MP4NewHandle(0, &description); + if (err) goto bail; + err = MP4HandleCat(description, prefix); + if (err) goto bail; + err = MP4HandleCat(description, itu_t_t35_data); + if (err) goto bail; + err = mdia->addGroupDescription(mdia, MP4T35SampleGroupEntry, description, index); + +bail: + if (prefix) + MP4DisposeHandle(prefix); + if (description) + MP4DisposeHandle(description); + TEST_RETURN(err); return err; } @@ -327,6 +362,8 @@ ISOGetSampleGroupSampleNumbers(MP4Media media, u32 groupType, u32 groupIndex, return err; } +/* TODO: add an API that will get sample numbers based on T35 header (if it35 is used for marking samples) */ + MP4_EXTERN(MP4Err) ISOSetSampleDependency(MP4Media media, s32 sample_index, MP4Handle dependencies) { diff --git a/IsoLib/libisomediafile/src/MP4Movies.h b/IsoLib/libisomediafile/src/MP4Movies.h index 9416039b..078a442a 100644 --- a/IsoLib/libisomediafile/src/MP4Movies.h +++ b/IsoLib/libisomediafile/src/MP4Movies.h @@ -1108,6 +1108,17 @@ extern "C" */ MP4_EXTERN(MP4Err) ISOAddGroupDescription(MP4Media media, u32 groupType, MP4Handle description, u32 *index); + /** + * @brief Adds a T.35 Sample Group Description to the indicated media. + * + * @param media input media object + * @param itu_t_t35_data pre-serialized (big-endian) T.35 data that will go inside sgpd + * @param complete_message_flag If set to 1 indicates that the entire T.35 is stored in itu_t_t35_data + * @param index output index of the added group + * @return MP4Err error code + */ + MP4_EXTERN(MP4Err) + ISOAddT35GroupDescription(MP4Media media, MP4Handle itu_t_t35_data, u32 complete_message_flag, u32 *index); /** * @brief Returns in the handle ‘description’ the group description associated with the given * group index of the given group type. diff --git a/IsoLib/libisomediafile/src/SampleTableAtom.c b/IsoLib/libisomediafile/src/SampleTableAtom.c index 4edb2e7c..653fc718 100644 --- a/IsoLib/libisomediafile/src/SampleTableAtom.c +++ b/IsoLib/libisomediafile/src/SampleTableAtom.c @@ -737,6 +737,10 @@ static MP4Err getSampleGroupSampleNumbers(struct MP4SampleTableAtom *self, u32 g (*outSampleNumbers)[(*outSampleCnt)++] = i; } } + else + { + /* TODO: make sure we can also get it based on default_group_description_index, */ + } bail: TEST_RETURN(err); diff --git a/test/test_data.h b/test/test_data.h index 8e6d9338..402ab52e 100644 --- a/test/test_data.h +++ b/test/test_data.h @@ -97,6 +97,13 @@ const u8 auFR[] = {0x28, 0x01, 0xAF, 0x78, 0xEB, 0x27, 0x7F, 0xFD, 0xCE, 0x7C, 0 0x10, 0xE3, 0x10, 0x50, 0xC4, 0x13, 0x88, 0x05, 0x8B, 0x60, 0xFB, 0x13, 0x89, 0x7C, 0x54, 0x50, 0x71, 0xBA, 0xE5, 0x24, 0x98}; +const u8 SEI_HDR[] = {0x4E, 0x01, 0x04, 0x40, 0xB5, 0x00, 0x3C, 0x00, 0x01, 0x04, 0x01, 0x40, + 0x00, 0x0F, 0xA3, 0x0D, 0x41, 0x86, 0xA0, 0xC3, 0x50, 0x49, 0xA8, 0xA4, + 0x08, 0x00, 0x00, 0x2E, 0x1A, 0x80, 0x50, 0x00, 0x34, 0xC8, 0x00, 0x01, + 0x90, 0x74, 0x76, 0x5A, 0x2D, 0x42, 0xD2, 0x41, 0xDE, 0xFA, 0x57, 0x47, + 0x1A, 0x84, 0x80, 0x00, 0x40, 0x1C, 0x0F, 0xA5, 0xFA, 0x60, 0x9E, 0xD9, + 0x0A, 0x85, 0xB1, 0xB1, 0x9D, 0xDF, 0xBF, 0x00, 0x80}; + } // namespace HEVC /// One file-level meta with 'test' handler and 2 EntityToGroups: diff --git a/test/test_helpers.h b/test/test_helpers.h index 9a269b6b..069e4251 100644 --- a/test/test_helpers.h +++ b/test/test_helpers.h @@ -185,95 +185,67 @@ inline std::vector getMetaSample(u32 x, u32 y, u32 w, u32 h) * @param repeatPattern number of times to repeat the pattern. No samples are added if this is 0 * @param sampleEntryH sample entry handle (for the first call) * @param lengthSize the length in bytes of the NALUnitLength field in an HEVC video sample + * @param add_sei if set adds an SEI message in front of each frame * @return MP4Err error code */ inline MP4Err addHEVCSamples(MP4Media media, std::string strPattern, u32 repeatPattern = 1, - MP4Handle sampleEntryH = 0, u32 lengthSize = 1) + MP4Handle sampleEntryH = 0, u32 lengthSize = 1, bool add_sei = false) { MP4Err err; - u32 sampleCount = 0; MP4Handle sampleDataH, durationsH, sizesH; - err = MP4NewHandle(sizeof(u32), &durationsH); - CHECK(err == MP4NoErr); + err = MP4NewHandle(sizeof(u32), &durationsH); *((u32 *)*durationsH) = TIMESCALE / FPS; std::vector bufferData; std::vector bufferSizes; - for(std::string::const_iterator it = strPattern.cbegin(); it != strPattern.cend(); ++it) + + // a lambda function to add NAL units + auto addNALUnit = [&](const u8* data, u32 size) { - switch(*it) + u32 totalSize = 0; + if(add_sei) { - case 'r': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auRed, sizeof(HEVC::auRed)); - bufferSizes.push_back(sizeof(HEVC::auRed) + lengthSize); - break; - case 'b': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auBlue, sizeof(HEVC::auBlue)); - bufferSizes.push_back(sizeof(HEVC::auBlue) + lengthSize); - break; - case 'g': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auGreen, sizeof(HEVC::auGreen)); - bufferSizes.push_back(sizeof(HEVC::auGreen) + lengthSize); - break; - case 'y': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auYellow, sizeof(HEVC::auYellow)); - bufferSizes.push_back(sizeof(HEVC::auYellow) + lengthSize); - break; - case 'w': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auWhite, sizeof(HEVC::auWhite)); - bufferSizes.push_back(sizeof(HEVC::auWhite) + lengthSize); - break; - case 'k': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auBlack, sizeof(HEVC::auBlack)); - bufferSizes.push_back(sizeof(HEVC::auBlack) + lengthSize); - break; - case 'R': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auRU, sizeof(HEVC::auRU)); - bufferSizes.push_back(sizeof(HEVC::auRU) + lengthSize); - break; - case 'U': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auUA, sizeof(HEVC::auUA)); - bufferSizes.push_back(sizeof(HEVC::auUA) + lengthSize); - break; - case 'D': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auDE, sizeof(HEVC::auDE)); - bufferSizes.push_back(sizeof(HEVC::auDE) + lengthSize); - break; - case 'F': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auFR, sizeof(HEVC::auFR)); - bufferSizes.push_back(sizeof(HEVC::auFR) + lengthSize); - break; - case 'N': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auNL, sizeof(HEVC::auNL)); - bufferSizes.push_back(sizeof(HEVC::auNL) + lengthSize); - break; - case 'I': - appendDataWithLengthField(bufferData, lengthSize, HEVC::auID, sizeof(HEVC::auID)); - bufferSizes.push_back(sizeof(HEVC::auID) + lengthSize); - break; - default: - break; + appendDataWithLengthField(bufferData, lengthSize, HEVC::SEI_HDR, sizeof(HEVC::SEI_HDR)); + totalSize += sizeof(HEVC::SEI_HDR) + lengthSize; + } + appendDataWithLengthField(bufferData, lengthSize, data, size); + totalSize += size + lengthSize; + bufferSizes.push_back(totalSize); + }; + + for (char c : strPattern) + { + switch(c) { + case 'r': addNALUnit(HEVC::auRed, sizeof(HEVC::auRed)); break; + case 'b': addNALUnit(HEVC::auBlue, sizeof(HEVC::auBlue)); break; + case 'g': addNALUnit(HEVC::auGreen, sizeof(HEVC::auGreen)); break; + case 'y': addNALUnit(HEVC::auYellow, sizeof(HEVC::auYellow)); break; + case 'w': addNALUnit(HEVC::auWhite, sizeof(HEVC::auWhite)); break; + case 'k': addNALUnit(HEVC::auBlack, sizeof(HEVC::auBlack)); break; + case 'R': addNALUnit(HEVC::auRU, sizeof(HEVC::auRU)); break; + case 'U': addNALUnit(HEVC::auUA, sizeof(HEVC::auUA)); break; + case 'D': addNALUnit(HEVC::auDE, sizeof(HEVC::auDE)); break; + case 'F': addNALUnit(HEVC::auFR, sizeof(HEVC::auFR)); break; + case 'N': addNALUnit(HEVC::auNL, sizeof(HEVC::auNL)); break; + case 'I': addNALUnit(HEVC::auID, sizeof(HEVC::auID)); break; + default: break; } } // repeat pattern std::vector bufferDataPattern = bufferData; std::vector bufferSizesPattern = bufferSizes; - std::string fullPattern = strPattern; for(u32 n = 1; n < repeatPattern; ++n) { bufferData.insert(bufferData.end(), bufferDataPattern.begin(), bufferDataPattern.end()); bufferSizes.insert(bufferSizes.end(), bufferSizesPattern.begin(), bufferSizesPattern.end()); - fullPattern += strPattern; } // create handles and copy data err = MP4NewHandle(bufferData.size() * sizeof(u8), &sampleDataH); - CHECK(err == MP4NoErr); std::memcpy((*sampleDataH), bufferData.data(), bufferData.size() * sizeof(u8)); err = MP4NewHandle(sizeof(u32) * bufferSizes.size(), &sizesH); - CHECK(err == MP4NoErr); for(u32 n = 0; n < bufferSizes.size(); n++) { ((u32 *)*sizesH)[n] = bufferSizes[n]; @@ -283,12 +255,9 @@ inline MP4Err addHEVCSamples(MP4Media media, std::string strPattern, u32 repeatP durationsH, sizesH, sampleEntryH, 0, 0); CHECK(err == MP4NoErr); - err = MP4DisposeHandle(sampleDataH); - CHECK(err == MP4NoErr); - err = MP4DisposeHandle(durationsH); - CHECK(err == MP4NoErr); - err = MP4DisposeHandle(sizesH); - CHECK(err == MP4NoErr); + MP4DisposeHandle(sampleDataH); + MP4DisposeHandle(durationsH); + MP4DisposeHandle(sizesH); return err; } @@ -430,11 +399,9 @@ inline MP4Err addMebxSamples(MP4Media media, std::string strPattern, u32 repeatP u32 lk_w = 0, u32 lk_k = 0, u32 lk_g = 0) { MP4Err err; - u32 sampleCount = 0; MP4Handle sampleDataH, durationsH, sizesH; - err = MP4NewHandle(sizeof(u32), &durationsH); - CHECK(err == MP4NoErr); + err = MP4NewHandle(sizeof(u32), &durationsH); *((u32 *)*durationsH) = TIMESCALE / FPS; std::vector bufferData; @@ -575,10 +542,8 @@ inline MP4Err addMebxSamples(MP4Media media, std::string strPattern, u32 repeatP // create handles and copy data err = MP4NewHandle(bufferData.size() * sizeof(u8), &sampleDataH); - CHECK(err == MP4NoErr); std::memcpy((*sampleDataH), bufferData.data(), bufferData.size() * sizeof(u8)); err = MP4NewHandle(sizeof(u32) * bufferSizes.size(), &sizesH); - CHECK(err == MP4NoErr); for(u32 n = 0; n < bufferSizes.size(); n++) { ((u32 *)*sizesH)[n] = bufferSizes[n]; @@ -588,12 +553,9 @@ inline MP4Err addMebxSamples(MP4Media media, std::string strPattern, u32 repeatP durationsH, sizesH, sampleEntryH, 0, 0); CHECK(err == MP4NoErr); - err = MP4DisposeHandle(sampleDataH); - CHECK(err == MP4NoErr); - err = MP4DisposeHandle(durationsH); - CHECK(err == MP4NoErr); - err = MP4DisposeHandle(sizesH); - CHECK(err == MP4NoErr); + MP4DisposeHandle(sampleDataH); + MP4DisposeHandle(durationsH); + MP4DisposeHandle(sizesH); return err; } @@ -626,7 +588,6 @@ inline MP4Err checkRedMebxSamples(std::string strPattern, u32 repeatPattern, MP4 { u32 redSize = 0; err = MP4GetHandleSize(auData, &redSize); - CHECK(err == MP4NoErr); CHECK(0 == redSize); break; } diff --git a/test/test_t35.cpp b/test/test_t35.cpp index 61e5b9a8..7e67eaad 100644 --- a/test/test_t35.cpp +++ b/test/test_t35.cpp @@ -42,41 +42,141 @@ const std::string strTestFile = strDataPath + "/isobmff/hvc1_hdr10plus_original. */ TEST_CASE("T35") { - std::string strT35marking = "test_t35_marking.mp4"; - - /** - * @brief Read a file with T.35 NAL Units and create a sample group that is used for marking purposes only - * - * create Defragmented file with NORMAL SampleToGroupBox - * create Defragmented file with COMPACT SampleToGroupBox - * create Defragmented file with AUTO SampleToGroupBox (smaller size is used automatically) - * - */ - SECTION("Check defragmentation of sample groups") + std::string strT35Default = "test_samplegroups_t35_defaultHDR10p.mp4"; + + MP4Err err; + + MP4Handle it35_prefix; + MP4NewHandle(5, &it35_prefix); + (*it35_prefix)[0] = 0xB5; + (*it35_prefix)[1] = 0x00; + (*it35_prefix)[2] = 0x3C; + (*it35_prefix)[3] = 0x00; + (*it35_prefix)[4] = 0x01; + + // TODO: implement a command line tool that will take an HEVC mp4 with T.35 SEIs in samples + // then it will parse all these T.35, and try to move them into sample groups to save space. + // SECTION("TBD") + // { + // MP4Err err; + // MP4Movie moov; + // MP4Track trak; + // MP4Media media; + + // err = MP4OpenMovieFile(&moov, strTestFile.c_str(), MP4OpenMovieDebug); + // err = MP4GetMovieIndTrack(moov, 1, &trak); + // err = MP4GetTrackMedia(trak, &media); + + // u32 codecType = 0; + // u32 nalUnitLength = 0; + // err = MP4GetMovieIndTrackSampleEntryType(moov, 1, &codecType); + // err = MP4GetMovieIndTrackNALUnitLength(moov, 1, &nalUnitLength); + // REQUIRE(codecType == ISOHEVCSampleEntryAtomType); + // CHECK(nalUnitLength == 4); + + // u32 sampleCnt = 0; + // err = MP4GetMediaSampleCount(media, &sampleCnt); + // CHECK(30 == sampleCnt); + + // // TBD iterate through samples and look through T.35 SEI NAL Units + // // we need to build up a table that will contain + // // T.35 data and the number of sample the same data was detected in. For example data_blob1 in sample 1,2,5. data_blob2 in samples 3,4,5,6 etc. + + // } + + SECTION("Check creation of it35 default sample group using ISOAddT35GroupDescription") + { + MP4Movie moov; + MP4Media media; + MP4Track trak; + + u32 lengthSize = 4; + u32 temp = 0; + + MP4Handle spsHandle, ppsHandle, vpsHandle, sampleEntryH; + err = MP4NewHandle(sizeof(HEVC::SPS), &spsHandle); + std::memcpy((*spsHandle), HEVC::SPS, sizeof(HEVC::SPS)); + err = MP4NewHandle(sizeof(HEVC::PPS), &ppsHandle); + std::memcpy((*ppsHandle), HEVC::PPS, sizeof(HEVC::PPS)); + err = MP4NewHandle(sizeof(HEVC::VPS), &vpsHandle); + std::memcpy((*vpsHandle), HEVC::VPS, sizeof(HEVC::VPS)); + err = MP4NewHandle(0, &sampleEntryH); + + err = MP4NewMovie(&moov, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); + REQUIRE(err == MP4NoErr); + + err = MP4NewMovieTrack(moov, MP4NewTrackIsVisual, &trak); + REQUIRE(err == MP4NoErr); + err = MP4AddTrackToMovieIOD(trak); + CHECK(err == MP4NoErr); + err = MP4NewTrackMedia(trak, &media, MP4VisualHandlerType, TIMESCALE, NULL); + REQUIRE(err == MP4NoErr); + + err = MP4BeginMediaEdits(media); + err = ISONewHEVCSampleDescription(trak, sampleEntryH, 1, lengthSize, spsHandle, ppsHandle, vpsHandle); + REQUIRE(err == MP4NoErr); + + // check getter + err = ISOGetGroupDescriptionEntryCount(media, MP4T35SampleGroupEntry, &temp); + CHECK(err == MP4NotFoundErr); + CHECK(temp == 0); + + // just add sample entry, call addHEVCSamples with sample count = 0 + err = addHEVCSamples(media, "", 0, sampleEntryH, lengthSize, true); + CHECK(err == MP4NoErr); + err = MP4EndMediaEdits(media); + CHECK(err == MP4NoErr); + // add samples + err = addHEVCSamples(media, "rb", 3, nullptr, lengthSize, true); + CHECK(err == MP4NoErr); + + // Add T.35 sample group description header. Default sample group (all samples have this header) + err = ISOSetSamplestoGroupType(media, SAMPLE_GROUP_NORMAL); + CHECK(err == MP4NoErr); + err = ISOAddT35GroupDescription(media, it35_prefix, 0, &temp); + CHECK(err == MP4NoErr); + err = ISOGetGroupDescriptionEntryCount(media, MP4T35SampleGroupEntry, &temp); + CHECK(temp == 1); + + err = MP4WriteMovieToFile(moov, strT35Default.c_str()); + CHECK(err == MP4NoErr); + } + + SECTION("Check default it35 sample group") { - MP4Err err; MP4Movie moov; MP4Track trak; MP4Media media; - err = MP4OpenMovieFile(&moov, strTestFile.c_str(), MP4OpenMovieDebug); + u32 it35_sg_cnt = 0; + u32 *sample_numbers; + u32 sample_cnt = 0; + + err = MP4OpenMovieFile(&moov, strT35Default.c_str(), MP4OpenMovieDebug); err = MP4GetMovieIndTrack(moov, 1, &trak); err = MP4GetTrackMedia(trak, &media); - u32 codecType = 0; - u32 nalUnitLength = 0; - err = MP4GetMovieIndTrackSampleEntryType(moov, 1, &codecType); - err = MP4GetMovieIndTrackNALUnitLength(moov, 1, &nalUnitLength); - REQUIRE(codecType == ISOHEVCSampleEntryAtomType); - CHECK(nalUnitLength == 4); - - u32 sampleCnt = 0; - err = MP4GetMediaSampleCount(media, &sampleCnt); - CHECK(30 == sampleCnt); + err = ISOGetGroupDescriptionEntryCount(media, MP4T35SampleGroupEntry, &it35_sg_cnt); + CHECK(err == MP4NoErr); + CHECK(1 == it35_sg_cnt); - // TBD iterate through samples and look through T.35 SEI NAL Units - // we need to build up a table that will contain - // T.35 data and the number of sample the same data was detected in. For example data_blob1 in sample 1,2,5. data_blob2 in samples 3,4,5,6 etc. + MP4Handle entryH; + u32 size = 0; + MP4NewHandle(0, &entryH); + err = ISOGetGroupDescription(media, MP4T35SampleGroupEntry, 1, entryH); + CHECK(err == MP4NoErr); + MP4GetHandleSize(entryH, &size); + CHECK(6 == size); + + err = ISOGetSampleGroupSampleNumbers(media, MP4T35SampleGroupEntry, 1, &sample_numbers, &sample_cnt); + CHECK(err == MP4NoErr); + + u32 check_sample_cnt = 0; + MP4GetMediaSampleCount(media, &check_sample_cnt); + CHECK(check_sample_cnt > 0); + CHECK(check_sample_cnt == sample_cnt); } + + MP4DisposeHandle(it35_prefix); } From 7289fca66d008d0aaa0d162718a8ac382db0ae14 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Tue, 8 Jul 2025 16:16:38 -0700 Subject: [PATCH 03/11] when defragmenting an empty stts make sure to add the first entry --- IsoLib/libisomediafile/src/TimeToSampleAtom.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/IsoLib/libisomediafile/src/TimeToSampleAtom.c b/IsoLib/libisomediafile/src/TimeToSampleAtom.c index 2d68ac47..2ab7bfdd 100644 --- a/IsoLib/libisomediafile/src/TimeToSampleAtom.c +++ b/IsoLib/libisomediafile/src/TimeToSampleAtom.c @@ -150,6 +150,12 @@ static MP4Err extendLastSampleDuration(struct MP4TimeToSampleAtom *self, u32 dur err = MP4NoErr; current = (sttsEntryPtr)self->currentEntry; + if (current == NULL) { + /* lets treat it as the first sample */ + err = addSample(self, duration); + goto bail; + } + if(current->sampleCount == 1) { current->sampleDuration += duration; @@ -163,7 +169,6 @@ static MP4Err extendLastSampleDuration(struct MP4TimeToSampleAtom *self, u32 dur bail: TEST_RETURN(err); - return err; } From 48ff054026e1b4ae765fe95b62483877d79651ac Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Tue, 8 Jul 2025 16:20:19 -0700 Subject: [PATCH 04/11] just go for it :) --- IsoLib/libisomediafile/src/ISOSampleDescriptions.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/IsoLib/libisomediafile/src/ISOSampleDescriptions.c b/IsoLib/libisomediafile/src/ISOSampleDescriptions.c index 85489a9f..164a588f 100644 --- a/IsoLib/libisomediafile/src/ISOSampleDescriptions.c +++ b/IsoLib/libisomediafile/src/ISOSampleDescriptions.c @@ -1211,17 +1211,6 @@ ISOGetHEVCNALUs(MP4Handle sampleEntryH, MP4Handle nalus, u32 extraction_mode) err = sampleEntryHToAtomPtr(sampleEntryH, (MP4AtomPtr *)&entry, MP4VisualSampleEntryAtomType); if(err) goto bail; - if(entry->type == MP4EncVisualSampleEntryAtomType || - entry->type == MP4RestrictedVideoSampleEntryAtomType) - { - u32 origFmt = 0; - err = ISOGetOriginalFormat(sampleEntryH, &origFmt); - if(origFmt != ISOHEVCSampleEntryAtomType && origFmt != ISOLHEVCSampleEntryAtomType) - BAILWITHERROR(MP4BadParamErr); - } - else if(entry->type != ISOHEVCSampleEntryAtomType && entry->type != ISOLHEVCSampleEntryAtomType) - BAILWITHERROR(MP4BadParamErr); - MP4GetListEntryAtom(entry->ExtensionAtomList, ISOHEVCConfigAtomType, (MP4AtomPtr *)&hvcC); MP4GetListEntryAtom(entry->ExtensionAtomList, ISOLHEVCConfigAtomType, (MP4AtomPtr *)&lhvC); From ae6492399034dd301905e8807da081b6913dd506 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Tue, 26 Aug 2025 01:21:40 -0700 Subject: [PATCH 05/11] setup sniffer --- CMakeLists.txt | 1 + IsoLib/pcm_sniffer/CMakeLists.txt | 54 +++++++++++++ IsoLib/pcm_sniffer/pcm_sniffer.cpp | 122 +++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 IsoLib/pcm_sniffer/CMakeLists.txt create mode 100644 IsoLib/pcm_sniffer/pcm_sniffer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index adbb1e1a..7b30f523 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,3 +43,4 @@ add_subdirectory(IsoLib/isoiff_tool) add_subdirectory(IsoLib/pcm_audio_example) add_subdirectory(test) add_subdirectory(IsoLib/vvc_base) +add_subdirectory(IsoLib/pcm_sniffer) diff --git a/IsoLib/pcm_sniffer/CMakeLists.txt b/IsoLib/pcm_sniffer/CMakeLists.txt new file mode 100644 index 00000000..a71b5f61 --- /dev/null +++ b/IsoLib/pcm_sniffer/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.16) + +# Name & languages +project(pcm_sniffer LANGUAGES C CXX) + +# C++ standard +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Includes: libisomediafile core + HM +include_directories( + ../libisomediafile/src + + # HM + ../HM/source/Lib + ../HM/source/Lib/TLibCommon + ../HM/source/Lib/TLibDecoder +) + +# Platform-specific include roots for libisomediafile +include_directories( + # Linux + $<$:${CMAKE_CURRENT_LIST_DIR}/../libisomediafile/linux> + # Windows + $<$:${CMAKE_CURRENT_LIST_DIR}/../libisomediafile/w32> + # macOS + $<$:${CMAKE_CURRENT_LIST_DIR}/../libisomediafile/macosx> +) + +# common warnings +if (MSVC) + add_compile_options(/W4) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +else() + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + + +# ---- Executable ---- +add_executable( + pcm_sniffer + pcm_sniffer.cpp +) + +# Link libraries +target_link_libraries( + pcm_sniffer + + PRIVATE + libisomediafile + TLibDecoder + TLibCommon +) diff --git a/IsoLib/pcm_sniffer/pcm_sniffer.cpp b/IsoLib/pcm_sniffer/pcm_sniffer.cpp new file mode 100644 index 00000000..fefe36bb --- /dev/null +++ b/IsoLib/pcm_sniffer/pcm_sniffer.cpp @@ -0,0 +1,122 @@ +/** + * @file pcm_sniffer.cpp + * @brief Implementation of a simple PCM sniffer tool for HEVC + * @version 0.1 + * @date 2025-08-26 + * + * @copyright This software module was originally developed by Apple Computer, Inc. in the course of + * development of MPEG-4. This software module is an implementation of a part of one or more MPEG-4 + * tools as specified by MPEG-4. ISO/IEC gives users of MPEG-4 free license to this software module + * or modifications thereof for use in hardware or software products claiming conformance to MPEG-4 + * only for evaluation and testing purposes. Those intending to use this software module in hardware + * or software products are advised that its use may infringe existing patents. The original + * developer of this software module and his/her company, the subsequent editors and their + * companies, and ISO/IEC have no liability for use of this software module or modifications thereof + * in an implementation. + * + * Copyright is not released for non MPEG-4 conforming products. Apple Computer, Inc. retains full + * right to use the code for its own purpose, assign or donate the code to a third party and to + * inhibit third parties from using the code for non MPEG-4 conforming products. This copyright + * notice must be included in all copies or derivative works. + * + */ + +extern "C" { + #include "MP4Movies.h" + #include "MP4Atoms.h" +} + +#include +#include +#include + +class PcmSniffer { +public: + int run(const std::string& fileName) { + ISOErr err; + ISOMovie moov = nullptr; + + // Open movie + err = ISOOpenMovieFile(&moov, fileName.c_str(), MP4OpenMovieNormal); + if (err) { + std::cerr << "Failed to open file: " << fileName << " (err=" << err << ")\n"; + return err; + } + + u32 trackCount = 0; + err = ISOGetMovieTrackCount(moov, &trackCount); + if (err) return err; + + std::cout << "Movie has " << trackCount << " tracks\n"; + + for (u32 trackNumber = 1; trackNumber <= trackCount; ++trackNumber) { + ISOTrack trak = nullptr; + err = ISOGetMovieIndTrack(moov, trackNumber, &trak); + if (err) continue; + + // Create track reader + ISOTrackReader reader = nullptr; + err = ISOCreateTrackReader(trak, &reader); + if (err) continue; + + // Get sample entry + ISOHandle sampleEntryH; + ISONewHandle(0, &sampleEntryH); + err = MP4TrackReaderGetCurrentSampleDescription(reader, sampleEntryH); + if (err) { ISODisposeHandle(sampleEntryH); continue; } + + u32 sampleEntryType = 0; + ISOGetSampleDescriptionType(sampleEntryH, &sampleEntryType); + + char typeStr[5] = {0}; + MP4TypeToString(sampleEntryType, typeStr); + std::cout << "Track " << trackNumber << " sample entry type: " << typeStr << "\n"; + + if (sampleEntryType == ISOHEVCSampleEntryAtomType) { + std::cout << " -> HEVC track, extracting parameter sets\n"; + + ISOHandle nalusH; + ISONewHandle(0, &nalusH); + + // mode=0 → get VPS, SPS, PPS all together + err = ISOGetHEVCNALUs(sampleEntryH, nalusH, 0); + if (!err) { + u32 size = 0; + MP4GetHandleSize(nalusH, &size); + std::cout << "Extracted " << size << " bytes of NALUs in AnnexB\n"; + + if (size > 0) { + // Dump to file + std::string outName = "track_" + std::to_string(trackNumber) + "_ps.bin"; + std::ofstream out(outName, std::ios::binary); + if (out) { + out.write(*nalusH, size); + std::cout << " -> Wrote parameter sets to " << outName << "\n"; + } else { + std::cerr << " -> Failed to open " << outName << " for writing\n"; + } + } + } else { + std::cerr << "ISOGetHEVCNALUs failed with " << err << "\n"; + } + + ISODisposeHandle(nalusH); + } + + ISODisposeHandle(sampleEntryH); + MP4DisposeTrackReader(reader); + } + + ISODisposeMovie(moov); + return 0; + } +}; + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Usage: pcm_sniffer \n"; + return 1; + } + PcmSniffer sniffer; + return sniffer.run(argv[1]); +} From b0785d75ccb29b2b7587b4da9b7fec08ae660805 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Tue, 26 Aug 2025 01:22:33 -0700 Subject: [PATCH 06/11] bugfix: if there is nothing to merge don't merge --- .../libisomediafile/src/TrackFragmentAtom.c | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/IsoLib/libisomediafile/src/TrackFragmentAtom.c b/IsoLib/libisomediafile/src/TrackFragmentAtom.c index 909605a9..ffb2f45a 100644 --- a/IsoLib/libisomediafile/src/TrackFragmentAtom.c +++ b/IsoLib/libisomediafile/src/TrackFragmentAtom.c @@ -689,30 +689,38 @@ static MP4Err mergeSampleAuxiliaryInformation(struct MP4TrackFragmentAtom *self, minf = (MP4MediaInformationAtomPtr)mdia->information; stbl = (MP4SampleTableAtomPtr)minf->sampleTable; - for(i = 0; i < self->saizList->entryCount; i++) - { - MP4SampleAuxiliaryInformationSizesAtomPtr saizOfTraf; - MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfTraf; - MP4SampleAuxiliaryInformationSizesAtomPtr saizOfStbl; - MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfStbl; + for (i = 0; i < self->saizList->entryCount; i++) { + MP4SampleAuxiliaryInformationSizesAtomPtr saizOfTraf = NULL; + MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfTraf = NULL; + MP4SampleAuxiliaryInformationSizesAtomPtr saizOfStbl = NULL; + MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfStbl = NULL; + err = MP4GetListEntry(self->saizList, i, (char **)&saizOfTraf); + if (err || !saizOfTraf) continue; + err = MP4GetListEntry(self->saioList, i, (char **)&saioOfTraf); + if (err || !saioOfTraf) continue; err = stbl->getSampleAuxiliaryInformation( - stbl, (saizOfTraf->flags & 1), saizOfTraf->aux_info_type, saizOfTraf->aux_info_type_parameter, - &saizOfStbl, &saioOfStbl); - if(err) goto bail; - - err = saizOfStbl->mergeSizes((MP4AtomPtr)saizOfStbl, (MP4AtomPtr)saizOfTraf); - if(err) goto bail; - err = saioOfStbl->mergeOffsets((MP4AtomPtr)saioOfStbl, (MP4AtomPtr)saioOfTraf, - tfhd->base_data_offset); - if(err) goto bail; + stbl, + (saizOfTraf->flags & 1), + saizOfTraf->aux_info_type, + saizOfTraf->aux_info_type_parameter, + &saizOfStbl, &saioOfStbl); + if (err) goto bail; + + /* Defensive checks: if stbl had no saiz/saio, skip merging */ + if (saizOfStbl && saioOfStbl) { + err = saizOfStbl->mergeSizes((MP4AtomPtr)saizOfStbl, (MP4AtomPtr)saizOfTraf); + if (err) goto bail; + err = saioOfStbl->mergeOffsets((MP4AtomPtr)saioOfStbl, (MP4AtomPtr)saioOfTraf, + tfhd->base_data_offset); + if (err) goto bail; + } } bail: TEST_RETURN(err); - return err; } From 3eed32e86b2213a5257926053801c965974f5454 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Tue, 26 Aug 2025 09:27:57 -0700 Subject: [PATCH 07/11] add implementation using HM --- IsoLib/pcm_sniffer/pcm_sniffer.cpp | 210 ++++++++++++++++++----------- 1 file changed, 131 insertions(+), 79 deletions(-) diff --git a/IsoLib/pcm_sniffer/pcm_sniffer.cpp b/IsoLib/pcm_sniffer/pcm_sniffer.cpp index fefe36bb..2b3a6522 100644 --- a/IsoLib/pcm_sniffer/pcm_sniffer.cpp +++ b/IsoLib/pcm_sniffer/pcm_sniffer.cpp @@ -21,102 +21,154 @@ * */ +// libisomediafile headers extern "C" { #include "MP4Movies.h" #include "MP4Atoms.h" } +// HM headers +#include "AnnexBread.h" // InputByteStream, AnnexBStats, byteStreamNALUnit +#include "NALread.h" // InputNALUnit, read +#include "TDecCAVLC.h" // TDecCavlc +#include "TComSlice.h" // TComSPS, CHANNEL_TYPE_LUMA + +// C++ headers #include -#include +#include #include +#include + +static void fourccToStr(u32 fcc, char out[5]) { + out[0] = char((fcc >> 24) & 0xFF); + out[1] = char((fcc >> 16) & 0xFF); + out[2] = char((fcc >> 8) & 0xFF); + out[3] = char((fcc ) & 0xFF); + out[4] = 0; +} class PcmSniffer { public: - int run(const std::string& fileName) { - ISOErr err; - ISOMovie moov = nullptr; - - // Open movie - err = ISOOpenMovieFile(&moov, fileName.c_str(), MP4OpenMovieNormal); - if (err) { - std::cerr << "Failed to open file: " << fileName << " (err=" << err << ")\n"; - return err; + int run(const std::string& fileName) { + MP4Err err = MP4NoErr; + MP4Movie moov = nullptr; + + // Open MP4 + err = MP4OpenMovieFile(&moov, fileName.c_str(), MP4OpenMovieNormal); + if (err) { + std::cerr << "Failed to open " << fileName << " (err=" << err << ")\n"; + return err; + } + + u32 trackCount = 0; + if ((err = MP4GetMovieTrackCount(moov, &trackCount))) { + MP4DisposeMovie(moov); + return err; + } + std::cout << "Movie has " << trackCount << " tracks\n"; + + for (u32 trackNumber = 1; trackNumber <= trackCount; ++trackNumber) { + MP4Track trak = nullptr; + if (MP4GetMovieIndTrack(moov, trackNumber, &trak) != MP4NoErr || !trak) continue; + + MP4TrackReader reader = nullptr; + if (MP4CreateTrackReader(trak, &reader) != MP4NoErr || !reader) continue; + + MP4Handle sampleEntryH = nullptr; + MP4NewHandle(0, &sampleEntryH); + if (!sampleEntryH) { MP4DisposeTrackReader(reader); continue; } + + err = MP4TrackReaderGetCurrentSampleDescription(reader, sampleEntryH); + if (err) { + MP4DisposeHandle(sampleEntryH); + MP4DisposeTrackReader(reader); + continue; + } + + u32 sampleEntryType = 0; + ISOGetSampleDescriptionType(sampleEntryH, &sampleEntryType); + char typeStr[5]; fourccToStr(sampleEntryType, typeStr); + std::cout << "Track " << trackNumber << " sample entry: " << typeStr << "\n"; + + // TODO: sampleEntryType == MP4_FOUR_CHAR_CODE('e', 'n', 'c', 'v') + if (sampleEntryType == ISOHEVCSampleEntryAtomType || sampleEntryType == MP4_FOUR_CHAR_CODE('h', 'e', 'v', '1')) { + std::cout << " -> HEVC track: extracting parameter sets\n"; + + MP4Handle nalusH = nullptr; + MP4NewHandle(0, &nalusH); + if (!nalusH) { + MP4DisposeHandle(sampleEntryH); + MP4DisposeTrackReader(reader); + continue; } - u32 trackCount = 0; - err = ISOGetMovieTrackCount(moov, &trackCount); - if (err) return err; - - std::cout << "Movie has " << trackCount << " tracks\n"; - - for (u32 trackNumber = 1; trackNumber <= trackCount; ++trackNumber) { - ISOTrack trak = nullptr; - err = ISOGetMovieIndTrack(moov, trackNumber, &trak); - if (err) continue; - - // Create track reader - ISOTrackReader reader = nullptr; - err = ISOCreateTrackReader(trak, &reader); - if (err) continue; - - // Get sample entry - ISOHandle sampleEntryH; - ISONewHandle(0, &sampleEntryH); - err = MP4TrackReaderGetCurrentSampleDescription(reader, sampleEntryH); - if (err) { ISODisposeHandle(sampleEntryH); continue; } - - u32 sampleEntryType = 0; - ISOGetSampleDescriptionType(sampleEntryH, &sampleEntryType); - - char typeStr[5] = {0}; - MP4TypeToString(sampleEntryType, typeStr); - std::cout << "Track " << trackNumber << " sample entry type: " << typeStr << "\n"; - - if (sampleEntryType == ISOHEVCSampleEntryAtomType) { - std::cout << " -> HEVC track, extracting parameter sets\n"; - - ISOHandle nalusH; - ISONewHandle(0, &nalusH); - - // mode=0 → get VPS, SPS, PPS all together - err = ISOGetHEVCNALUs(sampleEntryH, nalusH, 0); - if (!err) { - u32 size = 0; - MP4GetHandleSize(nalusH, &size); - std::cout << "Extracted " << size << " bytes of NALUs in AnnexB\n"; - - if (size > 0) { - // Dump to file - std::string outName = "track_" + std::to_string(trackNumber) + "_ps.bin"; - std::ofstream out(outName, std::ios::binary); - if (out) { - out.write(*nalusH, size); - std::cout << " -> Wrote parameter sets to " << outName << "\n"; - } else { - std::cerr << " -> Failed to open " << outName << " for writing\n"; - } - } - } else { - std::cerr << "ISOGetHEVCNALUs failed with " << err << "\n"; - } - - ISODisposeHandle(nalusH); + // extraction_mode = 0 get NALUs from hvcC and from lhvC (if present) + err = ISOGetHEVCNALUs(sampleEntryH, nalusH, 0); + if (!err) { + u32 size = 0; MP4GetHandleSize(nalusH, &size); + std::cout << "Got " << size << " NALU bytes from sample entry\n"; + + if (size > 0) { + // HM wants a std::istream. Wrap into istringstream, then InputByteStream + std::string annexB(reinterpret_cast(*nalusH), size); + std::istringstream iss(annexB, std::ios::in | std::ios::binary); + InputByteStream ibs(iss); + + AnnexBStats stats{}; + TDecCavlc cavlc; + + while (true) { + std::vector nalUnit; + Bool eof = byteStreamNALUnit(ibs, nalUnit, stats); + if (nalUnit.empty() && eof) break; + + // Load NAL bytes into InputNALUnit + InputNALUnit nalu; + { + auto& bs = nalu.getBitstream(); // reference to internal bitstream + bs.getFifo() = nalUnit; // copy NAL payload bytes + bs.resetToStart(); // reset reader indices + } + + // Parse NAL header. Sets nalu.m_nalUnitType and primes RBSP + read(nalu); + + if (nalu.m_nalUnitType == NAL_UNIT_SPS) { + // Tell CAVLC where to read from, then parse SPS + cavlc.setBitstream(&nalu.getBitstream()); + TComSPS sps; + cavlc.parseSPS(&sps); + + std::cout << " SPS id=" << sps.getSPSId() + << " res=" << sps.getPicWidthInLumaSamples() + << "x" << sps.getPicHeightInLumaSamples() + << " bitDepth=" << sps.getBitDepth(CHANNEL_TYPE_LUMA) + << " pcm_enabled_flag=" << sps.getUsePCM() + << "\n"; + } } - - ISODisposeHandle(sampleEntryH); - MP4DisposeTrackReader(reader); + } + } else { + std::cerr << "ISOGetHEVCNALUs failed with " << err << "\n"; } - ISODisposeMovie(moov); - return 0; + MP4DisposeHandle(nalusH); + } + + MP4DisposeHandle(sampleEntryH); + MP4DisposeTrackReader(reader); } + + MP4DisposeMovie(moov); + return 0; + } }; int main(int argc, char** argv) { - if (argc < 2) { - std::cerr << "Usage: pcm_sniffer \n"; - return 1; - } - PcmSniffer sniffer; - return sniffer.run(argv[1]); + if (argc < 2) { + std::cerr << "Usage: pcm_sniffer \n"; + return 1; + } + PcmSniffer sniffer; + return sniffer.run(argv[1]); } From 1204c0c0893d3afc57cf2bf6041b0f0af8978307 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Wed, 27 Aug 2025 17:31:55 -0700 Subject: [PATCH 08/11] rename API. Support for encv/resv --- IsoLib/favs_example/src/demux_main.c | 2 +- IsoLib/libisomediafile/src/ISOMovies.h | 2 +- .../src/ISOSampleDescriptions.c | 2 +- IsoLib/pcm_sniffer/pcm_sniffer.cpp | 23 ++++++++++++------- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/IsoLib/favs_example/src/demux_main.c b/IsoLib/favs_example/src/demux_main.c index 9c809d21..882f4d30 100644 --- a/IsoLib/favs_example/src/demux_main.c +++ b/IsoLib/favs_example/src/demux_main.c @@ -182,7 +182,7 @@ ISOErr playMyMovie(struct ParamStruct *parameters, char *filename) { /* Write Sample Entry NAL Units in the beginning of the trak output file */ MP4NewHandle(0, &sampleEntryNALs); - err = ISOGetHEVCNALUs(sampleEntryH, sampleEntryNALs, parameters->hevcExtractionMode); + err = ISOGetHEVCNALUsFromSampleEntry(sampleEntryH, sampleEntryNALs, parameters->hevcExtractionMode); if(err) { printf("Failed to extract NAL units with mode %u (err = %d)\n", parameters->hevcExtractionMode, err); diff --git a/IsoLib/libisomediafile/src/ISOMovies.h b/IsoLib/libisomediafile/src/ISOMovies.h index 24ea3997..04d6a814 100644 --- a/IsoLib/libisomediafile/src/ISOMovies.h +++ b/IsoLib/libisomediafile/src/ISOMovies.h @@ -705,7 +705,7 @@ extern "C" * @param extraction_mode if set to 0 - get them all; 1 - hvcC only; 2 - lhvC only */ ISO_EXTERN(ISOErr) - ISOGetHEVCNALUs(MP4Handle sampleEntryH, MP4Handle nalus, u32 extraction_mode); + ISOGetHEVCNALUsFromSampleEntry(MP4Handle sampleEntryH, MP4Handle nalus, u32 extraction_mode); /** * @brief Gets a restricted video parameter set (AVC or HEVC), placing it in the given handle * @ingroup SampleDescr diff --git a/IsoLib/libisomediafile/src/ISOSampleDescriptions.c b/IsoLib/libisomediafile/src/ISOSampleDescriptions.c index 164a588f..02c24f6d 100644 --- a/IsoLib/libisomediafile/src/ISOSampleDescriptions.c +++ b/IsoLib/libisomediafile/src/ISOSampleDescriptions.c @@ -1199,7 +1199,7 @@ ISOGetHEVCSampleDescriptionPS(MP4Handle sampleEntryH, MP4Handle ps, u32 where, u } MP4_EXTERN(MP4Err) -ISOGetHEVCNALUs(MP4Handle sampleEntryH, MP4Handle nalus, u32 extraction_mode) +ISOGetHEVCNALUsFromSampleEntry(MP4Handle sampleEntryH, MP4Handle nalus, u32 extraction_mode) { MP4Err err = MP4NoErr; MP4VisualSampleEntryAtomPtr entry = NULL; diff --git a/IsoLib/pcm_sniffer/pcm_sniffer.cpp b/IsoLib/pcm_sniffer/pcm_sniffer.cpp index 2b3a6522..fa167486 100644 --- a/IsoLib/pcm_sniffer/pcm_sniffer.cpp +++ b/IsoLib/pcm_sniffer/pcm_sniffer.cpp @@ -88,12 +88,19 @@ class PcmSniffer { u32 sampleEntryType = 0; ISOGetSampleDescriptionType(sampleEntryH, &sampleEntryType); char typeStr[5]; fourccToStr(sampleEntryType, typeStr); - std::cout << "Track " << trackNumber << " sample entry: " << typeStr << "\n"; + std::cout << "Track " << trackNumber << " sample entry: " << typeStr; - // TODO: sampleEntryType == MP4_FOUR_CHAR_CODE('e', 'n', 'c', 'v') - if (sampleEntryType == ISOHEVCSampleEntryAtomType || sampleEntryType == MP4_FOUR_CHAR_CODE('h', 'e', 'v', '1')) { - std::cout << " -> HEVC track: extracting parameter sets\n"; + // If resv or encv, get the original format + if (sampleEntryType == MP4RestrictedVideoSampleEntryAtomType || sampleEntryType == MP4EncVisualSampleEntryAtomType) { + ISOGetOriginalFormat(sampleEntryH, &sampleEntryType); + fourccToStr(sampleEntryType, typeStr); + std::cout << ":" << typeStr << "\n"; + } + else { + std::cout << "\n"; + } + if (sampleEntryType == ISOHEVCSampleEntryAtomType || sampleEntryType == MP4_FOUR_CHAR_CODE('h', 'e', 'v', '1')) { MP4Handle nalusH = nullptr; MP4NewHandle(0, &nalusH); if (!nalusH) { @@ -102,11 +109,11 @@ class PcmSniffer { continue; } - // extraction_mode = 0 get NALUs from hvcC and from lhvC (if present) - err = ISOGetHEVCNALUs(sampleEntryH, nalusH, 0); + // extraction_mode = 0 get NALUs from hvcC (HM may have issues with lhvC) + err = ISOGetHEVCNALUsFromSampleEntry(sampleEntryH, nalusH, 0); if (!err) { u32 size = 0; MP4GetHandleSize(nalusH, &size); - std::cout << "Got " << size << " NALU bytes from sample entry\n"; + // std::cout << "Got " << size << " NALU bytes from sample entry\n"; if (size > 0) { // HM wants a std::istream. Wrap into istringstream, then InputByteStream @@ -149,7 +156,7 @@ class PcmSniffer { } } } else { - std::cerr << "ISOGetHEVCNALUs failed with " << err << "\n"; + std::cerr << "ISOGetHEVCNALUsFromSampleEntry failed with " << err << "\n"; } MP4DisposeHandle(nalusH); From 874298df01b58fc1cf85119acae459da07b9217b Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Wed, 27 Aug 2025 17:34:32 -0700 Subject: [PATCH 09/11] clangformat --- IsoLib/libisomediafile/src/MP4Media.c | 24 +++++++------- IsoLib/libisomediafile/src/MP4Movies.h | 6 ++-- IsoLib/libisomediafile/src/SampleTableAtom.c | 2 +- IsoLib/libisomediafile/src/TimeToSampleAtom.c | 3 +- .../libisomediafile/src/TrackFragmentAtom.c | 31 +++++++++---------- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/IsoLib/libisomediafile/src/MP4Media.c b/IsoLib/libisomediafile/src/MP4Media.c index 8481cf18..822cdf10 100644 --- a/IsoLib/libisomediafile/src/MP4Media.c +++ b/IsoLib/libisomediafile/src/MP4Media.c @@ -181,12 +181,13 @@ ISOAddGroupDescription(MP4Media media, u32 groupType, MP4Handle description, u32 } MP4_EXTERN(MP4Err) -ISOAddT35GroupDescription(MP4Media media, MP4Handle itu_t_t35_data, u32 complete_message_flag, u32 *index) +ISOAddT35GroupDescription(MP4Media media, MP4Handle itu_t_t35_data, u32 complete_message_flag, + u32 *index) { MP4Err err; MP4MediaAtomPtr mdia; MP4Handle description = NULL; - MP4Handle prefix = NULL; + MP4Handle prefix = NULL; if(media == NULL || itu_t_t35_data == NULL) { @@ -195,23 +196,21 @@ ISOAddT35GroupDescription(MP4Media media, MP4Handle itu_t_t35_data, u32 complete mdia = (MP4MediaAtomPtr)media; err = MP4NewHandle(1, &prefix); - if (err) goto bail; + if(err) goto bail; (*prefix)[0] = (complete_message_flag ? 0x80 : 0x00); err = MP4NewHandle(0, &description); - if (err) goto bail; + if(err) goto bail; err = MP4HandleCat(description, prefix); - if (err) goto bail; + if(err) goto bail; err = MP4HandleCat(description, itu_t_t35_data); - if (err) goto bail; + if(err) goto bail; - err = mdia->addGroupDescription(mdia, MP4T35SampleGroupEntry, description, index); + err = mdia->addGroupDescription(mdia, MP4T35SampleGroupEntry, description, index); bail: - if (prefix) - MP4DisposeHandle(prefix); - if (description) - MP4DisposeHandle(description); + if(prefix) MP4DisposeHandle(prefix); + if(description) MP4DisposeHandle(description); TEST_RETURN(err); return err; } @@ -362,7 +361,8 @@ ISOGetSampleGroupSampleNumbers(MP4Media media, u32 groupType, u32 groupIndex, return err; } -/* TODO: add an API that will get sample numbers based on T35 header (if it35 is used for marking samples) */ +/* TODO: add an API that will get sample numbers based on T35 header (if it35 is used for marking + * samples) */ MP4_EXTERN(MP4Err) ISOSetSampleDependency(MP4Media media, s32 sample_index, MP4Handle dependencies) diff --git a/IsoLib/libisomediafile/src/MP4Movies.h b/IsoLib/libisomediafile/src/MP4Movies.h index 078a442a..ab69424d 100644 --- a/IsoLib/libisomediafile/src/MP4Movies.h +++ b/IsoLib/libisomediafile/src/MP4Movies.h @@ -1113,12 +1113,14 @@ extern "C" * * @param media input media object * @param itu_t_t35_data pre-serialized (big-endian) T.35 data that will go inside sgpd - * @param complete_message_flag If set to 1 indicates that the entire T.35 is stored in itu_t_t35_data + * @param complete_message_flag If set to 1 indicates that the entire T.35 is stored in + * itu_t_t35_data * @param index output index of the added group * @return MP4Err error code */ MP4_EXTERN(MP4Err) - ISOAddT35GroupDescription(MP4Media media, MP4Handle itu_t_t35_data, u32 complete_message_flag, u32 *index); + ISOAddT35GroupDescription(MP4Media media, MP4Handle itu_t_t35_data, u32 complete_message_flag, + u32 *index); /** * @brief Returns in the handle ‘description’ the group description associated with the given * group index of the given group type. diff --git a/IsoLib/libisomediafile/src/SampleTableAtom.c b/IsoLib/libisomediafile/src/SampleTableAtom.c index 653fc718..e5f42cc0 100644 --- a/IsoLib/libisomediafile/src/SampleTableAtom.c +++ b/IsoLib/libisomediafile/src/SampleTableAtom.c @@ -737,7 +737,7 @@ static MP4Err getSampleGroupSampleNumbers(struct MP4SampleTableAtom *self, u32 g (*outSampleNumbers)[(*outSampleCnt)++] = i; } } - else + else { /* TODO: make sure we can also get it based on default_group_description_index, */ } diff --git a/IsoLib/libisomediafile/src/TimeToSampleAtom.c b/IsoLib/libisomediafile/src/TimeToSampleAtom.c index 2ab7bfdd..a10a9c48 100644 --- a/IsoLib/libisomediafile/src/TimeToSampleAtom.c +++ b/IsoLib/libisomediafile/src/TimeToSampleAtom.c @@ -150,7 +150,8 @@ static MP4Err extendLastSampleDuration(struct MP4TimeToSampleAtom *self, u32 dur err = MP4NoErr; current = (sttsEntryPtr)self->currentEntry; - if (current == NULL) { + if(current == NULL) + { /* lets treat it as the first sample */ err = addSample(self, duration); goto bail; diff --git a/IsoLib/libisomediafile/src/TrackFragmentAtom.c b/IsoLib/libisomediafile/src/TrackFragmentAtom.c index ffb2f45a..21e2f214 100644 --- a/IsoLib/libisomediafile/src/TrackFragmentAtom.c +++ b/IsoLib/libisomediafile/src/TrackFragmentAtom.c @@ -689,33 +689,32 @@ static MP4Err mergeSampleAuxiliaryInformation(struct MP4TrackFragmentAtom *self, minf = (MP4MediaInformationAtomPtr)mdia->information; stbl = (MP4SampleTableAtomPtr)minf->sampleTable; - for (i = 0; i < self->saizList->entryCount; i++) { - MP4SampleAuxiliaryInformationSizesAtomPtr saizOfTraf = NULL; + for(i = 0; i < self->saizList->entryCount; i++) + { + MP4SampleAuxiliaryInformationSizesAtomPtr saizOfTraf = NULL; MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfTraf = NULL; - MP4SampleAuxiliaryInformationSizesAtomPtr saizOfStbl = NULL; + MP4SampleAuxiliaryInformationSizesAtomPtr saizOfStbl = NULL; MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfStbl = NULL; err = MP4GetListEntry(self->saizList, i, (char **)&saizOfTraf); - if (err || !saizOfTraf) continue; + if(err || !saizOfTraf) continue; err = MP4GetListEntry(self->saioList, i, (char **)&saioOfTraf); - if (err || !saioOfTraf) continue; + if(err || !saioOfTraf) continue; err = stbl->getSampleAuxiliaryInformation( - stbl, - (saizOfTraf->flags & 1), - saizOfTraf->aux_info_type, - saizOfTraf->aux_info_type_parameter, - &saizOfStbl, &saioOfStbl); - if (err) goto bail; - - /* Defensive checks: if stbl had no saiz/saio, skip merging */ - if (saizOfStbl && saioOfStbl) { + stbl, (saizOfTraf->flags & 1), saizOfTraf->aux_info_type, saizOfTraf->aux_info_type_parameter, + &saizOfStbl, &saioOfStbl); + if(err) goto bail; + + /* Defensive checks: if stbl had no saiz/saio, skip merging */ + if(saizOfStbl && saioOfStbl) + { err = saizOfStbl->mergeSizes((MP4AtomPtr)saizOfStbl, (MP4AtomPtr)saizOfTraf); - if (err) goto bail; + if(err) goto bail; err = saioOfStbl->mergeOffsets((MP4AtomPtr)saioOfStbl, (MP4AtomPtr)saioOfTraf, tfhd->base_data_offset); - if (err) goto bail; + if(err) goto bail; } } From 1320a5fc484559730394de26c892bb06f3c81df5 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Wed, 27 Aug 2025 17:35:37 -0700 Subject: [PATCH 10/11] only look inside hvcC. HM has issues with lhvC --- IsoLib/pcm_sniffer/pcm_sniffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IsoLib/pcm_sniffer/pcm_sniffer.cpp b/IsoLib/pcm_sniffer/pcm_sniffer.cpp index fa167486..f012d381 100644 --- a/IsoLib/pcm_sniffer/pcm_sniffer.cpp +++ b/IsoLib/pcm_sniffer/pcm_sniffer.cpp @@ -110,7 +110,7 @@ class PcmSniffer { } // extraction_mode = 0 get NALUs from hvcC (HM may have issues with lhvC) - err = ISOGetHEVCNALUsFromSampleEntry(sampleEntryH, nalusH, 0); + err = ISOGetHEVCNALUsFromSampleEntry(sampleEntryH, nalusH, 1); if (!err) { u32 size = 0; MP4GetHandleSize(nalusH, &size); // std::cout << "Got " << size << " NALU bytes from sample entry\n"; From 47aaf5fe0df3946f4541b85b612d239cc22d4af1 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Thu, 28 Aug 2025 11:26:03 -0700 Subject: [PATCH 11/11] bugfix: sidx reference count + debug msgs --- IsoLib/libisomediafile/src/SegmentIndexAtom.c | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/IsoLib/libisomediafile/src/SegmentIndexAtom.c b/IsoLib/libisomediafile/src/SegmentIndexAtom.c index bec787ea..00a08e0e 100644 --- a/IsoLib/libisomediafile/src/SegmentIndexAtom.c +++ b/IsoLib/libisomediafile/src/SegmentIndexAtom.c @@ -196,6 +196,8 @@ static MP4Err createFromInputStream(MP4AtomPtr s, MP4AtomPtr proto, MP4InputStre MP4Err err; u32 i; u32 tmp32; + char debug_buffer[70]; + u16 tmpReferenceCount = 0; MP4SegmentIndexAtomPtr self = (MP4SegmentIndexAtomPtr)s; @@ -211,11 +213,10 @@ static MP4Err createFromInputStream(MP4AtomPtr s, MP4AtomPtr proto, MP4InputStre GET32(firstOffset); GET16(reserved1); - GET16(referenceCount); + GET16_V(tmpReferenceCount); - for(i = 0; i < self->referenceCount; i++) + for(i = 0; i < tmpReferenceCount; i++) { - u8 referenceType; u32 referencedSize; u32 subsegmentDuration; @@ -223,24 +224,37 @@ static MP4Err createFromInputStream(MP4AtomPtr s, MP4AtomPtr proto, MP4InputStre u8 SAPType; u32 SAPDeltaTime; - GET32_V(tmp32); + snprintf(debug_buffer, sizeof(debug_buffer), "\nEntry %d of %d", i, self->referenceCount); + DEBUG_MSG(debug_buffer); + + GET32_V_NOMSG(tmp32); referenceType = (tmp32 >> 31) & 0x01; - referencedSize = tmp32 & 0x7FFF; + referencedSize = tmp32 & 0x7FFFFFFF; + snprintf(debug_buffer, sizeof(debug_buffer), "reference_type = %d", referenceType); + DEBUG_MSG(debug_buffer); + snprintf(debug_buffer, sizeof(debug_buffer), "referenced_size = %d", referencedSize); + DEBUG_MSG(debug_buffer); GET32_V(subsegmentDuration); - GET32_V(tmp32); + GET32_V_NOMSG(tmp32); startsWithSAP = (tmp32 >> 31) & 0x01; SAPType = (tmp32 >> 28) & 0x07; SAPDeltaTime = tmp32 & 0x0FFFFFFF; + snprintf(debug_buffer, sizeof(debug_buffer), "starts_with_SAP = %d", startsWithSAP); + DEBUG_MSG(debug_buffer); + snprintf(debug_buffer, sizeof(debug_buffer), "SAP_type = %d", SAPType); + DEBUG_MSG(debug_buffer); + snprintf(debug_buffer, sizeof(debug_buffer), "SAP_delta_time = %d", SAPDeltaTime); + DEBUG_MSG(debug_buffer); err = addReference(self, referenceType, referencedSize, subsegmentDuration, startsWithSAP, SAPType, SAPDeltaTime); if(err) goto bail; } + assert(self->referenceCount == tmpReferenceCount); assert(self->bytesRead == self->size); bail: TEST_RETURN(err); - return err; }