Browse code

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>

Karthick J authored on 2017/11/30 11:55:51
Showing 3 changed files
... ...
@@ -249,6 +249,9 @@ DASH-templated name to used for the media segments. Default is "chunk-stream$Rep
249 249
 URL of the page that will return the UTC timestamp in ISO format. Example: "https://time.akamai.com/?iso"
250 250
 @item -http_user_agent @var{user_agent}
251 251
 Override User-Agent field in HTTP header. Applicable only for HTTP output.
252
+@item -hls_playlist @var{hls_playlist}
253
+Generate HLS playlist files as well. The master playlist is generated with the filename master.m3u8.
254
+One media playlist file is generated for each stream with filenames media_0.m3u8, media_1.m3u8, etc.
252 255
 @item -adaptation_sets @var{adaptation_sets}
253 256
 Assign streams to AdaptationSets. Syntax is "id=x,streams=a,b,c id=y,streams=d,e" with x and y being the IDs
254 257
 of the adaptation sets and a,b,c,d and e are the indices of the mapped streams.
... ...
@@ -135,7 +135,7 @@ OBJS-$(CONFIG_CONCAT_DEMUXER)            += concatdec.o
135 135
 OBJS-$(CONFIG_CRC_MUXER)                 += crcenc.o
136 136
 OBJS-$(CONFIG_DATA_DEMUXER)              += rawdec.o
137 137
 OBJS-$(CONFIG_DATA_MUXER)                += rawenc.o
138
-OBJS-$(CONFIG_DASH_MUXER)                += dash.o dashenc.o
138
+OBJS-$(CONFIG_DASH_MUXER)                += dash.o dashenc.o hlsplaylist.o
139 139
 OBJS-$(CONFIG_DASH_DEMUXER)              += dash.o dashdec.o
140 140
 OBJS-$(CONFIG_DAUD_DEMUXER)              += dauddec.o
141 141
 OBJS-$(CONFIG_DAUD_MUXER)                += daudenc.o
... ...
@@ -36,6 +36,7 @@
36 36
 #include "avc.h"
37 37
 #include "avformat.h"
38 38
 #include "avio_internal.h"
39
+#include "hlsplaylist.h"
39 40
 #include "internal.h"
40 41
 #include "isom.h"
41 42
 #include "os_support.h"
... ...
@@ -101,6 +102,8 @@ typedef struct DASHContext {
101 101
     const char *media_seg_name;
102 102
     const char *utc_timing_url;
103 103
     const char *user_agent;
104
+    int hls_playlist;
105
+    int master_playlist_created;
104 106
 } DASHContext;
105 107
 
106 108
 static struct codec_string {
... ...
@@ -217,6 +220,14 @@ static void set_http_options(AVDictionary **options, DASHContext *c)
217 217
         av_dict_set(options, "user_agent", c->user_agent, 0);
218 218
 }
219 219
 
220
+static void get_hls_playlist_name(char *playlist_name, int string_size,
221
+                                  const char *base_url, int id) {
222
+    if (base_url)
223
+        snprintf(playlist_name, string_size, "%smedia_%d.m3u8", base_url, id);
224
+    else
225
+        snprintf(playlist_name, string_size, "media_%d.m3u8", id);
226
+}
227
+
220 228
 static int flush_init_segment(AVFormatContext *s, OutputStream *os)
221 229
 {
222 230
     DASHContext *c = s->priv_data;
... ...
@@ -262,7 +273,8 @@ static void dash_free(AVFormatContext *s)
262 262
     av_freep(&c->streams);
263 263
 }
264 264
 
265
-static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c)
265
+static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext *c,
266
+                                int representation_id, int final)
266 267
 {
267 268
     int i, start_index = 0, start_number = 1;
268 269
     if (c->window_size) {
... ...
@@ -322,6 +334,55 @@ static void output_segment_list(OutputStream *os, AVIOContext *out, DASHContext
322 322
         }
323 323
         avio_printf(out, "\t\t\t\t</SegmentList>\n");
324 324
     }
325
+    if (c->hls_playlist && start_index < os->nb_segments)
326
+    {
327
+        int timescale = os->ctx->streams[0]->time_base.den;
328
+        char temp_filename_hls[1024];
329
+        char filename_hls[1024];
330
+        AVIOContext *out_hls = NULL;
331
+        AVDictionary *http_opts = NULL;
332
+        int target_duration = 0;
333
+        const char *proto = avio_find_protocol_name(c->dirname);
334
+        int use_rename = proto && !strcmp(proto, "file");
335
+
336
+        get_hls_playlist_name(filename_hls, sizeof(filename_hls),
337
+                              c->dirname, representation_id);
338
+
339
+        snprintf(temp_filename_hls, sizeof(temp_filename_hls), use_rename ? "%s.tmp" : "%s", filename_hls);
340
+
341
+        set_http_options(&http_opts, c);
342
+        avio_open2(&out_hls, temp_filename_hls, AVIO_FLAG_WRITE, NULL, &http_opts);
343
+        av_dict_free(&http_opts);
344
+        for (i = start_index; i < os->nb_segments; i++) {
345
+            Segment *seg = os->segments[i];
346
+            double duration = (double) seg->duration / timescale;
347
+            if (target_duration <= duration)
348
+                target_duration = hls_get_int_from_double(duration);
349
+        }
350
+
351
+        ff_hls_write_playlist_header(out_hls, 6, -1, target_duration,
352
+                                     start_number, PLAYLIST_TYPE_NONE);
353
+
354
+        ff_hls_write_init_file(out_hls, os->initfile, c->single_file,
355
+                               os->init_range_length, os->init_start_pos);
356
+
357
+        for (i = start_index; i < os->nb_segments; i++) {
358
+            Segment *seg = os->segments[i];
359
+            ff_hls_write_file_entry(out_hls, 0, c->single_file,
360
+                                    (double) seg->duration / timescale, 0,
361
+                                    seg->range_length, seg->start_pos, NULL,
362
+                                    c->single_file ? os->initfile : seg->file,
363
+                                    NULL);
364
+        }
365
+
366
+        if (final)
367
+            ff_hls_write_end_list(out_hls);
368
+
369
+        avio_close(out_hls);
370
+        if (use_rename)
371
+            avpriv_io_move(temp_filename_hls, filename_hls);
372
+    }
373
+
325 374
 }
326 375
 
327 376
 static char *xmlescape(const char *str) {
... ...
@@ -391,7 +452,8 @@ static void format_date_now(char *buf, int size)
391 391
     }
392 392
 }
393 393
 
394
-static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index)
394
+static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index,
395
+                                int final)
395 396
 {
396 397
     DASHContext *c = s->priv_data;
397 398
     AdaptationSet *as = &c->as[as_index];
... ...
@@ -430,7 +492,7 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind
430 430
             avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n",
431 431
                 s->streams[i]->codecpar->channels);
432 432
         }
433
-        output_segment_list(os, out, c);
433
+        output_segment_list(os, out, c, i, final);
434 434
         avio_printf(out, "\t\t\t</Representation>\n");
435 435
     }
436 436
     avio_printf(out, "\t\t</AdaptationSet>\n");
... ...
@@ -650,7 +712,7 @@ static int write_manifest(AVFormatContext *s, int final)
650 650
     }
651 651
 
652 652
     for (i = 0; i < c->nb_as; i++) {
653
-        if ((ret = write_adaptation_set(s, out, i)) < 0)
653
+        if ((ret = write_adaptation_set(s, out, i, final)) < 0)
654 654
             return ret;
655 655
     }
656 656
     avio_printf(out, "\t</Period>\n");
... ...
@@ -662,8 +724,43 @@ static int write_manifest(AVFormatContext *s, int final)
662 662
     avio_flush(out);
663 663
     ff_format_io_close(s, &out);
664 664
 
665
-    if (use_rename)
666
-        return avpriv_io_move(temp_filename, s->filename);
665
+    if (use_rename) {
666
+        if ((ret = avpriv_io_move(temp_filename, s->filename)) < 0)
667
+            return ret;
668
+    }
669
+
670
+    if (c->hls_playlist && !c->master_playlist_created) {
671
+        char filename_hls[1024];
672
+
673
+        if (*c->dirname)
674
+            snprintf(filename_hls, sizeof(filename_hls), "%s/master.m3u8", c->dirname);
675
+        else
676
+            snprintf(filename_hls, sizeof(filename_hls), "master.m3u8");
677
+
678
+        snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", filename_hls);
679
+
680
+        set_http_options(&opts, c);
681
+        ret = avio_open2(&out, temp_filename, AVIO_FLAG_WRITE, NULL, &opts);
682
+        if (ret < 0) {
683
+            av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
684
+            return ret;
685
+        }
686
+        av_dict_free(&opts);
687
+
688
+        ff_hls_write_playlist_version(out, 6);
689
+
690
+        for (i = 0; i < s->nb_streams; i++) {
691
+            char playlist_file[64];
692
+            AVStream *st = s->streams[i];
693
+            get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
694
+            ff_hls_write_stream_info(st, out, st->codecpar->bit_rate, playlist_file);
695
+        }
696
+        avio_close(out);
697
+        if (use_rename)
698
+            if ((ret = avpriv_io_move(temp_filename, filename_hls)) < 0)
699
+                return ret;
700
+        c->master_playlist_created = 1;
701
+    }
667 702
 
668 703
     return 0;
669 704
 }
... ...
@@ -1206,6 +1303,7 @@ static const AVOption options[] = {
1206 1206
     { "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 },
1207 1207
     { "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 },
1208 1208
     { "http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
1209
+    { "hls_playlist", "Generate HLS playlist files(master.m3u8, media_%d.m3u8)", OFFSET(hls_playlist), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, E },
1209 1210
     { NULL },
1210 1211
 };
1211 1212