mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-09-20 13:22:19 +02:00
6a8abac194
When all the players were gone, the waveOut is closed once. From this state, playing an audio again might take long. This change fixes the issue by not closing the waveOut even when all the players are gone. This change also changes the returning value of read when the player doesn't play.
628 lines
11 KiB
Go
628 lines
11 KiB
Go
// Copyright 2021 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 readerdriver
|
|
|
|
import (
|
|
"io"
|
|
"runtime"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
const headerBufferSize = 2048
|
|
|
|
func IsAvailable() bool {
|
|
return true
|
|
}
|
|
|
|
type header struct {
|
|
waveOut uintptr
|
|
buffer []byte
|
|
waveHdr *wavehdr
|
|
}
|
|
|
|
func newHeader(waveOut uintptr, bufferSize int) (*header, error) {
|
|
h := &header{
|
|
waveOut: waveOut,
|
|
buffer: make([]byte, bufferSize),
|
|
}
|
|
h.waveHdr = &wavehdr{
|
|
lpData: uintptr(unsafe.Pointer(&h.buffer[0])),
|
|
dwBufferLength: uint32(bufferSize),
|
|
}
|
|
if err := waveOutPrepareHeader(waveOut, h.waveHdr); err != nil {
|
|
return nil, err
|
|
}
|
|
return h, nil
|
|
}
|
|
|
|
func (h *header) Write(data []byte) error {
|
|
if n := len(h.buffer) - len(data); n > 0 {
|
|
data = append(data, make([]byte, n)...)
|
|
}
|
|
copy(h.buffer, data)
|
|
return waveOutWrite(h.waveOut, h.waveHdr)
|
|
}
|
|
|
|
func (h *header) IsQueued() bool {
|
|
return h.waveHdr.dwFlags&whdrInqueue != 0
|
|
}
|
|
|
|
func (h *header) Close() error {
|
|
return waveOutUnprepareHeader(h.waveOut, h.waveHdr)
|
|
}
|
|
|
|
type context struct {
|
|
sampleRate int
|
|
channelNum int
|
|
bitDepthInBytes int
|
|
}
|
|
|
|
func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan struct{}, error) {
|
|
ready := make(chan struct{})
|
|
close(ready)
|
|
|
|
c := &context{
|
|
sampleRate: sampleRate,
|
|
channelNum: channelNum,
|
|
bitDepthInBytes: bitDepthInBytes,
|
|
}
|
|
thePlayers.setContext(c)
|
|
return c, ready, nil
|
|
}
|
|
|
|
func (c *context) Suspend() error {
|
|
return thePlayers.suspend()
|
|
}
|
|
|
|
func (c *context) Resume() error {
|
|
return thePlayers.resume()
|
|
}
|
|
|
|
type players struct {
|
|
context *context
|
|
players map[*playerImpl]struct{}
|
|
buf []byte
|
|
err error
|
|
|
|
waveOut uintptr
|
|
headers []*header
|
|
|
|
cond *sync.Cond
|
|
}
|
|
|
|
func (p *players) setContext(context *context) {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
p.context = context
|
|
}
|
|
|
|
func (p *players) add(player *playerImpl) error {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
|
|
if p.err != nil {
|
|
return p.err
|
|
}
|
|
|
|
if p.players == nil {
|
|
p.players = map[*playerImpl]struct{}{}
|
|
}
|
|
p.players[player] = struct{}{}
|
|
|
|
if p.waveOut != 0 {
|
|
return nil
|
|
}
|
|
|
|
numBlockAlign := p.context.channelNum * p.context.bitDepthInBytes
|
|
f := &waveformatex{
|
|
wFormatTag: waveFormatPCM,
|
|
nChannels: uint16(p.context.channelNum),
|
|
nSamplesPerSec: uint32(p.context.sampleRate),
|
|
nAvgBytesPerSec: uint32(p.context.sampleRate * numBlockAlign),
|
|
wBitsPerSample: uint16(p.context.bitDepthInBytes * 8),
|
|
nBlockAlign: uint16(numBlockAlign),
|
|
}
|
|
|
|
w, err := waveOutOpen(f, waveOutOpenCallback)
|
|
const elementNotFound = 1168
|
|
if e, ok := err.(*winmmError); ok && e.errno == elementNotFound {
|
|
// TODO: No device was found. Return the dummy device (hajimehoshi/oto#77).
|
|
// TODO: Retry to open the device when possible.
|
|
return err
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
p.waveOut = w
|
|
p.headers = make([]*header, 0, 4)
|
|
for len(p.headers) < cap(p.headers) {
|
|
h, err := newHeader(p.waveOut, headerBufferSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.headers = append(p.headers, h)
|
|
}
|
|
|
|
if err := p.readAndWriteBuffersImpl(); err != nil {
|
|
return err
|
|
}
|
|
|
|
go p.loop()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *players) remove(player *playerImpl) error {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
return p.removeImpl(player)
|
|
}
|
|
|
|
func (p *players) removeImpl(player *playerImpl) error {
|
|
if p.err != nil {
|
|
return p.err
|
|
}
|
|
delete(p.players, player)
|
|
return nil
|
|
}
|
|
|
|
func (p *players) shouldWait() bool {
|
|
if p.waveOut == 0 {
|
|
return false
|
|
}
|
|
|
|
if len(p.buf) < headerBufferSize*len(p.headers) {
|
|
return false
|
|
}
|
|
|
|
for _, h := range p.headers {
|
|
if !h.IsQueued() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (p *players) wait() bool {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
|
|
for p.shouldWait() {
|
|
p.cond.Wait()
|
|
}
|
|
return p.waveOut != 0
|
|
}
|
|
|
|
func (p *players) loop() {
|
|
for {
|
|
if !p.wait() {
|
|
return
|
|
}
|
|
if err := p.readAndWriteBuffers(); err != nil {
|
|
p.setError(err)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *players) setError(err error) {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
p.err = err
|
|
}
|
|
|
|
func (p *players) suspend() error {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
|
|
if p.waveOut == 0 {
|
|
return nil
|
|
}
|
|
if err := waveOutPause(p.waveOut); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *players) resume() error {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
|
|
if p.waveOut == 0 {
|
|
return nil
|
|
}
|
|
if err := waveOutRestart(p.waveOut); err != nil {
|
|
return err
|
|
}
|
|
p.cond.Signal()
|
|
return nil
|
|
}
|
|
|
|
var waveOutOpenCallback = windows.NewCallbackCDecl(func(hwo, uMsg, dwInstance, dwParam1, dwParam2 uintptr) uintptr {
|
|
const womDone = 0x3bd
|
|
if uMsg != womDone {
|
|
return 0
|
|
}
|
|
thePlayers.cond.Signal()
|
|
return 0
|
|
})
|
|
|
|
func (p *players) readAndWriteBuffers() error {
|
|
p.cond.L.Lock()
|
|
defer p.cond.L.Unlock()
|
|
return p.readAndWriteBuffersImpl()
|
|
}
|
|
|
|
func (p *players) readAndWriteBuffersImpl() error {
|
|
headerNum := 0
|
|
for _, h := range p.headers {
|
|
if h.IsQueued() {
|
|
continue
|
|
}
|
|
headerNum++
|
|
}
|
|
if headerNum == 0 {
|
|
return nil
|
|
}
|
|
|
|
if n := headerBufferSize*headerNum - len(p.buf); n > 0 {
|
|
// Do mixing of the current players instead of mixing on the OS side.
|
|
// Apparently, mixing on the Go side is more effient and requires less buffers.
|
|
//
|
|
// waveOutSetVolume is not used since it doesn't work correctly in some environments.
|
|
var volumes []float64
|
|
var bufs [][]byte
|
|
for pl := range p.players {
|
|
buf := make([]byte, n)
|
|
n := pl.read(buf)
|
|
bufs = append(bufs, buf[:n])
|
|
volumes = append(volumes, pl.Volume())
|
|
}
|
|
|
|
buf := make([]byte, n)
|
|
switch p.context.bitDepthInBytes {
|
|
case 1:
|
|
const (
|
|
max = 127
|
|
min = -128
|
|
offset = 128
|
|
)
|
|
for i := 0; i < n; i++ {
|
|
var x int16
|
|
for j, b := range bufs {
|
|
if len(b) <= i {
|
|
continue
|
|
}
|
|
xx := int16(b[i]) - offset
|
|
x += int16(float64(xx) * volumes[j])
|
|
}
|
|
if x > max {
|
|
x = max
|
|
}
|
|
if x < min {
|
|
x = min
|
|
}
|
|
buf[i] = byte(x + offset)
|
|
}
|
|
case 2:
|
|
const (
|
|
max = (1 << 15) - 1
|
|
min = -(1 << 15)
|
|
)
|
|
for i := 0; i < n/2; i++ {
|
|
var x int32
|
|
for j, b := range bufs {
|
|
if len(b) <= 2*i {
|
|
continue
|
|
}
|
|
xx := int32(int16(b[2*i]) | (int16(b[2*i+1]) << 8))
|
|
x += int32(float64(xx) * volumes[j])
|
|
}
|
|
if x > max {
|
|
x = max
|
|
}
|
|
if x < min {
|
|
x = min
|
|
}
|
|
buf[2*i] = byte(x)
|
|
buf[2*i+1] = byte(x >> 8)
|
|
}
|
|
}
|
|
p.buf = append(p.buf, buf...)
|
|
}
|
|
|
|
for _, h := range p.headers {
|
|
if len(p.buf) < headerBufferSize {
|
|
break
|
|
}
|
|
|
|
if h.IsQueued() {
|
|
continue
|
|
}
|
|
|
|
if err := h.Write(p.buf[:headerBufferSize]); err != nil {
|
|
// This error can happen when e.g. a new HDMI connection is detected (hajimehoshi/oto#51).
|
|
const errorNotFound = 1168
|
|
if werr := err.(*winmmError); werr.fname == "waveOutWrite" && werr.errno == errorNotFound {
|
|
// TODO: Retry later.
|
|
}
|
|
return err
|
|
}
|
|
p.buf = p.buf[headerBufferSize:]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var thePlayers = players{
|
|
cond: sync.NewCond(&sync.Mutex{}),
|
|
}
|
|
|
|
type player struct {
|
|
p *playerImpl
|
|
}
|
|
|
|
type playerImpl struct {
|
|
context *context
|
|
src io.Reader
|
|
err error
|
|
state playerState
|
|
buf []byte
|
|
eof bool
|
|
volume float64
|
|
|
|
m sync.Mutex
|
|
}
|
|
|
|
func (c *context) NewPlayer(src io.Reader) Player {
|
|
p := &player{
|
|
p: &playerImpl{
|
|
context: c,
|
|
src: src,
|
|
volume: 1,
|
|
},
|
|
}
|
|
runtime.SetFinalizer(p, (*player).Close)
|
|
return p
|
|
}
|
|
|
|
func (p *player) Err() error {
|
|
return p.p.Err()
|
|
}
|
|
|
|
func (p *playerImpl) Err() error {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
|
|
return p.err
|
|
}
|
|
|
|
func (p *player) Play() {
|
|
p.p.Play()
|
|
}
|
|
|
|
func (p *playerImpl) Play() {
|
|
// Call Play asynchronously since playImpl might take long.
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
close(ch)
|
|
p.playImpl()
|
|
}()
|
|
|
|
// Wait until the mutex is locked in the above goroutine.
|
|
<-ch
|
|
}
|
|
|
|
func (p *playerImpl) playImpl() {
|
|
if p.err != nil {
|
|
return
|
|
}
|
|
if p.state != playerPaused {
|
|
return
|
|
}
|
|
|
|
buf := make([]byte, p.context.maxBufferSize())
|
|
for len(p.buf) < p.context.maxBufferSize() {
|
|
n, err := p.src.Read(buf)
|
|
if err != nil && err != io.EOF {
|
|
p.setErrorImpl(err)
|
|
return
|
|
}
|
|
p.buf = append(p.buf, buf[:n]...)
|
|
if err == io.EOF {
|
|
p.eof = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if p.eof && len(p.buf) == 0 {
|
|
return
|
|
}
|
|
|
|
// thePlayers can has another mutex, and double mutex might introduce a deadlock.
|
|
p.m.Unlock()
|
|
err := thePlayers.add(p)
|
|
p.m.Lock()
|
|
|
|
if err != nil {
|
|
p.setErrorImpl(err)
|
|
return
|
|
}
|
|
|
|
p.state = playerPlay
|
|
// Do not create the player's own loop. Scheduling on Winodws is inefficient compared to the other OSes.
|
|
}
|
|
|
|
func (p *player) Pause() {
|
|
p.p.Pause()
|
|
}
|
|
|
|
func (p *playerImpl) Pause() {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
p.pauseImpl()
|
|
}
|
|
|
|
func (p *playerImpl) pauseImpl() {
|
|
if p.err != nil {
|
|
return
|
|
}
|
|
if p.state != playerPlay {
|
|
return
|
|
}
|
|
|
|
p.state = playerPaused
|
|
}
|
|
|
|
func (p *player) Reset() {
|
|
p.p.Reset()
|
|
}
|
|
|
|
func (p *playerImpl) Reset() {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
p.resetImpl()
|
|
}
|
|
|
|
func (p *playerImpl) resetImpl() {
|
|
if p.err != nil {
|
|
return
|
|
}
|
|
if p.state == playerClosed {
|
|
return
|
|
}
|
|
|
|
p.state = playerPaused
|
|
p.buf = p.buf[:0]
|
|
p.eof = false
|
|
}
|
|
|
|
func (p *player) IsPlaying() bool {
|
|
return p.p.IsPlaying()
|
|
}
|
|
|
|
func (p *playerImpl) IsPlaying() bool {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
return p.state == playerPlay
|
|
}
|
|
|
|
func (p *player) Volume() float64 {
|
|
return p.p.Volume()
|
|
}
|
|
|
|
func (p *playerImpl) Volume() float64 {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
return p.volume
|
|
}
|
|
|
|
func (p *player) SetVolume(volume float64) {
|
|
p.p.SetVolume(volume)
|
|
}
|
|
|
|
func (p *playerImpl) SetVolume(volume float64) {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
p.volume = volume
|
|
}
|
|
|
|
func (p *player) UnplayedBufferSize() int {
|
|
return p.p.UnplayedBufferSize()
|
|
}
|
|
|
|
func (p *playerImpl) UnplayedBufferSize() int {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
return len(p.buf)
|
|
}
|
|
|
|
func (p *player) Close() error {
|
|
runtime.SetFinalizer(p, nil)
|
|
return p.p.Close()
|
|
}
|
|
|
|
func (p *playerImpl) Close() error {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
return p.closeImpl()
|
|
}
|
|
|
|
func (p *playerImpl) closeImpl() error {
|
|
p.state = playerClosed
|
|
|
|
p.m.Unlock()
|
|
err := thePlayers.remove(p)
|
|
p.m.Lock()
|
|
|
|
if err != nil && p.err == nil {
|
|
p.err = err
|
|
}
|
|
return p.err
|
|
}
|
|
|
|
func (p *playerImpl) setError(err error) {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
p.setErrorImpl(err)
|
|
}
|
|
|
|
func (p *playerImpl) setErrorImpl(err error) {
|
|
p.err = err
|
|
p.closeImpl()
|
|
}
|
|
|
|
func (p *playerImpl) read(buf []byte) int {
|
|
p.m.Lock()
|
|
defer p.m.Unlock()
|
|
|
|
if p.state != playerPlay {
|
|
return 0
|
|
}
|
|
|
|
if len(p.buf) == 0 && p.eof {
|
|
p.pauseImpl()
|
|
return 0
|
|
}
|
|
|
|
if len(p.buf) < p.context.maxBufferSize() {
|
|
buf := make([]byte, p.context.maxBufferSize())
|
|
n, err := p.src.Read(buf)
|
|
if err != nil && err != io.EOF {
|
|
p.setErrorImpl(err)
|
|
return len(buf)
|
|
}
|
|
|
|
p.buf = append(p.buf, buf[:n]...)
|
|
if err == io.EOF {
|
|
p.eof = true
|
|
}
|
|
}
|
|
|
|
bytesPerSample := p.context.channelNum * p.context.bitDepthInBytes
|
|
n := len(p.buf) / bytesPerSample * bytesPerSample
|
|
n = copy(buf, p.buf[:n])
|
|
p.buf = p.buf[n:]
|
|
|
|
return n
|
|
}
|