Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
Signed by untrusted user: crueter
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -0,0 +1,359 @@
/*
* 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/AudioStreamBuilder.h"
#include "AudioInputStreamOpenSLES.h"
#include "AudioStreamOpenSLES.h"
#include "OpenSLESUtilities.h"
using namespace oboe;
static SLuint32 OpenSLES_convertInputPreset(InputPreset oboePreset) {
SLuint32 openslPreset = SL_ANDROID_RECORDING_PRESET_NONE;
switch(oboePreset) {
case InputPreset::Generic:
openslPreset = SL_ANDROID_RECORDING_PRESET_GENERIC;
break;
case InputPreset::Camcorder:
openslPreset = SL_ANDROID_RECORDING_PRESET_CAMCORDER;
break;
case InputPreset::VoiceRecognition:
case InputPreset::VoicePerformance:
openslPreset = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
break;
case InputPreset::VoiceCommunication:
openslPreset = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION;
break;
case InputPreset::Unprocessed:
openslPreset = SL_ANDROID_RECORDING_PRESET_UNPROCESSED;
break;
default:
break;
}
return openslPreset;
}
AudioInputStreamOpenSLES::AudioInputStreamOpenSLES(const AudioStreamBuilder &builder)
: AudioStreamOpenSLES(builder) {
}
AudioInputStreamOpenSLES::~AudioInputStreamOpenSLES() {
}
// Calculate masks specific to INPUT streams.
SLuint32 AudioInputStreamOpenSLES::channelCountToChannelMask(int channelCount) const {
// Derived from internal sles_channel_in_mask_from_count(chanCount);
// in "frameworks/wilhelm/src/android/channels.cpp".
// Yes, it seems strange to use SPEAKER constants to describe inputs.
// But that is how OpenSL ES does it internally.
switch (channelCount) {
case 1:
return SL_SPEAKER_FRONT_LEFT;
case 2:
return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
default:
return channelCountToChannelMaskDefault(channelCount);
}
}
Result AudioInputStreamOpenSLES::open() {
logUnsupportedAttributes();
SLAndroidConfigurationItf configItf = nullptr;
if (getSdkVersion() < __ANDROID_API_M__ && mFormat == AudioFormat::Float){
// TODO: Allow floating point format on API <23 using float->int16 converter
return Result::ErrorInvalidFormat;
}
// If audio format is unspecified then choose a suitable default.
// API 23+: FLOAT
// API <23: INT16
if (mFormat == AudioFormat::Unspecified){
mFormat = (getSdkVersion() < __ANDROID_API_M__) ?
AudioFormat::I16 : AudioFormat::Float;
}
Result oboeResult = AudioStreamOpenSLES::open();
if (Result::OK != oboeResult) return oboeResult;
SLuint32 bitsPerSample = static_cast<SLuint32>(getBytesPerSample() * kBitsPerByte);
// configure audio sink
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(),
};
SLDataSink audioSink = {&loc_bufq, &format_pcm};
/**
* API 23 (Marshmallow) introduced support for floating-point data representation and an
* extended data format type: SLAndroidDataFormat_PCM_EX for recording streams (playback streams
* got this in API 21). If running on API 23+ use this newer format type, creating it from our
* original format.
*/
SLAndroidDataFormat_PCM_EX format_pcm_ex;
if (getSdkVersion() >= __ANDROID_API_M__) {
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 stream.
result = (*mObjectInterface)->GetInterface(mObjectInterface,
EngineOpenSLES::getInstance().getIidAndroidConfiguration(),
&configItf);
if (SL_RESULT_SUCCESS != result) {
LOGW("%s() GetInterface(SL_IID_ANDROIDCONFIGURATION) failed with %s",
__func__, getSLErrStr(result));
} else {
if (getInputPreset() == InputPreset::VoicePerformance) {
LOGD("OpenSL ES does not support InputPreset::VoicePerformance. Use VoiceRecognition.");
mInputPreset = InputPreset::VoiceRecognition;
}
SLuint32 presetValue = OpenSLES_convertInputPreset(getInputPreset());
result = (*configItf)->SetConfiguration(configItf,
SL_ANDROID_KEY_RECORDING_PRESET,
&presetValue,
sizeof(SLuint32));
if (SL_RESULT_SUCCESS != result
&& presetValue != SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION) {
presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
LOGD("Setting InputPreset %d failed. Using VoiceRecognition instead.", getInputPreset());
mInputPreset = InputPreset::VoiceRecognition;
(*configItf)->SetConfiguration(configItf,
SL_ANDROID_KEY_RECORDING_PRESET,
&presetValue,
sizeof(SLuint32));
}
result = configurePerformanceMode(configItf);
if (SL_RESULT_SUCCESS != result) {
goto error;
}
}
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,
EngineOpenSLES::getInstance().getIidRecord(),
&mRecordInterface);
if (SL_RESULT_SUCCESS != result) {
LOGE("GetInterface RECORD 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 AudioInputStreamOpenSLES::close() {
LOGD("AudioInputStreamOpenSLES::%s()", __func__);
std::lock_guard<std::mutex> lock(mLock);
Result result = Result::OK;
if (getState() == StreamState::Closed) {
result = Result::ErrorClosed;
} else {
(void) requestStop_l();
if (OboeGlobals::areWorkaroundsEnabled()) {
sleepBeforeClose();
}
// invalidate any interfaces
mRecordInterface = nullptr;
result = AudioStreamOpenSLES::close_l();
}
return result;
}
Result AudioInputStreamOpenSLES::setRecordState_l(SLuint32 newState) {
LOGD("AudioInputStreamOpenSLES::%s(%u)", __func__, newState);
Result result = Result::OK;
if (mRecordInterface == nullptr) {
LOGW("AudioInputStreamOpenSLES::%s() mRecordInterface is null", __func__);
return Result::ErrorInvalidState;
}
SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState);
//LOGD("AudioInputStreamOpenSLES::%s(%u) returned %u", __func__, newState, slResult);
if (SL_RESULT_SUCCESS != slResult) {
LOGE("AudioInputStreamOpenSLES::%s(%u) returned error %s",
__func__, newState, getSLErrStr(slResult));
result = Result::ErrorInternal; // TODO review
}
return result;
}
Result AudioInputStreamOpenSLES::requestStart() {
LOGD("AudioInputStreamOpenSLES(): %s() called", __func__);
std::lock_guard<std::mutex> lock(mLock);
StreamState initialState = getState();
switch (initialState) {
case StreamState::Starting:
case StreamState::Started:
return Result::OK;
case StreamState::Closed:
return Result::ErrorClosed;
default:
break;
}
// We use a callback if the user requests one
// OR if we have an internal callback to fill the blocking IO buffer.
setDataCallbackEnabled(true);
setState(StreamState::Starting);
closePerformanceHint();
if (getBufferDepth(mSimpleBufferQueueInterface) == 0) {
// Enqueue the first buffer to start the streaming.
// This does not call the callback function.
enqueueCallbackBuffer(mSimpleBufferQueueInterface);
}
Result result = setRecordState_l(SL_RECORDSTATE_RECORDING);
if (result == Result::OK) {
setState(StreamState::Started);
} else {
setState(initialState);
}
return result;
}
Result AudioInputStreamOpenSLES::requestPause() {
LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input "
"streams", __func__);
return Result::ErrorUnimplemented; // Matches AAudio behavior.
}
Result AudioInputStreamOpenSLES::requestFlush() {
LOGW("AudioInputStreamOpenSLES::%s() is intentionally not implemented for input "
"streams", __func__);
return Result::ErrorUnimplemented; // Matches AAudio behavior.
}
Result AudioInputStreamOpenSLES::requestStop() {
LOGD("AudioInputStreamOpenSLES(): %s() called", __func__);
std::lock_guard<std::mutex> lock(mLock);
return requestStop_l();
}
// Call under mLock
Result AudioInputStreamOpenSLES::requestStop_l() {
StreamState initialState = getState();
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 = setRecordState_l(SL_RECORDSTATE_STOPPED);
if (result == Result::OK) {
mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
setState(StreamState::Stopped);
} else {
setState(initialState);
}
return result;
}
void AudioInputStreamOpenSLES::updateFramesWritten() {
if (usingFIFO()) {
AudioStreamBuffered::updateFramesWritten();
} else {
mFramesWritten = getFramesProcessedByServer();
}
}
Result AudioInputStreamOpenSLES::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 (mRecordInterface == nullptr) {
mLock.unlock();
return Result::ErrorNull;
}
SLmillisecond msec = 0;
SLresult slResult = (*mRecordInterface)->GetPosition(mRecordInterface, &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;
}

View file

@ -0,0 +1,64 @@
/*
* 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 "oboe/Oboe.h"
#include "EngineOpenSLES.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;
protected:
Result requestStop_l();
Result updateServiceFrameCounter() override;
void updateFramesWritten() override;
private:
SLuint32 channelCountToChannelMask(int chanCount) const;
Result setRecordState_l(SLuint32 newState);
SLRecordItf mRecordInterface = nullptr;
};
} // namespace oboe
#endif //AUDIO_INPUT_STREAM_OPENSL_ES_H_

View file

@ -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;
}

View file

@ -0,0 +1,78 @@
/*
* 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 "oboe/Oboe.h"
#include "EngineOpenSLES.h"
#include "AudioStreamOpenSLES.h"
namespace oboe {
/**
* INTERNAL USE ONLY
*/
class AudioOutputStreamOpenSLES : public AudioStreamOpenSLES {
public:
AudioOutputStreamOpenSLES();
explicit AudioOutputStreamOpenSLES(const AudioStreamBuilder &builder);
virtual ~AudioOutputStreamOpenSLES() = default;
Result open() override;
Result close() override;
Result requestStart() override;
Result requestPause() override;
Result requestFlush() override;
Result requestStop() override;
protected:
Result requestPause_l();
void setFramesRead(int64_t framesRead);
Result updateServiceFrameCounter() override;
void updateFramesRead() override;
private:
SLuint32 channelCountToChannelMask(int chanCount) const;
Result onAfterDestroy() override;
Result requestFlush_l();
Result requestStop_l();
/**
* Set OpenSL ES PLAYSTATE.
*
* @param newState SL_PLAYSTATE_PAUSED, SL_PLAYSTATE_PLAYING, SL_PLAYSTATE_STOPPED
* @return
*/
Result setPlayState_l(SLuint32 newState);
SLPlayItf mPlayInterface = nullptr;
};
} // namespace oboe
#endif //AUDIO_OUTPUT_STREAM_OPENSL_ES_H_

View file

@ -0,0 +1,285 @@
/*
* Copyright (C) 2016 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 <memory>
#include "oboe/Oboe.h"
#include "common/OboeDebug.h"
#include "oboe/AudioClock.h"
#include "opensles/AudioStreamBuffered.h"
namespace oboe {
constexpr int kDefaultBurstsPerBuffer = 16; // arbitrary, allows dynamic latency tuning
constexpr int kMinBurstsPerBuffer = 4; // arbitrary, allows dynamic latency tuning
constexpr int kMinFramesPerBuffer = 48 * 32; // arbitrary
/*
* AudioStream with a FifoBuffer
*/
AudioStreamBuffered::AudioStreamBuffered(const AudioStreamBuilder &builder)
: AudioStream(builder) {
}
void AudioStreamBuffered::allocateFifo() {
// If the caller does not provide a callback use our own internal
// callback that reads data from the FIFO.
if (usingFIFO()) {
// FIFO is configured with the same format and channels as the stream.
int32_t capacityFrames = getBufferCapacityInFrames();
if (capacityFrames == oboe::kUnspecified) {
capacityFrames = getFramesPerBurst() * kDefaultBurstsPerBuffer;
} else {
int32_t minFramesPerBufferByBursts = getFramesPerBurst() * kMinBurstsPerBuffer;
if (capacityFrames <= minFramesPerBufferByBursts) {
capacityFrames = minFramesPerBufferByBursts;
} else {
capacityFrames = std::max(kMinFramesPerBuffer, capacityFrames);
// round up to nearest burst
int32_t numBursts = (capacityFrames + getFramesPerBurst() - 1)
/ getFramesPerBurst();
capacityFrames = numBursts * getFramesPerBurst();
}
}
mFifoBuffer = std::make_unique<FifoBuffer>(getBytesPerFrame(), capacityFrames);
mBufferCapacityInFrames = capacityFrames;
mBufferSizeInFrames = mBufferCapacityInFrames;
}
}
void AudioStreamBuffered::updateFramesWritten() {
if (mFifoBuffer) {
mFramesWritten = static_cast<int64_t>(mFifoBuffer->getWriteCounter());
} // or else it will get updated by processBufferCallback()
}
void AudioStreamBuffered::updateFramesRead() {
if (mFifoBuffer) {
mFramesRead = static_cast<int64_t>(mFifoBuffer->getReadCounter());
} // or else it will get updated by processBufferCallback()
}
// This is called by the OpenSL ES callback to read or write the back end of the FIFO.
DataCallbackResult AudioStreamBuffered::onDefaultCallback(void *audioData, int numFrames) {
int32_t framesTransferred = 0;
if (getDirection() == oboe::Direction::Output) {
// Read from the FIFO and write to audioData, clear part of buffer if not enough data.
framesTransferred = mFifoBuffer->readNow(audioData, numFrames);
} else {
// Read from audioData and write to the FIFO
framesTransferred = mFifoBuffer->write(audioData, numFrames); // There is no writeNow()
}
if (framesTransferred < numFrames) {
LOGD("AudioStreamBuffered::%s(): xrun! framesTransferred = %d, numFrames = %d",
__func__, framesTransferred, numFrames);
// TODO If we do not allow FIFO to wrap then our timestamps will drift when there is an XRun!
incrementXRunCount();
}
markCallbackTime(static_cast<int32_t>(numFrames)); // so foreground knows how long to wait.
return DataCallbackResult::Continue;
}
void AudioStreamBuffered::markCallbackTime(int32_t numFrames) {
mLastBackgroundSize = numFrames;
mBackgroundRanAtNanoseconds = AudioClock::getNanoseconds();
}
int64_t AudioStreamBuffered::predictNextCallbackTime() {
if (mBackgroundRanAtNanoseconds == 0) {
return 0;
}
int64_t nanosPerBuffer = (kNanosPerSecond * mLastBackgroundSize) / getSampleRate();
const int64_t margin = 200 * kNanosPerMicrosecond; // arbitrary delay so we wake up just after
return mBackgroundRanAtNanoseconds + nanosPerBuffer + margin;
}
// Common code for read/write.
// @return Result::OK with frames read/written, or Result::Error*
ResultWithValue<int32_t> AudioStreamBuffered::transfer(
void *readBuffer,
const void *writeBuffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
// Validate arguments.
if (readBuffer != nullptr && writeBuffer != nullptr) {
LOGE("AudioStreamBuffered::%s(): both buffers are not NULL", __func__);
return ResultWithValue<int32_t>(Result::ErrorInternal);
}
if (getDirection() == Direction::Input && readBuffer == nullptr) {
LOGE("AudioStreamBuffered::%s(): readBuffer is NULL", __func__);
return ResultWithValue<int32_t>(Result::ErrorNull);
}
if (getDirection() == Direction::Output && writeBuffer == nullptr) {
LOGE("AudioStreamBuffered::%s(): writeBuffer is NULL", __func__);
return ResultWithValue<int32_t>(Result::ErrorNull);
}
if (numFrames < 0) {
LOGE("AudioStreamBuffered::%s(): numFrames is negative", __func__);
return ResultWithValue<int32_t>(Result::ErrorOutOfRange);
} else if (numFrames == 0) {
return ResultWithValue<int32_t>(numFrames);
}
if (timeoutNanoseconds < 0) {
LOGE("AudioStreamBuffered::%s(): timeoutNanoseconds is negative", __func__);
return ResultWithValue<int32_t>(Result::ErrorOutOfRange);
}
int32_t result = 0;
uint8_t *readData = reinterpret_cast<uint8_t *>(readBuffer);
const uint8_t *writeData = reinterpret_cast<const uint8_t *>(writeBuffer);
int32_t framesLeft = numFrames;
int64_t timeToQuit = 0;
bool repeat = true;
// Calculate when to timeout.
if (timeoutNanoseconds > 0) {
timeToQuit = AudioClock::getNanoseconds() + timeoutNanoseconds;
}
// Loop until we get the data, or we have an error, or we timeout.
do {
// read or write
if (getDirection() == Direction::Input) {
result = mFifoBuffer->read(readData, framesLeft);
if (result > 0) {
readData += mFifoBuffer->convertFramesToBytes(result);
framesLeft -= result;
}
} else {
// between zero and capacity
uint32_t fullFrames = mFifoBuffer->getFullFramesAvailable();
// Do not write above threshold size.
int32_t emptyFrames = getBufferSizeInFrames() - static_cast<int32_t>(fullFrames);
int32_t framesToWrite = std::max(0, std::min(framesLeft, emptyFrames));
result = mFifoBuffer->write(writeData, framesToWrite);
if (result > 0) {
writeData += mFifoBuffer->convertFramesToBytes(result);
framesLeft -= result;
}
}
// If we need more data then sleep and try again.
if (framesLeft > 0 && result >= 0 && timeoutNanoseconds > 0) {
int64_t timeNow = AudioClock::getNanoseconds();
if (timeNow >= timeToQuit) {
LOGE("AudioStreamBuffered::%s(): TIMEOUT", __func__);
repeat = false; // TIMEOUT
} else {
// Figure out how long to sleep.
int64_t sleepForNanos;
int64_t wakeTimeNanos = predictNextCallbackTime();
if (wakeTimeNanos <= 0) {
// No estimate available. Sleep for one burst.
sleepForNanos = (getFramesPerBurst() * kNanosPerSecond) / getSampleRate();
} else {
// Don't sleep past timeout.
if (wakeTimeNanos > timeToQuit) {
wakeTimeNanos = timeToQuit;
}
sleepForNanos = wakeTimeNanos - timeNow;
// Avoid rapid loop with no sleep.
const int64_t minSleepTime = kNanosPerMillisecond; // arbitrary
if (sleepForNanos < minSleepTime) {
sleepForNanos = minSleepTime;
}
}
AudioClock::sleepForNanos(sleepForNanos);
}
} else {
repeat = false;
}
} while(repeat);
if (result < 0) {
return ResultWithValue<int32_t>(static_cast<Result>(result));
} else {
int32_t framesWritten = numFrames - framesLeft;
return ResultWithValue<int32_t>(framesWritten);
}
}
// Write to the FIFO so the callback can read from it.
ResultWithValue<int32_t> AudioStreamBuffered::write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
if (getState() == StreamState::Closed){
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
if (getDirection() == Direction::Input) {
return ResultWithValue<int32_t>(Result::ErrorUnavailable); // TODO review, better error code?
}
Result result = updateServiceFrameCounter();
if (result != Result::OK) return ResultWithValue<int32_t>(static_cast<Result>(result));
return transfer(nullptr, buffer, numFrames, timeoutNanoseconds);
}
// Read data from the FIFO that was written by the callback.
ResultWithValue<int32_t> AudioStreamBuffered::read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
if (getState() == StreamState::Closed){
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
if (getDirection() == Direction::Output) {
return ResultWithValue<int32_t>(Result::ErrorUnavailable); // TODO review, better error code?
}
Result result = updateServiceFrameCounter();
if (result != Result::OK) return ResultWithValue<int32_t>(static_cast<Result>(result));
return transfer(buffer, nullptr, numFrames, timeoutNanoseconds);
}
// Only supported when we are not using a callback.
ResultWithValue<int32_t> AudioStreamBuffered::setBufferSizeInFrames(int32_t requestedFrames)
{
if (getState() == StreamState::Closed){
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
if (!mFifoBuffer) {
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
}
if (requestedFrames > mFifoBuffer->getBufferCapacityInFrames()) {
requestedFrames = mFifoBuffer->getBufferCapacityInFrames();
} else if (requestedFrames < getFramesPerBurst()) {
requestedFrames = getFramesPerBurst();
}
mBufferSizeInFrames = requestedFrames;
return ResultWithValue<int32_t>(requestedFrames);
}
int32_t AudioStreamBuffered::getBufferCapacityInFrames() const {
if (mFifoBuffer) {
return mFifoBuffer->getBufferCapacityInFrames();
} else {
return AudioStream::getBufferCapacityInFrames();
}
}
bool AudioStreamBuffered::isXRunCountSupported() const {
// XRun count is only supported if we're using blocking I/O (not callbacks)
return (!isDataCallbackSpecified());
}
} // namespace oboe

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2016 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_STREAM_BUFFERED_H
#define OBOE_STREAM_BUFFERED_H
#include <cstring>
#include <cassert>
#include "common/OboeDebug.h"
#include "oboe/AudioStream.h"
#include "oboe/AudioStreamCallback.h"
#include "oboe/FifoBuffer.h"
namespace oboe {
// A stream that contains a FIFO buffer.
// This is used to implement blocking reads and writes.
class AudioStreamBuffered : public AudioStream {
public:
AudioStreamBuffered();
explicit AudioStreamBuffered(const AudioStreamBuilder &builder);
void allocateFifo();
ResultWithValue<int32_t> write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
int32_t getBufferCapacityInFrames() const override;
ResultWithValue<int32_t> getXRunCount() override {
return ResultWithValue<int32_t>(mXRunCount);
}
bool isXRunCountSupported() const override;
protected:
DataCallbackResult onDefaultCallback(void *audioData, int numFrames) override;
// If there is no callback then we need a FIFO between the App and OpenSL ES.
bool usingFIFO() const { return !isDataCallbackSpecified(); }
virtual Result updateServiceFrameCounter() = 0;
void updateFramesRead() override;
void updateFramesWritten() override;
private:
int64_t predictNextCallbackTime();
void markCallbackTime(int32_t numFrames);
// Read or write to the FIFO.
// Only pass one pointer and set the other to nullptr.
ResultWithValue<int32_t> transfer(void *readBuffer,
const void *writeBuffer,
int32_t numFrames,
int64_t timeoutNanoseconds);
void incrementXRunCount() {
++mXRunCount;
}
std::unique_ptr<FifoBuffer> mFifoBuffer{};
int64_t mBackgroundRanAtNanoseconds = 0;
int32_t mLastBackgroundSize = 0;
int32_t mXRunCount = 0;
};
} // namespace oboe
#endif //OBOE_STREAM_BUFFERED_H

View file

@ -0,0 +1,523 @@
/* Copyright 2015 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 <sys/types.h>
#include <cassert>
#include <android/log.h>
#include "common/OboeDebug.h"
#include "oboe/AudioClock.h"
#include "oboe/AudioStream.h"
#include "oboe/AudioStreamBuilder.h"
#include "EngineOpenSLES.h"
#include "AudioStreamOpenSLES.h"
#include "OpenSLESUtilities.h"
using namespace oboe;
AudioStreamOpenSLES::AudioStreamOpenSLES(const AudioStreamBuilder &builder)
: AudioStreamBuffered(builder) {
// OpenSL ES does not support device IDs. So overwrite value from builder.
mDeviceIds.clear();
// OpenSL ES does not support session IDs. So overwrite value from builder.
mSessionId = SessionId::None;
}
static constexpr int32_t kHighLatencyBufferSizeMillis = 20; // typical Android period
static constexpr SLuint32 kAudioChannelCountMax = 30; // TODO Why 30?
static constexpr SLuint32 SL_ANDROID_UNKNOWN_CHANNELMASK = 0; // Matches name used internally.
SLuint32 AudioStreamOpenSLES::channelCountToChannelMaskDefault(int channelCount) const {
if (channelCount > kAudioChannelCountMax) {
return SL_ANDROID_UNKNOWN_CHANNELMASK;
}
SLuint32 bitfield = (1 << channelCount) - 1;
// Check for OS at run-time.
if(getSdkVersion() >= __ANDROID_API_N__) {
return SL_ANDROID_MAKE_INDEXED_CHANNEL_MASK(bitfield);
}
// Indexed channels masks were added in N.
// For before N, the best we can do is use a positional channel mask.
return bitfield;
}
static bool s_isLittleEndian() {
static uint32_t value = 1;
return (*reinterpret_cast<uint8_t *>(&value) == 1); // Does address point to LSB?
}
SLuint32 AudioStreamOpenSLES::getDefaultByteOrder() {
return s_isLittleEndian() ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
}
Result AudioStreamOpenSLES::open() {
#ifndef OBOE_SUPPRESS_LOG_SPAM
LOGI("AudioStreamOpenSLES::open() chans=%d, rate=%d", mChannelCount, mSampleRate);
#endif
// OpenSL ES only supports I16 and Float
if (mFormat != AudioFormat::I16 && mFormat != AudioFormat::Float) {
LOGW("%s() Android's OpenSL ES implementation only supports I16 and Float. Format: %s",
__func__, oboe::convertToText(mFormat));
return Result::ErrorInvalidFormat;
}
SLresult result = EngineOpenSLES::getInstance().open();
if (SL_RESULT_SUCCESS != result) {
return Result::ErrorInternal;
}
Result oboeResult = AudioStreamBuffered::open();
if (oboeResult != Result::OK) {
EngineOpenSLES::getInstance().close();
return oboeResult;
}
// Convert to defaults if UNSPECIFIED
if (mSampleRate == kUnspecified) {
mSampleRate = DefaultStreamValues::SampleRate;
}
if (mChannelCount == kUnspecified) {
mChannelCount = DefaultStreamValues::ChannelCount;
}
if (mContentType == kUnspecified) {
mContentType = ContentType::Music;
}
if (static_cast<const int32_t>(mUsage) == kUnspecified) {
mUsage = Usage::Media;
}
mSharingMode = SharingMode::Shared;
return Result::OK;
}
SLresult AudioStreamOpenSLES::finishCommonOpen(SLAndroidConfigurationItf configItf) {
// Setting privacy sensitive mode and allowed capture policy are not supported for OpenSL ES.
mPrivacySensitiveMode = PrivacySensitiveMode::Unspecified;
mAllowedCapturePolicy = AllowedCapturePolicy::Unspecified;
// Spatialization Behavior is not supported for OpenSL ES.
mSpatializationBehavior = SpatializationBehavior::Never;
SLresult result = registerBufferQueueCallback();
if (SL_RESULT_SUCCESS != result) {
return result;
}
result = updateStreamParameters(configItf);
if (SL_RESULT_SUCCESS != result) {
return result;
}
Result oboeResult = configureBufferSizes(mSampleRate);
if (Result::OK != oboeResult) {
return (SLresult) oboeResult;
}
allocateFifo();
calculateDefaultDelayBeforeCloseMillis();
return SL_RESULT_SUCCESS;
}
static int32_t roundUpDivideByN(int32_t x, int32_t n) {
return (x + n - 1) / n;
}
int32_t AudioStreamOpenSLES::calculateOptimalBufferQueueLength() {
int32_t queueLength = kBufferQueueLengthDefault;
int32_t likelyFramesPerBurst = estimateNativeFramesPerBurst();
int32_t minCapacity = mBufferCapacityInFrames; // specified by app or zero
// The buffer capacity needs to be at least twice the size of the requested callbackSize
// so that we can have double buffering.
minCapacity = std::max(minCapacity, kDoubleBufferCount * mFramesPerCallback);
if (minCapacity > 0) {
int32_t queueLengthFromCapacity = roundUpDivideByN(minCapacity, likelyFramesPerBurst);
queueLength = std::max(queueLength, queueLengthFromCapacity);
}
queueLength = std::min(queueLength, kBufferQueueLengthMax); // clip to max
// TODO Investigate the effect of queueLength on latency for normal streams. (not low latency)
return queueLength;
}
/**
* The best information we have is if DefaultStreamValues::FramesPerBurst
* was set by the app based on AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER.
* Without that we just have to guess.
* @return
*/
int32_t AudioStreamOpenSLES::estimateNativeFramesPerBurst() {
int32_t framesPerBurst = DefaultStreamValues::FramesPerBurst;
LOGD("AudioStreamOpenSLES:%s() DefaultStreamValues::FramesPerBurst = %d",
__func__, DefaultStreamValues::FramesPerBurst);
framesPerBurst = std::max(framesPerBurst, 16);
// Calculate the size of a fixed duration high latency buffer based on sample rate.
// Estimate sample based on default options in order of priority.
int32_t sampleRate = 48000;
sampleRate = (DefaultStreamValues::SampleRate > 0)
? DefaultStreamValues::SampleRate : sampleRate;
sampleRate = (mSampleRate > 0) ? mSampleRate : sampleRate;
int32_t framesPerHighLatencyBuffer =
(kHighLatencyBufferSizeMillis * sampleRate) / kMillisPerSecond;
// For high latency streams, use a larger buffer size.
// Performance Mode support was added in N_MR1 (7.1)
if (getSdkVersion() >= __ANDROID_API_N_MR1__
&& mPerformanceMode != PerformanceMode::LowLatency
&& framesPerBurst < framesPerHighLatencyBuffer) {
// Find a multiple of framesPerBurst >= framesPerHighLatencyBuffer.
int32_t numBursts = roundUpDivideByN(framesPerHighLatencyBuffer, framesPerBurst);
framesPerBurst *= numBursts;
LOGD("AudioStreamOpenSLES:%s() NOT low latency, numBursts = %d, mSampleRate = %d, set framesPerBurst = %d",
__func__, numBursts, mSampleRate, framesPerBurst);
}
return framesPerBurst;
}
Result AudioStreamOpenSLES::configureBufferSizes(int32_t sampleRate) {
LOGD("AudioStreamOpenSLES:%s(%d) initial mFramesPerBurst = %d, mFramesPerCallback = %d",
__func__, mSampleRate, mFramesPerBurst, mFramesPerCallback);
mFramesPerBurst = estimateNativeFramesPerBurst();
mFramesPerCallback = (mFramesPerCallback > 0) ? mFramesPerCallback : mFramesPerBurst;
LOGD("AudioStreamOpenSLES:%s(%d) final mFramesPerBurst = %d, mFramesPerCallback = %d",
__func__, mSampleRate, mFramesPerBurst, mFramesPerCallback);
mBytesPerCallback = mFramesPerCallback * getBytesPerFrame();
if (mBytesPerCallback <= 0) {
LOGE("AudioStreamOpenSLES::open() bytesPerCallback < 0 = %d, bad format?",
mBytesPerCallback);
return Result::ErrorInvalidFormat; // causing bytesPerFrame == 0
}
for (int i = 0; i < mBufferQueueLength; ++i) {
mCallbackBuffer[i] = std::make_unique<uint8_t[]>(mBytesPerCallback);
}
if (!usingFIFO()) {
mBufferCapacityInFrames = mFramesPerBurst * mBufferQueueLength;
// Check for overflow.
if (mBufferCapacityInFrames <= 0) {
mBufferCapacityInFrames = 0;
LOGE("AudioStreamOpenSLES::open() numeric overflow because mFramesPerBurst = %d",
mFramesPerBurst);
return Result::ErrorOutOfRange;
}
mBufferSizeInFrames = mBufferCapacityInFrames;
}
return Result::OK;
}
SLuint32 AudioStreamOpenSLES::convertPerformanceMode(PerformanceMode oboeMode) const {
SLuint32 openslMode = SL_ANDROID_PERFORMANCE_NONE;
switch(oboeMode) {
case PerformanceMode::None:
openslMode = SL_ANDROID_PERFORMANCE_NONE;
break;
case PerformanceMode::LowLatency:
openslMode = (getSessionId() == SessionId::None) ? SL_ANDROID_PERFORMANCE_LATENCY : SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS;
break;
case PerformanceMode::PowerSaving:
openslMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
break;
default:
break;
}
return openslMode;
}
PerformanceMode AudioStreamOpenSLES::convertPerformanceMode(SLuint32 openslMode) const {
PerformanceMode oboeMode = PerformanceMode::None;
switch(openslMode) {
case SL_ANDROID_PERFORMANCE_NONE:
oboeMode = PerformanceMode::None;
break;
case SL_ANDROID_PERFORMANCE_LATENCY:
case SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS:
oboeMode = PerformanceMode::LowLatency;
break;
case SL_ANDROID_PERFORMANCE_POWER_SAVING:
oboeMode = PerformanceMode::PowerSaving;
break;
default:
break;
}
return oboeMode;
}
void AudioStreamOpenSLES::logUnsupportedAttributes() {
// Log unsupported attributes
// only report if changed from the default
// Device ID
if (!mDeviceIds.empty()) {
LOGW("Device ID [AudioStreamBuilder::setDeviceId()] "
"is not supported on OpenSLES streams.");
}
// Sharing Mode
if (mSharingMode != SharingMode::Shared) {
LOGW("SharingMode [AudioStreamBuilder::setSharingMode()] "
"is not supported on OpenSLES streams.");
}
// Performance Mode
int sdkVersion = getSdkVersion();
if (mPerformanceMode != PerformanceMode::None && sdkVersion < __ANDROID_API_N_MR1__) {
LOGW("PerformanceMode [AudioStreamBuilder::setPerformanceMode()] "
"is not supported on OpenSLES streams running on pre-Android N-MR1 versions.");
}
// Content Type
if (static_cast<const int32_t>(mContentType) != kUnspecified) {
LOGW("ContentType [AudioStreamBuilder::setContentType()] "
"is not supported on OpenSLES streams.");
}
// Session Id
if (mSessionId != SessionId::None) {
LOGW("SessionId [AudioStreamBuilder::setSessionId()] "
"is not supported on OpenSLES streams.");
}
// Privacy Sensitive Mode
if (mPrivacySensitiveMode != PrivacySensitiveMode::Unspecified) {
LOGW("PrivacySensitiveMode [AudioStreamBuilder::setPrivacySensitiveMode()] "
"is not supported on OpenSLES streams.");
}
// Spatialization Behavior
if (mSpatializationBehavior != SpatializationBehavior::Unspecified) {
LOGW("SpatializationBehavior [AudioStreamBuilder::setSpatializationBehavior()] "
"is not supported on OpenSLES streams.");
}
if (mIsContentSpatialized) {
LOGW("Boolean [AudioStreamBuilder::setIsContentSpatialized()] "
"is not supported on OpenSLES streams.");
}
// Allowed Capture Policy
if (mAllowedCapturePolicy != AllowedCapturePolicy::Unspecified) {
LOGW("AllowedCapturePolicy [AudioStreamBuilder::setAllowedCapturePolicy()] "
"is not supported on OpenSLES streams.");
}
// Package Name
if (!mPackageName.empty()) {
LOGW("PackageName [AudioStreamBuilder::setPackageName()] "
"is not supported on OpenSLES streams.");
}
// Attribution Tag
if (!mAttributionTag.empty()) {
LOGW("AttributionTag [AudioStreamBuilder::setAttributionTag()] "
"is not supported on OpenSLES streams.");
}
}
SLresult AudioStreamOpenSLES::configurePerformanceMode(SLAndroidConfigurationItf configItf) {
if (configItf == nullptr) {
LOGW("%s() called with NULL configuration", __func__);
mPerformanceMode = PerformanceMode::None;
return SL_RESULT_INTERNAL_ERROR;
}
if (getSdkVersion() < __ANDROID_API_N_MR1__) {
LOGW("%s() not supported until N_MR1", __func__);
mPerformanceMode = PerformanceMode::None;
return SL_RESULT_SUCCESS;
}
SLresult result = SL_RESULT_SUCCESS;
SLuint32 performanceMode = convertPerformanceMode(getPerformanceMode());
result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
&performanceMode, sizeof(performanceMode));
if (SL_RESULT_SUCCESS != result) {
LOGW("SetConfiguration(PERFORMANCE_MODE, SL %u) returned %s",
performanceMode, getSLErrStr(result));
mPerformanceMode = PerformanceMode::None;
}
return result;
}
SLresult AudioStreamOpenSLES::updateStreamParameters(SLAndroidConfigurationItf configItf) {
SLresult result = SL_RESULT_SUCCESS;
if(getSdkVersion() >= __ANDROID_API_N_MR1__ && configItf != nullptr) {
SLuint32 performanceMode = 0;
SLuint32 performanceModeSize = sizeof(performanceMode);
result = (*configItf)->GetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
&performanceModeSize, &performanceMode);
// A bug in GetConfiguration() before P caused a wrong result code to be returned.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
result = SL_RESULT_SUCCESS; // Ignore actual result before P.
}
if (SL_RESULT_SUCCESS != result) {
LOGW("GetConfiguration(SL_ANDROID_KEY_PERFORMANCE_MODE) returned %d", result);
mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None.
} else {
mPerformanceMode = convertPerformanceMode(performanceMode); // convert SL to Oboe mode
}
} else {
mPerformanceMode = PerformanceMode::None; // If we can't query it then assume None.
}
return result;
}
// This is called under mLock.
Result AudioStreamOpenSLES::close_l() {
LOGD("AudioOutputStreamOpenSLES::%s() called", __func__);
if (mState == StreamState::Closed) {
return Result::ErrorClosed;
}
AudioStreamBuffered::close();
onBeforeDestroy();
// Mark as CLOSED before we unlock for the join.
// This will prevent other threads from trying to close().
setState(StreamState::Closed);
SLObjectItf tempObjectInterface = mObjectInterface;
mObjectInterface = nullptr;
if (tempObjectInterface != nullptr) {
// Temporarily unlock so we can join() the callback thread.
mLock.unlock();
(*tempObjectInterface)->Destroy(tempObjectInterface); // Will join the callback!
mLock.lock();
}
onAfterDestroy();
mSimpleBufferQueueInterface = nullptr;
EngineOpenSLES::getInstance().close();
return Result::OK;
}
SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq) {
SLresult result = (*bq)->Enqueue(
bq, mCallbackBuffer[mCallbackBufferIndex].get(), mBytesPerCallback);
mCallbackBufferIndex = (mCallbackBufferIndex + 1) % mBufferQueueLength;
return result;
}
int32_t AudioStreamOpenSLES::getBufferDepth(SLAndroidSimpleBufferQueueItf bq) {
SLAndroidSimpleBufferQueueState queueState;
SLresult result = (*bq)->GetState(bq, &queueState);
return (result == SL_RESULT_SUCCESS) ? queueState.count : -1;
}
bool AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) {
bool shouldStopStream = false;
// Ask the app callback to process the buffer.
DataCallbackResult result =
fireDataCallback(mCallbackBuffer[mCallbackBufferIndex].get(), mFramesPerCallback);
if (result == DataCallbackResult::Continue) {
// Pass the buffer to OpenSLES.
SLresult enqueueResult = enqueueCallbackBuffer(bq);
if (enqueueResult != SL_RESULT_SUCCESS) {
LOGE("%s() returned %d", __func__, enqueueResult);
shouldStopStream = true;
}
// Update Oboe client position with frames handled by the callback.
if (getDirection() == Direction::Input) {
mFramesRead += mFramesPerCallback;
} else {
mFramesWritten += mFramesPerCallback;
}
} else if (result == DataCallbackResult::Stop) {
LOGD("Oboe callback returned Stop");
shouldStopStream = true;
} else {
LOGW("Oboe callback returned unexpected value = %d", static_cast<int>(result));
shouldStopStream = true;
}
if (shouldStopStream) {
mCallbackBufferIndex = 0;
}
return shouldStopStream;
}
// This callback handler is called every time a buffer has been processed by OpenSL ES.
static void bqCallbackGlue(SLAndroidSimpleBufferQueueItf bq, void *context) {
bool shouldStopStream = (reinterpret_cast<AudioStreamOpenSLES *>(context))
->processBufferCallback(bq);
if (shouldStopStream) {
(reinterpret_cast<AudioStreamOpenSLES *>(context))->requestStop();
}
}
SLresult AudioStreamOpenSLES::registerBufferQueueCallback() {
// The BufferQueue
SLresult result = (*mObjectInterface)->GetInterface(mObjectInterface,
EngineOpenSLES::getInstance().getIidAndroidSimpleBufferQueue(),
&mSimpleBufferQueueInterface);
if (SL_RESULT_SUCCESS != result) {
LOGE("get buffer queue interface:%p result:%s",
mSimpleBufferQueueInterface,
getSLErrStr(result));
} else {
// Register the BufferQueue callback
result = (*mSimpleBufferQueueInterface)->RegisterCallback(mSimpleBufferQueueInterface,
bqCallbackGlue, this);
if (SL_RESULT_SUCCESS != result) {
LOGE("RegisterCallback result:%s", getSLErrStr(result));
}
}
return result;
}
int64_t AudioStreamOpenSLES::getFramesProcessedByServer() {
updateServiceFrameCounter();
int64_t millis64 = mPositionMillis.get();
int64_t framesProcessed = millis64 * getSampleRate() / kMillisPerSecond;
return framesProcessed;
}
Result AudioStreamOpenSLES::waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) {
Result oboeResult = Result::ErrorTimeout;
int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary
int64_t timeLeftNanos = timeoutNanoseconds;
while (true) {
const StreamState state = getState(); // this does not require a lock
if (nextState != nullptr) {
*nextState = state;
}
if (currentState != state) { // state changed?
oboeResult = Result::OK;
break;
}
// Did we timeout or did user ask for non-blocking?
if (timeLeftNanos <= 0) {
break;
}
if (sleepTimeNanos > timeLeftNanos){
sleepTimeNanos = timeLeftNanos;
}
AudioClock::sleepForNanos(sleepTimeNanos);
timeLeftNanos -= sleepTimeNanos;
}
return oboeResult;
}

View file

@ -0,0 +1,145 @@
/*
* Copyright 2015 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_AUDIO_STREAM_OPENSL_ES_H_
#define OBOE_AUDIO_STREAM_OPENSL_ES_H_
#include <memory>
#include "oboe/Oboe.h"
#include "common/MonotonicCounter.h"
#include "opensles/AudioStreamBuffered.h"
#include "opensles/EngineOpenSLES.h"
namespace oboe {
constexpr int kBitsPerByte = 8;
constexpr int kBufferQueueLengthDefault = 2; // double buffered for callbacks
constexpr int kBufferQueueLengthMax = 8; // AudioFlinger won't use more than 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:
AudioStreamOpenSLES();
explicit AudioStreamOpenSLES(const AudioStreamBuilder &builder);
virtual ~AudioStreamOpenSLES() = default;
virtual Result open() override;
/**
* Query the current state, eg. OBOE_STREAM_STATE_PAUSING
*
* @return state or a negative error.
*/
StreamState getState() override { return mState.load(); }
AudioApi getAudioApi() const override {
return AudioApi::OpenSLES;
}
/**
* Process next OpenSL ES buffer.
* Called by by OpenSL ES framework.
*
* This is public, but don't call it directly.
*
* @return whether the current stream should be stopped.
*/
bool processBufferCallback(SLAndroidSimpleBufferQueueItf bq);
Result waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) override;
protected:
/**
* Finish setting up the stream. Common for INPUT and OUTPUT.
*
* @param configItf
* @return SL_RESULT_SUCCESS if OK.
*/
SLresult finishCommonOpen(SLAndroidConfigurationItf configItf);
// This must be called under mLock.
Result close_l();
SLuint32 channelCountToChannelMaskDefault(int channelCount) const;
virtual Result onBeforeDestroy() { return Result::OK; }
virtual Result onAfterDestroy() { return Result::OK; }
static SLuint32 getDefaultByteOrder();
int32_t getBufferDepth(SLAndroidSimpleBufferQueueItf bq);
int32_t calculateOptimalBufferQueueLength();
int32_t estimateNativeFramesPerBurst();
SLresult enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq);
SLresult configurePerformanceMode(SLAndroidConfigurationItf configItf);
PerformanceMode convertPerformanceMode(SLuint32 openslMode) const;
SLuint32 convertPerformanceMode(PerformanceMode oboeMode) const;
void logUnsupportedAttributes();
/**
* Internal use only.
* Use this instead of directly setting the internal state variable.
*/
void setState(StreamState state) {
mState.store(state);
}
int64_t getFramesProcessedByServer();
// OpenSLES stuff
SLObjectItf mObjectInterface = nullptr;
SLAndroidSimpleBufferQueueItf mSimpleBufferQueueInterface = nullptr;
int mBufferQueueLength = 0;
int32_t mBytesPerCallback = oboe::kUnspecified;
MonotonicCounter mPositionMillis; // for tracking OpenSL ES service position
private:
constexpr static int kDoubleBufferCount = 2;
SLresult registerBufferQueueCallback();
SLresult updateStreamParameters(SLAndroidConfigurationItf configItf);
Result configureBufferSizes(int32_t sampleRate);
std::unique_ptr<uint8_t[]> mCallbackBuffer[kBufferQueueLengthMax];
int mCallbackBufferIndex = 0;
std::atomic<StreamState> mState{StreamState::Uninitialized};
};
} // namespace oboe
#endif // OBOE_AUDIO_STREAM_OPENSL_ES_H_

View file

@ -0,0 +1,206 @@
/*
* 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 <dlfcn.h>
#include "common/OboeDebug.h"
#include "EngineOpenSLES.h"
#include "OpenSLESUtilities.h"
using namespace oboe;
// OpenSL ES is deprecated in SDK 30.
// So we use custom dynamic linking to access the library.
#define LIB_OPENSLES_NAME "libOpenSLES.so"
EngineOpenSLES &EngineOpenSLES::getInstance() {
static EngineOpenSLES sInstance;
return sInstance;
}
// Satisfy extern in OpenSLES.h
// These are required because of b/337360630, which was causing
// Oboe to have link failures if libOpenSLES.so was not available.
// If you are statically linking Oboe and libOpenSLES.so is a shared library
// and you observe crashes, you can pass DO_NOT_DEFINE_OPENSL_ES_CONSTANTS to cmake.
#ifndef DO_NOT_DEFINE_OPENSL_ES_CONSTANTS
SL_API const SLInterfaceID SL_IID_ENGINE = nullptr;
SL_API const SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE = nullptr;
SL_API const SLInterfaceID SL_IID_ANDROIDCONFIGURATION = nullptr;
SL_API const SLInterfaceID SL_IID_RECORD = nullptr;
SL_API const SLInterfaceID SL_IID_BUFFERQUEUE = nullptr;
SL_API const SLInterfaceID SL_IID_VOLUME = nullptr;
SL_API const SLInterfaceID SL_IID_PLAY = nullptr;
#endif
static const char *getSafeDlerror() {
static const char *defaultMessage = "not found?";
char *errorMessage = dlerror();
return (errorMessage == nullptr) ? defaultMessage : errorMessage;
}
// Load the OpenSL ES library and the one primary entry point.
// @return true if linked OK
bool EngineOpenSLES::linkOpenSLES() {
if (mDynamicLinkState == kLinkStateBad) {
LOGE("%s(), OpenSL ES not available, based on previous link failure.", __func__);
} else if (mDynamicLinkState == kLinkStateUninitialized) {
// Set to BAD now in case we return because of an error.
// This is safe form race conditions because this function is always called
// under mLock amd the state is only accessed from this function.
mDynamicLinkState = kLinkStateBad;
// Use RTLD_NOW to avoid the unpredictable behavior that RTLD_LAZY can cause.
// Also resolving all the links now will prevent a run-time penalty later.
mLibOpenSlesLibraryHandle = dlopen(LIB_OPENSLES_NAME, RTLD_NOW);
if (mLibOpenSlesLibraryHandle == nullptr) {
LOGE("%s() could not dlopen(%s), %s", __func__, LIB_OPENSLES_NAME, getSafeDlerror());
return false;
} else {
mFunction_slCreateEngine = (prototype_slCreateEngine) dlsym(
mLibOpenSlesLibraryHandle,
"slCreateEngine");
LOGD("%s(): dlsym(%s) returned %p", __func__,
"slCreateEngine", mFunction_slCreateEngine);
if (mFunction_slCreateEngine == nullptr) {
LOGE("%s(): dlsym(slCreateEngine) returned null, %s", __func__, getSafeDlerror());
return false;
}
// Load IID interfaces.
LOCAL_SL_IID_ENGINE = getIidPointer("SL_IID_ENGINE");
if (LOCAL_SL_IID_ENGINE == nullptr) return false;
LOCAL_SL_IID_ANDROIDSIMPLEBUFFERQUEUE = getIidPointer(
"SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
if (LOCAL_SL_IID_ANDROIDSIMPLEBUFFERQUEUE == nullptr) return false;
LOCAL_SL_IID_ANDROIDCONFIGURATION = getIidPointer(
"SL_IID_ANDROIDCONFIGURATION");
if (LOCAL_SL_IID_ANDROIDCONFIGURATION == nullptr) return false;
LOCAL_SL_IID_RECORD = getIidPointer("SL_IID_RECORD");
if (LOCAL_SL_IID_RECORD == nullptr) return false;
LOCAL_SL_IID_BUFFERQUEUE = getIidPointer("SL_IID_BUFFERQUEUE");
if (LOCAL_SL_IID_BUFFERQUEUE == nullptr) return false;
LOCAL_SL_IID_VOLUME = getIidPointer("SL_IID_VOLUME");
if (LOCAL_SL_IID_VOLUME == nullptr) return false;
LOCAL_SL_IID_PLAY = getIidPointer("SL_IID_PLAY");
if (LOCAL_SL_IID_PLAY == nullptr) return false;
mDynamicLinkState = kLinkStateGood;
}
}
return (mDynamicLinkState == kLinkStateGood);
}
// A symbol like SL_IID_PLAY is a pointer to a structure.
// The dlsym() function returns the address of the pointer, not the structure.
// To get the address of the structure we have to dereference the pointer.
SLInterfaceID EngineOpenSLES::getIidPointer(const char *symbolName) {
SLInterfaceID *iid_address = (SLInterfaceID *) dlsym(
mLibOpenSlesLibraryHandle,
symbolName);
if (iid_address == nullptr) {
LOGE("%s(): dlsym(%s) returned null, %s", __func__, symbolName, getSafeDlerror());
return (SLInterfaceID) nullptr;
}
return *iid_address; // Get address of the structure.
}
SLresult EngineOpenSLES::open() {
std::lock_guard<std::mutex> lock(mLock);
SLresult result = SL_RESULT_SUCCESS;
if (mOpenCount++ == 0) {
// load the library and link to it
if (!linkOpenSLES()) {
result = SL_RESULT_FEATURE_UNSUPPORTED;
goto error;
};
// create engine
result = (*mFunction_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,
EngineOpenSLES::getInstance().getIidEngine(),
&mEngineInterface);
if (SL_RESULT_SUCCESS != result) {
LOGE("EngineOpenSLES - GetInterface() engine result:%s", getSLErrStr(result));
goto error;
}
}
return result;
error:
close_l();
return result;
}
void EngineOpenSLES::close() {
std::lock_guard<std::mutex> lock(mLock);
close_l();
}
// This must be called under mLock
void EngineOpenSLES::close_l() {
if (--mOpenCount == 0) {
if (mEngineObject != nullptr) {
(*mEngineObject)->Destroy(mEngineObject);
mEngineObject = nullptr;
mEngineInterface = nullptr;
}
}
}
SLresult EngineOpenSLES::createOutputMix(SLObjectItf *objectItf) {
return (*mEngineInterface)->CreateOutputMix(mEngineInterface, objectItf, 0, 0, 0);
}
SLresult EngineOpenSLES::createAudioPlayer(SLObjectItf *objectItf,
SLDataSource *audioSource,
SLDataSink *audioSink) {
SLInterfaceID ids[] = {LOCAL_SL_IID_BUFFERQUEUE, LOCAL_SL_IID_ANDROIDCONFIGURATION};
SLboolean reqs[] = {SL_BOOLEAN_TRUE, 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) {
SLInterfaceID ids[] = {LOCAL_SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
LOCAL_SL_IID_ANDROIDCONFIGURATION };
SLboolean reqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
return (*mEngineInterface)->CreateAudioRecorder(mEngineInterface, objectItf, audioSource,
audioSink,
sizeof(ids) / sizeof(ids[0]), ids, reqs);
}

View file

@ -0,0 +1,107 @@
/*
* 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 <atomic>
#include <mutex>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
namespace oboe {
typedef SLresult (*prototype_slCreateEngine)(
SLObjectItf *pEngine,
SLuint32 numOptions,
const SLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
const SLInterfaceID *pInterfaceIds,
const SLboolean *pInterfaceRequired
);
/**
* INTERNAL USE ONLY
*/
class EngineOpenSLES {
public:
static EngineOpenSLES &getInstance();
bool linkOpenSLES();
SLresult open();
void close();
SLresult createOutputMix(SLObjectItf *objectItf);
SLresult createAudioPlayer(SLObjectItf *objectItf,
SLDataSource *audioSource,
SLDataSink *audioSink);
SLresult createAudioRecorder(SLObjectItf *objectItf,
SLDataSource *audioSource,
SLDataSink *audioSink);
SLInterfaceID getIidEngine() { return LOCAL_SL_IID_ENGINE; }
SLInterfaceID getIidAndroidSimpleBufferQueue() { return LOCAL_SL_IID_ANDROIDSIMPLEBUFFERQUEUE; }
SLInterfaceID getIidAndroidConfiguration() { return LOCAL_SL_IID_ANDROIDCONFIGURATION; }
SLInterfaceID getIidRecord() { return LOCAL_SL_IID_RECORD; }
SLInterfaceID getIidBufferQueue() { return LOCAL_SL_IID_BUFFERQUEUE; }
SLInterfaceID getIidVolume() { return LOCAL_SL_IID_VOLUME; }
SLInterfaceID getIidPlay() { return LOCAL_SL_IID_PLAY; }
private:
// Make this a safe Singleton
EngineOpenSLES()= default;
~EngineOpenSLES()= default;
EngineOpenSLES(const EngineOpenSLES&)= delete;
EngineOpenSLES& operator=(const EngineOpenSLES&)= delete;
SLInterfaceID getIidPointer(const char *symbolName);
/**
* Close the OpenSL ES engine.
* This must be called under mLock
*/
void close_l();
std::mutex mLock;
int32_t mOpenCount = 0;
static constexpr int32_t kLinkStateUninitialized = 0;
static constexpr int32_t kLinkStateGood = 1;
static constexpr int32_t kLinkStateBad = 2;
int32_t mDynamicLinkState = kLinkStateUninitialized;
SLObjectItf mEngineObject = nullptr;
SLEngineItf mEngineInterface = nullptr;
// These symbols are loaded using dlsym().
prototype_slCreateEngine mFunction_slCreateEngine = nullptr;
void *mLibOpenSlesLibraryHandle = nullptr;
SLInterfaceID LOCAL_SL_IID_ENGINE = nullptr;
SLInterfaceID LOCAL_SL_IID_ANDROIDSIMPLEBUFFERQUEUE = nullptr;
SLInterfaceID LOCAL_SL_IID_ANDROIDCONFIGURATION = nullptr;
SLInterfaceID LOCAL_SL_IID_RECORD = nullptr;
SLInterfaceID LOCAL_SL_IID_BUFFERQUEUE = nullptr;
SLInterfaceID LOCAL_SL_IID_VOLUME = nullptr;
SLInterfaceID LOCAL_SL_IID_PLAY = nullptr;
};
} // namespace oboe
#endif //OBOE_ENGINE_OPENSLES_H

View file

@ -0,0 +1,103 @@
/*
* 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 "OpenSLESUtilities.h"
namespace oboe {
/*
* OSLES Helpers
*/
const char *getSLErrStr(SLresult code) {
switch (code) {
case SL_RESULT_SUCCESS:
return "SL_RESULT_SUCCESS";
case SL_RESULT_PRECONDITIONS_VIOLATED:
return "SL_RESULT_PRECONDITIONS_VIOLATED";
case SL_RESULT_PARAMETER_INVALID:
return "SL_RESULT_PARAMETER_INVALID";
case SL_RESULT_MEMORY_FAILURE:
return "SL_RESULT_MEMORY_FAILURE";
case SL_RESULT_RESOURCE_ERROR:
return "SL_RESULT_RESOURCE_ERROR";
case SL_RESULT_RESOURCE_LOST:
return "SL_RESULT_RESOURCE_LOST";
case SL_RESULT_IO_ERROR:
return "SL_RESULT_IO_ERROR";
case SL_RESULT_BUFFER_INSUFFICIENT:
return "SL_RESULT_BUFFER_INSUFFICIENT";
case SL_RESULT_CONTENT_CORRUPTED:
return "SL_RESULT_CONTENT_CORRUPTED";
case SL_RESULT_CONTENT_UNSUPPORTED:
return "SL_RESULT_CONTENT_UNSUPPORTED";
case SL_RESULT_CONTENT_NOT_FOUND:
return "SL_RESULT_CONTENT_NOT_FOUND";
case SL_RESULT_PERMISSION_DENIED:
return "SL_RESULT_PERMISSION_DENIED";
case SL_RESULT_FEATURE_UNSUPPORTED:
return "SL_RESULT_FEATURE_UNSUPPORTED";
case SL_RESULT_INTERNAL_ERROR:
return "SL_RESULT_INTERNAL_ERROR";
case SL_RESULT_UNKNOWN_ERROR:
return "SL_RESULT_UNKNOWN_ERROR";
case SL_RESULT_OPERATION_ABORTED:
return "SL_RESULT_OPERATION_ABORTED";
case SL_RESULT_CONTROL_LOST:
return "SL_RESULT_CONTROL_LOST";
default:
return "Unknown SL error";
}
}
SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat(
SLDataFormat_PCM format, SLuint32 representation) {
SLAndroidDataFormat_PCM_EX format_pcm_ex;
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.numChannels = format.numChannels;
format_pcm_ex.sampleRate = format.samplesPerSec;
format_pcm_ex.bitsPerSample = format.bitsPerSample;
format_pcm_ex.containerSize = format.containerSize;
format_pcm_ex.channelMask = format.channelMask;
format_pcm_ex.endianness = format.endianness;
format_pcm_ex.representation = representation;
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::I24:
case AudioFormat::I32:
case AudioFormat::IEC61937:
case AudioFormat::Invalid:
case AudioFormat::Unspecified:
case AudioFormat::MP3:
case AudioFormat::AAC_LC:
case AudioFormat::AAC_HE_V1:
case AudioFormat::AAC_HE_V2:
case AudioFormat::AAC_ELD:
case AudioFormat::AAC_XHE:
case AudioFormat::OPUS:
default:
return 0;
}
}
} // namespace oboe

View file

@ -0,0 +1,44 @@
/*
* 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_OPENSLES_OPENSLESUTILITIES_H
#define OBOE_OPENSLES_OPENSLESUTILITIES_H
#include <SLES/OpenSLES_Android.h>
#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
* SLAndroidDataFormat_PCM_EX object which allows audio samples to be represented using
* single precision floating-point.
*
* @param format
* @param representation
* @return the extended PCM format
*/
SLAndroidDataFormat_PCM_EX OpenSLES_createExtendedFormat(SLDataFormat_PCM format,
SLuint32 representation);
SLuint32 OpenSLES_ConvertFormatToRepresentation(AudioFormat format);
} // namespace oboe
#endif //OBOE_OPENSLES_OPENSLESUTILITIES_H

View file

@ -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<std::mutex> 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<std::mutex> lock(mLock);
if (--mOpenCount == 0) {
// destroy output mix object, and invalidate all associated interfaces
if (mOutputMixObject != nullptr) {
(*mOutputMixObject)->Destroy(mOutputMixObject);
mOutputMixObject = nullptr;
}
}
}
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);
}

View file

@ -0,0 +1,57 @@
/*
* 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 <atomic>
#include <mutex>
#include "EngineOpenSLES.h"
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 = nullptr;
};
} // namespace oboe
#endif //OBOE_OUTPUT_MIXER_OPENSLES_H