History

movenc: Allow writing timed ID3 metadata

This is based on a spec at https://aomediacodec.github.io/id3-emsg/, further based on ISO/IEC 23009-1:2019. Within libavformat, timed ID3 metadata (already supported by the mpegts demuxer and muxer) is handled as a separate data AVStream with codec type AV_CODEC_ID_TIMED_ID3. However, it doesn't have a corresponding track in the mov file - instead, these events are written as separate toplevel 'emsg' boxes.

Martin Storsjö committed 2 years ago

Commit fbd5e231 parent e4e3d253 files · +104 −8Browse files

Unified · Split

3 files
  1. libavformat/movenc.c +48 −1
  2. libavformat/tests/movenc.c +48 −7
  3. tests/ref/fate/movenc +8 −0
libavformat/movenc.c+48−1
@@ -5519,7 +5519,7 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) · 5518 unmodified lines
5519{5519{
5520 MOVMuxContext *mov = s->priv_data;5520 MOVMuxContext *mov = s->priv_data;
5521 int64_t pos = avio_tell(pb);5521 int64_t pos = avio_tell(pb);
5522 int has_h264 = 0, has_av1 = 0, has_video = 0, has_dolby = 0;5522 int has_h264 = 0, has_av1 = 0, has_video = 0, has_dolby = 0, has_id3 = 0;
5523 int has_iamf = 0;5523 int has_iamf = 0;
5524 5524
5525#if CONFIG_IAMFENC5525#if CONFIG_IAMFENC
@@ -5550,6 +5550,8 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) · 24 unmodified lines
5550 st->codecpar->nb_coded_side_data,5550 st->codecpar->nb_coded_side_data,
5551 AV_PKT_DATA_DOVI_CONF))5551 AV_PKT_DATA_DOVI_CONF))
5552 has_dolby = 1;5552 has_dolby = 1;
5553 if (st->codecpar->codec_id == AV_CODEC_ID_TIMED_ID3)
5554 has_id3 = 1;
5553 }5555 }
5554 5556
5555 avio_wb32(pb, 0); /* size */5557 avio_wb32(pb, 0); /* size */
@@ -5629,6 +5631,9 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) · 73 unmodified lines
5629 if (mov->flags & FF_MOV_FLAG_DASH && mov->flags & FF_MOV_FLAG_GLOBAL_SIDX)5631 if (mov->flags & FF_MOV_FLAG_DASH && mov->flags & FF_MOV_FLAG_GLOBAL_SIDX)
5630 ffio_wfourcc(pb, "dash");5632 ffio_wfourcc(pb, "dash");
5631 5633
5634 if (has_id3)
5635 ffio_wfourcc(pb, "aid3");
5636
5632 return update_size(pb, pos);5637 return update_size(pb, pos);
5633}5638}
5634 5639
@@ -6712,6 +6717,34 @@ static int mov_build_iamf_packet(AVFormatContext *s, MOVTrack *trk, AVPacket *pk · 1077 unmodified lines
6712}6717}
6713#endif6718#endif
6714 6719
6720static int mov_write_emsg_tag(AVIOContext *pb, AVStream *st, AVPacket *pkt)
6721{
6722 int64_t pos = avio_tell(pb);
6723 const char *scheme_id_uri = "https://aomedia.org/emsg/ID3";
6724 const char *value = "";
6725
6726 av_assert0(st->time_base.num == 1);
6727
6728 avio_write_marker(pb,
6729 av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q),
6730 AVIO_DATA_MARKER_BOUNDARY_POINT);
6731
6732 avio_wb32(pb, 0); /* size */
6733 ffio_wfourcc(pb, "emsg");
6734 avio_w8(pb, 1); /* version */
6735 avio_wb24(pb, 0);
6736 avio_wb32(pb, st->time_base.den); /* timescale */
6737 avio_wb64(pb, pkt->pts); /* presentation_time */
6738 avio_wb32(pb, 0xFFFFFFFFU); /* event_duration */
6739 avio_wb32(pb, 0); /* id */
6740 /* null terminated UTF8 strings */
6741 avio_write(pb, scheme_id_uri, strlen(scheme_id_uri) + 1);
6742 avio_write(pb, value, strlen(value) + 1);
6743 avio_write(pb, pkt->data, pkt->size);
6744
6745 return update_size(pb, pos);
6746}
6747
6715static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)6748static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)
6716{6749{
6717 MOVMuxContext *mov = s->priv_data;6750 MOVMuxContext *mov = s->priv_data;
@@ -6722,6 +6755,11 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) · 4 unmodified lines
6722 return 1;6755 return 1;
6723 }6756 }
6724 6757
6758 if (s->streams[pkt->stream_index]->codecpar->codec_id == AV_CODEC_ID_TIMED_ID3) {
6759 mov_write_emsg_tag(s->pb, s->streams[pkt->stream_index], pkt);
6760 return 0;
6761 }
6762
6725 trk = s->streams[pkt->stream_index]->priv_data;6763 trk = s->streams[pkt->stream_index]->priv_data;
6726 6764
6727#if CONFIG_IAMFENC6765#if CONFIG_IAMFENC
@@ -7381,6 +7419,12 @@ static int mov_init(AVFormatContext *s) · 653 unmodified lines
7381 AVStream *st = s->streams[i];7419 AVStream *st = s->streams[i];
7382 if (st->priv_data)7420 if (st->priv_data)
7383 continue;7421 continue;
7422 // Don't produce a track in the output file for timed ID3 streams.
7423 if (st->codecpar->codec_id == AV_CODEC_ID_TIMED_ID3) {
7424 // Leave priv_data set to NULL for these AVStreams that don't
7425 // have a corresponding track.
7426 continue;
7427 }
7384 st->priv_data = st;7428 st->priv_data = st;
7385 mov->nb_tracks++;7429 mov->nb_tracks++;
7386 }7430 }
@@ -7480,6 +7524,9 @@ static int mov_init(AVFormatContext *s) · 93 unmodified lines
7480 MOVTrack *track = st->priv_data;7524 MOVTrack *track = st->priv_data;
7481 AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL,0);7525 AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL,0);
7482 7526
7527 if (!track)
7528 continue;
7529
7483 if (!track->st) {7530 if (!track->st) {
7484 track->st = st;7531 track->st = st;
7485 track->par = st->codecpar;7532 track->par = st->codecpar;
libavformat/tests/movenc.c+48−7
@@ -58,7 +58,7 @@ struct AVMD5* md5; · 57 unmodified lines
58uint8_t hash[HASH_SIZE];58uint8_t hash[HASH_SIZE];
59 59
60AVPacket *pkt;60AVPacket *pkt;
61AVStream *video_st, *audio_st;61AVStream *video_st, *audio_st, *id3_st;
62int64_t audio_dts, video_dts;62int64_t audio_dts, video_dts;
63 63
64int bframes;64int bframes;
@@ -177,7 +177,7 @@ static void check_func(int value, int line, const char *msg, ...) · 112 unmodified lines
177}177}
178#define check(value, ...) check_func(value, __LINE__, __VA_ARGS__)178#define check(value, ...) check_func(value, __LINE__, __VA_ARGS__)
179 179
180static void init_fps(int bf, int audio_preroll, int fps)180static void init_fps(int bf, int audio_preroll, int fps, int id3)
181{181{
182 AVStream *st;182 AVStream *st;
183 int iobuf_size = force_iobuf_size ? force_iobuf_size : sizeof(iobuf);183 int iobuf_size = force_iobuf_size ? force_iobuf_size : sizeof(iobuf);
@@ -225,6 +225,17 @@ static void init_fps(int bf, int audio_preroll, int fps) · 41 unmodified lines
225 memcpy(st->codecpar->extradata, aac_extradata, sizeof(aac_extradata));225 memcpy(st->codecpar->extradata, aac_extradata, sizeof(aac_extradata));
226 audio_st = st;226 audio_st = st;
227 227
228 if (id3) {
229 st = avformat_new_stream(ctx, NULL);
230 if (!st)
231 exit(1);
232 st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
233 st->codecpar->codec_id = AV_CODEC_ID_TIMED_ID3;
234 st->time_base.num = 1;
235 st->time_base.den = 1000;
236 id3_st = st;
237 }
238
228 if (avformat_write_header(ctx, &opts) < 0)239 if (avformat_write_header(ctx, &opts) < 0)
229 exit(1);240 exit(1);
230 av_dict_free(&opts);241 av_dict_free(&opts);
@@ -243,7 +254,7 @@ static void init_fps(int bf, int audio_preroll, int fps) · 12 unmodified lines
243 254
244static void init(int bf, int audio_preroll)255static void init(int bf, int audio_preroll)
245{256{
246 init_fps(bf, audio_preroll, 30);257 init_fps(bf, audio_preroll, 30, 0);
247}258}
248 259
249static void mux_frames(int n, int c)260static void mux_frames(int n, int c)
@@ -314,6 +325,23 @@ static void mux_frames(int n, int c) · 64 unmodified lines
314 }325 }
315}326}
316 327
328static void mux_id3(void)
329{
330 uint8_t pktdata[8] = { 0 };
331 av_packet_unref(pkt);
332
333 pkt->dts = pkt->pts = av_rescale_q(video_dts + (bframes ? duration : 0),
334 video_st->time_base, id3_st->time_base);
335 pkt->stream_index = id3_st->index;
336 pkt->duration = 0;
337
338 AV_WB32(pktdata + 4, pkt->pts);
339 pkt->data = pktdata;
340 pkt->size = 8;
341
342 av_write_frame(ctx, pkt);
343}
344
317static void mux_gops(int n)345static void mux_gops(int n)
318{346{
319 mux_frames(gop_size * n, 0);347 mux_frames(gop_size * n, 0);
@@ -702,7 +730,7 @@ int main(int argc, char **argv) · 382 unmodified lines
702 // by the edit list.730 // by the edit list.
703 init_out("vfr");731 init_out("vfr");
704 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0);732 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0);
705 init_fps(1, 1, 3);733 init_fps(1, 1, 3, 0);
706 mux_frames(gop_size/2, 0);734 mux_frames(gop_size/2, 0);
707 duration /= 10;735 duration /= 10;
708 mux_frames(gop_size/2, 0);736 mux_frames(gop_size/2, 0);
@@ -721,7 +749,7 @@ int main(int argc, char **argv) · 12 unmodified lines
721 clear_duration = 1;749 clear_duration = 1;
722 init_out("vfr-noduration");750 init_out("vfr-noduration");
723 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0);751 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0);
724 init_fps(1, 1, 3);752 init_fps(1, 1, 3, 0);
725 mux_frames(gop_size/2, 0);753 mux_frames(gop_size/2, 0);
726 duration /= 10;754 duration /= 10;
727 mux_frames(gop_size/2, 0);755 mux_frames(gop_size/2, 0);
@@ -737,7 +765,7 @@ int main(int argc, char **argv) · 9 unmodified lines
737 force_iobuf_size = 1500;765 force_iobuf_size = 1500;
738 init_out("large_frag");766 init_out("large_frag");
739 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0);767 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0);
740 init_fps(1, 1, 3);768 init_fps(1, 1, 3, 0);
741 mux_gops(2);769 mux_gops(2);
742 finish();770 finish();
743 close_out();771 close_out();
@@ -751,7 +779,7 @@ int main(int argc, char **argv) · 7 unmodified lines
751 init_out("vfr-noduration-interleave");779 init_out("vfr-noduration-interleave");
752 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0);780 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0);
753 av_dict_set(&opts, "frag_duration", "650000", 0);781 av_dict_set(&opts, "frag_duration", "650000", 0);
754 init_fps(1, 1, 30);782 init_fps(1, 1, 30, 0);
755 mux_frames(gop_size/2, 0);783 mux_frames(gop_size/2, 0);
756 // Pretend that the packet duration is the normal, even if784 // Pretend that the packet duration is the normal, even if
757 // we actually skip a bunch of frames. (I.e., simulate that785 // we actually skip a bunch of frames. (I.e., simulate that
@@ -788,6 +816,19 @@ int main(int argc, char **argv) · 30 unmodified lines
788 finish();816 finish();
789 close_out();817 close_out();
790 818
819 // Write a manually fragmented file, with timed ID3 packets at the head
820 // of each fragment.
821 init_out("emsg");
822 av_dict_set(&opts, "movflags", "+frag_custom+cmaf", 0);
823 init_fps(1, 0, 30, 1);
824 mux_id3();
825 mux_gops(2);
826 av_write_frame(ctx, NULL); // Flush fragment.
827 mux_id3();
828 mux_gops(2);
829 finish();
830 close_out();
831
791 av_free(md5);832 av_free(md5);
792 av_packet_free(&pkt);833 av_packet_free(&pkt);
793 834
tests/ref/fate/movenc+8−0
@@ -151,3 +151,11 @@ write_data len 900, time 0, type sync atom moof · 150 unmodified lines
151write_data len 908, time 1000000, type sync atom moof151write_data len 908, time 1000000, type sync atom moof
152write_data len 148, time nopts, type trailer atom -152write_data len 148, time nopts, type trailer atom -
1533be575022e446855bca1e45b7942cc0c 3115 empty-moov-neg-cts1533be575022e446855bca1e45b7942cc0c 3115 empty-moov-neg-cts
154write_data len 28, time nopts, type header atom ftyp
155write_data len 1123, time nopts, type header atom -
156write_data len 70, time 0, type boundary atom emsg
157write_data len 1832, time 0, type sync atom moof
158write_data len 70, time 2000000, type boundary atom emsg
159write_data len 1840, time 2000000, type sync atom moof
160write_data len 148, time nopts, type trailer atom -
161b72c56c795693820b156f452354a51ff 5111 emsg

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t