summaryrefslogtreecommitdiff
path: root/main/sdp_state.c
diff options
context:
space:
mode:
authorMark Michelson <mmichelson@digium.com>2017-03-21 15:44:44 -0500
committerRichard Mudgett <rmudgett@digium.com>2017-04-25 13:03:33 -0500
commit32b3e36c683da0cea37a01c006037ff31f8a2b1d (patch)
tree24942142d9e821f99645c174ff626b49a0178f46 /main/sdp_state.c
parent59203c51cc6a9676ef1ab42aebe070a55f55ead2 (diff)
SDP: Ensure SDPs "merge" properly.
The gist of this work ensures that when a remote SDP is received, it is merged properly with the local capabilities. The remote SDP is converted into a stream topology. That topology is then merged with the current local topology on the SDP state. That new merged topology is then used to create an SDP. Finally, adjustments are made to RTP instances based on knowledge gained from the remote SDP. There are also a battery of tests in this commit that ensure that some basic SDP merges work as expected. While this may not sound like a big change, it has the property that it caused lots of ancillary changes. * The remote SDP is no longer stored on the SDP state. Biggest reason: there's no need for it. The remote SDP is used at the time it is being set and nowhere else. * Some new SDP APIs were added in order to find attributes and convert generic SDP attributes into rtpmap structures. * Writing tests made me realize that retrieving a value from an SDP options structure, the SDP options needs to be made const. * The SDP state machine was essentially gutted by a previous commit. Initially, I attempted to reinstate it, but I found that as it had been defined, it was not all that useful. What was more useful was knowing the role we play in SDP negotiation, so the SDP state machine has been transformed into an indicator of role. * Rather than storing separate local and joint stream state capabilities, it makes more sense to keep track of current stream state and update it as things change. Change-Id: I5938c2be3c6f0a003aa88a39a59e0880f8b2df3d
Diffstat (limited to 'main/sdp_state.c')
-rw-r--r--main/sdp_state.c1010
1 files changed, 828 insertions, 182 deletions
diff --git a/main/sdp_state.c b/main/sdp_state.c
index 5858a65ab..fc7fff449 100644
--- a/main/sdp_state.c
+++ b/main/sdp_state.c
@@ -24,43 +24,41 @@
#include "asterisk/utils.h"
#include "asterisk/netsock2.h"
#include "asterisk/rtp_engine.h"
+#include "asterisk/format.h"
+#include "asterisk/format_cap.h"
+#include "asterisk/config.h"
+#include "asterisk/codec.h"
#include "../include/asterisk/sdp.h"
#include "asterisk/stream.h"
#include "sdp_private.h"
-enum ast_sdp_state_machine {
- /*! \brief The initial state.
+enum ast_sdp_role {
+ /*!
+ * \brief The role has not yet been determined.
*
- * The state machine starts here. It also goes back to this
- * state whenever ast_sdp_state_reset() is called.
+ * When the SDP state is allocated, this is the starting role.
+ * Similarly, when the SDP state is reset, the role is reverted
+ * to this.
*/
- SDP_STATE_INITIAL,
- /*! \brief We are the SDP offerer.
+ SDP_ROLE_NOT_SET,
+ /*!
+ * \brief We are the offerer.
*
- * The state machine enters this state if in the initial state
- * and ast_sdp_state_get_local() is called. When this state is
- * entered, a local SDP is created and then returned.
+ * If a local SDP is requested before a remote SDP has been set, then
+ * we assume the role of offerer. This means that we will generate an
+ * SDP from the local capabilities and configured options.
*/
- SDP_STATE_OFFERER,
- /*! \brief We are the SDP answerer.
+ SDP_ROLE_OFFERER,
+ /*!
+ * \brief We are the answerer.
*
- * The state machine enters this state if in the initial state
- * and ast_sdp_state_set_remote() is called.
+ * If a remote SDP is set before a local SDP is requested, then we
+ * assume the role of answerer. This means that we will generate an
+ * SDP based on a merge of the remote capabilities and our local capabilities.
*/
- SDP_STATE_ANSWERER,
- /*! \brief The SDP has been negotiated.
- *
- * This state can be entered from either the offerer or answerer
- * state. When this state is entered, a joint SDP is created.
- */
- SDP_STATE_NEGOTIATED,
- /*! \brief Not an actual state.
- *
- * This is just here to mark the end of the enumeration.
- */
- SDP_STATE_END,
+ SDP_ROLE_ANSWERER,
};
typedef int (*state_fn)(struct ast_sdp_state *state);
@@ -76,34 +74,185 @@ struct sdp_state_stream {
unsigned int locally_held;
};
+static void sdp_state_stream_free(struct sdp_state_stream *state_stream)
+{
+ if (state_stream->instance) {
+ ast_rtp_instance_destroy(state_stream->instance);
+ }
+ ast_free(state_stream);
+}
+
+AST_VECTOR(sdp_state_streams, struct sdp_state_stream *);
+
struct sdp_state_capabilities {
/*! Stream topology */
struct ast_stream_topology *topology;
/*! Additional information about the streams */
- AST_VECTOR(, struct sdp_state_stream) streams;
+ struct sdp_state_streams streams;
/*! An explicit global connection address */
struct ast_sockaddr connection_address;
};
+static void sdp_state_capabilities_free(struct sdp_state_capabilities *capabilities)
+{
+ if (!capabilities) {
+ return;
+ }
+
+ ast_stream_topology_free(capabilities->topology);
+ AST_VECTOR_CALLBACK_VOID(&capabilities->streams, sdp_state_stream_free);
+ AST_VECTOR_FREE(&capabilities->streams);
+ ast_free(capabilities);
+}
+
+/* TODO
+ * This isn't set anywhere yet.
+ */
+/*! \brief Scheduler for RTCP purposes */
+static struct ast_sched_context *sched;
+
+/*! \brief Internal function which creates an RTP instance */
+static struct ast_rtp_instance *create_rtp(const struct ast_sdp_options *options,
+ enum ast_media_type media_type)
+{
+ struct ast_rtp_instance *rtp;
+ struct ast_rtp_engine_ice *ice;
+ struct ast_sockaddr temp_media_address;
+ static struct ast_sockaddr address_rtp;
+ struct ast_sockaddr *media_address = &address_rtp;
+
+ if (options->bind_rtp_to_media_address && !ast_strlen_zero(options->media_address)) {
+ ast_sockaddr_parse(&temp_media_address, options->media_address, 0);
+ media_address = &temp_media_address;
+ } else {
+ if (ast_check_ipv6()) {
+ ast_sockaddr_parse(&address_rtp, "::", 0);
+ } else {
+ ast_sockaddr_parse(&address_rtp, "0.0.0.0", 0);
+ }
+ }
+
+ if (!(rtp = ast_rtp_instance_new(options->rtp_engine, sched, media_address, NULL))) {
+ ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n",
+ options->rtp_engine);
+ return NULL;
+ }
+
+ ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, 1);
+ ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_NAT, options->rtp_symmetric);
+
+ if (options->ice == AST_SDP_ICE_DISABLED && (ice = ast_rtp_instance_get_ice(rtp))) {
+ ice->stop(rtp);
+ }
+
+ if (options->telephone_event) {
+ ast_rtp_instance_dtmf_mode_set(rtp, AST_RTP_DTMF_MODE_RFC2833);
+ ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_DTMF, 1);
+ }
+
+ if (media_type == AST_MEDIA_TYPE_AUDIO &&
+ (options->tos_audio || options->cos_audio)) {
+ ast_rtp_instance_set_qos(rtp, options->tos_audio,
+ options->cos_audio, "SIP RTP Audio");
+ } else if (media_type == AST_MEDIA_TYPE_VIDEO &&
+ (options->tos_video || options->cos_video)) {
+ ast_rtp_instance_set_qos(rtp, options->tos_video,
+ options->cos_video, "SIP RTP Video");
+ }
+
+ ast_rtp_instance_set_last_rx(rtp, time(NULL));
+
+ return rtp;
+}
+
+static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const struct ast_stream_topology *topology,
+ const struct ast_sdp_options *options)
+{
+ struct sdp_state_capabilities *capabilities;
+ int i;
+
+ capabilities = ast_calloc(1, sizeof(*capabilities));
+ if (!capabilities) {
+ return NULL;
+ }
+
+ capabilities->topology = ast_stream_topology_clone(topology);
+ if (!capabilities->topology) {
+ sdp_state_capabilities_free(capabilities);
+ return NULL;
+ }
+
+ if (AST_VECTOR_INIT(&capabilities->streams,
+ ast_stream_topology_get_count(topology))) {
+ sdp_state_capabilities_free(capabilities);
+ return NULL;
+ }
+ ast_sockaddr_setnull(&capabilities->connection_address);
+
+ for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
+ struct sdp_state_stream *state_stream;
+ enum ast_media_type stream_type;
+
+ state_stream = ast_calloc(1, sizeof(*state_stream));
+ if (!state_stream) {
+ return NULL;
+ }
+
+ stream_type = ast_stream_get_type(ast_stream_topology_get_stream(topology, i));
+
+ if (stream_type == AST_MEDIA_TYPE_AUDIO || stream_type == AST_MEDIA_TYPE_VIDEO) {
+ state_stream->instance = create_rtp(options, stream_type);
+ }
+
+ if (!state_stream->instance) {
+ sdp_state_stream_free(state_stream);
+ return NULL;
+ }
+
+ AST_VECTOR_APPEND(&capabilities->streams, state_stream);
+ }
+
+ return capabilities;
+}
+
+/*!
+ * \brief SDP state, the main structure used to keep track of SDP negotiation
+ * and settings.
+ *
+ * Most fields are pretty self-explanatory, but negotiated_capabilities and
+ * proposed_capabilities could use some further explanation. When an SDP
+ * state is allocated, a stream topology is provided that dictates the
+ * types of streams to offer in the resultant SDP. At the time the SDP
+ * is allocated, this topology is used to create the proposed_capabilities.
+ *
+ * If we are the SDP offerer, then the proposed_capabilities are what are used
+ * to generate the SDP offer. When the SDP answer arrives, the proposed capabilities
+ * are merged with the SDP answer to create the negotiated capabilities.
+ *
+ * If we are the SDP answerer, then the incoming SDP offer is merged with our
+ * proposed capabilities to to create the negotiated capabilities. These negotiated
+ * capabilities are what we send in our SDP answer.
+ *
+ * Any changes that a user of the API performs will occur on the proposed capabilities.
+ * The negotiated capabilities are only altered based on actual SDP negotiation. This is
+ * done so that the negotiated capabilities can be fallen back on if the proposed
+ * capabilities run into some sort of issue.
+ */
struct ast_sdp_state {
- /*! Local capabilities, learned through configuration */
- struct sdp_state_capabilities local_capabilities;
+ /*! Current capabilities */
+ struct sdp_state_capabilities *negotiated_capabilities;
+ /*! Proposed capabilities */
+ struct sdp_state_capabilities *proposed_capabilities;
/*! Remote capabilities, learned through remote SDP */
struct ast_stream_topology *remote_capabilities;
- /*! Joint capabilities. The combined local and remote capabilities. */
- struct sdp_state_capabilities joint_capabilities;
/*! Local SDP. Generated via the options and local capabilities. */
struct ast_sdp *local_sdp;
- /*! Remote SDP. Received directly from a peer. */
- struct ast_sdp *remote_sdp;
- /*! Joint SDP. The merged local and remote SDPs. */
- struct ast_sdp *joint_sdp;
/*! SDP options. Configured options beyond media capabilities. */
struct ast_sdp_options *options;
/*! Translator that puts SDPs into the expected representation */
struct ast_sdp_translator *translator;
- /*! The current state machine state that we are in */
- enum ast_sdp_state_machine state;
+ /*! The role that we occupy in SDP negotiation */
+ enum ast_sdp_role role;
};
struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams,
@@ -124,56 +273,39 @@ struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams,
return NULL;
}
- if (ast_sdp_state_update_local_topology(sdp_state, streams)) {
+ sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(streams, options);
+ if (!sdp_state->proposed_capabilities) {
ast_sdp_state_free(sdp_state);
return NULL;
}
- sdp_state->state = SDP_STATE_INITIAL;
+ sdp_state->role = SDP_ROLE_NOT_SET;
return sdp_state;
}
-static void sdp_state_capabilities_free(struct sdp_state_capabilities *sdp_capabilities)
-{
- int stream_index;
-
- for (stream_index = 0; stream_index < AST_VECTOR_SIZE(&sdp_capabilities->streams); stream_index++) {
- struct sdp_state_stream *stream_state = AST_VECTOR_GET_ADDR(&sdp_capabilities->streams, stream_index);
- enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(sdp_capabilities->topology, stream_index));
-
- if (type == AST_MEDIA_TYPE_AUDIO || type == AST_MEDIA_TYPE_VIDEO) {
- ast_rtp_instance_destroy(stream_state->instance);
- }
- }
-
- ast_stream_topology_free(sdp_capabilities->topology);
- AST_VECTOR_FREE(&sdp_capabilities->streams);
-}
-
void ast_sdp_state_free(struct ast_sdp_state *sdp_state)
{
if (!sdp_state) {
return;
}
- sdp_state_capabilities_free(&sdp_state->local_capabilities);
+ sdp_state_capabilities_free(sdp_state->negotiated_capabilities);
+ sdp_state_capabilities_free(sdp_state->proposed_capabilities);
ast_stream_topology_free(sdp_state->remote_capabilities);
- sdp_state_capabilities_free(&sdp_state->joint_capabilities);
ast_sdp_free(sdp_state->local_sdp);
- ast_sdp_free(sdp_state->remote_sdp);
- ast_sdp_free(sdp_state->joint_sdp);
ast_sdp_options_free(sdp_state->options);
ast_sdp_translator_free(sdp_state->translator);
+ ast_free(sdp_state);
}
static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state *sdp_state, int stream_index)
{
- if (stream_index >= AST_VECTOR_SIZE(&sdp_state->local_capabilities.streams)) {
+ if (stream_index >= AST_VECTOR_SIZE(&sdp_state->proposed_capabilities->streams)) {
return NULL;
}
- return AST_VECTOR_GET_ADDR(&sdp_state->local_capabilities.streams, stream_index);
+ return AST_VECTOR_GET(&sdp_state->proposed_capabilities->streams, stream_index);
}
struct ast_rtp_instance *ast_sdp_state_get_rtp_instance(
@@ -195,7 +327,7 @@ const struct ast_sockaddr *ast_sdp_state_get_connection_address(const struct ast
{
ast_assert(sdp_state != NULL);
- return &sdp_state->local_capabilities.connection_address;
+ return &sdp_state->proposed_capabilities->connection_address;
}
int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state,
@@ -218,7 +350,7 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_
return 0;
}
- type = ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->local_capabilities.topology,
+ type = ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology,
stream_index));
if (type == AST_MEDIA_TYPE_AUDIO || type == AST_MEDIA_TYPE_VIDEO) {
@@ -228,10 +360,10 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_
}
/* If an explicit global connection address is set use it here for the IP part */
- if (!ast_sockaddr_isnull(&sdp_state->local_capabilities.connection_address)) {
+ if (!ast_sockaddr_isnull(&sdp_state->proposed_capabilities->connection_address)) {
int port = ast_sockaddr_port(address);
- ast_sockaddr_copy(address, &sdp_state->local_capabilities.connection_address);
+ ast_sockaddr_copy(address, &sdp_state->proposed_capabilities->connection_address);
ast_sockaddr_set_port(address, port);
}
@@ -242,11 +374,12 @@ const struct ast_stream_topology *ast_sdp_state_get_joint_topology(
const struct ast_sdp_state *sdp_state)
{
ast_assert(sdp_state != NULL);
- if (sdp_state->state == SDP_STATE_NEGOTIATED) {
- return sdp_state->joint_capabilities.topology;
- } else {
- return sdp_state->local_capabilities.topology;
+
+ if (sdp_state->negotiated_capabilities) {
+ return sdp_state->negotiated_capabilities->topology;
}
+
+ return sdp_state->proposed_capabilities->topology;
}
const struct ast_stream_topology *ast_sdp_state_get_local_topology(
@@ -254,7 +387,7 @@ const struct ast_stream_topology *ast_sdp_state_get_local_topology(
{
ast_assert(sdp_state != NULL);
- return sdp_state->local_capabilities.topology;
+ return sdp_state->proposed_capabilities->topology;
}
const struct ast_sdp_options *ast_sdp_state_get_options(
@@ -265,120 +398,427 @@ const struct ast_sdp_options *ast_sdp_state_get_options(
return sdp_state->options;
}
-#if 0
-static int merge_sdps(struct ast_sdp_state *sdp_state)
-{
- ast_assert(sdp_state->local_sdp != NULL);
- ast_assert(sdp_state->remote_sdp != NULL);
- /* XXX STUB */
- /* The goal of this function is to take
- * sdp_state->local_sdp and sdp_state->remote_sdp
- * and negotiate those into a joint SDP. This joint
- * SDP should be stored in sdp_state->joint_sdp. After
- * the joint SDP is created, the joint SDP should be
- * used to create the joint topology. Finally, if necessary,
- * the RTP session may need to be adjusted in some ways. For
- * instance, if we previously opened three ports for three
- * streams, but we negotiate down to two streams, then we
- * can shut down the port for the third stream. Similarly,
- * if we end up negotiating something like BUNDLE, then we may
- * need to tell the RTP layer to close ports and to multiplex
- * streams.
- */
+/*!
+ * \brief Merge two streams into a joint stream.
+ *
+ * \param local Our local stream
+ * \param remote A remote stream
+ * \retval NULL An error occurred
+ * \retval non-NULL The joint stream created
+ */
+static struct ast_stream *merge_streams(const struct ast_stream *local,
+ const struct ast_stream *remote)
+{
+ struct ast_stream *joint_stream;
+ struct ast_format_cap *joint_cap;
+ struct ast_format_cap *local_cap;
+ struct ast_format_cap *remote_cap;
+ struct ast_str *local_buf = ast_str_alloca(128);
+ struct ast_str *remote_buf = ast_str_alloca(128);
+ struct ast_str *joint_buf = ast_str_alloca(128);
+
+ joint_stream = ast_stream_alloc(ast_codec_media_type2str(ast_stream_get_type(remote)),
+ ast_stream_get_type(remote));
+ if (!joint_stream) {
+ return NULL;
+ }
- return 0;
+ if (!local) {
+ return joint_stream;
+ }
+
+ joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!joint_cap) {
+ ast_stream_free(joint_stream);
+ return NULL;
+ }
+
+ local_cap = ast_stream_get_formats(local);
+ remote_cap = ast_stream_get_formats(remote);
+
+ ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap);
+
+ ast_debug(3, "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n",
+ ast_format_cap_get_names(local_cap, &local_buf),
+ ast_format_cap_get_names(remote_cap, &remote_buf),
+ ast_format_cap_get_names(joint_cap, &joint_buf),
+ ast_format_cap_count(joint_cap));
+
+ ast_stream_set_formats(joint_stream, joint_cap);
+
+ ao2_ref(joint_cap, -1);
+
+ return joint_stream;
}
-#endif
-/* TODO
- * This isn't set anywhere yet.
+/*!
+ * \brief Get a local stream that corresponds with a remote stream.
+ *
+ * \param local The local topology
+ * \param media_type The type of stream we are looking for
+ * \param[in,out] media_indices Keeps track of where to start searching in the topology
+ * \retval NULL No corresponding stream found
+ * \retval non-NULL The corresponding stream
*/
-/*! \brief Scheduler for RTCP purposes */
-static struct ast_sched_context *sched;
+static int get_corresponding_index(const struct ast_stream_topology *local,
+ enum ast_media_type media_type, int *media_indices)
+{
+ int i;
+ int winner = -1;
-/*! \brief Internal function which creates an RTP instance */
-static struct ast_rtp_instance *create_rtp(const struct ast_sdp_options *options,
- enum ast_media_type media_type)
+ for (i = media_indices[media_type]; i < ast_stream_topology_get_count(local); ++i) {
+ struct ast_stream *candidate;
+
+ candidate = ast_stream_topology_get_stream(local, i);
+ if (ast_stream_get_type(candidate) == media_type) {
+ winner = i;
+ break;
+ }
+ }
+
+ media_indices[media_type] = i + 1;
+ return winner;
+}
+
+/*!
+ * \brief Merge existing stream capabilities and a new topology into joint capabilities.
+ *
+ * This is a bit complicated. The idea is that we already have some capabilities set, and
+ * we've now been confronted with a new stream topology. We want to take what's been
+ * presented to us and merge those new capabilities with our own.
+ *
+ * For each of the new streams, we try to find a corresponding stream in our current
+ * capabilities. If we find one, then we get the compatible formats of the two streams
+ * and create a new stream with those formats set. We then will re-use the underlying
+ * media instance (such as an RTP instance) on this merged stream.
+ *
+ * The create_new parameter determines whether we should attempt to create new media
+ * instances.
+ * If we do not find a corresponding stream, then we create a new one. If the
+ * create_new parameter is true, this created stream is made a clone of the new stream,
+ * and a media instance is created. If the create_new parameter is not true, then the
+ * created stream has no formats set and no media instance is created for it.
+ *
+ * \param current Current capabilities of the SDP state (may be NULL)
+ * \param new_topology The new topology to base merged capabilities on
+ * \param options The options set on the SDP state
+ * \retval NULL An error occurred
+ * \retval non-NULL The merged capabilities
+ */
+static struct sdp_state_capabilities *merge_capabilities(const struct sdp_state_capabilities *current,
+ const struct ast_stream_topology *new_topology, const struct ast_sdp_options *options, int create_missing)
+{
+ struct sdp_state_capabilities *joint_capabilities;
+ struct ast_stream_topology *topology;
+ int media_indices[AST_MEDIA_TYPE_END] = {0};
+ int i;
+
+ ast_assert(current != NULL);
+
+ joint_capabilities = ast_calloc(1, sizeof(*joint_capabilities));
+ if (!joint_capabilities) {
+ return NULL;
+ }
+
+ joint_capabilities->topology = ast_stream_topology_alloc();
+ if (!joint_capabilities->topology) {
+ goto fail;
+ }
+
+ AST_VECTOR_INIT(&joint_capabilities->streams, AST_VECTOR_SIZE(&current->streams));
+ ast_sockaddr_copy(&joint_capabilities->connection_address, &current->connection_address);
+ topology = current->topology;
+
+ for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {
+ enum ast_media_type new_stream_type;
+ struct ast_stream *new_stream;
+ struct ast_stream *current_stream;
+ struct ast_stream *joint_stream;
+ struct sdp_state_stream *current_state_stream;
+ struct sdp_state_stream *joint_state_stream;
+ int current_index;
+
+ joint_state_stream = ast_calloc(1, sizeof(*joint_state_stream));
+ if (!joint_state_stream) {
+ goto fail;
+ }
+
+ new_stream = ast_stream_topology_get_stream(new_topology, i);
+ new_stream_type = ast_stream_get_type(new_stream);
+
+ current_index = get_corresponding_index(topology, new_stream_type, media_indices);
+
+ if (current_index >= 0) {
+ current_stream = ast_stream_topology_get_stream(topology, current_index);
+ joint_stream = merge_streams(current_stream, new_stream);
+ if (!joint_stream) {
+ goto fail;
+ }
+
+ current_state_stream = AST_VECTOR_GET(&current->streams, current_index);
+ joint_state_stream->instance = ao2_bump(current_state_stream->instance);
+
+ if (!ast_sockaddr_isnull(&current_state_stream->connection_address)) {
+ ast_sockaddr_copy(&joint_state_stream->connection_address, &current_state_stream->connection_address);
+ } else {
+ ast_sockaddr_setnull(&joint_state_stream->connection_address);
+ }
+ joint_state_stream->locally_held = current_state_stream->locally_held;
+ } else if (create_missing) {
+ /* We don't have a stream state that corresponds to the stream in the new topology, so
+ * create a stream state as appropriate.
+ */
+ joint_stream = ast_stream_clone(new_stream);
+ if (!joint_stream) {
+ goto fail;
+ }
+ if (new_stream_type == AST_MEDIA_TYPE_AUDIO || new_stream_type == AST_MEDIA_TYPE_VIDEO) {
+ joint_state_stream->instance = create_rtp(options, new_stream_type);
+ if (!joint_state_stream->instance) {
+ goto fail;
+ }
+ }
+ ast_sockaddr_setnull(&joint_state_stream->connection_address);
+ joint_state_stream->locally_held = 0;
+ } else {
+ /* We don't have a stream that corresponds to the stream in the new topology. Create a
+ * dummy stream to go in its place so that the resulting SDP created will contain
+ * the stream but will have no port or codecs set
+ */
+ joint_stream = ast_stream_alloc("dummy", new_stream_type);
+ if (!joint_stream) {
+ goto fail;
+ }
+ }
+
+ ast_stream_topology_append_stream(joint_capabilities->topology, joint_stream);
+ AST_VECTOR_APPEND(&joint_capabilities->streams, joint_state_stream);
+ }
+
+ return joint_capabilities;
+
+fail:
+ sdp_state_capabilities_free(joint_capabilities);
+ return NULL;
+}
+
+/*!
+ * \brief Apply remote SDP's ICE information to our RTP session
+ *
+ * \param state The SDP state on which negotiation has taken place
+ * \param options The SDP options we support
+ * \param remote_sdp The SDP we most recently received
+ * \param remote_m_line The stream on which we are examining ICE candidates
+ */
+static void update_ice(const struct ast_sdp_state *state, struct ast_rtp_instance *rtp, const struct ast_sdp_options *options,
+ const struct ast_sdp *remote_sdp, const struct ast_sdp_m_line *remote_m_line)
{
- struct ast_rtp_instance *rtp;
struct ast_rtp_engine_ice *ice;
- struct ast_sockaddr temp_media_address;
- static struct ast_sockaddr address_rtp;
- struct ast_sockaddr *media_address = &address_rtp;
+ const struct ast_sdp_a_line *attr;
+ unsigned int attr_i;
- if (options->bind_rtp_to_media_address && !ast_strlen_zero(options->media_address)) {
- ast_sockaddr_parse(&temp_media_address, options->media_address, 0);
- media_address = &temp_media_address;
+ /* If ICE support is not enabled or available exit early */
+ if (ast_sdp_options_get_ice(options) != AST_SDP_ICE_ENABLED_STANDARD || !(ice = ast_rtp_instance_get_ice(rtp))) {
+ return;
+ }
+
+ attr = ast_sdp_m_find_attribute(remote_m_line, "ice-ufrag", -1);
+ if (!attr) {
+ attr = ast_sdp_find_attribute(remote_sdp, "ice-ufrag", -1);
+ }
+ if (attr) {
+ ice->set_authentication(rtp, attr->value, NULL);
} else {
- if (ast_check_ipv6()) {
- ast_sockaddr_parse(&address_rtp, "::", 0);
+ return;
+ }
+
+ attr = ast_sdp_m_find_attribute(remote_m_line, "ice-pwd", -1);
+ if (!attr) {
+ attr = ast_sdp_find_attribute(remote_sdp, "ice-pwd", -1);
+ }
+ if (attr) {
+ ice->set_authentication(rtp, NULL, attr->value);
+ } else {
+ return;
+ }
+
+ if (ast_sdp_m_find_attribute(remote_m_line, "ice-lite", -1)) {
+ ice->ice_lite(rtp);
+ }
+
+ /* Find all of the candidates */
+ for (attr_i = 0; attr_i < ast_sdp_m_get_a_count(remote_m_line); ++attr_i) {
+ char foundation[32];
+ char transport[32];
+ char address[INET6_ADDRSTRLEN + 1];
+ char cand_type[6];
+ char relay_address[INET6_ADDRSTRLEN + 1] = "";
+ unsigned int port;
+ unsigned int relay_port = 0;
+ struct ast_rtp_engine_ice_candidate candidate = { 0, };
+
+ attr = ast_sdp_m_get_a(remote_m_line, attr_i);
+
+ /* If this is not a candidate line skip it */
+ if (strcmp(attr->name, "candidate")) {
+ continue;
+ }
+
+ if (sscanf(attr->value, "%31s %30u %31s %30u %46s %30u typ %5s %*s %23s %*s %30u",
+ foundation, &candidate.id, transport, (unsigned *)&candidate.priority, address,
+ &port, cand_type, relay_address, &relay_port) < 7) {
+ /* Candidate did not parse properly */
+ continue;
+ }
+
+ if (ast_sdp_options_get_rtcp_mux(options)
+ && ast_sdp_m_find_attribute(remote_m_line, "rtcp-mux", -1)
+ && candidate.id > 1) {
+ /* Remote side may have offered RTP and RTCP candidates. However, if we're using RTCP MUX,
+ * then we should ignore RTCP candidates.
+ */
+ continue;
+ }
+
+ candidate.foundation = foundation;
+ candidate.transport = transport;
+
+ ast_sockaddr_parse(&candidate.address, address, PARSE_PORT_FORBID);
+ ast_sockaddr_set_port(&candidate.address, port);
+
+ if (!strcasecmp(cand_type, "host")) {
+ candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_HOST;
+ } else if (!strcasecmp(cand_type, "srflx")) {
+ candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_SRFLX;
+ } else if (!strcasecmp(cand_type, "relay")) {
+ candidate.type = AST_RTP_ICE_CANDIDATE_TYPE_RELAYED;
} else {
- ast_sockaddr_parse(&address_rtp, "0.0.0.0", 0);
+ continue;
}
+
+ if (!ast_strlen_zero(relay_address)) {
+ ast_sockaddr_parse(&candidate.relay_address, relay_address, PARSE_PORT_FORBID);
+ }
+
+ if (relay_port) {
+ ast_sockaddr_set_port(&candidate.relay_address, relay_port);
+ }
+
+ ice->add_remote_candidate(rtp, &candidate);
}
- if (!(rtp = ast_rtp_instance_new(options->rtp_engine, sched, media_address, NULL))) {
- ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n",
- options->rtp_engine);
- return NULL;
+ if (state->role == SDP_ROLE_OFFERER) {
+ ice->set_role(rtp, AST_RTP_ICE_ROLE_CONTROLLING);
+ } else {
+ ice->set_role(rtp, AST_RTP_ICE_ROLE_CONTROLLED);
}
- ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, 1);
- ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_NAT, options->rtp_symmetric);
+ ice->start(rtp);
+}
- if (options->ice == AST_SDP_ICE_DISABLED && (ice = ast_rtp_instance_get_ice(rtp))) {
- ice->stop(rtp);
+/*!
+ * \brief Update RTP instances based on merged SDPs
+ *
+ * RTP instances, when first allocated, cannot make assumptions about what the other
+ * side supports and thus has to go with some default behaviors. This function gets
+ * called after we know both what we support and what the remote endpoint supports.
+ * This way, we can update the RTP instance to reflect what is supported by both
+ * sides.
+ *
+ * \param state The SDP state in which SDPs have been negotiated
+ * \param rtp The RTP instance that is being updated
+ * \param options Our locally-supported SDP options
+ * \param remote_sdp The SDP we most recently received
+ * \param remote_m_line The remote SDP stream that corresponds to the RTP instance we are modifying
+ */
+static void update_rtp_after_merge(const struct ast_sdp_state *state, struct ast_rtp_instance *rtp,
+ const struct ast_sdp_options *options,
+ const struct ast_sdp *remote_sdp,
+ const struct ast_sdp_m_line *remote_m_line)
+{
+ if (ast_sdp_options_get_rtcp_mux(options) && ast_sdp_m_find_attribute(remote_m_line, "rtcp-mux", -1)) {
+ ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX);
+ } else {
+ ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_STANDARD);
}
- if (options->telephone_event) {
- ast_rtp_instance_dtmf_mode_set(rtp, AST_RTP_DTMF_MODE_RFC2833);
- ast_rtp_instance_set_prop(rtp, AST_RTP_PROPERTY_DTMF, 1);
+ if (ast_sdp_options_get_ice(options) == AST_SDP_ICE_ENABLED_STANDARD) {
+ update_ice(state, rtp, options, remote_sdp, remote_m_line);
}
+}
- if (media_type == AST_MEDIA_TYPE_AUDIO &&
- (options->tos_audio || options->cos_audio)) {
- ast_rtp_instance_set_qos(rtp, options->tos_audio,
- options->cos_audio, "SIP RTP Audio");
- } else if (media_type == AST_MEDIA_TYPE_VIDEO &&
- (options->tos_video || options->cos_video)) {
- ast_rtp_instance_set_qos(rtp, options->tos_video,
- options->cos_video, "SIP RTP Video");
- }
+static void set_negotiated_capabilities(struct ast_sdp_state *sdp_state,
+ struct sdp_state_capabilities *new_capabilities)
+{
+ struct sdp_state_capabilities *old_capabilities = sdp_state->negotiated_capabilities;
- ast_rtp_instance_set_last_rx(rtp, time(NULL));
+ sdp_state->negotiated_capabilities = new_capabilities;
+ sdp_state_capabilities_free(old_capabilities);
+}
- return rtp;
+static void set_proposed_capabilities(struct ast_sdp_state *sdp_state,
+ struct sdp_state_capabilities *new_capabilities)
+{
+ struct sdp_state_capabilities *old_capabilities = sdp_state->proposed_capabilities;
+
+ sdp_state->proposed_capabilities = new_capabilities;
+ sdp_state_capabilities_free(old_capabilities);
}
-static int sdp_state_setup_local_streams(struct ast_sdp_state *sdp_state)
+static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state,
+ const struct sdp_state_capabilities *capabilities);
+
+/*!
+ * \brief Merge SDPs into a joint SDP.
+ *
+ * This function is used to take a remote SDP and merge it with our local
+ * capabilities to produce a new local SDP. After creating the new local SDP,
+ * it then iterates through media instances and updates them as necessary. For
+ * instance, if a specific RTP feature is supported by both us and the far end,
+ * then we can ensure that the feature is enabled.
+ *
+ * \param sdp_state The current SDP state
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+static int merge_sdps(struct ast_sdp_state *sdp_state,
+ const struct ast_sdp *remote_sdp)
{
- int stream_index;
+ struct sdp_state_capabilities *joint_capabilities;
+ int i;
- for (stream_index = 0; stream_index < AST_VECTOR_SIZE(&sdp_state->local_capabilities.streams); stream_index++) {
- struct sdp_state_stream *stream_state_local = AST_VECTOR_GET_ADDR(&sdp_state->local_capabilities.streams, stream_index);
- struct sdp_state_stream *stream_state_joint = NULL;
- enum ast_media_type type_local = ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->local_capabilities.topology, stream_index));
- enum ast_media_type type_joint = AST_MEDIA_TYPE_UNKNOWN;
+ sdp_state->remote_capabilities = ast_get_topology_from_sdp(remote_sdp);
+ if (!sdp_state->remote_capabilities) {
+ return -1;
+ }
- if (stream_index < AST_VECTOR_SIZE(&sdp_state->joint_capabilities.streams)) {
- stream_state_joint = AST_VECTOR_GET_ADDR(&sdp_state->joint_capabilities.streams, stream_index);
- type_joint = ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->joint_capabilities.topology, stream_index));
- }
+ joint_capabilities = merge_capabilities(sdp_state->proposed_capabilities,
+ sdp_state->remote_capabilities, sdp_state->options, 0);
+ if (!joint_capabilities) {
+ return -1;
+ }
+ set_negotiated_capabilities(sdp_state, joint_capabilities);
- /* If we can reuse an existing media stream then do so */
- if (type_local == type_joint) {
- if (type_local == AST_MEDIA_TYPE_AUDIO || type_local == AST_MEDIA_TYPE_VIDEO) {
- stream_state_local->instance = ao2_bump(stream_state_joint->instance);
- continue;
- }
- }
+ if (sdp_state->local_sdp) {
+ ast_sdp_free(sdp_state->local_sdp);
+ sdp_state->local_sdp = NULL;
+ }
- if (type_local == AST_MEDIA_TYPE_AUDIO || type_local == AST_MEDIA_TYPE_VIDEO) {
- /* We need to create a new RTP instance */
- stream_state_local->instance = create_rtp(sdp_state->options, type_local);
- if (!stream_state_local->instance) {
- return -1;
- }
+ sdp_state->local_sdp = sdp_create_from_state(sdp_state, joint_capabilities);
+ if (!sdp_state->local_sdp) {
+ return -1;
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(&joint_capabilities->streams); ++i) {
+ struct sdp_state_stream *state_stream;
+ enum ast_media_type stream_type;
+
+ stream_type = ast_stream_get_type(ast_stream_topology_get_stream(joint_capabilities->topology, i));
+
+ state_stream = AST_VECTOR_GET(&joint_capabilities->streams, i);
+ if ((stream_type == AST_MEDIA_TYPE_AUDIO || stream_type == AST_MEDIA_TYPE_VIDEO) && state_stream->instance) {
+ update_rtp_after_merge(sdp_state, state_stream->instance, sdp_state->options,
+ remote_sdp, ast_sdp_get_m(remote_sdp, i));
}
}
@@ -389,11 +829,10 @@ const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_stat
{
ast_assert(sdp_state != NULL);
- if (!sdp_state->local_sdp) {
- if (sdp_state_setup_local_streams(sdp_state)) {
- return NULL;
- }
- sdp_state->local_sdp = ast_sdp_create_from_state(sdp_state);
+ if (sdp_state->role == SDP_ROLE_NOT_SET) {
+ ast_assert(sdp_state->local_sdp == NULL);
+ sdp_state->role = SDP_ROLE_OFFERER;
+ sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->proposed_capabilities);
}
return sdp_state->local_sdp;
@@ -410,11 +849,15 @@ const void *ast_sdp_state_get_local_sdp_impl(struct ast_sdp_state *sdp_state)
return ast_sdp_translator_from_sdp(sdp_state->translator, sdp);
}
-void ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, struct ast_sdp *sdp)
+void ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, const struct ast_sdp *sdp)
{
ast_assert(sdp_state != NULL);
- sdp_state->remote_sdp = sdp;
+ if (sdp_state->role == SDP_ROLE_NOT_SET) {
+ sdp_state->role = SDP_ROLE_ANSWERER;
+ }
+
+ merge_sdps(sdp_state, sdp);
}
int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, void *remote)
@@ -427,8 +870,7 @@ int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, void
if (!sdp) {
return -1;
}
-
- sdp_state->remote_sdp = sdp;
+ ast_sdp_state_set_remote_sdp(sdp_state, sdp);
return 0;
}
@@ -440,37 +882,27 @@ int ast_sdp_state_reset(struct ast_sdp_state *sdp_state)
ast_sdp_free(sdp_state->local_sdp);
sdp_state->local_sdp = NULL;
- ast_sdp_free(sdp_state->remote_sdp);
- sdp_state->remote_sdp = NULL;
-
- ast_sdp_free(sdp_state->joint_sdp);
- sdp_state->joint_sdp = NULL;
-
ast_stream_topology_free(sdp_state->remote_capabilities);
sdp_state->remote_capabilities = NULL;
- ast_stream_topology_free(sdp_state->joint_capabilities.topology);
- sdp_state->joint_capabilities.topology = NULL;
+ set_proposed_capabilities(sdp_state, NULL);
- sdp_state->state = SDP_STATE_INITIAL;
+ sdp_state->role = SDP_ROLE_NOT_SET;
return 0;
}
int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams)
{
+ struct sdp_state_capabilities *capabilities;
ast_assert(sdp_state != NULL);
ast_assert(streams != NULL);
- sdp_state_capabilities_free(&sdp_state->local_capabilities);
- sdp_state->local_capabilities.topology = ast_stream_topology_clone(streams);
- if (!sdp_state->local_capabilities.topology) {
- return -1;
- }
-
- if (AST_VECTOR_INIT(&sdp_state->local_capabilities.streams, ast_stream_topology_get_count(streams))) {
+ capabilities = merge_capabilities(sdp_state->proposed_capabilities, streams, sdp_state->options, 1);
+ if (!capabilities) {
return -1;
}
+ set_proposed_capabilities(sdp_state, capabilities);
return 0;
}
@@ -480,9 +912,9 @@ void ast_sdp_state_set_local_address(struct ast_sdp_state *sdp_state, struct ast
ast_assert(sdp_state != NULL);
if (!address) {
- ast_sockaddr_setnull(&sdp_state->local_capabilities.connection_address);
+ ast_sockaddr_setnull(&sdp_state->proposed_capabilities->connection_address);
} else {
- ast_sockaddr_copy(&sdp_state->local_capabilities.connection_address, address);
+ ast_sockaddr_copy(&sdp_state->proposed_capabilities->connection_address, address);
}
}
@@ -533,3 +965,217 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat
return stream_state->locally_held;
}
+
+static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_state *sdp_state,
+ const struct ast_sdp_options *options, const struct sdp_state_capabilities *capabilities, int stream_index)
+{
+ struct ast_stream *stream;
+ struct ast_sdp_m_line *m_line;
+ struct ast_format_cap *caps;
+ int i;
+ int rtp_code;
+ int min_packet_size = 0;
+ int max_packet_size = 0;
+ enum ast_media_type media_type;
+ char tmp[64];
+ struct ast_sockaddr address_rtp;
+ struct ast_rtp_instance *rtp;
+ struct ast_sdp_a_line *a_line;
+
+ stream = ast_stream_topology_get_stream(capabilities->topology, stream_index);
+ rtp = AST_VECTOR_GET(&capabilities->streams, stream_index)->instance;
+
+ ast_assert(sdp && options && stream);
+
+ media_type = ast_stream_get_type(stream);
+ if (rtp) {
+ if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) {
+ return -1;
+ }
+ } else {
+ ast_sockaddr_setnull(&address_rtp);
+ }
+
+ m_line = ast_sdp_m_alloc(
+ ast_codec_media_type2str(ast_stream_get_type(stream)),
+ ast_sockaddr_port(&address_rtp), 1,
+ options->encryption != AST_SDP_ENCRYPTION_DISABLED ? "RTP/SAVP" : "RTP/AVP",
+ NULL);
+ if (!m_line) {
+ return -1;
+ }
+
+ caps = ast_stream_get_formats(stream);
+
+ for (i = 0; i < ast_format_cap_count(caps); i++) {
+ struct ast_format *format = ast_format_cap_get_format(caps, i);
+
+ if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(rtp), 1, format, 0)) == -1) {
+ ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
+ ao2_ref(format, -1);
+ continue;
+ }
+
+ if (ast_sdp_m_add_format(m_line, options, rtp_code, 1, format, 0)) {
+ ast_sdp_m_free(m_line);
+ ao2_ref(format, -1);
+ return -1;
+ }
+
+ if (ast_format_get_maximum_ms(format) &&
+ ((ast_format_get_maximum_ms(format) < max_packet_size) || !max_packet_size)) {
+ max_packet_size = ast_format_get_maximum_ms(format);
+ }
+
+ ao2_ref(format, -1);
+ }
+
+ if (rtp && media_type != AST_MEDIA_TYPE_VIDEO) {
+ for (i = 1LL; i <= AST_RTP_MAX; i <<= 1) {
+ if (!(options->telephone_event & i)) {
+ continue;
+ }
+
+ rtp_code = ast_rtp_codecs_payload_code(
+ ast_rtp_instance_get_codecs(rtp), 0, NULL, i);
+
+ if (rtp_code == -1) {
+ continue;
+ }
+
+ if (ast_sdp_m_add_format(m_line, options, rtp_code, 0, NULL, i)) {
+ continue;
+ }
+
+ if (i == AST_RTP_DTMF) {
+ snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code);
+ a_line = ast_sdp_a_alloc("fmtp", tmp);
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+ }
+ }
+ }
+
+ if (ast_sdp_m_get_a_count(m_line) == 0) {
+ return 0;
+ }
+
+ /* If ptime is set add it as an attribute */
+ min_packet_size = ast_rtp_codecs_get_framing(ast_rtp_instance_get_codecs(rtp));
+ if (!min_packet_size) {
+ min_packet_size = ast_format_cap_get_framing(caps);
+ }
+ if (min_packet_size) {
+ snprintf(tmp, sizeof(tmp), "%d", min_packet_size);
+
+ a_line = ast_sdp_a_alloc("ptime", tmp);
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+ }
+
+ if (max_packet_size) {
+ snprintf(tmp, sizeof(tmp), "%d", max_packet_size);
+ a_line = ast_sdp_a_alloc("maxptime", tmp);
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+ }
+
+ a_line = ast_sdp_a_alloc(ast_sdp_state_get_locally_held(sdp_state, stream_index) ? "sendonly" : "sendrecv", "");
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+
+ if (ast_sdp_add_m(sdp, m_line)) {
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief Create an SDP based on current SDP state
+ *
+ * \param sdp_state The current SDP state
+ * \retval NULL Failed to create SDP
+ * \retval non-NULL Newly-created SDP
+ */
+static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state,
+ const struct sdp_state_capabilities *capabilities)
+{
+ struct ast_sdp *sdp = NULL;
+ struct ast_stream_topology *topology;
+ const struct ast_sdp_options *options;
+ int stream_num;
+ struct ast_sdp_o_line *o_line = NULL;
+ struct ast_sdp_c_line *c_line = NULL;
+ struct ast_sdp_s_line *s_line = NULL;
+ struct ast_sdp_t_line *t_line = NULL;
+ char *address_type;
+ struct timeval tv = ast_tvnow();
+ uint32_t t;
+ int stream_count;
+
+ options = ast_sdp_state_get_options(sdp_state);
+ topology = capabilities->topology;
+
+ t = tv.tv_sec + 2208988800UL;
+ address_type = (strchr(options->media_address, ':') ? "IP6" : "IP4");
+
+ o_line = ast_sdp_o_alloc(options->sdpowner, t, t, address_type, options->media_address);
+ if (!o_line) {
+ goto error;
+ }
+ c_line = ast_sdp_c_alloc(address_type, options->media_address);
+ if (!c_line) {
+ goto error;
+ }
+
+ s_line = ast_sdp_s_alloc(options->sdpsession);
+ if (!s_line) {
+ goto error;
+ }
+
+ sdp = ast_sdp_alloc(o_line, c_line, s_line, NULL);
+ if (!sdp) {
+ goto error;
+ }
+
+ stream_count = ast_stream_topology_get_count(topology);
+
+ for (stream_num = 0; stream_num < stream_count; stream_num++) {
+ enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(topology, stream_num));
+
+ if (type == AST_MEDIA_TYPE_AUDIO || type == AST_MEDIA_TYPE_VIDEO) {
+ if (sdp_add_m_from_rtp_stream(sdp, sdp_state, options, capabilities, stream_num)) {
+ goto error;
+ }
+ }
+ }
+
+ return sdp;
+
+error:
+ if (sdp) {
+ ast_sdp_free(sdp);
+ } else {
+ ast_sdp_t_free(t_line);
+ ast_sdp_s_free(s_line);
+ ast_sdp_c_free(c_line);
+ ast_sdp_o_free(o_line);
+ }
+
+ return NULL;
+}
+