]> git.sesse.net Git - ffmpeg/blobdiff - libavformat/hlsenc.c
avcodec/decode: Pass on the Closed Captions Side Data
[ffmpeg] / libavformat / hlsenc.c
index 40143c86aa27db397024ea58f71193fc42b4684a..418f153c6f99f58679085bc6defdbf1b1077d284 100644 (file)
@@ -85,8 +85,14 @@ typedef enum HLSFlags {
     HLS_SECOND_LEVEL_SEGMENT_DURATION = (1 << 9), // include segment duration (microsec) in segment filenames when use_localtime  e.g.: %%09t
     HLS_SECOND_LEVEL_SEGMENT_SIZE = (1 << 10), // include segment size (bytes) in segment filenames when use_localtime  e.g.: %%014s
     HLS_TEMP_FILE = (1 << 11),
+    HLS_PERIODIC_REKEY = (1 << 12),
 } HLSFlags;
 
+typedef enum {
+    SEGMENT_TYPE_MPEGTS,
+    SEGMENT_TYPE_FMP4,
+} SegmentType;
+
 typedef enum {
     PLAYLIST_TYPE_NONE,
     PLAYLIST_TYPE_EVENT,
@@ -115,6 +121,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
@@ -139,6 +148,7 @@ typedef struct HLSContext {
     HLSSegment *old_segments;
 
     char *basename;
+    char *base_output_dirname;
     char *vtt_basename;
     char *vtt_m3u8_name;
     char *baseurl;
@@ -164,6 +174,7 @@ typedef struct HLSContext {
 
     double initial_prog_date_time;
     char current_segment_final_filename_fmt[1024]; // when renaming segments
+    char *user_agent;
 } HLSContext;
 
 static int get_int_from_double(double val)
@@ -204,6 +215,22 @@ static int mkdir_p(const char *path) {
     return ret;
 }
 
+static void set_http_options(AVFormatContext *s, AVDictionary **options, HLSContext *c)
+{
+    const char *proto = avio_find_protocol_name(s->filename);
+    int http_base_proto = proto ? (!av_strcasecmp(proto, "http") || !av_strcasecmp(proto, "https")) : 0;
+
+    if (c->method) {
+        av_dict_set(options, "method", c->method, 0);
+    } else if (http_base_proto) {
+        av_log(c, AV_LOG_WARNING, "No HTTP method set, hls muxer defaulting to method PUT.\n");
+        av_dict_set(options, "method", "PUT", 0);
+    }
+    if (c->user_agent)
+        av_dict_set(options, "user_agent", c->user_agent, 0);
+
+}
+
 static int replace_int_data_in_filename(char *buf, int buf_size, const char *filename, char placeholder, int64_t number)
 {
     const char *p;
@@ -256,6 +283,16 @@ fail:
     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;
@@ -508,9 +545,11 @@ 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;
+    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);
@@ -547,13 +586,56 @@ static int hls_mux_init(AVFormatContext *s)
         if (!(st = avformat_new_stream(loc, NULL)))
             return AVERROR(ENOMEM);
         avcodec_parameters_copy(st->codecpar, s->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;
+        } 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);
     }
     hls->start_pos = 0;
     hls->new_start = 1;
+    hls->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;
+        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);
+            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 (ret < 0)
+            return ret;
+        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;
 }
 
@@ -899,19 +981,6 @@ static void hls_free_segments(HLSSegment *p)
     }
 }
 
-static void set_http_options(AVFormatContext *s, AVDictionary **options, HLSContext *c)
-{
-    const char *proto = avio_find_protocol_name(s->filename);
-    int http_base_proto = proto ? (!av_strcasecmp(proto, "http") || !av_strcasecmp(proto, "https")) : 0;
-
-    if (c->method) {
-        av_dict_set(options, "method", c->method, 0);
-    } else if (http_base_proto) {
-        av_log(c, AV_LOG_WARNING, "No HTTP method set, hls muxer defaulting to method PUT.\n");
-        av_dict_set(options, "method", "PUT", 0);
-    }
-}
-
 static void write_m3u8_head_block(HLSContext *hls, AVIOContext *out, int version,
                                   int target_duration, int64_t sequence)
 {
@@ -961,6 +1030,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");
 
@@ -1001,13 +1074,21 @@ static int hls_window(AVFormatContext *s, int last)
             avio_printf(out, "#EXT-X-DISCONTINUITY\n");
         }
 
-        if (hls->flags & HLS_ROUND_DURATIONS)
-            avio_printf(out, "#EXTINF:%ld,\n",  lrint(en->duration));
-        else
-            avio_printf(out, "#EXTINF:%f,\n", en->duration);
-        if (byterange_mode)
-             avio_printf(out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
-                         en->size, en->pos);
+        if ((hls->segment_type == SEGMENT_TYPE_FMP4) && (en == hls->segments)) {
+            avio_printf(out, "#EXT-X-MAP:URI=\"%s\"", hls->fmp4_init_filename);
+            if (hls->flags & HLS_SINGLE_FILE) {
+                avio_printf(out, ",BYTERANGE=\"%"PRId64"@%"PRId64"\"", en->size, en->pos);
+            }
+            avio_printf(out, "\n");
+        } else {
+            if (hls->flags & HLS_ROUND_DURATIONS)
+                avio_printf(out, "#EXTINF:%ld,\n",  lrint(en->duration));
+            else
+                avio_printf(out, "#EXTINF:%f,\n", en->duration);
+            if (byterange_mode)
+                avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"\n",
+                            en->size, en->pos);
+        }
         if (hls->flags & HLS_PROGRAM_DATE_TIME) {
             time_t tt, wrongsecs;
             int milli;
@@ -1032,9 +1113,11 @@ 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);
-        avio_printf(out, "%s\n", en->filename);
+        if (!((hls->segment_type == SEGMENT_TYPE_FMP4) && (en == hls->segments))) {
+            if (hls->baseurl)
+                avio_printf(out, "%s", hls->baseurl);
+            avio_printf(out, "%s\n", en->filename);
+        }
     }
 
     if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)
@@ -1158,12 +1241,15 @@ static int hls_start(AVFormatContext *s)
             av_log(s, AV_LOG_WARNING, "Cannot use both -hls_key_info_file and -hls_enc,"
                   " will use -hls_key_info_file priority\n");
         }
-        if (c->key_info_file) {
-            if ((err = hls_encryption_start(s)) < 0)
-                goto fail;
-        } else {
-            if ((err = do_encrypt(s)) < 0)
-                goto fail;
+
+        if (c->number <= 1 || (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)
+                    goto fail;
+            }
         }
         if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0))
                 < 0)
@@ -1194,15 +1280,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 && !(c->flags & HLS_SINGLE_FILE)) {
+            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) {
@@ -1218,14 +1308,19 @@ fail:
     return err;
 }
 
-static const char * get_default_pattern_localtime_fmt(void)
+static const char * get_default_pattern_localtime_fmt(AVFormatContext *s)
 {
     char b[21];
     time_t t = time(NULL);
     struct tm *p, tmbuf;
+    HLSContext *hls = s->priv_data;
+
     p = localtime_r(&t, &tmbuf);
     // no %s support when strftime returned error or left format string unchanged
     // also no %s support on MSVC, which invokes the invalid parameter handler on unsupported format strings, instead of returning an error
+    if (hls->segment_type == SEGMENT_TYPE_FMP4) {
+        return (HAVE_LIBC_MSVCRT || !strftime(b, sizeof(b), "%s", p) || !strcmp(b, "%s")) ? "-%Y%m%d%H%M%S.m4s" : "-%s.m4s";
+    }
     return (HAVE_LIBC_MSVCRT || !strftime(b, sizeof(b), "%s", p) || !strcmp(b, "%s")) ? "-%Y%m%d%H%M%S.ts" : "-%s.ts";
 }
 
@@ -1233,14 +1328,17 @@ static int hls_write_header(AVFormatContext *s)
 {
     HLSContext *hls = s->priv_data;
     int ret, i;
-    char *p;
+    char *p = NULL;
     const char *pattern = "%d.ts";
-    const char *pattern_localtime_fmt = get_default_pattern_localtime_fmt();
+    const char *pattern_localtime_fmt = get_default_pattern_localtime_fmt(s);
     const char *vtt_pattern = "%d.vtt";
     AVDictionary *options = NULL;
-    int basename_size;
-    int vtt_basename_size;
+    int basename_size = 0;
+    int vtt_basename_size = 0;
 
+    if (hls->segment_type == SEGMENT_TYPE_FMP4) {
+        pattern = "%d.m4s";
+    }
     if ((hls->start_sequence_source_type == HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH) ||
         (hls->start_sequence_source_type == HLS_START_SEQUENCE_AS_FORMATTED_DATETIME)) {
         time_t t = time(NULL); // we will need it in either case
@@ -1289,7 +1387,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;
@@ -1311,8 +1413,13 @@ static int hls_write_header(AVFormatContext *s)
             goto fail;
         }
     } else {
-        if (hls->flags & HLS_SINGLE_FILE)
-            pattern = ".ts";
+        if (hls->flags & HLS_SINGLE_FILE) {
+            if (hls->segment_type == SEGMENT_TYPE_FMP4) {
+                pattern = ".m4s";
+            } else {
+                pattern = ".ts";
+            }
+        }
 
         if (hls->use_localtime) {
             basename_size = strlen(s->filename) + strlen(pattern_localtime_fmt) + 1;
@@ -1336,6 +1443,32 @@ static int hls_write_header(AVFormatContext *s)
             av_strlcat(hls->basename, pattern, basename_size);
         }
     }
+
+    if (av_strcasecmp(hls->fmp4_init_filename, "init.mp4")) {
+        int fmp4_init_filename_len = strlen(hls->fmp4_init_filename) + 1;
+        hls->base_output_dirname = av_malloc(fmp4_init_filename_len);
+        if (!hls->base_output_dirname) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        av_strlcpy(hls->base_output_dirname, hls->fmp4_init_filename, fmp4_init_filename_len);
+    } else {
+        hls->base_output_dirname = av_malloc(basename_size);
+        if (!hls->base_output_dirname) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+
+        av_strlcpy(hls->base_output_dirname, s->filename, basename_size);
+        p = strrchr(hls->base_output_dirname, '/');
+        if (p) {
+            *(p + 1) = '\0';
+            av_strlcat(hls->base_output_dirname, hls->fmp4_init_filename, basename_size);
+        } else {
+            av_strlcpy(hls->base_output_dirname, hls->fmp4_init_filename, basename_size);
+        }
+    }
+
     if (!hls->use_localtime) {
         ret = sls_flag_check_duration_size_index(hls);
         if (ret < 0) {
@@ -1376,6 +1509,14 @@ static int hls_write_header(AVFormatContext *s)
         av_strlcat(hls->vtt_basename, vtt_pattern, vtt_basename_size);
     }
 
+    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) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+    }
+
     if ((ret = hls_mux_init(s)) < 0)
         goto fail;
 
@@ -1390,8 +1531,10 @@ static int hls_write_header(AVFormatContext *s)
         }
     }
 
-    if ((ret = hls_start(s)) < 0)
-        goto fail;
+    if (hls->segment_type != SEGMENT_TYPE_FMP4 || hls->flags & HLS_SINGLE_FILE) {
+        if ((ret = hls_start(s)) < 0)
+            goto fail;
+    }
 
     av_dict_copy(&options, hls->format_options, 0);
     ret = avformat_write_header(hls->avf, &options);
@@ -1429,6 +1572,7 @@ 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);
@@ -1448,7 +1592,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) {
@@ -1495,7 +1639,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);
@@ -1505,7 +1649,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;
@@ -1518,12 +1662,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 || byterange_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);
@@ -1533,6 +1683,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) {
@@ -1556,9 +1707,10 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
             return ret;
         }
 
-        if ((ret = hls_window(s, 0)) < 0) {
-            return ret;
-        }
+        if (!hls->fmp4_init_mode || byterange_mode)
+            if ((ret = hls_window(s, 0)) < 0) {
+                return ret;
+            }
     }
 
     ret = ff_write_chained(oc, stream_index, pkt, s, 0);
@@ -1600,12 +1752,14 @@ static int hls_write_trailer(struct AVFormatContext *s)
         ff_format_io_close(s, &vtt_oc->pb);
     }
     av_freep(&hls->basename);
+    av_freep(&hls->base_output_dirname);
     av_freep(&hls->key_basename);
     avformat_free_context(oc);
 
     hls->avf = NULL;
     hls_window(s, 1);
 
+    av_freep(&hls->fmp4_init_filename);
     if (vtt_oc) {
         av_freep(&hls->vtt_basename);
         av_freep(&hls->vtt_m3u8_name);
@@ -1640,6 +1794,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_INT, {.i64 = SEGMENT_TYPE_MPEGTS }, 0, SEGMENT_TYPE_FMP4, 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"},
+    {"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"},
+    {"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"},
@@ -1653,6 +1811,7 @@ static const AVOption options[] = {
     {"second_level_segment_index", "include segment index in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_INDEX }, 0, UINT_MAX,   E, "flags"},
     {"second_level_segment_duration", "include segment duration in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_DURATION }, 0, UINT_MAX,   E, "flags"},
     {"second_level_segment_size", "include segment size in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_SIZE }, 0, UINT_MAX,   E, "flags"},
+    {"periodic_rekey", "reload keyinfo file periodically for re-keying", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_PERIODIC_REKEY }, 0, UINT_MAX,   E, "flags"},
     {"use_localtime", "set filename expansion with strftime at segment creation", OFFSET(use_localtime), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },
     {"use_localtime_mkdir", "create last directory component in strftime-generated filename", OFFSET(use_localtime_mkdir), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E },
     {"hls_playlist_type", "set the HLS playlist type", OFFSET(pl_type), AV_OPT_TYPE_INT, {.i64 = PLAYLIST_TYPE_NONE }, 0, PLAYLIST_TYPE_NB-1, E, "pl_type" },
@@ -1663,6 +1822,7 @@ static const AVOption options[] = {
     {"generic", "start_number value (default)", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_START_NUMBER }, INT_MIN, INT_MAX, E, "start_sequence_source_type" },
     {"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},
     { NULL },
 };
 
@@ -1682,7 +1842,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,