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

1940 lines
66 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_utils_fast_mixer"
//#define LOG_NDEBUG 0
#include <stdint.h>
#include <dirent.h>
#include <sys/types.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <cutils/list.h>
#include <string.h>
#include "aml_malloc_debug.h"
#include "aml_dump_debug.h"
#include "aml_ringbuffer.h"
#include "audio_data_process.h"
#include "aml_audio_limiter.h"
#include "aml_volume_utils.h"
#include "aml_audio_log.h"
#include "aml_fast_mixer.h"
#include "aml_hwmixer_handle.h"
#include "aml_audio_ease.h"
#include "aml_audio_timer.h"
#include "format.h"
static int aml_hwmixer_pcm_mmap_get_hw_ptr(struct aml_hwmixer_handle *mixer,
unsigned int *hw_ptr, struct timespec *tstamp)
{
(void)mixer;
(void)hw_ptr;
(void)tstamp;
return -1;
}
static int aml_hwmixer_pcm_mmap_commit(struct aml_hwmixer_handle *mixer, unsigned int frames)
{
(void)mixer;
(void)frames;
return -1;
}
static int aml_hwmixer_pcm_get_buffer_size(struct aml_hwmixer_handle *mixer)
{
(void)mixer;
return -1;
}
static int aml_hwmixer_pcm_frames_to_bytes(struct aml_hwmixer_handle *mixer, unsigned int frames)
{
(void)mixer;
(void)frames;
return -1;
}
/*****************************************************************
* Private struct or define, do not move to header file.
*******************************************************************/
#define AML_FAST_MIXER_INPORT_NUM 8
typedef enum AML_FAST_MIXER_INPORT_STATE {
AML_FAST_MIXER_INPORT_IDLE = 0,
AML_FAST_MIXER_INPORT_ACTIVE,
AML_FAST_MIXER_INPORT_PAUSING,
AML_FAST_MIXER_INPORT_PAUSING_FADE,
AML_FAST_MIXER_INPORT_PAUSED,
AML_FAST_MIXER_INPORT_RESUMING, // next stage will change to ACTIVE
AML_FAST_MIXER_INPORT_FLUSHING,
AML_FAST_MIXER_INPORT_FLUSHED,
AML_FAST_MIXER_INPORT_STOPPING,
AML_FAST_MIXER_INPORT_STOPPED,
AML_FAST_MIXER_INPORT_STANDBYING,
AML_FAST_MIXER_INPORT_STANDBYED,
AML_FAST_MIXER_INPORT_STATE_MAX,
} aml_fast_mixer_inport_state_t;
typedef struct aml_fast_mixer_inport_message {
aml_fast_mixer_msg_t msg_what;
struct listnode list;
} aml_fast_mixer_inport_message_t;
typedef struct fast_mixer_format_buffer {
void *buf_ptr;
size_t buf_size;
audio_format_t format;
int valid_samples;
} fast_mixer_format_buffer_t;
typedef struct aml_fast_mixer_buffer {
struct ring_buffer rbuffer;
struct pcm *mixer_handle;
struct pcm_config mixer_pcm_config;
struct aml_mixer_handle alsa_mixer;
audio_resample_config_t resample_config;
aml_audio_resample_t *resample_handle;
aml_pcm_downmix_st *downmixer;
fast_mixer_format_buffer_t format_buf;
} aml_fast_mixer_buffer_st;
typedef struct aml_fast_mixer_inport {
aml_fast_mixer_inport_param_st param;
aml_fast_mixer_buffer_st buffer;
aml_fast_mixer_inport_state_t state;
int port_id;
uint64_t drop_frames;
uint64_t last_frames_position;
struct timespec lasttimestamp;
pthread_mutex_t apts_lock;
aml_audio_ease_t *vol_ease;
struct listnode msg_list;
pthread_mutex_t msg_lock;
aml_audio_ease_t *fade_ease;
// Temporary interleaved data buffer
void *data_bufptr;
size_t data_buflen;
int data_frames;
bool data_valid;
// Temporary planar data buffer
aml_planar_buf_st data_planar_buf;
int64_t write_time_us;
float volume;
bool use_hw_mixer;
bool first_read;
bool mute;
bool standby_info_show; // used for debug or logcat info
struct aml_hwmixer_handle hwmixer_hdl;
} aml_fast_mixer_inport_st;
typedef struct aml_fast_mixer {
pthread_mutex_t lock;
aml_fast_mixer_inport_st *inports[AML_FAST_MIXER_INPORT_NUM];
aml_pcm_mixing_st *sw_mixer[AML_FAST_MIXER_PCM_MAX];
char name[FAST_MIXER_NAME_MAX_LEN];
bool initialized;
bool drop_enable;
bool data_used;
bool data_is_planar;
bool limiter_enable;
} aml_fast_mixer_st;
static const char *fast_mixer_port_msg_string[AML_FAST_MIXER_MSG_CNT_MAX] = {
"MSG_PAUSE",
"MSG_RESUME",
"MSG_FLUSH",
"MSG_STOP",
"MSG_STANDBY"
};
static int fast_mixer_remove_inport_all_messages(aml_fast_mixer_inport_st *port);
/* alloc new fast mixer */
void* aml_fast_mixer_new(const char *name, bool data_is_planar)
{
int i = 0;
aml_fast_mixer_st *mixer = NULL;
aml_pcm_mixing_st *sw_mixer = NULL;
mixer = (aml_fast_mixer_st *)aml_audio_calloc(1, sizeof(aml_fast_mixer_st));
if (mixer == NULL) {
AM_LOGE("calloc aml_fast_mixer_st failed !");
return NULL;
}
pthread_mutex_init(&mixer->lock, NULL);
for (i = 0; i < AML_FAST_MIXER_PCM_MAX; i++) {
sw_mixer = (aml_pcm_mixing_st *)aml_audio_calloc(1, sizeof(aml_pcm_mixing_st));
if (sw_mixer == NULL) {
AM_LOGE("calloc aml_pcm_mixing_st failed !");
goto error;
}
mixer->sw_mixer[i] = sw_mixer;
// planar data only use the first mixer;
if (data_is_planar) {
break;
}
}
if (name != NULL) {
snprintf(mixer->name, sizeof(mixer->name)-1, "fmixer_%s", name);
} else {
snprintf(mixer->name, sizeof(mixer->name)-1, "fmixer_anon");
}
mixer->drop_enable = false;
mixer->limiter_enable = true;
mixer->data_is_planar = data_is_planar;
AM_LOGI("%s limiter_enable, data_is_planar %d, success", mixer->name, data_is_planar);
mixer->initialized = true;
return mixer;
error:
for (i = 0; i < AML_FAST_MIXER_PCM_MAX; i++) {
if (mixer->sw_mixer[i] != NULL) {
aml_audio_free(mixer->sw_mixer[i]);
mixer->sw_mixer[i] = NULL;
}
}
aml_audio_free(mixer);
mixer = NULL;
return mixer;
}
/* release fast mixer */
void aml_fast_mixer_free(void *fast_mixer)
{
int port_id = 0;
aml_fast_mixer_st *mixer = fast_mixer;
AM_LOGI("enter");
if (mixer == NULL) {
AM_LOGE("fast_mixer is NULL");
return;
}
for (port_id = 0; port_id < AML_FAST_MIXER_INPORT_NUM; port_id++) {
if (mixer->inports[port_id] != NULL) {
aml_fast_mixer_inport_remove(mixer, port_id);
}
}
pthread_mutex_lock(&mixer->lock);
mixer->initialized = false;
for (port_id = 0; port_id < AML_FAST_MIXER_PCM_MAX; port_id++) {
if (mixer->sw_mixer[port_id] != NULL) {
deinit_aml_pcm_mixer(mixer->sw_mixer[port_id]);
aml_audio_free(mixer->sw_mixer[port_id]);
mixer->sw_mixer[port_id] = NULL;
}
}
pthread_mutex_unlock(&mixer->lock);
pthread_mutex_destroy(&mixer->lock);
aml_audio_free(fast_mixer);
AM_LOGI("success");
}
static bool aml_fast_mixer_init_buffer(aml_fast_mixer_inport_st *input_port, const aml_fast_mixer_inport_param_st *param)
{
int ret = 0;
int rbuffer_size = 0;
bool use_sw_mixer = true;
aml_fast_mixer_buffer_st *mix_buffer = NULL;
fast_mixer_format_buffer_t *p_format_buf = NULL;
if (input_port == NULL || param == NULL) {
return false;
}
mix_buffer = &input_port->buffer;
if (param->mixer_type == AML_FAST_MIX_TYPE_SW) {
input_port->use_hw_mixer = false;
use_sw_mixer = true;
} else if (param->mixer_type == AML_FAST_MIX_TYPE_HW) {
input_port->use_hw_mixer = true;
use_sw_mixer = false;
input_port->hwmixer_hdl.param.mmap_type = AML_HWMIXER_MMAP_NONE; //normal type
} else if (param->mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
input_port->use_hw_mixer = true;
use_sw_mixer = false;
input_port->hwmixer_hdl.param.mmap_type = AML_HWMIXER_MMAP_NOIRQ; //mmap noirq type
}
// init hwmixer handle if use hw mixer
if (input_port->use_hw_mixer) {
ret = aml_hwmixer_handle_init(&input_port->hwmixer_hdl, &input_port->param.config);
if (0 != ret) {
AM_LOGD("aml_hwmixer_handle_init fail and back to AML_FAST_MIX_TYPE_SW");
use_sw_mixer = true;
} else {
input_port->use_hw_mixer = true;
use_sw_mixer = false;
}
}
// init ringbuffer if use sw mixer
if (use_sw_mixer && (param->buffer_frame_count > 0)) {
rbuffer_size = param->buffer_frame_count * param->config.frame_size;
ret = ring_buffer_init(&mix_buffer->rbuffer, rbuffer_size);
if (ret != 0) {
AM_LOGE("ring_buffer_init failed");
return false;
}
} else {
memset(&mix_buffer->rbuffer, 0, sizeof(mix_buffer->rbuffer));
}
p_format_buf = &mix_buffer->format_buf;
p_format_buf->buf_ptr = NULL;
p_format_buf->buf_size = 0;
p_format_buf->valid_samples = 0;
p_format_buf->format = AUDIO_FORMAT_INVALID;
return true;
}
static void aml_fast_mixer_deinit_buffer(aml_fast_mixer_inport_st *input_port)
{
aml_fast_mixer_buffer_st *mix_buffer = NULL;
fast_mixer_format_buffer_t *p_format_buf = NULL;
if (input_port == NULL) {
return;
}
mix_buffer = &input_port->buffer;
/* deinit resource of hw mixer */
if (input_port->use_hw_mixer) {
if (aml_hwmixer_handle_deinit(&input_port->hwmixer_hdl)) {
AM_LOGE("aml_hwmixer_handle_deinit error!");
}
/* deinit resource of sw mixer */
} else {
ring_buffer_release(&mix_buffer->rbuffer);
memset(&mix_buffer->resample_config, 0, sizeof(mix_buffer->resample_config));
if (mix_buffer->resample_handle) {
aml_audio_resample_close(mix_buffer->resample_handle);
mix_buffer->resample_handle = NULL;
}
}
if (mix_buffer->downmixer) {
deinit_aml_pcm_downmix(mix_buffer->downmixer);
aml_audio_free(mix_buffer->downmixer);
mix_buffer->downmixer = NULL;
}
p_format_buf = &mix_buffer->format_buf;
if (p_format_buf->buf_ptr) {
aml_audio_free(p_format_buf->buf_ptr);
p_format_buf->buf_ptr = NULL;
p_format_buf->buf_size = 0;
p_format_buf->valid_samples = 0;
p_format_buf->format = AUDIO_FORMAT_INVALID;
}
}
static void aml_fast_mixer_logParameters(const aml_fast_mixer_inport_param_st *param)
{
if (param == NULL) {
return;
}
// This is very helpful for debugging in the future. Please leave it in.
AM_LOGI("config : sampleRate = %6d, channelCnt = %d, channelMask = %#x, format = 0x%x, frame_size = %d",
param->config.sampleRate, param->config.channelCnt, param->config.channelMask,
param->config.format, param->config.frame_size);
AM_LOGI("mixer_type = %d, estimate_buffer_level = %d, start_threshold_frame = %d, buffer_frame_count = %d",
param->mixer_type, param->estimate_buffer_level, param->start_threshold_frame,
param->buffer_frame_count);
AM_LOGI("encoder_exclusive = %d, disable_fading = %d", param->encoder_exclusive, param->disable_fading);
/* info of hwmixer */
if (param->mixer_type == AML_FAST_MIX_TYPE_HW ||
param->mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
AM_LOGI("hw_mixer_config : sampleRate = %d, channelCnt = %d, format = %d",
param->hw_mixer_config.rate, param->hw_mixer_config.channels, param->hw_mixer_config.format);
AM_LOGI("hw_mixer_config : period_size = %d, period_count = %d, start_threshold = %lu, stop_threshold = %lu",
param->hw_mixer_config.period_size, param->hw_mixer_config.period_count,
param->hw_mixer_config.start_threshold, param->hw_mixer_config.stop_threshold);
}
AM_LOGI("read_callback = %p, callback_data = %p, desc = (%s)",
param->read_callback, param->callback_data, param->desc_info);
}
int aml_fast_mixer_inport_add(void *fast_mixer, const aml_fast_mixer_inport_param_st *param)
{
int port_id = 0;
aml_fast_mixer_st *mixer = fast_mixer;
aml_fast_mixer_inport_st *new_port = NULL;
const struct audioCfg *input_cfg = NULL;
if (mixer == NULL || !mixer->initialized || param == NULL) {
AM_LOGE("mixer %p or not initialized, param %p", mixer, param);
return -1;
}
if (!param->read_callback && param->buffer_frame_count <= 0
&& param->mixer_type != AML_FAST_MIX_TYPE_HW_NOIRQ) {
AM_LOGE("invalid param : read_callback is NULL, bufferFrameCount %d", param->buffer_frame_count);
return -1;
}
AM_LOGI("----------------------------------->");
aml_fast_mixer_logParameters(param);
AM_LOGI("<-----------------------------------");
input_cfg = &param->config;
if (input_cfg->sampleRate == 0|| input_cfg->channelCnt == 0
|| !audio_channel_mask_is_valid(input_cfg->channelMask)
|| !audio_is_linear_pcm(input_cfg->format)
|| input_cfg->frame_size == 0) {
AM_LOGE("invalid config : sampleRate %d, channelCnt %d, channelMask 0x%x, format %d, frame_size %d",
input_cfg->sampleRate, input_cfg->channelCnt, input_cfg->channelMask, input_cfg->format, input_cfg->frame_size);
return -1;
}
pthread_mutex_lock(&mixer->lock);
for (port_id = 0; port_id < AML_FAST_MIXER_INPORT_NUM; port_id++) {
if (mixer->inports[port_id] == NULL) {
break;
} else {
const char *p_desc = mixer->inports[port_id]->param.desc_info;
AM_LOGI("%s, exist inports[%d] %s", mixer->name, port_id, p_desc);
}
}
if (port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("%s can not find avail input port", mixer->name);
pthread_mutex_unlock(&mixer->lock);
return -1;
}
new_port = (aml_fast_mixer_inport_st *)aml_audio_calloc(1, sizeof(aml_fast_mixer_inport_st));
if (new_port == NULL) {
AM_LOGE("calloc %zu bytes fail", sizeof(aml_fast_mixer_inport_st));
pthread_mutex_unlock(&mixer->lock);
return -1;
}
mixer->inports[port_id] = new_port;
new_port->state = AML_FAST_MIXER_INPORT_IDLE;
new_port->port_id = port_id;
memcpy(&new_port->param, param, sizeof(*param));
new_port->param.desc_info[sizeof(param->desc_info)-1] = '\0';
pthread_mutex_init(&new_port->apts_lock, NULL);
pthread_mutex_init(&new_port->msg_lock, NULL);
list_init(&new_port->msg_list);
pthread_mutex_unlock(&mixer->lock);
if (aml_audio_ease_init(&new_port->vol_ease) != 0) {
AM_LOGE("init vol_ease fail !");
goto error_init_vol_ease;
}
new_port->volume = 1.0f;
new_port->mute = false;
new_port->vol_ease->start_volume = 1.0f;
new_port->vol_ease->current_volume = 1.0f;
new_port->vol_ease->target_volume = 1.0f;
new_port->vol_ease->do_easing = false;
new_port->data_valid = false;
new_port->data_planar_buf.data_valid = false;
new_port->standby_info_show = false;
if (!aml_fast_mixer_init_buffer(new_port, param)) {
AM_LOGE("aml_fast_mixer_init_buffer fail !");
goto error_init_buffer;
}
new_port->first_read = true;
new_port->state = AML_FAST_MIXER_INPORT_IDLE;
AM_LOGI("%s port %d success !", mixer->name, port_id);
return port_id;
error_init_buffer:
aml_audio_ease_close(new_port->vol_ease);
new_port->vol_ease = NULL;
error_init_vol_ease:
pthread_mutex_lock(&mixer->lock);
aml_audio_free(new_port);
mixer->inports[port_id] = NULL;
pthread_mutex_unlock(&mixer->lock);
return -1;
}
int aml_fast_mixer_inport_remove(void *fast_mixer, int port_id)
{
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
char desc_info[FAST_MIXER_INPORT_DESC_MAX_LEN];
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid inport_id %d", port_id);
return -1;
}
pthread_mutex_lock(&mixer->lock);
input_port = mixer->inports[port_id];
if (input_port == NULL) {
pthread_mutex_unlock(&mixer->lock);
return -1;
}
mixer->inports[port_id] = NULL;
pthread_mutex_unlock(&mixer->lock);
memset(desc_info, 0, sizeof(desc_info));
memcpy(desc_info, input_port->param.desc_info, sizeof(desc_info)-1);
aml_audio_ease_close(input_port->vol_ease);
input_port->vol_ease = NULL;
if (input_port->fade_ease != NULL) {
aml_audio_ease_close(input_port->fade_ease);
input_port->fade_ease = NULL;
}
if (input_port->data_bufptr != NULL) {
aml_audio_free(input_port->data_bufptr);
input_port->data_bufptr = NULL;
input_port->data_buflen = 0;
}
aml_planar_free_buf(&input_port->data_planar_buf);
fast_mixer_remove_inport_all_messages(input_port);
aml_fast_mixer_deinit_buffer(input_port);
aml_audio_free(input_port);
AM_LOGI("%s port %d (%s) ok !", mixer->name, port_id, desc_info);
return 0;
}
static const char *fast_mixer_inport_msg_to_str(aml_fast_mixer_msg_t msg)
{
if (msg < 0 || msg >= AML_FAST_MIXER_MSG_CNT_MAX) {
return "invalid !";
}
return fast_mixer_port_msg_string[msg];
}
void aml_fast_mixer_inport_send_message(void *fast_mixer, int port_id, aml_fast_mixer_msg_t msg)
{
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
aml_fast_mixer_inport_message_t *p_msg = NULL;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid port_id %d", port_id);
return;
}
if (msg < 0 || msg >= AML_FAST_MIXER_MSG_CNT_MAX) {
AM_LOGE("port_id %d, invalid message %d", port_id, msg);
return;
}
p_msg = aml_audio_calloc(1, sizeof(aml_fast_mixer_inport_message_t));
if (p_msg == NULL) {
AM_LOGE("port_id %d, no memory, size:%zu", port_id, sizeof(aml_fast_mixer_inport_message_t));
return;
}
p_msg->msg_what = msg;
/* Hold mixer->lock across lookup and enqueue so inport_remove cannot
* free the port while we still touch msg_list.
*/
pthread_mutex_lock(&mixer->lock);
input_port = mixer->inports[port_id];
if (input_port == NULL) {
pthread_mutex_unlock(&mixer->lock);
aml_audio_free(p_msg);
AM_LOGE("port_id %d, input port is NULL", port_id);
return;
}
pthread_mutex_lock(&input_port->msg_lock);
list_add_tail(&input_port->msg_list, &p_msg->list);
pthread_mutex_unlock(&input_port->msg_lock);
pthread_mutex_unlock(&mixer->lock);
AM_LOGI("%s port_id %d, %s", mixer->name, port_id, fast_mixer_inport_msg_to_str(msg));
}
static aml_fast_mixer_inport_message_t *fast_mixer_get_inport_message(aml_fast_mixer_inport_st *port)
{
aml_fast_mixer_inport_message_t *p_msg = NULL;
struct listnode *item = NULL;
pthread_mutex_lock(&port->msg_lock);
if (!list_empty(&port->msg_list)) {
item = list_head(&port->msg_list);
p_msg = node_to_item(item, aml_fast_mixer_inport_message_t, list);
}
pthread_mutex_unlock(&port->msg_lock);
return p_msg;
}
static bool fast_mixer_inport_message_empty(aml_fast_mixer_inport_st *port)
{
bool empty = true;
pthread_mutex_lock(&port->msg_lock);
if (!list_empty(&port->msg_list)) {
empty = false;
}
pthread_mutex_unlock(&port->msg_lock);
return empty;
}
static int fast_mixer_remove_inport_message(aml_fast_mixer_inport_st *port, aml_fast_mixer_inport_message_t *p_msg)
{
if (port == NULL || p_msg == NULL) {
return 0;
}
pthread_mutex_lock(&port->msg_lock);
list_remove(&p_msg->list);
pthread_mutex_unlock(&port->msg_lock);
aml_audio_free(p_msg);
return 0;
}
static int fast_mixer_remove_inport_all_messages(aml_fast_mixer_inport_st *port)
{
aml_fast_mixer_inport_message_t *p_msg = NULL;
struct listnode *node = NULL, *n = NULL;
pthread_mutex_lock(&port->msg_lock);
list_for_each_safe(node, n, &port->msg_list) {
p_msg = node_to_item(node, aml_fast_mixer_inport_message_t, list);
AM_LOGI("msg: %s", fast_mixer_inport_msg_to_str(p_msg->msg_what));
list_remove(&p_msg->list);
aml_audio_free(p_msg);
}
pthread_mutex_unlock(&port->msg_lock);
return 0;
}
static void fast_mixer_process_inport_msg(aml_fast_mixer_inport_st *in_port)
{
aml_fast_mixer_inport_message_t *msg = fast_mixer_get_inport_message(in_port);
if (msg) {
AM_LOGI("msg: %s", fast_mixer_inport_msg_to_str(msg->msg_what));
switch (msg->msg_what) {
case AML_FAST_MIXER_MSG_PAUSE:
in_port->state = AML_FAST_MIXER_INPORT_PAUSING;
break;
case AML_FAST_MIXER_MSG_RESUME:
in_port->state = AML_FAST_MIXER_INPORT_RESUMING;
break;
case AML_FAST_MIXER_MSG_FLUSH:
in_port->state = AML_FAST_MIXER_INPORT_FLUSHING;
break;
case AML_FAST_MIXER_MSG_STOP:
in_port->state = AML_FAST_MIXER_INPORT_STOPPING;
break;
case AML_FAST_MIXER_MSG_STANDBY:
in_port->state = AML_FAST_MIXER_INPORT_STANDBYING;
break;
default:
AM_LOGE("not support %d", msg->msg_what);
break;
}
fast_mixer_remove_inport_message(in_port, msg);
}
}
void aml_fast_mixer_inport_set_mute(void *fast_mixer, int port_id, bool mute)
{
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid port_id %d", port_id);
return;
}
input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("port_id %d, input port is NULL", port_id);
return;
}
input_port->mute = mute;
// set hw mixer state by pcm api
if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
float src_vol = 0;
if (mute) {
src_vol = 0;
} else {
src_vol = input_port->volume;
}
// set src volume to hwmixer handle
aml_hwmixer_handle_config(&input_port->hwmixer_hdl, AML_HWMIXER_CFG_TYPE_VOLUME_SET, &src_vol);
}
AM_LOGI("%s port_id %d, mute %d", mixer->name, port_id, mute);
}
void aml_fast_mixer_inport_set_volume(void *fast_mixer, int port_id, float volume)
{
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid port_id %d", port_id);
return;
}
input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("port_id %d, input port is NULL", port_id);
return;
}
input_port->volume = volume;
// set hw mixer state by pcm api
if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
float src_vol = 0;
if (input_port->mute) {
src_vol = 0;
} else {
src_vol = volume;
}
// set src volume to hwmixer handle
aml_hwmixer_handle_config(&input_port->hwmixer_hdl, AML_HWMIXER_CFG_TYPE_VOLUME_SET, &src_vol);
}
AM_LOGI("%s port_id %d, volume %f", mixer->name, port_id, volume);
}
int aml_fast_mixer_inport_get_position(void *fast_mixer, int port_id, uint64_t *frames, struct timespec *timestamp)
{
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM || frames == NULL || timestamp == NULL) {
AM_LOGE("invalid parameter");
return -1;
}
input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("input_port is NULL");
return -1;
}
/* HW noirq get position and timestamp from alsa */
if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
int ret = 0;
unsigned int hw_ptr = 0;
struct timespec hw_timestamp = {0, 0};
/* get hw ptr from pcm_mmap_get_hw_ptr */
ret = aml_hwmixer_pcm_mmap_get_hw_ptr(&input_port->hwmixer_hdl, &hw_ptr, &hw_timestamp);
if (ret < 0) {
AM_LOGE("pcm_mmap_get_hw_ptr err");
return ret;
}
pthread_mutex_lock(&input_port->apts_lock);
*frames = (uint64_t)hw_ptr;
*timestamp = hw_timestamp;
pthread_mutex_unlock(&input_port->apts_lock);
} else {
/* SW and HW with irq get position and timestamp info from input port */
pthread_mutex_lock(&input_port->apts_lock);
*frames = input_port->last_frames_position + input_port->drop_frames;
*timestamp = input_port->lasttimestamp;
pthread_mutex_unlock(&input_port->apts_lock);
}
return 0;
}
/* get pcm info when use hw mixer */
int aml_fast_mixer_inport_get_pcm_info(void *fast_mixer, int port_id,
struct pcm *pcm, struct pcm_config *pcm_cfg)
{
int ret = -1;
aml_fast_mixer_st *mixer = fast_mixer;
struct pcm *temp_pcm = NULL;
struct pcm_config *temp_cfg = NULL;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid parameter");
return -1;
}
aml_fast_mixer_inport_st *input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("input_port is NULL");
return -1;
}
if (input_port->use_hw_mixer) {
temp_pcm = input_port->hwmixer_hdl.param.pcm_handle;
temp_cfg = &input_port->hwmixer_hdl.param.mixer_pcm_config;
memcpy(pcm_cfg, temp_cfg, sizeof(struct pcm_config));
pcm = temp_pcm;
ret = 0;
}
return ret;
}
/* pcm operations for hwmixer or other alsa card */
int aml_fast_mixer_inport_pcm_func(void *fast_mixer, int port_id,
aml_fast_mixer_pcm_func_t pcm_func,
aml_fast_mixer_pcm_data_st *pcm_data)
{
int ret = -1;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid parameter");
return -1;
}
aml_fast_mixer_inport_st *input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("input_port is NULL");
return -1;
}
/* only for hw mxier case */
if (!input_port->use_hw_mixer) {
ret = -1;
}
AM_LOGD("port_id = %d, pcm_func = %d", port_id, pcm_func);
/* pcm func without data */
switch (pcm_func) {
case AML_FAST_MIXER_PCM_GET_POLL_FD:
ret = aml_hwmixer_pcm_get_poll_fd(&input_port->hwmixer_hdl);
goto exit;
case AML_FAST_MIXER_PCM_GET_BUFFER_SIZE:
ret = aml_hwmixer_pcm_get_buffer_size(&input_port->hwmixer_hdl);
goto exit;
default:
ret = -1;
}
/* pcm func with data */
if (!pcm_data) {
AM_LOGE("pcm_data is NULL");
return -1;
}
switch (pcm_func) {
case AML_FAST_MIXER_PCM_MMAP_BEGIN:
ret = aml_hwmixer_pcm_mmap_begin(&input_port->hwmixer_hdl, &pcm_data->pcm_areas,
&pcm_data->pcm_offset, &pcm_data->frames);
break;
case AML_FAST_MIXER_PCM_MMAP_COMMIT:
ret = aml_hwmixer_pcm_mmap_commit(&input_port->hwmixer_hdl, pcm_data->frames);
break;
case AML_FAST_MIXER_PCM_MMAP_GET_HW_PTR:
ret = aml_hwmixer_pcm_mmap_get_hw_ptr(&input_port->hwmixer_hdl, &pcm_data->pcm_offset, &pcm_data->tstamp);
break;
case AML_FAST_MIXER_PCM_FRAMES_TO_BYTES:
ret = aml_hwmixer_pcm_frames_to_bytes(&input_port->hwmixer_hdl, pcm_data->frames);
break;
default:
AM_LOGD("not supported id %d", pcm_func);
ret = -1;
}
exit:
return ret;
}
int aml_fast_mixer_inport_avail_frames(void *fast_mixer, int port_id)
{
int avail_frames = 0;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid parameter");
return -1;
}
input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("input_port is NULL");
return -1;
}
if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_SW) {
avail_frames = get_buffer_read_space(&input_port->buffer.rbuffer) / input_port->param.config.frame_size;
} else if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW ||
input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
avail_frames = aml_hwmixer_get_avail_frames(&input_port->hwmixer_hdl);
}
return avail_frames;
}
int aml_fast_mixer_inport_write_space(void *fast_mixer, int port_id)
{
int writable_bytes = 0;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGV("mixer is NULL or not initialized");
return 0;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid parameter");
return 0;
}
input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("input_port is NULL");
return 0;
}
if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_SW) {
writable_bytes = get_buffer_write_space(&input_port->buffer.rbuffer);
} else if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW ||
input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
// todo
}
return writable_bytes;
}
int aml_fast_mixer_inport_write(void *fast_mixer, int port_id, const void *buffer, int bytes)
{
int ret = 0;
int avail_bytes = 0;
int frame_size = 0;
const struct audioCfg *input_cfg = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
bool dump_enable = get_debug_value(AML_DUMP_AUDIOHAL_FAST_MIXER);
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return 0;
}
if (buffer == NULL || bytes <= 0 || port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid param, buffer %p, bytes %d, port_id %d", buffer, bytes, port_id);
return 0;
}
aml_fast_mixer_inport_st *input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("input_port is NULL");
return 0;
}
input_cfg = &input_port->param.config;
frame_size = input_cfg->frame_size;
if (dump_enable) {
char filepath[64];
memset(filepath, 0, sizeof(filepath));
snprintf(filepath, sizeof(filepath)-1, "/data/vendor/audiohal/%s_in_%d_write.pcm", mixer->name, input_port->port_id);
aml_dump_audio_bitstreams(filepath, buffer, bytes);
}
if (mixer->drop_enable) {
avail_bytes = get_buffer_read_space(&input_port->buffer.rbuffer);
if (avail_bytes > 0) {
ring_buffer_reset(&input_port->buffer.rbuffer);
AM_LOGI("%s drop enable, drop ringbuffer %d frames", mixer->name, avail_bytes / frame_size);
}
input_port->first_read = true;
pthread_mutex_lock(&input_port->apts_lock);
input_port->drop_frames += ((bytes + avail_bytes) / frame_size);
clock_gettime(CLOCK_MONOTONIC, &input_port->lasttimestamp);
pthread_mutex_unlock(&input_port->apts_lock);
AM_LOGI("%s drop enable, drop %d frames", mixer->name, bytes / frame_size);
return 0;
}
if (input_port->first_read && input_port->param.estimate_buffer_level) {
input_port->write_time_us = (int64_t)aml_audio_get_systime();
}
if (input_port->param.read_callback) {
// nothing to do ?
} else if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_SW) {
// write data to ring buffer
avail_bytes = get_buffer_write_space(&input_port->buffer.rbuffer);
if (avail_bytes < bytes) {
AM_LOGE("%s port_id %d avail_bytes %d < bytes %d, override data !", mixer->name, port_id, avail_bytes, bytes);
}
ret = ring_buffer_write(&input_port->buffer.rbuffer, (void *)buffer, bytes, COVER_WRITE);
} else if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW) {
// write data to hwmixer and do process inside
if (aml_hwmixer_handle_write(&input_port->hwmixer_hdl, (void *)buffer, bytes)) {
AM_LOGE("aml_hwmixer_handle_write error!");
ret = 0;
}
// update position and timestamp
pthread_mutex_lock(&input_port->apts_lock);
input_port->last_frames_position += bytes/frame_size;
clock_gettime(CLOCK_MONOTONIC, &input_port->lasttimestamp);
pthread_mutex_unlock(&input_port->apts_lock);
} else if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
// nothing to do
}
return ret;
}
static inline void _mixer_inport_invalid(aml_fast_mixer_inport_st *input_port)
{
aml_pcm_downmix_st *p_downmixer = NULL;
fast_mixer_format_buffer_t *p_format_buf = NULL;
if (input_port->data_bufptr && input_port->data_buflen > 0 && input_port->data_valid) {
memset(input_port->data_bufptr, 0, input_port->data_buflen);
}
p_downmixer = input_port->buffer.downmixer;
if (p_downmixer && p_downmixer->output_buf && p_downmixer->output_buf_size > 0) {
memset(p_downmixer->output_buf, 0, p_downmixer->output_buf_size);
}
p_format_buf = &input_port->buffer.format_buf;
if (p_format_buf->buf_ptr && p_format_buf->buf_size > 0 && p_format_buf->valid_samples > 0) {
memset(p_format_buf->buf_ptr, 0, p_format_buf->buf_size);
}
p_format_buf->valid_samples = 0;
p_format_buf->format = AUDIO_FORMAT_INVALID;
input_port->data_frames = 0;
input_port->data_valid = false;
input_port->data_planar_buf.data_valid = false;
input_port->data_planar_buf.format = AUDIO_FORMAT_INVALID;
}
static int _aml_fast_mixer_inport_reset(aml_fast_mixer_inport_st *input_port)
{
int avail_frames = 0;
aml_fast_mixer_buffer_st *mix_buffer = NULL;
if (input_port == NULL) {
return -1;
}
mix_buffer = &input_port->buffer;
pthread_mutex_lock(&input_port->apts_lock);
input_port->drop_frames = 0;
input_port->last_frames_position = 0;
memset(&input_port->lasttimestamp, 0, sizeof(input_port->lasttimestamp));
pthread_mutex_unlock(&input_port->apts_lock);
if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_SW) {
if (input_port->param.buffer_frame_count > 0) {
avail_frames = get_buffer_read_space(&mix_buffer->rbuffer) / input_port->param.config.frame_size;
AM_LOGI("port %d %s drop %d frames", input_port->port_id, input_port->param.desc_info, avail_frames);
ring_buffer_reset(&mix_buffer->rbuffer);
}
memset(&mix_buffer->resample_config, 0, sizeof(mix_buffer->resample_config));
if (mix_buffer->resample_handle) {
aml_audio_resample_close(mix_buffer->resample_handle);
mix_buffer->resample_handle = NULL;
}
if (mix_buffer->downmixer) {
deinit_aml_pcm_downmix(mix_buffer->downmixer);
aml_audio_free(mix_buffer->downmixer);
mix_buffer->downmixer = NULL;
}
} else if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW) {
if (aml_hwmixer_handle_reset(&input_port->hwmixer_hdl)) {
AM_LOGE("aml_hwmixer_handle_reset error");
}
} else if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
// nothing todo
}
input_port->state = AML_FAST_MIXER_INPORT_IDLE;
input_port->first_read = true;
return 0;
}
int aml_fast_mixer_inport_flush(void *fast_mixer, int port_id)
{
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (port_id < 0 || port_id >= AML_FAST_MIXER_INPORT_NUM) {
AM_LOGE("invalid parameter");
return -EINVAL;
}
aml_fast_mixer_inport_st *input_port = mixer->inports[port_id];
if (input_port == NULL) {
AM_LOGE("input_port is NULL");
return -EINVAL;
}
pthread_mutex_lock(&mixer->lock);
input_port->state = AML_FAST_MIXER_INPORT_IDLE;
pthread_mutex_unlock(&mixer->lock);
_aml_fast_mixer_inport_reset(input_port);
return 0;
}
int aml_fast_mixer_all_inports_flush(void *fast_mixer)
{
int port_id = 0;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
for (port_id = 0; port_id < AML_FAST_MIXER_INPORT_NUM; port_id++) {
aml_fast_mixer_inport_st *input_port = mixer->inports[port_id];
if (input_port == NULL) {
continue;
}
pthread_mutex_lock(&mixer->lock);
input_port->state = AML_FAST_MIXER_INPORT_IDLE;
pthread_mutex_unlock(&mixer->lock);
_aml_fast_mixer_inport_reset(input_port);
}
return 0;
}
#if 0
static int aml_fast_mixer_inport_resample(aml_fast_mixer_buffer_st *mix_buffer,
void *in_buffer, int in_bytes, const struct audioCfg *in_cfg, const struct audioCfg *mix_cfg)
{
int ret = 0;
/* init resample config */
if (in_cfg->sampleRate != mix_buffer->resample_config.output_sr) {
if (mix_buffer->resample_handle) {
aml_audio_resample_close(mix_buffer->resample_handle);
mix_buffer->resample_handle = NULL;
}
mix_buffer->resample_config.aformat = in_cfg->format;
mix_buffer->resample_config.channels = in_cfg->channelCnt;
mix_buffer->resample_config.input_sr = in_cfg->sampleRate;
mix_buffer->resample_config.output_sr = mix_cfg->sampleRate;
AM_LOGI("init resampler from in-samplerate(%d) to out-samplerate(%d) channels(%d) format(%d)\n",
mix_buffer->resample_config.input_sr, mix_buffer->resample_config.output_sr,
mix_buffer->resample_config.channels, mix_buffer->resample_config.aformat);
ret = aml_audio_resample_init((aml_audio_resample_t **)&mix_buffer->resample_handle,
AML_AUDIO_SIMPLE_RESAMPLE, &mix_buffer->resample_config);
if (ret < 0) {
AM_LOGE("resample init error\n");
mix_buffer->resample_handle = NULL;
return -1;
}
} else {
mix_buffer->resample_handle = NULL;
}
if (mix_buffer->resample_handle) {
if (aml_audio_resample_process(mix_buffer->resample_handle, in_buffer, in_bytes) != 0) {
return -1;
}
}
return 0;
}
#endif
static void fast_mixer_config_fading(aml_fast_mixer_inport_st *in_port, bool is_fade_in, int avail_frames, const struct audioCfg *data_cfg)
{
ease_setting_t setting;
aml_data_format_t data_format;
int ease_frames = 512;
float current_volume = 1.0f;
if (ease_frames > avail_frames && !is_fade_in) {
AM_LOGI("fade out, avail_frames %d", avail_frames);
ease_frames = avail_frames;
}
data_format.format = data_cfg->format;
data_format.sr = data_cfg->sampleRate;
data_format.ch = data_cfg->channelCnt;
data_format.endian = 0;
memset(&setting, 0, sizeof(setting));
if (in_port->fade_ease == NULL) {
if (aml_audio_ease_init(&in_port->fade_ease) != 0) {
AM_LOGE("init fade_ease fail !");
return;
}
}
if (is_fade_in) {
setting.start_volume = 0;
setting.target_volume = 1.0;
} else {
setting.start_volume = 1.0;
setting.target_volume = 0;
}
if (in_port->fade_ease && in_port->fade_ease->do_easing) {
current_volume = aml_audio_ease_get_current_volume(in_port->fade_ease);
setting.start_volume = current_volume;
AM_LOGI("input port %d do_easing, start_volume %f", in_port->port_id, setting.start_volume);
}
AM_LOGI("fade %s, easing frames %d", is_fade_in ? "in" : "out", ease_frames);
setting.ease_type = EaseLinear;
setting.ease_frames = ease_frames;
aml_audio_ease_config_frame(in_port->fade_ease, &setting, &data_format);
}
static int _mixer_inport_volume_mute_process(aml_fast_mixer_inport_st *input_port, void *buffer, int bytes, const struct audioCfg *data_cfg)
{
float next_volume = 1.0f;
float current_volume = 1.0f;
float target_volume = 1.0f;
ease_setting_t ease_setting;
aml_data_format_t data_format;
aml_audio_ease_t *p_volume_ease = NULL;
int debug_value = get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER);
if (input_port == NULL || buffer == NULL || bytes <= 0 || data_cfg == NULL) {
AM_LOGE("invalid, input_port(%p) buffer(%p) bytes(%d) data_cfg(%p)", input_port, buffer, bytes, data_cfg);
return -1;
}
p_volume_ease = input_port->vol_ease;
next_volume = input_port->volume;
if (input_port->mute) {
next_volume = 0;
}
current_volume = p_volume_ease->current_volume;
target_volume = p_volume_ease->target_volume;
if (!is_float_equal(target_volume, next_volume) || !p_volume_ease->do_easing) {
memset(&ease_setting, 0, sizeof(ease_setting));
ease_setting.ease_type = EaseLinear;
ease_setting.ease_frames = 384;
ease_setting.start_volume = current_volume;
ease_setting.target_volume = next_volume;
data_format.format = data_cfg->format;
data_format.sr = data_cfg->sampleRate;
data_format.ch = data_cfg->channelCnt;
data_format.endian = 0;
aml_audio_ease_config_frame(p_volume_ease, &ease_setting, &data_format);
}
bool skip_easing = false;
if (is_float_equal(p_volume_ease->start_volume, p_volume_ease->target_volume) || aml_audio_ease_done(p_volume_ease)) {
if (is_float_equal(p_volume_ease->target_volume, 1.0f)) {
skip_easing = true;
if (debug_value) {
AM_LOGD("port %d volume is 1.0f, skip easing !", input_port->port_id);
}
} else if (is_float_equal(p_volume_ease->target_volume, 0.0f)) {
skip_easing = true;
memset(buffer, 0, bytes);
}
}
if (skip_easing) {
p_volume_ease->ease_frames_elapsed = p_volume_ease->ease_frames;
p_volume_ease->do_easing = false;
} else {
aml_audio_ease_process(input_port->vol_ease, buffer, bytes);
}
return 0;
}
static int _mixer_inport_read(aml_fast_mixer_st *mixer, int port_id, int req_frames)
{
int ret = 0;
int read_bytes = 0;
int read_frames = 0;
int avail_frames = 0;
int64_t estimated_frames = 0;
int request_bytes = 0;
bool config_fade_out = false;
bool config_fade_in = false;
bool dump_enable = get_debug_value(AML_DUMP_AUDIOHAL_FAST_MIXER);
bool debug_enable = get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER);
const struct audioCfg *input_cfg = NULL;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_buffer_st *mix_buffer = NULL;
aml_fast_mixer_inport_param_st *input_param = NULL;
input_port = mixer->inports[port_id];
input_param = &input_port->param;
input_cfg = &input_param->config;
mix_buffer = &input_port->buffer;
request_bytes = req_frames * input_cfg->frame_size;
ret = aml_audio_check_and_realloc((void **)&input_port->data_bufptr, &input_port->data_buflen, request_bytes);
if ((ret != 0) || (input_port->data_bufptr == NULL)) {
AM_LOGE("allocate input_port %d data_bufptr(%d bytes) failed", input_port->port_id, request_bytes);
goto read_end;
}
// process input port message
fast_mixer_process_inport_msg(input_port);
if (input_port->state == AML_FAST_MIXER_INPORT_PAUSING) {
input_port->state = AML_FAST_MIXER_INPORT_PAUSING_FADE;
config_fade_out = true;
} else if (input_port->state == AML_FAST_MIXER_INPORT_PAUSING_FADE) {
if ((input_port && input_port->fade_ease == NULL)
|| (input_port && input_port->fade_ease && !input_port->fade_ease->do_easing)) {
input_port->state = AML_FAST_MIXER_INPORT_PAUSED;
}
} else if (input_port->state == AML_FAST_MIXER_INPORT_RESUMING) {
input_port->state = AML_FAST_MIXER_INPORT_ACTIVE;
} else if (input_port->state == AML_FAST_MIXER_INPORT_FLUSHING) {
_aml_fast_mixer_inport_reset(input_port);
input_port->state = AML_FAST_MIXER_INPORT_FLUSHED;
} else if (input_port->state == AML_FAST_MIXER_INPORT_STOPPING) {
_aml_fast_mixer_inport_reset(input_port);
input_port->state = AML_FAST_MIXER_INPORT_STOPPED;
} else if (input_port->state == AML_FAST_MIXER_INPORT_STANDBYING) {
if (input_port->param.mixer_type == AML_FAST_MIX_TYPE_SW && input_param->read_callback == NULL) {
int remaining_bytes = get_buffer_read_space(&input_port->buffer.rbuffer);
if (remaining_bytes > 0) {
if (input_port->standby_info_show == false) {
AM_LOGI("%s port_id %d prepare to standby, remaining_bytes %d, req_frames %d", mixer->name, port_id, remaining_bytes, req_frames);
}
input_port->standby_info_show = true;
} else {
AM_LOGI("%s port_id %d is standby", mixer->name, port_id);
input_port->state = AML_FAST_MIXER_INPORT_STANDBYED;
input_port->standby_info_show = false;
}
} else {
// hw mixer do some operation? a todo list
input_port->state = AML_FAST_MIXER_INPORT_STANDBYED;
AM_LOGI("%s port_id %d is standby", mixer->name, port_id);
}
}
if (input_port->state == AML_FAST_MIXER_INPORT_PAUSED
|| input_port->state == AML_FAST_MIXER_INPORT_FLUSHED
|| input_port->state == AML_FAST_MIXER_INPORT_STOPPED
|| input_port->state == AML_FAST_MIXER_INPORT_STANDBYED) {
// prepare for the next reading
input_port->first_read = true;
return 0;
}
if (get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER)) {
if (input_port->data_valid) {
AM_LOGW("%s port_id %d already has valid data, override !", mixer->name, port_id);
}
}
// read data
if (input_param->read_callback) {
read_bytes = input_param->read_callback(input_param->callback_data, mixer->drop_enable, input_port->data_bufptr, request_bytes);
} else if (input_param->mixer_type == AML_FAST_MIX_TYPE_SW) {
if (input_port->first_read) {
avail_frames = get_buffer_read_space(&mix_buffer->rbuffer) / input_cfg->frame_size;
if (input_port->param.estimate_buffer_level && avail_frames > 0) {
int64_t curr_time_us = (int64_t)aml_audio_get_systime();
if (curr_time_us > input_port->write_time_us) {
int64_t diff_time_us = curr_time_us - input_port->write_time_us;
estimated_frames = diff_time_us * input_cfg->sampleRate / 1000000L;
if (debug_enable) {
AM_LOGI("%s %s port %d, diff_time_us %" PRId64 ", sampleRate %d",
__func__, mixer->name, port_id, diff_time_us, input_cfg->sampleRate);
}
if (estimated_frames > input_port->param.buffer_frame_count/2) {
AM_LOGE("%s %s port %d, invalid estimated_frames %" PRId64 " (discard it), buffer_frame_count %d",
__func__, mixer->name, port_id, estimated_frames, input_port->param.buffer_frame_count);
estimated_frames = 0;
}
}
AM_LOGI("%s %s port %d, avail_frames %d, estimated_frames %" PRId64 "",
__func__, mixer->name, port_id, avail_frames, estimated_frames);
avail_frames += estimated_frames;
}
if (avail_frames < input_port->param.start_threshold_frame) {
goto read_end;
}
input_port->first_read = false;
config_fade_in = true;
}
if (get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER)) {
avail_frames = get_buffer_read_space(&mix_buffer->rbuffer) / input_cfg->frame_size;
AM_LOGI("%s port %d, avail_frames %d, latency %d ms", mixer->name, port_id, avail_frames,
avail_frames * 1000 / input_cfg->sampleRate);
}
read_bytes = ring_buffer_read(&mix_buffer->rbuffer, input_port->data_bufptr, request_bytes);
// HW Mixer do not need read because data already write to hwmixer in aml_fast_mixer_inport_write
} else if (input_param->mixer_type == AML_FAST_MIX_TYPE_HW ||
input_param->mixer_type == AML_FAST_MIX_TYPE_HW_NOIRQ) {
read_frames = 0;
goto read_end;
}
if (read_bytes <= 0) {
AM_LOGW("%s port %d without data", mixer->name, port_id);
goto read_end;
} else if (read_bytes < request_bytes) {
AM_LOGW("%s port %d not enough data (%d < %d)", mixer->name, port_id, read_bytes, request_bytes);
}
if (dump_enable) {
char filepath[64];
memset(filepath, 0, sizeof(filepath));
snprintf(filepath, sizeof(filepath)-1, "/data/vendor/audiohal/%s_in_%d_read.pcm", mixer->name, input_port->port_id);
aml_dump_audio_bitstreams(filepath, input_port->data_bufptr, read_bytes);
}
read_frames = read_bytes / input_cfg->frame_size;
_mixer_inport_volume_mute_process(input_port, input_port->data_bufptr, read_bytes, input_cfg);
// fade in/out process
if (!input_param->disable_fading) {
if (config_fade_in || config_fade_out) {
fast_mixer_config_fading(input_port, config_fade_in, avail_frames, input_cfg);
}
if (input_port->fade_ease && input_port->fade_ease->do_easing && read_bytes > 0) {
aml_audio_ease_process(input_port->fade_ease, input_port->data_bufptr, read_bytes);
}
}
if (read_frames > 0) {
input_port->data_frames = read_frames;
input_port->data_valid = true;
}
pthread_mutex_lock(&input_port->apts_lock);
input_port->last_frames_position += read_frames;
clock_gettime(CLOCK_MONOTONIC, &input_port->lasttimestamp);
pthread_mutex_unlock(&input_port->apts_lock);
read_end:
return read_frames;
}
static int _mixer_inport_process(aml_fast_mixer_st *p_mixer, int port_id, int req_frames,
void **p_out_buffer, struct audioCfg *p_out_cfg, const struct audioCfg *p_mix_cfg)
{
int ret = 0;
int read_frames = 0;
int read_samples = 0;
const struct audioCfg *input_cfg = NULL;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_buffer_st *mix_buffer = NULL;
aml_fast_mixer_inport_param_st *input_param = NULL;
fast_mixer_format_buffer_t *p_format_buf = NULL;
void *out_buffer = NULL;
struct audioCfg out_cfg;
input_port = p_mixer->inports[port_id];
input_param = &input_port->param;
input_cfg = &input_param->config;
mix_buffer = &input_port->buffer;
if (input_port->data_valid == false) {
AM_LOGI("data_valid is false");
return 0;
}
if (input_port->data_frames < req_frames) {
AM_LOGW("%s port %d data_frames(%d) < req_frames(%d)", p_mixer->name, port_id, input_port->data_frames, req_frames);
}
memcpy(&out_cfg, input_cfg, sizeof(out_cfg));
out_buffer = input_port->data_bufptr;
read_frames = input_port->data_frames;
// do downmix
if (out_cfg.channelCnt > 2 && out_cfg.channelCnt > p_mix_cfg->channelCnt) {
if (mix_buffer->downmixer == NULL) {
mix_buffer->downmixer = aml_audio_malloc(sizeof(*mix_buffer->downmixer));
if (mix_buffer->downmixer) {
init_aml_pcm_downmix(mix_buffer->downmixer);
} else {
AM_LOGE("malloc %zu bytes fail !", sizeof(*mix_buffer->downmixer));
}
}
if (mix_buffer->downmixer) {
if (do_downmix_to_2ch(mix_buffer->downmixer, out_buffer, read_frames, &out_cfg) >= 0) {
out_cfg.channelCnt = 2;
out_cfg.channelMask = AUDIO_CHANNEL_OUT_STEREO;
out_buffer = mix_buffer->downmixer->output_buf;
}
}
}
/* do resample */
/* need define start_threshold to cache buffer cause length of resample output each time might change*/
if (out_cfg.sampleRate != p_mix_cfg->sampleRate) {
// 1. fast inport should not call resample function.
// 2. this function may be called multi times(one source mixed into multi output), then resample work abnormally.
#if 0
if (aml_fast_mixer_inport_resample(mix_buffer, out_buffer, read_bytes, &out_cfg, mix_cfg) == 0) {
out_cfg.sampleRate = p_mix_cfg->sampleRate;
out_buffer = mix_buffer->resample_handle->resample_buffer;
read_bytes = mix_buffer->resample_handle->resample_size;
read_frames = mix_buffer->resample_handle->resample_size / mix_buffer->resample_handle->frame_bytes;
}
#endif
AM_LOGE("not support sampleRate %d, only support %d", out_cfg.sampleRate, p_mix_cfg->sampleRate);
}
// Do format convert at here, reduce multi output mixer loading.
p_format_buf = &mix_buffer->format_buf;
read_samples = read_frames * out_cfg.channelCnt;
if (out_cfg.format != p_mix_cfg->format) {
if (p_format_buf->format != p_mix_cfg->format || p_format_buf->valid_samples != read_samples) {
size_t req_buf_size = read_samples * audio_bytes_per_sample(p_mix_cfg->format);
ret = aml_audio_check_and_realloc(&p_format_buf->buf_ptr, &p_format_buf->buf_size, req_buf_size);
if ((ret != 0) || (p_format_buf->buf_ptr == NULL)) {
AM_LOGW("allocate format_buf(%zu bytes) failed", req_buf_size);
} else {
memcpy_by_audio_format(p_format_buf->buf_ptr, p_mix_cfg->format, out_buffer, out_cfg.format, read_samples);
out_buffer = p_format_buf->buf_ptr;
out_cfg.format = p_mix_cfg->format;
p_format_buf->format = p_mix_cfg->format;
p_format_buf->valid_samples = read_samples;
}
} else {
out_buffer = p_format_buf->buf_ptr;
out_cfg.format = p_format_buf->format;
}
}
if (p_out_buffer) {
*p_out_buffer = out_buffer;
}
if (p_out_cfg) {
memcpy(p_out_cfg, &out_cfg, sizeof(*p_out_cfg));
}
return read_frames;
}
static void _fast_mixer_enable_limiter(aml_pcm_mixing_st *p_sw_mixer, const struct audioCfg *config)
{
const float limiter_peak = 1.0f / (1 << AML_FAST_MIXER_HEADROOM);
const int proc_frames = AML_FAST_MIXER_PROC_FRAMES;
const int release_time_ms = AML_FAST_MIXER_RELEASE_TIME_MS;
audio_format_t limiter_format = AUDIO_FORMAT_PCM_32_BIT;
if (pcm_mixer_get_limiter_status(p_sw_mixer)) {
// limiter is enabled, nothing to do.
return;
}
if (config->format == AUDIO_FORMAT_PCM_FLOAT) {
limiter_format = AUDIO_FORMAT_PCM_FLOAT;
}
pcm_mixer_set_limiter(p_sw_mixer, limiter_peak, proc_frames, release_time_ms, limiter_format);
pcm_mixer_enable_limiter(p_sw_mixer, true);
}
static inline bool _mixer_inport_to_skip(aml_fast_mixer_inport_st *input_port)
{
if (fast_mixer_inport_message_empty(input_port)) {
if (input_port->state == AML_FAST_MIXER_INPORT_IDLE
|| input_port->state == AML_FAST_MIXER_INPORT_PAUSED
|| input_port->state == AML_FAST_MIXER_INPORT_FLUSHED
|| input_port->state == AML_FAST_MIXER_INPORT_STOPPED
|| input_port->state == AML_FAST_MIXER_INPORT_STANDBYED) {
return true;
}
}
return false;
}
int aml_fast_mixer_mixing(void *fast_mixer, void *buffer, int bytes, const struct audioCfg *p_mix_config, const aml_fast_mixer_pcm_t pcm_type)
{
int index = 0;
int read_frames = 0;
int mix_frames = 0;
void *read_buf = NULL;
struct audioCfg read_cfg;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
aml_pcm_mixing_st *p_sw_mixer = NULL;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (buffer == NULL || 0 == bytes || p_mix_config == NULL) {
AM_LOGE("invalid parameter");
return -EINVAL;
}
if (pcm_type < 0 || pcm_type >= AML_FAST_MIXER_PCM_MAX) {
AM_LOGE("invalid pcm_type %d", pcm_type);
return -EINVAL;
}
p_sw_mixer = mixer->sw_mixer[pcm_type];
mix_frames = bytes / p_mix_config->frame_size;
if (mixer->limiter_enable) {
_fast_mixer_enable_limiter(p_sw_mixer, p_mix_config);
}
pthread_mutex_lock(&mixer->lock);
static_pcm_mixer_start(p_sw_mixer, p_mix_config, buffer, bytes);
for (index = 0; index < AML_FAST_MIXER_INPORT_NUM; index++) {
input_port = mixer->inports[index];
if (input_port == NULL) {
continue;
}
if (_mixer_inport_to_skip(input_port)) {
if (get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER)) {
AM_LOGD("%s port %d, state %d, skip", mixer->name, index, input_port->state);
}
continue;
}
// reading data ...
read_frames = _mixer_inport_process(mixer, index, mix_frames, &read_buf, &read_cfg, p_mix_config);
if (read_frames <= 0) {
continue;
}
// mixing data when type is SW
if (!input_port->use_hw_mixer) {
pcm_mixer_add_data(p_sw_mixer, read_buf, read_frames, &read_cfg);
}
}
mixer->data_used = true;
static_pcm_mixer_end(p_sw_mixer);
pthread_mutex_unlock(&mixer->lock);
return 0;
}
static int _mixer_inport_planar_convert(aml_fast_mixer_inport_st *input_port,
const void *buffer, int ch_num, int frames, audio_format_t format)
{
int i = 0;
int convert_frames = 0;
size_t req_buf_size = 0;
aml_planar_buf_st *p_dst_buf = NULL;
if (input_port == NULL || ch_num > AML_PLANAR_MAX_CH_NUM) {
return 0;
}
p_dst_buf = &input_port->data_planar_buf;
// data is ready, not ready to convert again
if (p_dst_buf->data_valid
&& p_dst_buf->ch_num == ch_num
&& p_dst_buf->format == format
&& p_dst_buf->per_ch_frames == frames) {
return frames;
}
req_buf_size = audio_bytes_per_sample(format) * frames;
for (i = 0; i < ch_num; i++) {
if (aml_audio_check_and_realloc(&p_dst_buf->ch_data[i], &p_dst_buf->ch_buf_size[i], req_buf_size) != 0) {
AM_LOGE("realloc %zu bytes fail !", req_buf_size);
return 0;
}
}
convert_frames = aml_interleaved_to_planar_pcm(buffer, ch_num, frames, format, p_dst_buf);
return convert_frames;
}
int aml_fast_mixer_planar_mix(void *fast_mixer, void **ppdata, int frames, int ch_num,
int sample_rate, audio_format_t format, bool unlimited_float, const aml_fast_mixer_pcm_t pcm_type)
{
int index = 0;
int read_frames = 0;
void *read_buf = NULL;
struct audioCfg read_cfg;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *p_mixer = fast_mixer;
aml_pcm_mixing_st *p_sw_mixer = NULL;
aml_planar_buf_st main_planar_buf;
aml_planar_buf_st *p_in_planar_buf = NULL;
struct audioCfg mix_config;
if (p_mixer == NULL || !p_mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
if (ppdata == NULL || frames <= 0 || ch_num <= 0 || ch_num > AML_PLANAR_MAX_CH_NUM) {
AM_LOGE("invalid param, ppdata %p, frames %d, ch_num %d", ppdata, frames, ch_num);
return -EINVAL;
}
p_sw_mixer = p_mixer->sw_mixer[0];
if (p_sw_mixer == NULL || !p_mixer->data_is_planar) {
AM_LOGE("error, p_sw_mixer %p, data_is_planar %d", p_sw_mixer, p_mixer->data_is_planar);
return -1;
}
aml_planar_fill_buf(&main_planar_buf, ppdata, frames, ch_num, format);
mix_config.sampleRate = sample_rate;
mix_config.channelCnt = ch_num;
mix_config.channelMask = audio_channel_out_mask_from_count(ch_num);
mix_config.format = format;
mix_config.frame_size = audio_bytes_per_sample(format);
pthread_mutex_lock(&p_mixer->lock);
aml_static_planar_pcm_mixer_start(p_sw_mixer, &main_planar_buf);
for (index = 0; index < AML_FAST_MIXER_INPORT_NUM; index++) {
input_port = p_mixer->inports[index];
if (input_port == NULL) {
continue;
}
if (_mixer_inport_to_skip(input_port)) {
if (get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER)) {
AM_LOGD("%s port %d, state %d, skip", p_mixer->name, index, input_port->state);
}
continue;
}
if (input_port->param.encoder_exclusive && pcm_type != AML_FAST_MIXER_PCM_ENCODE) {
if (get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER)) {
AM_LOGD("%s port %d, encoder_exclusive, pcm_type %d, skip", p_mixer->name, index, pcm_type);
}
continue;
}
// reading data ...
read_frames = _mixer_inport_process(p_mixer, index, frames, &read_buf, &read_cfg, &mix_config);
if (read_frames <= 0) {
continue;
}
// convert interleaved pcm to input port data_planar_buf
_mixer_inport_planar_convert(input_port, read_buf, read_cfg.channelCnt, read_frames, read_cfg.format);
p_in_planar_buf = &input_port->data_planar_buf;
// mixing data when type is SW
if (!input_port->use_hw_mixer) {
planar_pcm_mixer_add_data(p_sw_mixer, p_in_planar_buf, unlimited_float);
}
}
p_mixer->data_used = true;
aml_static_planar_pcm_mixer_end(p_sw_mixer);
pthread_mutex_unlock(&p_mixer->lock);
return 0;
}
int aml_fast_mixer_config(void *fast_mixer, aml_fast_mixer_cfg_type_t cfg_type, void* value)
{
int ret = 0;
int enable = 0;
aml_fast_mixer_st *mixer = fast_mixer;
if (!mixer || !value) {
AM_LOGE("invalid parameter");
return -EINVAL;
}
pthread_mutex_lock(&mixer->lock);
switch (cfg_type) {
case AML_FAST_MIXER_CFG_TYPE_DROP:
enable = (*(int *)value != 0);
if (enable != mixer->drop_enable || get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER)) {
AM_LOGI("%s AML_FAST_MIXER_CFG_TYPE_DROP, value %d", mixer->name, enable);
}
mixer->drop_enable = enable;
break;
default:
//add open function for other input type here
AM_LOGE("%s cfg_type %d not supported", mixer->name, cfg_type);
ret = -EINVAL;
break;
}
pthread_mutex_unlock(&mixer->lock);
return ret;
}
bool aml_fast_mixer_output_is_stable(void *fast_mixer)
{
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL) {
AM_LOGE("fast_mixer is NULL");
return false;
}
if (mixer->drop_enable) {
return false;
}
return true;
}
int aml_fast_mixer_inports_read(void *fast_mixer, int request_frame)
{
int ret = 0;
int index = 0;
int read_frame = 0;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
pthread_mutex_lock(&mixer->lock);
for (index = 0; index < AML_FAST_MIXER_INPORT_NUM; index++) {
input_port = mixer->inports[index];
if (input_port == NULL) {
continue;
}
if (_mixer_inport_to_skip(input_port)) {
if (get_debug_value(AML_DEBUG_AUDIOHAL_FAST_MIXER)) {
AM_LOGD("%s port %d, state %d, skip", mixer->name, index, input_port->state);
}
continue;
}
// reading data ...
read_frame = _mixer_inport_read(mixer, index, request_frame);
if (read_frame > ret) {
ret = read_frame;
}
}
pthread_mutex_unlock(&mixer->lock);
return ret;
}
int aml_fast_mixer_inports_invalid(void *fast_mixer)
{
int index = 0;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return -1;
}
pthread_mutex_lock(&mixer->lock);
for (index = 0; index < AML_FAST_MIXER_INPORT_NUM; index++) {
input_port = mixer->inports[index];
if (input_port == NULL) {
continue;
}
_mixer_inport_invalid(input_port);
}
pthread_mutex_unlock(&mixer->lock);
return 0;
}
bool aml_fast_mixer_inports_active(void *fast_mixer)
{
int index = 0;
bool ret = false;
aml_fast_mixer_inport_st *input_port = NULL;
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return false;
}
pthread_mutex_lock(&mixer->lock);
for (index = 0; index < AML_FAST_MIXER_INPORT_NUM; index++) {
input_port = mixer->inports[index];
if (input_port == NULL) {
continue;
}
if (!_mixer_inport_to_skip(input_port)) {
ret = true;
break;
}
}
pthread_mutex_unlock(&mixer->lock);
return ret;
}
bool aml_fast_mixer_get_data_used(const void *fast_mixer)
{
const aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return false;
}
return mixer->data_used;
}
void aml_fast_mixer_set_data_used(void *fast_mixer, bool used)
{
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL || !mixer->initialized) {
AM_LOGE("mixer is NULL or not initialized");
return;
}
mixer->data_used = used;
}
void aml_fast_mixer_enable_limiter(void *fast_mixer, bool enable)
{
aml_fast_mixer_st *mixer = fast_mixer;
if (mixer == NULL) {
AM_LOGE("mixer is NULL");
return;
}
mixer->limiter_enable = enable;
}