[FFmpeg-devel] [PATCH] avformat/segment.c: add experimental -segment_list_flags iframe

Aman Gupta ffmpeg at tmm1.net
Wed May 21 10:33:52 CEST 2014


The HLS spec supports [I-FRAMES-ONLY playlists][1] where BYTERANGEs
represent locations of keyframes inside mpegts segments. These are used
by iOS devices to show previews when a user seeks around a stream.

Apple's mediafilesegmenter can generate iframe.m3u8s alongside regular
playlists with --iframe-index-file. The stream and iframe playlists
share ts segments, with the iframe playlist pointing to the keyframes in
the stream. This would be a useful feature for ffmpeg, but is not
implemented by this patch.

Instead, I added a new `-segment_list_flags iframe` mode where
a dedicated playlist and segment is generated just for previewing.
A single .ts file is sufficient (as the playlist uses byteoffsets), and
it contains only keyframes (making it a fraction the size of the
original stream).

Here's how to generate iframe.m3u8 and iframe0.ts for an h264 input.
Notice that I only map the video stream into the segmenter.

  ./ffmpeg -i input.mkv -map 0:0 -c:v copy -bsf:v h264_mp4toannexb \
           -f stream_segment -segment_format mpegts \
           -segment_list iframe.m3u8 -segment_list_flags iframe iframe%d.ts

[1] https://developer.apple.com/library/ios/technotes/tn2288/_index.html#//apple_ref/doc/uid/DTS40012238-CH1-I_FRAME_PLAYLIST

Signed-off-by: Aman Gupta <ffmpeg at tmm1.net>
---
 libavformat/segment.c | 87 ++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 68 insertions(+), 19 deletions(-)

diff --git a/libavformat/segment.c b/libavformat/segment.c
index fe84f27..77cb1b4 100644
--- a/libavformat/segment.c
+++ b/libavformat/segment.c
@@ -44,6 +44,8 @@ typedef struct SegmentListEntry {
     double start_time, end_time;
     int64_t start_pts;
     int64_t offset_pts;
+    int64_t start_pos;
+    int64_t size;
     char *filename;
     struct SegmentListEntry *next;
 } SegmentListEntry;
@@ -58,8 +60,9 @@ typedef enum {
     LIST_TYPE_NB,
 } ListType;
 
-#define SEGMENT_LIST_FLAG_CACHE 1
-#define SEGMENT_LIST_FLAG_LIVE  2
+#define SEGMENT_LIST_FLAG_CACHE  1
+#define SEGMENT_LIST_FLAG_LIVE   2
+#define SEGMENT_LIST_FLAG_IFRAME 4
 
 typedef struct {
     const AVClass *class;  /**< Class for private options. */
@@ -241,7 +244,12 @@ static int segment_list_open(AVFormatContext *s)
         double max_duration = 0;
 
         avio_printf(seg->list_pb, "#EXTM3U\n");
-        avio_printf(seg->list_pb, "#EXT-X-VERSION:3\n");
+        if (!(seg->list_flags & SEGMENT_LIST_FLAG_IFRAME)) {
+          avio_printf(seg->list_pb, "#EXT-X-VERSION:3\n");
+        } else {
+          avio_printf(seg->list_pb, "#EXT-X-VERSION:4\n");
+          avio_printf(seg->list_pb, "#EXT-X-I-FRAMES-ONLY\n");
+        }
         avio_printf(seg->list_pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", seg->segment_list_entries->index);
         avio_printf(seg->list_pb, "#EXT-X-ALLOW-CACHE:%s\n",
                     seg->list_flags & SEGMENT_LIST_FLAG_CACHE ? "YES" : "NO");
@@ -259,7 +267,8 @@ static int segment_list_open(AVFormatContext *s)
     return ret;
 }
 
-static void segment_list_print_entry(AVIOContext      *list_ioctx,
+static void segment_list_print_entry(SegmentContext   *seg,
+                                     AVIOContext      *list_ioctx,
                                      ListType          list_type,
                                      const SegmentListEntry *list_entry,
                                      void *log_ctx)
@@ -274,8 +283,13 @@ static void segment_list_print_entry(AVIOContext      *list_ioctx,
         avio_printf(list_ioctx, ",%f,%f\n", list_entry->start_time, list_entry->end_time);
         break;
     case LIST_TYPE_M3U8:
-        avio_printf(list_ioctx, "#EXTINF:%f,\n%s\n",
-                    list_entry->end_time - list_entry->start_time, list_entry->filename);
+        if (!(seg->list_flags & SEGMENT_LIST_FLAG_IFRAME)) {
+            avio_printf(list_ioctx, "#EXTINF:%f,\n%s\n",
+                        list_entry->end_time - list_entry->start_time, list_entry->filename);
+        } else {
+            avio_printf(list_ioctx, "#EXTINF:%f,\n#EXT-X-BYTERANGE:%lld@%lld\n%s\n",
+                        list_entry->end_time - list_entry->start_time, list_entry->size, list_entry->start_pos, list_entry->filename);
+        }
         break;
     case LIST_TYPE_FFCONCAT:
     {
@@ -308,12 +322,29 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last)
         av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n",
                oc->filename);
 
+    ret = segment_list_append(s, seg, is_last);
+    if (ret < 0)
+        goto end;
+
+    av_log(s, AV_LOG_VERBOSE, "segment:'%s' count:%d ended\n",
+           seg->avf->filename, seg->segment_count);
+    seg->segment_count++;
+
+end:
+    avio_close(oc->pb);
+
+    return ret;
+}
+
+static int segment_list_append(AVFormatContext *s, SegmentContext *seg, int is_last)
+{
+    int ret = 0;
+
     if (seg->list) {
         if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) {
             SegmentListEntry *entry = av_mallocz(sizeof(*entry));
             if (!entry) {
-                ret = AVERROR(ENOMEM);
-                goto end;
+                return AVERROR(ENOMEM);
             }
 
             /* append new element */
@@ -334,25 +365,18 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last)
 
             avio_close(seg->list_pb);
             if ((ret = segment_list_open(s)) < 0)
-                goto end;
+                return ret;
             for (entry = seg->segment_list_entries; entry; entry = entry->next)
-                segment_list_print_entry(seg->list_pb, seg->list_type, entry, s);
+                segment_list_print_entry(seg, seg->list_pb, seg->list_type, entry, s);
             if (seg->list_type == LIST_TYPE_M3U8 && is_last)
                 avio_printf(seg->list_pb, "#EXT-X-ENDLIST\n");
         } else {
-            segment_list_print_entry(seg->list_pb, seg->list_type, &seg->cur_entry, s);
+            segment_list_print_entry(seg, seg->list_pb, seg->list_type, &seg->cur_entry, s);
         }
         avio_flush(seg->list_pb);
     }
 
-    av_log(s, AV_LOG_VERBOSE, "segment:'%s' count:%d ended\n",
-           seg->avf->filename, seg->segment_count);
-    seg->segment_count++;
-
-end:
-    avio_close(oc->pb);
-
-    return ret;
+    return 0;
 }
 
 static int parse_times(void *log_ctx, int64_t **times, int *nb_times,
@@ -665,6 +689,7 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
 {
     SegmentContext *seg = s->priv_data;
     AVStream *st = s->streams[pkt->stream_index];
+    AVFormatContext *oc = seg->avf;
     int64_t end_pts = INT64_MAX, offset;
     int start_frame = INT_MAX;
     int ret;
@@ -685,6 +710,29 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
            pkt->stream_index == seg->reference_stream_index ? seg->frame_count : -1);
 
     if (pkt->stream_index == seg->reference_stream_index &&
+        (seg->list_flags & SEGMENT_LIST_FLAG_IFRAME)) {
+
+        if (!seg->is_first_pkt && !seg->cur_entry.size)
+            seg->cur_entry.size = oc->pb->pos - seg->cur_entry.start_pos;
+
+        seg->cur_entry.end_time = (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base);
+
+        if (pkt->flags & AV_PKT_FLAG_KEY) {
+            if (seg->cur_entry.size) {
+                segment_list_append(s, seg, 0);
+                set_segment_filename(s);
+            }
+            seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap*seg->segment_idx_wrap_nb;
+            seg->cur_entry.start_time = seg->is_first_pkt ? 0 : (double)pkt->pts * av_q2d(st->time_base);
+            seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q);
+            seg->cur_entry.start_pos = oc->pb->pos == 0 ? 564 : oc->pb->pos;
+            seg->cur_entry.size = 0;
+        } else {
+          // skip non-keyframes
+          return 0;
+        }
+
+    } else if (pkt->stream_index == seg->reference_stream_index &&
         pkt->flags & AV_PKT_FLAG_KEY &&
         (seg->frame_count >= start_frame ||
          (pkt->pts != AV_NOPTS_VALUE &&
@@ -784,6 +832,7 @@ static const AVOption options[] = {
     { "segment_list_flags","set flags affecting segment list generation", OFFSET(list_flags), AV_OPT_TYPE_FLAGS, {.i64 = SEGMENT_LIST_FLAG_CACHE }, 0, UINT_MAX, E, "list_flags"},
     { "cache",             "allow list caching",                                    0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_CACHE }, INT_MIN, INT_MAX,   E, "list_flags"},
     { "live",              "enable live-friendly list generation (useful for HLS)", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_LIVE }, INT_MIN, INT_MAX,    E, "list_flags"},
+    { "iframe",            "generate i-frame-only stream and playlist",             0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_LIST_FLAG_IFRAME }, INT_MIN, INT_MAX,  E, "list_flags"},
 
     { "segment_list_size", "set the maximum number of playlist entries", OFFSET(list_size), AV_OPT_TYPE_INT,  {.i64 = 0},     0, INT_MAX, E },
 
-- 
1.9.1



More information about the ffmpeg-devel mailing list