Files
BPI-CM9-Linux/aml-comp/multimedia/aml_audio_hal/utils/aml_audio_limiter.c
T

1031 lines
33 KiB
C
Raw Normal View History

2026-08-13 11:28:40 +08:00
/*
* Copyright (C) 2025 Amlogic Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "audio_hw_limiter"
#include <math.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <system/audio.h>
#include <audio_utils/primitives.h>
#include "aml_malloc_debug.h"
#include "aml_dump_debug.h"
#include "audio_data_process.h"
#include "aml_audio_limiter.h"
#include "aml_volume_utils.h"
#include "aml_audio_log.h"
#include "format.h"
// NTS AUDIO-LIMITER-HEAAC-HEAAC :
// will detect sample max vule, left small room to avoid reach clipping detect range.
#define LIMITER_INT32_0DB ((INT32_MAX - 128 * INT16_MAX) & 0xFFFF0000)
typedef union {
int32_t i;
float f;
} limiter_value;
typedef enum limiter_ease_type {
LimEase_Linear = 0, // small gain
LimEase_InOutQuad, // large gain
LimEase_InOutCubic, // not used it
LimEase_InOutQuart, // not used it
LimEase_Max
} limiter_ease_type_e;
typedef struct limiter_curve {
limiter_ease_type_e type;
float *data_buf_ptr;
size_t data_buf_size;
int data_len;
} limiter_curve_st;
typedef struct aml_audio_limiter {
int enable;
audio_format_t format;
int process_frames;
limiter_value last_sample_max;
float curr_gain;
float last_gain;
float peak_float;
int32_t peak_int32;
float release_factor;
float response_factor;
int release_delay;
int release_delay_count;
// data limit gain apply part
// input data --> delay_buf -> output
//float pre_gain;
float post_gain;
void *delay_buf_ptr;
void *src_temp_buf;
size_t src_temp_buf_size;
size_t delay_buf_size;
2026-11-11 08:50:43 +08:00
aml_planar_buf_st delay_planar_buf;
aml_planar_buf_st temp_planar_buf;
2026-08-13 11:28:40 +08:00
// when process_frames is small,
// use these member to improve waveform
float average_gain;
float out_curr_gain;
float out_last_gain;
int out_lazy_count;
limiter_curve_st curves[LimEase_Max];
} aml_audio_limiter_st;
// function declare
static int limiter_gen_curve_data(aml_audio_limiter_st *lim, limiter_ease_type_e ease_type, int proc_frames);
void* aml_audio_limiter_new(audio_format_t data_format)
{
aml_audio_limiter_st *limiter = NULL;
limiter = (aml_audio_limiter_st *)aml_audio_calloc(1, sizeof(aml_audio_limiter_st));
if (limiter == NULL) {
2026-11-11 08:50:43 +08:00
AM_LOGE("malloc limiter failed!");
2026-08-13 11:28:40 +08:00
return NULL;
}
if (data_format != AUDIO_FORMAT_PCM_32_BIT && data_format != AUDIO_FORMAT_PCM_FLOAT && data_format != AUDIO_FORMAT_PCM_16_BIT) {
2026-11-11 08:50:43 +08:00
AM_LOGE("not support data_format %d", data_format);
2026-08-13 11:28:40 +08:00
aml_audio_free(limiter);
limiter = NULL;
return NULL;
}
if (data_format == AUDIO_FORMAT_PCM_16_BIT) {
data_format = AUDIO_FORMAT_PCM_32_BIT;
}
if (data_format == AUDIO_FORMAT_PCM_FLOAT) {
limiter->last_sample_max.f = 0.0f;
} else {
limiter->last_sample_max.i = 0;
}
limiter->format = data_format;
limiter->curr_gain = 1.0f;
limiter->last_gain = 1.0f;
limiter->out_curr_gain = 1.0f;
limiter->out_last_gain = 1.0f;
limiter->peak_float = 1.0f;
limiter->peak_int32 = LIMITER_INT32_0DB;
limiter->average_gain = 0.0f;
limiter->post_gain = 1.0f;
limiter->delay_buf_ptr = NULL;
limiter->delay_buf_size = 0;
limiter->src_temp_buf = NULL;
limiter->src_temp_buf_size = 0;
2026-11-11 08:50:43 +08:00
AM_LOGI("success ,limiter->format =%d", limiter->format);
2026-08-13 11:28:40 +08:00
return limiter;
}
void aml_audio_limiter_free(void *limiter)
{
int i = 0;
limiter_curve_st *p_curve = NULL;
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
if (lim == NULL) {
2026-11-11 08:50:43 +08:00
AM_LOGE("limiter is NULL");
2026-08-13 11:28:40 +08:00
return;
}
if (lim->src_temp_buf != NULL) {
2026-11-11 08:50:43 +08:00
AM_LOGI("free delay_buf_ptr %p, bytes %zu", lim->delay_buf_ptr, lim->src_temp_buf_size);
2026-08-13 11:28:40 +08:00
aml_audio_free(lim->src_temp_buf);
lim->src_temp_buf = NULL;
lim->src_temp_buf_size = 0;
}
if (lim->delay_buf_ptr != NULL) {
2026-11-11 08:50:43 +08:00
AM_LOGI("free delay_buf_ptr %p, bytes %zu", lim->delay_buf_ptr, lim->delay_buf_size);
2026-08-13 11:28:40 +08:00
aml_audio_free(lim->delay_buf_ptr);
lim->delay_buf_ptr = NULL;
lim->delay_buf_size = 0;
}
2026-11-11 08:50:43 +08:00
aml_planar_free_buf(&lim->delay_planar_buf);
aml_planar_free_buf(&lim->temp_planar_buf);
2026-08-13 11:28:40 +08:00
for (i = 0; i < LimEase_Max; i++) {
p_curve = &lim->curves[i];
if (p_curve->data_buf_ptr) {
2026-11-11 08:50:43 +08:00
AM_LOGI("free curves type %d, bytes %zu", p_curve->type, p_curve->data_buf_size);
2026-08-13 11:28:40 +08:00
aml_audio_free(p_curve->data_buf_ptr);
p_curve->data_buf_ptr = NULL;
p_curve->data_buf_size = 0;
p_curve->data_len = 0;
}
}
lim->enable = 0;
aml_audio_free(lim);
2026-11-11 08:50:43 +08:00
AM_LOGI("success");
2026-08-13 11:28:40 +08:00
}
static void limiter_smooth_output_gain(aml_audio_limiter_st *lim)
{
int percent = 0;
float gain_step = 0.0f;
float last_average = 0.0f;
float diff_value = 0.0f;
float out_curr_gain = 0.0f;
const float AVERAGE_FACTOR = 0.05;
if (lim == NULL || lim->process_frames <= 0) return;
// when process_frames >=256 frames, output gain is good enough to handle extreme case.
if (lim->process_frames >= 256) {
lim->out_last_gain = lim->out_curr_gain;
lim->out_curr_gain = lim->curr_gain;
return;
}
lim->out_last_gain = lim->out_curr_gain;
out_curr_gain = lim->curr_gain;
if (is_float_equal(lim->last_gain, 1.0f) && is_float_equal(lim->curr_gain, 1.0f)) {
lim->out_curr_gain = 1.0f;
lim->average_gain = 0;
lim->out_lazy_count = 0;
return;
}
last_average = lim->average_gain;
gain_step = (lim->curr_gain - lim->last_gain) / lim->process_frames;
lim->average_gain = lim->average_gain * (1 - AVERAGE_FACTOR) + lim->curr_gain * AVERAGE_FACTOR;
diff_value = lim->average_gain - last_average;
percent = (diff_value * 100 / AVERAGE_FACTOR);
// sinetone sample is stable, avoid tiny jitter
2026-11-11 08:50:43 +08:00
//AM_LOGV("percent %d, __gain_step %f,", percent, gain_step);
2026-08-13 11:28:40 +08:00
if (percent == 0) {
if (gain_step > 0.000001) {
lim->out_lazy_count += 1;
} else if (gain_step < 0) {
lim->out_lazy_count = 0;
}
if (gain_step > 0.000001 && lim->out_lazy_count <= 2 && (lim->curr_gain - lim->last_gain < 0.005)) {
out_curr_gain = lim->out_last_gain;
}
}
lim->out_curr_gain = out_curr_gain;
}
static float limiter_find_sample_max_float(const float *buffer, const int count)
{
int i = 0;
float sample_max = 0.0f;
float sample_abs = 0.0f;
for (i = 0; i < count; i++) {
sample_abs = (buffer[i] < 0 ? -(buffer[i]) : buffer[i]);
if (sample_max < sample_abs) {
sample_max = sample_abs;
}
}
return sample_max;
}
static int32_t limiter_find_sample_max_int32(const int32_t *buffer, const int count)
{
int i = 0;
int32_t sample_max = 0;
int32_t sample_abs = 0;
for (i = 0; i < count; i++) {
sample_abs = (buffer[i] < 0 ? -(buffer[i]) : buffer[i]);
if (sample_max < sample_abs) {
sample_max = sample_abs;
}
}
return sample_max;
}
2026-11-11 08:50:43 +08:00
static float limiter_planar_find_sample_max_float(
const aml_planar_buf_st *p_planar_buf, int frame_offset, int proc_frames)
{
int i = 0;
int j = 0;
float sample_max = 0.0f;
float sample_abs = 0.0f;
const float *pch = NULL;
const float **ppdata = (const float **)p_planar_buf->ch_data;
int ch_num = p_planar_buf->ch_num;
int buf_frames = p_planar_buf->per_ch_frames;
if (frame_offset + proc_frames > buf_frames) {
AM_LOGW("offset %d + proc %d > buf_frames %d", frame_offset, proc_frames, buf_frames);
proc_frames = buf_frames - frame_offset;
if (proc_frames < 0) {
proc_frames = 0;
}
}
for (i = 0; i < ch_num; i++) {
pch = ppdata[i] + frame_offset;
for (j = 0; j < proc_frames; j++) {
sample_abs = (pch[j] < 0 ? -(pch[j]) : pch[j]);
if (sample_max < sample_abs) {
sample_max = sample_abs;
}
}
}
return sample_max;
}
static int32_t limiter_planar_find_sample_max_int32(
const aml_planar_buf_st *p_planar_buf, int frame_offset, int proc_frames)
{
int i = 0;
int j = 0;
int32_t sample_max = 0;
int32_t sample_abs = 0;
const int32_t *pch = NULL;
const int32_t **ppdata = (const int32_t **)p_planar_buf->ch_data;
int ch_num = p_planar_buf->ch_num;
int buf_frames = p_planar_buf->per_ch_frames;
if (frame_offset + proc_frames > buf_frames) {
AM_LOGW("offset %d + proc %d > buf_frames %d", frame_offset, proc_frames, buf_frames);
proc_frames = buf_frames - frame_offset;
if (proc_frames < 0) {
proc_frames = 0;
}
}
for (i = 0; i < ch_num; i++) {
pch = ppdata[i] + frame_offset;
for (j = 0; j < proc_frames; j++) {
sample_abs = (pch[j] < 0 ? -(pch[j]) : pch[j]);
if (sample_max < sample_abs) {
sample_max = sample_abs;
}
}
}
return sample_max;
}
static limiter_value limiter_planar_get_block_max_value(void *limiter,
const aml_planar_buf_st *p_planar_buf, int frame_offset, int proc_frames)
{
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
limiter_value max_value;
memset(&max_value, 0, sizeof(max_value));
if (lim->format == AUDIO_FORMAT_PCM_FLOAT) {
max_value.f = limiter_planar_find_sample_max_float(p_planar_buf, frame_offset, proc_frames);
} else {
max_value.i = limiter_planar_find_sample_max_int32(p_planar_buf, frame_offset, proc_frames);
}
return max_value;
}
static void limiter_calculate_gain_from_max(void *limiter, limiter_value current_max)
2026-08-13 11:28:40 +08:00
{
float gain_new = 1.0f;
const float PRECISION = 1e-04;
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
int debug_value = get_debug_value(AML_DEBUG_AUDIOHAL_LIMITER);
float f_sample_max = 0.0f;
float f_current_max = 0.0f;
int i_sample_max = 0;
int i_current_max = 0;
if (lim == NULL || lim->enable == 0) {
return;
}
if (lim->format == AUDIO_FORMAT_PCM_FLOAT) {
2026-11-11 08:50:43 +08:00
f_current_max = current_max.f;
2026-08-13 11:28:40 +08:00
f_sample_max = f_current_max;
if (f_sample_max < lim->last_sample_max.f) {
f_sample_max = lim->last_sample_max.f;
}
lim->last_sample_max.f = f_current_max;
if (debug_value) {
2026-11-11 08:50:43 +08:00
AM_LOGI("f_current_max %f, f_sample_max %f", f_current_max, f_sample_max);
2026-08-13 11:28:40 +08:00
}
} else {
2026-11-11 08:50:43 +08:00
i_current_max = current_max.i;
2026-08-13 11:28:40 +08:00
i_sample_max = i_current_max;
if (i_sample_max < lim->last_sample_max.i) {
i_sample_max = lim->last_sample_max.i;
}
lim->last_sample_max.i = i_current_max;
if (debug_value) {
2026-11-11 08:50:43 +08:00
AM_LOGI("i_current_max %d, i_sample_max %d", i_current_max, i_sample_max);
2026-08-13 11:28:40 +08:00
}
}
lim->last_gain = lim->curr_gain;
if (lim->format == AUDIO_FORMAT_PCM_FLOAT) {
if (f_sample_max > lim->peak_float && f_sample_max != 0) {
gain_new = lim->peak_float / f_sample_max;
}
} else {
if (i_sample_max > lim->peak_int32 && i_sample_max != 0) {
gain_new = ((float)lim->peak_int32) / i_sample_max;
}
}
if (!is_float_equal(gain_new, 1.0f) || !is_float_equal(lim->last_gain, 1.0f)) {
if (gain_new <= lim->curr_gain) {
lim->curr_gain = gain_new;
lim->release_delay_count = 0;
} else {
if (lim->release_delay_count < lim->release_delay) {
lim->release_delay_count += 1;
} else {
lim->curr_gain += (gain_new - lim->curr_gain) * lim->release_factor;
}
}
}
if (lim->curr_gain > 1.0f) {
lim->curr_gain = 1.0f;
}
if ((fabs(lim->curr_gain - 1.0f) < PRECISION)) {
lim->curr_gain = 1.0f;
}
if (debug_value) {
2026-11-11 08:50:43 +08:00
AM_LOGI("lim %p curr_gain %f", lim, lim->curr_gain);
2026-08-13 11:28:40 +08:00
}
limiter_smooth_output_gain(lim);
2026-11-11 08:50:43 +08:00
}
2026-08-13 11:28:40 +08:00
2026-11-11 08:50:43 +08:00
static void limiter_calculate_gain(void *limiter, const void *buffer, const int sample_count)
{
limiter_value current_max;
float *float_buf = (float *)buffer;
int *int32_buf = (int *)buffer;
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
memset(&current_max, 0, sizeof(current_max));
if (lim == NULL || lim->enable == 0) {
return;
}
if (lim->format == AUDIO_FORMAT_PCM_FLOAT) {
current_max.f = limiter_find_sample_max_float(float_buf, sample_count);
} else {
current_max.i = limiter_find_sample_max_int32(int32_buf, sample_count);
}
limiter_calculate_gain_from_max(limiter, current_max);
2026-08-13 11:28:40 +08:00
}
static inline void limiter_get_gain(aml_audio_limiter_st *limiter, float *last_gain, float *curr_gain)
{
*last_gain = limiter->out_last_gain;
*curr_gain = limiter->out_curr_gain;
}
static float limiter_calculate_factor(int time_ms, int proc_frames, int sample_rate)
{
/*
* Y = Y * factor + X * (1 - factor)
* Yn − X = (factor)^n ∗(Y0 - X)
* (factor)^n = (Yn - X) / (Y0 − X)
* (factor)^n < target
*
* count * log(factor) < log(target)
* log(factor) < log(target) / count
*
* factor = exp(log(target) / count)
*/
double factor = 1.0f;
float count = (float)time_ms * sample_rate / (proc_frames * 1000);
const float TARGET = 5.0f / 100; // Diff 0.05 : is regarded attack or release action complete
2026-11-11 08:50:43 +08:00
AM_LOGI("count %.04f, proc_frames %d, sample_rate %d, time_ms %d, target %f", count, proc_frames, sample_rate, time_ms, TARGET);
2026-08-13 11:28:40 +08:00
factor = exp(log(TARGET) / count);
// arrvie 1/2 X :
int half_index = log(1 - 0.5) / log(factor);
2026-11-11 08:50:43 +08:00
AM_LOGI("arrive half of target = %d\n", half_index);
2026-08-13 11:28:40 +08:00
return factor;
}
#if 0
//for debug
static void limiter_verify_factor(float factor, int count)
{
float X = 2.0f;
float Y = 1.0f;
int i = 0;
for (i = 0; i < count; i++) {
2026-11-11 08:50:43 +08:00
AM_LOGD("%f", Y);
2026-08-13 11:28:40 +08:00
Y = Y * factor + X * (1.0f - factor);
}
}
#endif
int aml_audio_limiter_set(void *limiter, float peak_level, int attack_time, int release_time, int proc_frames, limiter_config_mode_e limiter_type)
{
char info_buf[32];
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
if (lim == NULL) {
return -1;
}
if (attack_time < 30) {
attack_time = 30;
} else if (attack_time > 300) {
attack_time = 300;
}
if (release_time < 60) {
release_time = 60;
} else if (release_time > 600) {
release_time = 600;
}
if (peak_level < 0.0f || peak_level > 1.0f) {
2026-11-11 08:50:43 +08:00
AM_LOGE("fail, invalid peak_level %f !", peak_level);
2026-08-13 11:28:40 +08:00
return -1;
}
lim->release_factor = limiter_calculate_factor(release_time, proc_frames, 48000);
lim->response_factor = limiter_calculate_factor(attack_time, proc_frames, 48000);
lim->process_frames = proc_frames;
lim->release_delay = -1;
switch (proc_frames) {
case 384:
lim->release_delay = 1;
break;
case 256:
lim->release_delay = 2;
break;
case 120:
case 128:
lim->release_delay = 3;
break;
case 64:
lim->release_delay = 6;
break;
default:
break;
}
if (lim->release_delay <= 0) {
2026-11-11 08:50:43 +08:00
AM_LOGE("fail, not support proc_frames %d !", proc_frames);
2026-08-13 11:28:40 +08:00
return -1;
}
//limiter_gen_curve_data(lim, LimEase_Linear, proc_frames);
limiter_gen_curve_data(lim, LimEase_InOutQuad, proc_frames);
memset(info_buf, 0, sizeof(info_buf));
if (lim->format == AUDIO_FORMAT_PCM_FLOAT) {
lim->peak_float = peak_level;
snprintf(info_buf, sizeof(info_buf)-1, "float %.03f", peak_level);
} else {
if (is_float_equal(peak_level, 0.125)) {
lim->peak_int32 = LIMITER_INT32_0DB / 8;
} else if (is_float_equal(peak_level, 0.25)) {
lim->peak_int32 = LIMITER_INT32_0DB / 4;
} else if (is_float_equal(peak_level, 0.5)) {
lim->peak_int32 = LIMITER_INT32_0DB / 2;
} else {
lim->peak_int32 = LIMITER_INT32_0DB * peak_level;
}
snprintf(info_buf, sizeof(info_buf)-1, "int32 %d", lim->peak_int32);
}
//lim->pre_gain = peak_level;
if (limiter_type == AML_LIMITER) {
lim->post_gain = 1.0f / peak_level; //Use limiter
} else {
lim->post_gain = 1.0f; //Use limiter as DRC
}
lim->average_gain = 1.0f;
lim->out_lazy_count = 0;
2026-11-11 08:50:43 +08:00
AM_LOGI("release_factor = %f(release_time %d ms), process_frames = %d, release_delay = %d", lim->release_factor, release_time, lim->process_frames, lim->release_delay);
AM_LOGI("peak_level: %f (dB %.03f, %s), post_gain %.03f,\n", peak_level, AmplToDb(peak_level), info_buf, lim->post_gain);
2026-08-13 11:28:40 +08:00
return 0;
}
static inline float limiter_ease_next(limiter_ease_type_e ease, float t, float b, float c, float d)
{
float result = 0.0f;
switch (ease) {
case LimEase_Linear:
result = (c * t) / d + b;
break;
case LimEase_InOutQuad:
if ((t /= d / 2) < 1) {
result = ((c / 2) * (t * t)) + b;
} else {
--t;
result = -c / 2 * (((t - 2) * t) - 1) + b;
}
break;
case LimEase_InOutCubic:
if ((t /= d / 2) < 1) {
result = c / 2 * t * t * t + b;
} else {
t -= 2;
result = c / 2 * (t * t * t + 2) + b;
}
break;
case LimEase_InOutQuart:
if ((t /= d / 2) < 1) {
result = c / 2 * t * t * t * t + b;
} else {
t -= 2;
result = -c / 2 * (t * t * t * t - 2) + b;
}
break;
default:
break;
}
return result;
}
static int limiter_gen_curve_data(aml_audio_limiter_st *lim, limiter_ease_type_e ease_type, int proc_frames)
{
int i = 0;
int ret = 0;
int ease_frames = proc_frames;
float vol_delta = 1.0f;
float start_volume = 0.0f;
float current_volume = 0.0f;
float ease_frames_elapsed = 0.0f;
limiter_curve_st *p_curve = NULL;
size_t req_buf_size = 0;
if (ease_type < 0 || ease_type >= LimEase_Max) {
2026-11-11 08:50:43 +08:00
AM_LOGE("invalid ease_type %d", ease_type);
2026-08-13 11:28:40 +08:00
return -1;
}
p_curve = &lim->curves[ease_type];
req_buf_size = sizeof(float) * proc_frames;
ret = aml_audio_check_and_realloc((void **)&p_curve->data_buf_ptr, &p_curve->data_buf_size, req_buf_size);
if ((ret != 0) || (p_curve->data_buf_ptr == NULL)) {
2026-11-11 08:50:43 +08:00
AM_LOGE("allocate data_buf_ptr(%zu bytes) failed", req_buf_size);
2026-08-13 11:28:40 +08:00
return -1;
}
memset(p_curve->data_buf_ptr, 0, p_curve->data_buf_size);
for (i = 0; i < proc_frames; i++) {
current_volume = limiter_ease_next(ease_type, ease_frames_elapsed, start_volume, vol_delta, ease_frames);
p_curve->data_buf_ptr[i] = current_volume;
ease_frames_elapsed++;
}
p_curve->type = ease_type;
p_curve->data_len = proc_frames;
return 0;
}
static inline float * get_curver_data(aml_audio_limiter_st *lim, limiter_ease_type_e ease_type, int proc_frames)
{
float *p_curve_data = lim->curves[ease_type].data_buf_ptr;
if (p_curve_data == NULL || lim->curves[ease_type].data_len < proc_frames) {
if (limiter_gen_curve_data(lim, ease_type, proc_frames) != 0) {
2026-11-11 08:50:43 +08:00
AM_LOGE("limiter_gen_curve_data type %d fail !", ease_type);
2026-08-13 11:28:40 +08:00
return NULL;
}
p_curve_data = lim->curves[ease_type].data_buf_ptr;
}
return p_curve_data;
}
static void limiter_apply_gain_fade(aml_audio_limiter_st *lim, float last_gain, float curr_gain, void *buf, int frame_num, int channels, int format)
{
int32_t *input32 = (int32_t *)buf;
float *input_float = (float *)buf;
unsigned int i = 0, j= 0;
float gain_step = 0.0f;
float next_gain = 1.0f;
float diff_gain = 0.0f;
float *p_fade_curve = NULL;
int debug_value = get_debug_value(AML_DEBUG_AUDIOHAL_LIMITER);
if (buf == NULL || channels == 0 || frame_num == 0) {
return;
}
p_fade_curve = get_curver_data(lim, LimEase_InOutQuad, frame_num);
if (p_fade_curve == NULL) {
2026-11-11 08:50:43 +08:00
AM_LOGE("p_fade_curve is NULL");
2026-08-13 11:28:40 +08:00
return;
}
if (!is_float_equal(last_gain, curr_gain)) {
gain_step = (curr_gain - last_gain)/frame_num;
diff_gain = curr_gain - last_gain;
}
if (format == AUDIO_FORMAT_PCM_32_BIT) {
for (i = 0; i < frame_num; i++) {
//next_gain = last_gain + i * gain_step;
next_gain = last_gain + diff_gain * p_fade_curve[i];
for (j = 0; j < channels; j ++) {
input32[i * channels + j] = clamp32((int64_t)(next_gain * input32[i * channels + j]));
}
}
} else if (format == AUDIO_FORMAT_PCM_FLOAT) {
for (i = 0; i < frame_num; i++) {
//next_gain = last_gain + i * gain_step;
next_gain = last_gain + diff_gain * p_fade_curve[i];
for (j = 0; j < channels; j ++) {
input_float[i * channels + j] = clamp_float((next_gain * input_float[i * channels + j]));
}
}
}
if (debug_value) {
char info_buf[32];
memset(info_buf, 0, sizeof(info_buf));
if (format == AUDIO_FORMAT_PCM_32_BIT) {
int sample_max = limiter_find_sample_max_int32(buf, frame_num * channels);
snprintf(info_buf, sizeof(info_buf)-1, "%d, %.03f", sample_max, ((float)sample_max)/INT32_MAX);
} else if (format == AUDIO_FORMAT_PCM_FLOAT) {
float sample_max = limiter_find_sample_max_float(buf, frame_num * channels);
snprintf(info_buf, sizeof(info_buf)-1, "%.03f", sample_max);
}
2026-11-11 08:50:43 +08:00
AM_LOGI("%p final sample_max %s", lim, info_buf);
2026-08-13 11:28:40 +08:00
}
return;
}
2026-11-11 08:50:43 +08:00
static void limiter_planar_apply_gain_fade(aml_audio_limiter_st *lim,
float last_gain, float curr_gain, aml_planar_buf_st *p_planar_buf, int frame_num)
{
unsigned int i = 0, j = 0;
float next_gain = 1.0f;
float diff_gain = 0.0f;
float *p_fade_curve = NULL;
int debug_value = get_debug_value(AML_DEBUG_AUDIOHAL_LIMITER);
if (p_planar_buf == NULL || frame_num <= 0) {
return;
}
p_fade_curve = get_curver_data(lim, LimEase_InOutQuad, frame_num);
if (p_fade_curve == NULL) {
AM_LOGE("p_fade_curve is NULL");
return;
}
if (!is_float_equal(last_gain, curr_gain)) {
diff_gain = curr_gain - last_gain;
} else {
if (is_float_equal(curr_gain, 1.0f)) {
goto exit;
}
}
if (frame_num > p_planar_buf->per_ch_frames) {
AM_LOGW("frame_num(%d) truncate to %d", frame_num, p_planar_buf->per_ch_frames);
frame_num = p_planar_buf->per_ch_frames;
}
for (i = 0; i < (unsigned int)p_planar_buf->ch_num; i++) {
if (p_planar_buf->format == AUDIO_FORMAT_PCM_32_BIT) {
int32_t *p_ch_int32 = p_planar_buf->ch_data[i];
for (j = 0; j < (unsigned int)frame_num; j++) {
next_gain = last_gain + diff_gain * p_fade_curve[j];
p_ch_int32[j] = clamp32((int64_t)(next_gain * p_ch_int32[j]));
}
} else if (p_planar_buf->format == AUDIO_FORMAT_PCM_FLOAT) {
float *p_ch_float = p_planar_buf->ch_data[i];
for (j = 0; j < (unsigned int)frame_num; j++) {
next_gain = last_gain + diff_gain * p_fade_curve[j];
p_ch_float[j] = clamp_float((next_gain * p_ch_float[j]));
}
}
}
exit:
if (debug_value) {
char info_buf[32];
memset(info_buf, 0, sizeof(info_buf));
if (p_planar_buf->format == AUDIO_FORMAT_PCM_32_BIT) {
int sample_max = limiter_planar_find_sample_max_int32(p_planar_buf, 0, frame_num);
snprintf(info_buf, sizeof(info_buf)-1, "%d, %.03f", sample_max, ((float)sample_max)/INT32_MAX);
} else if (p_planar_buf->format == AUDIO_FORMAT_PCM_FLOAT) {
float sample_max = limiter_planar_find_sample_max_float(p_planar_buf, 0, frame_num);
snprintf(info_buf, sizeof(info_buf)-1, "%.03f", sample_max);
}
AM_LOGI("%p final sample_max %s", lim, info_buf);
}
}
2026-08-13 11:28:40 +08:00
int aml_audio_limiter_process(void *limiter,
void *dst_buf, const audio_format_t dst_format,
void *src_buf, audio_format_t src_format,
const int channels, const int frame_num)
{
int ret = 0;
int block_frames = 0;
int block_samples = 0;
int handle_samples = 0;
const int sample_size = 4;
size_t block_bytes = 0;
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
void *curr_src_ptr = NULL;
int debug_value = get_debug_value(AML_DEBUG_AUDIOHAL_LIMITER);
int in_frames = frame_num;
int in_samples = in_frames * channels;
int in_bytes = in_samples * sample_size;
float last_gain = 1.0f;
float curr_gain = 1.0f;
float last_boost = 1.0f;
float curr_boost = 1.0f;
if (limiter == NULL || dst_buf == NULL || src_buf == NULL || channels <= 0 || frame_num <= 0) {
2026-11-11 08:50:43 +08:00
AM_LOGE("invalid param (limiter %p, buffer (%p, %p), channels %d, frame_num %d)", limiter, dst_buf, src_buf, channels, frame_num);
2026-08-13 11:28:40 +08:00
return -1;
}
if (!lim->enable) {
2026-11-11 08:50:43 +08:00
AM_LOGV("limiter is not enable !");
2026-08-13 11:28:40 +08:00
return -1;
}
ret = aml_audio_check_and_realloc(&lim->src_temp_buf, &lim->src_temp_buf_size, in_bytes);
if ((ret != 0) || (lim->src_temp_buf == NULL)) {
AM_LOGE("allocate src_temp_buf_ptr(%d bytes) failed", in_bytes);
return -1;
}
if (src_format == AUDIO_FORMAT_PCM_16_BIT) {
memcpy_by_audio_format(lim->src_temp_buf, lim->format, src_buf, AUDIO_FORMAT_PCM_16_BIT, in_samples);
src_format = AUDIO_FORMAT_PCM_32_BIT;;
} else {
memcpy_by_audio_format(lim->src_temp_buf, src_format, src_buf, src_format, in_samples);
}
if (lim->format != src_format) {
2026-11-11 08:50:43 +08:00
AM_LOGE("limiter format(%d) does NOT match src format(%d)", lim->format, src_format);
2026-08-13 11:28:40 +08:00
return -1;
}
if ((frame_num % lim->process_frames) != 0) {
2026-11-11 08:50:43 +08:00
AM_LOGE("not support frame_num %d (limiter process_frames %d)", frame_num, lim->process_frames);
2026-08-13 11:28:40 +08:00
return -1;
}
block_frames = lim->process_frames;
block_samples = block_frames * channels;
block_bytes = block_samples * sample_size;
ret = aml_audio_check_and_realloc(&lim->delay_buf_ptr, &lim->delay_buf_size, block_bytes);
if ((ret != 0) || (lim->delay_buf_ptr == NULL)) {
AM_LOGE("allocate delay_buf_ptr(%zu bytes) failed", block_bytes);
if (lim->src_temp_buf != NULL) {
aml_audio_free(lim->src_temp_buf);
lim->src_temp_buf = NULL;
lim->src_temp_buf_size = 0;
}
return -1;
}
for (handle_samples = 0; handle_samples < frame_num * channels; handle_samples += block_samples) {
curr_src_ptr = (uint8_t *)lim->src_temp_buf + (handle_samples * audio_bytes_per_sample(lim->format));
// calculate gain
if (debug_value) {
char name[64] = {0};
snprintf(name, 64, "audio_before_limiter_cal_%s_lim_%p.pcm", src_format == AUDIO_FORMAT_PCM_32_BIT ? "int32":"float", lim);
aml_dump_audio_bitstreams(name, curr_src_ptr, block_samples *sizeof(lim->format));
}
limiter_calculate_gain(lim, curr_src_ptr, block_samples);
limiter_get_gain(lim, &last_gain, &curr_gain);
// apply boost gain to delay buffer
last_boost = last_gain * lim->post_gain;
curr_boost = curr_gain * lim->post_gain;
limiter_apply_gain_fade(lim, last_boost, curr_boost, lim->delay_buf_ptr, block_frames, channels, lim->format);
// output delay buffer data, then move input buffer data into it
if (lim->format == dst_format) {
memcpy(dst_buf, lim->delay_buf_ptr, block_bytes);
} else {
memcpy_by_audio_format(dst_buf, dst_format, lim->delay_buf_ptr, lim->format, block_samples);
}
memcpy(lim->delay_buf_ptr, curr_src_ptr, block_bytes);
// update destination and source buffer pointer
dst_buf = (uint8_t *)dst_buf + (block_samples * audio_bytes_per_sample(dst_format));
}
return 0;
}
2026-11-11 08:50:43 +08:00
int aml_audio_limiter_planar_process(void *limiter,
aml_planar_buf_st *p_src_buf, aml_planar_buf_st *p_dst_buf, audio_format_t dst_format)
{
int ret = 0;
int block_frames = 0;
int handle_frames = 0;
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
int ch_num = 0;
int frame_num = 0;
audio_format_t src_format;
int debug_value = get_debug_value(AML_DEBUG_AUDIOHAL_LIMITER);
float last_gain = 1.0f;
float curr_gain = 1.0f;
float last_boost = 1.0f;
float curr_boost = 1.0f;
limiter_value block_max_value;
if (limiter == NULL || p_src_buf == NULL || p_dst_buf == NULL
|| p_src_buf->ch_num <= 0 || p_src_buf->per_ch_frames <= 0) {
AM_LOGE("invalid param, limiter %p, buffer (src %p, dst %p)",
limiter, p_src_buf, p_dst_buf);
return -1;
}
if (!lim->enable) {
AM_LOGV("limiter is not enable !");
return -1;
}
memset(&block_max_value, 0, sizeof(block_max_value));
ch_num = p_src_buf->ch_num;
src_format = p_src_buf->format;
frame_num = p_src_buf->per_ch_frames;
if (lim->format != src_format) {
AM_LOGE("limiter format(%d) does NOT match src format(%d)", lim->format, src_format);
return -1;
}
if ((frame_num % lim->process_frames) != 0) {
AM_LOGE("not support frame_num %d (limiter process_frames %d)", frame_num, lim->process_frames);
return -1;
}
block_frames = lim->process_frames;
ret = aml_planar_check_and_realloc_buf(&lim->delay_planar_buf, block_frames, ch_num, src_format);
if (ret != 0) {
AM_LOGE("allocate delay_planar_buf(%d frames) failed", block_frames);
return -1;
}
ret = aml_planar_check_and_realloc_buf(&lim->temp_planar_buf, block_frames, ch_num, src_format);
if (ret != 0) {
AM_LOGE("allocate temp_planar_buf(%d frames) failed", block_frames);
return -1;
}
for (handle_frames = 0; handle_frames < frame_num; handle_frames += block_frames) {
block_max_value = limiter_planar_get_block_max_value(lim, p_src_buf, handle_frames, block_frames);
limiter_calculate_gain_from_max(lim, block_max_value);
limiter_get_gain(lim, &last_gain, &curr_gain);
if (debug_value) {
AM_LOGI("limiter %p, last_gain %f, curr_gain %f", lim, last_gain, curr_gain);
}
last_boost = last_gain * lim->post_gain;
curr_boost = curr_gain * lim->post_gain;
limiter_planar_apply_gain_fade(lim, last_boost, curr_boost, &lim->delay_planar_buf, block_frames);
if (p_src_buf == p_dst_buf) {
aml_planar_memcpy_buf(&lim->temp_planar_buf, 0, src_format, p_src_buf, handle_frames, block_frames);
}
aml_planar_memcpy_buf(p_dst_buf, handle_frames, dst_format, &lim->delay_planar_buf, 0, block_frames);
if (p_src_buf == p_dst_buf) {
p_src_buf->per_ch_frames = frame_num;
aml_planar_memcpy_buf(&lim->delay_planar_buf, 0, src_format, &lim->temp_planar_buf, 0, block_frames);
} else {
aml_planar_memcpy_buf(&lim->delay_planar_buf, 0, src_format, p_src_buf, handle_frames, block_frames);
}
}
return 0;
}
2026-08-13 11:28:40 +08:00
int aml_audio_limiter_enable(void *limiter, bool enable)
{
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
if (lim == NULL) {
2026-11-11 08:50:43 +08:00
AM_LOGE("fail, limiter is NULL");
2026-08-13 11:28:40 +08:00
return -1;
}
if (lim->enable != enable) {
aml_audio_limiter_flush(limiter);
}
lim->enable = enable;
2026-11-11 08:50:43 +08:00
AM_LOGI("enable=%d ok", enable);
2026-08-13 11:28:40 +08:00
return 0;
}
int aml_audio_limiter_get_delay(void *limiter)
{
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
if (lim == NULL || (!lim->enable)) {
return 0;
}
return lim->process_frames;
}
static void limiter_reset(aml_audio_limiter_st *limiter)
{
limiter->curr_gain = 1.0f;
limiter->last_gain = 1.0f;
if (limiter->format == AUDIO_FORMAT_PCM_FLOAT) {
limiter->last_sample_max.f = 0.0f;
} else {
limiter->last_sample_max.i = 0;
}
limiter->release_delay_count = 0;
limiter->average_gain = 1.0f;
limiter->out_curr_gain = 1.0f;
limiter->out_last_gain = 1.0f;
limiter->out_lazy_count = 0;
}
int aml_audio_limiter_flush(void *limiter)
{
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
if (lim == NULL) {
return -1;
}
if (lim->delay_buf_ptr) {
memset(lim->delay_buf_ptr, 0, lim->delay_buf_size);
}
if (lim->src_temp_buf) {
memset(lim->src_temp_buf, 0, lim->src_temp_buf_size);
}
2026-11-11 08:50:43 +08:00
aml_planar_clear_buf(&lim->delay_planar_buf);
aml_planar_clear_buf(&lim->temp_planar_buf);
2026-08-13 11:28:40 +08:00
limiter_reset(lim);
2026-11-11 08:50:43 +08:00
AM_LOGI("ok");
2026-08-13 11:28:40 +08:00
return 0;
}