Commit fa385774 for libheif

commit fa38577416b743346c315d957becc90b269de2ee
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Wed Apr 8 13:53:32 2026 +0200

    svt: resolve image sequence encoding with v4.x and resolve low-delay encoding (#1680)

diff --git a/libheif/plugins/encoder_svt.cc b/libheif/plugins/encoder_svt.cc
index 3e614908..2c9b7a00 100644
--- a/libheif/plugins/encoder_svt.cc
+++ b/libheif/plugins/encoder_svt.cc
@@ -86,6 +86,7 @@ struct encoder_struct_svt
   EbBufferHeaderType input_buffer;

   bool still_image_mode = false;
+  bool low_delay_mode = false;

   // --- output

@@ -835,16 +836,17 @@ static heif_error svt_start_sequence_encoding_intern(void* encoder_raw, const he
   svt_config.enc_mode = (int8_t) encoder->speed;

   if (image_sequence) {
-    // svt_config.force_key_frames = true; TODO: this does not seem to work (see all [1])
+    // svt_config.force_key_frames = true; TODO: this does not seem to work (see all [1]), tested with SVT 3.0.1

 #if 1
 #if SVT_AV1_CHECK_VERSION(4, 1, 0)
     switch (options->gop_structure) {
       case heif_sequence_gop_structure_intra_only:
-        //svt_config.pred_structure = 1; // LOW_DELAY
+        svt_config.pred_structure = PredStructure::ALL_INTRA;
         break;
       case heif_sequence_gop_structure_lowdelay:
-        //svt_config.pred_structure = 1; // LOW_DELAY
+        svt_config.pred_structure = PredStructure::LOW_DELAY;
+        encoder->low_delay_mode = true;
         break;
       case heif_sequence_gop_structure_unrestricted:
         svt_config.pred_structure = PredStructure::RANDOM_ACCESS;
@@ -853,10 +855,9 @@ static heif_error svt_start_sequence_encoding_intern(void* encoder_raw, const he
 #elif SVT_AV1_CHECK_VERSION(4, 0, 0)
     switch (options->gop_structure) {
       case heif_sequence_gop_structure_intra_only:
-        //svt_config.pred_structure = 1; // LOW_DELAY
-        break;
       case heif_sequence_gop_structure_lowdelay:
-        //svt_config.pred_structure = 1; // LOW_DELAY
+        svt_config.pred_structure = 1; // LOW_DELAY
+        encoder->low_delay_mode = true;
         break;
       case heif_sequence_gop_structure_unrestricted:
         svt_config.pred_structure = 2; // RANDOM_ACCESS
@@ -866,10 +867,9 @@ static heif_error svt_start_sequence_encoding_intern(void* encoder_raw, const he
     // TODO: setting pref_structure to SVT_AV1_PRED_LOW_DELAY_B hangs the encoder
     switch (options->gop_structure) {
       case heif_sequence_gop_structure_intra_only:
-        //svt_config.pred_structure = SvtAv1PredStructure::SVT_AV1_PRED_LOW_DELAY_B;
-        break;
       case heif_sequence_gop_structure_lowdelay:
-        //svt_config.pred_structure = SvtAv1PredStructure::SVT_AV1_PRED_LOW_DELAY_B;
+        svt_config.pred_structure = SvtAv1PredStructure::SVT_AV1_PRED_LOW_DELAY_B;
+        encoder->low_delay_mode = true;
         break;
       case heif_sequence_gop_structure_unrestricted:
         svt_config.pred_structure = SvtAv1PredStructure::SVT_AV1_PRED_RANDOM_ACCESS;
@@ -878,7 +878,7 @@ static heif_error svt_start_sequence_encoding_intern(void* encoder_raw, const he
 #endif

     if (options->keyframe_distance_max) {
-      svt_config.intra_period_length = options->keyframe_distance_max; // TODO -1 ?
+      svt_config.intra_period_length = options->keyframe_distance_max - 1;
     }
 #endif
   }
@@ -943,17 +943,15 @@ static heif_error read_encoder_output_packets(void* encoder_raw, bool done_sendi

         memcpy(encoder->output_packets.back().compressedData.data(),
                data, n);
-
-        /* TODO: this is a hack
-         * When using
-         *           svt_config.pred_structure = 1; // LOW_DELAY
-         * svt_av1_enc_get_packet() hangs on the second call. As a workaround, we can leave the
-         * loop when we got the image:
-         */
-        if (output_packet.is_keyframe)
-          break;
       }
       svt_av1_enc_release_out_buffer(&output_buf);
+
+      // In LOW_DELAY mode, svt_av1_enc_get_packet() is always a blocking call
+      // (enforcing a "picture in, picture out" model). We must exit after
+      // retrieving one packet per frame, otherwise the next call blocks forever.
+      if (encoder->low_delay_mode && !done_sending_pics) {
+        break;
+      }
     }
   } while (res == EB_ErrorNone && !encode_at_eos);

@@ -1109,9 +1107,8 @@ static heif_error svt_encode_sequence_frame(void* encoder_raw, const heif_image*
   input_buffer.flags = 0;
   // input_buffer.pts = 0;

-  EbAv1PictureType frame_type = EB_AV1_KEY_PICTURE;
-
-  input_buffer.pic_type = frame_type;
+  //EbAv1PictureType frame_type = EB_AV1_KEY_PICTURE;
+  //input_buffer.pic_type = frame_type;

   res = svt_av1_enc_send_picture(svt_encoder, &input_buffer);
   if (res != EB_ErrorNone) {
@@ -1127,8 +1124,15 @@ static heif_error svt_end_sequence_encoding(void* encoder_raw)
 {
   auto* encoder = (encoder_struct_svt*) encoder_raw;

+  // In LOW_DELAY mode, all packets have already been retrieved during per-frame
+  // encoding (picture-in, picture-out model). No flush is needed, and sending an
+  // EOS buffer would deadlock because SVT-AV1's LOW_DELAY path does not release
+  // the internal resources allocated for the EOS picture.
+  if (encoder->low_delay_mode) {
+    return heif_error_ok;
+  }
+
   EbComponentType*& svt_encoder = encoder->svt_encoder;
-  //EbBufferHeaderType& input_buffer = encoder->input_buffer;

   // --- flush encoder