ebiten/audio/internal/readerdriver/driver_unix.go

488 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.
// +build aix dragonfly freebsd hurd illumos linux netbsd openbsd solaris
// +build !android
package readerdriver
// #cgo pkg-config: libpulse
// #cgo LDFLAGS: -lpulse
//
// #include <pulse/pulseaudio.h>
//
// void ebiten_readerdriver_contextStateCallback(pa_context *context, void *userdata);
// void ebiten_readerdriver_streamWriteCallback(pa_stream *stream, size_t requested_bytes, void *userdata);
// void ebiten_readerdriver_streamStateCallback(pa_stream *stream, void *userdata);
// void ebiten_readerdriver_streamSuccessCallback(pa_stream *stream, void *userdata);
import "C"
import (
"fmt"
"io"
"runtime"
"sync"
"unsafe"
)
func IsAvailable() bool {
return true
}
type context struct {
sampleRate int
channelNum int
bitDepthInBytes int
mainloop *C.pa_threaded_mainloop
context *C.pa_context
stream *C.pa_stream
players map[*playerImpl]struct{}
buf []float32
m sync.Mutex
}
func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan struct{}, error) {
ready := make(chan struct{})
close(ready)
c := &context{
sampleRate: sampleRate,
channelNum: channelNum,
bitDepthInBytes: bitDepthInBytes,
}
c.mainloop = C.pa_threaded_mainloop_new()
if c.mainloop == nil {
return nil, nil, fmt.Errorf("readerdriver: pa_threaded_mainloop_new failed")
}
mainloopAPI := C.pa_threaded_mainloop_get_api(c.mainloop)
if mainloopAPI == nil {
return nil, nil, fmt.Errorf("readerdriver: pa_threaded_mainloop_get_api failed")
}
contextName := C.CString("pcm-playback")
defer C.free(unsafe.Pointer(contextName))
c.context = C.pa_context_new(mainloopAPI, contextName)
if c.context == nil {
return nil, nil, fmt.Errorf("readerdriver: pa_context_new failed")
}
C.pa_context_set_state_callback(c.context, C.pa_context_notify_cb_t(C.ebiten_readerdriver_contextStateCallback), unsafe.Pointer(c.mainloop))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
C.pa_threaded_mainloop_lock(c.mainloop)
defer C.pa_threaded_mainloop_unlock(c.mainloop)
if C.pa_threaded_mainloop_start(c.mainloop) != 0 {
return nil, nil, fmt.Errorf("readerdriver: pa_threaded_mainloop_start failed")
}
if C.pa_context_connect(c.context, nil, C.PA_CONTEXT_NOAUTOSPAWN, nil) != 0 {
return nil, nil, fmt.Errorf("readerdriver: pa_context_connect failed")
}
// Wait until the context is ready.
for {
contextState := C.pa_context_get_state(c.context)
if C.PA_CONTEXT_IS_GOOD(contextState) == 0 {
return nil, nil, fmt.Errorf("readerdriver: context state is bad")
}
if contextState == C.PA_CONTEXT_READY {
break
}
C.pa_threaded_mainloop_wait(c.mainloop)
}
sampleSpecificatiom := C.pa_sample_spec{
format: C.PA_SAMPLE_FLOAT32LE,
rate: C.uint(sampleRate),
channels: C.uchar(channelNum),
}
var m C.pa_channel_map
switch channelNum {
case 1:
C.pa_channel_map_init_mono(&m)
case 2:
C.pa_channel_map_init_stereo(&m)
}
streamName := C.CString("Playback")
defer C.free(unsafe.Pointer(streamName))
c.stream = C.pa_stream_new(c.context, streamName, &sampleSpecificatiom, &m)
C.pa_stream_set_state_callback(c.stream, C.pa_stream_notify_cb_t(C.ebiten_readerdriver_streamStateCallback), unsafe.Pointer(c.mainloop))
C.pa_stream_set_write_callback(c.stream, C.pa_stream_request_cb_t(C.ebiten_readerdriver_streamWriteCallback), unsafe.Pointer(c))
const defaultValue = 0xffffffff
bufferAttr := C.pa_buffer_attr{
maxlength: defaultValue,
tlength: 2048,
prebuf: defaultValue,
minreq: defaultValue,
}
var streamFlags C.pa_stream_flags_t = C.PA_STREAM_START_CORKED | C.PA_STREAM_INTERPOLATE_TIMING |
C.PA_STREAM_NOT_MONOTONIC | C.PA_STREAM_AUTO_TIMING_UPDATE |
C.PA_STREAM_ADJUST_LATENCY
if C.pa_stream_connect_playback(c.stream, nil, &bufferAttr, streamFlags, nil, nil) != 0 {
return nil, nil, fmt.Errorf("readerdriver: pa_stream_connect_playback failed")
}
// Wait until the stream is ready.
for {
streamState := C.pa_stream_get_state(c.stream)
if C.PA_STREAM_IS_GOOD(streamState) == 0 {
return nil, nil, fmt.Errorf("readerdriver: stream state is bad")
}
if streamState == C.PA_STREAM_READY {
break
}
C.pa_threaded_mainloop_wait(c.mainloop)
}
C.pa_stream_cork(c.stream, 0, C.pa_stream_success_cb_t(C.ebiten_readerdriver_streamSuccessCallback), unsafe.Pointer(c.mainloop))
go c.loop()
return c, ready, nil
}
func (c *context) loop() {
var players []*playerImpl
for {
c.m.Lock()
players = players[:0]
for p := range c.players {
players = append(players, p)
}
c.m.Unlock()
for _, p := range players {
p.load()
}
}
}
func (c *context) Suspend() error {
C.pa_stream_cork(c.stream, 1, C.pa_stream_success_cb_t(C.ebiten_readerdriver_streamSuccessCallback), unsafe.Pointer(c.mainloop))
return nil
}
func (c *context) Resume() error {
C.pa_stream_cork(c.stream, 0, C.pa_stream_success_cb_t(C.ebiten_readerdriver_streamSuccessCallback), unsafe.Pointer(c.mainloop))
return nil
}
func (c *context) addPlayer(player *playerImpl) {
c.m.Lock()
defer c.m.Unlock()
if c.players == nil {
c.players = map[*playerImpl]struct{}{}
}
c.players[player] = struct{}{}
}
func (c *context) removePlayer(player *playerImpl) {
c.m.Lock()
defer c.m.Unlock()
delete(c.players, player)
}
//export ebiten_readerdriver_contextStateCallback
func ebiten_readerdriver_contextStateCallback(context *C.pa_context, mainloop unsafe.Pointer) {
C.pa_threaded_mainloop_signal((*C.pa_threaded_mainloop)(mainloop), 0)
}
//export ebiten_readerdriver_streamStateCallback
func ebiten_readerdriver_streamStateCallback(stream *C.pa_stream, mainloop unsafe.Pointer) {
C.pa_threaded_mainloop_signal((*C.pa_threaded_mainloop)(mainloop), 0)
}
//export ebiten_readerdriver_streamSuccessCallback
func ebiten_readerdriver_streamSuccessCallback(stream *C.pa_stream, userdata unsafe.Pointer) {
}
//export ebiten_readerdriver_streamWriteCallback
func ebiten_readerdriver_streamWriteCallback(stream *C.pa_stream, requestedBytes C.size_t, userdata unsafe.Pointer) {
c := (*context)(userdata)
var buf unsafe.Pointer
var buf32 []float32
var bytesToFill C.size_t = 256
var players []*playerImpl
for n := int(requestedBytes); n > 0; n -= int(bytesToFill) {
c.m.Lock()
players = players[:0]
for p := range c.players {
players = append(players, p)
}
c.m.Unlock()
C.pa_stream_begin_write(stream, &buf, &bytesToFill)
if len(buf32) < int(bytesToFill)/4 {
buf32 = make([]float32, bytesToFill/4)
} else {
for i := 0; i < int(bytesToFill)/4; i++ {
buf32[i] = 0
}
}
for _, p := range players {
p.addBuffer(buf32[:bytesToFill/4])
}
for i := uintptr(0); i < uintptr(bytesToFill/4); i++ {
*(*float32)(unsafe.Pointer(uintptr(buf) + 4*i)) = buf32[i]
}
C.pa_stream_write(stream, buf, bytesToFill, nil, 0, C.PA_SEEK_RELATIVE)
}
}
type player struct {
p *playerImpl
}
type playerImpl struct {
context *context
src io.Reader
volume float64
err error
state playerState
buf []byte
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() {
p.m.Lock()
defer p.m.Unlock()
p.playImpl()
}
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 {
break
}
}
p.state = playerPlay
p.m.Unlock()
p.context.addPlayer(p)
p.m.Lock()
}
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.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.state == playerClosed {
return
}
p.state = playerPaused
p.buf = p.buf[:0]
}
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.m.Unlock()
p.context.removePlayer(p)
p.m.Lock()
if p.state == playerClosed {
return nil
}
p.state = playerClosed
p.buf = nil
return p.err
}
func (p *playerImpl) addBuffer(buf []float32) int {
p.m.Lock()
defer p.m.Unlock()
if p.state != playerPlay {
return 0
}
bitDepthInBytes := p.context.bitDepthInBytes
n := len(p.buf) / bitDepthInBytes
if n > len(buf) {
n = len(buf)
}
volume := float32(p.volume)
for i := 0; i < n; i++ {
var v float32
switch bitDepthInBytes {
case 1:
v8 := p.buf[i]
v = float32(v8-(1<<7)) / (1 << 7)
case 2:
v16 := int16(p.buf[2*i]) | (int16(p.buf[2*i+1]) << 8)
v = float32(v16) / (1 << 15)
}
buf[i] += v * volume
}
p.buf = p.buf[n*bitDepthInBytes:]
return n
}
func (p *playerImpl) load() {
p.m.Lock()
defer p.m.Unlock()
if p.err != nil {
return
}
if p.state == playerClosed {
return
}
if len(p.buf) >= p.context.maxBufferSize() {
return
}
buf := make([]byte, 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 && len(p.buf) == 0 {
p.resetImpl()
}
}
func (p *playerImpl) setErrorImpl(err error) {
p.err = err
p.closeImpl()
}