[FFmpeg-cvslog] avformat/mov_muxer: Extended MOV muxer to handle APV video content

Dawid Kozinski git at videolan.org
Fri Jul 18 20:58:11 EEST 2025


ffmpeg | branch: master | Dawid Kozinski <d.kozinski at samsung.com> | Mon Jul 14 11:18:57 2025 +0200| [8baa691e5fa63ff8e1ac2684e70f2f7f40f77615] | committer: James Almer

avformat/mov_muxer: Extended MOV muxer to handle APV video content

- Changes in mov_write_video_tag function to handle APV elementary stream
- Provided structure APVDecoderConfigurationRecord that specifies the decoder configuration information for APV video content

Co-Authored-by: James Almer <jamrial at gmail.com>
Signed-off-by: Dawid Kozinski <d.kozinski at samsung.com>
Signed-off-by: James Almer <jamrial at gmail.com>

> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=8baa691e5fa63ff8e1ac2684e70f2f7f40f77615
---

 Changelog             |   2 +-
 libavcodec/cbs_apv.c  |  18 ++-
 libavformat/Makefile  |   2 +-
 libavformat/apv.c     | 382 ++++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/apv.h     |  49 +++++++
 libavformat/cbs.h     |   1 -
 libavformat/cbs_apv.c |   2 +
 libavformat/movenc.c  |  43 ++++++
 libavformat/movenc.h  |   2 +
 tests/ref/fate/source |   1 +
 10 files changed, 493 insertions(+), 9 deletions(-)

diff --git a/Changelog b/Changelog
index ecea094a5f..e6f5058de1 100644
--- a/Changelog
+++ b/Changelog
@@ -22,7 +22,7 @@ version <next>:
 - G.728 decoder
 - pad_cuda filter
 - Sanyo LD-ADPCM decoder
-- APV in MP4/ISOBMFF demuxing
+- APV in MP4/ISOBMFF muxing and demuxing
 - OpenHarmony hardware decoder/encoder
 
 
diff --git a/libavcodec/cbs_apv.c b/libavcodec/cbs_apv.c
index ebf57d3bbb..6182b62663 100644
--- a/libavcodec/cbs_apv.c
+++ b/libavcodec/cbs_apv.c
@@ -68,7 +68,7 @@ static void cbs_apv_derive_tile_info(APVDerivedTileInfo *ti,
 
 
 #define HEADER(name) do { \
-        ff_cbs_trace_header(ctx, name); \
+        CBS_FUNC(trace_header)(ctx, name); \
     } while (0)
 
 #define CHECK(call) do { \
@@ -102,7 +102,7 @@ static void cbs_apv_derive_tile_info(APVDerivedTileInfo *ti,
 
 #define xu(width, name, var, range_min, range_max, subs, ...) do { \
         uint32_t value; \
-        CHECK(ff_cbs_read_unsigned(ctx, rw, width, #name, \
+        CHECK(CBS_FUNC(read_unsigned)(ctx, rw, width, #name, \
                                    SUBSCRIPTS(subs, __VA_ARGS__), \
                                    &value, range_min, range_max)); \
         var = value; \
@@ -124,6 +124,7 @@ static void cbs_apv_derive_tile_info(APVDerivedTileInfo *ti,
 #undef infer
 #undef byte_alignment
 
+#if CBS_WRITE
 #define WRITE
 #define READWRITE write
 #define RWContext PutBitContext
@@ -131,7 +132,7 @@ static void cbs_apv_derive_tile_info(APVDerivedTileInfo *ti,
 
 #define xu(width, name, var, range_min, range_max, subs, ...) do { \
         uint32_t value = var; \
-        CHECK(ff_cbs_write_unsigned(ctx, rw, width, #name, \
+        CHECK(CBS_FUNC(write_unsigned)(ctx, rw, width, #name, \
                                     SUBSCRIPTS(subs, __VA_ARGS__), \
                                     value, range_min, range_max)); \
     } while (0)
@@ -157,6 +158,7 @@ static void cbs_apv_derive_tile_info(APVDerivedTileInfo *ti,
 #undef xu
 #undef infer
 #undef byte_alignment
+#endif // CBS_WRITE
 
 
 static int cbs_apv_split_fragment(CodedBitstreamContext *ctx,
@@ -234,7 +236,7 @@ static int cbs_apv_split_fragment(CodedBitstreamContext *ctx,
 
         // Could select/skip frames based on type/group_id here.
 
-        err = ff_cbs_append_unit_data(frag, pbu_header.pbu_type,
+        err = CBS_FUNC(append_unit_data)(frag, pbu_header.pbu_type,
                                       data, pbu_size, frag->data_ref);
         if (err < 0)
             goto fail;
@@ -259,7 +261,7 @@ static int cbs_apv_read_unit(CodedBitstreamContext *ctx,
     if (err < 0)
         return err;
 
-    err = ff_cbs_alloc_unit_content(ctx, unit);
+    err = CBS_FUNC(alloc_unit_content)(ctx, unit);
     if (err < 0)
         return err;
 
@@ -316,6 +318,7 @@ static int cbs_apv_write_unit(CodedBitstreamContext *ctx,
                               CodedBitstreamUnit *unit,
                               PutBitContext *pbc)
 {
+#if CBS_WRITE
     int err;
 
     switch (unit->type) {
@@ -358,6 +361,9 @@ static int cbs_apv_write_unit(CodedBitstreamContext *ctx,
     }
 
     return 0;
+#else
+    return AVERROR(ENOSYS);
+#endif
 }
 
 static int cbs_apv_assemble_fragment(CodedBitstreamContext *ctx,
@@ -441,7 +447,7 @@ static const CodedBitstreamUnitTypeDescriptor cbs_apv_unit_types[] = {
     CBS_UNIT_TYPE_END_OF_LIST
 };
 
-const CodedBitstreamType ff_cbs_type_apv = {
+const CodedBitstreamType CBS_FUNC(type_apv) = {
     .codec_id          = AV_CODEC_ID_APV,
 
     .priv_data_size    = sizeof(CodedBitstreamAPVContext),
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 816eb9be4a..c39c015e7d 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -381,7 +381,7 @@ OBJS-$(CONFIG_MOV_DEMUXER)               += mov.o mov_chan.o mov_esds.o \
 OBJS-$(CONFIG_MOV_MUXER)                 += movenc.o \
                                             movenchint.o mov_chan.o rtp.o \
                                             movenccenc.o movenc_ttml.o rawutils.o \
-                                            dovi_isom.o evc.o cbs.o cbs_av1.o
+                                            apv.o dovi_isom.o evc.o cbs.o cbs_av1.o cbs_apv.o
 OBJS-$(CONFIG_MP2_MUXER)                 += rawenc.o
 OBJS-$(CONFIG_MP3_DEMUXER)               += mp3dec.o replaygain.o
 OBJS-$(CONFIG_MP3_MUXER)                 += mp3enc.o rawenc.o id3v2enc.o
diff --git a/libavformat/apv.c b/libavformat/apv.c
new file mode 100644
index 0000000000..432d57318d
--- /dev/null
+++ b/libavformat/apv.c
@@ -0,0 +1,382 @@
+/*
+ * APV helper functions for muxers
+ * Copyright (c) 2025 Dawid Kozinski <d.kozinski at samsung.com>
+ *
+ * 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/avassert.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+
+#include "apv.h"
+#include "cbs.h"
+#include "avformat.h"
+#include "avio.h"
+#include "avio_internal.h"
+#include "libavcodec/cbs_apv.h"
+#include "libavcodec/packet.h"
+
+typedef struct APVDecoderFrameInfo {
+    uint8_t color_description_present_flag;         // 1 bit
+
+    // The variable indicates whether the capture_time_distance value in the APV bitstream's frame header should be ignored during playback.
+    // If capture_time_distance_ignored is set to true, the capture_time_distance information will not be utilized,
+    // and timing information for playback should be calculated using an alternative method.
+    // If set to false, the capture_time_distance value will be used as is from the frame header.
+    // It is recommended to set this variable to true, allowing the use of MP4 timestamps for playback and recording,
+    // which enables the conventional compression and playback methods based on the timestamp table defined by the ISO-based file format.
+    uint8_t capture_time_distance_ignored;          // 1-bit
+
+    uint8_t profile_idc;                            // 8 bits
+    uint8_t level_idc;                              // 8 bits
+    uint8_t band_idc;                               // 8 bits
+    uint32_t frame_width;                           // 32 bits
+    uint32_t frame_height;                          // 32 bits
+    uint8_t chroma_format_idc;                      // 4 bits
+    uint8_t bit_depth_minus8;                       // 4 bits
+    uint8_t capture_time_distance;                  // 8 bits
+
+    // if (color_description_present_flag)
+    uint8_t color_primaries;                        // 8 bits
+    uint8_t transfer_characteristics;               // 8 bits
+    uint8_t matrix_coefficients;                    // 8 bits
+    uint8_t full_range_flag;                        // 1 bit
+} APVDecoderFrameInfo;
+
+typedef struct APVDecoderConfigurationEntry {
+    uint8_t pbu_type;                   // 8 bits
+    uint8_t number_of_frame_info;       // 8 bits
+
+    APVDecoderFrameInfo *frame_info;   // An array of size number_of_frame_info storing elements of type APVDecoderFrameInfo*
+} APVDecoderConfigurationEntry;
+
+// ISOBMFF binding for APV
+// @see https://github.com/openapv/openapv/blob/main/readme/apv_isobmff.md
+typedef struct APVDecoderConfigurationRecord  {
+    uint8_t configurationVersion;           // 8 bits
+    uint8_t number_of_configuration_entry;  // 8 bits
+
+    APVDecoderConfigurationEntry *configuration_entry; // table of size number_of_configuration_entry
+
+    CodedBitstreamContext *cbc;
+    CodedBitstreamFragment frag;
+} APVDecoderConfigurationRecord;
+
+void ff_isom_write_apvc(AVIOContext *pb, const APVDecoderConfigurationRecord *apvc, void *logctx)
+{
+    av_log(logctx, AV_LOG_TRACE, "configurationVersion:                           %"PRIu8"\n",
+           apvc->configurationVersion);
+
+    av_log(logctx, AV_LOG_TRACE, "number_of_configuration_entry:                  %"PRIu8"\n",
+           apvc->number_of_configuration_entry);
+
+    for (int i = 0; i < apvc->number_of_configuration_entry; i++) {
+        const APVDecoderConfigurationEntry *configuration_entry = &apvc->configuration_entry[i];
+
+        av_log(logctx, AV_LOG_TRACE, "pbu_type:                                       %"PRIu8"\n",
+               configuration_entry->pbu_type);
+
+        av_log(logctx, AV_LOG_TRACE, "number_of_frame_info:                           %"PRIu8"\n",
+               configuration_entry->number_of_frame_info);
+
+        for (int j = 0; j < configuration_entry->number_of_frame_info; j++) {
+            const APVDecoderFrameInfo *frame_info = &configuration_entry->frame_info[j];
+
+            av_log(logctx, AV_LOG_TRACE, "color_description_present_flag:                 %"PRIu8"\n",
+                   frame_info->color_description_present_flag);
+
+            av_log(logctx, AV_LOG_TRACE, "capture_time_distance_ignored:                  %"PRIu8"\n",
+                   frame_info->capture_time_distance_ignored);
+
+            av_log(logctx, AV_LOG_TRACE, "profile_idc:                                    %"PRIu8"\n",
+                   frame_info->profile_idc);
+
+            av_log(logctx, AV_LOG_TRACE, "level_idc:                                      %"PRIu8"\n",
+                   frame_info->level_idc);
+
+            av_log(logctx, AV_LOG_TRACE, "band_idc:                                       %"PRIu8"\n",
+                   frame_info->band_idc);
+
+            av_log(logctx, AV_LOG_TRACE, "frame_width:                                    %"PRIu32"\n",
+                   frame_info->frame_width);
+
+            av_log(logctx, AV_LOG_TRACE, "frame_height:                                   %"PRIu32"\n",
+                   frame_info->frame_height);
+
+            av_log(logctx, AV_LOG_TRACE, "chroma_format_idc:                              %"PRIu8"\n",
+                   frame_info->chroma_format_idc);
+
+            av_log(logctx, AV_LOG_TRACE, "bit_depth_minus8:                               %"PRIu8"\n",
+                   frame_info->bit_depth_minus8);
+
+            av_log(logctx, AV_LOG_TRACE, "capture_time_distance:                          %"PRIu8"\n",
+                   frame_info->capture_time_distance);
+
+            if (frame_info->color_description_present_flag) {
+                av_log(logctx, AV_LOG_TRACE, "color_primaries:                                %"PRIu8"\n",
+                       frame_info->color_primaries);
+
+                av_log(logctx, AV_LOG_TRACE, "transfer_characteristics:                       %"PRIu8"\n",
+                       frame_info->transfer_characteristics);
+
+                av_log(logctx, AV_LOG_TRACE, "matrix_coefficients:                            %"PRIu8"\n",
+                       frame_info->matrix_coefficients);
+
+                av_log(logctx, AV_LOG_TRACE, "full_range_flag:                                %"PRIu8"\n",
+                       frame_info->full_range_flag);
+            }
+        }
+    }
+
+    /* unsigned int(8) configurationVersion = 1; */
+    avio_w8(pb, apvc->configurationVersion);
+
+    avio_w8(pb, apvc->number_of_configuration_entry);
+
+    for (int i = 0; i < apvc->number_of_configuration_entry; i++) {
+        const APVDecoderConfigurationEntry *configuration_entry = &apvc->configuration_entry[i];
+
+        avio_w8(pb, configuration_entry->pbu_type);
+        avio_w8(pb, configuration_entry->number_of_frame_info);
+
+        for (int j = 0; j < configuration_entry->number_of_frame_info; j++) {
+            const APVDecoderFrameInfo *frame_info = &configuration_entry->frame_info[j];
+
+            /* reserved_zero_6bits
+             * unsigned int(1) color_description_present_flag
+             * unsigned int(1) capture_time_distance_ignored */
+            avio_w8(pb, frame_info->color_description_present_flag << 1 |
+                        frame_info->capture_time_distance_ignored);
+
+            /* unsigned int(8) profile_idc */
+            avio_w8(pb, frame_info->profile_idc);
+
+            /* unsigned int(8) level_idc */
+            avio_w8(pb, frame_info->level_idc);
+
+            /* unsigned int(8) band_idc */
+            avio_w8(pb, frame_info->band_idc);
+
+            /* unsigned int(32) frame_width_minus1 */
+            avio_wb32(pb, frame_info->frame_width);
+
+            /* unsigned int(32) frame_height_minus1 */
+            avio_wb32(pb, frame_info->frame_height);
+
+            /* unsigned int(4) chroma_format_idc */
+            /* unsigned int(4) bit_depth_minus8 */
+            avio_w8(pb, (frame_info->chroma_format_idc << 4) |
+                        frame_info->bit_depth_minus8);
+
+            /* unsigned int(8) capture_time_distance */
+            avio_w8(pb, frame_info->capture_time_distance);
+
+            if (frame_info->color_description_present_flag) {
+                /* unsigned int(8) color_primaries */
+                avio_w8(pb, frame_info->color_primaries);
+
+                /* unsigned int(8) transfer_characteristics */
+                avio_w8(pb, frame_info->transfer_characteristics);
+
+                /* unsigned int(8) matrix_coefficients */
+                avio_w8(pb, frame_info->matrix_coefficients);
+
+                /* unsigned int(1) full_range_flag
+                 * reserved_zero_7bits */
+                avio_w8(pb, frame_info->full_range_flag << 7);
+            }
+        }
+    }
+}
+
+static const CodedBitstreamUnitType decompose_unit_types[] = {
+    APV_PBU_PRIMARY_FRAME, APV_PBU_NON_PRIMARY_FRAME,
+    APV_PBU_PREVIEW_FRAME, APV_PBU_DEPTH_FRAME, APV_PBU_ALPHA_FRAME
+};
+
+static int apv_add_configuration_entry(APVDecoderConfigurationRecord *apvc, int pbu_type)
+{
+    APVDecoderConfigurationEntry *temp;
+
+    av_assert0(apvc->number_of_configuration_entry < FF_ARRAY_ELEMS(decompose_unit_types));
+    temp = av_realloc_array(apvc->configuration_entry,
+                            apvc->number_of_configuration_entry + 1, sizeof(*apvc->configuration_entry));
+
+    if (!temp)
+        return AVERROR(ENOMEM);
+
+    apvc->configuration_entry = temp;
+    memset(&apvc->configuration_entry[apvc->number_of_configuration_entry], 0, sizeof(*apvc->configuration_entry));
+    apvc->configuration_entry[apvc->number_of_configuration_entry].pbu_type = pbu_type;
+    apvc->number_of_configuration_entry++;
+
+    return 0;
+}
+
+static int apv_add_frameinfo(APVDecoderConfigurationEntry *configuration_entry,
+                             const APVDecoderFrameInfo *frame_info)
+{
+    APVDecoderFrameInfo *temp;
+
+    if (configuration_entry->number_of_frame_info >= UINT8_MAX)
+        return AVERROR(EINVAL);
+
+    temp = av_realloc_array(configuration_entry->frame_info,
+                            configuration_entry->number_of_frame_info + 1, sizeof(*configuration_entry->frame_info));
+
+    if (!temp)
+        return AVERROR(ENOMEM);
+
+    configuration_entry->frame_info = temp;
+    memcpy(&configuration_entry->frame_info[configuration_entry->number_of_frame_info], frame_info, sizeof(*frame_info));
+    configuration_entry->number_of_frame_info++;
+
+    return 0;
+}
+
+int ff_isom_parse_apvc(APVDecoderConfigurationRecord *apvc,
+                       const AVPacket *pkt, void *logctx)
+{
+    APVDecoderFrameInfo frame_info = { .capture_time_distance_ignored = 1 };
+    int ret;
+
+    if (pkt->size < 8 || AV_RB32(pkt->data) != APV_SIGNATURE)
+        /* We can't write a valid apvC from the provided data */
+        return AVERROR_INVALIDDATA;
+
+    ret = ff_lavf_cbs_read(apvc->cbc, &apvc->frag, pkt->buf, pkt->data, pkt->size);
+    if (ret < 0) {
+        av_log(logctx, AV_LOG_ERROR, "Failed to parse access unit.\n");
+        return ret;
+    }
+
+    for (int i = 0; i < apvc->frag.nb_units; i++) {
+        const CodedBitstreamUnit *pbu = &apvc->frag.units[i];
+        int j;
+
+        switch (pbu->type) {
+        case APV_PBU_PRIMARY_FRAME:
+        case APV_PBU_NON_PRIMARY_FRAME:
+        case APV_PBU_PREVIEW_FRAME:
+        case APV_PBU_DEPTH_FRAME:
+        case APV_PBU_ALPHA_FRAME:
+            break;
+        default:
+            continue;
+        };
+
+        const APVRawFrame *frame        = pbu->content;
+        const APVRawFrameHeader *header = &frame->frame_header;
+        const APVRawFrameInfo *info     = &header->frame_info;
+        int bit_depth = info->bit_depth_minus8 + 8;
+
+        if (bit_depth < 8 || bit_depth > 16 || bit_depth % 2)
+            break;
+
+        frame_info.profile_idc = info->profile_idc;
+        frame_info.level_idc = info->level_idc;
+        frame_info.band_idc = info->band_idc;
+
+        frame_info.frame_width = info->frame_width;
+        frame_info.frame_height =info->frame_height;
+        frame_info.chroma_format_idc = info->chroma_format_idc;
+        frame_info.bit_depth_minus8 = info->bit_depth_minus8;
+        frame_info.capture_time_distance = info->capture_time_distance;
+
+        frame_info.color_description_present_flag = header->color_description_present_flag;
+        if (frame_info.color_description_present_flag) {
+            frame_info.color_primaries = header->color_primaries;
+            frame_info.transfer_characteristics = header->transfer_characteristics;
+            frame_info.matrix_coefficients = header->matrix_coefficients;
+            frame_info.full_range_flag = header->full_range_flag;
+        }
+
+        for (j = 0; j < apvc->number_of_configuration_entry; j++) {
+            int k;
+
+            if (apvc->configuration_entry[j].pbu_type != pbu->type)
+                continue;
+
+            for (k = 0; k < apvc->configuration_entry[j].number_of_frame_info; k++) {
+                if (!memcmp(&apvc->configuration_entry[j].frame_info[k], &frame_info, sizeof(frame_info)))
+                    break;
+            }
+            if (k == apvc->configuration_entry[j].number_of_frame_info) {
+                ret = apv_add_frameinfo(&apvc->configuration_entry[j], &frame_info);
+                if (ret < 0)
+                    goto end;
+            }
+            break;
+        }
+
+        if (j == apvc->number_of_configuration_entry) {
+            ret = apv_add_configuration_entry(apvc, pbu->type);
+            if (ret < 0)
+                goto end;
+            ret = apv_add_frameinfo(&apvc->configuration_entry[j], &frame_info);
+            if (ret < 0)
+                goto end;
+        }
+    }
+
+    ret = 0;
+end:
+    ff_lavf_cbs_fragment_reset(&apvc->frag);
+
+    return ret;
+}
+
+int ff_isom_init_apvc(APVDecoderConfigurationRecord **papvc, void *logctx)
+{
+    APVDecoderConfigurationRecord *apvc = av_mallocz(sizeof(*apvc));
+
+    if (!apvc)
+        return AVERROR(ENOMEM);
+
+    int ret = ff_lavf_cbs_init(&apvc->cbc, AV_CODEC_ID_APV, logctx);
+    if (ret < 0) {
+        av_freep(&apvc);
+        return ret;
+    }
+
+    apvc->cbc->decompose_unit_types    = decompose_unit_types;
+    apvc->cbc->nb_decompose_unit_types = FF_ARRAY_ELEMS(decompose_unit_types);
+
+    apvc->configurationVersion = 1;
+
+    *papvc = apvc;
+
+    return 0;
+}
+
+void ff_isom_close_apvc(APVDecoderConfigurationRecord **papvc)
+{
+    APVDecoderConfigurationRecord *apvc = *papvc;
+
+    if (!apvc)
+        return;
+
+    for (int i = 0; i < apvc->number_of_configuration_entry; i++)
+        av_freep(&apvc->configuration_entry[i].frame_info);
+    av_freep(&apvc->configuration_entry);
+
+    ff_lavf_cbs_fragment_free(&apvc->frag);
+    ff_lavf_cbs_close(&apvc->cbc);
+
+    av_freep(papvc);
+}
diff --git a/libavformat/apv.h b/libavformat/apv.h
new file mode 100644
index 0000000000..00c9094904
--- /dev/null
+++ b/libavformat/apv.h
@@ -0,0 +1,49 @@
+/*
+ * APV helper functions for muxers
+ * Copyright (c) 2025 Dawid Kozinski <d.kozinski at samsung.com>
+ *
+ * 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 AVFORMAT_APV_H
+#define AVFORMAT_APV_H
+
+#include <stdint.h>
+
+#include "avio.h"
+
+struct APVDecoderConfigurationRecord;
+struct AVPacket;
+
+/**
+ * Writes APV sample metadata to the provided AVIOContext.
+ *
+ * @param pb pointer to the AVIOContext where the apv sample metadata shall be written
+ * @param buf input data buffer
+ * @param size size in bytes of the input data buffer
+ *
+ * @return 0 in case of success, a negative error code in case of failure
+ */
+void ff_isom_write_apvc(AVIOContext *pb, const struct APVDecoderConfigurationRecord *apvc,
+                        void *logctx);
+
+int ff_isom_init_apvc(struct APVDecoderConfigurationRecord **papvc, void *logctx);
+int ff_isom_parse_apvc(struct APVDecoderConfigurationRecord *apvc,
+                       const struct AVPacket *pkt, void *logctx);
+void ff_isom_close_apvc(struct APVDecoderConfigurationRecord **papvc);
+
+#endif // AVFORMAT_APV_H
diff --git a/libavformat/cbs.h b/libavformat/cbs.h
index 0fab3a7457..e4dc231001 100644
--- a/libavformat/cbs.h
+++ b/libavformat/cbs.h
@@ -22,7 +22,6 @@
 #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
diff --git a/libavformat/cbs_apv.c b/libavformat/cbs_apv.c
new file mode 100644
index 0000000000..145e5d09bb
--- /dev/null
+++ b/libavformat/cbs_apv.c
@@ -0,0 +1,2 @@
+#include "cbs.h"
+#include "libavcodec/cbs_apv.c"
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index a651d6d618..8d13ac81cd 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -37,6 +37,7 @@
 #include "av1.h"
 #include "avc.h"
 #include "evc.h"
+#include "apv.h"
 #include "libavcodec/ac3_parser_internal.h"
 #include "libavcodec/dnxhddata.h"
 #include "libavcodec/flac.h"
@@ -1643,6 +1644,21 @@ static int mov_write_vvcc_tag(AVIOContext *pb, MOVTrack *track)
     return update_size(pb, pos);
 }
 
+static int mov_write_apvc_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track)
+{
+    int64_t pos = avio_tell(pb);
+
+    avio_wb32(pb, 0);
+    ffio_wfourcc(pb, "apvC");
+
+    avio_w8  (pb, 0); /* version */
+    avio_wb24(pb, 0); /* flags */
+
+    ff_isom_write_apvc(pb, track->apv, s);
+
+    return update_size(pb, pos);
+}
+
 /* also used by all avid codecs (dv, imx, meridien) and their variants */
 static int mov_write_avid_tag(AVIOContext *pb, MOVTrack *track)
 {
@@ -1902,6 +1918,17 @@ static int mov_get_evc_codec_tag(AVFormatContext *s, MOVTrack *track)
     return tag;
 }
 
+static int mov_get_apv_codec_tag(AVFormatContext *s, MOVTrack *track)
+{
+    int tag = track->par->codec_tag;
+
+    if (!tag)
+        tag = MKTAG('a', 'p', 'v', '1');
+
+    return tag;
+}
+
+
 static const struct {
     enum AVPixelFormat pix_fmt;
     uint32_t tag;
@@ -1988,6 +2015,8 @@ static unsigned int mov_get_codec_tag(AVFormatContext *s, MOVTrack *track)
             tag = mov_get_h264_codec_tag(s, track);
         else if (track->par->codec_id == AV_CODEC_ID_EVC)
             tag = mov_get_evc_codec_tag(s, track);
+        else if (track->par->codec_id == AV_CODEC_ID_APV)
+            tag = mov_get_apv_codec_tag(s, track);
         else if (track->par->codec_id == AV_CODEC_ID_DNXHD)
             tag = mov_get_dnxhd_codec_tag(s, track);
         else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) {
@@ -2753,6 +2782,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
     }
     else if (track->par->codec_id ==AV_CODEC_ID_EVC) {
         mov_write_evcc_tag(pb, track);
+    } else if (track->par->codec_id ==AV_CODEC_ID_APV) {
+        mov_write_apvc_tag(mov->fc, pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_VP9) {
         mov_write_vpcc_tag(mov->fc, pb, track);
     } else if (track->par->codec_id == AV_CODEC_ID_AV1) {
@@ -6815,6 +6846,12 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
                 avio_w8(pb, pkt->data[i + 2]);
             }
         }
+    } else if (par->codec_id == AV_CODEC_ID_APV) {
+        ff_isom_parse_apvc(trk->apv, pkt, s);
+        avio_wb32(s->pb, pkt->size);
+        size += 4;
+
+        avio_write(s->pb, pkt->data, pkt->size);
     } else {
         if (trk->cenc.aes_ctr) {
             if (par->codec_id == AV_CODEC_ID_H264 && par->extradata_size > 4) {
@@ -7544,6 +7581,7 @@ static void mov_free(AVFormatContext *s)
             ff_iamf_uninit_context(track->iamf);
         av_freep(&track->iamf);
 #endif
+        ff_isom_close_apvc(&track->apv);
 
         avpriv_packet_list_free(&track->squashed_packet_queue);
     }
@@ -8049,6 +8087,10 @@ static int mov_init(AVFormatContext *s)
                  * so just forbid muxing VP8 streams altogether until a new version does */
                 av_log(s, AV_LOG_ERROR, "VP8 muxing is currently not supported.\n");
                 return AVERROR_PATCHWELCOME;
+            } else if (track->par->codec_id == AV_CODEC_ID_APV) {
+                ret = ff_isom_init_apvc(&track->apv, s);
+                if (ret < 0)
+                    return ret;
             }
             if (is_cover_image(st)) {
                 track->cover_image = av_packet_alloc();
@@ -8658,6 +8700,7 @@ static const AVCodecTag codec_mp4_tags[] = {
     { AV_CODEC_ID_VVC,             MKTAG('v', 'v', 'c', '1') },
     { AV_CODEC_ID_VVC,             MKTAG('v', 'v', 'i', '1') },
     { AV_CODEC_ID_EVC,             MKTAG('e', 'v', 'c', '1') },
+    { AV_CODEC_ID_APV,             MKTAG('a', 'p', 'v', '1') },
     { AV_CODEC_ID_MPEG2VIDEO,      MKTAG('m', 'p', '4', 'v') },
     { AV_CODEC_ID_MPEG1VIDEO,      MKTAG('m', 'p', '4', 'v') },
     { AV_CODEC_ID_MJPEG,           MKTAG('m', 'p', '4', 'v') },
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index d73f7f314d..2b81d38982 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -176,6 +176,8 @@ typedef struct MOVTrack {
     int first_iamf_idx;
     int last_iamf_idx;
     AVIOContext *iamf_buf;
+
+    struct APVDecoderConfigurationRecord *apv;
 } MOVTrack;
 
 typedef enum {
diff --git a/tests/ref/fate/source b/tests/ref/fate/source
index d4b9bcee4c..54af72c008 100644
--- a/tests/ref/fate/source
+++ b/tests/ref/fate/source
@@ -11,6 +11,7 @@ libavfilter/file_open.c
 libavfilter/log2_tab.c
 libavfilter/riscv/cpu_common.c
 libavformat/cbs.c
+libavformat/cbs_apv.c
 libavformat/cbs_av1.c
 libavformat/file_open.c
 libavformat/golomb_tab.c



More information about the ffmpeg-cvslog mailing list