/* * 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" // use this flag to dump all recorded audio into a file //#define MONITOR_RECORDING #ifdef MONITOR_RECORDING #define MONITOR_TARGET "/sdcard/monitor.raw" #include <stdio.h> static FILE* gMonitorFp = NULL; #endif #define KEY_RECORDING_SOURCE_PARAMSIZE sizeof(SLuint32) #define KEY_RECORDING_PRESET_PARAMSIZE sizeof(SLuint32) //----------------------------------------------------------------------------- // Internal utility functions //---------------------------- SLresult audioRecorder_setPreset(CAudioRecorder* ar, SLuint32 recordPreset) { SLresult result = SL_RESULT_SUCCESS; int newRecordSource = android::AUDIO_SOURCE_DEFAULT; switch (recordPreset) { case SL_ANDROID_RECORDING_PRESET_GENERIC: newRecordSource = android::AUDIO_SOURCE_DEFAULT; break; case SL_ANDROID_RECORDING_PRESET_CAMCORDER: newRecordSource = android::AUDIO_SOURCE_CAMCORDER; break; case SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION: newRecordSource = android::AUDIO_SOURCE_VOICE_RECOGNITION; 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 NULL 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_getPreset(CAudioRecorder* ar, SLuint32* pPreset) { SLresult result = SL_RESULT_SUCCESS; switch (ar->mRecordSource) { case android::AUDIO_SOURCE_DEFAULT: case android::AUDIO_SOURCE_MIC: *pPreset = SL_ANDROID_RECORDING_PRESET_GENERIC; break; case android::AUDIO_SOURCE_VOICE_UPLINK: case android::AUDIO_SOURCE_VOICE_DOWNLINK: case android::AUDIO_SOURCE_VOICE_CALL: *pPreset = SL_ANDROID_RECORDING_PRESET_NONE; break; case android::AUDIO_SOURCE_VOICE_RECOGNITION: *pPreset = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; break; case android::AUDIO_SOURCE_CAMCORDER: *pPreset = SL_ANDROID_RECORDING_PRESET_CAMCORDER; break; default: *pPreset = SL_ANDROID_RECORDING_PRESET_NONE; result = SL_RESULT_INTERNAL_ERROR; 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_checkSourceSinkSupport(CAudioRecorder* ar) { const SLDataSource *pAudioSrc = &ar->mDataSource.u.mSource; const SLDataSink *pAudioSnk = &ar->mDataSink.u.mSink; // Sink check: // only buffer queue sinks are supported, regardless of the data source if (SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE != *(SLuint32 *)pAudioSnk->pLocator) { SL_LOGE(ERROR_RECORDER_SINK_MUST_BE_ANDROIDSIMPLEBUFFERQUEUE); return SL_RESULT_PARAMETER_INVALID; } else { // only PCM buffer queues are supported SLuint32 formatType = *(SLuint32 *)pAudioSnk->pFormat; if (SL_DATAFORMAT_PCM == formatType) { SLDataFormat_PCM *df_pcm = (SLDataFormat_PCM *)ar->mDataSink.u.mSink.pFormat; ar->mSampleRateMilliHz = df_pcm->samplesPerSec; ar->mNumChannels = df_pcm->numChannels; SL_LOGV("AudioRecorder requested sample rate = %lumHz, %u channel(s)", ar->mSampleRateMilliHz, ar->mNumChannels); } else { SL_LOGE(ERROR_RECORDER_SINK_FORMAT_MUST_BE_PCM); return SL_RESULT_PARAMETER_INVALID; } } // 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; 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]; // FIXME handle 8bit based on buffer format short *pDest = (short*)((char *)oldFront->mBuffer + ar->mBufferQueue.mSizeConsumed); if (ar->mBufferQueue.mSizeConsumed + pBuff->size < oldFront->mSize) { // can't consume the whole or rest of the buffer in one shot ar->mBufferQueue.mSizeConsumed += pBuff->size; // leave pBuff->size untouched // consume data // FIXME can we avoid holding the lock during the copy? memcpy (pDest, pBuff->i16, pBuff->size); #ifdef MONITOR_RECORDING if (NULL != gMonitorFp) { fwrite(pBuff->i16, pBuff->size, 1, gMonitorFp); } #endif } else { // finish pushing the buffer or push the buffer in one shot pBuff->size = oldFront->mSize - ar->mBufferQueue.mSizeConsumed; 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++; // consume data // FIXME can we avoid holding the lock during the copy? memcpy (pDest, pBuff->i16, pBuff->size); #ifdef MONITOR_RECORDING if (NULL != gMonitorFp) { fwrite(pBuff->i16, pBuff->size, 1, gMonitorFp); } #endif // 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 { // 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; } } //----------------------------------------------------------------------------- SLresult android_audioRecorder_create(CAudioRecorder* ar) { SL_LOGV("android_audioRecorder_create(%p) entering", ar); SLresult result = SL_RESULT_SUCCESS; // the following platform-independent fields have been initialized in CreateAudioRecorder() // ar->mNumChannels // ar->mSampleRateMilliHz ar->mAudioRecord = NULL; ar->mRecordSource = android::AUDIO_SOURCE_DEFAULT; return result; } //----------------------------------------------------------------------------- SLresult android_audioRecorder_setConfig(CAudioRecorder* ar, const SLchar *configKey, const void *pConfigValue, SLuint32 valueSize) { SLresult result = SL_RESULT_SUCCESS; if (NULL == ar) { result = SL_RESULT_INTERNAL_ERROR; } else if (NULL == pConfigValue) { SL_LOGE(ERROR_CONFIG_NULL_PARAM); result = SL_RESULT_PARAMETER_INVALID; } else 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_PARAMETER_INVALID; } else { result = audioRecorder_setPreset(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 = SL_RESULT_SUCCESS; if (NULL == ar) { return SL_RESULT_INTERNAL_ERROR; } else if (NULL == pValueSize) { SL_LOGE(ERROR_CONFIG_NULL_PARAM); result = SL_RESULT_PARAMETER_INVALID; } else if(strcmp((const char*)configKey, (const char*)SL_ANDROID_KEY_RECORDING_PRESET) == 0) { // recording preset if (KEY_RECORDING_PRESET_PARAMSIZE > *pValueSize) { SL_LOGE(ERROR_CONFIG_VALUESIZE_TOO_LOW); result = SL_RESULT_PARAMETER_INVALID; } else { *pValueSize = KEY_RECORDING_PRESET_PARAMSIZE; if (NULL != pConfigValue) { result = audioRecorder_getPreset(ar, (SLuint32*)pConfigValue); } } } else { SL_LOGE(ERROR_CONFIG_UNKNOWN_KEY); result = SL_RESULT_PARAMETER_INVALID; } return result; } //----------------------------------------------------------------------------- SLresult android_audioRecorder_realize(CAudioRecorder* ar, SLboolean async) { SL_LOGV("android_audioRecorder_realize(%p) entering", ar); SLresult result = SL_RESULT_SUCCESS; // initialize platform-independent CAudioRecorder fields if (SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE != ar->mDataSink.mLocator.mLocatorType) { SL_LOGE(ERROR_RECORDER_SINK_MUST_BE_ANDROIDSIMPLEBUFFERQUEUE); return SL_RESULT_CONTENT_UNSUPPORTED; } // the following platform-independent fields have been initialized in CreateAudioRecorder() // ar->mNumChannels // ar->mSampleRateMilliHz SL_LOGV("new AudioRecord %u channels, %lu mHz", ar->mNumChannels, ar->mSampleRateMilliHz); // initialize platform-specific CAudioRecorder fields ar->mAudioRecord = new android::AudioRecord(); ar->mAudioRecord->set(ar->mRecordSource, // source sles_to_android_sampleRate(ar->mSampleRateMilliHz), // sample rate in Hertz android::AudioSystem::PCM_16_BIT, //FIXME use format from buffer queue sink sles_to_android_channelMask(ar->mNumChannels, 0 /*no channel mask*/), // channel config 0, //frameCount min 0, // flags audioRecorder_callback,// callback_t (void*)ar, // user, callback data, here the AudioRecorder 0, // notificationFrames false); // threadCanCallJava, note: this will prevent direct Java // callbacks, but we don't want them in the recording loop if (android::NO_ERROR != ar->mAudioRecord->initCheck()) { SL_LOGE("android_audioRecorder_realize(%p) error creating AudioRecord object", ar); result = SL_RESULT_CONTENT_UNSUPPORTED; } #ifdef MONITOR_RECORDING gMonitorFp = fopen(MONITOR_TARGET, "w"); if (NULL == gMonitorFp) { SL_LOGE("error opening %s", MONITOR_TARGET); } else { SL_LOGE("recording to %s", MONITOR_TARGET); } // SL_LOGE so it's always displayed #endif return result; } //----------------------------------------------------------------------------- void android_audioRecorder_destroy(CAudioRecorder* ar) { SL_LOGV("android_audioRecorder_destroy(%p) entering", ar); if (NULL != ar->mAudioRecord) { ar->mAudioRecord->stop(); delete ar->mAudioRecord; ar->mAudioRecord = NULL; } #ifdef MONITOR_RECORDING if (NULL != gMonitorFp) { fclose(gMonitorFp); gMonitorFp = NULL; } #endif } //----------------------------------------------------------------------------- void android_audioRecorder_setRecordState(CAudioRecorder* ar, SLuint32 state) { SL_LOGV("android_audioRecorder_setRecordState(%p, %lu) entering", ar, state); if (NULL == ar->mAudioRecord) { 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_useEventMask(CAudioRecorder *ar) { IRecord *pRecordItf = &ar->mRecord; SLuint32 eventFlags = pRecordItf->mCallbackEventsMask; if (NULL == ar->mAudioRecord) { 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 %ld", 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) || (NULL == ar->mAudioRecord)) { *pPosMsec = 0; } else { uint32_t positionInFrames; ar->mAudioRecord->getPosition(&positionInFrames); if (ar->mSampleRateMilliHz == 0) { *pPosMsec = 0; } else { *pPosMsec = ((int64_t)positionInFrames * 1000) / sles_to_android_sampleRate(ar->mSampleRateMilliHz); } } }