diff --git a/CMakeLists.txt b/CMakeLists.txt index a58ff119..12aa9949 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,5 +50,6 @@ if(NOT ISOBMFF_BUILD_LIB_ONLY) add_subdirectory(IsoLib/isoiff_tool) add_subdirectory(IsoLib/pcm_audio_example) add_subdirectory(IsoLib/vvc_base) + add_subdirectory(IsoLib/pcm_sniffer) add_subdirectory(test) endif() 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 85489a9f..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; @@ -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); 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..822cdf10 100644 --- a/IsoLib/libisomediafile/src/MP4Media.c +++ b/IsoLib/libisomediafile/src/MP4Media.c @@ -177,7 +177,41 @@ 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 +361,9 @@ 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.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..ab69424d 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 ); @@ -1098,6 +1108,19 @@ 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..e5f42cc0 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/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; } diff --git a/IsoLib/libisomediafile/src/TimeToSampleAtom.c b/IsoLib/libisomediafile/src/TimeToSampleAtom.c index 2d68ac47..a10a9c48 100644 --- a/IsoLib/libisomediafile/src/TimeToSampleAtom.c +++ b/IsoLib/libisomediafile/src/TimeToSampleAtom.c @@ -150,6 +150,13 @@ 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 +170,6 @@ static MP4Err extendLastSampleDuration(struct MP4TimeToSampleAtom *self, u32 dur bail: TEST_RETURN(err); - return err; } diff --git a/IsoLib/libisomediafile/src/TrackFragmentAtom.c b/IsoLib/libisomediafile/src/TrackFragmentAtom.c index 909605a9..21e2f214 100644 --- a/IsoLib/libisomediafile/src/TrackFragmentAtom.c +++ b/IsoLib/libisomediafile/src/TrackFragmentAtom.c @@ -691,28 +691,35 @@ static MP4Err mergeSampleAuxiliaryInformation(struct MP4TrackFragmentAtom *self, for(i = 0; i < self->saizList->entryCount; i++) { - MP4SampleAuxiliaryInformationSizesAtomPtr saizOfTraf; - MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfTraf; - MP4SampleAuxiliaryInformationSizesAtomPtr saizOfStbl; - MP4SampleAuxiliaryInformationOffsetsAtomPtr saioOfStbl; + 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; + /* 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; } 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..f012d381 --- /dev/null +++ b/IsoLib/pcm_sniffer/pcm_sniffer.cpp @@ -0,0 +1,181 @@ +/** + * @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. + * + */ + +// 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 + +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) { + 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; + + // 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) { + MP4DisposeHandle(sampleEntryH); + MP4DisposeTrackReader(reader); + continue; + } + + // extraction_mode = 0 get NALUs from hvcC (HM may have issues with lhvC) + err = ISOGetHEVCNALUsFromSampleEntry(sampleEntryH, nalusH, 1); + 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"; + } + } + } + } else { + std::cerr << "ISOGetHEVCNALUsFromSampleEntry failed with " << err << "\n"; + } + + 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]); +} 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_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 new file mode 100644 index 00000000..7e67eaad --- /dev/null +++ b/test/test_t35.cpp @@ -0,0 +1,182 @@ +/** + * @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 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") + { + MP4Movie moov; + MP4Track trak; + MP4Media media; + + 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); + + err = ISOGetGroupDescriptionEntryCount(media, MP4T35SampleGroupEntry, &it35_sg_cnt); + CHECK(err == MP4NoErr); + CHECK(1 == it35_sg_cnt); + + 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); +}