[FFmpeg-devel] [PATCH] hlsenc: single_file, support HLS ver 4 byteranges

Raento Mika mika.raento at elisa.fi
Sun Sep 14 14:35:35 CEST 2014


On 14/09/14 15:20, "Mika Raento" <mika.raento at elisa.fi> wrote:

>This adds a new option -hls_flags single_file that creates one .ts file
>for HLS and adds byteranges to the .m3u8 file, instead of creating one
>.ts file for each segment.
>
>This is helpful at least for storing large number of videos, as the
>number of files per video is drastically reduced and copying and storing
>those files takes less requests and inodes.
>
>This is based on work by Nicolas Martyanoff, discussed on ffmpeg-devel
>in July 2014. That patch seems abandoned by the author, and contained
>unrelated changes. This patch tries to add the minimum amount of code to
>support the byterange playlists.
>---
> doc/muxers.texi      | 23 +++++++++++++++----
> libavformat/hlsenc.c | 64
>++++++++++++++++++++++++++++++++++++++++++----------
> 2 files changed, 71 insertions(+), 16 deletions(-)
>
>diff --git a/doc/muxers.texi b/doc/muxers.texi
>index 57e81f4..40ae857 100644
>--- a/doc/muxers.texi
>+++ b/doc/muxers.texi
>@@ -194,15 +194,19 @@ can not be smaller than one centi second.
> Apple HTTP Live Streaming muxer that segments MPEG-TS according to
> the HTTP Live Streaming (HLS) specification.
> 
>-It creates a playlist file and numbered segment files. The output
>-filename specifies the playlist filename; the segment filenames
>-receive the same basename as the playlist, a sequential number and
>-a .ts extension.
>+It creates a playlist file, and one or more segment files. The output
>filename
>+specifies the playlist filename.
>+
>+By default, the muxer creates a file for each segment produced. These
>files
>+have the same name as the playlist, followed by a sequential number and a
>+.ts extension.
> 
> For example, to convert an input file with @command{ffmpeg}:
> @example
> ffmpeg -i in.nut out.m3u8
> @end example
>+This example will produce the playlist, @file{out.m3u8}, and segment
>files:
>+ at file{out0.ts}, @file{out1.ts}, @file{out2.ts}, etc.
> 
> See also the @ref{segment} muxer, which provides a more generic and
> flexible implementation of a segmenter, and can be used to perform HLS
>@@ -241,6 +245,17 @@ Note that the playlist sequence number must be
>unique for each segment
> and it is not to be confused with the segment filename sequence number
> which can be cyclic, for example if the @option{wrap} option is
> specified.
>+
>+ at item hls_flags single_file
>+If this flag is set, the muxer will store all segments in a single
>MPEG-TS
>+file, and will use byte ranges in the playlist. HLS playlists generated
>with
>+this way will have the version number 4.
>+For example:
>+ at example
>+ffmpeg -i in.nut -hls_flags single_file out.m3u8
>+ at end example
>+Will produce the playlist, @file{out.m3u8}, and a single segment file,
>+ at file{out.ts}.
> @end table
> 
> @anchor{ico}
>diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
>index 11f1e5b..b5b41ae 100644
>--- a/libavformat/hlsenc.c
>+++ b/libavformat/hlsenc.c
>@@ -34,10 +34,17 @@
> typedef struct HLSSegment {
>     char filename[1024];
>     double duration; /* in seconds */
>+    int64_t pos;
>+    int64_t size;
> 
>     struct HLSSegment *next;
> } HLSSegment;
> 
>+typedef enum HLSFlags {
>+    // Generate a single media file and use byte ranges in the playlist.
>+    HLS_SINGLE_FILE = (1 << 0),
>+} HLSFlags;
>+
> typedef struct HLSContext {
>     const AVClass *class;  // Class for private options.
>     unsigned number;
>@@ -50,12 +57,15 @@ typedef struct HLSContext {
>     float time;            // Set by a private option.
>     int max_nb_segments;   // Set by a private option.
>     int  wrap;             // Set by a private option.
>+    uint32_t flags;        // enum HLSFlags
> 
>     int64_t recording_time;
>     int has_video;
>     int64_t start_pts;
>     int64_t end_pts;
>     double duration;      // last segment duration computed so far, in
>seconds
>+    int64_t start_pos;    // last segment starting position
>+    int64_t size;         // last segment size
>     int nb_entries;
> 
>     HLSSegment *segments;
>@@ -88,12 +98,14 @@ static int hls_mux_init(AVFormatContext *s)
>         avcodec_copy_context(st->codec, s->streams[i]->codec);
>         st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
>     }
>+    hls->start_pos = 0;
> 
>     return 0;
> }
> 
> /* Create a new segment and append it to the segment list */
>-static int hls_append_segment(HLSContext *hls, double duration)
>+static int hls_append_segment(HLSContext *hls, double duration, int64_t
>pos,
>+                              int64_t size)
> {
>     HLSSegment *en = av_malloc(sizeof(*en));
> 
>@@ -103,6 +115,8 @@ static int hls_append_segment(HLSContext *hls, double
>duration)
>     av_strlcpy(en->filename, av_basename(hls->avf->filename),
>sizeof(en->filename));
> 
>     en->duration = duration;
>+    en->pos      = pos;
>+    en->size     = size;
>     en->next     = NULL;
> 
>     if (!hls->segments)
>@@ -142,6 +156,7 @@ static int hls_window(AVFormatContext *s, int last)
>     int target_duration = 0;
>     int ret = 0;
>     int64_t sequence = FFMAX(hls->start_sequence, hls->sequence -
>hls->nb_entries);
>+    int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3;
> 
>     if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE,
>                           &s->interrupt_callback, NULL)) < 0)
>@@ -153,7 +168,7 @@ static int hls_window(AVFormatContext *s, int last)
>     }
> 
>     avio_printf(hls->pb, "#EXTM3U\n");
>-    avio_printf(hls->pb, "#EXT-X-VERSION:3\n");
>+    avio_printf(hls->pb, "#EXT-X-VERSION:%d\n", version);
>     avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n", target_duration);
>     avio_printf(hls->pb, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
> 
>@@ -162,6 +177,9 @@ static int hls_window(AVFormatContext *s, int last)
> 
>     for (en = hls->segments; en; en = en->next) {
>         avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration);
>+        if (hls->flags & HLS_SINGLE_FILE)
>+             avio_printf(hls->pb,
>"#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
>+                         en->size, en->pos);
>         if (hls->baseurl)
>             avio_printf(hls->pb, "%s", hls->baseurl);
>         avio_printf(hls->pb, "%s\n", en->filename);
>@@ -181,11 +199,15 @@ static int hls_start(AVFormatContext *s)
>     AVFormatContext *oc = c->avf;
>     int err = 0;
> 
>-    if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
>-                              c->basename, c->wrap ? c->sequence %
>c->wrap : c->sequence) < 0) {
>-        av_log(oc, AV_LOG_ERROR, "Invalid segment filename template
>'%s'\n", c->basename);
>-        return AVERROR(EINVAL);
>-    }
>+    if (c->flags & HLS_SINGLE_FILE)
>+        av_strlcpy(oc->filename, c->basename,
>+                   sizeof(oc->filename));
>+    else
>+        if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
>+                                  c->basename, c->wrap ? c->sequence %
>c->wrap : c->sequence) < 0) {
>+            av_log(oc, AV_LOG_ERROR, "Invalid segment filename template
>'%s'\n", c->basename);
>+            return AVERROR(EINVAL);
>+        }
>     c->number++;
> 
>     if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
>@@ -210,6 +232,9 @@ static int hls_write_header(AVFormatContext *s)
>     hls->recording_time = hls->time * AV_TIME_BASE;
>     hls->start_pts      = AV_NOPTS_VALUE;
> 
>+    if (hls->flags & HLS_SINGLE_FILE)
>+        pattern = ".ts";
>+
>     for (i = 0; i < s->nb_streams; i++)
>         hls->has_video +=
>             s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO;
>@@ -289,17 +314,26 @@ static int hls_write_packet(AVFormatContext *s,
>AVPacket *pkt)
> 
>     if (can_split && av_compare_ts(pkt->pts - hls->start_pts,
>st->time_base,
>                                    end_pts, AV_TIME_BASE_Q) >= 0) {
>-        ret = hls_append_segment(hls, hls->duration);
>+        av_write_frame(oc, NULL); /* Flush any buffered data */
>+
>+        hls->size = hls->avf->pb->pos - hls->start_pos;
>+        int64_t start_pos = hls->avf->pb->pos;
>+        ret = hls_append_segment(hls, hls->duration, hls->start_pos,
>hls->size);
>+        hls->start_pos = start_pos;
>         if (ret)
>             return ret;
> 
>         hls->end_pts = pkt->pts;
>         hls->duration = 0;
> 
>-        av_write_frame(oc, NULL); /* Flush any buffered data */
>-        avio_close(oc->pb);
>+        if (hls->flags & HLS_SINGLE_FILE) {
>+            if (hls->avf->oformat->priv_class && hls->avf->priv_data)
>+                av_opt_set(hls->avf->priv_data, "mpegts_flags",
>"resend_headers", 0);
>+        } else {
>+            avio_close(oc->pb);
> 
>-        ret = hls_start(s);
>+            ret = hls_start(s);
>+        }
> 
>         if (ret)
>             return ret;
>@@ -321,10 +355,13 @@ static int hls_write_trailer(struct AVFormatContext
>*s)
>     AVFormatContext *oc = hls->avf;
> 
>     av_write_trailer(oc);
>+    hls->size = hls->avf->pb->pos - hls->start_pos;
>     avio_closep(&oc->pb);
>     avformat_free_context(oc);
>     av_free(hls->basename);
>-    hls_append_segment(hls, hls->duration);
>+    if (hls->duration > 0.0) {
>+        hls_append_segment(hls, hls->duration, hls->start_pos,
>hls->size);
>+    }
>     hls_window(s, 1);
> 
>     hls_free_segments(hls);
>@@ -340,6 +377,9 @@ static const AVOption options[] = {
>     {"hls_list_size", "set maximum number of playlist entries",
>OFFSET(max_nb_segments),    AV_OPT_TYPE_INT,    {.i64 = 5},     0,
>INT_MAX, E},
>     {"hls_wrap",      "set number after which the index wraps",
>OFFSET(wrap),    AV_OPT_TYPE_INT,    {.i64 = 0},     0, 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_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"},
>+
>     { NULL },
> };
> 
>-- 
>1.8.5.2 (Apple Git-48)
>
>

Ah, this has a bug in the calculation of the desired segment end time if
using the new flag, I'll submit a new version shortly.

   Mika



More information about the ffmpeg-devel mailing list