From 75bd8b828ce99eb05df8fcc2a96922bb3a147b47 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Mon, 19 Oct 2026 18:30:21 +0200 Subject: [PATCH 1/5] core: add SMBF_LAST media bug flag to keep a bug behind all others --- src/include/switch_types.h | 5 ++++- src/switch_core_media_bug.c | 30 +++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/include/switch_types.h b/src/include/switch_types.h index cd47eb0a0eb..fffbe71b9f2 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -1888,6 +1888,8 @@ SMBF_THREAD_LOCK - Only let the same thread who created the bug remove it. SMBF_PRUNE - SMBF_NO_PAUSE - SMBF_STEREO_SWAP - Record in stereo: Write Stream - left channel, Read Stream - right channel +SMBF_FIRST - Put the bug at the front of the list, before all other bugs +SMBF_LAST - Keep the bug at the end of the list: bugs added later are linked in front of it */ typedef enum { @@ -1919,7 +1921,8 @@ typedef enum { SMBF_READ_VIDEO_PATCH = (1 << 24), SMBF_READ_TEXT_STREAM = (1 << 25), SMBF_FIRST = (1 << 26), - SMBF_PAUSE = (1 << 27) + SMBF_PAUSE = (1 << 27), + SMBF_LAST = (1 << 28) } switch_media_bug_flag_enum_t; typedef uint32_t switch_media_bug_flag_t; diff --git a/src/switch_core_media_bug.c b/src/switch_core_media_bug.c index 7aabceb7b78..901924dcfe9 100644 --- a/src/switch_core_media_bug.c +++ b/src/switch_core_media_bug.c @@ -790,6 +790,29 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_push_spy_frame(switch_medi return SWITCH_STATUS_FALSE; } +/* + * Link a bug that is neither first nor the only one into the list: regular bugs go in front of the first SMBF_LAST bug (or at the end if + * there is none), SMBF_LAST bugs go to the end. That keeps SMBF_LAST bugs behind every other bug, so they see the frames as modified by all of them. + * The caller holds session->bug_rwlock for writing. + */ +static void switch_core_media_bug_insert(switch_core_session_t *session, switch_media_bug_t *bug) +{ + switch_media_bug_t *bp = session->bugs, *prev = NULL; + + while (bp && (switch_test_flag(bug, SMBF_LAST) || !switch_test_flag(bp, SMBF_LAST))) { + prev = bp; + bp = bp->next; + } + + bug->next = bp; + + if (prev) { + prev->next = bug; + } else { + session->bugs = bug; + } +} + #define MAX_BUG_BUFFER 1024 * 512 SWITCH_DECLARE(switch_status_t) switch_core_media_bug_add(switch_core_session_t *session, const char *function, @@ -866,6 +889,11 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_add(switch_core_session_t } #endif + if ((flags & SMBF_FIRST) && (flags & SMBF_LAST)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "SMBF_FIRST and SMBF_LAST are mutually exclusive.\n"); + return SWITCH_STATUS_GENERR; + } + if (!(bug = switch_core_session_alloc(session, sizeof(*bug)))) { return SWITCH_STATUS_MEMERR; } @@ -993,7 +1021,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_add(switch_core_session_t } if (!added && !bp->next) { - bp->next = bug; + switch_core_media_bug_insert(session, bug); break; } } From 30bf917a7f2b1abb1627887baa289c9dc2dbbff3 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Mon, 19 Oct 2026 18:36:44 +0200 Subject: [PATCH 2/5] core: link non-first media bugs once after the tap-only scan and document SMBF_LAST limits --- src/include/switch_types.h | 2 +- src/switch_core_media_bug.c | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/include/switch_types.h b/src/include/switch_types.h index fffbe71b9f2..6ae3ad43b75 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -1889,7 +1889,7 @@ SMBF_PRUNE - SMBF_NO_PAUSE - SMBF_STEREO_SWAP - Record in stereo: Write Stream - left channel, Read Stream - right channel SMBF_FIRST - Put the bug at the front of the list, before all other bugs -SMBF_LAST - Keep the bug at the end of the list: bugs added later are linked in front of it +SMBF_LAST - Keep the bug at the end of the list: bugs added later are linked in front of it (only honored at add time; exclusive with SMBF_FIRST) */ typedef enum { diff --git a/src/switch_core_media_bug.c b/src/switch_core_media_bug.c index 901924dcfe9..1e0a5138dfb 100644 --- a/src/switch_core_media_bug.c +++ b/src/switch_core_media_bug.c @@ -1019,11 +1019,10 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_add(switch_core_session_t if (bp->ready && !switch_test_flag(bp, SMBF_TAP_NATIVE_READ) && !switch_test_flag(bp, SMBF_TAP_NATIVE_WRITE)) { tap_only = 0; } + } - if (!added && !bp->next) { - switch_core_media_bug_insert(session, bug); - break; - } + if (!added) { + switch_core_media_bug_insert(session, bug); } switch_thread_rwlock_unlock(session->bug_rwlock); From 42af5c220b16e877402640a99f90b95821cfde9e Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Mon, 19 Oct 2026 18:36:44 +0200 Subject: [PATCH 3/5] tests: pin media bug ordering with SMBF_FIRST and SMBF_LAST --- tests/unit/switch_ivr_async.c | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/unit/switch_ivr_async.c b/tests/unit/switch_ivr_async.c index 7b6a5e86c82..273dfbb6101 100644 --- a/tests/unit/switch_ivr_async.c +++ b/tests/unit/switch_ivr_async.c @@ -62,6 +62,18 @@ static switch_status_t partial_play_and_collect_input_callback(switch_core_sessi return status; } +static switch_bool_t noop_bug_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type) +{ + return SWITCH_TRUE; +} + +static switch_status_t add_ordering_bug(switch_core_session_t *session, const char *function, switch_media_bug_flag_t flags) +{ + switch_media_bug_t *bug = NULL; + + return switch_core_media_bug_add(session, function, NULL, noop_bug_callback, NULL, 0, SMBF_READ_PING | flags, &bug); +} + FST_CORE_BEGIN("./conf_async") { FST_SUITE_BEGIN(switch_ivr_play_async) @@ -195,6 +207,39 @@ FST_CORE_BEGIN("./conf_async") unlink(record_filename); } FST_SESSION_END() + + FST_SESSION_BEGIN(media_bug_last_ordering) + { + const char *expected[] = { "first", "regular-1", "regular-2", "last-1", "last-2" }; + switch_stream_handle_t stream = { 0 }; + const char *pos; + int i; + + fst_check(add_ordering_bug(fst_session, "last-1", SMBF_LAST) == SWITCH_STATUS_SUCCESS); + /* the only bug is SMBF_LAST: a regular bug must become the head */ + fst_check(add_ordering_bug(fst_session, "regular-1", 0) == SWITCH_STATUS_SUCCESS); + fst_check(add_ordering_bug(fst_session, "first", SMBF_FIRST) == SWITCH_STATUS_SUCCESS); + fst_check(add_ordering_bug(fst_session, "last-2", SMBF_LAST) == SWITCH_STATUS_SUCCESS); + fst_check(add_ordering_bug(fst_session, "regular-2", 0) == SWITCH_STATUS_SUCCESS); + fst_check(add_ordering_bug(fst_session, "both", SMBF_FIRST | SMBF_LAST) == SWITCH_STATUS_GENERR); + + SWITCH_STANDARD_STREAM(stream); + switch_core_media_bug_enumerate(fst_session, &stream); + pos = (const char *)stream.data; + + for (i = 0; i < (int)(sizeof(expected) / sizeof(expected[0])); i++) { + const char *tag = switch_core_session_sprintf(fst_session, "%s", expected[i]); + + pos = pos ? strstr(pos, tag) : NULL; + fst_xcheck(pos != NULL, "Expect media bugs in order first, regular-1, regular-2, last-1, last-2"); + } + + fst_check(strstr((const char *)stream.data, "both") == NULL); + + switch_safe_free(stream.data); + switch_core_media_bug_remove_all(fst_session); + } + FST_SESSION_END() } FST_SUITE_END() } From 5ba06a6fe12ff9902208df7b48444ecaed86e9ea Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Mon, 19 Oct 2026 18:42:29 +0200 Subject: [PATCH 4/5] core: state which SMBF_LAST callbacks the bug ordering affects --- src/switch_core_media_bug.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/switch_core_media_bug.c b/src/switch_core_media_bug.c index 1e0a5138dfb..f4defb6dfe1 100644 --- a/src/switch_core_media_bug.c +++ b/src/switch_core_media_bug.c @@ -792,7 +792,8 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_push_spy_frame(switch_medi /* * Link a bug that is neither first nor the only one into the list: regular bugs go in front of the first SMBF_LAST bug (or at the end if - * there is none), SMBF_LAST bugs go to the end. That keeps SMBF_LAST bugs behind every other bug, so they see the frames as modified by all of them. + * there is none), SMBF_LAST bugs go to the end. That keeps SMBF_LAST bugs behind every other bug, so their stream and replace callbacks run after + * those of every other bug. * The caller holds session->bug_rwlock for writing. */ static void switch_core_media_bug_insert(switch_core_session_t *session, switch_media_bug_t *bug) From 3e0b3c4735e6d36722ed7cb7ec28be4ef3b9d895 Mon Sep 17 00:00:00 2001 From: Armin Sander Date: Mon, 19 Oct 2026 18:42:29 +0200 Subject: [PATCH 5/5] tests: drop redundant check for the rejected SMBF_FIRST | SMBF_LAST bug --- tests/unit/switch_ivr_async.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/switch_ivr_async.c b/tests/unit/switch_ivr_async.c index 273dfbb6101..2e50bef72c4 100644 --- a/tests/unit/switch_ivr_async.c +++ b/tests/unit/switch_ivr_async.c @@ -234,8 +234,6 @@ FST_CORE_BEGIN("./conf_async") fst_xcheck(pos != NULL, "Expect media bugs in order first, regular-1, regular-2, last-1, last-2"); } - fst_check(strstr((const char *)stream.data, "both") == NULL); - switch_safe_free(stream.data); switch_core_media_bug_remove_all(fst_session); }