Commit 45894f006c for ffmpeg

commit 45894f006c4d5d3158c36a477e147ea8407ac4bb
Author: Martin Storsjö <martin@martin.st>
Date:   Mon Oct 26 14:50:38 2026 +0200

    avcodec/libfdk-xhe-aacenc: Add support for two pass encoding

    The so far default one pass encoding requires a hardcoded
    loudness level parameter (which defaults to -24 dB).

    By doing two pass encoding, the first pass measures the loudness
    in order to be able to preserve the dynamics of the stream
    properly.

diff --git a/doc/encoders.texi b/doc/encoders.texi
index b111ca7c9b..d3487b40a5 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -864,6 +864,19 @@ If not set, the encoder tries to generate a hopefully unique ID for
 the stream.
 @end table

+In order to do two pass encoding (where the first pass measures the
+loudness of the stream, and the second pass uses that information
+for accurately preserving the dynamics of the stream), invoke ffmpeg
+e.g. like this:
+
+@example
+ffmpeg -i input.wav -c:a libfdk_xhe_aac -pass 1 -f null -
+ffmpeg -i input.wav -c:a libfdk_xhe_aac -pass 2 out.m4a
+@end example
+
+Doing two pass encoding is the recommended way to use the libfdk_xhe_aac
+encoder.
+
 @anchor{liblc3-enc}
 @section liblc3

diff --git a/libavcodec/libfdk-xhe-aacenc.c b/libavcodec/libfdk-xhe-aacenc.c
index fec9694b7c..fb9d67bf0e 100644
--- a/libavcodec/libfdk-xhe-aacenc.c
+++ b/libavcodec/libfdk-xhe-aacenc.c
@@ -17,6 +17,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */

+#include "libavutil/base64.h"
 #include "libavutil/channel_layout.h"
 #include "libavutil/common.h"
 #include "libavutil/mem.h"
@@ -45,6 +46,10 @@ typedef struct XHEAACContext {
     float *sample_queue;
     int sample_queue_size;
     int nb_sample_queue;
+
+    IIS_XHEAACENC_LOUDNESS_INSTANCE_HANDLE loudness_handle;
+
+    int loudness_exported;
 } XHEAACContext;

 static const AVOption xhe_aac_enc_options[] = {
@@ -99,12 +104,87 @@ static av_cold int xhe_aac_encode_close(AVCodecContext *avctx)
                    xhe_aac_get_error(err), err);
         s->handle = NULL;
     }
+    if (s->loudness_handle) {
+        err = IIS_xHEAACEnc_Loudness_Delete(s->loudness_handle);
+        if (err != IIS_XHEAACENC_NO_ERROR)
+            av_log(avctx, AV_LOG_ERROR, "Unexpected error on loudness close: %s (%d)\n",
+                   xhe_aac_get_error(err), err);
+        s->loudness_handle = NULL;
+    }
     ff_af_queue_close(&s->afq);
     av_freep(&s->sample_queue);

+    av_freep(&avctx->stats_out);
+
+    return 0;
+}
+
+static av_cold int xhe_aac_export_loudness(AVCodecContext *avctx)
+{
+    XHEAACContext *s = avctx->priv_data;
+    unsigned int size, b64_size;
+    const uint8_t *buf = NULL;
+    IIS_XHEAACENC_RETURN_CODE err;
+
+    err = IIS_xHEAACEnc_Loudness_Export(s->loudness_handle, &buf,
+                                        &size);
+    if (err != IIS_XHEAACENC_NO_ERROR) {
+        av_log(avctx, AV_LOG_ERROR, "Unable to export loudness: %s (%d)\n",
+               xhe_aac_get_error(err), err);
+        return AVERROR(EINVAL);
+    }
+    b64_size = AV_BASE64_SIZE(size);
+    avctx->stats_out = av_malloc(b64_size);
+    if (!avctx->stats_out)
+        return AVERROR(ENOMEM);
+
+    av_base64_encode(avctx->stats_out, b64_size,
+                     buf, size);
+
     return 0;
 }

+static av_cold int xhe_aac_import_loudness(AVCodecContext *avctx)
+{
+    XHEAACContext *s = avctx->priv_data;
+    int size;
+    uint8_t *buf = NULL;
+    int ret = AVERROR(EINVAL);
+    IIS_XHEAACENC_RETURN_CODE err;
+
+    if (!avctx->stats_in) {
+        av_log(avctx, AV_LOG_ERROR, "Must provide stats input for second pass\n");
+        return AVERROR(EINVAL);
+    }
+    size = AV_BASE64_DECODE_SIZE(strlen(avctx->stats_in));
+    if (size <= 0) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid stats input for second pass\n");
+        return AVERROR(EINVAL);
+    }
+
+    buf = av_malloc(size);
+    if (!buf)
+        return AVERROR(ENOMEM);
+    size = av_base64_decode(buf, avctx->stats_in, size);
+    if (size < 0) {
+        av_log(avctx, AV_LOG_ERROR, "Unable to decode stats input\n");
+        goto error;
+    }
+
+    err = IIS_xHEAACEnc_Loudness_Import(s->loudness_handle, buf, size);
+    if (err != IIS_XHEAACENC_NO_ERROR) {
+        av_log(avctx, AV_LOG_ERROR, "Unable to import loudness data: %s (%d)\n",
+               xhe_aac_get_error(err), err);
+        goto error;
+    }
+
+    ret = 0;
+
+error:
+    av_free(buf);
+    return ret;
+}
+
 static av_cold int xhe_aac_encode_init(AVCodecContext *avctx)
 {
     XHEAACContext *s = avctx->priv_data;
@@ -131,6 +211,37 @@ static av_cold int xhe_aac_encode_init(AVCodecContext *avctx)
         goto error;
     }

+    if (avctx->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2)) {
+        IIS_XHEAACENC_LOUDNESS_SETUP loudness_setup;
+
+        loudness_setup.sampleRate = avctx->sample_rate;
+        loudness_setup.channelConfig = channel_config;
+        loudness_setup.audioInputLengthSamples = 0;
+        loudness_setup.audioInputLengthAvailable = 0;
+
+        err = IIS_xHEAACEnc_Loudness_Open(&s->loudness_handle, loudness_setup);
+        if (err != IIS_XHEAACENC_NO_ERROR) {
+            av_log(avctx, AV_LOG_ERROR, "Unable to create loudness handle: %s (%d)\n",
+                   xhe_aac_get_error(err), err);
+            goto error;
+        }
+
+        if (avctx->flags & AV_CODEC_FLAG_PASS1) {
+            /* Arbitrary frame size for the first pass. */
+            avctx->frame_size = avctx->sample_rate * 25 / 1000;
+            /* For the first pass, we don't need to set up the
+             * encoder or anything - we just need the loudness handle. */
+            return 0;
+        }
+
+        /* For pass 2, import the loudness data from the previous run. */
+        int read_ret = xhe_aac_import_loudness(avctx);
+        if (read_ret != 0) {
+            ret = read_ret;
+            goto error;
+        }
+    }
+
     err = IIS_xHEAACEnc_Config_Open(&config_handle, avctx->sample_rate, channel_config);
     if (err != IIS_XHEAACENC_NO_ERROR) {
         av_log(avctx, AV_LOG_ERROR, "Unable to open config: %s (%d)\n",
@@ -237,12 +348,23 @@ static av_cold int xhe_aac_encode_init(AVCodecContext *avctx)
             goto error;
         }
     }
-    err = IIS_xHEAACEnc_Config_AddParamValueFloat(config_handle,
-              IIS_XHEAACENC_PARAMETER_LIVE_LOUDNESS_LEVEL, s->live_loudness_level);
-    if (err != IIS_XHEAACENC_NO_ERROR) {
-        av_log(avctx, AV_LOG_ERROR, "Unable to set live loudness level: %s (%d)\n",
-               xhe_aac_get_error(err), err);
-        goto error;
+
+    if (s->loudness_handle) {
+        err = IIS_xHEAACEnc_Config_AddParamValuePointer(config_handle,
+                  IIS_XHEAACENC_PARAMETER_LOUDNESS_DATA, s->loudness_handle);
+        if (err != IIS_XHEAACENC_NO_ERROR) {
+            av_log(avctx, AV_LOG_ERROR, "Unable to set loudness object: %s (%d)\n",
+                   xhe_aac_get_error(err), err);
+            goto error;
+        }
+    } else {
+        err = IIS_xHEAACEnc_Config_AddParamValueFloat(config_handle,
+                  IIS_XHEAACENC_PARAMETER_LIVE_LOUDNESS_LEVEL, s->live_loudness_level);
+        if (err != IIS_XHEAACENC_NO_ERROR) {
+            av_log(avctx, AV_LOG_ERROR, "Unable to set live loudness level: %s (%d)\n",
+                   xhe_aac_get_error(err), err);
+            goto error;
+        }
     }

     err = IIS_xHEAACEnc_Config_Finalize(config_handle);
@@ -369,6 +491,25 @@ static int xhe_aac_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
     int samples_next;
     int ret;

+    if (avctx->flags & AV_CODEC_FLAG_PASS1) {
+        *got_packet_ptr = 0;
+
+        if (frame) {
+            err = IIS_xHEAACEnc_Loudness_Measure(s->loudness_handle,
+                                                 (const float *)frame->data[0],
+                                                 frame->nb_samples * avctx->ch_layout.nb_channels);
+            if (err != IIS_XHEAACENC_NO_ERROR) {
+                av_log(avctx, AV_LOG_ERROR, "Unable to measure loudness: %s (%d)\n",
+                       xhe_aac_get_error(err), err);
+                return AVERROR(EINVAL);
+            }
+        } else if (!s->loudness_exported) {
+            s->loudness_exported = 1;
+            return xhe_aac_export_loudness(avctx);
+        }
+        return 0;
+    }
+
     if (frame) {
         int nb_new_samples = frame->nb_samples * avctx->ch_layout.nb_channels;

diff --git a/libavcodec/version.h b/libavcodec/version.h
index aa3a484c51..3ade8a8314 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -30,7 +30,7 @@
 #include "version_major.h"

 #define LIBAVCODEC_VERSION_MINOR  25
-#define LIBAVCODEC_VERSION_MICRO 101
+#define LIBAVCODEC_VERSION_MICRO 102

 #define LIBAVCODEC_VERSION_INT  AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
                                                LIBAVCODEC_VERSION_MINOR, \