Add PTT and audio send from default soundcard for the space key

This commit is contained in:
Nonoo 2020-10-30 22:12:30 +01:00
parent 5d38438f64
commit 32e2857285
6 changed files with 189 additions and 46 deletions

View file

@ -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 {

View file

@ -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

View file

@ -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])
}
}
}

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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)