Play a sound every N milliseconds

前端 未结 3 1410
北荒
北荒 2021-01-04 09:19

I\'m developing a metronome application. The user can select at runtime the bpm, and my app will play the \"tick\" sound accordingly. The \"tick\" is a single metronome \"sh

相关标签:
3条回答
  • 2021-01-04 09:32

    The alternative of looping through a synthesized sound seems to be the best choice for now. There was a great session about audio on Google I/O 2013 called High Performance Audio that I would certainly advice watching for having a deeper understanding on how the system works and what problems the developers will face when dealing with the audio latency. At about the 17:00 of the video, there is graph that shows jitter versus callbacks. In the perfect world that does not exist (oh really?), the jitter would be zero for all the scheduled audio callbacks made. But that is not the case, for there are jitters as high as 35 milliseconds or even greater, for the data in the graph was made using an unspecified ICS device and there are certainly worse scenarios than that.

    So, as a metronome is a precision tool and these jitters are not good at all, the scheduled playback approach should be left aside. I even made an reasonably realiable metronome work with a synthesized sound using AudioTrack.

    Hope it helps ^^

    0 讨论(0)
  • 2021-01-04 09:40

    You could try to use a TimerTask scheduled for fixed-rate execution on a Timer.

    Timer and TimerTask are both part of the Android SDK (and Java SE). The executions do not delay because of execution time of the previous event.

    Timer timer = new Timer("MetronomeTimer", true);
    TimerTask tone = new TimerTask(){
         @Override
         public void run(){
             //Play sound
         }
    };
    timer.scheduleAtFixedRate(tone, 500, 500); //120 BPM. Executes every 500 ms.
    

    You can then cancel the TimerTask when you need to change the BPM.

    tone.cancel();
    tone = new TimerTask(){...}
    timer.scheduleAtFixedRate(tone, 1000, 1000); //60 BPM. Executes every 1000 ms.
    

    Another possibility that may meet your requirements (from your comments) is spinning a thread and checking System.nanoTime() and sleeping in increments but spinning when you get close to wake up.

    long delayNanos = 500000000;
    long wakeup = System.nanoTime() + delayNanos; //Half second from right now
    long now;
    
    while(!done){
         now = System.nanoTime();
    
         //If we are less than 50 milliseconds from wake up. Spin away. 
         if(now <= wakeup - 50000000){
              //Sleep in very small increments, so we don't spin unrestricted.
    
              Thread.sleep(10); 
         }
         if(now >= wakeup){
               //Play sound
               wakeup += delayNanos;
         }
    }
    
    0 讨论(0)
  • 2021-01-04 09:56

    When this play sound is called

    mSoundManager.playSound(1);
    

    Android waits until that call is finished, then you call

    mHandler.postAtTime(this, SystemClock.uptimeMillis() + 200);
    

    however, if you reverse those calls, you may find that the timing is more accurate.

    mHandler.postAtTime(this, SystemClock.uptimeMillis() + 200);
    mSoundManager.playSound(1);
    

    You can't count on your sound taking exactly the same amount of time to play, so telling the handler to post first is a bit better. Still not ideal, however.

    0 讨论(0)
提交回复
热议问题