144 lines
3.1 KiB
Go
144 lines
3.1 KiB
Go
package gumbleopenal
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/layeh/gumble/gumble"
|
|
"github.com/timshannon/go-openal/openal"
|
|
)
|
|
|
|
var (
|
|
ErrState = errors.New("gumbleopenal: invalid state")
|
|
)
|
|
|
|
type Stream struct {
|
|
client *gumble.Client
|
|
link gumble.Detacher
|
|
|
|
deviceSource *openal.CaptureDevice
|
|
sourceFrameSize int
|
|
sourceStop chan bool
|
|
|
|
deviceSink *openal.Device
|
|
contextSink *openal.Context
|
|
}
|
|
|
|
func New(client *gumble.Client) (*Stream, error) {
|
|
s := &Stream{
|
|
client: client,
|
|
sourceFrameSize: client.Config.AudioFrameSize(),
|
|
}
|
|
|
|
s.deviceSource = openal.CaptureOpenDevice("", gumble.AudioSampleRate, openal.FormatMono16, uint32(s.sourceFrameSize))
|
|
|
|
s.deviceSink = openal.OpenDevice("")
|
|
s.contextSink = s.deviceSink.CreateContext()
|
|
s.contextSink.Activate()
|
|
|
|
s.link = client.Config.AttachAudio(s)
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func (s *Stream) Destroy() {
|
|
s.link.Detach()
|
|
if s.deviceSource != nil {
|
|
s.StopSource()
|
|
s.deviceSource.CaptureCloseDevice()
|
|
s.deviceSource = nil
|
|
}
|
|
if s.deviceSink != nil {
|
|
s.contextSink.Destroy()
|
|
s.deviceSink.CloseDevice()
|
|
s.contextSink = nil
|
|
s.deviceSink = nil
|
|
}
|
|
}
|
|
|
|
func (s *Stream) StartSource() error {
|
|
if s.sourceStop != nil {
|
|
return ErrState
|
|
}
|
|
s.deviceSource.CaptureStart()
|
|
s.sourceStop = make(chan bool)
|
|
go s.sourceRoutine()
|
|
return nil
|
|
}
|
|
|
|
func (s *Stream) StopSource() error {
|
|
if s.sourceStop == nil {
|
|
return ErrState
|
|
}
|
|
close(s.sourceStop)
|
|
s.sourceStop = nil
|
|
s.deviceSource.CaptureStop()
|
|
return nil
|
|
}
|
|
|
|
func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
|
go func() {
|
|
source := openal.NewSource()
|
|
var raw [gumble.AudioMaximumFrameSize * 2]byte
|
|
for packet := range e.C {
|
|
samples := len(packet.AudioBuffer)
|
|
if samples > cap(raw) {
|
|
continue
|
|
}
|
|
for i, value := range packet.AudioBuffer {
|
|
binary.LittleEndian.PutUint16(raw[i*2:], uint16(value))
|
|
}
|
|
for source.BuffersProcessed() > 0 {
|
|
source.UnqueueBuffer().Delete()
|
|
}
|
|
buffer := openal.NewBuffer()
|
|
buffer.SetData(openal.FormatMono16, raw[:samples*2], gumble.AudioSampleRate)
|
|
source.QueueBuffer(buffer)
|
|
if source.State() != openal.Playing {
|
|
source.Play()
|
|
}
|
|
}
|
|
for source.BuffersProcessed() > 0 {
|
|
source.UnqueueBuffer().Delete()
|
|
}
|
|
source.Delete()
|
|
}()
|
|
}
|
|
|
|
func (s *Stream) sourceRoutine() {
|
|
interval := s.client.Config.AudioInterval
|
|
frameSize := s.client.Config.AudioFrameSize()
|
|
|
|
if frameSize != s.sourceFrameSize {
|
|
s.deviceSource.CaptureCloseDevice()
|
|
s.sourceFrameSize = frameSize
|
|
s.deviceSource = openal.CaptureOpenDevice("", gumble.AudioSampleRate, openal.FormatMono16, uint32(s.sourceFrameSize))
|
|
}
|
|
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
stop := s.sourceStop
|
|
|
|
outgoing := s.client.AudioOutgoing()
|
|
defer close(outgoing)
|
|
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
case <-ticker.C:
|
|
buff := s.deviceSource.CaptureSamples(uint32(frameSize))
|
|
if len(buff) != frameSize*2 {
|
|
continue
|
|
}
|
|
int16Buffer := make([]int16, frameSize)
|
|
for i := range int16Buffer {
|
|
int16Buffer[i] = int16(binary.LittleEndian.Uint16(buff[i*2 : (i+1)*2]))
|
|
}
|
|
outgoing <- gumble.AudioBuffer(int16Buffer)
|
|
}
|
|
}
|
|
}
|