diff mbox series

[FFmpeg-devel,1/6] fftools/ffmpeg: replace MATCH_PER_STREAM_OPT(.., str, ..) with a function

Message ID 20240810171621.26757-1-anton@khirnov.net
State New
Headers show
Series [FFmpeg-devel,1/6] fftools/ffmpeg: replace MATCH_PER_STREAM_OPT(.., str, ..) with a function | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished

Commit Message

Anton Khirnov Aug. 10, 2024, 5:16 p.m. UTC
This has multiple advantages:
* The macro has multiple parameters that often have similar or identical
  values, yet very different meanings (one is the name of the
  OptionsContext member where the parsed options are stored, the other
  the name of the variable into which the result is written); this
  change makes each of these explicit.

* The macro returns on failure, which may cause leaks - this was the
  reason for adding MATCH_PER_STREAM_OPT_CLEAN(), also ost_add()
  currently leaks encoder_opts. The new function returns failure to its
  caller, which decides how to deal with it. While that adds a lot of
  error checks/forwards for now, those will be reduced in following
  commits.

* new code is type- and const- correct

Invocations of MATCH_PER_STREAM_OPT() with other types will be converted
in following commits.
---
 fftools/cmdutils.c        |   6 +-
 fftools/cmdutils.h        |   3 +
 fftools/ffmpeg.h          |   4 +-
 fftools/ffmpeg_demux.c    |  74 ++++++++++++------
 fftools/ffmpeg_mux.h      |   2 +-
 fftools/ffmpeg_mux_init.c | 156 +++++++++++++++++++++++++++-----------
 fftools/ffmpeg_opt.c      |  67 ++++++++++++++++
 7 files changed, 242 insertions(+), 70 deletions(-)
diff mbox series

Patch

diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c
index 9b18cf5e4d..6286fd87f7 100644
--- a/fftools/cmdutils.c
+++ b/fftools/cmdutils.c
@@ -246,6 +246,8 @@  static int write_option(void *optctx, const OptionDef *po, const char *opt,
                 (uint8_t *)optctx + po->u.off : po->u.dst_ptr;
     char *arg_allocated = NULL;
 
+    enum OptionType so_type = po->type;
+
     SpecifierOptList *sol = NULL;
     double num;
     int ret = 0;
@@ -310,6 +312,7 @@  static int write_option(void *optctx, const OptionDef *po, const char *opt,
             goto finish;
 
         *(int *)dst = num;
+        so_type = OPT_TYPE_INT;
     } else if (po->type == OPT_TYPE_INT64) {
         ret = parse_number(opt, arg, OPT_TYPE_INT64, INT64_MIN, (double)INT64_MAX, &num);
         if (ret < 0)
@@ -323,6 +326,7 @@  static int write_option(void *optctx, const OptionDef *po, const char *opt,
                    opt, arg);
             goto finish;
         }
+        so_type = OPT_TYPE_INT64;
     } else if (po->type == OPT_TYPE_FLOAT) {
         ret = parse_number(opt, arg, OPT_TYPE_FLOAT, -INFINITY, INFINITY, &num);
         if (ret < 0)
@@ -352,7 +356,7 @@  static int write_option(void *optctx, const OptionDef *po, const char *opt,
     }
 
     if (sol) {
-        sol->type = po->type;
+        sol->type = so_type;
         sol->opt_canon = (po->flags & OPT_HAS_CANON) ?
                          find_option(defs, po->u1.name_canon) : po;
     }
diff --git a/fftools/cmdutils.h b/fftools/cmdutils.h
index 14340dff7d..abc8d26607 100644
--- a/fftools/cmdutils.h
+++ b/fftools/cmdutils.h
@@ -120,6 +120,9 @@  typedef struct SpecifierOptList {
 
     /* Canonical option definition that was parsed into this list. */
     const struct OptionDef *opt_canon;
+    /* Type corresponding to the field that should be used from SpecifierOpt.u.
+     * May not match the option type, e.g. OPT_TYPE_BOOL options are stored as
+     * int, so this field would be OPT_TYPE_INT for them */
     enum OptionType type;
 } SpecifierOptList;
 
diff --git a/fftools/ffmpeg.h b/fftools/ffmpeg.h
index d0298d53cf..7d82d7d7c2 100644
--- a/fftools/ffmpeg.h
+++ b/fftools/ffmpeg.h
@@ -593,7 +593,7 @@  typedef struct OutputStream {
 
     KeyframeForceCtx kf;
 
-    char *logfile_prefix;
+    const char *logfile_prefix;
     FILE *logfile;
 
     // simple filtergraph feeding this stream, if any
@@ -902,6 +902,8 @@  void update_benchmark(const char *fmt, ...);
 
 const char *opt_match_per_type_str(const SpecifierOptList *sol,
                                    char mediatype);
+int opt_match_per_stream_str(void *logctx, const SpecifierOptList *sol,
+                             AVFormatContext *fc, AVStream *st, const char **out);
 
 int muxer_thread(void *arg);
 int encoder_thread(void *arg);
diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c
index 6f7d78c896..0c92d31c10 100644
--- a/fftools/ffmpeg_demux.c
+++ b/fftools/ffmpeg_demux.c
@@ -1077,14 +1077,19 @@  int ist_filter_add(InputStream *ist, InputFilter *ifilter, int is_simple,
     return ds->sch_idx_dec;
 }
 
-static int choose_decoder(const OptionsContext *o, AVFormatContext *s, AVStream *st,
+static int choose_decoder(const OptionsContext *o, void *logctx,
+                          AVFormatContext *s, AVStream *st,
                           enum HWAccelID hwaccel_id, enum AVHWDeviceType hwaccel_device_type,
                           const AVCodec **pcodec)
 
 {
-    char *codec_name = NULL;
+    const char *codec_name = NULL;
+    int ret;
+
+    ret = opt_match_per_stream_str(logctx, &o->codec_names, s, st, &codec_name);
+    if (ret < 0)
+        return ret;
 
-    MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, st);
     if (codec_name) {
         int ret = find_codec(NULL, codec_name, st->codecpar->codec_type, 0, pcodec);
         if (ret < 0)
@@ -1226,14 +1231,14 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
     AVCodecParameters *par = st->codecpar;
     DemuxStream *ds;
     InputStream *ist;
-    char *framerate = NULL, *hwaccel_device = NULL;
+    const char *framerate = NULL, *hwaccel_device = NULL;
     const char *hwaccel = NULL;
     const char *apply_cropping = NULL;
-    char *hwaccel_output_format = NULL;
-    char *codec_tag = NULL;
-    char *bsfs = NULL;
+    const char *hwaccel_output_format = NULL;
+    const char *codec_tag = NULL;
+    const char *bsfs = NULL;
     char *next;
-    char *discard_str = NULL;
+    const char *discard_str = NULL;
     int ret;
 
     ds  = demux_stream_alloc(d, st);
@@ -1256,7 +1261,9 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
     MATCH_PER_STREAM_OPT(autorotate, i, ds->autorotate, ic, st);
 
     ds->apply_cropping = CROP_ALL;
-    MATCH_PER_STREAM_OPT(apply_cropping, str, apply_cropping, ic, st);
+    ret = opt_match_per_stream_str(ist, &o->apply_cropping, ic, st, &apply_cropping);
+    if (ret < 0)
+        return ret;
     if (apply_cropping) {
         const AVOption opts[] = {
             { "apply_cropping", NULL, 0, AV_OPT_TYPE_INT,
@@ -1282,7 +1289,9 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
         }
     }
 
-    MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, ic, st);
+    ret = opt_match_per_stream_str(ist, &o->codec_tags, ic, st, &codec_tag);
+    if (ret < 0)
+        return ret;
     if (codec_tag) {
         uint32_t tag = strtol(codec_tag, &next, 0);
         if (*next) {
@@ -1299,9 +1308,14 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
         if (ret < 0)
             return ret;
 
-        MATCH_PER_STREAM_OPT(hwaccels, str, hwaccel, ic, st);
-        MATCH_PER_STREAM_OPT(hwaccel_output_formats, str,
-                             hwaccel_output_format, ic, st);
+        ret = opt_match_per_stream_str(ist, &o->hwaccels, ic, st, &hwaccel);
+        if (ret < 0)
+            return ret;
+
+        ret = opt_match_per_stream_str(ist, &o->hwaccel_output_formats, ic, st,
+                                       &hwaccel_output_format);
+        if (ret < 0)
+            return ret;
 
         if (!hwaccel_output_format && hwaccel && !strcmp(hwaccel, "cuvid")) {
             av_log(ist, AV_LOG_WARNING,
@@ -1360,7 +1374,9 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
             }
         }
 
-        MATCH_PER_STREAM_OPT(hwaccel_devices, str, hwaccel_device, ic, st);
+        ret = opt_match_per_stream_str(ist, &o->hwaccel_devices, ic, st, &hwaccel_device);
+        if (ret < 0)
+            return ret;
         if (hwaccel_device) {
             ds->dec_opts.hwaccel_device = av_strdup(hwaccel_device);
             if (!ds->dec_opts.hwaccel_device)
@@ -1368,7 +1384,7 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
         }
     }
 
-    ret = choose_decoder(o, ic, st, ds->dec_opts.hwaccel_id,
+    ret = choose_decoder(o, ist, ic, st, ds->dec_opts.hwaccel_id,
                          ds->dec_opts.hwaccel_device_type, &ist->dec);
     if (ret < 0)
         return ret;
@@ -1391,7 +1407,9 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
         (o->data_disable && ist->st->codecpar->codec_type == AVMEDIA_TYPE_DATA))
             ist->user_set_discard = AVDISCARD_ALL;
 
-    MATCH_PER_STREAM_OPT(discard, str, discard_str, ic, st);
+    ret = opt_match_per_stream_str(ist, &o->discard, ic, st, &discard_str);
+    if (ret < 0)
+        return ret;
     if (discard_str) {
         ret = av_opt_set(ist->st, "discard", discard_str, 0);
         if (ret  < 0) {
@@ -1413,7 +1431,9 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
 
     switch (par->codec_type) {
     case AVMEDIA_TYPE_VIDEO:
-        MATCH_PER_STREAM_OPT(frame_rates, str, framerate, ic, st);
+        ret = opt_match_per_stream_str(ist, &o->frame_rates, ic, st, &framerate);
+        if (ret < 0)
+            return ret;
         if (framerate) {
             ret = av_parse_video_rate(&ist->framerate, framerate);
             if (ret < 0) {
@@ -1430,8 +1450,11 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
 
         break;
     case AVMEDIA_TYPE_AUDIO: {
-        char *ch_layout_str = NULL;
-        MATCH_PER_STREAM_OPT(audio_ch_layouts, str, ch_layout_str, ic, st);
+        const char *ch_layout_str = NULL;
+
+        ret = opt_match_per_stream_str(ist, &o->audio_ch_layouts, ic, st, &ch_layout_str);
+        if (ret < 0)
+            return ret;
         if (ch_layout_str) {
             AVChannelLayout ch_layout;
             ret = av_channel_layout_from_string(&ch_layout, ch_layout_str);
@@ -1458,9 +1481,12 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
     }
     case AVMEDIA_TYPE_DATA:
     case AVMEDIA_TYPE_SUBTITLE: {
-        char *canvas_size = NULL;
+        const char *canvas_size = NULL;
         MATCH_PER_STREAM_OPT(fix_sub_duration, i, ist->fix_sub_duration, ic, st);
-        MATCH_PER_STREAM_OPT(canvas_sizes, str, canvas_size, ic, st);
+
+        ret = opt_match_per_stream_str(ist, &o->canvas_sizes, ic, st, &canvas_size);
+        if (ret < 0)
+            return ret;
         if (canvas_size) {
             ret = av_parse_video_size(&par->width, &par->height,
                                       canvas_size);
@@ -1490,7 +1516,9 @@  static int ist_add(const OptionsContext *o, Demuxer *d, AVStream *st, AVDictiona
     if (ist->st->sample_aspect_ratio.num)
         ist->par->sample_aspect_ratio = ist->st->sample_aspect_ratio;
 
-    MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, ic, st);
+    ret = opt_match_per_stream_str(ist, &o->bitstream_filters, ic, st, &bsfs);
+    if (ret < 0)
+        return ret;
     if (bsfs) {
         ret = av_bsf_list_parse_str(bsfs, &ds->bsf);
         if (ret < 0) {
@@ -1752,7 +1780,7 @@  int ifile_open(const OptionsContext *o, const char *filename, Scheduler *sch)
     /* apply forced codec ids */
     for (i = 0; i < ic->nb_streams; i++) {
         const AVCodec *dummy;
-        ret = choose_decoder(o, ic, ic->streams[i], HWACCEL_NONE, AV_HWDEVICE_TYPE_NONE,
+        ret = choose_decoder(o, f, ic, ic->streams[i], HWACCEL_NONE, AV_HWDEVICE_TYPE_NONE,
                              &dummy);
         if (ret < 0)
             return ret;
diff --git a/fftools/ffmpeg_mux.h b/fftools/ffmpeg_mux.h
index 1c1b407484..22d728a919 100644
--- a/fftools/ffmpeg_mux.h
+++ b/fftools/ffmpeg_mux.h
@@ -79,7 +79,7 @@  typedef struct MuxStream {
     int             ts_drop;
 #endif
 
-    char           *apad;
+    const char     *apad;
 } MuxStream;
 
 typedef struct Muxer {
diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c
index 4a2b5924a4..c7efeda7bf 100644
--- a/fftools/ffmpeg_mux_init.c
+++ b/fftools/ffmpeg_mux_init.c
@@ -70,11 +70,14 @@  static int choose_encoder(const OptionsContext *o, AVFormatContext *s,
                           OutputStream *ost, const AVCodec **enc)
 {
     enum AVMediaType type = ost->type;
-    char *codec_name = NULL;
+    const char *codec_name = NULL;
+    int ret;
 
     *enc = NULL;
 
-    MATCH_PER_STREAM_OPT(codec_names, str, codec_name, s, ost->st);
+    ret = opt_match_per_stream_str(ost, &o->codec_names, s, ost->st, &codec_name);
+    if (ret < 0)
+        return ret;
 
     if (type != AVMEDIA_TYPE_VIDEO      &&
         type != AVMEDIA_TYPE_AUDIO      &&
@@ -416,12 +419,17 @@  static int ost_get_filters(const OptionsContext *o, AVFormatContext *oc,
                            OutputStream *ost, char **dst)
 {
     const char *filters = NULL;
+    int ret;
 #if FFMPEG_OPT_FILTER_SCRIPT
     const char *filters_script = NULL;
 
-    MATCH_PER_STREAM_OPT(filter_scripts, str, filters_script, oc, ost->st);
+    ret = opt_match_per_stream_str(ost, &o->filter_scripts, oc, ost->st, &filters_script);
+    if (ret < 0)
+        return ret;
 #endif
-    MATCH_PER_STREAM_OPT(filters,        str, filters,        oc, ost->st);
+    ret = opt_match_per_stream_str(ost, &o->filters, oc, ost->st, &filters);
+    if (ret < 0)
+        return ret;
 
     if (!ost->enc) {
         if (
@@ -586,18 +594,22 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
     MuxStream       *ms = ms_from_ost(ost);
     AVFormatContext *oc = mux->fc;
     AVStream *st;
-    char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL;
+    const char *frame_rate = NULL, *max_frame_rate = NULL, *frame_aspect_ratio = NULL;
     int ret = 0;
 
     st  = ost->st;
 
-    MATCH_PER_STREAM_OPT(frame_rates, str, frame_rate, oc, st);
+    ret = opt_match_per_stream_str(ost, &o->frame_rates, oc, st, &frame_rate);
+    if (ret < 0)
+        return ret;
     if (frame_rate && av_parse_video_rate(&ost->frame_rate, frame_rate) < 0) {
         av_log(ost, AV_LOG_FATAL, "Invalid framerate value: %s\n", frame_rate);
         return AVERROR(EINVAL);
     }
 
-    MATCH_PER_STREAM_OPT(max_frame_rates, str, max_frame_rate, oc, st);
+    ret = opt_match_per_stream_str(ost, &o->max_frame_rates, oc, st, &max_frame_rate);
+    if (ret < 0)
+        return ret;
     if (max_frame_rate && av_parse_video_rate(&ost->max_frame_rate, max_frame_rate) < 0) {
         av_log(ost, AV_LOG_FATAL, "Invalid maximum framerate value: %s\n", max_frame_rate);
         return AVERROR(EINVAL);
@@ -608,7 +620,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
         return AVERROR(EINVAL);
     }
 
-    MATCH_PER_STREAM_OPT(frame_aspect_ratios, str, frame_aspect_ratio, oc, st);
+    ret = opt_match_per_stream_str(ost, &o->frame_aspect_ratios, oc, st, &frame_aspect_ratio);
+    if (ret < 0)
+        return ret;
     if (frame_aspect_ratio) {
         AVRational q;
         if (av_parse_ratio(&q, frame_aspect_ratio, 255, 0, NULL) < 0 ||
@@ -622,14 +636,16 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
     if (ost->enc_ctx) {
         AVCodecContext *video_enc = ost->enc_ctx;
         const char *p = NULL, *fps_mode = NULL;
-        char *frame_size = NULL;
-        char *frame_pix_fmt = NULL;
-        char *intra_matrix = NULL, *inter_matrix = NULL;
-        char *chroma_intra_matrix = NULL;
+        const char *frame_size = NULL;
+        const char *frame_pix_fmt = NULL;
+        const char *intra_matrix = NULL, *inter_matrix = NULL;
+        const char *chroma_intra_matrix = NULL;
         int do_pass = 0;
         int i;
 
-        MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->frame_sizes, oc, st, &frame_size);
+        if (ret < 0)
+            return ret;
         if (frame_size) {
             ret = av_parse_video_size(&video_enc->width, &video_enc->height, frame_size);
             if (ret < 0) {
@@ -638,7 +654,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
             }
         }
 
-        MATCH_PER_STREAM_OPT(frame_pix_fmts, str, frame_pix_fmt, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->frame_pix_fmts, oc, st, &frame_pix_fmt);
+        if (ret < 0)
+            return ret;
         if (frame_pix_fmt && *frame_pix_fmt == '+') {
             *keep_pix_fmt = 1;
             if (!*++frame_pix_fmt)
@@ -650,7 +668,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
                 return AVERROR(EINVAL);
         }
 
-        MATCH_PER_STREAM_OPT(intra_matrices, str, intra_matrix, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->intra_matrices, oc, st, &intra_matrix);
+        if (ret < 0)
+            return ret;
         if (intra_matrix) {
             if (!(video_enc->intra_matrix = av_mallocz(sizeof(*video_enc->intra_matrix) * 64)))
                 return AVERROR(ENOMEM);
@@ -659,7 +679,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
             if (ret < 0)
                 return ret;
         }
-        MATCH_PER_STREAM_OPT(chroma_intra_matrices, str, chroma_intra_matrix, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->chroma_intra_matrices, oc, st, &chroma_intra_matrix);
+        if (ret < 0)
+            return ret;
         if (chroma_intra_matrix) {
             uint16_t *p = av_mallocz(sizeof(*video_enc->chroma_intra_matrix) * 64);
             if (!p)
@@ -669,7 +691,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
             if (ret < 0)
                 return ret;
         }
-        MATCH_PER_STREAM_OPT(inter_matrices, str, inter_matrix, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->inter_matrices, oc, st, &inter_matrix);
+        if (ret < 0)
+            return ret;
         if (inter_matrix) {
             if (!(video_enc->inter_matrix = av_mallocz(sizeof(*video_enc->inter_matrix) * 64)))
                 return AVERROR(ENOMEM);
@@ -678,7 +702,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
                 return ret;
         }
 
-        MATCH_PER_STREAM_OPT(rc_overrides, str, p, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->rc_overrides, oc, st, &p);
+        if (ret < 0)
+            return ret;
         for (i = 0; p; i++) {
             int start, end, q;
             int e = sscanf(p, "%d,%d,%d", &start, &end, &q);
@@ -717,7 +743,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
                 video_enc->flags |= AV_CODEC_FLAG_PASS2;
         }
 
-        MATCH_PER_STREAM_OPT(passlogfiles, str, ost->logfile_prefix, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->passlogfiles, oc, st, &ost->logfile_prefix);
+        if (ret < 0)
+            return ret;
         if (ost->logfile_prefix &&
             !(ost->logfile_prefix = av_strdup(ost->logfile_prefix)))
             return AVERROR(ENOMEM);
@@ -778,7 +806,9 @@  static int new_stream_video(Muxer *mux, const OptionsContext *o,
 #else
         *vsync_method = VSYNC_AUTO;
 #endif
-        MATCH_PER_STREAM_OPT(fps_mode, str, fps_mode, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->fps_mode, oc, st, &fps_mode);
+        if (ret < 0)
+            return ret;
         if (fps_mode) {
             ret = parse_and_set_vsync(fps_mode, vsync_method, ost->file->index, ost->index, 0);
             if (ret < 0)
@@ -834,8 +864,9 @@  static int new_stream_audio(Muxer *mux, const OptionsContext *o,
     if (ost->enc_ctx) {
         AVCodecContext *audio_enc = ost->enc_ctx;
         int channels = 0;
-        char *layout = NULL;
-        char *sample_fmt = NULL;
+        const char *layout = NULL;
+        const char *sample_fmt = NULL;
+        int ret;
 
         MATCH_PER_STREAM_OPT(audio_channels, i, channels, oc, st);
         if (channels) {
@@ -843,13 +874,17 @@  static int new_stream_audio(Muxer *mux, const OptionsContext *o,
             audio_enc->ch_layout.nb_channels = channels;
         }
 
-        MATCH_PER_STREAM_OPT(audio_ch_layouts, str, layout, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->audio_ch_layouts, oc, st, &layout);
+        if (ret < 0)
+            return ret;
         if (layout && av_channel_layout_from_string(&audio_enc->ch_layout, layout) < 0) {
             av_log(ost, AV_LOG_FATAL, "Unknown channel layout: %s\n", layout);
             return AVERROR(EINVAL);
         }
 
-        MATCH_PER_STREAM_OPT(sample_fmts, str, sample_fmt, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->sample_fmts, oc, st, &sample_fmt);
+        if (ret < 0)
+            return ret;
         if (sample_fmt &&
             (audio_enc->sample_fmt = av_get_sample_fmt(sample_fmt)) == AV_SAMPLE_FMT_NONE) {
             av_log(ost, AV_LOG_FATAL, "Invalid sample format '%s'\n", sample_fmt);
@@ -858,7 +893,9 @@  static int new_stream_audio(Muxer *mux, const OptionsContext *o,
 
         MATCH_PER_STREAM_OPT(audio_sample_rate, i, audio_enc->sample_rate, oc, st);
 
-        MATCH_PER_STREAM_OPT(apad, str, ms->apad, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->apad, oc, st, &ms->apad);
+        if (ret < 0)
+            return ret;
     }
 
     return 0;
@@ -880,9 +917,12 @@  static int new_stream_subtitle(Muxer *mux, const OptionsContext *o,
             avcodec_descriptor_get(subtitle_enc->codec_id);
         int input_props = 0, output_props = 0;
 
-        char *frame_size = NULL;
+        const char *frame_size = NULL;
+        int ret;
 
-        MATCH_PER_STREAM_OPT(frame_sizes, str, frame_size, mux->fc, st);
+        ret = opt_match_per_stream_str(ost, &o->frame_sizes, mux->fc, st, &frame_size);
+        if (ret < 0)
+            return ret;
         if (frame_size) {
             int ret = av_parse_video_size(&subtitle_enc->width, &subtitle_enc->height, frame_size);
             if (ret < 0) {
@@ -1039,8 +1079,8 @@  static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
     int threads_manual = 0;
     AVRational enc_tb = { 0, 0 };
     enum VideoSyncMethod vsync_method = VSYNC_AUTO;
-    const char *bsfs = NULL, *time_base = NULL;
-    char *filters = NULL, *next, *codec_tag = NULL;
+    const char *bsfs = NULL, *time_base = NULL, *codec_tag = NULL;
+    char *filters = NULL, *next;
     double qscale = -1;
 
     st = avformat_new_stream(oc, NULL);
@@ -1151,9 +1191,9 @@  static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
     if (ost->enc_ctx) {
         AVCodecContext *enc = ost->enc_ctx;
         AVIOContext *s = NULL;
-        char *buf = NULL, *arg = NULL, *preset = 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;
+        const char *enc_time_base = NULL, *preset = NULL;
 
         ret = filter_codec_opts(o->g->codec_opts, enc->codec_id,
                                 oc, st, enc->codec, &encoder_opts,
@@ -1161,7 +1201,9 @@  static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
         if (ret < 0)
             goto fail;
 
-        MATCH_PER_STREAM_OPT(presets, str, preset, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->presets, oc, st, &preset);
+        if (ret < 0)
+            goto fail;
 
         MATCH_PER_STREAM_OPT(autoscale, i, autoscale, oc, st);
         if (preset && (!(ret = get_preset_file_2(preset, enc->codec->name, &s)))) {
@@ -1194,43 +1236,57 @@  static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
             goto fail;
         }
 
-        MATCH_PER_STREAM_OPT(enc_stats_pre, str, enc_stats_pre, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->enc_stats_pre, oc, st, &enc_stats_pre);
+        if (ret < 0)
+            goto fail;
         if (enc_stats_pre &&
             (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) {
             const char *format = "{fidx} {sidx} {n} {t}";
 
-            MATCH_PER_STREAM_OPT(enc_stats_pre_fmt, str, format, oc, st);
+            ret = opt_match_per_stream_str(ost, &o->enc_stats_pre_fmt, oc, st, &format);
+            if (ret < 0)
+                goto fail;
 
             ret = enc_stats_init(ost, &ost->enc_stats_pre, 1, enc_stats_pre, format);
             if (ret < 0)
                 goto fail;
         }
 
-        MATCH_PER_STREAM_OPT(enc_stats_post, str, enc_stats_post, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->enc_stats_post, oc, st, &enc_stats_post);
+        if (ret < 0)
+            goto fail;
         if (enc_stats_post &&
             (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) {
             const char *format = "{fidx} {sidx} {n} {t}";
 
-            MATCH_PER_STREAM_OPT(enc_stats_post_fmt, str, format, oc, st);
+            ret = opt_match_per_stream_str(ost, &o->enc_stats_post_fmt, oc, st, &format);
+            if (ret < 0)
+                goto fail;
 
             ret = enc_stats_init(ost, &ost->enc_stats_post, 0, enc_stats_post, format);
             if (ret < 0)
                 goto fail;
         }
 
-        MATCH_PER_STREAM_OPT(mux_stats, str, mux_stats, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->mux_stats, oc, st, &mux_stats);
+        if (ret < 0)
+            goto fail;
         if (mux_stats &&
             (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)) {
             const char *format = "{fidx} {sidx} {n} {t}";
 
-            MATCH_PER_STREAM_OPT(mux_stats_fmt, str, format, oc, st);
+            ret = opt_match_per_stream_str(ost, &o->mux_stats_fmt, oc, st, &format);
+            if (ret < 0)
+                goto fail;
 
             ret = enc_stats_init(ost, &ms->stats, 0, mux_stats, format);
             if (ret < 0)
                 goto fail;
         }
 
-        MATCH_PER_STREAM_OPT(enc_time_bases, str, enc_time_base, oc, st);
+        ret = opt_match_per_stream_str(ost, &o->enc_time_bases, oc, st, &enc_time_base);
+        if (ret < 0)
+            goto fail;
         if (enc_time_base && type == AVMEDIA_TYPE_SUBTITLE)
             av_log(ost, AV_LOG_WARNING,
                    "-enc_time_base not supported for subtitles, ignoring\n");
@@ -1293,7 +1349,9 @@  static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
         ost->bitexact        = !!(ost->enc_ctx->flags & AV_CODEC_FLAG_BITEXACT);
     }
 
-    MATCH_PER_STREAM_OPT(time_bases, str, time_base, oc, st);
+    ret = opt_match_per_stream_str(ost, &o->time_bases, oc, st, &time_base);
+    if (ret < 0)
+        return ret;
     if (time_base) {
         AVRational q;
         if (av_parse_ratio(&q, time_base, INT_MAX, 0, NULL) < 0 ||
@@ -1318,7 +1376,9 @@  static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
     ms->copy_prior_start = -1;
     MATCH_PER_STREAM_OPT(copy_prior_start, i, ms->copy_prior_start, oc ,st);
 
-    MATCH_PER_STREAM_OPT(bitstream_filters, str, bsfs, oc, st);
+    ret = opt_match_per_stream_str(ost, &o->bitstream_filters, oc, st, &bsfs);
+    if (ret < 0)
+        return ret;
     if (bsfs && *bsfs) {
         ret = av_bsf_list_parse_str(bsfs, &ms->bsf_ctx);
         if (ret < 0) {
@@ -1327,7 +1387,9 @@  static int ost_add(Muxer *mux, const OptionsContext *o, enum AVMediaType type,
         }
     }
 
-    MATCH_PER_STREAM_OPT(codec_tags, str, codec_tag, oc, st);
+    ret = opt_match_per_stream_str(ost, &o->codec_tags, oc, st, &codec_tag);
+    if (ret < 0)
+        return ret;
     if (codec_tag) {
         uint32_t tag = strtol(codec_tag, &next, 0);
         if (*next) {
@@ -2943,7 +3005,9 @@  static int set_dispositions(Muxer *mux, const OptionsContext *o)
 
         nb_streams[ost->type + 1]++;
 
-        MATCH_PER_STREAM_OPT_CLEAN(disposition, str, dispositions[i], ctx, ost->st, goto finish);
+        ret = opt_match_per_stream_str(ost, &o->disposition, ctx, ost->st, &dispositions[i]);
+        if (ret < 0)
+            goto finish;
 
         have_manual |= !!dispositions[i];
 
@@ -3088,8 +3152,12 @@  static int process_forced_keyframes(Muxer *mux, const OptionsContext *o)
     for (int i = 0; i < mux->of.nb_streams; i++) {
         OutputStream *ost = mux->of.streams[i];
         const char *forced_keyframes = NULL;
+        int ret;
 
-        MATCH_PER_STREAM_OPT(forced_key_frames, str, forced_keyframes, mux->fc, ost->st);
+        ret = opt_match_per_stream_str(ost, &o->forced_key_frames,
+                                       mux->fc, ost->st, &forced_keyframes);
+        if (ret < 0)
+            return ret;
 
         if (!(ost->type == AVMEDIA_TYPE_VIDEO &&
               ost->enc_ctx && forced_keyframes))
diff --git a/fftools/ffmpeg_opt.c b/fftools/ffmpeg_opt.c
index 2c201c74b2..736b3ee743 100644
--- a/fftools/ffmpeg_opt.c
+++ b/fftools/ffmpeg_opt.c
@@ -164,6 +164,73 @@  const char *opt_match_per_type_str(const SpecifierOptList *sol,
     return NULL;
 }
 
+static int opt_match_per_stream(void *logctx, enum OptionType type,
+                                const SpecifierOptList *sol,
+                                AVFormatContext *fc, AVStream *st)
+{
+    int matches = 0, match_idx = -1;
+
+    av_assert0((type == sol->type) || !sol->nb_opt);
+
+    for (int i = 0; i < sol->nb_opt; i++) {
+        const char *spec = sol->opt[i].specifier;
+        int ret = check_stream_specifier(fc, st, spec);
+        if (ret > 0) {
+            match_idx = i;
+            matches++;
+        } else if (ret < 0)
+            return ret;
+    }
+
+    if (matches > 1 && sol->opt_canon) {
+        const SpecifierOpt *so = &sol->opt[match_idx];
+        const char *spec = so->specifier && so->specifier[0] ? so->specifier : "";
+
+        char namestr[128] = "";
+        char optval_buf[32];
+        const char *optval = optval_buf;
+
+        snprintf(namestr, sizeof(namestr), "-%s", sol->opt_canon->name);
+        if (sol->opt_canon->flags & OPT_HAS_ALT) {
+            const char * const *names_alt = sol->opt_canon->u1.names_alt;
+            for (int i = 0; names_alt[i]; i++)
+                av_strlcatf(namestr, sizeof(namestr), "/-%s", names_alt[i]);
+        }
+
+        switch (sol->type) {
+        case OPT_TYPE_STRING: optval = so->u.str;                                             break;
+        case OPT_TYPE_INT:    snprintf(optval_buf, sizeof(optval_buf), "%d", so->u.i);        break;
+        case OPT_TYPE_INT64:  snprintf(optval_buf, sizeof(optval_buf), "%"PRId64, so->u.i64); break;
+        case OPT_TYPE_FLOAT:  snprintf(optval_buf, sizeof(optval_buf), "%f", so->u.f);        break;
+        case OPT_TYPE_DOUBLE: snprintf(optval_buf, sizeof(optval_buf), "%f", so->u.dbl);      break;
+        default: av_assert0(0);
+        }
+
+        av_log(logctx, AV_LOG_WARNING, "Multiple %s options specified for "
+               "stream %d, only the last option '-%s%s%s %s' will be used.\n",
+               namestr, st->index, sol->opt_canon->name, spec[0] ? ":" : "",
+               spec, optval);
+    }
+
+    return match_idx + 1;
+}
+
+#define OPT_MATCH_PER_STREAM(name, type, opt_type, m)                                   \
+int opt_match_per_stream_ ## name(void *logctx, const SpecifierOptList *sol,            \
+                                  AVFormatContext *fc, AVStream *st, type *out)         \
+{                                                                                       \
+    int ret = opt_match_per_stream(logctx, opt_type, sol, fc, st);                      \
+                                                                                        \
+    if (ret <= 0)                                                                       \
+        return ret;                                                                     \
+                                                                                        \
+    *out = sol->opt[ret - 1].u.m;                                                       \
+                                                                                        \
+    return 0;                                                                           \
+}
+
+OPT_MATCH_PER_STREAM(str, const char *, OPT_TYPE_STRING, str);
+
 int parse_and_set_vsync(const char *arg, int *vsync_var, int file_idx, int st_idx, int is_global)
 {
     if      (!av_strcasecmp(arg, "cfr"))         *vsync_var = VSYNC_CFR;