diff --git a/doc/ffmpeg.texi b/doc/ffmpeg.texi index ad235b78c4..1cbb1ccbdd 100644 --- a/doc/ffmpeg.texi +++ b/doc/ffmpeg.texi @@ -1249,6 +1249,13 @@ ffmpeg -i input.iamf -c:a copy -stream_group map=0=0:st=0:st=1:st=2:st=3 -stream -streamid 0:0 -streamid 1:1 -streamid 2:2 -streamid 3:3 output.mp4 @end example +@item -reinit_opts[:@var{stream_specifier}] pts=@var{pts}|@var{key}=@var{value}[:force_reinit=1][:@var{key}=@var{value},pts=@var{pts}...] (@emph{output,per-stream}) + +Creates a "," separated group of options to reconfigure an encoder at the +specified output @var{pts} in AV_TIME_BASE units. +The pseudo-option @var{force_reinit} ensures the encoder is fully reinitialized +instead of simply reconfigured. + @item -target @var{type} (@emph{output}) Specify target file type (@code{vcd}, @code{svcd}, @code{dvd}, @code{dv}, @code{dv50}). @var{type} may be prefixed with @code{pal-}, @code{ntsc-} or diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c index d38acffbdf..186b549102 100644 --- a/fftools/ffmpeg.c +++ b/fftools/ffmpeg.c @@ -416,6 +416,7 @@ static void frame_data_free(void *opaque, uint8_t *data) av_frame_side_data_free(&fd->side_data, &fd->nb_side_data); avcodec_parameters_free(&fd->par_enc); + av_dict_free(&fd->reinit_opts); av_free(data); } @@ -446,6 +447,7 @@ static int frame_data_ensure(AVBufferRef **dst, int writable) fd->par_enc = NULL; fd->side_data = NULL; fd->nb_side_data = 0; + fd->reinit_opts = NULL; if (fd_src->par_enc) { int ret = 0; @@ -454,6 +456,8 @@ static int frame_data_ensure(AVBufferRef **dst, int writable) ret = fd->par_enc ? avcodec_parameters_copy(fd->par_enc, fd_src->par_enc) : AVERROR(ENOMEM); + if (!ret && fd_src->reinit_opts) + ret = av_dict_copy(&fd->reinit_opts, fd_src->reinit_opts, 0); if (ret < 0) { av_buffer_unref(dst); av_buffer_unref(&src); diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h index 11682b34bb..c403acdb59 100644 --- a/fftools/ffmpeg.h +++ b/fftools/ffmpeg.h @@ -236,6 +236,7 @@ typedef struct OptionsContext { SpecifierOptList enc_time_bases; SpecifierOptList autoscale; SpecifierOptList bits_per_raw_sample; + SpecifierOptList enc_reinit_opts; SpecifierOptList enc_stats_pre; SpecifierOptList enc_stats_post; SpecifierOptList mux_stats; @@ -347,6 +348,8 @@ typedef struct OutputFilterOptions { AVFrameSideData **side_data; int nb_side_data; + const char *reinit_opts; + // for simple filtergraphs only, view specifier passed // along to the decoder const ViewSpecifier *vs; @@ -607,6 +610,11 @@ typedef struct Encoder { AVCodecContext *enc_ctx; + // initial encoder options + AVDictionary *encoder_opts; + // pts|key=value list of options to reinitialize encoder + char *reinit_opts; + uint32_t codec_tag; int flags; int flags2; @@ -716,6 +724,8 @@ typedef struct FrameData { AVFrameSideData **side_data; int nb_side_data; + + AVDictionary *reinit_opts; } FrameData; extern InputFile **input_files; diff --git a/fftools/ffmpeg_enc.c b/fftools/ffmpeg_enc.c index dfc7d5e9d8..8cf45bbf53 100644 --- a/fftools/ffmpeg_enc.c +++ b/fftools/ffmpeg_enc.c @@ -31,6 +31,7 @@ #include "libavutil/intreadwrite.h" #include "libavutil/log.h" #include "libavutil/mem.h" +#include "libavutil/opt.h" #include "libavutil/pixdesc.h" #include "libavutil/rational.h" #include "libavutil/time.h" @@ -49,6 +50,7 @@ typedef struct EncoderPriv { // number of packets received from the encoder uint64_t packets_encoded; + int got_first_packet; int opened; int attach_par; @@ -78,6 +80,7 @@ void enc_free(Encoder **penc) if (enc->enc_ctx) av_freep(&enc->enc_ctx->stats_in); avcodec_free_context(&enc->enc_ctx); + av_dict_free(&enc->encoder_opts); av_freep(penc); } @@ -96,6 +99,29 @@ static const AVClass enc_class = { .item_name = enc_item_name, }; +static int enc_realloc(Encoder *enc, const AVCodec *codec) +{ + EncoderPriv *ep = ep_from_enc(enc); + char *stats_in = NULL; + + if (enc->enc_ctx) + stats_in = enc->enc_ctx->stats_in; + avcodec_free_context(&enc->enc_ctx); + + ep->opened = 0; + ep->got_first_packet = 0; + + enc->enc_ctx = avcodec_alloc_context3(codec); + if (!enc->enc_ctx) { + av_freep(&stats_in); + return AVERROR(ENOMEM); + } + + enc->enc_ctx->stats_in = stats_in; + + return 0; +} + int enc_alloc(Encoder **penc, const AVCodec *codec, Scheduler *sch, unsigned sch_idx, void *log_parent) { @@ -181,7 +207,26 @@ static int hw_device_setup_for_encode(Encoder *e, AVCodecContext *enc_ctx, return 0; } -static int enc_reopen(void *opaque, const AVFrame *frame) +static int apply_enc_options(Encoder *e, AVDictionary **opts) +{ + AVCodecContext *enc_ctx = e->enc_ctx; + + int ret = av_opt_set_dict2(enc_ctx, opts, AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + av_log(e, AV_LOG_ERROR, "Error applying encoder options: %s\n", + av_err2str(ret)); + return ret; + } + + ret = check_avoptions(*opts); + if (ret < 0) + return ret; + + return 0; +} + +static int enc_reopen(void *opaque, const AVFrame *frame, + AVDictionary **extra_encoder_opts) { OutputStream *ost = opaque; InputStream *ist = ost->ist; @@ -190,9 +235,32 @@ static int enc_reopen(void *opaque, const AVFrame *frame) AVCodecContext *enc_ctx = e->enc_ctx; Decoder *dec = NULL; const AVCodec *enc = enc_ctx->codec; + AVDictionary *encoder_opts = NULL; FrameData *fd; + int threads_manual; int ret; + ret = av_dict_copy(&encoder_opts, ost->enc->encoder_opts, 0); + if (ret < 0) + return ret; + + threads_manual = !!av_dict_get(encoder_opts, "threads", NULL, 0); + ret = apply_enc_options(e, &encoder_opts); + av_dict_free(&encoder_opts); + if (ret < 0) + return ret; + + if (extra_encoder_opts) { + threads_manual |= !!av_dict_get(*extra_encoder_opts, "threads", NULL, 0); + ret = apply_enc_options(e, extra_encoder_opts); + if (ret < 0) + return ret; + } + + // default to automatic thread count + if (!threads_manual) + enc_ctx->thread_count = 0; + // frame is always non-NULL for audio and video av_assert0(frame || (enc->type != AVMEDIA_TYPE_VIDEO && enc->type != AVMEDIA_TYPE_AUDIO)); @@ -366,7 +434,7 @@ int enc_open(void *opaque, const AVFrame *frame) if (ep->opened) return 0; - ret = enc_reopen(opaque, frame); + ret = enc_reopen(opaque, frame, NULL); if (ret < 0) return ret; @@ -698,9 +766,19 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame, return AVERROR(ENOMEM); fd->wallclock[LATENCY_PROBE_ENC_POST] = av_gettime_relative(); + // attach extradata to first packet if the encoder was reinitialized + if (!ep->got_first_packet && ep->packets_encoded && enc->extradata_size) { + uint8_t *extradata = av_packet_new_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, + enc->extradata_size); + if (!extradata) + return AVERROR(ENOMEM); + memcpy(extradata, enc->extradata, enc->extradata_size); + ep->got_first_packet = 1; + } // attach stream parameters to first packet if requested avcodec_parameters_free(&fd->par_enc); - if (ep->attach_par && !ep->packets_encoded) { + if (!ep->packets_encoded) { + if (ep->attach_par) { fd->par_enc = avcodec_parameters_alloc(); if (!fd->par_enc) return AVERROR(ENOMEM); @@ -708,6 +786,8 @@ static int encode_frame(OutputFile *of, OutputStream *ost, AVFrame *frame, ret = avcodec_parameters_from_context(fd->par_enc, enc); if (ret < 0) return ret; + } + ep->got_first_packet = 1; } pkt->flags |= AV_PKT_FLAG_TRUSTED; @@ -878,12 +958,61 @@ static int flush_encoder(OutputStream *ost, EncoderThread *et) return ret; } +static int reinit_encoder(OutputStream *ost, EncoderThread *et) +{ + Encoder *e = ost->enc; + AVDictionary *copy = NULL; + const FrameData *fd = frame_data_c(et->frame); + int force_reinit, ret = AVERROR_BUG; + + ret = av_dict_copy(©, fd->reinit_opts, 0); + if (ret < 0) + return ret; + + force_reinit = !!av_dict_get(copy, "force_reinit", NULL, 0); + if (force_reinit) + av_dict_set(©, "force_reinit", NULL, 0); + // Lets try a graceful reconfiguration first + else if (e->enc_ctx->codec->capabilities & AV_CODEC_CAP_RECONF) { + ret = avcodec_encode_reconfigure(e->enc_ctx, ©); + if (!ret) + goto end; + + ret = av_dict_copy(©, fd->reinit_opts, AV_DICT_DONT_OVERWRITE); + if (ret < 0) + goto end; + av_dict_set(©, "force_reinit", NULL, 0); + + av_log(e, AV_LOG_INFO, "Could not reconfigure the encoder." + " Trying to restart it instead\n"); + } + + // Go ahead and do a full restart of the encoder + ret = flush_encoder(ost, et); + if (ret < 0 && ret != AVERROR_EOF) + goto end; + + ret = enc_realloc(e, e->enc_ctx->codec); + if (ret < 0) + goto end; + av_log(e, AV_LOG_DEBUG, "Restarting encoder\n"); + ret = enc_reopen(ost, et->frame, ©); + if (ret < 0) + goto end; + + ret = 0; +end: + av_dict_free(©); + return ret; +} + int encoder_thread(void *arg) { OutputStream *ost = arg; Encoder *e = ost->enc; EncoderPriv *ep = ep_from_enc(e); EncoderThread et; + const FrameData *fd; int ret = 0, input_status = 0; int name_set = 0; @@ -927,6 +1056,16 @@ int encoder_thread(void *arg) name_set = 1; } + fd = frame_data_c(et.frame); + if (fd && fd->reinit_opts) { + ret = reinit_encoder(ost, &et); + if (ret < 0) { + av_log(e, AV_LOG_ERROR, "Error reconfiguring or restarting encoder: %s\n", + av_err2str(ret)); + goto finish; + } + } + ret = frame_encode(ost, et.frame, et.pkt); av_packet_unref(et.pkt); diff --git a/fftools/ffmpeg_filter.c b/fftools/ffmpeg_filter.c index 79528a784d..114a694c0c 100644 --- a/fftools/ffmpeg_filter.c +++ b/fftools/ffmpeg_filter.c @@ -187,6 +187,11 @@ typedef struct FPSConvContext { int framerate_clip; } FPSConvContext; +typedef struct ReinitOpts { + int64_t pts; + AVDictionary *dict; +} ReinitOpts; + typedef struct OutputFilterPriv { OutputFilter ofilter; @@ -246,6 +251,9 @@ typedef struct OutputFilterPriv { int64_t next_pts; FPSConvContext fps; + AVFifo *reinit_opts_fifo; + ReinitOpts reinit_opts; + unsigned flags; } OutputFilterPriv; @@ -682,6 +690,7 @@ static OutputFilter *ofilter_alloc(FilterGraph *fg, enum AVMediaType type) ofilter->graph = fg; ofilter->type = type; av_opt_set_defaults(ofp); + ofp->reinit_opts = (ReinitOpts){ .pts = AV_NOPTS_VALUE }; ofilter->index = fg->nb_outputs - 1; snprintf(ofp->log_name, sizeof(ofp->log_name), "%co%d", @@ -822,6 +831,75 @@ static int set_channel_layout(OutputFilterPriv *f, const AVChannelLayout *layout return 0; } +static int parse_reinit_opts(AVFifo **pout, const char *opts, void *logctx) +{ + AVFifo *out; + int ret = AVERROR_BUG; + char *ptr, *str, *substr = NULL; + const char *token; + + str = av_strdup(opts); + if (!str) + return AVERROR(ENOMEM); + + out = av_fifo_alloc2(1, sizeof(ReinitOpts), AV_FIFO_FLAG_AUTO_GROW); + if (!out) { + ret = AVERROR(ENOMEM); + goto end; + } + + token = av_strtok(str, ",", &ptr); + while (token) { + ReinitOpts o = { 0 }; + const char *subtoken; + char *subptr, *endptr; + + substr = av_strdup(token); + if (!substr) { + ret = AVERROR(ENOMEM); + goto end; + } + subtoken = av_strtok(substr, "|", &subptr); + if (subtoken && subptr) { + if (!av_strstart(subtoken, "pts=", &subtoken)) { + av_log(logctx, AV_LOG_ERROR, "Invalid reinit identifier\n"); + ret = AVERROR(ENOMEM); + goto end; + } + o.pts = strtoll(subtoken, &endptr, 0); + if (*endptr || o.pts < 0) { + ret = AVERROR(EINVAL); + goto end; + } + ret = av_dict_parse_string(&o.dict, subptr, "=", ":", 0); + if (ret < 0) { + av_log(logctx, AV_LOG_ERROR, "Error parsing encoder options\n"); + goto end; + } + ret = av_fifo_write(out, &o, 1); + if (ret < 0) { + av_dict_free(&o.dict); + goto end; + } + } else { + ret = AVERROR(EINVAL); + goto end; + } + av_freep(&substr); + if (ptr) + ptr += strspn(ptr, " \n\t\r"); + token = av_strtok(NULL, ",", &ptr); + } + + ret = 0; +end: + *pout = out; + av_free(substr); + av_free(str); + + return ret; +} + int ofilter_bind_enc(OutputFilter *ofilter, unsigned sched_idx_enc, const OutputFilterOptions *opts) { @@ -848,6 +926,12 @@ int ofilter_bind_enc(OutputFilter *ofilter, unsigned sched_idx_enc, if (!ofilter->output_name) return AVERROR(EINVAL); + if (opts->reinit_opts) { + ret = parse_reinit_opts(&ofp->reinit_opts_fifo, opts->reinit_opts, ofilter); + if (ret < 0) + return ret; + } + ret = av_dict_copy(&ofp->sws_opts, opts->sws_opts, 0); if (ret < 0) return ret; @@ -1072,6 +1156,12 @@ void fg_free(FilterGraph **pfg) av_freep(&ofilter->name); av_freep(&ofilter->output_name); av_freep(&ofilter->apad); + av_dict_free(&ofp->reinit_opts.dict); + if (ofp->reinit_opts_fifo) { + while (av_fifo_read(ofp->reinit_opts_fifo, &ofp->reinit_opts, 1) >= 0) + av_dict_free(&ofp->reinit_opts.dict); + av_fifo_freep2(&ofp->reinit_opts_fifo); + } av_channel_layout_uninit(&ofp->ch_layout); av_frame_side_data_free(&ofp->side_data, &ofp->nb_side_data); av_freep(&fg->outputs[j]); @@ -3088,7 +3178,7 @@ static const char *unknown_if_null(const char *str) } static int send_frame(FilterGraph *fg, FilterGraphThread *fgt, - InputFilter *ifilter, AVFrame *frame) + InputFilter *ifilter, AVFrame *frame, int force_reinit) { FilterGraphPriv *fgp = fgp_from_fg(fg); InputFilterPriv *ifp = ifp_from_ifilter(ifilter); @@ -3165,7 +3255,7 @@ static int send_frame(FilterGraph *fg, FilterGraphThread *fgt, } /* (re)init the graph if possible, otherwise buffer the frame and return */ - if (need_reinit || !fgt->graph) { + if (need_reinit || force_reinit || !fgt->graph) { AVFrame *tmp = av_frame_alloc(); if (!tmp) @@ -3211,6 +3301,8 @@ static int send_frame(FilterGraph *fg, FilterGraphThread *fgt, av_bprintf(&reason, "downmix medatata changed, "); if (need_reinit & HWACCEL_CHANGED) av_bprintf(&reason, "hwaccel changed, "); + if (force_reinit) + av_bprintf(&reason, "reinitialization arguments were provided, "); if (reason.len > 1) reason.str[reason.len - 2] = '\0'; // remove last comma av_log(fg, AV_LOG_INFO, "Reconfiguring filter graph%s%s\n", reason.len ? " because " : "", reason.str); @@ -3312,6 +3404,42 @@ fail: return AVERROR(ENOMEM); } +static int check_reinit(FilterGraph *fg, FilterGraphThread *fgt) +{ + int ret, reinit = 0; + + for (unsigned i = 0; i < fg->nb_outputs; i++) { + OutputFilterPriv *ofp = ofp_from_ofilter(fg->outputs[i]); + + if (ofp->reinit_opts.pts == AV_NOPTS_VALUE && ofp->reinit_opts_fifo && + av_fifo_can_read(ofp->reinit_opts_fifo)) { + av_fifo_read(ofp->reinit_opts_fifo, &ofp->reinit_opts, 1); + } + if (ofp->reinit_opts.pts != AV_NOPTS_VALUE && + ofp->reinit_opts.pts <= av_rescale_q(fgt->frame->pts, fgt->frame->time_base, AV_TIME_BASE_Q)) { + FrameData *fd = frame_data(fgt->frame); + if (!fd) + return AVERROR(ENOMEM); + + av_dict_free(&fd->reinit_opts); + ret = av_dict_copy(&fd->reinit_opts, ofp->reinit_opts.dict, 0); + if (ret < 0) + return ret; + + av_opt_set_dict(ofp, &ofp->reinit_opts.dict); + av_dict_free(&ofp->reinit_opts.dict); + + if (av_fifo_can_read(ofp->reinit_opts_fifo)) + av_fifo_read(ofp->reinit_opts_fifo, &ofp->reinit_opts, 1); + else + ofp->reinit_opts = (ReinitOpts){ .pts = AV_NOPTS_VALUE }; + reinit = 1; + } + } + + return reinit; +} + static int filter_thread(void *arg) { FilterGraphPriv *fgp = arg; @@ -3378,7 +3506,9 @@ static int filter_thread(void *arg) ret = sub2video_frame(ifilter, (fgt.frame->buf[0] || hb_frame) ? fgt.frame : NULL, !fgt.graph); } else if (fgt.frame->buf[0]) { - ret = send_frame(fg, &fgt, ifilter, fgt.frame); + ret = check_reinit(fg, &fgt); + if (ret >= 0) + ret = send_frame(fg, &fgt, ifilter, fgt.frame, ret); } else { av_assert1(o == FRAME_OPAQUE_EOF); ret = send_eof(&fgt, ifilter, fgt.frame->pts, fgt.frame->time_base); diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c index 702a39c8d7..f80b4427b4 100644 --- a/fftools/ffmpeg_mux_init.c +++ b/fftools/ffmpeg_mux_init.c @@ -896,6 +896,7 @@ ost_bind_filter(const Muxer *mux, MuxStream *ms, OutputFilter *ofilter, 0 : mux->of.start_time, .vs = vs, .nb_threads = -1, + .reinit_opts = ost->enc->reinit_opts, .flags = OFILTER_FLAG_DISABLE_CONVERT * !!keep_pix_fmt | OFILTER_FLAG_AUTOSCALE * !!autoscale | @@ -1255,7 +1256,7 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type, AVIOContext *s = NULL; char *buf = NULL, *arg = NULL; const char *enc_stats_pre = NULL, *enc_stats_post = NULL, *mux_stats = NULL; - const char *enc_time_base = NULL, *preset = NULL; + const char *enc_time_base = NULL, *enc_reinit_opts = NULL, *preset = NULL; ret = filter_codec_opts(o->g->codec_opts, enc->id, oc, st, enc, &encoder_opts, @@ -1295,6 +1296,16 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type, goto fail; } + opt_match_per_stream_str(ost, &o->enc_reinit_opts, oc, st, &enc_reinit_opts); + if (enc_reinit_opts && + (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { + ost->enc->reinit_opts = av_strdup(enc_reinit_opts); + if (!ost->enc->reinit_opts) { + ret = AVERROR(ENOMEM); + goto fail; + } + } + opt_match_per_stream_str(ost, &o->enc_stats_pre, oc, st, &enc_stats_pre); if (enc_stats_pre && (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) { @@ -1356,6 +1367,9 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type, threads_manual = !!av_dict_get(encoder_opts, "threads", NULL, 0); + ret = av_dict_copy(&ost->enc->encoder_opts, encoder_opts, 0); + if (ret < 0) + goto fail; ret = av_opt_set_dict2(ost->enc->enc_ctx, &encoder_opts, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(ost, AV_LOG_ERROR, "Error applying encoder options: %s\n", diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c index 12fbd3bda8..fdbc0fdc13 100644 --- a/fftools/ffmpeg_opt.c +++ b/fftools/ffmpeg_opt.c @@ -1831,6 +1831,11 @@ const OptionDef options[] = { { .off = OFFSET(mux_stats_fmt) }, "format of the stats written with -stats_mux_pre" }, + { "reinit_opts", OPT_TYPE_STRING, OPT_PERSTREAM | OPT_EXPERT | OPT_OUTPUT, + { .off = OFFSET(enc_reinit_opts) }, + "List of encoder options to use to reinitialize the encoder at given timestamps", + "pts1|video_size=size:g=12,pts2|video_size=size..." }, + /* video options */ { "vframes", OPT_TYPE_FUNC, OPT_VIDEO | OPT_FUNC_ARG | OPT_PERFILE | OPT_OUTPUT | OPT_EXPERT | OPT_HAS_CANON, { .func_arg = opt_video_frames },