[FFmpeg-devel] [PATCH] Add metadatareader filter.

Raymond Cheng raycheng100 at hotmail.com
Thu Jul 7 02:44:11 EEST 2022


FFmpeg has a handy set of filters, metadata/ametadata, which allow us
to add, print or save per-frame metadata to a file. I will use these
filters when I want to save the results from speech transcription,
for instance. What is missing is a way to round-trip the saved
metadata, so I decided to add filters which go in the opposite
direction: metadatareader/ametadatareader. These filters inject
per-frame metadata into the filter graph from a previously saved
metadata/ametadata file.

If you had a speech transcription filter called speech_recognition
which produced per-frame metadata using the key "transcription",
here is how you might use the metadata reader to burn hard subtitles:

Pass 1: ffmpeg -i input -vn -sn -af speech_recognition,ametadata=mode=print:file=transcription.txt -f null /dev/null
Pass 2: ffmpeg -i input -vf metadatareader=file=transcription.txt,drawtext=fontsize=30:x=30:y=30:fontcolor=yellow:text="'%{metadata\:transcription}'" out.mp4

It should be noted in the example above that the metadata crossed
over from audio to video. It was saved from audio in Pass 1, and
applied to video on Pass 2. This is perfectly valid, as well as
non-crossover applications.

Signed-off-by: Raymond Cheng <raych at microsoft.com>
---
 Changelog                             |   1 +
 configure                             |   2 +
 libavfilter/Makefile                  |   2 +
 libavfilter/allfilters.c              |   2 +
 libavfilter/f_metadatareader.c        | 455 +++++++++++++++++
 libavutil/error_tracing.h             | 126 +++++
 tests/fate-run.sh                     |   3 +
 tests/fate/filter-audio.mak           |   7 +
 tests/fate/filter-video.mak           |   6 +
 tests/ref/fate/filter-ametadatareader | 690 ++++++++++++++++++++++++++
 tests/ref/fate/filter-metadatareader  |  14 +
 11 files changed, 1308 insertions(+)
 create mode 100644 libavfilter/f_metadatareader.c
 create mode 100644 libavutil/error_tracing.h
 create mode 100644 tests/ref/fate/filter-ametadatareader
 create mode 100644 tests/ref/fate/filter-metadatareader

diff --git a/Changelog b/Changelog
index ba679aa64e..646eac87e9 100644
--- a/Changelog
+++ b/Changelog
@@ -23,6 +23,7 @@ version 5.1:
 - virtualbass audio filter
 - VDPAU AV1 hwaccel
 - PHM image format support
+- metadatareader video and ametadatareader audio filter
 
 
 version 5.0:
diff --git a/configure b/configure
index fea512e8ef..8262f8d708 100755
--- a/configure
+++ b/configure
@@ -3614,6 +3614,7 @@ libzmq_protocol_select="network"
 
 # filters
 ametadata_filter_deps="avformat"
+ametadatareader_filter_deps="avformat"
 amovie_filter_deps="avcodec avformat"
 aresample_filter_deps="swresample"
 asr_filter_deps="pocketsphinx"
@@ -3679,6 +3680,7 @@ libplacebo_filter_deps="libplacebo vulkan"
 lv2_filter_deps="lv2"
 mcdeint_filter_deps="avcodec gpl"
 metadata_filter_deps="avformat"
+metadatareader_filter_deps="avformat"
 movie_filter_deps="avcodec avformat"
 mpdecimate_filter_deps="gpl"
 mpdecimate_filter_select="pixelutils"
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 22b0a0ca15..15de9b3815 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -71,6 +71,7 @@ OBJS-$(CONFIG_ALLPASS_FILTER)                += af_biquads.o
 OBJS-$(CONFIG_ALOOP_FILTER)                  += f_loop.o
 OBJS-$(CONFIG_AMERGE_FILTER)                 += af_amerge.o
 OBJS-$(CONFIG_AMETADATA_FILTER)              += f_metadata.o
+OBJS-$(CONFIG_AMETADATAREADER_FILTER)        += f_metadatareader.o
 OBJS-$(CONFIG_AMIX_FILTER)                   += af_amix.o
 OBJS-$(CONFIG_AMULTIPLY_FILTER)              += af_amultiply.o
 OBJS-$(CONFIG_ANEQUALIZER_FILTER)            += af_anequalizer.o
@@ -365,6 +366,7 @@ OBJS-$(CONFIG_MEDIAN_FILTER)                 += vf_median.o
 OBJS-$(CONFIG_MERGEPLANES_FILTER)            += vf_mergeplanes.o framesync.o
 OBJS-$(CONFIG_MESTIMATE_FILTER)              += vf_mestimate.o motion_estimation.o
 OBJS-$(CONFIG_METADATA_FILTER)               += f_metadata.o
+OBJS-$(CONFIG_METADATAREADER_FILTER)         += f_metadatareader.o
 OBJS-$(CONFIG_MIDEQUALIZER_FILTER)           += vf_midequalizer.o framesync.o
 OBJS-$(CONFIG_MINTERPOLATE_FILTER)           += vf_minterpolate.o motion_estimation.o
 OBJS-$(CONFIG_MIX_FILTER)                    += vf_mix.o framesync.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index ec70feef11..3e58e52ea7 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -58,6 +58,7 @@ extern const AVFilter ff_af_allpass;
 extern const AVFilter ff_af_aloop;
 extern const AVFilter ff_af_amerge;
 extern const AVFilter ff_af_ametadata;
+extern const AVFilter ff_af_ametadatareader;
 extern const AVFilter ff_af_amix;
 extern const AVFilter ff_af_amultiply;
 extern const AVFilter ff_af_anequalizer;
@@ -346,6 +347,7 @@ extern const AVFilter ff_vf_median;
 extern const AVFilter ff_vf_mergeplanes;
 extern const AVFilter ff_vf_mestimate;
 extern const AVFilter ff_vf_metadata;
+extern const AVFilter ff_vf_metadatareader;
 extern const AVFilter ff_vf_midequalizer;
 extern const AVFilter ff_vf_minterpolate;
 extern const AVFilter ff_vf_mix;
diff --git a/libavfilter/f_metadatareader.c b/libavfilter/f_metadatareader.c
new file mode 100644
index 0000000000..c36d3ac42b
--- /dev/null
+++ b/libavfilter/f_metadatareader.c
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2022 Raymond Cheng
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ /**
+  * @file
+  * filter for replaying previously saved frame metadata
+  */
+
+#include "config_components.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "libavfilter/avfilter.h"
+#include "libavfilter/internal.h"
+#include "libavfilter/audio.h"
+#include "libavutil/error_tracing.h"
+#include "libavutil/log.h"
+#include "libavutil/opt.h"
+#include "libavutil/samplefmt.h"
+
+// For all macros in error_tracing.h, we shall require AVFilterContext *ctx be defined, and
+// use ctx->priv as the private avclass.
+#undef ERROR_TRACKING_AVCLASS
+#define ERROR_TRACKING_AVCLASS ctx->priv
+
+//===========================================================================
+// Type Definitions
+//===========================================================================
+
+typedef struct MetadataEntry {
+    int64_t frame_num;
+    int64_t pts;
+    double pts_time;
+    AVDictionary *metadata;
+} MetadataEntry;
+
+typedef struct MetadataReaderContext {
+    const AVClass *class;
+
+    char *file_str;
+
+    int parsed_entries;
+    int allocated_entries;
+    MetadataEntry *rgEntries;
+    int curr_idx;
+} MetadataReaderContext;
+
+//===========================================================================
+// Methods
+//===========================================================================
+
+static av_cold int parse_line(AVFilterContext *ctx, char **rgTokens, int numTokens, char *pszLine, const char *pszDelims)
+{
+    int iResult = 0;
+    MetadataReaderContext *pThis = ctx->priv;
+    char szLineForError[8192];
+
+    int chars_to_copy = strlen(pszLine);
+    if (chars_to_copy >= FF_ARRAY_ELEMS(szLineForError))
+        chars_to_copy = FF_ARRAY_ELEMS(szLineForError) - 1;
+
+    strncpy(szLineForError, pszLine, chars_to_copy);
+    szLineForError[chars_to_copy] = '\0';
+    for (int i = 0; i < numTokens; i++) {
+        rgTokens[i] = strtok(i == 0 ? pszLine: NULL, pszDelims);
+        CHECKCOND_EXIT(rgTokens[i], AVERROR_INVALIDDATA);
+    }
+
+exit:
+    if (iResult < 0) {
+        av_log(pThis, AV_LOG_ERROR, "Failed to parse line from %s: %s\n", pThis->file_str,
+            iResult == AVERROR(ERANGE) ? pszLine : szLineForError);
+    }
+
+    return iResult;
+}
+
+static av_cold void remove_newline(char *psz, int idx)
+{
+    char c;
+    if (idx < 0)
+        return; // This can happen for small length
+
+    c = psz[idx];
+    if (c == '\r' || c == '\n')
+        psz[idx] = '\0';
+}
+
+static av_cold int check_for_nopts(AVFilterContext *ctx, char *psz)
+{
+    int iResult = 0;
+    MetadataReaderContext *pThis = ctx->priv;
+
+    const char szNOPTS[] = "NOPTS";
+    if (!strncmp(szNOPTS, psz, FF_ARRAY_ELEMS(szNOPTS))) {
+        av_log(pThis, AV_LOG_ERROR, "Found NOPTS in %s: Current implementation does not handle this!\n", pThis->file_str);
+        CHECKINT_EXIT(AVERROR_INVALIDDATA);
+    }
+
+exit:
+    return iResult;
+}
+
+static av_cold void remove_trailing_newlines(char *psz)
+{
+    int len = strlen(psz);
+    remove_newline(psz, len - 1);
+    remove_newline(psz, len - 2);
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    int iResult = 0;
+    MetadataReaderContext *pThis = ctx->priv;
+    FILE *file = NULL;
+    char szLine[8192];
+    char szLastKey[1024];
+    szLine[0] = '\0';
+    szLastKey[0] = '\0';
+
+    CHECKCOND_EXIT(pThis->file_str && pThis->file_str[0] != '\0', AVERROR(EINVAL));
+    pThis->curr_idx = -1; // We pre-increment this index so we can handle multi-line metadata
+
+    errno = 0;
+    file = fopen(pThis->file_str, "r");
+    if (!file) {
+        char *pszErrStr = strerror(errno);
+        av_log(pThis, AV_LOG_ERROR, "Failed to open file %s: errno = %d: %s\n", pThis->file_str, errno,
+            pszErrStr ? pszErrStr : "(No error string)");
+        CHECKINT_EXIT(AVERROR(ENOENT));
+    }
+
+    while (fgets(szLine, FF_ARRAY_ELEMS(szLine), file)) {
+        const char szFrame[] = "frame:";
+        if (!strncmp(szFrame, szLine, FF_ARRAY_ELEMS(szFrame) - 1)) {
+            const char szPts[] = "pts";
+            const char szPtsTime[] = "pts_time";
+            char *rgTokens[5] = { 0 };
+            MetadataEntry *entry;
+
+            pThis->curr_idx++;
+            pThis->parsed_entries++;
+            if (pThis->curr_idx >= pThis->allocated_entries) {
+                int new_allocated_entries = pThis->allocated_entries + 100;
+                CHECKINT_EXIT(av_reallocp_array(&pThis->rgEntries, new_allocated_entries, sizeof(pThis->rgEntries[0])));
+                pThis->allocated_entries = new_allocated_entries;
+            }
+
+            // Tokenize and verify the fixed values
+            CHECKINT_EXIT(parse_line(ctx, rgTokens, FF_ARRAY_ELEMS(rgTokens), szLine + FF_ARRAY_ELEMS(szFrame) - 1, " :"));
+            CHECKCOND_EXIT(!strncmp(szPts, rgTokens[1], FF_ARRAY_ELEMS(szPts)), AVERROR_INVALIDDATA);
+            CHECKCOND_EXIT(!strncmp(szPtsTime, rgTokens[3], FF_ARRAY_ELEMS(szPtsTime)), AVERROR_INVALIDDATA);
+
+            // Parse and save the values
+            entry = &pThis->rgEntries[pThis->curr_idx];
+            entry->frame_num = atoll(rgTokens[0]);
+            if (pThis->curr_idx > 0 && (entry - 1)->frame_num >= entry->frame_num) {
+                av_log(pThis, AV_LOG_ERROR, "Frame numbers in %s went from %"PRId64" to %"PRId64", expected monotonic increase!\n",
+                    pThis->file_str, (entry - 1)->frame_num, entry->frame_num);
+                CHECKINT_EXIT(AVERROR_INVALIDDATA);
+            }
+            CHECKINT_EXIT(check_for_nopts(ctx, rgTokens[2]));
+            entry->pts = atoll(rgTokens[2]);
+            if (pThis->curr_idx > 0 && (entry - 1)->pts >= entry->pts) {
+                av_log(pThis, AV_LOG_ERROR, "pts in %s went from %"PRId64" to %"PRId64", current implementation can't handle this!\n",
+                    pThis->file_str, (entry - 1)->pts, entry->pts);
+                CHECKINT_EXIT(AVERROR_INVALIDDATA);
+            }
+            CHECKINT_EXIT(check_for_nopts(ctx, rgTokens[4]));
+            entry->pts_time = atof(rgTokens[4]); // We don't have time_base, so we lose some accuracy, but probably close enough
+            if (pThis->curr_idx > 0 && (entry - 1)->pts_time >= entry->pts_time) {
+                av_log(pThis, AV_LOG_ERROR, "pts_time in %s went from %g to %g, current implementation can't handle this!\n",
+                    pThis->file_str, (entry - 1)->pts_time, entry->pts_time);
+                CHECKINT_EXIT(AVERROR_INVALIDDATA);
+            }
+
+            entry->metadata = NULL;
+            szLastKey[0] = '\0';
+        } else {
+            char *pszEquals;
+            if (pThis->curr_idx < 0) {
+                av_log(pThis, AV_LOG_ERROR, "Expected frame: line in %s, instead got: %s!\n", pThis->file_str, szLine);
+                CHECKINT_EXIT(AVERROR_INVALIDDATA);
+            }
+
+            pszEquals = strchr(szLine, '='); // Find first instance, ignore subsequent
+            if (pszEquals) {
+                // New key/value entry - remove any CRLF found at end of value string
+                int chars_to_copy;
+                char *pszValue = pszEquals + 1;
+                *pszEquals = '\0';
+                remove_trailing_newlines(pszValue);
+                CHECKINT_EXIT(av_dict_set(&pThis->rgEntries[pThis->curr_idx].metadata, szLine, pszValue, 0));
+
+                chars_to_copy = strlen(szLine);
+                if (chars_to_copy >= FF_ARRAY_ELEMS(szLastKey))
+                    chars_to_copy = FF_ARRAY_ELEMS(szLastKey) - 1;
+
+                strncpy(szLastKey, szLine, chars_to_copy);
+                szLastKey[chars_to_copy] = '\0';
+            } else {
+                // This should be a continuation of a prior key/value entry - prepend newline, then append value
+                remove_trailing_newlines(szLine);
+                CHECKINT_EXIT(av_dict_set(&pThis->rgEntries[pThis->curr_idx].metadata, szLastKey, "\n", AV_DICT_APPEND));
+                CHECKINT_EXIT(av_dict_set(&pThis->rgEntries[pThis->curr_idx].metadata, szLastKey, szLine, AV_DICT_APPEND));
+            }
+        }
+    }
+
+    pThis->curr_idx = 0;
+    fclose(file); // Ignore error
+
+exit:
+    return iResult;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    MetadataReaderContext *pThis = ctx->priv;
+    if (pThis->rgEntries) {
+        for (int i = 0; i < pThis->parsed_entries; i++)
+            av_dict_free(&pThis->rgEntries[i].metadata);
+
+        av_freep(&pThis->rgEntries);
+    }
+}
+
+// tolerance is exclusive - we snap to an entry which is different by LESS THAN but not equal to tolerance
+static int set_curr_idx(AVFilterContext *ctx, double pts_time, double tolerance, int *found)
+{
+    int iResult = 0;
+    MetadataReaderContext *pThis = ctx->priv;
+
+    // This function assumes that incoming pts's are increasing and do not regress.
+    *found = 0;
+    while (pThis->curr_idx < pThis->parsed_entries) {
+        MetadataEntry *entry = &pThis->rgEntries[pThis->curr_idx];
+        if (fabs(entry->pts_time - pts_time) < tolerance) {
+            *found = 1;
+            break; // Apply current entry's metadata dictionary
+        } else if (entry->pts_time > pts_time) {
+            if (pThis->curr_idx <= 0) {
+                av_log(pThis, AV_LOG_DEBUG, "No corresponding metadata entries found for pts_time %g.\n",
+                    pts_time);
+                goto exit;
+            }
+
+            if ((entry - 1)->pts_time <= pts_time) {
+                *found = 1;
+                pThis->curr_idx--; // Apply previous entry's metadata dictionary
+                break;
+            } else {
+                av_log(pThis, AV_LOG_ERROR, "pts_time may have regressed: %g may require rewinding more than one entry (not implemented)!\n",
+                    pts_time);
+                CHECKINT_EXIT(AVERROR(ENOTSUP));
+            }
+        } else if (pThis->curr_idx + 1 >= pThis->parsed_entries) {
+            // If we reach this point, then entry->pts_time is strictly < pts_time and no more entries,
+            // so current index is the correct one.
+            *found = 1;
+            break;
+        }
+
+        // Advance variables
+        pThis->curr_idx++;
+    }
+
+exit:
+    return iResult;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
+{
+    int iResult = 0;
+    AVFilterContext *ctx = inlink->dst;
+    MetadataReaderContext *pThis = (MetadataReaderContext*)ctx->priv;
+    int found;
+    double pts_time;
+
+    if (frame->pts == AV_NOPTS_VALUE) {
+        av_log(pThis, AV_LOG_ERROR, "Current implementation cannot handle PTS of AV_NOPTS_VALUE!");
+        CHECKINT_EXIT(AVERROR(ENOTSUP));
+    }
+
+    pts_time = av_q2d(inlink->time_base) * frame->pts;
+    CHECKINT_EXIT(set_curr_idx(ctx, pts_time, 0.001, &found)); // 1 millisecond tolerance
+    if (found) {
+        CHECKINT_EXIT(av_dict_copy(&frame->metadata, pThis->rgEntries[pThis->curr_idx].metadata, 0));
+    }
+
+    av_assert_int32_equals(1, ctx->nb_outputs);
+    CHECKINT_EXIT_EX_EAGAIN_EOF(ff_filter_frame(ctx->outputs[0], frame));
+
+exit:
+    if (iResult < 0) {
+        av_frame_free(&frame);
+    }
+
+    return iResult;
+}
+
+static int filter_frame_audio(AVFilterLink *inlink, AVFrame *frame)
+{
+    int iResult = 0;
+    AVFilterContext *ctx = inlink->dst;
+    MetadataReaderContext *pThis = (MetadataReaderContext*)ctx->priv;
+    AVFrame *pFrame = NULL;
+    int found;
+    int nb_samples_sent = 0;
+    double tolerance, pts_time, pts_end;
+
+    if (frame->pts == AV_NOPTS_VALUE) {
+        av_log(pThis, AV_LOG_ERROR, "Current implementation cannot handle PTS of AV_NOPTS_VALUE!");
+        CHECKINT_EXIT(AVERROR(ENOTSUP));
+    }
+
+    tolerance = 1.0 / inlink->sample_rate; // less than 1 audio sample tolerance
+    pts_time = av_q2d(inlink->time_base) * frame->pts;
+    pts_end = av_q2d(inlink->time_base) * (frame->pts + frame->nb_samples);
+    CHECKINT_EXIT(set_curr_idx(ctx, pts_time, tolerance, &found));
+    if (!found) {
+        av_assert_int32_equals(1, ctx->nb_outputs);
+        CHECKINT_EXIT_EX_EAGAIN_EOF(ff_filter_frame(ctx->outputs[0], frame));
+        goto exit;
+    }
+
+    while (found) {
+        int nb_samples;
+        double metadata_pts_end;
+        if (pThis->curr_idx + 1 < pThis->parsed_entries)
+            metadata_pts_end = pThis->rgEntries[pThis->curr_idx + 1].pts_time;
+        else
+            metadata_pts_end = pts_end;
+
+        nb_samples = (int)(inlink->sample_rate * (fmax(pts_end, metadata_pts_end) - pts_time) + 0.5);
+        av_assert_int32_ge(nb_samples, 1); // If nb_samples is 0 we are going into an infinite loop
+        if (nb_samples > frame->nb_samples - nb_samples_sent)
+            nb_samples = frame->nb_samples - nb_samples_sent;
+
+        pFrame = ff_get_audio_buffer(ctx->outputs[0], nb_samples);
+        CHECKCOND_EXIT(pFrame, AVERROR(ENOMEM));
+        CHECKINT_EXIT(av_samples_copy(pFrame->extended_data, frame->extended_data, 0, nb_samples_sent,
+            nb_samples, frame->ch_layout.nb_channels, frame->format));
+        CHECKINT_EXIT(av_dict_copy(&pFrame->metadata, pThis->rgEntries[pThis->curr_idx].metadata, 0));
+        pFrame->pts = frame->pts + nb_samples_sent;
+
+        av_assert_int32_equals(1, ctx->nb_outputs);
+        CHECKINT_EXIT_EX_EAGAIN_EOF(ff_filter_frame(ctx->outputs[0], pFrame));
+
+        nb_samples_sent += nb_samples;
+        if (nb_samples_sent >= frame->nb_samples)
+            break;
+
+        pts_time = av_q2d(inlink->time_base) * (frame->pts + nb_samples_sent);
+        CHECKINT_EXIT(set_curr_idx(ctx, pts_time, tolerance, &found));
+    }
+
+exit:
+    if (iResult < 0) {
+        av_frame_free(&pFrame);
+        av_frame_free(&frame);
+    }
+
+    return iResult;
+}
+
+#define OFFSET(x) offsetof(MetadataReaderContext, x)
+#define DEFINE_OPTIONS(filt_name, FLAGS) \
+static const AVOption filt_name##_options[] = { \
+    { "file", "set file to read metadata information from", OFFSET(file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, \
+    { NULL } \
+}
+
+#if CONFIG_AMETADATAREADER_FILTER
+
+DEFINE_OPTIONS(ametadatareader, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM);
+AVFILTER_DEFINE_CLASS(ametadatareader);
+
+static const AVFilterPad ainputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_AUDIO,
+        .filter_frame = filter_frame_audio,
+    },
+};
+
+static const AVFilterPad aoutputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_AUDIO,
+    },
+};
+
+const AVFilter ff_af_ametadatareader = {
+    .name = "ametadatareader",
+    .description = NULL_IF_CONFIG_SMALL("Replay a/v metadata from prior session onto audio stream."),
+    .priv_size = sizeof(MetadataReaderContext),
+    .priv_class = &ametadatareader_class,
+    .init = init,
+    .uninit = uninit,
+    FILTER_INPUTS(ainputs),
+    FILTER_OUTPUTS(aoutputs),
+    .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
+};
+#endif // CONFIG_AMETADATAREADER_FILTER
+
+#if CONFIG_METADATAREADER_FILTER
+
+DEFINE_OPTIONS(metadatareader, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM);
+AVFILTER_DEFINE_CLASS(metadatareader);
+
+static const AVFilterPad inputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+    },
+};
+
+static const AVFilterPad outputs[] = {
+    {
+        .name = "default",
+        .type = AVMEDIA_TYPE_VIDEO,
+    },
+};
+
+const AVFilter ff_vf_metadatareader = {
+    .name = "metadatareader",
+    .description = NULL_IF_CONFIG_SMALL("Replay a/v metadata from prior session onto video stream."),
+    .priv_size = sizeof(MetadataReaderContext),
+    .priv_class = &metadatareader_class,
+    .init = init,
+    .uninit = uninit,
+    FILTER_INPUTS(inputs),
+    FILTER_OUTPUTS(outputs),
+    .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
+};
+#endif // CONFIG_METADATAREADER_FILTER
diff --git a/libavutil/error_tracing.h b/libavutil/error_tracing.h
new file mode 100644
index 0000000000..0543c69619
--- /dev/null
+++ b/libavutil/error_tracing.h
@@ -0,0 +1,126 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * error code definitions
+ */
+
+#ifndef AVUTIL_ERROR_TRACING_H
+#define AVUTIL_ERROR_TRACING_H
+
+//===========================================================================
+// Macros
+//===========================================================================
+
+#define CHECKINT_EXIT_AVCLASS(x, avclass)                           \
+    iResult = (x);                                                  \
+    if (iResult < 0) {                                              \
+        av_log(avclass, AV_LOG_WARNING, "%s(%d): *** ERROR ***: %d!\n",  \
+        __FILE__, __LINE__, iResult);                               \
+        goto exit;                                                  \
+    } else {}                                                       \
+
+#define CHECKCOND_EXIT_AVCLASS(cond, errCode, avclass)  CHECKINT_EXIT_AVCLASS((cond) ? 0 : errCode, avclass)
+
+#define CHECKINT_EXIT_EX_EAGAIN_EOF_AVCLASS(x, avclass)                 \
+    iResult = (x);                                                      \
+    if (iResult < 0) {                                                  \
+        if (AVERROR(EAGAIN) != iResult && AVERROR_EOF != iResult) {     \
+            av_log(avclass, AV_LOG_WARNING, "%s(%d): *** ERROR ***: %d!\n",\
+                __FILE__, __LINE__, iResult);                           \
+            goto exit;                                                  \
+        } else {                                                        \
+            av_log(avclass, AV_LOG_DEBUG, "%s(%d): CHECKINT_EXIT_EX_EAGAIN_EOF saw %s!\n", \
+                __FILE__, __LINE__, AVERROR_EOF == iResult ? "AVERROR_EOF" : "AVERROR(EAGAIN)"); \
+        }                                                               \
+    } else {}
+
+#define CHECK_ARG_SIZES(expected, actual, size) \
+    static_assert(sizeof(actual) == size, "The parameter, " AV_STRINGIFY(actual) ", is not " AV_STRINGIFY(size) " bytes! Please use the correctly-sized macro."); \
+    static_assert(sizeof(expected) <= size, "The parameter, " AV_STRINGIFY(expected) ", is greater than " AV_STRINGIFY(size) " bytes! Please use the correctly-sized macro.");
+
+#define av_assert_equals_msg_avclass(expected, actual, msg, avclass, size, formattype) do { \
+    CHECK_ARG_SIZES(expected, actual, size)                             \
+    if ((expected) != (actual)) {                                       \
+        av_log(avclass, AV_LOG_PANIC,                                   \
+            "Assertion failed: Expected " formattype " (%s), actual is " formattype " (%s) %s at %s:%d\n", \
+               (expected), AV_STRINGIFY(expected), (actual),            \
+               AV_STRINGIFY(actual), msg, __FILE__, __LINE__);          \
+        abort();                                                        \
+    }                                                                   \
+} while (0)
+#define av_assert_int32_equals_msg_avclass(expected, actual, msg, avclass) av_assert_equals_msg_avclass(expected, actual, msg, avclass, 4, "%"PRId32)
+#define av_assert_int32_equals_avclass(expected, actual, avclass) av_assert_int32_equals_msg_avclass(expected, actual, "-", avclass)
+#define av_assert_int64_equals_msg_avclass(expected, actual, msg, avclass) av_assert_equals_msg_avclass(expected, actual, msg, avclass, 8, "%"PRId64)
+#define av_assert_int64_equals_avclass(expected, actual, avclass) av_assert_int64_equals_msg_avclass(expected, actual, "-", avclass)
+
+#define av_assert_ge_msg_avclass(actual, minvalue, msg, avclass, size, formattype) do { \
+    CHECK_ARG_SIZES(minvalue, actual, size)                             \
+    if ((actual) < (minvalue)) {                                        \
+        av_log(avclass, AV_LOG_PANIC,                                   \
+            "Assertion failed: %s (" formattype ") < %s (" formattype ") %s at %s:%d\n",        \
+               AV_STRINGIFY(actual), (actual), AV_STRINGIFY(minvalue),  \
+               (minvalue), msg, __FILE__, __LINE__);                    \
+        abort();                                                        \
+        }                                                               \
+} while (0)
+#define av_assert_int32_ge_msg_avclass(actual, minvalue, msg, avclass) av_assert_ge_msg_avclass(actual, minvalue, msg, avclass, 4, "%"PRId32)
+#define av_assert_int32_ge_avclass(actual, minvalue, avclass) av_assert_int32_ge_msg_avclass(actual, minvalue, "-", avclass)
+#define av_assert_int64_ge_msg_avclass(actual, minvalue, msg, avclass) av_assert_ge_msg_avclass(actual, minvalue, msg, avclass, 8, "%"PRId64)
+#define av_assert_int64_ge_avclass(actual, minvalue, avclass) av_assert_int64_ge_msg_avclass(actual, minvalue, "-", avclass)
+
+#define av_assert_str_equals_msg_avclass(expected, actual, msg, avclass) do { \
+    if (strcmp(expected, actual)) {                                     \
+        av_log(avclass, AV_LOG_PANIC,                                   \
+            "Assertion failed: Expected \"%s\" (%s), actual is \"%s\" (%s) %s at %s:%d\n", \
+               (expected), AV_STRINGIFY(expected), (actual),            \
+               AV_STRINGIFY(actual), msg, __FILE__, __LINE__);          \
+        abort();                                                        \
+        }                                                               \
+} while (0)
+#define av_assert_str_equals_avclass(expected, actual, avclass) av_assert_str_equals_msg_avclass(expected, actual, "-", avclass)
+
+//===========================================================================
+// Short Definitions
+//===========================================================================
+
+// By default, use NULL for avclass, but you SHOULD undef and re-define ERROR_TRACING_AVCLASS
+// to use your private avclass in your own *.c file. For instance, if all of your functions
+// use the convention, AVFilterContext *ctx, you can require ctx->priv always be defined
+// when using these macros, or if you always have a private pointer (eg MOVMuxContext *mov
+// in movenc.c), then you can require that mov be defined when using these macros.
+
+#define ERROR_TRACKING_AVCLASS          NULL
+
+#define CHECKINT_EXIT(x)                CHECKINT_EXIT_AVCLASS(x, ERROR_TRACKING_AVCLASS)
+#define CHECKCOND_EXIT(cond, errCode)   CHECKCOND_EXIT_AVCLASS(cond, errCode, ERROR_TRACKING_AVCLASS)
+#define CHECKINT_EXIT_EX_EAGAIN_EOF(x)  CHECKINT_EXIT_EX_EAGAIN_EOF_AVCLASS(x, ERROR_TRACKING_AVCLASS)
+
+#define av_assert_int32_equals_msg(expected, actual, msg)   av_assert_int32_equals_msg_avclass(expected, actual, msg, ERROR_TRACKING_AVCLASS)
+#define av_assert_int32_equals(expected, actual)            av_assert_int32_equals_avclass(expected, actual, ERROR_TRACKING_AVCLASS)
+#define av_assert_int64_equals_msg(expected, actual, msg)   av_assert_int64_equals_msg_avclass(expected, actual, msg, ERROR_TRACKING_AVCLASS)
+#define av_assert_int64_equals(expected, actual)            av_assert_int64_equals_avclass(expected, actual, ERROR_TRACKING_AVCLASS)
+#define av_assert_int32_ge_msg(actual, minvalue, msg)       av_assert_int32_ge_msg_avclass(actual, minvalue, msg, ERROR_TRACKING_AVCLASS)
+#define av_assert_int32_ge(actual, minvalue)                av_assert_int32_ge_avclass(actual, minvalue, ERROR_TRACKING_AVCLASS)
+#define av_assert_int64_ge_msg(actual, minvalue, msg)       av_assert_int64_ge_msg_avclass(actual, minvalue, msg, ERROR_TRACKING_AVCLASS)
+#define av_assert_int64_ge(actual, minvalue)                av_assert_int64_ge_avclass(actual, minvalue, ERROR_TRACKING_AVCLASS)
+#define av_assert_str_equals_msg(expected, actual, msg)     av_assert_str_equals_msg_avclass(expected, actual, msg, ERROR_TRACKING_AVCLASS)
+#define av_assert_str_equals(expected, actual)              av_assert_str_equals_avclass(expected, actual, ERROR_TRACKING_AVCLASS)
+
+#endif // AVUTIL_ERROR_TRACING_H
diff --git a/tests/fate-run.sh b/tests/fate-run.sh
index 525e8e5499..09a7525321 100755
--- a/tests/fate-run.sh
+++ b/tests/fate-run.sh
@@ -76,6 +76,7 @@ oneline(){
 }
 
 run(){
+    echo $target_exec $target_path/"$@" >> $outfile.cmd
     test "${V:-0}" -gt 0 && echo "$target_exec" $target_path/"$@" >&3
     $target_exec $target_path/"$@"
 }
@@ -98,6 +99,7 @@ probetags(){
 }
 
 runlocal(){
+    echo ${base}/"$@" ${base} >&3 >> $outfile.cmd
     test "${V:-0}" -gt 0 && echo ${base}/"$@" ${base} >&3
     ${base}/"$@" ${base}
 }
@@ -576,6 +578,7 @@ null(){
 # must be kept verbatim
 set -f
 
+#echo $command TO $outfile ERRTO $errfile > $outfile.cmd
 exec 3>&2
 eval $command >"$outfile" 2>$errfile
 err=$?
diff --git a/tests/fate/filter-audio.mak b/tests/fate/filter-audio.mak
index eff32b9f81..ca1b60f47a 100644
--- a/tests/fate/filter-audio.mak
+++ b/tests/fate/filter-audio.mak
@@ -397,6 +397,13 @@ fate-filter-hdcd-s32p: CMD = md5 -i $(SRC) -af hdcd -f s32le
 fate-filter-hdcd-s32p: CMP = oneline
 fate-filter-hdcd-s32p: REF = 0c5513e83eedaa10ab6fac9ddc173cf5
 
+# A metadata reader, reading the ref file and connected to a metadata (writer) should be identity transform
+FATE_AFILTER_SAMPLES-$(call ALLYES, AMETADATA_FILTER AMETADATAREADER_FILTER WAV_DEMUXER PCM_S16LE_DECODER) += fate-filter-ametadatareader
+fate-filter-ametadatareader: tests/data/asynth-44100-2.wav
+fate-filter-ametadatareader: CMD = ffmpeg -i $(TARGET_PATH)/tests/data/asynth-44100-2.wav -af \
+    ametadatareader=file=$(SRC_PATH)/tests/ref/fate/filter-metadatareader,ametadata=mode=print:file=- \
+    -f null /dev/null
+
 FATE_AFILTER-yes += fate-filter-formats
 fate-filter-formats: libavfilter/tests/formats$(EXESUF)
 fate-filter-formats: CMD = run libavfilter/tests/formats$(EXESUF)
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index faed832cd4..51d9058f0f 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -711,6 +711,12 @@ fate-filter-refcmp-ssim-rgb: CMD = refcmp_metadata ssim rgb24 0.015
 FATE_FILTER_REFCMP_METADATA-$(CONFIG_SSIM_FILTER) += fate-filter-refcmp-ssim-yuv
 fate-filter-refcmp-ssim-yuv: CMD = refcmp_metadata ssim yuv422p 0.015
 
+# A metadata reader, reading the ref file and connected to a metadata (writer) should be identity transform
+FATE_FILTER_SAMPLES-$(call ALLYES, FFMPEG LAVFI_INDEV TESTSRC2_FILTER METADATA_FILTER METADATAREADER_FILTER) += fate-filter-metadatareader
+fate-filter-metadatareader: CMD = ffmpeg -lavfi \
+    "testsrc2=size=300x200:rate=1:duration=5,format=rgb24,metadatareader=file=$(SRC_PATH)/tests/ref/fate/filter-metadatareader,metadata=mode=print:file=-" \
+    -f null /dev/null
+
 FATE_FILTER-$(call ALLYES, TESTSRC2_FILTER SPLIT_FILTER AVGBLUR_FILTER        \
                            METADATA_FILTER WRAPPED_AVFRAME_ENCODER NULL_MUXER \
                            PIPE_PROTOCOL) += $(FATE_FILTER_REFCMP_METADATA-yes)
diff --git a/tests/ref/fate/filter-ametadatareader b/tests/ref/fate/filter-ametadatareader
new file mode 100644
index 0000000000..e1a473e574
--- /dev/null
+++ b/tests/ref/fate/filter-ametadatareader
@@ -0,0 +1,690 @@
+frame:0    pts:0       pts_time:0
+TestKey1=AAAAA
+frame:1    pts:1024    pts_time:0.02322
+TestKey1=AAAAA
+frame:2    pts:2048    pts_time:0.0464399
+TestKey1=AAAAA
+frame:3    pts:3072    pts_time:0.0696599
+TestKey1=AAAAA
+frame:4    pts:4096    pts_time:0.0928798
+TestKey1=AAAAA
+frame:5    pts:5120    pts_time:0.1161
+TestKey1=AAAAA
+frame:6    pts:6144    pts_time:0.13932
+TestKey1=AAAAA
+frame:7    pts:7168    pts_time:0.16254
+TestKey1=AAAAA
+frame:8    pts:8192    pts_time:0.18576
+TestKey1=AAAAA
+frame:9    pts:9216    pts_time:0.20898
+TestKey1=AAAAA
+frame:10   pts:10240   pts_time:0.2322
+TestKey1=AAAAA
+frame:11   pts:11264   pts_time:0.25542
+TestKey1=AAAAA
+frame:12   pts:12288   pts_time:0.278639
+TestKey1=AAAAA
+frame:13   pts:13312   pts_time:0.301859
+TestKey1=AAAAA
+frame:14   pts:14336   pts_time:0.325079
+TestKey1=AAAAA
+frame:15   pts:15360   pts_time:0.348299
+TestKey1=AAAAA
+frame:16   pts:16384   pts_time:0.371519
+TestKey1=AAAAA
+frame:17   pts:17408   pts_time:0.394739
+TestKey1=AAAAA
+frame:18   pts:18432   pts_time:0.417959
+TestKey1=AAAAA
+frame:19   pts:19456   pts_time:0.441179
+TestKey1=AAAAA
+frame:20   pts:20480   pts_time:0.464399
+TestKey1=AAAAA
+frame:21   pts:21504   pts_time:0.487619
+TestKey1=AAAAA
+frame:22   pts:22528   pts_time:0.510839
+TestKey1=AAAAA
+frame:23   pts:23552   pts_time:0.534059
+TestKey1=AAAAA
+frame:24   pts:24576   pts_time:0.557279
+TestKey1=AAAAA
+frame:25   pts:25600   pts_time:0.580499
+TestKey1=AAAAA
+frame:26   pts:26624   pts_time:0.603719
+TestKey1=AAAAA
+frame:27   pts:27648   pts_time:0.626939
+TestKey1=AAAAA
+frame:28   pts:28672   pts_time:0.650159
+TestKey1=AAAAA
+frame:29   pts:29696   pts_time:0.673379
+TestKey1=AAAAA
+frame:30   pts:30720   pts_time:0.696599
+TestKey1=AAAAA
+frame:31   pts:31744   pts_time:0.719819
+TestKey1=AAAAA
+frame:32   pts:32768   pts_time:0.743039
+TestKey1=AAAAA
+frame:33   pts:33792   pts_time:0.766259
+TestKey1=AAAAA
+frame:34   pts:34816   pts_time:0.789478
+TestKey1=AAAAA
+frame:35   pts:35840   pts_time:0.812698
+TestKey1=AAAAA
+frame:36   pts:36864   pts_time:0.835918
+TestKey1=AAAAA
+frame:37   pts:37888   pts_time:0.859138
+TestKey1=AAAAA
+frame:38   pts:38912   pts_time:0.882358
+TestKey1=AAAAA
+frame:39   pts:39936   pts_time:0.905578
+TestKey1=AAAAA
+frame:40   pts:40960   pts_time:0.928798
+TestKey1=AAAAA
+frame:41   pts:41984   pts_time:0.952018
+TestKey1=AAAAA
+frame:42   pts:43008   pts_time:0.975238
+TestKey1=AAAAA
+frame:43   pts:44032   pts_time:0.998458
+TestKey1=AAAAA
+frame:44   pts:45056   pts_time:1.02168
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:45   pts:46080   pts_time:1.0449
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:46   pts:47104   pts_time:1.06812
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:47   pts:48128   pts_time:1.09134
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:48   pts:49152   pts_time:1.11456
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:49   pts:50176   pts_time:1.13778
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:50   pts:51200   pts_time:1.161
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:51   pts:52224   pts_time:1.18422
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:52   pts:53248   pts_time:1.20744
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:53   pts:54272   pts_time:1.23066
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:54   pts:55296   pts_time:1.25388
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:55   pts:56320   pts_time:1.2771
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:56   pts:57344   pts_time:1.30032
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:57   pts:58368   pts_time:1.32354
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:58   pts:59392   pts_time:1.34676
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:59   pts:60416   pts_time:1.36998
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:60   pts:61440   pts_time:1.3932
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:61   pts:62464   pts_time:1.41642
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:62   pts:63488   pts_time:1.43964
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:63   pts:64512   pts_time:1.46286
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:64   pts:65536   pts_time:1.48608
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:65   pts:66560   pts_time:1.5093
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:66   pts:67584   pts_time:1.53252
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:67   pts:68608   pts_time:1.55574
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:68   pts:69632   pts_time:1.57896
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:69   pts:70656   pts_time:1.60218
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:70   pts:71680   pts_time:1.6254
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:71   pts:72704   pts_time:1.64862
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:72   pts:73728   pts_time:1.67184
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:73   pts:74752   pts_time:1.69506
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:74   pts:75776   pts_time:1.71828
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:75   pts:76800   pts_time:1.7415
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:76   pts:77824   pts_time:1.76472
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:77   pts:78848   pts_time:1.78794
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:78   pts:79872   pts_time:1.81116
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:79   pts:80896   pts_time:1.83438
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:80   pts:81920   pts_time:1.8576
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:81   pts:82944   pts_time:1.88082
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:82   pts:83968   pts_time:1.90404
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:83   pts:84992   pts_time:1.92726
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:84   pts:86016   pts_time:1.95048
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:85   pts:87040   pts_time:1.9737
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:86   pts:88064   pts_time:1.99692
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:87   pts:89088   pts_time:2.02014
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:88   pts:90112   pts_time:2.04336
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:89   pts:91136   pts_time:2.06658
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:90   pts:92160   pts_time:2.0898
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:91   pts:93184   pts_time:2.11302
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:92   pts:94208   pts_time:2.13624
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:93   pts:95232   pts_time:2.15946
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:94   pts:96256   pts_time:2.18268
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:95   pts:97280   pts_time:2.2059
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:96   pts:98304   pts_time:2.22912
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:97   pts:99328   pts_time:2.25234
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:98   pts:100352  pts_time:2.27556
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:99   pts:101376  pts_time:2.29878
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:100  pts:102400  pts_time:2.322
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:101  pts:103424  pts_time:2.34522
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:102  pts:104448  pts_time:2.36844
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:103  pts:105472  pts_time:2.39166
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:104  pts:106496  pts_time:2.41488
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:105  pts:107520  pts_time:2.4381
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:106  pts:108544  pts_time:2.46132
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:107  pts:109568  pts_time:2.48454
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:108  pts:110592  pts_time:2.50776
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:109  pts:111616  pts_time:2.53098
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:110  pts:112640  pts_time:2.5542
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:111  pts:113664  pts_time:2.57741
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:112  pts:114688  pts_time:2.60063
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:113  pts:115712  pts_time:2.62385
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:114  pts:116736  pts_time:2.64707
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:115  pts:117760  pts_time:2.67029
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:116  pts:118784  pts_time:2.69351
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:117  pts:119808  pts_time:2.71673
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:118  pts:120832  pts_time:2.73995
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:119  pts:121856  pts_time:2.76317
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:120  pts:122880  pts_time:2.78639
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:121  pts:123904  pts_time:2.80961
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:122  pts:124928  pts_time:2.83283
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:123  pts:125952  pts_time:2.85605
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:124  pts:126976  pts_time:2.87927
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:125  pts:128000  pts_time:2.90249
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:126  pts:129024  pts_time:2.92571
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:127  pts:130048  pts_time:2.94893
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:128  pts:131072  pts_time:2.97215
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:129  pts:132096  pts_time:2.99537
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:130  pts:133120  pts_time:3.01859
+TestKey1=
+frame:131  pts:134144  pts_time:3.04181
+TestKey1=
+frame:132  pts:135168  pts_time:3.06503
+TestKey1=
+frame:133  pts:136192  pts_time:3.08825
+TestKey1=
+frame:134  pts:137216  pts_time:3.11147
+TestKey1=
+frame:135  pts:138240  pts_time:3.13469
+TestKey1=
+frame:136  pts:139264  pts_time:3.15791
+TestKey1=
+frame:137  pts:140288  pts_time:3.18113
+TestKey1=
+frame:138  pts:141312  pts_time:3.20435
+TestKey1=
+frame:139  pts:142336  pts_time:3.22757
+TestKey1=
+frame:140  pts:143360  pts_time:3.25079
+TestKey1=
+frame:141  pts:144384  pts_time:3.27401
+TestKey1=
+frame:142  pts:145408  pts_time:3.29723
+TestKey1=
+frame:143  pts:146432  pts_time:3.32045
+TestKey1=
+frame:144  pts:147456  pts_time:3.34367
+TestKey1=
+frame:145  pts:148480  pts_time:3.36689
+TestKey1=
+frame:146  pts:149504  pts_time:3.39011
+TestKey1=
+frame:147  pts:150528  pts_time:3.41333
+TestKey1=
+frame:148  pts:151552  pts_time:3.43655
+TestKey1=
+frame:149  pts:152576  pts_time:3.45977
+TestKey1=
+frame:150  pts:153600  pts_time:3.48299
+TestKey1=
+frame:151  pts:154624  pts_time:3.50621
+TestKey1=
+frame:152  pts:155648  pts_time:3.52943
+TestKey1=
+frame:153  pts:156672  pts_time:3.55265
+TestKey1=
+frame:154  pts:157696  pts_time:3.57587
+TestKey1=
+frame:155  pts:158720  pts_time:3.59909
+TestKey1=
+frame:156  pts:159744  pts_time:3.62231
+TestKey1=
+frame:157  pts:160768  pts_time:3.64553
+TestKey1=
+frame:158  pts:161792  pts_time:3.66875
+TestKey1=
+frame:159  pts:162816  pts_time:3.69197
+TestKey1=
+frame:160  pts:163840  pts_time:3.71519
+TestKey1=
+frame:161  pts:164864  pts_time:3.73841
+TestKey1=
+frame:162  pts:165888  pts_time:3.76163
+TestKey1=
+frame:163  pts:166912  pts_time:3.78485
+TestKey1=
+frame:164  pts:167936  pts_time:3.80807
+TestKey1=
+frame:165  pts:168960  pts_time:3.83129
+TestKey1=
+frame:166  pts:169984  pts_time:3.85451
+TestKey1=
+frame:167  pts:171008  pts_time:3.87773
+TestKey1=
+frame:168  pts:172032  pts_time:3.90095
+TestKey1=
+frame:169  pts:173056  pts_time:3.92417
+TestKey1=
+frame:170  pts:174080  pts_time:3.94739
+TestKey1=
+frame:171  pts:175104  pts_time:3.97061
+TestKey1=
+frame:172  pts:176128  pts_time:3.99383
+TestKey1=
+frame:173  pts:177152  pts_time:4.01705
+TestKey1=DDDDD EEEEE
+frame:174  pts:178176  pts_time:4.04027
+TestKey1=DDDDD EEEEE
+frame:175  pts:179200  pts_time:4.06349
+TestKey1=DDDDD EEEEE
+frame:176  pts:180224  pts_time:4.08671
+TestKey1=DDDDD EEEEE
+frame:177  pts:181248  pts_time:4.10993
+TestKey1=DDDDD EEEEE
+frame:178  pts:182272  pts_time:4.13315
+TestKey1=DDDDD EEEEE
+frame:179  pts:183296  pts_time:4.15637
+TestKey1=DDDDD EEEEE
+frame:180  pts:184320  pts_time:4.17959
+TestKey1=DDDDD EEEEE
+frame:181  pts:185344  pts_time:4.20281
+TestKey1=DDDDD EEEEE
+frame:182  pts:186368  pts_time:4.22603
+TestKey1=DDDDD EEEEE
+frame:183  pts:187392  pts_time:4.24925
+TestKey1=DDDDD EEEEE
+frame:184  pts:188416  pts_time:4.27247
+TestKey1=DDDDD EEEEE
+frame:185  pts:189440  pts_time:4.29569
+TestKey1=DDDDD EEEEE
+frame:186  pts:190464  pts_time:4.31891
+TestKey1=DDDDD EEEEE
+frame:187  pts:191488  pts_time:4.34213
+TestKey1=DDDDD EEEEE
+frame:188  pts:192512  pts_time:4.36535
+TestKey1=DDDDD EEEEE
+frame:189  pts:193536  pts_time:4.38857
+TestKey1=DDDDD EEEEE
+frame:190  pts:194560  pts_time:4.41179
+TestKey1=DDDDD EEEEE
+frame:191  pts:195584  pts_time:4.43501
+TestKey1=DDDDD EEEEE
+frame:192  pts:196608  pts_time:4.45823
+TestKey1=DDDDD EEEEE
+frame:193  pts:197632  pts_time:4.48145
+TestKey1=DDDDD EEEEE
+frame:194  pts:198656  pts_time:4.50467
+TestKey1=DDDDD EEEEE
+frame:195  pts:199680  pts_time:4.52789
+TestKey1=DDDDD EEEEE
+frame:196  pts:200704  pts_time:4.55111
+TestKey1=DDDDD EEEEE
+frame:197  pts:201728  pts_time:4.57433
+TestKey1=DDDDD EEEEE
+frame:198  pts:202752  pts_time:4.59755
+TestKey1=DDDDD EEEEE
+frame:199  pts:203776  pts_time:4.62077
+TestKey1=DDDDD EEEEE
+frame:200  pts:204800  pts_time:4.64399
+TestKey1=DDDDD EEEEE
+frame:201  pts:205824  pts_time:4.66721
+TestKey1=DDDDD EEEEE
+frame:202  pts:206848  pts_time:4.69043
+TestKey1=DDDDD EEEEE
+frame:203  pts:207872  pts_time:4.71365
+TestKey1=DDDDD EEEEE
+frame:204  pts:208896  pts_time:4.73687
+TestKey1=DDDDD EEEEE
+frame:205  pts:209920  pts_time:4.76009
+TestKey1=DDDDD EEEEE
+frame:206  pts:210944  pts_time:4.78331
+TestKey1=DDDDD EEEEE
+frame:207  pts:211968  pts_time:4.80653
+TestKey1=DDDDD EEEEE
+frame:208  pts:212992  pts_time:4.82975
+TestKey1=DDDDD EEEEE
+frame:209  pts:214016  pts_time:4.85297
+TestKey1=DDDDD EEEEE
+frame:210  pts:215040  pts_time:4.87619
+TestKey1=DDDDD EEEEE
+frame:211  pts:216064  pts_time:4.89941
+TestKey1=DDDDD EEEEE
+frame:212  pts:217088  pts_time:4.92263
+TestKey1=DDDDD EEEEE
+frame:213  pts:218112  pts_time:4.94585
+TestKey1=DDDDD EEEEE
+frame:214  pts:219136  pts_time:4.96907
+TestKey1=DDDDD EEEEE
+frame:215  pts:220160  pts_time:4.99229
+TestKey1=DDDDD EEEEE
+frame:216  pts:221184  pts_time:5.01551
+TestKey1=DDDDD EEEEE
+frame:217  pts:222208  pts_time:5.03873
+TestKey1=DDDDD EEEEE
+frame:218  pts:223232  pts_time:5.06195
+TestKey1=DDDDD EEEEE
+frame:219  pts:224256  pts_time:5.08517
+TestKey1=DDDDD EEEEE
+frame:220  pts:225280  pts_time:5.10839
+TestKey1=DDDDD EEEEE
+frame:221  pts:226304  pts_time:5.13161
+TestKey1=DDDDD EEEEE
+frame:222  pts:227328  pts_time:5.15483
+TestKey1=DDDDD EEEEE
+frame:223  pts:228352  pts_time:5.17805
+TestKey1=DDDDD EEEEE
+frame:224  pts:229376  pts_time:5.20127
+TestKey1=DDDDD EEEEE
+frame:225  pts:230400  pts_time:5.22449
+TestKey1=DDDDD EEEEE
+frame:226  pts:231424  pts_time:5.24771
+TestKey1=DDDDD EEEEE
+frame:227  pts:232448  pts_time:5.27093
+TestKey1=DDDDD EEEEE
+frame:228  pts:233472  pts_time:5.29415
+TestKey1=DDDDD EEEEE
+frame:229  pts:234496  pts_time:5.31737
+TestKey1=DDDDD EEEEE
+frame:230  pts:235520  pts_time:5.34059
+TestKey1=DDDDD EEEEE
+frame:231  pts:236544  pts_time:5.36381
+TestKey1=DDDDD EEEEE
+frame:232  pts:237568  pts_time:5.38703
+TestKey1=DDDDD EEEEE
+frame:233  pts:238592  pts_time:5.41025
+TestKey1=DDDDD EEEEE
+frame:234  pts:239616  pts_time:5.43347
+TestKey1=DDDDD EEEEE
+frame:235  pts:240640  pts_time:5.45669
+TestKey1=DDDDD EEEEE
+frame:236  pts:241664  pts_time:5.47991
+TestKey1=DDDDD EEEEE
+frame:237  pts:242688  pts_time:5.50313
+TestKey1=DDDDD EEEEE
+frame:238  pts:243712  pts_time:5.52635
+TestKey1=DDDDD EEEEE
+frame:239  pts:244736  pts_time:5.54957
+TestKey1=DDDDD EEEEE
+frame:240  pts:245760  pts_time:5.57279
+TestKey1=DDDDD EEEEE
+frame:241  pts:246784  pts_time:5.59601
+TestKey1=DDDDD EEEEE
+frame:242  pts:247808  pts_time:5.61923
+TestKey1=DDDDD EEEEE
+frame:243  pts:248832  pts_time:5.64245
+TestKey1=DDDDD EEEEE
+frame:244  pts:249856  pts_time:5.66567
+TestKey1=DDDDD EEEEE
+frame:245  pts:250880  pts_time:5.68889
+TestKey1=DDDDD EEEEE
+frame:246  pts:251904  pts_time:5.71211
+TestKey1=DDDDD EEEEE
+frame:247  pts:252928  pts_time:5.73533
+TestKey1=DDDDD EEEEE
+frame:248  pts:253952  pts_time:5.75855
+TestKey1=DDDDD EEEEE
+frame:249  pts:254976  pts_time:5.78177
+TestKey1=DDDDD EEEEE
+frame:250  pts:256000  pts_time:5.80499
+TestKey1=DDDDD EEEEE
+frame:251  pts:257024  pts_time:5.82821
+TestKey1=DDDDD EEEEE
+frame:252  pts:258048  pts_time:5.85143
+TestKey1=DDDDD EEEEE
+frame:253  pts:259072  pts_time:5.87465
+TestKey1=DDDDD EEEEE
+frame:254  pts:260096  pts_time:5.89787
+TestKey1=DDDDD EEEEE
+frame:255  pts:261120  pts_time:5.92109
+TestKey1=DDDDD EEEEE
+frame:256  pts:262144  pts_time:5.94431
+TestKey1=DDDDD EEEEE
+frame:257  pts:263168  pts_time:5.96753
+TestKey1=DDDDD EEEEE
+frame:258  pts:264192  pts_time:5.99075
+TestKey1=DDDDD EEEEE
diff --git a/tests/ref/fate/filter-metadatareader b/tests/ref/fate/filter-metadatareader
new file mode 100644
index 0000000000..f9301960d5
--- /dev/null
+++ b/tests/ref/fate/filter-metadatareader
@@ -0,0 +1,14 @@
+frame:0    pts:0       pts_time:0
+TestKey1=AAAAA
+frame:1    pts:1       pts_time:1
+TestKey1=BBBBB
+TestKey2=bbbbb
+frame:2    pts:2       pts_time:2
+TestKey1=CCCCC
+TestKey3=Multi
+Line
+Value
+frame:3    pts:3       pts_time:3
+TestKey1=
+frame:4    pts:4       pts_time:4
+TestKey1=DDDDD EEEEE
-- 
2.34.1



More information about the ffmpeg-devel mailing list