diff mbox

[FFmpeg-devel,v6,2/2] avformat/dashenc: Option to generate hls playlist as well

Message ID 1511959726-16707-1-git-send-email-kjeyapal@akamai.com
State Superseded
Headers show

Commit Message

Jeyapal, Karthick Nov. 29, 2017, 12:48 p.m. UTC
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(-)

Comments

Steven Liu Nov. 29, 2017, 2:05 p.m. UTC | #1
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
Steven Liu Nov. 29, 2017, 2:10 p.m. UTC | #2
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
Jeyapal, Karthick Nov. 29, 2017, 3:19 p.m. UTC | #3
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 mbox

Patch

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 },
 };