audio: Consume necessary and sufficient data on each Update (#205) (WIP)

This commit is contained in:
Hajime Hoshi 2016-04-15 01:37:48 +09:00
parent 4e2d661541
commit 715b5df7e1
4 changed files with 90 additions and 144 deletions

View File

@ -36,23 +36,14 @@ import (
) )
type mixingStream struct { type mixingStream struct {
sampleRate int sampleRate int
writtenBytes int players map[*Player]struct{}
frames int
players map[*Player]struct{}
// Note that Read (and other methods) need to be concurrent safe // Note that Read (and other methods) need to be concurrent safe
// because Read is called from another groutine (see NewContext). // because Read is called from another groutine (see NewContext).
sync.RWMutex sync.RWMutex
} }
func min(a, b int) int {
if a < b {
return a
}
return b
}
const ( const (
channelNum = 2 channelNum = 2
bytesPerSample = 2 bytesPerSample = 2
@ -62,26 +53,24 @@ const (
) )
func (s *mixingStream) SampleRate() int { func (s *mixingStream) SampleRate() int {
s.RLock()
defer s.RUnlock()
return s.sampleRate return s.sampleRate
} }
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (s *mixingStream) Read(b []byte) (int, error) { func (s *mixingStream) Read(b []byte) (int, error) {
s.Lock() s.Lock()
defer s.Unlock() 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 { if len(s.players) == 0 {
l := min(len(b), x-s.writtenBytes) l := len(b)
l &= mask l &= mask
copy(b, make([]byte, l)) copy(b, make([]byte, l))
s.writtenBytes += l
return l, nil return l, nil
} }
closed := []*Player{} closed := []*Player{}
@ -120,17 +109,9 @@ func (s *mixingStream) Read(b []byte) (int, error) {
for _, p := range closed { for _, p := range closed {
delete(s.players, p) delete(s.players, p)
} }
s.writtenBytes += l
return l, nil 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) { func (s *mixingStream) newPlayer(src ReadSeekCloser) (*Player, error) {
s.Lock() s.Lock()
defer s.Unlock() 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'. // 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. // In this case, audio goes on even when the game stops e.g. by diactivating the screen.
type Context struct { type Context struct {
stream *mixingStream stream *mixingStream
errorCh chan error driver *driver.Player
frames int
writtenBytes int
} }
// NewContext creates a new audio context with the given sample rate (e.g. 44100). // NewContext creates a new audio context with the given sample rate (e.g. 44100).
func NewContext(sampleRate int) (*Context, error) { func NewContext(sampleRate int) (*Context, error) {
// TODO: Panic if one context exists. // TODO: Panic if one context exists.
c := &Context{ c := &Context{}
errorCh: make(chan error),
}
c.stream = &mixingStream{ c.stream = &mixingStream{
sampleRate: sampleRate, sampleRate: sampleRate,
players: map[*Player]struct{}{}, players: map[*Player]struct{}{},
} }
// TODO: Rename this other than player // 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 { if err != nil {
return nil, err 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 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. // 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. // In async mode, the audio never stops even when the game stops.
func (c *Context) Update() error { func (c *Context) Update() error {
select { c.frames++
case err := <-c.errorCh: 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 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. // SampleRate returns the sample rate.

View File

@ -18,13 +18,11 @@ package driver
import ( import (
"errors" "errors"
"io"
"github.com/gopherjs/gopherjs/js" "github.com/gopherjs/gopherjs/js"
) )
type Player struct { type Player struct {
src io.Reader
sampleRate int sampleRate int
channelNum int channelNum int
bytesPerSample int bytesPerSample int
@ -33,7 +31,7 @@ type Player struct {
bufferSource *js.Object 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") class := js.Global.Get("AudioContext")
if class == js.Undefined { if class == js.Undefined {
class = js.Global.Get("webkitAudioContext") 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") return nil, errors.New("driver: audio couldn't be initialized")
} }
p := &Player{ p := &Player{
src: src,
sampleRate: sampleRate, sampleRate: sampleRate,
channelNum: channelNum, channelNum: channelNum,
bytesPerSample: bytesPerSample, bytesPerSample: bytesPerSample,
@ -63,37 +60,29 @@ func toLR(data []byte) ([]int16, []int16) {
return l, r return l, r
} }
const ( func (p *Player) Proceed(data []byte) error {
// 1024 seems not enough (some noise remains after the tab is deactivated).
bufferSize = 2048
)
func (p *Player) Proceed() error {
c := int64(p.context.Get("currentTime").Float() * float64(p.sampleRate)) c := int64(p.context.Get("currentTime").Float() * float64(p.sampleRate))
if p.positionInSamples < c { if p.positionInSamples < c {
p.positionInSamples = c p.positionInSamples = c
} }
b := make([]byte, bufferSize) n := len(data)
n, err := p.src.Read(b) buf := p.context.Call("createBuffer", p.channelNum, n/p.bytesPerSample/p.channelNum, p.sampleRate)
if 0 < n { l := buf.Call("getChannelData", 0)
buf := p.context.Call("createBuffer", p.channelNum, n/p.bytesPerSample/p.channelNum, p.sampleRate) r := buf.Call("getChannelData", 1)
l := buf.Call("getChannelData", 0) il, ir := toLR(data)
r := buf.Call("getChannelData", 1) const max = 1 << 15
il, ir := toLR(b[:n]) for i := 0; i < len(il); i++ {
const max = 1 << 15 l.SetIndex(i, float64(il[i])/max)
for i := 0; i < len(il); i++ { r.SetIndex(i, float64(ir[i])/max)
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))
} }
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 { func (p *Player) Close() error {

View File

@ -17,8 +17,8 @@
package driver package driver
import ( import (
"errors"
"fmt" "fmt"
"io"
"runtime" "runtime"
"github.com/hajimehoshi/go-openal/openal" "github.com/hajimehoshi/go-openal/openal"
@ -35,7 +35,6 @@ type Player struct {
alDevice *openal.Device alDevice *openal.Device
alSource openal.Source alSource openal.Source
alBuffers []openal.Buffer alBuffers []openal.Buffer
source io.Reader
sampleRate int sampleRate int
isClosed bool isClosed bool
alFormat openal.Format 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)) 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("") d := openal.OpenDevice("")
if d == nil { if d == nil {
return nil, fmt.Errorf("driver: OpenDevice must not return 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, alDevice: d,
alSource: s, alSource: s,
alBuffers: []openal.Buffer{}, alBuffers: []openal.Buffer{},
source: src,
sampleRate: sampleRate, sampleRate: sampleRate,
alFormat: alFormat(channelNum, bytesPerSample), alFormat: alFormat(channelNum, bytesPerSample),
} }
runtime.SetFinalizer(p, (*Player).Close) runtime.SetFinalizer(p, (*Player).Close)
bs := openal.NewBuffers(maxBufferNum) bs := openal.NewBuffers(maxBufferNum)
const bufferSize = 1024
emptyBytes := make([]byte, bufferSize) emptyBytes := make([]byte, bufferSize)
for _, b := range bs { for _, b := range bs {
// Note that the third argument of only the first buffer is used. // Note that the third argument of only the first buffer is used.
b.SetData(p.alFormat, emptyBytes, int32(p.sampleRate)) 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() p.alSource.Play()
return p, nil return p, nil
} }
const ( func (p *Player) Proceed(data []byte) error {
bufferSize = 1024
)
var (
tmpBuffer = make([]byte, bufferSize)
tmpAlBuffers = make([]openal.Buffer, maxBufferNum)
)
func (p *Player) Proceed() error {
if err := openal.Err(); err != nil { if err := openal.Err(); err != nil {
return fmt.Errorf("driver: starting Proceed: %v", err) return fmt.Errorf("driver: starting Proceed: %v", err)
} }
processedNum := p.alSource.BuffersProcessed() processedNum := p.alSource.BuffersProcessed()
if 0 < processedNum { if 0 < processedNum {
bufs := tmpAlBuffers[:processedNum] bufs := make([]openal.Buffer, processedNum)
p.alSource.UnqueueBuffers(bufs) p.alSource.UnqueueBuffers(bufs)
if err := openal.Err(); err != nil { if err := openal.Err(); err != nil {
return fmt.Errorf("driver: UnqueueBuffers: %v", err) return fmt.Errorf("driver: UnqueueBuffers: %v", err)
@ -118,20 +109,15 @@ func (p *Player) Proceed() error {
p.alBuffers = append(p.alBuffers, bufs...) p.alBuffers = append(p.alBuffers, bufs...)
} }
if 0 < len(p.alBuffers) { if len(p.alBuffers) == 0 {
n, err := p.source.Read(tmpBuffer) return errors.New("driver: p.alBuffers must > 0")
if 0 < n { }
buf := p.alBuffers[0] buf := p.alBuffers[0]
p.alBuffers = p.alBuffers[1:] p.alBuffers = p.alBuffers[1:]
buf.SetData(p.alFormat, tmpBuffer[:n], int32(p.sampleRate)) buf.SetData(p.alFormat, data, int32(p.sampleRate))
p.alSource.QueueBuffer(buf) p.alSource.QueueBuffer(buf)
if err := openal.Err(); err != nil { if err := openal.Err(); err != nil {
return fmt.Errorf("driver: QueueBuffer: %v", err) return fmt.Errorf("driver: QueueBuffer: %v", err)
}
}
if err != nil {
return err
}
} }
if p.alSource.State() == openal.Stopped || p.alSource.State() == openal.Initial { if p.alSource.State() == openal.Stopped || p.alSource.State() == openal.Initial {

View File

@ -27,7 +27,6 @@ import "C"
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"unsafe" "unsafe"
) )
@ -76,7 +75,6 @@ func releaseSemaphore() {
} }
type Player struct { type Player struct {
src io.Reader
out C.HWAVEOUT out C.HWAVEOUT
buffer []byte buffer []byte
headers []*header headers []*header
@ -84,7 +82,7 @@ type Player struct {
const bufferSize = 1024 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 numBlockAlign := channelNum * bytesPerSample
f := C.WAVEFORMATEX{ f := C.WAVEFORMATEX{
wFormatTag: C.WAVE_FORMAT_PCM, 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) return nil, fmt.Errorf("driver: waveOutOpen error: %d", err)
} }
p := &Player{ p := &Player{
src: src,
out: w, out: w,
buffer: []byte{}, buffer: []byte{},
headers: make([]*header, numHeader), headers: make([]*header, numHeader),
@ -114,35 +111,27 @@ func NewPlayer(src io.Reader, sampleRate, channelNum, bytesPerSample int) (*Play
return p, nil return p, nil
} }
func (p *Player) Proceed() error { func (p *Player) Proceed(data []byte) error {
if len(p.buffer) < bufferSize { p.buffer = append(p.buffer, data...)
b := make([]byte, bufferSize) if bufferSize > len(p.buffer) {
n, err := p.src.Read(b) return nil
if 0 < n { }
p.buffer = append(p.buffer, b[:n]...) sem <- struct{}{}
} headerToWrite := (*header)(nil)
if err != nil { for _, h := range p.headers {
return err // TODO: Need to check WHDR_DONE?
if h.waveHdr.dwFlags&C.WHDR_INQUEUE == 0 {
headerToWrite = h
break
} }
} }
if bufferSize <= len(p.buffer) { if headerToWrite == nil {
sem <- struct{}{} return errors.New("driver: no available buffers")
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 err := headerToWrite.Write(p.out, p.buffer[:bufferSize]); err != nil {
return err
}
p.buffer = p.buffer[bufferSize:]
return nil return nil
} }