avcodec/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 <werner.robitza@gmail.com>
This commit is contained in:
Werner Robitza
2026-02-25 16:43:53 +01:00
parent 13e95746ea
commit 5ba2525c7a

View File

@@ -21,6 +21,7 @@
*/
#include <stdint.h>
#include <inttypes.h>
#include <EbSvtAv1ErrorCodes.h>
#include <EbSvtAv1Enc.h>
#include <EbSvtAv1Metadata.h>
@@ -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;
@@ -341,6 +345,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_VERBOSE, "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_VERBOSE, "Starting first pass\n");
}
param->source_width = avctx->width;
param->source_height = avctx->height;
@@ -618,9 +658,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 = { 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_pass_stats.sz > 0 && first_pass_stats.buf) {
b64_size = AV_BASE64_SIZE(first_pass_stats.sz);
avctx->stats_out = av_malloc(b64_size);
if (!avctx->stats_out) {
av_log(avctx, AV_LOG_ERROR,
"Failed to allocate stats output buffer\n");
svt_av1_enc_release_out_buffer(&headerPtr);
return AVERROR(ENOMEM);
}
av_base64_encode(avctx->stats_out, b64_size,
first_pass_stats.buf, first_pass_stats.sz);
av_log(avctx, AV_LOG_VERBOSE,
"First pass stats: %"PRIu64" bytes, encoded to %d bytes\n",
first_pass_stats.sz, b64_size);
}
}
svt_enc->eos_flag = EOS_RECEIVED;
svt_av1_enc_release_out_buffer(&headerPtr);
return AVERROR_EOF;
}
#endif
@@ -688,6 +764,7 @@ static av_cold int eb_enc_close(AVCodecContext *avctx)
av_buffer_pool_uninit(&svt_enc->pool);
av_frame_free(&svt_enc->frame);
ff_dovi_ctx_unref(&svt_enc->dovi);
av_freep(&svt_enc->stats_buf);
return 0;
}