diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt index 93c65be39f..40e40bd42c 100644 --- a/libheif/CMakeLists.txt +++ b/libheif/CMakeLists.txt @@ -73,6 +73,8 @@ set(libheif_sources jpeg2000.cc vvc.h vvc.cc + avc.h + avc.cc ${libheif_headers}) add_library(heif ${libheif_sources}) diff --git a/libheif/avc.cc b/libheif/avc.cc new file mode 100644 index 0000000000..47739c8e13 --- /dev/null +++ b/libheif/avc.cc @@ -0,0 +1,145 @@ +/* + * HEIF AVC codec. + * Copyright (c) 2023 Brad Hards + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#include "avc.h" +#include +#include +#include +#include +#include + +Error Box_avcC::parse(BitstreamRange &range) { + m_configuration.configuration_version = range.read8(); + m_configuration.AVCProfileIndication = range.read8(); + m_configuration.profile_compatibility = range.read8(); + m_configuration.AVCLevelIndication = range.read8(); + uint8_t lengthSizeMinusOneWithReserved = range.read8(); + m_configuration.lengthSize = + (lengthSizeMinusOneWithReserved & 0b00000011) + 1; + + uint8_t numOfSequenceParameterSets = (range.read8() & 0b00011111); + for (int i = 0; i < numOfSequenceParameterSets; i++) { + uint16_t sequenceParameterSetLength = range.read16(); + std::vector sps(sequenceParameterSetLength); + range.read(sps.data(), sps.size()); + m_sps.push_back(sps); + } + + uint8_t numOfPictureParameterSets = range.read8(); + for (int i = 0; i < numOfPictureParameterSets; i++) { + uint16_t pictureParameterSetLength = range.read16(); + std::vector pps(pictureParameterSetLength); + range.read(pps.data(), pps.size()); + m_pps.push_back(pps); + } + + if ((m_configuration.AVCProfileIndication != 66) && + (m_configuration.AVCProfileIndication != 77) && + (m_configuration.AVCProfileIndication != 88)) { + // TODO: we don't support this yet + } + + return range.get_error(); +} + +Error Box_avcC::write(StreamWriter &writer) const { + size_t box_start = reserve_box_header_space(writer); + + writer.write8(m_configuration.configuration_version); + writer.write8(m_configuration.AVCProfileIndication); + writer.write8(m_configuration.profile_compatibility); + writer.write8(m_configuration.AVCLevelIndication); + uint8_t lengthSizeMinusOneWithReserved = 0b11111100 | ((m_configuration.lengthSize - 1) & 0b11); + writer.write8(lengthSizeMinusOneWithReserved); + uint8_t numSpsWithReserved = 0b11100000 | (m_sps.size() & 0b00011111); + writer.write8(numSpsWithReserved); + for (const auto &sps: m_sps) { + writer.write16((uint16_t) sps.size()); + writer.write(sps); + } + writer.write8(m_pps.size() & 0xFF); + for (const auto &pps: m_pps) { + writer.write16((uint16_t) pps.size()); + writer.write(pps); + } + prepend_header(writer, box_start); + + return Error::Ok; +} + +std::string Box_avcC::dump(Indent &indent) const { + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "configuration_version: " + << ((int)m_configuration.configuration_version) << "\n" + << indent << "AVCProfileIndication: " + << ((int)m_configuration.AVCProfileIndication) << " (" + << profileIndicationAsText() << ")" + << "\n" + << indent << "profile_compatibility: " + << ((int)m_configuration.profile_compatibility) << "\n" + << indent + << "AVCLevelIndication: " << ((int)m_configuration.AVCLevelIndication) + << "\n"; + + for (const auto &sps : m_sps) { + sstr << indent << "SPS: "; + for (uint8_t b : sps) { + sstr << std::setfill('0') << std::setw(2) << std::hex << ((int)b) << " "; + } + sstr << "\n"; + sstr << std::dec; + } + + for (const auto &pps : m_pps) { + sstr << indent << "PPS: "; + for (uint8_t b : pps) { + sstr << std::setfill('0') << std::setw(2) << std::hex << ((int)b) << " "; + } + sstr << "\n"; + sstr << std::dec; + } + + return sstr.str(); +} + +std::string Box_avcC::profileIndicationAsText() const { + // See ISO/IEC 14496-10:2022 Annex A + switch (m_configuration.AVCProfileIndication) { + case 44: + return "CALVC 4:4:4"; + case 66: + return "Constrained Baseline"; + case 77: + return "Main"; + case 88: + return "Extended"; + case 100: + return "High variant"; + case 110: + return "High 10"; + case 122: + return "High 4:2:2"; + case 244: + return "High 4:4:4"; + default: + return "Unknown"; + } +} diff --git a/libheif/avc.h b/libheif/avc.h new file mode 100644 index 0000000000..595126442d --- /dev/null +++ b/libheif/avc.h @@ -0,0 +1,77 @@ +/* + * HEIF AVC codec. + * Copyright (c) 2023 Brad Hards + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef HEIF_AVC_H +#define HEIF_AVC_H + +#include "box.h" +#include "error.h" +#include +#include + +class Box_avcC : public Box { +public: + Box_avcC() { set_short_type(fourcc("avcC")); } + + struct configuration { + uint8_t configuration_version; + uint8_t AVCProfileIndication; // profile_idc + uint8_t profile_compatibility; // constraint set flags + uint8_t AVCLevelIndication; // level_idc + uint8_t lengthSize; + }; + + void set_configuration(const configuration& config) + { + m_configuration = config; + } + + const configuration& get_configuration() const + { + return m_configuration; + } + + const std::vector< std::vector > getSequenceParameterSets() const + { + return m_sps; + } + + const std::vector< std::vector > getPictureParameterSets() const + { + return m_pps; + } + + + std::string dump(Indent &) const override; + + Error write(StreamWriter &writer) const override; + +protected: + Error parse(BitstreamRange &range) override; + + std::string profileIndicationAsText() const; + +private: + configuration m_configuration; + std::vector< std::vector > m_sps; + std::vector< std::vector > m_pps; +}; + +#endif diff --git a/libheif/box.cc b/libheif/box.cc index e752ac4807..d0dda4a7ee 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -28,6 +28,7 @@ #include "hevc.h" #include "mask_image.h" #include "vvc.h" +#include "avc.h" #include #include @@ -610,6 +611,12 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) box = std::make_shared(); break; + // --- AVC (H.264) + + case fourcc("avcC"): + box = std::make_shared(); + break; + default: box = std::make_shared(); break; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fd9d115a89..dfd9de207d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,10 +16,11 @@ endmacro() # --- tests that require access to internal symbols if (WITH_REDUCED_VISIBILITY) - message(WARNING "Conversion and JPEG 2000 box unit tests can only be compiled with full symbol visibility (WITH_REDUCED_VISIBILITY=OFF)") + message(WARNING "Conversion, JPEG 2000 and AVC box unit tests can only be compiled with full symbol visibility (WITH_REDUCED_VISIBILITY=OFF)") else() add_libheif_test(conversion) add_libheif_test(jpeg2000) + add_libheif_test(avc_box) endif() # --- tests that only access the public API diff --git a/tests/avc_box.cc b/tests/avc_box.cc new file mode 100644 index 0000000000..c9165de8d0 --- /dev/null +++ b/tests/avc_box.cc @@ -0,0 +1,80 @@ +/* + libheif AVC (H.264) unit tests + + MIT License + + Copyright (c) 2023 Brad Hards + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "catch.hpp" +#include "libheif/avc.h" +#include "libheif/error.h" +#include +#include +#include + +TEST_CASE("avcC") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x34, 0x61, 0x76, 0x63, 0x43, 0x01, 0x42, 0x80, + 0x1e, 0xff, 0xe1, 0x00, 0x1a, 0x67, 0x64, 0x00, 0x28, 0xac, 0x72, + 0x04, 0x40, 0x40, 0x04, 0x1a, 0x10, 0x00, 0x00, 0x03, 0x00, 0x10, + 0x00, 0x00, 0x03, 0x03, 0x20, 0xf1, 0x83, 0x18, 0x46, 0x01, 0x00, + 0x07, 0x68, 0xe8, 0x43, 0x83, 0x92, 0xc8, 0xb0}; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("avcC")); + REQUIRE(box->get_type_string() == "avcC"); + std::shared_ptr avcC = std::dynamic_pointer_cast(box); + Box_avcC::configuration configuration = avcC->get_configuration(); + REQUIRE(configuration.configuration_version == 1); + REQUIRE(configuration.AVCProfileIndication == 66); + REQUIRE(configuration.profile_compatibility == 0x80); + REQUIRE(configuration.AVCLevelIndication == 30); + REQUIRE(avcC->getSequenceParameterSets().size() == 1); + REQUIRE(avcC->getSequenceParameterSets()[0].size() == 0x1a); + REQUIRE(avcC->getPictureParameterSets().size() == 1); + REQUIRE(avcC->getPictureParameterSets()[0].size() == 7); + Indent indent; + std::string dumpResult = box->dump(indent); + REQUIRE(dumpResult == "Box: avcC -----\n" + "size: 52 (header size: 8)\n" + "configuration_version: 1\n" + "AVCProfileIndication: 66 (Constrained Baseline)\n" + "profile_compatibility: 128\n" + "AVCLevelIndication: 30\n" + "SPS: 67 64 00 28 ac 72 04 40 40 04 1a 10 00 00 03 00 " + "10 00 00 03 03 20 f1 83 18 46 \n" + "PPS: 68 e8 43 83 92 c8 b0 \n"); + + StreamWriter writer; + Error err = avcC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector bytes = writer.get_data(); + REQUIRE(bytes == byteArray); +}