aboutsummaryrefslogtreecommitdiffstats
path: root/alc/backends/opensl.cpp
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2019-07-28 18:56:04 -0700
committerChris Robinson <[email protected]>2019-07-28 18:56:04 -0700
commitcb3e96e75640730b9391f0d2d922eecd9ee2ce79 (patch)
tree23520551bddb2a80354e44da47f54201fdc084f0 /alc/backends/opensl.cpp
parent93e60919c8f387c36c267ca9faa1ac653254aea6 (diff)
Rename Alc to alc
Diffstat (limited to 'alc/backends/opensl.cpp')
-rw-r--r--alc/backends/opensl.cpp936
1 files changed, 936 insertions, 0 deletions
diff --git a/alc/backends/opensl.cpp b/alc/backends/opensl.cpp
new file mode 100644
index 00000000..b34dc0cb
--- /dev/null
+++ b/alc/backends/opensl.cpp
@@ -0,0 +1,936 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* This is an OpenAL backend for Android using the native audio APIs based on
+ * OpenSL ES 1.0.1. It is based on source code for the native-audio sample app
+ * bundled with NDK.
+ */
+
+#include "config.h"
+
+#include "backends/opensl.h"
+
+#include <stdlib.h>
+#include <jni.h>
+
+#include <new>
+#include <array>
+#include <thread>
+#include <functional>
+
+#include "alcmain.h"
+#include "alu.h"
+#include "ringbuffer.h"
+#include "threads.h"
+#include "compat.h"
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+#include <SLES/OpenSLES_AndroidConfiguration.h>
+
+
+namespace {
+
+/* Helper macros */
+#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
+#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
+#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
+
+
+constexpr ALCchar opensl_device[] = "OpenSL";
+
+
+SLuint32 GetChannelMask(DevFmtChannels chans)
+{
+ switch(chans)
+ {
+ case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
+ case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT;
+ case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
+ SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT;
+ case DevFmtX51: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
+ SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
+ SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
+ case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
+ SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
+ SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT;
+ case DevFmtX61: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
+ SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
+ SL_SPEAKER_BACK_CENTER|
+ SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
+ case DevFmtX71: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT|
+ SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY|
+ SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT|
+ SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT;
+ case DevFmtAmbi3D:
+ break;
+ }
+ return 0;
+}
+
+#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
+SLuint32 GetTypeRepresentation(DevFmtType type)
+{
+ switch(type)
+ {
+ case DevFmtUByte:
+ case DevFmtUShort:
+ case DevFmtUInt:
+ return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
+ case DevFmtByte:
+ case DevFmtShort:
+ case DevFmtInt:
+ return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+ case DevFmtFloat:
+ return SL_ANDROID_PCM_REPRESENTATION_FLOAT;
+ }
+ return 0;
+}
+#endif
+
+const char *res_str(SLresult result)
+{
+ switch(result)
+ {
+ case SL_RESULT_SUCCESS: return "Success";
+ case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
+ case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
+ case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
+ case SL_RESULT_RESOURCE_ERROR: return "Resource error";
+ case SL_RESULT_RESOURCE_LOST: return "Resource lost";
+ case SL_RESULT_IO_ERROR: return "I/O error";
+ case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
+ case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
+ case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
+ case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
+ case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
+ case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
+ case SL_RESULT_INTERNAL_ERROR: return "Internal error";
+ case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
+ case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
+ case SL_RESULT_CONTROL_LOST: return "Control lost";
+#ifdef SL_RESULT_READONLY
+ case SL_RESULT_READONLY: return "ReadOnly";
+#endif
+#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED
+ case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
+#endif
+#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE
+ case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
+#endif
+ }
+ return "Unknown error code";
+}
+
+#define PRINTERR(x, s) do { \
+ if(UNLIKELY((x) != SL_RESULT_SUCCESS)) \
+ ERR("%s: %s\n", (s), res_str((x))); \
+} while(0)
+
+
+struct OpenSLPlayback final : public BackendBase {
+ OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~OpenSLPlayback() override;
+
+ static void processC(SLAndroidSimpleBufferQueueItf bq, void *context);
+ void process(SLAndroidSimpleBufferQueueItf bq);
+
+ int mixerProc();
+
+ ALCenum open(const ALCchar *name) override;
+ ALCboolean reset() override;
+ ALCboolean start() override;
+ void stop() override;
+ ClockLatency getClockLatency() override;
+
+ /* engine interfaces */
+ SLObjectItf mEngineObj{nullptr};
+ SLEngineItf mEngine{nullptr};
+
+ /* output mix interfaces */
+ SLObjectItf mOutputMix{nullptr};
+
+ /* buffer queue player interfaces */
+ SLObjectItf mBufferQueueObj{nullptr};
+
+ RingBufferPtr mRing{nullptr};
+ al::semaphore mSem;
+
+ ALsizei mFrameSize{0};
+
+ std::atomic<bool> mKillNow{true};
+ std::thread mThread;
+
+ DEF_NEWDEL(OpenSLPlayback)
+};
+
+OpenSLPlayback::~OpenSLPlayback()
+{
+ if(mBufferQueueObj)
+ VCALL0(mBufferQueueObj,Destroy)();
+ mBufferQueueObj = nullptr;
+
+ if(mOutputMix)
+ VCALL0(mOutputMix,Destroy)();
+ mOutputMix = nullptr;
+
+ if(mEngineObj)
+ VCALL0(mEngineObj,Destroy)();
+ mEngineObj = nullptr;
+ mEngine = nullptr;
+}
+
+
+/* this callback handler is called every time a buffer finishes playing */
+void OpenSLPlayback::processC(SLAndroidSimpleBufferQueueItf bq, void *context)
+{ static_cast<OpenSLPlayback*>(context)->process(bq); }
+
+void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf)
+{
+ /* A note on the ringbuffer usage: The buffer queue seems to hold on to the
+ * pointer passed to the Enqueue method, rather than copying the audio.
+ * Consequently, the ringbuffer contains the audio that is currently queued
+ * and waiting to play. This process() callback is called when a buffer is
+ * finished, so we simply move the read pointer up to indicate the space is
+ * available for writing again, and wake up the mixer thread to mix and
+ * queue more audio.
+ */
+ mRing->readAdvance(1);
+
+ mSem.post();
+}
+
+int OpenSLPlayback::mixerProc()
+{
+ SetRTPriority();
+ althrd_setname(MIXER_THREAD_NAME);
+
+ SLPlayItf player;
+ SLAndroidSimpleBufferQueueItf bufferQueue;
+ SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &bufferQueue)};
+ PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
+ PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
+ }
+
+ lock();
+ if(SL_RESULT_SUCCESS != result)
+ aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result);
+
+ while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) &&
+ mDevice->Connected.load(std::memory_order_acquire))
+ {
+ if(mRing->writeSpace() == 0)
+ {
+ SLuint32 state{0};
+
+ result = VCALL(player,GetPlayState)(&state);
+ PRINTERR(result, "player->GetPlayState");
+ if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
+ {
+ result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
+ PRINTERR(result, "player->SetPlayState");
+ }
+ if(SL_RESULT_SUCCESS != result)
+ {
+ aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result);
+ break;
+ }
+
+ if(mRing->writeSpace() == 0)
+ {
+ unlock();
+ mSem.wait();
+ lock();
+ continue;
+ }
+ }
+
+ auto data = mRing->getWriteVector();
+ aluMixData(mDevice, data.first.buf, data.first.len*mDevice->UpdateSize);
+ if(data.second.len > 0)
+ aluMixData(mDevice, data.second.buf, data.second.len*mDevice->UpdateSize);
+
+ size_t todo{data.first.len + data.second.len};
+ mRing->writeAdvance(todo);
+
+ for(size_t i{0};i < todo;i++)
+ {
+ if(!data.first.len)
+ {
+ data.first = data.second;
+ data.second.buf = nullptr;
+ data.second.len = 0;
+ }
+
+ result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
+ PRINTERR(result, "bufferQueue->Enqueue");
+ if(SL_RESULT_SUCCESS != result)
+ {
+ aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result);
+ break;
+ }
+
+ data.first.len--;
+ data.first.buf += mDevice->UpdateSize*mFrameSize;
+ }
+ }
+ unlock();
+
+ return 0;
+}
+
+
+ALCenum OpenSLPlayback::open(const ALCchar *name)
+{
+ if(!name)
+ name = opensl_device;
+ else if(strcmp(name, opensl_device) != 0)
+ return ALC_INVALID_VALUE;
+
+ // create engine
+ SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
+ PRINTERR(result, "slCreateEngine");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
+ PRINTERR(result, "engine->Realize");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
+ PRINTERR(result, "engine->GetInterface");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
+ PRINTERR(result, "engine->CreateOutputMix");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
+ PRINTERR(result, "outputMix->Realize");
+ }
+
+ if(SL_RESULT_SUCCESS != result)
+ {
+ if(mOutputMix)
+ VCALL0(mOutputMix,Destroy)();
+ mOutputMix = nullptr;
+
+ if(mEngineObj)
+ VCALL0(mEngineObj,Destroy)();
+ mEngineObj = nullptr;
+ mEngine = nullptr;
+
+ return ALC_INVALID_VALUE;
+ }
+
+ mDevice->DeviceName = name;
+ return ALC_NO_ERROR;
+}
+
+ALCboolean OpenSLPlayback::reset()
+{
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
+ SLDataLocator_OutputMix loc_outmix;
+ SLDataSource audioSrc;
+ SLDataSink audioSnk;
+ SLresult result;
+
+ if(mBufferQueueObj)
+ VCALL0(mBufferQueueObj,Destroy)();
+ mBufferQueueObj = nullptr;
+
+ mRing = nullptr;
+
+#if 0
+ if(!mDevice->Flags.get<FrequencyRequest>())
+ {
+ /* FIXME: Disabled until I figure out how to get the Context needed for
+ * the getSystemService call.
+ */
+ JNIEnv *env = Android_GetJNIEnv();
+ jobject jctx = Android_GetContext();
+
+ /* Get necessary stuff for using java.lang.Integer,
+ * android.content.Context, and android.media.AudioManager.
+ */
+ jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
+ jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
+ "parseInt", "(Ljava/lang/String;)I"
+ );
+ TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
+
+ jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
+ jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
+ "AUDIO_SERVICE", "Ljava/lang/String;"
+ );
+ jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
+ "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
+ );
+ TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
+ ctx_cls, ctx_audsvc, ctx_getSysSvc);
+
+ jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
+ jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
+ "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
+ );
+ jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
+ "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
+ );
+ TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
+ audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
+
+ const char *strchars;
+ jstring strobj;
+
+ /* Now make the calls. */
+ //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
+ strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
+ jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
+ strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
+ TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
+ JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
+
+ //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+ strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
+ jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
+ strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
+ TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
+ JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
+
+ //int sampleRate = Integer.parseInt(srateStr);
+ sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
+
+ strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
+ TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
+ JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
+
+ if(!sampleRate) sampleRate = device->Frequency;
+ else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
+ }
+#endif
+
+ mDevice->FmtChans = DevFmtStereo;
+ mDevice->FmtType = DevFmtShort;
+
+ SetDefaultWFXChannelOrder(mDevice);
+ mFrameSize = mDevice->frameSizeFromFmt();
+
+
+ const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
+ const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
+
+ loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
+
+#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
+ SLAndroidDataFormat_PCM_EX format_pcm{};
+ format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+ format_pcm.numChannels = mDevice->channelsFromFmt();
+ format_pcm.sampleRate = mDevice->Frequency * 1000;
+ format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
+ format_pcm.containerSize = format_pcm.bitsPerSample;
+ format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
+ format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
+ SL_BYTEORDER_BIGENDIAN;
+ format_pcm.representation = GetTypeRepresentation(mDevice->FmtType);
+#else
+ SLDataFormat_PCM format_pcm{};
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = mDevice->channelsFromFmt();
+ format_pcm.samplesPerSec = mDevice->Frequency * 1000;
+ format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
+ format_pcm.containerSize = format_pcm.bitsPerSample;
+ format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
+ format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
+ SL_BYTEORDER_BIGENDIAN;
+#endif
+
+ audioSrc.pLocator = &loc_bufq;
+ audioSrc.pFormat = &format_pcm;
+
+ loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+ loc_outmix.outputMix = mOutputMix;
+ audioSnk.pLocator = &loc_outmix;
+ audioSnk.pFormat = nullptr;
+
+
+ result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
+ ids.data(), reqs.data());
+ PRINTERR(result, "engine->CreateAudioPlayer");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ /* Set the stream type to "media" (games, music, etc), if possible. */
+ SLAndroidConfigurationItf config;
+ result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
+ PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
+ result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
+ sizeof(streamType));
+ PRINTERR(result, "config->SetConfiguration");
+ }
+
+ /* Clear any error since this was optional. */
+ result = SL_RESULT_SUCCESS;
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
+ PRINTERR(result, "bufferQueue->Realize");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
+ try {
+ mRing = CreateRingBuffer(num_updates, mFrameSize*mDevice->UpdateSize, true);
+ }
+ catch(std::exception& e) {
+ ERR("Failed allocating ring buffer %ux%ux%u: %s\n", mDevice->UpdateSize,
+ num_updates, mFrameSize, e.what());
+ result = SL_RESULT_MEMORY_FAILURE;
+ }
+ }
+
+ if(SL_RESULT_SUCCESS != result)
+ {
+ if(mBufferQueueObj)
+ VCALL0(mBufferQueueObj,Destroy)();
+ mBufferQueueObj = nullptr;
+
+ return ALC_FALSE;
+ }
+
+ return ALC_TRUE;
+}
+
+ALCboolean OpenSLPlayback::start()
+{
+ mRing->reset();
+
+ SLAndroidSimpleBufferQueueItf bufferQueue;
+ SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &bufferQueue)};
+ PRINTERR(result, "bufferQueue->GetInterface");
+ if(SL_RESULT_SUCCESS != result)
+ return ALC_FALSE;
+
+ result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
+ PRINTERR(result, "bufferQueue->RegisterCallback");
+ if(SL_RESULT_SUCCESS != result) return ALC_FALSE;
+
+ try {
+ mKillNow.store(false, std::memory_order_release);
+ mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
+ return ALC_TRUE;
+ }
+ catch(std::exception& e) {
+ ERR("Could not create playback thread: %s\n", e.what());
+ }
+ catch(...) {
+ }
+ return ALC_FALSE;
+}
+
+void OpenSLPlayback::stop()
+{
+ if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
+ return;
+
+ mSem.post();
+ mThread.join();
+
+ SLPlayItf player;
+ SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
+ PRINTERR(result, "bufferQueue->GetInterface");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
+ PRINTERR(result, "player->SetPlayState");
+ }
+
+ SLAndroidSimpleBufferQueueItf bufferQueue;
+ result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
+ PRINTERR(result, "bufferQueue->GetInterface");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL0(bufferQueue,Clear)();
+ PRINTERR(result, "bufferQueue->Clear");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
+ PRINTERR(result, "bufferQueue->RegisterCallback");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ SLAndroidSimpleBufferQueueState state;
+ do {
+ std::this_thread::yield();
+ result = VCALL(bufferQueue,GetState)(&state);
+ } while(SL_RESULT_SUCCESS == result && state.count > 0);
+ PRINTERR(result, "bufferQueue->GetState");
+ }
+}
+
+ClockLatency OpenSLPlayback::getClockLatency()
+{
+ ClockLatency ret;
+
+ lock();
+ ret.ClockTime = GetDeviceClockTime(mDevice);
+ ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
+ ret.Latency /= mDevice->Frequency;
+ unlock();
+
+ return ret;
+}
+
+
+struct OpenSLCapture final : public BackendBase {
+ OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
+ ~OpenSLCapture() override;
+
+ static void processC(SLAndroidSimpleBufferQueueItf bq, void *context);
+ void process(SLAndroidSimpleBufferQueueItf bq);
+
+ ALCenum open(const ALCchar *name) override;
+ ALCboolean start() override;
+ void stop() override;
+ ALCenum captureSamples(void *buffer, ALCuint samples) override;
+ ALCuint availableSamples() override;
+
+ /* engine interfaces */
+ SLObjectItf mEngineObj{nullptr};
+ SLEngineItf mEngine;
+
+ /* recording interfaces */
+ SLObjectItf mRecordObj{nullptr};
+
+ RingBufferPtr mRing{nullptr};
+ ALCuint mSplOffset{0u};
+
+ ALsizei mFrameSize{0};
+
+ DEF_NEWDEL(OpenSLCapture)
+};
+
+OpenSLCapture::~OpenSLCapture()
+{
+ if(mRecordObj)
+ VCALL0(mRecordObj,Destroy)();
+ mRecordObj = nullptr;
+
+ if(mEngineObj)
+ VCALL0(mEngineObj,Destroy)();
+ mEngineObj = nullptr;
+ mEngine = nullptr;
+}
+
+
+void OpenSLCapture::processC(SLAndroidSimpleBufferQueueItf bq, void *context)
+{ static_cast<OpenSLCapture*>(context)->process(bq); }
+
+void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf)
+{
+ /* A new chunk has been written into the ring buffer, advance it. */
+ mRing->writeAdvance(1);
+}
+
+
+ALCenum OpenSLCapture::open(const ALCchar* name)
+{
+ if(!name)
+ name = opensl_device;
+ else if(strcmp(name, opensl_device) != 0)
+ return ALC_INVALID_VALUE;
+
+ SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
+ PRINTERR(result, "slCreateEngine");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
+ PRINTERR(result, "engine->Realize");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
+ PRINTERR(result, "engine->GetInterface");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ mFrameSize = mDevice->frameSizeFromFmt();
+ /* Ensure the total length is at least 100ms */
+ ALsizei length{maxi(mDevice->BufferSize, mDevice->Frequency/10)};
+ /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
+ ALsizei update_len{clampi(mDevice->BufferSize/3, mDevice->Frequency/100,
+ mDevice->Frequency/100*5)};
+ ALsizei num_updates{(length+update_len-1) / update_len};
+
+ try {
+ mRing = CreateRingBuffer(num_updates, update_len*mFrameSize, false);
+
+ mDevice->UpdateSize = update_len;
+ mDevice->BufferSize = mRing->writeSpace() * update_len;
+ }
+ catch(std::exception& e) {
+ ERR("Failed to allocate ring buffer: %s\n", e.what());
+ result = SL_RESULT_MEMORY_FAILURE;
+ }
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
+ const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
+
+ SLDataLocator_IODevice loc_dev{};
+ loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
+ loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
+ loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+ loc_dev.device = nullptr;
+
+ SLDataSource audioSrc{};
+ audioSrc.pLocator = &loc_dev;
+ audioSrc.pFormat = nullptr;
+
+ SLDataLocator_AndroidSimpleBufferQueue loc_bq{};
+ loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+ loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
+
+#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
+ SLAndroidDataFormat_PCM_EX format_pcm{};
+ format_pcm.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
+ format_pcm.numChannels = mDevice->channelsFromFmt();
+ format_pcm.sampleRate = mDevice->Frequency * 1000;
+ format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
+ format_pcm.containerSize = format_pcm.bitsPerSample;
+ format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
+ format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
+ format_pcm.representation = GetTypeRepresentation(mDevice->FmtType);
+#else
+ SLDataFormat_PCM format_pcm{};
+ format_pcm.formatType = SL_DATAFORMAT_PCM;
+ format_pcm.numChannels = mDevice->channelsFromFmt();
+ format_pcm.samplesPerSec = mDevice->Frequency * 1000;
+ format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
+ format_pcm.containerSize = format_pcm.bitsPerSample;
+ format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
+ format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
+#endif
+
+ SLDataSink audioSnk{};
+ audioSnk.pLocator = &loc_bq;
+ audioSnk.pFormat = &format_pcm;
+
+ result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
+ ids.size(), ids.data(), reqs.data());
+ PRINTERR(result, "engine->CreateAudioRecorder");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ /* Set the record preset to "generic", if possible. */
+ SLAndroidConfigurationItf config;
+ result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
+ PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
+ if(SL_RESULT_SUCCESS == result)
+ {
+ SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
+ result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
+ sizeof(preset));
+ PRINTERR(result, "config->SetConfiguration");
+ }
+
+ /* Clear any error since this was optional. */
+ result = SL_RESULT_SUCCESS;
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
+ PRINTERR(result, "recordObj->Realize");
+ }
+
+ SLAndroidSimpleBufferQueueItf bufferQueue;
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
+ PRINTERR(result, "recordObj->GetInterface");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
+ PRINTERR(result, "bufferQueue->RegisterCallback");
+ }
+ if(SL_RESULT_SUCCESS == result)
+ {
+ const ALuint chunk_size{mDevice->UpdateSize * mFrameSize};
+
+ auto data = mRing->getWriteVector();
+ for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
+ {
+ result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
+ PRINTERR(result, "bufferQueue->Enqueue");
+ }
+ for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
+ {
+ result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
+ PRINTERR(result, "bufferQueue->Enqueue");
+ }
+ }
+
+ if(SL_RESULT_SUCCESS != result)
+ {
+ if(mRecordObj)
+ VCALL0(mRecordObj,Destroy)();
+ mRecordObj = nullptr;
+
+ if(mEngineObj)
+ VCALL0(mEngineObj,Destroy)();
+ mEngineObj = nullptr;
+ mEngine = nullptr;
+
+ return ALC_INVALID_VALUE;
+ }
+
+ mDevice->DeviceName = name;
+ return ALC_NO_ERROR;
+}
+
+ALCboolean OpenSLCapture::start()
+{
+ SLRecordItf record;
+ SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
+ PRINTERR(result, "recordObj->GetInterface");
+
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
+ PRINTERR(result, "record->SetRecordState");
+ }
+
+ if(SL_RESULT_SUCCESS != result)
+ {
+ aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result);
+ return ALC_FALSE;
+ }
+
+ return ALC_TRUE;
+}
+
+void OpenSLCapture::stop()
+{
+ SLRecordItf record;
+ SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
+ PRINTERR(result, "recordObj->GetInterface");
+
+ if(SL_RESULT_SUCCESS == result)
+ {
+ result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
+ PRINTERR(result, "record->SetRecordState");
+ }
+}
+
+ALCenum OpenSLCapture::captureSamples(void* buffer, ALCuint samples)
+{
+ ALsizei chunk_size = mDevice->UpdateSize * mFrameSize;
+ SLAndroidSimpleBufferQueueItf bufferQueue;
+ SLresult result;
+ ALCuint i;
+
+ result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
+ PRINTERR(result, "recordObj->GetInterface");
+
+ /* Read the desired samples from the ring buffer then advance its read
+ * pointer.
+ */
+ auto data = mRing->getReadVector();
+ for(i = 0;i < samples;)
+ {
+ ALCuint rem{minu(samples - i, mDevice->UpdateSize - mSplOffset)};
+ memcpy((ALCbyte*)buffer + i*mFrameSize, data.first.buf + mSplOffset*mFrameSize,
+ rem * mFrameSize);
+
+ mSplOffset += rem;
+ if(mSplOffset == mDevice->UpdateSize)
+ {
+ /* Finished a chunk, reset the offset and advance the read pointer. */
+ mSplOffset = 0;
+
+ mRing->readAdvance(1);
+ result = VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size);
+ PRINTERR(result, "bufferQueue->Enqueue");
+ if(SL_RESULT_SUCCESS != result) break;
+
+ data.first.len--;
+ if(!data.first.len)
+ data.first = data.second;
+ else
+ data.first.buf += chunk_size;
+ }
+
+ i += rem;
+ }
+
+ if(SL_RESULT_SUCCESS != result)
+ {
+ aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x", result);
+ return ALC_INVALID_DEVICE;
+ }
+
+ return ALC_NO_ERROR;
+}
+
+ALCuint OpenSLCapture::availableSamples()
+{ return mRing->readSpace()*mDevice->UpdateSize - mSplOffset; }
+
+} // namespace
+
+bool OSLBackendFactory::init() { return true; }
+
+bool OSLBackendFactory::querySupport(BackendType type)
+{ return (type == BackendType::Playback || type == BackendType::Capture); }
+
+void OSLBackendFactory::probe(DevProbe type, std::string *outnames)
+{
+ switch(type)
+ {
+ case DevProbe::Playback:
+ case DevProbe::Capture:
+ /* Includes null char. */
+ outnames->append(opensl_device, sizeof(opensl_device));
+ break;
+ }
+}
+
+BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
+{
+ if(type == BackendType::Playback)
+ return BackendPtr{new OpenSLPlayback{device}};
+ if(type == BackendType::Capture)
+ return BackendPtr{new OpenSLCapture{device}};
+ return nullptr;
+}
+
+BackendFactory &OSLBackendFactory::getFactory()
+{
+ static OSLBackendFactory factory{};
+ return factory;
+}