2

I am using oboe library to make a music app. There I produce music by writing PCM float values to the given pointer. I rarely have underruns which I can hearwhich. I also verify this with the following oboe APIs:

managedStream->getXRunCount();

The docs says the following:

 * An XRun is an Underrun or an Overrun.
 * During playing, an underrun will occur if the stream is not written in time
 * and the system runs out of valid data.
 * An underrun or overrun can cause an audible "pop" or "glitch".

I am trying to debug the issue. I found this awesome tutorial by Don. I captured a 10 seconds of systrace with the following, note that during this 10 seconds I heard 0 audable underrun pop/click sounds:

systrace.py --time=10 -o trace.html -a com.example.app audio sched freq

The result is the following by aaRdy call (blue box height shows how many sample are in the buffer, more height more sample which is wanted): enter image description here This is just a small portion of the aaRdy call. There is never a time that my buffer has 0 sample is it. I checked all of it. In the article Don written, he says the following:

But then the buffer starts empty, first dropping to 96 frames, then….oh dear…to zero! At zero we are guaranteed to have an audio glitch because there’s no data in the buffer.

Here is a screenshot from the article: enter image description here

My question looking at the article, I can gurantee that if the blue box is empty then there is an underrun. However, in my systrace it is never empty and I heard no underrun pop clicks BUT managedStream->getXRunCount();call actually returned 12. I am not sure if there is an underrun or not.

Does the decrease in blue boxes means that there is an underrun even when it's not 0?

cs guy
  • 926
  • 2
  • 13
  • 33
  • I don't know anything about audio processing but you can see how the value of the `aaRdy` systrace counter is measured in Android source code: https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libaaudio/src/client/AudioStreamInternal.cpp?q=aardy. Essentially, it's the number of full frames in the data buffer queue. When it's 0, it probably means the buffer is full. – Yi Yang Apr 14 '21 at 22:59
  • I think you meant to say 'when it's 0, the buffer is empty' – cs guy Apr 14 '21 at 23:04
  • It's the number of full frames available (`mAudioEndpoint->getFullFramesAvailable()`), meaning buffer frames available for writing. When it's 0, the buffer is completely occupied and something has to be dropped. – Yi Yang Apr 16 '21 at 03:27

2 Answers2

3

Don here :)

what does it mean to have a non-full buffer in the trace.

It means that at that moment in time the audio hardware is reading (and removing) data from the buffer faster than your app is writing it.

This is a fairly normal situation if your app uses significant CPU to generate audio data. Maybe some other process on the system was prioritized over the audio thread and your app couldn't generate the required number of audio frames fast enough.

This only becomes a problem if the slow writing situation lasts long enough for the buffer to be completely emptied, and the audio device reads from the buffer while empty. In which case you'll get an underrun and the XRuns counter will be incremented.

As you rightly state, the XRuns counter will record the number of underruns (or overruns for a recording stream) for the lifetime of the stream.

donturner
  • 17,867
  • 8
  • 59
  • 81
  • Thank your for the explanation. BTW I really appreciate oboe. It's fantastic and makes working with low level audio so much easier than poorly documented low level native IOS Audio Frameworks. Oboe is very well documented and it's been very fun to use oboe so far. Keep up the amazing work :) – cs guy Jun 04 '21 at 14:27
  • 2
    Thanks for your kind words, really pleased you're finding it useful. – donturner Jun 04 '21 at 16:33
  • @donturner please answer https://stackoverflow.com/questions/70053232/adding-effects-to-live-audio – Abhinav Chauhan Nov 21 '21 at 13:29
0

I dived deep and found out that

managedStream->getXRunCount();

Returns the number of XRuns for the streams whole lifetime. I thought it would return the value relative to previous call. So apparently I was getting 12 but there were no underrun because this was from the session which had previous underrun.

However, I would love to see an explanation of what does it mean to have a non-full buffer in the trace.

cs guy
  • 926
  • 2
  • 13
  • 33