FFmpeg
Loading...
Searching...
No Matches
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"
28#include "packet_internal.h"
29
30static const unsigned char empty_ttml_document[] =
31 "<tt xml:lang=\"\" xmlns=\"http://www.w3.org/ns/ttml\" />";
32
33static 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
125 while (!ff_packet_list_get(&track->squashed_packet_queue, pkt)) {
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
143 pkt, NULL,
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 = ff_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
217cleanup:
219 while (!ff_packet_list_get(&back_to_queue_list, pkt)) {
223
224 // unrelated to whether we succeed or not, we unref the packet
225 // received from the temporary list.
227
228 if (ret < 0) {
229 ff_packet_list_free(&back_to_queue_list);
230 break;
231 }
232 }
233 return ret;
234}
235
237 MOVTrack *track, AVPacket *pkt)
238{
239 MOVMuxContext *mov = s->priv_data;
240 AVFormatContext *ttml_ctx = NULL;
241 // values for the generated AVPacket
242 int64_t start_ts = AV_NOPTS_VALUE;
243 int64_t duration = 0;
244
245 int ret = AVERROR_BUG;
246
247 if ((ret = mov_init_ttml_writer(track, &ttml_ctx)) < 0) {
248 av_log(s, AV_LOG_ERROR, "Failed to initialize the TTML writer: %s\n",
249 av_err2str(ret));
250 goto cleanup;
251 }
252
253 if (mov->flags & FF_MOV_FLAG_FRAGMENT) {
254 int64_t calculated_start = AV_NOPTS_VALUE;
255 int64_t calculated_end = AV_NOPTS_VALUE;
256
257 mov_calculate_start_and_end_of_other_tracks(s, track, &calculated_start, &calculated_end);
258
259 if (calculated_start != AV_NOPTS_VALUE) {
260 start_ts = calculated_start;
261 duration = calculated_end - calculated_start;
263 "Calculated subtitle fragment start: %"PRId64", "
264 "duration: %"PRId64"\n",
265 start_ts, duration);
266 }
267 }
268
269 if (!track->squashed_packet_queue.head) {
270 // empty queue, write minimal empty document with zero duration
272 sizeof(empty_ttml_document) - 1);
273 if (start_ts == AV_NOPTS_VALUE) {
274 start_ts = 0;
275 duration = 0;
276 }
277 goto generate_packet;
278 }
279
280 if ((ret = mov_write_ttml_document_from_queue(s, ttml_ctx, track, pkt,
281 &start_ts,
282 &duration)) < 0) {
284 "Failed to generate a squashed TTML packet from the packet "
285 "queue: %s\n",
286 av_err2str(ret));
287 goto cleanup;
288 }
289
290generate_packet:
291 {
292 // Generate an AVPacket from the data written into the dynamic buffer.
293 uint8_t *buf = NULL;
294 int buf_len = avio_close_dyn_buf(ttml_ctx->pb, &buf);
295 ttml_ctx->pb = NULL;
296
297 if ((ret = av_packet_from_data(pkt, buf, buf_len)) < 0) {
299 "Failed to create a TTML AVPacket from AVIO data: %s\n",
300 av_err2str(ret));
301 av_freep(&buf);
302 goto cleanup;
303 }
304
305 pkt->pts = pkt->dts = start_ts;
306 pkt->duration = duration;
307 pkt->flags |= AV_PKT_FLAG_KEY;
308 }
309
310 ret = 0;
311
312cleanup:
313 if (ttml_ctx)
314 ffio_free_dyn_buf(&ttml_ctx->pb);
315
316 avformat_free_context(ttml_ctx);
317 return ret;
318}
Main libavformat public API header.
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
int avio_close_dyn_buf(AVIOContext *s, uint8_t **pbuffer)
Return the written size and a pointer to the buffer.
Definition aviobuf.c:1410
void avio_write(AVIOContext *s, const unsigned char *buf, int size)
Definition aviobuf.c:206
int avio_open_dyn_buf(AVIOContext **s)
Open a write only memory stream.
Definition aviobuf.c:1365
void ffio_free_dyn_buf(AVIOContext **s)
Free a dynamic buffer.
Definition aviobuf.c:1438
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
#define s(width, name)
Definition cbs_vp9.c:198
int avcodec_parameters_copy(AVCodecParameters *dst, const AVCodecParameters *src)
Definition codec_par.c:107
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
static AVPacket * pkt
static int64_t duration
Definition ffplay.c:330
static av_cold void cleanup(FlashSV2Context *s)
void av_packet_unref(AVPacket *pkt)
Wipe the packet.
Definition packet.c:434
#define AV_PKT_FLAG_KEY
The packet contains a keyframe.
Definition packet.h:650
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:172
int av_packet_ref(AVPacket *dst, const AVPacket *src)
Setup a new reference to the data described by a given packet.
Definition packet.c:442
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:538
AVStream * avformat_new_stream(AVFormatContext *s, const struct AVCodec *c)
Add a new stream to a media file.
void avformat_free_context(AVFormatContext *s)
Free an AVFormatContext and all its streams.
Definition avformat.c:150
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
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
int av_write_frame(AVFormatContext *s, AVPacket *pkt)
Write a packet to an output media file.
Definition mux.c:1176
#define AVERROR_BUG
Internal bug, also see AVERROR_BUG2.
Definition error.h:52
#define av_err2str(errnum)
Convenience macro, the return value should be used only directly in function arguments but never stan...
Definition error.h:122
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_TRACE
Extremely verbose debugging, useful for libav* development.
Definition log.h:236
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition log.h:216
#define AV_LOG_VERBOSE
Detailed information.
Definition log.h:226
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
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.
@ AV_ROUND_PASS_MINMAX
Flag telling rescaling functions to pass INT64_MIN/MAX through unchanged, avoiding special cases for ...
@ AV_ROUND_NEAR_INF
Round to nearest and halfway cases away from zero.
#define AV_NOPTS_VALUE
Undefined timestamp value.
Definition avutil.h:247
#define MOV_ISMV_TTML_TAG
Definition isom.h:482
unsigned offset
Definition libaomenc.c:763
#define FF_PACKETLIST_FLAG_PREPEND
Prepend created AVPacketList instead of appending.
int ff_packet_list_get(PacketList *list, AVPacket *pkt)
Remove the oldest AVPacket in the list and return it.
Definition packet_list.c:89
void ff_packet_list_free(PacketList *list)
Wipe the list and unref all the packets in it.
int ff_packet_list_put(PacketList *list, AVPacket *pkt, int(*copy)(AVPacket *dst, const AVPacket *src), int flags)
Append an AVPacket to the list.
Definition packet_list.c:40
#define FFMIN(a, b)
Definition macros.h:49
#define FFMAX(a, b)
Definition macros.h:47
Memory handling functions.
#define FF_MOV_FLAG_FRAGMENT
Definition movenc.h:279
static int mov_init_ttml_writer(MOVTrack *track, AVFormatContext **out_ctx)
Definition movenc_ttml.c:33
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)
static const unsigned char empty_ttml_document[]
Definition movenc_ttml.c:30
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
int ff_mov_generate_squashed_ttml_packet(AVFormatContext *s, MOVTrack *track, AVPacket *pkt)
uint32_t codec_tag
Additional information about the codec (corresponds to the AVI FOURCC).
Definition codec_par.h:61
Format I/O context.
Definition avformat.h:1333
AVIOContext * pb
I/O context.
Definition avformat.h:1375
AVStream ** streams
A list of all streams in the file.
Definition avformat.h:1401
This structure stores compressed data.
Definition packet.h:580
int64_t duration
Duration of this packet in AVStream->time_base units, 0 if unknown.
Definition packet.h:621
int64_t pts
Presentation timestamp in AVStream->time_base units; the time at which the decompressed packet will b...
Definition packet.h:596
int64_t dts
Decompression timestamp in AVStream->time_base units; the time at which the packet is decompressed.
Definition packet.h:602
Stream structure.
Definition avformat.h:766
AVCodecParameters * codecpar
Codec parameters associated with this stream.
Definition avformat.h:789
AVRational time_base
This is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented.
Definition avformat.h:805
int cts
Definition movenc.h:58
int64_t dts
Definition movenc.h:51
MOVTrack * tracks
Definition movenc.h:221
int entry
Definition movenc.h:97
PacketList squashed_packet_queue
Definition movenc.h:189
AVCodecParameters * par
Definition movenc.h:126
int64_t end_pts
Definition movenc.h:139
unsigned int squash_fragment_samples_to_one
Definition movenc.h:187
MOVIentry * cluster
Definition movenc.h:130
AVStream * st
Definition movenc.h:125
int64_t start_dts
Definition movenc.h:137
int64_t track_duration
Definition movenc.h:100
PacketListEntry * head
#define av_freep(p)
#define av_log(a,...)