2

I am trying to stream audio data from disk using OpenAL's buffer queueing mechanism. I load and enqueue 4 buffers, start the source playing, and check in a regular intervals to refresh the queue. Everything looks like it's going splendidly, up until the first time I try to load data into a recycled buffer I got from alSourceUnqueueBuffers(). In this situation, alBufferData() always sets AL_INVALID_OPERATION, which according to the official v1.1 spec, it doesn't seem like it should be able to do.

I have searched extensively on Google and StackOverflow, and can't seem to find any reason why this would happen. The closest thing I found was someone with a possibly-related issue in an archived forum post, but details are few and responses are null. There was also this SO question with slightly different circumstances, but the only answer's suggestion does not help.

Possibly helpful: I know my context and device are configured correctly, because loading small wav files completely into a single buffer and playing them works fine. Through experimentation, I've also found that queueing 2 buffers, starting the source playing, and immediately loading and enqueueing the other two buffers throws no errors; it's only when I've unqueued a processed buffer that I run into trouble.

The relevant code:

static constexpr int MAX_BUFFER_COUNT = 4;

#define alCall(funcCall) {funcCall; SoundyOutport::CheckError(__FILE__, __LINE__, #funcCall) ? abort() : ((void)0); }

bool SoundyOutport::CheckError(const string &pFile, int pLine, const string &pfunc)
{
    ALenum tErrCode = alGetError();
    if(tErrCode != 0)
    {
        auto tMsg = alGetString(tErrCode);
        Log::e(ro::TAG) << tMsg << " at " << pFile << "(" << pLine << "):\n"
                        << "\tAL call " << pfunc << " failed." << end;
        return true;
    }
    return false;
}

void SoundyOutport::EnqueueBuffer(const float* pData, int pFrames)
{
    static int called = 0;
    ++called;

    ALint tState;
    alCall(alGetSourcei(mSourceId, AL_SOURCE_TYPE, &tState));
    if(tState == AL_STATIC)
    {
        Stop();
//      alCall(alSourcei(mSourceId, AL_BUFFER, NULL));
    }

    ALuint tBufId = AL_NONE;
    int tQueuedBuffers = QueuedUpBuffers();
    int tReady = ProcessedBuffers();
    if(tQueuedBuffers < MAX_BUFFER_COUNT)
    {
        tBufId = mBufferIds[tQueuedBuffers];
    }
    else if(tReady > 0)
    {
        // the fifth time through, this code gets hit
        alCall(alSourceUnqueueBuffers(mSourceId, 1, &tBufId));

        // debug code: make sure these values go down by one
        tQueuedBuffers = QueuedUpBuffers();
        tReady = ProcessedBuffers();
    }
    else
    {
        return; // no update needed yet.
    }

    void* tConverted = convert(pData, pFrames);

    // the fifth time through, we get AL_INVALID_OPERATION, and call abort()
    alCall(alBufferData(tBufId, mFormat, tConverted, pFrames * mBitdepth/8, mSampleRate));

    alCall(alSourceQueueBuffers(mSourceId, 1, &mBufferId));
    if(mBitdepth == BITDEPTH_8)
    {
        delete (uint8_t*)tConverted;
    }
    else // if(mBitdepth == BITDEPTH_16)
    {
        delete (uint16_t*)tConverted;
    }
}

void SoundyOutport::PlayBufferedStream()
{
    if(!StreamingMode() || !QueuedUpBuffers())
    {
        Log::w(ro::TAG) << "Attempted to play an unbuffered stream" << end;
        return;
    }

    alCall(alSourcei(mSourceId, AL_LOOPING, AL_FALSE)); // never loop streams
    alCall(alSourcePlay(mSourceId));
}

int SoundyOutport::QueuedUpBuffers()
{
    int tCount = 0;
    alCall(alGetSourcei(mSourceId, AL_BUFFERS_QUEUED, &tCount));
    return tCount;
}

int SoundyOutport::ProcessedBuffers()
{
    int tCount = 0;
    alCall(alGetSourcei(mSourceId, AL_BUFFERS_PROCESSED, &tCount));
    return tCount;
}

void SoundyOutport::Stop()
{
    if(Playing())
    {
        alCall(alSourceStop(mSourceId));
    }

    int tBuffers;
    alCall(alGetSourcei(mSourceId, AL_BUFFERS_QUEUED, &tBuffers));
    if(tBuffers)
    {
        ALuint tDummy[tBuffers];
        alCall(alSourceUnqueueBuffers(mSourceId, tBuffers, tDummy));
    }
    alCall(alSourcei(mSourceId, AL_BUFFER, AL_NONE));
}

bool SoundyOutport::Playing()
{
    ALint tPlaying;
    alCall(alGetSourcei(mSourceId, AL_SOURCE_STATE, &tPlaying));
    return tPlaying == AL_PLAYING;
}

bool SoundyOutport::StreamingMode()
{
    ALint tState;
    alCall(alGetSourcei(mSourceId, AL_SOURCE_TYPE, &tState));
    return tState == AL_STREAMING;
}

bool SoundyOutport::StaticMode()
{
    ALint tState;
    alCall(alGetSourcei(mSourceId, AL_SOURCE_TYPE, &tState));
    return tState == AL_STATIC;
}

And here's an annotated screen cap of what I see in my debugger when I hit the error:

Annotated view of debugger contents

I've tried a bunch of little tweaks and variations, and the result is always the same. I've wasted too many days trying to fix this. Please help :)

Community
  • 1
  • 1
Ionoclast Brigham
  • 1,703
  • 1
  • 12
  • 19

1 Answers1

0

This error occurs when you trying to fill buffer with data, when the buffer is still queued to the source.

Also this code is wrong.

if(tQueuedBuffers < MAX_BUFFER_COUNT)
{
    tBufId = mBufferIds[tQueuedBuffers];
}
else if(tReady > 0)
{
    // the fifth time through, this code gets hit
    alCall(alSourceUnqueueBuffers(mSourceId, 1, &tBufId));

    // debug code: make sure these values go down by one
    tQueuedBuffers = QueuedUpBuffers();
    tReady = ProcessedBuffers();
}
else
{
    return; // no update needed yet.
}

You can fill buffer with data only if it unqueued from source. But your first if block gets tBufId that queued to the source. Rewrite code like so

if(tReady > 0)
{
    // the fifth time through, this code gets hit
    alCall(alSourceUnqueueBuffers(mSourceId, 1, &tBufId));

    // debug code: make sure these values go down by one
    tQueuedBuffers = QueuedUpBuffers();
    tReady = ProcessedBuffers();
}
else
{
    return; // no update needed yet.
}
mrDIMAS
  • 1
  • 1
  • I actually switched to SDL Mixer a long time ago, when no one seemed to know what might be up with my AL code. If anyone can chime in to verify this answer, I will be happy to accept it. – Ionoclast Brigham Nov 03 '15 at 18:34