[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