Android: popping is still audible on the release build, which carries no underrun counter #203

Open
opened 2026-08-22 17:17:51 +00:00 by logan · 0 comments
Collaborator

Report

Popping is still audible on Android during playback, on the most recent
release build. It is less obvious than what #135 described — that
was static; this is occasional — so #135's work improved it without
settling it.

Reported by ear, from the device, by the person holding it. Nothing in
this issue has been reproduced by an agent, and no tier here can hear a
phone.

Findings

The release build cannot answer the question #135 answered. #135
was closed on a measurement — zero underruns, in steady playback and
under a full index build — taken with the counter added by PR #191.
That counter is not in v0.5.0:

v0.5.0 tagged           2026-08-21 16:47:46 +0000
842fe47 "feat(player): count what the ring buffer misses"
                        2026-08-21 20:30:32 +0000
$ git merge-base --is-ancestor 842fe47 v0.5.0   ->  NO
$ git show v0.5.0:backend/player/buffered_streamer.go | grep -c UnderrunStats
0

So the build being listened to has no underrun counter and emits no
audio underrun line at all. "Measured at zero underruns" is a
statement about a debug build of main, and it is not a statement
about v0.5.0. Candidate 1 of #135 is not eliminated for this build;
it was never tested on it.

Nothing needs to change for it to become answerable. The instrument
is in an untagged file and logs at Info deliberately, so that a
build a phone actually runs can be read — the reasoning is in
reportUnderrunsLocked's comment. And make android-logs filters on
the fixed yellowjacket tag rather than one derived from the package
id, so it reads the release app as well as the .dev one. A
release cut from current main, or a debug build of it, reports this
with no code change.

The two builds differ in several ways that all bear on a real-time
deadline, and none of them were controlled for.
Listing them without
claiming a direction, because each could cut either way:

  • debuggable vs not — ART runs a debuggable process differently, and
    the release build is the AOT-optimised one;
  • attached to USB and charging vs on battery — which decides the CPU
    governor, and #135's measurement had to be attached, since reading
    the counter means logcat;
  • screen on with a debugger attached vs in a pocket with the screen
    off, where a mediaPlayback foreground service is the only thing
    keeping the process scheduled at all.

The third is the one worth trying first: it is the normal way this app
is used and the least like the conditions #135 verified in.

The two live candidates from #135 are unchanged and both are the same
TODO.
Candidate 2 is the sample rate — the speaker opens at 44100
while the device runs at 48000, so everything is resampled twice and
the stream opens as OpenSL ES rather than AAudio (#194, measured
with dumpsys audio, Reviewed/Confirmed). Candidate 3 is the device
buffer: InitSpeaker's comment says 200 ms, beep halves it, so Oboe
gets ~100 ms, and oto's own documentation for that option says to raise
it to "reduce noises". It has never been tuned per platform.

#194's framing needs revisiting because of this. It says in as many
words that it is "an efficiency and latency report, not a defect anyone
can hear, and it should be judged on those terms". That sentence rests
on #135's listening test, which was done on a build that predates the
instrument and under the conditions above. Someone can hear
something.

Direction

Measure on a build that can answer, before changing anything — which is
#135's own Direction, applied to the build in question rather than to
main.

  1. Install a build from current main (release or debug — the log line
    is Info and the tag is fixed either way) and listen until it pops.
    make android-logs while it is audible.
  2. Listen on battery, screen off, in a pocket first, then attach.
    Attaching is what the measurement requires and is itself one of the
    variables; if it only pops unattached, that is the finding, and the
    counter still reports cumulatively so the run is visible when you
    plug in afterwards.
  3. If runs is non-zero while it pops, it is candidate 1 after all and
    the levers are candidate 3's, in order of cost: raise the speaker
    buffer per platform (a phone can afford latency a desktop player
    cannot), raise the 2 s read-ahead, and consider holding the last
    sample instead of jumping to zero — which turns a step
    discontinuity into a much less audible artifact.
  4. If it is still zero, candidate 1 is out for this build too and
    the cause is below us. #194 becomes the prime suspect rather than an
    efficiency report: the double resampling and the OpenSL ES path are
    in the layer whose buffer is being missed.
  5. Whatever changes, judge it against the counter. That is the whole
    reason it exists (#194's Direction says the same).

Not a reopen of #135, deliberately. That issue's leading hypothesis
was disproven by measurement and its record — the instrument, two
measured conditions and a listening test — is worth keeping intact and
findable rather than being reopened under a new symptom. What it did
not establish, and what this issue is, is whether any of that holds for
the build people install.

**Report** Popping is still audible on Android during playback, on the most recent release build. It is **less obvious than what #135 described** — that was static; this is occasional — so #135's work improved it without settling it. Reported by ear, from the device, by the person holding it. Nothing in this issue has been reproduced by an agent, and no tier here can hear a phone. **Findings** **The release build cannot answer the question #135 answered.** #135 was closed on a measurement — zero underruns, in steady playback and under a full index build — taken with the counter added by PR #191. That counter is **not in v0.5.0**: ``` v0.5.0 tagged 2026-08-21 16:47:46 +0000 842fe47 "feat(player): count what the ring buffer misses" 2026-08-21 20:30:32 +0000 $ git merge-base --is-ancestor 842fe47 v0.5.0 -> NO $ git show v0.5.0:backend/player/buffered_streamer.go | grep -c UnderrunStats 0 ``` So the build being listened to has no underrun counter and emits no `audio underrun` line at all. "Measured at zero underruns" is a statement about a debug build of `main`, and it is not a statement about v0.5.0. **Candidate 1 of #135 is not eliminated for this build**; it was never tested on it. **Nothing needs to change for it to become answerable.** The instrument is in an untagged file and logs at **Info** deliberately, so that a build a phone actually runs can be read — the reasoning is in `reportUnderrunsLocked`'s comment. And `make android-logs` filters on the fixed `yellowjacket` tag rather than one derived from the package id, so it reads the **release** app as well as the `.dev` one. A release cut from current `main`, or a debug build of it, reports this with no code change. **The two builds differ in several ways that all bear on a real-time deadline, and none of them were controlled for.** Listing them without claiming a direction, because each could cut either way: - debuggable vs not — ART runs a debuggable process differently, and the release build is the AOT-optimised one; - attached to USB and charging vs on battery — which decides the CPU governor, and #135's measurement *had* to be attached, since reading the counter means logcat; - screen on with a debugger attached vs in a pocket with the screen off, where a `mediaPlayback` foreground service is the only thing keeping the process scheduled at all. The third is the one worth trying first: it is the normal way this app is used and the least like the conditions #135 verified in. **The two live candidates from #135 are unchanged and both are the same `TODO`.** Candidate 2 is the sample rate — the speaker opens at 44100 while the device runs at 48000, so everything is resampled twice and the stream opens as OpenSL ES rather than AAudio (**#194**, measured with `dumpsys audio`, `Reviewed/Confirmed`). Candidate 3 is the device buffer: `InitSpeaker`'s comment says 200 ms, beep halves it, so Oboe gets ~100 ms, and oto's own documentation for that option says to raise it to "reduce noises". It has never been tuned per platform. **#194's framing needs revisiting because of this.** It says in as many words that it is "an efficiency and latency report, not a defect anyone can hear, and it should be judged on those terms". That sentence rests on #135's listening test, which was done on a build that predates the instrument and under the conditions above. Someone *can* hear something. **Direction** Measure on a build that can answer, before changing anything — which is #135's own Direction, applied to the build in question rather than to `main`. 1. Install a build from current `main` (release or debug — the log line is Info and the tag is fixed either way) and listen until it pops. `make android-logs` while it is audible. 2. **Listen on battery, screen off, in a pocket first**, then attach. Attaching is what the measurement requires and is itself one of the variables; if it only pops unattached, that is the finding, and the counter still reports cumulatively so the run is visible when you plug in afterwards. 3. If `runs` is non-zero while it pops, it is candidate 1 after all and the levers are candidate 3's, in order of cost: raise the speaker buffer per platform (a phone can afford latency a desktop player cannot), raise the 2 s read-ahead, and consider holding the last sample instead of jumping to zero — which turns a step discontinuity into a much less audible artifact. 4. If it is **still zero**, candidate 1 is out for this build too and the cause is below us. #194 becomes the prime suspect rather than an efficiency report: the double resampling and the OpenSL ES path are in the layer whose buffer is being missed. 5. Whatever changes, judge it against the counter. That is the whole reason it exists (#194's Direction says the same). **Not a reopen of #135**, deliberately. That issue's leading hypothesis was disproven by measurement and its record — the instrument, two measured conditions and a listening test — is worth keeping intact and findable rather than being reopened under a new symptom. What it did not establish, and what this issue is, is whether any of that holds for the build people install.
logan added the Area/PlayerKind/BugPlatform/Android
Priority
High
2
labels 2026-08-22 17:18:03 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yonlu/yellowjacket#203