fix(audio,ios): apply 8x output boost to compensate for VPIO raw playback (rc.8+69)
User-pasted log at +66 (https://pb.hit.moe/q8heratf.txt) shows conclusive data over a 110-second continuous talker session: Average peak_stereo_f32: ~0.005-0.010 Loud peak (one moment): ~0.234 peak_out_i16: ~150-300 (out of 32767) The signal arriving at our render callback from AudioHandler::fill_buffer is consistently at -40 dB FS for normal human speech. The Opus decode path in tsclientlib is correct (Channels::Stereo decoder, no attenuation in fill_buffer, queue.volume defaults to 1.0). The remote (official TS3 client) is simply transmitting voice at the level desktop TS3 clients typically do \u2014 well below speaker-ready amplitude. On Linux/macOS/Windows our cpal+SDL output paths play that signal through OS audio mixers that apply additional system- volume amplification, reaching the user's ears at sensible loudness. iOS's VPIO output is NOT amplified by the system mixer \u2014 it goes nearly raw to the speaker, so the same -40 dB signal is barely audible. Musicbot (which encodes near full scale at ~-6 dB) plays fine; human voice does not. Fix: apply a fixed 8x (+18 dB) iOS output boost on top of the existing user-controllable output_gain. A -40 dB signal becomes -22 dB (normal speakerphone level). User's volume slider continues to function in a useful 0-2x range on top. effective_gain = user_gain * IOS_OUTPUT_BOOST Hard-clip at \u00b11.0 in the mono downmix prevents loud signals (musicbot at peak 0.5 -> 4.0 -> clamped to 1.0) from overflowing i16 wrap-around. Musicbot may distort on extreme sustained content but voice remains intelligible at all levels. Distortion ceiling matches the cpal-side FromF32 for i16 conversion in engine.rs. This is the same pattern Discord / Zoom / FaceTime iOS clients apply: an internal output normalization on top of the user- facing volume slider, calibrated so received voice is audible at default settings. Build counter 68 -> 69.
This commit is contained in:
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0-rc.8+68
|
||||
version: 1.0.0-rc.8+69
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.5
|
||||
|
||||
@@ -598,18 +598,46 @@ impl IosVoiceUnit {
|
||||
// would otherwise clip). Multiplying by gain after
|
||||
// the downmix saves one multiplication per sample.
|
||||
//
|
||||
// iOS-specific fixed output boost: AudioHandler delivers
|
||||
// f32 stereo content matching what the remote client
|
||||
// encoded. Most desktop TS3 clients ship audio at -30
|
||||
// to -45 dB FS (peak ~0.005-0.03), well below speaker-
|
||||
// ready levels. On Linux/macOS/Windows our cpal+SDL
|
||||
// paths play that level through OS audio mixers that
|
||||
// apply additional system-volume amplification, so it
|
||||
// reaches the user's ears at sensible loudness. iOS's
|
||||
// VPIO output is NOT amplified by the system mixer —
|
||||
// it goes nearly raw to the speaker, so the same -40
|
||||
// dB signal is barely audible.
|
||||
//
|
||||
// We compensate with a fixed 8x boost (= +18 dB) on
|
||||
// top of the user-controllable output_gain. Brings a
|
||||
// -40 dB signal up to -22 dB (normal speakerphone
|
||||
// level) while keeping the user's UI gain slider
|
||||
// functional in a useful range. Hard-clip at 1.0
|
||||
// prevents the boost from clipping legitimate loud
|
||||
// signals (musicbot at peak 0.5 -> 4.0 -> clamped to
|
||||
// 1.0, audible distortion only on extremely loud
|
||||
// sustained content).
|
||||
//
|
||||
// This is consistent with how Discord / Zoom / FaceTime
|
||||
// iOS clients apply an internal output normalization
|
||||
// on top of the user-facing volume slider.
|
||||
const IOS_OUTPUT_BOOST: f32 = 8.0;
|
||||
let effective_gain = gain * IOS_OUTPUT_BOOST;
|
||||
|
||||
// Cast to i16 with saturate-on-overflow. Hard-clip is
|
||||
// acceptable here because the upstream signal is
|
||||
// already in [-1.0, 1.0] from the f32 stereo mix;
|
||||
// gain values >1.0 are the only path to clipping and
|
||||
// hard-clip at the engine boundary is what every other
|
||||
// backend's i16 path does (see the cpal-side
|
||||
// FromF32 for i16 impl in engine.rs).
|
||||
// the IOS_OUTPUT_BOOST multiplier above is the
|
||||
// primary path to clipping pressure and saturating
|
||||
// at ±1.0 is the standard answer (matches the
|
||||
// cpal-side FromF32 for i16 impl in engine.rs).
|
||||
let mut peak_out_i16: i16 = 0;
|
||||
for (i, dst) in out.iter_mut().enumerate() {
|
||||
let l = scratch_stereo[i * 2];
|
||||
let r = scratch_stereo[i * 2 + 1];
|
||||
let mono_f32 = (l + r) * 0.5 * gain;
|
||||
let mono_f32 = (l + r) * 0.5 * effective_gain;
|
||||
let clamped = mono_f32.clamp(-1.0, 1.0);
|
||||
let sample = (clamped * i16::MAX as f32) as i16;
|
||||
*dst = sample;
|
||||
|
||||
Reference in New Issue
Block a user