From 2b248ef7830b7f3e0bb0b801b69153ba177fa310 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Mon, 8 Aug 2022 23:57:01 +0900 Subject: [PATCH] audio: refactoring: remove audio/internal/cbackend --- audio/context_cbackend.go | 30 +- audio/internal/cbackend/context.go | 67 ----- audio/internal/cbackend/player.go | 435 ----------------------------- go.mod | 2 +- go.sum | 4 +- 5 files changed, 28 insertions(+), 510 deletions(-) delete mode 100644 audio/internal/cbackend/context.go delete mode 100644 audio/internal/cbackend/player.go diff --git a/audio/context_cbackend.go b/audio/context_cbackend.go index e05274166..7b1442f60 100644 --- a/audio/context_cbackend.go +++ b/audio/context_cbackend.go @@ -20,20 +20,40 @@ package audio import ( "io" - "github.com/hajimehoshi/ebiten/v2/audio/internal/cbackend" + "github.com/hajimehoshi/oto/v2/mux" + + "github.com/hajimehoshi/ebiten/v2/internal/cbackend" ) func newContext(sampleRate, channelCount, bitDepthInBytes int) (context, chan struct{}, error) { - ctx, ready, err := cbackend.NewContext(sampleRate, channelCount, bitDepthInBytes) - return &contextProxy{ctx}, ready, err + ready := make(chan struct{}) + close(ready) + + c := &contextProxy{mux.New(sampleRate, channelCount, bitDepthInBytes)} + cbackend.OpenAudio(sampleRate, channelCount, c.mux.ReadFloat32s) + return c, ready, nil } // contextProxy is a proxy between cbackend.Context and context. type contextProxy struct { - *cbackend.Context + mux *mux.Mux } // NewPlayer implements context. func (c *contextProxy) NewPlayer(r io.Reader) player { - return c.Context.NewPlayer(r) + return c.mux.NewPlayer(r) +} + +func (c *contextProxy) Suspend() error { + // Do nothing so far. + return nil +} + +func (c *contextProxy) Resume() error { + // Do nothing so far. + return nil +} + +func (c *contextProxy) Err() error { + return nil } diff --git a/audio/internal/cbackend/context.go b/audio/internal/cbackend/context.go deleted file mode 100644 index c0cb780b3..000000000 --- a/audio/internal/cbackend/context.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2021 The Ebiten Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build ebitenginecbackend || ebitencbackend -// +build ebitenginecbackend ebitencbackend - -package cbackend - -import ( - "io" - - "github.com/hajimehoshi/ebiten/v2/internal/cbackend" -) - -type Context struct { - sampleRate int - channelCount int - bitDepthInBytes int - - players *players -} - -func NewContext(sampleRate, channelCount, bitDepthInBytes int) (*Context, chan struct{}, error) { - c := &Context{ - sampleRate: sampleRate, - channelCount: channelCount, - bitDepthInBytes: bitDepthInBytes, - players: newPlayers(), - } - cbackend.OpenAudio(sampleRate, channelCount, c.players.read) - ready := make(chan struct{}) - close(ready) - return c, ready, nil -} - -func (c *Context) NewPlayer(src io.Reader) *Player { - return newPlayer(c, src) -} - -func (c *Context) Suspend() error { - // Do nothing so far. - return nil -} - -func (c *Context) Resume() error { - // Do nothing so far. - return nil -} - -func (c *Context) Err() error { - return nil -} - -func (c *Context) defaultBufferSize() int { - return c.sampleRate * c.channelCount * c.bitDepthInBytes / 2 // 0.5[s] -} diff --git a/audio/internal/cbackend/player.go b/audio/internal/cbackend/player.go deleted file mode 100644 index 1e31a4ed5..000000000 --- a/audio/internal/cbackend/player.go +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright 2021 The Ebiten Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build ebitenginecbackend || ebitencbackend -// +build ebitenginecbackend ebitencbackend - -// TODO: This implementation is very similar to github.com/hajimehoshi/oto/v2's player.go -// Unify them if possible. - -package cbackend - -import ( - "errors" - "io" - "runtime" - "sync" -) - -type playerState int - -const ( - playerPaused playerState = iota - playerPlay - playerClosed -) - -type players struct { - players map[*playerImpl]struct{} - buf []float32 - cond *sync.Cond -} - -func newPlayers() *players { - p := &players{ - cond: sync.NewCond(&sync.Mutex{}), - } - go p.loop() - return p -} - -func (ps *players) shouldWait() bool { - for p := range ps.players { - if p.canReadSourceToBuffer() { - return false - } - } - return true -} - -func (ps *players) wait() { - ps.cond.L.Lock() - defer ps.cond.L.Unlock() - - for ps.shouldWait() { - ps.cond.Wait() - } -} - -func (ps *players) loop() { - var players []*playerImpl - for { - ps.wait() - - ps.cond.L.Lock() - players = players[:0] - for p := range ps.players { - players = append(players, p) - } - ps.cond.L.Unlock() - - for _, p := range players { - p.readSourceToBuffer() - } - } -} - -func (ps *players) addPlayer(player *playerImpl) { - ps.cond.L.Lock() - defer ps.cond.L.Unlock() - - if ps.players == nil { - ps.players = map[*playerImpl]struct{}{} - } - ps.players[player] = struct{}{} - ps.cond.Signal() -} - -func (ps *players) removePlayer(player *playerImpl) { - ps.cond.L.Lock() - defer ps.cond.L.Unlock() - - delete(ps.players, player) - ps.cond.Signal() -} - -func (ps *players) read(buf []float32) { - ps.cond.L.Lock() - players := make([]*playerImpl, 0, len(ps.players)) - for p := range ps.players { - players = append(players, p) - } - ps.cond.L.Unlock() - - for i := range buf { - buf[i] = 0 - } - for _, p := range players { - p.readBufferAndAdd(buf) - } - ps.cond.Signal() -} - -type Player struct { - p *playerImpl -} - -type playerImpl struct { - context *Context - src io.Reader - volume float64 - err error - state playerState - tmpbuf []byte - buf []byte - eof bool - bufferSize int - - m sync.Mutex -} - -func newPlayer(context *Context, src io.Reader) *Player { - p := &Player{ - p: &playerImpl{ - context: context, - src: src, - volume: 1, - bufferSize: context.defaultBufferSize(), - }, - } - runtime.SetFinalizer(p, (*Player).Close) - return p -} - -func (p *Player) Err() error { - return p.p.Err() -} - -func (p *playerImpl) Err() error { - p.m.Lock() - defer p.m.Unlock() - - return p.err -} - -func (p *Player) Play() { - p.p.Play() -} - -func (p *playerImpl) Play() { - ch := make(chan struct{}) - go func() { - p.m.Lock() - defer p.m.Unlock() - - close(ch) - p.playImpl() - }() - <-ch -} - -func (p *Player) SetBufferSize(bufferSize int) { - p.p.setBufferSize(bufferSize) -} - -func (p *playerImpl) setBufferSize(bufferSize int) { - p.m.Lock() - defer p.m.Unlock() - - orig := p.bufferSize - p.bufferSize = bufferSize - if bufferSize == 0 { - p.bufferSize = p.context.defaultBufferSize() - } - if orig != p.bufferSize { - p.tmpbuf = nil - } -} - -func (p *playerImpl) ensureTmpBuf() []byte { - if p.tmpbuf == nil { - p.tmpbuf = make([]byte, p.bufferSize) - } - return p.tmpbuf -} - -func (p *playerImpl) playImpl() { - if p.err != nil { - return - } - if p.state != playerPaused { - return - } - - if !p.eof { - buf := p.ensureTmpBuf() - for len(p.buf) < p.bufferSize { - n, err := p.src.Read(buf) - if err != nil && err != io.EOF { - p.setErrorImpl(err) - return - } - p.buf = append(p.buf, buf[:n]...) - if err == io.EOF { - p.eof = true - break - } - } - } - - if !p.eof || len(p.buf) > 0 { - p.state = playerPlay - } - - p.m.Unlock() - p.context.players.addPlayer(p) - p.m.Lock() -} - -func (p *Player) Pause() { - p.p.Pause() -} - -func (p *playerImpl) Pause() { - p.m.Lock() - defer p.m.Unlock() - - if p.state != playerPlay { - return - } - p.state = playerPaused -} - -func (p *Player) Seek(offset int64, whence int) (int64, error) { - return p.p.Seek(offset, whence) -} - -func (p *playerImpl) Seek(offset int64, whence int) (int64, error) { - p.m.Lock() - defer p.m.Unlock() - - // If a player is playing, keep playing even after this seeking. - if p.state == playerPlay { - defer p.playImpl() - } - - // Reset the internal buffer. - p.resetImpl() - - // Check if the source implements io.Seeker. - s, ok := p.src.(io.Seeker) - if !ok { - return 0, errors.New("cbackend: the source must implement io.Seeker") - } - return s.Seek(offset, whence) -} - -func (p *playerImpl) resetImpl() { - if p.state == playerClosed { - return - } - p.state = playerPaused - p.buf = p.buf[:0] - p.eof = false -} - -func (p *Player) IsPlaying() bool { - return p.p.IsPlaying() -} - -func (p *playerImpl) IsPlaying() bool { - p.m.Lock() - defer p.m.Unlock() - return p.state == playerPlay -} - -func (p *Player) Volume() float64 { - return p.p.Volume() -} - -func (p *playerImpl) Volume() float64 { - p.m.Lock() - defer p.m.Unlock() - return p.volume -} - -func (p *Player) SetVolume(volume float64) { - p.p.SetVolume(volume) -} - -func (p *playerImpl) SetVolume(volume float64) { - p.m.Lock() - defer p.m.Unlock() - p.volume = volume -} - -func (p *Player) UnplayedBufferSize() int { - return p.p.UnplayedBufferSize() -} - -func (p *playerImpl) UnplayedBufferSize() int { - p.m.Lock() - defer p.m.Unlock() - return len(p.buf) -} - -func (p *Player) Close() error { - runtime.SetFinalizer(p, nil) - return p.p.Close() -} - -func (p *playerImpl) Close() error { - p.m.Lock() - defer p.m.Unlock() - return p.closeImpl() -} - -func (p *playerImpl) closeImpl() error { - p.m.Unlock() - p.context.players.removePlayer(p) - p.m.Lock() - - if p.state == playerClosed { - return p.err - } - p.state = playerClosed - p.buf = nil - return p.err -} - -func (p *playerImpl) readBufferAndAdd(buf []float32) int { - p.m.Lock() - defer p.m.Unlock() - - if p.state != playerPlay { - return 0 - } - - bitDepthInBytes := p.context.bitDepthInBytes - n := len(p.buf) / bitDepthInBytes - if n > len(buf) { - n = len(buf) - } - volume := float32(p.volume) - src := p.buf[:n*bitDepthInBytes] - - for i := 0; i < n; i++ { - var v float32 - switch bitDepthInBytes { - case 1: - v8 := src[i] - v = float32(v8-(1<<7)) / (1 << 7) - case 2: - v16 := int16(src[2*i]) | (int16(src[2*i+1]) << 8) - v = float32(v16) / (1 << 15) - } - buf[i] += v * volume - } - - copy(p.buf, p.buf[n*bitDepthInBytes:]) - p.buf = p.buf[:len(p.buf)-n*bitDepthInBytes] - - if p.eof && len(p.buf) == 0 { - p.state = playerPaused - } - - return n -} - -func (p *playerImpl) canReadSourceToBuffer() bool { - p.m.Lock() - defer p.m.Unlock() - - if p.eof { - return false - } - return len(p.buf) < p.bufferSize -} - -func (p *playerImpl) readSourceToBuffer() { - p.m.Lock() - defer p.m.Unlock() - - if p.err != nil { - return - } - if p.state == playerClosed { - return - } - - if len(p.buf) >= p.bufferSize { - return - } - - buf := p.ensureTmpBuf() - n, err := p.src.Read(buf) - - if err != nil && err != io.EOF { - p.setErrorImpl(err) - return - } - - p.buf = append(p.buf, buf[:n]...) - if err == io.EOF { - p.eof = true - if len(p.buf) == 0 { - p.state = playerPaused - } - } -} - -func (p *playerImpl) setErrorImpl(err error) { - p.err = err - p.closeImpl() -} diff --git a/go.mod b/go.mod index b2f123dc5..2ac5f0ae8 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/hajimehoshi/bitmapfont/v2 v2.2.1 github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41 github.com/hajimehoshi/go-mp3 v0.3.3 - github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220802151442-4f655ffe7bb4 + github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220808142854-0d70fdd8205d github.com/jakecoffman/cp v1.2.1 github.com/jezek/xgb v1.0.1 github.com/jfreymuth/oggvorbis v1.0.3 diff --git a/go.sum b/go.sum index ff0dffe25..013b97a5e 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,8 @@ github.com/hajimehoshi/go-mp3 v0.3.3 h1:cWnfRdpye2m9ElSoVqneYRcpt/l3ijttgjMeQh+r github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1 h1:7cJz/zRQV4aJvMSSRqzN2TImoVVMpE0BCY4nrNJaDOM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220802151442-4f655ffe7bb4 h1:tykbQMVvjFgSvX5ndg05alWI0mfD7rm/DtysW9Z757U= -github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220802151442-4f655ffe7bb4/go.mod h1:ZH5KyijkM53TgspdS9sArUe7vv9EP7x4aNYJAmfGhvE= +github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220808142854-0d70fdd8205d h1:T64feTPB+qGhPnLRsyzlYNiz+jTd/pcPCqB8/VTgooY= +github.com/hajimehoshi/oto/v2 v2.3.0-alpha.6.0.20220808142854-0d70fdd8205d/go.mod h1:ZH5KyijkM53TgspdS9sArUe7vv9EP7x4aNYJAmfGhvE= github.com/jakecoffman/cp v1.2.1 h1:zkhc2Gpo9l4NLUZfeG3j33+3bQD7MkqPa+n5PdX+5mI= github.com/jakecoffman/cp v1.2.1/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= github.com/jezek/xgb v1.0.1 h1:YUGhxps0aR7J2Xplbs23OHnV1mWaxFVcOl9b+1RQkt8=