mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
audio: Consume necessary and sufficient data on each Update (#205) (WIP)
This commit is contained in:
parent
4e2d661541
commit
715b5df7e1
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user