X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fdashenc.c;h=9e72636f0ebe457be208467e7193fc4b441ff749;hb=7b6b8c92652d6683d97515352e4a9a4147b7da7c;hp=0f6f4f22faf81679b044017148b1b9cd8dc756a3;hpb=d28eb0e34d80f12b779c7b6befc5b350ffcdf476;p=ffmpeg diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 0f6f4f22faf..9e72636f0eb 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -76,11 +76,17 @@ typedef struct OutputStream { int nb_segments, segments_size, segment_index; Segment **segments; int64_t first_pts, start_pts, max_pts; - int64_t last_dts; + int64_t last_dts, last_pts; int bit_rate; - char bandwidth_str[64]; char codec_str[100]; + int written_len; + char filename[1024]; + char full_path[1024]; + char temp_path[1024]; + double availability_time_offset; + int total_pkt_size; + int muxer_overhead; } OutputStream; typedef struct DASHContext { @@ -90,7 +96,10 @@ 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; int remove_at_exit; int use_template; int use_timeline; @@ -105,12 +114,16 @@ typedef struct DASHContext { const char *init_seg_name; const char *media_seg_name; const char *utc_timing_url; + const char *method; const char *user_agent; int hls_playlist; int http_persistent; int master_playlist_created; AVIOContext *mpd_out; AVIOContext *m3u8_out; + int streaming; + int64_t timeout; + int index_correction; } DASHContext; static struct codec_string { @@ -247,7 +260,8 @@ static int flush_dynbuf(OutputStream *os, int *range_length) // write out to file *range_length = avio_close_dyn_buf(os->ctx->pb, &buffer); os->ctx->pb = NULL; - avio_write(os->out, buffer, *range_length); + avio_write(os->out, buffer + os->written_len, *range_length - os->written_len); + os->written_len = 0; av_free(buffer); // re-open buffer @@ -256,10 +270,14 @@ static int flush_dynbuf(OutputStream *os, int *range_length) static void set_http_options(AVDictionary **options, DASHContext *c) { + if (c->method) + av_dict_set(options, "method", c->method, 0); if (c->user_agent) av_dict_set(options, "user_agent", c->user_agent, 0); if (c->http_persistent) av_dict_set_int(options, "multiple_requests", 1, 0); + if (c->timeout >= 0) + av_dict_set_int(options, "timeout", c->timeout, 0); } static void get_hls_playlist_name(char *playlist_name, int string_size, @@ -331,8 +349,12 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatCont 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\tuse_timeline) - avio_printf(out, "duration=\"%"PRId64"\" ", c->last_duration); + if (!c->use_timeline) { + avio_printf(out, "duration=\"%"PRId64"\" ", c->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", c->init_seg_name, c->media_seg_name, c->use_timeline ? start_number : 1); if (c->use_timeline) { int64_t cur_time = 0; @@ -527,20 +549,25 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; + char bandwidth_str[64] = {'\0'}; if (os->as_idx - 1 != as_index) continue; + if (os->bit_rate > 0) + snprintf(bandwidth_str, sizeof(bandwidth_str), " bandwidth=\"%d\"", + os->bit_rate + os->muxer_overhead); + if (as->media_type == AVMEDIA_TYPE_VIDEO) { AVStream *st = s->streams[i]; avio_printf(out, "\t\t\tformat_name, os->codec_str, os->bandwidth_str, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height); + i, os->format_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->width, s->streams[i]->codecpar->height); 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"); } else { avio_printf(out, "\t\t\t\n", - i, os->format_name, os->codec_str, os->bandwidth_str, s->streams[i]->codecpar->sample_rate); + i, os->format_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->sample_rate); avio_printf(out, "\t\t\t\t\n", s->streams[i]->codecpar->channels); } @@ -728,9 +755,6 @@ static int write_manifest(AVFormatContext *s, int final) update_period = 500; 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->availability_start_time[0] && s->nb_streams > 0 && c->streams[0].nb_segments > 0) { - format_date_now(c->availability_start_time, sizeof(c->availability_start_time)); - } if (c->availability_start_time[0]) avio_printf(out, "\tavailabilityStartTime=\"%s\"\n", c->availability_start_time); format_date_now(now_str, sizeof(now_str)); @@ -808,20 +832,23 @@ static int write_manifest(AVFormatContext *s, int final) for (i = 0; i < s->nb_streams; i++) { char playlist_file[64]; AVStream *st = s->streams[i]; + OutputStream *os = &c->streams[i]; if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) continue; get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i); ff_hls_write_audio_rendition(out, (char *)audio_group, playlist_file, i, is_default); - max_audio_bitrate = FFMAX(st->codecpar->bit_rate, max_audio_bitrate); + max_audio_bitrate = FFMAX(st->codecpar->bit_rate + + os->muxer_overhead, max_audio_bitrate); is_default = 0; } for (i = 0; i < s->nb_streams; i++) { char playlist_file[64]; AVStream *st = s->streams[i]; + OutputStream *os = &c->streams[i]; char *agroup = NULL; - int stream_bitrate = st->codecpar->bit_rate; + int stream_bitrate = st->codecpar->bit_rate + os->muxer_overhead; if ((st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && max_audio_bitrate) { agroup = (char *)audio_group; stream_bitrate += max_audio_bitrate; @@ -859,6 +886,13 @@ static int dash_init(AVFormatContext *s) 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; + } +#endif + av_strlcpy(c->dirname, s->url, sizeof(c->dirname)); ptr = strrchr(c->dirname, '/'); if (ptr) { @@ -889,10 +923,7 @@ static int dash_init(AVFormatContext *s) char filename[1024]; os->bit_rate = s->streams[i]->codecpar->bit_rate; - if (os->bit_rate) { - snprintf(os->bandwidth_str, sizeof(os->bandwidth_str), - " bandwidth=\"%d\"", os->bit_rate); - } else { + if (!os->bit_rate) { 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); @@ -957,9 +988,12 @@ static int dash_init(AVFormatContext *s) os->init_start_pos = 0; if (!strcmp(os->format_name, "mp4")) { - av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + if (c->streaming) + av_dict_set(&opts, "movflags", "frag_every_frame+dash+delay_moov", 0); + else + av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); } else { - av_dict_set_int(&opts, "cluster_time_limit", c->min_seg_duration / 1000, 0); + 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 av_dict_set_int(&opts, "dash", 1, 0); av_dict_set_int(&opts, "dash_track_number", i + 1, 0); @@ -1005,8 +1039,8 @@ static int dash_init(AVFormatContext *s) os->segment_index = 1; } - if (!c->has_video && c->min_seg_duration <= 0) { - av_log(s, AV_LOG_WARNING, "no video stream and no min seg duration set\n"); + 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); } return 0; @@ -1018,21 +1052,16 @@ static int dash_write_header(AVFormatContext *s) int i, ret; for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; - if ((ret = avformat_write_header(os->ctx, NULL)) < 0) { - dash_free(s); + if ((ret = avformat_write_header(os->ctx, NULL)) < 0) return ret; - } } - ret = write_manifest(s, 0); - if (!ret) - av_log(s, AV_LOG_VERBOSE, "Manifest written to: %s\n", s->url); return ret; } static int add_segment(OutputStream *os, const char *file, int64_t time, int duration, int64_t start_pos, int64_t range_length, - int64_t index_length) + int64_t index_length, int next_exp_index) { int err; Segment *seg; @@ -1060,6 +1089,12 @@ static int add_segment(OutputStream *os, const char *file, seg->index_length = index_length; os->segments[os->nb_segments++] = seg; os->segment_index++; + //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; + } return 0; } @@ -1119,6 +1154,28 @@ static int update_stream_extradata(AVFormatContext *s, OutputStream *os, return 0; } +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); + dashenc_io_close(s, &out, filename); + } else if (unlink(filename) < 0) { + av_log(s, AV_LOG_ERROR, "failed to delete %s: %s\n", filename, strerror(errno)); + } +} + static int dash_flush(AVFormatContext *s, int final, int stream) { DASHContext *c = s->priv_data; @@ -1127,14 +1184,25 @@ static int dash_flush(AVFormatContext *s, int final, int stream) const char *proto = avio_find_protocol_name(s->url); int use_rename = proto && !strcmp(proto, "file"); - int cur_flush_segment_index = 0; - if (stream >= 0) + int cur_flush_segment_index = 0, next_exp_index = -1; + if (stream >= 0) { cur_flush_segment_index = c->streams[stream].segment_index; + //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; + } + } + for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; AVStream *st = s->streams[i]; - char filename[1024] = "", full_path[1024], temp_path[1024]; int range_length, index_length = 0; if (!os->packets_written) @@ -1152,24 +1220,11 @@ static int dash_flush(AVFormatContext *s, int final, int stream) continue; } - if (!os->init_range_length) { - flush_init_segment(s, os); - } - if (!c->single_file) { - AVDictionary *opts = NULL; - ff_dash_fill_tmpl_params(filename, sizeof(filename), c->media_seg_name, i, os->segment_index, os->bit_rate, os->start_pts); - snprintf(full_path, sizeof(full_path), "%s%s", c->dirname, filename); - snprintf(temp_path, sizeof(temp_path), use_rename ? "%s.tmp" : "%s", full_path); - set_http_options(&opts, c); - ret = dashenc_io_open(s, &os->out, temp_path, &opts); - if (ret < 0) - break; - av_dict_free(&opts); - if (!strcmp(os->format_name, "mp4")) + if (!strcmp(os->format_name, "mp4") && !os->written_len) write_styp(os->ctx->pb); } else { - snprintf(full_path, sizeof(full_path), "%s%s", c->dirname, os->initfile); + snprintf(os->full_path, sizeof(os->full_path), "%s%s", c->dirname, os->initfile); } ret = flush_dynbuf(os, &range_length); @@ -1178,30 +1233,34 @@ static int dash_flush(AVFormatContext *s, int final, int stream) os->packets_written = 0; if (c->single_file) { - find_index_range(s, full_path, os->pos, &index_length); + find_index_range(s, os->full_path, os->pos, &index_length); } else { - dashenc_io_close(s, &os->out, temp_path); + dashenc_io_close(s, &os->out, os->temp_path); if (use_rename) { - ret = avpriv_io_move(temp_path, full_path); + ret = avpriv_io_move(os->temp_path, os->full_path); if (ret < 0) break; } } + 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; + 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) { + if (bitrate >= 0) os->bit_rate = bitrate; - snprintf(os->bandwidth_str, sizeof(os->bandwidth_str), - " bandwidth=\"%d\"", os->bit_rate); - } } - add_segment(os, filename, os->start_pts, os->max_pts - os->start_pts, os->pos, range_length, index_length); - av_log(s, AV_LOG_VERBOSE, "Representation %d media segment %d written to: %s\n", i, os->segment_index, full_path); + 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); os->pos += range_length; } @@ -1217,7 +1276,7 @@ static int dash_flush(AVFormatContext *s, int final, int stream) for (j = 0; j < remove; j++) { char filename[1024]; snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->segments[j]->file); - unlink(filename); + dashenc_delete_file(s, filename); av_free(os->segments[j]); } os->nb_segments -= remove; @@ -1236,6 +1295,7 @@ 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]; + int64_t seg_end_duration, elapsed_duration; int ret; ret = update_stream_extradata(s, os, st->codecpar); @@ -1262,11 +1322,31 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) if (os->first_pts == AV_NOPTS_VALUE) os->first_pts = pkt->pts; + os->last_pts = pkt->pts; + + if (!c->availability_start_time[0]) + format_date_now(c->availability_start_time, + sizeof(c->availability_start_time)); + + 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; + } + + 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; + } if ((!c->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && pkt->flags & AV_PKT_FLAG_KEY && os->packets_written && - av_compare_ts(pkt->pts - os->start_pts, st->time_base, - c->min_seg_duration, AV_TIME_BASE_Q) >= 0) { + 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, @@ -1303,7 +1383,47 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) else os->max_pts = FFMAX(os->max_pts, pkt->pts + pkt->duration); os->packets_written++; - return ff_write_chained(os->ctx, 0, pkt, s, 0); + os->total_pkt_size += pkt->size; + 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 + if (!c->single_file && os->packets_written == 1) { + AVDictionary *opts = NULL; + const char *proto = avio_find_protocol_name(s->url); + 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), + c->media_seg_name, pkt->stream_index, + 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); + if (ret < 0) + return ret; + av_dict_free(&opts); + } + + //write out the data immediately in streaming mode + if (c->streaming && !strcmp(os->format_name, "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); + avio_write(os->out, buf + os->written_len, len - os->written_len); + os->written_len = len; + avio_flush(os->out); + } + + return ret; } static int dash_write_trailer(AVFormatContext *s) @@ -1330,9 +1450,9 @@ static int dash_write_trailer(AVFormatContext *s) for (i = 0; i < s->nb_streams; i++) { OutputStream *os = &c->streams[i]; snprintf(filename, sizeof(filename), "%s%s", c->dirname, os->initfile); - unlink(filename); + dashenc_delete_file(s, filename); } - unlink(s->url); + dashenc_delete_file(s, s->url); } return 0; @@ -1367,7 +1487,10 @@ 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 }, - { "min_seg_duration", "minimum segment duration (in microseconds)", OFFSET(min_seg_duration), AV_OPT_TYPE_INT, { .i64 = 5000000 }, 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 }, { "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 }, @@ -1376,9 +1499,13 @@ static const AVOption options[] = { { "init_seg_name", "DASH-templated name to used for the initialization segment", OFFSET(init_seg_name), AV_OPT_TYPE_STRING, {.str = "init-stream$RepresentationID$.m4s"}, 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$.m4s"}, 0, 0, E }, { "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 }, + { "method", "set the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, { "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 }, + { "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 }, { NULL }, };