1031 lines
33 KiB
C
1031 lines
33 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;
|
||
|
||
aml_planar_buf_st delay_planar_buf;
|
||
aml_planar_buf_st temp_planar_buf;
|
||
|
||
// 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) {
|
||
AM_LOGE("malloc limiter failed!");
|
||
return NULL;
|
||
}
|
||
if (data_format != AUDIO_FORMAT_PCM_32_BIT && data_format != AUDIO_FORMAT_PCM_FLOAT && data_format != AUDIO_FORMAT_PCM_16_BIT) {
|
||
AM_LOGE("not support data_format %d", 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;
|
||
|
||
AM_LOGI("success ,limiter->format =%d", 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) {
|
||
AM_LOGE("limiter is NULL");
|
||
return;
|
||
}
|
||
if (lim->src_temp_buf != NULL) {
|
||
AM_LOGI("free delay_buf_ptr %p, bytes %zu", 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) {
|
||
AM_LOGI("free delay_buf_ptr %p, bytes %zu", 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;
|
||
}
|
||
aml_planar_free_buf(&lim->delay_planar_buf);
|
||
aml_planar_free_buf(&lim->temp_planar_buf);
|
||
for (i = 0; i < LimEase_Max; i++) {
|
||
p_curve = &lim->curves[i];
|
||
if (p_curve->data_buf_ptr) {
|
||
AM_LOGI("free curves type %d, bytes %zu", 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);
|
||
AM_LOGI("success");
|
||
}
|
||
|
||
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
|
||
//AM_LOGV("percent %d, __gain_step %f,", 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 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)
|
||
{
|
||
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) {
|
||
f_current_max = current_max.f;
|
||
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) {
|
||
AM_LOGI("f_current_max %f, f_sample_max %f", f_current_max, f_sample_max);
|
||
}
|
||
} else {
|
||
i_current_max = current_max.i;
|
||
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) {
|
||
AM_LOGI("i_current_max %d, i_sample_max %d", i_current_max, i_sample_max);
|
||
}
|
||
}
|
||
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) {
|
||
AM_LOGI("lim %p curr_gain %f", lim, lim->curr_gain);
|
||
}
|
||
|
||
limiter_smooth_output_gain(lim);
|
||
}
|
||
|
||
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(¤t_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);
|
||
}
|
||
|
||
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
|
||
|
||
AM_LOGI("count %.04f, proc_frames %d, sample_rate %d, time_ms %d, target %f", 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);
|
||
AM_LOGI("arrive half of target = %d\n", 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++) {
|
||
AM_LOGD("%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) {
|
||
AM_LOGE("fail, invalid peak_level %f !", 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) {
|
||
AM_LOGE("fail, not support proc_frames %d !", 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;
|
||
|
||
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);
|
||
|
||
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) {
|
||
AM_LOGE("invalid ease_type %d", 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)) {
|
||
AM_LOGE("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) {
|
||
AM_LOGE("limiter_gen_curve_data type %d fail !", 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) {
|
||
AM_LOGE("p_fade_curve is NULL");
|
||
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);
|
||
}
|
||
AM_LOGI("%p final sample_max %s", lim, info_buf);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
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) {
|
||
AM_LOGE("invalid param (limiter %p, buffer (%p, %p), channels %d, frame_num %d)", limiter, dst_buf, src_buf, channels, frame_num);
|
||
return -1;
|
||
}
|
||
if (!lim->enable) {
|
||
AM_LOGV("limiter is not enable !");
|
||
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) {
|
||
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;
|
||
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_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;
|
||
}
|
||
|
||
int aml_audio_limiter_enable(void *limiter, bool enable)
|
||
{
|
||
aml_audio_limiter_st *lim = (aml_audio_limiter_st *)limiter;
|
||
|
||
if (lim == NULL) {
|
||
AM_LOGE("fail, limiter is NULL");
|
||
return -1;
|
||
}
|
||
if (lim->enable != enable) {
|
||
aml_audio_limiter_flush(limiter);
|
||
}
|
||
lim->enable = enable;
|
||
|
||
AM_LOGI("enable=%d ok", 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);
|
||
}
|
||
aml_planar_clear_buf(&lim->delay_planar_buf);
|
||
aml_planar_clear_buf(&lim->temp_planar_buf);
|
||
limiter_reset(lim);
|
||
|
||
AM_LOGI("ok");
|
||
return 0;
|
||
}
|