Merge pull request #17928 from BinBashBanana/master
Rewrite RWebAudio driver
This commit is contained in:
commit
cd4894d627
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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[],
|
||||||
|
|
|
@ -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
|
||||||
|
|
29
retroarch.c
29
retroarch.c
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue