Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: James Almer <jamrial@gmail.com>
To: ffmpeg-devel@ffmpeg.org
Subject: [FFmpeg-devel] [PATCH 6/6] avcodec/aacdec: refactor the channel layout derivation code
Date: Mon, 31 Oct 2022 18:18:32 -0300
Message-ID: <20221031211832.8213-4-jamrial@gmail.com> (raw)
In-Reply-To: <20221031211554.8176-1-jamrial@gmail.com>

Generalize the checks for channels in all positions, and properly support
the three height groups (normal, top, bottom) instead of manually setting
the relevant channels for the latter two after the normal height tags were
parsed.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 libavcodec/aacdec_template.c | 256 ++++++++++++-----------------------
 libavcodec/aacdectab.h       |  27 +++-
 2 files changed, 107 insertions(+), 176 deletions(-)

diff --git a/libavcodec/aacdec_template.c b/libavcodec/aacdec_template.c
index 4a88aeae1d..245fd9f6fe 100644
--- a/libavcodec/aacdec_template.c
+++ b/libavcodec/aacdec_template.c
@@ -238,13 +238,13 @@ static int assign_pair(struct elem_to_channel e2c_vec[MAX_ELEM_ID],
 }
 
 static int count_paired_channels(uint8_t (*layout_map)[3], int tags, int pos,
-                                 int *current)
+                                 int current)
 {
     int num_pos_channels = 0;
     int first_cpe        = 0;
     int sce_parity       = 0;
     int i;
-    for (i = *current; i < tags; i++) {
+    for (i = current; i < tags; i++) {
         if (layout_map[i][2] != pos)
             break;
         if (layout_map[i][0] == TYPE_CPE) {
@@ -259,207 +259,117 @@ static int count_paired_channels(uint8_t (*layout_map)[3], int tags, int pos,
             first_cpe         = 1;
         } else {
             num_pos_channels++;
-            sce_parity ^= 1;
+            sce_parity ^= (pos != AAC_CHANNEL_LFE);
         }
     }
     if (sce_parity &&
-        ((pos == AAC_CHANNEL_FRONT && first_cpe) || pos == AAC_CHANNEL_SIDE))
+        (pos == AAC_CHANNEL_FRONT && first_cpe))
         return -1;
-    *current = i;
+
     return num_pos_channels;
 }
 
-#define PREFIX_FOR_22POINT2 (AV_CH_LAYOUT_7POINT1_WIDE_BACK|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_LOW_FREQUENCY_2)
-static uint64_t sniff_channel_order(uint8_t (*layout_map)[3], int tags)
+static int assign_channels(struct elem_to_channel e2c_vec[MAX_ELEM_ID], uint8_t (*layout_map)[3],
+                           uint64_t *layout, int tags, int layer, int pos, int *current)
 {
-    int i, n, total_non_cc_elements;
-    struct elem_to_channel e2c_vec[4 * MAX_ELEM_ID] = { { 0 } };
-    int num_front_channels, num_side_channels, num_back_channels;
-    uint64_t layout = 0;
+    int i = *current, j = 0;
+    int nb_channels = count_paired_channels(layout_map, tags, pos, i);
 
-    if (FF_ARRAY_ELEMS(e2c_vec) < tags)
+    if (nb_channels < 0 || nb_channels > 5)
         return 0;
 
-    i = 0;
-    num_front_channels =
-        count_paired_channels(layout_map, tags, AAC_CHANNEL_FRONT, &i);
-    if (num_front_channels < 0)
-        return 0;
-    num_side_channels =
-        count_paired_channels(layout_map, tags, AAC_CHANNEL_SIDE, &i);
-    if (num_side_channels < 0)
-        return 0;
-    num_back_channels =
-        count_paired_channels(layout_map, tags, AAC_CHANNEL_BACK, &i);
-    if (num_back_channels < 0)
-        return 0;
+    if (pos == AAC_CHANNEL_LFE) {
+        while (nb_channels) {
+            if (aac_channel_map[layer][pos - 1][j] == AV_CHAN_NONE)
+                return -1;
+            e2c_vec[i] = (struct elem_to_channel) {
+                .av_position  = 1ULL << aac_channel_map[layer][pos - 1][j],
+                .syn_ele      = layout_map[i][0],
+                .elem_id      = layout_map[i][1],
+                .aac_position = pos
+            };
+            *layout |= e2c_vec[i].av_position;
+            i++;
+            j++;
+            nb_channels--;
+        }
+        *current = i;
 
-    if (num_side_channels == 0 && num_back_channels >= 4) {
-        num_side_channels = 2;
-        num_back_channels -= 2;
+        return 0;
     }
 
-    i = 0;
-    if (num_front_channels & 1) {
+    while (nb_channels & 1) {
+        if (aac_channel_map[layer][pos - 1][0] == AV_CHAN_NONE)
+            return -1;
+        if (aac_channel_map[layer][pos - 1][0] == AV_CHAN_UNUSED)
+            break;
         e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_FRONT_CENTER,
-            .syn_ele      = TYPE_SCE,
+            .av_position  = 1ULL << aac_channel_map[layer][pos - 1][0],
+            .syn_ele      = layout_map[i][0],
             .elem_id      = layout_map[i][1],
-            .aac_position = AAC_CHANNEL_FRONT
+            .aac_position = pos
         };
-        layout |= e2c_vec[i].av_position;
+        *layout |= e2c_vec[i].av_position;
         i++;
-        num_front_channels--;
+        nb_channels--;
     }
-    if (num_front_channels >= 4) {
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_FRONT_LEFT_OF_CENTER,
-                         AV_CH_FRONT_RIGHT_OF_CENTER,
-                         AAC_CHANNEL_FRONT, &layout);
-        num_front_channels -= 2;
-    }
-    if (num_front_channels >= 2) {
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_FRONT_LEFT,
-                         AV_CH_FRONT_RIGHT,
-                         AAC_CHANNEL_FRONT, &layout);
-        num_front_channels -= 2;
-    }
-    if (num_front_channels)
-        return 0; // Non standard PCE defined layout
 
-    if (num_side_channels >= 2) {
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_SIDE_LEFT,
-                         AV_CH_SIDE_RIGHT,
-                         AAC_CHANNEL_SIDE, &layout);
-        num_side_channels -= 2;
-    }
-    if (num_side_channels)
-        return 0; // Non standard PCE defined layout
-
-    if (num_back_channels >= 2) {
+    j = (pos != AAC_CHANNEL_SIDE) && nb_channels <= 3 ? 3 : 1;
+    while (nb_channels >= 2) {
+        if (aac_channel_map[layer][pos - 1][j]   == AV_CHAN_NONE ||
+            aac_channel_map[layer][pos - 1][j+1] == AV_CHAN_NONE)
+            return -1;
         i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_BACK_LEFT,
-                         AV_CH_BACK_RIGHT,
-                         AAC_CHANNEL_BACK, &layout);
-        num_back_channels -= 2;
-    }
-    if (num_back_channels) {
+                         1ULL << aac_channel_map[layer][pos - 1][j],
+                         1ULL << aac_channel_map[layer][pos - 1][j+1],
+                         pos, layout);
+        j += 2;
+        nb_channels -= 2;
+    }
+    while (nb_channels & 1) {
+        if (aac_channel_map[layer][pos - 1][5] == AV_CHAN_NONE)
+            return -1;
         e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_BACK_CENTER,
-            .syn_ele      = TYPE_SCE,
+            .av_position  = 1ULL << aac_channel_map[layer][pos - 1][5],
+            .syn_ele      = layout_map[i][0],
             .elem_id      = layout_map[i][1],
-            .aac_position = AAC_CHANNEL_BACK
+            .aac_position = pos
         };
-        layout |= e2c_vec[i].av_position;
+        *layout |= e2c_vec[i].av_position;
         i++;
-        num_back_channels--;
+        nb_channels--;
     }
-    if (num_back_channels)
-        return 0; // Non standard PCE defined layout
+    if (nb_channels)
+        return -1;
 
-    if (i < tags && layout_map[i][2] == AAC_CHANNEL_LFE) {
-        e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_LOW_FREQUENCY,
-            .syn_ele      = TYPE_LFE,
-            .elem_id      = layout_map[i][1],
-            .aac_position = AAC_CHANNEL_LFE
-        };
-        layout |= e2c_vec[i].av_position;
-        i++;
-    }
-    if (i < tags && layout_map[i][2] == AAC_CHANNEL_LFE) {
-        e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_LOW_FREQUENCY_2,
-            .syn_ele      = TYPE_LFE,
-            .elem_id      = layout_map[i][1],
-            .aac_position = AAC_CHANNEL_LFE
-        };
-        layout |= e2c_vec[i].av_position;
-        i++;
-    }
-    while (i < tags && layout_map[i][2] == AAC_CHANNEL_LFE) {
-        e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = UINT64_MAX,
-            .syn_ele      = TYPE_LFE,
-            .elem_id      = layout_map[i][1],
-            .aac_position = AAC_CHANNEL_LFE
-        };
-        i++;
-    }
+    *current = i;
 
-    // The previous checks would end up at 4 at this point for chan_config 14
-    if (layout == AV_CH_LAYOUT_5POINT1_BACK && tags == 5 && i == 4) {
-        const uint8_t (*reference_layout_map)[3] = aac_channel_layout_map[13];
-        for (int j = 0; j < tags; j++) {
-            if (layout_map[j][0] != reference_layout_map[j][0] ||
-                layout_map[j][2] != reference_layout_map[j][2])
-                goto end_of_layout_definition;
-        }
+    return 0;
+}
 
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_TOP_FRONT_LEFT,
-                         AV_CH_TOP_FRONT_RIGHT,
-                         AAC_CHANNEL_FRONT,
-                         &layout);
-    }
-    // The previous checks would end up at 8 at this point for 22.2
-    if (layout == PREFIX_FOR_22POINT2 && tags == 16 && i == 8) {
-        const uint8_t (*reference_layout_map)[3] = aac_channel_layout_map[12];
-        for (int j = 0; j < tags; j++) {
-            if (layout_map[j][0] != reference_layout_map[j][0] ||
-                layout_map[j][2] != reference_layout_map[j][2])
-                goto end_of_layout_definition;
-        }
+static uint64_t sniff_channel_order(uint8_t (*layout_map)[3], int tags)
+{
+    int i, n, total_non_cc_elements;
+    struct elem_to_channel e2c_vec[4 * MAX_ELEM_ID] = { { 0 } };
+    uint64_t layout = 0;
 
-        e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_TOP_FRONT_CENTER,
-            .syn_ele      = layout_map[i][0],
-            .elem_id      = layout_map[i][1],
-            .aac_position = layout_map[i][2]
-        }; layout |= e2c_vec[i].av_position; i++;
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_TOP_FRONT_LEFT,
-                         AV_CH_TOP_FRONT_RIGHT,
-                         AAC_CHANNEL_FRONT,
-                         &layout);
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_TOP_SIDE_LEFT,
-                         AV_CH_TOP_SIDE_RIGHT,
-                         AAC_CHANNEL_SIDE,
-                         &layout);
-        e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_TOP_CENTER,
-            .syn_ele      = layout_map[i][0],
-            .elem_id      = layout_map[i][1],
-            .aac_position = layout_map[i][2]
-        }; layout |= e2c_vec[i].av_position; i++;
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_TOP_BACK_LEFT,
-                         AV_CH_TOP_BACK_RIGHT,
-                         AAC_CHANNEL_BACK,
-                         &layout);
-        e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_TOP_BACK_CENTER,
-            .syn_ele      = layout_map[i][0],
-            .elem_id      = layout_map[i][1],
-            .aac_position = layout_map[i][2]
-        }; layout |= e2c_vec[i].av_position; i++;
-        e2c_vec[i] = (struct elem_to_channel) {
-            .av_position  = AV_CH_BOTTOM_FRONT_CENTER,
-            .syn_ele      = layout_map[i][0],
-            .elem_id      = layout_map[i][1],
-            .aac_position = layout_map[i][2]
-        }; layout |= e2c_vec[i].av_position; i++;
-        i += assign_pair(e2c_vec, layout_map, i,
-                         AV_CH_BOTTOM_FRONT_LEFT,
-                         AV_CH_BOTTOM_FRONT_RIGHT,
-                         AAC_CHANNEL_FRONT,
-                         &layout);
-    }
+    if (FF_ARRAY_ELEMS(e2c_vec) < tags)
+        return 0;
 
-end_of_layout_definition:
+    for (n = 0, i = 0; n < 3 && i < tags; n++) {
+        int ret = assign_channels(e2c_vec, layout_map, &layout, tags, n, AAC_CHANNEL_FRONT, &i);
+        if (ret < 0)
+            return 0;
+        ret = assign_channels(e2c_vec, layout_map, &layout, tags, n, AAC_CHANNEL_SIDE, &i);
+        if (ret < 0)
+		    return 0;
+        ret = assign_channels(e2c_vec, layout_map, &layout, tags, n, AAC_CHANNEL_BACK, &i);
+        if (ret < 0)
+		    return 0;
+        ret = assign_channels(e2c_vec, layout_map, &layout, tags, n, AAC_CHANNEL_LFE, &i);
+        if (ret < 0)
+		    return 0;
+    }
 
     total_non_cc_elements = n = i;
 
@@ -655,7 +565,7 @@ static int set_default_channel_config(AACContext *ac, AVCodecContext *avctx,
      * 7.1 layout was intended.
      */
     if (channel_config == 7 && avctx->strict_std_compliance < FF_COMPLIANCE_STRICT) {
-        layout_map[2][2] = AAC_CHANNEL_SIDE;
+        layout_map[2][2] = AAC_CHANNEL_BACK;
 
         if (!ac || !ac->warned_71_wide++) {
             av_log(avctx, AV_LOG_INFO, "Assuming an incorrectly encoded 7.1 channel layout"
diff --git a/libavcodec/aacdectab.h b/libavcodec/aacdectab.h
index 0e5e47da64..41f1db781d 100644
--- a/libavcodec/aacdectab.h
+++ b/libavcodec/aacdectab.h
@@ -49,12 +49,12 @@ static const uint8_t aac_channel_layout_map[16][16][3] = {
     { { 0, } },
     { { 0, } },
     { { TYPE_SCE, 0, AAC_CHANNEL_FRONT }, { TYPE_CPE, 0, AAC_CHANNEL_FRONT }, { TYPE_CPE, 1, AAC_CHANNEL_BACK }, { TYPE_SCE, 1, AAC_CHANNEL_BACK }, { TYPE_LFE, 0, AAC_CHANNEL_LFE  }, },
-    { { TYPE_SCE, 0, AAC_CHANNEL_FRONT }, { TYPE_CPE, 0, AAC_CHANNEL_FRONT }, { TYPE_CPE, 1, AAC_CHANNEL_SIDE }, { TYPE_CPE, 2, AAC_CHANNEL_BACK }, { TYPE_LFE, 0, AAC_CHANNEL_LFE  }, },
+    { { TYPE_SCE, 0, AAC_CHANNEL_FRONT }, { TYPE_CPE, 0, AAC_CHANNEL_FRONT }, { TYPE_CPE, 1, AAC_CHANNEL_BACK }, { TYPE_CPE, 2, AAC_CHANNEL_BACK }, { TYPE_LFE, 0, AAC_CHANNEL_LFE  }, },
     {
       { TYPE_SCE, 0, AAC_CHANNEL_FRONT }, // SCE1 = FC,
       { TYPE_CPE, 0, AAC_CHANNEL_FRONT }, // CPE1 = FLc and FRc,
       { TYPE_CPE, 1, AAC_CHANNEL_FRONT }, // CPE2 = FL and FR,
-      { TYPE_CPE, 2, AAC_CHANNEL_SIDE  }, // CPE3 = SiL and SiR,
+      { TYPE_CPE, 2, AAC_CHANNEL_BACK  }, // CPE3 = SiL and SiR,
       { TYPE_CPE, 3, AAC_CHANNEL_BACK  }, // CPE4 = BL and BR,
       { TYPE_SCE, 1, AAC_CHANNEL_BACK  }, // SCE2 = BC,
       { TYPE_LFE, 0, AAC_CHANNEL_LFE   }, // LFE1 = LFE1,
@@ -62,7 +62,7 @@ static const uint8_t aac_channel_layout_map[16][16][3] = {
       { TYPE_SCE, 2, AAC_CHANNEL_FRONT }, // SCE3 = TpFC,
       { TYPE_CPE, 4, AAC_CHANNEL_FRONT }, // CPE5 = TpFL and TpFR,
       { TYPE_CPE, 5, AAC_CHANNEL_SIDE  }, // CPE6 = TpSiL and TpSiR,
-      { TYPE_SCE, 3, AAC_CHANNEL_FRONT }, // SCE4 = TpC,
+      { TYPE_SCE, 3, AAC_CHANNEL_SIDE  }, // SCE4 = TpC,
       { TYPE_CPE, 6, AAC_CHANNEL_BACK  }, // CPE7 = TpBL and TpBR,
       { TYPE_SCE, 4, AAC_CHANNEL_BACK  }, // SCE5 = TpBC,
       { TYPE_SCE, 5, AAC_CHANNEL_FRONT }, // SCE6 = BtFC,
@@ -72,6 +72,27 @@ static const uint8_t aac_channel_layout_map[16][16][3] = {
     { { 0, } },
 };
 
+static const int16_t aac_channel_map[3][4][6] = {
+    {
+      { AV_CHAN_FRONT_CENTER,        AV_CHAN_FRONT_LEFT_OF_CENTER, AV_CHAN_FRONT_RIGHT_OF_CENTER, AV_CHAN_FRONT_LEFT,        AV_CHAN_FRONT_RIGHT,        AV_CHAN_NONE },
+      { AV_CHAN_UNUSED,              AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_NONE,              AV_CHAN_NONE,               AV_CHAN_NONE },
+      { AV_CHAN_UNUSED,              AV_CHAN_SIDE_LEFT,            AV_CHAN_SIDE_RIGHT,            AV_CHAN_BACK_LEFT,         AV_CHAN_BACK_RIGHT,         AV_CHAN_BACK_CENTER },
+      { AV_CHAN_LOW_FREQUENCY,       AV_CHAN_LOW_FREQUENCY_2,      AV_CHAN_NONE,                  AV_CHAN_NONE,              AV_CHAN_NONE,               AV_CHAN_NONE },
+    },
+    {
+      { AV_CHAN_TOP_FRONT_CENTER,    AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_TOP_FRONT_LEFT,    AV_CHAN_TOP_FRONT_RIGHT,    AV_CHAN_NONE },
+      { AV_CHAN_UNUSED,              AV_CHAN_TOP_SIDE_LEFT,        AV_CHAN_TOP_SIDE_RIGHT,        AV_CHAN_NONE,              AV_CHAN_NONE,               AV_CHAN_TOP_CENTER},
+      { AV_CHAN_UNUSED,              AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_TOP_BACK_LEFT,     AV_CHAN_TOP_BACK_RIGHT,     AV_CHAN_TOP_BACK_CENTER},
+      { AV_CHAN_NONE,                AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_NONE,              AV_CHAN_NONE,               AV_CHAN_NONE},
+    },
+    {
+      { AV_CHAN_BOTTOM_FRONT_CENTER, AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_BOTTOM_FRONT_LEFT, AV_CHAN_BOTTOM_FRONT_RIGHT, AV_CHAN_NONE },
+      { AV_CHAN_NONE,                AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_NONE,              AV_CHAN_NONE,               AV_CHAN_NONE },
+      { AV_CHAN_NONE,                AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_NONE,              AV_CHAN_NONE,               AV_CHAN_NONE },
+      { AV_CHAN_NONE,                AV_CHAN_NONE,                 AV_CHAN_NONE,                  AV_CHAN_NONE,              AV_CHAN_NONE,               AV_CHAN_NONE },
+    },
+};
+
 #if FF_API_OLD_CHANNEL_LAYOUT
 static const uint64_t aac_channel_layout[] = {
     AV_CH_LAYOUT_MONO,
-- 
2.38.1

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

  parent reply	other threads:[~2022-10-31 21:19 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-10-31 21:15 [FFmpeg-devel] [PATCH 1/6 v3] avcodec/aacdec: fix parsing streams with channel configuration 11 James Almer
2022-10-31 21:15 ` [FFmpeg-devel] [PATCH 2/6 v3] avutil/channel_layout: add a 7.1(top) channel layout James Almer
2022-10-31 21:18 ` [FFmpeg-devel] [PATCH 3/6] swresample/rematrix: support mixing top front left/right channels James Almer
2022-10-31 21:18 ` [FFmpeg-devel] [PATCH 4/6 v3] avcodec/aacdec: add support for channel configuration 14 James Almer
2022-10-31 21:18 ` [FFmpeg-devel] [PATCH 5/6] avcodec/aacdec: don't force a layout when a channel position is unknown James Almer
2022-10-31 21:18 ` James Almer [this message]
2022-11-03 13:47 ` [FFmpeg-devel] [PATCH 1/6 v3] avcodec/aacdec: fix parsing streams with channel configuration 11 James Almer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221031211832.8213-4-jamrial@gmail.com \
    --to=jamrial@gmail.com \
    --cc=ffmpeg-devel@ffmpeg.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git