History

avformat/movenc: add support for TTML muxing

Includes basic support for both the ISMV ('dfxp') and MP4 ('stpp') methods. This initial version also foregoes fragmentation support in case the built-in sample squashing is to be utilized, as this eases the initial review. Additionally, add basic tests for both muxing modes in MP4.

Jan Ekström committed 5 years ago

Commit e41bd071 parent 460beb99 files · +480 −3Browse files

Unified · Split

9 files
  1. libavformat/Makefile +1 −1
  2. libavformat/isom.h +3 −0
  3. libavformat/movenc.c +177 −2
  4. libavformat/movenc.h +5 −0
  5. libavformat/movenc_ttml.c +171 −0
  6. libavformat/movenc_ttml.h +31 −0
  7. tests/fate/subtitles.mak +4 −0
  8. tests/ref/fate/sub-ttml-mp4-dfxp +44 −0
  9. tests/ref/fate/sub-ttml-mp4-stpp +44 −0
libavformat/Makefile+1−1
@@ -337,7 +337,7 @@ OBJS-$(CONFIG_MOV_DEMUXER) += mov.o mov_chan.o mov_esds.o \ · 336 unmodified lines
337 qtpalette.o replaygain.o337 qtpalette.o replaygain.o
338OBJS-$(CONFIG_MOV_MUXER) += movenc.o av1.o avc.o hevc.o vpcc.o \338OBJS-$(CONFIG_MOV_MUXER) += movenc.o av1.o avc.o hevc.o vpcc.o \
339 movenchint.o mov_chan.o rtp.o \339 movenchint.o mov_chan.o rtp.o \
340 movenccenc.o rawutils.o340 movenccenc.o movenc_ttml.o rawutils.o
341OBJS-$(CONFIG_MP2_MUXER) += rawenc.o341OBJS-$(CONFIG_MP2_MUXER) += rawenc.o
342OBJS-$(CONFIG_MP3_DEMUXER) += mp3dec.o replaygain.o342OBJS-$(CONFIG_MP3_DEMUXER) += mp3dec.o replaygain.o
343OBJS-$(CONFIG_MP3_MUXER) += mp3enc.o rawenc.o id3v2enc.o343OBJS-$(CONFIG_MP3_MUXER) += mp3enc.o rawenc.o id3v2enc.o
libavformat/isom.h+3−0
@@ -387,4 +387,7 @@ static inline enum AVCodecID ff_mov_get_lpcm_codec_id(int bps, int flags) · 386 unmodified lines
387 return ff_get_pcm_codec_id(bps, flags & 1, flags & 2, flags & 4 ? -1 : 0);387 return ff_get_pcm_codec_id(bps, flags & 1, flags & 2, flags & 4 ? -1 : 0);
388}388}
389 389
390#define MOV_ISMV_TTML_TAG MKTAG('d', 'f', 'x', 'p')
391#define MOV_MP4_TTML_TAG MKTAG('s', 't', 'p', 'p')
392
390#endif /* AVFORMAT_ISOM_H */393#endif /* AVFORMAT_ISOM_H */
libavformat/movenc.c+177−2
@@ -57,6 +57,8 @@ · 56 unmodified lines
57#include "hevc.h"57#include "hevc.h"
58#include "rtpenc.h"58#include "rtpenc.h"
59#include "mov_chan.h"59#include "mov_chan.h"
60#include "movenc_ttml.h"
61#include "ttmlenc.h"
60#include "vpcc.h"62#include "vpcc.h"
61 63
62static const AVOption options[] = {64static const AVOption options[] = {
@@ -120,6 +122,7 @@ static const AVClass mov_isobmff_muxer_class = { · 57 unmodified lines
120};122};
121 123
122static int get_moov_size(AVFormatContext *s);124static int get_moov_size(AVFormatContext *s);
125static int mov_write_single_packet(AVFormatContext *s, AVPacket *pkt);
123 126
124static int utf8len(const uint8_t *b)127static int utf8len(const uint8_t *b)
125{128{
@@ -1788,7 +1791,29 @@ static int mov_write_subtitle_tag(AVIOContext *pb, MOVTrack *track) · 1662 unmodified lines
1788 1791
1789 if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE)1792 if (track->par->codec_id == AV_CODEC_ID_DVD_SUBTITLE)
1790 mov_write_esds_tag(pb, track);1793 mov_write_esds_tag(pb, track);
1791 else if (track->par->extradata_size)1794 else if (track->par->codec_id == AV_CODEC_ID_TTML) {
1795 switch (track->par->codec_tag) {
1796 case MOV_ISMV_TTML_TAG:
1797 // ISMV dfxp requires no extradata.
1798 break;
1799 case MOV_MP4_TTML_TAG:
1800 // As specified in 14496-30, XMLSubtitleSampleEntry
1801 // Namespace
1802 avio_put_str(pb, "http://www.w3.org/ns/ttml");
1803 // Empty schema_location
1804 avio_w8(pb, 0);
1805 // Empty auxiliary_mime_types
1806 avio_w8(pb, 0);
1807 break;
1808 default:
1809 av_log(NULL, AV_LOG_ERROR,
1810 "Unknown codec tag '%s' utilized for TTML stream with "
1811 "index %d (track id %d)!\n",
1812 av_fourcc2str(track->par->codec_tag), track->st->index,
1813 track->track_id);
1814 return AVERROR(EINVAL);
1815 }
1816 } else if (track->par->extradata_size)
1792 avio_write(pb, track->par->extradata, track->par->extradata_size);1817 avio_write(pb, track->par->extradata, track->par->extradata_size);
1793 1818
1794 if (track->mode == MODE_MP4 &&1819 if (track->mode == MODE_MP4 &&
@@ -2662,6 +2687,14 @@ static int mov_write_nmhd_tag(AVIOContext *pb) · 867 unmodified lines
2662 return 12;2687 return 12;
2663}2688}
2664 2689
2690static int mov_write_sthd_tag(AVIOContext *pb)
2691{
2692 avio_wb32(pb, 12);
2693 ffio_wfourcc(pb, "sthd");
2694 avio_wb32(pb, 0);
2695 return 12;
2696}
2697
2665static int mov_write_tcmi_tag(AVIOContext *pb, MOVTrack *track)2698static int mov_write_tcmi_tag(AVIOContext *pb, MOVTrack *track)
2666{2699{
2667 int64_t pos = avio_tell(pb);2700 int64_t pos = avio_tell(pb);
@@ -2788,6 +2821,8 @@ static int mov_write_hdlr_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *tra · 120 unmodified lines
2788 hdlr_type = "sbtl";2821 hdlr_type = "sbtl";
2789 } else if (track->tag == MKTAG('m','p','4','s')) {2822 } else if (track->tag == MKTAG('m','p','4','s')) {
2790 hdlr_type = "subp";2823 hdlr_type = "subp";
2824 } else if (track->tag == MOV_MP4_TTML_TAG) {
2825 hdlr_type = "subt";
2791 } else {2826 } else {
2792 hdlr_type = "text";2827 hdlr_type = "text";
2793 }2828 }
@@ -2866,6 +2901,8 @@ static int mov_write_minf_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext · 72 unmodified lines
2866 else if (track->par->codec_type == AVMEDIA_TYPE_SUBTITLE) {2901 else if (track->par->codec_type == AVMEDIA_TYPE_SUBTITLE) {
2867 if (track->tag == MKTAG('t','e','x','t') || is_clcp_track(track)) {2902 if (track->tag == MKTAG('t','e','x','t') || is_clcp_track(track)) {
2868 mov_write_gmhd_tag(pb, track);2903 mov_write_gmhd_tag(pb, track);
2904 } else if (track->tag == MOV_MP4_TTML_TAG) {
2905 mov_write_sthd_tag(pb);
2869 } else {2906 } else {
2870 mov_write_nmhd_tag(pb);2907 mov_write_nmhd_tag(pb);
2871 }2908 }
@@ -5251,6 +5288,68 @@ static int mov_flush_fragment_interleaving(AVFormatContext *s, MOVTrack *track) · 2379 unmodified lines
5251 return 0;5288 return 0;
5252}5289}
5253 5290
5291static int mov_write_squashed_packet(AVFormatContext *s, MOVTrack *track)
5292{
5293 MOVMuxContext *mov = s->priv_data;
5294 AVPacket *squashed_packet = mov->pkt;
5295 int ret = AVERROR_BUG;
5296
5297 switch (track->st->codecpar->codec_id) {
5298 case AV_CODEC_ID_TTML: {
5299 int had_packets = !!track->squashed_packet_queue;
5300
5301 if ((ret = ff_mov_generate_squashed_ttml_packet(s, track, squashed_packet)) < 0) {
5302 goto finish_squash;
5303 }
5304
5305 // We have generated a padding packet (no actual input packets in
5306 // queue) and its duration is zero. Skipping writing it.
5307 if (!had_packets && squashed_packet->duration == 0) {
5308 goto finish_squash;
5309 }
5310
5311 track->end_reliable = 1;
5312 break;
5313 }
5314 default:
5315 ret = AVERROR(EINVAL);
5316 goto finish_squash;
5317 }
5318
5319 squashed_packet->stream_index = track->st->index;
5320
5321 ret = mov_write_single_packet(s, squashed_packet);
5322
5323finish_squash:
5324 av_packet_unref(squashed_packet);
5325
5326 return ret;
5327}
5328
5329static int mov_write_squashed_packets(AVFormatContext *s)
5330{
5331 MOVMuxContext *mov = s->priv_data;
5332
5333 for (int i = 0; i < s->nb_streams; i++) {
5334 MOVTrack *track = &mov->tracks[i];
5335 int ret = AVERROR_BUG;
5336
5337 if (track->squash_fragment_samples_to_one && !track->entry) {
5338 if ((ret = mov_write_squashed_packet(s, track)) < 0) {
5339 av_log(s, AV_LOG_ERROR,
5340 "Failed to write squashed packet for %s stream with "
5341 "index %d and track id %d. Error: %s\n",
5342 avcodec_get_name(track->st->codecpar->codec_id),
5343 track->st->index, track->track_id,
5344 av_err2str(ret));
5345 return ret;
5346 }
5347 }
5348 }
5349
5350 return 0;
5351}
5352
5254static int mov_flush_fragment(AVFormatContext *s, int force)5353static int mov_flush_fragment(AVFormatContext *s, int force)
5255{5354{
5256 MOVMuxContext *mov = s->priv_data;5355 MOVMuxContext *mov = s->priv_data;
@@ -5262,6 +5361,11 @@ static int mov_flush_fragment(AVFormatContext *s, int force) · 5 unmodified lines
5262 if (!(mov->flags & FF_MOV_FLAG_FRAGMENT))5361 if (!(mov->flags & FF_MOV_FLAG_FRAGMENT))
5263 return 0;5362 return 0;
5264 5363
5364 // Check if we have any tracks that require squashing.
5365 // In that case, we'll have to write the packet here.
5366 if ((ret = mov_write_squashed_packets(s)) < 0)
5367 return ret;
5368
5265 // Try to fill in the duration of the last packet in each stream5369 // Try to fill in the duration of the last packet in each stream
5266 // from queued packets in the interleave queues. If the flushing5370 // from queued packets in the interleave queues. If the flushing
5267 // of fragments was triggered automatically by an AVPacket, we5371 // of fragments was triggered automatically by an AVPacket, we
@@ -5727,7 +5831,8 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) · 459 unmodified lines
5727 trk->cluster[trk->entry].entries = samples_in_chunk;5831 trk->cluster[trk->entry].entries = samples_in_chunk;
5728 trk->cluster[trk->entry].dts = pkt->dts;5832 trk->cluster[trk->entry].dts = pkt->dts;
5729 trk->cluster[trk->entry].pts = pkt->pts;5833 trk->cluster[trk->entry].pts = pkt->pts;
5730 if (!trk->entry && trk->start_dts != AV_NOPTS_VALUE) {5834 if (!trk->squash_fragment_samples_to_one &&
5835 !trk->entry && trk->start_dts != AV_NOPTS_VALUE) {
5731 if (!trk->frag_discont) {5836 if (!trk->frag_discont) {
5732 /* First packet of a new fragment. We already wrote the duration5837 /* First packet of a new fragment. We already wrote the duration
5733 * of the last packet of the previous fragment based on track_duration,5838 * of the last packet of the previous fragment based on track_duration,
@@ -6022,6 +6127,33 @@ static int mov_write_packet(AVFormatContext *s, AVPacket *pkt) · 288 unmodified lines
6022 }6127 }
6023 }6128 }
6024 6129
6130 if (trk->squash_fragment_samples_to_one) {
6131 /*
6132 * If the track has to have its samples squashed into one sample,
6133 * we just take it into the track's queue.
6134 * This will then be utilized as the samples get written in either
6135 * mov_flush_fragment or when the mux is finalized in
6136 * mov_write_trailer.
6137 */
6138 int ret = AVERROR_BUG;
6139
6140 if (pkt->pts == AV_NOPTS_VALUE) {
6141 av_log(s, AV_LOG_ERROR,
6142 "Packets without a valid presentation timestamp are "
6143 "not supported with packet squashing!\n");
6144 return AVERROR(EINVAL);
6145 }
6146
6147 if ((ret = avpriv_packet_list_put(&trk->squashed_packet_queue,
6148 &trk->squashed_packet_queue_end,
6149 pkt, av_packet_ref, 0)) < 0) {
6150 return ret;
6151 }
6152
6153 return 0;
6154 }
6155
6156
6025 if (trk->mode == MODE_MOV && trk->par->codec_type == AVMEDIA_TYPE_VIDEO) {6157 if (trk->mode == MODE_MOV && trk->par->codec_type == AVMEDIA_TYPE_VIDEO) {
6026 AVPacket *opkt = pkt;6158 AVPacket *opkt = pkt;
6027 int reshuffle_ret, ret;6159 int reshuffle_ret, ret;
@@ -6300,6 +6432,11 @@ static void mov_free(AVFormatContext *s) · 272 unmodified lines
6300 6432
6301 ff_mov_cenc_free(&mov->tracks[i].cenc);6433 ff_mov_cenc_free(&mov->tracks[i].cenc);
6302 ffio_free_dyn_buf(&mov->tracks[i].mdat_buf);6434 ffio_free_dyn_buf(&mov->tracks[i].mdat_buf);
6435
6436 if (mov->tracks[i].squashed_packet_queue) {
6437 avpriv_packet_list_free(&(mov->tracks[i].squashed_packet_queue),
6438 &(mov->tracks[i].squashed_packet_queue_end));
6439 }
6303 }6440 }
6304 6441
6305 av_freep(&mov->tracks);6442 av_freep(&mov->tracks);
@@ -6690,6 +6827,36 @@ static int mov_init(AVFormatContext *s) · 384 unmodified lines
6690 }6827 }
6691 } else if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {6828 } else if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
6692 track->timescale = st->time_base.den;6829 track->timescale = st->time_base.den;
6830
6831 if (track->par->codec_id == AV_CODEC_ID_TTML) {
6832 /* 14496-30 requires us to use a single sample per fragment
6833 for TTML, for which we define a per-track flag.
6834
6835 We set the flag in case we are receiving TTML paragraphs
6836 from the input, in other words in case we are not doing
6837 stream copy. */
6838 track->squash_fragment_samples_to_one =
6839 ff_is_ttml_stream_paragraph_based(track->par);
6840
6841 if (mov->flags & FF_MOV_FLAG_FRAGMENT &&
6842 track->squash_fragment_samples_to_one) {
6843 av_log(s, AV_LOG_ERROR,
6844 "Fragmentation is not currently supported for "
6845 "TTML in MP4/ISMV (track synchronization between "
6846 "subtitles and other media is not yet implemented)!\n");
6847 return AVERROR_PATCHWELCOME;
6848 }
6849
6850 if (track->mode != MODE_ISM &&
6851 track->par->codec_tag == MOV_ISMV_TTML_TAG &&
6852 s->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL) {
6853 av_log(s, AV_LOG_ERROR,
6854 "ISMV style TTML support with the 'dfxp' tag in "
6855 "non-ISMV formats is not officially supported. Add "
6856 "'-strict unofficial' if you want to use it.\n");
6857 return AVERROR_EXPERIMENTAL;
6858 }
6859 }
6693 } else if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA) {6860 } else if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA) {
6694 track->timescale = st->time_base.den;6861 track->timescale = st->time_base.den;
6695 } else {6862 } else {
@@ -7036,6 +7203,11 @@ static int mov_write_trailer(AVFormatContext *s) · 340 unmodified lines
7036 }7203 }
7037 }7204 }
7038 7205
7206 // Check if we have any tracks that require squashing.
7207 // In that case, we'll have to write the packet here.
7208 if ((res = mov_write_squashed_packets(s)) < 0)
7209 return res;
7210
7039 // If there were no chapters when the header was written, but there7211 // If there were no chapters when the header was written, but there
7040 // are chapters now, write them in the trailer. This only works7212 // are chapters now, write them in the trailer. This only works
7041 // when we are not doing fragments.7213 // when we are not doing fragments.
@@ -7180,6 +7352,8 @@ static const AVCodecTag codec_mp4_tags[] = { · 138 unmodified lines
7180 { AV_CODEC_ID_MOV_TEXT, MKTAG('t', 'x', '3', 'g') },7352 { AV_CODEC_ID_MOV_TEXT, MKTAG('t', 'x', '3', 'g') },
7181 { AV_CODEC_ID_BIN_DATA, MKTAG('g', 'p', 'm', 'd') },7353 { AV_CODEC_ID_BIN_DATA, MKTAG('g', 'p', 'm', 'd') },
7182 { AV_CODEC_ID_MPEGH_3D_AUDIO, MKTAG('m', 'h', 'm', '1') },7354 { AV_CODEC_ID_MPEGH_3D_AUDIO, MKTAG('m', 'h', 'm', '1') },
7355 { AV_CODEC_ID_TTML, MOV_MP4_TTML_TAG },
7356 { AV_CODEC_ID_TTML, MOV_ISMV_TTML_TAG },
7183 { AV_CODEC_ID_NONE, 0 },7357 { AV_CODEC_ID_NONE, 0 },
7184};7358};
7185#if CONFIG_MP4_MUXER || CONFIG_PSP_MUXER7359#if CONFIG_MP4_MUXER || CONFIG_PSP_MUXER
@@ -7188,6 +7362,7 @@ static const AVCodecTag *const mp4_codec_tags_list[] = { codec_mp4_tags, NULL }; · 2 unmodified lines
7188 7362
7189static const AVCodecTag codec_ism_tags[] = {7363static const AVCodecTag codec_ism_tags[] = {
7190 { AV_CODEC_ID_WMAPRO , MKTAG('w', 'm', 'a', ' ') },7364 { AV_CODEC_ID_WMAPRO , MKTAG('w', 'm', 'a', ' ') },
7365 { AV_CODEC_ID_TTML , MOV_ISMV_TTML_TAG },
7191 { AV_CODEC_ID_NONE , 0 },7366 { AV_CODEC_ID_NONE , 0 },
7192};7367};
7193 7368
libavformat/movenc.h+5−0
@@ -26,6 +26,7 @@ · 25 unmodified lines
26 26
27#include "avformat.h"27#include "avformat.h"
28#include "movenccenc.h"28#include "movenccenc.h"
29#include "libavcodec/packet_internal.h"
29 30
30#define MOV_FRAG_INFO_ALLOC_INCREMENT 6431#define MOV_FRAG_INFO_ALLOC_INCREMENT 64
31#define MOV_INDEX_CLUSTER_SIZE 102432#define MOV_INDEX_CLUSTER_SIZE 1024
@@ -163,6 +164,10 @@ typedef struct MOVTrack { · 131 unmodified lines
163 int pal_done;164 int pal_done;
164 165
165 int is_unaligned_qt_rgb;166 int is_unaligned_qt_rgb;
167
168 unsigned int squash_fragment_samples_to_one; //< flag to note formats where all samples for a fragment are to be squashed
169
170 PacketList *squashed_packet_queue, *squashed_packet_queue_end;
166} MOVTrack;171} MOVTrack;
167 172
168typedef enum {173typedef enum {
libavformat/movenc_ttml.c+171−0
@@ -0,0 +1,171 @@
1/*
2 * MP4, ISMV Muxer TTML helpers
3 * Copyright (c) 2021 24i
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include "avformat.h"
23#include "avio_internal.h"
24#include "isom.h"
25#include "movenc.h"
26#include "movenc_ttml.h"
27#include "libavcodec/packet_internal.h"
28
29static const unsigned char empty_ttml_document[] =
30 "<tt xml:lang=\"\" xmlns=\"http://www.w3.org/ns/ttml\" />";
31
32static int mov_init_ttml_writer(MOVTrack *track, AVFormatContext **out_ctx)
33{
34 AVStream *movenc_stream = track->st, *ttml_stream = NULL;
35 int ret = AVERROR_BUG;
36
37 if ((ret = avformat_alloc_output_context2(out_ctx, NULL,
38 "ttml", NULL)) < 0)
39 return ret;
40
41 if ((ret = avio_open_dyn_buf(&(*out_ctx)->pb)) < 0)
42 return ret;
43
44 if (!(ttml_stream = avformat_new_stream(*out_ctx, NULL))) {
45 return AVERROR(ENOMEM);
46 }
47
48 if ((ret = avcodec_parameters_copy(ttml_stream->codecpar,
49 movenc_stream->codecpar)) < 0)
50 return ret;
51
52 ttml_stream->time_base = movenc_stream->time_base;
53
54 return 0;
55}
56
57static int mov_write_ttml_document_from_queue(AVFormatContext *s,
58 AVFormatContext *ttml_ctx,
59 MOVTrack *track,
60 AVPacket *pkt,
61 int64_t *out_start_ts,
62 int64_t *out_duration)
63{
64 int ret = AVERROR_BUG;
65 int64_t start_ts = track->start_dts == AV_NOPTS_VALUE ?
66 0 : (track->start_dts + track->track_duration);
67 int64_t end_ts = start_ts;
68
69 if ((ret = avformat_write_header(ttml_ctx, NULL)) < 0) {
70 return ret;
71 }
72
73 while (!avpriv_packet_list_get(&track->squashed_packet_queue,
74 &track->squashed_packet_queue_end,
75 pkt)) {
76 end_ts = FFMAX(end_ts, pkt->pts + pkt->duration);
77
78 // in case of the 'dfxp' muxing mode, each written document is offset
79 // to its containing sample's beginning.
80 if (track->par->codec_tag == MOV_ISMV_TTML_TAG) {
81 pkt->dts = pkt->pts = (pkt->pts - start_ts);
82 }
83
84 pkt->stream_index = 0;
85
86 av_packet_rescale_ts(pkt, track->st->time_base,
87 ttml_ctx->streams[pkt->stream_index]->time_base);
88
89 if ((ret = av_write_frame(ttml_ctx, pkt)) < 0) {
90 goto cleanup;
91 }
92
93 av_packet_unref(pkt);
94 }
95
96 if ((ret = av_write_trailer(ttml_ctx)) < 0)
97 goto cleanup;
98
99 *out_start_ts = start_ts;
100 *out_duration = end_ts - start_ts;
101
102 ret = 0;
103
104cleanup:
105 return ret;
106}
107
108int ff_mov_generate_squashed_ttml_packet(AVFormatContext *s,
109 MOVTrack *track, AVPacket *pkt)
110{
111 AVFormatContext *ttml_ctx = NULL;
112 // values for the generated AVPacket
113 int64_t start_ts = 0;
114 int64_t duration = 0;
115
116 int ret = AVERROR_BUG;
117
118 if ((ret = mov_init_ttml_writer(track, &ttml_ctx)) < 0) {
119 av_log(s, AV_LOG_ERROR, "Failed to initialize the TTML writer: %s\n",
120 av_err2str(ret));
121 goto cleanup;
122 }
123
124 if (!track->squashed_packet_queue) {
125 // empty queue, write minimal empty document with zero duration
126 avio_write(ttml_ctx->pb, empty_ttml_document,
127 sizeof(empty_ttml_document) - 1);
128 start_ts = 0;
129 duration = 0;
130 goto generate_packet;
131 }
132
133 if ((ret = mov_write_ttml_document_from_queue(s, ttml_ctx, track, pkt,
134 &start_ts,
135 &duration)) < 0) {
136 av_log(s, AV_LOG_ERROR,
137 "Failed to generate a squashed TTML packet from the packet "
138 "queue: %s\n",
139 av_err2str(ret));
140 goto cleanup;
141 }
142
143generate_packet:
144 {
145 // Generate an AVPacket from the data written into the dynamic buffer.
146 uint8_t *buf = NULL;
147 int buf_len = avio_close_dyn_buf(ttml_ctx->pb, &buf);
148 ttml_ctx->pb = NULL;
149
150 if ((ret = av_packet_from_data(pkt, buf, buf_len)) < 0) {
151 av_log(s, AV_LOG_ERROR,
152 "Failed to create a TTML AVPacket from AVIO data: %s\n",
153 av_err2str(ret));
154 av_freep(&buf);
155 goto cleanup;
156 }
157
158 pkt->pts = pkt->dts = start_ts;
159 pkt->duration = duration;
160 pkt->flags |= AV_PKT_FLAG_KEY;
161 }
162
163 ret = 0;
164
165cleanup:
166 if (ttml_ctx)
167 ffio_free_dyn_buf(&ttml_ctx->pb);
168
169 avformat_free_context(ttml_ctx);
170 return ret;
171}
libavformat/movenc_ttml.h+31−0
@@ -0,0 +1,31 @@
1/*
2 * MP4, ISMV Muxer TTML helpers
3 * Copyright (c) 2021 24i
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * FFmpeg is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#ifndef AVFORMAT_MOVENC_TTML_H
23#define AVFORMAT_MOVENC_TTML_H
24
25#include "avformat.h"
26#include "movenc.h"
27
28int ff_mov_generate_squashed_ttml_packet(AVFormatContext *s,
29 MOVTrack *track, AVPacket *pkt);
30
31#endif /* AVFORMAT_MOVENC_TTML_H */
tests/fate/subtitles.mak+4−0
@@ -109,6 +109,10 @@ fate-sub-dvb: CMD = framecrc -i $(TARGET_SAMPLES)/sub/dvbsubtest_filter.ts -map · 108 unmodified lines
109FATE_SUBTITLES-$(call ALLYES, FILE_PROTOCOL PIPE_PROTOCOL SRT_DEMUXER SUBRIP_DECODER TTML_ENCODER TTML_MUXER) += fate-sub-ttmlenc109FATE_SUBTITLES-$(call ALLYES, FILE_PROTOCOL PIPE_PROTOCOL SRT_DEMUXER SUBRIP_DECODER TTML_ENCODER TTML_MUXER) += fate-sub-ttmlenc
110fate-sub-ttmlenc: CMD = fmtstdout ttml -i $(TARGET_SAMPLES)/sub/SubRip_capability_tester.srt110fate-sub-ttmlenc: CMD = fmtstdout ttml -i $(TARGET_SAMPLES)/sub/SubRip_capability_tester.srt
111 111
112FATE_SUBTITLES-$(call ALLYES, FILE_PROTOCOL SRT_DEMUXER MOV_DEMUXER SUBRIP_DECODER TTML_ENCODER TTML_MUXER MOV_MUXER) += fate-sub-ttml-mp4-stpp fate-sub-ttml-mp4-dfxp
113fate-sub-ttml-mp4-stpp: CMD = transcode srt $(TARGET_SAMPLES)/sub/SubRip_capability_tester.srt mp4 "-map 0:s -c:s ttml -time_base:s 1:1000" "-map 0 -c copy" "" "-of json -show_entries packet:stream=index,codec_type,codec_tag_string,codec_tag,codec_name,time_base,start_time,duration_ts,duration,nb_frames,nb_read_packets:stream_tags"
114fate-sub-ttml-mp4-dfxp: CMD = transcode srt $(TARGET_SAMPLES)/sub/SubRip_capability_tester.srt mp4 "-map 0:s -c:s ttml -time_base:s 1:1000 -tag:s dfxp -strict unofficial" "-map 0 -c copy" "" "-of json -show_entries packet:stream=index,codec_type,codec_tag_string,codec_tag,codec_name,time_base,start_time,duration_ts,duration,nb_frames,nb_read_packets:stream_tags"
115
112FATE_SUBTITLES-$(call ENCMUX, ASS, ASS) += $(FATE_SUBTITLES_ASS-yes)116FATE_SUBTITLES-$(call ENCMUX, ASS, ASS) += $(FATE_SUBTITLES_ASS-yes)
113FATE_SUBTITLES += $(FATE_SUBTITLES-yes)117FATE_SUBTITLES += $(FATE_SUBTITLES-yes)
114 118
tests/ref/fate/sub-ttml-mp4-dfxp+44−0
@@ -0,0 +1,44 @@
12e7e01c821c111466e7a2844826b7f6d *tests/data/fate/sub-ttml-mp4-dfxp.mp4
28519 tests/data/fate/sub-ttml-mp4-dfxp.mp4
3#tb 0: 1/1000
4#media_type 0: data
5#codec_id 0: none
60, 0, 0, 68500, 7866, 0x456c36b7
7{
8 "packets": [
9 {
10 "codec_type": "data",
11 "stream_index": 0,
12 "pts": 0,
13 "pts_time": "0.000000",
14 "dts": 0,
15 "dts_time": "0.000000",
16 "duration": 68500,
17 "duration_time": "68.500000",
18 "size": "7866",
19 "pos": "44",
20 "flags": "K_"
21 }
22 ],
23 "programs": [
24
25 ],
26 "streams": [
27 {
28 "index": 0,
29 "codec_type": "data",
30 "codec_tag_string": "dfxp",
31 "codec_tag": "0x70786664",
32 "time_base": "1/1000",
33 "start_time": "0.000000",
34 "duration_ts": 68500,
35 "duration": "68.500000",
36 "nb_frames": "1",
37 "nb_read_packets": "1",
38 "tags": {
39 "language": "und",
40 "handler_name": "SubtitleHandler"
41 }
42 }
43 ]
44}
tests/ref/fate/sub-ttml-mp4-stpp+44−0
@@ -0,0 +1,44 @@
1cbd2c7ff864a663b0d893deac5a0caec *tests/data/fate/sub-ttml-mp4-stpp.mp4
28547 tests/data/fate/sub-ttml-mp4-stpp.mp4
3#tb 0: 1/1000
4#media_type 0: data
5#codec_id 0: none
60, 0, 0, 68500, 7866, 0x456c36b7
7{
8 "packets": [
9 {
10 "codec_type": "data",
11 "stream_index": 0,
12 "pts": 0,
13 "pts_time": "0.000000",
14 "dts": 0,
15 "dts_time": "0.000000",
16 "duration": 68500,
17 "duration_time": "68.500000",
18 "size": "7866",
19 "pos": "44",
20 "flags": "K_"
21 }
22 ],
23 "programs": [
24
25 ],
26 "streams": [
27 {
28 "index": 0,
29 "codec_type": "data",
30 "codec_tag_string": "stpp",
31 "codec_tag": "0x70707473",
32 "time_base": "1/1000",
33 "start_time": "0.000000",
34 "duration_ts": 68500,
35 "duration": "68.500000",
36 "nb_frames": "1",
37 "nb_read_packets": "1",
38 "tags": {
39 "language": "und",
40 "handler_name": "SubtitleHandler"
41 }
42 }
43 ]
44}

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