[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