From: George Joseph Date: Fri, 26 Jun 2020 16:14:58 +0000 (-0600) Subject: Streams: Add features for Advanced Codec Negotiation X-Git-Tag: 18.0.0-rc1~54 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8d1064eaafe899044adbd30a40cdaa15c315c463;p=thirdparty%2Fasterisk.git 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(). Additional changes: * A new function ast_format_cap_append_names() appends the results to the ast_str buffer instead of replacing buffer contents. Change-Id: I2df77dedd0c72c52deb6e329effe057a8e06cd56 --- diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index afaecdfd52..2fc98b4c32 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -521,7 +521,7 @@ static int compatible_formats_exist(struct ast_stream_topology *top, struct ast_ 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 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s 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/ACN_streams.txt b/doc/CHANGES-staging/ACN_streams.txt new file mode 100644 index 0000000000..6b54d71992 --- /dev/null +++ b/doc/CHANGES-staging/ACN_streams.txt @@ -0,0 +1,44 @@ +Subject: Core +Master-Only: True + +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(). + +Additional changes: + * A new function ast_format_cap_append_names() appends the results + to the ast_str buffer instead of replacing buffer contents. diff --git a/doc/UPGRADE-staging/ACN_streams.txt b/doc/UPGRADE-staging/ACN_streams.txt new file mode 100644 index 0000000000..400a6d2cdf --- /dev/null +++ b/doc/UPGRADE-staging/ACN_streams.txt @@ -0,0 +1,5 @@ +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 37021ebeb0..e9796bbf1c 100644 --- a/include/asterisk/format_cap.h +++ b/include/asterisk/format_cap.h @@ -310,6 +310,17 @@ int ast_format_cap_has_type(const struct ast_format_cap *cap, enum ast_media_typ */ const char *ast_format_cap_get_names(const struct ast_format_cap *cap, struct ast_str **buf); +/*! + * \brief Append the names of codecs of a set of formats to an ast_str buffer + * \since 18 + * + * \param cap The capabilities structure containing the formats + * \param buf A \c ast_str buffer to append the names of the formats to + * + * \return The contents of the buffer in \c buf + */ +const char *ast_format_cap_append_names(const struct ast_format_cap *cap, struct ast_str **buf); + #ifndef AST_FORMAT_CAP_NAMES_LEN /*! Buffer size for callers of ast_format_cap_get_names to allocate. */ #define AST_FORMAT_CAP_NAMES_LEN 384 diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h index bbeeacb06a..ca637be3fd 100644 --- a/include/asterisk/stream.h +++ b/include/asterisk/stream.h @@ -29,6 +29,23 @@ #include "asterisk/codec.h" #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 */ @@ -75,8 +92,273 @@ enum ast_stream_state { * \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 of 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 { + /*! Which codec list to prefer */ + enum ast_stream_codec_negotiation_prefs_prefer_values prefer; + /*! The operation to perform on the lists */ + enum ast_stream_codec_negotiation_prefs_operation_values operation; + /*! What to keep after the operation is performed */ + enum ast_stream_codec_negotiation_prefs_keep_values keep; + /*! To allow or prevent transcoding */ + 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 * @@ -166,6 +448,31 @@ void ast_stream_set_type(struct ast_stream *stream, enum ast_media_type type); */ 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. + * + * \retval "" (empty string) if either buf or *buf are NULL + * \retval "(null stream)" if *stream was NULL + * \retval otherwise + * + * \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 + * + * Return format: + * :: (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 * @@ -310,6 +617,33 @@ struct ast_rtp_codecs *ast_stream_get_rtp_codecs(const struct ast_stream *stream */ 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 and metadata 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 *configured_stream, struct ast_stream_codec_negotiation_prefs *prefs, + struct ast_str **error_message); + /*! * \brief Create a stream topology * @@ -386,6 +720,18 @@ int ast_stream_topology_append_stream(struct ast_stream_topology *topology, */ int ast_stream_topology_get_count(const struct ast_stream_topology *topology); +/*! + * \brief Get the number of active (non-REMOVED) streams in a topology + * + * \param topology The topology of streams + * + * \return the number of active streams + * + * \since 18 + */ +int ast_stream_topology_get_active_count(const struct ast_stream_topology *topology); + + /*! * \brief Get a specific stream from the topology * @@ -471,16 +817,60 @@ struct ast_stream_topology *ast_stream_topology_create_from_format_cap( * * \param topology The topology of streams * - * \retval non-NULL success + * \retval non-NULL success (the resulting format caps must be unreffed by the caller) * \retval NULL failure * * \note The stream topology is NOT altered by this function. * * \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. + * + * \retval "" (empty string) if either buf or *buf are NULL + * \retval "(null topology)" if *topology was NULL + * \retval otherwise + * + * \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. + * + * Return format: + * ? ... + * + * Sample return: + * "final " + * + */ +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 (the resulting format caps must be unreffed by the caller) + * \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 * @@ -534,4 +924,34 @@ int ast_stream_get_group(const struct ast_stream *stream); */ 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 7424b81536..40d77248f6 100644 --- a/main/channel.c +++ b/main/channel.c @@ -6227,7 +6227,7 @@ static struct ast_channel *request_channel(const char *type, struct ast_format_c 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 bff1c3e78b..16414e642e 100644 --- a/main/core_unreal.c +++ b/main/core_unreal.c @@ -1054,7 +1054,7 @@ struct ast_unreal_pvt *ast_unreal_alloc_stream_topology(size_t size, ao2_destruc 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 ca60557a91..a97fd6d112 100644 --- a/main/format_cap.c +++ b/main/format_cap.c @@ -699,11 +699,19 @@ int ast_format_cap_identical(const struct ast_format_cap *cap1, const struct ast return internal_format_cap_identical(cap2, cap1); } -const char *ast_format_cap_get_names(const struct ast_format_cap *cap, struct ast_str **buf) +static const char *__ast_format_cap_get_names(const struct ast_format_cap *cap, struct ast_str **buf, int append) { int i; - ast_str_set(buf, 0, "("); + if (!buf || !*buf) { + return ""; + } + + if (append) { + ast_str_append(buf, 0, "("); + } else { + ast_str_set(buf, 0, "("); + } if (!cap || !AST_VECTOR_SIZE(&cap->preference_order)) { ast_str_append(buf, 0, "nothing)"); @@ -725,6 +733,16 @@ const char *ast_format_cap_get_names(const struct ast_format_cap *cap, struct as return ast_str_buffer(*buf); } +const char *ast_format_cap_get_names(const struct ast_format_cap *cap, struct ast_str **buf) +{ + return __ast_format_cap_get_names(cap, buf, 0); +} + +const char *ast_format_cap_append_names(const struct ast_format_cap *cap, struct ast_str **buf) +{ + return __ast_format_cap_get_names(cap, buf, 1); +} + int ast_format_cap_empty(const struct ast_format_cap *cap) { int count = ast_format_cap_count(cap); diff --git a/main/stream.c b/main/stream.c index c5aa5f8294..a21177d10c 100644 --- a/main/stream.c +++ b/main/stream.c @@ -44,6 +44,40 @@ struct ast_stream_metadata_entry { 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 @@ -91,6 +125,107 @@ struct ast_stream_topology { * \brief A vector of all the streams in this topology */ AST_VECTOR(, struct ast_stream *) streams; + /*! Indicates that this topology should not have further operations applied to it. */ + int final; +}; + +const char *ast_stream_codec_prefs_to_str(const struct ast_stream_codec_negotiation_prefs *prefs, struct ast_str **buf) +{ + if (!prefs || !buf || !*buf) { + return ""; + } + + 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 +300,7 @@ void ast_stream_free(struct ast_stream *stream) } ao2_cleanup(stream->formats); + ast_free(stream); } @@ -196,6 +332,26 @@ const struct ast_format_cap *ast_stream_get_formats(const struct ast_stream *str return stream->formats; } +const char *ast_stream_to_str(const struct ast_stream *stream, struct ast_str **buf) +{ + if (!buf || !*buf) { + return ""; + } + + if (!stream) { + ast_str_append(buf, 0, "(null stream)"); + return ast_str_buffer(*buf); + } + + 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_append_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,6 +531,108 @@ void ast_stream_set_rtp_codecs(struct ast_stream *stream, struct ast_rtp_codecs 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; + struct ast_format_cap *joint_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + struct ast_stream *joint_stream; + enum ast_media_type media_type = pending_stream ? pending_stream->type : AST_MEDIA_TYPE_UNKNOWN; + int res = 0; + + 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"); + } + ao2_cleanup(joint_caps); + 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) { + ast_str_append(error_message, 0, "No common formats available for media type '%s' ", + ast_codec_media_type2str(pending_stream->type)); + ast_format_cap_append_names(preferred_caps, error_message); + ast_str_append(error_message, 0, "<>"); + ast_format_cap_append_names(nonpreferred_caps, error_message); + ast_str_append(error_message, 0, " with prefs: "); + ast_stream_codec_prefs_to_str(prefs, error_message); + } + + ao2_cleanup(joint_caps); + 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) { + ao2_cleanup(joint_caps); + 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 *buf = ast_str_create((AST_FORMAT_CAP_NAMES_LEN * 3) + AST_STREAM_MAX_CODEC_PREFS_LENGTH); + if (buf) { + ast_str_set(&buf, 0, "Resolved '%s' stream ", ast_codec_media_type2str(pending_stream->type)); + ast_format_cap_append_names(preferred_caps, &buf); + ast_str_append(&buf, 0, "<>"); + ast_format_cap_append_names(nonpreferred_caps, &buf); + ast_str_append(&buf, 0, " to "); + ast_format_cap_append_names(joint_caps, &buf); + ast_str_append(&buf, 0, " with prefs: "); + ast_stream_codec_prefs_to_str(prefs, &buf); + ast_trace(1, "%s\n", ast_str_buffer(buf)); + ast_free(buf); + } + } + + ao2_cleanup(joint_caps); + return joint_stream; +} + static void stream_topology_destroy(void *data) { struct ast_stream_topology *topology = data; @@ -502,6 +760,22 @@ int ast_stream_topology_get_count(const struct ast_stream_topology *topology) return AST_VECTOR_SIZE(&topology->streams); } +int ast_stream_topology_get_active_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 +884,8 @@ struct ast_stream_topology *ast_stream_topology_create_from_format_cap( 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 +901,51 @@ struct ast_format_cap *ast_format_cap_from_stream_topology( 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 (!buf ||!*buf) { + return ""; + } + + if (!topology) { + ast_str_append(buf, 0, "(null topology)"); + return ast_str_buffer(*buf); + } + + 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 +1012,50 @@ void ast_stream_topology_map(const struct ast_stream_topology *topology, } } +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) +{ + struct ast_stream_topology *joint_topology = ast_stream_topology_alloc(); + int res = 0; + int i; + + if (!pending_topology || !configured_topology || !joint_topology) { + ao2_cleanup(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) { + ao2_cleanup(joint_topology); + 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); + ao2_cleanup(joint_topology); + return NULL; + } + } + + return 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 50f0ccbfdf..1333e2ecb7 100644 --- a/tests/test_stream.c +++ b/tests/test_stream.c @@ -2142,7 +2142,7 @@ AST_TEST_DEFINE(format_cap_from_stream_topology) 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);