mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-23 16:54:03 +02:00
Web Audio: Get single-threaded mode working to spec.
This commit is contained in:
+79
-5
@@ -42375,6 +42375,7 @@ typedef struct ma_context_state_webaudio
|
|||||||
|
|
||||||
typedef struct ma_device_state_webaudio
|
typedef struct ma_device_state_webaudio
|
||||||
{
|
{
|
||||||
|
ma_device_state_async async; /* Only used in single-threaded mode. Multi-threaded mode does everything straight from the Web Audio processing callback. */
|
||||||
#if defined(MA_USE_AUDIO_WORKLETS)
|
#if defined(MA_USE_AUDIO_WORKLETS)
|
||||||
EMSCRIPTEN_WEBAUDIO_T audioContext;
|
EMSCRIPTEN_WEBAUDIO_T audioContext;
|
||||||
EMSCRIPTEN_WEBAUDIO_T audioWorklet;
|
EMSCRIPTEN_WEBAUDIO_T audioWorklet;
|
||||||
@@ -42431,13 +42432,25 @@ void EMSCRIPTEN_KEEPALIVE ma_free_emscripten(void* p, const ma_allocation_callba
|
|||||||
|
|
||||||
void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_capture__webaudio(ma_device* pDevice, int frameCount, float* pFrames)
|
void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_capture__webaudio(ma_device* pDevice, int frameCount, float* pFrames)
|
||||||
{
|
{
|
||||||
|
ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice);
|
||||||
|
|
||||||
|
if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) {
|
||||||
|
ma_device_state_async_process(&pDeviceStateWebAudio->async, pDevice, NULL, pFrames, (ma_uint32)frameCount);
|
||||||
|
} else {
|
||||||
ma_device_handle_backend_data_callback(pDevice, NULL, pFrames, (ma_uint32)frameCount);
|
ma_device_handle_backend_data_callback(pDevice, NULL, pFrames, (ma_uint32)frameCount);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_playback__webaudio(ma_device* pDevice, int frameCount, float* pFrames)
|
void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_playback__webaudio(ma_device* pDevice, int frameCount, float* pFrames)
|
||||||
{
|
{
|
||||||
|
ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice);
|
||||||
|
|
||||||
|
if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) {
|
||||||
|
ma_device_state_async_process(&pDeviceStateWebAudio->async, pDevice, pFrames, NULL, (ma_uint32)frameCount);
|
||||||
|
} else {
|
||||||
ma_device_handle_backend_data_callback(pDevice, pFrames, NULL, (ma_uint32)frameCount);
|
ma_device_handle_backend_data_callback(pDevice, pFrames, NULL, (ma_uint32)frameCount);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -42732,11 +42745,15 @@ static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const
|
|||||||
|
|
||||||
Unfortunately the audio data is not interleaved so we'll need to convert it before we give the data to miniaudio
|
Unfortunately the audio data is not interleaved so we'll need to convert it before we give the data to miniaudio
|
||||||
for further processing.
|
for further processing.
|
||||||
|
|
||||||
|
NOTE: Do not use the internal period size for this. A value of 128 is way too small for web and as such miniaudio
|
||||||
|
uses a larger (approximate) value for that. The frame count here must be exactly what Web Audio expects (128 as
|
||||||
|
of now).
|
||||||
*/
|
*/
|
||||||
if (deviceType == ma_device_type_playback) {
|
if (deviceType == ma_device_type_playback) {
|
||||||
frameCount = pDevice->playback.internalPeriodSizeInFrames;
|
frameCount = 128;
|
||||||
} else {
|
} else {
|
||||||
frameCount = pDevice->capture.internalPeriodSizeInFrames;
|
frameCount = 128;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ma_device_get_status(pDevice) != ma_device_status_started) {
|
if (ma_device_get_status(pDevice) != ma_device_status_started) {
|
||||||
@@ -42787,6 +42804,7 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a
|
|||||||
int channels = 0;
|
int channels = 0;
|
||||||
size_t intermediaryBufferSizeInFrames;
|
size_t intermediaryBufferSizeInFrames;
|
||||||
int sampleRate;
|
int sampleRate;
|
||||||
|
ma_uint32 chosenPeriodSizeInFrames;
|
||||||
|
|
||||||
if (success == EM_FALSE) {
|
if (success == EM_FALSE) {
|
||||||
pParameters->pDeviceStateWebAudio->initResult = MA_ERROR;
|
pParameters->pDeviceStateWebAudio->initResult = MA_ERROR;
|
||||||
@@ -42895,12 +42913,33 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a
|
|||||||
/* We need to update the descriptors so that they reflect the internal data format. Both capture and playback should be the same. */
|
/* We need to update the descriptors so that they reflect the internal data format. Both capture and playback should be the same. */
|
||||||
sampleRate = EM_ASM_INT({ return emscriptenGetAudioObject($0).sampleRate; }, audioContext);
|
sampleRate = EM_ASM_INT({ return emscriptenGetAudioObject($0).sampleRate; }, audioContext);
|
||||||
|
|
||||||
|
/*
|
||||||
|
We're now going to choose a period size. The quantum size reported by Web Audio is too small for us to use so
|
||||||
|
we'll need to use something bigger for our internal bufferring.
|
||||||
|
*/
|
||||||
|
chosenPeriodSizeInFrames = intermediaryBufferSizeInFrames;
|
||||||
|
if (pParameters->pDescriptorCapture != NULL) {
|
||||||
|
if (chosenPeriodSizeInFrames < pParameters->pDescriptorCapture->periodSizeInFrames) {
|
||||||
|
chosenPeriodSizeInFrames = pParameters->pDescriptorCapture->periodSizeInFrames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pParameters->pDescriptorPlayback != NULL) {
|
||||||
|
if (chosenPeriodSizeInFrames < pParameters->pDescriptorPlayback->periodSizeInFrames) {
|
||||||
|
chosenPeriodSizeInFrames = pParameters->pDescriptorPlayback->periodSizeInFrames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* I'd like to keep this a power of two. In my testing, 512 was too small and results in glitching so going with 1024. */
|
||||||
|
if (chosenPeriodSizeInFrames < 1024) {
|
||||||
|
chosenPeriodSizeInFrames = 1024;
|
||||||
|
}
|
||||||
|
|
||||||
if (pParameters->pDescriptorCapture != NULL) {
|
if (pParameters->pDescriptorCapture != NULL) {
|
||||||
pParameters->pDescriptorCapture->format = ma_format_f32;
|
pParameters->pDescriptorCapture->format = ma_format_f32;
|
||||||
pParameters->pDescriptorCapture->channels = (ma_uint32)channels;
|
pParameters->pDescriptorCapture->channels = (ma_uint32)channels;
|
||||||
pParameters->pDescriptorCapture->sampleRate = (ma_uint32)sampleRate;
|
pParameters->pDescriptorCapture->sampleRate = (ma_uint32)sampleRate;
|
||||||
ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pParameters->pDescriptorCapture->channelMap, ma_countof(pParameters->pDescriptorCapture->channelMap), pParameters->pDescriptorCapture->channels);
|
ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pParameters->pDescriptorCapture->channelMap, ma_countof(pParameters->pDescriptorCapture->channelMap), pParameters->pDescriptorCapture->channels);
|
||||||
pParameters->pDescriptorCapture->periodSizeInFrames = intermediaryBufferSizeInFrames;
|
pParameters->pDescriptorCapture->periodSizeInFrames = chosenPeriodSizeInFrames;
|
||||||
pParameters->pDescriptorCapture->periodCount = 1;
|
pParameters->pDescriptorCapture->periodCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42909,10 +42948,14 @@ static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T a
|
|||||||
pParameters->pDescriptorPlayback->channels = (ma_uint32)channels;
|
pParameters->pDescriptorPlayback->channels = (ma_uint32)channels;
|
||||||
pParameters->pDescriptorPlayback->sampleRate = (ma_uint32)sampleRate;
|
pParameters->pDescriptorPlayback->sampleRate = (ma_uint32)sampleRate;
|
||||||
ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pParameters->pDescriptorPlayback->channelMap, ma_countof(pParameters->pDescriptorPlayback->channelMap), pParameters->pDescriptorPlayback->channels);
|
ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pParameters->pDescriptorPlayback->channelMap, ma_countof(pParameters->pDescriptorPlayback->channelMap), pParameters->pDescriptorPlayback->channels);
|
||||||
pParameters->pDescriptorPlayback->periodSizeInFrames = intermediaryBufferSizeInFrames;
|
pParameters->pDescriptorPlayback->periodSizeInFrames = chosenPeriodSizeInFrames;
|
||||||
pParameters->pDescriptorPlayback->periodCount = 1;
|
pParameters->pDescriptorPlayback->periodCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ma_device_get_threading_mode(pParameters->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) {
|
||||||
|
ma_device_state_async_init(deviceType, pParameters->pDescriptorPlayback, pParameters->pDescriptorCapture, ma_device_get_allocation_callbacks(pParameters->pDevice), &pParameters->pDeviceStateWebAudio->async);
|
||||||
|
}
|
||||||
|
|
||||||
/* At this point we're done and we can return. */
|
/* At this point we're done and we can return. */
|
||||||
ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_DEBUG, "AudioWorklets: Created worklet node: %d", pParameters->pDeviceStateWebAudio->audioWorklet);
|
ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_DEBUG, "AudioWorklets: Created worklet node: %d", pParameters->pDeviceStateWebAudio->audioWorklet);
|
||||||
pParameters->pDeviceStateWebAudio->initResult = MA_SUCCESS;
|
pParameters->pDeviceStateWebAudio->initResult = MA_SUCCESS;
|
||||||
@@ -43221,6 +43264,10 @@ static ma_result ma_device_init__webaudio(ma_device* pDevice, const void* pDevic
|
|||||||
pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames;
|
pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames;
|
||||||
pDescriptorPlayback->periodCount = 1;
|
pDescriptorPlayback->periodCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) {
|
||||||
|
ma_device_state_async_init(deviceType, pDescriptorPlayback, pDescriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateWebAudio->async);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -43283,6 +43330,10 @@ static void ma_device_uninit__webaudio(ma_device* pDevice)
|
|||||||
window.miniaudio.untrack_device_by_index($0);
|
window.miniaudio.untrack_device_by_index($0);
|
||||||
}, pDeviceStateWebAudio->deviceIndex);
|
}, pDeviceStateWebAudio->deviceIndex);
|
||||||
|
|
||||||
|
if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) {
|
||||||
|
ma_device_state_async_uninit(&pDeviceStateWebAudio->async, ma_device_get_allocation_callbacks(pDevice));
|
||||||
|
}
|
||||||
|
|
||||||
ma_free(pDeviceStateWebAudio->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice));
|
ma_free(pDeviceStateWebAudio->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice));
|
||||||
ma_free(pDeviceStateWebAudio, ma_device_get_allocation_callbacks(pDevice));
|
ma_free(pDeviceStateWebAudio, ma_device_get_allocation_callbacks(pDevice));
|
||||||
}
|
}
|
||||||
@@ -43324,6 +43375,29 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice)
|
|||||||
return MA_SUCCESS;
|
return MA_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ma_result ma_device_step__webaudio(ma_device* pDevice, ma_blocking_mode blockingMode)
|
||||||
|
{
|
||||||
|
ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice);
|
||||||
|
|
||||||
|
if (!ma_device_is_started(pDevice)) {
|
||||||
|
return MA_DEVICE_NOT_STARTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
A bit of a special case for the Web Audio backend. We only do something here for single-threaded mode. In multi-threaded
|
||||||
|
mode we just do data processing straight from the Web Audio callback. In single-threaded mode we need to make sure data
|
||||||
|
processing is done from the step function, which is where this comes in.
|
||||||
|
*/
|
||||||
|
if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_MULTITHREADED) {
|
||||||
|
MA_ASSERT(!"[Web Audio] Incorrectly calling step() in multi-threaded mode.");
|
||||||
|
return MA_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* It doesn't feel like a good idea to block on web page... I'm going to force non-blocking mode here. */
|
||||||
|
(void)blockingMode;
|
||||||
|
return ma_device_state_async_step(&pDeviceStateWebAudio->async, pDevice, MA_BLOCKING_MODE_NON_BLOCKING, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
#pragma GCC diagnostic pop /* -Wvariadic-macro-arguments-omitted, -Wdollar-in-identifier-extension */
|
#pragma GCC diagnostic pop /* -Wvariadic-macro-arguments-omitted, -Wdollar-in-identifier-extension */
|
||||||
|
|
||||||
static ma_device_backend_vtable ma_gDeviceBackendVTable_WebAudio =
|
static ma_device_backend_vtable ma_gDeviceBackendVTable_WebAudio =
|
||||||
@@ -43336,7 +43410,7 @@ static ma_device_backend_vtable ma_gDeviceBackendVTable_WebAudio =
|
|||||||
ma_device_uninit__webaudio,
|
ma_device_uninit__webaudio,
|
||||||
ma_device_start__webaudio,
|
ma_device_start__webaudio,
|
||||||
ma_device_stop__webaudio,
|
ma_device_stop__webaudio,
|
||||||
NULL, /* onDeviceStep */
|
ma_device_step__webaudio,
|
||||||
NULL /* onDeviceWakeup */
|
NULL /* onDeviceWakeup */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,20 @@
|
|||||||
#define DEVICE_CHANNELS 2
|
#define DEVICE_CHANNELS 2
|
||||||
#define DEVICE_SAMPLE_RATE 48000
|
#define DEVICE_SAMPLE_RATE 48000
|
||||||
|
|
||||||
|
ma_threading_mode threadingMode = MA_THREADING_MODE_MULTITHREADED;
|
||||||
|
/*ma_threading_mode threadingMode = MA_THREADING_MODE_SINGLE_THREADED;*/
|
||||||
|
|
||||||
ma_bool32 isRunning = MA_FALSE;
|
ma_bool32 isRunning = MA_FALSE;
|
||||||
ma_device device;
|
ma_device device;
|
||||||
ma_waveform sineWave; /* For playback example. */
|
ma_waveform sineWave; /* For playback example. */
|
||||||
|
|
||||||
|
|
||||||
void main_loop__em()
|
void main_loop__em(void* pUserData)
|
||||||
{
|
{
|
||||||
|
ma_device* pDevice = (ma_device*)pUserData;
|
||||||
|
if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) {
|
||||||
|
ma_device_step(pDevice, MA_BLOCKING_MODE_NON_BLOCKING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -35,6 +42,7 @@ static void do_playback()
|
|||||||
ma_waveform_config sineWaveConfig;
|
ma_waveform_config sineWaveConfig;
|
||||||
|
|
||||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||||
|
deviceConfig.threadingMode = threadingMode;
|
||||||
deviceConfig.playback.format = DEVICE_FORMAT;
|
deviceConfig.playback.format = DEVICE_FORMAT;
|
||||||
deviceConfig.playback.channels = DEVICE_CHANNELS;
|
deviceConfig.playback.channels = DEVICE_CHANNELS;
|
||||||
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
||||||
@@ -72,6 +80,7 @@ static void do_duplex()
|
|||||||
ma_device_config deviceConfig;
|
ma_device_config deviceConfig;
|
||||||
|
|
||||||
deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
||||||
|
deviceConfig.threadingMode = threadingMode;
|
||||||
deviceConfig.capture.pDeviceID = NULL;
|
deviceConfig.capture.pDeviceID = NULL;
|
||||||
deviceConfig.capture.format = DEVICE_FORMAT;
|
deviceConfig.capture.format = DEVICE_FORMAT;
|
||||||
deviceConfig.capture.channels = 2;
|
deviceConfig.capture.channels = 2;
|
||||||
@@ -129,7 +138,7 @@ int main(int argc, char** argv)
|
|||||||
/* The device must be started in response to an input event. */
|
/* The device must be started in response to an input event. */
|
||||||
emscripten_set_mouseup_callback("canvas", &device, 0, on_canvas_click);
|
emscripten_set_mouseup_callback("canvas", &device, 0, on_canvas_click);
|
||||||
|
|
||||||
emscripten_set_main_loop(main_loop__em, 0, 1);
|
emscripten_set_main_loop_arg(main_loop__em, &device, 0, 1);
|
||||||
|
|
||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
|
|||||||
Reference in New Issue
Block a user