Message ID | 1511959726-16707-1-git-send-email-kjeyapal@akamai.com |
---|---|
State | Superseded |
Headers | show |
2017-11-29 20:48 GMT+08:00 Karthick J <kjeyapal@akamai.com>: > This is to take full advantage of Common Media Application Format. > Now server can generate one content and serve both HLS and DASH players. > --- > doc/muxers.texi | 3 ++ > libavformat/Makefile | 2 +- > libavformat/dashenc.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++-- > 3 files changed, 103 insertions(+), 5 deletions(-) > > diff --git a/doc/muxers.texi b/doc/muxers.texi > index 8ec48c2..3d0c7bf 100644 > --- a/doc/muxers.texi > +++ b/doc/muxers.texi > @@ -249,6 +249,9 @@ DASH-templated name to used for the media segments. Default is "chunk-stream$Rep > URL of the page that will return the UTC timestamp in ISO format. Example: "https://time.akamai.com/?iso" > @item -http_user_agent @var{user_agent} > Override User-Agent field in HTTP header. Applicable only for HTTP output. > +@item -hls_playlist @var{hls_playlist} > +Generate HLS playlist files as well. The master playlist is generated with the filename master.m3u8. > +One media playlist file is generated for each stream with filenames media_0.m3u8, media_1.m3u8, etc. > @item -adaptation_sets @var{adaptation_sets} > Assign streams to AdaptationSets. Syntax is "id=x,streams=a,b,c id=y,streams=d,e" with x and y being the IDs > of the adaptation sets and a,b,c,d and e are the indices of the mapped streams. > diff --git a/libavformat/Makefile b/libavformat/Makefile > index fd8b9f9..4bffdf2 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 0fee3cd..ee9dc85 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]; > + if (target_duration < seg->duration) > + target_duration = seg->duration; > + } > + target_duration = lrint((double) target_duration / timescale); What value will set when the target_duration is 1.023 ? I think we have talk about the value and the specification describe the standard process way. You can move the function get_int_from_double to the common file and use that. > + > + 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"); > @@ -665,6 +727,38 @@ static int write_manifest(AVFormatContext *s, int final) > if (use_rename) > return avpriv_io_move(temp_filename, s->filename); > > + 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) > + avpriv_io_move(temp_filename, filename_hls); > + c->master_playlist_created = 1; > + } > + > return 0; > } > > @@ -1206,6 +1300,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 }, > }; > > -- > 1.9.1 > Thanks
2017-11-29 22:05 GMT+08:00 Steven Liu <lingjiujianke@gmail.com>: > 2017-11-29 20:48 GMT+08:00 Karthick J <kjeyapal@akamai.com>: >> This is to take full advantage of Common Media Application Format. >> Now server can generate one content and serve both HLS and DASH players. >> --- >> doc/muxers.texi | 3 ++ >> libavformat/Makefile | 2 +- >> libavformat/dashenc.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++-- >> 3 files changed, 103 insertions(+), 5 deletions(-) >> >> diff --git a/doc/muxers.texi b/doc/muxers.texi >> index 8ec48c2..3d0c7bf 100644 >> --- a/doc/muxers.texi >> +++ b/doc/muxers.texi >> @@ -249,6 +249,9 @@ DASH-templated name to used for the media segments. Default is "chunk-stream$Rep >> URL of the page that will return the UTC timestamp in ISO format. Example: "https://time.akamai.com/?iso" >> @item -http_user_agent @var{user_agent} >> Override User-Agent field in HTTP header. Applicable only for HTTP output. >> +@item -hls_playlist @var{hls_playlist} >> +Generate HLS playlist files as well. The master playlist is generated with the filename master.m3u8. >> +One media playlist file is generated for each stream with filenames media_0.m3u8, media_1.m3u8, etc. >> @item -adaptation_sets @var{adaptation_sets} >> Assign streams to AdaptationSets. Syntax is "id=x,streams=a,b,c id=y,streams=d,e" with x and y being the IDs >> of the adaptation sets and a,b,c,d and e are the indices of the mapped streams. >> diff --git a/libavformat/Makefile b/libavformat/Makefile >> index fd8b9f9..4bffdf2 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 0fee3cd..ee9dc85 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]; >> + if (target_duration < seg->duration) >> + target_duration = seg->duration; >> + } >> + target_duration = lrint((double) target_duration / timescale); > What value will set when the target_duration is 1.023 ? I think we > have talk about the value and the specification describe the standard > process way. > You can move the function get_int_from_double to the common file and use that. > >> + >> + 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"); >> @@ -665,6 +727,38 @@ static int write_manifest(AVFormatContext *s, int final) >> if (use_rename) >> return avpriv_io_move(temp_filename, s->filename); >> >> + 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) >> + avpriv_io_move(temp_filename, filename_hls); >> + c->master_playlist_created = 1; >> + } >> + >> return 0; >> } >> >> @@ -1206,6 +1300,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 }, >> }; >> >> -- >> 1.9.1 >> > > > > Thanks BTW, this patch you can make a new patch, 1/1 not V7 2/2, because i have pushed the first patch. Thanks
On 11/29/17, 7:43 PM, "Steven Liu" <lingjiujianke@gmail.com> wrote: >> + target_duration = lrint((double) target_duration / timescale); >What value will set when the target_duration is 1.023 ? I think we >have talk about the value and the specification describe the standard >process way. >You can move the function get_int_from_double to the common file and use that. Thanks for reminding me. I have corrected it and sent a fresh patchset as you suggested. http://ffmpeg.org/pipermail/ffmpeg-devel/2017-November/221317.html regards, Karthick
diff --git a/doc/muxers.texi b/doc/muxers.texi index 8ec48c2..3d0c7bf 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -249,6 +249,9 @@ DASH-templated name to used for the media segments. Default is "chunk-stream$Rep URL of the page that will return the UTC timestamp in ISO format. Example: "https://time.akamai.com/?iso" @item -http_user_agent @var{user_agent} Override User-Agent field in HTTP header. Applicable only for HTTP output. +@item -hls_playlist @var{hls_playlist} +Generate HLS playlist files as well. The master playlist is generated with the filename master.m3u8. +One media playlist file is generated for each stream with filenames media_0.m3u8, media_1.m3u8, etc. @item -adaptation_sets @var{adaptation_sets} Assign streams to AdaptationSets. Syntax is "id=x,streams=a,b,c id=y,streams=d,e" with x and y being the IDs of the adaptation sets and a,b,c,d and e are the indices of the mapped streams. diff --git a/libavformat/Makefile b/libavformat/Makefile index fd8b9f9..4bffdf2 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 0fee3cd..ee9dc85 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]; + if (target_duration < seg->duration) + target_duration = seg->duration; + } + target_duration = lrint((double) target_duration / timescale); + + 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"); @@ -665,6 +727,38 @@ static int write_manifest(AVFormatContext *s, int final) if (use_rename) return avpriv_io_move(temp_filename, s->filename); + 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) + avpriv_io_move(temp_filename, filename_hls); + c->master_playlist_created = 1; + } + return 0; } @@ -1206,6 +1300,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 }, };