Skip to content

Improve voice system. Fix long-standing and recent bugs (stutter, loss of speech) - #5239

Merged
Dutchman101 merged 1 commit into
multitheftauto:masterfrom
Dutchman101:voice-stability-1
Aug 20, 2026
Merged

Improve voice system. Fix long-standing and recent bugs (stutter, loss of speech)#5239
Dutchman101 merged 1 commit into
multitheftauto:masterfrom
Dutchman101:voice-stability-1

Conversation

@Dutchman101

@Dutchman101 Dutchman101 commented Aug 19, 2026

Copy link
Copy Markdown
Member

Voice: fixes for the DoS-hardening regressions, plus the long-standing voice bugs

This is a follow-up for 9e28567, to fix voice stuttering and intermittent loss of speech. The hardening was good, but it introduced some user-visible problems, and while fixing those I also made fixes for bugs that have been in the voice code for years. The hardening's protections stay intact - where they dropped things, we now queue them instead.

Part 1: improvements over the DoS hardening commit (9e28567)

  • No more dropped audio from the decode cap. The hardening limits the client to 6 Speex decodes per rendered frame so a packet flood can't eat your CPU. The problem: packets past that limit were just thrown away, which made relayed bursts sound choppy. This change queues the overflow (bounded to 64 frames, oldest recycled if it ever fills) and drains it on later frames, so the CPU stays bounded and the audio isn't lost.

  • No more fake stop/start events mid-sentence. The hardening's dropped packets starve the playback buffer, which fires a BASS "stall" and the client immediately reported onClientPlayerVoiceStop - then a start again as soon as packets resumed. Scripts saw constant flicker. The stop is now only reported after 250ms of both audio silence and packet silence, and only once every queued frame has been decoded. (To be fair: the immediate-stop-on-stall behavior itself is old - 2018 - but the cap turned a rare glitch into a constant one, and this fix handles both.)

  • Reworked the hardening's own additions so they cooperate with the queue: the packet timestamp is stamped before the uniform-byte skip (so silent-but-held push-to-talk still counts as "talking"), and the per-frame decode counter became a shared budget between packet intake and the queue drain, with the drain getting first pick.

Part 2: "Long-standing voice bugs" (General improvements)

Sender side (the player talking):

  • Voice stuttering / garbled audio. The recording ring buffer kept its read/write positions in sync using a fixed-size advance that didn't match how much audio had actually been overwritten. Over a long talk session the positions drifted and the client re-encoded the wrong segments. The read position now advances by exactly what was overwritten, with a flag that tracks when the buffer is exactly full.
  • Cut-off word tails on key release. Audio is only sent every 100ms. If you released the key between sends, the remaining audio sat unsent and only got flushed on the next press. Releasing now flushes the buffer immediately, and only the genuinely half-finished partial frame is discarded.
  • Server stuck in "transmitting". The voice-end packet was unreliable. If it was lost in transit, the server never reset the speaker's state, scripts never got onClientPlayerVoiceStop, and the next press was treated as a continuation. The end packet is now sent reliably.
  • PTT toggle doing nothing after release. During the tiny drain window getVoicePTTState reported "talking", so pressing toggle "turned it off" instead of on. It now reports active only while actually recording, and re-pressing during the drain correctly continues the burst.

Listener side (the player hearing):

  • Volume bug from 2016. The user's volume scale was multiplied into m_fVolume at creation and again at play time, so setting volume back to 1.0 never restored full volume. The base value now stays neutral and the scale is applied only at play time.
    Playback speed set to zero. m_fDefaultFrequency was never initialized (it sat at zero since 2016), so SetPlaybackSpeed multiplied against 0. It's now seeded from the sample rate and read back from BASS as a fallback.

  • Canceled start events didn't actually mute. If a script canceled onClientPlayerVoiceStart, the client just returned for that one packet and re-asked for the next one, so a muted player kept triggering the event and kept being fed into BASS. Canceling now silences the whole burst and drops the frames already queued.

  • BASS queue capped. The push stream now has a one-second limit, so a stalled or flooded stream can't grow memory without bound.

  • Safe teardown. DeInit is guarded so a half-initialized voice object can't crash on cleanup.

Server side:

  • max_voice_packets_per_interval default 32 > 64. (Note: this one isn't a bug fix over 9e28567 - the server-side packet cap came from an earlier hardening change. It's compensation: the new release-flush legitimately bursts past the old limit, and without the raise the server would discard the exact tail we're trying to keep. The cap still exists and still bounds floods.)

Independent files:

  • ConvertFileToMono now handles zero-byte decodes and a wrong success check on BASS_StreamPutData (0 is a valid result, not an error), so bad sound files fail cleanly instead of creating broken silent streams.

Testing

  • All scenarios tested with multiple clients

Fix voice stuttering and intermittent loss of speech. Various other improvements as well (see PR desc)
@FileEX FileEX added the bugfix Solution to a bug of any kind label Aug 19, 2026
@FileEX FileEX added this to the 1.7 (Current) milestone Aug 19, 2026
@Dutchman101
Dutchman101 merged commit a452a49 into multitheftauto:master Aug 20, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Solution to a bug of any kind

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants