* [FFmpeg-devel] [PR] avcodec/eac3enc: add dependent substream encoding for multichannel layouts (PR #21668)
@ 2026-02-06 22:30 ShortKatz via ffmpeg-devel
0 siblings, 0 replies; only message in thread
From: ShortKatz via ffmpeg-devel @ 2026-02-06 22:30 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: ShortKatz
PR #21668 opened by ShortKatz
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21668
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21668.patch
Add support for E-AC-3 channel layouts beyond 5.1 by encoding extra channels (back surround, top height) in a dependent substream. Supported layouts: 7.1, 5.1.2, 5.1.4, 7.1.2.
Add FATE framecrc tests for all four layouts using self-generated samples.
>From f32ba6711cb3929d07ea7df3382281965352cb1b Mon Sep 17 00:00:00 2001
From: Nomis101 <Nomis101@web.de>
Date: Fri, 6 Feb 2026 23:28:33 +0100
Subject: [PATCH] avcodec/eac3enc: add dependent substream encoding for
multichannel layouts
---
libavcodec/ac3enc.c | 830 ++++++++++++++++++++++++++++++-
libavcodec/ac3enc.h | 30 ++
libavcodec/eac3enc.c | 160 +++++-
libavcodec/eac3enc.h | 8 +
tests/fate/ac3.mak | 27 +-
tests/ref/fate/eac3-encode-5.1.2 | 13 +
tests/ref/fate/eac3-encode-5.1.4 | 13 +
tests/ref/fate/eac3-encode-7.1 | 13 +
tests/ref/fate/eac3-encode-7.1.2 | 13 +
9 files changed, 1104 insertions(+), 3 deletions(-)
create mode 100644 tests/ref/fate/eac3-encode-5.1.2
create mode 100644 tests/ref/fate/eac3-encode-5.1.4
create mode 100644 tests/ref/fate/eac3-encode-7.1
create mode 100644 tests/ref/fate/eac3-encode-7.1.2
diff --git a/libavcodec/ac3enc.c b/libavcodec/ac3enc.c
index 5a1a3ab63a..a662084fe2 100644
--- a/libavcodec/ac3enc.c
+++ b/libavcodec/ac3enc.c
@@ -32,6 +32,7 @@
#include "libavutil/avassert.h"
#include "libavutil/channel_layout.h"
#include "libavutil/crc.h"
+#include "libavutil/float_dsp.h"
#include "libavutil/internal.h"
#include "libavutil/mem.h"
#include "libavutil/mem_internal.h"
@@ -1978,11 +1979,652 @@ static void ac3_output_frame(AC3EncodeContext *s, unsigned char *frame)
output_frame_end(s, &pb);
}
+/**
+ * Extract exponents from MDCT coefficients for dependent channels.
+ */
+static void dep_extract_exponents(AC3EncodeContext *s)
+{
+ int ch;
+ int chan_size = AC3_MAX_COEFS * s->num_blocks;
+
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ s->ac3dsp.extract_exponents(s->dep_blocks[0].exp[ch],
+ s->dep_blocks[0].fixed_coef[ch],
+ chan_size);
+ }
+}
+
+/**
+ * Encode exponents for a single channel in the dependent stream.
+ * Applies differential encoding and constrains deltas to [-2, 2].
+ */
+static void dep_encode_exponents_blk_ch(uint8_t *exp, int nb_coefs, int exp_strategy)
+{
+ int nb_groups, i, k;
+
+ /* Multiply by 3 because each grouped value represents 3 individual exponents */
+ nb_groups = exponent_group_tab[0][exp_strategy-1][nb_coefs] * 3;
+
+ /* for D25 and D45 modes, average and expand the exponents */
+ switch (exp_strategy) {
+ case EXP_D25:
+ for (i = 1, k = 1; i <= nb_groups; i++) {
+ uint8_t exp_min = FFMIN(exp[k], exp[k+1]);
+ exp[i] = exp_min;
+ k += 2;
+ }
+ break;
+ case EXP_D45:
+ for (i = 1, k = 1; i <= nb_groups; i++) {
+ uint8_t exp_min = FFMIN(FFMIN(exp[k], exp[k+1]), FFMIN(exp[k+2], exp[k+3]));
+ exp[i] = exp_min;
+ k += 4;
+ }
+ break;
+ }
+
+ /* DC exponent constraint */
+ if (exp[0] > 15)
+ exp[0] = 15;
+
+ /* constrain deltas to [-2, 2] */
+ for (i = 1; i <= nb_groups; i++)
+ exp[i] = FFMIN(exp[i], exp[i-1] + 2);
+ i--;
+ while (--i >= 0)
+ exp[i] = FFMIN(exp[i], exp[i+1] + 2);
+
+ /* expand exponents to decoder values */
+ switch (exp_strategy) {
+ case EXP_D25:
+ for (i = nb_groups, k = (nb_groups * 2); i > 0; i--) {
+ uint8_t exp1 = exp[i];
+ exp[k--] = exp1;
+ exp[k--] = exp1;
+ }
+ break;
+ case EXP_D45:
+ for (i = nb_groups, k = (nb_groups * 4); i > 0; i--) {
+ exp[k] = exp[k-1] = exp[k-2] = exp[k-3] = exp[i];
+ k -= 4;
+ }
+ break;
+ }
+}
+
+/**
+ * Encode exponents for all dependent channels.
+ */
+static void dep_encode_exponents(AC3EncodeContext *s)
+{
+ int blk, blk1, ch;
+ uint8_t *exp;
+ int nb_coefs, num_reuse_blocks;
+
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ exp = s->dep_blocks[0].exp[ch];
+
+ blk = 0;
+ while (blk < s->num_blocks) {
+ AC3Block *block = &s->dep_blocks[blk];
+ nb_coefs = block->end_freq[ch];
+ blk1 = blk + 1;
+
+ /* set reference blocks for EXP_REUSE */
+ s->dep_exp_ref_block[ch][blk] = blk;
+ while (blk1 < s->num_blocks && s->dep_exp_strategy[ch][blk1] == EXP_REUSE) {
+ s->dep_exp_ref_block[ch][blk1] = blk;
+ blk1++;
+ }
+ num_reuse_blocks = blk1 - blk - 1;
+
+ /* for EXP_REUSE, select minimum exponents */
+ s->ac3dsp.ac3_exponent_min(exp, num_reuse_blocks, AC3_MAX_COEFS);
+
+ dep_encode_exponents_blk_ch(exp, nb_coefs, s->dep_exp_strategy[ch][blk]);
+
+ exp += AC3_MAX_COEFS * (num_reuse_blocks + 1);
+ blk = blk1;
+ }
+ }
+}
+
+
+/**
+ * Group exponents for dependent channels.
+ */
+static void dep_group_exponents(AC3EncodeContext *s)
+{
+ int blk, ch, i;
+ int group_size, nb_groups;
+ uint8_t *p;
+ int delta0, delta1, delta2;
+ int exp0, exp1;
+
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ int exp_strategy = s->dep_exp_strategy[ch][blk];
+ AC3Block *block = &s->dep_blocks[blk];
+
+ if (exp_strategy == EXP_REUSE)
+ continue;
+
+ group_size = exp_strategy + (exp_strategy == EXP_D45);
+ nb_groups = exponent_group_tab[0][exp_strategy-1][block->end_freq[ch]];
+ p = block->exp[ch];
+
+ /* DC exponent */
+ exp1 = *p++;
+ block->grouped_exp[ch][0] = exp1;
+
+ /* remaining exponents are delta encoded */
+ for (i = 1; i <= nb_groups; i++) {
+ exp0 = exp1;
+ exp1 = p[0];
+ p += group_size;
+ delta0 = exp1 - exp0 + 2;
+ av_assert2(delta0 >= 0 && delta0 <= 4);
+
+ exp0 = exp1;
+ exp1 = p[0];
+ p += group_size;
+ delta1 = exp1 - exp0 + 2;
+ av_assert2(delta1 >= 0 && delta1 <= 4);
+
+ exp0 = exp1;
+ exp1 = p[0];
+ p += group_size;
+ delta2 = exp1 - exp0 + 2;
+ av_assert2(delta2 >= 0 && delta2 <= 4);
+
+ block->grouped_exp[ch][i] = ((delta0 * 5 + delta1) * 5) + delta2;
+ }
+ }
+ }
+}
+
+/**
+ * Count mantissa bits for dependent channels with given SNR offset.
+ */
+static int dep_count_mantissa_bits(AC3EncodeContext *s, int snr_offset)
+{
+ int blk, ch;
+ LOCAL_ALIGNED_16(uint16_t, mant_cnt, [AC3_MAX_BLOCKS], [16]);
+ int end_freq;
+
+ /* Initialize mantissa counts */
+ for (blk = 0; blk < AC3_MAX_BLOCKS; blk++) {
+ memset(mant_cnt[blk], 0, sizeof(mant_cnt[blk]));
+ mant_cnt[blk][1] = mant_cnt[blk][2] = 2;
+ mant_cnt[blk][4] = 1;
+ }
+
+ /* Calculate bap values and count bits */
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ int ref_blk = s->dep_exp_ref_block[ch][blk];
+ end_freq = block->end_freq[ch];
+
+ /* Calculate bap for this channel/block */
+ s->ac3dsp.bit_alloc_calc_bap(s->dep_blocks[ref_blk].mask[ch],
+ s->dep_blocks[ref_blk].psd[ch],
+ 0, end_freq,
+ snr_offset, s->bit_alloc.floor,
+ ff_ac3_bap_tab,
+ s->dep_ref_bap[ch][blk]);
+
+ /* Update mantissa counts */
+ s->ac3dsp.update_bap_counts(mant_cnt[blk],
+ s->dep_ref_bap[ch][blk],
+ end_freq);
+ }
+ }
+
+ return s->ac3dsp.compute_mantissa_size(mant_cnt);
+}
+
+/**
+ * Compute bit allocation for dependent channels with rate control.
+ * Calculate psd, mask, and bap for each channel, ensuring bits fit in frame.
+ */
+static int dep_compute_bit_allocation(AC3EncodeContext *s)
+{
+ int blk, ch;
+ int snr_offset, snr_incr;
+ int bits_left, mantissa_bits;
+ int header_bits, exponent_bits;
+ int end_freq;
+
+ /* Initialize ref_bap pointers */
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ s->dep_ref_bap[ch][blk] = &s->dep_bap_buffer[AC3_MAX_COEFS * (s->num_blocks * ch + blk)];
+ }
+ }
+
+ end_freq = s->dep_blocks[0].end_freq[0];
+
+ /* Calculate psd and mask for all channels/blocks */
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ if (s->dep_exp_strategy[ch][blk] != EXP_REUSE) {
+ /* Calculate psd */
+ ff_ac3_bit_alloc_calc_psd(block->exp[ch], 0,
+ block->end_freq[ch], block->psd[ch],
+ block->band_psd[ch]);
+
+ /* Calculate mask */
+ ff_ac3_bit_alloc_calc_mask(&s->bit_alloc, block->band_psd[ch],
+ 0, block->end_freq[ch],
+ ff_ac3_fast_gain_tab[s->dep_fast_gain_code[ch]],
+ 0, /* not LFE */
+ DBA_NONE, 0, NULL, NULL, NULL,
+ block->mask[ch]);
+ }
+ }
+ }
+
+ /* Calculate header bits for dependent frame:
+ * sync word (16) + BSI (~80) + per-block overhead (~20 each)
+ * + aux/CRC (18) */
+ header_bits = 114 + 20 * s->num_blocks;
+
+ /* Calculate exponent bits:
+ * DC exp (4 bits) + grouped exponents (7 bits each) per channel per non-reuse block */
+ exponent_bits = 0;
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ if (s->dep_exp_strategy[ch][blk] != EXP_REUSE) {
+ int nb_groups = exponent_group_tab[0][s->dep_exp_strategy[ch][blk]-1][end_freq];
+ exponent_bits += 4 + 7 * nb_groups + 2; /* DC exp + grouped exps + gain range */
+ }
+ }
+ }
+
+ /* Calculate bits available for mantissas */
+ bits_left = s->dependent_frame_size * 8 - header_bits - exponent_bits;
+ if (bits_left < 0)
+ bits_left = 0;
+
+ /* Try bit allocation at max SNR offset first. If it doesn't fit,
+ * reduce bandwidth until it does. This handles the case where even
+ * at maximum SNR offset, the floor term in bap calculation causes
+ * high-energy bins to require too many mantissa bits. */
+ mantissa_bits = dep_count_mantissa_bits(s, (1023 - 240) * 4);
+ while (mantissa_bits > bits_left && end_freq > 76) {
+ /* Reduce bandwidth by 3 (one bandwidth code step) */
+ end_freq -= 3;
+ for (blk = 0; blk < s->num_blocks; blk++)
+ for (ch = 0; ch < s->dependent_channels; ch++)
+ s->dep_blocks[blk].end_freq[ch] = end_freq;
+
+ /* Recalculate exponent bits with new bandwidth */
+ exponent_bits = 0;
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ if (s->dep_exp_strategy[ch][blk] != EXP_REUSE) {
+ int nb_groups = exponent_group_tab[0][s->dep_exp_strategy[ch][blk]-1][end_freq];
+ exponent_bits += 4 + 7 * nb_groups + 2;
+ }
+ }
+ }
+ bits_left = s->dependent_frame_size * 8 - header_bits - exponent_bits;
+ if (bits_left < 0)
+ bits_left = 0;
+
+ /* Recompute psd/mask with reduced bandwidth */
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ if (s->dep_exp_strategy[ch][blk] != EXP_REUSE) {
+ ff_ac3_bit_alloc_calc_psd(block->exp[ch], 0,
+ end_freq, block->psd[ch],
+ block->band_psd[ch]);
+ ff_ac3_bit_alloc_calc_mask(&s->bit_alloc, block->band_psd[ch],
+ 0, end_freq,
+ ff_ac3_fast_gain_tab[s->dep_fast_gain_code[ch]],
+ 0, DBA_NONE, 0, NULL, NULL, NULL,
+ block->mask[ch]);
+ }
+ }
+ }
+
+ mantissa_bits = dep_count_mantissa_bits(s, (1023 - 240) * 4);
+ }
+
+ /* Start SNR search from main stream's coarse offset */
+ snr_offset = s->coarse_snr_offset << 4;
+
+ /* Find lowest SNR offset where bits fit */
+ while (snr_offset <= 1023) {
+ mantissa_bits = dep_count_mantissa_bits(s, (snr_offset - 240) * 4);
+ if (mantissa_bits <= bits_left)
+ break;
+ snr_offset += 64;
+ }
+
+ if (snr_offset > 1023)
+ snr_offset = 1023;
+
+ /* Refine: binary search for lowest offset that fits.
+ * Don't go below 1 because snr_offset=0 converted to (0-240)*4=-960
+ * triggers a special case in bit_alloc_calc_bap that sets all bap=0. */
+ for (snr_incr = 32; snr_incr > 0; snr_incr >>= 1) {
+ while (snr_offset - snr_incr >= 1) {
+ mantissa_bits = dep_count_mantissa_bits(s, (snr_offset - snr_incr - 240) * 4);
+ if (mantissa_bits <= bits_left) {
+ snr_offset -= snr_incr;
+ } else {
+ break;
+ }
+ }
+ }
+
+ /* Final pass: calculate bap values with chosen SNR offset */
+ snr_offset = (snr_offset - 240) * 4;
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ int ref_blk = s->dep_exp_ref_block[ch][blk];
+ s->ac3dsp.bit_alloc_calc_bap(s->dep_blocks[ref_blk].mask[ch],
+ s->dep_blocks[ref_blk].psd[ch],
+ 0, block->end_freq[ch],
+ snr_offset, s->bit_alloc.floor,
+ ff_ac3_bap_tab,
+ s->dep_ref_bap[ch][blk]);
+ }
+ }
+
+ /* Store coarse and fine SNR offsets for header */
+ {
+ int combined = snr_offset / 4 + 240;
+ s->dep_coarse_snr_offset = combined >> 4;
+ s->dep_fine_snr_offset[0] = combined & 0xF;
+ }
+
+ return 0;
+}
+
+/**
+ * Quantize mantissas for dependent channels.
+ */
+static void dep_quantize_mantissas(AC3EncodeContext *s)
+{
+ int blk, ch;
+
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+ AC3Mant m = { 0 };
+
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ int ref_blk = s->dep_exp_ref_block[ch][blk];
+ quantize_mantissas_blk_ch(&m, block->fixed_coef[ch],
+ s->dep_blocks[ref_blk].exp[ch],
+ s->dep_ref_bap[ch][blk], block->qmant[ch],
+ 0, block->end_freq[ch]);
+ }
+ }
+}
+
+/**
+ * Process the dependent substream for extended channel layouts.
+ * This performs full MDCT transformation, exponent encoding, bit allocation,
+ * and mantissa quantization for the extra channels (e.g., back surrounds in 7.1).
+ *
+ * @param s AC-3 encoder private context
+ * @param samples input sample data (all channels)
+ */
+static void process_dependent_substream(AC3EncodeContext *s, uint8_t * const *samples)
+{
+ int blk, ch;
+ const unsigned sampletype_size = SAMPLETYPE_SIZE(s);
+
+ int end_freq = s->bandwidth_code * 3 + 73; /* Same bandwidth as independent stream */
+
+ /* Initialize end_freq for all dependent blocks and channels */
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ s->dep_blocks[blk].end_freq[ch] = end_freq;
+ }
+ }
+
+ /* Set exponent strategies for dependent channels */
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ /* Use EXP_D15 for first block, EXP_REUSE for rest */
+ s->dep_exp_strategy[ch][0] = EXP_D15;
+ for (blk = 1; blk < s->num_blocks; blk++)
+ s->dep_exp_strategy[ch][blk] = EXP_REUSE;
+ }
+
+ /* Apply MDCT to dependent channels
+ * Note: EAC3 is always float, so we use the float path exclusively.
+ * The fdsp is actually AVFloatDSPContext* at runtime for EAC3. */
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ int input_ch_idx = s->channel_map_dep[ch];
+ const uint8_t *input_samples0 = s->dep_samples[ch];
+ const uint8_t *input_samples1 = samples[input_ch_idx];
+
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+ /* Float path - EAC3 dependent streams are always float */
+ float *windowed = s->windowed_samples_float;
+ const float *in0 = (const float *)input_samples0;
+ const float *in1 = (const float *)input_samples1;
+
+ /* Apply window using fdsp (cast to float dsp context) */
+ AVFloatDSPContext *fdsp = (AVFloatDSPContext *)s->fdsp;
+ fdsp->vector_fmul(windowed, in0, s->mdct_window_float, AC3_BLOCK_SIZE);
+ fdsp->vector_fmul_reverse(windowed + AC3_BLOCK_SIZE, in1,
+ s->mdct_window_float, AC3_BLOCK_SIZE);
+
+ /* Apply MDCT */
+ s->tx_fn(s->tx, block->mdct_coef[ch], windowed, sizeof(float));
+
+ /* Advance input pointers */
+ input_samples0 = input_samples1;
+ input_samples1 = input_samples1 + AC3_BLOCK_SIZE * sampletype_size;
+ }
+
+ /* Store last 256 samples for next frame */
+ memcpy(s->dep_samples[ch], input_samples0, AC3_BLOCK_SIZE * sampletype_size);
+ }
+
+ /* Scale float coefficients to fixed-point */
+ if (!s->fixed_point) {
+ int chan_size = AC3_MAX_COEFS * s->num_blocks;
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ s->ac3dsp.float_to_fixed24(s->dep_blocks[0].fixed_coef[ch],
+ (const float *)s->dep_blocks[0].mdct_coef[ch],
+ chan_size);
+ }
+ }
+
+ /* Extract exponents from MDCT coefficients */
+ dep_extract_exponents(s);
+
+ /* Encode exponents */
+ dep_encode_exponents(s);
+
+ /* Compute bit allocation */
+ dep_compute_bit_allocation(s);
+
+ /* Group exponents */
+ dep_group_exponents(s);
+
+ /* Quantize mantissas */
+ dep_quantize_mantissas(s);
+}
+
+/**
+ * Output mantissas for dependent channels in one block.
+ */
+static void dep_output_mantissas(AC3EncodeContext *s, PutBitContext *pb, int blk)
+{
+ int ch, i;
+ AC3Block *block = &s->dep_blocks[blk];
+
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ int end_freq = block->end_freq[ch];
+ uint8_t *bap = s->dep_ref_bap[ch][blk];
+ uint16_t *qmant = block->qmant[ch];
+
+ for (i = 0; i < end_freq; i++) {
+ int b = bap[i];
+ int q = qmant[i];
+
+ switch (b) {
+ case 0:
+ break;
+ case 1:
+ /* 3 values grouped in 5 bits, 128 means already output */
+ if (q != 128)
+ put_bits(pb, 5, q);
+ break;
+ case 2:
+ /* 3 values grouped in 7 bits */
+ if (q != 128)
+ put_bits(pb, 7, q);
+ break;
+ case 3:
+ /* 3-bit signed mantissa */
+ put_sbits(pb, 3, q);
+ break;
+ case 4:
+ /* 2 values grouped in 7 bits */
+ if (q != 128)
+ put_bits(pb, 7, q);
+ break;
+ case 14:
+ /* 14-bit signed mantissa */
+ put_sbits(pb, 14, q);
+ break;
+ case 15:
+ /* 16-bit signed mantissa */
+ put_sbits(pb, 16, q);
+ break;
+ default:
+ /* bap 5-13: (bap-1) bit signed mantissa */
+ put_sbits(pb, b - 1, q);
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Encode and output the dependent substream for extended channel layouts.
+ * This outputs a fully encoded E-AC-3 dependent frame with real audio data.
+ */
+static void eac3_output_dependent_frame(AC3EncodeContext *s, unsigned char *frame)
+{
+ PutBitContext pb;
+ int blk, ch, i;
+ const AVCRC *crc_ctx = av_crc_get_table(AV_CRC_16_ANSI);
+ int pad_bytes;
+ uint16_t crc2;
+
+ init_put_bits(&pb, frame, s->dependent_frame_size);
+
+ /* Output dependent frame header */
+ ff_eac3_output_dep_frame_header(s, &pb);
+
+ /* Output audio blocks for dependent stream */
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+
+ /* dynamic range - 1 bit for stereo (channel_mode != 0) */
+ put_bits(&pb, 1, 0);
+
+ /* spectral extension - for E-AC3 block 0, output spx_in_use directly */
+ put_bits(&pb, 1, 0);
+
+ /* No coupling in dependent stream - already signaled in header */
+
+ /* stereo rematrixing - required for stereo mode (acmod=2) */
+ if (s->dependent_channels == 2) {
+ if (blk == 0) {
+ /* Block 0: output rematrixing flags (4 bands, no strategy bit) */
+ put_bits(&pb, 1, 0); /* band 0 rematrixing flag */
+ put_bits(&pb, 1, 0); /* band 1 rematrixing flag */
+ put_bits(&pb, 1, 0); /* band 2 rematrixing flag */
+ put_bits(&pb, 1, 0); /* band 3 rematrixing flag */
+ } else {
+ /* Blocks 1-5: output new_rematrixing_strategy bit */
+ put_bits(&pb, 1, 0); /* no new rematrixing strategy */
+ }
+ }
+
+ /* bandwidth - only when not reusing exponents */
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ if (s->dep_exp_strategy[ch][blk] != EXP_REUSE) {
+ /* Calculate bandwidth code from end_freq */
+ int bw_code = (block->end_freq[ch] - 73) / 3;
+ bw_code = av_clip(bw_code, 0, 60);
+ put_bits(&pb, 6, bw_code);
+ }
+ }
+
+ /* exponents */
+ for (ch = 0; ch < s->dependent_channels; ch++) {
+ int nb_groups;
+ int exp_strategy = s->dep_exp_strategy[ch][blk];
+
+ if (exp_strategy == EXP_REUSE)
+ continue;
+
+ /* DC exponent */
+ put_bits(&pb, 4, block->grouped_exp[ch][0]);
+
+ /* exponent groups */
+ nb_groups = exponent_group_tab[0][exp_strategy-1][block->end_freq[ch]];
+ for (i = 1; i <= nb_groups; i++)
+ put_bits(&pb, 7, block->grouped_exp[ch][i]);
+
+ /* gain range info */
+ put_bits(&pb, 2, 0);
+ }
+
+ /* Note: E-AC-3 to AC-3 converter SNR offset is NOT output for dependent frames.
+ * It's only read for independent frames (frame_type == 0). */
+
+ /* mantissas */
+ dep_output_mantissas(s, &pb, blk);
+ }
+
+ /* auxiliary data */
+ put_bits(&pb, 1, 0);
+
+ /* CRC */
+ put_bits(&pb, 1, 0); /* crcrsv */
+
+ /* Pad and compute CRC */
+ flush_put_bits(&pb);
+
+ pad_bytes = s->dependent_frame_size - (put_bits_ptr(&pb) - frame) - 2;
+ if (pad_bytes < 0) {
+ av_log(s->avctx, AV_LOG_WARNING, "Dependent frame overflow by %d bytes\n", -pad_bytes);
+ pad_bytes = 0;
+ }
+ if (pad_bytes > 0)
+ memset(put_bits_ptr(&pb), 0, pad_bytes);
+
+ /* compute crc2 for E-AC-3 */
+ crc2 = av_bswap16(av_crc(crc_ctx, 0, frame + 2, s->dependent_frame_size - 4));
+ if (crc2 == 0x0B77) {
+ frame[s->dependent_frame_size - 3] ^= 0x1;
+ crc2 ^= 0x8005;
+ }
+ AV_WB16(frame + s->dependent_frame_size - 2, crc2);
+}
+
int ff_ac3_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
const AVFrame *frame, int *got_packet_ptr)
{
AC3EncodeContext *const s = avctx->priv_data;
int ret;
+ int total_frame_size;
if (s->options.allow_per_frame_metadata) {
ret = ac3_validate_metadata(s);
@@ -1993,6 +2635,37 @@ int ff_ac3_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
if (s->bit_alloc.sr_code == 1 || s->eac3)
ac3_adjust_frame_size(s);
+ /* For extended channel layouts, calculate dependent frame size */
+ if (s->eac3_dependent_enabled) {
+ /* Dependent stream bitrate scales with number of extra channels
+ * Use ~96kbps per dependent channel for full audio encoding */
+ int dep_bitrate = s->dependent_channels * 96000;
+ int frame_samples = AC3_BLOCK_SIZE * s->num_blocks;
+ int min_frame_size;
+ int end_freq = s->bandwidth_code * 3 + 73;
+
+ s->dependent_frame_size = ((dep_bitrate / 16) * frame_samples / s->sample_rate) * 2;
+
+ /* Minimum frame size accounting for real audio encoding:
+ * - Header: ~80 bytes
+ * - Per block: exponents (~end_freq/3 * 7 bits) + mantissas (~end_freq * 6 bits avg)
+ * - 6 blocks, per channel */
+ min_frame_size = 80 + (s->num_blocks * end_freq * s->dependent_channels * 8 / 8);
+ if (s->dependent_frame_size < min_frame_size)
+ s->dependent_frame_size = min_frame_size;
+
+ /* E-AC-3 max frame size is 4096 bytes (frmsiz is 11 bits: (4096/2-1) = 2047)
+ * Cap at 3840 to leave some margin */
+ if (s->dependent_frame_size > 3840)
+ s->dependent_frame_size = 3840;
+
+ /* Ensure frame size is even and word-aligned */
+ s->dependent_frame_size = (s->dependent_frame_size + 3) & ~3;
+ total_frame_size = s->frame_size + s->dependent_frame_size;
+ } else {
+ total_frame_size = s->frame_size;
+ }
+
s->encode_frame(s, frame->extended_data);
ac3_apply_rematrixing(s);
@@ -2009,11 +2682,21 @@ int ff_ac3_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
ac3_quantize_mantissas(s);
- ret = ff_get_encode_buffer(avctx, avpkt, s->frame_size, 0);
+ /* For extended channel layouts, process the dependent stream with full audio encoding */
+ if (s->eac3_dependent_enabled)
+ process_dependent_substream(s, frame->extended_data);
+
+ ret = ff_get_encode_buffer(avctx, avpkt, total_frame_size, 0);
if (ret < 0)
return ret;
+
+ /* Output independent frame */
ac3_output_frame(s, avpkt->data);
+ /* Output dependent frame mode */
+ if (s->eac3_dependent_enabled)
+ eac3_output_dependent_frame(s, avpkt->data + s->frame_size);
+
if (frame->pts != AV_NOPTS_VALUE)
avpkt->pts = frame->pts - ff_samples_to_time_base(avctx, avctx->initial_padding);
@@ -2170,12 +2853,50 @@ av_cold int ff_ac3_encode_close(AVCodecContext *avctx)
av_freep(&s->cpl_coord_buffer);
av_freep(&s->fdsp);
+ /* Free dependent substream buffers */
+ if (s->eac3_dependent_enabled) {
+ for (int ch = 0; ch < s->dependent_channels; ch++)
+ av_freep(&s->dep_samples[ch]);
+ av_freep(&s->dep_bap_buffer);
+ av_freep(&s->dep_mdct_coef_buffer);
+ av_freep(&s->dep_fixed_coef_buffer);
+ av_freep(&s->dep_exp_buffer);
+ av_freep(&s->dep_grouped_exp_buffer);
+ av_freep(&s->dep_psd_buffer);
+ av_freep(&s->dep_band_psd_buffer);
+ av_freep(&s->dep_mask_buffer);
+ av_freep(&s->dep_qmant_buffer);
+ }
+
av_tx_uninit(&s->tx);
return 0;
}
+/**
+ * E-AC-3 dependent substream channel definitions.
+ * Maps channel mask bits to E-AC-3 custom channel map bit positions.
+ * The custom channel map is a 16-bit field where bits indicate extra channels.
+ */
+static const struct {
+ uint64_t av_mask; ///< FFmpeg channel mask
+ int map_bit; ///< E-AC-3 custom channel map bit position (0-15, from MSB)
+ int num_channels; ///< Number of channels this entry represents
+} eac3_dep_channel_map[] = {
+ { AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER, 5, 2 },
+ { AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT, 6, 2 },
+ { AV_CH_BACK_CENTER, 7, 1 },
+ { AV_CH_TOP_CENTER, 8, 1 },
+ { AV_CH_SURROUND_DIRECT_LEFT | AV_CH_SURROUND_DIRECT_RIGHT, 9, 2 },
+ { AV_CH_WIDE_LEFT | AV_CH_WIDE_RIGHT, 10, 2 },
+ { AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT, 11, 2 },
+ { AV_CH_TOP_FRONT_CENTER, 12, 1 },
+ { AV_CH_TOP_BACK_LEFT | AV_CH_TOP_BACK_RIGHT, 13, 2 },
+ { AV_CH_LOW_FREQUENCY_2, 14, 1 },
+ { 0, 0, 0 } /* terminator */
+};
+
/*
* Set channel information during initialization.
*/
@@ -2185,6 +2906,49 @@ static av_cold void set_channel_info(AVCodecContext *avctx)
uint64_t mask = av_channel_layout_subset(&avctx->ch_layout, ~(uint64_t)0);
int channels = avctx->ch_layout.nb_channels;
+ /* Base 5.1 channel mask (what goes in independent stream) */
+ const uint64_t base_mask = AV_CH_FRONT_LEFT | AV_CH_FRONT_CENTER | AV_CH_FRONT_RIGHT |
+ AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT | AV_CH_LOW_FREQUENCY;
+
+ /* Initialize dependent substream settings */
+ s->eac3_dependent_enabled = 0;
+ s->dependent_channel_map = 0;
+ s->dependent_channels = 0;
+ s->input_channels = channels;
+
+ /* Check for channels beyond 5.1 - requires E-AC-3 */
+ if (s->eac3 && channels > 6) {
+ uint64_t dep_mask = mask & ~base_mask;
+ int dep_ch_idx = 0;
+
+ /* Check each possible dependent channel type */
+ for (int i = 0; eac3_dep_channel_map[i].av_mask != 0; i++) {
+ uint64_t ch_mask = eac3_dep_channel_map[i].av_mask;
+ if ((dep_mask & ch_mask) == ch_mask) {
+ /* This channel group is present - add to dependent stream */
+ s->dependent_channel_map |= (1 << (15 - eac3_dep_channel_map[i].map_bit));
+
+ /* Find input channel indices for this channel group */
+ for (int ch = 0; ch < avctx->ch_layout.nb_channels && dep_ch_idx < 16; ch++) {
+ enum AVChannel channel = av_channel_layout_channel_from_index(&avctx->ch_layout, ch);
+ uint64_t ch_bit = (channel != AV_CHAN_NONE && channel < 64) ? (1ULL << channel) : 0;
+ if (ch_bit && (ch_mask & ch_bit)) {
+ s->channel_map_dep[dep_ch_idx++] = ch;
+ }
+ }
+
+ /* Remove from main mask */
+ mask &= ~ch_mask;
+ }
+ }
+
+ if (dep_ch_idx > 0) {
+ s->eac3_dependent_enabled = 1;
+ s->dependent_channels = dep_ch_idx;
+ channels = avctx->ch_layout.nb_channels - dep_ch_idx;
+ }
+ }
+
s->lfe_on = !!(mask & AV_CH_LOW_FREQUENCY);
s->channels = channels;
s->fbw_channels = channels - s->lfe_on;
@@ -2234,6 +2998,9 @@ static av_cold int validate_options(AC3EncodeContext *s)
case 4: avctx->bit_rate = 384000; break;
case 5: avctx->bit_rate = 448000; break;
}
+ /* Add extra bitrate for dependent stream (~64kbps per channel) */
+ if (s->eac3_dependent_enabled)
+ avctx->bit_rate += s->dependent_channels * 64000;
}
/* validate bit rate */
@@ -2465,6 +3232,67 @@ static av_cold int allocate_buffers(AC3EncodeContext *s)
}
}
+ /* Allocate buffers for dependent substream (7.1, 5.1.2, 5.1.4, 7.1.2, etc.) */
+ if (s->eac3_dependent_enabled) {
+ int dep_channels = s->dependent_channels;
+ int dep_channel_blocks = dep_channels * s->num_blocks;
+ int dep_total_coefs = AC3_MAX_COEFS * dep_channel_blocks;
+
+ /* Sample buffers for dependent channels */
+ for (ch = 0; ch < dep_channels; ch++) {
+ s->dep_samples[ch] = av_mallocz((AC3_FRAME_SIZE + AC3_BLOCK_SIZE) * sampletype_size);
+ if (!s->dep_samples[ch])
+ return AVERROR(ENOMEM);
+ }
+
+ if (!FF_ALLOC_TYPED_ARRAY(s->dep_bap_buffer, dep_total_coefs) ||
+ !FF_ALLOCZ_TYPED_ARRAY(s->dep_mdct_coef_buffer, dep_total_coefs) ||
+ !FF_ALLOC_TYPED_ARRAY(s->dep_exp_buffer, dep_total_coefs) ||
+ !FF_ALLOC_TYPED_ARRAY(s->dep_grouped_exp_buffer, dep_channel_blocks * 128) ||
+ !FF_ALLOC_TYPED_ARRAY(s->dep_psd_buffer, dep_total_coefs) ||
+ !FF_ALLOC_TYPED_ARRAY(s->dep_band_psd_buffer, dep_channel_blocks * 64) ||
+ !FF_ALLOC_TYPED_ARRAY(s->dep_mask_buffer, dep_channel_blocks * 64) ||
+ !FF_ALLOC_TYPED_ARRAY(s->dep_qmant_buffer, dep_total_coefs))
+ return AVERROR(ENOMEM);
+
+ if (!s->fixed_point) {
+ if (!FF_ALLOCZ_TYPED_ARRAY(s->dep_fixed_coef_buffer, dep_total_coefs))
+ return AVERROR(ENOMEM);
+ }
+
+ /* Initialize dependent block pointers */
+ for (blk = 0; blk < s->num_blocks; blk++) {
+ AC3Block *block = &s->dep_blocks[blk];
+
+ for (ch = 0; ch < dep_channels; ch++) {
+ /* arrangement: block, channel, coeff */
+ block->grouped_exp[ch] = &s->dep_grouped_exp_buffer[128 * (blk * dep_channels + ch)];
+ block->psd[ch] = &s->dep_psd_buffer [AC3_MAX_COEFS * (blk * dep_channels + ch)];
+ block->band_psd[ch] = &s->dep_band_psd_buffer [64 * (blk * dep_channels + ch)];
+ block->mask[ch] = &s->dep_mask_buffer [64 * (blk * dep_channels + ch)];
+ block->qmant[ch] = &s->dep_qmant_buffer [AC3_MAX_COEFS * (blk * dep_channels + ch)];
+
+ /* arrangement: channel, block, coeff */
+ block->exp[ch] = &s->dep_exp_buffer [AC3_MAX_COEFS * (s->num_blocks * ch + blk)];
+ block->mdct_coef[ch] = &s->dep_mdct_coef_buffer [AC3_MAX_COEFS * (s->num_blocks * ch + blk)];
+ if (s->fixed_point)
+ block->fixed_coef[ch] = (int32_t *)block->mdct_coef[ch];
+ else
+ block->fixed_coef[ch] = &s->dep_fixed_coef_buffer[AC3_MAX_COEFS * (s->num_blocks * ch + blk)];
+
+ /* Set bandwidth for dependent channels - use same as main stream */
+ block->end_freq[ch] = s->bandwidth_code * 3 + 73;
+ }
+ }
+
+ /* Initialize SNR offsets for dependent stream */
+ s->dep_coarse_snr_offset = 40;
+ for (ch = 0; ch < dep_channels; ch++) {
+ s->dep_fast_gain_code[ch] = 4;
+ s->dep_fine_snr_offset[ch] = 0;
+ }
+ }
+
return 0;
}
diff --git a/libavcodec/ac3enc.h b/libavcodec/ac3enc.h
index 5e98ad188b..8c9cc9d0bd 100644
--- a/libavcodec/ac3enc.h
+++ b/libavcodec/ac3enc.h
@@ -266,9 +266,39 @@ typedef struct AC3EncodeContext {
DECLARE_ALIGNED(32, float, windowed_samples_float)[AC3_WINDOW_SIZE];
DECLARE_ALIGNED(32, int32_t, windowed_samples_fixed)[AC3_WINDOW_SIZE];
};
+
+ /* E-AC-3 dependent substream support (7.1, 5.1.2, 5.1.4, 7.1.2, etc.) */
+ int eac3_dependent_enabled; ///< dependent substream enabled
+ uint16_t dependent_channel_map; ///< 16-bit custom channel map for dependent
+ int dependent_channels; ///< number of channels in dependent stream
+ int dependent_frame_size; ///< frame size for dependent substream
+
+ /* Channel mapping for dependent substream */
+ int input_channels; ///< total channels from input
+ uint8_t channel_map_dep[16]; ///< input channel indices for dependent stream
+
+ /* Separate buffers for dependent substream */
+ uint8_t *dep_samples[16]; ///< sample buffers for dependent channels
+ CoefType *dep_mdct_coef_buffer;
+ int32_t *dep_fixed_coef_buffer;
+ uint8_t *dep_exp_buffer;
+ uint8_t *dep_grouped_exp_buffer;
+ uint8_t *dep_bap_buffer;
+ int16_t *dep_psd_buffer;
+ int16_t *dep_band_psd_buffer;
+ int16_t *dep_mask_buffer;
+ uint16_t *dep_qmant_buffer;
+ AC3Block dep_blocks[AC3_MAX_BLOCKS]; ///< blocks for dependent substream
+ uint8_t dep_exp_strategy[16][AC3_MAX_BLOCKS]; ///< exponent strategies for dependent
+ uint8_t dep_exp_ref_block[16][AC3_MAX_BLOCKS]; ///< reference blocks for EXP_REUSE
+ uint8_t *dep_ref_bap[16][AC3_MAX_BLOCKS]; ///< bit allocation pointers for dependent
+ int dep_coarse_snr_offset; ///< coarse SNR offset for dependent
+ int dep_fine_snr_offset[16]; ///< fine SNR offsets for dependent
+ int dep_fast_gain_code[16]; ///< fast gain codes for dependent
} AC3EncodeContext;
extern const AVChannelLayout ff_ac3_ch_layouts[19];
+extern const AVChannelLayout ff_eac3_ch_layouts[];
extern const AVOption ff_ac3_enc_options[];
extern const AVClass ff_ac3enc_class;
extern const FFCodecDefault ff_ac3_enc_defaults[];
diff --git a/libavcodec/eac3enc.c b/libavcodec/eac3enc.c
index 10b1ab337c..537109115c 100644
--- a/libavcodec/eac3enc.c
+++ b/libavcodec/eac3enc.c
@@ -27,6 +27,7 @@
#define AC3ENC_FLOAT 1
#include "libavutil/attributes.h"
+#include "libavutil/channel_layout.h"
#include "libavutil/thread.h"
#include "ac3enc.h"
#include "codec_internal.h"
@@ -42,6 +43,63 @@ static const AVClass eac3enc_class = {
.version = LIBAVUTIL_VERSION_INT,
};
+/**
+ * List of supported channel layouts for E-AC-3.
+ * Includes extended layouts (7.1, 5.1.2, etc.) not supported by AC-3.
+ * These use the dependent substream mechanism for extra channels.
+ * Layouts with more than 5 dependent channels (e.g., 7.1.4) would require
+ * multiple dependent substreams and are not yet supported.
+ */
+const AVChannelLayout ff_eac3_ch_layouts[] = {
+ AV_CHANNEL_LAYOUT_MONO,
+ AV_CHANNEL_LAYOUT_STEREO,
+ AV_CHANNEL_LAYOUT_2_1,
+ AV_CHANNEL_LAYOUT_SURROUND,
+ AV_CHANNEL_LAYOUT_2_2,
+ AV_CHANNEL_LAYOUT_QUAD,
+ AV_CHANNEL_LAYOUT_4POINT0,
+ AV_CHANNEL_LAYOUT_5POINT0,
+ AV_CHANNEL_LAYOUT_5POINT0_BACK,
+ {
+ .nb_channels = 2,
+ .order = AV_CHANNEL_ORDER_NATIVE,
+ .u.mask = AV_CH_LAYOUT_MONO | AV_CH_LOW_FREQUENCY,
+ },
+ {
+ .nb_channels = 3,
+ .order = AV_CHANNEL_ORDER_NATIVE,
+ .u.mask = AV_CH_LAYOUT_STEREO | AV_CH_LOW_FREQUENCY,
+ },
+ {
+ .nb_channels = 4,
+ .order = AV_CHANNEL_ORDER_NATIVE,
+ .u.mask = AV_CH_LAYOUT_2_1 | AV_CH_LOW_FREQUENCY,
+ },
+ {
+ .nb_channels = 4,
+ .order = AV_CHANNEL_ORDER_NATIVE,
+ .u.mask = AV_CH_LAYOUT_SURROUND | AV_CH_LOW_FREQUENCY,
+ },
+ {
+ .nb_channels = 5,
+ .order = AV_CHANNEL_ORDER_NATIVE,
+ .u.mask = AV_CH_LAYOUT_4POINT0 | AV_CH_LOW_FREQUENCY,
+ },
+ AV_CHANNEL_LAYOUT_5POINT1,
+ AV_CHANNEL_LAYOUT_5POINT1_BACK,
+ /* E-AC-3 extended layouts using dependent substream */
+ AV_CHANNEL_LAYOUT_7POINT1, /* 5.1 + Lb/Rb */
+ AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK,/* 5.1.2 - 5.1 + TBL/TBR */
+ { /* 5.1.2 with top front instead of top back */
+ .nb_channels = 8,
+ .order = AV_CHANNEL_ORDER_NATIVE,
+ .u.mask = AV_CH_LAYOUT_5POINT1 | AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT,
+ },
+ AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK,/* 5.1.4 - 5.1 + TFL/TFR + TBL/TBR */
+ AV_CHANNEL_LAYOUT_7POINT1POINT2, /* 7.1.2 - 7.1 + TFL/TFR */
+ { 0 },
+};
+
/**
* LUT for finding a matching frame exponent strategy index from a set of
* exponent strategies for a single channel across all 6 blocks.
@@ -127,6 +185,106 @@ void ff_eac3_set_cpl_states(AC3EncodeContext *s)
}
}
+
+/**
+ * Get the acmod value for the dependent substream based on channel count.
+ * The dependent stream uses standard acmod values for its channel configuration.
+ */
+static int get_dep_acmod(int dep_channels)
+{
+ switch (dep_channels) {
+ case 1: return AC3_CHMODE_MONO; /* 1/0 */
+ case 2: return AC3_CHMODE_STEREO; /* 2/0 */
+ case 3: return AC3_CHMODE_3F; /* 3/0 */
+ case 4: return AC3_CHMODE_2F2R; /* 2/2 */
+ case 5: return AC3_CHMODE_3F2R; /* 3/2 */
+ default: return AC3_CHMODE_STEREO; /* fallback to stereo */
+ }
+}
+
+/**
+ * Write the E-AC-3 dependent substream frame header to the output bitstream.
+ * Used for extended channel layouts (7.1, 5.1.2, 5.1.4, 7.1.2, etc.) where extra
+ * channels are encoded in a dependent substream.
+ */
+void ff_eac3_output_dep_frame_header(AC3EncodeContext *s, PutBitContext *pb)
+{
+ int blk, ch;
+ AC3EncOptions *opt = &s->options;
+ int dep_acmod = get_dep_acmod(s->dependent_channels);
+ int frmsiz = (s->dependent_frame_size / 2) - 1;
+
+ put_bits_assume_flushed(pb);
+
+ put_bits(pb, 16, 0x0b77); /* sync word */
+
+ /* BSI header for dependent substream */
+ put_bits(pb, 2, 1); /* stream type = dependent */
+ put_bits(pb, 3, 0); /* substream id = 0 */
+ put_bits(pb, 11, frmsiz); /* frame size */
+ put_bits(pb, 2, s->bit_alloc.sr_code); /* sample rate code */
+ put_bits(pb, 2, s->num_blks_code); /* number of blocks */
+ put_bits(pb, 3, dep_acmod); /* acmod based on dep channels */
+ put_bits(pb, 1, 0); /* LFE off in dependent */
+ put_bits(pb, 5, s->bitstream_id); /* bitstream id (16 for EAC3) */
+ put_bits(pb, 5, -opt->dialogue_level); /* dialogue normalization */
+ put_bits(pb, 1, 0); /* no compression gain */
+
+ /* Custom channel map - critical for dependent streams */
+ put_bits(pb, 1, 1); /* channel map exists */
+ put_bits(pb, 16, s->dependent_channel_map); /* 16-bit channel map */
+
+ /* No mixing metadata for dependent stream */
+ put_bits(pb, 1, 0);
+
+ /* No info metadata for dependent stream */
+ put_bits(pb, 1, 0);
+
+ /* Note: converter sync flag is NOT output for dependent frames.
+ * It's only used for independent frames with num_blocks != 6. */
+
+ put_bits(pb, 1, 0); /* no additional bit stream info */
+
+ /* Frame header */
+ if (s->num_blocks == 6) {
+ put_bits(pb, 1, 1); /* use block exponent strategy */
+ put_bits(pb, 1, 0); /* aht enabled = no */
+ }
+ put_bits(pb, 2, 0); /* snr offset strategy = 1 */
+ put_bits(pb, 1, 0); /* transient pre-noise processing enabled = no */
+ put_bits(pb, 1, 0); /* block switch syntax enabled = no */
+ put_bits(pb, 1, 0); /* dither flag syntax enabled = no */
+ put_bits(pb, 1, 0); /* bit allocation model syntax enabled = no */
+ put_bits(pb, 1, 0); /* fast gain codes enabled = no */
+ put_bits(pb, 1, 0); /* dba syntax enabled = no */
+ put_bits(pb, 1, 0); /* skip field syntax enabled = no */
+ put_bits(pb, 1, 0); /* spx enabled = no */
+
+ /* Coupling strategy - no coupling for dependent stream */
+ if (dep_acmod > AC3_CHMODE_MONO) {
+ put_bits(pb, 1, 0); /* blk 0: coupling not in use */
+ for (blk = 1; blk < s->num_blocks; blk++)
+ put_bits(pb, 1, 0); /* blk N: no new coupling strategy */
+ }
+
+ /* Exponent strategy - block-based: EXP_D15 for blk0, EXP_REUSE for rest */
+ for (blk = 0; blk < s->num_blocks; blk++)
+ for (ch = 0; ch < s->dependent_channels; ch++)
+ put_bits(pb, 2, s->dep_exp_strategy[ch][blk]);
+
+ /* E-AC-3 to AC-3 converter exponent strategy - NOT used for dependent frames
+ * The decoder only reads this for independent frames (frame_type == 0).
+ * For dependent frames (frame_type == 1), skip this entirely. */
+
+ /* SNR offsets */
+ put_bits(pb, 6, s->dep_coarse_snr_offset);
+ put_bits(pb, 4, s->dep_fine_snr_offset[0]);
+
+ /* Block start info */
+ if (s->num_blocks > 1)
+ put_bits(pb, 1, 0);
+}
+
/**
* Write the E-AC-3 frame header to the output bitstream.
*/
@@ -278,7 +436,7 @@ const FFCodec ff_eac3_encoder = {
CODEC_SAMPLEFMTS(AV_SAMPLE_FMT_FLTP),
.p.priv_class = &eac3enc_class,
CODEC_SAMPLERATES_ARRAY(ff_ac3_sample_rate_tab),
- CODEC_CH_LAYOUTS_ARRAY(ff_ac3_ch_layouts),
+ CODEC_CH_LAYOUTS_ARRAY(ff_eac3_ch_layouts),
.defaults = ff_ac3_enc_defaults,
.caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
};
diff --git a/libavcodec/eac3enc.h b/libavcodec/eac3enc.h
index 0523de411b..490dd8b7c6 100644
--- a/libavcodec/eac3enc.h
+++ b/libavcodec/eac3enc.h
@@ -29,6 +29,8 @@
#include "ac3enc.h"
+struct PutBitContext;
+
/**
* Determine frame exponent strategy use and indices.
*/
@@ -41,4 +43,10 @@ void ff_eac3_get_frame_exp_strategy(AC3EncodeContext *s);
*/
void ff_eac3_set_cpl_states(AC3EncodeContext *s);
+/**
+ * Write the E-AC-3 dependent substream frame header.
+ * Used for encodings where extra channels are in a dependent substream.
+ */
+void ff_eac3_output_dep_frame_header(AC3EncodeContext *s, struct PutBitContext *pb);
+
#endif /* AVCODEC_EAC3ENC_H */
diff --git a/tests/fate/ac3.mak b/tests/fate/ac3.mak
index 9f655c3ff4..0b64fd5476 100644
--- a/tests/fate/ac3.mak
+++ b/tests/fate/ac3.mak
@@ -105,4 +105,29 @@ fate-eac3-core-bsf: REF = b704bf851e99b7442e9bed368b60e6ca
FATE_SAMPLES_AVCONV += $(FATE_AC3-yes) $(FATE_EAC3-yes)
-fate-ac3: $(FATE_AC3-yes) $(FATE_EAC3-yes)
+# E-AC-3 dependent substream encoding tests.
+# These test encoding multichannel layouts that use dependent substreams
+# for channels beyond 5.1. Self-generated samples, no external SAMPLES needed.
+FATE_EAC3_DEP_ENC-$(call FRAMECRC, WAV, PCM_S16LE, ARESAMPLE_FILTER EAC3_ENCODER) += fate-eac3-encode-7.1
+fate-eac3-encode-7.1: tests/data/asynth-44100-8.wav
+fate-eac3-encode-7.1: SRC = $(TARGET_PATH)/tests/data/asynth-44100-8.wav
+fate-eac3-encode-7.1: CMD = framecrc -i $(SRC) -c:a eac3 -b:a 640k -frames:a 6 -af aresample
+
+FATE_EAC3_DEP_ENC-$(call FRAMECRC, WAV, PCM_S16LE, ARESAMPLE_FILTER EAC3_ENCODER) += fate-eac3-encode-5.1.2
+fate-eac3-encode-5.1.2: tests/data/asynth-44100-8.wav
+fate-eac3-encode-5.1.2: SRC = $(TARGET_PATH)/tests/data/asynth-44100-8.wav
+fate-eac3-encode-5.1.2: CMD = framecrc -i $(SRC) -c:a eac3 -b:a 640k -frames:a 6 -af "aresample=ochl=5.1.2"
+
+FATE_EAC3_DEP_ENC-$(call FRAMECRC, WAV, PCM_S16LE, ARESAMPLE_FILTER EAC3_ENCODER) += fate-eac3-encode-5.1.4
+fate-eac3-encode-5.1.4: tests/data/asynth-44100-10.wav
+fate-eac3-encode-5.1.4: SRC = $(TARGET_PATH)/tests/data/asynth-44100-10.wav
+fate-eac3-encode-5.1.4: CMD = framecrc -i $(SRC) -c:a eac3 -b:a 640k -frames:a 6 -af "aresample=ochl=5.1.4"
+
+FATE_EAC3_DEP_ENC-$(call FRAMECRC, WAV, PCM_S16LE, ARESAMPLE_FILTER EAC3_ENCODER) += fate-eac3-encode-7.1.2
+fate-eac3-encode-7.1.2: tests/data/asynth-44100-10.wav
+fate-eac3-encode-7.1.2: SRC = $(TARGET_PATH)/tests/data/asynth-44100-10.wav
+fate-eac3-encode-7.1.2: CMD = framecrc -i $(SRC) -c:a eac3 -b:a 640k -frames:a 6 -af "aresample=ochl=7.1.2"
+
+FATE_AVCONV += $(FATE_EAC3_DEP_ENC-yes)
+
+fate-ac3: $(FATE_AC3-yes) $(FATE_EAC3-yes) $(FATE_EAC3_DEP_ENC-yes)
diff --git a/tests/ref/fate/eac3-encode-5.1.2 b/tests/ref/fate/eac3-encode-5.1.2
new file mode 100644
index 0000000000..3e11bf9d82
--- /dev/null
+++ b/tests/ref/fate/eac3-encode-5.1.2
@@ -0,0 +1,13 @@
+#tb 0: 1/44100
+#media_type 0: audio
+#codec_id 0: eac3
+#sample_rate 0: 44100
+#channel_layout_name 0: 5.1.2
+0, -256, -256, 1536, 5758, 0x6d4d644b
+0, 1280, 1280, 1536, 5760, 0x165f9d38
+0, 2816, 2816, 1536, 5758, 0x5164aecf
+0, 4352, 4352, 1536, 5758, 0x5983c3e0
+0, 5888, 5888, 1536, 5758, 0x94a16fe0
+0, 7424, 7424, 1536, 5758, 0x67806895
+0, 8960, 8960, 1536, 5760, 0x9eb75cd6
+0, 10496, 10496, 1536, 5758, 0x5ac6a122
diff --git a/tests/ref/fate/eac3-encode-5.1.4 b/tests/ref/fate/eac3-encode-5.1.4
new file mode 100644
index 0000000000..b83f1d0332
--- /dev/null
+++ b/tests/ref/fate/eac3-encode-5.1.4
@@ -0,0 +1,13 @@
+#tb 0: 1/44100
+#media_type 0: audio
+#codec_id 0: eac3
+#sample_rate 0: 44100
+#channel_layout_name 0: 5.1.4
+0, -256, -256, 1536, 6626, 0xdefa60a6
+0, 1280, 1280, 1536, 6628, 0xf50f8daa
+0, 2816, 2816, 1536, 6626, 0x4d50af63
+0, 4352, 4352, 1536, 6626, 0x1ff9a2c4
+0, 5888, 5888, 1536, 6626, 0x524a57d7
+0, 7424, 7424, 1536, 6626, 0xbb3c8e9a
+0, 8960, 8960, 1536, 6628, 0x52f5588d
+0, 10496, 10496, 1536, 6626, 0x99abae61
diff --git a/tests/ref/fate/eac3-encode-7.1 b/tests/ref/fate/eac3-encode-7.1
new file mode 100644
index 0000000000..6da16cbd25
--- /dev/null
+++ b/tests/ref/fate/eac3-encode-7.1
@@ -0,0 +1,13 @@
+#tb 0: 1/44100
+#media_type 0: audio
+#codec_id 0: eac3
+#sample_rate 0: 44100
+#channel_layout_name 0: 7.1
+0, -256, -256, 1536, 5758, 0x87ae4e00
+0, 1280, 1280, 1536, 5760, 0x11378061
+0, 2816, 2816, 1536, 5758, 0xb5fea1d0
+0, 4352, 4352, 1536, 5758, 0xaa44938c
+0, 5888, 5888, 1536, 5758, 0x2f434897
+0, 7424, 7424, 1536, 5758, 0x9eaf81d5
+0, 8960, 8960, 1536, 5760, 0xd4644744
+0, 10496, 10496, 1536, 5758, 0x3e5e9a7d
diff --git a/tests/ref/fate/eac3-encode-7.1.2 b/tests/ref/fate/eac3-encode-7.1.2
new file mode 100644
index 0000000000..7dfae38187
--- /dev/null
+++ b/tests/ref/fate/eac3-encode-7.1.2
@@ -0,0 +1,13 @@
+#tb 0: 1/44100
+#media_type 0: audio
+#codec_id 0: eac3
+#sample_rate 0: 44100
+#channel_layout_name 0: 7.1.2
+0, -256, -256, 1536, 6626, 0xd968dead
+0, 1280, 1280, 1536, 6628, 0x6e301b19
+0, 2816, 2816, 1536, 6626, 0xd075ef71
+0, 4352, 4352, 1536, 6626, 0x90ed1ec3
+0, 5888, 5888, 1536, 6626, 0x175be815
+0, 7424, 7424, 1536, 6626, 0xe9b0e572
+0, 8960, 8960, 1536, 6628, 0x3911082a
+0, 10496, 10496, 1536, 6626, 0x6bcc0375
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-02-06 22:31 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-06 22:30 [FFmpeg-devel] [PR] avcodec/eac3enc: add dependent substream encoding for multichannel layouts (PR #21668) ShortKatz via ffmpeg-devel
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git