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-20 15:23:12 +02:00
|
|
|
|
|
|
|
|
"github.com/akosmarton/papipes"
|
|
|
|
|
)
|
|
|
|
|
|
2020-10-26 10:33:24 +01:00
|
|
|
const audioSampleRate = 48000
|
|
|
|
|
|
2020-10-20 15:23:12 +02:00
|
|
|
type audioStruct struct {
|
|
|
|
|
source papipes.Source
|
|
|
|
|
sink papipes.Sink
|
|
|
|
|
|
2020-10-23 14:00:59 +02:00
|
|
|
deinitNeededChan chan bool
|
|
|
|
|
deinitFinishedChan chan bool
|
|
|
|
|
|
2020-10-20 15:23:12 +02:00
|
|
|
// Send to this channel to play audio.
|
|
|
|
|
play chan []byte
|
2020-10-21 09:26:21 +02:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 14:00:59 +02:00
|
|
|
func (a *audioStruct) playLoop(deinitNeededChan, deinitFinishedChan chan bool) {
|
2020-10-20 15:23:12 +02:00
|
|
|
for {
|
2020-10-23 14:00:59 +02:00
|
|
|
select {
|
|
|
|
|
case <-a.canPlay:
|
|
|
|
|
case <-deinitNeededChan:
|
|
|
|
|
deinitFinishedChan <- true
|
|
|
|
|
return
|
|
|
|
|
}
|
2020-10-20 15:23:12 +02:00
|
|
|
|
2020-10-20 16:22:26 +02:00
|
|
|
for {
|
2020-10-20 17:20:52 +02:00
|
|
|
a.mutex.Lock()
|
|
|
|
|
if a.playBuf.Len() < 1920 {
|
|
|
|
|
a.mutex.Unlock()
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d := make([]byte, 1920)
|
|
|
|
|
bytesToWrite, err := a.playBuf.Read(d)
|
|
|
|
|
a.mutex.Unlock()
|
2020-10-20 16:22:26 +02:00
|
|
|
if err != nil {
|
2020-10-20 17:20:52 +02:00
|
|
|
log.Error(err)
|
|
|
|
|
break
|
2020-10-20 16:22:26 +02:00
|
|
|
}
|
2020-10-20 17:20:52 +02:00
|
|
|
if bytesToWrite != len(d) {
|
|
|
|
|
log.Error("buffer underread")
|
2020-10-20 16:22:26 +02:00
|
|
|
break
|
2020-10-20 16:04:35 +02:00
|
|
|
}
|
2020-10-20 17:20:52 +02:00
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
written, err := a.source.Write(d)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if _, ok := err.(*os.PathError); !ok {
|
2020-10-23 14:00:59 +02:00
|
|
|
reportError(err)
|
2020-10-20 17:20:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bytesToWrite -= written
|
|
|
|
|
if bytesToWrite == 0 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
d = d[written:]
|
|
|
|
|
}
|
2020-10-20 16:04:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 14:00:59 +02:00
|
|
|
func (a *audioStruct) recLoop(deinitNeededChan, deinitFinishedChan chan bool) {
|
|
|
|
|
defer func() {
|
|
|
|
|
deinitFinishedChan <- true
|
|
|
|
|
}()
|
|
|
|
|
|
2020-10-20 16:04:35 +02:00
|
|
|
frameBuf := make([]byte, 1920)
|
|
|
|
|
buf := bytes.NewBuffer([]byte{})
|
|
|
|
|
|
|
|
|
|
for {
|
2020-10-23 14:00:59 +02:00
|
|
|
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 {
|
2020-10-23 14:00:59 +02:00
|
|
|
reportError(err)
|
2020-10-20 16:04:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf.Write(frameBuf[:n])
|
|
|
|
|
|
|
|
|
|
for buf.Len() >= len(frameBuf) {
|
|
|
|
|
n, err = buf.Read(frameBuf)
|
|
|
|
|
if err != nil {
|
2020-10-23 14:00:59 +02:00
|
|
|
reportError(err)
|
2020-10-20 16:04:35 +02:00
|
|
|
}
|
|
|
|
|
if n != len(frameBuf) {
|
2020-10-23 14:00:59 +02:00
|
|
|
reportError(errors.New("audio buffer read error"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case a.rec <- frameBuf:
|
|
|
|
|
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-23 14:00:59 +02:00
|
|
|
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 {
|
2020-10-23 14:00:59 +02:00
|
|
|
select {
|
|
|
|
|
case d = <-a.play:
|
|
|
|
|
case <-a.deinitNeededChan:
|
2020-10-25 11:03:39 +01:00
|
|
|
a.close()
|
|
|
|
|
|
2020-10-23 14:00:59 +02:00
|
|
|
recLoopDeinitNeededChan <- true
|
|
|
|
|
<-recLoopDeinitFinishedChan
|
|
|
|
|
playLoopDeinitNeededChan <- true
|
|
|
|
|
<-playLoopDeinitFinishedChan
|
|
|
|
|
|
|
|
|
|
a.deinitFinishedChan <- true
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-20 17:20:52 +02:00
|
|
|
a.mutex.Lock()
|
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
|
|
|
|
2020-10-23 14:00:59 +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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 14:00:59 +02:00
|
|
|
func (a *audioStruct) init(devName string) error {
|
|
|
|
|
a.source.Name = "kappanhang-" + devName
|
|
|
|
|
a.source.Filename = "/tmp/kappanhang-" + devName + ".source"
|
2020-10-26 10:33:24 +01:00
|
|
|
a.source.Rate = audioSampleRate
|
2020-10-20 15:23:12 +02:00
|
|
|
a.source.Format = "s16le"
|
|
|
|
|
a.source.Channels = 1
|
2020-10-26 10:33:24 +01:00
|
|
|
a.source.SetProperty("device.buffering.buffer_size", (audioSampleRate*16)/10) // 100 ms
|
2020-10-21 16:09:10 +02:00
|
|
|
a.source.SetProperty("device.description", "kappanhang: "+devName)
|
2020-10-20 15:23:12 +02:00
|
|
|
|
2020-10-23 14:00:59 +02:00
|
|
|
a.sink.Name = "kappanhang-" + devName
|
|
|
|
|
a.sink.Filename = "/tmp/kappanhang-" + devName + ".sink"
|
2020-10-26 10:33:24 +01:00
|
|
|
a.sink.Rate = audioSampleRate
|
2020-10-20 15:23:12 +02:00
|
|
|
a.sink.Format = "s16le"
|
|
|
|
|
a.sink.Channels = 1
|
2020-10-26 10:33:24 +01:00
|
|
|
a.sink.SetProperty("device.buffering.buffer_size", (audioSampleRate*16)/10)
|
2020-10-21 16:09:10 +02:00
|
|
|
a.sink.SetProperty("device.description", "kappanhang: "+devName)
|
2020-10-20 15:23:12 +02:00
|
|
|
|
|
|
|
|
if err := a.source.Open(); err != nil {
|
2020-10-23 14:00:59 +02:00
|
|
|
return err
|
2020-10-20 15:23:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := a.sink.Open(); err != nil {
|
2020-10-23 14:00:59 +02:00
|
|
|
return err
|
2020-10-20 15:23:12 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-23 16:20:09 +02:00
|
|
|
log.Print("opened device " + a.source.Name)
|
|
|
|
|
|
2020-10-20 15:23:12 +02:00
|
|
|
a.playBuf = bytes.NewBuffer([]byte{})
|
|
|
|
|
a.play = make(chan []byte)
|
|
|
|
|
a.canPlay = make(chan bool)
|
|
|
|
|
a.rec = make(chan []byte)
|
2020-10-23 14:00:59 +02:00
|
|
|
a.deinitNeededChan = make(chan bool)
|
|
|
|
|
a.deinitFinishedChan = make(chan bool)
|
2020-10-20 15:23:12 +02:00
|
|
|
go a.loop()
|
2020-10-23 14:00:59 +02:00
|
|
|
return nil
|
2020-10-20 15:23:12 +02:00
|
|
|
}
|
|
|
|
|
|
2020-10-25 11:03:39 +01:00
|
|
|
func (a *audioStruct) close() {
|
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.close()
|
2020-10-23 14:00:59 +02:00
|
|
|
|
|
|
|
|
if a.deinitNeededChan != nil {
|
|
|
|
|
a.deinitNeededChan <- true
|
|
|
|
|
<-a.deinitFinishedChan
|
|
|
|
|
}
|
2020-10-20 15:23:12 +02:00
|
|
|
}
|