[FFmpeg-devel] [PATCH] lavf/segment: add segment_frames option

Stefano Sabatini stefasab at gmail.com
Sun Dec 9 20:27:36 CET 2012


This is meant to address trac ticket #1483.
---
 doc/muxers.texi       |   15 +++++++
 libavformat/segment.c |  107 ++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 108 insertions(+), 14 deletions(-)

diff --git a/doc/muxers.texi b/doc/muxers.texi
index 59032e8..9508fc5 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -589,6 +589,14 @@ the specified time and the time set by @var{force_key_frames}.
 Specify a list of split points. @var{times} contains a list of comma
 separated duration specifications, in increasing order.
 
+ at item segment_frames @var{frames}
+Specify a list of split video frame numbers. @var{frames} contains a
+list of comma separated integer numbers, in increasing order.
+
+This option specifies to start a new segment whenever a video key
+frame is found and the sequential number (starting from 0) of the
+video frame is greater or equal to the next value in the list.
+
 @item segment_wrap @var{limit}
 Wrap around segment index once it reaches @var{limit}.
 
@@ -633,6 +641,13 @@ In order to force key frames on the input file, transcoding is
 required.
 
 @item
+Segment the input file by splitting the input file according to the
+frame numbers sequence specified with the @var{segment_frame} option:
+ at example
+ffmpeg -i in.mkv -codec copy -map 0 -f segment -segment_list out.csv -segment_frames 100,200,300,500,800 out%03d.nut
+ at end example
+
+ at item
 To convert the @file{in.mkv} to TS segments using the @code{libx264}
 and @code{libfaac} encoders:
 @example
diff --git a/libavformat/segment.c b/libavformat/segment.c
index 0dce86c..b830d3c 100644
--- a/libavformat/segment.c
+++ b/libavformat/segment.c
@@ -46,7 +46,6 @@ typedef enum {
     LIST_TYPE_NB,
 } ListType;
 
-
 #define SEGMENT_LIST_FLAG_CACHE 1
 #define SEGMENT_LIST_FLAG_LIVE  2
 
@@ -66,9 +65,16 @@ typedef struct {
     AVIOContext *list_pb;  ///< list file put-byte context
     char *time_str;        ///< segment duration specification string
     int64_t time;          ///< segment duration
+
     char *times_str;       ///< segment times specification string
     int64_t *times;        ///< list of segment interval specification
     int nb_times;          ///< number of elments in the times array
+
+    char *frames_str;      ///< segment frame numbers specification string
+    int *frames;           ///< list of frame number specification
+    int nb_frames;         ///< number of elments in the frames array
+    int frame_count;
+
     char *time_delta_str;  ///< approximation value duration used for the segment times
     int64_t time_delta;
     int  individual_header_trailer; /**< Set by a private option. */
@@ -303,6 +309,65 @@ end:
     return ret;
 }
 
+static int parse_frames(void *log_ctx, int **frames, int *nb_frames,
+                        const char *frames_str)
+{
+    char *p;
+    int i, ret = 0;
+    char *frames_str1 = av_strdup(frames_str);
+    char *saveptr = NULL;
+
+    if (!frames_str1)
+        return AVERROR(ENOMEM);
+
+#define FAIL(err) ret = err; goto end
+
+    *nb_frames = 1;
+    for (p = frames_str1; *p; p++)
+        if (*p == ',')
+            (*nb_frames)++;
+
+    *frames = av_malloc(sizeof(**frames) * *nb_frames);
+    if (!*frames) {
+        av_log(log_ctx, AV_LOG_ERROR, "Could not allocate forced frames array\n");
+        FAIL(AVERROR(ENOMEM));
+    }
+
+    p = frames_str1;
+    for (i = 0; i < *nb_frames; i++) {
+        long int f;
+        char *tailptr;
+        char *fstr = av_strtok(p, ",", &saveptr);
+
+        p = NULL;
+        if (!fstr) {
+            av_log(log_ctx, AV_LOG_ERROR, "Empty frame specification in frame list %s\n",
+                   frames_str);
+            FAIL(AVERROR(EINVAL));
+        }
+        f = strtol(fstr, &tailptr, 10);
+        if (*tailptr || f <= 0 || f >= INT_MAX) {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Invalid argument '%s', must be a positive integer <= INT64_MAX\n",
+                   fstr);
+            FAIL(AVERROR(EINVAL));
+        }
+        (*frames)[i] = f;
+
+        /* check on monotonicity */
+        if (i && (*frames)[i-1] > (*frames)[i]) {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Specified frame %d is greater than the following frame %d\n",
+                   (*frames)[i], (*frames)[i-1]);
+            FAIL(AVERROR(EINVAL));
+        }
+    }
+
+end:
+    av_free(frames_str1);
+    return ret;
+}
+
 static int open_null_ctx(AVIOContext **ctx)
 {
     int buf_size = 32768;
@@ -333,22 +398,26 @@ static int seg_write_header(AVFormatContext *s)
     if (!seg->write_header_trailer)
         seg->individual_header_trailer = 0;
 
-    if (seg->time_str && seg->times_str) {
+    if (!!seg->time_str + !!seg->times_str + !!seg->frames_str > 1) {
         av_log(s, AV_LOG_ERROR,
-               "segment_time and segment_times options are mutually exclusive, select just one of them\n");
+               "segment_time, segment_times, and segment_frames options "
+               "are mutually exclusive, select just one of them\n");
         return AVERROR(EINVAL);
     }
 
-    if ((seg->list_flags & SEGMENT_LIST_FLAG_LIVE) && seg->times_str) {
+    if ((seg->list_flags & SEGMENT_LIST_FLAG_LIVE) && (seg->times_str || seg->frames_str)) {
         av_log(s, AV_LOG_ERROR,
-               "segment_flags +live and segment_times options are mutually exclusive:"
-               "specify -segment_time if you want a live-friendly list\n");
+               "segment_flags +live and segment_times or segment_frames options are mutually exclusive: "
+               "specify segment_time option if you want a live-friendly list\n");
         return AVERROR(EINVAL);
     }
 
     if (seg->times_str) {
         if ((ret = parse_times(s, &seg->times, &seg->nb_times, seg->times_str)) < 0)
             return ret;
+    } else if (seg->frames_str) {
+        if ((ret = parse_frames(s, &seg->frames, &seg->nb_frames, seg->frames_str)) < 0)
+            return ret;
     } else {
         /* set default value if not specified */
         if (!seg->time_str)
@@ -455,25 +524,30 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
     SegmentContext *seg = s->priv_data;
     AVFormatContext *oc = seg->avf;
     AVStream *st = s->streams[pkt->stream_index];
-    int64_t end_pts;
+    int64_t end_pts = INT64_MAX;
+    int start_frame = INT_MAX;
     int ret;
 
     if (seg->times) {
         end_pts = seg->segment_count <= seg->nb_times ?
             seg->times[seg->segment_count-1] : INT64_MAX;
+    } else if (seg->frames) {
+        start_frame = seg->segment_count <= seg->nb_frames ?
+            seg->frames[seg->segment_count-1] : INT_MAX;
     } else {
         end_pts = seg->time * seg->segment_count;
     }
 
     /* if the segment has video, start a new segment *only* with a key video frame */
-    if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO || !seg->has_video) &&
-        pkt->pts != AV_NOPTS_VALUE &&
-        av_compare_ts(pkt->pts, st->time_base,
-                      end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0 &&
-        pkt->flags & AV_PKT_FLAG_KEY) {
+    if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO && seg->frame_count >= start_frame && pkt->flags & AV_PKT_FLAG_KEY) ||
+        ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO || !seg->has_video) &&
+         pkt->pts != AV_NOPTS_VALUE &&
+         av_compare_ts(pkt->pts, st->time_base,
+                       end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0 &&
+         pkt->flags & AV_PKT_FLAG_KEY)) {
 
-        av_log(s, AV_LOG_DEBUG, "Next segment starts with packet stream:%d pts:%"PRId64" pts_time:%f\n",
-               pkt->stream_index, pkt->pts, pkt->pts * av_q2d(st->time_base));
+        av_log(s, AV_LOG_DEBUG, "Next segment starts with packet stream:%d pts:%"PRId64" pts_time:%f frame:%d\n",
+               pkt->stream_index, pkt->pts, pkt->pts * av_q2d(st->time_base), seg->frame_count);
 
         ret = segment_end(s, seg->individual_header_trailer);
 
@@ -512,6 +586,9 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
     ret = ff_write_chained(oc, pkt->stream_index, pkt, s);
 
 fail:
+    if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO)
+        seg->frame_count++;
+
     if (ret < 0) {
         if (seg->list)
             avio_close(seg->list_pb);
@@ -541,6 +618,7 @@ fail:
 
     av_opt_free(seg);
     av_freep(&seg->times);
+    av_freep(&seg->frames);
 
     avformat_free_context(oc);
     return ret;
@@ -566,6 +644,7 @@ static const AVOption options[] = {
     { "segment_time",      "set segment duration",                       OFFSET(time_str),AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
     { "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta_str), AV_OPT_TYPE_STRING, {.str = "0"}, 0, 0, E },
     { "segment_times",     "set segment split time points",              OFFSET(times_str),AV_OPT_TYPE_STRING,{.str = NULL},  0, 0,       E },
+    { "segment_frames",    "set segment split frame numbers",            OFFSET(frames_str),AV_OPT_TYPE_STRING,{.str = NULL},  0, 0,       E },
     { "segment_wrap",      "set number after which the index wraps",     OFFSET(segment_idx_wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E },
     { "segment_start_number", "set the sequence number of the first segment", OFFSET(segment_idx), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E },
 
-- 
1.7.9.5



More information about the ffmpeg-devel mailing list