提交 274bd167 编写于 作者: S Steven Liu

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: NSteven Liu <lq@onvideo.cn>
上级 64ecb78b
...@@ -614,6 +614,23 @@ in the playlist. ...@@ -614,6 +614,23 @@ in the playlist.
Hex-coded 16byte initialization vector for every segment instead Hex-coded 16byte initialization vector for every segment instead
of the autogenerated ones. of the autogenerated ones.
@item hls_segment_type @var{flags}
Possible values:
@table @samp
@item mpegts
If this flag is set, the hls segment files will format to mpegts.
the mpegts files is used in all hls versions.
@item fmp4
If this flag is set, the hls segment files will format to fragment mp4 looks like dash.
the fmp4 files is used in hls after version 7.
@end table
@item hls_fmp4_init_filename @var{filename}
set filename to the fragment files header file, default filename is @file{init.mp4}.
@item hls_flags @var{flags} @item hls_flags @var{flags}
Possible values: Possible values:
......
...@@ -87,6 +87,11 @@ typedef enum HLSFlags { ...@@ -87,6 +87,11 @@ typedef enum HLSFlags {
HLS_TEMP_FILE = (1 << 11), HLS_TEMP_FILE = (1 << 11),
} HLSFlags; } HLSFlags;
typedef enum {
SEGMENT_TYPE_MPEGTS,
SEGMENT_TYPE_FMP4,
} SegmentType;
typedef enum { typedef enum {
PLAYLIST_TYPE_NONE, PLAYLIST_TYPE_NONE,
PLAYLIST_TYPE_EVENT, PLAYLIST_TYPE_EVENT,
...@@ -115,6 +120,9 @@ typedef struct HLSContext { ...@@ -115,6 +120,9 @@ typedef struct HLSContext {
uint32_t flags; // enum HLSFlags uint32_t flags; // enum HLSFlags
uint32_t pl_type; // enum PlaylistType uint32_t pl_type; // enum PlaylistType
char *segment_filename; char *segment_filename;
char *fmp4_init_filename;
int segment_type;
int fmp4_init_mode;
int use_localtime; ///< flag to expand filename with localtime int use_localtime; ///< flag to expand filename with localtime
int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename
...@@ -256,6 +264,16 @@ fail: ...@@ -256,6 +264,16 @@ fail:
return -1; return -1;
} }
static void write_styp(AVIOContext *pb)
{
avio_wb32(pb, 24);
ffio_wfourcc(pb, "styp");
ffio_wfourcc(pb, "msdh");
avio_wb32(pb, 0); /* minor */
ffio_wfourcc(pb, "msdh");
ffio_wfourcc(pb, "msix");
}
static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) { static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) {
HLSSegment *segment, *previous_segment = NULL; HLSSegment *segment, *previous_segment = NULL;
...@@ -508,6 +526,7 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) ...@@ -508,6 +526,7 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
static int hls_mux_init(AVFormatContext *s) static int hls_mux_init(AVFormatContext *s)
{ {
AVDictionary *options = NULL;
HLSContext *hls = s->priv_data; HLSContext *hls = s->priv_data;
AVFormatContext *oc; AVFormatContext *oc;
AVFormatContext *vtt_oc = NULL; AVFormatContext *vtt_oc = NULL;
...@@ -553,7 +572,35 @@ static int hls_mux_init(AVFormatContext *s) ...@@ -553,7 +572,35 @@ static int hls_mux_init(AVFormatContext *s)
} }
hls->start_pos = 0; hls->start_pos = 0;
hls->new_start = 1; hls->new_start = 1;
hls->fmp4_init_mode = 0;
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
hls->fmp4_init_mode = 1;
if ((ret = s->io_open(s, &oc->pb, hls->fmp4_init_filename, AVIO_FLAG_WRITE, NULL)) < 0) {
av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", hls->fmp4_init_filename);
return ret;
}
if (hls->format_options_str) {
ret = av_dict_parse_string(&hls->format_options, hls->format_options_str, "=", ":", 0);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n",
hls->format_options_str);
return ret;
}
}
av_dict_copy(&options, hls->format_options, 0);
av_dict_set(&options, "fflags", "-autobsf", 0);
av_dict_set(&options, "movflags", "frag_custom+dash+delay_moov", 0);
ret = avformat_init_output(oc, &options);
if (av_dict_count(options)) {
av_log(s, AV_LOG_ERROR, "Some of the provided format options in '%s' are not recognized\n", hls->format_options_str);
av_dict_free(&options);
return AVERROR(EINVAL);
}
av_dict_free(&options);
}
return 0; return 0;
} }
...@@ -922,6 +969,9 @@ static void write_m3u8_head_block(HLSContext *hls, AVIOContext *out, int version ...@@ -922,6 +969,9 @@ static void write_m3u8_head_block(HLSContext *hls, AVIOContext *out, int version
} }
avio_printf(out, "#EXT-X-TARGETDURATION:%d\n", target_duration); avio_printf(out, "#EXT-X-TARGETDURATION:%d\n", target_duration);
avio_printf(out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); avio_printf(out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
avio_printf(out, "#EXT-X-MAP:URI=\"%s\"\n", hls->fmp4_init_filename);
}
av_log(hls, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); av_log(hls, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
} }
...@@ -961,6 +1011,10 @@ static int hls_window(AVFormatContext *s, int last) ...@@ -961,6 +1011,10 @@ static int hls_window(AVFormatContext *s, int last)
sequence = 0; sequence = 0;
} }
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
version = 7;
}
if (!use_rename && !warned_non_file++) if (!use_rename && !warned_non_file++)
av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporary partial files\n"); av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporary partial files\n");
...@@ -1194,15 +1248,19 @@ static int hls_start(AVFormatContext *s) ...@@ -1194,15 +1248,19 @@ static int hls_start(AVFormatContext *s)
} }
av_dict_free(&options); av_dict_free(&options);
/* We only require one PAT/PMT per segment. */ if (c->segment_type == SEGMENT_TYPE_FMP4) {
if (oc->oformat->priv_class && oc->priv_data) { write_styp(oc->pb);
char period[21]; } else {
/* We only require one PAT/PMT per segment. */
if (oc->oformat->priv_class && oc->priv_data) {
char period[21];
snprintf(period, sizeof(period), "%d", (INT_MAX / 2) - 1); snprintf(period, sizeof(period), "%d", (INT_MAX / 2) - 1);
av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0); av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
av_opt_set(oc->priv_data, "sdt_period", period, 0); av_opt_set(oc->priv_data, "sdt_period", period, 0);
av_opt_set(oc->priv_data, "pat_period", period, 0); av_opt_set(oc->priv_data, "pat_period", period, 0);
}
} }
if (c->vtt_basename) { if (c->vtt_basename) {
...@@ -1289,7 +1347,11 @@ static int hls_write_header(AVFormatContext *s) ...@@ -1289,7 +1347,11 @@ static int hls_write_header(AVFormatContext *s)
"More than a single video stream present, " "More than a single video stream present, "
"expect issues decoding it.\n"); "expect issues decoding it.\n");
hls->oformat = av_guess_format("mpegts", NULL, NULL); if (hls->segment_type == SEGMENT_TYPE_FMP4) {
hls->oformat = av_guess_format("mp4", NULL, NULL);
} else {
hls->oformat = av_guess_format("mpegts", NULL, NULL);
}
if (!hls->oformat) { if (!hls->oformat) {
ret = AVERROR_MUXER_NOT_FOUND; ret = AVERROR_MUXER_NOT_FOUND;
...@@ -1390,8 +1452,10 @@ static int hls_write_header(AVFormatContext *s) ...@@ -1390,8 +1452,10 @@ static int hls_write_header(AVFormatContext *s)
} }
} }
if ((ret = hls_start(s)) < 0) if (hls->segment_type != SEGMENT_TYPE_FMP4) {
goto fail; if ((ret = hls_start(s)) < 0)
goto fail;
}
av_dict_copy(&options, hls->format_options, 0); av_dict_copy(&options, hls->format_options, 0);
ret = avformat_write_header(hls->avf, &options); ret = avformat_write_header(hls->avf, &options);
...@@ -1448,7 +1512,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1448,7 +1512,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
AVStream *st = s->streams[pkt->stream_index]; AVStream *st = s->streams[pkt->stream_index];
int64_t end_pts = hls->recording_time * hls->number; int64_t end_pts = hls->recording_time * hls->number;
int is_ref_pkt = 1; int is_ref_pkt = 1;
int ret, can_split = 1; int ret = 0, can_split = 1;
int stream_index = 0; int stream_index = 0;
if (hls->sequence - hls->nb_entries > hls->start_sequence && hls->init_time > 0) { 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,7 +1559,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
} }
} }
if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, if (hls->fmp4_init_mode || can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
end_pts, AV_TIME_BASE_Q) >= 0) { end_pts, AV_TIME_BASE_Q) >= 0) {
int64_t new_start_pos; int64_t new_start_pos;
char *old_filename = av_strdup(hls->avf->filename); char *old_filename = av_strdup(hls->avf->filename);
...@@ -1505,7 +1569,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1505,7 +1569,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
} }
av_write_frame(oc, NULL); /* Flush any buffered data */ av_write_frame(hls->avf, NULL); /* Flush any buffered data */
new_start_pos = avio_tell(hls->avf->pb); new_start_pos = avio_tell(hls->avf->pb);
hls->size = new_start_pos - hls->start_pos; hls->size = new_start_pos - hls->start_pos;
...@@ -1518,12 +1582,18 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1518,12 +1582,18 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
} }
if ((hls->flags & HLS_TEMP_FILE) && oc->filename[0]) { if ((hls->flags & HLS_TEMP_FILE) && oc->filename[0]) {
if (!(hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size <= 0)) if (!(hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size <= 0))
if (hls->avf->oformat->priv_class && hls->avf->priv_data) if ((hls->avf->oformat->priv_class && hls->avf->priv_data) && hls->segment_type != SEGMENT_TYPE_FMP4)
av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0); av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0);
hls_rename_temp_file(s, oc); hls_rename_temp_file(s, oc);
} }
ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size); if (hls->fmp4_init_mode) {
hls->number--;
}
if (!hls->fmp4_init_mode)
ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
hls->start_pos = new_start_pos; hls->start_pos = new_start_pos;
if (ret < 0) { if (ret < 0) {
av_free(old_filename); av_free(old_filename);
...@@ -1533,6 +1603,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1533,6 +1603,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
hls->end_pts = pkt->pts; hls->end_pts = pkt->pts;
hls->duration = 0; hls->duration = 0;
hls->fmp4_init_mode = 0;
if (hls->flags & HLS_SINGLE_FILE) { if (hls->flags & HLS_SINGLE_FILE) {
hls->number++; hls->number++;
} else if (hls->max_seg_size > 0) { } else if (hls->max_seg_size > 0) {
...@@ -1640,6 +1711,10 @@ static const AVOption options[] = { ...@@ -1640,6 +1711,10 @@ static const AVOption options[] = {
{"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}, {"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},
{"hls_enc_iv", "hex-coded 16 byte initialization vector", OFFSET(iv), AV_OPT_TYPE_STRING, .flags = E}, {"hls_enc_iv", "hex-coded 16 byte initialization vector", OFFSET(iv), AV_OPT_TYPE_STRING, .flags = E},
{"hls_subtitle_path", "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_subtitle_path", "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
{"hls_segment_type", "set hls segment files type", OFFSET(segment_type), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "segment_type"},
{"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"},
{"mpegts", "make segment file to mpegts files in m3u8", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_MPEGTS }, 0, UINT_MAX, E, "segment_type"},
{"hls_fmp4_init_filename", "set fragment mp4 file init filename", OFFSET(fmp4_init_filename), AV_OPT_TYPE_STRING, {.str = "init.mp4"}, 0, 0, E},
{"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"}, {"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},
{"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"}, {"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"},
{"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"}, {"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,7 +1757,7 @@ AVOutputFormat ff_hls_muxer = {
.audio_codec = AV_CODEC_ID_AAC, .audio_codec = AV_CODEC_ID_AAC,
.video_codec = AV_CODEC_ID_H264, .video_codec = AV_CODEC_ID_H264,
.subtitle_codec = AV_CODEC_ID_WEBVTT, .subtitle_codec = AV_CODEC_ID_WEBVTT,
.flags = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH, .flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH,
.write_header = hls_write_header, .write_header = hls_write_header,
.write_packet = hls_write_packet, .write_packet = hls_write_packet,
.write_trailer = hls_write_trailer, .write_trailer = hls_write_trailer,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册