/* GStreamer * Copyright (C) <2005,2006> Wim Taymans * * 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. */ /* * Unless otherwise indicated, Source Code is licensed under MIT license. * See further explanation attached in License Statement (distributed in the file * LICENSE). * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * SECTION:gstsdpmessage * @title: GstSDPMessage * @short_description: Helper methods for dealing with SDP messages * * The GstSDPMessage helper functions makes it easy to parse and create SDP * messages. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include "gstsdpmessage.h" #define FREE_STRING(field) g_free (field); (field) = NULL #define REPLACE_STRING(field, val) FREE_STRING(field); (field) = g_strdup (val) static void free_string (gchar ** str) { FREE_STRING (*str); } #define INIT_ARRAY(field, type, init_func) \ G_STMT_START { \ if (field) { \ guint i; \ for(i = 0; i < (field)->len; i++) \ init_func (&g_array_index ((field), type, i)); \ g_array_set_size ((field), 0); \ } \ else \ (field) = g_array_new (FALSE, TRUE, sizeof (type)); \ } G_STMT_END #define FREE_ARRAY(field) \ G_STMT_START { \ if (field) \ g_array_free ((field), TRUE); \ (field) = NULL; \ } G_STMT_END #define DEFINE_STRING_SETTER(field) \ GstSDPResult gst_sdp_message_set_##field (GstSDPMessage *msg, const gchar *val) { \ g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); \ g_free (msg->field); \ msg->field = g_strdup (val); \ return GST_SDP_OK; \ } #define DEFINE_STRING_GETTER(field) \ const gchar* gst_sdp_message_get_##field (const GstSDPMessage *msg) { \ g_return_val_if_fail (msg != NULL, NULL); \ return msg->field; \ } #define DEFINE_ARRAY_LEN(field) \ guint gst_sdp_message_##field##_len (const GstSDPMessage *msg) { \ g_return_val_if_fail (msg != NULL, 0); \ return msg->field->len; \ } #define DEFINE_ARRAY_GETTER(method, field, type) \ const type * gst_sdp_message_get_##method (const GstSDPMessage *msg, guint idx) { \ g_return_val_if_fail (msg != NULL, NULL); \ return &g_array_index (msg->field, type, idx); \ } #define DEFINE_PTR_ARRAY_GETTER(method, field, type) \ const type gst_sdp_message_get_##method (const GstSDPMessage *msg, guint idx) { \ g_return_val_if_fail (msg != NULL, (type) 0); \ return g_array_index (msg->field, type, idx); \ } #define DEFINE_ARRAY_INSERT(method, field, intype, dup_method, type) \ GstSDPResult gst_sdp_message_insert_##method (GstSDPMessage *msg, gint idx, intype val) { \ type vt; \ type* v = &vt; \ g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); \ dup_method (v, val); \ if (idx == -1) \ g_array_append_val (msg->field, vt); \ else \ g_array_insert_val (msg->field, idx, vt); \ return GST_SDP_OK; \ } #define DEFINE_ARRAY_REPLACE(method, field, intype, free_method, dup_method, type) \ GstSDPResult gst_sdp_message_replace_##method (GstSDPMessage *msg, guint idx, intype val) { \ type *v; \ g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); \ v = &g_array_index (msg->field, type, idx); \ free_method (v); \ dup_method (v, val); \ return GST_SDP_OK; \ } #define DEFINE_ARRAY_REMOVE(method, field, type, free_method) \ GstSDPResult gst_sdp_message_remove_##method (GstSDPMessage *msg, guint idx) { \ type *v; \ g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); \ v = &g_array_index (msg->field, type, idx); \ free_method (v); \ g_array_remove_index (msg->field, idx); \ return GST_SDP_OK; \ } #define DEFINE_ARRAY_ADDER(method, type) \ GstSDPResult gst_sdp_message_add_##method (GstSDPMessage *msg, const type val) { \ g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); \ return gst_sdp_message_insert_##method (msg, -1, val); \ } #define dup_string(v,val) ((*v) = g_strdup (val)) #define INIT_STR_ARRAY(field) \ INIT_ARRAY (field, gchar *, free_string) #define DEFINE_STR_ARRAY_GETTER(method, field) \ DEFINE_PTR_ARRAY_GETTER(method, field, gchar *) #define DEFINE_STR_ARRAY_INSERT(method, field) \ DEFINE_ARRAY_INSERT (method, field, const gchar *, dup_string, gchar *) #define DEFINE_STR_ARRAY_ADDER(method, field) \ DEFINE_ARRAY_ADDER (method, gchar *) #define DEFINE_STR_ARRAY_REPLACE(method, field) \ DEFINE_ARRAY_REPLACE (method, field, const gchar *, free_string, dup_string, gchar *) #define DEFINE_STR_ARRAY_REMOVE(method, field) \ DEFINE_ARRAY_REMOVE (method, field, gchar *, free_string) static GstSDPMessage *gst_sdp_message_boxed_copy (GstSDPMessage * orig); static void gst_sdp_message_boxed_free (GstSDPMessage * msg); G_DEFINE_BOXED_TYPE (GstSDPMessage, gst_sdp_message, gst_sdp_message_boxed_copy, gst_sdp_message_boxed_free); static GstSDPMessage * gst_sdp_message_boxed_copy (GstSDPMessage * orig) { GstSDPMessage *copy; if (!orig) return NULL; if (gst_sdp_message_copy (orig, ©) == GST_SDP_OK) return copy; return NULL; } static void gst_sdp_message_boxed_free (GstSDPMessage * msg) { gst_sdp_message_free (msg); } static void gst_sdp_origin_init (GstSDPOrigin * origin) { FREE_STRING (origin->username); FREE_STRING (origin->sess_id); FREE_STRING (origin->sess_version); FREE_STRING (origin->nettype); FREE_STRING (origin->addrtype); FREE_STRING (origin->addr); } static void gst_sdp_key_init (GstSDPKey * key) { FREE_STRING (key->type); FREE_STRING (key->data); } /** * gst_sdp_message_new: * @msg: (out) (transfer full): pointer to new #GstSDPMessage * * Allocate a new GstSDPMessage and store the result in @msg. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_new (GstSDPMessage ** msg) { GstSDPMessage *newmsg; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); newmsg = g_new0 (GstSDPMessage, 1); *msg = newmsg; return gst_sdp_message_init (newmsg); } /** * gst_sdp_message_new_from_text: * @text: A dynamically allocated string representing the SDP description * @msg: (out) (transfer full): pointer to new #GstSDPMessage * * Parse @text and create a new SDPMessage from these. * * Returns: a #GstSDPResult. * Since: 1.16 */ GstSDPResult gst_sdp_message_new_from_text (const gchar * text, GstSDPMessage ** msg) { GstSDPResult res; if ((res = gst_sdp_message_new (msg)) != GST_SDP_OK) return res; res = gst_sdp_message_parse_buffer ((const guint8 *) text, strlen (text), *msg); return res; } /** * gst_sdp_message_init: * @msg: a #GstSDPMessage * * Initialize @msg so that its contents are as if it was freshly allocated * with gst_sdp_message_new(). This function is mostly used to initialize a message * allocated on the stack. gst_sdp_message_uninit() undoes this operation. * * When this function is invoked on newly allocated data (with malloc or on the * stack), its contents should be set to 0 before calling this function. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_init (GstSDPMessage * msg) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); FREE_STRING (msg->version); gst_sdp_origin_init (&msg->origin); FREE_STRING (msg->session_name); FREE_STRING (msg->information); FREE_STRING (msg->uri); INIT_STR_ARRAY (msg->emails); INIT_STR_ARRAY (msg->phones); gst_sdp_connection_clear (&msg->connection); INIT_ARRAY (msg->bandwidths, GstSDPBandwidth, gst_sdp_bandwidth_clear); INIT_ARRAY (msg->times, GstSDPTime, gst_sdp_time_clear); INIT_ARRAY (msg->zones, GstSDPZone, gst_sdp_zone_clear); gst_sdp_key_init (&msg->key); INIT_ARRAY (msg->attributes, GstSDPAttribute, gst_sdp_attribute_clear); INIT_ARRAY (msg->medias, GstSDPMedia, gst_sdp_media_uninit); return GST_SDP_OK; } /** * gst_sdp_message_uninit: * @msg: a #GstSDPMessage * * Free all resources allocated in @msg. @msg should not be used anymore after * this function. This function should be used when @msg was allocated on the * stack and initialized with gst_sdp_message_init(). * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_uninit (GstSDPMessage * msg) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); gst_sdp_message_init (msg); FREE_ARRAY (msg->emails); FREE_ARRAY (msg->phones); FREE_ARRAY (msg->bandwidths); FREE_ARRAY (msg->times); FREE_ARRAY (msg->zones); FREE_ARRAY (msg->attributes); FREE_ARRAY (msg->medias); return GST_SDP_OK; } /** * gst_sdp_message_copy: * @msg: a #GstSDPMessage * @copy: (out) (transfer full): pointer to new #GstSDPMessage * * Allocate a new copy of @msg and store the result in @copy. The value in * @copy should be release with gst_sdp_message_free function. * * Returns: a #GstSDPResult * * Since: 1.2 */ GstSDPResult gst_sdp_message_copy (const GstSDPMessage * msg, GstSDPMessage ** copy) { GstSDPResult ret; GstSDPMessage *cp; guint i, len; if (msg == NULL) return GST_SDP_EINVAL; ret = gst_sdp_message_new (copy); if (ret != GST_SDP_OK) return ret; cp = *copy; REPLACE_STRING (cp->version, msg->version); gst_sdp_message_set_origin (cp, msg->origin.username, msg->origin.sess_id, msg->origin.sess_version, msg->origin.nettype, msg->origin.addrtype, msg->origin.addr); REPLACE_STRING (cp->session_name, msg->session_name); REPLACE_STRING (cp->information, msg->information); REPLACE_STRING (cp->uri, msg->uri); len = gst_sdp_message_emails_len (msg); for (i = 0; i < len; i++) { gst_sdp_message_add_email (cp, gst_sdp_message_get_email (msg, i)); } len = gst_sdp_message_phones_len (msg); for (i = 0; i < len; i++) { gst_sdp_message_add_phone (cp, gst_sdp_message_get_phone (msg, i)); } gst_sdp_message_set_connection (cp, msg->connection.nettype, msg->connection.addrtype, msg->connection.address, msg->connection.ttl, msg->connection.addr_number); len = gst_sdp_message_bandwidths_len (msg); for (i = 0; i < len; i++) { const GstSDPBandwidth *bw = gst_sdp_message_get_bandwidth (msg, i); gst_sdp_message_add_bandwidth (cp, bw->bwtype, bw->bandwidth); } len = gst_sdp_message_times_len (msg); for (i = 0; i < len; i++) { const gchar **repeat = NULL; const GstSDPTime *time = gst_sdp_message_get_time (msg, i); if (time->repeat != NULL) { guint j; repeat = g_malloc0 ((time->repeat->len + 1) * sizeof (gchar *)); for (j = 0; j < time->repeat->len; j++) { repeat[j] = g_array_index (time->repeat, char *, j); } repeat[j] = NULL; } gst_sdp_message_add_time (cp, time->start, time->stop, repeat); g_free ((gchar **) repeat); } len = gst_sdp_message_zones_len (msg); for (i = 0; i < len; i++) { const GstSDPZone *zone = gst_sdp_message_get_zone (msg, i); gst_sdp_message_add_zone (cp, zone->time, zone->typed_time); } gst_sdp_message_set_key (cp, msg->key.type, msg->key.data); len = gst_sdp_message_attributes_len (msg); for (i = 0; i < len; i++) { const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i); gst_sdp_message_add_attribute (cp, attr->key, attr->value); } len = gst_sdp_message_medias_len (msg); for (i = 0; i < len; i++) { GstSDPMedia *media_copy; const GstSDPMedia *media = gst_sdp_message_get_media (msg, i); if (gst_sdp_media_copy (media, &media_copy) == GST_SDP_OK) { gst_sdp_message_add_media (cp, media_copy); gst_sdp_media_free (media_copy); } } return GST_SDP_OK; } /** * gst_sdp_message_free: * @msg: a #GstSDPMessage * * Free all resources allocated by @msg. @msg should not be used anymore after * this function. This function should be used when @msg was dynamically * allocated with gst_sdp_message_new(). * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_free (GstSDPMessage * msg) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); gst_sdp_message_uninit (msg); g_free (msg); return GST_SDP_OK; } /** * gst_sdp_address_is_multicast: * @nettype: a network type * @addrtype: an address type * @addr: an address * * Check if the given @addr is a multicast address. * * Returns: TRUE when @addr is multicast. */ gboolean gst_sdp_address_is_multicast (const gchar * nettype, const gchar * addrtype, const gchar * addr) { gboolean ret = FALSE; GInetAddress *iaddr; g_return_val_if_fail (addr, FALSE); /* we only support IN */ if (nettype && strcmp (nettype, "IN") != 0) return FALSE; /* guard against parse failures */ if ((iaddr = g_inet_address_new_from_string (addr)) == NULL) return FALSE; ret = g_inet_address_get_is_multicast (iaddr); g_object_unref (iaddr); return ret; } /** * gst_sdp_message_as_text: * @msg: a #GstSDPMessage * * Convert the contents of @msg to a text string. * * Returns: A dynamically allocated string representing the SDP description. */ gchar * gst_sdp_message_as_text (const GstSDPMessage * msg) { /* change all vars so they match rfc? */ GString *lines; guint i; g_return_val_if_fail (msg != NULL, NULL); lines = g_string_new (""); if (msg->version) g_string_append_printf (lines, "v=%s\r\n", msg->version); if (msg->origin.sess_id && msg->origin.sess_version && msg->origin.nettype && msg->origin.addrtype && msg->origin.addr) g_string_append_printf (lines, "o=%s %s %s %s %s %s\r\n", msg->origin.username ? msg->origin.username : "-", msg->origin.sess_id, msg->origin.sess_version, msg->origin.nettype, msg->origin.addrtype, msg->origin.addr); if (msg->session_name) g_string_append_printf (lines, "s=%s\r\n", msg->session_name); if (msg->information) g_string_append_printf (lines, "i=%s\r\n", msg->information); if (msg->uri) g_string_append_printf (lines, "u=%s\r\n", msg->uri); for (i = 0; i < gst_sdp_message_emails_len (msg); i++) g_string_append_printf (lines, "e=%s\r\n", gst_sdp_message_get_email (msg, i)); for (i = 0; i < gst_sdp_message_phones_len (msg); i++) g_string_append_printf (lines, "p=%s\r\n", gst_sdp_message_get_phone (msg, i)); if (msg->connection.nettype && msg->connection.addrtype && msg->connection.address) { g_string_append_printf (lines, "c=%s %s %s", msg->connection.nettype, msg->connection.addrtype, msg->connection.address); if (gst_sdp_address_is_multicast (msg->connection.nettype, msg->connection.addrtype, msg->connection.address)) { /* only add ttl for IP4 */ if (strcmp (msg->connection.addrtype, "IP4") == 0) g_string_append_printf (lines, "/%u", msg->connection.ttl); if (msg->connection.addr_number > 1) g_string_append_printf (lines, "/%u", msg->connection.addr_number); } g_string_append_printf (lines, "\r\n"); } for (i = 0; i < gst_sdp_message_bandwidths_len (msg); i++) { const GstSDPBandwidth *bandwidth = gst_sdp_message_get_bandwidth (msg, i); g_string_append_printf (lines, "b=%s:%u\r\n", bandwidth->bwtype, bandwidth->bandwidth); } if (gst_sdp_message_times_len (msg) == 0) { g_string_append_printf (lines, "t=0 0\r\n"); } else { for (i = 0; i < gst_sdp_message_times_len (msg); i++) { const GstSDPTime *times = gst_sdp_message_get_time (msg, i); g_string_append_printf (lines, "t=%s %s\r\n", times->start, times->stop); if (times->repeat != NULL) { guint j; g_string_append_printf (lines, "r=%s", g_array_index (times->repeat, gchar *, 0)); for (j = 1; j < times->repeat->len; j++) g_string_append_printf (lines, " %s", g_array_index (times->repeat, gchar *, j)); g_string_append_printf (lines, "\r\n"); } } } if (gst_sdp_message_zones_len (msg) > 0) { const GstSDPZone *zone = gst_sdp_message_get_zone (msg, 0); g_string_append_printf (lines, "z=%s %s", zone->time, zone->typed_time); for (i = 1; i < gst_sdp_message_zones_len (msg); i++) { zone = gst_sdp_message_get_zone (msg, i); g_string_append_printf (lines, " %s %s", zone->time, zone->typed_time); } g_string_append_printf (lines, "\r\n"); } if (msg->key.type) { g_string_append_printf (lines, "k=%s", msg->key.type); if (msg->key.data) g_string_append_printf (lines, ":%s", msg->key.data); g_string_append_printf (lines, "\r\n"); } for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) { const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i); if (attr->key) { g_string_append_printf (lines, "a=%s", attr->key); if (attr->value && attr->value[0] != '\0') g_string_append_printf (lines, ":%s", attr->value); g_string_append_printf (lines, "\r\n"); } } for (i = 0; i < gst_sdp_message_medias_len (msg); i++) { const GstSDPMedia *media = gst_sdp_message_get_media (msg, i); gchar *sdp_media_str; sdp_media_str = gst_sdp_media_as_text (media); g_string_append_printf (lines, "%s", sdp_media_str); g_free (sdp_media_str); } return g_string_free (lines, FALSE); } static int hex_to_int (gchar c) { return c >= '0' && c <= '9' ? c - '0' : c >= 'A' && c <= 'F' ? c - 'A' + 10 : c >= 'a' && c <= 'f' ? c - 'a' + 10 : 0; } /** * gst_sdp_message_parse_uri: * @uri: the start of the uri * @msg: the result #GstSDPMessage * * Parse the null-terminated @uri and store the result in @msg. * * The uri should be of the form: * * scheme://[address[:ttl=ttl][:noa=noa]]/[sessionname] * [#type=value *[&type=value]] * * where value is url encoded. This looslely resembles * http://tools.ietf.org/html/draft-fujikawa-sdp-url-01 * * Returns: #GST_SDP_OK on success. */ GstSDPResult gst_sdp_message_parse_uri (const gchar * uri, GstSDPMessage * msg) { GstSDPResult res; gchar *message; const gchar *colon, *slash, *hash, *p; GString *lines; g_return_val_if_fail (uri != NULL, GST_SDP_EINVAL); g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); colon = strstr (uri, "://"); if (!colon) goto no_colon; /* FIXME connection info goes here */ slash = strstr (colon + 3, "/"); if (!slash) goto no_slash; /* FIXME session name goes here */ hash = strstr (slash + 1, "#"); if (!hash) goto no_hash; lines = g_string_new (""); /* unescape */ for (p = hash + 1; *p; p++) { if (*p == '&') g_string_append_printf (lines, "\r\n"); else if (*p == '+') g_string_append_c (lines, ' '); else if (*p == '%') { gchar a, b; if ((a = p[1])) { if ((b = p[2])) { g_string_append_c (lines, (hex_to_int (a) << 4) | hex_to_int (b)); p += 2; } } else { p++; } } else g_string_append_c (lines, *p); } message = g_string_free (lines, FALSE); res = gst_sdp_message_parse_buffer ((const guint8 *) message, strlen (message), msg); g_free (message); return res; /* ERRORS */ no_colon: { return GST_SDP_EINVAL; } no_slash: { return GST_SDP_EINVAL; } no_hash: { return GST_SDP_EINVAL; } } static const guchar acceptable[96] = { /* X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF */ 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00, /* 2X !"#$%&'()*+,-./ */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 3X 0123456789:;<=>? */ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 4X @ABCDEFGHIJKLMNO */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, /* 5X PQRSTUVWXYZ[\]^_ */ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, /* 6X `abcdefghijklmno */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 /* 7X pqrstuvwxyz{|}~DEL */ }; static const gchar hex[16] = "0123456789ABCDEF"; #define ACCEPTABLE_CHAR(a) (((guchar)(a))>=32 && ((guchar)(a))<128 && acceptable[(((guchar)a))-32]) /** * gst_sdp_message_as_uri: * @scheme: the uri scheme * @msg: the #GstSDPMessage * * Creates a uri from @msg with the given @scheme. The uri has the format: * * \@scheme:///[#type=value *[&type=value]] * * Where each value is url encoded. * * Returns: a uri for @msg. */ gchar * gst_sdp_message_as_uri (const gchar * scheme, const GstSDPMessage * msg) { gchar *serialized, *p; gchar *res; GString *lines; gboolean first; g_return_val_if_fail (scheme != NULL, NULL); g_return_val_if_fail (msg != NULL, NULL); serialized = gst_sdp_message_as_text (msg); lines = g_string_new (""); g_string_append_printf (lines, "%s:///#", scheme); /* now escape */ first = TRUE; for (p = serialized; *p; p++) { if (first) { g_string_append_printf (lines, "%c=", *p); if (*(p + 1)) p++; first = FALSE; continue; } if (*p == '\r') continue; else if (*p == '\n') { if (*(p + 1)) g_string_append_c (lines, '&'); first = TRUE; } else if (*p == ' ') g_string_append_c (lines, '+'); else if (ACCEPTABLE_CHAR (*p)) g_string_append_c (lines, *p); else { /* escape */ g_string_append_printf (lines, "%%%c%c", hex[*p >> 4], hex[*p & 0xf]); } } res = g_string_free (lines, FALSE); g_free (serialized); return res; } /** * gst_sdp_message_set_version: * @msg: a #GstSDPMessage * @version: the version * * Set the version in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_SETTER (version); /** * gst_sdp_message_get_version: * @msg: a #GstSDPMessage * * Get the version in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_GETTER (version); /** * gst_sdp_message_set_origin: * @msg: a #GstSDPMessage * @username: the user name * @sess_id: a session id * @sess_version: a session version * @nettype: a network type * @addrtype: an address type * @addr: an address * * Configure the SDP origin in @msg with the given parameters. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_message_set_origin (GstSDPMessage * msg, const gchar * username, const gchar * sess_id, const gchar * sess_version, const gchar * nettype, const gchar * addrtype, const gchar * addr) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); REPLACE_STRING (msg->origin.username, username); REPLACE_STRING (msg->origin.sess_id, sess_id); REPLACE_STRING (msg->origin.sess_version, sess_version); REPLACE_STRING (msg->origin.nettype, nettype); REPLACE_STRING (msg->origin.addrtype, addrtype); REPLACE_STRING (msg->origin.addr, addr); return GST_SDP_OK; } /** * gst_sdp_message_get_origin: * @msg: a #GstSDPMessage * * Get the origin of @msg. * * Returns: a #GstSDPOrigin. The result remains valid as long as @msg is valid. */ const GstSDPOrigin * gst_sdp_message_get_origin (const GstSDPMessage * msg) { g_return_val_if_fail (msg != NULL, NULL); return &msg->origin; } /** * gst_sdp_message_set_session_name: * @msg: a #GstSDPMessage * @session_name: the session name * * Set the session name in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_SETTER (session_name); /** * gst_sdp_message_get_session_name: * @msg: a #GstSDPMessage * * Get the session name in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_GETTER (session_name); /** * gst_sdp_message_set_information: * @msg: a #GstSDPMessage * @information: the information * * Set the information in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_SETTER (information); /** * gst_sdp_message_get_information: * @msg: a #GstSDPMessage * * Get the information in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_GETTER (information); /** * gst_sdp_message_set_uri: * @msg: a #GstSDPMessage * @uri: the URI * * Set the URI in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_SETTER (uri); /** * gst_sdp_message_get_uri: * @msg: a #GstSDPMessage * * Get the URI in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STRING_GETTER (uri); /** * gst_sdp_message_emails_len: * @msg: a #GstSDPMessage * * Get the number of emails in @msg. * * Returns: the number of emails in @msg. */ DEFINE_ARRAY_LEN (emails); /** * gst_sdp_message_get_email: * @msg: a #GstSDPMessage * @idx: an email index * * Get the email with number @idx from @msg. * * Returns: the email at position @idx. */ DEFINE_STR_ARRAY_GETTER (email, emails); /** * gst_sdp_message_insert_email: * @msg: a #GstSDPMessage * @idx: an index * @email: an email * * Insert @email into the array of emails in @msg at index @idx. * When -1 is given as @idx, the email is inserted at the end. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_STR_ARRAY_INSERT (email, emails); /** * gst_sdp_message_replace_email: * @msg: a #GstSDPMessage * @idx: an email index * @email: an email * * Replace the email in @msg at index @idx with @email. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_STR_ARRAY_REPLACE (email, emails); /** * gst_sdp_message_remove_email: * @msg: a #GstSDPMessage * @idx: an email index * * Remove the email in @msg at index @idx. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_STR_ARRAY_REMOVE (email, emails); /** * gst_sdp_message_add_email: * @msg: a #GstSDPMessage * @email: an email * * Add @email to the list of emails in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STR_ARRAY_ADDER (email, emails); /** * gst_sdp_message_phones_len: * @msg: a #GstSDPMessage * * Get the number of phones in @msg. * * Returns: the number of phones in @msg. */ DEFINE_ARRAY_LEN (phones); /** * gst_sdp_message_get_phone: * @msg: a #GstSDPMessage * @idx: a phone index * * Get the phone with number @idx from @msg. * * Returns: the phone at position @idx. */ DEFINE_STR_ARRAY_GETTER (phone, phones); /** * gst_sdp_message_insert_phone: * @msg: a #GstSDPMessage * @idx: a phone index * @phone: a phone * * Insert @phone into the array of phone numbers in @msg at index @idx. * When -1 is given as @idx, the phone is inserted at the end. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_STR_ARRAY_INSERT (phone, phones); /** * gst_sdp_message_replace_phone: * @msg: a #GstSDPMessage * @idx: a phone index * @phone: a phone * * Replace the phone number in @msg at index @idx with @phone. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_STR_ARRAY_REPLACE (phone, phones); /** * gst_sdp_message_remove_phone: * @msg: a #GstSDPMessage * @idx: a phone index * * Remove the phone number in @msg at index @idx. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_STR_ARRAY_REMOVE (phone, phones); /** * gst_sdp_message_add_phone: * @msg: a #GstSDPMessage * @phone: a phone * * Add @phone to the list of phones in @msg. * * Returns: a #GstSDPResult. */ DEFINE_STR_ARRAY_ADDER (phone, phones); /** * gst_sdp_message_set_connection: * @msg: a #GstSDPMessage * @nettype: the type of network. "IN" is defined to have the meaning * "Internet". * @addrtype: the type of address. * @address: the address * @ttl: the time to live of the address * @addr_number: the number of layers * * Configure the SDP connection in @msg with the given parameters. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_set_connection (GstSDPMessage * msg, const gchar * nettype, const gchar * addrtype, const gchar * address, guint ttl, guint addr_number) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); REPLACE_STRING (msg->connection.nettype, nettype); REPLACE_STRING (msg->connection.addrtype, addrtype); REPLACE_STRING (msg->connection.address, address); msg->connection.ttl = ttl; msg->connection.addr_number = addr_number; return GST_SDP_OK; } /** * gst_sdp_message_get_connection: * @msg: a #GstSDPMessage * * Get the connection of @msg. * * Returns: a #GstSDPConnection. The result remains valid as long as @msg is valid. */ const GstSDPConnection * gst_sdp_message_get_connection (const GstSDPMessage * msg) { g_return_val_if_fail (msg != NULL, NULL); return &msg->connection; } /** * gst_sdp_bandwidth_set: * @bw: a #GstSDPBandwidth * @bwtype: the bandwidth modifier type * @bandwidth: the bandwidth in kilobits per second * * Set bandwidth information in @bw. * * Returns: a #GstSDPResult. * * Since: 1.2 */ GstSDPResult gst_sdp_bandwidth_set (GstSDPBandwidth * bw, const gchar * bwtype, guint bandwidth) { g_return_val_if_fail (bw != NULL, GST_SDP_EINVAL); bw->bwtype = g_strdup (bwtype); bw->bandwidth = bandwidth; return GST_SDP_OK; } /** * gst_sdp_bandwidth_clear: * @bw: a #GstSDPBandwidth * * Reset the bandwidth information in @bw. * * Returns: a #GstSDPResult. * * Since: 1.2 */ GstSDPResult gst_sdp_bandwidth_clear (GstSDPBandwidth * bw) { g_return_val_if_fail (bw != NULL, GST_SDP_EINVAL); FREE_STRING (bw->bwtype); bw->bandwidth = 0; return GST_SDP_OK; } /** * gst_sdp_message_bandwidths_len: * @msg: a #GstSDPMessage * * Get the number of bandwidth information in @msg. * * Returns: the number of bandwidth information in @msg. */ DEFINE_ARRAY_LEN (bandwidths); /** * gst_sdp_message_get_bandwidth: * @msg: a #GstSDPMessage * @idx: the bandwidth index * * Get the bandwidth at index @idx from @msg. * * Returns: a #GstSDPBandwidth. */ DEFINE_ARRAY_GETTER (bandwidth, bandwidths, GstSDPBandwidth); #define DUP_BANDWIDTH(v, val) memcpy (v, val, sizeof (GstSDPBandwidth)) #define FREE_BANDWIDTH(v) gst_sdp_bandwidth_clear(v) /** * gst_sdp_message_insert_bandwidth: * @msg: a #GstSDPMessage * @idx: an index * @bw: the bandwidth * * Insert bandwidth parameters into the array of bandwidths in @msg * at index @idx. * When -1 is given as @idx, the bandwidth is inserted at the end. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_INSERT (bandwidth, bandwidths, GstSDPBandwidth *, DUP_BANDWIDTH, GstSDPBandwidth); /** * gst_sdp_message_replace_bandwidth: * @msg: a #GstSDPMessage * @idx: the bandwidth index * @bw: the bandwidth * * Replace the bandwidth information in @msg at index @idx with @bw. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REPLACE (bandwidth, bandwidths, GstSDPBandwidth *, FREE_BANDWIDTH, DUP_BANDWIDTH, GstSDPBandwidth); /** * gst_sdp_message_remove_bandwidth: * @msg: a #GstSDPMessage * @idx: the bandwidth index * * Remove the bandwidth information in @msg at index @idx. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REMOVE (bandwidth, bandwidths, GstSDPBandwidth, FREE_BANDWIDTH); /** * gst_sdp_message_add_bandwidth: * @msg: a #GstSDPMessage * @bwtype: the bandwidth modifier type * @bandwidth: the bandwidth in kilobits per second * * Add the specified bandwidth information to @msg. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_add_bandwidth (GstSDPMessage * msg, const gchar * bwtype, guint bandwidth) { GstSDPBandwidth bw; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); gst_sdp_bandwidth_set (&bw, bwtype, bandwidth); return gst_sdp_message_insert_bandwidth (msg, -1, &bw); } /** * gst_sdp_time_set: * @t: a #GstSDPTime * @start: the start time * @stop: the stop time * @repeat: (array zero-terminated=1): the repeat times * * Set time information @start, @stop and @repeat in @t. * * Returns: a #GstSDPResult. * * Since: 1.2 */ GstSDPResult gst_sdp_time_set (GstSDPTime * t, const gchar * start, const gchar * stop, const gchar ** repeat) { g_return_val_if_fail (t != NULL, GST_SDP_EINVAL); t->start = g_strdup (start); t->stop = g_strdup (stop); if (repeat) { t->repeat = g_array_new (FALSE, TRUE, sizeof (gchar *)); for (; *repeat; repeat++) { gchar *r = g_strdup (*repeat); g_array_append_val (t->repeat, r); } } else t->repeat = NULL; return GST_SDP_OK; } /** * gst_sdp_time_clear: * @t: a #GstSDPTime * * Reset the time information in @t. * * Returns: a #GstSDPResult. * * Since: 1.2 */ GstSDPResult gst_sdp_time_clear (GstSDPTime * t) { g_return_val_if_fail (t != NULL, GST_SDP_EINVAL); FREE_STRING (t->start); FREE_STRING (t->stop); INIT_STR_ARRAY (t->repeat); FREE_ARRAY (t->repeat); return GST_SDP_OK; } /** * gst_sdp_message_times_len: * @msg: a #GstSDPMessage * * Get the number of time information entries in @msg. * * Returns: the number of time information entries in @msg. */ DEFINE_ARRAY_LEN (times); /** * gst_sdp_message_get_time: * @msg: a #GstSDPMessage * @idx: the time index * * Get time information with index @idx from @msg. * * Returns: a #GstSDPTime. */ DEFINE_ARRAY_GETTER (time, times, GstSDPTime); #define DUP_TIME(v, val) memcpy (v, val, sizeof (GstSDPTime)) #define FREE_TIME(v) gst_sdp_time_clear(v) /** * gst_sdp_message_insert_time: * @msg: a #GstSDPMessage * @idx: an index * @t: a #GstSDPTime * * Insert time parameters into the array of times in @msg * at index @idx. * When -1 is given as @idx, the times are inserted at the end. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_INSERT (time, times, GstSDPTime *, DUP_TIME, GstSDPTime); /** * gst_sdp_message_replace_time: * @msg: a #GstSDPMessage * @idx: the index * @t: a #GstSDPTime * * Replace the time information in @msg at index @idx with @t. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REPLACE (time, times, GstSDPTime *, FREE_TIME, DUP_TIME, GstSDPTime); /** * gst_sdp_message_remove_time: * @msg: a #GstSDPMessage * @idx: the index * * Remove the time information in @msg at index @idx. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REMOVE (time, times, GstSDPTime, FREE_TIME); /** * gst_sdp_message_add_time: * @msg: a #GstSDPMessage * @start: the start time * @stop: the stop time * @repeat: (array zero-terminated=1): the repeat times * * Add time information @start and @stop to @msg. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_add_time (GstSDPMessage * msg, const gchar * start, const gchar * stop, const gchar ** repeat) { GstSDPTime times; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); gst_sdp_time_set (×, start, stop, repeat); g_array_append_val (msg->times, times); return GST_SDP_OK; } /** * gst_sdp_zone_set: * @zone: a #GstSDPZone * @adj_time: the NTP time that a time zone adjustment happens * @typed_time: the offset from the time when the session was first scheduled * * Set zone information in @zone. * * Returns: a #GstSDPResult. * * Since: 1.2 */ GstSDPResult gst_sdp_zone_set (GstSDPZone * zone, const gchar * adj_time, const gchar * typed_time) { g_return_val_if_fail (zone != NULL, GST_SDP_EINVAL); zone->time = g_strdup (adj_time); zone->typed_time = g_strdup (typed_time); return GST_SDP_OK; } /** * gst_sdp_zone_clear: * @zone: a #GstSDPZone * * Reset the zone information in @zone. * * Returns: a #GstSDPResult. * * Since: 1.2 */ GstSDPResult gst_sdp_zone_clear (GstSDPZone * zone) { g_return_val_if_fail (zone != NULL, GST_SDP_EINVAL); FREE_STRING (zone->time); FREE_STRING (zone->typed_time); return GST_SDP_OK; } /** * gst_sdp_message_zones_len: * @msg: a #GstSDPMessage * * Get the number of time zone information entries in @msg. * * Returns: the number of time zone information entries in @msg. */ DEFINE_ARRAY_LEN (zones); /** * gst_sdp_message_get_zone: * @msg: a #GstSDPMessage * @idx: the zone index * * Get time zone information with index @idx from @msg. * * Returns: a #GstSDPZone. */ DEFINE_ARRAY_GETTER (zone, zones, GstSDPZone); #define DUP_ZONE(v, val) memcpy (v, val, sizeof (GstSDPZone)) #define FREE_ZONE(v) gst_sdp_zone_clear(v) /** * gst_sdp_message_insert_zone: * @msg: a #GstSDPMessage * @idx: an index * @zone: a #GstSDPZone * * Insert zone parameters into the array of zones in @msg * at index @idx. * When -1 is given as @idx, the zone is inserted at the end. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_INSERT (zone, zones, GstSDPZone *, DUP_ZONE, GstSDPZone); /** * gst_sdp_message_replace_zone: * @msg: a #GstSDPMessage * @idx: the index * @zone: a #GstSDPZone * * Replace the zone information in @msg at index @idx with @zone. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REPLACE (zone, zones, GstSDPZone *, FREE_ZONE, DUP_ZONE, GstSDPZone); /** * gst_sdp_message_remove_zone: * @msg: a #GstSDPMessage * @idx: the index * * Remove the zone information in @msg at index @idx. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REMOVE (zone, zones, GstSDPZone, FREE_ZONE); /** * gst_sdp_message_add_zone: * @msg: a #GstSDPMessage * @adj_time: the NTP time that a time zone adjustment happens * @typed_time: the offset from the time when the session was first scheduled * * Add time zone information to @msg. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_add_zone (GstSDPMessage * msg, const gchar * adj_time, const gchar * typed_time) { GstSDPZone zone; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); gst_sdp_zone_set (&zone, adj_time, typed_time); g_array_append_val (msg->zones, zone); return GST_SDP_OK; } /** * gst_sdp_message_set_key: * @msg: a #GstSDPMessage * @type: the encryption type * @data: the encryption data * * Adds the encryption information to @msg. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_set_key (GstSDPMessage * msg, const gchar * type, const gchar * data) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); REPLACE_STRING (msg->key.type, type); REPLACE_STRING (msg->key.data, data); return GST_SDP_OK; } /** * gst_sdp_message_get_key: * @msg: a #GstSDPMessage * * Get the encryption information from @msg. * * Returns: a #GstSDPKey. */ const GstSDPKey * gst_sdp_message_get_key (const GstSDPMessage * msg) { g_return_val_if_fail (msg != NULL, NULL); return &msg->key; } /** * gst_sdp_attribute_set: * @attr: a #GstSDPAttribute * @key: the key * @value: (nullable): the value * * Set the attribute with @key and @value. * * Returns: @GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_attribute_set (GstSDPAttribute * attr, const gchar * key, const gchar * value) { g_return_val_if_fail (attr != NULL, GST_SDP_EINVAL); g_return_val_if_fail (key != NULL, GST_SDP_EINVAL); attr->key = g_strdup (key); attr->value = g_strdup (value); return GST_SDP_OK; } /** * gst_sdp_attribute_clear: * @attr: a #GstSDPAttribute * * Clear the attribute. * * Returns: @GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_attribute_clear (GstSDPAttribute * attr) { g_return_val_if_fail (attr != NULL, GST_SDP_EINVAL); FREE_STRING (attr->key); FREE_STRING (attr->value); return GST_SDP_OK; } /** * gst_sdp_message_attributes_len: * @msg: a #GstSDPMessage * * Get the number of attributes in @msg. * * Returns: the number of attributes in @msg. */ DEFINE_ARRAY_LEN (attributes); /** * gst_sdp_message_get_attribute: * @msg: a #GstSDPMessage * @idx: the index * * Get the attribute at position @idx in @msg. * * Returns: the #GstSDPAttribute at position @idx. */ DEFINE_ARRAY_GETTER (attribute, attributes, GstSDPAttribute); /** * gst_sdp_message_get_attribute_val_n: * @msg: a #GstSDPMessage * @key: the key * @nth: the index * * Get the @nth attribute with key @key in @msg. * * Returns: the attribute value of the @nth attribute with @key. */ const gchar * gst_sdp_message_get_attribute_val_n (const GstSDPMessage * msg, const gchar * key, guint nth) { guint i; g_return_val_if_fail (msg != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); for (i = 0; i < msg->attributes->len; i++) { GstSDPAttribute *attr; attr = &g_array_index (msg->attributes, GstSDPAttribute, i); if (!strcmp (attr->key, key)) { if (nth == 0) return attr->value; else nth--; } } return NULL; } /** * gst_sdp_message_get_attribute_val: * @msg: a #GstSDPMessage * @key: the key * * Get the first attribute with key @key in @msg. * * Returns: the attribute value of the first attribute with @key. */ const gchar * gst_sdp_message_get_attribute_val (const GstSDPMessage * msg, const gchar * key) { return gst_sdp_message_get_attribute_val_n (msg, key, 0); } #define DUP_ATTRIBUTE(v, val) memcpy (v, val, sizeof (GstSDPAttribute)) #define FREE_ATTRIBUTE(v) gst_sdp_attribute_clear(v) /** * gst_sdp_message_insert_attribute: * @msg: a #GstSDPMessage * @idx: an index * @attr: a #GstSDPAttribute * * Insert attribute into the array of attributes in @msg * at index @idx. * When -1 is given as @idx, the attribute is inserted at the end. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_INSERT (attribute, attributes, GstSDPAttribute *, DUP_ATTRIBUTE, GstSDPAttribute); /** * gst_sdp_message_replace_attribute: * @msg: a #GstSDPMessage * @idx: the index * @attr: a #GstSDPAttribute * * Replace the attribute in @msg at index @idx with @attr. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REPLACE (attribute, attributes, GstSDPAttribute *, FREE_ATTRIBUTE, DUP_ATTRIBUTE, GstSDPAttribute); /** * gst_sdp_message_remove_attribute: * @msg: a #GstSDPMessage * @idx: the index * * Remove the attribute in @msg at index @idx. * * Returns: a #GstSDPResult. * * Since: 1.2 */ DEFINE_ARRAY_REMOVE (attribute, attributes, GstSDPAttribute, FREE_ATTRIBUTE); /** * gst_sdp_message_add_attribute: * @msg: a #GstSDPMessage * @key: the key * @value: (nullable): the value * * Add the attribute with @key and @value to @msg. * * Returns: @GST_SDP_OK. */ GstSDPResult gst_sdp_message_add_attribute (GstSDPMessage * msg, const gchar * key, const gchar * value) { GstSDPAttribute attr; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); g_return_val_if_fail (key != NULL, GST_SDP_EINVAL); gst_sdp_attribute_set (&attr, key, value); g_array_append_val (msg->attributes, attr); return GST_SDP_OK; } /** * gst_sdp_message_medias_len: * @msg: a #GstSDPMessage * * Get the number of media descriptions in @msg. * * Returns: the number of media descriptions in @msg. */ DEFINE_ARRAY_LEN (medias); /** * gst_sdp_message_get_media: * @msg: a #GstSDPMessage * @idx: the index * * Get the media description at index @idx in @msg. * * Returns: a #GstSDPMedia. */ DEFINE_ARRAY_GETTER (media, medias, GstSDPMedia); /** * gst_sdp_message_add_media: * @msg: a #GstSDPMessage * @media: a #GstSDPMedia to add * * Adds @media to the array of medias in @msg. This function takes ownership of * the contents of @media so that @media will have to be reinitialized with * gst_sdp_media_init() before it can be used again. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_add_media (GstSDPMessage * msg, GstSDPMedia * media) { guint len; GstSDPMedia *nmedia; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); len = msg->medias->len; g_array_set_size (msg->medias, len + 1); nmedia = &g_array_index (msg->medias, GstSDPMedia, len); memcpy (nmedia, media, sizeof (GstSDPMedia)); memset (media, 0, sizeof (GstSDPMedia)); return GST_SDP_OK; } /* media access */ /** * gst_sdp_media_new: * @media: (out) (transfer full): pointer to new #GstSDPMedia * * Allocate a new GstSDPMedia and store the result in @media. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_media_new (GstSDPMedia ** media) { GstSDPMedia *newmedia; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); newmedia = g_new0 (GstSDPMedia, 1); *media = newmedia; return gst_sdp_media_init (newmedia); } /** * gst_sdp_media_init: * @media: a #GstSDPMedia * * Initialize @media so that its contents are as if it was freshly allocated * with gst_sdp_media_new(). This function is mostly used to initialize a media * allocated on the stack. gst_sdp_media_uninit() undoes this operation. * * When this function is invoked on newly allocated data (with malloc or on the * stack), its contents should be set to 0 before calling this function. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_media_init (GstSDPMedia * media) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); FREE_STRING (media->media); media->port = 0; media->num_ports = 0; FREE_STRING (media->proto); INIT_STR_ARRAY (media->fmts); FREE_STRING (media->information); INIT_ARRAY (media->connections, GstSDPConnection, gst_sdp_connection_clear); INIT_ARRAY (media->bandwidths, GstSDPBandwidth, gst_sdp_bandwidth_clear); gst_sdp_key_init (&media->key); INIT_ARRAY (media->attributes, GstSDPAttribute, gst_sdp_attribute_clear); return GST_SDP_OK; } /** * gst_sdp_media_uninit: * @media: a #GstSDPMedia * * Free all resources allocated in @media. @media should not be used anymore after * this function. This function should be used when @media was allocated on the * stack and initialized with gst_sdp_media_init(). * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_media_uninit (GstSDPMedia * media) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); gst_sdp_media_init (media); FREE_ARRAY (media->fmts); FREE_ARRAY (media->connections); FREE_ARRAY (media->bandwidths); FREE_ARRAY (media->attributes); return GST_SDP_OK; } /** * gst_sdp_media_free: * @media: a #GstSDPMedia * * Free all resources allocated by @media. @media should not be used anymore after * this function. This function should be used when @media was dynamically * allocated with gst_sdp_media_new(). * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_media_free (GstSDPMedia * media) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); gst_sdp_media_uninit (media); g_free (media); return GST_SDP_OK; } /** * gst_sdp_media_copy: * @media: a #GstSDPMedia * @copy: (out) (transfer full): pointer to new #GstSDPMedia * * Allocate a new copy of @media and store the result in @copy. The value in * @copy should be release with gst_sdp_media_free function. * * Returns: a #GstSDPResult * * Since: 1.2 */ GstSDPResult gst_sdp_media_copy (const GstSDPMedia * media, GstSDPMedia ** copy) { GstSDPResult ret; GstSDPMedia *cp; guint i, len; if (media == NULL) return GST_SDP_EINVAL; ret = gst_sdp_media_new (copy); if (ret != GST_SDP_OK) return ret; cp = *copy; REPLACE_STRING (cp->media, media->media); cp->port = media->port; cp->num_ports = media->num_ports; REPLACE_STRING (cp->proto, media->proto); len = gst_sdp_media_formats_len (media); for (i = 0; i < len; i++) { gst_sdp_media_add_format (cp, gst_sdp_media_get_format (media, i)); } REPLACE_STRING (cp->information, media->information); len = gst_sdp_media_connections_len (media); for (i = 0; i < len; i++) { const GstSDPConnection *connection = gst_sdp_media_get_connection (media, i); gst_sdp_media_add_connection (cp, connection->nettype, connection->addrtype, connection->address, connection->ttl, connection->addr_number); } len = gst_sdp_media_bandwidths_len (media); for (i = 0; i < len; i++) { const GstSDPBandwidth *bw = gst_sdp_media_get_bandwidth (media, i); gst_sdp_media_add_bandwidth (cp, bw->bwtype, bw->bandwidth); } gst_sdp_media_set_key (cp, media->key.type, media->key.data); len = gst_sdp_media_attributes_len (media); for (i = 0; i < len; i++) { const GstSDPAttribute *att = gst_sdp_media_get_attribute (media, i); gst_sdp_media_add_attribute (cp, att->key, att->value); } return GST_SDP_OK; } /** * gst_sdp_media_as_text: * @media: a #GstSDPMedia * * Convert the contents of @media to a text string. * * Returns: A dynamically allocated string representing the media. */ gchar * gst_sdp_media_as_text (const GstSDPMedia * media) { GString *lines; guint i; g_return_val_if_fail (media != NULL, NULL); lines = g_string_new (""); if (media->media) g_string_append_printf (lines, "m=%s", media->media); g_string_append_printf (lines, " %u", media->port); if (media->num_ports > 1) g_string_append_printf (lines, "/%u", media->num_ports); g_string_append_printf (lines, " %s", media->proto); for (i = 0; i < gst_sdp_media_formats_len (media); i++) g_string_append_printf (lines, " %s", gst_sdp_media_get_format (media, i)); g_string_append_printf (lines, "\r\n"); if (media->information) g_string_append_printf (lines, "i=%s", media->information); for (i = 0; i < gst_sdp_media_connections_len (media); i++) { const GstSDPConnection *conn = gst_sdp_media_get_connection (media, i); if (conn->nettype && conn->addrtype && conn->address) { g_string_append_printf (lines, "c=%s %s %s", conn->nettype, conn->addrtype, conn->address); if (gst_sdp_address_is_multicast (conn->nettype, conn->addrtype, conn->address)) { /* only add TTL for IP4 multicast */ if (strcmp (conn->addrtype, "IP4") == 0) g_string_append_printf (lines, "/%u", conn->ttl); if (conn->addr_number > 1) g_string_append_printf (lines, "/%u", conn->addr_number); } g_string_append_printf (lines, "\r\n"); } } for (i = 0; i < gst_sdp_media_bandwidths_len (media); i++) { const GstSDPBandwidth *bandwidth = gst_sdp_media_get_bandwidth (media, i); g_string_append_printf (lines, "b=%s:%u\r\n", bandwidth->bwtype, bandwidth->bandwidth); } if (media->key.type) { g_string_append_printf (lines, "k=%s", media->key.type); if (media->key.data) g_string_append_printf (lines, ":%s", media->key.data); g_string_append_printf (lines, "\r\n"); } for (i = 0; i < gst_sdp_media_attributes_len (media); i++) { const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i); if (attr->key) { g_string_append_printf (lines, "a=%s", attr->key); if (attr->value && attr->value[0] != '\0') g_string_append_printf (lines, ":%s", attr->value); g_string_append_printf (lines, "\r\n"); } } return g_string_free (lines, FALSE); } /** * gst_sdp_media_get_media: * @media: a #GstSDPMedia * * Get the media description of @media. * * Returns: the media description. */ const gchar * gst_sdp_media_get_media (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, NULL); return media->media; } /** * gst_sdp_media_set_media: * @media: a #GstSDPMedia * @med: the media description * * Set the media description of @media to @med. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_media_set_media (GstSDPMedia * media, const gchar * med) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (med != NULL, GST_SDP_EINVAL); g_free (media->media); media->media = g_strdup (med); return GST_SDP_OK; } /** * gst_sdp_media_get_port: * @media: a #GstSDPMedia * * Get the port number for @media. * * Returns: the port number of @media. */ guint gst_sdp_media_get_port (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, 0); return media->port; } /** * gst_sdp_media_get_num_ports: * @media: a #GstSDPMedia * * Get the number of ports for @media. * * Returns: the number of ports for @media. */ guint gst_sdp_media_get_num_ports (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, 0); return media->num_ports; } /** * gst_sdp_media_set_port_info: * @media: a #GstSDPMedia * @port: the port number * @num_ports: the number of ports * * Set the port information in @media. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_media_set_port_info (GstSDPMedia * media, guint port, guint num_ports) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); media->port = port; media->num_ports = num_ports; return GST_SDP_OK; } /** * gst_sdp_media_get_proto: * @media: a #GstSDPMedia * * Get the transport protocol of @media * * Returns: the transport protocol of @media. */ const gchar * gst_sdp_media_get_proto (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, NULL); return media->proto; } /** * gst_sdp_media_set_proto: * @media: a #GstSDPMedia * @proto: the media transport protocol * * Set the media transport protocol of @media to @proto. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_media_set_proto (GstSDPMedia * media, const gchar * proto) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_free (media->proto); media->proto = g_strdup (proto); return GST_SDP_OK; } /** * gst_sdp_media_formats_len: * @media: a #GstSDPMedia * * Get the number of formats in @media. * * Returns: the number of formats in @media. */ guint gst_sdp_media_formats_len (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, 0); return media->fmts->len; } /** * gst_sdp_media_get_format: * @media: a #GstSDPMedia * @idx: an index * * Get the format information at position @idx in @media. * * Returns: the format at position @idx. */ const gchar * gst_sdp_media_get_format (const GstSDPMedia * media, guint idx) { g_return_val_if_fail (media != NULL, NULL); if (idx >= media->fmts->len) return NULL; return g_array_index (media->fmts, gchar *, idx); } /** * gst_sdp_media_insert_format: * @media: a #GstSDPMedia * @idx: an index * @format: the format * * Insert the format information to @media at @idx. When @idx is -1, * the format is appended. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_insert_format (GstSDPMedia * media, gint idx, const gchar * format) { gchar *fmt; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (format != NULL, GST_SDP_EINVAL); fmt = g_strdup (format); if (idx == -1) g_array_append_val (media->fmts, fmt); else g_array_insert_val (media->fmts, idx, fmt); return GST_SDP_OK; } /** * gst_sdp_media_replace_format: * @media: a #GstSDPMedia * @idx: an index * @format: the format * * Replace the format information in @media at @idx with @format. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_replace_format (GstSDPMedia * media, guint idx, const gchar * format) { gchar **old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (format != NULL, GST_SDP_EINVAL); old = &g_array_index (media->fmts, gchar *, idx); g_free (*old); *old = g_strdup (format); return GST_SDP_OK; } /** * gst_sdp_media_remove_format: * @media: a #GstSDPMedia * @idx: an index * * Remove the format information in @media at @idx. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_remove_format (GstSDPMedia * media, guint idx) { gchar **old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); old = &g_array_index (media->fmts, gchar *, idx); g_free (*old); g_array_remove_index (media->fmts, idx); return GST_SDP_OK; } /** * gst_sdp_media_add_format: * @media: a #GstSDPMedia * @format: the format * * Add the format information to @media. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_media_add_format (GstSDPMedia * media, const gchar * format) { gchar *fmt; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (format != NULL, GST_SDP_EINVAL); fmt = g_strdup (format); g_array_append_val (media->fmts, fmt); return GST_SDP_OK; } /** * gst_sdp_media_get_information: * @media: a #GstSDPMedia * * Get the information of @media * * Returns: the information of @media. */ const gchar * gst_sdp_media_get_information (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, NULL); return media->information; } /** * gst_sdp_media_set_information: * @media: a #GstSDPMedia * @information: the media information * * Set the media information of @media to @information. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_media_set_information (GstSDPMedia * media, const gchar * information) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_free (media->information); media->information = g_strdup (information); return GST_SDP_OK; } /** * gst_sdp_connection_set: * @conn: a #GstSDPConnection * @nettype: the type of network. "IN" is defined to have the meaning * "Internet". * @addrtype: the type of address. * @address: the address * @ttl: the time to live of the address * @addr_number: the number of layers * * Set the connection with the given parameters. * * Returns: @GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_connection_set (GstSDPConnection * conn, const gchar * nettype, const gchar * addrtype, const gchar * address, guint ttl, guint addr_number) { g_return_val_if_fail (conn != NULL, GST_SDP_EINVAL); g_return_val_if_fail (nettype != NULL, GST_SDP_EINVAL); g_return_val_if_fail (addrtype != NULL, GST_SDP_EINVAL); g_return_val_if_fail (address != NULL, GST_SDP_EINVAL); conn->nettype = g_strdup (nettype); conn->addrtype = g_strdup (addrtype); conn->address = g_strdup (address); conn->ttl = ttl; conn->addr_number = addr_number; return GST_SDP_OK; } /** * gst_sdp_connection_clear: * @conn: a #GstSDPConnection * * Clear the connection. * * Returns: @GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_connection_clear (GstSDPConnection * conn) { g_return_val_if_fail (conn != NULL, GST_SDP_EINVAL); FREE_STRING (conn->nettype); FREE_STRING (conn->addrtype); FREE_STRING (conn->address); conn->ttl = 0; conn->addr_number = 0; return GST_SDP_OK; } /** * gst_sdp_media_connections_len: * @media: a #GstSDPMedia * * Get the number of connection fields in @media. * * Returns: the number of connections in @media. */ guint gst_sdp_media_connections_len (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, 0); return media->connections->len; } /** * gst_sdp_media_get_connection: * @media: a #GstSDPMedia * @idx: an index * * Get the connection at position @idx in @media. * * Returns: the #GstSDPConnection at position @idx. */ const GstSDPConnection * gst_sdp_media_get_connection (const GstSDPMedia * media, guint idx) { g_return_val_if_fail (media != NULL, NULL); g_return_val_if_fail (idx < media->connections->len, NULL); return &g_array_index (media->connections, GstSDPConnection, idx); } /** * gst_sdp_media_insert_connection: * @media: a #GstSDPMedia * @idx: an index * @conn: a #GstSDPConnection * * Insert the connection information to @media at @idx. When @idx is -1, * the connection is appended. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_insert_connection (GstSDPMedia * media, gint idx, GstSDPConnection * conn) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (conn != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx == -1 || idx < media->connections->len, GST_SDP_EINVAL); if (idx == -1) g_array_append_val (media->connections, *conn); else g_array_insert_val (media->connections, idx, *conn); return GST_SDP_OK; } /** * gst_sdp_media_replace_connection: * @media: a #GstSDPMedia * @idx: an index * @conn: a #GstSDPConnection * * Replace the connection information in @media at @idx with @conn. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_replace_connection (GstSDPMedia * media, guint idx, GstSDPConnection * conn) { GstSDPConnection *old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (conn != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx < media->connections->len, GST_SDP_EINVAL); old = &g_array_index (media->connections, GstSDPConnection, idx); gst_sdp_connection_clear (old); *old = *conn; return GST_SDP_OK; } /** * gst_sdp_media_remove_connection: * @media: a #GstSDPMedia * @idx: an index * * Remove the connection information in @media at @idx. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_remove_connection (GstSDPMedia * media, guint idx) { GstSDPConnection *old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx < media->connections->len, GST_SDP_EINVAL); old = &g_array_index (media->connections, GstSDPConnection, idx); gst_sdp_connection_clear (old); g_array_remove_index (media->connections, idx); return GST_SDP_OK; } /** * gst_sdp_media_add_connection: * @media: a #GstSDPMedia * @nettype: the type of network. "IN" is defined to have the meaning * "Internet". * @addrtype: the type of address. * @address: the address * @ttl: the time to live of the address * @addr_number: the number of layers * * Add the given connection parameters to @media. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_media_add_connection (GstSDPMedia * media, const gchar * nettype, const gchar * addrtype, const gchar * address, guint ttl, guint addr_number) { GstSDPConnection conn; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (nettype != NULL, GST_SDP_EINVAL); g_return_val_if_fail (addrtype != NULL, GST_SDP_EINVAL); g_return_val_if_fail (address != NULL, GST_SDP_EINVAL); gst_sdp_connection_set (&conn, nettype, addrtype, address, ttl, addr_number); g_array_append_val (media->connections, conn); return GST_SDP_OK; } /** * gst_sdp_media_bandwidths_len: * @media: a #GstSDPMedia * * Get the number of bandwidth fields in @media. * * Returns: the number of bandwidths in @media. */ guint gst_sdp_media_bandwidths_len (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, 0); return media->bandwidths->len; } /** * gst_sdp_media_get_bandwidth: * @media: a #GstSDPMedia * @idx: an index * * Get the bandwidth at position @idx in @media. * * Returns: the #GstSDPBandwidth at position @idx. */ const GstSDPBandwidth * gst_sdp_media_get_bandwidth (const GstSDPMedia * media, guint idx) { g_return_val_if_fail (media != NULL, NULL); return &g_array_index (media->bandwidths, GstSDPBandwidth, idx); } /** * gst_sdp_media_insert_bandwidth: * @media: a #GstSDPMedia * @idx: an index * @bw: a #GstSDPBandwidth * * Insert the bandwidth information to @media at @idx. When @idx is -1, * the bandwidth is appended. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_insert_bandwidth (GstSDPMedia * media, gint idx, GstSDPBandwidth * bw) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (bw != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx == -1 || idx < media->bandwidths->len, GST_SDP_EINVAL); if (idx == -1) g_array_append_val (media->bandwidths, *bw); else g_array_insert_val (media->bandwidths, idx, *bw); return GST_SDP_OK; } /** * gst_sdp_media_replace_bandwidth: * @media: a #GstSDPMedia * @idx: an index * @bw: a #GstSDPBandwidth * * Replace the bandwidth information in @media at @idx with @bw. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_replace_bandwidth (GstSDPMedia * media, guint idx, GstSDPBandwidth * bw) { GstSDPBandwidth *old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (bw != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx < media->bandwidths->len, GST_SDP_EINVAL); old = &g_array_index (media->bandwidths, GstSDPBandwidth, idx); gst_sdp_bandwidth_clear (old); *old = *bw; return GST_SDP_OK; } /** * gst_sdp_media_remove_bandwidth: * @media: a #GstSDPMedia * @idx: an index * * Remove the bandwidth information in @media at @idx. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_remove_bandwidth (GstSDPMedia * media, guint idx) { GstSDPBandwidth *old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx < media->bandwidths->len, GST_SDP_EINVAL); old = &g_array_index (media->bandwidths, GstSDPBandwidth, idx); gst_sdp_bandwidth_clear (old); g_array_remove_index (media->bandwidths, idx); return GST_SDP_OK; } /** * gst_sdp_media_add_bandwidth: * @media: a #GstSDPMedia * @bwtype: the bandwidth modifier type * @bandwidth: the bandwidth in kilobits per second * * Add the bandwidth information with @bwtype and @bandwidth to @media. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_media_add_bandwidth (GstSDPMedia * media, const gchar * bwtype, guint bandwidth) { GstSDPBandwidth bw; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (bwtype != NULL, GST_SDP_EINVAL); gst_sdp_bandwidth_set (&bw, bwtype, bandwidth); g_array_append_val (media->bandwidths, bw); return GST_SDP_OK; } /** * gst_sdp_media_set_key: * @media: a #GstSDPMedia * @type: the encryption type * @data: the encryption data * * Adds the encryption information to @media. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_media_set_key (GstSDPMedia * media, const gchar * type, const gchar * data) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_free (media->key.type); media->key.type = g_strdup (type); g_free (media->key.data); media->key.data = g_strdup (data); return GST_SDP_OK; } /** * gst_sdp_media_get_key: * @media: a #GstSDPMedia * * Get the encryption information from @media. * * Returns: a #GstSDPKey. */ const GstSDPKey * gst_sdp_media_get_key (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, NULL); return &media->key; } /** * gst_sdp_media_attributes_len: * @media: a #GstSDPMedia * * Get the number of attribute fields in @media. * * Returns: the number of attributes in @media. */ guint gst_sdp_media_attributes_len (const GstSDPMedia * media) { g_return_val_if_fail (media != NULL, 0); return media->attributes->len; } /** * gst_sdp_media_add_attribute: * @media: a #GstSDPMedia * @key: a key * @value: (nullable): a value * * Add the attribute with @key and @value to @media. * * Returns: #GST_SDP_OK. */ GstSDPResult gst_sdp_media_add_attribute (GstSDPMedia * media, const gchar * key, const gchar * value) { GstSDPAttribute attr; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (key != NULL, GST_SDP_EINVAL); gst_sdp_attribute_set (&attr, key, value); g_array_append_val (media->attributes, attr); return GST_SDP_OK; } /** * gst_sdp_media_get_attribute: * @media: a #GstSDPMedia * @idx: an index * * Get the attribute at position @idx in @media. * * Returns: the #GstSDPAttribute at position @idx. */ const GstSDPAttribute * gst_sdp_media_get_attribute (const GstSDPMedia * media, guint idx) { g_return_val_if_fail (media != NULL, NULL); g_return_val_if_fail (idx < media->attributes->len, NULL); return &g_array_index (media->attributes, GstSDPAttribute, idx); } /** * gst_sdp_media_get_attribute_val_n: * @media: a #GstSDPMedia * @key: a key * @nth: an index * * Get the @nth attribute value for @key in @media. * * Returns: the @nth attribute value. */ const gchar * gst_sdp_media_get_attribute_val_n (const GstSDPMedia * media, const gchar * key, guint nth) { guint i; g_return_val_if_fail (media != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); for (i = 0; i < media->attributes->len; i++) { GstSDPAttribute *attr; attr = &g_array_index (media->attributes, GstSDPAttribute, i); if (!strcmp (attr->key, key)) { if (nth == 0) return attr->value; else nth--; } } return NULL; } /** * gst_sdp_media_get_attribute_val: * @media: a #GstSDPMedia * @key: a key * * Get the first attribute value for @key in @media. * * Returns: the first attribute value for @key. */ const gchar * gst_sdp_media_get_attribute_val (const GstSDPMedia * media, const gchar * key) { g_return_val_if_fail (media != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); return gst_sdp_media_get_attribute_val_n (media, key, 0); } /** * gst_sdp_media_insert_attribute: * @media: a #GstSDPMedia * @idx: an index * @attr: a #GstSDPAttribute * * Insert the attribute to @media at @idx. When @idx is -1, * the attribute is appended. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_insert_attribute (GstSDPMedia * media, gint idx, GstSDPAttribute * attr) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (attr != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx == -1 || idx < media->attributes->len, GST_SDP_EINVAL); if (idx == -1) g_array_append_val (media->attributes, *attr); else g_array_insert_val (media->attributes, idx, *attr); return GST_SDP_OK; } /** * gst_sdp_media_replace_attribute: * @media: a #GstSDPMedia * @idx: an index * @attr: a #GstSDPAttribute * * Replace the attribute in @media at @idx with @attr. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_replace_attribute (GstSDPMedia * media, guint idx, GstSDPAttribute * attr) { GstSDPAttribute *old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (attr != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx < media->attributes->len, GST_SDP_EINVAL); old = &g_array_index (media->attributes, GstSDPAttribute, idx); gst_sdp_attribute_clear (old); *old = *attr; return GST_SDP_OK; } /** * gst_sdp_media_remove_attribute: * @media: a #GstSDPMedia * @idx: an index * * Remove the attribute in @media at @idx. * * Returns: #GST_SDP_OK. * * Since: 1.2 */ GstSDPResult gst_sdp_media_remove_attribute (GstSDPMedia * media, guint idx) { GstSDPAttribute *old; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (idx < media->attributes->len, GST_SDP_EINVAL); old = &g_array_index (media->attributes, GstSDPAttribute, idx); gst_sdp_attribute_clear (old); g_array_remove_index (media->attributes, idx); return GST_SDP_OK; } static void read_string (gchar * dest, guint size, gchar ** src) { guint idx; idx = 0; /* skip spaces */ while (g_ascii_isspace (**src)) (*src)++; while (!g_ascii_isspace (**src) && **src != '\0') { if (idx < size - 1) dest[idx++] = **src; (*src)++; } if (size > 0) dest[idx] = '\0'; } static void read_string_del (gchar * dest, guint size, gchar del, gchar ** src) { guint idx; idx = 0; /* skip spaces */ while (g_ascii_isspace (**src)) (*src)++; while (**src != del && **src != '\0') { if (idx < size - 1) dest[idx++] = **src; (*src)++; } if (size > 0) dest[idx] = '\0'; } enum { SDP_SESSION, SDP_MEDIA, }; typedef struct { guint state; GstSDPMessage *msg; GstSDPMedia *media; } SDPContext; static gboolean gst_sdp_parse_line (SDPContext * c, gchar type, gchar * buffer) { gchar str[8192]; gchar *p = buffer; #define READ_STRING(field) \ do { read_string (str, sizeof (str), &p); REPLACE_STRING (field, str); } while (0) #define READ_UINT(field) \ do { read_string (str, sizeof (str), &p); field = strtoul (str, NULL, 10); } while (0) switch (type) { case 'v': if (buffer[0] != '0') GST_WARNING ("wrong SDP version"); gst_sdp_message_set_version (c->msg, buffer); break; case 'o': READ_STRING (c->msg->origin.username); READ_STRING (c->msg->origin.sess_id); READ_STRING (c->msg->origin.sess_version); READ_STRING (c->msg->origin.nettype); READ_STRING (c->msg->origin.addrtype); READ_STRING (c->msg->origin.addr); break; case 's': REPLACE_STRING (c->msg->session_name, buffer); break; case 'i': if (c->state == SDP_SESSION) { REPLACE_STRING (c->msg->information, buffer); } else { REPLACE_STRING (c->media->information, buffer); } break; case 'u': REPLACE_STRING (c->msg->uri, buffer); break; case 'e': gst_sdp_message_add_email (c->msg, buffer); break; case 'p': gst_sdp_message_add_phone (c->msg, buffer); break; case 'c': { GstSDPConnection conn; gchar *str2; memset (&conn, 0, sizeof (conn)); str2 = p; while ((str2 = strchr (str2, '/'))) *str2++ = ' '; READ_STRING (conn.nettype); READ_STRING (conn.addrtype); READ_STRING (conn.address); /* only read TTL for IP4 */ if (strcmp (conn.addrtype, "IP4") == 0) READ_UINT (conn.ttl); READ_UINT (conn.addr_number); if (c->state == SDP_SESSION) { gst_sdp_message_set_connection (c->msg, conn.nettype, conn.addrtype, conn.address, conn.ttl, conn.addr_number); } else { gst_sdp_media_add_connection (c->media, conn.nettype, conn.addrtype, conn.address, conn.ttl, conn.addr_number); } gst_sdp_connection_clear (&conn); break; } case 'b': { gchar str2[32]; read_string_del (str, sizeof (str), ':', &p); if (*p != '\0') p++; read_string (str2, sizeof (str2), &p); if (c->state == SDP_SESSION) gst_sdp_message_add_bandwidth (c->msg, str, atoi (str2)); else gst_sdp_media_add_bandwidth (c->media, str, atoi (str2)); break; } case 't': break; case 'k': read_string_del (str, sizeof (str), ':', &p); if (*p != '\0') p++; if (c->state == SDP_SESSION) gst_sdp_message_set_key (c->msg, str, p); else gst_sdp_media_set_key (c->media, str, p); break; case 'a': read_string_del (str, sizeof (str), ':', &p); if (*p != '\0') p++; if (c->state == SDP_SESSION) gst_sdp_message_add_attribute (c->msg, str, p); else gst_sdp_media_add_attribute (c->media, str, p); break; case 'm': { gchar *slash; GstSDPMedia nmedia; c->state = SDP_MEDIA; memset (&nmedia, 0, sizeof (nmedia)); gst_sdp_media_init (&nmedia); /* m= / ... */ READ_STRING (nmedia.media); read_string (str, sizeof (str), &p); slash = g_strrstr (str, "/"); if (slash) { *slash = '\0'; nmedia.port = atoi (str); nmedia.num_ports = atoi (slash + 1); } else { nmedia.port = atoi (str); nmedia.num_ports = 0; } READ_STRING (nmedia.proto); do { read_string (str, sizeof (str), &p); gst_sdp_media_add_format (&nmedia, str); } while (*p != '\0'); gst_sdp_message_add_media (c->msg, &nmedia); c->media = &g_array_index (c->msg->medias, GstSDPMedia, c->msg->medias->len - 1); break; } default: break; } return TRUE; } /** * gst_sdp_message_parse_buffer: * @data: (array length=size): the start of the buffer * @size: the size of the buffer * @msg: the result #GstSDPMessage * * Parse the contents of @size bytes pointed to by @data and store the result in * @msg. * * Returns: #GST_SDP_OK on success. */ GstSDPResult gst_sdp_message_parse_buffer (const guint8 * data, guint size, GstSDPMessage * msg) { gchar *p, *s; SDPContext c; gchar type; gchar *buffer = NULL; guint bufsize = 0; guint len = 0; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); g_return_val_if_fail (data != NULL, GST_SDP_EINVAL); g_return_val_if_fail (size != 0, GST_SDP_EINVAL); c.state = SDP_SESSION; c.msg = msg; c.media = NULL; #define SIZE_CHECK_GUARD \ G_STMT_START { \ if (p - (gchar *) data >= size) \ goto out; \ } G_STMT_END p = (gchar *) data; while (TRUE) { while (p - (gchar *) data < size && g_ascii_isspace (*p)) p++; SIZE_CHECK_GUARD; type = *p++; if (type == '\0') break; SIZE_CHECK_GUARD; if (*p != '=') goto line_done; p++; SIZE_CHECK_GUARD; s = p; while (p - (gchar *) data < size && *p != '\n' && *p != '\r' && *p != '\0') p++; len = p - s; if (bufsize <= len) { buffer = g_realloc (buffer, len + 1); bufsize = len + 1; } memcpy (buffer, s, len); buffer[len] = '\0'; gst_sdp_parse_line (&c, type, buffer); SIZE_CHECK_GUARD; line_done: while (p - (gchar *) data < size && *p != '\n' && *p != '\0') p++; SIZE_CHECK_GUARD; if (*p == '\n') p++; } #undef SIZE_CHECK_GUARD out: g_free (buffer); return GST_SDP_OK; } static void print_media (GstSDPMedia * media) { g_print (" media: '%s'\n", GST_STR_NULL (media->media)); g_print (" port: '%u'\n", media->port); g_print (" num_ports: '%u'\n", media->num_ports); g_print (" proto: '%s'\n", GST_STR_NULL (media->proto)); if (media->fmts->len > 0) { guint i; g_print (" formats:\n"); for (i = 0; i < media->fmts->len; i++) { g_print (" format '%s'\n", g_array_index (media->fmts, gchar *, i)); } } g_print (" information: '%s'\n", GST_STR_NULL (media->information)); if (media->connections->len > 0) { guint i; g_print (" connections:\n"); for (i = 0; i < media->connections->len; i++) { GstSDPConnection *conn = &g_array_index (media->connections, GstSDPConnection, i); g_print (" nettype: '%s'\n", GST_STR_NULL (conn->nettype)); g_print (" addrtype: '%s'\n", GST_STR_NULL (conn->addrtype)); g_print (" address: '%s'\n", GST_STR_NULL (conn->address)); g_print (" ttl: '%u'\n", conn->ttl); g_print (" addr_number: '%u'\n", conn->addr_number); } } if (media->bandwidths->len > 0) { guint i; g_print (" bandwidths:\n"); for (i = 0; i < media->bandwidths->len; i++) { GstSDPBandwidth *bw = &g_array_index (media->bandwidths, GstSDPBandwidth, i); g_print (" type: '%s'\n", GST_STR_NULL (bw->bwtype)); g_print (" bandwidth: '%u'\n", bw->bandwidth); } } g_print (" key:\n"); g_print (" type: '%s'\n", GST_STR_NULL (media->key.type)); g_print (" data: '%s'\n", GST_STR_NULL (media->key.data)); if (media->attributes->len > 0) { guint i; g_print (" attributes:\n"); for (i = 0; i < media->attributes->len; i++) { GstSDPAttribute *attr = &g_array_index (media->attributes, GstSDPAttribute, i); g_print (" attribute '%s' : '%s'\n", attr->key, attr->value); } } } /** * gst_sdp_message_dump: * @msg: a #GstSDPMessage * * Dump the parsed contents of @msg to stdout. * * Returns: a #GstSDPResult. */ GstSDPResult gst_sdp_message_dump (const GstSDPMessage * msg) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); g_print ("sdp packet %p:\n", msg); g_print (" version: '%s'\n", GST_STR_NULL (msg->version)); g_print (" origin:\n"); g_print (" username: '%s'\n", GST_STR_NULL (msg->origin.username)); g_print (" sess_id: '%s'\n", GST_STR_NULL (msg->origin.sess_id)); g_print (" sess_version: '%s'\n", GST_STR_NULL (msg->origin.sess_version)); g_print (" nettype: '%s'\n", GST_STR_NULL (msg->origin.nettype)); g_print (" addrtype: '%s'\n", GST_STR_NULL (msg->origin.addrtype)); g_print (" addr: '%s'\n", GST_STR_NULL (msg->origin.addr)); g_print (" session_name: '%s'\n", GST_STR_NULL (msg->session_name)); g_print (" information: '%s'\n", GST_STR_NULL (msg->information)); g_print (" uri: '%s'\n", GST_STR_NULL (msg->uri)); if (msg->emails->len > 0) { guint i; g_print (" emails:\n"); for (i = 0; i < msg->emails->len; i++) { g_print (" email '%s'\n", g_array_index (msg->emails, gchar *, i)); } } if (msg->phones->len > 0) { guint i; g_print (" phones:\n"); for (i = 0; i < msg->phones->len; i++) { g_print (" phone '%s'\n", g_array_index (msg->phones, gchar *, i)); } } g_print (" connection:\n"); g_print (" nettype: '%s'\n", GST_STR_NULL (msg->connection.nettype)); g_print (" addrtype: '%s'\n", GST_STR_NULL (msg->connection.addrtype)); g_print (" address: '%s'\n", GST_STR_NULL (msg->connection.address)); g_print (" ttl: '%u'\n", msg->connection.ttl); g_print (" addr_number: '%u'\n", msg->connection.addr_number); if (msg->bandwidths->len > 0) { guint i; g_print (" bandwidths:\n"); for (i = 0; i < msg->bandwidths->len; i++) { GstSDPBandwidth *bw = &g_array_index (msg->bandwidths, GstSDPBandwidth, i); g_print (" type: '%s'\n", GST_STR_NULL (bw->bwtype)); g_print (" bandwidth: '%u'\n", bw->bandwidth); } } g_print (" key:\n"); g_print (" type: '%s'\n", GST_STR_NULL (msg->key.type)); g_print (" data: '%s'\n", GST_STR_NULL (msg->key.data)); if (msg->attributes->len > 0) { guint i; g_print (" attributes:\n"); for (i = 0; i < msg->attributes->len; i++) { GstSDPAttribute *attr = &g_array_index (msg->attributes, GstSDPAttribute, i); g_print (" attribute '%s' : '%s'\n", attr->key, attr->value); } } if (msg->medias->len > 0) { guint i; g_print (" medias:\n"); for (i = 0; i < msg->medias->len; i++) { g_print (" media %u:\n", i); print_media (&g_array_index (msg->medias, GstSDPMedia, i)); } } return GST_SDP_OK; } static const gchar * gst_sdp_get_attribute_for_pt (const GstSDPMedia * media, const gchar * name, gint pt) { guint i; for (i = 0;; i++) { const gchar *attr; gint val; if ((attr = gst_sdp_media_get_attribute_val_n (media, name, i)) == NULL) break; if (sscanf (attr, "%d ", &val) != 1) continue; if (val == pt) return attr; } return NULL; } /* this may modify the input string (and resets) */ #define PARSE_INT(p, del, res) \ G_STMT_START { \ gchar *t = p; \ p = strstr (p, del); \ if (p == NULL) \ res = -1; \ else { \ char prev = *p; \ *p = '\0'; \ res = atoi (t); \ *p = prev; \ p++; \ } \ } G_STMT_END /* this may modify the string without reset */ #define PARSE_STRING(p, del, res) \ G_STMT_START { \ gchar *t = p; \ p = strstr (p, del); \ if (p == NULL) { \ res = NULL; \ p = t; \ } \ else { \ *p = '\0'; \ p++; \ res = t; \ } \ } G_STMT_END #define SKIP_SPACES(p) \ while (*p && g_ascii_isspace (*p)) \ p++; /* rtpmap contains: * * /[/] */ static gboolean gst_sdp_parse_rtpmap (const gchar * rtpmap, gint * payload, gchar ** name, gint * rate, gchar ** params) { gchar *p, *t, *orig_value; p = orig_value = g_strdup (rtpmap); PARSE_INT (p, " ", *payload); if (*payload == -1) goto fail; SKIP_SPACES (p); if (*p == '\0') goto fail; PARSE_STRING (p, "/", *name); if (*name == NULL) { GST_DEBUG ("no rate, name %s", p); /* no rate, assume -1 then, this is not supposed to happen but RealMedia * streams seem to omit the rate. */ *name = g_strdup (p); *rate = -1; *params = NULL; goto out; } else { *name = strdup (*name); } t = p; p = strstr (p, "/"); if (p == NULL) { *rate = atoi (t); *params = NULL; goto out; } p++; *rate = atoi (t); if (*p == '\0') *params = NULL; else *params = g_strdup (p); out: g_free (orig_value); return TRUE; fail: g_free (orig_value); return FALSE; } /** * gst_sdp_media_add_rtcp_fb_attributes_from_media: * @media: a #GstSDPMedia * @pt: payload type * @caps: a #GstCaps * * Parse given @media for "rtcp-fb" attributes and add it to the @caps. * * Mapping of caps from SDP fields: * * a=rtcp-fb:(payload) (param1) [param2]... * * Returns: a #GstSDPResult. */ static GstSDPResult gst_sdp_media_add_rtcp_fb_attributes_from_media (const GstSDPMedia * media, gint pt, GstCaps * caps) { const gchar *rtcp_fb; gchar *p, *to_free; gint payload, i; GstStructure *s; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); s = gst_caps_get_structure (caps, 0); for (i = 0;; i++) { gboolean all_formats = FALSE; if ((rtcp_fb = gst_sdp_media_get_attribute_val_n (media, "rtcp-fb", i)) == NULL) break; /* p is now of the format attr... */ to_free = p = g_strdup (rtcp_fb); /* check if it applies to all formats */ if (*p == '*') { p++; all_formats = TRUE; } else { PARSE_INT (p, " ", payload); } if (all_formats || (payload != -1 && payload == pt)) { gchar *tmp, *key; SKIP_SPACES (p); /* replace space with '-' */ key = g_strdup_printf ("rtcp-fb-%s", p); for (tmp = key; (tmp = strchr (tmp, ' ')); ++tmp) { *tmp = '-'; } gst_structure_set (s, key, G_TYPE_BOOLEAN, TRUE, NULL); GST_DEBUG ("adding caps: %s=TRUE", key); g_free (key); } g_free (to_free); } return GST_SDP_OK; } static void gst_sdp_media_caps_adjust_h264 (GstCaps * caps) { long int spsint; guint8 sps[2]; const gchar *profile_level_id; GstStructure *s = gst_caps_get_structure (caps, 0); if (g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "H264") || g_strcmp0 (gst_structure_get_string (s, "level-asymmetry-allowed"), "1")) return; profile_level_id = gst_structure_get_string (s, "profile-level-id"); if (!profile_level_id) return; spsint = strtol (profile_level_id, NULL, 16); sps[0] = spsint >> 16; sps[1] = spsint >> 8; GST_DEBUG ("'level-asymmetry-allowed' is set so we shouldn't care about " "'profile-level-id' and only set a 'profile' instead"); gst_structure_set (s, "profile", G_TYPE_STRING, gst_codec_utils_h264_get_profile (sps, 2), NULL); gst_structure_remove_fields (s, "level-asymmetry-allowed", "profile-level-id", NULL); } /** * gst_sdp_media_get_caps_from_media: * @media: a #GstSDPMedia * @pt: a payload type * * Mapping of caps from SDP fields: * * a=rtpmap:(payload) (encoding_name)/(clock_rate)[/(encoding_params)] * * a=framesize:(payload) (width)-(height) * * a=fmtp:(payload) (param)[=(value)];... * * Note that the extmap attribute is set only by gst_sdp_media_attributes_to_caps(). * * Returns: a #GstCaps, or %NULL if an error happened * * Since: 1.8 */ GstCaps * gst_sdp_media_get_caps_from_media (const GstSDPMedia * media, gint pt) { GstCaps *caps; const gchar *rtpmap; gchar *fmtp = NULL; gchar *framesize = NULL; gchar *name = NULL; gint rate = -1; gchar *params = NULL; gchar *tmp; GstStructure *s; gint payload = 0; gboolean ret; g_return_val_if_fail (media != NULL, NULL); /* get and parse rtpmap */ rtpmap = gst_sdp_get_attribute_for_pt (media, "rtpmap", pt); if (rtpmap) { ret = gst_sdp_parse_rtpmap (rtpmap, &payload, &name, &rate, ¶ms); if (!ret) { GST_ERROR ("error parsing rtpmap, ignoring"); rtpmap = NULL; } } /* dynamic payloads need rtpmap or we fail */ if (rtpmap == NULL && pt >= 96) goto no_rtpmap; /* check if we have a rate, if not, we need to look up the rate from the * default rates based on the payload types. */ if (rate == -1) { const GstRTPPayloadInfo *info; if (GST_RTP_PAYLOAD_IS_DYNAMIC (pt)) { /* dynamic types, use media and encoding_name */ tmp = g_ascii_strdown (media->media, -1); info = gst_rtp_payload_info_for_name (tmp, name); g_free (tmp); } else { /* static types, use payload type */ info = gst_rtp_payload_info_for_pt (pt); } if (info) { if ((rate = info->clock_rate) == 0) rate = -1; } /* we fail if we cannot find one */ if (rate == -1) goto no_rate; } tmp = g_ascii_strdown (media->media, -1); caps = gst_caps_new_simple ("application/x-unknown", "media", G_TYPE_STRING, tmp, "payload", G_TYPE_INT, pt, NULL); g_free (tmp); s = gst_caps_get_structure (caps, 0); gst_structure_set (s, "clock-rate", G_TYPE_INT, rate, NULL); /* encoding name must be upper case */ if (name != NULL) { tmp = g_ascii_strup (name, -1); gst_structure_set (s, "encoding-name", G_TYPE_STRING, tmp, NULL); g_free (tmp); } /* params must be lower case */ if (params != NULL) { tmp = g_ascii_strdown (params, -1); gst_structure_set (s, "encoding-params", G_TYPE_STRING, tmp, NULL); g_free (tmp); } /* parse optional fmtp: field */ if ((fmtp = g_strdup (gst_sdp_get_attribute_for_pt (media, "fmtp", pt)))) { gchar *p; gint payload = 0; p = fmtp; /* p is now of the format [=];... */ PARSE_INT (p, " ", payload); if (payload != -1 && payload == pt) { gchar **pairs; gint i; /* [=] are separated with ';' */ pairs = g_strsplit (p, ";", 0); for (i = 0; pairs[i]; i++) { gchar *valpos; const gchar *val, *key; gint j; const gchar *reserved_keys[] = { "media", "payload", "clock-rate", "encoding-name", "encoding-params" }; /* the key may not have a '=', the value can have other '='s */ valpos = strstr (pairs[i], "="); if (valpos) { /* we have a '=' and thus a value, remove the '=' with \0 */ *valpos = '\0'; /* value is everything between '=' and ';'. We split the pairs at ; * boundaries so we can take the remainder of the value. Some servers * put spaces around the value which we strip off here. Alternatively * we could strip those spaces in the depayloaders should these spaces * actually carry any meaning in the future. */ val = g_strstrip (valpos + 1); } else { /* simple ;.. is translated into =1;... */ val = "1"; } /* strip the key of spaces, convert key to lowercase but not the value. */ key = g_strstrip (pairs[i]); /* skip keys from the fmtp, which we already use ourselves for the * caps. Some software is adding random things like clock-rate into * the fmtp, and we would otherwise here set a string-typed clock-rate * in the caps... and thus fail to create valid RTP caps */ for (j = 0; j < G_N_ELEMENTS (reserved_keys); j++) { if (g_ascii_strcasecmp (reserved_keys[j], key) == 0) { key = ""; break; } } if (strlen (key) > 0) { tmp = g_ascii_strdown (key, -1); gst_structure_set (s, tmp, G_TYPE_STRING, val, NULL); g_free (tmp); } } g_strfreev (pairs); } } /* parse framesize: field */ if ((framesize = g_strdup (gst_sdp_media_get_attribute_val (media, "framesize")))) { gchar *p; /* p is now of the format - */ p = framesize; PARSE_INT (p, " ", payload); if (payload != -1 && payload == pt) { gst_structure_set (s, "a-framesize", G_TYPE_STRING, p, NULL); } } gst_sdp_media_caps_adjust_h264 (caps); /* parse rtcp-fb: field */ gst_sdp_media_add_rtcp_fb_attributes_from_media (media, pt, caps); out: g_free (framesize); g_free (fmtp); g_free (name); g_free (params); return caps; /* ERRORS */ no_rtpmap: { GST_ERROR ("rtpmap type not given for dynamic payload %d", pt); caps = NULL; goto out; } no_rate: { GST_ERROR ("rate unknown for payload type %d", pt); caps = NULL; goto out; } } /** * gst_sdp_media_set_media_from_caps: * @caps: a #GstCaps * @media: a #GstSDPMedia * * Mapping of caps to SDP fields: * * a=rtpmap:(payload) (encoding_name) or (clock_rate)[or (encoding_params)] * * a=framesize:(payload) (width)-(height) * * a=fmtp:(payload) (param)[=(value)];... * * a=rtcp-fb:(payload) (param1) [param2]... * * a=extmap:(id)[/direction] (extensionname) (extensionattributes) * * Returns: a #GstSDPResult. * * Since: 1.8 */ GstSDPResult gst_sdp_media_set_media_from_caps (const GstCaps * caps, GstSDPMedia * media) { const gchar *caps_str, *caps_enc, *caps_params; gchar *tmp; gint caps_pt, caps_rate; guint n_fields, j; gboolean first, nack, nack_pli, ccm_fir, transport_cc; GString *fmtp; GstStructure *s; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); s = gst_caps_get_structure (caps, 0); if (s == NULL) { GST_ERROR ("ignoring stream without media type"); goto error; } /* get media type and payload for the m= line */ caps_str = gst_structure_get_string (s, "media"); gst_sdp_media_set_media (media, caps_str); gst_structure_get_int (s, "payload", &caps_pt); tmp = g_strdup_printf ("%d", caps_pt); gst_sdp_media_add_format (media, tmp); g_free (tmp); /* get clock-rate, media type and params for the rtpmap attribute */ gst_structure_get_int (s, "clock-rate", &caps_rate); caps_enc = gst_structure_get_string (s, "encoding-name"); caps_params = gst_structure_get_string (s, "encoding-params"); if (caps_enc) { if (caps_params) tmp = g_strdup_printf ("%d %s/%d/%s", caps_pt, caps_enc, caps_rate, caps_params); else tmp = g_strdup_printf ("%d %s/%d", caps_pt, caps_enc, caps_rate); gst_sdp_media_add_attribute (media, "rtpmap", tmp); g_free (tmp); } /* get rtcp-fb attributes */ if (gst_structure_get_boolean (s, "rtcp-fb-nack", &nack)) { if (nack) { tmp = g_strdup_printf ("%d nack", caps_pt); gst_sdp_media_add_attribute (media, "rtcp-fb", tmp); g_free (tmp); GST_DEBUG ("adding rtcp-fb-nack to pt=%d", caps_pt); } } if (gst_structure_get_boolean (s, "rtcp-fb-nack-pli", &nack_pli)) { if (nack_pli) { tmp = g_strdup_printf ("%d nack pli", caps_pt); gst_sdp_media_add_attribute (media, "rtcp-fb", tmp); g_free (tmp); GST_DEBUG ("adding rtcp-fb-nack-pli to pt=%d", caps_pt); } } if (gst_structure_get_boolean (s, "rtcp-fb-ccm-fir", &ccm_fir)) { if (ccm_fir) { tmp = g_strdup_printf ("%d ccm fir", caps_pt); gst_sdp_media_add_attribute (media, "rtcp-fb", tmp); g_free (tmp); GST_DEBUG ("adding rtcp-fb-ccm-fir to pt=%d", caps_pt); } } if (gst_structure_get_boolean (s, "rtcp-fb-transport-cc", &transport_cc)) { if (transport_cc) { tmp = g_strdup_printf ("%d transport-cc", caps_pt); gst_sdp_media_add_attribute (media, "rtcp-fb", tmp); g_free (tmp); GST_DEBUG ("adding rtcp-fb-transport-cc to pt=%d", caps_pt); } } /* collect all other properties and add them to fmtp, extmap or attributes */ fmtp = g_string_new (""); g_string_append_printf (fmtp, "%d ", caps_pt); first = TRUE; n_fields = gst_structure_n_fields (s); for (j = 0; j < n_fields; j++) { const gchar *fname, *fval; fname = gst_structure_nth_field_name (s, j); /* filter out standard properties */ if (!strcmp (fname, "media")) continue; if (!strcmp (fname, "payload")) continue; if (!strcmp (fname, "clock-rate")) continue; if (!strcmp (fname, "encoding-name")) continue; if (!strcmp (fname, "encoding-params")) continue; if (!strcmp (fname, "ssrc")) continue; if (!strcmp (fname, "timestamp-offset")) continue; if (!strcmp (fname, "seqnum-offset")) continue; if (g_str_has_prefix (fname, "srtp-")) continue; if (g_str_has_prefix (fname, "srtcp-")) continue; /* handled later */ if (g_str_has_prefix (fname, "x-gst-rtsp-server-rtx-time")) continue; if (g_str_has_prefix (fname, "rtcp-fb-")) continue; if (!strcmp (fname, "a-framesize")) { /* a-framesize attribute */ if ((fval = gst_structure_get_string (s, fname))) { tmp = g_strdup_printf ("%d %s", caps_pt, fval); gst_sdp_media_add_attribute (media, fname + 2, tmp); g_free (tmp); } continue; } if (g_str_has_prefix (fname, "a-")) { /* attribute */ if ((fval = gst_structure_get_string (s, fname))) gst_sdp_media_add_attribute (media, fname + 2, fval); continue; } if (g_str_has_prefix (fname, "x-")) { /* attribute */ if ((fval = gst_structure_get_string (s, fname))) gst_sdp_media_add_attribute (media, fname, fval); continue; } /* extmap */ if (g_str_has_prefix (fname, "extmap-")) { gchar *endptr; guint id = strtoull (fname + 7, &endptr, 10); const GValue *arr; if (*endptr != '\0' || id == 0 || id == 15 || id > 9999) continue; if ((fval = gst_structure_get_string (s, fname))) { gchar *extmap = g_strdup_printf ("%u %s", id, fval); gst_sdp_media_add_attribute (media, "extmap", extmap); g_free (extmap); } else if ((arr = gst_structure_get_value (s, fname)) && G_VALUE_HOLDS (arr, GST_TYPE_ARRAY) && gst_value_array_get_size (arr) == 3) { const GValue *val; const gchar *direction, *extensionname, *extensionattributes; val = gst_value_array_get_value (arr, 0); direction = g_value_get_string (val); val = gst_value_array_get_value (arr, 1); extensionname = g_value_get_string (val); val = gst_value_array_get_value (arr, 2); extensionattributes = g_value_get_string (val); if (!extensionname || *extensionname == '\0') continue; if (direction && *direction != '\0' && extensionattributes && *extensionattributes != '\0') { gchar *extmap = g_strdup_printf ("%u/%s %s %s", id, direction, extensionname, extensionattributes); gst_sdp_media_add_attribute (media, "extmap", extmap); g_free (extmap); } else if (direction && *direction != '\0') { gchar *extmap = g_strdup_printf ("%u/%s %s", id, direction, extensionname); gst_sdp_media_add_attribute (media, "extmap", extmap); g_free (extmap); } else if (extensionattributes && *extensionattributes != '\0') { gchar *extmap = g_strdup_printf ("%u %s %s", id, extensionname, extensionattributes); gst_sdp_media_add_attribute (media, "extmap", extmap); g_free (extmap); } else { gchar *extmap = g_strdup_printf ("%u %s", id, extensionname); gst_sdp_media_add_attribute (media, "extmap", extmap); g_free (extmap); } } continue; } /* rid values */ if (g_str_has_prefix (fname, "rid-")) { const char *rid_id = &fname[strlen ("rid-")]; const GValue *arr; if (!rid_id || !*rid_id) continue; if ((fval = gst_structure_get_string (s, fname))) { char *rid_val = g_strdup_printf ("%s %s", rid_id, fval); gst_sdp_media_add_attribute (media, "rid", rid_val); g_free (rid_val); } else if ((arr = gst_structure_get_value (s, fname)) && GST_VALUE_HOLDS_ARRAY (arr) && gst_value_array_get_size (arr) > 1) { const gchar *direction, *param; GString *str; guint i, n; str = g_string_new (NULL); g_string_append_printf (str, "%s ", rid_id); n = gst_value_array_get_size (arr); for (i = 0; i < n; i++) { const GValue *val = gst_value_array_get_value (arr, i); if (i == 0) { direction = g_value_get_string (val); g_string_append_printf (str, "%s", direction); } else { param = g_value_get_string (val); if (i == 1) g_string_append_c (str, ' '); else g_string_append_c (str, ';'); g_string_append_printf (str, "%s", param); } } gst_sdp_media_add_attribute (media, "rid", str->str); g_string_free (str, TRUE); } else { GST_WARNING ("caps field %s is an unsupported format", fname); } continue; } if ((fval = gst_structure_get_string (s, fname))) { /* "profile" is our internal representation of the notion of * "level-asymmetry-allowed" with caps, convert it back to the SDP * representation */ if (!g_strcmp0 (gst_structure_get_string (s, "encoding-name"), "H264") && !g_strcmp0 (fname, "profile")) { fname = "level-asymmetry-allowed"; fval = "1"; } g_string_append_printf (fmtp, "%s%s=%s", first ? "" : ";", fname, fval); first = FALSE; } } if (!first) { tmp = g_string_free (fmtp, FALSE); gst_sdp_media_add_attribute (media, "fmtp", tmp); g_free (tmp); } else { g_string_free (fmtp, TRUE); } return GST_SDP_OK; /* ERRORS */ error: { GST_DEBUG ("ignoring stream"); return GST_SDP_EINVAL; } } /** * gst_sdp_make_keymgmt: * @uri: a #gchar URI * @base64: a #gchar base64-encoded key data * * Makes key management data * * Returns: (transfer full): a #gchar key-mgmt data, * * Since: 1.8 */ gchar * gst_sdp_make_keymgmt (const gchar * uri, const gchar * base64) { g_return_val_if_fail (uri != NULL, NULL); g_return_val_if_fail (base64 != NULL, NULL); return g_strdup_printf ("prot=mikey;uri=\"%s\";data=\"%s\"", uri, base64); } static gboolean gst_sdp_parse_keymgmt (const gchar * keymgmt, GstMIKEYMessage ** mikey) { gsize size; guchar *data; gchar *orig_value; gchar *p, *kmpid; p = orig_value = g_strdup (keymgmt); SKIP_SPACES (p); if (*p == '\0') { g_free (orig_value); return FALSE; } PARSE_STRING (p, " ", kmpid); if (kmpid == NULL || !g_str_equal (kmpid, "mikey")) { g_free (orig_value); return FALSE; } data = g_base64_decode (p, &size); g_free (orig_value); /* Don't need this any more */ if (data == NULL) return FALSE; *mikey = gst_mikey_message_new_from_data (data, size, NULL, NULL); g_free (data); return (*mikey != NULL); } static GstSDPResult sdp_add_attributes_to_keymgmt (GArray * attributes, GstMIKEYMessage ** mikey) { GstSDPResult res = GST_SDP_OK; if (attributes->len > 0) { guint i; for (i = 0; i < attributes->len; i++) { GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i); if (g_str_equal (attr->key, "key-mgmt")) { res = gst_sdp_parse_keymgmt (attr->value, mikey); break; } } } return res; } /** * gst_sdp_message_parse_keymgmt: * @msg: a #GstSDPMessage * @mikey: (out) (transfer full): pointer to new #GstMIKEYMessage * * Creates a new #GstMIKEYMessage after parsing the key-mgmt attribute * from a #GstSDPMessage. * * Returns: a #GstSDPResult. * * Since: 1.8.1 */ GstSDPResult gst_sdp_message_parse_keymgmt (const GstSDPMessage * msg, GstMIKEYMessage ** mikey) { g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); return sdp_add_attributes_to_keymgmt (msg->attributes, mikey); } /** * gst_sdp_media_parse_keymgmt: * @media: a #GstSDPMedia * @mikey: (out) (transfer full): pointer to new #GstMIKEYMessage * * Creates a new #GstMIKEYMessage after parsing the key-mgmt attribute * from a #GstSDPMedia. * * Returns: a #GstSDPResult. * * Since: 1.8.1 */ GstSDPResult gst_sdp_media_parse_keymgmt (const GstSDPMedia * media, GstMIKEYMessage ** mikey) { g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); return sdp_add_attributes_to_keymgmt (media->attributes, mikey); } static GstSDPResult sdp_add_attributes_to_caps (GArray * attributes, GstCaps * caps) { if (attributes->len > 0) { GstStructure *s; guint i; s = gst_caps_get_structure (caps, 0); for (i = 0; i < attributes->len; i++) { GstSDPAttribute *attr = &g_array_index (attributes, GstSDPAttribute, i); gchar *tofree, *key; key = attr->key; /* skip some of the attribute we already handle */ if (!strcmp (key, "fmtp")) continue; if (!strcmp (key, "rtpmap")) continue; if (!strcmp (key, "control")) continue; if (!strcmp (key, "range")) continue; if (!strcmp (key, "framesize")) continue; if (!strcmp (key, "key-mgmt")) continue; if (!strcmp (key, "extmap")) continue; if (!strcmp (key, "rid")) continue; /* string must be valid UTF8 */ if (!g_utf8_validate (attr->value, -1, NULL)) continue; if (!g_str_has_prefix (key, "x-")) tofree = key = g_strdup_printf ("a-%s", key); else tofree = NULL; GST_DEBUG ("adding caps: %s=%s", key, attr->value); gst_structure_set (s, key, G_TYPE_STRING, attr->value, NULL); g_free (tofree); } } return GST_SDP_OK; } static GstSDPResult gst_sdp_media_add_extmap_attributes (GArray * attributes, GstCaps * caps) { const gchar *extmap; gchar *p, *tmp, *to_free; guint id, i; GstStructure *s; g_return_val_if_fail (attributes != NULL, GST_SDP_EINVAL); g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); s = gst_caps_get_structure (caps, 0); for (i = 0; i < attributes->len; i++) { GstSDPAttribute *attr; const gchar *direction, *extensionname, *extensionattributes; attr = &g_array_index (attributes, GstSDPAttribute, i); if (strcmp (attr->key, "extmap") != 0) continue; extmap = attr->value; /* p is now of the format id[/direction] extensionname [extensionattributes] */ to_free = p = g_strdup (extmap); id = strtoul (p, &tmp, 10); if (id == 0 || id == 15 || id > 9999 || (*tmp != ' ' && *tmp != '/')) { GST_ERROR ("Invalid extmap '%s'", to_free); goto next; } else if (*tmp == '/') { p = tmp; p++; PARSE_STRING (p, " ", direction); /* Invalid format */ if (direction == NULL || *direction == '\0') { GST_ERROR ("Invalid extmap '%s'", to_free); goto next; } } else { /* At the space */ p = tmp; direction = ""; } SKIP_SPACES (p); tmp = strstr (p, " "); if (tmp == NULL) { extensionname = p; extensionattributes = ""; } else { extensionname = p; *tmp = '\0'; p = tmp + 1; SKIP_SPACES (p); extensionattributes = p; } if (extensionname == NULL || *extensionname == '\0') { GST_ERROR ("Invalid extmap '%s'", to_free); goto next; } if (*direction != '\0' || *extensionattributes != '\0') { GValue arr = G_VALUE_INIT; GValue val = G_VALUE_INIT; gchar *key; key = g_strdup_printf ("extmap-%u", id); g_value_init (&arr, GST_TYPE_ARRAY); g_value_init (&val, G_TYPE_STRING); g_value_set_string (&val, direction); gst_value_array_append_value (&arr, &val); g_value_set_string (&val, extensionname); gst_value_array_append_value (&arr, &val); g_value_set_string (&val, extensionattributes); gst_value_array_append_value (&arr, &val); gst_structure_set_value (s, key, &arr); g_value_unset (&val); g_value_unset (&arr); GST_DEBUG ("adding caps: %s=<%s,%s,%s>", key, direction, extensionname, extensionattributes); g_free (key); } else { gchar *key; key = g_strdup_printf ("extmap-%u", id); gst_structure_set (s, key, G_TYPE_STRING, extensionname, NULL); GST_DEBUG ("adding caps: %s=%s", key, extensionname); g_free (key); } next: g_free (to_free); } return GST_SDP_OK; } /* parses RID SDP attributes (RFC8851) into caps */ static GstSDPResult gst_sdp_media_add_rid_attributes (GArray * attributes, GstCaps * caps) { const gchar *rid; char *p, *to_free; guint i; GstStructure *s; g_return_val_if_fail (attributes != NULL, GST_SDP_EINVAL); g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); g_return_val_if_fail (gst_caps_is_writable (caps), GST_SDP_EINVAL); s = gst_caps_get_structure (caps, 0); for (i = 0; i < attributes->len; i++) { GstSDPAttribute *attr; const char *direction, *params, *id; const char *tmp; attr = &g_array_index (attributes, GstSDPAttribute, i); if (strcmp (attr->key, "rid") != 0) continue; rid = attr->value; /* p is now of the format id dir ;-separated-params */ to_free = p = g_strdup (rid); PARSE_STRING (p, " ", id); if (id == NULL || *id == '\0') { GST_ERROR ("Invalid rid \'%s\'", to_free); goto next; } tmp = id; while (*tmp && (*tmp == '-' || *tmp == '_' || g_ascii_isalnum (*tmp))) tmp++; if (*tmp != '\0') { GST_ERROR ("Invalid rid-id \'%s\'", id); goto next; } SKIP_SPACES (p); PARSE_STRING (p, " ", direction); if (direction == NULL || *direction == '\0') { direction = p; params = NULL; } else { SKIP_SPACES (p); params = p; } if (direction == NULL || *direction == '\0' || (g_strcmp0 (direction, "send") != 0 && g_strcmp0 (direction, "recv") != 0)) { GST_ERROR ("Invalid rid direction \'%s\'", p); goto next; } if (params && *params != '\0') { GValue arr = G_VALUE_INIT; GValue val = G_VALUE_INIT; gchar *key; #if !defined(GST_DISABLE_DEBUG) GString *debug_params = g_string_new (NULL); int i = 0; #endif key = g_strdup_printf ("rid-%s", id); g_value_init (&arr, GST_TYPE_ARRAY); g_value_init (&val, G_TYPE_STRING); g_value_set_string (&val, direction); gst_value_array_append_and_take_value (&arr, &val); val = (GValue) G_VALUE_INIT; while (*p) { const char *param; gboolean done = FALSE; PARSE_STRING (p, ";", param); if (param) { } else if (*p) { param = p; done = TRUE; } else { break; } g_value_init (&val, G_TYPE_STRING); g_value_set_string (&val, param); gst_value_array_append_and_take_value (&arr, &val); val = (GValue) G_VALUE_INIT; #if !defined(GST_DISABLE_DEBUG) if (i++ > 0) g_string_append_c (debug_params, ','); g_string_append (debug_params, param); #endif if (done) break; } gst_structure_take_value (s, key, &arr); arr = (GValue) G_VALUE_INIT; #if !defined(GST_DISABLE_DEBUG) { char *debug_str = g_string_free (debug_params, FALSE); GST_DEBUG ("adding caps: %s=<%s,%s>", key, direction, debug_str); g_free (debug_str); } #endif g_free (key); } else { gchar *key; key = g_strdup_printf ("rid-%s", id); gst_structure_set (s, key, G_TYPE_STRING, direction, NULL); GST_DEBUG ("adding caps: %s=%s", key, direction); g_free (key); } next: g_clear_pointer (&to_free, g_free); } return GST_SDP_OK; } /** * gst_sdp_message_attributes_to_caps: * @msg: a #GstSDPMessage * @caps: a #GstCaps * * Mapping of attributes of #GstSDPMessage to #GstCaps * * Returns: a #GstSDPResult. * * Since: 1.8 */ GstSDPResult gst_sdp_message_attributes_to_caps (const GstSDPMessage * msg, GstCaps * caps) { GstSDPResult res; GstMIKEYMessage *mikey = NULL; g_return_val_if_fail (msg != NULL, GST_SDP_EINVAL); g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); gst_sdp_message_parse_keymgmt (msg, &mikey); if (mikey) { if (gst_mikey_message_to_caps (mikey, caps)) { res = GST_SDP_EINVAL; goto done; } } res = sdp_add_attributes_to_caps (msg->attributes, caps); if (res == GST_SDP_OK) { /* parse global extmap field */ res = gst_sdp_media_add_extmap_attributes (msg->attributes, caps); } done: if (mikey) gst_mikey_message_unref (mikey); return res; } /** * gst_sdp_media_attributes_to_caps: * @media: a #GstSDPMedia * @caps: a #GstCaps * * Mapping of attributes of #GstSDPMedia to #GstCaps * * Returns: a #GstSDPResult. * * Since: 1.8 */ GstSDPResult gst_sdp_media_attributes_to_caps (const GstSDPMedia * media, GstCaps * caps) { GstSDPResult res; GstMIKEYMessage *mikey = NULL; g_return_val_if_fail (media != NULL, GST_SDP_EINVAL); g_return_val_if_fail (caps != NULL && GST_IS_CAPS (caps), GST_SDP_EINVAL); gst_sdp_media_parse_keymgmt (media, &mikey); if (mikey) { if (!gst_mikey_message_to_caps (mikey, caps)) { res = GST_SDP_EINVAL; goto done; } } res = sdp_add_attributes_to_caps (media->attributes, caps); if (res == GST_SDP_OK) { /* parse media extmap field */ res = gst_sdp_media_add_extmap_attributes (media->attributes, caps); } if (res == GST_SDP_OK) { /* parse media rid fields */ res = gst_sdp_media_add_rid_attributes (media->attributes, caps); } done: if (mikey) gst_mikey_message_unref (mikey); return res; }