Alexander
Alexander

Reputation: 481

My OpenAL C++ audio streaming buffer gliching

I have for the first time coding sound generating with OpenAL in C++. What I want to do is to generate endless sinus wave into a double buffering way. And the problem is that the sound is glittering/lags. I Think it is between the buffering and I don't know why it is like that.

My code:

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

I generate the sinus wave in the runBuffer() method. And the sinus generator is correct because when I increase the buffer array from 4096 to 40960 the glittering/lags sound with bigger interval. Thank you very much if some one know the problem and will share it :)

Upvotes: 2

Views: 1801

Answers (2)

moritz
moritz

Reputation: 198

Similar Problems are all over the internet and I'm not 100% sure this is the solution to this on. But it probably is, and if not it might at least help others. Most other threads are on different forums and I'm not registering everywhere just to share my knowledge...

The code below is what I came up after 2 days of experimenting. Most solutions I found did not work for me... (it's not exactly my code, I stripped it of some parts special to my case, so I'm sorry if there are typos or similar that prevent it from being copied verbatim)

My experiments were on an iPhone. Some of the the things I found out, might be iOS-specific.

The problem is that there is no guaranty at what point a processed buffer is marked as such and is available for unqueueing. Trying to build a version that sleeps until a buffer becomes available again I saw that this might be much(I use very small buffers) later than expected. So I realised that the common idea to wait until a buffer is available(which works for most frameworks, but not openAL) is wrong. Instead you should wait until the time you should enqueue another buffer. With that you have to give up the idea of double-buffering. When the time comes you should check if a buffer exists and unqueue it. But if none is available you need to create a 3rd...

Waiting for when a buffer should be enqueue can be done by calculating times relative to the system-clock, which worked fairly well for me but I decided to go for a version where I rely on a time source that is definitivly in sync with openAL. Best I came up with was wait depending on what s left in the queue. Here, iOS seems not fully in accordance to openAL-spec because AL_SAMPLE_OFFSET should be exact to one sample but I never saw anything but multiples of 2048. That's about 45 microseconds @44100, this is where the 50000 in the code comes from(little more than the smalest unit iOS handles) Depending on the block-size this can easily be bigger. But with that code I had 3 times that alSourcePlay() was needed again in the last ~hour(compared to up to 10 per minute with other implementations that claimed to be the solution)

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

Upvotes: 1

Scott Stensland
Scott Stensland

Reputation: 28325

I have written OpenAL streaming servers so I know your pain - my instinct is to confirm you have spawned separate threads for the I/O logic which available your streaming audio data - separate from the thread to hold your above OpenAL code ??? If not this will cause your symptoms. Here is a simple launch of each logical chunk into its own thread :

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();

where launch_producer_streaming_io is a method being called with its input parms which services the input/output to continuously supply the audio data ... launch_consumer_openal is a method launched in its own thread where you instantiate your OpenAL class

Upvotes: 0

Related Questions