encodebin: Add APIs to set element properties on encoding profiles

User often want to set encoder properties on encoding profiles,
this introduces a way to easily 'preset' properties when defining the
profile. This uses GstStructure to define those properties the same
way it is done in `splitmux` for example as it makes simple to handle.

This also defines a more complex structure type where we can map a set
of properties to set depending on the muxer/encoder factory that has
been picked by EncodeBin so it is quite flexible.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/merge_requests/1002>
This commit is contained in:
Thibault Saunier 2020-11-20 18:35:49 -03:00 committed by GStreamer Merge Bot
parent a8fdaba2ab
commit a8fca8d040
5 changed files with 300 additions and 35 deletions

View file

@ -30,8 +30,8 @@
*
* Functions to create and handle encoding profiles.
*
* Encoding profiles describe the media types and settings one wishes to use
* for an encoding process. The top-level profiles are commonly
* Encoding profiles describe the media types and settings one wishes to use for
* an encoding process. The top-level profiles are commonly
* #GstEncodingContainerProfile(s) (which contains a user-readable name and
* description along with which container format to use). These, in turn,
* reference one or more #GstEncodingProfile(s) which indicate which encoding
@ -101,18 +101,21 @@
*
* ### Setting properties on muxers or on the encoding profile itself
*
* Moreover, you can set extra properties `presence`, `single-segment` and
* `variable-framerate` * of an * encoding profile using the `|presence=` syntax
* as in:
* Moreover, you can set the extra properties:
*
* * `|element-properties,property1=true` (See
* #gst_encoding_profile_set_element_properties)
* * `|presence=true` (See See #gst_encoding_profile_get_presence)
* * `|single-segment=true` (See #gst_encoding_profile_set_single_segment)
* * `|single-segment=true` (See
* #gst_encoding_video_profile_set_variableframerate)
*
* for example:
*
* ```
* video/webm:video/x-vp8|presence=1,variable-framerate=true|single-segment=true:audio/x-vorbis
* video/webm:video/x-vp8|presence=1|element-properties,target-bitrate=500000:audio/x-vorbis
* ```
*
* This field allows specifies the maximum number of times a
* #GstEncodingProfile can be used inside an encodebin. If 0, it is not a
* mandatory stream and can be used as many times as necessary.
*
* ### Enforcing properties to the stream itself (video size, number of audio channels, etc..)
*
* You can also use the `restriction_caps->encoded_format_caps` syntax to
@ -297,6 +300,8 @@
#include <string.h>
/* GstEncodingProfile API */
#define PROFILE_LOCK(profile) (g_mutex_lock(&((GstEncodingProfile*)profile)->lock))
#define PROFILE_UNLOCK(profile) (g_mutex_unlock(&((GstEncodingProfile*)profile)->lock))
struct _GstEncodingProfile
{
@ -309,10 +314,14 @@ struct _GstEncodingProfile
gchar *preset;
gchar *preset_name;
guint presence;
GstCaps *restriction;
gboolean allow_dynamic_output;
gboolean enabled;
gboolean single_segment;
GMutex lock; // {
GstCaps *restriction;
GstStructure *element_properties;
// }
};
struct _GstEncodingProfileClass
@ -326,6 +335,7 @@ enum
{
FIRST_PROPERTY,
PROP_RESTRICTION_CAPS,
PROP_ELEMENT_PROPERTIES,
LAST_PROPERTY
};
@ -391,6 +401,11 @@ _encoding_profile_get_property (GObject * object, guint prop_id,
case PROP_RESTRICTION_CAPS:
gst_value_set_caps (value, prof->restriction);
break;
case PROP_ELEMENT_PROPERTIES:
PROFILE_LOCK (prof);
gst_value_set_structure (value, prof->element_properties);
PROFILE_UNLOCK (prof);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -408,6 +423,14 @@ _encoding_profile_set_property (GObject * object, guint prop_id,
gst_encoding_profile_set_restriction (prof, gst_caps_copy
(gst_value_get_caps (value)));
break;
case PROP_ELEMENT_PROPERTIES:
{
const GstStructure *structure = gst_value_get_structure (value);
gst_encoding_profile_set_element_properties (prof,
structure ? gst_structure_copy (structure) : NULL);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -441,11 +464,30 @@ gst_encoding_profile_class_init (GstEncodingProfileClass * klass)
_properties[PROP_RESTRICTION_CAPS] =
g_param_spec_boxed ("restriction-caps", "Restriction caps",
"The restriction caps to use", GST_TYPE_CAPS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_property (gobject_class,
PROP_RESTRICTION_CAPS, _properties[PROP_RESTRICTION_CAPS]);
/**
* GstEncodingProfile:element-properties:
*
* A #GstStructure defining the properties to be set to the element
* the profile represents.
*
* For example for `av1enc`:
*
* ```
* element-properties,row-mt=true, end-usage=vbr
* ```
*
* Since: 1.20
*/
_properties[PROP_ELEMENT_PROPERTIES] =
g_param_spec_boxed ("element-properties", "Element properties",
"The element properties to use. "
"Example: {properties,boolean-prop=true,string-prop=\"hi\"}.",
GST_TYPE_STRUCTURE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, LAST_PROPERTY, _properties);
}
/**
@ -791,6 +833,84 @@ gst_encoding_profile_set_restriction (GstEncodingProfile * profile,
_properties[PROP_RESTRICTION_CAPS]);
}
/**
* gst_encoding_profile_set_element_properties:
* @self: a #GstEncodingProfile
* @element_properties: (transfer full): A #GstStructure defining the properties
* to be set to the element the profile represents.
*
* This allows setting the muxing/encoding element properties.
*
* **Set properties generically**
*
* ``` properties
* [element-properties, boolean-prop=true, string-prop="hi"]
* ```
*
* **Mapping properties with well known element factories**
*
* ``` properties
* element-properties-map, map = {
* [openh264enc, gop-size=32, ],
* [x264enc, key-int-max=32, tune=zerolatency],
* }
* ```
*
* Since: 1.20
*/
void
gst_encoding_profile_set_element_properties (GstEncodingProfile * self,
GstStructure * element_properties)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (self));
g_return_if_fail (!element_properties
|| GST_IS_STRUCTURE (element_properties));
#ifndef G_DISABLE_CHECKS
if (element_properties &&
(gst_structure_has_name (element_properties, "element-properties-map")
|| gst_structure_has_name (element_properties, "properties-map")
|| gst_structure_has_name (element_properties, "map")))
g_return_if_fail (gst_structure_has_field_typed (element_properties, "map",
GST_TYPE_LIST));
#endif
PROFILE_LOCK (self);
if (self->element_properties)
gst_structure_free (self->element_properties);
if (element_properties)
self->element_properties = element_properties;
else
self->element_properties = NULL;
PROFILE_UNLOCK (self);
g_object_notify_by_pspec (G_OBJECT (self),
_properties[PROP_ELEMENT_PROPERTIES]);
}
/**
* gst_encoding_profile_get_element_properties:
* @self: a #GstEncodingProfile
*
* Returns: (transfer full) (nullable): The properties that are going to be set on the underlying element
*
* Since: 1.20
*/
GstStructure *
gst_encoding_profile_get_element_properties (GstEncodingProfile * self)
{
GstStructure *res = NULL;
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (self), NULL);
PROFILE_LOCK (self);
if (self->element_properties)
res = gst_structure_copy (self->element_properties);
PROFILE_UNLOCK (self);
return res;
}
/* Container profiles */
struct _GstEncodingContainerProfile
@ -1648,6 +1768,30 @@ done:
return profile;
}
static gboolean
gst_structure_validate_name (const gchar * name)
{
const gchar *s;
g_return_val_if_fail (name != NULL, FALSE);
if (G_UNLIKELY (!g_ascii_isalpha (*name)))
return FALSE;
/* FIXME: test name string more */
s = &name[1];
while (*s && (g_ascii_isalnum (*s) || strchr ("/-_.:+", *s) != NULL))
s++;
if (*s == ',')
return TRUE;
if (G_UNLIKELY (*s != '\0'))
return FALSE;
return TRUE;
}
static GstEncodingProfile *
create_encoding_stream_profile (gchar * serialized_profile,
GList * muxers_and_encoders, GstCaps * raw_audio_caps,
@ -1659,6 +1803,7 @@ create_encoding_stream_profile (gchar * serialized_profile,
gchar *strcaps, *strpresence, **strprops_v, **restriction_format,
**preset_v, *preset_name = NULL, *factory_name = NULL,
*variable_framerate = NULL;
GstStructure *element_properties = NULL;
GstCaps *restrictioncaps = NULL;
GstEncodingProfile *profile = NULL;
@ -1694,12 +1839,26 @@ create_encoding_stream_profile (gchar * serialized_profile,
}
for (propi = 1; strprops_v[propi]; propi++) {
gchar **propv = g_strsplit (strprops_v[propi], "=", -1);
gchar **propv;
gchar *presence_str = NULL;
gchar *prop = strprops_v[propi];
GstStructure *tmpstruct = NULL;
if (gst_structure_validate_name (prop))
tmpstruct = gst_structure_new_from_string (prop);
if (tmpstruct) {
if (element_properties)
gst_structure_free (element_properties);
element_properties = tmpstruct;
continue;
}
propv = g_strsplit (prop, "=", -1);
if (propv[1] && propv[2]) {
g_warning ("Wrong format for property: %s, only 1 `=` is expected",
strprops_v[propi]);
prop);
return NULL;
}
@ -1723,6 +1882,9 @@ create_encoding_stream_profile (gchar * serialized_profile,
single_segment = g_value_get_boolean (&v);
g_value_reset (&v);
} else {
g_warning ("Unsupported property: %s", propv[0]);
return NULL;
}
if (presence_str) {
@ -1806,6 +1968,9 @@ create_encoding_stream_profile (gchar * serialized_profile,
return NULL;
}
if (element_properties)
gst_encoding_profile_set_element_properties (profile, element_properties);
return profile;
}

View file

@ -264,7 +264,14 @@ GST_PBUTILS_API
GstEncodingProfile * gst_encoding_profile_from_discoverer (GstDiscovererInfo *info);
GST_PBUTILS_API
GstEncodingProfile * gst_encoding_profile_copy (GstEncodingProfile *self);
GstEncodingProfile * gst_encoding_profile_copy (GstEncodingProfile *self);
GST_PBUTILS_API
void gst_encoding_profile_set_element_properties (GstEncodingProfile *self,
GstStructure *element_properties);
GST_PBUTILS_API
GstStructure *gst_encoding_profile_get_element_properties (GstEncodingProfile *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstEncodingAudioProfile, gst_object_unref)

View file

@ -870,12 +870,84 @@ beach:
return parser;
}
static gboolean
_set_properties (GQuark property_id, const GValue * value, GObject * element)
{
GST_DEBUG_OBJECT (element, "Setting %s", g_quark_to_string (property_id));
g_object_set_property (element, g_quark_to_string (property_id), value);
return TRUE;
}
static void
set_element_properties_from_encoding_profile (GstEncodingProfile * profile,
GParamSpec * arg G_GNUC_UNUSED, GstElement * element)
{
gint i;
const GValue *v;
GstElementFactory *factory;
GstStructure *properties =
gst_encoding_profile_get_element_properties (profile);
if (!properties)
return;
if (!gst_structure_has_name (properties, "element-properties-map")) {
gst_structure_foreach (properties,
(GstStructureForeachFunc) _set_properties, element);
goto done;
}
factory = gst_element_get_factory (element);
if (!factory) {
GST_INFO_OBJECT (profile, "No factory for underlying element, "
"not setting properties");
return;
}
v = gst_structure_get_value (properties, "map");
for (i = 0; i < gst_value_list_get_size (v); i++) {
const GValue *map_value = gst_value_list_get_value (v, i);
const GstStructure *tmp_properties;
if (!GST_VALUE_HOLDS_STRUCTURE (map_value)) {
g_warning ("Invalid value type %s in the property map "
"(expected GstStructure)", G_VALUE_TYPE_NAME (map_value));
continue;
}
tmp_properties = gst_value_get_structure (map_value);
if (!gst_structure_has_name (tmp_properties, GST_OBJECT_NAME (factory))) {
GST_INFO_OBJECT (GST_OBJECT_PARENT (element),
"Ignoring values for %" GST_PTR_FORMAT, tmp_properties);
continue;
}
GST_DEBUG_OBJECT (GST_OBJECT_PARENT (element),
"Setting %" GST_PTR_FORMAT " on %" GST_PTR_FORMAT, tmp_properties,
element);
gst_structure_foreach (tmp_properties,
(GstStructureForeachFunc) _set_properties, element);
goto done;
}
GST_ERROR_OBJECT (GST_OBJECT_PARENT (element), "Unknown factory: %s",
GST_OBJECT_NAME (factory));
done:
gst_structure_free (properties);
}
static GstElement *
_create_element_and_set_preset (GstElementFactory * factory,
const gchar * preset, const gchar * name, const gchar * preset_name)
GstEncodingProfile * profile, const gchar * name)
{
GstElement *res = NULL;
const gchar *preset;
const gchar *preset_name;
preset_name = gst_encoding_profile_get_preset_name (profile);
preset = gst_encoding_profile_get_preset (profile);
GST_DEBUG ("Creating element from factory %s (preset factory name: %s"
" preset name: %s)", GST_OBJECT_NAME (factory), preset_name, preset);
@ -902,6 +974,12 @@ _create_element_and_set_preset (GstElementFactory * factory,
}
}
/* Else we keep it */
if (res) {
set_element_properties_from_encoding_profile (profile, NULL, res);
g_signal_connect (profile, "notify::element-properties",
G_CALLBACK (set_element_properties_from_encoding_profile), res);
}
return res;
}
@ -914,11 +992,8 @@ _get_encoder (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof)
GstElement *encoder = NULL;
GstElementFactory *encoderfact = NULL;
GstCaps *format;
const gchar *preset, *preset_name;
format = gst_encoding_profile_get_format (sprof);
preset = gst_encoding_profile_get_preset (sprof);
preset_name = gst_encoding_profile_get_preset_name (sprof);
GST_DEBUG ("Getting list of encoders for format %" GST_PTR_FORMAT, format);
@ -948,8 +1023,7 @@ _get_encoder (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof)
for (tmp = encoders; tmp; tmp = tmp->next) {
encoderfact = (GstElementFactory *) tmp->data;
if ((encoder = _create_element_and_set_preset (encoderfact, preset,
NULL, preset_name)))
if ((encoder = _create_element_and_set_preset (encoderfact, sprof, NULL)))
break;
}
@ -1458,7 +1532,7 @@ _create_stream_group (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof,
tosync = g_list_append (tosync, sgroup->identity);
} else {
GST_INFO_OBJECT (ebin, "Single segment is not supported when avoiding"
" to reencode!");
" to re-encode!");
}
}
@ -1900,11 +1974,7 @@ _get_formatter (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof)
GstElement *formatter = NULL;
GstElementFactory *formatterfact = NULL;
GstCaps *format;
const gchar *preset, *preset_name;
format = gst_encoding_profile_get_format (sprof);
preset = gst_encoding_profile_get_preset (sprof);
preset_name = gst_encoding_profile_get_preset_name (sprof);
GST_DEBUG ("Getting list of formatters for format %" GST_PTR_FORMAT, format);
@ -1923,8 +1993,7 @@ _get_formatter (GstEncodeBaseBin * ebin, GstEncodingProfile * sprof)
GST_OBJECT_NAME (formatterfact));
if ((formatter =
_create_element_and_set_preset (formatterfact, preset,
NULL, preset_name)))
_create_element_and_set_preset (formatterfact, sprof, NULL)))
break;
}
@ -1969,10 +2038,9 @@ _get_muxer (GstEncodeBaseBin * ebin)
GstElementFactory *muxerfact = NULL;
const GList *tmp;
GstCaps *format;
const gchar *preset, *preset_name;
const gchar *preset_name;
format = gst_encoding_profile_get_format (ebin->profile);
preset = gst_encoding_profile_get_preset (ebin->profile);
preset_name = gst_encoding_profile_get_preset_name (ebin->profile);
GST_DEBUG_OBJECT (ebin, "Getting list of muxers for format %" GST_PTR_FORMAT,
@ -2037,8 +2105,7 @@ _get_muxer (GstEncodeBaseBin * ebin)
/* Only use a muxer than can use all streams and than can accept the
* preset (which may be present or not) */
if (cansinkstreams && (muxer =
_create_element_and_set_preset (muxerfact, preset, "muxer",
preset_name)))
_create_element_and_set_preset (muxerfact, ebin->profile, "muxer")))
break;
}
@ -2259,8 +2326,11 @@ stream_group_free (GstEncodeBaseBin * ebin, StreamGroup * sgroup)
if (sgroup->inqueue)
gst_element_set_state (sgroup->inqueue, GST_STATE_NULL);
if (sgroup->encoder)
if (sgroup->encoder) {
gst_element_set_state (sgroup->encoder, GST_STATE_NULL);
g_signal_handlers_disconnect_by_func (sgroup->profile,
set_element_properties_from_encoding_profile, sgroup->encoder);
}
if (sgroup->fakesink)
gst_element_set_state (sgroup->fakesink, GST_STATE_NULL);
if (sgroup->outfilter) {
@ -2372,6 +2442,8 @@ gst_encode_base_bin_tear_down_profile (GstEncodeBaseBin * ebin)
/* Remove muxer if present */
if (ebin->muxer) {
g_signal_handlers_disconnect_by_func (ebin->profile,
set_element_properties_from_encoding_profile, ebin->muxer);
gst_element_set_state (ebin->muxer, GST_STATE_NULL);
gst_bin_remove (GST_BIN (ebin), ebin->muxer);
ebin->muxer = NULL;

View file

@ -0,0 +1,20 @@
meta,
seek=false,
handles-states=true,
args = {
"audiotestsrc num-buffers=4 ! encodebin name=ebin profile=\"vorbisenc|element-properties,managed=true,name=audioencoder\" ! fakesink",
}
pause
check-properties, audioencoder::managed=true
set-properties, ebin::profile::element-properties=[
element-properties-map, map = {
[vorbisenc, managed=false],
[somethingelse, whatever=false],
},
]
check-properties, audioencoder::managed=false
stop

View file

@ -19,6 +19,7 @@ tests = [
'videorate/rate_2_0_with_decoder',
'compositor/renogotiate_failing_unsupported_src_format',
'giosrc/read-growing-file',
'encodebin/set-encoder-properties',
]
env = environment()