audio/internal/readerdriver: Remove goroutines for Windows

Also this change changed the buffers to use smaller but more
headers to improve latency.

Updates #1768
This commit is contained in:
Hajime Hoshi 2021-08-19 01:44:04 +09:00
parent ded679c071
commit 9f1113b733

View File

@ -16,13 +16,15 @@ package readerdriver
import ( import (
"fmt" "fmt"
"sync"
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
const headerBufferSize = 8192 // Avoid goroutines on Windows (#1768).
// Apparently, switching contexts might take longer than other platforms.
const headerBufferSize = 4096
func IsAvailable() bool { func IsAvailable() bool {
return true return true
@ -73,7 +75,7 @@ type context struct {
waveOut uintptr waveOut uintptr
headers []*header headers []*header
cond *sync.Cond buf32 []float32
players *players players *players
} }
@ -88,7 +90,6 @@ func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan stru
sampleRate: sampleRate, sampleRate: sampleRate,
channelNum: channelNum, channelNum: channelNum,
bitDepthInBytes: bitDepthInBytes, bitDepthInBytes: bitDepthInBytes,
cond: sync.NewCond(&sync.Mutex{}),
players: newPlayers(), players: newPlayers(),
} }
theContext = c theContext = c
@ -117,7 +118,7 @@ func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan stru
} }
c.waveOut = w c.waveOut = w
c.headers = make([]*header, 0, 3) c.headers = make([]*header, 0, 6)
for len(c.headers) < cap(c.headers) { for len(c.headers) < cap(c.headers) {
h, err := newHeader(c.waveOut, headerBufferSize) h, err := newHeader(c.waveOut, headerBufferSize)
if err != nil { if err != nil {
@ -126,7 +127,10 @@ func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan stru
c.headers = append(c.headers, h) c.headers = append(c.headers, h)
} }
go c.loop() c.buf32 = make([]float32, headerBufferSize/4)
for range c.headers {
c.appendBuffers()
}
return c, ready, nil return c, ready, nil
} }
@ -161,36 +165,22 @@ var waveOutOpenCallback = windows.NewCallbackCDecl(func(hwo, uMsg, dwInstance, d
if uMsg != womDone { if uMsg != womDone {
return 0 return 0
} }
theContext.cond.Signal() theContext.appendBuffers()
return 0 return 0
}) })
func (c *context) loop() { func (c *context) appendBuffers() {
buf32 := make([]float32, headerBufferSize/4) for i := range c.buf32 {
for { c.buf32[i] = 0
c.appendBuffer(buf32)
} }
} c.players.read(c.buf32)
func (c *context) appendBuffer(buf32 []float32) {
c.cond.L.Lock()
defer c.cond.L.Unlock()
for !c.isHeaderAvailable() {
c.cond.Wait()
}
for i := range buf32 {
buf32[i] = 0
}
c.players.read(buf32)
for _, h := range c.headers { for _, h := range c.headers {
if h.IsQueued() { if h.IsQueued() {
continue continue
} }
if err := h.Write(buf32); err != nil { if err := h.Write(c.buf32); err != nil {
// This error can happen when e.g. a new HDMI connection is detected (hajimehoshi/oto#51). // This error can happen when e.g. a new HDMI connection is detected (hajimehoshi/oto#51).
const errorNotFound = 1168 const errorNotFound = 1168
if werr := err.(*winmmError); werr.fname == "waveOutWrite" { if werr := err.(*winmmError); werr.fname == "waveOutWrite" {
@ -204,6 +194,5 @@ func (c *context) appendBuffer(buf32 []float32) {
// TODO: Treat the error corretly // TODO: Treat the error corretly
panic(fmt.Errorf("readerdriver: Queueing the header failed: %v", err)) panic(fmt.Errorf("readerdriver: Queueing the header failed: %v", err))
} }
return
} }
} }