diff mbox series

[FFmpeg-devel] avcodec/libsvtav1: Add support for multipass encoding

Message ID 20220927210605.692742-1-gustav.grusell@gmail.com
State New
Headers show
Series [FFmpeg-devel] avcodec/libsvtav1: Add support for multipass encoding | expand

Checks

Context Check Description
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Gustav Grusell Sept. 27, 2022, 9:06 p.m. UTC
Implements support for 2-pass CRF and 3-pass VBR by implementing
reading and writing of stats file, and passing the pass number on
to the encoder. For 3-pass VBR, the first pass should be run with
'-pass 1', the second with '-pass 3' and the third with '-pass 2'.

Signed-off-by: Gustav Grusell <gustav.grusell@gmail.com>
---
 libavcodec/libsvtav1.c | 82 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

Comments

Gustav Grusell Oct. 6, 2022, 6:03 a.m. UTC | #1
Any chance of someone finding the time to have a look at this?

/Gustav

On Tue, Sep 27, 2022 at 11:06 PM Gustav Grusell <gustav.grusell@gmail.com>
wrote:

> Implements support for 2-pass CRF and 3-pass VBR by implementing
> reading and writing of stats file, and passing the pass number on
> to the encoder. For 3-pass VBR, the first pass should be run with
> '-pass 1', the second with '-pass 3' and the third with '-pass 2'.
>
> Signed-off-by: Gustav Grusell <gustav.grusell@gmail.com>
> ---
>  libavcodec/libsvtav1.c | 82 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 82 insertions(+)
>
> diff --git a/libavcodec/libsvtav1.c b/libavcodec/libsvtav1.c
> index 2f5634cee0..73fece49b7 100644
> --- a/libavcodec/libsvtav1.c
> +++ b/libavcodec/libsvtav1.c
> @@ -24,6 +24,7 @@
>  #include <EbSvtAv1ErrorCodes.h>
>  #include <EbSvtAv1Enc.h>
>
> +#include "libavutil/base64.h"
>  #include "libavutil/common.h"
>  #include "libavutil/frame.h"
>  #include "libavutil/imgutils.h"
> @@ -312,6 +313,22 @@ static int config_enc_params(EbSvtAv1EncConfiguration
> *param,
>          cpb_props->avg_bitrate = avctx->bit_rate;
>      }
>
> +    if (avctx->flags & AV_CODEC_FLAG_PASS2) {
> +        if (param->rate_control_mode == SVT_AV1_RC_MODE_VBR) {
> +            if (avctx->flags & AV_CODEC_FLAG_PASS1) {
> +                param->pass = 2;
> +            } else {
> +                param->pass = 3;
> +            }
> +        } else {
> +            param->pass = 2;
> +        }
> +    } else if (avctx->flags & AV_CODEC_FLAG_PASS1) {
> +        param->pass = 1;
> +    } else {
> +        param->pass = 0;
> +    }
> +
>      return 0;
>  }
>
> @@ -371,6 +388,34 @@ static av_cold int eb_enc_init(AVCodecContext *avctx)
>          return ret;
>      }
>
> +    if (svt_enc->enc_params.pass >= 2) {
> +        int decode_size;
> +
> +        if (!avctx->stats_in) {
> +            av_log(avctx, AV_LOG_ERROR, "No stats file for %s pass\n",
> +                   svt_enc->enc_params.pass == 2 ? "second" : "third");
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        svt_enc->enc_params.rc_stats_buffer.sz = strlen(avctx->stats_in)
> * 3 / 4;
> +        svt_enc->enc_params.rc_stats_buffer.buf = av_malloc(svt_enc->
> enc_params.rc_stats_buffer.sz);
> +        if (!svt_enc->enc_params.rc_stats_buffer.buf) {
> +            av_log(avctx, AV_LOG_ERROR,
> +                   "Stat buffer alloc (%"SIZE_SPECIFIER" bytes) failed\n",
> +                   svt_enc->enc_params.rc_stats_buffer.sz);
> +            svt_enc->enc_params.rc_stats_buffer.sz = 0;
> +            return AVERROR(ENOMEM);
> +        }
> +        decode_size =
> av_base64_decode(svt_enc->enc_params.rc_stats_buffer.buf, avctx->stats_in,
> +                                       svt_enc->
> enc_params.rc_stats_buffer.sz);
> +        if (decode_size < 0) {
> +            av_log(avctx, AV_LOG_ERROR, "Stat buffer decode failed\n");
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        svt_enc->enc_params.rc_stats_buffer.sz = decode_size;
> +    }
> +
>      svt_ret = svt_av1_enc_set_parameter(svt_enc->svt_handle,
> &svt_enc->enc_params);
>      if (svt_ret != EB_ErrorNone) {
>          return svt_print_error(avctx, svt_ret, "Error setting encoder
> parameters");
> @@ -544,6 +589,38 @@ static int eb_receive_packet(AVCodecContext *avctx,
> AVPacket *pkt)
>      if (headerPtr->flags & EB_BUFFERFLAG_EOS)
>          svt_enc->eos_flag = EOS_RECEIVED;
>
> +    if (svt_enc->eos_flag == EOS_RECEIVED) {
> +        if (svt_enc->enc_params.pass == 1) {
> +            SvtAv1FixedBuf first_pass_stat;
> +            EbErrorType    ret = svt_av1_enc_get_stream_info(
> +                svt_enc->svt_handle,
> +                SVT_AV1_STREAM_INFO_FIRST_PASS_STATS_OUT,
> +                &first_pass_stat);
> +            if (ret == EB_ErrorNone) {
> +                size_t b64_size = AV_BASE64_SIZE(first_pass_stat.sz);
> +                avctx->stats_out = av_malloc(b64_size);
> +                if (!avctx->stats_out) {
> +                    av_log(avctx, AV_LOG_ERROR, "Stat buffer alloc
> (%"SIZE_SPECIFIER" bytes) failed\n",
> +                           b64_size);
> +                    return AVERROR(ENOMEM);
> +                }
> +                av_base64_encode(avctx->stats_out, b64_size,
> first_pass_stat.buf,
> +                                 first_pass_stat.sz);
> +            }
> +        }
> +        if (svt_enc->enc_params.pass == 2) {
> +            size_t b64_size = AV_BASE64_SIZE(svt_enc->
> enc_params.rc_stats_buffer.sz);
> +            avctx->stats_out = av_malloc(b64_size);
> +            if (!avctx->stats_out) {
> +                av_log(avctx, AV_LOG_ERROR, "Stat buffer alloc
> (%"SIZE_SPECIFIER" bytes) failed\n",
> +                       b64_size);
> +                return AVERROR(ENOMEM);
> +            }
> +            av_base64_encode(avctx->stats_out, b64_size,
> svt_enc->enc_params.rc_stats_buffer.buf,
> +                             svt_enc->enc_params.rc_stats_buffer.sz);
> +        }
> +    }
> +
>      ff_side_data_set_encoder_stats(pkt, headerPtr->qp * FF_QP2LAMBDA,
> NULL, 0, pict_type);
>
>      svt_av1_enc_release_out_buffer(&headerPtr);
> @@ -564,6 +641,11 @@ static av_cold int eb_enc_close(AVCodecContext *avctx)
>          av_freep(&svt_enc->in_buf);
>      }
>
> +    if (svt_enc->enc_params.rc_stats_buffer.buf) {
> +        av_freep(&svt_enc->enc_params.rc_stats_buffer.buf);
> +        svt_enc->enc_params.rc_stats_buffer.sz = 0;
> +    }
> +
>      av_buffer_pool_uninit(&svt_enc->pool);
>      av_frame_free(&svt_enc->frame);
>
> --
> 2.34.1
>
>
Lynne Oct. 30, 2022, 1:10 p.m. UTC | #2
Sep 27, 2022, 23:06 by gustav.grusell@gmail.com:

> Implements support for 2-pass CRF and 3-pass VBR by implementing
> reading and writing of stats file, and passing the pass number on
> to the encoder. For 3-pass VBR, the first pass should be run with
> '-pass 1', the second with '-pass 3' and the third with '-pass 2'.
>
> Signed-off-by: Gustav Grusell <gustav.grusell@gmail.com>
> ---
>  libavcodec/libsvtav1.c | 82 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 82 insertions(+)
>
> diff --git a/libavcodec/libsvtav1.c b/libavcodec/libsvtav1.c
> index 2f5634cee0..73fece49b7 100644
> --- a/libavcodec/libsvtav1.c
> +++ b/libavcodec/libsvtav1.c
> @@ -24,6 +24,7 @@
>  #include <EbSvtAv1ErrorCodes.h>
>  #include <EbSvtAv1Enc.h>
>  
> +#include "libavutil/base64.h"
>  #include "libavutil/common.h"
>  #include "libavutil/frame.h"
>  #include "libavutil/imgutils.h"
> @@ -312,6 +313,22 @@ static int config_enc_params(EbSvtAv1EncConfiguration *param,
>  cpb_props->avg_bitrate = avctx->bit_rate;
>  }
>  
> +    if (avctx->flags & AV_CODEC_FLAG_PASS2) {
> +        if (param->rate_control_mode == SVT_AV1_RC_MODE_VBR) {
> +            if (avctx->flags & AV_CODEC_FLAG_PASS1) {
> +                param->pass = 2;
> +            } else {
> +                param->pass = 3;
> +            }
> +        } else {
> +            param->pass = 2;
> +        }
> +    } else if (avctx->flags & AV_CODEC_FLAG_PASS1) {
> +        param->pass = 1;
> +    } else {
> +        param->pass = 0;
> +    }
> +
>  return 0;
>  }
>  
> @@ -371,6 +388,34 @@ static av_cold int eb_enc_init(AVCodecContext *avctx)
>  return ret;
>  }
>  
> +    if (svt_enc->enc_params.pass >= 2) {
> +        int decode_size;
> +
> +        if (!avctx->stats_in) {
> +            av_log(avctx, AV_LOG_ERROR, "No stats file for %s pass\n",
> +                   svt_enc->enc_params.pass == 2 ? "second" : "third");
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        svt_enc->enc_params.rc_stats_buffer.sz = strlen(avctx->stats_in) * 3 / 4;
> +        svt_enc->enc_params.rc_stats_buffer.buf = av_malloc(svt_enc->enc_params.rc_stats_buffer.sz);
> +        if (!svt_enc->enc_params.rc_stats_buffer.buf) {
> +            av_log(avctx, AV_LOG_ERROR,
> +                   "Stat buffer alloc (%"SIZE_SPECIFIER" bytes) failed\n",
> +                   svt_enc->enc_params.rc_stats_buffer.sz);
> +            svt_enc->enc_params.rc_stats_buffer.sz = 0;
> +            return AVERROR(ENOMEM);
> +        }
> +        decode_size = av_base64_decode(svt_enc->enc_params.rc_stats_buffer.buf, avctx->stats_in,
> +                                       svt_enc->enc_params.rc_stats_buffer.sz);
> +        if (decode_size < 0) {
> +            av_log(avctx, AV_LOG_ERROR, "Stat buffer decode failed\n");
> +            return AVERROR_INVALIDDATA;
> +        }
> +
> +        svt_enc->enc_params.rc_stats_buffer.sz = decode_size;
> +    }
> +
>  svt_ret = svt_av1_enc_set_parameter(svt_enc->svt_handle, &svt_enc->enc_params);
>  if (svt_ret != EB_ErrorNone) {
>  return svt_print_error(avctx, svt_ret, "Error setting encoder parameters");
> @@ -544,6 +589,38 @@ static int eb_receive_packet(AVCodecContext *avctx, AVPacket *pkt)
>  if (headerPtr->flags & EB_BUFFERFLAG_EOS)
>  svt_enc->eos_flag = EOS_RECEIVED;
>  
> +    if (svt_enc->eos_flag == EOS_RECEIVED) {
> +        if (svt_enc->enc_params.pass == 1) {
> +            SvtAv1FixedBuf first_pass_stat;
> +            EbErrorType    ret = svt_av1_enc_get_stream_info(
> +                svt_enc->svt_handle,
> +                SVT_AV1_STREAM_INFO_FIRST_PASS_STATS_OUT,
> +                &first_pass_stat);
> +            if (ret == EB_ErrorNone) {
> +                size_t b64_size = AV_BASE64_SIZE(first_pass_stat.sz);
> +                avctx->stats_out = av_malloc(b64_size);
> +                if (!avctx->stats_out) {
> +                    av_log(avctx, AV_LOG_ERROR, "Stat buffer alloc (%"SIZE_SPECIFIER" bytes) failed\n",
> +                           b64_size);
> +                    return AVERROR(ENOMEM);
> +                }
> +                av_base64_encode(avctx->stats_out, b64_size, first_pass_stat.buf,
> +                                 first_pass_stat.sz);
> +            }
> +        }
> +        if (svt_enc->enc_params.pass == 2) {
> +            size_t b64_size = AV_BASE64_SIZE(svt_enc->enc_params.rc_stats_buffer.sz);
> +            avctx->stats_out = av_malloc(b64_size);
> +            if (!avctx->stats_out) {
> +                av_log(avctx, AV_LOG_ERROR, "Stat buffer alloc (%"SIZE_SPECIFIER" bytes) failed\n",
> +                       b64_size);
> +                return AVERROR(ENOMEM);
> +            }
> +            av_base64_encode(avctx->stats_out, b64_size, svt_enc->enc_params.rc_stats_buffer.buf,
> +                             svt_enc->enc_params.rc_stats_buffer.sz);
> +        }
> +    }
> +
>  ff_side_data_set_encoder_stats(pkt, headerPtr->qp * FF_QP2LAMBDA, NULL, 0, pict_type);
>  
>  svt_av1_enc_release_out_buffer(&headerPtr);
> @@ -564,6 +641,11 @@ static av_cold int eb_enc_close(AVCodecContext *avctx)
>  av_freep(&svt_enc->in_buf);
>  }
>  
> +    if (svt_enc->enc_params.rc_stats_buffer.buf) {
> +        av_freep(&svt_enc->enc_params.rc_stats_buffer.buf);
> +        svt_enc->enc_params.rc_stats_buffer.sz = 0;
> +    }
> +
>  av_buffer_pool_uninit(&svt_enc->pool);
>  av_frame_free(&svt_enc->frame); 
>

2-pass doesn't seem to work, the encoder complains it's not the final pass.
Oneric Oct. 30, 2022, 10:20 p.m. UTC | #3
On Sun, Oct 30, 2022 at 14:10:29 +0100, Lynne wrote:
> Sep 27, 2022, 23:06 by gustav.grusell@gmail.com:
> 
> > Implements support for 2-pass CRF and 3-pass VBR by implementing
> > reading and writing of stats file, and passing the pass number on
> > to the encoder. For 3-pass VBR, the first pass should be run with
> > '-pass 1', the second with '-pass 3' and the third with '-pass 2'.
> >
> > [...]
> 
> 2-pass doesn't seem to work, the encoder complains it's not the final pass.

fwiw, 2-pass CRF and 3-pass VBR work for me on a short sample with this
patch applied on top of 882a17068fd8e62c7d38c14e6fb160d7c9fc446a and using
SVT-AV1 1.3.0.

I'm not sure if 2-pass VBR is supposed to work, though I didn't look into
it too much. SVT-AV1 docs only mention 3-pass VBR[1] afaict and while
requesting output already in the second pass (`--pass 2` for SvtAv1EncApp
and `-pass 3` for patched ffmpeg) "works", the result misses the targeted
overall bitrate even more than singlepass VBR.

For reference here are the commands I used:

2-pass CRF:
-----------
  ./ffmpeg -i org.mkv -c:v libsvtav1 \
    -preset 4 -crf 50 -g 300 -pass 1 -f null /dev/null \
  && ./ffmpeg -i org.mkv -c:v libsvtav1 \
    -preset 4 -crf 50 -g 300 -pass 2 -f matroska crf_pass2.mkv


3-pass VBR:
-----------
  ./ffmpeg -i org.mkv -c:v libsvtav1 \
    -preset 4 -svtav1-params 'rc=1:tbr=500k' -g 300 -pass 1 \
    -f null /dev/null \
 && ./fmpeg -i org.mkv -c:v libsvtav1 \
    -preset 4 -svtav1-params 'rc=1:tbr=500k' -g 300 -pass 3 \
    -f null /dev/null \
 && ./fmpeg -i org.mkv -c:v libsvtav1 \
    -preset 4 -svtav1-params 'rc=1:tbr=500k' -g 300 -pass 2 \
    -f matroska vbr_pass2.mkv


"2-pass VBR" (probably bad idea):
---------------------------------
  ./ffmpeg -i org.mkv -c:v libsvtav1 \
    -preset 4 -svtav1-params 'rc=1:tbr=500k' -g 300 -pass 1 \
    -f null /dev/null \
  && ./fmpeg -i org.mkv -c:v libsvtav1 \
    -preset 4 -svtav1-params 'rc=1:tbr=500k' -g 300 -pass 3 \
    -f matroska vbr_pass2.mkv


[1]: https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/91b94efb2809e83d9bf041d8575b32f234dfef27/Docs/svt-av1_encoder_user_guide.md#multi-pass-vbr-1000-kbps-at-maximum-quality-from-24fps-yuv-1920x1080-input
Gustav Grusell Nov. 3, 2022, 3:41 p.m. UTC | #4
On Sun, Oct 30, 2022 at 11:21 PM Oneric <oneric@oneric.de> wrote:

> On Sun, Oct 30, 2022 at 14:10:29 +0100, Lynne wrote:
> > Sep 27, 2022, 23:06 by gustav.grusell@gmail.com:
> >
> > > Implements support for 2-pass CRF and 3-pass VBR by implementing
> > > reading and writing of stats file, and passing the pass number on
> > > to the encoder. For 3-pass VBR, the first pass should be run with
> > > '-pass 1', the second with '-pass 3' and the third with '-pass 2'.
> > >
> > > [...]
> >
> > 2-pass doesn't seem to work, the encoder complains it's not the final
> pass.
>
> fwiw, 2-pass CRF and 3-pass VBR work for me on a short sample with this
> patch applied on top of 882a17068fd8e62c7d38c14e6fb160d7c9fc446a and using
> SVT-AV1 1.3.0.
>
> Ah good to hear the patch works also with SVT-AV1 1.3.0. I hadn't found
the time to check this.


> I'm not sure if 2-pass VBR is supposed to work, though I didn't look into
> it too much. SVT-AV1 docs only mention 3-pass VBR[1] afaict and while
> requesting output already in the second pass (`--pass 2` for SvtAv1EncApp
> and `-pass 3` for patched ffmpeg) "works", the result misses the targeted
> overall bitrate even more than singlepass VBR.
>

My understanding is also that SVT-AV1 does not support 2-pass VBR, like you
say there is no
mention of 2-pass VBR in the docs. The rate-control appendix gives some
details about
how the 3-pass VBR works, as far as I understand the second pass uses CRF so
makes sense that it would miss the target bitrate.

https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Appendix-Rate-Control.md#vbr-rate-control-flow

cheers,
Gustav
diff mbox series

Patch

diff --git a/libavcodec/libsvtav1.c b/libavcodec/libsvtav1.c
index 2f5634cee0..73fece49b7 100644
--- a/libavcodec/libsvtav1.c
+++ b/libavcodec/libsvtav1.c
@@ -24,6 +24,7 @@ 
 #include <EbSvtAv1ErrorCodes.h>
 #include <EbSvtAv1Enc.h>
 
+#include "libavutil/base64.h"
 #include "libavutil/common.h"
 #include "libavutil/frame.h"
 #include "libavutil/imgutils.h"
@@ -312,6 +313,22 @@  static int config_enc_params(EbSvtAv1EncConfiguration *param,
         cpb_props->avg_bitrate = avctx->bit_rate;
     }
 
+    if (avctx->flags & AV_CODEC_FLAG_PASS2) {
+        if (param->rate_control_mode == SVT_AV1_RC_MODE_VBR) {
+            if (avctx->flags & AV_CODEC_FLAG_PASS1) {
+                param->pass = 2;
+            } else {
+                param->pass = 3;
+            }
+        } else {
+            param->pass = 2;
+        }
+    } else if (avctx->flags & AV_CODEC_FLAG_PASS1) {
+        param->pass = 1;
+    } else {
+        param->pass = 0;
+    }
+
     return 0;
 }
 
@@ -371,6 +388,34 @@  static av_cold int eb_enc_init(AVCodecContext *avctx)
         return ret;
     }
 
+    if (svt_enc->enc_params.pass >= 2) {
+        int decode_size;
+
+        if (!avctx->stats_in) {
+            av_log(avctx, AV_LOG_ERROR, "No stats file for %s pass\n",
+                   svt_enc->enc_params.pass == 2 ? "second" : "third");
+            return AVERROR_INVALIDDATA;
+        }
+
+        svt_enc->enc_params.rc_stats_buffer.sz = strlen(avctx->stats_in) * 3 / 4;
+        svt_enc->enc_params.rc_stats_buffer.buf = av_malloc(svt_enc->enc_params.rc_stats_buffer.sz);
+        if (!svt_enc->enc_params.rc_stats_buffer.buf) {
+            av_log(avctx, AV_LOG_ERROR,
+                   "Stat buffer alloc (%"SIZE_SPECIFIER" bytes) failed\n",
+                   svt_enc->enc_params.rc_stats_buffer.sz);
+            svt_enc->enc_params.rc_stats_buffer.sz = 0;
+            return AVERROR(ENOMEM);
+        }
+        decode_size = av_base64_decode(svt_enc->enc_params.rc_stats_buffer.buf, avctx->stats_in,
+                                       svt_enc->enc_params.rc_stats_buffer.sz);
+        if (decode_size < 0) {
+            av_log(avctx, AV_LOG_ERROR, "Stat buffer decode failed\n");
+            return AVERROR_INVALIDDATA;
+        }
+
+        svt_enc->enc_params.rc_stats_buffer.sz = decode_size;
+    }
+
     svt_ret = svt_av1_enc_set_parameter(svt_enc->svt_handle, &svt_enc->enc_params);
     if (svt_ret != EB_ErrorNone) {
         return svt_print_error(avctx, svt_ret, "Error setting encoder parameters");
@@ -544,6 +589,38 @@  static int eb_receive_packet(AVCodecContext *avctx, AVPacket *pkt)
     if (headerPtr->flags & EB_BUFFERFLAG_EOS)
         svt_enc->eos_flag = EOS_RECEIVED;
 
+    if (svt_enc->eos_flag == EOS_RECEIVED) {
+        if (svt_enc->enc_params.pass == 1) {
+            SvtAv1FixedBuf first_pass_stat;
+            EbErrorType    ret = svt_av1_enc_get_stream_info(
+                svt_enc->svt_handle,
+                SVT_AV1_STREAM_INFO_FIRST_PASS_STATS_OUT,
+                &first_pass_stat);
+            if (ret == EB_ErrorNone) {
+                size_t b64_size = AV_BASE64_SIZE(first_pass_stat.sz);
+                avctx->stats_out = av_malloc(b64_size);
+                if (!avctx->stats_out) {
+                    av_log(avctx, AV_LOG_ERROR, "Stat buffer alloc (%"SIZE_SPECIFIER" bytes) failed\n",
+                           b64_size);
+                    return AVERROR(ENOMEM);
+                }
+                av_base64_encode(avctx->stats_out, b64_size, first_pass_stat.buf,
+                                 first_pass_stat.sz);
+            }
+        }
+        if (svt_enc->enc_params.pass == 2) {
+            size_t b64_size = AV_BASE64_SIZE(svt_enc->enc_params.rc_stats_buffer.sz);
+            avctx->stats_out = av_malloc(b64_size);
+            if (!avctx->stats_out) {
+                av_log(avctx, AV_LOG_ERROR, "Stat buffer alloc (%"SIZE_SPECIFIER" bytes) failed\n",
+                       b64_size);
+                return AVERROR(ENOMEM);
+            }
+            av_base64_encode(avctx->stats_out, b64_size, svt_enc->enc_params.rc_stats_buffer.buf,
+                             svt_enc->enc_params.rc_stats_buffer.sz);
+        }
+    }
+
     ff_side_data_set_encoder_stats(pkt, headerPtr->qp * FF_QP2LAMBDA, NULL, 0, pict_type);
 
     svt_av1_enc_release_out_buffer(&headerPtr);
@@ -564,6 +641,11 @@  static av_cold int eb_enc_close(AVCodecContext *avctx)
         av_freep(&svt_enc->in_buf);
     }
 
+    if (svt_enc->enc_params.rc_stats_buffer.buf) {
+        av_freep(&svt_enc->enc_params.rc_stats_buffer.buf);
+        svt_enc->enc_params.rc_stats_buffer.sz = 0;
+    }
+
     av_buffer_pool_uninit(&svt_enc->pool);
     av_frame_free(&svt_enc->frame);