enhance(main/pulseaudio): add AAudio source module for microphone input#29074
enhance(main/pulseaudio): add AAudio source module for microphone input#29074ferrumclaudepilgrim wants to merge 1 commit into
Conversation
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. |
|
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. |
|
Not only pulseaudio can be affected, but also other audio servers like openal-soft. oboe might help. This might be related: |
|
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. |
|
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 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. |
e7bf6b9 to
c7c3181
Compare
|
Probably merging aaudio module should be fine until you will have fully working oboe alternative for both of them. |
|
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. |
|
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. |
robertkirkman
left a comment
There was a problem hiding this comment.
I can confirm that this is working. Is this ready to merge?
|
If approved and ready on your end I am ready. Excited to contribute now and in the future! |
|
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. |
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.
33de024 to
e25d8c1
Compare
|
Squashing was a challenge. I apologize. Still new to the contribution aspect. Thank you for your understanding! |
|
Could you estimate, how much of the PR was generated by LLM? |
|
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()> " |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
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. |
|
Hey, sorry for the delayed response. @tomty89 appreciate the detailed breakdown on the other parameters -- that context is helpful.
Happy to clarify anything else if needed. |
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.
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 I have a question regarding this PR, and also regarding the existing 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, |
|
@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:
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? |
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. |
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. |
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.
|
Update: the Oboe follow-up is now on #29319 as commit 3 (module-oboe-sink + Per your 2026-04-16 note to upload additional Oboe code on the Oboe PR while |

Summary
PulseAudio on Android 12+ has no working microphone input.
module-sles-sourcefails with
SL_RESULT_CONTENT_UNSUPPORTED(error 12) because OpenSL ES inputsupport was removed. No
module-aaudio-sourceexists upstream.This PR adds
module-aaudio-source.c-- an AAudio-based source module thatmirrors the architecture of the existing
module-aaudio-sink.c. It enablesmicrophone input for any application routing audio through PulseAudio.
Changes
packages/pulseaudio/module-aaudio-source.c(new) -- AAudio input module.Uses
AAUDIO_DIRECTION_INPUTwith 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, andno_close_hackarguments.packages/pulseaudio/build.sh(modified) -- BumpsTERMUX_PKG_REVISIONfrom 1 to 2. Installs the new source file into the PulseAudio source tree
before build.
packages/pulseaudio/meson.patch(modified) -- Registersmodule-aaudio-sourcein the meson build alongsidemodule-aaudio-sink.Design notes
The module mirrors
module-aaudio-sink.cclosely:__INTRODUCED_INguard for AAudio headersCHKmacro patternstate_func_io/state_func_mainstate machineno_close_hackworkaround for close-during-callback issueserror_callbackwith busy-wait on INIT + suspend/resume recoveryKey source-specific differences from the sink:
pa_asyncmsgq_post(non-blocking) instead ofpa_asyncmsgq_send(blocking),because the source RT callback hands off captured data rather than waiting for
rendered data
AAUDIO_DIRECTION_INPUTandAAudioStreamBuilder_setInputPresetPA_SOURCE_LATENCYflag passed topa_source_newbuilder 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:
pactl list sources shortparecordcaptures real audio (48kHz stereo s16le)Known issue (resolved): During development, an unload crash was discovered
(double-close of AAudioStream --
state_func_iocloses the stream onIDLE->SUSPENDED but does not null the pointer, so
pa__donecloses it again,triggering a FORTIFY abort on
pthread_mutex_lock). The submitted code includesthe fix (
u->streamnulled after close instate_func_io). Theno_close_hackparameter 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
$PREFIX/etc/pulse/default.pa, uncomment:load-module module-aaudio-sourcepulseaudio -k && pulseaudio --startpactl list sources shortshould showAAudio_sourceparecord --channels=1 --rate=48000 test.wavthenpaplay test.wavIssues