[Asterisk-code-review] Streams: Add features for Advanced Codec Negotiation (asterisk[master])
George Joseph
asteriskteam at digium.com
Fri Jun 26 11:36:58 CDT 2020
George Joseph has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/14577 )
Change subject: Streams: Add features for Advanced Codec Negotiation
......................................................................
Streams: Add features for Advanced Codec Negotiation
The Streams API becomes the home for the core ACN capabilities.
These include...
* Parsing and formatting of codec negotation preferences.
* Resolving pending streams and topologies with those configured
using configured preferences.
* Utility functions for creating string representations of
streams, topologies, and negotiation preferences.
For codec negotiation preferences:
* Added ast_stream_codec_prefs_parse() which takes a string
representation of codec negotiation preferences, which
may come from a pjsip endpoint for example, and populates
a ast_stream_codec_negotiation_prefs structure.
* Added ast_stream_codec_prefs_to_str() which does the reverse.
* Added many functions to parse individual parameter name
and value strings to their respectrive enum values, and the
reverse.
For streams:
* Added ast_stream_create_resolved() which takes a "live" stream
and resolves it with a configured stream and the negotiation
preferences to create a new stream.
* Added ast_stream_to_str() which create a string representation
of a stream suitable for debug or display purposes.
For topology:
* Added ast_stream_topology_create_resolved() which takes a "live"
topology and resolves it, stream by stream, with a configured
topology stream and the negotiation preferences to create a new
topology.
* Added ast_stream_topology_to_str() which create a string
representation of a topology suitable for debug or display
purposes.
* Renamed ast_format_caps_from_topology() to
ast_stream_topology_get_formats() to be more consistent with
the existing ast_stream_get_formats().
Change-Id: I2df77dedd0c72c52deb6e329effe057a8e06cd56
---
M channels/chan_pjsip.c
A doc/CHANGES-staging/stream_topology.txt
A doc/UPGRADE-staging/stream_topology.txt
M include/asterisk/format_cap.h
M include/asterisk/stream.h
M main/channel.c
M main/core_unreal.c
M main/format_cap.c
M main/stream.c
M tests/test_stream.c
10 files changed, 898 insertions(+), 13 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/77/14577/1
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index afaecdf..2fc98b4 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -521,7 +521,7 @@
struct ast_format_cap *cap_from_top;
int res;
- cap_from_top = ast_format_cap_from_stream_topology(top);
+ cap_from_top = ast_stream_topology_get_formats(top);
if (!cap_from_top) {
return 0;
@@ -581,7 +581,7 @@
ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
topology = ast_stream_topology_clone(session->endpoint->media.topology);
} else {
- caps = ast_format_cap_from_stream_topology(session->pending_media_state->topology);
+ caps = ast_stream_topology_get_formats(session->pending_media_state->topology);
topology = ast_stream_topology_clone(session->pending_media_state->topology);
}
diff --git a/doc/CHANGES-staging/stream_topology.txt b/doc/CHANGES-staging/stream_topology.txt
new file mode 100644
index 0000000..57ed8fa
--- /dev/null
+++ b/doc/CHANGES-staging/stream_topology.txt
@@ -0,0 +1,6 @@
+Subject: Core
+Master-Only: True
+
+New capabilities for Advanced Codec Negotiation have been added
+to Streams and one API changed name... ast_format_cap_from_stream_topology
+has been renamed to ast_stream_topology_get_formats.
diff --git a/doc/UPGRADE-staging/stream_topology.txt b/doc/UPGRADE-staging/stream_topology.txt
new file mode 100644
index 0000000..e65c792
--- /dev/null
+++ b/doc/UPGRADE-staging/stream_topology.txt
@@ -0,0 +1,7 @@
+Subject: Core
+Master-Only: True
+
+The ast_format_cap_from_stream_topology() function has been renamed
+to ast_stream_topology_get_formats().
+
+
diff --git a/include/asterisk/format_cap.h b/include/asterisk/format_cap.h
index 37021eb..56e9f84 100644
--- a/include/asterisk/format_cap.h
+++ b/include/asterisk/format_cap.h
@@ -306,6 +306,14 @@
* \param cap The capabilities structure containing the formats
* \param buf A \c ast_str buffer to populate with the names of the formats
*
+ * \since 18 (update)
+ * If buf is NULL the function will internally allocate memory for the ast_str and
+ * automatically free it when the format_cap is destroyed. This makes it easier to
+ * use this function in debugging scenatios because the caller doesn't have to
+ * worry about allocating or freeing an sast_str. The buffer is also appended
+ * to rather than set for cases where the caller wishes to prepend some text
+ * to the result.
+ *
* \return The contents of the buffer in \c buf
*/
const char *ast_format_cap_get_names(const struct ast_format_cap *cap, struct ast_str **buf);
@@ -324,4 +332,19 @@
*/
int ast_format_cap_empty(const struct ast_format_cap *cap);
+/*!
+ * \brief Print format caps as debug message
+ * \since 18.0.0
+ *
+ * \param __level The debug level to print on
+ * \param __id Some string representing an id like endpoint name, channel name, etc.
+ * \param __tag An arbitrary string to print
+ * \param __caps The format caps to print
+ */
+#define ast_format_cap_debug(__level, __id, __tag, __caps) \
+({ \
+ struct ast_str *__s1 = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); \
+ ast_debug(__level, "%s %s caps: %s\n", __id, __tag, ast_format_cap_get_names(__caps, &__s1)); \
+})
+
#endif /* _AST_FORMAT_CAP_H */
diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h
index bbeeacb..5d2fc66 100644
--- a/include/asterisk/stream.h
+++ b/include/asterisk/stream.h
@@ -30,6 +30,23 @@
#include "asterisk/vector.h"
/*!
+ * \brief Internal macro to assist converting enums to strings
+ * \internal
+ * \since 18
+ *
+ * This macro checks that _value is in the bounds
+ * of the enum/string map.
+ */
+#define _stream_maps_to_str(_mapname, _value) \
+({ \
+ const char *_rtn = ""; \
+ if (ARRAY_IN_BOUNDS(_value, _mapname)) { \
+ _rtn = _mapname[_value]; \
+ } \
+ _rtn; \
+})
+
+/*!
* \brief Forward declaration for a stream, as it is opaque
*/
struct ast_stream;
@@ -75,9 +92,266 @@
* \brief Set when the stream is not sending OR receiving media
*/
AST_STREAM_STATE_INACTIVE,
+ /*!
+ * \brief Sentinel
+ */
+ AST_STREAM_STATE_END
};
/*!
+ * \brief Stream state enum to string map
+ * \internal
+ * \since 18
+ */
+extern const char *ast_stream_state_map[AST_STREAM_STATE_END];
+
+/*!
+ * \brief Safely get the name of a stream state
+ * \since 18
+ *
+ * \param stream_state One of enum ast_stream_state
+ * \returns A constant string with the name of the state or an empty string
+ * if an invalid value was passed in.
+ */
+#define ast_stream_state_to_str(stream_state) _stream_maps_to_str(ast_stream_state_map, stream_state)
+
+/*!
+ * \brief Advanced Codec Negotiation Preferences
+ * \since 18
+ */
+
+/*!
+ * \brief The preference parameters themselves
+ * \since 18
+ */
+enum ast_stream_codec_negotiation_params {
+ CODEC_NEGOTIATION_PARAM_UNSPECIFIED = 0,
+ /*! Which if the lists to "prefer" */
+ CODEC_NEGOTIATION_PARAM_PREFER,
+ /*! "operation" to perform */
+ CODEC_NEGOTIATION_PARAM_OPERATION,
+ /*! "keep" all or only first */
+ CODEC_NEGOTIATION_PARAM_KEEP,
+ /*! Allow or prevent "transcode" */
+ CODEC_NEGOTIATION_PARAM_TRANSCODE,
+ /*! Sentinel */
+ CODEC_NEGOTIATION_PARAM_END,
+};
+
+/*!
+ * \brief The "prefer" values
+ * \since 18
+ */
+enum ast_stream_codec_negotiation_prefs_prefer_values {
+ CODEC_NEGOTIATION_PREFER_UNSPECIFIED = 0,
+ /*! Prefer the "pending" list */
+ CODEC_NEGOTIATION_PREFER_PENDING,
+ /*! Prefer the "configured" list */
+ CODEC_NEGOTIATION_PREFER_CONFIGURED,
+ /*! Sentinel */
+ CODEC_NEGOTIATION_PREFER_END,
+};
+
+/*!
+ * \brief The "operation" values
+ * \since 18
+ */
+enum ast_stream_codec_negotiation_prefs_operation_values {
+ CODEC_NEGOTIATION_OPERATION_UNSPECIFIED = 0,
+ /*! "intersect": only those codecs that appear in both lists */
+ CODEC_NEGOTIATION_OPERATION_INTERSECT,
+ /*! "union": all codecs in both lists */
+ CODEC_NEGOTIATION_OPERATION_UNION,
+ /*! "only_preferred": only the codecs in the preferred list */
+ CODEC_NEGOTIATION_OPERATION_ONLY_PREFERRED,
+ /*! "only_nonpreferred": only the codecs in the non-preferred list */
+ CODEC_NEGOTIATION_OPERATION_ONLY_NONPREFERRED,
+ /*! Sentinel */
+ CODEC_NEGOTIATION_OPERATION_END,
+};
+
+/*!
+ * \brief The "keep" values
+ * \since 18
+ */
+enum ast_stream_codec_negotiation_prefs_keep_values {
+ CODEC_NEGOTIATION_KEEP_UNSPECIFIED = 0,
+ /*! "keep" all codecs after performing the operation */
+ CODEC_NEGOTIATION_KEEP_ALL,
+ /*! "keep" only the first codec after performing the operation */
+ CODEC_NEGOTIATION_KEEP_FIRST,
+ /*! Sentinel */
+ CODEC_NEGOTIATION_KEEP_END,
+};
+
+/*!
+ * \brief The "transcode" values
+ * \since 18
+ */
+enum ast_stream_codec_negotiation_prefs_transcode_values {
+ CODEC_NEGOTIATION_TRANSCODE_UNSPECIFIED = 0,
+ /*! "allow" transcoding */
+ CODEC_NEGOTIATION_TRANSCODE_ALLOW,
+ /*! "prevent" transcoding */
+ CODEC_NEGOTIATION_TRANSCODE_PREVENT,
+ /*! Sentinel */
+ CODEC_NEGOTIATION_TRANSCODE_END,
+};
+
+/*!
+ * \brief Preference enum to string map
+ * \internal
+ * \since 18
+ */
+extern const char *ast_stream_codec_negotiation_params_map[CODEC_NEGOTIATION_PARAM_END];
+/*!
+ * \brief "prefer" enum to string map
+ * \internal
+ * \since 18
+ */
+extern const char *ast_stream_codec_negotiation_prefer_map[CODEC_NEGOTIATION_PREFER_END];
+/*!
+ * \brief "operation" enum to string map
+ * \internal
+ * \since 18
+ */
+extern const char *ast_stream_codec_negotiation_operation_map[CODEC_NEGOTIATION_OPERATION_END];
+/*!
+ * \brief "keep" enum to string map
+ * \internal
+ * \since 18
+ */
+extern const char *ast_stream_codec_negotiation_keep_map[CODEC_NEGOTIATION_KEEP_END];
+/*!
+ * \brief "transcode" state enum to string map
+ * \internal
+ * \since 18
+ */
+extern const char *ast_stream_codec_negotiation_transcode_map[CODEC_NEGOTIATION_TRANSCODE_END];
+
+/*!
+ * \brief Safely get the name of a preference parameter
+ * \since 18
+ *
+ * \param value One of enum \ref ast_stream_codec_negotiation_params
+ * \returns A constant string with the name of the preference or an empty string
+ * if an invalid value was passed in.
+ */
+#define ast_stream_codec_param_to_str(value) _stream_maps_to_str(ast_stream_codec_negotiation_params_map, value)
+
+/*!
+ * \brief Safely get the name of a "prefer" parameter value
+ * \since 18
+ *
+ * \param value One of enum \ref ast_stream_codec_negotiation_prefer_values
+ * \returns A constant string with the name of the value or an empty string
+ * if an invalid value was passed in.
+ */
+#define ast_stream_codec_prefer_to_str(value) _stream_maps_to_str(ast_stream_codec_negotiation_prefer_map, value)
+
+/*!
+ * \brief Safely get the name of an "operation" parameter value
+ * \since 18
+ *
+ * \param value One of enum \ref ast_stream_codec_negotiation_operation_values
+ * \returns A constant string with the name of the value or an empty string
+ * if an invalid value was passed in.
+ */
+#define ast_stream_codec_operation_to_str(value) _stream_maps_to_str(ast_stream_codec_negotiation_operation_map, value)
+
+/*!
+ * \brief Safely get the name of a "keep" parameter value
+ * \since 18
+ *
+ * \param value One of enum \ref ast_stream_codec_negotiation_keep_values
+ * \returns A constant string with the name of the value or an empty string
+ * if an invalid value was passed in.
+ */
+#define ast_stream_codec_keep_to_str(value) _stream_maps_to_str(ast_stream_codec_negotiation_keep_map, value)
+
+/*!
+ * \brief Safely get the name of a "transcode" parameter value
+ * \since 18
+ *
+ * \param value One of enum \ref ast_stream_codec_negotiation_transcode_values
+ * \returns A constant string with the name of the value or an empty string
+ * if an invalid value was passed in.
+ */
+#define ast_stream_codec_transcode_to_str(value) _stream_maps_to_str(ast_stream_codec_negotiation_transcode_map, value)
+
+/*!
+ * \brief
+ * \since 18
+ *
+ * The structure that makes up a codec negotiation preference object
+ */
+struct ast_stream_codec_negotiation_prefs {
+ enum ast_stream_codec_negotiation_prefs_prefer_values prefer;
+ enum ast_stream_codec_negotiation_prefs_operation_values operation;
+ enum ast_stream_codec_negotiation_prefs_keep_values keep;
+ enum ast_stream_codec_negotiation_prefs_transcode_values transcode;
+};
+
+/*!
+ * \brief Define for allocating buffer space for to_str() functions
+ * \since 18
+ */
+#define AST_STREAM_MAX_CODEC_PREFS_LENGTH (128)
+
+/*!
+ * \brief Return a string representing the codec preferences
+ * \since 18
+ *
+ * This function can be used for debugging purposes but is also
+ * used in pjsip_configuration as a sorcery parameter handler
+ *
+ * \param prefs A pointer to a ast_stream_codec_negotiation_prefs structure
+ * \param buf A pointer to an ast_str* used for the output. See note below.
+ *
+ * \returns the contents of the ast_str as a const char *.
+ *
+ * \warning No attempt should ever be made to free the returned
+ * char * and it should be dup'd if needed after the ast_str is freed.
+ *
+ * \note
+ * buf can't be NULL but it CAN contain a NULL value. If so, a new
+ * ast_str will be allocated and the value of buf updated with a pointer
+ * to it. Whether the caller supplies the ast_str or it's allocated by
+ * this function, it's the caller's responsibility to free it.
+ *
+ * Sample output:
+ * "prefer: configured, operation: union, keep:all, transcode:prevent"
+ */
+const char *ast_stream_codec_prefs_to_str(const struct ast_stream_codec_negotiation_prefs *prefs,
+ struct ast_str **buf);
+
+/*!
+ * \brief Parses a string representing the codec prefs into a ast_stream_codec_negotiation_pref structure
+ * \since 18
+ *
+ * This function is mainly used by pjsip_configuration as a sorcery parameter handler.
+ *
+ * \param pref_string A string in the format described by ast_stream_codec_prefs_to_str().
+ * \param prefs Pointer to a ast_stream_codec_negotiation_prefs structure to receive the parsed values.
+ * \param error_message An optional ast_str** into which parsing errors will be placed.
+ *
+ * \retval 0 if success
+ * \retval -1 if failed
+ *
+ * \details
+ * Whitespace around the ':' and ',' separators is ignored and the parameters
+ * can be specified in any order. Parameters missing in the input string
+ * will have their values set to the appropriate *_UNSPECIFIED value and will not
+ * be considered an error. It's up to the caller to decide whether set a default
+ * value, return an error, etc.
+ *
+ * Sample input:
+ * "prefer : configured , operation: union,keep:all, transcode:prevent"
+ */
+int ast_stream_codec_prefs_parse(const char *pref_string, struct ast_stream_codec_negotiation_prefs *prefs,
+ struct ast_str **error_message);
+
+/*!
* \brief Create a new media stream representation
*
* \param name A name for the stream
@@ -167,6 +441,42 @@
const struct ast_format_cap *ast_stream_get_formats(const struct ast_stream *stream);
/*!
+ * \brief Get a string representing the stream for debugging/display purposes
+ * \since 18
+ *
+ * \param stream A stream
+ * \param buf A pointer to an ast_str* used for the output. See note below.
+ *
+ * \returns the contents of the ast_str as a const char *.
+ *
+ * \warning No attempt should ever be made to free the returned
+ * char * and it should be dup'd if needed after the ast_str is freed.
+ *
+ * \details
+ * If buf is not NULL and *buf is not null, the string representation of
+ * the stream will be appended to the provided ast_str and a pointer to
+ * the ast_str's buffer returned.
+ *
+ * If buf is not NULL but *buf is NULL, it's an error and NULL will be retured
+ *
+ * If buf is NULL, an ast_str will be internally allocated and a pointer to
+ * the ast_str's buffer returned. When the stream is freed, the internal
+ * ast_str will be freed.
+ *
+ * It stream is NULL, "(null stream)" is returned. If there's some other error,
+ * an empty string is returned. This makes the function safe to use even if
+ * any of the arguments are NULL.
+ *
+ * Return format:
+ * <name>:<media_type>:<stream_state> (formats)
+ *
+ * Sample return:
+ * "audio:audio:sendrecv (ulaw,g722)"
+ *
+ */
+const char *ast_stream_to_str(const struct ast_stream *stream, struct ast_str **buf);
+
+/*!
* \brief Get the count of the current negotiated formats of a stream
*
* \param stream The media stream
@@ -311,6 +621,33 @@
void ast_stream_set_rtp_codecs(struct ast_stream *stream, struct ast_rtp_codecs *rtp_codecs);
/*!
+ * \brief Create a resolved stream from 2 streams
+ * \since 18
+ *
+ * \param pending_stream The "live" stream created from an SDP,
+ * passed through the core, or used to create an SDP.
+ * \param configured_stream The static stream used to validate the pending stream.
+ * \param prefs A pointer to an ast_stream_codec_negotiation_prefs structure.
+ * \param error_message If supplied, error messages will be appended.
+ *
+ * \details
+ * The resulting stream will contain all of the attributes of the pending stream
+ * but will contain only the formats that passed the validation specified by
+ * the ast_stream_codec_negotiation_prefs structure. This may mean that the
+ * stream's format_caps will be empty. It's up to the caller to determine
+ * what to do with the stream in that case. I.E. Free it, set it to the
+ * REMOVED state, etc. A stream will always be returned unless there was
+ * some catastrophic allocation failure.
+ *
+ * \retval NULL if there was some allocation failure.
+ * \retval A new, resolved stream.
+ *
+ */
+struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
+ struct ast_stream *configired_stream, struct ast_stream_codec_negotiation_prefs *prefs,
+ struct ast_str **error_message);
+
+/*!
* \brief Create a stream topology
*
* \retval non-NULL success
@@ -387,6 +724,18 @@
int ast_stream_topology_get_count(const struct ast_stream_topology *topology);
/*!
+ * \brief Get the number of non-REMOVED streams in a topology
+ *
+ * \param topology The topology of streams
+ *
+ * \return the number of streams
+ *
+ * \since 18
+ */
+int ast_stream_topology_get_valid_count(const struct ast_stream_topology *topology);
+
+
+/*!
* \brief Get a specific stream from the topology
*
* \param topology The topology of streams
@@ -478,10 +827,66 @@
*
* \since 15
*/
-struct ast_format_cap *ast_format_cap_from_stream_topology(
+struct ast_format_cap *ast_stream_topology_get_formats(
struct ast_stream_topology *topology);
/*!
+ * \brief Get a string representing the topology for debugging/display purposes
+ * \since 18
+ *
+ * \param topology A stream topology
+ * \param buf A pointer to an ast_str* used for the output. See note below.
+ *
+ * \returns the contents of the ast_str as a const char *.
+ *
+ * \warning No attempt should ever be made to free the returned
+ * char * and it should be dup'd if needed after the ast_str is freed.
+ *
+ * If buf is not NULL and *buf is not null, the string representation of
+ * the topology will be appended to the provided ast_str and a pointer to
+ * the ast_str's buffer returned.
+ *
+ * If buf is not NULL but *buf is NULL, it's an error and NULL will be retured
+ *
+ * If buf is NULL, an ast_str will be internally allocated and a pointer to
+ * the ast_str's buffer returned. When the topology is freed, the internal
+ * ast_str will be freed.
+ *
+ * It topology is NULL, "(null topology)" is returned. If there's some other error,
+ * an empty string is returned. This makes the function safe to use even if
+ * any of the arguments are NULL.
+ *
+ * Return format:
+ * <final>? <stream> ...
+ *
+ * Sample return:
+ * "final <audio:audio:sendrecv (ulaw,g722)> <video:video:sendonly (h264)>"
+ *
+ */
+const char *ast_stream_topology_to_str(const struct ast_stream_topology *topology, struct ast_str **buf);
+
+/*!
+ * \brief Create a format capabilities structure containing all the formats from all the streams
+ * of a particular type in the topology.
+ * \since 18
+ *
+ * \details
+ * A helper function that, given a stream topology and a media type, creates a format
+ * capabilities structure containing all formats from all active streams with the particular type.
+ *
+ * \param topology The topology of streams
+ * \param type The media type
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \note The stream topology is NOT altered by this function.
+ *
+ */
+struct ast_format_cap *ast_stream_topology_get_formats_by_type(
+ struct ast_stream_topology *topology, enum ast_media_type type);
+
+/*!
* \brief Gets the first active stream of a specific type from the topology
*
* \param topology The topology of streams
@@ -534,4 +939,34 @@
*/
void ast_stream_set_group(struct ast_stream *stream, int group);
+/*!
+ * \brief Create a resolved stream topology from 2 topologies
+ * \since 18
+ *
+ * \param pending_topology The "live" topology created from an SDP,
+ * passed through the core, or used to create an SDP.
+ * \param configured_topology The static topology used to validate the pending topology.
+ * It MUST have only 1 stream per media type.
+ * \param prefs A pointer to an ast_stream_codec_negotiation_prefs structure.
+ * \param error_message If supplied, error messages will be appended.
+ *
+ * \details
+ * The streams in the resolved topology will contain all of the attributes
+ * of the corresponding stream from the pending topology. It's format_caps
+ * however will contain only the formats that passed the validation
+ * specified by the ast_stream_codec_negotiation_prefs structure. This may
+ * mean that some of the streams format_caps will be empty. If that's the case,
+ * the stream will be in a REMOVED state. With those rules in mind,
+ * a resolved topology will always be returned (unless there's some catastrophic
+ * allocation failure) and the resolved topology is guaranteed to have the same
+ * number of streams, in the same order, as the pending topology.
+ *
+ * \retval NULL if there was some allocation failure.
+ * \retval The joint topology.
+ */
+struct ast_stream_topology *ast_stream_topology_create_resolved(
+ struct ast_stream_topology *pending_topology, struct ast_stream_topology *validation_topology,
+ struct ast_stream_codec_negotiation_prefs *prefs,
+ struct ast_str **error_message);
+
#endif /* _AST_STREAM_H */
diff --git a/main/channel.c b/main/channel.c
index 7424b81..40d7724 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -6227,7 +6227,7 @@
if (!request_cap && topology) {
/* Turn the request stream topology into capabilities */
- request_cap = tmp_converted_cap = ast_format_cap_from_stream_topology(topology);
+ request_cap = tmp_converted_cap = ast_stream_topology_get_formats(topology);
}
/* find the best audio format to use */
diff --git a/main/core_unreal.c b/main/core_unreal.c
index bff1c3e..16414e6 100644
--- a/main/core_unreal.c
+++ b/main/core_unreal.c
@@ -1054,7 +1054,7 @@
return NULL;
}
- unreal->reqcap = ast_format_cap_from_stream_topology(topology);
+ unreal->reqcap = ast_stream_topology_get_formats(topology);
if (!unreal->reqcap) {
ao2_ref(unreal, -1);
return NULL;
diff --git a/main/format_cap.c b/main/format_cap.c
index ca60557..7dd1fdf 100644
--- a/main/format_cap.c
+++ b/main/format_cap.c
@@ -58,6 +58,8 @@
AST_VECTOR(, struct format_cap_framed *) preference_order;
/*! \brief Global framing size, applies to all formats if no framing present on format */
unsigned int framing;
+ /*! \brief internal string to hold prefernece names */
+ struct ast_str *preference_names;
};
/*! \brief Linked list for formats */
@@ -72,6 +74,8 @@
struct ast_format_cap *cap = obj;
int idx;
+ ast_free(cap->preference_names);
+
for (idx = 0; idx < AST_VECTOR_SIZE(&cap->formats); idx++) {
struct format_cap_framed_list *list = AST_VECTOR_GET_ADDR(&cap->formats, idx);
struct format_cap_framed *framed;
@@ -703,7 +707,29 @@
{
int i;
- ast_str_set(buf, 0, "(");
+ if (!cap) {
+ if (buf) {
+ ast_str_append(buf, 0, "(null formats)");
+ return ast_str_buffer(*buf);
+ }
+
+ return "(null formats)";
+ }
+
+ if (!buf) {
+ if (!cap->preference_names) {
+ ((struct ast_format_cap *)cap)->preference_names = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ if (!cap->preference_names) {
+ return NULL;
+ }
+ }
+ buf = &((struct ast_format_cap *)cap)->preference_names;
+ ast_str_reset(*buf);
+ } else if (!*buf) {
+ return NULL;
+ }
+
+ ast_str_append(buf, 0, "(");
if (!cap || !AST_VECTOR_SIZE(&cap->preference_order)) {
ast_str_append(buf, 0, "nothing)");
diff --git a/main/stream.c b/main/stream.c
index c5aa5f8..58a7f10 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -44,6 +44,40 @@
char name_value[0];
};
+const char *ast_stream_codec_negotiation_params_map[] = {
+ [CODEC_NEGOTIATION_PARAM_UNSPECIFIED] = "unspecified",
+ [CODEC_NEGOTIATION_PARAM_PREFER] = "prefer",
+ [CODEC_NEGOTIATION_PARAM_OPERATION] = "operation",
+ [CODEC_NEGOTIATION_PARAM_KEEP] = "keep",
+ [CODEC_NEGOTIATION_PARAM_TRANSCODE] = "transcode",
+};
+
+const char *ast_stream_codec_negotiation_prefer_map[] = {
+ [CODEC_NEGOTIATION_PREFER_UNSPECIFIED] = "unspecified",
+ [CODEC_NEGOTIATION_PREFER_PENDING] = "pending",
+ [CODEC_NEGOTIATION_PREFER_CONFIGURED] = "configured",
+};
+
+const char *ast_stream_codec_negotiation_operation_map[] = {
+ [CODEC_NEGOTIATION_OPERATION_UNSPECIFIED] = "unspecified",
+ [CODEC_NEGOTIATION_OPERATION_INTERSECT] = "intersect",
+ [CODEC_NEGOTIATION_OPERATION_UNION] = "union",
+ [CODEC_NEGOTIATION_OPERATION_ONLY_PREFERRED] = "only_preferred",
+ [CODEC_NEGOTIATION_OPERATION_ONLY_NONPREFERRED] = "only_nonpreferred",
+};
+
+const char *ast_stream_codec_negotiation_keep_map[] = {
+ [CODEC_NEGOTIATION_KEEP_UNSPECIFIED] = "unspecified",
+ [CODEC_NEGOTIATION_KEEP_ALL] = "all",
+ [CODEC_NEGOTIATION_KEEP_FIRST] = "first",
+};
+
+const char *ast_stream_codec_negotiation_transcode_map[] = {
+ [CODEC_NEGOTIATION_TRANSCODE_UNSPECIFIED] = "unspecified",
+ [CODEC_NEGOTIATION_TRANSCODE_ALLOW] = "allow",
+ [CODEC_NEGOTIATION_TRANSCODE_PREVENT] = "prevent",
+};
+
struct ast_stream {
/*!
* \brief The type of media the stream is handling
@@ -80,6 +114,8 @@
*/
struct ast_rtp_codecs *rtp_codecs;
+ struct ast_str *display_str;
+
/*!
* \brief Name for the stream within the context of the channel it is on
*/
@@ -91,6 +127,111 @@
* \brief A vector of all the streams in this topology
*/
AST_VECTOR(, struct ast_stream *) streams;
+ int final;
+ struct ast_str *display_str;
+};
+
+const char *ast_stream_codec_prefs_to_str(const struct ast_stream_codec_negotiation_prefs *prefs, struct ast_str **buf)
+{
+ if (!prefs || !buf) {
+ return "";
+ }
+
+ if (!*buf) {
+ *buf = ast_str_create(AST_STREAM_MAX_CODEC_PREFS_LENGTH);
+ }
+
+ ast_str_append(buf, 0, "%s:%s, %s:%s, %s:%s, %s:%s",
+ ast_stream_codec_param_to_str(CODEC_NEGOTIATION_PARAM_PREFER),
+ ast_stream_codec_prefer_to_str(prefs->prefer),
+ ast_stream_codec_param_to_str(CODEC_NEGOTIATION_PARAM_OPERATION),
+ ast_stream_codec_operation_to_str(prefs->operation),
+ ast_stream_codec_param_to_str(CODEC_NEGOTIATION_PARAM_KEEP),
+ ast_stream_codec_keep_to_str(prefs->keep),
+ ast_stream_codec_param_to_str(CODEC_NEGOTIATION_PARAM_TRANSCODE),
+ ast_stream_codec_transcode_to_str(prefs->transcode)
+ );
+
+ return ast_str_buffer(*buf);
+}
+
+/*!
+ * \internal
+ * \brief Sets a codec prefs member based on a the preference name and string value
+ *
+ * \warning This macro will cause the calling function to return if a preference name
+ * is matched but the value isn't valid for this preference.
+ */
+#define set_pref_value(_name, _value, _prefs, _UC, _lc, _error_message) \
+({ \
+ int _res = 0; \
+ if (strcmp(_name, ast_stream_codec_negotiation_params_map[CODEC_NEGOTIATION_PARAM_ ## _UC]) == 0) { \
+ int i; \
+ for (i = CODEC_NEGOTIATION_ ## _UC ## _UNSPECIFIED + 1; i < CODEC_NEGOTIATION_ ## _UC ## _END; i++) { \
+ if (strcmp(value, ast_stream_codec_negotiation_ ## _lc ## _map[i]) == 0) { \
+ prefs->_lc = i; \
+ } \
+ } \
+ if ( prefs->_lc == CODEC_NEGOTIATION_ ## _UC ## _UNSPECIFIED) { \
+ _res = -1; \
+ if (_error_message) { \
+ ast_str_append(_error_message, 0, "Codec preference '%s' has invalid value '%s'", name, value); \
+ } \
+ } \
+ } \
+ if (_res < 0) { \
+ return _res; \
+ } \
+})
+
+int ast_stream_codec_prefs_parse(const char *pref_string, struct ast_stream_codec_negotiation_prefs *prefs,
+ struct ast_str **error_message)
+{
+ char *initial_value = ast_strdupa(pref_string);
+ char *current_value;
+ char *pref;
+ char *saveptr1;
+ char *saveptr2;
+ char *name;
+ char *value;
+
+ if (!prefs) {
+ return -1;
+ }
+
+ prefs->prefer = CODEC_NEGOTIATION_PREFER_UNSPECIFIED;
+ prefs->operation = CODEC_NEGOTIATION_OPERATION_UNSPECIFIED;
+ prefs->keep = CODEC_NEGOTIATION_KEEP_UNSPECIFIED;
+ prefs->transcode = CODEC_NEGOTIATION_TRANSCODE_UNSPECIFIED;
+
+ for (current_value = initial_value; (pref = strtok_r(current_value, ",", &saveptr1)) != NULL; ) {
+ name = strtok_r(pref, ": ", &saveptr2);
+ value = strtok_r(NULL, ": ", &saveptr2);
+
+ if (!name || !value) {
+ if (error_message) {
+ ast_str_append(error_message, 0, "Codec preference '%s' is invalid", pref);
+ }
+ return -1;
+ }
+
+ set_pref_value(name, value, prefs, OPERATION, operation, error_message);
+ set_pref_value(name, value, prefs, PREFER, prefer, error_message);
+ set_pref_value(name, value, prefs, KEEP, keep, error_message);
+ set_pref_value(name, value, prefs, TRANSCODE, transcode, error_message);
+
+ current_value = NULL;
+ }
+
+ return 0;
+}
+
+const char *ast_stream_state_map[] = {
+ [AST_STREAM_STATE_REMOVED] = "removed",
+ [AST_STREAM_STATE_SENDRECV] = "sendrecv",
+ [AST_STREAM_STATE_SENDONLY] = "sendonly",
+ [AST_STREAM_STATE_RECVONLY] = "recvonly",
+ [AST_STREAM_STATE_INACTIVE] = "inactive",
};
struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type)
@@ -165,6 +306,8 @@
}
ao2_cleanup(stream->formats);
+ ast_free(stream->display_str);
+
ast_free(stream);
}
@@ -196,6 +339,39 @@
return stream->formats;
}
+const char *ast_stream_to_str(const struct ast_stream *stream, struct ast_str **buf)
+{
+ if (!stream) {
+ if (buf) {
+ ast_str_append(buf, 0, "(null stream)");
+ return ast_str_buffer(*buf);
+ }
+ return "(null stream)";
+ }
+
+ if (!buf) {
+ if (!stream->display_str) {
+ ((struct ast_stream *)stream)->display_str = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ if (!stream->display_str) {
+ return "";
+ }
+ }
+ buf = &((struct ast_stream *)stream)->display_str;
+ ast_str_reset(*buf);
+ }
+ else if (!*buf) {
+ return "";
+ }
+
+ ast_str_append(buf, 0, "%s:%s:%s ",
+ S_OR(stream->name, "noname"),
+ ast_codec_media_type2str(stream->type),
+ ast_stream_state_map[stream->state]);
+ ast_format_cap_get_names(stream->formats, buf);
+
+ return ast_str_buffer(*buf);
+}
+
int ast_stream_get_format_count(const struct ast_stream *stream)
{
ast_assert(stream != NULL);
@@ -375,12 +551,120 @@
stream->rtp_codecs = rtp_codecs;
}
+struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
+ struct ast_stream *validation_stream, struct ast_stream_codec_negotiation_prefs *prefs,
+ struct ast_str **error_message)
+{
+ struct ast_format_cap *preferred_caps = NULL;
+ struct ast_format_cap *nonpreferred_caps = NULL;
+ RAII_VAR(struct ast_format_cap *, joint_caps, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
+ struct ast_stream *joint_stream;
+ enum ast_media_type media_type = pending_stream ? pending_stream->type : AST_MEDIA_TYPE_UNKNOWN;
+ int res = 0;
+ SCOPE_TRACE(1);
+
+ if (!pending_stream || !validation_stream || !prefs || !joint_caps
+ || media_type == AST_MEDIA_TYPE_UNKNOWN) {
+ if (error_message) {
+ ast_str_append(error_message, 0, "Invalid arguments");
+ }
+ return NULL;
+ }
+
+ if (prefs->prefer == CODEC_NEGOTIATION_PREFER_PENDING) {
+ preferred_caps = pending_stream->formats;
+ nonpreferred_caps = validation_stream->formats;
+ } else {
+ preferred_caps = validation_stream->formats;
+ nonpreferred_caps = pending_stream->formats;
+ }
+ ast_format_cap_set_framing(joint_caps, ast_format_cap_get_framing(pending_stream->formats));
+
+ switch(prefs->operation) {
+ case CODEC_NEGOTIATION_OPERATION_ONLY_PREFERRED:
+ res = ast_format_cap_append_from_cap(joint_caps, preferred_caps, media_type);
+ break;
+ case CODEC_NEGOTIATION_OPERATION_ONLY_NONPREFERRED:
+ res = ast_format_cap_append_from_cap(joint_caps, nonpreferred_caps, media_type);
+ break;
+ case CODEC_NEGOTIATION_OPERATION_INTERSECT:
+ res = ast_format_cap_get_compatible(preferred_caps, nonpreferred_caps, joint_caps);
+ break;
+ case CODEC_NEGOTIATION_OPERATION_UNION:
+ res = ast_format_cap_append_from_cap(joint_caps, preferred_caps, media_type);
+ if (res == 0) {
+ res = ast_format_cap_append_from_cap(joint_caps, nonpreferred_caps, media_type);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (res) {
+ if (error_message) {
+ struct ast_str *pf = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ struct ast_str *nf = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ struct ast_str *cc = ast_str_create(AST_STREAM_MAX_CODEC_PREFS_LENGTH);
+
+ ast_str_append(error_message, 0, "No common formats available for media type '%s' %s<>%s with prefs: %s\n",
+ ast_codec_media_type2str(pending_stream->type),
+ ast_format_cap_get_names(preferred_caps, &pf),
+ ast_format_cap_get_names(nonpreferred_caps, &nf),
+ ast_stream_codec_prefs_to_str(prefs, &cc));
+ ast_free(&pf);
+ ast_free(&nf);
+ ast_free(&cc);
+ }
+
+ return NULL;
+ }
+
+ if (!ast_format_cap_empty(joint_caps)) {
+ if (prefs->keep == CODEC_NEGOTIATION_KEEP_FIRST) {
+ struct ast_format *single = ast_format_cap_get_format(joint_caps, 0);
+ ast_format_cap_remove_by_type(joint_caps, AST_MEDIA_TYPE_UNKNOWN);
+ ast_format_cap_append(joint_caps, single, 0);
+ ao2_ref(single, -1);
+ }
+ }
+
+ joint_stream = ast_stream_clone(pending_stream, NULL);
+ if (!joint_stream) {
+ return NULL;
+ }
+
+ /* ref to joint_caps will be transferred to the stream */
+ ast_stream_set_formats(joint_stream, joint_caps);
+
+ if (TRACE_ATLEAST(1)) {
+ struct ast_str *pf = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ struct ast_str *nf = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ struct ast_str *jf = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ struct ast_str *cc = ast_str_create(AST_STREAM_MAX_CODEC_PREFS_LENGTH);
+
+ ast_trace(1, "Resolved '%s' stream %s<>%s to %s with prefs: %s\n",
+ ast_codec_media_type2str(pending_stream->type),
+ ast_format_cap_get_names(preferred_caps, &pf),
+ ast_format_cap_get_names(nonpreferred_caps, &nf),
+ ast_format_cap_get_names(joint_caps, &jf),
+ ast_stream_codec_prefs_to_str(prefs, &cc)
+ );
+ ast_free(pf);
+ ast_free(nf);
+ ast_free(jf);
+ ast_free(cc);
+ }
+
+ return joint_stream;
+}
+
static void stream_topology_destroy(void *data)
{
struct ast_stream_topology *topology = data;
AST_VECTOR_CALLBACK_VOID(&topology->streams, ast_stream_free);
AST_VECTOR_FREE(&topology->streams);
+ ast_free(topology->display_str);
}
#define TOPOLOGY_INITIAL_STREAM_COUNT 2
@@ -502,6 +786,22 @@
return AST_VECTOR_SIZE(&topology->streams);
}
+int ast_stream_topology_get_valid_count(const struct ast_stream_topology *topology)
+{
+ int i;
+ int count = 0;
+ ast_assert(topology != NULL);
+
+ for (i = 0; i < AST_VECTOR_SIZE(&topology->streams); i++) {
+ struct ast_stream *stream = AST_VECTOR_GET(&topology->streams, i);
+ if (stream->state != AST_STREAM_STATE_REMOVED) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
struct ast_stream *ast_stream_topology_get_stream(
const struct ast_stream_topology *topology, unsigned int stream_num)
{
@@ -610,8 +910,8 @@
return topology;
}
-struct ast_format_cap *ast_format_cap_from_stream_topology(
- struct ast_stream_topology *topology)
+struct ast_format_cap *ast_stream_topology_get_formats_by_type(
+ struct ast_stream_topology *topology, enum ast_media_type type)
{
struct ast_format_cap *caps;
int i;
@@ -627,17 +927,63 @@
struct ast_stream *stream;
stream = AST_VECTOR_GET(&topology->streams, i);
- if (!stream->formats
- || stream->state == AST_STREAM_STATE_REMOVED) {
+ if (!stream->formats || stream->state == AST_STREAM_STATE_REMOVED) {
continue;
}
-
- ast_format_cap_append_from_cap(caps, stream->formats, AST_MEDIA_TYPE_UNKNOWN);
+ if (type == AST_MEDIA_TYPE_UNKNOWN || type == stream->type) {
+ ast_format_cap_append_from_cap(caps, stream->formats, AST_MEDIA_TYPE_UNKNOWN);
+ }
}
return caps;
}
+struct ast_format_cap *ast_stream_topology_get_formats(
+ struct ast_stream_topology *topology)
+{
+ return ast_stream_topology_get_formats_by_type(topology, AST_MEDIA_TYPE_UNKNOWN);
+}
+
+const char *ast_stream_topology_to_str(const struct ast_stream_topology *topology,
+ struct ast_str **buf)
+{
+ int i;
+
+ if (!topology) {
+ if (buf) {
+ ast_str_append(buf, 0, "(null topology)");
+ return ast_str_buffer(*buf);
+ }
+ return "(null topology)";
+ }
+
+ if (!buf) {
+ if (!topology->display_str) {
+ ((struct ast_stream_topology *)topology)->display_str = ast_str_create(AST_FORMAT_CAP_NAMES_LEN);
+ if (!topology->display_str) {
+ return "";
+ }
+ }
+ buf = &((struct ast_stream_topology *)topology)->display_str;
+ ast_str_reset(*buf);
+ } else if (!*buf) {
+ return "";
+ }
+
+ ast_str_append(buf, 0, "%s", S_COR(topology->final, "final", ""));
+
+ for (i = 0; i < AST_VECTOR_SIZE(&topology->streams); i++) {
+ struct ast_stream *stream;
+
+ stream = AST_VECTOR_GET(&topology->streams, i);
+ ast_str_append(buf, 0, " <");
+ ast_stream_to_str(stream, buf);
+ ast_str_append(buf, 0, ">");
+ }
+
+ return ast_str_buffer(*buf);
+}
+
struct ast_stream *ast_stream_topology_get_first_stream_by_type(
const struct ast_stream_topology *topology,
enum ast_media_type type)
@@ -704,6 +1050,48 @@
}
}
+struct ast_stream_topology *ast_stream_topology_create_resolved(
+ struct ast_stream_topology *pending_topology, struct ast_stream_topology *configured_topology,
+ struct ast_stream_codec_negotiation_prefs *prefs, struct ast_str**error_message)
+{
+ RAII_VAR(struct ast_stream_topology *, joint_topology, ast_stream_topology_alloc(), ao2_cleanup);
+ int res = 0;
+ int i;
+ SCOPE_TRACE(1);
+
+ if (!pending_topology || !configured_topology || !joint_topology) {
+ return NULL;
+ }
+
+ for (i = 0; i < AST_VECTOR_SIZE(&pending_topology->streams); i++) {
+ struct ast_stream *pending_stream = AST_VECTOR_GET(&pending_topology->streams, i);
+ struct ast_stream *configured_stream =
+ ast_stream_topology_get_first_stream_by_type(configured_topology, pending_stream->type);
+ struct ast_stream *joint_stream;
+
+ if (!configured_stream) {
+ joint_stream = ast_stream_clone(pending_stream, NULL);
+ if (!joint_stream) {
+ return NULL;
+ }
+ ast_stream_set_state(joint_stream, AST_STREAM_STATE_REMOVED);
+ } else {
+ joint_stream = ast_stream_create_resolved(pending_stream, configured_stream, prefs, error_message);
+ if (ast_stream_get_format_count(joint_stream) == 0) {
+ ast_stream_set_state(joint_stream, AST_STREAM_STATE_REMOVED);
+ }
+ }
+
+ res = ast_stream_topology_append_stream(joint_topology, joint_stream);
+ if (res < 0) {
+ ast_stream_free(joint_stream);
+ return NULL;
+ }
+ }
+
+ return ao2_bump(joint_topology);
+}
+
int ast_stream_get_group(const struct ast_stream *stream)
{
ast_assert(stream != NULL);
diff --git a/tests/test_stream.c b/tests/test_stream.c
index 50f0ccb..1333e2e 100644
--- a/tests/test_stream.c
+++ b/tests/test_stream.c
@@ -2142,7 +2142,7 @@
return AST_TEST_FAIL;
}
- stream_caps = ast_format_cap_from_stream_topology(topology);
+ stream_caps = ast_stream_topology_get_formats(topology);
if (!stream_caps) {
ast_test_status_update(test, "Failed to create a format capabilities from a stream topology\n");
ast_stream_topology_free(topology);
--
To view, visit https://gerrit.asterisk.org/c/asterisk/+/14577
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: I2df77dedd0c72c52deb6e329effe057a8e06cd56
Gerrit-Change-Number: 14577
Gerrit-PatchSet: 1
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200626/48b5ebe2/attachment-0001.html>
More information about the asterisk-code-review
mailing list