EDIT: the hypothesis in this post is wrong. I have struck the parts that did not hold up. See my follow-up below for the CalyxOS comparison that refutes it. The measurements themselves still stand.
Following up on my post above: I found some time to dig into this. I have not opened the GitLab issue yet, but here is the data, in case it is useful to someone before I do. Here is the data, in case it is useful to someone. (I later tried to register on GitLab and could not, see follow-up.)
Up front: I used Claude Opus 5 to help me read the audio dumps. I am not an Android audio developer. Everything under “measurements” is raw dumpsys and logcat output from my own device and can be reproduced by anyone. The explanation at the end is a hypothesis Claude and I worked out together, and I want to be clear that I only suspect I know what is going on. Please treat the numbers as data and the reasoning as a guess that may well be wrong. It was wrong.
Device
ro.build.display.id BP2A.250805.005
ro.build.version.release 16
ro.build.version.incremental 1788499613
Signal 8.24.2, WhatsApp 2.26.32.83
Both ro.build.fingerprint and ro.vendor.build.fingerprint report Fairphone/FP4eea/FP4:15/FP4.SREL.15.19.2/15.19.2 on this build, which looks like a spoofed fingerprint rather than a real vendor version, so I would not read anything into it.
Correction: CalyxOS on the same phone reports the exact same fingerprint string, so it is simply inherited and carries no information either way. The properties that actually indicate the vendor version are ro.vendor.build.id, .version.release, .version.sdk and .date, which I failed to capture while I was still on /e/OS.
Symptom matches the OP exactly: far end sounds like a chipmunk on Signal and WhatsApp, cellular calls fine, the far end hears me normally, identical on earpiece, speaker, wired headset and Bluetooth.
Measurements
Control, a 60 s cellular call: the voip_rx thread’s counter went from 0 to 282,240 frames over a 99.5 s window, i.e. 5.88 s of audio, and the thread’s local log shows the only activity was a 0.5 s system tone. So cellular calls do not use this path.
One Signal call, 60 s window taken mid-call: 5,190,720 frames = 108.1 s of audio in 60.1 s of wall clock, of which the stream was active for 58.2 s. That is a factor of 1.8 to 1.86.
Important caveat on that number: it is saturated, not measured. Over the session the thread did 13,470 writes for 269.4 s of nominal audio with 112.5 s spent throttling, a mean cycle of 10.9 ms against a 20 ms HAL buffer (960 frames at 48 kHz). AudioFlinger sleeps at most half a buffer per cycle, so 2.0 is the highest ratio this counter can ever show. The real consumption rate is therefore at or above 2x, with no upper bound from this data, and a 3x error would look identical. I cannot distinguish 2x from 3x this way, that needs an acoustic measurement, which I have not done.
Supporting stats after the call: localSR 82380 against 48000 nominal, 13,458 FIFO underruns, Thread throttle time 112510 ms.
The configuration I think matters The configuration, for reference
In /vendor/etc/audio_policy_configuration.xml and in the _a2dp_offload_disabled variant:
xml
<mixPort name="voip_rx" role="source" flags="AUDIO_OUTPUT_FLAG_VOIP_RX">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="8000,16000,32000,48000"
channelMasks="AUDIO_CHANNEL_OUT_MONO,AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
<mixPort name="voip_tx" role="sink" flags="AUDIO_INPUT_FLAG_VOIP_TX">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="8000,16000,32000,48000"
channelMasks="AUDIO_CHANNEL_IN_MONO"/>
</mixPort>
RX allows mono or stereo, TX allows mono only. TX works, RX does not. This is identical on CalyxOS, where audio is correct, so the asymmetry is not the cause. During the call the HAL logs two channels for VoIP playback and one for the matching capture path:
platform_check_and_set_codec_backend_cfg:becf: afe: bitwidth 16, samplerate 48000
channels 2, backend_idx 0 usecase = 33 device (handset)
platform_set_channel_map: set mapping(1 2 0 0 0 0 0 0) for channel:2
platform_check_capture_codec_backend_cfg:txbecf: afe: ... new channel: 1
This is not new
The same symptom has been reported on the FP4 under custom ROMs since 2022, but always app-specific: Teams on /e/OS stable (Feb 2022), Teams on LineageOS 20 (Dec 2022), Skype on LineageOS 20 (Jul 2023). Not on stock FairphoneOS. That part still seems worth knowing.
That fits together like this. The pitch is determined by the channel count the thread is opened with, not by what the individual app supplies, my WhatsApp track was 16 kHz mono and was affected anyway, because the mixer sits in front of it. If, before A16, the thread took its channel count from the first client to attach, then Teams and Skype (stereo) triggered it and Signal and WhatsApp (mono) did not. Under A16 the thread is opened at audioserver init with the widest mask the profile offers, so it is always stereo, and now everyone is affected.
The guess, then: the stream is opened stereo on a VoIP frontend that expects mono, so every stereo frame is consumed as two mono samples and everything plays at double speed. The stereo-on-a-mono-frontend guess would give exactly 2x, but as noted above my measurement cannot confirm that specific value, so treat the mechanism as unverified. What the data does establish is that the VOIP_RX stream is consumed far faster than its nominal rate.
Two things argue against blaming the apps. Signal’s track is 48 kHz stereo and WhatsApp’s is 16 kHz mono, they attach to the same thread, and both sound identically wrong. And backend_idx 0 is shared with normal playback, where music and ringtones are pitch-correct, so the codec backend clock is fine.
What I could not check
- No root, so I could not read
/proc/asound/.../hw_params to see what the ALSA frontend actually negotiated. That would settle it. (Still true on CalyxOS.)
No A15 device to compare against, so I cannot demonstrate that the same profile behaved differently before. Since resolved by the CalyxOS comparison in the follow-up.
- My factor measurement is only good to about ±0.15, so I cannot prove it is exactly 2.0 rather than something else.
One thing that would really help Answered
/e/OS is LineageOS-based, the FP4 device tree is shared, and LineageOS 23.2 (Android 16) exists for this device. If LOS 23.2 shows the same thing, the fix belongs upstream rather than with /e/.
So if you are on LineageOS 23.x, or on the last stable /e/OS on an FP4, could you run this and post the output? No root, no call needed, works from a normal boot:
adb shell dumpsys media.audio_policy | grep -A3 "IOProfile name:voip_rx"
If your line says Channel mask: 0x1 and mine says 0x3, that pins the regression precisely and points at a one-line fix. If yours also says 0x3, my hypothesis is wrong and the cause is elsewhere, which is just as useful to know.
CalyxOS 7.2.4.50 on the same phone reports Channel mask: 0x3 and has correct audio. So my hypothesis was wrong and the cause is elsewhere. Details in the follow-up.
Happy to attach the full dumps if a developer wants them.