Browse code

hlsenc: single_file, support HLS ver 4 byteranges

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.

Signed-off-by: Michael Niedermayer <michaelni@gmx.at>

Mika Raento authored on 2014/09/15 22:26:57
Showing 2 changed files
... ...
@@ -194,15 +194,19 @@ can not be smaller than one centi second.
194 194
 Apple HTTP Live Streaming muxer that segments MPEG-TS according to
195 195
 the HTTP Live Streaming (HLS) specification.
196 196
 
197
-It creates a playlist file and numbered segment files. The output
198
-filename specifies the playlist filename; the segment filenames
199
-receive the same basename as the playlist, a sequential number and
200
-a .ts extension.
197
+It creates a playlist file, and one or more segment files. The output filename
198
+specifies the playlist filename.
199
+
200
+By default, the muxer creates a file for each segment produced. These files
201
+have the same name as the playlist, followed by a sequential number and a
202
+.ts extension.
201 203
 
202 204
 For example, to convert an input file with @command{ffmpeg}:
203 205
 @example
204 206
 ffmpeg -i in.nut out.m3u8
205 207
 @end example
208
+This example will produce the playlist, @file{out.m3u8}, and segment files:
209
+@file{out0.ts}, @file{out1.ts}, @file{out2.ts}, etc.
206 210
 
207 211
 See also the @ref{segment} muxer, which provides a more generic and
208 212
 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
241 241
 and it is not to be confused with the segment filename sequence number
242 242
 which can be cyclic, for example if the @option{wrap} option is
243 243
 specified.
244
+
245
+@item hls_flags single_file
246
+If this flag is set, the muxer will store all segments in a single MPEG-TS
247
+file, and will use byte ranges in the playlist. HLS playlists generated with
248
+this way will have the version number 4.
249
+For example:
250
+@example
251
+ffmpeg -i in.nut -hls_flags single_file out.m3u8
252
+@end example
253
+Will produce the playlist, @file{out.m3u8}, and a single segment file,
254
+@file{out.ts}.
244 255
 @end table
245 256
 
246 257
 @anchor{ico}
... ...
@@ -34,10 +34,17 @@
34 34
 typedef struct HLSSegment {
35 35
     char filename[1024];
36 36
     double duration; /* in seconds */
37
+    int64_t pos;
38
+    int64_t size;
37 39
 
38 40
     struct HLSSegment *next;
39 41
 } HLSSegment;
40 42
 
43
+typedef enum HLSFlags {
44
+    // Generate a single media file and use byte ranges in the playlist.
45
+    HLS_SINGLE_FILE = (1 << 0),
46
+} HLSFlags;
47
+
41 48
 typedef struct HLSContext {
42 49
     const AVClass *class;  // Class for private options.
43 50
     unsigned number;
... ...
@@ -50,12 +57,15 @@ typedef struct HLSContext {
50 50
     float time;            // Set by a private option.
51 51
     int max_nb_segments;   // Set by a private option.
52 52
     int  wrap;             // Set by a private option.
53
+    uint32_t flags;        // enum HLSFlags
53 54
 
54 55
     int64_t recording_time;
55 56
     int has_video;
56 57
     int64_t start_pts;
57 58
     int64_t end_pts;
58 59
     double duration;      // last segment duration computed so far, in seconds
60
+    int64_t start_pos;    // last segment starting position
61
+    int64_t size;         // last segment size
59 62
     int nb_entries;
60 63
 
61 64
     HLSSegment *segments;
... ...
@@ -88,12 +98,14 @@ static int hls_mux_init(AVFormatContext *s)
88 88
         avcodec_copy_context(st->codec, s->streams[i]->codec);
89 89
         st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
90 90
     }
91
+    hls->start_pos = 0;
91 92
 
92 93
     return 0;
93 94
 }
94 95
 
95 96
 /* Create a new segment and append it to the segment list */
96
-static int hls_append_segment(HLSContext *hls, double duration)
97
+static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
98
+                              int64_t size)
97 99
 {
98 100
     HLSSegment *en = av_malloc(sizeof(*en));
99 101
 
... ...
@@ -103,6 +115,8 @@ static int hls_append_segment(HLSContext *hls, double duration)
103 103
     av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename));
104 104
 
105 105
     en->duration = duration;
106
+    en->pos      = pos;
107
+    en->size     = size;
106 108
     en->next     = NULL;
107 109
 
108 110
     if (!hls->segments)
... ...
@@ -142,6 +156,7 @@ static int hls_window(AVFormatContext *s, int last)
142 142
     int target_duration = 0;
143 143
     int ret = 0;
144 144
     int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries);
145
+    int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3;
145 146
 
146 147
     if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE,
147 148
                           &s->interrupt_callback, NULL)) < 0)
... ...
@@ -153,7 +168,7 @@ static int hls_window(AVFormatContext *s, int last)
153 153
     }
154 154
 
155 155
     avio_printf(hls->pb, "#EXTM3U\n");
156
-    avio_printf(hls->pb, "#EXT-X-VERSION:3\n");
156
+    avio_printf(hls->pb, "#EXT-X-VERSION:%d\n", version);
157 157
     avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n", target_duration);
158 158
     avio_printf(hls->pb, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
159 159
 
... ...
@@ -162,6 +177,9 @@ static int hls_window(AVFormatContext *s, int last)
162 162
 
163 163
     for (en = hls->segments; en; en = en->next) {
164 164
         avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration);
165
+        if (hls->flags & HLS_SINGLE_FILE)
166
+             avio_printf(hls->pb, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
167
+                         en->size, en->pos);
165 168
         if (hls->baseurl)
166 169
             avio_printf(hls->pb, "%s", hls->baseurl);
167 170
         avio_printf(hls->pb, "%s\n", en->filename);
... ...
@@ -181,11 +199,15 @@ static int hls_start(AVFormatContext *s)
181 181
     AVFormatContext *oc = c->avf;
182 182
     int err = 0;
183 183
 
184
-    if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
185
-                              c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) {
186
-        av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->basename);
187
-        return AVERROR(EINVAL);
188
-    }
184
+    if (c->flags & HLS_SINGLE_FILE)
185
+        av_strlcpy(oc->filename, c->basename,
186
+                   sizeof(oc->filename));
187
+    else
188
+        if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
189
+                                  c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) {
190
+            av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->basename);
191
+            return AVERROR(EINVAL);
192
+        }
189 193
     c->number++;
190 194
 
191 195
     if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
... ...
@@ -210,6 +232,9 @@ static int hls_write_header(AVFormatContext *s)
210 210
     hls->recording_time = hls->time * AV_TIME_BASE;
211 211
     hls->start_pts      = AV_NOPTS_VALUE;
212 212
 
213
+    if (hls->flags & HLS_SINGLE_FILE)
214
+        pattern = ".ts";
215
+
213 216
     for (i = 0; i < s->nb_streams; i++)
214 217
         hls->has_video +=
215 218
             s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO;
... ...
@@ -289,17 +314,28 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
289 289
 
290 290
     if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base,
291 291
                                    end_pts, AV_TIME_BASE_Q) >= 0) {
292
-        ret = hls_append_segment(hls, hls->duration);
292
+        int64_t new_start_pos;
293
+        av_write_frame(oc, NULL); /* Flush any buffered data */
294
+
295
+        new_start_pos = avio_tell(hls->avf->pb);
296
+        hls->size = new_start_pos - hls->start_pos;
297
+        ret = hls_append_segment(hls, hls->duration, hls->start_pos, hls->size);
298
+        hls->start_pos = new_start_pos;
293 299
         if (ret)
294 300
             return ret;
295 301
 
296 302
         hls->end_pts = pkt->pts;
297 303
         hls->duration = 0;
298 304
 
299
-        av_write_frame(oc, NULL); /* Flush any buffered data */
300
-        avio_close(oc->pb);
305
+        if (hls->flags & HLS_SINGLE_FILE) {
306
+            if (hls->avf->oformat->priv_class && hls->avf->priv_data)
307
+                av_opt_set(hls->avf->priv_data, "mpegts_flags", "resend_headers", 0);
308
+            hls->number++;
309
+        } else {
310
+            avio_close(oc->pb);
301 311
 
302
-        ret = hls_start(s);
312
+            ret = hls_start(s);
313
+        }
303 314
 
304 315
         if (ret)
305 316
             return ret;
... ...
@@ -321,10 +357,11 @@ static int hls_write_trailer(struct AVFormatContext *s)
321 321
     AVFormatContext *oc = hls->avf;
322 322
 
323 323
     av_write_trailer(oc);
324
+    hls->size = avio_tell(hls->avf->pb) - hls->start_pos;
324 325
     avio_closep(&oc->pb);
325 326
     avformat_free_context(oc);
326 327
     av_free(hls->basename);
327
-    hls_append_segment(hls, hls->duration);
328
+    hls_append_segment(hls, hls->duration, hls->start_pos, hls->size);
328 329
     hls_window(s, 1);
329 330
 
330 331
     hls_free_segments(hls);
... ...
@@ -340,6 +377,9 @@ static const AVOption options[] = {
340 340
     {"hls_list_size", "set maximum number of playlist entries",  OFFSET(max_nb_segments),    AV_OPT_TYPE_INT,    {.i64 = 5},     0, INT_MAX, E},
341 341
     {"hls_wrap",      "set number after which the index wraps",  OFFSET(wrap),    AV_OPT_TYPE_INT,    {.i64 = 0},     0, INT_MAX, E},
342 342
     {"hls_base_url",  "url to prepend to each playlist entry",   OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E},
343
+    {"hls_flags",     "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},
344
+    {"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"},
345
+
343 346
     { NULL },
344 347
 };
345 348