341 changes: 316 additions & 25 deletions libavformat/movenc.c
Expand Up @@ -1337,7 +1337,7 @@ static int mov_write_av1c_tag(AVIOContext *pb, MOVTrack *track)

avio_wb32(pb, 0);
ffio_wfourcc(pb, "av1C");
ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1);
ff_isom_write_av1c(pb, track->vos_data, track->vos_len, track->mode != MODE_AVIF);
return update_size(pb, pos);
}

Expand Down Expand Up @@ -2039,12 +2039,13 @@ static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track, int prefer_icc)
}
}

/* We should only ever be called by MOV or MP4. */
av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4);
/* We should only ever be called for MOV, MP4 and AVIF. */
av_assert0(track->mode == MODE_MOV || track->mode == MODE_MP4 ||
track->mode == MODE_AVIF);

avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "colr");
if (track->mode == MODE_MP4)
if (track->mode == MODE_MP4 || track->mode == MODE_AVIF)
ffio_wfourcc(pb, "nclx");
else
ffio_wfourcc(pb, "nclc");
Expand All @@ -2054,7 +2055,7 @@ static int mov_write_colr_tag(AVIOContext *pb, MOVTrack *track, int prefer_icc)
avio_wb16(pb, track->par->color_primaries);
avio_wb16(pb, track->par->color_trc);
avio_wb16(pb, track->par->color_space);
if (track->mode == MODE_MP4) {
if (track->mode == MODE_MP4 || track->mode == MODE_AVIF) {
int full_range = track->par->color_range == AVCOL_RANGE_JPEG;
avio_w8(pb, full_range << 7);
}
Expand Down Expand Up @@ -2120,7 +2121,7 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track)
|| (track->par->width == 1440 && track->par->height == 1080)
|| (track->par->width == 1920 && track->par->height == 1080);

if (track->mode == MODE_MOV &&
if ((track->mode == MODE_AVIF || track->mode == MODE_MOV) &&
(encoder = av_dict_get(track->st->metadata, "encoder", NULL, 0))) {
av_strlcpy(compressor_name, encoder->value, 32);
} else if (track->par->codec_id == AV_CODEC_ID_MPEG2VIDEO && xdcam_res) {
Expand All @@ -2141,6 +2142,25 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track)
}
}

static int mov_write_ccst_tag(AVIOContext *pb)
{
int64_t pos = avio_tell(pb);
// Write sane defaults:
// all_ref_pics_intra = 0 : all samples can use any type of reference.
// intra_pred_used = 1 : intra prediction may or may not be used.
// max_ref_per_pic = 15 : reserved value to indicate that any number of
// reference images can be used.
uint8_t ccstValue = (0 << 7) | /* all_ref_pics_intra */
(1 << 6) | /* intra_pred_used */
(15 << 2); /* max_ref_per_pic */
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "ccst");
avio_wb32(pb, 0); /* Version & flags */
avio_w8(pb, ccstValue);
avio_wb24(pb, 0); /* reserved */
return update_size(pb, pos);
}

static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track)
{
int ret = AVERROR_BUG;
Expand Down Expand Up @@ -2274,7 +2294,7 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
else
av_log(mov->fc, AV_LOG_WARNING, "Not writing 'gama' atom. Format is not MOV.\n");
}
if (track->mode == MODE_MOV || track->mode == MODE_MP4) {
if (track->mode == MODE_MOV || track->mode == MODE_MP4 || track->mode == MODE_AVIF) {
int has_color_info = track->par->color_primaries != AVCOL_PRI_UNSPECIFIED &&
track->par->color_trc != AVCOL_TRC_UNSPECIFIED &&
track->par->color_space != AVCOL_SPC_UNSPECIFIED;
Expand Down Expand Up @@ -2326,6 +2346,9 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex
if (avid)
avio_wb32(pb, 0);

if (track->mode == MODE_AVIF)
mov_write_ccst_tag(pb);

return update_size(pb, pos);
}

Expand Down Expand Up @@ -2828,8 +2851,13 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra
if (track) {
hdlr = (track->mode == MODE_MOV) ? "mhlr" : "\0\0\0\0";
if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) {
hdlr_type = "vide";
descr = "VideoHandler";
if (track->mode == MODE_AVIF) {
hdlr_type = "pict";
descr = "PictureHandler";
} else {
hdlr_type = "vide";
descr = "VideoHandler";
}
} else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) {
hdlr_type = "soun";
descr = "SoundHandler";
Expand Down Expand Up @@ -2894,6 +2922,128 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra
return update_size(pb, pos);
}

static int mov_write_pitm_tag(AVIOContext *pb, int item_id)
{
int64_t pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "pitm");
avio_wb32(pb, 0); /* Version & flags */
avio_wb16(pb, item_id); /* item_id */
return update_size(pb, pos);
}

static int mov_write_iloc_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
{
int64_t pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "iloc");
avio_wb32(pb, 0); /* Version & flags */
avio_w8(pb, (4 << 4) + 4); /* offset_size(4) and length_size(4) */
avio_w8(pb, 0); /* base_offset_size(4) and reserved(4) */
avio_wb16(pb, 1); /* item_count */

avio_wb16(pb, 1); /* item_id */
avio_wb16(pb, 0); /* data_reference_index */
avio_wb16(pb, 1); /* extent_count */
mov->avif_extent_pos = avio_tell(pb);
avio_wb32(pb, 0); /* extent_offset (written later) */
// For animated AVIF, we simply write the first packet's size.
avio_wb32(pb, mov->avif_extent_length); /* extent_length */

return update_size(pb, pos);
}

static int mov_write_iinf_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
{
int64_t infe_pos;
int64_t iinf_pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "iinf");
avio_wb32(pb, 0); /* Version & flags */
avio_wb16(pb, 1); /* entry_count */

infe_pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "infe");
avio_w8(pb, 0x2); /* Version */
avio_wb24(pb, 0); /* flags */
avio_wb16(pb, 1); /* item_id */
avio_wb16(pb, 0); /* item_protection_index */
avio_write(pb, "av01", 4); /* item_type */
avio_write(pb, "Color\0", 6); /* item_name */
update_size(pb, infe_pos);

return update_size(pb, iinf_pos);
}

static int mov_write_ispe_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
{
int64_t pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "ispe");
avio_wb32(pb, 0); /* Version & flags */
avio_wb32(pb, s->streams[0]->codecpar->width); /* image_width */
avio_wb32(pb, s->streams[0]->codecpar->height); /* image_height */
return update_size(pb, pos);
}

static int mov_write_pixi_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
{
int64_t pos = avio_tell(pb);
const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "pixi");
avio_wb32(pb, 0); /* Version & flags */
avio_w8(pb, pixdesc->nb_components); /* num_channels */
for (int i = 0; i < pixdesc->nb_components; ++i) {
avio_w8(pb, pixdesc->comp[i].depth); /* bits_per_channel */
}
return update_size(pb, pos);
}

static int mov_write_ipco_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
{
int64_t pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "ipco");
mov_write_ispe_tag(pb, mov, s);
mov_write_pixi_tag(pb, mov, s);
mov_write_av1c_tag(pb, &mov->tracks[0]);
mov_write_colr_tag(pb, &mov->tracks[0], 0);
return update_size(pb, pos);
}

static int mov_write_ipma_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
{
int64_t pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "ipma");
avio_wb32(pb, 0); /* Version & flags */
avio_wb32(pb, 1); /* entry_count */
avio_wb16(pb, 1); /* item_ID */
avio_w8(pb, 4); /* association_count */

// ispe association.
avio_w8(pb, 1); /* essential and property_index */
// pixi association.
avio_w8(pb, 2); /* essential and property_index */
// av1C association.
avio_w8(pb, 0x80 | 3); /* essential and property_index */
// colr association.
avio_w8(pb, 4); /* essential and property_index */
return update_size(pb, pos);
}

static int mov_write_iprp_tag(AVIOContext *pb, MOVMuxContext *mov, AVFormatContext *s)
{
int64_t pos = avio_tell(pb);
avio_wb32(pb, 0); /* size */
ffio_wfourcc(pb, "iprp");
mov_write_ipco_tag(pb, mov, s);
mov_write_ipma_tag(pb, mov, s);
return update_size(pb, pos);
}

static int mov_write_hmhd_tag(AVIOContext *pb)
{
/* This atom must be present, but leaving the values at zero
Expand Down Expand Up @@ -3139,7 +3289,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov,
if (st && (track->par->codec_type == AVMEDIA_TYPE_VIDEO ||
track->par->codec_type == AVMEDIA_TYPE_SUBTITLE)) {
int64_t track_width_1616;
if (track->mode == MODE_MOV) {
if (track->mode == MODE_MOV || track->mode == MODE_AVIF) {
track_width_1616 = track->par->width * 0x10000ULL;
} else {
track_width_1616 = av_rescale(st->sample_aspect_ratio.num,
Expand Down Expand Up @@ -3538,6 +3688,7 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov)
int max_track_id = 1, i;
int64_t max_track_len = 0;
int version;
int timescale;

for (i = 0; i < mov->nb_streams; i++) {
if (mov->tracks[i].entry > 0 && mov->tracks[i].timescale) {
Expand Down Expand Up @@ -3572,7 +3723,12 @@ static int mov_write_mvhd_tag(AVIOContext *pb, MOVMuxContext *mov)
avio_wb32(pb, mov->time); /* creation time */
avio_wb32(pb, mov->time); /* modification time */
}
avio_wb32(pb, mov->movie_timescale);

timescale = mov->movie_timescale;
if (mov->mode == MODE_AVIF && !timescale)
timescale = mov->tracks[0].timescale;

avio_wb32(pb, timescale);
(version == 1) ? avio_wb64(pb, max_track_len) : avio_wb32(pb, max_track_len); /* duration of longest track */

avio_wb32(pb, 0x00010000); /* reserved (preferred rate) 1.0 = normal */
Expand Down Expand Up @@ -3949,8 +4105,15 @@ static int mov_write_meta_tag(AVIOContext *pb, MOVMuxContext *mov,
mov_write_mdta_hdlr_tag(pb, mov, s);
mov_write_mdta_keys_tag(pb, mov, s);
mov_write_mdta_ilst_tag(pb, mov, s);
}
else {
} else if (mov->mode == MODE_AVIF) {
mov_write_hdlr_tag(s, pb, &mov->tracks[0]);
// We always write the primary item id as 1 since only one track is
// supported for AVIF.
mov_write_pitm_tag(pb, 1);
mov_write_iloc_tag(pb, mov, s);
mov_write_iinf_tag(pb, mov, s);
mov_write_iprp_tag(pb, mov, s);
} else {
/* iTunes metadata tag */
mov_write_itunes_hdlr_tag(pb, mov, s);
mov_write_ilst_tag(pb, mov, s);
Expand Down Expand Up @@ -4280,10 +4443,11 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
}

mov_write_mvhd_tag(pb, mov);
if (mov->mode != MODE_MOV && !mov->iods_skip)
if (mov->mode != MODE_MOV && mov->mode != MODE_AVIF && !mov->iods_skip)
mov_write_iods_tag(pb, mov);
for (i = 0; i < mov->nb_streams; i++) {
if (mov->tracks[i].entry > 0 || mov->flags & FF_MOV_FLAG_FRAGMENT) {
if (mov->tracks[i].entry > 0 || mov->flags & FF_MOV_FLAG_FRAGMENT ||
mov->mode == MODE_AVIF) {
int ret = mov_write_trak_tag(s, pb, mov, &(mov->tracks[i]), i < s->nb_streams ? s->streams[i] : NULL);
if (ret < 0)
return ret;
Expand All @@ -4294,7 +4458,7 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,

if (mov->mode == MODE_PSP)
mov_write_uuidusmt_tag(pb, s);
else
else if (mov->mode != MODE_AVIF)
mov_write_udta_tag(pb, mov, s);

return update_size(pb, pos);
Expand Down Expand Up @@ -5041,6 +5205,9 @@ static void mov_write_ftyp_tag_internal(AVIOContext *pb, AVFormatContext *s,
else if (mov->mode == MODE_3GP) {
ffio_wfourcc(pb, has_h264 ? "3gp6" : "3gp4");
minor = has_h264 ? 0x100 : 0x200;
} else if (mov->mode == MODE_AVIF) {
ffio_wfourcc(pb, mov->is_animated_avif ? "avis" : "avif");
minor = 0;
} else if (mov->mode & MODE_3G2) {
ffio_wfourcc(pb, has_h264 ? "3g2b" : "3g2a");
minor = has_h264 ? 0x20000 : 0x10000;
Expand Down Expand Up @@ -5104,6 +5271,31 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s)
// compatible brand a second time.
if (mov->mode == MODE_ISM) {
ffio_wfourcc(pb, "piff");
} else if (mov->mode == MODE_AVIF) {
const AVPixFmtDescriptor *pix_fmt_desc =
av_pix_fmt_desc_get(s->streams[0]->codecpar->format);
const int depth = pix_fmt_desc->comp[0].depth;
if (mov->is_animated_avif) {
// For animated AVIF, major brand is "avis". Add "avif" as a
// compatible brand.
ffio_wfourcc(pb, "avif");
ffio_wfourcc(pb, "msf1");
ffio_wfourcc(pb, "iso8");
}
ffio_wfourcc(pb, "mif1");
ffio_wfourcc(pb, "miaf");
if (depth == 8 || depth == 10) {
// MA1B and MA1A brands are based on AV1 profile. Short hand for
// computing that is based on chroma subsampling type. 420 chroma
// subsampling is MA1B. 444 chroma subsampling is MA1A.
if (!pix_fmt_desc->log2_chroma_w && !pix_fmt_desc->log2_chroma_h) {
// 444 chroma subsampling.
ffio_wfourcc(pb, "MA1A");
} else {
// 420 chroma subsampling.
ffio_wfourcc(pb, "MA1B");
}
}
} else if (mov->mode != MODE_MOV) {
// We add tfdt atoms when fragmenting, signal this with the iso6 compatible
// brand, if not already the major brand. This is compatible with users that
Expand Down Expand Up @@ -5707,7 +5899,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
if (ret < 0)
return ret;

if (mov->flags & FF_MOV_FLAG_FRAGMENT) {
if (mov->flags & FF_MOV_FLAG_FRAGMENT || mov->mode == MODE_AVIF) {
int ret;
if (mov->moov_written || mov->flags & FF_MOV_FLAG_EMPTY_MOOV) {
if (mov->frag_interleave && mov->fragments > 0) {
Expand Down Expand Up @@ -5848,7 +6040,11 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt)
avio_write(pb, reformatted_data, size);
} else {
size = ff_av1_filter_obus(pb, pkt->data, pkt->size);
if (trk->mode == MODE_AVIF && !mov->avif_extent_length) {
mov->avif_extent_length = size;
}
}

#if CONFIG_AC3_PARSER
} else if (par->codec_id == AV_CODEC_ID_EAC3) {
size = handle_eac3(mov, pkt, trk);
Expand Down Expand Up @@ -6581,11 +6777,15 @@ static int mov_init(AVFormatContext *s)
else if (IS_MODE(ipod, IPOD)) mov->mode = MODE_IPOD;
else if (IS_MODE(ismv, ISMV)) mov->mode = MODE_ISM;
else if (IS_MODE(f4v, F4V)) mov->mode = MODE_F4V;
else if (IS_MODE(avif, AVIF)) mov->mode = MODE_AVIF;
#undef IS_MODE

if (mov->flags & FF_MOV_FLAG_DELAY_MOOV)
mov->flags |= FF_MOV_FLAG_EMPTY_MOOV;

if (mov->mode == MODE_AVIF)
mov->flags |= FF_MOV_FLAG_DELAY_MOOV;

/* Set the FRAGMENT flag if any of the fragmentation methods are
* enabled. */
if (mov->max_fragment_duration || mov->max_fragment_size ||
Expand Down Expand Up @@ -6666,11 +6866,25 @@ static int mov_init(AVFormatContext *s)
/* Non-seekable output is ok if using fragmentation. If ism_lookahead
* is enabled, we don't support non-seekable output at all. */
if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL) &&
(!(mov->flags & FF_MOV_FLAG_FRAGMENT) || mov->ism_lookahead)) {
(!(mov->flags & FF_MOV_FLAG_FRAGMENT) || mov->ism_lookahead ||
mov->mode == MODE_AVIF)) {
av_log(s, AV_LOG_ERROR, "muxer does not support non seekable output\n");
return AVERROR(EINVAL);
}

/* AVIF output must have exactly one video stream */
if (mov->mode == MODE_AVIF) {
if (s->nb_streams > 1) {
av_log(s, AV_LOG_ERROR, "AVIF output requires exactly one stream\n");
return AVERROR(EINVAL);
}
if (s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) {
av_log(s, AV_LOG_ERROR, "AVIF output requires one video stream\n");
return AVERROR(EINVAL);
}
s->streams[0]->disposition |= AV_DISPOSITION_DEFAULT;
}

mov->nb_streams = s->nb_streams;
if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters)
mov->chapter_track = mov->nb_streams++;
Expand Down Expand Up @@ -6813,12 +7027,13 @@ static int mov_init(AVFormatContext *s)
pix_fmt == AV_PIX_FMT_MONOWHITE ||
pix_fmt == AV_PIX_FMT_MONOBLACK;
}
if (track->par->codec_id == AV_CODEC_ID_VP9 ||
track->par->codec_id == AV_CODEC_ID_AV1) {
if (track->mode != MODE_MP4) {
av_log(s, AV_LOG_ERROR, "%s only supported in MP4.\n", avcodec_get_name(track->par->codec_id));
return AVERROR(EINVAL);
}
if (track->par->codec_id == AV_CODEC_ID_VP9 && track->mode != MODE_MP4) {
av_log(s, AV_LOG_ERROR, "%s only supported in MP4.\n", avcodec_get_name(track->par->codec_id));
return AVERROR(EINVAL);
} else if (track->par->codec_id == AV_CODEC_ID_AV1 &&
track->mode != MODE_MP4 && track->mode != MODE_AVIF) {
av_log(s, AV_LOG_ERROR, "%s only supported in MP4 and AVIF.\n", avcodec_get_name(track->par->codec_id));
return AVERROR(EINVAL);
} else if (track->par->codec_id == AV_CODEC_ID_VP8) {
/* altref frames handling is not defined in the spec as of version v1.0,
* so just forbid muxing VP8 streams altogether until a new version does */
Expand Down Expand Up @@ -7036,7 +7251,7 @@ static int mov_write_header(AVFormatContext *s)
FF_MOV_FLAG_FRAG_EVERY_FRAME)) &&
!mov->max_fragment_duration && !mov->max_fragment_size)
mov->flags |= FF_MOV_FLAG_FRAG_KEYFRAME;
} else {
} else if (mov->mode != MODE_AVIF) {
if (mov->flags & FF_MOV_FLAG_FASTSTART)
mov->reserved_header_pos = avio_tell(pb);
mov_write_mdat_tag(pb, mov);
Expand Down Expand Up @@ -7324,6 +7539,50 @@ static int mov_check_bitstream(AVFormatContext *s, AVStream *st,
return ret;
}

static int avif_write_trailer(AVFormatContext *s)
{
AVIOContext *pb = s->pb;
MOVMuxContext *mov = s->priv_data;
int64_t pos_backup, mdat_pos;
uint8_t *buf;
int buf_size, moov_size;

if (mov->moov_written) return 0;

mov->is_animated_avif = s->streams[0]->nb_frames > 1;
mov_write_identification(pb, s);
mov_write_meta_tag(pb, mov, s);

moov_size = get_moov_size(s);
mov->tracks[0].data_offset = avio_tell(pb) + moov_size + 8;

if (mov->is_animated_avif) {
int ret;
if ((ret = mov_write_moov_tag(pb, mov, s)) < 0)
return ret;
}

buf_size = avio_get_dyn_buf(mov->mdat_buf, &buf);
avio_wb32(pb, buf_size + 8);
ffio_wfourcc(pb, "mdat");
mdat_pos = avio_tell(pb);

if (mdat_pos != (uint32_t)mdat_pos) {
av_log(s, AV_LOG_ERROR, "mdat offset does not fit in 32 bits\n");
return AVERROR_INVALIDDATA;
}

avio_write(pb, buf, buf_size);

// write extent offset.
pos_backup = avio_tell(pb);
avio_seek(pb, mov->avif_extent_pos, SEEK_SET);
avio_wb32(pb, mdat_pos); /* rewrite offset */
avio_seek(pb, pos_backup, SEEK_SET);

return 0;
}

#if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER
static const AVCodecTag codec_3gp_tags[] = {
{ AV_CODEC_ID_H263, MKTAG('s','2','6','3') },
Expand Down Expand Up @@ -7406,6 +7665,20 @@ static const AVCodecTag codec_f4v_tags[] = {
{ AV_CODEC_ID_NONE, 0 },
};

#if CONFIG_AVIF_MUXER
static const AVCodecTag codec_avif_tags[] = {
{ AV_CODEC_ID_AV1, MKTAG('a','v','0','1') },
{ AV_CODEC_ID_NONE, 0 },
};
static const AVCodecTag *const codec_avif_tags_list[] = { codec_avif_tags, NULL };

static const AVClass mov_avif_muxer_class = {
.class_name = "avif muxer",
.item_name = av_default_item_name,
.version = LIBAVUTIL_VERSION_INT,
};
#endif

#if CONFIG_MOV_MUXER
const AVOutputFormat ff_mov_muxer = {
.name = "mov",
Expand Down Expand Up @@ -7568,3 +7841,21 @@ const AVOutputFormat ff_f4v_muxer = {
.priv_class = &mov_isobmff_muxer_class,
};
#endif
#if CONFIG_AVIF_MUXER
const AVOutputFormat ff_avif_muxer = {
.name = "avif",
.long_name = NULL_IF_CONFIG_SMALL("AVIF"),
.mime_type = "image/avif",
.extensions = "avif",
.priv_data_size = sizeof(MOVMuxContext),
.video_codec = AV_CODEC_ID_AV1,
.init = mov_init,
.write_header = mov_write_header,
.write_packet = mov_write_packet,
.write_trailer = avif_write_trailer,
.deinit = mov_free,
.flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH,
.codec_tag = codec_avif_tags_list,
.priv_class = &mov_avif_muxer_class,
};
#endif
5 changes: 5 additions & 0 deletions libavformat/movenc.h
Expand Up @@ -43,6 +43,7 @@
#define MODE_IPOD 0x20
#define MODE_ISM 0x40
#define MODE_F4V 0x80
#define MODE_AVIF 0x100

typedef struct MOVIentry {
uint64_t pos;
Expand Down Expand Up @@ -244,6 +245,10 @@ typedef struct MOVMuxContext {
MOVPrftBox write_prft;
int empty_hdlr_name;
int movie_timescale;

int64_t avif_extent_pos;
int avif_extent_length;
int is_animated_avif;
} MOVMuxContext;

#define FF_MOV_FLAG_RTP_HINT (1 << 0)
Expand Down
2 changes: 1 addition & 1 deletion libavformat/version.h
Expand Up @@ -31,7 +31,7 @@

#include "version_major.h"

#define LIBAVFORMAT_VERSION_MINOR 23
#define LIBAVFORMAT_VERSION_MINOR 24
#define LIBAVFORMAT_VERSION_MICRO 100

#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
Expand Down