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 {
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.

View File

@ -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 {

View File

@ -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 {

View File

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