[FFmpeg-devel] [PATCH] avcodec/libx265: add pass and x265-stats option (PR #21232)

2025-12-18 Thread Werner Robitza via ffmpeg-devel
PR #21232 opened by Werner Robitza (slhck)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21232
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21232.patch

Add support for standard -pass and -passlogfile options, matching the behavior
of libx264.
Add the -x265-stats option to specify the stats filename.
Update documentation.



>From 4a40be44b9b252211684b8f2e3ee0b52ed8e5c45 Mon Sep 17 00:00:00 2001
From: Werner Robitza 
Date: Thu, 18 Dec 2025 12:47:17 +0100
Subject: [PATCH] avcodec/libx265: add pass and x265-stats option

Add support for standard -pass and -passlogfile options, matching the behavior
of libx264.
Add the -x265-stats option to specify the stats filename.
Update documentation.

Signed-off-by: Werner Robitza 
---
 doc/encoders.texi |  4 
 fftools/ffmpeg_mux_init.c |  5 +
 libavcodec/libx265.c  | 20 
 3 files changed, 29 insertions(+)

diff --git a/doc/encoders.texi b/doc/encoders.texi
index e93a3cc275..610ec9a04c 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -3083,6 +3083,10 @@ Quantizer curve compression factor
 Normally, when forcing a I-frame type, the encoder can select any type
 of I-frame. This option forces it to choose an IDR-frame.
 
+@item x265-stats
+Specify the file name for 2-pass stats. This is set automatically when using
+the @option{-passlogfile} option.
+
 @item udu_sei @var{boolean}
 Import user data unregistered SEI if available into output. Default is 0 (off).
 
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index 194a87875d..0569f62836 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -730,6 +730,11 @@ static int new_stream_video(Muxer *mux, const 
OptionsContext *o,
  AV_OPT_SEARCH_CHILDREN) > 
0)
 av_opt_set(video_enc, "stats", logfilename,
AV_OPT_SEARCH_CHILDREN);
+} else if (!strcmp(video_enc->codec->name, "libx265")) {
+if (av_opt_is_set_to_default_by_name(video_enc, "x265-stats",
+ AV_OPT_SEARCH_CHILDREN) > 
0)
+av_opt_set(video_enc, "x265-stats", logfilename,
+   AV_OPT_SEARCH_CHILDREN);
 } else {
 if (video_enc->flags & AV_CODEC_FLAG_PASS2) {
 char  *logbuffer = read_file_to_string(logfilename);
diff --git a/libavcodec/libx265.c b/libavcodec/libx265.c
index 341868e7cd..89cd7d5dbe 100644
--- a/libavcodec/libx265.c
+++ b/libavcodec/libx265.c
@@ -71,6 +71,7 @@ typedef struct libx265Context {
 char *preset;
 char *tune;
 char *profile;
+char *stats;
 AVDictionary *x265_opts;
 
 void *sei_data;
@@ -529,6 +530,24 @@ static av_cold int libx265_encode_init(AVCodecContext 
*avctx)
 }
 }
 
+if (avctx->flags & AV_CODEC_FLAG_PASS1) {
+if (ctx->api->param_parse(ctx->params, "pass", "1") == 
X265_PARAM_BAD_VALUE) {
+av_log(avctx, AV_LOG_ERROR, "Invalid value for param \"pass\".\n");
+return AVERROR(EINVAL);
+}
+} else if (avctx->flags & AV_CODEC_FLAG_PASS2) {
+if (ctx->api->param_parse(ctx->params, "pass", "2") == 
X265_PARAM_BAD_VALUE) {
+av_log(avctx, AV_LOG_ERROR, "Invalid value for param \"pass\".\n");
+return AVERROR(EINVAL);
+}
+}
+if (ctx->stats) {
+if (ctx->api->param_parse(ctx->params, "stats", ctx->stats) == 
X265_PARAM_BAD_VALUE) {
+av_log(avctx, AV_LOG_ERROR, "Invalid value \"%s\" for param 
\"stats\".\n", ctx->stats);
+return AVERROR(EINVAL);
+}
+}
+
 if (ctx->params->rc.vbvBufferSize && avctx->rc_initial_buffer_occupancy > 
1000 &&
 ctx->params->rc.vbvBufferInit == 0.9) {
 ctx->params->rc.vbvBufferInit = 
(float)avctx->rc_initial_buffer_occupancy / 1000;
@@ -1009,6 +1028,7 @@ static const AVOption options[] = {
 { "preset",  "set the x265 preset",
 OFFSET(preset),AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
 { "tune","set the x265 tune parameter",
 OFFSET(tune),  AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
 { "profile", "set the x265 profile",   
 OFFSET(profile),   AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
+{ "x265-stats",  "Filename for 2 pass stats",  
 OFFSET(stats), AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE },
 { "udu_sei", "Use user data unregistered SEI if available",
 OFFSET(udu_sei),   AV_OPT_TYPE_BOOL,   { .i64 = 0 }, 0, 1, 
VE },
 { "a53cc",   "Use A53 Closed Captions (if available)", 
 OFFSET(a53_cc),AV_OPT_TYPE_BOOL,   { .i64 = 0 }, 0, 1, 
VE },
 { "x265-params", "set

[FFmpeg-devel] [PATCH] libsvtav1: Enable 2-pass encoding (PR #21239)

2025-12-19 Thread Werner Robitza via ffmpeg-devel
PR #21239 opened by Werner Robitza (slhck)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21239
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21239.patch

This patch enables two-pass encoding for libsvtav1 by implementing
support for AV_CODEC_FLAG_PASS1 and AV_CODEC_FLAG_PASS2.

Previously, users requiring two-pass encoding with SVT-AV1 had to use
the standalone SvtAv1EncApp tool. This patch allows 2-pass encoding
directly through FFmpeg.

Based on patch by Fredrik Lundkvist, with review feedback from James
Almer and Andreas Rheinhardt.
See: https://ffmpeg.org/pipermail/ffmpeg-devel/2024-May/327452.html

Changes:

- Use AV_BASE64_DECODE_SIZE macro for buffer size calculation
- Allocate own buffer for rc_stats_buffer (non-ownership pointer)
- Error handling with buffer cleanup

Signed-off-by: Werner Robitza 


>From 7b70b050c059feea1a00cc0c9b0fbbca1b8ef2ab Mon Sep 17 00:00:00 2001
From: Werner Robitza 
Date: Fri, 19 Dec 2025 11:37:28 +0100
Subject: [PATCH] libsvtav1: Enable 2-pass encoding

This patch enables two-pass encoding for libsvtav1 by implementing
support for AV_CODEC_FLAG_PASS1 and AV_CODEC_FLAG_PASS2.

Previously, users requiring two-pass encoding with SVT-AV1 had to use
the standalone SvtAv1EncApp tool. This patch allows 2-pass encoding
directly through FFmpeg.

Based on patch by Fredrik Lundkvist, with review feedback from James
Almer and Andreas Rheinhardt.
See: https://ffmpeg.org/pipermail/ffmpeg-devel/2024-May/327452.html

Changes:

- Use AV_BASE64_DECODE_SIZE macro for buffer size calculation
- Allocate own buffer for rc_stats_buffer (non-ownership pointer)
- Error handling with buffer cleanup

Signed-off-by: Werner Robitza 
---
 libavcodec/libsvtav1.c | 83 --
 1 file changed, 80 insertions(+), 3 deletions(-)

diff --git a/libavcodec/libsvtav1.c b/libavcodec/libsvtav1.c
index 7047b72422..e2b589ec89 100644
--- a/libavcodec/libsvtav1.c
+++ b/libavcodec/libsvtav1.c
@@ -21,6 +21,7 @@
  */
 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -28,6 +29,7 @@
 #include "libavutil/common.h"
 #include "libavutil/frame.h"
 #include "libavutil/imgutils.h"
+#include "libavutil/base64.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/mastering_display_metadata.h"
 #include "libavutil/mem.h"
@@ -65,6 +67,8 @@ typedef struct SvtContext {
 
 DOVIContext dovi;
 
+uint8_t *stats_buf;
+
 // User options.
 AVDictionary *svtav1_opts;
 int enc_mode;
@@ -337,6 +341,42 @@ static int config_enc_params(EbSvtAv1EncConfiguration 
*param,
 return AVERROR(ENOSYS);
 }
 #endif
+if (avctx->flags & AV_CODEC_FLAG_PASS2) {
+int stats_sz;
+
+if (!avctx->stats_in) {
+av_log(avctx, AV_LOG_ERROR, "No stats file for second pass\n");
+return AVERROR(EINVAL);
+}
+
+stats_sz = AV_BASE64_DECODE_SIZE(strlen(avctx->stats_in));
+if (stats_sz <= 0) {
+av_log(avctx, AV_LOG_ERROR, "Invalid stats file size\n");
+return AVERROR(EINVAL);
+}
+
+svt_enc->stats_buf = av_malloc(stats_sz);
+if (!svt_enc->stats_buf) {
+av_log(avctx, AV_LOG_ERROR, "Failed to allocate stats buffer\n");
+return AVERROR(ENOMEM);
+}
+
+stats_sz = av_base64_decode(svt_enc->stats_buf, avctx->stats_in, 
stats_sz);
+if (stats_sz < 0) {
+av_log(avctx, AV_LOG_ERROR, "Failed to decode stats file\n");
+av_freep(&svt_enc->stats_buf);
+return AVERROR(EINVAL);
+}
+
+param->rc_stats_buffer.buf = svt_enc->stats_buf;
+param->rc_stats_buffer.sz  = stats_sz;
+param->pass = 2;
+
+av_log(avctx, AV_LOG_INFO, "Using %d bytes of 2-pass stats\n", 
stats_sz);
+} else if (avctx->flags & AV_CODEC_FLAG_PASS1) {
+param->pass = 1;
+av_log(avctx, AV_LOG_INFO, "Starting first pass\n");
+}
 
 param->source_width = avctx->width;
 param->source_height= avctx->height;
@@ -614,9 +654,45 @@ static int eb_receive_packet(AVCodecContext *avctx, 
AVPacket *pkt)
 
 #if SVT_AV1_CHECK_VERSION(2, 0, 0)
 if (headerPtr->flags & EB_BUFFERFLAG_EOS) {
- svt_enc->eos_flag = EOS_RECEIVED;
- svt_av1_enc_release_out_buffer(&headerPtr);
- return AVERROR_EOF;
+if (avctx->flags & AV_CODEC_FLAG_PASS1) {
+SvtAv1FixedBuf first_pass_stats = { NULL, 0 };
+EbErrorType svt_ret_stats;
+int b64_size;
+
+svt_ret_stats = svt_av1_enc_get_stream_info(
+svt_enc->svt_handle,
+SVT_AV1_STREAM_INFO_FIRST_PASS_STATS_OUT,
+&first_pass_stats);
+
+if (svt_ret_stats != EB_ErrorNone) {
+av_log(avctx, AV_LOG_ERROR,
+   "Failed to get first pass stats\n");
+svt_av1_enc_release_out_buffer(&headerPtr);
+return AVERROR_EXTERNAL;
+}
+
+if (first_p