FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
vf_psnr.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2011 Roger Pau Monné <roger.pau@entel.upc.edu>
3  * Copyright (c) 2011 Stefano Sabatini
4  * Copyright (c) 2013 Paul B Mahol
5  *
6  * This file is part of FFmpeg.
7  *
8  * FFmpeg is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * FFmpeg is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with FFmpeg; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 /**
24  * @file
25  * Caculate the PSNR between two input videos.
26  */
27 
28 #include "libavutil/opt.h"
29 #include "libavutil/pixdesc.h"
30 #include "avfilter.h"
31 #include "dualinput.h"
32 #include "drawutils.h"
33 #include "formats.h"
34 #include "internal.h"
35 #include "video.h"
36 
37 typedef struct PSNRContext {
38  const AVClass *class;
40  double mse, min_mse, max_mse;
41  uint64_t nb_frames;
42  FILE *stats_file;
44  int max[4], average_max;
45  int is_rgb;
47  char comps[4];
49  int planewidth[4];
50  int planeheight[4];
51 
53  const uint8_t *m[4], const int ml[4],
54  const uint8_t *r[4], const int rl[4],
55  int w, int h, double mse[4]);
56 } PSNRContext;
57 
58 #define OFFSET(x) offsetof(PSNRContext, x)
59 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
60 
61 static const AVOption psnr_options[] = {
62  {"stats_file", "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
63  {"f", "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
64  { NULL }
65 };
66 
68 
69 static inline unsigned pow2(unsigned base)
70 {
71  return base*base;
72 }
73 
74 static inline double get_psnr(double mse, uint64_t nb_frames, int max)
75 {
76  return 10.0 * log(pow2(max) / (mse / nb_frames)) / log(10.0);
77 }
78 
79 static inline
81  const uint8_t *main_data[4], const int main_linesizes[4],
82  const uint8_t *ref_data[4], const int ref_linesizes[4],
83  int w, int h, double mse[4])
84 {
85  int i, c, j;
86 
87  for (c = 0; c < s->nb_components; c++) {
88  const int outw = s->planewidth[c];
89  const int outh = s->planeheight[c];
90  const uint8_t *main_line = main_data[c];
91  const uint8_t *ref_line = ref_data[c];
92  const int ref_linesize = ref_linesizes[c];
93  const int main_linesize = main_linesizes[c];
94  uint64_t m = 0;
95 
96  for (i = 0; i < outh; i++) {
97  int m2 = 0;
98  for (j = 0; j < outw; j++)
99  m2 += pow2(main_line[j] - ref_line[j]);
100  m += m2;
101  ref_line += ref_linesize;
102  main_line += main_linesize;
103  }
104  mse[c] = m / (double)(outw * outh);
105  }
106 }
107 
108 static inline
110  const uint8_t *main_data[4], const int main_linesizes[4],
111  const uint8_t *ref_data[4], const int ref_linesizes[4],
112  int w, int h, double mse[4])
113 {
114  int i, c, j;
115 
116  for (c = 0; c < s->nb_components; c++) {
117  const int outw = s->planewidth[c];
118  const int outh = s->planeheight[c];
119  const uint16_t *main_line = (uint16_t *)main_data[c];
120  const uint16_t *ref_line = (uint16_t *)ref_data[c];
121  const int ref_linesize = ref_linesizes[c] / 2;
122  const int main_linesize = main_linesizes[c] / 2;
123  uint64_t m = 0;
124 
125  for (i = 0; i < outh; i++) {
126  for (j = 0; j < outw; j++)
127  m += pow2(main_line[j] - ref_line[j]);
128  ref_line += ref_linesize;
129  main_line += main_linesize;
130  }
131  mse[c] = m / (double)(outw * outh);
132  }
133 }
134 
135 static void set_meta(AVDictionary **metadata, const char *key, char comp, float d)
136 {
137  char value[128];
138  snprintf(value, sizeof(value), "%0.2f", d);
139  if (comp) {
140  char key2[128];
141  snprintf(key2, sizeof(key2), "%s%c", key, comp);
142  av_dict_set(metadata, key2, value, 0);
143  } else {
144  av_dict_set(metadata, key, value, 0);
145  }
146 }
147 
149  const AVFrame *ref)
150 {
151  PSNRContext *s = ctx->priv;
152  double comp_mse[4], mse = 0;
153  int j, c;
154  AVDictionary **metadata = avpriv_frame_get_metadatap(main);
155 
156  s->compute_mse(s, (const uint8_t **)main->data, main->linesize,
157  (const uint8_t **)ref->data, ref->linesize,
158  main->width, main->height, comp_mse);
159 
160  for (j = 0; j < s->nb_components; j++)
161  mse += comp_mse[j];
162  mse /= s->nb_components;
163 
164  s->min_mse = FFMIN(s->min_mse, mse);
165  s->max_mse = FFMAX(s->max_mse, mse);
166 
167  s->mse += mse;
168  s->nb_frames++;
169 
170  for (j = 0; j < s->nb_components; j++) {
171  c = s->is_rgb ? s->rgba_map[j] : j;
172  set_meta(metadata, "lavfi.psnr.mse.", s->comps[j], comp_mse[c]);
173  set_meta(metadata, "lavfi.psnr.mse_avg", 0, mse);
174  set_meta(metadata, "lavfi.psnr.psnr.", s->comps[j], get_psnr(comp_mse[c], 1, s->max[c]));
175  set_meta(metadata, "lavfi.psnr.psnr_avg", 0, get_psnr(mse, 1, s->average_max));
176  }
177 
178  if (s->stats_file) {
179  fprintf(s->stats_file, "n:%"PRId64" mse_avg:%0.2f ", s->nb_frames, mse);
180  for (j = 0; j < s->nb_components; j++) {
181  c = s->is_rgb ? s->rgba_map[j] : j;
182  fprintf(s->stats_file, "mse_%c:%0.2f ", s->comps[j], comp_mse[c]);
183  }
184  for (j = 0; j < s->nb_components; j++) {
185  c = s->is_rgb ? s->rgba_map[j] : j;
186  fprintf(s->stats_file, "psnr_%c:%0.2f ", s->comps[j],
187  get_psnr(comp_mse[c], 1, s->max[c]));
188  }
189  fprintf(s->stats_file, "\n");
190  }
191 
192  return main;
193 }
194 
195 static av_cold int init(AVFilterContext *ctx)
196 {
197  PSNRContext *s = ctx->priv;
198 
199  s->min_mse = +INFINITY;
200  s->max_mse = -INFINITY;
201 
202  if (s->stats_file_str) {
203  s->stats_file = fopen(s->stats_file_str, "w");
204  if (!s->stats_file) {
205  int err = AVERROR(errno);
206  char buf[128];
207  av_strerror(err, buf, sizeof(buf));
208  av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s: %s\n",
209  s->stats_file_str, buf);
210  return err;
211  }
212  }
213 
214  s->dinput.process = do_psnr;
215  return 0;
216 }
217 
219 {
220  static const enum PixelFormat pix_fmts[] = {
222 #define PF_NOALPHA(suf) AV_PIX_FMT_YUV420##suf, AV_PIX_FMT_YUV422##suf, AV_PIX_FMT_YUV444##suf
223 #define PF_ALPHA(suf) AV_PIX_FMT_YUVA420##suf, AV_PIX_FMT_YUVA422##suf, AV_PIX_FMT_YUVA444##suf
224 #define PF(suf) PF_NOALPHA(suf), PF_ALPHA(suf)
225  PF(P), PF(P9), PF(P10), PF_NOALPHA(P12), PF_NOALPHA(P14), PF(P16),
233  };
234 
236  return 0;
237 }
238 
239 static int config_input_ref(AVFilterLink *inlink)
240 {
241  const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
242  AVFilterContext *ctx = inlink->dst;
243  PSNRContext *s = ctx->priv;
244  int j;
245 
246  s->nb_components = desc->nb_components;
247  if (ctx->inputs[0]->w != ctx->inputs[1]->w ||
248  ctx->inputs[0]->h != ctx->inputs[1]->h) {
249  av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n");
250  return AVERROR(EINVAL);
251  }
252  if (ctx->inputs[0]->format != ctx->inputs[1]->format) {
253  av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n");
254  return AVERROR(EINVAL);
255  }
256 
257  switch (inlink->format) {
258  case AV_PIX_FMT_GRAY8:
259  case AV_PIX_FMT_GRAY16:
260  case AV_PIX_FMT_GBRP:
261  case AV_PIX_FMT_GBRP9:
262  case AV_PIX_FMT_GBRP10:
263  case AV_PIX_FMT_GBRP12:
264  case AV_PIX_FMT_GBRP14:
265  case AV_PIX_FMT_GBRP16:
266  case AV_PIX_FMT_GBRAP:
267  case AV_PIX_FMT_GBRAP16:
268  case AV_PIX_FMT_YUVJ411P:
269  case AV_PIX_FMT_YUVJ420P:
270  case AV_PIX_FMT_YUVJ422P:
271  case AV_PIX_FMT_YUVJ440P:
272  case AV_PIX_FMT_YUVJ444P:
273  s->max[0] = (1 << (desc->comp[0].depth_minus1 + 1)) - 1;
274  s->max[1] = (1 << (desc->comp[1].depth_minus1 + 1)) - 1;
275  s->max[2] = (1 << (desc->comp[2].depth_minus1 + 1)) - 1;
276  s->max[3] = (1 << (desc->comp[3].depth_minus1 + 1)) - 1;
277  break;
278  default:
279  s->max[0] = 235 * (1 << (desc->comp[0].depth_minus1 - 7));
280  s->max[1] = 240 * (1 << (desc->comp[1].depth_minus1 - 7));
281  s->max[2] = 240 * (1 << (desc->comp[2].depth_minus1 - 7));
282  s->max[3] = (1 << (desc->comp[3].depth_minus1 + 1)) - 1;
283  }
284 
285  s->is_rgb = ff_fill_rgba_map(s->rgba_map, inlink->format) >= 0;
286  s->comps[0] = s->is_rgb ? 'r' : 'y' ;
287  s->comps[1] = s->is_rgb ? 'g' : 'u' ;
288  s->comps[2] = s->is_rgb ? 'b' : 'v' ;
289  s->comps[3] = 'a';
290 
291  for (j = 0; j < s->nb_components; j++)
292  s->average_max += s->max[j];
293  s->average_max /= s->nb_components;
294 
295  s->planeheight[1] = s->planeheight[2] = FF_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
296  s->planeheight[0] = s->planeheight[3] = inlink->h;
297  s->planewidth[1] = s->planewidth[2] = FF_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
298  s->planewidth[0] = s->planewidth[3] = inlink->w;
299 
301 
302  return 0;
303 }
304 
305 static int config_output(AVFilterLink *outlink)
306 {
307  AVFilterContext *ctx = outlink->src;
308  PSNRContext *s = ctx->priv;
309  AVFilterLink *mainlink = ctx->inputs[0];
310  int ret;
311 
312  outlink->w = mainlink->w;
313  outlink->h = mainlink->h;
314  outlink->time_base = mainlink->time_base;
315  outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
316  outlink->frame_rate = mainlink->frame_rate;
317  if ((ret = ff_dualinput_init(ctx, &s->dinput)) < 0)
318  return ret;
319 
320  return 0;
321 }
322 
323 static int filter_frame(AVFilterLink *inlink, AVFrame *inpicref)
324 {
325  PSNRContext *s = inlink->dst->priv;
326  return ff_dualinput_filter_frame(&s->dinput, inlink, inpicref);
327 }
328 
329 static int request_frame(AVFilterLink *outlink)
330 {
331  PSNRContext *s = outlink->src->priv;
332  return ff_dualinput_request_frame(&s->dinput, outlink);
333 }
334 
335 static av_cold void uninit(AVFilterContext *ctx)
336 {
337  PSNRContext *s = ctx->priv;
338 
339  if (s->nb_frames > 0) {
340  av_log(ctx, AV_LOG_INFO, "PSNR average:%0.2f min:%0.2f max:%0.2f\n",
341  get_psnr(s->mse, s->nb_frames, s->average_max),
342  get_psnr(s->max_mse, 1, s->average_max),
343  get_psnr(s->min_mse, 1, s->average_max));
344  }
345 
347 
348  if (s->stats_file)
349  fclose(s->stats_file);
350 }
351 
352 static const AVFilterPad psnr_inputs[] = {
353  {
354  .name = "main",
355  .type = AVMEDIA_TYPE_VIDEO,
356  .filter_frame = filter_frame,
357  },{
358  .name = "reference",
359  .type = AVMEDIA_TYPE_VIDEO,
360  .filter_frame = filter_frame,
361  .config_props = config_input_ref,
362  },
363  { NULL }
364 };
365 
366 static const AVFilterPad psnr_outputs[] = {
367  {
368  .name = "default",
369  .type = AVMEDIA_TYPE_VIDEO,
370  .config_props = config_output,
371  .request_frame = request_frame,
372  },
373  { NULL }
374 };
375 
377  .name = "psnr",
378  .description = NULL_IF_CONFIG_SMALL("Calculate the PSNR between two video streams."),
379  .init = init,
380  .uninit = uninit,
381  .query_formats = query_formats,
382  .priv_size = sizeof(PSNRContext),
383  .priv_class = &psnr_class,
384  .inputs = psnr_inputs,
385  .outputs = psnr_outputs,
386 };