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