X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fdashenc.c;h=0ceaa5fdfd4f1993cedbb65cb98686d3d212d579;hb=ef6a9e5e311f09fa8032974fa4d0c1e166a959bb;hp=1b74bce060fc6c5f0cb64dc85030e6f8d4e8fc24;hpb=ecb5546e97c479378e057f527cc1d79a46c31cb1;p=ffmpeg diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 1b74bce060f..0ceaa5fdfd4 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -31,10 +31,12 @@ #include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" #include "libavutil/opt.h" +#include "libavutil/parseutils.h" #include "libavutil/rational.h" #include "libavutil/time.h" #include "libavutil/time_internal.h" +#include "av1.h" #include "avc.h" #include "avformat.h" #include "avio_internal.h" @@ -56,6 +58,17 @@ typedef enum { SEGMENT_TYPE_NB } SegmentType; +enum { + FRAG_TYPE_NONE = 0, + FRAG_TYPE_EVERY_FRAME, + FRAG_TYPE_DURATION, + FRAG_TYPE_PFRAMES, + FRAG_TYPE_NB +}; + +#define MPD_PROFILE_DASH 1 +#define MPD_PROFILE_DVB 2 + typedef struct Segment { char file[1024]; int64_t start_pos; @@ -67,28 +80,45 @@ typedef struct Segment { } Segment; typedef struct AdaptationSet { - char id[10]; + int id; + char *descriptor; + int64_t seg_duration; + int64_t frag_duration; + int frag_type; enum AVMediaType media_type; AVDictionary *metadata; AVRational min_frame_rate, max_frame_rate; int ambiguous_frame_rate; + int64_t max_frag_duration; + int max_width, max_height; + int nb_streams; + AVRational par; + int trick_idx; } AdaptationSet; typedef struct OutputStream { AVFormatContext *ctx; int ctx_inited, as_idx; AVIOContext *out; + AVCodecParserContext *parser; + AVCodecContext *parser_avctx; int packets_written; char initfile[1024]; int64_t init_start_pos, pos; int init_range_length; int nb_segments, segments_size, segment_index; + int64_t seg_duration; + int64_t frag_duration; + int64_t last_duration; Segment **segments; int64_t first_pts, start_pts, max_pts; int64_t last_dts, last_pts; + int last_flags; int bit_rate; + int first_segment_bit_rate; SegmentType segment_type; /* segment type selected for this particular stream */ const char *format_name; + const char *extension_name; const char *single_file_name; /* file names selected for this particular stream */ const char *init_seg_name; const char *media_seg_name; @@ -99,8 +129,15 @@ typedef struct OutputStream { char full_path[1024]; char temp_path[1024]; double availability_time_offset; + AVProducerReferenceTime producer_reference_time; + char producer_reference_time_str[100]; int total_pkt_size; + int64_t total_pkt_duration; int muxer_overhead; + int frag_type; + int64_t gop_size; + AVRational sar; + int coding_dependency; } OutputStream; typedef struct DASHContext { @@ -110,10 +147,8 @@ typedef struct DASHContext { int nb_as; int window_size; int extra_window_size; -#if FF_API_DASH_MIN_SEG_DURATION - int min_seg_duration; -#endif int64_t seg_duration; + int64_t frag_duration; int remove_at_exit; int use_template; int use_timeline; @@ -124,6 +159,7 @@ typedef struct DASHContext { int64_t total_duration; char availability_start_time[100]; time_t start_time_s; + int64_t presentation_time_offset; char dirname[1024]; const char *single_file_name; /* file names as specified in options */ const char *init_seg_name; @@ -131,7 +167,9 @@ typedef struct DASHContext { const char *utc_timing_url; const char *method; const char *user_agent; + AVDictionary *http_opts; int hls_playlist; + const char *hls_master_name; int http_persistent; int master_playlist_created; AVIOContext *mpd_out; @@ -139,11 +177,25 @@ typedef struct DASHContext { int streaming; int64_t timeout; int index_correction; - char *format_options_str; + AVDictionary *format_options; + int global_sidx; SegmentType segment_type_option; /* segment type as specified in options */ int ignore_io_errors; int lhls; + int ldash; int master_publish_rate; + int nr_of_streams_to_flush; + int nr_of_streams_flushed; + int frag_type; + int write_prft; + int64_t max_gop_size; + int64_t max_segment_duration; + int profile; + int64_t target_latency; + int target_latency_refid; + AVRational min_playback_rate; + AVRational max_playback_rate; + int64_t update_period; } DASHContext; static struct codec_string { @@ -214,6 +266,16 @@ static const char *get_format_str(SegmentType segment_type) { return NULL; } +static const char *get_extension_str(SegmentType type, int single_file) +{ + switch (type) { + + case SEGMENT_TYPE_MP4: return single_file ? "mp4" : "m4s"; + case SEGMENT_TYPE_WEBM: return "webm"; + default: return NULL; + } +} + static int handle_io_open_error(AVFormatContext *s, int err, char *url) { DASHContext *c = s->priv_data; char errbuf[AV_ERROR_MAX_STRING_SIZE]; @@ -251,6 +313,12 @@ static int init_segment_types(AVFormatContext *s) av_log(s, AV_LOG_ERROR, "Could not select DASH segment type for stream %d\n", i); return AVERROR_MUXER_NOT_FOUND; } + os->extension_name = get_extension_str(segment_type, c->single_file); + if (!os->extension_name) { + av_log(s, AV_LOG_ERROR, "Could not get extension type for stream %d\n", i); + return AVERROR_MUXER_NOT_FOUND; + } + has_mp4_streams |= segment_type == SEGMENT_TYPE_MP4; } @@ -365,10 +433,25 @@ static void set_codec_str(AVFormatContext *s, AVCodecParameters *par, av_strlcatf(str, size, ".%02x%02x%02x", extradata[1], extradata[2], extradata[3]); av_free(tmpbuf); + } else if (!strcmp(str, "av01")) { + AV1SequenceParameters seq; + if (!par->extradata_size) + return; + if (ff_av1_parse_seq_header(&seq, par->extradata, par->extradata_size) < 0) + return; + + av_strlcatf(str, size, ".%01u.%02u%s.%02u", + seq.profile, seq.level, seq.tier ? "H" : "M", seq.bitdepth); + if (seq.color_description_present_flag) + av_strlcatf(str, size, ".%01u.%01u%01u%01u.%02u.%02u.%02u.%01u", + seq.monochrome, + seq.chroma_subsampling_x, seq.chroma_subsampling_y, seq.chroma_sample_position, + seq.color_primaries, seq.transfer_characteristics, seq.matrix_coefficients, + seq.color_range); } } -static int flush_dynbuf(OutputStream *os, int *range_length) +static int flush_dynbuf(DASHContext *c, OutputStream *os, int *range_length) { uint8_t *buffer; @@ -380,22 +463,28 @@ static int flush_dynbuf(OutputStream *os, int *range_length) av_write_frame(os->ctx, NULL); avio_flush(os->ctx->pb); - // 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); + 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; + } } static void set_http_options(AVDictionary **options, DASHContext *c) { if (c->method) av_dict_set(options, "method", c->method, 0); + av_dict_copy(options, c->http_opts, 0); if (c->user_agent) av_dict_set(options, "user_agent", c->user_agent, 0); if (c->http_persistent) @@ -463,7 +552,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, } ff_hls_write_playlist_header(c->m3u8_out, 6, -1, target_duration, - start_number, PLAYLIST_TYPE_NONE); + start_number, PLAYLIST_TYPE_NONE, 0); ff_hls_write_init_file(c->m3u8_out, os->initfile, c->single_file, os->init_range_length, os->init_start_pos); @@ -483,7 +572,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, (double) seg->duration / timescale, 0, seg->range_length, seg->start_pos, NULL, c->single_file ? os->initfile : seg->file, - &prog_date_time); + &prog_date_time, 0, 0, 0); if (ret < 0) { av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n"); } @@ -498,9 +587,7 @@ static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, 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); - } + ff_rename(temp_filename_hls, filename_hls, os->ctx); } static int flush_init_segment(AVFormatContext *s, OutputStream *os) @@ -508,7 +595,7 @@ static int flush_init_segment(AVFormatContext *s, OutputStream *os) DASHContext *c = s->priv_data; int ret, range_length; - ret = flush_dynbuf(os, &range_length); + ret = flush_dynbuf(c, os, &range_length); if (ret < 0) return ret; @@ -527,8 +614,10 @@ static void dash_free(AVFormatContext *s) int i, j; if (c->as) { - for (i = 0; i < c->nb_as; i++) + for (i = 0; i < c->nb_as; i++) { av_dict_free(&c->as[i].metadata); + av_freep(&c->as[i].descriptor); + } av_freep(&c->as); c->nb_as = 0; } @@ -537,11 +626,16 @@ static void dash_free(AVFormatContext *s) return; for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; - if (os->ctx && os->ctx->pb) - ffio_free_dyn_buf(&os->ctx->pb); + if (os->ctx && os->ctx->pb) { + if (!c->single_file) + ffio_free_dyn_buf(&os->ctx->pb); + else + avio_close(os->ctx->pb); + } ff_format_io_close(s, &os->out); - if (os->ctx) - avformat_free_context(os->ctx); + avformat_free_context(os->ctx); + avcodec_free_context(&os->parser_avctx); + av_parser_close(os->parser); for (j = 0; j < os->nb_segments; j++) av_free(os->segments[j]); av_free(os->segments); @@ -566,12 +660,18 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatCont int timescale = c->use_timeline ? os->ctx->streams[0]->time_base.den : AV_TIME_BASE; avio_printf(out, "\t\t\t\tuse_timeline) { - avio_printf(out, "duration=\"%"PRId64"\" ", c->seg_duration); + avio_printf(out, "duration=\"%"PRId64"\" ", os->seg_duration); if (c->streaming && os->availability_time_offset) avio_printf(out, "availabilityTimeOffset=\"%.3f\" ", os->availability_time_offset); } - avio_printf(out, "initialization=\"%s\" media=\"%s\" startNumber=\"%d\">\n", os->init_seg_name, os->media_seg_name, c->use_timeline ? start_number : 1); + if (c->streaming && os->availability_time_offset && !final) + avio_printf(out, "availabilityTimeComplete=\"false\" "); + + avio_printf(out, "initialization=\"%s\" media=\"%s\" startNumber=\"%d\"", os->init_seg_name, os->media_seg_name, c->use_timeline ? start_number : 1); + if (c->presentation_time_offset) + avio_printf(out, " presentationTimeOffset=\"%"PRId64"\"", c->presentation_time_offset); + avio_printf(out, ">\n"); if (c->use_timeline) { int64_t cur_time = 0; avio_printf(out, "\t\t\t\t\t\n"); @@ -599,7 +699,7 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatCont avio_printf(out, "\t\t\t\t\n"); } else if (c->single_file) { avio_printf(out, "\t\t\t\t%s\n", os->initfile); - avio_printf(out, "\t\t\t\t\n", AV_TIME_BASE, c->last_duration, start_number); + avio_printf(out, "\t\t\t\t\n", AV_TIME_BASE, FFMIN(os->seg_duration, os->last_duration), start_number); avio_printf(out, "\t\t\t\t\t\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]; @@ -610,7 +710,7 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatCont } avio_printf(out, "\t\t\t\t\n"); } else { - avio_printf(out, "\t\t\t\t\n", AV_TIME_BASE, c->last_duration, start_number); + avio_printf(out, "\t\t\t\t\n", AV_TIME_BASE, FFMIN(os->seg_duration, os->last_duration), start_number); avio_printf(out, "\t\t\t\t\t\n", os->initfile); for (i = start_index; i < os->nb_segments; i++) { Segment *seg = os->segments[i]; @@ -680,10 +780,9 @@ static void write_time(AVIOContext *out, int64_t time) avio_printf(out, "%d.%dS", seconds, fractions / (AV_TIME_BASE / 10)); } -static void format_date_now(char *buf, int size) +static void format_date(char *buf, int size, int64_t time_us) { struct tm *ptm, tmbuf; - 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); @@ -707,20 +806,32 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind AVDictionaryEntry *lang, *role; int i; - avio_printf(out, "\t\tid, as->media_type == AVMEDIA_TYPE_VIDEO ? "video" : "audio"); 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); + else 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)) + avio_printf(out, " frameRate=\"%d/%d\"", as->max_frame_rate.num, as->max_frame_rate.den); + if (as->media_type == AVMEDIA_TYPE_VIDEO) { + avio_printf(out, " maxWidth=\"%d\" maxHeight=\"%d\"", as->max_width, as->max_height); + avio_printf(out, " par=\"%d:%d\"", as->par.num, as->par.den); + } lang = av_dict_get(as->metadata, "language", NULL, 0); if (lang) avio_printf(out, " lang=\"%s\"", lang->value); avio_printf(out, ">\n"); + if (!final && c->ldash && as->max_frag_duration && !(c->profile & MPD_PROFILE_DVB)) + avio_printf(out, "\t\t\t\n", as->max_frag_duration); + if (as->trick_idx >= 0) + avio_printf(out, "\t\t\t\n", as->id, as->trick_idx); role = av_dict_get(as->metadata, "role", NULL, 0); if (role) avio_printf(out, "\t\t\t\n", role->value); - + if (as->descriptor) + avio_printf(out, "\t\t\t%s\n", as->descriptor); for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; OutputStream *os = &c->streams[i]; char bandwidth_str[64] = {'\0'}; @@ -728,15 +839,30 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind continue; if (os->bit_rate > 0) - snprintf(bandwidth_str, sizeof(bandwidth_str), " bandwidth=\"%d\"", - os->bit_rate); + snprintf(bandwidth_str, sizeof(bandwidth_str), " bandwidth=\"%d\"", os->bit_rate); + else if (final) { + int average_bit_rate = os->pos * 8 * AV_TIME_BASE / c->total_duration; + snprintf(bandwidth_str, sizeof(bandwidth_str), " bandwidth=\"%d\"", average_bit_rate); + } else if (os->first_segment_bit_rate > 0) + snprintf(bandwidth_str, sizeof(bandwidth_str), " bandwidth=\"%d\"", os->first_segment_bit_rate); if (as->media_type == AVMEDIA_TYPE_VIDEO) { - AVStream *st = s->streams[i]; avio_printf(out, "\t\t\tformat_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height); - if (st->avg_frame_rate.num) + if (st->codecpar->field_order == AV_FIELD_UNKNOWN) + avio_printf(out, " scanType=\"unknown\""); + else if (st->codecpar->field_order != AV_FIELD_PROGRESSIVE) + avio_printf(out, " scanType=\"interlaced\""); + avio_printf(out, " sar=\"%d:%d\"", os->sar.num, os->sar.den); + if (st->avg_frame_rate.num && av_cmp_q(as->min_frame_rate, as->max_frame_rate) < 0) avio_printf(out, " frameRate=\"%d/%d\"", st->avg_frame_rate.num, st->avg_frame_rate.den); + if (as->trick_idx >= 0) { + AdaptationSet *tas = &c->as[as->trick_idx]; + if (!as->ambiguous_frame_rate && !tas->ambiguous_frame_rate) + avio_printf(out, " maxPlayoutRate=\"%d\"", FFMAX((int)av_q2d(av_div_q(tas->min_frame_rate, as->min_frame_rate)), 1)); + } + if (!os->coding_dependency) + avio_printf(out, " codingDependency=\"false\""); avio_printf(out, ">\n"); } else { avio_printf(out, "\t\t\t\n", @@ -744,6 +870,15 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind avio_printf(out, "\t\t\t\t\n", s->streams[i]->codecpar->channels); } + if (!final && c->write_prft && os->producer_reference_time_str[0]) { + avio_printf(out, "\t\t\t\t\n", + i, os->producer_reference_time.flags ? "captured" : "encoder", os->producer_reference_time_str, c->presentation_time_offset); + avio_printf(out, "\t\t\t\t\t\n", c->utc_timing_url); + avio_printf(out, "\t\t\t\t\n"); + } + if (!final && c->ldash && os->gop_size && os->frag_type != FRAG_TYPE_NONE && !(c->profile & MPD_PROFILE_DVB) && + (os->frag_type != FRAG_TYPE_DURATION || os->frag_duration != os->seg_duration)) + avio_printf(out, "\t\t\t\t\n", os->gop_size); output_segment_list(os, out, s, i, final); avio_printf(out, "\t\t\t\n"); } @@ -755,8 +890,13 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind static int add_adaptation_set(AVFormatContext *s, AdaptationSet **as, enum AVMediaType type) { DASHContext *c = s->priv_data; + void *mem; - void *mem = av_realloc(c->as, sizeof(*c->as) * (c->nb_as + 1)); + if (c->profile & MPD_PROFILE_DVB && (c->nb_as + 1) > 16) { + av_log(s, AV_LOG_ERROR, "DVB-DASH profile allows a max of 16 Adaptation Sets\n"); + return AVERROR(EINVAL); + } + mem = av_realloc(c->as, sizeof(*c->as) * (c->nb_as + 1)); if (!mem) return AVERROR(ENOMEM); c->as = mem; @@ -765,6 +905,8 @@ static int add_adaptation_set(AVFormatContext *s, AdaptationSet **as, enum AVMed *as = &c->as[c->nb_as - 1]; memset(*as, 0, sizeof(**as)); (*as)->media_type = type; + (*as)->frag_type = -1; + (*as)->trick_idx = -1; return 0; } @@ -782,7 +924,12 @@ static int adaptation_set_add_stream(AVFormatContext *s, int as_idx, int i) av_log(s, AV_LOG_ERROR, "Stream %d is already assigned to an AdaptationSet\n", i); return AVERROR(EINVAL); } + if (c->profile & MPD_PROFILE_DVB && (as->nb_streams + 1) > 16) { + av_log(s, AV_LOG_ERROR, "DVB-DASH profile allows a max of 16 Representations per Adaptation Set\n"); + return AVERROR(EINVAL); + } os->as_idx = as_idx; + ++as->nb_streams; return 0; } @@ -791,7 +938,7 @@ 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; + enum { new_set, parse_default, parsing_streams, parse_seg_duration, parse_frag_duration } state; AdaptationSet *as; int i, n, ret; @@ -800,32 +947,121 @@ static int parse_adaptation_sets(AVFormatContext *s) 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); + as->id = i; c->streams[i].as_idx = c->nb_as; + ++as->nb_streams; } goto end; } // syntax id=0,streams=0,1,2 id=1,streams=3,4 and so on + // option id=0,descriptor=descriptor_str,streams=0,1,2 and so on + // option id=0,seg_duration=2.5,frag_duration=0.5,streams=0,1,2 + // id=1,trick_id=0,seg_duration=10,frag_type=none,streams=3 and so on + // descriptor is useful to the scheme defined by ISO/IEC 23009-1:2014/Amd.2:2015 + // descriptor_str should be a self-closing xml tag. + // seg_duration and frag_duration have the same syntax as the global options of + // the same name, and the former have precedence over them if set. state = new_set; while (*p) { if (*p == ' ') { p++; continue; } else if (state == new_set && av_strstart(p, "id=", &p)) { + char id_str[10], *end_str; + + n = strcspn(p, ","); + snprintf(id_str, sizeof(id_str), "%.*s", n, p); + + i = strtol(id_str, &end_str, 10); + if (id_str == end_str || i < 0 || i > c->nb_as) { + av_log(s, AV_LOG_ERROR, "\"%s\" is not a valid value for an AdaptationSet id\n", id_str); + return AVERROR(EINVAL); + } if ((ret = add_adaptation_set(s, &as, AVMEDIA_TYPE_UNKNOWN)) < 0) return ret; + as->id = i; + + p += n; + if (*p) + p++; + state = parse_default; + } else if (state != new_set && av_strstart(p, "seg_duration=", &p)) { + state = parse_seg_duration; + } else if (state != new_set && av_strstart(p, "frag_duration=", &p)) { + state = parse_frag_duration; + } else if (state == parse_seg_duration || state == parse_frag_duration) { + char str[32]; + int64_t usecs = 0; + + n = strcspn(p, ","); + snprintf(str, sizeof(str), "%.*s", n, p); + p += n; + if (*p) + p++; + + ret = av_parse_time(&usecs, str, 1); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Unable to parse option value \"%s\" as duration\n", str); + return ret; + } + + if (state == parse_seg_duration) + as->seg_duration = usecs; + else + as->frag_duration = usecs; + state = parse_default; + } else if (state != new_set && av_strstart(p, "frag_type=", &p)) { + char type_str[16]; n = strcspn(p, ","); - snprintf(as->id, sizeof(as->id), "%.*s", n, p); + snprintf(type_str, sizeof(type_str), "%.*s", n, p); + p += n; + if (*p) + p++; + if (!strcmp(type_str, "duration")) + as->frag_type = FRAG_TYPE_DURATION; + else if (!strcmp(type_str, "pframes")) + as->frag_type = FRAG_TYPE_PFRAMES; + else if (!strcmp(type_str, "every_frame")) + as->frag_type = FRAG_TYPE_EVERY_FRAME; + else if (!strcmp(type_str, "none")) + as->frag_type = FRAG_TYPE_NONE; + else { + av_log(s, AV_LOG_ERROR, "Unable to parse option value \"%s\" as fragment type\n", type_str); + return ret; + } + state = parse_default; + } else if (state != new_set && av_strstart(p, "descriptor=", &p)) { + n = strcspn(p, ">") + 1; //followed by one comma, so plus 1 + if (n < strlen(p)) { + as->descriptor = av_strndup(p, n); + } else { + av_log(s, AV_LOG_ERROR, "Parse error, descriptor string should be a self-closing xml tag\n"); + return AVERROR(EINVAL); + } p += n; if (*p) p++; - state = parse_id; - } else if (state == parse_id && av_strstart(p, "streams=", &p)) { + state = parse_default; + } else if ((state != new_set) && av_strstart(p, "trick_id=", &p)) { + char trick_id_str[10], *end_str; + + n = strcspn(p, ","); + snprintf(trick_id_str, sizeof(trick_id_str), "%.*s", n, p); + p += n; + + as->trick_idx = strtol(trick_id_str, &end_str, 10); + if (trick_id_str == end_str || as->trick_idx < 0) + return AVERROR(EINVAL); + + if (*p) + p++; + state = parse_default; + } else if ((state != new_set) && av_strstart(p, "streams=", &p)) { //descriptor and durations are optional state = parsing_streams; } else if (state == parsing_streams) { AdaptationSet *as = &c->as[c->nb_as - 1]; @@ -883,6 +1119,22 @@ end: return AVERROR(EINVAL); } } + + // check references for trick mode AdaptationSet + for (i = 0; i < c->nb_as; i++) { + as = &c->as[i]; + if (as->trick_idx < 0) + continue; + for (n = 0; n < c->nb_as; n++) { + if (c->as[n].id == as->trick_idx) + break; + } + if (n >= c->nb_as) { + av_log(s, AV_LOG_ERROR, "reference AdaptationSet id \"%d\" not found for trick mode AdaptationSet id \"%d\"\n", as->trick_idx, as->id); + return AVERROR(EINVAL); + } + } + return 0; } @@ -914,8 +1166,13 @@ static int write_manifest(AVFormatContext *s, int final) "\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"); + "\tprofiles=\""); + if (c->profile & MPD_PROFILE_DASH) + avio_printf(out, "%s%s", "urn:mpeg:dash:profile:isoff-live:2011", c->profile & MPD_PROFILE_DVB ? "," : "\"\n"); + if (c->profile & MPD_PROFILE_DVB) + avio_printf(out, "%s", "urn:dvb:dash:profile:dvb-dash:2014\"\n"); + avio_printf(out, "\ttype=\"%s\"\n", + final ? "static" : "dynamic"); if (final) { avio_printf(out, "\tmediaPresentationDuration=\""); write_time(out, c->total_duration); @@ -925,11 +1182,14 @@ static int write_manifest(AVFormatContext *s, int final) char now_str[100]; if (c->use_template && !c->use_timeline) update_period = 500; + if (c->update_period) + update_period = c->update_period; avio_printf(out, "\tminimumUpdatePeriod=\"PT%"PRId64"S\"\n", update_period); - avio_printf(out, "\tsuggestedPresentationDelay=\"PT%"PRId64"S\"\n", c->last_duration / AV_TIME_BASE); + if (!c->ldash) + avio_printf(out, "\tsuggestedPresentationDelay=\"PT%"PRId64"S\"\n", c->last_duration / AV_TIME_BASE); if (c->availability_start_time[0]) avio_printf(out, "\tavailabilityStartTime=\"%s\"\n", c->availability_start_time); - format_date_now(now_str, sizeof(now_str)); + format_date(now_str, sizeof(now_str), av_gettime()); if (now_str[0]) avio_printf(out, "\tpublishTime=\"%s\"\n", now_str); if (c->window_size && c->use_template) { @@ -938,8 +1198,11 @@ static int write_manifest(AVFormatContext *s, int final) avio_printf(out, "\"\n"); } } + avio_printf(out, "\tmaxSegmentDuration=\""); + write_time(out, c->max_segment_duration); + avio_printf(out, "\"\n"); avio_printf(out, "\tminBufferTime=\""); - write_time(out, c->last_duration * 2); + write_time(out, c->ldash && c->max_gop_size ? c->max_gop_size : c->last_duration * 2); avio_printf(out, "\">\n"); avio_printf(out, "\t\n"); if (title) { @@ -949,6 +1212,19 @@ static int write_manifest(AVFormatContext *s, int final) } avio_printf(out, "\t\n"); + avio_printf(out, "\t\n"); + if (!final && c->target_latency && c->target_latency_refid >= 0) { + avio_printf(out, "\t\ttarget_latency / 1000); + if (s->nb_streams > 1) + avio_printf(out, " referenceId=\"%d\"", c->target_latency_refid); + avio_printf(out, "/>\n"); + } + if (av_cmp_q(c->min_playback_rate, (AVRational) {1, 1}) || + av_cmp_q(c->max_playback_rate, (AVRational) {1, 1})) + avio_printf(out, "\t\t\n", + av_q2d(c->min_playback_rate), av_q2d(c->max_playback_rate)); + avio_printf(out, "\t\n"); + 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); @@ -974,7 +1250,7 @@ static int write_manifest(AVFormatContext *s, int final) dashenc_io_close(s, &c->mpd_out, temp_filename); if (use_rename) { - if ((ret = avpriv_io_move(temp_filename, s->url)) < 0) + if ((ret = ff_rename(temp_filename, s->url, s)) < 0) return ret; } @@ -991,9 +1267,9 @@ static int write_manifest(AVFormatContext *s, int final) return 0; if (*c->dirname) - snprintf(filename_hls, sizeof(filename_hls), "%smaster.m3u8", c->dirname); + snprintf(filename_hls, sizeof(filename_hls), "%s%s", c->dirname, c->hls_master_name); else - snprintf(filename_hls, sizeof(filename_hls), "master.m3u8"); + snprintf(filename_hls, sizeof(filename_hls), "%s", c->hls_master_name); snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", filename_hls); @@ -1034,7 +1310,13 @@ static int write_manifest(AVFormatContext *s, int final) OutputStream *os = &c->streams[i]; char *agroup = NULL; char *codec_str_ptr = NULL; - int stream_bitrate = st->codecpar->bit_rate + os->muxer_overhead; + int stream_bitrate = os->muxer_overhead; + if (os->bit_rate > 0) + stream_bitrate += os->bit_rate; + else if (final) + stream_bitrate += os->pos * 8 * AV_TIME_BASE / c->total_duration; + else if (os->first_segment_bit_rate > 0) + stream_bitrate += os->first_segment_bit_rate; if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) continue; if (os->segment_type != SEGMENT_TYPE_MP4) @@ -1052,11 +1334,11 @@ static int write_manifest(AVFormatContext *s, int final) get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i); ff_hls_write_stream_info(st, c->m3u8_out, stream_bitrate, playlist_file, agroup, - codec_str_ptr, NULL); + codec_str_ptr, NULL, NULL); } dashenc_io_close(s, &c->m3u8_out, temp_filename); if (use_rename) - if ((ret = avpriv_io_move(temp_filename, filename_hls)) < 0) + if ((ret = ff_rename(temp_filename, filename_hls, s)) < 0) return ret; c->master_playlist_created = 1; } @@ -1079,17 +1361,16 @@ static int dash_init(AVFormatContext *s) char *ptr; char basename[1024]; + c->nr_of_streams_to_flush = 0; if (c->single_file_name) c->single_file = 1; if (c->single_file) c->use_template = 0; -#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; + if (!c->profile) { + av_log(s, AV_LOG_ERROR, "At least one profile must be enabled.\n"); + return AVERROR(EINVAL); } -#endif 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"); @@ -1106,6 +1387,60 @@ static int dash_init(AVFormatContext *s) c->lhls = 0; } + if (c->ldash && !c->streaming) { + av_log(s, AV_LOG_WARNING, "LDash option will be ignored as streaming is not enabled\n"); + c->ldash = 0; + } + + if (c->target_latency && !c->streaming) { + av_log(s, AV_LOG_WARNING, "Target latency option will be ignored as streaming is not enabled\n"); + c->target_latency = 0; + } + + 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; + } + if (c->frag_type == FRAG_TYPE_NONE && c->streaming) { + av_log(s, AV_LOG_VERBOSE, "Changing frag_type from none to every_frame as streaming is enabled\n"); + c->frag_type = FRAG_TYPE_EVERY_FRAME; + } + + if (c->write_prft < 0) { + c->write_prft = c->ldash; + if (c->ldash) + av_log(s, AV_LOG_VERBOSE, "Enabling Producer Reference Time element for Low Latency mode\n"); + } + + if (c->write_prft && !c->utc_timing_url) { + av_log(s, AV_LOG_WARNING, "Producer Reference Time element option will be ignored as utc_timing_url is not set\n"); + c->write_prft = 0; + } + + if (c->write_prft && !c->streaming) { + av_log(s, AV_LOG_WARNING, "Producer Reference Time element option will be ignored as streaming is not enabled\n"); + c->write_prft = 0; + } + + if (c->ldash && !c->write_prft) { + av_log(s, AV_LOG_WARNING, "Low Latency mode enabled without Producer Reference Time element option! Resulting manifest may not be complaint\n"); + } + + if (c->target_latency && !c->write_prft) { + av_log(s, AV_LOG_WARNING, "Target latency option will be ignored as Producer Reference Time element will not be written\n"); + c->target_latency = 0; + } + + if (av_cmp_q(c->max_playback_rate, c->min_playback_rate) < 0) { + av_log(s, AV_LOG_WARNING, "Minimum playback rate value is higer than the Maximum. Both will be ignored\n"); + c->min_playback_rate = c->max_playback_rate = (AVRational) {1, 1}; + } + av_strlcpy(c->dirname, s->url, sizeof(c->dirname)); ptr = strrchr(c->dirname, '/'); if (ptr) { @@ -1151,22 +1486,18 @@ static int dash_init(AVFormatContext *s) dict_copy_entry(&as->metadata, s->streams[i]->metadata, "language"); dict_copy_entry(&as->metadata, s->streams[i]->metadata, "role"); - ctx = avformat_alloc_context(); - if (!ctx) - return AVERROR(ENOMEM); - if (c->init_seg_name) { - os->init_seg_name = av_strireplace(c->init_seg_name, "$ext$", os->format_name); + os->init_seg_name = av_strireplace(c->init_seg_name, "$ext$", os->extension_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); + os->media_seg_name = av_strireplace(c->media_seg_name, "$ext$", os->extension_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); + os->single_file_name = av_strireplace(c->single_file_name, "$ext$", os->extension_name); if (!os->single_file_name) return AVERROR(ENOMEM); } @@ -1180,12 +1511,20 @@ static int dash_init(AVFormatContext *s) "Override -init_seg_name and/or -media_seg_name and/or " "-single_file_name to end with the extension .webm\n"); } + 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; + } } + os->ctx = ctx = avformat_alloc_context(); + if (!ctx) + return AVERROR(ENOMEM); + ctx->oformat = av_guess_format(os->format_name, NULL, NULL); if (!ctx->oformat) return AVERROR_MUXER_NOT_FOUND; - os->ctx = ctx; ctx->interrupt_callback = s->interrupt_callback; ctx->opaque = s->opaque; ctx->io_close = s->io_close; @@ -1201,8 +1540,17 @@ static int dash_init(AVFormatContext *s) ctx->avoid_negative_ts = s->avoid_negative_ts; ctx->flags = s->flags; - if ((ret = avio_open_dyn_buf(&ctx->pb)) < 0) - return ret; + os->parser = av_parser_init(st->codecpar->codec_id); + if (os->parser) { + os->parser_avctx = avcodec_alloc_context3(NULL); + if (!os->parser_avctx) + return AVERROR(ENOMEM); + ret = avcodec_parameters_to_context(os->parser_avctx, st->codecpar); + if (ret < 0) + return ret; + // We only want to parse frame headers + os->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; + } if (c->single_file) { if (os->single_file_name) @@ -1214,26 +1562,73 @@ static int dash_init(AVFormatContext *s) } snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile); set_http_options(&opts, c); - ret = s->io_open(s, &os->out, filename, AVIO_FLAG_WRITE, &opts); + 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); + } av_dict_free(&opts); if (ret < 0) return ret; os->init_start_pos = 0; - if (c->format_options_str) { - ret = av_dict_parse_string(&opts, c->format_options_str, "=", ":", 0); - if (ret < 0) - return ret; + av_dict_copy(&opts, c->format_options, 0); + if (!as->seg_duration) + as->seg_duration = c->seg_duration; + if (!as->frag_duration) + as->frag_duration = c->frag_duration; + if (as->frag_type < 0) + as->frag_type = c->frag_type; + os->seg_duration = as->seg_duration; + os->frag_duration = as->frag_duration; + os->frag_type = as->frag_type; + + c->max_segment_duration = FFMAX(c->max_segment_duration, as->seg_duration); + + if (c->profile & MPD_PROFILE_DVB && (os->seg_duration > 15000000 || os->seg_duration < 960000)) { + av_log(s, AV_LOG_ERROR, "Segment duration %"PRId64" is outside the allowed range for DVB-DASH profile\n", os->seg_duration); + return AVERROR(EINVAL); } + if (os->frag_type == FRAG_TYPE_DURATION && !os->frag_duration) { + av_log(s, AV_LOG_WARNING, "frag_type set to duration for stream %d but no frag_duration set\n", i); + os->frag_type = c->streaming ? FRAG_TYPE_EVERY_FRAME : FRAG_TYPE_NONE; + } + if (os->frag_type == FRAG_TYPE_DURATION && os->frag_duration > os->seg_duration) { + av_log(s, AV_LOG_ERROR, "Fragment duration %"PRId64" is longer than Segment duration %"PRId64"\n", os->frag_duration, os->seg_duration); + return AVERROR(EINVAL); + } + if (os->frag_type == FRAG_TYPE_PFRAMES && (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO || !os->parser)) { + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !os->parser) + av_log(s, AV_LOG_WARNING, "frag_type set to P-Frame reordering, but no parser found for stream %d\n", i); + os->frag_type = c->streaming ? FRAG_TYPE_EVERY_FRAME : FRAG_TYPE_NONE; + } + if (os->frag_type != FRAG_TYPE_PFRAMES && as->trick_idx < 0) + // Set this now if a parser isn't used + os->coding_dependency = 1; + if (os->segment_type == SEGMENT_TYPE_MP4) { if (c->streaming) - // frag_every_frame : Allows lower latency streaming // skip_sidx : Reduce bitrate overhead // skip_trailer : Avoids growing memory usage with time - av_dict_set(&opts, "movflags", "frag_every_frame+dash+delay_moov+skip_sidx+skip_trailer", 0); + av_dict_set(&opts, "movflags", "+dash+delay_moov+skip_sidx+skip_trailer", AV_DICT_APPEND); + else { + if (c->global_sidx) + av_dict_set(&opts, "movflags", "+dash+delay_moov+global_sidx+skip_trailer", AV_DICT_APPEND); + else + av_dict_set(&opts, "movflags", "+dash+delay_moov+skip_trailer", AV_DICT_APPEND); + } + if (os->frag_type == FRAG_TYPE_EVERY_FRAME) + av_dict_set(&opts, "movflags", "+frag_every_frame", AV_DICT_APPEND); else - av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + av_dict_set(&opts, "movflags", "+frag_custom", AV_DICT_APPEND); + if (os->frag_type == FRAG_TYPE_DURATION) + av_dict_set_int(&opts, "frag_duration", os->frag_duration, 0); + if (c->write_prft) + av_dict_set(&opts, "write_prft", "wallclock", 0); } else { av_dict_set_int(&opts, "cluster_time_limit", c->seg_duration / 1000, 0); av_dict_set_int(&opts, "cluster_size_limit", 5 * 1024 * 1024, 0); // set a large cluster size limit @@ -1257,6 +1652,7 @@ static int dash_init(AVFormatContext *s) s->avoid_negative_ts = ctx->avoid_negative_ts; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { AVRational avg_frame_rate = s->streams[i]->avg_frame_rate; + AVRational par; if (avg_frame_rate.num > 0) { if (av_cmp_q(avg_frame_rate, as->min_frame_rate) < 0) as->min_frame_rate = avg_frame_rate; @@ -1265,6 +1661,27 @@ static int dash_init(AVFormatContext *s) } else { as->ambiguous_frame_rate = 1; } + + if (st->codecpar->width > as->max_width) + as->max_width = st->codecpar->width; + if (st->codecpar->height > as->max_height) + as->max_height = st->codecpar->height; + + if (st->sample_aspect_ratio.num) + os->sar = st->sample_aspect_ratio; + else + os->sar = (AVRational){1,1}; + av_reduce(&par.num, &par.den, + st->codecpar->width * (int64_t)os->sar.num, + st->codecpar->height * (int64_t)os->sar.den, + 1024 * 1024); + + if (as->par.num && av_cmp_q(par, as->par)) { + av_log(s, AV_LOG_ERROR, "Conflicting stream aspect ratios values in Adaptation Set %d. Please ensure all adaptation sets have the same aspect ratio\n", os->as_idx); + return AVERROR(EINVAL); + } + as->par = par; + c->has_video = 1; } @@ -1274,12 +1691,21 @@ static int dash_init(AVFormatContext *s) os->max_pts = AV_NOPTS_VALUE; os->last_dts = AV_NOPTS_VALUE; os->segment_index = 1; + + if (s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + c->nr_of_streams_to_flush++; } if (!c->has_video && c->seg_duration <= 0) { av_log(s, AV_LOG_WARNING, "no video stream and no seg duration set\n"); return AVERROR(EINVAL); } + if (!c->has_video && c->frag_type == FRAG_TYPE_PFRAMES) + av_log(s, AV_LOG_WARNING, "no video stream and P-frame fragmentation set\n"); + + c->nr_of_streams_flushed = 0; + c->target_latency_refid = -1; + return 0; } @@ -1311,7 +1737,7 @@ static int add_segment(OutputStream *os, const char *file, 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) * + if ((err = av_reallocp_array(&os->segments, sizeof(*os->segments), os->segments_size)) < 0) { os->segments_size = 0; os->nb_segments = 0; @@ -1376,23 +1802,25 @@ static void find_index_range(AVFormatContext *s, const char *full_path, } static int update_stream_extradata(AVFormatContext *s, OutputStream *os, - AVCodecParameters *par, - AVRational *frame_rate) + AVPacket *pkt, AVRational *frame_rate) { + AVCodecParameters *par = os->ctx->streams[0]->codecpar; uint8_t *extradata; + size_t extradata_size; + int ret; - if (os->ctx->streams[0]->codecpar->extradata_size || !par->extradata_size) + if (par->extradata_size) return 0; - extradata = av_malloc(par->extradata_size); - - if (!extradata) - return AVERROR(ENOMEM); + extradata = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &extradata_size); + if (!extradata_size) + return 0; - memcpy(extradata, par->extradata, par->extradata_size); + ret = ff_alloc_extradata(par, extradata_size); + if (ret < 0) + return ret; - os->ctx->streams[0]->codecpar->extradata = extradata; - os->ctx->streams[0]->codecpar->extradata_size = par->extradata_size; + memcpy(par->extradata, extradata, extradata_size); set_codec_str(s, par, frame_rate, os->codec_str, sizeof(os->codec_str)); @@ -1429,28 +1857,20 @@ static void dashenc_delete_file(AVFormatContext *s, char *filename) { 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); - } + AVBPrint buf; - memcpy(filename, c->dirname, dirname_len); + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); - 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); + av_bprintf(&buf, "%s%s", c->dirname, file); + if (!av_bprint_is_complete(&buf)) { + av_bprint_finalize(&buf, NULL); + av_log(s, AV_LOG_WARNING, "Out of memory for filename\n"); + return AVERROR(ENOMEM); } - memcpy(filename + dirname_len, file, file_len + 1); // include the terminating zero - dashenc_delete_file(s, filename); + dashenc_delete_file(s, buf.str); + av_bprint_finalize(&buf, NULL); return 0; } @@ -1487,7 +1907,7 @@ static int dash_flush(AVFormatContext *s, int final, int stream) c->streams[stream].first_pts, s->streams[stream]->time_base, AV_TIME_BASE_Q); - next_exp_index = (pts_diff / c->seg_duration) + 1; + next_exp_index = (pts_diff / c->streams[stream].seg_duration) + 1; } } @@ -1495,6 +1915,7 @@ static int dash_flush(AVFormatContext *s, int final, int stream) OutputStream *os = &c->streams[i]; AVStream *st = s->streams[i]; int range_length, index_length = 0; + int64_t duration; if (!os->packets_written) continue; @@ -1503,6 +1924,9 @@ static int dash_flush(AVFormatContext *s, int final, int stream) // Flush all audio streams as well, in sync with video keyframes, // but not the other video streams. if (stream >= 0 && i != stream) { + if (s->streams[stream]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO && + s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) + continue; if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) continue; // Make sure we don't flush audio streams multiple times, when @@ -1511,14 +1935,10 @@ static int dash_flush(AVFormatContext *s, int final, int stream) continue; } - if (!c->single_file) { - if (os->segment_type == SEGMENT_TYPE_MP4 && !os->written_len) - write_styp(os->ctx->pb); - } else { + if (c->single_file) snprintf(os->full_path, sizeof(os->full_path), "%s%s", c->dirname, os->initfile); - } - ret = flush_dynbuf(os, &range_length); + ret = flush_dynbuf(c, os, &range_length); if (ret < 0) break; os->packets_written = 0; @@ -1529,26 +1949,23 @@ static int dash_flush(AVFormatContext *s, int final, int stream) dashenc_io_close(s, &os->out, os->temp_path); if (use_rename) { - ret = avpriv_io_move(os->temp_path, os->full_path); + ret = ff_rename(os->temp_path, os->full_path, os->ctx); if (ret < 0) break; } } - if (!os->muxer_overhead) + duration = av_rescale_q(os->max_pts - os->start_pts, st->time_base, AV_TIME_BASE_Q); + os->last_duration = FFMAX(os->last_duration, duration); + + if (!os->muxer_overhead && os->max_pts > os->start_pts) 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); + 8 * AV_TIME_BASE) / duration; os->total_pkt_size = 0; + os->total_pkt_duration = 0; - if (!os->bit_rate) { - // calculate average bitrate of first segment - 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); - if (bitrate >= 0) - os->bit_rate = bitrate; + if (!os->bit_rate && !os->first_segment_bit_rate) { + os->first_segment_bit_rate = (int64_t) range_length * 8 * AV_TIME_BASE / duration; } add_segment(os, os->filename, os->start_pts, os->max_pts - os->start_pts, os->pos, range_length, index_length, next_exp_index); av_log(s, AV_LOG_VERBOSE, "Representation %d media segment %d written to: %s\n", i, os->segment_index, os->full_path); @@ -1565,20 +1982,78 @@ static int dash_flush(AVFormatContext *s, int final, int stream) } } - if (ret >= 0) + if (final) { + for (i = 0; i < s->nb_streams; i++) { + OutputStream *os = &c->streams[i]; + if (os->ctx && os->ctx_inited) { + int64_t file_size = avio_tell(os->ctx->pb); + av_write_trailer(os->ctx); + if (c->global_sidx) { + int j, start_index, start_number; + int64_t 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; + } + } + + } + } + } + if (ret >= 0) { + if (c->has_video && !final) { + 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; + } ret = write_manifest(s, final); + } return ret; } +static int dash_parse_prft(DASHContext *c, AVPacket *pkt) +{ + OutputStream *os = &c->streams[pkt->stream_index]; + AVProducerReferenceTime *prft; + size_t side_data_size; + + prft = (AVProducerReferenceTime *)av_packet_get_side_data(pkt, AV_PKT_DATA_PRFT, &side_data_size); + if (!prft || side_data_size != sizeof(AVProducerReferenceTime) || (prft->flags && prft->flags != 24)) { + // No encoder generated or user provided capture time AVProducerReferenceTime side data. Instead + // of letting the mov muxer generate one, do it here so we can also use it for the manifest. + prft = (AVProducerReferenceTime *)av_packet_new_side_data(pkt, AV_PKT_DATA_PRFT, + sizeof(AVProducerReferenceTime)); + if (!prft) + return AVERROR(ENOMEM); + prft->wallclock = av_gettime(); + prft->flags = 24; + } + if (os->first_pts == AV_NOPTS_VALUE) { + os->producer_reference_time = *prft; + if (c->target_latency_refid < 0) + c->target_latency_refid = pkt->stream_index; + } + + return 0; +} + 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]; + AdaptationSet *as = &c->as[os->as_idx - 1]; int64_t seg_end_duration, elapsed_duration; int ret; - ret = update_stream_extradata(s, os, st->codecpar, &st->avg_frame_rate); + ret = update_stream_extradata(s, os, pkt, &st->avg_frame_rate); if (ret < 0) return ret; @@ -1600,54 +2075,94 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) pkt->dts = 0; } - if (os->first_pts == AV_NOPTS_VALUE) + if (c->write_prft) { + ret = dash_parse_prft(c, pkt); + if (ret < 0) + return ret; + } + + if (os->first_pts == AV_NOPTS_VALUE) { os->first_pts = pkt->pts; + } os->last_pts = pkt->pts; if (!c->availability_start_time[0]) { int64_t start_time_us = av_gettime(); c->start_time_s = start_time_us / 1000000; - format_date_now(c->availability_start_time, - sizeof(c->availability_start_time)); + format_date(c->availability_start_time, + sizeof(c->availability_start_time), start_time_us); } - 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 - + if (!os->packets_written) + os->availability_time_offset = 0; + + if (!os->availability_time_offset && + ((os->frag_type == FRAG_TYPE_DURATION && os->seg_duration != os->frag_duration) || + (os->frag_type == FRAG_TYPE_EVERY_FRAME && pkt->duration))) { + AdaptationSet *as = &c->as[os->as_idx - 1]; + int64_t frame_duration = 0; + + switch (os->frag_type) { + case FRAG_TYPE_DURATION: + frame_duration = os->frag_duration; + break; + case FRAG_TYPE_EVERY_FRAME: + frame_duration = av_rescale_q(pkt->duration, st->time_base, AV_TIME_BASE_Q); + break; + } + + os->availability_time_offset = ((double) os->seg_duration - frame_duration) / AV_TIME_BASE; + as->max_frag_duration = FFMAX(frame_duration, as->max_frag_duration); } 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; + seg_end_duration = (int64_t) os->segment_index * os->seg_duration; } else { elapsed_duration = pkt->pts - os->start_pts; - seg_end_duration = c->seg_duration; + seg_end_duration = os->seg_duration; + } + + if (os->parser && + (os->frag_type == FRAG_TYPE_PFRAMES || + as->trick_idx >= 0)) { + // Parse the packets only in scenarios where it's needed + uint8_t *data; + int size; + av_parser_parse2(os->parser, os->parser_avctx, + &data, &size, pkt->data, pkt->size, + pkt->pts, pkt->dts, pkt->pos); + + os->coding_dependency |= os->parser->pict_type != AV_PICTURE_TYPE_I; } - if ((!c->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && - pkt->flags & AV_PKT_FLAG_KEY && os->packets_written && + if (pkt->flags & AV_PKT_FLAG_KEY && os->packets_written && av_compare_ts(elapsed_duration, st->time_base, seg_end_duration, AV_TIME_BASE_Q) >= 0) { - int64_t prev_duration = c->last_duration; - - c->last_duration = av_rescale_q(pkt->pts - os->start_pts, - st->time_base, - AV_TIME_BASE_Q); - c->total_duration = av_rescale_q(pkt->pts - os->first_pts, - 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"); + if (!c->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + c->last_duration = av_rescale_q(pkt->pts - os->start_pts, + st->time_base, + AV_TIME_BASE_Q); + c->total_duration = av_rescale_q(pkt->pts - os->first_pts, + st->time_base, + AV_TIME_BASE_Q); + + if ((!c->use_timeline || !c->use_template) && os->last_duration) { + if (c->last_duration < os->last_duration*9/10 || + c->last_duration > os->last_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"); + } } } + if (c->write_prft && os->producer_reference_time.wallclock && !os->producer_reference_time_str[0]) + format_date(os->producer_reference_time_str, + sizeof(os->producer_reference_time_str), + os->producer_reference_time.wallclock); + if ((ret = dash_flush(s, 0, pkt->stream_index)) < 0) return ret; } @@ -1665,11 +2180,42 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) os->max_pts = pkt->pts + pkt->duration; else os->max_pts = FFMAX(os->max_pts, pkt->pts + pkt->duration); - os->packets_written++; - os->total_pkt_size += pkt->size; + + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && + os->frag_type == FRAG_TYPE_PFRAMES && + os->packets_written) { + av_assert0(os->parser); + if ((os->parser->pict_type == AV_PICTURE_TYPE_P && + st->codecpar->video_delay && + !(os->last_flags & AV_PKT_FLAG_KEY)) || + pkt->flags & AV_PKT_FLAG_KEY) { + ret = av_write_frame(os->ctx, NULL); + if (ret < 0) + return ret; + + if (!os->availability_time_offset) { + int64_t frag_duration = av_rescale_q(os->total_pkt_duration, st->time_base, + AV_TIME_BASE_Q); + os->availability_time_offset = ((double) os->seg_duration - + frag_duration) / AV_TIME_BASE; + as->max_frag_duration = FFMAX(frag_duration, as->max_frag_duration); + } + } + } + + if (pkt->flags & AV_PKT_FLAG_KEY && (os->packets_written || os->nb_segments) && !os->gop_size && as->trick_idx < 0) { + os->gop_size = os->last_duration + av_rescale_q(os->total_pkt_duration, st->time_base, AV_TIME_BASE_Q); + c->max_gop_size = FFMAX(c->max_gop_size, os->gop_size); + } + if ((ret = ff_write_chained(os->ctx, 0, pkt, s, 0)) < 0) return ret; + os->packets_written++; + os->total_pkt_size += pkt->size; + os->total_pkt_duration += pkt->duration; + os->last_flags = pkt->flags; + if (!os->init_range_length) flush_init_segment(s, os); @@ -1678,6 +2224,8 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) AVDictionary *opts = NULL; const char *proto = avio_find_protocol_name(s->url); int use_rename = proto && !strcmp(proto, "file"); + if (os->segment_type == SEGMENT_TYPE_MP4) + write_styp(os->ctx->pb); os->filename[0] = os->full_path[0] = os->temp_path[0] = '\0'; ff_dash_fill_tmpl_params(os->filename, sizeof(os->filename), os->media_seg_name, pkt->stream_index, @@ -1702,8 +2250,6 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) if (c->streaming && os->segment_type == SEGMENT_TYPE_MP4) { 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); if (os->out) { @@ -1735,31 +2281,22 @@ static int dash_write_trailer(AVFormatContext *s) } dash_flush(s, 1, -1); - for (i = 0; i < s->nb_streams; ++i) { - OutputStream *os = &c->streams[i]; - if (os->ctx && os->ctx_inited) { - av_write_trailer(os->ctx); - } - - if (c->remove_at_exit) { + if (c->remove_at_exit) { + for (i = 0; i < s->nb_streams; ++i) { + OutputStream *os = &c->streams[i]; dashenc_delete_media_segments(s, os, os->nb_segments); dashenc_delete_segment_file(s, os->initfile); + 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); + } } - } - - if (c->remove_at_exit) { dashenc_delete_file(s, s->url); if (c->hls_playlist && c->master_playlist_created) { char filename[1024]; - for (i = 0; i < s->nb_streams; ++i) { - OutputStream *os = &c->streams[i]; - if (os->segment_type == SEGMENT_TYPE_MP4) { - get_hls_playlist_name(filename, sizeof(filename), c->dirname, i); - dashenc_delete_file(s, filename); - } - } - snprintf(filename, sizeof(filename), "%smaster.m3u8", c->dirname); + snprintf(filename, sizeof(filename), "%s%s", c->dirname, c->hls_master_name); dashenc_delete_file(s, filename); } } @@ -1780,10 +2317,8 @@ static int dash_check_bitstream(struct AVFormatContext *s, const AVPacket *avpkt 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; + st->internal->bsfc = ost->internal->bsfc; + ost->internal->bsfc = NULL; } return ret; } @@ -1796,10 +2331,13 @@ static const AVOption options[] = { { "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 }, { "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 }, -#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 }, + { "frag_duration", "fragment duration (in seconds, fractional value can be set)", OFFSET(frag_duration), AV_OPT_TYPE_DURATION, { .i64 = 0 }, 0, INT_MAX, E }, + { "frag_type", "set type of interval for fragments", OFFSET(frag_type), AV_OPT_TYPE_INT, {.i64 = FRAG_TYPE_NONE }, 0, FRAG_TYPE_NB - 1, E, "frag_type"}, + { "none", "one fragment per segment", 0, AV_OPT_TYPE_CONST, {.i64 = FRAG_TYPE_NONE }, 0, UINT_MAX, E, "frag_type"}, + { "every_frame", "fragment at every frame", 0, AV_OPT_TYPE_CONST, {.i64 = FRAG_TYPE_EVERY_FRAME }, 0, UINT_MAX, E, "frag_type"}, + { "duration", "fragment at specific time intervals", 0, AV_OPT_TYPE_CONST, {.i64 = FRAG_TYPE_DURATION }, 0, UINT_MAX, E, "frag_type"}, + { "pframes", "fragment at keyframes and following P-Frame reordering (Video only, experimental)", 0, AV_OPT_TYPE_CONST, {.i64 = FRAG_TYPE_PFRAMES }, 0, UINT_MAX, E, "frag_type"}, { "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 }, @@ -1812,17 +2350,29 @@ static const AVOption options[] = { { "http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, { "http_persistent", "Use persistent HTTP connections", OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, { "hls_playlist", "Generate HLS playlist files(master.m3u8, media_%d.m3u8)", OFFSET(hls_playlist), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, + { "hls_master_name", "HLS master playlist name", OFFSET(hls_master_name), AV_OPT_TYPE_STRING, {.str = "master.m3u8"}, 0, 0, E }, { "streaming", "Enable/Disable streaming mode of output. Each frame will be moof fragment", OFFSET(streaming), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, { "timeout", "set timeout for socket I/O operations", OFFSET(timeout), AV_OPT_TYPE_DURATION, { .i64 = -1 }, -1, INT_MAX, .flags = E }, { "index_correction", "Enable/Disable segment index correction logic", OFFSET(index_correction), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, - { "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}, + { "format_options","set list of options for the container format (mp4/webm) used for dash", OFFSET(format_options), AV_OPT_TYPE_DICT, {.str = NULL}, 0, 0, E}, + { "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 }, { "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"}, { "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"}, { "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 }, { "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 }, + { "ldash", "Enable Low-latency dash. Constrains the value of a few elements", OFFSET(ldash), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, { "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}, + { "write_prft", "Write producer reference time element", OFFSET(write_prft), AV_OPT_TYPE_BOOL, {.i64 = -1}, -1, 1, E}, + { "mpd_profile", "Set profiles. Elements and values used in the manifest may be constrained by them", OFFSET(profile), AV_OPT_TYPE_FLAGS, {.i64 = MPD_PROFILE_DASH }, 0, UINT_MAX, E, "mpd_profile"}, + { "dash", "MPEG-DASH ISO Base media file format live profile", 0, AV_OPT_TYPE_CONST, {.i64 = MPD_PROFILE_DASH }, 0, UINT_MAX, E, "mpd_profile"}, + { "dvb_dash", "DVB-DASH profile", 0, AV_OPT_TYPE_CONST, {.i64 = MPD_PROFILE_DVB }, 0, UINT_MAX, E, "mpd_profile"}, + { "http_opts", "HTTP protocol options", OFFSET(http_opts), AV_OPT_TYPE_DICT, { .str = NULL }, 0, 0, E }, + { "target_latency", "Set desired target latency for Low-latency dash", OFFSET(target_latency), AV_OPT_TYPE_DURATION, { .i64 = 0 }, 0, INT_MAX, E }, + { "min_playback_rate", "Set desired minimum playback rate", OFFSET(min_playback_rate), AV_OPT_TYPE_RATIONAL, { .dbl = 1.0 }, 0.5, 1.5, E }, + { "max_playback_rate", "Set desired maximum playback rate", OFFSET(max_playback_rate), AV_OPT_TYPE_RATIONAL, { .dbl = 1.0 }, 0.5, 1.5, E }, + { "update_period", "Set the mpd update interval", OFFSET(update_period), AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E}, { NULL }, };