[FFmpeg-devel] [PATCH 3/3] libavfilter: extractqp filter
Juan De León
juandl at google.com
Tue Aug 20 02:36:55 EEST 2019
Extracts quantization parameters data from AVEncodeInfoFrame side data,
if available, then calculates min/max/avg and outputs the results in a log file.
Signed-off-by: Juan De León <juandl at google.com>
---
doc/filters.texi | 40 ++++++
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vf_extractqp.c | 243 +++++++++++++++++++++++++++++++++++++
4 files changed, 285 insertions(+)
create mode 100644 libavfilter/vf_extractqp.c
diff --git a/doc/filters.texi b/doc/filters.texi
index e081cdc7bc..46a82b147a 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -9607,6 +9607,46 @@ ffmpeg -i video.avi -filter_complex 'extractplanes=y+u+v[y][u][v]' -map '[y]' y.
@end example
@end itemize
+ at section extractqp
+
+Extracts Quantization Parameters from @code{AVFrameSideData} and calculates min/max/avg QP.
+
+QP extraction must be enabled with the option @code{-debug extractqp} before decoding the stream and calling the filter.
+
+The filter accepts the following options:
+ at table @option
+ at item stats_file, f
+If specified, the filter will use the named file to output the QP data. If set to '-' the data is sent to standard output.
+ at item log
+If specificed, sets the log level for the output out of three options (defaults to frame):
+ at table @samp
+ at item frame
+Default log level. Outputs min/max/avg per frame.
+ at item block
+Outputs qp data for every block in each frame.
+ at item all
+Outputs min/max/avg and block data per frame.
+ at end table
+ at end table
+
+Supported decoders:
+ at itemize
+ at item x264
+ at end itemize
+
+ at subsection Examples
+
+ at itemize
+ at item Get QP min/max/avg and store it in QP.log
+ at example
+ffmpeg -debug extractqp -i <input> -lavfi extractqp="stats_file=QP.log" -f null -
+ at end example
+ at item Output only block data
+ at example
+ffmpeg -debug extractqp -i <input> -lavfi extractqp="stats_file=QP.log:log=block" -f null -
+ at end example
+ at end itemize
+
@section elbg
Apply a posterize effect using the ELBG (Enhanced LBG) algorithm.
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index efc7bbb153..5944558c14 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -231,6 +231,7 @@ OBJS-$(CONFIG_EROSION_FILTER) += vf_neighbor.o
OBJS-$(CONFIG_EROSION_OPENCL_FILTER) += vf_neighbor_opencl.o opencl.o \
opencl/neighbor.o
OBJS-$(CONFIG_EXTRACTPLANES_FILTER) += vf_extractplanes.o
+OBJS-$(CONFIG_EXTRACTQP_FILTER) += vf_extractqp.o
OBJS-$(CONFIG_FADE_FILTER) += vf_fade.o
OBJS-$(CONFIG_FFTDNOIZ_FILTER) += vf_fftdnoiz.o
OBJS-$(CONFIG_FFTFILT_FILTER) += vf_fftfilt.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index abd726d616..93ea25401a 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -217,6 +217,7 @@ extern AVFilter ff_vf_eq;
extern AVFilter ff_vf_erosion;
extern AVFilter ff_vf_erosion_opencl;
extern AVFilter ff_vf_extractplanes;
+extern AVFilter ff_vf_extractqp;
extern AVFilter ff_vf_fade;
extern AVFilter ff_vf_fftdnoiz;
extern AVFilter ff_vf_fftfilt;
diff --git a/libavfilter/vf_extractqp.c b/libavfilter/vf_extractqp.c
new file mode 100644
index 0000000000..67a1770849
--- /dev/null
+++ b/libavfilter/vf_extractqp.c
@@ -0,0 +1,243 @@
+/*
+ * 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
+ */
+
+#include <limits.h>
+#include <stddef.h>
+
+#include "libavutil/frame.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "libavutil/encode_info.h"
+#include "libavfilter/avfilter.h"
+#include "libavfilter/internal.h"
+#include "libavfilter/video.h"
+#include "libavformat/avio.h"
+
+// Output flags
+#define EXTRACTQP_LOG_FRAME (1<<0)
+#define EXTRACTQP_LOG_BLOCK (1<<1)
+
+typedef struct ExtractQPContext {
+ const AVClass *class;
+ int log_level, nb_frames;
+ int frame_w, frame_h;
+ AVEncodeInfoFrame *frame_info;
+ int min_q, max_q;
+ double avg_q;
+ FILE *stats_file;
+ char *stats_file_str;
+ AVIOContext *avio_context;
+} ExtractQPContext;
+
+#define OFFSET(x) offsetof(ExtractQPContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+static const AVOption extractqp_options[] = {
+ { "stats_file", "Set file to store QP information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
+ { "f", "Set file to store QP information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
+ { "log", "Set output log level for filter" , OFFSET(log_level), AV_OPT_TYPE_INT, {.i64=EXTRACTQP_LOG_FRAME}, INT_MIN, INT_MAX, FLAGS, "log"},
+ { "frame", "Default log level. Outputs min/max/avg per frame.", 0, AV_OPT_TYPE_CONST, {.i64=EXTRACTQP_LOG_FRAME}, INT_MIN, INT_MAX, FLAGS, "log"},
+ { "block", "Outputs both calculations and blocks.", 0, AV_OPT_TYPE_CONST, {.i64=EXTRACTQP_LOG_BLOCK}, INT_MIN, INT_MAX, FLAGS, "log"},
+ { "all", "Outputs both calculations and blocks.", 0, AV_OPT_TYPE_CONST, {.i64=EXTRACTQP_LOG_BLOCK+EXTRACTQP_LOG_FRAME}, INT_MIN, INT_MAX, FLAGS, "log"},
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(extractqp);
+
+/**
+ * Reset calculations to process next frame.
+ */
+static int resetStats(ExtractQPContext *s)
+{
+ s->min_q = INT_MAX;
+ s->max_q = INT_MIN;
+ s->avg_q = 0;
+ return 0;
+}
+
+static int calculateStats(ExtractQPContext *s)
+{
+ AVEncodeInfoFrame *frame_info = s->frame_info;
+
+ /*
+ * Sometimes frame_width*frame_height does not match the coded pic dimensions.
+ * Average is always accurate if the area of each block is summed to get the
+ * area of the coded frame.
+ */
+ int pixcount = 0;
+
+ // Calculates min, max, and avg of the deltas then adds base quantizer
+ if (frame_info->nb_blocks > 0) {
+ for(int i = 0; i < frame_info->nb_blocks; i++) {
+ AVEncodeInfoBlock *block = av_encode_info_get_block(frame_info, i);
+ s->min_q = FFMIN(block->delta_q, s->min_q);
+ s->max_q = FFMAX(block->delta_q, s->max_q);
+
+ // Normalizes delta_q based on block size.
+ s->avg_q += block->delta_q * block->w*block->h;
+ pixcount += block->w * block->h;
+ }
+
+ s->avg_q= s->avg_q / pixcount;
+
+ s->min_q += frame_info->plane_q[0];
+ s->max_q += frame_info->plane_q[0];
+ s->avg_q += frame_info->plane_q[0];
+ }
+
+ else {
+ s->min_q = s->max_q = s->avg_q = frame_info->plane_q[0];
+ }
+
+ return 0;
+}
+
+static int printStats(ExtractQPContext *s)
+{
+ AVEncodeInfoFrame *frame_info = s->frame_info;
+
+ //prints for default, log=frame, or log=all
+ if (s->log_level & EXTRACTQP_LOG_FRAME) {
+ avio_printf(s->avio_context, "n:%d min_q:%d max_q:%d avg_q:%.3f ",
+ s->nb_frames, s->min_q, s->max_q, s->avg_q);
+
+ avio_printf(s->avio_context, "base_q:%d ac_q:%d dc_q:%d ",
+ frame_info->plane_q[0], frame_info->ac_q, frame_info->dc_q);
+
+ avio_printf(s->avio_context, "uv_q:%d ac_uv_q:%d dc_uv_q:%d ",
+ frame_info->plane_q[1], frame_info->ac_q, frame_info->dc_q);
+
+ avio_printf(s->avio_context, "v_q:%d ", frame_info->plane_q[2]);
+
+ avio_write(s->avio_context, "\n", sizeof (char));
+ }
+
+ //prints for log=block or log=all
+ if(s->log_level & EXTRACTQP_LOG_BLOCK) {
+ AVEncodeInfoBlock *block;
+ int qp;
+ for (int i=0; i<frame_info->nb_blocks; i++) {
+ block = av_encode_info_get_block(frame_info, i);
+ qp = block->delta_q + frame_info->plane_q[0];
+
+ avio_printf(s->avio_context, "n:%d x:%d y:%d w:%d h:%d qp:%d\n",
+ s->nb_frames, block->src_x, block->src_y, block->w, block->h, qp);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Main filtering function, checks for AV_FRAME_DATA_ENCODE_INFO type in
+ * AVFrameSideData for each frame and calculates min, max, and average qp
+ * for each frame, then prints the results in a log file.
+ * User manual in doc/filters.texi
+ */
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+ AVFilterContext *ctx = inlink->dst;
+ AVFilterLink *outlink = ctx->outputs[0];
+ ExtractQPContext *s = ctx->priv;
+
+ s->nb_frames++;
+
+ if (ctx->is_disabled) {
+ return ff_filter_frame(outlink, in);
+ }
+
+ AVFrameSideData *sd = av_frame_get_side_data(in, AV_FRAME_DATA_ENCODE_INFO);
+ if (!sd) {
+ av_log(ctx, AV_LOG_ERROR, "No encode info side data found in frame %d\n", s->nb_frames);
+ return ff_filter_frame(outlink, in);
+ }
+
+ s->frame_info = (AVEncodeInfoFrame *)sd->data;
+ if (!(s->frame_info)) {
+ av_log(ctx, AV_LOG_ERROR, "Empty side data in frame:%d\n", s->nb_frames);
+ return ff_filter_frame(outlink, in);
+ }
+
+ calculateStats(s);
+ printStats(s);
+ resetStats(s);
+
+ return ff_filter_frame(outlink, in);
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+ ExtractQPContext *s = ctx->priv;
+ int ret;
+
+ resetStats(s);
+
+ s->avio_context = NULL;
+ if (s->stats_file_str) {
+ if (!strcmp("-", s->stats_file_str)) {
+ ret = avio_open(&s->avio_context, "pipe:1", AVIO_FLAG_WRITE);
+ } else {
+ ret = avio_open(&s->avio_context, s->stats_file_str, AVIO_FLAG_WRITE);
+ }
+ }
+ if (ret < 0) {
+ char buf[128];
+ av_strerror(ret, buf, sizeof(buf));
+ av_log(ctx, AV_LOG_ERROR, "Could not open %s: %s\n",
+ s->stats_file_str, buf);
+ return ret;
+ }
+
+ return 0;
+}
+
+static av_cold int uninit(AVFilterContext *ctx) {
+ ExtractQPContext *s = ctx->priv;
+ if (s->avio_context) {
+ avio_closep(&s->avio_context);
+ }
+ return 0;
+}
+
+static const AVFilterPad extractqp_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = filter_frame,
+ },
+ { NULL }
+};
+
+static const AVFilterPad extractqp_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ },
+ { NULL }
+};
+
+AVFilter ff_vf_extractqp = {
+ .name = "extractqp",
+ .description = NULL_IF_CONFIG_SMALL("Extract qp from AV_FRAME_DATA_ENCODE_INFO side data type."),
+ .init = init,
+ .uninit = uninit,
+ .priv_size = sizeof(ExtractQPContext),
+ .priv_class = &extractqp_class,
+ .inputs = extractqp_inputs,
+ .outputs = extractqp_outputs,
+};
--
2.23.0.rc1.153.gdeed80330f-goog
More information about the ffmpeg-devel
mailing list