-
Notifications
You must be signed in to change notification settings - Fork 7
MAIN_VS_DEV_COMPARISON
| Aspect | Main Branch | Dev Branch |
|---|---|---|
| Audio Backend | PulseAudio | ALSA (direct) |
| Sync Error | None (~0ms) | ~200ms constant |
| Playback Timing | Wrong times | Correct times |
| Resampler | ResamplingAudioSampleSource |
UnifiedPolyphaseResampler |
| Rate Conversion | None (48kHz → 48kHz) | Configurable (48kHz → 192kHz) |
| Output Format | Float32 | S16/S24/S32 (configurable) |
Testing with Native Rate mode (48kHz → 48kHz, no rate conversion) still shows ~200ms sync error. This proves the UnifiedPolyphaseResampler is not the cause.
Main branch uses PulseAudio:
- PulseAudio handles its own timing internally
- Has ~15ms timing jitter (which we compensate for with wider deadbands)
- Uses
pa_simple_write()- blocking write to PulseAudio server - PulseAudio manages the buffer and timing
Dev branch uses ALSA directly:
- ALSA provides raw hardware access
- We get actual buffer sizes via
snd_pcm_get_params() - Uses
snd_pcm_writei()- blocking write to hardware - We are responsible for timing
Both branches push audio to the output:
Source.Read() → Resampler → Player.Write()
Neither is pull-based like windowsSpin's WASAPI implementation.
// Write to PulseAudio
SimpleWrite(_paHandle, (IntPtr)ptr, (UIntPtr)(samplesRead * sizeof(float)), out var error);- Uses
pa_simple_*API - Buffer: 50ms target (BufferMs = 50)
- Latency query:
pa_simple_get_latency() - Format: Float32 only
// Write to ALSA
AlsaNative.WriteInterleaved(pcmHandle, (IntPtr)ptr, (nuint)frames);- Uses
snd_pcm_*API - Latency: 50ms target (TargetLatencyUs = 50000)
- Actual latency: Queried via
snd_pcm_get_params() - Format: Configurable (S16/S24/S32)
sourceFactory: (buffer, timeFunc) =>
{
var source = new BufferedAudioSampleSource(buffer, timeFunc);
// Wrap with resampling for smooth playback rate adjustment (±4%)
return new ResamplingAudioSampleSource(
source,
buffer,
_loggerFactory.CreateLogger<ResamplingAudioSampleSource>());
}sourceFactory: (buffer, timeFunc) =>
{
IAudioSampleSource source = new BufferedAudioSampleSource(buffer, timeFunc);
var targetRate = outputFormat?.SampleRate ?? buffer.Format.SampleRate;
// Unified polyphase resampler handles both rate conversion and sync adjustment
return new UnifiedPolyphaseResampler(
source,
buffer.Format.SampleRate,
targetRate,
buffer,
_loggerFactory.CreateLogger<UnifiedPolyphaseResampler>());
}| Aspect | Main: ResamplingAudioSampleSource | Dev: UnifiedPolyphaseResampler |
|---|---|---|
| Algorithm | Linear interpolation | Polyphase FIR (Kaiser sinc) |
| Rate Conversion | None | Yes (any ratio) |
| Sync Adjustment | ±4% playback rate | ±4% playback rate |
| Quality | Basic (OK for ±4%) | High (needed for 4x upsampling) |
| Buffer | 8192 frames | Variable (based on quality) |
PulseAudio has its own timing correction. When we write to PulseAudio:
- PulseAudio buffers the audio
- PulseAudio may adjust timing internally
- Our sync error calculation doesn't account for PulseAudio's internal buffer
In contrast, ALSA gives us raw hardware access:
- We write directly to the hardware buffer
- ALSA reports actual buffer sizes
- Our OutputLatencyMs calculation is based on actual hardware
Main (PulseAudio):
var latencyUs = SimpleGetLatency(_paHandle, out var latencyError);
OutputLatencyMs = (int)(latencyUs / 1000);Dev (ALSA):
var getResult = AlsaNative.GetParams(_pcmHandle, out var actualBufferSize, out var actualPeriodSize);
OutputLatencyMs = AlsaNative.CalculateLatencyMs(actualBufferSize, (uint)actualSampleRate);The ALSA latency might be reported differently than PulseAudio's latency.
PulseAudio's pa_simple_write() vs ALSA's snd_pcm_writei() may have different blocking behavior:
- PulseAudio: Blocks until the server accepts the data (into PA's buffer)
- ALSA: Blocks until the hardware buffer has room
This could cause a timing offset between when we think audio plays vs when it actually plays.
You mentioned main "has other issues of playing audio at the wrong times". This is interesting because:
- Main has no sync error (error ≈ 0ms)
- But audio plays at wrong times
- Dev has sync error (~200ms)
- But... does audio play at the RIGHT times in dev?
Question: Is the ~200ms sync error in dev actually causing audio to play LATE by 200ms? Or is the sync error measurement wrong?
The SDK's sync calculation might not be accounting for our OutputLatencyMs properly. Check if:
// Sync error should include output latency offset
effectiveSyncError = measuredSyncError - outputLatencyMsCheck if the IClockSynchronizer.OutputLatencyMs property is being set correctly in both branches.
Try using PulseAudio in dev branch (just change BackendFactory) to see if the sync error disappears. This would confirm it's a backend timing issue.
Add logging to measure when audio actually reaches the speakers (using external measurement) vs when we think it does.
-
src/MultiRoomAudio/Audio/PulseAudio/PulseAudioPlayer.cs(still exists but not used)
src/MultiRoomAudio/Audio/Alsa/AlsaPlayer.cssrc/MultiRoomAudio/Audio/Alsa/AlsaNative.cssrc/MultiRoomAudio/Audio/Alsa/AlsaBackend.cssrc/MultiRoomAudio/Audio/Alsa/AlsaDeviceEnumerator.cssrc/MultiRoomAudio/Audio/Alsa/AlsaCapabilityProbe.cssrc/MultiRoomAudio/Audio/UnifiedPolyphaseResampler.cssrc/MultiRoomAudio/Audio/BitDepthConverter.cssrc/MultiRoomAudio/Audio/BackendFactory.cssrc/MultiRoomAudio/Audio/IBackend.cs
-
src/MultiRoomAudio/Services/PlayerManagerService.cs- Backend abstraction, output format config -
src/MultiRoomAudio/Services/ConfigurationService.cs- Added NativeRate, OutputSampleRate, OutputBitDepth -
src/MultiRoomAudio/Models/PlayerConfig.cs- Added NativeRate, OutputFormat
An A/B test option has been added to compare the two resamplers:
- In the "Add Player" dialog, check "Native Rate (no upsampling)"
- This enables the "Use Simple Resampler (A/B test)" checkbox
- Check it to use the main branch's linear resampler instead of the polyphase resampler
Test Procedure:
- Create a player with Native Rate ON, Simple Resampler OFF → expect ~200ms sync error
- Create a player with Native Rate ON, Simple Resampler ON → if sync error disappears, confirms it's the polyphase resampler
- A/B test: Use the new checkbox to compare resamplers (see above)
- Deep dive: Compare how OutputLatencyMs is used in sync calculation
- External validation: Use external tool to measure actual audio latency vs calculated
Getting Started
Installation
Configuration
Development
Links