From 621ac1a7caefe4e0b50de2ed16981c051bb93f0d Mon Sep 17 00:00:00 2001 From: Rafael Diniz Date: Wed, 4 Nov 2026 02:57:22 +0000 Subject: [PATCH 1/5] aac-enc: support the 960 sample frame length The AAC encoder only knew the 1024 sample transform for the plain AAC object types. The 960 transform (DRM, DAB+) needs its own scalefactor band tables for the long and the 120 sample short blocks at the supported sampling rates and its own TNS band limits; the frame length checks and the granule length parameter accept it, and it is reported as a capability. The 960 tables and the TNS limits follow the OpenDigitalRadio patches to fdk-aac 2.0.x (ODR-AudioEnc). Co-Authored-By: Claude Sonnet 5.5 --- cpp/libAACenc/include/aacenc_lib.h | 2 + cpp/libAACenc/src/aacenc.cpp | 1 + cpp/libAACenc/src/aacenc_lib.cpp | 3 +- cpp/libAACenc/src/aacenc_tns.cpp | 19 +++ cpp/libAACenc/src/psy_configuration.cpp | 180 +++++++++++++++++++++++- cpp/libAACenc/src/psy_const.h | 1 + 6 files changed, 202 insertions(+), 4 deletions(-) diff --git a/cpp/libAACenc/include/aacenc_lib.h b/cpp/libAACenc/include/aacenc_lib.h index 902ba118f..62c47a7e3 100644 --- a/cpp/libAACenc/include/aacenc_lib.h +++ b/cpp/libAACenc/include/aacenc_lib.h @@ -1318,6 +1318,8 @@ typedef enum { AACENC_GRANULE_LENGTH = 0x0105, /*!< Core encoder (AAC) audio frame length in samples: - 1024: Default configuration. + - 960: Length for DRM (and DAB+) with the AAC-LC, HE-AAC and + HE-AAC v2 object types. - 512: Default length in LD/ELD configuration. - 480: Length in LD/ELD configuration. - 256: Length for ELD reduced delay mode (x2). diff --git a/cpp/libAACenc/src/aacenc.cpp b/cpp/libAACenc/src/aacenc.cpp index 1af8a2e18..3d3e25314 100644 --- a/cpp/libAACenc/src/aacenc.cpp +++ b/cpp/libAACenc/src/aacenc.cpp @@ -495,6 +495,7 @@ AAC_ENCODER_ERROR FDKaacEnc_Initialize( /* check frame length */ switch (config->framelength) { case 1024: + case 960: if (isLowDelay(config->audioObjectType)) { return AAC_ENC_INVALID_FRAME_LENGTH; } diff --git a/cpp/libAACenc/src/aacenc_lib.cpp b/cpp/libAACenc/src/aacenc_lib.cpp index ee7f91869..cca1f6843 100644 --- a/cpp/libAACenc/src/aacenc_lib.cpp +++ b/cpp/libAACenc/src/aacenc_lib.cpp @@ -2102,7 +2102,7 @@ AACENC_ERROR aacEncGetLibInfo(LIB_INFO *info) { LIB_VERSION_STRING(&info[i]); /* Capability flags */ - info[i].flags = 0 | CAPF_AAC_1024 | CAPF_AAC_LC | CAPF_AAC_512 | + info[i].flags = 0 | CAPF_AAC_1024 | CAPF_AAC_LC | CAPF_AAC_960 | CAPF_AAC_512 | CAPF_AAC_480 | CAPF_AAC_DRC | CAPF_AAC_ELD_DOWNSCALE; /* End of flags */ @@ -2258,6 +2258,7 @@ AACENC_ERROR aacEncoder_SetParam(const HANDLE_AACENCODER hAacEncoder, if (settings->userFramelength != value) { switch (value) { case 1024: + case 960: case 512: case 480: case 256: diff --git a/cpp/libAACenc/src/aacenc_tns.cpp b/cpp/libAACenc/src/aacenc_tns.cpp index 4462600c3..343615060 100644 --- a/cpp/libAACenc/src/aacenc_tns.cpp +++ b/cpp/libAACenc/src/aacenc_tns.cpp @@ -196,6 +196,22 @@ static const TNS_MAX_TAB_ENTRY tnsMaxBandsTab1024[] = { {44100, {42, 14}}, {32000, {51, 14}}, {24000, {46, 14}}, {22050, {46, 14}}, {16000, {42, 14}}, {12000, {42, 14}}, {11025, {42, 14}}, {8000, {39, 14}}}; +static const TNS_MAX_TAB_ENTRY tnsMaxBandsTab960[] = +{ + { 96000, { 31, 9}}, + { 88200, { 31, 9}}, + { 64000, { 34, 10}}, + { 48000, { 49, 14}}, + { 44100, { 49, 14}}, + { 32000, { 49, 14}}, + { 24000, { 46, 15}}, + { 22050, { 46, 14}}, + { 16000, { 46, 15}}, + { 12000, { 42, 15}}, + { 11025, { 42, 15}}, + { 8000, { 40, 15}} +}; + static const TNS_MAX_TAB_ENTRY tnsMaxBandsTab120[] = { {48000, {12, -1}}, /* 48000 */ {44100, {12, -1}}, /* 44100 */ @@ -278,6 +294,9 @@ static INT getTnsMaxBands(const INT sampleRate, const INT granuleLength, switch (granuleLength) { case 960: + pMaxBandsTab = tnsMaxBandsTab960; + maxBandsTabSize = sizeof(tnsMaxBandsTab960) / sizeof(TNS_MAX_TAB_ENTRY); + break; case 1024: pMaxBandsTab = tnsMaxBandsTab1024; maxBandsTabSize = sizeof(tnsMaxBandsTab1024) / sizeof(TNS_MAX_TAB_ENTRY); diff --git a/cpp/libAACenc/src/psy_configuration.cpp b/cpp/libAACenc/src/psy_configuration.cpp index eef90bc0f..b444b583a 100644 --- a/cpp/libAACenc/src/psy_configuration.cpp +++ b/cpp/libAACenc/src/psy_configuration.cpp @@ -130,6 +130,161 @@ static const SFB_INFO_TAB sfbInfoTab[] = { }; + + +const SFB_PARAM_LONG p_FDKaacEnc_8000_long_960 = { + 40, + { 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 16, + 16, 16, 16, 16, 16, 16, 20, 20, 20, 20, 24, 24, 24, 28, + 28, 32, 36, 36, 40, 44, 48, 52, 56, 60, 64, 16 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_8000_short_120 = { + 15, + { 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 12, 16, 20, 12 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_11025_long_960 = { + 42, + { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 16, 16, 16, 16, 20, 20, 20, 24, + 24, 28, 28, 32, 36, 40, 40, 44, 48, 52, 56, 60, 64, 64 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_11025_short_120 = { + 15, + { 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 12, 12, 16, 20, 12 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_12000_long_960 = { + 42, + { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 16, 16, 16, 16, 20, 20, 20, 24, + 24, 28, 28, 32, 36, 40, 40, 44, 48, 52, 56, 60, 64, 64 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_12000_short_120 = { + 15, + { 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 12, 12, 16, 20, 12 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_16000_long_960 = { + 42, + { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 16, 16, 16, 16, 20, 20, 20, 24, + 24, 28, 28, 32, 36, 40, 40, 44, 48, 52, 56, 60, 64, 64 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_16000_short_120 = { + 15, + { 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 12, 12, 16, 20, 12 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_22050_long_960 = { + 46, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16, + 20, 20, 24, 24, 28, 28, 32, 36, 36, 40, 44, 48, 52, 52, + 64, 64, 64, 64 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_22050_short_120 = { + 15, + { 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 12, 12, 16, 16, 12 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_24000_long_960 = { + 46, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16, + 20, 20, 24, 24, 28, 28, 32, 36, 36, 40, 44, 48, 52, 52, + 64, 64, 64, 64 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_24000_short_120 = { + 15, + { 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 12, 12, 16, 16, 12 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_32000_long_960 = { + 49, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, + 12, 16, 16, 20, 20, 24, 24, 28, 28, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32 } +}; + +const SFB_PARAM_SHORT p_FDKaacEnc_32000_short_120 = { + 14, + { 4, 4, 4, 4, 4, 8, 8, 8, 12, 12, 12, 16, 16, 8 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_44100_long_960 = { + 49, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, + 8, 8, 8, 12, 12, 12, 12, 16, 16, 20, 20, 24, 24, 28, + 28, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32 } +}; + +const SFB_PARAM_SHORT p_FDKaacEnc_44100_short_120 = { + 14, + { 4, 4, 4, 4, 4, 8, 8, 8, 12, 12, 12, 16, 16, 8 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_48000_long_960 = { + 49, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, + 8, 8, 8, 12, 12, 12, 12, 16, 16, 20, 20, 24, 24, 28, + 28, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_48000_short_120 = { + 14, + { 4, 4, 4, 4, 4, 8, 8, 8, 12, 12, 12, 16, 16, 8 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_64000_long_960 = { + 46, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, + 12, 16, 16, 16, 20, 24, 24, 28, 36, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 16 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_64000_short_120 = { + 12, + { 4, 4, 4, 4, 4, 4, 8, 8, 8, 16, 28, 28 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_88200_long_960 = { + 40, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 12, + 12, 12, 12, 12, 16, 16, 24, 28, 36, 44, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_88200_short_120 = { + 12, + { 4, 4, 4, 4, 4, 4, 8, 8, 8, 16, 28, 28 } +}; + +const SFB_PARAM_LONG p_FDKaacEnc_96000_long_960 = { + 40, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 12, + 12, 12, 12, 12, 16, 16, 24, 28, 36, 44, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 } +}; +const SFB_PARAM_SHORT p_FDKaacEnc_96000_short_120 = { + 12, + { 4, 4, 4, 4, 4, 4, 8, 8, 8, 16, 28, 28 } +}; + + +static const SFB_INFO_TAB sfbInfoTab960[] = { + { 8000, &p_FDKaacEnc_8000_long_960, &p_FDKaacEnc_8000_short_120}, + {11025, &p_FDKaacEnc_11025_long_960, &p_FDKaacEnc_11025_short_120}, + {12000, &p_FDKaacEnc_12000_long_960, &p_FDKaacEnc_12000_short_120}, + {16000, &p_FDKaacEnc_16000_long_960, &p_FDKaacEnc_16000_short_120}, + {22050, &p_FDKaacEnc_22050_long_960, &p_FDKaacEnc_22050_short_120}, + {24000, &p_FDKaacEnc_24000_long_960, &p_FDKaacEnc_24000_short_120}, + {32000, &p_FDKaacEnc_32000_long_960, &p_FDKaacEnc_32000_short_120}, + {44100, &p_FDKaacEnc_44100_long_960, &p_FDKaacEnc_44100_short_120}, + {48000, &p_FDKaacEnc_48000_long_960, &p_FDKaacEnc_48000_short_120}, + {64000, &p_FDKaacEnc_64000_long_960, &p_FDKaacEnc_64000_short_120}, + {88200, &p_FDKaacEnc_88200_long_960, &p_FDKaacEnc_88200_short_120}, + {96000, &p_FDKaacEnc_96000_long_960, &p_FDKaacEnc_96000_short_120}, +}; + + /* 22050 and 24000 Hz */ static const SFB_PARAM_LONG p_22050_long_512 = { 31, {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 12, 12, @@ -204,10 +359,13 @@ AAC_ENCODER_ERROR FDKaacEnc_initSfbTable(const LONG sampleRate, */ switch (granuleLength) { case 1024: - case 960: sfbInfo = sfbInfoTab; size = (INT)(sizeof(sfbInfoTab) / sizeof(SFB_INFO_TAB)); break; + case 960: + sfbInfo = sfbInfoTab960; + size = (INT)(sizeof(sfbInfoTab960)/sizeof(SFB_INFO_TAB)); + break; case 512: sfbInfo = sfbInfoTabLD512; size = sizeof(sfbInfoTabLD512); @@ -287,23 +445,31 @@ static FIXP_DBL FDKaacEnc_BarcLineValue(INT noOfLines, INT fftLine, center_freq = fftLine * samplingFreq; /* q11 or q8 */ + switch (noOfLines) { case 1024: center_freq = center_freq << 2; /* q13 */ break; + case 960: + center_freq = fMult(center_freq, INV480) << 3; + break; case 128: center_freq = center_freq << 5; /* q13 */ break; + case 120: + center_freq = fMult(center_freq, INV480) << 6; + break; case 512: - center_freq = (fftLine * samplingFreq) << 3; // q13 + center_freq = (fftLine * samplingFreq) << 3; // q13 break; case 480: - center_freq = fMult(center_freq, INV480) << 4; // q13 + center_freq = fMult(center_freq, INV480) << 4; // q13 break; default: center_freq = (FIXP_DBL)0; } + x1 = fMult(center_freq, FOURBY3EM4); /* q13 * q43 - (DFRACT_BITS-1) = q25 */ x2 = fMult(center_freq, PZZZ76) << 2; /* q13 * q41 - (DFRACT_BITS-1) + 2 = q25 */ @@ -468,6 +634,14 @@ static void FDKaacEnc_initMinSnr(const LONG bitrate, const LONG samplerate, qperwin = qperwin - 9; pePerWindow = fMult(pePerWindow, FL2FXCONST_DBL(480.f / 512.f)); break; + case 960: + pePerWindow = fMult(pePerWindow, FL2FXCONST_DBL(960.f/1024.f)); + qperwin = qperwin - 10; + break; + case 120: + pePerWindow = fMult(pePerWindow, FL2FXCONST_DBL(120.f/128.f)); + qperwin = qperwin - 7; + break; } /* for short blocks it is assumed that more bits are available */ diff --git a/cpp/libAACenc/src/psy_const.h b/cpp/libAACenc/src/psy_const.h index c2d53042c..c3f3f64a6 100644 --- a/cpp/libAACenc/src/psy_const.h +++ b/cpp/libAACenc/src/psy_const.h @@ -108,6 +108,7 @@ amm-info@iis.fraunhofer.de #define TRANS_FAC 8 /* encoder short long ratio */ +#define FRAME_LEN_LONG_960 (960) #define FRAME_MAXLEN_SHORT ((1024) / TRANS_FAC) #define FRAME_LEN_SHORT_128 ((1024) / TRANS_FAC) #define FRAME_LEN_SHORT_120 (FRAME_LEN_LONG_960 / TRANS_FAC) From 22f6343cd3c105bfd3d6d9b533333902bcf053ae Mon Sep 17 00:00:00 2001 From: Rafael Diniz Date: Wed, 4 Nov 2026 02:57:52 +0000 Subject: [PATCH 2/5] aac-enc: add Huffman codeword reordering (HCR) DRM AAC (ETSI ES 201 980 5.4.1) uses the error resilient HCR tool: the spectral codewords are sorted by priority, the priority codewords are placed one per segment and the others in the room left in the segments, set by set, with the read direction alternating. aacenc_hcr.cpp does this for one long block channel and returns the two side info lengths (reordered data length and longest codeword) with the reordered bits; it mirrors the decoder (libAACdec/src/aacdec_hcr*.cpp), codeword by codeword and bit by bit. Not used by the encoder yet, the next commit does. Co-Authored-By: Claude Sonnet 5.5 --- CMakeLists.txt | 1 + Makefile.am | 1 + cpp/libAACenc/src/aacenc_hcr.cpp | 180 +++++++++++++++++++++++++++++++ cpp/libAACenc/src/aacenc_hcr.h | 30 ++++++ 4 files changed, 212 insertions(+) create mode 100644 cpp/libAACenc/src/aacenc_hcr.cpp create mode 100644 cpp/libAACenc/src/aacenc_hcr.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e74caa0c..90d2a6de7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,7 @@ set(AACENC_SRC libAACenc/src/aacEnc_ram.cpp libAACenc/src/aacEnc_rom.cpp libAACenc/src/aacenc.cpp + libAACenc/src/aacenc_hcr.cpp libAACenc/src/aacenc_lib.cpp libAACenc/src/aacenc_pns.cpp libAACenc/src/aacenc_tns.cpp diff --git a/Makefile.am b/Makefile.am index 72ca300d3..ce6351408 100644 --- a/Makefile.am +++ b/Makefile.am @@ -141,6 +141,7 @@ AACENC_SRC = \ cpp/libAACenc/src/aacEnc_ram.cpp \ cpp/libAACenc/src/aacEnc_rom.cpp \ cpp/libAACenc/src/aacenc.cpp \ + cpp/libAACenc/src/aacenc_hcr.cpp \ cpp/libAACenc/src/aacenc_lib.cpp \ cpp/libAACenc/src/aacenc_pns.cpp \ cpp/libAACenc/src/aacenc_tns.cpp \ diff --git a/cpp/libAACenc/src/aacenc_hcr.cpp b/cpp/libAACenc/src/aacenc_hcr.cpp new file mode 100644 index 000000000..87f64d126 --- /dev/null +++ b/cpp/libAACenc/src/aacenc_hcr.cpp @@ -0,0 +1,180 @@ +/* Huffman codeword reordering (HCR) for the DRM AAC encoder, ISO/IEC 14496-3 + 4.5.2.3 and ETSI ES 201 980 clause 5.4.1. + + Written for the Dream DRM software (https://github.com/Drm-tools/dream) as the + encoder side of libAACdec/src/aacdec_hcr*.cpp, and distributed under the terms + of the Fraunhofer FDK AAC Codec licence of this library (see NOTICE). */ + +#include "aacenc_hcr.h" +#include "bit_cnt.h" +#include "FDK_bitstream.h" +#include "aacEnc_rom.h" + +#define HCR_MAX_CB 32 +#define HCR_HI_LTAB(a) ((a) >> 16) +#define HCR_LO_LTAB(a) ((a) & 0xffff) + +/* FDKaacEnc_codeValues() writes codebooks 5 and 6 two codewords at a time; HCR + needs every pair on its own */ +static void hcrCodePair56(const SHORT *v, INT cb, FDK_BITSTREAM *bs) { + const INT t0 = v[0] + 4, t1 = v[1] + 4; + if (cb == 5) + FDKwriteBits(bs, FDKaacEnc_huff_ctab5[t0][t1], + HCR_HI_LTAB(FDKaacEnc_huff_ltab5_6[t0][t1])); + else + FDKwriteBits(bs, FDKaacEnc_huff_ctab6[t0][t1], + HCR_LO_LTAB(FDKaacEnc_huff_ltab5_6[t0][t1])); +} + +/* the same tables as in the decoder (libAACdec/src/aac_rom.cpp) */ +static const UCHAR hcrMaxCwLen[HCR_MAX_CB] = { + 0, 11, 9, 20, 16, 13, 11, 14, 12, 17, 14, 49, 0, 0, 0, 0, + 14, 17, 21, 21, 25, 25, 29, 29, 29, 29, 33, 33, 33, 37, 37, 41}; +static const UCHAR hcrDimShift[HCR_MAX_CB] = {1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; +static const UCHAR hcrPriority[HCR_MAX_CB] = { + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 22, 0, 0, 0, 0, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21}; + +typedef struct { + UCHAR cb; + UCHAR len; + UCHAR done; + UCHAR pos; /* bits already placed */ + UCHAR bit[64]; /* the codeword: body, sign bits, escape sequences */ +} HCR_CW; + +typedef struct { + INT left, right; /* next free bit from the left / from the right */ + INT start; /* first bit of the segment in the reordered data */ + INT width; + INT remaining; +} HCR_SEG; + +/* The codeword arrays are large: keep them off the stack */ +static HCR_CW hcrCw[HCR_MAX_CODEWORDS]; +static HCR_SEG hcrSeg[HCR_MAX_CODEWORDS]; + +INT FDKaacEnc_HcrEncode(HCR_ENC_DATA *hcr, const INT *sfbOffset, + const SECTION_DATA *sd, SHORT *quantSpectrum) { + INT nCw = 0; + INT total = 0; + INT longest = 0; + INT prio, i, k, s, t; + + if (sd->blockType == SHORT_WINDOW) return 1; /* not supported */ + + /* codewords of all sections, ordered by priority (stable inside a priority) */ + for (prio = 22; prio >= 1; prio--) { + for (i = 0; i < sd->noOfSections; i++) { + const INT cb = sd->huffsection[i].codeBook; + if (cb < 0 || cb >= HCR_MAX_CB || hcrPriority[cb] != prio) continue; + { + const INT sfbStart = sd->huffsection[i].sfbStart; + const INT sfbEnd = sfbStart + sd->huffsection[i].sfbCnt; + const INT line0 = sfbOffset[sfbStart]; + const INT lines = sfbOffset[sfbEnd] - line0; + const INT dim = 1 << hcrDimShift[cb]; + for (k = 0; k + dim <= lines; k += dim) { + UCHAR buf[64]; + FDK_BITSTREAM bs; + INT len, b; + if (nCw >= HCR_MAX_CODEWORDS) return 2; + FDKinitBitStream(&bs, buf, sizeof(buf), 0, BS_WRITER); + if (cb == 5 || cb == 6) + hcrCodePair56(quantSpectrum + line0 + k, cb, &bs); + else + FDKaacEnc_codeValues(quantSpectrum + line0 + k, dim, cb, &bs); + len = (INT)FDKgetValidBits(&bs); + FDKsyncCache(&bs); + if (len <= 0 || len > 49) return 3; + hcrCw[nCw].cb = (UCHAR)cb; + hcrCw[nCw].len = (UCHAR)len; + hcrCw[nCw].done = 0; + hcrCw[nCw].pos = 0; + for (b = 0; b < len; b++) + hcrCw[nCw].bit[b] = (buf[b >> 3] >> (7 - (b & 7))) & 1; + total += len; + if (len > longest) longest = len; + nCw++; + } + } + } + } + + hcr->lengthReordered = total; + hcr->longestCodeword = longest; + if (nCw == 0) return 0; + if (total > HCR_MAX_BITS || longest > 49) return 4; + + /* segments: one per codeword while they fit, the last one takes the rest */ + { + INT nSeg = 0, start = 0; + for (k = 0; k < nCw; k++) { + INT w = hcrMaxCwLen[hcrCw[k].cb]; + if (w > longest) w = longest; + if (start + w <= total) { + hcrSeg[nSeg].start = start; + hcrSeg[nSeg].width = w; + start += w; + nSeg++; + } else { + if (nSeg == 0) return 5; + hcrSeg[nSeg - 1].width = total - hcrSeg[nSeg - 1].start; + break; + } + } + for (s = 0; s < nSeg; s++) { + hcrSeg[s].left = 0; + hcrSeg[s].right = hcrSeg[s].width - 1; + hcrSeg[s].remaining = hcrSeg[s].width; + } + + /* priority codewords: codeword s in segment s, from the left */ + for (s = 0; s < nSeg; s++) { + HCR_CW *c = &hcrCw[s]; + if (c->len > hcrSeg[s].width) return 6; + for (i = 0; i < c->len; i++) + hcr->bits[hcrSeg[s].start + hcrSeg[s].left++] = c->bit[i]; + hcrSeg[s].remaining -= c->len; + c->done = 1; + } + + /* the other codewords, set by set: first set from the right, then + alternating. Trial t gives segment s the codeword (s - t) mod nSeg. */ + { + INT first = nSeg; + INT set = 1; + while (first < nCw) { + const INT cnt = (nCw - first < nSeg) ? (nCw - first) : nSeg; + const INT fromRight = (set & 1); + if (set >= 14) return 9; /* the decoder handles at most 14 sets */ + for (t = 0; t < nSeg; t++) { + for (s = 0; s < nSeg; s++) { + INT c = (s - t) % nSeg; + HCR_CW *cw; + if (c < 0) c += nSeg; + if (c >= cnt || hcrSeg[s].remaining <= 0) continue; + cw = &hcrCw[first + c]; + while (!cw->done && hcrSeg[s].remaining > 0) { + const INT p = fromRight ? hcrSeg[s].right-- : hcrSeg[s].left++; + hcr->bits[hcrSeg[s].start + p] = cw->bit[cw->pos++]; + hcrSeg[s].remaining--; + if (cw->pos == cw->len) cw->done = 1; + } + } + } + first += cnt; + set++; + } + } + + /* everything has to be in place */ + for (k = 0; k < nCw; k++) + if (!hcrCw[k].done) return 7; + for (s = 0; s < nSeg; s++) + if (hcrSeg[s].remaining != 0) return 8; + } + return 0; +} diff --git a/cpp/libAACenc/src/aacenc_hcr.h b/cpp/libAACenc/src/aacenc_hcr.h new file mode 100644 index 000000000..82fad38ca --- /dev/null +++ b/cpp/libAACenc/src/aacenc_hcr.h @@ -0,0 +1,30 @@ +/* Huffman codeword reordering (HCR) for the DRM AAC encoder, ISO/IEC 14496-3 + 4.5.2.3 and ETSI ES 201 980 clause 5.4.1. + + Written for the Dream DRM software (https://github.com/Drm-tools/dream) as the + encoder side of libAACdec/src/aacdec_hcr*.cpp, and distributed under the terms + of the Fraunhofer FDK AAC Codec licence of this library (see NOTICE). */ + +/* The codewords of one channel are sorted by priority, placed one per segment + (priority codewords) and the others in the space left in the segments, set by + set, trial by trial, exactly as the decoder takes them out again. */ +#ifndef AACENC_HCR_H +#define AACENC_HCR_H + +#include "common_fix.h" +#include "dyn_bits.h" + +#define HCR_MAX_CODEWORDS 512 +#define HCR_MAX_BITS 16383 /* 14 bit length field */ + +typedef struct { + INT lengthReordered; /* length_of_reordered_spectral_data */ + INT longestCodeword; /* length_of_longest_codeword */ + UCHAR bits[HCR_MAX_BITS + 1]; /* the reordered spectral data, one bit per entry */ +} HCR_ENC_DATA; + +/* Returns 0 on success. Long blocks only. */ +INT FDKaacEnc_HcrEncode(HCR_ENC_DATA *hcr, const INT *sfbOffset, + const SECTION_DATA *sectionData, SHORT *quantSpectrum); + +#endif From 955b4bd867fb37b869851b33e086935432916b47 Mon Sep 17 00:00:00 2001 From: Rafael Diniz Date: Wed, 4 Nov 2026 02:58:39 +0000 Subject: [PATCH 3/5] aac-enc: encode DRM AAC, HE-AAC and HE-AAC v2 (AOT 143, 144, 145) Adds the virtual DRM object types AOT_DRM_AAC, AOT_DRM_SBR and AOT_DRM_MPEG_PS to the encoder, the counterpart of what the decoder already does for TT_DRM (ETSI ES 201 980 clause 5.4). They run on the logic of AOT 2, 5 and 29 with the DRM syntax: error robust AAC scalable elements (the element lists the decoder uses), the 960 transform, long blocks only, no PNS, VCB11 section data (5 bit codebooks, escape codebook sections one sfb long), HCR spectral data (aacenc_hcr.cpp) and the 8 bit CRC in front of the protected region, written as the first byte of the raw frame and patched in after the region. With SBR, the SBR payload (8 bit CRC, header, data, PS in the extension) is written at the end of the frame in reverse bit order after the AAC data and zero stuffing, so that the first SBR bit is the last bit of the frame; the SBR encoder gets the scalable syntax (the mono bs_coupling bit) and the DRM CRC flags. Frames are returned as [crc][frame] with transport type TT_MP4_RAW. Checked against the decoder with TT_DRM: mono and stereo AAC at 12 and 24 kHz, HE-AAC mono and stereo, HE-AAC v2, 8 to 48 kbit/s; all frames decode with the right spectrum and a wrong CRC byte is rejected. Co-Authored-By: Claude Sonnet 5.5 --- cpp/libAACenc/include/aacenc_lib.h | 8 ++ cpp/libAACenc/src/aacenc.cpp | 14 ++- cpp/libAACenc/src/aacenc_lib.cpp | 49 ++++++++++- cpp/libAACenc/src/bitenc.cpp | 130 +++++++++++++++++++++++++--- cpp/libAACenc/src/dyn_bits.cpp | 19 ++-- cpp/libAACenc/src/psy_main.cpp | 6 ++ cpp/libSBRenc/include/sbr_encoder.h | 1 + cpp/libSBRenc/src/bit_sbr.cpp | 2 +- cpp/libSBRenc/src/sbr_encoder.cpp | 9 ++ 9 files changed, 215 insertions(+), 23 deletions(-) diff --git a/cpp/libAACenc/include/aacenc_lib.h b/cpp/libAACenc/include/aacenc_lib.h index 62c47a7e3..83d82e97b 100644 --- a/cpp/libAACenc/include/aacenc_lib.h +++ b/cpp/libAACenc/include/aacenc_lib.h @@ -1272,6 +1272,14 @@ typedef enum { - 129: MPEG-2 AAC Low Complexity. - 132: MPEG-2 AAC Low Complexity with Spectral Band Replication (HE-AAC). + - 143, 144, 145: Virtual AOTs for DRM (ETSI ES 201 980 clause + 5.4): AAC, AAC with SBR and AAC with SBR and Parametric Stereo + (::AOT_DRM_AAC, ::AOT_DRM_SBR, ::AOT_DRM_MPEG_PS). Error robust + AAC scalable syntax with HCR and VCB11, the 960 transform (long + blocks only), raw frames (::TT_MP4_RAW) of one CRC byte followed + by the frame, the SBR data at the end of the frame in reverse bit + order. The input rate is the AAC rate (12 or 24 kHz), twice that + with SBR; stereo and parametric stereo need 24 kHz. Please note that the virtual MPEG-2 AOT's basically disables non-existing Perceptual Noise Substitution tool in AAC encoder diff --git a/cpp/libAACenc/src/aacenc.cpp b/cpp/libAACenc/src/aacenc.cpp index 3d3e25314..68d6c511c 100644 --- a/cpp/libAACenc/src/aacenc.cpp +++ b/cpp/libAACenc/src/aacenc.cpp @@ -485,10 +485,11 @@ AAC_ENCODER_ERROR FDKaacEnc_Initialize( return AAC_ENC_UNSUPPORTED_BITRATE; } - if (config->syntaxFlags & AC_ER_VCB11) { + /* VCB11 (section data syntax) and HCR (aacenc_hcr.cpp) are supported for DRM only */ + if ((config->syntaxFlags & AC_ER_VCB11) && !(config->syntaxFlags & AC_DRM)) { return AAC_ENC_UNSUPPORTED_ER_FORMAT; } - if (config->syntaxFlags & AC_ER_HCR) { + if ((config->syntaxFlags & AC_ER_HCR) && !(config->syntaxFlags & AC_DRM)) { return AAC_ENC_UNSUPPORTED_ER_FORMAT; } @@ -563,12 +564,17 @@ AAC_ENCODER_ERROR FDKaacEnc_Initialize( * full reset requested. */ ErrorStatus = FDKaacEnc_psyInit(hAacEnc->psyKernel, hAacEnc->psyOut, hAacEnc->maxFrames, hAacEnc->maxChannels, - config->audioObjectType, cm); + (config->syntaxFlags & AC_DRM) + ? AOT_DRM_AAC + : config->audioObjectType, + cm); if (ErrorStatus != AAC_ENC_OK) goto bail; } ErrorStatus = FDKaacEnc_psyMainInit( - hAacEnc->psyKernel, config->audioObjectType, cm, config->sampleRate, + hAacEnc->psyKernel, + (config->syntaxFlags & AC_DRM) ? AOT_DRM_AAC : config->audioObjectType, + cm, config->sampleRate, config->framelength, psyBitrate, tnsMask, hAacEnc->bandwidth90dB, config->usePns, config->useIS, config->useMS, config->syntaxFlags, initFlags); diff --git a/cpp/libAACenc/src/aacenc_lib.cpp b/cpp/libAACenc/src/aacenc_lib.cpp index cca1f6843..c5e7c45c6 100644 --- a/cpp/libAACenc/src/aacenc_lib.cpp +++ b/cpp/libAACenc/src/aacenc_lib.cpp @@ -886,7 +886,14 @@ static AACENC_ERROR FDKaacEnc_AdjustEncSettings(HANDLE_AACENCODER hAacEncoder, hAacConfig->useIS = config->userIntensity; } - hAacConfig->audioObjectType = config->userAOT; + /* DRM AOTs run on the logic of the plain AOTs (see the switch below) */ + hAacConfig->audioObjectType = + (config->userAOT == AOT_DRM_AAC) + ? AOT_AAC_LC + : ((config->userAOT == AOT_DRM_SBR) + ? AOT_SBR + : ((config->userAOT == AOT_DRM_MPEG_PS) ? AOT_PS + : config->userAOT)); hAacConfig->channelMode = GetCoreChannelMode(config->userChannelMode, hAacConfig->audioObjectType); hAacConfig->nChannels = @@ -922,6 +929,28 @@ static AACENC_ERROR FDKaacEnc_AdjustEncSettings(HANDLE_AACENCODER hAacEncoder, /* Adapt internal AOT when necessary. */ switch (config->userAOT) { + case AOT_DRM_AAC: + case AOT_DRM_SBR: + case AOT_DRM_MPEG_PS: + /* DRM AAC (ETSI ES 201 980 5.4): ER AAC scalable syntax with HCR and + VCB11, 960 transform, raw frames. The SBR / PS logic of the plain + AOTs is reused, so the internal AOT is the plain one. */ + hAacConfig->audioObjectType = + (config->userAOT == AOT_DRM_AAC) + ? AOT_AAC_LC + : ((config->userAOT == AOT_DRM_SBR) ? AOT_SBR : AOT_PS); + hAacConfig->usePns = 0; + hAacConfig->epConfig = 1; + hAacConfig->syntaxFlags |= + AC_DRM | AC_ER | AC_SCALABLE | AC_ER_VCB11 | AC_ER_HCR; + config->userTpType = TT_MP4_RAW; + hAacConfig->framelength = (config->userFramelength != (UINT)-1) + ? config->userFramelength + : 960; + if (hAacConfig->framelength != 960) { + return AACENC_INVALID_CONFIG; + } + break; case AOT_MP2_AAC_LC: case AOT_MP2_SBR: hAacConfig->usePns = 0; @@ -2126,6 +2155,24 @@ AACENC_ERROR aacEncoder_SetParam(const HANDLE_AACENCODER hAacEncoder, if (settings->userAOT != (AUDIO_OBJECT_TYPE)value) { /* check if AOT matches the allocated modules */ switch (value) { + case AOT_DRM_MPEG_PS: + if (!(hAacEncoder->encoder_modis & (ENC_MODE_FLAG_PS))) { + err = AACENC_INVALID_CONFIG; + goto bail; + } + FDK_FALLTHROUGH; + case AOT_DRM_SBR: + if (!(hAacEncoder->encoder_modis & (ENC_MODE_FLAG_SBR))) { + err = AACENC_INVALID_CONFIG; + goto bail; + } + FDK_FALLTHROUGH; + case AOT_DRM_AAC: + if (!(hAacEncoder->encoder_modis & (ENC_MODE_FLAG_AAC))) { + err = AACENC_INVALID_CONFIG; + goto bail; + } + break; case AOT_PS: if (!(hAacEncoder->encoder_modis & (ENC_MODE_FLAG_PS))) { err = AACENC_INVALID_CONFIG; diff --git a/cpp/libAACenc/src/bitenc.cpp b/cpp/libAACenc/src/bitenc.cpp index 957e8216e..b5a1ac3f8 100644 --- a/cpp/libAACenc/src/bitenc.cpp +++ b/cpp/libAACenc/src/bitenc.cpp @@ -101,6 +101,8 @@ amm-info@iis.fraunhofer.de *******************************************************************************/ #include "bitenc.h" +#include "aacenc_hcr.h" +#include "FDK_crc.h" #include "bit_cnt.h" #include "dyn_bits.h" #include "qc_data.h" @@ -244,7 +246,7 @@ static INT FDKaacEnc_encodeSectionData(SECTION_DATA *sectionData, INT sectLen; INT i; INT dbgVal = FDKgetValidBits(hBitStream); - INT sectCbBits = 4; + INT sectCbBits = useVCB11 ? 5 : 4; switch (sectionData->blockType) { case LONG_WINDOW: @@ -263,6 +265,15 @@ static INT FDKaacEnc_encodeSectionData(SECTION_DATA *sectionData, for (i = 0; i < sectionData->noOfSections; i++) { INT codeBook = sectionData->huffsection[i].codeBook; + if (useVCB11 && (codeBook == 11 || codeBook >= 16)) { + /* VCB11: escape codebook sections are one sfb long, without length */ + INT n; + for (n = 0; n < sectionData->huffsection[i].sfbCnt; n++) { + FDKwriteBits(hBitStream, codeBook, sectCbBits); + } + continue; + } + FDKwriteBits(hBitStream, codeBook, sectCbBits); { @@ -826,8 +837,22 @@ INT FDKaacEnc_writeExtensionData(HANDLE_TRANSPORTENC hTpEnc, if (syntaxFlags & (AC_SCALABLE | AC_ER)) { { - if ((syntaxFlags & AC_ELD) && ((pExtension->type == EXT_SBR_DATA) || - (pExtension->type == EXT_SBR_DATA_CRC))) { + if ((syntaxFlags & AC_DRM) && + ((pExtension->type == EXT_FILL_DATA) || + (pExtension->type == EXT_FIL))) { + /* DRM: no extension payload syntax, the stuffing is plain zero bits */ + if (hBitStream != NULL) { + INT writeBits = payloadBits; + while (writeBits > 0) { + INT n = (writeBits > 16) ? 16 : writeBits; + FDKwriteBits(hBitStream, 0, n); + writeBits -= n; + } + } + extBitsUsed += payloadBits; + } else if (((syntaxFlags & AC_ELD) || (syntaxFlags & AC_DRM)) && + ((pExtension->type == EXT_SBR_DATA) || + (pExtension->type == EXT_SBR_DATA_CRC))) { if (hBitStream != NULL) { int i, writeBits = payloadBits; UCHAR *extPayloadData = pExtension->pPayload; @@ -927,6 +952,10 @@ AAC_ENCODER_ERROR FDKaacEnc_ChannelElementWrite( int i, ch, decision_bit; INT crcReg1 = -1, crcReg2 = -1; UCHAR numberOfChannels; + FDK_CRCINFO drmCrcInfo; + INT drmCrcRegion = 0, drmCrcBitPos = 0; + static HCR_ENC_DATA hcrData[2]; /* not reentrant, like aacenc_hcr.cpp */ + INT hcrOk[2] = {0, 0}; if (hTpEnc != NULL) { /* Get bitstream handle */ @@ -940,7 +969,9 @@ AAC_ENCODER_ERROR FDKaacEnc_ChannelElementWrite( } /* Get channel element sequence table */ - list = getBitstreamElementList(aot, epConfig, numberOfChannels, 0, 0); + list = getBitstreamElementList( + (syntaxFlags & AC_DRM) ? AOT_DRM_AAC : aot, epConfig, numberOfChannels, + 0, 0); if (list == NULL) { error = AAC_ENC_UNSUPPORTED_AOT; goto bail; @@ -1102,12 +1133,36 @@ AAC_ENCODER_ERROR FDKaacEnc_ChannelElementWrite( case esc1_hcr: if (syntaxFlags & AC_ER_HCR) { - error = AAC_ENC_UNKNOWN; + /* length_of_reordered_spectral_data (14), length_of_longest_codeword (6) */ + bitDemand += 20; + if (hBitStream != NULL && qcOutChannel != NULL) { + if (FDKaacEnc_HcrEncode(&hcrData[ch], psyOutChannel[ch]->sfbOffsets, + pChSectionData, + qcOutChannel[ch]->quantSpec) != 0) { + error = AAC_ENC_UNKNOWN; + hcrData[ch].lengthReordered = 0; + hcrData[ch].longestCodeword = 0; + } else { + hcrOk[ch] = 1; + } + FDKwriteBits(hBitStream, hcrData[ch].lengthReordered, 14); + FDKwriteBits(hBitStream, hcrData[ch].longestCodeword, 6); + } } break; case spectral_data: - if (hBitStream != NULL) { + if (hBitStream != NULL && (syntaxFlags & AC_ER_HCR)) { + INT b; + if (hcrOk[ch]) { + for (b = 0; b < hcrData[ch].lengthReordered; b++) + FDKwriteBits(hBitStream, hcrData[ch].bits[b], 1); + } + if (hcrData[ch].lengthReordered != + qcOutChannel[ch]->sectionData.huffmanBits) { + return AAC_ENC_WRITE_SPEC_ERROR; + } + } else if (hBitStream != NULL) { INT spectralBits = 0; spectralBits = FDKaacEnc_encodeSpectralData( @@ -1133,19 +1188,35 @@ AAC_ENCODER_ERROR FDKaacEnc_ChannelElementWrite( } break; case adtscrc_end_reg1: - case drmcrc_end_reg: if (hTpEnc != NULL) { transportEnc_CrcEndReg(hTpEnc, crcReg1); } break; + case drmcrc_end_reg: + if (hBitStream != NULL) { + /* CRC of the region goes into the byte in front of it */ + FDK_BITSTREAM bsWriter; + FDKcrcEndReg(&drmCrcInfo, hBitStream, drmCrcRegion); + FDKinitBitStream(&bsWriter, hBitStream->hBitBuf.Buffer, + hBitStream->hBitBuf.bufSize, 0, BS_WRITER); + FDKpushFor(&bsWriter, drmCrcBitPos); + FDKwriteBits(&bsWriter, FDKcrcGetCRC(&drmCrcInfo) ^ 0xFF, 8); + FDKsyncCache(&bsWriter); + } + break; case adtscrc_end_reg2: if (hTpEnc != NULL) { transportEnc_CrcEndReg(hTpEnc, crcReg2); } break; case drmcrc_start_reg: - if (hTpEnc != NULL) { - crcReg1 = transportEnc_CrcStartReg(hTpEnc, 0); + /* 8 bit CRC in front of the protected region, filled in at the end */ + bitDemand += 8; + if (hBitStream != NULL) { + drmCrcBitPos = (INT)FDKgetValidBits(hBitStream); + FDKwriteBits(hBitStream, 0, 8); + FDKcrcInit(&drmCrcInfo, 0x001d, 0xFFFF, 8); + drmCrcRegion = FDKcrcStartReg(&drmCrcInfo, hBitStream, 0); } break; case next_channel: @@ -1300,7 +1371,46 @@ AAC_ENCODER_ERROR FDKaacEnc_WriteBitstream(HANDLE_TRANSPORTENC hTpEnc, FDKwriteBits(hBs, ID_END, EL_ID_BITS); } - if (doByteAlign) { + if (syntaxFlags & AC_DRM) { + /* DRM AAC + SBR (ETSI ES 201 980 5.4.2): the SBR payload sits at the end + of the frame in reverse bit order, the first SBR bit is the last bit of + the frame. Everything before it is the AAC data and zero stuffing. */ + INT sbrBits = 0, e, el; + for (el = 0; el < channelMapping->nElements; el++) { + for (e = 0; e < qcOut->qcElement[el]->nExtensions; e++) { + const QC_OUT_EXTENSION *ex = &qcOut->qcElement[el]->extension[e]; + if (ex->type == EXT_SBR_DATA || ex->type == EXT_SBR_DATA_CRC) + sbrBits += ex->nPayloadBits; + } + } + if (((FDKgetValidBits(hBs) - alignAnchor + qcOut->alignBits + sbrBits) & + 0x7) != 0) { + return AAC_ENC_WRITTEN_BITS_ERROR; + } + FDKaacEnc_ByteAlignment(hBs, qcOut->alignBits); + for (el = 0; el < channelMapping->nElements; el++) { + for (e = 0; e < qcOut->qcElement[el]->nExtensions; e++) { + const QC_OUT_EXTENSION *ex = &qcOut->qcElement[el]->extension[e]; + if (ex->type == EXT_SBR_DATA || ex->type == EXT_SBR_DATA_CRC) { + INT b; + /* sbr_crc_bits (8, G8, start value all ones, inverted) over all the + SBR data behind them */ + UINT crc = 0xFF; + for (b = 8; b < ex->nPayloadBits; b++) { + UINT bit = (ex->pPayload[b >> 3] >> (7 - (b & 7))) & 1; + UINT flag = ((crc >> 7) & 1) ^ bit; + crc = (crc << 1) & 0xFF; + if (flag) crc ^= 0x1d; + } + crc ^= 0xFF; + for (b = ex->nPayloadBits - 1; b >= 8; b--) + FDKwriteBits(hBs, (ex->pPayload[b >> 3] >> (7 - (b & 7))) & 1, 1); + for (b = 0; b < 8; b++) + FDKwriteBits(hBs, (crc >> b) & 1, 1); + } + } + } + } else if (doByteAlign) { /* Assure byte alignment*/ if (((FDKgetValidBits(hBs) - alignAnchor + qcOut->alignBits) & 0x7) != 0) { return AAC_ENC_WRITTEN_BITS_ERROR; diff --git a/cpp/libAACenc/src/dyn_bits.cpp b/cpp/libAACenc/src/dyn_bits.cpp index 74baeb88f..c47451b24 100644 --- a/cpp/libAACenc/src/dyn_bits.cpp +++ b/cpp/libAACenc/src/dyn_bits.cpp @@ -114,9 +114,13 @@ static INT FDKaacEnc_getSideInfoBits(const SECTION_INFO* const huffsection, const INT useHCR) { INT sideInfoBits; - if (useHCR && - ((huffsection->codeBook == 11) || (huffsection->codeBook >= 16))) { - sideInfoBits = 5; + if (useHCR) { + /* VCB11: 5 bit codebook, escape codebook sections are one sfb each */ + if ((huffsection->codeBook == 11) || (huffsection->codeBook >= 16)) { + sideInfoBits = 5 * huffsection->sfbCnt; + } else { + sideInfoBits = sideInfoTab[huffsection->sfbCnt] + 1; + } } else { sideInfoBits = sideInfoTab[huffsection->sfbCnt]; } @@ -411,11 +415,12 @@ static void FDKaacEnc_noiselessCounter( /* the sections in the sectionData are now marked with the optimal code * book */ - FDKaacEnc_findBestBook(bitLookUp[i], &(huffsection[i].codeBook), - useVCB11); + INT huffBits = FDKaacEnc_findBestBook( + bitLookUp[i], &(huffsection[i].codeBook), useVCB11); - sectionData->huffmanBits += - huffsection[i].sectionBits - + sectionData->huffmanBits += huffBits; + huffsection[i].sectionBits = + huffBits + FDKaacEnc_getSideInfoBits(&huffsection[i], sideInfoTab, useVCB11); } diff --git a/cpp/libAACenc/src/psy_main.cpp b/cpp/libAACenc/src/psy_main.cpp index f6345e4e5..717a5dacc 100644 --- a/cpp/libAACenc/src/psy_main.cpp +++ b/cpp/libAACenc/src/psy_main.cpp @@ -239,6 +239,12 @@ AAC_ENCODER_ERROR FDKaacEnc_psyInitStates(PSY_INTERNAL *hPsy, FDKaacEnc_InitBlockSwitching(&psyStatic->blockSwitchingControl, isLowDelay(audioObjectType)); + if (audioObjectType == AOT_DRM_AAC) { + /* DRM: the HCR encoder only handles long blocks */ + psyStatic->blockSwitchingControl.allowShortFrames = 0; + psyStatic->blockSwitchingControl.allowLookAhead = 0; + } + return AAC_ENC_OK; } diff --git a/cpp/libSBRenc/include/sbr_encoder.h b/cpp/libSBRenc/include/sbr_encoder.h index d979ba6c7..1e2f15017 100644 --- a/cpp/libSBRenc/include/sbr_encoder.h +++ b/cpp/libSBRenc/include/sbr_encoder.h @@ -174,6 +174,7 @@ typedef struct sbrConfiguration { INT SendHeaderDataTime; /*!< SBR header send update frequency in ms. */ INT useWaveCoding; /*!< Flag: usage of wavecoding tool. */ INT crcSbr; /*!< Flag: usage of SBR-CRC. */ + INT drmSyntax; /*!< Flag: DRM SBR syntax (8 bit CRC). */ INT dynBwSupported; /*!< Flag: support for dynamic bandwidth in this combination. */ INT parametricCoding; /*!< Flag: usage of parametric coding tool. */ diff --git a/cpp/libSBRenc/src/bit_sbr.cpp b/cpp/libSBRenc/src/bit_sbr.cpp index 5a65e98f7..0fb623c58 100644 --- a/cpp/libSBRenc/src/bit_sbr.cpp +++ b/cpp/libSBRenc/src/bit_sbr.cpp @@ -417,7 +417,7 @@ static INT encodeSbrSingleChannelElement( } } else { if (sbrSyntaxFlags & SBR_SYNTAX_SCALABLE) { - payloadBits += FDKwriteBits(hBitStream, 1, SI_SBR_COUPLING_BITS); + payloadBits += FDKwriteBits(hBitStream, 0, SI_SBR_COUPLING_BITS); } payloadBits += encodeSbrGrid(sbrEnvData, hBitStream); } diff --git a/cpp/libSBRenc/src/sbr_encoder.cpp b/cpp/libSBRenc/src/sbr_encoder.cpp index c3da07286..761651d5e 100644 --- a/cpp/libSBRenc/src/sbr_encoder.cpp +++ b/cpp/libSBRenc/src/sbr_encoder.cpp @@ -646,6 +646,7 @@ static UINT FDKsbrEnc_InitializeSbrDefaults(sbrConfigurationPtr config, config->SendHeaderDataTime = 1000; config->useWaveCoding = 0; config->crcSbr = 0; + config->drmSyntax = 0; config->dynBwSupported = 1; if (isLowDelay) config->tran_thr = 6000; @@ -1681,6 +1682,10 @@ static INT FDKsbrEnc_EnvInit(HANDLE_SBR_ELEMENT hSbrElement, if (params->crcSbr) { hSbrElement->sbrConfigData.sbrSyntaxFlags |= SBR_SYNTAX_CRC; } + if (params->drmSyntax) { + hSbrElement->sbrConfigData.sbrSyntaxFlags |= + SBR_SYNTAX_CRC | SBR_SYNTAX_DRM_CRC | SBR_SYNTAX_SCALABLE; + } hSbrElement->sbrConfigData.noQmfBands = 64 >> (2 - params->downSampleFactor); switch (hSbrElement->sbrConfigData.noQmfBands) { @@ -2193,6 +2198,10 @@ INT sbrEncoder_Init(HANDLE_SBR_ENCODER hSbrEncoder, error = 1; goto bail; } + if (syntaxFlags & AC_DRM) { + sbrConfig[el].crcSbr = 1; + sbrConfig[el].drmSyntax = 1; + } /* * Modify sbrConfig structure according to Element parameters From f2c4fa9f374ed977d68ca8af099f06d5500a7bb8 Mon Sep 17 00:00:00 2001 From: Rafael Diniz Date: Wed, 4 Nov 2026 02:59:13 +0000 Subject: [PATCH 4/5] Document the DRM encoder object types and where the 960 transform tables come from Co-Authored-By: Claude Sonnet 5.5 --- README.DRM.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 README.DRM.md diff --git a/README.DRM.md b/README.DRM.md new file mode 100644 index 000000000..433233849 --- /dev/null +++ b/README.DRM.md @@ -0,0 +1,39 @@ +# DRM AAC encoding + +The encoder can produce the audio of the DRM broadcasting system (ETSI ES 201 980, +clause 5.4) through three virtual audio object types, the counterparts of what the +decoder reads with `TT_DRM`: + +| `AACENC_AOT` | value | content | +|-------------------------|-------|-----------------------------------------------------| +| `AOT_DRM_AAC` | 143 | AAC, mono or stereo | +| `AOT_DRM_SBR` | 144 | AAC with SBR (HE-AAC), mono or stereo | +| `AOT_DRM_MPEG_PS` | 145 | AAC with SBR and parametric stereo (HE-AAC v2) | + +Use them with `AACENC_TRANSMUX = TT_MP4_RAW` and an input sampling rate of 12 or 24 kHz +(the AAC rate of the DRM service), twice that with SBR. Stereo and parametric stereo +need 24 kHz. One call to `aacEncEncode()` returns one raw frame: a CRC byte followed by +the frame, which is what a DRM audio super frame carries per audio frame. + +What the DRM syntax needs, and this adds to the encoder: + +* the 960 sample transform (scalefactor band tables and TNS limits for it), +* the error robust AAC scalable element syntax with HCR (`aacenc_hcr.cpp`) and VCB11, + long blocks only, no PNS, +* the 8 bit CRC over the side information, +* the SBR payload (with its own 8 bit CRC) at the end of the frame in reverse bit + order. + +## Origin + +The 960 sample tables and TNS limits follow the patches of OpenDigitalRadio +([ODR-AudioEnc](https://github.com/Opendigitalradio/ODR-AudioEnc)) to fdk-aac 2.0.x; the +DRM encoding was written for the Dream DRM software +() and tested against the decoder of this library +and in a complete DRM transmitter / receiver chain. It has not been checked against +other DRM receivers. + +## Licence + +The Fraunhofer FDK AAC Codec licence in `NOTICE` applies to the whole library including +these additions; no patent licence is granted, see the licence text. From 07ecc612eec99f852c3d45c37f47c3703b8e78bd Mon Sep 17 00:00:00 2001 From: Rafael Diniz Date: Wed, 11 Nov 2026 15:47:59 +0000 Subject: [PATCH 5/5] Makefile.am: distribute README.DRM.md Co-Authored-By: Claude Sonnet 5.5 --- Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile.am b/Makefile.am index ce6351408..a473ea48e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -625,6 +625,7 @@ EXTRA_DIST = \ $(top_srcdir)/autogen.sh \ $(top_srcdir)/MODULE_LICENSE_FRAUNHOFER \ $(top_srcdir)/NOTICE \ + $(top_srcdir)/README.DRM.md \ $(top_srcdir)/OWNERS \ $(top_srcdir)/Android.bp \ $(top_srcdir)/fdk-aac.def \