Skip to content

High-quality two-way audio (mic + HQ music at once): no-root for in-app use, system-wide mic needs root #720

Description

@IvanChanPing

I looked at how the two-way audio actually works and what's realistically reachable on Android, and here's where the no-root line falls — further than I expected on one side, hard-blocked on the other.

The good news: keeping high-quality playback AND using the AirPods mic at the same time is genuinely doable without root, as long as LibrePods is the one using the mic. The reason Android normally drops music to phone-call quality is that asking the framework for a Bluetooth mic (startBluetoothSco / setCommunicationDevice) forces HFP and opens an SCO link, which suspends the A2DP media stream. The AACP mic path never touches that — the mic audio comes down LibrePods' own L2CAP socket (the same PSM 4097 channel it already opens via the reflected private BluetoothSocket constructor), and A2DP is a separate stream the Bluetooth stack owns, so it just keeps playing. No SCO, no profile switch, no root.

From the high-res-mic work (#655), the uplink is AAC-ELD carried in AACP 0x58 packets — so the steps are: open the channel, send the mic-start control sequence, parse the 0x58 frames, decode AAC-ELD to PCM. All of that runs in-app with no special permission. Once you have the PCM, LibrePods can do anything with it internally — record it, transcribe it, feed it straight into its own WebRTC/VoIP encoder — while music keeps playing to the buds in HQ. That already covers a lot (in-app calls, voice notes, on-device transcription with good audio).

The wall, and it's a real one: you can't make that PCM show up as a normal microphone that other apps (Zoom, WhatsApp, a voice recorder) record from, without root. Android input devices are enumerated by audioserver from the vendor audio HAL, and there's no app API to register a virtual input source. AudioRecord only takes the fixed MediaRecorder.AudioSource constants; AudioPlaybackCapture is an output-tap that can't capture a mic or inject input; AudioTrack is output; accessibility can't publish audio. The one AOSP injection primitive (AudioPolicy.createAudioTrackSource / registerAudioPolicy with USAGE_VIRTUAL_SOURCE) needs MODIFY_AUDIO_ROUTING, which is signature|privileged — not user-grantable, and not something ADB/Shizuku can hand out. So a system-wide virtual mic needs root (a virtual input HAL or an AudioFlinger hook), which matches the README note.

So concretely:

  • No-root, shippable: high-quality music + AirPods mic together, used inside LibrePods (its own call / record / transcribe features). The remaining work is the reverse-engineering — confirming the 0x58 mic-start sequence and the AAC-ELD framing ([Linux/rust] Implemented high-res microphone support #655 is already on this).
  • Root-only: turning it into a system microphone other apps can use.

Sources: docs/AAP Definitions.md + #655 (mic = AAC-ELD in 0x58); BluetoothConnectionManager.kt / AirPodsService.kt (classic-L2CAP to PSM 4097 via the reflected socket constructor); Android AudioRecord / AudioPlaybackCapture / AudioPolicy docs (MODIFY_AUDIO_ROUTING is signature|privileged).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions