[FFmpeg-devel] [PATCH] avformat/hlsenc: add single file mode
Nicolas Martyanoff
khaelin at gmail.com
Mon Jul 21 16:54:23 CEST 2014
HLS version 4 offers the possibility to keep the media file whole instead of
splitting it. In that case, segments are specified with byte ranges.
We introduce a new '-hls_flags' option for the hlsenc muxer, with a single
flag for the time being, 'single_file'.
---
doc/muxers.texi | 23 ++++++++++---
libavformat/hlsenc.c | 94 ++++++++++++++++++++++++++++++++++++++++++----------
2 files changed, 96 insertions(+), 21 deletions(-)
diff --git a/doc/muxers.texi b/doc/muxers.texi
index dc2a08b..186619e 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 5dde17a..c07f890 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -34,10 +34,17 @@
typedef struct HLSSegment {
char filename[1024];
double duration; /* in seconds */
+ size_t size; /* in bytes */
+ int64_t pos; /* in bytes */
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.
@@ -51,6 +58,7 @@ typedef struct HLSContext {
float target_duration;
int wrap;
+ uint32_t flags; // enum HLSFlags
int64_t recording_time;
int has_video;
@@ -60,6 +68,8 @@ typedef struct HLSContext {
int ref_stream; ///< the index of the stream used as time reference
double segment_duration; // last segment duration computed so far, in seconds
+ size_t segment_size; // in bytes
+ int64_t segment_pos; // offset in bytes
HLSSegment *segments;
HLSSegment *last_segment;
@@ -78,6 +88,8 @@ static int hls_mux_init(AVFormatContext *ctx)
hls = ctx->priv_data;
hls->segment_duration = 0.0;
+ hls->segment_size = 0;
+ hls->segment_pos = 0;
hls->first_pts = AV_NOPTS_VALUE;
hls->last_pts = AV_NOPTS_VALUE;
@@ -159,6 +171,8 @@ static int hls_append_segment(HLSContext *hls)
av_strlcpy(segment->filename, basename, sizeof(segment->filename));
segment->duration = hls->segment_duration;
+ segment->size = hls->segment_size;
+ segment->pos = hls->segment_pos;
segment->next = NULL;
@@ -204,12 +218,22 @@ static int hls_generate_playlist(AVFormatContext *ctx)
HLSSegment *segment;
int target_duration;
int64_t sequence;
+ unsigned int version;
int ret;
hls = ctx->priv_data;
target_duration = 0;
ret = 0;
+ if (hls->flags & HLS_SINGLE_FILE) {
+ /* Byte ranges are part of HLS version 4. */
+ version = 4;
+ } else {
+ /* We use floating point values for segment durations, which are part
+ * of HLS version 3. */
+ version = 3;
+ }
+
ret = avio_open2(&hls->pb, ctx->filename, AVIO_FLAG_WRITE,
&ctx->interrupt_callback, NULL);
if (ret < 0)
@@ -221,7 +245,7 @@ static int hls_generate_playlist(AVFormatContext *ctx)
}
avio_printf(hls->pb, "#EXTM3U\n");
- avio_printf(hls->pb, "#EXT-X-VERSION:3\n");
+ avio_printf(hls->pb, "#EXT-X-VERSION:%u\n", version);
avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n", target_duration);
sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries);
@@ -229,6 +253,12 @@ static int hls_generate_playlist(AVFormatContext *ctx)
for (segment = hls->segments; segment; segment = segment->next) {
avio_printf(hls->pb, "#EXTINF:%f,\n", segment->duration);
+
+ if (hls->flags & HLS_SINGLE_FILE) {
+ avio_printf(hls->pb, "#EXT-X-BYTERANGE:%"PRIu64"@%"PRIi64"\n",
+ (uint64_t)segment->size, segment->pos);
+ }
+
avio_printf(hls->pb, "%s%s\n",
(hls->baseurl ? hls->baseurl : ""), segment->filename);
}
@@ -255,11 +285,17 @@ static int hls_create_file(AVFormatContext *ctx)
file_idx = hls->sequence;
}
- if (av_get_frame_filename(hls->ctx->filename, sizeof(hls->ctx->filename),
- hls->media_filename, file_idx) < 0) {
- av_log(hls->ctx, AV_LOG_ERROR,
- "Invalid segment filename template '%s'\n", hls->media_filename);
- return AVERROR(EINVAL);
+ if (hls->flags & HLS_SINGLE_FILE) {
+ av_strlcpy(hls->ctx->filename, hls->media_filename,
+ sizeof(hls->ctx->filename));
+ } else {
+ if (av_get_frame_filename(hls->ctx->filename, sizeof(hls->ctx->filename),
+ hls->media_filename, file_idx) < 0) {
+ av_log(hls->ctx, AV_LOG_ERROR,
+ "Invalid segment filename template '%s'\n",
+ hls->media_filename);
+ return AVERROR(EINVAL);
+ }
}
ret = avio_open2(&hls->ctx->pb, hls->ctx->filename, AVIO_FLAG_WRITE,
@@ -307,7 +343,9 @@ static int hls_write_header(AVFormatContext *ctx)
}
av_strlcpy(hls->media_filename, filename, basename_size);
- av_strlcat(hls->media_filename, "%d.ts", basename_size);
+ if (!(hls->flags & HLS_SINGLE_FILE))
+ av_strlcat(hls->media_filename, "%d", basename_size);
+ av_strlcat(hls->media_filename, ".ts", basename_size);
/* Initialize the muxer and create the first file */
ret = hls_mux_init(ctx);
@@ -377,6 +415,8 @@ static int hls_write_packet(AVFormatContext *ctx, AVPacket *pkt)
if (can_split) {
/* Write a segment ending just before this packet */
+ hls->segment_size = (size_t)(hls->ctx->pb->pos - hls->segment_pos);
+
ret = hls_append_segment(hls);
if (ret)
return ret;
@@ -385,15 +425,26 @@ static int hls_write_packet(AVFormatContext *ctx, AVPacket *pkt)
/* Get ready for the next segment starting with this packet */
hls->segment_duration = 0.0;
+ hls->segment_size = 0;
- /* Flush any buffered data and close the current file */
- av_write_frame(hls->ctx, NULL);
- avio_close(hls->ctx->pb);
+ if (hls->flags & HLS_SINGLE_FILE) {
+ /* Mark the current position, i.e. the position where the next
+ * segment is going to start. */
+ hls->segment_pos = hls->ctx->pb->pos;
- /* Open the next file */
- ret = hls_create_file(ctx);
- if (ret)
- return ret;
+ // Each segment must start with a MPEG-TS header.
+ if (hls->ctx->oformat->priv_class && hls->ctx->priv_data)
+ av_opt_set(hls->ctx->priv_data, "mpegts_flags", "resend_headers", 0);
+ } else {
+ /* Flush any buffered data and close the current file */
+ av_write_frame(hls->ctx, NULL);
+ avio_close(hls->ctx->pb);
+
+ /* Open the next file */
+ ret = hls_create_file(ctx);
+ if (ret)
+ return ret;
+ }
}
ret = ff_write_chained(hls->ctx, pkt->stream_index, pkt, ctx);
@@ -409,13 +460,18 @@ static int hls_write_trailer(struct AVFormatContext *ctx)
hls = ctx->priv_data;
+ if (hls->segment_duration > 0.0) {
+ hls->segment_size = (size_t)(hls->ctx->pb->pos - hls->segment_pos);
+ hls_append_segment(hls);
+ }
+
+ hls_generate_playlist(ctx);
+
av_write_trailer(hls->ctx);
avio_closep(&hls->ctx->pb);
+
avformat_free_context(hls->ctx);
av_free(hls->media_filename);
- hls_append_segment(hls);
-
- hls_generate_playlist(ctx);
hls_free_segments(hls);
avio_close(hls->pb);
@@ -430,6 +486,10 @@ 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.5
More information about the ffmpeg-devel
mailing list