blob: bdcf4523a68f4a1a235f6aebbd63a791871e9460 [file] [log] [blame]
/*
** Copyright 2010, 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_NDEBUG 0
#define LOG_TAG "AudioPostProcessor"
#include <fcntl.h>
#include <utils/Log.h>
#include "AudioHardware.h"
#include "AudioPostProcessor.h"
#include <sys/stat.h>
#include "mot_acoustics.h"
// hardware specific functions
extern uint16_t HC_CTO_AUDIO_MM_PARAMETER_TABLE[];
///////////////////////////////////
// Some logging #defines
#define ECNS_LOG_ENABLE_OFFSET 1 // 2nd word of the configuration buffer
#define ECNS_LOGGING_BITS 0xBFFF // 15 possible logpoints
#define MOT_LOG_DELIMITER_START 0xFEED
#define MOT_LOG_DELIMITER_END 0xF00D
#define BASIC_DOCK_PROP_VALUE 0
#define ECNSLOGPATH "/data/ecns"
#define DOCK_PROP_PATH "/sys/class/switch/dock/dock_prop"
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#endif
namespace android {
AudioPostProcessor::AudioPostProcessor() :
mEcnsScratchBuf(0), mEcnsDlBuf(0)
{
LOGD("%s",__FUNCTION__);
// One-time CTO Audio configuration
mAudioMmEnvVar.cto_audio_mm_param_block_ptr = HC_CTO_AUDIO_MM_PARAMETER_TABLE;
mAudioMmEnvVar.cto_audio_mm_pcmlogging_buffer_block_ptr = mPcmLoggingBuf;
mAudioMmEnvVar.pcmlogging_buffer_block_size = ARRAY_SIZE(mPcmLoggingBuf);
mAudioMmEnvVar.cto_audio_mm_runtime_param_mem_ptr = mRuntimeParam;
mAudioMmEnvVar.cto_audio_mm_static_memory_block_ptr = mStaticMem;
mAudioMmEnvVar.cto_audio_mm_scratch_memory_block_ptr = mScratchMem;
mAudioMmEnvVar.accy = CTO_AUDIO_MM_ACCY_INVALID;
mAudioMmEnvVar.sample_rate = CTO_AUDIO_MM_SAMPL_44100;
// Initial conditions for EC/NS
stopEcns();
}
AudioPostProcessor::~AudioPostProcessor()
{
if (mEcnsRunning) {
LOGD("%s",__FUNCTION__);
stopEcns();
}
}
uint32_t AudioPostProcessor::convOutDevToCTO(uint32_t outDev)
{
uint32_t dock_prop = 0;
// Only loudspeaker and audio docks are currently in this table
switch (outDev) {
case CPCAP_AUDIO_OUT_SPEAKER:
return CTO_AUDIO_MM_ACCY_LOUDSPEAKER;
case CPCAP_AUDIO_OUT_ANLG_DOCK_HEADSET:
dock_prop = read_dock_prop(DOCK_PROP_PATH);
if ((dock_prop < 0) || (dock_prop == BASIC_DOCK_PROP_VALUE)) {
// Basic dock, or error getting the dock ID
return CTO_AUDIO_MM_ACCY_INVALID;
}
else // speaker dock
return CTO_AUDIO_MM_ACCY_DOCK;
default:
return CTO_AUDIO_MM_ACCY_INVALID;
}
}
uint32_t AudioPostProcessor::convRateToCto(uint32_t rate)
{
switch (rate) {
case 44100: // Most likely.
return CTO_AUDIO_MM_SAMPL_44100;
case 8000:
return CTO_AUDIO_MM_SAMPL_8000;
case 11025:
return CTO_AUDIO_MM_SAMPL_11025;
case 12000:
return CTO_AUDIO_MM_SAMPL_12000;
case 16000:
return CTO_AUDIO_MM_SAMPL_16000;
case 22050:
return CTO_AUDIO_MM_SAMPL_22050;
case 24000:
return CTO_AUDIO_MM_SAMPL_24000;
case 32000:
return CTO_AUDIO_MM_SAMPL_32000;
case 48000:
return CTO_AUDIO_MM_SAMPL_48000;
default:
return CTO_AUDIO_MM_SAMPL_44100;
}
}
void AudioPostProcessor::configMmAudio()
{
if (mAudioMmEnvVar.accy != CTO_AUDIO_MM_ACCY_INVALID) {
LOGD("Configure CTO Audio MM processing");
// fetch the corresponding runtime audio parameter
api_cto_audio_mm_param_parser(&(mAudioMmEnvVar), (int16_t *)0, (int16_t *)0);
// Initialize algorithm static memory
api_cto_audio_mm_init(&(mAudioMmEnvVar), (int16_t *)0, (int16_t *)0);
} else {
LOGD("CTO Audio MM processing is disabled.");
}
}
void AudioPostProcessor::enableEcns(bool value)
{
if(mEcnsEnabled!=value)
LOGD("enableEcns(%s)",value?"true":"false");
mEcnsEnabled = value;
if (!mEcnsEnabled)
stopEcns();
}
void AudioPostProcessor::setAudioDev(struct cpcap_audio_stream *outDev,
struct cpcap_audio_stream *inDev,
bool is_bt, bool is_bt_ec, bool is_spdif)
{
uint32_t dock_prop = 0;
uint32_t mm_accy = convOutDevToCTO(outDev->id);
Mutex::Autolock lock(mMmLock);
if (is_bt) {
if (is_bt_ec)
mEcnsMode = CTO_AUDIO_USECASE_NB_BLUETOOTH_WITH_ECNS;
else
mEcnsMode = CTO_AUDIO_USECASE_NB_BLUETOOTH_WITHOUT_ECNS;
} else if (is_spdif) // May need a more complex check here for HDMI vs. others
mEcnsMode = CTO_AUDIO_USECASE_NB_ACCY_1;
else if (outDev->id==CPCAP_AUDIO_OUT_HEADSET && inDev->id==CPCAP_AUDIO_IN_MIC1)
mEcnsMode = CTO_AUDIO_USECASE_NB_HEADSET_WITH_HANDSET_MIC;
else if (outDev->id==CPCAP_AUDIO_OUT_HEADSET)
mEcnsMode = CTO_AUDIO_USECASE_NB_HEADSET;
else if (outDev->id==CPCAP_AUDIO_OUT_ANLG_DOCK_HEADSET) {
dock_prop = read_dock_prop(DOCK_PROP_PATH);
if ((dock_prop < 0) || (dock_prop == BASIC_DOCK_PROP_VALUE))
// Basic dock, or error getting the dock ID
mEcnsMode = CTO_AUDIO_USECASE_NB_ACCY_1;
else
// Speaker Dock
mEcnsMode = CTO_AUDIO_USECASE_NB_DEDICATED_DOCK;
}
else
mEcnsMode = CTO_AUDIO_USECASE_NB_SPKRPHONE;
if (mEcnsEnabled) {
// We may need to reset the EC/NS if the output device changed.
stopEcns();
}
LOGV("setAudioDev %d", outDev->id);
if (mm_accy != mAudioMmEnvVar.accy) {
mAudioMmEnvVar.accy = mm_accy;
configMmAudio();
}
}
// Setting the HW sampling rate may require reconfiguration of audio processing.
void AudioPostProcessor::setPlayAudioRate(int sampRate)
{
uint32_t rate = convRateToCto(sampRate);
Mutex::Autolock lock(mMmLock);
LOGD("AudioPostProcessor::setPlayAudioRate %d", sampRate);
if (rate != mAudioMmEnvVar.sample_rate) {
mAudioMmEnvVar.sample_rate = rate;
configMmAudio();
}
}
void AudioPostProcessor::doMmProcessing(void * buffer, int numSamples)
{
Mutex::Autolock lock(mMmLock);
if (mAudioMmEnvVar.accy != CTO_AUDIO_MM_ACCY_INVALID &&
!mEcnsEnabled) {
// Apply the CTO audio effects in-place.
mAudioMmEnvVar.frame_size = numSamples;
api_cto_audio_mm_main(&mAudioMmEnvVar, (int16_t *)buffer, (int16_t *)buffer);
}
}
int AudioPostProcessor::getEcnsRate (void)
{
return mEcnsRate;
}
void AudioPostProcessor::initEcns(int rate, int bytes)
{
LOGD("%s",__FUNCTION__);
CTO_AUDIO_USECASES_CTRL mode;
Mutex::Autolock lock(mEcnsBufLock);
if (rate != 8000 && rate != 16000) {
LOGW("Invalid rate for EC/NS, disabling");
mEcnsEnabled = 0;
mEcnsRunning = 0;
return;
}
mode = mEcnsMode;
mEcnsRate = rate;
if (mEcnsRate==16000) {
// Offset to the 16K (WB) block in the coefficients file
mode = CTO_AUDIO_USECASES_CTRL(mode + CTO_AUDIO_USECASE_WB_HANDSET);
}
LOGD("%s for mode %d at %d size %d",__FUNCTION__, mode, mEcnsRate, bytes);
mEcnsCtrl.framesize = bytes/2;
mEcnsCtrl.micFlag = 0; // 0- one mic. 1- dual mic. 2- three mic.
mEcnsCtrl.digital_mode = (rate == 8000) ? 0 : 1; // 8K or 16K
mEcnsCtrl.usecase = mode;
mMemBlocks.staticMemory_1 = mStaticMemory_1;
mMemBlocks.staticMemory_2 = NULL;
mMemBlocks.mot_datalog = mMotDatalog;
mMemBlocks.gainTableMemory = mParamTable;
FILE * fp = fopen("/system/etc/voip_aud_params.bin", "r");
if (fp) {
if (fread(mParamTable, sizeof(mParamTable), 1, fp) < 1) {
LOGE("Cannot read VOIP parameter file. Disabling EC/NS.");
fclose(fp);
mEcnsEnabled = 0;
mEcnsRunning = 0;
return;
}
fclose(fp);
}
else {
LOGE("Cannot open VOIP parameter file. Disabling EC/NS.");
mEcnsEnabled = 0;
mEcnsRunning = 0;
return;
}
mEcnsRunning = 1;
mEcnsOutBuf = 0;
mEcnsOutBufSize = 0;
mEcnsOutBufReadOffset = 0;
// Send setup parameters to the EC/NS module, init the module.
API_MOT_SETUP(&mEcnsCtrl, &mMemBlocks);
API_MOT_INIT(&mEcnsCtrl, &mMemBlocks);
}
void AudioPostProcessor::stopEcns (void)
{
if (mEcnsRunning)
LOGD("%s",__FUNCTION__);
AutoMutex lock(mEcnsBufLock);
mEcnsRunning = 0;
mEcnsRate = 0;
if (mEcnsScratchBuf) {
free(mEcnsScratchBuf);
mEcnsScratchBuf = 0;
}
mEcnsScratchBufSize = 0;
mEcnsOutFd = -1;
for (int i=0;i<15;i++) {
if (mLogFp[i])
fclose(mLogFp[i]);
mLogFp[i]=0;
}
if (mEcnsDlBuf) {
free(mEcnsDlBuf);
mEcnsDlBuf = 0;
}
mEcnsDlBufSize = 0;
// In case write() is blocked, set it free.
mEcnsBufCond.signal();
}
// Returns: Bytes written (actually "to-be-written" by read thread).
int AudioPostProcessor::writeDownlinkEcns(int fd, void * buffer, bool stereo,
int bytes, Mutex *fdLock)
{
int written = 0;
mEcnsBufLock.lock();
if (mEcnsEnabled && !mEcnsRunning) {
long usecs = 20*1000;
// Give the read thread a chance to catch up.
LOGV("%s: delay %d msecs for ec/ns to start",__FUNCTION__, (int)(usecs/1000));
mEcnsBufLock.unlock();
usleep(usecs);
mEcnsBufLock.lock();
written = bytes; // Pretend all data was consumed even if ecns isn't running
}
if (mEcnsRunning) {
// Only run through here after initEcns has been done by read thread.
mEcnsOutFd = fd;
mEcnsOutBuf = buffer;
mEcnsOutBufSize = bytes;
mEcnsOutBufReadOffset = 0;
mEcnsOutFdLockp = fdLock;
mEcnsOutStereo = stereo;
if (mEcnsBufCond.waitRelative(mEcnsBufLock, seconds(1)) != NO_ERROR) {
LOGE("%s: Capture thread is stalled.", __FUNCTION__);
}
if (mEcnsOutBufSize != 0)
LOGD("%s: Buffer not consumed", __FUNCTION__);
else
written = bytes; // All data consumed
}
mEcnsBufLock.unlock();
return written;
}
// Returns: Bytes processed.
int AudioPostProcessor::applyUplinkEcns(void * buffer, int bytes, int rate)
{
static int16 ul_gbuff2[160];
int16_t *dl_buf;
int16_t *ul_buf = (int16_t *)buffer;
int dl_buf_bytes=0;
// The write thread could have left us with one frame of data in the
// driver when we started reading.
static bool onetime;
if (!mEcnsEnabled)
return 0;
LOGV("%s %d bytes at %d Hz",__FUNCTION__, bytes, rate);
if (mEcnsEnabled && !mEcnsRunning) {
initEcns(rate, bytes);
onetime=true;
}
// In case the rate switched..
if (mEcnsEnabled && rate != mEcnsRate) {
stopEcns();
initEcns(rate, bytes);
onetime=true;
}
if (!mEcnsRunning) {
LOGE("EC/NS failed to init, read returns.");
return -1;
}
if (onetime) {
// Pad a half-buffer to the start of the call, to avoid some starvation
// due to scheduling jitter.
int16_t *zerobuf;
size_t zerosize = (bytes/2*(mEcnsOutStereo?2:1))&(~0x3);
onetime = false;
zerobuf = (int16_t *) calloc(1, zerosize);
if ((mEcnsOutFd != -1) && zerobuf) {
LOGD("Prefill of output driver with %d bytes", zerosize);
mEcnsOutFdLockp->lock();
::write(mEcnsOutFd, zerobuf, zerosize);
mEcnsOutFdLockp->unlock();
}
free(zerobuf);
}
for (int i=0; mEcnsOutBuf==0 && i<1; i++) {
// Try to wait a bit for downlink speech to come.
usleep(5000);
}
mEcnsBufLock.lock();
// Need a contiguous stereo playback buffer in the end.
if (bytes*2 != mEcnsDlBufSize || !mEcnsDlBuf) {
if (mEcnsDlBuf)
free(mEcnsDlBuf);
mEcnsDlBuf = (int16_t*)malloc(bytes*2);
if (mEcnsDlBuf)
mEcnsDlBufSize = bytes*2;
}
dl_buf = mEcnsDlBuf;
if (!dl_buf) {
mEcnsBufLock.unlock();
return -1;
}
// Need to gather appropriate amount of downlink speech.
// Take oldest scratch data first. The scratch buffer holds fractions of buffers
// that were too small for processing.
if (mEcnsScratchBuf && mEcnsScratchBufSize) {
dl_buf_bytes = mEcnsScratchBufSize > bytes ? bytes:mEcnsScratchBufSize;
memcpy(dl_buf, mEcnsScratchBuf, dl_buf_bytes);
//LOGD("Took %d bytes from mEcnsScratchBuf", dl_buf_bytes);
mEcnsScratchBufSize -= dl_buf_bytes;
if (mEcnsScratchBufSize==0) {
// This should always be true.
free(mEcnsScratchBuf);
mEcnsScratchBuf = 0;
mEcnsScratchBufSize = 0;
}
}
// Take fresh data from write thread second.
if (dl_buf_bytes < bytes) {
int bytes_to_copy = mEcnsOutBufSize - mEcnsOutBufReadOffset;
bytes_to_copy = bytes_to_copy + dl_buf_bytes > bytes?
bytes-dl_buf_bytes:bytes_to_copy;
if (bytes_to_copy) {
memcpy((void *)((unsigned int)dl_buf+dl_buf_bytes),
(void *)((unsigned int)mEcnsOutBuf+mEcnsOutBufReadOffset),
bytes_to_copy);
dl_buf_bytes += bytes_to_copy;
}
//LOGD("Took %d bytes from mEcnsOutBuf. Need %d more.", bytes_to_copy,
// bytes-dl_buf_bytes);
mEcnsOutBufReadOffset += bytes_to_copy;
if (mEcnsOutBufSize - mEcnsOutBufReadOffset < bytes) {
// We've depleted the output buffer, it's smaller than one uplink "frame".
// First take any unused data into scratch, then free the write thread.
if (mEcnsScratchBuf) {
LOGE("Memleak - coding error");
free(mEcnsScratchBuf);
}
if (mEcnsOutBufSize - mEcnsOutBufReadOffset > 0) {
if ((mEcnsScratchBuf=malloc(mEcnsOutBufSize - mEcnsOutBufReadOffset)) == 0) {
LOGE("%s: Alloc failed, scratch data lost.",__FUNCTION__);
} else {
mEcnsScratchBufSize = mEcnsOutBufSize - mEcnsOutBufReadOffset;
//LOGD("....store %d bytes into scratch buf %p",
// mEcnsScratchBufSize, mEcnsScratchBuf);
memcpy(mEcnsScratchBuf,
(void *)((unsigned int)mEcnsOutBuf+mEcnsOutBufReadOffset),
mEcnsScratchBufSize);
}
}
mEcnsOutBuf = 0;
mEcnsOutBufSize = 0;
mEcnsOutBufReadOffset = 0;
//LOGD("Signal write thread - need data.");
mEcnsBufCond.signal();
}
}
mEcnsBufLock.unlock();
// Pad downlink with zeroes as last resort. We have to process the UL speech.
if (dl_buf_bytes < bytes) {
LOGV("%s:EC/NS Starved for downlink data. have %d need %d.",
__FUNCTION__,dl_buf_bytes, bytes);
memset(&dl_buf[dl_buf_bytes/sizeof(int16_t)],
0,
bytes-dl_buf_bytes);
}
// Do Echo Cancellation
API_MOT_LOG_RESET(&mEcnsCtrl, &mMemBlocks);
API_MOT_DOWNLINK(&mEcnsCtrl, &mMemBlocks, (int16*)dl_buf, (int16*)ul_buf, &(ul_gbuff2[0]));
API_MOT_UPLINK(&mEcnsCtrl, &mMemBlocks, (int16*)dl_buf, (int16*)ul_buf, &(ul_gbuff2[0]));
// Playback the echo-cancelled speech to driver.
// Include zero padding. Our echo canceller needs a consistent path.
// TODO: Don't playback here, move it to the write() thread. Also, make sure
// the output is really stereo before upconverting (i.e. SCO Audio)
if (dl_buf_bytes && mEcnsOutStereo) {
// Convert up to stereo, in place.
for (int i = dl_buf_bytes/2-1; i >= 0; i--) {
dl_buf[i*2] = dl_buf[i];
dl_buf[i*2+1] = dl_buf[i];
}
dl_buf_bytes *= 2;
}
if (mEcnsOutFd != -1) {
mEcnsOutFdLockp->lock();
::write(mEcnsOutFd, dl_buf, bytes*(mEcnsOutStereo?2:1));
mEcnsOutFdLockp->unlock();
}
// Do the CTO SuperAPI internal logging. It can log various steps of uplink and downlink speech.
// (Do this after writing output to avoid adding latency.)
ecnsLogToFile(bytes);
return bytes;
}
void AudioPostProcessor::ecnsLogToFile(int bytes)
{
static int numPoints;
uint16_t *logp;
int mode = mEcnsMode + (mEcnsRate==16000?CTO_AUDIO_USECASE_WB_HANDSET:0);
uint16_t *audioProfile = &mParamTable[AUDIO_PROFILE_PARAMETER_BLOCK_WORD16_SIZE*mode];
if (audioProfile[ECNS_LOG_ENABLE_OFFSET] & ECNS_LOGGING_BITS) {
if (!mLogFp[0]) {
numPoints = 0;
LOGE("EC/NS AUDIO LOGGER CONFIGURATION:");
LOGE("log enable %04X",
audioProfile[ECNS_LOG_ENABLE_OFFSET]);
mkdir(ECNSLOGPATH, 00770);
for (uint16_t i=1;i>0;i<<=1) {
if (i&ECNS_LOGGING_BITS&audioProfile[ECNS_LOG_ENABLE_OFFSET]) {
numPoints++;
}
}
LOGE("Number of log points is %d.",numPoints);
logp = mMotDatalog;
for (int i=0;i<numPoints;i++) {
char fname[80];
fname[0]='\0';
// Log format: FEED TAG1 LEN1 F00D [bytes]
LOGV("feed? %04X",logp[0]);
sprintf(fname, ECNSLOGPATH"/log-0x%04X.pcm",
logp[1]);
LOGD("fname[%d] = %s",i,fname);
LOGD("len = %d*2", logp[2]);
LOGV("food? %04X", logp[3]);
mLogFp[i]=fopen((const char *)fname, "w");
logp += 4 + logp[2];
}
}
logp = mMotDatalog;
for (int i=0; i<numPoints; i++) {
if (mLogFp[i]) {
fwrite(&logp[4], logp[2]*sizeof(uint16_t), 1, mLogFp[i]);
logp += 4+logp[2];
} else {
LOGE("EC/NS logging enabled, but file not open.");
}
}
}
}
int AudioPostProcessor::read_dock_prop(char const *path)
{
int fd = -1;
const size_t SIZE = 7;
static int already_warned = -1;
char buffer[SIZE];
/* the docks come with a property id AC000 for basic docks
and AC002 for speaker docks, numbers might change, keeping
them for now.
*/
unsigned long int basic_dock_prop = 0xAC000;
unsigned long int spkr_dock_prop;
buffer[SIZE - 1] = '\0';
fd = open(path, O_RDONLY);
if (fd >= 0) {
int amt = read(fd, buffer, SIZE-1);
if (amt != SIZE-1) {
LOGE("Incomplete dock property read, cannot validate dock");
return -1;
}
spkr_dock_prop = strtoul(buffer, NULL, 16);
if (spkr_dock_prop <= 0) {
LOGE("dock property conversion error");
return -EINVAL;
}
close(fd);
LOGV("buffer = %s, spkr_dock_prop = 0x%X", buffer, spkr_dock_prop);
spkr_dock_prop = spkr_dock_prop ^ basic_dock_prop;
LOGV("dock_prop returned = %d", spkr_dock_prop);
return spkr_dock_prop;
} else {
if (already_warned == -1) {
LOGE("read_dock_prop failed to open %s\n", path);
already_warned = 1;
}
return -errno;
}
}
} //namespace android