Skip to content

Commit

Permalink
avfomat/hlsenc: support fmp4 format in hls
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
T-bagwell committed Jul 3, 2017
1 parent 64ecb78 commit 274bd16
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 16 deletions.
17 changes: 17 additions & 0 deletions doc/muxers.texi
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,23 @@ in the playlist.
Hex-coded 16byte initialization vector for every segment instead
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}
Possible values:

Expand Down
107 changes: 91 additions & 16 deletions libavformat/hlsenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ typedef enum HLSFlags {
HLS_TEMP_FILE = (1 << 11),
} HLSFlags;

typedef enum {
SEGMENT_TYPE_MPEGTS,
SEGMENT_TYPE_FMP4,
} SegmentType;

typedef enum {
PLAYLIST_TYPE_NONE,
PLAYLIST_TYPE_EVENT,
Expand Down Expand Up @@ -115,6 +120,9 @@ typedef struct HLSContext {
uint32_t flags; // enum HLSFlags
uint32_t pl_type; // enum PlaylistType
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_mkdir;///< flag to mkdir dirname in timebased filename
Expand Down Expand Up @@ -256,6 +264,16 @@ static int replace_int_data_in_filename(char *buf, int buf_size, const char *fil
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) {

HLSSegment *segment, *previous_segment = NULL;
Expand Down Expand Up @@ -508,6 +526,7 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)

static int hls_mux_init(AVFormatContext *s)
{
AVDictionary *options = NULL;
HLSContext *hls = s->priv_data;
AVFormatContext *oc;
AVFormatContext *vtt_oc = NULL;
Expand Down Expand Up @@ -553,7 +572,35 @@ static int hls_mux_init(AVFormatContext *s)
}
hls->start_pos = 0;
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;
}

Expand Down Expand Up @@ -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-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);
}

Expand Down Expand Up @@ -961,6 +1011,10 @@ static int hls_window(AVFormatContext *s, int last)
sequence = 0;
}

if (hls->segment_type == SEGMENT_TYPE_FMP4) {
version = 7;
}

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");

Expand Down Expand Up @@ -1194,15 +1248,19 @@ static int hls_start(AVFormatContext *s)
}
av_dict_free(&options);

/* We only require one PAT/PMT per segment. */
if (oc->oformat->priv_class && oc->priv_data) {
char period[21];
if (c->segment_type == SEGMENT_TYPE_FMP4) {
write_styp(oc->pb);
} 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, "sdt_period", period, 0);
av_opt_set(oc->priv_data, "pat_period", period, 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, "pat_period", period, 0);
}
}

if (c->vtt_basename) {
Expand Down Expand Up @@ -1289,7 +1347,11 @@ static int hls_write_header(AVFormatContext *s)
"More than a single video stream present, "
"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) {
ret = AVERROR_MUXER_NOT_FOUND;
Expand Down Expand Up @@ -1390,8 +1452,10 @@ static int hls_write_header(AVFormatContext *s)
}
}

if ((ret = hls_start(s)) < 0)
goto fail;
if (hls->segment_type != SEGMENT_TYPE_FMP4) {
if ((ret = hls_start(s)) < 0)
goto fail;
}

av_dict_copy(&options, hls->format_options, 0);
ret = avformat_write_header(hls->avf, &options);
Expand Down Expand Up @@ -1448,7 +1512,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
AVStream *st = s->streams[pkt->stream_index];
int64_t end_pts = hls->recording_time * hls->number;
int is_ref_pkt = 1;
int ret, can_split = 1;
int ret = 0, can_split = 1;
int stream_index = 0;

if (hls->sequence - hls->nb_entries > hls->start_sequence && hls->init_time > 0) {
Expand Down Expand Up @@ -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) {
int64_t new_start_pos;
char *old_filename = av_strdup(hls->avf->filename);
Expand All @@ -1505,7 +1569,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
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);
hls->size = new_start_pos - hls->start_pos;
Expand All @@ -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_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);
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;
if (ret < 0) {
av_free(old_filename);
Expand All @@ -1533,6 +1603,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
hls->end_pts = pkt->pts;
hls->duration = 0;

hls->fmp4_init_mode = 0;
if (hls->flags & HLS_SINGLE_FILE) {
hls->number++;
} else if (hls->max_seg_size > 0) {
Expand Down Expand Up @@ -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_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_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"},
{"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"},
Expand Down Expand Up @@ -1682,7 +1757,7 @@ AVOutputFormat ff_hls_muxer = {
.audio_codec = AV_CODEC_ID_AAC,
.video_codec = AV_CODEC_ID_H264,
.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_packet = hls_write_packet,
.write_trailer = hls_write_trailer,
Expand Down

0 comments on commit 274bd16

Please sign in to comment.