diff --git a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index dcb0553948..82736fe1e5 100644 --- a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -261,6 +261,8 @@ struct BufferHelpers } }; +class SLRealtimeThread; + //============================================================================== class OpenSLAudioIODevice : public AudioIODevice { @@ -951,6 +953,9 @@ public: static const char* const openSLTypeName; private: + //============================================================================== + friend class SLRealtimeThread; + //============================================================================== DynamicLibrary slLibrary; int actualBufferSize, sampleRate; @@ -1087,3 +1092,182 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; } + +//============================================================================== +class SLRealtimeThread +{ +public: + static constexpr int numBuffers = 4; + + SLRealtimeThread() + { + if (auto createEngine = (OpenSLAudioIODevice::OpenSLSession::CreateEngineFunc) slLibrary.getFunction ("slCreateEngine")) + { + SLObjectItf obj = nullptr; + auto err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr); + + if (err != SL_RESULT_SUCCESS || obj == nullptr) + return; + + if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) + { + (*obj)->Destroy (obj); + return; + } + + engine = SlRef::cast (SlObjectRef (obj)); + + if (engine == nullptr) + { + (*obj)->Destroy (obj); + return; + } + + obj = nullptr; + err = (*engine)->CreateOutputMix (engine, &obj, 0, nullptr, nullptr); + + if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) + { + (*obj)->Destroy (obj); + return; + } + + outputMix = SlRef::cast (SlObjectRef (obj)); + + if (outputMix == nullptr) + { + (*obj)->Destroy (obj); + return; + } + + SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast (numBuffers)}; + SLDataLocator_OutputMix outputMixLocator = {SL_DATALOCATOR_OUTPUTMIX, outputMix}; + + PCMDataFormatEx dataFormat; + BufferHelpers::initPCMDataFormat (dataFormat, 1, OpenSLAudioIODevice::getNativeSampleRate()); + + SLDataSource source = { &queueLocator, &dataFormat }; + SLDataSink sink = { &outputMixLocator, nullptr }; + + SLInterfaceID queueInterfaces[] = { &IntfIID::iid }; + SLboolean trueFlag = SL_BOOLEAN_TRUE; + + obj = nullptr; + err = (*engine)->CreateAudioPlayer (engine, &obj, &source, &sink, 1, queueInterfaces, &trueFlag); + + if (err != SL_RESULT_SUCCESS || obj == nullptr) + return; + + if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) + { + (*obj)->Destroy (obj); + return; + } + + player = SlRef::cast (SlObjectRef (obj)); + + if (player == nullptr) + { + (*obj)->Destroy (obj); + return; + } + + queue = SlRef::cast (player); + if (queue == nullptr) + return; + + if ((*queue)->RegisterCallback (queue, staticFinished, this) != SL_RESULT_SUCCESS) + { + queue = nullptr; + return; + } + + pthread_cond_init (&threadReady, nullptr); + pthread_mutex_init (&threadReadyMutex, nullptr); + } + } + + bool isOK() const { return queue != nullptr; } + + pthread_t startThread (void* (*entry) (void*), void* userPtr) + { + memset (buffer.getData(), 0, static_cast (sizeof (int16) * static_cast (bufferSize * numBuffers))); + + for (int i = 0; i < numBuffers; ++i) + { + int16* dst = buffer.getData() + (bufferSize * i); + (*queue)->Enqueue (queue, dst, static_cast (static_cast (bufferSize) * sizeof (int16))); + } + + pthread_mutex_lock (&threadReadyMutex); + + threadEntryProc = entry; + threadUserPtr = userPtr; + + (*player)->SetPlayState (player, SL_PLAYSTATE_PLAYING); + + pthread_cond_wait (&threadReady, &threadReadyMutex); + pthread_mutex_unlock (&threadReadyMutex); + + return threadID; + } + + void finished() + { + if (threadEntryProc != nullptr) + { + pthread_mutex_lock (&threadReadyMutex); + + threadID = pthread_self(); + + pthread_cond_signal (&threadReady); + pthread_mutex_unlock (&threadReadyMutex); + + threadEntryProc (threadUserPtr); + threadEntryProc = nullptr; + + (*player)->SetPlayState (player, SL_PLAYSTATE_STOPPED); + MessageManager::callAsync ([this] () { delete this; }); + } + } + +private: + //============================================================================= + static void staticFinished (SLAndroidSimpleBufferQueueItf, void* context) + { + static_cast (context)->finished(); + } + + //============================================================================= + DynamicLibrary slLibrary { "libOpenSLES.so" }; + + SlRef engine; + SlRef outputMix; + SlRef player; + SlRef queue; + + int bufferSize = OpenSLAudioIODevice::getNativeBufferSize(); + HeapBlock buffer { HeapBlock (static_cast (1 * bufferSize * numBuffers)) }; + + void* (*threadEntryProc) (void*) = nullptr; + void* threadUserPtr = nullptr; + + pthread_cond_t threadReady; + pthread_mutex_t threadReadyMutex; + pthread_t threadID; +}; + +pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr) +{ + ScopedPointer thread (new SLRealtimeThread); + + if (! thread->isOK()) + return 0; + + pthread_t threadID = thread->startThread (entry, userPtr); + + // the thread will de-allocate itself + thread.release(); + + return threadID; +} diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h index 6183f86bdd..ea007b1f06 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h @@ -925,8 +925,30 @@ extern "C" void* threadEntryProc (void* userData) return nullptr; } +#if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && (JUCE_USE_ANDROID_OPENSLES || (! defined(JUCE_USE_ANDROID_OPENSLES) && JUCE_ANDROID_API_VERSION > 8)) +#define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1 +#endif + +#if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE +extern pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr); +#endif + void Thread::launchThread() { + #if JUCE_ANDROID + if (isAndroidRealtimeThread) + { + #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE + threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this); + threadId = (ThreadID) threadHandle; + + return; + #else + jassertfalse; + #endif + } + #endif + threadHandle = 0; pthread_t handle = 0; pthread_attr_t attr; diff --git a/modules/juce_core/threads/juce_Thread.cpp b/modules/juce_core/threads/juce_Thread.cpp index 243033e9e1..7e0a94ac21 100644 --- a/modules/juce_core/threads/juce_Thread.cpp +++ b/modules/juce_core/threads/juce_Thread.cpp @@ -20,14 +20,8 @@ ============================================================================== */ -Thread::Thread (const String& threadName_, const size_t stackSize) - : threadName (threadName_), - threadHandle (nullptr), - threadId (0), - threadPriority (5), - threadStackSize (stackSize), - affinityMask (0), - shouldExit (false) +Thread::Thread (const String& name, const size_t stackSize) + : threadName (name), threadStackSize (stackSize) { } @@ -126,12 +120,21 @@ void Thread::startThread() } } -void Thread::startThread (const int priority) +void Thread::startThread (int priority) { const ScopedLock sl (startStopLock); if (threadHandle == nullptr) { + auto isRealtime = (priority == realtimeAudioPriority); + + #if JUCE_ANDROID + isAndroidRealtimeThread = isRealtime; + #endif + + if (isRealtime) + priority = 9; + threadPriority = priority; startThread(); } @@ -218,8 +221,13 @@ bool Thread::stopThread (const int timeOutMilliseconds) } //============================================================================== -bool Thread::setPriority (const int newPriority) +bool Thread::setPriority (int newPriority) { + bool isRealtime = (newPriority == realtimeAudioPriority); + + if (isRealtime) + newPriority = 9; + // NB: deadlock possible if you try to set the thread prio from the thread itself, // so using setCurrentThreadPriority instead in that case. if (getCurrentThreadId() == getThreadId()) @@ -227,6 +235,14 @@ bool Thread::setPriority (const int newPriority) const ScopedLock sl (startStopLock); + #if JUCE_ANDROID + // you cannot switch from or to an Android realtime thread once the + // thread is already running! + jassert (isThreadRunning() && (isRealtime == isAndroidRealtimeThread)); + + isAndroidRealtimeThread = isRealtime; + #endif + if ((! isThreadRunning()) || setThreadPriority (threadHandle, newPriority)) { threadPriority = newPriority; diff --git a/modules/juce_core/threads/juce_Thread.h b/modules/juce_core/threads/juce_Thread.h index fc84bce9d7..e3f177c588 100644 --- a/modules/juce_core/threads/juce_Thread.h +++ b/modules/juce_core/threads/juce_Thread.h @@ -92,7 +92,7 @@ public: Launches the thread with a given priority, where 0 = lowest, 10 = highest. If the thread is already running, its priority will be changed. - @see startThread, setPriority + @see startThread, setPriority, realtimeAudioPriority */ void startThread (int priority); @@ -164,11 +164,38 @@ public: bool waitForThreadToExit (int timeOutMilliseconds) const; //============================================================================== + /** Special realtime audio thread priority + + This priority will create a high-priority thread which is best suited + for realtime audio processing. + + Currently, this priority is identical to priority 9, except when building + for Android with OpenSL support. + + In this case, JUCE will ask OpenSL to consturct a super high priority thread + specifically for realtime audio processing. + + Note that this priority can only be set **before** the thread has + started. Switching to this priority, or from this priority to a different + priority, is not supported under Android and will assert. + + For best performance this thread should yield at regular intervals + and not call any blocking APIS. + + @see startThread, setPriority, sleep, WaitableEvent + */ + enum + { + realtimeAudioPriority = -1 + }; + /** Changes the thread's priority. May return false if for some reason the priority can't be changed. @param priority the new priority, in the range 0 (lowest) to 10 (highest). A priority of 5 is normal. + + @see realtimeAudioPriority */ bool setPriority (int priority); @@ -272,14 +299,18 @@ public: private: //============================================================================== const String threadName; - void* volatile threadHandle; - ThreadID threadId; + void* volatile threadHandle = nullptr; + ThreadID threadId = {}; CriticalSection startStopLock; WaitableEvent startSuspensionEvent, defaultEvent; - int threadPriority; + int threadPriority = 5; size_t threadStackSize; - uint32 affinityMask; - bool volatile shouldExit; + uint32 affinityMask = 0; + bool volatile shouldExit = false; + + #if JUCE_ANDROID + bool isAndroidRealtimeThread = false; + #endif #ifndef DOXYGEN friend void JUCE_API juce_threadEntryPoint (void*);