Skip to content

Fix bugs and optimize latency for Sonos in airupnp#583

Open
andresgit763 wants to merge 4 commits into
philippe44:masterfrom
andresgit763:fix/airupnp-bugs-and-optimizations
Open

Fix bugs and optimize latency for Sonos in airupnp#583
andresgit763 wants to merge 4 commits into
philippe44:masterfrom
andresgit763:fix/airupnp-bugs-and-optimizations

Conversation

@andresgit763

Copy link
Copy Markdown

Summary

Bug fixes:

  • Add missing va_end(args) in HandleRAOP — called on early return and normal exit
  • Heap-allocate ProtocolInfo via strdup/asprintf (was string literals, inconsistent with asprintf in PCM path)
  • Free ProtocolInfo in DelMRDevice to prevent leak
  • Set Device->Running = true after ProtocolInfo assignment to prevent NULL deref race
  • Initialize TransitionPoll to 0 in AddMRDevice

Sonos-specific optimizations:

  • Pre-stage SetAVTransportURI at RAOP_STREAM time (was deferred to RAOP_PLAY), saving ~300ms
  • Auto-set HTTPLength = -3 (chunked) only for Sonos devices (detected via TOPOLOGY_IDX)
  • Set metadata duration to 24h instead of 1ms (prevents premature stream termination)

General improvements:

  • Fast polling (50ms × 8 ticks) after state transitions for quicker UPnP state detection
  • Default drift correction enabled
  • Volume echo suppression reduced from 1000ms to 500ms
  • Dynamic sample rate in PCM ProtocolInfo

Test plan

  • Tested on 2x Sonos Play:1 (ZPS12, firmware 86.6-75110)
  • Silence frames reduced 53% (236 → 111 avg)
  • Zero connection errors (baseline had ~3 per session)
  • Non-Sonos devices unaffected (HTTPLength default unchanged)

Related: #582

Bug fixes:
- Add missing va_end(args) in HandleRAOP (all paths)
- Heap-allocate ProtocolInfo (was string literal, leaked on strdup path)
- Free ProtocolInfo in DelMRDevice
- Set Device->Running after ProtocolInfo assignment (prevent NULL deref)
- Initialize TransitionPoll to 0 in AddMRDevice

Sonos-specific optimizations:
- Pre-stage SetAVTransportURI at STREAM time (was deferred to PLAY)
- Auto-set HTTPLength=-3 (chunked) for Sonos devices only
- Set duration to 24h instead of 1ms (prevents premature stop)
- Enable drift correction by default

General improvements:
- Fast polling (50ms x 8 ticks) after state transitions
- Reduce volume echo suppression from 1000ms to 500ms
- Dynamic sample rate in PCM ProtocolInfo
Comment thread airupnp/src/airupnp.c
switch (event) {
case RAOP_STREAM:
// a PLAY will come later, so we'll do the load at that time
case RAOP_STREAM: {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% that RAOP_STREAM is not sent multiple times in some cases. Have you verified that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it again, RECORD can arrive without a prior TEARDOWN in some edge cases. I'll add a guard so setURI only fires if the device isn't already in RAOP_STREAM state.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be really careful with these as there are many funky cases

Inspired by swyh-rs DLNA compatibility approach:

- Add DLNA.ORG_PN profile names (FLAC, WAV, LPCM) to ProtocolInfo
- Change DLNA.ORG_OP from 00 to 01 (byte-seek supported)
- Update DLNA.ORG_FLAGS to 01700000 (streaming-transfer +
  background-transfer + connection-stalling + DLNA v1.5)
- Populate sampleFrequency/bitsPerSample/channels in metadata
  so DIDL <res> element includes audio format attributes for
  all codecs, not just PCM
@andresgit763

Copy link
Copy Markdown
Author

Added a second commit with DLNA compatibility improvements inspired by swyh-rs:

  • DLNA ProtocolInfo flags: Added DLNA.ORG_PN profile names, changed OP from 00 to 01 (byte-seek), updated FLAGS to `01700000` (streaming-transfer + connection-stalling + DLNA v1.5)
  • DIDL metadata: Populate sampleFrequency/bitsPerSample/nrAudioChannels for all codecs (was PCM-only), helping renderers pre-configure decoders

Comment thread airupnp/src/airupnp.c
Device->MetaData.duration = 86400000;
Device->MetaData.title = "Streaming from AirConnect";
// Sonos supports chunked transfer encoding for FLAC/WAV
if (!Device->Config.HTTPLength || Device->Config.HTTPLength == -1) Device->Config.HTTPLength = -3;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you totally sure of that. AFAIR, old Sonos devices fail with chunked on flac

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on a Play:1 (S1 hardware) and chunked FLAC works fine. swyh-rs and your LMS-uPnP Sonos profile both default to chunked for FLAC/WAV too. The known problem is chunked MP3 specifically. But I've only tested on my own hardware so if you've seen it break on other models I trust your experience over mine.

@philippe44 philippe44 May 3, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having said that, I can't overwrite user's decision on HTTP length so I can't force it to -3 no matter what. If user wants flac, then he can add chunked.

RECORD can arrive without a prior TEARDOWN in edge cases.
Skip pre-staging setURI if the device is already in RAOP_STREAM state.
Sonos buffers a fixed number of bytes before playback begins.
FLAC's compression means fewer bytes per second, so the buffer
takes 2-3x longer to fill compared to WAV. Switching to WAV
on Sonos devices significantly reduces startup latency.

Only applies when the default codec is flac and the device is
detected as Sonos (via TOPOLOGY service). Users who override
the codec in per-device config are unaffected.
@andresgit763
andresgit763 force-pushed the fix/airupnp-bugs-and-optimizations branch from ae7be35 to eaa2ac3 Compare May 3, 2026 20:17
Comment thread airupnp/src/airupnp.c
else if (strcasestr(Device->Config.Codec, "aac")) Device->ProtocolInfo = "http-get:*:audio/aac:DLNA.ORG_PN=AAC_ADTS;DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000";
else if (strcasestr(Device->Config.Codec, "mp3")) Device->ProtocolInfo = "http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000";
else Device->ProtocolInfo = "http-get:*:audio/flac:DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000";
// set protocolinfo — flags modeled after swyh-rs for streaming compatibility

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is a very touchy topic. I had soooo many compatibility issues with protocolinfo and various players that I can't change that.

Comment thread airupnp/src/airupnp.c
true, // Metadata
"", // RTP:HTTP Latency (0 = use AirPlay requested)
false, // drift
true, // drift

@philippe44 philippe44 May 3, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is false by default and user can change it to true with -r. Such change would break compatibility with existing versions

Comment thread airupnp/src/airupnp.c
Device->MetaData.title = "Streaming from AirConnect";
// Sonos supports chunked transfer encoding for FLAC/WAV
if (!Device->Config.HTTPLength || Device->Config.HTTPLength == -1) Device->Config.HTTPLength = -3;
// Sonos buffers a fixed number of BYTES before playback starts; WAV's higher

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No you can't do that either, that would mean that user's config is overwritten, event if not silently. It is up to users to decide to use wav on their Sonos if they want lower latencies.

Comment thread airupnp/src/airupnp.c
} else {
Device->MetaData.remote_title = "Streaming from AirConnect";
}
// populate audio format metadata so DIDL includes sampleFrequency/bitsPerSample

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is needed except for raw pcm

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.

2 participants