[FFmpeg-devel] [Patch] Add input swap functionality to movie filter (src_movie.c)

Felt, Patrick Patrick.Felt at echostar.com
Wed May 4 02:15:26 CEST 2016


Afternoon all,
  I apologize if this isn’t right way to submit a patch. Attached is a patch for src_movie.c that modifies it to allow for one to use the process_command() infrastructure to swap the input file on the fly.  I’ve added a few options to the filter and exposed filename to the process_command subsystem.  Useful for live streaming and “changing the channel”.

————————
diff --git a/libavfilter/src_movie.c b/libavfilter/src_movie.c
index 0bdcad4..604d4f6 100644
--- a/libavfilter/src_movie.c
+++ b/libavfilter/src_movie.c
@@ -44,10 +44,18 @@
 #include "internal.h"
 #include "video.h"

+/* libavfilter documentation says that filter init will be called only once.  ffmpeg calls the init twice to enable it to validate
+ * the complex filtering has the right input and output pads. this allows us to bypass the first call if we've specified a stream
+ * specifier string */
+static int initRun=0;
+static int uninitRun=0;
+
 typedef struct MovieStream {
     AVStream *st;
     AVCodecContext *codec_ctx;
     int done;
+    int64_t lastPts;    /* this is used exclusively by the reset code */
+    int64_t basePts;    /* idem */
 } MovieStream;

 typedef struct MovieContext {
@@ -68,6 +76,10 @@ typedef struct MovieContext {
     int max_stream_index; /**< max stream # actually used for output */
     MovieStream *st; /**< array of all streams, one per output */
     int *out_index; /**< stream number -> output number map, or -1 */
+
+    int isInitialized; /* allows us to determine if we've initialized already so when we reset to a new file we don't try to allocate pads again */
+    int ptsMode; /* determine how we handle pts when we reset to a new file */
+    int restartMode; /* some stream types (eg: pipes) don't like being opened more than once.  see the note with the global statics */
 } MovieContext;

 #define OFFSET(x) offsetof(MovieContext, x)
@@ -84,6 +96,10 @@ static const AVOption movie_options[]= {
     { "streams",      "set streams",             OFFSET(stream_specs), AV_OPT_TYPE_STRING, {.str =  0},  CHAR_MAX, CHAR_MAX, FLAGS },
     { "s",            "set streams",             OFFSET(stream_specs), AV_OPT_TYPE_STRING, {.str =  0},  CHAR_MAX, CHAR_MAX, FLAGS },
     { "loop",         "set loop count",          OFFSET(loop_count),   AV_OPT_TYPE_INT,    {.i64 =  1},  0,        INT_MAX, FLAGS },
+    { "pts_mode",     "set pts mode",            OFFSET(ptsMode),      AV_OPT_TYPE_INT,    {.i64 =  1},  1,        INT_MAX, FLAGS },
+    { "pm",           "set pts mode",            OFFSET(ptsMode),      AV_OPT_TYPE_INT,    {.i64 =  1},  1,        INT_MAX, FLAGS },
+    { "restart_mode", "set restart mode",        OFFSET(restartMode),  AV_OPT_TYPE_INT,    {.i64 =  1},  0,        INT_MAX, FLAGS },
+    { "rm",           "set restart mode",        OFFSET(restartMode),  AV_OPT_TYPE_INT,    {.i64 =  1},  0,        INT_MAX, FLAGS },
     { NULL },
 };

@@ -200,26 +216,35 @@ static av_cold int movie_common_init(AVFilterContext *ctx)
 {
     MovieContext *movie = ctx->priv;
     AVInputFormat *iformat = NULL;
-    int64_t timestamp;
-    int nb_streams = 1, ret, i;
-    char default_streams[16], *stream_specs, *spec, *cursor;
+    int64_t timestamp, maxPts=0;
+    int nb_streams = 1, ret, i, result=0;
+    char *stream_specs = NULL, *spec, *cursor, *ptr;
     char name[16];
     AVStream *st;

     if (!movie->file_name) {
         av_log(ctx, AV_LOG_ERROR, "No filename provided!\n");
-        return AVERROR(EINVAL);
+        result = AVERROR(EINVAL);
+        goto done;
     }

     movie->seek_point = movie->seek_point_d * 1000000 + 0.5;

-    stream_specs = movie->stream_specs;
-    if (!stream_specs) {
-        snprintf(default_streams, sizeof(default_streams), "d%c%d",
+    if (!movie->stream_specs) {
+        /* we don't have a stream_spec specified by the user, let's default to either audio or video based on filter name */
+        stream_specs = (char *)av_malloc(16);
+        snprintf(stream_specs, 16, "d%c%d",
                  !strcmp(ctx->filter->name, "amovie") ? 'a' : 'v',
                  movie->stream_index);
-        stream_specs = default_streams;
+    } else {
+        stream_specs = av_strdup(movie->stream_specs);
+    }
+
+    if (!stream_specs) {
+        result = AVERROR(ENOMEM);
+        goto done;
     }
+
     for (cursor = stream_specs; *cursor; cursor++)
         if (*cursor == '+')
             nb_streams++;
@@ -227,7 +252,42 @@ static av_cold int movie_common_init(AVFilterContext *ctx)
     if (movie->loop_count != 1 && nb_streams != 1) {
         av_log(ctx, AV_LOG_ERROR,
                "Loop with several streams is currently unsupported\n");
-        return AVERROR_PATCHWELCOME;
+        result = AVERROR_PATCHWELCOME;
+        goto done;
+    }
+
+    /* if this is the first time we've ever gone through the init function, let's just fake the pad creation
+     * for what we'll end up doing long term */
+    if ((movie->restartMode == 2) && (!initRun)) {
+        int padCount;
+
+        initRun = 1;
+
+        /* parse through the stream_spec to see what we're supposed to have for pads.  */
+        for (ptr=stream_specs, padCount=0; ; padCount++, ptr=NULL) {
+            AVFilterPad pad = { .config_props=movie_config_output_props, .request_frame=movie_request_frame };
+
+            spec = av_strtok(ptr, "+", &cursor);
+            if (!spec) {
+                break;
+            }
+            snprintf(name, sizeof(name), "out%d", padCount);
+            pad.name = av_strdup(name);
+            if (!pad.name) {
+                result = AVERROR(ENOMEM);
+                goto done;
+            }
+
+            if (spec[1] == 'a') {
+                pad.type=AVMEDIA_TYPE_AUDIO;
+                av_log(ctx, AV_LOG_DEBUG, "creating a test pad of type audio\n");
+            } else {
+                pad.type=AVMEDIA_TYPE_VIDEO;
+                av_log(ctx, AV_LOG_DEBUG, "creating a test pad of type video\n");
+            }
+            ff_insert_outpad(ctx, padCount, &pad);
+        }
+        goto done;
     }

     av_register_all();
@@ -239,7 +299,8 @@ static av_cold int movie_common_init(AVFilterContext *ctx)
     if ((ret = avformat_open_input(&movie->format_ctx, movie->file_name, iformat, NULL)) < 0) {
         av_log(ctx, AV_LOG_ERROR,
                "Failed to avformat_open_input '%s'\n", movie->file_name);
-        return ret;
+        result =  ret;
+        goto done;
     }
     if ((ret = avformat_find_stream_info(movie->format_ctx, NULL)) < 0)
         av_log(ctx, AV_LOG_WARNING, "Failed to find stream info\n");
@@ -253,43 +314,83 @@ static av_cold int movie_common_init(AVFilterContext *ctx)
                 av_log(ctx, AV_LOG_ERROR,
                        "%s: seek value overflow with start_time:%"PRId64" seek_point:%"PRId64"\n",
                        movie->file_name, movie->format_ctx->start_time, movie->seek_point);
-                return AVERROR(EINVAL);
+                result = AVERROR(EINVAL);
+                goto done;
             }
             timestamp += movie->format_ctx->start_time;
         }
         if ((ret = av_seek_frame(movie->format_ctx, -1, timestamp, AVSEEK_FLAG_BACKWARD)) < 0) {
             av_log(ctx, AV_LOG_ERROR, "%s: could not seek to position %"PRId64"\n",
                    movie->file_name, timestamp);
-            return ret;
+            result = ret;
+            goto done;
         }
     }

     for (i = 0; i < movie->format_ctx->nb_streams; i++)
         movie->format_ctx->streams[i]->discard = AVDISCARD_ALL;

-    movie->st = av_calloc(nb_streams, sizeof(*movie->st));
-    if (!movie->st)
-        return AVERROR(ENOMEM);
+    /* isInitialized will be set if the current call is because of the process_command
+     * if we are just resetting, we don't want to stomp over already set up memory
+     * structures. */
+    if (!movie->isInitialized) {
+        movie->st = av_calloc(nb_streams, sizeof(*movie->st));
+    }
+    if (!movie->st) {
+        result = AVERROR(ENOMEM);
+        goto done;
+    }
+
+    /* if we are resetting, we need to find the maximum pts of all streams.  when starting a stream
+     * the pts is assumed to be 0.  we also cannot just use the pts in the stream or we'll end up
+     * looking like we're getting in a bunch of frames that are from some time in the past.  by
+     * finding the max pts of all streams we can re-base all streams to something common that is
+     * at least current time for the overall output. the variables used here are set in
+     * movie_push_frame() */
+    for (i = 0; i < nb_streams; i++) {
+        if (movie->st[i].lastPts > maxPts) {
+            maxPts = movie->st[i].lastPts;
+        }
+    }

     for (i = 0; i < nb_streams; i++) {
         spec = av_strtok(stream_specs, "+", &cursor);
-        if (!spec)
-            return AVERROR_BUG;
+        if (!spec) {
+            result = AVERROR_BUG;
+            goto done;
+        }
         stream_specs = NULL; /* for next strtok */
         st = find_stream(ctx, movie->format_ctx, spec);
-        if (!st)
-            return AVERROR(EINVAL);
+        if (!st) {
+            result = AVERROR(EINVAL);
+            goto done;
+        }
         st->discard = AVDISCARD_DEFAULT;
         movie->st[i].st = st;
+        if (movie->isInitialized) {
+            /* we must be in a reset condition.  we need to set up the pts counter as describe above */
+            movie->st[i].basePts = maxPts;
+            movie->st[i].lastPts = 0;
+        } else {
+            movie->st[i].basePts = 0;
+            movie->st[i].lastPts = 0;
+        }
         movie->max_stream_index = FFMAX(movie->max_stream_index, st->index);
     }
-    if (av_strtok(NULL, "+", &cursor))
-        return AVERROR_BUG;
+    if (av_strtok(NULL, "+", &cursor)) {
+        result = AVERROR_BUG;
+        goto done;
+    }

-    movie->out_index = av_calloc(movie->max_stream_index + 1,
+    /* again, we don't want to stomp any set up memory structures if we are just here for a reset */
+    if (!movie->isInitialized) {
+        movie->out_index = av_calloc(movie->max_stream_index + 1,
                                  sizeof(*movie->out_index));
-    if (!movie->out_index)
-        return AVERROR(ENOMEM);
+    }
+    if (!movie->out_index) {
+        result = AVERROR(ENOMEM);
+        goto done;
+    }
     for (i = 0; i <= movie->max_stream_index; i++)
         movie->out_index[i] = -1;
     for (i = 0; i < nb_streams; i++) {
@@ -298,19 +399,28 @@ static av_cold int movie_common_init(AVFilterContext *ctx)
         snprintf(name, sizeof(name), "out%d", i);
         pad.type          = movie->st[i].st->codecpar->codec_type;
         pad.name          = av_strdup(name);
-        if (!pad.name)
-            return AVERROR(ENOMEM);
+        if (!pad.name) {
+            result = AVERROR(ENOMEM);
+            goto done;
+        }
         pad.config_props  = movie_config_output_props;
         pad.request_frame = movie_request_frame;
-        ff_insert_outpad(ctx, i, &pad);
+        /* again, don't stomp set up memory stuff */
+        if (!movie->isInitialized) {
+            ff_insert_outpad(ctx, i, &pad);
+        }
         ret = open_stream(ctx, &movie->st[i]);
-        if (ret < 0)
-            return ret;
+        if (ret < 0) {
+            result = ret;
+            goto done;
+        }
         if ( movie->st[i].st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
             !movie->st[i].st->codecpar->channel_layout) {
             ret = guess_channel_layout(&movie->st[i], i, ctx);
-            if (ret < 0)
-                return ret;
+            if (ret < 0) {
+                result = ret;
+                goto done;
+            }
         }
     }

@@ -318,7 +428,13 @@ static av_cold int movie_common_init(AVFilterContext *ctx)
            movie->seek_point, movie->format_name, movie->file_name,
            movie->stream_index);

-    return 0;
+    movie->isInitialized = 1;
+
+done:
+    if (stream_specs) {
+        av_free(stream_specs);
+    }
+    return result;
 }

 static av_cold void movie_uninit(AVFilterContext *ctx)
@@ -326,9 +442,16 @@ static av_cold void movie_uninit(AVFilterContext *ctx)
     MovieContext *movie = ctx->priv;
     int i;

+    /* if this is our first time through un-init we didn't actually init anything.
+     * don't try to free it */
+    if ((movie->restartMode == 2) && (!uninitRun)) {
+        uninitRun = 1;
+        return;
+    }
+
     for (i = 0; i < ctx->nb_outputs; i++) {
         av_freep(&ctx->output_pads[i].name);
-        if (movie->st[i].st)
+        if (movie->st && movie->st[i].st)
             avcodec_free_context(&movie->st[i].codec_ctx);
     }
     av_freep(&movie->st);
@@ -548,7 +671,21 @@ static int movie_push_frame(AVFilterContext *ctx, unsigned out_id)
         return 0;
     }

-    frame->pts = av_frame_get_best_effort_timestamp(frame);
+    /* based on our pts_mode we will change how we are going to play with the pts for the output frames.
+     * pts mode 1 is the default layout; take the pts in the frame as before.
+     * pts mode 2 is using our re-basing logic to allow for resetting to a new input file */
+    /* TODO: we might see a performance increase here if we were to change the logic up to always
+     * add basePts.  wouldn't we get rid of some jumps and compares? */
+    switch(movie->ptsMode) {
+        case 2:
+            frame->pts = av_frame_get_best_effort_timestamp(frame) + st->basePts;
+            st->lastPts = frame->pts;
+            break;
+        case 1:
+        default:
+            frame->pts = av_frame_get_best_effort_timestamp(frame);
+            break;
+    }
     ff_dlog(ctx, "movie_push_frame(): file:'%s' %s\n", movie->file_name,
             describe_frame_to_str((char[1024]){0}, 1024, frame, frame_type, outlink));

@@ -582,6 +719,30 @@ static int movie_request_frame(AVFilterLink *outlink)
     }
 }

+static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
+                           char *res, int res_len, int flags) {
+    int result = 0;
+    MovieContext *movie = ctx->priv;
+
+    av_log(ctx, AV_LOG_DEBUG, "in parse_command: %s\n", cmd);
+    if (strcmp(cmd, "reset") == 0) {
+        if (movie->format_ctx) {
+            avformat_close_input(&movie->format_ctx);
+        }
+        result = movie_common_init(ctx);
+    } else if (strcmp(cmd, "filename") == 0) {
+        if (movie->file_name) {
+            av_freep(&movie->file_name);
+        }
+        movie->file_name = av_strdup(args);
+        movie->format_name = NULL;
+    } else if (strcmp(cmd, "format") == 0) {
+        movie->format_name = av_strdup(args);
+    }
+    av_log(ctx, AV_LOG_ERROR, "leaving parse_command\n");
+    return result;
+}
+
 #if CONFIG_MOVIE_FILTER

 AVFILTER_DEFINE_CLASS(movie);
@@ -594,6 +755,7 @@ AVFilter ff_avsrc_movie = {
     .init          = movie_common_init,
     .uninit        = movie_uninit,
     .query_formats = movie_query_formats,
+    .process_command = process_command,

     .inputs    = NULL,
     .outputs   = NULL,
@@ -614,6 +776,7 @@ AVFilter ff_avsrc_amovie = {
     .init          = movie_common_init,
     .uninit        = movie_uninit,
     .query_formats = movie_query_formats,
+    .process_command = process_command,

     .inputs     = NULL,
     .outputs    = NULL,



More information about the ffmpeg-devel mailing list