Snapcast.Stream (Snapcast v0.2.1)

Copy Markdown View Source

Decodes one source to PCM and paces it out as timestamped WireChunks.

ffmpeg is used purely as a decoder: it floods raw signed little-endian PCM into a buffer as fast as it likes. The pacer owns the audio timeline — chunk N is stamped with start + N * chunk_ms (server clock) and emitted once realtime is within bufferMs of that play time. Because the server assigns the timestamps (not arrival order), there is no producer/consumer drift: the only requirement is to send a chunk before its play deadline, and the bufferMs lead absorbs all jitter.

source is a binary path/URL, or a 0-arity function returning one (resolved when the stream starts, e.g. for short-lived signed URLs).

Summary

Functions

Returns a specification to start this module under a supervisor.

The next timestamp this stream will use (µs), so a successor can continue the timeline.

PCM payload as snapcast expects it on the wire for the given bit depth.

Functions

attach(stream, session_pid)

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

high_water(stream)

The next timestamp this stream will use (µs), so a successor can continue the timeline.

start_link(opts)

stop(stream)

wire_pcm(pcm, arg2)

PCM payload as snapcast expects it on the wire for the given bit depth.

Snapcast has no packed 3-byte PCM format: its shared SampleFormat forces a 4-byte sample for 24-bit (common/sample_format.cpp sets sample_size_ = 4 when bits == 24) — S24_LE carried in a 32-bit word. Crucially, the client's software volume scales each 4-byte sample as an int32_t (player.cpp adjustVolume<int32_t> for sampleSize() == 4), so the 24-bit value must be sign-extended into a valid little-endian int32: the low 3 bytes carry the sample (what the audio backend reads as 24-bit), the high byte is the sign. ffmpeg emits packed 3-byte s24le, so we widen each sample to a sign-extended 32-bit word here.

Sending packed 3-byte samples while advertising 24 bits produced static (frame size mismatch); zero-padding the high byte instead produced garbled audio (negative samples read as large positive int32s once volume ≠ 1.0). 16- and 32-bit already match snapcast's sample width and pass through untouched.