chore(tci): probe binary frames per stream type

The frame log had one budget for the whole session, and the first forty
receive-audio frames spend it in under two seconds. A transmit-chrono
frame — the thing the voice keyer will have to answer, and whose size and
cadence cannot be read off the documentation — only appears once the
operator keys the radio, by which time nothing would have been logged.

Counted per type now, so the first frames of each kind are recorded
whenever they turn up.
This commit is contained in:
2026-08-25 08:29:24 +02:00
parent 01a23ccb77
commit 6a6b7ad6c2
+18 -5
View File
@@ -81,7 +81,7 @@ type tciAudio struct {
samples int64 samples int64
peak float64 peak float64
peakAt time.Time peakAt time.Time
probe int probeByType map[int]int
lastErr string lastErr string
// widthLogged keeps the one-line note about the sample width to once a // widthLogged keeps the one-line note about the sample width to once a
// session — it is a fact about the radio, not an event. // session — it is a fact about the radio, not an event.
@@ -110,7 +110,6 @@ func (t *TCI) StartTCIAudio(rx, rate int) error {
t.audio.rx = rx t.audio.rx = rx
t.audio.rate = rate t.audio.rate = rate
t.audio.frames, t.audio.samples, t.audio.peak = 0, 0, 0 t.audio.frames, t.audio.samples, t.audio.peak = 0, 0, 0
t.audio.probe = 0
t.audio.lastErr = "" t.audio.lastErr = ""
t.audio.mu.Unlock() t.audio.mu.Unlock()
@@ -182,10 +181,20 @@ func (t *TCI) handleBinary(data []byte) {
length := int(le.Uint32(data[20:])) length := int(le.Uint32(data[20:]))
stype := int(le.Uint32(data[24:])) stype := int(le.Uint32(data[24:]))
// Counted PER STREAM TYPE, not overall.
//
// A single counter was spent on the first forty receive-audio frames, which
// arrive twenty-four times a second — so a transmit-chrono or transmit-audio
// frame, the two this needs to see before the voice keyer can be written,
// would never have been logged at all. They only appear once the operator
// keys the radio, long after any global budget is gone.
t.audio.mu.Lock() t.audio.mu.Lock()
probe := t.audio.probe if t.audio.probeByType == nil {
t.audio.probeByType = map[int]int{}
}
probe := t.audio.probeByType[stype]
if probe < tciAudioProbeMax { if probe < tciAudioProbeMax {
t.audio.probe++ t.audio.probeByType[stype]++
} }
t.audio.mu.Unlock() t.audio.mu.Unlock()
if probe < tciAudioProbeMax { if probe < tciAudioProbeMax {
@@ -194,7 +203,11 @@ func (t *TCI) handleBinary(data []byte) {
} }
if stype != tciStreamRXAudio { if stype != tciStreamRXAudio {
return // IQ, TX audio echo, chrono: not this file's business yet // IQ, transmit audio, chrono. Nothing consumes them yet — but the chrono
// frames are what a voice keyer over TCI would have to answer, and their
// size and cadence cannot be guessed from the documentation. They are
// logged (per type, see above) and dropped.
return
} }
if codec != 0 { if codec != 0 {
t.audioErr(fmt.Sprintf("stream is codec=%d, and nothing here decodes a compressed stream", codec)) t.audioErr(fmt.Sprintf("stream is codec=%d, and nothing here decodes a compressed stream", codec))