Skip to content

VoIPService: acquire a Wi-Fi low-latency lock during calls - #2014

Open
neonlight911 wants to merge 1 commit into
DrKLO:masterfrom
neonlight911:voip-wifi-lock
Open

VoIPService: acquire a Wi-Fi low-latency lock during calls#2014
neonlight911 wants to merge 1 commit into
DrKLO:masterfrom
neonlight911:voip-wifi-lock

Conversation

@neonlight911

@neonlight911 neonlight911 commented Aug 13, 2026

Copy link
Copy Markdown

Problem

VoIPService acquires a PARTIAL_WAKE_LOCK for the CPU but nothing for the Wi-Fi radio. The chipset therefore stays in power save for the whole call, and downlink packets that arrive while the radio dozes are buffered by the access point until the next beacon/DTIM — adding up to ~120 ms.

While speech is continuous the packet flow itself keeps the radio awake, so the effect is masked. It surfaces in DTX gaps: the first packets after a pause arrive late, the jitter buffer expands, and the user hears lag that accumulates over the call.

Measurements

Xiaomi 12T Pro, Android 15 (SDK 35), 802.11ax 5 GHz at −52 dBm, 573 Mbps, WPA3-SAE. ICMP from the AP controller to the phone, 40 packets per run at 500 ms intervals — the sparse interval is what a speech gap looks like to the Wi-Fi stack.

Condition WifiLock Screen avg RTT max RTT runs
Idle, no call none on 56.3 ms ~120 ms 6
Telegram call, at ear none off 8.5–19.9 ms 47–182 ms 3
Telegram call, speakerphone none on 13.3 ms 57–192 ms 5
Meet call, screen locked held off 12.0 ms 62–184 ms 5
Meet call, at ear held on 4.06 ms 6.5–10.7 ms 3
Meet call, speakerphone held on 3.88 ms 6.1–10.0 ms 5

Google Meet already takes this lock and produces the last two rows on the same link:

Locks held:
    WifiLock{VideoChatWifiLock type=4 uid=1000 workSource=WorkSource{10173 com.google.android.apps.tachyon}}

Telegram's counter in dumpsys wifi stayed at its baseline across three separate calls (169 → 169, 171 → 171), i.e. no lock is taken in any call mode.

The two factors are separable and both are needed: a held-but-ineffective lock (12.0 ms) is equivalent to no lock at all (13.3 ms). The ~120 ms tail is present in every row except the last two. The figure matches one beacon interval (100 TU ≈ 102.4 ms) plus delivery — textbook DTIM buffering of a dozing station.

Ruled out as causes: AP not overloaded (controller CPU 1 %), 0 % loss over 400 packets, dtim-period already 1, beacon interval already at the platform minimum (RouterOS rejects < 100 ms), Android wifi_power_save set to 0, and DSCP EF marking makes no difference (62.3 vs 66.5 ms).

Why this is the app, not the ROM

A fair objection is that the vendor ROM might be silently dropping a lock request rather than the app never making one. Checked two ways:

1. Bytecode of the installed build. Pulling the shipping APK (12.9.2) and scanning all five DEX files, the string createWifiLock occurs exactly once, and the only two classes referencing a Wi-Fi lock are:

Landroid/net/wifi/WifiManager$WifiLock;          (framework type)
Lcom/google/android/exoplayer2/WifiLockManager;  (bundled ExoPlayer)

There is no Wi-Fi lock reference anywhere in the VoIP code path. The single call site is ExoPlayer's media playback, which uses WIFI_MODE_FULL_HIGH_PERF and is disabled by default — matching the measurement, where 0 full high perf was acquired throughout, so even that one never fired. No request is made from the call path, and nothing can be dropped.

2. The ROM honours locks. Meet's lock on the same device appears correctly in Locks held with proper workSource attribution and yields a 3× latency improvement. If this ROM discarded Wi-Fi locks, Meet's would not work either.

Change

  • wifiLock field alongside the existing cpuWakelock
  • acquired in onCreate() right after cpuWakelock.acquire(), using WIFI_MODE_FULL_LOW_LATENCY on API 29+ and falling back to WIFI_MODE_FULL_HIGH_PERF below
  • released in onDestroy() next to cpuWakelock.release(), guarded and null-reset
  • both paths wrapped in try/catch logging through FileLog, so a vendor quirk can't take down a call

android.permission.WAKE_LOCK and android.permission.ACCESS_WIFI_STATE are already declared in the manifest — no manifest change needed.

Scope

This is not a legacy-Android issue: the measurements are from Android 15 (SDK 35), and WIFI_MODE_FULL_LOW_LATENCY is the modern mechanism, introduced in Android 10 as an extension to the Wi-Fi lock API (WIFI_MODE_FULL_HIGH_PERF is the older one, API 12).

AOSP documents low-latency mode as active when all of these hold:

  1. "Wi-Fi is enabled and the device has internet access"
  2. "The app has created and acquired a Wi-Fi lock, and is running in the foreground"
  3. "The screen is on"

— and states the framework explicitly disables power save while the mode is active.

Condition 3 was verified here: with the lock continuously held, turning the screen off moved the average from 4.06 ms to 12.0 ms and the tail back to 184 ms. So the change helps speakerphone, video and screen-on voice calls; a call held to the ear with the display off remains subject to that platform restriction.

Testing status

Please treat this as needing review from someone with the build environment. The patch was written and reviewed against the current master (base blob 0f5e7be), checked for import availability and brace balance, but I have not compiled or run it — I don't have the SDK/NDK toolchain for this project set up. The measurements above come from instrumenting the released build (12.9.2) and comparing it against Google Meet, not from a build of this branch.

All measurements are from a single device on a single network. AOSP notes vendors must implement WLAN driver and HAL support for low-latency mode, so the size of the improvement may vary by manufacturer; the missing-lock finding itself is an app code path and is device-independent.

VoIPService holds a PARTIAL_WAKE_LOCK for the CPU but nothing for the
Wi-Fi radio, so the chipset stays in power save for the duration of a
call. Downlink packets arriving while the radio dozes are buffered by
the AP until the next beacon/DTIM, adding up to ~120ms.

The effect is masked while speech is continuous, because the packet
flow itself keeps the radio awake. It surfaces in DTX gaps: the first
packets after a pause arrive late and the jitter buffer expands, which
the user hears as lag that accumulates over the call.

Measured on a Xiaomi 12T Pro (Android 15) over 802.11ax 5GHz at
-52dBm, pinging the phone from the AP controller at 500ms intervals:

  no lock, idle          avg 56.3ms   max ~120ms
  no lock, in call       avg 13.3ms   max 192ms
  lock held, in call     avg  3.9ms   max  10ms

Google Meet already takes this lock (VideoChatWifiLock, type=4) and
shows the last row on the same link.

Note that WIFI_MODE_FULL_LOW_LATENCY is only effective while the app
is in the foreground with the screen on, so this helps speakerphone,
video and screen-on voice calls; a call held to the ear with the
display off is subject to that platform restriction.
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.

1 participant