Commit 41bad534 for libheif
commit 41bad534c8d316c08507fcb26480d1c76223c802
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Tue Sep 29 21:23:05 2026 +0200
Write the constraint flags of the HEVC profile into hvcC
The 48 constraint flags in the 'hvcC' box of an encoded image were always
zero, for two reasons. parse_sps_for_hvcC_configuration() skipped them in the
SPS, and HEVCDecoderConfigurationRecord::write() shifted each byte once too
often, which dropped the first flag of the byte and moved the others.
ISO/IEC 14496-15 demands that the values in 'hvcC' match those of the
parameter sets. The flags carry general_progressive_source_flag and
general_frame_only_constraint_flag, and within the range extensions and the
screen content coding extensions they are what tells the profiles apart.
This changes the 'hvcC' box that libheif writes for all HEVC encoders.
diff --git a/libheif/codecs/hevc_boxes.cc b/libheif/codecs/hevc_boxes.cc
index 1658ac15..848b6188 100644
--- a/libheif/codecs/hevc_boxes.cc
+++ b/libheif/codecs/hevc_boxes.cc
@@ -123,11 +123,11 @@ Error HEVCDecoderConfigurationRecord::write(StreamWriter& writer) const
uint8_t byte = 0;
for (int b = 0; b < 8; b++) {
+ byte = (uint8_t) (byte << 1);
+
if (general_constraint_indicator_flags[i * 8 + b]) {
byte |= 1;
}
-
- byte = (uint8_t) (byte << 1);
}
writer.write8(byte);
@@ -625,9 +625,11 @@ Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size,
config->general_profile_idc = reader.get_bits8(5);
config->general_profile_compatibility_flags = reader.get_bits32(32);
- reader.skip_bits(16); // skip reserved bits
- reader.skip_bits(16); // skip reserved bits
- reader.skip_bits(16); // skip reserved bits
+ // general_progressive_source_flag up to the last of the constraint flags. Within the range
+ // extensions and the screen content coding extensions, they tell the profiles apart.
+ for (int i = 0; i < HEVCDecoderConfigurationRecord::NUM_CONSTRAINT_INDICATOR_FLAGS; i++) {
+ config->general_constraint_indicator_flags[i] = (reader.get_bits(1) != 0);
+ }
config->general_level_idc = reader.get_bits8(8);
diff --git a/tests/hevc_sps.cc b/tests/hevc_sps.cc
index c208e745..20b6d74a 100644
--- a/tests/hevc_sps.cc
+++ b/tests/hevc_sps.cc
@@ -27,8 +27,11 @@
#include "catch_amalgamated.hpp"
#include "codecs/hevc_boxes.h"
#include "codecs/decoder.h"
+#include "bitstream.h"
#include "error.h"
+#include "libheif/heif.h"
#include <cstdint>
+#include <memory>
#include <vector>
// SPS NAL unit (with emulation prevention bytes) taken from the hvcC box of
@@ -62,6 +65,56 @@ TEST_CASE("SPS conformance window yields visible and coded size")
}
+TEST_CASE("SPS constraint flags are copied to hvcC")
+{
+ HEVCDecoderConfigurationRecord config{};
+ uint32_t width = 0, height = 0;
+ ImageSize coded{};
+
+ Error err = parse_sps_for_hvcC_configuration(rainbow_sps.data(), rainbow_sps.size(),
+ &config, &width, &height, &coded);
+ REQUIRE(!err);
+
+ // The SPS has general_progressive_source_flag and general_frame_only_constraint_flag
+ // set: the constraint flags are 0x90 and five zero bytes.
+
+ CHECK(config.general_profile_idc == HEVCDecoderConfigurationRecord::Profile_MainStillPicture);
+
+ for (int i = 0; i < HEVCDecoderConfigurationRecord::NUM_CONSTRAINT_INDICATOR_FLAGS; i++) {
+ INFO("constraint flag " << i);
+ CHECK(config.general_constraint_indicator_flags[i] == (i == 0 || i == 3));
+ }
+
+ // They are written in the same order, the first flag in the most significant bit.
+
+ config.configuration_version = 1;
+
+ // a flag in each of the bytes, among them the first and the last bit of a byte
+ config.general_constraint_indicator_flags[8] = true;
+ config.general_constraint_indicator_flags[23] = true;
+ config.general_constraint_indicator_flags[47] = true;
+
+ StreamWriter writer;
+ err = config.write(writer);
+ REQUIRE(!err);
+
+ const std::vector<uint8_t> data = writer.get_data();
+ REQUIRE(data.size() >= 12);
+
+ const std::vector<uint8_t> constraint_flags(data.begin() + 6, data.begin() + 12);
+ CHECK(constraint_flags == std::vector<uint8_t>{0x90, 0x80, 0x01, 0x00, 0x00, 0x01});
+
+ // ... and read back into the same flags.
+
+ BitstreamRange range(std::make_shared<StreamReader_memory>(data.data(), data.size(), false), data.size());
+
+ HEVCDecoderConfigurationRecord parsed{};
+ err = parsed.parse(range, heif_get_global_security_limits());
+ REQUIRE(!err);
+ CHECK(parsed.general_constraint_indicator_flags == config.general_constraint_indicator_flags);
+}
+
+
TEST_CASE("SPS chroma_format_idc out of range is rejected")
{
// Same SPS with chroma_format_idc changed from 1 (uvlc '010') to 4