* [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding @ 2022-02-17 5:51 Vignesh Venkatasubramanian 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header Vignesh Venkatasubramanian ` (3 more replies) 0 siblings, 4 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-17 5:51 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to libaom-av1 encoder to enforce some of the single image constraints in the AV1 encoder. Setting this flag will limit the encoder to producing exactly one frame and the sequence header that is produced by the encoder will be conformant to the AVIF specification [1]. Part of Fixing Trac ticket #7621 [1] https://aomediacodec.github.io/av1-avif Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavcodec/libaomenc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c index 963cc1bcbc..0398060a2f 100644 --- a/libavcodec/libaomenc.c +++ b/libavcodec/libaomenc.c @@ -99,6 +99,7 @@ typedef struct AOMEncoderContext { int enable_restoration; int usage; int tune; + int is_avif; int enable_rect_partitions; int enable_1to4_partitions; int enable_ab_partitions; @@ -746,6 +747,18 @@ static av_cold int aom_init(AVCodecContext *avctx, if (res < 0) return res; + if (ctx->is_avif) { + // Set the maximum number of frames to 1. This will let libaom set + // still_picture and reduced_still_picture_header to 1 in the Sequence + // Header as required by AVIF still images. + enccfg.g_limit = 1; + // Reduce memory usage for still images. + enccfg.g_lag_in_frames = 0; + // All frames will be key frames. + enccfg.kf_max_dist = 0; + enccfg.kf_mode = AOM_KF_DISABLED; + } + /* Construct Encoder Context */ res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); if (res != AOM_CODEC_OK) { @@ -1290,6 +1303,7 @@ static const AVOption options[] = { { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, FF_AV1_PROFILE_OPTS + { "avif-image", "Encode in single frame mode for still AVIF images.", OFFSET(is_avif), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE }, { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, -- 2.35.1.265.g69c8d7142f-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-02-17 5:51 [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding Vignesh Venkatasubramanian @ 2022-02-17 5:51 ` Vignesh Venkatasubramanian 2022-03-02 22:57 ` James Almer 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing Vignesh Venkatasubramanian ` (2 subsequent siblings) 3 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-17 5:51 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to omit seq header when generating the av1C atom. For now, this does not change any behavior. This will be used by a follow-up patch to add AVIF support. Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavformat/av1.c | 7 +++++-- libavformat/av1.h | 4 +++- libavformat/matroskaenc.c | 4 ++-- libavformat/movenc.c | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libavformat/av1.c b/libavformat/av1.c index 1fcfac2356..95ca7cc47f 100644 --- a/libavformat/av1.c +++ b/libavformat/av1.c @@ -361,7 +361,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int return AVERROR_INVALIDDATA; } -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, + int write_seq_header) { AVIOContext *meta_pb; AV1SequenceParameters seq_params; @@ -451,7 +452,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) flush_put_bits(&pbc); avio_write(pb, header, sizeof(header)); - avio_write(pb, seq, seq_size); + if (write_seq_header) { + avio_write(pb, seq, seq_size); + } meta_size = avio_get_dyn_buf(meta_pb, &meta); if (meta_size) diff --git a/libavformat/av1.h b/libavformat/av1.h index f57dabe986..a393fbb78f 100644 --- a/libavformat/av1.h +++ b/libavformat/av1.h @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int * @param pb pointer to the AVIOContext where the av1C box shall be written * @param buf input data buffer * @param size size in bytes of the input data buffer + * @param write_seq_header If 1, Sequence Header OBU will be written inside the + * av1C box. Otherwise, Sequence Header OBU will be omitted. * * @return >= 0 in case of success, a negative AVERROR code in case of failure */ -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); #endif /* AVFORMAT_AV1_H */ diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 38d9485288..5061961283 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1087,7 +1087,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, - par->extradata_size); + par->extradata_size, 1); else put_ebml_void(pb, 4 + 3); break; @@ -2663,7 +2663,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) ret = avio_open_dyn_buf(&dyn_cp); if (ret < 0) return ret; - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); if ((ret = dyn_cp->error) < 0 || !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 4c868919ae..1a746a67fd 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); return update_size(pb, pos); } -- 2.35.1.265.g69c8d7142f-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header Vignesh Venkatasubramanian @ 2022-03-02 22:57 ` James Almer 2022-03-02 23:22 ` Vignesh Venkatasubramanian 2022-03-02 23:23 ` Vignesh Venkatasubramanian 0 siblings, 2 replies; 71+ messages in thread From: James Almer @ 2022-03-02 22:57 UTC (permalink / raw) To: ffmpeg-devel On 2/17/2022 2:51 AM, Vignesh Venkatasubramanian wrote: > Add a parameter to omit seq header when generating the av1C atom. > > For now, this does not change any behavior. This will be used by a > follow-up patch to add AVIF support. > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > libavformat/av1.c | 7 +++++-- > libavformat/av1.h | 4 +++- > libavformat/matroskaenc.c | 4 ++-- > libavformat/movenc.c | 2 +- > 4 files changed, 11 insertions(+), 6 deletions(-) > > diff --git a/libavformat/av1.c b/libavformat/av1.c > index 1fcfac2356..95ca7cc47f 100644 > --- a/libavformat/av1.c > +++ b/libavformat/av1.c > @@ -361,7 +361,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int > return AVERROR_INVALIDDATA; > } > > -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) > +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, > + int write_seq_header) > { > AVIOContext *meta_pb; > AV1SequenceParameters seq_params; > @@ -451,7 +452,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) > flush_put_bits(&pbc); > > avio_write(pb, header, sizeof(header)); > - avio_write(pb, seq, seq_size); > + if (write_seq_header) { > + avio_write(pb, seq, seq_size); > + } > > meta_size = avio_get_dyn_buf(meta_pb, &meta); > if (meta_size) > diff --git a/libavformat/av1.h b/libavformat/av1.h > index f57dabe986..a393fbb78f 100644 > --- a/libavformat/av1.h > +++ b/libavformat/av1.h > @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int > * @param pb pointer to the AVIOContext where the av1C box shall be written > * @param buf input data buffer > * @param size size in bytes of the input data buffer > + * @param write_seq_header If 1, Sequence Header OBU will be written inside the > + * av1C box. Otherwise, Sequence Header OBU will be omitted. > * > * @return >= 0 in case of success, a negative AVERROR code in case of failure > */ > -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); > +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); > > #endif /* AVFORMAT_AV1_H */ > diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c > index 38d9485288..5061961283 100644 > --- a/libavformat/matroskaenc.c > +++ b/libavformat/matroskaenc.c > @@ -1087,7 +1087,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, > case AV_CODEC_ID_AV1: > if (par->extradata_size) > return ff_isom_write_av1c(dyn_cp, par->extradata, > - par->extradata_size); > + par->extradata_size, 1); > else > put_ebml_void(pb, 4 + 3); > break; > @@ -2663,7 +2663,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) > ret = avio_open_dyn_buf(&dyn_cp); > if (ret < 0) > return ret; > - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); > + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); > codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); > if ((ret = dyn_cp->error) < 0 || > !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 4c868919ae..1a746a67fd 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1303,7 +1303,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); > + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); > return update_size(pb, pos); > } > This patch no longer applies. _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-03-02 22:57 ` James Almer @ 2022-03-02 23:22 ` Vignesh Venkatasubramanian 2022-03-02 23:23 ` Vignesh Venkatasubramanian 1 sibling, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-02 23:22 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to omit seq header when generating the av1C atom. For now, this does not change any behavior. This will be used by a follow-up patch to add AVIF support. Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavformat/av1.c | 7 +++++-- libavformat/av1.h | 4 +++- libavformat/matroskaenc.c | 4 ++-- libavformat/movenc.c | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libavformat/av1.c b/libavformat/av1.c index 7caea0c377..e7c3056524 100644 --- a/libavformat/av1.c +++ b/libavformat/av1.c @@ -395,7 +395,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int return is_av1c ? 0 : AVERROR_INVALIDDATA; } -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, + int write_seq_header) { AVIOContext *meta_pb; AV1SequenceParameters seq_params; @@ -485,7 +486,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) flush_put_bits(&pbc); avio_write(pb, header, sizeof(header)); - avio_write(pb, seq, seq_size); + if (write_seq_header) { + avio_write(pb, seq, seq_size); + } meta_size = avio_get_dyn_buf(meta_pb, &meta); if (meta_size) diff --git a/libavformat/av1.h b/libavformat/av1.h index f57dabe986..a393fbb78f 100644 --- a/libavformat/av1.h +++ b/libavformat/av1.h @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int * @param pb pointer to the AVIOContext where the av1C box shall be written * @param buf input data buffer * @param size size in bytes of the input data buffer + * @param write_seq_header If 1, Sequence Header OBU will be written inside the + * av1C box. Otherwise, Sequence Header OBU will be omitted. * * @return >= 0 in case of success, a negative AVERROR code in case of failure */ -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); #endif /* AVFORMAT_AV1_H */ diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 38d9485288..5061961283 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1087,7 +1087,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, - par->extradata_size); + par->extradata_size, 1); else put_ebml_void(pb, 4 + 3); break; @@ -2663,7 +2663,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) ret = avio_open_dyn_buf(&dyn_cp); if (ret < 0) return ret; - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); if ((ret = dyn_cp->error) < 0 || !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 4c868919ae..1a746a67fd 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); return update_size(pb, pos); } -- 2.35.1.616.g0bdcbb4464-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-03-02 22:57 ` James Almer 2022-03-02 23:22 ` Vignesh Venkatasubramanian @ 2022-03-02 23:23 ` Vignesh Venkatasubramanian 2022-03-02 23:27 ` James Almer 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-02 23:23 UTC (permalink / raw) To: FFmpeg development discussions and patches On Wed, Mar 2, 2022 at 2:57 PM James Almer <jamrial@gmail.com> wrote: > > On 2/17/2022 2:51 AM, Vignesh Venkatasubramanian wrote: > > Add a parameter to omit seq header when generating the av1C atom. > > > > For now, this does not change any behavior. This will be used by a > > follow-up patch to add AVIF support. > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > libavformat/av1.c | 7 +++++-- > > libavformat/av1.h | 4 +++- > > libavformat/matroskaenc.c | 4 ++-- > > libavformat/movenc.c | 2 +- > > 4 files changed, 11 insertions(+), 6 deletions(-) > > > > diff --git a/libavformat/av1.c b/libavformat/av1.c > > index 1fcfac2356..95ca7cc47f 100644 > > --- a/libavformat/av1.c > > +++ b/libavformat/av1.c > > @@ -361,7 +361,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int > > return AVERROR_INVALIDDATA; > > } > > > > -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) > > +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, > > + int write_seq_header) > > { > > AVIOContext *meta_pb; > > AV1SequenceParameters seq_params; > > @@ -451,7 +452,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) > > flush_put_bits(&pbc); > > > > avio_write(pb, header, sizeof(header)); > > - avio_write(pb, seq, seq_size); > > + if (write_seq_header) { > > + avio_write(pb, seq, seq_size); > > + } > > > > meta_size = avio_get_dyn_buf(meta_pb, &meta); > > if (meta_size) > > diff --git a/libavformat/av1.h b/libavformat/av1.h > > index f57dabe986..a393fbb78f 100644 > > --- a/libavformat/av1.h > > +++ b/libavformat/av1.h > > @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int > > * @param pb pointer to the AVIOContext where the av1C box shall be written > > * @param buf input data buffer > > * @param size size in bytes of the input data buffer > > + * @param write_seq_header If 1, Sequence Header OBU will be written inside the > > + * av1C box. Otherwise, Sequence Header OBU will be omitted. > > * > > * @return >= 0 in case of success, a negative AVERROR code in case of failure > > */ > > -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); > > +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); > > > > #endif /* AVFORMAT_AV1_H */ > > diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c > > index 38d9485288..5061961283 100644 > > --- a/libavformat/matroskaenc.c > > +++ b/libavformat/matroskaenc.c > > @@ -1087,7 +1087,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, > > case AV_CODEC_ID_AV1: > > if (par->extradata_size) > > return ff_isom_write_av1c(dyn_cp, par->extradata, > > - par->extradata_size); > > + par->extradata_size, 1); > > else > > put_ebml_void(pb, 4 + 3); > > break; > > @@ -2663,7 +2663,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) > > ret = avio_open_dyn_buf(&dyn_cp); > > if (ret < 0) > > return ret; > > - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); > > + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); > > codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); > > if ((ret = dyn_cp->error) < 0 || > > !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > index 4c868919ae..1a746a67fd 100644 > > --- a/libavformat/movenc.c > > +++ b/libavformat/movenc.c > > @@ -1303,7 +1303,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); > > + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); > > return update_size(pb, pos); > > } > > > > This patch no longer applies. I have generated a new patch after sync'ing to the latest master. I did not notice any conflicts. Can you please check if the updated patch applies? > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-03-02 23:23 ` Vignesh Venkatasubramanian @ 2022-03-02 23:27 ` James Almer 2022-03-28 20:48 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: James Almer @ 2022-03-02 23:27 UTC (permalink / raw) To: ffmpeg-devel On 3/2/2022 8:23 PM, Vignesh Venkatasubramanian wrote: > On Wed, Mar 2, 2022 at 2:57 PM James Almer <jamrial@gmail.com> wrote: >> >> On 2/17/2022 2:51 AM, Vignesh Venkatasubramanian wrote: >>> Add a parameter to omit seq header when generating the av1C atom. >>> >>> For now, this does not change any behavior. This will be used by a >>> follow-up patch to add AVIF support. >>> >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> >>> --- >>> libavformat/av1.c | 7 +++++-- >>> libavformat/av1.h | 4 +++- >>> libavformat/matroskaenc.c | 4 ++-- >>> libavformat/movenc.c | 2 +- >>> 4 files changed, 11 insertions(+), 6 deletions(-) >>> >>> diff --git a/libavformat/av1.c b/libavformat/av1.c >>> index 1fcfac2356..95ca7cc47f 100644 >>> --- a/libavformat/av1.c >>> +++ b/libavformat/av1.c >>> @@ -361,7 +361,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int >>> return AVERROR_INVALIDDATA; >>> } >>> >>> -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) >>> +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, >>> + int write_seq_header) >>> { >>> AVIOContext *meta_pb; >>> AV1SequenceParameters seq_params; >>> @@ -451,7 +452,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) >>> flush_put_bits(&pbc); >>> >>> avio_write(pb, header, sizeof(header)); >>> - avio_write(pb, seq, seq_size); >>> + if (write_seq_header) { >>> + avio_write(pb, seq, seq_size); >>> + } >>> >>> meta_size = avio_get_dyn_buf(meta_pb, &meta); >>> if (meta_size) >>> diff --git a/libavformat/av1.h b/libavformat/av1.h >>> index f57dabe986..a393fbb78f 100644 >>> --- a/libavformat/av1.h >>> +++ b/libavformat/av1.h >>> @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int >>> * @param pb pointer to the AVIOContext where the av1C box shall be written >>> * @param buf input data buffer >>> * @param size size in bytes of the input data buffer >>> + * @param write_seq_header If 1, Sequence Header OBU will be written inside the >>> + * av1C box. Otherwise, Sequence Header OBU will be omitted. >>> * >>> * @return >= 0 in case of success, a negative AVERROR code in case of failure >>> */ >>> -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); >>> +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); >>> >>> #endif /* AVFORMAT_AV1_H */ >>> diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c >>> index 38d9485288..5061961283 100644 >>> --- a/libavformat/matroskaenc.c >>> +++ b/libavformat/matroskaenc.c >>> @@ -1087,7 +1087,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, >>> case AV_CODEC_ID_AV1: >>> if (par->extradata_size) >>> return ff_isom_write_av1c(dyn_cp, par->extradata, >>> - par->extradata_size); >>> + par->extradata_size, 1); >>> else >>> put_ebml_void(pb, 4 + 3); >>> break; >>> @@ -2663,7 +2663,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) >>> ret = avio_open_dyn_buf(&dyn_cp); >>> if (ret < 0) >>> return ret; >>> - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); >>> + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); >>> codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); >>> if ((ret = dyn_cp->error) < 0 || >>> !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { >>> diff --git a/libavformat/movenc.c b/libavformat/movenc.c >>> index 4c868919ae..1a746a67fd 100644 >>> --- a/libavformat/movenc.c >>> +++ b/libavformat/movenc.c >>> @@ -1303,7 +1303,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); >>> + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); >>> return update_size(pb, pos); >>> } >>> >> >> This patch no longer applies. > > I have generated a new patch after sync'ing to the latest master. I > did not notice any conflicts. Can you please check if the updated > patch applies? The new one does, yes. Thanks. > >> _______________________________________________ >> 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". > > > _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-03-02 23:27 ` James Almer @ 2022-03-28 20:48 ` Vignesh Venkatasubramanian 2022-04-13 20:40 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-28 20:48 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to omit seq header when generating the av1C atom. For now, this does not change any behavior. This will be used by a follow-up patch to add AVIF support. Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavformat/av1.c | 7 +++++-- libavformat/av1.h | 4 +++- libavformat/matroskaenc.c | 4 ++-- libavformat/movenc.c | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libavformat/av1.c b/libavformat/av1.c index 79065d0c9f..b6eaf50627 100644 --- a/libavformat/av1.c +++ b/libavformat/av1.c @@ -395,7 +395,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int return is_av1c ? 0 : AVERROR_INVALIDDATA; } -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, + int write_seq_header) { AVIOContext *meta_pb; AV1SequenceParameters seq_params; @@ -485,7 +486,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) flush_put_bits(&pbc); avio_write(pb, header, sizeof(header)); - avio_write(pb, seq, seq_size); + if (write_seq_header) { + avio_write(pb, seq, seq_size); + } meta_size = avio_get_dyn_buf(meta_pb, &meta); if (meta_size) diff --git a/libavformat/av1.h b/libavformat/av1.h index f57dabe986..a393fbb78f 100644 --- a/libavformat/av1.h +++ b/libavformat/av1.h @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int * @param pb pointer to the AVIOContext where the av1C box shall be written * @param buf input data buffer * @param size size in bytes of the input data buffer + * @param write_seq_header If 1, Sequence Header OBU will be written inside the + * av1C box. Otherwise, Sequence Header OBU will be omitted. * * @return >= 0 in case of success, a negative AVERROR code in case of failure */ -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); #endif /* AVFORMAT_AV1_H */ diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 3b8ca11f28..d789a618a4 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1089,7 +1089,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, - par->extradata_size); + par->extradata_size, 1); else put_ebml_void(pb, 4 + 3); break; @@ -2665,7 +2665,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) ret = avio_open_dyn_buf(&dyn_cp); if (ret < 0) return ret; - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); if ((ret = dyn_cp->error) < 0 || !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 46d66c29c2..70ceb0dea4 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1306,7 +1306,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); + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); return update_size(pb, pos); } -- 2.35.1.1021.g381101b075-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-03-28 20:48 ` Vignesh Venkatasubramanian @ 2022-04-13 20:40 ` Vignesh Venkatasubramanian 2022-05-02 21:36 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-13 20:40 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to omit seq header when generating the av1C atom. For now, this does not change any behavior. This will be used by a follow-up patch to add AVIF support. Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavformat/av1.c | 7 +++++-- libavformat/av1.h | 4 +++- libavformat/matroskaenc.c | 4 ++-- libavformat/movenc.c | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libavformat/av1.c b/libavformat/av1.c index 79065d0c9f..b6eaf50627 100644 --- a/libavformat/av1.c +++ b/libavformat/av1.c @@ -395,7 +395,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int return is_av1c ? 0 : AVERROR_INVALIDDATA; } -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, + int write_seq_header) { AVIOContext *meta_pb; AV1SequenceParameters seq_params; @@ -485,7 +486,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) flush_put_bits(&pbc); avio_write(pb, header, sizeof(header)); - avio_write(pb, seq, seq_size); + if (write_seq_header) { + avio_write(pb, seq, seq_size); + } meta_size = avio_get_dyn_buf(meta_pb, &meta); if (meta_size) diff --git a/libavformat/av1.h b/libavformat/av1.h index f57dabe986..a393fbb78f 100644 --- a/libavformat/av1.h +++ b/libavformat/av1.h @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int * @param pb pointer to the AVIOContext where the av1C box shall be written * @param buf input data buffer * @param size size in bytes of the input data buffer + * @param write_seq_header If 1, Sequence Header OBU will be written inside the + * av1C box. Otherwise, Sequence Header OBU will be omitted. * * @return >= 0 in case of success, a negative AVERROR code in case of failure */ -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); #endif /* AVFORMAT_AV1_H */ diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 3b8ca11f28..d789a618a4 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1089,7 +1089,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, - par->extradata_size); + par->extradata_size, 1); else put_ebml_void(pb, 4 + 3); break; @@ -2665,7 +2665,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) ret = avio_open_dyn_buf(&dyn_cp); if (ret < 0) return ret; - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); if ((ret = dyn_cp->error) < 0 || !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { diff --git a/libavformat/movenc.c b/libavformat/movenc.c index b9956e699c..4202d0b79a 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1334,7 +1334,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); + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); return update_size(pb, pos); } -- 2.35.1.1178.g4f1659d476-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header 2022-04-13 20:40 ` Vignesh Venkatasubramanian @ 2022-05-02 21:36 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-02 21:36 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to omit seq header when generating the av1C atom. For now, this does not change any behavior. This will be used by a follow-up patch to add AVIF support. Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavformat/av1.c | 7 +++++-- libavformat/av1.h | 4 +++- libavformat/matroskaenc.c | 4 ++-- libavformat/movenc.c | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libavformat/av1.c b/libavformat/av1.c index 79065d0c9f..b6eaf50627 100644 --- a/libavformat/av1.c +++ b/libavformat/av1.c @@ -395,7 +395,8 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int return is_av1c ? 0 : AVERROR_INVALIDDATA; } -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, + int write_seq_header) { AVIOContext *meta_pb; AV1SequenceParameters seq_params; @@ -485,7 +486,9 @@ int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size) flush_put_bits(&pbc); avio_write(pb, header, sizeof(header)); - avio_write(pb, seq, seq_size); + if (write_seq_header) { + avio_write(pb, seq, seq_size); + } meta_size = avio_get_dyn_buf(meta_pb, &meta); if (meta_size) diff --git a/libavformat/av1.h b/libavformat/av1.h index f57dabe986..a393fbb78f 100644 --- a/libavformat/av1.h +++ b/libavformat/av1.h @@ -96,9 +96,11 @@ int ff_av1_parse_seq_header(AV1SequenceParameters *seq, const uint8_t *buf, int * @param pb pointer to the AVIOContext where the av1C box shall be written * @param buf input data buffer * @param size size in bytes of the input data buffer + * @param write_seq_header If 1, Sequence Header OBU will be written inside the + * av1C box. Otherwise, Sequence Header OBU will be omitted. * * @return >= 0 in case of success, a negative AVERROR code in case of failure */ -int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size); +int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, int write_seq_header); #endif /* AVFORMAT_AV1_H */ diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c index 3b8ca11f28..d789a618a4 100644 --- a/libavformat/matroskaenc.c +++ b/libavformat/matroskaenc.c @@ -1089,7 +1089,7 @@ static int mkv_write_native_codecprivate(AVFormatContext *s, AVIOContext *pb, case AV_CODEC_ID_AV1: if (par->extradata_size) return ff_isom_write_av1c(dyn_cp, par->extradata, - par->extradata_size); + par->extradata_size, 1); else put_ebml_void(pb, 4 + 3); break; @@ -2665,7 +2665,7 @@ static int mkv_check_new_extra_data(AVFormatContext *s, const AVPacket *pkt) ret = avio_open_dyn_buf(&dyn_cp); if (ret < 0) return ret; - ff_isom_write_av1c(dyn_cp, side_data, side_data_size); + ff_isom_write_av1c(dyn_cp, side_data, side_data_size, 1); codecpriv_size = avio_get_dyn_buf(dyn_cp, &codecpriv); if ((ret = dyn_cp->error) < 0 || !codecpriv_size && (ret = AVERROR_INVALIDDATA)) { diff --git a/libavformat/movenc.c b/libavformat/movenc.c index c2aed6329f..271db99b46 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1335,7 +1335,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); + ff_isom_write_av1c(pb, track->vos_data, track->vos_len, 1); return update_size(pb, pos); } -- 2.36.0.464.gb9c8b46e94-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-17 5:51 [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding Vignesh Venkatasubramanian 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header Vignesh Venkatasubramanian @ 2022-02-17 5:51 ` Vignesh Venkatasubramanian 2022-02-22 18:40 ` Vignesh Venkatasubramanian 2022-02-17 18:09 ` [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding James Zern 2022-02-17 20:59 ` James Almer 3 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-17 5:51 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- libavformat/movenc.h | 5 + 4 files changed, 283 insertions(+), 24 deletions(-) diff --git a/configure b/configure index 1535dc3c5b..87b380fe3a 100755 --- a/configure +++ b/configure @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..400c17afbd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1a746a67fd..69b5f4bc76 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); } @@ -2004,12 +2004,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"); @@ -2019,7 +2020,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); } @@ -2103,6 +2104,8 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track) av_strlcatf(compressor_name, len, " %d%c", track->par->height, interlaced ? 'i' : 'p'); av_strlcatf(compressor_name, len, "%d", rate * (interlaced + 1)); + } else if (track->par->codec_id == AV_CODEC_ID_AV1) { + av_strlcatf(compressor_name, len, "libaom Encoder"); } } @@ -2123,6 +2126,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2239,7 +2244,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; @@ -2792,7 +2797,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2859,6 +2867,131 @@ 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); + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + int i; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, num_channels); /* num_channels */ + for (i = 0; i < num_channels; ++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 @@ -3056,7 +3189,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3104,7 +3237,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, @@ -3439,7 +3572,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3914,8 +4048,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); @@ -4245,10 +4386,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; @@ -4259,7 +4401,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); @@ -5002,6 +5144,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; @@ -5065,6 +5210,30 @@ 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, "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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5669,7 +5838,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) { @@ -5802,7 +5971,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } } else if (par->codec_id == AV_CODEC_ID_AV1) { - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { + if (trk->mode == MODE_AVIF) { + avio_write(pb, pkt->data, pkt->size); + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size, &offset); if (ret < 0) @@ -6230,6 +6401,10 @@ fail: } } + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = pkt->size; + } + return mov_write_single_packet(s, pkt); } } @@ -6569,11 +6744,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 || @@ -6797,12 +6976,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 */ @@ -7003,7 +7183,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); @@ -7291,6 +7471,54 @@ 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; + int i; + + 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); + for (i = 0; i < mov->nb_streams; i++) + mov->tracks[i].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); + + avio_write(pb, buf, buf_size); + ffio_free_dyn_buf(&mov->mdat_buf); + + // 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); + + mov->moov_written = 1; + mov->mdat_size = 0; + for (i = 0; i < mov->nb_streams; i++) { + mov->tracks[i].entry = 0; + mov->tracks[i].end_reliable = 0; + } + return 0; +} + #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, @@ -7373,6 +7601,12 @@ static const AVCodecTag codec_f4v_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; +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 }; + #if CONFIG_MOV_MUXER const AVOutputFormat ff_mov_muxer = { .name = "mov", @@ -7535,3 +7769,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_isobmff_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.265.g69c8d7142f-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing Vignesh Venkatasubramanian @ 2022-02-22 18:40 ` Vignesh Venkatasubramanian 2022-02-22 20:03 ` James Almer 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-22 18:40 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- libavformat/movenc.h | 5 + 4 files changed, 283 insertions(+), 24 deletions(-) diff --git a/configure b/configure index 1535dc3c5b..87b380fe3a 100755 --- a/configure +++ b/configure @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..400c17afbd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1a746a67fd..05537d1e78 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); } @@ -2004,12 +2004,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"); @@ -2019,7 +2020,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); } @@ -2103,6 +2104,8 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track) av_strlcatf(compressor_name, len, " %d%c", track->par->height, interlaced ? 'i' : 'p'); av_strlcatf(compressor_name, len, "%d", rate * (interlaced + 1)); + } else if (track->par->codec_id == AV_CODEC_ID_AV1 && track->mode == MODE_AVIF) { + av_strlcatf(compressor_name, len, "libaom Encoder"); } } @@ -2123,6 +2126,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2239,7 +2244,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; @@ -2792,7 +2797,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2859,6 +2867,131 @@ 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); + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + int i; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, num_channels); /* num_channels */ + for (i = 0; i < num_channels; ++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 @@ -3056,7 +3189,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3104,7 +3237,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, @@ -3439,7 +3572,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3914,8 +4048,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); @@ -4245,10 +4386,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; @@ -4259,7 +4401,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); @@ -5002,6 +5144,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; @@ -5065,6 +5210,30 @@ 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, "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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5669,7 +5838,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) { @@ -5802,7 +5971,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } } else if (par->codec_id == AV_CODEC_ID_AV1) { - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { + if (trk->mode == MODE_AVIF) { + avio_write(pb, pkt->data, pkt->size); + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size, &offset); if (ret < 0) @@ -6230,6 +6401,10 @@ fail: } } + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = pkt->size; + } + return mov_write_single_packet(s, pkt); } } @@ -6569,11 +6744,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 || @@ -6797,12 +6976,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 */ @@ -7003,7 +7183,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); @@ -7291,6 +7471,54 @@ 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; + int i; + + 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); + for (i = 0; i < mov->nb_streams; i++) + mov->tracks[i].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); + + avio_write(pb, buf, buf_size); + ffio_free_dyn_buf(&mov->mdat_buf); + + // 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); + + mov->moov_written = 1; + mov->mdat_size = 0; + for (i = 0; i < mov->nb_streams; i++) { + mov->tracks[i].entry = 0; + mov->tracks[i].end_reliable = 0; + } + return 0; +} + #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, @@ -7373,6 +7601,12 @@ static const AVCodecTag codec_f4v_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; +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 }; + #if CONFIG_MOV_MUXER const AVOutputFormat ff_mov_muxer = { .name = "mov", @@ -7535,3 +7769,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_isobmff_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.473.g83b2b277ed-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-22 18:40 ` Vignesh Venkatasubramanian @ 2022-02-22 20:03 ` James Almer 2022-02-22 21:37 ` Vignesh Venkatasubramanian 2022-02-22 21:38 ` Vignesh Venkatasubramanian 0 siblings, 2 replies; 71+ messages in thread From: James Almer @ 2022-02-22 20:03 UTC (permalink / raw) To: ffmpeg-devel On 2/22/2022 3:40 PM, Vignesh Venkatasubramanian wrote: > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- > libavformat/movenc.h | 5 + > 4 files changed, 283 insertions(+), 24 deletions(-) > > diff --git a/configure b/configure > index 1535dc3c5b..87b380fe3a 100755 > --- a/configure > +++ b/configure > @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index d066a7745b..400c17afbd 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 1a746a67fd..05537d1e78 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1303,7 +1303,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); > } > > @@ -2004,12 +2004,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"); > @@ -2019,7 +2020,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); > } > @@ -2103,6 +2104,8 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track) > av_strlcatf(compressor_name, len, " %d%c", track->par->height, interlaced ? 'i' : 'p'); > > av_strlcatf(compressor_name, len, "%d", rate * (interlaced + 1)); > + } else if (track->par->codec_id == AV_CODEC_ID_AV1 && track->mode == MODE_AVIF) { > + av_strlcatf(compressor_name, len, "libaom Encoder"); libaom is not the only AV1 encoder supported by libavcodec, a libavformat user could even not use a libavcodec based encoder to begin with, and then there's also the codec copy scenario where no encoder is used at all. This should probably use the same path as MODE_MOV above if there's a key "encoder" in track->st->metadata, and write it. _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-22 20:03 ` James Almer @ 2022-02-22 21:37 ` Vignesh Venkatasubramanian 2022-02-22 21:38 ` Vignesh Venkatasubramanian 1 sibling, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-22 21:37 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 302 +++++++++++++++++++++++++++++++++++---- libavformat/movenc.h | 5 + 4 files changed, 284 insertions(+), 25 deletions(-) diff --git a/configure b/configure index 1535dc3c5b..87b380fe3a 100755 --- a/configure +++ b/configure @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..400c17afbd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1a746a67fd..9575f8cc6f 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); } @@ -2004,12 +2004,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"); @@ -2019,7 +2020,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); } @@ -2085,7 +2086,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) { @@ -2103,6 +2104,8 @@ static void find_compressor(char * compressor_name, int len, MOVTrack *track) av_strlcatf(compressor_name, len, " %d%c", track->par->height, interlaced ? 'i' : 'p'); av_strlcatf(compressor_name, len, "%d", rate * (interlaced + 1)); + } else if (track->par->codec_id == AV_CODEC_ID_AV1 && track->mode == MODE_AVIF) { + av_strlcatf(compressor_name, len, "libaom Encoder"); } } @@ -2123,6 +2126,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2239,7 +2244,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; @@ -2792,7 +2797,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2859,6 +2867,131 @@ 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); + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + int i; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, num_channels); /* num_channels */ + for (i = 0; i < num_channels; ++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 @@ -3056,7 +3189,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3104,7 +3237,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, @@ -3439,7 +3572,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3914,8 +4048,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); @@ -4245,10 +4386,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; @@ -4259,7 +4401,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); @@ -5002,6 +5144,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; @@ -5065,6 +5210,30 @@ 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, "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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5669,7 +5838,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) { @@ -5802,7 +5971,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } } else if (par->codec_id == AV_CODEC_ID_AV1) { - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { + if (trk->mode == MODE_AVIF) { + avio_write(pb, pkt->data, pkt->size); + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size, &offset); if (ret < 0) @@ -6230,6 +6401,10 @@ fail: } } + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = pkt->size; + } + return mov_write_single_packet(s, pkt); } } @@ -6569,11 +6744,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 || @@ -6797,12 +6976,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 */ @@ -7003,7 +7183,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); @@ -7291,6 +7471,54 @@ 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; + int i; + + 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); + for (i = 0; i < mov->nb_streams; i++) + mov->tracks[i].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); + + avio_write(pb, buf, buf_size); + ffio_free_dyn_buf(&mov->mdat_buf); + + // 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); + + mov->moov_written = 1; + mov->mdat_size = 0; + for (i = 0; i < mov->nb_streams; i++) { + mov->tracks[i].entry = 0; + mov->tracks[i].end_reliable = 0; + } + return 0; +} + #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, @@ -7373,6 +7601,12 @@ static const AVCodecTag codec_f4v_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; +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 }; + #if CONFIG_MOV_MUXER const AVOutputFormat ff_mov_muxer = { .name = "mov", @@ -7535,3 +7769,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_isobmff_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.473.g83b2b277ed-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-22 20:03 ` James Almer 2022-02-22 21:37 ` Vignesh Venkatasubramanian @ 2022-02-22 21:38 ` Vignesh Venkatasubramanian 2022-02-22 21:43 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-22 21:38 UTC (permalink / raw) To: FFmpeg development discussions and patches On Tue, Feb 22, 2022 at 12:03 PM James Almer <jamrial@gmail.com> wrote: > > > On 2/22/2022 3:40 PM, Vignesh Venkatasubramanian wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verfied to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- > > libavformat/movenc.h | 5 + > > 4 files changed, 283 insertions(+), 24 deletions(-) > > > > diff --git a/configure b/configure > > index 1535dc3c5b..87b380fe3a 100755 > > --- a/configure > > +++ b/configure > > @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > avi_demuxer_select="riffdec exif" > > avi_muxer_select="riffenc" > > +avif_muxer_select="mov_muxer" > > caf_demuxer_select="iso_media" > > caf_muxer_select="iso_media" > > dash_muxer_select="mp4_muxer" > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > index d066a7745b..400c17afbd 100644 > > --- a/libavformat/allformats.c > > +++ b/libavformat/allformats.c > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > extern const AVInputFormat ff_av1_demuxer; > > extern const AVInputFormat ff_avi_demuxer; > > extern const AVOutputFormat ff_avi_muxer; > > +extern const AVOutputFormat ff_avif_muxer; > > extern const AVInputFormat ff_avisynth_demuxer; > > extern const AVOutputFormat ff_avm2_muxer; > > extern const AVInputFormat ff_avr_demuxer; > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > index 1a746a67fd..05537d1e78 100644 > > --- a/libavformat/movenc.c > > +++ b/libavformat/movenc.c > > @@ -1303,7 +1303,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); > > } > > > > @@ -2004,12 +2004,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"); > > @@ -2019,7 +2020,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); > > } > > @@ -2103,6 +2104,8 @@ static void find_compressor(char * > compressor_name, int len, MOVTrack *track) > > av_strlcatf(compressor_name, len, " %d%c", track->par->height, > interlaced ? 'i' : 'p'); > > > > av_strlcatf(compressor_name, len, "%d", rate * (interlaced + > 1)); > > + } else if (track->par->codec_id == AV_CODEC_ID_AV1 && track->mode > == MODE_AVIF) { > > + av_strlcatf(compressor_name, len, "libaom Encoder"); > libaom is not the only AV1 encoder supported by libavcodec, a > libavformat user could even not use a libavcodec based encoder to begin > with, and then there's also the codec copy scenario where no encoder is > used at all. > > This should probably use the same path as MODE_MOV above if there's a > key "encoder" in track->st->metadata, and write it. > Done. > _______________________________________________ > 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". > -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-22 21:38 ` Vignesh Venkatasubramanian @ 2022-02-22 21:43 ` Vignesh Venkatasubramanian 2022-02-24 17:34 ` Vignesh Venkatasubramanian 2022-03-03 15:36 ` James Almer 0 siblings, 2 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-22 21:43 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- libavformat/movenc.h | 5 + 4 files changed, 282 insertions(+), 25 deletions(-) diff --git a/configure b/configure index 1535dc3c5b..87b380fe3a 100755 --- a/configure +++ b/configure @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..400c17afbd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1a746a67fd..53258f0d11 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); } @@ -2004,12 +2004,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"); @@ -2019,7 +2020,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); } @@ -2085,7 +2086,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) { @@ -2123,6 +2124,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2239,7 +2242,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; @@ -2792,7 +2795,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2859,6 +2865,131 @@ 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); + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + int i; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, num_channels); /* num_channels */ + for (i = 0; i < num_channels; ++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 @@ -3056,7 +3187,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3104,7 +3235,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, @@ -3439,7 +3570,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3914,8 +4046,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); @@ -4245,10 +4384,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; @@ -4259,7 +4399,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); @@ -5002,6 +5142,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; @@ -5065,6 +5208,30 @@ 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, "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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5669,7 +5836,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) { @@ -5802,7 +5969,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } } else if (par->codec_id == AV_CODEC_ID_AV1) { - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { + if (trk->mode == MODE_AVIF) { + avio_write(pb, pkt->data, pkt->size); + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size, &offset); if (ret < 0) @@ -6230,6 +6399,10 @@ fail: } } + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = pkt->size; + } + return mov_write_single_packet(s, pkt); } } @@ -6569,11 +6742,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 || @@ -6797,12 +6974,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 */ @@ -7003,7 +7181,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); @@ -7291,6 +7469,54 @@ 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; + int i; + + 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); + for (i = 0; i < mov->nb_streams; i++) + mov->tracks[i].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); + + avio_write(pb, buf, buf_size); + ffio_free_dyn_buf(&mov->mdat_buf); + + // 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); + + mov->moov_written = 1; + mov->mdat_size = 0; + for (i = 0; i < mov->nb_streams; i++) { + mov->tracks[i].entry = 0; + mov->tracks[i].end_reliable = 0; + } + return 0; +} + #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, @@ -7373,6 +7599,12 @@ static const AVCodecTag codec_f4v_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; +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 }; + #if CONFIG_MOV_MUXER const AVOutputFormat ff_mov_muxer = { .name = "mov", @@ -7535,3 +7767,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_isobmff_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.473.g83b2b277ed-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-22 21:43 ` Vignesh Venkatasubramanian @ 2022-02-24 17:34 ` Vignesh Venkatasubramanian 2022-03-01 16:49 ` Vignesh Venkatasubramanian 2022-03-03 15:36 ` James Almer 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-24 17:34 UTC (permalink / raw) To: FFmpeg development discussions and patches On Tue, Feb 22, 2022 at 1:43 PM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- > libavformat/movenc.h | 5 + > 4 files changed, 282 insertions(+), 25 deletions(-) > > diff --git a/configure b/configure > index 1535dc3c5b..87b380fe3a 100755 > --- a/configure > +++ b/configure > @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index d066a7745b..400c17afbd 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 1a746a67fd..53258f0d11 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1303,7 +1303,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); > } > > @@ -2004,12 +2004,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"); > @@ -2019,7 +2020,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); > } > @@ -2085,7 +2086,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) { > @@ -2123,6 +2124,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > avio_wb32(pb, 0); /* size */ > if (mov->encryption_scheme != MOV_ENC_NONE) { > ffio_wfourcc(pb, "encv"); > + } else if (track->mode == MODE_AVIF) { > + ffio_wfourcc(pb, "av01"); > } else { > avio_wl32(pb, track->tag); // store it byteswapped > } > @@ -2239,7 +2242,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; > @@ -2792,7 +2795,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "ffmpeg"; > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > hdlr_type = "vide"; > descr = "VideoHandler"; > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > @@ -2859,6 +2865,131 @@ 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); > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > + int i; > + avio_wb32(pb, 0); /* size */ > + ffio_wfourcc(pb, "pixi"); > + avio_wb32(pb, 0); /* Version & flags */ > + avio_w8(pb, num_channels); /* num_channels */ > + for (i = 0; i < num_channels; ++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 > @@ -3056,7 +3187,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > display_matrix = NULL; > } > > - if (track->flags & MOV_TRACK_ENABLED) > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > flags |= MOV_TKHD_FLAG_ENABLED; > > if (track->mode == MODE_ISM) > @@ -3104,7 +3235,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, > @@ -3439,7 +3570,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > mov_write_tapt_tag(pb, track); > } > } > - mov_write_track_udta_tag(pb, mov, st); > + if (track->mode != MODE_AVIF) > + mov_write_track_udta_tag(pb, mov, st); > track->entry = entry_backup; > track->chunkCount = chunk_backup; > return update_size(pb, pos); > @@ -3914,8 +4046,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); > @@ -4245,10 +4384,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; > @@ -4259,7 +4399,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); > @@ -5002,6 +5142,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; > @@ -5065,6 +5208,30 @@ 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, "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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > + // 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 > @@ -5669,7 +5836,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) { > @@ -5802,7 +5969,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > } > } > } else if (par->codec_id == AV_CODEC_ID_AV1) { > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > + if (trk->mode == MODE_AVIF) { > + avio_write(pb, pkt->data, pkt->size); > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > &size, &offset); > if (ret < 0) > @@ -6230,6 +6399,10 @@ fail: > } > } > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > + mov->avif_extent_length = pkt->size; > + } > + > return mov_write_single_packet(s, pkt); > } > } > @@ -6569,11 +6742,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 || > @@ -6797,12 +6974,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 */ > @@ -7003,7 +7181,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); > @@ -7291,6 +7469,54 @@ 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; > + int i; > + > + 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); > + for (i = 0; i < mov->nb_streams; i++) > + mov->tracks[i].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); > + > + avio_write(pb, buf, buf_size); > + ffio_free_dyn_buf(&mov->mdat_buf); > + > + // 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); > + > + mov->moov_written = 1; > + mov->mdat_size = 0; > + for (i = 0; i < mov->nb_streams; i++) { > + mov->tracks[i].entry = 0; > + mov->tracks[i].end_reliable = 0; > + } > + return 0; > +} > + > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > static const AVCodecTag codec_3gp_tags[] = { > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > @@ -7373,6 +7599,12 @@ static const AVCodecTag codec_f4v_tags[] = { > { AV_CODEC_ID_NONE, 0 }, > }; > > +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 }; > + > #if CONFIG_MOV_MUXER > const AVOutputFormat ff_mov_muxer = { > .name = "mov", > @@ -7535,3 +7767,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_isobmff_muxer_class, > +}; > +#endif > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index 2ac84ed070..55b8469f68 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -242,6 +243,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) > -- > 2.35.1.473.g83b2b277ed-goog > If there are no further comments, can this series of patches be merged please? :) -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-24 17:34 ` Vignesh Venkatasubramanian @ 2022-03-01 16:49 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-01 16:49 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, Feb 24, 2022 at 9:34 AM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > On Tue, Feb 22, 2022 at 1:43 PM Vignesh Venkatasubramanian > <vigneshv@google.com> wrote: > > > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verfied to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- > > libavformat/movenc.h | 5 + > > 4 files changed, 282 insertions(+), 25 deletions(-) > > > > diff --git a/configure b/configure > > index 1535dc3c5b..87b380fe3a 100755 > > --- a/configure > > +++ b/configure > > @@ -3393,6 +3393,7 @@ asf_stream_muxer_select="asf_muxer" > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > avi_demuxer_select="riffdec exif" > > avi_muxer_select="riffenc" > > +avif_muxer_select="mov_muxer" > > caf_demuxer_select="iso_media" > > caf_muxer_select="iso_media" > > dash_muxer_select="mp4_muxer" > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > index d066a7745b..400c17afbd 100644 > > --- a/libavformat/allformats.c > > +++ b/libavformat/allformats.c > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > extern const AVInputFormat ff_av1_demuxer; > > extern const AVInputFormat ff_avi_demuxer; > > extern const AVOutputFormat ff_avi_muxer; > > +extern const AVOutputFormat ff_avif_muxer; > > extern const AVInputFormat ff_avisynth_demuxer; > > extern const AVOutputFormat ff_avm2_muxer; > > extern const AVInputFormat ff_avr_demuxer; > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > index 1a746a67fd..53258f0d11 100644 > > --- a/libavformat/movenc.c > > +++ b/libavformat/movenc.c > > @@ -1303,7 +1303,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); > > } > > > > @@ -2004,12 +2004,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"); > > @@ -2019,7 +2020,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); > > } > > @@ -2085,7 +2086,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) { > > @@ -2123,6 +2124,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > avio_wb32(pb, 0); /* size */ > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > ffio_wfourcc(pb, "encv"); > > + } else if (track->mode == MODE_AVIF) { > > + ffio_wfourcc(pb, "av01"); > > } else { > > avio_wl32(pb, track->tag); // store it byteswapped > > } > > @@ -2239,7 +2242,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; > > @@ -2792,7 +2795,10 @@ 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) { > > + if (track->mode == MODE_AVIF) { > > + hdlr_type = "pict"; > > + descr = "ffmpeg"; > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > hdlr_type = "vide"; > > descr = "VideoHandler"; > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > @@ -2859,6 +2865,131 @@ 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); > > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > + int i; > > + avio_wb32(pb, 0); /* size */ > > + ffio_wfourcc(pb, "pixi"); > > + avio_wb32(pb, 0); /* Version & flags */ > > + avio_w8(pb, num_channels); /* num_channels */ > > + for (i = 0; i < num_channels; ++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 > > @@ -3056,7 +3187,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > display_matrix = NULL; > > } > > > > - if (track->flags & MOV_TRACK_ENABLED) > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > flags |= MOV_TKHD_FLAG_ENABLED; > > > > if (track->mode == MODE_ISM) > > @@ -3104,7 +3235,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, > > @@ -3439,7 +3570,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > mov_write_tapt_tag(pb, track); > > } > > } > > - mov_write_track_udta_tag(pb, mov, st); > > + if (track->mode != MODE_AVIF) > > + mov_write_track_udta_tag(pb, mov, st); > > track->entry = entry_backup; > > track->chunkCount = chunk_backup; > > return update_size(pb, pos); > > @@ -3914,8 +4046,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); > > @@ -4245,10 +4384,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; > > @@ -4259,7 +4399,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); > > @@ -5002,6 +5142,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; > > @@ -5065,6 +5208,30 @@ 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, "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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > + // 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 > > @@ -5669,7 +5836,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) { > > @@ -5802,7 +5969,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > } > > } > > } else if (par->codec_id == AV_CODEC_ID_AV1) { > > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > + if (trk->mode == MODE_AVIF) { > > + avio_write(pb, pkt->data, pkt->size); > > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > &size, &offset); > > if (ret < 0) > > @@ -6230,6 +6399,10 @@ fail: > > } > > } > > > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > + mov->avif_extent_length = pkt->size; > > + } > > + > > return mov_write_single_packet(s, pkt); > > } > > } > > @@ -6569,11 +6742,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 || > > @@ -6797,12 +6974,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 */ > > @@ -7003,7 +7181,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); > > @@ -7291,6 +7469,54 @@ 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; > > + int i; > > + > > + 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); > > + for (i = 0; i < mov->nb_streams; i++) > > + mov->tracks[i].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); > > + > > + avio_write(pb, buf, buf_size); > > + ffio_free_dyn_buf(&mov->mdat_buf); > > + > > + // 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); > > + > > + mov->moov_written = 1; > > + mov->mdat_size = 0; > > + for (i = 0; i < mov->nb_streams; i++) { > > + mov->tracks[i].entry = 0; > > + mov->tracks[i].end_reliable = 0; > > + } > > + return 0; > > +} > > + > > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > > static const AVCodecTag codec_3gp_tags[] = { > > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > > @@ -7373,6 +7599,12 @@ static const AVCodecTag codec_f4v_tags[] = { > > { AV_CODEC_ID_NONE, 0 }, > > }; > > > > +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 }; > > + > > #if CONFIG_MOV_MUXER > > const AVOutputFormat ff_mov_muxer = { > > .name = "mov", > > @@ -7535,3 +7767,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_isobmff_muxer_class, > > +}; > > +#endif > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > index 2ac84ed070..55b8469f68 100644 > > --- a/libavformat/movenc.h > > +++ b/libavformat/movenc.h > > @@ -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; > > @@ -242,6 +243,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) > > -- > > 2.35.1.473.g83b2b277ed-goog > > > > If there are no further comments, can this series of patches be merged > please? :) > Ping on this please! > -- > Vignesh -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-02-22 21:43 ` Vignesh Venkatasubramanian 2022-02-24 17:34 ` Vignesh Venkatasubramanian @ 2022-03-03 15:36 ` James Almer 2022-03-03 19:16 ` Vignesh Venkatasubramanian 2022-03-03 19:20 ` Vignesh Venkatasubramanian 1 sibling, 2 replies; 71+ messages in thread From: James Almer @ 2022-03-03 15:36 UTC (permalink / raw) To: ffmpeg-devel On 2/22/2022 6:43 PM, Vignesh Venkatasubramanian wrote: > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- > libavformat/movenc.h | 5 + > 4 files changed, 282 insertions(+), 25 deletions(-) With a single frame i get no errors in that compliance tool, but when i encode an animated AVIF i get the following: [heif][Rule #12] Error: CodingConstraintsBox ('ccst') shall be present once [heif][Rule #28] Error: Wrong arity for boxes { ccst } in parents { avc1 avc2 avc3 avc4 hev1 hev2 hvc1 hvc2 av01 }: expected in range [1-1], found 0 [heif][Rule #31] Error: 'msf1' brand: this file shall conform to HEIF (section 7.2) [heif][Rule #31] Error: 'msf1' brand: 'iso8' shall be present among the compatible brands array [heif][Rule #32] Error: 'mif1' brand: this file shall conform to HEIF section 6, check the other errors for details [heif][Rule #33] Error: 'msf1' brand: this file shall conform to HEIF section 7, check the other errors for details All but one of these should be solved by writing a ccst box after the av1C box in the sample entry. The missing one should be solved by writing the iso8 compatible brand. The ccst box looks like it would need some bitstream information, so either you parse the packets to get it, or just hardcode sane defaults, considering it's used as a hint and it's not required for demuxing. _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-03 15:36 ` James Almer @ 2022-03-03 19:16 ` Vignesh Venkatasubramanian 2022-03-04 11:24 ` James Almer 2022-03-10 16:01 ` Andreas Rheinhardt 2022-03-03 19:20 ` Vignesh Venkatasubramanian 1 sibling, 2 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-03 19:16 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 305 insertions(+), 25 deletions(-) diff --git a/configure b/configure index 8c69ab0c86..6d7020e96b 100755 --- a/configure +++ b/configure @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..400c17afbd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1a746a67fd..504403ab0b 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); } @@ -2004,12 +2004,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"); @@ -2019,7 +2020,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); } @@ -2085,7 +2086,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) { @@ -2106,6 +2107,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; @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2239,7 +2261,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; @@ -2291,6 +2313,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); } @@ -2792,7 +2817,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2859,6 +2887,131 @@ 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); + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + int i; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, num_channels); /* num_channels */ + for (i = 0; i < num_channels; ++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 @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3104,7 +3257,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, @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3914,8 +4068,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); @@ -4245,10 +4406,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; @@ -4259,7 +4421,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); @@ -5002,6 +5164,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; @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5669,7 +5859,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) { @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } } else if (par->codec_id == AV_CODEC_ID_AV1) { - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { + if (trk->mode == MODE_AVIF) { + avio_write(pb, pkt->data, pkt->size); + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size, &offset); if (ret < 0) @@ -6230,6 +6422,10 @@ fail: } } + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = pkt->size; + } + return mov_write_single_packet(s, pkt); } } @@ -6569,11 +6765,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 || @@ -6797,12 +6997,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 */ @@ -7003,7 +7204,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); @@ -7291,6 +7492,54 @@ 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; + int i; + + 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); + for (i = 0; i < mov->nb_streams; i++) + mov->tracks[i].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); + + avio_write(pb, buf, buf_size); + ffio_free_dyn_buf(&mov->mdat_buf); + + // 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); + + mov->moov_written = 1; + mov->mdat_size = 0; + for (i = 0; i < mov->nb_streams; i++) { + mov->tracks[i].entry = 0; + mov->tracks[i].end_reliable = 0; + } + return 0; +} + #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; +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 }; + #if CONFIG_MOV_MUXER const AVOutputFormat ff_mov_muxer = { .name = "mov", @@ -7535,3 +7790,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_isobmff_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.616.g0bdcbb4464-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-03 19:16 ` Vignesh Venkatasubramanian @ 2022-03-04 11:24 ` James Almer 2022-03-04 17:52 ` Vignesh Venkatasubramanian 2022-03-04 17:54 ` Vignesh Venkatasubramanian 2022-03-10 16:01 ` Andreas Rheinhardt 1 sibling, 2 replies; 71+ messages in thread From: James Almer @ 2022-03-04 11:24 UTC (permalink / raw) To: ffmpeg-devel On 3/3/2022 4:16 PM, Vignesh Venkatasubramanian wrote: > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 305 insertions(+), 25 deletions(-) > > diff --git a/configure b/configure > index 8c69ab0c86..6d7020e96b 100755 > --- a/configure > +++ b/configure > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index d066a7745b..400c17afbd 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 1a746a67fd..504403ab0b 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1303,7 +1303,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); > } > > @@ -2004,12 +2004,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"); > @@ -2019,7 +2020,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); > } > @@ -2085,7 +2086,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) { > @@ -2106,6 +2107,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; > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > avio_wb32(pb, 0); /* size */ > if (mov->encryption_scheme != MOV_ENC_NONE) { > ffio_wfourcc(pb, "encv"); > + } else if (track->mode == MODE_AVIF) { > + ffio_wfourcc(pb, "av01"); > } else { > avio_wl32(pb, track->tag); // store it byteswapped > } > @@ -2239,7 +2261,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; > @@ -2291,6 +2313,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); > } > > @@ -2792,7 +2817,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "ffmpeg"; > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > hdlr_type = "vide"; > descr = "VideoHandler"; > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > @@ -2859,6 +2887,131 @@ 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); > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > + int i; > + avio_wb32(pb, 0); /* size */ > + ffio_wfourcc(pb, "pixi"); > + avio_wb32(pb, 0); /* Version & flags */ > + avio_w8(pb, num_channels); /* num_channels */ > + for (i = 0; i < num_channels; ++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 > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > display_matrix = NULL; > } > > - if (track->flags & MOV_TRACK_ENABLED) > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > flags |= MOV_TKHD_FLAG_ENABLED; > > if (track->mode == MODE_ISM) > @@ -3104,7 +3257,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, > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > mov_write_tapt_tag(pb, track); > } > } > - mov_write_track_udta_tag(pb, mov, st); > + if (track->mode != MODE_AVIF) > + mov_write_track_udta_tag(pb, mov, st); > track->entry = entry_backup; > track->chunkCount = chunk_backup; > return update_size(pb, pos); > @@ -3914,8 +4068,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); > @@ -4245,10 +4406,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; > @@ -4259,7 +4421,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); > @@ -5002,6 +5164,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; > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > + // 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 > @@ -5669,7 +5859,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) { > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > } > } > } else if (par->codec_id == AV_CODEC_ID_AV1) { > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > + if (trk->mode == MODE_AVIF) { > + avio_write(pb, pkt->data, pkt->size); > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > &size, &offset); > if (ret < 0) > @@ -6230,6 +6422,10 @@ fail: > } > } > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > + mov->avif_extent_length = pkt->size; > + } > + > return mov_write_single_packet(s, pkt); > } > } > @@ -6569,11 +6765,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 || > @@ -6797,12 +6997,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 */ > @@ -7003,7 +7204,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); > @@ -7291,6 +7492,54 @@ 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; > + int i; > + > + 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); > + for (i = 0; i < mov->nb_streams; i++) > + mov->tracks[i].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); > + > + avio_write(pb, buf, buf_size); > + ffio_free_dyn_buf(&mov->mdat_buf); > + > + // write extent offset. > + pos_backup = avio_tell(pb); > + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); You can't seek unless the output is seekable. Try to write the meta tag into a dynamic buffer (which is always seekable), like it's done for the mdat one above, then write it to the output pb when it's complete. But if that's not possible, then you need to ensure mov_init() aborts when !(s->pb->seekable & AVIO_SEEKABLE_NORMAL) && track->mode == MODE_AVIF (updating the existing check). > + avio_wb32(pb, mdat_pos); /* rewrite offset */ > + avio_seek(pb, pos_backup, SEEK_SET); > + > + mov->moov_written = 1; > + mov->mdat_size = 0; > + for (i = 0; i < mov->nb_streams; i++) { > + mov->tracks[i].entry = 0; > + mov->tracks[i].end_reliable = 0; > + } > + return 0; > +} > + > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > static const AVCodecTag codec_3gp_tags[] = { > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { > { AV_CODEC_ID_NONE, 0 }, > }; > > +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 }; > + > #if CONFIG_MOV_MUXER > const AVOutputFormat ff_mov_muxer = { > .name = "mov", > @@ -7535,3 +7790,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_isobmff_muxer_class, > +}; > +#endif > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index 2ac84ed070..55b8469f68 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -242,6 +243,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) _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-04 11:24 ` James Almer @ 2022-03-04 17:52 ` Vignesh Venkatasubramanian 2022-03-04 17:54 ` Vignesh Venkatasubramanian 1 sibling, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-04 17:52 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 326 +++++++++++++++++++++++++++++++++++---- libavformat/movenc.h | 5 + 4 files changed, 307 insertions(+), 26 deletions(-) diff --git a/configure b/configure index 8c69ab0c86..6d7020e96b 100755 --- a/configure +++ b/configure @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..400c17afbd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1a746a67fd..8f618c22bb 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); } @@ -2004,12 +2004,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"); @@ -2019,7 +2020,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); } @@ -2085,7 +2086,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) { @@ -2106,6 +2107,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; @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2239,7 +2261,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; @@ -2291,6 +2313,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); } @@ -2792,7 +2817,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2859,6 +2887,131 @@ 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); + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + int i; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, num_channels); /* num_channels */ + for (i = 0; i < num_channels; ++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 @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3104,7 +3257,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, @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3914,8 +4068,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); @@ -4245,10 +4406,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; @@ -4259,7 +4421,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); @@ -5002,6 +5164,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; @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5669,7 +5859,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) { @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } } else if (par->codec_id == AV_CODEC_ID_AV1) { - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { + if (trk->mode == MODE_AVIF) { + avio_write(pb, pkt->data, pkt->size); + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size, &offset); if (ret < 0) @@ -6230,6 +6422,10 @@ fail: } } + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = pkt->size; + } + return mov_write_single_packet(s, pkt); } } @@ -6569,11 +6765,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 || @@ -6654,7 +6854,8 @@ 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); } @@ -6797,12 +6998,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 */ @@ -7003,7 +7205,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); @@ -7291,6 +7493,54 @@ 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; + int i; + + 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); + for (i = 0; i < mov->nb_streams; i++) + mov->tracks[i].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); + + avio_write(pb, buf, buf_size); + ffio_free_dyn_buf(&mov->mdat_buf); + + // 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); + + mov->moov_written = 1; + mov->mdat_size = 0; + for (i = 0; i < mov->nb_streams; i++) { + mov->tracks[i].entry = 0; + mov->tracks[i].end_reliable = 0; + } + return 0; +} + #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER static const AVCodecTag codec_3gp_tags[] = { { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, @@ -7373,6 +7623,12 @@ static const AVCodecTag codec_f4v_tags[] = { { AV_CODEC_ID_NONE, 0 }, }; +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 }; + #if CONFIG_MOV_MUXER const AVOutputFormat ff_mov_muxer = { .name = "mov", @@ -7535,3 +7791,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_isobmff_muxer_class, +}; +#endif diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.616.g0bdcbb4464-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-04 11:24 ` James Almer 2022-03-04 17:52 ` Vignesh Venkatasubramanian @ 2022-03-04 17:54 ` Vignesh Venkatasubramanian 2022-03-09 19:34 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-04 17:54 UTC (permalink / raw) To: FFmpeg development discussions and patches On Fri, Mar 4, 2022 at 3:24 AM James Almer <jamrial@gmail.com> wrote: > > On 3/3/2022 4:16 PM, Vignesh Venkatasubramanian wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verfied to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- > > libavformat/movenc.h | 5 + > > 4 files changed, 305 insertions(+), 25 deletions(-) > > > > diff --git a/configure b/configure > > index 8c69ab0c86..6d7020e96b 100755 > > --- a/configure > > +++ b/configure > > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > avi_demuxer_select="riffdec exif" > > avi_muxer_select="riffenc" > > +avif_muxer_select="mov_muxer" > > caf_demuxer_select="iso_media" > > caf_muxer_select="iso_media" > > dash_muxer_select="mp4_muxer" > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > index d066a7745b..400c17afbd 100644 > > --- a/libavformat/allformats.c > > +++ b/libavformat/allformats.c > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > extern const AVInputFormat ff_av1_demuxer; > > extern const AVInputFormat ff_avi_demuxer; > > extern const AVOutputFormat ff_avi_muxer; > > +extern const AVOutputFormat ff_avif_muxer; > > extern const AVInputFormat ff_avisynth_demuxer; > > extern const AVOutputFormat ff_avm2_muxer; > > extern const AVInputFormat ff_avr_demuxer; > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > index 1a746a67fd..504403ab0b 100644 > > --- a/libavformat/movenc.c > > +++ b/libavformat/movenc.c > > @@ -1303,7 +1303,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); > > } > > > > @@ -2004,12 +2004,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"); > > @@ -2019,7 +2020,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); > > } > > @@ -2085,7 +2086,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) { > > @@ -2106,6 +2107,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; > > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > avio_wb32(pb, 0); /* size */ > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > ffio_wfourcc(pb, "encv"); > > + } else if (track->mode == MODE_AVIF) { > > + ffio_wfourcc(pb, "av01"); > > } else { > > avio_wl32(pb, track->tag); // store it byteswapped > > } > > @@ -2239,7 +2261,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; > > @@ -2291,6 +2313,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); > > } > > > > @@ -2792,7 +2817,10 @@ 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) { > > + if (track->mode == MODE_AVIF) { > > + hdlr_type = "pict"; > > + descr = "ffmpeg"; > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > hdlr_type = "vide"; > > descr = "VideoHandler"; > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > @@ -2859,6 +2887,131 @@ 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); > > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > + int i; > > + avio_wb32(pb, 0); /* size */ > > + ffio_wfourcc(pb, "pixi"); > > + avio_wb32(pb, 0); /* Version & flags */ > > + avio_w8(pb, num_channels); /* num_channels */ > > + for (i = 0; i < num_channels; ++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 > > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > display_matrix = NULL; > > } > > > > - if (track->flags & MOV_TRACK_ENABLED) > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > flags |= MOV_TKHD_FLAG_ENABLED; > > > > if (track->mode == MODE_ISM) > > @@ -3104,7 +3257,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, > > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > mov_write_tapt_tag(pb, track); > > } > > } > > - mov_write_track_udta_tag(pb, mov, st); > > + if (track->mode != MODE_AVIF) > > + mov_write_track_udta_tag(pb, mov, st); > > track->entry = entry_backup; > > track->chunkCount = chunk_backup; > > return update_size(pb, pos); > > @@ -3914,8 +4068,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); > > @@ -4245,10 +4406,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; > > @@ -4259,7 +4421,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); > > @@ -5002,6 +5164,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; > > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > + // 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 > > @@ -5669,7 +5859,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) { > > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > } > > } > > } else if (par->codec_id == AV_CODEC_ID_AV1) { > > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > + if (trk->mode == MODE_AVIF) { > > + avio_write(pb, pkt->data, pkt->size); > > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > &size, &offset); > > if (ret < 0) > > @@ -6230,6 +6422,10 @@ fail: > > } > > } > > > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > + mov->avif_extent_length = pkt->size; > > + } > > + > > return mov_write_single_packet(s, pkt); > > } > > } > > @@ -6569,11 +6765,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 || > > @@ -6797,12 +6997,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 */ > > @@ -7003,7 +7204,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); > > @@ -7291,6 +7492,54 @@ 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; > > + int i; > > + > > + 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); > > + for (i = 0; i < mov->nb_streams; i++) > > + mov->tracks[i].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); > > + > > + avio_write(pb, buf, buf_size); > > + ffio_free_dyn_buf(&mov->mdat_buf); > > + > > + // write extent offset. > > + pos_backup = avio_tell(pb); > > + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); > > You can't seek unless the output is seekable. > > Try to write the meta tag into a dynamic buffer (which is always > seekable), like it's done for the mdat one above, then write it to the > output pb when it's complete. But if that's not possible, then you need > to ensure mov_init() aborts when !(s->pb->seekable & > AVIO_SEEKABLE_NORMAL) && track->mode == MODE_AVIF (updating the existing > check). > Done. I added the check to abort on mov_init when output is not seekable. It is easier to do this since all the mov_write* calls require seeking for updating the size. Verified it bails out correctly by trying to write output to stdout (which is not seekable). > > + avio_wb32(pb, mdat_pos); /* rewrite offset */ > > + avio_seek(pb, pos_backup, SEEK_SET); > > + > > + mov->moov_written = 1; > > + mov->mdat_size = 0; > > + for (i = 0; i < mov->nb_streams; i++) { > > + mov->tracks[i].entry = 0; > > + mov->tracks[i].end_reliable = 0; > > + } > > + return 0; > > +} > > + > > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > > static const AVCodecTag codec_3gp_tags[] = { > > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > > @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { > > { AV_CODEC_ID_NONE, 0 }, > > }; > > > > +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 }; > > + > > #if CONFIG_MOV_MUXER > > const AVOutputFormat ff_mov_muxer = { > > .name = "mov", > > @@ -7535,3 +7790,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_isobmff_muxer_class, > > +}; > > +#endif > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > index 2ac84ed070..55b8469f68 100644 > > --- a/libavformat/movenc.h > > +++ b/libavformat/movenc.h > > @@ -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; > > @@ -242,6 +243,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) > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-04 17:54 ` Vignesh Venkatasubramanian @ 2022-03-09 19:34 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-09 19:34 UTC (permalink / raw) To: FFmpeg development discussions and patches On Fri, Mar 4, 2022 at 9:54 AM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > On Fri, Mar 4, 2022 at 3:24 AM James Almer <jamrial@gmail.com> wrote: > > > > On 3/3/2022 4:16 PM, Vignesh Venkatasubramanian wrote: > > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > > > Sample usage for still image: > > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > > > Sample usage for animated AVIF image: > > > ffmpeg -i video.mp4 animated.avif > > > > > > We can re-use any of the AV1 encoding options that will make > > > sense for image encoding (like bitrate, tiles, encoding speed, > > > etc). > > > > > > The files generated by this muxer has been verified to be valid > > > AVIF files by the following: > > > 1) Displays on Chrome (both still and animated images). > > > 2) Displays on Firefox (only still images, firefox does not support > > > animated AVIF yet). > > > 3) Verfied to be valid by Compliance Warden: > > > https://github.com/gpac/ComplianceWarden > > > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > > --- > > > configure | 1 + > > > libavformat/allformats.c | 1 + > > > libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- > > > libavformat/movenc.h | 5 + > > > 4 files changed, 305 insertions(+), 25 deletions(-) > > > > > > diff --git a/configure b/configure > > > index 8c69ab0c86..6d7020e96b 100755 > > > --- a/configure > > > +++ b/configure > > > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > > avi_demuxer_select="riffdec exif" > > > avi_muxer_select="riffenc" > > > +avif_muxer_select="mov_muxer" > > > caf_demuxer_select="iso_media" > > > caf_muxer_select="iso_media" > > > dash_muxer_select="mp4_muxer" > > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > > index d066a7745b..400c17afbd 100644 > > > --- a/libavformat/allformats.c > > > +++ b/libavformat/allformats.c > > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > > extern const AVInputFormat ff_av1_demuxer; > > > extern const AVInputFormat ff_avi_demuxer; > > > extern const AVOutputFormat ff_avi_muxer; > > > +extern const AVOutputFormat ff_avif_muxer; > > > extern const AVInputFormat ff_avisynth_demuxer; > > > extern const AVOutputFormat ff_avm2_muxer; > > > extern const AVInputFormat ff_avr_demuxer; > > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > > index 1a746a67fd..504403ab0b 100644 > > > --- a/libavformat/movenc.c > > > +++ b/libavformat/movenc.c > > > @@ -1303,7 +1303,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); > > > } > > > > > > @@ -2004,12 +2004,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"); > > > @@ -2019,7 +2020,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); > > > } > > > @@ -2085,7 +2086,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) { > > > @@ -2106,6 +2107,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; > > > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > > avio_wb32(pb, 0); /* size */ > > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > > ffio_wfourcc(pb, "encv"); > > > + } else if (track->mode == MODE_AVIF) { > > > + ffio_wfourcc(pb, "av01"); > > > } else { > > > avio_wl32(pb, track->tag); // store it byteswapped > > > } > > > @@ -2239,7 +2261,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; > > > @@ -2291,6 +2313,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); > > > } > > > > > > @@ -2792,7 +2817,10 @@ 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) { > > > + if (track->mode == MODE_AVIF) { > > > + hdlr_type = "pict"; > > > + descr = "ffmpeg"; > > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > > hdlr_type = "vide"; > > > descr = "VideoHandler"; > > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > > @@ -2859,6 +2887,131 @@ 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); > > > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > > + int i; > > > + avio_wb32(pb, 0); /* size */ > > > + ffio_wfourcc(pb, "pixi"); > > > + avio_wb32(pb, 0); /* Version & flags */ > > > + avio_w8(pb, num_channels); /* num_channels */ > > > + for (i = 0; i < num_channels; ++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 > > > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > > display_matrix = NULL; > > > } > > > > > > - if (track->flags & MOV_TRACK_ENABLED) > > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > > flags |= MOV_TKHD_FLAG_ENABLED; > > > > > > if (track->mode == MODE_ISM) > > > @@ -3104,7 +3257,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, > > > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > > mov_write_tapt_tag(pb, track); > > > } > > > } > > > - mov_write_track_udta_tag(pb, mov, st); > > > + if (track->mode != MODE_AVIF) > > > + mov_write_track_udta_tag(pb, mov, st); > > > track->entry = entry_backup; > > > track->chunkCount = chunk_backup; > > > return update_size(pb, pos); > > > @@ -3914,8 +4068,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); > > > @@ -4245,10 +4406,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; > > > @@ -4259,7 +4421,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); > > > @@ -5002,6 +5164,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; > > > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > > + // 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 > > > @@ -5669,7 +5859,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) { > > > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > > } > > > } > > > } else if (par->codec_id == AV_CODEC_ID_AV1) { > > > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > + if (trk->mode == MODE_AVIF) { > > > + avio_write(pb, pkt->data, pkt->size); > > > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > > &size, &offset); > > > if (ret < 0) > > > @@ -6230,6 +6422,10 @@ fail: > > > } > > > } > > > > > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > > + mov->avif_extent_length = pkt->size; > > > + } > > > + > > > return mov_write_single_packet(s, pkt); > > > } > > > } > > > @@ -6569,11 +6765,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 || > > > @@ -6797,12 +6997,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 */ > > > @@ -7003,7 +7204,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); > > > @@ -7291,6 +7492,54 @@ 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; > > > + int i; > > > + > > > + 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); > > > + for (i = 0; i < mov->nb_streams; i++) > > > + mov->tracks[i].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); > > > + > > > + avio_write(pb, buf, buf_size); > > > + ffio_free_dyn_buf(&mov->mdat_buf); > > > + > > > + // write extent offset. > > > + pos_backup = avio_tell(pb); > > > + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); > > > > You can't seek unless the output is seekable. > > > > Try to write the meta tag into a dynamic buffer (which is always > > seekable), like it's done for the mdat one above, then write it to the > > output pb when it's complete. But if that's not possible, then you need > > to ensure mov_init() aborts when !(s->pb->seekable & > > AVIO_SEEKABLE_NORMAL) && track->mode == MODE_AVIF (updating the existing > > check). > > > > Done. I added the check to abort on mov_init when output is not > seekable. It is easier to do this since all the mov_write* calls > require seeking for updating the size. > > Verified it bails out correctly by trying to write output to stdout > (which is not seekable). > If there are no further comments, can this be merged please? :) > > > + avio_wb32(pb, mdat_pos); /* rewrite offset */ > > > + avio_seek(pb, pos_backup, SEEK_SET); > > > + > > > + mov->moov_written = 1; > > > + mov->mdat_size = 0; > > > + for (i = 0; i < mov->nb_streams; i++) { > > > + mov->tracks[i].entry = 0; > > > + mov->tracks[i].end_reliable = 0; > > > + } > > > + return 0; > > > +} > > > + > > > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > > > static const AVCodecTag codec_3gp_tags[] = { > > > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > > > @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { > > > { AV_CODEC_ID_NONE, 0 }, > > > }; > > > > > > +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 }; > > > + > > > #if CONFIG_MOV_MUXER > > > const AVOutputFormat ff_mov_muxer = { > > > .name = "mov", > > > @@ -7535,3 +7790,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_isobmff_muxer_class, > > > +}; > > > +#endif > > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > > index 2ac84ed070..55b8469f68 100644 > > > --- a/libavformat/movenc.h > > > +++ b/libavformat/movenc.h > > > @@ -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; > > > @@ -242,6 +243,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) > > _______________________________________________ > > 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". > > > > -- > Vignesh -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-03 19:16 ` Vignesh Venkatasubramanian 2022-03-04 11:24 ` James Almer @ 2022-03-10 16:01 ` Andreas Rheinhardt 2022-03-10 18:12 ` Vignesh Venkatasubramanian 2022-03-10 18:14 ` Vignesh Venkatasubramanian 1 sibling, 2 replies; 71+ messages in thread From: Andreas Rheinhardt @ 2022-03-10 16:01 UTC (permalink / raw) To: ffmpeg-devel Vignesh Venkatasubramanian: > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 305 insertions(+), 25 deletions(-) > > diff --git a/configure b/configure > index 8c69ab0c86..6d7020e96b 100755 > --- a/configure > +++ b/configure > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index d066a7745b..400c17afbd 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 1a746a67fd..504403ab0b 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1303,7 +1303,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); > } > > @@ -2004,12 +2004,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"); > @@ -2019,7 +2020,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); > } > @@ -2085,7 +2086,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) { > @@ -2106,6 +2107,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; > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > avio_wb32(pb, 0); /* size */ > if (mov->encryption_scheme != MOV_ENC_NONE) { > ffio_wfourcc(pb, "encv"); > + } else if (track->mode == MODE_AVIF) { > + ffio_wfourcc(pb, "av01"); > } else { > avio_wl32(pb, track->tag); // store it byteswapped > } > @@ -2239,7 +2261,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; > @@ -2291,6 +2313,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); > } > > @@ -2792,7 +2817,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "ffmpeg"; > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > hdlr_type = "vide"; > descr = "VideoHandler"; > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > @@ -2859,6 +2887,131 @@ 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); > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > + int i; > + avio_wb32(pb, 0); /* size */ > + ffio_wfourcc(pb, "pixi"); > + avio_wb32(pb, 0); /* Version & flags */ > + avio_w8(pb, num_channels); /* num_channels */ > + for (i = 0; i < num_channels; ++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 > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > display_matrix = NULL; > } > > - if (track->flags & MOV_TRACK_ENABLED) > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > flags |= MOV_TKHD_FLAG_ENABLED; > > if (track->mode == MODE_ISM) > @@ -3104,7 +3257,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, > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > mov_write_tapt_tag(pb, track); > } > } > - mov_write_track_udta_tag(pb, mov, st); > + if (track->mode != MODE_AVIF) > + mov_write_track_udta_tag(pb, mov, st); > track->entry = entry_backup; > track->chunkCount = chunk_backup; > return update_size(pb, pos); > @@ -3914,8 +4068,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. Various parts of this patch seem to presume this (they always use the first stream), yet I fail to see what ensures this. > + 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); > @@ -4245,10 +4406,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; > @@ -4259,7 +4421,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); > @@ -5002,6 +5164,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; > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > + // 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 > @@ -5669,7 +5859,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) { > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > } > } > } else if (par->codec_id == AV_CODEC_ID_AV1) { > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > + if (trk->mode == MODE_AVIF) { > + avio_write(pb, pkt->data, pkt->size); > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > &size, &offset); > if (ret < 0) > @@ -6230,6 +6422,10 @@ fail: > } > } > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > + mov->avif_extent_length = pkt->size; > + } > + > return mov_write_single_packet(s, pkt); > } > } > @@ -6569,11 +6765,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 || > @@ -6797,12 +6997,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 */ > @@ -7003,7 +7204,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); > @@ -7291,6 +7492,54 @@ 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; > + int i; > + > + 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); > + for (i = 0; i < mov->nb_streams; i++) > + mov->tracks[i].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); > + > + avio_write(pb, buf, buf_size); > + ffio_free_dyn_buf(&mov->mdat_buf); > + > + // 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); > + > + mov->moov_written = 1; > + mov->mdat_size = 0; > + for (i = 0; i < mov->nb_streams; i++) { > + mov->tracks[i].entry = 0; > + mov->tracks[i].end_reliable = 0; > + } Why this? write_trailer is only called once. > + return 0; > +}> + > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > static const AVCodecTag codec_3gp_tags[] = { > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { > { AV_CODEC_ID_NONE, 0 }, > }; > > +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 }; Why is this not under #if CONFIG_AVIF_MUXER (or part of the other #if CONFIG_AVIF_MUXER below)? > + > #if CONFIG_MOV_MUXER > const AVOutputFormat ff_mov_muxer = { > .name = "mov", > @@ -7535,3 +7790,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_isobmff_muxer_class, This gives this muxer all the options of the other muxers; yet which one of these are actually supported? E.g. is faststart supported? The code for it is in mov_write_trailer() and that is not called. > +}; > +#endif > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index 2ac84ed070..55b8469f68 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -242,6 +243,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) _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-10 16:01 ` Andreas Rheinhardt @ 2022-03-10 18:12 ` Vignesh Venkatasubramanian 2022-03-21 20:46 ` Andreas Rheinhardt 2022-03-10 18:14 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-10 18:12 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 322 insertions(+), 26 deletions(-) diff --git a/configure b/configure index 8c69ab0c86..6d7020e96b 100755 --- a/configure +++ b/configure @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d066a7745b..400c17afbd 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1a746a67fd..ff41579300 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1303,7 +1303,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); } @@ -2004,12 +2004,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"); @@ -2019,7 +2020,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); } @@ -2085,7 +2086,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) { @@ -2106,6 +2107,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; @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2239,7 +2261,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; @@ -2291,6 +2313,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); } @@ -2792,7 +2817,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2859,6 +2887,131 @@ 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); + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); + int i; + avio_wb32(pb, 0); /* size */ + ffio_wfourcc(pb, "pixi"); + avio_wb32(pb, 0); /* Version & flags */ + avio_w8(pb, num_channels); /* num_channels */ + for (i = 0; i < num_channels; ++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 @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3104,7 +3257,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, @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3914,8 +4068,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); @@ -4245,10 +4406,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; @@ -4259,7 +4421,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); @@ -5002,6 +5164,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; @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5669,7 +5859,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) { @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } } } else if (par->codec_id == AV_CODEC_ID_AV1) { - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { + if (trk->mode == MODE_AVIF) { + avio_write(pb, pkt->data, pkt->size); + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, &size, &offset); if (ret < 0) @@ -6230,6 +6422,10 @@ fail: } } + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { + mov->avif_extent_length = pkt->size; + } + return mov_write_single_packet(s, pkt); } } @@ -6569,11 +6765,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 || @@ -6654,11 +6854,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); + } + } + + mov->nb_streams = s->nb_streams; if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) mov->chapter_track = mov->nb_streams++; @@ -6797,12 +7011,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 */ @@ -7003,7 +7218,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); @@ -7291,6 +7506,48 @@ 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; + int i; + + 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); + for (i = 0; i < mov->nb_streams; i++) + mov->tracks[i].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); + + avio_write(pb, buf, buf_size); + ffio_free_dyn_buf(&mov->mdat_buf); + + // 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') }, @@ -7373,6 +7630,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", @@ -7535,3 +7806,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.723.g4982287a31-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-10 18:12 ` Vignesh Venkatasubramanian @ 2022-03-21 20:46 ` Andreas Rheinhardt 2022-03-22 16:45 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Andreas Rheinhardt @ 2022-03-21 20:46 UTC (permalink / raw) To: ffmpeg-devel Vignesh Venkatasubramanian: > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 322 insertions(+), 26 deletions(-) > > diff --git a/configure b/configure > index 8c69ab0c86..6d7020e96b 100755 > --- a/configure > +++ b/configure > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index d066a7745b..400c17afbd 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 1a746a67fd..ff41579300 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1303,7 +1303,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); > } > > @@ -2004,12 +2004,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"); > @@ -2019,7 +2020,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); > } > @@ -2085,7 +2086,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) { > @@ -2106,6 +2107,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; > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > avio_wb32(pb, 0); /* size */ > if (mov->encryption_scheme != MOV_ENC_NONE) { > ffio_wfourcc(pb, "encv"); > + } else if (track->mode == MODE_AVIF) { > + ffio_wfourcc(pb, "av01"); > } else { > avio_wl32(pb, track->tag); // store it byteswapped > } > @@ -2239,7 +2261,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; > @@ -2291,6 +2313,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); > } > > @@ -2792,7 +2817,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "ffmpeg"; > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > hdlr_type = "vide"; > descr = "VideoHandler"; > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > @@ -2859,6 +2887,131 @@ 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); > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); Is the number of planes really the correct number here? After all, for a muxer (instead of a decoder) it does not matter whether the chroma planes are interleaved like in AV_PIX_FMT_NV12 or not like in AV_PIX_FMT_YUV420P. You should better use pixdesc->nb_components. > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > + int i; We allow and use "for (int i = 0;" from C99 to save lines like these. > + avio_wb32(pb, 0); /* size */ > + ffio_wfourcc(pb, "pixi"); > + avio_wb32(pb, 0); /* Version & flags */ > + avio_w8(pb, num_channels); /* num_channels */ > + for (i = 0; i < num_channels; ++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 > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > display_matrix = NULL; > } > > - if (track->flags & MOV_TRACK_ENABLED) > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > flags |= MOV_TKHD_FLAG_ENABLED; > > if (track->mode == MODE_ISM) > @@ -3104,7 +3257,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, > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > mov_write_tapt_tag(pb, track); > } > } > - mov_write_track_udta_tag(pb, mov, st); > + if (track->mode != MODE_AVIF) > + mov_write_track_udta_tag(pb, mov, st); > track->entry = entry_backup; > track->chunkCount = chunk_backup; > return update_size(pb, pos); > @@ -3914,8 +4068,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); > @@ -4245,10 +4406,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; > @@ -4259,7 +4421,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); > @@ -5002,6 +5164,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; > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > + // 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 > @@ -5669,7 +5859,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) { > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > } > } > } else if (par->codec_id == AV_CODEC_ID_AV1) { > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > + if (trk->mode == MODE_AVIF) { Why this? AVIF requires that AVI-in-ISOBMFF sample format is honoured and this contains e.g. "OBUs of type OBU_TEMPORAL_DELIMITER, OBU_PADDING, or OBU_REDUNDANT_FRAME_HEADER SHOULD NOT be used". ff_av1_filter_obus(_buf)? merely ensures that these OBUs are stripped away. If the aim of this check is to disallow hint tracks or so, then it fails (all it does is ensuring that both the ordinary track as well as the hint track get data that might contain OBUs that should not be there). > + avio_write(pb, pkt->data, pkt->size); > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > &size, &offset); > if (ret < 0) > @@ -6230,6 +6422,10 @@ fail: > } > } > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > + mov->avif_extent_length = pkt->size; > + } > + > return mov_write_single_packet(s, pkt); > } > } > @@ -6569,11 +6765,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 || > @@ -6654,11 +6854,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); > + } > + } > + > + > mov->nb_streams = s->nb_streams; > if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > mov->chapter_track = mov->nb_streams++; > @@ -6797,12 +7011,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 */ > @@ -7003,7 +7218,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); > @@ -7291,6 +7506,48 @@ 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; > + int i; > + > + 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); > + for (i = 0; i < mov->nb_streams; i++) > + mov->tracks[i].data_offset = avio_tell(pb) + moov_size + 8; Don't call avio_tell() in a loop (and I wonder whether this loop is even necessary given that s->nb_streams is checked to be one). > + > + 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); > + > + avio_write(pb, buf, buf_size); > + ffio_free_dyn_buf(&mov->mdat_buf); Unnecessary: This will be freed in mov_free(). > + > + // write extent offset. > + pos_backup = avio_tell(pb); > + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); > + avio_wb32(pb, mdat_pos); /* rewrite offset */ What guarantees that this fits into 32bits? > + 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') }, > @@ -7373,6 +7630,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, > +}; It's not mandatory for a muxer to have a private class; it is only necessary for options. If you do not have options (like here), then the AVClass is useless. I actually wanted that you support the options that make sense for AVIF and I dislike that the avif muxer uses a different write_trailer than every other muxer here (which is the reason why e.g. the faststart option is not supported by it). Is it really so different? Furthermore, given that you don't use the same AVClass as everyone else, the values for fields that are AVOpt-enabled are zero; and this does not always coincide with the default value of the relevant option. See e.g. skip_iods, iods_audio_profile, iods_video_profile etc. movie_timescale will even be set to a value that is outside of its legal range. I don't know whether this can lead to any divide-by-zero crashes or asserts, but it is certainly very fragile. > +#endif > + > #if CONFIG_MOV_MUXER > const AVOutputFormat ff_mov_muxer = { > .name = "mov", > @@ -7535,3 +7806,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 > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index 2ac84ed070..55b8469f68 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -242,6 +243,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) _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-21 20:46 ` Andreas Rheinhardt @ 2022-03-22 16:45 ` Vignesh Venkatasubramanian 2022-03-22 16:46 ` Vignesh Venkatasubramanian 2022-04-13 21:04 ` Andreas Rheinhardt 0 siblings, 2 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-22 16:45 UTC (permalink / raw) To: FFmpeg development discussions and patches On Mon, Mar 21, 2022 at 1:46 PM Andreas Rheinhardt <andreas.rheinhardt@outlook.com> wrote: > > Vignesh Venkatasubramanian: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verfied to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- > > libavformat/movenc.h | 5 + > > 4 files changed, 322 insertions(+), 26 deletions(-) > > > > diff --git a/configure b/configure > > index 8c69ab0c86..6d7020e96b 100755 > > --- a/configure > > +++ b/configure > > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > avi_demuxer_select="riffdec exif" > > avi_muxer_select="riffenc" > > +avif_muxer_select="mov_muxer" > > caf_demuxer_select="iso_media" > > caf_muxer_select="iso_media" > > dash_muxer_select="mp4_muxer" > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > index d066a7745b..400c17afbd 100644 > > --- a/libavformat/allformats.c > > +++ b/libavformat/allformats.c > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > extern const AVInputFormat ff_av1_demuxer; > > extern const AVInputFormat ff_avi_demuxer; > > extern const AVOutputFormat ff_avi_muxer; > > +extern const AVOutputFormat ff_avif_muxer; > > extern const AVInputFormat ff_avisynth_demuxer; > > extern const AVOutputFormat ff_avm2_muxer; > > extern const AVInputFormat ff_avr_demuxer; > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > index 1a746a67fd..ff41579300 100644 > > --- a/libavformat/movenc.c > > +++ b/libavformat/movenc.c > > @@ -1303,7 +1303,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); > > } > > > > @@ -2004,12 +2004,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"); > > @@ -2019,7 +2020,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); > > } > > @@ -2085,7 +2086,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) { > > @@ -2106,6 +2107,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; > > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > avio_wb32(pb, 0); /* size */ > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > ffio_wfourcc(pb, "encv"); > > + } else if (track->mode == MODE_AVIF) { > > + ffio_wfourcc(pb, "av01"); > > } else { > > avio_wl32(pb, track->tag); // store it byteswapped > > } > > @@ -2239,7 +2261,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; > > @@ -2291,6 +2313,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); > > } > > > > @@ -2792,7 +2817,10 @@ 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) { > > + if (track->mode == MODE_AVIF) { > > + hdlr_type = "pict"; > > + descr = "ffmpeg"; > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > hdlr_type = "vide"; > > descr = "VideoHandler"; > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > @@ -2859,6 +2887,131 @@ 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); > > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > Is the number of planes really the correct number here? After all, for a > muxer (instead of a decoder) it does not matter whether the chroma > planes are interleaved like in AV_PIX_FMT_NV12 or not like in > AV_PIX_FMT_YUV420P. You should better use pixdesc->nb_components. > Done. > > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > + int i; > > We allow and use "for (int i = 0;" from C99 to save lines like these. > Done. > > + avio_wb32(pb, 0); /* size */ > > + ffio_wfourcc(pb, "pixi"); > > + avio_wb32(pb, 0); /* Version & flags */ > > + avio_w8(pb, num_channels); /* num_channels */ > > + for (i = 0; i < num_channels; ++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 > > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > display_matrix = NULL; > > } > > > > - if (track->flags & MOV_TRACK_ENABLED) > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > flags |= MOV_TKHD_FLAG_ENABLED; > > > > if (track->mode == MODE_ISM) > > @@ -3104,7 +3257,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, > > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > mov_write_tapt_tag(pb, track); > > } > > } > > - mov_write_track_udta_tag(pb, mov, st); > > + if (track->mode != MODE_AVIF) > > + mov_write_track_udta_tag(pb, mov, st); > > track->entry = entry_backup; > > track->chunkCount = chunk_backup; > > return update_size(pb, pos); > > @@ -3914,8 +4068,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); > > @@ -4245,10 +4406,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; > > @@ -4259,7 +4421,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); > > @@ -5002,6 +5164,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; > > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > + // 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 > > @@ -5669,7 +5859,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) { > > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > } > > } > > } else if (par->codec_id == AV_CODEC_ID_AV1) { > > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > + if (trk->mode == MODE_AVIF) { > > Why this? AVIF requires that AVI-in-ISOBMFF sample format is honoured > and this contains e.g. "OBUs of type OBU_TEMPORAL_DELIMITER, > OBU_PADDING, or OBU_REDUNDANT_FRAME_HEADER SHOULD NOT be used". > ff_av1_filter_obus(_buf)? merely ensures that these OBUs are stripped away. > > If the aim of this check is to disallow hint tracks or so, then it fails > (all it does is ensuring that both the ordinary track as well as the > hint track get data that might contain OBUs that should not be there). > Done. I had to update the extent offset code here as well. > > + avio_write(pb, pkt->data, pkt->size); > > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > &size, &offset); > > if (ret < 0) > > @@ -6230,6 +6422,10 @@ fail: > > } > > } > > > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > + mov->avif_extent_length = pkt->size; > > + } > > + > > return mov_write_single_packet(s, pkt); > > } > > } > > @@ -6569,11 +6765,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 || > > @@ -6654,11 +6854,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); > > + } > > + } > > + > > + > > mov->nb_streams = s->nb_streams; > > if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > > mov->chapter_track = mov->nb_streams++; > > @@ -6797,12 +7011,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 */ > > @@ -7003,7 +7218,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); > > @@ -7291,6 +7506,48 @@ 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; > > + int i; > > + > > + 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); > > + for (i = 0; i < mov->nb_streams; i++) > > + mov->tracks[i].data_offset = avio_tell(pb) + moov_size + 8; > > Don't call avio_tell() in a loop (and I wonder whether this loop is even > necessary given that s->nb_streams is checked to be one). > Removed the loop. I was thinking about potentially supporting multiple output tracks in the future (for alpha channel for example), but this code would not work in that case any way. > > + > > + 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); > > + > > + avio_write(pb, buf, buf_size); > > + ffio_free_dyn_buf(&mov->mdat_buf); > > Unnecessary: This will be freed in mov_free(). > Removed. > > + > > + // write extent offset. > > + pos_backup = avio_tell(pb); > > + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); > > + avio_wb32(pb, mdat_pos); /* rewrite offset */ > > What guarantees that this fits into 32bits? > Added a check to fail if it does not fit in 32-bits. > > + 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') }, > > @@ -7373,6 +7630,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, > > +}; > > It's not mandatory for a muxer to have a private class; it is only > necessary for options. If you do not have options (like here), then the > AVClass is useless. > I actually wanted that you support the options that make sense for AVIF > and I dislike that the avif muxer uses a different write_trailer than > every other muxer here (which is the reason why e.g. the faststart > option is not supported by it). Is it really so different? > Furthermore, given that you don't use the same AVClass as everyone else, > the values for fields that are AVOpt-enabled are zero; and this does not > always coincide with the default value of the relevant option. See e.g. > skip_iods, iods_audio_profile, iods_video_profile etc. movie_timescale > will even be set to a value that is outside of its legal range. I don't > know whether this can lead to any divide-by-zero crashes or asserts, but > it is certainly very fragile. > Hmm, i see a couple of solutions here: 1) Use the same private class as the MOV muxer and document that not all options are supported when the format is AVIF. I think there are already cases within this file where not all muxers may support all the options. 2) Leave it as-is since the code always checks for AVIF mode first and then does the rest, so the defaults of the non-relevant options should not matter. But i agree that this is very fragile. One way to ensure future changes don't break this structure is to add a fate test. Please let me know what you think. About using a separate write_trailer function, i can re-use the existing mov_write_trailer but it will mostly be a special case for AVIF mode with the code in avif_write_trailer, so i thought it was cleaner to have a separate function anyway. I am not sure if there are any other implications because of this. If you prefer that i move it into mov_write_trailer, please let me know and i can do that as well. > > +#endif > > + > > #if CONFIG_MOV_MUXER > > const AVOutputFormat ff_mov_muxer = { > > .name = "mov", > > @@ -7535,3 +7806,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 > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > index 2ac84ed070..55b8469f68 100644 > > --- a/libavformat/movenc.h > > +++ b/libavformat/movenc.h > > @@ -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; > > @@ -242,6 +243,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) > > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-22 16:45 ` Vignesh Venkatasubramanian @ 2022-03-22 16:46 ` Vignesh Venkatasubramanian 2022-03-28 17:06 ` Vignesh Venkatasubramanian 2022-04-13 21:04 ` Andreas Rheinhardt 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-22 16:46 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 319 insertions(+), 25 deletions(-) diff --git a/configure b/configure index dff53e88bc..5aaf198704 100755 --- a/configure +++ b/configure @@ -3394,6 +3394,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 587ad59b3c..29e58353ee 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 1c31f48abb..b9b424bc97 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1306,7 +1306,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); } @@ -2007,12 +2007,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"); @@ -2022,7 +2023,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); } @@ -2088,7 +2089,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) { @@ -2109,6 +2110,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; @@ -2126,6 +2146,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2242,7 +2264,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; @@ -2294,6 +2316,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); } @@ -2795,7 +2820,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2862,6 +2890,129 @@ 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 @@ -3059,7 +3210,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3107,7 +3258,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, @@ -3442,7 +3593,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3917,8 +4069,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); @@ -4248,10 +4407,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; @@ -4262,7 +4422,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); @@ -5005,6 +5165,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; @@ -5068,6 +5231,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5672,7 +5860,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) { @@ -5813,7 +6001,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); @@ -6545,11 +6737,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 || @@ -6630,11 +6826,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); + } + } + + mov->nb_streams = s->nb_streams; if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) mov->chapter_track = mov->nb_streams++; @@ -6773,12 +6983,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 */ @@ -6982,7 +7193,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); @@ -7270,6 +7481,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') }, @@ -7352,6 +7607,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", @@ -7514,3 +7783,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.894.gb6a874cedc-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-22 16:46 ` Vignesh Venkatasubramanian @ 2022-03-28 17:06 ` Vignesh Venkatasubramanian 2022-03-28 20:49 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-28 17:06 UTC (permalink / raw) To: FFmpeg development discussions and patches On Tue, Mar 22, 2022 at 9:46 AM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 319 insertions(+), 25 deletions(-) > > diff --git a/configure b/configure > index dff53e88bc..5aaf198704 100755 > --- a/configure > +++ b/configure > @@ -3394,6 +3394,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index 587ad59b3c..29e58353ee 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 1c31f48abb..b9b424bc97 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1306,7 +1306,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); > } > > @@ -2007,12 +2007,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"); > @@ -2022,7 +2023,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); > } > @@ -2088,7 +2089,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) { > @@ -2109,6 +2110,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; > @@ -2126,6 +2146,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > avio_wb32(pb, 0); /* size */ > if (mov->encryption_scheme != MOV_ENC_NONE) { > ffio_wfourcc(pb, "encv"); > + } else if (track->mode == MODE_AVIF) { > + ffio_wfourcc(pb, "av01"); > } else { > avio_wl32(pb, track->tag); // store it byteswapped > } > @@ -2242,7 +2264,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; > @@ -2294,6 +2316,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); > } > > @@ -2795,7 +2820,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "ffmpeg"; > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > hdlr_type = "vide"; > descr = "VideoHandler"; > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > @@ -2862,6 +2890,129 @@ 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 > @@ -3059,7 +3210,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > display_matrix = NULL; > } > > - if (track->flags & MOV_TRACK_ENABLED) > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > flags |= MOV_TKHD_FLAG_ENABLED; > > if (track->mode == MODE_ISM) > @@ -3107,7 +3258,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, > @@ -3442,7 +3593,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > mov_write_tapt_tag(pb, track); > } > } > - mov_write_track_udta_tag(pb, mov, st); > + if (track->mode != MODE_AVIF) > + mov_write_track_udta_tag(pb, mov, st); > track->entry = entry_backup; > track->chunkCount = chunk_backup; > return update_size(pb, pos); > @@ -3917,8 +4069,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); > @@ -4248,10 +4407,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; > @@ -4262,7 +4422,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); > @@ -5005,6 +5165,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; > @@ -5068,6 +5231,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > + // 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 > @@ -5672,7 +5860,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) { > @@ -5813,7 +6001,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); > @@ -6545,11 +6737,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 || > @@ -6630,11 +6826,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); > + } > + } > + > + > mov->nb_streams = s->nb_streams; > if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > mov->chapter_track = mov->nb_streams++; > @@ -6773,12 +6983,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 */ > @@ -6982,7 +7193,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); > @@ -7270,6 +7481,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') }, > @@ -7352,6 +7607,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", > @@ -7514,3 +7783,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 > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index 2ac84ed070..55b8469f68 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -242,6 +243,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) > -- > 2.35.1.894.gb6a874cedc-goog > Any more comments on this? If not, can this be merged please? :) -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-28 17:06 ` Vignesh Venkatasubramanian @ 2022-03-28 20:49 ` Vignesh Venkatasubramanian 2022-04-07 18:25 ` Vignesh Venkatasubramanian 2022-04-13 17:21 ` James Zern 0 siblings, 2 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-28 20:49 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specifiation: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verfied to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 319 insertions(+), 25 deletions(-) diff --git a/configure b/configure index e4d36aa639..b9a79d8982 100755 --- a/configure +++ b/configure @@ -3396,6 +3396,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 587ad59b3c..29e58353ee 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 70ceb0dea4..920f3dcf25 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1306,7 +1306,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); } @@ -2007,12 +2007,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"); @@ -2022,7 +2023,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); } @@ -2088,7 +2089,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) { @@ -2109,6 +2110,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; @@ -2126,6 +2146,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2242,7 +2264,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; @@ -2294,6 +2316,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); } @@ -2795,7 +2820,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2862,6 +2890,129 @@ 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 @@ -3059,7 +3210,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3107,7 +3258,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, @@ -3442,7 +3593,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3917,8 +4069,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); @@ -4248,10 +4407,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; @@ -4262,7 +4422,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); @@ -5005,6 +5165,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; @@ -5068,6 +5231,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { + // 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 @@ -5671,7 +5859,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) { @@ -5812,7 +6000,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); @@ -6545,11 +6737,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 || @@ -6630,11 +6826,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); + } + } + + mov->nb_streams = s->nb_streams; if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) mov->chapter_track = mov->nb_streams++; @@ -6773,12 +6983,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 */ @@ -6982,7 +7193,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); @@ -7270,6 +7481,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') }, @@ -7352,6 +7607,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", @@ -7514,3 +7783,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 2ac84ed070..55b8469f68 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -242,6 +243,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) -- 2.35.1.1021.g381101b075-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-28 20:49 ` Vignesh Venkatasubramanian @ 2022-04-07 18:25 ` Vignesh Venkatasubramanian 2022-04-13 17:21 ` James Zern 1 sibling, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-07 18:25 UTC (permalink / raw) To: FFmpeg development discussions and patches On Mon, Mar 28, 2022 at 1:49 PM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 319 insertions(+), 25 deletions(-) > > diff --git a/configure b/configure > index e4d36aa639..b9a79d8982 100755 > --- a/configure > +++ b/configure > @@ -3396,6 +3396,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index 587ad59b3c..29e58353ee 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 70ceb0dea4..920f3dcf25 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1306,7 +1306,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); > } > > @@ -2007,12 +2007,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"); > @@ -2022,7 +2023,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); > } > @@ -2088,7 +2089,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) { > @@ -2109,6 +2110,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; > @@ -2126,6 +2146,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > avio_wb32(pb, 0); /* size */ > if (mov->encryption_scheme != MOV_ENC_NONE) { > ffio_wfourcc(pb, "encv"); > + } else if (track->mode == MODE_AVIF) { > + ffio_wfourcc(pb, "av01"); > } else { > avio_wl32(pb, track->tag); // store it byteswapped > } > @@ -2242,7 +2264,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; > @@ -2294,6 +2316,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); > } > > @@ -2795,7 +2820,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "ffmpeg"; > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > hdlr_type = "vide"; > descr = "VideoHandler"; > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > @@ -2862,6 +2890,129 @@ 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 > @@ -3059,7 +3210,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > display_matrix = NULL; > } > > - if (track->flags & MOV_TRACK_ENABLED) > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > flags |= MOV_TKHD_FLAG_ENABLED; > > if (track->mode == MODE_ISM) > @@ -3107,7 +3258,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, > @@ -3442,7 +3593,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > mov_write_tapt_tag(pb, track); > } > } > - mov_write_track_udta_tag(pb, mov, st); > + if (track->mode != MODE_AVIF) > + mov_write_track_udta_tag(pb, mov, st); > track->entry = entry_backup; > track->chunkCount = chunk_backup; > return update_size(pb, pos); > @@ -3917,8 +4069,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); > @@ -4248,10 +4407,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; > @@ -4262,7 +4422,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); > @@ -5005,6 +5165,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; > @@ -5068,6 +5231,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > + // 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 > @@ -5671,7 +5859,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) { > @@ -5812,7 +6000,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); > @@ -6545,11 +6737,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 || > @@ -6630,11 +6826,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); > + } > + } > + > + > mov->nb_streams = s->nb_streams; > if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > mov->chapter_track = mov->nb_streams++; > @@ -6773,12 +6983,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 */ > @@ -6982,7 +7193,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); > @@ -7270,6 +7481,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') }, > @@ -7352,6 +7607,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", > @@ -7514,3 +7783,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 > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index 2ac84ed070..55b8469f68 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -242,6 +243,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) > -- > 2.35.1.1021.g381101b075-goog > Another ping on this? -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-28 20:49 ` Vignesh Venkatasubramanian 2022-04-07 18:25 ` Vignesh Venkatasubramanian @ 2022-04-13 17:21 ` James Zern 2022-04-13 20:40 ` Vignesh Venkatasubramanian 2022-04-13 20:41 ` Vignesh Venkatasubramanian 1 sibling, 2 replies; 71+ messages in thread From: James Zern @ 2022-04-13 17:21 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: Vignesh Venkatasubramanian On Mon, Mar 28, 2022 at 1:49 PM Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > Specification > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verfied to be valid by Compliance Warden: Verified > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 319 insertions(+), 25 deletions(-) > There might be some other issues, you can try tools/patcheck. > [...] > @@ -5068,6 +5231,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { !... is the preferred style. > @@ -6773,12 +6983,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); This is indented with tabs. _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-13 17:21 ` James Zern @ 2022-04-13 20:40 ` Vignesh Venkatasubramanian 2022-04-13 21:01 ` Andreas Rheinhardt 2022-05-02 17:28 ` James Zern 2022-04-13 20:41 ` Vignesh Venkatasubramanian 1 sibling, 2 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-13 20:40 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specification: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verified to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 319 insertions(+), 25 deletions(-) diff --git a/configure b/configure index 358a614854..ef9d6cdc92 100755 --- a/configure +++ b/configure @@ -3398,6 +3398,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 7c1d0ac38f..320ddf9898 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 4202d0b79a..302ee1fda5 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1334,7 +1334,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); } @@ -2035,12 +2035,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"); @@ -2050,7 +2051,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); } @@ -2116,7 +2117,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) { @@ -2137,6 +2138,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; @@ -2154,6 +2174,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2270,7 +2292,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; @@ -2322,6 +2344,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); } @@ -2823,7 +2848,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "ffmpeg"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2890,6 +2918,129 @@ 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 @@ -3087,7 +3238,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3135,7 +3286,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, @@ -3470,7 +3621,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3945,8 +4097,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); @@ -4276,10 +4435,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; @@ -4290,7 +4450,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); @@ -5033,6 +5193,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; @@ -5096,6 +5259,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 @@ -5699,7 +5887,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) { @@ -5840,7 +6028,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); @@ -6573,11 +6765,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 || @@ -6658,11 +6854,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); + } + } + + mov->nb_streams = s->nb_streams; if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) mov->chapter_track = mov->nb_streams++; @@ -6801,12 +7011,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 */ @@ -7024,7 +7235,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); @@ -7312,6 +7523,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') }, @@ -7394,6 +7649,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", @@ -7556,3 +7825,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index 67d6d4fb66..c72bca1208 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -243,6 +244,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) -- 2.35.1.1178.g4f1659d476-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-13 20:40 ` Vignesh Venkatasubramanian @ 2022-04-13 21:01 ` Andreas Rheinhardt 2022-04-13 21:33 ` Vignesh Venkatasubramanian 2022-05-02 17:28 ` James Zern 1 sibling, 1 reply; 71+ messages in thread From: Andreas Rheinhardt @ 2022-04-13 21:01 UTC (permalink / raw) To: ffmpeg-devel Vignesh Venkatasubramanian: > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verified to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 319 insertions(+), 25 deletions(-) > > +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; Can it happen that moov_written is true? What happens if it is? (I presume the file to be invalid.) > + > + 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; > +} > + - Andreas _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-13 21:01 ` Andreas Rheinhardt @ 2022-04-13 21:33 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-13 21:33 UTC (permalink / raw) To: FFmpeg development discussions and patches On Wed, Apr 13, 2022 at 2:01 PM Andreas Rheinhardt <andreas.rheinhardt@outlook.com> wrote: > > Vignesh Venkatasubramanian: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verified to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > > libavformat/movenc.h | 5 + > > 4 files changed, 319 insertions(+), 25 deletions(-) > > > > +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; > > Can it happen that moov_written is true? What happens if it is? (I > presume the file to be invalid.) > This is more of a sanity check. I don't think this will ever happen (as long as write_trailer is not called more than once). If you prefer, i can remove it. > > + > > + 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; > > +} > > + > > - Andreas > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-13 20:40 ` Vignesh Venkatasubramanian 2022-04-13 21:01 ` Andreas Rheinhardt @ 2022-05-02 17:28 ` James Zern 2022-05-02 21:34 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: James Zern @ 2022-05-02 17:28 UTC (permalink / raw) To: FFmpeg development discussions and patches On Wed, Apr 13, 2022 at 1:40 PM Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verified to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 319 insertions(+), 25 deletions(-) > It would be good to have a Changelog entry after the ticket is fixed. > [...] > @@ -2823,7 +2848,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "ffmpeg"; Should this be PictureHandler or blank? What's written for heic? > [...] > +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); > +} > + > + This extra line could be removed. > [...] > + /* 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); > + } > + } > + > + This one too. _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-02 17:28 ` James Zern @ 2022-05-02 21:34 ` Vignesh Venkatasubramanian 2022-05-02 21:35 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-02 21:34 UTC (permalink / raw) To: FFmpeg development discussions and patches On Mon, May 2, 2022 at 10:28 AM James Zern <jzern-at-google.com@ffmpeg.org> wrote: > > On Wed, Apr 13, 2022 at 1:40 PM Vignesh Venkatasubramanian > <vigneshv-at-google.com@ffmpeg.org> wrote: > > > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verified to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > > libavformat/movenc.h | 5 + > > 4 files changed, 319 insertions(+), 25 deletions(-) > > > > It would be good to have a Changelog entry after the ticket is fixed. > Acknowledged. Will do. > > [...] > > @@ -2823,7 +2848,10 @@ 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) { > > + if (track->mode == MODE_AVIF) { > > + hdlr_type = "pict"; > > + descr = "ffmpeg"; > > Should this be PictureHandler or blank? What's written for heic? > Hmm, i used the string "ffmpeg" because libavif was writing "libavif" [1]. But i think it makes sense to be consistent with the other types here. I have updated the string to be "PictureHandler". [1] https://github.com/AOMediaCodec/libavif/blob/45b6b1b88b54de6031c15b4f9dbee10f86244d1b/src/write.c#L456 > > [...] > > +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); > > +} > > + > > + > > This extra line could be removed. Removed. > > > [...] > > + /* 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); > > + } > > + } > > + > > + > > This one too. Removed. > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-02 21:34 ` Vignesh Venkatasubramanian @ 2022-05-02 21:35 ` Vignesh Venkatasubramanian 2022-05-03 23:39 ` James Zern 2022-05-04 2:46 ` "zhilizhao(赵志立)" 0 siblings, 2 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-02 21:35 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specification: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verified to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 335 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 317 insertions(+), 25 deletions(-) diff --git a/configure b/configure index 196873c4aa..2992f9760e 100755 --- a/configure +++ b/configure @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 63876c468f..1802536633 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 271db99b46..aa24ed1a73 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1335,7 +1335,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); } @@ -2037,12 +2037,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"); @@ -2052,7 +2053,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); } @@ -2118,7 +2119,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) { @@ -2139,6 +2140,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; @@ -2156,6 +2176,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb32(pb, 0); /* size */ if (mov->encryption_scheme != MOV_ENC_NONE) { ffio_wfourcc(pb, "encv"); + } else if (track->mode == MODE_AVIF) { + ffio_wfourcc(pb, "av01"); } else { avio_wl32(pb, track->tag); // store it byteswapped } @@ -2272,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; @@ -2324,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); } @@ -2825,7 +2850,10 @@ 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) { + if (track->mode == MODE_AVIF) { + hdlr_type = "pict"; + descr = "PictureHandler"; + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { hdlr_type = "vide"; descr = "VideoHandler"; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2892,6 +2920,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 @@ -3089,7 +3239,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, display_matrix = NULL; } - if (track->flags & MOV_TRACK_ENABLED) + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) flags |= MOV_TKHD_FLAG_ENABLED; if (track->mode == MODE_ISM) @@ -3137,7 +3287,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, @@ -3472,7 +3622,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3947,8 +4098,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); @@ -4278,10 +4436,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; @@ -4292,7 +4451,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); @@ -5039,6 +5198,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; @@ -5102,6 +5264,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 @@ -5705,7 +5892,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) { @@ -5846,7 +6033,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); @@ -6579,11 +6770,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 || @@ -6664,11 +6859,24 @@ 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); + } + } + mov->nb_streams = s->nb_streams; if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) mov->chapter_track = mov->nb_streams++; @@ -6811,12 +7019,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 */ @@ -7034,7 +7243,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); @@ -7322,6 +7531,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') }, @@ -7404,6 +7657,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", @@ -7566,3 +7833,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index ca507e0e04..281576cc66 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -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) -- 2.36.0.464.gb9c8b46e94-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-02 21:35 ` Vignesh Venkatasubramanian @ 2022-05-03 23:39 ` James Zern 2022-05-04 2:46 ` "zhilizhao(赵志立)" 1 sibling, 0 replies; 71+ messages in thread From: James Zern @ 2022-05-03 23:39 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: Vignesh Venkatasubramanian On Mon, May 2, 2022 at 2:35 PM Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verified to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 335 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 317 insertions(+), 25 deletions(-) > lgtm _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-02 21:35 ` Vignesh Venkatasubramanian 2022-05-03 23:39 ` James Zern @ 2022-05-04 2:46 ` "zhilizhao(赵志立)" 2022-05-04 16:45 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: "zhilizhao(赵志立)" @ 2022-05-04 2:46 UTC (permalink / raw) To: FFmpeg development discussions and patches > On May 3, 2022, at 5:35 AM, Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verified to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 335 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 317 insertions(+), 25 deletions(-) > > […] > static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) > { > int ret = AVERROR_BUG; > @@ -2156,6 +2176,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > avio_wb32(pb, 0); /* size */ > if (mov->encryption_scheme != MOV_ENC_NONE) { > ffio_wfourcc(pb, "encv"); > + } else if (track->mode == MODE_AVIF) { > + ffio_wfourcc(pb, "av01"); Can the ‘else’ path handle the case? > } else { > avio_wl32(pb, track->tag); // store it byteswapped > } > @@ -2272,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; > @@ -2324,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); > } > > @@ -2825,7 +2850,10 @@ 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) { > + if (track->mode == MODE_AVIF) { > + hdlr_type = "pict"; > + descr = "PictureHandler"; > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { I prefer put check inside the ‘if (track->par->codec_type == AVMEDIA_TYPE_VIDEO)’. It’s a special case of ‘AVMEDIA_TYPE_VIDEO’. > hdlr_type = "vide"; > descr = "VideoHandler"; > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > @@ -2892,6 +2920,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 > @@ -3089,7 +3239,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > display_matrix = NULL; > } > > - if (track->flags & MOV_TRACK_ENABLED) > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > flags |= MOV_TKHD_FLAG_ENABLED; Set track->flags properly to avoid adding the mode check everywhere. > > if (track->mode == MODE_ISM) > @@ -3137,7 +3287,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, > @@ -3472,7 +3622,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > mov_write_tapt_tag(pb, track); > } > } > - mov_write_track_udta_tag(pb, mov, st); > + if (track->mode != MODE_AVIF) > + mov_write_track_udta_tag(pb, mov, st); Please check if the check can be removed. mov_write_track_udta_tag() does nothing for mode other than MOV/MP4. > track->entry = entry_backup; > track->chunkCount = chunk_backup; > return update_size(pb, pos); > @@ -3947,8 +4098,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); > @@ -4278,10 +4436,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; > @@ -4292,7 +4451,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); > @@ -5039,6 +5198,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; > @@ -5102,6 +5264,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 > @@ -5705,7 +5892,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) { > @@ -5846,7 +6033,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); > @@ -6579,11 +6770,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 || > @@ -6664,11 +6859,24 @@ 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); > + } > + } > + > mov->nb_streams = s->nb_streams; > if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > mov->chapter_track = mov->nb_streams++; > @@ -6811,12 +7019,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 */ > @@ -7034,7 +7243,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); > @@ -7322,6 +7531,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') }, > @@ -7404,6 +7657,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", > @@ -7566,3 +7833,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 > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index ca507e0e04..281576cc66 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -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) > -- > 2.36.0.464.gb9c8b46e94-goog > > _______________________________________________ > 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". _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-04 2:46 ` "zhilizhao(赵志立)" @ 2022-05-04 16:45 ` Vignesh Venkatasubramanian 2022-05-04 16:48 ` Vignesh Venkatasubramanian 2022-05-04 17:10 ` "zhilizhao(赵志立)" 0 siblings, 2 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-04 16:45 UTC (permalink / raw) To: FFmpeg development discussions and patches On Tue, May 3, 2022 at 7:46 PM "zhilizhao(赵志立)" <quinkblack@foxmail.com> wrote: > > Thanks for the review! > > > On May 3, 2022, at 5:35 AM, Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > > > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verified to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 335 ++++++++++++++++++++++++++++++++++++--- > > libavformat/movenc.h | 5 + > > 4 files changed, 317 insertions(+), 25 deletions(-) > > > > > > […] > > > static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track) > > { > > int ret = AVERROR_BUG; > > @@ -2156,6 +2176,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > avio_wb32(pb, 0); /* size */ > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > ffio_wfourcc(pb, "encv"); > > + } else if (track->mode == MODE_AVIF) { > > + ffio_wfourcc(pb, "av01"); > > Can the ‘else’ path handle the case? > You are right. Removed the "else if" case. > > } else { > > avio_wl32(pb, track->tag); // store it byteswapped > > } > > @@ -2272,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; > > @@ -2324,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); > > } > > > > @@ -2825,7 +2850,10 @@ 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) { > > + if (track->mode == MODE_AVIF) { > > + hdlr_type = "pict"; > > + descr = "PictureHandler"; > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > I prefer put check inside the ‘if (track->par->codec_type == AVMEDIA_TYPE_VIDEO)’. > It’s a special case of ‘AVMEDIA_TYPE_VIDEO’. > Done. > > hdlr_type = "vide"; > > descr = "VideoHandler"; > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > @@ -2892,6 +2920,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 > > @@ -3089,7 +3239,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > display_matrix = NULL; > > } > > > > - if (track->flags & MOV_TRACK_ENABLED) > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > flags |= MOV_TKHD_FLAG_ENABLED; > > Set track->flags properly to avoid adding the mode check everywhere. > Done. I have set the disposition field in mov_init which will in-turn set the track->flags properly. > > > > if (track->mode == MODE_ISM) > > @@ -3137,7 +3287,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, > > @@ -3472,7 +3622,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > mov_write_tapt_tag(pb, track); > > } > > } > > - mov_write_track_udta_tag(pb, mov, st); > > + if (track->mode != MODE_AVIF) > > + mov_write_track_udta_tag(pb, mov, st); > > Please check if the check can be removed. mov_write_track_udta_tag() does nothing > for mode other than MOV/MP4. > mov_write_track_udta_tag() writes itunes meta and loci tags when mode is not MOV/MP4. So this check is necessary. > > track->entry = entry_backup; > > track->chunkCount = chunk_backup; > > return update_size(pb, pos); > > @@ -3947,8 +4098,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); > > @@ -4278,10 +4436,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; > > @@ -4292,7 +4451,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); > > @@ -5039,6 +5198,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; > > @@ -5102,6 +5264,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 > > @@ -5705,7 +5892,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) { > > @@ -5846,7 +6033,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); > > @@ -6579,11 +6770,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 || > > @@ -6664,11 +6859,24 @@ 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); > > + } > > + } > > + > > mov->nb_streams = s->nb_streams; > > if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > > mov->chapter_track = mov->nb_streams++; > > @@ -6811,12 +7019,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 */ > > @@ -7034,7 +7243,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); > > @@ -7322,6 +7531,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') }, > > @@ -7404,6 +7657,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", > > @@ -7566,3 +7833,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 > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > index ca507e0e04..281576cc66 100644 > > --- a/libavformat/movenc.h > > +++ b/libavformat/movenc.h > > @@ -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; > > @@ -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) > > -- > > 2.36.0.464.gb9c8b46e94-goog > > > > _______________________________________________ > > 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". > > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-04 16:45 ` Vignesh Venkatasubramanian @ 2022-05-04 16:48 ` Vignesh Venkatasubramanian 2022-05-04 17:10 ` "zhilizhao(赵志立)" 1 sibling, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-04 16:48 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specification: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verified to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 336 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 318 insertions(+), 25 deletions(-) diff --git a/configure b/configure index 196873c4aa..2992f9760e 100755 --- a/configure +++ b/configure @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 63876c468f..1802536633 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 271db99b46..b1c8b0fd81 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1335,7 +1335,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); } @@ -2037,12 +2037,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"); @@ -2052,7 +2053,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); } @@ -2118,7 +2119,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) { @@ -2139,6 +2140,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; @@ -2272,7 +2292,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; @@ -2324,6 +2344,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); } @@ -2826,8 +2849,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"; @@ -2892,6 +2920,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 @@ -3137,7 +3287,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, @@ -3472,7 +3622,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_tapt_tag(pb, track); } } - mov_write_track_udta_tag(pb, mov, st); + if (track->mode != MODE_AVIF) + mov_write_track_udta_tag(pb, mov, st); track->entry = entry_backup; track->chunkCount = chunk_backup; return update_size(pb, pos); @@ -3947,8 +4098,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); @@ -4278,10 +4436,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; @@ -4292,7 +4451,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); @@ -5039,6 +5198,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; @@ -5102,6 +5264,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 @@ -5705,7 +5892,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) { @@ -5846,7 +6033,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); @@ -6579,11 +6770,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 || @@ -6664,11 +6859,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++; @@ -6811,12 +7020,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 */ @@ -7034,7 +7244,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); @@ -7322,6 +7532,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') }, @@ -7404,6 +7658,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", @@ -7566,3 +7834,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index ca507e0e04..281576cc66 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -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) -- 2.36.0.512.ge40c2bad7a-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-04 16:45 ` Vignesh Venkatasubramanian 2022-05-04 16:48 ` Vignesh Venkatasubramanian @ 2022-05-04 17:10 ` "zhilizhao(赵志立)" 2022-05-04 17:14 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: "zhilizhao(赵志立)" @ 2022-05-04 17:10 UTC (permalink / raw) To: FFmpeg development discussions and patches > On May 5, 2022, at 12:45 AM, Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > >>> >>> - mov_write_track_udta_tag(pb, mov, st); >>> + if (track->mode != MODE_AVIF) >>> + mov_write_track_udta_tag(pb, mov, st); >> >> Please check if the check can be removed. mov_write_track_udta_tag() does nothing >> for mode other than MOV/MP4. >> > > mov_write_track_udta_tag() writes itunes meta and loci tags when mode > is not MOV/MP4. So this check is necessary. > I think you are referring to ‘mov_write_udta_tag', not ‘mov_write_track_udta_tag', no? _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-04 17:10 ` "zhilizhao(赵志立)" @ 2022-05-04 17:14 ` Vignesh Venkatasubramanian 2022-05-04 17:15 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-04 17:14 UTC (permalink / raw) To: FFmpeg development discussions and patches On Wed, May 4, 2022 at 10:10 AM "zhilizhao(赵志立)" <quinkblack@foxmail.com> wrote: > > > > > On May 5, 2022, at 12:45 AM, Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > > > >>> > >>> - mov_write_track_udta_tag(pb, mov, st); > >>> + if (track->mode != MODE_AVIF) > >>> + mov_write_track_udta_tag(pb, mov, st); > >> > >> Please check if the check can be removed. mov_write_track_udta_tag() does nothing > >> for mode other than MOV/MP4. > >> > > > > mov_write_track_udta_tag() writes itunes meta and loci tags when mode > > is not MOV/MP4. So this check is necessary. > > > > I think you are referring to ‘mov_write_udta_tag', not > ‘mov_write_track_udta_tag', no? Ah yes you are right. mov_write_track_udta_tag doesn't do anything for non-MOV/MP4. I have removed the check. > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-04 17:14 ` Vignesh Venkatasubramanian @ 2022-05-04 17:15 ` Vignesh Venkatasubramanian 2022-05-11 16:54 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-04 17:15 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specification: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verified to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 333 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 316 insertions(+), 24 deletions(-) diff --git a/configure b/configure index 196873c4aa..2992f9760e 100755 --- a/configure +++ b/configure @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 63876c468f..1802536633 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 271db99b46..1a20fe17ca 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1335,7 +1335,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); } @@ -2037,12 +2037,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"); @@ -2052,7 +2053,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); } @@ -2118,7 +2119,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) { @@ -2139,6 +2140,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; @@ -2272,7 +2292,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; @@ -2324,6 +2344,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); } @@ -2826,8 +2849,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"; @@ -2892,6 +2920,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 @@ -3137,7 +3287,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, @@ -3947,8 +4097,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); @@ -4278,10 +4435,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; @@ -4292,7 +4450,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); @@ -5039,6 +5197,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; @@ -5102,6 +5263,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 @@ -5705,7 +5891,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) { @@ -5846,7 +6032,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); @@ -6579,11 +6769,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 || @@ -6664,11 +6858,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++; @@ -6811,12 +7019,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 */ @@ -7034,7 +7243,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); @@ -7322,6 +7531,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') }, @@ -7404,6 +7657,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", @@ -7566,3 +7833,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index ca507e0e04..281576cc66 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -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) -- 2.36.0.512.ge40c2bad7a-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-04 17:15 ` Vignesh Venkatasubramanian @ 2022-05-11 16:54 ` Vignesh Venkatasubramanian 2022-05-11 17:25 ` Gyan Doshi 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-11 16:54 UTC (permalink / raw) To: FFmpeg development discussions and patches On Wed, May 4, 2022 at 10:15 AM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verified to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 333 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 316 insertions(+), 24 deletions(-) > > diff --git a/configure b/configure > index 196873c4aa..2992f9760e 100755 > --- a/configure > +++ b/configure > @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index 63876c468f..1802536633 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 271db99b46..1a20fe17ca 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1335,7 +1335,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); > } > > @@ -2037,12 +2037,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"); > @@ -2052,7 +2053,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); > } > @@ -2118,7 +2119,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) { > @@ -2139,6 +2140,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; > @@ -2272,7 +2292,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; > @@ -2324,6 +2344,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); > } > > @@ -2826,8 +2849,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"; > @@ -2892,6 +2920,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 > @@ -3137,7 +3287,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, > @@ -3947,8 +4097,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); > @@ -4278,10 +4435,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; > @@ -4292,7 +4450,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); > @@ -5039,6 +5197,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; > @@ -5102,6 +5263,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 > @@ -5705,7 +5891,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) { > @@ -5846,7 +6032,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); > @@ -6579,11 +6769,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 || > @@ -6664,11 +6858,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++; > @@ -6811,12 +7019,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 */ > @@ -7034,7 +7243,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); > @@ -7322,6 +7531,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') }, > @@ -7404,6 +7657,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", > @@ -7566,3 +7833,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 > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index ca507e0e04..281576cc66 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -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) > -- > 2.36.0.512.ge40c2bad7a-goog > Any more comments on this? If not, can this be merged please. -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-11 16:54 ` Vignesh Venkatasubramanian @ 2022-05-11 17:25 ` Gyan Doshi 2022-05-12 10:26 ` Gyan Doshi 0 siblings, 1 reply; 71+ messages in thread From: Gyan Doshi @ 2022-05-11 17:25 UTC (permalink / raw) To: ffmpeg-devel On 2022-05-11 10:24 pm, Vignesh Venkatasubramanian wrote: > On Wed, May 4, 2022 at 10:15 AM Vignesh Venkatasubramanian > <vigneshv@google.com> wrote: >> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. >> >> AVIF Specification: https://aomediacodec.github.io/av1-avif >> >> Sample usage for still image: >> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif >> >> Sample usage for animated AVIF image: >> ffmpeg -i video.mp4 animated.avif >> >> We can re-use any of the AV1 encoding options that will make >> sense for image encoding (like bitrate, tiles, encoding speed, >> etc). >> >> The files generated by this muxer has been verified to be valid >> AVIF files by the following: >> 1) Displays on Chrome (both still and animated images). >> 2) Displays on Firefox (only still images, firefox does not support >> animated AVIF yet). >> 3) Verified to be valid by Compliance Warden: >> https://github.com/gpac/ComplianceWarden >> >> Fixes the encoder/muxer part of Trac Ticket #7621 >> >> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> >> --- >> configure | 1 + >> libavformat/allformats.c | 1 + >> libavformat/movenc.c | 333 ++++++++++++++++++++++++++++++++++++--- >> libavformat/movenc.h | 5 + >> 4 files changed, 316 insertions(+), 24 deletions(-) >> >> diff --git a/configure b/configure >> index 196873c4aa..2992f9760e 100755 >> --- a/configure >> +++ b/configure >> @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" >> av1_demuxer_select="av1_frame_merge_bsf av1_parser" >> avi_demuxer_select="riffdec exif" >> avi_muxer_select="riffenc" >> +avif_muxer_select="mov_muxer" >> caf_demuxer_select="iso_media" >> caf_muxer_select="iso_media" >> dash_muxer_select="mp4_muxer" >> diff --git a/libavformat/allformats.c b/libavformat/allformats.c >> index 63876c468f..1802536633 100644 >> --- a/libavformat/allformats.c >> +++ b/libavformat/allformats.c >> @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; >> extern const AVInputFormat ff_av1_demuxer; >> extern const AVInputFormat ff_avi_demuxer; >> extern const AVOutputFormat ff_avi_muxer; >> +extern const AVOutputFormat ff_avif_muxer; >> extern const AVInputFormat ff_avisynth_demuxer; >> extern const AVOutputFormat ff_avm2_muxer; >> extern const AVInputFormat ff_avr_demuxer; >> diff --git a/libavformat/movenc.c b/libavformat/movenc.c >> index 271db99b46..1a20fe17ca 100644 >> --- a/libavformat/movenc.c >> +++ b/libavformat/movenc.c >> @@ -1335,7 +1335,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); >> } >> >> @@ -2037,12 +2037,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"); >> @@ -2052,7 +2053,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); >> } >> @@ -2118,7 +2119,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) { >> @@ -2139,6 +2140,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; >> @@ -2272,7 +2292,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; >> @@ -2324,6 +2344,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); >> } >> >> @@ -2826,8 +2849,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"; >> @@ -2892,6 +2920,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 >> @@ -3137,7 +3287,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, >> @@ -3947,8 +4097,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); >> @@ -4278,10 +4435,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; >> @@ -4292,7 +4450,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); >> @@ -5039,6 +5197,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; >> @@ -5102,6 +5263,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 >> @@ -5705,7 +5891,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) { >> @@ -5846,7 +6032,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); >> @@ -6579,11 +6769,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 || >> @@ -6664,11 +6858,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++; >> @@ -6811,12 +7019,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 */ >> @@ -7034,7 +7243,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); >> @@ -7322,6 +7531,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') }, >> @@ -7404,6 +7657,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", >> @@ -7566,3 +7833,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 >> diff --git a/libavformat/movenc.h b/libavformat/movenc.h >> index ca507e0e04..281576cc66 100644 >> --- a/libavformat/movenc.h >> +++ b/libavformat/movenc.h >> @@ -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; >> @@ -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) >> -- >> 2.36.0.512.ge40c2bad7a-goog >> > Any more comments on this? If not, can this be merged please. Will test and push. Regards, Gyan _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-11 17:25 ` Gyan Doshi @ 2022-05-12 10:26 ` Gyan Doshi 2022-05-12 16:23 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Gyan Doshi @ 2022-05-12 10:26 UTC (permalink / raw) To: ffmpeg-devel On 2022-05-11 10:55 pm, Gyan Doshi wrote: > > > On 2022-05-11 10:24 pm, Vignesh Venkatasubramanian wrote: >> On Wed, May 4, 2022 at 10:15 AM Vignesh Venkatasubramanian >> <vigneshv@google.com> wrote: >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. >>> >>> AVIF Specification: https://aomediacodec.github.io/av1-avif >>> >>> Sample usage for still image: >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif I see an option for av1 encoder called still-picture but no avif-image. Muxing works and files do play back, however for animated AVIF, the demuxer complains "Invalid mvhd time scale 0, defaulting to 1". Regards, Gyan >>> >>> Sample usage for animated AVIF image: >>> ffmpeg -i video.mp4 animated.avif >>> >>> We can re-use any of the AV1 encoding options that will make >>> sense for image encoding (like bitrate, tiles, encoding speed, >>> etc). >>> >>> The files generated by this muxer has been verified to be valid >>> AVIF files by the following: >>> 1) Displays on Chrome (both still and animated images). >>> 2) Displays on Firefox (only still images, firefox does not support >>> animated AVIF yet). >>> 3) Verified to be valid by Compliance Warden: >>> https://github.com/gpac/ComplianceWarden >>> >>> Fixes the encoder/muxer part of Trac Ticket #7621 >>> >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> >>> --- >>> configure | 1 + >>> libavformat/allformats.c | 1 + >>> libavformat/movenc.c | 333 >>> ++++++++++++++++++++++++++++++++++++--- >>> libavformat/movenc.h | 5 + >>> 4 files changed, 316 insertions(+), 24 deletions(-) >>> >>> diff --git a/configure b/configure >>> index 196873c4aa..2992f9760e 100755 >>> --- a/configure >>> +++ b/configure >>> @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" >>> av1_demuxer_select="av1_frame_merge_bsf av1_parser" >>> avi_demuxer_select="riffdec exif" >>> avi_muxer_select="riffenc" >>> +avif_muxer_select="mov_muxer" >>> caf_demuxer_select="iso_media" >>> caf_muxer_select="iso_media" >>> dash_muxer_select="mp4_muxer" >>> diff --git a/libavformat/allformats.c b/libavformat/allformats.c >>> index 63876c468f..1802536633 100644 >>> --- a/libavformat/allformats.c >>> +++ b/libavformat/allformats.c >>> @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; >>> extern const AVInputFormat ff_av1_demuxer; >>> extern const AVInputFormat ff_avi_demuxer; >>> extern const AVOutputFormat ff_avi_muxer; >>> +extern const AVOutputFormat ff_avif_muxer; >>> extern const AVInputFormat ff_avisynth_demuxer; >>> extern const AVOutputFormat ff_avm2_muxer; >>> extern const AVInputFormat ff_avr_demuxer; >>> diff --git a/libavformat/movenc.c b/libavformat/movenc.c >>> index 271db99b46..1a20fe17ca 100644 >>> --- a/libavformat/movenc.c >>> +++ b/libavformat/movenc.c >>> @@ -1335,7 +1335,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); >>> } >>> >>> @@ -2037,12 +2037,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"); >>> @@ -2052,7 +2053,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); >>> } >>> @@ -2118,7 +2119,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) { >>> @@ -2139,6 +2140,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; >>> @@ -2272,7 +2292,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; >>> @@ -2324,6 +2344,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); >>> } >>> >>> @@ -2826,8 +2849,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"; >>> @@ -2892,6 +2920,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 >>> @@ -3137,7 +3287,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, >>> @@ -3947,8 +4097,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); >>> @@ -4278,10 +4435,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; >>> @@ -4292,7 +4450,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); >>> @@ -5039,6 +5197,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; >>> @@ -5102,6 +5263,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 >>> @@ -5705,7 +5891,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) { >>> @@ -5846,7 +6032,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); >>> @@ -6579,11 +6769,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 || >>> @@ -6664,11 +6858,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++; >>> @@ -6811,12 +7019,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 */ >>> @@ -7034,7 +7243,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); >>> @@ -7322,6 +7531,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') }, >>> @@ -7404,6 +7657,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", >>> @@ -7566,3 +7833,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 >>> diff --git a/libavformat/movenc.h b/libavformat/movenc.h >>> index ca507e0e04..281576cc66 100644 >>> --- a/libavformat/movenc.h >>> +++ b/libavformat/movenc.h >>> @@ -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; >>> @@ -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) >>> -- >>> 2.36.0.512.ge40c2bad7a-goog >>> >> Any more comments on this? If not, can this be merged please. > > Will test and push. > > Regards, > Gyan > _______________________________________________ > 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". _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-12 10:26 ` Gyan Doshi @ 2022-05-12 16:23 ` Vignesh Venkatasubramanian 2022-05-12 16:23 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-12 16:23 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, May 12, 2022 at 3:27 AM Gyan Doshi <ffmpeg@gyani.pro> wrote: > > > > On 2022-05-11 10:55 pm, Gyan Doshi wrote: > > > > > > On 2022-05-11 10:24 pm, Vignesh Venkatasubramanian wrote: > >> On Wed, May 4, 2022 at 10:15 AM Vignesh Venkatasubramanian > >> <vigneshv@google.com> wrote: > >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > >>> > >>> AVIF Specification: https://aomediacodec.github.io/av1-avif > >>> > >>> Sample usage for still image: > >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > I see an option for av1 encoder called still-picture but no avif-image. > Sorry this flag was updated as a result of a review comment in an earlier patch. I forgot to update this commit message. Updated now. "still-picture" flag is what you want to set for the still images. > Muxing works and files do play back, however for animated AVIF, the > demuxer complains > "Invalid mvhd time scale 0, defaulting to 1". > I was going by playback in Chrome/Firefox and Compliance Warden's [1] validation. I have updated the muxer to use the track's timescale if movie_timescale is not set. The demuxer no longer warns about the timescale. > Regards, > Gyan > > > >>> > >>> Sample usage for animated AVIF image: > >>> ffmpeg -i video.mp4 animated.avif > >>> > >>> We can re-use any of the AV1 encoding options that will make > >>> sense for image encoding (like bitrate, tiles, encoding speed, > >>> etc). > >>> > >>> The files generated by this muxer has been verified to be valid > >>> AVIF files by the following: > >>> 1) Displays on Chrome (both still and animated images). > >>> 2) Displays on Firefox (only still images, firefox does not support > >>> animated AVIF yet). > >>> 3) Verified to be valid by Compliance Warden: > >>> https://github.com/gpac/ComplianceWarden > >>> > >>> Fixes the encoder/muxer part of Trac Ticket #7621 > >>> > >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > >>> --- > >>> configure | 1 + > >>> libavformat/allformats.c | 1 + > >>> libavformat/movenc.c | 333 > >>> ++++++++++++++++++++++++++++++++++++--- > >>> libavformat/movenc.h | 5 + > >>> 4 files changed, 316 insertions(+), 24 deletions(-) > >>> > >>> diff --git a/configure b/configure > >>> index 196873c4aa..2992f9760e 100755 > >>> --- a/configure > >>> +++ b/configure > >>> @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" > >>> av1_demuxer_select="av1_frame_merge_bsf av1_parser" > >>> avi_demuxer_select="riffdec exif" > >>> avi_muxer_select="riffenc" > >>> +avif_muxer_select="mov_muxer" > >>> caf_demuxer_select="iso_media" > >>> caf_muxer_select="iso_media" > >>> dash_muxer_select="mp4_muxer" > >>> diff --git a/libavformat/allformats.c b/libavformat/allformats.c > >>> index 63876c468f..1802536633 100644 > >>> --- a/libavformat/allformats.c > >>> +++ b/libavformat/allformats.c > >>> @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > >>> extern const AVInputFormat ff_av1_demuxer; > >>> extern const AVInputFormat ff_avi_demuxer; > >>> extern const AVOutputFormat ff_avi_muxer; > >>> +extern const AVOutputFormat ff_avif_muxer; > >>> extern const AVInputFormat ff_avisynth_demuxer; > >>> extern const AVOutputFormat ff_avm2_muxer; > >>> extern const AVInputFormat ff_avr_demuxer; > >>> diff --git a/libavformat/movenc.c b/libavformat/movenc.c > >>> index 271db99b46..1a20fe17ca 100644 > >>> --- a/libavformat/movenc.c > >>> +++ b/libavformat/movenc.c > >>> @@ -1335,7 +1335,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); > >>> } > >>> > >>> @@ -2037,12 +2037,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"); > >>> @@ -2052,7 +2053,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); > >>> } > >>> @@ -2118,7 +2119,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) { > >>> @@ -2139,6 +2140,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; > >>> @@ -2272,7 +2292,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; > >>> @@ -2324,6 +2344,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); > >>> } > >>> > >>> @@ -2826,8 +2849,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"; > >>> @@ -2892,6 +2920,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 > >>> @@ -3137,7 +3287,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, > >>> @@ -3947,8 +4097,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); > >>> @@ -4278,10 +4435,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; > >>> @@ -4292,7 +4450,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); > >>> @@ -5039,6 +5197,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; > >>> @@ -5102,6 +5263,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 > >>> @@ -5705,7 +5891,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) { > >>> @@ -5846,7 +6032,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); > >>> @@ -6579,11 +6769,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 || > >>> @@ -6664,11 +6858,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++; > >>> @@ -6811,12 +7019,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 */ > >>> @@ -7034,7 +7243,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); > >>> @@ -7322,6 +7531,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') }, > >>> @@ -7404,6 +7657,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", > >>> @@ -7566,3 +7833,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 > >>> diff --git a/libavformat/movenc.h b/libavformat/movenc.h > >>> index ca507e0e04..281576cc66 100644 > >>> --- a/libavformat/movenc.h > >>> +++ b/libavformat/movenc.h > >>> @@ -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; > >>> @@ -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) > >>> -- > >>> 2.36.0.512.ge40c2bad7a-goog > >>> > >> Any more comments on this? If not, can this be merged please. > > > > Will test and push. > > > > Regards, > > Gyan > > _______________________________________________ > > 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". > > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-12 16:23 ` Vignesh Venkatasubramanian @ 2022-05-12 16:23 ` Vignesh Venkatasubramanian 2022-05-13 7:22 ` Gyan Doshi 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-12 16:23 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add an AVIF muxer by re-using the existing the mov/mp4 muxer. AVIF Specification: https://aomediacodec.github.io/av1-avif Sample usage for still image: ffmpeg -i image.png -c:v libaom-av1 -still-picture 1 image.avif Sample usage for animated AVIF image: ffmpeg -i video.mp4 animated.avif We can re-use any of the AV1 encoding options that will make sense for image encoding (like bitrate, tiles, encoding speed, etc). The files generated by this muxer has been verified to be valid AVIF files by the following: 1) Displays on Chrome (both still and animated images). 2) Displays on Firefox (only still images, firefox does not support animated AVIF yet). 3) Verified to be valid by Compliance Warden: https://github.com/gpac/ComplianceWarden Fixes the encoder/muxer part of Trac Ticket #7621 Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> --- configure | 1 + libavformat/allformats.c | 1 + libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- libavformat/movenc.h | 5 + 4 files changed, 323 insertions(+), 25 deletions(-) diff --git a/configure b/configure index 196873c4aa..2992f9760e 100755 --- a/configure +++ b/configure @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" av1_demuxer_select="av1_frame_merge_bsf av1_parser" avi_demuxer_select="riffdec exif" avi_muxer_select="riffenc" +avif_muxer_select="mov_muxer" caf_demuxer_select="iso_media" caf_muxer_select="iso_media" dash_muxer_select="mp4_muxer" diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 63876c468f..1802536633 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; extern const AVInputFormat ff_av1_demuxer; extern const AVInputFormat ff_avi_demuxer; extern const AVOutputFormat ff_avi_muxer; +extern const AVOutputFormat ff_avif_muxer; extern const AVInputFormat ff_avisynth_demuxer; extern const AVOutputFormat ff_avm2_muxer; extern const AVInputFormat ff_avr_demuxer; diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 271db99b46..a07c0ae2b4 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -1335,7 +1335,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); } @@ -2037,12 +2037,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"); @@ -2052,7 +2053,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); } @@ -2118,7 +2119,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) { @@ -2139,6 +2140,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; @@ -2272,7 +2292,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; @@ -2324,6 +2344,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); } @@ -2826,8 +2849,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"; @@ -2892,6 +2920,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 @@ -3137,7 +3287,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, @@ -3536,6 +3686,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) { @@ -3570,7 +3721,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 */ @@ -3947,8 +4103,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); @@ -4278,10 +4441,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; @@ -4292,7 +4456,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); @@ -5039,6 +5203,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; @@ -5102,6 +5269,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 @@ -5705,7 +5897,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) { @@ -5846,7 +6038,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); @@ -6579,11 +6775,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 || @@ -6664,11 +6864,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++; @@ -6811,12 +7025,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 */ @@ -7034,7 +7249,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); @@ -7322,6 +7537,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') }, @@ -7404,6 +7663,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", @@ -7566,3 +7839,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 diff --git a/libavformat/movenc.h b/libavformat/movenc.h index ca507e0e04..281576cc66 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -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; @@ -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) -- 2.36.0.550.gb090851708-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-05-12 16:23 ` Vignesh Venkatasubramanian @ 2022-05-13 7:22 ` Gyan Doshi 0 siblings, 0 replies; 71+ messages in thread From: Gyan Doshi @ 2022-05-13 7:22 UTC (permalink / raw) To: ffmpeg-devel On 2022-05-12 09:53 pm, Vignesh Venkatasubramanian wrote: > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. Bumped lavf minor and pushed set as ab05e9a7f2...84241e63cf Regards, Gyan > > AVIF Specification: https://aomediacodec.github.io/av1-avif > > Sample usage for still image: > ffmpeg -i image.png -c:v libaom-av1 -still-picture 1 image.avif > > Sample usage for animated AVIF image: > ffmpeg -i video.mp4 animated.avif > > We can re-use any of the AV1 encoding options that will make > sense for image encoding (like bitrate, tiles, encoding speed, > etc). > > The files generated by this muxer has been verified to be valid > AVIF files by the following: > 1) Displays on Chrome (both still and animated images). > 2) Displays on Firefox (only still images, firefox does not support > animated AVIF yet). > 3) Verified to be valid by Compliance Warden: > https://github.com/gpac/ComplianceWarden > > Fixes the encoder/muxer part of Trac Ticket #7621 > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > configure | 1 + > libavformat/allformats.c | 1 + > libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- > libavformat/movenc.h | 5 + > 4 files changed, 323 insertions(+), 25 deletions(-) > > diff --git a/configure b/configure > index 196873c4aa..2992f9760e 100755 > --- a/configure > +++ b/configure > @@ -3404,6 +3404,7 @@ asf_stream_muxer_select="asf_muxer" > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > avi_demuxer_select="riffdec exif" > avi_muxer_select="riffenc" > +avif_muxer_select="mov_muxer" > caf_demuxer_select="iso_media" > caf_muxer_select="iso_media" > dash_muxer_select="mp4_muxer" > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index 63876c468f..1802536633 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > extern const AVInputFormat ff_av1_demuxer; > extern const AVInputFormat ff_avi_demuxer; > extern const AVOutputFormat ff_avi_muxer; > +extern const AVOutputFormat ff_avif_muxer; > extern const AVInputFormat ff_avisynth_demuxer; > extern const AVOutputFormat ff_avm2_muxer; > extern const AVInputFormat ff_avr_demuxer; > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > index 271db99b46..a07c0ae2b4 100644 > --- a/libavformat/movenc.c > +++ b/libavformat/movenc.c > @@ -1335,7 +1335,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); > } > > @@ -2037,12 +2037,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"); > @@ -2052,7 +2053,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); > } > @@ -2118,7 +2119,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) { > @@ -2139,6 +2140,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; > @@ -2272,7 +2292,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; > @@ -2324,6 +2344,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); > } > > @@ -2826,8 +2849,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"; > @@ -2892,6 +2920,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 > @@ -3137,7 +3287,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, > @@ -3536,6 +3686,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) { > @@ -3570,7 +3721,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 */ > @@ -3947,8 +4103,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); > @@ -4278,10 +4441,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; > @@ -4292,7 +4456,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); > @@ -5039,6 +5203,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; > @@ -5102,6 +5269,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 > @@ -5705,7 +5897,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) { > @@ -5846,7 +6038,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); > @@ -6579,11 +6775,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 || > @@ -6664,11 +6864,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++; > @@ -6811,12 +7025,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 */ > @@ -7034,7 +7249,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); > @@ -7322,6 +7537,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') }, > @@ -7404,6 +7663,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", > @@ -7566,3 +7839,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 > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > index ca507e0e04..281576cc66 100644 > --- a/libavformat/movenc.h > +++ b/libavformat/movenc.h > @@ -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; > @@ -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) _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-13 17:21 ` James Zern 2022-04-13 20:40 ` Vignesh Venkatasubramanian @ 2022-04-13 20:41 ` Vignesh Venkatasubramanian 1 sibling, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-13 20:41 UTC (permalink / raw) To: James Zern; +Cc: FFmpeg development discussions and patches On Wed, Apr 13, 2022 at 10:22 AM James Zern <jzern@google.com> wrote: > > On Mon, Mar 28, 2022 at 1:49 PM Vignesh Venkatasubramanian > <vigneshv-at-google.com@ffmpeg.org> wrote: > > > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > Specification > Fixed. > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verfied to be valid by Compliance Warden: > > Verified > Fixed. > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 337 ++++++++++++++++++++++++++++++++++++--- > > libavformat/movenc.h | 5 + > > 4 files changed, 319 insertions(+), 25 deletions(-) > > > > There might be some other issues, you can try tools/patcheck. > Thanks, i ran this and only found a couple of other "} before else-if" warnings. In those cases, i was just being consistent with the code around it. > > [...] > > @@ -5068,6 +5231,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > !... is the preferred style. Done. > > > @@ -6773,12 +6983,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); > > This is indented with tabs. Oops, fixed. -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-22 16:45 ` Vignesh Venkatasubramanian 2022-03-22 16:46 ` Vignesh Venkatasubramanian @ 2022-04-13 21:04 ` Andreas Rheinhardt 2022-04-13 21:35 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: Andreas Rheinhardt @ 2022-04-13 21:04 UTC (permalink / raw) To: ffmpeg-devel Vignesh Venkatasubramanian: > On Mon, Mar 21, 2022 at 1:46 PM Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> wrote: >> >> Vignesh Venkatasubramanian: >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. >>> >>> AVIF Specifiation: https://aomediacodec.github.io/av1-avif >>> >>> Sample usage for still image: >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif >>> >>> Sample usage for animated AVIF image: >>> ffmpeg -i video.mp4 animated.avif >>> >>> We can re-use any of the AV1 encoding options that will make >>> sense for image encoding (like bitrate, tiles, encoding speed, >>> etc). >>> >>> The files generated by this muxer has been verified to be valid >>> AVIF files by the following: >>> 1) Displays on Chrome (both still and animated images). >>> 2) Displays on Firefox (only still images, firefox does not support >>> animated AVIF yet). >>> 3) Verfied to be valid by Compliance Warden: >>> https://github.com/gpac/ComplianceWarden >>> >>> Fixes the encoder/muxer part of Trac Ticket #7621 >>> >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> >>> --- >>> configure | 1 + >>> libavformat/allformats.c | 1 + >>> libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- >>> libavformat/movenc.h | 5 + >>> 4 files changed, 322 insertions(+), 26 deletions(-) >>> >>> diff --git a/configure b/configure >>> index 8c69ab0c86..6d7020e96b 100755 >>> --- a/configure >>> +++ b/configure >>> @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" >>> av1_demuxer_select="av1_frame_merge_bsf av1_parser" >>> avi_demuxer_select="riffdec exif" >>> avi_muxer_select="riffenc" >>> +avif_muxer_select="mov_muxer" >>> caf_demuxer_select="iso_media" >>> caf_muxer_select="iso_media" >>> dash_muxer_select="mp4_muxer" >>> diff --git a/libavformat/allformats.c b/libavformat/allformats.c >>> index d066a7745b..400c17afbd 100644 >>> --- a/libavformat/allformats.c >>> +++ b/libavformat/allformats.c >>> @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; >>> extern const AVInputFormat ff_av1_demuxer; >>> extern const AVInputFormat ff_avi_demuxer; >>> extern const AVOutputFormat ff_avi_muxer; >>> +extern const AVOutputFormat ff_avif_muxer; >>> extern const AVInputFormat ff_avisynth_demuxer; >>> extern const AVOutputFormat ff_avm2_muxer; >>> extern const AVInputFormat ff_avr_demuxer; >>> diff --git a/libavformat/movenc.c b/libavformat/movenc.c >>> index 1a746a67fd..ff41579300 100644 >>> --- a/libavformat/movenc.c >>> +++ b/libavformat/movenc.c >>> @@ -1303,7 +1303,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); >>> } >>> >>> @@ -2004,12 +2004,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"); >>> @@ -2019,7 +2020,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); >>> } >>> @@ -2085,7 +2086,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) { >>> @@ -2106,6 +2107,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; >>> @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex >>> avio_wb32(pb, 0); /* size */ >>> if (mov->encryption_scheme != MOV_ENC_NONE) { >>> ffio_wfourcc(pb, "encv"); >>> + } else if (track->mode == MODE_AVIF) { >>> + ffio_wfourcc(pb, "av01"); >>> } else { >>> avio_wl32(pb, track->tag); // store it byteswapped >>> } >>> @@ -2239,7 +2261,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; >>> @@ -2291,6 +2313,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); >>> } >>> >>> @@ -2792,7 +2817,10 @@ 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) { >>> + if (track->mode == MODE_AVIF) { >>> + hdlr_type = "pict"; >>> + descr = "ffmpeg"; >>> + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { >>> hdlr_type = "vide"; >>> descr = "VideoHandler"; >>> } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { >>> @@ -2859,6 +2887,131 @@ 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); >>> + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); >> >> Is the number of planes really the correct number here? After all, for a >> muxer (instead of a decoder) it does not matter whether the chroma >> planes are interleaved like in AV_PIX_FMT_NV12 or not like in >> AV_PIX_FMT_YUV420P. You should better use pixdesc->nb_components. >> > > Done. > >>> + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); >>> + int i; >> >> We allow and use "for (int i = 0;" from C99 to save lines like these. >> > > Done. > >>> + avio_wb32(pb, 0); /* size */ >>> + ffio_wfourcc(pb, "pixi"); >>> + avio_wb32(pb, 0); /* Version & flags */ >>> + avio_w8(pb, num_channels); /* num_channels */ >>> + for (i = 0; i < num_channels; ++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 >>> @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, >>> display_matrix = NULL; >>> } >>> >>> - if (track->flags & MOV_TRACK_ENABLED) >>> + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) >>> flags |= MOV_TKHD_FLAG_ENABLED; >>> >>> if (track->mode == MODE_ISM) >>> @@ -3104,7 +3257,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, >>> @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext >>> mov_write_tapt_tag(pb, track); >>> } >>> } >>> - mov_write_track_udta_tag(pb, mov, st); >>> + if (track->mode != MODE_AVIF) >>> + mov_write_track_udta_tag(pb, mov, st); >>> track->entry = entry_backup; >>> track->chunkCount = chunk_backup; >>> return update_size(pb, pos); >>> @@ -3914,8 +4068,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); >>> @@ -4245,10 +4406,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; >>> @@ -4259,7 +4421,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); >>> @@ -5002,6 +5164,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; >>> @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { >>> + // 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 >>> @@ -5669,7 +5859,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) { >>> @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) >>> } >>> } >>> } else if (par->codec_id == AV_CODEC_ID_AV1) { >>> - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { >>> + if (trk->mode == MODE_AVIF) { >> >> Why this? AVIF requires that AVI-in-ISOBMFF sample format is honoured >> and this contains e.g. "OBUs of type OBU_TEMPORAL_DELIMITER, >> OBU_PADDING, or OBU_REDUNDANT_FRAME_HEADER SHOULD NOT be used". >> ff_av1_filter_obus(_buf)? merely ensures that these OBUs are stripped away. >> >> If the aim of this check is to disallow hint tracks or so, then it fails >> (all it does is ensuring that both the ordinary track as well as the >> hint track get data that might contain OBUs that should not be there). >> > > Done. I had to update the extent offset code here as well. > >>> + avio_write(pb, pkt->data, pkt->size); >>> + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { >>> ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, >>> &size, &offset); >>> if (ret < 0) >>> @@ -6230,6 +6422,10 @@ fail: >>> } >>> } >>> >>> + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { >>> + mov->avif_extent_length = pkt->size; >>> + } >>> + >>> return mov_write_single_packet(s, pkt); >>> } >>> } >>> @@ -6569,11 +6765,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 || >>> @@ -6654,11 +6854,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); >>> + } >>> + } >>> + >>> + >>> mov->nb_streams = s->nb_streams; >>> if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) >>> mov->chapter_track = mov->nb_streams++; >>> @@ -6797,12 +7011,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 */ >>> @@ -7003,7 +7218,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); >>> @@ -7291,6 +7506,48 @@ 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; >>> + int i; >>> + >>> + 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); >>> + for (i = 0; i < mov->nb_streams; i++) >>> + mov->tracks[i].data_offset = avio_tell(pb) + moov_size + 8; >> >> Don't call avio_tell() in a loop (and I wonder whether this loop is even >> necessary given that s->nb_streams is checked to be one). >> > > Removed the loop. I was thinking about potentially supporting multiple > output tracks in the future (for alpha channel for example), but this > code would not work in that case any way. > >>> + >>> + 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); >>> + >>> + avio_write(pb, buf, buf_size); >>> + ffio_free_dyn_buf(&mov->mdat_buf); >> >> Unnecessary: This will be freed in mov_free(). >> > > Removed. > >>> + >>> + // write extent offset. >>> + pos_backup = avio_tell(pb); >>> + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); >>> + avio_wb32(pb, mdat_pos); /* rewrite offset */ >> >> What guarantees that this fits into 32bits? >> > > Added a check to fail if it does not fit in 32-bits. > >>> + 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') }, >>> @@ -7373,6 +7630,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, >>> +}; >> >> It's not mandatory for a muxer to have a private class; it is only >> necessary for options. If you do not have options (like here), then the >> AVClass is useless. >> I actually wanted that you support the options that make sense for AVIF >> and I dislike that the avif muxer uses a different write_trailer than >> every other muxer here (which is the reason why e.g. the faststart >> option is not supported by it). Is it really so different? >> Furthermore, given that you don't use the same AVClass as everyone else, >> the values for fields that are AVOpt-enabled are zero; and this does not >> always coincide with the default value of the relevant option. See e.g. >> skip_iods, iods_audio_profile, iods_video_profile etc. movie_timescale >> will even be set to a value that is outside of its legal range. I don't >> know whether this can lead to any divide-by-zero crashes or asserts, but >> it is certainly very fragile. >> > > Hmm, i see a couple of solutions here: > 1) Use the same private class as the MOV muxer and document that not > all options are supported when the format is AVIF. I think there are > already cases within this file where not all muxers may support all > the options. > 2) Leave it as-is since the code always checks for AVIF mode first and > then does the rest, so the defaults of the non-relevant options should > not matter. But i agree that this is very fragile. One way to ensure > future changes don't break this structure is to add a fate test. > Can you tell which options (if enabled) would work and which would not work or would create spec-incompliant output? > Please let me know what you think. > > About using a separate write_trailer function, i can re-use the > existing mov_write_trailer but it will mostly be a special case for > AVIF mode with the code in avif_write_trailer, so i thought it was > cleaner to have a separate function anyway. I am not sure if there are > any other implications because of this. If you prefer that i move it > into mov_write_trailer, please let me know and i can do that as well. > _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-13 21:04 ` Andreas Rheinhardt @ 2022-04-13 21:35 ` Vignesh Venkatasubramanian 2022-04-21 16:38 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-13 21:35 UTC (permalink / raw) To: FFmpeg development discussions and patches On Wed, Apr 13, 2022 at 2:04 PM Andreas Rheinhardt <andreas.rheinhardt@outlook.com> wrote: > > Vignesh Venkatasubramanian: > > On Mon, Mar 21, 2022 at 1:46 PM Andreas Rheinhardt > > <andreas.rheinhardt@outlook.com> wrote: > >> > >> Vignesh Venkatasubramanian: > >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > >>> > >>> AVIF Specifiation: https://aomediacodec.github.io/av1-avif > >>> > >>> Sample usage for still image: > >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > >>> > >>> Sample usage for animated AVIF image: > >>> ffmpeg -i video.mp4 animated.avif > >>> > >>> We can re-use any of the AV1 encoding options that will make > >>> sense for image encoding (like bitrate, tiles, encoding speed, > >>> etc). > >>> > >>> The files generated by this muxer has been verified to be valid > >>> AVIF files by the following: > >>> 1) Displays on Chrome (both still and animated images). > >>> 2) Displays on Firefox (only still images, firefox does not support > >>> animated AVIF yet). > >>> 3) Verfied to be valid by Compliance Warden: > >>> https://github.com/gpac/ComplianceWarden > >>> > >>> Fixes the encoder/muxer part of Trac Ticket #7621 > >>> > >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > >>> --- > >>> configure | 1 + > >>> libavformat/allformats.c | 1 + > >>> libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- > >>> libavformat/movenc.h | 5 + > >>> 4 files changed, 322 insertions(+), 26 deletions(-) > >>> > >>> diff --git a/configure b/configure > >>> index 8c69ab0c86..6d7020e96b 100755 > >>> --- a/configure > >>> +++ b/configure > >>> @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > >>> av1_demuxer_select="av1_frame_merge_bsf av1_parser" > >>> avi_demuxer_select="riffdec exif" > >>> avi_muxer_select="riffenc" > >>> +avif_muxer_select="mov_muxer" > >>> caf_demuxer_select="iso_media" > >>> caf_muxer_select="iso_media" > >>> dash_muxer_select="mp4_muxer" > >>> diff --git a/libavformat/allformats.c b/libavformat/allformats.c > >>> index d066a7745b..400c17afbd 100644 > >>> --- a/libavformat/allformats.c > >>> +++ b/libavformat/allformats.c > >>> @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > >>> extern const AVInputFormat ff_av1_demuxer; > >>> extern const AVInputFormat ff_avi_demuxer; > >>> extern const AVOutputFormat ff_avi_muxer; > >>> +extern const AVOutputFormat ff_avif_muxer; > >>> extern const AVInputFormat ff_avisynth_demuxer; > >>> extern const AVOutputFormat ff_avm2_muxer; > >>> extern const AVInputFormat ff_avr_demuxer; > >>> diff --git a/libavformat/movenc.c b/libavformat/movenc.c > >>> index 1a746a67fd..ff41579300 100644 > >>> --- a/libavformat/movenc.c > >>> +++ b/libavformat/movenc.c > >>> @@ -1303,7 +1303,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); > >>> } > >>> > >>> @@ -2004,12 +2004,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"); > >>> @@ -2019,7 +2020,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); > >>> } > >>> @@ -2085,7 +2086,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) { > >>> @@ -2106,6 +2107,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; > >>> @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > >>> avio_wb32(pb, 0); /* size */ > >>> if (mov->encryption_scheme != MOV_ENC_NONE) { > >>> ffio_wfourcc(pb, "encv"); > >>> + } else if (track->mode == MODE_AVIF) { > >>> + ffio_wfourcc(pb, "av01"); > >>> } else { > >>> avio_wl32(pb, track->tag); // store it byteswapped > >>> } > >>> @@ -2239,7 +2261,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; > >>> @@ -2291,6 +2313,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); > >>> } > >>> > >>> @@ -2792,7 +2817,10 @@ 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) { > >>> + if (track->mode == MODE_AVIF) { > >>> + hdlr_type = "pict"; > >>> + descr = "ffmpeg"; > >>> + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > >>> hdlr_type = "vide"; > >>> descr = "VideoHandler"; > >>> } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > >>> @@ -2859,6 +2887,131 @@ 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); > >>> + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > >> > >> Is the number of planes really the correct number here? After all, for a > >> muxer (instead of a decoder) it does not matter whether the chroma > >> planes are interleaved like in AV_PIX_FMT_NV12 or not like in > >> AV_PIX_FMT_YUV420P. You should better use pixdesc->nb_components. > >> > > > > Done. > > > >>> + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > >>> + int i; > >> > >> We allow and use "for (int i = 0;" from C99 to save lines like these. > >> > > > > Done. > > > >>> + avio_wb32(pb, 0); /* size */ > >>> + ffio_wfourcc(pb, "pixi"); > >>> + avio_wb32(pb, 0); /* Version & flags */ > >>> + avio_w8(pb, num_channels); /* num_channels */ > >>> + for (i = 0; i < num_channels; ++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 > >>> @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > >>> display_matrix = NULL; > >>> } > >>> > >>> - if (track->flags & MOV_TRACK_ENABLED) > >>> + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > >>> flags |= MOV_TKHD_FLAG_ENABLED; > >>> > >>> if (track->mode == MODE_ISM) > >>> @@ -3104,7 +3257,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, > >>> @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > >>> mov_write_tapt_tag(pb, track); > >>> } > >>> } > >>> - mov_write_track_udta_tag(pb, mov, st); > >>> + if (track->mode != MODE_AVIF) > >>> + mov_write_track_udta_tag(pb, mov, st); > >>> track->entry = entry_backup; > >>> track->chunkCount = chunk_backup; > >>> return update_size(pb, pos); > >>> @@ -3914,8 +4068,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); > >>> @@ -4245,10 +4406,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; > >>> @@ -4259,7 +4421,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); > >>> @@ -5002,6 +5164,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; > >>> @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > >>> + // 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 > >>> @@ -5669,7 +5859,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) { > >>> @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > >>> } > >>> } > >>> } else if (par->codec_id == AV_CODEC_ID_AV1) { > >>> - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > >>> + if (trk->mode == MODE_AVIF) { > >> > >> Why this? AVIF requires that AVI-in-ISOBMFF sample format is honoured > >> and this contains e.g. "OBUs of type OBU_TEMPORAL_DELIMITER, > >> OBU_PADDING, or OBU_REDUNDANT_FRAME_HEADER SHOULD NOT be used". > >> ff_av1_filter_obus(_buf)? merely ensures that these OBUs are stripped away. > >> > >> If the aim of this check is to disallow hint tracks or so, then it fails > >> (all it does is ensuring that both the ordinary track as well as the > >> hint track get data that might contain OBUs that should not be there). > >> > > > > Done. I had to update the extent offset code here as well. > > > >>> + avio_write(pb, pkt->data, pkt->size); > >>> + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > >>> ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > >>> &size, &offset); > >>> if (ret < 0) > >>> @@ -6230,6 +6422,10 @@ fail: > >>> } > >>> } > >>> > >>> + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > >>> + mov->avif_extent_length = pkt->size; > >>> + } > >>> + > >>> return mov_write_single_packet(s, pkt); > >>> } > >>> } > >>> @@ -6569,11 +6765,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 || > >>> @@ -6654,11 +6854,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); > >>> + } > >>> + } > >>> + > >>> + > >>> mov->nb_streams = s->nb_streams; > >>> if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > >>> mov->chapter_track = mov->nb_streams++; > >>> @@ -6797,12 +7011,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 */ > >>> @@ -7003,7 +7218,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); > >>> @@ -7291,6 +7506,48 @@ 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; > >>> + int i; > >>> + > >>> + 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); > >>> + for (i = 0; i < mov->nb_streams; i++) > >>> + mov->tracks[i].data_offset = avio_tell(pb) + moov_size + 8; > >> > >> Don't call avio_tell() in a loop (and I wonder whether this loop is even > >> necessary given that s->nb_streams is checked to be one). > >> > > > > Removed the loop. I was thinking about potentially supporting multiple > > output tracks in the future (for alpha channel for example), but this > > code would not work in that case any way. > > > >>> + > >>> + 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); > >>> + > >>> + avio_write(pb, buf, buf_size); > >>> + ffio_free_dyn_buf(&mov->mdat_buf); > >> > >> Unnecessary: This will be freed in mov_free(). > >> > > > > Removed. > > > >>> + > >>> + // write extent offset. > >>> + pos_backup = avio_tell(pb); > >>> + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); > >>> + avio_wb32(pb, mdat_pos); /* rewrite offset */ > >> > >> What guarantees that this fits into 32bits? > >> > > > > Added a check to fail if it does not fit in 32-bits. > > > >>> + 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') }, > >>> @@ -7373,6 +7630,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, > >>> +}; > >> > >> It's not mandatory for a muxer to have a private class; it is only > >> necessary for options. If you do not have options (like here), then the > >> AVClass is useless. > >> I actually wanted that you support the options that make sense for AVIF > >> and I dislike that the avif muxer uses a different write_trailer than > >> every other muxer here (which is the reason why e.g. the faststart > >> option is not supported by it). Is it really so different? > >> Furthermore, given that you don't use the same AVClass as everyone else, > >> the values for fields that are AVOpt-enabled are zero; and this does not > >> always coincide with the default value of the relevant option. See e.g. > >> skip_iods, iods_audio_profile, iods_video_profile etc. movie_timescale > >> will even be set to a value that is outside of its legal range. I don't > >> know whether this can lead to any divide-by-zero crashes or asserts, but > >> it is certainly very fragile. > >> > > > > Hmm, i see a couple of solutions here: > > 1) Use the same private class as the MOV muxer and document that not > > all options are supported when the format is AVIF. I think there are > > already cases within this file where not all muxers may support all > > the options. > > 2) Leave it as-is since the code always checks for AVIF mode first and > > then does the rest, so the defaults of the non-relevant options should > > not matter. But i agree that this is very fragile. One way to ensure > > future changes don't break this structure is to add a fate test. > > > > Can you tell which options (if enabled) would work and which would not > work or would create spec-incompliant output? > The following options will make sense for AVIF (and may just work as-is, i am not sure of this): write_colr, prefer_icc, video_track_timescale and a few other generic MOV options like use_stream_ids_as_track_ids, empty_hdlr_name, and brand. > > Please let me know what you think. > > > > About using a separate write_trailer function, i can re-use the > > existing mov_write_trailer but it will mostly be a special case for > > AVIF mode with the code in avif_write_trailer, so i thought it was > > cleaner to have a separate function anyway. I am not sure if there are > > any other implications because of this. If you prefer that i move it > > into mov_write_trailer, please let me know and i can do that as well. > > > > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-13 21:35 ` Vignesh Venkatasubramanian @ 2022-04-21 16:38 ` Vignesh Venkatasubramanian 2022-04-29 16:03 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-21 16:38 UTC (permalink / raw) To: FFmpeg development discussions and patches On Wed, Apr 13, 2022 at 2:35 PM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > On Wed, Apr 13, 2022 at 2:04 PM Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> wrote: > > > > Vignesh Venkatasubramanian: > > > On Mon, Mar 21, 2022 at 1:46 PM Andreas Rheinhardt > > > <andreas.rheinhardt@outlook.com> wrote: > > >> > > >> Vignesh Venkatasubramanian: > > >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > >>> > > >>> AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > >>> > > >>> Sample usage for still image: > > >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > >>> > > >>> Sample usage for animated AVIF image: > > >>> ffmpeg -i video.mp4 animated.avif > > >>> > > >>> We can re-use any of the AV1 encoding options that will make > > >>> sense for image encoding (like bitrate, tiles, encoding speed, > > >>> etc). > > >>> > > >>> The files generated by this muxer has been verified to be valid > > >>> AVIF files by the following: > > >>> 1) Displays on Chrome (both still and animated images). > > >>> 2) Displays on Firefox (only still images, firefox does not support > > >>> animated AVIF yet). > > >>> 3) Verfied to be valid by Compliance Warden: > > >>> https://github.com/gpac/ComplianceWarden > > >>> > > >>> Fixes the encoder/muxer part of Trac Ticket #7621 > > >>> > > >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > >>> --- > > >>> configure | 1 + > > >>> libavformat/allformats.c | 1 + > > >>> libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- > > >>> libavformat/movenc.h | 5 + > > >>> 4 files changed, 322 insertions(+), 26 deletions(-) > > >>> > > >>> diff --git a/configure b/configure > > >>> index 8c69ab0c86..6d7020e96b 100755 > > >>> --- a/configure > > >>> +++ b/configure > > >>> @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > >>> av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > >>> avi_demuxer_select="riffdec exif" > > >>> avi_muxer_select="riffenc" > > >>> +avif_muxer_select="mov_muxer" > > >>> caf_demuxer_select="iso_media" > > >>> caf_muxer_select="iso_media" > > >>> dash_muxer_select="mp4_muxer" > > >>> diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > >>> index d066a7745b..400c17afbd 100644 > > >>> --- a/libavformat/allformats.c > > >>> +++ b/libavformat/allformats.c > > >>> @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > >>> extern const AVInputFormat ff_av1_demuxer; > > >>> extern const AVInputFormat ff_avi_demuxer; > > >>> extern const AVOutputFormat ff_avi_muxer; > > >>> +extern const AVOutputFormat ff_avif_muxer; > > >>> extern const AVInputFormat ff_avisynth_demuxer; > > >>> extern const AVOutputFormat ff_avm2_muxer; > > >>> extern const AVInputFormat ff_avr_demuxer; > > >>> diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > >>> index 1a746a67fd..ff41579300 100644 > > >>> --- a/libavformat/movenc.c > > >>> +++ b/libavformat/movenc.c > > >>> @@ -1303,7 +1303,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); > > >>> } > > >>> > > >>> @@ -2004,12 +2004,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"); > > >>> @@ -2019,7 +2020,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); > > >>> } > > >>> @@ -2085,7 +2086,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) { > > >>> @@ -2106,6 +2107,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; > > >>> @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > >>> avio_wb32(pb, 0); /* size */ > > >>> if (mov->encryption_scheme != MOV_ENC_NONE) { > > >>> ffio_wfourcc(pb, "encv"); > > >>> + } else if (track->mode == MODE_AVIF) { > > >>> + ffio_wfourcc(pb, "av01"); > > >>> } else { > > >>> avio_wl32(pb, track->tag); // store it byteswapped > > >>> } > > >>> @@ -2239,7 +2261,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; > > >>> @@ -2291,6 +2313,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); > > >>> } > > >>> > > >>> @@ -2792,7 +2817,10 @@ 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) { > > >>> + if (track->mode == MODE_AVIF) { > > >>> + hdlr_type = "pict"; > > >>> + descr = "ffmpeg"; > > >>> + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > >>> hdlr_type = "vide"; > > >>> descr = "VideoHandler"; > > >>> } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > >>> @@ -2859,6 +2887,131 @@ 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); > > >>> + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > >> > > >> Is the number of planes really the correct number here? After all, for a > > >> muxer (instead of a decoder) it does not matter whether the chroma > > >> planes are interleaved like in AV_PIX_FMT_NV12 or not like in > > >> AV_PIX_FMT_YUV420P. You should better use pixdesc->nb_components. > > >> > > > > > > Done. > > > > > >>> + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > >>> + int i; > > >> > > >> We allow and use "for (int i = 0;" from C99 to save lines like these. > > >> > > > > > > Done. > > > > > >>> + avio_wb32(pb, 0); /* size */ > > >>> + ffio_wfourcc(pb, "pixi"); > > >>> + avio_wb32(pb, 0); /* Version & flags */ > > >>> + avio_w8(pb, num_channels); /* num_channels */ > > >>> + for (i = 0; i < num_channels; ++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 > > >>> @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > >>> display_matrix = NULL; > > >>> } > > >>> > > >>> - if (track->flags & MOV_TRACK_ENABLED) > > >>> + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > >>> flags |= MOV_TKHD_FLAG_ENABLED; > > >>> > > >>> if (track->mode == MODE_ISM) > > >>> @@ -3104,7 +3257,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, > > >>> @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > >>> mov_write_tapt_tag(pb, track); > > >>> } > > >>> } > > >>> - mov_write_track_udta_tag(pb, mov, st); > > >>> + if (track->mode != MODE_AVIF) > > >>> + mov_write_track_udta_tag(pb, mov, st); > > >>> track->entry = entry_backup; > > >>> track->chunkCount = chunk_backup; > > >>> return update_size(pb, pos); > > >>> @@ -3914,8 +4068,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); > > >>> @@ -4245,10 +4406,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; > > >>> @@ -4259,7 +4421,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); > > >>> @@ -5002,6 +5164,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; > > >>> @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > >>> + // 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 > > >>> @@ -5669,7 +5859,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) { > > >>> @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > >>> } > > >>> } > > >>> } else if (par->codec_id == AV_CODEC_ID_AV1) { > > >>> - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > >>> + if (trk->mode == MODE_AVIF) { > > >> > > >> Why this? AVIF requires that AVI-in-ISOBMFF sample format is honoured > > >> and this contains e.g. "OBUs of type OBU_TEMPORAL_DELIMITER, > > >> OBU_PADDING, or OBU_REDUNDANT_FRAME_HEADER SHOULD NOT be used". > > >> ff_av1_filter_obus(_buf)? merely ensures that these OBUs are stripped away. > > >> > > >> If the aim of this check is to disallow hint tracks or so, then it fails > > >> (all it does is ensuring that both the ordinary track as well as the > > >> hint track get data that might contain OBUs that should not be there). > > >> > > > > > > Done. I had to update the extent offset code here as well. > > > > > >>> + avio_write(pb, pkt->data, pkt->size); > > >>> + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > >>> ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > >>> &size, &offset); > > >>> if (ret < 0) > > >>> @@ -6230,6 +6422,10 @@ fail: > > >>> } > > >>> } > > >>> > > >>> + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > >>> + mov->avif_extent_length = pkt->size; > > >>> + } > > >>> + > > >>> return mov_write_single_packet(s, pkt); > > >>> } > > >>> } > > >>> @@ -6569,11 +6765,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 || > > >>> @@ -6654,11 +6854,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); > > >>> + } > > >>> + } > > >>> + > > >>> + > > >>> mov->nb_streams = s->nb_streams; > > >>> if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > > >>> mov->chapter_track = mov->nb_streams++; > > >>> @@ -6797,12 +7011,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 */ > > >>> @@ -7003,7 +7218,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); > > >>> @@ -7291,6 +7506,48 @@ 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; > > >>> + int i; > > >>> + > > >>> + 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); > > >>> + for (i = 0; i < mov->nb_streams; i++) > > >>> + mov->tracks[i].data_offset = avio_tell(pb) + moov_size + 8; > > >> > > >> Don't call avio_tell() in a loop (and I wonder whether this loop is even > > >> necessary given that s->nb_streams is checked to be one). > > >> > > > > > > Removed the loop. I was thinking about potentially supporting multiple > > > output tracks in the future (for alpha channel for example), but this > > > code would not work in that case any way. > > > > > >>> + > > >>> + 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); > > >>> + > > >>> + avio_write(pb, buf, buf_size); > > >>> + ffio_free_dyn_buf(&mov->mdat_buf); > > >> > > >> Unnecessary: This will be freed in mov_free(). > > >> > > > > > > Removed. > > > > > >>> + > > >>> + // write extent offset. > > >>> + pos_backup = avio_tell(pb); > > >>> + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); > > >>> + avio_wb32(pb, mdat_pos); /* rewrite offset */ > > >> > > >> What guarantees that this fits into 32bits? > > >> > > > > > > Added a check to fail if it does not fit in 32-bits. > > > > > >>> + 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') }, > > >>> @@ -7373,6 +7630,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, > > >>> +}; > > >> > > >> It's not mandatory for a muxer to have a private class; it is only > > >> necessary for options. If you do not have options (like here), then the > > >> AVClass is useless. > > >> I actually wanted that you support the options that make sense for AVIF > > >> and I dislike that the avif muxer uses a different write_trailer than > > >> every other muxer here (which is the reason why e.g. the faststart > > >> option is not supported by it). Is it really so different? > > >> Furthermore, given that you don't use the same AVClass as everyone else, > > >> the values for fields that are AVOpt-enabled are zero; and this does not > > >> always coincide with the default value of the relevant option. See e.g. > > >> skip_iods, iods_audio_profile, iods_video_profile etc. movie_timescale > > >> will even be set to a value that is outside of its legal range. I don't > > >> know whether this can lead to any divide-by-zero crashes or asserts, but > > >> it is certainly very fragile. > > >> > > > > > > Hmm, i see a couple of solutions here: > > > 1) Use the same private class as the MOV muxer and document that not > > > all options are supported when the format is AVIF. I think there are > > > already cases within this file where not all muxers may support all > > > the options. > > > 2) Leave it as-is since the code always checks for AVIF mode first and > > > then does the rest, so the defaults of the non-relevant options should > > > not matter. But i agree that this is very fragile. One way to ensure > > > future changes don't break this structure is to add a fate test. > > > > > > > Can you tell which options (if enabled) would work and which would not > > work or would create spec-incompliant output? > > > > The following options will make sense for AVIF (and may just work > as-is, i am not sure of this): write_colr, prefer_icc, > video_track_timescale and a few other generic MOV options like > use_stream_ids_as_track_ids, empty_hdlr_name, and brand. > Any thoughts on this? > > > Please let me know what you think. > > > > > > About using a separate write_trailer function, i can re-use the > > > existing mov_write_trailer but it will mostly be a special case for > > > AVIF mode with the code in avif_write_trailer, so i thought it was > > > cleaner to have a separate function anyway. I am not sure if there are > > > any other implications because of this. If you prefer that i move it > > > into mov_write_trailer, please let me know and i can do that as well. > > > > > > > _______________________________________________ > > 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". > > > > -- > Vignesh -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-04-21 16:38 ` Vignesh Venkatasubramanian @ 2022-04-29 16:03 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-29 16:03 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, Apr 21, 2022 at 9:38 AM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > On Wed, Apr 13, 2022 at 2:35 PM Vignesh Venkatasubramanian > <vigneshv@google.com> wrote: > > > > On Wed, Apr 13, 2022 at 2:04 PM Andreas Rheinhardt > > <andreas.rheinhardt@outlook.com> wrote: > > > > > > Vignesh Venkatasubramanian: > > > > On Mon, Mar 21, 2022 at 1:46 PM Andreas Rheinhardt > > > > <andreas.rheinhardt@outlook.com> wrote: > > > >> > > > >> Vignesh Venkatasubramanian: > > > >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > >>> > > > >>> AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > >>> > > > >>> Sample usage for still image: > > > >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > >>> > > > >>> Sample usage for animated AVIF image: > > > >>> ffmpeg -i video.mp4 animated.avif > > > >>> > > > >>> We can re-use any of the AV1 encoding options that will make > > > >>> sense for image encoding (like bitrate, tiles, encoding speed, > > > >>> etc). > > > >>> > > > >>> The files generated by this muxer has been verified to be valid > > > >>> AVIF files by the following: > > > >>> 1) Displays on Chrome (both still and animated images). > > > >>> 2) Displays on Firefox (only still images, firefox does not support > > > >>> animated AVIF yet). > > > >>> 3) Verfied to be valid by Compliance Warden: > > > >>> https://github.com/gpac/ComplianceWarden > > > >>> > > > >>> Fixes the encoder/muxer part of Trac Ticket #7621 > > > >>> > > > >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > > >>> --- > > > >>> configure | 1 + > > > >>> libavformat/allformats.c | 1 + > > > >>> libavformat/movenc.c | 341 ++++++++++++++++++++++++++++++++++++--- > > > >>> libavformat/movenc.h | 5 + > > > >>> 4 files changed, 322 insertions(+), 26 deletions(-) > > > >>> > > > >>> diff --git a/configure b/configure > > > >>> index 8c69ab0c86..6d7020e96b 100755 > > > >>> --- a/configure > > > >>> +++ b/configure > > > >>> @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > > >>> av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > > >>> avi_demuxer_select="riffdec exif" > > > >>> avi_muxer_select="riffenc" > > > >>> +avif_muxer_select="mov_muxer" > > > >>> caf_demuxer_select="iso_media" > > > >>> caf_muxer_select="iso_media" > > > >>> dash_muxer_select="mp4_muxer" > > > >>> diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > > >>> index d066a7745b..400c17afbd 100644 > > > >>> --- a/libavformat/allformats.c > > > >>> +++ b/libavformat/allformats.c > > > >>> @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > > >>> extern const AVInputFormat ff_av1_demuxer; > > > >>> extern const AVInputFormat ff_avi_demuxer; > > > >>> extern const AVOutputFormat ff_avi_muxer; > > > >>> +extern const AVOutputFormat ff_avif_muxer; > > > >>> extern const AVInputFormat ff_avisynth_demuxer; > > > >>> extern const AVOutputFormat ff_avm2_muxer; > > > >>> extern const AVInputFormat ff_avr_demuxer; > > > >>> diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > > >>> index 1a746a67fd..ff41579300 100644 > > > >>> --- a/libavformat/movenc.c > > > >>> +++ b/libavformat/movenc.c > > > >>> @@ -1303,7 +1303,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); > > > >>> } > > > >>> > > > >>> @@ -2004,12 +2004,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"); > > > >>> @@ -2019,7 +2020,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); > > > >>> } > > > >>> @@ -2085,7 +2086,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) { > > > >>> @@ -2106,6 +2107,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; > > > >>> @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > > >>> avio_wb32(pb, 0); /* size */ > > > >>> if (mov->encryption_scheme != MOV_ENC_NONE) { > > > >>> ffio_wfourcc(pb, "encv"); > > > >>> + } else if (track->mode == MODE_AVIF) { > > > >>> + ffio_wfourcc(pb, "av01"); > > > >>> } else { > > > >>> avio_wl32(pb, track->tag); // store it byteswapped > > > >>> } > > > >>> @@ -2239,7 +2261,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; > > > >>> @@ -2291,6 +2313,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); > > > >>> } > > > >>> > > > >>> @@ -2792,7 +2817,10 @@ 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) { > > > >>> + if (track->mode == MODE_AVIF) { > > > >>> + hdlr_type = "pict"; > > > >>> + descr = "ffmpeg"; > > > >>> + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > > >>> hdlr_type = "vide"; > > > >>> descr = "VideoHandler"; > > > >>> } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > > >>> @@ -2859,6 +2887,131 @@ 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); > > > >>> + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > > >> > > > >> Is the number of planes really the correct number here? After all, for a > > > >> muxer (instead of a decoder) it does not matter whether the chroma > > > >> planes are interleaved like in AV_PIX_FMT_NV12 or not like in > > > >> AV_PIX_FMT_YUV420P. You should better use pixdesc->nb_components. > > > >> > > > > > > > > Done. > > > > > > > >>> + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > > >>> + int i; > > > >> > > > >> We allow and use "for (int i = 0;" from C99 to save lines like these. > > > >> > > > > > > > > Done. > > > > > > > >>> + avio_wb32(pb, 0); /* size */ > > > >>> + ffio_wfourcc(pb, "pixi"); > > > >>> + avio_wb32(pb, 0); /* Version & flags */ > > > >>> + avio_w8(pb, num_channels); /* num_channels */ > > > >>> + for (i = 0; i < num_channels; ++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 > > > >>> @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > > >>> display_matrix = NULL; > > > >>> } > > > >>> > > > >>> - if (track->flags & MOV_TRACK_ENABLED) > > > >>> + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > > >>> flags |= MOV_TKHD_FLAG_ENABLED; > > > >>> > > > >>> if (track->mode == MODE_ISM) > > > >>> @@ -3104,7 +3257,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, > > > >>> @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > > >>> mov_write_tapt_tag(pb, track); > > > >>> } > > > >>> } > > > >>> - mov_write_track_udta_tag(pb, mov, st); > > > >>> + if (track->mode != MODE_AVIF) > > > >>> + mov_write_track_udta_tag(pb, mov, st); > > > >>> track->entry = entry_backup; > > > >>> track->chunkCount = chunk_backup; > > > >>> return update_size(pb, pos); > > > >>> @@ -3914,8 +4068,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); > > > >>> @@ -4245,10 +4406,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; > > > >>> @@ -4259,7 +4421,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); > > > >>> @@ -5002,6 +5164,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; > > > >>> @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > > >>> + // 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 > > > >>> @@ -5669,7 +5859,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) { > > > >>> @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > > >>> } > > > >>> } > > > >>> } else if (par->codec_id == AV_CODEC_ID_AV1) { > > > >>> - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > >>> + if (trk->mode == MODE_AVIF) { > > > >> > > > >> Why this? AVIF requires that AVI-in-ISOBMFF sample format is honoured > > > >> and this contains e.g. "OBUs of type OBU_TEMPORAL_DELIMITER, > > > >> OBU_PADDING, or OBU_REDUNDANT_FRAME_HEADER SHOULD NOT be used". > > > >> ff_av1_filter_obus(_buf)? merely ensures that these OBUs are stripped away. > > > >> > > > >> If the aim of this check is to disallow hint tracks or so, then it fails > > > >> (all it does is ensuring that both the ordinary track as well as the > > > >> hint track get data that might contain OBUs that should not be there). > > > >> > > > > > > > > Done. I had to update the extent offset code here as well. > > > > > > > >>> + avio_write(pb, pkt->data, pkt->size); > > > >>> + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > >>> ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > > >>> &size, &offset); > > > >>> if (ret < 0) > > > >>> @@ -6230,6 +6422,10 @@ fail: > > > >>> } > > > >>> } > > > >>> > > > >>> + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > > >>> + mov->avif_extent_length = pkt->size; > > > >>> + } > > > >>> + > > > >>> return mov_write_single_packet(s, pkt); > > > >>> } > > > >>> } > > > >>> @@ -6569,11 +6765,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 || > > > >>> @@ -6654,11 +6854,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); > > > >>> + } > > > >>> + } > > > >>> + > > > >>> + > > > >>> mov->nb_streams = s->nb_streams; > > > >>> if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) > > > >>> mov->chapter_track = mov->nb_streams++; > > > >>> @@ -6797,12 +7011,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 */ > > > >>> @@ -7003,7 +7218,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); > > > >>> @@ -7291,6 +7506,48 @@ 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; > > > >>> + int i; > > > >>> + > > > >>> + 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); > > > >>> + for (i = 0; i < mov->nb_streams; i++) > > > >>> + mov->tracks[i].data_offset = avio_tell(pb) + moov_size + 8; > > > >> > > > >> Don't call avio_tell() in a loop (and I wonder whether this loop is even > > > >> necessary given that s->nb_streams is checked to be one). > > > >> > > > > > > > > Removed the loop. I was thinking about potentially supporting multiple > > > > output tracks in the future (for alpha channel for example), but this > > > > code would not work in that case any way. > > > > > > > >>> + > > > >>> + 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); > > > >>> + > > > >>> + avio_write(pb, buf, buf_size); > > > >>> + ffio_free_dyn_buf(&mov->mdat_buf); > > > >> > > > >> Unnecessary: This will be freed in mov_free(). > > > >> > > > > > > > > Removed. > > > > > > > >>> + > > > >>> + // write extent offset. > > > >>> + pos_backup = avio_tell(pb); > > > >>> + avio_seek(pb, mov->avif_extent_pos, SEEK_SET); > > > >>> + avio_wb32(pb, mdat_pos); /* rewrite offset */ > > > >> > > > >> What guarantees that this fits into 32bits? > > > >> > > > > > > > > Added a check to fail if it does not fit in 32-bits. > > > > > > > >>> + 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') }, > > > >>> @@ -7373,6 +7630,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, > > > >>> +}; > > > >> > > > >> It's not mandatory for a muxer to have a private class; it is only > > > >> necessary for options. If you do not have options (like here), then the > > > >> AVClass is useless. > > > >> I actually wanted that you support the options that make sense for AVIF > > > >> and I dislike that the avif muxer uses a different write_trailer than > > > >> every other muxer here (which is the reason why e.g. the faststart > > > >> option is not supported by it). Is it really so different? > > > >> Furthermore, given that you don't use the same AVClass as everyone else, > > > >> the values for fields that are AVOpt-enabled are zero; and this does not > > > >> always coincide with the default value of the relevant option. See e.g. > > > >> skip_iods, iods_audio_profile, iods_video_profile etc. movie_timescale > > > >> will even be set to a value that is outside of its legal range. I don't > > > >> know whether this can lead to any divide-by-zero crashes or asserts, but > > > >> it is certainly very fragile. > > > >> > > > > > > > > Hmm, i see a couple of solutions here: > > > > 1) Use the same private class as the MOV muxer and document that not > > > > all options are supported when the format is AVIF. I think there are > > > > already cases within this file where not all muxers may support all > > > > the options. > > > > 2) Leave it as-is since the code always checks for AVIF mode first and > > > > then does the rest, so the defaults of the non-relevant options should > > > > not matter. But i agree that this is very fragile. One way to ensure > > > > future changes don't break this structure is to add a fate test. > > > > > > > > > > Can you tell which options (if enabled) would work and which would not > > > work or would create spec-incompliant output? > > > > > > > The following options will make sense for AVIF (and may just work > > as-is, i am not sure of this): write_colr, prefer_icc, > > video_track_timescale and a few other generic MOV options like > > use_stream_ids_as_track_ids, empty_hdlr_name, and brand. > > > > Any thoughts on this? > Another ping on this. :) > > > > Please let me know what you think. > > > > > > > > About using a separate write_trailer function, i can re-use the > > > > existing mov_write_trailer but it will mostly be a special case for > > > > AVIF mode with the code in avif_write_trailer, so i thought it was > > > > cleaner to have a separate function anyway. I am not sure if there are > > > > any other implications because of this. If you prefer that i move it > > > > into mov_write_trailer, please let me know and i can do that as well. > > > > > > > > > > _______________________________________________ > > > 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". > > > > > > > > -- > > Vignesh > > > > -- > Vignesh -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-10 16:01 ` Andreas Rheinhardt 2022-03-10 18:12 ` Vignesh Venkatasubramanian @ 2022-03-10 18:14 ` Vignesh Venkatasubramanian 2022-03-15 15:59 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-10 18:14 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, Mar 10, 2022 at 8:01 AM Andreas Rheinhardt <andreas.rheinhardt@outlook.com> wrote: > > Vignesh Venkatasubramanian: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verfied to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- > > libavformat/movenc.h | 5 + > > 4 files changed, 305 insertions(+), 25 deletions(-) > > > > diff --git a/configure b/configure > > index 8c69ab0c86..6d7020e96b 100755 > > --- a/configure > > +++ b/configure > > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > avi_demuxer_select="riffdec exif" > > avi_muxer_select="riffenc" > > +avif_muxer_select="mov_muxer" > > caf_demuxer_select="iso_media" > > caf_muxer_select="iso_media" > > dash_muxer_select="mp4_muxer" > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > index d066a7745b..400c17afbd 100644 > > --- a/libavformat/allformats.c > > +++ b/libavformat/allformats.c > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > extern const AVInputFormat ff_av1_demuxer; > > extern const AVInputFormat ff_avi_demuxer; > > extern const AVOutputFormat ff_avi_muxer; > > +extern const AVOutputFormat ff_avif_muxer; > > extern const AVInputFormat ff_avisynth_demuxer; > > extern const AVOutputFormat ff_avm2_muxer; > > extern const AVInputFormat ff_avr_demuxer; > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > index 1a746a67fd..504403ab0b 100644 > > --- a/libavformat/movenc.c > > +++ b/libavformat/movenc.c > > @@ -1303,7 +1303,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); > > } > > > > @@ -2004,12 +2004,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"); > > @@ -2019,7 +2020,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); > > } > > @@ -2085,7 +2086,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) { > > @@ -2106,6 +2107,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; > > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > avio_wb32(pb, 0); /* size */ > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > ffio_wfourcc(pb, "encv"); > > + } else if (track->mode == MODE_AVIF) { > > + ffio_wfourcc(pb, "av01"); > > } else { > > avio_wl32(pb, track->tag); // store it byteswapped > > } > > @@ -2239,7 +2261,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; > > @@ -2291,6 +2313,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); > > } > > > > @@ -2792,7 +2817,10 @@ 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) { > > + if (track->mode == MODE_AVIF) { > > + hdlr_type = "pict"; > > + descr = "ffmpeg"; > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > hdlr_type = "vide"; > > descr = "VideoHandler"; > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > @@ -2859,6 +2887,131 @@ 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); > > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > + int i; > > + avio_wb32(pb, 0); /* size */ > > + ffio_wfourcc(pb, "pixi"); > > + avio_wb32(pb, 0); /* Version & flags */ > > + avio_w8(pb, num_channels); /* num_channels */ > > + for (i = 0; i < num_channels; ++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 > > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > display_matrix = NULL; > > } > > > > - if (track->flags & MOV_TRACK_ENABLED) > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > flags |= MOV_TKHD_FLAG_ENABLED; > > > > if (track->mode == MODE_ISM) > > @@ -3104,7 +3257,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, > > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > mov_write_tapt_tag(pb, track); > > } > > } > > - mov_write_track_udta_tag(pb, mov, st); > > + if (track->mode != MODE_AVIF) > > + mov_write_track_udta_tag(pb, mov, st); > > track->entry = entry_backup; > > track->chunkCount = chunk_backup; > > return update_size(pb, pos); > > @@ -3914,8 +4068,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. > > Various parts of this patch seem to presume this (they always use the > first stream), yet I fail to see what ensures this. > It does not make sense to have an AVIF output with more than one (video) stream. I have added a check in mov_init to ensure that there is only one video track to be output when format is 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); > > @@ -4245,10 +4406,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; > > @@ -4259,7 +4421,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); > > @@ -5002,6 +5164,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; > > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > + // 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 > > @@ -5669,7 +5859,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) { > > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > } > > } > > } else if (par->codec_id == AV_CODEC_ID_AV1) { > > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > + if (trk->mode == MODE_AVIF) { > > + avio_write(pb, pkt->data, pkt->size); > > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > &size, &offset); > > if (ret < 0) > > @@ -6230,6 +6422,10 @@ fail: > > } > > } > > > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > + mov->avif_extent_length = pkt->size; > > + } > > + > > return mov_write_single_packet(s, pkt); > > } > > } > > @@ -6569,11 +6765,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 || > > @@ -6797,12 +6997,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 */ > > @@ -7003,7 +7204,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); > > @@ -7291,6 +7492,54 @@ 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; > > + int i; > > + > > + 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); > > + for (i = 0; i < mov->nb_streams; i++) > > + mov->tracks[i].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); > > + > > + avio_write(pb, buf, buf_size); > > + ffio_free_dyn_buf(&mov->mdat_buf); > > + > > + // 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); > > + > > + mov->moov_written = 1; > > + mov->mdat_size = 0; > > + for (i = 0; i < mov->nb_streams; i++) { > > + mov->tracks[i].entry = 0; > > + mov->tracks[i].end_reliable = 0; > > + } > > Why this? write_trailer is only called once. > Removed. > > + return 0; > > +}> + > > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > > static const AVCodecTag codec_3gp_tags[] = { > > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > > @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { > > { AV_CODEC_ID_NONE, 0 }, > > }; > > > > +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 }; > > Why is this not under #if CONFIG_AVIF_MUXER (or part of the other #if > CONFIG_AVIF_MUXER below)? > Done. I added the #if here to keep all the muxers and tags grouped together as before. > > + > > #if CONFIG_MOV_MUXER > > const AVOutputFormat ff_mov_muxer = { > > .name = "mov", > > @@ -7535,3 +7790,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_isobmff_muxer_class, > > This gives this muxer all the options of the other muxers; yet which one > of these are actually supported? E.g. is faststart supported? The code > for it is in mov_write_trailer() and that is not called. > I see. Yeah most of the options may not make sense for AVIF. I was hoping to re-use some of the options as-is for things like ICC profile in the future. I have added a separate class for AVIF with no options for now. We can copy over some of the options when they are supported in AVIF mode. > > +}; > > +#endif > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > index 2ac84ed070..55b8469f68 100644 > > --- a/libavformat/movenc.h > > +++ b/libavformat/movenc.h > > @@ -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; > > @@ -242,6 +243,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) > > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-10 18:14 ` Vignesh Venkatasubramanian @ 2022-03-15 15:59 ` Vignesh Venkatasubramanian 2022-03-21 17:07 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-15 15:59 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, Mar 10, 2022 at 10:14 AM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > On Thu, Mar 10, 2022 at 8:01 AM Andreas Rheinhardt > <andreas.rheinhardt@outlook.com> wrote: > > > > Vignesh Venkatasubramanian: > > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > > > Sample usage for still image: > > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > > > Sample usage for animated AVIF image: > > > ffmpeg -i video.mp4 animated.avif > > > > > > We can re-use any of the AV1 encoding options that will make > > > sense for image encoding (like bitrate, tiles, encoding speed, > > > etc). > > > > > > The files generated by this muxer has been verified to be valid > > > AVIF files by the following: > > > 1) Displays on Chrome (both still and animated images). > > > 2) Displays on Firefox (only still images, firefox does not support > > > animated AVIF yet). > > > 3) Verfied to be valid by Compliance Warden: > > > https://github.com/gpac/ComplianceWarden > > > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > > --- > > > configure | 1 + > > > libavformat/allformats.c | 1 + > > > libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- > > > libavformat/movenc.h | 5 + > > > 4 files changed, 305 insertions(+), 25 deletions(-) > > > > > > diff --git a/configure b/configure > > > index 8c69ab0c86..6d7020e96b 100755 > > > --- a/configure > > > +++ b/configure > > > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > > avi_demuxer_select="riffdec exif" > > > avi_muxer_select="riffenc" > > > +avif_muxer_select="mov_muxer" > > > caf_demuxer_select="iso_media" > > > caf_muxer_select="iso_media" > > > dash_muxer_select="mp4_muxer" > > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > > index d066a7745b..400c17afbd 100644 > > > --- a/libavformat/allformats.c > > > +++ b/libavformat/allformats.c > > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > > extern const AVInputFormat ff_av1_demuxer; > > > extern const AVInputFormat ff_avi_demuxer; > > > extern const AVOutputFormat ff_avi_muxer; > > > +extern const AVOutputFormat ff_avif_muxer; > > > extern const AVInputFormat ff_avisynth_demuxer; > > > extern const AVOutputFormat ff_avm2_muxer; > > > extern const AVInputFormat ff_avr_demuxer; > > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > > index 1a746a67fd..504403ab0b 100644 > > > --- a/libavformat/movenc.c > > > +++ b/libavformat/movenc.c > > > @@ -1303,7 +1303,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); > > > } > > > > > > @@ -2004,12 +2004,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"); > > > @@ -2019,7 +2020,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); > > > } > > > @@ -2085,7 +2086,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) { > > > @@ -2106,6 +2107,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; > > > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > > avio_wb32(pb, 0); /* size */ > > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > > ffio_wfourcc(pb, "encv"); > > > + } else if (track->mode == MODE_AVIF) { > > > + ffio_wfourcc(pb, "av01"); > > > } else { > > > avio_wl32(pb, track->tag); // store it byteswapped > > > } > > > @@ -2239,7 +2261,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; > > > @@ -2291,6 +2313,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); > > > } > > > > > > @@ -2792,7 +2817,10 @@ 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) { > > > + if (track->mode == MODE_AVIF) { > > > + hdlr_type = "pict"; > > > + descr = "ffmpeg"; > > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > > hdlr_type = "vide"; > > > descr = "VideoHandler"; > > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > > @@ -2859,6 +2887,131 @@ 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); > > > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > > + int i; > > > + avio_wb32(pb, 0); /* size */ > > > + ffio_wfourcc(pb, "pixi"); > > > + avio_wb32(pb, 0); /* Version & flags */ > > > + avio_w8(pb, num_channels); /* num_channels */ > > > + for (i = 0; i < num_channels; ++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 > > > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > > display_matrix = NULL; > > > } > > > > > > - if (track->flags & MOV_TRACK_ENABLED) > > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > > flags |= MOV_TKHD_FLAG_ENABLED; > > > > > > if (track->mode == MODE_ISM) > > > @@ -3104,7 +3257,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, > > > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > > mov_write_tapt_tag(pb, track); > > > } > > > } > > > - mov_write_track_udta_tag(pb, mov, st); > > > + if (track->mode != MODE_AVIF) > > > + mov_write_track_udta_tag(pb, mov, st); > > > track->entry = entry_backup; > > > track->chunkCount = chunk_backup; > > > return update_size(pb, pos); > > > @@ -3914,8 +4068,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. > > > > Various parts of this patch seem to presume this (they always use the > > first stream), yet I fail to see what ensures this. > > > > It does not make sense to have an AVIF output with more than one > (video) stream. I have added a check in mov_init to ensure that there > is only one video track to be output when format is 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); > > > @@ -4245,10 +4406,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; > > > @@ -4259,7 +4421,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); > > > @@ -5002,6 +5164,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; > > > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > > + // 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 > > > @@ -5669,7 +5859,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) { > > > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > > } > > > } > > > } else if (par->codec_id == AV_CODEC_ID_AV1) { > > > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > + if (trk->mode == MODE_AVIF) { > > > + avio_write(pb, pkt->data, pkt->size); > > > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > > &size, &offset); > > > if (ret < 0) > > > @@ -6230,6 +6422,10 @@ fail: > > > } > > > } > > > > > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > > + mov->avif_extent_length = pkt->size; > > > + } > > > + > > > return mov_write_single_packet(s, pkt); > > > } > > > } > > > @@ -6569,11 +6765,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 || > > > @@ -6797,12 +6997,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 */ > > > @@ -7003,7 +7204,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); > > > @@ -7291,6 +7492,54 @@ 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; > > > + int i; > > > + > > > + 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); > > > + for (i = 0; i < mov->nb_streams; i++) > > > + mov->tracks[i].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); > > > + > > > + avio_write(pb, buf, buf_size); > > > + ffio_free_dyn_buf(&mov->mdat_buf); > > > + > > > + // 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); > > > + > > > + mov->moov_written = 1; > > > + mov->mdat_size = 0; > > > + for (i = 0; i < mov->nb_streams; i++) { > > > + mov->tracks[i].entry = 0; > > > + mov->tracks[i].end_reliable = 0; > > > + } > > > > Why this? write_trailer is only called once. > > > > Removed. > > > > + return 0; > > > +}> + > > > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > > > static const AVCodecTag codec_3gp_tags[] = { > > > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > > > @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { > > > { AV_CODEC_ID_NONE, 0 }, > > > }; > > > > > > +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 }; > > > > Why is this not under #if CONFIG_AVIF_MUXER (or part of the other #if > > CONFIG_AVIF_MUXER below)? > > > > Done. I added the #if here to keep all the muxers and tags grouped > together as before. > > > > + > > > #if CONFIG_MOV_MUXER > > > const AVOutputFormat ff_mov_muxer = { > > > .name = "mov", > > > @@ -7535,3 +7790,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_isobmff_muxer_class, > > > > This gives this muxer all the options of the other muxers; yet which one > > of these are actually supported? E.g. is faststart supported? The code > > for it is in mov_write_trailer() and that is not called. > > > > I see. Yeah most of the options may not make sense for AVIF. I was > hoping to re-use some of the options as-is for things like ICC profile > in the future. I have added a separate class for AVIF with no options > for now. We can copy over some of the options when they are supported > in AVIF mode. > > > > > +}; > > > +#endif > > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > > index 2ac84ed070..55b8469f68 100644 > > > --- a/libavformat/movenc.h > > > +++ b/libavformat/movenc.h > > > @@ -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; > > > @@ -242,6 +243,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) > > > > _______________________________________________ > > 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". > > > > -- > Vignesh Any other comments on this? If not, can this be merged please. -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-15 15:59 ` Vignesh Venkatasubramanian @ 2022-03-21 17:07 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-21 17:07 UTC (permalink / raw) To: FFmpeg development discussions and patches On Tue, Mar 15, 2022 at 8:59 AM Vignesh Venkatasubramanian <vigneshv@google.com> wrote: > > On Thu, Mar 10, 2022 at 10:14 AM Vignesh Venkatasubramanian > <vigneshv@google.com> wrote: > > > > On Thu, Mar 10, 2022 at 8:01 AM Andreas Rheinhardt > > <andreas.rheinhardt@outlook.com> wrote: > > > > > > Vignesh Venkatasubramanian: > > > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > > > > > Sample usage for still image: > > > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > > > > > Sample usage for animated AVIF image: > > > > ffmpeg -i video.mp4 animated.avif > > > > > > > > We can re-use any of the AV1 encoding options that will make > > > > sense for image encoding (like bitrate, tiles, encoding speed, > > > > etc). > > > > > > > > The files generated by this muxer has been verified to be valid > > > > AVIF files by the following: > > > > 1) Displays on Chrome (both still and animated images). > > > > 2) Displays on Firefox (only still images, firefox does not support > > > > animated AVIF yet). > > > > 3) Verfied to be valid by Compliance Warden: > > > > https://github.com/gpac/ComplianceWarden > > > > > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > > > --- > > > > configure | 1 + > > > > libavformat/allformats.c | 1 + > > > > libavformat/movenc.c | 323 ++++++++++++++++++++++++++++++++++++--- > > > > libavformat/movenc.h | 5 + > > > > 4 files changed, 305 insertions(+), 25 deletions(-) > > > > > > > > diff --git a/configure b/configure > > > > index 8c69ab0c86..6d7020e96b 100755 > > > > --- a/configure > > > > +++ b/configure > > > > @@ -3390,6 +3390,7 @@ asf_stream_muxer_select="asf_muxer" > > > > av1_demuxer_select="av1_frame_merge_bsf av1_parser" > > > > avi_demuxer_select="riffdec exif" > > > > avi_muxer_select="riffenc" > > > > +avif_muxer_select="mov_muxer" > > > > caf_demuxer_select="iso_media" > > > > caf_muxer_select="iso_media" > > > > dash_muxer_select="mp4_muxer" > > > > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > > > > index d066a7745b..400c17afbd 100644 > > > > --- a/libavformat/allformats.c > > > > +++ b/libavformat/allformats.c > > > > @@ -81,6 +81,7 @@ extern const AVOutputFormat ff_au_muxer; > > > > extern const AVInputFormat ff_av1_demuxer; > > > > extern const AVInputFormat ff_avi_demuxer; > > > > extern const AVOutputFormat ff_avi_muxer; > > > > +extern const AVOutputFormat ff_avif_muxer; > > > > extern const AVInputFormat ff_avisynth_demuxer; > > > > extern const AVOutputFormat ff_avm2_muxer; > > > > extern const AVInputFormat ff_avr_demuxer; > > > > diff --git a/libavformat/movenc.c b/libavformat/movenc.c > > > > index 1a746a67fd..504403ab0b 100644 > > > > --- a/libavformat/movenc.c > > > > +++ b/libavformat/movenc.c > > > > @@ -1303,7 +1303,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); > > > > } > > > > > > > > @@ -2004,12 +2004,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"); > > > > @@ -2019,7 +2020,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); > > > > } > > > > @@ -2085,7 +2086,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) { > > > > @@ -2106,6 +2107,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; > > > > @@ -2123,6 +2143,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex > > > > avio_wb32(pb, 0); /* size */ > > > > if (mov->encryption_scheme != MOV_ENC_NONE) { > > > > ffio_wfourcc(pb, "encv"); > > > > + } else if (track->mode == MODE_AVIF) { > > > > + ffio_wfourcc(pb, "av01"); > > > > } else { > > > > avio_wl32(pb, track->tag); // store it byteswapped > > > > } > > > > @@ -2239,7 +2261,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; > > > > @@ -2291,6 +2313,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); > > > > } > > > > > > > > @@ -2792,7 +2817,10 @@ 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) { > > > > + if (track->mode == MODE_AVIF) { > > > > + hdlr_type = "pict"; > > > > + descr = "ffmpeg"; > > > > + } else if (track->par->codec_type == AVMEDIA_TYPE_VIDEO) { > > > > hdlr_type = "vide"; > > > > descr = "VideoHandler"; > > > > } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { > > > > @@ -2859,6 +2887,131 @@ 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); > > > > + int num_channels = av_pix_fmt_count_planes(s->streams[0]->codecpar->format); > > > > + const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->streams[0]->codecpar->format); > > > > + int i; > > > > + avio_wb32(pb, 0); /* size */ > > > > + ffio_wfourcc(pb, "pixi"); > > > > + avio_wb32(pb, 0); /* Version & flags */ > > > > + avio_w8(pb, num_channels); /* num_channels */ > > > > + for (i = 0; i < num_channels; ++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 > > > > @@ -3056,7 +3209,7 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVMuxContext *mov, > > > > display_matrix = NULL; > > > > } > > > > > > > > - if (track->flags & MOV_TRACK_ENABLED) > > > > + if (track->flags & MOV_TRACK_ENABLED || track->mode == MODE_AVIF) > > > > flags |= MOV_TKHD_FLAG_ENABLED; > > > > > > > > if (track->mode == MODE_ISM) > > > > @@ -3104,7 +3257,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, > > > > @@ -3439,7 +3592,8 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext > > > > mov_write_tapt_tag(pb, track); > > > > } > > > > } > > > > - mov_write_track_udta_tag(pb, mov, st); > > > > + if (track->mode != MODE_AVIF) > > > > + mov_write_track_udta_tag(pb, mov, st); > > > > track->entry = entry_backup; > > > > track->chunkCount = chunk_backup; > > > > return update_size(pb, pos); > > > > @@ -3914,8 +4068,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. > > > > > > Various parts of this patch seem to presume this (they always use the > > > first stream), yet I fail to see what ensures this. > > > > > > > It does not make sense to have an AVIF output with more than one > > (video) stream. I have added a check in mov_init to ensure that there > > is only one video track to be output when format is 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); > > > > @@ -4245,10 +4406,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; > > > > @@ -4259,7 +4421,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); > > > > @@ -5002,6 +5164,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; > > > > @@ -5065,6 +5230,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 == 0 && pix_fmt_desc->log2_chroma_h == 0) { > > > > + // 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 > > > > @@ -5669,7 +5859,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) { > > > > @@ -5802,7 +5992,9 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) > > > > } > > > > } > > > > } else if (par->codec_id == AV_CODEC_ID_AV1) { > > > > - if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > > + if (trk->mode == MODE_AVIF) { > > > > + avio_write(pb, pkt->data, pkt->size); > > > > + } else if (trk->hint_track >= 0 && trk->hint_track < mov->nb_streams) { > > > > ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, > > > > &size, &offset); > > > > if (ret < 0) > > > > @@ -6230,6 +6422,10 @@ fail: > > > > } > > > > } > > > > > > > > + if (trk->mode == MODE_AVIF && !mov->avif_extent_length) { > > > > + mov->avif_extent_length = pkt->size; > > > > + } > > > > + > > > > return mov_write_single_packet(s, pkt); > > > > } > > > > } > > > > @@ -6569,11 +6765,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 || > > > > @@ -6797,12 +6997,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 */ > > > > @@ -7003,7 +7204,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); > > > > @@ -7291,6 +7492,54 @@ 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; > > > > + int i; > > > > + > > > > + 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); > > > > + for (i = 0; i < mov->nb_streams; i++) > > > > + mov->tracks[i].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); > > > > + > > > > + avio_write(pb, buf, buf_size); > > > > + ffio_free_dyn_buf(&mov->mdat_buf); > > > > + > > > > + // 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); > > > > + > > > > + mov->moov_written = 1; > > > > + mov->mdat_size = 0; > > > > + for (i = 0; i < mov->nb_streams; i++) { > > > > + mov->tracks[i].entry = 0; > > > > + mov->tracks[i].end_reliable = 0; > > > > + } > > > > > > Why this? write_trailer is only called once. > > > > > > > Removed. > > > > > > + return 0; > > > > +}> + > > > > #if CONFIG_TGP_MUXER || CONFIG_TG2_MUXER > > > > static const AVCodecTag codec_3gp_tags[] = { > > > > { AV_CODEC_ID_H263, MKTAG('s','2','6','3') }, > > > > @@ -7373,6 +7622,12 @@ static const AVCodecTag codec_f4v_tags[] = { > > > > { AV_CODEC_ID_NONE, 0 }, > > > > }; > > > > > > > > +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 }; > > > > > > Why is this not under #if CONFIG_AVIF_MUXER (or part of the other #if > > > CONFIG_AVIF_MUXER below)? > > > > > > > Done. I added the #if here to keep all the muxers and tags grouped > > together as before. > > > > > > + > > > > #if CONFIG_MOV_MUXER > > > > const AVOutputFormat ff_mov_muxer = { > > > > .name = "mov", > > > > @@ -7535,3 +7790,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_isobmff_muxer_class, > > > > > > This gives this muxer all the options of the other muxers; yet which one > > > of these are actually supported? E.g. is faststart supported? The code > > > for it is in mov_write_trailer() and that is not called. > > > > > > > I see. Yeah most of the options may not make sense for AVIF. I was > > hoping to re-use some of the options as-is for things like ICC profile > > in the future. I have added a separate class for AVIF with no options > > for now. We can copy over some of the options when they are supported > > in AVIF mode. > > > > > > > > +}; > > > > +#endif > > > > diff --git a/libavformat/movenc.h b/libavformat/movenc.h > > > > index 2ac84ed070..55b8469f68 100644 > > > > --- a/libavformat/movenc.h > > > > +++ b/libavformat/movenc.h > > > > @@ -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; > > > > @@ -242,6 +243,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) > > > > > > _______________________________________________ > > > 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". > > > > > > > > -- > > Vignesh > > Any other comments on this? If not, can this be merged please. > Another ping. :) > -- > Vignesh -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-03 15:36 ` James Almer 2022-03-03 19:16 ` Vignesh Venkatasubramanian @ 2022-03-03 19:20 ` Vignesh Venkatasubramanian 2022-03-03 19:46 ` James Almer 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-03 19:20 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, Mar 3, 2022 at 7:36 AM James Almer <jamrial@gmail.com> wrote: > > On 2/22/2022 6:43 PM, Vignesh Venkatasubramanian wrote: > > Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > > > > AVIF Specifiation: https://aomediacodec.github.io/av1-avif > > > > Sample usage for still image: > > ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > > > > Sample usage for animated AVIF image: > > ffmpeg -i video.mp4 animated.avif > > > > We can re-use any of the AV1 encoding options that will make > > sense for image encoding (like bitrate, tiles, encoding speed, > > etc). > > > > The files generated by this muxer has been verified to be valid > > AVIF files by the following: > > 1) Displays on Chrome (both still and animated images). > > 2) Displays on Firefox (only still images, firefox does not support > > animated AVIF yet). > > 3) Verfied to be valid by Compliance Warden: > > https://github.com/gpac/ComplianceWarden > > > > Fixes the encoder/muxer part of Trac Ticket #7621 > > > > Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > configure | 1 + > > libavformat/allformats.c | 1 + > > libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- > > libavformat/movenc.h | 5 + > > 4 files changed, 282 insertions(+), 25 deletions(-) > > With a single frame i get no errors in that compliance tool, but when i > encode an animated AVIF i get the following: > > [heif][Rule #12] Error: CodingConstraintsBox ('ccst') shall be present once > [heif][Rule #28] Error: Wrong arity for boxes { ccst } in parents { avc1 > avc2 avc3 avc4 hev1 hev2 hvc1 hvc2 av01 }: expected in range [1-1], found 0 > [heif][Rule #31] Error: 'msf1' brand: this file shall conform to HEIF > (section 7.2) > [heif][Rule #31] Error: 'msf1' brand: 'iso8' shall be present among the > compatible brands array > [heif][Rule #32] Error: 'mif1' brand: this file shall conform to HEIF > section 6, check the other errors for details > [heif][Rule #33] Error: 'msf1' brand: this file shall conform to HEIF > section 7, check the other errors for details > > All but one of these should be solved by writing a ccst box after the > av1C box in the sample entry. The missing one should be solved by > writing the iso8 compatible brand. > > The ccst box looks like it would need some bitstream information, so > either you parse the packets to get it, or just hardcode sane defaults, > considering it's used as a hint and it's not required for demuxing. I recently fixed these errors in libavif [1][2] (the reference AVIF encoder). I was hoping to have a follow-up patch since i already uploaded the existing patches. Since you have pointed this out now, i have included the fix in this patch itself. The new patch will now write the iso8 compatible brand and the ccst box with sane default values. The file produced is now identical to the file produced by libavif in terms of box structure. Also, notice that the compliance tool still shows the following error for animated avif: [avif][Rule #3] Warning: [ItemId=1] still_picture flag set to 0 [avif][Rule #4] Warning: [ItemId=1] reduced_still_picture_header flag set to 0 I believe these are incorrect since it does not make sense to set these flag to 0 for animated avif sequences. These warnings also show up with files produced by libavif. So it is okay to ignore them. I will file an issue with the compliance tool separately. Please take another look, thanks! [1] https://github.com/AOMediaCodec/libavif/pull/855 [2] https://github.com/AOMediaCodec/libavif/pull/856 > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-03 19:20 ` Vignesh Venkatasubramanian @ 2022-03-03 19:46 ` James Almer 2022-03-03 19:57 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: James Almer @ 2022-03-03 19:46 UTC (permalink / raw) To: ffmpeg-devel On 3/3/2022 4:20 PM, Vignesh Venkatasubramanian wrote: > On Thu, Mar 3, 2022 at 7:36 AM James Almer <jamrial@gmail.com> wrote: >> >> On 2/22/2022 6:43 PM, Vignesh Venkatasubramanian wrote: >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. >>> >>> AVIF Specifiation: https://aomediacodec.github.io/av1-avif >>> >>> Sample usage for still image: >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif >>> >>> Sample usage for animated AVIF image: >>> ffmpeg -i video.mp4 animated.avif >>> >>> We can re-use any of the AV1 encoding options that will make >>> sense for image encoding (like bitrate, tiles, encoding speed, >>> etc). >>> >>> The files generated by this muxer has been verified to be valid >>> AVIF files by the following: >>> 1) Displays on Chrome (both still and animated images). >>> 2) Displays on Firefox (only still images, firefox does not support >>> animated AVIF yet). >>> 3) Verfied to be valid by Compliance Warden: >>> https://github.com/gpac/ComplianceWarden >>> >>> Fixes the encoder/muxer part of Trac Ticket #7621 >>> >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> >>> --- >>> configure | 1 + >>> libavformat/allformats.c | 1 + >>> libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- >>> libavformat/movenc.h | 5 + >>> 4 files changed, 282 insertions(+), 25 deletions(-) >> >> With a single frame i get no errors in that compliance tool, but when i >> encode an animated AVIF i get the following: >> >> [heif][Rule #12] Error: CodingConstraintsBox ('ccst') shall be present once >> [heif][Rule #28] Error: Wrong arity for boxes { ccst } in parents { avc1 >> avc2 avc3 avc4 hev1 hev2 hvc1 hvc2 av01 }: expected in range [1-1], found 0 >> [heif][Rule #31] Error: 'msf1' brand: this file shall conform to HEIF >> (section 7.2) >> [heif][Rule #31] Error: 'msf1' brand: 'iso8' shall be present among the >> compatible brands array >> [heif][Rule #32] Error: 'mif1' brand: this file shall conform to HEIF >> section 6, check the other errors for details >> [heif][Rule #33] Error: 'msf1' brand: this file shall conform to HEIF >> section 7, check the other errors for details >> >> All but one of these should be solved by writing a ccst box after the >> av1C box in the sample entry. The missing one should be solved by >> writing the iso8 compatible brand. >> >> The ccst box looks like it would need some bitstream information, so >> either you parse the packets to get it, or just hardcode sane defaults, >> considering it's used as a hint and it's not required for demuxing. > > I recently fixed these errors in libavif [1][2] (the reference AVIF > encoder). I was hoping to have a follow-up patch since i already > uploaded the existing patches. Since you have pointed this out now, i > have included the fix in this patch itself. The new patch will now > write the iso8 compatible brand and the ccst box with sane default > values. The file produced is now identical to the file produced by > libavif in terms of box structure. > > Also, notice that the compliance tool still shows the following error > for animated avif: > > [avif][Rule #3] Warning: [ItemId=1] still_picture flag set to 0 > [avif][Rule #4] Warning: [ItemId=1] reduced_still_picture_header flag set to 0 > > I believe these are incorrect since it does not make sense to set > these flag to 0 for animated avif sequences. These warnings also show > up with files produced by libavif. So it is okay to ignore them. I > will file an issue with the compliance tool separately. The compliance tool uses the 1.0.0 revision of the spec, and what you mentioned was removed in the current unfinished draft: https://github.com/AOMediaCodec/av1-avif/pull/112 I assume the tool will be updated once a new revision is released, so yes, we can ignore them. > > Please take another look, thanks! I noticed that using -still-picture 1 and passing more than one frame to the libaom encoder will succeed, despite you setting enccfg.g_limit to 1, and encode every frame as key frames. I'd expect the encoder would error out if you try to feed it more frames. Is it a libaom bug? > > [1] https://github.com/AOMediaCodec/libavif/pull/855 > [2] https://github.com/AOMediaCodec/libavif/pull/856 >> _______________________________________________ >> 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". > > > _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing 2022-03-03 19:46 ` James Almer @ 2022-03-03 19:57 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-03 19:57 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, Mar 3, 2022 at 11:46 AM James Almer <jamrial@gmail.com> wrote: > > > > On 3/3/2022 4:20 PM, Vignesh Venkatasubramanian wrote: > > On Thu, Mar 3, 2022 at 7:36 AM James Almer <jamrial@gmail.com> wrote: > >> > >> On 2/22/2022 6:43 PM, Vignesh Venkatasubramanian wrote: > >>> Add an AVIF muxer by re-using the existing the mov/mp4 muxer. > >>> > >>> AVIF Specifiation: https://aomediacodec.github.io/av1-avif > >>> > >>> Sample usage for still image: > >>> ffmpeg -i image.png -c:v libaom-av1 -avif-image 1 image.avif > >>> > >>> Sample usage for animated AVIF image: > >>> ffmpeg -i video.mp4 animated.avif > >>> > >>> We can re-use any of the AV1 encoding options that will make > >>> sense for image encoding (like bitrate, tiles, encoding speed, > >>> etc). > >>> > >>> The files generated by this muxer has been verified to be valid > >>> AVIF files by the following: > >>> 1) Displays on Chrome (both still and animated images). > >>> 2) Displays on Firefox (only still images, firefox does not support > >>> animated AVIF yet). > >>> 3) Verfied to be valid by Compliance Warden: > >>> https://github.com/gpac/ComplianceWarden > >>> > >>> Fixes the encoder/muxer part of Trac Ticket #7621 > >>> > >>> Signed-off-by: Vignesh Venkatasubramanian <vigneshv@google.com> > >>> --- > >>> configure | 1 + > >>> libavformat/allformats.c | 1 + > >>> libavformat/movenc.c | 300 +++++++++++++++++++++++++++++++++++---- > >>> libavformat/movenc.h | 5 + > >>> 4 files changed, 282 insertions(+), 25 deletions(-) > >> > >> With a single frame i get no errors in that compliance tool, but when i > >> encode an animated AVIF i get the following: > >> > >> [heif][Rule #12] Error: CodingConstraintsBox ('ccst') shall be present once > >> [heif][Rule #28] Error: Wrong arity for boxes { ccst } in parents { avc1 > >> avc2 avc3 avc4 hev1 hev2 hvc1 hvc2 av01 }: expected in range [1-1], found 0 > >> [heif][Rule #31] Error: 'msf1' brand: this file shall conform to HEIF > >> (section 7.2) > >> [heif][Rule #31] Error: 'msf1' brand: 'iso8' shall be present among the > >> compatible brands array > >> [heif][Rule #32] Error: 'mif1' brand: this file shall conform to HEIF > >> section 6, check the other errors for details > >> [heif][Rule #33] Error: 'msf1' brand: this file shall conform to HEIF > >> section 7, check the other errors for details > >> > >> All but one of these should be solved by writing a ccst box after the > >> av1C box in the sample entry. The missing one should be solved by > >> writing the iso8 compatible brand. > >> > >> The ccst box looks like it would need some bitstream information, so > >> either you parse the packets to get it, or just hardcode sane defaults, > >> considering it's used as a hint and it's not required for demuxing. > > > > I recently fixed these errors in libavif [1][2] (the reference AVIF > > encoder). I was hoping to have a follow-up patch since i already > > uploaded the existing patches. Since you have pointed this out now, i > > have included the fix in this patch itself. The new patch will now > > write the iso8 compatible brand and the ccst box with sane default > > values. The file produced is now identical to the file produced by > > libavif in terms of box structure. > > > > Also, notice that the compliance tool still shows the following error > > for animated avif: > > > > [avif][Rule #3] Warning: [ItemId=1] still_picture flag set to 0 > > [avif][Rule #4] Warning: [ItemId=1] reduced_still_picture_header flag set to 0 > > > > I believe these are incorrect since it does not make sense to set > > these flag to 0 for animated avif sequences. These warnings also show > > up with files produced by libavif. So it is okay to ignore them. I > > will file an issue with the compliance tool separately. > > The compliance tool uses the 1.0.0 revision of the spec, and what you > mentioned was removed in the current unfinished draft: > https://github.com/AOMediaCodec/av1-avif/pull/112 > > I assume the tool will be updated once a new revision is released, so > yes, we can ignore them. > > > > > Please take another look, thanks! > > I noticed that using -still-picture 1 and passing more than one frame to > the libaom encoder will succeed, despite you setting enccfg.g_limit to > 1, and encode every frame as key frames. > I'd expect the encoder would error out if you try to feed it more > frames. Is it a libaom bug? > Hmm yeah it seems like libaom only uses the value to set the still-picture sequence header values in 1-pass mode. I think in a way it may be useful for us to be able to use AVIF output with the image2 muxer. For example, something like: ffmpeg -i video.mp4 -still-picture 1 -c:v libaom-av1 -an -f image2 image-%02d.avif This command does not work as of now, but I have some follow-up patches to make the image2 muxer work with AVIF images. > > > > [1] https://github.com/AOMediaCodec/libavif/pull/855 > > [2] https://github.com/AOMediaCodec/libavif/pull/856 > >> _______________________________________________ > >> 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". > > > > > > > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-02-17 5:51 [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding Vignesh Venkatasubramanian 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header Vignesh Venkatasubramanian 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing Vignesh Venkatasubramanian @ 2022-02-17 18:09 ` James Zern 2022-02-17 19:33 ` Vignesh Venkatasubramanian 2022-02-17 20:59 ` James Almer 3 siblings, 1 reply; 71+ messages in thread From: James Zern @ 2022-02-17 18:09 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: Vignesh Venkatasubramanian On Wed, Feb 16, 2022 at 9:51 PM Vignesh Venkatasubramanian <vigneshv-at-google.com@ffmpeg.org> wrote: > > Add a parameter to libaom-av1 encoder to enforce some of the single > image constraints in the AV1 encoder. Setting this flag will limit > the encoder to producing exactly one frame and the sequence header > that is produced by the encoder will be conformant to the AVIF > specification [1]. > > Part of Fixing Trac ticket #7621 > > [1] https://aomediacodec.github.io/av1-avif > > Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > libavcodec/libaomenc.c | 14 ++++++++++++++ > 1 file changed, 14 insertions(+) > > [...] > @@ -1290,6 +1303,7 @@ static const AVOption options[] = { > { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, > { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, > FF_AV1_PROFILE_OPTS > + { "avif-image", "Encode in single frame mode for still AVIF images.", OFFSET(is_avif), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE }, Can this instead be mapped to something that can be sent via aom-params? _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-02-17 18:09 ` [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding James Zern @ 2022-02-17 19:33 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-17 19:33 UTC (permalink / raw) To: James Zern; +Cc: FFmpeg development discussions and patches On Thu, Feb 17, 2022 at 10:09 AM James Zern <jzern@google.com> wrote: > > On Wed, Feb 16, 2022 at 9:51 PM Vignesh Venkatasubramanian > <vigneshv-at-google.com@ffmpeg.org> wrote: > > > > Add a parameter to libaom-av1 encoder to enforce some of the single > > image constraints in the AV1 encoder. Setting this flag will limit > > the encoder to producing exactly one frame and the sequence header > > that is produced by the encoder will be conformant to the AVIF > > specification [1]. > > > > Part of Fixing Trac ticket #7621 > > > > [1] https://aomediacodec.github.io/av1-avif > > > > Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > libavcodec/libaomenc.c | 14 ++++++++++++++ > > 1 file changed, 14 insertions(+) > > > > [...] > > @@ -1290,6 +1303,7 @@ static const AVOption options[] = { > > { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, > > { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, > > FF_AV1_PROFILE_OPTS > > + { "avif-image", "Encode in single frame mode for still AVIF images.", OFFSET(is_avif), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE }, > > Can this instead be mapped to something that can be sent via aom-params? From what i understand, aom-params can only be used to set key-value pairs that are set using the aom_codec_set_option() function call. For single image AVIF encodes, we are modifying some of the aom_codec_enc_cfg entries. There is no way to do that in a generic way other than to introduce a specific option like this. -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-02-17 5:51 [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding Vignesh Venkatasubramanian ` (2 preceding siblings ...) 2022-02-17 18:09 ` [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding James Zern @ 2022-02-17 20:59 ` James Almer 2022-02-17 21:18 ` [FFmpeg-devel] [PATCH] " Vignesh Venkatasubramanian 2022-02-17 21:20 ` [FFmpeg-devel] [PATCH 1/3] " Vignesh Venkatasubramanian 3 siblings, 2 replies; 71+ messages in thread From: James Almer @ 2022-02-17 20:59 UTC (permalink / raw) To: ffmpeg-devel On 2/17/2022 2:51 AM, Vignesh Venkatasubramanian wrote: > Add a parameter to libaom-av1 encoder to enforce some of the single > image constraints in the AV1 encoder. Setting this flag will limit > the encoder to producing exactly one frame and the sequence header > that is produced by the encoder will be conformant to the AVIF > specification [1]. > > Part of Fixing Trac ticket #7621 > > [1] https://aomediacodec.github.io/av1-avif > > Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> > --- > libavcodec/libaomenc.c | 14 ++++++++++++++ > 1 file changed, 14 insertions(+) > > diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c > index 963cc1bcbc..0398060a2f 100644 > --- a/libavcodec/libaomenc.c > +++ b/libavcodec/libaomenc.c > @@ -99,6 +99,7 @@ typedef struct AOMEncoderContext { > int enable_restoration; > int usage; > int tune; > + int is_avif; > int enable_rect_partitions; > int enable_1to4_partitions; > int enable_ab_partitions; > @@ -746,6 +747,18 @@ static av_cold int aom_init(AVCodecContext *avctx, > if (res < 0) > return res; > > + if (ctx->is_avif) { > + // Set the maximum number of frames to 1. This will let libaom set > + // still_picture and reduced_still_picture_header to 1 in the Sequence > + // Header as required by AVIF still images. > + enccfg.g_limit = 1; How will libaom react if you feed it more than 1 frame? Will it reject the input and return an error? > + // Reduce memory usage for still images. > + enccfg.g_lag_in_frames = 0; > + // All frames will be key frames. > + enccfg.kf_max_dist = 0; > + enccfg.kf_mode = AOM_KF_DISABLED; > + } > + > /* Construct Encoder Context */ > res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); > if (res != AOM_CODEC_OK) { > @@ -1290,6 +1303,7 @@ static const AVOption options[] = { > { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, > { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, > FF_AV1_PROFILE_OPTS > + { "avif-image", "Encode in single frame mode for still AVIF images.", OFFSET(is_avif), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE }, Judging by the above settings, this option should be called "still_picture". The description can mention it's required for AVIF output. An alternative is to add new AVProfiles, giving them values outside the range that would be written into the sequence header. Like FF_PROFILE_AV1_MAIN_STILL_PICTURE with a value of 0x80, FF_PROFILE_AV1_HIGH_STILL_PICTURE with a value of 0x81, etc. Then you just look for avctx->profile & 0x80 to know it's a still picture. Of course considerations would need to be done to avoid problems, like the line "enccfg.g_profile = avctx->profile;" being changed to "enccfg.g_profile = avctx->profile & 0x3;" (We can define a constant for that 0x3 mask called profile_bits or whatever). > { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, > { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, > { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH] avcodec/libaomenc: Add parameter for avif single image encoding 2022-02-17 20:59 ` James Almer @ 2022-02-17 21:18 ` Vignesh Venkatasubramanian 2022-02-17 21:20 ` [FFmpeg-devel] [PATCH 1/3] " Vignesh Venkatasubramanian 1 sibling, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-17 21:18 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to libaom-av1 encoder to enforce some of the single image constraints in the AV1 encoder. Setting this flag will limit the encoder to producing exactly one frame and the sequence header that is produced by the encoder will be conformant to the AVIF specification [1]. Part of Fixing Trac ticket #7621 [1] https://aomediacodec.github.io/av1-avif Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavcodec/libaomenc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c index 963cc1bcbc..638c1deca6 100644 --- a/libavcodec/libaomenc.c +++ b/libavcodec/libaomenc.c @@ -99,6 +99,7 @@ typedef struct AOMEncoderContext { int enable_restoration; int usage; int tune; + int still_picture; int enable_rect_partitions; int enable_1to4_partitions; int enable_ab_partitions; @@ -746,6 +747,18 @@ static av_cold int aom_init(AVCodecContext *avctx, if (res < 0) return res; + if (ctx->still_picture) { + // Set the maximum number of frames to 1. This will let libaom set + // still_picture and reduced_still_picture_header to 1 in the Sequence + // Header as required by AVIF still images. + enccfg.g_limit = 1; + // Reduce memory usage for still images. + enccfg.g_lag_in_frames = 0; + // All frames will be key frames. + enccfg.kf_max_dist = 0; + enccfg.kf_mode = AOM_KF_DISABLED; + } + /* Construct Encoder Context */ res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); if (res != AOM_CODEC_OK) { @@ -1290,6 +1303,7 @@ static const AVOption options[] = { { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, FF_AV1_PROFILE_OPTS + { "still-picture", "Encode in single frame mode (typically used for still AVIF images).", OFFSET(still_picture), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE }, { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, -- 2.35.1.265.g69c8d7142f-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* Re: [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-02-17 20:59 ` James Almer 2022-02-17 21:18 ` [FFmpeg-devel] [PATCH] " Vignesh Venkatasubramanian @ 2022-02-17 21:20 ` Vignesh Venkatasubramanian 2022-02-22 21:36 ` Vignesh Venkatasubramanian 1 sibling, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-17 21:20 UTC (permalink / raw) To: FFmpeg development discussions and patches On Thu, Feb 17, 2022 at 1:00 PM James Almer <jamrial@gmail.com> wrote: > > > > On 2/17/2022 2:51 AM, Vignesh Venkatasubramanian wrote: > > Add a parameter to libaom-av1 encoder to enforce some of the single > > image constraints in the AV1 encoder. Setting this flag will limit > > the encoder to producing exactly one frame and the sequence header > > that is produced by the encoder will be conformant to the AVIF > > specification [1]. > > > > Part of Fixing Trac ticket #7621 > > > > [1] https://aomediacodec.github.io/av1-avif > > > > Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> > > --- > > libavcodec/libaomenc.c | 14 ++++++++++++++ > > 1 file changed, 14 insertions(+) > > > > diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c > > index 963cc1bcbc..0398060a2f 100644 > > --- a/libavcodec/libaomenc.c > > +++ b/libavcodec/libaomenc.c > > @@ -99,6 +99,7 @@ typedef struct AOMEncoderContext { > > int enable_restoration; > > int usage; > > int tune; > > + int is_avif; > > int enable_rect_partitions; > > int enable_1to4_partitions; > > int enable_ab_partitions; > > @@ -746,6 +747,18 @@ static av_cold int aom_init(AVCodecContext *avctx, > > if (res < 0) > > return res; > > > > + if (ctx->is_avif) { > > + // Set the maximum number of frames to 1. This will let libaom set > > + // still_picture and reduced_still_picture_header to 1 in the Sequence > > + // Header as required by AVIF still images. > > + enccfg.g_limit = 1; > > How will libaom react if you feed it more than 1 frame? Will it reject > the input and return an error? > Yes, trying to encode a second frame after setting g_limit to 1 will result in aom_codec_encode() returning an error. > > + // Reduce memory usage for still images. > > + enccfg.g_lag_in_frames = 0; > > + // All frames will be key frames. > > + enccfg.kf_max_dist = 0; > > + enccfg.kf_mode = AOM_KF_DISABLED; > > + } > > + > > /* Construct Encoder Context */ > > res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); > > if (res != AOM_CODEC_OK) { > > @@ -1290,6 +1303,7 @@ static const AVOption options[] = { > > { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, > > { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, > > FF_AV1_PROFILE_OPTS > > + { "avif-image", "Encode in single frame mode for still AVIF images.", OFFSET(is_avif), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE }, > > Judging by the above settings, this option should be called > "still_picture". The description can mention it's required for AVIF output. > Renamed the variable and the option name to "still_picture". > An alternative is to add new AVProfiles, giving them values outside the > range that would be written into the sequence header. Like > FF_PROFILE_AV1_MAIN_STILL_PICTURE with a value of 0x80, > FF_PROFILE_AV1_HIGH_STILL_PICTURE with a value of 0x81, etc. > Then you just look for avctx->profile & 0x80 to know it's a still picture. > > Of course considerations would need to be done to avoid problems, like > the line "enccfg.g_profile = avctx->profile;" being changed to > "enccfg.g_profile = avctx->profile & 0x3;" (We can define a constant for > that 0x3 mask called profile_bits or whatever). > > > { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, > > { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, > > { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, > _______________________________________________ > 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". -- Vignesh _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-02-17 21:20 ` [FFmpeg-devel] [PATCH 1/3] " Vignesh Venkatasubramanian @ 2022-02-22 21:36 ` Vignesh Venkatasubramanian 2022-03-28 20:47 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-02-22 21:36 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to libaom-av1 encoder to enforce some of the single image constraints in the AV1 encoder. Setting this flag will limit the encoder to producing exactly one frame and the sequence header that is produced by the encoder will be conformant to the AVIF specification [1]. Part of Fixing Trac ticket #7621 [1] https://aomediacodec.github.io/av1-avif Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavcodec/libaomenc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c index 963cc1bcbc..a6d752ab4a 100644 --- a/libavcodec/libaomenc.c +++ b/libavcodec/libaomenc.c @@ -99,6 +99,7 @@ typedef struct AOMEncoderContext { int enable_restoration; int usage; int tune; + int still_picture; int enable_rect_partitions; int enable_1to4_partitions; int enable_ab_partitions; @@ -746,6 +747,18 @@ static av_cold int aom_init(AVCodecContext *avctx, if (res < 0) return res; + if (ctx->still_picture) { + // Set the maximum number of frames to 1. This will let libaom set + // still_picture and reduced_still_picture_header to 1 in the Sequence + // Header as required by AVIF still images. + enccfg.g_limit = 1; + // Reduce memory usage for still images. + enccfg.g_lag_in_frames = 0; + // All frames will be key frames. + enccfg.kf_max_dist = 0; + enccfg.kf_mode = AOM_KF_DISABLED; + } + /* Construct Encoder Context */ res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); if (res != AOM_CODEC_OK) { @@ -1290,6 +1303,7 @@ static const AVOption options[] = { { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, FF_AV1_PROFILE_OPTS + { "still-picture", "Encode in single frame mode (typically used for still AVIF images).", OFFSET(still_picture), AV_OPT_TYPE_BOOL, {.i64 = 0}, -1, 1, VE }, { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, -- 2.35.1.473.g83b2b277ed-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-02-22 21:36 ` Vignesh Venkatasubramanian @ 2022-03-28 20:47 ` Vignesh Venkatasubramanian 2022-04-13 20:39 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-03-28 20:47 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to libaom-av1 encoder to enforce some of the single image constraints in the AV1 encoder. Setting this flag will limit the encoder to producing exactly one frame and the sequence header that is produced by the encoder will be conformant to the AVIF specification [1]. Part of Fixing Trac ticket #7621 [1] https://aomediacodec.github.io/av1-avif Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavcodec/libaomenc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c index 7dbb6f6f39..ccd0823b97 100644 --- a/libavcodec/libaomenc.c +++ b/libavcodec/libaomenc.c @@ -100,6 +100,7 @@ typedef struct AOMEncoderContext { int enable_restoration; int usage; int tune; + int still_picture; int enable_rect_partitions; int enable_1to4_partitions; int enable_ab_partitions; @@ -747,6 +748,18 @@ static av_cold int aom_init(AVCodecContext *avctx, if (res < 0) return res; + if (ctx->still_picture) { + // Set the maximum number of frames to 1. This will let libaom set + // still_picture and reduced_still_picture_header to 1 in the Sequence + // Header as required by AVIF still images. + enccfg.g_limit = 1; + // Reduce memory usage for still images. + enccfg.g_lag_in_frames = 0; + // All frames will be key frames. + enccfg.kf_max_dist = 0; + enccfg.kf_mode = AOM_KF_DISABLED; + } + /* Construct Encoder Context */ res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); if (res != AOM_CODEC_OK) { @@ -1291,6 +1304,7 @@ static const AVOption options[] = { { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, FF_AV1_PROFILE_OPTS + { "still-picture", "Encode in single frame mode (typically used for still AVIF images).", OFFSET(still_picture), AV_OPT_TYPE_BOOL, {.i64 = 0}, -1, 1, VE }, { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, -- 2.35.1.1021.g381101b075-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-03-28 20:47 ` Vignesh Venkatasubramanian @ 2022-04-13 20:39 ` Vignesh Venkatasubramanian 2022-05-02 21:37 ` Vignesh Venkatasubramanian 0 siblings, 1 reply; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-04-13 20:39 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to libaom-av1 encoder to enforce some of the single image constraints in the AV1 encoder. Setting this flag will limit the encoder to producing exactly one frame and the sequence header that is produced by the encoder will be conformant to the AVIF specification [1]. Part of Fixing Trac ticket #7621 [1] https://aomediacodec.github.io/av1-avif Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavcodec/libaomenc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c index 054903e6e2..0411773bbf 100644 --- a/libavcodec/libaomenc.c +++ b/libavcodec/libaomenc.c @@ -100,6 +100,7 @@ typedef struct AOMEncoderContext { int enable_restoration; int usage; int tune; + int still_picture; int enable_rect_partitions; int enable_1to4_partitions; int enable_ab_partitions; @@ -747,6 +748,18 @@ static av_cold int aom_init(AVCodecContext *avctx, if (res < 0) return res; + if (ctx->still_picture) { + // Set the maximum number of frames to 1. This will let libaom set + // still_picture and reduced_still_picture_header to 1 in the Sequence + // Header as required by AVIF still images. + enccfg.g_limit = 1; + // Reduce memory usage for still images. + enccfg.g_lag_in_frames = 0; + // All frames will be key frames. + enccfg.kf_max_dist = 0; + enccfg.kf_mode = AOM_KF_DISABLED; + } + /* Construct Encoder Context */ res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); if (res != AOM_CODEC_OK) { @@ -1291,6 +1304,7 @@ static const AVOption options[] = { { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, FF_AV1_PROFILE_OPTS + { "still-picture", "Encode in single frame mode (typically used for still AVIF images).", OFFSET(still_picture), AV_OPT_TYPE_BOOL, {.i64 = 0}, -1, 1, VE }, { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, -- 2.35.1.1178.g4f1659d476-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
* [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding 2022-04-13 20:39 ` Vignesh Venkatasubramanian @ 2022-05-02 21:37 ` Vignesh Venkatasubramanian 0 siblings, 0 replies; 71+ messages in thread From: Vignesh Venkatasubramanian @ 2022-05-02 21:37 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Vignesh Venkatasubramanian Add a parameter to libaom-av1 encoder to enforce some of the single image constraints in the AV1 encoder. Setting this flag will limit the encoder to producing exactly one frame and the sequence header that is produced by the encoder will be conformant to the AVIF specification [1]. Part of Fixing Trac ticket #7621 [1] https://aomediacodec.github.io/av1-avif Signed-off-by:: Vignesh Venkatasubramanian <vigneshv@google.com> --- libavcodec/libaomenc.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavcodec/libaomenc.c b/libavcodec/libaomenc.c index 054903e6e2..0411773bbf 100644 --- a/libavcodec/libaomenc.c +++ b/libavcodec/libaomenc.c @@ -100,6 +100,7 @@ typedef struct AOMEncoderContext { int enable_restoration; int usage; int tune; + int still_picture; int enable_rect_partitions; int enable_1to4_partitions; int enable_ab_partitions; @@ -747,6 +748,18 @@ static av_cold int aom_init(AVCodecContext *avctx, if (res < 0) return res; + if (ctx->still_picture) { + // Set the maximum number of frames to 1. This will let libaom set + // still_picture and reduced_still_picture_header to 1 in the Sequence + // Header as required by AVIF still images. + enccfg.g_limit = 1; + // Reduce memory usage for still images. + enccfg.g_lag_in_frames = 0; + // All frames will be key frames. + enccfg.kf_max_dist = 0; + enccfg.kf_mode = AOM_KF_DISABLED; + } + /* Construct Encoder Context */ res = aom_codec_enc_init(&ctx->encoder, iface, &enccfg, flags); if (res != AOM_CODEC_OK) { @@ -1291,6 +1304,7 @@ static const AVOption options[] = { { "psnr", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_PSNR}, 0, 0, VE, "tune"}, { "ssim", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AOM_TUNE_SSIM}, 0, 0, VE, "tune"}, FF_AV1_PROFILE_OPTS + { "still-picture", "Encode in single frame mode (typically used for still AVIF images).", OFFSET(still_picture), AV_OPT_TYPE_BOOL, {.i64 = 0}, -1, 1, VE }, { "enable-rect-partitions", "Enable rectangular partitions", OFFSET(enable_rect_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-1to4-partitions", "Enable 1:4/4:1 partitions", OFFSET(enable_1to4_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, { "enable-ab-partitions", "Enable ab shape partitions", OFFSET(enable_ab_partitions), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, VE}, -- 2.36.0.464.gb9c8b46e94-goog _______________________________________________ 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". ^ permalink raw reply [flat|nested] 71+ messages in thread
end of thread, other threads:[~2022-05-13 7:22 UTC | newest] Thread overview: 71+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2022-02-17 5:51 [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding Vignesh Venkatasubramanian 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 2/3] avformat/av1: Add a parameter to av1c to omit seq header Vignesh Venkatasubramanian 2022-03-02 22:57 ` James Almer 2022-03-02 23:22 ` Vignesh Venkatasubramanian 2022-03-02 23:23 ` Vignesh Venkatasubramanian 2022-03-02 23:27 ` James Almer 2022-03-28 20:48 ` Vignesh Venkatasubramanian 2022-04-13 20:40 ` Vignesh Venkatasubramanian 2022-05-02 21:36 ` Vignesh Venkatasubramanian 2022-02-17 5:51 ` [FFmpeg-devel] [PATCH 3/3] avformat/movenc: Add support for AVIF muxing Vignesh Venkatasubramanian 2022-02-22 18:40 ` Vignesh Venkatasubramanian 2022-02-22 20:03 ` James Almer 2022-02-22 21:37 ` Vignesh Venkatasubramanian 2022-02-22 21:38 ` Vignesh Venkatasubramanian 2022-02-22 21:43 ` Vignesh Venkatasubramanian 2022-02-24 17:34 ` Vignesh Venkatasubramanian 2022-03-01 16:49 ` Vignesh Venkatasubramanian 2022-03-03 15:36 ` James Almer 2022-03-03 19:16 ` Vignesh Venkatasubramanian 2022-03-04 11:24 ` James Almer 2022-03-04 17:52 ` Vignesh Venkatasubramanian 2022-03-04 17:54 ` Vignesh Venkatasubramanian 2022-03-09 19:34 ` Vignesh Venkatasubramanian 2022-03-10 16:01 ` Andreas Rheinhardt 2022-03-10 18:12 ` Vignesh Venkatasubramanian 2022-03-21 20:46 ` Andreas Rheinhardt 2022-03-22 16:45 ` Vignesh Venkatasubramanian 2022-03-22 16:46 ` Vignesh Venkatasubramanian 2022-03-28 17:06 ` Vignesh Venkatasubramanian 2022-03-28 20:49 ` Vignesh Venkatasubramanian 2022-04-07 18:25 ` Vignesh Venkatasubramanian 2022-04-13 17:21 ` James Zern 2022-04-13 20:40 ` Vignesh Venkatasubramanian 2022-04-13 21:01 ` Andreas Rheinhardt 2022-04-13 21:33 ` Vignesh Venkatasubramanian 2022-05-02 17:28 ` James Zern 2022-05-02 21:34 ` Vignesh Venkatasubramanian 2022-05-02 21:35 ` Vignesh Venkatasubramanian 2022-05-03 23:39 ` James Zern 2022-05-04 2:46 ` "zhilizhao(赵志立)" 2022-05-04 16:45 ` Vignesh Venkatasubramanian 2022-05-04 16:48 ` Vignesh Venkatasubramanian 2022-05-04 17:10 ` "zhilizhao(赵志立)" 2022-05-04 17:14 ` Vignesh Venkatasubramanian 2022-05-04 17:15 ` Vignesh Venkatasubramanian 2022-05-11 16:54 ` Vignesh Venkatasubramanian 2022-05-11 17:25 ` Gyan Doshi 2022-05-12 10:26 ` Gyan Doshi 2022-05-12 16:23 ` Vignesh Venkatasubramanian 2022-05-12 16:23 ` Vignesh Venkatasubramanian 2022-05-13 7:22 ` Gyan Doshi 2022-04-13 20:41 ` Vignesh Venkatasubramanian 2022-04-13 21:04 ` Andreas Rheinhardt 2022-04-13 21:35 ` Vignesh Venkatasubramanian 2022-04-21 16:38 ` Vignesh Venkatasubramanian 2022-04-29 16:03 ` Vignesh Venkatasubramanian 2022-03-10 18:14 ` Vignesh Venkatasubramanian 2022-03-15 15:59 ` Vignesh Venkatasubramanian 2022-03-21 17:07 ` Vignesh Venkatasubramanian 2022-03-03 19:20 ` Vignesh Venkatasubramanian 2022-03-03 19:46 ` James Almer 2022-03-03 19:57 ` Vignesh Venkatasubramanian 2022-02-17 18:09 ` [FFmpeg-devel] [PATCH 1/3] avcodec/libaomenc: Add parameter for avif single image encoding James Zern 2022-02-17 19:33 ` Vignesh Venkatasubramanian 2022-02-17 20:59 ` James Almer 2022-02-17 21:18 ` [FFmpeg-devel] [PATCH] " Vignesh Venkatasubramanian 2022-02-17 21:20 ` [FFmpeg-devel] [PATCH 1/3] " Vignesh Venkatasubramanian 2022-02-22 21:36 ` Vignesh Venkatasubramanian 2022-03-28 20:47 ` Vignesh Venkatasubramanian 2022-04-13 20:39 ` Vignesh Venkatasubramanian 2022-05-02 21:37 ` Vignesh Venkatasubramanian
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