[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