From 324b6189da9f8a62ad8570c40502ad914ba1b2c5 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 21 Oct 2025 14:53:02 +0300 Subject: [PATCH 01/51] Add an audio limiter based on AGC2 from webrtc --- src/plugins/janus_audiobridge.c | 212 +++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 5 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index e936d3c827..43a70cd3ca 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -33,6 +33,7 @@ room-: { pin = sampling_rate = (e.g., 16000 for wideband mixing) spatial_audio = true|false (if true, the mix will be stereo to spatially place users, default=false) + use_limiter = true|false (whether to use a limiter to avoid clipping, default=false) audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must be negotiated/used or not for new joins, default=true) audiolevel_event = true|false (whether to emit event to other users or not, default=false) @@ -139,6 +140,7 @@ room-: { "allowed" : [ array of string tokens users can use to join this room, optional], "sampling_rate" : , "spatial_audio" : , + "use_limiter" : , "audiolevel_ext" : , "audiolevel_event" : , "audio_active_packets" : , @@ -1363,6 +1365,7 @@ static struct janus_json_parameter create_parameters[] = { {"default_expectedloss", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"default_bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"denoise", JANUS_JSON_BOOL, 0}, + {"use_limiter", JANUS_JSON_BOOL, 0} {"groups", JSON_ARRAY, 0} }; static struct janus_json_parameter edit_parameters[] = { @@ -1433,6 +1436,7 @@ static struct janus_json_parameter configure_parameters[] = { {"group", JSON_STRING, 0}, {"spatial_position", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"denoise", JANUS_JSON_BOOL, 0}, + {"use_limiter", JANUS_JSON_BOOL, 0} {"record", JANUS_JSON_BOOL, 0}, {"filename", JSON_STRING, 0}, {"display", JSON_STRING, 0}, @@ -1504,6 +1508,76 @@ static uint16_t rtp_range_min = JANUS_AUDIOBRIDGE_DEFAULT_RTP_RANGE_MIN; static uint16_t rtp_range_max = JANUS_AUDIOBRIDGE_DEFAULT_RTP_RANGE_MAX; static uint16_t rtp_range_slider = JANUS_AUDIOBRIDGE_DEFAULT_RTP_RANGE_MIN; +/* Constants for the audio limiter */ +#define K_SUB_FRAMES_IN_FRAME 20 +#define K_INITIAL_FILTER_STATE_LEVEL 0.0f +// Instant attack. +#define K_ATTACK_FILTER_CONSTANT 0.0f +// This constant affects the way scaling factors are interpolated for the first +// sub-frame of a frame. Only in the case in which the first sub-frame has an +// estimated level which is greater than the that of the previous analyzed +// sub-frame, linear interpolation is replaced with a power function which +// reduces the chances of over-shooting (and hence saturation), however reducing +// the fixed gain effectiveness. +#define K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER 8.0f +// Limiter decay constant. +// Computed as `10 ** (-1/20 * SUBFRAME_DURATION / K_DECAY_MS)` where: +// - `SUBFRAME_DURATION` is `K_FRAME_DURATION_MS / K_SUB_FRAMES_IN_FRAME`; +// - `K_FRAME_DURATION_MS` is 10 ms.; +// - `K_DECAY_MS` is 20.0f; +#define K_DECAY_FILTER_CONSTANT 0.9971259f +// Number of interpolation points for each region of the limiter. +// These values have been tuned to limit the interpolated gain curve error given +// the limiter parameters and allowing a maximum error of +/- 32768^-1. +#define K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS 22 +#define K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS 10 +#define K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS (K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS + K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS) +// Defined as DbfsToLinear(kLimiterMaxInputLevelDbFs) +#define K_MAX_INPUT_LEVEL_LINEAR 36766.300710566735f +static float approximation_params_x[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { + 30057.296875f, 30148.986328125f, 30240.67578125f, 30424.052734375f, + 30607.4296875f, 30790.806640625f, 30974.18359375f, 31157.560546875f, + 31340.939453125f, 31524.31640625f, 31707.693359375f, 31891.0703125f, + 32074.447265625f, 32257.82421875f, 32441.201171875f, 32624.580078125f, + 32807.95703125f, 32991.33203125f, 33174.7109375f, 33358.08984375f, + 33541.46484375f, 33724.84375f, 33819.53515625f, 34009.5390625f, + 34200.05859375f, 34389.81640625f, 34674.48828125f, 35054.375f, + 35434.86328125f, 35814.81640625f, 36195.16796875f, 36575.03125f +}; +static float approximation_params_m[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { + -3.515235675877192989e-07f, -1.050251626111275982e-06f, + -2.085213736791047268e-06f, -3.443004743530764244e-06f, + -4.773849468620028347e-06f, -6.077375928725814447e-06f, + -7.353257842623861507e-06f, -8.601219633419532329e-06f, + -9.821013009059242904e-06f, -1.101243378798244521e-05f, + -1.217532644659513608e-05f, -1.330956911260727793e-05f, + -1.441507538402220234e-05f, -1.549179251014720649e-05f, + -1.653970684856176376e-05f, -1.755882840370759368e-05f, + -1.854918446042574942e-05f, -1.951086778717581183e-05f, + -2.044398024736437947e-05f, -2.1348627342376858e-05f, + -2.222496914328075945e-05f, -2.265374678245279938e-05f, + -2.242570917587727308e-05f, -2.220122041762806475e-05f, + -2.19802095671184361e-05f, -2.176260204578284174e-05f, + -2.133731686626560986e-05f, -2.092481918225530535e-05f, + -2.052459603874012828e-05f, -2.013615448959171772e-05f, + -1.975903069251216948e-05f, -1.939277899509761482e-05f +}; + +static float approximation_params_q[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { + 1.010565876960754395f, 1.031631827354431152f, 1.062929749488830566f, + 1.104239225387573242f, 1.144973039627075195f, 1.185109615325927734f, + 1.224629044532775879f, 1.263512492179870605f, 1.301741957664489746f, + 1.339300632476806641f, 1.376173257827758789f, 1.412345528602600098f, + 1.447803974151611328f, 1.482536554336547852f, 1.516532182693481445f, + 1.549780607223510742f, 1.582272171974182129f, 1.613999366760253906f, + 1.644955039024353027f, 1.675132393836975098f, 1.704526185989379883f, + 1.718986630439758301f, 1.711274504661560059f, 1.703639745712280273f, + 1.696081161499023438f, 1.688597679138183594f, 1.673851132392883301f, + 1.659391283988952637f, 1.645209431648254395f, 1.631297469139099121f, + 1.617647409439086914f, 1.604251742362976074f +}; + + /* Asynchronous API message to handle */ typedef struct janus_audiobridge_message { janus_plugin_session *handle; @@ -1528,6 +1602,7 @@ typedef struct janus_audiobridge_room { gboolean spatial_audio; /* Whether the mix will use spatial audio, using stereo */ gboolean audiolevel_ext; /* Whether the ssrc-audio-level extension must be negotiated or not for new joins */ gboolean audiolevel_event; /* Whether to emit event to other users about audiolevel */ + gboolean use_limiter; /* Whether to use limiter or not */ uint default_expectedloss; /* Percent of packets we expect participants may miss, to help with outgoing FEC: can be overridden per-participant */ int32_t default_bitrate; /* Default bitrate to use for all Opus streams when encoding */ int audio_active_packets; /* Amount of packets with audio level for checkup */ @@ -2663,6 +2738,7 @@ int janus_audiobridge_init(janus_callbacks *callback, const char *config_path) { janus_config_item *spatial = janus_config_get(config, cat, janus_config_type_item, "spatial_audio"); janus_config_item *audiolevel_ext = janus_config_get(config, cat, janus_config_type_item, "audiolevel_ext"); janus_config_item *audiolevel_event = janus_config_get(config, cat, janus_config_type_item, "audiolevel_event"); + janus_config_item *use_limiter = janus_config_get(config, cat, janus_config_type_item, "use_limiter"); janus_config_item *audio_active_packets = janus_config_get(config, cat, janus_config_type_item, "audio_active_packets"); janus_config_item *audio_level_average = janus_config_get(config, cat, janus_config_type_item, "audio_level_average"); janus_config_item *default_expectedloss = janus_config_get(config, cat, janus_config_type_item, "default_expectedloss"); @@ -2741,6 +2817,7 @@ int janus_audiobridge_init(janus_callbacks *callback, const char *config_path) { continue; } audiobridge->spatial_audio = spatial && spatial->value && janus_is_true(spatial->value); + audiobridge->use_limiter = use_limiter && use_limiter->value && janus_is_true(use_limiter->value); audiobridge->audiolevel_ext = TRUE; if(audiolevel_ext != NULL && audiolevel_ext->value != NULL) audiobridge->audiolevel_ext = janus_is_true(audiolevel_ext->value); @@ -3264,6 +3341,7 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s json_t *mjrsdir = json_object_get(root, "mjrs_dir"); json_t *allowrtp = json_object_get(root, "allow_rtp_participants"); json_t *permanent = json_object_get(root, "permanent"); + json_t *use_limiter = json_object_get(root, "use_limiter"); if(allowed) { /* Make sure the "allowed" array only contains strings */ gboolean ok = TRUE; @@ -3390,6 +3468,7 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s audiobridge->spatial_audio = spatial ? json_is_true(spatial) : FALSE; audiobridge->audiolevel_ext = audiolevel_ext ? json_is_true(audiolevel_ext) : TRUE; audiobridge->audiolevel_event = audiolevel_event ? json_is_true(audiolevel_event) : FALSE; + audiobridge->use_limiter = use_limiter ? json_is_true(use_limiter) : FALSE; if(audiobridge->audiolevel_event) { audiobridge->audio_active_packets = 100; if(json_integer_value(audio_active_packets) > 0) { @@ -3579,6 +3658,8 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s janus_config_add(config, c, janus_config_item_create("audio_level_average", value)); } } + if (audiobridge->use_limiter) + janus_config_add(config, c, janus_config_item_create("use_limiter", "true")); if(audiobridge->allow_plainrtp) janus_config_add(config, c, janus_config_item_create("allow_rtp_participants", "true")); if(audiobridge->groups) { @@ -3760,6 +3841,8 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s janus_config_add(config, c, janus_config_item_create("audio_level_average", value)); } } + if (audiobridge->use_limiter) + janus_config_add(config, c, janus_config_item_create("use_limiter", "true")); if(audiobridge->allow_plainrtp) janus_config_add(config, c, janus_config_item_create("allow_rtp_participants", "true")); if(audiobridge->groups) { @@ -8510,6 +8593,22 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } + /* For audio limiter we split frame in subframes */ + int samples_in_sub_frame = 0; + float *per_sample_scaling_factors = NULL; + float *envelope = NULL; + float *scaling_factors = NULL; + uint32_t limiterBufferSize = 0; + float filter_state_level = K_INITIAL_FILTER_STATE_LEVEL; + float last_scaling_factor = 1.f; + /* In case audio limiter enabled, we need a buffer for it and some values */ + if (audiobridge->use_limiter) { + samples_in_sub_frame = samples / K_SUB_FRAMES_IN_FRAME; + per_sample_scaling_factors = g_malloc0((audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES) * sizeof(float)); + envelope = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); + scaling_factors = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); + } + /* Base RTP packets, in case there are forwarders involved */ gboolean have_opus[JANUS_AUDIOBRIDGE_MAX_GROUPS+1], have_alaw[JANUS_AUDIOBRIDGE_MAX_GROUPS+1], @@ -8536,6 +8635,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { int i=0; int count = 0, rf_count = 0, pf_count = 0, prev_count = 0; int lgain = 0, rgain = 0, diff = 0; + int mix_count = 0, sample_in_sub_frame = 0, sub_frame = 0; while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&audiobridge->destroyed)) { /* See if it's time to prepare a frame */ gettimeofday(&now, NULL); @@ -8679,6 +8779,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } } + mix_count++; } else { /* Add to the group submix */ int index = p->group-1; @@ -8803,6 +8904,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { buffer[i] += (resampled[i]*p->volume_gain)/100; } } + mix_count++; } else { /* Add to the group submix */ index = p->group-1; @@ -8825,13 +8927,99 @@ static void *janus_audiobridge_mixer_thread(void *data) { for(index=0; indexuse_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) { + /* Compute max envelope without smoothing. */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + for (sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { + envelope[sub_frame] = fmax(envelope[sub_frame], fabs(buffer[sub_frame * samples_in_sub_frame + sample_in_sub_frame])); + } + } + /* Make sure envelope increases happen one step earlier so that the + * corresponding *gain decrease* doesn't miss a sudden signal + * increase due to interpolation. + */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME - 1; ++sub_frame) { + if (envelope[sub_frame] < envelope[sub_frame + 1]) { + envelope[sub_frame] = envelope[sub_frame + 1]; + } + } + /* Add attack / decay smoothing. */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + const float envelope_value = envelope[sub_frame]; + if (envelope_value > filter_state_level) { + envelope[sub_frame] = envelope_value * (1 - K_ATTACK_FILTER_CONSTANT) + filter_state_level * K_ATTACK_FILTER_CONSTANT; + } else { + envelope[sub_frame] = envelope_value * (1 - K_DECAY_FILTER_CONSTANT) + filter_state_level * K_DECAY_FILTER_CONSTANT; + } + filter_state_level = envelope[sub_frame]; + } + scaling_factors[0] = last_scaling_factor; + for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const float input_level = envelope[i]; + if (input_level <= approximation_params_x[0]) { + /* Identity region. */ + scaling_factors[i+1] = 1.0f; + } else if (input_level >= K_MAX_INPUT_LEVEL_LINEAR) { + /* Saturating lower bound. The saturing samples exactly hit the clipping level. + * This method achieves has the lowest harmonic distorsion, but it + * may reduce the amplitude of the non-saturating samples too much. + */ + scaling_factors[i+1] = 32768.f / input_level; + } else { + /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ + size_t left = 0; + size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + while (left < right) { + size_t mid = left + (right - left) / 2; + if (approximation_params_x[mid] < input_level) + left = mid + 1; + else + right = mid; + } + /* Now left points to first element that is >= input_level, we need a previous element */ + const size_t index = (left > 0) ? left - 1 : 0; + /* Piece-wise linear interploation. */ + const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; + scaling_factors[i+1] = gain; + } + } + const int is_attack = scaling_factors[0] > scaling_factors[1]; + if (is_attack) { + for (int i = 0; i < samples_in_sub_frame; ++i) { + float t = (float)i / samples_in_sub_frame; + per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; + } + } + for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const int subframe_start = i * samples_in_sub_frame; + const float scaling_start = scaling_factors[i]; + const float scaling_end = scaling_factors[i + 1]; + const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; + + for (int j = 0; j < samples_in_sub_frame; ++j) { + per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; + } } + last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; } /* Are we recording the mix? (only do it if there's someone in, though...) */ if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { + /* If we use limiter and we mixed more than 1 track, apply it */ + if (audiobridge->use_limiter && mix_count > 1) { + for(i=0; i 32767) + outBuffer[i] = 32767; + else if (outBuffer[i] < -32768) + outBuffer[i] = -32768; } fwrite(outBuffer, sizeof(opus_int16), samples, audiobridge->recording); /* Every 5 seconds we update the wav header */ @@ -8900,9 +9088,20 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } } - for(i=0; i 1), apply it */ + if (audiobridge->use_limiter && mix_count > 2) { + for(i=0; i 32767) + outBuffer[i] = 32767; + else if (outBuffer[i] < -32768) + outBuffer[i] = -32768; + } + /* Enqueue this mixed frame for encoding in the participant thread */ janus_audiobridge_rtp_relay_packet *mixedpkt = g_malloc(sizeof(janus_audiobridge_rtp_relay_packet)); mixedpkt->data = g_malloc(samples*2); @@ -9065,6 +9264,9 @@ static void *janus_audiobridge_mixer_thread(void *data) { g_free(rtpbuffer); g_free(rtpalaw); g_free(rtpulaw); + g_free(envelope); + g_free(per_sample_scaling_factors); + g_free(scaling_factors); g_free(groupBuffers); if(groupEncoders) { for(index=0; index Date: Wed, 22 Oct 2025 10:04:20 +0300 Subject: [PATCH 02/51] fix clamping --- src/plugins/janus_audiobridge.c | 48 ++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 43a70cd3ca..9357117da5 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -9011,15 +9011,24 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* If we use limiter and we mixed more than 1 track, apply it */ if (audiobridge->use_limiter && mix_count > 1) { for(i=0; i 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = sample; } } - /* Clamp values that are outside int16 boundaries */ - for(i=0; i 32767) - outBuffer[i] = 32767; - else if (outBuffer[i] < -32768) - outBuffer[i] = -32768; + else { + /* Clamp values that are outside int16 boundaries */ + for(i=0; i 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = sample; + } } fwrite(outBuffer, sizeof(opus_int16), samples, audiobridge->recording); /* Every 5 seconds we update the wav header */ @@ -9092,14 +9101,23 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* If we use limiter and sumBuffer contains mix of more than 1 track (mix_count - 1 > 1), apply it */ if (audiobridge->use_limiter && mix_count > 2) { for(i=0; i 32767) - outBuffer[i] = 32767; - else if (outBuffer[i] < -32768) - outBuffer[i] = -32768; + opus_int32 sample = (opus_int32)(sumBuffer[i] * per_sample_scaling_factors[i] + 0.5f); + if(sample > 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = sample; + } + } else { + /* Clamp values that are outside int16 boundaries */ + for(i=0; i 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = sample; + } } /* Enqueue this mixed frame for encoding in the participant thread */ From 82b0cd3826de6955377cae0004552d22d1176b1f Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 22 Oct 2025 10:07:15 +0300 Subject: [PATCH 03/51] include math.h --- src/plugins/janus_audiobridge.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 9357117da5..98304752fa 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -1228,6 +1228,7 @@ room-: { #include #include #include +#include #include "../debug.h" #include "../apierror.h" From f750159969651e424f96562a112bdbed7aee36a5 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 22 Oct 2025 10:09:01 +0300 Subject: [PATCH 04/51] added missing commas -_-' --- src/plugins/janus_audiobridge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 98304752fa..96f0a849c1 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -1366,7 +1366,7 @@ static struct janus_json_parameter create_parameters[] = { {"default_expectedloss", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"default_bitrate", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"denoise", JANUS_JSON_BOOL, 0}, - {"use_limiter", JANUS_JSON_BOOL, 0} + {"use_limiter", JANUS_JSON_BOOL, 0}, {"groups", JSON_ARRAY, 0} }; static struct janus_json_parameter edit_parameters[] = { @@ -1437,7 +1437,7 @@ static struct janus_json_parameter configure_parameters[] = { {"group", JSON_STRING, 0}, {"spatial_position", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"denoise", JANUS_JSON_BOOL, 0}, - {"use_limiter", JANUS_JSON_BOOL, 0} + {"use_limiter", JANUS_JSON_BOOL, 0}, {"record", JANUS_JSON_BOOL, 0}, {"filename", JSON_STRING, 0}, {"display", JSON_STRING, 0}, From 7d76913d8553b716fed3b6da1648f988aa217552 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 22 Oct 2025 10:22:35 +0300 Subject: [PATCH 05/51] fixing typos --- src/plugins/janus_audiobridge.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 306a29842c..2395472903 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8607,6 +8607,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { uint32_t limiterBufferSize = 0; float filter_state_level = K_INITIAL_FILTER_STATE_LEVEL; float last_scaling_factor = 1.f; + opus_int32 sample = 0; /* In case audio limiter enabled, we need a buffer for it and some values */ if (audiobridge->use_limiter) { samples_in_sub_frame = samples / K_SUB_FRAMES_IN_FRAME; @@ -8941,7 +8942,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* Compute max envelope without smoothing. */ for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { for (sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { - envelope[sub_frame] = fmax(envelope[sub_frame], fabs(buffer[sub_frame * samples_in_sub_frame + sample_in_sub_frame])); + envelope[sub_frame] = fmax(envelope[sub_frame], abs(buffer[sub_frame * samples_in_sub_frame + sample_in_sub_frame])); } } /* Make sure envelope increases happen one step earlier so that the @@ -9017,7 +9018,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* If we use limiter and we mixed more than 1 track, apply it */ if (audiobridge->use_limiter && mix_count > 1) { for(i=0; i 32767) sample = 32767; else if(sample < -32768) @@ -9028,7 +9029,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { else { /* Clamp values that are outside int16 boundaries */ for(i=0; i 32767) sample = 32767; else if(sample < -32768) @@ -9105,8 +9106,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { } /* If we use limiter and sumBuffer contains mix of more than 1 track (mix_count - 1 > 1), apply it */ - if (audiobridge->use_limiter && mix_count > 2) { - for(i=0; iuse_limiter && mix_count > 2) { + for(i=0; i 32767) sample = 32767; From 5af1ec8ea7b4610fa2a3d761cf6a2c7ecedad61c Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 22 Oct 2025 10:39:32 +0300 Subject: [PATCH 06/51] remove unused variable --- src/plugins/janus_audiobridge.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 2395472903..93f5c71371 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8604,7 +8604,6 @@ static void *janus_audiobridge_mixer_thread(void *data) { float *per_sample_scaling_factors = NULL; float *envelope = NULL; float *scaling_factors = NULL; - uint32_t limiterBufferSize = 0; float filter_state_level = K_INITIAL_FILTER_STATE_LEVEL; float last_scaling_factor = 1.f; opus_int32 sample = 0; From aa0f27600373438260d538a8e8dbb0ac4ee09471 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 23 Oct 2025 12:43:46 +0300 Subject: [PATCH 07/51] Added WebRTC license text (not sure if the place is correct) --- src/plugins/audiobridge-deps/LICENSE.webrtc | 29 +++++++++++++++++++++ src/plugins/janus_audiobridge.c | 4 +++ 2 files changed, 33 insertions(+) create mode 100644 src/plugins/audiobridge-deps/LICENSE.webrtc diff --git a/src/plugins/audiobridge-deps/LICENSE.webrtc b/src/plugins/audiobridge-deps/LICENSE.webrtc new file mode 100644 index 0000000000..4c41b7b251 --- /dev/null +++ b/src/plugins/audiobridge-deps/LICENSE.webrtc @@ -0,0 +1,29 @@ +Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 93f5c71371..22224edfbb 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8938,6 +8938,10 @@ static void *janus_audiobridge_mixer_thread(void *data) { } /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ if (audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) { + /* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * The code below is adapted from the WebRTC project. + */ /* Compute max envelope without smoothing. */ for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { for (sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { From 11c45d960c9c1798be34156102d94b1b0bfa9789 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 23 Oct 2025 12:45:11 +0300 Subject: [PATCH 08/51] Move LICENSE.webrtc next to audiobridge code --- src/plugins/{audiobridge-deps => }/LICENSE.webrtc | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/plugins/{audiobridge-deps => }/LICENSE.webrtc (100%) diff --git a/src/plugins/audiobridge-deps/LICENSE.webrtc b/src/plugins/LICENSE.webrtc similarity index 100% rename from src/plugins/audiobridge-deps/LICENSE.webrtc rename to src/plugins/LICENSE.webrtc From 1a0d93e4b6de1de53cf7862cf9b58f59020908e6 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Fri, 24 Oct 2025 09:30:39 +0300 Subject: [PATCH 09/51] Adjust copyright comment for the code adapted from WebRTC project --- src/plugins/janus_audiobridge.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 22224edfbb..20ec1b0923 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8939,8 +8939,9 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ if (audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) { /* - * Copyright (c) 2011, The WebRTC project authors. All rights reserved. - * The code below is adapted from the WebRTC project. + * Calculating gain factors for limiter (adapted from WebRTC project). + * Original WebRTC code: https://webrtc.googlesource.com/src + * Licensed under BSD 3-Clause License. */ /* Compute max envelope without smoothing. */ for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { From 1ab2b5b0f3686772e89a398ee2921c8a58fcf19e Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Fri, 24 Oct 2025 09:55:27 +0300 Subject: [PATCH 10/51] AST-35514 --- src/plugins/janus_audiobridge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 20ec1b0923..c487c20704 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -9109,8 +9109,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } - /* If we use limiter and sumBuffer contains mix of more than 1 track (mix_count - 1 > 1), apply it */ - if(audiobridge->use_limiter && mix_count > 2) { + /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ + if(audiobridge->use_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { for(i=0; i 32767) From 21603ca4fc9ed3fa13cae93acbc9f947ed79e490 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Mon, 27 Oct 2025 14:22:32 +0300 Subject: [PATCH 11/51] extract initialization into a separate function (force inline) --- src/plugins/janus_audiobridge.c | 170 +++++++++++++++++--------------- 1 file changed, 90 insertions(+), 80 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index c487c20704..1d153b1442 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8535,6 +8535,94 @@ static void janus_audiobridge_update_wav_header(janus_audiobridge_room *audiobri } } +static inline __attribute__((always_inline)) void initialize_limiter( + opus_int32 *buffer, + float *envelope, + float *scaling_factors, + float *per_sample_scaling_factors, + int samples_in_sub_frame, + float *filter_state_level, + float *last_scaling_factor) { + + int sub_frame, sample_in_sub_frame, i; + /* + * Calculating gain factors for limiter (adapted from WebRTC project). + * Original WebRTC code: https://webrtc.googlesource.com/src + * Licensed under BSD 3-Clause License. + */ + /* Compute max envelope without smoothing. */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + for (sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { + envelope[sub_frame] = fmax(envelope[sub_frame], abs(buffer[sub_frame * samples_in_sub_frame + sample_in_sub_frame])); + } + } + /* Make sure envelope increases happen one step earlier so that the + * corresponding *gain decrease* doesn't miss a sudden signal + * increase due to interpolation. + */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME - 1; ++sub_frame) { + if (envelope[sub_frame] < envelope[sub_frame + 1]) + envelope[sub_frame] = envelope[sub_frame + 1]; + } + /* Add attack / decay smoothing. */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + const float envelope_value = envelope[sub_frame]; + if (envelope_value > *filter_state_level) { + envelope[sub_frame] = envelope_value * (1 - K_ATTACK_FILTER_CONSTANT) + *filter_state_level * K_ATTACK_FILTER_CONSTANT; + } else { + envelope[sub_frame] = envelope_value * (1 - K_DECAY_FILTER_CONSTANT) + *filter_state_level * K_DECAY_FILTER_CONSTANT; + } + *filter_state_level = envelope[sub_frame]; + } + scaling_factors[0] = *last_scaling_factor; + for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const float input_level = envelope[i]; + if (input_level <= approximation_params_x[0]) { + /* Identity region. */ + scaling_factors[i+1] = 1.0f; + } else if (input_level >= K_MAX_INPUT_LEVEL_LINEAR) { + /* Saturating lower bound. The saturing samples exactly hit the clipping level. + * This method achieves has the lowest harmonic distorsion, but it + * may reduce the amplitude of the non-saturating samples too much. + */ + scaling_factors[i+1] = 32768.f / input_level; + } else { + /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ + size_t left = 0; + size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + while (left < right) { + size_t mid = left + (right - left) / 2; + if (approximation_params_x[mid] < input_level) + left = mid + 1; + else + right = mid; + } + /* Now left points to first element that is >= input_level, we need a previous element */ + const size_t index = (left > 0) ? left - 1 : 0; + /* Piece-wise linear interploation. */ + const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; + scaling_factors[i+1] = gain; + } + } + const int is_attack = scaling_factors[0] > scaling_factors[1]; + if (is_attack) { + for (int i = 0; i < samples_in_sub_frame; ++i) { + float t = (float)i / samples_in_sub_frame; + per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; + } + } + for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const int subframe_start = i * samples_in_sub_frame; + const float scaling_start = scaling_factors[i]; + const float scaling_end = scaling_factors[i + 1]; + const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; + for (int j = 0; j < samples_in_sub_frame; ++j) { + per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; + } + } + *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; +} + /* Thread to mix the contributions from all participants */ static void *janus_audiobridge_mixer_thread(void *data) { JANUS_LOG(LOG_VERB, "Audio bridge thread starting...\n"); @@ -8937,86 +9025,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ - if (audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) { - /* - * Calculating gain factors for limiter (adapted from WebRTC project). - * Original WebRTC code: https://webrtc.googlesource.com/src - * Licensed under BSD 3-Clause License. - */ - /* Compute max envelope without smoothing. */ - for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { - for (sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { - envelope[sub_frame] = fmax(envelope[sub_frame], abs(buffer[sub_frame * samples_in_sub_frame + sample_in_sub_frame])); - } - } - /* Make sure envelope increases happen one step earlier so that the - * corresponding *gain decrease* doesn't miss a sudden signal - * increase due to interpolation. - */ - for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME - 1; ++sub_frame) { - if (envelope[sub_frame] < envelope[sub_frame + 1]) { - envelope[sub_frame] = envelope[sub_frame + 1]; - } - } - /* Add attack / decay smoothing. */ - for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { - const float envelope_value = envelope[sub_frame]; - if (envelope_value > filter_state_level) { - envelope[sub_frame] = envelope_value * (1 - K_ATTACK_FILTER_CONSTANT) + filter_state_level * K_ATTACK_FILTER_CONSTANT; - } else { - envelope[sub_frame] = envelope_value * (1 - K_DECAY_FILTER_CONSTANT) + filter_state_level * K_DECAY_FILTER_CONSTANT; - } - filter_state_level = envelope[sub_frame]; - } - scaling_factors[0] = last_scaling_factor; - for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { - const float input_level = envelope[i]; - if (input_level <= approximation_params_x[0]) { - /* Identity region. */ - scaling_factors[i+1] = 1.0f; - } else if (input_level >= K_MAX_INPUT_LEVEL_LINEAR) { - /* Saturating lower bound. The saturing samples exactly hit the clipping level. - * This method achieves has the lowest harmonic distorsion, but it - * may reduce the amplitude of the non-saturating samples too much. - */ - scaling_factors[i+1] = 32768.f / input_level; - } else { - /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ - size_t left = 0; - size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; - while (left < right) { - size_t mid = left + (right - left) / 2; - if (approximation_params_x[mid] < input_level) - left = mid + 1; - else - right = mid; - } - /* Now left points to first element that is >= input_level, we need a previous element */ - const size_t index = (left > 0) ? left - 1 : 0; - /* Piece-wise linear interploation. */ - const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; - scaling_factors[i+1] = gain; - } - } - const int is_attack = scaling_factors[0] > scaling_factors[1]; - if (is_attack) { - for (int i = 0; i < samples_in_sub_frame; ++i) { - float t = (float)i / samples_in_sub_frame; - per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; - } - } - for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { - const int subframe_start = i * samples_in_sub_frame; - const float scaling_start = scaling_factors[i]; - const float scaling_end = scaling_factors[i + 1]; - const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; - - for (int j = 0; j < samples_in_sub_frame; ++j) { - per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; - } - } - last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; - } + if (audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) + initialize_limiter(buffer, envelope, scaling_factors, per_sample_scaling_factors, samples_in_sub_frame, &filter_state_level, &last_scaling_factor); /* Are we recording the mix? (only do it if there's someone in, though...) */ if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { /* If we use limiter and we mixed more than 1 track, apply it */ From e75e9b089a0efcd898de2a48f1639300046549df Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 28 Oct 2025 14:06:42 +0300 Subject: [PATCH 12/51] extract some parts into a separate files, vectorize (AVX2, SSE4.2) --- src/Makefile.am | 1 + .../limiter}/LICENSE.webrtc | 0 .../audiobridge-deps/limiter/limiter.c | 311 ++++++++++++++++++ .../audiobridge-deps/limiter/limiter.h | 45 +++ src/plugins/janus_audiobridge.c | 166 +--------- 5 files changed, 364 insertions(+), 159 deletions(-) rename src/plugins/{ => audiobridge-deps/limiter}/LICENSE.webrtc (100%) create mode 100644 src/plugins/audiobridge-deps/limiter/limiter.c create mode 100644 src/plugins/audiobridge-deps/limiter/limiter.h diff --git a/src/Makefile.am b/src/Makefile.am index d316cd7043..116472ef28 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -397,6 +397,7 @@ plugins_libadd = \ if ENABLE_PLUGIN_AUDIOBRIDGE plugin_LTLIBRARIES += plugins/libjanus_audiobridge.la plugins_libjanus_audiobridge_la_SOURCES = plugins/janus_audiobridge.c \ + plugins/audiobridge-deps/limiter/limiter.c plugins/audiobridge-deps/limiter/limiter.h \ plugins/audiobridge-deps/jitter.c plugins/audiobridge-deps/resample.c plugins/audiobridge-deps/arch.h \ plugins/audiobridge-deps/os_support.h plugins/audiobridge-deps/speex/speex_jitter.h plugins/audiobridge-deps/speex/speex_resampler.h \ plugins/audiobridge-deps/speex/speexdsp_types.h plugins/audiobridge-deps/speex/speexdsp_config_types.h diff --git a/src/plugins/LICENSE.webrtc b/src/plugins/audiobridge-deps/limiter/LICENSE.webrtc similarity index 100% rename from src/plugins/LICENSE.webrtc rename to src/plugins/audiobridge-deps/limiter/LICENSE.webrtc diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c new file mode 100644 index 0000000000..a9b5e1d642 --- /dev/null +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -0,0 +1,311 @@ +#include "limiter.h" +#include +#include +#include + +/* SIMD intrinsics */ +#if defined(__AVX2__) || defined(__SSE4_2__) +#include +#include + +int has_avx2() { + unsigned int eax, ebx, ecx, edx; + // First check leaf 1 + if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { + if ((ecx & bit_OSXSAVE) && (ecx & bit_AVX)) { + if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) { + return (ebx & (1 << 5)) != 0; /* AVX2 — bit 5 of EBX */ + } + } + } + return 0; +} +int has_sse42() { + unsigned int eax, ebx, ecx, edx; + if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { + return (ecx & (1 << 20)) != 0; /* SSE4.2 — bit 20 of ECX */ + } + return 0; +} +#endif + + +/* Static data for the limiter */ +static float approximation_params_x[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { + 30057.296875f, 30148.986328125f, 30240.67578125f, 30424.052734375f, + 30607.4296875f, 30790.806640625f, 30974.18359375f, 31157.560546875f, + 31340.939453125f, 31524.31640625f, 31707.693359375f, 31891.0703125f, + 32074.447265625f, 32257.82421875f, 32441.201171875f, 32624.580078125f, + 32807.95703125f, 32991.33203125f, 33174.7109375f, 33358.08984375f, + 33541.46484375f, 33724.84375f, 33819.53515625f, 34009.5390625f, + 34200.05859375f, 34389.81640625f, 34674.48828125f, 35054.375f, + 35434.86328125f, 35814.81640625f, 36195.16796875f, 36575.03125f +}; + +static float approximation_params_m[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { + -3.515235675877192989e-07f, -1.050251626111275982e-06f, + -2.085213736791047268e-06f, -3.443004743530764244e-06f, + -4.773849468620028347e-06f, -6.077375928725814447e-06f, + -7.353257842623861507e-06f, -8.601219633419532329e-06f, + -9.821013009059242904e-06f, -1.101243378798244521e-05f, + -1.217532644659513608e-05f, -1.330956911260727793e-05f, + -1.441507538402220234e-05f, -1.549179251014720649e-05f, + -1.653970684856176376e-05f, -1.755882840370759368e-05f, + -1.854918446042574942e-05f, -1.951086778717581183e-05f, + -2.044398024736437947e-05f, -2.1348627342376858e-05f, + -2.222496914328075945e-05f, -2.265374678245279938e-05f, + -2.242570917587727308e-05f, -2.220122041762806475e-05f, + -2.19802095671184361e-05f, -2.176260204578284174e-05f, + -2.133731686626560986e-05f, -2.092481918225530535e-05f, + -2.052459603874012828e-05f, -2.013615448959171772e-05f, + -1.975903069251216948e-05f, -1.939277899509761482e-05f +}; + +static float approximation_params_q[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { + 1.010565876960754395f, 1.031631827354431152f, 1.062929749488830566f, + 1.104239225387573242f, 1.144973039627075195f, 1.185109615325927734f, + 1.224629044532775879f, 1.263512492179870605f, 1.301741957664489746f, + 1.339300632476806641f, 1.376173257827758789f, 1.412345528602600098f, + 1.447803974151611328f, 1.482536554336547852f, 1.516532182693481445f, + 1.549780607223510742f, 1.582272171974182129f, 1.613999366760253906f, + 1.644955039024353027f, 1.675132393836975098f, 1.704526185989379883f, + 1.718986630439758301f, 1.711274504661560059f, 1.703639745712280273f, + 1.696081161499023438f, 1.688597679138183594f, 1.673851132392883301f, + 1.659391283988952637f, 1.645209431648254395f, 1.631297469139099121f, + 1.617647409439086914f, 1.604251742362976074f +}; + + +/* Function pointer for the selected implementation */ +static void (*compute_max_envelope_func)(opus_int32 *buffer, float *envelope, int samples_in_sub_frame) = NULL; + +#if defined(__AVX2__) +void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ + /* Compute max envelope without smoothing. */ + int sub_frame, sample_in_sub_frame; + /* AVX2 implementation - process 8 32-bit integers at a time */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + const opus_int32 *sub_frame_buffer = &buffer[sub_frame * samples_in_sub_frame]; + __m256i max_val = _mm256_setzero_si256(); + + /* Process 8 integers at a time */ + int simd_samples = (samples_in_sub_frame / 8) * 8; + for (sample_in_sub_frame = 0; sample_in_sub_frame < simd_samples; sample_in_sub_frame += 8) { + __m256i vals = _mm256_loadu_si256((__m256i*)&sub_frame_buffer[sample_in_sub_frame]); + __m256i abs_vals = _mm256_abs_epi32(vals); + max_val = _mm256_max_epi32(max_val, abs_vals); + } + + /* Extract maximum from the vector */ + /* Get max of first 4 and last 4 elements */ + __m128i max_low = _mm256_extracti128_si256(max_val, 0); + __m128i max_high = _mm256_extracti128_si256(max_val, 1); + __m128i max_4 = _mm_max_epi32(max_low, max_high); + + /* Get max of first 2 and last 2 elements */ + __m128i max_2 = _mm_max_epi32(max_4, _mm_shuffle_epi32(max_4, _MM_SHUFFLE(1, 0, 3, 2))); + + /* Get max of first and last elements */ + __m128i max_1 = _mm_max_epi32(max_2, _mm_shuffle_epi32(max_2, _MM_SHUFFLE(0, 0, 0, 1))); + + int max_scalar = _mm_cvtsi128_si32(max_1); + + /* Process remaining samples */ + for (; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { + int abs_val = abs(sub_frame_buffer[sample_in_sub_frame]); + if (abs_val > max_scalar) { + max_scalar = abs_val; + } + } + + if ((float)max_scalar > envelope[sub_frame]) { + envelope[sub_frame] = (float)max_scalar; + } + } +} +#endif +#if defined(__SSE4_2__) +void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ + /* Compute max envelope without smoothing. */ + int sub_frame, sample_in_sub_frame; + /* SSE4.2 implementation - process 4 floats at a time */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + const opus_int32 *sub_frame_buffer = &buffer[sub_frame * samples_in_sub_frame]; + __m128 max_val = _mm_setzero_ps(); + + /* Process 4 floats at a time */ + int simd_samples = (samples_in_sub_frame / 4) * 4; + for (sample_in_sub_frame = 0; sample_in_sub_frame < simd_samples; sample_in_sub_frame += 4) { + /* Load 4 integers and convert to floats */ + __m128i vals_i = _mm_loadu_si128((__m128i*)&sub_frame_buffer[sample_in_sub_frame]); + __m128 vals = _mm_cvtepi32_ps(vals_i); + + /* Take absolute values */ + __m128 abs_vals = _mm_andnot_ps(_mm_set1_ps(-0.0f), vals); /* Clear sign bit */ + + max_val = _mm_max_ps(max_val, abs_vals); + } + + /* Extract maximum from the vector */ + __m128 max_2 = _mm_max_ps(max_val, _mm_shuffle_ps(max_val, max_val, _MM_SHUFFLE(1, 0, 3, 2))); + __m128 max_1 = _mm_max_ss(max_2, _mm_shuffle_ps(max_2, max_2, _MM_SHUFFLE(0, 0, 0, 1))); + + float max_scalar = _mm_cvtss_f32(max_1); + + /* Process remaining samples */ + for (; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { + float abs_val = (float)abs(sub_frame_buffer[sample_in_sub_frame]); + if (abs_val > max_scalar) { + max_scalar = abs_val; + } + } + + if (max_scalar > envelope[sub_frame]) { + envelope[sub_frame] = max_scalar; + } + } +} +#endif + + +static void compute_max_envelope_scalar(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ + /* Compute max envelope without smoothing. */ + int sub_frame, sample_in_sub_frame; + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + for (sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { + envelope[sub_frame] = fmax(envelope[sub_frame], abs(buffer[sub_frame * samples_in_sub_frame + sample_in_sub_frame])); + } + } +} + +static inline __attribute__((always_inline)) void compute_envelope( + opus_int32 *buffer, + float *envelope, + int samples_in_sub_frame, + float *filter_state_level) { + int sub_frame, sample_in_sub_frame; + compute_max_envelope_func(buffer, envelope, samples_in_sub_frame); + + /* Make sure envelope increases happen one step earlier so that the + * corresponding *gain decrease* doesn't miss a sudden signal + * increase due to interpolation. + */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME - 1; ++sub_frame) { + if (envelope[sub_frame] < envelope[sub_frame + 1]) + envelope[sub_frame] = envelope[sub_frame + 1]; + } + + /* Add attack / decay smoothing. */ + for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { + const float envelope_value = envelope[sub_frame]; + if (envelope_value > *filter_state_level) { + envelope[sub_frame] = envelope_value * (1 - K_ATTACK_FILTER_CONSTANT) + *filter_state_level * K_ATTACK_FILTER_CONSTANT; + } else { + envelope[sub_frame] = envelope_value * (1 - K_DECAY_FILTER_CONSTANT) + *filter_state_level * K_DECAY_FILTER_CONSTANT; + } + *filter_state_level = envelope[sub_frame]; + } +} +static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors( + float *scaling_factors, + float *per_sample_scaling_factors, + int samples_in_sub_frame) { + + const int is_attack = scaling_factors[0] > scaling_factors[1]; + if (is_attack) { + for (int i = 0; i < samples_in_sub_frame; ++i) { + float t = (float)i / samples_in_sub_frame; + per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; + } + } + + for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const int subframe_start = i * samples_in_sub_frame; + const float scaling_start = scaling_factors[i]; + const float scaling_end = scaling_factors[i + 1]; + const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; + + for (int j = 0; j < samples_in_sub_frame; ++j) { + per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; + } + } +} + +static inline __attribute__((always_inline)) void calculate_scaling_factors( + float *envelope, + float *scaling_factors, + float *last_scaling_factor) { + + int i; + + scaling_factors[0] = *last_scaling_factor; + for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const float input_level = envelope[i]; + if (input_level <= approximation_params_x[0]) { + /* Identity region. */ + scaling_factors[i+1] = 1.0f; + } else if (input_level >= K_MAX_INPUT_LEVEL_LINEAR) { + /* Saturating lower bound. The saturing samples exactly hit the clipping level. + * This method achieves has the lowest harmonic distorsion, but it + * may reduce the amplitude of the non-saturating samples too much. + */ + scaling_factors[i+1] = 32768.f / input_level; + } else { + /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ + size_t left = 0; + size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + while (left < right) { + size_t mid = left + (right - left) / 2; + if (approximation_params_x[mid] < input_level) + left = mid + 1; + else + right = mid; + } + /* Now left points to first element that is >= input_level, we need a previous element */ + const size_t index = (left > 0) ? left - 1 : 0; + /* Piece-wise linear interploation. */ + const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; + scaling_factors[i+1] = gain; + } + } + + *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; +} + +inline __attribute__((always_inline)) void compute_scaling_factors( + opus_int32 *buffer, + float *envelope, + float *scaling_factors, + float *per_sample_scaling_factors, + int samples_in_sub_frame, + float *filter_state_level, + float *last_scaling_factor) { + + int sub_frame, sample_in_sub_frame; + + /* + * Calculating gain factors for limiter (adapted from WebRTC project). + * Original WebRTC code: https://webrtc.googlesource.com/src + * Licensed under BSD 3-Clause License. + */ + compute_envelope(buffer, envelope, samples_in_sub_frame, filter_state_level); + calculate_scaling_factors(envelope, scaling_factors, last_scaling_factor); + compute_per_sample_scaling_factors(scaling_factors, per_sample_scaling_factors, samples_in_sub_frame); +} + +inline __attribute__((always_inline)) void init_limiter() { + #if defined(__AVX2__) + if (has_avx2()) { + compute_max_envelope_func = compute_max_envelope_avx2; + return; + } + #endif + #if defined(__SSE4_2__) + if (has_sse42()) { + compute_max_envelope_func = compute_max_envelope_sse42; + return; + } + #endif + + compute_max_envelope_func = compute_max_envelope_scalar; +} \ No newline at end of file diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h new file mode 100644 index 0000000000..ed74a06d21 --- /dev/null +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -0,0 +1,45 @@ +#ifndef LIMITER_H +#define LIMITER_H + +#include +#include +#include + +/* Constants for the audio limiter */ +#define K_SUB_FRAMES_IN_FRAME 20 +#define K_INITIAL_FILTER_STATE_LEVEL 0.0f +// Instant attack. +#define K_ATTACK_FILTER_CONSTANT 0.0f +// This constant affects the way scaling factors are interpolated for the first +// sub-frame of a frame. Only in the case in which the first sub-frame has an +// estimated level which is greater than the that of the previous analyzed +// sub-frame, linear interpolation is replaced with a power function which +// reduces the chances of over-shooting (and hence saturation), however reducing +// the fixed gain effectiveness. +#define K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER 8.0f +// Limiter decay constant. +// Computed as `10 ** (-1/20 * SUBFRAME_DURATION / K_DECAY_MS)` where: +// - `SUBFRAME_DURATION` is `K_FRAME_DURATION_MS / K_SUB_FRAMES_IN_FRAME`; +// - `K_FRAME_DURATION_MS` is 10 ms.; +// - `K_DECAY_MS` is 20.0f; +#define K_DECAY_FILTER_CONSTANT 0.9971259f +// Number of interpolation points for each region of the limiter. +// These values have been tuned to limit the interpolated gain curve error given +// the limiter parameters and allowing a maximum error of +/- 32768^-1. +#define K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS 22 +#define K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS 10 +#define K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS (K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS + K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS) +// Defined as DbfsToLinear(kLimiterMaxInputLevelDbFs) +#define K_MAX_INPUT_LEVEL_LINEAR 36766.300710566735f + +void compute_scaling_factors( + opus_int32 *buffer, + float *envelope, + float *scaling_factors, + float *per_sample_scaling_factors, + int samples_in_sub_frame, + float *filter_state_level, + float *last_scaling_factor); + +void init_limiter(); +#endif // LIMITER_H diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 1d153b1442..420284d53d 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -1243,6 +1243,7 @@ room-: { #include "../utils.h" #include "../ip-utils.h" +#include "./audiobridge-deps/limiter/limiter.h" /* Plugin information */ #define JANUS_AUDIOBRIDGE_VERSION 13 @@ -1509,76 +1510,6 @@ static uint16_t rtp_range_min = JANUS_AUDIOBRIDGE_DEFAULT_RTP_RANGE_MIN; static uint16_t rtp_range_max = JANUS_AUDIOBRIDGE_DEFAULT_RTP_RANGE_MAX; static uint16_t rtp_range_slider = JANUS_AUDIOBRIDGE_DEFAULT_RTP_RANGE_MIN; -/* Constants for the audio limiter */ -#define K_SUB_FRAMES_IN_FRAME 20 -#define K_INITIAL_FILTER_STATE_LEVEL 0.0f -// Instant attack. -#define K_ATTACK_FILTER_CONSTANT 0.0f -// This constant affects the way scaling factors are interpolated for the first -// sub-frame of a frame. Only in the case in which the first sub-frame has an -// estimated level which is greater than the that of the previous analyzed -// sub-frame, linear interpolation is replaced with a power function which -// reduces the chances of over-shooting (and hence saturation), however reducing -// the fixed gain effectiveness. -#define K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER 8.0f -// Limiter decay constant. -// Computed as `10 ** (-1/20 * SUBFRAME_DURATION / K_DECAY_MS)` where: -// - `SUBFRAME_DURATION` is `K_FRAME_DURATION_MS / K_SUB_FRAMES_IN_FRAME`; -// - `K_FRAME_DURATION_MS` is 10 ms.; -// - `K_DECAY_MS` is 20.0f; -#define K_DECAY_FILTER_CONSTANT 0.9971259f -// Number of interpolation points for each region of the limiter. -// These values have been tuned to limit the interpolated gain curve error given -// the limiter parameters and allowing a maximum error of +/- 32768^-1. -#define K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS 22 -#define K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS 10 -#define K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS (K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS + K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS) -// Defined as DbfsToLinear(kLimiterMaxInputLevelDbFs) -#define K_MAX_INPUT_LEVEL_LINEAR 36766.300710566735f -static float approximation_params_x[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { - 30057.296875f, 30148.986328125f, 30240.67578125f, 30424.052734375f, - 30607.4296875f, 30790.806640625f, 30974.18359375f, 31157.560546875f, - 31340.939453125f, 31524.31640625f, 31707.693359375f, 31891.0703125f, - 32074.447265625f, 32257.82421875f, 32441.201171875f, 32624.580078125f, - 32807.95703125f, 32991.33203125f, 33174.7109375f, 33358.08984375f, - 33541.46484375f, 33724.84375f, 33819.53515625f, 34009.5390625f, - 34200.05859375f, 34389.81640625f, 34674.48828125f, 35054.375f, - 35434.86328125f, 35814.81640625f, 36195.16796875f, 36575.03125f -}; -static float approximation_params_m[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { - -3.515235675877192989e-07f, -1.050251626111275982e-06f, - -2.085213736791047268e-06f, -3.443004743530764244e-06f, - -4.773849468620028347e-06f, -6.077375928725814447e-06f, - -7.353257842623861507e-06f, -8.601219633419532329e-06f, - -9.821013009059242904e-06f, -1.101243378798244521e-05f, - -1.217532644659513608e-05f, -1.330956911260727793e-05f, - -1.441507538402220234e-05f, -1.549179251014720649e-05f, - -1.653970684856176376e-05f, -1.755882840370759368e-05f, - -1.854918446042574942e-05f, -1.951086778717581183e-05f, - -2.044398024736437947e-05f, -2.1348627342376858e-05f, - -2.222496914328075945e-05f, -2.265374678245279938e-05f, - -2.242570917587727308e-05f, -2.220122041762806475e-05f, - -2.19802095671184361e-05f, -2.176260204578284174e-05f, - -2.133731686626560986e-05f, -2.092481918225530535e-05f, - -2.052459603874012828e-05f, -2.013615448959171772e-05f, - -1.975903069251216948e-05f, -1.939277899509761482e-05f -}; - -static float approximation_params_q[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { - 1.010565876960754395f, 1.031631827354431152f, 1.062929749488830566f, - 1.104239225387573242f, 1.144973039627075195f, 1.185109615325927734f, - 1.224629044532775879f, 1.263512492179870605f, 1.301741957664489746f, - 1.339300632476806641f, 1.376173257827758789f, 1.412345528602600098f, - 1.447803974151611328f, 1.482536554336547852f, 1.516532182693481445f, - 1.549780607223510742f, 1.582272171974182129f, 1.613999366760253906f, - 1.644955039024353027f, 1.675132393836975098f, 1.704526185989379883f, - 1.718986630439758301f, 1.711274504661560059f, 1.703639745712280273f, - 1.696081161499023438f, 1.688597679138183594f, 1.673851132392883301f, - 1.659391283988952637f, 1.645209431648254395f, 1.631297469139099121f, - 1.617647409439086914f, 1.604251742362976074f -}; - - /* Asynchronous API message to handle */ typedef struct janus_audiobridge_message { janus_plugin_session *handle; @@ -2633,6 +2564,10 @@ int janus_audiobridge_init(janus_callbacks *callback, const char *config_path) { JANUS_LOG(LOG_WARN, "Denoising via RNNoise NOT supported\n"); #endif + /* Setting function pointers according to runtime vectorization support (AVX2, SSE4.2 or scalar) */ + init_limiter(); + JANUS_LOG(LOG_INFO, "Initialized optimized limiter implementation\n") + /* Parse configuration to populate the rooms list */ if(config != NULL) { janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general"); @@ -8535,94 +8470,6 @@ static void janus_audiobridge_update_wav_header(janus_audiobridge_room *audiobri } } -static inline __attribute__((always_inline)) void initialize_limiter( - opus_int32 *buffer, - float *envelope, - float *scaling_factors, - float *per_sample_scaling_factors, - int samples_in_sub_frame, - float *filter_state_level, - float *last_scaling_factor) { - - int sub_frame, sample_in_sub_frame, i; - /* - * Calculating gain factors for limiter (adapted from WebRTC project). - * Original WebRTC code: https://webrtc.googlesource.com/src - * Licensed under BSD 3-Clause License. - */ - /* Compute max envelope without smoothing. */ - for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { - for (sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { - envelope[sub_frame] = fmax(envelope[sub_frame], abs(buffer[sub_frame * samples_in_sub_frame + sample_in_sub_frame])); - } - } - /* Make sure envelope increases happen one step earlier so that the - * corresponding *gain decrease* doesn't miss a sudden signal - * increase due to interpolation. - */ - for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME - 1; ++sub_frame) { - if (envelope[sub_frame] < envelope[sub_frame + 1]) - envelope[sub_frame] = envelope[sub_frame + 1]; - } - /* Add attack / decay smoothing. */ - for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { - const float envelope_value = envelope[sub_frame]; - if (envelope_value > *filter_state_level) { - envelope[sub_frame] = envelope_value * (1 - K_ATTACK_FILTER_CONSTANT) + *filter_state_level * K_ATTACK_FILTER_CONSTANT; - } else { - envelope[sub_frame] = envelope_value * (1 - K_DECAY_FILTER_CONSTANT) + *filter_state_level * K_DECAY_FILTER_CONSTANT; - } - *filter_state_level = envelope[sub_frame]; - } - scaling_factors[0] = *last_scaling_factor; - for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { - const float input_level = envelope[i]; - if (input_level <= approximation_params_x[0]) { - /* Identity region. */ - scaling_factors[i+1] = 1.0f; - } else if (input_level >= K_MAX_INPUT_LEVEL_LINEAR) { - /* Saturating lower bound. The saturing samples exactly hit the clipping level. - * This method achieves has the lowest harmonic distorsion, but it - * may reduce the amplitude of the non-saturating samples too much. - */ - scaling_factors[i+1] = 32768.f / input_level; - } else { - /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ - size_t left = 0; - size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; - while (left < right) { - size_t mid = left + (right - left) / 2; - if (approximation_params_x[mid] < input_level) - left = mid + 1; - else - right = mid; - } - /* Now left points to first element that is >= input_level, we need a previous element */ - const size_t index = (left > 0) ? left - 1 : 0; - /* Piece-wise linear interploation. */ - const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; - scaling_factors[i+1] = gain; - } - } - const int is_attack = scaling_factors[0] > scaling_factors[1]; - if (is_attack) { - for (int i = 0; i < samples_in_sub_frame; ++i) { - float t = (float)i / samples_in_sub_frame; - per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; - } - } - for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { - const int subframe_start = i * samples_in_sub_frame; - const float scaling_start = scaling_factors[i]; - const float scaling_end = scaling_factors[i + 1]; - const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; - for (int j = 0; j < samples_in_sub_frame; ++j) { - per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; - } - } - *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; -} - /* Thread to mix the contributions from all participants */ static void *janus_audiobridge_mixer_thread(void *data) { JANUS_LOG(LOG_VERB, "Audio bridge thread starting...\n"); @@ -9026,7 +8873,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { } /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ if (audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) - initialize_limiter(buffer, envelope, scaling_factors, per_sample_scaling_factors, samples_in_sub_frame, &filter_state_level, &last_scaling_factor); + compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, + samples_in_sub_frame, &filter_state_level, &last_scaling_factor); /* Are we recording the mix? (only do it if there's someone in, though...) */ if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { /* If we use limiter and we mixed more than 1 track, apply it */ From 22470427e1fb2fc93cb6012f2d7726573210a513 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 28 Oct 2025 14:14:55 +0300 Subject: [PATCH 13/51] fix compilation --- src/plugins/janus_audiobridge.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 420284d53d..b6572d0f4c 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -1243,7 +1243,7 @@ room-: { #include "../utils.h" #include "../ip-utils.h" -#include "./audiobridge-deps/limiter/limiter.h" +#include "audiobridge-deps/limiter/limiter.h" /* Plugin information */ #define JANUS_AUDIOBRIDGE_VERSION 13 @@ -2566,7 +2566,7 @@ int janus_audiobridge_init(janus_callbacks *callback, const char *config_path) { /* Setting function pointers according to runtime vectorization support (AVX2, SSE4.2 or scalar) */ init_limiter(); - JANUS_LOG(LOG_INFO, "Initialized optimized limiter implementation\n") + JANUS_LOG(LOG_INFO, "Initialized optimized limiter implementation\n"); /* Parse configuration to populate the rooms list */ if(config != NULL) { @@ -8576,7 +8576,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { int i=0; int count = 0, rf_count = 0, pf_count = 0, prev_count = 0; int lgain = 0, rgain = 0, diff = 0; - int mix_count = 0, sample_in_sub_frame = 0, sub_frame = 0; + int mix_count = 0, sample_in_sub_frame = 0; while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&audiobridge->destroyed)) { /* See if it's time to prepare a frame */ gettimeofday(&now, NULL); From 5bef9c08564ed4edba12f91e3e0a2d3db63535a7 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 28 Oct 2025 14:24:09 +0300 Subject: [PATCH 14/51] Added info log about used version of limiter (AVX2/SSE4.2 or scalar) --- src/plugins/audiobridge-deps/limiter/limiter.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index a9b5e1d642..e807194a9e 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -3,6 +3,8 @@ #include #include +#include "../../../debug.h" + /* SIMD intrinsics */ #if defined(__AVX2__) || defined(__SSE4_2__) #include @@ -296,16 +298,19 @@ inline __attribute__((always_inline)) void compute_scaling_factors( inline __attribute__((always_inline)) void init_limiter() { #if defined(__AVX2__) if (has_avx2()) { + JANUS_LOG(LOG_INFO, "Using AVX2 implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_avx2; return; } #endif #if defined(__SSE4_2__) if (has_sse42()) { + JANUS_LOG(LOG_INFO, "Using SSE4.2 implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_sse42; return; } #endif + JANUS_LOG(LOG_INFO, "Using scalar implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_scalar; } \ No newline at end of file From 7006830a59a38eba378ef0fc17593fb2bf05cbf9 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 28 Oct 2025 15:24:30 +0300 Subject: [PATCH 15/51] Adjust cpuid checks --- .../audiobridge-deps/limiter/limiter.c | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index e807194a9e..8f9dfd518c 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -12,20 +12,32 @@ int has_avx2() { unsigned int eax, ebx, ecx, edx; - // First check leaf 1 - if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { - if ((ecx & bit_OSXSAVE) && (ecx & bit_AVX)) { - if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) { - return (ebx & (1 << 5)) != 0; /* AVX2 — bit 5 of EBX */ - } - } - } - return 0; + /* 1. CPUID leaf 1: AVX + OSXSAVE */ + if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) + return 0; + + if (!(ecx & bit_AVX)) + return 0; + + if (!(ecx & bit_OSXSAVE)) + return 0; + + /* 2. Check tath OS saves XMM/YMM */ + unsigned long long xcr0 = __builtin_ia32_xgetbv(0); + if ((xcr0 & 0x6) != 0x6) + return 0; + + /* 3. CPUID leaf 7 subleaf 0: AVX2 */ + if (!__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) + return 0; + + /* AVX2 — bit 5 of EBX */ + return (ebx & (1u << 5)) != 0; } int has_sse42() { unsigned int eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { - return (ecx & (1 << 20)) != 0; /* SSE4.2 — bit 20 of ECX */ + return (ecx & (1u << 20)) != 0; /* SSE4.2 — bit 20 of ECX */ } return 0; } From f151694bde8736a6ad324f83c928b1adf1827f49 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 14:12:14 +0300 Subject: [PATCH 16/51] Update limiter.h and limiter.c --- .../audiobridge-deps/limiter/limiter.c | 604 +++++++++++++++++- .../audiobridge-deps/limiter/limiter.h | 45 +- 2 files changed, 598 insertions(+), 51 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 8f9dfd518c..49814c51af 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -43,7 +43,6 @@ int has_sse42() { } #endif - /* Static data for the limiter */ static float approximation_params_x[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { 30057.296875f, 30148.986328125f, 30240.67578125f, 30424.052734375f, @@ -89,9 +88,12 @@ static float approximation_params_q[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { 1.617647409439086914f, 1.604251742362976074f }; - -/* Function pointer for the selected implementation */ +/* Function pointers for the selected implementation */ static void (*compute_max_envelope_func)(opus_int32 *buffer, float *envelope, int samples_in_sub_frame) = NULL; +static void (*calculate_scaling_factors_func)(float *envelope, float *scaling_factors, float *last_scaling_factor) = NULL; +static void (*compute_per_sample_scaling_factors_func)(float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame) = NULL; +static void (*scale_buffer_func)(opus_int32 *buffer, int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer) = NULL; +static void (*clamp_buffer_func)(opus_int32 *buffer, int samples, opus_int16 *outBuffer) = NULL; #if defined(__AVX2__) void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ @@ -101,7 +103,7 @@ void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_ for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { const opus_int32 *sub_frame_buffer = &buffer[sub_frame * samples_in_sub_frame]; __m256i max_val = _mm256_setzero_si256(); - + /* Process 8 integers at a time */ int simd_samples = (samples_in_sub_frame / 8) * 8; for (sample_in_sub_frame = 0; sample_in_sub_frame < simd_samples; sample_in_sub_frame += 8) { @@ -109,21 +111,21 @@ void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_ __m256i abs_vals = _mm256_abs_epi32(vals); max_val = _mm256_max_epi32(max_val, abs_vals); } - + /* Extract maximum from the vector */ /* Get max of first 4 and last 4 elements */ __m128i max_low = _mm256_extracti128_si256(max_val, 0); __m128i max_high = _mm256_extracti128_si256(max_val, 1); __m128i max_4 = _mm_max_epi32(max_low, max_high); - + /* Get max of first 2 and last 2 elements */ __m128i max_2 = _mm_max_epi32(max_4, _mm_shuffle_epi32(max_4, _MM_SHUFFLE(1, 0, 3, 2))); - + /* Get max of first and last elements */ __m128i max_1 = _mm_max_epi32(max_2, _mm_shuffle_epi32(max_2, _MM_SHUFFLE(0, 0, 0, 1))); - + int max_scalar = _mm_cvtsi128_si32(max_1); - + /* Process remaining samples */ for (; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { int abs_val = abs(sub_frame_buffer[sample_in_sub_frame]); @@ -131,14 +133,354 @@ void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_ max_scalar = abs_val; } } - + if ((float)max_scalar > envelope[sub_frame]) { envelope[sub_frame] = (float)max_scalar; } } } +static inline __attribute__((always_inline)) void calculate_scaling_factors_avx2( + float *envelope, + float *scaling_factors, + float *last_scaling_factor) { + int i; + scaling_factors[0] = *last_scaling_factor; + + /* Constants for vectorized operations */ + const __m256 ones = _mm256_set1_ps(1.0f); + const __m256 threshold_low = _mm256_set1_ps(approximation_params_x[0]); + const __m256 threshold_high = _mm256_set1_ps(K_MAX_INPUT_LEVEL_LINEAR); + const __m256 scale_factor = _mm256_set1_ps(32768.0f); + + /* Process 8 elements at a time */ + for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 8; i += 8) { + /* Load 8 input levels */ + __m256 input_levels = _mm256_loadu_ps(&envelope[i]); + + /* Create masks for the three conditions */ + __m256 mask_low = _mm256_cmp_ps(input_levels, threshold_low, _CMP_LE_OQ); + __m256 mask_high = _mm256_cmp_ps(input_levels, threshold_high, _CMP_GE_OQ); + __m256 mask_mid = _mm256_andnot_ps(mask_low, _mm256_andnot_ps(mask_high, ones)); + + /* Calculate scaling factors for each case */ + /* Case 1: input_level <= approximation_params_x[0] -> scaling factor = 1.0f */ + __m256 result_low = ones; + + /* Case 2: input_level >= K_MAX_INPUT_LEVEL_LINEAR -> scaling factor = 32768.f / input_level */ + __m256 result_high = _mm256_div_ps(scale_factor, input_levels); + + /* Case 3: Middle region - use scalar processing for binary search */ + __m256 result_mid = _mm256_setzero_ps(); + + /* Handle middle region with scalar code (binary search cannot be easily vectorized) */ + float temp_input[8]; + float temp_result[8]; + _mm256_storeu_ps(temp_input, input_levels); + _mm256_storeu_ps(temp_result, result_mid); + + for (int j = 0; j < 8; j++) { + if (((float*)&mask_mid)[j] != 0.0f) { /* Check if mask is set for this element */ + const float input_level = temp_input[j]; + /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ + size_t left = 0; + size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + while (left < right) { + size_t mid = left + (right - left) / 2; + if (approximation_params_x[mid] < input_level) + left = mid + 1; + else + right = mid; + } + /* Now left points to first element that is >= input_level, we need a previous element */ + const size_t index = (left > 0) ? left - 1 : 0; + /* Piece-wise linear interploation. */ + const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; + temp_result[j] = gain; + } + } + + result_mid = _mm256_loadu_ps(temp_result); + + /* Combine results using masks */ + __m256 result = _mm256_blendv_ps( + _mm256_blendv_ps(result_mid, result_high, mask_high), + result_low, + mask_low + ); + + /* Store results */ + _mm256_storeu_ps(&scaling_factors[i + 1], result); + } + + /* Process remaining elements with scalar code */ + for (; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const float input_level = envelope[i]; + if (input_level <= approximation_params_x[0]) { + /* Identity region. */ + scaling_factors[i+1] = 1.0f; + } else if (input_level >= K_MAX_INPUT_LEVEL_LINEAR) { + /* Saturating lower bound. The saturing samples exactly hit the clipping level. + * This method achieves has the lowest harmonic distorsion, but it + * may reduce the amplitude of the non-saturating samples too much. + */ + scaling_factors[i+1] = 32768.f / input_level; + } else { + /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ + size_t left = 0; + size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + while (left < right) { + size_t mid = left + (right - left) / 2; + if (approximation_params_x[mid] < input_level) + left = mid + 1; + else + right = mid; + } + /* Now left points to first element that is >= input_level, we need a previous element */ + const size_t index = (left > 0) ? left - 1 : 0; + /* Piece-wise linear interploation. */ + const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; + scaling_factors[i+1] = gain; + } + } + + *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; +} + +static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors_avx2( + float *scaling_factors, + float *per_sample_scaling_factors, + int samples_in_sub_frame) { + + const int is_attack = scaling_factors[0] > scaling_factors[1]; + + /* Handle attack section with scalar code (powf is difficult to vectorize efficiently) */ + if (is_attack) { + for (int i = 0; i < samples_in_sub_frame; ++i) { + float t = (float)i / samples_in_sub_frame; + per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; + } + } + + /* Vectorized linear interpolation for the main loop */ + for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const int subframe_start = i * samples_in_sub_frame; + const float scaling_start = scaling_factors[i]; + const float scaling_end = scaling_factors[i + 1]; + const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; + + /* Vectorized processing - process 8 floats at a time */ + int j = 0; + const __m256 v_scaling_start = _mm256_set1_ps(scaling_start); + const __m256 v_scaling_diff = _mm256_set1_ps(scaling_diff); + + /* Process 8 elements at a time */ + for (; j <= samples_in_sub_frame - 8; j += 8) { + /* Create vector of indices [j, j+1, j+2, ..., j+7] */ + __m256i v_indices = _mm256_set_epi32(j+7, j+6, j+5, j+4, j+3, j+2, j+1, j); + __m256 v_indices_f = _mm256_cvtepi32_ps(v_indices); + + /* Calculate scaling_start + scaling_diff * j for all 8 elements */ + __m256 v_result = _mm256_fmadd_ps(v_scaling_diff, v_indices_f, v_scaling_start); + + /* Store results */ + _mm256_storeu_ps(&per_sample_scaling_factors[subframe_start + j], v_result); + } + + /* Handle remaining elements with scalar code */ + for (; j < samples_in_sub_frame; ++j) { + per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; + } + } +} + +static inline __attribute__((always_inline)) void scale_buffer_avx2( + opus_int32 *buffer, + int samples, + float *per_sample_scaling_factors, + opus_int16 *outBuffer){ + + int i = 0; + const __m256 v_half = _mm256_set1_ps(0.5f); + const __m256 v_min_val = _mm256_set1_ps(-32768.0f); + const __m256 v_max_val = _mm256_set1_ps(32767.0f); + + /* Process 8 elements at a time */ + for (; i <= samples - 8; i += 8) { + /* Load 8 integers from buffer and convert to floats */ + __m256i v_int_vals = _mm256_loadu_si256((__m256i*)&buffer[i]); + __m256 v_buf_vals = _mm256_cvtepi32_ps(v_int_vals); + + /* Load 8 scaling factors */ + __m256 v_scale_vals = _mm256_loadu_ps(&per_sample_scaling_factors[i]); + + /* Multiply buffer values with scaling factors */ + __m256 v_mult_result = _mm256_mul_ps(v_buf_vals, v_scale_vals); + + /* Add 0.5f for rounding */ + __m256 v_rounded = _mm256_add_ps(v_mult_result, v_half); + + /* Clamp values to [-32768, 32767] */ + v_rounded = _mm256_max_ps(v_rounded, v_min_val); + v_rounded = _mm256_min_ps(v_rounded, v_max_val); + + /* Convert to integers */ + __m256i v_int_result = _mm256_cvtps_epi32(v_rounded); + + /* Pack 32-bit integers to 16-bit integers */ + /* First, pack the lower 4 32-bit values */ + __m128i v_low = _mm256_extracti128_si256(v_int_result, 0); + __m128i v_high = _mm256_extracti128_si256(v_int_result, 1); + __m128i v_packed = _mm_packs_epi32(v_low, v_high); + + /* Store the 8 16-bit integers */ + _mm_storeu_si128((__m128i*)&outBuffer[i], v_packed); + } + + /* Handle remaining elements with scalar code */ + for (; i < samples; i++) { + opus_int32 sample = (opus_int32)(buffer[i] * per_sample_scaling_factors[i] + 0.5f); + if(sample > 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = sample; + } +} +static inline __attribute__((always_inline)) void clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ + int i = 0; + const __m256i v_min_val = _mm256_set1_epi32(-32768); + const __m256i v_max_val = _mm256_set1_epi32(32767); + + /* Process 8 elements at a time */ + for (; i <= samples - 8; i += 8) { + /* Load 8 integers from buffer */ + __m256i v_int_vals = _mm256_loadu_si256((__m256i*)&buffer[i]); + + /* Clamp values to [-32768, 32767] */ + v_int_vals = _mm256_max_epi32(v_int_vals, v_min_val); + v_int_vals = _mm256_min_epi32(v_int_vals, v_max_val); + + /* Pack 32-bit integers to 16-bit integers */ + /* First, pack the lower 4 32-bit values */ + __m128i v_low = _mm256_extracti128_si256(v_int_vals, 0); + __m128i v_high = _mm256_extracti128_si256(v_int_vals, 1); + __m128i v_packed = _mm_packs_epi32(v_low, v_high); + + /* Store the 8 16-bit integers */ + _mm_storeu_si128((__m128i*)&outBuffer[i], v_packed); + } + + /* Handle remaining elements with scalar code */ + for (; i < samples; i++) { + opus_int32 sample = buffer[i]; + if(sample > 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = (opus_int16)sample; + } +} #endif #if defined(__SSE4_2__) +static inline __attribute__((always_inline)) void scale_buffer_sse42( + opus_int32 *buffer, + int samples, + float *per_sample_scaling_factors, + opus_int16 *outBuffer){ + + int i = 0; + const __m128 v_half = _mm_set1_ps(0.5f); + const __m128 v_min_val = _mm_set1_ps(-32768.0f); + const __m128 v_max_val = _mm_set1_ps(32767.0f); + + /* Process 4 elements at a time */ + for (; i <= samples - 4; i += 4) { + /* Load 4 integers from buffer and convert to floats */ + __m128i v_int_vals = _mm_loadu_si128((__m128i*)&buffer[i]); + __m128 v_buf_vals = _mm_cvtepi32_ps(v_int_vals); + + /* Load 4 scaling factors */ + __m128 v_scale_vals = _mm_loadu_ps(&per_sample_scaling_factors[i]); + + /* Multiply buffer values with scaling factors */ + __m128 v_mult_result = _mm_mul_ps(v_buf_vals, v_scale_vals); + + /* Add 0.5f for rounding */ + __m128 v_rounded = _mm_add_ps(v_mult_result, v_half); + + /* Clamp values to [-32768, 32767] */ + v_rounded = _mm_max_ps(v_rounded, v_min_val); + v_rounded = _mm_min_ps(v_rounded, v_max_val); + + /* Convert to integers */ + __m128i v_int_result = _mm_cvtps_epi32(v_rounded); + + /* Pack 32-bit integers to 16-bit integers */ + __m128i v_low = v_int_result; + __m128i v_high = _mm_shuffle_epi32(v_int_result, _MM_SHUFFLE(3, 2, 3, 2)); + __m128i v_packed = _mm_packs_epi32(v_low, v_high); + + /* Store the 4 16-bit integers */ + _mm_storel_epi64((__m128i*)&outBuffer[i], v_packed); + } + + /* Handle remaining elements with scalar code */ + for (; i < samples; i++) { + opus_int32 sample = (opus_int32)(buffer[i] * per_sample_scaling_factors[i] + 0.5f); + if(sample > 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = sample; + } +} + +static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors_sse42( + float *scaling_factors, + float *per_sample_scaling_factors, + int samples_in_sub_frame) { + + const int is_attack = scaling_factors[0] > scaling_factors[1]; + + /* Handle attack section with scalar code (powf is difficult to vectorize efficiently) */ + if (is_attack) { + for (int i = 0; i < samples_in_sub_frame; ++i) { + float t = (float)i / samples_in_sub_frame; + per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; + } + } + + /* Vectorized linear interpolation for the main loop */ + for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const int subframe_start = i * samples_in_sub_frame; + const float scaling_start = scaling_factors[i]; + const float scaling_end = scaling_factors[i + 1]; + const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; + + /* Vectorized processing - process 4 floats at a time */ + int j = 0; + const __m128 v_scaling_start = _mm_set1_ps(scaling_start); + const __m128 v_scaling_diff = _mm_set1_ps(scaling_diff); + + /* Process 4 elements at a time */ + for (; j <= samples_in_sub_frame - 4; j += 4) { + /* Create vector of indices [j, j+1, j+2, j+3] */ + __m128i v_indices = _mm_set_epi32(j+3, j+2, j+1, j); + __m128 v_indices_f = _mm_cvtepi32_ps(v_indices); + + /* Calculate scaling_start + scaling_diff * j for all 4 elements */ + __m128 v_result = _mm_add_ps(v_scaling_start, _mm_mul_ps(v_scaling_diff, v_indices_f)); + + /* Store results */ + _mm_storeu_ps(&per_sample_scaling_factors[subframe_start + j], v_result); + } + + /* Handle remaining elements with scalar code */ + for (; j < samples_in_sub_frame; ++j) { + per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; + } + } +} void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; @@ -146,26 +488,26 @@ void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { const opus_int32 *sub_frame_buffer = &buffer[sub_frame * samples_in_sub_frame]; __m128 max_val = _mm_setzero_ps(); - + /* Process 4 floats at a time */ int simd_samples = (samples_in_sub_frame / 4) * 4; for (sample_in_sub_frame = 0; sample_in_sub_frame < simd_samples; sample_in_sub_frame += 4) { /* Load 4 integers and convert to floats */ __m128i vals_i = _mm_loadu_si128((__m128i*)&sub_frame_buffer[sample_in_sub_frame]); __m128 vals = _mm_cvtepi32_ps(vals_i); - + /* Take absolute values */ __m128 abs_vals = _mm_andnot_ps(_mm_set1_ps(-0.0f), vals); /* Clear sign bit */ - + max_val = _mm_max_ps(max_val, abs_vals); } - + /* Extract maximum from the vector */ __m128 max_2 = _mm_max_ps(max_val, _mm_shuffle_ps(max_val, max_val, _MM_SHUFFLE(1, 0, 3, 2))); __m128 max_1 = _mm_max_ss(max_2, _mm_shuffle_ps(max_2, max_2, _MM_SHUFFLE(0, 0, 0, 1))); - + float max_scalar = _mm_cvtss_f32(max_1); - + /* Process remaining samples */ for (; sample_in_sub_frame < samples_in_sub_frame; ++sample_in_sub_frame) { float abs_val = (float)abs(sub_frame_buffer[sample_in_sub_frame]); @@ -173,14 +515,155 @@ void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples max_scalar = abs_val; } } - + if (max_scalar > envelope[sub_frame]) { envelope[sub_frame] = max_scalar; } } } -#endif +static inline __attribute__((always_inline)) void calculate_scaling_factors_sse42( + float *envelope, + float *scaling_factors, + float *last_scaling_factor) { + + int i; + + scaling_factors[0] = *last_scaling_factor; + + /* Constants for vectorized operations */ + const __m128 ones = _mm_set1_ps(1.0f); + const __m128 threshold_low = _mm_set1_ps(approximation_params_x[0]); + const __m128 threshold_high = _mm_set1_ps(K_MAX_INPUT_LEVEL_LINEAR); + const __m128 scale_factor = _mm_set1_ps(32768.0f); + + /* Process 4 elements at a time */ + for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 4; i += 4) { + /* Load 4 input levels */ + __m128 input_levels = _mm_loadu_ps(&envelope[i]); + + /* Create masks for the three conditions */ + __m128 mask_low = _mm_cmp_ps(input_levels, threshold_low, _CMP_LE_OQ); + __m128 mask_high = _mm_cmp_ps(input_levels, threshold_high, _CMP_GE_OQ); + __m128 mask_mid = _mm_andnot_ps(mask_low, _mm_andnot_ps(mask_high, ones)); + /* Calculate scaling factors for each case */ + /* Case 1: input_level <= approximation_params_x[0] -> scaling factor = 1.0f */ + __m128 result_low = ones; + + /* Case 2: input_level >= K_MAX_INPUT_LEVEL_LINEAR -> scaling factor = 32768.f / input_level */ + __m128 result_high = _mm_div_ps(scale_factor, input_levels); + + /* Case 3: Middle region - use scalar processing for binary search */ + __m128 result_mid = _mm_setzero_ps(); + + /* Handle middle region with scalar code (binary search cannot be easily vectorized) */ + float temp_input[4]; + float temp_result[4]; + _mm_storeu_ps(temp_input, input_levels); + _mm_storeu_ps(temp_result, result_mid); + + for (int j = 0; j < 4; j++) { + if (((float*)&mask_mid)[j] != 0.0f) { /* Check if mask is set for this element */ + const float input_level = temp_input[j]; + /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ + size_t left = 0; + size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + while (left < right) { + size_t mid = left + (right - left) / 2; + if (approximation_params_x[mid] < input_level) + left = mid + 1; + else + right = mid; + } + /* Now left points to first element that is >= input_level, we need a previous element */ + const size_t index = (left > 0) ? left - 1 : 0; + /* Piece-wise linear interploation. */ + const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; + temp_result[j] = gain; + } + } + + result_mid = _mm_loadu_ps(temp_result); + + /* Combine results using masks */ + __m128 result = _mm_blendv_ps( + _mm_blendv_ps(result_mid, result_high, mask_high), + result_low, + mask_low + ); + + /* Store results */ + _mm_storeu_ps(&scaling_factors[i + 1], result); + } + + /* Process remaining elements with scalar code */ + for (; i < K_SUB_FRAMES_IN_FRAME; ++i) { + const float input_level = envelope[i]; + if (input_level <= approximation_params_x[0]) { + /* Identity region. */ + scaling_factors[i+1] = 1.0f; + } else if (input_level >= K_MAX_INPUT_LEVEL_LINEAR) { + /* Saturating lower bound. The saturing samples exactly hit the clipping level. + * This method achieves has the lowest harmonic distorsion, but it + * may reduce the amplitude of the non-saturating samples too much. + */ + scaling_factors[i+1] = 32768.f / input_level; + } else { + /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ + size_t left = 0; + size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + while (left < right) { + size_t mid = left + (right - left) / 2; + if (approximation_params_x[mid] < input_level) + left = mid + 1; + else + right = mid; + } + /* Now left points to first element that is >= input_level, we need a previous element */ + const size_t index = (left > 0) ? left - 1 : 0; + /* Piece-wise linear interploation. */ + const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; + scaling_factors[i+1] = gain; + } + } + + *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; +} + +static inline __attribute__((always_inline)) void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ + int i = 0; + const __m128i v_min_val = _mm_set1_epi32(-32768); + const __m128i v_max_val = _mm_set1_epi32(32767); + + /* Process 4 elements at a time */ + for (; i <= samples - 4; i += 4) { + /* Load 4 integers from buffer */ + __m128i v_int_vals = _mm_loadu_si128((__m128i*)&buffer[i]); + + /* Clamp values to [-32768, 32767] */ + v_int_vals = _mm_max_epi32(v_int_vals, v_min_val); + v_int_vals = _mm_min_epi32(v_int_vals, v_max_val); + + /* Pack 32-bit integers to 16-bit integers */ + __m128i v_low = v_int_vals; + __m128i v_high = _mm_shuffle_epi32(v_int_vals, _MM_SHUFFLE(3, 2, 3, 2)); + __m128i v_packed = _mm_packs_epi32(v_low, v_high); + + /* Store the 4 16-bit integers */ + _mm_storel_epi64((__m128i*)&outBuffer[i], v_packed); + } + + /* Handle remaining elements with scalar code */ + for (; i < samples; i++) { + opus_int32 sample = buffer[i]; + if(sample > 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = (opus_int16)sample; + } +} +#endif static void compute_max_envelope_scalar(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ @@ -199,7 +682,7 @@ static inline __attribute__((always_inline)) void compute_envelope( float *filter_state_level) { int sub_frame, sample_in_sub_frame; compute_max_envelope_func(buffer, envelope, samples_in_sub_frame); - + /* Make sure envelope increases happen one step earlier so that the * corresponding *gain decrease* doesn't miss a sudden signal * increase due to interpolation. @@ -208,7 +691,7 @@ static inline __attribute__((always_inline)) void compute_envelope( if (envelope[sub_frame] < envelope[sub_frame + 1]) envelope[sub_frame] = envelope[sub_frame + 1]; } - + /* Add attack / decay smoothing. */ for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { const float envelope_value = envelope[sub_frame]; @@ -220,11 +703,11 @@ static inline __attribute__((always_inline)) void compute_envelope( *filter_state_level = envelope[sub_frame]; } } -static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors( +static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors_scalar( float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame) { - + const int is_attack = scaling_factors[0] > scaling_factors[1]; if (is_attack) { for (int i = 0; i < samples_in_sub_frame; ++i) { @@ -232,29 +715,29 @@ static inline __attribute__((always_inline)) void compute_per_sample_scaling_fac per_sample_scaling_factors[i] = powf(1.0f - t, K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER) * (scaling_factors[0] - scaling_factors[1]) + scaling_factors[1]; } } - + for (int i = is_attack ? 1 : 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { const int subframe_start = i * samples_in_sub_frame; const float scaling_start = scaling_factors[i]; const float scaling_end = scaling_factors[i + 1]; const float scaling_diff = (scaling_end - scaling_start) / samples_in_sub_frame; - + for (int j = 0; j < samples_in_sub_frame; ++j) { per_sample_scaling_factors[subframe_start + j] = scaling_start + scaling_diff * j; } } } -static inline __attribute__((always_inline)) void calculate_scaling_factors( +static inline __attribute__((always_inline)) void calculate_scaling_factors_scalar( float *envelope, float *scaling_factors, float *last_scaling_factor) { - + int i; - + scaling_factors[0] = *last_scaling_factor; for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { - const float input_level = envelope[i]; + const float input_level = envelope[i]; if (input_level <= approximation_params_x[0]) { /* Identity region. */ scaling_factors[i+1] = 1.0f; @@ -282,7 +765,7 @@ static inline __attribute__((always_inline)) void calculate_scaling_factors( scaling_factors[i+1] = gain; } } - + *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; } @@ -294,17 +777,58 @@ inline __attribute__((always_inline)) void compute_scaling_factors( int samples_in_sub_frame, float *filter_state_level, float *last_scaling_factor) { - int sub_frame, sample_in_sub_frame; - /* * Calculating gain factors for limiter (adapted from WebRTC project). * Original WebRTC code: https://webrtc.googlesource.com/src * Licensed under BSD 3-Clause License. */ compute_envelope(buffer, envelope, samples_in_sub_frame, filter_state_level); - calculate_scaling_factors(envelope, scaling_factors, last_scaling_factor); - compute_per_sample_scaling_factors(scaling_factors, per_sample_scaling_factors, samples_in_sub_frame); + calculate_scaling_factors_func(envelope, scaling_factors, last_scaling_factor); + compute_per_sample_scaling_factors_func(scaling_factors, per_sample_scaling_factors, samples_in_sub_frame); +} + +static inline __attribute__((always_inline)) void scale_buffer_scalar( + opus_int32 *buffer, + int samples, + float *per_sample_scaling_factors, + opus_int16 *outBuffer){ + int i; + opus_int32 sample; + for(i=0; i 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = sample; + } +} + +inline __attribute__((always_inline)) void scale_buffer( + opus_int32 *buffer, + int samples, + float *per_sample_scaling_factors, + opus_int16 *outBuffer){ + scale_buffer_func(buffer, samples, per_sample_scaling_factors, outBuffer); +} + + +static inline __attribute__((always_inline)) void clamp_buffer_scalar(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ + int i; + opus_int32 sample; + for(i=0; i 32767) + sample = 32767; + else if(sample < -32768) + sample = -32768; + outBuffer[i] = (opus_int16)sample; + } +} + +inline __attribute__((always_inline)) void clamp_buffer(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ + clamp_buffer_func(buffer, samples, outBuffer); } inline __attribute__((always_inline)) void init_limiter() { @@ -312,6 +836,10 @@ inline __attribute__((always_inline)) void init_limiter() { if (has_avx2()) { JANUS_LOG(LOG_INFO, "Using AVX2 implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_avx2; + calculate_scaling_factors_func = calculate_scaling_factors_avx2; + compute_per_sample_scaling_factors_func = compute_per_sample_scaling_factors_avx2; + scale_buffer_func = scale_buffer_avx2; + clamp_buffer_func = clamp_buffer_avx2; return; } #endif @@ -319,10 +847,18 @@ inline __attribute__((always_inline)) void init_limiter() { if (has_sse42()) { JANUS_LOG(LOG_INFO, "Using SSE4.2 implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_sse42; + calculate_scaling_factors_func = calculate_scaling_factors_sse42; + compute_per_sample_scaling_factors_func = compute_per_sample_scaling_factors_sse42; + scale_buffer_func = scale_buffer_sse42; + clamp_buffer_func = clamp_buffer_sse42; return; } #endif JANUS_LOG(LOG_INFO, "Using scalar implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_scalar; -} \ No newline at end of file + calculate_scaling_factors_func = calculate_scaling_factors_scalar; + compute_per_sample_scaling_factors_func = compute_per_sample_scaling_factors_scalar; + scale_buffer_func = scale_buffer_scalar; + clamp_buffer_func = clamp_buffer_scalar; +} diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index ed74a06d21..85251402fc 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -8,28 +8,31 @@ /* Constants for the audio limiter */ #define K_SUB_FRAMES_IN_FRAME 20 #define K_INITIAL_FILTER_STATE_LEVEL 0.0f -// Instant attack. +/* Instant attack. */ #define K_ATTACK_FILTER_CONSTANT 0.0f -// This constant affects the way scaling factors are interpolated for the first -// sub-frame of a frame. Only in the case in which the first sub-frame has an -// estimated level which is greater than the that of the previous analyzed -// sub-frame, linear interpolation is replaced with a power function which -// reduces the chances of over-shooting (and hence saturation), however reducing -// the fixed gain effectiveness. +/* This constant affects the way scaling factors are interpolated for the first + * sub-frame of a frame. Only in the case in which the first sub-frame has an + * estimated level which is greater than the that of the previous analyzed + * sub-frame, linear interpolation is replaced with a power function which + * reduces the chances of over-shooting (and hence saturation), however reducing + * the fixed gain effectiveness. + */ #define K_ATTACK_FIRST_SUB_FRAME_INTERPOLATION_POWER 8.0f -// Limiter decay constant. -// Computed as `10 ** (-1/20 * SUBFRAME_DURATION / K_DECAY_MS)` where: -// - `SUBFRAME_DURATION` is `K_FRAME_DURATION_MS / K_SUB_FRAMES_IN_FRAME`; -// - `K_FRAME_DURATION_MS` is 10 ms.; -// - `K_DECAY_MS` is 20.0f; +/* Limiter decay constant. + * Computed as `10 ** (-1/20 * SUBFRAME_DURATION / K_DECAY_MS)` where: + * - `SUBFRAME_DURATION` is `K_FRAME_DURATION_MS / K_SUB_FRAMES_IN_FRAME`; + * - `K_FRAME_DURATION_MS` is 10 ms.; + * - `K_DECAY_MS` is 20.0f; + */ #define K_DECAY_FILTER_CONSTANT 0.9971259f -// Number of interpolation points for each region of the limiter. -// These values have been tuned to limit the interpolated gain curve error given -// the limiter parameters and allowing a maximum error of +/- 32768^-1. +/* Number of interpolation points for each region of the limiter. + * These values have been tuned to limit the interpolated gain curve error given + * the limiter parameters and allowing a maximum error of +/- 32768^-1. + */ #define K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS 22 #define K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS 10 #define K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS (K_INTERPOLATED_GAIN_CURVE_KNEE_POINTS + K_INTERPOLATED_GAIN_CURVE_BEYOND_KNEE_POINTS) -// Defined as DbfsToLinear(kLimiterMaxInputLevelDbFs) +/* Defined as DbfsToLinear(kLimiterMaxInputLevelDbFs) */ #define K_MAX_INPUT_LEVEL_LINEAR 36766.300710566735f void compute_scaling_factors( @@ -42,4 +45,12 @@ void compute_scaling_factors( float *last_scaling_factor); void init_limiter(); -#endif // LIMITER_H + +void scale_buffer( + opus_int32 *buffer, + int samples, + float *per_sample_scaling_factors, + opus_int16 *outBuffer); + +void clamp_buffer(opus_int32 *buffer, int samples, opus_int16 *outBuffer); +#endif /* LIMITER_H */ \ No newline at end of file From bf81fe2ce335be289eed17fbcea8192d963d7076 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 14:21:46 +0300 Subject: [PATCH 17/51] use scale_buffer and clamp_buffer --- src/plugins/janus_audiobridge.c | 55 +++++++-------------------------- 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index b6572d0f4c..436618502a 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8535,16 +8535,14 @@ static void *janus_audiobridge_mixer_thread(void *data) { } /* For audio limiter we split frame in subframes */ - int samples_in_sub_frame = 0; + int samples_in_sub_frame = samples / K_SUB_FRAMES_IN_FRAME; float *per_sample_scaling_factors = NULL; float *envelope = NULL; float *scaling_factors = NULL; float filter_state_level = K_INITIAL_FILTER_STATE_LEVEL; float last_scaling_factor = 1.f; - opus_int32 sample = 0; - /* In case audio limiter enabled, we need a buffer for it and some values */ + /* In case audio limiter enabled, we need some buffers for it */ if (audiobridge->use_limiter) { - samples_in_sub_frame = samples / K_SUB_FRAMES_IN_FRAME; per_sample_scaling_factors = g_malloc0((audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES) * sizeof(float)); envelope = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); scaling_factors = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); @@ -8576,7 +8574,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { int i=0; int count = 0, rf_count = 0, pf_count = 0, prev_count = 0; int lgain = 0, rgain = 0, diff = 0; - int mix_count = 0, sample_in_sub_frame = 0; + int mix_count = 0; while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&audiobridge->destroyed)) { /* See if it's time to prepare a frame */ gettimeofday(&now, NULL); @@ -8877,27 +8875,12 @@ static void *janus_audiobridge_mixer_thread(void *data) { samples_in_sub_frame, &filter_state_level, &last_scaling_factor); /* Are we recording the mix? (only do it if there's someone in, though...) */ if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { - /* If we use limiter and we mixed more than 1 track, apply it */ if (audiobridge->use_limiter && mix_count > 1) { - for(i=0; i 32767) - sample = 32767; - else if(sample < -32768) - sample = -32768; - outBuffer[i] = sample; - } - } - else { - /* Clamp values that are outside int16 boundaries */ - for(i=0; i 32767) - sample = 32767; - else if(sample < -32768) - sample = -32768; - outBuffer[i] = sample; - } + /* If we use limiter and we mixed more than 1 track, apply it */ + scale_buffer(buffer, samples, per_sample_scaling_factors, outBuffer); + } else { + /* Otherwise just clamp values that are outside int16 boundaries */ + clamp_buffer(buffer, samples, outBuffer); } fwrite(outBuffer, sizeof(opus_int16), samples, audiobridge->recording); /* Every 5 seconds we update the wav header */ @@ -8967,26 +8950,12 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } - /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ if(audiobridge->use_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { - for(i=0; i 32767) - sample = 32767; - else if(sample < -32768) - sample = -32768; - outBuffer[i] = sample; - } + /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ + scale_buffer(sumBuffer, samples, per_sample_scaling_factors, outBuffer); } else { - /* Clamp values that are outside int16 boundaries */ - for(i=0; i 32767) - sample = 32767; - else if(sample < -32768) - sample = -32768; - outBuffer[i] = sample; - } + /* Otherwise just clamp values that are outside int16 boundaries */ + clamp_buffer(sumBuffer, samples, outBuffer); } /* Enqueue this mixed frame for encoding in the participant thread */ From f531e0e0ebe711c980ea6ea38e657170ec9cbdab Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 14:37:02 +0300 Subject: [PATCH 18/51] fix declarations --- src/plugins/audiobridge-deps/limiter/limiter.c | 4 ++-- src/plugins/audiobridge-deps/limiter/limiter.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 49814c51af..5760a5fa14 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -10,7 +10,7 @@ #include #include -int has_avx2() { +int has_avx2(void) { unsigned int eax, ebx, ecx, edx; /* 1. CPUID leaf 1: AVX + OSXSAVE */ if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) @@ -34,7 +34,7 @@ int has_avx2() { /* AVX2 — bit 5 of EBX */ return (ebx & (1u << 5)) != 0; } -int has_sse42() { +int has_sse42(void) { unsigned int eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { return (ecx & (1u << 20)) != 0; /* SSE4.2 — bit 20 of ECX */ diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index 85251402fc..85cbca7114 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -44,7 +44,7 @@ void compute_scaling_factors( float *filter_state_level, float *last_scaling_factor); -void init_limiter(); +void init_limiter(void); void scale_buffer( opus_int32 *buffer, From 8f9dc93836a28f24d8617fda333cf8d7a895be8a Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 14:38:31 +0300 Subject: [PATCH 19/51] fix declarations --- src/plugins/audiobridge-deps/limiter/limiter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 5760a5fa14..cd8f4b115d 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -831,7 +831,7 @@ inline __attribute__((always_inline)) void clamp_buffer(opus_int32 *buffer, int clamp_buffer_func(buffer, samples, outBuffer); } -inline __attribute__((always_inline)) void init_limiter() { +inline __attribute__((always_inline)) void init_limiter(void) { #if defined(__AVX2__) if (has_avx2()) { JANUS_LOG(LOG_INFO, "Using AVX2 implementation of limiter\n"); From c91af23b8a59ee6d794560b9905350257e63b3bb Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 14:50:24 +0300 Subject: [PATCH 20/51] Cleanup --- .../audiobridge-deps/limiter/limiter.c | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index cd8f4b115d..2cdfcd690d 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -10,7 +10,7 @@ #include #include -int has_avx2(void) { +static int has_avx2(void) { unsigned int eax, ebx, ecx, edx; /* 1. CPUID leaf 1: AVX + OSXSAVE */ if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx)) @@ -34,7 +34,7 @@ int has_avx2(void) { /* AVX2 — bit 5 of EBX */ return (ebx & (1u << 5)) != 0; } -int has_sse42(void) { +static int has_sse42(void) { unsigned int eax, ebx, ecx, edx; if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { return (ecx & (1u << 20)) != 0; /* SSE4.2 — bit 20 of ECX */ @@ -96,7 +96,7 @@ static void (*scale_buffer_func)(opus_int32 *buffer, int samples, float *per_sam static void (*clamp_buffer_func)(opus_int32 *buffer, int samples, opus_int16 *outBuffer) = NULL; #if defined(__AVX2__) -void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ +static void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; /* AVX2 implementation - process 8 32-bit integers at a time */ @@ -139,7 +139,7 @@ void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_ } } } -static inline __attribute__((always_inline)) void calculate_scaling_factors_avx2( +static void calculate_scaling_factors_avx2( float *envelope, float *scaling_factors, float *last_scaling_factor) { @@ -246,11 +246,10 @@ static inline __attribute__((always_inline)) void calculate_scaling_factors_avx2 *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; } -static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors_avx2( +static void compute_per_sample_scaling_factors_avx2( float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame) { - const int is_attack = scaling_factors[0] > scaling_factors[1]; /* Handle attack section with scalar code (powf is difficult to vectorize efficiently) */ @@ -293,7 +292,7 @@ static inline __attribute__((always_inline)) void compute_per_sample_scaling_fac } } -static inline __attribute__((always_inline)) void scale_buffer_avx2( +static void scale_buffer_avx2( opus_int32 *buffer, int samples, float *per_sample_scaling_factors, @@ -346,7 +345,7 @@ static inline __attribute__((always_inline)) void scale_buffer_avx2( outBuffer[i] = sample; } } -static inline __attribute__((always_inline)) void clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ +static void clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i = 0; const __m256i v_min_val = _mm256_set1_epi32(-32768); const __m256i v_max_val = _mm256_set1_epi32(32767); @@ -382,7 +381,7 @@ static inline __attribute__((always_inline)) void clamp_buffer_avx2(opus_int32 * } #endif #if defined(__SSE4_2__) -static inline __attribute__((always_inline)) void scale_buffer_sse42( +static void scale_buffer_sse42( opus_int32 *buffer, int samples, float *per_sample_scaling_factors, @@ -435,7 +434,7 @@ static inline __attribute__((always_inline)) void scale_buffer_sse42( } } -static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors_sse42( +static void compute_per_sample_scaling_factors_sse42( float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame) { @@ -481,7 +480,7 @@ static inline __attribute__((always_inline)) void compute_per_sample_scaling_fac } } } -void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ +static void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; /* SSE4.2 implementation - process 4 floats at a time */ @@ -521,13 +520,11 @@ void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples } } } -static inline __attribute__((always_inline)) void calculate_scaling_factors_sse42( +static void calculate_scaling_factors_sse42( float *envelope, float *scaling_factors, float *last_scaling_factor) { - int i; - scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ @@ -630,7 +627,7 @@ static inline __attribute__((always_inline)) void calculate_scaling_factors_sse4 *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; } -static inline __attribute__((always_inline)) void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ +static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i = 0; const __m128i v_min_val = _mm_set1_epi32(-32768); const __m128i v_max_val = _mm_set1_epi32(32767); @@ -703,7 +700,7 @@ static inline __attribute__((always_inline)) void compute_envelope( *filter_state_level = envelope[sub_frame]; } } -static inline __attribute__((always_inline)) void compute_per_sample_scaling_factors_scalar( +static void compute_per_sample_scaling_factors_scalar( float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame) { @@ -728,7 +725,7 @@ static inline __attribute__((always_inline)) void compute_per_sample_scaling_fac } } -static inline __attribute__((always_inline)) void calculate_scaling_factors_scalar( +static void calculate_scaling_factors_scalar( float *envelope, float *scaling_factors, float *last_scaling_factor) { @@ -788,7 +785,7 @@ inline __attribute__((always_inline)) void compute_scaling_factors( compute_per_sample_scaling_factors_func(scaling_factors, per_sample_scaling_factors, samples_in_sub_frame); } -static inline __attribute__((always_inline)) void scale_buffer_scalar( +static void scale_buffer_scalar( opus_int32 *buffer, int samples, float *per_sample_scaling_factors, @@ -814,7 +811,7 @@ inline __attribute__((always_inline)) void scale_buffer( } -static inline __attribute__((always_inline)) void clamp_buffer_scalar(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ +static void clamp_buffer_scalar(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i; opus_int32 sample; for(i=0; i Date: Wed, 29 Oct 2025 15:43:03 +0300 Subject: [PATCH 21/51] cleanup --- src/plugins/audiobridge-deps/limiter/limiter.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 2cdfcd690d..fbd0fd0c42 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -143,7 +143,7 @@ static void calculate_scaling_factors_avx2( float *envelope, float *scaling_factors, float *last_scaling_factor) { - int i; + size_t i; scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ @@ -524,7 +524,7 @@ static void calculate_scaling_factors_sse42( float *envelope, float *scaling_factors, float *last_scaling_factor) { - int i; + size_t i; scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ @@ -628,7 +628,7 @@ static void calculate_scaling_factors_sse42( } static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ - int i = 0; + size_t i = 0; const __m128i v_min_val = _mm_set1_epi32(-32768); const __m128i v_max_val = _mm_set1_epi32(32767); @@ -677,7 +677,7 @@ static inline __attribute__((always_inline)) void compute_envelope( float *envelope, int samples_in_sub_frame, float *filter_state_level) { - int sub_frame, sample_in_sub_frame; + int sub_frame; compute_max_envelope_func(buffer, envelope, samples_in_sub_frame); /* Make sure envelope increases happen one step earlier so that the @@ -729,9 +729,7 @@ static void calculate_scaling_factors_scalar( float *envelope, float *scaling_factors, float *last_scaling_factor) { - - int i; - + size_t i; scaling_factors[0] = *last_scaling_factor; for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { const float input_level = envelope[i]; @@ -774,7 +772,6 @@ inline __attribute__((always_inline)) void compute_scaling_factors( int samples_in_sub_frame, float *filter_state_level, float *last_scaling_factor) { - int sub_frame, sample_in_sub_frame; /* * Calculating gain factors for limiter (adapted from WebRTC project). * Original WebRTC code: https://webrtc.googlesource.com/src @@ -790,7 +787,7 @@ static void scale_buffer_scalar( int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer){ - int i; + size_t i; opus_int32 sample; for(i=0; i Date: Wed, 29 Oct 2025 15:51:37 +0300 Subject: [PATCH 22/51] use int --- .../audiobridge-deps/limiter/limiter.c | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index fbd0fd0c42..dc7e1240ff 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -143,7 +143,7 @@ static void calculate_scaling_factors_avx2( float *envelope, float *scaling_factors, float *last_scaling_factor) { - size_t i; + int i; scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ @@ -182,17 +182,17 @@ static void calculate_scaling_factors_avx2( if (((float*)&mask_mid)[j] != 0.0f) { /* Check if mask is set for this element */ const float input_level = temp_input[j]; /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ - size_t left = 0; - size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + int left = 0; + int right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; while (left < right) { - size_t mid = left + (right - left) / 2; + int mid = left + (right - left) / 2; if (approximation_params_x[mid] < input_level) left = mid + 1; else right = mid; } /* Now left points to first element that is >= input_level, we need a previous element */ - const size_t index = (left > 0) ? left - 1 : 0; + const int index = (left > 0) ? left - 1 : 0; /* Piece-wise linear interploation. */ const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; temp_result[j] = gain; @@ -226,17 +226,17 @@ static void calculate_scaling_factors_avx2( scaling_factors[i+1] = 32768.f / input_level; } else { /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ - size_t left = 0; - size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + int left = 0; + int right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; while (left < right) { - size_t mid = left + (right - left) / 2; + int mid = left + (right - left) / 2; if (approximation_params_x[mid] < input_level) left = mid + 1; else right = mid; } /* Now left points to first element that is >= input_level, we need a previous element */ - const size_t index = (left > 0) ? left - 1 : 0; + const int index = (left > 0) ? left - 1 : 0; /* Piece-wise linear interploation. */ const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; scaling_factors[i+1] = gain; @@ -524,7 +524,7 @@ static void calculate_scaling_factors_sse42( float *envelope, float *scaling_factors, float *last_scaling_factor) { - size_t i; + int i; scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ @@ -563,17 +563,17 @@ static void calculate_scaling_factors_sse42( if (((float*)&mask_mid)[j] != 0.0f) { /* Check if mask is set for this element */ const float input_level = temp_input[j]; /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ - size_t left = 0; - size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + int left = 0; + int right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; while (left < right) { - size_t mid = left + (right - left) / 2; + int mid = left + (right - left) / 2; if (approximation_params_x[mid] < input_level) left = mid + 1; else right = mid; } /* Now left points to first element that is >= input_level, we need a previous element */ - const size_t index = (left > 0) ? left - 1 : 0; + const int index = (left > 0) ? left - 1 : 0; /* Piece-wise linear interploation. */ const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; temp_result[j] = gain; @@ -607,17 +607,17 @@ static void calculate_scaling_factors_sse42( scaling_factors[i+1] = 32768.f / input_level; } else { /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ - size_t left = 0; - size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + int left = 0; + int right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; while (left < right) { - size_t mid = left + (right - left) / 2; + int mid = left + (right - left) / 2; if (approximation_params_x[mid] < input_level) left = mid + 1; else right = mid; } /* Now left points to first element that is >= input_level, we need a previous element */ - const size_t index = (left > 0) ? left - 1 : 0; + const int index = (left > 0) ? left - 1 : 0; /* Piece-wise linear interploation. */ const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; scaling_factors[i+1] = gain; @@ -628,7 +628,7 @@ static void calculate_scaling_factors_sse42( } static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ - size_t i = 0; + int i = 0; const __m128i v_min_val = _mm_set1_epi32(-32768); const __m128i v_max_val = _mm_set1_epi32(32767); @@ -729,7 +729,7 @@ static void calculate_scaling_factors_scalar( float *envelope, float *scaling_factors, float *last_scaling_factor) { - size_t i; + int i; scaling_factors[0] = *last_scaling_factor; for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { const float input_level = envelope[i]; @@ -744,17 +744,17 @@ static void calculate_scaling_factors_scalar( scaling_factors[i+1] = 32768.f / input_level; } else { /* Knee and limiter regions; find the linear piece index. Searching in [0, K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS) */ - size_t left = 0; - size_t right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; + int left = 0; + int right = K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS; while (left < right) { - size_t mid = left + (right - left) / 2; + int mid = left + (right - left) / 2; if (approximation_params_x[mid] < input_level) left = mid + 1; else right = mid; } /* Now left points to first element that is >= input_level, we need a previous element */ - const size_t index = (left > 0) ? left - 1 : 0; + const int index = (left > 0) ? left - 1 : 0; /* Piece-wise linear interploation. */ const float gain = approximation_params_m[index] * input_level + approximation_params_q[index]; scaling_factors[i+1] = gain; @@ -787,7 +787,7 @@ static void scale_buffer_scalar( int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer){ - size_t i; + int i; opus_int32 sample; for(i=0; i Date: Wed, 29 Oct 2025 16:07:08 +0300 Subject: [PATCH 23/51] add some hints for compiler --- src/plugins/audiobridge-deps/limiter/limiter.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index dc7e1240ff..71c09b9aaf 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -154,6 +154,9 @@ static void calculate_scaling_factors_avx2( /* Process 8 elements at a time */ for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 8; i += 8) { + /* Assert that i is within expected bounds to help compiler optimization */ + __builtin_assume(i >= 0); + __builtin_assume(i <= K_SUB_FRAMES_IN_FRAME - 8); /* Load 8 input levels */ __m256 input_levels = _mm256_loadu_ps(&envelope[i]); @@ -535,6 +538,9 @@ static void calculate_scaling_factors_sse42( /* Process 4 elements at a time */ for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 4; i += 4) { + /* Assert that i is within expected bounds to help compiler optimization */ + __builtin_assume(i >= 0); + __builtin_assume(i <= K_SUB_FRAMES_IN_FRAME - 4); /* Load 4 input levels */ __m128 input_levels = _mm_loadu_ps(&envelope[i]); From 2cc2e4c7119b2bb4ad2492f6c43c2b40321efe00 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 16:20:11 +0300 Subject: [PATCH 24/51] fixing compilation --- src/Makefile.am | 2 +- src/plugins/audiobridge-deps/limiter/limiter.c | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index 116472ef28..7b284310e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -397,7 +397,7 @@ plugins_libadd = \ if ENABLE_PLUGIN_AUDIOBRIDGE plugin_LTLIBRARIES += plugins/libjanus_audiobridge.la plugins_libjanus_audiobridge_la_SOURCES = plugins/janus_audiobridge.c \ - plugins/audiobridge-deps/limiter/limiter.c plugins/audiobridge-deps/limiter/limiter.h \ + plugins/audiobridge-deps/limiter/limiter.h plugins/audiobridge-deps/limiter/limiter.c \ plugins/audiobridge-deps/jitter.c plugins/audiobridge-deps/resample.c plugins/audiobridge-deps/arch.h \ plugins/audiobridge-deps/os_support.h plugins/audiobridge-deps/speex/speex_jitter.h plugins/audiobridge-deps/speex/speex_resampler.h \ plugins/audiobridge-deps/speex/speexdsp_types.h plugins/audiobridge-deps/speex/speexdsp_config_types.h diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 71c09b9aaf..dc7e1240ff 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -154,9 +154,6 @@ static void calculate_scaling_factors_avx2( /* Process 8 elements at a time */ for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 8; i += 8) { - /* Assert that i is within expected bounds to help compiler optimization */ - __builtin_assume(i >= 0); - __builtin_assume(i <= K_SUB_FRAMES_IN_FRAME - 8); /* Load 8 input levels */ __m256 input_levels = _mm256_loadu_ps(&envelope[i]); @@ -538,9 +535,6 @@ static void calculate_scaling_factors_sse42( /* Process 4 elements at a time */ for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 4; i += 4) { - /* Assert that i is within expected bounds to help compiler optimization */ - __builtin_assume(i >= 0); - __builtin_assume(i <= K_SUB_FRAMES_IN_FRAME - 4); /* Load 4 input levels */ __m128 input_levels = _mm_loadu_ps(&envelope[i]); From 6278ec3c35a313791dd18e89e717fc72e1994e4c Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 17:09:50 +0300 Subject: [PATCH 25/51] trying to avoid a false UB warning in some builds --- src/plugins/audiobridge-deps/limiter/limiter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index dc7e1240ff..6329effe38 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -524,7 +524,7 @@ static void calculate_scaling_factors_sse42( float *envelope, float *scaling_factors, float *last_scaling_factor) { - int i; + size_t i; scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ From f225554146e3608e1c95a193bbfabba5bb198a19 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 17:30:08 +0300 Subject: [PATCH 26/51] Declare envelope fixed size --- .../audiobridge-deps/limiter/limiter.c | 28 ++++++++++--------- .../audiobridge-deps/limiter/limiter.h | 4 +-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 6329effe38..01e027faaf 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -89,14 +89,14 @@ static float approximation_params_q[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { }; /* Function pointers for the selected implementation */ -static void (*compute_max_envelope_func)(opus_int32 *buffer, float *envelope, int samples_in_sub_frame) = NULL; +static void (*compute_max_envelope_func)(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame) = NULL; static void (*calculate_scaling_factors_func)(float *envelope, float *scaling_factors, float *last_scaling_factor) = NULL; static void (*compute_per_sample_scaling_factors_func)(float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame) = NULL; static void (*scale_buffer_func)(opus_int32 *buffer, int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer) = NULL; static void (*clamp_buffer_func)(opus_int32 *buffer, int samples, opus_int16 *outBuffer) = NULL; #if defined(__AVX2__) -static void compute_max_envelope_avx2(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ +static void compute_max_envelope_avx2(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; /* AVX2 implementation - process 8 32-bit integers at a time */ @@ -480,7 +480,7 @@ static void compute_per_sample_scaling_factors_sse42( } } } -static void compute_max_envelope_sse42(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ +static void compute_max_envelope_sse42(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; /* SSE4.2 implementation - process 4 floats at a time */ @@ -662,7 +662,7 @@ static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outB } #endif -static void compute_max_envelope_scalar(opus_int32 *buffer, float *envelope, int samples_in_sub_frame){ +static void compute_max_envelope_scalar(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { @@ -674,7 +674,7 @@ static void compute_max_envelope_scalar(opus_int32 *buffer, float *envelope, int static inline __attribute__((always_inline)) void compute_envelope( opus_int32 *buffer, - float *envelope, + float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame, float *filter_state_level) { int sub_frame; @@ -729,7 +729,9 @@ static void calculate_scaling_factors_scalar( float *envelope, float *scaling_factors, float *last_scaling_factor) { + int i; + scaling_factors[0] = *last_scaling_factor; for (i = 0; i < K_SUB_FRAMES_IN_FRAME; ++i) { const float input_level = envelope[i]; @@ -765,12 +767,12 @@ static void calculate_scaling_factors_scalar( } inline __attribute__((always_inline)) void compute_scaling_factors( - opus_int32 *buffer, - float *envelope, - float *scaling_factors, - float *per_sample_scaling_factors, - int samples_in_sub_frame, - float *filter_state_level, + opus_int32 *buffer, + float envelope[K_SUB_FRAMES_IN_FRAME], + float *scaling_factors, + float *per_sample_scaling_factors, + int samples_in_sub_frame, + float *filter_state_level, float *last_scaling_factor) { /* * Calculating gain factors for limiter (adapted from WebRTC project). @@ -846,9 +848,9 @@ inline __attribute__((always_inline)) void init_limiter(void) { scale_buffer_func = scale_buffer_sse42; clamp_buffer_func = clamp_buffer_sse42; return; - } + } #endif - + JANUS_LOG(LOG_INFO, "Using scalar implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_scalar; calculate_scaling_factors_func = calculate_scaling_factors_scalar; diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index 85cbca7114..3fc6eda431 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -36,8 +36,8 @@ #define K_MAX_INPUT_LEVEL_LINEAR 36766.300710566735f void compute_scaling_factors( - opus_int32 *buffer, - float *envelope, + opus_int32 *buffer, + float envelope[K_SUB_FRAMES_IN_FRAME], float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame, From 04a2013e314f2e3faaf842f679928d0fd95c081b Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 17:37:46 +0300 Subject: [PATCH 27/51] Fixed sizes where possible --- .../audiobridge-deps/limiter/limiter.c | 24 +++++++++---------- .../audiobridge-deps/limiter/limiter.h | 8 +++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 01e027faaf..32cd4a7695 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -90,8 +90,8 @@ static float approximation_params_q[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { /* Function pointers for the selected implementation */ static void (*compute_max_envelope_func)(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame) = NULL; -static void (*calculate_scaling_factors_func)(float *envelope, float *scaling_factors, float *last_scaling_factor) = NULL; -static void (*compute_per_sample_scaling_factors_func)(float *scaling_factors, float *per_sample_scaling_factors, int samples_in_sub_frame) = NULL; +static void (*calculate_scaling_factors_func)(float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) = NULL; +static void (*compute_per_sample_scaling_factors_func)(float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) = NULL; static void (*scale_buffer_func)(opus_int32 *buffer, int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer) = NULL; static void (*clamp_buffer_func)(opus_int32 *buffer, int samples, opus_int16 *outBuffer) = NULL; @@ -140,8 +140,8 @@ static void compute_max_envelope_avx2(opus_int32 *buffer, float envelope[K_SUB_F } } static void calculate_scaling_factors_avx2( - float *envelope, - float *scaling_factors, + float envelope[K_SUB_FRAMES_IN_FRAME], + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { int i; scaling_factors[0] = *last_scaling_factor; @@ -247,7 +247,7 @@ static void calculate_scaling_factors_avx2( } static void compute_per_sample_scaling_factors_avx2( - float *scaling_factors, + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) { const int is_attack = scaling_factors[0] > scaling_factors[1]; @@ -435,7 +435,7 @@ static void scale_buffer_sse42( } static void compute_per_sample_scaling_factors_sse42( - float *scaling_factors, + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) { @@ -521,8 +521,8 @@ static void compute_max_envelope_sse42(opus_int32 *buffer, float envelope[K_SUB_ } } static void calculate_scaling_factors_sse42( - float *envelope, - float *scaling_factors, + float envelope[K_SUB_FRAMES_IN_FRAME], + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { size_t i; scaling_factors[0] = *last_scaling_factor; @@ -701,7 +701,7 @@ static inline __attribute__((always_inline)) void compute_envelope( } } static void compute_per_sample_scaling_factors_scalar( - float *scaling_factors, + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) { @@ -726,8 +726,8 @@ static void compute_per_sample_scaling_factors_scalar( } static void calculate_scaling_factors_scalar( - float *envelope, - float *scaling_factors, + float envelope[K_SUB_FRAMES_IN_FRAME], + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { int i; @@ -769,7 +769,7 @@ static void calculate_scaling_factors_scalar( inline __attribute__((always_inline)) void compute_scaling_factors( opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], - float *scaling_factors, + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame, float *filter_state_level, diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index 3fc6eda431..ec9a4ee548 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -38,10 +38,10 @@ void compute_scaling_factors( opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], - float *scaling_factors, - float *per_sample_scaling_factors, - int samples_in_sub_frame, - float *filter_state_level, + float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], + float *per_sample_scaling_factors, + int samples_in_sub_frame, + float *filter_state_level, float *last_scaling_factor); void init_limiter(void); From 9ebeac2894b815cf3c036854550fd1ff7557499c Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 29 Oct 2025 17:55:58 +0300 Subject: [PATCH 28/51] avoid subtraction to not confuse gcc --- src/plugins/audiobridge-deps/limiter/limiter.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 32cd4a7695..9fa53cd962 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -153,7 +153,7 @@ static void calculate_scaling_factors_avx2( const __m256 scale_factor = _mm256_set1_ps(32768.0f); /* Process 8 elements at a time */ - for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 8; i += 8) { + for (i = 0; i + 8 <= K_SUB_FRAMES_IN_FRAME; i += 8) { /* Load 8 input levels */ __m256 input_levels = _mm256_loadu_ps(&envelope[i]); @@ -273,7 +273,7 @@ static void compute_per_sample_scaling_factors_avx2( const __m256 v_scaling_diff = _mm256_set1_ps(scaling_diff); /* Process 8 elements at a time */ - for (; j <= samples_in_sub_frame - 8; j += 8) { + for (; j + 8 <= samples_in_sub_frame; j += 8) { /* Create vector of indices [j, j+1, j+2, ..., j+7] */ __m256i v_indices = _mm256_set_epi32(j+7, j+6, j+5, j+4, j+3, j+2, j+1, j); __m256 v_indices_f = _mm256_cvtepi32_ps(v_indices); @@ -304,7 +304,7 @@ static void scale_buffer_avx2( const __m256 v_max_val = _mm256_set1_ps(32767.0f); /* Process 8 elements at a time */ - for (; i <= samples - 8; i += 8) { + for (; i + 8 <= samples; i += 8) { /* Load 8 integers from buffer and convert to floats */ __m256i v_int_vals = _mm256_loadu_si256((__m256i*)&buffer[i]); __m256 v_buf_vals = _mm256_cvtepi32_ps(v_int_vals); @@ -351,7 +351,7 @@ static void clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBu const __m256i v_max_val = _mm256_set1_epi32(32767); /* Process 8 elements at a time */ - for (; i <= samples - 8; i += 8) { + for (; i + 8 <= samples; i += 8) { /* Load 8 integers from buffer */ __m256i v_int_vals = _mm256_loadu_si256((__m256i*)&buffer[i]); @@ -393,7 +393,7 @@ static void scale_buffer_sse42( const __m128 v_max_val = _mm_set1_ps(32767.0f); /* Process 4 elements at a time */ - for (; i <= samples - 4; i += 4) { + for (; i + 4 <= samples; i += 4) { /* Load 4 integers from buffer and convert to floats */ __m128i v_int_vals = _mm_loadu_si128((__m128i*)&buffer[i]); __m128 v_buf_vals = _mm_cvtepi32_ps(v_int_vals); @@ -462,7 +462,7 @@ static void compute_per_sample_scaling_factors_sse42( const __m128 v_scaling_diff = _mm_set1_ps(scaling_diff); /* Process 4 elements at a time */ - for (; j <= samples_in_sub_frame - 4; j += 4) { + for (; j + 4 <= samples_in_sub_frame; j += 4) { /* Create vector of indices [j, j+1, j+2, j+3] */ __m128i v_indices = _mm_set_epi32(j+3, j+2, j+1, j); __m128 v_indices_f = _mm_cvtepi32_ps(v_indices); @@ -534,7 +534,7 @@ static void calculate_scaling_factors_sse42( const __m128 scale_factor = _mm_set1_ps(32768.0f); /* Process 4 elements at a time */ - for (i = 0; i <= K_SUB_FRAMES_IN_FRAME - 4; i += 4) { + for (i = 0; i + 4 <= K_SUB_FRAMES_IN_FRAME; i += 4) { /* Load 4 input levels */ __m128 input_levels = _mm_loadu_ps(&envelope[i]); @@ -633,7 +633,7 @@ static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outB const __m128i v_max_val = _mm_set1_epi32(32767); /* Process 4 elements at a time */ - for (; i <= samples - 4; i += 4) { + for (; i + 4 <= samples; i += 4) { /* Load 4 integers from buffer */ __m128i v_int_vals = _mm_loadu_si128((__m128i*)&buffer[i]); From 1e5767cbb9ca83d777bf62f0e393b6521ff6f427 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 30 Oct 2025 08:23:31 +0300 Subject: [PATCH 29/51] use int for counter --- src/plugins/audiobridge-deps/limiter/limiter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 9fa53cd962..4721ed89e1 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -524,7 +524,7 @@ static void calculate_scaling_factors_sse42( float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { - size_t i; + int i; scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ From 9c54543b81cc54b22b6d19d4294a549f794a359e Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 30 Oct 2025 09:03:53 +0300 Subject: [PATCH 30/51] trying to help some versions of gcc to figure out that i < K_SUB_FRAMES_IN_FRAME --- src/plugins/audiobridge-deps/limiter/limiter.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 4721ed89e1..88fe1918bc 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -3,6 +3,7 @@ #include #include +#include #include "../../../debug.h" /* SIMD intrinsics */ @@ -593,6 +594,7 @@ static void calculate_scaling_factors_sse42( _mm_storeu_ps(&scaling_factors[i + 1], result); } + assert(i < K_SUB_FRAMES_IN_FRAME); /* Process remaining elements with scalar code */ for (; i < K_SUB_FRAMES_IN_FRAME; ++i) { const float input_level = envelope[i]; From 66015158532a992ae2e052b3bf6afa285810478d Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 30 Oct 2025 09:15:49 +0300 Subject: [PATCH 31/51] now gcc must be satisfied --- src/plugins/audiobridge-deps/limiter/limiter.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 88fe1918bc..8869adb8f1 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -3,7 +3,6 @@ #include #include -#include #include "../../../debug.h" /* SIMD intrinsics */ @@ -525,7 +524,7 @@ static void calculate_scaling_factors_sse42( float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { - int i; + unsigned i; scaling_factors[0] = *last_scaling_factor; /* Constants for vectorized operations */ @@ -594,7 +593,6 @@ static void calculate_scaling_factors_sse42( _mm_storeu_ps(&scaling_factors[i + 1], result); } - assert(i < K_SUB_FRAMES_IN_FRAME); /* Process remaining elements with scalar code */ for (; i < K_SUB_FRAMES_IN_FRAME; ++i) { const float input_level = envelope[i]; From 42471c0d0b09c9df87e2b037e3fe8f7a3372b022 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 30 Oct 2025 14:53:49 +0300 Subject: [PATCH 32/51] proper and optimized clamping --- .../audiobridge-deps/limiter/limiter.c | 129 ++++++------------ .../audiobridge-deps/limiter/limiter.h | 3 + 2 files changed, 46 insertions(+), 86 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 8869adb8f1..52c95db4c5 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -299,45 +299,29 @@ static void scale_buffer_avx2( opus_int16 *outBuffer){ int i = 0; - const __m256 v_half = _mm256_set1_ps(0.5f); - const __m256 v_min_val = _mm256_set1_ps(-32768.0f); - const __m256 v_max_val = _mm256_set1_ps(32767.0f); - /* Process 8 elements at a time */ for (; i + 8 <= samples; i += 8) { /* Load 8 integers from buffer and convert to floats */ __m256i v_int_vals = _mm256_loadu_si256((__m256i*)&buffer[i]); __m256 v_buf_vals = _mm256_cvtepi32_ps(v_int_vals); - /* Load 8 scaling factors */ __m256 v_scale_vals = _mm256_loadu_ps(&per_sample_scaling_factors[i]); - /* Multiply buffer values with scaling factors */ __m256 v_mult_result = _mm256_mul_ps(v_buf_vals, v_scale_vals); - - /* Add 0.5f for rounding */ - __m256 v_rounded = _mm256_add_ps(v_mult_result, v_half); - - /* Clamp values to [-32768, 32767] */ - v_rounded = _mm256_max_ps(v_rounded, v_min_val); - v_rounded = _mm256_min_ps(v_rounded, v_max_val); - /* Convert to integers */ - __m256i v_int_result = _mm256_cvtps_epi32(v_rounded); - + __m256i v_int_result = _mm256_cvtps_epi32(v_mult_result); /* Pack 32-bit integers to 16-bit integers */ /* First, pack the lower 4 32-bit values */ __m128i v_low = _mm256_extracti128_si256(v_int_result, 0); __m128i v_high = _mm256_extracti128_si256(v_int_result, 1); __m128i v_packed = _mm_packs_epi32(v_low, v_high); - /* Store the 8 16-bit integers */ _mm_storeu_si128((__m128i*)&outBuffer[i], v_packed); } /* Handle remaining elements with scalar code */ for (; i < samples; i++) { - opus_int32 sample = (opus_int32)(buffer[i] * per_sample_scaling_factors[i] + 0.5f); + opus_int32 sample = (opus_int32)lrintf(buffer[i] * per_sample_scaling_factors[i]); if(sample > 32767) sample = 32767; else if(sample < -32768) @@ -347,24 +331,15 @@ static void scale_buffer_avx2( } static void clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i = 0; - const __m256i v_min_val = _mm256_set1_epi32(-32768); - const __m256i v_max_val = _mm256_set1_epi32(32767); - /* Process 8 elements at a time */ for (; i + 8 <= samples; i += 8) { /* Load 8 integers from buffer */ __m256i v_int_vals = _mm256_loadu_si256((__m256i*)&buffer[i]); - - /* Clamp values to [-32768, 32767] */ - v_int_vals = _mm256_max_epi32(v_int_vals, v_min_val); - v_int_vals = _mm256_min_epi32(v_int_vals, v_max_val); - /* Pack 32-bit integers to 16-bit integers */ /* First, pack the lower 4 32-bit values */ __m128i v_low = _mm256_extracti128_si256(v_int_vals, 0); __m128i v_high = _mm256_extracti128_si256(v_int_vals, 1); __m128i v_packed = _mm_packs_epi32(v_low, v_high); - /* Store the 8 16-bit integers */ _mm_storeu_si128((__m128i*)&outBuffer[i], v_packed); } @@ -386,46 +361,28 @@ static void scale_buffer_sse42( int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer){ - int i = 0; - const __m128 v_half = _mm_set1_ps(0.5f); - const __m128 v_min_val = _mm_set1_ps(-32768.0f); - const __m128 v_max_val = _mm_set1_ps(32767.0f); - + const __m128i v_zero = _mm_setzero_si128(); /* Process 4 elements at a time */ for (; i + 4 <= samples; i += 4) { /* Load 4 integers from buffer and convert to floats */ __m128i v_int_vals = _mm_loadu_si128((__m128i*)&buffer[i]); __m128 v_buf_vals = _mm_cvtepi32_ps(v_int_vals); - /* Load 4 scaling factors */ __m128 v_scale_vals = _mm_loadu_ps(&per_sample_scaling_factors[i]); - /* Multiply buffer values with scaling factors */ __m128 v_mult_result = _mm_mul_ps(v_buf_vals, v_scale_vals); - - /* Add 0.5f for rounding */ - __m128 v_rounded = _mm_add_ps(v_mult_result, v_half); - - /* Clamp values to [-32768, 32767] */ - v_rounded = _mm_max_ps(v_rounded, v_min_val); - v_rounded = _mm_min_ps(v_rounded, v_max_val); - - /* Convert to integers */ - __m128i v_int_result = _mm_cvtps_epi32(v_rounded); - - /* Pack 32-bit integers to 16-bit integers */ - __m128i v_low = v_int_result; - __m128i v_high = _mm_shuffle_epi32(v_int_result, _MM_SHUFFLE(3, 2, 3, 2)); - __m128i v_packed = _mm_packs_epi32(v_low, v_high); - + /* Convert to integers (truncation) */ + __m128i v_int_result = _mm_cvtps_epi32(v_mult_result); + /* Pack 32-bit integers to 16-bit integers with saturation */ + __m128i v_packed = _mm_packs_epi32(v_int_result, v_zero); /* Store the 4 16-bit integers */ _mm_storel_epi64((__m128i*)&outBuffer[i], v_packed); } /* Handle remaining elements with scalar code */ for (; i < samples; i++) { - opus_int32 sample = (opus_int32)(buffer[i] * per_sample_scaling_factors[i] + 0.5f); + opus_int32 sample = (opus_int32)lrintf(buffer[i] * per_sample_scaling_factors[i]); if(sample > 32767) sample = 32767; else if(sample < -32768) @@ -629,25 +586,14 @@ static void calculate_scaling_factors_sse42( static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i = 0; - const __m128i v_min_val = _mm_set1_epi32(-32768); - const __m128i v_max_val = _mm_set1_epi32(32767); - /* Process 4 elements at a time */ for (; i + 4 <= samples; i += 4) { /* Load 4 integers from buffer */ - __m128i v_int_vals = _mm_loadu_si128((__m128i*)&buffer[i]); - - /* Clamp values to [-32768, 32767] */ - v_int_vals = _mm_max_epi32(v_int_vals, v_min_val); - v_int_vals = _mm_min_epi32(v_int_vals, v_max_val); - - /* Pack 32-bit integers to 16-bit integers */ - __m128i v_low = v_int_vals; - __m128i v_high = _mm_shuffle_epi32(v_int_vals, _MM_SHUFFLE(3, 2, 3, 2)); - __m128i v_packed = _mm_packs_epi32(v_low, v_high); - + __m128i vec_int32 = _mm_loadu_si128((__m128i*)&buffer[i]); + /* Pack 32-bit integers to 16-bit integers with saturation */ + __m128i vec_int16_packed = _mm_packs_epi32(vec_int32, _mm_setzero_si128()); /* Store the 4 16-bit integers */ - _mm_storel_epi64((__m128i*)&outBuffer[i], v_packed); + _mm_storel_epi64((__m128i*)&outBuffer[i], vec_int16_packed); } /* Handle remaining elements with scalar code */ @@ -792,7 +738,7 @@ static void scale_buffer_scalar( int i; opus_int32 sample; for(i=0; i 32767) sample = 32767; else if(sample < -32768) @@ -827,34 +773,45 @@ inline __attribute__((always_inline)) void clamp_buffer(opus_int32 *buffer, int clamp_buffer_func(buffer, samples, outBuffer); } -inline __attribute__((always_inline)) void init_limiter(void) { - #if defined(__AVX2__) - if (has_avx2()) { - JANUS_LOG(LOG_INFO, "Using AVX2 implementation of limiter\n"); - compute_max_envelope_func = compute_max_envelope_avx2; - calculate_scaling_factors_func = calculate_scaling_factors_avx2; - compute_per_sample_scaling_factors_func = compute_per_sample_scaling_factors_avx2; - scale_buffer_func = scale_buffer_avx2; - clamp_buffer_func = clamp_buffer_avx2; - return; - } - #endif - #if defined(__SSE4_2__) - if (has_sse42()) { - JANUS_LOG(LOG_INFO, "Using SSE4.2 implementation of limiter\n"); +inline __attribute__((always_inline)) void init_limiter_avx2(void) { + JANUS_LOG(LOG_INFO, "Using AVX2 implementation of limiter\n"); + compute_max_envelope_func = compute_max_envelope_avx2; + calculate_scaling_factors_func = calculate_scaling_factors_avx2; + compute_per_sample_scaling_factors_func = compute_per_sample_scaling_factors_avx2; + scale_buffer_func = scale_buffer_avx2; + clamp_buffer_func = clamp_buffer_avx2; +} + +inline __attribute__((always_inline)) void init_limiter_sse42(void) { + JANUS_LOG(LOG_INFO, "Using SSE4.2 implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_sse42; calculate_scaling_factors_func = calculate_scaling_factors_sse42; compute_per_sample_scaling_factors_func = compute_per_sample_scaling_factors_sse42; scale_buffer_func = scale_buffer_sse42; clamp_buffer_func = clamp_buffer_sse42; - return; - } - #endif +} +inline __attribute__((always_inline)) void init_limiter_scalar(void) { JANUS_LOG(LOG_INFO, "Using scalar implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_scalar; calculate_scaling_factors_func = calculate_scaling_factors_scalar; compute_per_sample_scaling_factors_func = compute_per_sample_scaling_factors_scalar; scale_buffer_func = scale_buffer_scalar; clamp_buffer_func = clamp_buffer_scalar; -} \ No newline at end of file +} + +inline __attribute__((always_inline)) void init_limiter(void) { + #if defined(__AVX2__) + if (has_avx2()) { + init_limiter_avx2(); + return; + } + #endif + #if defined(__SSE4_2__) + if (has_sse42()) { + init_limiter_sse42(); + return; + } + #endif + init_limiter_scalar(); +} diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index ec9a4ee548..2c2e768cb8 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -45,6 +45,9 @@ void compute_scaling_factors( float *last_scaling_factor); void init_limiter(void); +void init_limiter_avx2(void); +void init_limiter_sse42(void); +void init_limiter_scalar(void); void scale_buffer( opus_int32 *buffer, From 08879c47aef7aaa8bef62f3046adedc018cf7545 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Fri, 31 Oct 2025 09:59:54 +0300 Subject: [PATCH 33/51] add tests --- .../limiter/limiter_clamp_test.c | 60 +++++++++ .../limiter/limiter_scale_test.c | 118 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c create mode 100644 src/plugins/audiobridge-deps/limiter/limiter_scale_test.c diff --git a/src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c b/src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c new file mode 100644 index 0000000000..ee878c4446 --- /dev/null +++ b/src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "limiter.h" + +#define OPUS_SAMPLES 960 +#define SAMPLING_RATE 48000 +#define TEST_ITERATIONS 100000 + +int main(void) { + int samples = SAMPLING_RATE/50; + opus_int32 buffer[OPUS_SAMPLES]; + opus_int16 outBuffer_scalar[OPUS_SAMPLES], + outBuffer_avx2[OPUS_SAMPLES], + outBuffer_sse42[OPUS_SAMPLES]; + memset(buffer, 0, OPUS_SAMPLES*(4)); + memset(outBuffer_scalar, 0, OPUS_SAMPLES*(2)); + memset(outBuffer_avx2, 0, OPUS_SAMPLES*(2)); + memset(outBuffer_sse42, 0, OPUS_SAMPLES*(2)); + + int i=0, t=0; + for (t=0; t SHRT_MAX && outBuffer_scalar[i] != SHRT_MAX) + || (buffer[i] < SHRT_MIN && outBuffer_scalar[i] != SHRT_MIN)) { + printf("Error(outBuffer wrong value): scalar(%d) avx2(%d) sse42(%d) at index %d\n", outBuffer_scalar[i], outBuffer_avx2[i], outBuffer_sse42[i], i); + return 1; + } + } + } + printf("Clamp works fine (scalar, sse4.2 and avx2)\n"); + return 0; +} \ No newline at end of file diff --git a/src/plugins/audiobridge-deps/limiter/limiter_scale_test.c b/src/plugins/audiobridge-deps/limiter/limiter_scale_test.c new file mode 100644 index 0000000000..7a69dfc3eb --- /dev/null +++ b/src/plugins/audiobridge-deps/limiter/limiter_scale_test.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "limiter.h" + +#define OPUS_SAMPLES 960 +#define SAMPLING_RATE 48000 +#define TEST_ITERATIONS 100000 + +int main(void) { + int samples = SAMPLING_RATE/50; + opus_int32 buffer[OPUS_SAMPLES]; + opus_int16 outBuffer_scalar[OPUS_SAMPLES], + outBuffer_avx2[OPUS_SAMPLES], + outBuffer_sse42[OPUS_SAMPLES]; + memset(buffer, 0, OPUS_SAMPLES*(4)); + memset(outBuffer_scalar, 0, OPUS_SAMPLES*(2)); + memset(outBuffer_avx2, 0, OPUS_SAMPLES*(2)); + memset(outBuffer_sse42, 0, OPUS_SAMPLES*(2)); + int i=0, t=0; + + /* For audio limiter we split frame in subframes */ + int samples_in_sub_frame = samples / K_SUB_FRAMES_IN_FRAME; + + /* scalar */ + float *per_sample_scaling_factors_scalar = g_malloc0(OPUS_SAMPLES * sizeof(float)); + float *envelope_scalar = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); + float *scaling_factors_scalar = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); + + /* avx2 */ + float *per_sample_scaling_factors_avx2 = g_malloc0(OPUS_SAMPLES * sizeof(float)); + float *envelope_avx2 = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); + float *scaling_factors_avx2 = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); + + /* sse42 */ + float *per_sample_scaling_factors_sse42 = g_malloc0(OPUS_SAMPLES * sizeof(float)); + float *envelope_sse42 = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); + float *scaling_factors_sse42 = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); + + + for (t=0; t SHRT_MAX) + && (scaled > SHRT_MAX || scaled < SHRT_MIN) + && abs(scaled - outBuffer_scalar[i]) > 1) { + printf("Error(scaling_factors wrong value): scalar(%f) avx2(%f) sse42(%f) at index %d\n", + scaling_factors_scalar[i], scaling_factors_avx2[i], scaling_factors_sse42[i], i); + printf("Buffer: %d Scaled: %d\n", buffer[i], scaled); + printf("Iteration %d\n", t); + return 1; + } + } + } + for (i=0; i Date: Fri, 31 Oct 2025 10:58:49 +0300 Subject: [PATCH 34/51] fix conditional compilation --- src/plugins/audiobridge-deps/limiter/limiter.c | 4 ++++ src/plugins/audiobridge-deps/limiter/limiter.h | 10 ++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 52c95db4c5..8400133b92 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -773,6 +773,7 @@ inline __attribute__((always_inline)) void clamp_buffer(opus_int32 *buffer, int clamp_buffer_func(buffer, samples, outBuffer); } +#if defined(__AVX2__) inline __attribute__((always_inline)) void init_limiter_avx2(void) { JANUS_LOG(LOG_INFO, "Using AVX2 implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_avx2; @@ -781,7 +782,9 @@ inline __attribute__((always_inline)) void init_limiter_avx2(void) { scale_buffer_func = scale_buffer_avx2; clamp_buffer_func = clamp_buffer_avx2; } +#endif +#if defined(__SSE4_2__) inline __attribute__((always_inline)) void init_limiter_sse42(void) { JANUS_LOG(LOG_INFO, "Using SSE4.2 implementation of limiter\n"); compute_max_envelope_func = compute_max_envelope_sse42; @@ -790,6 +793,7 @@ inline __attribute__((always_inline)) void init_limiter_sse42(void) { scale_buffer_func = scale_buffer_sse42; clamp_buffer_func = clamp_buffer_sse42; } +#endif inline __attribute__((always_inline)) void init_limiter_scalar(void) { JANUS_LOG(LOG_INFO, "Using scalar implementation of limiter\n"); diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index 2c2e768cb8..c2cf19ba40 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -45,9 +45,15 @@ void compute_scaling_factors( float *last_scaling_factor); void init_limiter(void); +void init_limiter_scalar(void); + +#if defined(__AVX2__) void init_limiter_avx2(void); +#endif + +#if defined(__SSE4_2__) void init_limiter_sse42(void); -void init_limiter_scalar(void); +#endif void scale_buffer( opus_int32 *buffer, @@ -56,4 +62,4 @@ void scale_buffer( opus_int16 *outBuffer); void clamp_buffer(opus_int32 *buffer, int samples, opus_int16 *outBuffer); -#endif /* LIMITER_H */ \ No newline at end of file +#endif /* LIMITER_H */ From 5a93caff093e1d9632ffdafd528165c14417f63f Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 6 Nov 2025 11:12:48 +0300 Subject: [PATCH 35/51] License info --- .../audiobridge-deps/limiter/limiter.c | 34 ++++++++++++++ .../audiobridge-deps/limiter/limiter.h | 34 ++++++++++++++ src/plugins/janus_audiobridge.c | 45 +++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 8400133b92..1539e89137 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -1,3 +1,37 @@ +/* The code in this file contains code adapted from WebRTC project. */ +/* + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + #include "limiter.h" #include #include diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index c2cf19ba40..71569eb37e 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -1,3 +1,37 @@ +/* The code in this file contains code adapted from WebRTC project. */ +/* + Copyright (c) 2011, The WebRTC project authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + #ifndef LIMITER_H #define LIMITER_H diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 436618502a..d22e55e8bb 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -1200,6 +1200,51 @@ room-: { * pass a new \c rtp object with \c ip and \c port in a \c configure * request on the participant handle, which will finalize the media * establishment. + * + * \subsection ablimiter AudioBridge limiter + * + * The AudioBridge plugin has a built-in audio limiter, which can be used + * to limit the volume of the audio stream coming to participants. + * This limiter is disabled by default, and can be enabled by setting + * the \c use_limiter property to \c true in the \c create request. + * The limiter is applied to recording (if there are 2 or more participants + * speaking) and to all tracks that participants receive from the + * AudioBridge plugin (if there are 2 or mote participants speaking besides + * this participant). + * + * The limiter contains code parts adapted from the WebRTC code which is + * licensed under the BSD license: +\verbatim +Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +\endverbatim * */ From 2119b216ff1e8066c39c6eae4f00e5a8f4705622 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 6 Nov 2025 11:13:36 +0300 Subject: [PATCH 36/51] remove mistakenly added param --- src/plugins/janus_audiobridge.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index d22e55e8bb..a706335d98 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -1483,7 +1483,6 @@ static struct janus_json_parameter configure_parameters[] = { {"group", JSON_STRING, 0}, {"spatial_position", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}, {"denoise", JANUS_JSON_BOOL, 0}, - {"use_limiter", JANUS_JSON_BOOL, 0}, {"record", JANUS_JSON_BOOL, 0}, {"filename", JSON_STRING, 0}, {"display", JSON_STRING, 0}, From c101d9fb23e6cfdd64b93edb04fdeb7b741733fd Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 6 Nov 2025 11:16:19 +0300 Subject: [PATCH 37/51] Add fullMixBuffer, so we calculate it only once for all silent participants --- src/plugins/janus_audiobridge.c | 113 +++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 39 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index a706335d98..b6c16a1cce 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8533,10 +8533,12 @@ static void *janus_audiobridge_mixer_thread(void *data) { opus_int32 buffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], sumBuffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES]; opus_int16 outBuffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], - resampled[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], *curBuffer = NULL; + fullMixBuffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], + resampled[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], *curBuffer = NULL, *mixBuffer = NULL; memset(buffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 8 : 4)); memset(sumBuffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 8 : 4)); memset(outBuffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 4 : 2)); + memset(fullMixBuffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 4 : 2)); memset(resampled, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 4 : 2)); /* In case forwarding groups are enabled, we need additional buffers */ @@ -8619,6 +8621,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { int count = 0, rf_count = 0, pf_count = 0, prev_count = 0; int lgain = 0, rgain = 0, diff = 0; int mix_count = 0; + bool has_silent = FALSE; while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&audiobridge->destroyed)) { /* See if it's time to prepare a frame */ gettimeofday(&now, NULL); @@ -8691,6 +8694,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { buffer[i] = 0; if(groups_num > 0) memset(groupBuffers, 0, groupBuffersSize); + has_silent = FALSE; ps = participants_list; while(ps) { janus_audiobridge_participant *p = (janus_audiobridge_participant *)ps->data; @@ -8811,6 +8815,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } } + } else { + has_silent = TRUE; } janus_mutex_unlock(&p->qmutex); ps = ps->next; @@ -8916,17 +8922,31 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ if (audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, - samples_in_sub_frame, &filter_state_level, &last_scaling_factor); - /* Are we recording the mix? (only do it if there's someone in, though...) */ - if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { + samples_in_sub_frame, &filter_state_level, &last_scaling_factor); + + /* If we have silent participants, calculate once the full mix */ + if (has_silent) { if (audiobridge->use_limiter && mix_count > 1) { /* If we use limiter and we mixed more than 1 track, apply it */ - scale_buffer(buffer, samples, per_sample_scaling_factors, outBuffer); + scale_buffer(buffer, samples, per_sample_scaling_factors, fullMixBuffer); } else { /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(buffer, samples, outBuffer); + clamp_buffer(buffer, samples, fullMixBuffer); + } + } + /* Are we recording the mix? (only do it if there's someone in, though...) */ + if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { + /* If we didn't yet calculated full mix, do it now */ + if (!has_silent) { + if (audiobridge->use_limiter && mix_count > 1) { + /* If we use limiter and we mixed more than 1 track, apply it */ + scale_buffer(buffer, samples, per_sample_scaling_factors, fullMixBuffer); + } else { + /* Otherwise just clamp values that are outside int16 boundaries */ + clamp_buffer(buffer, samples, fullMixBuffer); + } } - fwrite(outBuffer, sizeof(opus_int16), samples, audiobridge->recording); + fwrite(fullMixBuffer, sizeof(opus_int16), samples, audiobridge->recording); /* Every 5 seconds we update the wav header */ gint64 now = janus_get_monotonic_time(); if(now - audiobridge->record_lastupdate >= 5*G_USEC_PER_SEC) { @@ -8966,48 +8986,63 @@ static void *janus_audiobridge_mixer_thread(void *data) { janus_mutex_unlock(&p->qmutex); /* Remove the participant's own contribution */ curBuffer = (opus_int16 *)((pkt && pkt->length && !pkt->silence) ? pkt->data : NULL); - if(!p->stereo) { - for(i=0; ivolume_gain == 100) - sumBuffer[i] = buffer[i] - (curBuffer ? (curBuffer[i]) : 0); - else - sumBuffer[i] = buffer[i] - (curBuffer ? (curBuffer[i]*p->volume_gain)/100 : 0); - } - } else { - diff = 50 - p->spatial_position; - lgain = 50 + diff; - rgain = 50 - diff; - for(i=0; istereo) { + for(i=0; ivolume_gain == 100) + sumBuffer[i] = buffer[i] - curBuffer[i]; + else + sumBuffer[i] = buffer[i] - ((curBuffer[i]*p->volume_gain)/100); + } + } else { + diff = 50 - p->spatial_position; + lgain = 50 + diff; + rgain = 50 - diff; + for(i=0; iuse_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { - /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ - scale_buffer(sumBuffer, samples, per_sample_scaling_factors, outBuffer); + if(audiobridge->use_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { + /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ + scale_buffer(sumBuffer, samples, per_sample_scaling_factors, outBuffer); + } else { + /* Otherwise just clamp values that are outside int16 boundaries */ + clamp_buffer(sumBuffer, samples, outBuffer); + } + mixBuffer = outBuffer; + } else if (!has_silent) { + JANUS_LOG(LOG_WARN, "[%s] No packet for participant (%s), but we have no silent participants either\n", audiobridge->room_id_str, p->user_id_str); + for(i=0; iuse_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { + /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ + scale_buffer(sumBuffer, samples, per_sample_scaling_factors, outBuffer); + } else { + /* Otherwise just clamp values that are outside int16 boundaries */ + clamp_buffer(sumBuffer, samples, outBuffer); + } + mixBuffer = outBuffer; } else { - /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(sumBuffer, samples, outBuffer); + mixBuffer = fullMixBuffer; } - /* Enqueue this mixed frame for encoding in the participant thread */ janus_audiobridge_rtp_relay_packet *mixedpkt = g_malloc(sizeof(janus_audiobridge_rtp_relay_packet)); mixedpkt->data = g_malloc(samples*2); if(p->codec != JANUS_AUDIOCODEC_OPUS && audiobridge->sampling_rate != 8000) { /* Downsample this from whatever the mixer uses */ - i = janus_audiobridge_resample(outBuffer, samples, audiobridge->sampling_rate, (int16_t *)mixedpkt->data, 8000); + i = janus_audiobridge_resample(mixBuffer, samples, audiobridge->sampling_rate, (int16_t *)mixedpkt->data, 8000); if(i == 0) { JANUS_LOG(LOG_WARN, "[G.711] Error downsampling from %d, skipping audio packet\n", audiobridge->sampling_rate); g_free(mixedpkt->data); @@ -9018,7 +9053,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } else { /* Just copy */ - memcpy(mixedpkt->data, outBuffer, samples*2); + memcpy(mixedpkt->data, mixBuffer, samples*2); } mixedpkt->length = samples; /* We set the number of samples here, not the data length */ mixedpkt->timestamp = ts; From 7943fc36a34806cfbd48eaf1956ef820fd2598b3 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 6 Nov 2025 11:22:27 +0300 Subject: [PATCH 38/51] fix type --- src/plugins/janus_audiobridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index b6c16a1cce..a19b0019a9 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8621,7 +8621,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { int count = 0, rf_count = 0, pf_count = 0, prev_count = 0; int lgain = 0, rgain = 0, diff = 0; int mix_count = 0; - bool has_silent = FALSE; + gboolean has_silent = FALSE; while(!g_atomic_int_get(&stopping) && !g_atomic_int_get(&audiobridge->destroyed)) { /* See if it's time to prepare a frame */ gettimeofday(&now, NULL); From 401ad3e6af467040bf84febaa92c6b923b248fe2 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Mon, 10 Nov 2025 12:10:14 +0300 Subject: [PATCH 39/51] set silence if decoded packet length is 0 --- src/plugins/janus_audiobridge.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index a19b0019a9..394f6e37b9 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -9409,6 +9409,9 @@ static void *janus_audiobridge_participant_thread(void *data) { g_free(pkt->data); g_free(pkt); continue; + } else if (pkt->length == 0) { + /* Empty packet => buffer is all zeroes (g_malloc0) => it's silence */ + pkt->silence = TRUE; } /* Queue the decoded packet for the mixer */ janus_mutex_lock(&participant->qmutex); From 362132a67441e3b5380fc99512bcc8acfb65f600 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Mon, 10 Nov 2025 13:05:43 +0300 Subject: [PATCH 40/51] set silence if decoded packet length is 0 --- src/plugins/janus_audiobridge.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 394f6e37b9..22fbdba54e 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -9315,6 +9315,9 @@ static void *janus_audiobridge_participant_thread(void *data) { g_free(pkt->data); g_free(pkt); break; + } else if (pkt->length == 0) { + /* Empty packet => buffer is all zeroes (g_malloc0) => it's silence */ + pkt->silence = TRUE; } /* Queue the decoded packet for the mixer */ janus_mutex_lock(&participant->qmutex); From 51c08b15818373155a9eeae311805024e8280f6e Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 11 Nov 2025 09:14:53 +0300 Subject: [PATCH 41/51] Additional cases when we have silent participants and need to calculate full mix --- src/plugins/janus_audiobridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 22fbdba54e..361941f993 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8703,6 +8703,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { !g_atomic_int_get(&p->active) || p->muted || g_atomic_int_get(&p->suspended) || !p->inbuf) { janus_mutex_unlock(&p->qmutex); ps = ps->next; + has_silent = TRUE; /* If we have someone who is not speaking, but listening */ continue; } GList *peek = g_list_first(p->inbuf); @@ -8715,6 +8716,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { JANUS_LOG(LOG_WARN, "[G.711] Error upsampling to %d, skipping audio packet\n", audiobridge->sampling_rate); janus_mutex_unlock(&p->qmutex); ps = ps->next; + has_silent = TRUE; /* If we have someone who is not speaking, but listening */ continue; } memcpy(pkt->data, resampled, pkt->length*2); From c7c600b8d881b193401a4a74fd7a9d3574c0f8c2 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 11 Nov 2025 09:21:38 +0300 Subject: [PATCH 42/51] Log info about enabled limiter when starting audiobridge --- src/plugins/janus_audiobridge.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 361941f993..bdbced7b32 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8589,6 +8589,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { float last_scaling_factor = 1.f; /* In case audio limiter enabled, we need some buffers for it */ if (audiobridge->use_limiter) { + JANUS_LOG(LOG_INFO, "Audio limiter enabled\n"); per_sample_scaling_factors = g_malloc0((audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES) * sizeof(float)); envelope = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); scaling_factors = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); From 3e5354000e99eb8db0ff5fd98fba77f6cbd8ee04 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 13 Nov 2025 09:14:35 +0300 Subject: [PATCH 43/51] cleanup --- .../limiter/limiter_clamp_test.c | 60 --------- .../limiter/limiter_scale_test.c | 118 ------------------ src/plugins/janus_audiobridge.c | 99 ++++----------- 3 files changed, 27 insertions(+), 250 deletions(-) delete mode 100644 src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c delete mode 100644 src/plugins/audiobridge-deps/limiter/limiter_scale_test.c diff --git a/src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c b/src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c deleted file mode 100644 index ee878c4446..0000000000 --- a/src/plugins/audiobridge-deps/limiter/limiter_clamp_test.c +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "limiter.h" - -#define OPUS_SAMPLES 960 -#define SAMPLING_RATE 48000 -#define TEST_ITERATIONS 100000 - -int main(void) { - int samples = SAMPLING_RATE/50; - opus_int32 buffer[OPUS_SAMPLES]; - opus_int16 outBuffer_scalar[OPUS_SAMPLES], - outBuffer_avx2[OPUS_SAMPLES], - outBuffer_sse42[OPUS_SAMPLES]; - memset(buffer, 0, OPUS_SAMPLES*(4)); - memset(outBuffer_scalar, 0, OPUS_SAMPLES*(2)); - memset(outBuffer_avx2, 0, OPUS_SAMPLES*(2)); - memset(outBuffer_sse42, 0, OPUS_SAMPLES*(2)); - - int i=0, t=0; - for (t=0; t SHRT_MAX && outBuffer_scalar[i] != SHRT_MAX) - || (buffer[i] < SHRT_MIN && outBuffer_scalar[i] != SHRT_MIN)) { - printf("Error(outBuffer wrong value): scalar(%d) avx2(%d) sse42(%d) at index %d\n", outBuffer_scalar[i], outBuffer_avx2[i], outBuffer_sse42[i], i); - return 1; - } - } - } - printf("Clamp works fine (scalar, sse4.2 and avx2)\n"); - return 0; -} \ No newline at end of file diff --git a/src/plugins/audiobridge-deps/limiter/limiter_scale_test.c b/src/plugins/audiobridge-deps/limiter/limiter_scale_test.c deleted file mode 100644 index 7a69dfc3eb..0000000000 --- a/src/plugins/audiobridge-deps/limiter/limiter_scale_test.c +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "limiter.h" - -#define OPUS_SAMPLES 960 -#define SAMPLING_RATE 48000 -#define TEST_ITERATIONS 100000 - -int main(void) { - int samples = SAMPLING_RATE/50; - opus_int32 buffer[OPUS_SAMPLES]; - opus_int16 outBuffer_scalar[OPUS_SAMPLES], - outBuffer_avx2[OPUS_SAMPLES], - outBuffer_sse42[OPUS_SAMPLES]; - memset(buffer, 0, OPUS_SAMPLES*(4)); - memset(outBuffer_scalar, 0, OPUS_SAMPLES*(2)); - memset(outBuffer_avx2, 0, OPUS_SAMPLES*(2)); - memset(outBuffer_sse42, 0, OPUS_SAMPLES*(2)); - int i=0, t=0; - - /* For audio limiter we split frame in subframes */ - int samples_in_sub_frame = samples / K_SUB_FRAMES_IN_FRAME; - - /* scalar */ - float *per_sample_scaling_factors_scalar = g_malloc0(OPUS_SAMPLES * sizeof(float)); - float *envelope_scalar = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); - float *scaling_factors_scalar = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); - - /* avx2 */ - float *per_sample_scaling_factors_avx2 = g_malloc0(OPUS_SAMPLES * sizeof(float)); - float *envelope_avx2 = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); - float *scaling_factors_avx2 = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); - - /* sse42 */ - float *per_sample_scaling_factors_sse42 = g_malloc0(OPUS_SAMPLES * sizeof(float)); - float *envelope_sse42 = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); - float *scaling_factors_sse42 = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); - - - for (t=0; t SHRT_MAX) - && (scaled > SHRT_MAX || scaled < SHRT_MIN) - && abs(scaled - outBuffer_scalar[i]) > 1) { - printf("Error(scaling_factors wrong value): scalar(%f) avx2(%f) sse42(%f) at index %d\n", - scaling_factors_scalar[i], scaling_factors_avx2[i], scaling_factors_sse42[i], i); - printf("Buffer: %d Scaled: %d\n", buffer[i], scaled); - printf("Iteration %d\n", t); - return 1; - } - } - } - for (i=0; i: { * pass a new \c rtp object with \c ip and \c port in a \c configure * request on the participant handle, which will finalize the media * establishment. - * - * \subsection ablimiter AudioBridge limiter - * - * The AudioBridge plugin has a built-in audio limiter, which can be used - * to limit the volume of the audio stream coming to participants. - * This limiter is disabled by default, and can be enabled by setting - * the \c use_limiter property to \c true in the \c create request. - * The limiter is applied to recording (if there are 2 or more participants - * speaking) and to all tracks that participants receive from the - * AudioBridge plugin (if there are 2 or mote participants speaking besides - * this participant). - * - * The limiter contains code parts adapted from the WebRTC code which is - * licensed under the BSD license: -\verbatim -Copyright (c) 2011, The WebRTC project authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -\endverbatim * */ @@ -1266,6 +1221,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifdef HAVE_RNNOISE #include #endif +/* We ship our own version of */ +#include "audiobridge-deps/limiter/limiter.h" #include #include @@ -1288,8 +1245,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../utils.h" #include "../ip-utils.h" -#include "audiobridge-deps/limiter/limiter.h" - /* Plugin information */ #define JANUS_AUDIOBRIDGE_VERSION 13 #define JANUS_AUDIOBRIDGE_VERSION_STRING "0.0.13" @@ -2609,8 +2564,8 @@ int janus_audiobridge_init(janus_callbacks *callback, const char *config_path) { #endif /* Setting function pointers according to runtime vectorization support (AVX2, SSE4.2 or scalar) */ + JANUS_LOG(LOG_INFO, "Initializing optimized limiter implementation\n"); init_limiter(); - JANUS_LOG(LOG_INFO, "Initialized optimized limiter implementation\n"); /* Parse configuration to populate the rooms list */ if(config != NULL) { @@ -3643,7 +3598,7 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s janus_config_add(config, c, janus_config_item_create("audio_level_average", value)); } } - if (audiobridge->use_limiter) + if(audiobridge->use_limiter) janus_config_add(config, c, janus_config_item_create("use_limiter", "true")); if(audiobridge->allow_plainrtp) janus_config_add(config, c, janus_config_item_create("allow_rtp_participants", "true")); @@ -3826,7 +3781,7 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s janus_config_add(config, c, janus_config_item_create("audio_level_average", value)); } } - if (audiobridge->use_limiter) + if(audiobridge->use_limiter) janus_config_add(config, c, janus_config_item_create("use_limiter", "true")); if(audiobridge->allow_plainrtp) janus_config_add(config, c, janus_config_item_create("allow_rtp_participants", "true")); @@ -8533,12 +8488,12 @@ static void *janus_audiobridge_mixer_thread(void *data) { opus_int32 buffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], sumBuffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES]; opus_int16 outBuffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], - fullMixBuffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], + fullmix_buffer[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], resampled[audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES], *curBuffer = NULL, *mixBuffer = NULL; memset(buffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 8 : 4)); memset(sumBuffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 8 : 4)); memset(outBuffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 4 : 2)); - memset(fullMixBuffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 4 : 2)); + memset(fullmix_buffer, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 4 : 2)); memset(resampled, 0, OPUS_SAMPLES*(audiobridge->spatial_audio ? 4 : 2)); /* In case forwarding groups are enabled, we need additional buffers */ @@ -8588,11 +8543,11 @@ static void *janus_audiobridge_mixer_thread(void *data) { float filter_state_level = K_INITIAL_FILTER_STATE_LEVEL; float last_scaling_factor = 1.f; /* In case audio limiter enabled, we need some buffers for it */ - if (audiobridge->use_limiter) { - JANUS_LOG(LOG_INFO, "Audio limiter enabled\n"); - per_sample_scaling_factors = g_malloc0((audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES) * sizeof(float)); + if(audiobridge->use_limiter) { + JANUS_LOG(LOG_INFO, "Audio limiter enabled for room %s (%s)\n", audiobridge->room_id_str, audiobridge->room_name); envelope = g_malloc0(K_SUB_FRAMES_IN_FRAME * sizeof(float)); - scaling_factors = g_malloc0((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); + scaling_factors = g_malloc((K_SUB_FRAMES_IN_FRAME + 1) * sizeof(float)); + per_sample_scaling_factors = g_malloc((audiobridge->spatial_audio ? OPUS_SAMPLES*2 : OPUS_SAMPLES) * sizeof(float)); } /* Base RTP packets, in case there are forwarders involved */ @@ -8923,33 +8878,33 @@ static void *janus_audiobridge_mixer_thread(void *data) { } } /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ - if (audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) + if(audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, samples_in_sub_frame, &filter_state_level, &last_scaling_factor); /* If we have silent participants, calculate once the full mix */ - if (has_silent) { - if (audiobridge->use_limiter && mix_count > 1) { + if(has_silent) { + if(audiobridge->use_limiter && mix_count > 1) { /* If we use limiter and we mixed more than 1 track, apply it */ - scale_buffer(buffer, samples, per_sample_scaling_factors, fullMixBuffer); + scale_buffer(buffer, samples, per_sample_scaling_factors, fullmix_buffer); } else { /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(buffer, samples, fullMixBuffer); + clamp_buffer(buffer, samples, fullmix_buffer); } } /* Are we recording the mix? (only do it if there's someone in, though...) */ if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { /* If we didn't yet calculated full mix, do it now */ - if (!has_silent) { - if (audiobridge->use_limiter && mix_count > 1) { + if(!has_silent) { + if(audiobridge->use_limiter && mix_count > 1) { /* If we use limiter and we mixed more than 1 track, apply it */ - scale_buffer(buffer, samples, per_sample_scaling_factors, fullMixBuffer); + scale_buffer(buffer, samples, per_sample_scaling_factors, fullmix_buffer); } else { /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(buffer, samples, fullMixBuffer); + clamp_buffer(buffer, samples, fullmix_buffer); } } - fwrite(fullMixBuffer, sizeof(opus_int16), samples, audiobridge->recording); + fwrite(fullmix_buffer, sizeof(opus_int16), samples, audiobridge->recording); /* Every 5 seconds we update the wav header */ gint64 now = janus_get_monotonic_time(); if(now - audiobridge->record_lastupdate >= 5*G_USEC_PER_SEC) { @@ -8989,7 +8944,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { janus_mutex_unlock(&p->qmutex); /* Remove the participant's own contribution */ curBuffer = (opus_int16 *)((pkt && pkt->length && !pkt->silence) ? pkt->data : NULL); - if (curBuffer) { + if(curBuffer) { if(!p->stereo) { for(i=0; ivolume_gain == 100) @@ -9025,8 +8980,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { clamp_buffer(sumBuffer, samples, outBuffer); } mixBuffer = outBuffer; - } else if (!has_silent) { - JANUS_LOG(LOG_WARN, "[%s] No packet for participant (%s), but we have no silent participants either\n", audiobridge->room_id_str, p->user_id_str); + } else if(!has_silent) { + /* JANUS_LOG(LOG_WARN, "[%s] No packet for participant (%s), but we have no silent participants either\n", audiobridge->room_id_str, p->user_id_str); */ for(i=0; iuse_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { @@ -9038,7 +8993,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { } mixBuffer = outBuffer; } else { - mixBuffer = fullMixBuffer; + mixBuffer = fullmix_buffer; } /* Enqueue this mixed frame for encoding in the participant thread */ janus_audiobridge_rtp_relay_packet *mixedpkt = g_malloc(sizeof(janus_audiobridge_rtp_relay_packet)); @@ -9318,7 +9273,7 @@ static void *janus_audiobridge_participant_thread(void *data) { g_free(pkt->data); g_free(pkt); break; - } else if (pkt->length == 0) { + } else if(pkt->length == 0) { /* Empty packet => buffer is all zeroes (g_malloc0) => it's silence */ pkt->silence = TRUE; } @@ -9415,7 +9370,7 @@ static void *janus_audiobridge_participant_thread(void *data) { g_free(pkt->data); g_free(pkt); continue; - } else if (pkt->length == 0) { + } else if(pkt->length == 0) { /* Empty packet => buffer is all zeroes (g_malloc0) => it's silence */ pkt->silence = TRUE; } From 07442c9a53ed78cfb892ba1b4c26c12a7fd4d888 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Thu, 13 Nov 2025 14:49:03 +0300 Subject: [PATCH 44/51] fix indentation --- src/plugins/janus_audiobridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 2f5bf9de40..2b54ed6e4e 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8880,7 +8880,7 @@ static void *janus_audiobridge_mixer_thread(void *data) { /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ if(audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, - samples_in_sub_frame, &filter_state_level, &last_scaling_factor); + samples_in_sub_frame, &filter_state_level, &last_scaling_factor); /* If we have silent participants, calculate once the full mix */ if(has_silent) { From bb8f45bceb634b029c738b1f9cc716c0d7f56e18 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 11 Feb 2026 10:17:52 +0300 Subject: [PATCH 45/51] Added links to repository and directory with C++ sources that were used to make audio limiter --- src/plugins/audiobridge-deps/limiter/limiter.c | 5 ++++- src/plugins/audiobridge-deps/limiter/limiter.h | 5 ++++- src/plugins/janus_audiobridge.c | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 1539e89137..3df3ddc90a 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -1,4 +1,7 @@ -/* The code in this file contains code adapted from WebRTC project. */ +/* The code in this file contains code adapted from WebRTC project. + * WebRTC repository: https://webrtc.googlesource.com/src + * Original source directory: modules/audio_processing/agc2 + */ /* Copyright (c) 2011, The WebRTC project authors. All rights reserved. diff --git a/src/plugins/audiobridge-deps/limiter/limiter.h b/src/plugins/audiobridge-deps/limiter/limiter.h index 71569eb37e..170e8a5dd7 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.h +++ b/src/plugins/audiobridge-deps/limiter/limiter.h @@ -1,4 +1,7 @@ -/* The code in this file contains code adapted from WebRTC project. */ +/* The code in this file contains code adapted from WebRTC project. + * WebRTC repository: https://webrtc.googlesource.com/src + * Original source directory: modules/audio_processing/agc2 + */ /* Copyright (c) 2011, The WebRTC project authors. All rights reserved. diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 2b54ed6e4e..287cb56ee9 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -1221,7 +1221,7 @@ room-: { #ifdef HAVE_RNNOISE #include #endif -/* We ship our own version of */ +/* We ship our own version of AGC2 (ported from WebRTC project) */ #include "audiobridge-deps/limiter/limiter.h" #include From 68c0a57936ad2b8e9198bf623310ecd18cf603c2 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 11 Feb 2026 10:35:04 +0300 Subject: [PATCH 46/51] Fix condition for limiter initialization --- src/plugins/janus_audiobridge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 287cb56ee9..53bbeec32c 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8877,8 +8877,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { mix_count++; } } - /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording */ - if(audiobridge->use_limiter && (mix_count > 2 || (audiobridge->recording && mix_count > 1))) + /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording or a silent participant */ + if(audiobridge->use_limiter && (mix_count > 2 || ((audiobridge->recording || has_silent) && mix_count > 1))) compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, samples_in_sub_frame, &filter_state_level, &last_scaling_factor); From 569a388c19a3555b5e4b0e998f435d30b9626759 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Wed, 11 Feb 2026 10:48:25 +0300 Subject: [PATCH 47/51] Simplified condition --- src/plugins/janus_audiobridge.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 53bbeec32c..bb9a3b563b 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8877,8 +8877,8 @@ static void *janus_audiobridge_mixer_thread(void *data) { mix_count++; } } - /* If we use limiter we should initialize it if we have more than 2 tracks mixed or we have more than 1 track mixed and recording or a silent participant */ - if(audiobridge->use_limiter && (mix_count > 2 || ((audiobridge->recording || has_silent) && mix_count > 1))) + /* If limiter is enabled and we use limiter (i.e. there are at least 2 tracks mixed) we should initialize it */ + if(audiobridge->use_limiter && mix_count > 1) compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, samples_in_sub_frame, &filter_state_level, &last_scaling_factor); From 0e4f4a021cf83d5400840fbd54e6c7558f0739c8 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Fri, 20 Feb 2026 14:12:40 +0300 Subject: [PATCH 48/51] Simplify initialization logic and use fullmix_buffer where needed --- src/plugins/janus_audiobridge.c | 45 ++++++++------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index bb9a3b563b..3d131d1ba0 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -8882,28 +8882,16 @@ static void *janus_audiobridge_mixer_thread(void *data) { compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, samples_in_sub_frame, &filter_state_level, &last_scaling_factor); - /* If we have silent participants, calculate once the full mix */ - if(has_silent) { - if(audiobridge->use_limiter && mix_count > 1) { - /* If we use limiter and we mixed more than 1 track, apply it */ - scale_buffer(buffer, samples, per_sample_scaling_factors, fullmix_buffer); - } else { - /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(buffer, samples, fullmix_buffer); - } + /* Always calculate the full mix when limiter is enabled and we have multiple mixed tracks */ + if(audiobridge->use_limiter && mix_count > 1) { + /* Apply limiter to create the fullmix_buffer */ + scale_buffer(buffer, samples, per_sample_scaling_factors, fullmix_buffer); + } else { + /* Otherwise just clamp values that are outside int16 boundaries */ + clamp_buffer(buffer, samples, fullmix_buffer); } /* Are we recording the mix? (only do it if there's someone in, though...) */ if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { - /* If we didn't yet calculated full mix, do it now */ - if(!has_silent) { - if(audiobridge->use_limiter && mix_count > 1) { - /* If we use limiter and we mixed more than 1 track, apply it */ - scale_buffer(buffer, samples, per_sample_scaling_factors, fullmix_buffer); - } else { - /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(buffer, samples, fullmix_buffer); - } - } fwrite(fullmix_buffer, sizeof(opus_int16), samples, audiobridge->recording); /* Every 5 seconds we update the wav header */ gint64 now = janus_get_monotonic_time(); @@ -8980,18 +8968,6 @@ static void *janus_audiobridge_mixer_thread(void *data) { clamp_buffer(sumBuffer, samples, outBuffer); } mixBuffer = outBuffer; - } else if(!has_silent) { - /* JANUS_LOG(LOG_WARN, "[%s] No packet for participant (%s), but we have no silent participants either\n", audiobridge->room_id_str, p->user_id_str); */ - for(i=0; iuse_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { - /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ - scale_buffer(sumBuffer, samples, per_sample_scaling_factors, outBuffer); - } else { - /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(sumBuffer, samples, outBuffer); - } - mixBuffer = outBuffer; } else { mixBuffer = fullmix_buffer; } @@ -9052,8 +9028,9 @@ static void *janus_audiobridge_mixer_thread(void *data) { if(go_on) { /* By default, let's send the mixed frame to everybody */ if(groups_num == 0) { + /* Use the fullmix_buffer */ for(i=0; i 0) { if(rfm->group == 0) { - /* We're forwarding the main mix */ + /* We're forwarding the main mix - use the fullmix_buffer */ for(i=0; igroup-1; From 64048a958c3b3b204f76226d94c3394f9f9c733a Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 30 Jun 2026 13:19:01 +0300 Subject: [PATCH 49/51] add janus_audiobridge_ prefix to all new functions --- .../audiobridge-deps/limiter/limiter.c | 104 +++++++++--------- .../audiobridge-deps/limiter/limiter.h | 14 +-- src/plugins/janus_audiobridge.c | 12 +- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/plugins/audiobridge-deps/limiter/limiter.c b/src/plugins/audiobridge-deps/limiter/limiter.c index 3df3ddc90a..1909ccc770 100644 --- a/src/plugins/audiobridge-deps/limiter/limiter.c +++ b/src/plugins/audiobridge-deps/limiter/limiter.c @@ -126,14 +126,14 @@ static float approximation_params_q[K_INTERPOLATED_GAIN_CURVE_TOTAL_POINTS] = { }; /* Function pointers for the selected implementation */ -static void (*compute_max_envelope_func)(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame) = NULL; -static void (*calculate_scaling_factors_func)(float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) = NULL; -static void (*compute_per_sample_scaling_factors_func)(float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) = NULL; -static void (*scale_buffer_func)(opus_int32 *buffer, int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer) = NULL; -static void (*clamp_buffer_func)(opus_int32 *buffer, int samples, opus_int16 *outBuffer) = NULL; +static void (*janus_audiobridge_compute_max_envelope_func)(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame) = NULL; +static void (*janus_audiobridge_calculate_scaling_factors_func)(float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) = NULL; +static void (*janus_audiobridge_compute_per_sample_scaling_factors_func)(float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) = NULL; +static void (*janus_audiobridge_scale_buffer_func)(opus_int32 *buffer, int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer) = NULL; +static void (*janus_audiobridge_clamp_buffer_func)(opus_int32 *buffer, int samples, opus_int16 *outBuffer) = NULL; #if defined(__AVX2__) -static void compute_max_envelope_avx2(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ +static void janus_audiobridge_compute_max_envelope_avx2(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; /* AVX2 implementation - process 8 32-bit integers at a time */ @@ -176,7 +176,7 @@ static void compute_max_envelope_avx2(opus_int32 *buffer, float envelope[K_SUB_F } } } -static void calculate_scaling_factors_avx2( +static void janus_audiobridge_calculate_scaling_factors_avx2( float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { @@ -283,7 +283,7 @@ static void calculate_scaling_factors_avx2( *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; } -static void compute_per_sample_scaling_factors_avx2( +static void janus_audiobridge_compute_per_sample_scaling_factors_avx2( float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) { @@ -329,7 +329,7 @@ static void compute_per_sample_scaling_factors_avx2( } } -static void scale_buffer_avx2( +static void janus_audiobridge_scale_buffer_avx2( opus_int32 *buffer, int samples, float *per_sample_scaling_factors, @@ -366,7 +366,7 @@ static void scale_buffer_avx2( outBuffer[i] = sample; } } -static void clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ +static void janus_audiobridge_clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i = 0; /* Process 8 elements at a time */ for (; i + 8 <= samples; i += 8) { @@ -393,7 +393,7 @@ static void clamp_buffer_avx2(opus_int32 *buffer, int samples, opus_int16 *outBu } #endif #if defined(__SSE4_2__) -static void scale_buffer_sse42( +static void janus_audiobridge_scale_buffer_sse42( opus_int32 *buffer, int samples, float *per_sample_scaling_factors, @@ -428,7 +428,7 @@ static void scale_buffer_sse42( } } -static void compute_per_sample_scaling_factors_sse42( +static void janus_audiobridge_compute_per_sample_scaling_factors_sse42( float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) { @@ -474,7 +474,7 @@ static void compute_per_sample_scaling_factors_sse42( } } } -static void compute_max_envelope_sse42(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ +static void janus_audiobridge_compute_max_envelope_sse42(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; /* SSE4.2 implementation - process 4 floats at a time */ @@ -514,7 +514,7 @@ static void compute_max_envelope_sse42(opus_int32 *buffer, float envelope[K_SUB_ } } } -static void calculate_scaling_factors_sse42( +static void janus_audiobridge_calculate_scaling_factors_sse42( float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { @@ -621,7 +621,7 @@ static void calculate_scaling_factors_sse42( *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; } -static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ +static void janus_audiobridge_clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i = 0; /* Process 4 elements at a time */ for (; i + 4 <= samples; i += 4) { @@ -645,7 +645,7 @@ static void clamp_buffer_sse42(opus_int32 *buffer, int samples, opus_int16 *outB } #endif -static void compute_max_envelope_scalar(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ +static void janus_audiobridge_compute_max_envelope_scalar(opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame){ /* Compute max envelope without smoothing. */ int sub_frame, sample_in_sub_frame; for (sub_frame = 0; sub_frame < K_SUB_FRAMES_IN_FRAME; ++sub_frame) { @@ -655,13 +655,13 @@ static void compute_max_envelope_scalar(opus_int32 *buffer, float envelope[K_SUB } } -static inline __attribute__((always_inline)) void compute_envelope( +static inline __attribute__((always_inline)) void janus_audiobridge_compute_envelope( opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], int samples_in_sub_frame, float *filter_state_level) { int sub_frame; - compute_max_envelope_func(buffer, envelope, samples_in_sub_frame); + janus_audiobridge_compute_max_envelope_func(buffer, envelope, samples_in_sub_frame); /* Make sure envelope increases happen one step earlier so that the * corresponding *gain decrease* doesn't miss a sudden signal @@ -683,7 +683,7 @@ static inline __attribute__((always_inline)) void compute_envelope( *filter_state_level = envelope[sub_frame]; } } -static void compute_per_sample_scaling_factors_scalar( +static void janus_audiobridge_compute_per_sample_scaling_factors_scalar( float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *per_sample_scaling_factors, int samples_in_sub_frame) { @@ -708,7 +708,7 @@ static void compute_per_sample_scaling_factors_scalar( } } -static void calculate_scaling_factors_scalar( +static void janus_audiobridge_calculate_scaling_factors_scalar( float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], float *last_scaling_factor) { @@ -749,7 +749,7 @@ static void calculate_scaling_factors_scalar( *last_scaling_factor = scaling_factors[K_SUB_FRAMES_IN_FRAME]; } -inline __attribute__((always_inline)) void compute_scaling_factors( +inline __attribute__((always_inline)) void janus_audiobridge_compute_scaling_factors( opus_int32 *buffer, float envelope[K_SUB_FRAMES_IN_FRAME], float scaling_factors[K_SUB_FRAMES_IN_FRAME + 1], @@ -762,12 +762,12 @@ inline __attribute__((always_inline)) void compute_scaling_factors( * Original WebRTC code: https://webrtc.googlesource.com/src * Licensed under BSD 3-Clause License. */ - compute_envelope(buffer, envelope, samples_in_sub_frame, filter_state_level); - calculate_scaling_factors_func(envelope, scaling_factors, last_scaling_factor); - compute_per_sample_scaling_factors_func(scaling_factors, per_sample_scaling_factors, samples_in_sub_frame); + janus_audiobridge_compute_envelope(buffer, envelope, samples_in_sub_frame, filter_state_level); + janus_audiobridge_calculate_scaling_factors_func(envelope, scaling_factors, last_scaling_factor); + janus_audiobridge_compute_per_sample_scaling_factors_func(scaling_factors, per_sample_scaling_factors, samples_in_sub_frame); } -static void scale_buffer_scalar( +static void janus_audiobridge_scale_buffer_scalar( opus_int32 *buffer, int samples, float *per_sample_scaling_factors, @@ -784,16 +784,16 @@ static void scale_buffer_scalar( } } -inline __attribute__((always_inline)) void scale_buffer( +inline __attribute__((always_inline)) void janus_audiobridge_scale_buffer( opus_int32 *buffer, int samples, float *per_sample_scaling_factors, opus_int16 *outBuffer){ - scale_buffer_func(buffer, samples, per_sample_scaling_factors, outBuffer); + janus_audiobridge_scale_buffer_func(buffer, samples, per_sample_scaling_factors, outBuffer); } -static void clamp_buffer_scalar(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ +static void janus_audiobridge_clamp_buffer_scalar(opus_int32 *buffer, int samples, opus_int16 *outBuffer){ int i; opus_int32 sample; for(i=0; iuse_limiter && mix_count > 1) - compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, + janus_audiobridge_compute_scaling_factors(buffer, envelope, scaling_factors, per_sample_scaling_factors, samples_in_sub_frame, &filter_state_level, &last_scaling_factor); /* Always calculate the full mix when limiter is enabled and we have multiple mixed tracks */ if(audiobridge->use_limiter && mix_count > 1) { /* Apply limiter to create the fullmix_buffer */ - scale_buffer(buffer, samples, per_sample_scaling_factors, fullmix_buffer); + janus_audiobridge_scale_buffer(buffer, samples, per_sample_scaling_factors, fullmix_buffer); } else { /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(buffer, samples, fullmix_buffer); + janus_audiobridge_clamp_buffer(buffer, samples, fullmix_buffer); } /* Are we recording the mix? (only do it if there's someone in, though...) */ if(audiobridge->recording != NULL && g_list_length(participants_list) > 0) { @@ -9036,10 +9036,10 @@ static void *janus_audiobridge_mixer_thread(void *data) { } if(audiobridge->use_limiter && (mix_count - (curBuffer ? 1 : 0)) > 1) { /* If we use limiter and sumBuffer contains mix of more than 1 track, apply it */ - scale_buffer(sumBuffer, samples, per_sample_scaling_factors, outBuffer); + janus_audiobridge_scale_buffer(sumBuffer, samples, per_sample_scaling_factors, outBuffer); } else { /* Otherwise just clamp values that are outside int16 boundaries */ - clamp_buffer(sumBuffer, samples, outBuffer); + janus_audiobridge_clamp_buffer(sumBuffer, samples, outBuffer); } mixBuffer = outBuffer; } else { From d1975235beb8584c9e11fc0bc2fd3232b3abf7e2 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 30 Jun 2026 13:34:24 +0300 Subject: [PATCH 50/51] Add "use_limiter" info to 'list' command result and it's documentation (and remove duplicated piece of docs) --- src/plugins/janus_audiobridge.c | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/src/plugins/janus_audiobridge.c b/src/plugins/janus_audiobridge.c index 82e832e975..f97acf4e27 100644 --- a/src/plugins/janus_audiobridge.c +++ b/src/plugins/janus_audiobridge.c @@ -473,36 +473,7 @@ room-: { "pin_required" : , "sampling_rate" : , "spatial_audio" : , - "record" : , - "num_participants" : - }, - // Other rooms - ] -} -\endverbatim - * - * To get a list of the available rooms (excluded those configured or - * created as private rooms) you can make use of the \c list request, - * which has to be formatted as follows: - * -\verbatim -{ - "request" : "list" -} -\endverbatim - * - * A successful request will produce a list of rooms in a \c success response: - * -\verbatim -{ - "audiobridge" : "success", - "rooms" : [ // Array of room objects - { // Room #1 - "room" : , - "description" : "", - "pin_required" : , - "sampling_rate" : , - "spatial_audio" : , + "use_limiter" : "record" : , "num_participants" : }, @@ -4135,6 +4106,7 @@ static json_t *janus_audiobridge_process_synchronous_request(janus_audiobridge_s json_object_set_new(rl, "description", json_string(room->room_name)); json_object_set_new(rl, "sampling_rate", json_integer(room->sampling_rate)); json_object_set_new(rl, "spatial_audio", room->spatial_audio ? json_true() : json_false()); + json_object_set_new(rl, "use_limiter", room->use_limiter ? json_true() : json_false()); json_object_set_new(rl, "pin_required", room->room_pin ? json_true() : json_false()); json_object_set_new(rl, "record", g_atomic_int_get(&room->record) ? json_true() : json_false()); json_object_set_new(rl, "muted", room->muted ? json_true() : json_false()); From d7ec2d0562b84e3f863c78e1c743ecdc03494b19 Mon Sep 17 00:00:00 2001 From: "val.v.petrov" Date: Tue, 30 Jun 2026 14:47:30 +0300 Subject: [PATCH 51/51] Add use_limiter to the sample audiobridge configuration (defaulted to false) --- conf/janus.plugin.audiobridge.jcfg.sample | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/janus.plugin.audiobridge.jcfg.sample b/conf/janus.plugin.audiobridge.jcfg.sample index 8cfbc398f6..05fdef1c55 100644 --- a/conf/janus.plugin.audiobridge.jcfg.sample +++ b/conf/janus.plugin.audiobridge.jcfg.sample @@ -7,6 +7,7 @@ # only works if signed tokens are used in the core as well) # sampling_rate = (e.g., 16000 for wideband mixing) # spatial_audio = true|false (if true, the mix will be stereo to spatially place users, default=false) +# use_limiter = true|false (whether to use a limiter to avoid clipping, default=false) # audiolevel_ext = true|false (whether the ssrc-audio-level RTP extension must # be negotiated/used or not for new joins, default=true) # audiolevel_event = true|false (whether to emit event to other users or not, default=false) @@ -80,6 +81,7 @@ room-1234: { secret = "adminpwd" sampling_rate = 16000 record = false + use_limiter = false #record_dir = "/path/to/" #record_file = "recording.wav" }