Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenSL ES buffer callbacks on Android #10

Closed
ghost opened this issue Apr 21, 2015 · 9 comments
Closed

OpenSL ES buffer callbacks on Android #10

ghost opened this issue Apr 21, 2015 · 9 comments

Comments

@ghost
Copy link

ghost commented Apr 21, 2015

On some Android implementations the OpenSL ES back-end seems to crash the program when playback is stopped. The cause of the crash is a SIGSEGV and happens shortly after playback is stopped. When logging calls to opensl_callback and opensl_stop_playback it seems that the stop function frees the buffer while the callback function is still running:

04-21 15:24:26.779 2832-2853/test D/opensl.c﹕ opensl_callback
04-21 15:24:26.799 2832-2853/test D/opensl.c﹕ opensl_callback done
04-21 15:24:26.819 2832-2853/test D/opensl.c﹕ opensl_callback
04-21 15:24:26.829 2832-2845/test D/opensl.c﹕ opensl_stop_playback
04-21 15:24:26.829 2832-2845/test D/opensl.c﹕ opensl_stop_playback done
04-21 15:24:26.849 2832-2853/test W/libOpenSLES﹕ Leaving BufferQueue::Enqueue (SL_RESULT_PARAMETER_INVALID)
04-21 15:24:26.849 2832-2853/test D/OpenAl﹕ AL lib: (EE) opensl_callback: bq->Enqueue: Parameter invalid
04-21 15:24:26.849 2832-2853/test D/opensl.c﹕ opensl_callback done

The probability of this happening seems to be greatly increased when HRTFs are enabled. These crashes happen on the Android Emulator running API 17 and on a real device running API 19.

@loki666
Copy link

loki666 commented Aug 13, 2015

I'm using a slightly modified version of the opensl backend with orx game engine. and It seems to works fine so far.

I moved VCALL0(bufferQueue,Clear)(); in opensl_start_playback function.

static void opensl_stop_playback(ALCdevice *Device)
{
    osl_data *data = Device->ExtraData;
    SLPlayItf player;
    SLresult result;

    result = VCALL(data->bufferQueueObject,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");
    }

    free(data->buffer);
    data->buffer = NULL;
    data->bufferSize = 0;
}

static ALCboolean opensl_start_playback(ALCdevice *Device)
{
    osl_data *data = Device->ExtraData;
    SLAndroidSimpleBufferQueueItf bufferQueue;
    SLPlayItf player;
    SLresult result;
    ALuint i;

    result = VCALL(data->bufferQueueObject,GetInterface)(SL_IID_BUFFERQUEUE, &bufferQueue);
    PRINTERR(result, "bufferQueue->GetInterface");

    if(SL_RESULT_SUCCESS == result)
    {
        result = VCALL(bufferQueue,RegisterCallback)(opensl_callback, Device);
        PRINTERR(result, "bufferQueue->RegisterCallback");
    }

    if(SL_RESULT_SUCCESS == result)
    {
        data->frameSize = FrameSizeFromDevFmt(Device->FmtChans, Device->FmtType);
        data->bufferSize = Device->UpdateSize * data->frameSize;
        data->buffer = calloc(Device->NumUpdates, data->bufferSize);
        if(!data->buffer)
        {
            result = SL_RESULT_MEMORY_FAILURE;
            PRINTERR(result, "calloc");
        }
    }

    if(SL_RESULT_SUCCESS == result)
    {
        result = VCALL0(bufferQueue,Clear)();
        PRINTERR(result, "bufferQueue->Clear");
    }

    /* enqueue the first buffer to kick off the callbacks */
    for(i = 0;i < Device->NumUpdates;i++)
    {
        if(SL_RESULT_SUCCESS == result)
        {
            ALvoid *buf = (ALbyte*)data->buffer + i*data->bufferSize;
            result = VCALL(bufferQueue,Enqueue)(buf, data->bufferSize);
            PRINTERR(result, "bufferQueue->Enqueue");
        }
    }
    data->curBuffer = 0;
    if(SL_RESULT_SUCCESS == result)
    {
        result = VCALL(data->bufferQueueObject,GetInterface)(SL_IID_PLAY, &player);
        PRINTERR(result, "bufferQueue->GetInterface");
    }
    if(SL_RESULT_SUCCESS == result)
    {
        result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
        PRINTERR(result, "player->SetPlayState");
    }

    if(SL_RESULT_SUCCESS != result)
    {
        if(data->bufferQueueObject != NULL)
            VCALL0(data->bufferQueueObject,Destroy)();
        data->bufferQueueObject = NULL;

        free(data->buffer);
        data->buffer = NULL;
        data->bufferSize = 0;

        return ALC_FALSE;
    }

    return ALC_TRUE;
}

check if this works for you

@kcat
Copy link
Owner

kcat commented Aug 13, 2015

Unfortunately I don't think that's a real fix. If the callback is still being called after the stream is stopped and cleared, surely it would still get called after just being stopped?

Perhaps a better fix would be to call
VCALL(bufferQueue,RegisterCallback)(NULL, NULL);
in opensl_stop_playback, although I don't know if that's safe, or will guarantee the callback is no longer running when it returns.

@loki666
Copy link

loki666 commented Aug 13, 2015

VCALL(bufferQueue,RegisterCallback)(NULL, NULL); could work, but you'll have to active poll the state of the player until it's SL_PLAYSTATE_STOPPED before calling that.

also I forgot, I added a NULL check on data->buffer in opensl_callback. that's probably the source of the SIGSEGV

my guess is that opensl_stop_playback is executed between aluMixData and VCALL(bq,Enqueue)(buf, data->bufferSize);

VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED); is asynchronous and wont stop the callback thread if it's executing the callback.
So before freeing the buffer and clearing, we have to make sure the player is really stopped.

@kcat
Copy link
Owner

kcat commented Aug 13, 2015

also I forgot, I added a NULL check on data->buffer in opensl_callback.

Unfortunately I don't think that would be enough. It'd be possible for the callback to see a non-NULL buffer, then the stop method goes and frees it before the callback is done with it.

Does the SLPlayItf::GetPlaybackState method return the actual state of the player, or what was last set with SLPlayItf::SetPlaybackState?

@loki666
Copy link

loki666 commented Aug 13, 2015

not sure...

but you can query, the bufferQueue state and wait until state.count == 0.
This should ensure that the player is actually stopped, and the buffer
callback won't be called.

so the sequence should be

VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
VCALL0(bufferQueue,Clear)(); // not required since we are waiting for the
enqueued buffers to consume
VCALL(bufferQueue,GetState)(&state);
while(state.count) {
VCALL(bufferQueue,GetState)(&state);
}

or something...

an other options could be to starve the bufferQueue (by flipping a
boolean), leaving the player in the PLAYING state, ready to Enqueue new
buffer to resume.

On Thu, Aug 13, 2015 at 11:28 PM, kcat notifications@github.com wrote:

also I forgot, I added a NULL check on data->buffer in opensl_callback.

Unfortunately I don't think that would be enough. It'd be possible for the
callback to see a non-NULL buffer, then the stop method goes and frees it
before the callback is done with it.

Does the SLPlayItf::GetPlaybackState method return the actual state of
the player, or what was last set with SLPlayItf::SetPlaybackState?


Reply to this email directly or view it on GitHub
#10 (comment).

@kcat
Copy link
Owner

kcat commented Aug 13, 2015

I attempted a fix in commit 7d4e368. Can you test that to see if it works?

@loki666
Copy link

loki666 commented Aug 14, 2015

seems to work here, but on my device I have a very small buffer (240 frames) so it's difficult to reproduce

@fatalfeel
Copy link

here is my source download on android
on the bottom of link
http://fatalfeel.blogspot.tw/2013/09/openal-hrtf-3d-sound.html

and thanks kcat help~

@kcat
Copy link
Owner

kcat commented Aug 5, 2016

Since it's been nearly a year without hearing about a problem, I'll assume this is fixed. If there's still a problem, please create a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants