TinySound icon indicating copy to clipboard operation
TinySound copied to clipboard

High Performance-Usage of the Update-Runner-Thread on Embedded-Systems

Open meschie opened this issue 12 years ago • 1 comments

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

meschie avatar Sep 24 '12 19:09 meschie

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.

finnkuusisto avatar Sep 30 '12 14:09 finnkuusisto