kappanhang/audio-linux.go

308 lines
6.7 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"
"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
2020-10-20 15:23:12 +02:00
source papipes.Source
sink papipes.Sink
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-20 17:20:52 +02:00
mutex sync.Mutex
2020-10-20 15:23:12 +02:00
playBuf *bytes.Buffer
canPlay chan bool
togglePlaybackToDefaultSoundcardChan chan bool
defaultSoundCardStream *pulse.Stream
2020-10-20 15:23:12 +02:00
}
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) {
2020-10-20 15:23:12 +02:00
for {
select {
case <-a.canPlay:
case <-a.togglePlaybackToDefaultSoundcardChan:
a.togglePlaybackToDefaultSoundcard()
case <-deinitNeededChan:
deinitFinishedChan <- true
return
}
2020-10-20 15:23:12 +02:00
for {
2020-10-20 17:20:52 +02:00
a.mutex.Lock()
if a.playBuf.Len() < audioFrameSize {
2020-10-20 17:20:52 +02:00
a.mutex.Unlock()
break
}
d := make([]byte, audioFrameSize)
2020-10-20 17:20:52 +02:00
bytesToWrite, err := a.playBuf.Read(d)
a.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
a.playToVirtualSoundcard(d[:bytesToWrite])
if a.defaultSoundCardStream != nil {
a.playToDefaultSoundcard(d[:bytesToWrite])
2020-10-20 17:20:52 +02:00
}
2020-10-20 16:04:35 +02:00
}
}
}
func (a *audioStruct) recLoop(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-20 16:04:35 +02:00
n, err := a.sink.Read(frameBuf)
if err != nil {
if _, ok := err.(*os.PathError); !ok {
reportError(err)
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() {
playLoopDeinitNeededChan := make(chan bool)
playLoopDeinitFinishedChan := make(chan bool)
go a.playLoop(playLoopDeinitNeededChan, playLoopDeinitFinishedChan)
recLoopDeinitNeededChan := make(chan bool)
recLoopDeinitFinishedChan := make(chan bool)
go a.recLoop(recLoopDeinitNeededChan, recLoopDeinitFinishedChan)
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
recLoopDeinitNeededChan <- true
<-recLoopDeinitFinishedChan
playLoopDeinitNeededChan <- true
<-playLoopDeinitFinishedChan
a.deinitFinishedChan <- true
return
}
2020-10-20 17:20:52 +02:00
a.mutex.Lock()
free := maxPlayBufferSize - a.playBuf.Len()
if free < len(d) {
b := make([]byte, len(d)-free)
_, _ = a.playBuf.Read(b)
}
2020-10-20 16:04:35 +02:00
a.playBuf.Write(d)
2020-10-20 17:20:52 +02:00
a.mutex.Unlock()
2020-10-20 15:23:12 +02:00
// Non-blocking notify.
2020-10-20 16:04:35 +02:00
select {
case a.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
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)
// Cleanup previous pipes.
sources, err := papipes.GetActiveSources()
if err == nil {
for _, i := range sources {
if i.Filename == a.source.Filename {
i.Close()
}
2020-10-27 14:12:20 +01:00
}
}
if err := a.source.Open(); err != nil {
return err
2020-10-27 14:12:20 +01:00
}
}
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)
// Cleanup previous pipes.
sinks, err := papipes.GetActiveSinks()
if err == nil {
for _, i := range sinks {
if i.Filename == a.sink.Filename {
i.Close()
}
}
}
2020-10-20 15:23:12 +02:00
if err := a.sink.Open(); err != nil {
return err
}
2020-10-20 15:23:12 +02:00
}
if a.playBuf == nil {
log.Print("opened device " + a.source.Name)
2020-10-23 16:20:09 +02:00
a.playBuf = bytes.NewBuffer([]byte{})
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()
}
return nil
2020-10-20 15:23:12 +02:00
}
func (a *audioStruct) closeIfNeeded() {
2020-10-20 15:23:12 +02:00
if a.source.IsOpen() {
if err := a.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
}
}
if a.sink.IsOpen() {
if err := a.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.closeIfNeeded()
if a.deinitNeededChan != nil {
a.deinitNeededChan <- true
<-a.deinitFinishedChan
}
2020-10-20 15:23:12 +02:00
}