mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
ba963e46b5
This uses `getDisplayMedia()`. Support for this is extremely browser and system specific so I'm not advertising support for this documentation. Public issue https://github.com/mackron/miniaudio/issues/967
309 lines
10 KiB
C
309 lines
10 KiB
C
#define MA_DEBUG_OUTPUT
|
|
#define MA_NO_DECODING
|
|
#define MA_NO_ENCODING
|
|
#include "../../miniaudio.c"
|
|
#include "../../extras/backends/sdl2/miniaudio_sdl2.c"
|
|
|
|
#include <stdio.h>
|
|
|
|
/*#define DEVICE_BACKEND ma_device_backend_sdl2*/
|
|
#define DEVICE_BACKEND ma_device_backend_webaudio
|
|
#define DEVICE_FORMAT ma_format_f32
|
|
#define DEVICE_CHANNELS 2
|
|
#define DEVICE_SAMPLE_RATE 48000
|
|
|
|
ma_threading_mode threadingMode = MA_THREADING_MODE_MULTI_THREADED;
|
|
/*ma_threading_mode threadingMode = MA_THREADING_MODE_SINGLE_THREADED;*/
|
|
|
|
ma_bool32 isRunning = MA_FALSE;
|
|
ma_device device;
|
|
ma_waveform sineWave; /* For playback example. */
|
|
|
|
|
|
void main_loop__em(void* pUserData)
|
|
{
|
|
if (threadingMode == MA_THREADING_MODE_SINGLE_THREADED) {
|
|
ma_device_step((ma_device*)pUserData, MA_BLOCKING_MODE_NON_BLOCKING);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void data_callback_playback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
|
{
|
|
MA_ASSERT(pDevice->playback.channels == DEVICE_CHANNELS);
|
|
|
|
ma_waveform_read_pcm_frames(&sineWave, pOutput, frameCount, NULL);
|
|
|
|
(void)pInput; /* Unused. */
|
|
}
|
|
|
|
static void do_playback()
|
|
{
|
|
ma_result result;
|
|
ma_device_config deviceConfig;
|
|
ma_waveform_config sineWaveConfig;
|
|
ma_device_backend_config backend;
|
|
|
|
backend = ma_device_backend_config_init(DEVICE_BACKEND, NULL);
|
|
|
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
|
deviceConfig.threadingMode = threadingMode;
|
|
deviceConfig.playback.format = DEVICE_FORMAT;
|
|
deviceConfig.playback.channels = DEVICE_CHANNELS;
|
|
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
|
deviceConfig.dataCallback = data_callback_playback;
|
|
deviceConfig.pUserData = &sineWave;
|
|
deviceConfig.pBackendConfigs = &backend;
|
|
deviceConfig.backendConfigCount = 1;
|
|
result = ma_device_init_ex(&backend, 1, NULL, &deviceConfig, &device);
|
|
if (result != MA_SUCCESS) {
|
|
printf("Failed to open playback device. %s.\n", ma_result_description(result));
|
|
return;
|
|
}
|
|
|
|
sineWaveConfig = ma_waveform_config_init(device.playback.format, device.playback.channels, device.sampleRate, ma_waveform_type_sine, 0.2, 220);
|
|
ma_waveform_init(&sineWaveConfig, &sineWave);
|
|
|
|
result = ma_device_start(&device);
|
|
if (result != MA_SUCCESS) {
|
|
printf("Failed to start device. %s.\n", ma_result_description(result));
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void data_callback_duplex(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
|
{
|
|
MA_ASSERT(pDevice->capture.format == pDevice->playback.format);
|
|
MA_ASSERT(pDevice->capture.channels == pDevice->playback.channels);
|
|
|
|
/* In this example the format and channel count are the same for both input and output which means we can just memcpy(). */
|
|
MA_COPY_MEMORY(pOutput, pInput, frameCount * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels));
|
|
}
|
|
|
|
static void do_duplex()
|
|
{
|
|
ma_result result;
|
|
ma_device_config deviceConfig;
|
|
ma_device_backend_config backend;
|
|
|
|
backend = ma_device_backend_config_init(DEVICE_BACKEND, NULL);
|
|
|
|
deviceConfig = ma_device_config_init(ma_device_type_duplex);
|
|
deviceConfig.threadingMode = threadingMode;
|
|
deviceConfig.capture.pDeviceID = NULL;
|
|
deviceConfig.capture.format = DEVICE_FORMAT;
|
|
deviceConfig.capture.channels = 2;
|
|
deviceConfig.playback.pDeviceID = NULL;
|
|
deviceConfig.playback.format = DEVICE_FORMAT;
|
|
deviceConfig.playback.channels = 2;
|
|
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
|
deviceConfig.dataCallback = data_callback_duplex;
|
|
deviceConfig.pBackendConfigs = &backend;
|
|
deviceConfig.backendConfigCount = 1;
|
|
result = ma_device_init_ex(&backend, 1, NULL, &deviceConfig, &device);
|
|
if (result != MA_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
if (ma_device_start(&device) != MA_SUCCESS) {
|
|
printf("Failed to start device.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
ma_device loopbackPlaybackDevice;
|
|
ma_pcm_rb loopbackRB;
|
|
|
|
void data_callback_loopback_capture(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
|
{
|
|
ma_uint32 framesToWrite;
|
|
void* pBuffer;
|
|
|
|
(void)pDevice;
|
|
(void)pOutput;
|
|
|
|
/* Write to the ring buffer. */
|
|
framesToWrite = frameCount;
|
|
ma_pcm_rb_acquire_write(&loopbackRB, &framesToWrite, &pBuffer);
|
|
MA_COPY_MEMORY(pBuffer, pInput, framesToWrite);
|
|
ma_pcm_rb_commit_write(&loopbackRB, framesToWrite);
|
|
}
|
|
|
|
void data_callback_loopback_playback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
|
{
|
|
ma_uint32 framesToRead;
|
|
void* pBuffer;
|
|
|
|
(void)pDevice;
|
|
(void)pInput;
|
|
|
|
/* Read from the ring buffer. */
|
|
framesToRead = frameCount;
|
|
ma_pcm_rb_acquire_read(&loopbackRB, &framesToRead, &pBuffer);
|
|
MA_COPY_MEMORY(pOutput, pBuffer, framesToRead);
|
|
ma_pcm_rb_commit_read(&loopbackRB, framesToRead);
|
|
}
|
|
|
|
static void do_loopback()
|
|
{
|
|
ma_result result;
|
|
ma_device_config deviceConfig;
|
|
ma_device_backend_config backend;
|
|
|
|
backend = ma_device_backend_config_init(DEVICE_BACKEND, NULL);
|
|
|
|
deviceConfig = ma_device_config_init(ma_device_type_loopback);
|
|
deviceConfig.threadingMode = threadingMode;
|
|
deviceConfig.capture.format = DEVICE_FORMAT;
|
|
deviceConfig.capture.channels = 2;
|
|
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
|
deviceConfig.dataCallback = data_callback_loopback_capture;
|
|
deviceConfig.pBackendConfigs = &backend;
|
|
deviceConfig.backendConfigCount = 1;
|
|
result = ma_device_init_ex(&backend, 1, NULL, &deviceConfig, &device);
|
|
if (result != MA_SUCCESS) {
|
|
printf("Failed to initialize loopback device.\n");
|
|
return;
|
|
}
|
|
|
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
|
deviceConfig.threadingMode = threadingMode;
|
|
deviceConfig.playback.format = DEVICE_FORMAT;
|
|
deviceConfig.playback.channels = 2;
|
|
deviceConfig.sampleRate = DEVICE_SAMPLE_RATE;
|
|
deviceConfig.dataCallback = data_callback_loopback_playback;
|
|
deviceConfig.pBackendConfigs = &backend;
|
|
deviceConfig.backendConfigCount = 1;
|
|
result = ma_device_init_ex(&backend, 1, NULL, &deviceConfig, &loopbackPlaybackDevice);
|
|
if (result != MA_SUCCESS) {
|
|
printf("Failed to initialize loopback playback device.\n");
|
|
return;
|
|
}
|
|
|
|
|
|
/* We need a ring buffer. */
|
|
printf("device.capture.internalPeriodSizeInFrames = %u\n", device.capture.internalPeriodSizeInFrames);
|
|
ma_pcm_rb_init(DEVICE_FORMAT, device.capture.channels, device.capture.internalPeriodSizeInFrames * 100, NULL, NULL, &loopbackRB);
|
|
|
|
|
|
if (ma_device_start(&loopbackPlaybackDevice) != MA_SUCCESS) {
|
|
printf("Failed to start loopback playback device.");
|
|
return;
|
|
}
|
|
|
|
if (ma_device_start(&device) != MA_SUCCESS) {
|
|
printf("Failed to start device.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static EM_BOOL on_canvas_click(int eventType, const EmscriptenMouseEvent* pMouseEvent, void* pUserData)
|
|
{
|
|
if (isRunning == MA_FALSE) {
|
|
/* */ if (pMouseEvent->button == 0) { /* Left click. */
|
|
do_playback();
|
|
} else if (pMouseEvent->button == 2) { /* Right click. */
|
|
do_duplex();
|
|
} else if (pMouseEvent->button == 1) { /* Middle click. */
|
|
do_loopback();
|
|
}
|
|
|
|
isRunning = MA_TRUE;
|
|
} else {
|
|
if (ma_device_get_status(&device) == ma_device_status_started) {
|
|
ma_device_stop(&device);
|
|
} else {
|
|
ma_device_start(&device);
|
|
}
|
|
}
|
|
|
|
(void)eventType;
|
|
(void)pUserData;
|
|
|
|
return EM_FALSE;
|
|
}
|
|
|
|
|
|
static void print_device_info(const ma_device_info* pDeviceInfo)
|
|
{
|
|
ma_uint32 iFormat;
|
|
|
|
printf("%s\n", pDeviceInfo->name);
|
|
printf(" Default: %s\n", (pDeviceInfo->isDefault) ? "Yes" : "No");
|
|
printf(" Format Count: %d\n", pDeviceInfo->nativeDataFormatCount);
|
|
for (iFormat = 0; iFormat < pDeviceInfo->nativeDataFormatCount; ++iFormat) {
|
|
printf(" %s, [%d, %d], [%d, %d]\n",
|
|
ma_get_format_name(pDeviceInfo->nativeDataFormats[iFormat].format),
|
|
pDeviceInfo->nativeDataFormats[iFormat].minChannels, pDeviceInfo->nativeDataFormats[iFormat].maxChannels,
|
|
pDeviceInfo->nativeDataFormats[iFormat].minSampleRate, pDeviceInfo->nativeDataFormats[iFormat].maxSampleRate);
|
|
}
|
|
}
|
|
|
|
static void enumerate_devices()
|
|
{
|
|
ma_result result;
|
|
ma_context context;
|
|
ma_device_backend_config backend;
|
|
ma_device_info* pPlaybackDevices;
|
|
ma_device_info* pCaptureDevices;
|
|
ma_uint32 playbackDeviceCount;
|
|
ma_uint32 captureDeviceCount;
|
|
ma_uint32 iDevice;
|
|
|
|
backend = ma_device_backend_config_init(DEVICE_BACKEND, NULL);
|
|
|
|
result = ma_context_init(&backend, 1, NULL, &context);
|
|
if (result != MA_SUCCESS) {
|
|
printf("Failed to create context for device enumeration.\n");
|
|
return;
|
|
}
|
|
|
|
result = ma_context_get_devices(&context, &pPlaybackDevices, &playbackDeviceCount, &pCaptureDevices, &captureDeviceCount);
|
|
if (result != MA_SUCCESS) {
|
|
printf("Failed to enumerate devices.\n");
|
|
ma_context_uninit(&context);
|
|
return;
|
|
}
|
|
|
|
printf("Playback Devices\n");
|
|
printf("----------------\n");
|
|
for (iDevice = 0; iDevice < playbackDeviceCount; iDevice += 1) {
|
|
printf("%d: ", iDevice);
|
|
print_device_info(&pPlaybackDevices[iDevice]);
|
|
}
|
|
printf("\n");
|
|
|
|
printf("Capture Devices\n");
|
|
printf("---------------\n");
|
|
for (iDevice = 0; iDevice < captureDeviceCount; iDevice += 1) {
|
|
printf("%d: ", iDevice);
|
|
print_device_info(&pCaptureDevices[iDevice]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
enumerate_devices();
|
|
|
|
printf("Click inside canvas to start playing:\n");
|
|
printf(" Left click for playback\n");
|
|
printf(" Right click for duplex\n");
|
|
printf(" Middle click for loopback\n");
|
|
|
|
/* The device must be started in response to an input event. */
|
|
emscripten_set_mouseup_callback("canvas", &device, 0, on_canvas_click);
|
|
|
|
emscripten_set_main_loop_arg(main_loop__em, &device, 0, 1);
|
|
|
|
(void)argc;
|
|
(void)argv;
|
|
return 0;
|
|
}
|