X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fdashenc.c;h=1b74bce060fc6c5f0cb64dc85030e6f8d4e8fc24;hb=c2a221c5ae5017e11654b9688ac97e9f5d3570b2;hp=4d9b564a94582c426eaaf63467e347e6a5374601;hpb=f176d6587bcf7c15c9d9f1acb05d8f970ade57de;p=ffmpeg diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 4d9b564a945..1b74bce060f 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -32,6 +32,7 @@ #include "libavutil/mathematics.h" #include "libavutil/opt.h" #include "libavutil/rational.h" +#include "libavutil/time.h" #include "libavutil/time_internal.h" #include "avc.h" @@ -60,6 +61,7 @@ typedef struct Segment { int64_t start_pos; int range_length, index_length; int64_t time; + double prog_date_time; int64_t duration; int n; } Segment; @@ -121,6 +123,7 @@ typedef struct DASHContext { int64_t last_duration; int64_t total_duration; char availability_start_time[100]; + time_t start_time_s; char dirname[1024]; const char *single_file_name; /* file names as specified in options */ const char *init_seg_name; @@ -139,6 +142,8 @@ typedef struct DASHContext { char *format_options_str; SegmentType segment_type_option; /* segment type as specified in options */ int ignore_io_errors; + int lhls; + int master_publish_rate; } DASHContext; static struct codec_string { @@ -407,6 +412,97 @@ static void get_hls_playlist_name(char *playlist_name, int string_size, snprintf(playlist_name, string_size, "media_%d.m3u8", id); } +static void get_start_index_number(OutputStream *os, DASHContext *c, + int *start_index, int *start_number) { + *start_index = 0; + *start_number = 1; + if (c->window_size) { + *start_index = FFMAX(os->nb_segments - c->window_size, 0); + *start_number = FFMAX(os->segment_index - c->window_size, 1); + } +} + +static void write_hls_media_playlist(OutputStream *os, AVFormatContext *s, + int representation_id, int final, + char *prefetch_url) { + DASHContext *c = s->priv_data; + int timescale = os->ctx->streams[0]->time_base.den; + char temp_filename_hls[1024]; + char filename_hls[1024]; + AVDictionary *http_opts = NULL; + int target_duration = 0; + int ret = 0; + const char *proto = avio_find_protocol_name(c->dirname); + int use_rename = proto && !strcmp(proto, "file"); + int i, start_index, start_number; + double prog_date_time = 0; + + get_start_index_number(os, c, &start_index, &start_number); + + if (!c->hls_playlist || start_index >= os->nb_segments || + os->segment_type != SEGMENT_TYPE_MP4) + return; + + get_hls_playlist_name(filename_hls, sizeof(filename_hls), + c->dirname, representation_id); + + snprintf(temp_filename_hls, sizeof(temp_filename_hls), use_rename ? "%s.tmp" : "%s", filename_hls); + + set_http_options(&http_opts, c); + ret = dashenc_io_open(s, &c->m3u8_out, temp_filename_hls, &http_opts); + av_dict_free(&http_opts); + if (ret < 0) { + handle_io_open_error(s, ret, temp_filename_hls); + return; + } + for (i = start_index; i < os->nb_segments; i++) { + Segment *seg = os->segments[i]; + double duration = (double) seg->duration / timescale; + if (target_duration <= duration) + target_duration = lrint(duration); + } + + ff_hls_write_playlist_header(c->m3u8_out, 6, -1, target_duration, + start_number, PLAYLIST_TYPE_NONE); + + ff_hls_write_init_file(c->m3u8_out, os->initfile, c->single_file, + os->init_range_length, os->init_start_pos); + + for (i = start_index; i < os->nb_segments; i++) { + Segment *seg = os->segments[i]; + + if (prog_date_time == 0) { + if (os->nb_segments == 1) + prog_date_time = c->start_time_s; + else + prog_date_time = seg->prog_date_time; + } + seg->prog_date_time = prog_date_time; + + ret = ff_hls_write_file_entry(c->m3u8_out, 0, c->single_file, + (double) seg->duration / timescale, 0, + seg->range_length, seg->start_pos, NULL, + c->single_file ? os->initfile : seg->file, + &prog_date_time); + if (ret < 0) { + av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n"); + } + } + + if (prefetch_url) + avio_printf(c->m3u8_out, "#EXT-X-PREFETCH:%s\n", prefetch_url); + + if (final) + ff_hls_write_end_list(c->m3u8_out); + + dashenc_io_close(s, &c->m3u8_out, temp_filename_hls); + + if (use_rename) + if (avpriv_io_move(temp_filename_hls, filename_hls) < 0) { + av_log(os->ctx, AV_LOG_WARNING, "renaming file %s to %s failed\n\n", temp_filename_hls, filename_hls); + } +} + static int flush_init_segment(AVFormatContext *s, OutputStream *os) { DASHContext *c = s->priv_data; @@ -463,11 +559,8 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatCont int representation_id, int final) { DASHContext *c = s->priv_data; - int i, start_index = 0, start_number = 1; - if (c->window_size) { - start_index = FFMAX(os->nb_segments - c->window_size, 0); - start_number = FFMAX(os->segment_index - c->window_size, 1); - } + int i, start_index, start_number; + get_start_index_number(os, c, &start_index, &start_number); if (c->use_template) { int timescale = c->use_timeline ? os->ctx->streams[0]->time_base.den : AV_TIME_BASE; @@ -525,63 +618,8 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, AVFormatCont } avio_printf(out, "\t\t\t\t\n"); } - if (c->hls_playlist && start_index < os->nb_segments && os->segment_type == SEGMENT_TYPE_MP4) - { - int timescale = os->ctx->streams[0]->time_base.den; - char temp_filename_hls[1024]; - char filename_hls[1024]; - AVDictionary *http_opts = NULL; - int target_duration = 0; - int ret = 0; - const char *proto = avio_find_protocol_name(c->dirname); - int use_rename = proto && !strcmp(proto, "file"); - - get_hls_playlist_name(filename_hls, sizeof(filename_hls), - c->dirname, representation_id); - - snprintf(temp_filename_hls, sizeof(temp_filename_hls), use_rename ? "%s.tmp" : "%s", filename_hls); - - set_http_options(&http_opts, c); - ret = dashenc_io_open(s, &c->m3u8_out, temp_filename_hls, &http_opts); - av_dict_free(&http_opts); - if (ret < 0) { - handle_io_open_error(s, ret, temp_filename_hls); - return; - } - for (i = start_index; i < os->nb_segments; i++) { - Segment *seg = os->segments[i]; - double duration = (double) seg->duration / timescale; - if (target_duration <= duration) - target_duration = lrint(duration); - } - - ff_hls_write_playlist_header(c->m3u8_out, 6, -1, target_duration, - start_number, PLAYLIST_TYPE_NONE); - - ff_hls_write_init_file(c->m3u8_out, os->initfile, c->single_file, - os->init_range_length, os->init_start_pos); - - for (i = start_index; i < os->nb_segments; i++) { - Segment *seg = os->segments[i]; - ret = ff_hls_write_file_entry(c->m3u8_out, 0, c->single_file, - (double) seg->duration / timescale, 0, - seg->range_length, seg->start_pos, NULL, - c->single_file ? os->initfile : seg->file, - NULL); - if (ret < 0) { - av_log(os->ctx, AV_LOG_WARNING, "ff_hls_write_file_entry get error\n"); - } - } - - if (final) - ff_hls_write_end_list(c->m3u8_out); - - dashenc_io_close(s, &c->m3u8_out, temp_filename_hls); - - if (use_rename) - if (avpriv_io_move(temp_filename_hls, filename_hls) < 0) { - av_log(os->ctx, AV_LOG_WARNING, "renaming file %s to %s failed\n\n", temp_filename_hls, filename_hls); - } + if (!c->lhls || final) { + write_hls_media_playlist(os, s, representation_id, final, NULL); } } @@ -644,12 +682,20 @@ static void write_time(AVIOContext *out, int64_t time) static void format_date_now(char *buf, int size) { - time_t t = time(NULL); struct tm *ptm, tmbuf; - ptm = gmtime_r(&t, &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); + ptm = gmtime_r(&time_s, &tmbuf); if (ptm) { - if (!strftime(buf, size, "%Y-%m-%dT%H:%M:%SZ", ptm)) + int len; + if (!strftime(buf, size, "%Y-%m-%dT%H:%M:%S", ptm)) { buf[0] = '\0'; + return; + } + len = strlen(buf); + snprintf(buf + len, size - len, ".%03dZ", millisec); } } @@ -932,13 +978,18 @@ static int write_manifest(AVFormatContext *s, int final) return ret; } - if (c->hls_playlist && !c->master_playlist_created) { + if (c->hls_playlist) { char filename_hls[1024]; const char *audio_group = "A1"; char audio_codec_str[128] = "\0"; int is_default = 1; int max_audio_bitrate = 0; + // Publish master playlist only the configured rate + if (c->master_playlist_created && (!c->master_publish_rate || + c->streams[0].segment_index % c->master_publish_rate)) + return 0; + if (*c->dirname) snprintf(filename_hls, sizeof(filename_hls), "%smaster.m3u8", c->dirname); else @@ -965,7 +1016,7 @@ static int write_manifest(AVFormatContext *s, int final) continue; get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i); ff_hls_write_audio_rendition(c->m3u8_out, (char *)audio_group, - playlist_file, i, is_default); + playlist_file, NULL, i, is_default); max_audio_bitrate = FFMAX(st->codecpar->bit_rate + os->muxer_overhead, max_audio_bitrate); if (!av_strnstr(audio_codec_str, os->codec_str, sizeof(audio_codec_str))) { @@ -1039,6 +1090,21 @@ static int dash_init(AVFormatContext *s) c->seg_duration = c->min_seg_duration; } #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"); + return AVERROR_EXPERIMENTAL; + } + + if (c->lhls && !c->streaming) { + av_log(s, AV_LOG_WARNING, "LHLS option will be ignored as streaming is not enabled\n"); + c->lhls = 0; + } + + if (c->lhls && !c->hls_playlist) { + av_log(s, AV_LOG_WARNING, "LHLS option will be ignored as hls_playlist is not enabled\n"); + c->lhls = 0; + } av_strlcpy(c->dirname, s->url, sizeof(c->dirname)); ptr = strrchr(c->dirname, '/'); @@ -1162,7 +1228,10 @@ static int dash_init(AVFormatContext *s) if (os->segment_type == SEGMENT_TYPE_MP4) { if (c->streaming) - av_dict_set(&opts, "movflags", "frag_every_frame+dash+delay_moov+global_sidx", 0); + // 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); else av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); } else { @@ -1535,9 +1604,12 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) os->first_pts = pkt->pts; os->last_pts = pkt->pts; - if (!c->availability_start_time[0]) + 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)); + } if (!os->availability_time_offset && pkt->duration) { int64_t frame_duration = av_rescale_q(pkt->duration, st->time_base, @@ -1620,6 +1692,10 @@ static int dash_write_packet(AVFormatContext *s, AVPacket *pkt) if (ret < 0) { return handle_io_open_error(s, ret, os->temp_path); } + if (c->lhls) { + char *prefetch_url = use_rename ? NULL : os->filename; + write_hls_media_playlist(os, s, pkt->stream_index, 0, prefetch_url); + } } //write out the data immediately in streaming mode @@ -1745,6 +1821,8 @@ static const AVOption options[] = { { "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 }, + { "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}, { NULL }, };