Files
BPI-CM9-Linux/aml-comp/multimedia/aml_audio_hal/audio_hal/amlAudioMixer.c
T

1829 lines
68 KiB
C
Raw Normal View History

2026-08-13 11:28:40 +08:00
/*
* 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"
2026-11-11 08:50:43 +08:00
#include "aml_audio_property.h"
2026-08-13 11:28:40 +08:00
#define DUMP_AUDIOMIXER_INPORT_READ 0x1
#define DUMP_AUDIOMIXER_OUTDUMP 0x2
#define AML_LIMITER_RELEASE_TIME (100)
2026-11-11 08:50:43 +08:00
#define MIXER_MIX_CHANNELS_8 (8)
#define MIXER_MIX_CHANNELS_10 (10)
#define MIXER_MIX_CHANNELS_DEFAULT MIXER_MIX_CHANNELS_8
#define MIXER_MIX_CHANNELS_PROP "vendor.media.audio.submix.mix_channels"
2026-08-13 11:28:40 +08:00
static int mixer_update_limiter_gain(struct amlAudioMixer *audio_mixer);
2026-11-11 08:50:43 +08:00
static int mixer_reconfig_effect_pcm_port(struct amlAudioMixer *audio_mixer);
2026-08-13 11:28:40 +08:00
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
2026-11-11 08:50:43 +08:00
/* mix bus 8ch (7.1) or 10ch (7.1.2); derive stereo / MULTI 8ch / EFFECT */
uint32_t mix_channel_cnt;
bool effect_out_enable;
2026-08-13 11:28:40 +08:00
bool mc_out_enable;
port_state mc_out_status;
aml_pcm_mixing_st multich_mixer;
aml_pcm_downmix_st pcm_downmix;
2026-11-11 08:50:43 +08:00
aml_pcm_downmix_st pcm_downmix_8ch;
void *extract_buf;
size_t extract_buf_size;
audio_channel_mask_t valid_channel_mask;
2026-08-13 11:28:40 +08:00
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;
}
2026-11-11 08:50:43 +08:00
if (port_index == MIXER_OUTPUT_PORT_STEREO_PCM) {
free_output_port(out_port);
aml_mixer_ctrl_set_int(&adev->alsa_mixer, AML_MIXER_ID_SPDIF_FORMAT, AML_STEREO_PCM);
} else {
free_buffer_output_port(out_port);
}
2026-08-13 11:28:40 +08:00
audio_mixer->out_ports[port_index] = NULL;
pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]);
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;
}
2026-11-11 08:50:43 +08:00
static int mixer_output_write_port(struct amlAudioMixer *audio_mixer, MIXER_OUTPUT_PORT port_index)
2026-08-13 11:28:40 +08:00
{
2026-11-11 08:50:43 +08:00
output_port *out_port = mixer_get_cur_outport_with_lock(audio_mixer, port_index);
2026-08-13 11:28:40 +08:00
if (out_port) {
2026-11-11 08:50:43 +08:00
if (out_port->bytes_avail > 0 && out_port->write) {
out_port->write(out_port, out_port->data_buf, out_port->bytes_avail);
set_outport_data_avail(out_port, 0);
}
2026-08-13 11:28:40 +08:00
release_cur_outport_lock(audio_mixer, port_index);
}
return 0;
}
static int mixer_output_write(struct amlAudioMixer *audio_mixer)
{
2026-11-11 08:50:43 +08:00
if (audio_mixer->submix_standby) {
mixer_output_startup(audio_mixer);
2026-08-13 11:28:40 +08:00
}
2026-11-11 08:50:43 +08:00
/* EFFECT: TV/SBR speaker */
if (audio_mixer->effect_out_enable) {
mixer_output_write_port(audio_mixer, MIXER_OUTPUT_PORT_EFFECT_PCM);
}
2026-08-13 11:28:40 +08:00
2026-11-11 08:50:43 +08:00
/* STEREO: TV/SBR HP/SPDIF; STB speaker */
mixer_output_write_port(audio_mixer, MIXER_OUTPUT_PORT_STEREO_PCM);
/* MULTI 8ch: eARC/HDMI when sink supports mch PCM */
if (audio_mixer->mc_out_enable) {
mixer_output_write_port(audio_mixer, MIXER_OUTPUT_PORT_MULTI_PCM);
2026-08-13 11:28:40 +08:00
}
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;
}
2026-11-11 08:50:43 +08:00
static uint32_t mixer_normalize_mix_ch(uint32_t ch)
{
return (ch == MIXER_MIX_CHANNELS_10) ? MIXER_MIX_CHANNELS_10 : MIXER_MIX_CHANNELS_8;
}
static audio_channel_mask_t mixer_mix_layout_mask(uint32_t ch)
{
return (ch == MIXER_MIX_CHANNELS_10) ?
AUDIO_CHANNEL_OUT_7POINT1POINT2 : AUDIO_CHANNEL_OUT_7POINT1;
}
2026-08-13 11:28:40 +08:00
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;
2026-11-11 08:50:43 +08:00
mixer_cfg.channelCnt = mixer_normalize_mix_ch(audio_mixer->mix_channel_cnt);
mixer_cfg.channelMask = mixer_mix_layout_mask(mixer_cfg.channelCnt);
2026-08-13 11:28:40 +08:00
mixer_cfg.frame_size = mixer_cfg.channelCnt * audio_bytes_per_sample(mixer_cfg.format);
2026-11-11 08:50:43 +08:00
audio_mixer->mix_channel_cnt = mixer_cfg.channelCnt;
2026-08-13 11:28:40 +08:00
2026-11-11 08:50:43 +08:00
AM_LOGI("[Submix]init_multich_mixer---format:%x ch:%d mask:0x%x frame_size:%d",
mixer_cfg.format, mixer_cfg.channelCnt, mixer_cfg.channelMask, mixer_cfg.frame_size);
2026-08-13 11:28:40 +08:00
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)
{
2026-11-11 08:50:43 +08:00
output_port *port = NULL;
MIXER_OUTPUT_PORT idx = MIXER_OUTPUT_PORT_MULTI_PCM;
2026-08-13 11:28:40 +08:00
R_CHECK_POINTER_LEGAL((void)0, audio_mixer, "");
if (audio_mixer->mc_out_enable == enable) {
return;
}
2026-11-11 08:50:43 +08:00
AM_LOGI("mc_out_enable %d -> %d", audio_mixer->mc_out_enable, enable);
2026-08-13 11:28:40 +08:00
audio_mixer->mc_out_enable = enable;
2026-11-11 08:50:43 +08:00
/* close mch alsa/spdif so npcm can reuse the handle */
if (!enable) {
pthread_mutex_lock(&audio_mixer->outport_locks[idx]);
port = audio_mixer->out_ports[idx];
if (port && port->standby) {
port->standby(port);
2026-08-13 11:28:40 +08:00
}
2026-11-11 08:50:43 +08:00
pthread_mutex_unlock(&audio_mixer->outport_locks[idx]);
2026-08-13 11:28:40 +08:00
}
2026-11-11 08:50:43 +08:00
}
int mixer_set_mix_channel_count(struct amlAudioMixer *audio_mixer, uint32_t ch)
{
uint32_t mix_ch;
struct audioCfg base_cfg;
R_CHECK_POINTER_LEGAL(-EINVAL, audio_mixer, "");
if (ch != MIXER_MIX_CHANNELS_8 && ch != MIXER_MIX_CHANNELS_10) {
AM_LOGW("mix_ch %u invalid, use %d", ch, MIXER_MIX_CHANNELS_8);
}
mix_ch = mixer_normalize_mix_ch(ch);
pthread_mutex_lock(&audio_mixer->lock);
if (mix_ch == audio_mixer->mix_channel_cnt &&
audio_mixer->multich_mixer.cfg.channelCnt == mix_ch) {
pthread_mutex_unlock(&audio_mixer->lock);
return 0;
}
AM_LOGI("mix_channel_cnt %u -> %u", audio_mixer->mix_channel_cnt, mix_ch);
memcpy(&base_cfg, &audio_mixer->multich_mixer.cfg, sizeof(base_cfg));
audio_mixer->mix_channel_cnt = mix_ch;
deinit_multich_mixer(audio_mixer);
if (init_multich_mixer(audio_mixer, &base_cfg) < 0) {
pthread_mutex_unlock(&audio_mixer->lock);
AM_LOGE("reinit mix bus failed");
return -1;
}
mixer_reconfig_effect_pcm_port(audio_mixer);
pthread_mutex_unlock(&audio_mixer->lock);
return 0;
}
uint32_t mixer_get_mix_channel_count(struct amlAudioMixer *audio_mixer)
{
R_CHECK_POINTER_LEGAL(0, audio_mixer, "");
return audio_mixer->mix_channel_cnt;
2026-08-13 11:28:40 +08:00
}
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};
2026-11-11 08:50:43 +08:00
snprintf(before_dump_name, 64, "mixed_before_limiter_%uch.pcm", in_cfg.channelCnt);
2026-08-13 11:28:40 +08:00
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};
2026-11-11 08:50:43 +08:00
snprintf(after_dump_name, 64, "mixed_after_limiter_%uch.pcm", in_cfg.channelCnt);
2026-08-13 11:28:40 +08:00
aml_dump_audio_bitstreams(after_dump_name, buffer, bytes);
}
return 0;
}
2026-11-11 08:50:43 +08:00
static void mixer_make_pcm_cfg(struct audioCfg *dst, const struct audioCfg *src,
uint32_t ch, audio_channel_mask_t mask)
2026-08-13 11:28:40 +08:00
{
2026-11-11 08:50:43 +08:00
memcpy(dst, src, sizeof(*dst));
dst->channelCnt = ch;
dst->channelMask = mask;
dst->frame_size = ch * audio_bytes_per_sample(src->format);
}
static int init_mixer_buffer_output_port(struct amlAudioMixer *audio_mixer,
MIXER_OUTPUT_PORT output_type, struct audioCfg *config, size_t buf_frames)
{
output_port *out_port = NULL;
R_CHECK_PARAM_LEGAL(-1, output_type, MIXER_OUTPUT_PORT_STEREO_PCM, MIXER_OUTPUT_PORT_NUM - 1, "");
pthread_mutex_lock(&audio_mixer->outport_locks[output_type]);
AM_LOGI("buffer output port:%s", mixerOutputType2Str(output_type));
out_port = new_buffer_output_port(output_type, config, buf_frames);
if (out_port == NULL) {
AM_LOGE("new_buffer_output_port fail");
pthread_mutex_unlock(&audio_mixer->outport_locks[output_type]);
return -1;
}
audio_mixer->out_ports[output_type] = out_port;
pthread_mutex_unlock(&audio_mixer->outport_locks[output_type]);
return 0;
}
static void mixer_fill_outport_buf(struct amlAudioMixer *audio_mixer,
MIXER_OUTPUT_PORT port_index, const void *src, size_t bytes)
{
output_port *port = NULL;
2026-08-13 11:28:40 +08:00
pthread_mutex_lock(&audio_mixer->outport_locks[port_index]);
2026-11-11 08:50:43 +08:00
port = audio_mixer->out_ports[port_index];
if (port && src && port->data_buf) {
if (bytes > port->data_buf_len) {
AM_LOGE("port %s bytes %zu > buf %zu, truncate",
mixerOutputType2Str(port_index), bytes, port->data_buf_len);
bytes = port->data_buf_len;
2026-08-13 11:28:40 +08:00
}
2026-11-11 08:50:43 +08:00
memcpy(port->data_buf, src, bytes);
set_outport_data_avail(port, bytes);
2026-08-13 11:28:40 +08:00
}
pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]);
2026-11-11 08:50:43 +08:00
}
static void mixer_fill_effect_pcm_port(struct amlAudioMixer *audio_mixer,
const void *src, size_t bytes, const struct audioCfg *packed_cfg)
{
MIXER_OUTPUT_PORT port_index = MIXER_OUTPUT_PORT_EFFECT_PCM;
output_port *port = NULL;
pthread_mutex_lock(&audio_mixer->outport_locks[port_index]);
port = audio_mixer->out_ports[port_index];
if (port && src && port->data_buf && packed_cfg) {
memcpy(&port->cfg, packed_cfg, sizeof(*packed_cfg));
if (bytes > port->data_buf_len) {
AM_LOGE("port %s bytes %zu > buf %zu, truncate",
mixerOutputType2Str(port_index), bytes, port->data_buf_len);
bytes = port->data_buf_len;
}
memcpy(port->data_buf, src, bytes);
set_outport_data_avail(port, bytes);
}
pthread_mutex_unlock(&audio_mixer->outport_locks[port_index]);
}
int mixer_get_inport_frame_size(struct amlAudioMixer *audio_mixer, uint8_t port_index)
{
input_port *in_port = NULL;
R_CHECK_POINTER_LEGAL(0, audio_mixer, "");
R_CHECK_PARAM_LEGAL(0, port_index, 0, NR_INPORTS - 1, "");
in_port = audio_mixer->in_ports[port_index];
if (!in_port) {
return 0;
}
return in_port->cfg.frame_size;
}
int mixer_get_inport_channel_count(struct amlAudioMixer *audio_mixer, uint8_t port_index)
{
input_port *in_port = NULL;
R_CHECK_POINTER_LEGAL(0, audio_mixer, "");
R_CHECK_PARAM_LEGAL(0, port_index, 0, NR_INPORTS - 1, "");
in_port = audio_mixer->in_ports[port_index];
if (!in_port) {
return 0;
}
return in_port->cfg.channelCnt;
}
static int mixer_reconfig_effect_pcm_port(struct amlAudioMixer *audio_mixer)
{
MIXER_OUTPUT_PORT idx = MIXER_OUTPUT_PORT_EFFECT_PCM;
output_port *port = NULL;
struct audioCfg cfg;
size_t need = 0;
mixer_make_pcm_cfg(&cfg, &audio_mixer->multich_mixer.cfg,
audio_mixer->mix_channel_cnt,
mixer_mix_layout_mask(audio_mixer->mix_channel_cnt));
need = MIXER_FRAME_COUNT * cfg.frame_size;
pthread_mutex_lock(&audio_mixer->outport_locks[idx]);
port = audio_mixer->out_ports[idx];
if (!port) {
pthread_mutex_unlock(&audio_mixer->outport_locks[idx]);
return 0;
}
memcpy(&port->cfg, &cfg, sizeof(cfg));
if (need > port->data_buf_len) {
char *buf = (char *)aml_audio_realloc(port->data_buf, need);
if (!buf) {
AM_LOGE("realloc EFFECT_PCM buf %zu failed", need);
pthread_mutex_unlock(&audio_mixer->outport_locks[idx]);
return -ENOMEM;
}
port->data_buf = buf;
port->data_buf_len = need;
}
port->data_buf_frame_cnt = MIXER_FRAME_COUNT;
port->bytes_avail = 0;
pthread_mutex_unlock(&audio_mixer->outport_locks[idx]);
AM_LOGI("EFFECT_PCM ch:%u mask:0x%x buf:%zu", cfg.channelCnt, cfg.channelMask, need);
return 0;
2026-08-13 11:28:40 +08:00
}
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;
2026-11-11 08:50:43 +08:00
struct audioCfg packed_cfg;
void *stereo_src = NULL;
int ret = 0;
2026-08-13 11:28:40 +08:00
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);
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);
}
2026-11-11 08:50:43 +08:00
audio_mixer->valid_channel_mask = mixed_data_valid_channelMask;
2026-08-13 11:28:40 +08:00
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)) {
2026-11-11 08:50:43 +08:00
snprintf(acFilePathStr, ENUM_TYPE_STR_MAX_LEN, "%s_%uch.pcm", "SubMix_after_mixed",
p_multich_mixer->cfg.channelCnt);
2026-08-13 11:28:40 +08:00
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;
2026-11-11 08:50:43 +08:00
ret = aml_ai_audio_process(process_frames, p_multich_mixer->cfg.channelCnt, p_multich_mixer->cfg.format, mixed_data_valid_channelMask, mixed_data_ptr);
2026-08-13 11:28:40 +08:00
if (ret != 0) {
AM_LOGE("aml_ai_audio_process failed, ret=%d", ret);
}
}
2026-11-11 08:50:43 +08:00
/* packed PCM by occupied channels, for EFFECT (VX/DAP) and stereo downmix */
mixer_make_pcm_cfg(&packed_cfg, &p_multich_mixer->cfg,
audio_channel_count_from_out_mask(mixed_data_valid_channelMask),
mixed_data_valid_channelMask);
stereo_src = mixed_data_ptr;
if (packed_cfg.channelCnt != p_multich_mixer->cfg.channelCnt ||
packed_cfg.channelMask != p_multich_mixer->cfg.channelMask) {
ret = pcm_extract_by_channel_mask(&audio_mixer->extract_buf, &audio_mixer->extract_buf_size,
mixed_data_ptr, p_multich_mixer->cfg.channelMask,
mixed_data_valid_channelMask, p_multich_mixer->cfg.format, MIXER_FRAME_COUNT);
if (ret == 0) {
stereo_src = audio_mixer->extract_buf;
} else {
AM_LOGE("pcm_extract_by_channel_mask fail, fallback mix-bus");
packed_cfg = p_multich_mixer->cfg;
stereo_src = mixed_data_ptr;
2026-08-13 11:28:40 +08:00
}
}
2026-11-11 08:50:43 +08:00
/* EFFECT_PCM: TV/SBR speaker tap; STB has no this port */
if (audio_mixer->out_ports[MIXER_OUTPUT_PORT_EFFECT_PCM]) {
mixer_fill_effect_pcm_port(audio_mixer, stereo_src,
MIXER_FRAME_COUNT * packed_cfg.frame_size, &packed_cfg);
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) {
output_port *p_effect = audio_mixer->out_ports[MIXER_OUTPUT_PORT_EFFECT_PCM];
if (p_effect) {
aml_dump_audio_bitstreams("outport_effect.pcm", p_effect->data_buf, p_effect->bytes_avail);
}
}
}
/* MULTI_PCM: 8ch for eARC/HDMI only when sink supports mch PCM */
if (audio_mixer->mc_out_enable) {
if (p_multich_mixer->cfg.channelCnt == MIXER_MIX_CHANNELS_8) {
mixer_fill_outport_buf(audio_mixer, MIXER_OUTPUT_PORT_MULTI_PCM, mixed_data_ptr, mixed_data_size);
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) {
aml_dump_audio_bitstreams("outport_8ch.pcm", mixed_data_ptr, mixed_data_size);
}
} else {
ret = do_downmix_to_8ch(&audio_mixer->pcm_downmix_8ch, mixed_data_ptr, MIXER_FRAME_COUNT, &p_multich_mixer->cfg);
if (ret == 0) {
mixer_fill_outport_buf(audio_mixer, MIXER_OUTPUT_PORT_MULTI_PCM,
audio_mixer->pcm_downmix_8ch.output_buf, audio_mixer->pcm_downmix_8ch.output_buf_size);
if (get_audiomixer_dump_enable(DUMP_AUDIOMIXER_OUTDUMP)) {
aml_dump_audio_bitstreams("outport_8ch.pcm",
audio_mixer->pcm_downmix_8ch.output_buf, audio_mixer->pcm_downmix_8ch.output_buf_size);
}
} else {
AM_LOGE("do_downmix_to_8ch failed");
}
}
}
/* stereo downmix: TV/SBR HP/SPDIF, STB speaker */
2026-08-13 11:28:40 +08:00
pthread_mutex_lock(&audio_mixer->outport_locks[port_index]);
2026-11-11 08:50:43 +08:00
do_downmix_to_2ch(p_downmix, stereo_src, MIXER_FRAME_COUNT, &packed_cfg);
2026-08-13 11:28:40 +08:00
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;
2026-11-11 08:50:43 +08:00
audio_mixer->mix_channel_cnt = mixer_normalize_mix_ch(
(uint32_t)aml_audio_property_get_int(MIXER_MIX_CHANNELS_PROP, MIXER_MIX_CHANNELS_DEFAULT));
2026-08-13 11:28:40 +08:00
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);
2026-11-11 08:50:43 +08:00
{
struct audioCfg cfg_8ch;
struct audioCfg cfg_effect;
mixer_make_pcm_cfg(&cfg_8ch, &audio_mixer->multich_mixer.cfg,
MIXER_MIX_CHANNELS_8, AUDIO_CHANNEL_OUT_7POINT1);
mixer_make_pcm_cfg(&cfg_effect, &audio_mixer->multich_mixer.cfg,
audio_mixer->mix_channel_cnt, mixer_mix_layout_mask(audio_mixer->mix_channel_cnt));
if (init_mixer_buffer_output_port(audio_mixer, MIXER_OUTPUT_PORT_MULTI_PCM, &cfg_8ch, MIXER_FRAME_COUNT) < 0) {
AM_LOGE("init MULTI_PCM buffer out port failed");
goto err_state;
}
if (need_speaker_effect_path(adev)) {
if (init_mixer_buffer_output_port(audio_mixer, MIXER_OUTPUT_PORT_EFFECT_PCM,
&cfg_effect, MIXER_FRAME_COUNT) < 0) {
AM_LOGE("init EFFECT_PCM buffer out port failed");
goto err_state;
}
audio_mixer->effect_out_enable = true;
} else {
AM_LOGI("skip EFFECT_PCM: not TV/SBR");
}
}
2026-08-13 11:28:40 +08:00
init_aml_pcm_downmix(&audio_mixer->pcm_downmix);
2026-11-11 08:50:43 +08:00
init_aml_pcm_downmix(&audio_mixer->pcm_downmix_8ch);
audio_mixer->valid_channel_mask = AUDIO_CHANNEL_OUT_STEREO;
2026-08-13 11:28:40 +08:00
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);
2026-11-11 08:50:43 +08:00
deinit_aml_pcm_downmix(&audio_mixer->pcm_downmix_8ch);
if (audio_mixer->extract_buf) {
aml_audio_free(audio_mixer->extract_buf);
audio_mixer->extract_buf = NULL;
}
2026-08-13 11:28:40 +08:00
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;
}
2026-11-11 08:50:43 +08:00
dprintf(s32Fd, "[AML_HAL]---------------Submix submix_standby: [%d] mix_ch: [%u] valid_mask: %#x mc_out: [%d]---------\n",
pstAudioMixer->submix_standby, pstAudioMixer->mix_channel_cnt, pstAudioMixer->valid_channel_mask,
pstAudioMixer->mc_out_enable);
2026-08-13 11:28:40 +08:00
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");
2026-11-11 08:50:43 +08:00
for (int port_index = MIXER_OUTPUT_PORT_STEREO_PCM; port_index < MIXER_OUTPUT_PORT_NUM; port_index++) {
output_port *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);
dprintf(s32Fd, "[AML_HAL] bytes_avail : %zu | ch_mask : %#x\n",
pstOutPort->bytes_avail, pstOutPort->cfg.channelMask);
release_cur_outport_lock(pstAudioMixer, port_index);
}
2026-08-13 11:28:40 +08:00
}
}
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);
2026-11-11 08:50:43 +08:00
frame_size = mixer_get_inport_frame_size(adev->audio_mixer, out->inputPortID);
2026-08-13 11:28:40 +08:00
pthread_mutex_unlock(&adev->audio_mixer_lock);
2026-11-11 08:50:43 +08:00
if (frame_size <= 0) {
frame_size = 4;
}
2026-08-13 11:28:40 +08:00
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);
}