[PR] avcodec/aacenc: add support for writing PCE layouts with height channels (PR #24225)

James Almer via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <[email protected]>
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]
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.