Fix ESP32 audio dropouts and improve streaming reliability

FLAC encoder now tracks frame numbers and reuses per-channel sample
buffers to reduce allocations. Client dialer always frees the active
slot on release so mDNS re-emissions can reconnect cleanly. Server
send buffer uses drop-oldest instead of hard failure, with lateness
tracking. Added engine stats logging (chunks/sec, kbps) and writer
diagnostics. UI simplified to a single play/pause toggle that
auto-starts playback on preset selection. Added --preset CLI flag
for headless startup. Removed stale systemd service files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 14:50:30 +02:00
parent d72e439fd9
commit 6e22834c5c
12 changed files with 158 additions and 82 deletions

View File

@@ -24,6 +24,8 @@ type FLACEncoder struct {
channels int
bitDepth int
blockSize int
frameNum uint64
chanBufs [][]int32 // reused per-channel sample buffers
}
// NewFLACEncoder creates a FLAC encoder with prediction analysis enabled.
@@ -54,6 +56,11 @@ func NewFLACEncoder(sampleRate, channels, bitDepth, blockSize int) (*FLACEncoder
copy(codecHeader, buf.Bytes())
buf.Reset()
chanBufs := make([][]int32, channels)
for ch := range chanBufs {
chanBufs[ch] = make([]int32, blockSize)
}
return &FLACEncoder{
encoder: enc,
buf: buf,
@@ -62,6 +69,7 @@ func NewFLACEncoder(sampleRate, channels, bitDepth, blockSize int) (*FLACEncoder
channels: channels,
bitDepth: bitDepth,
blockSize: blockSize,
chanBufs: chanBufs,
}, nil
}
@@ -92,20 +100,18 @@ func (e *FLACEncoder) Encode(samples []int32) ([]byte, error) {
shift = uint(24 - e.bitDepth)
}
// De-interleave into per-channel slices.
// De-interleave into pre-allocated per-channel buffers.
subframes := make([]*frame.Subframe, e.channels)
for ch := 0; ch < e.channels; ch++ {
channelSamples := make([]int32, e.blockSize)
buf := e.chanBufs[ch]
for i := 0; i < e.blockSize; i++ {
channelSamples[i] = samples[i*e.channels+ch] >> shift
buf[i] = samples[i*e.channels+ch] >> shift
}
subframes[ch] = &frame.Subframe{
SubHeader: frame.SubHeader{
// PredVerbatim signals the encoder to run prediction analysis
// and pick the optimal method (Constant, Fixed, or Verbatim).
Pred: frame.PredVerbatim,
},
Samples: channelSamples,
Samples: buf,
NSamples: e.blockSize,
}
}
@@ -127,9 +133,11 @@ func (e *FLACEncoder) Encode(samples []int32) ([]byte, error) {
SampleRate: uint32(e.sampleRate),
Channels: channelAssign,
BitsPerSample: uint8(e.bitDepth),
Num: e.frameNum,
},
Subframes: subframes,
}
e.frameNum++
e.buf.Reset()
if err := e.encoder.WriteFrame(f); err != nil {