| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * 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_hal_submixing" |
| //#define LOG_NDEBUG 0 |
| #define DEBUG_DUMP 0 |
| |
| #define __USE_GNU |
| #include <cutils/log.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <sys/prctl.h> |
| #include <stdlib.h> |
| #include <system/audio.h> |
| #include <aml_volume_utils.h> |
| #include <inttypes.h> |
| #include <audio_utils/primitives.h> |
| |
| #ifdef ENABLE_AEC_APP |
| #include "audio_aec.h" |
| #endif |
| #ifndef NO_AUDIO_CAP |
| #include <IpcBuffer/IpcBuffer_c.h> |
| #endif |
| |
| #include "amlAudioMixer.h" |
| #include "audio_hw_utils.h" |
| #include "hw_avsync.h" |
| #include "audio_hwsync.h" |
| #include "audio_data_process.h" |
| #include "audio_virtual_buf.h" |
| |
| #include "audio_hw.h" |
| #include "a2dp_hal.h" |
| #include "audio_bt_sco.h" |
| #include "aml_audio_timer.h" |
| #include "aml_malloc_debug.h" |
| #include "audio_hw_resource_mgr.h" |
| #include "aml_dump_debug.h" |
| #include "aml_audio_output.h" |
| |
| #define DUMP_AUDIOMIXER_INPORT_READ 0x1 |
| #define DUMP_AUDIOMIXER_OUTDUMP 0x2 |
| |
| static int get_audiomixer_dump_enable(int dump_type) { |
| int value = 0; |
| value = get_debug_value(AML_DUMP_AUDIOHAL_SUBMIXING); |
| return (value & dump_type); |
| } |
| |
| enum { |
| INPORT_NORMAL, // inport not underrun |
| INPORT_UNDERRUN, //inport doesn't have data, underrun may happen later |
| INPORT_FEED_SILENCE_DONE, //underrun will happen quickly, we feed some silence data to avoid noise |
| }; |
| |
| //simple mixer support: 2 in , 1 out |
| struct amlAudioMixer { |
| input_port *in_ports[NR_INPORTS]; |
| uint32_t inportsMasks; // records of inport IDs |
| uint32_t inportsAvailMasks; // 1<< NR_INPORTS - 1 |
| MIXER_OUTPUT_PORT cur_output_port_type; |
| output_port *out_ports[MIXER_OUTPUT_PORT_NUM]; |
| pthread_mutex_t outport_locks[MIXER_OUTPUT_PORT_NUM]; |
| pthread_mutex_t inport_lock; |
| ssize_t (*write)(struct amlAudioMixer *mixer, void *buffer, int bytes); |
| void *in_tmp_buffer; /* mixer temp input buffer. */ |
| void *out_tmp_buffer; /* mixer temp output buffer. */ |
| size_t frame_size_tmp; |
| size_t tmp_buffer_size; /* mixer temp buffer size. */ |
| uint32_t hwsync_frame_size; |
| pthread_t out_mixer_tid; |
| pthread_mutex_t lock; |
| int exit_thread; |
| int mixing_enable; |
| aml_mixer_state state; |
| struct timespec tval_last_write; |
| struct aml_audio_device *adev; |
| bool continuous_output; |
| //int init_ok : 1; |
| int submix_standby; |
| //aml_audio_mixer_run_state_type_e run_state; |
| |
| /*multich pcm output*/ |
| bool mc_out_enable; |
| port_state mc_out_status; |
| aml_pcm_mixing_st multich_mixer; |
| aml_pcm_downmix_st pcm_downmix; |
| /*end*/ |
| }; |
| |
| int mixer_set_state(struct amlAudioMixer *audio_mixer, aml_mixer_state state) |
| { |
| audio_mixer->state = state; |
| return 0; |
| } |
| |
| int mixer_set_continuous_output(struct amlAudioMixer *audio_mixer, |
| bool continuous_output) |
| { |
| audio_mixer->continuous_output = continuous_output; |
| return 0; |
| } |
| |
| bool mixer_is_continuous_enabled(struct amlAudioMixer *audio_mixer) |
| { |
| return audio_mixer->continuous_output; |
| } |
| |
| aml_mixer_state mixer_get_state(struct amlAudioMixer *audio_mixer) |
| { |
| return audio_mixer->state; |
| } |
| |
| /** |
| * Returns the first initialized port according to pMasks and resets |
| * the corresponding bit. Can be called repeatedly to iterate over |
| * initialized ports. |
| */ |
| static inline input_port *mixer_get_inport_by_mask_right_first( |
| struct amlAudioMixer *audio_mixer, uint32_t *pMasks) |
| { |
| uint8_t bit_position = get_bit_position_in_mask(NR_INPORTS - 1, pMasks); |
| return audio_mixer->in_ports[bit_position]; |
| } |
| |
| /** |
| * Returns the index of the first available supported port. |
| */ |
| static unsigned int mixer_get_available_inport_index(struct amlAudioMixer *audio_mixer) |
| { |
| unsigned int index = 0; |
| unsigned int mask = audio_mixer->inportsAvailMasks; |
| |
| AM_LOGD("+inportsAvailMasks: %#x", audio_mixer->inportsAvailMasks); |
| index = __builtin_ctz(audio_mixer->inportsAvailMasks); |
| audio_mixer->inportsAvailMasks &= ~(1 << index); |
| AM_LOGD("-inportsAvailMasks:%#x, index %d", audio_mixer->inportsAvailMasks, index); |
| return index; |
| } |
| |
| int init_mixer_input_port(struct amlAudioMixer *audio_mixer, |
| struct audio_config *config, |
| audio_output_flags_t flags, |
| int (*on_notify_cbk)(void *data), |
| void *notify_data, |
| int (*on_input_avail_cbk)(void *data), |
| void *input_avail_data, |
| meta_data_cbk_t on_meta_data_cbk, |
| void *meta_data, |
| float volume) |
| { |
| R_CHECK_POINTER_LEGAL(-EINVAL, audio_mixer, ""); |
| R_CHECK_POINTER_LEGAL(-EINVAL, config, ""); |
| R_CHECK_POINTER_LEGAL(-EINVAL, notify_data, ""); |
| |
| input_port *in_port = NULL; |
| int port_index = -1; |
| struct aml_stream_out *aml_out = notify_data; |
| bool direct_on = false; |
| |
| if (aml_out->inputPortID != -1) { |
| AM_LOGW("stream input port id:%d exits delete it.", aml_out->inputPortID); |
| delete_mixer_input_port(audio_mixer, aml_out->inputPortID); |
| } |
| |
| port_index = mixer_get_available_inport_index(audio_mixer); |
| R_CHECK_PARAM_LEGAL(-1, port_index, 0, NR_INPORTS - 1, ""); |
| /* if direct on, ie. the ALSA buffer is full, no need padding data anymore */ |
| direct_on = (audio_mixer->in_ports[AML_MIXER_INPUT_PORT_PCM_DIRECT] != NULL); |
| in_port = new_input_port(MIXER_FRAME_COUNT, config, flags, volume, direct_on); |
| |
| if (audio_mixer->in_ports[port_index] != NULL) { |
| AM_LOGW("inport index:[%d]%s already exists! recreate", port_index, mixerInputType2Str(port_index)); |
| free_input_port(audio_mixer->in_ports[port_index]); |
| } |
| |
| in_port->ID = port_index; |
| AM_LOGI("input port:%s, size %d frames, frame_write_sum:%" PRId64 "", |
| mixerInputType2Str(in_port->enInPortType), MIXER_FRAME_COUNT, aml_out->frame_write_sum); |
| audio_mixer->in_ports[port_index] = in_port; |
| audio_mixer->inportsMasks |= 1 << port_index; |
| aml_out->inputPortID = port_index; |
| |
| set_port_notify_cbk(in_port, on_notify_cbk, notify_data); |
| set_port_input_avail_cbk(in_port, on_input_avail_cbk, input_avail_data); |
| if (on_meta_data_cbk && meta_data) { |
| in_port->is_hwsync = true; |
| set_port_meta_data_cbk(in_port, on_meta_data_cbk, meta_data); |
| } |
| in_port->initial_frames = aml_out->frame_write_sum; |
| return 0; |
| } |
| |
| int delete_mixer_input_port(struct amlAudioMixer *audio_mixer, int port_index) |
| { |
| R_CHECK_PARAM_LEGAL(-EINVAL, port_index, 0, NR_INPORTS - 1, ""); |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| |
| AM_LOGI("input port ID:%d, type:%s, cur mask:%#x", port_index, |
| mixerInputType2Str(in_port->enInPortType), audio_mixer->inportsMasks); |
| pthread_mutex_lock(&audio_mixer->lock); |
| pthread_mutex_lock(&audio_mixer->inport_lock); |
| free_input_port(in_port); |
| audio_mixer->in_ports[port_index] = NULL; |
| audio_mixer->inportsMasks &= ~(1 << port_index); |
| audio_mixer->inportsAvailMasks |= 1 << port_index; |
| pthread_mutex_unlock(&audio_mixer->inport_lock); |
| pthread_mutex_unlock(&audio_mixer->lock); |
| return 0; |
| } |
| |
| int send_mixer_inport_message(struct amlAudioMixer *audio_mixer, uint8_t port_index, PORT_MSG msg) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return send_inport_message(in_port, msg); |
| } |
| |
| int send_mixer_outport_message(struct amlAudioMixer *audio_mixer, uint8_t port_index, |
| PORT_MSG msg, void *info, int info_len) |
| { |
| output_port *out_port = audio_mixer->out_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, out_port, "port_index:%d", port_index); |
| return send_outport_message(out_port, msg, info, info_len); |
| } |
| |
| void set_mixer_hwsync_frame_size(struct amlAudioMixer *audio_mixer, uint32_t frame_size) |
| { |
| AM_LOGI("framesize %d", frame_size); |
| audio_mixer->hwsync_frame_size = frame_size; |
| } |
| |
| uint32_t get_mixer_hwsync_frame_size(struct amlAudioMixer *audio_mixer) |
| { |
| return audio_mixer->hwsync_frame_size; |
| } |
| |
| uint32_t get_mixer_inport_consumed_frames(struct amlAudioMixer *audio_mixer, uint8_t port_index) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return get_inport_consumed_size(in_port) / in_port->cfg.frame_size; |
| } |
| |
| int set_mixer_inport_volume(struct amlAudioMixer *audio_mixer, uint8_t port_index, float vol) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| if (vol > 1.0 || vol < 0) { |
| AM_LOGE("invalid vol %f", vol); |
| return -EINVAL; |
| } |
| set_inport_volume(in_port, vol); |
| return 0; |
| } |
| |
| float get_mixer_inport_volume(struct amlAudioMixer *audio_mixer, uint8_t port_index) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return get_inport_volume(in_port); |
| } |
| |
| int mixer_write_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index, const void *buffer, int bytes) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| int written = 0; |
| |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| written = in_port->write(in_port, buffer, bytes); |
| if (get_inport_state(in_port) != ACTIVE) { |
| AM_LOGI("input port:%s is active now", mixerInputType2Str(in_port->enInPortType)); |
| set_inport_state(in_port, ACTIVE); |
| } |
| AM_LOGV("portIndex %d", port_index); |
| return written; |
| } |
| |
| int mixer_read_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index, void *buffer, int bytes) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return in_port->read(in_port, buffer, bytes); |
| } |
| |
| int mixer_set_inport_state(struct amlAudioMixer *audio_mixer, uint8_t port_index, port_state state) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return set_inport_state(in_port, state); |
| } |
| |
| port_state mixer_get_inport_state(struct amlAudioMixer *audio_mixer, uint8_t port_index) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return get_inport_state(in_port); |
| } |
| |
| //TODO: handle message queue |
| static void mixer_procs_msg_queue(struct amlAudioMixer *audio_mixer __unused) |
| { |
| AM_LOGV("start"); |
| return; |
| } |
| |
| static inline output_port *mixer_get_cur_outport_with_lock(struct amlAudioMixer *audio_mixer, MIXER_OUTPUT_PORT port_index) |
| { |
| output_port *out_port = NULL; |
| if (port_index < MIXER_OUTPUT_PORT_STEREO_PCM || port_index > MIXER_OUTPUT_PORT_NUM - 1) { |
| AM_LOGE("port_index err, need check!!"); |
| return NULL; |
| } |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| out_port = audio_mixer->out_ports[port_index]; |
| if (out_port == NULL) { |
| AM_LOGE("out_port is null"); |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| return NULL; |
| } |
| return out_port; |
| } |
| void inline release_cur_outport_lock(struct amlAudioMixer *audio_mixer, MIXER_OUTPUT_PORT port_index) |
| { |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| } |
| |
| |
| size_t get_outport_data_avail(output_port *outport) |
| { |
| return outport->bytes_avail; |
| } |
| |
| int set_outport_data_avail(output_port *outport, size_t avail) |
| { |
| if (avail > outport->data_buf_len) { |
| AM_LOGE("invalid avail %zu", avail); |
| return -EINVAL; |
| } |
| outport->bytes_avail = avail; |
| return 0; |
| } |
| |
| int init_mixer_output_port(struct amlAudioMixer *audio_mixer, |
| MIXER_OUTPUT_PORT output_type, |
| struct audioCfg *config, |
| size_t buf_frames) |
| { |
| R_CHECK_PARAM_LEGAL(-1, output_type, MIXER_OUTPUT_PORT_STEREO_PCM, MIXER_OUTPUT_PORT_NUM - 1, ""); |
| struct aml_audio_device *adev = audio_mixer->adev; |
| |
| pthread_mutex_lock(&audio_mixer->outport_locks[output_type]); |
| AM_LOGI("output port:%s", mixerOutputType2Str(output_type)); |
| output_port *out_port = new_output_port(output_type, config, buf_frames); |
| if (out_port == NULL) { |
| AM_LOGW("new_output_port fail"); |
| pthread_mutex_unlock(&audio_mixer->outport_locks[output_type]); |
| return -1; |
| } |
| audio_mixer->cur_output_port_type = output_type; |
| |
| //set_port_notify_cbk(port, on_notify_cbk, notify_data); |
| //set_port_input_avail_cbk(port, on_input_avail_cbk, input_avail_data); |
| audio_mixer->out_ports[output_type] = out_port; |
| if (config->channelCnt > 2) { |
| aml_mixer_ctrl_set_int(&adev->alsa_mixer, AML_MIXER_ID_I2S2HDMI_FORMAT, AML_MULTI_CH_LPCM); |
| } else { |
| aml_mixer_ctrl_set_int(&adev->alsa_mixer, AML_MIXER_ID_SPDIF_FORMAT, AML_STEREO_PCM); |
| } |
| |
| #ifdef ENABLE_AEC_APP |
| out_port->aec = audio_mixer->adev->aec; |
| struct pcm_config alsa_config; |
| output_get_alsa_config(out_port, &alsa_config); |
| int aec_ret = init_aec_reference_config(out_port->aec, alsa_config); |
| NO_R_CHECK_RET(aec_ret, "AEC: Speaker config init failed!"); |
| #endif |
| pthread_mutex_unlock(&audio_mixer->outport_locks[output_type]); |
| return 0; |
| } |
| |
| int delete_mixer_output_port(struct amlAudioMixer *audio_mixer, MIXER_OUTPUT_PORT port_index) |
| { |
| R_CHECK_PARAM_LEGAL(-1, port_index, MIXER_OUTPUT_PORT_STEREO_PCM, MIXER_OUTPUT_PORT_NUM - 1, ""); |
| struct aml_audio_device *adev = audio_mixer->adev; |
| #ifdef ENABLE_AEC_APP |
| destroy_aec_reference_config(adev->aec); |
| #endif |
| AM_LOGI("output port:%s", mixerOutputType2Str(port_index)); |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| audio_mixer->cur_output_port_type = MIXER_OUTPUT_PORT_INVAL; |
| output_port *out_port = audio_mixer->out_ports[port_index]; |
| if (out_port == NULL) { |
| AM_LOGW("out_port is null"); |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| return -1; |
| } |
| free_output_port(out_port); |
| audio_mixer->out_ports[port_index] = NULL; |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| aml_mixer_ctrl_set_int(&adev->alsa_mixer, AML_MIXER_ID_SPDIF_FORMAT, AML_STEREO_PCM); |
| return 0; |
| } |
| |
| static int mixer_output_startup(struct amlAudioMixer *audio_mixer) |
| { |
| output_port *out_port = NULL; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (out_port) { |
| AM_LOGI("output port:%s", mixerOutputType2Str(port_index)); |
| out_port->start(out_port); |
| release_cur_outport_lock(audio_mixer, port_index); |
| } |
| audio_mixer->submix_standby = 0; |
| |
| return 0; |
| } |
| |
| int mixer_output_standby(struct amlAudioMixer *audio_mixer) |
| { |
| ALOGI("[%s:%d] request sleep thread", __func__, __LINE__); |
| int timeoutMs = 200; |
| (void)audio_mixer; |
| //audio_mixer->run_state = AML_AUDIO_MIXER_RUN_STATE_REQ_SLEEP; |
| return 0; |
| } |
| |
| static int mixer_thread_sleep(struct amlAudioMixer *audio_mixer) |
| { |
| output_port *out_port = NULL; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (out_port) { |
| AM_LOGI("output port:%s", mixerOutputType2Str(port_index)); |
| if (false == audio_mixer->submix_standby) { |
| ALOGI("[%s:%d] start going to standby", __func__, __LINE__); |
| out_port->standby(out_port); |
| audio_mixer->submix_standby = true; |
| } |
| release_cur_outport_lock(audio_mixer, port_index); |
| } |
| return 0; |
| } |
| |
| int mixer_output_dummy(struct amlAudioMixer *audio_mixer, bool en) |
| { |
| output_port *out_port = NULL; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (out_port) { |
| AM_LOGI("output port:%s, en:%d", mixerOutputType2Str(port_index), en); |
| outport_set_dummy(out_port, en); |
| release_cur_outport_lock(audio_mixer, port_index); |
| } |
| |
| return 0; |
| } |
| |
| static int mixer_output_write(struct amlAudioMixer *audio_mixer) |
| { |
| audio_config_base_t in_data_config = {48000, AUDIO_CHANNEL_OUT_STEREO, AUDIO_FORMAT_PCM_16_BIT}; |
| output_port *out_port = NULL; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| /* stereo pcm output */ |
| if (out_port) { |
| struct aml_audio_device *adev = audio_mixer->adev; |
| int count = 3; |
| |
| while (out_port->bytes_avail > 0) { |
| if (audio_mixer->submix_standby) { |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| mixer_output_startup(audio_mixer); |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| } |
| |
| out_port->write(out_port, out_port->data_buf, out_port->bytes_avail); |
| set_outport_data_avail(out_port, 0); |
| }; |
| release_cur_outport_lock(audio_mixer, port_index); |
| } |
| |
| |
| /* output multi-ch pcm */ |
| port_index = MIXER_OUTPUT_PORT_MULTI_PCM; |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| output_port * mc_out_port = audio_mixer->out_ports[port_index]; |
| if (mc_out_port && mc_out_port->bytes_avail > 0) { |
| mc_out_port->write(mc_out_port, mc_out_port->data_buf, mc_out_port->bytes_avail); |
| mc_out_port->bytes_avail = 0; |
| } |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| |
| return 0; |
| } |
| |
| int init_mixer_temp_buffer(struct amlAudioMixer *audio_mixer) |
| { |
| output_port *out_port = NULL; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (out_port) { |
| audio_mixer->frame_size_tmp = out_port->cfg.channelCnt * audio_bytes_per_sample(out_port->cfg.format); |
| release_cur_outport_lock(audio_mixer, port_index); |
| } |
| audio_mixer->tmp_buffer_size = MIXER_FRAME_COUNT * audio_mixer->frame_size_tmp; |
| audio_mixer->in_tmp_buffer = aml_audio_realloc(audio_mixer->in_tmp_buffer, audio_mixer->tmp_buffer_size); |
| if (audio_mixer->in_tmp_buffer == NULL) { |
| AM_LOGW("allocate amlAudioMixer fail."); |
| return -1; |
| } |
| |
| audio_mixer->out_tmp_buffer = aml_audio_realloc(audio_mixer->out_tmp_buffer, audio_mixer->tmp_buffer_size); |
| if (audio_mixer->out_tmp_buffer == NULL) { |
| AM_LOGE("allocate amlAudioMixer out_tmp_buffer no memory"); |
| aml_audio_free(audio_mixer->in_tmp_buffer); |
| audio_mixer->in_tmp_buffer = NULL; |
| return -1; |
| } |
| return 0; |
| } |
| |
| void deinit_mixer_temp_buffer(struct amlAudioMixer *audio_mixer) |
| { |
| if (audio_mixer->in_tmp_buffer) { |
| aml_audio_free(audio_mixer->in_tmp_buffer); |
| audio_mixer->in_tmp_buffer = NULL; |
| } |
| if (audio_mixer->out_tmp_buffer) { |
| aml_audio_free(audio_mixer->out_tmp_buffer); |
| audio_mixer->out_tmp_buffer = NULL; |
| } |
| } |
| |
| #define DEFAULT_KERNEL_FRAMES (DEFAULT_PLAYBACK_PERIOD_SIZE*DEFAULT_PLAYBACK_PERIOD_CNT) |
| |
| static int mixer_update_tstamp(struct amlAudioMixer *audio_mixer) |
| { |
| output_port *out_port = NULL; |
| input_port *in_port = NULL; |
| unsigned int avail; |
| uint32_t masks = audio_mixer->inportsMasks; |
| |
| while (masks) { |
| in_port = mixer_get_inport_by_mask_right_first(audio_mixer, &masks); |
| if (NULL != in_port && in_port->enInPortType == AML_MIXER_INPUT_PORT_PCM_SYSTEM) { |
| break; |
| } |
| } |
| |
| /*only deal with system audio */ |
| if (in_port == NULL) { |
| AM_LOGV("in_port:%p is null", in_port); |
| return 0; |
| } |
| |
| struct aml_audio_device *adev = audio_mixer->adev; |
| #if 0 |
| if (adev->active_outport == OUTPORT_A2DP) { |
| uint64_t a2dp_latency_frames = a2dp_out_get_latency(adev) * in_port->cfg.sampleRate / MSEC_PER_SEC; |
| if (in_port->mix_consumed_frames + in_port->initial_frames > a2dp_latency_frames) { |
| in_port->presentation_frames = in_port->mix_consumed_frames + in_port->initial_frames - a2dp_latency_frames; |
| } else { |
| in_port->presentation_frames = 0; |
| } |
| clock_gettime(CLOCK_MONOTONIC, &in_port->timestamp); |
| return 0; |
| } |
| #endif |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (out_port) { |
| if (aml_audio_pcm_out_get_timestamp(adev, &avail, &in_port->timestamp) == 0) { |
| size_t kernel_buf_size = DEFAULT_KERNEL_FRAMES; |
| int64_t signed_frames = in_port->mix_consumed_frames - kernel_buf_size + avail; |
| if (signed_frames < 0) { |
| signed_frames = 0; |
| } |
| in_port->presentation_frames = in_port->initial_frames + signed_frames; |
| AM_LOGV("present frames:%" PRId64 ", initial %" PRId64 ", consumed %" PRId64 ", sec:%ld, nanosec:%ld", |
| in_port->presentation_frames, |
| in_port->initial_frames, |
| in_port->mix_consumed_frames, |
| in_port->timestamp.tv_sec, |
| in_port->timestamp.tv_nsec); |
| } |
| release_cur_outport_lock(audio_mixer, port_index); |
| } |
| return 0; |
| } |
| |
| inline float get_fade_step_by_size(int fade_size, int frame_size) |
| { |
| return 1.0/(fade_size/frame_size); |
| } |
| |
| int init_fade(struct fade_out *fade_out, int fade_size, |
| int sample_size, int channel_cnt) |
| { |
| fade_out->vol = 1.0; |
| fade_out->target_vol = 0; |
| fade_out->fade_size = fade_size; |
| fade_out->sample_size = sample_size; |
| fade_out->channel_cnt = channel_cnt; |
| fade_out->stride = get_fade_step_by_size(fade_size, sample_size * channel_cnt); |
| AM_LOGI("size %d, stride %f", fade_size, fade_out->stride); |
| return 0; |
| } |
| |
| int process_fade_out(void *buf, int bytes, struct fade_out *fout) |
| { |
| int i = 0; |
| int frame_cnt = bytes / fout->sample_size / fout->channel_cnt; |
| int16_t *sample = (int16_t *)buf; |
| |
| if (fout->channel_cnt != 2 || fout->sample_size != 2) |
| AM_LOGE("not support yet"); |
| AM_LOGI("++++fade out vol %f, size %d", fout->vol, fout->fade_size); |
| for (i = 0; i < frame_cnt; i++) { |
| sample[i] = sample[i]*fout->vol; |
| sample[i+1] = sample[i+1]*fout->vol; |
| fout->vol -= fout->stride; |
| if (fout->vol < 0) |
| fout->vol = 0; |
| } |
| fout->fade_size -= bytes; |
| AM_LOGI("----fade out vol %f, size %d", fout->vol, fout->fade_size); |
| |
| return 0; |
| } |
| |
| static int update_inport_avail(input_port *in_port) |
| { |
| // first throw away the padding frames |
| if (in_port->padding_frames > 0) { |
| in_port->padding_frames -= in_port->data_buf_frame_cnt; |
| set_inport_pts_valid(in_port, false); |
| } else { |
| in_port->mix_consumed_frames += in_port->data_buf_frame_cnt; |
| set_inport_pts_valid(in_port, true); |
| } |
| in_port->data_valid = 1; |
| return 0; |
| } |
| |
| static void process_port_msg(input_port *in_port) |
| { |
| port_message *msg = get_inport_message(in_port); |
| if (msg) { |
| AM_LOGI("msg: %s", port_msg_to_str(msg->msg_what)); |
| switch (msg->msg_what) { |
| case MSG_PAUSE: { |
| struct aml_stream_out *out = (struct aml_stream_out *)in_port->notify_cbk_data; |
| if ((out->avsync_ctx) && (out->avsync_ctx->mediasync_ctx)) { |
| mediasync_wrap_setPause(out->avsync_ctx->mediasync_ctx->handle, true); |
| } |
| set_inport_state(in_port, PAUSING); |
| break; |
| } |
| case MSG_FLUSH: |
| set_inport_state(in_port, FLUSHING); |
| break; |
| |
| case MSG_RESUME: { |
| set_inport_state(in_port, RESUMING); |
| break; |
| } |
| default: |
| AM_LOGE("not support"); |
| } |
| |
| remove_inport_message(in_port, msg); |
| } |
| } |
| |
| int mixer_flush_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return reset_input_port(in_port); |
| } |
| |
| int mixer_seek_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index, int bytes) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return seek_input_port(in_port, bytes); |
| } |
| |
| static int mixer_inports_read(struct amlAudioMixer *audio_mixer) |
| { |
| unsigned int masks = audio_mixer->inportsMasks; |
| AM_LOGV("+++"); |
| while (masks) { |
| input_port *in_port = NULL; |
| in_port = mixer_get_inport_by_mask_right_first(audio_mixer, &masks); |
| if (NULL == in_port) { |
| continue; |
| } |
| int ret = 0, fade_out = 0, fade_in = 0; |
| aml_mixer_input_port_type_e type = in_port->enInPortType; |
| process_port_msg(in_port); |
| port_state state = get_inport_state(in_port); |
| |
| if (type == AML_MIXER_INPUT_PORT_PCM_DIRECT) { |
| //if in pausing states, don't retrieve data |
| if (state == PAUSING) { |
| fade_out = 1; |
| } else if (state == RESUMING) { |
| fade_in = 1; |
| AM_LOGI("input port:%s tsync resume", mixerInputType2Str(type)); |
| set_inport_state(in_port, ACTIVE); |
| } else if (state == STOPPED || state == PAUSED || state == FLUSHED) { |
| AM_LOGV("input port:%s stopped, paused or flushed", mixerInputType2Str(type)); |
| continue; |
| } else if (state == FLUSHING) { |
| mixer_flush_inport(audio_mixer, in_port->ID); |
| AM_LOGI("input port:%s flushing->flushed", mixerInputType2Str(type)); |
| set_inport_state(in_port, FLUSHED); |
| continue; |
| } |
| if (get_inport_state(in_port) == ACTIVE && in_port->data_valid) { |
| AM_LOGI("input port:%s data already valid", mixerInputType2Str(type)); |
| continue; |
| } |
| } else { |
| if (in_port->data_valid) { |
| AM_LOGI("input port ID:%d port:%s, data already valid", in_port->ID, mixerInputType2Str(type)); |
| continue; |
| } |
| } |
| |
| int input_avail_size = in_port->rbuf_avail(in_port); |
| AM_LOGV("input port:%s, portId:%d, avail:%d, masks:%#x, inportsMasks:%#x, data_len_bytes:%zu", |
| mixerInputType2Str(type), in_port->ID, input_avail_size, masks, audio_mixer->inportsMasks, in_port->data_len_bytes); |
| if (input_avail_size >= in_port->data_len_bytes) { |
| if (in_port->first_read) { |
| if (input_avail_size < in_port->inport_start_threshold) { |
| continue; |
| } else { |
| AM_LOGI("input port:%s first start, portId:%d, avail:%d", |
| mixerInputType2Str(type), in_port->ID, input_avail_size); |
| in_port->first_read = false; |
| } |
| } |
| ret = mixer_read_inport(audio_mixer, in_port->ID, in_port->data, in_port->data_len_bytes); |
| if (ret == (int)in_port->data_len_bytes) { |
| apply_volume_fade(in_port->last_volume, in_port->volume, in_port->data, sizeof(uint16_t), in_port->cfg.channelCnt, in_port->data_len_bytes); |
| in_port->last_volume = in_port->volume; |
| if (fade_out) { |
| struct aml_stream_out *out = (struct aml_stream_out *)in_port->notify_cbk_data; |
| AM_LOGI("output port:%s fade out, pausing->pausing_1, tsync pause audio. mediasync_ctx %p", mixerInputType2Str(type), out->avsync_ctx->mediasync_ctx); |
| if (out->avsync_ctx->mediasync_ctx) |
| mediasync_wrap_setPause(out->avsync_ctx->mediasync_ctx->handle, true); |
| audio_fade_func(in_port->data, ret, 0); |
| set_inport_state(in_port, PAUSED); |
| /* Mute the last data to prevent gap. */ |
| ring_buffer_clear(in_port->r_buf); |
| } else if (fade_in) { |
| AM_LOGI("input port:%s fade in", mixerInputType2Str(type)); |
| audio_fade_func(in_port->data, ret, 1); |
| set_inport_state(in_port, ACTIVE); |
| } |
| update_inport_avail(in_port); |
| if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_INPORT_READ)) { |
| char file_name[128] = { 0 }; |
| snprintf(file_name, 128, "%s_%d.pcm", "inport_read", in_port->ID); |
| aml_dump_audio_bitstreams(file_name, in_port->data, in_port->data_len_bytes); |
| } |
| } else { |
| AM_LOGW("port:%s read fail, have read:%d Byte, need %zu Byte", |
| mixerInputType2Str(type), ret, in_port->data_len_bytes); |
| } |
| } else { |
| struct aml_audio_device *adev = audio_mixer->adev; |
| if (adev->debug_flag) { |
| AM_LOGD("port:%d ring buffer data is not enough", in_port->ID); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| int mixer_need_wait_forever(struct amlAudioMixer *audio_mixer) |
| { |
| return mixer_get_state(audio_mixer) != MIXER_INPORTS_READY; |
| } |
| |
| static inline int16_t CLIP16(int r) |
| { |
| return (r > 0x7fff) ? 0x7fff : |
| (r < -0x8000) ? 0x8000 : |
| r; |
| } |
| |
| static uint32_t hwsync_align_to_frame(uint32_t consumed_size, uint32_t frame_size) |
| { |
| return consumed_size - (consumed_size % frame_size); |
| } |
| |
| static int retrieve_hwsync_header(struct amlAudioMixer *audio_mixer, |
| input_port *in_port, output_port *out_port) |
| { |
| uint32_t frame_size = get_mixer_hwsync_frame_size(audio_mixer); |
| uint32_t port_consumed_size = get_inport_consumed_size(in_port); |
| int diff_ms = 0; |
| struct hw_avsync_header header; |
| int ret = 0; |
| |
| if (frame_size == 0) { |
| AM_LOGV("invalid frame size 0"); |
| return -EINVAL; |
| } |
| |
| if (!in_port->is_hwsync) { |
| AM_LOGE("not hwsync port"); |
| return -EINVAL; |
| } |
| |
| memset(&header, 0, sizeof(struct hw_avsync_header)); |
| AM_LOGV("direct out port bytes before cbk %zu", out_port->bytes_avail); |
| if (!in_port->meta_data_cbk) { |
| AM_LOGE("no meta_data_cbk set!!"); |
| return -EINVAL; |
| } |
| AM_LOGV("port %p, data %p", in_port, in_port->meta_data_cbk_data); |
| ret = in_port->meta_data_cbk(in_port->meta_data_cbk_data, |
| port_consumed_size, &header, &diff_ms); |
| if (ret < 0) { |
| if (ret != -EAGAIN) |
| AM_LOGE("meta_data_cbk fail err = %d!!", ret); |
| return ret; |
| } |
| AM_LOGV("meta data cbk, diff ms = %d", diff_ms); |
| if (diff_ms > 0) { |
| in_port->bytes_to_insert = diff_ms * 48 * 4; |
| } else if (diff_ms < 0) { |
| in_port->bytes_to_skip = -diff_ms * 48 * 4; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int mixer_do_mixing_32bit(struct amlAudioMixer *audio_mixer) |
| { |
| input_port *in_port_sys = audio_mixer->in_ports[AML_MIXER_INPUT_PORT_PCM_SYSTEM]; |
| input_port *in_port_drct = audio_mixer->in_ports[AML_MIXER_INPUT_PORT_PCM_DIRECT]; |
| output_port *out_port = NULL; |
| struct aml_audio_device *adev = audio_mixer->adev; |
| int16_t *data_sys, *data_drct, *data_mixed; |
| int mixing = 0, sys_only = 0, direct_only = 0; |
| int dirct_okay = 0, sys_okay = 0; |
| float dirct_vol = 1.0, sys_vol = 1.0; |
| int mixed_32 = 0; |
| size_t i = 0, mixing_len_bytes = 0; |
| size_t frames = 0; |
| size_t frames_written = 0; |
| float gain_speaker = adev->sink_gain[OUTPORT_SPEAKER]; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (NULL == out_port) { |
| AM_LOGE("out_port is NULL, need check!!!"); |
| return -1; |
| } |
| release_cur_outport_lock(audio_mixer, port_index); |
| |
| if (!in_port_sys && !in_port_drct) { |
| AM_LOGE("sys or direct pcm must exist!!!"); |
| return 0; |
| } |
| |
| if (in_port_sys && in_port_sys->data_valid) { |
| sys_okay = 1; |
| } |
| if (in_port_drct && in_port_drct->data_valid) { |
| dirct_okay = 1; |
| } |
| if (sys_okay && dirct_okay) { |
| mixing = 1; |
| } else if (dirct_okay) { |
| AM_LOGV("only direct okay"); |
| direct_only = 1; |
| } else if (sys_okay) { |
| sys_only = 1; |
| } else { |
| AM_LOGV("sys direct both not ready!"); |
| return -EINVAL; |
| } |
| |
| data_mixed = (int16_t *)out_port->data_buf; |
| memset(audio_mixer->out_tmp_buffer, 0 , MIXER_FRAME_COUNT * out_port->cfg.frame_size); |
| if (mixing) { |
| AM_LOGV("mixing"); |
| data_sys = (int16_t *)in_port_sys->data; |
| data_drct = (int16_t *)in_port_drct->data; |
| mixing_len_bytes = in_port_drct->data_len_bytes; |
| //TODO: check if the two stream's frames are equal |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("audiodrct.raw", in_port_drct->data, in_port_drct->data_len_bytes); |
| aml_dump_audio_bitstreams("audiosyst.raw", in_port_sys->data, in_port_sys->data_len_bytes); |
| } |
| if (in_port_drct->is_hwsync && in_port_drct->bytes_to_insert < mixing_len_bytes) { |
| retrieve_hwsync_header(audio_mixer, in_port_drct, out_port); |
| } |
| |
| // insert data for direct hwsync case, only send system sound |
| if (in_port_drct->bytes_to_insert >= mixing_len_bytes) { |
| frames = mixing_len_bytes / in_port_drct->cfg.frame_size; |
| AM_LOGD("insert mixing data, need %zu, insert length %zu", |
| in_port_drct->bytes_to_insert, mixing_len_bytes); |
| //memcpy(data_mixed, data_sys, mixing_len_bytes); |
| //memcpy(audio_mixer->out_tmp_buffer, data_sys, mixing_len_bytes); |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("systbeforemix.raw", |
| data_sys, in_port_sys->data_len_bytes); |
| } |
| frames_written = do_mixing_2ch(audio_mixer->out_tmp_buffer, data_sys, |
| frames, in_port_sys->cfg.format, out_port->cfg.format); |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("sysAftermix.raw", |
| audio_mixer->out_tmp_buffer, frames * FRAMESIZE_32BIT_STEREO); |
| } |
| if (is_TV(adev)) { |
| apply_volume(gain_speaker, audio_mixer->out_tmp_buffer, |
| sizeof(uint32_t), frames * FRAMESIZE_32BIT_STEREO); |
| } |
| |
| extend_channel_2_8(data_mixed, audio_mixer->out_tmp_buffer, |
| frames, 2, 8); |
| |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("dataInsertMixed.raw", |
| data_mixed, frames * out_port->cfg.frame_size); |
| } |
| in_port_drct->bytes_to_insert -= mixing_len_bytes; |
| in_port_sys->data_valid = 0; |
| set_outport_data_avail(out_port, frames * out_port->cfg.frame_size); |
| } else { |
| frames = mixing_len_bytes / in_port_drct->cfg.frame_size; |
| frames_written = do_mixing_2ch(audio_mixer->out_tmp_buffer, data_drct, |
| frames, in_port_drct->cfg.format, out_port->cfg.format); |
| if (DEBUG_DUMP) |
| aml_dump_audio_bitstreams("tmpMixed0.raw", |
| audio_mixer->out_tmp_buffer, frames * audio_mixer->frame_size_tmp); |
| frames_written = do_mixing_2ch(audio_mixer->out_tmp_buffer, data_sys, |
| frames, in_port_sys->cfg.format, out_port->cfg.format); |
| if (DEBUG_DUMP) |
| aml_dump_audio_bitstreams("tmpMixed1.raw", |
| audio_mixer->out_tmp_buffer, frames * audio_mixer->frame_size_tmp); |
| if (is_TV(adev)) { |
| apply_volume(gain_speaker, audio_mixer->out_tmp_buffer, |
| sizeof(uint32_t), frames * FRAMESIZE_32BIT_STEREO); |
| } |
| |
| extend_channel_2_8(data_mixed, audio_mixer->out_tmp_buffer, |
| frames, 2, 8); |
| |
| in_port_drct->data_valid = 0; |
| in_port_sys->data_valid = 0; |
| set_outport_data_avail(out_port, frames * out_port->cfg.frame_size); |
| } |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("data_mixed.raw", |
| out_port->data_buf, frames * out_port->cfg.frame_size); |
| } |
| } |
| |
| if (sys_only) { |
| frames = in_port_sys->data_buf_frame_cnt; |
| AM_LOGV("sys_only, frames %zu", frames); |
| mixing_len_bytes = in_port_sys->data_len_bytes; |
| data_sys = (int16_t *)in_port_sys->data; |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("audiosyst.raw", |
| in_port_sys->data, mixing_len_bytes); |
| } |
| // processing data and make conversion according to cfg |
| // processing_and_convert(data_mixed, data_sys, frames, in_port_sys->cfg, out_port->cfg); |
| frames_written = do_mixing_2ch(audio_mixer->out_tmp_buffer, data_sys, |
| frames, in_port_sys->cfg.format, out_port->cfg.format); |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("sysTmp.raw", |
| audio_mixer->out_tmp_buffer, frames * FRAMESIZE_32BIT_STEREO); |
| } |
| if (is_TV(adev)) { |
| apply_volume(gain_speaker, audio_mixer->out_tmp_buffer, |
| sizeof(uint32_t), frames * FRAMESIZE_32BIT_STEREO); |
| } |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("sysvol.raw", |
| audio_mixer->out_tmp_buffer, frames * FRAMESIZE_32BIT_STEREO); |
| } |
| |
| extend_channel_2_8(data_mixed, audio_mixer->out_tmp_buffer, frames, 2, 8); |
| |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("extendsys.raw", |
| data_mixed, frames * out_port->cfg.frame_size); |
| } |
| in_port_sys->data_valid = 0; |
| set_outport_data_avail(out_port, frames * out_port->cfg.frame_size); |
| } |
| |
| if (direct_only) { |
| AM_LOGV("direct_only"); |
| //dirct_vol = get_inport_volume(in_port_drct); |
| mixing_len_bytes = in_port_drct->data_len_bytes; |
| data_drct = (int16_t *)in_port_drct->data; |
| AM_LOGV("direct_only, inport consumed %zu", |
| get_inport_consumed_size(in_port_drct)); |
| |
| if (in_port_drct->is_hwsync && in_port_drct->bytes_to_insert < mixing_len_bytes) { |
| retrieve_hwsync_header(audio_mixer, in_port_drct, out_port); |
| } |
| |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("audiodrct.raw", |
| in_port_drct->data, mixing_len_bytes); |
| } |
| // insert 0 data to delay audio |
| if (in_port_drct->bytes_to_insert >= mixing_len_bytes) { |
| frames = mixing_len_bytes / in_port_drct->cfg.frame_size; |
| AM_LOGD("inserting direct_only, need %zu, insert length %zu", |
| in_port_drct->bytes_to_insert, mixing_len_bytes); |
| memset(data_mixed, 0, mixing_len_bytes); |
| extend_channel_2_8(data_mixed, audio_mixer->out_tmp_buffer, |
| frames, 2, 8); |
| in_port_drct->bytes_to_insert -= mixing_len_bytes; |
| set_outport_data_avail(out_port, frames * out_port->cfg.frame_size); |
| } else { |
| AM_LOGV("direct_only, vol %f", dirct_vol); |
| frames = mixing_len_bytes / in_port_drct->cfg.frame_size; |
| //cpy_16bit_data_with_gain(data_mixed, data_drct, |
| // in_port_drct->data_len_bytes, dirct_vol); |
| AM_LOGV("direct_only, frames %zu, bytes %zu", frames, mixing_len_bytes); |
| |
| frames_written = do_mixing_2ch(audio_mixer->out_tmp_buffer, data_drct, |
| frames, in_port_drct->cfg.format, out_port->cfg.format); |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("dirctTmp.raw", |
| audio_mixer->out_tmp_buffer, frames * FRAMESIZE_32BIT_STEREO); |
| } |
| if (is_TV(adev)) { |
| apply_volume(gain_speaker, audio_mixer->out_tmp_buffer, |
| sizeof(uint32_t), frames * FRAMESIZE_32BIT_STEREO); |
| } |
| |
| extend_channel_2_8(data_mixed, audio_mixer->out_tmp_buffer, |
| frames, 2, 8); |
| |
| if (DEBUG_DUMP) { |
| aml_dump_audio_bitstreams("exDrct.raw", |
| data_mixed, frames * out_port->cfg.frame_size); |
| } |
| in_port_drct->data_valid = 0; |
| set_outport_data_avail(out_port, frames * out_port->cfg.frame_size); |
| } |
| } |
| |
| if (0) { |
| aml_dump_audio_bitstreams("data_mixed.raw", |
| out_port->data_buf, mixing_len_bytes); |
| } |
| return 0; |
| } |
| |
| static int mixer_add_mixing_data(struct amlAudioMixer *audio_mixer, void *input, input_port *in_port, output_port *out_port) |
| { |
| aml_pcm_mixing_st *p_multich_mixer = &audio_mixer->multich_mixer; |
| aml_pcm_downmix_st *p_downmix = &audio_mixer->pcm_downmix; |
| if (in_port->data_buf_frame_cnt < MIXER_FRAME_COUNT) { |
| AM_LOGE("input port type:%s buf frames:%zu too small", |
| mixerInputType2Str(in_port->enInPortType), in_port->data_buf_frame_cnt); |
| return -EINVAL; |
| } |
| if (in_port->cfg.channelCnt != 2) { |
| do_downmix_to_2ch(p_downmix, input, MIXER_FRAME_COUNT, &in_port->cfg); |
| do_mixing_2ch(audio_mixer->out_tmp_buffer, p_downmix->output_buf, MIXER_FRAME_COUNT, in_port->cfg.format, out_port->cfg.format); |
| } else { |
| do_mixing_2ch(audio_mixer->out_tmp_buffer, input, MIXER_FRAME_COUNT, in_port->cfg.format, out_port->cfg.format); |
| } |
| |
| if (audio_mixer->mc_out_enable && audio_mixer->mc_out_status != STOPPED) { |
| do_mixing_multi_ch(p_multich_mixer, input, MIXER_FRAME_COUNT, &in_port->cfg); |
| } |
| in_port->data_valid = 0; |
| AM_LOGV("input port ID:%d channels:%d", in_port->ID, out_port->cfg.channelCnt); |
| return 0; |
| } |
| |
| int init_multich_mixer_buffer(struct amlAudioMixer *audio_mixer, struct audioCfg *p_mixer_cfg, int mixed_frames) |
| { |
| R_CHECK_POINTER_LEGAL(-1, audio_mixer, ""); |
| R_CHECK_POINTER_LEGAL(-1, p_mixer_cfg, ""); |
| |
| if (init_aml_pcm_mixer(&audio_mixer->multich_mixer, p_mixer_cfg, mixed_frames) != 0) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| void deinit_multich_mixer_buffer(struct amlAudioMixer *audio_mixer) |
| { |
| if (audio_mixer == NULL) { |
| AM_LOGV("audio_mixer = NULL"); |
| return; |
| } |
| deinit_aml_pcm_mixer(&audio_mixer->multich_mixer); |
| } |
| |
| void mixer_enable_multich_output(struct amlAudioMixer *audio_mixer, bool enable) |
| { |
| output_port *mc_out_port = NULL; |
| const MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_MULTI_PCM; |
| if (audio_mixer->mc_out_enable == enable) { |
| return; |
| } |
| AM_LOGI("mc_out_enable %d", enable); |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| audio_mixer->mc_out_enable = enable; |
| mc_out_port = audio_mixer->out_ports[port_index]; |
| if (!enable && mc_out_port) { |
| /* close multich alsa handle, then npcm can use it. */ |
| if (mc_out_port->port_status != STOPPED) { |
| mc_out_port->standby(mc_out_port); |
| } |
| free_mc_output_port(mc_out_port); |
| audio_mixer->out_ports[port_index] = NULL; |
| } |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| } |
| |
| /* |
| * 2ch pcm output always exist, multi-ch pcm output is additional and dependent with sink device. |
| */ |
| static int mixer_config_multich_output(struct amlAudioMixer *audio_mixer, struct audioCfg *out_port_cfg) |
| { |
| int ret = 0; |
| uint32_t masks = 0; |
| input_port *in_port = NULL; |
| int sink_max_channels = 2; |
| uint32_t input_channels = 2; |
| uint32_t mixer_max_channels = 2; |
| audio_channel_mask_t mixer_max_ch_mask = AUDIO_CHANNEL_OUT_STEREO; |
| struct aml_audio_device *adev = audio_mixer->adev; |
| struct audioCfg *p_mixer_cfg = &audio_mixer->multich_mixer.cfg; |
| struct audioCfg mixer_cfg; |
| bool input_port_empty = true; |
| const MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_MULTI_PCM; |
| struct aml_arc_hdmi_desc* hdmi_descs = get_arc_hdmi_cap(adev); |
| |
| sink_max_channels = hdmi_descs->pcm_fmt.max_channels; |
| if (is_TV(adev)) { |
| if (is_earc_connected(adev)) { |
| sink_max_channels = 8; |
| } else { |
| sink_max_channels = 2; |
| } |
| } |
| |
| if (is_bypass_submix_active(adev)) { |
| AM_LOGV("is_bypass_submix_active"); |
| return 0; |
| } |
| |
| #if 0 |
| masks = audio_mixer->inportsMasks; |
| while (masks) { |
| in_port = mixer_get_inport_by_mask_right_first(audio_mixer, &masks); |
| if (in_port == NULL) { |
| continue; |
| } |
| |
| input_port_empty = false; |
| input_channels = in_port->cfg.channelCnt; |
| if (input_channels > mixer_max_channels && input_channels <= sink_max_channels) { |
| /*multich_output default output 8 channel*/ |
| mixer_max_channels = 8; |
| mixer_max_ch_mask = AUDIO_CHANNEL_OUT_7POINT1; |
| } |
| } |
| if (input_port_empty) { |
| mixer_max_channels = 2; |
| mixer_max_ch_mask = AUDIO_CHANNEL_OUT_STEREO; |
| } |
| |
| /* If not connected A2DP/Headphone, and HDMI RX/ARC supports multi-channel, so we have multi-channel output. |
| * Otherwise the default 2 channel output. |
| */ |
| if (adev->out_device & AUDIO_DEVICE_OUT_ALL_A2DP |
| || adev->out_device & AUDIO_DEVICE_OUT_WIRED_HEADSET |
| || adev->out_device & AUDIO_DEVICE_OUT_WIRED_HEADPHONE |
| || !audio_mixer->mc_out_enable) { |
| mixer_max_channels = 2; |
| mixer_max_ch_mask = AUDIO_CHANNEL_OUT_STEREO; |
| } else if (adev->is_netflix) { |
| if (mixer_max_channels <= 8 && sink_max_channels >= 8) { |
| /*multich_output default output 8 channel*/ |
| mixer_max_channels = 8; |
| mixer_max_ch_mask = AUDIO_CHANNEL_OUT_7POINT1; |
| } |
| } |
| #else |
| mixer_max_channels = 8; |
| mixer_max_ch_mask = AUDIO_CHANNEL_OUT_7POINT1; |
| #endif |
| |
| if ((mixer_max_channels != p_mixer_cfg->channelCnt) || (mixer_max_ch_mask != p_mixer_cfg->channelMask)) { |
| memcpy(&mixer_cfg, out_port_cfg, sizeof(*out_port_cfg)); |
| mixer_cfg.format = AUDIO_FORMAT_PCM_16_BIT; // raw (IEC format) output always output 16bit pcm |
| mixer_cfg.channelCnt = mixer_max_channels; |
| mixer_cfg.channelMask = mixer_max_ch_mask; |
| mixer_cfg.frame_size = mixer_cfg.channelCnt * audio_bytes_per_sample(mixer_cfg.format); |
| deinit_multich_mixer_buffer(audio_mixer); |
| init_multich_mixer_buffer(audio_mixer, &mixer_cfg, MIXER_FRAME_COUNT); |
| } |
| |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| output_port *mc_out_port = audio_mixer->out_ports[port_index]; |
| if (audio_mixer->mc_out_enable && mixer_max_channels != 2) { |
| if (mc_out_port == NULL || mc_out_port->cfg.channelCnt != mixer_max_channels) { |
| AM_LOGI("mc output channel change to %d", mixer_max_channels); |
| if (mc_out_port != NULL) { |
| free_mc_output_port(mc_out_port); |
| audio_mixer->out_ports[port_index] = NULL; |
| } |
| |
| mc_out_port = new_mc_output_port(p_mixer_cfg, MIXER_FRAME_COUNT); |
| if (mc_out_port == NULL) { |
| AM_LOGE("new_mc_output_port failed !"); |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| return -1; |
| } |
| audio_mixer->out_ports[port_index] = mc_out_port; |
| mc_out_port->start(mc_out_port); |
| } else if (mc_out_port->port_status != ACTIVE) { |
| mc_out_port->start(mc_out_port); |
| } |
| }else if (mc_out_port) { |
| if (mc_out_port->port_status != STOPPED) { |
| mc_out_port->standby(mc_out_port); |
| } |
| mc_out_port->port_status = STOPPED; |
| } |
| if (mc_out_port) { |
| audio_mixer->mc_out_status = mc_out_port->port_status; |
| } else { |
| audio_mixer->mc_out_status = STOPPED; |
| } |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| AM_LOGV("****mixer_max_channels:%u, sink_max_channels:%d", mixer_max_channels, sink_max_channels); |
| |
| return ret; |
| } |
| |
| static int mixer_do_mixing_16bit(struct amlAudioMixer *audio_mixer) |
| { |
| bool is_data_valid = false; |
| input_port *in_port = NULL; |
| output_port *out_port = NULL; |
| struct aml_audio_device *adev = audio_mixer->adev; |
| char acFilePathStr[ENUM_TYPE_STR_MAX_LEN] = {0}; |
| uint32_t need_output_ch = 2; |
| uint32_t cur_output_ch = 2; |
| uint32_t masks = 0; |
| aml_pcm_mixing_st *p_multich_mixer = &audio_mixer->multich_mixer; |
| struct aml_arc_hdmi_desc *hdmi_desc = get_arc_hdmi_cap(adev); |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (!out_port) { |
| AM_LOGE("out_port is NULL, need check!!!"); |
| return -1; |
| } |
| cur_output_ch = out_port->cfg.channelCnt; |
| release_cur_outport_lock(audio_mixer, port_index); |
| mixer_config_multich_output(audio_mixer, &out_port->cfg); |
| if (p_multich_mixer->mixed_buf) { |
| memset(p_multich_mixer->mixed_buf, 0, p_multich_mixer->mixed_buf_size); |
| } |
| |
| memset(audio_mixer->out_tmp_buffer, 0, audio_mixer->tmp_buffer_size); |
| masks = audio_mixer->inportsMasks; |
| while (masks) { |
| in_port = mixer_get_inport_by_mask_right_first(audio_mixer, &masks); |
| if (NULL == in_port || 0 == in_port->data_valid) { |
| continue; |
| } |
| is_data_valid = true; |
| if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_INPORT_READ)) { |
| snprintf(acFilePathStr, ENUM_TYPE_STR_MAX_LEN, "%s_%d.pcm", "SubMix_before_mixer_inport", in_port->ID); |
| aml_dump_audio_bitstreams(acFilePathStr, in_port->data, in_port->data_len_bytes); |
| } |
| //if (get_debug_value(AML_DEBUG_AUDIOHAL_LEVEL_DETECT)) { |
| // check_audio_level(mixerInputType2Str(in_port->enInPortType), in_port->data, in_port->data_len_bytes); |
| //} |
| if (AML_MIXER_INPUT_PORT_PCM_DIRECT == in_port->enInPortType) { |
| if (in_port->is_hwsync && in_port->bytes_to_insert < in_port->data_len_bytes) { |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| retrieve_hwsync_header(audio_mixer, in_port, out_port); |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| } |
| if (in_port->bytes_to_insert >= in_port->data_len_bytes) { |
| in_port->bytes_to_insert -= in_port->data_len_bytes; |
| AM_LOGD("PCM_DIRECT inport insert mute data, still need %zu, inserted length %zu", |
| in_port->bytes_to_insert, in_port->data_len_bytes); |
| continue; |
| } |
| } |
| |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| mixer_add_mixing_data(audio_mixer, in_port->data, in_port, out_port); |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| } |
| /* only check the valid on a2dp case, normal alsa output we need continuous output, |
| * otherwise it will cause noise at the end |
| */ |
| if (!is_data_valid && (adev->out_device & AUDIO_DEVICE_OUT_ALL_A2DP)) { |
| if (adev->debug_flag) { |
| AM_LOGI("inport no valid data"); |
| } |
| return -1; |
| } |
| |
| /* stereo pcm output */ |
| pthread_mutex_lock(&audio_mixer->outport_locks[port_index]); |
| memcpy(out_port->data_buf, audio_mixer->out_tmp_buffer, audio_mixer->tmp_buffer_size); |
| if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) { |
| snprintf(acFilePathStr, ENUM_TYPE_STR_MAX_LEN, "%s_%dch.pcm", "SubMix_after_mixed", need_output_ch); |
| aml_dump_audio_bitstreams(acFilePathStr, out_port->data_buf, audio_mixer->tmp_buffer_size); |
| } |
| //if (get_debug_value(AML_DEBUG_AUDIOHAL_LEVEL_DETECT)) { |
| // check_audio_level("audio_mixed", out_port->data_buf, audio_mixer->tmp_buffer_size); |
| //} |
| set_outport_data_avail(out_port, audio_mixer->tmp_buffer_size); |
| pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]); |
| |
| /* multich_output data */ |
| pthread_mutex_lock(&audio_mixer->outport_locks[MIXER_OUTPUT_PORT_MULTI_PCM]); |
| output_port *mc_out_port = audio_mixer->out_ports[MIXER_OUTPUT_PORT_MULTI_PCM]; |
| void *mixed_data_ptr = p_multich_mixer->mixed_buf; |
| int mixed_data_size = p_multich_mixer->mixed_buf_size; |
| if (mixed_data_ptr && mc_out_port && mc_out_port->port_status != STOPPED) { |
| if (mixed_data_size > mc_out_port->data_buf_len) { |
| AM_LOGE("mixed_data_size too large(%d > %zu), truncate", mixed_data_size, mc_out_port->data_buf_len); |
| mixed_data_size = mc_out_port->data_buf_len; |
| } |
| memcpy(mc_out_port->data_buf, mixed_data_ptr, mixed_data_size); |
| mc_out_port->bytes_avail = mixed_data_size; |
| if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) { |
| snprintf(acFilePathStr, ENUM_TYPE_STR_MAX_LEN, "%s_mch.pcm", "SubMix_after_mixed"); |
| aml_dump_audio_bitstreams(acFilePathStr, mc_out_port->data_buf, mc_out_port->bytes_avail); |
| } |
| } |
| pthread_mutex_unlock(&audio_mixer->outport_locks[MIXER_OUTPUT_PORT_MULTI_PCM]); |
| return 0; |
| } |
| |
| int notify_mixer_input_avail(struct amlAudioMixer *audio_mixer) |
| { |
| for (uint8_t port_index = 0; port_index < NR_INPORTS; port_index++) { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| if (in_port && in_port->on_input_avail_cbk) |
| in_port->on_input_avail_cbk(in_port->input_avail_cbk_data); |
| } |
| |
| return 0; |
| } |
| |
| int notify_mixer_exit(struct amlAudioMixer *audio_mixer) |
| { |
| for (uint8_t port_index = 0; port_index < NR_INPORTS; port_index++) { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| if (in_port && in_port->on_notify_cbk) |
| in_port->on_notify_cbk(in_port->notify_cbk_data); |
| } |
| |
| return 0; |
| } |
| |
| #define THROTTLE_TIME_US 3000 |
| static void *mixer_32b_threadloop(void *data) |
| { |
| struct amlAudioMixer *audio_mixer = data; |
| int ret = 0; |
| struct aml_audio_device *adev = (struct aml_audio_device *)adev_get_handle(); |
| |
| AM_LOGI("++start"); |
| |
| audio_mixer->exit_thread = 0; |
| prctl(PR_SET_NAME, "amlAudioMixer32"); |
| aml_audio_set_cpu23_affinity(); |
| while (!audio_mixer->exit_thread) { |
| //if (adev->low_power) { |
| // ALOGI("%s(), low_power mode, wait forever (line %d)", __func__, __LINE__); |
| // pthread_cond_wait(&adev->wake_cond, &adev->wake_lock); |
| // ALOGI("%s(), system resume, wakeup (line %d)", __func__, __LINE__); |
| //} |
| |
| //pthread_mutex_lock(&audio_mixer->lock); |
| //mixer_procs_msg_queue(audio_mixer); |
| // processing throttle |
| struct timespec tval_new; |
| clock_gettime(CLOCK_MONOTONIC, &tval_new); |
| const uint32_t delta_us = tspec_diff_to_us(audio_mixer->tval_last_write, tval_new); |
| ret = mixer_inports_read(audio_mixer); |
| if (ret < 0) { |
| //usleep(5000); |
| AM_LOGV("data not enough, next turn"); |
| notify_mixer_input_avail(audio_mixer); |
| continue; |
| //notify_mixer_input_avail(audio_mixer); |
| //continue; |
| } |
| notify_mixer_input_avail(audio_mixer); |
| AM_LOGV("do mixing"); |
| mixer_do_mixing_32bit(audio_mixer); |
| uint64_t tpast_us = 0; |
| clock_gettime(CLOCK_MONOTONIC, &tval_new); |
| tpast_us = tspec_diff_to_us(audio_mixer->tval_last_write, tval_new); |
| // audio patching should not in this write |
| // TODO: fix me, make compatible with source output |
| if (!is_dev_patch_exist(audio_mixer->adev)) { |
| mixer_output_write(audio_mixer); |
| mixer_update_tstamp(audio_mixer); |
| } |
| } |
| |
| AM_LOGI("--"); |
| return NULL; |
| } |
| |
| uint32_t get_mixer_inport_count(struct amlAudioMixer *audio_mixer) |
| { |
| return __builtin_popcount(audio_mixer->inportsMasks); |
| } |
| |
| static bool is_submix_disable(struct amlAudioMixer *audio_mixer) { |
| struct aml_audio_device *adev = audio_mixer->adev; |
| |
| if (is_dev_patch_exist(adev)) { |
| return false; |
| } else if (is_bypass_submix_active(adev)) { |
| return true; |
| } |
| return false; |
| } |
| |
| static void *mixer_16b_threadloop(void *data) |
| { |
| struct amlAudioMixer *audio_mixer = data; |
| struct audio_virtual_buf *pstVirtualBuffer = NULL; |
| struct aml_audio_device *adev = (struct aml_audio_device *)adev_get_handle(); |
| |
| AM_LOGI("begin create thread"); |
| if (audio_mixer->mixing_enable == 0) { |
| pthread_exit(0); |
| AM_LOGI("mixing_enable is 0 exit thread"); |
| return NULL; |
| } |
| audio_mixer->exit_thread = 0; |
| prctl(PR_SET_NAME, "amlAudioMixer16"); |
| aml_audio_set_cpu23_affinity(); |
| aml_set_thread_priority("amlAudioMixer16", audio_mixer->out_mixer_tid, 5); |
| while (!audio_mixer->exit_thread) { |
| if (pstVirtualBuffer == NULL) { |
| audio_virtual_buf_open((void **)&pstVirtualBuffer, "mixer_16bit_thread", |
| MIXER_WRITE_PERIOD_TIME_NANO * 4, MIXER_WRITE_PERIOD_TIME_NANO * 4, 0); |
| audio_virtual_buf_process((void *)pstVirtualBuffer, MIXER_WRITE_PERIOD_TIME_NANO * 4); |
| } |
| //if (adev->low_power) { |
| // ALOGI("%s(), low_power mode, wait forever (line %d)", __func__, __LINE__); |
| // pthread_cond_wait(&adev->wake_cond, &adev->wake_lock); |
| // ALOGI("%s(), system resume, wakeup (line %d)", __func__, __LINE__); |
| //} |
| |
| pthread_mutex_lock(&audio_mixer->lock); |
| mixer_inports_read(audio_mixer); |
| pthread_mutex_unlock(&audio_mixer->lock); |
| |
| audio_virtual_buf_process((void *)pstVirtualBuffer, MIXER_WRITE_PERIOD_TIME_NANO); |
| pthread_mutex_lock(&audio_mixer->lock); |
| notify_mixer_input_avail(audio_mixer); |
| mixer_do_mixing_16bit(audio_mixer); |
| pthread_mutex_unlock(&audio_mixer->lock); |
| |
| if (!is_submix_disable(audio_mixer)) { |
| pthread_mutex_lock(&audio_mixer->lock); |
| mixer_output_write(audio_mixer); |
| mixer_update_tstamp(audio_mixer); |
| pthread_mutex_unlock(&audio_mixer->lock); |
| } |
| } |
| if (pstVirtualBuffer != NULL) { |
| audio_virtual_buf_close((void **)&pstVirtualBuffer); |
| } |
| |
| AM_LOGI("exit thread"); |
| return NULL; |
| } |
| |
| uint32_t mixer_get_inport_latency_frames(struct amlAudioMixer *audio_mixer, int port_index) |
| { |
| if (-1 == port_index) { |
| AM_LOGE("-1 == port_index err, need check!!"); |
| return 0; |
| } |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| if (in_port->first_read) { |
| return in_port->inport_start_threshold / in_port->cfg.frame_size; |
| } |
| return in_port->get_latency_frames(in_port); |
| } |
| |
| uint32_t mixer_get_outport_latency_frames(struct amlAudioMixer *audio_mixer) |
| { |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| output_port *out_port = NULL; |
| R_CHECK_PARAM_LEGAL(-1, port_index, MIXER_OUTPUT_PORT_STEREO_PCM, MIXER_OUTPUT_PORT_NUM - 1, ""); |
| out_port = audio_mixer->out_ports[port_index]; |
| if (out_port == NULL) { |
| AM_LOGW("out_port is null"); |
| return -1; |
| } |
| uint32_t ret = outport_get_latency_frames(out_port); |
| return ret; |
| } |
| |
| int pcm_mixer_thread_run(struct amlAudioMixer *audio_mixer) |
| { |
| int ret = 0; |
| audio_format_t format = AUDIO_FORMAT_PCM; |
| AM_LOGI("++"); |
| R_CHECK_POINTER_LEGAL(-EINVAL, audio_mixer, ""); |
| output_port *out_port = NULL; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (out_port) { |
| format = out_port->cfg.format; |
| release_cur_outport_lock( audio_mixer, port_index); |
| } |
| |
| if (audio_mixer->out_mixer_tid > 0) { |
| AM_LOGE("out mixer thread already running"); |
| return -EINVAL; |
| } |
| audio_mixer->mixing_enable = 1; |
| switch (format) { |
| case AUDIO_FORMAT_PCM_32_BIT: |
| //ret = pthread_create(&audio_mixer->out_mixer_tid, NULL, mixer_32b_threadloop, audio_mixer); |
| ALOGI("%s(), whatever 32bit output, mixing 16bit for 32 is for TV alsa output", __func__); |
| break; |
| case AUDIO_FORMAT_PCM_16_BIT: |
| ret = pthread_create(&audio_mixer->out_mixer_tid, NULL, mixer_16b_threadloop, audio_mixer); |
| break; |
| default: |
| AM_LOGE("format not supported"); |
| break; |
| } |
| if (ret < 0) { |
| AM_LOGE("thread run failed."); |
| } |
| AM_LOGI("++mixing_enable:%d, format:%#x", audio_mixer->mixing_enable, format); |
| |
| return ret; |
| } |
| |
| int pcm_mixer_thread_exit(struct amlAudioMixer *audio_mixer) |
| { |
| audio_mixer->mixing_enable = 0; |
| AM_LOGI("++ audio_mixer->mixing_enable %d", audio_mixer->mixing_enable); |
| // block exit |
| audio_mixer->exit_thread = 1; |
| pthread_join(audio_mixer->out_mixer_tid, NULL); |
| audio_mixer->out_mixer_tid = 0; |
| |
| notify_mixer_exit(audio_mixer); |
| return 0; |
| } |
| |
| struct amlAudioMixer *newAmlAudioMixer(struct aml_audio_device *adev, struct audioCfg cfg) |
| { |
| struct amlAudioMixer *audio_mixer = NULL; |
| int ret = 0; |
| AM_LOGD(""); |
| |
| audio_mixer = aml_audio_calloc(1, sizeof(*audio_mixer)); |
| R_CHECK_POINTER_LEGAL(NULL, audio_mixer, "allocate amlAudioMixer:%zu no memory", sizeof(struct amlAudioMixer)); |
| audio_mixer->adev = adev; |
| audio_mixer->submix_standby = 1; |
| audio_mixer->mc_out_enable = false; |
| mixer_set_state(audio_mixer, MIXER_IDLE); |
| |
| for (int i = 0; i < MIXER_OUTPUT_PORT_NUM; i++) { |
| pthread_mutex_init(&audio_mixer->outport_locks[i], NULL); |
| } |
| |
| ret = init_mixer_output_port(audio_mixer, MIXER_OUTPUT_PORT_STEREO_PCM, &cfg, MIXER_FRAME_COUNT); |
| if (ret < 0) { |
| AM_LOGE("init mixer out port failed"); |
| goto err_state; |
| } |
| init_mixer_temp_buffer(audio_mixer); |
| init_aml_pcm_downmix(&audio_mixer->pcm_downmix); |
| audio_mixer->inportsMasks = 0; |
| audio_mixer->inportsAvailMasks = (1 << NR_INPORTS) - 1; |
| pthread_mutex_init(&audio_mixer->lock, NULL); |
| pthread_mutex_init(&audio_mixer->inport_lock, NULL); |
| |
| return audio_mixer; |
| |
| err_state: |
| deinit_mixer_temp_buffer(audio_mixer); |
| err_tmp: |
| aml_audio_free(audio_mixer); |
| return NULL; |
| } |
| |
| void freeAmlAudioMixer(struct amlAudioMixer *audio_mixer) |
| { |
| R_CHECK_POINTER_LEGAL((void)0, audio_mixer, ""); |
| pthread_mutex_destroy(&audio_mixer->lock); |
| pthread_mutex_destroy(&audio_mixer->inport_lock); |
| if (audio_mixer->cur_output_port_type == MIXER_OUTPUT_PORT_STEREO_PCM || |
| audio_mixer->cur_output_port_type == MIXER_OUTPUT_PORT_MULTI_PCM) { |
| delete_mixer_output_port(audio_mixer, audio_mixer->cur_output_port_type); |
| } |
| for (int i = 0; i < MIXER_OUTPUT_PORT_NUM; i++) { |
| pthread_mutex_destroy(&audio_mixer->outport_locks[i]); |
| } |
| deinit_mixer_temp_buffer(audio_mixer); |
| deinit_aml_pcm_downmix(&audio_mixer->pcm_downmix); |
| aml_audio_free(audio_mixer); |
| } |
| |
| int mixer_get_presentation_position( |
| struct amlAudioMixer *audio_mixer, |
| int port_index, |
| uint64_t *frames, |
| struct timespec *timestamp) |
| { |
| int ret = 0; |
| R_CHECK_PARAM_LEGAL(-1, port_index, 0, NR_INPORTS - 1, ""); |
| pthread_mutex_lock(&audio_mixer->inport_lock); |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| if (in_port == NULL) { |
| AM_LOGE("in_port is null pointer, port_index:%d", port_index); |
| pthread_mutex_unlock(&audio_mixer->inport_lock); |
| return -EINVAL; |
| } |
| *frames = in_port->presentation_frames; |
| *timestamp = in_port->timestamp; |
| if (!is_inport_pts_valid(in_port)) { |
| AM_LOGW("not valid now"); |
| ret = -EINVAL; |
| } |
| pthread_mutex_unlock(&audio_mixer->inport_lock); |
| return ret; |
| } |
| |
| int mixer_set_padding_size( |
| struct amlAudioMixer *audio_mixer, |
| uint8_t port_index, |
| int padding_bytes) |
| { |
| input_port *in_port = audio_mixer->in_ports[port_index]; |
| R_CHECK_POINTER_LEGAL(-EINVAL, in_port, "port_index:%d", port_index); |
| return set_inport_padding_size(in_port, padding_bytes); |
| } |
| |
| int mixer_outport_pcm_restart(struct amlAudioMixer *audio_mixer) |
| { |
| output_port *out_port = NULL; |
| MIXER_OUTPUT_PORT port_index = audio_mixer->cur_output_port_type; |
| out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index); |
| if (out_port) { |
| outport_pcm_restart(out_port); |
| release_cur_outport_lock(audio_mixer, port_index); |
| } |
| return 0; |
| } |
| |
| bool has_hwsync_stream_running(struct audio_stream_out *stream) |
| { |
| struct aml_stream_out *aml_out = (struct aml_stream_out *)stream; |
| struct aml_audio_device *adev = aml_out->dev; |
| struct amlAudioMixer *audio_mixer = adev->audio_mixer; |
| if (audio_mixer == NULL) |
| return false; |
| |
| unsigned int masks = audio_mixer->inportsMasks; |
| |
| while (masks) { |
| input_port *in_port = mixer_get_inport_by_mask_right_first(audio_mixer, &masks); |
| if (NULL != in_port && in_port->enInPortType == AML_MIXER_INPUT_PORT_PCM_DIRECT |
| && in_port->notify_cbk_data) { |
| struct aml_stream_out *out = (struct aml_stream_out *)in_port->notify_cbk_data; |
| if ((out != aml_out) && out->need_sync && !out->standby) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void mixer_dump(const struct aml_audio_device *pstAmlDev, int s32Fd) |
| { |
| if (NULL == pstAmlDev || NULL == pstAmlDev->audio_mixer) { |
| dprintf(s32Fd, "[AML_HAL] [%s:%d] device or sub mixing is NULL !\n", __func__, __LINE__); |
| return; |
| } |
| struct amlAudioMixer *pstAudioMixer = (struct amlAudioMixer *)pstAmlDev->audio_mixer; |
| if (NULL == pstAudioMixer) { |
| dprintf(s32Fd, "[AML_HAL] [%s:%d] struct amlAudioMixer is NULL !\n", __func__, __LINE__); |
| return; |
| } |
| dprintf(s32Fd, "[AML_HAL]---------------Submix input port description cnt: [%d](masks:%#x)---------\n", |
| get_mixer_inport_count(pstAudioMixer), pstAudioMixer->inportsMasks); |
| for (uint8_t index=0; index < NR_INPORTS; index++) { |
| input_port *pstInputPort = pstAudioMixer->in_ports[index]; |
| if (pstInputPort) { |
| dprintf(s32Fd, "[AML_HAL] input port type: %s(ID:%d)\n", mixerInputType2Str(pstInputPort->enInPortType), pstInputPort->ID); |
| dprintf(s32Fd, "[AML_HAL] Status : %d\n", |
| pstInputPort->port_status); |
| dprintf(s32Fd, "[AML_HAL] Channel : %10d | Format : %#10x\n", |
| pstInputPort->cfg.channelCnt, pstInputPort->cfg.format); |
| dprintf(s32Fd, "[AML_HAL] FrameCnt : %zu | data size : %zu Byte\n", |
| pstInputPort->data_buf_frame_cnt, pstInputPort->data_len_bytes); |
| dprintf(s32Fd, "[AML_HAL] rbuf size : %10d Byte| Avail size : %10d Byte\n", |
| pstInputPort->r_buf->size, get_buffer_read_space(pstInputPort->r_buf)); |
| dprintf(s32Fd, "[AML_HAL] is_hwsync : %10d | start_threshold : %10d Byte\n", |
| pstInputPort->is_hwsync, pstInputPort->inport_start_threshold); |
| } |
| } |
| dprintf(s32Fd, "[AML_HAL]---------------------Submix output port description----------------------\n"); |
| output_port *pstOutPort = NULL; |
| MIXER_OUTPUT_PORT port_index = pstAudioMixer->cur_output_port_type; |
| pstOutPort = mixer_get_cur_outport_with_lock(pstAudioMixer, port_index); |
| if (pstOutPort) { |
| dprintf(s32Fd, "[AML_HAL] output port type: %s\n", mixerOutputType2Str(pstOutPort->enOutPortType)); |
| dprintf(s32Fd, "[AML_HAL] Channel : %10d | Format : %#10x\n", pstOutPort->cfg.channelCnt, pstOutPort->cfg.format); |
| dprintf(s32Fd, "[AML_HAL] FrameCnt : %zu | data size : %zu Byte\n", |
| pstOutPort->data_buf_frame_cnt, pstOutPort->data_buf_len); |
| release_cur_outport_lock(pstAudioMixer, port_index); |
| } else { |
| dprintf(s32Fd, "[AML_HAL] not find output port description!!!\n"); |
| } |
| } |
| |
| int on_notify_cbk(void *data) |
| { |
| struct aml_stream_out *out = data; |
| pthread_cond_broadcast(&out->cond); |
| return 0; |
| } |
| |
| int on_input_avail_cbk(void *data) |
| { |
| struct aml_stream_out *out = data; |
| pthread_cond_broadcast(&out->cond); |
| return 0; |
| } |
| |
| static ssize_t aml_out_write_to_mixer(struct audio_stream_out *stream, const void* buffer, |
| size_t bytes) |
| { |
| struct aml_stream_out *out = (struct aml_stream_out *)stream; |
| struct aml_audio_device *adev = out->dev; |
| struct amlAudioMixer *audio_mixer = adev->audio_mixer; |
| const char *data = (char *)buffer; |
| size_t written_total = 0, frame_size = 4; |
| uint32_t latency_frames = 0; |
| struct timespec ts; |
| |
| if (adev->is_netflix && STREAM_AUX_INPUT == out->stream_type) { |
| aml_audio_data_handle(stream, buffer, bytes); |
| } |
| |
| if (out->stream_type != STREAM_MAIN_INPUT) { |
| int channel = 2; |
| switch (out->audioCfg.channel_mask) { |
| case AUDIO_CHANNEL_OUT_MONO: |
| channel = 1; |
| break; |
| case AUDIO_CHANNEL_OUT_STEREO: |
| channel = 2; |
| break; |
| case AUDIO_CHANNEL_OUT_5POINT1: |
| channel = 6; |
| break; |
| case AUDIO_CHANNEL_OUT_7POINT1: |
| channel = 8; |
| break; |
| default: |
| channel = 2; |
| break; |
| } |
| if (48000 != out->audioCfg.sample_rate) { |
| int ret = aml_audio_resample_process_wrapper(&out->resample_handle, (char *)buffer, bytes, out->audioCfg.sample_rate, channel); |
| if (0 != ret) { |
| ALOGE("aml_audio_resample_process_wrapper failed"); |
| } else { |
| data = out->resample_handle->resample_buffer; |
| bytes = out->resample_handle->resample_size; |
| } |
| } |
| out->config.rate = 48000; |
| } |
| |
| do { |
| ssize_t written = 0; |
| AM_LOGV("stream streamtype: %s, written_total %zu, bytes %zu", |
| streamtype2Str(out->stream_type), written_total, bytes); |
| |
| written = mixer_write_inport(audio_mixer, |
| out->inputPortID, data, bytes - written_total); |
| if (written < 0) { |
| AM_LOGE("write failed, errno = %zu", written); |
| return written; |
| } |
| |
| if (written > 0) { |
| written_total += written; |
| data += written; |
| //latency_frames = mixer_get_inport_latency_frames(audio_mixer, out->port_index) + |
| // mixer_get_outport_latency_frames(audio_mixer); |
| //pthread_mutex_lock(&out->lock); |
| //clock_gettime(CLOCK_MONOTONIC, &out->timestamp); |
| //out->last_frames_postion += written / frame_size - latency_frames; |
| //pthread_mutex_unlock(&out->lock); |
| } |
| AM_LOGV("port index(%d) written(%zu), written_total(%zu), bytes(%zu)", |
| out->inputPortID, written, written_total, bytes); |
| |
| if (written_total >= bytes) { |
| AM_LOGV("exit"); |
| break; |
| } |
| |
| //usleep((bytes- written_total) * 1000 / 5 / 48); |
| //if (out->port_index == 1) { |
| ts_wait_time_us(&ts, 5000); |
| AM_LOGV("-wait...."); |
| bool wakeup = false; |
| pthread_mutex_lock(&out->cond_lock); |
| while (!wakeup) { |
| pthread_cond_timedwait(&out->cond, &out->cond_lock, &ts); |
| wakeup = true; |
| } |
| AM_LOGV("--wait wakeup"); |
| pthread_mutex_unlock(&out->cond_lock); |
| //} |
| } while (1); |
| |
| return written_total; |
| } |
| |
| ssize_t out_write_direct_pcm(struct audio_stream_out *stream, const void *buffer, |
| size_t bytes) |
| { |
| struct aml_stream_out *out = (struct aml_stream_out *)stream; |
| struct aml_audio_device *adev = out->dev; |
| struct amlAudioMixer *audio_mixer = adev->audio_mixer; |
| struct timespec tval, new_tval; |
| uint64_t us_since_last_write = 0; |
| //uint64_t begin_time, end_time; |
| ssize_t written = 0; |
| ssize_t remain = 0; |
| int frame_size = 4; |
| int64_t throttle_timeus = 0;//aml_audio_get_throttle_timeus(bytes); |
| |
| clock_gettime(CLOCK_MONOTONIC, &tval); |
| //begin_time = get_systime_ns(); |
| R_CHECK_PARAM_LEGAL(-1, out->inputPortID, 0, NR_INPORTS - 1, ""); |
| set_mixer_inport_volume(audio_mixer, out->inputPortID, out->volume_l); |
| out->last_volume_l = out->volume_l; |
| out->last_volume_r = out->volume_r; |
| written = aml_out_write_to_mixer(stream, buffer, bytes); |
| if (written >= 0) { |
| remain = bytes - written; |
| out->frame_write_sum += written / frame_size; |
| if (remain > 0) { |
| AM_LOGE("INVALID partial written"); |
| } |
| clock_gettime(CLOCK_MONOTONIC, &new_tval); |
| if (tval.tv_sec > new_tval.tv_sec) |
| AM_LOGE("FATAL ERROR"); |
| |
| //AM_LOGD(" %lld us, %lld", new_tval.tv_sec, tval.tv_sec); |
| |
| us_since_last_write = (new_tval.tv_sec - out->timestamp.tv_sec) * 1000000ull + |
| (new_tval.tv_nsec - out->timestamp.tv_nsec) / 1000; |
| //out->timestamp = new_tval; |
| |
| int used_this_write = (new_tval.tv_sec - tval.tv_sec) * 1000000 + |
| (new_tval.tv_nsec - tval.tv_nsec) / 1000; |
| int target_us = bytes * 1000 / frame_size / 48; |
| AM_LOGI_IF(adev->debug_flag, "++bytes %zu, written %zu, out->port_index %d(out %p)used_this_write %d us", bytes, written, out->inputPortID, out, used_this_write); |
| |
| AM_LOGV("time spent on write %" PRId64 " us, written %zd", us_since_last_write, written); |
| AM_LOGV("used_this_write %d us, target %d us", used_this_write, target_us); |
| throttle_timeus = target_us - us_since_last_write; |
| if (throttle_timeus > 0 && throttle_timeus < 200000) { |
| AM_LOGV("throttle time %" PRId64 " us", throttle_timeus); |
| if (throttle_timeus > 1800) { |
| AM_LOGV("actual throttle %" PRId64 " us, since last %" PRId64 " us", |
| throttle_timeus, us_since_last_write); |
| } else { |
| AM_LOGV("%" PRId64 " us, but un-throttle", throttle_timeus); |
| } |
| } else if (throttle_timeus != 0) { |
| // first time write, sleep |
| //usleep(target_us - 100); |
| AM_LOGV("invalid throttle time %" PRId64 " us, us since last %" PRId64 " us", throttle_timeus, us_since_last_write); |
| AM_LOGV("\n\n"); |
| } |
| } else { |
| AM_LOGE("write fail, err = %zd", written); |
| } |
| |
| // TODO: means first write, need check this by method |
| if (us_since_last_write > 500000) { |
| //usleep(bytes * 1000 / 48 / frame_size); |
| AM_LOGV("invalid duration %" PRIu64 " us", us_since_last_write); |
| //AM_LOGE("last write %ld s, %ld ms", out->timestamp.tv_sec, out->timestamp.tv_nsec/1000000); |
| //AM_LOGE("before write %ld s, %ld ms", tval.tv_sec, tval.tv_nsec/1000000); |
| //AM_LOGE("after write %ld s, %ld ms", new_tval.tv_sec, new_tval.tv_nsec/1000000); |
| } |
| |
| exit: |
| // update new timestamp |
| clock_gettime(CLOCK_MONOTONIC, &out->timestamp); |
| out->lasttimestamp.tv_sec = out->timestamp.tv_sec; |
| out->lasttimestamp.tv_nsec = out->timestamp.tv_nsec; |
| if (written >= 0) { |
| uint32_t latency_frames = mixer_get_inport_latency_frames(audio_mixer, out->inputPortID); |
| //+ mixer_get_outport_latency_frames(audio_mixer); |
| if (out->frame_write_sum > latency_frames) |
| out->last_frames_position = out->frame_write_sum - latency_frames; |
| else |
| out->last_frames_position = out->frame_write_sum; |
| |
| if (0) { |
| AM_LOGI("last position %" PRId64 ", latency_frames %d", out->last_frames_position, latency_frames); |
| } |
| } |
| |
| return written; |
| } |
| |
| static int startMixingThread(struct amlAudioMixer *audio_mixer) |
| { |
| return pcm_mixer_thread_run(audio_mixer); |
| } |
| |
| |
| static int initSubMixingOutput( |
| enum MIXER_TYPE type, |
| struct aml_audio_device *adev) |
| { |
| R_CHECK_POINTER_LEGAL(-EINVAL, adev, ""); |
| if (type == MIXER_LPCM) { |
| struct audioCfg cfg; |
| output_get_default_config(&cfg); |
| struct amlAudioMixer *amixer = newAmlAudioMixer(adev, cfg); |
| R_CHECK_POINTER_LEGAL(-ENOMEM, amixer, "newAmlAudioMixer failed"); |
| adev->audio_mixer = amixer; |
| |
| startMixingThread(adev->audio_mixer); |
| } else if (type == MIXER_MS12) { |
| //TODO |
| AM_LOGW("not support yet, in TODO list"); |
| } else { |
| AM_LOGE("not support"); |
| return -EINVAL; |
| } |
| return 0; |
| }; |
| |
| int initHalSubMixing(enum MIXER_TYPE type, |
| struct aml_audio_device *adev, |
| bool isTV) |
| { |
| int ret = 0; |
| ALOGI("type %d, isTV %d", type, isTV); |
| ret = initSubMixingOutput(type, adev); |
| if (ret < 0) { |
| AM_LOGE("fail to init mixer"); |
| goto err1; |
| } |
| return 0; |
| err1: |
| return ret; |
| } |
| |
| |
| static int exitMixingThread(struct amlAudioMixer *audio_mixer) |
| { |
| return pcm_mixer_thread_exit(audio_mixer); |
| } |
| |
| static int releaseSubMixingOutput(struct amlAudioMixer *audio_mixer) |
| { |
| R_CHECK_POINTER_LEGAL(-EINVAL, audio_mixer, ""); |
| AM_LOGI("++"); |
| exitMixingThread(audio_mixer); |
| freeAmlAudioMixer(audio_mixer); |
| audio_mixer = NULL; |
| return 0; |
| } |
| |
| |
| int deleteHalSubMixing(struct aml_audio_device *adev) |
| { |
| releaseSubMixingOutput(adev->audio_mixer); |
| return 0; |
| } |
| |
| static int subMixingOutMsg(struct aml_audio_device *adev, PORT_MSG msg, void *info, int info_len) |
| { |
| struct amlAudioMixer *audio_mixer = NULL; |
| int ret = 0; |
| |
| audio_mixer = adev->audio_mixer; |
| send_mixer_outport_message(audio_mixer, MIXER_OUTPUT_PORT_STEREO_PCM, msg, info, info_len); |
| |
| return 0; |
| } |
| |
| int subMixingOutputRestart(struct aml_audio_device *adev) |
| { |
| struct amlAudioMixer *audio_mixer = adev->audio_mixer; |
| return mixer_outport_pcm_restart(audio_mixer); |
| } |
| |