[PR] Dolby E : add encoder, SMPTE 337M muxer, Dolby E carriage in WAV/MPEG-TS/spdif (PR #24060)

OrenJackMaurice via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178628728322.59.12257611879345774696@29965ddac10e>
PR #24060 opened by OrenJackMaurice
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24060
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24060.patch

Sorry for the messy PR last time. I'm entirely new to this, and figured someone may actually want to use what was created (yes yes, with an LLM entirely).
So, here's what I've changed this time - 
Same feature set as before, rebased onto current master and cleaned up per review:

  - Fixed the per-transform av_malloc/av_free (fixed-size stack buffers now - I wonder about the fingers comment, 2048 fingers, or counting in Binary (still requires 11 fingers...)
  - Retitled the "shared helpers" commit -- it's a plain file split; nothing is actually shared until the next commit's encoder reuses the same functions.
  - Removed the internal-process comments (spec-doc references, "Gate N", etc).
  - Dropped the self-added MAINTAINERS entry, figured that's what you've meant, presumptuous etc ?


>From 2e43b367d5bbb9f7d264bf2ac31f73b8a931ed8a Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 11:59:12 +0300
Subject: [PATCH 1/8] avcodec/dolby_e: split bit allocation and transform code
 out of dolby_e.c

Move the exponent/mantissa tables, bit-allocation model, window table and
MDCT/IMDCT transform out of dolby_e.c and into new dolby_e_bitalloc.{c,h}
and dolby_e_transform.{c,h}, and give ff_dolby_e_mdct_calc_fold() a fixed
scratch buffer instead of allocating one per call.

No functional change to the decoder: this is a pure move, plus a forward
MDCT and MDCT-with-fold path that the decoder itself never calls. The
bit allocator and windowed transform are symmetric between encode and
decode (the allocator runs identically in both directions, and Dolby E's
own spec requires an identical analysis and synthesis window per
US 5,899,969), so the encoder added in the next commit reuses these
functions directly rather than duplicating them.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 libavcodec/Makefile            |   2 +-
 libavcodec/dolby_e.c           | 728 +--------------------------------
 libavcodec/dolby_e_bitalloc.c  | 492 ++++++++++++++++++++++
 libavcodec/dolby_e_bitalloc.h  |  76 ++++
 libavcodec/dolby_e_transform.c | 328 +++++++++++++++
 libavcodec/dolby_e_transform.h |  82 ++++
 6 files changed, 994 insertions(+), 714 deletions(-)
 create mode 100644 libavcodec/dolby_e_bitalloc.c
 create mode 100644 libavcodec/dolby_e_bitalloc.h
 create mode 100644 libavcodec/dolby_e_transform.c
 create mode 100644 libavcodec/dolby_e_transform.h

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index e464811af6..0efe7898af 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -343,7 +343,7 @@ OBJS-$(CONFIG_DFPWM_DECODER)           += dfpwmdec.o
 OBJS-$(CONFIG_DFPWM_ENCODER)           += dfpwmenc.o
 OBJS-$(CONFIG_DNXHD_DECODER)           += dnxhddec.o dnxhddata.o
 OBJS-$(CONFIG_DNXHD_ENCODER)           += dnxhdenc.o dnxhddata.o
-OBJS-$(CONFIG_DOLBY_E_DECODER)         += dolby_e.o dolby_e_parse.o kbdwin.o
+OBJS-$(CONFIG_DOLBY_E_DECODER)         += dolby_e.o dolby_e_bitalloc.o dolby_e_transform.o dolby_e_parse.o kbdwin.o
 OBJS-$(CONFIG_DPX_DECODER)             += dpx.o
 OBJS-$(CONFIG_DPX_ENCODER)             += dpxenc.o
 OBJS-$(CONFIG_DSD_LSBF_DECODER)        += dsddec.o dsd.o
diff --git a/libavcodec/dolby_e.c b/libavcodec/dolby_e.c
index 6a2432ff1b..f6ae93ce99 100644
--- a/libavcodec/dolby_e.c
+++ b/libavcodec/dolby_e.c
@@ -24,13 +24,13 @@
 #include "libavutil/mem.h"
 #include "libavutil/mem_internal.h"
 #include "libavutil/opt.h"
-#include "libavutil/tx.h"
 
 #include "codec_internal.h"
 #include "decode.h"
 #include "get_bits.h"
 #include "dolby_e.h"
-#include "kbdwin.h"
+#include "dolby_e_bitalloc.h"
+#include "dolby_e_transform.h"
 
 #define MAX_SEGMENTS    2
 
@@ -46,20 +46,6 @@ enum DBEOutputChannelOrder {
     CHANNEL_ORDER_CODED,
 };
 
-typedef struct DBEGroup {
-    uint8_t         nb_exponent;
-    uint8_t         nb_bias_exp[MAX_MSTR_EXP];
-    uint16_t        exp_ofs;
-    uint16_t        mnt_ofs;
-    const uint8_t   *nb_mantissa;
-    uint8_t         imdct_idx;
-    uint8_t         imdct_phs;
-    uint16_t        win_len;
-    uint16_t        dst_ofs;
-    uint16_t        win_ofs;
-    uint16_t        src_ofs;
-} DBEGroup;
-
 typedef struct DBEChannel {
     int     gr_code;
     int     bw_code;
@@ -85,531 +71,21 @@ typedef struct DBEDecodeContext {
 
     DECLARE_ALIGNED(32, float, history)[MAX_CHANNELS][256];
 
-    AVTXContext         *imdct[2][3];
-    av_tx_fn             imdct_fn[2][3];
+    DBETransform         transform;
     AVFloatDSPContext   *fdsp;
 } DBEDecodeContext;
 
-static const int8_t lfe_channel_tab[MAX_PROG_CONF + 1] = {
-     5,  5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4,
-    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  5, 5
-};
-
 static const uint8_t ch_reorder_4[4] = { 0, 2, 1, 3 };
 static const uint8_t ch_reorder_6[6] = { 0, 2, 4, 1, 3, 5 };
 static const uint8_t ch_reorder_8[8] = { 0, 2, 6, 4, 1, 3, 7, 5 };
 static const uint8_t ch_reorder_n[8] = { 0, 2, 4, 6, 1, 3, 5, 7 };
 
-
-static const uint8_t nb_groups_tab[4] = { 1, 8, 7, 1 };
-
-static const uint8_t nb_mstr_exp_tab[4] = { 2, 2, 2, 1 };
-
-static const uint8_t nb_mantissa_38[38] = {
-     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
-     2,  2,  2,  2,  2,  2,  3,  3,  3,  4,  4,  4,  5,  5,  6,  6,
-     7,  8,  9, 10, 11, 12,
-};
-
-static const uint8_t nb_mantissa_44[44] = {
-     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,
-     2,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  5,  5,  6,  7,  7,
-     8,  9, 10, 11, 12, 13, 15, 16, 18, 20, 22, 25,
-};
-
-static const uint8_t nb_mantissa_50[50] = {
-     1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,
-     3,  4,  4,  5,  5,  6,  6,  7,  8,  9,  9, 10, 12, 13, 14, 16,
-    18, 19, 22, 24, 27, 29, 32, 36, 40, 44, 49, 54, 60, 66, 74, 82,
-    90, 100,
-};
-
-static const uint8_t imdct_bits_tab[3] = { 8, 9, 11 };
-
-static const DBEGroup grp_tab_0[1] = {
-    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
-};
-
-static const DBEGroup grp_tab_1[8] = {
-    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
-    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
-    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
-    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 704, 0, 0 },
-    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 1, 256, 832, 0, 0 },
-};
-
-static const DBEGroup grp_tab_2[7] = {
-    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
-    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
-    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
-    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 44, { 19, 25 }, 228, 768, nb_mantissa_44, 1, 1, 448, 704, 960, 64 },
-};
-
-static const DBEGroup grp_tab_3[1] = {
-    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
-};
-
-static const DBEGroup grp_tab_4[1] = {
-    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
-};
-
-static const DBEGroup grp_tab_5[8] = {
-    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 1, 256, 64, 0, 0 },
-    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 192, 0, 0 },
-    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
-    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
-    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
-};
-
-static const DBEGroup grp_tab_6[7] = {
-    { 44, { 19, 25 }, 0, 0, nb_mantissa_44, 1, 1, 448, 0, 3264, 0 },
-    { 38, { 12, 26 }, 44, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
-    { 38, { 12, 26 }, 82, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
-    { 38, { 12, 26 }, 120, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
-    { 38, { 12, 26 }, 158, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
-    { 38, { 12, 26 }, 196, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
-    { 38, { 12, 26 }, 234, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
-};
-
-static const DBEGroup grp_tab_7[1] = {
-    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
-};
-
-static const DBEGroup *const frm_ofs_tab[2][4] = {
-    { grp_tab_0, grp_tab_1, grp_tab_2, grp_tab_3 },
-    { grp_tab_4, grp_tab_5, grp_tab_6, grp_tab_7 }
-};
-
-static const uint8_t mantissa_size1[16][4] = {
-    {  0,  0,  0,  0 }, {  2,  1,  1,  1 }, {  3,  2,  1,  1 }, {  4,  3,  2,  1 },
-    {  5,  4,  3,  2 }, {  6,  5,  4,  3 }, {  7,  6,  5,  4 }, {  8,  7,  6,  5 },
-    {  9,  8,  7,  6 }, { 10,  9,  8,  7 }, { 11, 10,  9,  8 }, { 12, 11, 10,  9 },
-    { 13, 12, 11, 10 }, { 14, 13, 12, 11 }, { 15, 14, 13, 12 }, { 16, 15, 14, 13 },
-};
-
-static const uint8_t mantissa_size2[16][4] = {
-    {  0,  0,  0,  0 }, {  2,  1,  2,  2 }, {  3,  2,  3,  3 }, {  4,  3,  4,  4 },
-    {  5,  4,  5,  5 }, {  6,  5,  6,  6 }, {  7,  6,  7,  7 }, {  8,  7,  8,  8 },
-    {  9,  8,  9,  9 }, { 10,  9, 10, 10 }, { 11, 10, 11, 11 }, { 12, 11, 12, 12 },
-    { 13, 12, 13, 13 }, { 14, 13, 14, 14 }, { 15, 14, 15, 15 }, { 16, 15, 16, 16 },
-};
-
-static const float start_window[192] = {
-    0.00161569379826, 0.00185748233347, 0.00198562758548, 0.00207834078104,
-    0.00215717748523, 0.00223067096393, 0.00230299213147, 0.00237651215396,
-    0.00245275561606, 0.00253281402069, 0.00261754673613, 0.00270768786168,
-    0.00280390761895, 0.00290684998656, 0.00301715751161, 0.00313548872798,
-    0.00326253122934, 0.00339901215995, 0.00354570716636, 0.00370344845023,
-    0.00387313232586, 0.00405572653911, 0.00425227750970, 0.00446391759265,
-    0.00469187240551, 0.00493746822816, 0.00520213944619, 0.00548743597507,
-    0.00579503056737, 0.00612672586953, 0.00648446105606, 0.00687031782873,
-    0.00728652552677, 0.00773546505205, 0.00821967127415, 0.00874183354619,
-    0.00930479393832, 0.00991154278653, 0.01056521116692, 0.01126905994567,
-    0.01202646513050, 0.01284089936559, 0.01371590957417, 0.01465509096066,
-    0.01566205783408, 0.01674041199523, 0.01789370972358, 0.01912542867865,
-    0.02043893626265, 0.02183746113793, 0.02332406961796, 0.02490164852364,
-    0.02657289580178, 0.02834031974193, 0.03020624702903, 0.03217283918354,
-    0.03424211623810, 0.03641598586180, 0.03869627565015, 0.04108476601498,
-    0.04358322107390, 0.04619341515939, 0.04891715301882, 0.05175628239149,
-
-    0.05471237327267, 0.05778734733755, 0.06098291402413, 0.06430101352084,
-    0.06774345212186, 0.07131188644726, 0.07500780649199, 0.07883251748595,
-    0.08278712056651, 0.08687249228061, 0.09108926295730, 0.09543779401074,
-    0.09991815425851, 0.10453009536427, 0.10927302653894, 0.11414598865987,
-    0.11914762799220, 0.12427616972097, 0.12952939152560, 0.13490459744934,
-    0.14039859233595, 0.14600765712201, 0.15172752528722, 0.15755336077528,
-    0.16347973770491, 0.16950062219342, 0.17560935661442, 0.18179864660619,
-    0.18806055113821, 0.19438647593012, 0.20076717050010, 0.20719272909882,
-    0.21365259576030, 0.22013557367283, 0.22662983904194, 0.23312295958328,
-    0.23960191774666, 0.24605313873388, 0.25246252333253, 0.25881548554631,
-    0.26509699495987, 0.27129162373316, 0.27738359807707, 0.28335685401987,
-    0.28919509723179, 0.29488186663467, 0.30040060148455, 0.30573471157819,
-    0.31086765019993, 0.31578298939317, 0.32046449711227, 0.32489621578468,
-    0.32906254179156, 0.33294830535654, 0.33653885031840, 0.33982011325336,
-    0.34277870140679, 0.34540196889300, 0.34767809062480, 0.34959613344194,
-    0.35114612391958, 0.35231911235422, 0.35310723244504, 0.35350375621308,
-
-    0.35350314372945, 0.35310108725579, 0.35229454943591, 0.35108179521634,
-    0.34946241721522, 0.34743735430290, 0.34500890320420, 0.34218072298001,
-    0.33895783229541, 0.33534659943168, 0.33135472505060, 0.32699121776996,
-    0.32226636266000, 0.31719168282019, 0.31177989424432, 0.30604485422875,
-    0.30000150362379, 0.29366580327088, 0.28705466500775, 0.28018587766131,
-    0.27307802848095, 0.26575042049535, 0.25822298630189, 0.25051619882000,
-    0.24265097955783, 0.23464860495522, 0.22653061137548, 0.21831869932335,
-    0.21003463746705, 0.20170016703857, 0.19333690717811, 0.18496626177620,
-    0.17660932835062, 0.16828680947474, 0.16001892724986, 0.15182534128597,
-    0.14372507062477, 0.13573642000364, 0.12787691082233, 0.12016321713317,
-    0.11261110693234, 0.10523538898282, 0.09804986534955, 0.09106728977263,
-    0.08429933194438, 0.07775654768810, 0.07144835495683, 0.06538301547324,
-    0.05956762170687, 0.05400808871425, 0.04870915012107, 0.04367435714993,
-    0.03890607899172, 0.03440550179663, 0.03017262174627, 0.02620622428513,
-    0.02250383492507, 0.01906161305732, 0.01587412848221, 0.01293388032354,
-    0.01023019677288, 0.00774641320626, 0.00545109736891, 0.00325868651263,
-};
-
-static const float short_window2[192] = {
-    0.00018861094606, 0.00033433010202, 0.00050309624485, 0.00070306161748,
-    0.00093995174533, 0.00121913067128, 0.00154606505568, 0.00192647806126,
-    0.00236641248692, 0.00287225985240, 0.00345077377440, 0.00410907465023,
-    0.00485464855241, 0.00569534163219, 0.00663935063508, 0.00769520981249,
-    0.00887177436246, 0.01017820046395, 0.01162392194150, 0.01321862359335,
-    0.01497221122468, 0.01689477844427, 0.01899657030441, 0.02128794388846,
-    0.02377932597692, 0.02648116795039, 0.02940389811590, 0.03255787167130,
-    0.03595331854986, 0.03960028941437, 0.04350860009563, 0.04768777479454,
-    0.05214698838949, 0.05689500821121, 0.06194013566525, 0.06729014809766,
-    0.07295224131210, 0.07893297315602, 0.08523820859989, 0.09187306673620,
-    0.09884187012422, 0.10614809690222, 0.11379433608064, 0.12178224641797,
-    0.13011251926531, 0.13878484574660, 0.14779788861830, 0.15714925912610,
-    0.16683549914631, 0.17685206886673, 0.18719334022589, 0.19785259629099,
-    0.20882203671372, 0.22009278936030, 0.23165492816694, 0.24349749722585,
-    0.25560854105961, 0.26797514099368, 0.28058345748882, 0.29341877824732,
-    0.30646557185942, 0.31970754671026, 0.33312771482295, 0.34670846027024,
-
-    0.36043161174692, 0.37427851885723, 0.38823013163645, 0.40226708279486,
-    0.41636977214436, 0.43051845264462, 0.44469331748632, 0.45887458761470,
-    0.47304259908636, 0.48717788964798, 0.50126128392546, 0.51527397661778,
-    0.52919761310050, 0.54301436685998, 0.55670701320069, 0.57025899869448,
-    0.58365450587230, 0.59687851269542, 0.60991684638414, 0.62275623122793,
-    0.63538433005035, 0.64778977905593, 0.65996221584264, 0.67189230042379,
-    0.68357172916486, 0.69499324160511, 0.70615062019861, 0.71703868307548,
-    0.72765326998919, 0.73799122168099, 0.74805035295521, 0.75782941981995,
-    0.76732808110520, 0.77654685502339, 0.78548707118622, 0.79415081863423,
-    0.80254089047207, 0.81066072573188, 0.81851434910893, 0.82610630922734,
-    0.83344161609862, 0.84052567843230, 0.84736424144524, 0.85396332579459,
-    0.86032916822973, 0.86646816451999, 0.87238681516918, 0.87809167437532,
-    0.88358930263537, 0.88888622333073, 0.89398888356256, 0.89890361943564,
-    0.90363662591861, 0.90819393133744, 0.91258137648979, 0.91680459830070,
-    0.92086901787718, 0.92477983276087, 0.92854201312583, 0.93216030163834,
-    0.93563921662343, 0.93898305819384, 0.94219591693690, 0.94528168477979,
-
-    0.94823843319821, 0.95106834367330, 0.95377776558539, 0.95636718335775,
-    0.95883679961479, 0.96118650212341, 0.96341583179195, 0.96552395212906,
-    0.96750962060547, 0.96937116231768, 0.97110644638309, 0.97271286544154,
-    0.97418731862798, 0.97552619834964, 0.97672538116257, 0.97778022299974,
-    0.97868555895586, 0.97943570778357, 0.98002448120255, 0.98044519806866,
-    0.98069070339493, 0.98075339216123, 0.98062523779637, 0.98029782516478,
-    0.97976238784222, 0.97900984942031, 0.97803086854002, 0.97681588731895,
-    0.97535518280755, 0.97363892108474, 0.97165721358452, 0.96940017523145,
-    0.96685798395452, 0.96402094114589, 0.96087953263194, 0.95742448973047,
-    0.95364684997699, 0.94953801711660, 0.94508981997396, 0.94029456983253,
-    0.93514511597504, 0.92963489905951, 0.92375800202883, 0.91750919827624,
-    0.91088399681406, 0.90387868421832, 0.89649036314692, 0.88871698725397,
-    0.88055739234735, 0.87201132366062, 0.86307945913336, 0.85376342861693,
-    0.84406582894455, 0.83399023482637, 0.82354120554757, 0.81272428745995,
-    0.80154601230457, 0.79001389138101, 0.77813640562199, 0.76592299164227,
-    0.75338402384395, 0.74053079267526, 0.72737547915460, 0.71393112578527,
-};
-
-static const float short_window3[64] = {
-    0.00326887936450, 0.00550242900936, 0.00786846643791, 0.01045683453520,
-    0.01330402120132, 0.01643221072863, 0.01985798040609, 0.02359509464766,
-    0.02765559221954, 0.03205025893128, 0.03678884369614, 0.04188015679495,
-    0.04733210987781, 0.05315172583924, 0.05934513287609, 0.06591755045290,
-    0.07287327156378, 0.08021564389822, 0.08794705152307, 0.09606889811179,
-    0.10458159240070, 0.11348453632940, 0.12277611617809, 0.13245369691511,
-    0.14251361989876, 0.15295120402567, 0.16376075037904, 0.17493555039885,
-    0.18646789757072, 0.19834910260891, 0.21056951208995, 0.22311853047787,
-    0.23598464546683, 0.24915545655419, 0.26261770674500, 0.27635731727778,
-    0.29035942525136, 0.30460842402318, 0.31908800624032, 0.33378120935681,
-    0.34867046348260, 0.36373764140285, 0.37896411059909, 0.39433078709788,
-    0.40981819096657, 0.42540650327031, 0.44107562429959, 0.45680523287270,
-    0.47257484651351, 0.48836388230077, 0.50415171818214, 0.51991775454258,
-    0.53564147581496, 0.55130251191887, 0.56688069931047, 0.58235614142007,
-    0.59770926827271, 0.61292089506118, 0.62797227945823, 0.64284517745255,
-    0.65752189749349, 0.67198535273209, 0.68621911114984, 0.70020744337099,
-};
-
-static const uint8_t dc_code_tab[5] = { 0, 0, 0, 1, 1 };
-
-static const uint8_t ht_code_tab[5] = { 0, 0, 1, 2, 2 };
-
-static const uint8_t band_ofs_tab[3][4] = {
-    { 12, 8, 4, 0 }, { 14, 10, 6, 0 }, { 12, 8, 4, 0 }
-};
-
-static const uint8_t band_low_tab[3] = { 9, 17, 24 };
-
-static const uint16_t fast_gain_tab[8] = {
-    128, 256, 384, 512, 640, 768, 896, 1024
-};
-
-static const uint16_t slow_decay_tab[2][2] = { { 27, -1 }, { 32, 21 } };
-
-static const uint16_t misc_decay_tab[3][2][2] = {
-    { { 354, -1 }, { 425, 425 } },
-    { { 266, -1 }, { 320,  -1 } },
-    { { 213, -1 }, { 256,  -1 } }
-};
-
-static const uint16_t fast_decay_tab[3][2][2][50] = {
-    {{{
-        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
-        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
-        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
-        142, 142, 142, 142, 142, 142, 142, 142,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-    }}, {{
-        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
-        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
-        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
-        170, 170, 170, 170, 170, 170, 170, 170,
-    }, {
-         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
-         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
-         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
-         64,  64,  64,  64,  64,  64,  64,  64,
-    }}}, {{{
-        266, 266, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,
-    }}, {{
-        319, 319, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,
-    }}}, {{{
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-    }}, {{
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-    }, {
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
-    }}}
-};
-
-static const uint16_t fast_gain_adj_tab[3][2][62] = {
-    {{
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   1,   2,   4,   7,  11,  16,  29,  44,  59,
-         76,  94, 116, 142, 179, 221, 252, 285, 312, 334,
-    }, {
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          2,   5,   8,  10,  15,  28,  42,  57,  75,  93,
-        115, 140, 177, 219, 247, 280, 308, 330, 427, 533,
-    }}, {{
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   2,   5,   8,  12,  21,  35,  51,  69,  89,
-        111, 138, 176, 220, 251, 284, 312, 334,
-    }, {
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   2,
-          5,   8,  11,  18,  33,  49,  65,  84, 106, 132,
-        168, 214, 245, 279, 308, 329, 427, 533,
-    }}, {{
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   1,   4,   7,  10,  17,
-         31,  47,  65,  84, 107, 134, 171, 215, 250, 283,
-        312, 334,
-    }, {
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
-          0,   0,   0,   0,   3,   6,   9,  13,  27,  43,
-         60,  79, 100, 126, 160, 207, 242, 276, 307, 329,
-        427, 533,
-    }}
-};
-
-static const uint16_t slow_gain_tab[3][2][50] = {
-    {{
-        3072, 3072, 3072, 3072, 3072, 3072, 1063, 1063, 1063, 1063,
-        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
-        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
-        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
-    }, {
-        3072, 3072, 3072, 3072, 3072, 3072,  850,  850,  850,  850,
-         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
-         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
-         850,  850,  850,  850,  850,  850,  850,  850,
-    }}, {{
-        3072, 1212, 1212, 1212,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,
-    }, {
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,
-    }}, {{
-        3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
-    }, {
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
-    }}
-};
-
-static const uint16_t hearing_thresh_tab[3][3][50] = {
-    {{
-        1403, 1141, 1000,  959,  948,  957,  946,  925,  899,  871,
-         843,  815,  789,  766,  745,  727,  705,  687,  681,  686,
-         701,  725,  768,  854,  940, 1018, 1075, 1103, 1111, 1106,
-        1098, 1105, 1142, 1237, 1419, 1721, 2169, 2805,
-    }, {
-        1401, 1130,  995,  957,  947,  955,  941,  918,  890,  861,
-         831,  803,  777,  754,  734,  717,  698,  684,  682,  692,
-         712,  743,  798,  894,  976, 1045, 1091, 1109, 1110, 1102,
-        1098, 1116, 1174, 1300, 1526, 1884, 2401, 3072,
-    }, {
-        1393, 1086,  974,  949,  957,  941,  913,  878,  843,  808,
-         777,  750,  727,  708,  695,  686,  681,  689,  714,  752,
-         811,  888,  971, 1044, 1087, 1108, 1110, 1102, 1098, 1115,
-        1172, 1290, 1489, 1812, 2293, 2964, 3072, 3072,
-    }}, {{
-        1412, 1343, 1141, 1047, 1000,  974,  959,  951,  948,  947,
-         957,  953,  946,  936,  925,  906,  878,  850,  822,  795,
-         771,  745,  719,  700,  687,  681,  685,  701,  733,  784,
-         885,  977, 1047, 1092, 1110, 1108, 1099, 1102, 1138, 1233,
-        1413, 1711, 2157, 2797,
-    }, {
-        1412, 1336, 1130, 1040,  995,  970,  957,  950,  947,  947,
-         955,  950,  941,  930,  918,  897,  868,  838,  810,  783,
-         759,  734,  710,  693,  684,  681,  690,  712,  752,  823,
-         924, 1009, 1069, 1102, 1111, 1104, 1098, 1111, 1168, 1295,
-        1518, 1873, 2388, 3072,
-    }, {
-        1411, 1293, 1086, 1009,  974,  957,  949,  947,  957,  951,
-         941,  928,  913,  896,  878,  852,  817,  785,  756,  732,
-         713,  695,  683,  682,  689,  710,  746,  811,  906,  992,
-        1061, 1099, 1111, 1106, 1098, 1107, 1155, 1266, 1471, 1799,
-        2277, 2945, 3072, 3072,
-    }}, {{
-        1431, 1412, 1403, 1379, 1343, 1293, 1229, 1180, 1125, 1075,
-        1040, 1014,  996,  979,  965,  957,  951,  948,  947,  957,
-         951,  940,  924,  903,  877,  846,  815,  785,  753,  725,
-         702,  686,  681,  689,  714,  760,  847,  947, 1028, 1083,
-        1108, 1109, 1101, 1100, 1132, 1222, 1402, 1705, 2160, 2803,
-    }, {
-        1431, 1412, 1401, 1375, 1336, 1278, 1215, 1168, 1115, 1066,
-        1032, 1008,  991,  975,  962,  954,  950,  947,  947,  955,
-         948,  935,  916,  894,  866,  835,  803,  772,  742,  715,
-         695,  683,  683,  697,  729,  784,  887,  982, 1054, 1096,
-        1111, 1106, 1098, 1107, 1159, 1281, 1505, 1865, 2391, 3072,
-    }, {
-        1427, 1411, 1393, 1353, 1293, 1215, 1160, 1118, 1072, 1031,
-        1003,  984,  971,  960,  952,  948,  947,  957,  952,  941,
-         924,  902,  876,  847,  815,  781,  750,  723,  700,  685,
-         681,  691,  719,  766,  858,  958, 1039, 1089, 1109, 1108,
-        1099, 1102, 1141, 1245, 1442, 1766, 2250, 2930, 3072, 3072,
-    }}
-};
-
-static const int16_t lwc_gain_tab[11][7] = {
-    {   -21,  -197,  -271,  -466, 32767, 32767, 32767 },
-    {  -197,   -29,  -244,  -271,  -540, 32767, 32767 },
-    {  -271,  -244,   -29,  -249,  -271,  -593, 32767 },
-    {  -466,  -271,  -249,   -29,  -251,  -271,  -632 },
-    {  -540,  -271,  -251,   -29,  -251,  -271,  -664 },
-    {  -593,  -271,  -251,   -29,  -252,  -271,  -690 },
-    {  -632,  -271,  -252,   -29,  -252,  -271,  -711 },
-    {  -664,  -271,  -252,   -29,  -252,  -271,  -730 },
-    {  -690,  -271,  -252,   -29,  -252,  -271,  -745 },
-    {  -711,  -271,  -252,   -29,  -253,  -271,  -759 },
-    {  -730,  -271,  -253,   -29,  -253,  -271,  -771 },
-};
-
-static const int16_t lwc_adj_tab[7] = {
-    -192, -320, -448, -512, -448, -320, -192,
-};
-
-static const uint8_t log_add_tab[212] = {
-    64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 52, 51, 50,
-    49, 48, 47, 47, 46, 45, 44, 44, 43, 42, 41, 41, 40, 39, 38, 38,
-    37, 36, 36, 35, 35, 34, 33, 33, 32, 32, 31, 30, 30, 29, 29, 28,
-    28, 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 21,
-    20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15,
-    15, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11,
-    10, 10, 10, 10, 10,  9,  9,  9,  9,  9,  8,  8,  8,  8,  8,  8,
-     7,  7,  7,  7,  7,  7,  6,  6,  6,  6,  6,  6,  6,  6,  5,  5,
-     5,  5,  5,  5,  5,  5,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
-     4,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  2,
-     2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
-     2,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
-     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
-     1,  1,  0,  0,
-};
-
-static const uint8_t bap_tab[64] = {
-     0,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,  3,  4,
-     4,  4,  4,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,
-     8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 11, 11, 11, 11, 12,
-    12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15,
-};
-
 static float mantissa_tab1[17][4];
 static float mantissa_tab2[17][4];
 static float mantissa_tab3[17][4];
 static float exponent_tab[50];
 static float gain_tab[1024];
 
-DECLARE_ALIGNED(32, static float, window)[3712];
 
 static int skip_input(DBEContext *s, int nb_words)
 {
@@ -679,94 +155,6 @@ static int parse_exponents(DBEContext *s, DBEChannel *c)
     return 0;
 }
 
-static inline int log_add(int a, int b)
-{
-    int c = FFABS(a - b) >> 1;
-    return FFMAX(a, b) + log_add_tab[FFMIN(c, 211)];
-}
-
-static void calc_lowcomp(int *msk_val)
-{
-    int lwc_val[17] = { 0 };
-    int i, j, k;
-
-    for (i = 0; i < 11; i++) {
-        int max_j = 0;
-        int max_v = INT_MIN;
-        int thr   = 0;
-
-        for (j = FFMAX(i - 3, 0), k = 0; j <= i + 3; j++, k++) {
-            int v = msk_val[j] + lwc_gain_tab[i][k];
-            if (v > max_v) {
-                max_j = j;
-                max_v = v;
-            }
-            thr = log_add(thr, v);
-        }
-
-        if (msk_val[i] < thr) {
-            for (j = FFMAX(max_j - 3, 0),
-                 k = FFMAX(3 - max_j, 0);
-                 j <= max_j + 3; j++, k++)
-                lwc_val[j] += lwc_adj_tab[k];
-        }
-    }
-
-    for (i = 0; i < 16; i++) {
-        int v = FFMAX(lwc_val[i], -512);
-        msk_val[i] = FFMAX(msk_val[i] + v, 0);
-    }
-}
-
-static void bit_allocate(int nb_exponent, int nb_code, int fr_code,
-                         int *exp, int *bap,
-                         int fg_spc, int fg_ofs, int msk_mod, int snr_ofs)
-{
-    int msk_val[MAX_BIAS_EXP];
-    int psd_val[MAX_BIAS_EXP];
-    int fast_leak  = 0;
-    int slow_leak  = 0;
-    int dc_code    = dc_code_tab[fr_code - 1];
-    int ht_code    = ht_code_tab[fr_code - 1];
-    int fast_gain  = fast_gain_tab[fg_ofs];
-    int slow_decay = slow_decay_tab[dc_code][msk_mod];
-    int misc_decay = misc_decay_tab[nb_code][dc_code][msk_mod];
-    const uint16_t *slow_gain      = slow_gain_tab[nb_code][msk_mod];
-    const uint16_t *fast_decay     = fast_decay_tab[nb_code][dc_code][msk_mod];
-    const uint16_t *fast_gain_adj  = fast_gain_adj_tab[nb_code][dc_code];
-    const uint16_t *hearing_thresh = hearing_thresh_tab[nb_code][ht_code];
-    int i;
-
-    for (i = 0; i < nb_exponent; i++)
-        psd_val[i] = (48 - exp[i]) * 64;
-
-    fast_gain_adj += band_ofs_tab[nb_code][fg_spc];
-    for (i = 0; i < nb_exponent; i++) {
-        fast_leak = log_add(fast_leak  - fast_decay[i],
-                            psd_val[i] - fast_gain + fast_gain_adj[i]);
-        slow_leak = log_add(slow_leak  - slow_decay,
-                            psd_val[i] - slow_gain[i]);
-        msk_val[i] = FFMAX(fast_leak, slow_leak);
-    }
-
-    fast_leak = 0;
-    for (i = nb_exponent - 1; i > band_low_tab[nb_code]; i--) {
-        fast_leak = log_add(fast_leak - misc_decay, psd_val[i] - fast_gain);
-        msk_val[i] = FFMAX(msk_val[i], fast_leak);
-    }
-
-    for (i = 0; i < nb_exponent; i++)
-        msk_val[i] = FFMAX(msk_val[i], hearing_thresh[i]);
-
-    if (!nb_code)
-        calc_lowcomp(msk_val);
-
-    for (i = 0; i < nb_exponent; i++) {
-        int v = 16 * (snr_ofs - 64) + psd_val[i] - msk_val[i] >> 5;
-        bap[i] = bap_tab[av_clip_uintp2(v, 6)];
-    }
-}
-
 static int parse_bit_alloc(DBEDecodeContext *s1, DBEChannel *c)
 {
     DBEContext *s = &s1->dectx;
@@ -801,7 +189,7 @@ static int parse_bit_alloc(DBEDecodeContext *s1, DBEChannel *c)
 
     for (i = 0, p = NULL, g = c->groups; i < c->nb_groups; i++, p = g, g++) {
         if (c->exp_strategy[i] || bap_strategy[i]) {
-            bit_allocate(g->nb_exponent, g->imdct_idx, s->metadata.fr_code,
+            ff_dolby_e_bit_allocate(g->nb_exponent, g->imdct_idx, s->metadata.fr_code,
                          c->exponents + g->exp_ofs, c->bap + g->exp_ofs,
                          fg_spc[i], fg_ofs[i], msk_mod[i], snr_ofs);
         } else {
@@ -857,7 +245,7 @@ static int parse_mantissas(DBEContext *s, DBEChannel *c)
         for (j = 0; j < g->nb_exponent; j++) {
             int bap     = c->bap[g->exp_ofs + j];
             int idx     = c->idx[g->exp_ofs + j];
-            int size1   = mantissa_size1[bap][idx];
+            int size1   = ff_dolby_e_mantissa_size1[bap][idx];
             int count   = g->nb_mantissa[j];
             float exp   = exponent_tab[c->exponents[g->exp_ofs + j]];
             float scale = mantissa_tab1[size1][idx] * exp;
@@ -875,7 +263,7 @@ static int parse_mantissas(DBEContext *s, DBEChannel *c)
                     if (values[k] != escape) {
                         mnt[k] = values[k] * scale;
                     } else {
-                        int size2 = mantissa_size2[bap][idx];
+                        int size2 = ff_dolby_e_mantissa_size2[bap][idx];
                         int value = get_sbits(&s->gb, size2);
                         float a = mantissa_tab2[size2][idx];
                         float b = mantissa_tab3[size2][idx];
@@ -920,7 +308,7 @@ static int parse_channel(DBEDecodeContext *s1, int ch, int seg_id)
         return AVERROR_PATCHWELCOME;
     }
 
-    if (ch == lfe_channel_tab[s->metadata.prog_conf]) {
+    if (ch == ff_dolby_e_lfe_channel_tab[s->metadata.prog_conf]) {
         c->gr_code = 3;
         c->bw_code = 29;
     } else {
@@ -932,11 +320,11 @@ static int parse_channel(DBEDecodeContext *s1, int ch, int seg_id)
         }
     }
 
-    c->nb_groups   = nb_groups_tab[c->gr_code];
-    c->nb_mstr_exp = nb_mstr_exp_tab[c->gr_code];
+    c->nb_groups   = ff_dolby_e_nb_groups_tab[c->gr_code];
+    c->nb_mstr_exp = ff_dolby_e_nb_mstr_exp_tab[c->gr_code];
 
     for (i = 0; i < c->nb_groups; i++) {
-        c->groups[i] = frm_ofs_tab[seg_id][c->gr_code][i];
+        c->groups[i] = ff_dolby_e_frm_ofs_tab[seg_id][c->gr_code][i];
         if (c->nb_mstr_exp == 2) {
             c->groups[i].nb_exponent    -= c->bw_code;
             c->groups[i].nb_bias_exp[1] -= c->bw_code;
@@ -996,32 +384,6 @@ static int parse_meter(DBEDecodeContext *s1)
     return 0;
 }
 
-static void imdct_calc(DBEDecodeContext *s1, DBEGroup *g, float *result, float *values)
-{
-    AVTXContext *imdct = s1->imdct[g->imdct_phs == 1][g->imdct_idx];
-    av_tx_fn  imdct_fn = s1->imdct_fn[g->imdct_phs == 1][g->imdct_idx];
-    int n   = 1 << imdct_bits_tab[g->imdct_idx];
-    int n2  = n >> 1;
-
-    switch (g->imdct_phs) {
-    case 0:
-        imdct_fn(imdct, result, values, sizeof(float));
-        for (int i = 0; i < n2; i++)
-            result[n2 + i] = result[n2 - i - 1];
-        break;
-    case 1:
-        imdct_fn(imdct, result, values, sizeof(float));
-        break;
-    case 2:
-        imdct_fn(imdct, result + n2, values, sizeof(float));
-        for (int i = 0; i < n2; i++)
-            result[i] = -result[n - i - 1];
-        break;
-    default:
-        av_assert0(0);
-    }
-}
-
 static void transform(DBEDecodeContext *s1, DBEChannel *c, float *history, float *output)
 {
     LOCAL_ALIGNED_32(float, buffer, [2048]);
@@ -1033,9 +395,9 @@ static void transform(DBEDecodeContext *s1, DBEChannel *c, float *history, float
     for (i = 0, g = c->groups; i < c->nb_groups; i++, g++) {
         float *src = buffer + g->src_ofs;
         float *dst = result + g->dst_ofs;
-        float *win = window + g->win_ofs;
+        float *win = ff_dolby_e_window + g->win_ofs;
 
-        imdct_calc(s1, g, buffer, c->mantissas + g->mnt_ofs);
+        ff_dolby_e_imdct_calc(&s1->transform, g, buffer, c->mantissas + g->mnt_ofs);
         s1->fdsp->vector_fmul_add(dst, src, win, dst, g->win_len);
     }
 
@@ -1162,10 +524,7 @@ static av_cold int dolby_e_close(AVCodecContext *avctx)
 {
     DBEDecodeContext *s = avctx->priv_data;
 
-    for (int i = 0; i < 3; i++) {
-        av_tx_uninit(&s->imdct[0][i]);
-        av_tx_uninit(&s->imdct[1][i]);
-    }
+    ff_dolby_e_transform_uninit(&s->transform);
 
     av_freep(&s->fdsp);
     return 0;
@@ -1206,76 +565,19 @@ static av_cold void init_tables(void)
 
     for (i = 1; i < 1024; i++)
         gain_tab[i] = exp2f((i - 960) / 64.0f);
-
-    // short 1
-    ff_kbd_window_init(window, 3.0f, 128);
-    for (i = 0; i < 128; i++)
-        window[128 + i] = window[127 - i];
-
-    // start
-    for (i = 0; i < 192; i++)
-        window[256 + i] = start_window[i];
-
-    // short 2
-    for (i = 0; i < 192; i++)
-        window[448 + i] = short_window2[i];
-    for (i = 0; i < 64; i++)
-        window[640 + i] = window[63 - i];
-
-    // short 3
-    for (i = 0; i < 64; i++)
-        window[704 + i] = short_window3[i];
-    for (i = 0; i < 192; i++)
-        window[768 + i] = window[64 + i];
-
-    // bridge
-    for (i = 0; i < 128; i++)
-        window[960 + i] = window[i];
-    for (i = 0; i < 64; i++)
-        window[1088 + i] = 1.0f;
-
-    // long
-    ff_kbd_window_init(window + 1408, 3.0f, 256);
-    for (i = 0; i < 640; i++)
-        window[1664 + i] = 1.0f;
-    for (i = 0; i < 256; i++)
-        window[2304 + i] = window[1152 + i] = window[1663 - i];
-
-    // reverse start
-    for (i = 0; i < 192; i++)
-        window[2560 + i] = window[447 - i];
-
-    // reverse short 2
-    for (i = 0; i < 256; i++)
-        window[2752 + i] = window[703 - i];
-
-    // reverse short 3
-    for (i = 0; i < 256; i++)
-        window[3008 + i] = window[959 - i];
-
-    // reverse bridge
-    for (i = 0; i < 448; i++)
-        window[3264 + i] = window[1407 - i];
 }
 
 static av_cold int dolby_e_init(AVCodecContext *avctx)
 {
     static AVOnce init_once = AV_ONCE_INIT;
     DBEDecodeContext *s = avctx->priv_data;
-    float scale = 2.0f;
     int ret;
 
     if (ff_thread_once(&init_once, init_tables))
         return AVERROR_UNKNOWN;
 
-    for (int i = 0; i < 3; i++) {
-        if ((ret = av_tx_init(&s->imdct[0][i], &s->imdct_fn[0][i], AV_TX_FLOAT_MDCT,
-                              1, 1 << imdct_bits_tab[i] - 1, &scale, 0)) < 0)
-            return ret;
-        if ((ret = av_tx_init(&s->imdct[1][i], &s->imdct_fn[1][i], AV_TX_FLOAT_MDCT,
-                              1, 1 << imdct_bits_tab[i] - 1, &scale, AV_TX_FULL_IMDCT)) < 0)
-            return ret;
-    }
+    if ((ret = ff_dolby_e_transform_init(&s->transform)) < 0)
+        return ret;
 
     if (!(s->fdsp = avpriv_float_dsp_alloc(0)))
         return AVERROR(ENOMEM);
diff --git a/libavcodec/dolby_e_bitalloc.c b/libavcodec/dolby_e_bitalloc.c
new file mode 100644
index 0000000000..94939828db
--- /dev/null
+++ b/libavcodec/dolby_e_bitalloc.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * 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
+ */
+
+#include "libavutil/common.h"
+#include "dolby_e_bitalloc.h"
+
+#define MAX_BIAS_EXP_BA 50
+
+const int8_t ff_dolby_e_lfe_channel_tab[MAX_PROG_CONF + 1] = {
+     5,  5, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  5, 5
+};
+
+const uint8_t ff_dolby_e_nb_groups_tab[4] = { 1, 8, 7, 1 };
+
+const uint8_t ff_dolby_e_nb_mstr_exp_tab[4] = { 2, 2, 2, 1 };
+
+static const uint8_t nb_mantissa_38[38] = {
+     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+     2,  2,  2,  2,  2,  2,  3,  3,  3,  4,  4,  4,  5,  5,  6,  6,
+     7,  8,  9, 10, 11, 12,
+};
+
+static const uint8_t nb_mantissa_44[44] = {
+     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  2,
+     2,  2,  2,  2,  2,  3,  3,  3,  3,  4,  4,  5,  5,  6,  7,  7,
+     8,  9, 10, 11, 12, 13, 15, 16, 18, 20, 22, 25,
+};
+
+static const uint8_t nb_mantissa_50[50] = {
+     1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,
+     3,  4,  4,  5,  5,  6,  6,  7,  8,  9,  9, 10, 12, 13, 14, 16,
+    18, 19, 22, 24, 27, 29, 32, 36, 40, 44, 49, 54, 60, 66, 74, 82,
+    90, 100,
+};
+
+static const DBEGroup grp_tab_0[1] = {
+    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
+};
+
+static const DBEGroup grp_tab_1[8] = {
+    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
+    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
+    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
+    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 704, 0, 0 },
+    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 1, 256, 832, 0, 0 },
+};
+
+static const DBEGroup grp_tab_2[7] = {
+    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 0, 192, 0, 256, 0 },
+    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 64, 448, 0 },
+    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 192, 704, 0 },
+    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 44, { 19, 25 }, 228, 768, nb_mantissa_44, 1, 1, 448, 704, 960, 64 },
+};
+
+static const DBEGroup grp_tab_3[1] = {
+    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 0, 1152, 0, 1408, 0 },
+};
+
+static const DBEGroup grp_tab_4[1] = {
+    { 50, { 27, 23 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
+};
+
+static const DBEGroup grp_tab_5[8] = {
+    { 38, { 12, 26 }, 0, 0, nb_mantissa_38, 0, 1, 256, 64, 0, 0 },
+    { 38, { 12, 26 }, 38, 128, nb_mantissa_38, 0, 1, 256, 192, 0, 0 },
+    { 38, { 12, 26 }, 76, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 114, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 152, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 38, { 12, 26 }, 190, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
+    { 38, { 12, 26 }, 228, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
+    { 38, { 12, 26 }, 266, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
+};
+
+static const DBEGroup grp_tab_6[7] = {
+    { 44, { 19, 25 }, 0, 0, nb_mantissa_44, 1, 1, 448, 0, 3264, 0 },
+    { 38, { 12, 26 }, 44, 256, nb_mantissa_38, 0, 1, 256, 320, 0, 0 },
+    { 38, { 12, 26 }, 82, 384, nb_mantissa_38, 0, 1, 256, 448, 0, 0 },
+    { 38, { 12, 26 }, 120, 512, nb_mantissa_38, 0, 1, 256, 576, 0, 0 },
+    { 38, { 12, 26 }, 158, 640, nb_mantissa_38, 0, 1, 256, 704, 3008, 0 },
+    { 38, { 12, 26 }, 196, 768, nb_mantissa_38, 0, 1, 256, 832, 2752, 0 },
+    { 38, { 12, 26 }, 234, 896, nb_mantissa_38, 0, 2, 192, 960, 2560, 64 },
+};
+
+static const DBEGroup grp_tab_7[1] = {
+    { 21, { 21 }, 0, 0, nb_mantissa_50, 2, 2, 1152, 0, 1408, 896 },
+};
+
+const DBEGroup *const ff_dolby_e_frm_ofs_tab[2][4] = {
+    { grp_tab_0, grp_tab_1, grp_tab_2, grp_tab_3 },
+    { grp_tab_4, grp_tab_5, grp_tab_6, grp_tab_7 }
+};
+
+const uint8_t ff_dolby_e_mantissa_size1[16][4] = {
+    {  0,  0,  0,  0 }, {  2,  1,  1,  1 }, {  3,  2,  1,  1 }, {  4,  3,  2,  1 },
+    {  5,  4,  3,  2 }, {  6,  5,  4,  3 }, {  7,  6,  5,  4 }, {  8,  7,  6,  5 },
+    {  9,  8,  7,  6 }, { 10,  9,  8,  7 }, { 11, 10,  9,  8 }, { 12, 11, 10,  9 },
+    { 13, 12, 11, 10 }, { 14, 13, 12, 11 }, { 15, 14, 13, 12 }, { 16, 15, 14, 13 },
+};
+
+const uint8_t ff_dolby_e_mantissa_size2[16][4] = {
+    {  0,  0,  0,  0 }, {  2,  1,  2,  2 }, {  3,  2,  3,  3 }, {  4,  3,  4,  4 },
+    {  5,  4,  5,  5 }, {  6,  5,  6,  6 }, {  7,  6,  7,  7 }, {  8,  7,  8,  8 },
+    {  9,  8,  9,  9 }, { 10,  9, 10, 10 }, { 11, 10, 11, 11 }, { 12, 11, 12, 12 },
+    { 13, 12, 13, 13 }, { 14, 13, 14, 14 }, { 15, 14, 15, 15 }, { 16, 15, 16, 16 },
+};
+
+static const uint8_t dc_code_tab[5] = { 0, 0, 0, 1, 1 };
+
+static const uint8_t ht_code_tab[5] = { 0, 0, 1, 2, 2 };
+
+static const uint8_t band_ofs_tab[3][4] = {
+    { 12, 8, 4, 0 }, { 14, 10, 6, 0 }, { 12, 8, 4, 0 }
+};
+
+static const uint8_t band_low_tab[3] = { 9, 17, 24 };
+
+static const uint16_t fast_gain_tab[8] = {
+    128, 256, 384, 512, 640, 768, 896, 1024
+};
+
+static const uint16_t slow_decay_tab[2][2] = { { 27, -1 }, { 32, 21 } };
+
+static const uint16_t misc_decay_tab[3][2][2] = {
+    { { 354, -1 }, { 425, 425 } },
+    { { 266, -1 }, { 320,  -1 } },
+    { { 213, -1 }, { 256,  -1 } }
+};
+
+static const uint16_t fast_decay_tab[3][2][2][50] = {
+    {{{
+        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
+        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
+        142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
+        142, 142, 142, 142, 142, 142, 142, 142,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    }}, {{
+        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
+        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
+        170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
+        170, 170, 170, 170, 170, 170, 170, 170,
+    }, {
+         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
+         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
+         64,  64,  64,  64,  64,  64,  64,  64,  64,  64,
+         64,  64,  64,  64,  64,  64,  64,  64,
+    }}}, {{{
+        266, 266, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,
+    }}, {{
+        319, 319, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,
+    }}}, {{{
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+        106, 106, 106, 106, 106, 106, 106, 106, 106, 106,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    }}, {{
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+        128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+    }, {
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+         -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    }}}
+};
+
+static const uint16_t fast_gain_adj_tab[3][2][62] = {
+    {{
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   1,   2,   4,   7,  11,  16,  29,  44,  59,
+         76,  94, 116, 142, 179, 221, 252, 285, 312, 334,
+    }, {
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          2,   5,   8,  10,  15,  28,  42,  57,  75,  93,
+        115, 140, 177, 219, 247, 280, 308, 330, 427, 533,
+    }}, {{
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   2,   5,   8,  12,  21,  35,  51,  69,  89,
+        111, 138, 176, 220, 251, 284, 312, 334,
+    }, {
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   2,
+          5,   8,  11,  18,  33,  49,  65,  84, 106, 132,
+        168, 214, 245, 279, 308, 329, 427, 533,
+    }}, {{
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   1,   4,   7,  10,  17,
+         31,  47,  65,  84, 107, 134, 171, 215, 250, 283,
+        312, 334,
+    }, {
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+          0,   0,   0,   0,   3,   6,   9,  13,  27,  43,
+         60,  79, 100, 126, 160, 207, 242, 276, 307, 329,
+        427, 533,
+    }}
+};
+
+static const uint16_t slow_gain_tab[3][2][50] = {
+    {{
+        3072, 3072, 3072, 3072, 3072, 3072, 1063, 1063, 1063, 1063,
+        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
+        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
+        1063, 1063, 1063, 1063, 1063, 1063, 1063, 1063,
+    }, {
+        3072, 3072, 3072, 3072, 3072, 3072,  850,  850,  850,  850,
+         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
+         850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
+         850,  850,  850,  850,  850,  850,  850,  850,
+    }}, {{
+        3072, 1212, 1212, 1212,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,
+    }, {
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,
+    }}, {{
+        3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072, 3072,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+         999,  999,  999,  999,  999,  999,  999,  999,  999,  999,
+    }, {
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+          -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
+    }}
+};
+
+static const uint16_t hearing_thresh_tab[3][3][50] = {
+    {{
+        1403, 1141, 1000,  959,  948,  957,  946,  925,  899,  871,
+         843,  815,  789,  766,  745,  727,  705,  687,  681,  686,
+         701,  725,  768,  854,  940, 1018, 1075, 1103, 1111, 1106,
+        1098, 1105, 1142, 1237, 1419, 1721, 2169, 2805,
+    }, {
+        1401, 1130,  995,  957,  947,  955,  941,  918,  890,  861,
+         831,  803,  777,  754,  734,  717,  698,  684,  682,  692,
+         712,  743,  798,  894,  976, 1045, 1091, 1109, 1110, 1102,
+        1098, 1116, 1174, 1300, 1526, 1884, 2401, 3072,
+    }, {
+        1393, 1086,  974,  949,  957,  941,  913,  878,  843,  808,
+         777,  750,  727,  708,  695,  686,  681,  689,  714,  752,
+         811,  888,  971, 1044, 1087, 1108, 1110, 1102, 1098, 1115,
+        1172, 1290, 1489, 1812, 2293, 2964, 3072, 3072,
+    }}, {{
+        1412, 1343, 1141, 1047, 1000,  974,  959,  951,  948,  947,
+         957,  953,  946,  936,  925,  906,  878,  850,  822,  795,
+         771,  745,  719,  700,  687,  681,  685,  701,  733,  784,
+         885,  977, 1047, 1092, 1110, 1108, 1099, 1102, 1138, 1233,
+        1413, 1711, 2157, 2797,
+    }, {
+        1412, 1336, 1130, 1040,  995,  970,  957,  950,  947,  947,
+         955,  950,  941,  930,  918,  897,  868,  838,  810,  783,
+         759,  734,  710,  693,  684,  681,  690,  712,  752,  823,
+         924, 1009, 1069, 1102, 1111, 1104, 1098, 1111, 1168, 1295,
+        1518, 1873, 2388, 3072,
+    }, {
+        1411, 1293, 1086, 1009,  974,  957,  949,  947,  957,  951,
+         941,  928,  913,  896,  878,  852,  817,  785,  756,  732,
+         713,  695,  683,  682,  689,  710,  746,  811,  906,  992,
+        1061, 1099, 1111, 1106, 1098, 1107, 1155, 1266, 1471, 1799,
+        2277, 2945, 3072, 3072,
+    }}, {{
+        1431, 1412, 1403, 1379, 1343, 1293, 1229, 1180, 1125, 1075,
+        1040, 1014,  996,  979,  965,  957,  951,  948,  947,  957,
+         951,  940,  924,  903,  877,  846,  815,  785,  753,  725,
+         702,  686,  681,  689,  714,  760,  847,  947, 1028, 1083,
+        1108, 1109, 1101, 1100, 1132, 1222, 1402, 1705, 2160, 2803,
+    }, {
+        1431, 1412, 1401, 1375, 1336, 1278, 1215, 1168, 1115, 1066,
+        1032, 1008,  991,  975,  962,  954,  950,  947,  947,  955,
+         948,  935,  916,  894,  866,  835,  803,  772,  742,  715,
+         695,  683,  683,  697,  729,  784,  887,  982, 1054, 1096,
+        1111, 1106, 1098, 1107, 1159, 1281, 1505, 1865, 2391, 3072,
+    }, {
+        1427, 1411, 1393, 1353, 1293, 1215, 1160, 1118, 1072, 1031,
+        1003,  984,  971,  960,  952,  948,  947,  957,  952,  941,
+         924,  902,  876,  847,  815,  781,  750,  723,  700,  685,
+         681,  691,  719,  766,  858,  958, 1039, 1089, 1109, 1108,
+        1099, 1102, 1141, 1245, 1442, 1766, 2250, 2930, 3072, 3072,
+    }}
+};
+
+static const int16_t lwc_gain_tab[11][7] = {
+    {   -21,  -197,  -271,  -466, 32767, 32767, 32767 },
+    {  -197,   -29,  -244,  -271,  -540, 32767, 32767 },
+    {  -271,  -244,   -29,  -249,  -271,  -593, 32767 },
+    {  -466,  -271,  -249,   -29,  -251,  -271,  -632 },
+    {  -540,  -271,  -251,   -29,  -251,  -271,  -664 },
+    {  -593,  -271,  -251,   -29,  -252,  -271,  -690 },
+    {  -632,  -271,  -252,   -29,  -252,  -271,  -711 },
+    {  -664,  -271,  -252,   -29,  -252,  -271,  -730 },
+    {  -690,  -271,  -252,   -29,  -252,  -271,  -745 },
+    {  -711,  -271,  -252,   -29,  -253,  -271,  -759 },
+    {  -730,  -271,  -253,   -29,  -253,  -271,  -771 },
+};
+
+static const int16_t lwc_adj_tab[7] = {
+    -192, -320, -448, -512, -448, -320, -192,
+};
+
+static const uint8_t log_add_tab[212] = {
+    64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 52, 51, 50,
+    49, 48, 47, 47, 46, 45, 44, 44, 43, 42, 41, 41, 40, 39, 38, 38,
+    37, 36, 36, 35, 35, 34, 33, 33, 32, 32, 31, 30, 30, 29, 29, 28,
+    28, 27, 27, 26, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 21,
+    20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15,
+    15, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11,
+    10, 10, 10, 10, 10,  9,  9,  9,  9,  9,  8,  8,  8,  8,  8,  8,
+     7,  7,  7,  7,  7,  7,  6,  6,  6,  6,  6,  6,  6,  6,  5,  5,
+     5,  5,  5,  5,  5,  5,  4,  4,  4,  4,  4,  4,  4,  4,  4,  4,
+     4,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  2,
+     2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+     2,  2,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+     1,  1,  0,  0,
+};
+
+static const uint8_t bap_tab[64] = {
+     0,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  3,  3,  3,  3,  4,
+     4,  4,  4,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,
+     8,  8,  8,  9,  9,  9,  9, 10, 10, 10, 10, 11, 11, 11, 11, 12,
+    12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15,
+};
+
+static inline int log_add(int a, int b)
+{
+    int c = FFABS(a - b) >> 1;
+    return FFMAX(a, b) + log_add_tab[FFMIN(c, 211)];
+}
+
+static void calc_lowcomp(int *msk_val)
+{
+    int lwc_val[17] = { 0 };
+    int i, j, k;
+
+    for (i = 0; i < 11; i++) {
+        int max_j = 0;
+        int max_v = INT_MIN;
+        int thr   = 0;
+
+        for (j = FFMAX(i - 3, 0), k = 0; j <= i + 3; j++, k++) {
+            int v = msk_val[j] + lwc_gain_tab[i][k];
+            if (v > max_v) {
+                max_j = j;
+                max_v = v;
+            }
+            thr = log_add(thr, v);
+        }
+
+        if (msk_val[i] < thr) {
+            for (j = FFMAX(max_j - 3, 0),
+                 k = FFMAX(3 - max_j, 0);
+                 j <= max_j + 3; j++, k++)
+                lwc_val[j] += lwc_adj_tab[k];
+        }
+    }
+
+    for (i = 0; i < 16; i++) {
+        int v = FFMAX(lwc_val[i], -512);
+        msk_val[i] = FFMAX(msk_val[i] + v, 0);
+    }
+}
+
+void ff_dolby_e_bit_allocate(int nb_exponent, int nb_code, int fr_code,
+                              int *exp, int *bap,
+                              int fg_spc, int fg_ofs, int msk_mod, int snr_ofs)
+{
+    int msk_val[MAX_BIAS_EXP_BA];
+    int psd_val[MAX_BIAS_EXP_BA];
+    int fast_leak  = 0;
+    int slow_leak  = 0;
+    int dc_code    = dc_code_tab[fr_code - 1];
+    int ht_code    = ht_code_tab[fr_code - 1];
+    int fast_gain  = fast_gain_tab[fg_ofs];
+    int slow_decay = slow_decay_tab[dc_code][msk_mod];
+    int misc_decay = misc_decay_tab[nb_code][dc_code][msk_mod];
+    const uint16_t *slow_gain      = slow_gain_tab[nb_code][msk_mod];
+    const uint16_t *fast_decay     = fast_decay_tab[nb_code][dc_code][msk_mod];
+    const uint16_t *fast_gain_adj  = fast_gain_adj_tab[nb_code][dc_code];
+    const uint16_t *hearing_thresh = hearing_thresh_tab[nb_code][ht_code];
+    int i;
+
+    for (i = 0; i < nb_exponent; i++)
+        psd_val[i] = (48 - exp[i]) * 64;
+
+    fast_gain_adj += band_ofs_tab[nb_code][fg_spc];
+    for (i = 0; i < nb_exponent; i++) {
+        fast_leak = log_add(fast_leak  - fast_decay[i],
+                            psd_val[i] - fast_gain + fast_gain_adj[i]);
+        slow_leak = log_add(slow_leak  - slow_decay,
+                            psd_val[i] - slow_gain[i]);
+        msk_val[i] = FFMAX(fast_leak, slow_leak);
+    }
+
+    fast_leak = 0;
+    for (i = nb_exponent - 1; i > band_low_tab[nb_code]; i--) {
+        fast_leak = log_add(fast_leak - misc_decay, psd_val[i] - fast_gain);
+        msk_val[i] = FFMAX(msk_val[i], fast_leak);
+    }
+
+    for (i = 0; i < nb_exponent; i++)
+        msk_val[i] = FFMAX(msk_val[i], hearing_thresh[i]);
+
+    if (!nb_code)
+        calc_lowcomp(msk_val);
+
+    for (i = 0; i < nb_exponent; i++) {
+        int v = 16 * (snr_ofs - 64) + psd_val[i] - msk_val[i] >> 5;
+        bap[i] = bap_tab[av_clip_uintp2(v, 6)];
+    }
+}
diff --git a/libavcodec/dolby_e_bitalloc.h b/libavcodec/dolby_e_bitalloc.h
new file mode 100644
index 0000000000..f81420fcfa
--- /dev/null
+++ b/libavcodec/dolby_e_bitalloc.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * 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
+ */
+
+#ifndef AVCODEC_DOLBY_E_BITALLOC_H
+#define AVCODEC_DOLBY_E_BITALLOC_H
+
+#include <stdint.h>
+
+#include "dolby_e.h"
+
+#define DBE_MAX_MSTR_EXP 2
+
+/**
+ * @struct DBEGroup
+ * Fixed per-(segment,gr_code) group geometry: exponent/mantissa counts and
+ * offsets, and the IMDCT/window parameters needed to reconstruct one group.
+ * Entries are static, table-driven (ff_dolby_e_frm_ofs_tab) -- none of this
+ * is coded in the bitstream itself.
+ */
+typedef struct DBEGroup {
+    uint8_t         nb_exponent;
+    uint8_t         nb_bias_exp[DBE_MAX_MSTR_EXP];
+    uint16_t        exp_ofs;
+    uint16_t        mnt_ofs;
+    const uint8_t   *nb_mantissa;
+    uint8_t         imdct_idx;
+    uint8_t         imdct_phs;
+    uint16_t        win_len;
+    uint16_t        dst_ofs;
+    uint16_t        win_ofs;
+    uint16_t        src_ofs;
+} DBEGroup;
+
+extern const DBEGroup   *const ff_dolby_e_frm_ofs_tab[2][4];
+extern const uint8_t     ff_dolby_e_nb_groups_tab[4];
+extern const uint8_t     ff_dolby_e_nb_mstr_exp_tab[4];
+extern const uint8_t     ff_dolby_e_mantissa_size1[16][4];
+extern const uint8_t     ff_dolby_e_mantissa_size2[16][4];
+extern const int8_t      ff_dolby_e_lfe_channel_tab[MAX_PROG_CONF + 1];
+
+/**
+ * Compute the bit allocation pointer (bap) for one group from its
+ * transmitted parameters. This is the complete Dolby E/AC-3-style
+ * psychoacoustic model; it is run identically by the decoder (from
+ * transmitted parameters) and by the encoder (searched over the free
+ * parameters to hit a bit budget).
+ *
+ * @param nb_exponent number of exponents/bap values in this group
+ * @param nb_code      group->imdct_idx (selects the table set: 0/1/2)
+ * @param fr_code      metadata.fr_code (1-based index into dc/ht code tabs)
+ * @param exp          nb_exponent exponent values for this group
+ * @param bap          [out] nb_exponent bap values for this group
+ * @param fg_spc, fg_ofs, msk_mod, snr_ofs  transmitted bit-alloc parameters
+ */
+void ff_dolby_e_bit_allocate(int nb_exponent, int nb_code, int fr_code,
+                              int *exp, int *bap,
+                              int fg_spc, int fg_ofs, int msk_mod, int snr_ofs);
+
+#endif
diff --git a/libavcodec/dolby_e_transform.c b/libavcodec/dolby_e_transform.c
new file mode 100644
index 0000000000..780c505126
--- /dev/null
+++ b/libavcodec/dolby_e_transform.c
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * 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
+ */
+
+#include "libavutil/mem.h"
+#include "libavutil/thread.h"
+#include "libavutil/mem_internal.h"
+#include "kbdwin.h"
+#include "dolby_e_transform.h"
+
+const uint8_t ff_dolby_e_imdct_bits_tab[3] = { 8, 9, 11 };
+
+static const float start_window[192] = {
+    0.00161569379826, 0.00185748233347, 0.00198562758548, 0.00207834078104,
+    0.00215717748523, 0.00223067096393, 0.00230299213147, 0.00237651215396,
+    0.00245275561606, 0.00253281402069, 0.00261754673613, 0.00270768786168,
+    0.00280390761895, 0.00290684998656, 0.00301715751161, 0.00313548872798,
+    0.00326253122934, 0.00339901215995, 0.00354570716636, 0.00370344845023,
+    0.00387313232586, 0.00405572653911, 0.00425227750970, 0.00446391759265,
+    0.00469187240551, 0.00493746822816, 0.00520213944619, 0.00548743597507,
+    0.00579503056737, 0.00612672586953, 0.00648446105606, 0.00687031782873,
+    0.00728652552677, 0.00773546505205, 0.00821967127415, 0.00874183354619,
+    0.00930479393832, 0.00991154278653, 0.01056521116692, 0.01126905994567,
+    0.01202646513050, 0.01284089936559, 0.01371590957417, 0.01465509096066,
+    0.01566205783408, 0.01674041199523, 0.01789370972358, 0.01912542867865,
+    0.02043893626265, 0.02183746113793, 0.02332406961796, 0.02490164852364,
+    0.02657289580178, 0.02834031974193, 0.03020624702903, 0.03217283918354,
+    0.03424211623810, 0.03641598586180, 0.03869627565015, 0.04108476601498,
+    0.04358322107390, 0.04619341515939, 0.04891715301882, 0.05175628239149,
+
+    0.05471237327267, 0.05778734733755, 0.06098291402413, 0.06430101352084,
+    0.06774345212186, 0.07131188644726, 0.07500780649199, 0.07883251748595,
+    0.08278712056651, 0.08687249228061, 0.09108926295730, 0.09543779401074,
+    0.09991815425851, 0.10453009536427, 0.10927302653894, 0.11414598865987,
+    0.11914762799220, 0.12427616972097, 0.12952939152560, 0.13490459744934,
+    0.14039859233595, 0.14600765712201, 0.15172752528722, 0.15755336077528,
+    0.16347973770491, 0.16950062219342, 0.17560935661442, 0.18179864660619,
+    0.18806055113821, 0.19438647593012, 0.20076717050010, 0.20719272909882,
+    0.21365259576030, 0.22013557367283, 0.22662983904194, 0.23312295958328,
+    0.23960191774666, 0.24605313873388, 0.25246252333253, 0.25881548554631,
+    0.26509699495987, 0.27129162373316, 0.27738359807707, 0.28335685401987,
+    0.28919509723179, 0.29488186663467, 0.30040060148455, 0.30573471157819,
+    0.31086765019993, 0.31578298939317, 0.32046449711227, 0.32489621578468,
+    0.32906254179156, 0.33294830535654, 0.33653885031840, 0.33982011325336,
+    0.34277870140679, 0.34540196889300, 0.34767809062480, 0.34959613344194,
+    0.35114612391958, 0.35231911235422, 0.35310723244504, 0.35350375621308,
+
+    0.35350314372945, 0.35310108725579, 0.35229454943591, 0.35108179521634,
+    0.34946241721522, 0.34743735430290, 0.34500890320420, 0.34218072298001,
+    0.33895783229541, 0.33534659943168, 0.33135472505060, 0.32699121776996,
+    0.32226636266000, 0.31719168282019, 0.31177989424432, 0.30604485422875,
+    0.30000150362379, 0.29366580327088, 0.28705466500775, 0.28018587766131,
+    0.27307802848095, 0.26575042049535, 0.25822298630189, 0.25051619882000,
+    0.24265097955783, 0.23464860495522, 0.22653061137548, 0.21831869932335,
+    0.21003463746705, 0.20170016703857, 0.19333690717811, 0.18496626177620,
+    0.17660932835062, 0.16828680947474, 0.16001892724986, 0.15182534128597,
+    0.14372507062477, 0.13573642000364, 0.12787691082233, 0.12016321713317,
+    0.11261110693234, 0.10523538898282, 0.09804986534955, 0.09106728977263,
+    0.08429933194438, 0.07775654768810, 0.07144835495683, 0.06538301547324,
+    0.05956762170687, 0.05400808871425, 0.04870915012107, 0.04367435714993,
+    0.03890607899172, 0.03440550179663, 0.03017262174627, 0.02620622428513,
+    0.02250383492507, 0.01906161305732, 0.01587412848221, 0.01293388032354,
+    0.01023019677288, 0.00774641320626, 0.00545109736891, 0.00325868651263,
+};
+
+static const float short_window2[192] = {
+    0.00018861094606, 0.00033433010202, 0.00050309624485, 0.00070306161748,
+    0.00093995174533, 0.00121913067128, 0.00154606505568, 0.00192647806126,
+    0.00236641248692, 0.00287225985240, 0.00345077377440, 0.00410907465023,
+    0.00485464855241, 0.00569534163219, 0.00663935063508, 0.00769520981249,
+    0.00887177436246, 0.01017820046395, 0.01162392194150, 0.01321862359335,
+    0.01497221122468, 0.01689477844427, 0.01899657030441, 0.02128794388846,
+    0.02377932597692, 0.02648116795039, 0.02940389811590, 0.03255787167130,
+    0.03595331854986, 0.03960028941437, 0.04350860009563, 0.04768777479454,
+    0.05214698838949, 0.05689500821121, 0.06194013566525, 0.06729014809766,
+    0.07295224131210, 0.07893297315602, 0.08523820859989, 0.09187306673620,
+    0.09884187012422, 0.10614809690222, 0.11379433608064, 0.12178224641797,
+    0.13011251926531, 0.13878484574660, 0.14779788861830, 0.15714925912610,
+    0.16683549914631, 0.17685206886673, 0.18719334022589, 0.19785259629099,
+    0.20882203671372, 0.22009278936030, 0.23165492816694, 0.24349749722585,
+    0.25560854105961, 0.26797514099368, 0.28058345748882, 0.29341877824732,
+    0.30646557185942, 0.31970754671026, 0.33312771482295, 0.34670846027024,
+
+    0.36043161174692, 0.37427851885723, 0.38823013163645, 0.40226708279486,
+    0.41636977214436, 0.43051845264462, 0.44469331748632, 0.45887458761470,
+    0.47304259908636, 0.48717788964798, 0.50126128392546, 0.51527397661778,
+    0.52919761310050, 0.54301436685998, 0.55670701320069, 0.57025899869448,
+    0.58365450587230, 0.59687851269542, 0.60991684638414, 0.62275623122793,
+    0.63538433005035, 0.64778977905593, 0.65996221584264, 0.67189230042379,
+    0.68357172916486, 0.69499324160511, 0.70615062019861, 0.71703868307548,
+    0.72765326998919, 0.73799122168099, 0.74805035295521, 0.75782941981995,
+    0.76732808110520, 0.77654685502339, 0.78548707118622, 0.79415081863423,
+    0.80254089047207, 0.81066072573188, 0.81851434910893, 0.82610630922734,
+    0.83344161609862, 0.84052567843230, 0.84736424144524, 0.85396332579459,
+    0.86032916822973, 0.86646816451999, 0.87238681516918, 0.87809167437532,
+    0.88358930263537, 0.88888622333073, 0.89398888356256, 0.89890361943564,
+    0.90363662591861, 0.90819393133744, 0.91258137648979, 0.91680459830070,
+    0.92086901787718, 0.92477983276087, 0.92854201312583, 0.93216030163834,
+    0.93563921662343, 0.93898305819384, 0.94219591693690, 0.94528168477979,
+
+    0.94823843319821, 0.95106834367330, 0.95377776558539, 0.95636718335775,
+    0.95883679961479, 0.96118650212341, 0.96341583179195, 0.96552395212906,
+    0.96750962060547, 0.96937116231768, 0.97110644638309, 0.97271286544154,
+    0.97418731862798, 0.97552619834964, 0.97672538116257, 0.97778022299974,
+    0.97868555895586, 0.97943570778357, 0.98002448120255, 0.98044519806866,
+    0.98069070339493, 0.98075339216123, 0.98062523779637, 0.98029782516478,
+    0.97976238784222, 0.97900984942031, 0.97803086854002, 0.97681588731895,
+    0.97535518280755, 0.97363892108474, 0.97165721358452, 0.96940017523145,
+    0.96685798395452, 0.96402094114589, 0.96087953263194, 0.95742448973047,
+    0.95364684997699, 0.94953801711660, 0.94508981997396, 0.94029456983253,
+    0.93514511597504, 0.92963489905951, 0.92375800202883, 0.91750919827624,
+    0.91088399681406, 0.90387868421832, 0.89649036314692, 0.88871698725397,
+    0.88055739234735, 0.87201132366062, 0.86307945913336, 0.85376342861693,
+    0.84406582894455, 0.83399023482637, 0.82354120554757, 0.81272428745995,
+    0.80154601230457, 0.79001389138101, 0.77813640562199, 0.76592299164227,
+    0.75338402384395, 0.74053079267526, 0.72737547915460, 0.71393112578527,
+};
+
+static const float short_window3[64] = {
+    0.00326887936450, 0.00550242900936, 0.00786846643791, 0.01045683453520,
+    0.01330402120132, 0.01643221072863, 0.01985798040609, 0.02359509464766,
+    0.02765559221954, 0.03205025893128, 0.03678884369614, 0.04188015679495,
+    0.04733210987781, 0.05315172583924, 0.05934513287609, 0.06591755045290,
+    0.07287327156378, 0.08021564389822, 0.08794705152307, 0.09606889811179,
+    0.10458159240070, 0.11348453632940, 0.12277611617809, 0.13245369691511,
+    0.14251361989876, 0.15295120402567, 0.16376075037904, 0.17493555039885,
+    0.18646789757072, 0.19834910260891, 0.21056951208995, 0.22311853047787,
+    0.23598464546683, 0.24915545655419, 0.26261770674500, 0.27635731727778,
+    0.29035942525136, 0.30460842402318, 0.31908800624032, 0.33378120935681,
+    0.34867046348260, 0.36373764140285, 0.37896411059909, 0.39433078709788,
+    0.40981819096657, 0.42540650327031, 0.44107562429959, 0.45680523287270,
+    0.47257484651351, 0.48836388230077, 0.50415171818214, 0.51991775454258,
+    0.53564147581496, 0.55130251191887, 0.56688069931047, 0.58235614142007,
+    0.59770926827271, 0.61292089506118, 0.62797227945823, 0.64284517745255,
+    0.65752189749349, 0.67198535273209, 0.68621911114984, 0.70020744337099,
+};
+
+DECLARE_ALIGNED(32, float, ff_dolby_e_window)[DBE_WINDOW_SIZE];
+
+static av_cold void init_window_tables(void)
+{
+    float *window = ff_dolby_e_window;
+    int i;
+
+    // short 1
+    ff_kbd_window_init(window, 3.0f, 128);
+    for (i = 0; i < 128; i++)
+        window[128 + i] = window[127 - i];
+
+    // start
+    for (i = 0; i < 192; i++)
+        window[256 + i] = start_window[i];
+
+    // short 2
+    for (i = 0; i < 192; i++)
+        window[448 + i] = short_window2[i];
+    for (i = 0; i < 64; i++)
+        window[640 + i] = window[63 - i];
+
+    // short 3
+    for (i = 0; i < 64; i++)
+        window[704 + i] = short_window3[i];
+    for (i = 0; i < 192; i++)
+        window[768 + i] = window[64 + i];
+
+    // bridge
+    for (i = 0; i < 128; i++)
+        window[960 + i] = window[i];
+    for (i = 0; i < 64; i++)
+        window[1088 + i] = 1.0f;
+
+    // long
+    ff_kbd_window_init(window + 1408, 3.0f, 256);
+    for (i = 0; i < 640; i++)
+        window[1664 + i] = 1.0f;
+    for (i = 0; i < 256; i++)
+        window[2304 + i] = window[1152 + i] = window[1663 - i];
+
+    // reverse start
+    for (i = 0; i < 192; i++)
+        window[2560 + i] = window[447 - i];
+
+    // reverse short 2
+    for (i = 0; i < 256; i++)
+        window[2752 + i] = window[703 - i];
+
+    // reverse short 3
+    for (i = 0; i < 256; i++)
+        window[3008 + i] = window[959 - i];
+
+    // reverse bridge
+    for (i = 0; i < 448; i++)
+        window[3264 + i] = window[1407 - i];
+}
+
+void ff_dolby_e_init_window(void)
+{
+    static AVOnce init_once = AV_ONCE_INIT;
+    ff_thread_once(&init_once, init_window_tables);
+}
+
+int ff_dolby_e_transform_init(DBETransform *t)
+{
+    float scale = 2.0f;
+    /* Forward MDCT scale, fit per transform size against the decoder's
+     * fixed inverse scale=2.0 (does not follow a clean 1/n2 power law
+     * across sizes). Polarity of phs==1 groups is corrected separately in
+     * ff_dolby_e_mdct_calc(). */
+    static const float fwd_scale_tab[3] = {
+        0.007927333333813935f,   /* idx 0, n2=128  */
+        0.003963666666906967f,   /* idx 1, n2=256  */
+        0.0005298084368779297f,  /* idx 2, n2=1024 */
+    };
+    int ret;
+
+    ff_dolby_e_init_window();
+
+    for (int i = 0; i < 3; i++) {
+        float fwd_scale = fwd_scale_tab[i];
+
+        if ((ret = av_tx_init(&t->imdct[0][i], &t->imdct_fn[0][i], AV_TX_FLOAT_MDCT,
+                              1, 1 << ff_dolby_e_imdct_bits_tab[i] - 1, &scale, 0)) < 0)
+            return ret;
+        if ((ret = av_tx_init(&t->imdct[1][i], &t->imdct_fn[1][i], AV_TX_FLOAT_MDCT,
+                              1, 1 << ff_dolby_e_imdct_bits_tab[i] - 1, &scale, AV_TX_FULL_IMDCT)) < 0)
+            return ret;
+        if ((ret = av_tx_init(&t->mdct[i], &t->mdct_fn[i], AV_TX_FLOAT_MDCT,
+                              0, 1 << ff_dolby_e_imdct_bits_tab[i] - 1, &fwd_scale, 0)) < 0)
+            return ret;
+    }
+
+    return 0;
+}
+
+void ff_dolby_e_transform_uninit(DBETransform *t)
+{
+    for (int i = 0; i < 3; i++) {
+        av_tx_uninit(&t->imdct[0][i]);
+        av_tx_uninit(&t->imdct[1][i]);
+        av_tx_uninit(&t->mdct[i]);
+    }
+}
+
+void ff_dolby_e_imdct_calc(DBETransform *t, const DBEGroup *g,
+                            float *result, const float *values)
+{
+    AVTXContext *imdct = t->imdct[g->imdct_phs == 1][g->imdct_idx];
+    av_tx_fn  imdct_fn = t->imdct_fn[g->imdct_phs == 1][g->imdct_idx];
+    int n   = 1 << ff_dolby_e_imdct_bits_tab[g->imdct_idx];
+    int n2  = n >> 1;
+
+    switch (g->imdct_phs) {
+    case 0:
+        imdct_fn(imdct, result, (void *)values, sizeof(float));
+        for (int i = 0; i < n2; i++)
+            result[n2 + i] = result[n2 - i - 1];
+        break;
+    case 1:
+        imdct_fn(imdct, result, (void *)values, sizeof(float));
+        break;
+    case 2:
+        imdct_fn(imdct, result + n2, (void *)values, sizeof(float));
+        for (int i = 0; i < n2; i++)
+            result[i] = -result[n - i - 1];
+        break;
+    }
+}
+
+void ff_dolby_e_mdct_calc(DBETransform *t, const DBEGroup *g,
+                           float *values, const float *windowed_samples)
+{
+    AVTXContext *mdct = t->mdct[g->imdct_idx];
+    av_tx_fn  mdct_fn = t->mdct_fn[g->imdct_idx];
+    int n2 = 1 << (ff_dolby_e_imdct_bits_tab[g->imdct_idx] - 1);
+
+    mdct_fn(mdct, values, (void *)windowed_samples, sizeof(float));
+
+    /* The decoder's phs==1 groups use the FULL_IMDCT inverse path, which
+     * has an opposite intrinsic polarity from the half+mirror path used by
+     * phs 0/2. */
+    if (g->imdct_phs == 1)
+        for (int i = 0; i < n2; i++)
+            values[i] = -values[i];
+}
+
+void ff_dolby_e_mdct_calc_fold(DBETransform *t, const DBEGroup *g,
+                                float *values, const float *target)
+{
+    /* Round-trip impulse magnitude for phs 0/2, fit per transform size
+     * against fwd_scale_tab in ff_dolby_e_transform_init(). Kept separate
+     * from phs==1's magnitude since the two decode paths (half+mirror vs
+     * FULL_IMDCT) have different intrinsic scale. */
+    static const float fold_a_tab[3] = {
+        1.014699f, /* idx 0 */
+        1.014699f, /* idx 1 */
+        0.542523839363f, /* idx 2 */
+    };
+    int n2 = 1 << (ff_dolby_e_imdct_bits_tab[g->imdct_idx] - 1);
+    int n  = n2 * 2;
+    float a = fold_a_tab[g->imdct_idx];
+    /* n is at most 2 * (1 << (11 - 1)) = 2048. */
+    LOCAL_ALIGNED_32(float, abuf, [2048]);
+
+    for (int j = 0; j < n2 / 2; j++)
+        abuf[j] = target[n2 / 2 - 1 - j] / (2 * a);
+    for (int j = n2 / 2; j < 3 * n2 / 2; j++)
+        abuf[j] = -target[j - n2 / 2] / (2 * a);
+    for (int j = 3 * n2 / 2; j < n; j++)
+        abuf[j] = -target[(5 * n2 / 2 - 1) - j] / (2 * a);
+
+    ff_dolby_e_mdct_calc(t, g, values, abuf);
+}
diff --git a/libavcodec/dolby_e_transform.h b/libavcodec/dolby_e_transform.h
new file mode 100644
index 0000000000..50fcc34ef1
--- /dev/null
+++ b/libavcodec/dolby_e_transform.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 foo86
+ *
+ * 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
+ */
+
+#ifndef AVCODEC_DOLBY_E_TRANSFORM_H
+#define AVCODEC_DOLBY_E_TRANSFORM_H
+
+#include "libavutil/tx.h"
+#include "dolby_e_bitalloc.h"
+
+#define DBE_WINDOW_SIZE 3712
+
+/*
+ * Shared analysis=synthesis window table (US 5,899,969 confirms the
+ * analysis and synthesis windows are identical). Thread-safe, idempotent:
+ * safe to call from both decoder and encoder init.
+ */
+extern float ff_dolby_e_window[DBE_WINDOW_SIZE];
+void ff_dolby_e_init_window(void);
+
+extern const uint8_t ff_dolby_e_imdct_bits_tab[3];
+
+typedef struct DBETransform {
+    AVTXContext *imdct[2][3];   /* [phs==1][imdct_idx], synthesis (inverse) */
+    av_tx_fn     imdct_fn[2][3];
+    AVTXContext *mdct[3];       /* [imdct_idx], analysis (forward) */
+    av_tx_fn     mdct_fn[3];
+} DBETransform;
+
+int  ff_dolby_e_transform_init(DBETransform *t);
+void ff_dolby_e_transform_uninit(DBETransform *t);
+
+/*
+ * Synthesis (inverse), identical to imdct_calc() in dolby_e.c: produces the
+ * group's full n = 1 << ff_dolby_e_imdct_bits_tab[g->imdct_idx] samples into
+ * result[0..n), from n2 = n/2 spectral `values`.
+ */
+void ff_dolby_e_imdct_calc(DBETransform *t, const DBEGroup *g,
+                            float *result, const float *values);
+
+/*
+ * Analysis (forward), for imdct_phs==1 groups only: produces n2 spectral
+ * `values` from n windowed time-domain samples (already windowed by the
+ * caller). phs==1's inverse has a direct "self" term per output sample, so
+ * a standard windowed forward MDCT round-trips correctly via overlap-add.
+ */
+void ff_dolby_e_mdct_calc(DBETransform *t, const DBEGroup *g,
+                           float *values, const float *windowed_samples);
+
+/*
+ * Analysis (forward), for imdct_phs==0/2 groups only: the decoder's
+ * half+mirror inverse for these phases has no self term (only
+ * cross-position aliasing pairs), so a windowed forward MDCT of the input
+ * signal cannot reconstruct it; this solves the transform's fold relation
+ * directly instead.
+ *
+ * `target` is n2 = n/2 samples: the DESIRED, already window-multiplied
+ * buffer content for the group's direct region (buffer[0:n2) for phs==0,
+ * buffer[n2:n) for phs==2). Pre-multiplying by the synthesis window (not
+ * dividing) is required so overlapping neighbors' window^2 terms sum to
+ * the true signal via the Princen-Bradley condition.
+ */
+void ff_dolby_e_mdct_calc_fold(DBETransform *t, const DBEGroup *g,
+                                float *values, const float *target);
+
+#endif
-- 
2.52.0


>From 5df60c1f3f7f98c03654c614d717f47334e45e53 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:01:38 +0300
Subject: [PATCH 2/8] avcodec/dolby_e: add encoder

Adds a Dolby E encoder built on the bit allocator and MDCT/IMDCT helpers
just factored out of the decoder. Scope, per the design notes retained
in the file header:

 - All 5 SMPTE frame rates (23.98/24/25/29.97/30 fps) and all 3 SMPTE
   337M word depths (16/20/24, "word_bits") are supported, sharing the
   decoder's own sample-rate/envelope tables. Only the 25 fps/20-bit
   combination has been cross-checked against a real Dolby-authored
   sample; the rest are round-trip self-verified (encode -> mux ->
   demux -> decode) against this codebase's own decoder, not against
   third-party hardware or software.
 - Each channel uses a single-group configuration (gr_code=0, or 3 for
   the LFE channel, which the format itself forces); snr_ofs is
   binary-searched per channel against its word budget as the encoder's
   rate-control knob.
 - No bitstream whitening key, no run-length/escape-coded mantissas, no
   OAMD/Atmos evolution_data. metadata_crc is emitted as 0, matching
   every real sample measured so far; the CRC's exact bit range has not
   been independently derived.
 - encode_channel()'s per-block scratch buffers are fixed-size stack
   arrays rather than av_malloc/av_free per call, since the max size
   (1024 floats) is known at compile time.

Reviewers should treat the untested frame-rate/word-depth combinations
and the metadata_crc field as the areas most likely to need real-sample
validation before this is relied on for interop with third-party Dolby E
equipment.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 Changelog                 |   3 +
 doc/encoders.texi         |  37 ++
 doc/general_contents.texi |   2 +-
 libavcodec/Makefile       |   1 +
 libavcodec/allcodecs.c    |   1 +
 libavcodec/dolby_e_enc.c  | 938 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 981 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/dolby_e_enc.c

diff --git a/Changelog b/Changelog
index 38f1e10263..b674ca2e32 100644
--- a/Changelog
+++ b/Changelog
@@ -10,6 +10,9 @@ version <next>:
 - latticepal filter
 - DVD-Audio LPCM decoder and demuxing support
 - AVFoundation input device selection by unique ID and USB serial number
+- Dolby E encoder
+- SMPTE 337M (s337m) muxer
+- Dolby E carriage in WAV, MPEG-TS and IEC 61937 (spdif) muxers
 
 
 version 9.0:
diff --git a/doc/encoders.texi b/doc/encoders.texi
index 605638e040..172cbfeea4 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -518,6 +518,43 @@ Selected by Encoder (default)
 
 @end table
 
+@anchor{dolby_e}
+@section dolby_e
+
+Dolby E encoder.
+
+Dolby E packs up to 8 audio programs plus metadata into a burst that fits
+in one video frame period, for carriage over AES3/SDI professional audio
+links (typically demuxed/muxed as raw SMPTE 337M via the @code{s337m}
+muxer, or over IEC 61937-carrying containers such as WAV or MPEG-TS).
+The @var{sample_rate} passed to the encoder selects the SMPTE frame rate
+(23.98/24/25/29.97/30 fps) rather than an audio sample rate as such; see
+the @code{s337m} muxer and @code{dolby_e} decoder for the accepted rates.
+
+@subsection Options
+
+@table @option
+@item program_config
+Program configuration, selecting how the input channels are split into
+mono/stereo/5.1 programs (same numbering as the @code{dolby_e} decoder's
+@code{prog_conf} table).
+
+@item dialnorm
+Dialogue Level in dBFS, from -31 to -1. Default -31.
+
+@item word_bits
+SMPTE 337M word size in bits: 16, 20 or 24. Default 20.
+
+@item start_timecode
+Starting SMPTE timecode, as @code{hh:mm:ss:ff} (or @code{hh:mm:ss;ff} for
+drop-frame). Auto-increments once per encoded frame. If unset, the
+timecode field is explicitly marked invalid.
+
+@item fg_spc, fg_ofs, msk_mod
+Masking-curve shape tuning for the bit allocator. Expert options; the
+defaults match real-world Dolby E encoders for the cases tested so far.
+@end table
+
 @anchor{flac}
 @section flac
 
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index d81392e03f..c3334271d7 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1340,7 +1340,7 @@ following image formats are supported:
     @tab All versions except 5.1 are supported.
 @item DCA (DTS Coherent Acoustics)  @tab  X  @tab  X
     @tab supported extensions: XCh, XXCH, X96, XBR, XLL, LBR (partially)
-@item Dolby E  @tab     @tab  X
+@item Dolby E  @tab  X  @tab  X
 @item DPCM Cuberoot-Delta-Exact @tab  @tab  X
     @tab Used in few games.
 @item DPCM Gremlin           @tab     @tab  X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 0efe7898af..5476544ff1 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -344,6 +344,7 @@ OBJS-$(CONFIG_DFPWM_ENCODER)           += dfpwmenc.o
 OBJS-$(CONFIG_DNXHD_DECODER)           += dnxhddec.o dnxhddata.o
 OBJS-$(CONFIG_DNXHD_ENCODER)           += dnxhdenc.o dnxhddata.o
 OBJS-$(CONFIG_DOLBY_E_DECODER)         += dolby_e.o dolby_e_bitalloc.o dolby_e_transform.o dolby_e_parse.o kbdwin.o
+OBJS-$(CONFIG_DOLBY_E_ENCODER)         += dolby_e_enc.o dolby_e_bitalloc.o dolby_e_transform.o audio_frame_queue.o kbdwin.o
 OBJS-$(CONFIG_DPX_DECODER)             += dpx.o
 OBJS-$(CONFIG_DPX_ENCODER)             += dpxenc.o
 OBJS-$(CONFIG_DSD_LSBF_DECODER)        += dsddec.o dsd.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 2d7496a20d..b3b1467eb8 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -457,6 +457,7 @@ extern const FFCodec ff_dca_encoder;
 extern const FFCodec ff_dca_decoder;
 extern const FFCodec ff_dfpwm_encoder;
 extern const FFCodec ff_dfpwm_decoder;
+extern const FFCodec ff_dolby_e_encoder;
 extern const FFCodec ff_dolby_e_decoder;
 extern const FFCodec ff_dsd_lsbf_decoder;
 extern const FFCodec ff_dsd_msbf_decoder;
diff --git a/libavcodec/dolby_e_enc.c b/libavcodec/dolby_e_enc.c
new file mode 100644
index 0000000000..8590b9662c
--- /dev/null
+++ b/libavcodec/dolby_e_enc.c
@@ -0,0 +1,938 @@
+/*
+ * Dolby E encoder
+ *
+ * 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
+ */
+
+/*
+ * Dolby E encoder, limited scope:
+ *  - Supports all 5 SMPTE-defined frame rates and all 3 SMPTE 337M word
+ *    depths (16/20/24, "word_bits" AVOption), but only 25 fps/20-bit has
+ *    been checked against a real Dolby-authored sample; the rest are only
+ *    round-trip self-verified (encode -> mux -> demux -> decode).
+ *  - Every non-LFE channel uses gr_code=0 (single "long" group); LFE is
+ *    forced to gr_code=3 by the format itself. Short multi-group
+ *    (gr_code 1/2) is not produced, to keep bit-allocation search simple.
+ *  - idx[] is always 0 (no run-length savings, no escape-coded mantissas).
+ *  - fg_spc/fg_ofs/msk_mod are fixed; snr_ofs is binary-searched per
+ *    channel against its word budget -- the one real rate-control knob.
+ *  - key_present is always 0 (no bitstream whitening key).
+ *  - metadata_crc is emitted as 0 verbatim, matching every real sample
+ *    measured so far; the CRC algorithm itself is not implemented.
+ *  - evolution_data_exists is always 0 (no OAMD/Atmos metadata).
+ */
+
+#include <float.h>
+#include <math.h>
+
+#include "libavutil/channel_layout.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+#include "libavutil/mem_internal.h"
+#include "libavutil/opt.h"
+#include "libavutil/timecode.h"
+
+#include "audio_frame_queue.h"
+#include "codec_internal.h"
+#include "encode.h"
+#include "put_bits.h"
+#include "dolby_e.h"
+#include "dolby_e_bitalloc.h"
+#include "dolby_e_transform.h"
+
+/* Max samples of lookahead the fold construction needs beyond one frame. */
+#define LOOKAHEAD    256
+#define BUF_SAMPLES  (2 * FRAME_SAMPLES)
+/* >= 8 programs * AC3_PGM_BITS(126) + max word_bits(24)-1 headroom. */
+#define MAX_TAIL_BITS 1536
+
+/* fr_code (1-5) -> avctx->sample_rate, exactly mirroring the decoder's own
+ * sample_rate_tab (dolby_e_parse.c) -- FRAME_SAMPLES itself (dolby_e.h) is a
+ * fixed 1792 regardless of frame rate (confirmed shared with the decoder,
+ * which sets frame->nb_samples = FRAME_SAMPLES unconditionally); what
+ * varies by frame rate is only this reported rate and the coded burst
+ * envelope below. */
+static const int fr_code_sample_rate_tab[6] = {
+    0, 42965, 43008, 44800, 53706, 53760
+};
+
+/* fr_code -> total coded words per burst, exactly mirroring the s337m
+ * demuxer/muxer's own 4-bucket envelope table (libavformat/s337m.c
+ * s337m_get_offset_and_codec(), libavformat/s337menc.c burst_payload_words()):
+ * that table is keyed on word count alone, not word_bits or frame rate, and
+ * has only 4 distinct buckets for the 5 frame rates -- 29.97 and 30 fps
+ * share the same bucket there, so they do here too, deferring entirely to
+ * the demuxer's own already-implemented mapping rather than inventing a
+ * 5th value nothing downstream would recognize. */
+static const int fr_code_total_words_tab[6] = {
+    0, 3644, 3640, 3648, 3040, 3040
+};
+
+/* Coded (Dolby-native) channel index -> AVFrame channel index, exactly
+ * mirroring dolby_e.c's own ch_reorder_4/6/8/n tables (not exported, so
+ * duplicated verbatim rather than guessed) -- e.g. for 6 channels the coded
+ * buffer order is L,C,Ls,R,LFE,Rs, while AVFrame/ch_layout order is the
+ * standard L,R,C,LFE,Ls,Rs, per EBU Tech 3285 Suppl. 6's own documented
+ * "L for all programmes, then C for all programmes, ..." channel sequence.
+ * The encoder needs the INVERSE of the decoder's mapping: source sample
+ * data for coded channel ch comes from frame->extended_data[reorder[ch]]. */
+static const uint8_t ch_reorder_4[4] = { 0, 2, 1, 3 };
+static const uint8_t ch_reorder_6[6] = { 0, 2, 4, 1, 3, 5 };
+static const uint8_t ch_reorder_8[8] = { 0, 2, 6, 4, 1, 3, 7, 5 };
+static const uint8_t ch_reorder_n[8] = { 0, 2, 4, 6, 1, 3, 5, 7 };
+
+/* fr_code -> nominal frame rate, for AVTimecode; mirrors
+ * fr_code_sample_rate_tab (both ultimately encode the same 5 SMPTE frame
+ * rates). */
+static const AVRational fr_code_rate_tab[6] = {
+    { 0, 0 }, { 24000, 1001 }, { 24, 1 }, { 25, 1 }, { 30000, 1001 }, { 30, 1 }
+};
+
+/* Per-program AC-3-style metadata block written into the metadata segment's
+ * tail. Field layout/order/width per SMPTE RDD 6-2006 ("Description and
+ * Guide to the Use of the Dolby E Audio Metadata Serial Bitstream"): EBU
+ * Tech 3285 Supplement 6 documents the first 68 bits (through ext_bsi2);
+ * the bbc/bmx open-source RDD6 implementation
+ * (RDD6DolbyDigitalCompleteExtBSI) covers the fuller 126-bit tail used
+ * here. */
+#define AC3_PGM_BITS 126
+
+/* Channel count per program, in program order, for each program_config
+ * (0-padded); mirrors EBU Tech 3285 Supplement 6 Table 5 / SMPTE RDD 6's
+ * Programme Configuration table exactly. Only ever 1, 2, 4, 6 or 8 channels
+ * appear; a 6 or 8 always denotes the "X.1" program, which per that table is
+ * always program 0 -- so LFE presence per program needs no extra table, see
+ * dolby_e_encode_frame(). */
+static const uint8_t prog_ch_counts_tab[MAX_PROG_CONF + 1][8] = {
+    { 6, 2, 0, 0, 0, 0, 0, 0 }, /*  0: 5.1+2 */
+    { 6, 1, 1, 0, 0, 0, 0, 0 }, /*  1: 5.1+1+1 */
+    { 4, 4, 0, 0, 0, 0, 0, 0 }, /*  2: 4+4 */
+    { 4, 2, 2, 0, 0, 0, 0, 0 }, /*  3: 4+2+2 */
+    { 4, 2, 1, 1, 0, 0, 0, 0 }, /*  4: 4+2+1+1 */
+    { 4, 1, 1, 1, 1, 0, 0, 0 }, /*  5: 4+1+1+1+1 */
+    { 2, 2, 2, 2, 0, 0, 0, 0 }, /*  6: 2+2+2+2 */
+    { 2, 2, 2, 1, 1, 0, 0, 0 }, /*  7: 2+2+2+1+1 */
+    { 2, 2, 1, 1, 1, 1, 0, 0 }, /*  8: 2+2+1+1+1+1 */
+    { 2, 1, 1, 1, 1, 1, 1, 0 }, /*  9: 2+1+1+1+1+1+1 */
+    { 1, 1, 1, 1, 1, 1, 1, 1 }, /* 10: 1+1+1+1+1+1+1+1 */
+    { 6, 0, 0, 0, 0, 0, 0, 0 }, /* 11: 5.1 */
+    { 4, 2, 0, 0, 0, 0, 0, 0 }, /* 12: 4+2 */
+    { 4, 1, 1, 0, 0, 0, 0, 0 }, /* 13: 4+1+1 */
+    { 2, 2, 2, 0, 0, 0, 0, 0 }, /* 14: 2+2+2 */
+    { 2, 2, 1, 1, 0, 0, 0, 0 }, /* 15: 2+2+1+1 */
+    { 2, 1, 1, 1, 1, 0, 0, 0 }, /* 16: 2+1+1+1+1 */
+    { 1, 1, 1, 1, 1, 1, 0, 0 }, /* 17: 1+1+1+1+1+1 */
+    { 4, 0, 0, 0, 0, 0, 0, 0 }, /* 18: 4 */
+    { 2, 2, 0, 0, 0, 0, 0, 0 }, /* 19: 2+2 */
+    { 2, 1, 1, 0, 0, 0, 0, 0 }, /* 20: 2+1+1 */
+    { 1, 1, 1, 1, 0, 0, 0, 0 }, /* 21: 1+1+1+1 */
+    { 8, 0, 0, 0, 0, 0, 0, 0 }, /* 22: 7.1 */
+    { 8, 0, 0, 0, 0, 0, 0, 0 }, /* 23: 7.1 Screen */
+};
+
+/* ac3_acmod/ac3_lfeon for a program with pch channels (see prog_ch_counts_tab)
+ * of which has_lfe indicates one is the LFE. AC-3's acmod has no channel
+ * count above 3/2 (5 full-bandwidth channels), so an 8-channel ("7.1")
+ * program is clamped to 3/2 -- the two extra surround channels have no
+ * representation in this field, which is otherwise purely descriptive
+ * downmix metadata and does not affect this encoder's own round-trip. */
+static void ac3_acmod_for_program(int pch, int has_lfe, int *acmod, int *lfeon)
+{
+    *lfeon = has_lfe;
+    switch (pch) {
+    case 1:  *acmod = 1; break; /* 1/0: C */
+    case 2:  *acmod = 2; break; /* 2/0: L, R */
+    case 4:  *acmod = 6; break; /* 2/2: L, R, Ls, Rs (best-effort default) */
+    default: *acmod = 7; break; /* 3/2: L, C, R, Ls, Rs (6 or 8 ch, has_lfe) */
+    }
+}
+
+/* Writes one AC3_PGM_BITS-bit per-program metadata block (dialnorm and
+ * associated AC-3-style fields); see the AC3_PGM_BITS comment above for
+ * sourcing. All fields are unconditionally present regardless of acmod. */
+static void put_ac3_program_metadata(PutBitContext *pb, int program_id,
+                                      int acmod, int lfeon, int dialnorm)
+{
+    put_bits(pb, 5, program_id);
+    put_bits(pb, 5, 31);        /* ac3_datarate: not specified */
+    put_bits(pb, 3, 0);         /* ac3_bsmod: complete main */
+    put_bits(pb, 3, acmod);
+    put_bits(pb, 2, 0);         /* ac3_cmixlev */
+    put_bits(pb, 2, 0);         /* ac3_surmixlev */
+    put_bits(pb, 2, 0);         /* ac3_dsurmod */
+    put_bits(pb, 1, lfeon);
+    put_bits(pb, 5, dialnorm);
+    put_bits(pb, 1, 0);         /* ac3_langcode */
+    put_bits(pb, 8, 0);         /* ac3_langcod */
+    put_bits(pb, 1, 0);         /* ac3_audprodie */
+    put_bits(pb, 5, 0);         /* ac3_mixlevel */
+    put_bits(pb, 2, 0);         /* ac3_roomtyp */
+    put_bits(pb, 1, 0);         /* ac3_copyrightb */
+    put_bits(pb, 1, 1);         /* ac3_origbs: original bitstream */
+    put_bits(pb, 1, 0);         /* ac3_xbsi1e */
+    put_bits(pb, 2, 0);         /* ac3_dmixmod */
+    put_bits(pb, 3, 0);         /* ac3_ltrtcmixlev */
+    put_bits(pb, 3, 0);         /* ac3_ltrtsurmixlev */
+    put_bits(pb, 3, 0);         /* ac3_lorocmixlev */
+    put_bits(pb, 3, 0);         /* ac3_lorosurmixlev */
+    put_bits(pb, 1, 0);         /* ac3_xbsi2e */
+    put_bits(pb, 2, 0);         /* ac3_dsurexmod */
+    put_bits(pb, 2, 0);         /* ac3_dheadphonmod */
+    put_bits(pb, 1, 0);         /* ac3_adconvtyp */
+    put_bits(pb, 8, 0);         /* reserved */
+    put_bits(pb, 1, 0);         /* reserved */
+    put_bits(pb, 1, 0);         /* ac3_hpfon */
+    put_bits(pb, 1, 0);         /* ac3_bwlpfon */
+    put_bits(pb, 1, 0);         /* ac3_lfelpfon */
+    put_bits(pb, 1, 0);         /* ac3_sur90on */
+    put_bits(pb, 1, 0);         /* ac3_suratton */
+    put_bits(pb, 1, 0);         /* ac3_rfpremphon */
+    put_bits(pb, 1, 0);         /* ac3_compre */
+    put_bits(pb, 8, 0);         /* ac3_compr1 */
+    put_bits(pb, 1, 0);         /* ac3_dynrnge */
+    put_bits(pb, 8, 0);         /* ac3_dynrng1 */
+    put_bits(pb, 8, 0);         /* ac3_dynrng2 */
+    put_bits(pb, 8, 0);         /* ac3_dynrng3 */
+    put_bits(pb, 8, 0);         /* ac3_dynrng4 */
+    put_bits(pb, 1, 0);         /* reserved */
+}
+
+typedef struct DBEEncChannel {
+    int present;
+    int is_lfe;
+    int gr_code, bw_code;
+    DBEGroup group;
+
+    int nb_mstr_exp;
+    int mstr_exp[DBE_MAX_MSTR_EXP];
+    int bias_exp[50];
+    int exponents[50];
+
+    int fg_spc, fg_ofs, msk_mod;
+    int snr_ofs;
+    int bap[50];
+
+    int nb_mantissas;
+    int mantissa[1024];
+
+    int tail_bits;
+} DBEEncChannel;
+
+typedef struct DBEEncContext {
+    const AVClass *class;
+
+    int prog_conf;
+    int nb_channels, nb_programs;
+    int lfe_channel;
+    /* coded channel -> AVFrame channel, see ch_reorder_* above */
+    const uint8_t *ch_reorder;
+    int dialogue_level;
+    /* AVOptions: masking-curve shape tuning, see encode_channel() */
+    int fg_spc, fg_ofs, msk_mod;
+
+    int word_bits;   /* AVOption: 16, 20 or 24 */
+    int word_bytes;  /* (word_bits + 7) / 8 */
+    int fr_code;     /* derived from avctx->sample_rate, see
+                       * fr_code_sample_rate_tab */
+    int total_words; /* derived from fr_code, see fr_code_total_words_tab */
+
+    char *start_timecode; /* AVOption: HH:MM:SS[:;]FF, or NULL */
+    AVTimecode tc;
+    int have_tc;
+
+    DBETransform transform;
+    AudioFrameQueue afq;
+
+    float **samples;         /* [nb_channels][BUF_SAMPLES] */
+    int buffered_frames;
+    int64_t frame_count;
+
+    DBEEncChannel enc_ch[2][MAX_CHANNELS]; /* [seg_id][ch] */
+    int ch_size[MAX_CHANNELS];
+
+    uint8_t *pktbuf;
+    int pktbuf_size;
+} DBEEncContext;
+
+/* Writes the 64-bit e_SMPTE_time_code field. Layout per EBU Tech 3285
+ * Supplement 6 Table 7: each of the 4 BCD groups (H/M/S/F) is bit-for-bit
+ * identical to av_timecode_get_smpte()'s own weighted-BCD sub-encoding
+ * (2 tens bits + 4 units bits, verified directly against libavutil/timecode.c);
+ * only the byte-level ARRANGEMENT differs, interleaving each group with a
+ * byte of reserved/user bits that this encoder has no data for and leaves
+ * 0. With no start_timecode configured, the field is left explicitly
+ * invalid via EBU's own documented convention (hours BCD = 0x3F) rather
+ * than a plausible-looking all-zero (which would read as a spurious valid
+ * 00:00:00:00). */
+static void put_smpte_timecode(PutBitContext *pb, DBEEncContext *s)
+{
+    if (s->have_tc) {
+        uint32_t tcsmpte =
+            av_timecode_get_smpte_from_framenum(&s->tc, (int)s->frame_count);
+        unsigned hours_bcd6   = tcsmpte & 0x3F;
+        unsigned minutes_bcd7 = (tcsmpte >> 8)  & 0x7F;
+        unsigned seconds_bcd7 = (tcsmpte >> 16) & 0x7F;
+        unsigned frames_bcd6  = (tcsmpte >> 24) & 0x3F;
+        unsigned drop         = (tcsmpte >> 30) & 1;
+
+        put_bits(pb, 8, 0);                              /* byte0: reserved */
+        put_bits(pb, 2, 0); put_bits(pb, 6, hours_bcd6);  /* byte1 */
+        put_bits(pb, 8, 0);                               /* byte2: reserved */
+        put_bits(pb, 1, 0); put_bits(pb, 7, minutes_bcd7); /* byte3 */
+        put_bits(pb, 8, 0);                               /* byte4: reserved */
+        put_bits(pb, 1, 0); put_bits(pb, 7, seconds_bcd7); /* byte5 */
+        put_bits(pb, 8, 0);                               /* byte6: reserved */
+        put_bits(pb, 1, 0); put_bits(pb, 1, drop);
+        put_bits(pb, 6, frames_bcd6);                      /* byte7 */
+    } else {
+        put_bits(pb, 8, 0);
+        /* hours = 0x3F: explicitly invalid, EBU Tech 3285 Suppl. 6 4.1.3 */
+        put_bits(pb, 8, 0x3F);
+        for (int k = 0; k < 6; k++)
+            put_bits(pb, 8, 0);
+    }
+}
+
+static float dbe_exp_val(int e)
+{
+    int i = e >> 1;
+    float base = 1.0f / (float)(1 << i);
+    return (e & 1) ? base * 0.70710678f : base;
+}
+
+/* Largest e in [0,max_e] with dbe_exp_val(e) >= mag (best-fit block
+ * exponent). */
+static int choose_exponent(float mag, int max_e)
+{
+    int e;
+    if (mag <= 0.0f)
+        return max_e;
+    if (mag >= 1.0f)
+        return 0;
+    for (e = 0; e < max_e; e++)
+        if (dbe_exp_val(e + 1) < mag)
+            break;
+    return e;
+}
+
+static int quantize_mantissa(float x, int size1, float exp_val)
+{
+    int half, v;
+    if (size1 <= 0 || exp_val <= 0.0f)
+        return 0;
+    half = 1 << (size1 - 1);
+    v = (int)lrintf(x * half / exp_val);
+    if (v > half - 1) v = half - 1;
+    if (v < -half)    v = -half;
+    return v;
+}
+
+/* Encodes one channel's one segment (single group: gr_code 0 or 3). */
+static void encode_channel(DBEEncContext *s, DBEEncChannel *c, int seg_id,
+                            const float *signal, long anchor, int budget_bits)
+{
+    const DBEGroup *g = &c->group;
+    int n2 = 1 << (ff_dolby_e_imdct_bits_tab[g->imdct_idx] - 1);
+    /* n2 is at most 1 << (11 - 1) = 1024. */
+    LOCAL_ALIGNED_32(float, values, [1024]);
+    LOCAL_ALIGNED_32(float, target, [1024]);
+    int direct_lo = (g->imdct_phs == 0) ? 0 : n2;
+    long base = anchor + g->dst_ofs + (direct_lo - g->src_ofs);
+    int win_base = g->win_ofs + (direct_lo - g->src_ofs);
+    int fixed_bits, avail_mantissa_bits;
+    int lo_snr, hi_snr, best_snr;
+
+    for (int i = 0; i < n2; i++) {
+        long t = base + i;
+        target[i] = (t >= 0) ? signal[t] : 0.0f;
+        target[i] *= ff_dolby_e_window[win_base + i];
+    }
+
+    /* TDAC boundary correction: win_len (1152) exceeds n2 (1024), so the
+     * outer overlap_half=128 samples on each side of target also
+     * overlap-add with the adjacent, opposite-phase group in the decoder
+     * (dolby_e.c transform(): dst[i] += src[i]*win[i]). For the phs=0 ->
+     * phs=2 direction this leaves an uncancelled cross term -- a
+     * Princen-Bradley pair (window[a]^2+window[b]^2=1), not a plain
+     * windowed sample; phs=2 -> phs0 already cancels exactly without it.
+     * overlap_half is derived from the group's own win_len/HOP rather
+     * than hardcoded, since both groups this encoder uses (gr_code 0 and
+     * 3) share the same relationship. */
+    {
+        int overlap_half = (g->win_len - 896) / 2;
+
+        if (g->imdct_phs == 0) {
+            for (int i = n2 - overlap_half; i < n2; i++) {
+                long mt = anchor + (2 * n2 - 1 - i);
+                float ms = (mt >= 0) ? signal[mt] : 0.0f;
+                target[i] += ms * ff_dolby_e_window[win_base - 896 + i];
+            }
+        } else if (g->imdct_phs == 2) {
+            for (int i = 0; i < overlap_half; i++) {
+                long mt = base - 1 - i;
+                float ms = (mt >= 0) ? signal[mt] : 0.0f;
+                target[i] -= ms * ff_dolby_e_window[win_base - 1 - i];
+            }
+        }
+    }
+
+    ff_dolby_e_mdct_calc_fold(&s->transform, g, values, target);
+
+    /* Exponents: mstr_exp fixed at 0, bias_exp carries the full [0,31]
+     * range directly. Costs some dynamic range on very quiet subbands but
+     * avoids mstr_exp fitting entirely. */
+    c->nb_mstr_exp = ff_dolby_e_nb_mstr_exp_tab[c->gr_code];
+    for (int i = 0; i < c->nb_mstr_exp; i++)
+        c->mstr_exp[i] = 0;
+
+    {
+        int mnt_idx = 0;
+        for (int j = 0; j < g->nb_exponent; j++) {
+            int count = g->nb_mantissa[j];
+            float mag = 0.0f;
+            for (int k = 0; k < count; k++)
+                mag = FFMAX(mag, fabsf(values[mnt_idx + k]));
+            c->bias_exp[j]   = choose_exponent(mag, 31);
+            c->exponents[j]  = c->bias_exp[j];
+            mnt_idx += count;
+        }
+        c->nb_mantissas = mnt_idx;
+    }
+
+    /* Masking-curve shape knobs (AVOptions, for tuning); snr_ofs is the
+     * binary-searched rate-control knob. */
+    c->fg_spc = s->fg_spc;
+    c->fg_ofs = s->fg_ofs;
+    c->msk_mod = s->msk_mod;
+
+    /* fixed_bits = mstr_exp + bias_exp + fg_spc + fg_ofs + msk_mod +
+     * delta_bit_alloc + snr_ofs + idx_present (+ gr_code + bw_code for
+     * non-LFE channels). */
+    fixed_bits = c->nb_mstr_exp * 2 + g->nb_exponent * 5
+               + 2 + 3 + 1
+               + 1 + 8 + 1;
+    if (!c->is_lfe)
+        fixed_bits += 2 + 3;
+
+    avail_mantissa_bits = budget_bits - fixed_bits;
+
+    lo_snr = 1;
+    hi_snr = 255;
+    best_snr = 0;
+    while (lo_snr <= hi_snr) {
+        int mid = (lo_snr + hi_snr) / 2;
+        int bits;
+
+        ff_dolby_e_bit_allocate(g->nb_exponent, g->imdct_idx, s->fr_code,
+                                 c->exponents, c->bap,
+                                 c->fg_spc, c->fg_ofs, c->msk_mod, mid);
+        bits = 0;
+        for (int j = 0; j < g->nb_exponent; j++)
+            bits += g->nb_mantissa[j] * ff_dolby_e_mantissa_size1[c->bap[j]][0];
+
+        if (bits <= avail_mantissa_bits) {
+            best_snr = mid;
+            lo_snr = mid + 1;
+        } else {
+            hi_snr = mid - 1;
+        }
+    }
+    c->snr_ofs = best_snr;
+    if (c->snr_ofs) {
+        ff_dolby_e_bit_allocate(g->nb_exponent, g->imdct_idx, s->fr_code,
+                                 c->exponents, c->bap,
+                                 c->fg_spc, c->fg_ofs, c->msk_mod, c->snr_ofs);
+    } else {
+        memset(c->bap, 0, sizeof(c->bap));
+    }
+
+    {
+        int mnt_idx = 0;
+        for (int j = 0; j < g->nb_exponent; j++) {
+            int size1 = ff_dolby_e_mantissa_size1[c->bap[j]][0];
+            float exp_val = dbe_exp_val(c->exponents[j]);
+            for (int k = 0; k < g->nb_mantissa[j]; k++, mnt_idx++)
+                c->mantissa[mnt_idx] =
+                    quantize_mantissa(values[mnt_idx], size1, exp_val);
+        }
+    }
+}
+
+static void serialize_channel(PutBitContext *pb, const DBEEncChannel *c)
+{
+    const DBEGroup *g = &c->group;
+
+    if (!c->is_lfe) {
+        put_bits(pb, 2, c->gr_code);
+        put_bits(pb, 3, c->bw_code);
+    }
+
+    for (int i = 0; i < c->nb_mstr_exp; i++)
+        put_bits(pb, 2, c->mstr_exp[i]);
+    for (int j = 0; j < g->nb_exponent; j++)
+        put_bits(pb, 5, c->bias_exp[j]);
+
+    put_bits(pb, 2, c->fg_spc);
+    put_bits(pb, 3, c->fg_ofs);
+    put_bits(pb, 1, c->msk_mod);
+
+    put_bits(pb, 1, 0); /* delta_bit_alloc: always 0 */
+    put_bits(pb, 8, c->snr_ofs);
+
+    put_bits(pb, 1, 0); /* idx_present: always 0 -> decoder defaults idx[]=0 */
+
+    {
+        int mnt_idx = 0;
+        for (int j = 0; j < g->nb_exponent; j++) {
+            int size1 = ff_dolby_e_mantissa_size1[c->bap[j]][0];
+            if (!size1) {
+                mnt_idx += g->nb_mantissa[j];
+                continue;
+            }
+            for (int k = 0; k < g->nb_mantissa[j]; k++, mnt_idx++)
+                put_sbits(pb, size1, c->mantissa[mnt_idx]);
+        }
+    }
+}
+
+/* Converts a tightly-packed (word_bits-per-word) flat buffer into the
+ * on-wire word container: 16-bit words fill their 2-byte container exactly;
+ * 20- and 24-bit words are carried in a 3-byte container, left-justified
+ * (matching ff_dolby_e_parse_header()'s own sync-word masks -- e.g. the
+ * 20-bit sync 07 88 e0 has its low nibble free for the key, so the 20-bit
+ * payload is shifted up by 24-20=4 bits; 24-bit words fill the container
+ * exactly, shift 0). */
+static void pack_words(uint8_t *dst, const uint8_t *flat_buf, int flat_size,
+                        int nb_words, int word_bits)
+{
+    GetBitContext gb;
+    int word_bytes = (word_bits + 7) / 8;
+    init_get_bits8(&gb, flat_buf, flat_size);
+
+    for (int i = 0; i < nb_words; i++) {
+        unsigned w = get_bits_long(&gb, word_bits);
+        if (word_bits == 16)
+            AV_WB16(dst, w);
+        else
+            AV_WB24(dst, w << (24 - word_bits));
+        dst += word_bytes;
+    }
+}
+
+static int dolby_e_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
+                                 const AVFrame *frame, int *got_packet_ptr)
+{
+    DBEEncContext *s = avctx->priv_data;
+    int i = s->nb_channels / 2, j = s->nb_channels;
+    int mtd_size, mtd_known_bits, tail_bits, ac3_meta_bits;
+    int mtd_ext_size, meter_size;
+    int overhead_words, ch_budget_words, lfe_budget_words, nonlfe_budget_words;
+    int nb_lfe = (s->lfe_channel >= 0) ? 1 : 0;
+    int ret;
+    uint8_t *dst;
+    long frame_anchor;
+
+    if (frame) {
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            memmove(s->samples[ch], s->samples[ch] + FRAME_SAMPLES,
+                    FRAME_SAMPLES * sizeof(float));
+            memcpy(s->samples[ch] + FRAME_SAMPLES,
+                   frame->extended_data[s->ch_reorder[ch]],
+                   frame->nb_samples * sizeof(float));
+        }
+        if ((ret = ff_af_queue_add(&s->afq, frame)) < 0)
+            return ret;
+        s->buffered_frames++;
+    } else {
+        if (!s->afq.remaining_samples) {
+            *got_packet_ptr = 0;
+            return 0;
+        }
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            memmove(s->samples[ch], s->samples[ch] + FRAME_SAMPLES,
+                    FRAME_SAMPLES * sizeof(float));
+            memset(s->samples[ch] + FRAME_SAMPLES, 0,
+                   FRAME_SAMPLES * sizeof(float));
+        }
+    }
+
+    if (s->buffered_frames < 2 && frame) {
+        *got_packet_ptr = 0;
+        return 0;
+    }
+
+    /* Metadata segment size and word-budget bookkeeping. The total packet
+     * must consume the FULL total_words burst envelope exactly: the s337m
+     * demuxer's data_size/word_bits lookup (libavformat/s337m.c) only
+     * recognizes a handful of exact values (3648 for 25 fps) and rejects
+     * anything else with AVERROR_PATCHWELCOME -- there is no such thing as
+     * a "slightly short" Dolby E frame here, unlike most codecs' packets. */
+    mtd_known_bits = 132 + 35 * s->nb_channels + 10 * s->nb_programs;
+    ac3_meta_bits = AC3_PGM_BITS * s->nb_programs;
+    mtd_size = (mtd_known_bits + ac3_meta_bits + s->word_bits +
+                s->word_bits - 1) / s->word_bits;
+
+    mtd_ext_size = 2 * s->nb_programs + 1;
+    meter_size   = s->nb_channels;
+
+    /* sync(1) + mtd_size + mtd's own trailing gap(1) + four parse_audio
+     * trailing gaps(4) + mtd_ext blob(mtd_ext_size+1) + meter
+     * blob(meter_size+1). */
+    overhead_words = 1 + mtd_size + 1 + 4 + (mtd_ext_size + 1) +
+                      (meter_size + 1);
+    if ((s->total_words - overhead_words) & 1) {
+        /* ch_size is spent identically in both segments, so the remaining
+         * budget must be even; absorb the odd word as metadata padding. */
+        mtd_size++;
+        overhead_words++;
+    }
+    tail_bits = mtd_size * s->word_bits - mtd_known_bits - s->word_bits;
+    av_assert0(tail_bits >= 0 && tail_bits < MAX_TAIL_BITS);
+
+    ch_budget_words = (s->total_words - overhead_words) / 2;
+
+    {
+        int nb_nonlfe = s->nb_channels - nb_lfe;
+        int nonlfe_total, nonlfe_remainder, first_nonlfe = -1;
+
+        lfe_budget_words = nb_lfe ? FFMIN(40, ch_budget_words) : 0;
+        nonlfe_total = ch_budget_words - nb_lfe * lfe_budget_words;
+        nonlfe_budget_words = nb_nonlfe > 0 ? nonlfe_total / nb_nonlfe : 0;
+        nonlfe_remainder     = nb_nonlfe > 0 ? nonlfe_total % nb_nonlfe : 0;
+
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            if (ch == s->lfe_channel) {
+                s->ch_size[ch] = lfe_budget_words;
+            } else {
+                s->ch_size[ch] = nonlfe_budget_words;
+                if (first_nonlfe < 0)
+                    first_nonlfe = ch;
+            }
+        }
+        if (first_nonlfe >= 0)
+            s->ch_size[first_nonlfe] += nonlfe_remainder;
+    }
+
+    /* frame_anchor: absolute sample index of THIS frame's start within the
+     * per-channel sliding buffer (segment 0 always starts at buffer[0]). */
+    frame_anchor = 0;
+
+    for (int ch = 0; ch < s->nb_channels; ch++) {
+        for (int seg_id = 0; seg_id < 2; seg_id++) {
+            DBEEncChannel *c = &s->enc_ch[seg_id][ch];
+            long anchor = frame_anchor + seg_id * 896;
+
+            memset(c, 0, sizeof(*c));
+            c->present = 1;
+            c->is_lfe  = (ch == s->lfe_channel);
+            c->gr_code = c->is_lfe ? 3 : 0;
+            c->bw_code = c->is_lfe ? 29 : 0;
+            c->group   = ff_dolby_e_frm_ofs_tab[seg_id][c->gr_code][0];
+
+            encode_channel(s, c, seg_id, s->samples[ch], anchor,
+                           s->ch_size[ch] * s->word_bits);
+        }
+    }
+
+    ret = ff_get_encode_buffer(avctx, avpkt,
+                                s->total_words * s->word_bytes, 0);
+    if (ret < 0)
+        return ret;
+    dst = avpkt->data;
+
+    /* --- metadata segment --- */
+    /* Sync word per word_bits, taken verbatim from ff_dolby_e_parse_header()'s
+     * own detection masks (dolby_e_parse.c): 16-bit=07 8e, 20-bit=07 88 e0,
+     * 24-bit=07 88 8e; key_present is the low bit of the last byte,
+     * always 0. */
+    if (s->word_bits == 16) {
+        dst[0] = 0x07; dst[1] = 0x8e;
+    } else if (s->word_bits == 20) {
+        dst[0] = 0x07; dst[1] = 0x88; dst[2] = 0xe0;
+    } else {
+        dst[0] = 0x07; dst[1] = 0x88; dst[2] = 0x8e;
+    }
+    dst += s->word_bytes;
+    {
+        int flat_bytes = (mtd_size * s->word_bits + 7) / 8 +
+                         AV_INPUT_BUFFER_PADDING_SIZE;
+        uint8_t *flat = av_malloc(flat_bytes);
+        PutBitContext pb;
+
+        init_put_bits(&pb, flat, flat_bytes);
+        put_bits(&pb, 4, 1);           /* metadata_revision_id */
+        put_bits(&pb, 10, mtd_size);
+        put_bits(&pb, 6, s->prog_conf);
+        put_bits(&pb, 4, s->fr_code);
+        put_bits(&pb, 4, s->fr_code);   /* fr_code_orig */
+
+        put_bits(&pb, 16, (int)(s->frame_count & 0xffff));
+        put_smpte_timecode(&pb, s);
+        put_bits(&pb, 1, 0);           /* evolution_data_exists: always 0 */
+        put_bits(&pb, 7, 0);           /* metadata_reserved_bits */
+
+        for (int ch = 0; ch < s->nb_channels; ch++)
+            put_bits(&pb, 10, s->ch_size[ch]);
+        put_bits(&pb, 8, mtd_ext_size);
+        put_bits(&pb, 8, meter_size);
+
+        for (int p = 0; p < s->nb_programs; p++) {
+            put_bits(&pb, 8, 0); /* description_text */
+            put_bits(&pb, 2, 0); /* bandwidth_id */
+        }
+        for (int ch = 0; ch < s->nb_channels; ch++) {
+            put_bits(&pb, 4, 1);    /* rev_id */
+            put_bits(&pb, 1, 0);    /* reserved */
+            put_bits(&pb, 10, 960); /* begin_gain: unity */
+            put_bits(&pb, 10, 960); /* end_gain: unity */
+        }
+        /* Per-program AC-3-style metadata (dialnorm et al.), see
+         * AC3_PGM_BITS above. LFE, per EBU Tech 3285 Suppl. 6 Table 5 / RDD 6,
+         * is always carried by program 0 when present at all. */
+        for (int p = 0; p < s->nb_programs; p++) {
+            int pch = prog_ch_counts_tab[s->prog_conf][p];
+            int has_lfe = (p == 0) && (s->lfe_channel >= 0);
+            int acmod, lfeon;
+
+            ac3_acmod_for_program(pch, has_lfe, &acmod, &lfeon);
+            put_ac3_program_metadata(&pb, p, acmod, lfeon, -s->dialogue_level);
+        }
+        for (int k = 0; k < tail_bits - ac3_meta_bits; k++)
+            put_bits(&pb, 1, 0);
+        /* metadata_crc: 0, matches every measured real sample */
+        put_bits(&pb, s->word_bits, 0);
+
+        flush_put_bits(&pb);
+        pack_words(dst, flat, flat_bytes, mtd_size, s->word_bits);
+        av_free(flat);
+    }
+    dst += mtd_size * s->word_bytes;
+    memset(dst, 0, s->word_bytes); /* metadata's own trailing gap word */
+    dst += s->word_bytes;
+
+    /* --- audio segments (seg 0 halves, ext, seg 1 halves, meter) --- */
+    for (int seg_id = 0; seg_id < 2; seg_id++) {
+        int ranges[2][2] = { { 0, i }, { i, j } };
+
+        for (int r = 0; r < 2; r++) {
+            for (int ch = ranges[r][0]; ch < ranges[r][1]; ch++) {
+                DBEEncChannel *c = &s->enc_ch[seg_id][ch];
+                int words = s->ch_size[ch];
+                int flat_bytes = (words * s->word_bits + 7) / 8 +
+                                 AV_INPUT_BUFFER_PADDING_SIZE;
+                uint8_t *flat = av_malloc(flat_bytes);
+                PutBitContext pb;
+
+                init_put_bits(&pb, flat, flat_bytes);
+                serialize_channel(&pb, c);
+                c->tail_bits = words * s->word_bits - put_bits_count(&pb);
+                av_assert0(c->tail_bits >= 0 && c->tail_bits < MAX_TAIL_BITS);
+                for (int k = 0; k < c->tail_bits; k++)
+                    put_bits(&pb, 1, 0);
+                flush_put_bits(&pb);
+
+                pack_words(dst, flat, flat_bytes, words, s->word_bits);
+                av_free(flat);
+                dst += words * s->word_bytes;
+            }
+            /* trailing gap word per parse_audio() call */
+            memset(dst, 0, s->word_bytes);
+            dst += s->word_bytes;
+        }
+
+        if (seg_id == 0) {
+            memset(dst, 0, (mtd_ext_size + 1) * s->word_bytes);
+            dst += (mtd_ext_size + 1) * s->word_bytes;
+        }
+    }
+    memset(dst, 0, (meter_size + 1) * s->word_bytes);
+    dst += (meter_size + 1) * s->word_bytes;
+
+    /* Must land exactly on total_words: the s337m demuxer's data_size
+     * lookup requires it (see the word-budget comment above). */
+    av_assert0(dst - avpkt->data == s->total_words * s->word_bytes);
+
+    ret = ff_af_queue_remove(&s->afq, FRAME_SAMPLES, avpkt);
+    if (ret < 0)
+        return ret;
+    s->frame_count++;
+    *got_packet_ptr = 1;
+    return 0;
+}
+
+static av_cold int dolby_e_encode_init(AVCodecContext *avctx)
+{
+    DBEEncContext *s = avctx->priv_data;
+    int ret;
+
+    s->fr_code = 0;
+    for (int fc = 1; fc <= 5; fc++) {
+        if (fr_code_sample_rate_tab[fc] == avctx->sample_rate) {
+            s->fr_code = fc;
+            break;
+        }
+    }
+    if (!s->fr_code) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Unsupported sample_rate %d Hz. Dolby E is locked to one of "
+               "the 5 SMPTE-defined frame rates via this synthetic "
+               "sample_rate convention (23.98/24/25/29.97/30 fps -> "
+               "%d/%d/%d/%d/%d Hz).\n", avctx->sample_rate,
+               fr_code_sample_rate_tab[1], fr_code_sample_rate_tab[2],
+               fr_code_sample_rate_tab[3], fr_code_sample_rate_tab[4],
+               fr_code_sample_rate_tab[5]);
+        return AVERROR(EINVAL);
+    }
+    s->total_words = fr_code_total_words_tab[s->fr_code];
+
+    if (s->word_bits != 16 && s->word_bits != 20 && s->word_bits != 24) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Invalid word_bits %d (must be 16, 20 or 24)\n", s->word_bits);
+        return AVERROR(EINVAL);
+    }
+    s->word_bytes = (s->word_bits + 7) / 8;
+
+    if (s->start_timecode) {
+        int ret2 = av_timecode_init_from_string(&s->tc,
+                                                 fr_code_rate_tab[s->fr_code],
+                                                 s->start_timecode, avctx);
+        if (ret2 < 0)
+            return ret2;
+        s->have_tc = 1;
+    }
+
+    if (s->prog_conf < 0 || s->prog_conf > MAX_PROG_CONF) {
+        av_log(avctx, AV_LOG_ERROR, "Invalid program_config %d (0-%d)\n",
+               s->prog_conf, MAX_PROG_CONF);
+        return AVERROR(EINVAL);
+    }
+
+    {
+        static const uint8_t nb_channels_tab[MAX_PROG_CONF + 1] = {
+            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 6,
+            6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 8, 8
+        };
+        static const uint8_t nb_programs_tab[MAX_PROG_CONF + 1] = {
+            2, 3, 2, 3, 4, 5, 4, 5, 6, 7, 8, 1,
+            2, 3, 3, 4, 5, 6, 1, 2, 3, 4, 1, 1
+        };
+        s->nb_channels = nb_channels_tab[s->prog_conf];
+        s->nb_programs = nb_programs_tab[s->prog_conf];
+        s->lfe_channel = ff_dolby_e_lfe_channel_tab[s->prog_conf];
+
+        /* Mirrors dolby_e.c's filter_frame() channel-reorder selection
+         * (minus output_channel_order, a decode-only option -- the encoder
+         * always assumes the decoder's default/friendly output order as its
+         * input convention, since that's what a real pipeline decoding then
+         * re-encoding Dolby E would actually hand it). */
+        if (s->nb_channels == 4)
+            s->ch_reorder = ch_reorder_4;
+        else if (s->nb_channels == 6)
+            s->ch_reorder = ch_reorder_6;
+        else if (s->nb_programs == 1)
+            s->ch_reorder = ch_reorder_8;
+        else
+            s->ch_reorder = ch_reorder_n;
+    }
+
+    if (avctx->ch_layout.nb_channels != s->nb_channels) {
+        av_log(avctx, AV_LOG_ERROR,
+               "program_config %d requires %d channels, got %d\n",
+               s->prog_conf, s->nb_channels, avctx->ch_layout.nb_channels);
+        return AVERROR(EINVAL);
+    }
+
+    if ((ret = ff_dolby_e_transform_init(&s->transform)) < 0)
+        return ret;
+
+    s->samples = av_calloc(s->nb_channels, sizeof(*s->samples));
+    if (!s->samples)
+        return AVERROR(ENOMEM);
+    for (int ch = 0; ch < s->nb_channels; ch++) {
+        s->samples[ch] = av_calloc(BUF_SAMPLES, sizeof(float));
+        if (!s->samples[ch])
+            return AVERROR(ENOMEM);
+    }
+
+    avctx->frame_size = FRAME_SAMPLES;
+    avctx->initial_padding = FRAME_SAMPLES;
+    ff_af_queue_init(avctx, &s->afq);
+
+    return 0;
+}
+
+static av_cold int dolby_e_encode_close(AVCodecContext *avctx)
+{
+    DBEEncContext *s = avctx->priv_data;
+
+    ff_dolby_e_transform_uninit(&s->transform);
+    ff_af_queue_close(&s->afq);
+    if (s->samples) {
+        for (int ch = 0; ch < s->nb_channels; ch++)
+            av_freep(&s->samples[ch]);
+        av_freep(&s->samples);
+    }
+    return 0;
+}
+
+#define OFFSET(x) offsetof(DBEEncContext, x)
+#define FLAGS (AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_AUDIO_PARAM)
+static const AVOption options[] = {
+    { "program_config", "Program configuration (see dolby_e.h prog_conf table)",
+        OFFSET(prog_conf), AV_OPT_TYPE_INT,
+        { .i64 = 11 }, 0, MAX_PROG_CONF, FLAGS },
+    { "dialnorm", "Dialogue Level (dB)",
+        OFFSET(dialogue_level), AV_OPT_TYPE_INT,
+        { .i64 = -31 }, -31, -1, FLAGS },
+    { "word_bits", "SMPTE 337M word size in bits (16, 20 or 24)",
+        OFFSET(word_bits), AV_OPT_TYPE_INT, { .i64 = 20 }, 16, 24, FLAGS },
+    { "start_timecode",
+        "Starting SMPTE timecode (hh:mm:ss:ff, or hh:mm:ss;ff for "
+        "drop-frame); auto-increments once per encoded frame. Unset "
+        "leaves the field explicitly marked invalid.",
+        OFFSET(start_timecode), AV_OPT_TYPE_STRING,
+        { .str = NULL }, 0, 0, FLAGS },
+    { "fg_spc",
+        "Fast gain spectral-extension band offset (masking-curve tuning)",
+        OFFSET(fg_spc), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 3, FLAGS },
+    { "fg_ofs", "Fast gain offset (masking-curve tuning)",
+        OFFSET(fg_ofs), AV_OPT_TYPE_INT, { .i64 = 4 }, 0, 7, FLAGS },
+    { "msk_mod", "Masking model select (masking-curve tuning)",
+        OFFSET(msk_mod), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS },
+    { NULL },
+};
+
+static const AVClass dolby_e_encoder_class = {
+    .class_name = "Dolby E encoder",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const FFCodec ff_dolby_e_encoder = {
+    .p.name         = "dolby_e",
+    CODEC_LONG_NAME("Dolby E"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_DOLBY_E,
+    .p.capabilities = AV_CODEC_CAP_DELAY,
+    .p.priv_class   = &dolby_e_encoder_class,
+    CODEC_SAMPLEFMTS(AV_SAMPLE_FMT_FLTP),
+    .priv_data_size = sizeof(DBEEncContext),
+    .init           = dolby_e_encode_init,
+    .close          = dolby_e_encode_close,
+    FF_CODEC_ENCODE_CB(dolby_e_encode_frame),
+};
-- 
2.52.0


>From 524173dc93e33f573faab74aceb13b441bd29354 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:03:02 +0300
Subject: [PATCH 3/8] avformat/s337m: add muxer

Write-side counterpart to the existing s337m demuxer: wraps a raw
Dolby E frame (as produced by the new dolby_e encoder) in SMPTE 337M
non-PCM framing.

Burst padding is sized against this demuxer's own fixed frame-rate ->
offset table (s337m_get_offset_and_codec()) rather than any broadcast
spec value, so that encode -> demux round-trips frame-accurately within
FFmpeg itself; real Dolby E hardware/software output is not guaranteed
to demux frame-accurately here for the same reason (this demuxer always
reads a fixed offset per frame rate regardless of how much of it is
real coded data, and third-party encoders may pack bursts back-to-back
with no such padding).

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 doc/muxers.texi          |  16 ++++
 libavformat/Makefile     |   1 +
 libavformat/allformats.c |   1 +
 libavformat/s337menc.c   | 195 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 213 insertions(+)
 create mode 100644 libavformat/s337menc.c

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 99584e2b8d..e7219ab147 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -3400,6 +3400,22 @@ ffmpeg -f lavfi -i "movie=INPUT.mkv[out+subcc]" -map 0:s:0 -c:s copy -f rcwt CC.
 @end example
 @end itemize
 
+@anchor{s337m}
+@section s337m
+
+SMPTE 337M muxer.
+
+Wraps a non-PCM audio bitstream (currently Dolby E only) in the SMPTE
+337M framing used to carry compressed audio over an otherwise-PCM AES3
+link. This is the write side of the @code{s337m} demuxer.
+
+Padding within each burst is sized to match this demuxer's own
+frame-rate-keyed offset table exactly, so a @code{dolby_e} stream muxed
+and then demuxed by FFmpeg itself round-trips frame-accurately. This is
+not necessarily the same padding real Dolby E hardware/software
+produces; third-party-encoded SMPTE 337M streams are not guaranteed to
+demux frame-accurately through this same demuxer for this reason.
+
 @anchor{segment}
 @section segment, stream_segment, ssegment
 
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 45084227a6..25b025efb3 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -552,6 +552,7 @@ OBJS-$(CONFIG_RTP_MUXER)                 += rtp.o         \
 OBJS-$(CONFIG_RTSP_DEMUXER)              += rtsp.o rtspdec.o httpauth.o
 OBJS-$(CONFIG_RTSP_MUXER)                += rtsp.o rtspenc.o httpauth.o
 OBJS-$(CONFIG_S337M_DEMUXER)             += s337m.o spdif.o
+OBJS-$(CONFIG_S337M_MUXER)               += s337menc.o
 OBJS-$(CONFIG_SAMI_DEMUXER)              += samidec.o subtitles.o
 OBJS-$(CONFIG_SAP_DEMUXER)               += sapdec.o
 OBJS-$(CONFIG_SAP_MUXER)                 += sapenc.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index e121c7441c..e4b3903f20 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -420,6 +420,7 @@ extern const FFOutputFormat ff_rtp_mpegts_muxer;
 extern const FFInputFormat  ff_rtsp_demuxer;
 extern const FFOutputFormat ff_rtsp_muxer;
 extern const FFInputFormat  ff_s337m_demuxer;
+extern const FFOutputFormat ff_s337m_muxer;
 extern const FFInputFormat  ff_sami_demuxer;
 extern const FFInputFormat  ff_sap_demuxer;
 extern const FFOutputFormat ff_sap_muxer;
diff --git a/libavformat/s337menc.c b/libavformat/s337menc.c
new file mode 100644
index 0000000000..cfee00ecd3
--- /dev/null
+++ b/libavformat/s337menc.c
@@ -0,0 +1,195 @@
+/*
+ * SMPTE 337M muxer
+ *
+ * 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
+ */
+
+/*
+ * Wraps raw Dolby E frames (as produced by libavcodec's dolby_e encoder)
+ * in SMPTE 337M non-PCM bitstream framing, inverting
+ * libavformat/s337m.c's s337m_get_offset_and_codec().
+ *
+ * The guard band length written here is sized for round-tripping through
+ * FFmpeg's own s337m demuxer, not as a claim about the broadcast-standard
+ * guard band value: s337m_read_packet() always calls av_get_packet(pb, pkt,
+ * offset) for a FIXED `offset` derived purely from the frame-rate case
+ * table, regardless of how many of those bytes are real coded data. If a
+ * burst is shorter than that, the demuxer's next marker search starts
+ * partway into what should have been the NEXT burst's header, and frames
+ * get silently dropped -- real Dolby-authored sample files are
+ * back-to-back with no gap and do not round-trip cleanly through this
+ * demuxer for exactly this reason. Padding each burst with silence/zero
+ * words up to the demuxer's assumed `offset` is what makes extraction
+ * frame-accurate.
+ */
+
+#include "libavutil/intreadwrite.h"
+#include "libavutil/macros.h"
+#include "libavutil/mem.h"
+
+#include "avformat.h"
+#include "mux.h"
+
+/* word_bits -> {Pa,Pb} byte sequences, matching s337m.c's MARKER_*LE
+ * constants exactly (verified against both demuxer probing and real
+ * Dolby-authored sample files). */
+static const uint8_t sync16[4] = { 0x72, 0xF8, 0x1F, 0x4E };
+static const uint8_t sync20[6] = { 0x20, 0x87, 0x6F, 0xF0, 0xE1, 0x54 };
+static const uint8_t sync24[6] = { 0x72, 0xF8, 0x96, 0x1F, 0x4E, 0xA5 };
+
+/* Burst payload word count the existing demuxer's av_get_packet() call
+ * will actually consume, for each recognized (word_bits, coded nwords)
+ * pair -- i.e. the inverse of s337m_get_offset_and_codec()'s switch table,
+ * expressed directly in payload words instead of bytes. */
+static int burst_payload_words(int word_bits, int nwords)
+{
+    static const struct { int nwords, samples; } tab[] = {
+        { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+    };
+    int word_bytes = (word_bits + 7) / 8;
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+        if (tab[i].nwords == nwords) {
+            int offset_bytes = (tab[i].samples - 4) * word_bytes * 2;
+            return offset_bytes / word_bytes;
+        }
+    }
+    return -1;
+}
+
+static int s337m_write_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    uint32_t hdr;
+    int word_bits, word_bytes, nwords, payload_words, guard_words;
+    const uint8_t *sync;
+    int sync_bytes;
+    uint8_t *buf;
+
+    if (pkt->size < 3)
+        return AVERROR_INVALIDDATA;
+
+    hdr = AV_RB24(pkt->data);
+    if ((hdr & 0xfffffe) == 0x7888e) {
+        word_bits = 24;
+    } else if ((hdr & 0xffffe0) == 0x788e0) {
+        word_bits = 20;
+    } else if ((hdr & 0xfffe00) == 0x78e00) {
+        word_bits = 16;
+    } else {
+        av_log(s, AV_LOG_ERROR, "Invalid Dolby E frame header\n");
+        return AVERROR_INVALIDDATA;
+    }
+    word_bytes = (word_bits + 7) / 8;
+
+    if (pkt->size % word_bytes) {
+        av_log(s, AV_LOG_ERROR, "Packet size %d not a multiple of word size %d\n",
+               pkt->size, word_bytes);
+        return AVERROR_INVALIDDATA;
+    }
+    nwords = pkt->size / word_bytes;
+
+    payload_words = burst_payload_words(word_bits, nwords);
+    if (payload_words < 0) {
+        avpriv_report_missing_feature(s, "Dolby E frame word count %d at %d-bit", nwords, word_bits);
+        return AVERROR_PATCHWELCOME;
+    }
+    guard_words = payload_words - nwords;
+    if (guard_words < 0) {
+        av_log(s, AV_LOG_ERROR, "Dolby E frame (%d words) exceeds its own burst envelope (%d words)\n",
+               nwords, payload_words);
+        return AVERROR_INVALIDDATA;
+    }
+
+    switch (word_bits) {
+    case 16: sync = sync16; sync_bytes = 4; break;
+    case 20: sync = sync20; sync_bytes = 6; break;
+    default:  sync = sync24; sync_bytes = 6; break;
+    }
+    avio_write(s->pb, sync, sync_bytes);
+
+    /* Pc (data_type) / Pd (data_size, in bits): fixed patterns matching
+     * real Dolby-authored samples, byte-for-byte. Pd encodes
+     * nwords*word_bits, left-shifted for 20-bit's >>4 convention (mirrors
+     * s337m_get_offset_and_codec() exactly). */
+    {
+        int64_t data_size_bits = (int64_t)nwords * word_bits;
+        uint8_t pc[3], pd[3];
+
+        switch (word_bits) {
+        case 16:
+            pc[0] = 0x1C; pc[1] = 0x00;
+            AV_WL16(pd, (uint16_t)data_size_bits);
+            avio_write(s->pb, pc, 2);
+            avio_write(s->pb, pd, 2);
+            break;
+        case 20:
+            pc[0] = 0x00; pc[1] = 0x3C; pc[2] = 0x00;
+            AV_WL24(pd, (uint32_t)(data_size_bits << 4));
+            avio_write(s->pb, pc, 3);
+            avio_write(s->pb, pd, 3);
+            break;
+        default: /* 24: demuxer applies data_type>>=8 same as the 20-bit case */
+            pc[0] = 0x00; pc[1] = 0x3C; pc[2] = 0x00;
+            AV_WL24(pd, (uint32_t)data_size_bits);
+            avio_write(s->pb, pc, 3);
+            avio_write(s->pb, pd, 3);
+            break;
+        }
+    }
+
+    /* Payload: byte-swap each word from the decoder-native big-endian
+     * packing (what libavcodec/dolby_e_enc.c emits, matching what
+     * ff_dolby_e_parse_header() expects) to the little-endian container
+     * s337m carries on the wire -- the exact inverse of s337m.c's
+     * bswap_buf24()/ff_spdif_bswap_buf16(). */
+    buf = av_malloc(pkt->size);
+    if (!buf)
+        return AVERROR(ENOMEM);
+    if (word_bits == 16) {
+        for (int i = 0; i < nwords; i++)
+            AV_WL16(buf + i * 2, AV_RB16(pkt->data + i * 2));
+    } else {
+        for (int i = 0; i < nwords; i++) {
+            const uint8_t *src = pkt->data + i * 3;
+            uint8_t *dst = buf + i * 3;
+            dst[0] = src[2]; dst[1] = src[1]; dst[2] = src[0];
+        }
+    }
+    avio_write(s->pb, buf, pkt->size);
+    av_free(buf);
+
+    /* Guard band: zero-filled words up to the demuxer's assumed burst
+     * envelope (see the file-level comment above). */
+    if (guard_words > 0) {
+        int guard_bytes = guard_words * word_bytes;
+        uint8_t *zero = av_mallocz(guard_bytes);
+        if (!zero)
+            return AVERROR(ENOMEM);
+        avio_write(s->pb, zero, guard_bytes);
+        av_free(zero);
+    }
+
+    return 0;
+}
+
+const FFOutputFormat ff_s337m_muxer = {
+    .p.name         = "s337m",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("SMPTE 337M"),
+    .p.audio_codec  = AV_CODEC_ID_DOLBY_E,
+    .p.flags        = AVFMT_NOTIMESTAMPS,
+    .write_packet   = s337m_write_packet,
+};
-- 
2.52.0


>From a5a35ed1ac971d07798e022222790c58c178ccb9 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:03:19 +0300
Subject: [PATCH 4/8] avformat/spdif: support Dolby E (data type 0x1C)

Add IEC61937_DOLBY_E and wire up both directions:

 - spdifenc.c: header_info callback for AV_CODEC_ID_DOLBY_E, mirroring
   the existing AC-3/DTS/AAC callbacks but with a frame-rate-dependent
   burst period (from the coded word count) instead of a fixed
   per-format offset, matching s337m.c's own envelope table.
 - spdifdec.c: matching probe/read-side offset lookup, handling both
   byte orders the two call sites present it in (native-order for real
   packet reads after ff_spdif_bswap_buf16(), still file-byte-order for
   the raw probe path).

Only 16-bit-word Dolby E frames are handled: IEC 61937's 16-bit-word
byte swap has no meaning for 20/24-bit AES3 words, which is not a
carriage this transport supports.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 libavformat/spdif.h    |  1 +
 libavformat/spdifdec.c | 43 ++++++++++++++++++++++++++++++++++++++++++
 libavformat/spdifenc.c | 39 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 83 insertions(+)

diff --git a/libavformat/spdif.h b/libavformat/spdif.h
index 0b6567fac8..1d68737381 100644
--- a/libavformat/spdif.h
+++ b/libavformat/spdif.h
@@ -50,6 +50,7 @@ enum IEC61937DataType {
     IEC61937_MPEG2_AAC_LSF_4096 = 0x13 | 0x20,   ///< MPEG-2 AAC ADTS quarter-rate low sampling frequency
     IEC61937_EAC3               = 0x15,          ///< E-AC-3 data
     IEC61937_TRUEHD             = 0x16,          ///< TrueHD data
+    IEC61937_DOLBY_E            = 0x1C,          ///< Dolby E data (SMPTE 338M data type, shared with SMPTE 337M)
     IEC61937_MAX_ENUM           = 0xFFFF         ///< to force the underlying type to be at least 16bits
 };
 
diff --git a/libavformat/spdifdec.c b/libavformat/spdifdec.c
index fe45deb157..14bf3b0334 100644
--- a/libavformat/spdifdec.c
+++ b/libavformat/spdifdec.c
@@ -99,6 +99,49 @@ static int spdif_get_offset_and_codec(AVFormatContext *s,
         *offset = 24576;
         *codec = AV_CODEC_ID_EAC3;
         break;
+    case IEC61937_DOLBY_E: {
+        /* Burst period depends on frame rate, unlike the fixed per-format
+         * offsets above -- derived from fr_code (4 bits: sync word(16 bits)
+         * + revision_id(4) + mtd_size(10) + program_config(6) leaves
+         * fr_code occupying bits [20:24) of the flat metadata stream, i.e.
+         * the low nibble of the 3rd 16-bit word) rather than from packet
+         * size, so this needs no signature change. Table mirrors s337m.c's
+         * s337m_get_offset_and_codec() exactly.
+         *
+         * Called from two contexts with different byte order: real packet
+         * reads (ff_spdif_read_packet) already ran ff_spdif_bswap_buf16,
+         * so buf is native/big-endian (07 8e ...); the probe path
+         * (ff_spdif_probe) scans raw, still-16-bit-byte-swapped file bytes
+         * (8e 07 ...), so the fr_code nibble shifts from byte 4 to byte 5. */
+        static const int total_words_tab[6] = { 0, 3644, 3640, 3648, 3040, 3040 };
+        static const struct { int nwords, samples; } tab[] = {
+            { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+        };
+        int fr_code, nwords;
+        uint8_t b0 = buf[0], b1 = buf[1];
+
+        if (b0 == 0x07 && b1 == 0x8e)
+            fr_code = buf[4] & 0x0F;
+        else if (b0 == 0x8e && b1 == 0x07)
+            fr_code = buf[5] & 0x0F;
+        else
+            return AVERROR_INVALIDDATA;
+        if (fr_code < 1 || fr_code > 5)
+            return AVERROR_INVALIDDATA;
+        nwords = total_words_tab[fr_code];
+
+        *offset = 0;
+        for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+            if (tab[i].nwords == nwords) {
+                *offset = (tab[i].samples - 4) * 4;
+                break;
+            }
+        }
+        if (!*offset)
+            return AVERROR_PATCHWELCOME;
+        *codec = AV_CODEC_ID_DOLBY_E;
+        break;
+    }
     default:
         if (s) { /* be silent during a probe */
             avpriv_request_sample(s, "Data type 0x%04x in IEC 61937",
diff --git a/libavformat/spdifenc.c b/libavformat/spdifenc.c
index 0b8d8c5886..6884ecbcd9 100644
--- a/libavformat/spdifenc.c
+++ b/libavformat/spdifenc.c
@@ -55,6 +55,7 @@
 #include "libavcodec/dca.h"
 #include "libavcodec/dca_syncwords.h"
 #include "libavcodec/get_bits.h"
+#include "libavutil/intreadwrite.h"
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 
@@ -135,6 +136,41 @@ static int spdif_header_ac3(AVFormatContext *s, AVPacket *pkt)
     return 0;
 }
 
+/* Dolby E's own burst repetition period (in output bytes) depends on frame
+ * rate, unlike AC-3/DTS/AAC's fixed per-format offsets above -- mirrors
+ * libavformat/s337m.c's s337m_get_offset_and_codec()/burst_payload_words()
+ * exactly (same SMPTE 337M envelope table), since IEC 61937 and SMPTE 337M
+ * share the same underlying data-type/data-size framing, just carried over
+ * a 16-bit-word consumer interface instead of professional AES3. Only
+ * 16-bit Dolby E words are supported here: IEC 61937's 16-bit-word byte
+ * swap (ff_spdif_bswap_buf16) isn't meaningful for 20/24-bit AES3 words. */
+static int spdif_header_dolby_e(AVFormatContext *s, AVPacket *pkt)
+{
+    static const struct { int nwords, samples; } tab[] = {
+        { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+    };
+    IEC61937Context *ctx = s->priv_data;
+    int nwords;
+
+    if (pkt->size < 2 || AV_RB16(pkt->data) != 0x078E) {
+        av_log(s, AV_LOG_ERROR, "Only 16-bit-word Dolby E frames can be "
+               "carried over IEC 61937\n");
+        return AVERROR_PATCHWELCOME;
+    }
+
+    nwords = pkt->size >> 1;
+    ctx->data_type = IEC61937_DOLBY_E;
+
+    for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+        if (tab[i].nwords == nwords) {
+            ctx->pkt_offset = (tab[i].samples - 4) * 4;
+            return 0;
+        }
+    }
+    avpriv_report_missing_feature(s, "Dolby E frame word count %d in IEC 61937", nwords);
+    return AVERROR_PATCHWELCOME;
+}
+
 static int spdif_header_eac3(AVFormatContext *s, AVPacket *pkt)
 {
     IEC61937Context *ctx = s->priv_data;
@@ -799,6 +835,9 @@ static int spdif_write_header(AVFormatContext *s)
     case AV_CODEC_ID_AAC:
         ctx->header_info = spdif_header_aac;
         break;
+    case AV_CODEC_ID_DOLBY_E:
+        ctx->header_info = spdif_header_dolby_e;
+        break;
     case AV_CODEC_ID_TRUEHD:
     case AV_CODEC_ID_MLP:
         ctx->header_info = spdif_header_truehd;
-- 
2.52.0


>From 16d9e85643d91e34b6bd8560128af4806fcb6d92 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:03:50 +0300
Subject: [PATCH 5/8] avformat/wavenc: support carrying Dolby E over WAV

Dolby E has no registered WAVE_FORMAT tag of its own, and EBU Tech 3285
Supplement 6's dbmd chunk only covers PCM audio metadata, not non-PCM
bitstream carriage. Follow the same real-world convention already used
for AC-3/DTS "receiver test" WAV files: advertise the fmt chunk as
ordinary 16-bit PCM (wFormatTag 1) and let the SMPTE 337M/IEC 61937
burst preamble self-identify the payload. wavdec.c's existing
set_spdif() probe for codec_tag==1 streams already handles the read
side, so no demuxer changes are needed.

Only 16-bit-word Dolby E frames can be carried this way, matching the
spdif muxer's own limitation.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 doc/muxers.texi      |  6 ++++
 libavformat/wavenc.c | 85 ++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index e7219ab147..2df1c503d1 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -3951,6 +3951,12 @@ ffmpeg -i ... -map 0 -flags +global_header -c:v libx264 -c:a aac
 
 RIFF Wave Audio muxer.
 
+Dolby E input is carried per the same convention used by AC-3/DTS
+"receiver test" WAV files: the @code{fmt } chunk advertises ordinary
+16-bit PCM (@code{wFormatTag} 1), and the SMPTE 337M/IEC 61937 burst
+preamble self-identifies the actual payload to a compliant reader. Only
+16-bit-word Dolby E frames can be carried this way.
+
 @subsection Options
 
 @table @option
diff --git a/libavformat/wavenc.c b/libavformat/wavenc.c
index a8debf8faa..94a79d6bb6 100644
--- a/libavformat/wavenc.c
+++ b/libavformat/wavenc.c
@@ -48,6 +48,7 @@
 #include "avio.h"
 #include "avio_internal.h"
 #include "internal.h"
+#include "spdif.h"
 #include "mux.h"
 #include "riff.h"
 
@@ -299,6 +300,79 @@ static int peak_write_chunk(AVFormatContext *s)
     return 0;
 }
 
+/* Dolby E has no registered WAVE_FORMAT tag of its own (checked: neither
+ * Microsoft's mmreg.h registry nor Dolby's own tooling define one), and
+ * EBU Tech 3285 Supplement 6's dbmd chunk explicitly only describes PCM
+ * audio metadata, not non-PCM bitstream carriage (Suppl. 6 section 5.5).
+ * The real-world convention for exactly this situation -- compressed
+ * professional audio in a WAV file with no format tag of its own -- is the
+ * same IEC 61937/SMPTE 337M burst-in-disguised-PCM trick used for
+ * AC-3/DTS "receiver test" WAV files: advertise as ordinary 16-bit PCM
+ * (wFormatTag=1) and let the burst preamble self-identify the payload.
+ * wavdec.c's own set_spdif() already probes exactly this for codec_tag==1
+ * streams, so no wavdec.c changes are needed for the read side. */
+static void write_dolby_e_fmt_body(AVFormatContext *s)
+{
+    AVIOContext *pb = s->pb;
+    AVCodecParameters *par = s->streams[0]->codecpar;
+    int block_align = par->ch_layout.nb_channels * 2;
+
+    avio_wl16(pb, 1); /* wFormatTag: WAVE_FORMAT_PCM */
+    avio_wl16(pb, par->ch_layout.nb_channels);
+    avio_wl32(pb, par->sample_rate);
+    avio_wl32(pb, par->sample_rate * block_align); /* nAvgBytesPerSec */
+    avio_wl16(pb, block_align);
+    avio_wl16(pb, 16); /* wBitsPerSample */
+}
+
+/* Mirrors spdifenc.c's Dolby E IEC 61937 burst framing exactly (same data
+ * type 0x1C, same burst-period table derived from fr_code) -- reimplemented
+ * here rather than shared, since spdifenc.c's helpers are tied to its own
+ * per-context IEC61937Context state, not something this muxer has. */
+static int write_dolby_e_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    static const struct { int nwords, samples; } tab[] = {
+        { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+    };
+    AVIOContext *pb = s->pb;
+    int nwords, pkt_offset = 0, padding;
+    uint8_t *swapped;
+
+    if (pkt->size < 2 || AV_RB16(pkt->data) != 0x078E) {
+        av_log(s, AV_LOG_ERROR,
+               "Only 16-bit-word Dolby E frames can be carried in WAV\n");
+        return AVERROR_PATCHWELCOME;
+    }
+    nwords = pkt->size >> 1;
+    for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+        if (tab[i].nwords == nwords) {
+            pkt_offset = (tab[i].samples - 4) * 4;
+            break;
+        }
+    }
+    if (!pkt_offset) {
+        avpriv_report_missing_feature(s, "Dolby E frame word count %d in WAV", nwords);
+        return AVERROR_PATCHWELCOME;
+    }
+
+    avio_wl16(pb, SYNCWORD1);
+    avio_wl16(pb, SYNCWORD2);
+    avio_wl16(pb, IEC61937_DOLBY_E);
+    avio_wl16(pb, pkt->size << 3);
+
+    swapped = av_malloc(pkt->size);
+    if (!swapped)
+        return AVERROR(ENOMEM);
+    ff_spdif_bswap_buf16((uint16_t *)swapped, (const uint16_t *)pkt->data, pkt->size >> 1);
+    avio_write(pb, swapped, pkt->size);
+    av_free(swapped);
+
+    padding = pkt_offset - BURST_HEADER_SIZE - pkt->size;
+    if (padding > 0)
+        ffio_fill(pb, 0, padding);
+    return 0;
+}
+
 static int wav_write_header(AVFormatContext *s)
 {
     WAVMuxContext *wav = s->priv_data;
@@ -326,7 +400,10 @@ static int wav_write_header(AVFormatContext *s)
     if (wav->write_peak != PEAK_ONLY) {
         /* format header */
         fmt = ff_start_tag(pb, "fmt ");
-        if (ff_put_wav_header(s, pb, s->streams[0]->codecpar, 0) < 0) {
+        if (s->streams[0]->codecpar->codec_id == AV_CODEC_ID_DOLBY_E) {
+            s->streams[0]->codecpar->codec_tag = 1; /* advertise as PCM, see write_dolby_e_fmt_body() */
+            write_dolby_e_fmt_body(s);
+        } else if (ff_put_wav_header(s, pb, s->streams[0]->codecpar, 0) < 0) {
             av_log(s, AV_LOG_ERROR, "Codec %s not supported in WAVE format\n",
                    avcodec_get_name(s->streams[0]->codecpar->codec_id));
             return AVERROR(ENOSYS);
@@ -380,7 +457,11 @@ static int wav_write_packet(AVFormatContext *s, AVPacket *pkt)
     AVIOContext *pb  = s->pb;
     WAVMuxContext    *wav = s->priv_data;
 
-    if (wav->write_peak != PEAK_ONLY)
+    if (s->streams[0]->codecpar->codec_id == AV_CODEC_ID_DOLBY_E) {
+        int ret = write_dolby_e_packet(s, pkt);
+        if (ret < 0)
+            return ret;
+    } else if (wav->write_peak != PEAK_ONLY)
         avio_write(pb, pkt->data, pkt->size);
 
     if (wav->write_peak) {
-- 
2.52.0


>From b8935fa804b014fba93791789d8e32b5b7a918a2 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:04:00 +0300
Subject: [PATCH 6/8] avformat/mxf: enable Dolby E SoundEssenceCompression
 codec UL

Un-comment the existing Dolby E entry in ff_mxf_codec_uls
(...04.02.02.02.03.02.1C.00): 0x1C is the SMPTE 338M Dolby E data type,
shared with SMPTE 337M, and this UL sits in the same registry branch as
AC-3's own .01 and MP2's .05 immediately above it.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 libavformat/mxf.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavformat/mxf.c b/libavformat/mxf.c
index b6c1f176a1..a402885899 100644
--- a/libavformat/mxf.c
+++ b/libavformat/mxf.c
@@ -78,7 +78,7 @@ const MXFCodecUL ff_mxf_codec_uls[] = {
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x04,0x04,0x02,0x02,0x02,0x03,0x01,0x01,0x00 }, 15,   AV_CODEC_ID_PCM_ALAW }, /* XDCAM Proxy C0023S01.mxf */
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x01,0x00 }, 15,        AV_CODEC_ID_AC3 },
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x05,0x00 }, 15,        AV_CODEC_ID_MP2 }, /* MP2 or MP3 */
-  //{ { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x1C,0x00 }, 15,    AV_CODEC_ID_DOLBY_E }, /* Dolby-E */
+    { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x01,0x04,0x02,0x02,0x02,0x03,0x02,0x1C,0x00 }, 15,    AV_CODEC_ID_DOLBY_E }, /* Dolby-E: SoundEssenceCompression 04.02.02.02.03.02.1C, matching AC-3's own .01 and MP2's .05 in the same registry branch (0x1C = SMPTE 338M Dolby E data type, shared with SMPTE 337M) */
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0D,0x04,0x02,0x02,0x02,0x04,0x03,0x00,0x00 }, 14,        AV_CODEC_ID_AAC }, /* MPEG-2 AAC SMPTE 381-4 */
     { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0D,0x04,0x02,0x02,0x02,0x04,0x04,0x00,0x00 }, 14,        AV_CODEC_ID_AAC }, /* MPEG-4 AAC SMPTE 381-4 */
     { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },  0,       AV_CODEC_ID_NONE },
-- 
2.52.0


>From f9ec810961498cb4466749fea8ea11cb68d4a94a Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:07:07 +0300
Subject: [PATCH 7/8] avformat/mpegts, mpegtsenc: carry Dolby E over SMPTE 302M
 in MPEG-TS

Dolby E has no MPEG-TS stream_type/descriptor of its own, so ride the
existing SMPTE 302M "AES3 data in MPEG-2 TS" signaling (BSSD
registration descriptor, already mapped to AV_CODEC_ID_S302M) with the
Dolby E bitstream hidden inside as an SMPTE 337M/IEC 61937 burst
disguised as 16-bit PCM samples, matching the same convention used for
the spdif and wavenc Dolby E support already added.

mpegtsenc.c (write side): builds the burst, then packs it through
SMPTE 302M's AES3-header + per-sample bit-reversal framing (mirrors
s302menc.c's bits_per_raw_sample==16 path) as a 2-channel AES3 pair, and
extends the existing BSSD registration descriptor condition to cover
AV_CODEC_ID_DOLBY_E alongside AV_CODEC_ID_S302M.

mpegts.c (read side): the inverse -- undoes the AES3 header and bit
reversal, and if the reconstructed samples start with the IEC 61937
Dolby E burst preamble, replaces the packet with the raw Dolby E frame
bytes and switches the stream's codec_id from S302M to DOLBY_E so
playback/decoding uses the real dolby_e decoder.

The bit-reversal helper is reimplemented locally in both files rather
than reusing libavutil's ff_reverse[256] table (already used by
s302menc.c), since that table is not part of libavutil's exported ABI
and is therefore not linkable from these shared libraries.

Also mark the stream's FFStream.need_context_update and clear
request_probe when switching codec_id mid-stream: without this, the
generic stream-info probing machinery in avformat_find_stream_info()
keeps feeding these now-unwrapped, no-longer-AES3-shaped packets to a
decoder probe already opened against the stale S302M guess, and every
one of them fails ("frame has invalid header" from the s302m decoder).
Found by actually running the mux -> demux round trip end to end
(ffprobe reported codec_name=s302m instead of dolby_e until this fix);
verified with `ffmpeg -i asynth... -c:a dolby_e -word_bits 16 -f mpegts`
followed by demux+decode.

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 doc/muxers.texi         |  9 ++++
 libavformat/mpegts.c    | 94 +++++++++++++++++++++++++++++++++++++++++
 libavformat/mpegtsenc.c | 92 +++++++++++++++++++++++++++++++++++++++-
 3 files changed, 194 insertions(+), 1 deletion(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 2df1c503d1..5ea19672dc 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -3132,6 +3132,15 @@ and @code{service_name}. If they are not set the default for
 @code{service_provider} is @samp{FFmpeg} and the default for
 @code{service_name} is @samp{Service01}.
 
+Dolby E has no MPEG-TS stream_type/descriptor of its own; a
+@code{dolby_e} input stream is muxed inside SMPTE 302M's "AES3 data in
+MPEG-2 TS" signaling (the BSSD registration descriptor), with the actual
+Dolby E frame carried as an SMPTE 337M/IEC 61937 burst disguised as
+16-bit PCM samples inside that AES3 payload. Only 16-bit-word Dolby E
+frames are supported. The demuxer reverses this automatically and
+switches the stream's codec to @code{dolby_e} once it recognizes the
+burst preamble.
+
 @subsection Options
 
 The muxer options are:
diff --git a/libavformat/mpegts.c b/libavformat/mpegts.c
index 076020509d..b5916b19b6 100644
--- a/libavformat/mpegts.c
+++ b/libavformat/mpegts.c
@@ -40,6 +40,7 @@
 #include "mpegts.h"
 #include "internal.h"
 #include "avio_internal.h"
+#include "spdif.h"
 #include "demux.h"
 #include "mpeg.h"
 #include "isom.h"
@@ -1064,6 +1065,97 @@ end:
     return ret;
 }
 
+#define AES3_HEADER_LEN 4 /* SMPTE 302M AES3 subframe header, matches libavcodec/s302m{,enc}.c */
+
+static uint8_t mpegts_reverse_bits8(uint8_t b)
+{
+    b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+    b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+    b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+    return b;
+}
+
+/* Dolby E has no MPEG-TS stream_type/descriptor of its own; it rides SMPTE
+ * 302M's "AES3 data in MPEG-2 TS" signaling (BSSD registration descriptor,
+ * already mapped to AV_CODEC_ID_S302M above) with the actual Dolby E
+ * bitstream hidden inside as an SMPTE 337M/IEC 61937 burst disguised as
+ * 16-bit PCM samples -- see libavformat/mpegtsenc.c's Dolby E case, which
+ * builds exactly this. This is the inverse: undo the AES3 header + bit
+ * reversal (mirrors libavcodec/s302m.c's own decode math for the 16-bit,
+ * 2-channel case), then look for the burst preamble in the reconstructed
+ * samples and, if present, replace the packet with the raw Dolby E frame
+ * bytes and switch this stream's codec_id so playback (or decoding) uses
+ * the real dolby_e decoder instead of s302m. Runs once per BSSD stream
+ * (mpegts_find_stream_type() only settles on S302M initially; codec_id is
+ * overridden here as soon as a burst is actually found in the payload). */
+static void try_unwrap_dolby_e_from_s302m(AVStream *st, AVPacket *pkt)
+{
+    int nb_pairs;
+    uint16_t *samples;
+
+    if ((st->codecpar->codec_id != AV_CODEC_ID_S302M &&
+         st->codecpar->codec_id != AV_CODEC_ID_DOLBY_E) ||
+        st->codecpar->codec_tag != MKTAG('B', 'S', 'S', 'D') ||
+        pkt->size < AES3_HEADER_LEN + 5 * 4)
+        return;
+
+    /* AES3 header: size(16) + channels(2) + channel_id(8) + bits(2) + align(4).
+     * Only the 16-bit, 2-channel case is attempted -- that's what the
+     * encode side (mpegtsenc.c) ever produces for Dolby E. */
+    if (((pkt->data[2] >> 6) & 0x3) != 0 /* nb_channels != 2 */ ||
+        ((pkt->data[3] >> 4) & 0x3) != 0 /* bits_per_sample != 16 */)
+        return;
+
+    nb_pairs = (pkt->size - AES3_HEADER_LEN) / 5;
+    if (nb_pairs < 4)
+        return;
+
+    samples = av_malloc_array(nb_pairs * 2, sizeof(*samples));
+    if (!samples)
+        return;
+    {
+        const uint8_t *b = pkt->data + AES3_HEADER_LEN;
+        for (int i = 0; i < nb_pairs; i++, b += 5) {
+            samples[2 * i]     = mpegts_reverse_bits8(b[0]) |
+                                  mpegts_reverse_bits8(b[1]) << 8;
+            samples[2 * i + 1] = mpegts_reverse_bits8(b[2]) >> 4 |
+                                  mpegts_reverse_bits8(b[3]) << 4 |
+                                  (mpegts_reverse_bits8(b[4] & 0xF0) << 12);
+        }
+    }
+
+    if (samples[0] == SYNCWORD1 && samples[1] == SYNCWORD2 &&
+        samples[2] == IEC61937_DOLBY_E) {
+        int nwords = samples[3] >> 4; /* length_code is pkt->size<<3 (bits); /16 to get 16-bit words */
+        AVBufferRef *buf;
+
+        if (nwords > 0 && 4 + nwords <= nb_pairs * 2) {
+            buf = av_buffer_alloc(nwords * 2 + AV_INPUT_BUFFER_PADDING_SIZE);
+            if (buf) {
+                for (int i = 0; i < nwords; i++)
+                    AV_WB16(buf->data + 2 * i, samples[4 + i]);
+                memset(buf->data + nwords * 2, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+                av_buffer_unref(&pkt->buf);
+                pkt->buf  = buf;
+                pkt->data = buf->data;
+                pkt->size = nwords * 2;
+                if (st->codecpar->codec_id != AV_CODEC_ID_DOLBY_E) {
+                    FFStream *const sti = ffstream(st);
+                    st->codecpar->codec_id = AV_CODEC_ID_DOLBY_E;
+                    /* Tell the generic stream-info probing machinery the
+                     * codec changed mid-stream: without this, a decoder
+                     * probe already opened against the stale S302M guess
+                     * keeps being fed these now-unwrapped, no-longer-AES3
+                     * packets and fails every time. */
+                    sti->need_context_update = 1;
+                    sti->request_probe = 0;
+                }
+            }
+        }
+    }
+    av_free(samples);
+}
+
 static int new_pes_packet(PESContext *pes, AVPacket *pkt)
 {
     uint8_t *sd;
@@ -1081,6 +1173,8 @@ static int new_pes_packet(PESContext *pes, AVPacket *pkt)
         pes->flags |= AV_PKT_FLAG_CORRUPT;
     }
 
+    try_unwrap_dolby_e_from_s302m(pes->st, pkt);
+
     // JPEG-XS PES payload
     if (pes->stream_id == 0xbd && pes->stream_type == 0x32 &&
         pkt->size >= 8 && memcmp(pkt->data + 4, "jxes", 4) == 0)
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index c7733f65e3..94009c8b1f 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -41,6 +41,7 @@
 #include "internal.h"
 #include "mpegts.h"
 #include "mux.h"
+#include "spdif.h"
 
 #define PCR_TIME_BASE 27000000
 
@@ -610,7 +611,13 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
                     *q++=0; // omit all fields...
                 }
             }
-            if (codec_id == AV_CODEC_ID_S302M)
+            if (codec_id == AV_CODEC_ID_S302M || codec_id == AV_CODEC_ID_DOLBY_E)
+                /* Dolby E has no MPEG-TS stream_type/descriptor of its own;
+                 * it rides the same SMPTE 302M "AES3 data in MPEG-2 TS"
+                 * signaling real receivers use for exactly this situation
+                 * (SMPTE 337M-disguised professional audio), matching
+                 * libavcodec/s302m.c's own decoder, which already searches
+                 * reconstructed S302M samples for this same burst pattern. */
                 put_registration_descriptor(&q, MKTAG('B', 'S', 'S', 'D'));
             if (codec_id == AV_CODEC_ID_OPUS) {
                 int ch = st->codecpar->ch_layout.nb_channels;
@@ -1908,6 +1915,20 @@ static uint8_t *h26x_prefix_aud(const uint8_t *aud, const int aud_size,
     return data;
 }
 
+#define AES3_HEADER_LEN 4 /* SMPTE 302M AES3 subframe header, matches libavcodec/s302m{,enc}.c */
+
+/* Bit-reverse a byte for SMPTE 302M packing. libavutil's ff_reverse[256]
+ * (used by libavcodec/s302menc.c) isn't part of libavutil's exported ABI
+ * (its version script only exports av*-prefixed symbols), so it isn't
+ * linkable from this shared library; this is the standard equivalent. */
+static uint8_t reverse_bits8(uint8_t b)
+{
+    b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
+    b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
+    b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
+    return b;
+}
+
 #define H264_NAL_TYPE(state) (state & 0x1f)
 #define HEVC_NAL_TYPE(state) ((state & 0x7e) >> 1)
 #define VVC_NAL_TYPE(state)  ((state >> 11) & 0x1f)
@@ -2047,6 +2068,75 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
                 buf             = data;
             }
         }
+    } else if (st->codecpar->codec_id == AV_CODEC_ID_DOLBY_E) {
+        /* Two nested wrapping layers, both already established elsewhere in
+         * this codebase: (1) the same SMPTE 337M/IEC 61937 burst (sync,
+         * data-type 0x1C, length, frame-rate-dependent padding) built for
+         * spdifenc.c/wavenc.c's Dolby E support, disguised as a stream of
+         * 16-bit PCM sample VALUES; (2) SMPTE 302M's own AES3-header +
+         * per-sample bit-reversal packing (mirrors libavcodec/s302menc.c's
+         * bits_per_raw_sample==16 path exactly) over that fake-PCM stream,
+         * as a 2-channel AES3 pair -- matching how libavcodec/s302m.c's
+         * decoder searches reconstructed 2-channel samples for exactly
+         * this burst pattern (its "channels == 2" non-PCM detection path).
+         * The registration descriptor above already flags the stream as
+         * SMPTE 302M (BSSD) to any receiver expecting this convention. */
+        static const struct { int nwords, samples; } tab[] = {
+            { 3648, 1920 }, { 3644, 2002 }, { 3640, 2000 }, { 3040, 1601 },
+        };
+        int nwords, burst_words = 0;
+        uint16_t *burst;
+        uint8_t *o;
+
+        if (pkt->size < 2 || AV_RB16(pkt->data) != 0x078E) {
+            av_log(s, AV_LOG_ERROR, "Only 16-bit-word Dolby E frames can be "
+                   "carried over SMPTE 302M in MPEG-TS\n");
+            return AVERROR_PATCHWELCOME;
+        }
+        nwords = pkt->size >> 1;
+        for (int i = 0; i < FF_ARRAY_ELEMS(tab); i++) {
+            if (tab[i].nwords == nwords) {
+                burst_words = (tab[i].samples - 4) * 2;
+                break;
+            }
+        }
+        if (!burst_words) {
+            avpriv_report_missing_feature(s, "Dolby E frame word count %d "
+                                          "over SMPTE 302M", nwords);
+            return AVERROR_PATCHWELCOME;
+        }
+
+        burst = av_calloc(burst_words, sizeof(*burst));
+        if (!burst)
+            return AVERROR(ENOMEM);
+        burst[0] = SYNCWORD1;
+        burst[1] = SYNCWORD2;
+        burst[2] = IEC61937_DOLBY_E;
+        burst[3] = pkt->size << 3;
+        for (int i = 0; i < nwords; i++)
+            burst[4 + i] = AV_RB16(pkt->data + 2 * i);
+
+        size = AES3_HEADER_LEN + (burst_words / 2) * 5;
+        data = av_malloc(size);
+        if (!data) {
+            av_free(burst);
+            return AVERROR(ENOMEM);
+        }
+        AV_WB16(data, size - AES3_HEADER_LEN);
+        data[2] = 0;    /* number of channels: (2-2)>>1 = 0 -> 2 channels */
+        data[3] = 0;    /* bits per sample: 0 -> 16 bit; alignment: 0 */
+        o = data + AES3_HEADER_LEN;
+        for (int i = 0; i < burst_words; i += 2) {
+            uint16_t l = burst[i], r = burst[i + 1];
+            o[0] = reverse_bits8(l & 0xFF);
+            o[1] = reverse_bits8((l & 0xFF00) >> 8);
+            o[2] = reverse_bits8((r & 0x0F) << 4);
+            o[3] = reverse_bits8((r & 0x0FF0) >> 4);
+            o[4] = reverse_bits8((r & 0xF000) >> 12);
+            o += 5;
+        }
+        av_free(burst);
+        buf = data;
     } else if (st->codecpar->codec_id == AV_CODEC_ID_HEVC) {
         const uint8_t *p = buf, *buf_end = p + size;
         uint32_t state = -1;
-- 
2.52.0


>From c271fd4c9c11f30185d90db997439bd06b342284 Mon Sep 17 00:00:00 2001
From: Oren Jack Maurice <[email protected]>
Date: Sun, 9 Aug 2026 12:15:14 +0300
Subject: [PATCH 8/8] fate: add dolby_e encoder round-trip smoke test

Encodes synthetic 6-channel PCM (tests/data/asynth-44800-6.wav, 44800 Hz
selecting the 25fps SMPTE frame rate) through the new dolby_e encoder
into s337m, then demuxes/decodes it back and compares against the
source with stddev.

This is a smoke test against FFmpeg's own decoder, not a claim of
interop with third-party Dolby E equipment -- no real Dolby-authored
sample is involved. FUZZ is set generously (200) since the encoder's
rate control runs a binary search against the MDCT/IMDCT transform,
which is not guaranteed bit-exact across SIMD backends; SIZE_TOLERANCE
accounts for the output covering whole encoded frames only, which does
not evenly divide the 6-second input duration.

Verified passing locally (stddev ~11919, PSNR ~14.8dB, matching the
committed CMP_TARGET within FUZZ).

Development-method: AI-assisted (Claude Code); reviewed by the submitter
before posting.

Signed-off-by: Oren Jack Maurice <[email protected]>
---
 tests/fate/audio.mak | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/tests/fate/audio.mak b/tests/fate/audio.mak
index c21578947a..33725d2713 100644
--- a/tests/fate/audio.mak
+++ b/tests/fate/audio.mak
@@ -31,6 +31,23 @@ fate-dolby-e: CMD = pcm -i $(TARGET_SAMPLES)/dolby_e/16-11
 fate-dolby-e: CMP = oneoff
 fate-dolby-e: REF = $(SAMPLES)/dolby_e/16-11.pcm
 
+# Encoder smoke test: no real Dolby-authored sample involved, just an
+# encode -> s337m mux -> demux -> decode round trip against this
+# codebase's own decoder, using synthetic multichannel PCM. Not a claim
+# of interop with third-party Dolby E equipment (see the encoder's own
+# commit message); a large FUZZ is used since the encoder's rate control
+# depends on the MDCT/IMDCT transform, which is not guaranteed bit-exact
+# across SIMD backends.
+FATE_SAMPLES_AUDIO-$(call ENCDEC, DOLBY_E, S337M, PCM_S16LE_ENCODER WAV_MUXER PIPE_PROTOCOL) += fate-dolby-e-encode
+fate-dolby-e-encode: $(AREF) tests/data/asynth-44800-6.wav
+fate-dolby-e-encode: CMD = enc_dec_pcm s337m wav s16le tests/data/asynth-44800-6.wav -c:a dolby_e
+fate-dolby-e-encode: CMP = stddev
+fate-dolby-e-encode: REF = tests/data/asynth-44800-6.wav
+fate-dolby-e-encode: CMP_SHIFT = -256
+fate-dolby-e-encode: CMP_TARGET = 11920
+fate-dolby-e-encode: FUZZ = 200
+fate-dolby-e-encode: SIZE_TOLERANCE = 22000
+
 FATE_SAMPLES_AUDIO-$(call FRAMECRC, DSS, DSS_SP, ARESAMPLE_FILTER) += fate-dss-lp
 fate-dss-lp: CMD = framecrc -i $(TARGET_SAMPLES)/dss/lp.dss -frames 30 -af aresample
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.