From 5435fb600cfc3d5211ff8f6847d3bcc764207e68 Mon Sep 17 00:00:00 2001 From: Nonoo Date: Fri, 30 Oct 2020 17:16:48 +0100 Subject: [PATCH] Fix audio monitor playback --- audio-linux.go | 250 +++++++++++++++++++++++++++++++------------------ keyboard.go | 8 +- 2 files changed, 160 insertions(+), 98 deletions(-) diff --git a/audio-linux.go b/audio-linux.go index f5b113b..71c64e9 100644 --- a/audio-linux.go +++ b/audio-linux.go @@ -22,9 +22,6 @@ const maxPlayBufferSize = audioFrameSize * 5 type audioStruct struct { devName string - source papipes.Source - sink papipes.Sink - deinitNeededChan chan bool deinitFinishedChan chan bool @@ -33,28 +30,51 @@ type audioStruct struct { // Read from this channel for audio. rec chan []byte - mutex sync.Mutex - playBuf *bytes.Buffer - canPlay chan bool + virtualSoundcardStream struct { + source papipes.Source + sink papipes.Sink - togglePlaybackToDefaultSoundcardChan chan bool - defaultSoundCardStream *pulse.Stream + mutex sync.Mutex + playBuf *bytes.Buffer + canPlay chan bool + } + + defaultSoundcardStream struct { + togglePlaybackChan chan bool + stream *pulse.Stream + + mutex sync.Mutex + playBuf *bytes.Buffer + canPlay chan bool + } } var audio audioStruct func (a *audioStruct) defaultSoundCardStreamDeinit() { - _ = a.defaultSoundCardStream.Drain() - a.defaultSoundCardStream.Free() - a.defaultSoundCardStream = nil + _ = a.defaultSoundcardStream.stream.Drain() + a.defaultSoundcardStream.stream.Free() + a.defaultSoundcardStream.stream = nil } func (a *audioStruct) togglePlaybackToDefaultSoundcard() { - if a.defaultSoundCardStream == nil { + if a.defaultSoundcardStream.togglePlaybackChan == nil { + return + } + + // Non-blocking send to channel. + select { + case a.defaultSoundcardStream.togglePlaybackChan <- true: + default: + } +} + +func (a *audioStruct) doTogglePlaybackToDefaultSoundcard() { + if a.defaultSoundcardStream.stream == nil { log.Print("turned on audio playback") statusLog.reportAudioMon(true) ss := pulse.SampleSpec{Format: pulse.SAMPLE_S16LE, Rate: 48000, Channels: 1} - a.defaultSoundCardStream, _ = pulse.Playback("kappanhang", a.devName, &ss) + a.defaultSoundcardStream.stream, _ = pulse.Playback("kappanhang", a.devName, &ss) } else { a.defaultSoundCardStreamDeinit() log.Print("turned off audio playback") @@ -62,56 +82,27 @@ func (a *audioStruct) togglePlaybackToDefaultSoundcard() { } } -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) { +func (a *audioStruct) playLoopToDefaultSoundcard(deinitNeededChan, deinitFinishedChan chan bool) { for { select { - case <-a.canPlay: - case <-a.togglePlaybackToDefaultSoundcardChan: - a.togglePlaybackToDefaultSoundcard() + case <-a.defaultSoundcardStream.canPlay: + case <-a.defaultSoundcardStream.togglePlaybackChan: + a.doTogglePlaybackToDefaultSoundcard() case <-deinitNeededChan: - if a.defaultSoundCardStream != nil { - a.defaultSoundCardStreamDeinit() - } deinitFinishedChan <- true return } for { - a.mutex.Lock() - if a.playBuf.Len() < audioFrameSize { - a.mutex.Unlock() + a.defaultSoundcardStream.mutex.Lock() + if a.defaultSoundcardStream.playBuf.Len() < audioFrameSize { + a.defaultSoundcardStream.mutex.Unlock() break } d := make([]byte, audioFrameSize) - bytesToWrite, err := a.playBuf.Read(d) - a.mutex.Unlock() + bytesToWrite, err := a.defaultSoundcardStream.playBuf.Read(d) + a.defaultSoundcardStream.mutex.Unlock() if err != nil { log.Error(err) break @@ -121,9 +112,57 @@ func (a *audioStruct) playLoop(deinitNeededChan, deinitFinishedChan chan bool) { break } - a.playToVirtualSoundcard(d[:bytesToWrite]) - if a.defaultSoundCardStream != nil { - a.playToDefaultSoundcard(d[:bytesToWrite]) + for len(d) > 0 && a.defaultSoundcardStream.stream != nil { + written, err := a.defaultSoundcardStream.stream.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.virtualSoundcardStream.canPlay: + case <-deinitNeededChan: + deinitFinishedChan <- true + return + } + + for { + a.virtualSoundcardStream.mutex.Lock() + if a.virtualSoundcardStream.playBuf.Len() < audioFrameSize { + a.virtualSoundcardStream.mutex.Unlock() + break + } + + d := make([]byte, audioFrameSize) + bytesToWrite, err := a.virtualSoundcardStream.playBuf.Read(d) + a.virtualSoundcardStream.mutex.Unlock() + if err != nil { + log.Error(err) + break + } + if bytesToWrite != len(d) { + log.Error("buffer underread") + break + } + + for len(d) > 0 { + written, err := a.virtualSoundcardStream.source.Write(d) + if err != nil { + if _, ok := err.(*os.PathError); !ok { + reportError(err) + } + break + } + d = d[written:] } } } @@ -144,7 +183,7 @@ func (a *audioStruct) recLoop(deinitNeededChan, deinitFinishedChan chan bool) { default: } - n, err := a.sink.Read(frameBuf) + n, err := a.virtualSoundcardStream.sink.Read(frameBuf) if err != nil { if _, ok := err.(*os.PathError); !ok { reportError(err) @@ -181,6 +220,9 @@ func (a *audioStruct) loop() { playLoopDeinitNeededChan := make(chan bool) playLoopDeinitFinishedChan := make(chan bool) go a.playLoop(playLoopDeinitNeededChan, playLoopDeinitFinishedChan) + playLoopToDefaultSoundcardDeinitNeededChan := make(chan bool) + playLoopToDefaultSoundcardDeinitFinishedChan := make(chan bool) + go a.playLoopToDefaultSoundcard(playLoopToDefaultSoundcardDeinitNeededChan, playLoopToDefaultSoundcardDeinitFinishedChan) recLoopDeinitNeededChan := make(chan bool) recLoopDeinitFinishedChan := make(chan bool) go a.recLoop(recLoopDeinitNeededChan, recLoopDeinitFinishedChan) @@ -197,24 +239,48 @@ func (a *audioStruct) loop() { playLoopDeinitNeededChan <- true <-playLoopDeinitFinishedChan + if a.defaultSoundcardStream.stream != nil { + a.defaultSoundCardStreamDeinit() + } + + playLoopToDefaultSoundcardDeinitNeededChan <- true + <-playLoopToDefaultSoundcardDeinitFinishedChan + a.deinitFinishedChan <- true return } - a.mutex.Lock() - free := maxPlayBufferSize - a.playBuf.Len() + a.virtualSoundcardStream.mutex.Lock() + free := maxPlayBufferSize - a.virtualSoundcardStream.playBuf.Len() if free < len(d) { b := make([]byte, len(d)-free) - _, _ = a.playBuf.Read(b) + _, _ = a.virtualSoundcardStream.playBuf.Read(b) } - a.playBuf.Write(d) - a.mutex.Unlock() + a.virtualSoundcardStream.playBuf.Write(d) + a.virtualSoundcardStream.mutex.Unlock() // Non-blocking notify. select { - case a.canPlay <- true: + case a.virtualSoundcardStream.canPlay <- true: default: } + + if a.defaultSoundcardStream.stream != nil { + a.defaultSoundcardStream.mutex.Lock() + free := maxPlayBufferSize - a.defaultSoundcardStream.playBuf.Len() + if free < len(d) { + b := make([]byte, len(d)-free) + _, _ = a.defaultSoundcardStream.playBuf.Read(b) + } + a.defaultSoundcardStream.playBuf.Write(d) + a.defaultSoundcardStream.mutex.Unlock() + + // Non-blocking notify. + select { + case a.defaultSoundcardStream.canPlay <- true: + default: + } + } } } @@ -224,63 +290,65 @@ 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-" + 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: "+a.devName) + if !a.virtualSoundcardStream.source.IsOpen() { + a.virtualSoundcardStream.source.Name = "kappanhang-" + a.devName + a.virtualSoundcardStream.source.Filename = "/tmp/kappanhang-" + a.devName + ".source" + a.virtualSoundcardStream.source.Rate = audioSampleRate + a.virtualSoundcardStream.source.Format = "s16le" + a.virtualSoundcardStream.source.Channels = 1 + a.virtualSoundcardStream.source.SetProperty("device.buffering.buffer_size", bufferSizeInBits) + a.virtualSoundcardStream.source.SetProperty("device.description", "kappanhang: "+a.devName) // Cleanup previous pipes. sources, err := papipes.GetActiveSources() if err == nil { for _, i := range sources { - if i.Filename == a.source.Filename { + if i.Filename == a.virtualSoundcardStream.source.Filename { i.Close() } } } - if err := a.source.Open(); err != nil { + if err := a.virtualSoundcardStream.source.Open(); err != nil { return err } } - if !a.sink.IsOpen() { - 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: "+a.devName) + if !a.virtualSoundcardStream.sink.IsOpen() { + a.virtualSoundcardStream.sink.Name = "kappanhang-" + a.devName + a.virtualSoundcardStream.sink.Filename = "/tmp/kappanhang-" + a.devName + ".sink" + a.virtualSoundcardStream.sink.Rate = audioSampleRate + a.virtualSoundcardStream.sink.Format = "s16le" + a.virtualSoundcardStream.sink.Channels = 1 + a.virtualSoundcardStream.sink.UseSystemClockForTiming = true + a.virtualSoundcardStream.sink.SetProperty("device.buffering.buffer_size", bufferSizeInBits) + a.virtualSoundcardStream.sink.SetProperty("device.description", "kappanhang: "+a.devName) // Cleanup previous pipes. sinks, err := papipes.GetActiveSinks() if err == nil { for _, i := range sinks { - if i.Filename == a.sink.Filename { + if i.Filename == a.virtualSoundcardStream.sink.Filename { i.Close() } } } - if err := a.sink.Open(); err != nil { + if err := a.virtualSoundcardStream.sink.Open(); err != nil { return err } } - if a.playBuf == nil { - log.Print("opened device " + a.source.Name) + if a.virtualSoundcardStream.playBuf == nil { + log.Print("opened device " + a.virtualSoundcardStream.source.Name) - a.playBuf = bytes.NewBuffer([]byte{}) + a.virtualSoundcardStream.playBuf = bytes.NewBuffer([]byte{}) + a.defaultSoundcardStream.playBuf = bytes.NewBuffer([]byte{}) a.play = make(chan []byte) - a.canPlay = make(chan bool) + a.virtualSoundcardStream.canPlay = make(chan bool) + a.defaultSoundcardStream.canPlay = make(chan bool) a.rec = make(chan []byte) - a.togglePlaybackToDefaultSoundcardChan = make(chan bool) + a.defaultSoundcardStream.togglePlaybackChan = make(chan bool) a.deinitNeededChan = make(chan bool) a.deinitFinishedChan = make(chan bool) go a.loop() @@ -289,16 +357,16 @@ func (a *audioStruct) initIfNeeded(devName string) error { } func (a *audioStruct) closeIfNeeded() { - if a.source.IsOpen() { - if err := a.source.Close(); err != nil { + if a.virtualSoundcardStream.source.IsOpen() { + if err := a.virtualSoundcardStream.source.Close(); err != nil { if _, ok := err.(*os.PathError); !ok { log.Error(err) } } } - if a.sink.IsOpen() { - if err := a.sink.Close(); err != nil { + if a.virtualSoundcardStream.sink.IsOpen() { + if err := a.virtualSoundcardStream.sink.Close(); err != nil { if _, ok := err.(*os.PathError); !ok { log.Error(err) } diff --git a/keyboard.go b/keyboard.go index 8bf308e..80ca409 100644 --- a/keyboard.go +++ b/keyboard.go @@ -16,13 +16,7 @@ func (s *keyboardStruct) loop() { 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: - } - } + audio.togglePlaybackToDefaultSoundcard() } } }