我的OpenAL c++音频流缓冲区故障

My OpenAL C++ audio streaming buffer gliching

本文关键字:缓冲区 故障 音频流 c++ OpenAL 我的      更新时间:2023-10-16

我第一次在c++中使用OpenAL编码声音生成。我要做的是产生无限的窦波以双重缓冲的方式。问题是声音闪烁/滞后。我想是在缓冲之间,我不知道为什么会这样。

我代码:

void _OpenALEngine::play()
{
    if(!m_running && !m_threadRunning)
    {
        ALfloat sourcePos[] = {0,0,0};
        ALfloat sourceVel[] = {0,0,0};
        ALfloat sourceOri[] = {0,0,0,0,0,0};
        alGenSources(1, &FSourceID);
        alSourcefv (FSourceID, AL_POSITION, sourcePos);
        alSourcefv (FSourceID, AL_VELOCITY, sourceVel);
        alSourcefv (FSourceID, AL_DIRECTION, sourceOri);
        GetALError();
        ALuint FBufferID[2];
        alGenBuffers( 2, &FBufferID[0] );
        GetALError();
        // Gain
        ALfloat listenerPos[] = {0,0,0};
        ALfloat listenerVel[] = {0,0,0};
        ALfloat listenerOri[] = {0,0,0,0,0,0};
        alListenerf( AL_GAIN, 1.0 );
        alListenerfv(AL_POSITION, listenerPos);
        alListenerfv(AL_VELOCITY, listenerVel);
        alListenerfv(AL_ORIENTATION, listenerOri);
        GetALError();
        alSourceQueueBuffers( FSourceID, 2, &FBufferID[0] );
        GetALError();
        alSourcePlay(FSourceID);
        GetALError();
        m_running = true;
        m_threadRunning = true;
        Threading::Thread thread(Threading::ThreadStart(this, &_OpenALEngine::threadPlaying));
        thread.Start();
    }
}
Void _OpenALEngine::threadPlaying()
{
    while(m_running)
    {
        // Check how much data is processed in OpenAL's internal queue.
        ALint Processed;
        alGetSourcei( FSourceID, AL_BUFFERS_PROCESSED, &Processed );
        GetALError();
        // Add more buffers while we need them.
        while ( Processed-- )
        {
            alSourceUnqueueBuffers( FSourceID, 1, &BufID );
            runBuffer(); // <--- Generate the sinus wave and submit the Array to the submitBuffer method.
            alSourceQueueBuffers( FSourceID, 1, &BufID );
            ALint val;
            alGetSourcei(FSourceID, AL_SOURCE_STATE, &val);
            if(val != AL_PLAYING)
            {
                alSourcePlay(FSourceID);
            }
        }
        // Don't kill the CPU.
        Thread::Sleep(1);
    }
    m_threadRunning = false;
    return Void();
}
void _OpenALEngine::submitBuffer(byte* buffer, int length)
{
    // Submit more data to OpenAL
    alBufferData( BufID, AL_FORMAT_MONO8, buffer, length * sizeof(byte), 44100 );
}

我在runBuffer()方法中生成窦波。而窦发生器是正确的,因为当我将缓冲阵列从4096增加到40960时,闪烁/滞后的声音间隔更大。非常感谢,如果有人知道这个问题,并将分享:)

类似的问题在互联网上到处都是,我不能100%确定这是解决这个问题的办法。但它可能是,如果不是,它至少可以帮助别人。大多数其他线程都在不同的论坛上,我不是到处注册只是为了分享我的知识…

下面的代码是我经过2天的实验得出的。我找到的大多数解决方案都不适合我……(它不完全是我的代码,我剥离了一些特殊的部分,所以我很抱歉,如果有错别字或类似的阻止它被逐字复制)

我的实验是在iPhone上进行的。我发现的一些东西,可能是ios特有的。

问题是不能保证在什么时候一个已处理的缓冲区被标记为可用于取消队列。试图构建一个在缓冲区再次可用之前休眠的版本,我发现这可能比预期晚得多(我使用非常小的缓冲区)。所以我意识到,等到缓冲区可用(这适用于大多数框架,但不是openAL)的共同想法是错误的。相反,您应该等到应该将另一个缓冲区排队的时间。这样你就不得不放弃双缓冲的想法了。当时间到来时,您应该检查是否存在缓冲区并取消它的队列。但如果没有可用的,你需要创建第三个…

等待缓冲区何时应该排队可以通过计算相对于系统时钟的时间来完成,这对我来说工作得相当好,但我决定去一个版本,我依赖于一个时间源,这是明确的与openAL同步。我能想到的最好办法就是等一等,看队伍里还有多少人。在这里,iOS似乎不完全符合openAL-spec,因为AL_SAMPLE_OFFSET应该精确到一个样本,但我从来没有看到任何东西,除了2048的倍数。这大约是45微秒@44100,这就是代码中50000的来源(比iOS处理的最小单位多一点)根据块的大小,这个值可以很容易地变大。但是使用这些代码,我在最后一个小时内再次需要3次alSourcePlay()(相比之下,与声称是解决方案的其他实现相比,每分钟最多需要10次)

uint64 enqueued(0);  // keep track of samples in queue
while (bKeepRunning)
{
   // check if enough in buffer and wait
   ALint off;
   alGetSourcei(m_Source,  AL_SAMPLE_OFFSET, &off);
   uint32 left((enqueued-off)*1000000/SAMPLE_RATE);
   if (left > 50000) // at least 50000 mic-secs in buffer
     usleep(left - 50000);
   // check for available buffer
   ALuint buffer;
   ALint processed;
   alGetSourcei(m_Source, AL_BUFFERS_PROCESSED, &processed);
   switch (processed)
   {
   case 0:  // no buffer to unqueue->create new
     alGenBuffers(1, &buffer);
     break;
   case 1:  // on buffer to unqueue->use that
     alSourceUnqueueBuffers(m_Source, 1, &buffer);
     enqueued -= BLOCK_SIZE_SAMPLES; 
     break;
   default:  // multiple buffers to unqueue->take one,delete on
     {       // could also delete more if processed>2
             // but doesn't happen often
             // therefore simple implementation(will del. in next loop)
       ALuint bufs[2];
       alSourceUnqueueBuffers(m_Source, 2, bufs);
       alDeleteBuffers(1, bufs);
       buffer = bufs[1];
       enqueued -= 2*BLOCK_SIZE_SAMPLES; 
     }
     break;
   }
   // fill block
   alBufferData(buffer, AL_FORMAT_STEREO16, pData,
                BLOCK_SIZE_SAMPLES*4, SAMPLE_RATE);
   alSourceQueueBuffers(m_Source, 1, &buffer);
   //check state
   ALint state;
   alGetSourcei(m_Source, AL_SOURCE_STATE, &state);
   if (state != AL_PLAYING)
   {
     enqueued = BLOCK_SIZE_SAMPLES;
     alSourcePlay(m_Source);
   }
   else
     enqueued += BLOCK_SIZE_SAMPLES;
}

我写过OpenAL流服务器,所以我知道你的痛苦——我的直觉是确认你已经为I/O逻辑生成了单独的线程,这些线程可用你的流音频数据——与保存你上面的OpenAL代码的线程分开?否则会引起你的症状。下面是将每个逻辑块启动到自己的线程的简单示例:

std::thread t1(launch_producer_streaming_io, chosen_file, another_input_parm);
std::this_thread::sleep_for (std::chrono::milliseconds( 100));
std::thread t2(launch_consumer_openal, its_input_parm1, parm2);
// -------------------------
t1.join();
t2.join();

其中launch_producer_streaming_io是一个方法被调用与它的输入参数服务的输入/输出连续提供音频数据…launch_consumer_openal是一个在自己的线程中启动的方法,您可以在其中实例化OpenAL类