Merge pull request #17928 from BinBashBanana/master

Rewrite RWebAudio driver
This commit is contained in:
LibretroAdmin 2025-05-26 12:53:27 +02:00 committed by GitHub
commit cd4894d627
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 330 additions and 196 deletions

View File

@ -3,6 +3,7 @@
- EMSCRIPTEN/RWEBCAM: Fix camera driver - EMSCRIPTEN/RWEBCAM: Fix camera driver
- EMSCRIPTEN/RWEBINPUT: Add accelerometer/gyroscope support - EMSCRIPTEN/RWEBINPUT: Add accelerometer/gyroscope support
- EMSCRIPTEN/RWEBPAD: Add rumble support - EMSCRIPTEN/RWEBPAD: Add rumble support
- EMSCRIPTEN/RWEBAUDIO: Rewrite driver, set as default audio driver
# 1.21.0 # 1.21.0
- 3DS: Fix unique IDs for newer cores - 3DS: Fix unique IDs for newer cores

View File

@ -27,7 +27,7 @@ HAVE_GLSL = 1
HAVE_SCREENSHOTS = 1 HAVE_SCREENSHOTS = 1
HAVE_REWIND = 1 HAVE_REWIND = 1
HAVE_AUDIOMIXER = 1 HAVE_AUDIOMIXER = 1
HAVE_CC_RESAMPLER = 1 HAVE_CC_RESAMPLER ?= 1
HAVE_EGL ?= 0 HAVE_EGL ?= 0
HAVE_OPENGLES = 1 HAVE_OPENGLES = 1
HAVE_RJPEG = 0 HAVE_RJPEG = 0
@ -54,8 +54,6 @@ HAVE_BSV_MOVIE = 1
HAVE_CHD ?= 0 HAVE_CHD ?= 0
HAVE_NETPLAYDISCOVERY ?= 0 HAVE_NETPLAYDISCOVERY ?= 0
HAVE_AL ?= 1
# enables pthreads, requires special headers on the web server: # enables pthreads, requires special headers on the web server:
# see https://web.dev/articles/coop-coep # see https://web.dev/articles/coop-coep
HAVE_THREADS ?= 0 HAVE_THREADS ?= 0
@ -63,18 +61,14 @@ HAVE_THREADS ?= 0
# requires HAVE_THREADS # requires HAVE_THREADS
HAVE_AUDIOWORKLET ?= 0 HAVE_AUDIOWORKLET ?= 0
# WARNING -- READ BEFORE ENABLING # doesn't work on PROXY_TO_PTHREAD
# The rwebaudio driver is known to have several audio bugs, such as HAVE_RWEBAUDIO ?= 1
# minor crackling, or the entire page freezing/crashing.
# It works perfectly on chrome, but even firefox has really bad audio quality.
# I should also note, the driver on iOS is completely broken (crashes the page).
# You have been warned.
HAVE_RWEBAUDIO ?= 0
# whether the browser thread is allowed to block to wait for audio to play, # requires ASYNC or PROXY_TO_PTHREAD
# may lead to the issues mentioned above. HAVE_AL ?= 0
# currently this variable is only used by audioworklet;
# rwebaudio will always busywait and openal will never busywait. # whether the browser thread is allowed to block to wait for audio to play, not CPU usage-friendly!
# currently this variable is only used by rwebaudio and audioworklet; openal will never busywait.
ALLOW_AUDIO_BUSYWAIT ?= 0 ALLOW_AUDIO_BUSYWAIT ?= 0
# minimal asyncify; better performance than full asyncify, # minimal asyncify; better performance than full asyncify,
@ -196,6 +190,9 @@ endif
ifeq ($(HAVE_RWEBAUDIO), 1) ifeq ($(HAVE_RWEBAUDIO), 1)
LDFLAGS += --js-library emscripten/library_rwebaudio.js LDFLAGS += --js-library emscripten/library_rwebaudio.js
DEFINES += -DHAVE_RWEBAUDIO DEFINES += -DHAVE_RWEBAUDIO
ifeq ($(PROXY_TO_PTHREAD), 1)
$(error ERROR: RWEBAUDIO is incompatible with PROXY_TO_PTHREAD)
endif
endif endif
ifeq ($(HAVE_AUDIOWORKLET), 1) ifeq ($(HAVE_AUDIOWORKLET), 1)
@ -205,6 +202,13 @@ ifeq ($(HAVE_AUDIOWORKLET), 1)
ifeq ($(HAVE_THREADS), 0) ifeq ($(HAVE_THREADS), 0)
$(error ERROR: AUDIOWORKLET requires HAVE_THREADS) $(error ERROR: AUDIOWORKLET requires HAVE_THREADS)
endif endif
endif
ifeq ($(HAVE_AL), 1)
LDFLAGS += -lopenal
DEFINES += -DHAVE_AL
endif
ifeq ($(PROXY_TO_PTHREAD), 1) ifeq ($(PROXY_TO_PTHREAD), 1)
else ifeq ($(ASYNC), 1) else ifeq ($(ASYNC), 1)
else else
@ -218,7 +222,6 @@ ifeq ($(HAVE_AUDIOWORKLET), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
endif endif
endif endif
endif
ifeq ($(ALLOW_AUDIO_BUSYWAIT), 1) ifeq ($(ALLOW_AUDIO_BUSYWAIT), 1)
DEFINES += -DEMSCRIPTEN_AUDIO_BUSYWAIT DEFINES += -DEMSCRIPTEN_AUDIO_BUSYWAIT
@ -227,16 +230,11 @@ endif
# explanation of some of these defines: # explanation of some of these defines:
# EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK: audio blocking occurs in the main loop instead of in the audio driver functions. # EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK: audio blocking occurs in the main loop instead of in the audio driver functions.
# EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK: along with above, enables external blocking in the write function. # EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK: along with above, enables external blocking in the write function.
# ALLOW_AUDIO_BUSYWAIT: write function will busywait. init function may still use an external block. # EMSCRIPTEN_AUDIO_BUSYWAIT: write function will busywait. init function may still use an external block.
# EMSCRIPTEN_AUDIO_ASYNC_BLOCK: external block uses emscripten_sleep (requires MIN_ASYNC). # EMSCRIPTEN_AUDIO_ASYNC_BLOCK: external block uses emscripten_sleep (requires MIN_ASYNC).
# EMSCRIPTEN_AUDIO_FAKE_BLOCK: external block uses main loop timing (doesn't require asyncify). # EMSCRIPTEN_AUDIO_FAKE_BLOCK: external block uses main loop timing (doesn't require asyncify).
# when building with either PROXY_TO_PTHREAD or ASYNC (full asyncify), none of the above are required. # when building with either PROXY_TO_PTHREAD or ASYNC (full asyncify), none of the above are required.
ifeq ($(HAVE_AL), 1)
LDFLAGS += -lopenal
DEFINES += -DHAVE_AL
endif
ifeq ($(HAVE_THREADS), 1) ifeq ($(HAVE_THREADS), 1)
LDFLAGS += -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD_POOL_SIZE) LDFLAGS += -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD_POOL_SIZE)
CFLAGS += -pthread -s SHARED_MEMORY CFLAGS += -pthread -s SHARED_MEMORY

View File

@ -1,6 +1,7 @@
/* RetroArch - A frontend for libretro. /* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2015 - Michael Lelli * Copyright (C) 2010-2015 - Michael Lelli
* Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2011-2017 - Daniel De Matteis
* Copyright (C) 2025 - OlyB
* *
* RetroArch is free software: you can redistribute it and/or modify it under the terms * RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found- * of the GNU General Public License as published by the Free Software Found-
@ -17,81 +18,230 @@
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <boolean.h> #include <boolean.h>
#include <retro_timers.h>
#include "../audio_driver.h" #include "../audio_driver.h"
#include "../../verbosity.h"
#include "../../frontend/drivers/platform_emscripten.h"
#define RWEBAUDIO_BUFFER_SIZE_MS 10
/* forward declarations */ /* forward declarations */
unsigned RWebAudioSampleRate(void); unsigned RWebAudioSampleRate(void);
void *RWebAudioInit(unsigned latency); void *RWebAudioInit(unsigned latency);
ssize_t RWebAudioWrite(const void *s, size_t len); ssize_t RWebAudioQueueBuffer(size_t num_frames, float *left, float *right);
bool RWebAudioStop(void); bool RWebAudioStop(void);
bool RWebAudioStart(void); bool RWebAudioStart(void);
void RWebAudioSetNonblockState(bool state); void RWebAudioSetNonblockState(bool state);
void RWebAudioFree(void); void RWebAudioFree(void);
size_t RWebAudioWriteAvail(void); size_t RWebAudioWriteAvailFrames(void);
size_t RWebAudioBufferSize(void); size_t RWebAudioBufferSizeFrames(void);
void RWebAudioRecalibrateTime(void);
bool RWebAudioResumeCtx(void);
typedef struct rweb_audio typedef struct rwebaudio_data
{ {
bool is_paused; size_t tmpbuf_frames;
} rweb_audio_t; size_t tmpbuf_offset;
float *tmpbuf_left;
float *tmpbuf_right;
bool nonblock;
bool running;
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
bool block_requested;
#endif
} rwebaudio_data_t;
static rwebaudio_data_t *rwebaudio_static_data = NULL;
static void rwebaudio_free(void *data) static void rwebaudio_free(void *data)
{ {
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
if (!rwebaudio)
return;
RWebAudioFree(); RWebAudioFree();
free(data); if (rwebaudio->tmpbuf_left)
free(rwebaudio->tmpbuf_left);
if (rwebaudio->tmpbuf_right)
free(rwebaudio->tmpbuf_right);
free(rwebaudio);
rwebaudio_static_data = NULL;
} }
static void *rwebaudio_init(const char *device, unsigned rate, unsigned latency, static void *rwebaudio_init(const char *device, unsigned rate, unsigned latency,
unsigned block_frames, unsigned block_frames,
unsigned *new_rate) unsigned *new_rate)
{ {
rweb_audio_t *rwebaudio = (rweb_audio_t*)calloc(1, sizeof(rweb_audio_t)); rwebaudio_data_t *rwebaudio;
if (rwebaudio_static_data)
{
RARCH_ERR("[RWebAudio] Tried to start already running driver!\n");
return NULL;
}
rwebaudio = (rwebaudio_data_t*)calloc(1, sizeof(rwebaudio_data_t));
if (!rwebaudio) if (!rwebaudio)
return NULL; return NULL;
if (RWebAudioInit(latency)) if (!RWebAudioInit(latency))
{
RARCH_ERR("[RWebAudio] Failed to initialize driver!\n");
return NULL;
}
rwebaudio_static_data = rwebaudio;
*new_rate = RWebAudioSampleRate(); *new_rate = RWebAudioSampleRate();
rwebaudio->tmpbuf_frames = RWEBAUDIO_BUFFER_SIZE_MS * *new_rate / 1000;
rwebaudio->tmpbuf_left = memalign(sizeof(float), rwebaudio->tmpbuf_frames * sizeof(float));
rwebaudio->tmpbuf_right = memalign(sizeof(float), rwebaudio->tmpbuf_frames * sizeof(float));
RARCH_LOG("[RWebAudio] Device rate: %d Hz.\n", *new_rate);
RARCH_LOG("[RWebAudio] Buffer size: %lu bytes.\n", RWebAudioBufferSizeFrames() * 2 * sizeof(float));
return rwebaudio; return rwebaudio;
} }
static ssize_t rwebaudio_write(void *data, const void *s, size_t len) static ssize_t rwebaudio_write(void *data, const void *s, size_t len)
{ {
return RWebAudioWrite(s, len); rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
const float *samples = (const float*)s;
size_t num_frames = len / 2 / sizeof(float);
size_t written = 0;
if (!rwebaudio)
return -1;
while (num_frames)
{
rwebaudio->tmpbuf_left[rwebaudio->tmpbuf_offset] = *(samples++);
rwebaudio->tmpbuf_right[rwebaudio->tmpbuf_offset] = *(samples++);
num_frames--;
if (++rwebaudio->tmpbuf_offset == rwebaudio->tmpbuf_frames)
{
size_t queued = RWebAudioQueueBuffer(rwebaudio->tmpbuf_frames, rwebaudio->tmpbuf_left, rwebaudio->tmpbuf_right);
rwebaudio->tmpbuf_offset = 0;
/* fast-forward or context is suspended */
if (queued < rwebaudio->tmpbuf_frames)
break;
written += queued;
}
}
if (rwebaudio->nonblock)
return written;
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
if (RWebAudioWriteAvailFrames() == 0)
{
rwebaudio->block_requested = true;
platform_emscripten_enter_fake_block(1);
}
#endif
/* async external block doesn't need to do anything else */
#else
while (RWebAudioWriteAvailFrames() == 0)
{
#ifdef EMSCRIPTEN_FULL_ASYNCIFY
retro_sleep(1);
#endif
RWebAudioResumeCtx();
}
#endif
return written;
}
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
/* returns true if fake block should continue */
bool rwebaudio_external_block(void)
{
rwebaudio_data_t *rwebaudio = rwebaudio_static_data;
if (!rwebaudio)
return false;
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
if (!rwebaudio->block_requested)
return false;
#endif
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
while (!rwebaudio->nonblock && RWebAudioWriteAvailFrames() == 0)
{
RWebAudioResumeCtx();
#ifdef EMSCRIPTEN_AUDIO_ASYNC_BLOCK
retro_sleep(1);
#else
return true;
#endif
}
#endif
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
rwebaudio->block_requested = false;
platform_emscripten_exit_fake_block();
return true; /* return to RAF if needed */
#endif
return false;
}
#endif
void rwebaudio_recalibrate_time(void)
{
if (rwebaudio_static_data)
RWebAudioRecalibrateTime();
} }
static bool rwebaudio_stop(void *data) static bool rwebaudio_stop(void *data)
{ {
rweb_audio_t *rwebaudio = (rweb_audio_t*)data; rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
if (!rwebaudio) if (!rwebaudio)
return false; return false;
rwebaudio->is_paused = true; rwebaudio->running = false;
return RWebAudioStop(); return RWebAudioStop();
} }
static void rwebaudio_set_nonblock_state(void *data, bool state)
{
RWebAudioSetNonblockState(state);
}
static bool rwebaudio_alive(void *data)
{
rweb_audio_t *rwebaudio = (rweb_audio_t*)data;
if (!rwebaudio)
return false;
return !rwebaudio->is_paused;
}
static bool rwebaudio_start(void *data, bool is_shutdown) static bool rwebaudio_start(void *data, bool is_shutdown)
{ {
rweb_audio_t *rwebaudio = (rweb_audio_t*)data; rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
if (!rwebaudio) if (!rwebaudio)
return false; return false;
rwebaudio->is_paused = false; rwebaudio->running = true;
return RWebAudioStart(); return RWebAudioStart();
} }
static size_t rwebaudio_write_avail(void *data) {return RWebAudioWriteAvail();} static bool rwebaudio_alive(void *data)
static size_t rwebaudio_buffer_size(void *data) {return RWebAudioBufferSize();} {
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
if (!rwebaudio)
return false;
return rwebaudio->running;
}
static void rwebaudio_set_nonblock_state(void *data, bool state)
{
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
if (!rwebaudio)
return;
rwebaudio->nonblock = state;
RWebAudioSetNonblockState(state);
}
static size_t rwebaudio_write_avail(void *data)
{
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
size_t avail_frames;
if (!rwebaudio)
return 0;
avail_frames = RWebAudioWriteAvailFrames();
if (avail_frames > rwebaudio->tmpbuf_offset)
return (avail_frames - rwebaudio->tmpbuf_offset) * 2 * sizeof(float);
return 0;
}
static size_t rwebaudio_buffer_size(void *data)
{
return RWebAudioBufferSizeFrames() * 2 * sizeof(float);
}
static bool rwebaudio_use_float(void *data) { return true; } static bool rwebaudio_use_float(void *data) { return true; }
audio_driver_t audio_rwebaudio = { audio_driver_t audio_rwebaudio = {

View File

@ -1177,7 +1177,7 @@
/* Desired audio latency in milliseconds. Might not be honored /* Desired audio latency in milliseconds. Might not be honored
* if driver can't provide given latency. */ * if driver can't provide given latency. */
#if defined(ANDROID) || defined(RETROFW) || defined(MIYOO) || (defined(EMSCRIPTEN) && !defined(HAVE_AUDIOWORKLET)) #if defined(ANDROID) || defined(RETROFW) || defined(MIYOO) || (defined(EMSCRIPTEN) && defined(HAVE_AL))
/* For most Android devices, 64ms is way too low. */ /* For most Android devices, 64ms is way too low. */
#define DEFAULT_OUT_LATENCY 128 #define DEFAULT_OUT_LATENCY 128
#define DEFAULT_IN_LATENCY 128 #define DEFAULT_IN_LATENCY 128
@ -1683,7 +1683,7 @@
#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__) #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__)
#define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_LOWER #define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_LOWER
#elif defined(PSP) || defined(_3DS) || defined(VITA) || defined(PS2) || defined(DINGUX) || defined(EMSCRIPTEN) #elif defined(PSP) || defined(_3DS) || defined(VITA) || defined(PS2) || defined(DINGUX)
#define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_LOWEST #define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_LOWEST
#else #else
#define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_NORMAL #define DEFAULT_AUDIO_RESAMPLER_QUALITY_LEVEL RESAMPLER_QUALITY_NORMAL

View File

@ -586,7 +586,7 @@ static const enum microphone_driver_enum MICROPHONE_DEFAULT_DRIVER = MICROPHONE_
#if defined(RS90) || defined(MIYOO) #if defined(RS90) || defined(MIYOO)
static const enum audio_resampler_driver_enum AUDIO_DEFAULT_RESAMPLER_DRIVER = AUDIO_RESAMPLER_NEAREST; static const enum audio_resampler_driver_enum AUDIO_DEFAULT_RESAMPLER_DRIVER = AUDIO_RESAMPLER_NEAREST;
#elif defined(PSP) || defined(EMSCRIPTEN) #elif defined(PSP) || (defined(EMSCRIPTEN) && defined(HAVE_CC_RESAMPLER))
static const enum audio_resampler_driver_enum AUDIO_DEFAULT_RESAMPLER_DRIVER = AUDIO_RESAMPLER_CC; static const enum audio_resampler_driver_enum AUDIO_DEFAULT_RESAMPLER_DRIVER = AUDIO_RESAMPLER_CC;
#else #else
static const enum audio_resampler_driver_enum AUDIO_DEFAULT_RESAMPLER_DRIVER = AUDIO_RESAMPLER_SINC; static const enum audio_resampler_driver_enum AUDIO_DEFAULT_RESAMPLER_DRIVER = AUDIO_RESAMPLER_SINC;

View File

@ -1,140 +1,81 @@
//"use strict"; //"use strict";
var LibraryRWebAudio = { var LibraryRWebAudio = {
$RA__deps: ['$Browser'], $RWA: {
$RA: { /* add 10 ms of silence on start, seems to prevent underrun on start/unpausing (terrible crackling in firefox) */
BUFFER_SIZE: 2048, MIN_START_OFFSET_SEC: 0.01,
/* firefox and safari need more latency (transparent to audio driver) */
EXTRA_LATENCY_SEC_NONCHROME: 0.01,
EXTRA_LATENCY_SEC_CHROME: 0,
PLATFORM_EMSCRIPTEN_BROWSER_CHROMIUM: 1,
context: null, context: null,
buffers: [], contextRunning: false,
numBuffers: 0,
bufIndex: 0,
bufOffset: 0,
startTime: 0,
nonblock: false, nonblock: false,
currentTimeWorkaround: false, endTime: 0,
latency: 0,
setStartTime: function() { virtualBufferFrames: 0,
if (RA.context.currentTime) { currentTimeDiff: 0,
RA.startTime = window['performance']['now']() - RA.context.currentTime * 1000; extraLatencySec: 0
Module["resumeMainLoop"]();
} else window['setTimeout'](RA.setStartTime, 0);
}, },
getCurrentPerfTime: function() { /* AudioContext.currentTime can be inaccurate: https://bugzilla.mozilla.org/show_bug.cgi?id=901247 */
if (RA.startTime) return (window['performance']['now']() - RA.startTime) / 1000; $RWebAudioGetCurrentTime: function() {
else return 0; return performance.now() / 1000 - RWA.currentTimeDiff;
}, },
process: function(queueBuffers) { $RWebAudioStateChangeCB: function() {
var currentTime = RA.getCurrentPerfTime(); RWA.contextRunning = RWA.context.state == "running";
for (var i = 0; i < RA.bufIndex; i++) {
if (RA.buffers[i].endTime !== 0 && RA.buffers[i].endTime < currentTime) {
RA.buffers[i].endTime = 0;
var buf = RA.buffers.splice(i, 1);
RA.buffers[RA.numBuffers - 1] = buf[0];
i--;
RA.bufIndex--;
} else if (!RA.startTime) {
RA.setStartTime();
}
}
}, },
fillBuffer: function(buf, samples) { RWebAudioResumeCtx: function() {
var count = 0; if (!RWA.contextRunning) RWA.context.resume();
const leftBuffer = RA.buffers[RA.bufIndex].getChannelData(0); return RWA.contextRunning;
const rightBuffer = RA.buffers[RA.bufIndex].getChannelData(1);
while (samples && RA.bufOffset !== RA.BUFFER_SIZE) {
leftBuffer[RA.bufOffset] = {{{ makeGetValue('buf', 'count * 8', 'float') }}};
rightBuffer[RA.bufOffset] = {{{ makeGetValue('buf', 'count * 8 + 4', 'float') }}};
RA.bufOffset++;
count++;
samples--;
}
return count;
},
queueAudio: function() {
var index = RA.bufIndex;
var startTime;
if (RA.bufIndex) startTime = RA.buffers[RA.bufIndex - 1].endTime;
else startTime = RA.context.currentTime;
RA.buffers[index].endTime = startTime + RA.buffers[index].duration;
const bufferSource = RA.context.createBufferSource();
bufferSource.buffer = RA.buffers[index];
bufferSource.connect(RA.context.destination);
bufferSource.start(startTime);
RA.bufIndex++;
RA.bufOffset = 0;
},
block: function() {
do {
RA.process();
} while (RA.bufIndex === RA.numBuffers);
}
}, },
RWebAudioInit__deps: ["$RWebAudioStateChangeCB", "platform_emscripten_get_browser"],
RWebAudioInit: function(latency) { RWebAudioInit: function(latency) {
var ac = window['AudioContext'] || window['webkitAudioContext']; var ac = window.AudioContext || window.webkitAudioContext;
if (!ac) return 0; if (!ac) return 0;
RA.context = new ac(); RWA.context = new ac();
RWA.currentTimeDiff = performance.now() / 1000 - RWA.context.currentTime;
RWA.nonblock = false;
RWA.endTime = 0;
RWA.latency = latency;
RWA.virtualBufferFrames = Math.round(RWA.latency * RWA.context.sampleRate / 1000);
RWA.context.addEventListener("statechange", RWebAudioStateChangeCB);
RWebAudioStateChangeCB();
RWA.extraLatencySec = (_platform_emscripten_get_browser() == RWA.PLATFORM_EMSCRIPTEN_BROWSER_CHROMIUM) ? RWA.EXTRA_LATENCY_SEC_CHROME : RWA.EXTRA_LATENCY_SEC_NONCHROME;
RA.numBuffers = ((latency * RA.context.sampleRate) / (1000 * RA.BUFFER_SIZE))|0;
if (RA.numBuffers < 2) RA.numBuffers = 2;
for (var i = 0; i < RA.numBuffers; i++) {
RA.buffers[i] = RA.context.createBuffer(2, RA.BUFFER_SIZE, RA.context.sampleRate);
RA.buffers[i].endTime = 0
}
RA.nonblock = false;
RA.startTime = 0;
// chrome hack to get currentTime running
RA.context.createGain();
window['setTimeout'](RA.setStartTime, 0);
Module["pauseMainLoop"]();
return 1; return 1;
}, },
RWebAudioSampleRate: function() { RWebAudioSampleRate: function() {
return RA.context.sampleRate; return RWA.context.sampleRate;
}, },
RWebAudioWrite: function (buf, size) { RWebAudioQueueBuffer__deps: ["$RWebAudioGetCurrentTime", "RWebAudioResumeCtx", "RWebAudioWriteAvailFrames"],
RA.process(); RWebAudioQueueBuffer: function(num_frames, left, right) {
var samples = size / 8; if (RWA.nonblock && _RWebAudioWriteAvailFrames() < num_frames) return 0;
var count = 0; if (!_RWebAudioResumeCtx()) return 0;
while (samples) { var buffer = RWA.context.createBuffer(2, num_frames, RWA.context.sampleRate);
if (RA.bufIndex === RA.numBuffers) { buffer.getChannelData(0).set(HEAPF32.subarray(left >> 2, (left >> 2) + num_frames));
if (RA.nonblock) break; buffer.getChannelData(1).set(HEAPF32.subarray(right >> 2, (right >> 2) + num_frames));
else RA.block(); var bufferSource = RWA.context.createBufferSource();
} bufferSource.buffer = buffer;
bufferSource.connect(RWA.context.destination);
var fill = RA.fillBuffer(buf, samples); var currentTime = RWebAudioGetCurrentTime();
samples -= fill; /* when empty, start rounded up to nearest 1 ms, add MIN_START_OFFSET_SEC */
count += fill; var startTime = RWA.endTime > currentTime ? RWA.endTime : Math.ceil(currentTime * 1000) / 1000 + RWA.MIN_START_OFFSET_SEC;
buf += fill * 8; RWA.endTime = startTime + buffer.duration;
bufferSource.start(startTime + RWA.extraLatencySec);
if (RA.bufOffset === RA.BUFFER_SIZE) { return num_frames;
RA.queueAudio();
}
}
return count * 8;
}, },
RWebAudioStop: function() { RWebAudioStop: function() {
RA.bufIndex = 0;
RA.bufOffset = 0;
return true; return true;
}, },
@ -143,29 +84,32 @@ var LibraryRWebAudio = {
}, },
RWebAudioSetNonblockState: function(state) { RWebAudioSetNonblockState: function(state) {
RA.nonblock = state; RWA.nonblock = state;
}, },
RWebAudioFree__deps: ["$RWebAudioStateChangeCB"],
RWebAudioFree: function() { RWebAudioFree: function() {
RA.bufIndex = 0; RWA.context.removeEventListener("statechange", RWebAudioStateChangeCB);
RA.bufOffset = 0; RWA.context.close();
RWA.contextRunning = false;
}, },
RWebAudioBufferSize: function() { RWebAudioBufferSizeFrames: function() {
return RA.numBuffers * RA.BUFFER_SIZE * 8; return RWA.virtualBufferFrames;
}, },
RWebAudioWriteAvail: function() { RWebAudioWriteAvailFrames__deps: ["$RWebAudioGetCurrentTime"],
RA.process(); RWebAudioWriteAvailFrames: function() {
return ((RA.numBuffers - RA.bufIndex) * RA.BUFFER_SIZE - RA.bufOffset) * 8; var avail = Math.round(((RWA.latency / 1000) - RWA.endTime + RWebAudioGetCurrentTime()) * RWA.context.sampleRate);
if (avail <= 0) return 0;
if (avail >= RWA.virtualBufferFrames) return RWA.virtualBufferFrames;
return avail;
}, },
RWebAudioRecalibrateTime: function() { RWebAudioRecalibrateTime: function() {
if (RA.startTime) { if (RWA.contextRunning) RWA.currentTimeDiff = performance.now() / 1000 - RWA.context.currentTime;
RA.startTime = window['performance']['now']() - RA.context.currentTime * 1000;
}
} }
}; };
autoAddDeps(LibraryRWebAudio, '$RA'); autoAddDeps(LibraryRWebAudio, '$RWA');
addToLibrary(LibraryRWebAudio); addToLibrary(LibraryRWebAudio);

View File

@ -532,6 +532,16 @@ void platform_emscripten_set_canvas_size(int width, int height)
PlatformEmscriptenSetCanvasSize(width, height); PlatformEmscriptenSetCanvasSize(width, height);
} }
enum platform_emscripten_browser platform_emscripten_get_browser(void)
{
return emscripten_platform_data->browser;
}
enum platform_emscripten_os platform_emscripten_get_os(void)
{
return emscripten_platform_data->os;
}
/* frontend driver impl */ /* frontend driver impl */
static void frontend_emscripten_get_env(int *argc, char *argv[], static void frontend_emscripten_get_env(int *argc, char *argv[],

View File

@ -184,4 +184,18 @@ void platform_emscripten_set_wake_lock(bool state);
*/ */
void platform_emscripten_set_canvas_size(int width, int height); void platform_emscripten_set_canvas_size(int width, int height);
/**
* Get the browser that the program is running in.
*
* @return enum platform_emscripten_browser
*/
enum platform_emscripten_browser platform_emscripten_get_browser(void);
/**
* Get the OS that the program is running in.
*
* @return enum platform_emscripten_os
*/
enum platform_emscripten_os platform_emscripten_get_os(void);
#endif #endif

View File

@ -6004,11 +6004,17 @@ int rarch_main(int argc, char *argv[], void *data)
#if defined(EMSCRIPTEN) #if defined(EMSCRIPTEN)
#if defined(EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK) && defined(HAVE_AUDIOWORKLET) #ifdef EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
#ifdef HAVE_AUDIOWORKLET
bool audioworklet_external_block(void); bool audioworklet_external_block(void);
#endif #endif
#ifdef HAVE_RWEBAUDIO #ifdef HAVE_RWEBAUDIO
void RWebAudioRecalibrateTime(void); bool rwebaudio_external_block(void);
#endif
#endif
#ifdef HAVE_RWEBAUDIO
void rwebaudio_recalibrate_time(void);
#endif #endif
void emscripten_mainloop(void) void emscripten_mainloop(void)
@ -6033,13 +6039,19 @@ void emscripten_mainloop(void)
if (platform_emscripten_should_drop_iter()) if (platform_emscripten_should_drop_iter())
return; return;
#if defined(EMSCRIPTEN_AUDIO_FAKE_BLOCK) && defined(HAVE_AUDIOWORKLET) #ifdef HAVE_RWEBAUDIO
rwebaudio_recalibrate_time();
#endif
#ifdef EMSCRIPTEN_AUDIO_FAKE_BLOCK
#ifdef HAVE_AUDIOWORKLET
if (audioworklet_external_block()) if (audioworklet_external_block())
return; return;
#endif #endif
#ifdef HAVE_RWEBAUDIO #ifdef HAVE_RWEBAUDIO
RWebAudioRecalibrateTime(); if (rwebaudio_external_block())
return;
#endif
#endif #endif
emscripten_frame_count++; emscripten_frame_count++;
@ -6064,8 +6076,13 @@ void emscripten_mainloop(void)
ret = runloop_iterate(); ret = runloop_iterate();
#if defined(EMSCRIPTEN_AUDIO_ASYNC_BLOCK) && defined(HAVE_AUDIOWORKLET) #ifdef EMSCRIPTEN_AUDIO_ASYNC_BLOCK
#ifdef HAVE_AUDIOWORKLET
audioworklet_external_block(); audioworklet_external_block();
#endif
#ifdef HAVE_RWEBAUDIO
rwebaudio_external_block();
#endif
#endif #endif
task_queue_check(); task_queue_check();