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
make_generic_overrides(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
gst_ffmpeg_cfg_init(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
cmp_enum_value(GEnumValue * val1,GEnumValue * val2)80 cmp_enum_value (GEnumValue * val1, GEnumValue * val2)
81 {
82 return val1->value - val2->value;
83 }
84
85 static GType
register_enum(const AVClass ** obj,const AVOption * top_opt)86 register_enum (const AVClass ** obj, const AVOption * top_opt)
87 {
88 const AVOption *opt = NULL;
89 GType res = 0;
90 GArray *values = g_array_new (TRUE, TRUE, sizeof (GEnumValue));
91 gchar *lower_obj_name = g_ascii_strdown ((*obj)->class_name, -1);
92 gchar *enum_name = g_strdup_printf ("%s-%s", lower_obj_name, top_opt->unit);
93 gboolean none_default = TRUE;
94
95 g_strcanon (enum_name, G_CSET_a_2_z G_CSET_DIGITS, '-');
96
97 if ((res = g_type_from_name (enum_name)))
98 goto done;
99
100 while ((opt = av_opt_next (obj, opt))) {
101 if (opt->type == AV_OPT_TYPE_CONST && !g_strcmp0 (top_opt->unit, opt->unit)) {
102 GEnumValue val;
103
104 val.value = opt->default_val.i64;
105 val.value_name = g_strdup (opt->help ? opt->help : opt->name);
106 val.value_nick = g_strdup (opt->name);
107
108 if (opt->default_val.i64 == top_opt->default_val.i64)
109 none_default = FALSE;
110
111 g_array_append_val (values, val);
112 }
113 }
114
115 if (values->len) {
116 guint i = 0;
117 gint cur_val;
118 gboolean cur_val_set = FALSE;
119
120 /* Sometimes ffmpeg sets a default value but no named constants with
121 * this value, we assume this means "unspecified" and add our own
122 */
123 if (none_default) {
124 GEnumValue val;
125
126 val.value = top_opt->default_val.i64;
127 val.value_name = g_strdup ("Unspecified");
128 val.value_nick = g_strdup ("unknown");
129 g_array_append_val (values, val);
130 }
131
132 g_array_sort (values, (GCompareFunc) cmp_enum_value);
133
134 /* Dedup, easy once sorted
135 * We do this because ffmpeg can expose multiple names for the
136 * same constant, the way we expose enums makes this too confusing.
137 */
138 while (i < values->len) {
139 if (cur_val_set) {
140 if (g_array_index (values, GEnumValue, i).value == cur_val) {
141 g_array_remove_index (values, i);
142 } else {
143 cur_val = g_array_index (values, GEnumValue, i).value;
144 i++;
145 }
146 } else {
147 cur_val = g_array_index (values, GEnumValue, i).value;
148 cur_val_set = TRUE;
149 i++;
150 }
151 }
152
153 res =
154 g_enum_register_static (enum_name, &g_array_index (values, GEnumValue,
155 0));
156 }
157
158 done:
159 g_free (lower_obj_name);
160 g_free (enum_name);
161 return res;
162 }
163
164 static gint
cmp_flags_value(GEnumValue * val1,GEnumValue * val2)165 cmp_flags_value (GEnumValue * val1, GEnumValue * val2)
166 {
167 return val1->value - val2->value;
168 }
169
170 static GType
register_flags(const AVClass ** obj,const AVOption * top_opt)171 register_flags (const AVClass ** obj, const AVOption * top_opt)
172 {
173 const AVOption *opt = NULL;
174 GType res = 0;
175 GArray *values = g_array_new (TRUE, TRUE, sizeof (GEnumValue));
176 gchar *lower_obj_name = g_ascii_strdown ((*obj)->class_name, -1);
177 gchar *flags_name = g_strdup_printf ("%s-%s", lower_obj_name, top_opt->unit);
178
179 g_strcanon (flags_name, G_CSET_a_2_z G_CSET_DIGITS, '-');
180
181 if ((res = g_type_from_name (flags_name)))
182 goto done;
183
184 while ((opt = av_opt_next (obj, opt))) {
185 if (opt->type == AV_OPT_TYPE_CONST && !g_strcmp0 (top_opt->unit, opt->unit)) {
186 GFlagsValue val;
187
188 /* We expose pass manually, hardcoding this isn't very nice, but
189 * I don't expect we want to do that sort of things often enough
190 * to warrant a general mechanism
191 */
192 if (!g_strcmp0 (top_opt->name, "flags")) {
193 if (opt->default_val.i64 == AV_CODEC_FLAG_QSCALE ||
194 opt->default_val.i64 == AV_CODEC_FLAG_PASS1 ||
195 opt->default_val.i64 == AV_CODEC_FLAG_PASS2) {
196 continue;
197 }
198 }
199
200 val.value = opt->default_val.i64;
201 val.value_name = g_strdup (opt->help ? opt->help : opt->name);
202 val.value_nick = g_strdup (opt->name);
203
204 g_array_append_val (values, val);
205 }
206 }
207
208 if (values->len) {
209 g_array_sort (values, (GCompareFunc) cmp_flags_value);
210
211 res =
212 g_flags_register_static (flags_name, &g_array_index (values,
213 GFlagsValue, 0));
214 }
215
216 done:
217 g_free (lower_obj_name);
218 g_free (flags_name);
219 return res;
220 }
221
222 static guint
install_opts(GObjectClass * gobject_class,const AVClass ** obj,guint prop_id,gint flags,const gchar * extra_help,GHashTable * overrides)223 install_opts (GObjectClass * gobject_class, const AVClass ** obj, guint prop_id,
224 gint flags, const gchar * extra_help, GHashTable * overrides)
225 {
226 const AVOption *opt = NULL;
227
228 while ((opt = av_opt_next (obj, opt))) {
229 GParamSpec *pspec = NULL;
230 AVOptionRanges *r;
231 gdouble min = G_MINDOUBLE;
232 gdouble max = G_MAXDOUBLE;
233 gchar *help;
234 const gchar *name;
235
236 if (overrides && g_hash_table_contains (overrides, opt->name)) {
237 gboolean skip;
238 const GstStructure *s =
239 (GstStructure *) g_hash_table_lookup (overrides, opt->name);
240
241 name = gst_structure_get_name (s);
242 if (gst_structure_get_boolean (s, "skip", &skip) && skip) {
243 continue;
244 }
245 } else {
246 name = opt->name;
247 }
248
249 if ((opt->flags & flags) != flags)
250 continue;
251
252 if (g_object_class_find_property (gobject_class, name))
253 continue;
254
255 if (av_opt_query_ranges (&r, obj, opt->name, AV_OPT_SEARCH_FAKE_OBJ) >= 0) {
256 if (r->nb_ranges == 1) {
257 min = r->range[0]->value_min;
258 max = r->range[0]->value_max;
259 }
260 av_opt_freep_ranges (&r);
261 }
262
263 help = g_strdup_printf ("%s%s", opt->help, extra_help);
264
265 switch (opt->type) {
266 case AV_OPT_TYPE_INT:
267 if (opt->unit) {
268 GType enum_gtype;
269 enum_gtype = register_enum (obj, opt);
270
271 if (enum_gtype) {
272 pspec = g_param_spec_enum (name, name, help,
273 enum_gtype, opt->default_val.i64, G_PARAM_READWRITE);
274 g_object_class_install_property (gobject_class, prop_id++, pspec);
275 } else { /* Some options have a unit but no named constants associated */
276 pspec = g_param_spec_int (name, name, help,
277 (gint) min, (gint) max, opt->default_val.i64,
278 G_PARAM_READWRITE);
279 g_object_class_install_property (gobject_class, prop_id++, pspec);
280 }
281 } else {
282 pspec = g_param_spec_int (name, name, help,
283 (gint) min, (gint) max, opt->default_val.i64, G_PARAM_READWRITE);
284 g_object_class_install_property (gobject_class, prop_id++, pspec);
285 }
286 break;
287 case AV_OPT_TYPE_FLAGS:
288 if (opt->unit) {
289 GType flags_gtype;
290 flags_gtype = register_flags (obj, opt);
291
292 if (flags_gtype) {
293 pspec = g_param_spec_flags (name, name, help,
294 flags_gtype, opt->default_val.i64, G_PARAM_READWRITE);
295 g_object_class_install_property (gobject_class, prop_id++, pspec);
296 }
297 }
298 break;
299 case AV_OPT_TYPE_DURATION: /* Fall through */
300 case AV_OPT_TYPE_INT64:
301 /* FIXME 2.0: Workaround for worst property related API change. We
302 * continue using a 32 bit integer for the bitrate property as
303 * otherwise too much existing code will fail at runtime.
304 *
305 * See https://gitlab.freedesktop.org/gstreamer/gst-libav/issues/41#note_142808 */
306 if (g_strcmp0 (name, "bitrate") == 0) {
307 pspec = g_param_spec_int (name, name, help,
308 (gint) MAX (min, G_MININT), (gint) MIN (max, G_MAXINT),
309 (gint) opt->default_val.i64, G_PARAM_READWRITE);
310 } else {
311 /* ffmpeg expresses all ranges with doubles, this is sad */
312 pspec = g_param_spec_int64 (name, name, help,
313 (min == (gdouble) INT64_MIN ? INT64_MIN : (gint64) min),
314 (max == (gdouble) INT64_MAX ? INT64_MAX : (gint64) max),
315 opt->default_val.i64, G_PARAM_READWRITE);
316 }
317 g_object_class_install_property (gobject_class, prop_id++, pspec);
318 break;
319 case AV_OPT_TYPE_DOUBLE:
320 pspec = g_param_spec_double (name, name, help,
321 min, max, opt->default_val.dbl, G_PARAM_READWRITE);
322 g_object_class_install_property (gobject_class, prop_id++, pspec);
323 break;
324 case AV_OPT_TYPE_FLOAT:
325 pspec = g_param_spec_float (name, name, help,
326 (gfloat) min, (gfloat) max, (gfloat) opt->default_val.dbl,
327 G_PARAM_READWRITE);
328 g_object_class_install_property (gobject_class, prop_id++, pspec);
329 break;
330 case AV_OPT_TYPE_STRING:
331 pspec = g_param_spec_string (name, name, help,
332 opt->default_val.str, G_PARAM_READWRITE);
333 g_object_class_install_property (gobject_class, prop_id++, pspec);
334 break;
335 case AV_OPT_TYPE_UINT64:
336 /* ffmpeg expresses all ranges with doubles, this is appalling */
337 pspec = g_param_spec_uint64 (name, name, help,
338 (gint64) (min == (gdouble) 0 ? 0 : min),
339 (gint64) (max == (gdouble) UINT64_MAX ? UINT64_MAX : min),
340 opt->default_val.i64, G_PARAM_READWRITE);
341 g_object_class_install_property (gobject_class, prop_id++, pspec);
342 break;
343 case AV_OPT_TYPE_BOOL:
344 pspec = g_param_spec_boolean (name, name, help,
345 opt->default_val.i64 ? TRUE : FALSE, G_PARAM_READWRITE);
346 g_object_class_install_property (gobject_class, prop_id++, pspec);
347 break;
348 /* TODO: didn't find options for the video encoders with
349 * the following type, add support if needed */
350 case AV_OPT_TYPE_CHANNEL_LAYOUT:
351 case AV_OPT_TYPE_COLOR:
352 case AV_OPT_TYPE_VIDEO_RATE:
353 case AV_OPT_TYPE_SAMPLE_FMT:
354 case AV_OPT_TYPE_PIXEL_FMT:
355 case AV_OPT_TYPE_IMAGE_SIZE:
356 case AV_OPT_TYPE_DICT:
357 case AV_OPT_TYPE_BINARY:
358 case AV_OPT_TYPE_RATIONAL:
359 default:
360 break;
361 }
362
363 g_free (help);
364
365 if (pspec) {
366 g_param_spec_set_qdata (pspec, avoption_quark, (gpointer) opt);
367 }
368 }
369
370 return prop_id;
371 }
372
373 void
gst_ffmpeg_cfg_install_properties(GObjectClass * klass,AVCodec * in_plugin,guint base,gint flags)374 gst_ffmpeg_cfg_install_properties (GObjectClass * klass, AVCodec * in_plugin,
375 guint base, gint flags)
376 {
377 gint prop_id;
378 AVCodecContext *ctx;
379
380 prop_id = base;
381 g_return_if_fail (base > 0);
382
383 ctx = avcodec_alloc_context3 (in_plugin);
384 if (!ctx)
385 g_warning ("could not get context");
386
387 prop_id =
388 install_opts ((GObjectClass *) klass, &in_plugin->priv_class, prop_id, 0,
389 " (Private codec option)", NULL);
390 prop_id =
391 install_opts ((GObjectClass *) klass, &ctx->av_class, prop_id, flags,
392 " (Generic codec option, might have no effect)", generic_overrides);
393
394 if (ctx) {
395 gst_ffmpeg_avcodec_close (ctx);
396 av_free (ctx);
397 }
398 }
399
400 static gint
set_option_value(AVCodecContext * ctx,GParamSpec * pspec,const GValue * value,const AVOption * opt)401 set_option_value (AVCodecContext * ctx, GParamSpec * pspec,
402 const GValue * value, const AVOption * opt)
403 {
404 int res = -1;
405
406 switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) {
407 case G_TYPE_INT:
408 res = av_opt_set_int (ctx, opt->name,
409 g_value_get_int (value), AV_OPT_SEARCH_CHILDREN);
410 break;
411 case G_TYPE_INT64:
412 res = av_opt_set_int (ctx, opt->name,
413 g_value_get_int64 (value), AV_OPT_SEARCH_CHILDREN);
414 break;
415 case G_TYPE_UINT64:
416 res = av_opt_set_int (ctx, opt->name,
417 g_value_get_uint64 (value), AV_OPT_SEARCH_CHILDREN);
418 break;
419 case G_TYPE_DOUBLE:
420 res = av_opt_set_double (ctx, opt->name,
421 g_value_get_double (value), AV_OPT_SEARCH_CHILDREN);
422 break;
423 case G_TYPE_FLOAT:
424 res = av_opt_set_double (ctx, opt->name,
425 g_value_get_float (value), AV_OPT_SEARCH_CHILDREN);
426 break;
427 case G_TYPE_STRING:
428 res = av_opt_set (ctx, opt->name,
429 g_value_get_string (value), AV_OPT_SEARCH_CHILDREN);
430 /* Some code in FFmpeg returns ENOMEM if the string is NULL:
431 * *dst = av_strdup(val);
432 * return *dst ? 0 : AVERROR(ENOMEM);
433 * That makes little sense, let's ignore that
434 */
435 if (!g_value_get_string (value))
436 res = 0;
437 break;
438 case G_TYPE_BOOLEAN:
439 res = av_opt_set_int (ctx, opt->name,
440 g_value_get_boolean (value), AV_OPT_SEARCH_CHILDREN);
441 break;
442 default:
443 if (G_IS_PARAM_SPEC_ENUM (pspec)) {
444 res = av_opt_set_int (ctx, opt->name,
445 g_value_get_enum (value), AV_OPT_SEARCH_CHILDREN);
446 } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) {
447 res = av_opt_set_int (ctx, opt->name,
448 g_value_get_flags (value), AV_OPT_SEARCH_CHILDREN);
449 } else { /* oops, bit lazy we don't cover this case yet */
450 g_critical ("%s does not yet support type %s", GST_FUNCTION,
451 g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)));
452 }
453 }
454
455 return res;
456 }
457
458 gboolean
gst_ffmpeg_cfg_set_property(AVCodecContext * refcontext,const GValue * value,GParamSpec * pspec)459 gst_ffmpeg_cfg_set_property (AVCodecContext * refcontext, const GValue * value,
460 GParamSpec * pspec)
461 {
462 const AVOption *opt;
463
464 opt = g_param_spec_get_qdata (pspec, avoption_quark);
465
466 if (!opt)
467 return FALSE;
468
469 return set_option_value (refcontext, pspec, value, opt) >= 0;
470 }
471
472 gboolean
gst_ffmpeg_cfg_get_property(AVCodecContext * refcontext,GValue * value,GParamSpec * pspec)473 gst_ffmpeg_cfg_get_property (AVCodecContext * refcontext, GValue * value,
474 GParamSpec * pspec)
475 {
476 const AVOption *opt;
477 int res = -1;
478
479 opt = g_param_spec_get_qdata (pspec, avoption_quark);
480
481 if (!opt)
482 return FALSE;
483
484 switch (G_PARAM_SPEC_VALUE_TYPE (pspec)) {
485 case G_TYPE_INT:
486 {
487 int64_t val;
488 if ((res = av_opt_get_int (refcontext, opt->name,
489 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
490 g_value_set_int (value, val);
491 break;
492 }
493 case G_TYPE_INT64:
494 {
495 int64_t val;
496 if ((res = av_opt_get_int (refcontext, opt->name,
497 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
498 g_value_set_int64 (value, val);
499 break;
500 }
501 case G_TYPE_UINT64:
502 {
503 int64_t val;
504 if ((res = av_opt_get_int (refcontext, opt->name,
505 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
506 g_value_set_uint64 (value, val);
507 break;
508 }
509 case G_TYPE_DOUBLE:
510 {
511 gdouble val;
512 if ((res = av_opt_get_double (refcontext, opt->name,
513 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
514 g_value_set_double (value, val);
515 break;
516 }
517 case G_TYPE_FLOAT:
518 {
519 gdouble val;
520 if ((res = av_opt_get_double (refcontext, opt->name,
521 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
522 g_value_set_float (value, (gfloat) val);
523 break;
524 }
525 case G_TYPE_STRING:
526 {
527 uint8_t *val;
528 if ((res = av_opt_get (refcontext, opt->name,
529 AV_OPT_SEARCH_CHILDREN | AV_OPT_ALLOW_NULL, &val) >= 0)) {
530 g_value_set_string (value, (gchar *) val);
531 }
532 break;
533 }
534 case G_TYPE_BOOLEAN:
535 {
536 int64_t val;
537 if ((res = av_opt_get_int (refcontext, opt->name,
538 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
539 g_value_set_boolean (value, val ? TRUE : FALSE);
540 break;
541 }
542 default:
543 if (G_IS_PARAM_SPEC_ENUM (pspec)) {
544 int64_t val;
545
546 if ((res = av_opt_get_int (refcontext, opt->name,
547 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
548 g_value_set_enum (value, val);
549 } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) {
550 int64_t val;
551
552 if ((res = av_opt_get_int (refcontext, opt->name,
553 AV_OPT_SEARCH_CHILDREN, &val) >= 0))
554 g_value_set_flags (value, val);
555 } else { /* oops, bit lazy we don't cover this case yet */
556 g_critical ("%s does not yet support type %s", GST_FUNCTION,
557 g_type_name (G_PARAM_SPEC_VALUE_TYPE (pspec)));
558 }
559 }
560
561 return res >= 0;
562 }
563
564 void
gst_ffmpeg_cfg_fill_context(GObject * object,AVCodecContext * context)565 gst_ffmpeg_cfg_fill_context (GObject * object, AVCodecContext * context)
566 {
567 GParamSpec **pspecs;
568 guint num_props, i;
569
570 pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object),
571 &num_props);
572
573 for (i = 0; i < num_props; ++i) {
574 GParamSpec *pspec = pspecs[i];
575 const AVOption *opt;
576 GValue value = G_VALUE_INIT;
577
578 opt = g_param_spec_get_qdata (pspec, avoption_quark);
579
580 if (!opt)
581 continue;
582
583 g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
584 g_object_get_property (object, pspec->name, &value);
585 set_option_value (context, pspec, &value, opt);
586 g_value_unset (&value);
587 }
588 g_free (pspecs);
589 }
590
591 void
gst_ffmpeg_cfg_finalize(void)592 gst_ffmpeg_cfg_finalize (void)
593 {
594 GST_ERROR ("Finalizing");
595 g_assert (generic_overrides);
596 g_hash_table_unref (generic_overrides);
597 }
598