audio: Reuse al.Source objects (crash still happens on piano)

This commit is contained in:
Hajime Hoshi 2016-02-12 21:39:48 +09:00
parent f0d7a811e1
commit b2724e8694
4 changed files with 79 additions and 33 deletions

View File

@ -48,6 +48,7 @@ const (
freqGS = 830.6 freqGS = 830.6
) )
// Twinkle, Twinkle, Little Star
const score = `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR` const score = `CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR`
var scoreIndex = 0 var scoreIndex = 0
@ -86,14 +87,7 @@ func toBytes(l, r []int16) []byte {
return b return b
} }
type stream struct { func addNote() error {
*bytes.Reader
}
func (s *stream) Close() error {
return nil
}
func addNote() {
size := sampleRate / 60 size := sampleRate / 60
notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2} notes := []float64{freqC, freqD, freqE, freqF, freqG, freqA * 2, freqB * 2}
@ -122,8 +116,12 @@ func addNote() {
square(l, vol, freq, 0.25) square(l, vol, freq, 0.25)
square(r, vol, freq, 0.25) square(r, vol, freq, 0.25)
b := toBytes(l, r) b := toBytes(l, r)
p := audio.NewPlayer(&stream{bytes.NewReader(b)}, sampleRate) p, err := audio.NewPlayer(bytes.NewReader(b), sampleRate)
if err != nil {
return err
}
p.Play() p.Play()
return nil
} }
func update(screen *ebiten.Image) error { func update(screen *ebiten.Image) error {
@ -131,7 +129,9 @@ func update(screen *ebiten.Image) error {
frames++ frames++
}() }()
if frames%30 == 0 { if frames%30 == 0 {
addNote() if err := addNote(); err != nil {
return err
}
} }
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS())) ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %0.2f", ebiten.CurrentFPS()))
return nil return nil

View File

@ -78,12 +78,18 @@ func (s *stream) Close() error {
return nil return nil
} }
func addNote(freq float64, vol float64) { func addNote(freq float64, vol float64) error {
f := int(freq) f := int(freq)
if n, ok := noteCache[f]; ok { if n, ok := noteCache[f]; ok {
p := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate) p, err := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate)
if err == audio.ErrTooManyPlayers {
return nil
}
if err != nil {
return err
}
p.Play() p.Play()
return return nil
} }
length := len(pcm) * baseFreq / f length := len(pcm) * baseFreq / f
l := make([]int16, length) l := make([]int16, length)
@ -99,8 +105,15 @@ func addNote(freq float64, vol float64) {
} }
n := toBytes(l, r) n := toBytes(l, r)
noteCache[f] = n noteCache[f] = n
p := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate) p, err := audio.NewPlayer(&stream{bytes.NewReader(n)}, sampleRate)
if err == audio.ErrTooManyPlayers {
return nil
}
if err != nil {
return err
}
p.Play() p.Play()
return nil
} }
var keys = []ebiten.Key{ var keys = []ebiten.Key{
@ -193,7 +206,9 @@ func update(screen *ebiten.Image) error {
if keyStates[key] != 1 { if keyStates[key] != 1 {
continue continue
} }
addNote(220*math.Exp2(float64(i-1)/12.0), 1.0) if err := addNote(220*math.Exp2(float64(i-1)/12.0), 1.0); err != nil {
return err
}
} }
screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff}) screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})

View File

@ -15,6 +15,7 @@
package audio package audio
import ( import (
"errors"
"io" "io"
) )
@ -30,10 +31,12 @@ type Player struct {
// without a header (e.g. RIFF header). // without a header (e.g. RIFF header).
// //
// TODO: Pass sample rate and num of channels. // TODO: Pass sample rate and num of channels.
func NewPlayer(src io.ReadSeeker, sampleRate int) *Player { func NewPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
return newPlayer(src, sampleRate) return newPlayer(src, sampleRate)
} }
var ErrTooManyPlayers = errors.New("audio: too many players exist")
func (p *Player) Play() error { func (p *Player) Play() error {
return p.player.play() return p.player.play()
} }

View File

@ -26,13 +26,14 @@ import (
"golang.org/x/mobile/exp/audio/al" "golang.org/x/mobile/exp/audio/al"
) )
type readSeekCloser struct { type alSourceCacheEntry struct {
io.ReadSeeker source al.Source
sampleRate int
} }
func (r *readSeekCloser) Close() error { const maxSourceNum = 32
return nil
} var alSourceCache = []alSourceCacheEntry{}
type player struct { type player struct {
alSource al.Source alSource al.Source
@ -43,27 +44,52 @@ type player struct {
var m sync.Mutex var m sync.Mutex
var sn = 0 func newAlSource(sampleRate int) (al.Source, error) {
for _, e := range alSourceCache {
func newPlayer(src io.ReadSeeker, sampleRate int) *Player { if e.sampleRate != sampleRate {
m.Lock() continue
if err := al.OpenDevice(); err != nil { }
panic(fmt.Sprintf("audio: OpenAL initialization failed: %v", err)) s := e.source.State()
if s != al.Initial && s != al.Stopped {
continue
}
return e.source, nil
}
if maxSourceNum <= len(alSourceCache) {
return 0, ErrTooManyPlayers
} }
// TODO: Too many generating sources may cause error. Limit the number of sources.
s := al.GenSources(1) s := al.GenSources(1)
if err := al.Error(); err != 0 { if err := al.Error(); err != 0 {
panic(fmt.Sprintf("audio: al.GenSources error: %d", err)) panic(fmt.Sprintf("audio: al.GenSources error: %d", err))
} }
e := alSourceCacheEntry{
source: s[0],
sampleRate: sampleRate,
}
alSourceCache = append(alSourceCache, e)
return s[0], nil
}
func newPlayer(src io.ReadSeeker, sampleRate int) (*Player, error) {
m.Lock()
if e := al.OpenDevice(); e != nil {
m.Unlock()
return nil, fmt.Errorf("audio: OpenAL initialization failed: %v", e)
}
s, err := newAlSource(sampleRate)
if err != nil {
m.Unlock()
return nil, err
}
m.Unlock() m.Unlock()
p := &player{ p := &player{
alSource: s[0], alSource: s,
alBuffers: []al.Buffer{}, alBuffers: []al.Buffer{},
source: src, source: src,
sampleRate: sampleRate, sampleRate: sampleRate,
} }
runtime.SetFinalizer(p, (*player).close) runtime.SetFinalizer(p, (*player).close)
return &Player{p} return &Player{p}, nil
} }
const bufferSize = 1024 const bufferSize = 1024
@ -106,7 +132,8 @@ func (p *player) play() error {
// TODO: What if play is already called? // TODO: What if play is already called?
emptyBytes := make([]byte, bufferSize) emptyBytes := make([]byte, bufferSize)
m.Lock() m.Lock()
bufs := al.GenBuffers(8) queued := p.alSource.BuffersQueued()
bufs := al.GenBuffers(8 - int(queued))
for _, buf := range bufs { for _, buf := range bufs {
// Note that the third argument of only the first buffer is used. // Note that the third argument of only the first buffer is used.
buf.BufferData(al.FormatStereo16, emptyBytes, int32(p.sampleRate)) buf.BufferData(al.FormatStereo16, emptyBytes, int32(p.sampleRate))
@ -134,7 +161,8 @@ func (p *player) play() error {
func (p *player) close() error { func (p *player) close() error {
m.Lock() m.Lock()
if p.alSource != 0 { if p.alSource != 0 {
al.DeleteSources(p.alSource) al.RewindSources(p.alSource)
al.StopSources(p.alSource)
p.alSource = 0 p.alSource = 0
} }
if 0 < len(p.alBuffers) { if 0 < len(p.alBuffers) {
@ -142,7 +170,7 @@ func (p *player) close() error {
p.alBuffers = []al.Buffer{} p.alBuffers = []al.Buffer{}
} }
if err := al.Error(); err != 0 { if err := al.Error(); err != 0 {
panic(fmt.Sprintf("audio: close error: %d", err)) panic(fmt.Sprintf("audio: closing error: %d", err))
} }
m.Unlock() m.Unlock()
runtime.SetFinalizer(p, nil) runtime.SetFinalizer(p, nil)