macos - 使用从alSourceUnqueueBuffers()获得的缓冲区ID时,alBufferData()设置AL_INVALID_OPERATION

标签 macos audio game-engine openal

我正在尝试使用OpenAL的缓冲区排队机制从磁盘流式传输音频数据。我加载并排入4个缓冲区,开始播放源,并定期检查刷新队列。一切看起来都很棒,直到我第一次尝试将数据加载到从alSourceUnqueueBuffers()获得的回收缓冲区中之前。在这种情况下,alBufferData()始终设置AL_INVALID_OPERATION,根据the official v1.1 spec,它似乎不应该能够这样做。

我已经在Google和StackOverflow上进行了广泛的搜索,但似乎找不到发生这种情况的任何原因。我发现的最接近的东西是论坛存档中有人使用a possibly-related issue的人,但详细信息很少,响应为null。也有this SO question,情况略有不同,但是唯一答案的建议无济于事。

可能会有所帮助:我知道我的上下文和设备已正确配置,因为将小的wav文件完全加载到单个缓冲区中并可以正常播放。通过实验,我还发现对2个缓冲区进行排队,开始播放源文件以及立即加载和排队其他两个缓冲区不会引发任何错误;只有当我取消了已处理的缓冲区的排队时,我才遇到麻烦。

相关代码:

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

这是我遇到错误时在调试器中看到的带注释的屏幕截图:

我已经尝试了一些小调整和变化,结果始终是相同的。我已经浪费了太多时间试图解决这个问题。请帮忙 :)

最佳答案

当您尝试用数据填充缓冲区时,当缓冲区仍排队到源时,会发生此错误。

另外此代码是错误的。

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.
}

仅当数据从源中取消排队时,才可以用数据填充缓冲区。但是您的第一个(如果块)获得了排队到源的 tBufId 。像这样重写代码
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.
}

关于macos - 使用从alSourceUnqueueBuffers()获得的缓冲区ID时,alBufferData()设置AL_INVALID_OPERATION,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23072299/

相关文章:

game-engine - 3d 游戏编程模型?

macos - OS X 应用程序菜单栏未显示正确的标题

c++ - Objective-C++ 无法使用 MyType *__strong 类型的左值初始化类型为 id<...> 的参数,即使它符合协议(protocol)

swift - SpriteKit 背景图像滚动在接缝处闪烁

c++ - 读取RAW音频文件

audio - 在Audacity中为链式店添加白噪声

c++ - weak_import 生成链接器错误

c++ - 16 位音频的 fftw::peak 在 2f 出现错误

Java游戏开发。使瓦片 map 更快。优化建议

ruby - 是否可以在 RPG Maker XP 中更新 Ruby 版本