kappanhang/audio-linux.go

488 lines
13 KiB
Go
Raw Normal View History

2020-10-25 21:25:38 +01:00
// +build linux
2020-10-20 15:23:12 +02:00
package main
import (
"bytes"
2020-10-20 16:04:35 +02:00
"errors"
2020-11-03 12:49:32 +01:00
"io"
2020-10-20 16:04:35 +02:00
"os"
2020-10-20 17:20:52 +02:00
"sync"
2020-10-27 14:20:27 +01:00
"time"
2020-10-20 15:23:12 +02:00
"github.com/akosmarton/papipes"
"github.com/mesilliac/pulse-simple"
2020-10-20 15:23:12 +02:00
)
2020-10-26 10:33:24 +01:00
const audioSampleRate = 48000
const audioSampleBytes = 2
const pulseAudioBufferLength = 100 * time.Millisecond
const audioFrameSize = 1920 // 20ms
const maxPlayBufferSize = audioFrameSize * 5
2020-10-26 10:33:24 +01:00
2020-10-20 15:23:12 +02:00
type audioStruct struct {
devName string
deinitNeededChan chan bool
deinitFinishedChan chan bool
2020-10-20 15:23:12 +02:00
// Send to this channel to play audio.
play chan []byte
// Read from this channel for audio.
rec chan []byte
2020-10-20 15:23:12 +02:00
2020-10-30 17:16:48 +01:00
virtualSoundcardStream struct {
source papipes.Source
sink papipes.Sink
mutex sync.Mutex
playBuf *bytes.Buffer
canPlay chan bool
}
defaultSoundcardStream struct {
togglePlaybackChan chan bool
playStream *pulse.Stream
recStream *pulse.Stream
recLoopDeinitNeededChan chan bool
recLoopDeinitFinishedChan chan bool
2020-10-30 17:16:48 +01:00
mutex sync.Mutex
playBuf *bytes.Buffer
canPlay chan bool
}
2020-10-20 15:23:12 +02:00
}
var audio audioStruct
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
2020-10-30 16:21:26 +01:00
}
func (a *audioStruct) togglePlaybackToDefaultSoundcard() {
2020-10-30 17:16:48 +01:00
if a.defaultSoundcardStream.togglePlaybackChan == nil {
return
}
// Non-blocking send to channel.
select {
case a.defaultSoundcardStream.togglePlaybackChan <- true:
default:
}
}
func (a *audioStruct) toggleRecFromDefaultSoundcard() {
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)
}
}
}
2020-10-30 17:16:48 +01:00
func (a *audioStruct) doTogglePlaybackToDefaultSoundcard() {
if a.defaultSoundcardStream.playStream == nil {
log.Print("turned on audio playback")
statusLog.reportAudioMon(true)
ss := pulse.SampleSpec{Format: pulse.SAMPLE_S16LE, Rate: audioSampleRate, Channels: 1}
a.defaultSoundcardStream.playStream, _ = pulse.Playback("kappanhang", a.devName, &ss)
} else {
a.defaultSoundCardPlayStreamDeinit()
log.Print("turned off audio playback")
statusLog.reportAudioMon(false)
}
}
2020-10-30 17:16:48 +01:00
func (a *audioStruct) playLoopToDefaultSoundcard(deinitNeededChan, deinitFinishedChan chan bool) {
for {
select {
case <-a.defaultSoundcardStream.canPlay:
case <-a.defaultSoundcardStream.togglePlaybackChan:
a.doTogglePlaybackToDefaultSoundcard()
case <-deinitNeededChan:
deinitFinishedChan <- true
return
}
2020-10-30 17:16:48 +01:00
for {
a.defaultSoundcardStream.mutex.Lock()
if a.defaultSoundcardStream.playBuf.Len() < audioFrameSize {
a.defaultSoundcardStream.mutex.Unlock()
break
}
d := make([]byte, audioFrameSize)
bytesToWrite, err := a.defaultSoundcardStream.playBuf.Read(d)
a.defaultSoundcardStream.mutex.Unlock()
if err != nil {
log.Error(err)
break
}
if bytesToWrite != len(d) {
log.Error("buffer underread")
break
}
for len(d) > 0 && a.defaultSoundcardStream.playStream != nil {
written, err := a.defaultSoundcardStream.playStream.Write(d)
2020-10-30 17:16:48 +01:00
if err != nil {
if _, ok := err.(*os.PathError); !ok {
reportError(err)
}
break
}
d = d[written:]
}
}
}
}
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
}
}
}
}
2020-10-30 17:25:09 +01:00
func (a *audioStruct) playLoopToVirtualSoundcard(deinitNeededChan, deinitFinishedChan chan bool) {
2020-10-20 15:23:12 +02:00
for {
select {
2020-10-30 17:16:48 +01:00
case <-a.virtualSoundcardStream.canPlay:
case <-deinitNeededChan:
deinitFinishedChan <- true
return
}
2020-10-20 15:23:12 +02:00
for {
2020-10-30 17:16:48 +01:00
a.virtualSoundcardStream.mutex.Lock()
if a.virtualSoundcardStream.playBuf.Len() < audioFrameSize {
a.virtualSoundcardStream.mutex.Unlock()
2020-10-20 17:20:52 +02:00
break
}
d := make([]byte, audioFrameSize)
2020-10-30 17:16:48 +01:00
bytesToWrite, err := a.virtualSoundcardStream.playBuf.Read(d)
a.virtualSoundcardStream.mutex.Unlock()
if err != nil {
2020-10-20 17:20:52 +02:00
log.Error(err)
break
}
2020-10-20 17:20:52 +02:00
if bytesToWrite != len(d) {
log.Error("buffer underread")
break
2020-10-20 16:04:35 +02:00
}
2020-10-20 17:20:52 +02:00
2020-10-30 17:16:48 +01:00
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:]
2020-10-20 17:20:52 +02:00
}
2020-10-20 16:04:35 +02:00
}
}
}
2020-10-30 20:34:42 +01:00
func (a *audioStruct) recLoopFromVirtualSoundcard(deinitNeededChan, deinitFinishedChan chan bool) {
defer func() {
deinitFinishedChan <- true
}()
frameBuf := make([]byte, audioFrameSize)
2020-10-20 16:04:35 +02:00
buf := bytes.NewBuffer([]byte{})
for {
select {
case <-deinitNeededChan:
return
default:
}
2020-10-30 17:16:48 +01:00
n, err := a.virtualSoundcardStream.sink.Read(frameBuf)
2020-10-20 16:04:35 +02:00
if err != nil {
if _, ok := err.(*os.PathError); !ok {
reportError(err)
2020-11-03 12:49:32 +01:00
if err == io.EOF {
<-deinitNeededChan
return
}
2020-10-20 16:04:35 +02:00
}
}
// Do not send silence frames to the radio unnecessarily
2020-10-27 09:25:36 +01:00
if isAllZero(frameBuf[:n]) {
continue
}
2020-10-20 16:04:35 +02:00
buf.Write(frameBuf[:n])
for buf.Len() >= len(frameBuf) {
2020-10-27 11:55:09 +01:00
// 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)
2020-10-20 16:04:35 +02:00
if err != nil {
reportError(err)
2020-10-20 16:04:35 +02:00
}
if n != len(frameBuf) {
reportError(errors.New("audio buffer read error"))
}
select {
2020-10-27 11:55:09 +01:00
case a.rec <- b:
case <-deinitNeededChan:
return
2020-10-20 16:04:35 +02:00
}
2020-10-20 15:23:12 +02:00
}
}
}
func (a *audioStruct) loop() {
2020-10-30 20:34:42 +01:00
playLoopToVirtualSoundcardDeinitNeededChan := make(chan bool)
playLoopToVirtualSoundcardDeinitFinishedChan := make(chan bool)
go a.playLoopToVirtualSoundcard(playLoopToVirtualSoundcardDeinitNeededChan, playLoopToVirtualSoundcardDeinitFinishedChan)
2020-10-30 17:16:48 +01:00
playLoopToDefaultSoundcardDeinitNeededChan := make(chan bool)
playLoopToDefaultSoundcardDeinitFinishedChan := make(chan bool)
go a.playLoopToDefaultSoundcard(playLoopToDefaultSoundcardDeinitNeededChan, playLoopToDefaultSoundcardDeinitFinishedChan)
2020-10-30 17:25:09 +01:00
2020-10-30 20:34:42 +01:00
recLoopFromVirtualSoundcardDeinitNeededChan := make(chan bool)
recLoopFromVirtualSoundcardDeinitFinishedChan := make(chan bool)
go a.recLoopFromVirtualSoundcard(recLoopFromVirtualSoundcardDeinitNeededChan, recLoopFromVirtualSoundcardDeinitFinishedChan)
var d []byte
2020-10-20 15:23:12 +02:00
for {
select {
case d = <-a.play:
case <-a.deinitNeededChan:
a.closeIfNeeded()
2020-10-25 11:03:39 +01:00
2020-10-30 20:34:42 +01:00
recLoopFromVirtualSoundcardDeinitNeededChan <- true
<-recLoopFromVirtualSoundcardDeinitFinishedChan
playLoopToVirtualSoundcardDeinitNeededChan <- true
<-playLoopToVirtualSoundcardDeinitFinishedChan
if a.defaultSoundcardStream.playStream != nil {
a.defaultSoundCardPlayStreamDeinit()
2020-10-30 17:16:48 +01:00
}
playLoopToDefaultSoundcardDeinitNeededChan <- true
<-playLoopToDefaultSoundcardDeinitFinishedChan
a.deinitFinishedChan <- true
return
}
2020-10-30 17:16:48 +01:00
a.virtualSoundcardStream.mutex.Lock()
free := maxPlayBufferSize - a.virtualSoundcardStream.playBuf.Len()
if free < len(d) {
b := make([]byte, len(d)-free)
2020-10-30 17:16:48 +01:00
_, _ = a.virtualSoundcardStream.playBuf.Read(b)
}
2020-10-30 17:16:48 +01:00
a.virtualSoundcardStream.playBuf.Write(d)
a.virtualSoundcardStream.mutex.Unlock()
2020-10-20 15:23:12 +02:00
// Non-blocking notify.
2020-10-20 16:04:35 +02:00
select {
2020-10-30 17:16:48 +01:00
case a.virtualSoundcardStream.canPlay <- true:
2020-10-20 16:04:35 +02:00
default:
2020-10-20 15:23:12 +02:00
}
2020-10-30 17:16:48 +01:00
if a.defaultSoundcardStream.playStream != nil {
2020-10-30 17:16:48 +01:00
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:
}
}
2020-10-20 15:23:12 +02:00
}
}
// 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()
2020-10-27 14:20:27 +01:00
2020-10-30 17:16:48 +01:00
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 {
2020-10-30 17:16:48 +01:00
if i.Filename == a.virtualSoundcardStream.source.Filename {
i.Close()
}
2020-10-27 14:12:20 +01:00
}
}
2020-10-30 17:16:48 +01:00
if err := a.virtualSoundcardStream.source.Open(); err != nil {
return err
2020-10-27 14:12:20 +01:00
}
}
2020-10-30 17:16:48 +01:00
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 {
2020-10-30 17:16:48 +01:00
if i.Filename == a.virtualSoundcardStream.sink.Filename {
i.Close()
}
}
}
2020-10-20 15:23:12 +02:00
2020-10-30 17:16:48 +01:00
if err := a.virtualSoundcardStream.sink.Open(); err != nil {
return err
}
2020-10-20 15:23:12 +02:00
}
2020-10-30 17:16:48 +01:00
if a.virtualSoundcardStream.playBuf == nil {
log.Print("opened device " + a.virtualSoundcardStream.source.Name)
2020-10-23 16:20:09 +02:00
2020-10-30 17:25:09 +01:00
a.play = make(chan []byte)
a.rec = make(chan []byte)
2020-10-30 17:16:48 +01:00
a.virtualSoundcardStream.playBuf = bytes.NewBuffer([]byte{})
a.defaultSoundcardStream.playBuf = bytes.NewBuffer([]byte{})
a.virtualSoundcardStream.canPlay = make(chan bool)
a.defaultSoundcardStream.canPlay = make(chan bool)
a.defaultSoundcardStream.togglePlaybackChan = make(chan bool)
2020-10-30 17:25:09 +01:00
a.deinitNeededChan = make(chan bool)
a.deinitFinishedChan = make(chan bool)
go a.loop()
}
return nil
2020-10-20 15:23:12 +02:00
}
func (a *audioStruct) closeIfNeeded() {
2020-10-30 17:16:48 +01:00
if a.virtualSoundcardStream.source.IsOpen() {
if err := a.virtualSoundcardStream.source.Close(); err != nil {
2020-10-20 16:04:35 +02:00
if _, ok := err.(*os.PathError); !ok {
log.Error(err)
}
2020-10-20 15:23:12 +02:00
}
}
2020-10-30 17:16:48 +01:00
if a.virtualSoundcardStream.sink.IsOpen() {
if err := a.virtualSoundcardStream.sink.Close(); err != nil {
2020-10-20 16:04:35 +02:00
if _, ok := err.(*os.PathError); !ok {
log.Error(err)
}
2020-10-20 15:23:12 +02:00
}
}
2020-10-25 11:03:39 +01:00
}
func (a *audioStruct) deinit() {
a.defaultSoundCardRecStreamDeinit()
a.closeIfNeeded()
if a.deinitNeededChan != nil {
a.deinitNeededChan <- true
<-a.deinitFinishedChan
}
2020-10-20 15:23:12 +02:00
}