[FFmpeg-cvslog] avformat/dashenc: Option to generate hls playlist as well

Karthick J git at videolan.org
Thu Nov 30 05:16:34 EET 2017


ffmpeg | branch: master | Karthick J <kjeyapal at akamai.com> | Thu Nov 30 10:55:51 2017 +0800| [8c2b37e678e3d5ab16fef471fffc741b88622a85] | committer: Steven Liu

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 at onvideo.cn>

> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=8c2b37e678e3d5ab16fef471fffc741b88622a85
---

 doc/muxers.texi       |   3 ++
 libavformat/Makefile  |   2 +-
 libavformat/dashenc.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 108 insertions(+), 7 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 8ec48c2055..3d0c7bfbd3 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.
+ at 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 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 },
 };
 



More information about the ffmpeg-cvslog mailing list