diff options
author | Karthick J <kjeyapal@akamai.com> | 2017-11-30 10:55:51 +0800 |
---|---|---|
committer | Steven Liu <lq@chinaffmpeg.org> | 2017-11-30 10:55:51 +0800 |
commit | 8c2b37e678e3d5ab16fef471fffc741b88622a85 (patch) | |
tree | 6d696154f4ace27d92183792209718f8a08bea4c /libavformat | |
parent | 3684b5e56a54b850bd725ffc63cb454e23fd79db (diff) | |
download | ffmpeg-8c2b37e678e3d5ab16fef471fffc741b88622a85.tar.gz |
avformat/dashenc: Option to generate hls playlist as well
This is to take full advantage of Common Media Application Format(CMAF).
Now server can generate one content and serve both HLS and DASH players.
Reviewed-by: Steven Liu <lq@onvideo.cn>
Diffstat (limited to 'libavformat')
-rw-r--r-- | libavformat/Makefile | 2 | ||||
-rw-r--r-- | libavformat/dashenc.c | 110 |
2 files changed, 105 insertions, 7 deletions
diff --git a/libavformat/Makefile b/libavformat/Makefile index fd8b9f9899..4bffdf2205 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -135,7 +135,7 @@ OBJS-$(CONFIG_CONCAT_DEMUXER) += concatdec.o OBJS-$(CONFIG_CRC_MUXER) += crcenc.o OBJS-$(CONFIG_DATA_DEMUXER) += rawdec.o OBJS-$(CONFIG_DATA_MUXER) += rawenc.o -OBJS-$(CONFIG_DASH_MUXER) += dash.o dashenc.o +OBJS-$(CONFIG_DASH_MUXER) += dash.o dashenc.o hlsplaylist.o OBJS-$(CONFIG_DASH_DEMUXER) += dash.o dashdec.o OBJS-$(CONFIG_DAUD_DEMUXER) += dauddec.o OBJS-$(CONFIG_DAUD_MUXER) += daudenc.o diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 0fee3cd86a..1783675d00 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -36,6 +36,7 @@ #include "avc.h" #include "avformat.h" #include "avio_internal.h" +#include "hlsplaylist.h" #include "internal.h" #include "isom.h" #include "os_support.h" @@ -101,6 +102,8 @@ typedef struct DASHContext { const char *media_seg_name; const char *utc_timing_url; const char *user_agent; + int hls_playlist; + int master_playlist_created; } DASHContext; static struct codec_string { @@ -217,6 +220,14 @@ static void set_http_options(AVDictionary **options, DASHContext *c) av_dict_set(options, "user_agent", c->user_agent, 0); } +static void get_hls_playlist_name(char *playlist_name, int string_size, + const char *base_url, int id) { + if (base_url) + snprintf(playlist_name, string_size, "%smedia_%d.m3u8", base_url, id); + else + snprintf(playlist_name, string_size, "media_%d.m3u8", id); +} + static int flush_init_segment(AVFormatContext *s, OutputStream *os) { DASHContext *c = s->priv_data; @@ -262,7 +273,8 @@ static void dash_free(AVFormatContext *s) av_freep(&c->streams); } -static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c) +static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c, + int representation_id, int final) { int i, start_index = 0, start_number = 1; if (c->window_size) { @@ -322,6 +334,55 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext } avio_printf(out, "\t\t\t\t</SegmentList>\n"); } + if (c->hls_playlist && start_index < os->nb_segments) + { + int timescale = os->ctx->streams[0]->time_base.den; + char temp_filename_hls[1024]; + char filename_hls[1024]; + AVIOContext *out_hls = NULL; + AVDictionary *http_opts = NULL; + int target_duration = 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); + avio_open2(&out_hls, temp_filename_hls, AVIO_FLAG_WRITE, NULL, &http_opts); + av_dict_free(&http_opts); + 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 = hls_get_int_from_double(duration); + } + + ff_hls_write_playlist_header(out_hls, 6, -1, target_duration, + start_number, PLAYLIST_TYPE_NONE); + + ff_hls_write_init_file(out_hls, 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]; + ff_hls_write_file_entry(out_hls, 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 (final) + ff_hls_write_end_list(out_hls); + + avio_close(out_hls); + if (use_rename) + avpriv_io_move(temp_filename_hls, filename_hls); + } + } static char *xmlescape(const char *str) { @@ -391,7 +452,8 @@ static void format_date_now(char *buf, int size) } } -static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index) +static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index, + int final) { DASHContext *c = s->priv_data; AdaptationSet *as = &c->as[as_index]; @@ -430,7 +492,7 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", s->streams[i]->codecpar->channels); } - output_segment_list(os, out, c); + output_segment_list(os, out, c, i, final); avio_printf(out, "\t\t\t</Representation>\n"); } avio_printf(out, "\t\t</AdaptationSet>\n"); @@ -650,7 +712,7 @@ static int write_manifest(AVFormatContext *s, int final) } for (i = 0; i < c->nb_as; i++) { - if ((ret = write_adaptation_set(s, out, i)) < 0) + if ((ret = write_adaptation_set(s, out, i, final)) < 0) return ret; } avio_printf(out, "\t</Period>\n"); @@ -662,8 +724,43 @@ static int write_manifest(AVFormatContext *s, int final) avio_flush(out); ff_format_io_close(s, &out); - if (use_rename) - return avpriv_io_move(temp_filename, s->filename); + if (use_rename) { + if ((ret = avpriv_io_move(temp_filename, s->filename)) < 0) + return ret; + } + + if (c->hls_playlist && !c->master_playlist_created) { + char filename_hls[1024]; + + if (*c->dirname) + snprintf(filename_hls, sizeof(filename_hls), "%s/master.m3u8", c->dirname); + else + snprintf(filename_hls, sizeof(filename_hls), "master.m3u8"); + + snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", filename_hls); + + set_http_options(&opts, c); + ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, NULL, &opts); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename); + return ret; + } + av_dict_free(&opts); + + ff_hls_write_playlist_version(out, 6); + + for (i = 0; i < s->nb_streams; i++) { + char playlist_file[64]; + AVStream *st = s->streams[i]; + get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i); + ff_hls_write_stream_info(st, out, st->codecpar->bit_rate, playlist_file); + } + avio_close(out); + if (use_rename) + if ((ret = avpriv_io_move(temp_filename, filename_hls)) < 0) + return ret; + c->master_playlist_created = 1; + } return 0; } @@ -1206,6 +1303,7 @@ static const AVOption options[] = { { "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 }, { "http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + { "hls_playlist", "Generate HLS playlist files(master.m3u8, media_%d.m3u8)", OFFSET(hls_playlist), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E }, { NULL }, }; |