Commit 0db5198b3f for asterisk.org
commit 0db5198b3f4d4019962e986bfcce3943055c70fb
Author: Fran Vicente <25110862+f-vicente@users.noreply.github.com>
Date: Wed Sep 2 15:03:04 2026 +0200
app_confbridge: Add announcements option to disable conference announcements
Every conference unconditionally created an announcer channel (CBAnn) and a
taskprocessor thread to play announcements to the conference as a whole, even on
systems that never play any. That costs a channel, a thread and a bridge member
per conference.
A new bridge profile option, announcements, defaults to yes and preserves the
previous behavior. When set to no, no announcer channel is created and nothing
is played to the conference as a whole: the join and leave sounds, recorded name
intros, the participant count announced to everyone, sound_begin and
sound_leader_has_left are all suppressed, and users are never prompted to record
their name regardless of the announce_join_leave user profile option.
Prompts played only to the participant that caused them are unaffected, since
they are played on that participant's own channel. sound_kicked, sound_muted,
sound_unmuted, the join sound played back by hear_own_join_sound and the
participant count requested from the DTMF menu all still play.
The announcer channel exists for the lifetime of the conference, so the value
that takes effect is the one from the bridge profile of the channel that creates
the conference.
Resolves: #2131
UserNote: A new ConfBridge bridge profile option, announcements, can be set to
no to stop announcements from being played to the conference as a whole. No
announcer channel is then created, which saves a channel and a taskprocessor
thread per conference. Prompts played only to the participant that caused them
are not affected. It defaults to yes, which is the previous behavior.
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index 44378e52f5..e7b96fa32b 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -1127,6 +1127,12 @@ static int announce_user_count(struct confbridge_conference *conference, struct
const char *only_one = conf_get_sound(CONF_SOUND_ONLY_ONE, conference->b_profile.sounds);
const char *there_are = conf_get_sound(CONF_SOUND_THERE_ARE, conference->b_profile.sounds);
+ if (!user && !conference->playback_queue) {
+ /* Announcements to the entire conference are disabled. Announcements to a
+ * single caller are played on that caller's channel, so they still work. */
+ return 0;
+ }
+
if (conference->activeusers <= 1) {
/* Awww we are the only person in the conference bridge OR we only have waitmarked users */
return 0;
@@ -1920,20 +1926,29 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
/* Set the initial state to EMPTY */
conference->state = CONF_STATE_EMPTY;
- if (alloc_playback_chan(conference)) {
- ao2_unlink(conference_bridges, conference);
- ao2_ref(conference, -1);
- ao2_unlock(conference_bridges);
- ast_log(LOG_ERROR, "Could not allocate announcer channel for conference '%s'\n", conference_name);
- return NULL;
- }
+ /* The announcer channel lives for as long as the conference does, so whether it
+ * gets created is decided once here, using the profile of the channel that
+ * creates the conference. Leaving playback_chan and playback_queue NULL is what
+ * tells the rest of this module that announcements are not available. */
+ if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_ANNOUNCEMENTS)) {
+ if (alloc_playback_chan(conference)) {
+ ao2_unlink(conference_bridges, conference);
+ ao2_ref(conference, -1);
+ ao2_unlock(conference_bridges);
+ ast_log(LOG_ERROR, "Could not allocate announcer channel for conference '%s'\n", conference_name);
+ return NULL;
+ }
- if (push_announcer(conference)) {
- ao2_unlink(conference_bridges, conference);
- ao2_ref(conference, -1);
- ao2_unlock(conference_bridges);
- ast_log(LOG_ERROR, "Could not add announcer channel for conference '%s' bridge\n", conference_name);
- return NULL;
+ if (push_announcer(conference)) {
+ ao2_unlink(conference_bridges, conference);
+ ao2_ref(conference, -1);
+ ao2_unlock(conference_bridges);
+ ast_log(LOG_ERROR, "Could not add announcer channel for conference '%s' bridge\n", conference_name);
+ return NULL;
+ }
+ } else {
+ ast_debug(1, "Announcements are disabled for conference '%s', not creating an announcer channel\n",
+ conference_name);
}
if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_RECORD_CONFERENCE)) {
@@ -2134,6 +2149,11 @@ static int play_sound_helper(struct confbridge_conference *conference, const cha
{
struct playback_task_data ptd;
+ /* Announcements are disabled for this conference, there is nothing to play them on */
+ if (!conference->playback_queue) {
+ return 0;
+ }
+
/* Do not waste resources trying to play files that do not exist */
if (ast_strlen_zero(filename)) {
if (say_number < 0) {
@@ -2386,6 +2406,11 @@ static int async_play_sound_helper(struct confbridge_conference *conference,
{
struct async_playback_task_data *aptd;
+ /* Announcements are disabled for this conference, there is nothing to play them on */
+ if (!conference->playback_queue) {
+ return 0;
+ }
+
/* Do not waste resources trying to play files that do not exist */
if (ast_strlen_zero(filename)) {
if (say_number < 0) {
@@ -2636,6 +2661,14 @@ static int async_delete_name_rec(struct confbridge_conference *conference,
return 0;
}
+ /* Without a playback queue there is no playback to wait for, so just remove it now */
+ if (!conference->playback_queue) {
+ ast_filedelete(filename, NULL);
+ ast_debug(1, "Conference '%s' removed user name file '%s'\n",
+ conference->name, filename);
+ return 0;
+ }
+
atd = async_delete_name_rec_task_data_alloc(conference, filename);
if (!atd) {
return -1;
@@ -2769,8 +2802,10 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
}
}
- /* See if we need them to record a intro name */
- if (!quiet &&
+ /* See if we need them to record a intro name. There is no point in asking for one
+ * when announcements are disabled, since it could never be played. The conference
+ * does not necessarily exist yet, so this caller's own bridge profile is used. */
+ if (!quiet && ast_test_flag(&user.b_profile, BRIDGE_OPT_ANNOUNCEMENTS) &&
(ast_test_flag(&user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE) ||
(ast_test_flag(&user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE_REVIEW)))) {
if (conf_rec_name(&user, args.conf_name)) {
diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c
index 1414e0e985..2e22b6be5a 100644
--- a/apps/confbridge/conf_config_parser.c
+++ b/apps/confbridge/conf_config_parser.c
@@ -749,6 +749,38 @@
This feature must also be enabled in user profiles.</para>
</description>
</configOption>
+ <configOption name="announcements" default="yes">
+ <since>
+ <version>20.22.0</version>
+ <version>22.12.0</version>
+ <version>23.6.0</version>
+ </since>
+ <synopsis>Enables announcements played to the conference as a whole</synopsis>
+ <description>
+ <para>When set to <literal>no</literal>, no announcer channel
+ (<literal>CBAnn</literal>) is created for the conference and no
+ announcement is ever played to the conference as a whole. This
+ suppresses the join and leave sounds, recorded name intros, the
+ participant count announced to everyone, and the
+ <literal>sound_begin</literal> and
+ <literal>sound_leader_has_left</literal> prompts. Users are also
+ never prompted to record their name, regardless of the
+ <literal>announce_join_leave</literal> user profile option.</para>
+ <para>Prompts that are played only to the participant that caused
+ them are not affected. <literal>sound_kicked</literal>,
+ <literal>sound_muted</literal>, <literal>sound_unmuted</literal>,
+ the join sound played back by
+ <literal>hear_own_join_sound</literal> and the participant count
+ requested from the DTMF menu are all still played.</para>
+ <para>Disabling announcements saves a channel and a taskprocessor
+ thread per conference and removes a member from the mix, which is
+ worthwhile for systems running many conferences that never play
+ announcements.</para>
+ <para>The value that takes effect is the one from the bridge
+ profile of the channel that creates the conference, since the
+ announcer channel exists for the lifetime of the conference.</para>
+ </description>
+ </configOption>
<configOption name="template">
<since>
<version>11.0.0</version>
@@ -2025,6 +2057,10 @@ static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e,
b_profile.flags & BRIDGE_OPT_ENABLE_EVENTS ?
"yes" : "no");
+ ast_cli(a->fd,"Announcements: %s\n",
+ b_profile.flags & BRIDGE_OPT_ANNOUNCEMENTS ?
+ "yes" : "no");
+
ast_cli(a->fd,"sound_only_person: %s\n", conf_get_sound(CONF_SOUND_ONLY_PERSON, b_profile.sounds));
ast_cli(a->fd,"sound_only_one: %s\n", conf_get_sound(CONF_SOUND_ONLY_ONE, b_profile.sounds));
ast_cli(a->fd,"sound_has_joined: %s\n", conf_get_sound(CONF_SOUND_HAS_JOINED, b_profile.sounds));
@@ -2672,6 +2708,7 @@ int conf_load_config(void)
aco_option_register_custom(&cfg_info, "remb_behavior", ACO_EXACT, bridge_types, "average", remb_behavior_handler, 0);
aco_option_register(&cfg_info, "remb_estimated_bitrate", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, remb_estimated_bitrate));
aco_option_register(&cfg_info, "enable_events", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), BRIDGE_OPT_ENABLE_EVENTS);
+ aco_option_register(&cfg_info, "announcements", ACO_EXACT, bridge_types, "yes", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), BRIDGE_OPT_ANNOUNCEMENTS);
/* This option should only be used with the CONFBRIDGE dialplan function */
aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0);
diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h
index 38e984a43e..e79320e504 100644
--- a/apps/confbridge/include/confbridge.h
+++ b/apps/confbridge/include/confbridge.h
@@ -91,6 +91,7 @@ enum bridge_profile_flags {
BRIDGE_OPT_REMB_BEHAVIOR_LOWEST_ALL = (1 << 13), /*!< The lowest estimated maximum bitrate from all receivers is sent to each sender */
BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL = (1 << 14), /*!< The highest estimated maximum bitrate from all receivers is sent to each sender */
BRIDGE_OPT_REMB_BEHAVIOR_FORCE = (1 << 15), /*!< Force the REMB estimated bitrate to that specified in remb_estimated_bitrate */
+ BRIDGE_OPT_ANNOUNCEMENTS = (1 << 16), /*!< Set if the conference should have an announcer channel to play announcements to the conference as a whole */
};
enum conf_menu_action_id {
diff --git a/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample
index f582323f10..2d7baa1055 100644
--- a/configs/samples/confbridge.conf.sample
+++ b/configs/samples/confbridge.conf.sample
@@ -288,6 +288,26 @@ type=bridge
; "text/x-ast-confbridge-event".
; This feature must also be enabled in user profiles.
+;announcements=yes ; When set to "no", no announcer channel (CBAnn) is created for
+ ; this conference and nothing is ever played to the conference as
+ ; a whole. The join and leave sounds, recorded name intros, the
+ ; participant count announced to everyone, sound_begin and
+ ; sound_leader_has_left are all suppressed, and users are never
+ ; prompted to record their name regardless of the
+ ; announce_join_leave user profile option.
+ ; Prompts played only to the participant that caused them are
+ ; not affected: sound_kicked, sound_muted, sound_unmuted, the
+ ; join sound played back by hear_own_join_sound and the
+ ; participant count requested from the DTMF menu all still play.
+ ; Disabling announcements saves a channel and a taskprocessor
+ ; thread per conference and removes a member from the mix, which
+ ; is worthwhile for systems running many conferences that never
+ ; play announcements.
+ ; The value that takes effect is the one from the bridge profile
+ ; of the channel that creates the conference, since the announcer
+ ; channel exists for the lifetime of the conference.
+ ; This defaults to "yes".
+
; All sounds in the conference are customizable using the bridge profile options below.
; Simply state the option followed by the filename or full path of the filename after
; the option. Example: sound_had_joined=conf-hasjoin This will play the conf-hasjoin