798 lines
25 KiB
C
798 lines
25 KiB
C
/*
|
||
* 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;
|
||
|
||
// 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);
|
||
|
||
bool is_float_equal(float a, float b)
|
||
{
|
||
const float PRECISION = 1e-06;
|
||
return (fabs(a - b) < PRECISION);
|
||
}
|
||
|
||
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) {
|
||
ALOGE("%s malloc limiter failed!", __func__);
|
||
return NULL;
|
||
}
|
||
if (data_format != AUDIO_FORMAT_PCM_32_BIT && data_format != AUDIO_FORMAT_PCM_FLOAT && data_format != AUDIO_FORMAT_PCM_16_BIT) {
|
||
ALOGE("%s not support data_format %d", __func__, data_format);
|
||
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;
|
||
|
||
ALOGI("%s success ,limiter->format =%d", __func__,limiter->format);
|
||
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) {
|
||
ALOGE("%s limiter is NULL", __func__);
|
||
return;
|
||
}
|
||
if (lim->src_temp_buf != NULL) {
|
||
ALOGI("%s free delay_buf_ptr %p, bytes %zu", __func__, lim->delay_buf_ptr, lim->src_temp_buf_size);
|
||
aml_audio_free(lim->src_temp_buf);
|
||
lim->src_temp_buf = NULL;
|
||
lim->src_temp_buf_size = 0;
|
||
}
|
||
if (lim->delay_buf_ptr != NULL) {
|
||
ALOGI("%s free delay_buf_ptr %p, bytes %zu", __func__, lim->delay_buf_ptr, lim->delay_buf_size);
|
||
aml_audio_free(lim->delay_buf_ptr);
|
||
lim->delay_buf_ptr = NULL;
|
||
lim->delay_buf_size = 0;
|
||
}
|
||
for (i = 0; i < LimEase_Max; i++) {
|
||
p_curve = &lim->curves[i];
|
||
if (p_curve->data_buf_ptr) {
|
||
ALOGI("%s free curves type %d, bytes %zu", __func__, p_curve->type, p_curve->data_buf_size);
|
||
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);
|
||
ALOGI("%s success", __func__);
|
||
}
|
||
|
||
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
|
||
//ALOGV("%s percent %d, __gain_step %f, ", __func__, percent, gain_step);
|
||
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;
|
||
}
|
||
|
||
|
||
static void limiter_calculate_gain(void *limiter, const void *buffer, const int sample_count)
|
||
{
|
||
float gain_new = 1.0f;
|
||
float *float_buf = (float *)buffer;
|
||
int *int32_buf = (int *)buffer;
|
||
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;
|
||
}
|
||
|
||
// find out the max sample value
|
||
if (lim->format == AUDIO_FORMAT_PCM_FLOAT) {
|
||
f_current_max = limiter_find_sample_max_float(float_buf, sample_count);
|
||
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) {
|
||
ALOGI("%s f_current_max %f, f_sample_max %f", __func__, f_current_max, f_sample_max);
|
||
}
|
||
} else {
|
||
i_current_max = limiter_find_sample_max_int32(int32_buf, sample_count);
|
||
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) {
|
||
ALOGI("%s i_current_max %d, i_sample_max %d", __func__, i_current_max, i_sample_max);
|
||
}
|
||
}
|
||
lim->last_gain = lim->curr_gain;
|
||
|
||
// limit output data level
|
||
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;
|
||
}
|
||
}
|
||
|
||
// calculate a proper gain value
|
||
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 {
|
||
//slow release time
|
||
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)) {
|
||
// Avoid gain value very close to 0.9999(0.0001)
|
||
lim->curr_gain = 1.0f;
|
||
}
|
||
if (debug_value) {
|
||
ALOGI("%s lim %p curr_gain %f", __func__, lim, lim->curr_gain);
|
||
}
|
||
|
||
// Patch for algorithm shortcoming
|
||
limiter_smooth_output_gain(lim);
|
||
|
||
return;
|
||
}
|
||
|
||
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
|
||
|
||
ALOGI("%s : count %.04f, proc_frames %d, sample_rate %d, time_ms %d, target %f", __func__,
|
||
count, proc_frames, sample_rate, time_ms, TARGET);
|
||
factor = exp(log(TARGET) / count);
|
||
|
||
// arrvie 1/2 X :
|
||
int half_index = log(1 - 0.5) / log(factor);
|
||
ALOGI("%s : arrive half of target = %d\n", __func__, half_index);
|
||
|
||
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++) {
|
||
ALOGD("%f", Y);
|
||
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) {
|
||
ALOGE("%s fail, invalid peak_level %f !", __func__, peak_level);
|
||
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) {
|
||
ALOGE("%s fail, not support proc_frames %d !", __func__, proc_frames);
|
||
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;
|
||
|
||
ALOGI("%s release_factor = %f(release_time %d ms), process_frames = %d, release_delay = %d", __func__,
|
||
lim->release_factor, release_time, lim->process_frames, lim->release_delay);
|
||
ALOGI("%s peak_level: %f (dB %.03f, %s), post_gain %.03f,\n", __func__,
|
||
peak_level, AmplToDb(peak_level), info_buf, lim->post_gain);
|
||
|
||
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) {
|
||
ALOGE("%s : invalid ease_type %d", __func__, ease_type);
|
||
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)) {
|
||
ALOGE("allocate data_buf_ptr(%zu bytes) failed", req_buf_size);
|
||
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) {
|
||
ALOGE("%s limiter_gen_curve_data type %d fail !", __func__, ease_type);
|
||
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) {
|
||
ALOGE("%s p_fade_curve is NULL", __func__);
|
||
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);
|
||
}
|
||
ALOGI("%s %p final sample_max %s", __func__, lim, info_buf);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
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) {
|
||
ALOGE("%s invalid param (limiter %p, buffer (%p, %p), channels %d, frame_num %d)", __func__,
|
||
limiter, dst_buf, src_buf, channels, frame_num);
|
||
return -1;
|
||
}
|
||
if (!lim->enable) {
|
||
ALOGV("%s : limiter is not enable !", __func__);
|
||
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) {
|
||
ALOGE("%s limiter format(%d) does NOT match src format(%d)", __func__, lim->format, src_format);
|
||
return -1;
|
||
}
|
||
|
||
if ((frame_num % lim->process_frames) != 0) {
|
||
ALOGE("%s not support frame_num %d (limiter process_frames %d)", __func__, frame_num, lim->process_frames);
|
||
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;
|
||
}
|
||
|
||
int aml_audio_limiter_enable(void *limiter, bool enable)
|
||
{
|
||
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
|
||
|
||
if (lim == NULL) {
|
||
ALOGE("%s fail, limiter is NULL", __func__);
|
||
return -1;
|
||
}
|
||
if (lim->enable != enable) {
|
||
aml_audio_limiter_flush(limiter);
|
||
}
|
||
lim->enable = enable;
|
||
|
||
ALOGI("%s enable=%d ok", __func__, enable);
|
||
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);
|
||
}
|
||
limiter_reset(lim);
|
||
|
||
ALOGI("%s ok", __func__);
|
||
return 0;
|
||
}
|