This repository has been archived on 2019-06-23. You can view files and clone it, but cannot push or open issues/pull-requests.
mumbledj/vendor/github.com/layeh/gumble/gumbleopenal/stream.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)
}
}
}