libav: avcfg: Avoid brittle comparision
[platform/upstream/gstreamer.git] / subprojects / gst-libav / ext / libav / gstavcfg.c
1 /* GStreamer
2  *
3  * FFMpeg Configuration
4  *
5  * Copyright (C) <2006> Mark Nauwelaerts <manauw@skynet.be>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library 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  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include "gstav.h"
29 #include "gstavvidenc.h"
30 #include "gstavcfg.h"
31
32 #include <string.h>
33 #include <libavutil/opt.h>
34
35 static GQuark avoption_quark;
36 static GHashTable *generic_overrides = NULL;
37
38 static void
39 make_generic_overrides (void)
40 {
41   g_assert (!generic_overrides);
42   generic_overrides = g_hash_table_new_full (g_str_hash, g_str_equal,
43       g_free, (GDestroyNotify) gst_structure_free);
44
45   g_hash_table_insert (generic_overrides, g_strdup ("b"),
46       gst_structure_new_empty ("bitrate"));
47   g_hash_table_insert (generic_overrides, g_strdup ("ab"),
48       gst_structure_new_empty ("bitrate"));
49   g_hash_table_insert (generic_overrides, g_strdup ("g"),
50       gst_structure_new_empty ("gop-size"));
51   g_hash_table_insert (generic_overrides, g_strdup ("bt"),
52       gst_structure_new_empty ("bitrate-tolerance"));
53   g_hash_table_insert (generic_overrides, g_strdup ("bf"),
54       gst_structure_new_empty ("max-bframes"));
55
56   /* Those are exposed through caps */
57   g_hash_table_insert (generic_overrides, g_strdup ("profile"),
58       gst_structure_new ("profile", "skip", G_TYPE_BOOLEAN, TRUE, NULL));
59   g_hash_table_insert (generic_overrides, g_strdup ("level"),
60       gst_structure_new ("level", "skip", G_TYPE_BOOLEAN, TRUE, NULL));
61   g_hash_table_insert (generic_overrides, g_strdup ("color_primaries"),
62       gst_structure_new ("color_primaries", "skip", G_TYPE_BOOLEAN, TRUE,
63           NULL));
64   g_hash_table_insert (generic_overrides, g_strdup ("color_trc"),
65       gst_structure_new ("color_trc", "skip", G_TYPE_BOOLEAN, TRUE, NULL));
66   g_hash_table_insert (generic_overrides, g_strdup ("colorspace"),
67       gst_structure_new ("colorspace", "skip", G_TYPE_BOOLEAN, TRUE, NULL));
68   g_hash_table_insert (generic_overrides, g_strdup ("color_range"),
69       gst_structure_new ("color_range", "skip", G_TYPE_BOOLEAN, TRUE, NULL));
70 }
71
72 void
73 gst_ffmpeg_cfg_init (void)
74 {
75   avoption_quark = g_quark_from_static_string ("ffmpeg-cfg-param-spec-data");
76   make_generic_overrides ();
77 }
78
79 static gint
80 cmp_enum_value (GEnumValue * val1, GEnumValue * val2)
81 {
82   if (val1->value == val2->value)
83     return 0;
84   return (val1->value > val2->value) ? 1 : -1;
85 }
86
87 static GType
88 register_enum (const AVClass ** obj, const AVOption * top_opt)
89 {
90   const AVOption *opt = NULL;
91   GType res = 0;
92   GArray *values = g_array_new (TRUE, TRUE, sizeof (GEnumValue));
93   gchar *lower_obj_name = g_ascii_strdown ((*obj)->class_name, -1);
94   gchar *enum_name = g_strdup_printf ("%s-%s", lower_obj_name, top_opt->unit);
95   gboolean none_default = TRUE;
96   const gchar *enum_name_strip;
97
98   g_strcanon (enum_name, G_CSET_a_2_z G_CSET_DIGITS, '-');
99
100   /* strip leading '-'s */
101   enum_name_strip = enum_name;
102   while (enum_name_strip[0] == '-')
103     enum_name_strip++;
104
105   if (enum_name_strip[0] == '\0')
106     goto done;
107
108   if ((res = g_type_from_name (enum_name_strip)))
109     goto done;
110
111   while ((opt = av_opt_next (obj, opt))) {
112     if (opt->type == AV_OPT_TYPE_CONST && !g_strcmp0 (top_opt->unit, opt->unit)) {
113       GEnumValue val;
114
115       val.value = opt->default_val.i64;
116       val.value_name = g_strdup (opt->help ? opt->help : opt->name);
117       val.value_nick = g_strdup (opt->name);
118
119       if (opt->default_val.i64 == top_opt->default_val.i64)
120         none_default = FALSE;
121
122       g_array_append_val (values, val);
123     }
124   }
125
126   if (values->len) {
127     guint i = 0;
128     gint cur_val;
129     gboolean cur_val_set = FALSE;
130
131     /* Sometimes ffmpeg sets a default value but no named constants with
132      * this value, we assume this means "unspecified" and add our own
133      */
134     if (none_default) {
135       GEnumValue val;
136
137       val.value = top_opt->default_val.i64;
138       val.value_name = g_strdup ("Unspecified");
139       val.value_nick = g_strdup ("unknown");
140       g_array_append_val (values, val);
141     }
142
143     g_array_sort (values, (GCompareFunc) cmp_enum_value);
144
145     /* Dedup, easy once sorted
146      * We do this because ffmpeg can expose multiple names for the
147      * same constant, the way we expose enums makes this too confusing.
148      */
149     while (i < values->len) {
150       if (cur_val_set) {
151         if (g_array_index (values, GEnumValue, i).value == cur_val) {
152           GEnumValue val = g_array_index (values, GEnumValue, i);
153           /* Don't leak the strings */
154           g_free ((gchar *) val.value_name);
155           g_free ((gchar *) val.value_nick);
156           g_array_remove_index (values, i);
157         } else {
158           cur_val = g_array_index (values, GEnumValue, i).value;
159           i++;
160         }
161       } else {
162         cur_val = g_array_index (values, GEnumValue, i).value;
163         cur_val_set = TRUE;
164         i++;
165       }
166     }
167
168     res = g_enum_register_static (enum_name_strip,
169         &g_array_index (values, GEnumValue, 0));
170
171     gst_type_mark_as_plugin_api (res, 0);
172   }
173
174 done:
175   g_free (lower_obj_name);
176   g_free (enum_name);
177   return res;
178 }
179
180 static gint
181 cmp_flags_value (GFlagsValue * val1, GFlagsValue * val2)
182 {
183   if (val1->value == val2->value)
184     return 0;
185   return (val1->value > val2->value) ? 1 : -1;
186 }
187
188 static GType
189 register_flags (const AVClass ** obj, const AVOption * top_opt)
190 {
191   const AVOption *opt = NULL;
192   GType res = 0;
193   GArray *values = g_array_new (TRUE, TRUE, sizeof (GEnumValue));
194   gchar *lower_obj_name = g_ascii_strdown ((*obj)->class_name, -1);
195   gchar *flags_name = g_strdup_printf ("%s-%s", lower_obj_name, top_opt->unit);
196   const gchar *flags_name_strip;
197
198   g_strcanon (flags_name, G_CSET_a_2_z G_CSET_DIGITS, '-');
199
200   /* strip leading '-'s */
201   flags_name_strip = flags_name;
202   while (flags_name_strip[0] == '-')
203     flags_name_strip++;
204
205   if (flags_name_strip[0] == '\0')
206     goto done;
207
208   if ((res = g_type_from_name (flags_name_strip)))
209     goto done;
210
211   while ((opt = av_opt_next (obj, opt))) {
212     if (opt->type == AV_OPT_TYPE_CONST && !g_strcmp0 (top_opt->unit, opt->unit)) {
213       GFlagsValue val;
214
215       /* We expose pass manually, hardcoding this isn't very nice, but
216        * I don't expect we want to do that sort of things often enough
217        * to warrant a general mechanism
218        */
219       if (!g_strcmp0 (top_opt->name, "flags")) {
220         if (opt->default_val.i64 == AV_CODEC_FLAG_QSCALE ||
221             opt->default_val.i64 == AV_CODEC_FLAG_PASS1 ||
222             opt->default_val.i64 == AV_CODEC_FLAG_PASS2) {
223           continue;
224         }
225       }
226
227       val.value = opt->default_val.i64;
228       val.value_name = g_strdup (opt->help ? opt->help : opt->name);
229       val.value_nick = g_strdup (opt->name);
230
231       g_array_append_val (values, val);
232     }
233   }
234
235   if (values->len) {
236     g_array_sort (values, (GCompareFunc) cmp_flags_value);
237
238     res =
239         g_flags_register_static (flags_name_strip, &g_array_index (values,
240             GFlagsValue, 0));
241
242     gst_type_mark_as_plugin_api (res, 0);
243   }
244
245 done:
246   g_free (lower_obj_name);
247   g_free (flags_name);
248   return res;
249 }
250
251 static guint
252 install_opts (GObjectClass * gobject_class, const AVClass ** obj, guint prop_id,
253     gint flags, const gchar * extra_help, GHashTable * overrides)
254 {
255   const AVOption *opt = NULL;
256
257   while ((opt = av_opt_next (obj, opt))) {
258     GParamSpec *pspec = NULL;
259     AVOptionRanges *r;
260     gdouble min = G_MINDOUBLE;
261     gdouble max = G_MAXDOUBLE;
262     gchar *help;
263     const gchar *name;
264
265     if (overrides && g_hash_table_contains (overrides, opt->name)) {
266       gboolean skip;
267       const GstStructure *s =
268           (GstStructure *) g_hash_table_lookup (overrides, opt->name);
269
270       name = gst_structure_get_name (s);
271       if (gst_structure_get_boolean (s, "skip", &skip) && skip) {
272         continue;
273       }
274     } else {
275       name = opt->name;
276     }
277
278     if ((opt->flags & flags) != flags)
279       continue;
280
281     if (g_object_class_find_property (gobject_class, name))
282       continue;
283
284     if (av_opt_query_ranges (&r, obj, opt->name, AV_OPT_SEARCH_FAKE_OBJ) >= 0) {
285       if (r->nb_ranges == 1) {
286         min = r->range[0]->value_min;
287         max = r->range[0]->value_max;
288       }
289       av_opt_freep_ranges (&r);
290     }
291
292     help = g_strdup_printf ("%s%s", opt->help, extra_help);
293
294     switch (opt->type) {
295       case AV_OPT_TYPE_INT:
296         if (opt->unit) {
297           GType enum_gtype;
298           enum_gtype = register_enum (obj, opt);
299
300           if (enum_gtype) {
301             pspec = g_param_spec_enum (name, name, help,
302                 enum_gtype, opt->default_val.i64, G_PARAM_READWRITE);
303             g_object_class_install_property (gobject_class, prop_id++, pspec);
304           } else {              /* Some options have a unit but no named constants associated */
305             pspec = g_param_spec_int (name, name, help,
306                 (gint) min, (gint) max, opt->default_val.i64,
307                 G_PARAM_READWRITE);
308             g_object_class_install_property (gobject_class, prop_id++, pspec);
309           }
310         } else {
311           pspec = g_param_spec_int (name, name, help,
312               (gint) min, (gint) max, opt->default_val.i64, G_PARAM_READWRITE);
313           g_object_class_install_property (gobject_class, prop_id++, pspec);
314         }
315         break;
316       case AV_OPT_TYPE_FLAGS:
317         if (opt->unit) {
318           GType flags_gtype;
319           flags_gtype = register_flags (obj, opt);
320
321           if (flags_gtype) {
322             pspec = g_param_spec_flags (name, name, help,
323                 flags_gtype, opt->default_val.i64, G_PARAM_READWRITE);
324             g_object_class_install_property (gobject_class, prop_id++, pspec);
325           }
326         }
327         break;
328       case AV_OPT_TYPE_DURATION:       /* Fall through */
329       case AV_OPT_TYPE_INT64:
330         /* FIXME 2.0: Workaround for worst property related API change. We
331          * continue using a 32 bit integer for the bitrate property as
332          * otherwise too much existing code will fail at runtime.
333          *
334          * See https://gitlab.freedesktop.org/gstreamer/gst-libav/issues/41#note_142808 */
335         if (g_strcmp0 (name, "bitrate") == 0) {
336           pspec = g_param_spec_int (name, name, help,
337               (gint) MAX (min, G_MININT), (gint) MIN (max, G_MAXINT),
338               (gint) opt->default_val.i64, G_PARAM_READWRITE);
339         } else {
340           /* ffmpeg expresses all ranges with doubles, this is sad */
341           pspec = g_param_spec_int64 (name, name, help,
342               (min == (gdouble) INT64_MIN ? INT64_MIN : (gint64) min),
343               (max == (gdouble) INT64_MAX ? INT64_MAX : (gint64) max),
344               opt->default_val.i64, G_PARAM_READWRITE);
345         }
346         g_object_class_install_property (gobject_class, prop_id++, pspec);
347         break;
348       case AV_OPT_TYPE_DOUBLE:
349         pspec = g_param_spec_double (name, name, help,
350             min, max, opt->default_val.dbl, G_PARAM_READWRITE);
351         g_object_class_install_property (gobject_class, prop_id++, pspec);
352         break;
353       case AV_OPT_TYPE_FLOAT:
354         pspec = g_param_spec_float (name, name, help,
355             (gfloat) min, (gfloat) max, (gfloat) opt->default_val.dbl,
356             G_PARAM_READWRITE);
357         g_object_class_install_property (gobject_class, prop_id++, pspec);
358         break;
359       case AV_OPT_TYPE_STRING:
360         pspec = g_param_spec_string (name, name, help,
361             opt->default_val.str, G_PARAM_READWRITE);
362         g_object_class_install_property (gobject_class, prop_id++, pspec);
363         break;
364       case AV_OPT_TYPE_UINT64:
365         /* ffmpeg expresses all ranges with doubles, this is appalling */
366         pspec = g_param_spec_uint64 (name, name, help,
367             (guint64) (min <= (gdouble) 0 ? 0 : (guint64) min),
368             (guint64) (max >=
369                 /* Biggest value before UINT64_MAX that can be represented as double */
370                 (gdouble) 18446744073709550000.0 ?
371                 /* The Double conversion rounds UINT64_MAX to a bigger */
372                 /* value, so the following smaller limit must be used. */
373                 G_GUINT64_CONSTANT (18446744073709550000) : (guint64) max),
374             opt->default_val.i64, G_PARAM_READWRITE);
375         g_object_class_install_property (gobject_class, prop_id++, pspec);
376         break;
377       case AV_OPT_TYPE_BOOL:
378         pspec = g_param_spec_boolean (name, name, help,
379             opt->default_val.i64 ? TRUE : FALSE, G_PARAM_READWRITE);
380         g_object_class_install_property (gobject_class, prop_id++, pspec);
381         break;
382         /* TODO: didn't find options for the video encoders with
383          * the following type, add support if needed */
384       case AV_OPT_TYPE_CHANNEL_LAYOUT:
385       case AV_OPT_TYPE_COLOR:
386       case AV_OPT_TYPE_VIDEO_RATE:
387       case AV_OPT_TYPE_SAMPLE_FMT:
388       case AV_OPT_TYPE_PIXEL_FMT:
389       case AV_OPT_TYPE_IMAGE_SIZE:
390       case AV_OPT_TYPE_DICT:
391       case AV_OPT_TYPE_BINARY:
392       case AV_OPT_TYPE_RATIONAL:
393       default:
394         break;
395     }
396
397     g_free (help);
398
399     if (pspec) {
400       g_param_spec_set_qdata (pspec, avoption_quark, (gpointer) opt);
401     }
402   }
403
404   return prop_id;
405 }
406
407 void
408 gst_ffmpeg_cfg_install_properties (GObjectClass * klass, AVCodec * in_plugin,
409     guint base, gint flags)
410 {
411   gint prop_id;
412   AVCodecContext *ctx;
413
414   prop_id = base;
415   g_return_if_fail (base > 0);
416
417   ctx = avcodec_alloc_context3 (in_plugin);
418   if (!ctx)
419     g_warning ("could not get context");
420
421   prop_id =
422       install_opts ((GObjectClass *) klass, &in_plugin->priv_class, prop_id, 0,
423       " (Private codec option)", NULL);
424   prop_id =
425       install_opts ((GObjectClass *) klass, &ctx->av_class, prop_id, flags,
426       " (Generic codec option, might have no effect)", generic_overrides);
427
428   if (ctx) {
429     gst_ffmpeg_avcodec_close (ctx);
430     av_free (ctx);
431   }
432 }
433
434 static gint
435 set_option_value (AVCodecContext * ctx, GParamSpec * pspec,
436     const GValue * value, const AVOption * opt)
437 {
438   int res = -1;
439
440   switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) {
441     case G_TYPE_INT:
442       res = av_opt_set_int (ctx, opt->name,
443           g_value_get_int (value), AV_OPT_SEARCH_CHILDREN);
444       break;
445     case G_TYPE_INT64:
446       res = av_opt_set_int (ctx, opt->name,
447           g_value_get_int64 (value), AV_OPT_SEARCH_CHILDREN);
448       break;
449     case G_TYPE_UINT64:
450       res = av_opt_set_int (ctx, opt->name,
451           g_value_get_uint64 (value), AV_OPT_SEARCH_CHILDREN);
452       break;
453     case G_TYPE_DOUBLE:
454       res = av_opt_set_double (ctx, opt->name,
455           g_value_get_double (value), AV_OPT_SEARCH_CHILDREN);
456       break;
457     case G_TYPE_FLOAT:
458       res = av_opt_set_double (ctx, opt->name,
459           g_value_get_float (value), AV_OPT_SEARCH_CHILDREN);
460       break;
461     case G_TYPE_STRING:
462       res = av_opt_set (ctx, opt->name,
463           g_value_get_string (value), AV_OPT_SEARCH_CHILDREN);
464       /* Some code in FFmpeg returns ENOMEM if the string is NULL:
465        * *dst = av_strdup(val);
466        * return *dst ? 0 : AVERROR(ENOMEM);
467        * That makes little sense, let's ignore that
468        */
469       if (!g_value_get_string (value))
470         res = 0;
471       break;
472     case G_TYPE_BOOLEAN:
473       res = av_opt_set_int (ctx, opt->name,
474           g_value_get_boolean (value), AV_OPT_SEARCH_CHILDREN);
475       break;
476     default:
477       if (G_IS_PARAM_SPEC_ENUM (pspec)) {
478         res = av_opt_set_int (ctx, opt->name,
479             g_value_get_enum (value), AV_OPT_SEARCH_CHILDREN);
480       } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) {
481         res = av_opt_set_int (ctx, opt->name,
482             g_value_get_flags (value), AV_OPT_SEARCH_CHILDREN);
483       } else {                  /* oops, bit lazy we don't cover this case yet */
484         g_critical ("%s does not yet support type %s", GST_FUNCTION,
485             g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)));
486       }
487   }
488
489   return res;
490 }
491
492 gboolean
493 gst_ffmpeg_cfg_set_property (AVCodecContext * refcontext, const GValue * value,
494     GParamSpec * pspec)
495 {
496   const AVOption *opt;
497
498   opt = g_param_spec_get_qdata (pspec, avoption_quark);
499
500   if (!opt)
501     return FALSE;
502
503   return set_option_value (refcontext, pspec, value, opt) >= 0;
504 }
505
506 gboolean
507 gst_ffmpeg_cfg_get_property (AVCodecContext * refcontext, GValue * value,
508     GParamSpec * pspec)
509 {
510   const AVOption *opt;
511   int res = -1;
512
513   opt = g_param_spec_get_qdata (pspec, avoption_quark);
514
515   if (!opt)
516     return FALSE;
517
518   switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) {
519     case G_TYPE_INT:
520     {
521       int64_t val;
522       if ((res = av_opt_get_int (refcontext, opt->name,
523                   AV_OPT_SEARCH_CHILDREN, &val) >= 0))
524         g_value_set_int (value, val);
525       break;
526     }
527     case G_TYPE_INT64:
528     {
529       int64_t val;
530       if ((res = av_opt_get_int (refcontext, opt->name,
531                   AV_OPT_SEARCH_CHILDREN, &val) >= 0))
532         g_value_set_int64 (value, val);
533       break;
534     }
535     case G_TYPE_UINT64:
536     {
537       int64_t val;
538       if ((res = av_opt_get_int (refcontext, opt->name,
539                   AV_OPT_SEARCH_CHILDREN, &val) >= 0))
540         g_value_set_uint64 (value, val);
541       break;
542     }
543     case G_TYPE_DOUBLE:
544     {
545       gdouble val;
546       if ((res = av_opt_get_double (refcontext, opt->name,
547                   AV_OPT_SEARCH_CHILDREN, &val) >= 0))
548         g_value_set_double (value, val);
549       break;
550     }
551     case G_TYPE_FLOAT:
552     {
553       gdouble val;
554       if ((res = av_opt_get_double (refcontext, opt->name,
555                   AV_OPT_SEARCH_CHILDREN, &val) >= 0))
556         g_value_set_float (value, (gfloat) val);
557       break;
558     }
559     case G_TYPE_STRING:
560     {
561       uint8_t *val;
562       if ((res = av_opt_get (refcontext, opt->name,
563                   AV_OPT_SEARCH_CHILDREN | AV_OPT_ALLOW_NULL, &val) >= 0)) {
564         g_value_set_string (value, (gchar *) val);
565       }
566       break;
567     }
568     case G_TYPE_BOOLEAN:
569     {
570       int64_t val;
571       if ((res = av_opt_get_int (refcontext, opt->name,
572                   AV_OPT_SEARCH_CHILDREN, &val) >= 0))
573         g_value_set_boolean (value, val ? TRUE : FALSE);
574       break;
575     }
576     default:
577       if (G_IS_PARAM_SPEC_ENUM (pspec)) {
578         int64_t val;
579
580         if ((res = av_opt_get_int (refcontext, opt->name,
581                     AV_OPT_SEARCH_CHILDREN, &val) >= 0))
582           g_value_set_enum (value, val);
583       } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) {
584         int64_t val;
585
586         if ((res = av_opt_get_int (refcontext, opt->name,
587                     AV_OPT_SEARCH_CHILDREN, &val) >= 0))
588           g_value_set_flags (value, val);
589       } else {                  /* oops, bit lazy we don't cover this case yet */
590         g_critical ("%s does not yet support type %s", GST_FUNCTION,
591             g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)));
592       }
593   }
594
595   return res >= 0;
596 }
597
598 void
599 gst_ffmpeg_cfg_fill_context (GObject * object, AVCodecContext * context)
600 {
601   GParamSpec **pspecs;
602   guint num_props, i;
603
604   pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object),
605       &num_props);
606
607   for (i = 0; i < num_props; ++i) {
608     GParamSpec *pspec = pspecs[i];
609     const AVOption *opt;
610     GValue value = G_VALUE_INIT;
611
612     opt = g_param_spec_get_qdata (pspec, avoption_quark);
613
614     if (!opt)
615       continue;
616
617     g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
618     g_object_get_property (object, pspec->name, &value);
619     set_option_value (context, pspec, &value, opt);
620     g_value_unset (&value);
621   }
622   g_free (pspecs);
623 }
624
625 void
626 gst_ffmpeg_cfg_finalize (void)
627 {
628   GST_ERROR ("Finalizing");
629   g_assert (generic_overrides);
630   g_hash_table_unref (generic_overrides);
631 }