True OpenAL AudioDevice

Any community contributions to libgdx go here! Some may get included in the core API when permission is granted.

True OpenAL AudioDevice

Postby schmop » Tue Jun 28, 2011 4:18 pm

Hi,

I was toying around with libgdx's audio devices (with the lwjgl backend, which uses OpenAL, as does Jogl) to see if I could stream incoming audio with the writeSamples methods... and I couldn't! Sampling rate and #channels parameters seemed to be ignored (or at least weirdly treated) and repeated calls to the method left gaps in the audio output. Checking out the sources, I saw there was no OpenAl audio device, but a java sound device as a temporary replacement. So I went about writing the missing device:

Code: Select all
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Vector;

import org.lwjgl.openal.AL10;

import com.badlogic.gdx.audio.AudioDevice;
import com.badlogic.gdx.utils.BufferUtils;
import com.badlogic.gdx.utils.GdxRuntimeException;

public class OpenALDevice implements AudioDevice
{
   int sourceId;
   IntBuffer bufferIds;
   
   boolean isMono;
   int samplingRate;
   
   ShortBuffer transferBuffer;
   int currentBuffer, currentCursor;
   Vector<Integer> idleBuffers;
   
   int bufferSize = 2048;
   int nBuffers = 4;
   
   int errnum;
   
   public OpenALDevice(int samplingRate, boolean isMono)
   {
      this.sourceId = AL10.alGenSources();
      if ((errnum = AL10.alGetError()) != AL10.AL_NO_ERROR)
         throw new GdxRuntimeException("Couldn't create source - "+errnum);
      this.bufferIds = BufferUtils.newIntBuffer(nBuffers);
      AL10.alGenBuffers(bufferIds);
      if ((errnum = AL10.alGetError()) != AL10.AL_NO_ERROR)
         throw new GdxRuntimeException("Couldn't create buffers - "+errnum);
      
      this.isMono = isMono;
      this.samplingRate = samplingRate;
      
      this.transferBuffer = BufferUtils.newShortBuffer(bufferSize);
      this.currentBuffer = -1;
      this.currentCursor = 0;
      
      for (int i=0;i<transferBuffer.capacity();i++)
         transferBuffer.put(i, (short)0);
      
      this.idleBuffers = new Vector<Integer>(bufferIds.capacity());
      for (int i=0;i<bufferIds.capacity();i++)
      {
         AL10.alBufferData(bufferIds.get(i), isMono ? AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16,
            transferBuffer, samplingRate);
         idleBuffers.add(bufferIds.get(i));
      }
   }
   
   long timeout = 5000;
   int acquireBuffer()
   {
      if (!idleBuffers.isEmpty())
         return idleBuffers.remove(idleBuffers.size()-1);
      else if (AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING)
      {
         while (AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED) > 0)
            idleBuffers.add(AL10.alSourceUnqueueBuffers(sourceId));
         return idleBuffers.remove(idleBuffers.size()-1);
      }
      else
      {
         long start = System.currentTimeMillis();
         while (AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED) == 0)
         {
            if (System.currentTimeMillis()-start >= timeout)
               return -1;
            try {Thread.sleep(50);}
            catch (Exception e) {}
         }
         return AL10.alSourceUnqueueBuffers(sourceId);
      }
   }
   
   void submitBuffer(int bufferId)
   {
      AL10.alBufferData(bufferId, isMono ? AL10.AL_FORMAT_MONO16 : AL10.AL_FORMAT_STEREO16,
         transferBuffer, samplingRate);
      AL10.alSourceQueueBuffers(sourceId, bufferId);
      if (AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING)
         AL10.alSourcePlay(sourceId);
   }
   
   @Override public void dispose()
   {
      AL10.alSourceStop(sourceId);
      AL10.alDeleteBuffers(bufferIds);
      AL10.alDeleteSources(sourceId);
   }

   @Override public int getLatency() {return (int)(1000*bufferSize*nBuffers*(isMono ? 1. : .5)/samplingRate);}

   @Override public boolean isMono() {return isMono;}

   @Override public void writeSamples(short [] samples, int offset, int numSamples)
   {
      while (numSamples > 0)
      {
         if (currentBuffer == -1)
         {
            currentBuffer = acquireBuffer();
            if (currentBuffer == -1)
               //throw new GdxRuntimeException("OpenAL device timeout");
               return;
            currentCursor = 0;
         }
         
         int nWrite = Math.min(transferBuffer.capacity()-currentCursor, numSamples);
         transferBuffer.position(currentCursor);
         transferBuffer.put(samples, offset, nWrite);
         transferBuffer.position(0);
         currentCursor += nWrite;
         offset += nWrite;
         numSamples -= nWrite;
         
         if (currentCursor == transferBuffer.capacity())
         {
            submitBuffer(currentBuffer);
            currentBuffer = -1;
            currentCursor = 0;
         }
      }
   }

   @Override public void writeSamples(float [] samples, int offset, int numSamples)
   {
   }
}


Probably not robust enough for something advanced, but the AudioDevice interface doesn't give much control anyway. I didn't do the writeSamples variant with floats, I got lazy...
Anyways, I hope it can be of use to someone
Cheers
schmop
 
Posts: 17
Joined: Tue Jun 21, 2011 10:55 am

Re: True OpenAL AudioDevice

Postby mzechner » Tue Jun 28, 2011 6:15 pm

Can we integrate this in the core with your permission? You'd become a contributor. The only thing i'd need is a signed CLA (yeah, i know that sucks, legal stuff always does...).

I wanted to write this for 1.0, but didn't get around doing it yet.
mzechner
Site Admin
 
Posts: 4879
Joined: Sat Jul 10, 2010 3:50 pm


Return to Libgdx Contributions

Who is online

Users browsing this forum: No registered users and 1 guest

cron