Commit 0bc3a351 for libheif
commit 0bc3a3512854f74783529897d417b0ab821e28de
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Mon Sep 21 03:28:02 2026 +0200
Sequence encoders: extract pending output instead of moving out of the optional
Rename Encoder::encode_sequence_get_data() to encode_sequence_extract_data()
and let it hand out the pending CodedImageData with
std::exchange(m_current_output_data, std::nullopt).
Moving out of a std::optional leaves it engaged with a moved-from value, so
after the first frame the member was never disengaged again. "Nothing
pending" was represented two ways: a disengaged optional before the first
frame, and an engaged one with empty vectors afterwards, which the caller
had to detect with a separate bitstream/properties emptiness check. The
HEVC/VVC get_data() paths were written against the second form only, which
is what let the header-only first drain fixed in #1907 dereference the
disengaged optional.
With the reset, a disengaged optional is the only "no output pending"
state. HEVC and VVC drop the got_some_data flag and use that single check;
HEVC also drops the redundant bitstream.empty() test in front of the hvcC
attachment. AVIF no longer parks an empty CodedImageData when the encoder
is still buffering. AVC keeps its m_output_image_complete gate in front of
the exchange, because x264_encoder_headers() emits an SEI together with the
SPS/PPS and that SEI has to be held back until the slice data arrives. The
JPEG, JPEG 2000 and uncompressed encoders used has_value() as their
encode_sequence_started() answer and now track that with a bool.
Sequence output for HEVC, AVC, AVIF, VVC and JPEG is byte-identical to the
previous code, and the test suite passes with -D_GLIBCXX_ASSERTIONS.
diff --git a/libheif/codecs/avc_enc.cc b/libheif/codecs/avc_enc.cc
index 955ecdf2..cb900936 100644
--- a/libheif/codecs/avc_enc.cc
+++ b/libheif/codecs/avc_enc.cc
@@ -25,6 +25,7 @@
#include "api_structs.h"
#include <string>
+#include <utility>
#include "plugins/nalu_utils.h"
@@ -175,15 +176,16 @@ Error Encoder_AVC::encode_sequence_flush(heif_encoder* encoder)
}
-std::optional<Encoder::CodedImageData> Encoder_AVC::encode_sequence_get_data()
+std::optional<Encoder::CodedImageData> Encoder_AVC::encode_sequence_extract_data()
{
- if (m_output_image_complete) {
- m_output_image_complete = false;
- return std::move(m_current_output_data);
- }
- else {
+ // Non-VCL NALs can arrive ahead of their picture (x264 emits an SEI together with the
+ // SPS/PPS headers). They are held back until the slice data has been collected.
+ if (!m_output_image_complete) {
return std::nullopt;
}
+
+ m_output_image_complete = false;
+ return std::exchange(m_current_output_data, std::nullopt);
}
Error Encoder_AVC::get_data(heif_encoder* encoder)
diff --git a/libheif/codecs/avc_enc.h b/libheif/codecs/avc_enc.h
index c879be8e..08541fca 100644
--- a/libheif/codecs/avc_enc.h
+++ b/libheif/codecs/avc_enc.h
@@ -51,7 +51,7 @@ public:
Error encode_sequence_flush(heif_encoder* encoder) override;
- std::optional<CodedImageData> encode_sequence_get_data() override;
+ std::optional<CodedImageData> encode_sequence_extract_data() override;
std::shared_ptr<Box_VisualSampleEntry> get_sample_description_box(const CodedImageData&) const override;
@@ -59,7 +59,7 @@ private:
bool m_encoder_active = false;
bool m_end_of_sequence_reached = false;
- // Whether the hvcC is complete and was returned in an encode_sequence_get_data() call.
+ // Whether the avcC is complete and was returned in an encode_sequence_extract_data() call.
bool m_avcC_has_SPS = false;
bool m_avcC_has_PPS = false;
std::shared_ptr<class Box_avcC> m_avcC;
diff --git a/libheif/codecs/avif_enc.cc b/libheif/codecs/avif_enc.cc
index dba271cb..e6352d6a 100644
--- a/libheif/codecs/avif_enc.cc
+++ b/libheif/codecs/avif_enc.cc
@@ -25,6 +25,7 @@
#include "api_structs.h"
#include <string>
+#include <utility>
enum heif_av1_obu_type : uint8_t
{
@@ -183,6 +184,11 @@ Error Encoder_AVIF::get_data(heif_encoder* encoder)
codedImage.codingConstraints.intra_pred_used = true;
codedImage.codingConstraints.all_ref_pics_intra = m_all_refs_intra;
+ if (codedImage.bitstream.empty() && codedImage.properties.empty()) {
+ // The encoder is still buffering frames and returned nothing. Leave no output pending.
+ return {};
+ }
+
m_current_output_data = std::move(codedImage);
return {};
@@ -199,9 +205,9 @@ Error Encoder_AVIF::encode_sequence_flush(heif_encoder* encoder)
}
-std::optional<Encoder::CodedImageData> Encoder_AVIF::encode_sequence_get_data()
+std::optional<Encoder::CodedImageData> Encoder_AVIF::encode_sequence_extract_data()
{
- return std::move(m_current_output_data);
+ return std::exchange(m_current_output_data, std::nullopt);
}
diff --git a/libheif/codecs/avif_enc.h b/libheif/codecs/avif_enc.h
index f704a4f8..a0754e59 100644
--- a/libheif/codecs/avif_enc.h
+++ b/libheif/codecs/avif_enc.h
@@ -53,7 +53,7 @@ public:
Error encode_sequence_flush(heif_encoder* encoder) override;
- std::optional<CodedImageData> encode_sequence_get_data() override;
+ std::optional<CodedImageData> encode_sequence_extract_data() override;
std::shared_ptr<Box_VisualSampleEntry> get_sample_description_box(const CodedImageData&) const override;
diff --git a/libheif/codecs/encoder.h b/libheif/codecs/encoder.h
index a10d7ed7..75203046 100644
--- a/libheif/codecs/encoder.h
+++ b/libheif/codecs/encoder.h
@@ -94,7 +94,11 @@ public:
virtual Error encode_sequence_flush(heif_encoder* encoder) { return {}; }
- virtual std::optional<CodedImageData> encode_sequence_get_data() { return std::nullopt; }
+ // Hands out the coded data collected since the previous call and leaves nothing pending
+ // in the encoder, so that the next call returns std::nullopt until new data has arrived.
+ // The result may carry only properties (e.g. the codec configuration box that some
+ // encoders attach at the end of the sequence) and an empty bitstream.
+ virtual std::optional<CodedImageData> encode_sequence_extract_data() { return std::nullopt; }
virtual std::shared_ptr<Box_VisualSampleEntry> get_sample_description_box(const CodedImageData&) const { return {}; }
};
diff --git a/libheif/codecs/hevc_enc.cc b/libheif/codecs/hevc_enc.cc
index 20186582..75b6d932 100644
--- a/libheif/codecs/hevc_enc.cc
+++ b/libheif/codecs/hevc_enc.cc
@@ -25,6 +25,7 @@
#include "api_structs.h"
#include <string>
+#include <utility>
#include "plugins/nalu_utils.h"
@@ -170,17 +171,15 @@ Error Encoder_HEVC::encode_sequence_flush(heif_encoder* encoder)
}
-std::optional<Encoder::CodedImageData> Encoder_HEVC::encode_sequence_get_data()
+std::optional<Encoder::CodedImageData> Encoder_HEVC::encode_sequence_extract_data()
{
- return std::move(m_current_output_data);
+ return std::exchange(m_current_output_data, std::nullopt);
}
Error Encoder_HEVC::get_data(heif_encoder* encoder)
{
//CodedImageData codedImage;
- bool got_some_data = false;
-
for (;;) {
uint8_t* data;
int size;
@@ -196,8 +195,6 @@ Error Encoder_HEVC::get_data(heif_encoder* encoder)
break;
}
- got_some_data = true;
-
const uint8_t nal_type = (data[0] >> 1);
const bool is_sync = (nal_type == 19 || nal_type == 20 || nal_type == 21);
const bool is_image_data = (nal_type >= 0 && nal_type <= HEVC_NAL_UNIT_MAX_VCL);
@@ -243,17 +240,10 @@ Error Encoder_HEVC::get_data(heif_encoder* encoder)
}
}
- if (!got_some_data) {
- return {};
- }
-
- // The encoder can hand out parameter-set NALs before any slice data. x265,
- // for example, emits VPS/SPS/PPS from encoder_headers() as soon as the
- // sequence encoder is opened, so the first get_data() after
- // start_sequence_encoding() sees only headers. Those are collected into
- // m_hvcC and leave m_current_output_data unset, so there is no coded
- // image to report yet. Without this check the dereferences below run on a
- // disengaged std::optional.
+ // No coded image to report when the encoder returned no NALs, or only parameter sets.
+ // x265, for example, emits VPS/SPS/PPS from encoder_headers() as soon as the sequence
+ // encoder is opened, so the first get_data() after start_sequence_encoding() sees only
+ // headers. Those went into m_hvcC above and m_current_output_data stays disengaged.
if (!m_current_output_data) {
return {};
}
@@ -268,8 +258,7 @@ Error Encoder_HEVC::get_data(heif_encoder* encoder)
// TODO: it's maybe better to return this at the end so that we are sure to have all headers
// and also complete codingConstraints.
- if (!m_current_output_data->bitstream.empty() &&
- m_hvcC_has_VPS && m_hvcC_has_SPS && m_hvcC_has_PPS && !m_hvcC_sent) {
+ if (m_hvcC_has_VPS && m_hvcC_has_SPS && m_hvcC_has_PPS && !m_hvcC_sent) {
//if (/*m_end_of_sequence_reached &&*/ m_hvcC && !m_hvcC_sent) {
m_current_output_data->properties.push_back(m_hvcC);
m_hvcC = nullptr;
diff --git a/libheif/codecs/hevc_enc.h b/libheif/codecs/hevc_enc.h
index dc57fcc9..cc9e5ac0 100644
--- a/libheif/codecs/hevc_enc.h
+++ b/libheif/codecs/hevc_enc.h
@@ -51,7 +51,7 @@ public:
Error encode_sequence_flush(heif_encoder* encoder) override;
- std::optional<CodedImageData> encode_sequence_get_data() override;
+ std::optional<CodedImageData> encode_sequence_extract_data() override;
std::shared_ptr<Box_VisualSampleEntry> get_sample_description_box(const CodedImageData&) const override;
@@ -59,7 +59,7 @@ private:
bool m_encoder_active = false;
bool m_end_of_sequence_reached = false;
- // Whether the hvcC is complete and was returned in an encode_sequence_get_data() call.
+ // Whether the hvcC is complete and was returned in an encode_sequence_extract_data() call.
bool m_hvcC_has_VPS = false;
bool m_hvcC_has_SPS = false;
bool m_hvcC_has_PPS = false;
diff --git a/libheif/codecs/jpeg2000_enc.h b/libheif/codecs/jpeg2000_enc.h
index a535bab7..ee899da2 100644
--- a/libheif/codecs/jpeg2000_enc.h
+++ b/libheif/codecs/jpeg2000_enc.h
@@ -42,7 +42,7 @@ public:
std::shared_ptr<Box_VisualSampleEntry> get_sample_description_box(const CodedImageData&) const override;
- bool encode_sequence_started() const override { return m_codedImageData.has_value(); }
+ bool encode_sequence_started() const override { return m_sequence_started; }
Error encode_sequence_frame(const std::shared_ptr<HeifPixelImage>& image,
heif_encoder* encoder,
@@ -62,6 +62,7 @@ public:
m_codedImageData->frame_nr = frame_number;
m_codedImageData->is_sync_frame = true;
+ m_sequence_started = true;
return {};
}
@@ -71,13 +72,14 @@ public:
return {};
}
- std::optional<CodedImageData> encode_sequence_get_data() override
+ std::optional<CodedImageData> encode_sequence_extract_data() override
{
- return std::move(m_codedImageData);
+ return std::exchange(m_codedImageData, std::nullopt);
}
private:
std::optional<CodedImageData> m_codedImageData;
+ bool m_sequence_started = false;
};
diff --git a/libheif/codecs/jpeg_enc.h b/libheif/codecs/jpeg_enc.h
index ed8b8cdd..c9f90f7f 100644
--- a/libheif/codecs/jpeg_enc.h
+++ b/libheif/codecs/jpeg_enc.h
@@ -45,7 +45,7 @@ public:
std::shared_ptr<Box_VisualSampleEntry> get_sample_description_box(const CodedImageData&) const override;
- bool encode_sequence_started() const override { return m_codedImageData.has_value(); }
+ bool encode_sequence_started() const override { return m_sequence_started; }
Error encode_sequence_frame(const std::shared_ptr<HeifPixelImage>& image,
heif_encoder* encoder,
@@ -65,6 +65,7 @@ public:
m_codedImageData->frame_nr = frame_number;
m_codedImageData->is_sync_frame = true;
+ m_sequence_started = true;
return {};
}
@@ -74,13 +75,14 @@ public:
return {};
}
- std::optional<CodedImageData> encode_sequence_get_data() override
+ std::optional<CodedImageData> encode_sequence_extract_data() override
{
- return std::move(m_codedImageData);
+ return std::exchange(m_codedImageData, std::nullopt);
}
private:
std::optional<CodedImageData> m_codedImageData;
+ bool m_sequence_started = false;
};
diff --git a/libheif/codecs/uncompressed/unc_enc.h b/libheif/codecs/uncompressed/unc_enc.h
index 01d7eeeb..25bb3f46 100644
--- a/libheif/codecs/uncompressed/unc_enc.h
+++ b/libheif/codecs/uncompressed/unc_enc.h
@@ -42,7 +42,7 @@ public:
std::shared_ptr<class Box_VisualSampleEntry> get_sample_description_box(const CodedImageData&) const override;
- bool encode_sequence_started() const override { return m_codedImageData.has_value(); }
+ bool encode_sequence_started() const override { return m_sequence_started; }
Error encode_sequence_frame(const std::shared_ptr<HeifPixelImage>& image,
heif_encoder* encoder,
@@ -64,6 +64,7 @@ public:
m_codedImageData->frame_nr = frame_number;
m_codedImageData->is_sync_frame = true;
+ m_sequence_started = true;
return {};
}
@@ -73,13 +74,14 @@ public:
return {};
}
- std::optional<CodedImageData> encode_sequence_get_data() override
+ std::optional<CodedImageData> encode_sequence_extract_data() override
{
- return std::move(m_codedImageData);
+ return std::exchange(m_codedImageData, std::nullopt);
}
private:
std::optional<CodedImageData> m_codedImageData;
+ bool m_sequence_started = false;
};
diff --git a/libheif/codecs/vvc_enc.cc b/libheif/codecs/vvc_enc.cc
index df4ae0f3..0bd3350e 100644
--- a/libheif/codecs/vvc_enc.cc
+++ b/libheif/codecs/vvc_enc.cc
@@ -25,6 +25,7 @@
#include "api_structs.h"
#include <string>
+#include <utility>
#include "plugins/nalu_utils.h"
@@ -175,17 +176,15 @@ Error Encoder_VVC::encode_sequence_flush(heif_encoder* encoder)
}
-std::optional<Encoder::CodedImageData> Encoder_VVC::encode_sequence_get_data()
+std::optional<Encoder::CodedImageData> Encoder_VVC::encode_sequence_extract_data()
{
- return std::move(m_current_output_data);
+ return std::exchange(m_current_output_data, std::nullopt);
}
Error Encoder_VVC::get_data(heif_encoder* encoder)
{
//CodedImageData codedImage;
- bool got_some_data = false;
-
for (;;) {
uint8_t* data;
int size;
@@ -201,8 +200,6 @@ Error Encoder_VVC::get_data(heif_encoder* encoder)
break;
}
- got_some_data = true;
-
const uint8_t nal_type = (data[1] >> 3);
const bool is_sync = (nal_type == 7 || nal_type == 8 || nal_type == 9);
const bool is_image_data = (nal_type >= 0 && nal_type <= VVC_NAL_UNIT_MAX_VCL);
@@ -249,17 +246,9 @@ Error Encoder_VVC::get_data(heif_encoder* encoder)
}
}
- if (!got_some_data) {
- return {};
- }
-
- // The encoder can hand out parameter-set NALs before any slice data. x265,
- // for example, emits VPS/SPS/PPS from encoder_headers() as soon as the
- // sequence encoder is opened, so the first get_data() after
- // start_sequence_encoding() sees only headers. Those are collected into
- // m_vvcC and leave m_current_output_data unset, so there is no coded
- // image to report yet. Without this check the dereferences below run on a
- // disengaged std::optional.
+ // No coded image to report when the encoder returned no NALs, or only parameter sets
+ // (an encoder may emit VPS/SPS/PPS as soon as the sequence encoder is opened, before any
+ // slice data). Those went into m_vvcC above and m_current_output_data stays disengaged.
if (!m_current_output_data) {
return {};
}
diff --git a/libheif/codecs/vvc_enc.h b/libheif/codecs/vvc_enc.h
index b629816d..a6630e27 100644
--- a/libheif/codecs/vvc_enc.h
+++ b/libheif/codecs/vvc_enc.h
@@ -54,13 +54,13 @@ public:
Error encode_sequence_flush(heif_encoder* encoder) override;
- std::optional<CodedImageData> encode_sequence_get_data() override;
+ std::optional<CodedImageData> encode_sequence_extract_data() override;
private:
bool m_encoder_active = false;
bool m_end_of_sequence_reached = false;
- // Whether the vvcC is complete and was returned in an encode_sequence_get_data() call.
+ // Whether the vvcC is complete and was returned in an encode_sequence_extract_data() call.
bool m_vvcC_has_VPS = false;
bool m_vvcC_has_SPS = false;
bool m_vvcC_has_PPS = false;
diff --git a/libheif/sequences/track_visual.cc b/libheif/sequences/track_visual.cc
index deb7cdb1..92add576 100644
--- a/libheif/sequences/track_visual.cc
+++ b/libheif/sequences/track_visual.cc
@@ -651,9 +651,10 @@ Result<bool> Track_Visual::process_encoded_data(heif_encoder* h_encoder)
{
auto encoder = m_chunks.back()->get_encoder();
- std::optional<Encoder::CodedImageData> encodingResult = encoder->encode_sequence_get_data();
+ std::optional<Encoder::CodedImageData> encodingResult = encoder->encode_sequence_extract_data();
if (!encodingResult) {
- return {};
+ // nothing pending in the encoder
+ return {false};
}
const Encoder::CodedImageData& data = *encodingResult;