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

Posted

技术标签:

【中文标题】我的 OpenAL C++ 音频流缓冲区故障【英文标题】:My OpenAL C++ audio streaming buffer gliching 【发布时间】:2014-06-29 22:46:02 【问题描述】:

我第一次在 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 时,闪烁/滞后的声音具有更大的间隔。如果有人知道问题并会分享它,非常感谢你:)

【问题讨论】:

告诉我们这是如何解决的 不,抱歉,我无法使用它。现在我现在改用 Windows 多媒体库:planet-source-code.com/vb/scripts/… 但很快我会再试一次并修复它。 【参考方案1】:

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

下面的代码是我经过 2 天的实验得出的。我发现的大多数解决方案都不适合我...... (这不完全是我的代码,我删除了我的一些特殊部分,所以如果有拼写错误或类似内容阻止它被逐字复制,我很抱歉)

我的实验是在 iPhone 上进行的。我发现的一些内容可能是特定于 ios 的。

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

等待缓冲区何时入队可以通过计算相对于系统时钟的时间来完成,这对我来说效果很好,但我决定选择一个我依赖于明确同步的时间源的版本使用 openAL。我想出的最好的方法是等待队列中剩下的内容。在这里,iOS 似乎并不完全符合 openAL 规范,因为 AL_SAMPLE_OFFSET 应该精确到一个样本,但我只看到了 2048 的倍数。这大约是 45 微秒@44100,这就是代码中 50000 的来源(多一点比最小的 iOS 处理单元) 根据块大小,这很容易变大。但是使用该代码,我在最后〜小时内再次需要 alSourcePlay() 3 次(相比之下,声称是解决方案的其他实现每分钟最多 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;

【讨论】:

【参考方案2】:

我已经编写了 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 类

【讨论】:

以上是关于我的 OpenAL C++ 音频流缓冲区故障的主要内容,如果未能解决你的问题,请参考以下文章

从视频文件中提取音频并在 OpenAL 中播放

使用 OpenAL(C++) 录制声音。缓冲区大小

将音频流写入循环缓冲区但分段错误读取值

openal:获取源的当前播放位置

从音频缓冲区和视频缓冲区 C++ Windows 构造 mp4 文件

获取系统音频流缓冲区以进行可视化