| Line | Branch | Exec | Source | 
|---|---|---|---|
| 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 "libavcodec/packet_internal.h" | ||
| 29 | |||
| 30 | static const unsigned char empty_ttml_document[] = | ||
| 31 | "<tt xml:lang=\"\" xmlns=\"http://www.w3.org/ns/ttml\" />"; | ||
| 32 | |||
| 33 | 28 | static int mov_init_ttml_writer(MOVTrack *track, AVFormatContext **out_ctx) | |
| 34 | { | ||
| 35 | 28 | AVStream *movenc_stream = track->st, *ttml_stream = NULL; | |
| 36 | 28 | int ret = AVERROR_BUG; | |
| 37 | |||
| 38 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | if ((ret = avformat_alloc_output_context2(out_ctx, NULL, | 
| 39 | "ttml", NULL)) < 0) | ||
| 40 | ✗ | return ret; | |
| 41 | |||
| 42 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | if ((ret = avio_open_dyn_buf(&(*out_ctx)->pb)) < 0) | 
| 43 | ✗ | return ret; | |
| 44 | |||
| 45 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | if (!(ttml_stream = avformat_new_stream(*out_ctx, NULL))) { | 
| 46 | ✗ | return AVERROR(ENOMEM); | |
| 47 | } | ||
| 48 | |||
| 49 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 28 times. 
         | 
      28 | if ((ret = avcodec_parameters_copy(ttml_stream->codecpar, | 
| 50 | 28 | movenc_stream->codecpar)) < 0) | |
| 51 | ✗ | return ret; | |
| 52 | |||
| 53 | 28 | ttml_stream->time_base = movenc_stream->time_base; | |
| 54 | |||
| 55 | 28 | return 0; | |
| 56 | } | ||
| 57 | |||
| 58 | 26 | static void mov_calculate_start_and_end_of_other_tracks( | |
| 59 | AVFormatContext *s, MOVTrack *track, int64_t *start_pts, int64_t *end_pts) | ||
| 60 | { | ||
| 61 | 26 | 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 | 26 | int64_t max_track_end_dts = *start_pts = track->end_pts; | |
| 66 | |||
| 67 | 
        2/2✓ Branch 0 taken 52 times. 
          ✓ Branch 1 taken 26 times. 
         | 
      78 | for (unsigned int i = 0; i < s->nb_streams; i++) { | 
| 68 | 52 | 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 | 
        2/2✓ Branch 0 taken 26 times. 
          ✓ Branch 1 taken 26 times. 
         | 
      52 | if (track == other_track || | 
| 74 | 
        1/2✓ Branch 0 taken 26 times. 
          ✗ Branch 1 not taken. 
         | 
      26 | other_track->squash_fragment_samples_to_one || | 
| 75 | 
        1/2✓ Branch 0 taken 26 times. 
          ✗ Branch 1 not taken. 
         | 
      26 | other_track->start_dts == AV_NOPTS_VALUE || | 
| 76 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 26 times. 
         | 
      26 | !other_track->entry) { | 
| 77 | 26 | continue; | |
| 78 | } | ||
| 79 | |||
| 80 | 26 | int64_t picked_start = av_rescale_q_rnd(other_track->cluster[0].dts + other_track->cluster[0].cts, | |
| 81 | 26 | other_track->st->time_base, | |
| 82 | 26 | track->st->time_base, | |
| 83 | AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); | ||
| 84 | 26 | int64_t picked_end = av_rescale_q_rnd(other_track->end_pts, | |
| 85 | 26 | other_track->st->time_base, | |
| 86 | 26 | track->st->time_base, | |
| 87 | AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); | ||
| 88 | |||
| 89 | 
        2/2✓ Branch 0 taken 2 times. 
          ✓ Branch 1 taken 24 times. 
         | 
      26 | if (*start_pts == AV_NOPTS_VALUE) | 
| 90 | 2 | *start_pts = picked_start; | |
| 91 | 
        1/2✓ Branch 0 taken 24 times. 
          ✗ Branch 1 not taken. 
         | 
      24 | else if (picked_start >= track->end_pts) | 
| 92 | 24 | *start_pts = FFMIN(*start_pts, picked_start); | |
| 93 | |||
| 94 | 26 | max_track_end_dts = FFMAX(max_track_end_dts, picked_end); | |
| 95 | } | ||
| 96 | |||
| 97 | 26 | *end_pts = max_track_end_dts; | |
| 98 | 26 | } | |
| 99 | |||
| 100 | 28 | static int mov_write_ttml_document_from_queue(AVFormatContext *s, | |
| 101 | AVFormatContext *ttml_ctx, | ||
| 102 | MOVTrack *track, | ||
| 103 | AVPacket *pkt, | ||
| 104 | int64_t *out_start_ts, | ||
| 105 | int64_t *out_duration) | ||
| 106 | { | ||
| 107 | 28 | int ret = AVERROR_BUG; | |
| 108 | 56 | int64_t start_ts = track->start_dts == AV_NOPTS_VALUE ? | |
| 109 | 
        2/2✓ Branch 0 taken 24 times. 
          ✓ Branch 1 taken 4 times. 
         | 
      28 | 0 : (track->start_dts + track->track_duration); | 
| 110 | 28 | int64_t end_ts = start_ts; | |
| 111 | 28 | unsigned int time_limited = 0; | |
| 112 | 28 | PacketList back_to_queue_list = { 0 }; | |
| 113 | |||
| 114 | 
        2/2✓ Branch 0 taken 26 times. 
          ✓ Branch 1 taken 2 times. 
         | 
      28 | if (*out_start_ts != AV_NOPTS_VALUE) { | 
| 115 | // we have non-nopts values here, thus we have been given a time range | ||
| 116 | 26 | time_limited = 1; | |
| 117 | 26 | start_ts = *out_start_ts; | |
| 118 | 26 | end_ts = *out_start_ts + *out_duration; | |
| 119 | } | ||
| 120 | |||
| 121 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | if ((ret = avformat_write_header(ttml_ctx, NULL)) < 0) { | 
| 122 | ✗ | return ret; | |
| 123 | } | ||
| 124 | |||
| 125 | 
        2/2✓ Branch 1 taken 136 times. 
          ✓ Branch 2 taken 26 times. 
         | 
      162 | while (!avpriv_packet_list_get(&track->squashed_packet_queue, pkt)) { | 
| 126 | 136 | int64_t pts_before = pkt->pts; | |
| 127 | 136 | int64_t duration_before = pkt->duration; | |
| 128 | |||
| 129 | 
        2/2✓ Branch 0 taken 62 times. 
          ✓ Branch 1 taken 74 times. 
         | 
      136 | if (time_limited) { | 
| 130 | // special cases first: | ||
| 131 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 62 times. 
         | 
      62 | if (pkt->pts + pkt->duration < start_ts) { | 
| 132 | // too late for our fragment, unfortunately | ||
| 133 | // unref and proceed to next packet in queue. | ||
| 134 | ✗ | av_log(s, AV_LOG_WARNING, | |
| 135 | "Very late TTML packet in queue, dropping packet with " | ||
| 136 | "pts: %"PRId64", duration: %"PRId64"\n", | ||
| 137 | pkt->pts, pkt->duration); | ||
| 138 | ✗ | av_packet_unref(pkt); | |
| 139 | ✗ | continue; | |
| 140 | 
        2/2✓ Branch 0 taken 2 times. 
          ✓ Branch 1 taken 60 times. 
         | 
      62 | } else if (pkt->pts >= end_ts) { | 
| 141 | // starts after this fragment, put back to original queue | ||
| 142 | 2 | ret = avpriv_packet_list_put(&track->squashed_packet_queue, | |
| 143 | pkt, NULL, | ||
| 144 | FF_PACKETLIST_FLAG_PREPEND); | ||
| 145 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 2 times. 
         | 
      2 | if (ret < 0) | 
| 146 | ✗ | goto cleanup; | |
| 147 | |||
| 148 | 2 | break; | |
| 149 | } | ||
| 150 | |||
| 151 | // limit packet pts to start_ts | ||
| 152 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 60 times. 
         | 
      60 | if (pkt->pts < start_ts) { | 
| 153 | ✗ | pkt->duration -= start_ts - pkt->pts; | |
| 154 | ✗ | pkt->pts = start_ts; | |
| 155 | } | ||
| 156 | |||
| 157 | 
        2/2✓ Branch 0 taken 34 times. 
          ✓ Branch 1 taken 26 times. 
         | 
      60 | 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 | 34 | int64_t offset = end_ts - pkt->pts; | |
| 162 | |||
| 163 | 34 | ret = avpriv_packet_list_put(&back_to_queue_list, | |
| 164 | pkt, av_packet_ref, | ||
| 165 | FF_PACKETLIST_FLAG_PREPEND); | ||
| 166 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 34 times. 
         | 
      34 | if (ret < 0) | 
| 167 | ✗ | goto cleanup; | |
| 168 | |||
| 169 | 34 | back_to_queue_list.head->pkt.pts = | |
| 170 | 34 | back_to_queue_list.head->pkt.dts = | |
| 171 | 34 | back_to_queue_list.head->pkt.pts + offset; | |
| 172 | 34 | back_to_queue_list.head->pkt.duration -= offset; | |
| 173 | |||
| 174 | // and for our normal packet we just set duration to offset | ||
| 175 | 34 | pkt->duration = offset; | |
| 176 | } | ||
| 177 | } else { | ||
| 178 | 74 | end_ts = FFMAX(end_ts, pkt->pts + pkt->duration); | |
| 179 | } | ||
| 180 | |||
| 181 | 134 | av_log(s, AV_LOG_TRACE, | |
| 182 | "TTML packet writeout: pts: %"PRId64" (%"PRId64"), " | ||
| 183 | "duration: %"PRId64"\n", | ||
| 184 | 134 | pkt->pts, pkt->pts - start_ts, pkt->duration); | |
| 185 | 
        3/4✓ Branch 0 taken 134 times. 
          ✗ Branch 1 not taken. 
          ✓ Branch 2 taken 34 times. 
          ✓ Branch 3 taken 100 times. 
         | 
      134 | if (pkt->pts != pts_before || pkt->duration != duration_before) { | 
| 186 | 34 | av_log(s, AV_LOG_TRACE, | |
| 187 | "Adjustments: pts: %"PRId64", duration: %"PRId64"\n", | ||
| 188 | 34 | 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 | 
        2/2✓ Branch 0 taken 67 times. 
          ✓ Branch 1 taken 67 times. 
         | 
      134 | if (track->par->codec_tag == MOV_ISMV_TTML_TAG) { | 
| 194 | 67 | pkt->dts = pkt->pts = (pkt->pts - start_ts); | |
| 195 | } | ||
| 196 | |||
| 197 | 134 | pkt->stream_index = 0; | |
| 198 | |||
| 199 | 134 | av_packet_rescale_ts(pkt, track->st->time_base, | |
| 200 | 134 | ttml_ctx->streams[pkt->stream_index]->time_base); | |
| 201 | |||
| 202 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 134 times. 
         | 
      134 | if ((ret = av_write_frame(ttml_ctx, pkt)) < 0) { | 
| 203 | ✗ | goto cleanup; | |
| 204 | } | ||
| 205 | |||
| 206 | 134 | av_packet_unref(pkt); | |
| 207 | } | ||
| 208 | |||
| 209 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | if ((ret = av_write_trailer(ttml_ctx)) < 0) | 
| 210 | ✗ | goto cleanup; | |
| 211 | |||
| 212 | 28 | *out_start_ts = start_ts; | |
| 213 | 28 | *out_duration = end_ts - start_ts; | |
| 214 | |||
| 215 | 28 | ret = 0; | |
| 216 | |||
| 217 | 28 | cleanup: | |
| 218 | 28 | av_packet_unref(pkt); | |
| 219 | 
        2/2✓ Branch 1 taken 34 times. 
          ✓ Branch 2 taken 28 times. 
         | 
      62 | while (!avpriv_packet_list_get(&back_to_queue_list, pkt)) { | 
| 220 | 34 | ret = avpriv_packet_list_put(&track->squashed_packet_queue, | |
| 221 | pkt, av_packet_ref, | ||
| 222 | FF_PACKETLIST_FLAG_PREPEND); | ||
| 223 | |||
| 224 | // unrelated to whether we succeed or not, we unref the packet | ||
| 225 | // received from the temporary list. | ||
| 226 | 34 | av_packet_unref(pkt); | |
| 227 | |||
| 228 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 34 times. 
         | 
      34 | if (ret < 0) { | 
| 229 | ✗ | avpriv_packet_list_free(&back_to_queue_list); | |
| 230 | ✗ | break; | |
| 231 | } | ||
| 232 | } | ||
| 233 | 28 | return ret; | |
| 234 | } | ||
| 235 | |||
| 236 | 28 | int ff_mov_generate_squashed_ttml_packet(AVFormatContext *s, | |
| 237 | MOVTrack *track, AVPacket *pkt) | ||
| 238 | { | ||
| 239 | 28 | MOVMuxContext *mov = s->priv_data; | |
| 240 | 28 | AVFormatContext *ttml_ctx = NULL; | |
| 241 | // values for the generated AVPacket | ||
| 242 | 28 | int64_t start_ts = AV_NOPTS_VALUE; | |
| 243 | 28 | int64_t duration = 0; | |
| 244 | |||
| 245 | 28 | int ret = AVERROR_BUG; | |
| 246 | |||
| 247 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | 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 | 
        2/2✓ Branch 0 taken 26 times. 
          ✓ Branch 1 taken 2 times. 
         | 
      28 | if (mov->flags & FF_MOV_FLAG_FRAGMENT) { | 
| 254 | 26 | int64_t calculated_start = AV_NOPTS_VALUE; | |
| 255 | 26 | int64_t calculated_end = AV_NOPTS_VALUE; | |
| 256 | |||
| 257 | 26 | mov_calculate_start_and_end_of_other_tracks(s, track, &calculated_start, &calculated_end); | |
| 258 | |||
| 259 | 
        1/2✓ Branch 0 taken 26 times. 
          ✗ Branch 1 not taken. 
         | 
      26 | if (calculated_start != AV_NOPTS_VALUE) { | 
| 260 | 26 | start_ts = calculated_start; | |
| 261 | 26 | duration = calculated_end - calculated_start; | |
| 262 | 26 | av_log(s, AV_LOG_VERBOSE, | |
| 263 | "Calculated subtitle fragment start: %"PRId64", " | ||
| 264 | "duration: %"PRId64"\n", | ||
| 265 | start_ts, duration); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | 
        1/2✗ Branch 0 not taken. 
          ✓ Branch 1 taken 28 times. 
         | 
      28 | if (!track->squashed_packet_queue.head) { | 
| 270 | // empty queue, write minimal empty document with zero duration | ||
| 271 | ✗ | avio_write(ttml_ctx->pb, empty_ttml_document, | |
| 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 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | if ((ret = mov_write_ttml_document_from_queue(s, ttml_ctx, track, pkt, | 
| 281 | &start_ts, | ||
| 282 | &duration)) < 0) { | ||
| 283 | ✗ | av_log(s, AV_LOG_ERROR, | |
| 284 | "Failed to generate a squashed TTML packet from the packet " | ||
| 285 | "queue: %s\n", | ||
| 286 | ✗ | av_err2str(ret)); | |
| 287 | ✗ | goto cleanup; | |
| 288 | } | ||
| 289 | |||
| 290 | 28 | generate_packet: | |
| 291 | { | ||
| 292 | // Generate an AVPacket from the data written into the dynamic buffer. | ||
| 293 | 28 | uint8_t *buf = NULL; | |
| 294 | 28 | int buf_len = avio_close_dyn_buf(ttml_ctx->pb, &buf); | |
| 295 | 28 | ttml_ctx->pb = NULL; | |
| 296 | |||
| 297 | 
        1/2✗ Branch 1 not taken. 
          ✓ Branch 2 taken 28 times. 
         | 
      28 | if ((ret = av_packet_from_data(pkt, buf, buf_len)) < 0) { | 
| 298 | ✗ | av_log(s, AV_LOG_ERROR, | |
| 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 | 28 | pkt->pts = pkt->dts = start_ts; | |
| 306 | 28 | pkt->duration = duration; | |
| 307 | 28 | pkt->flags |= AV_PKT_FLAG_KEY; | |
| 308 | } | ||
| 309 | |||
| 310 | 28 | ret = 0; | |
| 311 | |||
| 312 | 28 | cleanup: | |
| 313 | 
        1/2✓ Branch 0 taken 28 times. 
          ✗ Branch 1 not taken. 
         | 
      28 | if (ttml_ctx) | 
| 314 | 28 | ffio_free_dyn_buf(&ttml_ctx->pb); | |
| 315 | |||
| 316 | 28 | avformat_free_context(ttml_ctx); | |
| 317 | 28 | return ret; | |
| 318 | } | ||
| 319 |