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 <jua...@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 +@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: +@table @option +@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. +@item log +If specificed, sets the log level for the output out of three options (defaults to frame): +@table @samp +@item frame +Default log level. Outputs min/max/avg per frame. +@item block +Outputs qp data for every block in each frame. +@item all +Outputs min/max/avg and block data per frame. +@end table +@end table + +Supported decoders: +@itemize +@item x264 +@end itemize + +@subsection Examples + +@itemize +@item Get QP min/max/avg and store it in QP.log +@example +ffmpeg -debug extractqp -i <input> -lavfi extractqp="stats_file=QP.log" -f null - +@end example +@item Output only block data +@example +ffmpeg -debug extractqp -i <input> -lavfi extractqp="stats_file=QP.log:log=block" -f null - +@end example +@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".