Browse code

avfomat/hlsenc: support fmp4 format in hls

add the fmp4 format into hlsenc
because the fmp4 format add into hls from version 7.
the spec link is:
https://tools.ietf.org/html/draft-pantos-http-live-streaming-20
and the describe on WWDC
https://developer.apple.com/videos/play/wwdc2017/515/

Signed-off-by: Steven Liu <lq@onvideo.cn>

Steven Liu authored on 2017/07/03 21:20:44
Showing 2 changed files
... ...
@@ -614,6 +614,23 @@ in the playlist.
614 614
 Hex-coded 16byte initialization vector for every segment instead
615 615
 of the autogenerated ones.
616 616
 
617
+@item hls_segment_type @var{flags}
618
+Possible values:
619
+
620
+@table @samp
621
+@item mpegts
622
+If this flag is set, the hls segment files will format to mpegts.
623
+the mpegts files is used in all hls versions.
624
+
625
+@item fmp4
626
+If this flag is set, the hls segment files will format to fragment mp4 looks like dash.
627
+the fmp4 files is used in hls after version 7.
628
+
629
+@end table
630
+
631
+@item hls_fmp4_init_filename @var{filename}
632
+set filename to the fragment files header file, default filename is @file{init.mp4}.
633
+
617 634
 @item hls_flags @var{flags}
618 635
 Possible values:
619 636
 
... ...
@@ -88,6 +88,11 @@ typedef enum HLSFlags {
88 88
 } HLSFlags;
89 89
 
90 90
 typedef enum {
91
+    SEGMENT_TYPE_MPEGTS,
92
+    SEGMENT_TYPE_FMP4,
93
+} SegmentType;
94
+
95
+typedef enum {
91 96
     PLAYLIST_TYPE_NONE,
92 97
     PLAYLIST_TYPE_EVENT,
93 98
     PLAYLIST_TYPE_VOD,
... ...
@@ -115,6 +120,9 @@ typedef struct HLSContext {
115 115
     uint32_t flags;        // enum HLSFlags
116 116
     uint32_t pl_type;      // enum PlaylistType
117 117
     char *segment_filename;
118
+    char *fmp4_init_filename;
119
+    int segment_type;
120
+    int fmp4_init_mode;
118 121
 
119 122
     int use_localtime;      ///< flag to expand filename with localtime
120 123
     int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename
... ...
@@ -256,6 +264,16 @@ fail:
256 256
     return -1;
257 257
 }
258 258
 
259
+static void write_styp(AVIOContext *pb)
260
+{
261
+    avio_wb32(pb, 24);
262
+    ffio_wfourcc(pb, "styp");
263
+    ffio_wfourcc(pb, "msdh");
264
+    avio_wb32(pb, 0); /* minor */
265
+    ffio_wfourcc(pb, "msdh");
266
+    ffio_wfourcc(pb, "msix");
267
+}
268
+
259 269
 static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) {
260 270
 
261 271
     HLSSegment *segment, *previous_segment = NULL;
... ...
@@ -508,6 +526,7 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
508 508
 
509 509
 static int hls_mux_init(AVFormatContext *s)
510 510
 {
511
+    AVDictionary *options = NULL;
511 512
     HLSContext *hls = s->priv_data;
512 513
     AVFormatContext *oc;
513 514
     AVFormatContext *vtt_oc = NULL;
... ...
@@ -553,7 +572,35 @@ static int hls_mux_init(AVFormatContext *s)
553 553
     }
554 554
     hls->start_pos = 0;
555 555
     hls->new_start = 1;
556
+    hls->fmp4_init_mode = 0;
556 557
 
558
+    if (hls->segment_type == SEGMENT_TYPE_FMP4) {
559
+        hls->fmp4_init_mode = 1;
560
+        if ((ret = s->io_open(s, &oc->pb, hls->fmp4_init_filename, AVIO_FLAG_WRITE, NULL)) < 0) {
561
+            av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", hls->fmp4_init_filename);
562
+            return ret;
563
+        }
564
+
565
+        if (hls->format_options_str) {
566
+            ret = av_dict_parse_string(&hls->format_options, hls->format_options_str, "=", ":", 0);
567
+            if (ret < 0) {
568
+                av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n",
569
+                       hls->format_options_str);
570
+                return ret;
571
+            }
572
+        }
573
+
574
+        av_dict_copy(&options, hls->format_options, 0);
575
+        av_dict_set(&options, "fflags", "-autobsf", 0);
576
+        av_dict_set(&options, "movflags", "frag_custom+dash+delay_moov", 0);
577
+        ret = avformat_init_output(oc, &options);
578
+        if (av_dict_count(options)) {
579
+            av_log(s, AV_LOG_ERROR, "Some of the provided format options in '%s' are not recognized\n", hls->format_options_str);
580
+            av_dict_free(&options);
581
+            return AVERROR(EINVAL);
582
+        }
583
+        av_dict_free(&options);
584
+    }
557 585
     return 0;
558 586
 }
559 587
 
... ...
@@ -922,6 +969,9 @@ static void write_m3u8_head_block(HLSContext *hls, AVIOContext *out, int version
922 922
     }
923 923
     avio_printf(out, "#EXT-X-TARGETDURATION:%d\n", target_duration);
924 924
     avio_printf(out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
925
+    if (hls->segment_type == SEGMENT_TYPE_FMP4) {
926
+        avio_printf(out, "#EXT-X-MAP:URI=\"%s\"\n", hls->fmp4_init_filename);
927
+    }
925 928
     av_log(hls, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
926 929
 }
927 930
 
... ...
@@ -961,6 +1011,10 @@ static int hls_window(AVFormatContext *s, int last)
961 961
         sequence = 0;
962 962
     }
963 963
 
964
+    if (hls->segment_type == SEGMENT_TYPE_FMP4) {
965
+        version = 7;
966
+    }
967
+
964 968
     if (!use_rename && !warned_non_file++)
965 969
         av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporary partial files\n");
966 970
 
... ...
@@ -1194,15 +1248,19 @@ static int hls_start(AVFormatContext *s)
1194 1194
     }
1195 1195
     av_dict_free(&options);
1196 1196
 
1197
-    /* We only require one PAT/PMT per segment. */
1198
-    if (oc->oformat->priv_class && oc->priv_data) {
1199
-        char period[21];
1197
+    if (c->segment_type == SEGMENT_TYPE_FMP4) {
1198
+            write_styp(oc->pb);
1199
+    } else {
1200
+        /* We only require one PAT/PMT per segment. */
1201
+        if (oc->oformat->priv_class && oc->priv_data) {
1202
+            char period[21];
1200 1203
 
1201
-        snprintf(period, sizeof(period), "%d", (INT_MAX / 2) - 1);
1204
+            snprintf(period, sizeof(period), "%d", (INT_MAX / 2) - 1);
1202 1205
 
1203
-        av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
1204
-        av_opt_set(oc->priv_data, "sdt_period", period, 0);
1205
-        av_opt_set(oc->priv_data, "pat_period", period, 0);
1206
+            av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
1207
+            av_opt_set(oc->priv_data, "sdt_period", period, 0);
1208
+            av_opt_set(oc->priv_data, "pat_period", period, 0);
1209
+        }
1206 1210
     }
1207 1211
 
1208 1212
     if (c->vtt_basename) {
... ...
@@ -1289,7 +1347,11 @@ static int hls_write_header(AVFormatContext *s)
1289 1289
                "More than a single video stream present, "
1290 1290
                "expect issues decoding it.\n");
1291 1291
 
1292
-    hls->oformat = av_guess_format("mpegts", NULL, NULL);
1292
+    if (hls->segment_type == SEGMENT_TYPE_FMP4) {
1293
+        hls->oformat = av_guess_format("mp4", NULL, NULL);
1294
+    } else {
1295
+        hls->oformat = av_guess_format("mpegts", NULL, NULL);
1296
+    }
1293 1297
 
1294 1298
     if (!hls->oformat) {
1295 1299
         ret = AVERROR_MUXER_NOT_FOUND;
... ...
@@ -1390,8 +1452,10 @@ static int hls_write_header(AVFormatContext *s)
1390 1390
         }
1391 1391
     }
1392 1392
 
1393
-    if ((ret = hls_start(s)) < 0)
1394
-        goto fail;
1393
+    if (hls->segment_type != SEGMENT_TYPE_FMP4) {
1394
+        if ((ret = hls_start(s)) < 0)
1395
+            goto fail;
1396
+    }
1395 1397
 
1396 1398
     av_dict_copy(&options, hls->format_options, 0);
1397 1399
     ret = avformat_write_header(hls->avf, &options);
... ...
@@ -1448,7 +1512,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
1448 1448
     AVStream *st = s->streams[pkt->stream_index];
1449 1449
     int64_t end_pts = hls->recording_time * hls->number;
1450 1450
     int is_ref_pkt = 1;
1451
-    int ret, can_split = 1;
1451
+    int ret = 0, can_split = 1;
1452 1452
     int stream_index = 0;
1453 1453
 
1454 1454
     if (hls->sequence - hls->nb_entries > hls->start_sequence && hls->init_time > 0) {
... ...
@@ -1495,7 +1559,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
1495 1495
         }
1496 1496
 
1497 1497
     }
1498
-    if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
1498
+    if (hls->fmp4_init_mode || can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
1499 1499
                                    end_pts, AV_TIME_BASE_Q) >= 0) {
1500 1500
         int64_t new_start_pos;
1501 1501
         char *old_filename = av_strdup(hls->avf->filename);
... ...
@@ -1505,7 +1569,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
1505 1505
             return AVERROR(ENOMEM);
1506 1506
         }
1507 1507
 
1508
-        av_write_frame(oc, NULL); /* Flush any buffered data */
1508
+        av_write_frame(hls->avf, NULL); /* Flush any buffered data */
1509 1509
 
1510 1510
         new_start_pos = avio_tell(hls->avf->pb);
1511 1511
         hls->size = new_start_pos - hls->start_pos;
... ...
@@ -1518,12 +1582,18 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
1518 1518
         }
1519 1519
         if ((hls->flags & HLS_TEMP_FILE) && oc->filename[0]) {
1520 1520
             if (!(hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size <= 0))
1521
-                if (hls->avf->oformat->priv_class && hls->avf->priv_data)
1521
+                if ((hls->avf->oformat->priv_class && hls->avf->priv_data) && hls->segment_type != SEGMENT_TYPE_FMP4)
1522 1522
                     av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0);
1523 1523
             hls_rename_temp_file(s, oc);
1524 1524
         }
1525 1525
 
1526
-        ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
1526
+        if (hls->fmp4_init_mode) {
1527
+            hls->number--;
1528
+        }
1529
+
1530
+        if (!hls->fmp4_init_mode)
1531
+            ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
1532
+
1527 1533
         hls->start_pos = new_start_pos;
1528 1534
         if (ret < 0) {
1529 1535
             av_free(old_filename);
... ...
@@ -1533,6 +1603,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
1533 1533
         hls->end_pts = pkt->pts;
1534 1534
         hls->duration = 0;
1535 1535
 
1536
+        hls->fmp4_init_mode = 0;
1536 1537
         if (hls->flags & HLS_SINGLE_FILE) {
1537 1538
             hls->number++;
1538 1539
         } else if (hls->max_seg_size > 0) {
... ...
@@ -1640,6 +1711,10 @@ static const AVOption options[] = {
1640 1640
     {"hls_enc_key_url",    "url to access the key to decrypt the segments", OFFSET(key_url),      AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
1641 1641
     {"hls_enc_iv",    "hex-coded 16 byte initialization vector", OFFSET(iv),      AV_OPT_TYPE_STRING, .flags = E},
1642 1642
     {"hls_subtitle_path",     "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,    E},
1643
+    {"hls_segment_type",     "set hls segment files type", OFFSET(segment_type), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "segment_type"},
1644
+    {"fmp4",   "make segment file to fragment mp4 files in m3u8", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_FMP4 }, 0, UINT_MAX,   E, "segment_type"},
1645
+    {"mpegts",   "make segment file to mpegts files in m3u8", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_MPEGTS }, 0, UINT_MAX,   E, "segment_type"},
1646
+    {"hls_fmp4_init_filename", "set fragment mp4 file init filename", OFFSET(fmp4_init_filename),   AV_OPT_TYPE_STRING, {.str = "init.mp4"},            0,       0,         E},
1643 1647
     {"hls_flags",     "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},
1644 1648
     {"single_file",   "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX,   E, "flags"},
1645 1649
     {"temp_file", "write segment to temporary file and rename when complete", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_TEMP_FILE }, 0, UINT_MAX,   E, "flags"},
... ...
@@ -1682,7 +1757,7 @@ AVOutputFormat ff_hls_muxer = {
1682 1682
     .audio_codec    = AV_CODEC_ID_AAC,
1683 1683
     .video_codec    = AV_CODEC_ID_H264,
1684 1684
     .subtitle_codec = AV_CODEC_ID_WEBVTT,
1685
-    .flags          = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH,
1685
+    .flags          = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH,
1686 1686
     .write_header   = hls_write_header,
1687 1687
     .write_packet   = hls_write_packet,
1688 1688
     .write_trailer  = hls_write_trailer,