From 715b5df7e117cda364f3e7071728aea3cbdb24d4 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 15 Apr 2016 01:37:48 +0900 Subject: [PATCH] audio: Consume necessary and sufficient data on each Update (#205) (WIP) --- exp/audio/audio.go | 90 +++++++++------------ exp/audio/internal/driver/driver_js.go | 49 +++++------ exp/audio/internal/driver/driver_openal.go | 46 ++++------- exp/audio/internal/driver/driver_windows.go | 49 +++++------ 4 files changed, 90 insertions(+), 144 deletions(-) diff --git a/exp/audio/audio.go b/exp/audio/audio.go index f682d9b57..22455ed2b 100644 --- a/exp/audio/audio.go +++ b/exp/audio/audio.go @@ -36,23 +36,14 @@ import ( ) type mixingStream struct { - sampleRate int - writtenBytes int - frames int - players map[*Player]struct{} + sampleRate int + players map[*Player]struct{} // Note that Read (and other methods) need to be concurrent safe // because Read is called from another groutine (see NewContext). sync.RWMutex } -func min(a, b int) int { - if a < b { - return a - } - return b -} - const ( channelNum = 2 bytesPerSample = 2 @@ -62,26 +53,24 @@ const ( ) func (s *mixingStream) SampleRate() int { - s.RLock() - defer s.RUnlock() return s.sampleRate } +func min(a, b int) int { + if a < b { + return a + } + return b +} + func (s *mixingStream) Read(b []byte) (int, error) { s.Lock() defer s.Unlock() - bytesPerFrame := s.sampleRate * bytesPerSample * channelNum / ebiten.FPS - x := s.frames*bytesPerFrame + len(b) - if x <= s.writtenBytes { - return 0, nil - } - if len(s.players) == 0 { - l := min(len(b), x-s.writtenBytes) + l := len(b) l &= mask copy(b, make([]byte, l)) - s.writtenBytes += l return l, nil } closed := []*Player{} @@ -120,17 +109,9 @@ func (s *mixingStream) Read(b []byte) (int, error) { for _, p := range closed { delete(s.players, p) } - s.writtenBytes += l return l, nil } -func (s *mixingStream) update() error { - s.Lock() - defer s.Unlock() - s.frames++ - return nil -} - func (s *mixingStream) newPlayer(src ReadSeekCloser) (*Player, error) { s.Lock() defer s.Unlock() @@ -219,40 +200,26 @@ func (s *mixingStream) playerCurrent(player *Player) time.Duration { // You can also call Update independently from the game loop as 'async mode'. // In this case, audio goes on even when the game stops e.g. by diactivating the screen. type Context struct { - stream *mixingStream - errorCh chan error + stream *mixingStream + driver *driver.Player + frames int + writtenBytes int } // NewContext creates a new audio context with the given sample rate (e.g. 44100). func NewContext(sampleRate int) (*Context, error) { // TODO: Panic if one context exists. - c := &Context{ - errorCh: make(chan error), - } + c := &Context{} c.stream = &mixingStream{ sampleRate: sampleRate, players: map[*Player]struct{}{}, } // TODO: Rename this other than player - p, err := driver.NewPlayer(c.stream, sampleRate, channelNum, bytesPerSample) + p, err := driver.NewPlayer(sampleRate, channelNum, bytesPerSample) + c.driver = p if err != nil { return nil, err } - go func() { - // TODO: Is it OK to close asap? - defer p.Close() - for { - err := p.Proceed() - if err == io.EOF { - break - } - if err != nil { - c.errorCh <- err - return - } - time.Sleep(1 * time.Millisecond) - } - }() return c, nil } @@ -264,12 +231,27 @@ func NewContext(sampleRate int) (*Context, error) { // you will find audio stops when the game stops e.g. when the window is deactivated. // In async mode, the audio never stops even when the game stops. func (c *Context) Update() error { - select { - case err := <-c.errorCh: + c.frames++ + bytesPerFrame := c.stream.sampleRate * bytesPerSample * channelNum / ebiten.FPS + l := (c.frames * bytesPerFrame) - c.writtenBytes + l &= mask + c.writtenBytes += l + buf := make([]byte, l) + n, err := io.ReadFull(c.stream, buf) + if err != nil { return err - default: } - return c.stream.update() + if n != len(buf) { + return c.driver.Close() + } + err = c.driver.Proceed(buf) + if err == io.EOF { + return c.driver.Close() + } + if err != nil { + return err + } + return nil } // SampleRate returns the sample rate. diff --git a/exp/audio/internal/driver/driver_js.go b/exp/audio/internal/driver/driver_js.go index 28354004a..da9db9fc5 100644 --- a/exp/audio/internal/driver/driver_js.go +++ b/exp/audio/internal/driver/driver_js.go @@ -18,13 +18,11 @@ package driver import ( "errors" - "io" "github.com/gopherjs/gopherjs/js" ) type Player struct { - src io.Reader sampleRate int channelNum int bytesPerSample int @@ -33,7 +31,7 @@ type Player struct { bufferSource *js.Object } -func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Player, error) { +func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { class := js.Global.Get("AudioContext") if class == js.Undefined { class = js.Global.Get("webkitAudioContext") @@ -42,7 +40,6 @@ func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Play return nil, errors.New("driver: audio couldn't be initialized") } p := &Player{ - src: src, sampleRate: sampleRate, channelNum: channelNum, bytesPerSample: bytesPerSample, @@ -63,37 +60,29 @@ func toLR(data []byte) ([]int16, []int16) { return l, r } -const ( - // 1024 seems not enough (some noise remains after the tab is deactivated). - bufferSize = 2048 -) - -func (p *Player) Proceed() error { +func (p *Player) Proceed(data []byte) error { c := int64(p.context.Get("currentTime").Float() * float64(p.sampleRate)) if p.positionInSamples < c { p.positionInSamples = c } - b := make([]byte, bufferSize) - n, err := p.src.Read(b) - if 0 < n { - buf := p.context.Call("createBuffer", p.channelNum, n/p.bytesPerSample/p.channelNum, p.sampleRate) - l := buf.Call("getChannelData", 0) - r := buf.Call("getChannelData", 1) - il, ir := toLR(b[:n]) - const max = 1 << 15 - for i := 0; i < len(il); i++ { - l.SetIndex(i, float64(il[i])/max) - r.SetIndex(i, float64(ir[i])/max) - } - p.bufferSource = p.context.Call("createBufferSource") - p.bufferSource.Set("buffer", buf) - p.bufferSource.Call("connect", p.context.Get("destination")) - p.bufferSource.Call("start", float64(p.positionInSamples)/float64(p.sampleRate)) - // Call 'stop' or we'll get noisy sound especially on Chrome. - p.bufferSource.Call("stop", float64(p.positionInSamples+int64(len(il)))/float64(p.sampleRate)) - p.positionInSamples += int64(len(il)) + n := len(data) + buf := p.context.Call("createBuffer", p.channelNum, n/p.bytesPerSample/p.channelNum, p.sampleRate) + l := buf.Call("getChannelData", 0) + r := buf.Call("getChannelData", 1) + il, ir := toLR(data) + const max = 1 << 15 + for i := 0; i < len(il); i++ { + l.SetIndex(i, float64(il[i])/max) + r.SetIndex(i, float64(ir[i])/max) } - return err + p.bufferSource = p.context.Call("createBufferSource") + p.bufferSource.Set("buffer", buf) + p.bufferSource.Call("connect", p.context.Get("destination")) + p.bufferSource.Call("start", float64(p.positionInSamples)/float64(p.sampleRate)) + // Call 'stop' or we'll get noisy sound especially on Chrome. + p.bufferSource.Call("stop", float64(p.positionInSamples+int64(len(il)))/float64(p.sampleRate)) + p.positionInSamples += int64(len(il)) + return nil } func (p *Player) Close() error { diff --git a/exp/audio/internal/driver/driver_openal.go b/exp/audio/internal/driver/driver_openal.go index 4306c9c51..4f18409ea 100644 --- a/exp/audio/internal/driver/driver_openal.go +++ b/exp/audio/internal/driver/driver_openal.go @@ -17,8 +17,8 @@ package driver import ( + "errors" "fmt" - "io" "runtime" "github.com/hajimehoshi/go-openal/openal" @@ -35,7 +35,6 @@ type Player struct { alDevice *openal.Device alSource openal.Source alBuffers []openal.Buffer - source io.Reader sampleRate int isClosed bool alFormat openal.Format @@ -55,7 +54,7 @@ func alFormat(channelNum, bytesPerSample int) openal.Format { panic(fmt.Sprintf("driver: invalid channel num (%d) or bytes per sample (%d)", channelNum, bytesPerSample)) } -func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Player, error) { +func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { d := openal.OpenDevice("") if d == nil { return nil, fmt.Errorf("driver: OpenDevice must not return nil") @@ -78,39 +77,31 @@ func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Play alDevice: d, alSource: s, alBuffers: []openal.Buffer{}, - source: src, sampleRate: sampleRate, alFormat: alFormat(channelNum, bytesPerSample), } runtime.SetFinalizer(p, (*Player).Close) bs := openal.NewBuffers(maxBufferNum) + const bufferSize = 1024 emptyBytes := make([]byte, bufferSize) for _, b := range bs { // Note that the third argument of only the first buffer is used. b.SetData(p.alFormat, emptyBytes, int32(p.sampleRate)) - p.alSource.QueueBuffer(b) + //p.alSource.QueueBuffer(b) + p.alBuffers = append(p.alBuffers, b) } p.alSource.Play() return p, nil } -const ( - bufferSize = 1024 -) - -var ( - tmpBuffer = make([]byte, bufferSize) - tmpAlBuffers = make([]openal.Buffer, maxBufferNum) -) - -func (p *Player) Proceed() error { +func (p *Player) Proceed(data []byte) error { if err := openal.Err(); err != nil { return fmt.Errorf("driver: starting Proceed: %v", err) } processedNum := p.alSource.BuffersProcessed() if 0 < processedNum { - bufs := tmpAlBuffers[:processedNum] + bufs := make([]openal.Buffer, processedNum) p.alSource.UnqueueBuffers(bufs) if err := openal.Err(); err != nil { return fmt.Errorf("driver: UnqueueBuffers: %v", err) @@ -118,20 +109,15 @@ func (p *Player) Proceed() error { p.alBuffers = append(p.alBuffers, bufs...) } - if 0 < len(p.alBuffers) { - n, err := p.source.Read(tmpBuffer) - if 0 < n { - buf := p.alBuffers[0] - p.alBuffers = p.alBuffers[1:] - buf.SetData(p.alFormat, tmpBuffer[:n], int32(p.sampleRate)) - p.alSource.QueueBuffer(buf) - if err := openal.Err(); err != nil { - return fmt.Errorf("driver: QueueBuffer: %v", err) - } - } - if err != nil { - return err - } + if len(p.alBuffers) == 0 { + return errors.New("driver: p.alBuffers must > 0") + } + buf := p.alBuffers[0] + p.alBuffers = p.alBuffers[1:] + buf.SetData(p.alFormat, data, int32(p.sampleRate)) + p.alSource.QueueBuffer(buf) + if err := openal.Err(); err != nil { + return fmt.Errorf("driver: QueueBuffer: %v", err) } if p.alSource.State() == openal.Stopped || p.alSource.State() == openal.Initial { diff --git a/exp/audio/internal/driver/driver_windows.go b/exp/audio/internal/driver/driver_windows.go index 2d49fcc23..677065f5f 100644 --- a/exp/audio/internal/driver/driver_windows.go +++ b/exp/audio/internal/driver/driver_windows.go @@ -27,7 +27,6 @@ import "C" import ( "errors" "fmt" - "io" "unsafe" ) @@ -76,7 +75,6 @@ func releaseSemaphore() { } type Player struct { - src io.Reader out C.HWAVEOUT buffer []byte headers []*header @@ -84,7 +82,7 @@ type Player struct { const bufferSize = 1024 -func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Player, error) { +func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { numBlockAlign := channelNum * bytesPerSample f := C.WAVEFORMATEX{ wFormatTag: C.WAVE_FORMAT_PCM, @@ -99,7 +97,6 @@ func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Play return nil, fmt.Errorf("driver: waveOutOpen error: %d", err) } p := &Player{ - src: src, out: w, buffer: []byte{}, headers: make([]*header, numHeader), @@ -114,35 +111,27 @@ func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Play return p, nil } -func (p *Player) Proceed() error { - if len(p.buffer) < bufferSize { - b := make([]byte, bufferSize) - n, err := p.src.Read(b) - if 0 < n { - p.buffer = append(p.buffer, b[:n]...) - } - if err != nil { - return err +func (p *Player) Proceed(data []byte) error { + p.buffer = append(p.buffer, data...) + if bufferSize > len(p.buffer) { + return nil + } + sem <- struct{}{} + headerToWrite := (*header)(nil) + for _, h := range p.headers { + // TODO: Need to check WHDR_DONE? + if h.waveHdr.dwFlags&C.WHDR_INQUEUE == 0 { + headerToWrite = h + break } } - if bufferSize <= len(p.buffer) { - sem <- struct{}{} - headerToWrite := (*header)(nil) - for _, h := range p.headers { - // TODO: Need to check WHDR_DONE? - if h.waveHdr.dwFlags&C.WHDR_INQUEUE == 0 { - headerToWrite = h - break - } - } - if headerToWrite == nil { - return errors.New("driver: no available buffers") - } - if err := headerToWrite.Write(p.out, p.buffer[:bufferSize]); err != nil { - return err - } - p.buffer = p.buffer[bufferSize:] + if headerToWrite == nil { + return errors.New("driver: no available buffers") } + if err := headerToWrite.Write(p.out, p.buffer[:bufferSize]); err != nil { + return err + } + p.buffer = p.buffer[bufferSize:] return nil }