/*
|
* Copyright (C) 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.
|
*/
|
|
#include "sles_allinclusive.h"
|
#include "android_prompts.h"
|
#include "channels.h"
|
|
#include <utils/String16.h>
|
|
#include <system/audio.h>
|
#include <SLES/OpenSLES_Android.h>
|
|
#include <android_runtime/AndroidRuntime.h>
|
|
#define KEY_RECORDING_SOURCE_PARAMSIZE sizeof(SLuint32)
|
#define KEY_RECORDING_PRESET_PARAMSIZE sizeof(SLuint32)
|
#define KEY_PERFORMANCE_MODE_PARAMSIZE sizeof(SLuint32)
|
|
//-----------------------------------------------------------------------------
|
// Internal utility functions
|
//----------------------------
|
|
SLresult audioRecorder_setPreset(CAudioRecorder* ar, SLuint32 recordPreset) {
|
SLresult result = SL_RESULT_SUCCESS;
|
|
audio_source_t newRecordSource = AUDIO_SOURCE_DEFAULT;
|
switch (recordPreset) {
|
case SL_ANDROID_RECORDING_PRESET_GENERIC:
|
newRecordSource = AUDIO_SOURCE_DEFAULT;
|
break;
|
case SL_ANDROID_RECORDING_PRESET_CAMCORDER:
|
newRecordSource = AUDIO_SOURCE_CAMCORDER;
|
break;
|
case SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION:
|
newRecordSource = AUDIO_SOURCE_VOICE_RECOGNITION;
|
break;
|
case SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION:
|
newRecordSource = AUDIO_SOURCE_VOICE_COMMUNICATION;
|
break;
|
case SL_ANDROID_RECORDING_PRESET_UNPROCESSED:
|
newRecordSource = AUDIO_SOURCE_UNPROCESSED;
|
break;
|
case SL_ANDROID_RECORDING_PRESET_NONE:
|
// it is an error to set preset "none"
|
default:
|
SL_LOGE(ERROR_RECORDERPRESET_SET_UNKNOWN_PRESET);
|
result = SL_RESULT_PARAMETER_INVALID;
|
}
|
|
// recording preset needs to be set before the object is realized
|
// (ap->mAudioRecord is supposed to be 0 until then)
|
if (SL_OBJECT_STATE_UNREALIZED != ar->mObject.mState) {
|
SL_LOGE(ERROR_RECORDERPRESET_REALIZED);
|
result = SL_RESULT_PRECONDITIONS_VIOLATED;
|
} else {
|
ar->mRecordSource = newRecordSource;
|
}
|
|
return result;
|
}
|
|
|
//-----------------------------------------------------------------------------
|
SLresult audioRecorder_setPerformanceMode(CAudioRecorder* ar, SLuint32 mode) {
|
SLresult result = SL_RESULT_SUCCESS;
|
SL_LOGV("performance mode set to %d", mode);
|
|
SLuint32 perfMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
|
switch (mode) {
|
case SL_ANDROID_PERFORMANCE_LATENCY:
|
perfMode = ANDROID_PERFORMANCE_MODE_LATENCY;
|
break;
|
case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:
|
perfMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
|
break;
|
case SL_ANDROID_PERFORMANCE_NONE:
|
perfMode = ANDROID_PERFORMANCE_MODE_NONE;
|
break;
|
case SL_ANDROID_PERFORMANCE_POWER_SAVING:
|
perfMode = ANDROID_PERFORMANCE_MODE_POWER_SAVING;
|
break;
|
default:
|
SL_LOGE(ERROR_CONFIG_PERF_MODE_UNKNOWN);
|
result = SL_RESULT_PARAMETER_INVALID;
|
break;
|
}
|
|
// performance mode needs to be set before the object is realized
|
// (ar->mAudioRecord is supposed to be NULL until then)
|
if (SL_OBJECT_STATE_UNREALIZED != ar->mObject.mState) {
|
SL_LOGE(ERROR_CONFIG_PERF_MODE_REALIZED);
|
result = SL_RESULT_PRECONDITIONS_VIOLATED;
|
} else {
|
ar->mPerformanceMode = perfMode;
|
}
|
|
return result;
|
}
|
|
|
SLresult audioRecorder_getPreset(CAudioRecorder* ar, SLuint32* pPreset) {
|
SLresult result = SL_RESULT_SUCCESS;
|
|
switch (ar->mRecordSource) {
|
case AUDIO_SOURCE_DEFAULT:
|
case AUDIO_SOURCE_MIC:
|
*pPreset = SL_ANDROID_RECORDING_PRESET_GENERIC;
|
break;
|
case AUDIO_SOURCE_VOICE_UPLINK:
|
case AUDIO_SOURCE_VOICE_DOWNLINK:
|
case AUDIO_SOURCE_VOICE_CALL:
|
*pPreset = SL_ANDROID_RECORDING_PRESET_NONE;
|
break;
|
case AUDIO_SOURCE_VOICE_RECOGNITION:
|
*pPreset = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
|
break;
|
case AUDIO_SOURCE_CAMCORDER:
|
*pPreset = SL_ANDROID_RECORDING_PRESET_CAMCORDER;
|
break;
|
case AUDIO_SOURCE_VOICE_COMMUNICATION:
|
*pPreset = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
|
break;
|
case AUDIO_SOURCE_UNPROCESSED:
|
*pPreset = SL_ANDROID_RECORDING_PRESET_UNPROCESSED;
|
break;
|
default:
|
*pPreset = SL_ANDROID_RECORDING_PRESET_NONE;
|
result = SL_RESULT_INTERNAL_ERROR;
|
break;
|
}
|
|
return result;
|
}
|
|
|
//-----------------------------------------------------------------------------
|
SLresult audioRecorder_getPerformanceMode(CAudioRecorder* ar, SLuint32 *pMode) {
|
SLresult result = SL_RESULT_SUCCESS;
|
|
switch (ar->mPerformanceMode) {
|
case ANDROID_PERFORMANCE_MODE_LATENCY:
|
*pMode = SL_ANDROID_PERFORMANCE_LATENCY;
|
break;
|
case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
|
*pMode = SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
|
break;
|
case ANDROID_PERFORMANCE_MODE_NONE:
|
*pMode = SL_ANDROID_PERFORMANCE_NONE;
|
break;
|
case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
|
*pMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
|
break;
|
default:
|
result = SL_RESULT_INTERNAL_ERROR;
|
*pMode = SL_ANDROID_PERFORMANCE_LATENCY;
|
break;
|
}
|
|
return result;
|
}
|
|
|
void audioRecorder_handleNewPos_lockRecord(CAudioRecorder* ar) {
|
//SL_LOGV("received event EVENT_NEW_POS from AudioRecord");
|
slRecordCallback callback = NULL;
|
void* callbackPContext = NULL;
|
|
interface_lock_shared(&ar->mRecord);
|
callback = ar->mRecord.mCallback;
|
callbackPContext = ar->mRecord.mContext;
|
interface_unlock_shared(&ar->mRecord);
|
|
if (NULL != callback) {
|
// getting this event implies SL_RECORDEVENT_HEADATNEWPOS was set in the event mask
|
(*callback)(&ar->mRecord.mItf, callbackPContext, SL_RECORDEVENT_HEADATNEWPOS);
|
}
|
}
|
|
|
void audioRecorder_handleMarker_lockRecord(CAudioRecorder* ar) {
|
//SL_LOGV("received event EVENT_MARKER from AudioRecord");
|
slRecordCallback callback = NULL;
|
void* callbackPContext = NULL;
|
|
interface_lock_shared(&ar->mRecord);
|
callback = ar->mRecord.mCallback;
|
callbackPContext = ar->mRecord.mContext;
|
interface_unlock_shared(&ar->mRecord);
|
|
if (NULL != callback) {
|
// getting this event implies SL_RECORDEVENT_HEADATMARKER was set in the event mask
|
(*callback)(&ar->mRecord.mItf, callbackPContext, SL_RECORDEVENT_HEADATMARKER);
|
}
|
}
|
|
|
void audioRecorder_handleOverrun_lockRecord(CAudioRecorder* ar) {
|
//SL_LOGV("received event EVENT_OVERRUN from AudioRecord");
|
slRecordCallback callback = NULL;
|
void* callbackPContext = NULL;
|
|
interface_lock_shared(&ar->mRecord);
|
if (ar->mRecord.mCallbackEventsMask & SL_RECORDEVENT_HEADSTALLED) {
|
callback = ar->mRecord.mCallback;
|
callbackPContext = ar->mRecord.mContext;
|
}
|
interface_unlock_shared(&ar->mRecord);
|
|
if (NULL != callback) {
|
(*callback)(&ar->mRecord.mItf, callbackPContext, SL_RECORDEVENT_HEADSTALLED);
|
}
|
}
|
|
//-----------------------------------------------------------------------------
|
SLresult android_audioRecorder_checkSourceSink(CAudioRecorder* ar) {
|
|
const SLDataSource *pAudioSrc = &ar->mDataSource.u.mSource;
|
const SLDataSink *pAudioSnk = &ar->mDataSink.u.mSink;
|
|
const SLuint32 sinkLocatorType = *(SLuint32 *)pAudioSnk->pLocator;
|
const SLuint32 sinkFormatType = *(SLuint32 *)pAudioSnk->pFormat;
|
|
const SLuint32 *df_representation = NULL; // pointer to representation field, if it exists
|
|
// sink must be an Android simple buffer queue with PCM data format
|
switch (sinkLocatorType) {
|
case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE: {
|
switch (sinkFormatType) {
|
case SL_ANDROID_DATAFORMAT_PCM_EX: {
|
const SLAndroidDataFormat_PCM_EX *df_pcm =
|
(SLAndroidDataFormat_PCM_EX *) pAudioSnk->pFormat;
|
// checkDataFormat() already checked representation
|
df_representation = &df_pcm->representation;
|
} // SL_ANDROID_DATAFORMAT_PCM_EX - fall through to next test.
|
FALLTHROUGH_INTENDED;
|
case SL_DATAFORMAT_PCM: {
|
const SLDataFormat_PCM *df_pcm = (const SLDataFormat_PCM *) pAudioSnk->pFormat;
|
// checkDataFormat already checked sample rate, channels, and mask
|
ar->mNumChannels = df_pcm->numChannels;
|
|
if (df_pcm->endianness != ar->mObject.mEngine->mEngine.mNativeEndianness) {
|
SL_LOGE("Cannot create audio recorder: unsupported byte order %u",
|
df_pcm->endianness);
|
return SL_RESULT_CONTENT_UNSUPPORTED;
|
}
|
|
ar->mSampleRateMilliHz = df_pcm->samplesPerSec; // Note: bad field name in SL ES
|
SL_LOGV("AudioRecorder requested sample rate = %u mHz, %u channel(s)",
|
ar->mSampleRateMilliHz, ar->mNumChannels);
|
|
// we don't support container size != sample depth
|
if (df_pcm->containerSize != df_pcm->bitsPerSample) {
|
SL_LOGE("Cannot create audio recorder: unsupported container size %u bits for "
|
"sample depth %u bits",
|
df_pcm->containerSize, (SLuint32)df_pcm->bitsPerSample);
|
return SL_RESULT_CONTENT_UNSUPPORTED;
|
}
|
|
} break;
|
default:
|
SL_LOGE(ERROR_RECORDER_SINK_FORMAT_MUST_BE_PCM);
|
return SL_RESULT_PARAMETER_INVALID;
|
} // switch (sourceFormatType)
|
} break; // case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
|
default:
|
SL_LOGE(ERROR_RECORDER_SINK_MUST_BE_ANDROIDSIMPLEBUFFERQUEUE);
|
return SL_RESULT_PARAMETER_INVALID;
|
} // switch (sourceLocatorType)
|
|
// Source check:
|
// only input device sources are supported
|
// check it's an IO device
|
if (SL_DATALOCATOR_IODEVICE != *(SLuint32 *)pAudioSrc->pLocator) {
|
SL_LOGE(ERROR_RECORDER_SOURCE_MUST_BE_IODEVICE);
|
return SL_RESULT_PARAMETER_INVALID;
|
} else {
|
|
// check it's an input device
|
SLDataLocator_IODevice *dl_iod = (SLDataLocator_IODevice *) pAudioSrc->pLocator;
|
if (SL_IODEVICE_AUDIOINPUT != dl_iod->deviceType) {
|
SL_LOGE(ERROR_RECORDER_IODEVICE_MUST_BE_AUDIOINPUT);
|
return SL_RESULT_PARAMETER_INVALID;
|
}
|
|
// check it's the default input device, others aren't supported here
|
if (SL_DEFAULTDEVICEID_AUDIOINPUT != dl_iod->deviceID) {
|
SL_LOGE(ERROR_RECORDER_INPUT_ID_MUST_BE_DEFAULT);
|
return SL_RESULT_PARAMETER_INVALID;
|
}
|
}
|
|
return SL_RESULT_SUCCESS;
|
}
|
//-----------------------------------------------------------------------------
|
static void audioRecorder_callback(int event, void* user, void *info) {
|
//SL_LOGV("audioRecorder_callback(%d, %p, %p) entering", event, user, info);
|
|
CAudioRecorder *ar = (CAudioRecorder *)user;
|
|
if (!android::CallbackProtector::enterCbIfOk(ar->mCallbackProtector)) {
|
// it is not safe to enter the callback (the track is about to go away)
|
return;
|
}
|
|
void * callbackPContext = NULL;
|
|
switch (event) {
|
case android::AudioRecord::EVENT_MORE_DATA: {
|
slBufferQueueCallback callback = NULL;
|
android::AudioRecord::Buffer* pBuff = (android::AudioRecord::Buffer*)info;
|
|
// push data to the buffer queue
|
interface_lock_exclusive(&ar->mBufferQueue);
|
|
if (ar->mBufferQueue.mState.count != 0) {
|
assert(ar->mBufferQueue.mFront != ar->mBufferQueue.mRear);
|
|
BufferHeader *oldFront = ar->mBufferQueue.mFront;
|
BufferHeader *newFront = &oldFront[1];
|
|
size_t availSink = oldFront->mSize - ar->mBufferQueue.mSizeConsumed;
|
size_t availSource = pBuff->size;
|
size_t bytesToCopy = availSink < availSource ? availSink : availSource;
|
void *pDest = (char *)oldFront->mBuffer + ar->mBufferQueue.mSizeConsumed;
|
memcpy(pDest, pBuff->raw, bytesToCopy);
|
|
if (bytesToCopy < availSink) {
|
// can't consume the whole or rest of the buffer in one shot
|
ar->mBufferQueue.mSizeConsumed += availSource;
|
// pBuff->size is already equal to bytesToCopy in this case
|
} else {
|
// finish pushing the buffer or push the buffer in one shot
|
pBuff->size = bytesToCopy;
|
ar->mBufferQueue.mSizeConsumed = 0;
|
if (newFront == &ar->mBufferQueue.mArray[ar->mBufferQueue.mNumBuffers + 1]) {
|
newFront = ar->mBufferQueue.mArray;
|
}
|
ar->mBufferQueue.mFront = newFront;
|
|
ar->mBufferQueue.mState.count--;
|
ar->mBufferQueue.mState.playIndex++;
|
|
// data has been copied to the buffer, and the buffer queue state has been updated
|
// we will notify the client if applicable
|
callback = ar->mBufferQueue.mCallback;
|
// save callback data
|
callbackPContext = ar->mBufferQueue.mContext;
|
}
|
} else { // empty queue
|
// no destination to push the data
|
pBuff->size = 0;
|
}
|
|
interface_unlock_exclusive(&ar->mBufferQueue);
|
|
// notify client
|
if (NULL != callback) {
|
(*callback)(&ar->mBufferQueue.mItf, callbackPContext);
|
}
|
}
|
break;
|
|
case android::AudioRecord::EVENT_OVERRUN:
|
audioRecorder_handleOverrun_lockRecord(ar);
|
break;
|
|
case android::AudioRecord::EVENT_MARKER:
|
audioRecorder_handleMarker_lockRecord(ar);
|
break;
|
|
case android::AudioRecord::EVENT_NEW_POS:
|
audioRecorder_handleNewPos_lockRecord(ar);
|
break;
|
|
case android::AudioRecord::EVENT_NEW_IAUDIORECORD:
|
// ignore for now
|
break;
|
|
default:
|
SL_LOGE("Encountered unknown AudioRecord event %d for CAudioRecord %p", event, ar);
|
break;
|
}
|
|
ar->mCallbackProtector->exitCb();
|
}
|
|
|
//-----------------------------------------------------------------------------
|
SLresult android_audioRecorder_create(CAudioRecorder* ar) {
|
SL_LOGV("android_audioRecorder_create(%p) entering", ar);
|
|
const SLDataSource *pAudioSrc = &ar->mDataSource.u.mSource;
|
const SLDataSink *pAudioSnk = &ar->mDataSink.u.mSink;
|
SLresult result = SL_RESULT_SUCCESS;
|
|
const SLuint32 sourceLocatorType = *(SLuint32 *)pAudioSrc->pLocator;
|
const SLuint32 sinkLocatorType = *(SLuint32 *)pAudioSnk->pLocator;
|
|
// the following platform-independent fields have been initialized in CreateAudioRecorder()
|
// ar->mNumChannels
|
// ar->mSampleRateMilliHz
|
|
if ((SL_DATALOCATOR_IODEVICE == sourceLocatorType) &&
|
(SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE == sinkLocatorType)) {
|
// microphone to simple buffer queue
|
ar->mAndroidObjType = AUDIORECORDER_FROM_MIC_TO_PCM_BUFFERQUEUE;
|
ar->mAudioRecord.clear();
|
ar->mCallbackProtector = new android::CallbackProtector();
|
ar->mRecordSource = AUDIO_SOURCE_DEFAULT;
|
ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_DEFAULT;
|
} else {
|
result = SL_RESULT_CONTENT_UNSUPPORTED;
|
}
|
|
return result;
|
}
|
|
|
//-----------------------------------------------------------------------------
|
SLresult android_audioRecorder_setConfig(CAudioRecorder* ar, const SLchar *configKey,
|
const void *pConfigValue, SLuint32 valueSize) {
|
|
SLresult result;
|
|
assert(NULL != ar && NULL != configKey && NULL != pConfigValue);
|
if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_RECORDING_PRESET) == 0) {
|
|
// recording preset
|
if (KEY_RECORDING_PRESET_PARAMSIZE > valueSize) {
|
SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
|
result = SL_RESULT_BUFFER_INSUFFICIENT;
|
} else {
|
result = audioRecorder_setPreset(ar, *(SLuint32*)pConfigValue);
|
}
|
|
} else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {
|
|
// performance mode
|
if (KEY_PERFORMANCE_MODE_PARAMSIZE > valueSize) {
|
SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
|
result = SL_RESULT_BUFFER_INSUFFICIENT;
|
} else {
|
result = audioRecorder_setPerformanceMode(ar, *(SLuint32*)pConfigValue);
|
}
|
} else {
|
SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
|
result = SL_RESULT_PARAMETER_INVALID;
|
}
|
|
return result;
|
}
|
|
|
//-----------------------------------------------------------------------------
|
SLresult android_audioRecorder_getConfig(CAudioRecorder* ar, const SLchar *configKey,
|
SLuint32* pValueSize, void *pConfigValue) {
|
|
SLresult result;
|
|
assert(NULL != ar && NULL != configKey && NULL != pValueSize);
|
if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_RECORDING_PRESET) == 0) {
|
|
// recording preset
|
if (NULL == pConfigValue) {
|
result = SL_RESULT_SUCCESS;
|
} else if (KEY_RECORDING_PRESET_PARAMSIZE > *pValueSize) {
|
SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
|
result = SL_RESULT_BUFFER_INSUFFICIENT;
|
} else {
|
result = audioRecorder_getPreset(ar, (SLuint32*)pConfigValue);
|
}
|
*pValueSize = KEY_RECORDING_PRESET_PARAMSIZE;
|
|
} else if (strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_PERFORMANCE_MODE) == 0) {
|
|
// performance mode
|
if (NULL == pConfigValue) {
|
result = SL_RESULT_SUCCESS;
|
} else if (KEY_PERFORMANCE_MODE_PARAMSIZE > *pValueSize) {
|
SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW);
|
result = SL_RESULT_BUFFER_INSUFFICIENT;
|
} else {
|
result = audioRecorder_getPerformanceMode(ar, (SLuint32*)pConfigValue);
|
}
|
*pValueSize = KEY_PERFORMANCE_MODE_PARAMSIZE;
|
|
} else {
|
SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY);
|
result = SL_RESULT_PARAMETER_INVALID;
|
}
|
|
return result;
|
}
|
|
// Called from android_audioRecorder_realize for a PCM buffer queue recorder before creating the
|
// AudioRecord to determine which performance modes are allowed based on effect interfaces present
|
static void checkAndSetPerformanceModePre(CAudioRecorder* ar)
|
{
|
SLuint32 allowedModes = ANDROID_PERFORMANCE_MODE_ALL;
|
assert(ar->mAndroidObjType == AUDIORECORDER_FROM_MIC_TO_PCM_BUFFERQUEUE);
|
|
// no need to check the buffer queue size, application side
|
// double-buffering (and more) is not a requirement for using fast tracks
|
|
// Check a blacklist of interfaces that are incompatible with fast tracks.
|
// The alternative, to check a whitelist of compatible interfaces, is
|
// more maintainable but is too slow. As a compromise, in a debug build
|
// we use both methods and warn if they produce different results.
|
// In release builds, we only use the blacklist method.
|
// If a blacklisted interface is added after realization using
|
// DynamicInterfaceManagement::AddInterface,
|
// then this won't be detected but the interface will be ineffective.
|
static const unsigned blacklist[] = {
|
MPH_ANDROIDACOUSTICECHOCANCELLATION,
|
MPH_ANDROIDAUTOMATICGAINCONTROL,
|
MPH_ANDROIDNOISESUPPRESSION,
|
MPH_ANDROIDEFFECT,
|
// FIXME The problem with a blacklist is remembering to add new interfaces here
|
};
|
|
for (unsigned i = 0; i < sizeof(blacklist)/sizeof(blacklist[0]); ++i) {
|
if (IsInterfaceInitialized(&ar->mObject, blacklist[i])) {
|
uint32_t flags = 0;
|
|
allowedModes &= ~ANDROID_PERFORMANCE_MODE_LATENCY;
|
|
// if generic effect interface is used we don't know which effect will be used and
|
// disable all low latency performance modes
|
if (blacklist[i] != MPH_ANDROIDEFFECT) {
|
switch (blacklist[i]) {
|
case MPH_ANDROIDACOUSTICECHOCANCELLATION:
|
SL_LOGV("checkAndSetPerformanceModePre found AEC name %s",
|
ar->mAcousticEchoCancellation.mAECDescriptor.name);
|
flags = ar->mAcousticEchoCancellation.mAECDescriptor.flags;
|
break;
|
case MPH_ANDROIDAUTOMATICGAINCONTROL:
|
SL_LOGV("checkAndSetPerformanceModePre found AGC name %s",
|
ar->mAutomaticGainControl.mAGCDescriptor.name);
|
flags = ar->mAutomaticGainControl.mAGCDescriptor.flags;
|
break;
|
case MPH_ANDROIDNOISESUPPRESSION:
|
SL_LOGV("checkAndSetPerformanceModePre found NS name %s",
|
ar->mNoiseSuppression.mNSDescriptor.name);
|
flags = ar->mNoiseSuppression.mNSDescriptor.flags;
|
break;
|
default:
|
break;
|
}
|
}
|
if ((flags & EFFECT_FLAG_HW_ACC_TUNNEL) == 0) {
|
allowedModes &= ~ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
|
break;
|
}
|
}
|
}
|
#if LOG_NDEBUG == 0
|
bool blacklistResult = (
|
(allowedModes &
|
(ANDROID_PERFORMANCE_MODE_LATENCY|ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS)) != 0);
|
bool whitelistResult = true;
|
static const unsigned whitelist[] = {
|
MPH_BUFFERQUEUE,
|
MPH_DYNAMICINTERFACEMANAGEMENT,
|
MPH_OBJECT,
|
MPH_RECORD,
|
MPH_ANDROIDCONFIGURATION,
|
MPH_ANDROIDSIMPLEBUFFERQUEUE,
|
};
|
for (unsigned mph = MPH_MIN; mph < MPH_MAX; ++mph) {
|
for (unsigned i = 0; i < sizeof(whitelist)/sizeof(whitelist[0]); ++i) {
|
if (mph == whitelist[i]) {
|
goto compatible;
|
}
|
}
|
if (IsInterfaceInitialized(&ar->mObject, mph)) {
|
whitelistResult = false;
|
break;
|
}
|
compatible: ;
|
}
|
if (whitelistResult != blacklistResult) {
|
SL_LOGW("whitelistResult != blacklistResult");
|
}
|
#endif
|
if (ar->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY) {
|
if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY) == 0) {
|
ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
|
}
|
}
|
if (ar->mPerformanceMode == ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) {
|
if ((allowedModes & ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) == 0) {
|
ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
|
}
|
}
|
}
|
|
// Called from android_audioRecorder_realize for a PCM buffer queue recorder after creating the
|
// AudioRecord to adjust performance mode based on actual input flags
|
static void checkAndSetPerformanceModePost(CAudioRecorder* ar)
|
{
|
audio_input_flags_t flags = ar->mAudioRecord->getFlags();
|
switch (ar->mPerformanceMode) {
|
case ANDROID_PERFORMANCE_MODE_LATENCY:
|
if ((flags & (AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)) ==
|
(AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)) {
|
break;
|
}
|
ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS;
|
FALLTHROUGH_INTENDED;
|
case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
|
if ((flags & AUDIO_INPUT_FLAG_FAST) == 0) {
|
ar->mPerformanceMode = ANDROID_PERFORMANCE_MODE_NONE;
|
}
|
break;
|
case ANDROID_PERFORMANCE_MODE_NONE:
|
default:
|
break;
|
}
|
}
|
//-----------------------------------------------------------------------------
|
SLresult android_audioRecorder_realize(CAudioRecorder* ar, SLboolean async) {
|
SL_LOGV("android_audioRecorder_realize(%p) entering", ar);
|
|
SLresult result = SL_RESULT_SUCCESS;
|
|
// already checked in created and checkSourceSink
|
assert(ar->mDataSink.mLocator.mLocatorType == SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE);
|
|
const SLDataFormat_PCM *df_pcm = &ar->mDataSink.mFormat.mPCM;
|
|
// the following platform-independent fields have been initialized in CreateAudioRecorder()
|
// ar->mNumChannels
|
// ar->mSampleRateMilliHz
|
|
uint32_t sampleRate = sles_to_android_sampleRate(df_pcm->samplesPerSec);
|
|
checkAndSetPerformanceModePre(ar);
|
|
audio_input_flags_t policy;
|
switch (ar->mPerformanceMode) {
|
case ANDROID_PERFORMANCE_MODE_NONE:
|
case ANDROID_PERFORMANCE_MODE_POWER_SAVING:
|
policy = AUDIO_INPUT_FLAG_NONE;
|
break;
|
case ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS:
|
policy = AUDIO_INPUT_FLAG_FAST;
|
break;
|
case ANDROID_PERFORMANCE_MODE_LATENCY:
|
default:
|
policy = (audio_input_flags_t)(AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW);
|
break;
|
}
|
|
SL_LOGV("Audio Record format: %dch(0x%x), %dbit, %dKHz",
|
df_pcm->numChannels,
|
df_pcm->channelMask,
|
df_pcm->bitsPerSample,
|
df_pcm->samplesPerSec / 1000000);
|
|
// note that df_pcm->channelMask has already been validated during object creation.
|
audio_channel_mask_t channelMask = sles_to_audio_input_channel_mask(df_pcm->channelMask);
|
|
// To maintain backward compatibility with previous releases, ignore
|
// channel masks that are not indexed.
|
if (channelMask == AUDIO_CHANNEL_INVALID
|
|| audio_channel_mask_get_representation(channelMask)
|
== AUDIO_CHANNEL_REPRESENTATION_POSITION) {
|
channelMask = audio_channel_in_mask_from_count(df_pcm->numChannels);
|
SL_LOGI("Emulating old channel mask behavior "
|
"(ignoring positional mask %#x, using default mask %#x based on "
|
"channel count of %d)", df_pcm->channelMask, channelMask,
|
df_pcm->numChannels);
|
}
|
SL_LOGV("SLES channel mask %#x converted to Android mask %#x", df_pcm->channelMask,
|
channelMask);
|
|
// initialize platform-specific CAudioRecorder fields
|
ar->mAudioRecord = new android::AudioRecord(
|
ar->mRecordSource, // source
|
sampleRate, // sample rate in Hertz
|
sles_to_android_sampleFormat(df_pcm), // format
|
channelMask, // channel mask
|
android::String16(), // app ops
|
0, // frameCount
|
audioRecorder_callback,// callback_t
|
(void*)ar, // user, callback data, here the AudioRecorder
|
0, // notificationFrames
|
AUDIO_SESSION_ALLOCATE,
|
android::AudioRecord::TRANSFER_CALLBACK,
|
// transfer type
|
policy); // audio_input_flags_t
|
|
android::status_t status = ar->mAudioRecord->initCheck();
|
if (android::NO_ERROR != status) {
|
SL_LOGE("android_audioRecorder_realize(%p) error creating AudioRecord object; status %d",
|
ar, status);
|
// FIXME should return a more specific result depending on status
|
result = SL_RESULT_CONTENT_UNSUPPORTED;
|
ar->mAudioRecord.clear();
|
return result;
|
}
|
|
// update performance mode according to actual flags granted to AudioRecord
|
checkAndSetPerformanceModePost(ar);
|
|
// If there is a JavaAudioRoutingProxy associated with this recorder, hook it up...
|
JNIEnv* j_env = NULL;
|
jclass clsAudioRecord = NULL;
|
jmethodID midRoutingProxy_connect = NULL;
|
if (ar->mAndroidConfiguration.mRoutingProxy != NULL &&
|
(j_env = android::AndroidRuntime::getJNIEnv()) != NULL &&
|
(clsAudioRecord = j_env->FindClass("android/media/AudioRecord")) != NULL &&
|
(midRoutingProxy_connect =
|
j_env->GetMethodID(clsAudioRecord, "deferred_connect", "(J)V")) != NULL) {
|
j_env->ExceptionClear();
|
j_env->CallVoidMethod(ar->mAndroidConfiguration.mRoutingProxy,
|
midRoutingProxy_connect,
|
ar->mAudioRecord.get());
|
if (j_env->ExceptionCheck()) {
|
SL_LOGE("Java exception releasing recorder routing object.");
|
result = SL_RESULT_INTERNAL_ERROR;
|
ar->mAudioRecord.clear();
|
return result;
|
}
|
}
|
|
if (ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY) {
|
audio_session_t sessionId = ar->mAudioRecord->getSessionId();
|
// initialize AEC
|
effect_descriptor_t *descriptor = &ar->mAcousticEchoCancellation.mAECDescriptor;
|
if (memcmp(SL_IID_ANDROIDACOUSTICECHOCANCELLATION, &descriptor->type,
|
sizeof(effect_uuid_t)) == 0) {
|
if ((ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) ||
|
(descriptor->flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
|
SL_LOGV("Need to initialize AEC for AudioRecorder=%p", ar);
|
android_aec_init(sessionId, &ar->mAcousticEchoCancellation);
|
}
|
}
|
|
// initialize AGC
|
descriptor = &ar->mAutomaticGainControl.mAGCDescriptor;
|
if (memcmp(SL_IID_ANDROIDAUTOMATICGAINCONTROL, &descriptor->type,
|
sizeof(effect_uuid_t)) == 0) {
|
if ((ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) ||
|
(descriptor->flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
|
SL_LOGV("Need to initialize AGC for AudioRecorder=%p", ar);
|
android_agc_init(sessionId, &ar->mAutomaticGainControl);
|
}
|
}
|
|
// initialize NS
|
descriptor = &ar->mNoiseSuppression.mNSDescriptor;
|
if (memcmp(SL_IID_ANDROIDNOISESUPPRESSION, &descriptor->type,
|
sizeof(effect_uuid_t)) == 0) {
|
if ((ar->mPerformanceMode != ANDROID_PERFORMANCE_MODE_LATENCY_EFFECTS) ||
|
(descriptor->flags & EFFECT_FLAG_HW_ACC_TUNNEL)) {
|
SL_LOGV("Need to initialize NS for AudioRecorder=%p", ar);
|
android_ns_init(sessionId, &ar->mNoiseSuppression);
|
}
|
}
|
}
|
|
return result;
|
}
|
|
|
//-----------------------------------------------------------------------------
|
/**
|
* Called with a lock on AudioRecorder, and blocks until safe to destroy
|
*/
|
void android_audioRecorder_preDestroy(CAudioRecorder* ar) {
|
object_unlock_exclusive(&ar->mObject);
|
if (ar->mCallbackProtector != 0) {
|
ar->mCallbackProtector->requestCbExitAndWait();
|
}
|
object_lock_exclusive(&ar->mObject);
|
}
|
|
|
//-----------------------------------------------------------------------------
|
void android_audioRecorder_destroy(CAudioRecorder* ar) {
|
SL_LOGV("android_audioRecorder_destroy(%p) entering", ar);
|
|
if (ar->mAudioRecord != 0) {
|
ar->mAudioRecord->stop();
|
ar->mAudioRecord.clear();
|
}
|
// explicit destructor
|
ar->mAudioRecord.~sp();
|
ar->mCallbackProtector.~sp();
|
}
|
|
|
//-----------------------------------------------------------------------------
|
void android_audioRecorder_setRecordState(CAudioRecorder* ar, SLuint32 state) {
|
SL_LOGV("android_audioRecorder_setRecordState(%p, %u) entering", ar, state);
|
|
if (ar->mAudioRecord == 0) {
|
return;
|
}
|
|
switch (state) {
|
case SL_RECORDSTATE_STOPPED:
|
ar->mAudioRecord->stop();
|
break;
|
case SL_RECORDSTATE_PAUSED:
|
// Note that pausing is treated like stop as this implementation only records to a buffer
|
// queue, so there is no notion of destination being "opened" or "closed" (See description
|
// of SL_RECORDSTATE in specification)
|
ar->mAudioRecord->stop();
|
break;
|
case SL_RECORDSTATE_RECORDING:
|
ar->mAudioRecord->start();
|
break;
|
default:
|
break;
|
}
|
|
}
|
|
|
//-----------------------------------------------------------------------------
|
void android_audioRecorder_useRecordEventMask(CAudioRecorder *ar) {
|
IRecord *pRecordItf = &ar->mRecord;
|
SLuint32 eventFlags = pRecordItf->mCallbackEventsMask;
|
|
if (ar->mAudioRecord == 0) {
|
return;
|
}
|
|
if ((eventFlags & SL_RECORDEVENT_HEADATMARKER) && (pRecordItf->mMarkerPosition != 0)) {
|
ar->mAudioRecord->setMarkerPosition((uint32_t)((((int64_t)pRecordItf->mMarkerPosition
|
* sles_to_android_sampleRate(ar->mSampleRateMilliHz)))/1000));
|
} else {
|
// clear marker
|
ar->mAudioRecord->setMarkerPosition(0);
|
}
|
|
if (eventFlags & SL_RECORDEVENT_HEADATNEWPOS) {
|
SL_LOGV("pos update period %d", pRecordItf->mPositionUpdatePeriod);
|
ar->mAudioRecord->setPositionUpdatePeriod(
|
(uint32_t)((((int64_t)pRecordItf->mPositionUpdatePeriod
|
* sles_to_android_sampleRate(ar->mSampleRateMilliHz)))/1000));
|
} else {
|
// clear periodic update
|
ar->mAudioRecord->setPositionUpdatePeriod(0);
|
}
|
|
if (eventFlags & SL_RECORDEVENT_HEADATLIMIT) {
|
// FIXME support SL_RECORDEVENT_HEADATLIMIT
|
SL_LOGD("[ FIXME: IRecord_SetCallbackEventsMask(SL_RECORDEVENT_HEADATLIMIT) on an "
|
"SL_OBJECTID_AUDIORECORDER to be implemented ]");
|
}
|
|
if (eventFlags & SL_RECORDEVENT_HEADMOVING) {
|
// FIXME support SL_RECORDEVENT_HEADMOVING
|
SL_LOGD("[ FIXME: IRecord_SetCallbackEventsMask(SL_RECORDEVENT_HEADMOVING) on an "
|
"SL_OBJECTID_AUDIORECORDER to be implemented ]");
|
}
|
|
if (eventFlags & SL_RECORDEVENT_BUFFER_FULL) {
|
// nothing to do for SL_RECORDEVENT_BUFFER_FULL since this will not be encountered on
|
// recording to buffer queues
|
}
|
|
if (eventFlags & SL_RECORDEVENT_HEADSTALLED) {
|
// nothing to do for SL_RECORDEVENT_HEADSTALLED, callback event will be checked against mask
|
// when AudioRecord::EVENT_OVERRUN is encountered
|
|
}
|
|
}
|
|
|
//-----------------------------------------------------------------------------
|
void android_audioRecorder_getPosition(CAudioRecorder *ar, SLmillisecond *pPosMsec) {
|
if ((NULL == ar) || (ar->mAudioRecord == 0)) {
|
*pPosMsec = 0;
|
} else {
|
uint32_t positionInFrames;
|
ar->mAudioRecord->getPosition(&positionInFrames);
|
if (ar->mSampleRateMilliHz == UNKNOWN_SAMPLERATE) {
|
*pPosMsec = 0;
|
} else {
|
*pPosMsec = ((int64_t)positionInFrames * 1000) /
|
sles_to_android_sampleRate(ar->mSampleRateMilliHz);
|
}
|
}
|
}
|