audio: Rename players to mux

This commit is contained in:
Hajime Hoshi 2019-01-09 21:55:42 +09:00
parent 5514a9c6ac
commit 14404334e7
2 changed files with 160 additions and 135 deletions

View File

@ -47,129 +47,6 @@ import (
"github.com/hajimehoshi/ebiten/internal/web" "github.com/hajimehoshi/ebiten/internal/web"
) )
type players struct {
players map[*playerImpl]struct{}
sync.RWMutex
}
const (
channelNum = 2
bytesPerSample = 2 * channelNum
// TODO: This assumes that bytesPerSample is a power of 2.
mask = ^(bytesPerSample - 1)
)
func (p *players) Read(b []byte) (int, error) {
p.Lock()
defer p.Unlock()
if len(p.players) == 0 {
l := len(b)
l &= mask
copy(b, make([]byte, l))
return l, nil
}
l := len(b)
l &= mask
allSkipped := true
for player := range p.players {
if player.shouldSkip() {
continue
}
allSkipped = false
s := player.bufferSizeInBytes()
if l > s {
l = s
l &= mask
}
}
if allSkipped {
l = 0
}
if l == 0 {
// If l is 0, all the players might reach EOF at the next update.
// However, this Read might block forever and never causes context switch
// on single-thread environment (e.g. browser).
// Call Gosched to cause context switch on purpose.
runtime.Gosched()
}
b16s := [][]int16{}
for player := range p.players {
buf, err := player.bufferToInt16(l)
if err != nil {
return 0, err
}
b16s = append(b16s, buf)
}
for i := 0; i < l/2; i++ {
x := 0
for _, b16 := range b16s {
x += int(b16[i])
}
if x > (1<<15)-1 {
x = (1 << 15) - 1
}
if x < -(1 << 15) {
x = -(1 << 15)
}
b[2*i] = byte(x)
b[2*i+1] = byte(x >> 8)
}
closed := []*playerImpl{}
for player := range p.players {
if player.eof() {
closed = append(closed, player)
}
}
for _, player := range closed {
if player.isFinalized() {
player.closeImpl()
}
delete(p.players, player)
}
return l, nil
}
func (p *players) addPlayer(player *playerImpl) {
p.Lock()
p.players[player] = struct{}{}
p.Unlock()
}
func (p *players) removePlayer(player *playerImpl) {
p.Lock()
delete(p.players, player)
p.Unlock()
}
func (p *players) hasPlayer(player *playerImpl) bool {
p.RLock()
_, ok := p.players[player]
p.RUnlock()
return ok
}
func (p *players) hasSource(src io.ReadCloser) bool {
p.RLock()
defer p.RUnlock()
for player := range p.players {
if player.src == src {
return true
}
}
return false
}
// A Context represents a current state of audio. // A Context represents a current state of audio.
// //
// At most one Context object can exist in one process. // At most one Context object can exist in one process.
@ -177,7 +54,7 @@ func (p *players) hasSource(src io.ReadCloser) bool {
// //
// For a typical usage example, see examples/wav/main.go. // For a typical usage example, see examples/wav/main.go.
type Context struct { type Context struct {
players *players mux *mux
sampleRate int sampleRate int
err error err error
ready bool ready bool
@ -226,9 +103,7 @@ func NewContext(sampleRate int) (*Context, error) {
sampleRate: sampleRate, sampleRate: sampleRate,
} }
theContext = c theContext = c
c.players = &players{ c.mux = newMux()
players: map[*playerImpl]struct{}{},
}
go c.loop() go c.loop()
@ -286,7 +161,7 @@ func (c *Context) loop() {
case <-suspendCh: case <-suspendCh:
<-resumeCh <-resumeCh
default: default:
if _, err := io.CopyN(p, c.players, 2048); err != nil { if _, err := io.CopyN(p, c.mux, 2048); err != nil {
c.err = err c.err = err
return return
} }
@ -360,7 +235,7 @@ type Player struct {
} }
type playerImpl struct { type playerImpl struct {
players *players mux *mux
src io.ReadCloser src io.ReadCloser
srcEOF bool srcEOF bool
sampleRate int sampleRate int
@ -405,12 +280,12 @@ type proceededValues struct {
// NewPlayer tries to call Seek of src to get the current position. // NewPlayer tries to call Seek of src to get the current position.
// NewPlayer returns error when the Seek returns error. // NewPlayer returns error when the Seek returns error.
func NewPlayer(context *Context, src io.ReadCloser) (*Player, error) { func NewPlayer(context *Context, src io.ReadCloser) (*Player, error) {
if context.players.hasSource(src) { if context.mux.hasSource(src) {
return nil, errors.New("audio: src cannot be shared with another Player") return nil, errors.New("audio: src cannot be shared with another Player")
} }
p := &Player{ p := &Player{
&playerImpl{ &playerImpl{
players: context.players, mux: context.mux,
src: src, src: src,
sampleRate: context.sampleRate, sampleRate: context.sampleRate,
buf: nil, buf: nil,
@ -495,7 +370,7 @@ func (p *Player) Close() error {
} }
func (p *playerImpl) Close() error { func (p *playerImpl) Close() error {
p.players.removePlayer(p) p.mux.removePlayer(p)
return p.closeImpl() return p.closeImpl()
} }
@ -528,7 +403,7 @@ func (p *Player) Play() error {
} }
func (p *playerImpl) Play() { func (p *playerImpl) Play() {
p.players.addPlayer(p) p.mux.addPlayer(p)
} }
func (p *playerImpl) readLoop() { func (p *playerImpl) readLoop() {
@ -714,7 +589,7 @@ func (p *Player) IsPlaying() bool {
} }
func (p *playerImpl) IsPlaying() bool { func (p *playerImpl) IsPlaying() bool {
return p.players.hasPlayer(p) return p.mux.hasPlayer(p)
} }
// Rewind rewinds the current position to the start. // Rewind rewinds the current position to the start.
@ -765,7 +640,7 @@ func (p *Player) Pause() error {
} }
func (p *playerImpl) Pause() { func (p *playerImpl) Pause() {
p.players.removePlayer(p) p.mux.removePlayer(p)
} }
// Current returns the current position. // Current returns the current position.

150
audio/mux.go Normal file
View File

@ -0,0 +1,150 @@
// Copyright 2019 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.
package audio
import (
"io"
"runtime"
"sync"
)
type mux struct {
ps map[*playerImpl]struct{}
m sync.RWMutex
}
const (
channelNum = 2
bytesPerSample = 2 * channelNum
// TODO: This assumes that bytesPerSample is a power of 2.
mask = ^(bytesPerSample - 1)
)
func newMux() *mux {
return &mux{
ps: map[*playerImpl]struct{}{},
}
}
func (m *mux) Read(b []byte) (int, error) {
m.m.Lock()
defer m.m.Unlock()
if len(m.ps) == 0 {
l := len(b)
l &= mask
copy(b, make([]byte, l))
return l, nil
}
l := len(b)
l &= mask
allSkipped := true
for p := range m.ps {
if p.shouldSkip() {
continue
}
allSkipped = false
s := p.bufferSizeInBytes()
if l > s {
l = s
l &= mask
}
}
if allSkipped {
l = 0
}
if l == 0 {
// If l is 0, all the ps might reach EOF at the next update.
// However, this Read might block forever and never causes context switch
// on single-thread environment (e.g. browser).
// Call Gosched to cause context switch on purpose.
runtime.Gosched()
}
b16s := [][]int16{}
for p := range m.ps {
buf, err := p.bufferToInt16(l)
if err != nil {
return 0, err
}
b16s = append(b16s, buf)
}
for i := 0; i < l/2; i++ {
x := 0
for _, b16 := range b16s {
x += int(b16[i])
}
if x > (1<<15)-1 {
x = (1 << 15) - 1
}
if x < -(1 << 15) {
x = -(1 << 15)
}
b[2*i] = byte(x)
b[2*i+1] = byte(x >> 8)
}
closed := []*playerImpl{}
for p := range m.ps {
if p.eof() {
closed = append(closed, p)
}
}
for _, p := range closed {
if p.isFinalized() {
p.closeImpl()
}
delete(m.ps, p)
}
return l, nil
}
func (m *mux) addPlayer(player *playerImpl) {
m.m.Lock()
m.ps[player] = struct{}{}
m.m.Unlock()
}
func (m *mux) removePlayer(player *playerImpl) {
m.m.Lock()
delete(m.ps, player)
m.m.Unlock()
}
func (m *mux) hasPlayer(player *playerImpl) bool {
m.m.RLock()
_, ok := m.ps[player]
m.m.RUnlock()
return ok
}
func (m *mux) hasSource(src io.ReadCloser) bool {
m.m.RLock()
defer m.m.RUnlock()
for p := range m.ps {
if p.src == src {
return true
}
}
return false
}