---
 configure                            |   1 +
 libavcodec/Makefile                  |   1 +
 libavcodec/apv.h                     |  86 ++++
 libavcodec/cbs.c                     |   6 +
 libavcodec/cbs_apv.c                 | 395 ++++++++++++++++++
 libavcodec/cbs_apv.h                 | 209 ++++++++++
 libavcodec/cbs_apv_syntax_template.c | 598 +++++++++++++++++++++++++++
 libavcodec/cbs_internal.h            |   4 +
 libavformat/cbs.h                    |   1 +
 9 files changed, 1301 insertions(+)
 create mode 100644 libavcodec/apv.h
 create mode 100644 libavcodec/cbs_apv.c
 create mode 100644 libavcodec/cbs_apv.h
 create mode 100644 libavcodec/cbs_apv_syntax_template.c

diff --git a/configure b/configure
index c94b8eac43..ca404d2797 100755
--- a/configure
+++ b/configure
@@ -2562,6 +2562,7 @@ CONFIG_EXTRA="
     bswapdsp
     cabac
     cbs
+    cbs_apv
     cbs_av1
     cbs_h264
     cbs_h265
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 7bd1dbec9a..a5f5c4e904 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -83,6 +83,7 @@ OBJS-$(CONFIG_BLOCKDSP)                += blockdsp.o
 OBJS-$(CONFIG_BSWAPDSP)                += bswapdsp.o
 OBJS-$(CONFIG_CABAC)                   += cabac.o
 OBJS-$(CONFIG_CBS)                     += cbs.o cbs_bsf.o
+OBJS-$(CONFIG_CBS_APV)                 += cbs_apv.o
 OBJS-$(CONFIG_CBS_AV1)                 += cbs_av1.o
 OBJS-$(CONFIG_CBS_H264)                += cbs_h2645.o cbs_sei.o h2645_parse.o
 OBJS-$(CONFIG_CBS_H265)                += cbs_h2645.o cbs_sei.o h2645_parse.o
diff --git a/libavcodec/apv.h b/libavcodec/apv.h
new file mode 100644
index 0000000000..27e089ea22
--- /dev/null
+++ b/libavcodec/apv.h
@@ -0,0 +1,86 @@
+/*
+ * 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_APV_H
+#define AVCODEC_APV_H
+
+// PBU types (section 5.3.3).
+enum {
+    APV_PBU_PRIMARY_FRAME           = 1,
+    APV_PBU_NON_PRIMARY_FRAME       = 2,
+    APV_PBU_PREVIEW_FRAME           = 25,
+    APV_PBU_DEPTH_FRAME             = 26,
+    APV_PBU_ALPHA_FRAME             = 27,
+    APV_PBU_ACCESS_UNIT_INFORMATION = 65,
+    APV_PBU_METADATA                = 66,
+    APV_PBU_FILLER                  = 67,
+};
+
+// Format parameters (section 4.2).
+enum {
+    APV_MAX_NUM_COMP = 4,
+    APV_MB_WIDTH     = 16,
+    APV_MB_HEIGHT    = 16,
+    APV_TR_SIZE      = 8,
+};
+
+// Chroma formats (section 4.2).
+enum {
+    APV_CHROMA_FORMAT_400  = 0,
+    APV_CHROMA_FORMAT_422  = 2,
+    APV_CHROMA_FORMAT_444  = 3,
+    APV_CHROMA_FORMAT_4444 = 4,
+};
+
+// Coefficient limits (section 5.3.15).
+enum {
+    APV_BLK_COEFFS      = (APV_TR_SIZE * APV_TR_SIZE),
+    APV_MIN_TRANS_COEFF = -32768,
+    APV_MAX_TRANS_COEFF =  32767,
+};
+
+// Profiles (section 10.1.3).
+enum {
+    APV_PROFILE_422_10  = 33,
+    APV_PROFILE_422_12  = 44,
+    APV_PROFILE_444_10  = 55,
+    APV_PROFILE_444_12  = 66,
+    APV_PROFILE_4444_10 = 77,
+    APV_PROFILE_4444_12 = 88,
+    APV_PROFILE_400_10  = 99,
+};
+
+// General level limits for tiles (section 10.1.4.1).
+enum {
+    APV_MIN_TILE_WIDTH_IN_MBS  = 16,
+    APV_MIN_TILE_HEIGHT_IN_MBS = 8,
+    APV_MAX_TILE_COLS          = 20,
+    APV_MAX_TILE_ROWS          = 20,
+    APV_MAX_TILE_COUNT         = APV_MAX_TILE_COLS * APV_MAX_TILE_ROWS,
+};
+
+// Metadata types (section 10.3.1).
+enum {
+    APV_METADATA_ITU_T_T35    = 4,
+    APV_METADATA_MDCV         = 5,
+    APV_METADATA_CLL          = 6,
+    APV_METADATA_FILLER       = 10,
+    APV_METADATA_USER_DEFINED = 170,
+};
+
+#endif /* AVCODEC_APV_H */
diff --git a/libavcodec/cbs.c b/libavcodec/cbs.c
index ba1034a72e..9b485420d5 100644
--- a/libavcodec/cbs.c
+++ b/libavcodec/cbs.c
@@ -31,6 +31,9 @@
 
 
 static const CodedBitstreamType *const cbs_type_table[] = {
+#if CBS_APV
+    &CBS_FUNC(type_apv),
+#endif
 #if CBS_AV1
     &CBS_FUNC(type_av1),
 #endif
@@ -58,6 +61,9 @@ static const CodedBitstreamType *const cbs_type_table[] = {
 };
 
 const enum AVCodecID CBS_FUNC(all_codec_ids)[] = {
+#if CBS_APV
+    AV_CODEC_ID_APV,
+#endif
 #if CBS_AV1
     AV_CODEC_ID_AV1,
 #endif
diff --git a/libavcodec/cbs_apv.c b/libavcodec/cbs_apv.c
new file mode 100644
index 0000000000..c37124168a
--- /dev/null
+++ b/libavcodec/cbs_apv.c
@@ -0,0 +1,395 @@
+/*
+ * 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 "cbs.h"
+#include "cbs_internal.h"
+#include "cbs_apv.h"
+
+
+static int cbs_apv_get_num_comp(const APVRawFrameHeader *fh)
+{
+    switch (fh->frame_info.chroma_format_idc) {
+    case APV_CHROMA_FORMAT_400:
+        return 1;
+    case APV_CHROMA_FORMAT_422:
+    case APV_CHROMA_FORMAT_444:
+        return 3;
+    case APV_CHROMA_FORMAT_4444:
+        return 4;
+    default:
+        av_assert0(0 && "Invalid chroma_format_idc");
+    }
+}
+
+static void cbs_apv_derive_tile_info(APVDerivedTileInfo *ti,
+                                     const APVRawFrameHeader *fh)
+{
+    int frame_width_in_mbs   = (fh->frame_info.frame_width  + 15) / 16;
+    int frame_height_in_mbs  = (fh->frame_info.frame_height + 15) / 16;
+    int start_mb, i;
+
+    start_mb = 0;
+    for (i = 0; start_mb < frame_width_in_mbs; i++) {
+        ti->col_starts[i] = start_mb * APV_MB_WIDTH;
+        start_mb += fh->tile_info.tile_width_in_mbs;
+    }
+    av_assert0(i <= APV_MAX_TILE_COLS);
+    ti->col_starts[i] = frame_width_in_mbs * APV_MB_WIDTH;
+    ti->tile_cols = i;
+
+    start_mb = 0;
+    for (i = 0; start_mb < frame_height_in_mbs; i++) {
+        av_assert0(i < APV_MAX_TILE_ROWS);
+        ti->row_starts[i] = start_mb * APV_MB_HEIGHT;
+        start_mb += fh->tile_info.tile_height_in_mbs;
+    }
+    av_assert0(i <= APV_MAX_TILE_ROWS);
+    ti->row_starts[i] = frame_height_in_mbs * APV_MB_HEIGHT;
+    ti->tile_rows = i;
+
+    ti->num_tiles = ti->tile_cols * ti->tile_rows;
+}
+
+
+#define HEADER(name) do { \
+        ff_cbs_trace_header(ctx, name); \
+    } while (0)
+
+#define CHECK(call) do { \
+        err = (call); \
+        if (err < 0) \
+            return err; \
+    } while (0)
+
+#define SUBSCRIPTS(subs, ...) (subs > 0 ? ((int[subs + 1]){ subs, __VA_ARGS__ 
}) : NULL)
+
+
+#define u(width, name, range_min, range_max) \
+    xu(width, name, current->name, range_min, range_max, 0, )
+#define ub(width, name) \
+    xu(width, name, current->name, 0, MAX_UINT_BITS(width), 0, )
+#define us(width, name, range_min, range_max, subs, ...) \
+    xu(width, name, current->name, range_min, range_max,  subs, __VA_ARGS__)
+#define ubs(width, name, subs, ...) \
+    xu(width, name, current->name, 0, MAX_UINT_BITS(width),  subs, __VA_ARGS__)
+
+#define fixed(width, name, value) do { \
+        av_unused uint32_t fixed_value = value; \
+        xu(width, name, fixed_value, value, value, 0, ); \
+    } while (0)
+
+
+#define READ
+#define READWRITE read
+#define RWContext GetBitContext
+#define FUNC(name) cbs_apv_read_ ## name
+
+#define xu(width, name, var, range_min, range_max, subs, ...) do { \
+        uint32_t value; \
+        CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, \
+                                   SUBSCRIPTS(subs, __VA_ARGS__), \
+                                   &value, range_min, range_max)); \
+        var = value; \
+    } while (0)
+
+#define infer(name, value) do { \
+        current->name = value; \
+    } while (0)
+
+#define byte_alignment(rw) (get_bits_count(rw) % 8)
+
+#include "cbs_apv_syntax_template.c"
+
+#undef READ
+#undef READWRITE
+#undef RWContext
+#undef FUNC
+#undef xu
+#undef infer
+#undef byte_alignment
+
+#define WRITE
+#define READWRITE write
+#define RWContext PutBitContext
+#define FUNC(name) cbs_apv_write_ ## name
+
+#define xu(width, name, var, range_min, range_max, subs, ...) do { \
+        uint32_t value = var; \
+        CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, \
+                                    SUBSCRIPTS(subs, __VA_ARGS__), \
+                                    value, range_min, range_max)); \
+    } while (0)
+
+#define infer(name, value) do { \
+        if (current->name != (value)) { \
+            av_log(ctx->log_ctx, AV_LOG_ERROR, \
+                   "%s does not match inferred value: " \
+                   "%"PRId64", but should be %"PRId64".\n", \
+                   #name, (int64_t)current->name, (int64_t)(value)); \
+            return AVERROR_INVALIDDATA; \
+        } \
+    } while (0)
+
+#define byte_alignment(rw) (put_bits_count(rw) % 8)
+
+#include "cbs_apv_syntax_template.c"
+
+#undef WRITE
+#undef READWRITE
+#undef RWContext
+#undef FUNC
+#undef xu
+#undef infer
+#undef byte_alignment
+
+
+static int cbs_apv_split_fragment(CodedBitstreamContext *ctx,
+                                  CodedBitstreamFragment *frag,
+                                  int header)
+{
+    uint8_t *data = frag->data;
+    size_t   size = frag->data_size;
+    int err, trace;
+
+    // Don't include parsing here in trace output.
+    trace = ctx->trace_enable;
+    ctx->trace_enable = 0;
+
+    while (size > 0) {
+        GetBitContext   gbc;
+        uint32_t        pbu_size;
+        APVRawPBUHeader pbu_header;
+
+        if (size < 8) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid PBU: "
+                   "fragment too short (%"SIZE_SPECIFIER" bytes).\n",
+                   size);
+            err = AVERROR_INVALIDDATA;
+            goto fail;
+        }
+
+        pbu_size = AV_RB32(data);
+        if (pbu_size < 4 ) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid PBU: "
+                   "pbu_size too small (%"PRIu32" bytes).\n",
+                   pbu_size);
+            err = AVERROR_INVALIDDATA;
+            goto fail;
+        }
+
+        data += 4;
+        size -= 4;
+
+        if (pbu_size > size) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid PBU: "
+                   "pbu_size too large (%"PRIu32" bytes).\n",
+                   pbu_size);
+            err = AVERROR_INVALIDDATA;
+            goto fail;
+        }
+
+        init_get_bits(&gbc, data, 8 * pbu_size);
+
+        err = cbs_apv_read_pbu_header(ctx, &gbc, &pbu_header);
+        if (err < 0)
+            return err;
+
+        // Could select/skip frames based on type/group_id here.
+
+        err = ff_cbs_append_unit_data(frag, pbu_header.pbu_type,
+                                      data, pbu_size, frag->data_ref);
+        if (err < 0)
+            return err;
+
+        data += pbu_size;
+        size -= pbu_size;
+    }
+
+    err = 0;
+fail:
+    ctx->trace_enable = trace;
+    return err;
+}
+
+static int cbs_apv_read_unit(CodedBitstreamContext *ctx,
+                             CodedBitstreamUnit *unit)
+{
+    GetBitContext gbc;
+    int err;
+
+    err = init_get_bits(&gbc, unit->data, 8 * unit->data_size);
+    if (err < 0)
+        return err;
+
+    err = ff_cbs_alloc_unit_content(ctx, unit);
+    if (err < 0)
+        return err;
+
+    switch (unit->type) {
+    case APV_PBU_PRIMARY_FRAME:
+        {
+            APVRawFrame *frame = unit->content;
+
+            err = cbs_apv_read_frame(ctx, &gbc, frame);
+            if (err < 0)
+                return err;
+
+            // Each tile inside the frame has pointers into the unit
+            // data buffer; make a single reference here for all of
+            // them together.
+            frame->tile_data_ref = av_buffer_ref(unit->data_ref);
+            if (!frame->tile_data_ref)
+                return AVERROR(ENOMEM);
+        }
+        break;
+    case APV_PBU_ACCESS_UNIT_INFORMATION:
+        {
+            err = cbs_apv_read_au_info(ctx, &gbc, unit->content);
+            if (err < 0)
+                return err;
+        }
+        break;
+    case APV_PBU_METADATA:
+        {
+            err = cbs_apv_read_metadata(ctx, &gbc, unit->content);
+            if (err < 0)
+                return err;
+        }
+        break;
+    case APV_PBU_FILLER:
+        {
+            err = cbs_apv_read_filler(ctx, &gbc, unit->content);
+            if (err < 0)
+                return err;
+        }
+        break;
+    default:
+        return AVERROR(ENOSYS);
+    }
+
+    return 0;
+}
+
+static int cbs_apv_write_unit(CodedBitstreamContext *ctx,
+                              CodedBitstreamUnit *unit,
+                              PutBitContext *pbc)
+{
+    int err;
+
+    switch (unit->type) {
+    case APV_PBU_PRIMARY_FRAME:
+        {
+            APVRawFrame *frame = unit->content;
+
+            err = cbs_apv_write_frame(ctx, pbc, frame);
+            if (err < 0)
+                return err;
+        }
+        break;
+    case APV_PBU_ACCESS_UNIT_INFORMATION:
+        {
+            err = cbs_apv_write_au_info(ctx, pbc, unit->content);
+            if (err < 0)
+                return err;
+        }
+        break;
+    case APV_PBU_METADATA:
+        {
+            err = cbs_apv_write_metadata(ctx, pbc, unit->content);
+            if (err < 0)
+                return err;
+        }
+        break;
+    case APV_PBU_FILLER:
+        {
+            err = cbs_apv_write_filler(ctx, pbc, unit->content);
+            if (err < 0)
+                return err;
+        }
+        break;
+    default:
+        return AVERROR(ENOSYS);
+    }
+
+    return 0;
+}
+
+static int cbs_apv_assemble_fragment(CodedBitstreamContext *ctx,
+                                     CodedBitstreamFragment *frag)
+{
+    size_t size = 0, pos;
+
+    for (int i = 0; i < frag->nb_units; i++)
+        size += frag->units[i].data_size + 4;
+
+    frag->data_ref = av_buffer_alloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
+    if (!frag->data_ref)
+        return AVERROR(ENOMEM);
+    frag->data = frag->data_ref->data;
+    memset(frag->data + size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
+
+    pos = 0;
+    for (int i = 0; i < frag->nb_units; i++) {
+        AV_WB32(frag->data + pos, frag->units[i].data_size);
+        pos += 4;
+
+        memcpy(frag->data + pos, frag->units[i].data,
+               frag->units[i].data_size);
+        pos += frag->units[i].data_size;
+    }
+    av_assert0(pos == size);
+    frag->data_size = size;
+
+    return 0;
+}
+
+
+static const CodedBitstreamUnitTypeDescriptor cbs_apv_unit_types[] = {
+    {
+        .nb_unit_types   = CBS_UNIT_TYPE_RANGE,
+        .unit_type.range = {
+            .start       = APV_PBU_PRIMARY_FRAME,
+            .end         = APV_PBU_ALPHA_FRAME,
+        },
+        .content_type    = CBS_CONTENT_TYPE_INTERNAL_REFS,
+        .content_size    = sizeof(APVRawFrame),
+        .type.ref        = {
+            .nb_offsets  = 1,
+            .offsets     = { offsetof(APVRawFrame, tile_data_ref) -
+                             sizeof(void*) },
+        },
+    },
+
+    CBS_UNIT_TYPE_POD(APV_PBU_METADATA, APVRawMetadata),
+
+    CBS_UNIT_TYPE_END_OF_LIST
+};
+
+const CodedBitstreamType ff_cbs_type_apv = {
+    .codec_id          = AV_CODEC_ID_APV,
+
+    .priv_data_size    = sizeof(CodedBitstreamAPVContext),
+
+    .unit_types        = cbs_apv_unit_types,
+
+    .split_fragment    = &cbs_apv_split_fragment,
+    .read_unit         = &cbs_apv_read_unit,
+    .write_unit        = &cbs_apv_write_unit,
+    .assemble_fragment = &cbs_apv_assemble_fragment,
+};
diff --git a/libavcodec/cbs_apv.h b/libavcodec/cbs_apv.h
new file mode 100644
index 0000000000..7a7f35626b
--- /dev/null
+++ b/libavcodec/cbs_apv.h
@@ -0,0 +1,209 @@
+/*
+ * 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_CBS_APV_H
+#define AVCODEC_CBS_APV_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "libavutil/buffer.h"
+#include "apv.h"
+
+// Arbitrary limits to avoid large structures.
+#define CBS_APV_MAX_AU_FRAMES         8
+#define CBS_APV_MAX_METADATA_PAYLOADS 8
+
+
+typedef struct APVRawPBUHeader {
+    uint8_t  pbu_type;
+    uint16_t group_id;
+    uint8_t  reserved_zero_8bits;
+} APVRawPBUHeader;
+
+typedef struct APVRawFiller {
+    size_t   filler_size;
+} APVRawFiller;
+
+typedef struct APVRawFrameInfo {
+    uint8_t  profile_idc;
+    uint8_t  level_idc;
+    uint8_t  band_idc;
+    uint8_t  reserved_zero_5bits;
+    uint32_t frame_width;
+    uint32_t frame_height;
+    uint8_t  chroma_format_idc;
+    uint8_t  bit_depth_minus8;
+    uint8_t  capture_time_distance;
+    uint8_t  reserved_zero_8bits;
+} APVRawFrameInfo;
+
+typedef struct APVRawQuantizationMatrix {
+    uint8_t  q_matrix[APV_MAX_NUM_COMP][APV_TR_SIZE][APV_TR_SIZE];
+} APVRawQuantizationMatrix;
+
+typedef struct APVRawTileInfo {
+    uint32_t tile_width_in_mbs;
+    uint32_t tile_height_in_mbs;
+    uint8_t  tile_size_present_in_fh_flag;
+    uint32_t tile_size_in_fh[APV_MAX_TILE_COUNT];
+} APVRawTileInfo;
+
+typedef struct APVRawFrameHeader {
+    APVRawFrameInfo frame_info;
+    uint8_t  reserved_zero_8bits;
+
+    uint8_t  color_description_present_flag;
+    uint8_t  color_primaries;
+    uint8_t  transfer_characteristics;
+    uint8_t  matrix_coefficients;
+    uint8_t  full_range_flag;
+
+    uint8_t  use_q_matrix;
+    APVRawQuantizationMatrix quantization_matrix;
+
+    APVRawTileInfo tile_info;
+
+    uint8_t  reserved_zero_8bits_2;
+} APVRawFrameHeader;
+
+typedef struct APVRawTileHeader {
+    uint16_t tile_header_size;
+    uint16_t tile_index;
+    uint32_t tile_data_size[APV_MAX_NUM_COMP];
+    uint8_t  tile_qp       [APV_MAX_NUM_COMP];
+    uint8_t  reserved_zero_8bits;
+} APVRawTileHeader;
+
+typedef struct APVRawTile {
+    APVRawTileHeader tile_header;
+
+    uint8_t         *tile_data[APV_MAX_NUM_COMP];
+    uint8_t         *tile_dummy_byte;
+    uint32_t         tile_dummy_byte_size;
+} APVRawTile;
+
+typedef struct APVRawFrame {
+    APVRawPBUHeader   pbu_header;
+    APVRawFrameHeader frame_header;
+    uint32_t          tile_size[APV_MAX_TILE_COUNT];
+    APVRawTile        tile     [APV_MAX_TILE_COUNT];
+    APVRawFiller      filler;
+
+    AVBufferRef      *tile_data_ref;
+} APVRawFrame;
+
+typedef struct APVRawAUInfo {
+    uint16_t num_frames;
+
+    uint8_t  pbu_type           [CBS_APV_MAX_AU_FRAMES];
+    uint8_t  group_id           [CBS_APV_MAX_AU_FRAMES];
+    uint8_t  reserved_zero_8bits[CBS_APV_MAX_AU_FRAMES];
+    APVRawFrameInfo frame_info  [CBS_APV_MAX_AU_FRAMES];
+
+    uint8_t  reserved_zero_8bits_2;
+
+    APVRawFiller filler;
+} APVRawAUInfo;
+
+typedef struct APVRawMetadataITUTT35 {
+    uint8_t  itu_t_t35_country_code;
+    uint8_t  itu_t_t35_country_code_extension;
+
+    uint8_t     *data;
+    AVBufferRef *data_ref;
+    size_t       data_size;
+} APVRawMetadataITUTT35;
+
+typedef struct APVRawMetadataMDCV {
+    uint16_t primary_chromaticity_x[3];
+    uint16_t primary_chromaticity_y[3];
+    uint16_t white_point_chromaticity_x;
+    uint16_t white_point_chromaticity_y;
+    uint32_t max_mastering_luminance;
+    uint32_t min_mastering_luminance;
+} APVRawMetadataMDCV;
+
+typedef struct APVRawMetadataCLL {
+    uint16_t max_cll;
+    uint16_t max_fall;
+} APVRawMetadataCLL;
+
+typedef struct APVRawMetadataFiller {
+    uint32_t payload_size;
+} APVRawMetadataFiller;
+
+typedef struct APVRawMetadataUserDefined {
+    uint8_t  uuid[16];
+
+    uint8_t     *data;
+    AVBufferRef *data_ref;
+    size_t       data_size;
+} APVRawMetadataUserDefined;
+
+typedef struct APVRawMetadataUndefined {
+    uint8_t     *data;
+    AVBufferRef *data_ref;
+    size_t       data_size;
+} APVRawMetadataUndefined;
+
+typedef struct APVRawMetadataPayload {
+    uint32_t payload_type;
+    uint32_t payload_size;
+    union {
+        APVRawMetadataITUTT35     itu_t_t35;
+        APVRawMetadataMDCV        mdcv;
+        APVRawMetadataCLL         cll;
+        APVRawMetadataFiller      filler;
+        APVRawMetadataUserDefined user_defined;
+        APVRawMetadataUndefined   undefined;
+    };
+} APVRawMetadataPayload;
+
+typedef struct APVRawMetadata {
+    APVRawPBUHeader pbu_header;
+
+    uint32_t metadata_size;
+    uint32_t metadata_count;
+
+    APVRawMetadataPayload payloads[CBS_APV_MAX_METADATA_PAYLOADS];
+
+    APVRawFiller filler;
+} APVRawMetadata;
+
+
+typedef struct APVDerivedTileInfo {
+    uint8_t  tile_cols;
+    uint8_t  tile_rows;
+    uint16_t num_tiles;
+    // The spec uses an extra element on the end of these arrays
+    // not corresponding to any tile.
+    uint16_t col_starts[APV_MAX_TILE_COLS + 1];
+    uint16_t row_starts[APV_MAX_TILE_ROWS + 1];
+} APVDerivedTileInfo;
+
+typedef struct CodedBitstreamAPVContext {
+    int bit_depth;
+    int num_comp;
+    int sub_width_c;
+    int sub_height_c;
+
+    APVDerivedTileInfo tile_info;
+} CodedBitstreamAPVContext;
+
+#endif /* AVCODEC_CBS_APV_H */
diff --git a/libavcodec/cbs_apv_syntax_template.c 
b/libavcodec/cbs_apv_syntax_template.c
new file mode 100644
index 0000000000..190e900a9b
--- /dev/null
+++ b/libavcodec/cbs_apv_syntax_template.c
@@ -0,0 +1,598 @@
+/*
+ * 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
+ */
+
+static int FUNC(pbu_header)(CodedBitstreamContext *ctx, RWContext *rw,
+                            APVRawPBUHeader *current)
+{
+    int err;
+
+    ub(8,  pbu_type);
+    ub(16, group_id);
+    u(8, reserved_zero_8bits, 0, 0);
+
+    return 0;
+}
+
+static int FUNC(byte_alignment)(CodedBitstreamContext *ctx, RWContext *rw)
+{
+    int err;
+
+    while (byte_alignment(rw) != 0)
+        fixed(1, alignment_bit_equal_to_zero, 0);
+
+    return 0;
+}
+
+static int FUNC(filler)(CodedBitstreamContext *ctx, RWContext *rw,
+                        APVRawFiller *current)
+{
+    int err;
+
+#ifdef READ
+    current->filler_size = 0;
+    while (show_bits(rw, 8) == 0xff) {
+        fixed(8, ff_byte, 0xff);
+        ++current->filler_size;
+    }
+#else
+    {
+        uint32_t i;
+        for (i = 0; i < current->filler_size; i++)
+            fixed(8, ff_byte, 0xff);
+    }
+#endif
+
+    return 0;
+}
+
+static int FUNC(frame_info)(CodedBitstreamContext *ctx, RWContext *rw,
+                            APVRawFrameInfo *current)
+{
+    int err;
+
+    ub(8,  profile_idc);
+    ub(8,  level_idc);
+    ub(3,  band_idc);
+
+    u(5, reserved_zero_5bits, 0, 0);
+
+    ub(24, frame_width);
+    ub(24, frame_height);
+
+    u(4, chroma_format_idc, 0, 4);
+    if (current->chroma_format_idc == 1) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR,
+               "chroma_format_idc 1 for 4:2:0 is not allowed in APV.\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    u(4, bit_depth_minus8, 2, 8);
+
+    ub(8, capture_time_distance);
+
+    u(8, reserved_zero_8bits, 0, 0);
+
+    return 0;
+}
+
+static int FUNC(quantization_matrix)(CodedBitstreamContext *ctx,
+                                     RWContext *rw,
+                                     APVRawQuantizationMatrix *current)
+{
+    const CodedBitstreamAPVContext *priv = ctx->priv_data;
+    int err;
+
+    for (int c = 0; c < priv->num_comp; c++) {
+        for (int y = 0; y < 8; y++) {
+            for (int x = 0; x < 8 ; x++) {
+                us(8, q_matrix[c][x][y], 1, 255, 3, c, x, y);
+            }
+        }
+    }
+
+    return 0;
+}
+
+static int FUNC(tile_info)(CodedBitstreamContext *ctx, RWContext *rw,
+                           APVRawTileInfo *current,
+                           const APVRawFrameHeader *fh)
+{
+    CodedBitstreamAPVContext *priv = ctx->priv_data;
+    int err;
+
+    u(20, tile_width_in_mbs,
+      APV_MIN_TILE_WIDTH_IN_MBS,  MAX_UINT_BITS(20));
+    u(20, tile_height_in_mbs,
+      APV_MIN_TILE_HEIGHT_IN_MBS, MAX_UINT_BITS(20));
+
+    ub(1, tile_size_present_in_fh_flag);
+
+    cbs_apv_derive_tile_info(&priv->tile_info, fh);
+
+    if (current->tile_size_present_in_fh_flag) {
+        for (int t = 0; t < priv->tile_info.num_tiles; t++) {
+            us(32, tile_size_in_fh[t], 10, MAX_UINT_BITS(32), 1, t);
+        }
+    }
+
+    return 0;
+}
+
+static int FUNC(frame_header)(CodedBitstreamContext *ctx, RWContext *rw,
+                              APVRawFrameHeader *current)
+{
+    CodedBitstreamAPVContext *priv = ctx->priv_data;
+    int err;
+
+    CHECK(FUNC(frame_info)(ctx, rw, &current->frame_info));
+
+    u(8, reserved_zero_8bits, 0, 0);
+
+    ub(1, color_description_present_flag);
+    if (current->color_description_present_flag) {
+        ub(8, color_primaries);
+        ub(8, transfer_characteristics);
+        ub(8, matrix_coefficients);
+    } else {
+        infer(color_primaries,          2);
+        infer(transfer_characteristics, 2);
+        infer(matrix_coefficients,      2);
+    }
+
+    priv->bit_depth = current->frame_info.bit_depth_minus8 + 8;
+    priv->num_comp  = cbs_apv_get_num_comp(current);
+    if (current->frame_info.chroma_format_idc == 2)
+        priv->sub_width_c = 2;
+    else
+        priv->sub_width_c = 1;
+    priv->sub_height_c = 1;
+
+    ub(1, use_q_matrix);
+    if (current->use_q_matrix) {
+        CHECK(FUNC(quantization_matrix)(ctx, rw,
+                                        &current->quantization_matrix));
+    } else {
+        for (int c = 0; c < priv->num_comp; c++) {
+            for (int y = 0; y < 8; y++) {
+                for (int x = 0; x < 8 ; x++) {
+                    infer(quantization_matrix.q_matrix[c][y][x], 16);
+                }
+            }
+        }
+    }
+
+    CHECK(FUNC(tile_info)(ctx, rw, &current->tile_info, current));
+
+    u(8, reserved_zero_8bits_2, 0, 0);
+
+    CHECK(FUNC(byte_alignment)(ctx, rw));
+
+    return 0;
+}
+
+static int FUNC(tile_header)(CodedBitstreamContext *ctx, RWContext *rw,
+                             APVRawTileHeader *current, int tile_idx)
+{
+    const CodedBitstreamAPVContext *priv = ctx->priv_data;
+    uint16_t expected_tile_header_size;
+    uint8_t max_qp;
+    int err;
+
+    expected_tile_header_size = 4 + priv->num_comp * (4 + 1) + 1;
+
+    u(16, tile_header_size,
+      expected_tile_header_size, expected_tile_header_size);
+
+    u(16, tile_index, tile_idx, tile_idx);
+
+    for (int c = 0; c < priv->num_comp; c++) {
+        us(32, tile_data_size[c], 1, MAX_UINT_BITS(32), 1, c);
+    }
+
+    max_qp = 3 + priv->bit_depth * 6;
+    for (int c = 0; c < priv->num_comp; c++) {
+        us(8, tile_qp[c], 0, max_qp, 1, c);
+    }
+
+    u(8, reserved_zero_8bits, 0, 0);
+
+    return 0;
+}
+
+static int FUNC(tile)(CodedBitstreamContext *ctx, RWContext *rw,
+                      APVRawTile *current, int tile_idx)
+{
+    const CodedBitstreamAPVContext *priv = ctx->priv_data;
+    int err;
+
+    CHECK(FUNC(tile_header)(ctx, rw, &current->tile_header, tile_idx));
+
+    for (int c = 0; c < priv->num_comp; c++) {
+        uint32_t comp_size = current->tile_header.tile_data_size[c];
+#ifdef READ
+        int pos = get_bits_count(rw);
+        av_assert0(pos % 8 == 0);
+        current->tile_data[c] = (uint8_t*)align_get_bits(rw);
+        skip_bits_long(rw, 8 * comp_size);
+#else
+        if (put_bytes_left(rw, 0) < comp_size)
+            return AVERROR(ENOSPC);
+        ff_copy_bits(rw, current->tile_data[c], comp_size * 8);
+#endif
+    }
+
+    return 0;
+}
+
+static int FUNC(frame)(CodedBitstreamContext *ctx, RWContext *rw,
+                       APVRawFrame *current)
+{
+    const CodedBitstreamAPVContext *priv = ctx->priv_data;
+    int err;
+
+    HEADER("Frame");
+
+    CHECK(FUNC(pbu_header)(ctx, rw, &current->pbu_header));
+
+    CHECK(FUNC(frame_header)(ctx, rw, &current->frame_header));
+
+    for (int t = 0; t < priv->tile_info.num_tiles; t++) {
+        us(32, tile_size[t], 10, MAX_UINT_BITS(32), 1, t);
+
+        CHECK(FUNC(tile)(ctx, rw, &current->tile[t], t));
+    }
+
+    CHECK(FUNC(filler)(ctx, rw, &current->filler));
+
+    return 0;
+}
+
+static int FUNC(au_info)(CodedBitstreamContext *ctx, RWContext *rw,
+                         APVRawAUInfo *current)
+{
+    int err;
+
+    HEADER("Access Unit Information");
+
+    u(16, num_frames, 1, CBS_APV_MAX_AU_FRAMES);
+
+    for (int i = 0; i < current->num_frames; i++) {
+        ubs(8, pbu_type[i], 1, i);
+        ubs(8, group_id[i], 1, i);
+
+        us(8, reserved_zero_8bits[i], 0, 0, 1, i);
+
+        CHECK(FUNC(frame_info)(ctx, rw, &current->frame_info[i]));
+    }
+
+    u(8, reserved_zero_8bits_2, 0, 0);
+
+    return 0;
+}
+
+static int FUNC(metadata_itu_t_t35)(CodedBitstreamContext *ctx,
+                                    RWContext *rw,
+                                    APVRawMetadataITUTT35 *current,
+                                    size_t payload_size)
+{
+    int err;
+    size_t read_size = payload_size - 1;
+
+    HEADER("ITU-T T.35 Metadata");
+
+    ub(8, itu_t_t35_country_code);
+
+    if (current->itu_t_t35_country_code == 0xff) {
+        ub(8, itu_t_t35_country_code_extension);
+        --read_size;
+    }
+
+#ifdef READ
+    current->data_size = read_size;
+    current->data_ref  = av_buffer_alloc(current->data_size);
+    if (!current->data_ref)
+        return AVERROR(ENOMEM);
+    current->data = current->data_ref->data;
+#else
+    if (current->data_size != read_size) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Write size mismatch: "
+               "payload %zu but expecting %zu\n",
+               current->data_size, read_size);
+        return AVERROR(EINVAL);
+    }
+#endif
+
+    for (size_t i = 0; i < current->data_size; i++) {
+        xu(8, itu_t_t35_payload[i],
+           current->data[i], 0x00, 0xff, 1, i);
+    }
+
+    return 0;
+}
+
+static int FUNC(metadata_mdcv)(CodedBitstreamContext *ctx,
+                               RWContext *rw,
+                               APVRawMetadataMDCV *current)
+{
+    int err, i;
+
+    HEADER("MDCV Metadata");
+
+    for (i = 0; i < 3; i++) {
+        ubs(16, primary_chromaticity_x[i], 1, i);
+        ubs(16, primary_chromaticity_y[i], 1, i);
+    }
+
+    ub(16, white_point_chromaticity_x);
+    ub(16, white_point_chromaticity_y);
+
+    ub(32, max_mastering_luminance);
+    ub(32, min_mastering_luminance);
+
+    return 0;
+}
+
+static int FUNC(metadata_cll)(CodedBitstreamContext *ctx,
+                              RWContext *rw,
+                              APVRawMetadataCLL *current)
+{
+    int err;
+
+    HEADER("CLL Metadata");
+
+    ub(16, max_cll);
+    ub(16, max_fall);
+
+    return 0;
+}
+
+static int FUNC(metadata_filler)(CodedBitstreamContext *ctx,
+                                 RWContext *rw,
+                                 APVRawMetadataFiller *current,
+                                 size_t payload_size)
+{
+    int err;
+
+    HEADER("Filler Metadata");
+
+    for (size_t i = 0; i < payload_size; i++)
+        fixed(8, ff_byte, 0xff);
+
+    return 0;
+}
+
+static int FUNC(metadata_user_defined)(CodedBitstreamContext *ctx,
+                                       RWContext *rw,
+                                       APVRawMetadataUserDefined *current,
+                                       size_t payload_size)
+{
+    int err;
+
+    HEADER("User-Defined Metadata");
+
+    for (int i = 0; i < 16; i++)
+        ubs(8, uuid[i], 1, i);
+
+#ifdef READ
+    current->data_size = payload_size - 16;
+    current->data_ref  = av_buffer_alloc(current->data_size);
+    if (!current->data_ref)
+        return AVERROR(ENOMEM);
+    current->data = current->data_ref->data;
+#else
+    if (current->data_size != payload_size - 16) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Write size mismatch: "
+               "payload %zu but expecting %zu\n",
+               current->data_size, payload_size - 16);
+        return AVERROR(EINVAL);
+    }
+#endif
+
+    for (size_t i = 0; i < current->data_size; i++) {
+        xu(8, user_defined_data_payload[i],
+           current->data[i], 0x00, 0xff, 1, i);
+    }
+
+    return 0;
+}
+
+static int FUNC(metadata_undefined)(CodedBitstreamContext *ctx,
+                                    RWContext *rw,
+                                    APVRawMetadataUndefined *current,
+                                    size_t payload_size)
+{
+    int err;
+
+    HEADER("Undefined Metadata");
+
+#ifdef READ
+    current->data_size = payload_size;
+    current->data_ref  = av_buffer_alloc(current->data_size);
+    if (!current->data_ref)
+        return AVERROR(ENOMEM);
+    current->data = current->data_ref->data;
+#else
+    if (current->data_size != payload_size) {
+        av_log(ctx->log_ctx, AV_LOG_ERROR, "Write size mismatch: "
+               "payload %zu but expecting %zu\n",
+               current->data_size, payload_size - 16);
+        return AVERROR(EINVAL);
+    }
+#endif
+
+    for (size_t i = 0; i < current->data_size; i++) {
+        xu(8, undefined_metadata_payload_byte[i],
+           current->data[i], 0x00, 0xff, 1, i);
+    }
+
+    return 0;
+}
+
+static int FUNC(metadata_payload)(CodedBitstreamContext *ctx,
+                                  RWContext *rw,
+                                  APVRawMetadataPayload *current)
+{
+    int err;
+
+    switch (current->payload_type) {
+    case APV_METADATA_ITU_T_T35:
+        CHECK(FUNC(metadata_itu_t_t35)(ctx, rw,
+                                       &current->itu_t_t35,
+                                       current->payload_size));
+        break;
+    case APV_METADATA_MDCV:
+        CHECK(FUNC(metadata_mdcv)(ctx, rw, &current->mdcv));
+        break;
+    case APV_METADATA_CLL:
+        CHECK(FUNC(metadata_cll)(ctx, rw, &current->cll));
+        break;
+    case APV_METADATA_FILLER:
+        CHECK(FUNC(metadata_filler)(ctx, rw,
+                                    &current->filler,
+                                    current->payload_size));
+        break;
+    case APV_METADATA_USER_DEFINED:
+        CHECK(FUNC(metadata_user_defined)(ctx, rw,
+                                          &current->user_defined,
+                                          current->payload_size));
+        break;
+    default:
+        CHECK(FUNC(metadata_undefined)(ctx, rw,
+                                       &current->undefined,
+                                       current->payload_size));
+    }
+
+    return 0;
+}
+
+static int FUNC(metadata)(CodedBitstreamContext *ctx, RWContext *rw,
+                          APVRawMetadata *current)
+{
+    int err;
+
+#ifdef WRITE
+    PutBitContext metadata_start_state;
+    uint32_t metadata_start_position;
+    int trace;
+#endif
+
+    HEADER("Metadata");
+
+    CHECK(FUNC(pbu_header)(ctx, rw, &current->pbu_header));
+
+#ifdef READ
+    ub(32, metadata_size);
+
+    for (int p = 0; p < CBS_APV_MAX_METADATA_PAYLOADS; p++) {
+        APVRawMetadataPayload *pl = &current->payloads[p];
+        uint32_t metadata_bytes_left = current->metadata_size;
+        uint32_t tmp;
+
+        pl->payload_type = 0;
+        while (show_bits(rw, 8) == 0xff) {
+            fixed(8, ff_byte, 0xff);
+            pl->payload_type += 255;
+            --metadata_bytes_left;
+        }
+        xu(8, metadata_payload_type, tmp, 0, 254, 0);
+        pl->payload_type += tmp;
+        --metadata_bytes_left;
+
+        pl->payload_size = 0;
+        while (show_bits(rw, 8) == 0xff) {
+            fixed(8, ff_byte, 0xff);
+            pl->payload_size += 255;
+            --metadata_bytes_left;
+        }
+        xu(8, metadata_payload_size, tmp, 0, 254, 0);
+        pl->payload_size += tmp;
+        --metadata_bytes_left;
+
+        if (pl->payload_size > metadata_bytes_left) {
+            av_log(ctx->log_ctx, AV_LOG_ERROR, "Invalid metadata: "
+                   "payload_size larger than remaining metadata size "
+                   "(%"PRIu32" bytes).\n", pl->payload_size);
+            return AVERROR_INVALIDDATA;
+        }
+
+        CHECK(FUNC(metadata_payload)(ctx, rw, pl));
+
+        metadata_bytes_left -= pl->payload_size;
+
+        if (metadata_bytes_left == 0)
+            break;
+    }
+#else
+    // Two passes: the first write finds the size (with tracing
+    // disabled), the second write does the real write.
+
+    metadata_start_state = *rw;
+    metadata_start_position = put_bits_count(rw);
+
+    trace = ctx->trace_enable;
+    ctx->trace_enable = 0;
+
+    for (int pass = 1; pass <= 2; pass++) {
+        *rw = metadata_start_state;
+
+        ub(32, metadata_size);
+
+        for (int p = 0; p < current->metadata_count; p++) {
+            APVRawMetadataPayload *pl = &current->payloads[p];
+            uint32_t payload_start_position;
+            uint32_t tmp;
+
+            payload_start_position = put_bits_count(rw);
+
+            tmp = pl->payload_type;
+            while (tmp >= 255) {
+                fixed(8, ff_byte, 0xff);
+                tmp -= 255;
+            }
+            xu(8, metadata_payload_type, tmp, 0, 254, 0);
+
+            tmp = pl->payload_size;
+            while (tmp >= 255) {
+                fixed(8, ff_byte, 0xff);
+                tmp -= 255;
+            }
+            xu(8, metadata_payload_size, tmp, 0, 254, 0);
+
+            err = FUNC(metadata_payload)(ctx, rw, pl);
+            ctx->trace_enable = trace;
+            if (err < 0)
+                return err;
+
+            if (pass == 1) {
+                pl->payload_size = (put_bits_count(rw) -
+                                    payload_start_position) / 8;
+            }
+        }
+
+        if (pass == 1) {
+            current->metadata_size = (put_bits_count(rw) -
+                                      metadata_start_position) / 8;
+            ctx->trace_enable = trace;
+        }
+    }
+#endif
+
+    CHECK(FUNC(filler)(ctx, rw, &current->filler));
+
+    return 0;
+}
diff --git a/libavcodec/cbs_internal.h b/libavcodec/cbs_internal.h
index 1ed1f04c15..c3265924ba 100644
--- a/libavcodec/cbs_internal.h
+++ b/libavcodec/cbs_internal.h
@@ -42,6 +42,9 @@
 #define CBS_TRACE 1
 #endif
 
+#ifndef CBS_APV
+#define CBS_APV CONFIG_CBS_APV
+#endif
 #ifndef CBS_AV1
 #define CBS_AV1 CONFIG_CBS_AV1
 #endif
@@ -383,6 +386,7 @@ int CBS_FUNC(write_signed)(CodedBitstreamContext *ctx, 
PutBitContext *pbc,
 #define CBS_UNIT_TYPE_END_OF_LIST { .nb_unit_types = 0 }
 
 
+extern const CodedBitstreamType CBS_FUNC(type_apv);
 extern const CodedBitstreamType CBS_FUNC(type_av1);
 extern const CodedBitstreamType CBS_FUNC(type_h264);
 extern const CodedBitstreamType CBS_FUNC(type_h265);
diff --git a/libavformat/cbs.h b/libavformat/cbs.h
index e4dc231001..0fab3a7457 100644
--- a/libavformat/cbs.h
+++ b/libavformat/cbs.h
@@ -22,6 +22,7 @@
 #define CBS_PREFIX lavf_cbs
 #define CBS_WRITE 0
 #define CBS_TRACE 0
+#define CBS_APV 0
 #define CBS_H264 0
 #define CBS_H265 0
 #define CBS_H266 0
-- 
2.47.2

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

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

Reply via email to