To me, it doesn't look like there is anything wrong with OpenSL itself, rather the locking mechanism it uses that I don't quite understand (I tried to look at the source, but it would probably require more time to figure out than I had). If it would use normal mutexes that would block a thread until the lock is released, rather than something that completely hangs the thread in case of conflict, then none of these deadlock issues would occur.
1. I can't say which device it is, but it runs Android 4.4.4 I also encountered the same issue over a year ago on multiple 4.1 and 4.2 devices (e.g. Samsung S3 and S4, Motorola Droid Razr, HTC One).
2. I don't have an executable that demonstrates the issue, but I can post some snippets of code that reproduce it for me quite reliably:
This is the callback function registered with the player queue OpenSL object, called for every buffer whose playback is completed:
static void playerQueueCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
FrameRef frame;
// Release frame and front of the playing queue
playingQueue.dequeue(frame);
// OpenSL is ready for more frames, do we have any?
while(overflowQueue.front(frame))
{
// Attempt to enqueue directly to OpenSL
if(enqueueToPlayQueue(frame->data(), frame->size()))
{
// Success, remove from local queue
overflowQueue.dequeue(frame);
// Push frame to the playing queue
playingQueue.enqueue(frame);
}
else
{
// Failed, OpenSL queue must be full -> leave frame in local queue and break
break;
}
}
break;
}
This is the function used to enqueue new buffers with OpenSL:
bool enqueueToPlayQueue(const void *buffer, unsigned int size)
{
if(g_playerQueue == 0)
{
return false;
}
else
{
SLresult result;
result = (*g_playerQueue)->Enqueue(g_playerQueue, buffer, size);
if(result != SL_RESULT_SUCCESS)
{
if(result == SL_RESULT_BUFFER_INSUFFICIENT)
{
LOGD("OpenSL queue is full");
}
return false;
}
else
{
return true;
}
}
}
Note that this is where the key to reproducing this deadlock is. This function is called both from the callback, when it is vritually guaranteed there is space in the OpenSL queue, but it is also called from the application's own thread as soon as a packet arrives from the network. Even if the OpenSL queue is full, it will attempt to enqueue a new buffer, and if that fails (return of SL_RESULT_BUFFER_INSUFFICIENT), that buffer will go into the overflow queue, to be enqueued later when space becomes available. In effect, the enqueue happens more often than it needs to, and there is a certain probability that this enqueue from the application's main thread will coincide with the lock being in place in OpenSL's own thread, which will lead to the deadlock like the one I pasted in the original post.
Just in case, this is the initialization code for OpenSL playback:
bool createPlayer()
{
SLresult result;
// Create and realize engine, and get interface
result = slCreateEngine(&g_engineObj, 0, 0, 0, 0, 0);
if(result != SL_RESULT_SUCCESS)
{
return false;
}
else
{
(*g_engineObj)->Realize(g_engineObj, SL_BOOLEAN_FALSE);
(*g_engineObj)->GetInterface(g_engineObj, SL_IID_ENGINE, &g_engine);
}
result = (*g_engine)->CreateOutputMix(g_engine, &g_outputMixObj, 0, 0, 0);
if(result != SL_RESULT_SUCCESS)
{
return false;
}
else
{
(*g_outputMixObj)->Realize(g_outputMixObj, SL_BOOLEAN_FALSE);
(*g_outputMixObj)->GetInterface(g_outputMixObj, SL_IID_OUTPUTMIX, &g_outputMix);
}
// Define audio source for playback (queue - PCM:Mono/8kHz/16-bit/little endian)
SLDataLocator_AndroidSimpleBufferQueue playerQueueLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
PLAY_QUEUE_SIZE };
SLDataFormat_PCM formatPcm = { SL_DATAFORMAT_PCM,
1, /* Mono */
SL_SAMPLINGRATE_8,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_CENTER,
SL_BYTEORDER_LITTLEENDIAN };
SLDataSource audioSrc = {&playerQueueLocator, &formatPcm};
// Define audio sink for playback
SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, g_outputMixObj };
SLDataSink audioSnk = {&outputMixLocator, 0};
// Create and realize player, and get interface
const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
const SLboolean reqs[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
result = (*g_engine)->CreateAudioPlayer(g_engine, &g_playerObj, &audioSrc, &audioSnk, 2, ids, reqs);
if(result != SL_RESULT_SUCCESS)
{
return false;
}
else
{
SLAndroidConfigurationItf playerConfig;
result = (*g_playerObj)->GetInterface(g_playerObj, SL_IID_ANDROIDCONFIGURATION, &playerConfig);
if(SL_RESULT_SUCCESS == result)
{
SLint32 streamType = SL_ANDROID_STREAM_VOICE;
(*playerConfig)->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32));
}
(*g_playerObj)->Realize(g_playerObj, SL_BOOLEAN_FALSE);
(*g_playerObj)->GetInterface(g_playerObj, SL_IID_PLAY, &g_play);
// Get interface and register callback for player queue
(*g_playerObj)->GetInterface(g_playerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &g_playerQueue);
(*g_playerQueue)->RegisterCallback(g_playerQueue, playerQueueCallback, 0);
return true;
}