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, _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel