2019-01-09 13:55:42 +01:00
|
|
|
// 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
|
|
|
|
|
2019-01-09 17:37:11 +01:00
|
|
|
// TODO: Now a player is not locked. Should we lock it?
|
|
|
|
|
2019-01-09 13:55:42 +01:00
|
|
|
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 {
|
2019-04-27 17:13:51 +02:00
|
|
|
p.closeImpl()
|
2019-01-09 13:55:42 +01:00
|
|
|
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
|
|
|
|
}
|