title: Android AudioTrack.onPlaybackPositionUpdateListener not always firing on time
body: <p>I have noticed that <em>AudioTrack.setPlaybackPositionUpdateListener()</em> sometimes does not seem to work as intended (at least in Android Studio emulators from API 19-22).</p>
  
  <p>I've made a little test program that has a button that when pressed, starts feeding an AudioTrack with one-second-long buffers of audio.  </p>
  
  <p>The AudioTrack is supposed to call back using <em>onPeriodicNotification()</em> which in turn flips the background color of the Activity and makes a Log.</p>
  
  <p><strong>Expected behavior:</strong></p>
  
  <p>The notification is sent and received more-or-less exactly every one second after having pressed the start button.</p>
  
  <p><strong>This does happen most of the time, but sometimes (seems to be mostly on API 19-22):</strong></p>
  
  <p>The first (after one-second) notification is missed/postponed, and instead we get two simultaneous notifications happening at two seconds. </p>
  
  <p>Why is this happening?  Is there a better way to get a callback based on the playback head position?</p>
  
  <p>The only reason I can think of is possibly a CPU frequency scaling issue as described around the 19:30 mark of <a href="https://www.youtube.com/watch?v=C0BPXZIvG-Q" rel="noreferrer">this video</a>.</p>
  
  <p>MainActivity:</p>
  
  <pre><code>public class MainActivity extends Activity {
  
      View rootView;
      Button startButton;
      int initialBackgroundColor;
      boolean colorFlipper = false;
      boolean isPlaying = false;
  
      // AUDIO -------------
      AudioTrack audioTrack;
      int sampleRateInHz = 44100;
      int bufferSizeInBytes = 44100;
      byte[] silenceArray;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
  
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          rootView = findViewById(R.id.rootView);
          startButton = findViewById(R.id.startButton);
          initialBackgroundColor = rootView.getDrawingCacheBackgroundColor();
  
          // AUDIO -------------
          audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, AudioFormat.CHANNEL_OUT_MONO,
                  AudioFormat.ENCODING_PCM_16BIT, bufferSizeInBytes, AudioTrack.MODE_STREAM);
          audioTrack.setPositionNotificationPeriod(44100); // this amounts to one second
  
  
          // create "dummy" (silent) sound... one second long
          silenceArray = new byte[bufferSizeInBytes];
          for (int i = 0; i &lt; (bufferSizeInBytes-1); i++)
              silenceArray[i] = 0;
  
      }
  
      public void startPressed(View v) {
          boolean wasPlayingWhenPressed = startButton.isSelected();
          startButton.setSelected(!startButton.isSelected());
          if (wasPlayingWhenPressed) {
              stop();
              startButton.setText("START");
          } else {
              start();
              startButton.setText("STOP");
          }
      }
  
      private void start() {
          isPlaying = true;
          audioTrack.reloadStaticData();
          audioTrack.play();
  
          Runnable r = new Runnable() {
              public void run() {
  
                  audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener(){
                      @Override
                      public void onMarkerReached(AudioTrack arg0) {
                      }
                      @Override
                      public void onPeriodicNotification(AudioTrack arg0) {
                          // this *should* be called every second after play is pressed,
                          // but sometimes the first call is postponed and comes out
                          // simultaneously with the second call
                          Log.i("XXX","onPeriodicNotification() was called");
                          flipBackgroundColor();
                      }
                  });
  
                  while(isPlaying) {
                      audioTrack.write(silenceArray,0,silenceArray.length);
                  }
              }
          };
  
          Thread backround_thread = new Thread(r);
          backround_thread.start();
      }
  
      private void stop() {
  
          isPlaying = false;
          audioTrack.stop();
  
      }
  
      private void flipBackgroundColor(){
  
          colorFlipper = !colorFlipper;
          if (colorFlipper) {
              rootView.setBackgroundColor(Color.RED);
          } else {
              rootView.setBackgroundColor(initialBackgroundColor);
          }
  
      }
  
  }
  </code></pre>
  
  <p>activity_main.xml:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      android:id="@+id/rootView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      tools:context=".MainActivity"&gt;
  
      &lt;Button
          android:id="@+id/startButton"
          android:onClick="startPressed"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_marginBottom="8dp"
          android:layout_marginEnd="8dp"
          android:layout_marginStart="8dp"
          android:layout_marginTop="8dp"
          android:text="start"
          app:layout_constraintBottom_toBottomOf="parent"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent" /&gt;
  
  &lt;/android.support.constraint.ConstraintLayout&gt;
  </code></pre>
  
  <p>build.gradle:</p>
  
  <pre><code>apply plugin: 'com.android.application'
  
  android {
      compileSdkVersion 27
      defaultConfig {
          applicationId "com.example.boober.stackqaudiotrackcallback"
          minSdkVersion 19
          targetSdkVersion 27
          versionCode 1
          versionName "1.0"
          testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
      }
      buildTypes {
          release {
              minifyEnabled false
              proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
          }
      }
  }
  
  dependencies {
      implementation fileTree(dir: 'libs', include: ['*.jar'])
      implementation 'com.android.support.constraint:constraint-layout:1.1.0'
      testImplementation 'junit:junit:4.12'
      androidTestImplementation 'com.android.support.test:runner:1.0.2'
      androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
  }
  </code></pre>