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) } } }