Skip to content

v2.5.8

Choose a tag to compare

@cometdom cometdom released this 10 Jul 16:52

Added

  • DoP (DSD over PCM) output mode (--dop CLI flag, issue #80 requested by yama3kzh). For DACs that receive Diretta PCM but decode DoP natively (e.g., connected via I2S or S/PDIF passthrough at the target), --dop causes DSD streams to be transmitted over the Diretta PCM channel as standard 24-bit DoP frames instead of native Diretta DSD. Encoding: 2 DSD bytes per channel packed into a 24-bit PCM word with alternating 0x05/0xFA markers in the MSB byte (standard DoP v1.1 format). Sample rate mapping: DSD64→176.4kHz, DSD128→352.8kHz, DSD256→705.6kHz, DSD512→1.4MHz. Ring buffer operates in PCM mode (24-bit packed, silence=0x00); DoP marker state resets cleanly on each track start via clear(). No change to PCM or native DSD paths; the flag is a no-op when playing PCM. Format transition logic (DSD64→DSD128, DSD→PCM) uses the existing full close/reopen sequence since the underlying source format is still DSD.
  • --dop-msb flag (issue #80). For DACs whose DoP decoder expects MSB-first DSD payload in 24-bit DoP frames (rather than the LSB-first default from DSF files), --dop-msb enables intra-byte bit reversal of each DSD byte before packing into DoP frames. The reversal uses a static 256-entry lookup table in DirettaRingBuffer — zero branches and zero heap allocations in the audio hot path. --dop-msb implies --dop. Verbose mode prints the first few DoP pushes with raw DSD bytes for diagnosis. Also accessible as DOP=msb in the config file and as a third option in the web UI DSD Output Mode selector.

Fixed

  • DoP: silence during stabilisation uses plain PCM 0x00 for all modes (issue #80). getNewStream() fills all silence periods (prefill, stabilisation, underrun) with memset(0x00) in all modes, including DoP. The working reference path (MinimServer/Asset UPnP pre-encode DSF to DoP at the source, sending 176.4kHz PCM to DRUP with isDoPMode=false; DRUP sends PCM 0x00 silence and the DAC locks on the first real audio frame) uses no DoP markers during silence. A DoP-specific silence generator ([0x69, 0x69, marker] per channel with alternating markers, tracked by m_doPSilenceMarkerState) was tried in an earlier revision but did not resolve "music + constant hiss" on the SFORZATO DAC-01 or HOLO Audio targets — the pre-audio marker sequence appears to disturb these DACs' DSD demodulators before real audio starts. Reverted to plain memset(0x00)m_doPSilenceMarkerState and its reset in configureRingPCM() have been removed. Verbose mode (--verbose) now logs the first 5 ring pops with hex-decoded marker and DSD bytes ([DoP POP #N]) for field diagnosis of DoP lock issues.
  • DoP: stabilisation silence count inflated 30× by wrong cycle-time formula (issue #80 — secondary contributor). The stabilisation in getNewStream() computed cycle time as efficientMTU / bytesPerSecond. For DoP at 176.4kHz (1056-byte buffers, MTU=1500): 1497 / 1,058,400 × 1e6 = 1.4 µs instead of ~1 ms, inflating the required buffer count to 70,000+ (capped at 3000) → ~3 s of silence instead of 100 ms. Fixed (commit 912a177) to use currentBytesPerBuffer. Same fix applied to the DSD branch.
  • DoP: even-frame invariant for ring pops and drift compensation (issue #80). Two defensive changes ensure the ring read position is always at a 0x05-aligned (even-indexed) DoP frame so any silence→audio transition resumes with a clean 0x05 marker: (1) pushDSDToDoP even-frame guard (if (pcmFrames % 2 != 0) pcmFrames--;, commit 209365f) — every push writes an even number of PCM frames, leaving m_dopMarkerState at false (0x05) after each push and keeping ring content always at even × 6 bytes; (2) 44.1k drift corrector in DoP mode — at 176.4kHz the accumulator receives 400 units/call (176400 % 1000 = 400) and fires at threshold to add an extra frame compensating for the non-integer 176.4 frames/ms; in DoP mode the threshold changes 1000→2000 and the bonus 1→2 frames, so every corrector firing adds 178 frames (even) instead of 177 (odd); average buffer rate is unchanged (2 frames / 2000 units = 1 frame / 1000 units). Together, all pops from the ring to the Diretta stream are even-frame counts, keeping the first real audio frame after any silence period at a predictable 0x05 marker. Unit test test_pushDSD_dop_marker_phase_invariant covers the push-side invariant.
  • DSD / DoP stabilisation divide-by-zero (issue #80). configureRingDSD() never stored m_sampleRate or m_bytesPerSample for DSD mode. When the first track is DSD, both were 0, causing a divide-by-zero in the DSD-branch cycle-time formula inside getNewStream(). Fixed by storing byteRate * 8 and 1 respectively at the end of configureRingDSD().
  • DoP: DSD byte pair packed in wrong order — root cause of "music + constant hiss" (commit 233b520, issue #80, confirmed by Dominique on HOLO Audio Spring 3 / DDC-0 and by yama3kzh on SFORZATO DAC-01 / Volumio Preciso). pushDSDToDoP() was writing each DoP 24-bit word as [DSD_byte_N, DSD_byte_N+1, marker] instead of the correct [DSD_byte_N+1, DSD_byte_N, marker]. Per DoP v1.1 the 24-bit PCM word carries bits[23:16]=marker, bits[15:8]=DSD_byte_N, bits[7:0]=DSD_byte_N+1; in little-endian memory this means byte[0]=DSD_N+1, byte[1]=DSD_N, byte[2]=marker — the opposite order from what was written. The framing was correct (DAC showed DSD2.8MHz / DoP lock detected), so the DAC accepted the stream; however the two DSD bytes within every frame were transposed, breaking the PDM correlation at byte boundaries and producing the characteristic "music audible + constant high-frequency hiss" symptom. Fix: a two-line swap in the pushDSDToDoP() output loop. This matches the MinimServer / Asset UPnP reference implementations. Note: after long sessions with the incorrect encoding, a Diretta target may enter a stale lock state; switching modes or restarting the target resets it.
  • CPU affinity: cores silently rejected as invalid on AMD Ryzen with HT/SMT disabled (reported by Kiran on a Ryzen 7730U running AudioLinux). sysconf(_SC_NPROCESSORS_ONLN) returns the count of online CPUs (8 on a Ryzen 7730U with HT off), which DRUP incorrectly used as the maximum valid CPU ID. With SMT disabled, Linux takes the odd-numbered logical CPUs offline and keeps the even-numbered ones active (0, 2, 4, ..., 14) — so cores 10, 12, 14 are perfectly valid but were rejected as >= 8. The validation silently cleared config.cpuAudio/Decode/Other, disabling DRUP's per-thread pthread_setaffinity_np calls entirely. Fix: new getOnlineCpus() helper reads /sys/devices/system/cpu/online and validates against actual set membership. Falls back to 0..N-1 if the file is unreadable. Same fix applied to slim2Diretta v1.4.10.
  • install.sh: FFmpeg check falsely reported [MISSING] movffmpeg -demuxers in FFmpeg 8.x lists the QuickTime demuxer as mov,mp4,m4a,3gp,3g2,mj2 (alias group); the post-build check searched for " mov " (space on both sides) which never matches because the alias list is comma-separated. Fixed to grep -qE " ${dem}[ ,]". The demuxer was always compiled in; only the check message was wrong.
  • install.sh: FFmpeg decode test failed on every minimal build — the test used -f lavfi -i "sine=..." which requires the lavfi demuxer and sine source filter, neither of which is included in the minimal audio-only configure profile. Every minimal build would print a false-positive [WARNING] FFmpeg decode test failed. Replaced with a silent s16le byte-pipe that works with the minimal profile.
  • install.sh: WebUI profiles not refreshed after --build / option 3build_renderer() now detects whether the WebUI is already installed and copies the updated profile JSON files after each build.