Commit c2ee154ed2 for asterisk.org

commit c2ee154ed2897a8461fd2f39733a4b4f4c627bd1
Author: George Joseph <gjoseph@sangoma.com>
Date:   Fri Mar 6 11:52:53 2026 -0700

    rtp: Set RTPAUDIOQOS variables when ast_softhangup is called.

    If a channel in Stasis/ARI is hung up by the channel driver, the RTPAUDIOQOS
    variables are set before the channel leaves Stasis and are therefore
    available to the ARI app via ChannelVarset events.  If the channel is hung up
    by ARI however, the channel leaves Stasis before the RTPAUDIOQOS variables
    are set so the app may not get the ChannelVarset events.

    We now set the RTPAUDIOQOS variables when ast_softhangup() is called as well
    as when the channel driver hangs up a channel.  Since ARI hangups call
    ast_softhangup(), the variables will be set before the channel leaves Stasis
    and the app should get the ChannelVarset events.
    ast_rtp_instance_set_stats_vars(), which actually sets the variables, now
    checks to see if the variables are already set before attempting to set them.
    This prevents double messages from being generated.

    Resolves: #1816

diff --git a/main/channel.c b/main/channel.c
index 76e7c3c787..ff2e38a343 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -75,6 +75,7 @@
 #include "asterisk/max_forwards.h"
 #include "asterisk/stream.h"
 #include "asterisk/message.h"
+#include "asterisk/rtp_engine.h"

 #include "channelstorage.h"

@@ -2463,8 +2464,21 @@ int ast_softhangup(struct ast_channel *chan, int cause)
 	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
 	int res;
 	int tech_cause = 0;
+	struct ast_rtp_glue *glue;
+	RAII_VAR(struct ast_rtp_instance *, rtp, NULL, ao2_cleanup);
+	const struct ast_channel_tech *tech;

 	ast_channel_lock(chan);
+
+	tech = ast_channel_tech(chan);
+	glue = ast_rtp_instance_get_glue(tech->type);
+	if (glue) {
+		glue->get_rtp_info(chan, &rtp);
+		if (rtp) {
+			ast_rtp_instance_set_stats_vars(chan, rtp);
+		}
+	}
+
 	res = ast_softhangup_nolock(chan, cause);
 	blob = ast_json_pack("{s: i, s: b}",
 			     "cause", cause,
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index 90198e089d..b0c1bc2c42 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -2704,14 +2704,36 @@ char *ast_rtp_instance_get_quality(struct ast_rtp_instance *instance, enum ast_r
 	return buf;
 }

+#define SET_STATS_VAR_HELPER(var_prefix, field) \
+({ \
+	value = ast_rtp_instance_get_quality(instance, field, quality_buf, sizeof(quality_buf)); \
+	if (value) { \
+		if (!chanvars || ast_strlen_zero(ast_var_find(chanvars, var_prefix))) { \
+			pbx_builtin_setvar_helper(chan, var_prefix, value); \
+			chanchanges++; \
+		} \
+		if (bridge) { \
+			if (!bridgevars || ast_strlen_zero(ast_var_find(bridgevars, var_prefix "BRIDGED"))) { \
+				pbx_builtin_setvar_helper(bridge, var_prefix "BRIDGED", value); \
+				bridgechanges++; \
+			} \
+		} \
+	} \
+})
+
 void ast_rtp_instance_set_stats_vars(struct ast_channel *chan, struct ast_rtp_instance *instance)
 {
 	char quality_buf[AST_MAX_USER_FIELD];
-	char *quality;
+	char *value;
 	struct ast_channel *bridge;
+	struct varshead *chanvars = ast_channel_varshead(chan);
+	struct varshead *bridgevars = NULL;
+	int chanchanges = 0;
+	int bridgechanges = 0;

 	bridge = ast_channel_bridge_peer(chan);
 	if (bridge) {
+		bridgevars = ast_channel_varshead(bridge);
 		ast_channel_lock_both(chan, bridge);
 		ast_channel_stage_snapshot(bridge);
 	} else {
@@ -2719,55 +2741,28 @@ void ast_rtp_instance_set_stats_vars(struct ast_channel *chan, struct ast_rtp_in
 	}
 	ast_channel_stage_snapshot(chan);

-	quality = ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY,
-		quality_buf, sizeof(quality_buf));
-	if (quality) {
-		pbx_builtin_setvar_helper(chan, "RTPAUDIOQOS", quality);
-		if (bridge) {
-			pbx_builtin_setvar_helper(bridge, "RTPAUDIOQOSBRIDGED", quality);
-		}
-	}
+	SET_STATS_VAR_HELPER("RTPAUDIOQOS", AST_RTP_INSTANCE_STAT_FIELD_QUALITY);

-	quality = ast_rtp_instance_get_quality(instance,
-		AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER, quality_buf, sizeof(quality_buf));
-	if (quality) {
-		pbx_builtin_setvar_helper(chan, "RTPAUDIOQOSJITTER", quality);
-		if (bridge) {
-			pbx_builtin_setvar_helper(bridge, "RTPAUDIOQOSJITTERBRIDGED", quality);
-		}
-	}
+	SET_STATS_VAR_HELPER("RTPAUDIOQOSJITTER", AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER);

-	quality = ast_rtp_instance_get_quality(instance,
-		AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS, quality_buf, sizeof(quality_buf));
-	if (quality) {
-		pbx_builtin_setvar_helper(chan, "RTPAUDIOQOSLOSS", quality);
-		if (bridge) {
-			pbx_builtin_setvar_helper(bridge, "RTPAUDIOQOSLOSSBRIDGED", quality);
-		}
-	}
+	SET_STATS_VAR_HELPER("RTPAUDIOQOSLOSS", AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS);

-	quality = ast_rtp_instance_get_quality(instance,
-		AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT, quality_buf, sizeof(quality_buf));
-	if (quality) {
-		pbx_builtin_setvar_helper(chan, "RTPAUDIOQOSRTT", quality);
-		if (bridge) {
-			pbx_builtin_setvar_helper(bridge, "RTPAUDIOQOSRTTBRIDGED", quality);
-		}
-	}
+	SET_STATS_VAR_HELPER("RTPAUDIOQOSRTT", AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT);

-	quality = ast_rtp_instance_get_quality(instance,
-		AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES, quality_buf, sizeof(quality_buf));
-	if (quality) {
-		pbx_builtin_setvar_helper(chan, "RTPAUDIOQOSMES", quality);
-		if (bridge) {
-			pbx_builtin_setvar_helper(bridge, "RTPAUDIOQOSMESBRIDGED", quality);
-		}
-	}
+	SET_STATS_VAR_HELPER("RTPAUDIOQOSMES", AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES);

-	ast_channel_stage_snapshot_done(chan);
+	if (chanchanges) {
+		ast_channel_stage_snapshot_done(chan);
+	} else {
+		ast_clear_flag(ast_channel_flags(chan), AST_FLAG_SNAPSHOT_STAGE);
+	}
 	ast_channel_unlock(chan);
 	if (bridge) {
-		ast_channel_stage_snapshot_done(bridge);
+		if (bridgechanges) {
+			ast_channel_stage_snapshot_done(bridge);
+		} else {
+			ast_clear_flag(ast_channel_flags(bridge), AST_FLAG_SNAPSHOT_STAGE);
+		}
 		ast_channel_unlock(bridge);
 		ast_channel_unref(bridge);
 	}