diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a4bfadbf..1b82a0fda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,13 @@ set (oboe_sources src/fifo/FifoController.cpp src/fifo/FifoControllerBase.cpp src/fifo/FifoControllerIndirect.cpp + src/opensles/AudioInputStreamOpenSLES.cpp + src/opensles/AudioOutputStreamOpenSLES.cpp src/opensles/AudioStreamBuffered.cpp src/opensles/AudioStreamOpenSLES.cpp + src/opensles/EngineOpenSLES.cpp src/opensles/OpenSLESUtilities.cpp + src/opensles/OutputMixerOpenSLES.cpp ) add_library(oboe STATIC ${oboe_sources}) @@ -25,4 +29,4 @@ target_compile_options(oboe PRIVATE -std=c++11 PRIVATE -Wall PRIVATE "$<$:-Werror>") # Only include -Werror when building debug config -target_link_libraries(oboe PRIVATE log OpenSLES) \ No newline at end of file +target_link_libraries(oboe PRIVATE log OpenSLES) diff --git a/src/common/AudioStreamBuilder.cpp b/src/common/AudioStreamBuilder.cpp index 6d92391b1..a3582e889 100644 --- a/src/common/AudioStreamBuilder.cpp +++ b/src/common/AudioStreamBuilder.cpp @@ -20,6 +20,8 @@ #include "OboeDebug.h" #include "oboe/Oboe.h" #include "oboe/AudioStreamBuilder.h" +#include "opensles/AudioInputStreamOpenSLES.h" +#include "opensles/AudioOutputStreamOpenSLES.h" #include "opensles/AudioStreamOpenSLES.h" namespace oboe { @@ -41,7 +43,11 @@ AudioStream *AudioStreamBuilder::build() { } // fall into using older existing API case AudioApi::OpenSLES: - stream = new AudioStreamOpenSLES(*this); + if (getDirection() == oboe::Direction::Output) { + stream = new AudioOutputStreamOpenSLES(*this); + } else if (getDirection() == oboe::Direction::Input) { + stream = new AudioInputStreamOpenSLES(*this); + } break; } return stream; diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp new file mode 100644 index 000000000..3ff10e64f --- /dev/null +++ b/src/opensles/AudioInputStreamOpenSLES.cpp @@ -0,0 +1,219 @@ +/* + * Copyright 2017 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 + +#include +#include + +#include "oboe/AudioStreamBuilder.h" +#include "AudioInputStreamOpenSLES.h" +#include "AudioStreamOpenSLES.h" +#include "OpenSLESUtilities.h" + +using namespace oboe; + +AudioInputStreamOpenSLES::AudioInputStreamOpenSLES(const AudioStreamBuilder &builder) + : AudioStreamOpenSLES(builder) { +} + +AudioInputStreamOpenSLES::~AudioInputStreamOpenSLES() { +} + +#define AUDIO_CHANNEL_COUNT_MAX 30u +#define SL_ANDROID_UNKNOWN_CHANNELMASK 0 + +int AudioInputStreamOpenSLES::chanCountToChanMask(int channelCount) { + // from internal sles_channel_in_mask_from_count(chanCount); + switch (channelCount) { + case 1: + return SL_SPEAKER_FRONT_LEFT; + case 2: + return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + default: { + if (channelCount > AUDIO_CHANNEL_COUNT_MAX) { + return SL_ANDROID_UNKNOWN_CHANNELMASK; + } else { + SLuint32 bitfield = (1 << channelCount) - 1; + return SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(bitfield); + } + } + } +} + +Result AudioInputStreamOpenSLES::open() { + + Result oboeResult = AudioStreamOpenSLES::open(); + if (Result::OK != oboeResult) return oboeResult; + + SLuint32 bitsPerSample = getBytesPerSample() * kBitsPerByte; + + // configure audio sink + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType + static_cast(mBurstsPerBuffer)}; // numBuffers + + // Define the audio data format. + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, // formatType + (SLuint32) mChannelCount, // numChannels + (SLuint32) (mSampleRate * kMillisPerSecond), // milliSamplesPerSec + bitsPerSample, // bitsPerSample + bitsPerSample, // containerSize; + (SLuint32) chanCountToChanMask(mChannelCount), // channelMask + getDefaultByteOrder(), + }; + + SLDataSink audioSink = {&loc_bufq, &format_pcm}; + + /** + * API 21 (Lollipop) introduced support for floating-point data representation and an extended + * data format type: SLAndroidDataFormat_PCM_EX. If running on API 21+ use this newer format + * type, creating it from our original format. + */ + SLAndroidDataFormat_PCM_EX format_pcm_ex; + if (__ANDROID_API__ >= __ANDROID_API_L__) { + SLuint32 representation = OpenSLES_ConvertFormatToRepresentation(getFormat()); + // Fill in the format structure. + format_pcm_ex = OpenSLES_createExtendedFormat(format_pcm, representation); + // Use in place of the previous format. + audioSink.pFormat = &format_pcm_ex; + } + + // configure audio source + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + NULL }; + SLDataSource audioSrc = {&loc_dev, NULL }; + + SLresult result = EngineOpenSLES::getInstance().createAudioRecorder(&mObjectInterface, + &audioSrc, + &audioSink); + if (SL_RESULT_SUCCESS != result) { + LOGE("createAudioRecorder() result:%s", getSLErrStr(result)); + goto error; + } + + // Configure the voice recognition preset, which has no + // signal processing, for lower latency. + SLAndroidConfigurationItf inputConfig; + result = (*mObjectInterface)->GetInterface(mObjectInterface, + SL_IID_ANDROIDCONFIGURATION, + &inputConfig); + if (SL_RESULT_SUCCESS == result) { + SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + (*inputConfig)->SetConfiguration(inputConfig, + SL_ANDROID_KEY_RECORDING_PRESET, + &presetValue, + sizeof(SLuint32)); + } + + result = (*mObjectInterface)->Realize(mObjectInterface, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("Realize recorder object result:%s", getSLErrStr(result)); + goto error; + } + + result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_RECORD, &mRecordInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("GetInterface RECORD result:%s", getSLErrStr(result)); + goto error; + } + + result = AudioStreamOpenSLES::registerBufferQueueCallback(); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + + return Result::OK; + +error: + return Result::ErrorInternal; // TODO convert error from SLES to OBOE +} + +Result AudioInputStreamOpenSLES::close() { + requestStop(); + mRecordInterface = NULL; + return AudioStreamOpenSLES::close(); +} + +Result AudioInputStreamOpenSLES::setRecordState(SLuint32 newState) { + Result result = Result::OK; + LOGD("AudioInputStreamOpenSLES::setRecordState(%d)", newState); + if (mRecordInterface == NULL) { + return Result::ErrorInvalidState; + } + SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState); + if(SL_RESULT_SUCCESS != slResult) { + LOGD("AudioInputStreamOpenSLES::setPlayState() returned %s", getSLErrStr(slResult)); + result = Result::ErrorInvalidState; // TODO review + } else { + setState(StreamState::Pausing); + } + return result; +} + +Result AudioInputStreamOpenSLES::requestStart() +{ + LOGD("AudioInputStreamOpenSLES::requestStart()"); + Result result = setRecordState(SL_RECORDSTATE_RECORDING); + if(result != Result::OK) { + result = Result::ErrorInvalidState; // TODO review + } else { + // Enqueue the first buffer so that we have data ready in the callback. + enqueueCallbackBuffer(mSimpleBufferQueueInterface); + setState(StreamState::Starting); + } + return result; +} + + +Result AudioInputStreamOpenSLES::requestPause() { + LOGD("AudioInputStreamOpenSLES::requestStop()"); + Result result = setRecordState(SL_RECORDSTATE_PAUSED); + if(result != Result::OK) { + result = Result::ErrorInvalidState; // TODO review + } else { + setState(StreamState::Pausing); + } + return result; +} + +Result AudioInputStreamOpenSLES::requestFlush() { + return Result::ErrorUnimplemented; // TODO +} + +Result AudioInputStreamOpenSLES::requestStop() { + LOGD("AudioInputStreamOpenSLES::requestStop()"); + Result result = setRecordState(SL_RECORDSTATE_STOPPED); + if(result != Result::OK) { + result = Result::ErrorInvalidState; // TODO review + } else { + setState(StreamState::Stopping); + } + return result; +} + +Result AudioInputStreamOpenSLES::waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) { + LOGD("AudioInputStreamOpenSLES::waitForStateChange()"); + if (mRecordInterface == NULL) { + return Result::ErrorInvalidState; + } + return Result::ErrorUnimplemented; // TODO +} diff --git a/src/opensles/AudioInputStreamOpenSLES.h b/src/opensles/AudioInputStreamOpenSLES.h new file mode 100644 index 000000000..89c03d07d --- /dev/null +++ b/src/opensles/AudioInputStreamOpenSLES.h @@ -0,0 +1,63 @@ +/* + * Copyright 2017 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. + */ + +#ifndef AUDIO_INPUT_STREAM_OPENSL_ES_H_ +#define AUDIO_INPUT_STREAM_OPENSL_ES_H_ + + +#include +#include + +#include "oboe/Oboe.h" +#include "AudioStreamOpenSLES.h" + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ + +class AudioInputStreamOpenSLES : public AudioStreamOpenSLES { +public: + AudioInputStreamOpenSLES(); + explicit AudioInputStreamOpenSLES(const AudioStreamBuilder &builder); + + virtual ~AudioInputStreamOpenSLES(); + + Result open() override; + Result close() override; + + Result requestStart() override; + Result requestPause() override; + Result requestFlush() override; + Result requestStop() override; + + Result waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) override; + + int chanCountToChanMask(int chanCount); + +private: + + Result setRecordState(SLuint32 newState); + + SLRecordItf mRecordInterface; +}; + +} // namespace oboe + +#endif //AUDIO_INPUT_STREAM_OPENSL_ES_H_ diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp new file mode 100644 index 000000000..21297661e --- /dev/null +++ b/src/opensles/AudioOutputStreamOpenSLES.cpp @@ -0,0 +1,223 @@ +/* + * Copyright 2017 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 + +#include +#include + +#include "oboe/AudioStreamBuilder.h" +#include "AudioOutputStreamOpenSLES.h" +#include "AudioStreamOpenSLES.h" +#include "OpenSLESUtilities.h" +#include "OutputMixerOpenSLES.h" + +using namespace oboe; + +AudioOutputStreamOpenSLES::AudioOutputStreamOpenSLES(const AudioStreamBuilder &builder) + : AudioStreamOpenSLES(builder) { +} + +AudioOutputStreamOpenSLES::~AudioOutputStreamOpenSLES() { +} + +// These will wind up in +constexpr int SL_ANDROID_SPEAKER_STEREO = (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); + +constexpr int SL_ANDROID_SPEAKER_QUAD = (SL_ANDROID_SPEAKER_STEREO + | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT); + +constexpr int SL_ANDROID_SPEAKER_5DOT1 = (SL_ANDROID_SPEAKER_QUAD + | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY); + +constexpr int SL_ANDROID_SPEAKER_7DOT1 = (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT + | SL_SPEAKER_SIDE_RIGHT); + +int AudioOutputStreamOpenSLES::chanCountToChanMask(int chanCount) { + int channelMask = 0; + + switch (chanCount) { + case 1: + channelMask = SL_SPEAKER_FRONT_CENTER; + break; + + case 2: + channelMask = SL_ANDROID_SPEAKER_STEREO; + break; + + case 4: // Quad + channelMask = SL_ANDROID_SPEAKER_QUAD; + break; + + case 6: // 5.1 + channelMask = SL_ANDROID_SPEAKER_5DOT1; + break; + + case 8: // 7.1 + channelMask = SL_ANDROID_SPEAKER_7DOT1; + break; + } + return channelMask; +} + +Result AudioOutputStreamOpenSLES::open() { + Result oboeResult = AudioStreamOpenSLES::open(); + if (Result::OK != oboeResult) return oboeResult; + + SLresult result = OutputMixerOpenSL::getInstance().open(); + if (SL_RESULT_SUCCESS != result) { + AudioStreamOpenSLES::close(); + return Result::ErrorInternal; + } + + SLuint32 bitsPerSample = getBytesPerSample() * kBitsPerByte; + + // configure audio source + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType + static_cast(mBurstsPerBuffer)}; // numBuffers + + // Define the audio data format. + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, // formatType + (SLuint32) mChannelCount, // numChannels + (SLuint32) (mSampleRate * kMillisPerSecond), // milliSamplesPerSec + bitsPerSample, // bitsPerSample + bitsPerSample, // containerSize; + (SLuint32) chanCountToChanMask(mChannelCount), // channelMask + getDefaultByteOrder(), + }; + + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + + /** + * API 21 (Lollipop) introduced support for floating-point data representation and an extended + * data format type: SLAndroidDataFormat_PCM_EX. If running on API 21+ use this newer format + * type, creating it from our original format. + */ + SLAndroidDataFormat_PCM_EX format_pcm_ex; + if (__ANDROID_API__ >= __ANDROID_API_L__) { + SLuint32 representation = OpenSLES_ConvertFormatToRepresentation(getFormat()); + // Fill in the format structure. + format_pcm_ex = OpenSLES_createExtendedFormat(format_pcm, representation); + // Use in place of the previous format. + audioSrc.pFormat = &format_pcm_ex; + } + + result = OutputMixerOpenSL::getInstance().createAudioPlayer(&mObjectInterface, + &audioSrc); + if (SL_RESULT_SUCCESS != result) { + LOGE("createAudioPlayer() result:%s", getSLErrStr(result)); + goto error; + } + + result = (*mObjectInterface)->Realize(mObjectInterface, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("Realize player object result:%s", getSLErrStr(result)); + goto error; + } + + result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_PLAY, &mPlayInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("GetInterface PLAY result:%s", getSLErrStr(result)); + goto error; + } + + result = AudioStreamOpenSLES::registerBufferQueueCallback(); + if (SL_RESULT_SUCCESS != result) { + goto error; + } + + return Result::OK; +error: + return Result::ErrorInternal; // TODO convert error from SLES to OBOE +} + +Result AudioOutputStreamOpenSLES::close() { + requestPause(); + // invalidate any interfaces + mPlayInterface = NULL; + OutputMixerOpenSL::getInstance().close(); + return AudioStreamOpenSLES::close(); +} + +Result AudioOutputStreamOpenSLES::setPlayState(SLuint32 newState) { + Result result = Result::OK; + LOGD("AudioOutputStreamOpenSLES(): setPlayState()"); + if (mPlayInterface == NULL) { + return Result::ErrorInvalidState; + } + SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState); + if(SL_RESULT_SUCCESS != slResult) { + LOGD("AudioOutputStreamOpenSLES(): setPlayState() returned %s", getSLErrStr(slResult)); + result = Result::ErrorInvalidState; // TODO review + } else { + setState(StreamState::Pausing); + } + return result; +} + +Result AudioOutputStreamOpenSLES::requestStart() { + LOGD("AudioOutputStreamOpenSLES(): requestStart()"); + Result result = setPlayState(SL_PLAYSTATE_PLAYING); + if(result != Result::OK) { + result = Result::ErrorInvalidState; // TODO review + } else { + processBufferCallback(mSimpleBufferQueueInterface); + setState(StreamState::Starting); + } + return result; +} + +Result AudioOutputStreamOpenSLES::requestPause() { + LOGD("AudioOutputStreamOpenSLES(): requestPause()"); + Result result = setPlayState(SL_PLAYSTATE_PAUSED); + if(result != Result::OK) { + result = Result::ErrorInvalidState; // TODO review + } else { + setState(StreamState::Pausing); + } + return result; +} + +Result AudioOutputStreamOpenSLES::requestFlush() { + LOGD("AudioOutputStreamOpenSLES(): requestFlush()"); + if (mPlayInterface == NULL) { + return Result::ErrorInvalidState; + } + return Result::ErrorUnimplemented; // TODO +} + +Result AudioOutputStreamOpenSLES::requestStop() { + LOGD("AudioOutputStreamOpenSLES(): requestStop()"); + Result result = setPlayState(SL_PLAYSTATE_STOPPED); + if(result != Result::OK) { + result = Result::ErrorInvalidState; // TODO review + } else { + setState(StreamState::Stopping); + } + return result; +} + +Result AudioOutputStreamOpenSLES::waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) { + LOGD("AudioOutputStreamOpenSLES::waitForStateChange()"); + if (mPlayInterface == NULL) { + return Result::ErrorInvalidState; + } + return Result::ErrorUnimplemented; // TODO +} diff --git a/src/opensles/AudioOutputStreamOpenSLES.h b/src/opensles/AudioOutputStreamOpenSLES.h new file mode 100644 index 000000000..f8b83de9f --- /dev/null +++ b/src/opensles/AudioOutputStreamOpenSLES.h @@ -0,0 +1,69 @@ +/* + * Copyright 2017 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. + */ + +#ifndef AUDIO_OUTPUT_STREAM_OPENSL_ES_H_ +#define AUDIO_OUTPUT_STREAM_OPENSL_ES_H_ + + +#include +#include + +#include "oboe/Oboe.h" +#include "AudioStreamOpenSLES.h" + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ +class AudioOutputStreamOpenSLES : public AudioStreamOpenSLES { +public: + AudioOutputStreamOpenSLES(); + explicit AudioOutputStreamOpenSLES(const AudioStreamBuilder &builder); + + virtual ~AudioOutputStreamOpenSLES(); + + Result open() override; + Result close() override; + + Result requestStart() override; + Result requestPause() override; + Result requestFlush() override; + Result requestStop() override; + + Result waitForStateChange(StreamState currentState, + StreamState *nextState, + int64_t timeoutNanoseconds) override; + + int chanCountToChanMask(int chanCount); + +private: + + /** + * Set OpenSL ES PLAYSTATE. + * + * @param newState SL_PLAYSTATE_PAUSED, SL_PLAYSTATE_PLAYING, SL_PLAYSTATE_STOPPED + * @return + */ + Result setPlayState(SLuint32 newState); + + SLPlayItf mPlayInterface = nullptr; + +}; + +} // namespace oboe + +#endif //AUDIO_OUTPUT_STREAM_OPENSL_ES_H_ diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp index dfb9f8493..3830eeb7c 100644 --- a/src/opensles/AudioStreamOpenSLES.cpp +++ b/src/opensles/AudioStreamOpenSLES.cpp @@ -33,214 +33,31 @@ #define DEFAULT_SAMPLE_RATE 48000 // very common rate for mobile audio and video #define DEFAULT_CHANNEL_COUNT 2 // stereo -#define OBOE_BITS_PER_BYTE 8 // common value - -namespace oboe { - -/* - * OSLES Helpers - */ -static const char *errStrings[] = { - "SL_RESULT_SUCCESS", // 0 - "SL_RESULT_PRECONDITIONS_VIOLATE", // 1 - "SL_RESULT_PARAMETER_INVALID", // 2 - "SL_RESULT_MEMORY_FAILURE", // 3 - "SL_RESULT_RESOURCE_ERROR", // 4 - "SL_RESULT_RESOURCE_LOST", // 5 - "SL_RESULT_IO_ERROR", // 6 - "SL_RESULT_BUFFER_INSUFFICIENT", // 7 - "SL_RESULT_CONTENT_CORRUPTED", // 8 - "SL_RESULT_CONTENT_UNSUPPORTED", // 9 - "SL_RESULT_CONTENT_NOT_FOUND", // 10 - "SL_RESULT_PERMISSION_DENIED", // 11 - "SL_RESULT_FEATURE_UNSUPPORTED", // 12 - "SL_RESULT_INTERNAL_ERROR", // 13 - "SL_RESULT_UNKNOWN_ERROR", // 14 - "SL_RESULT_OPERATION_ABORTED", // 15 - "SL_RESULT_CONTROL_LOST" // 16 -}; - -const char *getSLErrStr(SLresult code) { - return errStrings[code]; -} - -// These will wind up in -#define SL_ANDROID_SPEAKER_QUAD (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT \ - | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT) - -#define SL_ANDROID_SPEAKER_5DOT1 (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT \ - | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY| SL_SPEAKER_BACK_LEFT \ - | SL_SPEAKER_BACK_RIGHT) - -#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT \ - |SL_SPEAKER_SIDE_RIGHT) - -int chanCountToChanMask(int chanCount) { - int channelMask = 0; - - switch (chanCount) { - case 1: - channelMask = SL_SPEAKER_FRONT_CENTER; - break; - - case 2: - channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - break; - - case 4: // Quad - channelMask = SL_ANDROID_SPEAKER_QUAD; - break; - - case 6: // 5.1 - channelMask = SL_ANDROID_SPEAKER_5DOT1; - break; - - case 8: // 7.1 - channelMask = SL_ANDROID_SPEAKER_7DOT1; - break; - } - return channelMask; -} - -static const char *TAG = "AAudioStreamOpenSLES"; - -// engine interfaces -static int32_t sOpenCount = 0; -static SLObjectItf sEngineObject = 0; -static SLEngineItf sEngineEngine; - -// output mix interfaces -static SLObjectItf sOutputMixObject = 0; - -static void CloseSLEngine(); - -SLresult AudioStreamOpenSLES::enqueueBuffer() { - // Ask the callback to fill the output buffer with data. - Result result = fireCallback(mCallbackBuffer, mFramesPerCallback); - if (result != Result::OK) { - LOGE("Oboe callback returned %d", result); - return SL_RESULT_INTERNAL_ERROR; - } else { - // Pass the data to OpenSLES. - return (*bq_)->Enqueue(bq_, mCallbackBuffer, mBytesPerCallback); - } -} - -// this callback handler is called every time a buffer finishes playing -static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { - ((AudioStreamOpenSLES *) context)->enqueueBuffer(); -} - -static SLresult OpenSLEngine() { - SLresult result = SL_RESULT_SUCCESS; - if (sOpenCount > 0) { - ++sOpenCount; - return SL_RESULT_SUCCESS; - } - - // create engine - result = slCreateEngine(&sEngineObject, 0, NULL, 0, NULL, NULL); - if (SL_RESULT_SUCCESS != result) { - LOGE("OpenSLEngine() - slCreateEngine() result:%s", getSLErrStr(result)); - return result; - } - - // realize the engine - result = (*sEngineObject)->Realize(sEngineObject, SL_BOOLEAN_FALSE); - if (SL_RESULT_SUCCESS != result) { - LOGE("OpenSLEngine() - Realize() engine result:%s", getSLErrStr(result)); - goto error; - } - - // get the engine interface, which is needed in order to create other objects - result = (*sEngineObject)->GetInterface(sEngineObject, SL_IID_ENGINE, &sEngineEngine); - if (SL_RESULT_SUCCESS != result) { - LOGE("OpenSLEngine() - GetInterface() engine result:%s", getSLErrStr(result)); - goto error; - } - - // get the output mixer - result = (*sEngineEngine)->CreateOutputMix(sEngineEngine, &sOutputMixObject, 0, 0, 0); - if (SL_RESULT_SUCCESS != result) { - LOGE("OpenSLEngine() - CreateOutputMix() result:%s", getSLErrStr(result)); - goto error; - } - - // realize the output mix - result = (*sOutputMixObject)->Realize(sOutputMixObject, SL_BOOLEAN_FALSE); - if (SL_RESULT_SUCCESS != result) { - LOGE("OpenSLEngine() - Realize() sOutputMixObject result:%s", getSLErrStr(result)); - goto error; - } - - ++sOpenCount; - return result; - -error: - CloseSLEngine(); - return result; -} - -static void CloseSLEngine() { -// __android_log_print(ANDROID_LOG_INFO, TAG, "CloseSLEngine()"); - --sOpenCount; - if (sOpenCount > 0) { - return; - } - // destroy output mix object, and invalidate all associated interfaces - if (sOutputMixObject != NULL) { - (*sOutputMixObject)->Destroy(sOutputMixObject); - sOutputMixObject = NULL; - } - - if (sEngineObject != NULL) { - (*sEngineObject)->Destroy(sEngineObject); - sEngineObject = NULL; - sEngineEngine = NULL; - } -} +using namespace oboe; AudioStreamOpenSLES::AudioStreamOpenSLES(const AudioStreamBuilder &builder) : AudioStreamBuffered(builder) { - bqPlayerObject_ = NULL; - bq_ = NULL; - bqPlayerPlay_ = NULL; + mSimpleBufferQueueInterface = NULL; mFramesPerBurst = builder.getDefaultFramesPerBurst(); - OpenSLEngine(); - LOGD("AudioStreamOpenSLES(): after OpenSLEngine()"); + LOGD("AudioStreamOpenSLES(): after OpenSLContext()"); } AudioStreamOpenSLES::~AudioStreamOpenSLES() { - CloseSLEngine(); delete[] mCallbackBuffer; } -static SLuint32 ConvertFormatToRepresentation(AudioFormat format) { - switch(format) { - case AudioFormat::Invalid: - case AudioFormat::Unspecified: - return 0; - case AudioFormat::I16: - return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; - case AudioFormat::Float: - return SL_ANDROID_PCM_REPRESENTATION_FLOAT; - } -} - static bool s_isLittleEndian() { static uint32_t value = 1; return *((uint8_t *) &value) == 1; // Does address point to LSB? } -static SLuint32 s_getDefaultByteOrder() { +SLuint32 AudioStreamOpenSLES::getDefaultByteOrder() { return s_isLittleEndian() ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; } Result AudioStreamOpenSLES::open() { - SLresult result; - - __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayerOpenSLES::Open(chans:%d, rate:%d)", + LOGI("AudioStreamOpenSLES::open(chans:%d, rate:%d)", mChannelCount, mSampleRate); if (__ANDROID_API__ < __ANDROID_API_L__ && mFormat == AudioFormat::Float){ @@ -248,6 +65,11 @@ Result AudioStreamOpenSLES::open() { return Result::ErrorInvalidFormat; } + SLresult result = EngineOpenSLES::getInstance().open(); + if (SL_RESULT_SUCCESS != result) { + return Result::ErrorInternal; + } + // If audio format is unspecified then choose a suitable default. // API 21+: FLOAT // API <21: INT16 @@ -279,78 +101,11 @@ Result AudioStreamOpenSLES::open() { } mBytesPerCallback = mFramesPerCallback * getBytesPerFrame(); + delete[] mCallbackBuffer; // to prevent memory leaks mCallbackBuffer = new uint8_t[mBytesPerCallback]; LOGD("AudioStreamOpenSLES(): mFramesPerCallback = %d", mFramesPerCallback); LOGD("AudioStreamOpenSLES(): mBytesPerCallback = %d", mBytesPerCallback); - SLuint32 bitsPerSample = getBytesPerSample() * OBOE_BITS_PER_BYTE; - - // configure audio source - SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { - SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType - static_cast(mBurstsPerBuffer)}; // numBuffers - - // SLuint32 chanMask = SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT; - - // Define the audio data format. - SLDataFormat_PCM format_pcm = { - SL_DATAFORMAT_PCM, // formatType - (SLuint32) mChannelCount, // numChannels - (SLuint32) (mSampleRate * kMillisPerSecond), // milliSamplesPerSec - bitsPerSample, // bitsPerSample - bitsPerSample, // containerSize; - (SLuint32) chanCountToChanMask(mChannelCount), // channelMask - s_getDefaultByteOrder(), - }; - - SLDataSource audioSrc = {&loc_bufq, &format_pcm}; - - /** - * API 21 (Lollipop) introduced support for floating-point data representation and an extended - * data format type: SLAndroidDataFormat_PCM_EX. If running on API 21+ use this newer format - * type, creating it from our original format. - */ - if (__ANDROID_API__ >= __ANDROID_API_L__) { - SLuint32 representation = ConvertFormatToRepresentation(getFormat()); - SLAndroidDataFormat_PCM_EX format_pcm_ex = OpenSLES_createExtendedFormat(format_pcm, - representation); - // Overwrite the previous format. - audioSrc.pFormat = &format_pcm_ex; - } - - // configure audio sink - SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, sOutputMixObject}; - SLDataSink audioSnk = {&loc_outmix, NULL}; - - const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE}; - const SLboolean req[] = {SL_BOOLEAN_TRUE}; - - // The Player - result = (*sEngineEngine)->CreateAudioPlayer(sEngineEngine, &bqPlayerObject_, &audioSrc, - &audioSnk, - sizeof(ids) / sizeof(ids[0]), ids, req); - LOGD("CreateAudioPlayer() result:%s", getSLErrStr(result)); - assert(SL_RESULT_SUCCESS == result); - - result = (*bqPlayerObject_)->Realize(bqPlayerObject_, SL_BOOLEAN_FALSE); - LOGD("Realize player object result:%s", getSLErrStr(result)); - assert(SL_RESULT_SUCCESS == result); - - result = (*bqPlayerObject_)->GetInterface(bqPlayerObject_, SL_IID_PLAY, &bqPlayerPlay_); - LOGD("get player interface result:%s", getSLErrStr(result)); - assert(SL_RESULT_SUCCESS == result); - - // The BufferQueue - result = (*bqPlayerObject_)->GetInterface(bqPlayerObject_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &bq_); - LOGD("get bufferqueue interface:%p result:%s", bq_, getSLErrStr(result)); - assert(SL_RESULT_SUCCESS == result); - - // The register BufferQueue callback - result = (*bq_)->RegisterCallback(bq_, bqPlayerCallback, this); - LOGD("register callback result:%s", getSLErrStr(result)); - assert(SL_RESULT_SUCCESS == result); - mSharingMode = SharingMode::Shared; mBufferCapacityInFrames = mFramesPerBurst * mBurstsPerBuffer; @@ -358,94 +113,56 @@ Result AudioStreamOpenSLES::open() { } Result AudioStreamOpenSLES::close() { -// __android_log_write(ANDROID_LOG_INFO, TAG, "AudioStreamOpenSLES()"); - // TODO make sure callback is no longer being called - if (bqPlayerObject_ != NULL) { - (*bqPlayerObject_)->Destroy(bqPlayerObject_); - bqPlayerObject_ = NULL; + if (mObjectInterface != NULL) { + (*mObjectInterface)->Destroy(mObjectInterface); + mObjectInterface = NULL; - // invalidate any interfaces - bqPlayerPlay_ = NULL; - bq_ = NULL; } + mSimpleBufferQueueInterface = NULL; + EngineOpenSLES::getInstance().close(); return Result::OK; } -Result AudioStreamOpenSLES::setPlayState(SLuint32 newState) -{ - Result result = Result::OK; - LOGD("AudioStreamOpenSLES(): setPlayState()"); - if (bqPlayerPlay_ == NULL) { - return Result::ErrorInvalidState; - } - SLresult slResult = (*bqPlayerPlay_)->SetPlayState(bqPlayerPlay_, newState); - if(SL_RESULT_SUCCESS != slResult) { - LOGD("AudioStreamOpenSLES(): setPlayState() returned %s", getSLErrStr(slResult)); - result = Result::ErrorInvalidState; // TODO review - } else { - setState(StreamState::Pausing); - } - return result; +SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq) { + return (*bq)->Enqueue(bq, mCallbackBuffer, mBytesPerCallback); } -Result AudioStreamOpenSLES::requestStart() -{ - LOGD("AudioStreamOpenSLES(): requestStart()"); - Result result = setPlayState(SL_PLAYSTATE_PLAYING); - if(result != Result::OK) { - result = Result::ErrorInvalidState; // TODO review - } else { - enqueueBuffer(); - setState(StreamState::Starting); - } - return result; -} - - -Result AudioStreamOpenSLES::requestPause() { - LOGD("AudioStreamOpenSLES(): requestPause()"); - Result result = setPlayState(SL_PLAYSTATE_PAUSED); - if(result != Result::OK) { - result = Result::ErrorInvalidState; // TODO review +SLresult AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) { + // Ask the callback to fill the output buffer with data. + Result result = fireCallback(mCallbackBuffer, mFramesPerCallback); + if (result != Result::OK) { + LOGE("Oboe callback returned %d", result); + return SL_RESULT_INTERNAL_ERROR; } else { - setState(StreamState::Pausing); + // Pass the data to OpenSLES. + return enqueueCallbackBuffer(bq); } - return result; } -Result AudioStreamOpenSLES::requestFlush() { - LOGD("AudioStreamOpenSLES(): requestFlush()"); - if (bqPlayerPlay_ == NULL) { - return Result::ErrorInvalidState; - } - return Result::ErrorUnimplemented; // TODO +// this callback handler is called every time a buffer needs processing +static void bqCallbackGlue(SLAndroidSimpleBufferQueueItf bq, void *context) { + ((AudioStreamOpenSLES *) context)->processBufferCallback(bq); } -Result AudioStreamOpenSLES::requestStop() -{ - LOGD("AudioStreamOpenSLES(): requestStop()"); - Result result = setPlayState(SL_PLAYSTATE_STOPPED); - if(result != Result::OK) { - result = Result::ErrorInvalidState; // TODO review +SLresult AudioStreamOpenSLES::registerBufferQueueCallback() { + // The BufferQueue + SLresult result = (*mObjectInterface)->GetInterface(mObjectInterface, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &mSimpleBufferQueueInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("get buffer queue interface:%p result:%s", + mSimpleBufferQueueInterface, + getSLErrStr(result)); } else { - setState(StreamState::Stopping); + // Register the BufferQueue callback + result = (*mSimpleBufferQueueInterface)->RegisterCallback(mSimpleBufferQueueInterface, + bqCallbackGlue, this); + if (SL_RESULT_SUCCESS != result) { + LOGE("RegisterCallback result:%s", getSLErrStr(result)); + } } return result; } -Result AudioStreamOpenSLES::waitForStateChange(StreamState currentState, - StreamState *nextState, - int64_t timeoutNanoseconds) -{ - LOGD("AudioStreamOpenSLES(): waitForStateChange()"); - if (bqPlayerPlay_ == NULL) { - return Result::ErrorInvalidState; - } - return Result::ErrorUnimplemented; // TODO -} - int32_t AudioStreamOpenSLES::getFramesPerBurst() { return mFramesPerBurst; } - -} // namespace oboe diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h index 41ec329bd..7056e57b4 100644 --- a/src/opensles/AudioStreamOpenSLES.h +++ b/src/opensles/AudioStreamOpenSLES.h @@ -14,24 +14,29 @@ * limitations under the License. */ -#ifndef AUDIO_STREAM_OPENSL_ES_H_ -#define AUDIO_STREAM_OPENSL_ES_H_ +#ifndef OBOE_AUDIO_STREAM_OPENSL_ES_H_ +#define OBOE_AUDIO_STREAM_OPENSL_ES_H_ #include #include #include "oboe/Oboe.h" #include "AudioStreamBuffered.h" +#include "EngineOpenSLES.h" namespace oboe { +constexpr int kBitsPerByte = 8; + /** + * INTERNAL USE ONLY + * * A stream that wraps OpenSL ES. * * Do not instantiate this class directly. * Use an OboeStreamBuilder to create one. */ -// + class AudioStreamOpenSLES : public AudioStreamBuffered { public: @@ -40,20 +45,8 @@ class AudioStreamOpenSLES : public AudioStreamBuffered { virtual ~AudioStreamOpenSLES(); - Result open() override; - Result close() override; - - Result requestStart() override; - Result requestPause() override; - Result requestFlush() override; - Result requestStop() override; - - // public, but don't call directly (called by the OSLES callback) - SLresult enqueueBuffer(); - - Result waitForStateChange(StreamState currentState, - StreamState *nextState, - int64_t timeoutNanoseconds) override; + virtual Result open() override; + virtual Result close() override; /** * Query the current state, eg. OBOE_STREAM_STATE_PAUSING @@ -64,8 +57,21 @@ class AudioStreamOpenSLES : public AudioStreamBuffered { int32_t getFramesPerBurst() override; + /** + * Process next OpenSL ES buffer. + * Called by by OpenSL ES framework. + * + * This is public, but don't call it directly. + */ + SLresult processBufferCallback(SLAndroidSimpleBufferQueueItf bq); + protected: -private: + + static SLuint32 getDefaultByteOrder(); + + SLresult registerBufferQueueCallback(); + + SLresult enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq); /** * Internal use only. @@ -75,26 +81,17 @@ class AudioStreamOpenSLES : public AudioStreamBuffered { mState = state; } - /** - * Set OpenSL ES state. - * - * @param newState SL_PLAYSTATE_PAUSED, SL_PLAYSTATE_PLAYING, SL_PLAYSTATE_STOPPED - * @return - */ - Result setPlayState(SLuint32 newState); + // OpenSLES stuff + SLObjectItf mObjectInterface = nullptr; + SLAndroidSimpleBufferQueueItf mSimpleBufferQueueInterface = nullptr; - uint8_t *mCallbackBuffer; - int32_t mBytesPerCallback; + uint8_t *mCallbackBuffer = nullptr; + int32_t mBytesPerCallback = oboe::kUnspecified; int32_t mFramesPerBurst = 0; int32_t mBurstsPerBuffer = 2; // Double buffered StreamState mState = StreamState::Uninitialized; - - // OpenSLES stuff - SLObjectItf bqPlayerObject_; - SLPlayItf bqPlayerPlay_; - SLAndroidSimpleBufferQueueItf bq_; }; } // namespace oboe -#endif // AUDIO_STREAM_OPENSL_ES_H_ +#endif // OBOE_AUDIO_STREAM_OPENSL_ES_H_ diff --git a/src/opensles/EngineOpenSLES.cpp b/src/opensles/EngineOpenSLES.cpp new file mode 100644 index 000000000..8df3fed91 --- /dev/null +++ b/src/opensles/EngineOpenSLES.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2017 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 "common/OboeDebug.h" +#include "EngineOpenSLES.h" +#include "OpenSLESUtilities.h" + +using namespace oboe; + +EngineOpenSLES &EngineOpenSLES::getInstance() { + static EngineOpenSLES sInstance; + return sInstance; +} + +SLresult EngineOpenSLES::open() { + std::lock_guard lock(mLock); + + SLresult result = SL_RESULT_SUCCESS; + if (mOpenCount++ == 0) { + + // create engine + result = slCreateEngine(&mEngineObject, 0, NULL, 0, NULL, NULL); + if (SL_RESULT_SUCCESS != result) { + LOGE("EngineOpenSLES - slCreateEngine() result:%s", getSLErrStr(result)); + goto error; + } + + // realize the engine + result = (*mEngineObject)->Realize(mEngineObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("EngineOpenSLES - Realize() engine result:%s", getSLErrStr(result)); + goto error; + } + + // get the engine interface, which is needed in order to create other objects + result = (*mEngineObject)->GetInterface(mEngineObject, SL_IID_ENGINE, &mEngineInterface); + if (SL_RESULT_SUCCESS != result) { + LOGE("EngineOpenSLES - GetInterface() engine result:%s", getSLErrStr(result)); + goto error; + } + } + + return result; + +error: + close(); + return result; +} + +void EngineOpenSLES::close() { + std::lock_guard lock(mLock); + if (--mOpenCount == 0) { + if (mEngineObject != NULL) { + (*mEngineObject)->Destroy(mEngineObject); + mEngineObject = NULL; + mEngineInterface = NULL; + } + } +} + +SLresult EngineOpenSLES::createOutputMix(SLObjectItf *objectItf) { + return (*mEngineInterface)->CreateOutputMix(mEngineInterface, objectItf, 0, 0, 0); +} + +SLresult EngineOpenSLES::createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink) { + + const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE}; + const SLboolean reqs[] = {SL_BOOLEAN_TRUE}; + + return (*mEngineInterface)->CreateAudioPlayer(mEngineInterface, objectItf, audioSource, + audioSink, + sizeof(ids) / sizeof(ids[0]), ids, reqs); +} + +SLresult EngineOpenSLES::createAudioRecorder(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink) { + + const SLInterfaceID ids[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_ANDROIDCONFIGURATION }; + const SLboolean reqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + + return (*mEngineInterface)->CreateAudioRecorder(mEngineInterface, objectItf, audioSource, + audioSink, + sizeof(ids) / sizeof(ids[0]), ids, reqs); +} + diff --git a/src/opensles/EngineOpenSLES.h b/src/opensles/EngineOpenSLES.h new file mode 100644 index 000000000..7658c955a --- /dev/null +++ b/src/opensles/EngineOpenSLES.h @@ -0,0 +1,65 @@ +/* + * Copyright 2017 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. + */ + +#ifndef OBOE_ENGINE_OPENSLES_H +#define OBOE_ENGINE_OPENSLES_H + +#include +#include + +#include +#include + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ +class EngineOpenSLES { +public: + static EngineOpenSLES &getInstance(); + + SLresult open(); + + void close(); + + SLresult createOutputMix(SLObjectItf *objectItf); + + SLresult createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink); + SLresult createAudioRecorder(SLObjectItf *objectItf, + SLDataSource *audioSource, + SLDataSink *audioSink); + +private: + // Make this a safe Singleton + EngineOpenSLES()= default; + ~EngineOpenSLES()= default; + EngineOpenSLES(const EngineOpenSLES&)= delete; + EngineOpenSLES& operator=(const EngineOpenSLES&)= delete; + + std::mutex mLock; + int32_t mOpenCount = 0; + + SLObjectItf mEngineObject = 0; + SLEngineItf mEngineInterface; +}; + +} // namespace oboe + + +#endif //OBOE_ENGINE_OPENSLES_H diff --git a/src/opensles/OpenSLESUtilities.cpp b/src/opensles/OpenSLESUtilities.cpp index 0636bda32..45f1c1880 100644 --- a/src/opensles/OpenSLESUtilities.cpp +++ b/src/opensles/OpenSLESUtilities.cpp @@ -18,6 +18,33 @@ namespace oboe { +/* + * OSLES Helpers + */ +static const char *errStrings[] = { + "SL_RESULT_SUCCESS", // 0 + "SL_RESULT_PRECONDITIONS_VIOLATE", // 1 + "SL_RESULT_PARAMETER_INVALID", // 2 + "SL_RESULT_MEMORY_FAILURE", // 3 + "SL_RESULT_RESOURCE_ERROR", // 4 + "SL_RESULT_RESOURCE_LOST", // 5 + "SL_RESULT_IO_ERROR", // 6 + "SL_RESULT_BUFFER_INSUFFICIENT", // 7 + "SL_RESULT_CONTENT_CORRUPTED", // 8 + "SL_RESULT_CONTENT_UNSUPPORTED", // 9 + "SL_RESULT_CONTENT_NOT_FOUND", // 10 + "SL_RESULT_PERMISSION_DENIED", // 11 + "SL_RESULT_FEATURE_UNSUPPORTED", // 12 + "SL_RESULT_INTERNAL_ERROR", // 13 + "SL_RESULT_UNKNOWN_ERROR", // 14 + "SL_RESULT_OPERATION_ABORTED", // 15 + "SL_RESULT_CONTROL_LOST" // 16 +}; + +const char *getSLErrStr(SLresult code) { + return errStrings[code]; +} + SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat( SLDataFormat_PCM format, SLuint32 representation) { SLAndroidDataFormat_PCM_EX format_pcm_ex; @@ -32,4 +59,17 @@ SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat( return format_pcm_ex; } +SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format) { + switch(format) { + case AudioFormat::I16: + return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; + case AudioFormat::Float: + return SL_ANDROID_PCM_REPRESENTATION_FLOAT; + case AudioFormat::Invalid: + case AudioFormat::Unspecified: + default: + return 0; + } +} + } // namespace oboe \ No newline at end of file diff --git a/src/opensles/OpenSLESUtilities.h b/src/opensles/OpenSLESUtilities.h index a0b73af4b..20f5498e5 100644 --- a/src/opensles/OpenSLESUtilities.h +++ b/src/opensles/OpenSLESUtilities.h @@ -18,9 +18,12 @@ #define OBOE_OPENSLES_OPENSLESUTILITIES_H #include +#include "oboe/Oboe.h" namespace oboe { +const char *getSLErrStr(SLresult code); + /** * Creates an extended PCM format from the supplied format and data representation. This method * should only be called on Android devices with API level 21+. API 21 introduced the @@ -34,6 +37,9 @@ namespace oboe { SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat(SLDataFormat_PCM format, SLuint32 representation); + +SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format); + } // namespace oboe #endif //OBOE_OPENSLES_OPENSLESUTILITIES_H diff --git a/src/opensles/OutputMixerOpenSLES.cpp b/src/opensles/OutputMixerOpenSLES.cpp new file mode 100644 index 000000000..901e9a75e --- /dev/null +++ b/src/opensles/OutputMixerOpenSLES.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2017 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 "common/OboeDebug.h" +#include "EngineOpenSLES.h" +#include "OpenSLESUtilities.h" +#include "OutputMixerOpenSLES.h" + +using namespace oboe; + +OutputMixerOpenSL &OutputMixerOpenSL::getInstance() { + static OutputMixerOpenSL sInstance; + return sInstance; +} + +SLresult OutputMixerOpenSL::open() { + std::lock_guard lock(mLock); + + SLresult result = SL_RESULT_SUCCESS; + if (mOpenCount++ == 0) { + // get the output mixer + result = EngineOpenSLES::getInstance().createOutputMix(&mOutputMixObject); + if (SL_RESULT_SUCCESS != result) { + LOGE("OutputMixerOpenSL() - createOutputMix() result:%s", getSLErrStr(result)); + goto error; + } + + // realize the output mix + result = (*mOutputMixObject)->Realize(mOutputMixObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + LOGE("OutputMixerOpenSL() - Realize() mOutputMixObject result:%s", getSLErrStr(result)); + goto error; + } + } + + return result; + +error: + close(); + return result; +} + +void OutputMixerOpenSL::close() { + std::lock_guard lock(mLock); + + if (--mOpenCount == 0) { + // destroy output mix object, and invalidate all associated interfaces + if (mOutputMixObject != NULL) { + (*mOutputMixObject)->Destroy(mOutputMixObject); + mOutputMixObject = NULL; + } + } +} + +SLresult OutputMixerOpenSL::createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource) { + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, mOutputMixObject}; + SLDataSink audioSink = {&loc_outmix, NULL}; + return EngineOpenSLES::getInstance().createAudioPlayer(objectItf, audioSource, &audioSink); +} diff --git a/src/opensles/OutputMixerOpenSLES.h b/src/opensles/OutputMixerOpenSLES.h new file mode 100644 index 000000000..1fc77e512 --- /dev/null +++ b/src/opensles/OutputMixerOpenSLES.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 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. + */ + +#ifndef OBOE_OUTPUT_MIXER_OPENSLES_H +#define OBOE_OUTPUT_MIXER_OPENSLES_H + +#include +#include + +#include +#include + +namespace oboe { + +/** + * INTERNAL USE ONLY + */ + +class OutputMixerOpenSL { +public: + static OutputMixerOpenSL &getInstance(); + + SLresult open(); + + void close(); + + SLresult createAudioPlayer(SLObjectItf *objectItf, + SLDataSource *audioSource); + +private: + // Make this a safe Singleton + OutputMixerOpenSL()= default; + ~OutputMixerOpenSL()= default; + OutputMixerOpenSL(const OutputMixerOpenSL&)= delete; + OutputMixerOpenSL& operator=(const OutputMixerOpenSL&)= delete; + + std::mutex mLock; + int32_t mOpenCount = 0; + + SLObjectItf mOutputMixObject = 0; +}; + +} // namespace oboe + +#endif //OBOE_OUTPUT_MIXER_OPENSLES_H