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

Showing the changes to libavformat (2 of 3 files). Show all

Unified · Split

2 files
  1. libavformat/movenc.c +48 −1
  2. libavformat/tests/movenc.c +48 −7
libavformat/movenc.c+48−1
@@ -5519,7 +5519,7 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) · 5518 unmodified lines
55195519 {
55205520 MOVMuxContext *mov = s->priv_data;
55215521 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;
55235523 int has_iamf = 0;
55245524
55255525 #if CONFIG_IAMFENC
@@ -5550,6 +5550,8 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) · 24 unmodified lines
55505550 st->codecpar->nb_coded_side_data,
55515551 AV_PKT_DATA_DOVI_CONF))
55525552 has_dolby = 1;
5553+ if (st->codecpar->codec_id == AV_CODEC_ID_TIMED_ID3)
5554+ has_id3 = 1;
55535555 }
55545556
55555557 avio_wb32(pb, 0); /* size */
@@ -5629,6 +5631,9 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) · 73 unmodified lines
56295631 if (mov->flags & FF_MOV_FLAG_DASH && mov->flags & FF_MOV_FLAG_GLOBAL_SIDX)
56305632 ffio_wfourcc(pb, "dash");
56315633
5634+ if (has_id3)
5635+ ffio_wfourcc(pb, "aid3");
5636+
56325637 return update_size(pb, pos);
56335638 }
56345639
@@ -6712,6 +6717,34 @@ static int mov_build_iamf_packet(AVFormatContext *s, MOVTrack *trk, AVPacket *pk · 1077 unmodified lines
67126717 }
67136718 #endif
67146719
6720+static 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+
67156748 static int mov_write_packet(AVFormatContext *s, AVPacket *pkt)
67166749 {
67176750 MOVMuxContext *mov = s->priv_data;
@@ -6722,6 +6755,11 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) · 4 unmodified lines
67226755 return 1;
67236756 }
67246757
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+
67256763 trk = s->streams[pkt->stream_index]->priv_data;
67266764
67276765 #if CONFIG_IAMFENC
@@ -7381,6 +7419,12 @@ static int mov_init(AVFormatContext *s) · 653 unmodified lines
73817419 AVStream *st = s->streams[i];
73827420 if (st->priv_data)
73837421 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+ }
73847428 st->priv_data = st;
73857429 mov->nb_tracks++;
73867430 }
@@ -7480,6 +7524,9 @@ static int mov_init(AVFormatContext *s) · 93 unmodified lines
74807524 MOVTrack *track = st->priv_data;
74817525 AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL,0);
74827526
7527+ if (!track)
7528+ continue;
7529+
74837530 if (!track->st) {
74847531 track->st = st;
74857532 track->par = st->codecpar;
libavformat/tests/movenc.c+48−7
@@ -58,7 +58,7 @@ struct AVMD5* md5; · 57 unmodified lines
5858 uint8_t hash[HASH_SIZE];
5959
6060 AVPacket *pkt;
61−AVStream *video_st, *audio_st;
61+AVStream *video_st, *audio_st, *id3_st;
6262 int64_t audio_dts, video_dts;
6363
6464 int bframes;
@@ -177,7 +177,7 @@ static void check_func(int value, int line, const char *msg, ...) · 112 unmodified lines
177177 }
178178 #define check(value, ...) check_func(value, __LINE__, __VA_ARGS__)
179179
180−static void init_fps(int bf, int audio_preroll, int fps)
180+static void init_fps(int bf, int audio_preroll, int fps, int id3)
181181 {
182182 AVStream *st;
183183 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
225225 memcpy(st->codecpar->extradata, aac_extradata, sizeof(aac_extradata));
226226 audio_st = st;
227227
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+
228239 if (avformat_write_header(ctx, &opts) < 0)
229240 exit(1);
230241 av_dict_free(&opts);
@@ -243,7 +254,7 @@ static void init_fps(int bf, int audio_preroll, int fps) · 12 unmodified lines
243254
244255 static void init(int bf, int audio_preroll)
245256 {
246− init_fps(bf, audio_preroll, 30);
257+ init_fps(bf, audio_preroll, 30, 0);
247258 }
248259
249260 static void mux_frames(int n, int c)
@@ -314,6 +325,23 @@ static void mux_frames(int n, int c) · 64 unmodified lines
314325 }
315326 }
316327
328+static 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+
317345 static void mux_gops(int n)
318346 {
319347 mux_frames(gop_size * n, 0);
@@ -702,7 +730,7 @@ int main(int argc, char **argv) · 382 unmodified lines
702730 // by the edit list.
703731 init_out("vfr");
704732 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0);
705− init_fps(1, 1, 3);
733+ init_fps(1, 1, 3, 0);
706734 mux_frames(gop_size/2, 0);
707735 duration /= 10;
708736 mux_frames(gop_size/2, 0);
@@ -721,7 +749,7 @@ int main(int argc, char **argv) · 12 unmodified lines
721749 clear_duration = 1;
722750 init_out("vfr-noduration");
723751 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov+dash", 0);
724− init_fps(1, 1, 3);
752+ init_fps(1, 1, 3, 0);
725753 mux_frames(gop_size/2, 0);
726754 duration /= 10;
727755 mux_frames(gop_size/2, 0);
@@ -737,7 +765,7 @@ int main(int argc, char **argv) · 9 unmodified lines
737765 force_iobuf_size = 1500;
738766 init_out("large_frag");
739767 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0);
740− init_fps(1, 1, 3);
768+ init_fps(1, 1, 3, 0);
741769 mux_gops(2);
742770 finish();
743771 close_out();
@@ -751,7 +779,7 @@ int main(int argc, char **argv) · 7 unmodified lines
751779 init_out("vfr-noduration-interleave");
752780 av_dict_set(&opts, "movflags", "+frag_keyframe+delay_moov", 0);
753781 av_dict_set(&opts, "frag_duration", "650000", 0);
754− init_fps(1, 1, 30);
782+ init_fps(1, 1, 30, 0);
755783 mux_frames(gop_size/2, 0);
756784 // Pretend that the packet duration is the normal, even if
757785 // we actually skip a bunch of frames. (I.e., simulate that
@@ -788,6 +816,19 @@ int main(int argc, char **argv) · 30 unmodified lines
788816 finish();
789817 close_out();
790818
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+
791832 av_free(md5);
792833 av_packet_free(&pkt);
793834

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