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