X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fhlsenc.c;h=8cd5ed5937cfa4083d23572388f55bde1cc2483b;hb=462a14b1e9373bc4e4c535b0417eae5773a6172a;hp=5ea9d216a4aa5c98abc183151b2a46da68f19fac;hpb=37d5c6c30d38f8e2fe0a57afec58b0ae539c307d;p=ffmpeg diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 5ea9d216a4a..8cd5ed5937c 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -1,6 +1,7 @@ /* * Apple HTTP Live Streaming segmenter * Copyright (c) 2012, Luca Barbato + * Copyright (c) 2017 Akamai Technologies, Inc. * * This file is part of FFmpeg. * @@ -56,6 +57,7 @@ typedef enum { #define KEYSIZE 16 #define LINE_BUFFER_SIZE 1024 #define HLS_MICROSECOND_UNIT 1000000 +#define POSTFIX_PATTERN "_%d" typedef struct HLSSegment { char filename[1024]; @@ -100,35 +102,18 @@ typedef enum { PLAYLIST_TYPE_NB, } PlaylistType; -typedef struct HLSContext { - const AVClass *class; // Class for private options. +typedef struct VariantStream { unsigned number; int64_t sequence; - int64_t start_sequence; - uint32_t start_sequence_source_type; // enum StartSequenceSourceType AVOutputFormat *oformat; AVOutputFormat *vtt_oformat; + AVIOContext *out; + int packets_written; + int init_range_length; AVFormatContext *avf; AVFormatContext *vtt_avf; - float time; // Set by a private option. - float init_time; // Set by a private option. - int max_nb_segments; // Set by a private option. -#if FF_API_HLS_WRAP - int wrap; // Set by a private option. -#endif - 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 - int allowcache; - int64_t recording_time; int has_video; int has_subtitle; int new_start; @@ -138,7 +123,6 @@ typedef struct HLSContext { double duration; // last segment duration computed so far, in seconds int64_t start_pos; // last segment starting position int64_t size; // last segment size - int64_t max_seg_size; // every segment file max size int nb_entries; int discontinuity_set; int discontinuity; @@ -148,9 +132,46 @@ typedef struct HLSContext { HLSSegment *old_segments; char *basename; - char *base_output_dirname; char *vtt_basename; char *vtt_m3u8_name; + char *m3u8_name; + + double initial_prog_date_time; + char current_segment_final_filename_fmt[1024]; // when renaming segments + + char *fmp4_init_filename; + char *base_output_dirname; + int fmp4_init_mode; + + AVStream **streams; + unsigned int nb_streams; + int m3u8_created; /* status of media play-list creation */ + char *baseurl; +} VariantStream; + +typedef struct HLSContext { + const AVClass *class; // Class for private options. + int64_t start_sequence; + uint32_t start_sequence_source_type; // enum StartSequenceSourceType + + float time; // Set by a private option. + float init_time; // Set by a private option. + int max_nb_segments; // Set by a private option. +#if FF_API_HLS_WRAP + int wrap; // Set by a private option. +#endif + uint32_t flags; // enum HLSFlags + uint32_t pl_type; // enum PlaylistType + char *segment_filename; + char *fmp4_init_filename; + int segment_type; + + int use_localtime; ///< flag to expand filename with localtime + int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename + int allowcache; + int64_t recording_time; + int64_t max_seg_size; // every segment file max size + char *baseurl; char *format_options_str; char *vtt_format_options_str; @@ -162,6 +183,7 @@ typedef struct HLSContext { char *key_url; char *iv; char *key_basename; + int encrypt_started; char *key_info_file; char key_file[LINE_BUFFER_SIZE + 1]; @@ -171,10 +193,17 @@ typedef struct HLSContext { AVDictionary *vtt_format_options; char *method; - - double initial_prog_date_time; - char current_segment_final_filename_fmt[1024]; // when renaming segments char *user_agent; + + VariantStream *var_streams; + unsigned int nb_varstreams; + + int master_m3u8_created; /* status of master play-list creation */ + char *master_m3u8_url; /* URL of the master m3u8 file */ + int version; /* HLS version */ + char *var_stream_map; /* user specified variant stream map string */ + char *master_pl_name; + unsigned int master_publish_rate; } HLSContext; static int get_int_from_double(double val) @@ -293,7 +322,8 @@ static void write_styp(AVIOContext *pb) ffio_wfourcc(pb, "msix"); } -static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) { +static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls, + VariantStream *vs) { HLSSegment *segment, *previous_segment = NULL; float playlist_duration = 0.0f; @@ -304,13 +334,13 @@ static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) { AVIOContext *out = NULL; const char *proto = NULL; - segment = hls->segments; + segment = vs->segments; while (segment) { playlist_duration += segment->duration; segment = segment->next; } - segment = hls->old_segments; + segment = vs->old_segments; while (segment) { playlist_duration -= segment->duration; previous_segment = segment; @@ -325,7 +355,7 @@ static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) { if (hls->segment_filename) { dirname = av_strdup(hls->segment_filename); } else { - dirname = av_strdup(hls->avf->filename); + dirname = av_strdup(vs->avf->filename); } if (!dirname) { ret = AVERROR(ENOMEM); @@ -355,9 +385,9 @@ static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) { proto = avio_find_protocol_name(s->filename); if (hls->method || (proto && !av_strcasecmp(proto, "http"))) { av_dict_set(&options, "method", "DELETE", 0); - if ((ret = hls->avf->io_open(hls->avf, &out, path, AVIO_FLAG_WRITE, &options)) < 0) + if ((ret = vs->avf->io_open(vs->avf, &out, path, AVIO_FLAG_WRITE, &options)) < 0) goto fail; - ff_format_io_close(hls->avf, &out); + ff_format_io_close(vs->avf, &out); } else if (unlink(path) < 0) { av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n", path, strerror(errno)); @@ -376,11 +406,11 @@ static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls) { if (hls->method || (proto && !av_strcasecmp(proto, "http"))) { av_dict_set(&options, "method", "DELETE", 0); - if ((ret = hls->avf->io_open(hls->avf, &out, sub_path, AVIO_FLAG_WRITE, &options)) < 0) { + if ((ret = vs->avf->io_open(vs->avf, &out, sub_path, AVIO_FLAG_WRITE, &options)) < 0) { av_free(sub_path); goto fail; } - ff_format_io_close(hls->avf, &out); + ff_format_io_close(vs->avf, &out); } else if (unlink(sub_path) < 0) { av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n", sub_path, strerror(errno)); @@ -414,7 +444,7 @@ static int randomize(uint8_t *buf, int len) return AVERROR(EINVAL); } -static int do_encrypt(AVFormatContext *s) +static int do_encrypt(AVFormatContext *s, VariantStream *vs) { HLSContext *hls = s->priv_data; int ret; @@ -422,7 +452,7 @@ static int do_encrypt(AVFormatContext *s) AVIOContext *pb; uint8_t key[KEYSIZE]; - len = strlen(hls->basename) + 4 + 1; + len = strlen(s->filename) + 4 + 1; hls->key_basename = av_mallocz(len); if (!hls->key_basename) return AVERROR(ENOMEM); @@ -443,7 +473,7 @@ static int do_encrypt(AVFormatContext *s) char buf[33]; if (!hls->iv) { - AV_WB64(iv + 8, hls->sequence); + AV_WB64(iv + 8, vs->sequence); } else { memcpy(iv, hls->iv, sizeof(iv)); } @@ -543,7 +573,7 @@ static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) return len; } -static int hls_mux_init(AVFormatContext *s) +static int hls_mux_init(AVFormatContext *s, VariantStream *vs) { AVDictionary *options = NULL; HLSContext *hls = s->priv_data; @@ -552,13 +582,13 @@ static int hls_mux_init(AVFormatContext *s) int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0); int i, ret; - ret = avformat_alloc_output_context2(&hls->avf, hls->oformat, NULL, NULL); + ret = avformat_alloc_output_context2(&vs->avf, vs->oformat, NULL, NULL); if (ret < 0) return ret; - oc = hls->avf; + oc = vs->avf; oc->filename[0] = '\0'; - oc->oformat = hls->oformat; + oc->oformat = vs->oformat; oc->interrupt_callback = s->interrupt_callback; oc->max_delay = s->max_delay; oc->opaque = s->opaque; @@ -566,51 +596,59 @@ static int hls_mux_init(AVFormatContext *s) oc->io_close = s->io_close; av_dict_copy(&oc->metadata, s->metadata, 0); - if(hls->vtt_oformat) { - ret = avformat_alloc_output_context2(&hls->vtt_avf, hls->vtt_oformat, NULL, NULL); + if(vs->vtt_oformat) { + ret = avformat_alloc_output_context2(&vs->vtt_avf, vs->vtt_oformat, NULL, NULL); if (ret < 0) return ret; - vtt_oc = hls->vtt_avf; - vtt_oc->oformat = hls->vtt_oformat; + vtt_oc = vs->vtt_avf; + vtt_oc->oformat = vs->vtt_oformat; av_dict_copy(&vtt_oc->metadata, s->metadata, 0); } - for (i = 0; i < s->nb_streams; i++) { + for (i = 0; i < vs->nb_streams; i++) { AVStream *st; AVFormatContext *loc; - if (s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) + if (vs->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) loc = vtt_oc; else loc = oc; if (!(st = avformat_new_stream(loc, NULL))) return AVERROR(ENOMEM); - avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar); + avcodec_parameters_copy(st->codecpar, vs->streams[i]->codecpar); if (!oc->oformat->codec_tag || - av_codec_get_id (oc->oformat->codec_tag, s->streams[i]->codecpar->codec_tag) == st->codecpar->codec_id || - av_codec_get_tag(oc->oformat->codec_tag, s->streams[i]->codecpar->codec_id) <= 0) { - st->codecpar->codec_tag = s->streams[i]->codecpar->codec_tag; + av_codec_get_id (oc->oformat->codec_tag, vs->streams[i]->codecpar->codec_tag) == st->codecpar->codec_id || + av_codec_get_tag(oc->oformat->codec_tag, vs->streams[i]->codecpar->codec_id) <= 0) { + st->codecpar->codec_tag = vs->streams[i]->codecpar->codec_tag; } else { st->codecpar->codec_tag = 0; } - st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; - st->time_base = s->streams[i]->time_base; - av_dict_copy(&st->metadata, s->streams[i]->metadata, 0); + st->sample_aspect_ratio = vs->streams[i]->sample_aspect_ratio; + st->time_base = vs->streams[i]->time_base; + av_dict_copy(&st->metadata, vs->streams[i]->metadata, 0); } - hls->start_pos = 0; - hls->new_start = 1; - hls->fmp4_init_mode = 0; + + vs->packets_written = 1; + vs->start_pos = 0; + vs->new_start = 1; + vs->fmp4_init_mode = 0; if (hls->segment_type == SEGMENT_TYPE_FMP4) { if (hls->max_seg_size > 0) { av_log(s, AV_LOG_WARNING, "Multi-file byterange mode is currently unsupported in the HLS muxer.\n"); return AVERROR_PATCHWELCOME; } - hls->fmp4_init_mode = !byterange_mode; + + vs->packets_written = 0; + vs->init_range_length = 0; + vs->fmp4_init_mode = !byterange_mode; set_http_options(s, &options, hls); - if ((ret = s->io_open(s, &oc->pb, hls->base_output_dirname, AVIO_FLAG_WRITE, &options)) < 0) { - av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", hls->fmp4_init_filename); + if ((ret = avio_open_dyn_buf(&oc->pb)) < 0) + return ret; + + if ((ret = s->io_open(s, &vs->out, vs->base_output_dirname, AVIO_FLAG_WRITE, &options)) < 0) { + av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", vs->fmp4_init_filename); return ret; } @@ -634,6 +672,7 @@ static int hls_mux_init(AVFormatContext *s) av_dict_free(&options); return AVERROR(EINVAL); } + avio_flush(oc->pb); av_dict_free(&options); } return 0; @@ -649,19 +688,20 @@ static HLSSegment *find_segment_by_filename(HLSSegment *segment, const char *fil return (HLSSegment *) NULL; } -static int sls_flags_filename_process(struct AVFormatContext *s, HLSContext *hls, HLSSegment *en, double duration, - int64_t pos, int64_t size) +static int sls_flags_filename_process(struct AVFormatContext *s, HLSContext *hls, + VariantStream *vs, HLSSegment *en, + double duration, int64_t pos, int64_t size) { if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) && - strlen(hls->current_segment_final_filename_fmt)) { - av_strlcpy(hls->avf->filename, hls->current_segment_final_filename_fmt, sizeof(hls->avf->filename)); + strlen(vs->current_segment_final_filename_fmt)) { + av_strlcpy(vs->avf->filename, vs->current_segment_final_filename_fmt, sizeof(vs->avf->filename)); if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) { - char * filename = av_strdup(hls->avf->filename); // %%s will be %s after strftime + char * filename = av_strdup(vs->avf->filename); // %%s will be %s after strftime if (!filename) { av_free(en); return AVERROR(ENOMEM); } - if (replace_int_data_in_filename(hls->avf->filename, sizeof(hls->avf->filename), + if (replace_int_data_in_filename(vs->avf->filename, sizeof(vs->avf->filename), filename, 's', pos + size) < 1) { av_log(hls, AV_LOG_ERROR, "Invalid second level segment filename template '%s', " @@ -674,12 +714,12 @@ static int sls_flags_filename_process(struct AVFormatContext *s, HLSContext *hls av_free(filename); } if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) { - char * filename = av_strdup(hls->avf->filename); // %%t will be %t after strftime + char * filename = av_strdup(vs->avf->filename); // %%t will be %t after strftime if (!filename) { av_free(en); return AVERROR(ENOMEM); } - if (replace_int_data_in_filename(hls->avf->filename, sizeof(hls->avf->filename), + if (replace_int_data_in_filename(vs->avf->filename, sizeof(vs->avf->filename), filename, 't', (int64_t)round(duration * HLS_MICROSECOND_UNIT)) < 1) { av_log(hls, AV_LOG_ERROR, "Invalid second level segment filename template '%s', " @@ -718,9 +758,9 @@ static int sls_flag_check_duration_size_index(HLSContext *hls) return ret; } -static int sls_flag_check_duration_size(HLSContext *hls) +static int sls_flag_check_duration_size(HLSContext *hls, VariantStream *vs) { - const char *proto = avio_find_protocol_name(hls->basename); + const char *proto = avio_find_protocol_name(vs->basename); int segment_renaming_ok = proto && !strcmp(proto, "file"); int ret = 0; @@ -738,14 +778,14 @@ static int sls_flag_check_duration_size(HLSContext *hls) return ret; } -static void sls_flag_file_rename(HLSContext *hls, char *old_filename) { +static void sls_flag_file_rename(HLSContext *hls, VariantStream *vs, char *old_filename) { if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) && - strlen(hls->current_segment_final_filename_fmt)) { - ff_rename(old_filename, hls->avf->filename, hls); + strlen(vs->current_segment_final_filename_fmt)) { + ff_rename(old_filename, vs->avf->filename, hls); } } -static int sls_flag_use_localtime_filename(AVFormatContext *oc, HLSContext *c) +static int sls_flag_use_localtime_filename(AVFormatContext *oc, HLSContext *c, VariantStream *vs) { if (c->flags & HLS_SECOND_LEVEL_SEGMENT_INDEX) { char * filename = av_strdup(oc->filename); // %%d will be %d after strftime @@ -753,9 +793,9 @@ static int sls_flag_use_localtime_filename(AVFormatContext *oc, HLSContext *c) return AVERROR(ENOMEM); if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), #if FF_API_HLS_WRAP - filename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { + filename, 'd', c->wrap ? vs->sequence % c->wrap : vs->sequence) < 1) { #else - filename, 'd', c->sequence) < 1) { + filename, 'd', vs->sequence) < 1) { #endif av_log(c, AV_LOG_ERROR, "Invalid second level segment filename template '%s', " "you can try to remove second_level_segment_index flag\n", @@ -766,8 +806,8 @@ static int sls_flag_use_localtime_filename(AVFormatContext *oc, HLSContext *c) av_free(filename); } if (c->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) { - av_strlcpy(c->current_segment_final_filename_fmt, oc->filename, - sizeof(c->current_segment_final_filename_fmt)); + av_strlcpy(vs->current_segment_final_filename_fmt, oc->filename, + sizeof(vs->current_segment_final_filename_fmt)); if (c->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) { char * filename = av_strdup(oc->filename); // %%s will be %s after strftime if (!filename) @@ -799,8 +839,9 @@ static int sls_flag_use_localtime_filename(AVFormatContext *oc, HLSContext *c) } /* Create a new segment and append it to the segment list */ -static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration, - int64_t pos, int64_t size) +static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, + VariantStream *vs, double duration, int64_t pos, + int64_t size) { HLSSegment *en = av_malloc(sizeof(*en)); const char *filename; @@ -810,24 +851,24 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double if (!en) return AVERROR(ENOMEM); - ret = sls_flags_filename_process(s, hls, en, duration, pos, size); + ret = sls_flags_filename_process(s, hls, vs, en, duration, pos, size); if (ret < 0) { return ret; } - filename = av_basename(hls->avf->filename); + filename = av_basename(vs->avf->filename); if (hls->use_localtime_mkdir) { - filename = hls->avf->filename; + filename = vs->avf->filename; } - if ((find_segment_by_filename(hls->segments, filename) || find_segment_by_filename(hls->old_segments, filename)) + if ((find_segment_by_filename(vs->segments, filename) || find_segment_by_filename(vs->old_segments, filename)) && !byterange_mode) { av_log(hls, AV_LOG_WARNING, "Duplicated segment filename detected: %s\n", filename); } av_strlcpy(en->filename, filename, sizeof(en->filename)); - if(hls->has_subtitle) - av_strlcpy(en->sub_filename, av_basename(hls->vtt_avf->filename), sizeof(en->sub_filename)); + if(vs->has_subtitle) + av_strlcpy(en->sub_filename, av_basename(vs->vtt_avf->filename), sizeof(en->sub_filename)); else en->sub_filename[0] = '\0'; @@ -837,9 +878,9 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double en->next = NULL; en->discont = 0; - if (hls->discontinuity) { + if (vs->discontinuity) { en->discont = 1; - hls->discontinuity = 0; + vs->discontinuity = 0; } if (hls->key_info_file || hls->encrypt) { @@ -847,45 +888,45 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string)); } - if (!hls->segments) - hls->segments = en; + if (!vs->segments) + vs->segments = en; else - hls->last_segment->next = en; + vs->last_segment->next = en; - hls->last_segment = en; + vs->last_segment = en; // EVENT or VOD playlists imply sliding window cannot be used if (hls->pl_type != PLAYLIST_TYPE_NONE) hls->max_nb_segments = 0; - if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) { - en = hls->segments; - hls->initial_prog_date_time += en->duration; - hls->segments = en->next; + if (hls->max_nb_segments && vs->nb_entries >= hls->max_nb_segments) { + en = vs->segments; + vs->initial_prog_date_time += en->duration; + vs->segments = en->next; if (en && hls->flags & HLS_DELETE_SEGMENTS && #if FF_API_HLS_WRAP !(hls->flags & HLS_SINGLE_FILE || hls->wrap)) { #else !(hls->flags & HLS_SINGLE_FILE)) { #endif - en->next = hls->old_segments; - hls->old_segments = en; - if ((ret = hls_delete_old_segments(s, hls)) < 0) + en->next = vs->old_segments; + vs->old_segments = en; + if ((ret = hls_delete_old_segments(s, hls, vs)) < 0) return ret; } else av_free(en); } else - hls->nb_entries++; + vs->nb_entries++; if (hls->max_seg_size > 0) { return 0; } - hls->sequence++; + vs->sequence++; return 0; } -static int parse_playlist(AVFormatContext *s, const char *url) +static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs) { HLSContext *hls = s->priv_data; AVIOContext *in; @@ -906,26 +947,26 @@ static int parse_playlist(AVFormatContext *s, const char *url) goto fail; } - hls->discontinuity = 0; + vs->discontinuity = 0; while (!avio_feof(in)) { read_chomp_line(in, line, sizeof(line)); if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { int64_t tmp_sequence = strtoll(ptr, NULL, 10); - if (tmp_sequence < hls->sequence) + if (tmp_sequence < vs->sequence) av_log(hls, AV_LOG_VERBOSE, "Found playlist sequence number was smaller """ "than specified start sequence number: %"PRId64" < %"PRId64", " "omitting\n", tmp_sequence, hls->start_sequence); else { av_log(hls, AV_LOG_DEBUG, "Found playlist sequence number: %"PRId64"\n", tmp_sequence); - hls->sequence = tmp_sequence; + vs->sequence = tmp_sequence; } } else if (av_strstart(line, "#EXT-X-DISCONTINUITY", &ptr)) { is_segment = 1; - hls->discontinuity = 1; + vs->discontinuity = 1; } else if (av_strstart(line, "#EXTINF:", &ptr)) { is_segment = 1; - hls->duration = atof(ptr); + vs->duration = atof(ptr); } else if (av_stristart(line, "#EXT-X-KEY:", &ptr)) { ptr = av_stristr(line, "URI=\""); if (ptr) { @@ -954,13 +995,13 @@ static int parse_playlist(AVFormatContext *s, const char *url) } else if (line[0]) { if (is_segment) { is_segment = 0; - new_start_pos = avio_tell(hls->avf->pb); - hls->size = new_start_pos - hls->start_pos; - av_strlcpy(hls->avf->filename, line, sizeof(line)); - ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size); + new_start_pos = avio_tell(vs->avf->pb); + vs->size = new_start_pos - vs->start_pos; + av_strlcpy(vs->avf->filename, line, sizeof(line)); + ret = hls_append_segment(s, hls, vs, vs->duration, vs->start_pos, vs->size); if (ret < 0) goto fail; - hls->start_pos = new_start_pos; + vs->start_pos = new_start_pos; } } } @@ -1005,7 +1046,127 @@ static void hls_rename_temp_file(AVFormatContext *s, AVFormatContext *oc) oc->filename[len-4] = '\0'; } -static int hls_window(AVFormatContext *s, int last) +static int get_relative_url(const char *master_url, const char *media_url, + char *rel_url, int rel_url_buf_size) +{ + char *p = NULL; + int base_len = -1; + p = strrchr(master_url, '/') ? strrchr(master_url, '/') :\ + strrchr(master_url, '\\'); + if (p) { + base_len = FFABS(p - master_url); + if (av_strncasecmp(master_url, media_url, base_len)) { + av_log(NULL, AV_LOG_WARNING, "Unable to find relative url\n"); + return AVERROR(EINVAL); + } + } + av_strlcpy(rel_url, &(media_url[base_len + 1]), rel_url_buf_size); + return 0; +} + +static int create_master_playlist(AVFormatContext *s, + VariantStream * const input_vs) +{ + HLSContext *hls = s->priv_data; + VariantStream *vs; + AVStream *vid_st, *aud_st; + AVIOContext *master_pb = 0; + AVDictionary *options = NULL; + unsigned int i, j; + int m3u8_name_size, ret, bandwidth; + char *m3U8_rel_name; + + input_vs->m3u8_created = 1; + if (!hls->master_m3u8_created) { + /* For the first time, wait until all the media playlists are created */ + for (i = 0; i < hls->nb_varstreams; i++) + if (!hls->var_streams[i].m3u8_created) + return 0; + } else { + /* Keep publishing the master playlist at the configured rate */ + if (&hls->var_streams[0] != input_vs || !hls->master_publish_rate || + input_vs->number % hls->master_publish_rate) + return 0; + } + + if (hls->user_agent) + av_dict_set(&options, "user-agent", hls->user_agent, 0); + + ret = s->io_open(s, &master_pb, hls->master_m3u8_url, AVIO_FLAG_WRITE,\ + &options); + av_dict_free(&options); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to open master play list file '%s'\n", + hls->master_m3u8_url); + goto fail; + } + + avio_printf(master_pb, "#EXTM3U\n"); + avio_printf(master_pb, "#EXT-X-VERSION:%d\n", hls->version); + + /* For variant streams with video add #EXT-X-STREAM-INF tag with attributes*/ + for (i = 0; i < hls->nb_varstreams; i++) { + vs = &(hls->var_streams[i]); + + m3u8_name_size = strlen(vs->m3u8_name) + 1; + m3U8_rel_name = av_malloc(m3u8_name_size); + if (!m3U8_rel_name) { + ret = AVERROR(ENOMEM); + goto fail; + } + av_strlcpy(m3U8_rel_name, vs->m3u8_name, m3u8_name_size); + ret = get_relative_url(hls->master_m3u8_url, vs->m3u8_name, + m3U8_rel_name, m3u8_name_size); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Unable to find relative URL\n"); + goto fail; + } + + vid_st = NULL; + aud_st = NULL; + for (j = 0; j < vs->nb_streams; j++) { + if (vs->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + vid_st = vs->streams[j]; + else if (vs->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) + aud_st = vs->streams[j]; + } + + if (!vid_st && !aud_st) { + av_log(NULL, AV_LOG_WARNING, "Media stream not found\n"); + continue; + } + + bandwidth = 0; + if (vid_st) + bandwidth += vid_st->codecpar->bit_rate; + if (aud_st) + bandwidth += aud_st->codecpar->bit_rate; + bandwidth += bandwidth / 10; + + if (!bandwidth) { + av_log(NULL, AV_LOG_WARNING, + "Bandwidth info not available, set audio and video bitrates\n"); + av_freep(&m3U8_rel_name); + continue; + } + + avio_printf(master_pb, "#EXT-X-STREAM-INF:BANDWIDTH=%d", bandwidth); + if (vid_st && vid_st->codecpar->width > 0 && vid_st->codecpar->height > 0) + avio_printf(master_pb, ",RESOLUTION=%dx%d", vid_st->codecpar->width, + vid_st->codecpar->height); + avio_printf(master_pb, "\n%s\n\n", m3U8_rel_name); + + av_freep(&m3U8_rel_name); + } +fail: + if(ret >=0) + hls->master_m3u8_created = 1; + av_freep(&m3U8_rel_name); + ff_format_io_close(s, &master_pb); + return ret; +} + +static int hls_window(AVFormatContext *s, int last, VariantStream *vs) { HLSContext *hls = s->priv_data; HLSSegment *en; @@ -1014,52 +1175,52 @@ static int hls_window(AVFormatContext *s, int last) AVIOContext *out = NULL; AVIOContext *sub_out = NULL; char temp_filename[1024]; - int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); - int version = 3; + int64_t sequence = FFMAX(hls->start_sequence, vs->sequence - vs->nb_entries); const char *proto = avio_find_protocol_name(s->filename); int use_rename = proto && !strcmp(proto, "file"); static unsigned warned_non_file; char *key_uri = NULL; char *iv_string = NULL; AVDictionary *options = NULL; - double prog_date_time = hls->initial_prog_date_time; + double prog_date_time = vs->initial_prog_date_time; int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0); + hls->version = 3; if (byterange_mode) { - version = 4; + hls->version = 4; sequence = 0; } if (hls->segment_type == SEGMENT_TYPE_FMP4) { - version = 7; + hls->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"); set_http_options(s, &options, hls); - snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", s->filename); + snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", vs->m3u8_name); if ((ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, &options)) < 0) goto fail; - for (en = hls->segments; en; en = en->next) { + for (en = vs->segments; en; en = en->next) { if (target_duration <= en->duration) target_duration = get_int_from_double(en->duration); } - hls->discontinuity_set = 0; - write_m3u8_head_block(hls, out, version, target_duration, sequence); + vs->discontinuity_set = 0; + write_m3u8_head_block(hls, out, hls->version, target_duration, sequence); if (hls->pl_type == PLAYLIST_TYPE_EVENT) { avio_printf(out, "#EXT-X-PLAYLIST-TYPE:EVENT\n"); } else if (hls->pl_type == PLAYLIST_TYPE_VOD) { avio_printf(out, "#EXT-X-PLAYLIST-TYPE:VOD\n"); } - if((hls->flags & HLS_DISCONT_START) && sequence==hls->start_sequence && hls->discontinuity_set==0 ){ + if((hls->flags & HLS_DISCONT_START) && sequence==hls->start_sequence && vs->discontinuity_set==0 ){ avio_printf(out, "#EXT-X-DISCONTINUITY\n"); - hls->discontinuity_set = 1; + vs->discontinuity_set = 1; } - for (en = hls->segments; en; en = en->next) { + for (en = vs->segments; en; en = en->next) { if ((hls->encrypt || hls->key_info_file) && (!key_uri || strcmp(en->key_uri, key_uri) || av_strcasecmp(en->iv_string, iv_string))) { avio_printf(out, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri); @@ -1074,8 +1235,8 @@ static int hls_window(AVFormatContext *s, int last) avio_printf(out, "#EXT-X-DISCONTINUITY\n"); } - if ((hls->segment_type == SEGMENT_TYPE_FMP4) && (en == hls->segments)) { - avio_printf(out, "#EXT-X-MAP:URI=\"%s\"", hls->fmp4_init_filename); + if ((hls->segment_type == SEGMENT_TYPE_FMP4) && (en == vs->segments)) { + avio_printf(out, "#EXT-X-MAP:URI=\"%s\"", vs->fmp4_init_filename); if (hls->flags & HLS_SINGLE_FILE) { avio_printf(out, ",BYTERANGE=\"%"PRId64"@%"PRId64"\"", en->size, en->pos); } @@ -1113,26 +1274,26 @@ static int hls_window(AVFormatContext *s, int last) avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s\n", buf0, milli, buf1); prog_date_time += en->duration; } - if (hls->baseurl) - avio_printf(out, "%s", hls->baseurl); + if (vs->baseurl) + avio_printf(out, "%s", vs->baseurl); avio_printf(out, "%s\n", en->filename); } if (last && (hls->flags & HLS_OMIT_ENDLIST)==0) avio_printf(out, "#EXT-X-ENDLIST\n"); - if( hls->vtt_m3u8_name ) { - if ((ret = s->io_open(s, &sub_out, hls->vtt_m3u8_name, AVIO_FLAG_WRITE, &options)) < 0) + if( vs->vtt_m3u8_name ) { + if ((ret = s->io_open(s, &sub_out, vs->vtt_m3u8_name, AVIO_FLAG_WRITE, &options)) < 0) goto fail; - write_m3u8_head_block(hls, sub_out, version, target_duration, sequence); + write_m3u8_head_block(hls, sub_out, hls->version, target_duration, sequence); - for (en = hls->segments; en; en = en->next) { + for (en = vs->segments; en; en = en->next) { avio_printf(sub_out, "#EXTINF:%f,\n", en->duration); if (byterange_mode) avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", en->size, en->pos); - if (hls->baseurl) - avio_printf(sub_out, "%s", hls->baseurl); + if (vs->baseurl) + avio_printf(sub_out, "%s", vs->baseurl); avio_printf(sub_out, "%s\n", en->sub_filename); } @@ -1146,33 +1307,38 @@ fail: ff_format_io_close(s, &out); ff_format_io_close(s, &sub_out); if (ret >= 0 && use_rename) - ff_rename(temp_filename, s->filename, s); + ff_rename(temp_filename, vs->m3u8_name, s); + + if (ret >= 0 && hls->master_pl_name) + if (create_master_playlist(s, vs) < 0) + av_log(s, AV_LOG_WARNING, "Master playlist creation failed\n"); + return ret; } -static int hls_start(AVFormatContext *s) +static int hls_start(AVFormatContext *s, VariantStream *vs) { HLSContext *c = s->priv_data; - AVFormatContext *oc = c->avf; - AVFormatContext *vtt_oc = c->vtt_avf; + AVFormatContext *oc = vs->avf; + AVFormatContext *vtt_oc = vs->vtt_avf; AVDictionary *options = NULL; char *filename, iv_string[KEYSIZE*2 + 1]; int err = 0; if (c->flags & HLS_SINGLE_FILE) { - av_strlcpy(oc->filename, c->basename, + av_strlcpy(oc->filename, vs->basename, sizeof(oc->filename)); - if (c->vtt_basename) - av_strlcpy(vtt_oc->filename, c->vtt_basename, + if (vs->vtt_basename) + av_strlcpy(vtt_oc->filename, vs->vtt_basename, sizeof(vtt_oc->filename)); } else if (c->max_seg_size > 0) { if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), #if FF_API_HLS_WRAP - c->basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { + vs->basename, 'd', c->wrap ? vs->sequence % c->wrap : vs->sequence) < 1) { #else - c->basename, 'd', c->sequence) < 1) { + vs->basename, 'd', vs->sequence) < 1) { #endif - av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s', you can try to use -use_localtime 1 with it\n", c->basename); + av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s', you can try to use -use_localtime 1 with it\n", vs->basename); return AVERROR(EINVAL); } } else { @@ -1181,12 +1347,12 @@ static int hls_start(AVFormatContext *s) struct tm *tm, tmpbuf; time(&now0); tm = localtime_r(&now0, &tmpbuf); - if (!strftime(oc->filename, sizeof(oc->filename), c->basename, tm)) { + if (!strftime(oc->filename, sizeof(oc->filename), vs->basename, tm)) { av_log(oc, AV_LOG_ERROR, "Could not get segment filename with use_localtime\n"); return AVERROR(EINVAL); } - err = sls_flag_use_localtime_filename(oc, c); + err = sls_flag_use_localtime_filename(oc, c, vs); if (err < 0) { return AVERROR(ENOMEM); } @@ -1207,26 +1373,26 @@ static int hls_start(AVFormatContext *s) } } else if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), #if FF_API_HLS_WRAP - c->basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { + vs->basename, 'd', c->wrap ? vs->sequence % c->wrap : vs->sequence) < 1) { #else - c->basename, 'd', c->sequence) < 1) { + vs->basename, 'd', vs->sequence) < 1) { #endif - av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try to use -use_localtime 1 with it\n", c->basename); + av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try to use -use_localtime 1 with it\n", vs->basename); return AVERROR(EINVAL); } - if( c->vtt_basename) { + if( vs->vtt_basename) { if (replace_int_data_in_filename(vtt_oc->filename, sizeof(vtt_oc->filename), #if FF_API_HLS_WRAP - c->vtt_basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { + vs->vtt_basename, 'd', c->wrap ? vs->sequence % c->wrap : vs->sequence) < 1) { #else - c->vtt_basename, 'd', c->sequence) < 1) { + vs->vtt_basename, 'd', vs->sequence) < 1) { #endif - av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->vtt_basename); + av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", vs->vtt_basename); return AVERROR(EINVAL); } } } - c->number++; + vs->number++; set_http_options(s, &options, c); @@ -1240,21 +1406,22 @@ static int hls_start(AVFormatContext *s) " will use -hls_key_info_file priority\n"); } - if (c->number <= 1 || (c->flags & HLS_PERIODIC_REKEY)) { + if (!c->encrypt_started || (c->flags & HLS_PERIODIC_REKEY)) { if (c->key_info_file) { if ((err = hls_encryption_start(s)) < 0) goto fail; } else { - if ((err = do_encrypt(s)) < 0) + if ((err = do_encrypt(s, vs)) < 0) goto fail; } + c->encrypt_started = 1; } if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0)) < 0) goto fail; err = av_strlcpy(iv_string, c->iv_string, sizeof(iv_string)); if (!err) - snprintf(iv_string, sizeof(iv_string), "%032"PRIx64, c->sequence); + snprintf(iv_string, sizeof(iv_string), "%032"PRIx64, vs->sequence); if ((err = av_dict_set(&options, "encryption_iv", iv_string, 0)) < 0) goto fail; @@ -1271,7 +1438,7 @@ static int hls_start(AVFormatContext *s) } else if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, &options)) < 0) goto fail; - if (c->vtt_basename) { + if (vs->vtt_basename) { set_http_options(s, &options, c); if ((err = s->io_open(s, &vtt_oc->pb, vtt_oc->filename, AVIO_FLAG_WRITE, &options)) < 0) goto fail; @@ -1293,7 +1460,7 @@ static int hls_start(AVFormatContext *s) } } - if (c->vtt_basename) { + if (vs->vtt_basename) { err = avformat_write_header(vtt_oc,NULL); if (err < 0) return err; @@ -1322,19 +1489,221 @@ static const char * get_default_pattern_localtime_fmt(AVFormatContext *s) return (HAVE_LIBC_MSVCRT || !strftime(b, sizeof(b), "%s", p) || !strcmp(b, "%s")) ? "-%Y%m%d%H%M%S.ts" : "-%s.ts"; } +static int format_name(char *name, int name_buf_len, int i) +{ + char *p; + char extension[10] = {'\0'}; + + p = strrchr(name, '.'); + if (p) { + av_strlcpy(extension, p, sizeof(extension)); + *p = '\0'; + } + + snprintf(name + strlen(name), name_buf_len - strlen(name), POSTFIX_PATTERN, i); + + if (strlen(extension)) + av_strlcat(name, extension, name_buf_len); + + return 0; +} + +static int get_nth_codec_stream_index(AVFormatContext *s, + enum AVMediaType codec_type, + int stream_id) +{ + unsigned int stream_index, cnt; + if (stream_id < 0 || stream_id > s->nb_streams - 1) + return -1; + cnt = 0; + for (stream_index = 0; stream_index < s->nb_streams; stream_index++) { + if (s->streams[stream_index]->codecpar->codec_type != codec_type) + continue; + if (cnt == stream_id) + return stream_index; + cnt++; + } + return -1; +} + +static int parse_variant_stream_mapstring(AVFormatContext *s) +{ + HLSContext *hls = s->priv_data; + VariantStream *vs; + int stream_index; + enum AVMediaType codec_type; + int nb_varstreams, nb_streams; + char *p, *q, *saveptr1, *saveptr2, *varstr, *keyval; + const char *val; + + /** + * Expected format for var_stream_map string is as below: + * "a:0,v:0 a:1,v:1" + * This string specifies how to group the audio, video and subtitle streams + * into different variant streams. The variant stream groups are separated + * by space. + * + * a:, v:, s: are keys to specify audio, video and subtitle streams + * respectively. Allowed values are 0 to 9 digits (limited just based on + * practical usage) + * + */ + p = av_strdup(hls->var_stream_map); + q = p; + while(av_strtok(q, " \t", &saveptr1)) { + q = NULL; + hls->nb_varstreams++; + } + av_freep(&p); + + hls->var_streams = av_mallocz(sizeof(*hls->var_streams) * hls->nb_varstreams); + if (!hls->var_streams) + return AVERROR(ENOMEM); + + p = hls->var_stream_map; + nb_varstreams = 0; + while (varstr = av_strtok(p, " \t", &saveptr1)) { + p = NULL; + + if (nb_varstreams < hls->nb_varstreams) + vs = &(hls->var_streams[nb_varstreams++]); + else + return AVERROR(EINVAL); + + q = varstr; + while (q < varstr + strlen(varstr)) { + if (!av_strncasecmp(q, "a:", 2) || !av_strncasecmp(q, "v:", 2) || + !av_strncasecmp(q, "s:", 2)) + vs->nb_streams++; + q++; + } + vs->streams = av_mallocz(sizeof(AVStream *) * vs->nb_streams); + if (!vs->streams) + return AVERROR(ENOMEM); + + nb_streams = 0; + while (keyval = av_strtok(varstr, ",", &saveptr2)) { + varstr = NULL; + + if (av_strstart(keyval, "v:", &val)) { + codec_type = AVMEDIA_TYPE_VIDEO; + } else if (av_strstart(keyval, "a:", &val)) { + codec_type = AVMEDIA_TYPE_AUDIO; + } else if (av_strstart(keyval, "s:", &val)) { + codec_type = AVMEDIA_TYPE_SUBTITLE; + } else { + av_log(s, AV_LOG_ERROR, "Invalid keyval %s\n", keyval); + return AVERROR(EINVAL); + } + + stream_index = -1; + if (av_isdigit(*val)) + stream_index = get_nth_codec_stream_index (s, codec_type, + atoi(val)); + + if (stream_index >= 0 && nb_streams < vs->nb_streams) { + vs->streams[nb_streams++] = s->streams[stream_index]; + } else { + av_log(s, AV_LOG_ERROR, "Unable to map stream at %s\n", keyval); + return AVERROR(EINVAL); + } + } + } + av_log(s, AV_LOG_DEBUG, "Number of variant streams %d\n", + hls->nb_varstreams); + + return 0; +} + +static int update_variant_stream_info(AVFormatContext *s) { + HLSContext *hls = s->priv_data; + unsigned int i; + + if (hls->var_stream_map) { + return parse_variant_stream_mapstring(s); + } else { + //By default, a single variant stream with all the codec streams is created + hls->nb_varstreams = 1; + hls->var_streams = av_mallocz(sizeof(*hls->var_streams) * + hls->nb_varstreams); + if (!hls->var_streams) + return AVERROR(ENOMEM); + + hls->var_streams[0].nb_streams = s->nb_streams; + hls->var_streams[0].streams = av_mallocz(sizeof(AVStream *) * + hls->var_streams[0].nb_streams); + if (!hls->var_streams[0].streams) + return AVERROR(ENOMEM); + + for (i = 0; i < s->nb_streams; i++) + hls->var_streams[0].streams[i] = s->streams[i]; + } + return 0; +} + +static int update_master_pl_info(AVFormatContext *s) { + HLSContext *hls = s->priv_data; + int m3u8_name_size, ret; + char *p; + + m3u8_name_size = strlen(s->filename) + strlen(hls->master_pl_name) + 1; + hls->master_m3u8_url = av_malloc(m3u8_name_size); + if (!hls->master_m3u8_url) { + ret = AVERROR(ENOMEM); + return ret; + } + + av_strlcpy(hls->master_m3u8_url, s->filename, m3u8_name_size); + p = strrchr(hls->master_m3u8_url, '/') ? + strrchr(hls->master_m3u8_url, '/') : + strrchr(hls->master_m3u8_url, '\\'); + if (p) { + *(p + 1) = '\0'; + av_strlcat(hls->master_m3u8_url, hls->master_pl_name, m3u8_name_size); + } else { + av_strlcpy(hls->master_m3u8_url, hls->master_pl_name, m3u8_name_size); + } + + return 0; +} + static int hls_write_header(AVFormatContext *s) { HLSContext *hls = s->priv_data; - int ret, i; + int ret, i, j; char *p = NULL; const char *pattern = "%d.ts"; const char *pattern_localtime_fmt = get_default_pattern_localtime_fmt(s); const char *vtt_pattern = "%d.vtt"; AVDictionary *options = NULL; int basename_size = 0; - int vtt_basename_size = 0; + int vtt_basename_size = 0, m3u8_name_size = 0; + VariantStream *vs = NULL; int fmp4_init_filename_len = strlen(hls->fmp4_init_filename) + 1; + ret = update_variant_stream_info(s); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Variant stream info update failed with status %x\n", + ret); + goto fail; + } + + //TODO: Updates needed to encryption functionality with periodic re-key when more than one variant streams are present + if (hls->nb_varstreams > 1 && hls->flags & HLS_PERIODIC_REKEY) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Periodic re-key not supported when more than one variant streams are present\n"); + goto fail; + } + + if (hls->master_pl_name) { + ret = update_master_pl_info(s); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Master stream info update failed with status %x\n", + ret); + goto fail; + } + } + if (hls->segment_type == SEGMENT_TYPE_FMP4) { pattern = "%d.m4s"; } @@ -1355,15 +1724,18 @@ static int hls_write_header(AVFormatContext *s) av_log(hls, AV_LOG_DEBUG, "start_number evaluated to %"PRId64"\n", hls->start_sequence); } - hls->sequence = hls->start_sequence; + for (i = 0; i < hls->nb_varstreams; i++) { + vs = &hls->var_streams[i]; + + vs->sequence = hls->start_sequence; hls->recording_time = (hls->init_time ? hls->init_time : hls->time) * AV_TIME_BASE; - hls->start_pts = AV_NOPTS_VALUE; - hls->current_segment_final_filename_fmt[0] = '\0'; + vs->start_pts = AV_NOPTS_VALUE; + vs->current_segment_final_filename_fmt[0] = '\0'; if (hls->flags & HLS_PROGRAM_DATE_TIME) { time_t now0; time(&now0); - hls->initial_prog_date_time = now0; + vs->initial_prog_date_time = now0; } if (hls->format_options_str) { @@ -1374,43 +1746,49 @@ static int hls_write_header(AVFormatContext *s) } } - for (i = 0; i < s->nb_streams; i++) { - hls->has_video += - s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO; - hls->has_subtitle += - s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE; + for (j = 0; j < vs->nb_streams; j++) { + vs->has_video += + vs->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO; + vs->has_subtitle += + vs->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE; } - if (hls->has_video > 1) + if (vs->has_video > 1) av_log(s, AV_LOG_WARNING, "More than a single video stream present, " "expect issues decoding it.\n"); if (hls->segment_type == SEGMENT_TYPE_FMP4) { - hls->oformat = av_guess_format("mp4", NULL, NULL); + vs->oformat = av_guess_format("mp4", NULL, NULL); } else { - hls->oformat = av_guess_format("mpegts", NULL, NULL); + vs->oformat = av_guess_format("mpegts", NULL, NULL); } - if (!hls->oformat) { + if (!vs->oformat) { ret = AVERROR_MUXER_NOT_FOUND; goto fail; } - if(hls->has_subtitle) { - hls->vtt_oformat = av_guess_format("webvtt", NULL, NULL); - if (!hls->oformat) { + if(vs->has_subtitle) { + vs->vtt_oformat = av_guess_format("webvtt", NULL, NULL); + if (!vs->oformat) { ret = AVERROR_MUXER_NOT_FOUND; goto fail; } } if (hls->segment_filename) { - hls->basename = av_strdup(hls->segment_filename); - if (!hls->basename) { + basename_size = strlen(hls->segment_filename) + 1; + if (hls->nb_varstreams > 1) { + basename_size += strlen(POSTFIX_PATTERN); + } + vs->basename = av_malloc(basename_size); + if (!vs->basename) { ret = AVERROR(ENOMEM); goto fail; } + + av_strlcpy(vs->basename, hls->segment_filename, basename_size); } else { if (hls->flags & HLS_SINGLE_FILE) { if (hls->segment_type == SEGMENT_TYPE_FMP4) { @@ -1425,52 +1803,96 @@ static int hls_write_header(AVFormatContext *s) } else { basename_size = strlen(s->filename) + strlen(pattern) + 1; } - hls->basename = av_malloc(basename_size); - if (!hls->basename) { + + if (hls->nb_varstreams > 1) { + basename_size += strlen(POSTFIX_PATTERN); + } + + vs->basename = av_malloc(basename_size); + if (!vs->basename) { ret = AVERROR(ENOMEM); goto fail; } - av_strlcpy(hls->basename, s->filename, basename_size); + av_strlcpy(vs->basename, s->filename, basename_size); - p = strrchr(hls->basename, '.'); + p = strrchr(vs->basename, '.'); if (p) *p = '\0'; if (hls->use_localtime) { - av_strlcat(hls->basename, pattern_localtime_fmt, basename_size); + av_strlcat(vs->basename, pattern_localtime_fmt, basename_size); } else { - av_strlcat(hls->basename, pattern, basename_size); + av_strlcat(vs->basename, pattern, basename_size); } } + m3u8_name_size = strlen(s->filename) + 1; + if (hls->nb_varstreams > 1) { + m3u8_name_size += strlen(POSTFIX_PATTERN); + } + + vs->m3u8_name = av_malloc(m3u8_name_size); + if (!vs->m3u8_name ) { + ret = AVERROR(ENOMEM); + goto fail; + } + + av_strlcpy(vs->m3u8_name, s->filename, m3u8_name_size); + + if (hls->nb_varstreams > 1) { + ret = format_name(vs->basename, basename_size, i); + if (ret < 0) + goto fail; + ret = format_name(vs->m3u8_name, m3u8_name_size, i); + if (ret < 0) + goto fail; + } + if (hls->segment_type == SEGMENT_TYPE_FMP4) { + if (hls->nb_varstreams > 1) + fmp4_init_filename_len += strlen(POSTFIX_PATTERN); + vs->fmp4_init_filename = av_malloc(fmp4_init_filename_len); + if (!vs->fmp4_init_filename ) { + ret = AVERROR(ENOMEM); + goto fail; + } + av_strlcpy(vs->fmp4_init_filename, hls->fmp4_init_filename, + fmp4_init_filename_len); + if (hls->nb_varstreams > 1) { + ret = format_name(vs->fmp4_init_filename, fmp4_init_filename_len, i); + if (ret < 0) + goto fail; + } + if (av_strcasecmp(hls->fmp4_init_filename, "init.mp4")) { - hls->base_output_dirname = av_malloc(fmp4_init_filename_len); - if (!hls->base_output_dirname) { + fmp4_init_filename_len = strlen(vs->fmp4_init_filename) + 1; + vs->base_output_dirname = av_malloc(fmp4_init_filename_len); + if (!vs->base_output_dirname) { ret = AVERROR(ENOMEM); goto fail; } - av_strlcpy(hls->base_output_dirname, hls->fmp4_init_filename, fmp4_init_filename_len); + av_strlcpy(vs->base_output_dirname, vs->fmp4_init_filename, + fmp4_init_filename_len); } else { - if (basename_size > 0) { - hls->base_output_dirname = av_malloc(basename_size); - } else { - hls->base_output_dirname = av_malloc(strlen(hls->fmp4_init_filename)); - } - if (!hls->base_output_dirname) { + fmp4_init_filename_len = strlen(vs->m3u8_name) + + strlen(vs->fmp4_init_filename) + 1; + + vs->base_output_dirname = av_malloc(fmp4_init_filename_len); + if (!vs->base_output_dirname) { ret = AVERROR(ENOMEM); goto fail; } - if (basename_size > 0) { - av_strlcpy(hls->base_output_dirname, s->filename, basename_size); - p = strrchr(hls->base_output_dirname, '/'); - } + av_strlcpy(vs->base_output_dirname, vs->m3u8_name, + fmp4_init_filename_len); + p = strrchr(vs->base_output_dirname, '/'); if (p) { *(p + 1) = '\0'; - av_strlcat(hls->base_output_dirname, hls->fmp4_init_filename, basename_size); + av_strlcat(vs->base_output_dirname, vs->fmp4_init_filename, + fmp4_init_filename_len); } else { - av_strlcpy(hls->base_output_dirname, hls->fmp4_init_filename, fmp4_init_filename_len); + av_strlcpy(vs->base_output_dirname, vs->fmp4_init_filename, + fmp4_init_filename_len); } } } @@ -1481,54 +1903,78 @@ static int hls_write_header(AVFormatContext *s) goto fail; } } else { - ret = sls_flag_check_duration_size(hls); + ret = sls_flag_check_duration_size(hls, vs); if (ret < 0) { goto fail; } } - if(hls->has_subtitle) { + if(vs->has_subtitle) { if (hls->flags & HLS_SINGLE_FILE) vtt_pattern = ".vtt"; vtt_basename_size = strlen(s->filename) + strlen(vtt_pattern) + 1; - hls->vtt_basename = av_malloc(vtt_basename_size); - if (!hls->vtt_basename) { + if (hls->nb_varstreams > 1) { + vtt_basename_size += strlen(POSTFIX_PATTERN); + } + + vs->vtt_basename = av_malloc(vtt_basename_size); + if (!vs->vtt_basename) { ret = AVERROR(ENOMEM); goto fail; } - hls->vtt_m3u8_name = av_malloc(vtt_basename_size); - if (!hls->vtt_m3u8_name ) { + vs->vtt_m3u8_name = av_malloc(vtt_basename_size); + if (!vs->vtt_m3u8_name ) { ret = AVERROR(ENOMEM); goto fail; } - av_strlcpy(hls->vtt_basename, s->filename, vtt_basename_size); - p = strrchr(hls->vtt_basename, '.'); + av_strlcpy(vs->vtt_basename, s->filename, vtt_basename_size); + p = strrchr(vs->vtt_basename, '.'); if (p) *p = '\0'; if( hls->subtitle_filename ) { - strcpy(hls->vtt_m3u8_name, hls->subtitle_filename); + strcpy(vs->vtt_m3u8_name, hls->subtitle_filename); } else { - strcpy(hls->vtt_m3u8_name, hls->vtt_basename); - av_strlcat(hls->vtt_m3u8_name, "_vtt.m3u8", vtt_basename_size); + strcpy(vs->vtt_m3u8_name, vs->vtt_basename); + av_strlcat(vs->vtt_m3u8_name, "_vtt.m3u8", vtt_basename_size); + } + av_strlcat(vs->vtt_basename, vtt_pattern, vtt_basename_size); + + if (hls->nb_varstreams > 1) { + ret= format_name(vs->vtt_basename, vtt_basename_size, i); + if (ret < 0) + goto fail; + ret = format_name(vs->vtt_m3u8_name, vtt_basename_size, i); + if (ret < 0) + goto fail; + } + } + + if (hls->baseurl) { + int baseurl_len; + baseurl_len = strlen(hls->baseurl); + vs->baseurl = av_malloc(baseurl_len); + if (!vs->baseurl) { + ret = AVERROR(ENOMEM); + goto fail; } - av_strlcat(hls->vtt_basename, vtt_pattern, vtt_basename_size); + av_strlcpy(vs->baseurl, hls->baseurl, baseurl_len); } if ((hls->flags & HLS_SINGLE_FILE) && (hls->segment_type == SEGMENT_TYPE_FMP4)) { - hls->fmp4_init_filename = av_strdup(hls->basename); - if (!hls->fmp4_init_filename) { + vs->fmp4_init_filename = av_strdup(vs->basename); + if (!vs->fmp4_init_filename) { ret = AVERROR(ENOMEM); goto fail; } } - if ((ret = hls_mux_init(s)) < 0) + if ((ret = hls_mux_init(s, vs)) < 0) goto fail; if (hls->flags & HLS_APPEND_LIST) { - parse_playlist(s, s->filename); - hls->discontinuity = 1; + parse_playlist(s, vs->m3u8_name, vs); + vs->discontinuity = 1; if (hls->init_time > 0) { av_log(s, AV_LOG_WARNING, "append_list mode does not support hls_init_time," " hls_init_time value will have no effect\n"); @@ -1538,21 +1984,23 @@ static int hls_write_header(AVFormatContext *s) } if (hls->segment_type != SEGMENT_TYPE_FMP4 || hls->flags & HLS_SINGLE_FILE) { - if ((ret = hls_start(s)) < 0) + if ((ret = hls_start(s, vs)) < 0) goto fail; } av_dict_copy(&options, hls->format_options, 0); - ret = avformat_write_header(hls->avf, &options); + ret = avformat_write_header(vs->avf, &options); if (av_dict_count(options)) { av_log(s, AV_LOG_ERROR, "Some of provided format options in '%s' are not recognized\n", hls->format_options_str); ret = AVERROR(EINVAL); + av_dict_free(&options); goto fail; } + av_dict_free(&options); //av_assert0(s->nb_streams == hls->avf->nb_streams); - for (i = 0; i < s->nb_streams; i++) { + for (j = 0; j < vs->nb_streams; j++) { AVStream *inner_st; - AVStream *outer_st = s->streams[i]; + AVStream *outer_st = vs->streams[j]; if (hls->max_seg_size > 0) { if ((outer_st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && @@ -1564,9 +2012,9 @@ static int hls_write_header(AVFormatContext *s) } if (outer_st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) - inner_st = hls->avf->streams[i]; - else if (hls->vtt_avf) - inner_st = hls->vtt_avf->streams[0]; + inner_st = vs->avf->streams[j]; + else if (vs->vtt_avf) + inner_st = vs->vtt_avf->streams[0]; else { /* We have a subtitle stream, when the user does not want one */ inner_st = NULL; @@ -1574,19 +2022,27 @@ static int hls_write_header(AVFormatContext *s) } avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den); } + } fail: - av_dict_free(&options); if (ret < 0) { - av_freep(&hls->fmp4_init_filename); - av_freep(&hls->basename); - av_freep(&hls->vtt_basename); av_freep(&hls->key_basename); - if (hls->avf) - avformat_free_context(hls->avf); - if (hls->vtt_avf) - avformat_free_context(hls->vtt_avf); - + for (i = 0; i < hls->nb_varstreams && hls->var_streams; i++) { + vs = &hls->var_streams[i]; + av_freep(&vs->basename); + av_freep(&vs->vtt_basename); + av_freep(&vs->fmp4_init_filename); + av_freep(&vs->m3u8_name); + av_freep(&vs->vtt_m3u8_name); + av_freep(&vs->streams); + av_freep(&vs->baseurl); + if (vs->avf) + avformat_free_context(vs->avf); + if (vs->vtt_avf) + avformat_free_context(vs->vtt_avf); + } + av_freep(&hls->var_streams); + av_freep(&hls->master_m3u8_url); } return ret; } @@ -1596,32 +2052,54 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) HLSContext *hls = s->priv_data; AVFormatContext *oc = NULL; AVStream *st = s->streams[pkt->stream_index]; - int64_t end_pts = hls->recording_time * hls->number; + int64_t end_pts = 0; int is_ref_pkt = 1; - int ret = 0, can_split = 1; + int ret = 0, can_split = 1, i, j; int stream_index = 0; + int range_length = 0; + uint8_t *buffer = NULL; + VariantStream *vs = NULL; + + for (i = 0; i < hls->nb_varstreams; i++) { + vs = &hls->var_streams[i]; + for (j = 0; j < vs->nb_streams; j++) { + if (vs->streams[j] == st) { + if( st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE ) { + oc = vs->vtt_avf; + stream_index = 0; + } else { + oc = vs->avf; + stream_index = j; + } + break; + } + } + + if (oc) + break; + } + + if (!oc) { + av_log(s, AV_LOG_ERROR, "Unable to find mapping variant stream\n"); + return AVERROR(ENOMEM); + } - if (hls->sequence - hls->nb_entries > hls->start_sequence && hls->init_time > 0) { + end_pts = hls->recording_time * vs->number; + + if (vs->sequence - vs->nb_entries > hls->start_sequence && hls->init_time > 0) { /* reset end_pts, hls->recording_time at end of the init hls list */ - int init_list_dur = hls->init_time * hls->nb_entries * AV_TIME_BASE; - int after_init_list_dur = (hls->sequence - hls->nb_entries ) * hls->time * AV_TIME_BASE; + int init_list_dur = hls->init_time * vs->nb_entries * AV_TIME_BASE; + int after_init_list_dur = (vs->sequence - vs->nb_entries ) * hls->time * AV_TIME_BASE; hls->recording_time = hls->time * AV_TIME_BASE; end_pts = init_list_dur + after_init_list_dur ; } - if( st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE ) { - oc = hls->vtt_avf; - stream_index = 0; - } else { - oc = hls->avf; - stream_index = pkt->stream_index; - } - if (hls->start_pts == AV_NOPTS_VALUE) { - hls->start_pts = pkt->pts; - hls->end_pts = pkt->pts; + if (vs->start_pts == AV_NOPTS_VALUE) { + vs->start_pts = pkt->pts; + vs->end_pts = pkt->pts; } - if (hls->has_video) { + if (vs->has_video) { can_split = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && ((pkt->flags & AV_PKT_FLAG_KEY) || (hls->flags & HLS_SPLIT_BY_TIME)); is_ref_pkt = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO; @@ -1630,82 +2108,92 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) is_ref_pkt = can_split = 0; if (is_ref_pkt) { - if (hls->new_start) { - hls->new_start = 0; - hls->duration = (double)(pkt->pts - hls->end_pts) + if (vs->new_start) { + vs->new_start = 0; + vs->duration = (double)(pkt->pts - vs->end_pts) * st->time_base.num / st->time_base.den; - hls->dpp = (double)(pkt->duration) * st->time_base.num / st->time_base.den; + vs->dpp = (double)(pkt->duration) * st->time_base.num / st->time_base.den; } else { if (pkt->duration) { - hls->duration += (double)(pkt->duration) * st->time_base.num / st->time_base.den; + vs->duration += (double)(pkt->duration) * st->time_base.num / st->time_base.den; } else { av_log(s, AV_LOG_WARNING, "pkt->duration = 0, maybe the hls segment duration will not precise\n"); - hls->duration = (double)(pkt->pts - hls->end_pts) * st->time_base.num / st->time_base.den; + vs->duration = (double)(pkt->pts - vs->end_pts) * st->time_base.num / st->time_base.den; } } } - if (hls->fmp4_init_mode || can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, + + if (vs->packets_written && can_split && av_compare_ts(pkt->pts - vs->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); + char *old_filename = av_strdup(vs->avf->filename); int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0); if (!old_filename) { return AVERROR(ENOMEM); } - av_write_frame(hls->avf, NULL); /* Flush any buffered data */ + av_write_frame(vs->avf, NULL); /* Flush any buffered data */ - new_start_pos = avio_tell(hls->avf->pb); - hls->size = new_start_pos - hls->start_pos; + new_start_pos = avio_tell(vs->avf->pb); + vs->size = new_start_pos - vs->start_pos; if (!byterange_mode) { - ff_format_io_close(s, &oc->pb); - if (hls->vtt_avf) { - ff_format_io_close(s, &hls->vtt_avf->pb); + if (hls->segment_type == SEGMENT_TYPE_FMP4 && !vs->init_range_length) { + avio_flush(oc->pb); + range_length = avio_close_dyn_buf(oc->pb, &buffer); + avio_write(vs->out, buffer, range_length); + vs->init_range_length = range_length; + avio_open_dyn_buf(&oc->pb); + vs->packets_written = 0; + ff_format_io_close(s, &vs->out); + } else { + ff_format_io_close(s, &oc->pb); + } + if (vs->vtt_avf) { + ff_format_io_close(s, &vs->vtt_avf->pb); } } 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) && hls->segment_type != SEGMENT_TYPE_FMP4) - av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0); + if ((vs->avf->oformat->priv_class && vs->avf->priv_data) && hls->segment_type != SEGMENT_TYPE_FMP4) + av_opt_set(vs->avf->priv_data, "mpegts_flags", "resend_headers", 0); hls_rename_temp_file(s, oc); } - if (hls->fmp4_init_mode) { - hls->number--; + if (vs->fmp4_init_mode) { + vs->number--; } - if (!hls->fmp4_init_mode || byterange_mode) - ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size); - - hls->start_pos = new_start_pos; + if (!vs->fmp4_init_mode || byterange_mode) + ret = hls_append_segment(s, hls, vs, vs->duration, vs->start_pos, vs->size); + vs->start_pos = new_start_pos; if (ret < 0) { av_free(old_filename); return ret; } - hls->end_pts = pkt->pts; - hls->duration = 0; + vs->end_pts = pkt->pts; + vs->duration = 0; - hls->fmp4_init_mode = 0; + vs->fmp4_init_mode = 0; if (hls->flags & HLS_SINGLE_FILE) { - hls->number++; + vs->number++; } else if (hls->max_seg_size > 0) { - if (hls->start_pos >= hls->max_seg_size) { - hls->sequence++; - sls_flag_file_rename(hls, old_filename); - ret = hls_start(s); - hls->start_pos = 0; + if (vs->start_pos >= hls->max_seg_size) { + vs->sequence++; + sls_flag_file_rename(hls, vs, old_filename); + ret = hls_start(s, vs); + vs->start_pos = 0; /* When split segment by byte, the duration is short than hls_time, * so it is not enough one segment duration as hls_time, */ - hls->number--; + vs->number--; } - hls->number++; + vs->number++; } else { - sls_flag_file_rename(hls, old_filename); - ret = hls_start(s); + sls_flag_file_rename(hls, vs, old_filename); + ret = hls_start(s, vs); } av_free(old_filename); @@ -1713,12 +2201,13 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) return ret; } - if (!hls->fmp4_init_mode || byterange_mode) - if ((ret = hls_window(s, 0)) < 0) { + if (!vs->fmp4_init_mode || byterange_mode) + if ((ret = hls_window(s, 0, vs)) < 0) { return ret; } } + vs->packets_written++; ret = ff_write_chained(oc, stream_index, pkt, s, 0); return ret; @@ -1727,9 +2216,18 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) static int hls_write_trailer(struct AVFormatContext *s) { HLSContext *hls = s->priv_data; - AVFormatContext *oc = hls->avf; - AVFormatContext *vtt_oc = hls->vtt_avf; - char *old_filename = av_strdup(hls->avf->filename); + AVFormatContext *oc = NULL; + AVFormatContext *vtt_oc = NULL; + char *old_filename = NULL; + int i; + VariantStream *vs = NULL; + + for (i = 0; i < hls->nb_varstreams; i++) { + vs = &hls->var_streams[i]; + + oc = vs->avf; + vtt_oc = vs->vtt_avf; + old_filename = av_strdup(vs->avf->filename); if (!old_filename) { return AVERROR(ENOMEM); @@ -1738,7 +2236,7 @@ static int hls_write_trailer(struct AVFormatContext *s) av_write_trailer(oc); if (oc->pb) { - hls->size = avio_tell(hls->avf->pb) - hls->start_pos; + vs->size = avio_tell(vs->avf->pb) - vs->start_pos; ff_format_io_close(s, &oc->pb); if ((hls->flags & HLS_TEMP_FILE) && oc->filename[0]) { @@ -1746,35 +2244,42 @@ static int hls_write_trailer(struct AVFormatContext *s) } /* after av_write_trailer, then duration + 1 duration per packet */ - hls_append_segment(s, hls, hls->duration + hls->dpp, hls->start_pos, hls->size); + hls_append_segment(s, hls, vs, vs->duration + vs->dpp, vs->start_pos, vs->size); } - sls_flag_file_rename(hls, old_filename); + sls_flag_file_rename(hls, vs, old_filename); if (vtt_oc) { if (vtt_oc->pb) av_write_trailer(vtt_oc); - hls->size = avio_tell(hls->vtt_avf->pb) - hls->start_pos; + vs->size = avio_tell(vs->vtt_avf->pb) - vs->start_pos; ff_format_io_close(s, &vtt_oc->pb); } - av_freep(&hls->basename); - av_freep(&hls->base_output_dirname); - av_freep(&hls->key_basename); + av_freep(&vs->basename); + av_freep(&vs->base_output_dirname); avformat_free_context(oc); - hls->avf = NULL; - hls_window(s, 1); + vs->avf = NULL; + hls_window(s, 1, vs); - av_freep(&hls->fmp4_init_filename); + av_freep(&vs->fmp4_init_filename); if (vtt_oc) { - av_freep(&hls->vtt_basename); - av_freep(&hls->vtt_m3u8_name); + av_freep(&vs->vtt_basename); + av_freep(&vs->vtt_m3u8_name); avformat_free_context(vtt_oc); } - hls_free_segments(hls->segments); - hls_free_segments(hls->old_segments); + hls_free_segments(vs->segments); + hls_free_segments(vs->old_segments); av_free(old_filename); + av_freep(&vs->m3u8_name); + av_freep(&vs->streams); + av_freep(&vs->baseurl); + } + + av_freep(&hls->key_basename); + av_freep(&hls->var_streams); + av_freep(&hls->master_m3u8_url); return 0; } @@ -1829,6 +2334,9 @@ static const AVOption options[] = { {"epoch", "seconds since epoch", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, {"datetime", "current datetime as YYYYMMDDhhmmss", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_FORMATTED_DATETIME }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, {"http_user_agent", "override User-Agent field in HTTP header", OFFSET(user_agent), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"var_stream_map", "Variant stream map string", OFFSET(var_stream_map), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"master_pl_name", "Create HLS master playlist with this name", OFFSET(master_pl_name), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"master_pl_publish_rate", "Publish master play list every after this many segment intervals", OFFSET(master_publish_rate), AV_OPT_TYPE_INT, {.i64 = 0}, 0, UINT_MAX, E}, { NULL }, };