VxMusic

Back to Log
Engineering18 min read

Optimizing Android Audio Latency: A Deep Dive

December 5, 2024

Android has historically lagged behind iOS in audio latency. While iOS devices consistently achieved sub-15ms round-trip latency, Android devices varied wildly, sometimes reaching 100ms+. For a rhythm game or a DJ app, this is unusable. But with the introduction of AAudio and Oboe, the gap has closed. Here is the engineering story of how we hit the 10ms target on Pixel devices.

The Villain: The Java Heap

The primary enemy of real-time audio is unpredictability. In a managed language like Java or Kotlin, the Garbage Collector (GC) can pause execution at any moment to clean up memory.

If the GC pauses your audio thread for even 5 milliseconds, the audio buffer runs dry. The user hears a "pop" or "glitch." This is called an underrun (XRUN).

The Solution: Do not use Java for the audio callback.

VxMusic uses C++ (JNI) for the critical audio path. We allocate all memory buffers at app startup. During playback, we never use `new`, `malloc`, or create objects. We simply read from a pre-allocated circular buffer and write to the audio output.

The Audio Flinger & Fast Path

The Android Audio Flinger is the system service responsible for mixing audio from all apps.

  • Normal Path: App writes to buffer → Audio Flinger mixes it → HAL → Hardware. This adds latency.
  • Fast Path: If your app requests the *exact* native sample rate and buffer size of the device, Audio Flinger can give you a "Direct Track."

To hit the Fast Path, we must query the device properties at runtime:

val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager val sampleRate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE) val framesPerBuffer = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)

AAudio vs OpenSL ES

For years, OpenSL ES was the standard C API for audio. It was verbose, difficult to debug, and prone to device-specific quirks. Google introduced AAudio in Android O (8.0) as a lightweight replacement.

VxMusic uses the Oboe library (by Google), which is a C++ wrapper. Oboe is brilliant: it uses AAudio on newer devices (Android 8.1+) and falls back to OpenSL ES on older ones, providing a unified API.

Thread Scheduling

Even with C++, other processes can steal CPU time. To prevent this, we boost the priority of our audio thread.

// Setting thread affinity and priority in C++ pid_t tid = gettid(); setpriority(PRIO_PROCESS, tid, -19); // Highest possible priority

Note: Android grants this privilege only if the app has the FOREGROUND_SERVICE permission and is currently visible or playing audio.

Results

After implementing these optimizations, our round-trip latency on a Pixel 7 dropped from 45ms (using standard Java MediaPlayer) to 8ms (using Oboe + Exclusive Mode). This makes real-time EQ and spatializer effects feel instantaneous.