Fix getDevices API

* Takes device type, so you only get either playback or capture devices
* Doesn't store devices in state, reducing risk of dangling pointers
* Uses names instead of identifiers, since miniaudio identifiers become
  invalid if you call "getDevices" again
* Better diagnostics
* Split up lovrAudioInitDevice to be per-type, cleaner that way
* UseDevice now takes type and name, instead of just identifier
This commit is contained in:
Nevyn Bengtsson 2021-01-13 11:38:22 +01:00 committed by Bjorn
parent 9c7bc7c8db
commit 138853ef01
3 changed files with 110 additions and 79 deletions

View File

@ -44,7 +44,7 @@ static int l_lovrAudioSetVolume(lua_State* L) {
static int l_lovrAudioNewSource(lua_State* L) {
Source* source = NULL;
SoundData* soundData = luax_totype(L, 1, SoundData);
bool spatial = true;
if (lua_istable(L, 2)) {
lua_getfield(L, 2, "spatial");
@ -53,7 +53,7 @@ static int l_lovrAudioNewSource(lua_State* L) {
}
lua_pop(L, 1);
}
if (soundData) {
source = lovrSourceCreate(soundData, spatial);
@ -87,13 +87,13 @@ static int l_lovrAudioGetCaptureStream(lua_State* L) {
}
static int l_lovrAudioGetDevices(lua_State *L) {
AudioDevice *devices;
size_t count;
lovrAudioGetDevices(&devices, &count);
AudioType type = luax_checkenum(L, 1, AudioType, "playback");
AudioDeviceArr *devices = lovrAudioGetDevices(type);
lua_newtable(L);
int listOfDevicesIdx = lua_gettop(L);
for(int i = 0; i < count; i++) {
AudioDevice *device = &devices[i];
for(int i = 0; i < devices->length; i++) {
AudioDevice *device = &devices->data[i];
lua_pushinteger(L, i+1); // key in listOfDevicesIdx, for the bottom settable
lua_newtable(L);
luax_pushenum(L, AudioType, device->type);
@ -102,24 +102,19 @@ static int l_lovrAudioGetDevices(lua_State *L) {
lua_setfield(L, -2, "name");
lua_pushboolean(L, device->isDefault);
lua_setfield(L, -2, "isDefault");
lua_pushlightuserdata(L, device->identifier);
lua_setfield(L, -2, "identifier");
lua_pushinteger(L, device->minChannels);
lua_setfield(L, -2, "minChannels");
lua_pushinteger(L, device->maxChannels);
lua_setfield(L, -2, "maxChannels");
lua_settable(L, listOfDevicesIdx);
}
lovrAudioFreeDevices(devices);
return 1;
}
static int l_lovrUseDevice(lua_State *L) {
AudioDeviceIdentifier ident = lua_touserdata(L, 1);
int sampleRate = lua_tointeger(L, 2);
SampleFormat format = luax_checkenum(L, 3, SampleFormat, "invalid");
lovrAudioUseDevice(ident, sampleRate, format);
AudioType type = luax_checkenum(L, 1, AudioType, "playback");
const char *name = lua_tostring(L, 2);
int sampleRate = lua_tointeger(L, 3);
SampleFormat format = luax_checkenum(L, 4, SampleFormat, "invalid");
lovrAudioUseDevice(type, name, sampleRate, format);
return 0;
}
@ -141,9 +136,9 @@ int luaopen_lovr_audio(lua_State* L) {
lua_newtable(L);
luax_register(L, lovrAudio);
luax_registertype(L, Source);
AudioConfig config[2] = {
{ .enable = true, .start = true, .format = SAMPLE_F32, .sampleRate = 44100 },
{ .enable = false, .start = false, .format = SAMPLE_F32, .sampleRate = 44100 }
AudioConfig config[2] = {
{ .enable = true, .start = true, .format = SAMPLE_F32, .sampleRate = 44100 },
{ .enable = false, .start = false, .format = SAMPLE_F32, .sampleRate = 44100 }
};
if (lovrAudioInit(config)) {
luax_atexit(L, lovrAudioDestroy);

View File

@ -45,8 +45,6 @@ static struct {
SoundData *captureStream;
arr_t(ma_data_converter*) converters;
Spatializer* spatializer;
AudioDevice *deviceInfos;
} state;
// Device callbacks
@ -179,27 +177,59 @@ void lovrAudioDestroy() {
free(state.converters.data[i]);
}
arr_free(&state.converters);
free(state.config[0].deviceName);
free(state.config[1].deviceName);
memset(&state, 0, sizeof(state));
}
bool lovrAudioInitDevice(AudioType type) {
ma_device_type deviceType = (type == AUDIO_PLAYBACK) ? ma_device_type_playback : ma_device_type_capture;
ma_device_config config = ma_device_config_init(deviceType);
config.sampleRate = state.config[type].sampleRate;
lovrAssert(state.config[AUDIO_PLAYBACK].format == OUTPUT_FORMAT, "Only f32 playback format currently supported");
config.playback.format = miniAudioFormatFromLovr[state.config[AUDIO_PLAYBACK].format];
config.capture.format = miniAudioFormatFromLovr[state.config[AUDIO_CAPTURE].format];
config.playback.pDeviceID = state.config[AUDIO_PLAYBACK].device;
config.capture.pDeviceID = state.config[AUDIO_CAPTURE].device;
config.playback.channels = OUTPUT_CHANNELS;
config.capture.channels = CAPTURE_CHANNELS;
config.dataCallback = callbacks[type];
ma_device_info *playbackDevices;
ma_uint32 playbackDeviceCount;
ma_device_info *captureDevices;
ma_uint32 captureDeviceCount;
ma_result gettingStatus = ma_context_get_devices(&state.context, &playbackDevices, &playbackDeviceCount, &captureDevices, &captureDeviceCount);
lovrAssert(gettingStatus == MA_SUCCESS, "Failed to enumerate audio devices during initialization: %s (%d)", ma_result_description(gettingStatus), gettingStatus);
ma_device_config config;
if (type == AUDIO_PLAYBACK) {
ma_device_type deviceType = ma_device_type_playback;
config = ma_device_config_init(deviceType);
lovrAssert(state.config[AUDIO_PLAYBACK].format == OUTPUT_FORMAT, "Only f32 playback format currently supported");
config.playback.format = miniAudioFormatFromLovr[state.config[AUDIO_PLAYBACK].format];
for(int i = 0; i < playbackDeviceCount && state.config[AUDIO_PLAYBACK].deviceName; i++) {
if (strcmp(playbackDevices[i].name, state.config[AUDIO_PLAYBACK].deviceName) == 0) {
config.playback.pDeviceID = &playbackDevices[i].id;
}
}
if (state.config[AUDIO_PLAYBACK].deviceName && config.playback.pDeviceID == NULL) {
lovrLog(LOG_WARN, "No audio playback device called '%s'; falling back to default.", state.config[AUDIO_PLAYBACK].deviceName);
}
config.playback.channels = OUTPUT_CHANNELS;
} else {
ma_device_type deviceType = ma_device_type_capture;
config = ma_device_config_init(deviceType);
config.capture.format = miniAudioFormatFromLovr[state.config[AUDIO_CAPTURE].format];
for(int i = 0; i < captureDeviceCount && state.config[AUDIO_CAPTURE].deviceName; i++) {
if (strcmp(captureDevices[i].name, state.config[AUDIO_CAPTURE].deviceName) == 0) {
config.capture.pDeviceID = &playbackDevices[i].id;
}
}
if (state.config[AUDIO_CAPTURE].deviceName && config.capture.pDeviceID == NULL) {
lovrLog(LOG_WARN, "No audio capture device called '%s'; falling back to default.", state.config[AUDIO_CAPTURE].deviceName);
}
config.capture.channels = CAPTURE_CHANNELS;
}
config.performanceProfile = ma_performance_profile_low_latency;
config.dataCallback = callbacks[type];
config.sampleRate = state.config[type].sampleRate;
int err = ma_device_init(&state.context, &config, &state.devices[type]);
ma_result err = ma_device_init(&state.context, &config, &state.devices[type]);
if (err != MA_SUCCESS) {
lovrLog(LOG_WARN, "audio", "Failed to enable audio device %d: %d\n", type, err);
lovrLog(LOG_WARN, "audio", "Failed to enable %s audio device: %s (%d)\n", type == AUDIO_PLAYBACK ? "playback" : "capture", ma_result_description(err), err);
return false;
}
@ -295,7 +325,7 @@ static void _lovrSourceAssignConverter(Source *source) {
ma_data_converter *converter = malloc(sizeof(ma_data_converter));
ma_result converterStatus = ma_data_converter_init(&config, converter);
lovrAssert(converterStatus == MA_SUCCESS, "Problem creating Source data converter #%d: %d", state.converters.length, converterStatus);
arr_expand(&state.converters, 1);
state.converters.data[state.converters.length++] = source->converter = converter;
}
@ -306,7 +336,7 @@ Source* lovrSourceCreate(SoundData* sound, bool spatial) {
source->sound = sound;
lovrRetain(source->sound);
source->volume = 1.f;
source->spatial = spatial;
mat4_identity(source->transform);
_lovrSourceAssignConverter(source);
@ -405,43 +435,45 @@ struct SoundData* lovrAudioGetCaptureStream()
// Devices
void lovrAudioGetDevices(AudioDevice **outDevices, size_t *outCount) {
if(state.deviceInfos)
free(state.deviceInfos);
ma_result gettingStatus = ma_context_get_devices(&state.context, NULL, NULL, NULL, NULL);
lovrAssert(gettingStatus == MA_SUCCESS, "Failed to enumerate audio devices: %d", gettingStatus);
*outCount = state.context.playbackDeviceInfoCount + state.context.captureDeviceInfoCount;
*outDevices = state.deviceInfos = calloc(*outCount, sizeof(AudioDevice));
for(int i = 0; i < *outCount; i++) {
ma_device_info *mainfo = &state.context.pDeviceInfos[i];
AudioDevice *lovrInfo = &state.deviceInfos[i];
lovrInfo->name = mainfo->name;
lovrInfo->type = i < state.context.playbackDeviceInfoCount ? AUDIO_PLAYBACK : AUDIO_CAPTURE;
lovrInfo->isDefault = mainfo->_private.isDefault; // remove _private after bumping miniaudio
lovrInfo->identifier = &mainfo->id;
lovrInfo->minChannels = mainfo->minChannels;
lovrInfo->maxChannels = mainfo->maxChannels;
AudioDeviceArr* lovrAudioGetDevices(AudioType type) {
ma_device_info *playbackDevices;
ma_uint32 playbackDeviceCount;
ma_device_info *captureDevices;
ma_uint32 captureDeviceCount;
ma_result gettingStatus = ma_context_get_devices(&state.context, &playbackDevices, &playbackDeviceCount, &captureDevices, &captureDeviceCount);
lovrAssert(gettingStatus == MA_SUCCESS, "Failed to enumerate audio devices: %s (%d)", ma_result_description(gettingStatus), gettingStatus);
ma_uint32 count = type == AUDIO_PLAYBACK ? playbackDeviceCount : captureDeviceCount;
ma_device_info *madevices = type == AUDIO_PLAYBACK ? playbackDevices : captureDevices;
AudioDeviceArr *devices = calloc(1, sizeof(AudioDeviceArr));
devices->capacity = devices->length = count;
devices->data = calloc(count, sizeof(AudioDevice));
for(int i = 0; i < count; i++) {
ma_device_info *mainfo = &madevices[i];
AudioDevice *lovrInfo = &devices->data[i];
lovrInfo->name = strdup(mainfo->name);
lovrInfo->type = type;
lovrInfo->isDefault = mainfo->isDefault;
}
return devices;
}
void lovrAudioUseDevice(AudioDeviceIdentifier identifier, int sampleRate, SampleFormat format) {
int deviceCount = state.context.playbackDeviceInfoCount + state.context.captureDeviceInfoCount;
for(int i = 0; i < deviceCount; i++) {
if (identifier == &state.context.pDeviceInfos[i].id) {
AudioType type = i < state.context.playbackDeviceInfoCount ? AUDIO_PLAYBACK : AUDIO_CAPTURE;
state.config[type].device = identifier;
if (sampleRate) state.config[type].sampleRate = sampleRate;
if (format != SAMPLE_INVALID) state.config[type].format = format;
lovrLog(LOG_INFO, "audio", "Switching to %s device %s (%p)", type?"capture":"playback", state.context.pDeviceInfos[i].name, identifier);
ma_device_uninit(&state.devices[type]);
if(state.config[type].enable)
lovrAudioInitDevice(type);
if(state.config[type].start)
ma_device_start(&state.devices[type]);
return;
}
void lovrAudioFreeDevices(AudioDeviceArr *devices) {
for(int i = 0; i < devices->length; i++) {
free((void*)devices->data[i].name);
}
lovrAssert(false, "Couldn't find the given identifier");
}
arr_free(devices);
}
void lovrAudioUseDevice(AudioType type, const char *deviceName, int sampleRate, SampleFormat format) {
free(state.config[type].deviceName);
state.config[type].deviceName = strdup(deviceName);
if (sampleRate) state.config[type].sampleRate = sampleRate;
if (format != SAMPLE_INVALID) state.config[type].format = format;
ma_device_uninit(&state.devices[type]);
if(state.config[type].enable)
lovrAudioInitDevice(type);
if(state.config[type].start)
ma_device_start(&state.devices[type]);
}

View File

@ -2,6 +2,7 @@
#include <stdint.h>
#include <stddef.h>
#include "modules/data/soundData.h"
#include "core/arr.h"
#pragma once
@ -26,7 +27,7 @@ typedef void* AudioDeviceIdentifier;
typedef struct {
bool enable;
bool start;
AudioDeviceIdentifier device;
char *deviceName;
int sampleRate;
SampleFormat format;
} AudioConfig;
@ -35,9 +36,8 @@ typedef struct {
AudioType type;
const char *name;
bool isDefault;
AudioDeviceIdentifier identifier;
int minChannels, maxChannels;
} AudioDevice;
typedef arr_t(AudioDevice) AudioDeviceArr;
bool lovrAudioInit(AudioConfig config[2]);
void lovrAudioDestroy(void);
@ -67,5 +67,9 @@ struct SoundData* lovrSourceGetSoundData(Source* source);
struct SoundData* lovrAudioGetCaptureStream();
void lovrAudioGetDevices(AudioDevice **outDevices, size_t *outCount);
void lovrAudioUseDevice(AudioDeviceIdentifier identifier, int sampleRate, SampleFormat format);
// Return a list of devices for the given type. Must be freed with lovrAudioFreeDevices.
AudioDeviceArr* lovrAudioGetDevices(AudioType type);
// free a list of devices returned from above call
void lovrAudioFreeDevices(AudioDeviceArr *devices);
void lovrAudioUseDevice(AudioType type, const char *deviceName, int sampleRate, SampleFormat format);