mirror of
https://github.com/nonoo/kappanhang.git
synced 2025-12-06 08:02:00 +01:00
Add 'l' hotkey for quickly listening into the incoming audio stream
This commit is contained in:
parent
d9d6ff887b
commit
eed09e0956
|
|
@ -141,6 +141,14 @@ disabled and the contents of the second line of the status bar will be written
|
|||
as new console log lines. This is also the case if a Unix/VT100 terminal is
|
||||
not available.
|
||||
|
||||
### Hotkeys
|
||||
|
||||
Currently the only supported hotkey is `l` (listen), which toggles audio
|
||||
stream playback to the default sound device. This is useful for quickly
|
||||
listening into the audio stream coming from the server (the transceiver).
|
||||
Note that audio will be played to the previously created virtual sound card
|
||||
regardless of this setting.
|
||||
|
||||
## Authors
|
||||
|
||||
- Norbert Varga HA2NON [nonoo@nonoo.hu](mailto:nonoo@nonoo.hu)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/akosmarton/papipes"
|
||||
"github.com/mesilliac/pulse-simple"
|
||||
)
|
||||
|
||||
const audioSampleRate = 48000
|
||||
|
|
@ -19,6 +20,8 @@ const audioFrameSize = 1920 // 20ms
|
|||
const maxPlayBufferSize = audioFrameSize * 5
|
||||
|
||||
type audioStruct struct {
|
||||
devName string
|
||||
|
||||
source papipes.Source
|
||||
sink papipes.Sink
|
||||
|
||||
|
|
@ -33,14 +36,58 @@ type audioStruct struct {
|
|||
mutex sync.Mutex
|
||||
playBuf *bytes.Buffer
|
||||
canPlay chan bool
|
||||
|
||||
togglePlaybackToDefaultSoundcardChan chan bool
|
||||
defaultSoundCardStream *pulse.Stream
|
||||
}
|
||||
|
||||
var audio audioStruct
|
||||
|
||||
func (a *audioStruct) togglePlaybackToDefaultSoundcard() {
|
||||
if a.defaultSoundCardStream == nil {
|
||||
log.Print("turned on audio playback")
|
||||
ss := pulse.SampleSpec{Format: pulse.SAMPLE_S16LE, Rate: 48000, Channels: 1}
|
||||
a.defaultSoundCardStream, _ = pulse.Playback("kappanhang", a.devName, &ss)
|
||||
} else {
|
||||
_ = a.defaultSoundCardStream.Drain()
|
||||
a.defaultSoundCardStream.Free()
|
||||
a.defaultSoundCardStream = nil
|
||||
log.Print("turned off audio playback")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *audioStruct) playToVirtualSoundcard(d []byte) {
|
||||
for len(d) > 0 {
|
||||
written, err := a.source.Write(d)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
reportError(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
d = d[written:]
|
||||
}
|
||||
}
|
||||
|
||||
func (a *audioStruct) playToDefaultSoundcard(d []byte) {
|
||||
for len(d) > 0 {
|
||||
written, err := a.defaultSoundCardStream.Write(d)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
reportError(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
d = d[written:]
|
||||
}
|
||||
}
|
||||
|
||||
func (a *audioStruct) playLoop(deinitNeededChan, deinitFinishedChan chan bool) {
|
||||
for {
|
||||
select {
|
||||
case <-a.canPlay:
|
||||
case <-a.togglePlaybackToDefaultSoundcardChan:
|
||||
a.togglePlaybackToDefaultSoundcard()
|
||||
case <-deinitNeededChan:
|
||||
deinitFinishedChan <- true
|
||||
return
|
||||
|
|
@ -65,19 +112,9 @@ func (a *audioStruct) playLoop(deinitNeededChan, deinitFinishedChan chan bool) {
|
|||
break
|
||||
}
|
||||
|
||||
for {
|
||||
written, err := a.source.Write(d)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
reportError(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
bytesToWrite -= written
|
||||
if bytesToWrite == 0 {
|
||||
break
|
||||
}
|
||||
d = d[written:]
|
||||
a.playToVirtualSoundcard(d[:bytesToWrite])
|
||||
if a.defaultSoundCardStream != nil {
|
||||
a.playToDefaultSoundcard(d[:bytesToWrite])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,16 +212,17 @@ func (a *audioStruct) loop() {
|
|||
// We only init the audio once, with the first device name we acquire, so apps using the virtual sound card
|
||||
// won't have issues with the interface going down while the app is running.
|
||||
func (a *audioStruct) initIfNeeded(devName string) error {
|
||||
a.devName = devName
|
||||
bufferSizeInBits := (audioSampleRate * audioSampleBytes * 8) / 1000 * pulseAudioBufferLength.Milliseconds()
|
||||
|
||||
if !a.source.IsOpen() {
|
||||
a.source.Name = "kappanhang-" + devName
|
||||
a.source.Filename = "/tmp/kappanhang-" + devName + ".source"
|
||||
a.source.Name = "kappanhang-" + a.devName
|
||||
a.source.Filename = "/tmp/kappanhang-" + a.devName + ".source"
|
||||
a.source.Rate = audioSampleRate
|
||||
a.source.Format = "s16le"
|
||||
a.source.Channels = 1
|
||||
a.source.SetProperty("device.buffering.buffer_size", bufferSizeInBits)
|
||||
a.source.SetProperty("device.description", "kappanhang: "+devName)
|
||||
a.source.SetProperty("device.description", "kappanhang: "+a.devName)
|
||||
|
||||
// Cleanup previous pipes.
|
||||
sources, err := papipes.GetActiveSources()
|
||||
|
|
@ -202,14 +240,14 @@ func (a *audioStruct) initIfNeeded(devName string) error {
|
|||
}
|
||||
|
||||
if !a.sink.IsOpen() {
|
||||
a.sink.Name = "kappanhang-" + devName
|
||||
a.sink.Filename = "/tmp/kappanhang-" + devName + ".sink"
|
||||
a.sink.Name = "kappanhang-" + a.devName
|
||||
a.sink.Filename = "/tmp/kappanhang-" + a.devName + ".sink"
|
||||
a.sink.Rate = audioSampleRate
|
||||
a.sink.Format = "s16le"
|
||||
a.sink.Channels = 1
|
||||
a.sink.UseSystemClockForTiming = true
|
||||
a.sink.SetProperty("device.buffering.buffer_size", bufferSizeInBits)
|
||||
a.sink.SetProperty("device.description", "kappanhang: "+devName)
|
||||
a.sink.SetProperty("device.description", "kappanhang: "+a.devName)
|
||||
|
||||
// Cleanup previous pipes.
|
||||
sinks, err := papipes.GetActiveSinks()
|
||||
|
|
@ -233,6 +271,7 @@ func (a *audioStruct) initIfNeeded(devName string) error {
|
|||
a.play = make(chan []byte)
|
||||
a.canPlay = make(chan bool)
|
||||
a.rec = make(chan []byte)
|
||||
a.togglePlaybackToDefaultSoundcardChan = make(chan bool)
|
||||
a.deinitNeededChan = make(chan bool)
|
||||
a.deinitFinishedChan = make(chan bool)
|
||||
go a.loop()
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/fatih/color v1.9.0
|
||||
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2
|
||||
github.com/mattn/go-isatty v0.0.11
|
||||
github.com/mesilliac/pulse-simple v0.0.0-20170506101341-75ac54e19fdf
|
||||
github.com/pborman/getopt v1.1.0
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.16.0
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -21,6 +21,8 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
|
|||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mesilliac/pulse-simple v0.0.0-20170506101341-75ac54e19fdf h1:Zkc2mThKEz8uIFMN5S9Vde4F075QqonswrYWngsjq0g=
|
||||
github.com/mesilliac/pulse-simple v0.0.0-20170506101341-75ac54e19fdf/go.mod h1:w/UDU7AYzhUNZpb9TmWkrEFVu1+yA8jn++5871x9hWc=
|
||||
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
|
||||
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
|
|
|
|||
44
keyboard.go
Normal file
44
keyboard.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type keyboardStruct struct {
|
||||
}
|
||||
|
||||
var keyboard keyboardStruct
|
||||
|
||||
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' {
|
||||
if audio.togglePlaybackToDefaultSoundcardChan != nil {
|
||||
// Non-blocking send to channel.
|
||||
select {
|
||||
case audio.togglePlaybackToDefaultSoundcardChan <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *keyboardStruct) init() {
|
||||
if err := exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run(); err != nil {
|
||||
log.Error("can't disable input buffering")
|
||||
}
|
||||
if err := exec.Command("stty", "-F", "/dev/tty", "-echo").Run(); err != nil {
|
||||
log.Error("can't disable displaying entered characters")
|
||||
}
|
||||
|
||||
go s.loop()
|
||||
}
|
||||
|
||||
func (s *keyboardStruct) deinit() {
|
||||
_ = exec.Command("stty", "-F", "/dev/tty", "echo").Run()
|
||||
}
|
||||
3
main.go
3
main.go
|
|
@ -99,6 +99,8 @@ func main() {
|
|||
osSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(osSignal, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
keyboard.init()
|
||||
|
||||
var shouldExit bool
|
||||
var exitCode int
|
||||
for !shouldExit {
|
||||
|
|
@ -120,6 +122,7 @@ func main() {
|
|||
audio.deinit()
|
||||
serialTCPSrv.deinit()
|
||||
serialPort.deinit()
|
||||
keyboard.deinit()
|
||||
|
||||
log.Print("exiting")
|
||||
os.Exit(exitCode)
|
||||
|
|
|
|||
Loading…
Reference in a new issue