Skip to content

Commit

Permalink
Adding iOS RoboVM AudioDevice implementation (#7371)
Browse files Browse the repository at this point in the history
  • Loading branch information
auraxangelic committed Apr 5, 2024
1 parent a6dc60f commit 7c6db62
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public void writeSamples (byte[] data, int offset, int length) {
buffers = BufferUtils.createIntBuffer(bufferCount);
alGetError();
alGenBuffers(buffers);
if (alGetError() != AL_NO_ERROR) throw new GdxRuntimeException("Unabe to allocate audio buffers.");
if (alGetError() != AL_NO_ERROR) throw new GdxRuntimeException("Unable to allocate audio buffers.");
}
alSourcei(sourceID, AL_LOOPING, AL_FALSE);
alSourcef(sourceID, AL_GAIN, volume);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class OpenALLwjglAudio implements LwjglAudio {
private ObjectMap<String, Class<? extends OpenALSound>> extensionToSoundClass = new ObjectMap();
private ObjectMap<String, Class<? extends OpenALMusic>> extensionToMusicClass = new ObjectMap();
private OpenALSound[] recentSounds;
private int mostRecetSound = -1;
private int mostRecentSound = -1;

Array<OpenALMusic> music = new Array(false, 1, OpenALMusic.class);
boolean noDevice = false;
Expand Down Expand Up @@ -362,15 +362,15 @@ public void dispose () {
* play */
protected void retain (OpenALSound sound, boolean stop) {
// Move the pointer ahead and wrap
mostRecetSound++;
mostRecetSound %= recentSounds.length;
mostRecentSound++;
mostRecentSound %= recentSounds.length;

if (stop) {
// Stop the least recent sound (the one we are about to bump off the buffer)
if (recentSounds[mostRecetSound] != null) recentSounds[mostRecetSound].stop();
if (recentSounds[mostRecentSound] != null) recentSounds[mostRecentSound].stop();
}

recentSounds[mostRecetSound] = sound;
recentSounds[mostRecentSound] = sound;
}

/** Removes the disposed sound from the least recently played list */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ public class IOSApplicationConfiguration {
/** The maximum number of threads to use for network requests. Default is {@link Integer#MAX_VALUE}. */
public int maxNetThreads = Integer.MAX_VALUE;

/** The minimal buffer size of the audio device. Below 2048 can lead to buggy behavior. */
public int audioDeviceBufferSize = 512;

/** How many buffers to use for audio device */
public int audioDeviceBufferCount = 9;

/** whether to use audio or not. Default is <code>true</code> **/
public boolean useAudio = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@

import org.robovm.apple.foundation.NSObject;
import org.robovm.objc.ObjCRuntime;
import org.robovm.objc.annotation.Method;
import org.robovm.objc.annotation.NativeClass;
import org.robovm.rt.bro.annotation.Library;
import org.robovm.rt.bro.ptr.VoidPtr;

/** @author Niklas Therning */
@Library(Library.INTERNAL)
@NativeClass
public class ALBuffer extends NSObject {

static {
ObjCRuntime.bind(ALBuffer.class);
}

@Method(selector = "bufferId")
public native int bufferId ();

@Method(selector = "initWithName:data:size:format:frequency:")
public native ALBuffer initWithNameDataSizeFormatFrequency (String name, VoidPtr data, int size, int format, int frequency);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

package com.badlogic.gdx.backends.iosrobovm.objectal;

public class ALConsts {
public static int AL_FORMAT_MONO16 = 0x1101;
public static int AL_FORMAT_STEREO16 = 0x1103;
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,18 @@ public class ALSource extends NSObject {
@Method(selector = "setLooping:")
public native void setLooping (boolean shouldLoop);

@Method(selector = "buffersProcessed")
public native int buffersProcessed ();

@Method(selector = "unqueueBuffer:")
public native boolean unqueueBuffer (ALBuffer buffer);

@Method(selector = "queueBuffer:")
public native boolean queueBuffer (ALBuffer buffer);

@Method(selector = "playing")
public native boolean playing ();

@Method(selector = "play")
public native int play ();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

package com.badlogic.gdx.backends.iosrobovm.objectal;

import org.robovm.apple.foundation.NSObject;
import org.robovm.objc.ObjCRuntime;
import org.robovm.objc.annotation.Method;
import org.robovm.objc.annotation.NativeClass;
import org.robovm.rt.bro.annotation.Library;
import org.robovm.rt.bro.ptr.VoidPtr;

/** @author Jile Gao */
@Library(Library.INTERNAL)
@NativeClass
public class ALWrapper extends NSObject {
static {
ObjCRuntime.bind(ALWrapper.class);
}

@Method(selector = "bufferData:format:data:size:frequency:")
public static native boolean bufferData (int bufferId, int format, VoidPtr data, int size, int frequency);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public final class OALAudioSession extends NSObject {
@Method
public native static OALAudioSession sharedInstance ();

@Method
public native boolean interrupted ();

@Method
public native void forceEndInterruption ();
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,12 @@ public OALIOSAudio (IOSApplicationConfiguration config) {
audio.setAllowIpod(config.allowIpod);
audio.setHonorSilentSwitch(!config.overrideRingerSwitch);
} else
Gdx.app.error("IOSAudio", "No OALSimpleAudio instance available, audio will not be availabe");
Gdx.app.error("IOSAudio", "No OALSimpleAudio instance available, audio will not be available");
}

@Override
public AudioDevice newAudioDevice (int samplingRate, boolean isMono) {
// TODO Auto-generated method stub
return null;
return new OALIOSAudioDevice(samplingRate, isMono, config.audioDeviceBufferSize, config.audioDeviceBufferCount);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@

package com.badlogic.gdx.backends.iosrobovm.objectal;

import com.badlogic.gdx.audio.AudioDevice;
import org.robovm.rt.bro.Struct;
import org.robovm.rt.bro.ptr.ShortPtr;
import org.robovm.rt.bro.ptr.VoidPtr;

import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;

import static com.badlogic.gdx.backends.iosrobovm.objectal.ALConsts.AL_FORMAT_MONO16;
import static com.badlogic.gdx.backends.iosrobovm.objectal.ALConsts.AL_FORMAT_STEREO16;

/** @author Jile Gao
* @author Berstanio */
class OALIOSAudioDevice implements AudioDevice {
private ALSource alSource;
private List<ALBuffer> alBuffers = new ArrayList<>();
private List<ALBuffer> alBuffersFree = new ArrayList<>();
private final int samplingRate;
private final boolean isMono;
private final int format;
private final ShortBuffer tmpBuffer;
private final int minSize;
private final int latency;

OALIOSAudioDevice (int samplingRate, boolean isMono, int minSize, int bufferCount) {
this.samplingRate = samplingRate;
this.isMono = isMono;
this.format = isMono ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
this.minSize = minSize;

tmpBuffer = ShortBuffer.allocate(minSize);
latency = minSize / (isMono ? 1 : 2) / bufferCount;
alSource = new ALSource();
for (int i = 0; i < bufferCount; i++) {
ALBuffer buffer = new ALBuffer().initWithNameDataSizeFormatFrequency("test", Struct.allocate(VoidPtr.class, 1), 2,
format, samplingRate);
alBuffersFree.add(buffer);
}
}

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

@Override
public void writeSamples (short[] samples, int offset, int numSamples) {
if (numSamples < 0) throw new IllegalArgumentException("numSamples cannot be < 0.");

ShortPtr shortPtr;
if (numSamples + tmpBuffer.position() >= minSize) {
shortPtr = Struct.allocate(ShortPtr.class, numSamples + tmpBuffer.position());
shortPtr.set(tmpBuffer.array(), 0, tmpBuffer.position());
shortPtr.next(tmpBuffer.position()).set(samples, offset, numSamples);
numSamples += tmpBuffer.position();
tmpBuffer.position(0);
} else {
tmpBuffer.put(samples, offset, numSamples);
return;
}

while (alBuffersFree.isEmpty()) {
if (OALAudioSession.sharedInstance().interrupted()) {
try {
Thread.sleep(2);
} catch (InterruptedException ignored) {
}
return;
}
int toFree = Math.min(alSource.buffersProcessed(), alBuffers.size());
for (int j = 0; j < toFree; j++) {
ALBuffer alBuffer = alBuffers.get(0);
if (alSource.unqueueBuffer(alBuffer)) {
alBuffersFree.add(alBuffer);
alBuffers.remove(alBuffer);
} else {
break;
}
}
if (alBuffersFree.isEmpty()) {
try {
Thread.sleep(2);
} catch (InterruptedException ignored) {
}
}
}

ALBuffer buffer = alBuffersFree.remove(0);
ALWrapper.bufferData(buffer.bufferId(), format, shortPtr.as(VoidPtr.class), numSamples * 2, samplingRate);
if (alSource.queueBuffer(buffer)) {
alBuffers.add(buffer);
}
if (!alSource.playing()) {
alSource.play();
}
}

@Override
public void writeSamples (float[] samples, int offset, int numSamples) {
short[] shortSamples = new short[samples.length];

for (int i = offset, j = 0; i < samples.length; i++, j++) {
float fValue = samples[i];
if (fValue > 1) fValue = 1;
if (fValue < -1) fValue = -1;
short value = (short)(fValue * Short.MAX_VALUE);
shortSamples[j] = value;
}
writeSamples(shortSamples, offset, numSamples);
}

@Override
public int getLatency () {
return latency;
}

@Override
public void dispose () {
alSource.stop();
alBuffers = null;
alBuffersFree = null;
alSource = null;
}

@Override
public void setVolume (float volume) {
alSource.setVolume(volume);
}

@Override
public void pause () {
alSource.setPaused(true);
}

@Override
public void resume () {
alSource.setPaused(false);
}
}

0 comments on commit 7c6db62

Please sign in to comment.