1612 lines
60 KiB
C
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);
|
|
}
|
|
|