dashenc.c 71.4 KB
Newer Older
1 2 3
/*
 * MPEG-DASH ISO BMFF segmenter
 * Copyright (c) 2014 Martin Storsjo
4
 * Copyright (c) 2018 Akamai Technologies, Inc.
5
 *
6
 * This file is part of FFmpeg.
7
 *
8
 * FFmpeg is free software; you can redistribute it and/or
9 10 11 12
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
13
 * FFmpeg is distributed in the hope that it will be useful,
14 15 16 17 18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with FFmpeg; if not, write to the Free Software
20 21 22 23 24 25 26 27
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "config.h"
#if HAVE_UNISTD_H
#include <unistd.h>
#endif

28
#include "libavutil/avassert.h"
29
#include "libavutil/avutil.h"
30 31 32 33
#include "libavutil/avstring.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
34
#include "libavutil/rational.h"
35
#include "libavutil/time.h"
36 37 38 39 40
#include "libavutil/time_internal.h"

#include "avc.h"
#include "avformat.h"
#include "avio_internal.h"
41
#include "hlsplaylist.h"
42 43 44
#if CONFIG_HTTP_PROTOCOL
#include "http.h"
#endif
45 46 47 48
#include "internal.h"
#include "isom.h"
#include "os_support.h"
#include "url.h"
49
#include "vpcc.h"
50
#include "dash.h"
51

52
typedef enum {
53 54
    SEGMENT_TYPE_AUTO = 0,
    SEGMENT_TYPE_MP4,
55 56 57 58
    SEGMENT_TYPE_WEBM,
    SEGMENT_TYPE_NB
} SegmentType;

59 60 61 62 63
typedef struct Segment {
    char file[1024];
    int64_t start_pos;
    int range_length, index_length;
    int64_t time;
64
    double prog_date_time;
65
    int64_t duration;
66 67 68
    int n;
} Segment;

69 70 71
typedef struct AdaptationSet {
    char id[10];
    enum AVMediaType media_type;
72
    AVDictionary *metadata;
73 74
    AVRational min_frame_rate, max_frame_rate;
    int ambiguous_frame_rate;
75 76
} AdaptationSet;

77 78
typedef struct OutputStream {
    AVFormatContext *ctx;
79
    int ctx_inited, as_idx;
80
    AVIOContext *out;
81 82
    int packets_written;
    char initfile[1024];
83
    int64_t init_start_pos, pos;
84 85 86
    int init_range_length;
    int nb_segments, segments_size, segment_index;
    Segment **segments;
87
    int64_t first_pts, start_pts, max_pts;
88
    int64_t last_dts, last_pts;
89
    int bit_rate;
90 91
    SegmentType segment_type;  /* segment type selected for this particular stream */
    const char *format_name;
92 93 94
    const char *single_file_name;  /* file names selected for this particular stream */
    const char *init_seg_name;
    const char *media_seg_name;
95 96

    char codec_str[100];
97
    int written_len;
98 99 100
    char filename[1024];
    char full_path[1024];
    char temp_path[1024];
101
    double availability_time_offset;
102 103
    int total_pkt_size;
    int muxer_overhead;
104 105 106 107
} OutputStream;

typedef struct DASHContext {
    const AVClass *class;  /* Class for private options. */
108 109 110
    char *adaptation_sets;
    AdaptationSet *as;
    int nb_as;
111 112
    int window_size;
    int extra_window_size;
113
#if FF_API_DASH_MIN_SEG_DURATION
114
    int min_seg_duration;
115 116
#endif
    int64_t seg_duration;
117 118 119 120 121
    int remove_at_exit;
    int use_template;
    int use_timeline;
    int single_file;
    OutputStream *streams;
122
    int has_video;
123 124
    int64_t last_duration;
    int64_t total_duration;
125
    char availability_start_time[100];
126
    time_t start_time_s;
127
    char dirname[1024];
128
    const char *single_file_name;  /* file names as specified in options */
129 130
    const char *init_seg_name;
    const char *media_seg_name;
131
    const char *utc_timing_url;
132
    const char *method;
133
    const char *user_agent;
134
    int hls_playlist;
135
    int http_persistent;
136
    int master_playlist_created;
137 138
    AVIOContext *mpd_out;
    AVIOContext *m3u8_out;
139
    int streaming;
140
    int64_t timeout;
141
    int index_correction;
142
    char *format_options_str;
143
    int global_sidx;
144
    SegmentType segment_type_option;  /* segment type as specified in options */
145
    int ignore_io_errors;
146
    int lhls;
147
    int master_publish_rate;
148 149
    int nr_of_streams_to_flush;
    int nr_of_streams_flushed;
150 151
} DASHContext;

152 153 154 155 156 157 158 159
static struct codec_string {
    int id;
    const char *str;
} codecs[] = {
    { AV_CODEC_ID_VP8, "vp8" },
    { AV_CODEC_ID_VP9, "vp9" },
    { AV_CODEC_ID_VORBIS, "vorbis" },
    { AV_CODEC_ID_OPUS, "opus" },
160
    { AV_CODEC_ID_FLAC, "flac" },
161 162 163
    { 0, NULL }
};

164 165 166 167
static struct format_string {
    SegmentType segment_type;
    const char *str;
} formats[] = {
168
    { SEGMENT_TYPE_AUTO, "auto" },
169 170 171 172 173
    { SEGMENT_TYPE_MP4, "mp4" },
    { SEGMENT_TYPE_WEBM, "webm" },
    { 0, NULL }
};

174 175 176 177 178 179 180 181 182 183 184 185
static int dashenc_io_open(AVFormatContext *s, AVIOContext **pb, char *filename,
                           AVDictionary **options) {
    DASHContext *c = s->priv_data;
    int http_base_proto = filename ? ff_is_http_proto(filename) : 0;
    int err = AVERROR_MUXER_NOT_FOUND;
    if (!*pb || !http_base_proto || !c->http_persistent) {
        err = s->io_open(s, pb, filename, AVIO_FLAG_WRITE, options);
#if CONFIG_HTTP_PROTOCOL
    } else {
        URLContext *http_url_context = ffio_geturlcontext(*pb);
        av_assert0(http_url_context);
        err = ff_http_do_new_request(http_url_context, filename);
186 187
        if (err < 0)
            ff_format_io_close(s, pb);
188 189 190 191 192 193 194 195 196
#endif
    }
    return err;
}

static void dashenc_io_close(AVFormatContext *s, AVIOContext **pb, char *filename) {
    DASHContext *c = s->priv_data;
    int http_base_proto = filename ? ff_is_http_proto(filename) : 0;

197 198 199
    if (!*pb)
        return;

200 201 202 203
    if (!http_base_proto || !c->http_persistent) {
        ff_format_io_close(s, pb);
#if CONFIG_HTTP_PROTOCOL
    } else {
204 205
        URLContext *http_url_context = ffio_geturlcontext(*pb);
        av_assert0(http_url_context);
206
        avio_flush(*pb);
207
        ffurl_shutdown(http_url_context, AVIO_FLAG_WRITE);
208 209 210 211
#endif
    }
}

212 213 214 215 216 217 218 219
static const char *get_format_str(SegmentType segment_type) {
    int i;
    for (i = 0; i < SEGMENT_TYPE_NB; i++)
        if (formats[i].segment_type == segment_type)
            return formats[i].str;
    return NULL;
}

220 221 222 223 224 225 226 227 228
static int handle_io_open_error(AVFormatContext *s, int err, char *url) {
    DASHContext *c = s->priv_data;
    char errbuf[AV_ERROR_MAX_STRING_SIZE];
    av_strerror(err, errbuf, sizeof(errbuf));
    av_log(s, c->ignore_io_errors ? AV_LOG_WARNING : AV_LOG_ERROR,
           "Unable to open %s for writing: %s\n", url, errbuf);
    return c->ignore_io_errors ? 0 : err;
}

229
static inline SegmentType select_segment_type(SegmentType segment_type, enum AVCodecID codec_id)
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
{
    if (segment_type == SEGMENT_TYPE_AUTO) {
        if (codec_id == AV_CODEC_ID_OPUS || codec_id == AV_CODEC_ID_VORBIS ||
            codec_id == AV_CODEC_ID_VP8 || codec_id == AV_CODEC_ID_VP9) {
            segment_type = SEGMENT_TYPE_WEBM;
        } else {
            segment_type = SEGMENT_TYPE_MP4;
        }
    }

    return segment_type;
}

static int init_segment_types(AVFormatContext *s)
{
    DASHContext *c = s->priv_data;
246
    int has_mp4_streams = 0;
247 248 249 250 251 252 253 254 255 256
    for (int i = 0; i < s->nb_streams; ++i) {
        OutputStream *os = &c->streams[i];
        SegmentType segment_type = select_segment_type(
            c->segment_type_option, s->streams[i]->codecpar->codec_id);
        os->segment_type = segment_type;
        os->format_name = get_format_str(segment_type);
        if (!os->format_name) {
            av_log(s, AV_LOG_ERROR, "Could not select DASH segment type for stream %d\n", i);
            return AVERROR_MUXER_NOT_FOUND;
        }
257 258 259 260 261 262
        has_mp4_streams |= segment_type == SEGMENT_TYPE_MP4;
    }

    if (c->hls_playlist && !has_mp4_streams) {
         av_log(s, AV_LOG_WARNING, "No mp4 streams, disabling HLS manifest generation\n");
         c->hls_playlist = 0;
263 264 265 266 267
    }

    return 0;
}

268 269 270 271 272 273 274 275 276 277
static int check_file_extension(const char *filename, const char *extension) {
    char *dot;
    if (!filename || !extension)
        return -1;
    dot = strrchr(filename, '.');
    if (dot && !strcmp(dot + 1, extension))
        return 0;
    return -1;
}

278 279 280 281 282
static void set_vp9_codec_str(AVFormatContext *s, AVCodecParameters *par,
                              AVRational *frame_rate, char *str, int size) {
    VPCC vpcc;
    int ret = ff_isom_get_vpcc_features(s, par, frame_rate, &vpcc);
    if (ret == 0) {
283
        av_strlcatf(str, size, "vp09.%02d.%02d.%02d",
284 285 286 287 288 289 290 291 292
                    vpcc.profile, vpcc.level, vpcc.bitdepth);
    } else {
        // Default to just vp9 in case of error while finding out profile or level
        av_log(s, AV_LOG_WARNING, "Could not find VP9 profile and/or level\n");
        av_strlcpy(str, "vp9", size);
    }
    return;
}

293
static void set_codec_str(AVFormatContext *s, AVCodecParameters *par,
294
                          AVRational *frame_rate, char *str, int size)
295 296 297
{
    const AVCodecTag *tags[2] = { NULL, NULL };
    uint32_t tag;
298 299 300 301 302
    int i;

    // common Webm codecs are not part of RFC 6381
    for (i = 0; codecs[i].id; i++)
        if (codecs[i].id == par->codec_id) {
303 304 305 306 307
            if (codecs[i].id == AV_CODEC_ID_VP9) {
                set_vp9_codec_str(s, par, frame_rate, str, size);
            } else {
                av_strlcpy(str, codecs[i].str, size);
            }
308 309 310 311
            return;
        }

    // for codecs part of RFC 6381
312
    if (par->codec_type == AVMEDIA_TYPE_VIDEO)
313
        tags[0] = ff_codec_movvideo_tags;
314
    else if (par->codec_type == AVMEDIA_TYPE_AUDIO)
315 316 317 318
        tags[0] = ff_codec_movaudio_tags;
    else
        return;

319 320 321
    tag = par->codec_tag;
    if (!tag)
        tag = av_codec_get_tag(tags, par->codec_id);
322 323 324 325 326 327 328 329 330 331
    if (!tag)
        return;
    if (size < 5)
        return;

    AV_WL32(str, tag);
    str[4] = '\0';
    if (!strcmp(str, "mp4a") || !strcmp(str, "mp4v")) {
        uint32_t oti;
        tags[0] = ff_mp4_obj_type;
332
        oti = av_codec_get_tag(tags, par->codec_id);
333
        if (oti)
334
            av_strlcatf(str, size, ".%02"PRIx32, oti);
335 336 337 338
        else
            return;

        if (tag == MKTAG('m', 'p', '4', 'a')) {
339 340
            if (par->extradata_size >= 2) {
                int aot = par->extradata[0] >> 3;
341
                if (aot == 31)
342
                    aot = ((AV_RB16(par->extradata) >> 5) & 0x3f) + 32;
343 344 345 346 347 348 349 350
                av_strlcatf(str, size, ".%d", aot);
            }
        } else if (tag == MKTAG('m', 'p', '4', 'v')) {
            // Unimplemented, should output ProfileLevelIndication as a decimal number
            av_log(s, AV_LOG_WARNING, "Incomplete RFC 6381 codec string for mp4v\n");
        }
    } else if (!strcmp(str, "avc1")) {
        uint8_t *tmpbuf = NULL;
351 352
        uint8_t *extradata = par->extradata;
        int extradata_size = par->extradata_size;
353 354 355 356 357 358 359
        if (!extradata_size)
            return;
        if (extradata[0] != 1) {
            AVIOContext *pb;
            if (avio_open_dyn_buf(&pb) < 0)
                return;
            if (ff_isom_write_avcc(pb, extradata, extradata_size) < 0) {
360
                ffio_free_dyn_buf(&pb);
361 362 363 364 365 366 367 368 369 370 371 372 373
                return;
            }
            extradata_size = avio_close_dyn_buf(pb, &extradata);
            tmpbuf = extradata;
        }

        if (extradata_size >= 4)
            av_strlcatf(str, size, ".%02x%02x%02x",
                        extradata[1], extradata[2], extradata[3]);
        av_free(tmpbuf);
    }
}

374
static int flush_dynbuf(DASHContext *c, OutputStream *os, int *range_length)
375 376 377 378 379 380 381 382 383 384 385
{
    uint8_t *buffer;

    if (!os->ctx->pb) {
        return AVERROR(EINVAL);
    }

    // flush
    av_write_frame(os->ctx, NULL);
    avio_flush(os->ctx->pb);

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
    if (!c->single_file) {
        // write out to file
        *range_length = avio_close_dyn_buf(os->ctx->pb, &buffer);
        os->ctx->pb = NULL;
        if (os->out)
            avio_write(os->out, buffer + os->written_len, *range_length - os->written_len);
        os->written_len = 0;
        av_free(buffer);

        // re-open buffer
        return avio_open_dyn_buf(&os->ctx->pb);
    } else {
        *range_length = avio_tell(os->ctx->pb) - os->pos;
        return 0;
    }
401 402
}

403 404
static void set_http_options(AVDictionary **options, DASHContext *c)
{
405 406
    if (c->method)
        av_dict_set(options, "method", c->method, 0);
407 408
    if (c->user_agent)
        av_dict_set(options, "user_agent", c->user_agent, 0);
409 410
    if (c->http_persistent)
        av_dict_set_int(options, "multiple_requests", 1, 0);
411 412
    if (c->timeout >= 0)
        av_dict_set_int(options, "timeout", c->timeout, 0);
413 414
}

415 416 417 418 419 420 421 422
static void get_hls_playlist_name(char *playlist_name, int string_size,
                                  const char *base_url, int id) {
    if (base_url)
        snprintf(playlist_name, string_size, "%smedia_%d.m3u8", base_url, id);
    else
        snprintf(playlist_name, string_size, "media_%d.m3u8", id);
}

423 424 425 426 427 428 429 430 431 432 433
static void get_start_index_number(OutputStream *os, DASHContext *c,
                                   int *start_index, int *start_number) {
    *start_index = 0;
    *start_number = 1;
    if (c->window_size) {
        *start_index  = FFMAX(os->nb_segments   - c->window_size, 0);
        *start_number = FFMAX(os->segment_index - c->window_size, 1);
    }
}

static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s,
434 435
                                     int representation_id, int final,
                                     char *prefetch_url) {
436 437 438 439 440 441 442 443 444 445
    DASHContext *c = s->priv_data;
    int timescale = os->ctx->streams[0]->time_base.den;
    char temp_filename_hls[1024];
    char filename_hls[1024];
    AVDictionary *http_opts = NULL;
    int target_duration = 0;
    int ret = 0;
    const char *proto = avio_find_protocol_name(c->dirname);
    int use_rename = proto && !strcmp(proto, "file");
    int i, start_index, start_number;
446
    double prog_date_time = 0;
447 448

    get_start_index_number(os, c, &start_index, &start_number);
449 450 451 452 453

    if (!c->hls_playlist || start_index >= os->nb_segments ||
        os->segment_type != SEGMENT_TYPE_MP4)
        return;

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
    get_hls_playlist_name(filename_hls, sizeof(filename_hls),
                          c->dirname, representation_id);

    snprintf(temp_filename_hls, sizeof(temp_filename_hls), use_rename ? "%s.tmp" : "%s", filename_hls);

    set_http_options(&http_opts, c);
    ret = dashenc_io_open(s, &c->m3u8_out, temp_filename_hls, &http_opts);
    av_dict_free(&http_opts);
    if (ret < 0) {
        handle_io_open_error(s, ret, temp_filename_hls);
        return;
    }
    for (i = start_index; i < os->nb_segments; i++) {
        Segment *seg = os->segments[i];
        double duration = (double) seg->duration / timescale;
        if (target_duration <= duration)
            target_duration = lrint(duration);
    }

    ff_hls_write_playlist_header(c->m3u8_out, 6, -1, target_duration,
                                 start_number, PLAYLIST_TYPE_NONE);

    ff_hls_write_init_file(c->m3u8_out, os->initfile, c->single_file,
                           os->init_range_length, os->init_start_pos);

    for (i = start_index; i < os->nb_segments; i++) {
        Segment *seg = os->segments[i];
481 482 483 484 485 486 487 488 489

        if (prog_date_time == 0) {
            if (os->nb_segments == 1)
                prog_date_time = c->start_time_s;
            else
                prog_date_time = seg->prog_date_time;
        }
        seg->prog_date_time = prog_date_time;

490 491 492 493
        ret = ff_hls_write_file_entry(c->m3u8_out, 0, c->single_file,
                                (double) seg->duration / timescale, 0,
                                seg->range_length, seg->start_pos, NULL,
                                c->single_file ? os->initfile : seg->file,
494
                                &prog_date_time);
495 496 497 498 499
        if (ret < 0) {
            av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n");
        }
    }

500 501 502
    if (prefetch_url)
        avio_printf(c->m3u8_out, "#EXT-X-PREFETCH:%s\n", prefetch_url);

503 504 505 506 507 508 509 510 511 512 513
    if (final)
        ff_hls_write_end_list(c->m3u8_out);

    dashenc_io_close(s, &c->m3u8_out, temp_filename_hls);

    if (use_rename)
        if (avpriv_io_move(temp_filename_hls, filename_hls) < 0) {
            av_log(os->ctx, AV_LOG_WARNING, "renaming file %s to %s failed\n\n", temp_filename_hls, filename_hls);
        }
}

514 515 516 517 518
static int flush_init_segment(AVFormatContext *s, OutputStream *os)
{
    DASHContext *c = s->priv_data;
    int ret, range_length;

519
    ret = flush_dynbuf(c, os, &range_length);
520 521 522 523
    if (ret < 0)
        return ret;

    os->pos = os->init_range_length = range_length;
524 525 526 527 528
    if (!c->single_file) {
        char filename[1024];
        snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
        dashenc_io_close(s, &os->out, filename);
    }
529 530 531
    return 0;
}

532 533 534 535
static void dash_free(AVFormatContext *s)
{
    DASHContext *c = s->priv_data;
    int i, j;
536 537

    if (c->as) {
538 539
        for (i = 0; i < c->nb_as; i++)
            av_dict_free(&c->as[i].metadata);
540 541 542 543
        av_freep(&c->as);
        c->nb_as = 0;
    }

544 545 546 547
    if (!c->streams)
        return;
    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
548 549 550 551 552 553
        if (os->ctx && os->ctx->pb) {
            if (!c->single_file)
                ffio_free_dyn_buf(&os->ctx->pb);
            else
                avio_close(os->ctx->pb);
        }
554
        ff_format_io_close(s, &os->out);
555 556 557 558 559
        if (os->ctx)
            avformat_free_context(os->ctx);
        for (j = 0; j < os->nb_segments; j++)
            av_free(os->segments[j]);
        av_free(os->segments);
560 561 562
        av_freep(&os->single_file_name);
        av_freep(&os->init_seg_name);
        av_freep(&os->media_seg_name);
563 564
    }
    av_freep(&c->streams);
565 566 567

    ff_format_io_close(s, &c->mpd_out);
    ff_format_io_close(s, &c->m3u8_out);
568 569
}

570
static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatContext *s,
571
                                int representation_id, int final)
572
{
573
    DASHContext *c = s->priv_data;
574 575
    int i, start_index, start_number;
    get_start_index_number(os, c, &start_index, &start_number);
576 577 578 579

    if (c->use_template) {
        int timescale = c->use_timeline ? os->ctx->streams[0]->time_base.den : AV_TIME_BASE;
        avio_printf(out, "\t\t\t\t<SegmentTemplate timescale=\"%d\" ", timescale);
580
        if (!c->use_timeline) {
581
            avio_printf(out, "duration=\"%"PRId64"\" ", c->seg_duration);
582 583 584 585
            if (c->streaming && os->availability_time_offset)
                avio_printf(out, "availabilityTimeOffset=\"%.3f\" ",
                            os->availability_time_offset);
        }
586
        avio_printf(out, "initialization=\"%s\" media=\"%s\" startNumber=\"%d\">\n", os->init_seg_name, os->media_seg_name, c->use_timeline ? start_number : 1);
587
        if (c->use_timeline) {
588
            int64_t cur_time = 0;
589 590 591 592 593
            avio_printf(out, "\t\t\t\t\t<SegmentTimeline>\n");
            for (i = start_index; i < os->nb_segments; ) {
                Segment *seg = os->segments[i];
                int repeat = 0;
                avio_printf(out, "\t\t\t\t\t\t<S ");
594 595
                if (i == start_index || seg->time != cur_time) {
                    cur_time = seg->time;
596
                    avio_printf(out, "t=\"%"PRId64"\" ", seg->time);
597
                }
598
                avio_printf(out, "d=\"%"PRId64"\" ", seg->duration);
599 600 601
                while (i + repeat + 1 < os->nb_segments &&
                       os->segments[i + repeat + 1]->duration == seg->duration &&
                       os->segments[i + repeat + 1]->time == os->segments[i + repeat]->time + os->segments[i + repeat]->duration)
602 603 604 605 606
                    repeat++;
                if (repeat > 0)
                    avio_printf(out, "r=\"%d\" ", repeat);
                avio_printf(out, "/>\n");
                i += 1 + repeat;
607
                cur_time += (1 + repeat) * seg->duration;
608 609 610 611 612 613
            }
            avio_printf(out, "\t\t\t\t\t</SegmentTimeline>\n");
        }
        avio_printf(out, "\t\t\t\t</SegmentTemplate>\n");
    } else if (c->single_file) {
        avio_printf(out, "\t\t\t\t<BaseURL>%s</BaseURL>\n", os->initfile);
614
        avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%"PRId64"\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number);
615 616 617 618 619 620 621 622 623 624
        avio_printf(out, "\t\t\t\t\t<Initialization range=\"%"PRId64"-%"PRId64"\" />\n", os->init_start_pos, os->init_start_pos + os->init_range_length - 1);
        for (i = start_index; i < os->nb_segments; i++) {
            Segment *seg = os->segments[i];
            avio_printf(out, "\t\t\t\t\t<SegmentURL mediaRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->range_length - 1);
            if (seg->index_length)
                avio_printf(out, "indexRange=\"%"PRId64"-%"PRId64"\" ", seg->start_pos, seg->start_pos + seg->index_length - 1);
            avio_printf(out, "/>\n");
        }
        avio_printf(out, "\t\t\t\t</SegmentList>\n");
    } else {
625
        avio_printf(out, "\t\t\t\t<SegmentList timescale=\"%d\" duration=\"%"PRId64"\" startNumber=\"%d\">\n", AV_TIME_BASE, c->last_duration, start_number);
626 627 628 629 630 631 632
        avio_printf(out, "\t\t\t\t\t<Initialization sourceURL=\"%s\" />\n", os->initfile);
        for (i = start_index; i < os->nb_segments; i++) {
            Segment *seg = os->segments[i];
            avio_printf(out, "\t\t\t\t\t<SegmentURL media=\"%s\" />\n", seg->file);
        }
        avio_printf(out, "\t\t\t\t</SegmentList>\n");
    }
633 634
    if (!c->lhls || final) {
        write_hls_media_playlist(os, s, representation_id, final, NULL);
635 636
    }

637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
}

static char *xmlescape(const char *str) {
    int outlen = strlen(str)*3/2 + 6;
    char *out = av_realloc(NULL, outlen + 1);
    int pos = 0;
    if (!out)
        return NULL;
    for (; *str; str++) {
        if (pos + 6 > outlen) {
            char *tmp;
            outlen = 2 * outlen + 6;
            tmp = av_realloc(out, outlen + 1);
            if (!tmp) {
                av_free(out);
                return NULL;
            }
            out = tmp;
        }
        if (*str == '&') {
            memcpy(&out[pos], "&amp;", 5);
            pos += 5;
        } else if (*str == '<') {
            memcpy(&out[pos], "&lt;", 4);
            pos += 4;
        } else if (*str == '>') {
            memcpy(&out[pos], "&gt;", 4);
            pos += 4;
        } else if (*str == '\'') {
            memcpy(&out[pos], "&apos;", 6);
            pos += 6;
        } else if (*str == '\"') {
            memcpy(&out[pos], "&quot;", 6);
            pos += 6;
        } else {
            out[pos++] = *str;
        }
    }
    out[pos] = '\0';
    return out;
}

static void write_time(AVIOContext *out, int64_t time)
{
    int seconds = time / AV_TIME_BASE;
    int fractions = time % AV_TIME_BASE;
    int minutes = seconds / 60;
    int hours = minutes / 60;
    seconds %= 60;
    minutes %= 60;
    avio_printf(out, "PT");
    if (hours)
        avio_printf(out, "%dH", hours);
    if (hours || minutes)
        avio_printf(out, "%dM", minutes);
    avio_printf(out, "%d.%dS", seconds, fractions / (AV_TIME_BASE / 10));
}

695 696 697
static void format_date_now(char *buf, int size)
{
    struct tm *ptm, tmbuf;
698 699 700 701 702
    int64_t time_us = av_gettime();
    int64_t time_ms = time_us / 1000;
    const time_t time_s = time_ms / 1000;
    int millisec = time_ms - (time_s * 1000);
    ptm = gmtime_r(&time_s, &tmbuf);
703
    if (ptm) {
704 705
        int len;
        if (!strftime(buf, size, "%Y-%m-%dT%H:%M:%S", ptm)) {
706
            buf[0] = '\0';
707 708 709 710
            return;
        }
        len = strlen(buf);
        snprintf(buf + len, size - len, ".%03dZ", millisec);
711 712 713
    }
}

714 715
static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index,
                                int final)
716 717 718
{
    DASHContext *c = s->priv_data;
    AdaptationSet *as = &c->as[as_index];
719
    AVDictionaryEntry *lang, *role;
720 721
    int i;

Rodger Combs's avatar
Rodger Combs committed
722
    avio_printf(out, "\t\t<AdaptationSet id=\"%s\" contentType=\"%s\" segmentAlignment=\"true\" bitstreamSwitching=\"true\"",
723
                as->id, as->media_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio");
724 725
    if (as->media_type == AVMEDIA_TYPE_VIDEO && as->max_frame_rate.num && !as->ambiguous_frame_rate && av_cmp_q(as->min_frame_rate, as->max_frame_rate) < 0)
        avio_printf(out, " maxFrameRate=\"%d/%d\"", as->max_frame_rate.num, as->max_frame_rate.den);
726 727 728
    lang = av_dict_get(as->metadata, "language", NULL, 0);
    if (lang)
        avio_printf(out, " lang=\"%s\"", lang->value);
Rodger Combs's avatar
Rodger Combs committed
729
    avio_printf(out, ">\n");
730

731 732 733
    role = av_dict_get(as->metadata, "role", NULL, 0);
    if (role)
        avio_printf(out, "\t\t\t<Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"%s\"/>\n", role->value);
734 735 736

    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
737
        char bandwidth_str[64] = {'\0'};
738 739 740 741

        if (os->as_idx - 1 != as_index)
            continue;

742 743
        if (os->bit_rate > 0)
            snprintf(bandwidth_str, sizeof(bandwidth_str), " bandwidth=\"%d\"",
744
                     os->bit_rate);
745

746
        if (as->media_type == AVMEDIA_TYPE_VIDEO) {
Rodger Combs's avatar
Rodger Combs committed
747
            AVStream *st = s->streams[i];
Rodger Combs's avatar
Rodger Combs committed
748
            avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"video/%s\" codecs=\"%s\"%s width=\"%d\" height=\"%d\"",
749
                i, os->format_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height);
Rodger Combs's avatar
Rodger Combs committed
750 751 752
            if (st->avg_frame_rate.num)
                avio_printf(out, " frameRate=\"%d/%d\"", st->avg_frame_rate.num, st->avg_frame_rate.den);
            avio_printf(out, ">\n");
753
        } else {
754
            avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/%s\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n",
755
                i, os->format_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->sample_rate);
756 757 758
            avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n",
                s->streams[i]->codecpar->channels);
        }
759
        output_segment_list(os, out, s, i, final);
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
        avio_printf(out, "\t\t\t</Representation>\n");
    }
    avio_printf(out, "\t\t</AdaptationSet>\n");

    return 0;
}

static int add_adaptation_set(AVFormatContext *s, AdaptationSet **as, enum AVMediaType type)
{
    DASHContext *c = s->priv_data;

    void *mem = av_realloc(c->as, sizeof(*c->as) * (c->nb_as + 1));
    if (!mem)
        return AVERROR(ENOMEM);
    c->as = mem;
    ++c->nb_as;

    *as = &c->as[c->nb_as - 1];
    memset(*as, 0, sizeof(**as));
    (*as)->media_type = type;

    return 0;
}

784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
static int adaptation_set_add_stream(AVFormatContext *s, int as_idx, int i)
{
    DASHContext *c = s->priv_data;
    AdaptationSet *as = &c->as[as_idx - 1];
    OutputStream *os = &c->streams[i];

    if (as->media_type != s->streams[i]->codecpar->codec_type) {
        av_log(s, AV_LOG_ERROR, "Codec type of stream %d doesn't match AdaptationSet's media type\n", i);
        return AVERROR(EINVAL);
    } else if (os->as_idx) {
        av_log(s, AV_LOG_ERROR, "Stream %d is already assigned to an AdaptationSet\n", i);
        return AVERROR(EINVAL);
    }
    os->as_idx = as_idx;

    return 0;
}

802 803 804 805 806 807 808 809
static int parse_adaptation_sets(AVFormatContext *s)
{
    DASHContext *c = s->priv_data;
    const char *p = c->adaptation_sets;
    enum { new_set, parse_id, parsing_streams } state;
    AdaptationSet *as;
    int i, n, ret;

810
    // default: one AdaptationSet for each stream
811
    if (!p) {
812 813 814 815
        for (i = 0; i < s->nb_streams; i++) {
            if ((ret = add_adaptation_set(s, &as, s->streams[i]->codecpar->codec_type)) < 0)
                return ret;
            snprintf(as->id, sizeof(as->id), "%d", i);
816

817
            c->streams[i].as_idx = c->nb_as;
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849
        }
        goto end;
    }

    // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on
    state = new_set;
    while (*p) {
        if (*p == ' ') {
            p++;
            continue;
        } else if (state == new_set && av_strstart(p, "id=", &p)) {

            if ((ret = add_adaptation_set(s, &as, AVMEDIA_TYPE_UNKNOWN)) < 0)
                return ret;

            n = strcspn(p, ",");
            snprintf(as->id, sizeof(as->id), "%.*s", n, p);

            p += n;
            if (*p)
                p++;
            state = parse_id;
        } else if (state == parse_id && av_strstart(p, "streams=", &p)) {
            state = parsing_streams;
        } else if (state == parsing_streams) {
            AdaptationSet *as = &c->as[c->nb_as - 1];
            char idx_str[8], *end_str;

            n = strcspn(p, " ,");
            snprintf(idx_str, sizeof(idx_str), "%.*s", n, p);
            p += n;

850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
            // if value is "a" or "v", map all streams of that type
            if (as->media_type == AVMEDIA_TYPE_UNKNOWN && (idx_str[0] == 'v' || idx_str[0] == 'a')) {
                enum AVMediaType type = (idx_str[0] == 'v') ? AVMEDIA_TYPE_VIDEO : AVMEDIA_TYPE_AUDIO;
                av_log(s, AV_LOG_DEBUG, "Map all streams of type %s\n", idx_str);

                for (i = 0; i < s->nb_streams; i++) {
                    if (s->streams[i]->codecpar->codec_type != type)
                        continue;

                    as->media_type = s->streams[i]->codecpar->codec_type;

                    if ((ret = adaptation_set_add_stream(s, c->nb_as, i)) < 0)
                        return ret;
                }
            } else { // select single stream
                i = strtol(idx_str, &end_str, 10);
                if (idx_str == end_str || i < 0 || i >= s->nb_streams) {
                    av_log(s, AV_LOG_ERROR, "Selected stream \"%s\" not found!\n", idx_str);
                    return AVERROR(EINVAL);
                }
                av_log(s, AV_LOG_DEBUG, "Map stream %d\n", i);

                if (as->media_type == AVMEDIA_TYPE_UNKNOWN) {
                    as->media_type = s->streams[i]->codecpar->codec_type;
                }
875

876 877
                if ((ret = adaptation_set_add_stream(s, c->nb_as, i)) < 0)
                    return ret;
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
            }

            if (*p == ' ')
                state = new_set;
            if (*p)
                p++;
        } else {
            return AVERROR(EINVAL);
        }
    }

end:
    // check for unassigned streams
    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
        if (!os->as_idx) {
            av_log(s, AV_LOG_ERROR, "Stream %d is not mapped to an AdaptationSet\n", i);
            return AVERROR(EINVAL);
        }
    }
    return 0;
}

901 902 903 904 905
static int write_manifest(AVFormatContext *s, int final)
{
    DASHContext *c = s->priv_data;
    AVIOContext *out;
    char temp_filename[1024];
906
    int ret, i;
907
    const char *proto = avio_find_protocol_name(s->url);
908 909
    int use_rename = proto && !strcmp(proto, "file");
    static unsigned int warned_non_file = 0;
910
    AVDictionaryEntry *title = av_dict_get(s->metadata, "title", NULL, 0);
911
    AVDictionary *opts = NULL;
912

913 914 915
    if (!use_rename && !warned_non_file++)
        av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporary partial files\n");

916
    snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", s->url);
917
    set_http_options(&opts, c);
918
    ret = dashenc_io_open(s, &c->mpd_out, temp_filename, &opts);
919
    av_dict_free(&opts);
920
    if (ret < 0) {
921
        return handle_io_open_error(s, ret, temp_filename);
922
    }
923
    out = c->mpd_out;
924 925 926 927 928 929 930 931 932 933 934 935
    avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
    avio_printf(out, "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
                "\txmlns=\"urn:mpeg:dash:schema:mpd:2011\"\n"
                "\txmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
                "\txsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\"\n"
                "\tprofiles=\"urn:mpeg:dash:profile:isoff-live:2011\"\n"
                "\ttype=\"%s\"\n", final ? "static" : "dynamic");
    if (final) {
        avio_printf(out, "\tmediaPresentationDuration=\"");
        write_time(out, c->total_duration);
        avio_printf(out, "\"\n");
    } else {
936
        int64_t update_period = c->last_duration / AV_TIME_BASE;
937
        char now_str[100];
938 939
        if (c->use_template && !c->use_timeline)
            update_period = 500;
940
        avio_printf(out, "\tminimumUpdatePeriod=\"PT%"PRId64"S\"\n", update_period);
941
        avio_printf(out, "\tsuggestedPresentationDelay=\"PT%"PRId64"S\"\n", c->last_duration / AV_TIME_BASE);
942 943
        if (c->availability_start_time[0])
            avio_printf(out, "\tavailabilityStartTime=\"%s\"\n", c->availability_start_time);
944 945 946
        format_date_now(now_str, sizeof(now_str));
        if (now_str[0])
            avio_printf(out, "\tpublishTime=\"%s\"\n", now_str);
947 948 949 950 951 952 953
        if (c->window_size && c->use_template) {
            avio_printf(out, "\ttimeShiftBufferDepth=\"");
            write_time(out, c->last_duration * c->window_size);
            avio_printf(out, "\"\n");
        }
    }
    avio_printf(out, "\tminBufferTime=\"");
954
    write_time(out, c->last_duration * 2);
955 956 957 958 959 960 961 962
    avio_printf(out, "\">\n");
    avio_printf(out, "\t<ProgramInformation>\n");
    if (title) {
        char *escaped = xmlescape(title->value);
        avio_printf(out, "\t\t<Title>%s</Title>\n", escaped);
        av_free(escaped);
    }
    avio_printf(out, "\t</ProgramInformation>\n");
963

964 965 966 967
    if (c->window_size && s->nb_streams > 0 && c->streams[0].nb_segments > 0 && !c->use_template) {
        OutputStream *os = &c->streams[0];
        int start_index = FFMAX(os->nb_segments - c->window_size, 0);
        int64_t start_time = av_rescale_q(os->segments[start_index]->time, s->streams[0]->time_base, AV_TIME_BASE_Q);
968
        avio_printf(out, "\t<Period id=\"0\" start=\"");
969 970 971
        write_time(out, start_time);
        avio_printf(out, "\">\n");
    } else {
972
        avio_printf(out, "\t<Period id=\"0\" start=\"PT0.0S\">\n");
973 974
    }

975
    for (i = 0; i < c->nb_as; i++) {
976
        if ((ret = write_adaptation_set(s, out, i, final)) < 0)
977
            return ret;
978 979
    }
    avio_printf(out, "\t</Period>\n");
980 981 982 983

    if (c->utc_timing_url)
        avio_printf(out, "\t<UTCTiming schemeIdUri=\"urn:mpeg:dash:utc:http-xsdate:2014\" value=\"%s\"/>\n", c->utc_timing_url);

984 985
    avio_printf(out, "</MPD>\n");
    avio_flush(out);
986
    dashenc_io_close(s, &c->mpd_out, temp_filename);
987

988
    if (use_rename) {
989
        if ((ret = avpriv_io_move(temp_filename, s->url)) < 0)
990 991 992
            return ret;
    }

993
    if (c->hls_playlist) {
994
        char filename_hls[1024];
995
        const char *audio_group = "A1";
996
        char audio_codec_str[128] = "\0";
997 998
        int is_default = 1;
        int max_audio_bitrate = 0;
999

1000 1001 1002 1003 1004
        // Publish master playlist only the configured rate
        if (c->master_playlist_created && (!c->master_publish_rate ||
             c->streams[0].segment_index % c->master_publish_rate))
            return 0;

1005
        if (*c->dirname)
1006
            snprintf(filename_hls, sizeof(filename_hls), "%smaster.m3u8", c->dirname);
1007 1008 1009 1010 1011 1012
        else
            snprintf(filename_hls, sizeof(filename_hls), "master.m3u8");

        snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", filename_hls);

        set_http_options(&opts, c);
1013
        ret = dashenc_io_open(s, &c->m3u8_out, temp_filename, &opts);
1014
        av_dict_free(&opts);
1015
        if (ret < 0) {
1016
            return handle_io_open_error(s, ret, temp_filename);
1017 1018
        }

1019
        ff_hls_write_playlist_version(c->m3u8_out, 7);
1020 1021 1022 1023

        for (i = 0; i < s->nb_streams; i++) {
            char playlist_file[64];
            AVStream *st = s->streams[i];
1024
            OutputStream *os = &c->streams[i];
1025 1026
            if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
                continue;
1027 1028
            if (os->segment_type != SEGMENT_TYPE_MP4)
                continue;
1029
            get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
1030
            ff_hls_write_audio_rendition(c->m3u8_out, (char *)audio_group,
1031
                                         playlist_file, NULL, i, is_default);
1032 1033
            max_audio_bitrate = FFMAX(st->codecpar->bit_rate +
                                      os->muxer_overhead, max_audio_bitrate);
1034 1035 1036 1037 1038
            if (!av_strnstr(audio_codec_str, os->codec_str, sizeof(audio_codec_str))) {
                if (strlen(audio_codec_str))
                    av_strlcat(audio_codec_str, ",", sizeof(audio_codec_str));
                av_strlcat(audio_codec_str, os->codec_str, sizeof(audio_codec_str));
            }
1039 1040 1041 1042 1043
            is_default = 0;
        }

        for (i = 0; i < s->nb_streams; i++) {
            char playlist_file[64];
1044
            char codec_str[128];
1045
            AVStream *st = s->streams[i];
1046
            OutputStream *os = &c->streams[i];
1047
            char *agroup = NULL;
1048
            char *codec_str_ptr = NULL;
1049
            int stream_bitrate = st->codecpar->bit_rate + os->muxer_overhead;
1050 1051
            if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
                continue;
1052 1053
            if (os->segment_type != SEGMENT_TYPE_MP4)
                continue;
1054
            av_strlcpy(codec_str, os->codec_str, sizeof(codec_str));
1055
            if (max_audio_bitrate) {
1056 1057
                agroup = (char *)audio_group;
                stream_bitrate += max_audio_bitrate;
1058 1059
                av_strlcat(codec_str, ",", sizeof(codec_str));
                av_strlcat(codec_str, audio_codec_str, sizeof(codec_str));
1060
            }
1061 1062 1063
            if (st->codecpar->codec_id != AV_CODEC_ID_HEVC) {
                codec_str_ptr = codec_str;
            }
1064
            get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
1065 1066
            ff_hls_write_stream_info(st, c->m3u8_out, stream_bitrate,
                                     playlist_file, agroup,
1067
                                     codec_str_ptr, NULL);
1068
        }
1069
        dashenc_io_close(s, &c->m3u8_out, temp_filename);
1070 1071 1072 1073 1074
        if (use_rename)
            if ((ret = avpriv_io_move(temp_filename, filename_hls)) < 0)
                return ret;
        c->master_playlist_created = 1;
    }
1075 1076

    return 0;
1077 1078
}

1079 1080 1081 1082 1083 1084 1085 1086
static int dict_copy_entry(AVDictionary **dst, const AVDictionary *src, const char *key)
{
    AVDictionaryEntry *entry = av_dict_get(src, key, NULL, 0);
    if (entry)
        av_dict_set(dst, key, entry->value, AV_DICT_DONT_OVERWRITE);
    return 0;
}

1087
static int dash_init(AVFormatContext *s)
1088 1089 1090 1091 1092 1093
{
    DASHContext *c = s->priv_data;
    int ret = 0, i;
    char *ptr;
    char basename[1024];

1094
    c->nr_of_streams_to_flush = 0;
1095 1096
    if (c->single_file_name)
        c->single_file = 1;
1097 1098 1099
    if (c->single_file)
        c->use_template = 0;

1100 1101 1102 1103 1104 1105
#if FF_API_DASH_MIN_SEG_DURATION
    if (c->min_seg_duration != 5000000) {
        av_log(s, AV_LOG_WARNING, "The min_seg_duration option is deprecated and will be removed. Please use the -seg_duration\n");
        c->seg_duration = c->min_seg_duration;
    }
#endif
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120
    if (c->lhls && s->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
        av_log(s, AV_LOG_ERROR,
               "LHLS is experimental, Please set -strict experimental in order to enable it.\n");
        return AVERROR_EXPERIMENTAL;
    }

    if (c->lhls && !c->streaming) {
        av_log(s, AV_LOG_WARNING, "LHLS option will be ignored as streaming is not enabled\n");
        c->lhls = 0;
    }

    if (c->lhls && !c->hls_playlist) {
        av_log(s, AV_LOG_WARNING, "LHLS option will be ignored as hls_playlist is not enabled\n");
        c->lhls = 0;
    }
1121

1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
    if (c->global_sidx && !c->single_file) {
        av_log(s, AV_LOG_WARNING, "Global SIDX option will be ignored as single_file is not enabled\n");
        c->global_sidx = 0;
    }

    if (c->global_sidx && c->streaming) {
        av_log(s, AV_LOG_WARNING, "Global SIDX option will be ignored as streaming is enabled\n");
        c->global_sidx = 0;
    }

1132
    av_strlcpy(c->dirname, s->url, sizeof(c->dirname));
1133 1134 1135 1136 1137 1138
    ptr = strrchr(c->dirname, '/');
    if (ptr) {
        av_strlcpy(basename, &ptr[1], sizeof(basename));
        ptr[1] = '\0';
    } else {
        c->dirname[0] = '\0';
1139
        av_strlcpy(basename, s->url, sizeof(basename));
1140 1141 1142 1143 1144 1145 1146
    }

    ptr = strrchr(basename, '.');
    if (ptr)
        *ptr = '\0';

    c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams);
1147 1148
    if (!c->streams)
        return AVERROR(ENOMEM);
1149

1150
    if ((ret = parse_adaptation_sets(s)) < 0)
Rodger Combs's avatar
Rodger Combs committed
1151
        return ret;
1152

1153 1154 1155
    if ((ret = init_segment_types(s)) < 0)
        return ret;

1156 1157
    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
1158
        AdaptationSet *as = &c->as[os->as_idx - 1];
1159 1160 1161 1162 1163
        AVFormatContext *ctx;
        AVStream *st;
        AVDictionary *opts = NULL;
        char filename[1024];

1164
        os->bit_rate = s->streams[i]->codecpar->bit_rate;
1165
        if (!os->bit_rate) {
1166 1167 1168 1169 1170 1171 1172
            int level = s->strict_std_compliance >= FF_COMPLIANCE_STRICT ?
                        AV_LOG_ERROR : AV_LOG_WARNING;
            av_log(s, level, "No bit rate set for stream %d\n", i);
            if (s->strict_std_compliance >= FF_COMPLIANCE_STRICT)
                return AVERROR(EINVAL);
        }

1173 1174 1175 1176
        // copy AdaptationSet language and role from stream metadata
        dict_copy_entry(&as->metadata, s->streams[i]->metadata, "language");
        dict_copy_entry(&as->metadata, s->streams[i]->metadata, "role");

1177
        ctx = avformat_alloc_context();
1178 1179
        if (!ctx)
            return AVERROR(ENOMEM);
1180

1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196
        if (c->init_seg_name) {
            os->init_seg_name = av_strireplace(c->init_seg_name, "$ext$", os->format_name);
            if (!os->init_seg_name)
                return AVERROR(ENOMEM);
        }
        if (c->media_seg_name) {
            os->media_seg_name = av_strireplace(c->media_seg_name, "$ext$", os->format_name);
            if (!os->media_seg_name)
                return AVERROR(ENOMEM);
        }
        if (c->single_file_name) {
            os->single_file_name = av_strireplace(c->single_file_name, "$ext$", os->format_name);
            if (!os->single_file_name)
                return AVERROR(ENOMEM);
        }

1197
        if (os->segment_type == SEGMENT_TYPE_WEBM) {
1198 1199 1200
            if ((!c->single_file && check_file_extension(os->init_seg_name, os->format_name) != 0) ||
                (!c->single_file && check_file_extension(os->media_seg_name, os->format_name) != 0) ||
                (c->single_file && check_file_extension(os->single_file_name, os->format_name) != 0)) {
1201 1202 1203 1204 1205
                av_log(s, AV_LOG_WARNING,
                       "One or many segment file names doesn't end with .webm. "
                       "Override -init_seg_name and/or -media_seg_name and/or "
                       "-single_file_name to end with the extension .webm\n");
            }
1206 1207 1208 1209 1210
            if (c->streaming) {
                // Streaming not supported as matroskaenc buffers internally before writing the output
                av_log(s, AV_LOG_WARNING, "One or more streams in WebM output format. Streaming option will be ignored\n");
                c->streaming = 0;
            }
1211 1212
        }

1213
        ctx->oformat = av_guess_format(os->format_name, NULL, NULL);
Rodger Combs's avatar
Rodger Combs committed
1214 1215
        if (!ctx->oformat)
            return AVERROR_MUXER_NOT_FOUND;
1216
        os->ctx = ctx;
1217 1218 1219 1220 1221
        ctx->interrupt_callback    = s->interrupt_callback;
        ctx->opaque                = s->opaque;
        ctx->io_close              = s->io_close;
        ctx->io_open               = s->io_open;
        ctx->strict_std_compliance = s->strict_std_compliance;
1222

1223 1224
        if (!(st = avformat_new_stream(ctx, NULL)))
            return AVERROR(ENOMEM);
1225
        avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar);
1226 1227
        st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
        st->time_base = s->streams[i]->time_base;
1228
        st->avg_frame_rate = s->streams[i]->avg_frame_rate;
1229
        ctx->avoid_negative_ts = s->avoid_negative_ts;
1230
        ctx->flags = s->flags;
1231

1232
        if (c->single_file) {
1233 1234
            if (os->single_file_name)
                ff_dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), os->single_file_name, i, 0, os->bit_rate, 0);
1235
            else
1236
                snprintf(os->initfile, sizeof(os->initfile), "%s-stream%d.%s", basename, i, os->format_name);
1237
        } else {
1238
            ff_dash_fill_tmpl_params(os->initfile, sizeof(os->initfile), os->init_seg_name, i, 0, os->bit_rate, 0);
1239
        }
1240
        snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile);
1241
        set_http_options(&opts, c);
1242 1243 1244 1245 1246 1247 1248 1249
        if (!c->single_file) {
            if ((ret = avio_open_dyn_buf(&ctx->pb)) < 0)
                return ret;
            ret = s->io_open(s, &os->out, filename, AVIO_FLAG_WRITE, &opts);
        } else {
            ctx->url = av_strdup(filename);
            ret = avio_open2(&ctx->pb, filename, AVIO_FLAG_WRITE, NULL, &opts);
        }
1250
        av_dict_free(&opts);
1251
        if (ret < 0)
1252
            return ret;
1253 1254
        os->init_start_pos = 0;

1255 1256 1257 1258 1259
        if (c->format_options_str) {
            ret = av_dict_parse_string(&opts, c->format_options_str, "=", ":", 0);
            if (ret < 0)
                return ret;
        }
1260

1261
        if (os->segment_type == SEGMENT_TYPE_MP4) {
1262
            if (c->streaming)
1263 1264 1265
                // frag_every_frame : Allows lower latency streaming
                // skip_sidx : Reduce bitrate overhead
                // skip_trailer : Avoids growing memory usage with time
1266
                av_dict_set(&opts, "movflags", "frag_every_frame+dash+delay_moov+skip_sidx+skip_trailer", 0);
1267 1268 1269 1270 1271 1272
            else {
                if (c->global_sidx)
                    av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov+global_sidx+skip_trailer", 0);
                else
                    av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov+skip_trailer", 0);
            }
1273
        } else {
1274
            av_dict_set_int(&opts, "cluster_time_limit", c->seg_duration / 1000, 0);
1275
            av_dict_set_int(&opts, "cluster_size_limit", 5 * 1024 * 1024, 0); // set a large cluster size limit
1276 1277 1278
            av_dict_set_int(&opts, "dash", 1, 0);
            av_dict_set_int(&opts, "dash_track_number", i + 1, 0);
            av_dict_set_int(&opts, "live", 1, 0);
1279
        }
1280 1281 1282
        ret = avformat_init_output(ctx, &opts);
        av_dict_free(&opts);
        if (ret < 0)
1283
            return ret;
1284 1285 1286
        os->ctx_inited = 1;
        avio_flush(ctx->pb);

1287
        av_log(s, AV_LOG_VERBOSE, "Representation %d init segment will be written to: %s\n", i, filename);
1288 1289 1290 1291 1292 1293

        s->streams[i]->time_base = st->time_base;
        // If the muxer wants to shift timestamps, request to have them shifted
        // already before being handed to this muxer, so we don't have mismatches
        // between the MPD and the actual segments.
        s->avoid_negative_ts = ctx->avoid_negative_ts;
1294
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
1295 1296
            AVRational avg_frame_rate = s->streams[i]->avg_frame_rate;
            if (avg_frame_rate.num > 0) {
1297 1298 1299 1300
                if (av_cmp_q(avg_frame_rate, as->min_frame_rate) < 0)
                    as->min_frame_rate = avg_frame_rate;
                if (av_cmp_q(as->max_frame_rate, avg_frame_rate) < 0)
                    as->max_frame_rate = avg_frame_rate;
1301
            } else {
1302
                as->ambiguous_frame_rate = 1;
1303
            }
1304
            c->has_video = 1;
1305
        }
1306

1307 1308
        set_codec_str(s, st->codecpar, &st->avg_frame_rate, os->codec_str,
                      sizeof(os->codec_str));
1309 1310
        os->first_pts = AV_NOPTS_VALUE;
        os->max_pts = AV_NOPTS_VALUE;
1311
        os->last_dts = AV_NOPTS_VALUE;
1312
        os->segment_index = 1;
1313 1314 1315

        if (s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
            c->nr_of_streams_to_flush++;
1316 1317
    }

1318 1319
    if (!c->has_video && c->seg_duration <= 0) {
        av_log(s, AV_LOG_WARNING, "no video stream and no seg duration set\n");
1320
        return AVERROR(EINVAL);
1321
    }
1322 1323 1324

    c->nr_of_streams_flushed = 0;

1325 1326 1327 1328 1329 1330 1331 1332 1333
    return 0;
}

static int dash_write_header(AVFormatContext *s)
{
    DASHContext *c = s->priv_data;
    int i, ret;
    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
1334
        if ((ret = avformat_write_header(os->ctx, NULL)) < 0)
1335
            return ret;
1336 1337 1338 1339

        // Flush init segment
        // Only for WebM segment, since for mp4 delay_moov is set and
        // the init segment is thus flushed after the first packets.
1340
        if (os->segment_type == SEGMENT_TYPE_WEBM &&
1341 1342
            (ret = flush_init_segment(s, os)) < 0)
            return ret;
1343
    }
1344 1345 1346 1347
    return ret;
}

static int add_segment(OutputStream *os, const char *file,
1348
                       int64_t time, int64_t duration,
1349
                       int64_t start_pos, int64_t range_length,
1350
                       int64_t index_length, int next_exp_index)
1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
{
    int err;
    Segment *seg;
    if (os->nb_segments >= os->segments_size) {
        os->segments_size = (os->segments_size + 1) * 2;
        if ((err = av_reallocp(&os->segments, sizeof(*os->segments) *
                               os->segments_size)) < 0) {
            os->segments_size = 0;
            os->nb_segments = 0;
            return err;
        }
    }
    seg = av_mallocz(sizeof(*seg));
    if (!seg)
        return AVERROR(ENOMEM);
    av_strlcpy(seg->file, file, sizeof(seg->file));
    seg->time = time;
    seg->duration = duration;
1369 1370 1371 1372
    if (seg->time < 0) { // If pts<0, it is expected to be cut away with an edit list
        seg->duration += seg->time;
        seg->time = 0;
    }
1373 1374 1375 1376 1377
    seg->start_pos = start_pos;
    seg->range_length = range_length;
    seg->index_length = index_length;
    os->segments[os->nb_segments++] = seg;
    os->segment_index++;
1378 1379 1380 1381 1382 1383
    //correcting the segment index if it has fallen behind the expected value
    if (os->segment_index < next_exp_index) {
        av_log(NULL, AV_LOG_WARNING, "Correcting the segment index after file %s: current=%d corrected=%d\n",
               file, os->segment_index, next_exp_index);
        os->segment_index = next_exp_index;
    }
1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396
    return 0;
}

static void write_styp(AVIOContext *pb)
{
    avio_wb32(pb, 24);
    ffio_wfourcc(pb, "styp");
    ffio_wfourcc(pb, "msdh");
    avio_wb32(pb, 0); /* minor */
    ffio_wfourcc(pb, "msdh");
    ffio_wfourcc(pb, "msix");
}

1397 1398
static void find_index_range(AVFormatContext *s, const char *full_path,
                             int64_t pos, int *index_length)
1399 1400
{
    uint8_t buf[8];
1401
    AVIOContext *pb;
1402 1403
    int ret;

1404
    ret = s->io_open(s, &pb, full_path, AVIO_FLAG_READ, NULL);
1405 1406
    if (ret < 0)
        return;
1407 1408
    if (avio_seek(pb, pos, SEEK_SET) != pos) {
        ff_format_io_close(s, &pb);
1409 1410
        return;
    }
1411 1412
    ret = avio_read(pb, buf, 8);
    ff_format_io_close(s, &pb);
1413 1414 1415 1416 1417 1418 1419
    if (ret < 8)
        return;
    if (AV_RL32(&buf[4]) != MKTAG('s', 'i', 'd', 'x'))
        return;
    *index_length = AV_RB32(&buf[0]);
}

1420
static int update_stream_extradata(AVFormatContext *s, OutputStream *os,
1421 1422
                                   AVCodecParameters *par,
                                   AVRational *frame_rate)
1423 1424 1425
{
    uint8_t *extradata;

1426
    if (os->ctx->streams[0]->codecpar->extradata_size || !par->extradata_size)
1427 1428
        return 0;

1429
    extradata = av_malloc(par->extradata_size);
1430 1431 1432 1433

    if (!extradata)
        return AVERROR(ENOMEM);

1434
    memcpy(extradata, par->extradata, par->extradata_size);
1435

1436 1437
    os->ctx->streams[0]->codecpar->extradata = extradata;
    os->ctx->streams[0]->codecpar->extradata_size = par->extradata_size;
1438

1439
    set_codec_str(s, par, frame_rate, os->codec_str, sizeof(os->codec_str));
1440 1441 1442 1443

    return 0;
}

1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459
static void dashenc_delete_file(AVFormatContext *s, char *filename) {
    DASHContext *c = s->priv_data;
    int http_base_proto = ff_is_http_proto(filename);

    if (http_base_proto) {
        AVIOContext *out = NULL;
        AVDictionary *http_opts = NULL;

        set_http_options(&http_opts, c);
        av_dict_set(&http_opts, "method", "DELETE", 0);

        if (dashenc_io_open(s, &out, filename, &http_opts) < 0) {
            av_log(s, AV_LOG_ERROR, "failed to delete %s\n", filename);
        }

        av_dict_free(&http_opts);
1460
        ff_format_io_close(s, &out);
1461 1462 1463 1464 1465 1466 1467
    } else {
        int res = avpriv_io_delete(filename);
        if (res < 0) {
            char errbuf[AV_ERROR_MAX_STRING_SIZE];
            av_strerror(res, errbuf, sizeof(errbuf));
            av_log(s, (res == AVERROR(ENOENT) ? AV_LOG_WARNING : AV_LOG_ERROR), "failed to delete %s: %s\n", filename, errbuf);
        }
1468 1469 1470
    }
}

1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511
static int dashenc_delete_segment_file(AVFormatContext *s, const char* file)
{
    DASHContext *c = s->priv_data;
    size_t dirname_len, file_len;
    char filename[1024];

    dirname_len = strlen(c->dirname);
    if (dirname_len >= sizeof(filename)) {
        av_log(s, AV_LOG_WARNING, "Cannot delete segments as the directory path is too long: %"PRIu64" characters: %s\n",
            (uint64_t)dirname_len, c->dirname);
        return AVERROR(ENAMETOOLONG);
    }

    memcpy(filename, c->dirname, dirname_len);

    file_len = strlen(file);
    if ((dirname_len + file_len) >= sizeof(filename)) {
        av_log(s, AV_LOG_WARNING, "Cannot delete segments as the path is too long: %"PRIu64" characters: %s%s\n",
            (uint64_t)(dirname_len + file_len), c->dirname, file);
        return AVERROR(ENAMETOOLONG);
    }

    memcpy(filename + dirname_len, file, file_len + 1); // include the terminating zero
    dashenc_delete_file(s, filename);

    return 0;
}

static inline void dashenc_delete_media_segments(AVFormatContext *s, OutputStream *os, int remove_count)
{
    for (int i = 0; i < remove_count; ++i) {
        dashenc_delete_segment_file(s, os->segments[i]->file);

        // Delete the segment regardless of whether the file was successfully deleted
        av_free(os->segments[i]);
    }

    os->nb_segments -= remove_count;
    memmove(os->segments, os->segments + remove_count, os->nb_segments * sizeof(*os->segments));
}

1512
static int dash_flush(AVFormatContext *s, int final, int stream)
1513 1514 1515
{
    DASHContext *c = s->priv_data;
    int i, ret = 0;
1516

1517
    const char *proto = avio_find_protocol_name(s->url);
1518 1519
    int use_rename = proto && !strcmp(proto, "file");

1520 1521
    int cur_flush_segment_index = 0, next_exp_index = -1;
    if (stream >= 0) {
1522
        cur_flush_segment_index = c->streams[stream].segment_index;
1523

1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535
        //finding the next segment's expected index, based on the current pts value
        if (c->use_template && !c->use_timeline && c->index_correction &&
            c->streams[stream].last_pts != AV_NOPTS_VALUE &&
            c->streams[stream].first_pts != AV_NOPTS_VALUE) {
            int64_t pts_diff = av_rescale_q(c->streams[stream].last_pts -
                                            c->streams[stream].first_pts,
                                            s->streams[stream]->time_base,
                                            AV_TIME_BASE_Q);
            next_exp_index = (pts_diff / c->seg_duration) + 1;
        }
    }

1536 1537
    for (i = 0; i < s->nb_streams; i++) {
        OutputStream *os = &c->streams[i];
1538
        AVStream *st = s->streams[i];
1539 1540 1541 1542 1543
        int range_length, index_length = 0;

        if (!os->packets_written)
            continue;

1544 1545 1546 1547
        // Flush the single stream that got a keyframe right now.
        // Flush all audio streams as well, in sync with video keyframes,
        // but not the other video streams.
        if (stream >= 0 && i != stream) {
1548
            if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
1549 1550 1551 1552 1553 1554 1555
                continue;
            // Make sure we don't flush audio streams multiple times, when
            // all video streams are flushed one at a time.
            if (c->has_video && os->segment_index > cur_flush_segment_index)
                continue;
        }

1556
        if (!c->single_file) {
1557
            if (os->segment_type == SEGMENT_TYPE_MP4 && !os->written_len)
1558
                write_styp(os->ctx->pb);
1559
        } else {
1560
            snprintf(os->full_path, sizeof(os->full_path), "%s%s", c->dirname, os->initfile);
1561
        }
1562

1563
        ret = flush_dynbuf(c, os, &range_length);
1564 1565
        if (ret < 0)
            break;
1566 1567 1568
        os->packets_written = 0;

        if (c->single_file) {
1569
            find_index_range(s, os->full_path, os->pos, &index_length);
1570
        } else {
1571
            dashenc_io_close(s, &os->out, os->temp_path);
1572 1573

            if (use_rename) {
1574
                ret = avpriv_io_move(os->temp_path, os->full_path);
1575 1576 1577
                if (ret < 0)
                    break;
            }
1578
        }
1579

1580 1581 1582 1583 1584 1585 1586
        if (!os->muxer_overhead)
            os->muxer_overhead = ((int64_t) (range_length - os->total_pkt_size) *
                                  8 * AV_TIME_BASE) /
                                 av_rescale_q(os->max_pts - os->start_pts,
                                              st->time_base, AV_TIME_BASE_Q);
        os->total_pkt_size = 0;

1587 1588
        if (!os->bit_rate) {
            // calculate average bitrate of first segment
1589 1590 1591
            int64_t bitrate = (int64_t) range_length * 8 * AV_TIME_BASE / av_rescale_q(os->max_pts - os->start_pts,
                                                                                       st->time_base,
                                                                                       AV_TIME_BASE_Q);
1592
            if (bitrate >= 0)
1593 1594
                os->bit_rate = bitrate;
        }
1595
        add_segment(os, os->filename, os->start_pts, os->max_pts - os->start_pts, os->pos, range_length, index_length, next_exp_index);
1596
        av_log(s, AV_LOG_VERBOSE, "Representation %d media segment %d written to: %s\n", i, os->segment_index, os->full_path);
1597 1598

        os->pos += range_length;
1599 1600
    }

1601
    if (c->window_size) {
1602 1603
        for (i = 0; i < s->nb_streams; i++) {
            OutputStream *os = &c->streams[i];
1604 1605 1606
            int remove_count = os->nb_segments - c->window_size - c->extra_window_size;
            if (remove_count > 0)
                dashenc_delete_media_segments(s, os, remove_count);
1607 1608 1609
        }
    }

1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632
    if (final) {
        for (i = 0; i < s->nb_streams; i++) {
            OutputStream *os = &c->streams[i];
            if (os->ctx && os->ctx_inited) {
                int file_size = avio_tell(os->ctx->pb);
                av_write_trailer(os->ctx);
                if (c->global_sidx) {
                    int j, start_index, start_number;
                    int sidx_size = avio_tell(os->ctx->pb) - file_size;
                    get_start_index_number(os, c, &start_index, &start_number);
                    if (start_index >= os->nb_segments ||
                        os->segment_type != SEGMENT_TYPE_MP4)
                        continue;
                    os->init_range_length += sidx_size;
                    for (j = start_index; j < os->nb_segments; j++) {
                        Segment *seg = os->segments[j];
                        seg->start_pos += sidx_size;
                    }
                }

            }
        }
    }
1633
    if (ret >= 0) {
1634
        if (c->has_video && !final) {
1635 1636 1637 1638 1639 1640
            c->nr_of_streams_flushed++;
            if (c->nr_of_streams_flushed != c->nr_of_streams_to_flush)
                return ret;

            c->nr_of_streams_flushed = 0;
        }
1641
        ret = write_manifest(s, final);
1642
    }
1643 1644 1645 1646 1647 1648 1649 1650
    return ret;
}

static int dash_write_packet(AVFormatContext *s, AVPacket *pkt)
{
    DASHContext *c = s->priv_data;
    AVStream *st = s->streams[pkt->stream_index];
    OutputStream *os = &c->streams[pkt->stream_index];
1651
    int64_t seg_end_duration, elapsed_duration;
1652 1653
    int ret;

1654
    ret = update_stream_extradata(s, os, st->codecpar, &st->avg_frame_rate);
1655 1656 1657
    if (ret < 0)
        return ret;

1658 1659 1660 1661 1662 1663 1664 1665 1666 1667
    // Fill in a heuristic guess of the packet duration, if none is available.
    // The mp4 muxer will do something similar (for the last packet in a fragment)
    // if nothing is set (setting it for the other packets doesn't hurt).
    // By setting a nonzero duration here, we can be sure that the mp4 muxer won't
    // invoke its heuristic (this doesn't have to be identical to that algorithm),
    // so that we know the exact timestamps of fragments.
    if (!pkt->duration && os->last_dts != AV_NOPTS_VALUE)
        pkt->duration = pkt->dts - os->last_dts;
    os->last_dts = pkt->dts;

1668 1669
    // If forcing the stream to start at 0, the mp4 muxer will set the start
    // timestamps to 0. Do the same here, to avoid mismatches in duration/timestamps.
1670
    if (os->first_pts == AV_NOPTS_VALUE &&
1671 1672 1673 1674 1675
        s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO) {
        pkt->pts -= pkt->dts;
        pkt->dts  = 0;
    }

1676 1677
    if (os->first_pts == AV_NOPTS_VALUE)
        os->first_pts = pkt->pts;
1678
    os->last_pts = pkt->pts;
1679

1680 1681 1682
    if (!c->availability_start_time[0]) {
        int64_t start_time_us = av_gettime();
        c->start_time_s = start_time_us / 1000000;
1683 1684
        format_date_now(c->availability_start_time,
                        sizeof(c->availability_start_time));
1685
    }
1686

1687 1688 1689 1690 1691 1692 1693
    if (!os->availability_time_offset && pkt->duration) {
        int64_t frame_duration = av_rescale_q(pkt->duration, st->time_base,
                                              AV_TIME_BASE_Q);
         os->availability_time_offset = ((double) c->seg_duration -
                                         frame_duration) / AV_TIME_BASE;
    }

1694 1695 1696 1697 1698 1699 1700 1701
    if (c->use_template && !c->use_timeline) {
        elapsed_duration = pkt->pts - os->first_pts;
        seg_end_duration = (int64_t) os->segment_index * c->seg_duration;
    } else {
        elapsed_duration = pkt->pts - os->start_pts;
        seg_end_duration = c->seg_duration;
    }

1702
    if ((!c->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) &&
1703
        pkt->flags & AV_PKT_FLAG_KEY && os->packets_written &&
1704 1705
        av_compare_ts(elapsed_duration, st->time_base,
                      seg_end_duration, AV_TIME_BASE_Q) >= 0) {
1706 1707
        int64_t prev_duration = c->last_duration;

1708
        c->last_duration = av_rescale_q(pkt->pts - os->start_pts,
1709 1710
                                        st->time_base,
                                        AV_TIME_BASE_Q);
1711
        c->total_duration = av_rescale_q(pkt->pts - os->first_pts,
1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723
                                         st->time_base,
                                         AV_TIME_BASE_Q);

        if ((!c->use_timeline || !c->use_template) && prev_duration) {
            if (c->last_duration < prev_duration*9/10 ||
                c->last_duration > prev_duration*11/10) {
                av_log(s, AV_LOG_WARNING,
                       "Segment durations differ too much, enable use_timeline "
                       "and use_template, or keep a stricter keyframe interval\n");
            }
        }

1724
        if ((ret = dash_flush(s, 0, pkt->stream_index)) < 0)
1725 1726 1727
            return ret;
    }

1728 1729 1730 1731
    if (!os->packets_written) {
        // If we wrote a previous segment, adjust the start time of the segment
        // to the end of the previous one (which is the same as the mp4 muxer
        // does). This avoids gaps in the timeline.
1732 1733
        if (os->max_pts != AV_NOPTS_VALUE)
            os->start_pts = os->max_pts;
1734
        else
1735
            os->start_pts = pkt->pts;
1736
    }
1737 1738 1739 1740
    if (os->max_pts == AV_NOPTS_VALUE)
        os->max_pts = pkt->pts + pkt->duration;
    else
        os->max_pts = FFMAX(os->max_pts, pkt->pts + pkt->duration);
1741
    os->packets_written++;
1742
    os->total_pkt_size += pkt->size;
1743 1744 1745 1746 1747 1748 1749
    if ((ret = ff_write_chained(os->ctx, 0, pkt, s, 0)) < 0)
        return ret;

    if (!os->init_range_length)
        flush_init_segment(s, os);

    //open the output context when the first frame of a segment is ready
1750
    if (!c->single_file && os->packets_written == 1) {
1751
        AVDictionary *opts = NULL;
1752
        const char *proto = avio_find_protocol_name(s->url);
1753 1754 1755
        int use_rename = proto && !strcmp(proto, "file");
        os->filename[0] = os->full_path[0] = os->temp_path[0] = '\0';
        ff_dash_fill_tmpl_params(os->filename, sizeof(os->filename),
1756
                                 os->media_seg_name, pkt->stream_index,
1757 1758 1759 1760 1761 1762 1763
                                 os->segment_index, os->bit_rate, os->start_pts);
        snprintf(os->full_path, sizeof(os->full_path), "%s%s", c->dirname,
                 os->filename);
        snprintf(os->temp_path, sizeof(os->temp_path),
                 use_rename ? "%s.tmp" : "%s", os->full_path);
        set_http_options(&opts, c);
        ret = dashenc_io_open(s, &os->out, os->temp_path, &opts);
1764
        av_dict_free(&opts);
1765
        if (ret < 0) {
1766
            return handle_io_open_error(s, ret, os->temp_path);
1767
        }
1768 1769 1770 1771
        if (c->lhls) {
            char *prefetch_url = use_rename ? NULL : os->filename;
            write_hls_media_playlist(os, s, pkt->stream_index, 0, prefetch_url);
        }
1772 1773
    }

1774
    //write out the data immediately in streaming mode
1775
    if (c->streaming && os->segment_type == SEGMENT_TYPE_MP4) {
1776 1777 1778 1779 1780 1781
        int len = 0;
        uint8_t *buf = NULL;
        if (!os->written_len)
            write_styp(os->ctx->pb);
        avio_flush(os->ctx->pb);
        len = avio_get_dyn_buf (os->ctx->pb, &buf);
1782 1783 1784 1785
        if (os->out) {
            avio_write(os->out, buf + os->written_len, len - os->written_len);
            avio_flush(os->out);
        }
1786 1787 1788
        os->written_len = len;
    }

1789
    return ret;
1790 1791 1792 1793 1794
}

static int dash_write_trailer(AVFormatContext *s)
{
    DASHContext *c = s->priv_data;
1795
    int i;
1796 1797 1798 1799 1800 1801

    if (s->nb_streams > 0) {
        OutputStream *os = &c->streams[0];
        // If no segments have been written so far, try to do a crude
        // guess of the segment duration
        if (!c->last_duration)
1802
            c->last_duration = av_rescale_q(os->max_pts - os->start_pts,
1803 1804
                                            s->streams[0]->time_base,
                                            AV_TIME_BASE_Q);
1805
        c->total_duration = av_rescale_q(os->max_pts - os->first_pts,
1806 1807 1808
                                         s->streams[0]->time_base,
                                         AV_TIME_BASE_Q);
    }
1809
    dash_flush(s, 1, -1);
1810

1811 1812 1813
    if (c->remove_at_exit) {
        for (i = 0; i < s->nb_streams; ++i) {
            OutputStream *os = &c->streams[i];
1814 1815
            dashenc_delete_media_segments(s, os, os->nb_segments);
            dashenc_delete_segment_file(s, os->initfile);
1816 1817 1818 1819 1820
            if (c->hls_playlist && os->segment_type == SEGMENT_TYPE_MP4) {
                char filename[1024];
                get_hls_playlist_name(filename, sizeof(filename), c->dirname, i);
                dashenc_delete_file(s, filename);
            }
1821
        }
1822
        dashenc_delete_file(s, s->url);
1823 1824

        if (c->hls_playlist && c->master_playlist_created) {
1825
            char filename[1024];
1826 1827 1828
            snprintf(filename, sizeof(filename), "%smaster.m3u8", c->dirname);
            dashenc_delete_file(s, filename);
        }
1829 1830 1831 1832 1833
    }

    return 0;
}

1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856
static int dash_check_bitstream(struct AVFormatContext *s, const AVPacket *avpkt)
{
    DASHContext *c = s->priv_data;
    OutputStream *os = &c->streams[avpkt->stream_index];
    AVFormatContext *oc = os->ctx;
    if (oc->oformat->check_bitstream) {
        int ret;
        AVPacket pkt = *avpkt;
        pkt.stream_index = 0;
        ret = oc->oformat->check_bitstream(oc, &pkt);
        if (ret == 1) {
            AVStream *st = s->streams[avpkt->stream_index];
            AVStream *ost = oc->streams[0];
            st->internal->bsfcs = ost->internal->bsfcs;
            st->internal->nb_bsfcs = ost->internal->nb_bsfcs;
            ost->internal->bsfcs = NULL;
            ost->internal->nb_bsfcs = 0;
        }
        return ret;
    }
    return 1;
}

1857 1858 1859
#define OFFSET(x) offsetof(DASHContext, x)
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
1860
    { "adaptation_sets", "Adaptation sets. Syntax: id=0,streams=0,1,2 id=1,streams=3,4 and so on", OFFSET(adaptation_sets), AV_OPT_TYPE_STRING, { 0 }, 0, 0, AV_OPT_FLAG_ENCODING_PARAM },
1861 1862
    { "window_size", "number of segments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E },
    { "extra_window_size", "number of segments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E },
1863 1864 1865 1866
#if FF_API_DASH_MIN_SEG_DURATION
    { "min_seg_duration", "minimum segment duration (in microseconds) (will be deprecated)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT, { .i64 = 5000000 }, 0, INT_MAX, E },
#endif
    { "seg_duration", "segment duration (in seconds, fractional value can be set)", OFFSET(seg_duration), AV_OPT_TYPE_DURATION, { .i64 = 5000000 }, 0, INT_MAX, E },
1867 1868 1869 1870
    { "remove_at_exit", "remove all segments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
    { "use_template", "Use SegmentTemplate instead of SegmentList", OFFSET(use_template), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E },
    { "use_timeline", "Use SegmentTimeline in SegmentTemplate", OFFSET(use_timeline), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, E },
    { "single_file", "Store all segments in one file, accessed using byte ranges", OFFSET(single_file), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1871
    { "single_file_name", "DASH-templated name to be used for baseURL. Implies storing all segments in one file, accessed using byte ranges", OFFSET(single_file_name), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
1872 1873
    { "init_seg_name", "DASH-templated name to used for the initialization segment", OFFSET(init_seg_name), AV_OPT_TYPE_STRING, {.str = "init-stream$RepresentationID$.$ext$"}, 0, 0, E },
    { "media_seg_name", "DASH-templated name to used for the media segments", OFFSET(media_seg_name), AV_OPT_TYPE_STRING, {.str = "chunk-stream$RepresentationID$-$Number%05d$.$ext$"}, 0, 0, E },
1874
    { "utc_timing_url", "URL of the page that will return the UTC timestamp in ISO format", OFFSET(utc_timing_url), AV_OPT_TYPE_STRING, { 0 }, 0, 0, E },
1875
    { "method", "set the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
1876
    { "http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
1877
    { "http_persistent", "Use persistent HTTP connections", OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },
1878
    { "hls_playlist", "Generate HLS playlist files(master.m3u8, media_%d.m3u8)", OFFSET(hls_playlist), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1879
    { "streaming", "Enable/Disable streaming mode of output. Each frame will be moof fragment", OFFSET(streaming), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1880
    { "timeout", "set timeout for socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_DURATION, { .i64 = -1 }, -1, INT_MAX, .flags = E },
1881
    { "index_correction", "Enable/Disable segment index correction logic", OFFSET(index_correction), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1882
    { "format_options","set list of options for the container format (mp4/webm) used for dash", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0, E},
1883
    { "global_sidx", "Write global SIDX atom. Applicable only for single file, mp4 output, non-streaming mode", OFFSET(global_sidx), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1884 1885
    { "dash_segment_type", "set dash segment files type", OFFSET(segment_type_option), AV_OPT_TYPE_INT, {.i64 = SEGMENT_TYPE_AUTO }, 0, SEGMENT_TYPE_NB - 1, E, "segment_type"},
    { "auto", "select segment file format based on codec", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_AUTO }, 0, UINT_MAX,   E, "segment_type"},
1886 1887
    { "mp4", "make segment file in ISOBMFF format", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_MP4 }, 0, UINT_MAX,   E, "segment_type"},
    { "webm", "make segment file in WebM format", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_WEBM }, 0, UINT_MAX,   E, "segment_type"},
1888
    { "ignore_io_errors", "Ignore IO errors during open and write. Useful for long-duration runs with network output", OFFSET(ignore_io_errors), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1889
    { "lhls", "Enable Low-latency HLS(Experimental). Adds #EXT-X-PREFETCH tag with current segment's URI", OFFSET(lhls), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1890
    { "master_m3u8_publish_rate", "Publish master playlist every after this many segment intervals", OFFSET(master_publish_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, UINT_MAX, E},
1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903
    { NULL },
};

static const AVClass dash_class = {
    .class_name = "dash muxer",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

AVOutputFormat ff_dash_muxer = {
    .name           = "dash",
    .long_name      = NULL_IF_CONFIG_SMALL("DASH Muxer"),
1904
    .extensions     = "mpd",
1905 1906 1907 1908
    .priv_data_size = sizeof(DASHContext),
    .audio_codec    = AV_CODEC_ID_AAC,
    .video_codec    = AV_CODEC_ID_H264,
    .flags          = AVFMT_GLOBALHEADER | AVFMT_NOFILE | AVFMT_TS_NEGATIVE,
1909
    .init           = dash_init,
1910 1911 1912
    .write_header   = dash_write_header,
    .write_packet   = dash_write_packet,
    .write_trailer  = dash_write_trailer,
1913
    .deinit         = dash_free,
1914
    .check_bitstream = dash_check_bitstream,
1915 1916
    .priv_class     = &dash_class,
};