feat(icom): live microphone over the network — the last remote brick
The talk button learns the network road the voice keyer already took: when To-radio is the radio itself, the microphone is captured and re-framed into the rig's 320-sample packets, the capture callback serving as the clock — the mic delivers in real time, so no ring and no pacer. Counters and the frame remainder live on the audio stream, so a talk session spans calls. PTT keys before and releases after, through the same code as the USB path. With this, a network Icom is a complete remote station over three UDP ports: RX audio to the headset, live voice and recorded messages back, CW through the rig's keyer, CAT for everything else.
This commit is contained in:
@@ -57,8 +57,14 @@ type icomAudio struct {
|
||||
rxLastSeq uint16
|
||||
rxMissing map[uint16]int
|
||||
|
||||
dumped int // packets hex-dumped so far (≤ icaDumpFirst)
|
||||
lastRx atomic.Int64 // UnixNano of last packet (liveness)
|
||||
dumped int // packets hex-dumped so far (≤ icaDumpFirst)
|
||||
|
||||
// Live-TX state — see SendTXChunk.
|
||||
txMu sync.Mutex
|
||||
txRem []byte
|
||||
txOuter uint16
|
||||
txSend uint16
|
||||
lastRx atomic.Int64 // UnixNano of last packet (liveness)
|
||||
|
||||
done chan struct{}
|
||||
closeOnce sync.Once
|
||||
@@ -292,3 +298,38 @@ func (a *icomAudio) PlayTX(pcm []byte, rate, ch, bits int, stop <-chan struct{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Live TX — the microphone, not a recorded message. Chunks arrive at the
|
||||
// microphone's own real-time pace (16 kHz mono 16-bit, whatever length the
|
||||
// capture delivers) and are re-framed into the rig's 320-sample packets; the
|
||||
// remainder waits for the next chunk. Counters and remainder live on the
|
||||
// stream so a talk session survives across calls.
|
||||
func (a *icomAudio) SendTXChunk(pcm []byte) error {
|
||||
a.txMu.Lock()
|
||||
defer a.txMu.Unlock()
|
||||
a.txRem = append(a.txRem, pcm...)
|
||||
const frameBytes = 640
|
||||
for len(a.txRem) >= frameBytes {
|
||||
select {
|
||||
case <-a.done:
|
||||
return fmt.Errorf("the audio stream closed")
|
||||
default:
|
||||
}
|
||||
pkt := make([]byte, 0x18+frameBytes)
|
||||
icnLE.PutUint32(pkt[0:], uint32(len(pkt)))
|
||||
a.txOuter++
|
||||
icnLE.PutUint16(pkt[6:], a.txOuter)
|
||||
icnLE.PutUint32(pkt[8:], a.aID)
|
||||
icnLE.PutUint32(pkt[12:], a.aRemote)
|
||||
pkt[0x10], pkt[0x11] = 0x81, 0x01
|
||||
icnBE.PutUint16(pkt[0x12:], a.txSend)
|
||||
a.txSend++
|
||||
icnBE.PutUint32(pkt[0x14:], frameBytes)
|
||||
copy(pkt[0x18:], a.txRem[:frameBytes])
|
||||
a.txRem = a.txRem[frameBytes:]
|
||||
if _, err := a.conn.Write(pkt); err != nil {
|
||||
return fmt.Errorf("sending mic audio to the rig: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -972,6 +972,15 @@ func (n *icomNet) PlayTXAudio(pcm []byte, rate, ch, bits int, stop <-chan struct
|
||||
return n.audio.PlayTX(pcm, rate, ch, bits, stop)
|
||||
}
|
||||
|
||||
// TXAudioSender returns a function that streams live microphone chunks to the
|
||||
// rig — the talk button's road, where PlayTXAudio is the voice keyer's.
|
||||
func (n *icomNet) TXAudioSender() (func([]byte) error, error) {
|
||||
if n.audio == nil {
|
||||
return nil, fmt.Errorf("the radio's audio stream is not open — enable RX audio in Settings → CAT first")
|
||||
}
|
||||
return n.audio.SendTXChunk, nil
|
||||
}
|
||||
|
||||
func icnOpenClose(seq uint16, sentid, rcvdid uint32, civSeq uint16, magic byte) []byte {
|
||||
b := make([]byte, 0x16)
|
||||
icnLE.PutUint32(b[0:], 0x16)
|
||||
|
||||
@@ -2064,3 +2064,16 @@ func (b *IcomSerial) PlayTXAudio(pcm []byte, rate, ch, bits int, stop <-chan str
|
||||
}
|
||||
return fmt.Errorf("this rig takes transmit audio through its USB sound card, not the CAT link")
|
||||
}
|
||||
|
||||
// TXAudioSender hands back the network transport's live-mic sender, when this
|
||||
// rig is reached over one.
|
||||
func (b *IcomSerial) TXAudioSender() (func([]byte) error, error) {
|
||||
port := b.port
|
||||
type sender interface {
|
||||
TXAudioSender() (func([]byte) error, error)
|
||||
}
|
||||
if p, ok := port.(sender); ok {
|
||||
return p.TXAudioSender()
|
||||
}
|
||||
return nil, fmt.Errorf("this rig takes transmit audio through its USB sound card, not the CAT link")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user