TinySound
TinySound copied to clipboard
High Performance-Usage of the Update-Runner-Thread on Embedded-Systems
On some embedded-systems there is a load of aproximatly 30-40% producted by the Thread.sleep(1) in this class.
The Issue presents itself in case of starting the SoundEngine on Application-Startup, while shuting it down at the end.
The Problem here is that many embedded-cpu's (e.g. AMD G-T56N, Intel Atom D525) don't have that much throught, so the sleep is not called that often.
The following Patch is kind of a solution for me, because starting the sound without latency is not a problem for me. While playback the sleep must not be bigger than 15ms, but while waiting for something to do, we can sleep a quater second. This reduces CPU-load to about 4%.
Test-Systems:
- Ubuntu 12.04 LTS 3.2.0-29-generic-pae AMD G-T56N 2GB RAM
- Ubuntu 10.04 LTS 2.6.32-40-generic Intel Atom D525 2GB RAM
- Environment: JAVA-AWT-Application with sound on Button-Interaction
On a Ubuntu 10.04 LTS 2.6.32-43-generic with Intel Core2 Quad Q6600 4GB RAM the Problem doesn't occur.
The Problem exists with JAVA-Versions:
- OpenJDK Runtime Environment (IcedTea6 1.9.13)
- OpenJDK Runtime Environment (IcedTea7 2.3.2)
- Java(TM) SE Runtime Environment (build 1.6.0_32-b05)
Index: src/kuusisto/tinysound/TinySound.java
===================================================================
--- src/kuusisto/tinysound/TinySound.java (revision 409)
+++ src/kuusisto/tinysound/TinySound.java (working copy)
@@ -145,6 +145,9 @@
} catch (Exception e) {}
TinySound.inited = true;
updateThread.start();
+
+ mixer.setUpdateRunner(updateThread);
+
//yield to potentially give the updater a chance
Thread.yield();
}
Index: src/kuusisto/tinysound/internal/Mixer.java
===================================================================
--- src/kuusisto/tinysound/internal/Mixer.java (revision 409)
+++ src/kuusisto/tinysound/internal/Mixer.java (working copy)
@@ -40,7 +40,12 @@
private List<MusicReference> musics;
private List<SoundReference> sounds;
private int[] dataBuf; //buffer for reading sound data
+ private Thread updateRunner;
+ public void setUpdateRunner(Thread pUpdateRunner) {
+ this.updateRunner = pUpdateRunner;
+ }
+
/**
* Construct a new Mixer for TinySound system.
*/
@@ -55,6 +60,7 @@
* @param music MusicReference to be registered
*/
public synchronized void registerMusicReference(MusicReference music) {
+ this.updateRunner.interrupt();
this.musics.add(music);
}
@@ -63,6 +69,7 @@
* @param sound SoundReference to be registered
*/
public synchronized void registerSoundReference(SoundReference sound) {
+ this.updateRunner.interrupt();
this.sounds.add(sound);
}
@@ -71,6 +78,7 @@
* @param music MusicReference to be unregistered
*/
public synchronized void unRegisterMusicReference(MusicReference music) {
+ this.updateRunner.interrupt();
this.musics.remove(music);
}
Index: src/kuusisto/tinysound/internal/UpdateRunner.java
===================================================================
--- src/kuusisto/tinysound/internal/UpdateRunner.java (revision 409)
+++ src/kuusisto/tinysound/internal/UpdateRunner.java (working copy)
@@ -71,62 +71,88 @@
int bufSize = (int)TinySound.FORMAT.getFrameRate() *
TinySound.FORMAT.getFrameSize();
byte[] audioBuffer = new byte[bufSize];
+
+ //init fallback buffer
+ byte[] audioBuffer2 = new byte[bufSize];
+ for (int i=0; i<audioBuffer2.length; i++) {
+ audioBuffer2[i] = 0;
+ }
+
//only buffer some maximum number of frames each update (25ms)
int maxFramesPerUpdate =
(int)((TinySound.FORMAT.getFrameRate() / 1000) * 25);
int numBytesRead = 0;
double framesAccrued = 0;
long lastUpdate = System.nanoTime();
+ int slowDownCounter = 0;
//keep running until told to stop
while (this.running.get()) {
- //check the time
- long currTime = System.nanoTime();
- //accrue frames
- double delta = currTime - lastUpdate;
- double secDelta = (delta / 1000000000L);
- framesAccrued += secDelta * TinySound.FORMAT.getFrameRate();
- //read frames if needed
- int framesToRead = (int)framesAccrued;
- int framesToSkip = 0;
- //check if we need to skip frames to catch up
- if (framesToRead > maxFramesPerUpdate) {
- framesToSkip = framesToRead - maxFramesPerUpdate;
- framesToRead = maxFramesPerUpdate;
- }
- //skip frames
- if (framesToSkip > 0) {
- int bytesToSkip = framesToSkip *
- TinySound.FORMAT.getFrameSize();
- this.mixer.skip(bytesToSkip);
- }
- //read frames
- if (framesToRead > 0) {
- //read from the mixer
- int bytesToRead = framesToRead *
- TinySound.FORMAT.getFrameSize();
- int tmpBytesRead = this.mixer.read(audioBuffer,
- numBytesRead, bytesToRead);
- numBytesRead += tmpBytesRead; //mark how many read
- //fill rest with zeroes
- int remaining = bytesToRead - tmpBytesRead;
- for (int i = 0; i < remaining; i++) {
- audioBuffer[numBytesRead + i] = 0;
+ try {
+ //check the time
+ long currTime = System.nanoTime();
+ //accrue frames
+ double delta = currTime - lastUpdate;
+ double secDelta = (delta / 1000000000L);
+ framesAccrued += secDelta * TinySound.FORMAT.getFrameRate();
+ //read frames if needed
+ int framesToRead = (int)framesAccrued;
+ int framesToSkip = 0;
+ //check if we need to skip frames to catch up
+ if (framesToRead > maxFramesPerUpdate) {
+ framesToSkip = framesToRead - maxFramesPerUpdate;
+ framesToRead = maxFramesPerUpdate;
}
- numBytesRead += remaining; //mark zeroes read
- }
- //mark frames read and skipped
- framesAccrued -= (framesToRead + framesToSkip);
- //write to speakers
- if (numBytesRead > 0) {
- this.outLine.write(audioBuffer, 0, numBytesRead);
- numBytesRead = 0;
+ //skip frames
+ if (framesToSkip > 0) {
+ int bytesToSkip = framesToSkip *
+ TinySound.FORMAT.getFrameSize();
+ this.mixer.skip(bytesToSkip);
+ }
+ //read frames
+ if (framesToRead > 0) {
+ //read from the mixer
+ int bytesToRead = framesToRead *
+ TinySound.FORMAT.getFrameSize();
+ int tmpBytesRead = this.mixer.read(audioBuffer,
+ numBytesRead, bytesToRead);
+ numBytesRead += tmpBytesRead; //mark how many read
+
+ if (tmpBytesRead>0) {
+ slowDownCounter=0;
+ }
+
+ //fill rest with zeroes
+ int remaining = bytesToRead - tmpBytesRead;
+ //this method is faster...
+ System.arraycopy(audioBuffer2, 0, audioBuffer, numBytesRead, remaining);
+
+// for (int i = 0; i < remaining; i++) {
+// audioBuffer[numBytesRead + i] = 0;
+// }
+ numBytesRead += remaining; //mark zeroes read
+ }
+ //mark frames read and skipped
+ framesAccrued -= (framesToRead + framesToSkip);
+ //write to speakers
+ if (numBytesRead > 0) {
+ this.outLine.write(audioBuffer, 0, numBytesRead);
+ numBytesRead = 0;
+ }
+ //mark last update
+ lastUpdate = currTime;
+ slowDownCounter++;
+ //give the CPU back to the OS for a bit
+
+ if (slowDownCounter > 1000) {
+ slowDownCounter = 1001;
+ Thread.sleep(1000);
+ } else {
+ Thread.sleep(5);
+ }
+ } catch (InterruptedException e) {
+ //ignore this exception because in case of interrupt, we woke the thread
+ slowDownCounter=0;
}
- //mark last update
- lastUpdate = currTime;
- //give the CPU back to the OS for a bit
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {}
}
}
I'm reluctant to include a change that could introduce so much latency to the start of playback. I appreciate the suggestion and will look try to come up with a solution.