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