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 } PSNRContext;
50 
51 #define OFFSET(x) offsetof(PSNRContext, x)
52 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
53 
54 static const AVOption psnr_options[] = {
55  {"stats_file", "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
56  {"f", "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
57  { NULL },
58 };
59 
61 
62 static inline int pow2(int base)
63 {
64  return base*base;
65 }
66 
67 static inline double get_psnr(double mse, uint64_t nb_frames, int max)
68 {
69  return 10.0 * log(pow2(max) / (mse / nb_frames)) / log(10.0);
70 }
71 
72 static inline
73 void compute_images_mse(const uint8_t *main_data[4], const int main_linesizes[4],
74  const uint8_t *ref_data[4], const int ref_linesizes[4],
75  int w, int h, const AVPixFmtDescriptor *desc,
76  double mse[4])
77 {
78  int i, c, j;
79 
80  for (c = 0; c < desc->nb_components; c++) {
81  int hsub = c == 1 || c == 2 ? desc->log2_chroma_w : 0;
82  int vsub = c == 1 || c == 2 ? desc->log2_chroma_h : 0;
83  const int outw = FF_CEIL_RSHIFT(w, hsub);
84  const int outh = FF_CEIL_RSHIFT(h, vsub);
85  const uint8_t *main_line = main_data[c];
86  const uint8_t *ref_line = ref_data[c];
87  const int ref_linesize = ref_linesizes[c];
88  const int main_linesize = main_linesizes[c];
89  int m = 0;
90 
91  for (i = 0; i < outh; i++) {
92  for (j = 0; j < outw; j++)
93  m += pow2(main_line[j] - ref_line[j]);
94  ref_line += ref_linesize;
95  main_line += main_linesize;
96  }
97  mse[c] = m / (outw * outh);
98  }
99 }
100 
101 static void set_meta(AVDictionary **metadata, const char *key, char comp, float d)
102 {
103  char value[128];
104  snprintf(value, sizeof(value), "%0.2f", d);
105  if (comp) {
106  char key2[128];
107  snprintf(key2, sizeof(key2), "%s%c", key, comp);
108  av_dict_set(metadata, key2, value, 0);
109  } else {
110  av_dict_set(metadata, key, value, 0);
111  }
112 }
113 
115  const AVFrame *ref)
116 {
117  PSNRContext *s = ctx->priv;
118  double comp_mse[4], mse = 0;
119  int j, c;
120  AVDictionary **metadata = avpriv_frame_get_metadatap(main);
121 
122  compute_images_mse((const uint8_t **)main->data, main->linesize,
123  (const uint8_t **)ref->data, ref->linesize,
124  main->width, main->height, s->desc, comp_mse);
125 
126  for (j = 0; j < s->desc->nb_components; j++)
127  mse += comp_mse[j];
128  mse /= s->desc->nb_components;
129 
130  s->min_mse = FFMIN(s->min_mse, mse);
131  s->max_mse = FFMAX(s->max_mse, mse);
132 
133  s->mse += mse;
134  s->nb_frames++;
135 
136  for (j = 0; j < s->desc->nb_components; j++) {
137  c = s->is_rgb ? s->rgba_map[j] : j;
138  set_meta(metadata, "lavfi.psnr.mse.", s->comps[j], comp_mse[c]);
139  set_meta(metadata, "lavfi.psnr.mse_avg", 0, mse);
140  set_meta(metadata, "lavfi.psnr.psnr.", s->comps[j], get_psnr(comp_mse[c], 1, s->max[c]));
141  set_meta(metadata, "lavfi.psnr.psnr_avg", 0, get_psnr(mse, 1, s->average_max));
142  }
143 
144  if (s->stats_file) {
145  fprintf(s->stats_file, "n:%"PRId64" mse_avg:%0.2f ", s->nb_frames, mse);
146  for (j = 0; j < s->desc->nb_components; j++) {
147  c = s->is_rgb ? s->rgba_map[j] : j;
148  fprintf(s->stats_file, "mse_%c:%0.2f ", s->comps[j], comp_mse[c]);
149  }
150  for (j = 0; j < s->desc->nb_components; j++) {
151  c = s->is_rgb ? s->rgba_map[j] : j;
152  fprintf(s->stats_file, "psnr_%c:%0.2f ", s->comps[j],
153  get_psnr(comp_mse[c], 1, s->max[c]));
154  }
155  fprintf(s->stats_file, "\n");
156  }
157 
158  return main;
159 }
160 
161 static av_cold int init(AVFilterContext *ctx)
162 {
163  PSNRContext *s = ctx->priv;
164 
165  s->min_mse = +INFINITY;
166  s->max_mse = -INFINITY;
167 
168  if (s->stats_file_str) {
169  s->stats_file = fopen(s->stats_file_str, "w");
170  if (!s->stats_file) {
171  int err = AVERROR(errno);
172  char buf[128];
173  av_strerror(err, buf, sizeof(buf));
174  av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s: %s\n",
175  s->stats_file_str, buf);
176  return err;
177  }
178  }
179 
180  s->dinput.process = do_psnr;
181  return 0;
182 }
183 
185 {
186  static const enum PixelFormat pix_fmts[] = {
195  };
196 
198  return 0;
199 }
200 
201 static int config_input_ref(AVFilterLink *inlink)
202 {
203  AVFilterContext *ctx = inlink->dst;
204  PSNRContext *s = ctx->priv;
205  int j;
206 
207  s->desc = av_pix_fmt_desc_get(inlink->format);
208  if (ctx->inputs[0]->w != ctx->inputs[1]->w ||
209  ctx->inputs[0]->h != ctx->inputs[1]->h) {
210  av_log(ctx, AV_LOG_ERROR, "Width and heigth of input videos must be same.\n");
211  return AVERROR(EINVAL);
212  }
213  if (ctx->inputs[0]->format != ctx->inputs[1]->format) {
214  av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n");
215  return AVERROR(EINVAL);
216  }
217 
218  switch (inlink->format) {
219  case AV_PIX_FMT_YUV410P:
220  case AV_PIX_FMT_YUV411P:
221  case AV_PIX_FMT_YUV420P:
222  case AV_PIX_FMT_YUV422P:
223  case AV_PIX_FMT_YUV440P:
224  case AV_PIX_FMT_YUV444P:
225  case AV_PIX_FMT_YUVA420P:
226  case AV_PIX_FMT_YUVA422P:
227  case AV_PIX_FMT_YUVA444P:
228  s->max[0] = 235;
229  s->max[3] = 255;
230  s->max[1] = s->max[2] = 240;
231  break;
232  default:
233  s->max[0] = s->max[1] = s->max[2] = s->max[3] = 255;
234  }
235 
236  s->is_rgb = ff_fill_rgba_map(s->rgba_map, inlink->format) >= 0;
237  s->comps[0] = s->is_rgb ? 'r' : 'y' ;
238  s->comps[1] = s->is_rgb ? 'g' : 'u' ;
239  s->comps[2] = s->is_rgb ? 'b' : 'v' ;
240  s->comps[3] = 'a';
241 
242  for (j = 0; j < s->desc->nb_components; j++)
243  s->average_max += s->max[j];
244  s->average_max /= s->desc->nb_components;
245 
246  return 0;
247 }
248 
249 static int config_output(AVFilterLink *outlink)
250 {
251  AVFilterContext *ctx = outlink->src;
252  AVFilterLink *mainlink = ctx->inputs[0];
253 
254  outlink->w = mainlink->w;
255  outlink->h = mainlink->h;
256  outlink->time_base = mainlink->time_base;
257  outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
258  outlink->frame_rate = mainlink->frame_rate;
259 
260  return 0;
261 }
262 
263 static int filter_frame_main(AVFilterLink *inlink, AVFrame *inpicref)
264 {
265  PSNRContext *s = inlink->dst->priv;
266  return ff_dualinput_filter_frame_main(&s->dinput, inlink, inpicref);
267 }
268 
269 static int filter_frame_ref(AVFilterLink *inlink, AVFrame *inpicref)
270 {
271  PSNRContext *s = inlink->dst->priv;
272  return ff_dualinput_filter_frame_second(&s->dinput, inlink, inpicref);
273 }
274 
275 static int request_frame(AVFilterLink *outlink)
276 {
277  PSNRContext *s = outlink->src->priv;
278  return ff_dualinput_request_frame(&s->dinput, outlink);
279 }
280 
281 static av_cold void uninit(AVFilterContext *ctx)
282 {
283  PSNRContext *s = ctx->priv;
284 
285  if (s->nb_frames > 0) {
286  av_log(ctx, AV_LOG_INFO, "PSNR average:%0.2f min:%0.2f max:%0.2f\n",
287  get_psnr(s->mse, s->nb_frames, s->average_max),
288  get_psnr(s->max_mse, 1, s->average_max),
289  get_psnr(s->min_mse, 1, s->average_max));
290  }
291 
293 
294  if (s->stats_file)
295  fclose(s->stats_file);
296 }
297 
298 static const AVFilterPad psnr_inputs[] = {
299  {
300  .name = "main",
301  .type = AVMEDIA_TYPE_VIDEO,
302  .filter_frame = filter_frame_main,
303  },{
304  .name = "reference",
305  .type = AVMEDIA_TYPE_VIDEO,
306  .filter_frame = filter_frame_ref,
307  .config_props = config_input_ref,
308  },
309  { NULL }
310 };
311 
312 static const AVFilterPad psnr_outputs[] = {
313  {
314  .name = "default",
315  .type = AVMEDIA_TYPE_VIDEO,
316  .config_props = config_output,
317  .request_frame = request_frame,
318  },
319  { NULL }
320 };
321 
323  .name = "psnr",
324  .description = NULL_IF_CONFIG_SMALL("Calculate the PSNR between two video streams."),
325  .init = init,
326  .uninit = uninit,
327  .query_formats = query_formats,
328  .priv_size = sizeof(PSNRContext),
329  .priv_class = &psnr_class,
330  .inputs = psnr_inputs,
331  .outputs = psnr_outputs,
332 };