Commit e8a975195b for asterisk.org

commit e8a975195b35a208a1e0b874df15658e09959f7d
Author: Daniel McLean <daniel.mclean@biamp.com>
Date:   Wed Oct 28 11:03:10 2026 +1000

    chan_rtp: Add a payload type option to UnicastRTP and externalMedia.

    A UnicastRTP channel has no SDP, so it sends with the payload type
    Asterisk assigns to its format and on receive only knows the static
    payload types. Media sent to it with a dynamic payload type is
    dropped, so it cannot receive formats that have no static payload
    type, such as slin16 and slin48.

    The new `p(<payload type>)` option takes a payload type in the dynamic
    range (96-127), which the channel then both sends with and accepts.
    ARI externalMedia passes it on from a new payload_type parameter,
    valid only for the rtp encapsulation. Without it nothing changes.

    Assisted-by: Claude Opus 5.5

    Resolves: #2268

    UserNote: UnicastRTP channels have a new `p(<payload type>)` option to
    send and receive media with a payload type in the dynamic range
    (96-127).

    DeveloperNote: A "payload_type" parameter has been added to the
    channels/externalMedia ARI endpoint. For the rtp encapsulation it sets
    the payload type, in the dynamic range 96-127, that the channel sends
    and receives media with.

diff --git a/channels/chan_rtp.c b/channels/chan_rtp.c
index 01b57d7aad..9373f34efa 100644
--- a/channels/chan_rtp.c
+++ b/channels/chan_rtp.c
@@ -41,6 +41,7 @@
 #include "asterisk/app.h"
 #include "asterisk/rtp_engine.h"
 #include "asterisk/causes.h"
+#include "asterisk/conversions.h"
 #include "asterisk/format_cache.h"
 #include "asterisk/multicast_rtp.h"
 #include "asterisk/dns_core.h"
@@ -253,11 +254,13 @@ enum {
 	OPT_RTP_CODEC =  (1 << 0),
 	OPT_RTP_ENGINE = (1 << 1),
 	OPT_RTP_GLUE = (1 << 2),
+	OPT_RTP_PAYLOAD = (1 << 3),
 };

 enum {
 	OPT_ARG_RTP_CODEC,
 	OPT_ARG_RTP_ENGINE,
+	OPT_ARG_RTP_PAYLOAD,
 	/* note: this entry _MUST_ be the last one in the enum */
 	OPT_ARG_ARRAY_SIZE
 };
@@ -269,6 +272,8 @@ AST_APP_OPTIONS(unicast_rtp_options, BEGIN_OPTIONS
 	AST_APP_OPTION_ARG('e', OPT_RTP_ENGINE, OPT_ARG_RTP_ENGINE),
 	/*! Provide RTP glue for the channel */
 	AST_APP_OPTION('g', OPT_RTP_GLUE),
+	/*! Set the dynamic payload type (96-127) to send and receive with */
+	AST_APP_OPTION_ARG('p', OPT_RTP_PAYLOAD, OPT_ARG_RTP_PAYLOAD),
 END_OPTIONS );

 static const struct ast_datastore_info chan_rtp_datastore_info = {
@@ -285,6 +290,7 @@ static struct ast_channel *unicast_rtp_request(const char *type, struct ast_form
 	struct ast_channel *chan;
 	struct ast_format_cap *caps = NULL;
 	struct ast_format *fmt = NULL;
+	int payload = -1;
 	const char *engine_name;
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(destination);
@@ -348,6 +354,15 @@ static struct ast_channel *unicast_rtp_request(const char *type, struct ast_form
 		}
 	}

+	if (ast_test_flag(&opts, OPT_RTP_PAYLOAD)
+		&& (ast_str_to_int(opt_args[OPT_ARG_RTP_PAYLOAD], &payload)
+			|| payload < AST_RTP_PT_FIRST_DYNAMIC || payload >= AST_RTP_MAX_PT)) {
+		ast_log(LOG_ERROR, "Payload type '%s' for '%s' is not in the dynamic range %d-%d\n",
+			opt_args[OPT_ARG_RTP_PAYLOAD], args.destination,
+			AST_RTP_PT_FIRST_DYNAMIC, AST_RTP_MAX_PT - 1);
+		goto failure;
+	}
+
 	caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
 	if (!caps) {
 		goto failure;
@@ -370,6 +385,16 @@ static struct ast_channel *unicast_rtp_request(const char *type, struct ast_form
 		goto failure;
 	}

+	if (payload >= 0) {
+		struct ast_rtp_codecs *codecs = ast_rtp_instance_get_codecs(instance);
+
+		if (ast_rtp_codecs_payload_replace_format(codecs, payload, fmt)
+			|| ast_rtp_codecs_payload_set_rx(codecs, payload, fmt) < 0) {
+			ast_rtp_instance_destroy(instance);
+			goto failure;
+		}
+	}
+
 	chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
 		requestor, 0, "UnicastRTP/%s-%p", args.destination, instance);
 	if (!chan) {
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index c9d4a417fc..b11e4325c6 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -2419,10 +2419,16 @@ static int external_media_rtp_udp(struct ast_ari_channels_external_media_args *a
 	char *endpoint;
 	struct ast_channel *chan;
 	struct varshead *vars;
+	char payload_type[16] = "";

-	if (ast_asprintf(&endpoint, "UnicastRTP/%s/c(%s)",
+	if (args->payload_type) {
+		snprintf(payload_type, sizeof(payload_type), "p(%d)", args->payload_type);
+	}
+
+	if (ast_asprintf(&endpoint, "UnicastRTP/%s/c(%s)%s",
 			args->external_host,
-			args->format) == -1) {
+			args->format,
+			payload_type) == -1) {
 		return 1;
 	}

@@ -2681,6 +2687,17 @@ void ast_ari_channels_external_media(struct ast_variable *headers,
 		}
 	}

+	if (args->payload_type) {
+		if (strcasecmp(args->encapsulation, "rtp")) {
+			ast_ari_response_error(response, 400, "Bad Request", "payload_type is only valid for the rtp encapsulation");
+			return;
+		}
+		if (args->payload_type < AST_RTP_PT_FIRST_DYNAMIC || args->payload_type >= AST_RTP_MAX_PT) {
+			ast_ari_response_error(response, 400, "Bad Request", "payload_type must be in the dynamic range 96-127");
+			return;
+		}
+	}
+
 	if (strcasecmp(args->encapsulation, "rtp") == 0 && strcasecmp(args->transport, "udp") == 0) {
 		if (external_media_rtp_udp(args, variables, report_event_variables, response)) {
 			ast_ari_response_error(
diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h
index 2be83a0e7e..15a8e9f34f 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -921,6 +921,8 @@ struct ast_ari_channels_external_media_args {
 	const char *data;
 	/*! Transport-specific data. For websocket this is appended to the dialstring. */
 	const char *transport_data;
+	/*! RTP payload type to send and receive media with, in the dynamic range 96-127. Only valid for the rtp encapsulation. */
+	int payload_type;
 };
 /*!
  * \brief Body parsing function for /channels/externalMedia.
diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c
index d07631df46..ec5eccfb22 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -3209,6 +3209,10 @@ int ast_ari_channels_external_media_parse_body(
 	if (field) {
 		args->transport_data = ast_json_string_get(field);
 	}
+	field = ast_json_object_get(body, "payload_type");
+	if (field) {
+		args->payload_type = ast_json_integer_get(field);
+	}
 	return 0;
 }

@@ -3264,6 +3268,9 @@ static void ast_ari_channels_external_media_cb(
 		if (strcmp(i->name, "transport_data") == 0) {
 			args.transport_data = (i->value);
 		} else
+		if (strcmp(i->name, "payload_type") == 0) {
+			args.payload_type = atoi(i->value);
+		} else
 		{}
 	}
 	args.variables = body;
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index f1b711d36a..82a9830a51 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -2137,6 +2137,19 @@
 							"required": false,
 							"allowMultiple": false,
 							"dataType": "string"
+						},
+						{
+							"name": "payload_type",
+							"description": "RTP payload type to send and receive media with, in the dynamic range 96-127. Only valid for the rtp encapsulation.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "int",
+							"allowableValues": {
+								"valueType": "RANGE",
+								"min": 96,
+								"max": 127
+							}
 						}
 					],
 					"errorResponses": [