blob: 2f8e6fe018732262391d8133bda771ee863dbb99 [file] [log] [blame]
/* 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
* @short_description: Encoding profile library
*
* <refsect2>
* <para>
* Functions to create and handle encoding profiles.
* </para>
* <para>
* 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.
* </para>
* <para>
* #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.
* </para>
* <para>
* Unlike other systems, the encoding profiles do not specify which #GstElement to use
* for the various encoding and muxing steps, but instead relies on specifying the format
* one wishes to use.
* </para>
* <para>
* Encoding profiles can be created at runtime by the application or loaded from (and saved
* to) file using the #GstEncodingTarget API.
* </para>
* </refsect2>
*
* <refsect2 id="gst-validate-transcoding--encoding-profile">
* <title>The encoding profile serialization format</title>
* <para>This is the serialization format of a <link linkend="GstEncodingProfile"><type>GstEncodingProfile</type></link>.</para>
* <para>
* Internally the transcoding application uses <link linkend="GstEncodeBin"><type>GstEncodeBin</type></link>. <command>gst-validate-transcoding-&GST_API_VERSION;</command> uses its own
* serialization format to describe the <link linkend="GstEncodeBin--profile"><type>GstEncodeBin.profile</type></link>
* property of the encodebin.
* </para>
* <para>
* The simplest serialized profile looks like:
* </para>
* <informalexample>
* <programlisting>muxer_source_caps:videoencoder_source_caps:audioencoder_source_caps</programlisting>
* </informalexample>
* <para>
* For example to encode a stream into a WebM container, with an OGG audio stream and a VP8 video stream,
* the serialized <link linkend="GstEncodingProfile"><type>GstEncodingProfile</type></link> will look like:
* </para>
* <informalexample>
* <programlisting>video/webm:video/x-vp8:audio/x-vorbis</programlisting>
* </informalexample>
* <para>
* You can also set the preset name of the encoding profile using the caps+preset_name syntax as in:
* </para>
* <informalexample>
* <programlisting>video/webm:video/x-vp8+youtube-preset:audio/x-vorbis</programlisting>
* </informalexample>
* <para>
* Moreover, you can set the <link linkend="gst-encoding-profile-set-presence">presence</link> property of an
* encoding profile using the <code>|presence</code> syntax as in:
* </para>
* <informalexample>
* <programlisting>video/webm:video/x-vp8|1:audio/x-vorbis</programlisting>
* </informalexample>
* <para>
* This field allows you to specify how many times maximum a <link linkend="GstEncodingProfile"><type>GstEncodingProfile</type></link> can be used inside an encodebin.
* </para>
* <para>
* You can also use the <code>restriction_caps->encoded_format_caps</code> syntax to specify the
* <link linked="gst-encoding-profile-get-restriction">restriction caps</link>
* to be set on a <link linkend="GstEncodingProfile"><type>GstEncodingProfile</type></link>. It corresponds to the
* restriction <link linkend="GstCaps"><type>GstCaps</type></link> 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. This property does not
* make sense for muxers.
* </para>
* <para>
* 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:
* </para>
* <informalexample>
* <programlisting>video/webm:video/x-raw,width=1920,height=1080->video/x-vp8:audio/x-vorbis</programlisting>
* </informalexample>
* <refsect3>
* <title>Some serialized encoding formats examples:</title>
* <para>
* MP3 audio and H264 in MP4:
* </para>
* <informalexample>
* <programlisting>video/quicktime,variant=iso:video/x-h264:audio/mpeg,mpegversion=1,layer=3</programlisting>
* </informalexample>
* <para>
* Vorbis and theora in OGG:
* </para>
* <informalexample>
* <programlisting>application/ogg:video/x-theora:audio/x-vorbis</programlisting>
* </informalexample>
* <para>
* AC3 and H264 in MPEG-TS:
* </para>
* <informalexample>
* <programlisting>video/mpegts:video/x-h264:audio/x-ac3</programlisting>
* </informalexample>
* </refsect3>
* </refsect2>
* <refsect2>
* <title>Example: Creating a profile</title>
* <para>
* |[
* #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;
*}
*
*
* ]|
* </para>
* </refsect2>
* <refsect2>
* <title>Example: Using an encoder preset with a profile</title>
* <para>
* |[
* #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, "theorapreset", 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;
*}
*
*
* ]|
* </para>
* </refsect2>
* <refsect2>
* <title>Example: Listing categories, targets and profiles</title>
* <para>
* |[
* #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);
*
* ...
* ]|
* </para>
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "encoding-profile.h"
#include "encoding-target.h"
#include <string.h>
/* GstEncodingProfile API */
struct _GstEncodingProfile
{
GObject parent;
/*< public > */
gchar *name;
gchar *description;
GstCaps *format;
gchar *preset;
gchar *preset_name;
guint presence;
GstCaps *restriction;
gboolean allow_dynamic_output;
gboolean enabled;
};
struct _GstEncodingProfileClass
{
GObjectClass parent_class;
};
enum
{
FIRST_PROPERTY,
PROP_RESTRICTION_CAPS,
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 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 volatile gsize g_define_type_id__volatile = 0;
if (g_once_init_enter (&g_define_type_id__volatile)) {
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) NULL,
(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__volatile, g_define_type_id);
}
return g_define_type_id__volatile;
}
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;
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;
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_object_class_install_property (gobject_class,
PROP_RESTRICTION_CAPS, _properties[PROP_RESTRICTION_CAPS]);
}
/**
* 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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
return profile->presence;
}
/**
* gst_encoding_profile_get_enabled:
* @profile: a #GstEncodingProfile
*
* Returns: Whther @profile is enabled or not
*/
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)
{
return (profile->restriction ? gst_caps_ref (profile->restriction) : NULL);
}
/**
* gst_encoding_profile_set_name:
* @profile: a #GstEncodingProfile
* @name: 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_free (profile->name);
profile->name = g_strdup (name);
}
/**
* gst_encoding_profile_set_description:
* @profile: a #GstEncodingProfile
* @description: 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_free (profile->description);
profile->description = g_strdup (description);
}
/**
* gst_encoding_profile_set_format:
* @profile: a #GstEncodingProfile
* @format: 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)
{
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)
{
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)
{
profile->allow_dynamic_output = allow_dynamic_output;
}
/**
* gst_encoding_profile_set_preset:
* @profile: a #GstEncodingProfile
* @preset: 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_free (profile->preset);
profile->preset = g_strdup (preset);
}
/**
* gst_encoding_profile_set_preset_name:
* @profile: a #GstEncodingProfile
* @preset_name: 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_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)
{
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.
*/
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: (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)
{
if (profile->restriction)
gst_caps_unref (profile->restriction);
profile->restriction = restriction;
g_object_notify_by_pspec (G_OBJECT (profile),
_properties[PROP_RESTRICTION_CAPS]);
}
/* 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_class_init (GstEncodingContainerProfileClass * k)
{
GObjectClass *gobject_class = (GObjectClass *) k;
gobject_class->finalize = gst_encoding_container_profile_finalize;
}
/**
* 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)
{
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_init (GstEncodingVideoProfile * prof)
{
/* Nothing to initialize */
}
static void
gst_encoding_video_profile_class_init (GstEncodingVideoProfileClass * klass)
{
}
/**
* 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)
{
return prof->pass;
}
/**
* gst_encoding_video_profile_get_variableframerate:
* @prof: a #GstEncodingVideoProfile
*
* Returns: Whether non-constant video framerate is allowed for encoding.
*/
gboolean
gst_encoding_video_profile_get_variableframerate (GstEncodingVideoProfile *
prof)
{
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)
{
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)
{
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->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);
if (g_list_find_custom (container->encodingprofiles, profile,
(GCompareFunc) _compare_encoding_profiles)) {
GST_ERROR
("Encoding profile already contains an identical GstEncodingProfile");
return 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: 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: 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: 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)
{
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;
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;
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;
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 (strcmp (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 && strcmp (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;
gchar **split;
gint split_length;
/* Splitup */
split = g_strsplit (pname, "/", 3);
split_length = g_strv_length (split);
if (split_length != 2 && split_length != 3)
return NULL;
res = gst_encoding_profile_find (split[0], split[1],
split_length == 3 ? split[2] : NULL);
g_strfreev (split);
return res;
}
static GstEncodingProfile *
parse_encoding_profile (const gchar * value)
{
GstEncodingProfile *res;
gchar **strpresence_v, **strcaps_v = g_strsplit (value, ":", 0);
guint i;
if (strcaps_v[0] && *strcaps_v[0]) {
GstCaps *caps = gst_caps_from_string (strcaps_v[0]);
if (caps == NULL) {
GST_ERROR ("Could not parse caps %s", strcaps_v[0]);
return NULL;
}
res =
GST_ENCODING_PROFILE (gst_encoding_container_profile_new
("User profile", "User profile", caps, NULL));
gst_caps_unref (caps);
} else {
res = NULL;
}
for (i = 1; strcaps_v[i] && *strcaps_v[i]; i++) {
GstEncodingProfile *profile = NULL;
gchar *strcaps, *strpresence;
gchar *preset_name = NULL;
GstCaps *caps;
gchar **restriction_format, **preset_v;
guint presence = 0;
GstCaps *restrictioncaps = NULL;
restriction_format = g_strsplit (strcaps_v[i], "->", 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];
}
strpresence_v = g_strsplit (strpresence, "|", 0);
if (strpresence_v[1]) { /* We have a presence */
gchar *endptr;
if (preset_v[1]) { /* We have preset and presence */
preset_name = g_strdup (strpresence_v[0]);
} else { /* We have a presence but no preset */
g_free (strcaps);
strcaps = g_strdup (strpresence_v[0]);
}
presence = g_ascii_strtoll (strpresence_v[1], &endptr, 10);
if (endptr == strpresence_v[1]) {
g_printerr ("Wrong presence %s\n", strpresence_v[1]);
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 (strpresence_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);
g_free (strcaps);
if (caps == NULL) {
g_warning ("Could not create caps for %s", strcaps_v[i]);
return NULL;
}
if (g_str_has_prefix (strcaps_v[i], "audio/")) {
profile = GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps,
preset_name, restrictioncaps, presence));
} else if (g_str_has_prefix (strcaps_v[i], "video/") ||
g_str_has_prefix (strcaps_v[i], "image/")) {
profile = GST_ENCODING_PROFILE (gst_encoding_video_profile_new (caps,
preset_name, restrictioncaps, presence));
}
g_free (preset_name);
gst_caps_unref (caps);
if (restrictioncaps)
gst_caps_unref (restrictioncaps);
if (profile == NULL) {
g_warning ("No way to create a preset for caps: %s", strcaps_v[i]);
return NULL;
}
if (res) {
if (!gst_encoding_container_profile_add_profile
(GST_ENCODING_CONTAINER_PROFILE (res), profile)) {
g_warning ("Can not create a preset for caps: %s", strcaps_v[i]);
return NULL;
}
} else {
res = profile;
}
}
g_strfreev (strcaps_v);
return res;
}
/* 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 = combo_search (profilename);
if (profile)
g_value_take_object (dest_value, (GObject *) profile);
}
static gboolean
gst_encoding_profile_deserialize_valfunc (GValue * value, const gchar * s)
{
GstEncodingProfile *profile;
profile = combo_search (s);
if (!profile)
profile = parse_encoding_profile (s);
if (profile) {
g_value_take_object (value, (GObject *) profile);
return TRUE;
}
return FALSE;
}
/**
* 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)
{
GstEncodingContainerProfile *profile;
GstDiscovererStreamInfo *sinfo;
GList *streams, *stream;
GstCaps *caps = NULL;
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;
caps = gst_discoverer_stream_info_get_caps (sinfo);
GST_LOG ("Container: %" GST_PTR_FORMAT "\n", caps);
profile =
gst_encoding_container_profile_new ("auto-generated",
"Automatically generated from GstDiscovererInfo", caps, NULL);
gst_caps_unref (caps);
if (!profile) {
GST_ERROR ("Failed to create container profile from caps %" GST_PTR_FORMAT,
caps);
return NULL;
}
streams =
gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO
(sinfo));
for (stream = streams; stream; stream = stream->next) {
GstEncodingProfile *sprofile = NULL;
sinfo = (GstDiscovererStreamInfo *) stream->data;
caps = gst_discoverer_stream_info_get_caps (sinfo);
GST_LOG ("Stream: %" GST_PTR_FORMAT "\n", caps);
if (GST_IS_DISCOVERER_AUDIO_INFO (sinfo)) {
sprofile =
(GstEncodingProfile *) gst_encoding_audio_profile_new (caps, NULL,
NULL, 0);
} else if (GST_IS_DISCOVERER_VIDEO_INFO (sinfo)) {
sprofile =
(GstEncodingProfile *) gst_encoding_video_profile_new (caps, NULL,
NULL, 0);
} else {
/* subtitles or other ? ignore for now */
}
if (sprofile)
gst_encoding_container_profile_add_profile (profile, sprofile);
else
GST_ERROR ("Failed to create stream profile from caps %" GST_PTR_FORMAT,
caps);
gst_caps_unref (caps);
}
gst_discoverer_stream_info_list_free (streams);
return (GstEncodingProfile *) profile;
}