FFmpeg
movenc_ttml.c
Go to the documentation of this file.
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 "libavutil/mem.h"
23 #include "avformat.h"
24 #include "avio_internal.h"
25 #include "isom.h"
26 #include "movenc.h"
27 #include "movenc_ttml.h"
29 
30 static const unsigned char empty_ttml_document[] =
31  "<tt xml:lang=\"\" xmlns=\"http://www.w3.org/ns/ttml\" />";
32 
33 static int mov_init_ttml_writer(MOVTrack *track, AVFormatContext **out_ctx)
34 {
35  AVStream *movenc_stream = track->st, *ttml_stream = NULL;
36  int ret = AVERROR_BUG;
37 
38  if ((ret = avformat_alloc_output_context2(out_ctx, NULL,
39  "ttml", NULL)) < 0)
40  return ret;
41 
42  if ((ret = avio_open_dyn_buf(&(*out_ctx)->pb)) < 0)
43  return ret;
44 
45  if (!(ttml_stream = avformat_new_stream(*out_ctx, NULL))) {
46  return AVERROR(ENOMEM);
47  }
48 
49  if ((ret = avcodec_parameters_copy(ttml_stream->codecpar,
50  movenc_stream->codecpar)) < 0)
51  return ret;
52 
53  ttml_stream->time_base = movenc_stream->time_base;
54 
55  return 0;
56 }
57 
59  AVFormatContext *s, MOVTrack *track, int64_t *start_pts, int64_t *end_pts)
60 {
61  MOVMuxContext *mov = s->priv_data;
62 
63  // Initialize at the end of the previous document/fragment, which is NOPTS
64  // until the first fragment is created.
65  int64_t max_track_end_dts = *start_pts = track->end_pts;
66 
67  for (unsigned int i = 0; i < s->nb_streams; i++) {
68  MOVTrack *other_track = &mov->tracks[i];
69 
70  // Skip our own track, any other track that needs squashing,
71  // or any track which still has its start_dts at NOPTS or
72  // any track that did not yet get any packets.
73  if (track == other_track ||
74  other_track->squash_fragment_samples_to_one ||
75  other_track->start_dts == AV_NOPTS_VALUE ||
76  !other_track->entry) {
77  continue;
78  }
79 
80  int64_t picked_start = av_rescale_q_rnd(other_track->cluster[0].dts + other_track->cluster[0].cts,
81  other_track->st->time_base,
82  track->st->time_base,
84  int64_t picked_end = av_rescale_q_rnd(other_track->end_pts,
85  other_track->st->time_base,
86  track->st->time_base,
88 
89  if (*start_pts == AV_NOPTS_VALUE)
90  *start_pts = picked_start;
91  else if (picked_start >= track->end_pts)
92  *start_pts = FFMIN(*start_pts, picked_start);
93 
94  max_track_end_dts = FFMAX(max_track_end_dts, picked_end);
95  }
96 
97  *end_pts = max_track_end_dts;
98 }
99 
101  AVFormatContext *ttml_ctx,
102  MOVTrack *track,
103  AVPacket *pkt,
104  int64_t *out_start_ts,
105  int64_t *out_duration)
106 {
107  int ret = AVERROR_BUG;
108  int64_t start_ts = track->start_dts == AV_NOPTS_VALUE ?
109  0 : (track->start_dts + track->track_duration);
110  int64_t end_ts = start_ts;
111  unsigned int time_limited = 0;
112  PacketList back_to_queue_list = { 0 };
113 
114  if (*out_start_ts != AV_NOPTS_VALUE) {
115  // we have non-nopts values here, thus we have been given a time range
116  time_limited = 1;
117  start_ts = *out_start_ts;
118  end_ts = *out_start_ts + *out_duration;
119  }
120 
121  if ((ret = avformat_write_header(ttml_ctx, NULL)) < 0) {
122  return ret;
123  }
124 
126  int64_t pts_before = pkt->pts;
127  int64_t duration_before = pkt->duration;
128 
129  if (time_limited) {
130  // special cases first:
131  if (pkt->pts + pkt->duration < start_ts) {
132  // too late for our fragment, unfortunately
133  // unref and proceed to next packet in queue.
135  "Very late TTML packet in queue, dropping packet with "
136  "pts: %"PRId64", duration: %"PRId64"\n",
137  pkt->pts, pkt->duration);
139  continue;
140  } else if (pkt->pts >= end_ts) {
141  // starts after this fragment, put back to original queue
145  if (ret < 0)
146  goto cleanup;
147 
148  break;
149  }
150 
151  // limit packet pts to start_ts
152  if (pkt->pts < start_ts) {
153  pkt->duration -= start_ts - pkt->pts;
154  pkt->pts = start_ts;
155  }
156 
157  if (pkt->pts + pkt->duration > end_ts) {
158  // goes over our current fragment, create duplicate and
159  // put it back to list after iteration has finished in
160  // order to handle multiple subtitles at the same time.
161  int64_t offset = end_ts - pkt->pts;
162 
163  ret = avpriv_packet_list_put(&back_to_queue_list,
166  if (ret < 0)
167  goto cleanup;
168 
169  back_to_queue_list.head->pkt.pts =
170  back_to_queue_list.head->pkt.dts =
171  back_to_queue_list.head->pkt.pts + offset;
172  back_to_queue_list.head->pkt.duration -= offset;
173 
174  // and for our normal packet we just set duration to offset
175  pkt->duration = offset;
176  }
177  } else {
178  end_ts = FFMAX(end_ts, pkt->pts + pkt->duration);
179  }
180 
182  "TTML packet writeout: pts: %"PRId64" (%"PRId64"), "
183  "duration: %"PRId64"\n",
184  pkt->pts, pkt->pts - start_ts, pkt->duration);
185  if (pkt->pts != pts_before || pkt->duration != duration_before) {
187  "Adjustments: pts: %"PRId64", duration: %"PRId64"\n",
188  pkt->pts - pts_before, pkt->duration - duration_before);
189  }
190 
191  // in case of the 'dfxp' muxing mode, each written document is offset
192  // to its containing sample's beginning.
193  if (track->par->codec_tag == MOV_ISMV_TTML_TAG) {
194  pkt->dts = pkt->pts = (pkt->pts - start_ts);
195  }
196 
197  pkt->stream_index = 0;
198 
200  ttml_ctx->streams[pkt->stream_index]->time_base);
201 
202  if ((ret = av_write_frame(ttml_ctx, pkt)) < 0) {
203  goto cleanup;
204  }
205 
207  }
208 
209  if ((ret = av_write_trailer(ttml_ctx)) < 0)
210  goto cleanup;
211 
212  *out_start_ts = start_ts;
213  *out_duration = end_ts - start_ts;
214 
215  ret = 0;
216 
217 cleanup:
218  while (!avpriv_packet_list_get(&back_to_queue_list, pkt)) {
222 
223  // unrelated to whether we succeed or not, we unref the packet
224  // received from the temporary list.
226 
227  if (ret < 0) {
228  avpriv_packet_list_free(&back_to_queue_list);
229  break;
230  }
231  }
232  return ret;
233 }
234 
236  MOVTrack *track, AVPacket *pkt)
237 {
238  MOVMuxContext *mov = s->priv_data;
239  AVFormatContext *ttml_ctx = NULL;
240  // values for the generated AVPacket
241  int64_t start_ts = AV_NOPTS_VALUE;
242  int64_t duration = 0;
243 
244  int ret = AVERROR_BUG;
245 
246  if ((ret = mov_init_ttml_writer(track, &ttml_ctx)) < 0) {
247  av_log(s, AV_LOG_ERROR, "Failed to initialize the TTML writer: %s\n",
248  av_err2str(ret));
249  goto cleanup;
250  }
251 
252  if (mov->flags & FF_MOV_FLAG_FRAGMENT) {
253  int64_t calculated_start = AV_NOPTS_VALUE;
254  int64_t calculated_end = AV_NOPTS_VALUE;
255 
256  mov_calculate_start_and_end_of_other_tracks(s, track, &calculated_start, &calculated_end);
257 
258  if (calculated_start != AV_NOPTS_VALUE) {
259  start_ts = calculated_start;
260  duration = calculated_end - calculated_start;
262  "Calculated subtitle fragment start: %"PRId64", "
263  "duration: %"PRId64"\n",
264  start_ts, duration);
265  }
266  }
267 
268  if (!track->squashed_packet_queue.head) {
269  // empty queue, write minimal empty document with zero duration
270  avio_write(ttml_ctx->pb, empty_ttml_document,
271  sizeof(empty_ttml_document) - 1);
272  if (start_ts == AV_NOPTS_VALUE) {
273  start_ts = 0;
274  duration = 0;
275  }
276  goto generate_packet;
277  }
278 
279  if ((ret = mov_write_ttml_document_from_queue(s, ttml_ctx, track, pkt,
280  &start_ts,
281  &duration)) < 0) {
283  "Failed to generate a squashed TTML packet from the packet "
284  "queue: %s\n",
285  av_err2str(ret));
286  goto cleanup;
287  }
288 
289 generate_packet:
290  {
291  // Generate an AVPacket from the data written into the dynamic buffer.
292  uint8_t *buf = NULL;
293  int buf_len = avio_close_dyn_buf(ttml_ctx->pb, &buf);
294  ttml_ctx->pb = NULL;
295 
296  if ((ret = av_packet_from_data(pkt, buf, buf_len)) < 0) {
298  "Failed to create a TTML AVPacket from AVIO data: %s\n",
299  av_err2str(ret));
300  av_freep(&buf);
301  goto cleanup;
302  }
303 
304  pkt->pts = pkt->dts = start_ts;
305  pkt->duration = duration;
307  }
308 
309  ret = 0;
310 
311 cleanup:
312  if (ttml_ctx)
313  ffio_free_dyn_buf(&ttml_ctx->pb);
314 
315  avformat_free_context(ttml_ctx);
316  return ret;
317 }
av_packet_unref
void av_packet_unref(AVPacket *pkt)
Wipe the packet.
Definition: packet.c:433
MOVTrack::end_pts
int64_t end_pts
Definition: movenc.h:131
AV_LOG_WARNING
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:216
PacketList::head
PacketListEntry * head
Definition: packet_internal.h:34
movenc_ttml.h
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
MOVTrack::squash_fragment_samples_to_one
unsigned int squash_fragment_samples_to_one
Definition: movenc.h:177
avformat_new_stream
AVStream * avformat_new_stream(AVFormatContext *s, const struct AVCodec *c)
Add a new stream to a media file.
empty_ttml_document
static const unsigned char empty_ttml_document[]
Definition: movenc_ttml.c:30
int64_t
long long int64_t
Definition: coverity.c:34
cleanup
static av_cold void cleanup(FlashSV2Context *s)
Definition: flashsv2enc.c:130
AVFormatContext::streams
AVStream ** streams
A list of all streams in the file.
Definition: avformat.h:1332
mov_calculate_start_and_end_of_other_tracks
static void mov_calculate_start_and_end_of_other_tracks(AVFormatContext *s, MOVTrack *track, int64_t *start_pts, int64_t *end_pts)
Definition: movenc_ttml.c:58
PacketList
Definition: packet_internal.h:33
MOVIentry::dts
int64_t dts
Definition: movenc.h:50
AV_LOG_VERBOSE
#define AV_LOG_VERBOSE
Detailed information.
Definition: log.h:226
AVPacket::duration
int64_t duration
Duration of this packet in AVStream->time_base units, 0 if unknown.
Definition: packet.h:576
AVCodecParameters::codec_tag
uint32_t codec_tag
Additional information about the codec (corresponds to the AVI FOURCC).
Definition: codec_par.h:59
FFMAX
#define FFMAX(a, b)
Definition: macros.h:47
MOVTrack::track_duration
int64_t track_duration
Definition: movenc.h:92
AV_PKT_FLAG_KEY
#define AV_PKT_FLAG_KEY
The packet contains a keyframe.
Definition: packet.h:613
MOVTrack
Definition: movenc.h:87
avpriv_packet_list_get
int avpriv_packet_list_get(PacketList *pkt_buffer, AVPacket *pkt)
Remove the oldest AVPacket in the list and return it.
Definition: packet.c:596
MOVTrack::cluster
MOVIentry * cluster
Definition: movenc.h:122
mov_write_ttml_document_from_queue
static int mov_write_ttml_document_from_queue(AVFormatContext *s, AVFormatContext *ttml_ctx, MOVTrack *track, AVPacket *pkt, int64_t *out_start_ts, int64_t *out_duration)
Definition: movenc_ttml.c:100
MOVTrack::st
AVStream * st
Definition: movenc.h:117
avio_close_dyn_buf
int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer)
Return the written size and a pointer to the buffer.
Definition: aviobuf.c:1410
AV_LOG_TRACE
#define AV_LOG_TRACE
Extremely verbose debugging, useful for libav* development.
Definition: log.h:236
pkt
AVPacket * pkt
Definition: movenc.c:60
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:210
MOVTrack::squashed_packet_queue
PacketList squashed_packet_queue
Definition: movenc.h:179
duration
int64_t duration
Definition: movenc.c:65
avio_open_dyn_buf
int avio_open_dyn_buf(AVIOContext **s)
Open a write only memory stream.
Definition: aviobuf.c:1365
FF_MOV_FLAG_FRAGMENT
#define FF_MOV_FLAG_FRAGMENT
Definition: movenc.h:269
s
#define s(width, name)
Definition: cbs_vp9.c:198
AV_ROUND_NEAR_INF
@ AV_ROUND_NEAR_INF
Round to nearest and halfway cases away from zero.
Definition: mathematics.h:135
AV_ROUND_PASS_MINMAX
@ AV_ROUND_PASS_MINMAX
Flag telling rescaling functions to pass INT64_MIN/MAX through unchanged, avoiding special cases for ...
Definition: mathematics.h:159
avformat_write_header
av_warn_unused_result int avformat_write_header(AVFormatContext *s, AVDictionary **options)
Allocate the stream private data and write the stream header to an output media file.
Definition: mux.c:467
avpriv_packet_list_free
void avpriv_packet_list_free(PacketList *pkt_buf)
Wipe the list and unref all the packets in it.
Definition: packet.c:610
MOVTrack::start_dts
int64_t start_dts
Definition: movenc.h:129
AVFormatContext
Format I/O context.
Definition: avformat.h:1264
AVStream::codecpar
AVCodecParameters * codecpar
Codec parameters associated with this stream.
Definition: avformat.h:767
AVStream::time_base
AVRational time_base
This is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented.
Definition: avformat.h:783
NULL
#define NULL
Definition: coverity.c:32
isom.h
avpriv_packet_list_put
int avpriv_packet_list_put(PacketList *packet_buffer, AVPacket *pkt, int(*copy)(AVPacket *dst, const AVPacket *src), int flags)
Append an AVPacket to the list.
Definition: packet.c:547
AVFormatContext::pb
AVIOContext * pb
I/O context.
Definition: avformat.h:1306
MOVMuxContext
Definition: movenc.h:201
mov_init_ttml_writer
static int mov_init_ttml_writer(MOVTrack *track, AVFormatContext **out_ctx)
Definition: movenc_ttml.c:33
MOVIentry::cts
int cts
Definition: movenc.h:57
av_write_frame
int av_write_frame(AVFormatContext *s, AVPacket *pkt)
Write a packet to an output media file.
Definition: mux.c:1176
av_packet_ref
int av_packet_ref(AVPacket *dst, const AVPacket *src)
Setup a new reference to the data described by a given packet.
Definition: packet.c:441
FF_PACKETLIST_FLAG_PREPEND
#define FF_PACKETLIST_FLAG_PREPEND
Prepend created AVPacketList instead of appending.
Definition: packet_internal.h:37
movenc.h
ff_mov_generate_squashed_ttml_packet
int ff_mov_generate_squashed_ttml_packet(AVFormatContext *s, MOVTrack *track, AVPacket *pkt)
Definition: movenc_ttml.c:235
av_packet_from_data
int av_packet_from_data(AVPacket *pkt, uint8_t *data, int size)
Initialize a reference-counted packet from av_malloc()ed data.
Definition: packet.c:173
av_err2str
#define av_err2str(errnum)
Convenience macro, the return value should be used only directly in function arguments but never stan...
Definition: error.h:122
AV_NOPTS_VALUE
#define AV_NOPTS_VALUE
Undefined timestamp value.
Definition: avutil.h:247
PacketListEntry::pkt
AVPacket pkt
Definition: packet_internal.h:30
AVPacket::dts
int64_t dts
Decompression timestamp in AVStream->time_base units; the time at which the packet is decompressed.
Definition: packet.h:557
avio_write
void avio_write(AVIOContext *s, const unsigned char *buf, int size)
Definition: aviobuf.c:206
offset
it s the only field you need to keep assuming you have a context There is some magic you don t need to care about around this just let it vf offset
Definition: writing_filters.txt:86
AVPacket::flags
int flags
A combination of AV_PKT_FLAG values.
Definition: packet.h:564
av_packet_rescale_ts
void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)
Convert valid timing fields (timestamps / durations) in a packet from one timebase to another.
Definition: packet.c:537
av_write_trailer
int av_write_trailer(AVFormatContext *s)
Write the stream trailer to an output media file and free the file private data.
Definition: mux.c:1238
MOVTrack::entry
int entry
Definition: movenc.h:89
i
#define i(width, name, range_min, range_max)
Definition: cbs_h2645.c:256
AVPacket::pts
int64_t pts
Presentation timestamp in AVStream->time_base units; the time at which the decompressed packet will b...
Definition: packet.h:551
avio_internal.h
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
ffio_free_dyn_buf
void ffio_free_dyn_buf(AVIOContext **s)
Free a dynamic buffer.
Definition: aviobuf.c:1438
ret
ret
Definition: filter_design.txt:187
AVStream
Stream structure.
Definition: avformat.h:744
avformat.h
MOVTrack::par
AVCodecParameters * par
Definition: movenc.h:118
MOVMuxContext::flags
int flags
Definition: movenc.h:213
avformat_free_context
void avformat_free_context(AVFormatContext *s)
Free an AVFormatContext and all its streams.
Definition: avformat.c:141
MOVMuxContext::tracks
MOVTrack * tracks
Definition: movenc.h:211
AVPacket::stream_index
int stream_index
Definition: packet.h:560
mem.h
packet_internal.h
AVPacket
This structure stores compressed data.
Definition: packet.h:535
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:35
AVERROR_BUG
#define AVERROR_BUG
Internal bug, also see AVERROR_BUG2.
Definition: error.h:52
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
avformat_alloc_output_context2
int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat, const char *format_name, const char *filename)
Allocate an AVFormatContext for an output format.
Definition: mux.c:95
MOV_ISMV_TTML_TAG
#define MOV_ISMV_TTML_TAG
Definition: isom.h:478
av_rescale_q_rnd
int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, enum AVRounding rnd)
Rescale a 64-bit integer by 2 rational numbers with specified rounding.
Definition: mathematics.c:134
avcodec_parameters_copy
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src)
Copy the contents of src to dst.
Definition: codec_par.c:107