mirror of
https://github.com/badvision/lawless-legends.git
synced 2025-01-18 19:31:49 +00:00
Update memory allocation for media playback and added load/tortute tests for sound routines
This commit is contained in:
parent
a3e9a44254
commit
461e6ced00
@ -20,6 +20,7 @@ package jace.core;
|
||||
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -177,7 +178,7 @@ public class SoundMixer extends Device {
|
||||
PLAYBACK_ENABLED = true;
|
||||
}
|
||||
|
||||
private static List<SoundBuffer> buffers = new ArrayList<>();
|
||||
private static List<SoundBuffer> buffers = Collections.synchronizedList(new ArrayList<>());
|
||||
public static SoundBuffer createBuffer(boolean stereo) throws InterruptedException, ExecutionException, SoundError {
|
||||
if (!PLAYBACK_ENABLED) {
|
||||
System.err.println("Sound playback not enabled, buffer not created.");
|
||||
@ -231,46 +232,7 @@ public class SoundMixer extends Device {
|
||||
return;
|
||||
}
|
||||
if (!currentBuffer.hasRemaining()) {
|
||||
buffersGenerated++;
|
||||
currentBuffer.flip();
|
||||
if (buffersGenerated > 2) {
|
||||
int[] unqueueBuffers = new int[]{currentBufferId};
|
||||
performSoundOperation(()->{
|
||||
int buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED);
|
||||
while (buffersProcessed < 1) {
|
||||
Thread.onSpinWait();
|
||||
buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED);
|
||||
}
|
||||
});
|
||||
if (!isAlive) {
|
||||
return;
|
||||
}
|
||||
performSoundOperation(()->{
|
||||
AL10.alSourceUnqueueBuffers(sourceId, unqueueBuffers);
|
||||
});
|
||||
}
|
||||
if (!isAlive) {
|
||||
return;
|
||||
}
|
||||
performSoundOperation(()->AL10.alBufferData(currentBufferId, audioFormat, currentBuffer, RATE));
|
||||
if (!isAlive) {
|
||||
return;
|
||||
}
|
||||
performSoundOperation(()->AL10.alSourceQueueBuffers(sourceId, currentBufferId));
|
||||
performSoundOperationAsync(()->{
|
||||
if (AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING) {
|
||||
AL10.alSourcePlay(sourceId);
|
||||
}
|
||||
});
|
||||
|
||||
// Swap AL buffers
|
||||
int tempId = currentBufferId;
|
||||
currentBufferId = alternateBufferId;
|
||||
alternateBufferId = tempId;
|
||||
// Swap Java buffers
|
||||
ShortBuffer tempBuffer = currentBuffer;
|
||||
currentBuffer = alternateBuffer;
|
||||
alternateBuffer = tempBuffer;
|
||||
this.flush();
|
||||
}
|
||||
currentBuffer.put(sample);
|
||||
}
|
||||
@ -302,6 +264,53 @@ public class SoundMixer extends Device {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() throws SoundError {
|
||||
buffersGenerated++;
|
||||
currentBuffer.flip();
|
||||
if (buffersGenerated > 2) {
|
||||
int[] unqueueBuffers = new int[]{currentBufferId};
|
||||
performSoundOperation(()->{
|
||||
int buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED);
|
||||
while (buffersProcessed < 1) {
|
||||
Thread.onSpinWait();
|
||||
buffersProcessed = AL10.alGetSourcei(sourceId, AL10.AL_BUFFERS_PROCESSED);
|
||||
}
|
||||
});
|
||||
if (!isAlive) {
|
||||
return;
|
||||
}
|
||||
performSoundOperation(()->{
|
||||
AL10.alSourceUnqueueBuffers(sourceId, unqueueBuffers);
|
||||
});
|
||||
}
|
||||
if (!isAlive) {
|
||||
return;
|
||||
}
|
||||
performSoundOperation(()->AL10.alBufferData(currentBufferId, audioFormat, currentBuffer, RATE));
|
||||
if (!isAlive) {
|
||||
return;
|
||||
}
|
||||
performSoundOperation(()->AL10.alSourceQueueBuffers(sourceId, currentBufferId));
|
||||
performSoundOperationAsync(()->{
|
||||
if (AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) != AL10.AL_PLAYING) {
|
||||
AL10.alSourcePlay(sourceId);
|
||||
}
|
||||
});
|
||||
|
||||
// Swap AL buffers
|
||||
int tempId = currentBufferId;
|
||||
currentBufferId = alternateBufferId;
|
||||
alternateBufferId = tempId;
|
||||
// Swap Java buffers
|
||||
ShortBuffer tempBuffer = currentBuffer;
|
||||
currentBuffer = alternateBuffer;
|
||||
alternateBuffer = tempBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
public int getActiveBuffers() {
|
||||
return buffers.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -313,7 +322,7 @@ public class SoundMixer extends Device {
|
||||
PLAYBACK_ENABLED = false;
|
||||
|
||||
while (!buffers.isEmpty()) {
|
||||
SoundBuffer buffer = buffers.get(0);
|
||||
SoundBuffer buffer = buffers.remove(0);
|
||||
try {
|
||||
buffer.shutdown();
|
||||
} catch (InterruptedException | ExecutionException | SoundError e) {
|
||||
@ -342,7 +351,6 @@ public class SoundMixer extends Device {
|
||||
return "mixer";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void reconfigure() {
|
||||
PLAYBACK_ENABLED = PLAYBACK_DRIVER_DETECTED && !MUTE;
|
||||
|
@ -29,8 +29,11 @@ public class Media {
|
||||
oggFile = oggStream.readAllBytes();
|
||||
}
|
||||
|
||||
ByteBuffer oggBuffer = null;
|
||||
STBVorbisInfo info = null;
|
||||
ShortBuffer tempSampleBuffer = null;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
ByteBuffer oggBuffer = MemoryUtil.memAlloc(oggFile.length);
|
||||
oggBuffer = MemoryUtil.memAlloc(oggFile.length);
|
||||
oggBuffer.put(oggFile);
|
||||
oggBuffer.flip();
|
||||
IntBuffer error = stack.callocInt(1);
|
||||
@ -38,19 +41,28 @@ public class Media {
|
||||
if (decoder == null || decoder <= 0) {
|
||||
throw new RuntimeException("Failed to open Ogg Vorbis file. Error: " + getError(error.get(0)) + " -- file is located at " + resourcePath);
|
||||
}
|
||||
STBVorbisInfo info = STBVorbisInfo.malloc(stack);
|
||||
info = STBVorbisInfo.malloc(stack);
|
||||
STBVorbis.stb_vorbis_get_info(decoder, info);
|
||||
totalSamples = STBVorbis.stb_vorbis_stream_length_in_samples(decoder);
|
||||
totalDuration = STBVorbis.stb_vorbis_stream_length_in_seconds(decoder);
|
||||
sampleRate = info.sample_rate();
|
||||
isStereo = info.channels() == 2;
|
||||
|
||||
sampleBuffer = MemoryUtil.memAllocShort(totalSamples);
|
||||
STBVorbis.stb_vorbis_get_samples_short_interleaved(decoder, isStereo?2:1, sampleBuffer);
|
||||
tempSampleBuffer = MemoryUtil.memAllocShort(totalSamples);
|
||||
STBVorbis.stb_vorbis_get_samples_short_interleaved(decoder, isStereo?2:1, tempSampleBuffer);
|
||||
STBVorbis.stb_vorbis_close(decoder);
|
||||
tempSampleBuffer.rewind();
|
||||
// copy sample buffer into byte buffer so we can deallocate, then transfer the buffer contents
|
||||
sampleBuffer = ShortBuffer.allocate(totalSamples*2);
|
||||
sampleBuffer.put(tempSampleBuffer);
|
||||
sampleBuffer.rewind();
|
||||
} catch (RuntimeException ex) {
|
||||
throw ex;
|
||||
} finally {
|
||||
if (oggBuffer != null)
|
||||
MemoryUtil.memFree(oggBuffer);
|
||||
if (tempSampleBuffer != null)
|
||||
MemoryUtil.memFree(tempSampleBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +116,8 @@ public class Media {
|
||||
}
|
||||
|
||||
public void close() {
|
||||
MemoryUtil.memFree(sampleBuffer);
|
||||
if (sampleBuffer != null)
|
||||
sampleBuffer.clear();
|
||||
if (tempFile != null && tempFile.exists())
|
||||
tempFile.delete();
|
||||
}
|
||||
@ -146,4 +159,12 @@ public class Media {
|
||||
public float getTotalDuration() {
|
||||
return totalDuration;
|
||||
}
|
||||
|
||||
public int getTotalSamples() {
|
||||
return totalSamples;
|
||||
}
|
||||
|
||||
public long getSampleRate() {
|
||||
return sampleRate;
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package jace.core;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.Test;
|
||||
@ -11,19 +13,31 @@ import jace.lawless.LawlessHacks;
|
||||
import jace.lawless.Media;
|
||||
|
||||
public class SoundTest extends AbstractFXTest {
|
||||
// @Test (commented out because it takes a while to run)
|
||||
@Test
|
||||
public void musicDecodeTest() {
|
||||
// For every song in the music folder, decode it and print out the duration
|
||||
// This is to make sure that the decoding is working properly
|
||||
// This is to make sure that the decoding is working properly and that
|
||||
// we don't have allocation/deallocation issues
|
||||
LawlessHacks lawless = new LawlessHacks();
|
||||
for (String score : lawless.scores.keySet()) {
|
||||
lawless.changeMusicScore(score);
|
||||
for (int track : lawless.scores.get(score).keySet()) {
|
||||
System.out.println("Loading score %s, track %d".formatted(score, track));
|
||||
Media m = lawless.getAudioTrack(track);
|
||||
System.out.println("Duration: " + m.getTotalDuration());
|
||||
// Note: This passed 1000 iterations of the test, so it's probably safe to assume there's no obvious memory leaks
|
||||
// for (int repeat = 0; repeat < 1000; repeat++) {
|
||||
for (String score : lawless.scores.keySet()) {
|
||||
lawless.changeMusicScore(score);
|
||||
for (int track : lawless.scores.get(score).keySet()) {
|
||||
System.out.println("Loading score %s, track %d".formatted(score, track));
|
||||
Media m = lawless.getAudioTrack(track);
|
||||
System.out.println("Duration: " + m.getTotalDuration());
|
||||
int count = 0;
|
||||
while (!m.isEnded()) {
|
||||
count++;
|
||||
m.getNextLeftSample();
|
||||
m.getNextRightSample();
|
||||
}
|
||||
assertEquals("Should read an expected number of samples from the song (%s), counted %s".formatted(m.getTotalSamples(), count), m.getTotalSamples(), count);
|
||||
m.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
// @Test
|
||||
@ -52,13 +66,33 @@ public class SoundTest extends AbstractFXTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void speakerTickTest() throws SoundError {
|
||||
// @Test
|
||||
// Commented out because it's annoying to hear all the time, but it worked without issues
|
||||
public void mixerTortureTest() throws SoundError, InterruptedException, ExecutionException {
|
||||
System.out.println("Performing speaker tick test...");
|
||||
SoundMixer.initSound();
|
||||
System.out.println("Create mixer");
|
||||
SoundMixer mixer = new SoundMixer();
|
||||
System.out.println("Attach mixer");
|
||||
mixer.attach();
|
||||
|
||||
// We want to create and destroy lots of buffers to make sure we don't have any memory leaks
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
// Print status every 1000 iterations
|
||||
if (i % 1000 == 0) {
|
||||
System.out.println("Iteration %d".formatted(i));
|
||||
}
|
||||
SoundBuffer buffer = SoundMixer.createBuffer(false);
|
||||
for (int j = 0; j < SoundMixer.BUFFER_SIZE*2; j++) {
|
||||
// Gerate a sin wave with a frequency sweep so we can tell if the buffer is being fully processed
|
||||
double x = Math.sin(j*j * 0.0001);
|
||||
buffer.playSample((short) (Short.MAX_VALUE * x));
|
||||
}
|
||||
buffer.flush();
|
||||
buffer.shutdown();
|
||||
}
|
||||
// Assert buffers are empty
|
||||
assertEquals("All buffers should be empty", 0, mixer.getActiveBuffers());
|
||||
System.out.println("Deactivating sound");
|
||||
mixer.detach();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user