mirror of
https://gitee.com/Vancouver2017/luban-lite.git
synced 2025-12-27 22:48:54 +00:00
563 lines
15 KiB
C
563 lines
15 KiB
C
/*
|
|
* Copyright (C) 2020-2025 ArtInChip Technology Co. Ltd
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Author: <xiaodong.zhao@artinchip.com>
|
|
* Desc: aic audio mix
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
|
|
#include <rtthread.h>
|
|
#include <rtdevice.h>
|
|
|
|
#include "aic_mix.h"
|
|
|
|
#include "artinchip_fb.h"
|
|
#include "aic_audio_render_manager.h"
|
|
#include "aic_audio_render_device.h"
|
|
#include "mpp_mem.h"
|
|
#include "mpp_log.h"
|
|
#include "mm_core.h"
|
|
#include "aic_core.h"
|
|
#include "aic_osal.h"
|
|
|
|
#define MIX_SUM_BUF_SIZE (2048) // 2048 samples
|
|
#define MIX_RESAMPLE_BUF_SIZE (8192)
|
|
|
|
struct aic_pcm_mix_manager *g_mix_manager = NULL;
|
|
|
|
static inline unsigned long buf_readable(const struct aic_pcm_mix *mix)
|
|
{
|
|
return (mix->wt - mix->rd) % mix->size;
|
|
}
|
|
|
|
static inline unsigned long buf_writable(const struct aic_pcm_mix *mix)
|
|
{
|
|
return mix->size - buf_readable(mix);
|
|
}
|
|
|
|
// Audio mixing function
|
|
static void generic_mix_areas_16_native(unsigned int size,
|
|
volatile int16_t *dst,
|
|
int16_t *src,
|
|
volatile int32_t *sum,
|
|
size_t dst_step,
|
|
size_t src_step,
|
|
size_t sum_step)
|
|
{
|
|
register int32_t sample;
|
|
for (;;) {
|
|
sample = *src;
|
|
if (!*dst) {
|
|
// Initialize accumulator and destination
|
|
*sum = sample;
|
|
*dst = *src;
|
|
} else {
|
|
// Mix samples with clipping protection
|
|
sample += *sum;
|
|
*sum = sample;
|
|
if (sample > 0x7fff)
|
|
sample = 0x7fff;
|
|
else if (sample < -0x8000)
|
|
sample = -0x8000;
|
|
*dst = sample;
|
|
}
|
|
if (!--size)
|
|
return;
|
|
// Move pointers using byte-based stepping
|
|
src = (int16_t *)((char *)src + src_step);
|
|
dst = (int16_t *)((char *)dst + dst_step);
|
|
sum = (int32_t *)((char *)sum + sum_step);
|
|
}
|
|
}
|
|
|
|
struct aic_pcm_mix_manager *aic_pcm_mix_manager_create(void)
|
|
{
|
|
struct aic_pcm_mix_manager *mix_manager;
|
|
|
|
mix_manager = malloc(sizeof(struct aic_pcm_mix_manager));
|
|
if (NULL == mix_manager) {
|
|
loge("mix_manager malloc fail!\n");
|
|
return NULL;
|
|
}
|
|
memset(mix_manager, 0, sizeof(struct aic_pcm_mix_manager));
|
|
|
|
if (pthread_mutex_init(&mix_manager->mutex, NULL)) {
|
|
loge("pthread_mutex_init mix mutex fail!\n");
|
|
goto mutex_err;
|
|
}
|
|
|
|
mix_manager->mix_buf = malloc(MIX_SUM_BUF_SIZE * sizeof(int16_t));
|
|
if (NULL == mix_manager->mix_buf) {
|
|
loge("mix_manager->mix_buf malloc fail!\n");
|
|
goto mix_buf_err;
|
|
}
|
|
|
|
mix_manager->sum_buf = malloc(MIX_SUM_BUF_SIZE * sizeof(int32_t));
|
|
if (NULL == mix_manager->sum_buf) {
|
|
loge("mix_manager->sum_buf malloc fail!\n");
|
|
goto sum_buf_err;
|
|
}
|
|
|
|
mix_manager->size = MIX_SUM_BUF_SIZE;
|
|
mix_manager->mix = NULL;
|
|
mix_manager->mix_cnt = 0;
|
|
|
|
mix_manager->resample_buf = malloc(MIX_RESAMPLE_BUF_SIZE);
|
|
if (NULL == mix_manager->resample_buf) {
|
|
loge("malloc resample_buf fail!\n");
|
|
goto resample_buf_err;
|
|
}
|
|
mix_manager->resample_buf_size = MIX_RESAMPLE_BUF_SIZE;
|
|
|
|
return mix_manager;
|
|
|
|
resample_buf_err:
|
|
free(mix_manager->sum_buf);
|
|
sum_buf_err:
|
|
free(mix_manager->mix_buf);
|
|
mix_buf_err:
|
|
pthread_mutex_destroy(&mix_manager->mutex);
|
|
mutex_err:
|
|
free(mix_manager);
|
|
return NULL;
|
|
}
|
|
|
|
int aic_pcm_mix_manager_destroy(struct aic_pcm_mix_manager *mix_manager)
|
|
{
|
|
if (NULL == mix_manager) {
|
|
loge("<%s:%d>invalid parameter!\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
if ((NULL != mix_manager->mix) || (NULL == g_mix_manager)) {
|
|
return -1;
|
|
}
|
|
|
|
pthread_mutex_destroy(&mix_manager->mutex);
|
|
|
|
if (mix_manager->mix_buf) {
|
|
free(mix_manager->mix_buf);
|
|
mix_manager->mix_buf = NULL;
|
|
}
|
|
|
|
if (mix_manager->sum_buf) {
|
|
free(mix_manager->sum_buf);
|
|
mix_manager->sum_buf = NULL;
|
|
}
|
|
|
|
if (mix_manager->resample_buf) {
|
|
free(mix_manager->resample_buf);
|
|
mix_manager->resample_buf = NULL;
|
|
}
|
|
|
|
free(mix_manager);
|
|
g_mix_manager = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int insert_mix_to_manager(struct aic_pcm_mix_manager *mix_manager, struct aic_pcm_mix *mix)
|
|
{
|
|
if ((NULL == mix_manager) || (NULL == mix)) {
|
|
loge("<%s:%d>invalid parameter!\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
pthread_mutex_lock(&mix_manager->mutex);
|
|
|
|
if (NULL == mix_manager->mix) {
|
|
mix->prev = mix->next = mix;
|
|
mix_manager->mix = mix;
|
|
} else {
|
|
struct aic_pcm_mix *tail = mix_manager->mix->prev;
|
|
mix->next = mix_manager->mix;
|
|
mix_manager->mix->prev = mix;
|
|
mix->prev = tail;
|
|
tail->next = mix;
|
|
}
|
|
|
|
mix->mix_manager = mix_manager;
|
|
mix_manager->mix_cnt++;
|
|
|
|
pthread_mutex_unlock(&mix_manager->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pop_mix_from_manager(struct aic_pcm_mix_manager *mix_manager, struct aic_pcm_mix *mix)
|
|
{
|
|
struct aic_pcm_mix *tmp;
|
|
|
|
if ((NULL == mix_manager) || (NULL == mix)) {
|
|
loge("<%s:%d>invalid parameter!\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
if (NULL == mix_manager->mix) {
|
|
return 0;
|
|
}
|
|
|
|
pthread_mutex_lock(&mix_manager->mutex);
|
|
|
|
if (mix_manager->mix == mix_manager->mix->next)
|
|
{
|
|
if (mix_manager->mix == mix) {
|
|
mix_manager->mix = NULL;
|
|
mix->prev = NULL;
|
|
mix->next = NULL;
|
|
mix_manager->mix_cnt--;
|
|
} else {
|
|
loge("<%s:%d> node:%p id:%d not found!\n", __func__, __LINE__, mix, mix->id);
|
|
}
|
|
} else {
|
|
tmp = mix_manager->mix;
|
|
do {
|
|
if (tmp == mix) {
|
|
struct aic_pcm_mix *prev = tmp->prev;
|
|
struct aic_pcm_mix *next = tmp->next;
|
|
if (prev && next) {
|
|
prev->next = next;
|
|
next->prev = prev;
|
|
|
|
// mix is header
|
|
if (mix == mix_manager->mix) {
|
|
mix_manager->mix = mix->next;
|
|
}
|
|
|
|
mix->prev = NULL;
|
|
mix->next = NULL;
|
|
mix_manager->mix_cnt--;
|
|
} else {
|
|
loge("<%s:%d> corrupted list: NULL pointer detected!\n", __func__, __LINE__);
|
|
}
|
|
break;
|
|
}
|
|
tmp = tmp->next;
|
|
} while (tmp != mix_manager->mix);
|
|
}
|
|
|
|
pthread_mutex_unlock(&mix_manager->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct aic_pcm_mix *aic_pcm_mix_create(struct aic_pcm_mix_manager *mix_manager, int id, int size)
|
|
{
|
|
struct aic_pcm_mix *mix = NULL;
|
|
|
|
if (size <= 0) {
|
|
loge("invalid parameter [%d]\n", size);
|
|
return NULL;
|
|
}
|
|
|
|
if (NULL == g_mix_manager) {
|
|
g_mix_manager = aic_pcm_mix_manager_create();
|
|
if (NULL == g_mix_manager) {
|
|
loge("create mix_manager fail!\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (NULL == mix_manager) {
|
|
mix_manager = g_mix_manager;
|
|
}
|
|
|
|
mix = malloc(sizeof(struct aic_pcm_mix));
|
|
if (NULL == mix) {
|
|
loge("malloc mix fail!\n");
|
|
return NULL;
|
|
}
|
|
memset(mix, 0, sizeof(struct aic_pcm_mix));
|
|
|
|
mix->data = malloc(size * sizeof(int16_t));
|
|
if (NULL == mix->data) {
|
|
free(mix);
|
|
loge("malloc mix->data fail!\n");
|
|
return NULL;
|
|
}
|
|
|
|
mix->size = size;
|
|
mix->rd = 0;
|
|
mix->wt = 0;
|
|
mix->id = id;
|
|
|
|
insert_mix_to_manager(mix_manager, mix);
|
|
|
|
return mix;
|
|
}
|
|
|
|
int aic_pcm_mix_destroy(struct aic_pcm_mix *mix)
|
|
{
|
|
if (NULL == mix) {
|
|
loge("<%s:%d>invalid parameter!\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
pop_mix_from_manager(mix->mix_manager, mix);
|
|
|
|
if (0 == mix->mix_manager->mix_cnt) {
|
|
aic_pcm_mix_manager_destroy(mix->mix_manager);
|
|
}
|
|
|
|
if (mix->data) {
|
|
free(mix->data);
|
|
mix->data = NULL;
|
|
}
|
|
|
|
free(mix);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int aic_pcm_mix_set_attr(struct aic_pcm_mix *mix, int sn, int ch, int sr, int bps)
|
|
{
|
|
struct aic_pcm_mix *tmp, *main_mix = NULL;
|
|
|
|
if (NULL == mix) {
|
|
loge("<%s:%d>invalid parameter!\n", __func__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
mix->sample_num = sn;
|
|
mix->ch_num = ch;
|
|
mix->sample_rate = mix->sample_rate_org = sr;
|
|
mix->byte_per_sample = bps;
|
|
|
|
printf("<%s:%d> id:%d sample_rate:%d\n", __func__, __LINE__, mix->id, mix->sample_rate);
|
|
|
|
// check if need resample
|
|
if (0 != mix->id) {
|
|
tmp = mix->next;
|
|
while (tmp != mix) {
|
|
if (0 == tmp->id) {
|
|
main_mix = tmp;
|
|
break;
|
|
}
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
if (NULL != main_mix) {
|
|
if (main_mix->sample_rate != mix->sample_rate) { // need resample
|
|
mix->sample_rate = main_mix->sample_rate;
|
|
printf("need do resample %d -> %d\n", mix->sample_rate_org, mix->sample_rate);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int aic_pcm_mix_set_vol(struct aic_pcm_mix *mix, int vol)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int resample_audio(struct aic_pcm_mix *mix, void *data, int size)
|
|
{
|
|
struct aic_pcm_mix_manager *mix_manager;
|
|
|
|
if ((NULL == mix) || (NULL == data) || (size <= 0)) {
|
|
loge("invalid parameter [%p/%p/%d]\n", mix, data, size);
|
|
return -1;
|
|
}
|
|
|
|
mix_manager = mix->mix_manager;
|
|
|
|
int num_samples = size / mix->ch_num / mix->byte_per_sample;
|
|
|
|
// clca output sample number
|
|
double ratio = (double)mix->sample_rate / mix->sample_rate_org;
|
|
int output_samples = (int)(num_samples * ratio + 0.5);
|
|
int out_size = output_samples * mix->ch_num * mix->byte_per_sample;
|
|
if (out_size > mix_manager->resample_buf_size) {
|
|
loge("resample_buf too small, need:%d actual:%d\n", out_size, mix_manager->resample_buf_size);
|
|
return -1;
|
|
}
|
|
|
|
int16_t* in_data = (int16_t*)data;
|
|
int16_t* out_data = (int16_t*)mix_manager->resample_buf;
|
|
// Linear interpolation resampling
|
|
for (int ch = 0; ch < mix->ch_num; ch++) {
|
|
for (int i = 0; i < output_samples; i++) {
|
|
double pos = (double)i / ratio;
|
|
int idx = (int)pos;
|
|
double frac = pos - idx;
|
|
|
|
if (idx >= num_samples - 1) {
|
|
out_data[i * mix->ch_num + ch] = in_data[(num_samples - 1) * mix->ch_num + ch];
|
|
} else {
|
|
if (2 == mix->byte_per_sample) {
|
|
int16_t sample1 = in_data[idx * mix->ch_num + ch];
|
|
int16_t sample2 = in_data[(idx + 1) * mix->ch_num + ch];
|
|
out_data[i * mix->ch_num + ch] = (int16_t)(sample1 * (1.0 - frac) + sample2 * frac);
|
|
} else if (1 == mix->byte_per_sample) {
|
|
int8_t sample1 = in_data[idx * mix->ch_num + ch];
|
|
int8_t sample2 = in_data[(idx + 1) * mix->ch_num + ch];
|
|
out_data[i * mix->ch_num + ch] = (int8_t)(sample1 * (1.0 - frac) + sample2 * frac);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (output_samples * mix->ch_num);
|
|
}
|
|
|
|
int aic_pcm_mix_write_data(struct aic_pcm_mix *mix, char *data, int size)
|
|
{
|
|
unsigned long avail;
|
|
int data_sample = 0, bps;
|
|
|
|
if ((NULL == mix) || (NULL == data) || (size <= 0)) {
|
|
loge("invalid parameter [%p/%p/%d]\n", mix, data, size);
|
|
return -1;
|
|
}
|
|
|
|
bps = mix->byte_per_sample;
|
|
if (0 == bps) {
|
|
bps = 2;
|
|
}
|
|
|
|
if (mix->sample_rate != mix->sample_rate_org) {
|
|
data_sample = resample_audio(mix, data, size);
|
|
}
|
|
|
|
if (data_sample > 0) { // do resample
|
|
data = mix->mix_manager->resample_buf;
|
|
} else {
|
|
data_sample = size / bps;
|
|
}
|
|
|
|
int n = 0;
|
|
while (1) {
|
|
avail = buf_writable(mix);
|
|
if (data_sample <= avail) {
|
|
break;
|
|
}
|
|
aicos_msleep(10);
|
|
if (++n >= 10) {
|
|
loge("no spce to write, drop data, size:%d\n", size);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
unsigned long wt = mix->wt % mix->size;
|
|
unsigned long first = mix->size - wt;
|
|
if (first > data_sample) {
|
|
first = data_sample;
|
|
}
|
|
memcpy(mix->data + wt, data, first * bps);
|
|
|
|
if (data_sample > first) {
|
|
data += first * bps;
|
|
memcpy(mix->data, data, (data_sample - first) * bps);
|
|
}
|
|
|
|
mix->wt += data_sample;
|
|
|
|
return size;
|
|
}
|
|
|
|
static int adjust_volume(int32_t *data, int samples, int divisor)
|
|
{
|
|
if ((NULL == data) || (samples <= 0) || (divisor <= 0)) {
|
|
loge("invalid parameter [%p / %d / %d]\n", data, samples, divisor);
|
|
return -1;
|
|
}
|
|
|
|
for (int i = 0; i < samples; i++) {
|
|
data[i] /= divisor;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_mix(struct aic_pcm_mix_manager *mix_manager, int samples)
|
|
{
|
|
unsigned long rd, avail;
|
|
struct aic_pcm_mix *mix;
|
|
int ret = 0, src_step, dst_step, sum_step;
|
|
|
|
if (NULL == mix_manager) {
|
|
mix_manager = g_mix_manager;
|
|
if (NULL == mix_manager) {
|
|
loge("mix_manager is null!\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (samples <= 0) {
|
|
loge("invalid samples:%d\n", samples);
|
|
return -1;
|
|
}
|
|
|
|
if (samples > mix_manager->size) {
|
|
loge("mix_buf and sum_buf too small, samples:%d buf_size:%d\n", samples, mix_manager->size);
|
|
return -1;
|
|
}
|
|
|
|
if (NULL == mix_manager->mix) {
|
|
loge("mix list is empty!\n");
|
|
return 0;
|
|
}
|
|
|
|
memset(mix_manager->mix_buf, 0, mix_manager->size * sizeof(int16_t));
|
|
memset(mix_manager->sum_buf, 0, mix_manager->size * sizeof(int32_t));
|
|
|
|
src_step = dst_step = sizeof(int16_t);
|
|
sum_step = sizeof(int32_t);
|
|
mix = mix_manager->mix;
|
|
|
|
pthread_mutex_lock(&mix_manager->mutex);
|
|
do {
|
|
avail = buf_readable(mix);
|
|
if (avail >= samples) {
|
|
|
|
// set main track volume 1/2
|
|
if (1 == mix->id) {
|
|
adjust_volume(mix_manager->sum_buf, samples, 2);
|
|
}
|
|
|
|
rd = mix->rd % mix->size;
|
|
|
|
unsigned long first = mix->size - rd;
|
|
if (first > samples) {
|
|
first = samples;
|
|
}
|
|
generic_mix_areas_16_native(samples, mix_manager->mix_buf, mix->data + rd,
|
|
mix_manager->sum_buf, src_step, dst_step, sum_step);
|
|
|
|
if (samples > first) {
|
|
generic_mix_areas_16_native(samples, mix_manager->mix_buf + first, mix->data,
|
|
mix_manager->sum_buf + first, src_step, dst_step, sum_step);
|
|
}
|
|
|
|
mix->rd += samples;
|
|
ret = samples;
|
|
}
|
|
|
|
mix = mix->next;
|
|
} while (mix != mix_manager->mix);
|
|
|
|
pthread_mutex_unlock(&mix_manager->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
unsigned long get_mix_reamin_data(struct aic_pcm_mix *mix)
|
|
{
|
|
if (NULL == mix) {
|
|
loge("invalid parameter!\n");
|
|
return 0;
|
|
}
|
|
|
|
return buf_readable(mix);
|
|
}
|