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:
parent
b574d4530e
commit
03c08027ac
4 changed files with 269 additions and 16 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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*);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue