diff --git a/include/asterisk/codec.h b/include/asterisk/codec.h
index 3873324b14399b453ae9bd5ab57e9695358acff2..2ae9551816ddd0674a2e321758e60fa331201db5 100644
--- a/include/asterisk/codec.h
+++ b/include/asterisk/codec.h
@@ -33,6 +33,7 @@ enum ast_media_type {
 	AST_MEDIA_TYPE_VIDEO,
 	AST_MEDIA_TYPE_IMAGE,
 	AST_MEDIA_TYPE_TEXT,
+	AST_MEDIA_TYPE_END,
 };
 
 struct ast_module;
diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h
index e73ed3fe7a7881bc350f441f8621ab7e66be3943..cffe6ea4cb138b56617970f2419c10386df38239 100644
--- a/include/asterisk/stream.h
+++ b/include/asterisk/stream.h
@@ -38,6 +38,11 @@ struct ast_stream;
  */
 struct ast_format_cap;
 
+/*!
+ * \brief The topology of a set of streams
+ */
+struct ast_stream_topology;
+
 /*!
  * \brief States that a stream may be in
  */
@@ -90,12 +95,25 @@ struct ast_stream *ast_stream_create(const char *name, enum ast_media_type type)
  */
 void ast_stream_destroy(struct ast_stream *stream);
 
+/*!
+ * \brief Create a deep clone of an existing stream
+ *
+ * \param stream The existing stream
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \since 15
+ */
+struct ast_stream *ast_stream_clone(const struct ast_stream *stream);
+
 /*!
  * \brief Get the name of a stream
  *
  * \param stream The media stream
  *
- * \return The name of the stream
+ * \retval non-NULL success
+ * \retval NULL failure
  *
  * \since 15
  */
@@ -106,7 +124,7 @@ const char *ast_stream_get_name(const struct ast_stream *stream);
  *
  * \param stream The media stream
  *
- * \return The media type of the stream
+ * \return The media type of the stream (AST_MEDIA_TYPE_UNKNOWN on error)
  *
  * \since 15
  */
@@ -127,7 +145,8 @@ void ast_stream_set_type(struct ast_stream *stream, enum ast_media_type type);
  *
  * \param stream The media stream
  *
- * \return The negotiated media formats
+ * \retval non-NULL success
+ * \retval NULL failure
  *
  * \note The reference count is not increased
  *
@@ -141,6 +160,9 @@ struct ast_format_cap *ast_stream_get_formats(const struct ast_stream *stream);
  * \param stream The media stream
  * \param caps The current negotiated formats
  *
+ * \note The new format capabilities structure has its refcount bumped and
+ * any existing format capabilities structure has its refcount decremented.
+ *
  * \since 15
  */
 void ast_stream_set_formats(struct ast_stream *stream, struct ast_format_cap *caps);
@@ -150,7 +172,7 @@ void ast_stream_set_formats(struct ast_stream *stream, struct ast_format_cap *ca
  *
  * \param stream The media stream
  *
- * \return The state of the stream
+ * \return The state of the stream (AST_STREAM_STATE_UNKNOWN on error)
  *
  * \since 15
  */
@@ -169,14 +191,129 @@ enum ast_stream_state ast_stream_get_state(const struct ast_stream *stream);
 void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state);
 
 /*!
- * \brief Get the number of the stream
+ * \brief Get the position of the stream in the topology
  *
  * \param stream The media stream
  *
- * \return The number of the stream
+ * \return The position of the stream (-1 on error)
+ *
+ * \since 15
+ */
+int ast_stream_get_position(const struct ast_stream *stream);
+
+/*!
+ * \brief Create a stream topology
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \since 15
+ */
+struct ast_stream_topology *ast_stream_topology_create(void);
+
+/*!
+ * \brief Create a deep clone of an existing stream topology
+ *
+ * \param topology The existing topology of streams
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \since 15
+ */
+struct ast_stream_topology *ast_stream_topology_clone(
+	const struct ast_stream_topology *topology);
+
+/*!
+ * \brief Destroy a stream topology
+ *
+ * \param topology The topology of streams
+ *
+ * \note All streams contained within the topology will be destroyed
+ *
+ * \since 15
+ */
+void ast_stream_topology_destroy(struct ast_stream_topology *topology);
+
+/*!
+ * \brief Append a stream to the topology
+ *
+ * \param topology The topology of streams
+ * \param stream The stream to append
+ *
+ * \returns the position of the stream in the topology (-1 on error)
+ *
+ * \since 15
+ */
+int ast_stream_topology_append_stream(struct ast_stream_topology *topology,
+	struct ast_stream *stream);
+
+/*!
+ * \brief Get the number of streams in a topology
+ *
+ * \param topology The topology of streams
+ *
+ * \return the number of streams (-1 on error)
+ *
+ * \since 15
+ */
+int ast_stream_topology_get_count(const struct ast_stream_topology *topology);
+
+/*!
+ * \brief Get a specific stream from the topology
+ *
+ * \param topology The topology of streams
+ * \param position The topology position to get
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \since 15
+ */
+struct ast_stream *ast_stream_topology_get_stream(
+	const struct ast_stream_topology *topology, unsigned int position);
+
+/*!
+ * \brief Set a specific position in a topology
+ *
+ * \param topology The topology of streams
+ * \param position The topology position to set
+ * \param stream The stream to put in its place
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note If an existing stream exists it will be destroyed
+ *
+ * \note You can overwrite an existing position in the topology or set
+ * the first unused position.  You can't set positions beyond that.
+ *
+ * \since 15
+ */
+int ast_stream_topology_set_stream(struct ast_stream_topology *topology,
+	unsigned int position, struct ast_stream *stream);
+
+/*!
+ * \brief A helper function that, given a format capabilities structure,
+ * creates a topology and separates the media types in format_cap into
+ * separate streams.
+ *
+ * \param caps The format capabilities structure
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \note The format capabilities reference is NOT altered by this function
+ * since a new format capabilities structure is created for each media type.
+ *
+ * \note Each stream will have its name set to the corresponding media type.
+ * For example: "AST_MEDIA_TYPE_AUDIO".
+ *
+ * \note Each stream will be set to the sendrecv state.
  *
  * \since 15
  */
-unsigned int ast_stream_get_num(const struct ast_stream *stream);
+struct ast_stream_topology *ast_stream_topology_create_from_format_cap(
+	struct ast_format_cap *cap);
 
 #endif /* _AST_STREAM_H */
diff --git a/main/format_cap.c b/main/format_cap.c
index 1fe342b3107066b91d4dfdaf283b2f246bf39d59..b0897c001c0e9f77640dffd6b362760506a33648 100644
--- a/main/format_cap.c
+++ b/main/format_cap.c
@@ -268,6 +268,7 @@ int ast_format_cap_append_from_cap(struct ast_format_cap *dst, const struct ast_
 {
 	int idx, res = 0;
 
+	/* NOTE:  The streams API is dependent on the formats being in "preference" order */
 	for (idx = 0; (idx < AST_VECTOR_SIZE(&src->preference_order)) && !res; ++idx) {
 		struct format_cap_framed *framed = AST_VECTOR_GET(&src->preference_order, idx);
 
diff --git a/main/stream.c b/main/stream.c
index fb3dbd5ce5d274ba986ea8e1af0db4f71b53bd66..0fabfc7387d86522368387dff6a066dd313158da 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -32,6 +32,8 @@
 #include "asterisk/logger.h"
 #include "asterisk/stream.h"
 #include "asterisk/strings.h"
+#include "asterisk/format.h"
+#include "asterisk/format_cap.h"
 
 struct ast_stream {
 	/*!
@@ -40,9 +42,9 @@ struct ast_stream {
 	enum ast_media_type type;
 
 	/*!
-	 * \brief Unique number for the stream within the context of the channel it is on
+	 * \brief The position of the stream in the topology
 	 */
-	unsigned int num;
+	unsigned int position;
 
 	/*!
 	 * \brief Current formats negotiated on the stream
@@ -60,6 +62,13 @@ struct ast_stream {
 	char name[0];
 };
 
+struct ast_stream_topology {
+	/*!
+	 * \brief A vector of all the streams in this topology
+	 */
+	AST_VECTOR(, struct ast_stream *) streams;
+};
+
 struct ast_stream *ast_stream_create(const char *name, enum ast_media_type type)
 {
 	struct ast_stream *stream;
@@ -71,11 +80,34 @@ struct ast_stream *ast_stream_create(const char *name, enum ast_media_type type)
 
 	stream->type = type;
 	stream->state = AST_STREAM_STATE_INACTIVE;
-	strcpy(stream->name, S_OR(name, ""));
+	strcpy(stream->name, S_OR(name, "")); /* Safe */
 
 	return stream;
 }
 
+struct ast_stream *ast_stream_clone(const struct ast_stream *stream)
+{
+	struct ast_stream *new_stream;
+	size_t stream_size;
+
+	if (!stream) {
+		return NULL;
+	}
+
+	stream_size = sizeof(*stream) + strlen(stream->name) + 1;
+	new_stream = ast_calloc(1, stream_size);
+	if (!new_stream) {
+		return NULL;
+	}
+
+	memcpy(new_stream, stream, stream_size);
+	if (new_stream->formats) {
+		ao2_ref(new_stream->formats, +1);
+	}
+
+	return new_stream;
+}
+
 void ast_stream_destroy(struct ast_stream *stream)
 {
 	if (!stream) {
@@ -88,41 +120,215 @@ void ast_stream_destroy(struct ast_stream *stream)
 
 const char *ast_stream_get_name(const struct ast_stream *stream)
 {
+	ast_assert(stream != NULL);
+
 	return stream->name;
 }
 
 enum ast_media_type ast_stream_get_type(const struct ast_stream *stream)
 {
+	ast_assert(stream != NULL);
+
 	return stream->type;
 }
 
 void ast_stream_set_type(struct ast_stream *stream, enum ast_media_type type)
 {
+	ast_assert(stream != NULL);
+
 	stream->type = type;
 }
 
 struct ast_format_cap *ast_stream_get_formats(const struct ast_stream *stream)
 {
+	ast_assert(stream != NULL);
+
 	return stream->formats;
 }
 
 void ast_stream_set_formats(struct ast_stream *stream, struct ast_format_cap *caps)
 {
+	ast_assert(stream != NULL);
+
 	ao2_cleanup(stream->formats);
 	stream->formats = ao2_bump(caps);
 }
 
 enum ast_stream_state ast_stream_get_state(const struct ast_stream *stream)
 {
+	ast_assert(stream != NULL);
+
 	return stream->state;
 }
 
 void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state)
 {
+	ast_assert(stream != NULL);
+
 	stream->state = state;
 }
 
-unsigned int ast_stream_get_num(const struct ast_stream *stream)
+int ast_stream_get_position(const struct ast_stream *stream)
+{
+	ast_assert(stream != NULL);
+
+	return stream->position;
+}
+
+#define TOPOLOGY_INITIAL_STREAM_COUNT 2
+struct ast_stream_topology *ast_stream_topology_create(void)
+{
+	struct ast_stream_topology *topology;
+
+	topology = ast_calloc(1, sizeof(*topology));
+	if (!topology) {
+		return NULL;
+	}
+
+	if (AST_VECTOR_INIT(&topology->streams, TOPOLOGY_INITIAL_STREAM_COUNT)) {
+		ast_free(topology);
+		topology = NULL;
+	}
+
+	return topology;
+}
+
+struct ast_stream_topology *ast_stream_topology_clone(
+	const struct ast_stream_topology *topology)
+{
+	struct ast_stream_topology *new_topology;
+	int i;
+
+	ast_assert(topology != NULL);
+
+	new_topology = ast_stream_topology_create();
+	if (!new_topology) {
+		return NULL;
+	}
+
+	for (i = 0; i < AST_VECTOR_SIZE(&topology->streams); i++) {
+		struct ast_stream *stream =
+			ast_stream_clone(AST_VECTOR_GET(&topology->streams, i));
+
+		if (!stream || AST_VECTOR_APPEND(&new_topology->streams, stream)) {
+			ast_stream_destroy(stream);
+			ast_stream_topology_destroy(new_topology);
+			return NULL;
+		}
+	}
+
+	return new_topology;
+}
+
+void ast_stream_topology_destroy(struct ast_stream_topology *topology)
+{
+	if (!topology) {
+		return;
+	}
+
+	AST_VECTOR_CALLBACK_VOID(&topology->streams, ast_stream_destroy);
+	AST_VECTOR_FREE(&topology->streams);
+	ast_free(topology);
+}
+
+int ast_stream_topology_append_stream(struct ast_stream_topology *topology, struct ast_stream *stream)
+{
+	ast_assert(topology && stream);
+
+	if (AST_VECTOR_APPEND(&topology->streams, stream)) {
+		return -1;
+	}
+
+	return AST_VECTOR_SIZE(&topology->streams) - 1;
+}
+
+int ast_stream_topology_get_count(const struct ast_stream_topology *topology)
+{
+	ast_assert(topology != NULL);
+
+	return AST_VECTOR_SIZE(&topology->streams);
+}
+
+struct ast_stream *ast_stream_topology_get_stream(
+	const struct ast_stream_topology *topology, unsigned int stream_num)
+{
+	ast_assert(topology != NULL);
+
+	return AST_VECTOR_GET(&topology->streams, stream_num);
+}
+
+int ast_stream_topology_set_stream(struct ast_stream_topology *topology,
+	unsigned int position, struct ast_stream *stream)
 {
-	return stream->num;
+	struct ast_stream *existing_stream;
+
+	ast_assert(topology && stream);
+
+	if (position > AST_VECTOR_SIZE(&topology->streams)) {
+		return -1;
+	}
+
+	existing_stream = AST_VECTOR_GET(&topology->streams, position);
+	ast_stream_destroy(existing_stream);
+
+	if (position == AST_VECTOR_SIZE(&topology->streams)) {
+		AST_VECTOR_APPEND(&topology->streams, stream);
+		return 0;
+	}
+
+	stream->position = position;
+	return AST_VECTOR_REPLACE(&topology->streams, position, stream);
+}
+
+struct ast_stream_topology *ast_stream_topology_create_from_format_cap(
+	struct ast_format_cap *cap)
+{
+	struct ast_stream_topology *topology;
+	enum ast_media_type type;
+
+	ast_assert(cap != NULL);
+
+	topology = ast_stream_topology_create();
+	if (!topology) {
+		return NULL;
+	}
+
+	for (type = AST_MEDIA_TYPE_UNKNOWN + 1; type < AST_MEDIA_TYPE_END; type++) {
+		struct ast_format_cap *new_cap;
+		struct ast_stream *stream;
+
+		if (!ast_format_cap_has_type(cap, type)) {
+			continue;
+		}
+
+		new_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+		if (!new_cap) {
+			ast_stream_topology_destroy(topology);
+			return NULL;
+		}
+
+		ast_format_cap_set_framing(new_cap, ast_format_cap_get_framing(cap));
+		if (ast_format_cap_append_from_cap(new_cap, cap, type)) {
+			ao2_cleanup(new_cap);
+			ast_stream_topology_destroy(topology);
+			return NULL;
+		}
+
+		stream = ast_stream_create(ast_codec_media_type2str(type), type);
+		if (!stream) {
+			ao2_cleanup(new_cap);
+			ast_stream_topology_destroy(topology);
+			return NULL;
+		}
+		/* We're transferring the initial ref so no bump needed */
+		stream->formats = new_cap;
+		stream->state = AST_STREAM_STATE_SENDRECV;
+		if (!ast_stream_topology_append_stream(topology, stream)) {
+			ast_stream_destroy(stream);
+			ast_stream_topology_destroy(topology);
+			return NULL;
+		}
+	}
+
+	return topology;
 }
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index 4d6a1a168b98a17838e8561d064747adc260587a..e32d2b65f8d91b9e59f2b0094033d5a12334d21e 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -87,7 +87,8 @@ static int media_type_to_fdno(enum ast_media_type media_type)
 	case AST_MEDIA_TYPE_VIDEO: return FD_VIDEO;
 	case AST_MEDIA_TYPE_TEXT:
 	case AST_MEDIA_TYPE_UNKNOWN:
-	case AST_MEDIA_TYPE_IMAGE: break;
+	case AST_MEDIA_TYPE_IMAGE:
+	case AST_MEDIA_TYPE_END: break;
 	}
 	return -1;
 }