1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-28 02:30:05 +00:00

Android: Added new thread priority specifically for realtime audio render threads. Currently, only implemented in Android.

This commit is contained in:
hogliux 2017-07-03 16:50:59 +01:00
parent b574d4530e
commit 03c08027ac
4 changed files with 269 additions and 16 deletions

View file

@ -261,6 +261,8 @@ struct BufferHelpers<float>
}
};
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<SLEngineItf_>::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<SLOutputMixItf_>::cast (SlObjectRef (obj));
if (outputMix == nullptr)
{
(*obj)->Destroy (obj);
return;
}
SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast<SLuint32> (numBuffers)};
SLDataLocator_OutputMix outputMixLocator = {SL_DATALOCATOR_OUTPUTMIX, outputMix};
PCMDataFormatEx dataFormat;
BufferHelpers<int16>::initPCMDataFormat (dataFormat, 1, OpenSLAudioIODevice::getNativeSampleRate());
SLDataSource source = { &queueLocator, &dataFormat };
SLDataSink sink = { &outputMixLocator, nullptr };
SLInterfaceID queueInterfaces[] = { &IntfIID<SLAndroidSimpleBufferQueueItf_>::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<SLPlayItf_>::cast (SlObjectRef (obj));
if (player == nullptr)
{
(*obj)->Destroy (obj);
return;
}
queue = SlRef<SLAndroidSimpleBufferQueueItf_>::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<size_t> (sizeof (int16) * static_cast<size_t> (bufferSize * numBuffers)));
for (int i = 0; i < numBuffers; ++i)
{
int16* dst = buffer.getData() + (bufferSize * i);
(*queue)->Enqueue (queue, dst, static_cast<SLuint32> (static_cast<size_t> (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<SLRealtimeThread*> (context)->finished();
}
//=============================================================================
DynamicLibrary slLibrary { "libOpenSLES.so" };
SlRef<SLEngineItf_> engine;
SlRef<SLOutputMixItf_> outputMix;
SlRef<SLPlayItf_> player;
SlRef<SLAndroidSimpleBufferQueueItf_> queue;
int bufferSize = OpenSLAudioIODevice::getNativeBufferSize();
HeapBlock<int16> buffer { HeapBlock<int16> (static_cast<size_t> (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<SLRealtimeThread> 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;
}

View file

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

View file

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

View file

@ -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*);