PR #24225 opened by James Almer (jamrial)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24225
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24225.patch

Fixes ticket #24218


>From 18e0b99acbf575ce2313fedd5d6a8eb7a26105a2 Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Thu, 20 Aug 2026 21:27:10 -0300
Subject: [PATCH] avcodec/aacenc: add support for writing PCE layouts with
 height channels

Fixes ticket #24218

Signed-off-by: James Almer <[email protected]>
---
 libavcodec/aacenc.c | 102 +++++++++++++++++++++++++++++++++++++++++++-
 libavcodec/aacenc.h |   2 +
 2 files changed, 102 insertions(+), 2 deletions(-)

diff --git a/libavcodec/aacenc.c b/libavcodec/aacenc.c
index d2b67d6b20..956e589836 100644
--- a/libavcodec/aacenc.c
+++ b/libavcodec/aacenc.c
@@ -31,6 +31,7 @@
 #include <float.h>
 
 #include "libavutil/channel_layout.h"
+#include "libavutil/crc.h"
 #include "libavutil/libm.h"
 #include "libavutil/float_dsp.h"
 #include "libavutil/mem.h"
@@ -296,6 +297,70 @@ static const AACPCEInfo aac_pce_configs[] = {
         .config_map = { 5, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_CPE, TYPE_SCE },
         .reorder_map = { 2, 0, 1, 6, 7, 3, 4, 5 },
     },
+    {
+        .layout = AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK,
+        .num_ele = { 3, 0, 1, 1 },
+        .pairing = { { 0, 1, 1 }, { 0 }, { 1 }, },
+        .index = { { 0, 0, 2 }, { 0 }, { 1 }, { 0 }, },
+        .height = { { 0, 0, 1 }, { 0 }, { 0 } },
+        .config_map = { 5, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_LFE, TYPE_CPE },
+        .reorder_map = { 2, 0, 1, 4, 5, 3, 6, 7 },
+    },
+    {
+        .layout = AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK,
+        .num_ele = { 3, 0, 2, 1 },
+        .pairing = { { 0, 1, 1 }, { 0 }, { 1, 1 }, },
+        .index = { { 0, 0, 2 }, { 0 }, { 1, 3 }, { 0 }, },
+        .height = { { 0, 0, 1 }, { 0 }, { 0, 1 } },
+        .config_map = { 6, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_LFE, TYPE_CPE, 
TYPE_CPE },
+        .reorder_map = { 2, 0, 1, 4, 5, 3, 6, 7, 8, 9 },
+    },
+    {
+        .layout = AV_CHANNEL_LAYOUT_7POINT1POINT2,
+        .num_ele = { 3, 0, 2, 1 },
+        .pairing = { { 0, 1, 1 }, { 0 }, { 1, 1 }, },
+        .index = { { 0, 0, 3 }, { 0 }, { 2, 1 }, { 0 } },
+        .height = { { 0, 0, 1 }, { 0 }, { 0, 0 } },
+        .config_map = { 6, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_CPE, TYPE_LFE, 
TYPE_CPE },
+        .reorder_map = { 2, 0, 1, 4, 5, 6, 7, 3, 8, 9 },
+    },
+    {
+        .layout = AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK,
+        .num_ele = { 3, 0, 3, 1 },
+        .pairing = { { 0, 1, 1 }, { 0 }, { 1, 1, 1 }, },
+        .index = { { 0, 0, 3 }, { 0 }, { 2, 1, 4 }, { 0 } },
+        .height = { { 0, 0, 1 }, { 0 }, { 0, 0, 1 } },
+        .config_map = { 7, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_CPE, TYPE_LFE, 
TYPE_CPE, TYPE_CPE },
+        .reorder_map = { 2, 0, 1, 4, 5, 6, 7, 3, 8, 9, 10, 11 },
+    },
+    {
+        .layout = AV_CHANNEL_LAYOUT_9POINT1POINT4_BACK,
+        .num_ele = { 4, 0, 3, 1 },
+        .pairing = { { 0, 1, 1, 1 }, { 0 }, { 1, 1, 1 }, },
+        .index = { { 0, 0, 1, 4 }, { 0 }, { 2, 3, 5 }, { 0 } },
+        .height = { { 0, 0, 0, 1 }, { 0 }, { 0, 0, 1 } },
+        .config_map = { 8, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_CPE, TYPE_CPE, 
TYPE_LFE, TYPE_CPE, TYPE_CPE },
+        .reorder_map = { 2, 6, 7, 0, 1, 8, 9, 4, 5, 3, 10, 11, 12, 13 },
+    },
+    {
+        .layout = AV_CHANNEL_LAYOUT_9POINT1POINT6,
+        .num_ele = { 4, 1, 3, 1 },
+        .pairing = { { 0, 1, 1, 1 }, { 1 }, { 1, 1, 1 }, },
+        .index = { { 0, 0, 1, 4 }, { 5 }, { 2, 3, 6 }, { 0 } },
+        .height = { { 0, 0, 0, 1 }, { 1 }, { 0, 0, 1 } },
+        .config_map = { 9, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_CPE, TYPE_CPE, 
TYPE_LFE, TYPE_CPE, TYPE_CPE, TYPE_CPE },
+        .reorder_map = { 2, 6, 7, 0, 1, 8, 9, 4, 5, 3, 10, 11, 14, 15, 12, 13 
},
+    },
+    {   /* Meant for order 2/mixed ambisonics */
+        .layout = { .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = 9,
+                    .u.mask = AV_CH_LAYOUT_OCTAGONAL | AV_CH_TOP_CENTER },
+        .num_ele = { 2, 1, 3, 0 },
+        .pairing = { { 0, 1 }, { 0 }, { 1, 1, 0 }, },
+        .index = { { 0, 0 }, { 2 }, { 1, 2, 1 }, },
+        .height = { { 0, 0, 0 }, { 1 }, { 0, 0, 0 } },
+        .config_map = { 6, TYPE_SCE, TYPE_CPE, TYPE_CPE, TYPE_CPE, TYPE_SCE, 
TYPE_SCE },
+        .reorder_map = { 2, 0, 1, 6, 7, 3, 4, 5, 8 },
+    },
 };
 
 static void put_pce(PutBitContext *pb, AVCodecContext *avctx)
@@ -331,8 +396,28 @@ static void put_pce(PutBitContext *pb, AVCodecContext 
*avctx)
     }
 
     align_put_bits(pb);
-    put_bits(pb, 8, strlen(aux_data));
-    ff_put_string(pb, aux_data, 0);
+    if (s->needs_height_ext) {
+        const AVCRC *crc_ctx = av_crc_get_table(AV_CRC_8_ATM);
+        PutBitContext height_pb;
+        uint8_t buf[16];
+        int bits = 8 + pce->num_ele[0] * 2 + pce->num_ele[1] * 2 + 
pce->num_ele[2] * 2;
+        int bytes = (bits + 7) / 8;
+
+        init_put_bits(&height_pb, buf, bytes);
+        put_bits(&height_pb, 8, 0xAC);
+        for (i = 0; i < 3; i++)
+            for (j = 0; j < pce->num_ele[i]; j++)
+                put_bits(&height_pb, 2, pce->height[i][j]);
+        flush_put_bits(&height_pb);
+
+        put_bits(pb, 8, bytes + 1);
+        ff_copy_bits(pb, buf, bits);
+        align_put_bits(pb);
+        put_bits(pb, 8, av_crc(crc_ctx, 0xFF, buf, bytes));
+    } else {
+        put_bits(pb, 8, strlen(aux_data));
+        ff_put_string(pb, aux_data, 0);
+    }
 }
 
 /**
@@ -1560,6 +1645,18 @@ static av_cold int alloc_buffers(AVCodecContext *avctx, 
AACEncContext *s)
     return 0;
 }
 
+static av_cold int check_height_ext(AVCodecContext *avctx, AACEncContext *s)
+{
+    for (int i = 0; i < avctx->ch_layout.nb_channels; i++) {
+        enum AVChannel ch = 
av_channel_layout_channel_from_index(&avctx->ch_layout, i);
+        if (ch >= AV_CHAN_TOP_FRONT_LEFT && ch <= AV_CHAN_TOP_BACK_RIGHT)
+            return 1;
+        // Layouts with TOP_SIDE channels also include the above.
+    }
+
+    return 0;
+}
+
 static av_cold int aac_encode_init(AVCodecContext *avctx)
 {
     AACEncContext *s = avctx->priv_data;
@@ -1600,6 +1697,7 @@ static av_cold int aac_encode_init(AVCodecContext *avctx)
         s->pce = aac_pce_configs[i];
         s->reorder_map = s->pce.reorder_map;
         s->chan_map = s->pce.config_map;
+        s->needs_height_ext = check_height_ext(avctx, s);
         chcfg = 0;
     } else {
         s->reorder_map = aac_chan_maps[chcfg - 1];
diff --git a/libavcodec/aacenc.h b/libavcodec/aacenc.h
index ec04465a6d..1ad2b7bc96 100644
--- a/libavcodec/aacenc.h
+++ b/libavcodec/aacenc.h
@@ -248,6 +248,7 @@ typedef struct AACPCEInfo {
     uint8_t num_ele[4];                          ///< front, side, back, lfe
     uint8_t pairing[3][8];                       ///< front, side, back
     uint8_t index[4][8];                         ///< front, side, back, lfe
+    uint8_t height[3][8];                        ///< front, side, back
     uint8_t config_map[16];                      ///< configs the encoder's 
channel specific settings
     uint8_t reorder_map[16];                     ///< maps channels from lavc 
to aac order
 } AACPCEInfo;
@@ -269,6 +270,7 @@ typedef struct AACEncContext {
 
     int profile;                                 ///< copied from avctx
     int needs_pce;                               ///< flag for non-standard 
layout
+    int needs_height_ext;                        ///< flag for PCE height 
extension
     LPCContext lpc;                              ///< used by TNS
     int samplerate_index;                        ///< MPEG-4 samplerate index
     int channels;                                ///< channel count
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to