gstreamer/subprojects/gst-plugins-base/gst-libs/gst/pbutils/encoding-profile.c
Mathieu Duponchelle 62c303c2e0 encoding-profile: don't order profiles by stream ID ..
when creating a profile from a discoverer info.

There is no justification for the existing code, and talking with
Thibault he cannot remember why the sort was in place.

On the other hand, this allows GES users to not have to implement
a callback for the select-tracks-for-object callback when using
it to trim a single clip, which the output profile was built from:
track elements will be placed in the appropriate track by default,
that is the one that will be connected to the matching profile.

For multi-clip timelines, the situation doesn't change, users will
still have to implement a callback and do the leg work of placing
track elements (if any) in a matching track (if any).

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1549>
2022-08-02 15:48:12 +00:00

2326 lines
65 KiB
C

/* GStreamer encoding profiles library
* Copyright (C) 2009-2010 Edward Hervey <edward.hervey@collabora.co.uk>
* (C) 2009-2010 Nokia Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:encoding-profile
* @title: GstEncodingProfile
* @short_description: Encoding profile library
* @symbols:
* - GstEncodingProfile
* - GstEncodingContainerProfile
* - GstEncodingVideoProfile
* - GstEncodingAudioProfile
*
* 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
* #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
* format should be used on each individual streams.
*
* #GstEncodingProfile(s) can be provided to the 'encodebin' element, which will
* take care of selecting and setting up the required elements to produce an
* output stream conforming to the specifications of the profile.
*
* The encoding profiles do not necessarily specify which #GstElement to use for
* the various encoding and muxing steps, as they allow to specifying the format
* one wishes to use.
*
* Encoding profiles can be created at runtime by the application or loaded from
* (and saved to) file using the #GstEncodingTarget API.
*
* ## The encoding profile serialization format
*
* Encoding profiles can be serialized to be used in the command line tools or
* to set it on other other #GObject-s using #gst_util_set_object_arg for
* example.
*
* The serialization format aims at being simple to understand although flexible
* enough to describe any possible encoding profile. There are several ways to
* describe the profile depending on the context but the general idea is that it
* is a colon separated list of EncodingProfiles descriptions, the first one
* needs to describe a #GstEncodingContainerProfile and the following ones
* describe elementary streams.
*
* ### Using encoders and muxer element factory name
*
* ```
* muxer_factory_name:video_encoder_factory_name:audio_encoder_factory_name
* ```
*
* For example to encode a stream into a WebM container, with an OGG audio
* stream and a VP8 video stream, the serialized #GstEncodingProfile looks like:
*
* ```
* webmmux:vp8enc:vorbisenc
* ```
*
* ### Define the encoding profile in a generic way using caps:
*
* ```
* muxer_source_caps:video_encoder_source_caps:audio_encoder_source_caps
* ```
*
* For example to encode a stream into a WebM container, with an OGG audio
* stream and a VP8 video stream, the serialized #GstEncodingProfile looks like:
*
* ```
* video/webm:video/x-vp8:audio/x-vorbis
* ```
*
* It is possible to mix caps and element type names so you can specify a
* specific video encoder while using caps for other encoders/muxer.
*
* ### Using preset
*
* You can also set the preset name of the encoding profile using the
* caps+preset_name syntax as in:
*
* ```
* video/webm:video/x-vp8+youtube-preset:audio/x-vorbis
* ```
*
* ### Setting properties on muxers or on the encoding profile itself
*
* 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|element-properties,target-bitrate=500000:audio/x-vorbis
* ```
*
* ### 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
* specify the restriction caps to be set on a #GstEncodingProfile
*
* It corresponds to the restriction #GstCaps to apply before the encoder that
* will be used in the profile (See #gst_encoding_profile_get_restriction). The
* fields present in restriction caps are properties of the raw stream (that is,
* before encoding), such as height and width for video and depth and sampling
* rate for audio. This property does not make sense for muxers. See
* #gst_encoding_profile_get_restriction for more details.
*
* To force a video stream to be encoded with a Full HD resolution (using WebM
* as the container format, VP8 as the video codec and Vorbis as the audio
* codec), you should use:
*
* ```
* "video/webm:video/x-raw,width=1920,height=1080->video/x-vp8:audio/x-vorbis"
* ```
*
* > NOTE: Make sure to enclose into quotes to avoid '>' to be reinterpreted by
* > the shell.
*
* In the case you are specifying encoders directly, the following is also
* possible:
*
* ```
* matroskamux:x264enc,width=1920,height=1080:audio/x-vorbis
* ```
*
* ## Some serialized encoding formats examples
*
* ### MP3 audio and H264 in MP4**
*
* ```
* video/quicktime,variant=iso:video/x-h264:audio/mpeg,mpegversion=1,layer=3
* ```
*
* ### Vorbis and theora in OGG
*
* ```
* application/ogg:video/x-theora:audio/x-vorbis
* ```
*
* ### AC3 and H264 in MPEG-TS
*
* ```
* video/mpegts:video/x-h264:audio/x-ac3
* ```
*
* ## Loading a profile from encoding targets
*
* Anywhere you have to use a string to define a #GstEncodingProfile, you
* can use load it from a #GstEncodingTarget using the following syntaxes:
*
* ```
* target_name[/profilename/category]
* ```
*
* or
*
* ```
* /path/to/target.gep:profilename
* ```
*
* ## Examples
*
* ### Creating a profile
*
* ``` c
* #include <gst/pbutils/encoding-profile.h>
* ...
* GstEncodingProfile *
* create_ogg_theora_profile(void)
*{
* GstEncodingContainerProfile *prof;
* GstCaps *caps;
*
* caps = gst_caps_from_string("application/ogg");
* prof = gst_encoding_container_profile_new("Ogg audio/video",
* "Standard OGG/THEORA/VORBIS",
* caps, NULL);
* gst_caps_unref (caps);
*
* caps = gst_caps_from_string("video/x-theora");
* gst_encoding_container_profile_add_profile(prof,
* (GstEncodingProfile*) gst_encoding_video_profile_new(caps, NULL, NULL, 0));
* gst_caps_unref (caps);
*
* caps = gst_caps_from_string("audio/x-vorbis");
* gst_encoding_container_profile_add_profile(prof,
* (GstEncodingProfile*) gst_encoding_audio_profile_new(caps, NULL, NULL, 0));
* gst_caps_unref (caps);
*
* return (GstEncodingProfile*) prof;
*}
*
* ```
*
* ### Example: Using an encoder preset with a profile
*
* ``` c
* #include <gst/pbutils/encoding-profile.h>
* ...
* GstEncodingProfile *
* create_ogg_theora_profile(void)
*{
* GstEncodingVideoProfile *v;
* GstEncodingAudioProfile *a;
* GstEncodingContainerProfile *prof;
* GstCaps *caps;
* GstPreset *preset;
*
* caps = gst_caps_from_string ("application/ogg");
* prof = gst_encoding_container_profile_new ("Ogg audio/video",
* "Standard OGG/THEORA/VORBIS",
* caps, NULL);
* gst_caps_unref (caps);
*
* preset = GST_PRESET (gst_element_factory_make ("theoraenc", "theorapreset"));
* g_object_set (preset, "bitrate", 1000, NULL);
* // The preset will be saved on the filesystem,
* // so try to use a descriptive name
* gst_preset_save_preset (preset, "theora_bitrate_preset");
* gst_object_unref (preset);
*
* caps = gst_caps_from_string ("video/x-theora");
* v = gst_encoding_video_profile_new (caps, "theora_bitrate_preset", NULL, 0);
* gst_encoding_container_profile_add_profile (prof, (GstEncodingProfile*) v);
* gst_caps_unref (caps);
*
* caps = gst_caps_from_string ("audio/x-vorbis");
* a = gst_encoding_audio_profile_new (caps, NULL, NULL, 0);
* gst_encoding_container_profile_add_profile (prof, (GstEncodingProfile*) a);
* gst_caps_unref (caps);
*
* return (GstEncodingProfile*) prof;
*}
*
* ```
*
* ### Listing categories, targets and profiles
*
* ``` c
* #include <gst/pbutils/encoding-profile.h>
* ...
* GstEncodingProfile *prof;
* GList *categories, *tmpc;
* GList *targets, *tmpt;
* ...
* categories = gst_encoding_list_available_categories ();
*
* ... Show available categories to user ...
*
* for (tmpc = categories; tmpc; tmpc = tmpc->next) {
* gchar *category = (gchar *) tmpc->data;
*
* ... and we can list all targets within that category ...
*
* targets = gst_encoding_list_all_targets (category);
*
* ... and show a list to our users ...
*
* g_list_foreach (targets, (GFunc) gst_encoding_target_unref, NULL);
* g_list_free (targets);
* }
*
* g_list_foreach (categories, (GFunc) g_free, NULL);
* g_list_free (categories);
*
* ...
* ```
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "encoding-profile.h"
#include "encoding-target.h"
#include <string.h>
#ifndef GST_DISABLE_GST_DEBUG
#define GST_CAT_DEFAULT gst_pb_utils_encoding_profile_ensure_debug_category()
static GstDebugCategory *
gst_pb_utils_encoding_profile_ensure_debug_category (void)
{
static gsize cat_gonce = 0;
if (g_once_init_enter (&cat_gonce)) {
GstDebugCategory *cat = NULL;
GST_DEBUG_CATEGORY_INIT (cat, "encoding-profile", 0,
"GstPbUtils encoding profile");
g_once_init_leave (&cat_gonce, (gsize) cat);
}
return (GstDebugCategory *) cat_gonce;
}
#endif /* GST_DISABLE_GST_DEBUG */
/* GstEncodingProfile API */
#define PROFILE_LOCK(profile) (g_mutex_lock(&((GstEncodingProfile*)profile)->lock))
#define PROFILE_UNLOCK(profile) (g_mutex_unlock(&((GstEncodingProfile*)profile)->lock))
struct _GstEncodingProfile
{
GObject parent;
/*< public > */
gchar *name;
gchar *description;
GstCaps *format;
gchar *preset;
gchar *preset_name;
guint presence;
gboolean allow_dynamic_output;
gboolean enabled;
gboolean single_segment;
GMutex lock; // {
GstCaps *restriction;
GstStructure *element_properties;
// }
};
struct _GstEncodingProfileClass
{
GObjectClass parent_class;
void (*copy) (GstEncodingProfile * self, GstEncodingProfile * copy);
};
enum
{
FIRST_PROPERTY,
PROP_RESTRICTION_CAPS,
PROP_ELEMENT_PROPERTIES,
LAST_PROPERTY
};
static GParamSpec *_properties[LAST_PROPERTY];
static void string_to_profile_transform (const GValue * src_value,
GValue * dest_value);
static gboolean gst_encoding_profile_deserialize_valfunc (GValue * value,
const gchar * s);
static gchar *gst_encoding_profile_serialize_valfunc (GValue * value);
static void gst_encoding_profile_class_init (GstEncodingProfileClass * klass);
static gpointer gst_encoding_profile_parent_class = NULL;
static void
gst_encoding_profile_class_intern_init (gpointer klass)
{
gst_encoding_profile_parent_class = g_type_class_peek_parent (klass);
gst_encoding_profile_class_init ((GstEncodingProfileClass *) klass);
}
GType
gst_encoding_profile_get_type (void)
{
static gsize g_define_type_id_init = 0;
if (g_once_init_enter (&g_define_type_id_init)) {
GType g_define_type_id = g_type_register_static_simple (G_TYPE_OBJECT,
g_intern_static_string ("GstEncodingProfile"),
sizeof (GstEncodingProfileClass),
(GClassInitFunc) gst_encoding_profile_class_intern_init,
sizeof (GstEncodingProfile),
NULL,
(GTypeFlags) 0);
static GstValueTable gstvtable = {
G_TYPE_NONE,
(GstValueCompareFunc) NULL,
(GstValueSerializeFunc) gst_encoding_profile_serialize_valfunc,
(GstValueDeserializeFunc) gst_encoding_profile_deserialize_valfunc
};
gstvtable.type = g_define_type_id;
/* Register a STRING=>PROFILE GValueTransformFunc */
g_value_register_transform_func (G_TYPE_STRING, g_define_type_id,
string_to_profile_transform);
/* Register gst-specific GValue functions */
gst_value_register (&gstvtable);
g_once_init_leave (&g_define_type_id_init, g_define_type_id);
}
return g_define_type_id_init;
}
static void
_encoding_profile_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstEncodingProfile *prof = (GstEncodingProfile *) object;
switch (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;
}
}
static void
_encoding_profile_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstEncodingProfile *prof = (GstEncodingProfile *) object;
switch (prop_id) {
case PROP_RESTRICTION_CAPS:
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;
}
}
static void
gst_encoding_profile_finalize (GObject * object)
{
GstEncodingProfile *prof = (GstEncodingProfile *) object;
g_free (prof->name);
if (prof->format)
gst_caps_unref (prof->format);
g_free (prof->preset);
g_free (prof->description);
if (prof->restriction)
gst_caps_unref (prof->restriction);
g_free (prof->preset_name);
}
static void
gst_encoding_profile_class_init (GstEncodingProfileClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
gobject_class->finalize = gst_encoding_profile_finalize;
gobject_class->set_property = _encoding_profile_set_property;
gobject_class->get_property = _encoding_profile_get_property;
_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_EXPLICIT_NOTIFY);
/**
* 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);
}
/**
* gst_encoding_profile_get_name:
* @profile: a #GstEncodingProfile
*
* Returns: the name of the profile, can be %NULL.
*/
const gchar *
gst_encoding_profile_get_name (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
return profile->name;
}
/**
* gst_encoding_profile_get_description:
* @profile: a #GstEncodingProfile
*
* Returns: the description of the profile, can be %NULL.
*/
const gchar *
gst_encoding_profile_get_description (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
return profile->description;
}
/**
* gst_encoding_profile_get_format:
* @profile: a #GstEncodingProfile
*
* Returns: (transfer full): the #GstCaps corresponding to the media format used
* in the profile. Unref after usage.
*/
GstCaps *
gst_encoding_profile_get_format (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
return (profile->format ? gst_caps_ref (profile->format) : NULL);
}
/**
* gst_encoding_profile_get_preset:
* @profile: a #GstEncodingProfile
*
* Returns: the name of the #GstPreset to be used in the profile.
* This is the name that has been set when saving the preset.
*/
const gchar *
gst_encoding_profile_get_preset (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
return profile->preset;
}
/**
* gst_encoding_profile_get_preset_name:
* @profile: a #GstEncodingProfile
*
* Returns: the name of the #GstPreset factory to be used in the profile.
*/
const gchar *
gst_encoding_profile_get_preset_name (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
return profile->preset_name;
}
/**
* gst_encoding_profile_get_presence:
* @profile: a #GstEncodingProfile
*
* Returns: The number of times the profile is used in its parent
* container profile. If 0, it is not a mandatory stream.
*/
guint
gst_encoding_profile_get_presence (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), 0);
return profile->presence;
}
/**
* gst_encoding_profile_get_enabled:
* @profile: a #GstEncodingProfile
*
* Returns: Whether @profile is enabled or not
*
* Since: 1.6
*/
gboolean
gst_encoding_profile_is_enabled (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
return profile->enabled;
}
/**
* gst_encoding_profile_get_restriction:
* @profile: a #GstEncodingProfile
*
* Returns: (transfer full): The restriction #GstCaps to apply before the encoder
* that will be used in the profile. The fields present in restriction caps are
* properties of the raw stream (that is before encoding), such as height and
* width for video and depth and sampling rate for audio. Does not apply to
* #GstEncodingContainerProfile (since there is no corresponding raw stream).
* Can be %NULL. Unref after usage.
*/
GstCaps *
gst_encoding_profile_get_restriction (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
return (profile->restriction ? gst_caps_ref (profile->restriction) : NULL);
}
/**
* gst_encoding_profile_set_name:
* @profile: a #GstEncodingProfile
* @name: (allow-none): the name to set on the profile
*
* Set @name as the given name for the @profile. A copy of @name will be made
* internally.
*/
void
gst_encoding_profile_set_name (GstEncodingProfile * profile, const gchar * name)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
g_free (profile->name);
profile->name = g_strdup (name);
}
/**
* gst_encoding_profile_set_description:
* @profile: a #GstEncodingProfile
* @description: (allow-none): the description to set on the profile
*
* Set @description as the given description for the @profile. A copy of
* @description will be made internally.
*/
void
gst_encoding_profile_set_description (GstEncodingProfile * profile,
const gchar * description)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
g_free (profile->description);
profile->description = g_strdup (description);
}
/**
* gst_encoding_profile_set_format:
* @profile: a #GstEncodingProfile
* @format: (transfer none): the media format to use in the profile.
*
* Sets the media format used in the profile.
*/
void
gst_encoding_profile_set_format (GstEncodingProfile * profile, GstCaps * format)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
if (profile->format)
gst_caps_unref (profile->format);
profile->format = gst_caps_ref (format);
}
/**
* gst_encoding_profile_get_allow_dynamic_output:
* @profile: a #GstEncodingProfile
*
* Get whether the format that has been negotiated in at some point can be renegotiated
* later during the encoding.
*/
gboolean
gst_encoding_profile_get_allow_dynamic_output (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
return profile->allow_dynamic_output;
}
/**
* gst_encoding_profile_set_allow_dynamic_output:
* @profile: a #GstEncodingProfile
* @allow_dynamic_output: Whether the format that has been negotiated first can be renegotiated
* during the encoding
*
* Sets whether the format that has been negotiated in at some point can be renegotiated
* later during the encoding.
*/
void
gst_encoding_profile_set_allow_dynamic_output (GstEncodingProfile * profile,
gboolean allow_dynamic_output)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
profile->allow_dynamic_output = allow_dynamic_output;
}
/**
* gst_encoding_profile_get_single_segment:
* @profile: a #GstEncodingProfile
*
* Returns: #TRUE if the stream represented by @profile should use a single
* segment before the encoder, #FALSE otherwise. This means that buffers will be retimestamped
* and segments will be eat so as to appear as one segment.
*
* Since: 1.18
*/
gboolean
gst_encoding_profile_get_single_segment (GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
return profile->single_segment;
}
/**
* gst_encoding_profile_set_single_segment:
* @profile: a #GstEncodingProfile
* @single_segment: #TRUE if the stream represented by @profile should use a
* single segment before the encoder, #FALSE otherwise.
*
* If using a single segment, buffers will be retimestamped and segments will be
* eat so as to appear as one segment.
*
* > *NOTE*: Single segment is not property supported when using
* > #encodebin:avoid-reencoding
*
* Since: 1.18
*/
void
gst_encoding_profile_set_single_segment (GstEncodingProfile * profile,
gboolean single_segment)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
profile->single_segment = single_segment;
}
/**
* gst_encoding_profile_set_preset:
* @profile: a #GstEncodingProfile
* @preset: (nullable): the element preset to use
*
* Sets the name of the #GstElement that implements the #GstPreset interface
* to use for the profile.
* This is the name that has been set when saving the preset.
*/
void
gst_encoding_profile_set_preset (GstEncodingProfile * profile,
const gchar * preset)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
g_free (profile->preset);
profile->preset = g_strdup (preset);
}
/**
* gst_encoding_profile_set_preset_name:
* @profile: a #GstEncodingProfile
* @preset_name: (allow-none): The name of the preset to use in this @profile.
*
* Sets the name of the #GstPreset's factory to be used in the profile.
*/
void
gst_encoding_profile_set_preset_name (GstEncodingProfile * profile,
const gchar * preset_name)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
g_free (profile->preset_name);
profile->preset_name = g_strdup (preset_name);
}
/**
* gst_encoding_profile_set_presence:
* @profile: a #GstEncodingProfile
* @presence: the number of time the profile can be used
*
* Set the number of time the profile is used in its parent
* container profile. If 0, it is not a mandatory stream
*/
void
gst_encoding_profile_set_presence (GstEncodingProfile * profile, guint presence)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
profile->presence = presence;
}
/**
* gst_encoding_profile_set_enabled:
* @profile: a #GstEncodingProfile
* @enabled: %FALSE to disable @profile, %TRUE to enable it
*
* Set whether the profile should be used or not.
*
* Since: 1.6
*/
void
gst_encoding_profile_set_enabled (GstEncodingProfile * profile,
gboolean enabled)
{
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
profile->enabled = enabled;
}
/**
* gst_encoding_profile_set_restriction:
* @profile: a #GstEncodingProfile
* @restriction: (allow-none) (transfer full): the restriction to apply
*
* Set the restriction #GstCaps to apply before the encoder
* that will be used in the profile. See gst_encoding_profile_get_restriction()
* for more about restrictions. Does not apply to #GstEncodingContainerProfile.
*/
void
gst_encoding_profile_set_restriction (GstEncodingProfile * profile,
GstCaps * restriction)
{
g_return_if_fail (restriction == NULL || GST_IS_CAPS (restriction));
g_return_if_fail (GST_IS_ENCODING_PROFILE (profile));
if (profile->restriction)
gst_caps_unref (profile->restriction);
profile->restriction = restriction;
g_object_notify_by_pspec (G_OBJECT (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
{
GstEncodingProfile parent;
GList *encodingprofiles;
};
struct _GstEncodingContainerProfileClass
{
GstEncodingProfileClass parent;
};
G_DEFINE_TYPE (GstEncodingContainerProfile, gst_encoding_container_profile,
GST_TYPE_ENCODING_PROFILE);
static void
gst_encoding_container_profile_init (GstEncodingContainerProfile * prof)
{
/* Nothing to initialize */
}
static void
gst_encoding_container_profile_finalize (GObject * object)
{
GstEncodingContainerProfile *prof = (GstEncodingContainerProfile *) object;
g_list_foreach (prof->encodingprofiles, (GFunc) g_object_unref, NULL);
g_list_free (prof->encodingprofiles);
G_OBJECT_CLASS (gst_encoding_container_profile_parent_class)->finalize
((GObject *) prof);
}
static void
gst_encoding_container_profile_copy (GstEncodingProfile * profile,
GstEncodingProfile * copy_profile)
{
GstEncodingContainerProfile *self = GST_ENCODING_CONTAINER_PROFILE (profile),
*copy = GST_ENCODING_CONTAINER_PROFILE (copy_profile);
GList *tmp;
for (tmp = self->encodingprofiles; tmp; tmp = tmp->next) {
gst_encoding_container_profile_add_profile (copy,
gst_encoding_profile_copy (tmp->data));
}
}
static void
gst_encoding_container_profile_class_init (GstEncodingContainerProfileClass * k)
{
GObjectClass *gobject_class = (GObjectClass *) k;
gobject_class->finalize = gst_encoding_container_profile_finalize;
((GstEncodingProfileClass *) k)->copy = gst_encoding_container_profile_copy;
}
/**
* gst_encoding_container_profile_get_profiles:
* @profile: a #GstEncodingContainerProfile
*
* Returns: (element-type GstPbutils.EncodingProfile) (transfer none):
* the list of contained #GstEncodingProfile.
*/
const GList *
gst_encoding_container_profile_get_profiles (GstEncodingContainerProfile *
profile)
{
g_return_val_if_fail (GST_IS_ENCODING_CONTAINER_PROFILE (profile), NULL);
return profile->encodingprofiles;
}
/* Video profiles */
struct _GstEncodingVideoProfile
{
GstEncodingProfile parent;
guint pass;
gboolean variableframerate;
};
struct _GstEncodingVideoProfileClass
{
GstEncodingProfileClass parent;
};
G_DEFINE_TYPE (GstEncodingVideoProfile, gst_encoding_video_profile,
GST_TYPE_ENCODING_PROFILE);
static void
gst_encoding_video_profile_copy (GstEncodingProfile * profile,
GstEncodingProfile * copy_profile)
{
GstEncodingVideoProfile *self = GST_ENCODING_VIDEO_PROFILE (profile),
*copy = GST_ENCODING_VIDEO_PROFILE (copy_profile);
copy->pass = self->pass;
copy->variableframerate = self->variableframerate;
}
static void
gst_encoding_video_profile_init (GstEncodingVideoProfile * prof)
{
/* Nothing to initialize */
}
static void
gst_encoding_video_profile_class_init (GstEncodingVideoProfileClass * klass)
{
((GstEncodingProfileClass *) klass)->copy = gst_encoding_video_profile_copy;
}
/**
* gst_encoding_video_profile_get_pass:
* @prof: a #GstEncodingVideoProfile
*
* Get the pass number if this is part of a multi-pass profile.
*
* Returns: The pass number. Starts at 1 for multi-pass. 0 if this is
* not a multi-pass profile
*/
guint
gst_encoding_video_profile_get_pass (GstEncodingVideoProfile * prof)
{
g_return_val_if_fail (GST_IS_ENCODING_VIDEO_PROFILE (prof), 0);
return prof->pass;
}
/**
* gst_encoding_video_profile_get_variableframerate:
* @prof: a #GstEncodingVideoProfile
*
* > *NOTE*: Fixed framerate won't be enforced when #encodebin:avoid-reencoding
* > is set.
*
* Returns: Whether non-constant video framerate is allowed for encoding.
*/
gboolean
gst_encoding_video_profile_get_variableframerate (GstEncodingVideoProfile *
prof)
{
g_return_val_if_fail (GST_IS_ENCODING_VIDEO_PROFILE (prof), FALSE);
return prof->variableframerate;
}
/**
* gst_encoding_video_profile_set_pass:
* @prof: a #GstEncodingVideoProfile
* @pass: the pass number for this profile
*
* Sets the pass number of this video profile. The first pass profile should have
* this value set to 1. If this video profile isn't part of a multi-pass profile,
* you may set it to 0 (the default value).
*/
void
gst_encoding_video_profile_set_pass (GstEncodingVideoProfile * prof, guint pass)
{
g_return_if_fail (GST_IS_ENCODING_VIDEO_PROFILE (prof));
prof->pass = pass;
}
/**
* gst_encoding_video_profile_set_variableframerate:
* @prof: a #GstEncodingVideoProfile
* @variableframerate: a boolean
*
* If set to %TRUE, then the incoming stream will be allowed to have non-constant
* framerate. If set to %FALSE (default value), then the incoming stream will
* be normalized by dropping/duplicating frames in order to produce a
* constance framerate.
*/
void
gst_encoding_video_profile_set_variableframerate (GstEncodingVideoProfile *
prof, gboolean variableframerate)
{
g_return_if_fail (GST_IS_ENCODING_VIDEO_PROFILE (prof));
prof->variableframerate = variableframerate;
}
/* Audio profiles */
struct _GstEncodingAudioProfile
{
GstEncodingProfile parent;
};
struct _GstEncodingAudioProfileClass
{
GstEncodingProfileClass parent;
};
G_DEFINE_TYPE (GstEncodingAudioProfile, gst_encoding_audio_profile,
GST_TYPE_ENCODING_PROFILE);
static void
gst_encoding_audio_profile_init (GstEncodingAudioProfile * prof)
{
/* Nothing to initialize */
}
static void
gst_encoding_audio_profile_class_init (GstEncodingAudioProfileClass * klass)
{
}
static inline gboolean
_gst_caps_is_equal_safe (GstCaps * a, GstCaps * b)
{
if (a == b)
return TRUE;
if ((a == NULL) || (b == NULL))
return FALSE;
return gst_caps_is_equal (a, b);
}
static gint
_compare_container_encoding_profiles (GstEncodingContainerProfile * ca,
GstEncodingContainerProfile * cb)
{
GList *tmp;
if (g_list_length (ca->encodingprofiles) !=
g_list_length (cb->encodingprofiles))
return -1;
for (tmp = ca->encodingprofiles; tmp; tmp = tmp->next) {
GstEncodingProfile *prof = (GstEncodingProfile *) tmp->data;
if (!gst_encoding_container_profile_contains_profile (ca, prof))
return -1;
}
return 0;
}
static gint
_compare_encoding_profiles (const GstEncodingProfile * a,
const GstEncodingProfile * b)
{
if ((G_TYPE_FROM_INSTANCE (a) != G_TYPE_FROM_INSTANCE (b)) ||
!_gst_caps_is_equal_safe (a->format, b->format) ||
(g_strcmp0 (a->preset, b->preset) != 0) ||
(g_strcmp0 (a->preset_name, b->preset_name) != 0) ||
(g_strcmp0 (a->name, b->name) != 0) ||
(g_strcmp0 (a->description, b->description) != 0))
return -1;
if (GST_IS_ENCODING_CONTAINER_PROFILE (a))
return
_compare_container_encoding_profiles (GST_ENCODING_CONTAINER_PROFILE
(a), GST_ENCODING_CONTAINER_PROFILE (b));
if (GST_IS_ENCODING_VIDEO_PROFILE (a)) {
GstEncodingVideoProfile *va = (GstEncodingVideoProfile *) a;
GstEncodingVideoProfile *vb = (GstEncodingVideoProfile *) b;
if ((va->pass != vb->pass)
|| (va->variableframerate != vb->variableframerate))
return -1;
}
return 0;
}
/**
* gst_encoding_container_profile_contains_profile:
* @container: a #GstEncodingContainerProfile
* @profile: a #GstEncodingProfile
*
* Checks if @container contains a #GstEncodingProfile identical to
* @profile.
*
* Returns: %TRUE if @container contains a #GstEncodingProfile identical
* to @profile, else %FALSE.
*/
gboolean
gst_encoding_container_profile_contains_profile (GstEncodingContainerProfile *
container, GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_CONTAINER_PROFILE (container), FALSE);
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
return (g_list_find_custom (container->encodingprofiles, profile,
(GCompareFunc) _compare_encoding_profiles) != NULL);
}
/**
* gst_encoding_container_profile_add_profile:
* @container: the #GstEncodingContainerProfile to use
* @profile: (transfer full): the #GstEncodingProfile to add.
*
* Add a #GstEncodingProfile to the list of profiles handled by @container.
*
* No copy of @profile will be made, if you wish to use it elsewhere after this
* method you should increment its reference count.
*
* Returns: %TRUE if the @stream was properly added, else %FALSE.
*/
gboolean
gst_encoding_container_profile_add_profile (GstEncodingContainerProfile *
container, GstEncodingProfile * profile)
{
g_return_val_if_fail (GST_IS_ENCODING_CONTAINER_PROFILE (container), FALSE);
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
container->encodingprofiles =
g_list_append (container->encodingprofiles, profile);
return TRUE;
}
static GstEncodingProfile *
common_creation (GType objtype, GstCaps * format, const gchar * preset,
const gchar * name, const gchar * description, GstCaps * restriction,
guint presence)
{
GstEncodingProfile *prof;
prof = (GstEncodingProfile *) g_object_new (objtype, NULL);
if (name)
prof->name = g_strdup (name);
if (description)
prof->description = g_strdup (description);
if (preset)
prof->preset = g_strdup (preset);
if (format)
prof->format = gst_caps_ref (format);
if (restriction)
prof->restriction = gst_caps_ref (restriction);
prof->presence = presence;
prof->preset_name = NULL;
prof->allow_dynamic_output = TRUE;
prof->enabled = TRUE;
return prof;
}
/**
* gst_encoding_container_profile_new:
* @name: (allow-none): The name of the container profile, can be %NULL
* @description: (allow-none): The description of the container profile,
* can be %NULL
* @format: (transfer none): The format to use for this profile
* @preset: (allow-none): The preset to use for this profile.
*
* Creates a new #GstEncodingContainerProfile.
*
* Returns: The newly created #GstEncodingContainerProfile.
*/
GstEncodingContainerProfile *
gst_encoding_container_profile_new (const gchar * name,
const gchar * description, GstCaps * format, const gchar * preset)
{
g_return_val_if_fail (GST_IS_CAPS (format), NULL);
return (GstEncodingContainerProfile *)
common_creation (GST_TYPE_ENCODING_CONTAINER_PROFILE, format, preset,
name, description, NULL, 0);
}
/**
* gst_encoding_video_profile_new:
* @format: (transfer none): the #GstCaps
* @preset: (allow-none): the preset(s) to use on the encoder, can be %NULL
* @restriction: (allow-none): the #GstCaps used to restrict the input to the encoder, can be
* NULL. See gst_encoding_profile_get_restriction() for more details.
* @presence: the number of time this stream must be used. 0 means any number of
* times (including never)
*
* Creates a new #GstEncodingVideoProfile
*
* All provided allocatable arguments will be internally copied, so can be
* safely freed/unreferenced after calling this method.
*
* If you wish to control the pass number (in case of multi-pass scenarios),
* please refer to the gst_encoding_video_profile_set_pass() documentation.
*
* If you wish to use/force a constant framerate please refer to the
* gst_encoding_video_profile_set_variableframerate() documentation.
*
* Returns: the newly created #GstEncodingVideoProfile.
*/
GstEncodingVideoProfile *
gst_encoding_video_profile_new (GstCaps * format, const gchar * preset,
GstCaps * restriction, guint presence)
{
return (GstEncodingVideoProfile *)
common_creation (GST_TYPE_ENCODING_VIDEO_PROFILE, format, preset, NULL,
NULL, restriction, presence);
}
/**
* gst_encoding_audio_profile_new:
* @format: (transfer none): the #GstCaps
* @preset: (allow-none): the preset(s) to use on the encoder, can be %NULL
* @restriction: (allow-none): the #GstCaps used to restrict the input to the encoder, can be
* NULL. See gst_encoding_profile_get_restriction() for more details.
* @presence: the number of time this stream must be used. 0 means any number of
* times (including never)
*
* Creates a new #GstEncodingAudioProfile
*
* All provided allocatable arguments will be internally copied, so can be
* safely freed/unreferenced after calling this method.
*
* Returns: the newly created #GstEncodingAudioProfile.
*/
GstEncodingAudioProfile *
gst_encoding_audio_profile_new (GstCaps * format, const gchar * preset,
GstCaps * restriction, guint presence)
{
return (GstEncodingAudioProfile *)
common_creation (GST_TYPE_ENCODING_AUDIO_PROFILE, format, preset, NULL,
NULL, restriction, presence);
}
/**
* gst_encoding_profile_is_equal:
* @a: a #GstEncodingProfile
* @b: a #GstEncodingProfile
*
* Checks whether the two #GstEncodingProfile are equal
*
* Returns: %TRUE if @a and @b are equal, else %FALSE.
*/
gboolean
gst_encoding_profile_is_equal (GstEncodingProfile * a, GstEncodingProfile * b)
{
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (a), FALSE);
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (b), FALSE);
return (_compare_encoding_profiles (a, b) == 0);
}
/**
* gst_encoding_profile_get_input_caps:
* @profile: a #GstEncodingProfile
*
* Computes the full output caps that this @profile will be able to consume.
*
* Returns: (transfer full): The full caps the given @profile can consume. Call
* gst_caps_unref() when you are done with the caps.
*/
GstCaps *
gst_encoding_profile_get_input_caps (GstEncodingProfile * profile)
{
GstCaps *out, *tmp;
GList *ltmp;
GstStructure *st, *outst;
GQuark out_name;
guint i, len;
GstCaps *fcaps;
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
GstCaps *res = gst_caps_new_empty ();
for (ltmp = GST_ENCODING_CONTAINER_PROFILE (profile)->encodingprofiles;
ltmp; ltmp = ltmp->next) {
GstEncodingProfile *sprof = (GstEncodingProfile *) ltmp->data;
res = gst_caps_merge (res, gst_encoding_profile_get_input_caps (sprof));
}
return res;
}
fcaps = profile->format;
/* fast-path */
if ((profile->restriction == NULL) || gst_caps_is_any (profile->restriction))
return gst_caps_ref (fcaps);
/* Combine the format with the restriction caps */
outst = gst_caps_get_structure (fcaps, 0);
out_name = gst_structure_get_name_id (outst);
tmp = gst_caps_new_empty ();
len = gst_caps_get_size (profile->restriction);
for (i = 0; i < len; i++) {
st = gst_structure_copy (gst_caps_get_structure (profile->restriction, i));
st->name = out_name;
gst_caps_append_structure (tmp, st);
}
out = gst_caps_intersect (tmp, fcaps);
gst_caps_unref (tmp);
return out;
}
/**
* gst_encoding_profile_get_type_nick:
* @profile: a #GstEncodingProfile
*
* Returns: the human-readable name of the type of @profile.
*/
const gchar *
gst_encoding_profile_get_type_nick (GstEncodingProfile * profile)
{
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile))
return "container";
if (GST_IS_ENCODING_VIDEO_PROFILE (profile))
return "video";
if (GST_IS_ENCODING_AUDIO_PROFILE (profile))
return "audio";
return NULL;
}
extern const gchar *pb_utils_get_file_extension_from_caps (const GstCaps *
caps);
gboolean pb_utils_is_tag (const GstCaps * caps);
static gboolean
gst_encoding_profile_has_format (GstEncodingProfile * profile,
const gchar * media_type)
{
GstCaps *caps;
gboolean ret;
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), FALSE);
caps = gst_encoding_profile_get_format (profile);
ret = gst_structure_has_name (gst_caps_get_structure (caps, 0), media_type);
gst_caps_unref (caps);
return ret;
}
static gboolean
gst_encoding_container_profile_has_video (GstEncodingContainerProfile * profile)
{
const GList *l;
g_return_val_if_fail (GST_IS_ENCODING_CONTAINER_PROFILE (profile), FALSE);
for (l = profile->encodingprofiles; l != NULL; l = l->next) {
if (GST_IS_ENCODING_VIDEO_PROFILE (l->data))
return TRUE;
if (GST_IS_ENCODING_CONTAINER_PROFILE (l->data) &&
gst_encoding_container_profile_has_video (l->data))
return TRUE;
}
return FALSE;
}
/**
* gst_encoding_profile_get_file_extension:
* @profile: a #GstEncodingProfile
*
* Returns: a suitable file extension for @profile, or NULL.
*/
const gchar *
gst_encoding_profile_get_file_extension (GstEncodingProfile * profile)
{
GstEncodingContainerProfile *cprofile;
const gchar *ext = NULL;
gboolean has_video;
GstCaps *caps;
guint num_children;
g_return_val_if_fail (GST_IS_ENCODING_PROFILE (profile), NULL);
caps = gst_encoding_profile_get_format (profile);
ext = pb_utils_get_file_extension_from_caps (caps);
if (!GST_IS_ENCODING_CONTAINER_PROFILE (profile))
goto done;
cprofile = GST_ENCODING_CONTAINER_PROFILE (profile);
num_children = g_list_length (cprofile->encodingprofiles);
/* if it's a tag container profile (e.g. id3mux/apemux), we need
* to look at what's inside it */
if (pb_utils_is_tag (caps)) {
GST_DEBUG ("tag container profile");
if (num_children == 1) {
GstEncodingProfile *child_profile = cprofile->encodingprofiles->data;
ext = gst_encoding_profile_get_file_extension (child_profile);
} else {
GST_WARNING ("expected exactly one child profile with tag profile");
}
goto done;
}
if (num_children == 0)
goto done;
/* special cases */
has_video = gst_encoding_container_profile_has_video (cprofile);
/* Ogg */
if (g_strcmp0 (ext, "ogg") == 0) {
/* ogg with video => .ogv */
if (has_video) {
ext = "ogv";
goto done;
}
/* ogg with just speex audio => .spx */
if (num_children == 1) {
GstEncodingProfile *child_profile = cprofile->encodingprofiles->data;
if (GST_IS_ENCODING_AUDIO_PROFILE (child_profile) &&
gst_encoding_profile_has_format (child_profile, "audio/x-speex")) {
ext = "spx";
goto done;
}
}
/* does anyone actually use .oga for ogg audio files? */
goto done;
}
/* Matroska */
if (has_video && g_strcmp0 (ext, "mka") == 0) {
ext = "mkv";
goto done;
}
/* Windows Media / ASF */
if (gst_encoding_profile_has_format (profile, "video/x-ms-asf")) {
const GList *l;
guint num_wmv = 0, num_wma = 0, num_other = 0;
for (l = cprofile->encodingprofiles; l != NULL; l = l->next) {
if (gst_encoding_profile_has_format (l->data, "video/x-wmv"))
++num_wmv;
else if (gst_encoding_profile_has_format (l->data, "audio/x-wma"))
++num_wma;
else
++num_other;
}
if (num_other > 0)
ext = "asf";
else if (num_wmv > 0)
ext = "wmv";
else if (num_wma > 0)
ext = "wma";
goto done;
}
done:
GST_INFO ("caps %" GST_PTR_FORMAT ", ext: %s", caps, GST_STR_NULL (ext));
gst_caps_unref (caps);
return ext;
}
/**
* gst_encoding_profile_find:
* @targetname: (transfer none): The name of the target
* @profilename: (transfer none) (allow-none): The name of the profile, if %NULL
* provided, it will default to the encoding profile called `default`.
* @category: (transfer none) (allow-none): The target category. Can be %NULL
*
* Find the #GstEncodingProfile with the specified name and category.
*
* Returns: (transfer full): The matching #GstEncodingProfile or %NULL.
*/
GstEncodingProfile *
gst_encoding_profile_find (const gchar * targetname, const gchar * profilename,
const gchar * category)
{
GstEncodingProfile *res = NULL;
GstEncodingTarget *target;
g_return_val_if_fail (targetname != NULL, NULL);
target = gst_encoding_target_load (targetname, category, NULL);
if (target) {
res =
gst_encoding_target_get_profile (target,
profilename ? profilename : "default");
gst_encoding_target_unref (target);
}
return res;
}
static GstEncodingProfile *
combo_search (const gchar * pname)
{
GstEncodingProfile *res = NULL;
gchar **split;
gint split_length;
/* Splitup */
split = g_strsplit (pname, "/", 3);
split_length = g_strv_length (split);
if (split_length > 3)
goto done;
res = gst_encoding_profile_find (split[0],
split_length == 2 ? split[1] : NULL, split_length == 3 ? split[2] : NULL);
done:
g_strfreev (split);
return res;
}
static GstCaps *
get_profile_format_from_possible_factory_name (const gchar * factory_desc,
gchar ** new_factory_name, GstCaps ** restrictions,
gboolean * is_rendering_muxer)
{
GList *tmp;
GstCaps *caps = NULL, *tmpcaps = gst_caps_from_string (factory_desc);
GstStructure *tmpstruct;
GstElementFactory *fact = NULL;
if (is_rendering_muxer)
*is_rendering_muxer = FALSE;
*new_factory_name = NULL;
if (gst_caps_get_size (tmpcaps) != 1)
goto done;
tmpstruct = gst_caps_get_structure (tmpcaps, 0);
fact = gst_element_factory_find (gst_structure_get_name (tmpstruct));
if (!fact)
goto done;
if (!gst_element_factory_list_is_type (fact,
GST_ELEMENT_FACTORY_TYPE_ENCODER | GST_ELEMENT_FACTORY_TYPE_MUXER)) {
GST_ERROR_OBJECT (fact,
"is not an encoder or muxer, it can't be"
" used in an encoding profile.");
goto done;
}
for (tmp = (GList *) gst_element_factory_get_static_pad_templates (fact);
tmp; tmp = tmp->next) {
GstStaticPadTemplate *templ = ((GstStaticPadTemplate *) tmp->data);
if (templ->direction == GST_PAD_SRC) {
GstCaps *tmpcaps = gst_static_caps_get (&templ->static_caps);
if (gst_caps_get_size (tmpcaps) > 0)
caps =
gst_caps_new_empty_simple (gst_structure_get_name
(gst_caps_get_structure (tmpcaps, 0)));
gst_caps_unref (tmpcaps);
if (caps)
break;
}
}
if (caps) {
*new_factory_name = g_strdup (gst_structure_get_name (tmpstruct));
if (gst_structure_n_fields (tmpstruct) && restrictions) {
const gchar *sname =
gst_structure_get_name (gst_caps_get_structure (caps, 0));
if (g_str_has_prefix (sname, "audio/"))
gst_structure_set_name (tmpstruct, "audio/x-raw");
else if (g_str_has_prefix (sname, "video/") ||
g_str_has_prefix (sname, "image/"))
gst_structure_set_name (tmpstruct, "video/x-raw");
*restrictions = tmpcaps;
tmpcaps = NULL;
}
} else if (gst_element_factory_list_is_type (fact,
GST_ELEMENT_FACTORY_TYPE_MUXER)) {
*new_factory_name = g_strdup (gst_structure_get_name (tmpstruct));
caps = gst_caps_ref (gst_caps_new_empty ());
if (is_rendering_muxer)
*is_rendering_muxer = TRUE;
}
done:
if (fact)
gst_object_unref (fact);
if (tmpcaps)
gst_caps_unref (tmpcaps);
return caps;
}
static GstEncodingProfile *
create_encoding_profile_from_caps (GstCaps * caps, gchar * preset_name,
GstCaps * restrictioncaps, gint presence, gboolean single_segment,
gchar * factory_name, GList * muxers_and_encoders, GstCaps * raw_audio_caps,
GstCaps * raw_video_caps, gboolean is_rendering_muxer)
{
GstEncodingProfile *profile = NULL;
GList *factories = NULL;
gboolean is_raw_audio = FALSE, is_raw_video = FALSE;
if (is_rendering_muxer) {
profile =
GST_ENCODING_PROFILE (gst_encoding_container_profile_new
("User profile", "User profile", caps, NULL));
goto done;
}
if (gst_caps_can_intersect (raw_audio_caps, caps)) {
is_raw_audio = TRUE;
} else if (gst_caps_can_intersect (raw_video_caps, caps)) {
is_raw_video = TRUE;
} else {
factories = gst_element_factory_list_filter (muxers_and_encoders, caps,
GST_PAD_SRC, FALSE);
if (!factories) {
GST_INFO ("Could not find factory for %" GST_PTR_FORMAT, caps);
return NULL;
}
}
if (is_raw_audio || (factories
&& gst_element_factory_list_is_type (factories->data,
GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER)))
profile =
GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps, preset_name,
restrictioncaps, presence));
else if (is_raw_video || (factories
&& gst_element_factory_list_is_type (factories->data,
GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER)))
profile =
GST_ENCODING_PROFILE (gst_encoding_video_profile_new (caps, preset_name,
restrictioncaps, presence));
else if (gst_element_factory_list_is_type (factories->data,
GST_ELEMENT_FACTORY_TYPE_MUXER))
profile =
GST_ENCODING_PROFILE (gst_encoding_container_profile_new
("User profile", "User profile", caps, NULL));
if (factories)
gst_plugin_feature_list_free (factories);
done:
if (factory_name && profile)
gst_encoding_profile_set_preset_name (profile, factory_name);
gst_encoding_profile_set_single_segment (profile, single_segment);
g_free (factory_name);
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,
GstCaps * raw_video_caps)
{
GstCaps *caps;
guint presence = 0;
gboolean single_segment = FALSE;
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;
restriction_format = g_strsplit (serialized_profile, "->", 0);
if (restriction_format[1]) {
restrictioncaps = gst_caps_from_string (restriction_format[0]);
strcaps = g_strdup (restriction_format[1]);
} else {
restrictioncaps = NULL;
strcaps = g_strdup (restriction_format[0]);
}
g_strfreev (restriction_format);
preset_v = g_strsplit (strcaps, "+", 0);
if (preset_v[1]) {
strpresence = preset_v[1];
g_free (strcaps);
strcaps = g_strdup (preset_v[0]);
} else {
strpresence = preset_v[0];
}
strprops_v = g_strsplit (strpresence, "|", 0);
if (strprops_v[1]) { /* We have a properties */
gchar *endptr;
guint propi;
if (preset_v[1]) { /* We have preset and properties */
preset_name = g_strdup (strprops_v[0]);
} else { /* We have a properties but no preset */
g_free (strcaps);
strcaps = g_strdup (strprops_v[0]);
}
for (propi = 1; strprops_v[propi]; propi++) {
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",
prop);
return NULL;
}
if (!propv[1]) {
presence_str = propv[0];
} else if (!g_strcmp0 (propv[0], "presence")) {
presence_str = propv[1];
} else if (!g_strcmp0 (propv[0], "variable-framerate")) {
variable_framerate = g_strdup (propv[1]);
} else if (!g_strcmp0 (propv[0], "single-segment")) {
GValue v = G_VALUE_INIT;
g_value_init (&v, G_TYPE_BOOLEAN);
if (!gst_value_deserialize (&v, propv[1])) {
g_warning ("Invalid value for property 'single-segment': %s",
propv[1]);
return NULL;
}
single_segment = g_value_get_boolean (&v);
g_value_reset (&v);
} else {
g_warning ("Unsupported property: %s", propv[0]);
return NULL;
}
if (presence_str) {
presence = g_ascii_strtoll (presence_str, &endptr, 10);
if (endptr == strprops_v[1]) {
g_warning ("Wrong presence %s", presence_str);
return NULL;
}
}
}
} else { /* We have no presence */
if (preset_v[1]) { /* Not presence but preset */
preset_name = g_strdup (preset_v[1]);
g_free (strcaps);
strcaps = g_strdup (preset_v[0]);
} /* Else we have no presence nor preset */
}
g_strfreev (strprops_v);
g_strfreev (preset_v);
GST_DEBUG ("Creating preset with restrictions: %" GST_PTR_FORMAT
", caps: %s, preset %s, presence %d", restrictioncaps, strcaps,
preset_name ? preset_name : "none", presence);
caps = gst_caps_from_string (strcaps);
if (caps) {
profile = create_encoding_profile_from_caps (caps, preset_name,
restrictioncaps, presence, single_segment, NULL, muxers_and_encoders,
raw_audio_caps, raw_video_caps, FALSE);
gst_caps_unref (caps);
}
if (!profile) {
gboolean is_rendering_muxer;
caps = get_profile_format_from_possible_factory_name (strcaps,
&factory_name, restrictioncaps ? NULL : &restrictioncaps,
&is_rendering_muxer);
if (caps) {
profile = create_encoding_profile_from_caps (caps, preset_name,
restrictioncaps, presence, single_segment, factory_name,
muxers_and_encoders, raw_audio_caps, raw_video_caps,
is_rendering_muxer);
gst_caps_unref (caps);
}
}
g_free (preset_name);
g_free (strcaps);
if (restrictioncaps)
gst_caps_unref (restrictioncaps);
if (variable_framerate) {
if (GST_IS_ENCODING_VIDEO_PROFILE (profile)) {
GValue v = {
0,
};
g_value_init (&v, G_TYPE_BOOLEAN);
if (gst_value_deserialize (&v, variable_framerate)) {
gst_encoding_video_profile_set_variableframerate
(GST_ENCODING_VIDEO_PROFILE (profile), g_value_get_boolean (&v));
} else {
GST_WARNING ("Invalid value for variable_framerate: %s",
variable_framerate);
}
g_value_reset (&v);
} else {
GST_WARNING
("Variable framerate specified on a non video encoding profile");
}
g_free (variable_framerate);
}
if (profile == NULL) {
GST_ERROR ("No way to create a profile for description: %s",
serialized_profile);
return NULL;
}
if (element_properties)
gst_encoding_profile_set_element_properties (profile, element_properties);
return profile;
}
static GstEncodingProfile *
parse_encoding_profile (const gchar * value)
{
GstEncodingProfile *res = NULL;
gchar *caps_str = NULL;
gchar **strcaps_v =
g_regex_split_simple ("(?<!\\\\)(?:\\\\\\\\)*:", value, 0, 0);
guint i;
GList *muxers_and_encoders =
gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_ENCODER |
GST_ELEMENT_FACTORY_TYPE_MUXER,
GST_RANK_MARGINAL);
GstCaps *raw_video_caps = gst_caps_new_empty_simple ("video/x-raw");
GstCaps *raw_audio_caps = gst_caps_new_empty_simple ("audio/x-raw");
/* The regex returns NULL if no ":" found, handle that case. */
if (strcaps_v == NULL)
strcaps_v = g_strsplit (value, ":", 0);
for (i = 0; strcaps_v[i] && *strcaps_v[i]; i++) {
GstEncodingProfile *profile;
caps_str = g_strcompress (strcaps_v[i]);
profile =
create_encoding_stream_profile (caps_str, muxers_and_encoders,
raw_audio_caps, raw_video_caps);
if (!profile) {
GST_ERROR ("Could not create profile for caps: %s", caps_str);
goto error;
}
if (res) {
if (!GST_IS_ENCODING_CONTAINER_PROFILE (res)) {
GST_ERROR ("The first described encoding profile was not a container"
" but you are trying to add more profiles to it. This is not possible");
goto error;
}
if (!gst_encoding_container_profile_add_profile
(GST_ENCODING_CONTAINER_PROFILE (res), profile)) {
GST_ERROR ("Can not add profile for caps: %s", caps_str);
goto error;
}
} else {
res = profile;
}
g_clear_pointer (&caps_str, g_free);
}
done:
g_free (caps_str);
g_strfreev (strcaps_v);
gst_caps_unref (raw_audio_caps);
gst_caps_unref (raw_video_caps);
gst_plugin_feature_list_free (muxers_and_encoders);
return res;
error:
g_clear_object (&res);
goto done;
}
static GstEncodingProfile *
profile_from_string (const gchar * string)
{
GstEncodingProfile *profile;
gchar *filename_end;
profile = combo_search (string);
if (profile)
return profile;
filename_end = g_strrstr (string, ".gep");
if (filename_end) {
GstEncodingTarget *target;
gchar *profilename = NULL, *filename;
if (filename_end[4] == ':')
profilename = g_strdup (&filename_end[5]);
if (filename_end[4] == '\0' || profilename) {
filename = g_strndup (string, filename_end - string + strlen (".gep"));
target = gst_encoding_target_load_from_file (filename, NULL);
if (target) {
profile = gst_encoding_target_get_profile (target,
profilename ? profilename : "default");
gst_encoding_target_unref (target);
}
g_free (profilename);
g_free (filename);
}
}
if (!profile)
profile = parse_encoding_profile (string);
return profile;
}
/* GValue transform function */
static void
string_to_profile_transform (const GValue * src_value, GValue * dest_value)
{
const gchar *profilename;
GstEncodingProfile *profile;
profilename = g_value_get_string (src_value);
profile = profile_from_string (profilename);
if (profile)
g_value_take_object (dest_value, (GObject *) profile);
}
static void
serialize_profile (GString * res, GstEncodingProfile * profile)
{
gchar *tmp;
if (res->len)
g_string_append_c (res, ':');
if (profile->restriction) {
tmp = gst_caps_to_string (profile->restriction);
g_string_append_printf (res, "%s->", tmp);
g_free (tmp);
}
tmp = gst_caps_to_string (profile->format);
g_string_append (res, tmp);
if (profile->presence)
g_string_append_printf (res, "|presence=%d", profile->presence);
if (profile->single_segment)
g_string_append_printf (res, "%ssingle-segment=true",
profile->presence ? "" : "|");
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
GList *tmp;
for (tmp = GST_ENCODING_CONTAINER_PROFILE (profile)->encodingprofiles; tmp;
tmp = tmp->next)
serialize_profile (res, tmp->data);
}
}
static gchar *
gst_encoding_profile_serialize_valfunc (GValue * value)
{
GString *res = g_string_new (NULL);
GstEncodingProfile *profile = g_value_get_object (value);
serialize_profile (res, profile);
return g_string_free (res, FALSE);
}
static gboolean
gst_encoding_profile_deserialize_valfunc (GValue * value, const gchar * s)
{
GstEncodingProfile *profile;
profile = profile_from_string (s);
if (profile) {
g_value_take_object (value, (GObject *) profile);
return TRUE;
}
return FALSE;
}
static GstEncodingProfile *
create_stream_profile_recurse (GstEncodingProfile * toplevel,
GstDiscovererStreamInfo * sinfo)
{
GstEncodingProfile *profile = NULL;
GstStructure *s;
GstCaps *caps;
caps = gst_discoverer_stream_info_get_caps (sinfo);
/* Should unify this with copy_and_clean_caps() */
caps = gst_caps_make_writable (caps);
s = gst_caps_get_structure (caps, 0);
gst_structure_remove_fields (s, "codec_data", "streamheader", "parsed",
"colorimetry", "framed", "stream-format", "alignment", "tier", "level",
"profile", "chroma-format", "bit-depth-luma", "bit-depth-chroma", NULL);
GST_LOG ("Stream: %" GST_PTR_FORMAT, caps);
if (GST_IS_DISCOVERER_AUDIO_INFO (sinfo)) {
profile =
(GstEncodingProfile *) gst_encoding_audio_profile_new (caps, NULL,
NULL, 0);
} else if (GST_IS_DISCOVERER_VIDEO_INFO (sinfo)) {
profile =
(GstEncodingProfile *) gst_encoding_video_profile_new (caps, NULL,
NULL, 0);
} else if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) {
GList *streams, *stream;
streams =
gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
(sinfo));
if (!toplevel || !GST_IS_ENCODING_CONTAINER_PROFILE (toplevel)) {
GstEncodingProfile *prev_toplevel = toplevel;
toplevel = (GstEncodingProfile *)
gst_encoding_container_profile_new ("auto-generated",
"Automatically generated from GstDiscovererInfo", caps, NULL);
if (prev_toplevel)
gst_encoding_container_profile_add_profile
(GST_ENCODING_CONTAINER_PROFILE (toplevel), prev_toplevel);
}
for (stream = streams; stream; stream = stream->next)
create_stream_profile_recurse (toplevel,
(GstDiscovererStreamInfo *) stream->data);
gst_discoverer_stream_info_list_free (streams);
} else {
GST_FIXME ("Ignoring stream of type '%s'",
g_type_name (G_OBJECT_TYPE (sinfo)));
/* subtitles or other ? ignore for now */
}
gst_caps_unref (caps);
if (profile) {
const gchar *stream_id = gst_discoverer_stream_info_get_stream_id (sinfo);
if (stream_id) {
const gchar *subid = strchr (stream_id, '/');
gst_encoding_profile_set_name (profile, subid ? subid : stream_id);
}
if (GST_IS_ENCODING_CONTAINER_PROFILE (toplevel))
gst_encoding_container_profile_add_profile ((GstEncodingContainerProfile
*)
toplevel, profile);
}
if (!toplevel && profile)
toplevel = profile;
sinfo = gst_discoverer_stream_info_get_next (sinfo);
if (sinfo)
return create_stream_profile_recurse (toplevel, sinfo);
return toplevel;
}
/**
* gst_encoding_profile_from_discoverer:
* @info: (transfer none): The #GstDiscovererInfo to read from
*
* Creates a #GstEncodingProfile matching the formats from the given
* #GstDiscovererInfo. Streams other than audio or video (eg,
* subtitles), are currently ignored.
*
* Returns: (transfer full): The new #GstEncodingProfile or %NULL.
*/
GstEncodingProfile *
gst_encoding_profile_from_discoverer (GstDiscovererInfo * info)
{
GstEncodingProfile *profile;
GstDiscovererStreamInfo *sinfo;
if (!info || gst_discoverer_info_get_result (info) != GST_DISCOVERER_OK)
return NULL;
sinfo = gst_discoverer_info_get_stream_info (info);
if (!sinfo)
return NULL;
profile = create_stream_profile_recurse (NULL, sinfo);
if (GST_IS_ENCODING_CONTAINER_PROFILE (profile)) {
if (!gst_encoding_container_profile_get_profiles
(GST_ENCODING_CONTAINER_PROFILE (profile))) {
GST_ERROR ("Failed to add any streams");
g_object_unref (profile);
return NULL;
}
}
return (GstEncodingProfile *) profile;
}
/**
* gst_encoding_profile_copy:
* @self: The #GstEncodingProfile to copy
*
* Makes a deep copy of @self
*
* Returns: (transfer full): The copy of @self
*
* Since: 1.12
*/
GstEncodingProfile *
gst_encoding_profile_copy (GstEncodingProfile * self)
{
GstEncodingProfileClass *klass =
(GstEncodingProfileClass *) G_OBJECT_GET_CLASS (self);
GstEncodingProfile *copy =
common_creation (G_OBJECT_TYPE (self), self->format, self->preset,
self->name, self->description, self->restriction, self->presence);
copy->enabled = self->enabled;
copy->allow_dynamic_output = self->allow_dynamic_output;
gst_encoding_profile_set_preset_name (copy, self->preset_name);
gst_encoding_profile_set_description (copy, self->description);
if (klass->copy)
klass->copy (self, copy);
return copy;
}