This document describes the current protocol v3 desktop proof. The separate
bad-network redesign tranche lives in docs/specs/bad-network-transport.md and
is allowed to introduce a future protocol v4 or equivalent wire break.
The TX CD+G source/memory slimdown work is documented separately in
docs/specs/tx-cdg-source-model.md. That tranche is intended to preserve the
current packet families while changing how TX stores and reads CD+G data.
- Wi-Fi-first, UDP multicast/broadcast-friendly transport
- portable binary framing for desktop and future embedded receivers
- live network audio plus live timed CD+G packets
- late-join support without requiring retransmit
- explicit pause-state signaling and fast video recovery anchors
- explicit versioning so protocol evolution stays parseable
Every datagram starts with the fixed header defined in proto/include/dashcdg/protocol.h.
Header fields:
magic:DKG1version: protocol version, currently3type: packet discriminatorflags: transport flags; todayDASHCDG_PACKET_FLAG_PAUSEDis used to advertise paused playback statesequence: sender-wide monotonically increasing datagram sequencesender_time_ms: sender monotonic time in millisecondspayload_length: payload size in bytesreserved: currently unused
Important framing constants:
DASHCDG_MAX_PACKET_SIZE = 1400DASHCDG_MAX_ASSET_CHUNK = 1024DASHCDG_MAX_AUDIO_FRAME_BYTES = 255DASHCDG_MAX_CDG_BATCH_PACKETS = 6DASHCDG_MAX_CDG_SNAPSHOT_CHUNK = 1024DASHCDG_MAX_FEC_PAYLOAD_BYTES = 255DASHCDG_SUBCHANNEL_PACKET_BYTES = 24
Purpose:
- session discovery
- late-join session metadata
- live media configuration
Fields:
song_idasset_sizechunk_sizepackets_per_secondaudio_sample_rateaudio_channelsaudio_frame_msaudio_bitrate_kbpsplayout_delay_msaudio_fec_group_sizecdg_fec_group_sizesession_start_ms
Current desktop TX behavior:
- always sent periodically
- advertises live audio metadata when the current track has an
.mp3 - advertises
0audio fields for CDG-only tracks - RX treats a fresh announce as a session re-anchor on track/session changes so playout does not wait for slow clock re-convergence alone
Purpose:
- late-join bootstrap of the full CD+G asset
Fields:
asset_offsetchunk_lengthchunk_bytes
Current desktop behavior:
- TX repeatedly loops the entire CD+G file in 1024-byte slices
- RX accepts out-of-order and duplicate chunks
- RX declares
readyonce it has the full asset and can seek deterministically - current TX sources those slices from a random-access CDG source
- headless/default TX may use a file-backed source; preview mode still has a memory-backed fallback
Slimdown target:
ASSET_CHUNKstays byte-addressed on the wire- TX should eventually stop needing the preview-only whole-memory fallback for deterministic local preview/seek behavior
Purpose:
- coarse session/playback update
- late-join asset availability reporting
Fields:
song_idsession_start_msplayback_msavailable_asset_bytestotal_asset_bytes
Current desktop behavior:
- TX emits frequent beacons during playback
- RX uses
sender_time_msplus the local offset estimator more heavily than the beaconplayback_msfield - TX also emits paused-state beacons during pause mode so RX can keep a healthy session view without advancing media
Purpose:
- live Opus audio transport
Fields:
media_sequencegroup_idgroup_indexframe_msencoded_lengthplayback_msencoded_bytes
Current desktop behavior:
- TX produces 48 kHz mono Opus frames incrementally on a dedicated audio thread
- a bounded
audio_ready_queueis the handoff boundary between audio production and packet pacing - TX sends 20 ms audio frames
- RX decodes frames into a queue-driven PortAudio stream
- RX uses the announced playout delay to decide when to start the stream
- desktop RX network mode uses network audio only; it no longer falls back to local-file MP3 playout
Threaded-runtime target:
- TX audio production moves to incremental decode/resample/encode rather than whole-track prebuild
media_sequence,group_id, andgroup_indexremain strictly monotonic on the wire even when frames are produced just ahead of playout deadlines- bounded producer queues, not whole-track arrays, become the pacing contract between TX audio production and TX network send
Purpose:
- live timed CD+G transport
Fields:
media_sequencegroup_idgroup_indexpacket_countreservedpacket_start_indexpacket_bytes
Current desktop behavior:
- each batch carries up to 6 raw CD+G subchannel packets
- TX timestamps batches by the source CD+G packet index converted into milliseconds
- RX applies batches in order to a live
dashcdg_cdg_state - current TX prebuilds all batches for a track in
cdg_batches
Stage A implementation note:
cdg_batchesnow stores metadata only; payload bytes are read from the canonical CDG source at send/FEC time
Threaded-runtime target:
- live CDG batches are produced from a timeline-driven video task and handed to the network sender through a bounded queue or equivalent publish boundary
- RX video progression and RX bootstrap progression become independent subsystems
Slimdown target:
- batch scheduling metadata can stay precomputed, but payload bytes should be read from the canonical CDG source at send time
- later source work should make file-backed and memory-backed paths equally deterministic for all TX use cases, including preview
Purpose:
- bounded late-join video state keyframe
- immediate live framebuffer bootstrap before the full asset is rebuilt
Fields:
snapshot_idpacket_indextotal_bytessnapshot_offsetchunk_lengthsnapshot_bytes
Current desktop behavior:
- TX periodically serializes the current live
dashcdg_cdg_stateinto bounded 1024-byte chunks - TX aligns each snapshot to the next live
CDG_BATCHpacket index boundary - RX reassembles the snapshot, restores the live framebuffer/palette state, and resumes from the aligned live
CDG_BATCHpacket index - the full
ASSET_CHUNKreplay still continues in parallel so deterministic seek/bootstrap completes in the background - RX can also apply snapshots mid-session while paused or recovering, not just before bootstrap completion
Threaded-runtime target:
- snapshots are a recovery and fast-start input to the RX video task
- snapshot failure or rejection must not permanently block later live video progression
Purpose:
- sender clock sync marker
Fields:
sync_idreserved
Current desktop behavior:
- emitted by TX
- observed by RX to refine the remote/local offset estimate
Purpose:
- send the explicit origin timestamp associated with a sync marker
Fields:
sync_idreservedorigin_time_ms
Current desktop behavior:
- emitted by TX immediately after
PTP_SYNC - observed by RX
- RX discards stale sync exchanges that exceed the current bounded age window rather than poisoning the clock estimate after delayed track loads
Purpose:
- reserved for future round-trip path-delay measurement
Fields:
request_idreserved
Current implementation status:
- parser/serializer exists
- RX now emits it after matching
PTP_SYNCplusPTP_FOLLOW_UP - TX listens for it and answers with
PTP_DELAY_RESP
Purpose:
- reserved for future reply carrying receiver-observed timing
Fields:
request_idreservedrequest_rx_time_ms
Current implementation status:
- parser/serializer exists
- TX emits it in response to
PTP_DELAY_REQ - RX consumes it and updates sender offset/path-delay estimates
- TX now timestamps the response after acquiring its main mutex, reducing stale timing caused by track-load stalls
- RX discards stale delayed responses that arrive outside the bounded exchange window
Purpose:
- bounded parity repair for short audio and CD+G media groups
Fields:
stream_typegroup_sizepayload_bytesreservedgroup_idpayload_length_xorreserved_bpayload_xor
Current implementation status:
- parser/serializer exists
- TX emits one XOR parity packet per completed audio/CD+G FEC group
- RX stores short per-group history and can recover a single missing media payload when parity and the other group members arrive
- audio sample rate:
48000 - channels:
1 - Opus frame duration:
20 ms - Opus bitrate target:
80 kbps - announced playout delay:
500 ms - announced audio FEC group size:
5 - announced CDG FEC group size:
9 - CD+G cadence:
300 packets/second - TX warmup before a new session:
1000 ms - CDG snapshot interval:
1000 ms
Desktop endpoint defaults:
- TX and RX default to
239.255.77.77:24684 V4_RX_STATSand PTP use the primary multicast group on the stats port (default239.255.77.77:24685) so all receivers can observe peersV4_REPAIR_NACKuses multicast239.255.77.78on the same stats port when unicast to the encoder is unavailable (TX joins this group in multicast mode); embedded senders prefer unicast to the last seen media/repair source address- desktop tools also accept explicit IPv4 broadcast endpoints such as
192.168.0.255
The current desktop receiver uses both bootstrap and live paths:
ANNOUNCEprepares or resets session stateASSET_CHUNKrebuilds the full CD+G asset for deterministic late joinCLOCK_BEACON,PTP_SYNC,PTP_FOLLOW_UP,PTP_DELAY_REQ, andPTP_DELAY_RESPmaintain a bounded sender/local offset estimate and path-delay estimateAUDIO_FRAMEfeeds the Opus decoder and PortAudio queueCDG_BATCHadvances the live CD+G stateCDG_SNAPSHOTcan seed or replace the current live visual stateFEC_PARITYcan recover a single missing media payload inside a protected group
RX startup is now network-audio-only for desktop network mode. When ANNOUNCE advertises audio metadata, RX initializes the Opus/PortAudio path and keeps a bounded pending queue for reordered AUDIO_FRAME and CDG_BATCH packets before declaring them late. Current desktop HUD/stdout observability also exposes startup gate state, clock step/peak/holdover data, and current FEC repair hotness.
Threaded-runtime target:
- socket receive parses packets and publishes typed packet events without waiting on render/UI work
- PTP observations feed a dedicated clock-discipline task
- bootstrap replay, audio progression, and live video progression advance independently
- render consumes immutable published frame snapshots rather than mutating receiver transport state directly
The current proof supports late join by combining:
- repeated
ANNOUNCE - continuous asset replay through
ASSET_CHUNK - periodic
CLOCK_BEACON - periodic
CDG_SNAPSHOTstate keyframes - ongoing live
AUDIO_FRAMEandCDG_BATCHtransmission
In practice, RX can start live audio before the bootstrap asset is complete, can show live video from a snapshot before deterministic asset rebuild is done, and only asserts full ready once the complete CD+G asset has been rebuilt.
- no authenticated control plane
- no retransmit, NACK, or repair-request path
- no playlist/session catalog packet family
- no hardware-timestamp or sub-millisecond PTP discipline yet; current proof uses millisecond software timestamps
- bounded XOR FEC only repairs a single missing media payload per group; it is not a long-window or burst-loss code
- audio recovery still relies on playout delay and bounded FEC rather than a separate audio-state snapshot/keyframe model
- no wire-level compatibility promise for protocol v1 peers
- the threaded-runtime queue ownership model is a desktop architecture refactor and does not change protocol v3 packet formats by itself
- TX no longer requires a whole-memory
.cdgpreload for default wire send; Stage A and the initial source abstraction are in place, but preview-mode memory fallback and later source-layer polish remain tracked indocs/specs/tx-cdg-source-model.md - the bad-network redesign work now intentionally targets a separate transport tranche rather than stretching v3 indefinitely
These are intentional current omissions, not undocumented behavior.