mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 19:28:57 +01:00
audio/internal/readerdriver: Use ALSA instead of PulseAudio
Updates #1633 Closes #1761 Updates #1766
This commit is contained in:
parent
3dcb3d7ea7
commit
13e84f0224
@ -8,7 +8,6 @@ packages:
|
||||
- libxrandr-dev
|
||||
- mesa-dev
|
||||
- pkgconf
|
||||
- pulseaudio-dev
|
||||
- go
|
||||
sources:
|
||||
- https://github.com/hajimehoshi/ebiten
|
||||
|
@ -1,8 +1,7 @@
|
||||
image: archlinux
|
||||
packages:
|
||||
- alsa
|
||||
- alsa-lib
|
||||
- libxcursor
|
||||
- libpulse
|
||||
- libxi
|
||||
- libxinerama
|
||||
- libxrandr
|
||||
|
@ -4,7 +4,6 @@ packages:
|
||||
- libc6-dev
|
||||
- libglu1-mesa-dev
|
||||
- libgl1-mesa-dev
|
||||
- libpulse-dev
|
||||
- libxcursor-dev
|
||||
- libxi-dev
|
||||
- libxinerama-dev
|
||||
|
@ -9,7 +9,6 @@ packages:
|
||||
- mesa-libGLES-devel
|
||||
- mesa-libGLU-devel
|
||||
- pkg-config
|
||||
- pulseaudio-libs-devel
|
||||
- go
|
||||
sources:
|
||||
- https://github.com/hajimehoshi/ebiten
|
||||
|
@ -4,7 +4,6 @@ packages:
|
||||
- libc6-dev
|
||||
- libglu1-mesa-dev
|
||||
- libgl1-mesa-dev
|
||||
- libpulse-dev
|
||||
- libxcursor-dev
|
||||
- libxi-dev
|
||||
- libxinerama-dev
|
||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libasound2-dev libgl1-mesa-dev libpulse-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libxxf86vm-dev
|
||||
sudo apt-get install libasound2-dev libgl1-mesa-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libxxf86vm-dev
|
||||
|
||||
- name: Install wasmbrowsertest
|
||||
run: |
|
||||
|
@ -17,20 +17,14 @@
|
||||
|
||||
package readerdriver
|
||||
|
||||
// #cgo pkg-config: libpulse
|
||||
// #cgo LDFLAGS: -lpulse
|
||||
// #cgo pkg-config: alsa
|
||||
//
|
||||
// #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);
|
||||
// #include <alsa/asoundlib.h>
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@ -43,16 +37,17 @@ type context struct {
|
||||
channelNum int
|
||||
bitDepthInBytes int
|
||||
|
||||
mainloop *C.pa_threaded_mainloop
|
||||
context *C.pa_context
|
||||
stream *C.pa_stream
|
||||
handle *C.snd_pcm_t
|
||||
supportsPause bool
|
||||
|
||||
players *players
|
||||
}
|
||||
|
||||
var theContext *context
|
||||
|
||||
const bufferSize = 4096
|
||||
func alsaError(err C.int) error {
|
||||
return fmt.Errorf("readerdriver: ALSA error: %s", C.GoString(C.snd_strerror(err)))
|
||||
}
|
||||
|
||||
func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan struct{}, error) {
|
||||
ready := make(chan struct{})
|
||||
@ -66,146 +61,126 @@ func NewContext(sampleRate, channelNum, bitDepthInBytes int) (Context, chan stru
|
||||
}
|
||||
theContext = c
|
||||
|
||||
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")
|
||||
// Open a default ALSA audio device for blocking stream playback
|
||||
cname := C.CString("default")
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
if err := C.snd_pcm_open(&c.handle, cname, C.SND_PCM_STREAM_PLAYBACK, 0); err < 0 {
|
||||
return nil, nil, alsaError(err)
|
||||
}
|
||||
|
||||
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")
|
||||
periodSize := C.snd_pcm_uframes_t(1024)
|
||||
bufferSize := periodSize * 2
|
||||
if err := c.alsaPcmHwParams(sampleRate, channelNum, &bufferSize, &periodSize); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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 code := C.pa_threaded_mainloop_start(c.mainloop); code != 0 {
|
||||
return nil, nil, fmt.Errorf("readerdriver: pa_threaded_mainloop_start failed: %s", C.GoString(C.pa_strerror(code)))
|
||||
}
|
||||
if code := C.pa_context_connect(c.context, nil, C.PA_CONTEXT_NOAUTOSPAWN, nil); code != 0 {
|
||||
return nil, nil, fmt.Errorf("readerdriver: pa_context_connect failed: %s", C.GoString(C.pa_strerror(code)))
|
||||
}
|
||||
|
||||
// Wait until the context is ready.
|
||||
go func() {
|
||||
buf32 := make([]float32, int(periodSize)*c.channelNum)
|
||||
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 err := c.readAndWrite(buf32); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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), nil)
|
||||
|
||||
const defaultValue = 0xffffffff
|
||||
bufferAttr := C.pa_buffer_attr{
|
||||
maxlength: defaultValue,
|
||||
tlength: bufferSize,
|
||||
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 code := C.pa_stream_connect_playback(c.stream, nil, &bufferAttr, streamFlags, nil, nil); code != 0 {
|
||||
return nil, nil, fmt.Errorf("readerdriver: pa_stream_connect_playback failed: %s", C.GoString(C.pa_strerror(code)))
|
||||
}
|
||||
|
||||
// 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))
|
||||
}()
|
||||
|
||||
return c, ready, nil
|
||||
}
|
||||
|
||||
func (c *context) alsaPcmHwParams(sampleRate, channelNum int, bufferSize, periodSize *C.snd_pcm_uframes_t) error {
|
||||
var params *C.snd_pcm_hw_params_t
|
||||
C.snd_pcm_hw_params_malloc(¶ms)
|
||||
defer C.free(unsafe.Pointer(params))
|
||||
|
||||
if err := C.snd_pcm_hw_params_any(c.handle, params); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
if err := C.snd_pcm_hw_params_set_access(c.handle, params, C.SND_PCM_ACCESS_RW_INTERLEAVED); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
if err := C.snd_pcm_hw_params_set_format(c.handle, params, C.SND_PCM_FORMAT_FLOAT_LE); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
if err := C.snd_pcm_hw_params_set_channels(c.handle, params, C.unsigned(channelNum)); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
if err := C.snd_pcm_hw_params_set_rate_resample(c.handle, params, 1); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
sr := C.unsigned(sampleRate)
|
||||
if err := C.snd_pcm_hw_params_set_rate_near(c.handle, params, &sr, nil); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
if err := C.snd_pcm_hw_params_set_buffer_size_near(c.handle, params, bufferSize); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
if err := C.snd_pcm_hw_params_set_period_size_near(c.handle, params, periodSize, nil); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
if err := C.snd_pcm_hw_params(c.handle, params); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
c.supportsPause = C.snd_pcm_hw_params_can_pause(params) == 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) readAndWrite(buf32 []float32) error {
|
||||
for i := range buf32 {
|
||||
buf32[i] = 0
|
||||
}
|
||||
c.players.read(buf32)
|
||||
|
||||
for len(buf32) > 0 {
|
||||
n := C.snd_pcm_writei(c.handle, unsafe.Pointer(&buf32[0]), C.snd_pcm_uframes_t(len(buf32)/c.channelNum))
|
||||
if n == -C.EPIPE {
|
||||
// Underrun or overrun occurred.
|
||||
if err := C.snd_pcm_prepare(c.handle); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if n < 0 {
|
||||
return alsaError(C.int(n))
|
||||
}
|
||||
buf32 = buf32[int(n)*c.channelNum:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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))
|
||||
if c.supportsPause {
|
||||
if err := C.snd_pcm_pause(c.handle, 1); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := C.snd_pcm_drop(c.handle); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
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))
|
||||
if c.supportsPause {
|
||||
if err := C.snd_pcm_pause(c.handle, 0); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
try:
|
||||
if err := C.snd_pcm_resume(c.handle); err < 0 {
|
||||
if err == -C.EAGAIN {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
goto try
|
||||
}
|
||||
if err == -C.ENOSYS {
|
||||
if err := C.snd_pcm_prepare(c.handle); err < 0 {
|
||||
return alsaError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return alsaError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//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 := theContext
|
||||
|
||||
var buf unsafe.Pointer
|
||||
var buf32 []float32
|
||||
var bytesToFill C.size_t = bufferSize
|
||||
for n := int(requestedBytes); n > 0; n -= int(bytesToFill) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
c.players.read(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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user