/* * 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 #include #include #include #include #include #include #include #include #include #include #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 = ¶m->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; }