Files
2026-08-24 10:03:24 +08:00

1612 lines
60 KiB
C

/*
* 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 "aml_audio_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>
#include <unistd.h>
#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 "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"
#include "aml_audio_limiter.h"
#define DUMP_AUDIOMIXER_INPORT_READ 0x1
#define DUMP_AUDIOMIXER_OUTDUMP 0x2
#define AML_LIMITER_RELEASE_TIME (100)
static int mixer_update_limiter_gain(struct amlAudioMixer *audio_mixer);
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
output_port *out_ports[MIXER_OUTPUT_PORT_NUM];
pthread_mutex_t outport_locks[MIXER_OUTPUT_PORT_NUM];
pthread_mutex_t inport_lock;
pthread_t out_mixer_tid;
pthread_mutex_t lock;
int exit_thread;
int mixing_enable;
aml_mixer_state state;
struct aml_audio_device *adev;
bool continuous_output;
int submix_standby; //current status
bool enable_standby;//This variable represents whether it is possible to enter standby mode
/*multich pcm output*/
bool mc_out_enable;
port_state mc_out_status;
aml_pcm_mixing_st multich_mixer;
aml_pcm_downmix_st pcm_downmix;
void *mixer_limiter;
float pre_gain, last_pre_gain;
/*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;
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,
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);
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_POINTER_LEGAL(-EINVAL, audio_mixer, "");
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);
}
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)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
R_CHECK_PARAM_LEGAL(-1, 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);
if (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 set_mixer_inport_mute(struct amlAudioMixer *audio_mixer, uint8_t port_index, bool mute)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
R_CHECK_PARAM_LEGAL(-1, 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);
if (in_port->port_mute != mute) {
set_inport_mute(in_port, mute);
}
return 0;
}
int mixer_write_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index, const void *buffer, int bytes)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
R_CHECK_PARAM_LEGAL(-1, port_index, 0, NR_INPORTS - 1, "");
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)
{
R_CHECK_POINTER_LEGAL(0, audio_mixer, "");
R_CHECK_PARAM_LEGAL(0, 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);
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);
}
static inline output_port *mixer_get_cur_outport_with_lock(struct amlAudioMixer *audio_mixer, MIXER_OUTPUT_PORT port_index)
{
output_port *out_port = NULL;
R_CHECK_PARAM_LEGAL(NULL, port_index, MIXER_OUTPUT_PORT_STEREO_PCM, MIXER_OUTPUT_PORT_NUM - 1, "");
R_CHECK_POINTER_LEGAL(NULL, audio_mixer, "");
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 release_cur_outport_lock(struct amlAudioMixer *audio_mixer, MIXER_OUTPUT_PORT port_index)
{
R_CHECK_POINTER_LEGAL((void)0, audio_mixer, "");
R_CHECK_PARAM_LEGAL((void)0, port_index, MIXER_OUTPUT_PORT_STEREO_PCM, MIXER_OUTPUT_PORT_NUM - 1, "");
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)
{
R_CHECK_POINTER_LEGAL(0, outport, "");
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->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);
}
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;
AM_LOGI("output port:%s", mixerOutputType2Str(port_index));
pthread_mutex_lock(&audio_mixer->outport_locks[port_index]);
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 = MIXER_OUTPUT_PORT_STEREO_PCM;
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__);
(void)audio_mixer;
//audio_mixer->run_state = AML_AUDIO_MIXER_RUN_STATE_REQ_SLEEP;
return 0;
}
int mixer_enable_standby(struct amlAudioMixer *audio_mixer, bool enable_standby)
{
if (audio_mixer) {
audio_mixer->enable_standby = enable_standby;
}
return 0;
}
static int mixer_thread_sleep(struct amlAudioMixer *audio_mixer)
{
output_port *out_port = NULL;
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_STEREO_PCM;
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 = MIXER_OUTPUT_PORT_STEREO_PCM;
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)
{
output_port *out_port = NULL;
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_STEREO_PCM;
out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index);
/* stereo pcm output */
if (out_port) {
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;
}
#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;
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_STEREO_PCM;
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;
}
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)
{
in_port->mix_consumed_frames += in_port->data_buf_frame_cnt;
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_pause_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
R_CHECK_PARAM_LEGAL(-1, 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);
return send_inport_message(in_port, MSG_PAUSE);
}
int mixer_resume_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
R_CHECK_PARAM_LEGAL(-1, 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);
return send_inport_message(in_port, MSG_RESUME);
}
int mixer_flush_inport(struct amlAudioMixer *audio_mixer, uint8_t port_index)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
R_CHECK_PARAM_LEGAL(-1, 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);
return send_inport_message(in_port, MSG_FLUSH);
}
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("+++");
mixer_update_limiter_gain(audio_mixer);
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 resume", mixerInputType2Str(type));
set_inport_state(in_port, ACTIVE);
} else if (state == FLUSHING) {
reset_input_port(in_port);
AM_LOGI("input port:%s flushing->flushed", mixerInputType2Str(type));
set_inport_state(in_port, FLUSHED);
continue;
} else if (state == STOPPED || state == PAUSED || state == FLUSHED) {
AM_LOGV("input port:%s stopped, paused or flushed", mixerInputType2Str(type));
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, audio_bytes_per_sample(in_port->cfg.format), in_port->cfg.channelCnt, in_port->data_len_bytes);
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_INPORT_READ)) {
char file_name[128] = { 0 };
snprintf(file_name, 128, "%s_%d.pcm", "inport_read_before_mute_process", in_port->ID);
aml_dump_audio_bitstreams(file_name, in_port->data, in_port->data_len_bytes);
}
aml_audio_mute_process(&in_port->mute_process_handle, in_port->data, in_port->data_len_bytes);
in_port->last_volume = in_port->volume;
if (fade_out) {
AM_LOGI("output port:%s fade out, pausing->paused", mixerInputType2Str(type));
audio_fade_func(in_port->data, ret, 0);
set_inport_state(in_port, PAUSED);
} 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 int mixer_add_mixing_data(struct amlAudioMixer *audio_mixer, void *input, input_port *in_port)
{
aml_pcm_mixing_st *p_multich_mixer = &audio_mixer->multich_mixer;
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;
}
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, in_port->cfg.channelCnt);
return 0;
}
static int init_multich_mixer(struct amlAudioMixer *audio_mixer, struct audioCfg *p_mixer_cfg)
{
int ret = 0;
struct audioCfg mixer_cfg;
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
R_CHECK_POINTER_LEGAL(-1, p_mixer_cfg, "");
memcpy(&mixer_cfg, p_mixer_cfg, sizeof(*p_mixer_cfg));
mixer_cfg.format = p_mixer_cfg->format;
mixer_cfg.channelCnt = 8;
mixer_cfg.channelMask = AUDIO_CHANNEL_OUT_7POINT1;
mixer_cfg.frame_size = mixer_cfg.channelCnt * audio_bytes_per_sample(mixer_cfg.format);
AM_LOGI("[Submix]init_multich_mixer---format:%x ch:%d frame_size:%d", mixer_cfg.format, mixer_cfg.channelCnt, mixer_cfg.frame_size);
ret = init_aml_pcm_mixer(&audio_mixer->multich_mixer, &mixer_cfg, MIXER_FRAME_COUNT);
if (ret < 0) {
deinit_aml_pcm_mixer(&audio_mixer->multich_mixer);
}
return ret;
}
static int deinit_multich_mixer(struct amlAudioMixer *audio_mixer)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
deinit_aml_pcm_mixer(&audio_mixer->multich_mixer);
return 0;
}
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;
R_CHECK_POINTER_LEGAL((void)0, audio_mixer, "");
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]);
}
static int deinit_mixer_limiter(struct amlAudioMixer *audio_mixer)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
if (audio_mixer->mixer_limiter) {
aml_audio_limiter_free(audio_mixer->mixer_limiter );
audio_mixer->mixer_limiter = NULL;
}
return 0;
}
static int init_mixer_limiter(struct amlAudioMixer *audio_mixer)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
struct aml_audio_device *adev = (struct aml_audio_device *)adev_get_handle();
int ret = 0;
audio_mixer->last_pre_gain = audio_mixer->pre_gain = 1.0f;
audio_format_t format = adev->audio_process_bitwidth == 32 ? AUDIO_FORMAT_PCM_32_BIT : AUDIO_FORMAT_PCM_16_BIT;
audio_mixer->mixer_limiter = aml_audio_limiter_new(format);
if (!audio_mixer->mixer_limiter) {
goto error;
}
ret = aml_audio_limiter_set(audio_mixer->mixer_limiter, audio_mixer->pre_gain, 1, AML_LIMITER_RELEASE_TIME, MIXER_FRAME_COUNT / 2, AML_LIMITER);
if (ret) {
goto error;
}
aml_audio_limiter_enable(audio_mixer->mixer_limiter, 1);
AM_LOGI("Enable mixer_limiter, limiter peak is %f, release time is %d", audio_mixer->pre_gain, AML_LIMITER_RELEASE_TIME);
return 0;
error:
deinit_mixer_limiter(audio_mixer);
return ret;
}
static int mixer_update_limiter_gain(struct amlAudioMixer *audio_mixer)
{
R_CHECK_POINTER_LEGAL(-1, audio_mixer, "");
int ret = 0;
int inport_count = get_mixer_inport_count(audio_mixer);
if (inport_count && fabsf(1.0f / inport_count - audio_mixer->pre_gain) > FLOAT_ZERO) {
audio_mixer->pre_gain = 1.0f / inport_count;
ret = aml_audio_limiter_set(audio_mixer->mixer_limiter, audio_mixer->pre_gain, 1, AML_LIMITER_RELEASE_TIME, MIXER_FRAME_COUNT / 2, AML_LIMITER);
AM_LOGI("update limiter pre_gain: %f", audio_mixer->pre_gain);
}
return ret;
}
static int mixer_loudness_input_control(struct amlAudioMixer *audio_mixer, void *buffer, int bytes, struct audioCfg in_cfg)
{
if (bytes <= 0 || NULL == buffer) {
AM_LOGE("Invalid param: bytes(%d) buffer(%p)", bytes, buffer);
return -1;
}
int channels = in_cfg.channelCnt;
int sample_size = audio_bytes_per_sample(in_cfg.format);
apply_volume_fade(audio_mixer->last_pre_gain, audio_mixer->pre_gain, buffer, sample_size, channels, bytes);
if (audio_mixer->pre_gain != audio_mixer->last_pre_gain) {
AM_LOGI(" pre_gain=%f last_pre_gain=%f channels=%d bytes=%d",
audio_mixer->pre_gain , audio_mixer->last_pre_gain, channels, bytes);
audio_mixer->last_pre_gain = audio_mixer->pre_gain;
}
return 0;
}
static int mixer_loudness_output_control(struct amlAudioMixer *audio_mixer,void *buffer, int bytes, struct audioCfg in_cfg)
{
void *loudness_limiter_handle = audio_mixer->mixer_limiter;
int sample_size = audio_bytes_per_sample(in_cfg.format);
if (!audio_mixer || !buffer || bytes <= 0) {
AM_LOGE("Invalid param: bytes(%d) buffer(%p)", bytes, buffer);
return -1;
}
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) {
char before_dump_name[64] = {0};
snprintf(before_dump_name, 64, "mixed_before_limiter_8ch.pcm");
aml_dump_audio_bitstreams(before_dump_name, buffer, bytes);
}
int process_frames =bytes / sample_size / in_cfg.channelCnt; //MIXER_FRAME_COUNT
aml_audio_limiter_process(loudness_limiter_handle, buffer, in_cfg.format, buffer, in_cfg.format, in_cfg.channelCnt, process_frames);
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) {
char after_dump_name[64] = {0};
snprintf(after_dump_name, 64, "mixed_after_limiter_8ch.pcm");
aml_dump_audio_bitstreams(after_dump_name, buffer, bytes);
}
return 0;
}
/*
* 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)
{
int ret = 0;
uint32_t mixer_max_channels = 8;
struct audioCfg *p_mixer_cfg = &audio_mixer->multich_mixer.cfg;
const MIXER_OUTPUT_PORT 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 (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]);
return ret;
}
static int mixer_do_mixing(struct amlAudioMixer *audio_mixer)
{
input_port *in_port = NULL;
output_port *out_port = NULL;
char acFilePathStr[ENUM_TYPE_STR_MAX_LEN] = {0};
uint32_t masks = 0;
aml_pcm_mixing_st *p_multich_mixer = &audio_mixer->multich_mixer;
aml_pcm_downmix_st *p_downmix = &audio_mixer->pcm_downmix;
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_STEREO_PCM;
struct aml_audio_device* adev = audio_mixer->adev;
audio_channel_mask_t mixed_data_valid_channelMask = AUDIO_CHANNEL_OUT_STEREO;
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;
}
release_cur_outport_lock(audio_mixer, port_index);
mixer_config_multich_output(audio_mixer);
if (p_multich_mixer->mixed_buf) {
memset(p_multich_mixer->mixed_buf, 0, p_multich_mixer->mixed_buf_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;
}
mixed_data_valid_channelMask |= in_port->cfg.channelMask;
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);
}
mixer_loudness_input_control(audio_mixer, (void *)in_port->data, in_port->data_len_bytes, in_port->cfg);
mixer_add_mixing_data(audio_mixer, in_port->data, in_port);
}
void *mixed_data_ptr = p_multich_mixer->mixed_buf;
int mixed_data_size = p_multich_mixer->mixed_buf_size;
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) {
snprintf(acFilePathStr, ENUM_TYPE_STR_MAX_LEN, "%s_8ch.pcm", "SubMix_after_mixed");
aml_dump_audio_bitstreams(acFilePathStr, mixed_data_ptr, mixed_data_size);
}
mixer_loudness_output_control(audio_mixer, mixed_data_ptr, mixed_data_size, p_multich_mixer->cfg);
/*AI Audio process*/
if (adev->aml_ai_audio_type != eAmlAiAudioUnsupported) {
int sample_size = audio_bytes_per_sample(p_multich_mixer->cfg.format);
int process_frames = mixed_data_size / sample_size / p_multich_mixer->cfg.channelCnt;
int ret = aml_ai_audio_process(process_frames, p_multich_mixer->cfg.channelCnt, p_multich_mixer->cfg.format, mixed_data_valid_channelMask, mixed_data_ptr);
if (ret != 0) {
AM_LOGE("aml_ai_audio_process failed, ret=%d", ret);
}
}
/* 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];
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", "mc_outport");
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]);
/* stereo pcm data */
pthread_mutex_lock(&audio_mixer->outport_locks[port_index]);
do_downmix_to_2ch(p_downmix, mixed_data_ptr, MIXER_FRAME_COUNT, &p_multich_mixer->cfg);
memcpy(out_port->data_buf, p_downmix->output_buf, p_downmix->output_buf_size);
set_outport_data_avail(out_port, p_downmix->output_buf_size);
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) {
snprintf(acFilePathStr, ENUM_TYPE_STR_MAX_LEN, "%s_2ch.pcm", "stero_outport");
aml_dump_audio_bitstreams(acFilePathStr, out_port->data_buf, out_port->bytes_avail);
}
pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]);
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;
}
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_threadloop(void *data)
{
struct amlAudioMixer *audio_mixer = data;
struct audio_virtual_buf *pstVirtualBuffer = NULL;
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, "amlAudioMixer");
aml_audio_set_cpu23_affinity();
aml_set_thread_priority("amlAudioMixer", audio_mixer->out_mixer_tid, 5);
while (!audio_mixer->exit_thread) {
if (pstVirtualBuffer == NULL) {
audio_virtual_buf_open((void **)&pstVirtualBuffer, "mixer_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);
}
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(audio_mixer);
pthread_mutex_unlock(&audio_mixer->lock);
if (!audio_mixer->inportsMasks && audio_mixer->enable_standby) {// no input, need standby submix and close alsa device
if (!audio_mixer->submix_standby) {
mixer_thread_sleep(audio_mixer);
continue;
} else {
continue;
}
}
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)
{
R_CHECK_POINTER_LEGAL(0, audio_mixer, "");
R_CHECK_PARAM_LEGAL(0, 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);
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)
{
R_CHECK_POINTER_LEGAL(0, audio_mixer, "");
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_STEREO_PCM;
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 = MIXER_OUTPUT_PORT_STEREO_PCM;
R_CHECK_PARAM_LEGAL(-1, port_index, MIXER_OUTPUT_PORT_STEREO_PCM, MIXER_OUTPUT_PORT_NUM - 1, "");
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:
case AUDIO_FORMAT_PCM_16_BIT:
ret = pthread_create(&audio_mixer->out_mixer_tid, NULL, mixer_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->enable_standby = true;
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_multich_mixer(audio_mixer, &cfg);
init_aml_pcm_downmix(&audio_mixer->pcm_downmix);
init_mixer_limiter(audio_mixer);
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:
aml_audio_free(audio_mixer);
return NULL;
}
void freeAmlAudioMixer(struct amlAudioMixer *audio_mixer)
{
R_CHECK_POINTER_LEGAL((void)0, audio_mixer, "");
unsigned int masks = audio_mixer->inportsMasks;
while (masks) {
uint8_t index = get_bit_position_in_mask(NR_INPORTS - 1, &masks);
delete_mixer_input_port(audio_mixer, index);
}
pthread_mutex_destroy(&audio_mixer->lock);
pthread_mutex_destroy(&audio_mixer->inport_lock);
for (int port_index = 0; port_index < MIXER_OUTPUT_PORT_NUM; port_index++) {
delete_mixer_output_port(audio_mixer, port_index);
}
for (int i = 0; i < MIXER_OUTPUT_PORT_NUM; i++) {
pthread_mutex_destroy(&audio_mixer->outport_locks[i]);
}
deinit_multich_mixer(audio_mixer);
deinit_aml_pcm_downmix(&audio_mixer->pcm_downmix);
deinit_mixer_limiter(audio_mixer);
aml_audio_free(audio_mixer);
}
int mixer_outport_pcm_restart(struct amlAudioMixer *audio_mixer)
{
output_port *out_port = NULL;
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_STEREO_PCM;
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;
}
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 submix_standby: [%d]---------\n", pstAudioMixer->submix_standby);
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] start_threshold : %10d Byte\n",
pstInputPort->inport_start_threshold);
}
}
dprintf(s32Fd, "[AML_HAL]---------------------Submix output port description----------------------\n");
output_port *pstOutPort = NULL;
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_STEREO_PCM;
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;
const char *data = (char *)buffer;
size_t written_total = 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);
pthread_mutex_lock(&adev->audio_mixer_lock);
written = mixer_write_inport(adev->audio_mixer,
out->inputPortID, data, bytes - written_total);
pthread_mutex_unlock(&adev->audio_mixer_lock);
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 timespec tval, new_tval;
uint64_t us_since_last_write = 0;
uint64_t pre_last_frames_position = out->last_frames_position;
//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, "");
pthread_mutex_lock(&adev->audio_mixer_lock);
set_mixer_inport_volume(adev->audio_mixer, out->inputPortID, out->volume_l);
set_mixer_inport_mute(adev->audio_mixer, out->inputPortID, out->final_mute);
pthread_mutex_unlock(&adev->audio_mixer_lock);
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);
}
// 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) {
pthread_mutex_lock(&adev->audio_mixer_lock);
uint32_t latency_frames = mixer_get_inport_latency_frames(adev->audio_mixer, out->inputPortID);
pthread_mutex_unlock(&adev->audio_mixer_lock);
//+ 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 = pre_last_frames_position;
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);
return 0;
}
int deleteHalSubMixing(struct aml_audio_device *adev)
{
releaseSubMixingOutput(adev->audio_mixer);
adev->audio_mixer = NULL;
return 0;
}
int subMixingOutputRestart(struct aml_audio_device *adev)
{
struct amlAudioMixer *audio_mixer = adev->audio_mixer;
return mixer_outport_pcm_restart(audio_mixer);
}