Removed
Link Here
|
1 |
https://git.ffmpeg.org/gitweb/ffmpeg.git/commitdiff/3d29724c008d |
2 |
|
3 |
--- configure.orig 2021-10-24 20:47:11 UTC |
4 |
+++ configure |
5 |
@@ -3663,7 +3663,7 @@ vidstabtransform_filter_deps="libvidstab" |
6 |
vaguedenoiser_filter_deps="gpl" |
7 |
vidstabdetect_filter_deps="libvidstab" |
8 |
vidstabtransform_filter_deps="libvidstab" |
9 |
-libvmaf_filter_deps="libvmaf pthreads" |
10 |
+libvmaf_filter_deps="libvmaf" |
11 |
zmq_filter_deps="libzmq" |
12 |
zoompan_filter_deps="swscale" |
13 |
zscale_filter_deps="libzimg const_nan" |
14 |
@@ -6441,7 +6441,7 @@ enabled libvidstab && require_pkg_config libvid |
15 |
enabled libuavs3d && require_pkg_config libuavs3d "uavs3d >= 1.1.41" uavs3d.h uavs3d_decode |
16 |
enabled libv4l2 && require_pkg_config libv4l2 libv4l2 libv4l2.h v4l2_ioctl |
17 |
enabled libvidstab && require_pkg_config libvidstab "vidstab >= 0.98" vid.stab/libvidstab.h vsMotionDetectInit |
18 |
-enabled libvmaf && require_pkg_config libvmaf "libvmaf >= 1.5.2" libvmaf.h compute_vmaf |
19 |
+enabled libvmaf && require_pkg_config libvmaf "libvmaf >= 2.0.0" libvmaf.h vmaf_init |
20 |
enabled libvo_amrwbenc && require libvo_amrwbenc vo-amrwbenc/enc_if.h E_IF_init -lvo-amrwbenc |
21 |
enabled libvorbis && require_pkg_config libvorbis vorbis vorbis/codec.h vorbis_info_init && |
22 |
require_pkg_config libvorbisenc vorbisenc vorbis/vorbisenc.h vorbis_encode_init |
23 |
--- doc/filters.texi.orig 2021-10-24 20:47:07 UTC |
24 |
+++ doc/filters.texi |
25 |
@@ -13867,66 +13867,58 @@ ffmpeg -i input.mov -vf lensfun=make=Canon:model="Cano |
26 |
|
27 |
@section libvmaf |
28 |
|
29 |
-Obtain the VMAF (Video Multi-Method Assessment Fusion) |
30 |
-score between two input videos. |
31 |
+Calulate the VMAF (Video Multi-Method Assessment Fusion) score for a |
32 |
+reference/distorted pair of input videos. |
33 |
|
34 |
The obtained VMAF score is printed through the logging system. |
35 |
|
36 |
It requires Netflix's vmaf library (libvmaf) as a pre-requisite. |
37 |
After installing the library it can be enabled using: |
38 |
@code{./configure --enable-libvmaf}. |
39 |
-If no model path is specified it uses the default model: @code{vmaf_v0.6.1.pkl}. |
40 |
|
41 |
The filter has following options: |
42 |
|
43 |
@table @option |
44 |
+@item model |
45 |
+A `|` delimited list of vmaf models. Each model can be configured with a number of parameters. |
46 |
+Default value: @code{"version=vmaf_v0.6.1"} |
47 |
+ |
48 |
@item model_path |
49 |
-Set the model path which is to be used for SVM. |
50 |
-Default value: @code{"/usr/local/share/model/vmaf_v0.6.1.pkl"} |
51 |
+Deprecated, use model='path=...'. |
52 |
|
53 |
-@item log_path |
54 |
-Set the file path to be used to store logs. |
55 |
- |
56 |
-@item log_fmt |
57 |
-Set the format of the log file (csv, json or xml). |
58 |
- |
59 |
@item enable_transform |
60 |
-This option can enable/disable the @code{score_transform} applied to the final predicted VMAF score, |
61 |
-if you have specified score_transform option in the input parameter file passed to @code{run_vmaf_training.py} |
62 |
-Default value: @code{false} |
63 |
+Deprecated, use model='enable_transform=true'. |
64 |
|
65 |
@item phone_model |
66 |
-Invokes the phone model which will generate VMAF scores higher than in the |
67 |
-regular model, which is more suitable for laptop, TV, etc. viewing conditions. |
68 |
-Default value: @code{false} |
69 |
+Deprecated, use model='enable_transform=true'. |
70 |
|
71 |
+@item enable_conf_interval |
72 |
+Deprecated, use model='enable_conf_interval=true'. |
73 |
+ |
74 |
+@item feature |
75 |
+A `|` delimited list of features. Each feature can be configured with a number of parameters. |
76 |
+ |
77 |
@item psnr |
78 |
-Enables computing psnr along with vmaf. |
79 |
-Default value: @code{false} |
80 |
+Deprecated, use feature='name=psnr'. |
81 |
|
82 |
@item ssim |
83 |
-Enables computing ssim along with vmaf. |
84 |
-Default value: @code{false} |
85 |
+Deprecated, use feature='name=ssim'. |
86 |
|
87 |
@item ms_ssim |
88 |
-Enables computing ms_ssim along with vmaf. |
89 |
-Default value: @code{false} |
90 |
+Deprecated, use feature='name=ms_ssim'. |
91 |
|
92 |
-@item pool |
93 |
-Set the pool method to be used for computing vmaf. |
94 |
-Options are @code{min}, @code{harmonic_mean} or @code{mean} (default). |
95 |
+@item log_path |
96 |
+Set the file path to be used to store log files. |
97 |
|
98 |
+@item log_fmt |
99 |
+Set the format of the log file (xml, json, csv, or sub). |
100 |
+ |
101 |
@item n_threads |
102 |
-Set number of threads to be used when computing vmaf. |
103 |
-Default value: @code{0}, which makes use of all available logical processors. |
104 |
+Set number of threads to be used when initializing libvmaf. |
105 |
+Default value: @code{0}, no threads. |
106 |
|
107 |
@item n_subsample |
108 |
-Set interval for frame subsampling used when computing vmaf. |
109 |
-Default value: @code{1} |
110 |
- |
111 |
-@item enable_conf_interval |
112 |
-Enables confidence interval. |
113 |
-Default value: @code{false} |
114 |
+Set frame subsampling interval to be used. |
115 |
@end table |
116 |
|
117 |
This filter also supports the @ref{framesync} options. |
118 |
@@ -13934,23 +13926,31 @@ This filter also supports the @ref{framesync} options. |
119 |
@subsection Examples |
120 |
@itemize |
121 |
@item |
122 |
-On the below examples the input file @file{main.mpg} being processed is |
123 |
-compared with the reference file @file{ref.mpg}. |
124 |
+In the examples below, a distorted video @file{distorted.mpg} is |
125 |
+compared with a reference file @file{reference.mpg}. |
126 |
|
127 |
+@item |
128 |
+Basic usage: |
129 |
@example |
130 |
-ffmpeg -i main.mpg -i ref.mpg -lavfi libvmaf -f null - |
131 |
+ffmpeg -i distorted.mpg -i reference.mpg -lavfi libvmaf=log_path=output.xml -f null - |
132 |
@end example |
133 |
|
134 |
@item |
135 |
-Example with options: |
136 |
+Example with multiple models: |
137 |
@example |
138 |
-ffmpeg -i main.mpg -i ref.mpg -lavfi libvmaf="psnr=1:log_fmt=json" -f null - |
139 |
+ffmpeg -i distorted.mpg -i reference.mpg -lavfi libvmaf='model=version=vmaf_v0.6.1\\:name=vmaf|version=vmaf_v0.6.1neg\\:name=vmaf_neg' -f null - |
140 |
@end example |
141 |
|
142 |
@item |
143 |
+Example with multiple addtional features: |
144 |
+@example |
145 |
+ffmpeg -i distorted.mpg -i reference.mpg -lavfi libvmaf='feature=name=psnr|name=ciede' -f null - |
146 |
+@end example |
147 |
+ |
148 |
+@item |
149 |
Example with options and different containers: |
150 |
@example |
151 |
-ffmpeg -i main.mpg -i ref.mkv -lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[main];[1:v]settb=AVTB,setpts=PTS-STARTPTS[ref];[main][ref]libvmaf=psnr=1:log_fmt=json" -f null - |
152 |
+ffmpeg -i distorted.mpg -i reference.mkv -lavfi "[0:v]settb=AVTB,setpts=PTS-STARTPTS[main];[1:v]settb=AVTB,setpts=PTS-STARTPTS[ref];[main][ref]libvmaf=log_fmt=json:log_path=output.json" -f null - |
153 |
@end example |
154 |
@end itemize |
155 |
|
156 |
--- libavfilter/vf_libvmaf.c.orig 2021-10-24 20:47:07 UTC |
157 |
+++ libavfilter/vf_libvmaf.c |
158 |
@@ -24,8 +24,8 @@ |
159 |
* Calculate the VMAF between two input videos. |
160 |
*/ |
161 |
|
162 |
-#include <pthread.h> |
163 |
#include <libvmaf.h> |
164 |
+ |
165 |
#include "libavutil/avstring.h" |
166 |
#include "libavutil/opt.h" |
167 |
#include "libavutil/pixdesc.h" |
168 |
@@ -39,23 +39,9 @@ typedef struct LIBVMAFContext { |
169 |
typedef struct LIBVMAFContext { |
170 |
const AVClass *class; |
171 |
FFFrameSync fs; |
172 |
- const AVPixFmtDescriptor *desc; |
173 |
- int width; |
174 |
- int height; |
175 |
- double vmaf_score; |
176 |
- int vmaf_thread_created; |
177 |
- pthread_t vmaf_thread; |
178 |
- pthread_mutex_t lock; |
179 |
- pthread_cond_t cond; |
180 |
- int eof; |
181 |
- AVFrame *gmain; |
182 |
- AVFrame *gref; |
183 |
- int frame_set; |
184 |
char *model_path; |
185 |
char *log_path; |
186 |
char *log_fmt; |
187 |
- int disable_clip; |
188 |
- int disable_avx; |
189 |
int enable_transform; |
190 |
int phone_model; |
191 |
int psnr; |
192 |
@@ -65,185 +51,488 @@ typedef struct LIBVMAFContext { |
193 |
int n_threads; |
194 |
int n_subsample; |
195 |
int enable_conf_interval; |
196 |
- int error; |
197 |
+ char *model_cfg; |
198 |
+ char *feature_cfg; |
199 |
+ VmafContext *vmaf; |
200 |
+ VmafModel **model; |
201 |
+ unsigned model_cnt; |
202 |
+ unsigned frame_cnt; |
203 |
+ unsigned bpc; |
204 |
} LIBVMAFContext; |
205 |
|
206 |
#define OFFSET(x) offsetof(LIBVMAFContext, x) |
207 |
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM |
208 |
|
209 |
static const AVOption libvmaf_options[] = { |
210 |
- {"model_path", "Set the model to be used for computing vmaf.", OFFSET(model_path), AV_OPT_TYPE_STRING, {.str="/usr/local/share/model/vmaf_v0.6.1.pkl"}, 0, 1, FLAGS}, |
211 |
- {"log_path", "Set the file path to be used to store logs.", OFFSET(log_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS}, |
212 |
- {"log_fmt", "Set the format of the log (csv, json or xml).", OFFSET(log_fmt), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS}, |
213 |
- {"enable_transform", "Enables transform for computing vmaf.", OFFSET(enable_transform), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS}, |
214 |
- {"phone_model", "Invokes the phone model that will generate higher VMAF scores.", OFFSET(phone_model), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS}, |
215 |
- {"psnr", "Enables computing psnr along with vmaf.", OFFSET(psnr), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS}, |
216 |
- {"ssim", "Enables computing ssim along with vmaf.", OFFSET(ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS}, |
217 |
- {"ms_ssim", "Enables computing ms-ssim along with vmaf.", OFFSET(ms_ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS}, |
218 |
+ {"model_path", "use model='path=...'.", OFFSET(model_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS|AV_OPT_FLAG_DEPRECATED}, |
219 |
+ {"log_path", "Set the file path to be used to write log.", OFFSET(log_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS}, |
220 |
+ {"log_fmt", "Set the format of the log (csv, json, xml, or sub).", OFFSET(log_fmt), AV_OPT_TYPE_STRING, {.str="xml"}, 0, 1, FLAGS}, |
221 |
+ {"enable_transform", "use model='enable_transform=true'.", OFFSET(enable_transform), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS|AV_OPT_FLAG_DEPRECATED}, |
222 |
+ {"phone_model", "use model='enable_transform=true'.", OFFSET(phone_model), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS|AV_OPT_FLAG_DEPRECATED}, |
223 |
+ {"psnr", "use feature='name=psnr'.", OFFSET(psnr), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS|AV_OPT_FLAG_DEPRECATED}, |
224 |
+ {"ssim", "use feature='name=ssim'.", OFFSET(ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS|AV_OPT_FLAG_DEPRECATED}, |
225 |
+ {"ms_ssim", "use feature='name=ms_ssim'.", OFFSET(ms_ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS|AV_OPT_FLAG_DEPRECATED}, |
226 |
{"pool", "Set the pool method to be used for computing vmaf.", OFFSET(pool), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS}, |
227 |
{"n_threads", "Set number of threads to be used when computing vmaf.", OFFSET(n_threads), AV_OPT_TYPE_INT, {.i64=0}, 0, UINT_MAX, FLAGS}, |
228 |
{"n_subsample", "Set interval for frame subsampling used when computing vmaf.", OFFSET(n_subsample), AV_OPT_TYPE_INT, {.i64=1}, 1, UINT_MAX, FLAGS}, |
229 |
- {"enable_conf_interval", "Enables confidence interval.", OFFSET(enable_conf_interval), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS}, |
230 |
+ {"enable_conf_interval", "model='enable_conf_interval=true'.", OFFSET(enable_conf_interval), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS|AV_OPT_FLAG_DEPRECATED}, |
231 |
+ {"model", "Set the model to be used for computing vmaf.", OFFSET(model_cfg), AV_OPT_TYPE_STRING, {.str="version=vmaf_v0.6.1"}, 0, 1, FLAGS}, |
232 |
+ {"feature", "Set the feature to be used for computing vmaf.", OFFSET(feature_cfg), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS}, |
233 |
{ NULL } |
234 |
}; |
235 |
|
236 |
FRAMESYNC_DEFINE_CLASS(libvmaf, LIBVMAFContext, fs); |
237 |
|
238 |
-#define read_frame_fn(type, bits) \ |
239 |
- static int read_frame_##bits##bit(float *ref_data, float *main_data, \ |
240 |
- float *temp_data, int stride, void *ctx) \ |
241 |
-{ \ |
242 |
- LIBVMAFContext *s = (LIBVMAFContext *) ctx; \ |
243 |
- int ret; \ |
244 |
- \ |
245 |
- pthread_mutex_lock(&s->lock); \ |
246 |
- \ |
247 |
- while (!s->frame_set && !s->eof) { \ |
248 |
- pthread_cond_wait(&s->cond, &s->lock); \ |
249 |
- } \ |
250 |
- \ |
251 |
- if (s->frame_set) { \ |
252 |
- int ref_stride = s->gref->linesize[0]; \ |
253 |
- int main_stride = s->gmain->linesize[0]; \ |
254 |
- \ |
255 |
- const type *ref_ptr = (const type *) s->gref->data[0]; \ |
256 |
- const type *main_ptr = (const type *) s->gmain->data[0]; \ |
257 |
- \ |
258 |
- float *ptr = ref_data; \ |
259 |
- float factor = 1.f / (1 << (bits - 8)); \ |
260 |
- \ |
261 |
- int h = s->height; \ |
262 |
- int w = s->width; \ |
263 |
- \ |
264 |
- int i,j; \ |
265 |
- \ |
266 |
- for (i = 0; i < h; i++) { \ |
267 |
- for ( j = 0; j < w; j++) { \ |
268 |
- ptr[j] = ref_ptr[j] * factor; \ |
269 |
- } \ |
270 |
- ref_ptr += ref_stride / sizeof(*ref_ptr); \ |
271 |
- ptr += stride / sizeof(*ptr); \ |
272 |
- } \ |
273 |
- \ |
274 |
- ptr = main_data; \ |
275 |
- \ |
276 |
- for (i = 0; i < h; i++) { \ |
277 |
- for (j = 0; j < w; j++) { \ |
278 |
- ptr[j] = main_ptr[j] * factor; \ |
279 |
- } \ |
280 |
- main_ptr += main_stride / sizeof(*main_ptr); \ |
281 |
- ptr += stride / sizeof(*ptr); \ |
282 |
- } \ |
283 |
- } \ |
284 |
- \ |
285 |
- ret = !s->frame_set; \ |
286 |
- \ |
287 |
- av_frame_unref(s->gref); \ |
288 |
- av_frame_unref(s->gmain); \ |
289 |
- s->frame_set = 0; \ |
290 |
- \ |
291 |
- pthread_cond_signal(&s->cond); \ |
292 |
- pthread_mutex_unlock(&s->lock); \ |
293 |
- \ |
294 |
- if (ret) { \ |
295 |
- return 2; \ |
296 |
- } \ |
297 |
- \ |
298 |
- return 0; \ |
299 |
+static enum VmafPixelFormat pix_fmt_map(enum AVPixelFormat av_pix_fmt) |
300 |
+{ |
301 |
+ switch (av_pix_fmt) { |
302 |
+ case AV_PIX_FMT_YUV420P: |
303 |
+ case AV_PIX_FMT_YUV420P10LE: |
304 |
+ case AV_PIX_FMT_YUV420P12LE: |
305 |
+ case AV_PIX_FMT_YUV420P16LE: |
306 |
+ return VMAF_PIX_FMT_YUV420P; |
307 |
+ case AV_PIX_FMT_YUV422P: |
308 |
+ case AV_PIX_FMT_YUV422P10LE: |
309 |
+ case AV_PIX_FMT_YUV422P12LE: |
310 |
+ case AV_PIX_FMT_YUV422P16LE: |
311 |
+ return VMAF_PIX_FMT_YUV422P; |
312 |
+ case AV_PIX_FMT_YUV444P: |
313 |
+ case AV_PIX_FMT_YUV444P10LE: |
314 |
+ case AV_PIX_FMT_YUV444P12LE: |
315 |
+ case AV_PIX_FMT_YUV444P16LE: |
316 |
+ return VMAF_PIX_FMT_YUV444P; |
317 |
+ default: |
318 |
+ return VMAF_PIX_FMT_UNKNOWN; |
319 |
+ } |
320 |
} |
321 |
|
322 |
-read_frame_fn(uint8_t, 8); |
323 |
-read_frame_fn(uint16_t, 10); |
324 |
+static int copy_picture_data(AVFrame *src, VmafPicture *dst, unsigned bpc) |
325 |
+{ |
326 |
+ int err = vmaf_picture_alloc(dst, pix_fmt_map(src->format), bpc, |
327 |
+ src->width, src->height); |
328 |
+ if (err) |
329 |
+ return AVERROR(ENOMEM); |
330 |
|
331 |
-static void compute_vmaf_score(LIBVMAFContext *s) |
332 |
+ for (unsigned i = 0; i < 3; i++) { |
333 |
+ uint8_t *src_data = src->data[i]; |
334 |
+ uint8_t *dst_data = dst->data[i]; |
335 |
+ for (unsigned j = 0; j < dst->h[i]; j++) { |
336 |
+ memcpy(dst_data, src_data, sizeof(*dst_data) * dst->w[i]); |
337 |
+ src_data += src->linesize[i]; |
338 |
+ dst_data += dst->stride[i]; |
339 |
+ } |
340 |
+ } |
341 |
+ |
342 |
+ return 0; |
343 |
+} |
344 |
+ |
345 |
+static int do_vmaf(FFFrameSync *fs) |
346 |
{ |
347 |
- int (*read_frame)(float *ref_data, float *main_data, float *temp_data, |
348 |
- int stride, void *ctx); |
349 |
- char *format; |
350 |
+ AVFilterContext *ctx = fs->parent; |
351 |
+ LIBVMAFContext *s = ctx->priv; |
352 |
+ VmafPicture pic_ref, pic_dist; |
353 |
+ AVFrame *ref, *dist; |
354 |
+ int err = 0; |
355 |
|
356 |
- if (s->desc->comp[0].depth <= 8) { |
357 |
- read_frame = read_frame_8bit; |
358 |
- } else { |
359 |
- read_frame = read_frame_10bit; |
360 |
+ int ret = ff_framesync_dualinput_get(fs, &dist, &ref); |
361 |
+ if (ret < 0) |
362 |
+ return ret; |
363 |
+ if (ctx->is_disabled || !ref) |
364 |
+ return ff_filter_frame(ctx->outputs[0], dist); |
365 |
+ |
366 |
+ err = copy_picture_data(ref, &pic_ref, s->bpc); |
367 |
+ if (err) { |
368 |
+ av_log(s, AV_LOG_ERROR, "problem during vmaf_picture_alloc.\n"); |
369 |
+ return AVERROR(ENOMEM); |
370 |
} |
371 |
|
372 |
- format = (char *) s->desc->name; |
373 |
+ err = copy_picture_data(dist, &pic_dist, s->bpc); |
374 |
+ if (err) { |
375 |
+ av_log(s, AV_LOG_ERROR, "problem during vmaf_picture_alloc.\n"); |
376 |
+ vmaf_picture_unref(&pic_ref); |
377 |
+ return AVERROR(ENOMEM); |
378 |
+ } |
379 |
|
380 |
- s->error = compute_vmaf(&s->vmaf_score, format, s->width, s->height, |
381 |
- read_frame, s, s->model_path, s->log_path, |
382 |
- s->log_fmt, 0, 0, s->enable_transform, |
383 |
- s->phone_model, s->psnr, s->ssim, |
384 |
- s->ms_ssim, s->pool, |
385 |
- s->n_threads, s->n_subsample, s->enable_conf_interval); |
386 |
+ err = vmaf_read_pictures(s->vmaf, &pic_ref, &pic_dist, s->frame_cnt++); |
387 |
+ if (err) { |
388 |
+ av_log(s, AV_LOG_ERROR, "problem during vmaf_read_pictures.\n"); |
389 |
+ return AVERROR(EINVAL); |
390 |
+ } |
391 |
+ |
392 |
+ return ff_filter_frame(ctx->outputs[0], dist); |
393 |
} |
394 |
|
395 |
-static void *call_vmaf(void *ctx) |
396 |
+ |
397 |
+static AVDictionary **delimited_dict_parse(char *str, unsigned *cnt) |
398 |
{ |
399 |
- LIBVMAFContext *s = (LIBVMAFContext *) ctx; |
400 |
- compute_vmaf_score(s); |
401 |
- if (!s->error) { |
402 |
- av_log(ctx, AV_LOG_INFO, "VMAF score: %f\n",s->vmaf_score); |
403 |
- } else { |
404 |
- pthread_mutex_lock(&s->lock); |
405 |
- pthread_cond_signal(&s->cond); |
406 |
- pthread_mutex_unlock(&s->lock); |
407 |
+ AVDictionary **dict = NULL; |
408 |
+ char *str_copy = NULL; |
409 |
+ char *saveptr = NULL; |
410 |
+ unsigned cnt2; |
411 |
+ int err = 0; |
412 |
+ |
413 |
+ if (!str) |
414 |
+ return NULL; |
415 |
+ |
416 |
+ cnt2 = 1; |
417 |
+ for (char *p = str; *p; p++) { |
418 |
+ if (*p == '|') |
419 |
+ cnt2++; |
420 |
} |
421 |
- pthread_exit(NULL); |
422 |
+ |
423 |
+ dict = av_calloc(cnt2, sizeof(*dict)); |
424 |
+ if (!dict) |
425 |
+ goto fail; |
426 |
+ |
427 |
+ str_copy = av_strdup(str); |
428 |
+ if (!str_copy) |
429 |
+ goto fail; |
430 |
+ |
431 |
+ *cnt = 0; |
432 |
+ for (unsigned i = 0; i < cnt2; i++) { |
433 |
+ char *s = av_strtok(i == 0 ? str_copy : NULL, "|", &saveptr); |
434 |
+ if (!s) |
435 |
+ continue; |
436 |
+ err = av_dict_parse_string(&dict[(*cnt)++], s, "=", ":", 0); |
437 |
+ if (err) |
438 |
+ goto fail; |
439 |
+ } |
440 |
+ |
441 |
+ av_free(str_copy); |
442 |
+ return dict; |
443 |
+ |
444 |
+fail: |
445 |
+ if (dict) { |
446 |
+ for (unsigned i = 0; i < *cnt; i++) { |
447 |
+ if (dict[i]) |
448 |
+ av_dict_free(&dict[i]); |
449 |
+ } |
450 |
+ av_free(dict); |
451 |
+ } |
452 |
+ |
453 |
+ av_free(str_copy); |
454 |
+ *cnt = 0; |
455 |
return NULL; |
456 |
} |
457 |
|
458 |
-static int do_vmaf(FFFrameSync *fs) |
459 |
+static int parse_features(AVFilterContext *ctx) |
460 |
{ |
461 |
- AVFilterContext *ctx = fs->parent; |
462 |
LIBVMAFContext *s = ctx->priv; |
463 |
- AVFrame *master, *ref; |
464 |
- int ret; |
465 |
+ AVDictionary **dict = NULL; |
466 |
+ unsigned dict_cnt; |
467 |
+ int err = 0; |
468 |
|
469 |
- ret = ff_framesync_dualinput_get(fs, &master, &ref); |
470 |
- if (ret < 0) |
471 |
- return ret; |
472 |
- if (!ref) |
473 |
- return ff_filter_frame(ctx->outputs[0], master); |
474 |
+ if (!s->feature_cfg) |
475 |
+ return 0; |
476 |
|
477 |
- pthread_mutex_lock(&s->lock); |
478 |
+ dict = delimited_dict_parse(s->feature_cfg, &dict_cnt); |
479 |
+ if (!dict) { |
480 |
+ av_log(ctx, AV_LOG_ERROR, |
481 |
+ "could not parse feature config: %s\n", s->feature_cfg); |
482 |
+ return AVERROR(EINVAL); |
483 |
+ } |
484 |
|
485 |
- while (s->frame_set && !s->error) { |
486 |
- pthread_cond_wait(&s->cond, &s->lock); |
487 |
+ for (unsigned i = 0; i < dict_cnt; i++) { |
488 |
+ char *feature_name = NULL; |
489 |
+ VmafFeatureDictionary *feature_opts_dict = NULL; |
490 |
+ AVDictionaryEntry *e = NULL; |
491 |
+ |
492 |
+ while (e = av_dict_get(dict[i], "", e, AV_DICT_IGNORE_SUFFIX)) { |
493 |
+ if (av_stristr(e->key, "name")) { |
494 |
+ feature_name = e->value; |
495 |
+ continue; |
496 |
+ } |
497 |
+ |
498 |
+ err = vmaf_feature_dictionary_set(&feature_opts_dict, e->key, |
499 |
+ e->value); |
500 |
+ if (err) { |
501 |
+ av_log(ctx, AV_LOG_ERROR, |
502 |
+ "could not set feature option: %s.%s=%s\n", |
503 |
+ feature_name, e->key, e->value); |
504 |
+ goto exit; |
505 |
+ } |
506 |
+ } |
507 |
+ |
508 |
+ err = vmaf_use_feature(s->vmaf, feature_name, feature_opts_dict); |
509 |
+ if (err) { |
510 |
+ av_log(ctx, AV_LOG_ERROR, |
511 |
+ "problem during vmaf_use_feature: %s\n", feature_name); |
512 |
+ goto exit; |
513 |
+ } |
514 |
} |
515 |
|
516 |
- if (s->error) { |
517 |
+exit: |
518 |
+ for (unsigned i = 0; i < dict_cnt; i++) { |
519 |
+ if (dict[i]) |
520 |
+ av_dict_free(&dict[i]); |
521 |
+ } |
522 |
+ av_free(dict); |
523 |
+ return err; |
524 |
+} |
525 |
+ |
526 |
+static int parse_models(AVFilterContext *ctx) |
527 |
+{ |
528 |
+ LIBVMAFContext *s = ctx->priv; |
529 |
+ AVDictionary **dict; |
530 |
+ unsigned dict_cnt; |
531 |
+ int err = 0; |
532 |
+ |
533 |
+ if (!s->model_cfg) return 0; |
534 |
+ |
535 |
+ dict_cnt = 0; |
536 |
+ dict = delimited_dict_parse(s->model_cfg, &dict_cnt); |
537 |
+ if (!dict) { |
538 |
av_log(ctx, AV_LOG_ERROR, |
539 |
- "libvmaf encountered an error, check log for details\n"); |
540 |
- pthread_mutex_unlock(&s->lock); |
541 |
+ "could not parse model config: %s\n", s->model_cfg); |
542 |
return AVERROR(EINVAL); |
543 |
} |
544 |
|
545 |
- av_frame_ref(s->gref, ref); |
546 |
- av_frame_ref(s->gmain, master); |
547 |
+ s->model_cnt = dict_cnt; |
548 |
+ s->model = av_calloc(s->model_cnt, sizeof(*s->model)); |
549 |
+ if (!s->model) |
550 |
+ return AVERROR(ENOMEM); |
551 |
|
552 |
- s->frame_set = 1; |
553 |
+ for (unsigned i = 0; i < dict_cnt; i++) { |
554 |
+ VmafModelConfig model_cfg = { 0 }; |
555 |
+ AVDictionaryEntry *e = NULL; |
556 |
+ char *version = NULL; |
557 |
+ char *path = NULL; |
558 |
|
559 |
- pthread_cond_signal(&s->cond); |
560 |
- pthread_mutex_unlock(&s->lock); |
561 |
+ while (e = av_dict_get(dict[i], "", e, AV_DICT_IGNORE_SUFFIX)) { |
562 |
+ if (av_stristr(e->key, "disable_clip")) { |
563 |
+ model_cfg.flags |= av_stristr(e->value, "true") ? |
564 |
+ VMAF_MODEL_FLAG_DISABLE_CLIP : 0; |
565 |
+ continue; |
566 |
+ } |
567 |
|
568 |
- return ff_filter_frame(ctx->outputs[0], master); |
569 |
+ if (av_stristr(e->key, "enable_transform")) { |
570 |
+ model_cfg.flags |= av_stristr(e->value, "true") ? |
571 |
+ VMAF_MODEL_FLAG_ENABLE_TRANSFORM : 0; |
572 |
+ continue; |
573 |
+ } |
574 |
+ |
575 |
+ if (av_stristr(e->key, "name")) { |
576 |
+ model_cfg.name = e->value; |
577 |
+ continue; |
578 |
+ } |
579 |
+ |
580 |
+ if (av_stristr(e->key, "version")) { |
581 |
+ version = e->value; |
582 |
+ continue; |
583 |
+ } |
584 |
+ |
585 |
+ if (av_stristr(e->key, "path")) { |
586 |
+ path = e->value; |
587 |
+ continue; |
588 |
+ } |
589 |
+ } |
590 |
+ |
591 |
+ if (version) { |
592 |
+ err = vmaf_model_load(&s->model[i], &model_cfg, version); |
593 |
+ if (err) { |
594 |
+ av_log(ctx, AV_LOG_ERROR, |
595 |
+ "could not load libvmaf model with version: %s\n", |
596 |
+ version); |
597 |
+ goto exit; |
598 |
+ } |
599 |
+ } |
600 |
+ |
601 |
+ if (path && !s->model[i]) { |
602 |
+ err = vmaf_model_load_from_path(&s->model[i], &model_cfg, path); |
603 |
+ if (err) { |
604 |
+ av_log(ctx, AV_LOG_ERROR, |
605 |
+ "could not load libvmaf model with path: %s\n", |
606 |
+ path); |
607 |
+ goto exit; |
608 |
+ } |
609 |
+ } |
610 |
+ |
611 |
+ if (!s->model[i]) { |
612 |
+ av_log(ctx, AV_LOG_ERROR, |
613 |
+ "could not load libvmaf model with config: %s\n", |
614 |
+ s->model_cfg); |
615 |
+ goto exit; |
616 |
+ } |
617 |
+ |
618 |
+ while (e = av_dict_get(dict[i], "", e, AV_DICT_IGNORE_SUFFIX)) { |
619 |
+ VmafFeatureDictionary *feature_opts_dict = NULL; |
620 |
+ char *feature_opt = NULL; |
621 |
+ |
622 |
+ char *feature_name = av_strtok(e->key, ".", &feature_opt); |
623 |
+ if (!feature_opt) |
624 |
+ continue; |
625 |
+ |
626 |
+ err = vmaf_feature_dictionary_set(&feature_opts_dict, |
627 |
+ feature_opt, e->value); |
628 |
+ if (err) { |
629 |
+ av_log(ctx, AV_LOG_ERROR, |
630 |
+ "could not set feature option: %s.%s=%s\n", |
631 |
+ feature_name, feature_opt, e->value); |
632 |
+ err = AVERROR(EINVAL); |
633 |
+ goto exit; |
634 |
+ } |
635 |
+ |
636 |
+ err = vmaf_model_feature_overload(s->model[i], feature_name, |
637 |
+ feature_opts_dict); |
638 |
+ if (err) { |
639 |
+ av_log(ctx, AV_LOG_ERROR, |
640 |
+ "could not overload feature: %s\n", feature_name); |
641 |
+ err = AVERROR(EINVAL); |
642 |
+ goto exit; |
643 |
+ } |
644 |
+ } |
645 |
+ } |
646 |
+ |
647 |
+ for (unsigned i = 0; i < s->model_cnt; i++) { |
648 |
+ err = vmaf_use_features_from_model(s->vmaf, s->model[i]); |
649 |
+ if (err) { |
650 |
+ av_log(ctx, AV_LOG_ERROR, |
651 |
+ "problem during vmaf_use_features_from_model\n"); |
652 |
+ err = AVERROR(EINVAL); |
653 |
+ goto exit; |
654 |
+ } |
655 |
+ } |
656 |
+ |
657 |
+exit: |
658 |
+ for (unsigned i = 0; i < dict_cnt; i++) { |
659 |
+ if (dict[i]) |
660 |
+ av_dict_free(&dict[i]); |
661 |
+ } |
662 |
+ av_free(dict); |
663 |
+ return err; |
664 |
} |
665 |
|
666 |
+static enum VmafLogLevel log_level_map(int log_level) |
667 |
+{ |
668 |
+ switch (log_level) { |
669 |
+ case AV_LOG_QUIET: |
670 |
+ return VMAF_LOG_LEVEL_NONE; |
671 |
+ case AV_LOG_ERROR: |
672 |
+ return VMAF_LOG_LEVEL_ERROR; |
673 |
+ case AV_LOG_WARNING: |
674 |
+ return VMAF_LOG_LEVEL_WARNING; |
675 |
+ case AV_LOG_INFO: |
676 |
+ return VMAF_LOG_LEVEL_INFO; |
677 |
+ case AV_LOG_DEBUG: |
678 |
+ return VMAF_LOG_LEVEL_DEBUG; |
679 |
+ default: |
680 |
+ return VMAF_LOG_LEVEL_INFO; |
681 |
+ } |
682 |
+} |
683 |
+ |
684 |
+static int parse_deprecated_options(AVFilterContext *ctx) |
685 |
+{ |
686 |
+ LIBVMAFContext *s = ctx->priv; |
687 |
+ VmafModel *model = NULL; |
688 |
+ VmafModelCollection *model_collection = NULL; |
689 |
+ enum VmafModelFlags flags = VMAF_MODEL_FLAGS_DEFAULT; |
690 |
+ int err = 0; |
691 |
+ |
692 |
+ VmafModelConfig model_cfg = { |
693 |
+ .name = "vmaf", |
694 |
+ .flags = flags, |
695 |
+ }; |
696 |
+ |
697 |
+ if (s->enable_transform || s->phone_model) |
698 |
+ flags |= VMAF_MODEL_FLAG_ENABLE_TRANSFORM; |
699 |
+ |
700 |
+ if (!s->model_path) |
701 |
+ goto extra_metrics_only; |
702 |
+ |
703 |
+ if (s->enable_conf_interval) { |
704 |
+ err = vmaf_model_collection_load_from_path(&model, &model_collection, |
705 |
+ &model_cfg, s->model_path); |
706 |
+ if (err) { |
707 |
+ av_log(ctx, AV_LOG_ERROR, |
708 |
+ "problem loading model file: %s\n", s->model_path); |
709 |
+ goto exit; |
710 |
+ } |
711 |
+ |
712 |
+ err = vmaf_use_features_from_model_collection(s->vmaf, model_collection); |
713 |
+ if (err) { |
714 |
+ av_log(ctx, AV_LOG_ERROR, |
715 |
+ "problem loading feature extractors from model file: %s\n", |
716 |
+ s->model_path); |
717 |
+ goto exit; |
718 |
+ } |
719 |
+ } else { |
720 |
+ err = vmaf_model_load_from_path(&model, &model_cfg, s->model_path); |
721 |
+ if (err) { |
722 |
+ av_log(ctx, AV_LOG_ERROR, |
723 |
+ "problem loading model file: %s\n", s->model_path); |
724 |
+ goto exit; |
725 |
+ } |
726 |
+ err = vmaf_use_features_from_model(s->vmaf, model); |
727 |
+ if (err) { |
728 |
+ av_log(ctx, AV_LOG_ERROR, |
729 |
+ "problem loading feature extractors from model file: %s\n", |
730 |
+ s->model_path); |
731 |
+ goto exit; |
732 |
+ } |
733 |
+ } |
734 |
+ |
735 |
+extra_metrics_only: |
736 |
+ if (s->psnr) { |
737 |
+ VmafFeatureDictionary *d = NULL; |
738 |
+ vmaf_feature_dictionary_set(&d, "enable_chroma", "false"); |
739 |
+ |
740 |
+ err = vmaf_use_feature(s->vmaf, "psnr", d); |
741 |
+ if (err) { |
742 |
+ av_log(ctx, AV_LOG_ERROR, |
743 |
+ "problem loading feature extractor: psnr\n"); |
744 |
+ goto exit; |
745 |
+ } |
746 |
+ } |
747 |
+ |
748 |
+ if (s->ssim) { |
749 |
+ err = vmaf_use_feature(s->vmaf, "float_ssim", NULL); |
750 |
+ if (err) { |
751 |
+ av_log(ctx, AV_LOG_ERROR, |
752 |
+ "problem loading feature extractor: ssim\n"); |
753 |
+ goto exit; |
754 |
+ } |
755 |
+ } |
756 |
+ |
757 |
+ if (s->ms_ssim) { |
758 |
+ err = vmaf_use_feature(s->vmaf, "float_ms_ssim", NULL); |
759 |
+ if (err) { |
760 |
+ av_log(ctx, AV_LOG_ERROR, |
761 |
+ "problem loading feature extractor: ms_ssim\n"); |
762 |
+ goto exit; |
763 |
+ } |
764 |
+ } |
765 |
+ |
766 |
+exit: |
767 |
+ return err; |
768 |
+} |
769 |
+ |
770 |
static av_cold int init(AVFilterContext *ctx) |
771 |
{ |
772 |
LIBVMAFContext *s = ctx->priv; |
773 |
+ int err = 0; |
774 |
|
775 |
- s->gref = av_frame_alloc(); |
776 |
- s->gmain = av_frame_alloc(); |
777 |
- if (!s->gref || !s->gmain) |
778 |
- return AVERROR(ENOMEM); |
779 |
+ VmafConfiguration cfg = { |
780 |
+ .log_level = log_level_map(av_log_get_level()), |
781 |
+ .n_subsample = s->n_subsample, |
782 |
+ .n_threads = s->n_threads, |
783 |
+ }; |
784 |
|
785 |
- s->error = 0; |
786 |
+ err = vmaf_init(&s->vmaf, cfg); |
787 |
+ if (err) |
788 |
+ return AVERROR(EINVAL); |
789 |
|
790 |
- s->vmaf_thread_created = 0; |
791 |
- pthread_mutex_init(&s->lock, NULL); |
792 |
- pthread_cond_init (&s->cond, NULL); |
793 |
+ err = parse_deprecated_options(ctx); |
794 |
+ if (err) |
795 |
+ return err; |
796 |
|
797 |
+ err = parse_models(ctx); |
798 |
+ if (err) |
799 |
+ return err; |
800 |
+ |
801 |
+ err = parse_features(ctx); |
802 |
+ if (err) |
803 |
+ return err; |
804 |
+ |
805 |
s->fs.on_event = do_vmaf; |
806 |
return 0; |
807 |
} |
808 |
@@ -265,31 +554,36 @@ static int config_input_ref(AVFilterLink *inlink) |
809 |
|
810 |
static int config_input_ref(AVFilterLink *inlink) |
811 |
{ |
812 |
- AVFilterContext *ctx = inlink->dst; |
813 |
+ AVFilterContext *ctx = inlink->dst; |
814 |
LIBVMAFContext *s = ctx->priv; |
815 |
- int th; |
816 |
+ const AVPixFmtDescriptor *desc; |
817 |
+ int err = 0; |
818 |
|
819 |
- if (ctx->inputs[0]->w != ctx->inputs[1]->w || |
820 |
- ctx->inputs[0]->h != ctx->inputs[1]->h) { |
821 |
- av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n"); |
822 |
- return AVERROR(EINVAL); |
823 |
+ if (ctx->inputs[0]->w != ctx->inputs[1]->w) { |
824 |
+ av_log(ctx, AV_LOG_ERROR, "input width must match.\n"); |
825 |
+ err |= AVERROR(EINVAL); |
826 |
} |
827 |
if (ctx->inputs[0]->format != ctx->inputs[1]->format) { |
828 |
av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n"); |
829 |
return AVERROR(EINVAL); |
830 |
} |
831 |
|
832 |
- s->desc = av_pix_fmt_desc_get(inlink->format); |
833 |
- s->width = ctx->inputs[0]->w; |
834 |
- s->height = ctx->inputs[0]->h; |
835 |
+ if (ctx->inputs[0]->h != ctx->inputs[1]->h) { |
836 |
+ av_log(ctx, AV_LOG_ERROR, "input height must match.\n"); |
837 |
+ err |= AVERROR(EINVAL); |
838 |
+ } |
839 |
|
840 |
- th = pthread_create(&s->vmaf_thread, NULL, call_vmaf, (void *) s); |
841 |
- if (th) { |
842 |
- av_log(ctx, AV_LOG_ERROR, "Thread creation failed.\n"); |
843 |
- return AVERROR(EINVAL); |
844 |
+ if (ctx->inputs[0]->format != ctx->inputs[1]->format) { |
845 |
+ av_log(ctx, AV_LOG_ERROR, "input pix_fmt must match.\n"); |
846 |
+ err |= AVERROR(EINVAL); |
847 |
} |
848 |
- s->vmaf_thread_created = 1; |
849 |
|
850 |
+ if (err) |
851 |
+ return err; |
852 |
+ |
853 |
+ desc = av_pix_fmt_desc_get(inlink->format); |
854 |
+ s->bpc = desc->comp[0].depth; |
855 |
+ |
856 |
return 0; |
857 |
} |
858 |
|
859 |
@@ -320,28 +614,80 @@ static int activate(AVFilterContext *ctx) |
860 |
return ff_framesync_activate(&s->fs); |
861 |
} |
862 |
|
863 |
+static enum VmafOutputFormat log_fmt_map(const char *log_fmt) |
864 |
+{ |
865 |
+ if (log_fmt) { |
866 |
+ if (av_stristr(log_fmt, "xml")) |
867 |
+ return VMAF_OUTPUT_FORMAT_XML; |
868 |
+ if (av_stristr(log_fmt, "json")) |
869 |
+ return VMAF_OUTPUT_FORMAT_JSON; |
870 |
+ if (av_stristr(log_fmt, "csv")) |
871 |
+ return VMAF_OUTPUT_FORMAT_CSV; |
872 |
+ if (av_stristr(log_fmt, "sub")) |
873 |
+ return VMAF_OUTPUT_FORMAT_SUB; |
874 |
+ } |
875 |
+ |
876 |
+ return VMAF_OUTPUT_FORMAT_XML; |
877 |
+} |
878 |
+ |
879 |
+static enum VmafPoolingMethod pool_method_map(const char *pool_method) |
880 |
+{ |
881 |
+ if (pool_method) { |
882 |
+ if (av_stristr(pool_method, "min")) |
883 |
+ return VMAF_POOL_METHOD_MIN; |
884 |
+ if (av_stristr(pool_method, "mean")) |
885 |
+ return VMAF_POOL_METHOD_MEAN; |
886 |
+ if (av_stristr(pool_method, "harmonic_mean")) |
887 |
+ return VMAF_POOL_METHOD_HARMONIC_MEAN; |
888 |
+ } |
889 |
+ |
890 |
+ return VMAF_POOL_METHOD_MEAN; |
891 |
+} |
892 |
+ |
893 |
static av_cold void uninit(AVFilterContext *ctx) |
894 |
{ |
895 |
LIBVMAFContext *s = ctx->priv; |
896 |
+ int err = 0; |
897 |
|
898 |
ff_framesync_uninit(&s->fs); |
899 |
|
900 |
- pthread_mutex_lock(&s->lock); |
901 |
- s->eof = 1; |
902 |
- pthread_cond_signal(&s->cond); |
903 |
- pthread_mutex_unlock(&s->lock); |
904 |
+ if (!s->frame_cnt) |
905 |
+ goto clean_up; |
906 |
|
907 |
- if (s->vmaf_thread_created) |
908 |
- { |
909 |
- pthread_join(s->vmaf_thread, NULL); |
910 |
- s->vmaf_thread_created = 0; |
911 |
+ err = vmaf_read_pictures(s->vmaf, NULL, NULL, 0); |
912 |
+ if (err) { |
913 |
+ av_log(ctx, AV_LOG_ERROR, |
914 |
+ "problem flushing libvmaf context.\n"); |
915 |
} |
916 |
|
917 |
- av_frame_free(&s->gref); |
918 |
- av_frame_free(&s->gmain); |
919 |
+ for (unsigned i = 0; i < s->model_cnt; i++) { |
920 |
+ double vmaf_score; |
921 |
+ err = vmaf_score_pooled(s->vmaf, s->model[i], pool_method_map(s->pool), |
922 |
+ &vmaf_score, 0, s->frame_cnt - 1); |
923 |
+ if (err) { |
924 |
+ av_log(ctx, AV_LOG_ERROR, |
925 |
+ "problem getting pooled vmaf score.\n"); |
926 |
+ } |
927 |
|
928 |
- pthread_mutex_destroy(&s->lock); |
929 |
- pthread_cond_destroy(&s->cond); |
930 |
+ av_log(ctx, AV_LOG_INFO, "VMAF score: %f\n", vmaf_score); |
931 |
+ } |
932 |
+ |
933 |
+ if (s->vmaf) { |
934 |
+ if (s->log_path && !err) |
935 |
+ vmaf_write_output(s->vmaf, s->log_path, log_fmt_map(s->log_fmt)); |
936 |
+ } |
937 |
+ |
938 |
+clean_up: |
939 |
+ if (s->model) { |
940 |
+ for (unsigned i = 0; i < s->model_cnt; i++) { |
941 |
+ if (s->model[i]) |
942 |
+ vmaf_model_destroy(s->model[i]); |
943 |
+ } |
944 |
+ av_free(s->model); |
945 |
+ } |
946 |
+ |
947 |
+ if (s->vmaf) |
948 |
+ vmaf_close(s->vmaf); |
949 |
} |
950 |
|
951 |
static const AVFilterPad libvmaf_inputs[] = { |