Skip to content

enhance(main/pulseaudio): add AAudio source module for microphone input#29074

Open
ferrumclaudepilgrim wants to merge 1 commit into
termux:masterfrom
ferrumclaudepilgrim:pulseaudio-aaudio-source
Open

enhance(main/pulseaudio): add AAudio source module for microphone input#29074
ferrumclaudepilgrim wants to merge 1 commit into
termux:masterfrom
ferrumclaudepilgrim:pulseaudio-aaudio-source

Conversation

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor

Summary

PulseAudio on Android 12+ has no working microphone input. module-sles-source
fails with SL_RESULT_CONTENT_UNSUPPORTED (error 12) because OpenSL ES input
support was removed. No module-aaudio-source exists upstream.

This PR adds module-aaudio-source.c -- an AAudio-based source module that
mirrors the architecture of the existing module-aaudio-sink.c. It enables
microphone input for any application routing audio through PulseAudio.

Changes

  1. packages/pulseaudio/module-aaudio-source.c (new) -- AAudio input module.
    Uses AAUDIO_DIRECTION_INPUT with a non-blocking async message queue pattern
    (RT callback thread -> pa_asyncmsgq_post -> PA IO thread -> pa_source_post).
    Supports source_name, source_properties, rate, latency,
    input_preset, and no_close_hack arguments.

  2. packages/pulseaudio/build.sh (modified) -- Bumps TERMUX_PKG_REVISION
    from 1 to 2. Installs the new source file into the PulseAudio source tree
    before build.

  3. packages/pulseaudio/meson.patch (modified) -- Registers
    module-aaudio-source in the meson build alongside module-aaudio-sink.

Design notes

The module mirrors module-aaudio-sink.c closely:

  • Same __INTRODUCED_IN guard for AAudio headers
  • Same CHK macro pattern
  • Same state_func_io / state_func_main state machine
  • Same no_close_hack workaround for close-during-callback issues
  • Same error_callback with busy-wait on INIT + suspend/resume recovery

Key source-specific differences from the sink:

  • pa_asyncmsgq_post (non-blocking) instead of pa_asyncmsgq_send (blocking),
    because the source RT callback hands off captured data rather than waiting for
    rendered data
  • AAUDIO_DIRECTION_INPUT and AAudioStreamBuilder_setInputPreset
  • No rewind handling (source-only -- rewind is a sink concept)
  • PA_SOURCE_LATENCY flag passed to pa_source_new
  • Builder cleanup in fail path (improvement -- the sink currently leaks the
    builder on failure)

This uses the same direct AAudio approach as the existing sink module. If the project migrates to Oboe (as discussed in #28861) or PipeWire in the future, this module would be superseded alongside the existing AAudio sink.

Testing

Tested on one device:

  • Device: Samsung Galaxy S26 Ultra, Android 16, ARM64
  • Environment: Native Termux, PulseAudio 17.0
  • Compiler: Termux clang 21.1.8
Test Result
Module loads from default.pa Pass
Source appears in pactl list sources short Pass
parecord captures real audio (48kHz stereo s16le) Pass
Voice input works through PulseAudio Pass
Record/stop cycle (5x) Pass
Suspend/resume Pass
Rapid suspend/resume (10x, 200ms intervals) Pass
Concurrent recordings (2 simultaneous parecord) Pass
Idle timeout + resume Pass

Known issue (resolved): During development, an unload crash was discovered
(double-close of AAudioStream -- state_func_io closes the stream on
IDLE->SUSPENDED but does not null the pointer, so pa__done closes it again,
triggering a FORTIFY abort on pthread_mutex_lock). The submitted code includes
the fix (u->stream nulled after close in state_func_io). The no_close_hack
parameter is retained as an additional safety measure for firmware-specific edge
cases, matching the upstream sink's pattern. Note: the upstream sink has the same
potential double-close issue in its close path.

How to enable

  1. Grant microphone permission: Android Settings > Apps > Termux > Permissions > Microphone
  2. Edit $PREFIX/etc/pulse/default.pa, uncomment: load-module module-aaudio-source
  3. Restart PulseAudio: pulseaudio -k && pulseaudio --start
  4. Verify: pactl list sources short should show AAudio_source
  5. Test: parecord --channels=1 --rate=48000 test.wav then paplay test.wav

Issues

@robertkirkman
Copy link
Copy Markdown
Member

PulseAudio on Android 12+ has no working microphone input.

My Android 13 device does have working microphone input with pulseaudio and OpenSL ES, however my device could be an exception. I understand that in general the idea of having module-aaudio-source working is good because some devices only work with aaudio, not opensl es.

@twaik
Copy link
Copy Markdown
Member

twaik commented Mar 23, 2026

Probably both source and sink backends should be rewritten with using oboe, because on some devices vendors mess with linking and it breaks termux-driven pulseaudio.

@robertkirkman
Copy link
Copy Markdown
Member

Not only pulseaudio can be affected, but also other audio servers like openal-soft. oboe might help. This might be related:

@robertkirkman
Copy link
Copy Markdown
Member

Though, in that case, there was another workaround possible which was to disable dynamic loading and enable dynamic linking of openal-soft itself to OpenSL ES, but at the time, I opened two PRs, and when you merged one of them, I closed the other one, since I assumed it meant that allowing the openal-soft package to remain in dynamic loading mode would be preferred, but I'm not sure why.

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

Submitted this mainly because there are people actually hitting the missing mic input right now (termux/termux-app#2311, termux/termux-api#450) and the module follows the same pattern as the existing AAudio sink, so it seemed like a reasonable stopgap.

On the Oboe front -- I'd actually been poking at that separately before this PR. Tried building it for Termux and wiring it into PA modules with extern "C" entry points (basically the same approach openal-soft uses for their Oboe backend). @robertkirkman good call connecting the openal-soft dots there, same underlying problem.

So the real question for @twaik and maintainers: is this AAudio module worth merging as a near-term fix, or would you rather wait and go straight to Oboe? I'm fine either way and happy to help with whichever direction. Pushing the CI fix for the hunk header shortly regardless.

@twaik
Copy link
Copy Markdown
Member

twaik commented Mar 24, 2026

Probably merging aaudio module should be fine until you will have fully working oboe alternative for both of them.

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

Thanks -- sounds good. The AAudio module is ready to go as-is for the stopgap. Last night I got curious and dug further into the Oboe side of things, and I may or may not have the package building on-device already. Sink and source modules passed stress testing. Will have a follow-up PR ready once this one lands.

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

Hooray for a successful build! Apologies for the failed builds earlier. This is my first PR and I missed the API level issue on the initial push. It's fixed now and all 4 architectures are passing.

@robertkirkman -- thanks for the correction on SLES still working on some Android 13 devices. That whole assuming thing still biting me.

@twaik -- appreciate the direction on Oboe. I've actually got it building from source in Termux already and the sink module is working through PulseAudio. Planning to put together a package PR for it once this one lands. Be good or be good at it.

Copy link
Copy Markdown
Member

@robertkirkman robertkirkman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that this is working. Is this ready to merge?

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

If approved and ready on your end I am ready. Excited to contribute now and in the future!

@robertkirkman
Copy link
Copy Markdown
Member

Ok if it gets 1 more approve I will merge it, otherwise I will merge it in 24 hours. Thank you for planning to maintain this in the future.

@robertkirkman
Copy link
Copy Markdown
Member

oh, in order for this PR to have individually buildable commits, could you please combine the two commits together into 1 commit that has all the changes from both commits combined?

image

@robertkirkman robertkirkman changed the title pulseaudio: add module-aaudio-source for microphone input via AAudio enhance(main/pulseaudio): add AAudio source module for microphone input Mar 26, 2026
module-sles-source fails on Android 12+ with SL_RESULT_CONTENT_UNSUPPORTED
(error 12). This adds module-aaudio-source using the AAudio input API,
mirroring the architecture of module-aaudio-sink.

AAudioStreamBuilder_setInputPreset is guarded behind __ANDROID_API__ >= 28
as the symbol is unavailable on API < 28. Related: termux#27978.
@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

Squashing was a challenge. I apologize. Still new to the contribution aspect. Thank you for your understanding!

@robertkirkman
Copy link
Copy Markdown
Member

Could you estimate, how much of the PR was generated by LLM?

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

Honest answer the code was generated with Claude Code. I run Termux daily on my phone and hit the mic input issue myself trying to get the /voice command to work in Claude Code via Termux on Android specifically, not via SSH. I used it to dig into the source code, several times over, then made it work. Knowing open-source projects are that for a reason I figured I'd read up and try to contribute. Making an app I use better and somebody else. Win/ win. Bottom line: I am a guy who has a Claude Max 20x sub and is trying to build stuff and help the right way while having a good time and learn as much as I can.

"latency=<buffer length> "
"pm=<performance mode> "
"input_preset=<AAudio input preset index> "
"no_close_hack=<avoid segfault caused by AAudioStream_close()> "
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does introduce certain pulseaudio module arguments in its API here, I am not knowledgeable about what these should look like so someone who knows should approve this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomty89 do you happen to know if this is correct?

Copy link
Copy Markdown
Contributor

@tomty89 tomty89 Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well all I can offer is what these are for "sink-wise".

latency exists mainly because in at least the case of playback, having a small buffer could result in audio to "break up" when the output requires larger buffer (such as the case of bluetooth audio). While AAudio provides an API for querying like how large should the buffer at least be, and that it is leveraged in the module by default (see get_latency(), and also where it is called), there is no way (at least none implemented) for pulse / the module to detect output change (from device has smaller to larger buffer requirement, such as from builtin speaker to bluetooth). In other words the latency / buffer size is fixed once the stream is initialized. The option allows user to e.g. override/increase the size/length with/to a value that is good enough for any potential output type. (Also on some devices the value returned by the API might be too "optimistic" or so anyway. Things were/are pretty "delicate" given the "variety" of Android phones/devices.)

As for pm, it just exposes what AAudio allows us to choose from -- lower latency requirement but supposedly comsumes more battery or higher latency but probably more power saving. (There's also the balanced mode.) I have no idea how much things could differ with these modes and it could even be very device-specific. The default chosen (by me, I think?) has been the low latency mode. (Because low latency sounded cool, I guess. Maybe also because I wanted to make sure things work fine with the more "extreme" cases.)

no_close_hack exists because when I developed the sink module, what I had was an Oreo Xperia (that no longer receives any system update even back then), on which the Android build comes with a bugged AAudio implementation. It's nothing but an option for enabling a workaround (that leaves the opened AAudio streams unclosed; which works until too many are opened/unclosed) on (old) devices with this bug.

Hope that these help. No idea what input_preset is for.

@robertkirkman
Copy link
Copy Markdown
Member

I have confirmed this is working on 32-bit ARM Android 8 (the oldest Android version that supports AAudio) and 64-bit ARM Android 13.

The audio quality is very good. There was an extremely loud noise recorded when I tapped the phone on a table during recording, but that is normal and indicates raw recording before applying any filters.

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

Hey, sorry for the delayed response.

@tomty89 appreciate the detailed breakdown on the other parameters -- that context is helpful.

input_preset is the recording-side equivalent. It tells Android what kind of audio capture you're doing so it can apply the right processing. The AAudio API offers a few options -- generic, camcorder, voice recognition, voice communication, and unprocessed. The module defaults to generic, and the option is exposed so users can override it if their use case needs something specific.

Happy to clarify anything else if needed.

@ferrumclaudepilgrim ferrumclaudepilgrim mentioned this pull request Apr 12, 2026
8 tasks
ferrumclaudepilgrim added a commit to ferrumclaudepilgrim/termux-packages that referenced this pull request Apr 12, 2026
Add Oboe 1.10.0, Google's C++ library for
high-performance audio on Android. Builds shared
library only (no static archive). Includes
hand-written pkg-config and CMake config files
since upstream does not provide them.

Suggested by @twaik in termux#28861 as a replacement
for the PulseAudio SLES backend to resolve
vendor-specific linking failures on newer Android
devices. Also discussed in termux#29074.
ferrumclaudepilgrim added a commit to ferrumclaudepilgrim/termux-packages that referenced this pull request Apr 13, 2026
Add Oboe 1.10.0, Google's C++ library for
high-performance audio on Android. Builds shared
library only (no static archive). Includes
hand-written pkg-config and CMake config files
since upstream does not provide them.

Suggested by @twaik in termux#28861 as a replacement
for the PulseAudio SLES backend to resolve
vendor-specific linking failures on newer Android
devices. Also discussed in termux#29074.
@robertkirkman
Copy link
Copy Markdown
Member

robertkirkman commented Apr 15, 2026

@ferrumclaudepilgrim I have a question regarding this PR, and also regarding the existing module-sles-source.c, module-sles-sink.c, and module-aaudio-sink.c.

The more I think about it, the more I think it might not be the best situation for these to remain in Termux forever. Do you think there are any blockers to opening a PR upstream to add all four, module-sles-source.c, module-sles-sink.c, module-aaudio-source.c and module-aaudio-sink,c, to upstream pulseaudio? If that were done, then these could also be used by other Android apps that have Pulseaudio, right? If there are blockers to that, how difficult do they seem to resolve?

@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

ferrumclaudepilgrim commented Apr 15, 2026

@robertkirkman so I'll be awkwardly honest as always. I did map this out pretty far. I didn't think to answer your question yet but it was likely where I was getting to, maybe just the long way.

My mind was:

I did manage to do quite a bit of digging along the way. I saw your openal-soft#1111 which I read as same problem different tree but same problem at heart. kcat basically said he is not sure what upstream can do, which pushed me toward "replace SLES with Oboe in our modules" rather than "get SLES fixed." But even with all of that I was still thinking inside the Termux tree. Upstreaming to PulseAudio proper just didn't cross my mind, and reading and comprehending this I see places where it could have.

A few reasons I stayed narrow, for context:

  • As I've probably redundantly said I am new to contributing and wanted to respect the process. I am unsure of the etiquette and didn't want to be overzealous, even though I clearly am.
  • When I researched the contribution process I saw PRs get knocked down for too wide or ambitious scope.
  • Top that off with my overall honesty of where I am at in my process. I wouldn't say I don't feel I can contribute, but as you can see, it has had some hurdles.

Overall I don't disagree, and in fact the opposite. I guess that leads me to the follow up of, knowing this now, where does that lead?

@robertkirkman
Copy link
Copy Markdown
Member

Rebuild the PulseAudio modules on top of Oboe to replace both SLES and AAudio, as I kept asking where the next leaf is.

Oh I see, I didn't quite realize that this was the next step in your plan. In that case, it seems like having Pulseaudio connected to Oboe is your goal that isn't quite finished yet. I suggest that if you want to continue that goal, you can upload any additional code for that you make in the PR which adds Oboe while waiting for it to get more reviews, since that code would be related to and depending on Oboe.

@robertkirkman
Copy link
Copy Markdown
Member

When I researched the contribution process I saw PRs get knocked down for too wide or ambitious scope.

Does this refer to Termux, or to the upstream Pulseaudio? if it refers to Pulseaudio, I understand what you mean, and maybe they would be unlikely to accept support for Android sound APIs. I still think it would be worth a try, if it's not too much work for you. If you would rather stick to making PRs in Termux for now, then that would also be fine.

ferrumclaudepilgrim added a commit to ferrumclaudepilgrim/termux-packages that referenced this pull request Apr 17, 2026
Add module-oboe-sink and module-oboe-source using Google's Oboe library.
Oboe handles audio backend selection and vendor quirks internally,
addressing vendor-specific SLES breakage affecting Xiaomi, OnePlus,
Samsung, and Redmi devices (termux#28861, termux#27978, termux#27367).

Cross-device verified: Samsung S26 Ultra (Android 16 / API 36) and
Pixel 10 Pro (Android 17 Beta / API 37). Both modules load, record,
play, suspend/resume, and unload cleanly with multi-cycle stress.

Add module-oboe-sink and module-oboe-source as commented-out options
in default.pa. SLES remains the active default. Users experiencing
SLES breakage can uncomment the Oboe modules.

Suggested by twaik in termux#28861 and termux#29074.
@ferrumclaudepilgrim
Copy link
Copy Markdown
Contributor Author

Update: the Oboe follow-up is now on #29319 as commit 3 (module-oboe-sink +
module-oboe-source), cross-device verified on Samsung S26 Ultra and Google
Pixel 10 Pro.

Per your 2026-04-16 note to upload additional Oboe code on the Oboe PR while
this one collects reviews, the module-oboe-* work landed there rather than
here. Leaving #29074 open as you suggested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants