blob: a94204d8b9e0eadabd0c23a4bee1ff5f7bc21ea8 [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
//#define DEBUG_TIMING
#ifdef DEBUG_TIMING
struct timeval mtv1, mtv2, mtv3, mtv4, mtv5, mtv6, mtv7, mtv8;
#define GETTIMEOFDAY gettimeofday
#else
#define GETTIMEOFDAY(a,b)
#endif
namespace android_audio_legacy {
AudioPostProcessor::AudioPostProcessor() :
mEcnsScratchBuf(0), mLogNumPoints(0), mEcnsDlBuf(0), mEcnsDlBufSize(0), mEcnsThread(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;
mEcnsThread = new EcnsThread();
// Initial conditions for EC/NS
stopEcns();
}
AudioPostProcessor::~AudioPostProcessor()
{
if (mEcnsRunning) {
LOGD("%s",__FUNCTION__);
enableEcns(0);
}
}
uint32_t AudioPostProcessor::convOutDevToCTO(uint32_t outDev)
{
int32_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(int value)
{
if (mEcnsEnabled!=value) {
LOGD("enableEcns() new %08x old %08x)", value, mEcnsEnabled);
mEcnsThread->requestExitAndWait();
stopEcns();
cleanupEcns();
mEcnsEnabled = value;
}
}
void AudioPostProcessor::setAudioDev(struct cpcap_audio_stream *outDev,
struct cpcap_audio_stream *inDev,
bool is_bt, bool is_bt_ec, bool is_spdif)
{
int32_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)
{
AutoMutex lock(mEcnsBufLock);
if (mEcnsRunning) {
LOGD("%s",__FUNCTION__);
mEcnsRunning = 0;
}
}
void AudioPostProcessor::cleanupEcns(void)
{
AutoMutex lock(mEcnsBufLock);
mEcnsRate = 0;
if (mEcnsScratchBuf) {
free(mEcnsScratchBuf);
mEcnsScratchBuf = 0;
}
mEcnsScratchBufSize = 0;
mEcnsOutFd = -1;
if (mEcnsDlBuf) {
free(mEcnsDlBuf);
mEcnsDlBuf = 0;
}
mEcnsDlBufSize = 0;
// In case write() is blocked, set it free.
mEcnsBufCond.signal();
ecnsLogToFile();
}
// Returns: Bytes written (actually "to-be-written" by EC/NS thread).
int AudioPostProcessor::writeDownlinkEcns(int fd, void * buffer, bool stereo,
int bytes, Mutex *fdLock)
{
int written = 0;
// write directly to pcm out driver if only NS is enabled
if (!(mEcnsEnabled & AEC)) {
if (fd >= 0) {
fdLock->lock();
::write(fd, buffer, bytes);
fdLock->unlock();
}
return bytes;
}
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 read.
int AudioPostProcessor::read(int fd, void * buffer, int bytes, int rate)
{
if (mEcnsEnabled) {
return mEcnsThread->readData(fd, buffer, bytes, rate, this);
}
ssize_t ret;
ret = ::read(fd, buffer, bytes);
if (ret < 0)
LOGE("Error reading from audio in: %s", strerror(errno));
return (int)ret;
}
// 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.");
if (mEcnsEnabled & AEC) {
mEcnsBufCond.signal();
}
return -1;
}
// do not get downlink audio if only NS is enabled
if (mEcnsEnabled & AEC) {
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();
}
}
LOGV_IF(dl_buf_bytes < bytes, "%s:EC/NS Starved for downlink data. have %d need %d.",
__FUNCTION__,dl_buf_bytes, bytes);
mEcnsBufLock.unlock();
} else {
if (mEcnsDlBufSize < bytes * 2) {
mEcnsDlBufSize = bytes * 2;
mEcnsDlBuf = (int16_t *)realloc(mEcnsDlBuf, mEcnsDlBufSize);
}
dl_buf = mEcnsDlBuf;
}
// Pad downlink with zeroes as last resort. We have to process the UL speech.
if (dl_buf_bytes < bytes) {
memset(&dl_buf[dl_buf_bytes/sizeof(int16_t)],
0,
bytes-dl_buf_bytes);
}
// Do Echo Cancellation
GETTIMEOFDAY(&mtv4, NULL);
API_MOT_LOG_RESET(&mEcnsCtrl, &mMemBlocks);
if (mEcnsEnabled & AEC) {
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.
if (mEcnsEnabled & AEC) {
if (mEcnsOutStereo) {
// Convert up to stereo, in place.
for (int i = 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;
}
GETTIMEOFDAY(&mtv5, NULL);
if (mEcnsOutFd != -1) {
mEcnsOutFdLockp->lock();
::write(mEcnsOutFd, &dl_buf[0],
bytes*(mEcnsOutStereo?2:1));
mEcnsOutFdLockp->unlock();
}
}
// Do the CTO SuperAPI internal logging.
// (Do this after writing output to avoid adding latency.)
GETTIMEOFDAY(&mtv6, NULL);
ecnsLogToRam(bytes);
return bytes;
}
void AudioPostProcessor::ecnsLogToRam (int bytes)
{
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 (!mLogBuf[0]) {
mLogNumPoints = 0;
mLogOffset = 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]) {
mLogNumPoints++;
}
}
LOGE("Number of log points is %d.", mLogNumPoints);
logp = mMotDatalog;
mLogSize = 10*60*50*bytes;
for (int i=0; i<mLogNumPoints; i++) {
// Allocate 10 minutes of logging per point
mLogBuf[i]=(char *)malloc(mLogSize);
if (!mLogBuf[i]) {
LOGE("%s: Memory allocation failed.", __FUNCTION__);
for (int j=0; j<i; j++) {
free(mLogBuf[j]);
mLogBuf[j]=0;
}
return;
}
}
}
if (mLogOffset+bytes > mLogSize)
return;
logp = mMotDatalog;
for (int i=0; i<mLogNumPoints; i++) {
if (mLogBuf[i]) {
mLogPoint[i] = logp[1];
memcpy(&mLogBuf[i][mLogOffset], &logp[4], logp[2]*sizeof(uint16_t));
logp += 4+logp[2];
} else {
LOGE("EC/NS logging enabled, but memory not allocated");
}
}
mLogOffset += bytes;
}
}
void AudioPostProcessor::ecnsLogToFile()
{
if (mLogNumPoints && mLogOffset > 16000*2) {
for (int i=0; i<mLogNumPoints; i++) {
FILE * fp;
char fname[80];
sprintf(fname, ECNSLOGPATH"/log-0x%04X.pcm", mLogPoint[i]);
fp = fopen((const char *)fname, "w");
if (fp) {
LOGE("Writing %d bytes to %s", mLogOffset, fname);
fwrite(mLogBuf[i], mLogOffset, 1, fp);
fclose(fp);
} else {
LOGE("Problem writing to %s", fname);
}
}
}
mLogOffset = 0;
}
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%lX", buffer, spkr_dock_prop);
spkr_dock_prop = spkr_dock_prop ^ basic_dock_prop;
LOGV("dock_prop returned = %lX", 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;
}
}
// ---------------------------------------------------------------------------------------------
// Echo Canceller thread
// Needed to isolate the EC/NS module from scheduling jitter of it's clients.
//
AudioPostProcessor::EcnsThread::EcnsThread() :
mReadBuf(0), mIsRunning(0)
{
}
AudioPostProcessor::EcnsThread::~EcnsThread()
{
}
int AudioPostProcessor::EcnsThread::readData(int fd, void * buffer, int bytes, int rate,
AudioPostProcessor * pp)
{
LOGV("%s: read %d bytes at %d rate", __FUNCTION__, bytes, rate);
Mutex::Autolock lock(mEcnsReadLock);
mProcessor = pp;
mFd = fd;
mClientBuf = buffer;
mReadSize = bytes;
mRate = rate;
if (!mIsRunning) {
LOGD("Create (run) the ECNS thread");
run("AudioPostProcessor::EcnsThread", ANDROID_PRIORITY_HIGHEST);
mIsRunning = true;
}
if (mEcnsReadCond.waitRelative(mEcnsReadLock, seconds(1)) != NO_ERROR) {
LOGE("%s: ECNS thread is stalled.", __FUNCTION__);
mClientBuf = 0;
return -1;
}
return bytes;
}
bool AudioPostProcessor::EcnsThread::threadLoop()
{
#ifdef DEBUG_TIMING
int count = 0;
int small_jitter = 0;
int medium_jitter = 0;
int large_jitter = 0;
#endif
ssize_t ret1 = 0, ret2;
struct timeval tv1, tv2;
int usecs;
bool half_done = false;
int ecnsStatus = 0;
LOGD("%s: Enter thread loop size %d rate %d", __FUNCTION__,
mReadSize, mRate);
mReadBuf = (int16_t *) malloc(mReadSize);
if (!mReadBuf)
goto error;
while (!exitPending() && ecnsStatus != -1) {
GETTIMEOFDAY(&mtv1, NULL);
if (!half_done)
ret1 = ::read(mFd, mReadBuf, mReadSize/2);
if(exitPending())
goto error;
GETTIMEOFDAY(&mtv2, NULL);
ret2 = ::read(mFd, (char *)mReadBuf+mReadSize/2, mReadSize/2);
if(exitPending())
goto error;
if (ret1 <= 0 || ret2 <= 0) {
LOGE("%s: Problem reading.", __FUNCTION__);
goto error;
}
GETTIMEOFDAY(&mtv3, NULL);
mEcnsReadLock.lock();
ecnsStatus = mProcessor->applyUplinkEcns(mReadBuf, mReadSize, mRate);
if (mClientBuf && mReadSize) {
// Give the buffer to the client.
memcpy(mClientBuf, mReadBuf, mReadSize);
// Avoid read overflow by reading before signaling the similar-priority read thread.
ret1 = ::read(mFd, mReadBuf, mReadSize/2);
half_done = true;
GETTIMEOFDAY(&mtv7, NULL);
mEcnsReadCond.signal();
mClientBuf = 0;
} else {
half_done = false;
LOGV("%s: Read overflow (ECNS sanity preserved)", __FUNCTION__);
}
mEcnsReadLock.unlock();
GETTIMEOFDAY(&mtv8, NULL);
#ifdef DEBUG_TIMING
count++;
tv1.tv_sec = mtv1.tv_sec;
tv1.tv_usec = mtv1.tv_usec;
tv2.tv_sec = mtv8.tv_sec;
tv2.tv_usec = mtv8.tv_usec;
// Compare first and last timestamps
tv2.tv_sec -= tv1.tv_sec;
if(tv2.tv_usec < tv1.tv_usec) {
tv2.tv_sec--;
tv2.tv_usec = 1000000 + tv2.tv_usec - tv1.tv_usec;
} else {
tv2.tv_usec = tv2.tv_usec - tv1.tv_usec;
}
usecs = tv2.tv_usec + tv2.tv_sec*1000000;
if (usecs > 25000) {
if (usecs > 30000)
large_jitter++;
else
medium_jitter++;
LOGD("jitter: usecs = %d should be 20000", usecs);
LOGD("Point 1 ( start): %03d.%06d:", (int)mtv1.tv_sec, (int)mtv1.tv_usec);
LOGD("Point 2 (after read1): %03d.%06d:", (int)mtv2.tv_sec, (int)mtv2.tv_usec);
LOGD("Point 3 (after read2): %03d.%06d:", (int)mtv3.tv_sec, (int)mtv3.tv_usec);
LOGD("Point 4 (before ECNS): %03d.%06d:", (int)mtv4.tv_sec, (int)mtv4.tv_usec);
LOGD("Point 5 (after ECNS): %03d.%06d:", (int)mtv5.tv_sec, (int)mtv5.tv_usec);
LOGD("Point 6 (after write): %03d.%06d:", (int)mtv6.tv_sec, (int)mtv6.tv_usec);
LOGD("Point 7 (before sgnl): %03d.%06d:", (int)mtv7.tv_sec, (int)mtv7.tv_usec);
LOGD("Point 8 (after unlck): %03d.%06d:", (int)mtv8.tv_sec, (int)mtv8.tv_usec);
} else if ((usecs > 22000) || (usecs < 18000)) {
small_jitter++;
LOGD("jitter: usecs = %d should be 20000", usecs);
}
if ((count % 500)== 0) {
LOGD("====================================== Statistics ===========================");
LOGD(" After %d seconds:", count/50);
LOGD(" Small jitters- %d (%02.5f%%)", small_jitter, ((float)small_jitter)*100/count);
LOGD(" Medium jitters- %d (%02.5f%%)", medium_jitter, ((float)medium_jitter)*100/count);
LOGD(" Large jitters- %d (%02.5f%%)", large_jitter, ((float)large_jitter)*100/count);
LOGD("=============================================================================");
}
#endif
}
error:
LOGD("%s: Exit thread loop, enabled = %d", __FUNCTION__,mProcessor->isEcnsEnabled());
if (mReadBuf) {
free (mReadBuf);
mReadBuf = 0;
}
mIsRunning = false;
return false;
}
} //namespace android