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, \