From 32e285728588a76a769a81258a7275e1dca82943 Mon Sep 17 00:00:00 2001 From: Nonoo Date: Fri, 30 Oct 2020 22:12:30 +0100 Subject: [PATCH] Add PTT and audio send from default soundcard for the space key --- audio-linux.go | 127 +++++++++++++++++++++++++++++++++++++++++----- civcontrol.go | 27 +++++++--- keyboard-linux.go | 13 +++-- main.go | 4 +- serialstream.go | 7 +-- statuslog.go | 57 ++++++++++++++------- 6 files changed, 189 insertions(+), 46 deletions(-) diff --git a/audio-linux.go b/audio-linux.go index 65af431..4a3593e 100644 --- a/audio-linux.go +++ b/audio-linux.go @@ -41,7 +41,11 @@ type audioStruct struct { defaultSoundcardStream struct { togglePlaybackChan chan bool - stream *pulse.Stream + playStream *pulse.Stream + recStream *pulse.Stream + + recLoopDeinitNeededChan chan bool + recLoopDeinitFinishedChan chan bool mutex sync.Mutex playBuf *bytes.Buffer @@ -51,10 +55,20 @@ type audioStruct struct { var audio audioStruct -func (a *audioStruct) defaultSoundCardStreamDeinit() { - _ = a.defaultSoundcardStream.stream.Drain() - a.defaultSoundcardStream.stream.Free() - a.defaultSoundcardStream.stream = nil +func (a *audioStruct) defaultSoundCardPlayStreamDeinit() { + _ = a.defaultSoundcardStream.playStream.Drain() + a.defaultSoundcardStream.playStream.Free() + a.defaultSoundcardStream.playStream = nil +} + +func (a *audioStruct) defaultSoundCardRecStreamDeinit() { + if a.defaultSoundcardStream.recStream == nil { + return + } + a.defaultSoundcardStream.recLoopDeinitNeededChan <- true + <-a.defaultSoundcardStream.recLoopDeinitFinishedChan + a.defaultSoundcardStream.recStream.Free() + a.defaultSoundcardStream.recStream = nil } func (a *audioStruct) togglePlaybackToDefaultSoundcard() { @@ -69,14 +83,50 @@ func (a *audioStruct) togglePlaybackToDefaultSoundcard() { } } +func (a *audioStruct) toggleRecFromDefaultSoundcard() { + if civControl == nil { + return + } + + if a.defaultSoundcardStream.recStream == nil { + ss := pulse.SampleSpec{Format: pulse.SAMPLE_S16LE, Rate: audioSampleRate, Channels: 1} + battr := pulse.NewBufferAttr() + battr.Fragsize = audioFrameSize + var err error + a.defaultSoundcardStream.recStream, err = pulse.NewStream("", "kappanhang", pulse.STREAM_RECORD, "", a.devName, + &ss, nil, battr) + if err == nil { + a.defaultSoundcardStream.recLoopDeinitNeededChan = make(chan bool) + a.defaultSoundcardStream.recLoopDeinitFinishedChan = make(chan bool) + go a.recLoopFromDefaultSoundcard() + log.Print("turned on audio rec") + statusLog.reportAudioRec(true) + + if err := civControl.setPTT(true); err != nil { + log.Error("can't turn on ptt: ", err) + } + } else { + log.Error("can't turn on rec: ", err) + a.defaultSoundcardStream.recStream = nil + } + } else { + a.defaultSoundCardRecStreamDeinit() + statusLog.reportAudioRec(false) + log.Print("turned off audio rec") + if err := civControl.setPTT(false); err != nil { + log.Error("can't turn off ptt: ", err) + } + } +} + func (a *audioStruct) doTogglePlaybackToDefaultSoundcard() { - if a.defaultSoundcardStream.stream == nil { + if a.defaultSoundcardStream.playStream == nil { log.Print("turned on audio playback") statusLog.reportAudioMon(true) - ss := pulse.SampleSpec{Format: pulse.SAMPLE_S16LE, Rate: 48000, Channels: 1} - a.defaultSoundcardStream.stream, _ = pulse.Playback("kappanhang", a.devName, &ss) + ss := pulse.SampleSpec{Format: pulse.SAMPLE_S16LE, Rate: audioSampleRate, Channels: 1} + a.defaultSoundcardStream.playStream, _ = pulse.Playback("kappanhang", a.devName, &ss) } else { - a.defaultSoundCardStreamDeinit() + a.defaultSoundCardPlayStreamDeinit() log.Print("turned off audio playback") statusLog.reportAudioMon(false) } @@ -112,8 +162,8 @@ func (a *audioStruct) playLoopToDefaultSoundcard(deinitNeededChan, deinitFinishe break } - for len(d) > 0 && a.defaultSoundcardStream.stream != nil { - written, err := a.defaultSoundcardStream.stream.Write(d) + for len(d) > 0 && a.defaultSoundcardStream.playStream != nil { + written, err := a.defaultSoundcardStream.playStream.Write(d) if err != nil { if _, ok := err.(*os.PathError); !ok { reportError(err) @@ -126,6 +176,54 @@ func (a *audioStruct) playLoopToDefaultSoundcard(deinitNeededChan, deinitFinishe } } +func (a *audioStruct) recLoopFromDefaultSoundcard() { + defer func() { + a.defaultSoundcardStream.recLoopDeinitFinishedChan <- true + }() + + frameBuf := make([]byte, audioFrameSize) + buf := bytes.NewBuffer([]byte{}) + + for { + select { + case <-a.defaultSoundcardStream.recLoopDeinitNeededChan: + return + default: + } + + n, err := a.defaultSoundcardStream.recStream.Read(frameBuf) + if err != nil { + if _, ok := err.(*os.PathError); !ok { + reportError(err) + } + } + + // Do not send silence frames to the radio unnecessarily + if isAllZero(frameBuf[:n]) { + continue + } + buf.Write(frameBuf[:n]) + + for buf.Len() >= len(frameBuf) { + // We need to create a new []byte slice for each chunk to be able to send it through the rec chan. + b := make([]byte, len(frameBuf)) + n, err = buf.Read(b) + if err != nil { + reportError(err) + } + if n != len(frameBuf) { + reportError(errors.New("audio buffer read error")) + } + + select { + case a.rec <- b: + case <-a.defaultSoundcardStream.recLoopDeinitNeededChan: + return + } + } + } +} + func (a *audioStruct) playLoopToVirtualSoundcard(deinitNeededChan, deinitFinishedChan chan bool) { for { select { @@ -240,8 +338,8 @@ func (a *audioStruct) loop() { playLoopToVirtualSoundcardDeinitNeededChan <- true <-playLoopToVirtualSoundcardDeinitFinishedChan - if a.defaultSoundcardStream.stream != nil { - a.defaultSoundCardStreamDeinit() + if a.defaultSoundcardStream.playStream != nil { + a.defaultSoundCardPlayStreamDeinit() } playLoopToDefaultSoundcardDeinitNeededChan <- true @@ -266,7 +364,7 @@ func (a *audioStruct) loop() { default: } - if a.defaultSoundcardStream.stream != nil { + if a.defaultSoundcardStream.playStream != nil { a.defaultSoundcardStream.mutex.Lock() free := maxPlayBufferSize - a.defaultSoundcardStream.playBuf.Len() if free < len(d) { @@ -378,6 +476,7 @@ func (a *audioStruct) closeIfNeeded() { } func (a *audioStruct) deinit() { + a.defaultSoundCardRecStreamDeinit() a.closeIfNeeded() if a.deinitNeededChan != nil { diff --git a/civcontrol.go b/civcontrol.go index bbc4d24..81139a0 100644 --- a/civcontrol.go +++ b/civcontrol.go @@ -5,8 +5,11 @@ import "math" const civAddress = 0xa4 type civControlStruct struct { + st *serialStream } +var civControl *civControlStruct + func (s *civControlStruct) decode(d []byte) { if len(d) < 6 || d[0] != 0xfe || d[1] != 0xfe || d[len(d)-1] != 0xfd { return @@ -26,7 +29,7 @@ func (s *civControlStruct) decode(d []byte) { case 0x14: s.decodePower(payload) case 0x1c: - s.decodePTT(payload) + s.decodeTransmitStatus(payload) } } @@ -98,7 +101,7 @@ func (s *civControlStruct) decodePower(d []byte) { statusLog.reportTxPower(percent) } -func (s *civControlStruct) decodePTT(d []byte) { +func (s *civControlStruct) decodeTransmitStatus(d []byte) { if len(d) < 2 { return } @@ -118,21 +121,31 @@ func (s *civControlStruct) decodePTT(d []byte) { statusLog.reportPTT(ptt, tune) } -func (s *civControlStruct) query(st *serialStream) error { +func (s *civControlStruct) setPTT(enable bool) error { + var b byte + if enable { + b = 1 + } + return s.st.send([]byte{254, 254, civAddress, 224, 0x1c, 0, b, 253}) +} + +func (s *civControlStruct) init(st *serialStream) error { + s.st = st + // Querying frequency. - if err := st.send([]byte{254, 254, civAddress, 224, 3, 253}); err != nil { + if err := s.st.send([]byte{254, 254, civAddress, 224, 3, 253}); err != nil { return err } // Querying mode. - if err := st.send([]byte{254, 254, civAddress, 224, 4, 253}); err != nil { + if err := s.st.send([]byte{254, 254, civAddress, 224, 4, 253}); err != nil { return err } // Querying power. - if err := st.send([]byte{254, 254, civAddress, 224, 0x14, 0x0a, 253}); err != nil { + if err := s.st.send([]byte{254, 254, civAddress, 224, 0x14, 0x0a, 253}); err != nil { return err } // Querying PTT. - if err := st.send([]byte{254, 254, civAddress, 224, 0x1c, 0, 253}); err != nil { + if err := s.st.send([]byte{254, 254, civAddress, 224, 0x1c, 0, 253}); err != nil { return err } return nil diff --git a/keyboard-linux.go b/keyboard-linux.go index ec12a10..48ceff4 100644 --- a/keyboard-linux.go +++ b/keyboard-linux.go @@ -12,14 +12,21 @@ type keyboardStruct struct { var keyboard keyboardStruct +func (s *keyboardStruct) handleKey(k byte) { + switch k { + case 'l': + audio.togglePlaybackToDefaultSoundcard() + case ' ': + audio.toggleRecFromDefaultSoundcard() + } +} + func (s *keyboardStruct) loop() { var b []byte = make([]byte, 1) for { n, err := os.Stdin.Read(b) if n > 0 && err == nil { - if b[0] == 'l' { - audio.togglePlaybackToDefaultSoundcard() - } + s.handleKey(b[0]) } } } diff --git a/main.go b/main.go index a71f0f6..995eb44 100644 --- a/main.go +++ b/main.go @@ -99,7 +99,7 @@ func main() { osSignal := make(chan os.Signal, 1) signal.Notify(osSignal, os.Interrupt, syscall.SIGTERM) - if statusLog.isRealtime() { + if statusLog.isRealtimeInternal() { keyboard.init() } @@ -125,7 +125,7 @@ func main() { serialTCPSrv.deinit() serialPort.deinit() - if statusLog.isRealtime() { + if statusLog.isRealtimeInternal() { keyboard.deinit() } diff --git a/serialstream.go b/serialstream.go index daca2f8..161bc7e 100644 --- a/serialstream.go +++ b/serialstream.go @@ -19,7 +19,6 @@ type serialStream struct { receivedSerialData bool lastReceivedSeq uint16 - civControl civControlStruct readFromSerialPort struct { buf bytes.Buffer @@ -93,7 +92,7 @@ func (s *serialStream) handleRxSeqBufEntry(e seqBufEntry) { e.data = e.data[21:] - s.civControl.decode(e.data) + civControl.decode(e.data) if serialPort.write != nil { serialPort.write <- e.data @@ -253,7 +252,8 @@ func (s *serialStream) init(devName string) error { s.readFromSerialPort.frameTimeout = time.NewTimer(0) <-s.readFromSerialPort.frameTimeout.C - if err := s.civControl.query(s); err != nil { + civControl = &civControlStruct{} + if err := civControl.init(s); err != nil { return err } @@ -270,6 +270,7 @@ func (s *serialStream) deinit() { s.deinitNeededChan <- true <-s.deinitFinishedChan } + civControl = nil s.common.deinit() s.rxSeqBuf.deinit() } diff --git a/statuslog.go b/statuslog.go index 1b8941a..953ee42 100644 --- a/statuslog.go +++ b/statuslog.go @@ -20,9 +20,12 @@ type statusLogData struct { filter string txPowerStr string - startTime time.Time - rttStr string - audioMonStr string + startTime time.Time + rttStr string + + audioMonOn bool + audioRecOn bool + audioStateStr string } type statusLogStruct struct { @@ -41,9 +44,10 @@ type statusLogStruct struct { tx string tune string } - audioMon struct { - on string - off string + audioStateStr struct { + off string + monOn string + rec string } } @@ -62,6 +66,16 @@ func (s *statusLogStruct) reportRTTLatency(l time.Duration) { s.data.rttStr = fmt.Sprint(l.Milliseconds()) } +func (s *statusLogStruct) updateAudioStateStr() { + if s.data.audioRecOn { + s.data.audioStateStr = s.preGenerated.audioStateStr.rec + } else if s.data.audioMonOn { + s.data.audioStateStr = s.preGenerated.audioStateStr.monOn + } else { + s.data.audioStateStr = s.preGenerated.audioStateStr.off + } +} + func (s *statusLogStruct) reportAudioMon(enabled bool) { s.mutex.Lock() defer s.mutex.Unlock() @@ -69,11 +83,19 @@ func (s *statusLogStruct) reportAudioMon(enabled bool) { if s.data == nil { return } - if enabled { - s.data.audioMonStr = s.preGenerated.audioMon.on - } else { - s.data.audioMonStr = s.preGenerated.audioMon.off + s.data.audioMonOn = enabled + s.updateAudioStateStr() +} + +func (s *statusLogStruct) reportAudioRec(enabled bool) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if s.data == nil { + return } + s.data.audioRecOn = enabled + s.updateAudioStateStr() } func (s *statusLogStruct) reportFrequency(f float64) { @@ -168,7 +190,7 @@ func (s *statusLogStruct) update() { txPowerStr = " txpwr " + s.data.txPowerStr } s.data.line1 = fmt.Sprint("state ", s.data.stateStr, " freq: ", fmt.Sprintf("%f", s.data.frequency/1000000), - modeStr, filterStr, txPowerStr, " audiomon ", s.data.audioMonStr) + modeStr, filterStr, txPowerStr, " audio ", s.data.audioStateStr) up, down, lost, retransmits := netstat.get() lostStr := "0" @@ -230,10 +252,10 @@ func (s *statusLogStruct) startPeriodicPrint() { s.initIfNeeded() s.data = &statusLogData{ - stateStr: s.preGenerated.stateStr.unknown, - startTime: time.Now(), - rttStr: "?", - audioMonStr: s.preGenerated.audioMon.off, + stateStr: s.preGenerated.stateStr.unknown, + startTime: time.Now(), + rttStr: "?", + audioStateStr: s.preGenerated.audioStateStr.off, } s.stopChan = make(chan bool) @@ -272,17 +294,18 @@ func (s *statusLogStruct) initIfNeeded() { c := color.New(color.FgHiWhite) c.Add(color.BgWhite) s.preGenerated.stateStr.unknown = c.Sprint(" ?? ") - s.preGenerated.audioMon.off = c.Sprint(" OFF ") + s.preGenerated.audioStateStr.off = c.Sprint(" OFF ") c = color.New(color.FgHiWhite) c.Add(color.BgGreen) s.preGenerated.stateStr.rx = c.Sprint(" RX ") - s.preGenerated.audioMon.on = c.Sprint(" ON ") + s.preGenerated.audioStateStr.monOn = c.Sprint(" MON ") c = color.New(color.FgHiWhite, color.BlinkRapid) c.Add(color.BgRed) s.preGenerated.stateStr.tx = c.Sprint(" TX ") s.preGenerated.stateStr.tune = c.Sprint(" TUNE ") + s.preGenerated.audioStateStr.rec = c.Sprint(" REC ") s.preGenerated.retransmitsColor = color.New(color.FgHiWhite) s.preGenerated.retransmitsColor.Add(color.BgYellow)