[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