mirror of
https://github.com/nonoo/kappanhang.git
synced 2026-01-08 01:39:58 +01:00
Fix audio monitor playback
This commit is contained in:
parent
fa01c69a20
commit
5435fb600c
250
audio-linux.go
250
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue