[FFmpeg-devel] [PATCH] hlsenc: single_file, support HLS ver 4 byteranges
Michael Niedermayer
michaelni at gmx.at
Mon Sep 15 00:50:03 CEST 2014
On Sun, Sep 14, 2014 at 03:37:00PM +0300, Mika Raento 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 | 65 ++++++++++++++++++++++++++++++++++++++++++----------
> 2 files changed, 72 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..9fcb999 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,27 @@ 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;
is it intended to access pos directly instead of using avio_tell() ?
> + int64_t start_pos = hls->avf->pb->pos;
this mixes declarations and statements, some compilers dislike that
> + 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);
> + hls->number++;
> + } else {
> + avio_close(oc->pb);
>
> - ret = hls_start(s);
> + ret = hls_start(s);
> + }
>
> if (ret)
> return ret;
> @@ -321,10 +356,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);
> + }
is this change related to the rest of the patch or a unrelated
bugfix ? if the later please split it in a seperate patch
[...]
--
Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
Avoid a single point of failure, be that a person or equipment.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 181 bytes
Desc: Digital signature
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20140915/63eaa77c/attachment.asc>
More information about the ffmpeg-devel
mailing list