aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/src/opensles/AudioOutputStreamOpenSLES.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'externals/oboe/src/opensles/AudioOutputStreamOpenSLES.cpp')
-rw-r--r--externals/oboe/src/opensles/AudioOutputStreamOpenSLES.cpp457
1 files changed, 457 insertions, 0 deletions
diff --git a/externals/oboe/src/opensles/AudioOutputStreamOpenSLES.cpp b/externals/oboe/src/opensles/AudioOutputStreamOpenSLES.cpp
new file mode 100644
index 0000000000..1b94885711
--- /dev/null
+++ b/externals/oboe/src/opensles/AudioOutputStreamOpenSLES.cpp
@@ -0,0 +1,457 @@
+/*
+ * 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 <cassert>
+
+#include "common/OboeDebug.h"
+#include "oboe/AudioClock.h"
+#include "oboe/AudioStreamBuilder.h"
+#include "AudioOutputStreamOpenSLES.h"
+#include "AudioStreamOpenSLES.h"
+#include "OpenSLESUtilities.h"
+#include "OutputMixerOpenSLES.h"
+
+using namespace oboe;
+
+static SLuint32 OpenSLES_convertOutputUsage(Usage oboeUsage) {
+ SLuint32 openslStream;
+ switch(oboeUsage) {
+ case Usage::Media:
+ case Usage::Game:
+ openslStream = SL_ANDROID_STREAM_MEDIA;
+ break;
+ case Usage::VoiceCommunication:
+ case Usage::VoiceCommunicationSignalling:
+ openslStream = SL_ANDROID_STREAM_VOICE;
+ break;
+ case Usage::Alarm:
+ openslStream = SL_ANDROID_STREAM_ALARM;
+ break;
+ case Usage::Notification:
+ case Usage::NotificationEvent:
+ openslStream = SL_ANDROID_STREAM_NOTIFICATION;
+ break;
+ case Usage::NotificationRingtone:
+ openslStream = SL_ANDROID_STREAM_RING;
+ break;
+ case Usage::AssistanceAccessibility:
+ case Usage::AssistanceNavigationGuidance:
+ case Usage::AssistanceSonification:
+ case Usage::Assistant:
+ default:
+ openslStream = SL_ANDROID_STREAM_SYSTEM;
+ break;
+ }
+ return openslStream;
+}
+
+AudioOutputStreamOpenSLES::AudioOutputStreamOpenSLES(const AudioStreamBuilder &builder)
+ : AudioStreamOpenSLES(builder) {
+}
+
+// These will wind up in <SLES/OpenSLES_Android.h>
+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);
+
+SLuint32 AudioOutputStreamOpenSLES::channelCountToChannelMask(int channelCount) const {
+ SLuint32 channelMask = 0;
+
+ switch (channelCount) {
+ 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;
+
+ default:
+ channelMask = channelCountToChannelMaskDefault(channelCount);
+ break;
+ }
+ return channelMask;
+}
+
+Result AudioOutputStreamOpenSLES::open() {
+ logUnsupportedAttributes();
+
+ SLAndroidConfigurationItf configItf = nullptr;
+
+
+ if (getSdkVersion() < __ANDROID_API_L__ && mFormat == AudioFormat::Float){
+ // TODO: Allow floating point format on API <21 using float->int16 converter
+ return Result::ErrorInvalidFormat;
+ }
+
+ // If audio format is unspecified then choose a suitable default.
+ // API 21+: FLOAT
+ // API <21: INT16
+ if (mFormat == AudioFormat::Unspecified){
+ mFormat = (getSdkVersion() < __ANDROID_API_L__) ?
+ AudioFormat::I16 : AudioFormat::Float;
+ }
+
+ 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 = static_cast<SLuint32>(getBytesPerSample() * kBitsPerByte);
+
+ // configure audio source
+ mBufferQueueLength = calculateOptimalBufferQueueLength();
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType
+ static_cast<SLuint32>(mBufferQueueLength)}; // numBuffers
+
+ // Define the audio data format.
+ SLDataFormat_PCM format_pcm = {
+ SL_DATAFORMAT_PCM, // formatType
+ static_cast<SLuint32>(mChannelCount), // numChannels
+ static_cast<SLuint32>(mSampleRate * kMillisPerSecond), // milliSamplesPerSec
+ bitsPerSample, // mBitsPerSample
+ bitsPerSample, // containerSize;
+ channelCountToChannelMask(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 (getSdkVersion() >= __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;
+ }
+
+ // Configure the stream.
+ result = (*mObjectInterface)->GetInterface(mObjectInterface,
+ EngineOpenSLES::getInstance().getIidAndroidConfiguration(),
+ (void *)&configItf);
+ if (SL_RESULT_SUCCESS != result) {
+ LOGW("%s() GetInterface(SL_IID_ANDROIDCONFIGURATION) failed with %s",
+ __func__, getSLErrStr(result));
+ } else {
+ result = configurePerformanceMode(configItf);
+ if (SL_RESULT_SUCCESS != result) {
+ goto error;
+ }
+
+ SLuint32 presetValue = OpenSLES_convertOutputUsage(getUsage());
+ result = (*configItf)->SetConfiguration(configItf,
+ SL_ANDROID_KEY_STREAM_TYPE,
+ &presetValue,
+ sizeof(presetValue));
+ if (SL_RESULT_SUCCESS != 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,
+ EngineOpenSLES::getInstance().getIidPlay(),
+ &mPlayInterface);
+ if (SL_RESULT_SUCCESS != result) {
+ LOGE("GetInterface PLAY result:%s", getSLErrStr(result));
+ goto error;
+ }
+
+ result = finishCommonOpen(configItf);
+ if (SL_RESULT_SUCCESS != result) {
+ goto error;
+ }
+
+ setState(StreamState::Open);
+ return Result::OK;
+
+error:
+ close(); // Clean up various OpenSL objects and prevent resource leaks.
+ return Result::ErrorInternal; // TODO convert error from SLES to OBOE
+}
+
+Result AudioOutputStreamOpenSLES::onAfterDestroy() {
+ OutputMixerOpenSL::getInstance().close();
+ return Result::OK;
+}
+
+Result AudioOutputStreamOpenSLES::close() {
+ LOGD("AudioOutputStreamOpenSLES::%s()", __func__);
+ std::lock_guard<std::mutex> lock(mLock);
+ Result result = Result::OK;
+ if (getState() == StreamState::Closed) {
+ result = Result::ErrorClosed;
+ } else {
+ (void) requestPause_l();
+ if (OboeGlobals::areWorkaroundsEnabled()) {
+ sleepBeforeClose();
+ }
+ // invalidate any interfaces
+ mPlayInterface = nullptr;
+ result = AudioStreamOpenSLES::close_l();
+ }
+ return result;
+}
+
+Result AudioOutputStreamOpenSLES::setPlayState_l(SLuint32 newState) {
+ LOGD("AudioOutputStreamOpenSLES::%s(%d) called", __func__, newState);
+ Result result = Result::OK;
+
+ if (mPlayInterface == nullptr){
+ LOGE("AudioOutputStreamOpenSLES::%s() mPlayInterface is null", __func__);
+ return Result::ErrorInvalidState;
+ }
+
+ SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
+ if (SL_RESULT_SUCCESS != slResult) {
+ LOGW("AudioOutputStreamOpenSLES(): %s() returned %s", __func__, getSLErrStr(slResult));
+ result = Result::ErrorInternal; // TODO convert slResult to Result::Error
+ }
+ return result;
+}
+
+Result AudioOutputStreamOpenSLES::requestStart() {
+ LOGD("AudioOutputStreamOpenSLES::%s() called", __func__);
+
+ mLock.lock();
+ StreamState initialState = getState();
+ switch (initialState) {
+ case StreamState::Starting:
+ case StreamState::Started:
+ mLock.unlock();
+ return Result::OK;
+ case StreamState::Closed:
+ mLock.unlock();
+ return Result::ErrorClosed;
+ default:
+ break;
+ }
+
+ // We use a callback if the user requests one
+ // OR if we have an internal callback to read the blocking IO buffer.
+ setDataCallbackEnabled(true);
+
+ setState(StreamState::Starting);
+ closePerformanceHint();
+
+ if (getBufferDepth(mSimpleBufferQueueInterface) == 0) {
+ // Enqueue the first buffer if needed to start the streaming.
+ // We may need to stop the current stream.
+ bool shouldStopStream = processBufferCallback(mSimpleBufferQueueInterface);
+ if (shouldStopStream) {
+ LOGD("Stopping the current stream.");
+ if (requestStop_l() != Result::OK) {
+ LOGW("Failed to flush the stream. Error %s", convertToText(flush()));
+ }
+ setState(initialState);
+ mLock.unlock();
+ return Result::ErrorClosed;
+ }
+ }
+
+ Result result = setPlayState_l(SL_PLAYSTATE_PLAYING);
+ if (result == Result::OK) {
+ setState(StreamState::Started);
+ mLock.unlock();
+ } else {
+ setState(initialState);
+ mLock.unlock();
+ }
+ return result;
+}
+
+Result AudioOutputStreamOpenSLES::requestPause() {
+ LOGD("AudioOutputStreamOpenSLES::%s() called", __func__);
+ std::lock_guard<std::mutex> lock(mLock);
+ return requestPause_l();
+}
+
+// Call under mLock
+Result AudioOutputStreamOpenSLES::requestPause_l() {
+ StreamState initialState = getState();
+ switch (initialState) {
+ case StreamState::Pausing:
+ case StreamState::Paused:
+ return Result::OK;
+ case StreamState::Uninitialized:
+ case StreamState::Closed:
+ return Result::ErrorClosed;
+ default:
+ break;
+ }
+
+ setState(StreamState::Pausing);
+ Result result = setPlayState_l(SL_PLAYSTATE_PAUSED);
+ if (result == Result::OK) {
+ // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused.
+ int64_t framesWritten = getFramesWritten();
+ if (framesWritten >= 0) {
+ setFramesRead(framesWritten);
+ }
+ setState(StreamState::Paused);
+ } else {
+ setState(initialState);
+ }
+ return result;
+}
+
+/**
+ * Flush/clear the queue buffers
+ */
+Result AudioOutputStreamOpenSLES::requestFlush() {
+ std::lock_guard<std::mutex> lock(mLock);
+ return requestFlush_l();
+}
+
+Result AudioOutputStreamOpenSLES::requestFlush_l() {
+ LOGD("AudioOutputStreamOpenSLES::%s() called", __func__);
+ if (getState() == StreamState::Closed) {
+ return Result::ErrorClosed;
+ }
+
+ Result result = Result::OK;
+ if (mPlayInterface == nullptr || mSimpleBufferQueueInterface == nullptr) {
+ result = Result::ErrorInvalidState;
+ } else {
+ SLresult slResult = (*mSimpleBufferQueueInterface)->Clear(mSimpleBufferQueueInterface);
+ if (slResult != SL_RESULT_SUCCESS){
+ LOGW("Failed to clear buffer queue. OpenSLES error: %s", getSLErrStr(slResult));
+ result = Result::ErrorInternal;
+ }
+ }
+ return result;
+}
+
+Result AudioOutputStreamOpenSLES::requestStop() {
+ std::lock_guard<std::mutex> lock(mLock);
+ return requestStop_l();
+}
+
+Result AudioOutputStreamOpenSLES::requestStop_l() {
+ StreamState initialState = getState();
+ LOGD("AudioOutputStreamOpenSLES::%s() called, initialState = %d", __func__, initialState);
+ switch (initialState) {
+ case StreamState::Stopping:
+ case StreamState::Stopped:
+ return Result::OK;
+ case StreamState::Uninitialized:
+ case StreamState::Closed:
+ return Result::ErrorClosed;
+ default:
+ break;
+ }
+
+ setState(StreamState::Stopping);
+
+ Result result = setPlayState_l(SL_PLAYSTATE_STOPPED);
+ if (result == Result::OK) {
+
+ // Also clear the buffer queue so the old data won't be played if the stream is restarted.
+ // Call the _l function that expects to already be under a lock.
+ if (requestFlush_l() != Result::OK) {
+ LOGW("Failed to flush the stream. Error %s", convertToText(flush()));
+ }
+
+ mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
+ int64_t framesWritten = getFramesWritten();
+ if (framesWritten >= 0) {
+ setFramesRead(framesWritten);
+ }
+ setState(StreamState::Stopped);
+ } else {
+ setState(initialState);
+ }
+ return result;
+}
+
+void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) {
+ int64_t millisWritten = framesRead * kMillisPerSecond / getSampleRate();
+ mPositionMillis.set(millisWritten);
+}
+
+void AudioOutputStreamOpenSLES::updateFramesRead() {
+ if (usingFIFO()) {
+ AudioStreamBuffered::updateFramesRead();
+ } else {
+ mFramesRead = getFramesProcessedByServer();
+ }
+}
+
+Result AudioOutputStreamOpenSLES::updateServiceFrameCounter() {
+ Result result = Result::OK;
+ // Avoid deadlock if another thread is trying to stop or close this stream
+ // and this is being called from a callback.
+ if (mLock.try_lock()) {
+
+ if (mPlayInterface == nullptr) {
+ mLock.unlock();
+ return Result::ErrorNull;
+ }
+ SLmillisecond msec = 0;
+ SLresult slResult = (*mPlayInterface)->GetPosition(mPlayInterface, &msec);
+ if (SL_RESULT_SUCCESS != slResult) {
+ LOGW("%s(): GetPosition() returned %s", __func__, getSLErrStr(slResult));
+ // set result based on SLresult
+ result = Result::ErrorInternal;
+ } else {
+ mPositionMillis.update32(msec);
+ }
+ mLock.unlock();
+ }
+ return result;
+}