avformat/hlsenc: refine EXT-X-BYTERANGE support for segments
[ffmpeg.git] / libavformat / hlsenc.c
index 0ca5932..428bae4 100644 (file)
@@ -104,6 +104,7 @@ 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;
 
@@ -396,6 +397,9 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double
     } else
         hls->nb_entries++;
 
+    if (hls->max_seg_size > 0) {
+        return 0;
+    }
     hls->sequence++;
 
     return 0;
@@ -476,7 +480,7 @@ static int hls_window(AVFormatContext *s, int last)
     AVIOContext *sub_out = NULL;
     char temp_filename[1024];
     int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries);
-    int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3;
+    int version = 3;
     const char *proto = avio_find_protocol_name(s->filename);
     int use_rename = proto && !strcmp(proto, "file");
     static unsigned warned_non_file;
@@ -484,6 +488,12 @@ static int hls_window(AVFormatContext *s, int last)
     char *iv_string = NULL;
     AVDictionary *options = NULL;
     double prog_date_time = hls->initial_prog_date_time;
+    int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
+
+    if (byterange_mode) {
+        version = 4;
+        sequence = 0;
+    }
 
     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 temporarly partial files\n");
@@ -533,7 +543,7 @@ static int hls_window(AVFormatContext *s, int last)
             avio_printf(out, "#EXTINF:%ld,\n",  lrint(en->duration));
         else
             avio_printf(out, "#EXTINF:%f,\n", en->duration);
-        if (hls->flags & HLS_SINGLE_FILE)
+        if (byterange_mode)
              avio_printf(out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
                          en->size, en->pos);
         if (hls->flags & HLS_PROGRAM_DATE_TIME) {
@@ -584,7 +594,7 @@ static int hls_window(AVFormatContext *s, int last)
 
         for (en = hls->segments; en; en = en->next) {
             avio_printf(sub_out, "#EXTINF:%f,\n", en->duration);
-            if (hls->flags & HLS_SINGLE_FILE)
+            if (byterange_mode)
                  avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
                          en->size, en->pos);
             if (hls->baseurl)
@@ -621,6 +631,13 @@ static int hls_start(AVFormatContext *s)
         if (c->vtt_basename)
             av_strlcpy(vtt_oc->filename, c->vtt_basename,
                   sizeof(vtt_oc->filename));
+    } else if (c->max_seg_size > 0) {
+        if (av_get_frame_filename2(oc->filename, sizeof(oc->filename),
+            c->basename, c->wrap ? c->sequence % c->wrap : c->sequence,
+            AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
+                av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s', you can try to use -use_localtime 1 with it\n", c->basename);
+                return AVERROR(EINVAL);
+        }
     } else {
         if (c->use_localtime) {
             time_t now0;
@@ -867,6 +884,16 @@ static int hls_write_header(AVFormatContext *s)
     for (i = 0; i < s->nb_streams; i++) {
         AVStream *inner_st;
         AVStream *outer_st = s->streams[i];
+
+        if (hls->max_seg_size > 0) {
+            if ((outer_st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) &&
+                (outer_st->codecpar->bit_rate > hls->max_seg_size)) {
+                av_log(s, AV_LOG_WARNING, "Your video bitrate is bigger than hls_segment_size, "
+                       "(%"PRId64 " > %"PRId64 "), the result maybe not be what you want.",
+                       outer_st->codecpar->bit_rate, hls->max_seg_size);
+            }
+        }
+
         if (outer_st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE)
             inner_st = hls->avf->streams[i];
         else if (hls->vtt_avf)
@@ -954,6 +981,21 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
             if (hls->avf->oformat->priv_class && hls->avf->priv_data)
                 av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0);
             hls->number++;
+        } else if (hls->max_seg_size > 0) {
+            if (hls->avf->oformat->priv_class && hls->avf->priv_data)
+                av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0);
+            if (hls->start_pos >= hls->max_seg_size) {
+                hls->sequence++;
+                ff_format_io_close(s, &oc->pb);
+                if (hls->vtt_avf)
+                    ff_format_io_close(s, &hls->vtt_avf->pb);
+                ret = hls_start(s);
+                hls->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--;
+            }
+            hls->number++;
         } else {
             ff_format_io_close(s, &oc->pb);
             if (hls->vtt_avf)
@@ -1028,6 +1070,7 @@ static const AVOption options[] = {
     {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E},
     {"hls_base_url",  "url to prepend to each playlist entry",   OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E},
     {"hls_segment_filename", "filename template for segment files", OFFSET(segment_filename),   AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
+    {"hls_segment_size", "maximum size per segment file, (in bytes)",  OFFSET(max_seg_size),    AV_OPT_TYPE_INT,    {.i64 = 0},               0,       INT_MAX,   E},
     {"hls_key_info_file",    "file with key URI and key file path", OFFSET(key_info_file),      AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
     {"hls_subtitle_path",     "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,    E},
     {"hls_flags",     "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},