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/RWEBINPUT: Add accelerometer/gyroscope support
|
||||
- EMSCRIPTEN/RWEBPAD: Add rumble support
|
||||
- EMSCRIPTEN/RWEBAUDIO: Rewrite driver, set as default audio driver
|
||||
|
||||
# 1.21.0
|
||||
- 3DS: Fix unique IDs for newer cores
|
||||
|
|
|
@ -27,7 +27,7 @@ HAVE_GLSL = 1
|
|||
HAVE_SCREENSHOTS = 1
|
||||
HAVE_REWIND = 1
|
||||
HAVE_AUDIOMIXER = 1
|
||||
HAVE_CC_RESAMPLER = 1
|
||||
HAVE_CC_RESAMPLER ?= 1
|
||||
HAVE_EGL ?= 0
|
||||
HAVE_OPENGLES = 1
|
||||
HAVE_RJPEG = 0
|
||||
|
@ -54,8 +54,6 @@ HAVE_BSV_MOVIE = 1
|
|||
HAVE_CHD ?= 0
|
||||
HAVE_NETPLAYDISCOVERY ?= 0
|
||||
|
||||
HAVE_AL ?= 1
|
||||
|
||||
# enables pthreads, requires special headers on the web server:
|
||||
# see https://web.dev/articles/coop-coep
|
||||
HAVE_THREADS ?= 0
|
||||
|
@ -63,18 +61,14 @@ HAVE_THREADS ?= 0
|
|||
# requires HAVE_THREADS
|
||||
HAVE_AUDIOWORKLET ?= 0
|
||||
|
||||
# WARNING -- READ BEFORE ENABLING
|
||||
# The rwebaudio driver is known to have several audio bugs, such as
|
||||
# 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
|
||||
# doesn't work on PROXY_TO_PTHREAD
|
||||
HAVE_RWEBAUDIO ?= 1
|
||||
|
||||
# whether the browser thread is allowed to block to wait for audio to play,
|
||||
# may lead to the issues mentioned above.
|
||||
# currently this variable is only used by audioworklet;
|
||||
# rwebaudio will always busywait and openal will never busywait.
|
||||
# requires ASYNC or PROXY_TO_PTHREAD
|
||||
HAVE_AL ?= 0
|
||||
|
||||
# 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
|
||||
|
||||
# minimal asyncify; better performance than full asyncify,
|
||||
|
@ -196,6 +190,9 @@ endif
|
|||
ifeq ($(HAVE_RWEBAUDIO), 1)
|
||||
LDFLAGS += --js-library emscripten/library_rwebaudio.js
|
||||
DEFINES += -DHAVE_RWEBAUDIO
|
||||
ifeq ($(PROXY_TO_PTHREAD), 1)
|
||||
$(error ERROR: RWEBAUDIO is incompatible with PROXY_TO_PTHREAD)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_AUDIOWORKLET), 1)
|
||||
|
@ -205,6 +202,13 @@ ifeq ($(HAVE_AUDIOWORKLET), 1)
|
|||
ifeq ($(HAVE_THREADS), 0)
|
||||
$(error ERROR: AUDIOWORKLET requires HAVE_THREADS)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_AL), 1)
|
||||
LDFLAGS += -lopenal
|
||||
DEFINES += -DHAVE_AL
|
||||
endif
|
||||
|
||||
ifeq ($(PROXY_TO_PTHREAD), 1)
|
||||
else ifeq ($(ASYNC), 1)
|
||||
else
|
||||
|
@ -218,7 +222,6 @@ ifeq ($(HAVE_AUDIOWORKLET), 1)
|
|||
DEFINES += -DEMSCRIPTEN_AUDIO_EXTERNAL_WRITE_BLOCK
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(ALLOW_AUDIO_BUSYWAIT), 1)
|
||||
DEFINES += -DEMSCRIPTEN_AUDIO_BUSYWAIT
|
||||
|
@ -227,16 +230,11 @@ endif
|
|||
# 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_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_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.
|
||||
|
||||
ifeq ($(HAVE_AL), 1)
|
||||
LDFLAGS += -lopenal
|
||||
DEFINES += -DHAVE_AL
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_THREADS), 1)
|
||||
LDFLAGS += -pthread -s PTHREAD_POOL_SIZE=$(PTHREAD_POOL_SIZE)
|
||||
CFLAGS += -pthread -s SHARED_MEMORY
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2010-2015 - Michael Lelli
|
||||
* 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
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
|
@ -17,81 +18,230 @@
|
|||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <boolean.h>
|
||||
#include <retro_timers.h>
|
||||
|
||||
#include "../audio_driver.h"
|
||||
#include "../../verbosity.h"
|
||||
#include "../../frontend/drivers/platform_emscripten.h"
|
||||
|
||||
#define RWEBAUDIO_BUFFER_SIZE_MS 10
|
||||
|
||||
/* forward declarations */
|
||||
unsigned RWebAudioSampleRate(void);
|
||||
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 RWebAudioStart(void);
|
||||
void RWebAudioSetNonblockState(bool state);
|
||||
void RWebAudioFree(void);
|
||||
size_t RWebAudioWriteAvail(void);
|
||||
size_t RWebAudioBufferSize(void);
|
||||
size_t RWebAudioWriteAvailFrames(void);
|
||||
size_t RWebAudioBufferSizeFrames(void);
|
||||
void RWebAudioRecalibrateTime(void);
|
||||
bool RWebAudioResumeCtx(void);
|
||||
|
||||
typedef struct rweb_audio
|
||||
typedef struct rwebaudio_data
|
||||
{
|
||||
bool is_paused;
|
||||
} rweb_audio_t;
|
||||
size_t tmpbuf_frames;
|
||||
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)
|
||||
{
|
||||
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
|
||||
if (!rwebaudio)
|
||||
return;
|
||||
|
||||
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,
|
||||
unsigned block_frames,
|
||||
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)
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
rweb_audio_t *rwebaudio = (rweb_audio_t*)data;
|
||||
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
|
||||
if (!rwebaudio)
|
||||
return false;
|
||||
rwebaudio->is_paused = true;
|
||||
rwebaudio->running = false;
|
||||
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)
|
||||
{
|
||||
rweb_audio_t *rwebaudio = (rweb_audio_t*)data;
|
||||
rwebaudio_data_t *rwebaudio = (rwebaudio_data_t*)data;
|
||||
if (!rwebaudio)
|
||||
return false;
|
||||
rwebaudio->is_paused = false;
|
||||
rwebaudio->running = true;
|
||||
return RWebAudioStart();
|
||||
}
|
||||
|
||||
static size_t rwebaudio_write_avail(void *data) {return RWebAudioWriteAvail();}
|
||||
static size_t rwebaudio_buffer_size(void *data) {return RWebAudioBufferSize();}
|
||||
static bool rwebaudio_alive(void *data)
|
||||
{
|
||||
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; }
|
||||
|
||||
audio_driver_t audio_rwebaudio = {
|
||||
|
|
|
@ -1177,7 +1177,7 @@
|
|||
|
||||
/* Desired audio latency in milliseconds. Might not be honored
|
||||
* 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. */
|
||||
#define DEFAULT_OUT_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__)
|
||||
#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
|
||||
#else
|
||||
#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)
|
||||
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;
|
||||
#else
|
||||
static const enum audio_resampler_driver_enum AUDIO_DEFAULT_RESAMPLER_DRIVER = AUDIO_RESAMPLER_SINC;
|
||||
|
|
|
@ -1,140 +1,81 @@
|
|||
//"use strict";
|
||||
|
||||
var LibraryRWebAudio = {
|
||||
$RA__deps: ['$Browser'],
|
||||
$RA: {
|
||||
BUFFER_SIZE: 2048,
|
||||
|
||||
$RWA: {
|
||||
/* add 10 ms of silence on start, seems to prevent underrun on start/unpausing (terrible crackling in firefox) */
|
||||
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,
|
||||
buffers: [],
|
||||
numBuffers: 0,
|
||||
bufIndex: 0,
|
||||
bufOffset: 0,
|
||||
startTime: 0,
|
||||
contextRunning: false,
|
||||
nonblock: false,
|
||||
currentTimeWorkaround: false,
|
||||
|
||||
setStartTime: function() {
|
||||
if (RA.context.currentTime) {
|
||||
RA.startTime = window['performance']['now']() - RA.context.currentTime * 1000;
|
||||
Module["resumeMainLoop"]();
|
||||
} else window['setTimeout'](RA.setStartTime, 0);
|
||||
endTime: 0,
|
||||
latency: 0,
|
||||
virtualBufferFrames: 0,
|
||||
currentTimeDiff: 0,
|
||||
extraLatencySec: 0
|
||||
},
|
||||
|
||||
getCurrentPerfTime: function() {
|
||||
if (RA.startTime) return (window['performance']['now']() - RA.startTime) / 1000;
|
||||
else return 0;
|
||||
/* AudioContext.currentTime can be inaccurate: https://bugzilla.mozilla.org/show_bug.cgi?id=901247 */
|
||||
$RWebAudioGetCurrentTime: function() {
|
||||
return performance.now() / 1000 - RWA.currentTimeDiff;
|
||||
},
|
||||
|
||||
process: function(queueBuffers) {
|
||||
var currentTime = RA.getCurrentPerfTime();
|
||||
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();
|
||||
}
|
||||
}
|
||||
$RWebAudioStateChangeCB: function() {
|
||||
RWA.contextRunning = RWA.context.state == "running";
|
||||
},
|
||||
|
||||
fillBuffer: function(buf, samples) {
|
||||
var count = 0;
|
||||
const leftBuffer = RA.buffers[RA.bufIndex].getChannelData(0);
|
||||
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);
|
||||
}
|
||||
RWebAudioResumeCtx: function() {
|
||||
if (!RWA.contextRunning) RWA.context.resume();
|
||||
return RWA.contextRunning;
|
||||
},
|
||||
|
||||
RWebAudioInit__deps: ["$RWebAudioStateChangeCB", "platform_emscripten_get_browser"],
|
||||
RWebAudioInit: function(latency) {
|
||||
var ac = window['AudioContext'] || window['webkitAudioContext'];
|
||||
|
||||
var ac = window.AudioContext || window.webkitAudioContext;
|
||||
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;
|
||||
},
|
||||
|
||||
RWebAudioSampleRate: function() {
|
||||
return RA.context.sampleRate;
|
||||
return RWA.context.sampleRate;
|
||||
},
|
||||
|
||||
RWebAudioWrite: function (buf, size) {
|
||||
RA.process();
|
||||
var samples = size / 8;
|
||||
var count = 0;
|
||||
RWebAudioQueueBuffer__deps: ["$RWebAudioGetCurrentTime", "RWebAudioResumeCtx", "RWebAudioWriteAvailFrames"],
|
||||
RWebAudioQueueBuffer: function(num_frames, left, right) {
|
||||
if (RWA.nonblock && _RWebAudioWriteAvailFrames() < num_frames) return 0;
|
||||
if (!_RWebAudioResumeCtx()) return 0;
|
||||
|
||||
while (samples) {
|
||||
if (RA.bufIndex === RA.numBuffers) {
|
||||
if (RA.nonblock) break;
|
||||
else RA.block();
|
||||
}
|
||||
var buffer = RWA.context.createBuffer(2, num_frames, RWA.context.sampleRate);
|
||||
buffer.getChannelData(0).set(HEAPF32.subarray(left >> 2, (left >> 2) + num_frames));
|
||||
buffer.getChannelData(1).set(HEAPF32.subarray(right >> 2, (right >> 2) + num_frames));
|
||||
var bufferSource = RWA.context.createBufferSource();
|
||||
bufferSource.buffer = buffer;
|
||||
bufferSource.connect(RWA.context.destination);
|
||||
|
||||
var fill = RA.fillBuffer(buf, samples);
|
||||
samples -= fill;
|
||||
count += fill;
|
||||
buf += fill * 8;
|
||||
var currentTime = RWebAudioGetCurrentTime();
|
||||
/* when empty, start rounded up to nearest 1 ms, add MIN_START_OFFSET_SEC */
|
||||
var startTime = RWA.endTime > currentTime ? RWA.endTime : Math.ceil(currentTime * 1000) / 1000 + RWA.MIN_START_OFFSET_SEC;
|
||||
RWA.endTime = startTime + buffer.duration;
|
||||
bufferSource.start(startTime + RWA.extraLatencySec);
|
||||
|
||||
if (RA.bufOffset === RA.BUFFER_SIZE) {
|
||||
RA.queueAudio();
|
||||
}
|
||||
}
|
||||
|
||||
return count * 8;
|
||||
return num_frames;
|
||||
},
|
||||
|
||||
RWebAudioStop: function() {
|
||||
RA.bufIndex = 0;
|
||||
RA.bufOffset = 0;
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@ -143,29 +84,32 @@ var LibraryRWebAudio = {
|
|||
},
|
||||
|
||||
RWebAudioSetNonblockState: function(state) {
|
||||
RA.nonblock = state;
|
||||
RWA.nonblock = state;
|
||||
},
|
||||
|
||||
RWebAudioFree__deps: ["$RWebAudioStateChangeCB"],
|
||||
RWebAudioFree: function() {
|
||||
RA.bufIndex = 0;
|
||||
RA.bufOffset = 0;
|
||||
RWA.context.removeEventListener("statechange", RWebAudioStateChangeCB);
|
||||
RWA.context.close();
|
||||
RWA.contextRunning = false;
|
||||
},
|
||||
|
||||
RWebAudioBufferSize: function() {
|
||||
return RA.numBuffers * RA.BUFFER_SIZE * 8;
|
||||
RWebAudioBufferSizeFrames: function() {
|
||||
return RWA.virtualBufferFrames;
|
||||
},
|
||||
|
||||
RWebAudioWriteAvail: function() {
|
||||
RA.process();
|
||||
return ((RA.numBuffers - RA.bufIndex) * RA.BUFFER_SIZE - RA.bufOffset) * 8;
|
||||
RWebAudioWriteAvailFrames__deps: ["$RWebAudioGetCurrentTime"],
|
||||
RWebAudioWriteAvailFrames: function() {
|
||||
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() {
|
||||
if (RA.startTime) {
|
||||
RA.startTime = window['performance']['now']() - RA.context.currentTime * 1000;
|
||||
}
|
||||
if (RWA.contextRunning) RWA.currentTimeDiff = performance.now() / 1000 - RWA.context.currentTime;
|
||||
}
|
||||
};
|
||||
|
||||
autoAddDeps(LibraryRWebAudio, '$RA');
|
||||
autoAddDeps(LibraryRWebAudio, '$RWA');
|
||||
addToLibrary(LibraryRWebAudio);
|
||||
|
|
|
@ -532,6 +532,16 @@ void platform_emscripten_set_canvas_size(int width, int 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 */
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
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_AUDIO_EXTERNAL_BLOCK) && defined(HAVE_AUDIOWORKLET)
|
||||
#ifdef EMSCRIPTEN_AUDIO_EXTERNAL_BLOCK
|
||||
#ifdef HAVE_AUDIOWORKLET
|
||||
bool audioworklet_external_block(void);
|
||||
#endif
|
||||
#ifdef HAVE_RWEBAUDIO
|
||||
void RWebAudioRecalibrateTime(void);
|
||||
bool rwebaudio_external_block(void);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RWEBAUDIO
|
||||
void rwebaudio_recalibrate_time(void);
|
||||
#endif
|
||||
|
||||
void emscripten_mainloop(void)
|
||||
|
@ -6033,13 +6039,19 @@ void emscripten_mainloop(void)
|
|||
if (platform_emscripten_should_drop_iter())
|
||||
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())
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RWEBAUDIO
|
||||
RWebAudioRecalibrateTime();
|
||||
if (rwebaudio_external_block())
|
||||
return;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
emscripten_frame_count++;
|
||||
|
@ -6064,8 +6076,13 @@ void emscripten_mainloop(void)
|
|||
|
||||
ret = runloop_iterate();
|
||||
|
||||
#if defined(EMSCRIPTEN_AUDIO_ASYNC_BLOCK) && defined(HAVE_AUDIOWORKLET)
|
||||
#ifdef EMSCRIPTEN_AUDIO_ASYNC_BLOCK
|
||||
#ifdef HAVE_AUDIOWORKLET
|
||||
audioworklet_external_block();
|
||||
#endif
|
||||
#ifdef HAVE_RWEBAUDIO
|
||||
rwebaudio_external_block();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
task_queue_check();
|
||||
|
|
Loading…
Reference in New Issue