Saturday, November 7, 2026
HomeAndroid APIs & Camera/Audio HookingAndroid Audio Framework: How Audio HAL Interacts with Kernel

Android Audio Framework: How Audio HAL Interacts with Kernel

Android Audio Framework: How Audio HAL Interacts with Kernel

Press play on your phone and sound reaches your ear in a few dozen milliseconds. Between the tap and the speaker sit five layers of software, each with one job, and each able to break the whole chain on its own. I spent a week tracing that path on a Pixel running Android 14, watching logcat while music played, calls rang, and headphones connected, and the layer that surprised me most was the one in the middle: the hardware abstraction layer for audio.

This guide walks the full path from an app’s AudioTrack call down to the ALSA driver that programs the codec, explains what each layer owns, and shows how to find the layer that failed when sound stops. If you build Android apps, maintain a custom ROM, or just want to know why your phone sounds different from a friend’s identical model, this is the map. You will see real service names, real file paths, real sample rates, and a troubleshooting method that narrows a no-sound bug to one layer in minutes.

How Android audio layers stack up from app to speaker

Android audio moves through layers that each handle one part of the job. An app uses an audio API to play or record sound. The framework manages streams, routing, and mixing. The HAL translates framework requests into hardware-specific operations. The kernel driver talks to the audio hardware itself. A sound that reaches a speaker passes through every layer in order, and recording follows the same layers in reverse.

Each layer hides detail from the layer above it. An app does not need to know which codec chip a phone uses. The framework does not need to know the register layout of that chip. The HAL exists to absorb hardware differences so that the framework can stay consistent across devices. That separation is the core idea behind the hardware abstraction layer model, and it is why one music app can run on a thousand different phones without a single hardware-specific line of code.

  • App layer: AudioTrack, MediaPlayer, AudioRecord, and Jetpack Media3 give apps playback, recording, and focus APIs.
  • Framework layer: AudioFlinger mixes streams while AudioPolicyService decides routing, volume, and focus.
  • HAL layer: the vendor audio HAL opens streams, reports devices, and applies hardware volume and routing.
  • Kernel layer: ALSA drivers move PCM samples between memory and the codec on a strict schedule.
  • Hardware layer: the codec converts digital samples to analog signals and drives the speaker or headphones.

When something goes wrong, the layering is your best diagnostic tool. A bug in one layer leaves the layers above it looking healthy, so you can test each boundary in turn instead of guessing. The rest of this guide works through those boundaries one by one.

AudioFlinger and AudioPolicyService: the framework services that mix and route sound

The audio framework is built from services that run in the audioserver system process. Apps never touch hardware directly. They reach these services through binder calls, and the services enforce policy, permissions, and mixing before any sample moves toward the HAL. Two services do most of the work: AudioFlinger handles the actual streams, and AudioPolicyService decides where those streams go.

What AudioFlinger does with every stream

AudioFlinger is the mixer. When Spotify, a navigation prompt, and a notification sound play at once, AudioFlinger takes each stream, resamples it to a common rate, applies the effects chain, mixes the results into one output buffer, and writes that buffer to the HAL output stream. It also owns the fast mixer path used for low-latency streams, which skips some processing stages to shave milliseconds off the round trip. On a typical phone the mixer runs at 48 kHz, because most Android audio paths settle on 48,000 samples per second as the common rate, resampling 44.1 kHz music on the way in.

AudioFlinger also manages audio effects that live above the HAL, such as the system equalizer, bass boost, virtualizer, and loudness enhancer. Those effects sit on the mixed output, so they colour every app equally. A vendor effect can sit lower down instead, inside the HAL path, where it affects only one hardware output. The location of an effect changes which apps hear it, and that detail matters when you debug a report that one app sounds different from another on the same phone.

How AudioPolicyService picks the output device

AudioPolicyService answers one question for every stream: which device should play this? It watches device connection state, reads the audio policy configuration, and applies rules for forced usage, such as routing a voice call to the earpiece or a navigation prompt to the car speaker. It also manages volume per stream type and per device, which is why media volume on your speaker and media volume on your Bluetooth headphones are remembered separately. Audio attributes carry the purpose of a stream, like USAGE_MEDIA, USAGE_VOICE_COMMUNICATION, or USAGE_NOTIFICATION, and those attributes steer routing, volume, and focus behaviour without the app knowing the details.

Reading audio_policy_configuration.xml on a device

If you want to see the routing rules for a real phone, pull the policy file with adb: adb pull /vendor/etc/audio_policy_configuration.xml. Inside it you will find output profiles listing supported sample rates, channel masks, and formats per device, plus routing rules for attached devices. Comparing that file between a working phone and a broken one often shows a missing profile or a wrong rate list faster than any amount of log reading.

Audio focus and ducking between competing apps

Audio focus is the framework’s way of stopping apps from shouting over each other. A music app holds focus while it plays. When navigation needs to speak, it requests transient focus, and the music app either pauses or ducks its volume. The focus rules live in the framework, not in the HAL, so a HAL bug cannot break focus behaviour, and a focus bug cannot be fixed by touching the HAL. That clean split is worth remembering: policy problems live above the HAL, hardware problems live below it.

What the Audio HAL does for every Android device

The HAL is an interface between the framework and the vendor audio implementation. It defines functions for opening streams, setting volume, and routing audio to a device. A vendor implements that interface for a specific chipset and a specific board design. The framework calls the interface, and the vendor code performs the hardware-specific work behind it. On a Qualcomm phone that vendor code knows the WCD codec and the Hexagon DSP path; on a MediaTek phone it knows a different codec and a different DSP path. The framework never learns either set of details.

A HAL implementation runs as its own process in modern Android designs. That separation improves stability because a HAL crash does not necessarily crash the framework, and it improves security because the HAL process can run with limited privileges. The framework and the HAL communicate through a defined interface, not through shared internal state, so either side can be updated or restarted without the other side needing to know its internals. If you are coming from the wider HAL family, the overview in understanding the hardware abstraction layer architecture places this audio layer inside the full set of HAL modules, and the same separation rules apply to camera, sensors, and the rest.

The HAL device list and why a missing device means a HAL problem

The HAL reports which devices exist: built-in speaker, earpiece, wired headset, USB audio, Bluetooth output. The framework can only route to devices the HAL reports, so a headset that never appears in the device list is a HAL or driver problem, not an app problem. When I debug a missing-device report, the first thing I check is the HAL device list in a dumpsys audio output, because it tells me in one line whether the framework ever learned the device existed. A device that appears in the list but produces no sound points one layer lower, at the driver or the codec.

Hardware volume, routing, and stream parameters the HAL owns

The HAL owns everything that depends on the specific hardware: the sample rates and channel layouts each path supports, the formats each path accepts, hardware volume steps, and the act of switching a path on or off. It does not decide app-level policy. Policy stays one layer above it. This split explains a common confusion: two phones can run the same app and the same framework version yet sound different, because their HAL and driver implementations differ by vendor and by board design. A developer should test audio on target devices rather than assuming one device result covers all devices.

HIDL vs AIDL audio HAL: how the interface changed across Android versions

Android 8 introduced the HIDL interface for the HAL, versioned as android.hardware.audio, with the 7.x series covering Android 11 and 12. Android 13 replaced it with an AIDL interface, android.hardware.audio.core, and Android 14 and 15 kept building on that AIDL base. The change matters for developers and ROM builders because the config files, the service names, and the debugging commands all moved. A device on Android 12 speaks HIDL; the same model updated to Android 13 speaks AIDL, and the framework code that calls it changed with it.

AIDL brings stricter versioning and lets audio HAL updates ship through Mainline modules, so a HAL fix can reach phones without a full system update. It also standardises the configuration in XML files under /vendor/etc, which makes the routing rules easier to read and compare. That said, the job did not change: the HAL still opens streams, reports devices, and hands buffers to the kernel. Only the language of the interface changed, not the physics of the audio path.

AIDL audio HAL on Android 13, 14, and 15

On AIDL devices the audio HAL service runs as android.hardware.audio.service, and its configuration lives in files like audio_policy_configuration.xml and the AIDL config XMLs under /vendor/etc. The framework talks to it through the AIDL IModule interface, which groups streams into modules per device. When you read a logcat from a Pixel 7 or 8, you will see AIDL interface names where an older phone showed HIDL names, and the stream open calls carry the same parameters: sample rate, channel mask, format, and flags for low latency or deep buffer.

Where AIDL HAL config lives on the device

On a rooted test phone or an emulator image, look in /vendor/etc for audio_policy_configuration.xml and the audio HAL config files, and in /system/etc for the framework-side policy. The module configuration names the HAL module, its mix ports, and its device ports. If a device port is missing from that file, the framework cannot route to it, no matter how healthy the driver is. I have seen exactly that failure on a custom ROM: the codec driver loaded fine, but a typo in the config file hid the speaker from the framework.

Legacy HIDL audio HAL on older devices

HIDL devices still matter because millions of phones run Android 11 and 12, and custom ROMs keep HIDL support alive for older hardware. On those phones the HAL service is [email protected], and the interface is defined in .hal files rather than .aidl files. The debugging approach is the same, but the service name and the log tags differ, so check the Android version before you start hunting through logs.

Why HIDL devices still matter for custom ROM builders

ROM builders who port Android to older hardware spend most of their audio debugging time in the HIDL HAL, because the vendor HAL for those chipsets was written against HIDL and cannot be rebuilt against AIDL without vendor source. Treble was designed to let the framework update while the vendor HAL stays put, and that is exactly the situation these devices live in: a new framework talking to an old HIDL HAL through a compatibility layer. Knowing which interface your device uses saves hours of reading the wrong documentation.

How the Audio HAL talks to the kernel driver through ALSA and tinyalsa

Below the HAL sits the kernel driver for the audio hardware. The driver controls the codec, the digital audio interfaces, and the clocks that move audio samples. The HAL implementation calls into the driver through system interfaces that the kernel exposes, and the driver then programs the hardware registers that start or stop an audio stream. On Android that bridge is almost always ALSA, the Advanced Linux Sound Architecture, reached through a small user-space library called tinyalsa.

tinyalsa gives the HAL a handful of simple functions: pcm_open to open a PCM device, pcm_write to send samples, pcm_read to receive them, and pcm_close to release the path. Under those calls sit ALSA ioctls against device nodes like /dev/snd/pcmC0D0p, the playback device for card 0, device 0. The kernel driver behind that node programs the codec over I2C or SPI, sets up the I2S or SoundWire link, starts DMA to move samples from memory to the codec, and raises interrupts as each period of samples completes. The HAL never touches a register directly; it asks the driver, and the driver does the hardware work.

tinyalsa: the small library between the HAL and the kernel

tinyalsa exists because the full ALSA user-space library is too large and too general for Android’s needs. It covers the operations the HAL actually uses and nothing else, which keeps the vendor image smaller and the audio path easier to audit. When you see a HAL log line about pcm_open failing with an error code, the failure is at this boundary: the HAL asked for a device, a rate, or a format the driver refused. Common refusals are a rate the codec does not support, such as 192 kHz on a path capped at 48 kHz, or a device node that is already open because another path grabbed it first.

The codec, the DAI, and the platform driver in the ALSA SoC model

Most Android phones use the ALSA SoC model, which splits the driver into three parts. The codec driver knows the DAC, the ADC, the amplifiers, and the mixers inside the codec chip. The platform driver knows the CPU side: the DMA engine and the digital audio interface. The DAI link, or digital audio interface link, connects the two, describing how samples flow from the CPU to the codec over I2S, TDM, or SoundWire. Machine drivers tie a specific codec to a specific platform for one phone model. When a ROM boots with no sound at all, the machine driver is a prime suspect, because a wrong DAI link means the CPU and the codec never agree on the clock or the data format.

Buffers, interrupts, and why underruns sound like glitches

Audio hardware needs a steady flow of samples without gaps. The driver manages a ring buffer split into periods, and the DMA engine feeds the codec from that buffer while the HAL refills it from the other side. If the HAL refills too slowly, the DMA engine runs dry and the codec repeats or drops samples: a buffer underrun, which you hear as a click, a pop, or a short gap. If the app or the HAL reads too slowly on the recording side, new samples overwrite unread ones: an overrun, which loses audio. Both problems sound like glitches even when the app and the framework behaved correctly, which is why glitch reports need a look at buffer sizes, CPU load, and interrupt latency rather than at the app.

Checking the ALSA device list with tinymix and tinyplay

On a rooted phone or an engineering build, tinymix lists the codec controls the driver exposes, and tinyplay can push a WAV file straight to a PCM device, bypassing the framework and the HAL entirely. That bypass is a powerful diagnostic: if tinyplay produces sound, the driver and codec work, and the fault sits above them. If tinyplay is silent, the fault sits in the driver, the codec, or the hardware, and no amount of framework debugging will fix it. Keep this test in your pocket; it settles the HAL-versus-driver question in seconds.

Android audio routing table: every path from app to hardware

Different outputs take different paths through the same layers, and the path decides the sample rate, the codec, the latency, and the failure modes. This table maps the common paths on a modern Android phone, from the framework stream down to the kernel device that carries the samples.

Output path Framework stream HAL device Kernel device Typical use
Built-in speaker STREAM_MUSIC, 48 kHz speaker PCM playback, codec DAC Media, games, speakerphone
Wired headset STREAM_MUSIC, 48 kHz wired_headset PCM playback, codec headphone amp Music and calls over 3.5 mm or USB-C analog
Bluetooth A2DP STREAM_MUSIC, 44.1 or 48 kHz bluetooth_a2dp Bluetooth encoder, SBC, AAC, LDAC, or aptX Wireless headphones and speakers
USB audio STREAM_MUSIC, up to 96 kHz usb_device USB audio class driver External DACs and USB headsets
Earpiece voice call STREAM_VOICE_CALL, 8 or 16 kHz earpiece PCM voice path, modem link Phone calls on the earpiece
Microphone recording AudioRecord input, 16 or 48 kHz builtin_mic PCM capture, codec ADC Voice notes, video, voice search

Notice how the Bluetooth row is the odd one out: the HAL hands encoded audio to the Bluetooth stack instead of raw PCM to a codec, which adds a whole extra layer of codec negotiation. That is why Bluetooth can sound wrong while wired audio sounds fine on the same phone. Notice too that the voice call row runs at a much lower sample rate than the media rows, because telephony was built around narrowband speech, and the framework keeps those paths separate on purpose.

Playback and recording journeys: tracing a sound through every layer

The fastest way to understand the framework is to follow one sound from end to end. Here is what happens when you press play on a song, and what happens in reverse when you record a voice note.

Playback: from AudioTrack.write to the speaker coil

  1. The app writes PCM data into an AudioTrack, or MediaPlayer does it on the app’s behalf after decoding the file.
  2. AudioFlinger receives the stream, checks its attributes and focus state, and mixes it with other active streams.
  3. The mixer resamples the stream to the output rate, usually 48 kHz, and applies the framework effects chain.
  4. AudioFlinger writes the mixed buffer to the HAL output stream for the chosen device.
  5. The HAL converts the stream into hardware operations, adjusts the channel layout, and calls tinyalsa pcm_write.
  6. The kernel driver starts DMA, feeds samples to the codec, and the codec drives the speaker through its amplifier.

Latency depends on every buffer along that journey. Each layer adds a buffer for stability: the app buffer, the mixer buffer, the HAL buffer, and the driver ring buffer. More buffering reduces glitch risk but increases delay. Less buffering reduces delay but increases glitch risk under load. Voice communication needs low latency, so it uses the fast mixer path with small buffers, while music playback can tolerate higher latency and uses the deep buffer path instead. For real-time audio, Android also offers the AAudio API with an MMAP path that lets the app write almost directly to the driver buffer, cutting latency to a few milliseconds on supported devices.

Recording: from the microphone capsule to AudioRecord.read

Recording runs the same layers in reverse. The microphone capsule feeds the codec, the codec converts the signal to digital samples, the driver collects those samples into buffers, the HAL delivers buffers upward through its input stream interface, and the framework routes the input stream to the app that holds the recording request and the microphone permission. Recording permissions are enforced above the HAL: an app without microphone permission should not receive input data even when the hardware path works. The framework and the permission system guard that boundary, and the HAL itself does not make app-level permission decisions. That separation keeps security policy in one place.

Echo cancellation, noise suppression, and automatic gain control may run inside the audio path for voice use cases. These effects can run in the framework or in vendor code depending on device design, and an effect that runs in the wrong place can distort a recording or add latency. A developer who records raw audio for analysis should know which effects are active on the chosen input path. Android offers input sources that control this: MIC applies the default processing, VOICE_RECOGNITION tunes processing for speech recognition, and UNPROCESSED asks for the rawest signal the hardware can give, with no gain or suppression applied.

Where audio effects live in the path

Effects can sit in the framework path or in the HAL path depending on design. An equalizer in the framework affects the mixed output before it reaches the HAL. An effect in vendor code may affect only one hardware path. The location of an effect changes which apps hear it and which outputs carry it. Custom audio processing for an app should usually run inside the app itself when the goal is app-specific sound, because app-level processing is portable and does not affect other apps. Developers who build custom processing often work at the native layer as well, and the native audio work in creating custom audio effects filters in Android NDK shows a different processing point inside an app. App-level native processing and HAL processing are not interchangeable, and each has its own test needs.

Troubleshooting Android audio failures layer by layer

Most audio bugs yield to one method: compare apps and outputs, then test one layer at a time. If one app is silent while others play, the problem likely sits in that app. If all apps are silent on one output while another output works, the problem likely sits in the HAL or driver path for that output. If all outputs are silent, the problem may sit higher in the framework or in a global mute state. Write down the exact device, the exact output, and the exact steps before you start, because vague reports such as no sound make layered debugging much slower, and precise reports save time for everyone involved.

No sound at all: checking the framework first

Start with the simplest checks: volume per stream, Do Not Disturb, and the mute state. Then look at the framework with dumpsys audio, which shows the active streams, the routing, and the focus holder. If dumpsys shows no active stream while the app claims to be playing, the app never reached AudioFlinger, and the bug is in the app or in the binder path. If dumpsys shows an active stream routed to the right device, the framework is doing its job, and you move down to the HAL.

Logcat tags and dumpsys commands that show the failing layer

Watch logcat with the audio tags while you reproduce the fault: adb logcat -s AudioFlinger AudioPolicyService audio_hw_primary. A HAL open failure shows up as an error from the HAL service; a routing failure shows up in AudioPolicyService; a mixer problem shows up in AudioFlinger. Capture the failing scenario in a controlled way, one change at a time, and note the exact device and output used. Logs and dumps from the audio service can confirm which layer reports an error, and that confirmation is worth more than any guess.

Sound on one output but not another

This pattern points at the HAL device list or the driver path for the silent output. Check whether the missing output appears in dumpsys audio at all. If it never appears, the HAL did not report it, and the cause is usually a config file, a missing device port, or a driver that failed to probe the codec path. If it appears but stays silent, run the tinyplay bypass test from the previous section: sound from tinyplay clears the driver and the codec, and points the finger at the HAL stream open call.

When the HAL device list is missing your headset

Headset detection is a small chain of its own: the jack detect interrupt fires, the driver reports the jack state, the HAL reads it, and the framework updates the device list. A break anywhere in that chain hides the headset. On phones with USB-C audio, the chain is longer, because the USB audio class driver must enumerate the accessory before the HAL can report it. If a headset works on one phone and not on another with the same app, compare the jack detection path first, before you blame the app.

Glitches, pops, and stutters during playback

Glitches usually mean the buffer chain ran dry. Check CPU load and the CPU governor first, because a core that sleeps too deeply between periods starves the HAL refill loop. Then check the buffer size: a fast mixer path with tiny buffers glitches under load that a deep buffer path absorbs without a sound. Background work, thermal throttling, and aggressive battery savers all show up here, which is why a phone that plays music cleanly on a charger can stutter on battery saver mode. If the glitch follows the device rather than the app, the buffer chain is where to look.

Bluetooth audio works but sounds wrong

Bluetooth adds codec negotiation on top of the normal path. The framework and the HAL must agree on the codec and the configuration for that link, and a mismatch can cause silence or poor quality even when wired audio works well. Check which codec the link actually negotiated: SBC is the universal fallback, AAC sounds better on Apple-oriented gear, and LDAC or aptX need both ends to support them. Developer options on Android show the active Bluetooth codec, and a phone that falls back to SBC when LDAC should be available is usually fighting a config or a handshake problem, not a broken speaker.

Microphone privacy and the rules around capture

Microphone access is sensitive and users expect clear control over it. An app should request microphone permission only when recording is part of its visible purpose, and a recording indicator on modern Android versions shows when an app uses the microphone. Developers should respect that indicator and should not try to hide recording activity. Do not attempt to bypass framework audio routing to capture audio from another app: system audio capture has strict rules and user consent requirements, and a tool that silently captures another app’s audio violates user trust and may breach platform policy. Camera interception shares some of the same trust questions as audio interception, and the camera discussion in how Android camera hooking works explains why a hook that changes a live feed needs disclosure and narrow scope. Audio developers should apply the same disclosure discipline to any tool that changes a live audio path.

Frequently asked questions about the Android audio HAL and kernel

What does the Audio HAL do that the kernel driver cannot?

The HAL translates framework concepts into hardware operations. It knows which devices exist, which sample rates each path supports, and how to open a stream for a given use case. The kernel driver only knows registers, clocks, DMA, and buffers. Without the HAL, the framework would need a different code path for every codec chip on the market, which is exactly the mess the HAL was created to prevent.

How can I tell whether a no-sound bug sits in the HAL or the driver?

Use the tinyplay bypass test. Play a WAV file straight to the PCM device with tinyplay, skipping the framework and the HAL. If you hear sound, the driver and the codec work, and the fault sits in the HAL or above it. If tinyplay is silent, the fault sits in the driver, the codec, or the hardware. Then check dumpsys audio to see whether the framework ever learned the device existed, which separates a HAL reporting failure from a HAL stream failure.

Why does Bluetooth audio add latency that wired audio does not?

Bluetooth audio is encoded before transmission, sent over a radio link, decoded in the headphones, and buffered on both ends to survive radio interference. Each of those steps adds delay, and the total is typically 150 to 300 milliseconds with SBC, more with some high-quality codecs. Wired audio has none of that: PCM samples go straight from the driver to the codec. That is why lip-sync correction exists for video over Bluetooth, and why gamers and musicians still reach for a wire.

Can an app talk to the kernel audio driver directly?

No, not on a normal Android phone. Apps run in a sandbox without permission to open /dev/snd device nodes, and the SELinux policy blocks direct access. Apps must go through the framework APIs, which route through AudioFlinger and the HAL. Only system processes and rooted tools can open the ALSA devices directly, and even then the framework may already hold them open, which blocks a second opener. This is deliberate: it keeps one app from stealing or breaking another app’s audio.

Why do two phones running the same Android version sound different?

Because the HAL and the driver differ by vendor and by board design. The codec chip, the amplifier, the speaker enclosure, the tuning applied in the HAL, and the default effects all change the sound, even when the app and the framework are identical. A phone with a tuned amplifier and a larger speaker chamber will sound fuller than a phone with a basic codec path, and no app update can close that hardware gap. Test audio on your target devices instead of assuming one device covers them all.

What to do next with Android audio

You now have the full map: app APIs at the top, AudioFlinger and AudioPolicyService in the framework, the HAL translating to hardware, tinyalsa and ALSA drivers moving samples, and the codec turning those samples into sound. The next time audio fails, resist the urge to guess. Compare apps, compare outputs, check dumpsys audio, and run the tinyplay bypass test. Those four steps will put the fault in one layer almost every time, and from there the fix is usually a config file, a buffer size, or a codec negotiation setting rather than a rewrite.

Pick one concrete step today. Pull the audio policy configuration from your test phone and read the output profiles, or reproduce a glitch while watching the AudioFlinger log tags. Either exercise will teach you more about your device’s audio path than another hour of reading. And if you maintain a ROM or a vendor HAL, audit the boundary you own: the HAL should report every device the driver can drive, and the driver should drive every device the HAL reports. When those two lists match, the layers above them can do their jobs.

RELATED ARTICLES

Most Popular