Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Copyright (c) 2003-2013 Loren Merritt | ||
3 | * Copyright (c) 2015 Paul B Mahol | ||
4 | * | ||
5 | * This file is part of FFmpeg. | ||
6 | * | ||
7 | * FFmpeg is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU Lesser General Public | ||
9 | * License as published by the Free Software Foundation; either | ||
10 | * version 2.1 of the License, or (at your option) any later version. | ||
11 | * | ||
12 | * FFmpeg is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with FFmpeg; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
20 | */ | ||
21 | |||
22 | /* Computes the Structural Similarity Metric between two video streams. | ||
23 | * original algorithm: | ||
24 | * Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli, | ||
25 | * "Image quality assessment: From error visibility to structural similarity," | ||
26 | * IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004. | ||
27 | * | ||
28 | * To improve speed, this implementation uses the standard approximation of | ||
29 | * overlapped 8x8 block sums, rather than the original gaussian weights. | ||
30 | */ | ||
31 | |||
32 | /* | ||
33 | * @file | ||
34 | * Calculate the SSIM between two input videos. | ||
35 | */ | ||
36 | |||
37 | #include "libavutil/avstring.h" | ||
38 | #include "libavutil/file_open.h" | ||
39 | #include "libavutil/mem.h" | ||
40 | #include "libavutil/opt.h" | ||
41 | #include "libavutil/pixdesc.h" | ||
42 | #include "avfilter.h" | ||
43 | #include "drawutils.h" | ||
44 | #include "filters.h" | ||
45 | #include "framesync.h" | ||
46 | #include "ssim.h" | ||
47 | |||
48 | typedef struct SSIMContext { | ||
49 | const AVClass *class; | ||
50 | FFFrameSync fs; | ||
51 | FILE *stats_file; | ||
52 | char *stats_file_str; | ||
53 | int nb_components; | ||
54 | int nb_threads; | ||
55 | int max; | ||
56 | uint64_t nb_frames; | ||
57 | double ssim[4], ssim_total; | ||
58 | char comps[4]; | ||
59 | double coefs[4]; | ||
60 | uint8_t rgba_map[4]; | ||
61 | int planewidth[4]; | ||
62 | int planeheight[4]; | ||
63 | int **temp; | ||
64 | int is_rgb; | ||
65 | double **score; | ||
66 | int (*ssim_plane)(AVFilterContext *ctx, void *arg, | ||
67 | int jobnr, int nb_jobs); | ||
68 | SSIMDSPContext dsp; | ||
69 | } SSIMContext; | ||
70 | |||
71 | #define OFFSET(x) offsetof(SSIMContext, x) | ||
72 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | ||
73 | |||
74 | static const AVOption ssim_options[] = { | ||
75 | {"stats_file", "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, | ||
76 | {"f", "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS }, | ||
77 | { NULL } | ||
78 | }; | ||
79 | |||
80 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
8 | FRAMESYNC_DEFINE_CLASS(ssim, SSIMContext, fs); |
81 | |||
82 | 50 | static void set_meta(AVDictionary **metadata, const char *key, char comp, float d) | |
83 | { | ||
84 | char value[128]; | ||
85 | 50 | snprintf(value, sizeof(value), "%f", d); | |
86 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 20 times.
|
50 | if (comp) { |
87 | char key2[128]; | ||
88 | 30 | snprintf(key2, sizeof(key2), "%s%c", key, comp); | |
89 | 30 | av_dict_set(metadata, key2, value, 0); | |
90 | } else { | ||
91 | 20 | av_dict_set(metadata, key, value, 0); | |
92 | } | ||
93 | 50 | } | |
94 | |||
95 | ✗ | static void ssim_4x4xn_16bit(const uint8_t *main8, ptrdiff_t main_stride, | |
96 | const uint8_t *ref8, ptrdiff_t ref_stride, | ||
97 | int64_t (*sums)[4], int width) | ||
98 | { | ||
99 | ✗ | const uint16_t *main16 = (const uint16_t *)main8; | |
100 | ✗ | const uint16_t *ref16 = (const uint16_t *)ref8; | |
101 | int x, y, z; | ||
102 | |||
103 | ✗ | main_stride >>= 1; | |
104 | ✗ | ref_stride >>= 1; | |
105 | |||
106 | ✗ | for (z = 0; z < width; z++) { | |
107 | ✗ | uint64_t s1 = 0, s2 = 0, ss = 0, s12 = 0; | |
108 | |||
109 | ✗ | for (y = 0; y < 4; y++) { | |
110 | ✗ | for (x = 0; x < 4; x++) { | |
111 | ✗ | unsigned a = main16[x + y * main_stride]; | |
112 | ✗ | unsigned b = ref16[x + y * ref_stride]; | |
113 | |||
114 | ✗ | s1 += a; | |
115 | ✗ | s2 += b; | |
116 | ✗ | ss += a*a; | |
117 | ✗ | ss += b*b; | |
118 | ✗ | s12 += a*b; | |
119 | } | ||
120 | } | ||
121 | |||
122 | ✗ | sums[z][0] = s1; | |
123 | ✗ | sums[z][1] = s2; | |
124 | ✗ | sums[z][2] = ss; | |
125 | ✗ | sums[z][3] = s12; | |
126 | ✗ | main16 += 4; | |
127 | ✗ | ref16 += 4; | |
128 | } | ||
129 | ✗ | } | |
130 | |||
131 | 1740 | static void ssim_4x4xn_8bit(const uint8_t *main, ptrdiff_t main_stride, | |
132 | const uint8_t *ref, ptrdiff_t ref_stride, | ||
133 | int (*sums)[4], int width) | ||
134 | { | ||
135 | int x, y, z; | ||
136 | |||
137 |
2/2✓ Branch 0 taken 108460 times.
✓ Branch 1 taken 1740 times.
|
110200 | for (z = 0; z < width; z++) { |
138 | 108460 | uint32_t s1 = 0, s2 = 0, ss = 0, s12 = 0; | |
139 | |||
140 |
2/2✓ Branch 0 taken 433840 times.
✓ Branch 1 taken 108460 times.
|
542300 | for (y = 0; y < 4; y++) { |
141 |
2/2✓ Branch 0 taken 1735360 times.
✓ Branch 1 taken 433840 times.
|
2169200 | for (x = 0; x < 4; x++) { |
142 | 1735360 | int a = main[x + y * main_stride]; | |
143 | 1735360 | int b = ref[x + y * ref_stride]; | |
144 | |||
145 | 1735360 | s1 += a; | |
146 | 1735360 | s2 += b; | |
147 | 1735360 | ss += a*a; | |
148 | 1735360 | ss += b*b; | |
149 | 1735360 | s12 += a*b; | |
150 | } | ||
151 | } | ||
152 | |||
153 | 108460 | sums[z][0] = s1; | |
154 | 108460 | sums[z][1] = s2; | |
155 | 108460 | sums[z][2] = ss; | |
156 | 108460 | sums[z][3] = s12; | |
157 | 108460 | main += 4; | |
158 | 108460 | ref += 4; | |
159 | } | ||
160 | 1740 | } | |
161 | |||
162 | ✗ | static float ssim_end1x(int64_t s1, int64_t s2, int64_t ss, int64_t s12, int max) | |
163 | { | ||
164 | ✗ | int64_t ssim_c1 = (int64_t)(.01*.01*max*max*64 + .5); | |
165 | ✗ | int64_t ssim_c2 = (int64_t)(.03*.03*max*max*64*63 + .5); | |
166 | |||
167 | ✗ | int64_t fs1 = s1; | |
168 | ✗ | int64_t fs2 = s2; | |
169 | ✗ | int64_t fss = ss; | |
170 | ✗ | int64_t fs12 = s12; | |
171 | ✗ | int64_t vars = fss * 64 - fs1 * fs1 - fs2 * fs2; | |
172 | ✗ | int64_t covar = fs12 * 64 - fs1 * fs2; | |
173 | |||
174 | ✗ | return (float)(2 * fs1 * fs2 + ssim_c1) * (float)(2 * covar + ssim_c2) | |
175 | ✗ | / ((float)(fs1 * fs1 + fs2 * fs2 + ssim_c1) * (float)(vars + ssim_c2)); | |
176 | } | ||
177 | |||
178 | 90160 | static float ssim_end1(int s1, int s2, int ss, int s12) | |
179 | { | ||
180 | static const int ssim_c1 = (int)(.01*.01*255*255*64 + .5); | ||
181 | static const int ssim_c2 = (int)(.03*.03*255*255*64*63 + .5); | ||
182 | |||
183 | 90160 | int fs1 = s1; | |
184 | 90160 | int fs2 = s2; | |
185 | 90160 | int fss = ss; | |
186 | 90160 | int fs12 = s12; | |
187 | 90160 | int vars = fss * 64 - fs1 * fs1 - fs2 * fs2; | |
188 | 90160 | int covar = fs12 * 64 - fs1 * fs2; | |
189 | |||
190 | 90160 | return (float)(2 * fs1 * fs2 + ssim_c1) * (float)(2 * covar + ssim_c2) | |
191 | 90160 | / ((float)(fs1 * fs1 + fs2 * fs2 + ssim_c1) * (float)(vars + ssim_c2)); | |
192 | } | ||
193 | |||
194 | ✗ | static float ssim_endn_16bit(const int64_t (*sum0)[4], const int64_t (*sum1)[4], int width, int max) | |
195 | { | ||
196 | ✗ | float ssim = 0.0; | |
197 | |||
198 | ✗ | for (int i = 0; i < width; i++) | |
199 | ✗ | ssim += ssim_end1x(sum0[i][0] + sum0[i + 1][0] + sum1[i][0] + sum1[i + 1][0], | |
200 | ✗ | sum0[i][1] + sum0[i + 1][1] + sum1[i][1] + sum1[i + 1][1], | |
201 | ✗ | sum0[i][2] + sum0[i + 1][2] + sum1[i][2] + sum1[i + 1][2], | |
202 | ✗ | sum0[i][3] + sum0[i + 1][3] + sum1[i][3] + sum1[i + 1][3], | |
203 | max); | ||
204 | ✗ | return ssim; | |
205 | } | ||
206 | |||
207 | 1470 | static double ssim_endn_8bit(const int (*sum0)[4], const int (*sum1)[4], int width) | |
208 | { | ||
209 | 1470 | double ssim = 0.0; | |
210 | |||
211 |
2/2✓ Branch 0 taken 90160 times.
✓ Branch 1 taken 1470 times.
|
91630 | for (int i = 0; i < width; i++) |
212 | 90160 | ssim += ssim_end1(sum0[i][0] + sum0[i + 1][0] + sum1[i][0] + sum1[i + 1][0], | |
213 | 90160 | sum0[i][1] + sum0[i + 1][1] + sum1[i][1] + sum1[i + 1][1], | |
214 | 90160 | sum0[i][2] + sum0[i + 1][2] + sum1[i][2] + sum1[i + 1][2], | |
215 | 90160 | sum0[i][3] + sum0[i + 1][3] + sum1[i][3] + sum1[i + 1][3]); | |
216 | 1470 | return ssim; | |
217 | } | ||
218 | |||
219 | #define SUM_LEN(w) (((w) >> 2) + 3) | ||
220 | |||
221 | typedef struct ThreadData { | ||
222 | const uint8_t *main_data[4]; | ||
223 | const uint8_t *ref_data[4]; | ||
224 | int main_linesize[4]; | ||
225 | int ref_linesize[4]; | ||
226 | int planewidth[4]; | ||
227 | int planeheight[4]; | ||
228 | double **score; | ||
229 | int **temp; | ||
230 | int nb_components; | ||
231 | int max; | ||
232 | SSIMDSPContext *dsp; | ||
233 | } ThreadData; | ||
234 | |||
235 | ✗ | static int ssim_plane_16bit(AVFilterContext *ctx, void *arg, | |
236 | int jobnr, int nb_jobs) | ||
237 | { | ||
238 | ✗ | ThreadData *td = arg; | |
239 | ✗ | double *score = td->score[jobnr]; | |
240 | ✗ | void *temp = td->temp[jobnr]; | |
241 | ✗ | const int max = td->max; | |
242 | |||
243 | ✗ | for (int c = 0; c < td->nb_components; c++) { | |
244 | ✗ | const uint8_t *main_data = td->main_data[c]; | |
245 | ✗ | const uint8_t *ref_data = td->ref_data[c]; | |
246 | ✗ | const int main_stride = td->main_linesize[c]; | |
247 | ✗ | const int ref_stride = td->ref_linesize[c]; | |
248 | ✗ | int width = td->planewidth[c]; | |
249 | ✗ | int height = td->planeheight[c]; | |
250 | ✗ | const int slice_start = ((height >> 2) * jobnr) / nb_jobs; | |
251 | ✗ | const int slice_end = ((height >> 2) * (jobnr+1)) / nb_jobs; | |
252 | ✗ | const int ystart = FFMAX(1, slice_start); | |
253 | ✗ | int z = ystart - 1; | |
254 | ✗ | double ssim = 0.0; | |
255 | ✗ | int64_t (*sum0)[4] = temp; | |
256 | ✗ | int64_t (*sum1)[4] = sum0 + SUM_LEN(width); | |
257 | |||
258 | ✗ | width >>= 2; | |
259 | ✗ | height >>= 2; | |
260 | |||
261 | ✗ | for (int y = ystart; y < slice_end; y++) { | |
262 | ✗ | for (; z <= y; z++) { | |
263 | ✗ | FFSWAP(void*, sum0, sum1); | |
264 | ✗ | ssim_4x4xn_16bit(&main_data[4 * z * main_stride], main_stride, | |
265 | ✗ | &ref_data[4 * z * ref_stride], ref_stride, | |
266 | sum0, width); | ||
267 | } | ||
268 | |||
269 | ✗ | ssim += ssim_endn_16bit((const int64_t (*)[4])sum0, (const int64_t (*)[4])sum1, width - 1, max); | |
270 | } | ||
271 | |||
272 | ✗ | score[c] = ssim; | |
273 | } | ||
274 | |||
275 | ✗ | return 0; | |
276 | } | ||
277 | |||
278 | 90 | static int ssim_plane(AVFilterContext *ctx, void *arg, | |
279 | int jobnr, int nb_jobs) | ||
280 | { | ||
281 | 90 | ThreadData *td = arg; | |
282 | 90 | double *score = td->score[jobnr]; | |
283 | 90 | void *temp = td->temp[jobnr]; | |
284 | 90 | SSIMDSPContext *dsp = td->dsp; | |
285 | |||
286 |
2/2✓ Branch 0 taken 270 times.
✓ Branch 1 taken 90 times.
|
360 | for (int c = 0; c < td->nb_components; c++) { |
287 | 270 | const uint8_t *main_data = td->main_data[c]; | |
288 | 270 | const uint8_t *ref_data = td->ref_data[c]; | |
289 | 270 | const int main_stride = td->main_linesize[c]; | |
290 | 270 | const int ref_stride = td->ref_linesize[c]; | |
291 | 270 | int width = td->planewidth[c]; | |
292 | 270 | int height = td->planeheight[c]; | |
293 | 270 | const int slice_start = ((height >> 2) * jobnr) / nb_jobs; | |
294 | 270 | const int slice_end = ((height >> 2) * (jobnr+1)) / nb_jobs; | |
295 | 270 | const int ystart = FFMAX(1, slice_start); | |
296 | 270 | int z = ystart - 1; | |
297 | 270 | double ssim = 0.0; | |
298 | 270 | int (*sum0)[4] = temp; | |
299 | 270 | int (*sum1)[4] = sum0 + SUM_LEN(width); | |
300 | |||
301 | 270 | width >>= 2; | |
302 | 270 | height >>= 2; | |
303 | |||
304 |
2/2✓ Branch 0 taken 1470 times.
✓ Branch 1 taken 270 times.
|
1740 | for (int y = ystart; y < slice_end; y++) { |
305 |
2/2✓ Branch 0 taken 1740 times.
✓ Branch 1 taken 1470 times.
|
3210 | for (; z <= y; z++) { |
306 | 1740 | FFSWAP(void*, sum0, sum1); | |
307 | 1740 | dsp->ssim_4x4_line(&main_data[4 * z * main_stride], main_stride, | |
308 | 1740 | &ref_data[4 * z * ref_stride], ref_stride, | |
309 | sum0, width); | ||
310 | } | ||
311 | |||
312 | 1470 | ssim += dsp->ssim_end_line((const int (*)[4])sum0, (const int (*)[4])sum1, width - 1); | |
313 | } | ||
314 | |||
315 | 270 | score[c] = ssim; | |
316 | } | ||
317 | |||
318 | 90 | return 0; | |
319 | } | ||
320 | |||
321 | 18 | static double ssim_db(double ssim, double weight) | |
322 | { | ||
323 |
1/2✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
|
18 | return (fabs(weight - ssim) > 1e-9) ? 10.0 * log10(weight / (weight - ssim)) : INFINITY; |
324 | } | ||
325 | |||
326 | 10 | static int do_ssim(FFFrameSync *fs) | |
327 | { | ||
328 | 10 | AVFilterContext *ctx = fs->parent; | |
329 | 10 | SSIMContext *s = ctx->priv; | |
330 | AVFrame *master, *ref; | ||
331 | AVDictionary **metadata; | ||
332 | 10 | double c[4] = {0}, ssimv = 0.0; | |
333 | ThreadData td; | ||
334 | int ret, i; | ||
335 | |||
336 | 10 | ret = ff_framesync_dualinput_get(fs, &master, &ref); | |
337 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (ret < 0) |
338 | ✗ | return ret; | |
339 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
|
10 | if (ctx->is_disabled || !ref) |
340 | ✗ | return ff_filter_frame(ctx->outputs[0], master); | |
341 | 10 | metadata = &master->metadata; | |
342 | |||
343 | 10 | s->nb_frames++; | |
344 | |||
345 | 10 | td.nb_components = s->nb_components; | |
346 | 10 | td.dsp = &s->dsp; | |
347 | 10 | td.score = s->score; | |
348 | 10 | td.temp = s->temp; | |
349 | 10 | td.max = s->max; | |
350 | |||
351 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 10 times.
|
40 | for (int n = 0; n < s->nb_components; n++) { |
352 | 30 | td.main_data[n] = master->data[n]; | |
353 | 30 | td.ref_data[n] = ref->data[n]; | |
354 | 30 | td.main_linesize[n] = master->linesize[n]; | |
355 | 30 | td.ref_linesize[n] = ref->linesize[n]; | |
356 | 30 | td.planewidth[n] = s->planewidth[n]; | |
357 | 30 | td.planeheight[n] = s->planeheight[n]; | |
358 | } | ||
359 | |||
360 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (master->color_range != ref->color_range) { |
361 | ✗ | av_log(ctx, AV_LOG_WARNING, "master and reference " | |
362 | "frames use different color ranges (%s != %s)\n", | ||
363 | ✗ | av_color_range_name(master->color_range), | |
364 | ✗ | av_color_range_name(ref->color_range)); | |
365 | } | ||
366 | |||
367 | 10 | ff_filter_execute(ctx, s->ssim_plane, &td, NULL, | |
368 | 10 | FFMIN((s->planeheight[1] + 3) >> 2, s->nb_threads)); | |
369 | |||
370 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 10 times.
|
40 | for (i = 0; i < s->nb_components; i++) { |
371 |
2/2✓ Branch 0 taken 270 times.
✓ Branch 1 taken 30 times.
|
300 | for (int j = 0; j < s->nb_threads; j++) |
372 | 270 | c[i] += s->score[j][i]; | |
373 | 30 | c[i] = c[i] / (((s->planewidth[i] >> 2) - 1) * ((s->planeheight[i] >> 2) - 1)); | |
374 | } | ||
375 | |||
376 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 10 times.
|
40 | for (i = 0; i < s->nb_components; i++) { |
377 | 30 | ssimv += s->coefs[i] * c[i]; | |
378 | 30 | s->ssim[i] += c[i]; | |
379 | } | ||
380 | |||
381 |
2/2✓ Branch 0 taken 30 times.
✓ Branch 1 taken 10 times.
|
40 | for (i = 0; i < s->nb_components; i++) { |
382 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 15 times.
|
30 | int cidx = s->is_rgb ? s->rgba_map[i] : i; |
383 | 30 | set_meta(metadata, "lavfi.ssim.", s->comps[i], c[cidx]); | |
384 | } | ||
385 | 10 | s->ssim_total += ssimv; | |
386 | |||
387 | 10 | set_meta(metadata, "lavfi.ssim.All", 0, ssimv); | |
388 | 10 | set_meta(metadata, "lavfi.ssim.dB", 0, ssim_db(ssimv, 1.0)); | |
389 | |||
390 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (s->stats_file) { |
391 | ✗ | fprintf(s->stats_file, "n:%"PRId64" ", s->nb_frames); | |
392 | |||
393 | ✗ | for (i = 0; i < s->nb_components; i++) { | |
394 | ✗ | int cidx = s->is_rgb ? s->rgba_map[i] : i; | |
395 | ✗ | fprintf(s->stats_file, "%c:%f ", s->comps[i], c[cidx]); | |
396 | } | ||
397 | |||
398 | ✗ | fprintf(s->stats_file, "All:%f (%f)\n", ssimv, ssim_db(ssimv, 1.0)); | |
399 | } | ||
400 | |||
401 | 10 | return ff_filter_frame(ctx->outputs[0], master); | |
402 | } | ||
403 | |||
404 | 4 | static av_cold int init(AVFilterContext *ctx) | |
405 | { | ||
406 | 4 | SSIMContext *s = ctx->priv; | |
407 | |||
408 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (s->stats_file_str) { |
409 | ✗ | if (!strcmp(s->stats_file_str, "-")) { | |
410 | ✗ | s->stats_file = stdout; | |
411 | } else { | ||
412 | ✗ | s->stats_file = avpriv_fopen_utf8(s->stats_file_str, "w"); | |
413 | ✗ | if (!s->stats_file) { | |
414 | ✗ | int err = AVERROR(errno); | |
415 | ✗ | av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s: %s\n", | |
416 | ✗ | s->stats_file_str, av_err2str(err)); | |
417 | ✗ | return err; | |
418 | } | ||
419 | } | ||
420 | } | ||
421 | |||
422 | 4 | s->fs.on_event = do_ssim; | |
423 | 4 | return 0; | |
424 | } | ||
425 | |||
426 | static const enum AVPixelFormat pix_fmts[] = { | ||
427 | AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, AV_PIX_FMT_GRAY10, | ||
428 | AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16, | ||
429 | AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV444P, | ||
430 | AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, | ||
431 | AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, | ||
432 | AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P, | ||
433 | AV_PIX_FMT_GBRP, | ||
434 | #define PF(suf) AV_PIX_FMT_YUV420##suf, AV_PIX_FMT_YUV422##suf, AV_PIX_FMT_YUV444##suf, AV_PIX_FMT_GBR##suf | ||
435 | PF(P9), PF(P10), PF(P12), PF(P14), PF(P16), | ||
436 | AV_PIX_FMT_NONE | ||
437 | }; | ||
438 | |||
439 | 2 | static int config_input_ref(AVFilterLink *inlink) | |
440 | { | ||
441 | 2 | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
442 | 2 | AVFilterContext *ctx = inlink->dst; | |
443 | 2 | SSIMContext *s = ctx->priv; | |
444 | 2 | int sum = 0; | |
445 | |||
446 | 2 | s->nb_threads = ff_filter_get_nb_threads(ctx); | |
447 | 2 | s->nb_components = desc->nb_components; | |
448 | |||
449 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (ctx->inputs[0]->w != ctx->inputs[1]->w || |
450 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | ctx->inputs[0]->h != ctx->inputs[1]->h) { |
451 | ✗ | av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n"); | |
452 | ✗ | return AVERROR(EINVAL); | |
453 | } | ||
454 | |||
455 | 2 | s->is_rgb = ff_fill_rgba_map(s->rgba_map, inlink->format) >= 0; | |
456 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | s->comps[0] = s->is_rgb ? 'R' : 'Y'; |
457 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | s->comps[1] = s->is_rgb ? 'G' : 'U'; |
458 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | s->comps[2] = s->is_rgb ? 'B' : 'V'; |
459 | 2 | s->comps[3] = 'A'; | |
460 | |||
461 | 2 | s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); | |
462 | 2 | s->planeheight[0] = s->planeheight[3] = inlink->h; | |
463 | 2 | s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); | |
464 | 2 | s->planewidth[0] = s->planewidth[3] = inlink->w; | |
465 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | for (int i = 0; i < s->nb_components; i++) |
466 | 6 | sum += s->planeheight[i] * s->planewidth[i]; | |
467 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | for (int i = 0; i < s->nb_components; i++) |
468 | 6 | s->coefs[i] = (double) s->planeheight[i] * s->planewidth[i] / sum; | |
469 | |||
470 | 2 | s->temp = av_calloc(s->nb_threads, sizeof(*s->temp)); | |
471 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!s->temp) |
472 | ✗ | return AVERROR(ENOMEM); | |
473 | |||
474 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2 times.
|
20 | for (int t = 0; t < s->nb_threads; t++) { |
475 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
18 | s->temp[t] = av_calloc(2 * SUM_LEN(inlink->w), (desc->comp[0].depth > 8) ? sizeof(int64_t[4]) : sizeof(int[4])); |
476 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
18 | if (!s->temp[t]) |
477 | ✗ | return AVERROR(ENOMEM); | |
478 | } | ||
479 | 2 | s->max = (1 << desc->comp[0].depth) - 1; | |
480 | |||
481 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | s->ssim_plane = desc->comp[0].depth > 8 ? ssim_plane_16bit : ssim_plane; |
482 | 2 | s->dsp.ssim_4x4_line = ssim_4x4xn_8bit; | |
483 | 2 | s->dsp.ssim_end_line = ssim_endn_8bit; | |
484 | #if ARCH_X86 | ||
485 | 2 | ff_ssim_init_x86(&s->dsp); | |
486 | #endif | ||
487 | |||
488 | 2 | s->score = av_calloc(s->nb_threads, sizeof(*s->score)); | |
489 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!s->score) |
490 | ✗ | return AVERROR(ENOMEM); | |
491 | |||
492 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2 times.
|
20 | for (int t = 0; t < s->nb_threads; t++) { |
493 | 18 | s->score[t] = av_calloc(s->nb_components, sizeof(*s->score[0])); | |
494 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
18 | if (!s->score[t]) |
495 | ✗ | return AVERROR(ENOMEM); | |
496 | } | ||
497 | |||
498 | 2 | return 0; | |
499 | } | ||
500 | |||
501 | 2 | static int config_output(AVFilterLink *outlink) | |
502 | { | ||
503 | 2 | AVFilterContext *ctx = outlink->src; | |
504 | 2 | SSIMContext *s = ctx->priv; | |
505 | 2 | AVFilterLink *mainlink = ctx->inputs[0]; | |
506 | 2 | FilterLink *il = ff_filter_link(mainlink); | |
507 | 2 | FilterLink *ol = ff_filter_link(outlink); | |
508 | int ret; | ||
509 | |||
510 | 2 | ret = ff_framesync_init_dualinput(&s->fs, ctx); | |
511 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (ret < 0) |
512 | ✗ | return ret; | |
513 | 2 | outlink->w = mainlink->w; | |
514 | 2 | outlink->h = mainlink->h; | |
515 | 2 | outlink->time_base = mainlink->time_base; | |
516 | 2 | outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio; | |
517 | 2 | ol->frame_rate = il->frame_rate; | |
518 | |||
519 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
|
2 | if ((ret = ff_framesync_configure(&s->fs)) < 0) |
520 | ✗ | return ret; | |
521 | |||
522 | 2 | outlink->time_base = s->fs.time_base; | |
523 | |||
524 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
|
4 | if (av_cmp_q(mainlink->time_base, outlink->time_base) || |
525 | 2 | av_cmp_q(ctx->inputs[1]->time_base, outlink->time_base)) | |
526 | ✗ | av_log(ctx, AV_LOG_WARNING, "not matching timebases found between first input: %d/%d and second input %d/%d, results may be incorrect!\n", | |
527 | mainlink->time_base.num, mainlink->time_base.den, | ||
528 | ✗ | ctx->inputs[1]->time_base.num, ctx->inputs[1]->time_base.den); | |
529 | |||
530 | 2 | return 0; | |
531 | } | ||
532 | |||
533 | 30 | static int activate(AVFilterContext *ctx) | |
534 | { | ||
535 | 30 | SSIMContext *s = ctx->priv; | |
536 | 30 | return ff_framesync_activate(&s->fs); | |
537 | } | ||
538 | |||
539 | 4 | static av_cold void uninit(AVFilterContext *ctx) | |
540 | { | ||
541 | 4 | SSIMContext *s = ctx->priv; | |
542 | |||
543 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (s->nb_frames > 0) { |
544 | char buf[256]; | ||
545 | 2 | buf[0] = 0; | |
546 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
|
8 | for (int i = 0; i < s->nb_components; i++) { |
547 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | int c = s->is_rgb ? s->rgba_map[i] : i; |
548 | 6 | av_strlcatf(buf, sizeof(buf), " %c:%f (%f)", s->comps[i], s->ssim[c] / s->nb_frames, | |
549 | 6 | ssim_db(s->ssim[c], s->nb_frames)); | |
550 | } | ||
551 | 4 | av_log(ctx, AV_LOG_INFO, "SSIM%s All:%f (%f)\n", buf, | |
552 | 2 | s->ssim_total / s->nb_frames, ssim_db(s->ssim_total, s->nb_frames)); | |
553 | } | ||
554 | |||
555 | 4 | ff_framesync_uninit(&s->fs); | |
556 | |||
557 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | if (s->stats_file && s->stats_file != stdout) |
558 | ✗ | fclose(s->stats_file); | |
559 | |||
560 |
3/4✓ Branch 0 taken 18 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
22 | for (int t = 0; t < s->nb_threads && s->score; t++) |
561 | 18 | av_freep(&s->score[t]); | |
562 | 4 | av_freep(&s->score); | |
563 | |||
564 |
3/4✓ Branch 0 taken 18 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
|
22 | for (int t = 0; t < s->nb_threads && s->temp; t++) |
565 | 18 | av_freep(&s->temp[t]); | |
566 | 4 | av_freep(&s->temp); | |
567 | 4 | } | |
568 | |||
569 | static const AVFilterPad ssim_inputs[] = { | ||
570 | { | ||
571 | .name = "main", | ||
572 | .type = AVMEDIA_TYPE_VIDEO, | ||
573 | },{ | ||
574 | .name = "reference", | ||
575 | .type = AVMEDIA_TYPE_VIDEO, | ||
576 | .config_props = config_input_ref, | ||
577 | }, | ||
578 | }; | ||
579 | |||
580 | static const AVFilterPad ssim_outputs[] = { | ||
581 | { | ||
582 | .name = "default", | ||
583 | .type = AVMEDIA_TYPE_VIDEO, | ||
584 | .config_props = config_output, | ||
585 | }, | ||
586 | }; | ||
587 | |||
588 | const AVFilter ff_vf_ssim = { | ||
589 | .name = "ssim", | ||
590 | .description = NULL_IF_CONFIG_SMALL("Calculate the SSIM between two video streams."), | ||
591 | .preinit = ssim_framesync_preinit, | ||
592 | .init = init, | ||
593 | .uninit = uninit, | ||
594 | .activate = activate, | ||
595 | .priv_size = sizeof(SSIMContext), | ||
596 | .priv_class = &ssim_class, | ||
597 | FILTER_INPUTS(ssim_inputs), | ||
598 | FILTER_OUTPUTS(ssim_outputs), | ||
599 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
600 | .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | | ||
601 | AVFILTER_FLAG_SLICE_THREADS | | ||
602 | AVFILTER_FLAG_METADATA_ONLY, | ||
603 | }; | ||
604 |