On 14/09/14 15:20, "Mika Raento" <mika.rae...@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: >+@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. >+ >+@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: >+@example >+ffmpeg -i in.nut -hls_flags single_file out.m3u8 >+@end example >+Will produce the playlist, @file{out.m3u8}, and a single segment file, >+@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel