[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