gstreamer/gst/rtsp/sdpmessage.c
Peter Kjellerstedt 12ab127d12 gst/rtsp/: Allow url to be NULL to be able to use it for server connections.
Original commit message from CVS:
Patch by: Peter Kjellerstedt  <pkj at axis com>
* gst/rtsp/COPYING.MIT:
* gst/rtsp/gstrtspsrc.c: (gst_rtspsrc_create_stream),
(gst_rtspsrc_stream_free), (gst_rtspsrc_cleanup),
(gst_rtspsrc_alloc_udp_ports), (pad_unblocked), (pad_blocked),
(gst_rtspsrc_stream_configure_transport),
(gst_rtspsrc_activate_streams), (gst_rtspsrc_loop_interleaved),
(gst_rtspsrc_loop_udp), (gst_rtspsrc_send),
(gst_rtspsrc_parse_methods),
(gst_rtspsrc_create_transports_string),
(gst_rtspsrc_prepare_transports), (gst_rtspsrc_setup_streams),
(gst_rtspsrc_open), (gst_rtspsrc_close):
* gst/rtsp/gstrtspsrc.h:
* gst/rtsp/rtspconnection.c: (rtsp_connection_create),
(rtsp_connection_connect), (rtsp_connection_send), (read_line),
(parse_request_line), (parse_line), (rtsp_connection_read),
(rtsp_connection_close):
* gst/rtsp/rtspdefs.c: (rtsp_init_status), (rtsp_strresult),
(rtsp_method_as_text), (rtsp_header_as_text),
(rtsp_status_as_text), (rtsp_find_header_field),
(rtsp_find_method):
* gst/rtsp/rtspdefs.h:
* gst/rtsp/rtspextwms.c: (rtsp_ext_wms_after_send),
(rtsp_ext_wms_configure_stream):
* gst/rtsp/rtspmessage.c: (rtsp_message_new), (rtsp_message_init),
(rtsp_message_new_request), (rtsp_message_init_request),
(rtsp_message_new_response), (rtsp_message_init_response),
(rtsp_message_init_data), (rtsp_message_unset),
(rtsp_message_free), (rtsp_message_add_header),
(rtsp_message_get_header), (rtsp_message_set_body),
(rtsp_message_get_body), (dump_mem), (rtsp_message_dump):
* gst/rtsp/rtspmessage.h:
* gst/rtsp/sdpmessage.c: (sdp_message_get_attribute_val_n),
(sdp_media_get_attribute_val_n), (read_string), (read_string_del),
(sdp_parse_line), (sdp_message_parse_buffer), (print_media),
(sdp_message_dump):
Allow url to be NULL to be able to use it for server connections.
Can now send responses as well as requests.
No longer hangs in an endless loop if EOF is received.
Can now convert a status code to a text string.
Return RTSP_HDR_INVALID for unknown headers.
Return RTSP_INVALID for unknown methods.
Copy CSeq and Session headers from the request.
Only free memory corresponding to the currently set message type.
Added const to function arguments as appropriate.
Avoid a compiler warning when initializing nmedia.
Use guint rather than gint to avoid compiler warnings.
Fix crasher in wms extension.
Factor out stream setup from open_connection.
Delay activation of streams when actual data is received from the
server, this prepares us to do proper protocol switching.
Added new license.
Fixes #380895.
2007-01-10 15:19:48 +00:00

762 lines
20 KiB
C

/* GStreamer
* Copyright (C) <2005,2006> Wim Taymans <wim@fluendo.com>
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, 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.
*/
#include <stdlib.h>
#include <string.h>
#include "sdpmessage.h"
/* FIXME, is currently allocated on the stack */
#define MAX_LINE_LEN 1024 * 16
#define FREE_STRING(field) g_free ((field)); (field) = NULL;
#define FREE_ARRAY(field) \
G_STMT_START { \
if (field) \
g_array_free (field, TRUE); \
field = NULL; \
} G_STMT_END
#define REPLACE_STRING(field,val) FREE_STRING(field);field=g_strdup (val);
#define INIT_ARRAY(field,type) \
G_STMT_START { \
if (field) \
g_array_set_size (field,0); \
else \
field = g_array_new (FALSE, TRUE, sizeof(type)); \
} G_STMT_END
#define DEFINE_STRING_SETTER(field) \
RTSPResult sdp_message_set_##field (SDPMessage *msg, gchar *val) { \
g_free (msg->field); \
msg->field = g_strdup (val); \
return RTSP_OK; \
}
#define DEFINE_STRING_GETTER(field) \
char* sdp_message_get_##field (SDPMessage *msg) { \
return msg->field; \
}
#define DEFINE_ARRAY_LEN(field) \
gint sdp_message_##field##_len (SDPMessage *msg) { \
return ((msg)->field->len); \
}
#define DEFINE_ARRAY_GETTER(method,field,type) \
type sdp_message_get_##method (SDPMessage *msg, guint idx) { \
return g_array_index ((msg)->field, type, idx); \
}
#define DEFINE_ARRAY_P_GETTER(method,field,type) \
type * sdp_message_get_##method (SDPMessage *msg, guint idx) { \
return &g_array_index ((msg)->field, type, idx); \
}
#define DEFINE_ARRAY_ADDER(method,field,type,dup_method) \
RTSPResult sdp_message_add_##method (SDPMessage *msg, type val) { \
type v = dup_method(val); \
g_array_append_val((msg)->field, v); \
return RTSP_OK; \
}
RTSPResult
sdp_message_new (SDPMessage ** msg)
{
SDPMessage *newmsg;
g_return_val_if_fail (msg != NULL, RTSP_EINVAL);
newmsg = g_new0 (SDPMessage, 1);
*msg = newmsg;
return sdp_message_init (newmsg);
}
RTSPResult
sdp_message_init (SDPMessage * msg)
{
g_return_val_if_fail (msg != NULL, RTSP_EINVAL);
FREE_STRING (msg->version);
FREE_STRING (msg->origin.username);
FREE_STRING (msg->origin.sess_id);
FREE_STRING (msg->origin.sess_version);
FREE_STRING (msg->origin.nettype);
FREE_STRING (msg->origin.addrtype);
FREE_STRING (msg->origin.addr);
FREE_STRING (msg->session_name);
FREE_STRING (msg->information);
FREE_STRING (msg->uri);
INIT_ARRAY (msg->emails, gchar *);
INIT_ARRAY (msg->phones, gchar *);
FREE_STRING (msg->connection.nettype);
FREE_STRING (msg->connection.addrtype);
FREE_STRING (msg->connection.address);
msg->connection.ttl = 0;
msg->connection.addr_number = 0;
INIT_ARRAY (msg->bandwidths, SDPBandwidth);
INIT_ARRAY (msg->times, SDPTime);
INIT_ARRAY (msg->zones, SDPZone);
FREE_STRING (msg->key.type);
FREE_STRING (msg->key.data);
INIT_ARRAY (msg->attributes, SDPAttribute);
INIT_ARRAY (msg->medias, SDPMedia);
return RTSP_OK;
}
RTSPResult
sdp_message_clean (SDPMessage * msg)
{
g_return_val_if_fail (msg != NULL, RTSP_EINVAL);
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 RTSP_OK;
}
RTSPResult
sdp_message_free (SDPMessage * msg)
{
g_return_val_if_fail (msg != NULL, RTSP_EINVAL);
sdp_message_clean (msg);
g_free (msg);
return RTSP_OK;
}
RTSPResult
sdp_media_new (SDPMedia ** media)
{
SDPMedia *newmedia;
g_return_val_if_fail (media != NULL, RTSP_EINVAL);
newmedia = g_new0 (SDPMedia, 1);
*media = newmedia;
return sdp_media_init (newmedia);
}
RTSPResult
sdp_media_init (SDPMedia * media)
{
g_return_val_if_fail (media != NULL, RTSP_EINVAL);
FREE_STRING (media->media);
media->port = 0;
media->num_ports = 0;
FREE_STRING (media->proto);
INIT_ARRAY (media->fmts, gchar *);
FREE_STRING (media->information);
INIT_ARRAY (media->connections, SDPConnection);
INIT_ARRAY (media->bandwidths, SDPBandwidth);
FREE_STRING (media->key.type);
FREE_STRING (media->key.data);
INIT_ARRAY (media->attributes, SDPAttribute);
return RTSP_OK;
}
DEFINE_STRING_SETTER (version);
DEFINE_STRING_GETTER (version);
RTSPResult
sdp_message_set_origin (SDPMessage * msg, gchar * username, gchar * sess_id,
gchar * sess_version, gchar * nettype, gchar * addrtype, gchar * addr)
{
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 RTSP_OK;
}
SDPOrigin *
sdp_message_get_origin (SDPMessage * msg)
{
return &msg->origin;
}
DEFINE_STRING_SETTER (session_name);
DEFINE_STRING_GETTER (session_name);
DEFINE_STRING_SETTER (information);
DEFINE_STRING_GETTER (information);
DEFINE_STRING_SETTER (uri);
DEFINE_STRING_GETTER (uri);
DEFINE_ARRAY_LEN (emails);
DEFINE_ARRAY_GETTER (email, emails, gchar *);
DEFINE_ARRAY_ADDER (email, emails, gchar *, g_strdup);
DEFINE_ARRAY_LEN (phones);
DEFINE_ARRAY_GETTER (phone, phones, gchar *);
DEFINE_ARRAY_ADDER (phone, phones, gchar *, g_strdup);
RTSPResult
sdp_message_set_connection (SDPMessage * msg, gchar * nettype, gchar * addrtype,
gchar * address, gint ttl, gint addr_number)
{
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 RTSP_OK;
}
SDPConnection *
sdp_message_get_connection (SDPMessage * msg)
{
return &msg->connection;
}
DEFINE_ARRAY_LEN (bandwidths);
DEFINE_ARRAY_P_GETTER (bandwidth, bandwidths, SDPBandwidth);
RTSPResult
sdp_message_add_bandwidth (SDPMessage * msg, gchar * bwtype, gint bandwidth)
{
SDPBandwidth bw;
bw.bwtype = g_strdup (bwtype);
bw.bandwidth = bandwidth;
g_array_append_val (msg->bandwidths, bw);
return RTSP_OK;
}
DEFINE_ARRAY_LEN (times);
DEFINE_ARRAY_P_GETTER (time, times, SDPTime);
RTSPResult
sdp_message_add_time (SDPMessage * msg, gchar * time)
{
return RTSP_OK;
}
DEFINE_ARRAY_LEN (zones);
DEFINE_ARRAY_P_GETTER (zone, zones, SDPZone);
RTSPResult
sdp_message_add_zone (SDPMessage * msg, gchar * time, gchar * typed_time)
{
SDPZone zone;
zone.time = g_strdup (time);
zone.typed_time = g_strdup (typed_time);
g_array_append_val (msg->zones, zone);
return RTSP_OK;
}
RTSPResult
sdp_message_set_key (SDPMessage * msg, gchar * type, gchar * data)
{
REPLACE_STRING (msg->key.type, type);
REPLACE_STRING (msg->key.data, data);
return RTSP_OK;
}
SDPKey *
sdp_message_get_key (SDPMessage * msg)
{
return &msg->key;
}
DEFINE_ARRAY_LEN (attributes);
DEFINE_ARRAY_P_GETTER (attribute, attributes, SDPAttribute);
gchar *
sdp_message_get_attribute_val_n (SDPMessage * msg, gchar * key, guint nth)
{
guint i;
for (i = 0; i < msg->attributes->len; i++) {
SDPAttribute *attr;
attr = &g_array_index (msg->attributes, SDPAttribute, i);
if (!strcmp (attr->key, key)) {
if (nth == 0)
return attr->value;
else
nth--;
}
}
return NULL;
}
gchar *
sdp_message_get_attribute_val (SDPMessage * msg, gchar * key)
{
return sdp_message_get_attribute_val_n (msg, key, 0);
}
RTSPResult
sdp_message_add_attribute (SDPMessage * msg, gchar * key, gchar * value)
{
SDPAttribute attr;
attr.key = g_strdup (key);
attr.value = g_strdup (value);
g_array_append_val (msg->attributes, attr);
return RTSP_OK;
}
DEFINE_ARRAY_LEN (medias);
DEFINE_ARRAY_P_GETTER (media, medias, SDPMedia);
RTSPResult
sdp_message_add_media (SDPMessage * msg, SDPMedia * media)
{
gint len;
SDPMedia *nmedia;
len = msg->medias->len;
g_array_set_size (msg->medias, len + 1);
nmedia = &g_array_index (msg->medias, SDPMedia, len);
memcpy (nmedia, media, sizeof (SDPMedia));
return RTSP_OK;
}
/* media access */
RTSPResult
sdp_media_add_attribute (SDPMedia * media, gchar * key, gchar * value)
{
SDPAttribute attr;
attr.key = g_strdup (key);
attr.value = g_strdup (value);
g_array_append_val (media->attributes, attr);
return RTSP_OK;
}
RTSPResult
sdp_media_add_bandwidth (SDPMedia * media, gchar * bwtype, gint bandwidth)
{
SDPBandwidth bw;
bw.bwtype = g_strdup (bwtype);
bw.bandwidth = bandwidth;
g_array_append_val (media->bandwidths, bw);
return RTSP_OK;
}
RTSPResult
sdp_media_add_format (SDPMedia * media, gchar * format)
{
gchar *fmt;
fmt = g_strdup (format);
g_array_append_val (media->fmts, fmt);
return RTSP_OK;
}
SDPAttribute *
sdp_media_get_attribute (SDPMedia * media, guint idx)
{
return &g_array_index (media->attributes, SDPAttribute, idx);
}
gchar *
sdp_media_get_attribute_val_n (SDPMedia * media, gchar * key, guint nth)
{
guint i;
for (i = 0; i < media->attributes->len; i++) {
SDPAttribute *attr;
attr = &g_array_index (media->attributes, SDPAttribute, i);
if (!strcmp (attr->key, key)) {
if (nth == 0)
return attr->value;
else
nth--;
}
}
return NULL;
}
gchar *
sdp_media_get_attribute_val (SDPMedia * media, gchar * key)
{
return sdp_media_get_attribute_val_n (media, key, 0);
}
gchar *
sdp_media_get_format (SDPMedia * media, guint idx)
{
if (idx >= media->fmts->len)
return NULL;
return g_array_index (media->fmts, gchar *, idx);
}
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
{
gint state;
SDPMessage *msg;
SDPMedia *media;
} SDPContext;
static gboolean
sdp_parse_line (SDPContext * c, gchar type, gchar * buffer)
{
gchar str[8192];
gchar *p = buffer;
#define READ_STRING(field) read_string (str, sizeof(str), &p);REPLACE_STRING (field, str);
#define READ_INT(field) read_string (str, sizeof(str), &p);field = atoi(str);
switch (type) {
case 'v':
if (buffer[0] != '0')
g_warning ("wrong SDP version");
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':
sdp_message_add_email (c->msg, buffer);
break;
case 'p':
sdp_message_add_phone (c->msg, buffer);
break;
case 'c':
READ_STRING (c->msg->connection.nettype);
READ_STRING (c->msg->connection.addrtype);
READ_STRING (c->msg->connection.address);
READ_INT (c->msg->connection.ttl);
READ_INT (c->msg->connection.addr_number);
break;
case 'b':
{
gchar str2[MAX_LINE_LEN];
read_string_del (str, sizeof (str), ':', &p);
read_string (str2, sizeof (str2), &p);
if (c->state == SDP_SESSION)
sdp_message_add_bandwidth (c->msg, str, atoi (str2));
else
sdp_media_add_bandwidth (c->media, str, atoi (str2));
break;
}
case 't':
break;
case 'k':
break;
case 'a':
read_string_del (str, sizeof (str), ':', &p);
if (*p != '\0')
p++;
if (c->state == SDP_SESSION)
sdp_message_add_attribute (c->msg, str, p);
else
sdp_media_add_attribute (c->media, str, p);
break;
case 'm':
{
gchar *slash;
SDPMedia nmedia = {.media = NULL };
c->state = SDP_MEDIA;
sdp_media_init (&nmedia);
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 = -1;
}
READ_STRING (nmedia.proto);
do {
read_string (str, sizeof (str), &p);
sdp_media_add_format (&nmedia, str);
} while (*p != '\0');
sdp_message_add_media (c->msg, &nmedia);
c->media =
&g_array_index (c->msg->medias, SDPMedia, c->msg->medias->len - 1);
break;
}
default:
break;
}
return TRUE;
}
RTSPResult
sdp_message_parse_buffer (guint8 * data, guint size, SDPMessage * msg)
{
gchar *p;
SDPContext c;
gchar type;
gchar buffer[MAX_LINE_LEN];
guint idx = 0;
g_return_val_if_fail (msg != NULL, RTSP_EINVAL);
g_return_val_if_fail (data != NULL, RTSP_EINVAL);
g_return_val_if_fail (size != 0, RTSP_EINVAL);
c.state = SDP_SESSION;
c.msg = msg;
c.media = NULL;
p = (gchar *) data;
while (TRUE) {
while (g_ascii_isspace (*p))
p++;
type = *p++;
if (type == '\0')
break;
if (*p != '=')
goto line_done;
p++;
idx = 0;
while (*p != '\n' && *p != '\r' && *p != '\0') {
if (idx < sizeof (buffer) - 1)
buffer[idx++] = *p;
p++;
}
buffer[idx] = '\0';
sdp_parse_line (&c, type, buffer);
line_done:
while (*p != '\n' && *p != '\0')
p++;
if (*p == '\n')
p++;
}
return RTSP_OK;
}
static void
print_media (SDPMedia * media)
{
g_print (" media: '%s'\n", media->media);
g_print (" port: '%d'\n", media->port);
g_print (" num_ports: '%d'\n", media->num_ports);
g_print (" proto: '%s'\n", 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", media->information);
g_print (" key:\n");
g_print (" type: '%s'\n", media->key.type);
g_print (" data: '%s'\n", media->key.data);
if (media->attributes->len > 0) {
guint i;
g_print (" attributes:\n");
for (i = 0; i < media->attributes->len; i++) {
SDPAttribute *attr = &g_array_index (media->attributes, SDPAttribute, i);
g_print (" attribute '%s' : '%s'\n", attr->key, attr->value);
}
}
}
RTSPResult
sdp_message_dump (SDPMessage * msg)
{
g_return_val_if_fail (msg != NULL, RTSP_EINVAL);
g_print ("sdp packet %p:\n", msg);
g_print (" version: '%s'\n", msg->version);
g_print (" origin:\n");
g_print (" username: '%s'\n", msg->origin.username);
g_print (" sess_id: '%s'\n", msg->origin.sess_id);
g_print (" sess_version: '%s'\n", msg->origin.sess_version);
g_print (" nettype: '%s'\n", msg->origin.nettype);
g_print (" addrtype: '%s'\n", msg->origin.addrtype);
g_print (" addr: '%s'\n", msg->origin.addr);
g_print (" session_name: '%s'\n", msg->session_name);
g_print (" information: '%s'\n", msg->information);
g_print (" uri: '%s'\n", 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", msg->connection.nettype);
g_print (" addrtype: '%s'\n", msg->connection.addrtype);
g_print (" address: '%s'\n", msg->connection.address);
g_print (" ttl: '%d'\n", msg->connection.ttl);
g_print (" addr_number: '%d'\n", msg->connection.addr_number);
g_print (" key:\n");
g_print (" type: '%s'\n", msg->key.type);
g_print (" data: '%s'\n", msg->key.data);
if (msg->attributes->len > 0) {
guint i;
g_print (" attributes:\n");
for (i = 0; i < msg->attributes->len; i++) {
SDPAttribute *attr = &g_array_index (msg->attributes, SDPAttribute, 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 %d:\n", i);
print_media (&g_array_index (msg->medias, SDPMedia, i));
}
}
return RTSP_OK;
}