[Tech Notes Home](README.md)

# Assert in releaseBuffer()

There is a bug that can sometimes cause an assert in ClientProxy or AudioTrackShared::releaseBuffer() when headsets are connected or disconnected. 
The bug was originally reported at: https://github.com/google/oboe/issues/535

You will see signatures like this in the logcat:

    F AudioTrackShared: releaseBuffer: mUnreleased out of range, !(stepCount:96 <= mUnreleased:0 <= mFrameCount:480), BufferSizeInFrames:480

## Platforms Affected

Android version 10 (Q) or earlier.

Oboe version 1.4.0 or earlier when using OpenSLES with an OUTPUT stream callback.

OR any version of Oboe if:
* Oboe is using using OpenSL ES or a non-MMAP Legacy AAudio stream
* AND you call stream->getFramesRead() or stream->getTimestamp(...) from inside
an OUTPUT stream callback,

It does **not** happen when Oboe uses AAudio MMAP because it does not call releaseBuffer().

## Workarounds

1. Use Oboe 1.4.1 or later.
1. Do not call stream->getFramesRead() or stream->getTimestamp() from inside the callback of an OUTPUT stream. If you absolutely must, then call them at the beginning of your callback to reduce the probability of a crash.

Here is a [fix in Oboe 1.4.1](https://github.com/google/oboe/pull/863) that removed a call to getPosition().

## Root Cause

The sequence of events is:
1. AudioFlinger AudioTrack obtains a buffer from the audio device
1. user plugs in headphones, which invalidates the audio device
1. app is called (callback) to render audio using the buffer
1. the app or Oboe calls getFramesRead() or getTimestamp(), which calls down to AudioTrack::getPosition() or AudioTrack::getTimestamp()
1. device routing change occurs because the audio device is [invalid](https://cs.android.com/android/platform/superproject/+/primary:frameworks/av/media/libaudioclient/AudioTrack.cpp;l=1239;drc=48e98cf8dbd9fa212a0e129822929dc40e6c3898)
1. callback ends by releasing the buffer back to a different device
1. AudioTrackShared::releaseBuffer() checks to make sure the device matches the one in ObtainBuffer() and asserts if they do not match.

Oboe, before V1.4.1, would update the server position in its callback. This called getPosition() in OpenSL ES, which called AudioTrack::getPosition().

The probability of the assert() is proportional to the time that the CPU spends between obtaining a buffer and calling restoreTrack_l().

This bug is tracked internally at: b/136268149

## Reproduce the Bug

These steps will trigger the bug most of the time:

1. Install OboeTester 1.5.22 or later, with Oboe < 1.4.1
1. Enter in a Terminal window: adb logcat | grep releaseBuffer
1. Launch OboeTester
1. Click TEST OUTPUT
1. Select API: OpenSL ES
1. Click OPEN
1. Click START, you should hear a tone
1. Slide "Workload" fader slowly up until you hear bad glitches.
1. Plug in headphones.
 
You may see a message like this in the logcat:

    AudioTrackShared: releaseBuffer: mUnreleased out of range, !(stepCount:96 <= mUnreleased:0 <= mFrameCount:480), BufferSizeInFrames:480

# OEM Information

These patches are available in Q AOSP:
1. [AudioTrack](https://android-review.googlesource.com/c/platform/frameworks/av/+/1251871/)
1. [AudioRecord](https://android-review.googlesource.com/c/platform/frameworks/av/+/1251872/)
