fix(icom): only forward-moving audio packets reach the sink

Every audio packet was delivered as if it were the next 20 ms, duplicates and
late retransmits included. The monitor's capped ring quietly threw the surplus
away, so the speakers sounded fine — but the recorder keeps every sample it is
given, and the file came out longer than the QSO, slowed and stuttering, each
lost-then-resent packet heard twice. trackRxSeq now says whether a packet
advances the stream, and only those are handed on.
This commit is contained in:
2026-08-29 16:43:41 +02:00
parent 6bf759b6ae
commit 490941f506
2 changed files with 25 additions and 6 deletions
+4 -2
View File
@@ -16,7 +16,8 @@
"Icom network audio: toggling Listening off and on no longer chops the sound — the monitor restarts as network-fed instead of also opening a USB capture.",
"QSO recorder: starting a fresh manual take resets the counter for good — it used to flash 0 and jump back to the previous takes elapsed time.",
"MY_RIG follows the radio actually connected: the entry form now asks the active radio first, before the per-band default — an IC-7760 on the air no longer logs as the Flex the band plan names.",
"QSO recorder: a manual take on a network Icom records the network RX stream, not the “From radio” sound card (which captured another radios DAX on a mixed station)."
"QSO recorder: a manual take on a network Icom records the network RX stream, not the “From radio” sound card (which captured another radios DAX on a mixed station).",
"Icom network audio: duplicate and late-retransmitted packets no longer reach the recorder — recordings came out longer than the QSO, slowed and stuttering, while the speakers played fine."
],
"fr": [
"Console Elecraft : le S-mètre est calibré sur un vrai K3 — S9 et les +dB correspondent désormais à laffichage de la radio (il lisait environ deux points S trop bas).",
@@ -32,7 +33,8 @@
"Audio réseau Icom : couper puis relancer Listening ne hache plus le son — le moniteur redémarre alimenté par le réseau au lieu douvrir en plus une capture USB.",
"Enregistreur de QSO : démarrer une nouvelle prise manuelle remet le compteur à zéro pour de bon — il affichait 0 puis resautait au temps de la prise précédente.",
"MY_RIG suit la radio réellement connectée : le formulaire interroge dabord la radio active, avant le défaut par bande — un IC-7760 à lantenne ne se logue plus comme le Flex prévu par le plan de bande.",
"Enregistreur de QSO : une prise manuelle sur un Icom réseau enregistre le flux RX réseau, pas la carte son « From Radio » (qui capturait le DAX dune autre radio sur une station mixte)."
"Enregistreur de QSO : une prise manuelle sur un Icom réseau enregistre le flux RX réseau, pas la carte son « From Radio » (qui capturait le DAX dune autre radio sur une station mixte).",
"Audio réseau Icom : les paquets dupliqués ou retransmis en retard natteignent plus lenregistreur — les enregistrements sortaient plus longs que le QSO, ralentis et hachés, alors que les haut-parleurs jouaient bien."
]
},
{
+21 -4
View File
@@ -134,12 +134,19 @@ func (a *icomAudio) audioPump() {
case typ == 0x05: // rig-initiated disconnect
debugLog.Printf("icom audio: rig sent DISCONNECT — audio stream dropped by the rig")
case typ == 0x00 && k > icaAudioOffset: // audio data packet
a.trackRxSeq(icnLE.Uint16(buf[6:]))
fresh := a.trackRxSeq(icnLE.Uint16(buf[6:]))
if a.dumped < icaDumpFirst {
a.dumped++
debugLog.Printf("icom audio raw #%d: len=%d head=% X", a.dumped, k, buf[:min(icaAudioOffset+8, k)])
}
if a.sink != nil {
// Only a packet that ADVANCES the sequence reaches the sink. A
// duplicate or a late retransmit used to be delivered as if it
// were the next 20 ms of audio: the monitor's capped ring threw
// the surplus away (the speakers stayed clean), but the recorder
// keeps every sample it is given — the file grew longer than the
// QSO and played back slowed and stuttering, each lost-then-
// resent packet heard twice.
if fresh && a.sink != nil {
payload := append([]byte(nil), buf[icaAudioOffset:k]...)
a.sink(payload)
}
@@ -159,26 +166,36 @@ func (a *icomAudio) audioPump() {
// trackRxSeq / sendRetransmitReq mirror icomNet's receive-side retransmit exactly
// (audio is as loss-sensitive as the scope stream). Duplicated deliberately so
// the audio stream owns its own seq state with no shared locking.
func (a *icomAudio) trackRxSeq(seq uint16) {
//
// The return value says whether this packet moves the stream FORWARD — the
// only kind the sink may hear. A duplicate is the same 20 ms again; a late
// retransmit would play old audio in the middle of new. Both are accounted
// for here and dropped by the caller.
func (a *icomAudio) trackRxSeq(seq uint16) bool {
if !a.rxHaveSeq {
a.rxHaveSeq = true
a.rxLastSeq = seq
return
return true
}
switch d := int16(seq - a.rxLastSeq); {
case d == 0:
return false
case d < 0:
delete(a.rxMissing, seq)
return false
case d == 1:
a.rxLastSeq = seq
return true
case int(d) <= icnMaxMissing:
for f := a.rxLastSeq + 1; f != seq; f++ {
a.rxMissing[f] = 0
}
a.rxLastSeq = seq
return true
default:
a.rxMissing = make(map[uint16]int)
a.rxLastSeq = seq
return true
}
}