mirror of
https://github.com/nonoo/kappanhang.git
synced 2026-03-11 07:43:49 +01:00
Add PTT and audio send from default soundcard for the space key
This commit is contained in:
parent
5d38438f64
commit
32e2857285
127
audio-linux.go
127
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
main.go
4
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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
57
statuslog.go
57
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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue