Skip to content

Commit cd4894d

Browse files
Merge pull request libretro#17928 from BinBashBanana/master
Rewrite RWebAudio driver
2 parents d8cd500 + 86308ef commit cd4894d

File tree

9 files changed

+327
-193
lines changed

9 files changed

+327
-193
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- EMSCRIPTEN/RWEBCAM: Fix camera driver
44
- EMSCRIPTEN/RWEBINPUT: Add accelerometer/gyroscope support
55
- EMSCRIPTEN/RWEBPAD: Add rumble support
6+
- EMSCRIPTEN/RWEBAUDIO: Rewrite driver, set as default audio driver
67

78
# 1.21.0
89
- 3DS: Fix unique IDs for newer cores

Makefile.emscripten

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ HAVE_GLSL = 1
2727
HAVE_SCREENSHOTS = 1
2828
HAVE_REWIND = 1
2929
HAVE_AUDIOMIXER = 1
30-
HAVE_CC_RESAMPLER = 1
30+
HAVE_CC_RESAMPLER ?= 1
3131
HAVE_EGL ?= 0
3232
HAVE_OPENGLES = 1
3333
HAVE_RJPEG = 0
@@ -54,27 +54,21 @@ HAVE_BSV_MOVIE = 1
5454
HAVE_CHD ?= 0
5555
HAVE_NETPLAYDISCOVERY ?= 0
5656

57-
HAVE_AL ?= 1
58-
5957
# enables pthreads, requires special headers on the web server:
6058
# see https://web.dev/articles/coop-coep
6159
HAVE_THREADS ?= 0
6260

6361
# requires HAVE_THREADS
6462
HAVE_AUDIOWORKLET ?= 0
6563

66-
# WARNING -- READ BEFORE ENABLING
67-
# The rwebaudio driver is known to have several audio bugs, such as
68-
# minor crackling, or the entire page freezing/crashing.
69-
# It works perfectly on chrome, but even firefox has really bad audio quality.
70-
# I should also note, the driver on iOS is completely broken (crashes the page).
71-
# You have been warned.
72-
HAVE_RWEBAUDIO ?= 0
73-
74-
# whether the browser thread is allowed to block to wait for audio to play,
75-
# may lead to the issues mentioned above.
76-
# currently this variable is only used by audioworklet;
77-
# rwebaudio will always busywait and openal will never busywait.
64+
# doesn't work on PROXY_TO_PTHREAD
65+
HAVE_RWEBAUDIO ?= 1
66+
67+
# requires ASYNC or PROXY_TO_PTHREAD
68+
HAVE_AL ?= 0
69+
70+
# whether the browser thread is allowed to block to wait for audio to play, not CPU usage-friendly!
71+
# currently this variable is only used by rwebaudio and audioworklet; openal will never busywait.
7872
ALLOW_AUDIO_BUSYWAIT ?= 0
7973

8074
# minimal asyncify; better performance than full asyncify,
@@ -196,6 +190,9 @@ endif
196190
ifeq ($(HAVE_RWEBAUDIO), 1)
197191
LDFLAGS += --js-library emscripten/library_rwebaudio.js
198192
DEFINES += -DHAVE_RWEBAUDIO
193+
ifeq ($(PROXY_TO_PTHREAD), 1)
194+
$(error ERROR: RWEBAUDIO is incompatible with PROXY_TO_PTHREAD)
195+
endif
199196
endif
200197

201198
ifeq ($(HAVE_AUDIOWORKLET), 1)
@@ -205,18 +202,24 @@ ifeq ($(HAVE_AUDIOWORKLET), 1)
205202
ifeq ($(HAVE_THREADS), 0)
206203
$(error ERROR: AUDIOWORKLET requires HAVE_THREADS)
207204
endif
208-
ifeq ($(PROXY_TO_PTHREAD), 1)
209-
else ifeq ($(ASYNC), 1)
205+
endif
206+
207+
ifeq ($(HAVE_AL), 1)
208+
LDFLAGS += -lopenal
209+
DEFINES += -DHAVE_AL
210+
endif
211+
212+
ifeq ($(PROXY_TO_PTHREAD), 1)
213+
else ifeq ($(ASYNC), 1)
214+
else
215+
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
216+
ifeq ($(MIN_ASYNC), 1)
217+
DEFINES += -DEMSCRIPTEN_AUDIO_ASYNC_BLOCK
210218
else
211-
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
212-
ifeq ($(MIN_ASYNC), 1)
213-
DEFINES += -DEMSCRIPTEN_AUDIO_ASYNC_BLOCK
214-
else
215-
DEFINES += -DEMSCRIPTEN_AUDIO_FAKE_BLOCK
216-
endif
217-
ifneq ($(ALLOW_AUDIO_BUSYWAIT), 1)
218-
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
219-
endif
219+
DEFINES += -DEMSCRIPTEN_AUDIO_FAKE_BLOCK
220+
endif
221+
ifneq ($(ALLOW_AUDIO_BUSYWAIT), 1)
222+
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
220223
endif
221224
endif
222225

@@ -227,16 +230,11 @@ endif
227230
# explanation of some of these defines:
228231
# EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK: audio blocking occurs in the main loop instead of in the audio driver functions.
229232
# EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK: along with above, enables external blocking in the write function.
230-
# ALLOW_AUDIO_BUSYWAIT: write function will busywait. init function may still use an external block.
233+
# EMSCRIPTEN_AUDIO_BUSYWAIT: write function will busywait. init function may still use an external block.
231234
# EMSCRIPTEN_AUDIO_ASYNC_BLOCK: external block uses emscripten_sleep (requires MIN_ASYNC).
232235
# EMSCRIPTEN_AUDIO_FAKE_BLOCK: external block uses main loop timing (doesn't require asyncify).
233236
# when building with either PROXY_TO_PTHREAD or ASYNC (full asyncify), none of the above are required.
234237

235-
ifeq ($(HAVE_AL), 1)
236-
LDFLAGS += -lopenal
237-
DEFINES += -DHAVE_AL
238-
endif
239-
240238
ifeq ($(HAVE_THREADS), 1)
241239
LDFLAGS += -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD_POOL_SIZE)
242240
CFLAGS += -pthread -s SHARED_MEMORY

audio/drivers/rwebaudio.c

Lines changed: 174 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* RetroArch - A frontend for libretro.
22
* Copyright (C) 2010-2015 - Michael Lelli
33
* Copyright (C) 2011-2017 - Daniel De Matteis
4+
* Copyright (C) 2025 - OlyB
45
*
56
* RetroArch is free software: you can redistribute it and/or modify it under the terms
67
* of the GNU General Public License as published by the Free Software Found-
@@ -17,81 +18,230 @@
1718
#include <stdlib.h>
1819
#include <unistd.h>
1920
#include <boolean.h>
21+
#include <retro_timers.h>
2022

2123
#include "../audio_driver.h"
24+
#include "../../verbosity.h"
25+
#include "../../frontend/drivers/platform_emscripten.h"
26+
27+
#define RWEBAUDIO_BUFFER_SIZE_MS 10
2228

2329
/* forward declarations */
2430
unsigned RWebAudioSampleRate(void);
2531
void *RWebAudioInit(unsigned latency);
26-
ssize_t RWebAudioWrite(const void *s, size_t len);
32+
ssize_t RWebAudioQueueBuffer(size_t num_frames, float *left, float *right);
2733
bool RWebAudioStop(void);
2834
bool RWebAudioStart(void);
2935
void RWebAudioSetNonblockState(bool state);
3036
void RWebAudioFree(void);
31-
size_t RWebAudioWriteAvail(void);
32-
size_t RWebAudioBufferSize(void);
37+
size_t RWebAudioWriteAvailFrames(void);
38+
size_t RWebAudioBufferSizeFrames(void);
39+
void RWebAudioRecalibrateTime(void);
40+
bool RWebAudioResumeCtx(void);
3341

34-
typedef struct rweb_audio
42+
typedef struct rwebaudio_data
3543
{
36-
bool is_paused;
37-
} rweb_audio_t;
44+
size_t tmpbuf_frames;
45+
size_t tmpbuf_offset;
46+
float *tmpbuf_left;
47+
float *tmpbuf_right;
48+
bool nonblock;
49+
bool running;
50+
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
51+
bool block_requested;
52+
#endif
53+
} rwebaudio_data_t;
54+
55+
static rwebaudio_data_t *rwebaudio_static_data = NULL;
3856

3957
static void rwebaudio_free(void *data)
4058
{
59+
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
60+
if (!rwebaudio)
61+
return;
62+
4163
RWebAudioFree();
42-
free(data);
64+
if (rwebaudio->tmpbuf_left)
65+
free(rwebaudio->tmpbuf_left);
66+
if (rwebaudio->tmpbuf_right)
67+
free(rwebaudio->tmpbuf_right);
68+
free(rwebaudio);
69+
rwebaudio_static_data = NULL;
4370
}
4471

4572
static void *rwebaudio_init(const char *device, unsigned rate, unsigned latency,
4673
unsigned block_frames,
4774
unsigned *new_rate)
4875
{
49-
rweb_audio_t *rwebaudio = (rweb_audio_t*)calloc(1, sizeof(rweb_audio_t));
76+
rwebaudio_data_t *rwebaudio;
77+
if (rwebaudio_static_data)
78+
{
79+
RARCH_ERR("[RWebAudio] Tried to start already running driver!\n");
80+
return NULL;
81+
}
82+
83+
rwebaudio = (rwebaudio_data_t*)calloc(1, sizeof(rwebaudio_data_t));
5084
if (!rwebaudio)
5185
return NULL;
52-
if (RWebAudioInit(latency))
53-
*new_rate = RWebAudioSampleRate();
86+
if (!RWebAudioInit(latency))
87+
{
88+
RARCH_ERR("[RWebAudio] Failed to initialize driver!\n");
89+
return NULL;
90+
}
91+
rwebaudio_static_data = rwebaudio;
92+
*new_rate = RWebAudioSampleRate();
93+
rwebaudio->tmpbuf_frames = RWEBAUDIO_BUFFER_SIZE_MS * *new_rate / 1000;
94+
rwebaudio->tmpbuf_left = memalign(sizeof(float), rwebaudio->tmpbuf_frames * sizeof(float));
95+
rwebaudio->tmpbuf_right = memalign(sizeof(float), rwebaudio->tmpbuf_frames * sizeof(float));
96+
RARCH_LOG("[RWebAudio] Device rate: %d Hz.\n", *new_rate);
97+
RARCH_LOG("[RWebAudio] Buffer size: %lu bytes.\n", RWebAudioBufferSizeFrames() * 2 * sizeof(float));
5498
return rwebaudio;
5599
}
56100

57101
static ssize_t rwebaudio_write(void *data, const void *s, size_t len)
58102
{
59-
return RWebAudioWrite(s, len);
103+
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
104+
const float *samples = (const float*)s;
105+
size_t num_frames = len / 2 / sizeof(float);
106+
size_t written = 0;
107+
if (!rwebaudio)
108+
return -1;
109+
110+
while (num_frames)
111+
{
112+
rwebaudio->tmpbuf_left[rwebaudio->tmpbuf_offset] = *(samples++);
113+
rwebaudio->tmpbuf_right[rwebaudio->tmpbuf_offset] = *(samples++);
114+
num_frames--;
115+
if (++rwebaudio->tmpbuf_offset == rwebaudio->tmpbuf_frames)
116+
{
117+
size_t queued = RWebAudioQueueBuffer(rwebaudio->tmpbuf_frames, rwebaudio->tmpbuf_left, rwebaudio->tmpbuf_right);
118+
rwebaudio->tmpbuf_offset = 0;
119+
/* fast-forward or context is suspended */
120+
if (queued < rwebaudio->tmpbuf_frames)
121+
break;
122+
written += queued;
123+
}
124+
}
125+
126+
if (rwebaudio->nonblock)
127+
return written;
128+
129+
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
130+
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
131+
if (RWebAudioWriteAvailFrames() == 0)
132+
{
133+
rwebaudio->block_requested = true;
134+
platform_emscripten_enter_fake_block(1);
135+
}
136+
#endif
137+
/* async external block doesn't need to do anything else */
138+
#else
139+
while (RWebAudioWriteAvailFrames() == 0)
140+
{
141+
#ifdef EMSCRIPTEN_FULL_ASYNCIFY
142+
retro_sleep(1);
143+
#endif
144+
RWebAudioResumeCtx();
145+
}
146+
#endif
147+
148+
return written;
60149
}
61150

62-
static bool rwebaudio_stop(void *data)
151+
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
152+
/* returns true if fake block should continue */
153+
bool rwebaudio_external_block(void)
63154
{
64-
rweb_audio_t *rwebaudio = (rweb_audio_t*)data;
155+
rwebaudio_data_t *rwebaudio = rwebaudio_static_data;
156+
65157
if (!rwebaudio)
66158
return false;
67-
rwebaudio->is_paused = true;
68-
return RWebAudioStop();
159+
160+
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
161+
if (!rwebaudio->block_requested)
162+
return false;
163+
#endif
164+
165+
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
166+
while (!rwebaudio->nonblock && RWebAudioWriteAvailFrames() == 0)
167+
{
168+
RWebAudioResumeCtx();
169+
#ifdef EMSCRIPTEN_AUDIO_ASYNC_BLOCK
170+
retro_sleep(1);
171+
#else
172+
return true;
173+
#endif
174+
}
175+
#endif
176+
177+
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
178+
rwebaudio->block_requested = false;
179+
platform_emscripten_exit_fake_block();
180+
return true; /* return to RAF if needed */
181+
#endif
182+
return false;
69183
}
184+
#endif
70185

71-
static void rwebaudio_set_nonblock_state(void *data, bool state)
186+
void rwebaudio_recalibrate_time(void)
72187
{
73-
RWebAudioSetNonblockState(state);
188+
if (rwebaudio_static_data)
189+
RWebAudioRecalibrateTime();
74190
}
75191

76-
static bool rwebaudio_alive(void *data)
192+
static bool rwebaudio_stop(void *data)
77193
{
78-
rweb_audio_t *rwebaudio = (rweb_audio_t*)data;
194+
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
79195
if (!rwebaudio)
80196
return false;
81-
return !rwebaudio->is_paused;
197+
rwebaudio->running = false;
198+
return RWebAudioStop();
82199
}
83200

84201
static bool rwebaudio_start(void *data, bool is_shutdown)
85202
{
86-
rweb_audio_t *rwebaudio = (rweb_audio_t*)data;
203+
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
87204
if (!rwebaudio)
88205
return false;
89-
rwebaudio->is_paused = false;
206+
rwebaudio->running = true;
90207
return RWebAudioStart();
91208
}
92209

93-
static size_t rwebaudio_write_avail(void *data) {return RWebAudioWriteAvail();}
94-
static size_t rwebaudio_buffer_size(void *data) {return RWebAudioBufferSize();}
210+
static bool rwebaudio_alive(void *data)
211+
{
212+
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
213+
if (!rwebaudio)
214+
return false;
215+
return rwebaudio->running;
216+
}
217+
218+
static void rwebaudio_set_nonblock_state(void *data, bool state)
219+
{
220+
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
221+
if (!rwebaudio)
222+
return;
223+
rwebaudio->nonblock = state;
224+
RWebAudioSetNonblockState(state);
225+
}
226+
227+
static size_t rwebaudio_write_avail(void *data)
228+
{
229+
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
230+
size_t avail_frames;
231+
if (!rwebaudio)
232+
return 0;
233+
234+
avail_frames = RWebAudioWriteAvailFrames();
235+
if (avail_frames > rwebaudio->tmpbuf_offset)
236+
return (avail_frames - rwebaudio->tmpbuf_offset) * 2 * sizeof(float);
237+
return 0;
238+
}
239+
240+
static size_t rwebaudio_buffer_size(void *data)
241+
{
242+
return RWebAudioBufferSizeFrames() * 2 * sizeof(float);
243+
}
244+
95245
static bool rwebaudio_use_float(void *data) { return true; }
96246

97247
audio_driver_t audio_rwebaudio = {

config.def.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,7 +1177,7 @@
11771177

11781178
/* Desired audio latency in milliseconds. Might not be honored
11791179
* if driver can't provide given latency. */
1180-
#if defined(ANDROID) || defined(RETROFW) || defined(MIYOO) || (defined(EMSCRIPTEN) && !defined(HAVE_AUDIOWORKLET))
1180+
#if defined(ANDROID) || defined(RETROFW) || defined(MIYOO) || (defined(EMSCRIPTEN) && defined(HAVE_AL))
11811181
/* For most Android devices, 64ms is way too low. */
11821182
#define DEFAULT_OUT_LATENCY 128
11831183
#define DEFAULT_IN_LATENCY 128
@@ -1683,7 +1683,7 @@
16831683

16841684
#if defined(__QNX__) || defined(_XBOX1) || defined(_XBOX360) || (defined(__MACH__) && defined(IOS)) || defined(ANDROID) || defined(WIIU) || defined(HAVE_NEON) || defined(GEKKO) || defined(__ARM_NEON__) || defined(__PS3__)
16851685
#define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_LOWER
1686-
#elif defined(PSP) || defined(_3DS) || defined(VITA) || defined(PS2) || defined(DINGUX) || defined(EMSCRIPTEN)
1686+
#elif defined(PSP) || defined(_3DS) || defined(VITA) || defined(PS2) || defined(DINGUX)
16871687
#define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_LOWEST
16881688
#else
16891689
#define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_NORMAL

0 commit comments

Comments
 (0)