From 51b7f45653cff969439aceecb465c112f6d1982e Mon Sep 17 00:00:00 2001 From: Chema Gonzalez Date: Thu, 15 Jan 2026 14:55:06 -0800 Subject: [PATCH 1/2] Fix warnings on Linux building MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tested: Before: ``` $ cd build && CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_TESTING=ON .. CMake Deprecation Warning at lib/mp4v2/CMakeLists.txt:1 (cmake_minimum_required): Compatibility with CMake < 3.10 will be removed from a future version of CMake. Update the VERSION argument value. Or, use the ... syntax to tell CMake that the project requires at least but has been updated to work with policies introduced by or earlier. ... | ^~~~~~~~~~~~~~~~~~~~ : note: this is the location of the previous definition [ 24%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/qtff/PictureAspectRatioBox.cpp.o In file included from /mp4seek/lib/mp4v2/libplatform/platform_posix.h:6, from /mp4seek/lib/mp4v2/libplatform/platform.h:24, from /mp4seek/lib/mp4v2/src/src.h:6, from /mp4seek/lib/mp4v2/src/impl.h:6, from /mp4seek/lib/mp4v2/src/qtff/impl.h:29, from /mp4seek/lib/mp4v2/src/qtff/PictureAspectRatioBox.cpp:24: /mp4seek/build/lib/mp4v2/libplatform/config.h:30:10: warning: ‘MP4V2_USE_STATIC_LIB’ redefined 30 | # define MP4V2_USE_STATIC_LIB | ^~~~~~~~~~~~~~~~~~~~ : note: this is the location of the previous definition [ 25%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/3gp.cpp.o In file included from /mp4seek/lib/mp4v2/libplatform/platform_posix.h:6, from /mp4seek/lib/mp4v2/libplatform/platform.h:24, from /mp4seek/lib/mp4v2/src/src.h:6, from /mp4seek/lib/mp4v2/src/impl.h:6, ... ``` After: ``` $ cd build && CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_TESTING=ON .. CMake Deprecation Warning at lib/mp4v2/CMakeLists.txt:1 (cmake_minimum_required): Compatibility with CMake < 3.10 will be removed from a future version of CMake. Update the VERSION argument value. Or, use the ... syntax to tell CMake that the project requires at least but has been updated to work with policies introduced by or earlier. -- Configuring done (1.1s) -- Generating done (0.0s) -- Build files have been written to: /mp4seek/build cd build && make -j 8 make[1]: Entering directory '/mp4seek/build' [ 2%] Built target gtest [ 4%] Built target gtest_main [ 5%] Built target gmock [ 7%] Built target gmock_main [ 8%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/io/File_posix.cpp.o [ 9%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/number/random_posix.cpp.o [ 9%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/io/FileSystem_posix.cpp.o [ 12%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/io/FileSystem.cpp.o [ 13%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/prog/option.cpp.o [ 13%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/io/File.cpp.o [ 13%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/time/time_posix.cpp.o [ 14%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/sys/error.cpp.o [ 15%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libplatform/time/time.cpp.o [ 16%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libutil/other.cpp.o [ 17%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libutil/TrackModifier.cpp.o [ 17%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/libutil/Utility.cpp.o [ 18%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/bmff/typebmff.cpp.o [ 19%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/itmf/CoverArtBox.cpp.o [ 20%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/itmf/generic.cpp.o [ 21%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/itmf/Tags.cpp.o [ 22%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/itmf/type.cpp.o [ 23%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/qtff/coding.cpp.o [ 24%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/qtff/ColorParameterBox.cpp.o [ 24%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/qtff/PictureAspectRatioBox.cpp.o [ 25%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/3gp.cpp.o [ 26%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/atom_ac3.cpp.o [ 27%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/atom_amr.cpp.o [ 28%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/atom_avc1.cpp.o [ 29%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/atom_avcC.cpp.o [ 30%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/atom_chpl.cpp.o [ 31%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/atom_colr.cpp.o [ 32%] Building CXX object lib/mp4v2/CMakeFiles/mp4v2.dir/src/atom_d263.cpp.o ... ``` --- libplatform/config.h.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libplatform/config.h.cmake b/libplatform/config.h.cmake index 49bacbe..8ad5ade 100644 --- a/libplatform/config.h.cmake +++ b/libplatform/config.h.cmake @@ -26,7 +26,7 @@ builds, but we use MP4V2_EXPORTS instead. */ #ifdef DLL_EXPORT # define MP4V2_EXPORTS -#else +#elif !defined(MP4V2_USE_STATIC_LIB) # define MP4V2_USE_STATIC_LIB #endif From d7bf4bf8f42c5348a6f4680722ae2833ae3d1fc9 Mon Sep 17 00:00:00 2001 From: Chema Gonzalez Date: Thu, 15 Jan 2026 17:59:00 -0800 Subject: [PATCH 2/2] Add support for HEVC video tracks --- CMakeLists.txt | 3 + include/mp4v2/track.h | 47 ++++++ src/atom_hev1.cpp | 86 +++++++++++ src/atom_hvc1.cpp | 86 +++++++++++ src/atom_hvcC.cpp | 324 ++++++++++++++++++++++++++++++++++++++++++ src/atom_stsd.cpp | 2 + src/atoms.h | 34 +++++ src/mp4.cpp | 64 +++++++++ src/mp4atom.cpp | 6 + src/mp4file.cpp | 69 +++++++++ src/mp4file.h | 20 +++ 11 files changed, 741 insertions(+) create mode 100644 src/atom_hev1.cpp create mode 100644 src/atom_hvc1.cpp create mode 100644 src/atom_hvcC.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 94a6b7e..a7112d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,9 @@ set(MP4V2_SOURCE_FILES src/atom_amr.cpp src/atom_avc1.cpp src/atom_avcC.cpp + src/atom_hvc1.cpp + src/atom_hev1.cpp + src/atom_hvcC.cpp src/atom_chpl.cpp src/atom_colr.cpp src/atom_d263.cpp diff --git a/include/mp4v2/track.h b/include/mp4v2/track.h index 8686004..1f58c7c 100644 --- a/include/mp4v2/track.h +++ b/include/mp4v2/track.h @@ -296,6 +296,53 @@ void MP4AddH264PictureParameterSet( const uint8_t* pPict, uint16_t pictLen ); +/** Add an HEVC (H265) video track. + * + * This function adds an HEVC video track to the file. The track uses + * the 'hvc1' sample entry type. + * + * @param hFile handle of file to add track to. + * @param timeScale the timescale of the track. + * @param sampleDuration the default sample duration. + * @param width the video width in pixels. + * @param height the video height in pixels. + * @param general_profile_idc the HEVC profile indicator. + * @param general_level_idc the HEVC level indicator. + * @param sampleLenFieldSizeMinusOne the size of the NAL unit length + * field minus one. Valid values are 0, 1, and 3, corresponding + * to NAL unit lengths of 1, 2, and 4 bytes. + * + * @return the track ID of the new track, or MP4_INVALID_TRACK_ID on failure. + */ +MP4V2_EXPORT +MP4TrackId MP4AddHEVCVideoTrack( + MP4FileHandle hFile, + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne ); + +/** Add an HEVC (H265) video track with hev1 sample entry. + * + * Similar to MP4AddHEVCVideoTrack but uses 'hev1' sample entry type. + * Use hev1 when parameter sets may appear in-band (within samples). + * + * @see MP4AddHEVCVideoTrack for parameter documentation. + */ +MP4V2_EXPORT +MP4TrackId MP4AddHEV1VideoTrack( + MP4FileHandle hFile, + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne ); + MP4V2_EXPORT void MP4SetH263Vendor( MP4FileHandle hFile, diff --git a/src/atom_hev1.cpp b/src/atom_hev1.cpp new file mode 100644 index 0000000..82b8ec3 --- /dev/null +++ b/src/atom_hev1.cpp @@ -0,0 +1,86 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * Contributor(s): + * Chema Gonzalez + */ + +#include "src/impl.h" + +namespace mp4v2 { +namespace impl { + +/////////////////////////////////////////////////////////////////////////////// + +MP4Hev1Atom::MP4Hev1Atom(MP4File &file) + : MP4Atom(file, "hev1") +{ + AddReserved(*this, "reserved1", 6); /* 0 */ + + AddProperty( /* 1 */ + new MP4Integer16Property(*this, "dataReferenceIndex")); + + AddReserved(*this, "reserved2", 16); /* 2 */ + + AddProperty( /* 3 */ + new MP4Integer16Property(*this, "width")); + AddProperty( /* 4 */ + new MP4Integer16Property(*this, "height")); + + AddReserved(*this, "reserved3", 14); /* 5 */ + + MP4StringProperty* pProp = + new MP4StringProperty(*this, "compressorName"); + pProp->SetFixedLength(32); + pProp->SetCountedFormat(true); + pProp->SetValue("HEVC Coding"); + AddProperty(pProp); /* 6 */ + + AddReserved(*this, "reserved4", 4); /* 7 */ + + ExpectChildAtom("hvcC", Required, OnlyOne); + ExpectChildAtom("btrt", Optional, OnlyOne); + ExpectChildAtom("colr", Optional, OnlyOne); + ExpectChildAtom("pasp", Optional, OnlyOne); +} + +void MP4Hev1Atom::Generate() +{ + MP4Atom::Generate(); + + ((MP4Integer16Property*)m_pProperties[1])->SetValue(1); + + // property reserved3 has non-zero fixed values + static uint8_t reserved3[14] = { + 0x00, 0x48, 0x00, 0x00, + 0x00, 0x48, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + }; + m_pProperties[5]->SetReadOnly(false); + ((MP4BytesProperty*)m_pProperties[5])-> + SetValue(reserved3, sizeof(reserved3)); + m_pProperties[5]->SetReadOnly(true); + + // property reserved4 has non-zero fixed values + static uint8_t reserved4[4] = { + 0x00, 0x18, 0xFF, 0xFF, + }; + m_pProperties[7]->SetReadOnly(false); + ((MP4BytesProperty*)m_pProperties[7])-> + SetValue(reserved4, sizeof(reserved4)); + m_pProperties[7]->SetReadOnly(true); +} + +/////////////////////////////////////////////////////////////////////////////// + +} +} // namespace mp4v2::impl diff --git a/src/atom_hvc1.cpp b/src/atom_hvc1.cpp new file mode 100644 index 0000000..757d267 --- /dev/null +++ b/src/atom_hvc1.cpp @@ -0,0 +1,86 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * Contributor(s): + * Chema Gonzalez + */ + +#include "src/impl.h" + +namespace mp4v2 { +namespace impl { + +/////////////////////////////////////////////////////////////////////////////// + +MP4Hvc1Atom::MP4Hvc1Atom(MP4File &file) + : MP4Atom(file, "hvc1") +{ + AddReserved(*this, "reserved1", 6); /* 0 */ + + AddProperty( /* 1 */ + new MP4Integer16Property(*this, "dataReferenceIndex")); + + AddReserved(*this, "reserved2", 16); /* 2 */ + + AddProperty( /* 3 */ + new MP4Integer16Property(*this, "width")); + AddProperty( /* 4 */ + new MP4Integer16Property(*this, "height")); + + AddReserved(*this, "reserved3", 14); /* 5 */ + + MP4StringProperty* pProp = + new MP4StringProperty(*this, "compressorName"); + pProp->SetFixedLength(32); + pProp->SetCountedFormat(true); + pProp->SetValue("HEVC Coding"); + AddProperty(pProp); /* 6 */ + + AddReserved(*this, "reserved4", 4); /* 7 */ + + ExpectChildAtom("hvcC", Required, OnlyOne); + ExpectChildAtom("btrt", Optional, OnlyOne); + ExpectChildAtom("colr", Optional, OnlyOne); + ExpectChildAtom("pasp", Optional, OnlyOne); +} + +void MP4Hvc1Atom::Generate() +{ + MP4Atom::Generate(); + + ((MP4Integer16Property*)m_pProperties[1])->SetValue(1); + + // property reserved3 has non-zero fixed values + static uint8_t reserved3[14] = { + 0x00, 0x48, 0x00, 0x00, + 0x00, 0x48, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + }; + m_pProperties[5]->SetReadOnly(false); + ((MP4BytesProperty*)m_pProperties[5])-> + SetValue(reserved3, sizeof(reserved3)); + m_pProperties[5]->SetReadOnly(true); + + // property reserved4 has non-zero fixed values + static uint8_t reserved4[4] = { + 0x00, 0x18, 0xFF, 0xFF, + }; + m_pProperties[7]->SetReadOnly(false); + ((MP4BytesProperty*)m_pProperties[7])-> + SetValue(reserved4, sizeof(reserved4)); + m_pProperties[7]->SetReadOnly(true); +} + +/////////////////////////////////////////////////////////////////////////////// + +} +} // namespace mp4v2::impl diff --git a/src/atom_hvcC.cpp b/src/atom_hvcC.cpp new file mode 100644 index 0000000..6ea5cec --- /dev/null +++ b/src/atom_hvcC.cpp @@ -0,0 +1,324 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * Contributor(s): + * Chema Gonzalez + */ + +#include "src/impl.h" + +namespace mp4v2 { +namespace impl { + +/////////////////////////////////////////////////////////////////////////////// + +/* + * HvcCSizeTableProperty is a special version of the MP4TableProperty - + * the BytesProperty will need to set the value before it can read + * from the file. This is similar to SizeTableProperty in atom_avcC.cpp. + */ +class HvcCSizeTableProperty : public MP4TableProperty +{ +public: + HvcCSizeTableProperty(MP4Atom& parentAtom, const char *name, MP4IntegerProperty *pCountProperty) : + MP4TableProperty(parentAtom, name, pCountProperty) {}; +protected: + void ReadEntry(MP4File& file, uint32_t index) { + // Each table entry has a size (16-bit), followed by the NAL unit data + // first, read the length + m_pProperties[0]->Read(file, index); + MP4IntegerProperty *pIntProp = (MP4IntegerProperty *)m_pProperties[0]; + // set the size in the bytes property + MP4BytesProperty *pBytesProp = (MP4BytesProperty *)m_pProperties[1]; + pBytesProp->SetValueSize(pIntProp->GetValue(index), index); + // And read the bytes + m_pProperties[1]->Read(file, index); + }; +private: + HvcCSizeTableProperty(); + HvcCSizeTableProperty ( const HvcCSizeTableProperty &src ); + HvcCSizeTableProperty &operator= ( const HvcCSizeTableProperty &src ); +}; + +/* + * HvcCNalArrayProperty represents one NAL unit array in the hvcC atom. + * Each array has: + * - array_completeness (1 bit) + reserved (1 bit) + NAL_unit_type (6 bits) + * - numNalus (16 bits) + * - For each NAL: nalUnitLength (16 bits) + nalUnit (bytes) + */ +class HvcCNalArrayProperty : public MP4TableProperty +{ +public: + HvcCNalArrayProperty(MP4Atom& parentAtom, const char *name, MP4IntegerProperty *pCountProperty) : + MP4TableProperty(parentAtom, name, pCountProperty) {}; +protected: + void ReadEntry(MP4File& file, uint32_t index); +private: + HvcCNalArrayProperty(); + HvcCNalArrayProperty ( const HvcCNalArrayProperty &src ); + HvcCNalArrayProperty &operator= ( const HvcCNalArrayProperty &src ); +}; + +void HvcCNalArrayProperty::ReadEntry(MP4File& file, uint32_t index) +{ + // Read the array header (1 byte: completeness + type) + m_pProperties[0]->Read(file, index); + + // Read the NAL count (16 bits) + m_pProperties[1]->Read(file, index); + MP4Integer16Property *pCountProp = (MP4Integer16Property *)m_pProperties[1]; + uint16_t nalCount = pCountProp->GetValue(index); + + // The NAL units themselves are in nested tables, but for simplicity + // we'll store them as opaque data that includes all NAL units for this array + // This is handled by the parent atom's custom read logic + + // For now, just read the remaining properties + for (uint32_t i = 2; i < m_pProperties.Size(); i++) { + m_pProperties[i]->Read(file, index); + } +} + +MP4HvcCAtom::MP4HvcCAtom(MP4File &file) + : MP4Atom(file, "hvcC") +{ + // Configuration version (always 1) + AddProperty( new MP4Integer8Property(*this, "configurationVersion")); /* 0 */ + + // general_profile_space (2 bits) + general_tier_flag (1 bit) + general_profile_idc (5 bits) + AddProperty( new MP4BitfieldProperty(*this, "general_profile_space", 2)); /* 1 */ + AddProperty( new MP4BitfieldProperty(*this, "general_tier_flag", 1)); /* 2 */ + AddProperty( new MP4BitfieldProperty(*this, "general_profile_idc", 5)); /* 3 */ + + // general_profile_compatibility_flags (32 bits) + AddProperty( new MP4Integer32Property(*this, "general_profile_compatibility_flags")); /* 4 */ + + // general_constraint_indicator_flags (48 bits = 6 bytes) + AddProperty( new MP4BytesProperty(*this, "general_constraint_indicator_flags", 6)); /* 5 */ + + // general_level_idc (8 bits) + AddProperty( new MP4Integer8Property(*this, "general_level_idc")); /* 6 */ + + // reserved (4 bits, 0xF) + min_spatial_segmentation_idc (12 bits) + AddProperty( new MP4BitfieldProperty(*this, "reserved1", 4)); /* 7 */ + AddProperty( new MP4BitfieldProperty(*this, "min_spatial_segmentation_idc", 12)); /* 8 */ + + // reserved (6 bits, 0x3F) + parallelismType (2 bits) + AddProperty( new MP4BitfieldProperty(*this, "reserved2", 6)); /* 9 */ + AddProperty( new MP4BitfieldProperty(*this, "parallelismType", 2)); /* 10 */ + + // reserved (6 bits, 0x3F) + chromaFormat (2 bits) + AddProperty( new MP4BitfieldProperty(*this, "reserved3", 6)); /* 11 */ + AddProperty( new MP4BitfieldProperty(*this, "chromaFormat", 2)); /* 12 */ + + // reserved (5 bits, 0x1F) + bitDepthLumaMinus8 (3 bits) + AddProperty( new MP4BitfieldProperty(*this, "reserved4", 5)); /* 13 */ + AddProperty( new MP4BitfieldProperty(*this, "bitDepthLumaMinus8", 3)); /* 14 */ + + // reserved (5 bits, 0x1F) + bitDepthChromaMinus8 (3 bits) + AddProperty( new MP4BitfieldProperty(*this, "reserved5", 5)); /* 15 */ + AddProperty( new MP4BitfieldProperty(*this, "bitDepthChromaMinus8", 3)); /* 16 */ + + // avgFrameRate (16 bits) + AddProperty( new MP4Integer16Property(*this, "avgFrameRate")); /* 17 */ + + // constantFrameRate (2 bits) + numTemporalLayers (3 bits) + + // temporalIdNested (1 bit) + lengthSizeMinusOne (2 bits) + AddProperty( new MP4BitfieldProperty(*this, "constantFrameRate", 2)); /* 18 */ + AddProperty( new MP4BitfieldProperty(*this, "numTemporalLayers", 3)); /* 19 */ + AddProperty( new MP4BitfieldProperty(*this, "temporalIdNested", 1)); /* 20 */ + AddProperty( new MP4BitfieldProperty(*this, "lengthSizeMinusOne", 2)); /* 21 */ + + // numOfArrays (8 bits) - count of NAL unit arrays + AddProperty( new MP4Integer8Property(*this, "numOfArrays")); /* 22 */ + + // The NAL unit arrays are complex: each array has a type and contains + // multiple NAL units. For simplicity, we store the raw bytes after numOfArrays + // and provide helper functions to parse/build them. + // This approach is simpler than deeply nested table properties. + AddProperty( new MP4BytesProperty(*this, "nalArraysData")); /* 23 */ +} + +void MP4HvcCAtom::Generate() +{ + MP4Atom::Generate(); + + // configurationVersion = 1 + ((MP4Integer8Property*)m_pProperties[0])->SetValue(1); + + // Set reserved fields to their required values + m_pProperties[7]->SetReadOnly(false); + ((MP4BitfieldProperty*)m_pProperties[7])->SetValue(0xF); // reserved1 + m_pProperties[7]->SetReadOnly(true); + + m_pProperties[9]->SetReadOnly(false); + ((MP4BitfieldProperty*)m_pProperties[9])->SetValue(0x3F); // reserved2 + m_pProperties[9]->SetReadOnly(true); + + m_pProperties[11]->SetReadOnly(false); + ((MP4BitfieldProperty*)m_pProperties[11])->SetValue(0x3F); // reserved3 + m_pProperties[11]->SetReadOnly(true); + + m_pProperties[13]->SetReadOnly(false); + ((MP4BitfieldProperty*)m_pProperties[13])->SetValue(0x1F); // reserved4 + m_pProperties[13]->SetReadOnly(true); + + m_pProperties[15]->SetReadOnly(false); + ((MP4BitfieldProperty*)m_pProperties[15])->SetValue(0x1F); // reserved5 + m_pProperties[15]->SetReadOnly(true); + + // lengthSizeMinusOne defaults to 3 (4-byte NAL length fields) + ((MP4BitfieldProperty*)m_pProperties[21])->SetValue(3); + + // numOfArrays defaults to 0 + ((MP4Integer8Property*)m_pProperties[22])->SetValue(0); +} + +void MP4HvcCAtom::Read() +{ + // Read all fixed properties first + ReadProperties(0, 23); + + // Get the number of NAL arrays + uint8_t numArrays = ((MP4Integer8Property*)m_pProperties[22])->GetValue(); + + if (numArrays > 0) { + // Calculate how many bytes remain in the atom for NAL array data + uint64_t remainingBytes = GetEnd() - m_File.GetPosition(); + + if (remainingBytes > 0) { + // Read all NAL array data as opaque bytes + MP4BytesProperty* pNalData = (MP4BytesProperty*)m_pProperties[23]; + pNalData->SetValueSize(remainingBytes); + pNalData->Read(m_File); + } + } + + Skip(); // Skip any remaining data +} + +// +// Clone - clone my properties to destination atom +// +void MP4HvcCAtom::Clone(MP4HvcCAtom *dstAtom) +{ + // Start with defaults + dstAtom->Generate(); + + // Copy non-reserved properties + // 0: configurationVersion (skip, set by Generate) + + // 1-3: profile_space, tier_flag, profile_idc + MP4Property *dstProperty; + + dstProperty = dstAtom->GetProperty(1); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[1])->GetValue()); + + dstProperty = dstAtom->GetProperty(2); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[2])->GetValue()); + + dstProperty = dstAtom->GetProperty(3); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[3])->GetValue()); + + // 4: profile_compatibility_flags + dstProperty = dstAtom->GetProperty(4); + ((MP4Integer32Property *)dstProperty)->SetValue( + ((MP4Integer32Property *)m_pProperties[4])->GetValue()); + + // 5: constraint_indicator_flags (6 bytes) + dstProperty = dstAtom->GetProperty(5); + uint8_t constraintFlags[6]; + ((MP4BytesProperty *)m_pProperties[5])->CopyValue(constraintFlags, 0); + ((MP4BytesProperty *)dstProperty)->SetValue(constraintFlags, 6); + + // 6: level_idc + dstProperty = dstAtom->GetProperty(6); + ((MP4Integer8Property *)dstProperty)->SetValue( + ((MP4Integer8Property *)m_pProperties[6])->GetValue()); + + // 7-8: reserved1, min_spatial_segmentation_idc + dstProperty = dstAtom->GetProperty(8); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[8])->GetValue()); + + // 9-10: reserved2, parallelismType + dstProperty = dstAtom->GetProperty(10); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[10])->GetValue()); + + // 11-12: reserved3, chromaFormat + dstProperty = dstAtom->GetProperty(12); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[12])->GetValue()); + + // 13-14: reserved4, bitDepthLumaMinus8 + dstProperty = dstAtom->GetProperty(14); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[14])->GetValue()); + + // 15-16: reserved5, bitDepthChromaMinus8 + dstProperty = dstAtom->GetProperty(16); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[16])->GetValue()); + + // 17: avgFrameRate + dstProperty = dstAtom->GetProperty(17); + ((MP4Integer16Property *)dstProperty)->SetValue( + ((MP4Integer16Property *)m_pProperties[17])->GetValue()); + + // 18-21: constantFrameRate, numTemporalLayers, temporalIdNested, lengthSizeMinusOne + dstProperty = dstAtom->GetProperty(18); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[18])->GetValue()); + + dstProperty = dstAtom->GetProperty(19); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[19])->GetValue()); + + dstProperty = dstAtom->GetProperty(20); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[20])->GetValue()); + + dstProperty = dstAtom->GetProperty(21); + ((MP4BitfieldProperty *)dstProperty)->SetValue( + ((MP4BitfieldProperty *)m_pProperties[21])->GetValue()); + + // 22: numOfArrays + dstProperty = dstAtom->GetProperty(22); + dstProperty->SetReadOnly(false); + ((MP4Integer8Property *)dstProperty)->SetValue( + ((MP4Integer8Property *)m_pProperties[22])->GetValue()); + dstProperty->SetReadOnly(true); + + // 23: nalArraysData (copy all NAL unit arrays) + MP4BytesProperty* srcNalData = (MP4BytesProperty*)m_pProperties[23]; + MP4BytesProperty* dstNalData = (MP4BytesProperty*)dstAtom->GetProperty(23); + + uint32_t nalDataSize = 0; + uint8_t* nalDataValue = nullptr; + srcNalData->GetValue(&nalDataValue, &nalDataSize); + + if (nalDataValue != nullptr && nalDataSize > 0) { + uint8_t* tmp = (uint8_t*)MP4Malloc(nalDataSize); + memcpy(tmp, nalDataValue, nalDataSize); + dstNalData->SetValue(tmp, nalDataSize); + MP4Free(tmp); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +} +} // namespace mp4v2::impl diff --git a/src/atom_stsd.cpp b/src/atom_stsd.cpp index c83ff39..c9e68b7 100644 --- a/src/atom_stsd.cpp +++ b/src/atom_stsd.cpp @@ -58,6 +58,8 @@ MP4StsdAtom::MP4StsdAtom(MP4File &file) ExpectChildAtom("sawb", Optional, Many); // For AMR-WB ExpectChildAtom("s263", Optional, Many); // For H.263 ExpectChildAtom("avc1", Optional, Many); + ExpectChildAtom("hvc1", Optional, Many); + ExpectChildAtom("hev1", Optional, Many); ExpectChildAtom("alac", Optional, Many); ExpectChildAtom("text", Optional, Many); ExpectChildAtom("tx3g", Optional, Many); diff --git a/src/atoms.h b/src/atoms.h index 382706f..3d66246 100644 --- a/src/atoms.h +++ b/src/atoms.h @@ -178,6 +178,40 @@ class MP4AvcCAtom : public MP4Atom { MP4AvcCAtom &operator= ( const MP4AvcCAtom &src ); }; +// H265/HEVC atoms + +class MP4Hvc1Atom : public MP4Atom { +public: + MP4Hvc1Atom(MP4File &file); + void Generate(); +private: + MP4Hvc1Atom(); + MP4Hvc1Atom( const MP4Hvc1Atom &src ); + MP4Hvc1Atom &operator= ( const MP4Hvc1Atom &src ); +}; + +class MP4Hev1Atom : public MP4Atom { +public: + MP4Hev1Atom(MP4File &file); + void Generate(); +private: + MP4Hev1Atom(); + MP4Hev1Atom( const MP4Hev1Atom &src ); + MP4Hev1Atom &operator= ( const MP4Hev1Atom &src ); +}; + +class MP4HvcCAtom : public MP4Atom { +public: + MP4HvcCAtom(MP4File &file); + void Generate(); + void Read(); + void Clone(MP4HvcCAtom *dstAtom); +private: + MP4HvcCAtom(); + MP4HvcCAtom( const MP4HvcCAtom &src ); + MP4HvcCAtom &operator= ( const MP4HvcCAtom &src ); +}; + class MP4D263Atom : public MP4Atom { public: diff --git a/src/mp4.cpp b/src/mp4.cpp index c56f3ee..6eebc3a 100644 --- a/src/mp4.cpp +++ b/src/mp4.cpp @@ -1273,6 +1273,70 @@ MP4FileHandle MP4ModifyCallbacks(const MP4IOCallbacks* callbacks, return MP4_INVALID_TRACK_ID; } + MP4TrackId MP4AddHEVCVideoTrack(MP4FileHandle hFile, + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne) + { + if (MP4_IS_VALID_FILE_HANDLE(hFile)) { + try { + MP4File *pFile = (MP4File *)hFile; + + return pFile->AddHEVCVideoTrack(timeScale, + sampleDuration, + width, + height, + general_profile_idc, + general_level_idc, + sampleLenFieldSizeMinusOne); + } + catch( Exception* x ) { + mp4v2::impl::log.errorf(*x); + delete x; + } + catch( ... ) { + mp4v2::impl::log.errorf( "%s: failed", __FUNCTION__ ); + } + } + return MP4_INVALID_TRACK_ID; + } + + MP4TrackId MP4AddHEV1VideoTrack(MP4FileHandle hFile, + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne) + { + if (MP4_IS_VALID_FILE_HANDLE(hFile)) { + try { + MP4File *pFile = (MP4File *)hFile; + + return pFile->AddHEV1VideoTrack(timeScale, + sampleDuration, + width, + height, + general_profile_idc, + general_level_idc, + sampleLenFieldSizeMinusOne); + } + catch( Exception* x ) { + mp4v2::impl::log.errorf(*x); + delete x; + } + catch( ... ) { + mp4v2::impl::log.errorf( "%s: failed", __FUNCTION__ ); + } + } + return MP4_INVALID_TRACK_ID; + } + MP4TrackId MP4AddEncH264VideoTrack( MP4FileHandle hFile, uint32_t timeScale, diff --git a/src/mp4atom.cpp b/src/mp4atom.cpp index 999fa3e..31e6119 100644 --- a/src/mp4atom.cpp +++ b/src/mp4atom.cpp @@ -892,6 +892,12 @@ MP4Atom::factory( MP4File &file, MP4Atom* parent, const char* type ) return new MP4VideoAtom( file, type ); if( ATOMID(type) == ATOMID("href") ) return new MP4HrefAtom(file); + if( ATOMID(type) == ATOMID("hvc1") ) + return new MP4Hvc1Atom(file); + if( ATOMID(type) == ATOMID("hev1") ) + return new MP4Hev1Atom(file); + if( ATOMID(type) == ATOMID("hvcC") ) + return new MP4HvcCAtom(file); break; case 'i': diff --git a/src/mp4file.cpp b/src/mp4file.cpp index 4e43903..3d2c056 100644 --- a/src/mp4file.cpp +++ b/src/mp4file.cpp @@ -2023,6 +2023,75 @@ MP4TrackId MP4File::AddEncH264VideoTrack( } +// HEVC/H265 track creation + +MP4TrackId MP4File::AddHEVCVideoTrack( + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne) +{ + MP4TrackId trackId = AddVideoTrackDefault(timeScale, + sampleDuration, + width, + height, + "hvc1"); + + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hvc1.width", width); + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hvc1.height", height); + + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hvc1.hvcC.general_profile_idc", + general_profile_idc); + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hvc1.hvcC.general_level_idc", + general_level_idc); + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hvc1.hvcC.lengthSizeMinusOne", + sampleLenFieldSizeMinusOne); + + return trackId; +} + +MP4TrackId MP4File::AddHEV1VideoTrack( + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne) +{ + MP4TrackId trackId = AddVideoTrackDefault(timeScale, + sampleDuration, + width, + height, + "hev1"); + + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hev1.width", width); + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hev1.height", height); + + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hev1.hvcC.general_profile_idc", + general_profile_idc); + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hev1.hvcC.general_level_idc", + general_level_idc); + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.hev1.hvcC.lengthSizeMinusOne", + sampleLenFieldSizeMinusOne); + + return trackId; +} + + void MP4File::AddH264SequenceParameterSet (MP4TrackId trackId, const uint8_t *pSequence, uint16_t sequenceLen) diff --git a/src/mp4file.h b/src/mp4file.h index a1a2eb4..ce8c18d 100644 --- a/src/mp4file.h +++ b/src/mp4file.h @@ -377,6 +377,26 @@ class MP4File void AddH264PictureParameterSet(MP4TrackId trackId, const uint8_t *pPicture, uint16_t pictureLen); + + // HEVC/H265 track creation + MP4TrackId AddHEVCVideoTrack( + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne); + + MP4TrackId AddHEV1VideoTrack( + uint32_t timeScale, + MP4Duration sampleDuration, + uint16_t width, + uint16_t height, + uint8_t general_profile_idc, + uint8_t general_level_idc, + uint8_t sampleLenFieldSizeMinusOne); + MP4TrackId AddHintTrack(MP4TrackId refTrackId); MP4TrackId AddTextTrack(MP4TrackId refTrackId);