[FFmpeg-devel] [PATCH] Add metadatareader filter.
Paul B Mahol
onemda at gmail.com
Sat Jul 9 10:59:46 EEST 2022
On Sat, Jul 9, 2022 at 8:44 AM Raymond Cheng <raycheng100 at hotmail.com>
wrote:
> I also feel, btw, that there is already precedent for this kind of implied
> relationship between f_metadata.c and f_metadatareader.c. For example, the
> demuxer/muxer pairs in libavformat, such as movenc.c and mov.c are
> complimentary, the latter is implicitly meant to read what the former has
> written, and there is no obligation to go to some third component as
> neutral ground. Same goes for the encoder/decoder pairs in libavcodec. I
> want the same "mated pair" status for f_metadata.c and f_metadatareader.c.
>
> Having said that, I am not at all opposed to modifying f_metadata.c to
> write VTT instead of its current format, and having f_metadatareader.c read
> VTT. The benefit of that is that VTT is a standalone format understood
> everywhere, so that producing such a VTT file can be used by browsers, not
> just f_metadatareader.c. You wouldn't have to burn hard subs by reading the
> VTT into f_metadatareader.c, you could just place it alongside the MP4 and
> the browser will render soft subs. But I'd prefer to do that in stages,
> with first stage making no modifications to f_metadata.c.
>
VTT is not good pick at all.
This functionality should be in metadata filter instead, and not in
separate filter.
Also it is too much complex implementation of interesting idea.
>
> ...Cheng
>
> -----Original Message-----
> From: Raymond Cheng <raycheng100 at hotmail.com>
> Sent: Saturday, July 9, 2022 9:26 AM
> To: FFmpeg development discussions and patches <ffmpeg-devel at ffmpeg.org>
> Subject: RE: [FFmpeg-devel] [PATCH] Add metadatareader filter.
>
> Thanks, it will probably take me a couple days to incorporate some of the
> feedback regarding style, but some comments:
>
> 1) The intent of metadatareader is to read what the metadata filter wrote
> to a file, which as you already know, does not make use of any established
> format. It simply calls vsnprintf. This intent is backed by FATE test, so
> any breaking changes ought to hopefully be caught (and if not, the tests
> can be enhanced). If I were to follow your suggestion of adopting a format,
> then I think I'd choose VTT, and this would be a much bigger change. It
> would necessitate that the metadata filter also be modified to output to
> VTT rather than use its current log format. I think if we wanted to do
> that, I'd still prefer to do that as a stage 2, and start with a stage 1
> where f_metadata.c is UNMODIFIED but we still have a f_metadatareader.c
> which can read what it writes to a log file. Are you suggesting that we can
> have such a stage 1, with unmodified f_metadata.c, where f_metadatareader.c
> is using an already established format already available in ffmpeg? I think
> I would be hesitant to go that path d
> ue to the asymmetry (f_metadatareader.c uses an existing libavformat to
> parse, but f_metadata.c does not and calls vsnprintf directly).
>
> 2) Regarding error_tracing.h, I'm all for having a discussion (I assume
> you are suggesting to start a new discussion thread), but perhaps also as a
> stage 2, maybe for stage 1, I will just delete the file and only move the
> macros currently being used into f_metadatareader.c so that they are
> self-contained and not intended for general use.
>
> ...Cheng
>
> -----Original Message-----
> From: ffmpeg-devel <ffmpeg-devel-bounces at ffmpeg.org> On Behalf Of Nicolas
> George
> Sent: Thursday, July 7, 2022 3:09 PM
> To: FFmpeg development discussions and patches <ffmpeg-devel at ffmpeg.org>
> Subject: Re: [FFmpeg-devel] [PATCH] Add metadatareader filter.
>
> Raymond Cheng (12022-07-06):
> > 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.
>
> Thanks for the contribution. The feature is interesting, but details
> will need to be worked on.
>
> The coding style needs to be consistent with the rest of FFmpeg's code.
> In particular, only use CamelCase for types, not for variables and
> functions. And we definitely do not include the type of variables in
> their names.
>
> >
> > 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 +++++++++++++++++
>
> It looks to me the file from which the metadata is taken gets parsed in
> this patch, and the syntax was invented for the occasion.
>
> First, if the syntax is invented for the occasion, it must be
> documented.
>
> Second, we try to avoid inventing a syntax for the occasion. Note that
> this objection could have been applied to the print option of the
> metadata filter; but printing is a much simpler task than parsing and is
> often done the quick-and-dirty way.
>
> I suggest to use libavformat to read frames metadata from a supported
> format.
>
> If libavformat does not contain a format that supports frame metadata
> and is easy to write, then we can invent a syntax there.
>
> But before inventing a syntax for the whole format, we can consider
> using a subtitle format and only invent a syntax to encode frame
> metadata in the subtitles text.
>
> > libavutil/error_tracing.h | 126 +++++
>
> I am all for adding syntactic sugar in libavutil to make error checking
> more lightweight. But it needs to be discussed in its own right to meet
> the needs and style of more developers.
>
> > tests/fate-run.sh | 3 +
>
> Spurious unrelated changes, debug?
>
> > 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)) {
>
> This is misusing FF_ARRAY_ELEMS(): you want the actual size.
>
> > + 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
>
> Regards,
>
> --
> Nicolas George
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request at ffmpeg.org with subject "unsubscribe".
>
More information about the ffmpeg-devel
mailing list