fftools/ffmpeg: rewrite checking whether codec AVOptions have been used

Share the code between encoding and decoding. Instead of checking every
stream's options dictionary (which is also used for other purposes),
track all used options in a dedicated dictionary.
release/7.1
Anton Khirnov 12 months ago
parent fc446eea05
commit f1664aabb1
  1. 17
      fftools/cmdutils.c
  2. 4
      fftools/cmdutils.h
  3. 50
      fftools/ffmpeg.c
  4. 4
      fftools/ffmpeg.h
  5. 50
      fftools/ffmpeg_demux.c
  6. 1
      fftools/ffmpeg_mux.c
  7. 3
      fftools/ffmpeg_mux.h
  8. 56
      fftools/ffmpeg_mux_init.c
  9. 18
      fftools/ffmpeg_opt.c
  10. 2
      fftools/ffplay.c
  11. 2
      fftools/ffprobe.c

@ -986,7 +986,7 @@ int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec)
int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id, int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id,
AVFormatContext *s, AVStream *st, const AVCodec *codec, AVFormatContext *s, AVStream *st, const AVCodec *codec,
AVDictionary **dst) AVDictionary **dst, AVDictionary **opts_used)
{ {
AVDictionary *ret = NULL; AVDictionary *ret = NULL;
const AVDictionaryEntry *t = NULL; const AVDictionaryEntry *t = NULL;
@ -1013,6 +1013,7 @@ int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id,
while (t = av_dict_iterate(opts, t)) { while (t = av_dict_iterate(opts, t)) {
const AVClass *priv_class; const AVClass *priv_class;
char *p = strchr(t->key, ':'); char *p = strchr(t->key, ':');
int used = 0;
/* check stream specification in opt name */ /* check stream specification in opt name */
if (p) { if (p) {
@ -1030,15 +1031,21 @@ int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id,
!codec || !codec ||
((priv_class = codec->priv_class) && ((priv_class = codec->priv_class) &&
av_opt_find(&priv_class, t->key, NULL, flags, av_opt_find(&priv_class, t->key, NULL, flags,
AV_OPT_SEARCH_FAKE_OBJ))) AV_OPT_SEARCH_FAKE_OBJ))) {
av_dict_set(&ret, t->key, t->value, 0); av_dict_set(&ret, t->key, t->value, 0);
else if (t->key[0] == prefix && used = 1;
} else if (t->key[0] == prefix &&
av_opt_find(&cc, t->key + 1, NULL, flags, av_opt_find(&cc, t->key + 1, NULL, flags,
AV_OPT_SEARCH_FAKE_OBJ)) AV_OPT_SEARCH_FAKE_OBJ)) {
av_dict_set(&ret, t->key + 1, t->value, 0); av_dict_set(&ret, t->key + 1, t->value, 0);
used = 1;
}
if (p) if (p)
*p = ':'; *p = ':';
if (used && opts_used)
av_dict_set(opts_used, t->key, "", 0);
} }
*dst = ret; *dst = ret;
@ -1063,7 +1070,7 @@ int setup_find_stream_info_opts(AVFormatContext *s,
for (int i = 0; i < s->nb_streams; i++) { for (int i = 0; i < s->nb_streams; i++) {
ret = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id, ret = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id,
s, s->streams[i], NULL, &opts[i]); s, s->streams[i], NULL, &opts[i], NULL);
if (ret < 0) if (ret < 0)
goto fail; goto fail;
} }

@ -371,11 +371,13 @@ int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec);
* @param codec The particular codec for which the options should be filtered. * @param codec The particular codec for which the options should be filtered.
* If null, the default one is looked up according to the codec id. * If null, the default one is looked up according to the codec id.
* @param dst a pointer to the created dictionary * @param dst a pointer to the created dictionary
* @param opts_used if non-NULL, every option stored in dst is also stored here,
* with specifiers preserved
* @return a non-negative number on success, a negative error code on failure * @return a non-negative number on success, a negative error code on failure
*/ */
int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id, int filter_codec_opts(const AVDictionary *opts, enum AVCodecID codec_id,
AVFormatContext *s, AVStream *st, const AVCodec *codec, AVFormatContext *s, AVStream *st, const AVCodec *codec,
AVDictionary **dst); AVDictionary **dst, AVDictionary **opts_used);
/** /**
* Setup AVCodecContext options for avformat_find_stream_info(). * Setup AVCodecContext options for avformat_find_stream_info().

@ -474,6 +474,56 @@ const FrameData *packet_data_c(AVPacket *pkt)
return ret < 0 ? NULL : (const FrameData*)pkt->opaque_ref->data; return ret < 0 ? NULL : (const FrameData*)pkt->opaque_ref->data;
} }
int check_avoptions_used(const AVDictionary *opts, const AVDictionary *opts_used,
void *logctx, int decode)
{
const AVClass *class = avcodec_get_class();
const AVClass *fclass = avformat_get_class();
const int flag = decode ? AV_OPT_FLAG_DECODING_PARAM :
AV_OPT_FLAG_ENCODING_PARAM;
const AVDictionaryEntry *e = NULL;
while ((e = av_dict_iterate(opts, e))) {
const AVOption *option, *foption;
char *optname, *p;
if (av_dict_get(opts_used, e->key, NULL, 0))
continue;
optname = av_strdup(e->key);
if (!optname)
return AVERROR(ENOMEM);
p = strchr(optname, ':');
if (p)
*p = 0;
option = av_opt_find(&class, optname, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
foption = av_opt_find(&fclass, optname, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
av_freep(&optname);
if (!option || foption)
continue;
if (!(option->flags & flag)) {
av_log(logctx, AV_LOG_ERROR, "Codec AVOption %s (%s) is not a %s "
"option.\n", e->key, option->help ? option->help : "",
decode ? "decoding" : "encoding");
return AVERROR(EINVAL);
}
av_log(logctx, AV_LOG_WARNING, "Codec AVOption %s (%s) has not been used "
"for any stream. The most likely reason is either wrong type "
"(e.g. a video option with no video streams) or that it is a "
"private option of some decoder which was not actually used "
"for any stream.\n", e->key, option->help ? option->help : "");
}
return 0;
}
void update_benchmark(const char *fmt, ...) void update_benchmark(const char *fmt, ...)
{ {
if (do_benchmark_all) { if (do_benchmark_all) {

@ -710,8 +710,10 @@ void term_exit(void);
void show_usage(void); void show_usage(void);
int check_avoptions_used(const AVDictionary *opts, const AVDictionary *opts_used,
void *logctx, int decode);
int assert_file_overwrite(const char *filename); int assert_file_overwrite(const char *filename);
AVDictionary *strip_specifiers(const AVDictionary *dict);
int find_codec(void *logctx, const char *name, int find_codec(void *logctx, const char *name,
enum AVMediaType type, int encoder, const AVCodec **codec); enum AVMediaType type, int encoder, const AVCodec **codec);
int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global); int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global);

@ -1207,7 +1207,7 @@ static DemuxStream *demux_stream_alloc(Demuxer *d, AVStream *st)
return ds; return ds;
} }
static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st) static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictionary **opts_used)
{ {
AVFormatContext *ic = d->f.ctx; AVFormatContext *ic = d->f.ctx;
AVCodecParameters *par = st->codecpar; AVCodecParameters *par = st->codecpar;
@ -1334,7 +1334,7 @@ static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st)
if (ist->dec) { if (ist->dec) {
ret = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id, ret = filter_codec_opts(o->g->codec_opts, ist->st->codecpar->codec_id,
ic, st, ist->dec, &ds->decoder_opts); ic, st, ist->dec, &ds->decoder_opts, opts_used);
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
@ -1553,8 +1553,7 @@ int ifile_open(const OptionsContext *o, const char *filename, Scheduler *sch)
const AVInputFormat *file_iformat = NULL; const AVInputFormat *file_iformat = NULL;
int err, i, ret = 0; int err, i, ret = 0;
int64_t timestamp; int64_t timestamp;
AVDictionary *unused_opts = NULL; AVDictionary *opts_used = NULL;
const AVDictionaryEntry *e = NULL;
const char* video_codec_name = NULL; const char* video_codec_name = NULL;
const char* audio_codec_name = NULL; const char* audio_codec_name = NULL;
const char* subtitle_codec_name = NULL; const char* subtitle_codec_name = NULL;
@ -1826,48 +1825,21 @@ int ifile_open(const OptionsContext *o, const char *filename, Scheduler *sch)
/* Add all the streams from the given input file to the demuxer */ /* Add all the streams from the given input file to the demuxer */
for (int i = 0; i < ic->nb_streams; i++) { for (int i = 0; i < ic->nb_streams; i++) {
ret = ist_add(o, d, ic->streams[i]); ret = ist_add(o, d, ic->streams[i], &opts_used);
if (ret < 0) if (ret < 0) {
av_dict_free(&opts_used);
return ret; return ret;
} }
}
/* dump the file content */ /* dump the file content */
av_dump_format(ic, f->index, filename, 0); av_dump_format(ic, f->index, filename, 0);
/* check if all codec options have been used */ /* check if all codec options have been used */
unused_opts = strip_specifiers(o->g->codec_opts); ret = check_avoptions_used(o->g->codec_opts, opts_used, d, 1);
for (i = 0; i < f->nb_streams; i++) { av_dict_free(&opts_used);
DemuxStream *ds = ds_from_ist(f->streams[i]); if (ret < 0)
e = NULL; return ret;
while ((e = av_dict_iterate(ds->decoder_opts, e)))
av_dict_set(&unused_opts, e->key, NULL, 0);
}
e = NULL;
while ((e = av_dict_iterate(unused_opts, e))) {
const AVClass *class = avcodec_get_class();
const AVOption *option = av_opt_find(&class, e->key, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
const AVClass *fclass = avformat_get_class();
const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
if (!option || foption)
continue;
if (!(option->flags & AV_OPT_FLAG_DECODING_PARAM)) {
av_log(d, AV_LOG_ERROR, "Codec AVOption %s (%s) is not a decoding "
"option.\n", e->key, option->help ? option->help : "");
return AVERROR(EINVAL);
}
av_log(d, AV_LOG_WARNING, "Codec AVOption %s (%s) has not been used "
"for any stream. The most likely reason is either wrong type "
"(e.g. a video option with no video streams) or that it is a "
"private option of some decoder which was not actually used "
"for any stream.\n", e->key, option->help ? option->help : "");
}
av_dict_free(&unused_opts);
for (i = 0; i < o->dump_attachment.nb_opt; i++) { for (i = 0; i < o->dump_attachment.nb_opt; i++) {
int j; int j;

@ -864,6 +864,7 @@ void of_free(OutputFile **pof)
av_freep(&mux->sch_stream_idx); av_freep(&mux->sch_stream_idx);
av_dict_free(&mux->opts); av_dict_free(&mux->opts);
av_dict_free(&mux->enc_opts_used);
av_packet_free(&mux->sq_pkt); av_packet_free(&mux->sq_pkt);

@ -99,6 +99,9 @@ typedef struct Muxer {
AVDictionary *opts; AVDictionary *opts;
// used to validate that all encoder avoptions have been actually used
AVDictionary *enc_opts_used;
/* filesize limit expressed in bytes */ /* filesize limit expressed in bytes */
int64_t limit_filesize; int64_t limit_filesize;
atomic_int_least64_t last_filesize; atomic_int_least64_t last_filesize;

@ -1160,7 +1160,8 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
const char *enc_time_base = NULL; const char *enc_time_base = NULL;
ret = filter_codec_opts(o->g->codec_opts, enc->codec_id, ret = filter_codec_opts(o->g->codec_opts, enc->codec_id,
oc, st, enc->codec, &ost->encoder_opts); oc, st, enc->codec, &ost->encoder_opts,
&mux->enc_opts_used);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -1265,7 +1266,8 @@ static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
} }
} else { } else {
ret = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st, ret = filter_codec_opts(o->g->codec_opts, AV_CODEC_ID_NONE, oc, st,
NULL, &ost->encoder_opts); NULL, &ost->encoder_opts,
&mux->enc_opts_used);
if (ret < 0) if (ret < 0)
return ret; return ret;
} }
@ -3118,53 +3120,6 @@ static int process_forced_keyframes(Muxer *mux, const OptionsContext *o)
return 0; return 0;
} }
static int validate_enc_avopt(Muxer *mux, const AVDictionary *codec_avopt)
{
const AVClass *class = avcodec_get_class();
const AVClass *fclass = avformat_get_class();
const OutputFile *of = &mux->of;
AVDictionary *unused_opts;
const AVDictionaryEntry *e;
unused_opts = strip_specifiers(codec_avopt);
for (int i = 0; i < of->nb_streams; i++) {
e = NULL;
while ((e = av_dict_iterate(of->streams[i]->encoder_opts, e)))
av_dict_set(&unused_opts, e->key, NULL, 0);
}
e = NULL;
while ((e = av_dict_iterate(unused_opts, e))) {
const AVOption *option = av_opt_find(&class, e->key, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
const AVOption *foption = av_opt_find(&fclass, e->key, NULL, 0,
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ);
if (!option || foption)
continue;
if (!(option->flags & AV_OPT_FLAG_ENCODING_PARAM)) {
av_log(mux, AV_LOG_ERROR, "Codec AVOption %s (%s) is not an "
"encoding option.\n", e->key, option->help ? option->help : "");
av_dict_free(&unused_opts);
return AVERROR(EINVAL);
}
// gop_timecode is injected by generic code but not always used
if (!strcmp(e->key, "gop_timecode"))
continue;
av_log(mux, AV_LOG_WARNING, "Codec AVOption %s (%s) has not been used "
"for any stream. The most likely reason is either wrong type "
"(e.g. a video option with no video streams) or that it is a "
"private option of some encoder which was not actually used for "
"any stream.\n", e->key, option->help ? option->help : "");
}
av_dict_free(&unused_opts);
return 0;
}
static const char *output_file_item_name(void *obj) static const char *output_file_item_name(void *obj)
{ {
const Muxer *mux = obj; const Muxer *mux = obj;
@ -3272,7 +3227,8 @@ int of_open(const OptionsContext *o, const char *filename, Scheduler *sch)
return err; return err;
/* check if all codec options have been used */ /* check if all codec options have been used */
err = validate_enc_avopt(mux, o->g->codec_opts); err = check_avoptions_used(o->g->codec_opts, mux->enc_opts_used, mux, 0);
av_dict_free(&mux->enc_opts_used);
if (err < 0) if (err < 0)
return err; return err;

@ -151,24 +151,6 @@ static int show_hwaccels(void *optctx, const char *opt, const char *arg)
return 0; return 0;
} }
/* return a copy of the input with the stream specifiers removed from the keys */
AVDictionary *strip_specifiers(const AVDictionary *dict)
{
const AVDictionaryEntry *e = NULL;
AVDictionary *ret = NULL;
while ((e = av_dict_iterate(dict, e))) {
char *p = strchr(e->key, ':');
if (p)
*p = 0;
av_dict_set(&ret, e->key, e->value, 0);
if (p)
*p = ':';
}
return ret;
}
const char *opt_match_per_type_str(const SpecifierOptList *sol, const char *opt_match_per_type_str(const SpecifierOptList *sol,
char mediatype) char mediatype)
{ {

@ -2673,7 +2673,7 @@ static int stream_component_open(VideoState *is, int stream_index)
avctx->flags2 |= AV_CODEC_FLAG2_FAST; avctx->flags2 |= AV_CODEC_FLAG2_FAST;
ret = filter_codec_opts(codec_opts, avctx->codec_id, ic, ret = filter_codec_opts(codec_opts, avctx->codec_id, ic,
ic->streams[stream_index], codec, &opts); ic->streams[stream_index], codec, &opts, NULL);
if (ret < 0) if (ret < 0)
goto fail; goto fail;

@ -3927,7 +3927,7 @@ static int open_input_file(InputFile *ifile, const char *filename,
AVDictionary *opts; AVDictionary *opts;
err = filter_codec_opts(codec_opts, stream->codecpar->codec_id, err = filter_codec_opts(codec_opts, stream->codecpar->codec_id,
fmt_ctx, stream, codec, &opts); fmt_ctx, stream, codec, &opts, NULL);
if (err < 0) if (err < 0)
exit(1); exit(1);

Loading…
Cancel
Save