audio: Non-blocking reading buffers

This commit is contained in:
Hajime Hoshi 2017-06-04 16:46:02 +09:00
parent 10bd1c1786
commit 8957b1b857

View File

@ -39,8 +39,7 @@ import (
) )
type players struct { type players struct {
players map[*Player]struct{} players map[*Player]struct{}
seekings map[*Player]struct{}
sync.RWMutex sync.RWMutex
} }
@ -65,9 +64,6 @@ func (p *players) Read(b []uint8) (int, error) {
players := []*Player{} players := []*Player{}
for player := range p.players { for player := range p.players {
if _, ok := p.seekings[player]; ok {
continue
}
players = append(players, player) players = append(players, player)
} }
if len(players) == 0 { if len(players) == 0 {
@ -79,12 +75,13 @@ func (p *players) Read(b []uint8) (int, error) {
closed := []*Player{} closed := []*Player{}
l := len(b) l := len(b)
for _, player := range players { for _, player := range players {
if err := player.readToBuffer(l); err == io.EOF { n, err := player.readToBuffer(l)
if err == io.EOF {
closed = append(closed, player) closed = append(closed, player)
} else if err != nil { } else if err != nil {
return 0, err return 0, err
} }
l = min(player.bufferLength(), l) l = min(n, l)
} }
l &= mask l &= mask
b16s := [][]int16{} b16s := [][]int16{}
@ -126,18 +123,6 @@ func (p *players) removePlayer(player *Player) {
p.Unlock() p.Unlock()
} }
func (p *players) addSeeking(player *Player) {
p.Lock()
p.seekings[player] = struct{}{}
p.Unlock()
}
func (p *players) removeSeeking(player *Player) {
p.Lock()
delete(p.seekings, player)
p.Unlock()
}
func (p *players) hasPlayer(player *Player) bool { func (p *players) hasPlayer(player *Player) bool {
p.RLock() p.RLock()
_, ok := p.players[player] _, ok := p.players[player]
@ -213,8 +198,7 @@ func NewContext(sampleRate int) (*Context, error) {
} }
theContext = c theContext = c
c.players = &players{ c.players = &players{
players: map[*Player]struct{}{}, players: map[*Player]struct{}{},
seekings: map[*Player]struct{}{},
} }
return c, nil return c, nil
@ -306,6 +290,7 @@ type Player struct {
players *players players *players
src ReadSeekCloser src ReadSeekCloser
sampleRate int sampleRate int
readingCh chan error
buf []uint8 buf []uint8
pos int64 pos int64
@ -380,21 +365,41 @@ func (p *Player) Close() error {
return err return err
} }
func (p *Player) readToBuffer(length int) error { func (p *Player) readToBuffer(length int) (int, error) {
bb := make([]uint8, length) if p.readingCh == nil {
p.srcM.Lock() p.readingCh = make(chan error)
n, err := p.src.Read(bb) go func() {
p.srcM.Unlock() defer close(p.readingCh)
if 0 < n { bb := make([]uint8, length)
p.m.Lock() p.srcM.Lock()
p.buf = append(p.buf, bb[:n]...) n, err := p.src.Read(bb)
p.m.Unlock() p.srcM.Unlock()
if err != nil {
p.readingCh <- err
return
}
if 0 < n {
p.m.Lock()
p.buf = append(p.buf, bb[:n]...)
p.m.Unlock()
}
}()
}
select {
case err := <-p.readingCh:
p.readingCh = nil
return len(p.buf), err
case <-time.After(15 * time.Millisecond):
return length, nil
} }
return err
} }
func (p *Player) bufferToInt16(lengthInBytes int) []int16 { func (p *Player) bufferToInt16(lengthInBytes int) []int16 {
r := make([]int16, lengthInBytes/2) r := make([]int16, lengthInBytes/2)
// This function must be called on the same goruotine of readToBuffer.
if p.readingCh != nil {
return r
}
p.m.RLock() p.m.RLock()
for i := 0; i < lengthInBytes/2; i++ { for i := 0; i < lengthInBytes/2; i++ {
r[i] = int16(p.buf[2*i]) | (int16(p.buf[2*i+1]) << 8) r[i] = int16(p.buf[2*i]) | (int16(p.buf[2*i+1]) << 8)
@ -405,19 +410,16 @@ func (p *Player) bufferToInt16(lengthInBytes int) []int16 {
} }
func (p *Player) proceed(length int) { func (p *Player) proceed(length int) {
// This function must be called on the same goruotine of readToBuffer.
if p.readingCh != nil {
return
}
p.m.Lock() p.m.Lock()
p.buf = p.buf[length:] p.buf = p.buf[length:]
p.pos += int64(length) p.pos += int64(length)
p.m.Unlock() p.m.Unlock()
} }
func (p *Player) bufferLength() int {
p.m.RLock()
l := len(p.buf)
p.m.RUnlock()
return l
}
// Play plays the stream. // Play plays the stream.
// //
// Play always returns nil. // Play always returns nil.
@ -450,12 +452,9 @@ func (p *Player) Rewind() error {
// //
// Seek returns error when seeking the source returns error. // Seek returns error when seeking the source returns error.
func (p *Player) Seek(offset time.Duration) error { func (p *Player) Seek(offset time.Duration) error {
p.players.addSeeking(p)
defer p.players.removeSeeking(p)
o := int64(offset) * bytesPerSample * channelNum * int64(p.sampleRate) / int64(time.Second) o := int64(offset) * bytesPerSample * channelNum * int64(p.sampleRate) / int64(time.Second)
o &= mask o &= mask
p.srcM.Lock() p.srcM.Lock()
// TODO: Seek finishes soon but after that, Read takes long time and players.Read can block.
pos, err := p.src.Seek(o, io.SeekStart) pos, err := p.src.Seek(o, io.SeekStart)
p.srcM.Unlock() p.srcM.Unlock()
if err != nil { if err != nil {