diff -ruN ffmpeg-7.1.1/configure ffmpeg-7.1.1-custom/configure --- ffmpeg-7.1.1/configure 2025-03-03 07:22:08.000000000 +0700 +++ ffmpeg-7.1.1-custom/configure 2025-07-14 08:24:16.313972645 +0700 @@ -213,6 +213,7 @@ --enable-jni enable JNI support [no] --enable-ladspa enable LADSPA audio filtering [no] --enable-lcms2 enable ICC profile support via LittleCMS 2 [no] + --enable-libaacplus enable AAC encoding using custom libaacplus [no] --enable-libaom enable AV1 video encoding/decoding via libaom [no] --enable-libaribb24 enable ARIB text and caption decoding via libaribb24 [no] --enable-libaribcaption enable ARIB text and caption decoding via libaribcaption [no] @@ -1874,6 +1875,10 @@ libxvid " +EXTERNAL_LIBRARY_RESTRICTED_LIST=" + libaacplus +" + EXTERNAL_LIBRARY_NONFREE_LIST=" decklink libfdk_aac @@ -1896,6 +1901,7 @@ " EXTERNAL_LIBRARY_LIST=" + $EXTERNAL_LIBRARY_RESTRICTED_LIST $EXTERNAL_LIBRARY_GPL_LIST $EXTERNAL_LIBRARY_NONFREE_LIST $EXTERNAL_LIBRARY_VERSION3_LIST @@ -3512,6 +3518,7 @@ hevc_videotoolbox_encoder_select="atsc_a53 videotoolbox_encoder" prores_videotoolbox_encoder_deps="pthreads" prores_videotoolbox_encoder_select="videotoolbox_encoder" +libaacplus_encoder_deps="libaacplus" libaom_av1_decoder_deps="libaom" libaom_av1_encoder_deps="libaom" libaom_av1_encoder_select="extract_extradata_bsf dovi_rpuenc" @@ -4502,7 +4509,7 @@ map "die_license_disabled version3" $EXTERNAL_LIBRARY_VERSION3_LIST $EXTERNAL_LIBRARY_GPLV3_LIST enabled gpl && map "die_license_disabled_gpl nonfree" $EXTERNAL_LIBRARY_NONFREE_LIST -map "die_license_disabled nonfree" $HWACCEL_LIBRARY_NONFREE_LIST +map "die_license_disabled nonfree" $HWACCEL_LIBRARY_NONFREE_LIST $EXTERNAL_LIBRARY_RESTRICTED_LIST enabled version3 && { enabled gpl && enable gplv3 || enable lgplv3; } @@ -6865,6 +6872,7 @@ enabled jni && { [ $target_os = "android" ] && check_headers jni.h && enabled pthreads || die "ERROR: jni not found"; } enabled ladspa && require_headers "ladspa.h dlfcn.h" enabled lcms2 && require_pkg_config lcms2 "lcms2 >= 2.13" lcms2.h cmsCreateContext +enabled libaacplus && require_pkg_config libaacplus "aacplus >= 3.0.0" aacplus.h aacplusEncOpen enabled libaom && require_pkg_config libaom "aom >= 2.0.0" aom/aom_codec.h aom_codec_version enabled libaribb24 && { check_pkg_config libaribb24 "aribb24 > 1.0.3" "aribb24/aribb24.h" arib_instance_new || { enabled gpl && require_pkg_config libaribb24 aribb24 "aribb24/aribb24.h" arib_instance_new; } || diff -ruN ffmpeg-7.1.1/libavcodec/allcodecs.c ffmpeg-7.1.1-custom/libavcodec/allcodecs.c --- ffmpeg-7.1.1/libavcodec/allcodecs.c 2024-09-30 06:31:47.000000000 +0700 +++ ffmpeg-7.1.1-custom/libavcodec/allcodecs.c 2025-07-14 08:19:18.226168604 +0700 @@ -756,6 +756,7 @@ extern const FFCodec ff_pcm_mulaw_at_decoder; extern const FFCodec ff_qdmc_at_decoder; extern const FFCodec ff_qdm2_at_decoder; +extern const FFCodec ff_libaacplus_encoder; extern FFCodec ff_libaom_av1_encoder; /* preferred over libaribb24 */ extern const FFCodec ff_libaribcaption_decoder; diff -ruN ffmpeg-7.1.1/libavcodec/libaacplus.c ffmpeg-7.1.1-custom/libavcodec/libaacplus.c --- ffmpeg-7.1.1/libavcodec/libaacplus.c 1970-01-01 07:00:00.000000000 +0700 +++ ffmpeg-7.1.1-custom/libavcodec/libaacplus.c 2025-07-14 09:42:49.266275099 +0700 @@ -0,0 +1,284 @@ +/* + * Interface to libaacplus for aac encoding + * Copyright (c) 2010 tipok , 2021-2024 Wrapper + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Interface to libaacplus encoder. + */ + +#include + +#include "libavutil/channel_layout.h" +#include "libavutil/common.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "avcodec.h" +#include "audio_frame_queue.h" +#include "codec_internal.h" +#include "encode.h" +#include "profiles.h" + +#define AACENC_BLOCKSIZE 1024 +#define CORE_DELAY (1600) +/* (96-64) makes AAC still some 64 core samples too early wrt SBR ... maybe -32 would be even more correct, but 1024-32 would need additional SBR bitstream delay by one frame */ +#define CORE_INPUT_OFFSET_PS (0) +/* ((1600 (core codec)*2 (multi rate) + 6*64 (sbr dec delay) - 2048 (sbr enc delay) + magic*/ +#define INPUT_DELAY ((CORE_DELAY) * 2 + 6 * 64 - (AACENC_BLOCKSIZE * 2) + 1) + +typedef struct aacPlusAudioContext +{ + const AVClass *class; + aacplusEncHandle aacplus_handle; + unsigned long max_output_bytes; + unsigned long samples_input; + int delay_sent; + + AudioFrameQueue afq; +} aacPlusAudioContext; + +static const AVOption aac_enc_options[] = { + FF_AAC_PROFILE_OPTS + { NULL } +}; + +static const AVClass aac_enc_class = { + .class_name = "libaac", + .item_name = av_default_item_name, + .option = aac_enc_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static av_cold int aacPlus_encode_init(AVCodecContext *avctx) +{ + aacPlusAudioContext *s = avctx->priv_data; + aacplusEncConfiguration *aacplus_cfg; + int flag_aacplus = (avctx->profile != AV_PROFILE_AAC_LOW && avctx->profile != AV_PROFILE_UNKNOWN); + + /* number of channels */ + if (avctx->ch_layout.nb_channels < 1 || avctx->ch_layout.nb_channels > 2) + { + av_log(avctx, AV_LOG_ERROR, "encoding %d channel(s) is not allowed\n", avctx->ch_layout.nb_channels); + return AVERROR(EINVAL); + } + + if (avctx->profile != AV_PROFILE_AAC_LOW && avctx->profile != AV_PROFILE_AAC_HE && avctx->profile != AV_PROFILE_UNKNOWN) + { + av_log(avctx, AV_LOG_ERROR, "invalid AAC profile: %d, only LC and HE-AAC were supported\n", avctx->profile); + return AVERROR(EINVAL); + } + + s->aacplus_handle = aacplusEncOpen(avctx->sample_rate, avctx->ch_layout.nb_channels, &s->samples_input, &s->max_output_bytes, flag_aacplus); + + if (!s->aacplus_handle) + { + av_log(avctx, AV_LOG_ERROR, "can't open encoder\n"); + return AVERROR(EINVAL); + } + + /* check aacplus version */ + aacplus_cfg = aacplusEncGetCurrentConfiguration(s->aacplus_handle); + + aacplus_cfg->bitRate = avctx->bit_rate; + aacplus_cfg->bandWidth = avctx->cutoff; + aacplus_cfg->outputFormat = !(avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER); + aacplus_cfg->inputFormat = avctx->sample_fmt == AV_SAMPLE_FMT_FLT ? AACPLUS_INPUT_FLOAT : AACPLUS_INPUT_16BIT; + + if (!aacplusEncSetConfiguration(s->aacplus_handle, aacplus_cfg)) + { + av_log(avctx, AV_LOG_ERROR, "libaacplus doesn't support this output format!\n"); + return AVERROR(EINVAL); + } + + avctx->frame_size = s->samples_input / avctx->ch_layout.nb_channels; + avctx->initial_padding = flag_aacplus ? INPUT_DELAY : CORE_DELAY; + + ff_af_queue_init(avctx, &s->afq); + + /* Set decoder specific info */ + avctx->extradata_size = 0; + if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) + { + unsigned char *buffer = NULL; + unsigned long decoder_specific_info_size; + + if (aacplusEncGetDecoderSpecificInfo(s->aacplus_handle, &buffer, &decoder_specific_info_size) == 1) + { + avctx->extradata_size = decoder_specific_info_size; + avctx->extradata = av_mallocz(decoder_specific_info_size + AV_INPUT_BUFFER_PADDING_SIZE); + + if (!avctx->extradata) + { + free(buffer); + return AVERROR(ENOMEM); + } + + memcpy(avctx->extradata, buffer, decoder_specific_info_size); + } + else + { + return -1; + } + free(buffer); + } + return 0; +} + +static int aacPlus_encode_frame(AVCodecContext *avctx, AVPacket *pkt, + const AVFrame *frame, int *got_packet) +{ + aacPlusAudioContext *s = avctx->priv_data; + int32_t *input_buffer; + int ret; + int discard_padding; + + if ((ret = ff_alloc_packet(avctx, pkt, s->max_output_bytes)) < 0) + return ret; + + if (!frame) + { + /* Flushing */ + ret = aacplusEncEncode(s->aacplus_handle, NULL, 0, pkt->data, pkt->size); + if (!ret) + { + return ret; // 0 - EOF + } + else if (ret < 0) + { + av_log(avctx, AV_LOG_ERROR, "cannot flush"); + return AVERROR(EINVAL); + } + + pkt->size = ret; + } + else + { + /* Encoding */ + input_buffer = (int32_t *)frame->data[0]; + if ((ret = ff_af_queue_add(&s->afq, frame)) < 0) + return ret; + + if ((ret = aacplusEncEncode(s->aacplus_handle, input_buffer, frame->nb_samples * avctx->ch_layout.nb_channels, pkt->data, pkt->size)) < 0) + { + av_log(avctx, AV_LOG_ERROR, "cannot encode"); + return AVERROR(EINVAL); + } + + pkt->size = ret; + } + + ff_af_queue_remove(&s->afq, avctx->frame_size, &pkt->pts, &pkt->duration); + + /* discard padding copied from fdkaac encoder */ + discard_padding = avctx->frame_size - pkt->duration; + + // Check if subtraction resulted in an overflow + if ((discard_padding < avctx->frame_size) != (pkt->duration > 0)) + { + av_log(avctx, AV_LOG_ERROR, "discard padding overflow\n"); + return AVERROR(EINVAL); + } + + if ((!s->delay_sent && avctx->initial_padding > 0) || discard_padding > 0) + { + uint8_t *side_data = + av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10); + if (!side_data) + return AVERROR(ENOMEM); + if (!s->delay_sent) + { + AV_WL32(side_data, avctx->initial_padding); + s->delay_sent = 1; + } + AV_WL32(side_data + 4, discard_padding); + } + + avpkt->flags |= AV_PKT_FLAG_KEY; + *got_packet = 1; + return 0; +} + +static void aacPlus_encode_flush(AVCodecContext *avctx) +{ + aacPlusAudioContext *s = avctx->priv_data; + uint8_t sink_null[32768]; + int32_t encode_empty[32768] = {0}; + int64_t pts, duration; + + ff_af_queue_remove(&s->afq, s->afq.frame_count, &pts, &duration); + if (aacplusEncEncode(s->aacplus_handle, encode_empty, s->samples_input, sink_null, s->max_output_bytes) < 0) + av_log(avctx, AV_LOG_ERROR, "error occured when trying to flush"); +} + +static av_cold int aacPlus_encode_close(AVCodecContext *avctx) +{ + aacPlusAudioContext *s = avctx->priv_data; + + if (s->aacplus_handle) + aacplusEncClose(s->aacplus_handle); + + ff_af_queue_close(&s->afq); + + return 0; +} + +static const FFCodecDefault defaults[] = { + {"b", "128000"}, + {NULL}}; + +static const AVProfile profiles[] = { + {AV_PROFILE_AAC_LOW, "LC"}, + {AV_PROFILE_AAC_HE, "HE-AAC"}, + // {FF_PROFILE_AAC_HE_V2, "HE-AACv2"}, + {AV_PROFILE_UNKNOWN}, +}; + +static const int aac_sample_rates[] = { + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000, 0 +}; + +static const AVChannelLayout aac_ch_layouts[2] = { + AV_CHANNEL_LAYOUT_MONO, + AV_CHANNEL_LAYOUT_STEREO +}; + +const FFCodec ff_libaacplus_encoder = { + .p.name = "libaac", + CODEC_LONG_NAME("custom 3GPP AAC+ encoder"), + .p.type = AVMEDIA_TYPE_AUDIO, + .p.id = AV_CODEC_ID_AAC, + .p.capabilities = AV_CODEC_CAP_SMALL_LAST_FRAME | AV_CODEC_CAP_DELAY | AV_CODEC_CAP_ENCODER_FLUSH, + .caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE, + .priv_data_size = sizeof(aacPlusAudioContext), + .init = aacPlus_encode_init, + FF_CODEC_ENCODE_CB(aacPlus_encode_frame), + .flush = aacPlus_encode_flush, + .close = aacPlus_encode_close, + .p.sample_fmts = (const enum AVSampleFormat[]){AV_SAMPLE_FMT_S16, + AV_SAMPLE_FMT_FLT, + AV_SAMPLE_FMT_NONE}, + .p.priv_class = &aac_enc_class, + .defaults = defaults, + .p.profiles = profiles, + .p.supported_samplerates = aac_sample_rates, + .p.ch_layouts = aac_ch_layouts, + .p.wrapper_name = "libaac", +}; diff -ruN ffmpeg-7.1.1/libavcodec/Makefile ffmpeg-7.1.1-custom/libavcodec/Makefile --- ffmpeg-7.1.1/libavcodec/Makefile 2024-09-30 06:31:47.000000000 +0700 +++ ffmpeg-7.1.1-custom/libavcodec/Makefile 2025-07-14 08:20:08.984859732 +0700 @@ -261,6 +261,7 @@ OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o OBJS-$(CONFIG_AURA_DECODER) += cyuv.o OBJS-$(CONFIG_AURA2_DECODER) += aura.o +OBJS-$(CONFIG_LIBAACPLUS_ENCODER) += libaacplus.o OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o