mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 19:58:54 +01:00
audio/internal/readerdriver: Implmeent a reader driver for Windows
Updates #1549
This commit is contained in:
parent
e43dcc2265
commit
5bd1a883d1
@ -15,6 +15,7 @@
|
|||||||
// +build !android
|
// +build !android
|
||||||
// +build !darwin
|
// +build !darwin
|
||||||
// +build !js
|
// +build !js
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
package readerdriver
|
package readerdriver
|
||||||
|
|
||||||
|
631
audio/internal/readerdriver/driver_windows.go
Normal file
631
audio/internal/readerdriver/driver_windows.go
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
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) {
|
||||||
|
p.cond.L.Lock()
|
||||||
|
defer p.cond.L.Unlock()
|
||||||
|
if p.players == nil {
|
||||||
|
p.players = map[*playerImpl]struct{}{}
|
||||||
|
}
|
||||||
|
p.players[player] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *players) remove(player *playerImpl) error {
|
||||||
|
p.cond.L.Lock()
|
||||||
|
defer p.cond.L.Unlock()
|
||||||
|
|
||||||
|
delete(p.players, player)
|
||||||
|
if len(p.players) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if p.waveOut == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range p.headers {
|
||||||
|
if err := h.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.headers = p.headers[:0]
|
||||||
|
if err := waveOutClose(p.waveOut); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.waveOut = 0
|
||||||
|
p.cond.Signal()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *players) play() error {
|
||||||
|
p.cond.L.Lock()
|
||||||
|
defer p.cond.L.Unlock()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.readAndWriteBuffersImpl()
|
||||||
|
|
||||||
|
go p.loop()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
p.readAndWriteBuffers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
p.cond.L.Lock()
|
||||||
|
defer p.cond.L.Unlock()
|
||||||
|
p.readAndWriteBuffersImpl()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *players) readAndWriteBuffersImpl() {
|
||||||
|
headerNum := 0
|
||||||
|
for _, h := range p.headers {
|
||||||
|
if h.IsQueued() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
headerNum++
|
||||||
|
}
|
||||||
|
if headerNum == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
for pl := range p.players {
|
||||||
|
pl.setError(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.buf = p.buf[headerBufferSize:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// thePlayers can has another mutex, and double mutex might introduce a deadlock.
|
||||||
|
p.m.Unlock()
|
||||||
|
thePlayers.add(p)
|
||||||
|
p.m.Lock()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
p.m.Unlock()
|
||||||
|
err := thePlayers.play()
|
||||||
|
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 len(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.buf) == 0 && p.eof {
|
||||||
|
p.pauseImpl()
|
||||||
|
return len(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
256
audio/internal/readerdriver/winmm_windows.go
Normal file
256
audio/internal/readerdriver/winmm_windows.go
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
winmm = windows.NewLazySystemDLL("winmm")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
procWaveOutOpen = winmm.NewProc("waveOutOpen")
|
||||||
|
procWaveOutClose = winmm.NewProc("waveOutClose")
|
||||||
|
procWaveOutPause = winmm.NewProc("waveOutPause")
|
||||||
|
procWaveOutPrepareHeader = winmm.NewProc("waveOutPrepareHeader")
|
||||||
|
procWaveOutRestart = winmm.NewProc("waveOutRestart")
|
||||||
|
procWaveOutUnprepareHeader = winmm.NewProc("waveOutUnprepareHeader")
|
||||||
|
procWaveOutWrite = winmm.NewProc("waveOutWrite")
|
||||||
|
)
|
||||||
|
|
||||||
|
type wavehdr struct {
|
||||||
|
lpData uintptr
|
||||||
|
dwBufferLength uint32
|
||||||
|
dwBytesRecorded uint32
|
||||||
|
dwUser uintptr
|
||||||
|
dwFlags uint32
|
||||||
|
dwLoops uint32
|
||||||
|
lpNext uintptr
|
||||||
|
reserved uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type waveformatex struct {
|
||||||
|
wFormatTag uint16
|
||||||
|
nChannels uint16
|
||||||
|
nSamplesPerSec uint32
|
||||||
|
nAvgBytesPerSec uint32
|
||||||
|
nBlockAlign uint16
|
||||||
|
wBitsPerSample uint16
|
||||||
|
cbSize uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
waveFormatPCM = 1
|
||||||
|
whdrInqueue = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
type mmresult uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
mmsyserrNoerror mmresult = 0
|
||||||
|
mmsyserrError mmresult = 1
|
||||||
|
mmsyserrBaddeviceid mmresult = 2
|
||||||
|
mmsyserrAllocated mmresult = 4
|
||||||
|
mmsyserrInvalidhandle mmresult = 5
|
||||||
|
mmsyserrNodriver mmresult = 6
|
||||||
|
mmsyserrNomem mmresult = 7
|
||||||
|
waverrBadformat mmresult = 32
|
||||||
|
waverrStillplaying mmresult = 33
|
||||||
|
waverrUnprepared mmresult = 34
|
||||||
|
waverrSync mmresult = 35
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m mmresult) String() string {
|
||||||
|
switch m {
|
||||||
|
case mmsyserrNoerror:
|
||||||
|
return "MMSYSERR_NOERROR"
|
||||||
|
case mmsyserrError:
|
||||||
|
return "MMSYSERR_ERROR"
|
||||||
|
case mmsyserrBaddeviceid:
|
||||||
|
return "MMSYSERR_BADDEVICEID"
|
||||||
|
case mmsyserrAllocated:
|
||||||
|
return "MMSYSERR_ALLOCATED"
|
||||||
|
case mmsyserrInvalidhandle:
|
||||||
|
return "MMSYSERR_INVALIDHANDLE"
|
||||||
|
case mmsyserrNodriver:
|
||||||
|
return "MMSYSERR_NODRIVER"
|
||||||
|
case mmsyserrNomem:
|
||||||
|
return "MMSYSERR_NOMEM"
|
||||||
|
case waverrBadformat:
|
||||||
|
return "WAVERR_BADFORMAT"
|
||||||
|
case waverrStillplaying:
|
||||||
|
return "WAVERR_STILLPLAYING"
|
||||||
|
case waverrUnprepared:
|
||||||
|
return "WAVERR_UNPREPARED"
|
||||||
|
case waverrSync:
|
||||||
|
return "WAVERR_SYNC"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("MMRESULT (%d)", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
type winmmError struct {
|
||||||
|
fname string
|
||||||
|
errno windows.Errno
|
||||||
|
mmresult mmresult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *winmmError) Error() string {
|
||||||
|
if e.errno != 0 {
|
||||||
|
return fmt.Sprintf("winmm error at %s: Errno: %d", e.fname, e.errno)
|
||||||
|
}
|
||||||
|
if e.mmresult != mmsyserrNoerror {
|
||||||
|
return fmt.Sprintf("winmm error at %s: %s", e.fname, e.mmresult)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("winmm error at %s", e.fname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waveOutOpen(f *waveformatex, callback uintptr) (uintptr, error) {
|
||||||
|
const (
|
||||||
|
waveMapper = 0xffffffff
|
||||||
|
callbackFunction = 0x30000
|
||||||
|
)
|
||||||
|
var w uintptr
|
||||||
|
var fdwOpen uintptr
|
||||||
|
if callback != 0 {
|
||||||
|
fdwOpen |= callbackFunction
|
||||||
|
}
|
||||||
|
r, _, e := procWaveOutOpen.Call(uintptr(unsafe.Pointer(&w)), waveMapper, uintptr(unsafe.Pointer(f)),
|
||||||
|
callback, 0, fdwOpen)
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
if e.(windows.Errno) != 0 {
|
||||||
|
return 0, &winmmError{
|
||||||
|
fname: "waveOutOpen",
|
||||||
|
errno: e.(windows.Errno),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mmresult(r) != mmsyserrNoerror {
|
||||||
|
return 0, &winmmError{
|
||||||
|
fname: "waveOutOpen",
|
||||||
|
mmresult: mmresult(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waveOutClose(hwo uintptr) error {
|
||||||
|
r, _, e := procWaveOutClose.Call(hwo)
|
||||||
|
if e.(windows.Errno) != 0 {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutClose",
|
||||||
|
errno: e.(windows.Errno),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mmresult(r) != mmsyserrNoerror {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutClose",
|
||||||
|
mmresult: mmresult(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waveOutPause(hwo uintptr) error {
|
||||||
|
r, _, e := procWaveOutPause.Call(hwo)
|
||||||
|
if e.(windows.Errno) != 0 {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutPause",
|
||||||
|
errno: e.(windows.Errno),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mmresult(r) != mmsyserrNoerror {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutPause",
|
||||||
|
mmresult: mmresult(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waveOutPrepareHeader(hwo uintptr, pwh *wavehdr) error {
|
||||||
|
r, _, e := procWaveOutPrepareHeader.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(wavehdr{}))
|
||||||
|
runtime.KeepAlive(pwh)
|
||||||
|
if e.(windows.Errno) != 0 {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutPrepareHeader",
|
||||||
|
errno: e.(windows.Errno),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mmresult(r) != mmsyserrNoerror {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutPrepareHeader",
|
||||||
|
mmresult: mmresult(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waveOutRestart(hwo uintptr) error {
|
||||||
|
r, _, e := procWaveOutRestart.Call(hwo)
|
||||||
|
if e.(windows.Errno) != 0 {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutRestart",
|
||||||
|
errno: e.(windows.Errno),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mmresult(r) != mmsyserrNoerror {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutRestart",
|
||||||
|
mmresult: mmresult(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waveOutUnprepareHeader(hwo uintptr, pwh *wavehdr) error {
|
||||||
|
r, _, e := procWaveOutUnprepareHeader.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(wavehdr{}))
|
||||||
|
runtime.KeepAlive(pwh)
|
||||||
|
if e.(windows.Errno) != 0 {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutUnprepareHeader",
|
||||||
|
errno: e.(windows.Errno),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mmresult(r) != mmsyserrNoerror {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutUnprepareHeader",
|
||||||
|
mmresult: mmresult(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waveOutWrite(hwo uintptr, pwh *wavehdr) error {
|
||||||
|
r, _, e := procWaveOutWrite.Call(hwo, uintptr(unsafe.Pointer(pwh)), unsafe.Sizeof(wavehdr{}))
|
||||||
|
runtime.KeepAlive(pwh)
|
||||||
|
if e.(windows.Errno) != 0 {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutWrite",
|
||||||
|
errno: e.(windows.Errno),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mmresult(r) != mmsyserrNoerror {
|
||||||
|
return &winmmError{
|
||||||
|
fname: "waveOutWrite",
|
||||||
|
mmresult: mmresult(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user