/* * Farstream - Farstream Codec * * Copyright 2007 Collabora Ltd. * @author: Philippe Kalaf * Copyright 2007 Nokia Corp. * * Copyright 2005 Collabora Ltd. * @author: Rob Taylor * * fs-codec.c - A Farstream codec * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "fs-codec.h" #include #include "fs-private.h" #define GST_CAT_DEFAULT _fs_conference_debug /** * SECTION:fs-codec * @short_description: Structure representing a media codec * * An #FsCodec is a way to exchange codec information between the client and * Farstream. The information specified in this structure is usually * representative of the codec information exchanged in the signaling. * */ GType fs_codec_get_type (void) { static GType codec_type = 0; if (codec_type == 0) { codec_type = g_boxed_type_register_static ( "FsCodec", (GBoxedCopyFunc)fs_codec_copy, (GBoxedFreeFunc)fs_codec_destroy); } return codec_type; } GType fs_codec_list_get_type (void) { static GType codec_list_type = 0; if (codec_list_type == 0) { codec_list_type = g_boxed_type_register_static ( "FsCodecGList", (GBoxedCopyFunc)fs_codec_list_copy, (GBoxedFreeFunc)fs_codec_list_destroy); } return codec_list_type; } G_DEFINE_BOXED_TYPE (FsCodecParameter, fs_codec_parameter, fs_codec_parameter_copy, fs_codec_parameter_free) G_DEFINE_BOXED_TYPE (FsFeedbackParameter, fs_feedback_parameter, fs_feedback_parameter_copy, fs_feedback_parameter_free) /** * fs_codec_new: * @id: codec identifier, if RTP this should be based on IETF RTP payload types * @encoding_name: Name of media type this encodes * @media_type: #FsMediaType for type of codec * @clock_rate: The clock rate this codec encodes at, if applicable * * Allocates and initializes a #FsCodec structure * * Returns: A newly allocated #FsCodec */ FsCodec * fs_codec_new (int id, const char *encoding_name, FsMediaType media_type, guint clock_rate) { FsCodec *codec = g_slice_new0 (FsCodec); codec->id = id; codec->encoding_name = g_strdup (encoding_name); codec->media_type = media_type; codec->clock_rate = clock_rate; codec->minimum_reporting_interval = G_MAXUINT; return codec; } /** * fs_codec_parameter_free: * @param: a #FsCodecParameter to free * * Frees a #FsCodecParameter * */ void fs_codec_parameter_free (FsCodecParameter *param) { g_free (param->name); g_free (param->value); g_slice_free (FsCodecParameter, param); } /** * fs_feedback_parameter_free: * @param: a #FsFeedbackParameter to free * * Frees a #FsFeedbackParameter * */ void fs_feedback_parameter_free (FsFeedbackParameter *param) { g_free (param->type); g_free (param->subtype); g_free (param->extra_params); g_slice_free (FsFeedbackParameter, param); } /** * fs_codec_destroy: (skip) * @codec: #FsCodec structure to free * * Deletes a #FsCodec structure and all its data. Is a no-op on %NULL codec */ void fs_codec_destroy (FsCodec * codec) { if (codec == NULL) return; g_free (codec->encoding_name); g_list_foreach (codec->optional_params, (GFunc) fs_codec_parameter_free, NULL); g_list_free (codec->optional_params); g_list_foreach (codec->feedback_params, (GFunc) fs_feedback_parameter_free, NULL); g_list_free (codec->feedback_params); g_slice_free (FsCodec, codec); } /** * fs_codec_copy: * @codec: codec to copy * * Copies a #FsCodec structure. * * Returns: a copy of the codec */ FsCodec * fs_codec_copy (const FsCodec * codec) { FsCodec *copy = NULL; GList *lp; GQueue list_copy = G_QUEUE_INIT; if (codec == NULL) return NULL; copy = fs_codec_new (codec->id, codec->encoding_name, codec->media_type, codec->clock_rate); copy->channels = codec->channels; copy->minimum_reporting_interval = codec->minimum_reporting_interval; for (lp = codec->optional_params; lp; lp = g_list_next (lp)) { FsCodecParameter *param_copy; FsCodecParameter *param = lp->data;; param_copy = g_slice_new (FsCodecParameter); param_copy->name = g_strdup (param->name); param_copy->value = g_strdup (param->value); g_queue_push_tail (&list_copy, param_copy); } copy->optional_params = list_copy.head; g_queue_init (&list_copy); for (lp = codec->feedback_params; lp; lp = g_list_next (lp)) { FsFeedbackParameter *param_copy; FsFeedbackParameter *param = lp->data;; param_copy = g_slice_new (FsFeedbackParameter); param_copy->type = g_strdup (param->type); param_copy->subtype = g_strdup (param->subtype); param_copy->extra_params = g_strdup (param->extra_params); g_queue_push_tail (&list_copy, param_copy); } copy->feedback_params = list_copy.head; return copy; } /** * fs_codec_list_destroy: (skip) * @codec_list: a GList of #FsCodec to delete * * Deletes a list of #FsCodec structures and the list itself. * Does nothing on %NULL lists. */ void fs_codec_list_destroy (GList *codec_list) { GList *lp; FsCodec *codec; for (lp = codec_list; lp; lp = g_list_next (lp)) { codec = (FsCodec *) lp->data; fs_codec_destroy (codec); lp->data = NULL; } g_list_free (codec_list); } /** * fs_codec_list_copy: * @codec_list: (transfer none) (element-type FsCodec): * a GList of #FsCodec to copy * * Copies a list of #FsCodec structures. * * Returns: (element-type FsCodec) (transfer full): The new list. */ GList * fs_codec_list_copy (const GList *codec_list) { GQueue copy = G_QUEUE_INIT; const GList *lp; for (lp = codec_list; lp; lp = g_list_next (lp)) { FsCodec *codec = (FsCodec *) lp->data; g_queue_push_tail (©, fs_codec_copy (codec)); } return copy.head; } /** * fs_codec_list_from_keyfile: * @filename: Name of the #GKeyFile to read the codecs parameters from * @error: location of a #GError, or NULL if no error occured * * Reads the content of a #GKeyFile of the following format into * a #GList of #FsCodec structures. * * * Example: * |[ * [audio/codec1] * clock-rate=8000 * * [audio/codec1:1] * clock-rate=16000 * * [audio/codec2] * one_param=QCIF * another_param=WOW * * [video/codec3] * wierd_param=42 * feedback:nack/pli=1 * feedback:tfrc= * ]| * * Return value: (element-type FsCodec) (transfer full): * The #GList of #FsCodec or %NULL if the keyfile was empty or an error occured. */ GList * fs_codec_list_from_keyfile (const gchar *filename, GError **error) { GKeyFile *keyfile = NULL; GList *codecs = NULL; GError *gerror = NULL; gchar **groups = NULL; gsize groups_count = 0; int i; g_return_val_if_fail (filename, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); keyfile = g_key_file_new (); if (!g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, error)) { goto out; } groups = g_key_file_get_groups (keyfile, &groups_count); if (!groups) goto out; for (i=0; i < groups_count && groups[i]; i++) { FsCodec *codec; gchar **keys = NULL; gsize keys_count; int j; gchar *encoding_name = NULL; gchar *next_tok = NULL; FsMediaType media_type; keys = g_key_file_get_keys (keyfile, groups[i], &keys_count, &gerror); if (!keys || gerror) { if (gerror) GST_WARNING ("Unable to read parameters for %s: %s\n", groups[i], gerror->message); else GST_WARNING ("Unknown errors while reading parameters for %s", groups[i]); g_clear_error (&gerror); goto next_codec; } next_tok = strchr (groups[i], '/'); if (!next_tok) { GST_WARNING ("Invalid codec name: %s", groups[i]); goto next_codec; } if ((next_tok - groups[i]) == 5 /* strlen ("audio") */ && !g_ascii_strncasecmp ("audio", groups[i], 5)) { media_type = FS_MEDIA_TYPE_AUDIO; } else if ((next_tok - groups[i]) == 5 /* strlen ("video") */ && !g_ascii_strncasecmp ("video", groups[i], 5)) { media_type = FS_MEDIA_TYPE_VIDEO; } else if ((next_tok - groups[i]) == 11 /* strlen ("application") */ && !g_ascii_strncasecmp ("application", groups[i], 11)) { media_type = FS_MEDIA_TYPE_APPLICATION; } else { GST_WARNING ("Invalid media type in codec name name %s", groups[i]); goto next_codec; } encoding_name = next_tok + 1; next_tok = strchr (encoding_name, ':'); if (encoding_name[0] == 0 || next_tok - encoding_name == 1) goto next_codec; if (next_tok) encoding_name = g_strndup (encoding_name, next_tok - encoding_name); else encoding_name = g_strdup (encoding_name); codec = fs_codec_new (FS_CODEC_ID_ANY, encoding_name, media_type, 0); g_free (encoding_name); for (j = 0; j < keys_count && keys[j]; j++) { if (!g_ascii_strcasecmp ("clock-rate", keys[j])) { codec->clock_rate = g_key_file_get_integer (keyfile, groups[i], keys[j], &gerror); if (gerror) { codec->clock_rate = 0; goto keyerror; } } else if (!g_ascii_strcasecmp ("id", keys[j])) { codec->id = g_key_file_get_integer (keyfile, groups[i], keys[j], &gerror); if (gerror) { codec->id = FS_CODEC_ID_ANY; goto keyerror; } if (codec->id < 0) codec->id = FS_CODEC_ID_DISABLE; } else if (!g_ascii_strcasecmp ("channels", keys[j])) { codec->channels = g_key_file_get_integer (keyfile, groups[i], keys[j], &gerror); if (gerror) { codec->channels = 0; goto keyerror; } } else if (!g_ascii_strcasecmp ("trr-int", keys[j])) { codec->minimum_reporting_interval = g_key_file_get_integer (keyfile, groups[i], keys[j], &gerror); if (gerror) { codec->minimum_reporting_interval = G_MAXUINT; goto keyerror; } } else if (g_str_has_prefix (keys[j], "feedback:")) { gchar *type = keys[j] + strlen ("feedback:"); gchar *subtype = strchr (type, '/'); gchar *extra_params; extra_params = g_key_file_get_string (keyfile, groups[i], keys[j], &gerror); if (gerror) goto keyerror; /* Replace / with \0 and point to name (the next char) */ if (subtype) { *subtype=0; subtype++; } else { subtype = ""; } fs_codec_add_feedback_parameter (codec, type, subtype, extra_params); g_free (extra_params); } else { FsCodecParameter *param = g_slice_new (FsCodecParameter); param->name = g_strdup (keys[j]); param->value = g_key_file_get_string (keyfile, groups[i], keys[j], &gerror); if (gerror) { fs_codec_parameter_free (param); goto keyerror; } if (!param->name || !param->value) fs_codec_parameter_free (param); else codec->optional_params = g_list_append (codec->optional_params, param); } continue; keyerror: GST_WARNING ("Error reading key %s codec %s: %s", keys[j], groups[i], gerror->message); g_clear_error (&gerror); } codecs = g_list_append (codecs, codec); next_codec: g_strfreev (keys); } out: g_strfreev (groups); g_key_file_free (keyfile); return codecs; } /** * fs_media_type_to_string: * @media_type: A media type * * Gives a user-printable string representing the media type * * Return value: a static string representing the media type */ const gchar * fs_media_type_to_string (FsMediaType media_type) { if (media_type == FS_MEDIA_TYPE_AUDIO) { return "audio"; } else if (media_type == FS_MEDIA_TYPE_VIDEO) { return "video"; } else if (media_type == FS_MEDIA_TYPE_APPLICATION) { return "application"; } else { return NULL; } } /** * fs_codec_to_string: * @codec: A farstream codec * * Returns a newly-allocated string representing the codec * * Return value: the newly-allocated string */ gchar * fs_codec_to_string (const FsCodec *codec) { GString *string = NULL; GList *item; gchar *charstring; if (codec == NULL) return g_strdup ("(NULL)"); string = g_string_new (""); g_string_printf (string, "%d: %s %s clock:%d channels:%d", codec->id, fs_media_type_to_string (codec->media_type), codec->encoding_name, codec->clock_rate, codec->channels); if (codec->minimum_reporting_interval != G_MAXUINT) g_string_append_printf (string, " trr-int=%u", codec->minimum_reporting_interval); for (item = codec->optional_params; item; item = g_list_next (item)) { FsCodecParameter *param = item->data; g_string_append_printf (string, " %s=%s", param->name, param->value); } for (item = codec->feedback_params; item; item = g_list_next (item)) { FsFeedbackParameter *param = item->data; g_string_append_printf (string, " %s/%s=%s", param->type, param->subtype, param->extra_params); } charstring = string->str; g_string_free (string, FALSE); return charstring; } static gboolean compare_optional_params (const gpointer p1, const gpointer p2) { const FsCodecParameter *param1 = p1; const FsCodecParameter *param2 = p2; if (!g_ascii_strcasecmp (param1->name, param2->name) && !strcmp (param1->value, param2->value)) return TRUE; else return FALSE; } static gboolean compare_feedback_params (const gpointer p1, const gpointer p2) { const FsFeedbackParameter *param1 = p1; const FsFeedbackParameter *param2 = p2; if (!g_ascii_strcasecmp (param1->subtype, param2->subtype) && !g_ascii_strcasecmp (param1->type, param2->type) && !g_strcmp0 (param1->extra_params, param2->extra_params)) return TRUE; else return FALSE; } /* * Check if all of the elements of list1 are in list2 * It compares GLists of X using the comparison function */ static gboolean compare_lists (GList *list1, GList *list2, gboolean (*compare_params) (const gpointer p1, const gpointer p2)) { GList *item1; for (item1 = g_list_first (list1); item1; item1 = g_list_next (item1)) { FsCodecParameter *param1 = item1->data; GList *item2 = NULL; for (item2 = g_list_first (list2); item2; item2 = g_list_next (item2)) { FsCodecParameter *param2 = item2->data; if (compare_params (param1, param2)) break; } if (!item2) return FALSE; } return TRUE; } /** * fs_codec_are_equal: * @codec1: First codec * @codec2: Second codec * * Compare two codecs, it will declare two codecs to be identical even * if their optional parameters are in a different order. %NULL encoding names * are ignored. * * Return value: %TRUE of the codecs are identical, %FALSE otherwise */ gboolean fs_codec_are_equal (const FsCodec *codec1, const FsCodec *codec2) { if (codec1 == codec2) return TRUE; if (!codec1 || !codec2) return FALSE; if (codec1->id != codec2->id || codec1->media_type != codec2->media_type || codec1->clock_rate != codec2->clock_rate || codec1->channels != codec2->channels || codec1->minimum_reporting_interval != codec2->minimum_reporting_interval || codec1->encoding_name == NULL || codec2->encoding_name == NULL || g_ascii_strcasecmp (codec1->encoding_name, codec2->encoding_name)) return FALSE; /* Is there a smarter way to compare to un-ordered linked lists * to make sure they contain exactly the same elements?? */ if (!compare_lists (codec1->optional_params, codec2->optional_params, compare_optional_params) || !compare_lists (codec2->optional_params, codec1->optional_params, compare_optional_params)) return FALSE; if (!compare_lists (codec1->feedback_params, codec2->feedback_params, compare_feedback_params) || !compare_lists (codec2->feedback_params, codec1->feedback_params, compare_feedback_params)) return FALSE; return TRUE; } /** * fs_codec_list_are_equal: * @list1: (element-type FsCodec) (allow-none): a #GList of #FsCodec * @list2: (element-type FsCodec) (allow-none): a #GList of #FsCodec * * Verifies if two glist of fscodecs are identical * * Returns: %TRUE if they are identical, %FALSE otherwise */ gboolean fs_codec_list_are_equal (GList *list1, GList *list2) { for (; list1 && list2; list1 = g_list_next (list1), list2 = g_list_next (list2)) { if (!fs_codec_are_equal (list1->data, list2->data)) return FALSE; } if (list1 == NULL && list2 == NULL) return TRUE; else return FALSE; } /** * fs_codec_add_optional_parameter: * @codec: The #FsCodec to add the parameter to * @name: The name of the optional parameter * @value: The extra_params of the optional parameter * * This function adds an new optional parameter to a #FsCodec */ void fs_codec_add_optional_parameter (FsCodec *codec, const gchar *name, const gchar *value) { FsCodecParameter *param; g_return_if_fail (name != NULL && value != NULL); param = g_slice_new (FsCodecParameter); param->name = g_strdup (name); param->value = g_strdup (value); codec->optional_params = g_list_append (codec->optional_params, param); } /** * fs_codec_remove_optional_parameter: * @codec: a #FsCodec * @param: a pointer to the #FsCodecParameter to remove * * Removes an optional parameter from a codec. * * NULL param will do nothing. */ void fs_codec_remove_optional_parameter (FsCodec *codec, FsCodecParameter *param) { g_return_if_fail (codec); if (!param) return; fs_codec_parameter_free (param); codec->optional_params = g_list_remove (codec->optional_params, param); } /** * fs_codec_get_optional_parameter: * @codec: a #FsCodec * @name: The name of the parameter to search for * @value: (allow-none): The value of the parameter to search for or %NULL for * any value * * Finds the #FsCodecParameter in the #FsCodec that has the requested name * and, if not %NULL, the requested value * * Returns: (transfer none): the #FsCodecParameter from the #FsCodec or %NULL */ FsCodecParameter * fs_codec_get_optional_parameter (FsCodec *codec, const gchar *name, const gchar *value) { GList *item = NULL; g_return_val_if_fail (codec != NULL, NULL); g_return_val_if_fail (name != NULL, NULL); for (item = g_list_first (codec->optional_params); item; item = g_list_next (item)) { FsCodecParameter *param = item->data; if (!g_ascii_strcasecmp (param->name, name) && (value == NULL || !g_ascii_strcasecmp (param->value, value))) return param; } return NULL; } /** * fs_codec_add_feedback_parameter: * @codec: The #FsCodec to add the parameter to * @type: The type of the feedback parameter * @subtype: The subtype of the feedback parameter * @extra_params: The extra_params of the feeback parameter * * This function adds an new feedback parameter to a #FsCodec */ void fs_codec_add_feedback_parameter (FsCodec *codec, const gchar *type, const gchar *subtype, const gchar *extra_params) { FsFeedbackParameter *param; g_return_if_fail (type != NULL); g_return_if_fail (subtype != NULL); g_return_if_fail (extra_params != NULL); param = g_slice_new (FsFeedbackParameter); param->type = g_strdup (type); param->subtype = g_strdup (subtype); param->extra_params = g_strdup (extra_params); codec->feedback_params = g_list_append (codec->feedback_params, param); } /** * fs_codec_get_feedback_parameter: * @codec: a #FsCodec * @type: (allow-none): The subtype of the parameter to search for or %NULL for * any type * @subtype: (allow-none): The subtype of the parameter to search for or %NULL * for any subtype * @extra_params: (allow-none): The extra_params of the parameter to search for * or %NULL for any extra_params * * Finds the #FsFeedbackParameter in the #FsCodec that has the requested * subtype, type and extra_params. One of which must be non-NULL; * * Returns: the #FsFeedbackParameter from the #FsCodec or %NULL */ FsFeedbackParameter * fs_codec_get_feedback_parameter (FsCodec *codec, const gchar *type, const gchar *subtype, const gchar *extra_params) { GList *item = NULL; g_return_val_if_fail (codec != NULL, NULL); g_return_val_if_fail (type != NULL || subtype != NULL, NULL); for (item = g_list_first (codec->feedback_params); item; item = g_list_next (item)) { FsFeedbackParameter *param = item->data; if (!g_ascii_strcasecmp (param->type, type) && (subtype == NULL || !g_ascii_strcasecmp (param->subtype, subtype)) && (extra_params == NULL || !g_ascii_strcasecmp (param->extra_params, extra_params))) return param; } return NULL; } /** * fs_codec_remove_feedback_parameter: * @codec: a #FsCodec * @item: (transfer none) (element-type FsFeedbackParameter): * a pointer to the #GList element to remove that contains a * #FsFeedbackParameter * * Removes an optional parameter from a codec. * * NULL param will do nothing. */ void fs_codec_remove_feedback_parameter (FsCodec *codec, GList *item) { g_return_if_fail (codec); if (!item) return; fs_feedback_parameter_free (item->data); codec->feedback_params = g_list_delete_link (codec->feedback_params, item); } /** * fs_codec_parameter_copy: * @param: a #FsCodecParameter * * Makes a copy of a #FsCodecParameter * * Returns: a newly allocated #FsCodecParameter */ FsCodecParameter * fs_codec_parameter_copy (const FsCodecParameter *param) { FsCodecParameter *outparam = g_slice_new (FsCodecParameter); outparam->name = g_strdup (param->name); outparam->value = g_strdup (param->value); return outparam; } /** * fs_feedback_parameter_copy: * @param: a #FsFeedbackParameter * * Makes a copy of a #FsFeedbackParameter * * Returns: a newly allocated #FsFeedbackParameter */ FsFeedbackParameter * fs_feedback_parameter_copy (const FsFeedbackParameter *param) { FsFeedbackParameter *outparam = g_slice_new (FsFeedbackParameter); outparam->type = g_strdup (param->type); outparam->subtype = g_strdup (param->subtype); outparam->extra_params = g_strdup (param->extra_params); return outparam; }