/* * fs-rtp-codec-specific.c - Per-codec SDP negotiation * * Farstream RTP/AVP/SAVP/AVPF Module * Copyright (C) 2007-2010 Collabora Ltd. * Copyright (C) 2007-2010 Nokia Corporation * @author Olivier Crete * * 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-rtp-codec-specific.h" #include #include #include #include #include #include "fs-rtp-conference.h" #define GST_CAT_DEFAULT fsrtpconference_nego /* * This must be kept to the maximum number of parameters + 1 */ #define MAX_PARAMS 20 struct SdpParam { gchar *name; /* The param type tell us if they should be added to the send or recv pipelines or both */ FsParamType paramtype; gboolean (*negotiate_param) (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); const gchar *default_value; }; struct SdpNegoFunction { FsMediaType media_type; const gchar *encoding_name; FsCodec * (* sdp_negotiate_codec) (FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes, const struct SdpNegoFunction *nf); const struct SdpParam params[MAX_PARAMS]; }; struct SdpParamMinMax { const gchar *encoding_name; const gchar *param_name; guint min; guint max; }; static FsCodec * sdp_negotiate_codec_default ( FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes, const struct SdpNegoFunction *nf); static FsCodec * sdp_negotiate_codec_h263_2000 ( FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes, const struct SdpNegoFunction *nf); static FsCodec * sdp_negotiate_codec_mandatory ( FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes, const struct SdpNegoFunction *nf); /* Generic param negotiation functions */ static gboolean param_minimum (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_maximum (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_both_maximum (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_equal_or_ignore (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_equal_or_not_default (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_equal_or_reject (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_list_commas (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_copy (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); /* Codec specific negotiation functions */ static gboolean param_ilbc_mode (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_h263_1998_custom (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_h263_1998_cpcf (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_telephone_events (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_h264_profile_level_id (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); static gboolean param_h264_min_req_profile (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec); const static struct SdpParamMinMax sdp_min_max_params[] = { {"H261", "qcif", 1, 4}, {"H261", "cif", 1, 4}, {"H263-1998", "sqcif", 1, 32}, {"H263-1998", "qcif", 1, 32}, {"H263-1998", "cif", 1, 32}, {"H263-1998", "cif4", 1, 32}, {"H263-1998", "cif16", 1, 32}, {"H263-1998", "bpp", 1, 65536}, {"H263-2000", "level", 0, 100}, {NULL, NULL} }; static const struct SdpNegoFunction sdp_nego_functions[] = { /* iLBC: RFC 3959 */ {FS_MEDIA_TYPE_AUDIO, "iLBC", sdp_negotiate_codec_default, { {"mode", FS_PARAM_TYPE_BOTH, param_ilbc_mode}, {NULL, 0, NULL} } }, /* H261: RFC 4587 */ {FS_MEDIA_TYPE_VIDEO, "H261", sdp_negotiate_codec_default, { {"qcif", FS_PARAM_TYPE_SEND, param_maximum}, {"cif", FS_PARAM_TYPE_SEND, param_both_maximum}, {"d", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {NULL, 0, NULL} } }, /* H263-1998 and H263-2000: RFC 4629 */ {FS_MEDIA_TYPE_VIDEO, "H263-1998", sdp_negotiate_codec_default, { {"sqcif", FS_PARAM_TYPE_SEND, param_maximum}, {"qcif", FS_PARAM_TYPE_SEND, param_maximum}, {"cif", FS_PARAM_TYPE_SEND, param_both_maximum}, {"cif4", FS_PARAM_TYPE_SEND, param_both_maximum}, {"cif16", FS_PARAM_TYPE_SEND, param_both_maximum}, {"custom", FS_PARAM_TYPE_SEND, param_h263_1998_custom}, {"f", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"i", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"j", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"t", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"k", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"n", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"p", FS_PARAM_TYPE_SEND, param_list_commas}, {"par", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"cpcf", FS_PARAM_TYPE_SEND, param_h263_1998_cpcf}, {"bpp", FS_PARAM_TYPE_SEND, param_minimum}, {"hrd", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"interlace", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {NULL, 0, NULL} } }, {FS_MEDIA_TYPE_VIDEO, "H263-2000", sdp_negotiate_codec_h263_2000, { /* Add H263-1998 params here */ {"profile", FS_PARAM_TYPE_BOTH, param_equal_or_reject, "0"}, {"level", FS_PARAM_TYPE_SEND, param_minimum, "0"}, {NULL, 0, NULL} } }, /* VORBIS: RFC 5215 */ {FS_MEDIA_TYPE_AUDIO, "VORBIS", sdp_negotiate_codec_default, { {"configuration", FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_MANDATORY, param_copy}, {NULL, 0, NULL} } }, /* THEORA: as an extension from vorbis using RFC 5215 */ {FS_MEDIA_TYPE_VIDEO, "THEORA", sdp_negotiate_codec_default, { {"configuration", FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_MANDATORY, param_copy}, {"delivery-method", FS_PARAM_TYPE_CONFIG, param_copy}, {NULL, 0, NULL} } }, {FS_MEDIA_TYPE_AUDIO, "G729", sdp_negotiate_codec_default, { {"annexb", FS_PARAM_TYPE_SEND, param_equal_or_not_default, "yes"}, {NULL, 0, NULL} } }, {FS_MEDIA_TYPE_VIDEO, "H264", sdp_negotiate_codec_default, { {"profile-level-id", FS_PARAM_TYPE_SEND, param_h264_profile_level_id}, {"max-mbps", FS_PARAM_TYPE_SEND, param_h264_min_req_profile}, {"max-fs", FS_PARAM_TYPE_SEND, param_h264_min_req_profile}, {"max-cpb", FS_PARAM_TYPE_SEND, param_h264_min_req_profile}, {"max-dpb", FS_PARAM_TYPE_SEND, param_h264_min_req_profile}, {"max-br", FS_PARAM_TYPE_SEND, param_h264_min_req_profile}, {"redundant-pic-cap", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"parameter-add", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"packetization-mode", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"deint-buf-cap", FS_PARAM_TYPE_SEND, param_minimum}, {"max-rcmd-nalu-size", FS_PARAM_TYPE_SEND, param_minimum}, {"sprop-parameter-sets", FS_PARAM_TYPE_CONFIG | FS_PARAM_TYPE_MANDATORY, param_copy}, {"sprop-interleaving-depth", FS_PARAM_TYPE_CONFIG, param_copy}, {"sprop-deint-buf-req", FS_PARAM_TYPE_CONFIG, param_copy}, {"sprop-init-buf-time", FS_PARAM_TYPE_CONFIG, param_copy}, {"sprop-max-don-diff", FS_PARAM_TYPE_CONFIG, param_copy}, {NULL, 0, NULL} } }, {FS_MEDIA_TYPE_AUDIO, "telephone-event", sdp_negotiate_codec_default, { {"", FS_PARAM_TYPE_SEND, param_telephone_events}, {"events", FS_PARAM_TYPE_SEND, param_telephone_events}, {NULL, 0, NULL} } }, /* JPEG2000: RFC 5371 */ {FS_MEDIA_TYPE_VIDEO, "JPEG2000", sdp_negotiate_codec_mandatory, { {"sampling", FS_PARAM_TYPE_BOTH | FS_PARAM_TYPE_MANDATORY, param_equal_or_reject}, {"interlace", FS_PARAM_TYPE_SEND, param_equal_or_ignore}, {"width", FS_PARAM_TYPE_SEND, param_minimum}, {"height", FS_PARAM_TYPE_SEND, param_minimum} } }, {0, NULL, NULL} }; static const struct SdpNegoFunction * get_sdp_nego_function (FsMediaType media_type, const gchar *encoding_name) { int i; for (i = 0; sdp_nego_functions[i].sdp_negotiate_codec; i++) if (sdp_nego_functions[i].media_type == media_type && !g_ascii_strcasecmp (sdp_nego_functions[i].encoding_name, encoding_name)) return &sdp_nego_functions[i]; return NULL; } /* * This function currently returns %TRUE if any configuration parameter is there * if some codecs require something more complicated, we will need a custom * functions for each codec */ gboolean codec_needs_config (FsCodec *codec) { const struct SdpNegoFunction *nf; int i; g_return_val_if_fail (codec, FALSE); nf = get_sdp_nego_function (codec->media_type, codec->encoding_name); if (!nf) return FALSE; for (i = 0; nf->params[i].name; i++) { if (nf->params[i].paramtype & FS_PARAM_TYPE_CONFIG && nf->params[i].paramtype & FS_PARAM_TYPE_MANDATORY) { if (!fs_codec_get_optional_parameter (codec, nf->params[i].name, NULL)) return TRUE; } } return FALSE; } static gboolean codec_param_check_type (const struct SdpNegoFunction *nf, const gchar *param_name, FsParamType paramtypes) { gint i; if (!nf) return FALSE; for (i = 0; nf->params[i].name; i++) if (nf->params[i].paramtype & paramtypes && !g_ascii_strcasecmp (nf->params[i].name, param_name)) return TRUE; return FALSE; } gboolean codec_has_config_data_named (FsCodec *codec, const gchar *param_name) { const struct SdpNegoFunction *nf; g_return_val_if_fail (codec, FALSE); g_return_val_if_fail (param_name, FALSE); nf = get_sdp_nego_function (codec->media_type, codec->encoding_name); if (nf) return codec_param_check_type (nf, param_name, FS_PARAM_TYPE_CONFIG); else return FALSE; } /** * codec_copy_filtered * @codec: a #FsCodec * @paramtypes: bitmask of types of parameters to remove * * Makes a copy of a #FsCodec, but removes all parameters that match * of the bits from the paramtypes element * * Returns: the newly-allocated #FsCodec */ FsCodec * codec_copy_filtered (FsCodec *codec, FsParamType paramtypes) { FsCodec *copy = fs_codec_copy (codec); GList *item = NULL; const struct SdpNegoFunction *nf; nf = get_sdp_nego_function (codec->media_type, codec->encoding_name); if (nf) { for (item = copy->optional_params; item;) { FsCodecParameter *param = item->data; GList *next = g_list_next (item); if (codec_param_check_type (nf, param->name, paramtypes)) fs_codec_remove_optional_parameter (copy, param); item = next; } } return copy; } /** * sdp_negotiate_codec: * * This function performs SDP offer-answer negotiation on a codec, it compares * the local codec (the one that would be sent in an offer) and the remote * codec (the one that would be received from the other side) and tries to see * if they can be negotiated into a new codec (what would be sent in a reply). * If such a codec can be created, it returns it, otherwise it returns NULL. * * RFC 3264 */ FsCodec * sdp_negotiate_codec (FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes) { const struct SdpNegoFunction *nf; g_return_val_if_fail (local_codec, NULL); g_return_val_if_fail (remote_codec, NULL); if (local_codec->media_type != remote_codec->media_type) { GST_LOG ("Wrong media type, local: %s, remote: %s", fs_media_type_to_string (local_codec->media_type), fs_media_type_to_string (remote_codec->media_type)); return NULL; } if (g_ascii_strcasecmp (local_codec->encoding_name, remote_codec->encoding_name)) { GST_LOG ("Encoding names dont match, local: %s, remote: %s", local_codec->encoding_name, remote_codec->encoding_name); return NULL; } if (local_codec->clock_rate && remote_codec->clock_rate && local_codec->clock_rate != remote_codec->clock_rate) { GST_LOG ("Clock rates differ local=%u remote=%u", local_codec->clock_rate, remote_codec->clock_rate); return NULL; } nf = get_sdp_nego_function (local_codec->media_type, local_codec->encoding_name); if (nf) return nf->sdp_negotiate_codec (local_codec, local_paramtypes, remote_codec, remote_paramtypes, nf); else return sdp_negotiate_codec_default (local_codec, local_paramtypes, remote_codec, remote_paramtypes, NULL); } static const struct SdpParam * get_sdp_param (const struct SdpNegoFunction *nf, const gchar *param_name) { static const struct SdpParam ptime_params = { "ptime", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_minimum }; static const struct SdpParam maxptime_params = { "maxptime", FS_PARAM_TYPE_SEND_AVOID_NEGO, param_minimum }; if (nf) { gint i; for (i = 0; nf->params[i].name; i++) if (!g_ascii_strcasecmp (param_name, nf->params[i].name)) return &nf->params[i]; if (nf->media_type != FS_MEDIA_TYPE_AUDIO) return NULL; } if (!g_ascii_strcasecmp (param_name, "ptime")) return &ptime_params; if (!g_ascii_strcasecmp (param_name, "maxptime")) return &maxptime_params; return NULL; } static gboolean param_negotiate (const struct SdpNegoFunction *nf, const gchar *param_name, FsCodec *local_codec, FsCodecParameter *local_param, FsParamType local_paramtypes, FsCodec *remote_codec, FsCodecParameter *remote_param, FsParamType remote_paramtypes, FsCodec *negotiated_codec) { const struct SdpParam *sdp_param = NULL; sdp_param = get_sdp_param (nf, param_name); if (sdp_param) { if ((sdp_param->paramtype & FS_PARAM_TYPE_BOTH) != FS_PARAM_TYPE_BOTH) { if (!(sdp_param->paramtype & local_paramtypes)) local_param = NULL; if (!(sdp_param->paramtype & remote_paramtypes)) remote_param = NULL; } if (local_param || remote_param) return sdp_param->negotiate_param (sdp_param, local_codec, local_param, remote_codec, remote_param, negotiated_codec); else return TRUE; } else { /* Assume unknown parameters are of type SEND */ if (!((remote_paramtypes | local_paramtypes) & FS_PARAM_TYPE_SEND)) return TRUE; if (local_param && remote_param) { /* Only accept codecs where unknown parameters are IDENTICAL if * they are present on both sides */ if (!g_ascii_strcasecmp (local_param->value, remote_param->value)) { fs_codec_add_optional_parameter (negotiated_codec, local_param->name, local_param->value); } else { GST_LOG ("Codec %s has different values for %s (\"%s\" and \"%s\")", local_codec->encoding_name, param_name, local_param->value, remote_param->value); return FALSE; } } else if (local_param) { fs_codec_add_optional_parameter (negotiated_codec, local_param->name, local_param->value); } else if (remote_param) { fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, remote_param->value); } } return TRUE; } static FsCodec * sdp_negotiate_codec_default (FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes, const struct SdpNegoFunction *nf) { FsCodec *negotiated_codec = NULL; FsCodec *local_codec_copy = NULL; GList *local_param_e = NULL, *remote_param_e = NULL; GST_LOG ("Using default codec negotiation function for %s", local_codec->encoding_name); if (local_codec->channels && remote_codec->channels && local_codec->channels != remote_codec->channels) { GST_LOG ("Channel counts differ local=%u remote=%u", local_codec->channels, remote_codec->channels); return NULL; } negotiated_codec = fs_codec_copy (remote_codec); while (negotiated_codec->optional_params) fs_codec_remove_optional_parameter (negotiated_codec, negotiated_codec->optional_params->data); /* Lets fix here missing clock rates and channels counts */ if (negotiated_codec->channels == 0 && local_codec->channels) negotiated_codec->channels = local_codec->channels; if (negotiated_codec->clock_rate == 0) negotiated_codec->clock_rate = local_codec->clock_rate; local_codec_copy = fs_codec_copy (local_codec); for (remote_param_e = remote_codec->optional_params; remote_param_e; remote_param_e = g_list_next (remote_param_e)) { FsCodecParameter *remote_param = remote_param_e->data; FsCodecParameter *local_param = fs_codec_get_optional_parameter ( local_codec_copy, remote_param->name, NULL); if (!param_negotiate (nf, remote_param->name, local_codec, local_param, local_paramtypes, remote_codec, remote_param, remote_paramtypes, negotiated_codec)) goto non_matching_codec; if (local_param) fs_codec_remove_optional_parameter (local_codec_copy, local_param); } for (local_param_e = local_codec_copy->optional_params; local_param_e; local_param_e = g_list_next (local_param_e)) { FsCodecParameter *local_param = local_param_e->data; if (!param_negotiate (nf, local_param->name, local_codec, local_param, local_paramtypes, remote_codec, NULL, remote_paramtypes, negotiated_codec)) goto non_matching_codec; } fs_codec_destroy (local_codec_copy); return negotiated_codec; non_matching_codec: GST_LOG ("Codecs don't really match"); fs_codec_destroy (local_codec_copy); fs_codec_destroy (negotiated_codec); return NULL; } /* * sdp_negotiate_codec_h263_2000: * * For H263-2000, the "profile" must be exactly the same. If it is not, * it must be rejected. If there is none, we assume its 0. * * If profile or level is used, no other parameter should be there. * * RFC 4629 */ static FsCodec * sdp_negotiate_codec_h263_2000 ( FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes, const struct SdpNegoFunction *nf) { const struct SdpNegoFunction *h263_1998_nf; GST_DEBUG ("Using H263-2000 negotiation function"); if (fs_codec_get_optional_parameter (remote_codec, "profile", NULL) && !fs_codec_get_optional_parameter (remote_codec, "level", NULL)) { GST_WARNING ("Can not accept a profile without a level"); return NULL; } if (fs_codec_get_optional_parameter (local_codec, "profile", NULL) && !fs_codec_get_optional_parameter (local_codec, "level", NULL)) { GST_WARNING ("Can not accept a profile without a level"); return NULL; } if (fs_codec_get_optional_parameter (remote_codec, "profile", NULL) || fs_codec_get_optional_parameter (remote_codec, "level", NULL) || fs_codec_get_optional_parameter (local_codec, "profile", NULL) || fs_codec_get_optional_parameter (local_codec, "level", NULL)) return sdp_negotiate_codec_default (local_codec, local_paramtypes, remote_codec, remote_paramtypes, nf); h263_1998_nf = get_sdp_nego_function (FS_MEDIA_TYPE_VIDEO, "H263-1998"); return sdp_negotiate_codec_default (local_codec, local_paramtypes, remote_codec, remote_paramtypes, h263_1998_nf); } static FsCodec * sdp_negotiate_codec_mandatory ( FsCodec *local_codec, FsParamType local_paramtypes, FsCodec *remote_codec, FsParamType remote_paramtypes, const struct SdpNegoFunction *nf) { gint i; for (i = 0; nf->params[i].name; i++) { if (nf->params[i].paramtype & FS_PARAM_TYPE_MANDATORY) { if ((nf->params[i].paramtype & local_paramtypes) || ((nf->params[i].paramtype & FS_PARAM_TYPE_BOTH) == FS_PARAM_TYPE_BOTH)) if (!fs_codec_get_optional_parameter (local_codec, nf->params[i].name, NULL)) return NULL; if ((nf->params[i].paramtype & remote_paramtypes) || ((nf->params[i].paramtype & FS_PARAM_TYPE_BOTH) == FS_PARAM_TYPE_BOTH)) if (!fs_codec_get_optional_parameter (remote_codec, nf->params[i].name, NULL)) return NULL; } } return sdp_negotiate_codec_default (local_codec, local_paramtypes, remote_codec, remote_paramtypes, nf); } struct event_range { int first; int last; }; static gint event_range_cmp (gconstpointer a, gconstpointer b) { const struct event_range *era = a; const struct event_range *erb = b; return era->first - erb->first; } static GList * parse_events (const gchar *events) { gchar **ranges_strv; GList *ranges = NULL; int i; ranges_strv = g_strsplit (events, ",", 0); for (i = 0; ranges_strv[i]; i++) { struct event_range *er = g_slice_new (struct event_range); gchar *p = NULL; er->first = atoi (ranges_strv[i]); p = strchr (ranges_strv[i], '-'); if (p) er->last = atoi (p + 1); else er->last = er->first; ranges = g_list_insert_sorted (ranges, er, event_range_cmp); } g_strfreev (ranges_strv); return ranges; } static void event_range_free (gpointer data) { g_slice_free (struct event_range, data); } static gchar * event_intersection (const gchar *remote_events, const gchar *local_events) { GList *remote_ranges = NULL; GList *local_ranges = NULL; GList *intersected_ranges = NULL; GList *item; GString *intersection_gstr; if (!g_regex_match_simple ("^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$", remote_events, 0, 0)) { GST_WARNING ("Invalid remote events (events=%s)", remote_events); return NULL; } if (!g_regex_match_simple ("^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$", local_events, 0, 0)) { GST_WARNING ("Invalid local events (events=%s)", local_events); return NULL; } remote_ranges = parse_events (remote_events); local_ranges = parse_events (local_events); while ((item = remote_ranges) != NULL) { struct event_range *er1 = item->data; GList *item2; item2 = local_ranges; while (item2) { struct event_range *er2 = item2->data; if (er1->last < er2->first) { break; } if (er1->first <= er2->last) { struct event_range *new_er = g_slice_new (struct event_range); new_er->first = MAX (er1->first, er2->first); new_er->last = MIN (er1->last, er2->last); intersected_ranges = g_list_append (intersected_ranges, new_er); } item2 = item2->next; if (er2->last < er1->last) { local_ranges = g_list_remove (local_ranges, er2); event_range_free (er2); } } remote_ranges = g_list_delete_link (remote_ranges, item); event_range_free (er1); } while (local_ranges) { event_range_free (local_ranges->data); local_ranges = g_list_delete_link (local_ranges, local_ranges); } if (!intersected_ranges) { GST_DEBUG ("There is no intersection before the events %s and %s", remote_events, local_events); return NULL; } intersection_gstr = g_string_new (""); while ((item = intersected_ranges) != NULL) { struct event_range *er = item->data; if (intersection_gstr->len) g_string_append_c (intersection_gstr, ','); if (er->first == er->last) g_string_append_printf (intersection_gstr, "%d", er->first); else g_string_append_printf (intersection_gstr, "%d-%d", er->first, er->last); intersected_ranges = g_list_delete_link (intersected_ranges, item); event_range_free (er); } return g_string_free (intersection_gstr, FALSE); } /** * param_telephone_events: * * For telephone events, it finds the list of events that are the same. * So it tried to intersect both lists to come up with a list of events that * both sides support. * * RFC 4733 */ static gboolean param_telephone_events (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { gchar *events; if (fs_codec_get_optional_parameter (negotiated_codec, "", NULL) || fs_codec_get_optional_parameter (negotiated_codec, "events", NULL)) return TRUE; if (!local_param) local_param = fs_codec_get_optional_parameter (local_codec, "", NULL); if (!local_param) local_param = fs_codec_get_optional_parameter (local_codec, "events", NULL); if (!remote_param) remote_param = fs_codec_get_optional_parameter (remote_codec, "", NULL); if (!remote_param) remote_param = fs_codec_get_optional_parameter (remote_codec, "events", NULL); if (!local_param) { fs_codec_add_optional_parameter (negotiated_codec, "events", remote_param->value); return TRUE; } if (!remote_param) { fs_codec_add_optional_parameter (negotiated_codec, "events", local_param->value); return TRUE; } events = event_intersection (local_param->value, remote_param->value); if (!events) { GST_LOG ("Non-intersecting values for \"events\" local=%s remote=%s", local_param->value, remote_param->value); return FALSE; } fs_codec_add_optional_parameter (negotiated_codec, "events", events); g_free (events); return TRUE; } /** * param_min_max: * * Expects both parameters to have numerical values. * If the type is known, verifies that is is valid. If it is, puts the * minimum or maximum depending on the gboolean value in the result. */ static gboolean param_min_max (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec, gboolean min, gboolean keep_single) { guint local_value = 0; gboolean local_valid = FALSE; guint remote_value = 0; gboolean remote_valid = FALSE; gchar *encoding_name = remote_codec ? remote_codec->encoding_name : local_codec->encoding_name; gchar *param_name = remote_param ? remote_param->name : local_param->name; int i; if (local_param) { local_value = strtol (local_param->value, NULL, 10); if (local_value || errno != EINVAL) local_valid = TRUE; } else if (sdp_param->default_value) { local_value = strtol (sdp_param->default_value, NULL, 10); if (local_value || errno != EINVAL) local_valid = TRUE; } if (remote_param) { remote_value = strtol (remote_param->value, NULL, 10); if (remote_value || errno != EINVAL) remote_valid = TRUE; } else if (sdp_param->default_value) { remote_value = strtol (sdp_param->default_value, NULL, 10); if (remote_value || errno != EINVAL) remote_valid = TRUE; } /* Validate values against min/max from table */ for (i = 0; sdp_min_max_params[i].encoding_name; i++) { if (!g_ascii_strcasecmp (encoding_name, sdp_min_max_params[i].encoding_name) && !g_ascii_strcasecmp (param_name, sdp_min_max_params[i].param_name)) { if (local_valid && (local_value < sdp_min_max_params[i].min || local_value > sdp_min_max_params[i].max)) local_valid = FALSE; if (remote_valid && (remote_value < sdp_min_max_params[i].min || remote_value > sdp_min_max_params[i].max)) return TRUE; break; } } if (local_valid && remote_valid) { gchar *tmp = g_strdup_printf ("%d", min ? MIN (local_value, remote_value):MAX (local_value, remote_value)); fs_codec_add_optional_parameter (negotiated_codec, param_name, tmp); g_free (tmp); } else if (remote_valid && keep_single) { fs_codec_add_optional_parameter (negotiated_codec, param_name, remote_param ? remote_param->value : sdp_param->default_value); } else if (local_valid && keep_single) { fs_codec_add_optional_parameter (negotiated_codec, param_name, local_param->value); } return TRUE; } /** * param_equal_or_ignore: * * If both params are equal, it is the result, otherwise they are removed. * Otherwise the result is nothing */ static gboolean param_equal_or_ignore (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { if (local_param && remote_param && !strcmp (local_param->value, remote_param->value)) fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, remote_param->value); return TRUE; } /** * param_equal_or_not_default: * * If both params are equal, it is the result. Otherwise, if one is not equal to * the default, the result is this param. */ static gboolean param_equal_or_not_default (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { if (local_param && remote_param && !strcmp (local_param->value, remote_param->value)) fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, remote_param->value); else if (remote_param && g_ascii_strcasecmp (remote_param->value, sdp_param->default_value)) fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, remote_param->value); else if (local_param && g_ascii_strcasecmp (local_param->value, sdp_param->default_value)) fs_codec_add_optional_parameter (negotiated_codec, local_param->name, local_param->value); return TRUE; } /** * param_minimum: * * Expects both parameters to have numerical values. * If the type is known, verifies that is is valid. If it is, puts the * minimum value in the result. */ static gboolean param_minimum (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { return param_min_max (sdp_param, local_codec, local_param, remote_codec, remote_param, negotiated_codec, TRUE, TRUE); } /** * param_maximum: * * Expects both parameters to have numerical values. * If the type is known, verifies that is is valid. If it is, puts the * maximum value in the result. */ static gboolean param_maximum (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { return param_min_max (sdp_param, local_codec, local_param, remote_codec, remote_param, negotiated_codec, FALSE, TRUE); } /** * param_both_maximum: * * Expects both parameters to have numerical values. * If the type is known, verifies that is is valid. If it is, puts the * maximum value in the result. * * Only gives a result if both sides have a value */ static gboolean param_both_maximum (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { return param_min_max (sdp_param, local_codec, local_param, remote_codec, remote_param, negotiated_codec, FALSE, FALSE); } /** * param_equal_or_reject: * * Reject the codec if both params are not equal (taking into account the * default value if there is one). * */ static gboolean param_equal_or_reject (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { const gchar *local_value = NULL; const gchar *remote_value = NULL; if (local_param) local_value = local_param->value; else if (sdp_param->default_value) local_value = sdp_param->default_value; if (remote_param) remote_value = remote_param->value; else if (sdp_param->default_value) remote_value = sdp_param->default_value; if (!local_value || !remote_value) { GST_DEBUG ("Missed a remote or a local value and don't have a default"); return FALSE; } if (strcmp (local_value, remote_value)) { GST_DEBUG ("Local value and remove value differ (%s != %s)", local_value, remote_value); return FALSE; } if (remote_param) fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, remote_param->value); else if (local_param) fs_codec_add_optional_parameter (negotiated_codec, local_param->name, local_param->value); return TRUE; } /** * param_list_commas: * * Does the intersection of two comma separated lists, returns a list * of elements that are in both. */ static gboolean param_list_commas (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { gchar **remote_strv = NULL; gchar **local_strv = NULL; GString *result = NULL; gint i; /* If one of them does not have it, just ignore it */ if (!remote_param || !local_param) return TRUE; remote_strv = g_strsplit (remote_param->value, ",", -1); local_strv = g_strsplit (local_param->value, ",", -1); for (i = 0; remote_strv[i]; i++) { gint j; for (j = 0; local_strv[j]; j++) { if (!g_ascii_strcasecmp (remote_strv[i], local_strv[j])) { if (!result) result = g_string_new (remote_strv[i]); else g_string_append_printf (result, ",%s", remote_strv[i]); } } } if (result) { fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, result->str); g_string_free (result, TRUE); } g_strfreev (remote_strv); g_strfreev (local_strv); return TRUE; } /** * param_copy: * * Copies the incoming parameter */ static gboolean param_copy (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { if (remote_param) fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, remote_param->value); else if (local_param) fs_codec_add_optional_parameter (negotiated_codec, local_param->name, local_param->value); return TRUE; } /** * param_ilbc_mode: * * For iLBC, the mode is 20 is both sides agree on 20, otherwise it is 30. * * RFC 3952 */ static gboolean param_ilbc_mode (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { if (local_param && strcmp (local_param->value, "20") && strcmp (local_param->value, "30")) { GST_DEBUG ("local iLBC has mode that is not 20 or 30 but %s", local_param->value); return FALSE; } if (remote_param && strcmp (remote_param->value, "20") && strcmp (remote_param->value, "30")) { GST_DEBUG ("remote iLBC has mode that is not 20 or 30 but %s", remote_param->value); return FALSE; } /* Only do mode=20 if both have it */ if (!local_param || !remote_param) return TRUE; if (!strcmp (local_param->value, "20") && !strcmp (remote_param->value, "20")) fs_codec_add_optional_parameter (negotiated_codec, "mode", "20"); else fs_codec_add_optional_parameter (negotiated_codec, "mode", "30"); return TRUE; } /** * param_h263_1998_custom * * If the first two params (x/y) are the same, it takes the maximum of the * 3rd params. */ static gboolean param_h263_1998_custom (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { guint remote_x, remote_y; gchar *match_string; guint match_len; guint final_mpi; GList *elem; gboolean got_one = FALSE; gchar *tmp; if (!remote_param || !local_param) return TRUE; /* Invalid param, can't parse, ignore it */ if (sscanf (remote_param->value, "%u,%u,%u", &remote_x, &remote_y, &final_mpi) != 3) return TRUE; match_string = g_strdup_printf ("%u,%u,", remote_x, remote_y); match_len = strlen (match_string); for (elem = local_codec->optional_params; elem; elem = g_list_next (elem)) { FsCodecParameter *local_param = elem->data; if (!g_ascii_strcasecmp (local_param->name, remote_param->name)) { if (!strncmp (local_param->value, match_string, match_len)) { guint local_x, local_y, local_mpi; if (sscanf (local_param->value, "%u,%u,%u", &local_x, &local_y, &local_mpi) == 3 && local_x == remote_x && local_y == remote_y) { final_mpi = MAX (final_mpi, local_mpi); got_one = TRUE; } } } } g_free (match_string); if (got_one) { tmp = g_strdup_printf ("%u,%u,%u", remote_x, remote_y, final_mpi); fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, tmp); g_free (tmp); } return TRUE; } /** * param_h263_1998_cpcf * * If the first two params (x/y) are the same, it takes the maximum of the * 6 other params */ static gboolean param_h263_1998_cpcf (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { guint remote_cd, remote_cf; gchar *match_string; guint match_len; guint final_sqcif, final_qcif, final_cif, final_4cif, final_16cif, final_custom; GList *elem; gboolean got_one = FALSE; gchar *tmp; if (!remote_param || !local_param) return TRUE; /* Invalid param, can't parse, ignore it */ if (sscanf (remote_param->value, "%u,%u,%u,%u,%u,%u,%u,%u", &remote_cd, &remote_cf, &final_sqcif, &final_qcif, &final_cif, &final_4cif, &final_16cif, &final_custom) != 8) return TRUE; match_string = g_strdup_printf ("%u,%u,", remote_cd, remote_cf); match_len = strlen (match_string); for (elem = local_codec->optional_params; elem; elem = g_list_next (elem)) { FsCodecParameter *local_param = elem->data; if (!g_ascii_strcasecmp (local_param->name, remote_param->name)) { if (!strncmp (local_param->value, match_string, match_len)) { guint local_cd, local_cf, local_sqcif, local_qcif, local_cif, local_4cif, local_16cif, local_custom; if (sscanf (local_param->value, "%u,%u,%u,%u,%u,%u,%u,%u", &local_cd, &local_cf, &local_sqcif, &local_qcif, &local_cif, &local_4cif, &local_16cif, &local_custom) == 8 && local_cd == remote_cd && local_cf == remote_cf) { final_sqcif = MAX(final_sqcif, local_sqcif); final_qcif = MAX (final_qcif, local_qcif); final_cif = MAX(final_cif, local_cif); final_4cif = MAX(final_4cif, local_4cif); final_16cif = MAX(final_16cif, local_16cif); final_custom = MAX(final_custom, local_custom); got_one = TRUE; } } } } g_free (match_string); if (got_one) { tmp = g_strdup_printf ("%u,%u,%u,%u,%u,%u,%u,%u", remote_cd, remote_cf, final_sqcif, final_qcif, final_cif, final_4cif, final_16cif, final_custom); fs_codec_add_optional_parameter (negotiated_codec, remote_param->name, tmp); g_free (tmp); } return TRUE; } static gboolean param_h264_profile_level_id (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { guint remote_value; guint local_value; guint remote_profile_idc; guint local_profile_idc; guint remote_profile_iop; guint local_profile_iop; guint nego_profile_iop; guint remote_level_idc; guint local_level_idc; guint nego_level_idc; gchar buf[7]; /* If either of them is not present, then we can only do baseline profile * with the minimum level */ if (!remote_param || !local_param) return TRUE; remote_value = strtol (remote_param->value, NULL, 16); if (remote_value == 0 && errno == EINVAL) return TRUE; local_value = strtol (local_param->value, NULL, 16); if (local_value == 0 && errno == EINVAL) return TRUE; remote_profile_idc = 0xFF & (remote_value>>16); local_profile_idc = 0xFF & (local_value>>16); if (remote_profile_idc != local_profile_idc) return TRUE; remote_profile_iop = 0xFF & (remote_value>>8); local_profile_iop = 0xFF & (local_value>>8); nego_profile_iop = remote_profile_iop | local_profile_iop; remote_level_idc = 0xFF & remote_value; local_level_idc = 0xFF & local_value; nego_level_idc = MIN (remote_level_idc, local_level_idc); g_snprintf (buf, 7, "%02X%02X%02X", local_profile_idc, nego_profile_iop, nego_level_idc); fs_codec_add_optional_parameter (negotiated_codec, sdp_param->name, buf); return TRUE; } static gboolean param_h264_min_req_profile (const struct SdpParam *sdp_param, FsCodec *local_codec, FsCodecParameter *local_param, FsCodec *remote_codec, FsCodecParameter *remote_param, FsCodec *negotiated_codec) { if (!fs_codec_get_optional_parameter (negotiated_codec, "profile-level-id", NULL)) { FsCodecParameter *local_profile = fs_codec_get_optional_parameter (local_codec, "profile-level-id", NULL); FsCodecParameter *remote_profile = fs_codec_get_optional_parameter (remote_codec, "profile-level-id", NULL); if (!local_profile || !remote_profile) return TRUE; param_h264_profile_level_id (NULL, local_codec, local_profile, remote_codec, remote_profile, negotiated_codec); if (!fs_codec_get_optional_parameter (negotiated_codec, "profile-level-id", NULL)) return TRUE; } return param_minimum (sdp_param, local_codec, local_param, remote_codec, remote_param, negotiated_codec); } static gboolean has_config_param_changed (FsCodec *codec1, FsCodec *codec2) { GList *itemp; for (itemp = codec1->optional_params; itemp; itemp = g_list_next (itemp)) { FsCodecParameter *param1 = itemp->data; if (codec_has_config_data_named (codec1, param1->name)) { FsCodecParameter *param2 = fs_codec_get_optional_parameter (codec2, param1->name, NULL); if (!param2 || strcmp (param1->value, param2->value)) return TRUE; } } return FALSE; } /** * codecs_list_has_codec_config_changed: * * Compares two lists of codecs and returns the codecs present in the * @new list have any of their config param changed since old (meaning that * this list should be sent to the other side in a reliable way). */ GList * codecs_list_has_codec_config_changed (GList *old, GList *new) { GList *item_new, *item_old; GQueue result = G_QUEUE_INIT; for (item_new = new; item_new; item_new = g_list_next (item_new)) { FsCodec *codec_new = item_new->data; for (item_old = old; item_old; item_old = g_list_next (item_old)) { FsCodec *codec_old = item_old->data; FsCodec *nego = sdp_negotiate_codec (codec_new, FS_PARAM_TYPE_BOTH, item_old->data, FS_PARAM_TYPE_BOTH); fs_codec_destroy (nego); if (nego) { if (has_config_param_changed (codec_new, codec_old) || has_config_param_changed (codec_old, codec_new)) { g_queue_push_tail (&result, fs_codec_copy (codec_new)); break; } } } } return result.head; }