Serialize OpenAL playback on a dedicated thread
This commit is contained in:
committed by
Brandon McGinty
parent
0f2eb4408f
commit
8c0887d562
+3
-15
@@ -27,7 +27,7 @@ type Barnard struct {
|
||||
Address string
|
||||
TLSConfig tls.Config
|
||||
|
||||
Stream *gumbleopenal.Stream
|
||||
Stream *gumbleopenal.Stream
|
||||
Tx bool
|
||||
AutoTransmit bool // auto-start transmission on connect
|
||||
Connected bool
|
||||
@@ -136,13 +136,7 @@ func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm
|
||||
b.UserConfig.ToggleMute(u)
|
||||
}
|
||||
|
||||
if au := u.AudioSource(); au != nil {
|
||||
if u.LocallyMuted() {
|
||||
au.SetGain(0)
|
||||
} else {
|
||||
au.SetGain(u.Volume())
|
||||
}
|
||||
}
|
||||
b.Stream.UpdateUserGain(u)
|
||||
}
|
||||
|
||||
// Update channel mute state
|
||||
@@ -174,13 +168,7 @@ func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm
|
||||
if key == *b.Hotkeys.MuteToggle {
|
||||
// Toggle mute for single user
|
||||
b.UserConfig.ToggleMute(treeItem.User)
|
||||
if au := treeItem.User.AudioSource(); au != nil {
|
||||
if treeItem.User.LocallyMuted() {
|
||||
au.SetGain(0)
|
||||
} else {
|
||||
au.SetGain(treeItem.User.Volume())
|
||||
}
|
||||
}
|
||||
b.Stream.UpdateUserGain(treeItem.User)
|
||||
b.RebuildUserChannelTreePreservingSelection()
|
||||
b.Ui.Refresh()
|
||||
}
|
||||
|
||||
@@ -243,8 +243,8 @@ func (b *Barnard) OnUserChange(e *gumble.UserChangeEvent) {
|
||||
if !e.User.LocallyMuted() {
|
||||
b.UserConfig.ToggleMute(e.User)
|
||||
}
|
||||
if au := e.User.AudioSource(); au != nil {
|
||||
au.SetGain(0)
|
||||
if b.Stream != nil {
|
||||
b.Stream.UpdateUserGain(e.User)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@ import (
|
||||
"errors"
|
||||
"math"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/barnard/audio"
|
||||
"git.stormux.org/storm/barnard/log"
|
||||
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
|
||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||
"git.stormux.org/storm/barnard/log"
|
||||
"git.stormux.org/storm/barnard/noise"
|
||||
)
|
||||
|
||||
@@ -55,6 +56,11 @@ func beep() {
|
||||
}
|
||||
}
|
||||
|
||||
type renderCommand struct {
|
||||
fn func()
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type Stream struct {
|
||||
client *gumble.Client
|
||||
link gumble.Detacher
|
||||
@@ -68,6 +74,8 @@ type Stream struct {
|
||||
|
||||
deviceSink *openal.Device
|
||||
contextSink *openal.Context
|
||||
renderCh chan renderCommand
|
||||
renderDone chan struct{}
|
||||
|
||||
noiseProcessor NoiseProcessor
|
||||
noiseProcessorRight NoiseProcessor
|
||||
@@ -147,11 +155,40 @@ func New(client *gumble.Client, inputDevice *string, outputDevice *string, test
|
||||
s.Destroy()
|
||||
return nil, ErrOutputDevice
|
||||
}
|
||||
s.contextSink.Activate()
|
||||
// OpenAL contexts are current to an OS thread. Move ownership to one
|
||||
// dedicated render thread before any source or buffer is created.
|
||||
openal.NullContext.Activate()
|
||||
s.startRenderer()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Stream) startRenderer() {
|
||||
s.renderCh = make(chan renderCommand)
|
||||
s.renderDone = make(chan struct{})
|
||||
ready := make(chan struct{})
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
s.contextSink.Activate()
|
||||
close(ready)
|
||||
defer close(s.renderDone)
|
||||
for command := range s.renderCh {
|
||||
command.fn()
|
||||
close(command.done)
|
||||
}
|
||||
openal.NullContext.Activate()
|
||||
}()
|
||||
<-ready
|
||||
}
|
||||
|
||||
// render executes fn on the sole OS thread that owns the OpenAL context.
|
||||
func (s *Stream) render(fn func()) {
|
||||
command := renderCommand{fn: fn, done: make(chan struct{})}
|
||||
s.renderCh <- command
|
||||
<-command.done
|
||||
}
|
||||
|
||||
func (s *Stream) AttachStream(client *gumble.Client) {
|
||||
s.link = client.Config.AttachAudio(s)
|
||||
}
|
||||
@@ -169,6 +206,20 @@ func (s *Stream) GetFilePlayer() FilePlayer {
|
||||
return s.filePlayer
|
||||
}
|
||||
|
||||
// UpdateUserGain applies a user's current mute and volume state on the
|
||||
// renderer thread.
|
||||
func (s *Stream) UpdateUserGain(user *gumble.User) {
|
||||
s.render(func() {
|
||||
if source := user.AudioSource(); source != nil {
|
||||
if user.LocallyMuted() {
|
||||
source.SetGain(0)
|
||||
} else {
|
||||
source.SetGain(user.Volume())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// SetErrorFunc sets a callback that is invoked when the microphone
|
||||
// capture device fails to provide audio data.
|
||||
func (s *Stream) SetErrorFunc(f func(error)) {
|
||||
@@ -197,9 +248,13 @@ func (s *Stream) Destroy() {
|
||||
s.deviceSource = nil
|
||||
}
|
||||
if s.deviceSink != nil {
|
||||
s.contextSink.Destroy()
|
||||
if s.contextSink != nil {
|
||||
s.render(func() { s.contextSink.Destroy() })
|
||||
close(s.renderCh)
|
||||
<-s.renderDone
|
||||
s.contextSink = nil
|
||||
}
|
||||
s.deviceSink.CloseDevice()
|
||||
s.contextSink = nil
|
||||
s.deviceSink = nil
|
||||
}
|
||||
}
|
||||
@@ -257,32 +312,34 @@ func (s *Stream) SetMicVolume(change float32, relative bool) {
|
||||
func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
go func(e *gumble.AudioStreamEvent) {
|
||||
log.Info("audio stream started for user %s", e.User.Name)
|
||||
var source = openal.NewSource()
|
||||
e.User.SetAudioSource(&source)
|
||||
|
||||
// Set initial gain based on volume and mute state
|
||||
if e.User.LocallyMuted() {
|
||||
source.SetGain(0)
|
||||
} else {
|
||||
source.SetGain(e.User.Volume())
|
||||
}
|
||||
|
||||
bufferCount := e.Client.Config.Buffers
|
||||
if bufferCount < 64 {
|
||||
bufferCount = 64
|
||||
}
|
||||
emptyBufs := openal.NewBuffers(bufferCount)
|
||||
var source openal.Source
|
||||
var emptyBufs openal.Buffers
|
||||
var raw [maxBufferSize]byte
|
||||
s.render(func() {
|
||||
source = openal.NewSource()
|
||||
e.User.SetAudioSource(&source)
|
||||
if e.User.LocallyMuted() {
|
||||
source.SetGain(0)
|
||||
} else {
|
||||
source.SetGain(e.User.Volume())
|
||||
}
|
||||
bufferCount := e.Client.Config.Buffers
|
||||
if bufferCount < 64 {
|
||||
bufferCount = 64
|
||||
}
|
||||
emptyBufs = openal.NewBuffers(bufferCount)
|
||||
})
|
||||
|
||||
reclaim := func() {
|
||||
if n := source.BuffersProcessed(); n > 0 {
|
||||
reclaimedBufs := make(openal.Buffers, n)
|
||||
source.UnqueueBuffers(reclaimedBufs)
|
||||
emptyBufs = append(emptyBufs, reclaimedBufs...)
|
||||
}
|
||||
s.render(func() {
|
||||
if n := source.BuffersProcessed(); n > 0 {
|
||||
reclaimedBufs := make(openal.Buffers, n)
|
||||
source.UnqueueBuffers(reclaimedBufs)
|
||||
emptyBufs = append(emptyBufs, reclaimedBufs...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var raw [maxBufferSize]byte
|
||||
|
||||
// Jitter buffer: collects incoming packets, reorders by
|
||||
// sequence number, and releases them in order after a small
|
||||
// initial delay (3-5 packets / 30-50ms).
|
||||
@@ -354,7 +411,9 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
break
|
||||
}
|
||||
reclaim()
|
||||
emptyBufs = s.processAudioPacket(pkt, e.User, &source, emptyBufs, &raw)
|
||||
s.render(func() {
|
||||
emptyBufs = s.processAudioPacket(pkt, e.User, &source, emptyBufs, &raw)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,12 +427,17 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
}
|
||||
if pkt != nil {
|
||||
reclaim()
|
||||
emptyBufs = s.processAudioPacket(pkt, e.User, &source, emptyBufs, &raw)
|
||||
s.render(func() {
|
||||
emptyBufs = s.processAudioPacket(pkt, e.User, &source, emptyBufs, &raw)
|
||||
})
|
||||
}
|
||||
}
|
||||
reclaim()
|
||||
emptyBufs.Delete()
|
||||
source.Delete()
|
||||
s.render(func() {
|
||||
emptyBufs.Delete()
|
||||
source.Delete()
|
||||
e.User.SetAudioSource(nil)
|
||||
})
|
||||
log.Debug("audio stream ended for user %s", e.User.Name)
|
||||
}(e)
|
||||
}
|
||||
|
||||
+6
-15
@@ -35,17 +35,13 @@ func (ti TreeItem) TreeItemStyle(fg, bg uiterm.Attribute, active bool) (uiterm.A
|
||||
|
||||
func (b *Barnard) changeVolume(users []*gumble.User, change float32) {
|
||||
for _, u := range users {
|
||||
au := u.AudioSource()
|
||||
if au == nil {
|
||||
if b.Stream == nil {
|
||||
continue
|
||||
}
|
||||
var boost uint16
|
||||
var cv float32
|
||||
var ng float32
|
||||
var curboost float32
|
||||
curboost = float32((u.Boost() - 1)) / 10
|
||||
cv = au.GetGain() + curboost
|
||||
ng = cv + change
|
||||
curboost := float32((u.Boost() - 1)) / 10
|
||||
ng = u.Volume() + curboost + change
|
||||
boost = uint16(1)
|
||||
if ng > 1.0 {
|
||||
perc := uint16((ng * 10)) - 10
|
||||
@@ -58,9 +54,7 @@ func (b *Barnard) changeVolume(users []*gumble.User, change float32) {
|
||||
}
|
||||
u.SetBoost(boost)
|
||||
u.SetVolume(ng)
|
||||
if !u.LocallyMuted() {
|
||||
au.SetGain(ng)
|
||||
}
|
||||
b.Stream.UpdateUserGain(u)
|
||||
b.UserConfig.UpdateConfig(u)
|
||||
}
|
||||
b.UserConfig.SaveConfig()
|
||||
@@ -68,16 +62,13 @@ func (b *Barnard) changeVolume(users []*gumble.User, change float32) {
|
||||
|
||||
func (b *Barnard) resetVolume(users []*gumble.User) {
|
||||
for _, u := range users {
|
||||
au := u.AudioSource()
|
||||
if au == nil {
|
||||
if b.Stream == nil {
|
||||
continue
|
||||
}
|
||||
// Reset to original volume (1.0) and boost (1)
|
||||
u.SetBoost(uint16(1))
|
||||
u.SetVolume(1.0)
|
||||
if !u.LocallyMuted() {
|
||||
au.SetGain(1.0)
|
||||
}
|
||||
b.Stream.UpdateUserGain(u)
|
||||
b.UserConfig.UpdateConfig(u)
|
||||
}
|
||||
b.UserConfig.SaveConfig()
|
||||
|
||||
Reference in New Issue
Block a user