X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fdashenc.c;h=7169e1101df16aa90303df2989e8da9f2af2c12f;hb=01ba52852d2ada3c79fe02a7de5bc1fdc27d56e8;hp=0f6f4f22faf81679b044017148b1b9cd8dc756a3;hpb=1325ac4c93f27dd56ebce37d8045d7ceb64dc645;p=ffmpeg diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 0f6f4f22faf..7169e1101df 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -81,6 +81,10 @@ typedef struct OutputStream { char bandwidth_str[64]; char codec_str[100]; + int written_len; + char filename[1024]; + char full_path[1024]; + char temp_path[1024]; } OutputStream; typedef struct DASHContext { @@ -90,7 +94,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 +112,15 @@ 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; } DASHContext; static struct codec_string { @@ -247,7 +257,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 +267,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, @@ -859,6 +874,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) { @@ -957,9 +979,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 +1030,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,10 +1043,8 @@ 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) @@ -1119,6 +1142,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; @@ -1134,7 +1179,6 @@ static int dash_flush(AVFormatContext *s, int final, int stream) 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 +1196,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,12 +1209,12 @@ 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; } @@ -1200,8 +1231,8 @@ static int dash_flush(AVFormatContext *s, int final, int stream) " 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); + 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 +1248,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; @@ -1266,7 +1297,7 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) 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) { + c->seg_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 +1334,46 @@ 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); + 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 +1400,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 +1437,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 +1449,12 @@ 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 }, { NULL }, };