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>
... | ... |
@@ -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, |