From 5c9235dd4ff8fc009b4deea30b907c9d928ac3a8 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:06:12 +0100 Subject: [PATCH 01/16] update miniaudio to 22b9d84622adf6f00350eff3845784c8c4c79f6e now with psvita support --- src/audio/miniaudio/ma_audio_system.c | 2 +- vendor/miniaudio/miniaudio.h | 51738 ++++++++++++++---------- 2 files changed, 30683 insertions(+), 21057 deletions(-) diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 3ec79ec7c..2cc4c09a1 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -271,7 +271,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior } slot->ownsDecoder = true; - result = ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, &ma->listenerGroups[0], &slot->maSound); + result = ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, &ma->listenerGroups[0], nullptr, &slot->maSound); if (result != MA_SUCCESS) { fprintf(stderr, "Audio: Failed to init sound from decoder for '%s' (error %d)\n", sound->name, result); ma_decoder_uninit(&slot->decoder); diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 5055a95b6..1609e75de 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.11.25 - 2026-03-04 +miniaudio - v0.11.26 - TBD David Reid - mackron@gmail.com @@ -557,13 +557,15 @@ To run locally, you'll need to use emrun: +----------------------------------+--------------------------------------------------------------------+ | MA_NO_WINMM | Disables the WinMM backend. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_ALSA | Disables the ALSA backend. | + | MA_NO_COREAUDIO | Disables the Core Audio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_PIPWIRE | Disables the PipeWire backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_PULSEAUDIO | Disables the PulseAudio backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_JACK | Disables the JACK backend. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_COREAUDIO | Disables the Core Audio backend. | + | MA_NO_ALSA | Disables the ALSA backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_SNDIO | Disables the sndio backend. | +----------------------------------+--------------------------------------------------------------------+ @@ -577,6 +579,8 @@ To run locally, you'll need to use emrun: +----------------------------------+--------------------------------------------------------------------+ | MA_NO_WEBAUDIO | Disables the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_DREAMCAST | Disables the Dreamcast backend. | + +----------------------------------+--------------------------------------------------------------------+ | MA_NO_CUSTOM | Disables support for custom backends. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NULL | Disables the null backend. | @@ -593,8 +597,8 @@ To run locally, you'll need to use emrun: | MA_ENABLE_WINMM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the WinMM backend. | +----------------------------------+--------------------------------------------------------------------+ - | MA_ENABLE_ALSA | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | - | | enable the ALSA backend. | + | MA_ENABLE_PIPEWIRE | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the PipeWire backend. | +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_PULSEAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the PulseAudio backend. | @@ -602,6 +606,9 @@ To run locally, you'll need to use emrun: | MA_ENABLE_JACK | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the JACK backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_ALSA | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the ALSA backend. | + +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_COREAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the Core Audio backend. | +----------------------------------+--------------------------------------------------------------------+ @@ -623,6 +630,9 @@ To run locally, you'll need to use emrun: | MA_ENABLE_WEBAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable the Web Audio backend. | +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_DREAMCAST | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the Dreamcast backend. | + +----------------------------------+--------------------------------------------------------------------+ | MA_ENABLE_CUSTOM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | | | enable custom backends. | +----------------------------------+--------------------------------------------------------------------+ @@ -664,13 +674,7 @@ To run locally, you'll need to use emrun: +----------------------------------+--------------------------------------------------------------------+ | MA_NO_THREADING | Disables the `ma_thread`, `ma_mutex`, `ma_semaphore` and | | | `ma_event` APIs. This option is useful if you only need to use | - | | miniaudio for data conversion, decoding and/or encoding. Some | - | | families of APIs require threading which means the following | - | | options must also be set: | - | | | - | | ``` | - | | MA_NO_DEVICE_IO | - | | ``` | + | | miniaudio for data conversion, decoding and/or encoding. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_GENERATION | Disables generation APIs such a `ma_waveform` and `ma_noise`. | +----------------------------------+--------------------------------------------------------------------+ @@ -794,8 +798,8 @@ If you don't need the number of frames that were successfully read you can pass the end of the file has been reached. `MA_AT_END` will be returned only when the number of frames read is 0. -When calling any data source function, with the exception of `ma_data_source_init()` and -`ma_data_source_uninit()`, you can pass in any object that implements a data source. For example, +When calling any data source function, with the exception of `ma_data_source_base_init()` and +`ma_data_source_base_uninit()`, you can pass in any object that implements a data source. For example, you could plug in a decoder like so: ```c @@ -938,64 +942,280 @@ Instead, initialize multiple data sources for each instance. 4.1. Custom Data Sources ------------------------ -You can implement a custom data source by implementing the functions in `ma_data_source_vtable`. -Your custom object must have `ma_data_source_base` as it's first member: +When implementing a custom data source it will typically have its own struct. This struct must have +`ma_data_source_base` as it's first member: ```c - struct my_data_source + struct custom_data_source { ma_data_source_base base; ... }; ``` -In your initialization routine, you need to call `ma_data_source_init()` in order to set up the -base object (`ma_data_source_base`): +You can implement a custom data source by implementing the functions in `ma_data_source_vtable`: ```c - static ma_result my_data_source_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) + // onSizeof + static size_t custom_data_source_sizeof(void) { - // Read data here. Output in the same format returned by my_data_source_get_data_format(). + // Return the size of your struct. + return sizeof(my_custom_data_source); } - static ma_result my_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) + // onUninit + static void custom_data_source_uninit(ma_data_source* pDataSource) { - // Seek to a specific PCM frame here. Return MA_NOT_IMPLEMENTED if seeking is not supported. + // Uninitialize the data source here. Do not free the data source pointer here. + custom_data_source_uninit((custom_data_source*)pDataSource); } - static ma_result my_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) + // onCopy + static ma_result custom_data_source_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) { - // Return the format of the data here. + // Make a copy of your data source. The new data source must be completely independent of + // the original data source. When the original is uninitialized, the new one must remain + // usable. pNewDataSource will already be pre-allocated using the size returned by the + // `onSizeof`. + // + // You can either leave `onCopy` set to null in the vtable, or you can return + // `MA_NOT_IMPLEMENTED` if your data source does cannot be copied. } - static ma_result my_data_source_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) + // onRead + static ma_result custom_data_source_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - // Retrieve the current position of the cursor here. Return MA_NOT_IMPLEMENTED and set *pCursor to 0 if there is no notion of a cursor. + // Read data here. The data format must be in the same format as returned by the + // `MA_DATA_SOURCE_GET_DATA_FORMAT` property (see `onProp` below). + // + // On output, set `pFramesRead` to the number of frames actually read. If you set this to + // 0, miniaudio will treat it as if it's reached the end. + // + // Return `MA_AT_END` when the end has been reached. } - static ma_result my_data_source_get_length(ma_data_source* pDataSource, ma_uint64* pLength) + // onSeek + static ma_result custom_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) { - // Retrieve the length in PCM frames here. Return MA_NOT_IMPLEMENTED and set *pLength to 0 if there is no notion of a length or if the length is unknown. + // Seek to a specific PCM frame here. Return `MA_NOT_IMPLEMENTED` if seeking is not + // supported, or set the `onSeek` memeber of the vtable to NULL. + } + + // onProp + static ma_result custom_data_source_prop(ma_data_source* pDataSource, int prop, void* pData) + { + // This function is used to retrieve or set properties of the data source. It's basically + // the same kind of idea as `ioctl()`. `prop` will be a property type which you + // discriminate against to determine what to do. The meaning of `pData` member depends on + // the property. + // + // You do not need to implement every property. Indeed, it's extremely rare that you need + // or want to. For any property that is not implemented just return `MA_NOT_IMPLEMENTED`, + // normally from a `default` in a switch. Return `MA_SUCCESS` for any property that is + // handled. If an error occurs during the handling of the property just return some other + // result code. + // + // See the switch below for a description and example of each property. This implements + // every property for documentation purposes, but a real data source would rarely need + // to implement everything. + custom_data_source* pCustomDataSource = (custom_data_source*)pDataSource; + + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + // Mandatory. + // + // This should fill a `ma_data_source_data_format` struct with the format, channel + // count and sample rate of the data source. + // + // pData: ma_data_source_data_format* + + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; + pDataFormat->format = pCustomDataSource->format; // <-- Must be set. + pDataFormat->channels = pCustomDataSource->channels; // <-- Must be set. + pDataFormat->sampleRate = pCustomDataSource->sampleRate; // <-- This can be 0 if your data source does not have the notion of a sample rate. + + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + // Optional. + // + // This should output the channel map used by the data source. This is optional and + // can be ignored if the data source doesn't really care about a channel map. If + // this is not implemented it will use the default channel map. + // + // pData: ma_channel[MA_MAX_CHANNELS] + + ma_channel_map_init_standard(ma_standard_channel_map_default, (ma_channel*)pData, MA_MAX_CHANNELS, pCustomDataSource->channels); + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + // Optional. + // + // Return the current cursor position in PCM frames. This is optional and can be + // gnored if the data source has no notion of a cursor. + // + // If the data source implements MA_DATA_SOURCE_SET_RANGE, the returned cursor + // position needs to be relative to the start of the range. + // + // pData: ma_uint64* + + *((ma_uint64*)pData) = pCustomDataSource->cursor; + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_LENGTH: + { + // Optional. + // + // Return the length in PCM frames. This is optional and can be ignored if the data + // source has no notion of a length. + // + // This should always return the full un-clamped length (the range should be ignored + // if MA_DATA_SOURCE_SET_RANGE is being handled). + // + // pData: ma_uint64* + + *((ma_uint64*)pData) = pCustomDataSource->length; + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_SET_LOOPING: + { + // Optional. + // + // Normally miniaudio will handle looping for you at a higher level, but if for + // some reason you need to handle it internally you can implement this. + // + // If this is implemented, MA_DATA_SOURCE_GET_LOOPING must also be implemented. + // + // Data sources should rarely need to implement this. + // + // pData: ma_bool32* + + pCustomDataSource->isLooping = *((ma_bool32*)pData); + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_LOOPING: + { + // Mandatory only if MA_DATA_SOURCE_SET_LOOPING is implemented. + // + // This should return whether or not the data source is looping. It's essentially + // the opposite of MA_DATA_SOURCE_SET_LOOPING. + // + // pData: ma_bool32* + + *((ma_bool32*)pData) = pCustomDataSource->isLooping; + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_SET_RANGE: + { + // Optional. + // + // Normally miniaudio will handle ranges for you at a higher level, but this lets + // you manage it internally. + // + // It is rare you would want or need to implement this. It can be useful if your + // data source acts as a proxy for another data source and you want to pass it + // through. + // + // pData: ma_pcm_range* + + ma_pcm_range* pRange = (ma_pcm_range*)pData; + pCustomDataSource->rangeBegInFrames = pRange->begInFrames; + pCustomDataSource->rangeEndInFrames = pRange->endInFrames; + + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_RANGE: + { + // Mandatory only if MA_DATA_SOURCE_SET_RANGE is implemented. + // + // This should return the range. The opposite of MA_DATA_SOURCE_SET_RANGE. + // + // pData: ma_pcm_range* + + ma_pcm_range* pRange = (ma_pcm_range*)pData; + pRange->begInFrames = pCustomDataSource->rangeBegInFrames; + pRange->endInFrames = pCustomDataSource->rangeEndInFrames; + + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_SET_LOOP_POINT: + { + // Optional. + // + // Normally miniaudio will handle loop points for you at a higher level, but this + // lets you manage it internally. + // + // It is rare you would want or need to implement this. It can be useful if your + // data source acts as a proxy for another data source and you want to pass it + // through. + // + // pData: ma_pcm_range* + + ma_pcm_range* pRange = (ma_pcm_range*)pData; + pCustomDataSource->loopBegInFrames = pRange->begInFrames; + pCustomDataSource->loopEndInFrames = pRange->endInFrames; + + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_LOOP_POINT: + { + // Mandatory only if MA_DATA_SOURCE_SET_LOOP_POINT is implemented. + // + // This should return the loop points. The opposite of MA_DATA_SOURCE_SET_LOOP_POINT. + // + // pData: ma_pcm_range* + + ma_pcm_range* pRange = (ma_pcm_range*)pData; + pRange->begInFrames = pCustomDataSource->loopBegInFrames; + pRange->endInFrames = pCustomDataSource->loopEndInFrames; + + return MA_SUCCESS; + } + + default: + { + // Return MA_NOT_IMPLEMENTED for any property the data source does not care about + // or is not aware of. + return MA_NOT_IMPLEMENTED; + } + } } - static ma_data_source_vtable g_my_data_source_vtable = + static ma_data_source_vtable g_custom_data_source_vtable = { - my_data_source_read, - my_data_source_seek, - my_data_source_get_data_format, - my_data_source_get_cursor, - my_data_source_get_length + custom_data_source_sizeof, + custom_data_source_uninit, + custom_data_source_read, + custom_data_source_seek, + custom_data_source_prop }; + ``` - ma_result my_data_source_init(my_data_source* pMyDataSource) +In your initialization routine, you need to call `ma_data_source_base_init()` to set up the +base object (`ma_data_source_base`): + + ma_result custom_data_source_init(custom_data_source* pMyDataSource) { ma_result result; ma_data_source_config baseConfig; baseConfig = ma_data_source_config_init(); - baseConfig.vtable = &g_my_data_source_vtable; + baseConfig.pVTable = &g_custom_data_source_vtable; - result = ma_data_source_init(&baseConfig, &pMyDataSource->base); + result = ma_data_source_base_init(&baseConfig, &pMyDataSource->base); if (result != MA_SUCCESS) { return result; } @@ -1005,18 +1225,18 @@ base object (`ma_data_source_base`): return MA_SUCCESS; } - void my_data_source_uninit(my_data_source* pMyDataSource) + void custom_data_source_uninit(custom_data_source* pMyDataSource) { // ... do the uninitialization of your custom data source here ... // You must uninitialize the base data source. - ma_data_source_uninit(&pMyDataSource->base); + ma_data_source_base_uninit(&pMyDataSource->base); } ``` -Note that `ma_data_source_init()` and `ma_data_source_uninit()` are never called directly outside -of the custom data source. It's up to the custom data source itself to call these within their own -init/uninit functions. +Note that `ma_data_source_base_init()` and `ma_data_source_base_uninit()` are never called directly +outside of the custom data source. It's up to the custom data source itself to call these within +their own init/uninit functions. @@ -1396,10 +1616,10 @@ default the attenuation model is set to `ma_attenuation_model_inverse` which is OpenAL's `AL_INVERSE_DISTANCE_CLAMPED`. Configure the attenuation model like so: ```c - ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse); + ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse, NULL); ``` -The supported attenuation models include the following: +The supported stock attenuation models include the following: +----------------------------------+----------------------------------------------+ | ma_attenuation_model_none | No distance attenuation. | @@ -1411,6 +1631,26 @@ The supported attenuation models include the following: | ma_attenuation_model_exponential | Exponential attenuation. | +----------------------------------+----------------------------------------------+ +The second parameter is a user data pointer which for stock attenuation models should always be set +to null. A custom attenuation model can be plugged in like so: + + ```c + float your_custom_attenuation_model(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff) + { + // This is just linear attenuation. You would plug in your own model here. + if (minDistance >= maxDistance) { + return 1; // To avoid division by zero. Do not attenuate. + } + + return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance); + } + + ma_sound_set_attenuation_model(&sound, your_custom_attenuation_model, &userDataIfRequiredByYourModel); + ``` + +The attenuation model cannot be changed while the sound is processing. You should set the +attenuation model once at initialization time before the sound is started. + To control how quickly a sound rolls off as it moves away from the listener, you need to configure the rolloff: @@ -1579,22 +1819,28 @@ formats through the use of custom decoders. To do so, pass in your `ma_decoding_ vtables into the resource manager config: ```c - ma_decoding_backend_vtable* pCustomBackendVTables[] = + ma_decoding_backend_vtable* pBackendVTables[] = { - &g_ma_decoding_backend_vtable_libvorbis, - &g_ma_decoding_backend_vtable_libopus + ma_decoding_backend_libvorbis, // Custom + ma_decoding_backend_libopus, // Custom + ma_decoding_backend_wav, // Stock + ma_decoding_backend_flac, // Stock + ma_decoding_backend_mp3 // Stock }; ... - resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables; - resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); - resourceManagerConfig.pCustomDecodingBackendUserData = NULL; + resourceManagerConfig.ppDecodingBackendVTables = pBackendVTables; + resourceManagerConfig.decodingBackendCount = sizeof(pBackendVTables) / sizeof(pBackendVTables[0]); ``` -This system can allow you to support any kind of file format. See the "Decoding" section for -details on how to implement custom decoders. The miniaudio repository includes examples for Opus -via libopus and libopusfile and Vorbis via libvorbis and libvorbisfile. +Note that when specifying a decoding backend, you need to specify every backend you want to +support, including stock backends. If `resourceManagerConfig.ppDecodingBackendVTables` is set to +null, only stock backends will be used, and will be in a fixed order of priority. The order in +which you list the backends in `resourceManagerConfig.ppDecodingBackendVTables` is the priority +order. This system can allow you to support any kind of file format in any priority you like. See +the "Decoding" section for details on how to implement custom decoders. The miniaudio repository +includes examples for Opus via libopus and libopusfile and Vorbis via libvorbis and libvorbisfile. Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the decoding of a page, a job will be posted to a queue which will then be processed by a job thread. @@ -2190,7 +2436,7 @@ pointer to the processing function and the number of input and output buses. Exa outputChannels[0] = channelsOut; ma_node_config nodeConfig = ma_node_config_init(); - nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.pVTable = &my_custom_node_vtable; nodeConfig.pInputChannels = inputChannels; nodeConfig.pOutputChannels = outputChannels; @@ -2219,7 +2465,7 @@ to `MA_NODE_BUS_COUNT_UNKNOWN`. In this case, the bus count should be set in the ... ma_node_config nodeConfig = ma_node_config_init(); - nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.pVTable = &my_custom_node_vtable; nodeConfig.inputBusCount = myBusCount; // <-- Since the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN, the input bus count should be set here. nodeConfig.pInputChannels = inputChannels; // <-- Make sure there are nodeConfig.inputBusCount elements in this array. nodeConfig.pOutputChannels = outputChannels; // <-- The vtable specifies 1 output bus, so there must be 1 element in this array. @@ -2360,7 +2606,7 @@ for specialized nodes: ``` When using a specialized node like `ma_data_source_node` or `ma_splitter_node`, be sure to not -modify the `vtable` member of the `nodeConfig` object. +modify the `pVTable` member of the `nodeConfig` object. 7.1. Timing @@ -2601,28 +2847,34 @@ A custom decoder must implement a data source. A vtable called `ma_decoding_back to be implemented which is then passed into the decoder config: ```c - ma_decoding_backend_vtable* pCustomBackendVTables[] = + ma_decoding_backend_vtable* pBackendVTables[] = { - &g_ma_decoding_backend_vtable_libvorbis, - &g_ma_decoding_backend_vtable_libopus + ma_decoding_backend_libvorbis, + ma_decoding_backend_libopus, + ma_decoding_backend_wav, + ma_decoding_backend_flac, + ma_decoding_backend_mp3 }; ... decoderConfig = ma_decoder_config_init_default(); - decoderConfig.pCustomBackendUserData = NULL; - decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; - decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + decoderConfig.ppBackendVTables = pBackendVTables; + decoderConfig.backendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); ``` +Note that when specify your own decoding backends, you must explicitly include any stock backends +you want to use, and in the order in which you want them to be tried. + The `ma_decoding_backend_vtable` vtable has the following functions: ``` + onInfo onInit onInitFile onInitFileW onInitMemory - onUninit + onUninit, ``` There are only two functions that must be implemented - `onInit` and `onUninit`. The other @@ -2655,6 +2907,14 @@ initialization routine is clean. When a decoder is uninitialized, the `onUninit` callback will be fired which will give you an opportunity to clean up and internal data. +The `onInfo` function is used to retrieve information about the decoding backend, such as its name +and encoding format. The encoding format is used as a hint to help miniaudio determine which +decoding backend to use. If you don't know the encoding format, you can set it to +`ma_encoding_format_unknown` and miniaudio will deal with it for you through trial and error. + +The miniaudio repository includes some examples of custom decoders in the "extras" folder. The +"custom_decoder" example demonstrates how to use a custom decoder with miniaudio. + 9. Encoding @@ -2971,23 +3231,29 @@ De-interleaved processing is not supported. To process frames, use `ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number of input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer -and the number of input frames that were consumed in the process. You can pass in NULL for the -input buffer in which case it will be treated as an infinitely large buffer of zeros. The output -buffer can also be NULL, in which case the processing will be treated as seek. +and the number of input frames that were consumed in the process. The sample rate can be changed dynamically on the fly. You can change this with explicit sample rates with `ma_resampler_set_rate()` and also with a decimal ratio with `ma_resampler_set_rate_ratio()`. The ratio is in/out. -Sometimes it's useful to know exactly how many input frames will be required to output a specific -number of frames. You can calculate this with `ma_resampler_get_required_input_frame_count()`. -Likewise, it's sometimes useful to know exactly how many frames would be output given a certain -number of input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. - Due to the nature of how resampling works, the resampler introduces some latency. This can be retrieved in terms of both the input rate and the output rate with `ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. +Below are some guidelines for some common scenarios: + + +-----------------------------------------------+-------------------------------------+ + | Scenario | Usage | + +-----------------------------------------------+-------------------------------------+ + | Fasest possible, quality not an issue | Linear, LPF == 0 | + | Fast, decent quality, infrequent rate changes | Linear, LPF >= 4 | + | Decent quality, frequent rate changes | Don't use miniaudio for resampling | + | High quality | Don't use miniaudio for resampling | + +-----------------------------------------------+-------------------------------------+ + +See below for how to configure the linear resampler's low-pass filter (LPF). + 10.3.1. Resampling Algorithms ----------------------------- @@ -3004,14 +3270,33 @@ The linear resampler performs low-pass filtering before or after downsampling or depending on the sample rates you're converting between. When decreasing the sample rate, the low-pass filter will be applied before downsampling. When increasing the rate it will be performed after upsampling. By default a fourth order low-pass filter will be applied. This can be configured -via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. +via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. It should be +set to a multiple of 2, such as 2, 4, 6, 8, etc. There are diminishing returns the higher you go. The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of the input and output sample rates (Nyquist Frequency). +When the low-pass filter is disabled (`lpfOrder` = 0), the resampler will run on an optimized code +path and should be efficient. + The API for the linear resampler is the same as the main resampler API, only it's called `ma_linear_resampler`. +The linear resampler supports s16 and f32 sample formats. The s16 path uses purely integer math, +whereas the f32 path will use floating point. For the s16 path, make sure you use reasonable ratios +for the input and output sample rates. It's OK to do make it large like 8000 -> 88200, but it's not +OK to use stupid numbers like 8000 -> 89999. If you need ratios like this, use the f32 path, or use +a different resampler. + +The linear resampler can support dynamic rate adjustments, but there are some tradeoffs to be aware +of. When not using low-pass filtering (LPF order = 0), rate changes work fine, however when the +low-pass filter is enabled (LPF order > 0), rate changes can sometimes result in some +discontinuities due to abrubt changes to the low-pass filter parameters. If you keep rate changes +small and infrequent it can sound decent, but you should do your own testing to ensure it meets +your specification. If you need a high quality resampler with support for dynamic rate adjustment +you should avoid using miniaudio's linear resampler. + + 10.3.2. Custom Resamplers ------------------------- @@ -3051,7 +3336,8 @@ The `onGetRequiredInputFrameCount` callback is used to give miniaudio a hint as frames are required to be available to produce the given number of output frames. Likewise, the `onGetExpectedOutputFrameCount` callback is used to determine how many output frames will be produced given the specified number of input frames. miniaudio will use these as a hint, but they -are optional and can be set to NULL if you're unable to implement them. +are optional and can be set to NULL if you're unable to implement them. The returned values should +be 100% accurate. If the best you can do is an estimate, do not implement these callbacks at all. @@ -3136,11 +3422,6 @@ input buffer in which case it will be treated as an infinitely large buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. -Sometimes it's useful to know exactly how many input frames will be required to output a specific -number of frames. You can calculate this with `ma_data_converter_get_required_input_frame_count()`. -Likewise, it's sometimes useful to know exactly how many frames would be output given a certain -number of input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. - Due to the nature of how resampling works, the data converter introduces some latency if resampling is required. This can be retrieved in terms of both the input rate and the output rate with `ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. @@ -3361,7 +3642,7 @@ with the `ma_waveform` API. Example: ... - ma_waveform_read_pcm_frames(&waveform, pOutput, frameCount); + ma_waveform_read_pcm_frames(&waveform, pOutput, frameCount, NULL); ``` The amplitude, frequency, type, and sample rate can be changed dynamically with @@ -3539,66 +3820,128 @@ as an error when returned by `ma_audio_buffer_unmap()`. 14. Ring Buffers ================ miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via -the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates on bytes, whereas the `ma_pcm_rb` -operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around -`ma_rb`. - -Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved -streams. The caller can also allocate their own backing memory for the ring buffer to use -internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for -you. +the `ma_ring_buffer` and `ma_audio_ring_buffer` APIs. The `ma_ring_buffer` API is a lowever level, +generic ring buffer, whereas the `ma_audio_ring_buffer` is designed for PCM audio data and can be +used as a data source. Ring buffers are 32-bit oriented. If you want to support 64-bit capacities +you will need to use something else. The examples below use the PCM frame variant of the ring buffer since that's most likely the one you will want to use. To initialize a ring buffer, do something like the following: ```c - ma_pcm_rb rb; - ma_result result = ma_pcm_rb_init(FORMAT, CHANNELS, BUFFER_SIZE_IN_FRAMES, NULL, NULL, &rb); + ma_audio_ring_buffer rb; + ma_audio_ring_buffer_config rbConfig; + + rbConfig = ma_audio_ring_buffer_config_init(FORMAT, CHANNELS, SAMPLE_RATE, BUFFER_SIZE_IN_FRAMES); + + ma_result result = ma_audio_ring_buffer_init(&rbConfig, &rb); if (result != MA_SUCCESS) { // Error } ``` -The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because -it's the PCM variant of the ring buffer API. For the regular ring buffer that operates on bytes you -would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes -instead of frames. The fourth parameter is an optional pre-allocated buffer and the fifth parameter -is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation routines. -Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. - -Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is -offset from each other based on the stride. To manage your sub-buffers you can use -`ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and -`ma_pcm_rb_get_subbuffer_ptr()`. - -Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section -of the ring buffer. You specify the number of frames you need, and on output it will set to what -was actually acquired. If the read or write pointer is positioned such that the number of frames -requested will require a loop, it will be clamped to the end of the buffer. Therefore, the number -of frames you're given may be less than the number you requested. - -After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the -buffer and then "commit" it with `ma_pcm_rb_commit_read()` or `ma_pcm_rb_commit_write()`. This is -where the read/write pointers are updated. When you commit you need to pass in the buffer that was -returned by the earlier call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is -only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and -`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was -originally requested. - -If you want to correct for drift between the write pointer and the read pointer you can use a -combination of `ma_pcm_rb_pointer_distance()`, `ma_pcm_rb_seek_read()` and -`ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only -move the read pointer forward via the consumer thread, and the write pointer forward by the -producer thread. If there is too much space between the pointers, move the read pointer forward. If -there is too little space between the pointers, move the write pointer forward. - -You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` -API. This is exactly the same, only you will use the `ma_rb` functions instead of `ma_pcm_rb` and -instead of frame counts you will pass around byte counts. - -The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most -significant bit being used to encode a loop flag and the internally managed buffers always being -aligned to `MA_SIMD_ALIGNMENT`. +The `ma_audio_ring_buffer_init()` follows the same config/init pattern used throughout miniaudio. +The sample rate is not used by the ring buffer internally and can be set to 0, but it is provided +for the purpose of the data source implementation. + +You can allocate your own underlying buffer to act as the backing store in which case you can +initialize specify it in the config: + + ```c + void* pBuffer = malloc(BUFFER_SIZE_IN_FRAMES * bytesPerFrame * 2); // <-- The 2x is important. + + rbConfig = ma_audio_ring_buffer_config_init(FORMAT, CHANNELS, SAMPLE_RATE, BUFFER_SIZE_IN_FRAMES); + rbConfig.pBuffer = pBuffer; + + ma_audio_ring_Buffer_init(&rbConfig, &rb); + ``` + +The size of the buffer must be 2 times the capacity. It is possible for you to use a mirrored +buffer where the second half of the buffer maps to the same physical memory as the first half. In +this case you can specify the `MA_RING_BUFFER_FLAG_MIRRORED` flag: + + ```c + void* pBuffer = ...; // <-- Your own OS-specific allocation routine goes here. It must have 2x the addressable space as the the capacity. + + rbConfig = ma_audio_ring_buffer_config_init(FORMAT, CHANNELS, SAMPLE_RATE, BUFFER_SIZE_IN_FRAMES); + rbConfig.flags = MA_RING_BUFFER_FLAG_MIRRORED; + rbConfig.pBuffer = pBuffer; + + ma_audio_ring_buffer_init(&rbConfig, &rb); + ``` + +To read and write data you map and unmap a pointer. For writing, you can do something like this: + + ```c + void* pMappedBuffer; + ma_uint32 desiredFrameCount = 128; + ma_uint32 mappedFrameCount = ma_audio_ring_buffer_map_produce(&rb, desiredFrameCount, &pMappedBuffer); + + do_something(pMappedBuffer, mappedFrameCount); + + ma_audio_ring_buffer_unmap_produce(&rb, mappedFrameCount); + ``` + +You first map a pointer by requesting the number of frames you want to use write. The returned +value is how many frames were actually mapped. If it's less than what you requested it means there +is not enough room to write the number of frames you requested. Once you've copied your data to the +mapped buffer you can unmap it. You cannot unmap more frames that were returned when the buffer was +mapped, but you can unmap less if you like. + +Reading works the same way as writing: + + ```c + void* pMappedBuffer; + ma_uint32 desiredFrameCount = 128; + ma_uint32 mappedFrameCount = ma_audio_ring_buffer_map_consume(&rb, desiredFrameCount, pMappedBuffer); + + do_something(pMappedBuffer, mappedFrameCount); + + ma_audio_ring_buffer_unmap_consume(&rb, mappedFrameCount); + ``` + +Instead of mapping a "produce" pointer, you map a "consume" pointer. If the returned number of +frames is less than what you requested it means there's not enough data to read. + +If the map/unmap system is inconvenient, there are some helper functions to write directly into the +ring buffer in a familiar way as is typical in miniaudio: + + ```c + ma_audio_ring_buffer_write_pcm_frames(&rb, pFramesToWrite, frameCount, &framesWritten); + ``` + +And to read: + + ```c + ma_audio_ring_buffer_read_pcm_frames(&rb, pAudioData, frameCount, &framesRead); + ``` + +The maximum allowed capacity of a ring buffer is 0x7FFFFFFF frames. + +`ma_audio_ring_buffer` can be used as a data source. To do so, simply pass it into any data source +function: + + ```c + ma_audio_ring_buffer rb; + ma_audio_ring_buffer_init(...); + + ... + + ma_data_source_read_pcm_frames(&rb, pAudioData, frameCount, &framesRead); + ``` + +The examples above were for `ma_audio_ring_buffer`, but the API is very similar for +`ma_ring_buffer` except that it works on "elements" instead of frames, and the size of each element +is defined by a stride which you pass in during initialization: + + ```c + ma_uint32 buffer[CAPACITY * 2]; + ma_ring_buffer rb; + ma_ring_buffer_init(CAPACITY, sizeof(ma_uint32), 0, buffer, &rb); + ``` + +The map/unmap system is the only way to read from and write to a `ma_ring_buffer`, and it works in +the same was as `ma_audio_ring_buffer`. Note that the ring buffer is only thread safe when used by a single consumer thread and single producer thread. @@ -3614,25 +3957,28 @@ each of these backends in the order listed in the table below. Note that backends that are not usable by the build target will not be included in the build. For example, ALSA, which is specific to Linux, will not be included in the Windows build. - +-------------+-----------------------+--------------------------------------------------------+ - | Name | Enum Name | Supported Operating Systems | - +-------------+-----------------------+--------------------------------------------------------+ - | WASAPI | ma_backend_wasapi | Windows Vista+ | - | DirectSound | ma_backend_dsound | Windows XP+ | - | WinMM | ma_backend_winmm | Windows 95+ | - | Core Audio | ma_backend_coreaudio | macOS, iOS | - | sndio | ma_backend_sndio | OpenBSD | - | audio(4) | ma_backend_audio4 | NetBSD, OpenBSD | - | OSS | ma_backend_oss | FreeBSD | - | PulseAudio | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | - | ALSA | ma_backend_alsa | Linux | - | JACK | ma_backend_jack | Cross Platform (disabled on BSD and Android) | - | AAudio | ma_backend_aaudio | Android 8+ | - | OpenSL ES | ma_backend_opensl | Android (API level 16+) | - | Web Audio | ma_backend_webaudio | Web (via Emscripten) | - | Custom | ma_backend_custom | Cross Platform | - | Null | ma_backend_null | Cross Platform (not used on Web) | - +-------------+-----------------------+--------------------------------------------------------+ + +-------------+------------------------------+--------------------------------------------------------+ + | Name | Enum Name | Supported Operating Systems | + +-------------+------------------------------+--------------------------------------------------------+ + | WASAPI | ma_device_backend_wasapi | Windows Vista+ | + | DirectSound | ma_device_backend_dsound | Windows XP+ | + | WinMM | ma_device_backend_winmm | Windows 95+ | + | Core Audio | ma_device_backend_coreaudio | macOS, iOS | + | PipeWire | ma_device_backend_pipewire | Linux | + | PulseAudio | ma_device_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | + | JACK | ma_device_backend_jack | Cross Platform (disabled on BSD and Android) | + | ALSA | ma_device_backend_alsa | Linux | + | sndio | ma_device_backend_sndio | OpenBSD | + | audio(4) | ma_device_backend_audio4 | NetBSD, OpenBSD | + | OSS | ma_device_backend_oss | FreeBSD | + | AAudio | ma_device_backend_aaudio | Android 8+ | + | OpenSL|ES | ma_device_backend_opensl | Android (API level 16+) | + | Web Audio | ma_device_backend_webaudio | Web (via Emscripten) | + | Dreamcast | ma_device_backend_dreamcast | KallistiOS | + | XAudio | ma_device_backend_xaudio | NXDK | + | PS Vita | ma_device_backend_vita | Vita SDK | + | Null | ma_device_backend_null | Cross Platform (not used on Web) | + +-------------+------------------------------+--------------------------------------------------------+ Some backends have some nuance details you may want to be aware of. @@ -3665,8 +4011,8 @@ Some backends have some nuance details you may want to be aware of. miniaudio's built-in resampler is to take advantage of any potential device-specific optimizations the driver may implement. -BSD ---- +15.3 BSD +-------- - The sndio backend is currently only enabled on OpenBSD builds. - The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can use it. @@ -3697,6 +4043,105 @@ BSD https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device may fail if you try to start playback without first handling some kind of user input. +15.6. Dreamcast +--------------- +The Dreamcast backend uses KallistiOS. + +miniaudio will call `snd_init()` automatically unless you disable it via the context config: + +```c +contextConfig.dreamcast.noInit = MA_TRUE; +``` + +The following device configuration is recommended. + + Format: ma_format_u8 or ma_format_s16 + Channels: 1 or 2 + Sample Rate: 44100, 22050 or 11025 + Period Size: 1536 to 16384, multiple of 32. Recommended you keep it a power of two. + noFixedSizedCallback: True (This will tell miniaudio to bypass one of its internal buffers.) + +If you follow the above rules, miniaudio will skip it's data conversion pipeline and will pass +through a direct pointer to an internal buffer to the data callback without any extra buffering or +data conversion. You are still free to use other formats, but it'll just have a bit more overhead. +The miniaudio resampler will have enough overhead that it's strongly recommended you keep the +sample rate set to one of the recommended values and pre-process your audio assets to match. + +You can initialize multiple devices and specify which voice channel you want to use. There is a +maximum of 64 voices. If you request two channels when initializing the device it will use two of +them. You can specify the index of the voice channel you want to use in the `dreamcast` device +config. You can use multiple devices to have the hardware do mixing for you, but if you do this +make sure you manage your voice channel properly. To configure the voice channel, set it in the +`dreamcast` config: + +```c +deviceConfig.dreamcast.voiceChannel = 2; // 0 is the default. The left channel will use `voiceChannel + 0`, the right channel will use `voiceChannel + 1`. +``` + +If you initialize more than one `ma_device` object, self manage a single `ma_context` object. It is +a single context to many devices: + +```c +ma_context context; +ma_context_init(...); + +deviceConfig.dreamcast.voiceChannel = 0; +ma_device_init(&context, &deviceConfig, &device0); + +deviceConfig.dreamcast.voiceChannel = 2; // <-- Make sure the voice channel is different between devices. Remember that a stereo device consumes two channels. +ma_device_init(&context, &deviceConfig, &device1); +``` + +Note that the `ma_device` struct itself has memory overhead just from its own members. + +By default audio will be processed on a background thread. You can disable threading and run your +device in single threaded mode by disabling it at compiling with the `MA_NO_THREADING` define. By +disabling threading at compile time you will also reduce memory usage of the `ma_device` struct. +When running in single threaded mode you need to manually step the device periodically with +`ma_device_step()`, which you would normally do each frame. + +15.7. XAudio (Original Xbox) +---------------------------- +The XAudio backend is used for the original Xbox and uses NXDK. + +You should only initialize a single `ma_device` object. Do not create more than one device thinking +that mixing will be done in hardware. Do your mixing in software. + +The following device configuration is optimal: + + Format: ma_foramt_s16 + Channels: 2 + Sample Rate: 48000 + Period Size: 1024 to 16383 + noFixedSizedCallback: True + +If you follow this configuration you can be guaranteed an optimized passthrough pipeline. You can +use a period size smaller than 1024, but in my testing I found that it can result in glitches. You +are not forced to use this configuration, but if you deviate from it miniaudio will need to do data +conversion. + +15.8. PlayStation Vita +---------------------- +The Vita backend uses Vita SDK. + +The following device configuration is optimal: + + Format: ma_foramt_s16 + Channels: 2 + Sample Rate: 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100 or 48000 + Period Size: Multiple of 64, between 64 and 65472 + noFixedSizedCallback: True + +If you follow this configuration you can be guaranteed an optimized passthrough pipeline, except +when using a sample rate other than 48000 on the Main port (see below). + +The BGM port (SCE_AUDIO_OUT_PORT_TYPE_BGM) is used by default. You can use the Main port by +configuring it in the device config: + + deviceConfig.vita.portType = MA_VITA_PORT_TYPE_MAIN; // Or MA_VITA_PORT_TYPE_BGM + +When using MA_VITA_PORT_TYPE_MAIN, the native sample rate will always be 48000 and using anything +else will invoke miniaudio's resampler which will have overhead. 16. Optimization Tips @@ -3747,24 +4192,16 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 11 -#define MA_VERSION_REVISION 25 +#define MA_VERSION_REVISION 26 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(push) - #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ - #pragma warning(disable:4214) /* nonstandard extension used: bit field types other than int */ #pragma warning(disable:4324) /* structure was padded due to alignment specifier */ -#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpedantic" /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ - #if defined(__clang__) - #pragma GCC diagnostic ignored "-Wc11-extensions" /* anonymous unions are a C11 extension */ - #endif #endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) || defined(__ppc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__powerpc64__) || defined(__ppc64__) #define MA_SIZEOF_PTR 8 #else #define MA_SIZEOF_PTR 4 @@ -3857,6 +4294,64 @@ typedef ma_uint16 wchar_t; #define MA_UINT64_MAX (((ma_uint64)0xFFFFFFFF << 32) | (ma_uint64)0xFFFFFFFF) /* Weird shifting syntax is for VC6 compatibility. */ +/* Architecture Detection */ +#if !defined(MA_64BIT) && !defined(MA_32BIT) + #ifdef _WIN32 + #ifdef _WIN64 + #define MA_64BIT + #else + #define MA_32BIT + #endif + #endif +#endif + +#if !defined(MA_64BIT) && !defined(MA_32BIT) + #ifdef __GNUC__ + #ifdef __LP64__ + #define MA_64BIT + #else + #define MA_32BIT + #endif + #endif +#endif + +#if !defined(MA_64BIT) && !defined(MA_32BIT) + #include + #if INTPTR_MAX == INT64_MAX + #define MA_64BIT + #else + #define MA_32BIT + #endif +#endif + +#if defined(__arm__) || defined(_M_ARM) + #define MA_ARM32 +#endif +#if defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) + #define MA_ARM64 +#endif + +#if defined(__riscv) + #if defined(__riscv_xlen) + #if __riscv_xlen == 64 + #define MA_RISCV64 + #elif __riscv_xlen == 32 + #define MA_RISCV32 + #endif + #endif +#endif + +#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)) + #define MA_X64 +#elif defined(__i386) || defined(_M_IX86) + #define MA_X86 +#elif defined(MA_ARM32) || defined(MA_ARM64) + #define MA_ARM +#elif defined(MA_RISCV32) || defined(MA_RISCV64) + #define MA_RISCV +#endif + + /* Platform/backend detection. */ #if defined(_WIN32) #define MA_WIN32 @@ -3895,26 +4390,6 @@ typedef ma_uint16 wchar_t; #if !defined(MA_WIN32) && !defined(MA_DOS) /* If it's not Win32, assume POSIX. */ #define MA_POSIX - #if !defined(MA_NO_THREADING) - /* - Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented. - You can use this to avoid including pthread.h in the header section. The downside is that it - results in some fixed sized structures being declared for the various types that are used in - miniaudio. The risk here is that these types might be too small for a given platform. This - risk is yours to take and no support will be offered if you enable this option. - */ - #ifndef MA_NO_PTHREAD_IN_HEADER - #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ - typedef pthread_t ma_pthread_t; - typedef pthread_mutex_t ma_pthread_mutex_t; - typedef pthread_cond_t ma_pthread_cond_t; - #else - typedef ma_uintptr ma_pthread_t; - typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t; - typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t; - #endif - #endif - #if defined(__unix__) #define MA_UNIX #endif @@ -3940,17 +4415,26 @@ typedef ma_uint16 wchar_t; #define MA_PROSPERO #endif #if defined(__3DS__) - #define MA_3DS + #define MA_N3DS #endif #if defined(__SWITCH__) || defined(__NX__) #define MA_SWITCH #endif + #if defined(__DREAMCAST__) + #define MA_DREAMCAST /* Assuming KallistiOS. */ + #endif + #if defined(__vita__) + #define MA_VITA /* Assuming Vita SDK. */ + #endif #if defined(__BEOS__) || defined(__HAIKU__) #define MA_BEOS #endif #if defined(__HAIKU__) #define MA_HAIKU #endif + #if defined(__sun) || defined(__sun__) + #define MA_SUN + #endif #endif #if !defined(MA_FALLTHROUGH) && defined(__cplusplus) && __cplusplus >= 201703L @@ -4046,8 +4530,18 @@ typedef ma_uint16 wchar_t; #endif +#ifndef MA_CACHE_LINE_SIZE + #if defined(__powerpc64__) || defined(__ppc64__) || defined(_ARCH_PPC64) + #define MA_CACHE_LINE_SIZE 128 + #elif defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__)) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED) + #define MA_CACHE_LINE_SIZE 128 + #else + #define MA_CACHE_LINE_SIZE 64 + #endif +#endif + /* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */ -#define MA_SIMD_ALIGNMENT 32 +#define MA_SIMD_ALIGNMENT 32 /* Special wchar_t type to ensure any structures in the public sections that reference it have a @@ -4063,6 +4557,112 @@ typedef wchar_t ma_wchar_win32; typedef ma_uint16 ma_wchar_win32; #endif +/* Disable threading on the Emscripten build if it's not built with pthread support. */ +#if defined(MA_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) + #ifndef MA_NO_THREADING + #define MA_NO_THREADING + #endif +#endif + +/* pthread. */ +#if defined(MA_POSIX) + #if !defined(MA_NO_THREADING) + /* + Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented. + You can use this to avoid including pthread.h in the header section. The downside is that it + results in some fixed sized structures being declared for the various types that are used in + miniaudio. The risk here is that these types might be too small for a given platform. This + risk is yours to take and no support will be offered if you enable this option. + */ + #ifndef MA_NO_PTHREAD_IN_HEADER + #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ + typedef pthread_t ma_pthread_t; + typedef pthread_mutex_t ma_pthread_mutex_t; + typedef pthread_cond_t ma_pthread_cond_t; + #else + typedef ma_uintptr ma_pthread_t; + typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t; + typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t; + #endif + #endif +#endif + + + +static MA_INLINE ma_bool32 ma_is_little_endian(void) +{ +#if defined(MA_X86) || defined(MA_X64) + return MA_TRUE; +#else + int n = 1; + return (*(char*)&n) == 1; +#endif +} + +static MA_INLINE ma_bool32 ma_is_big_endian(void) +{ + return !ma_is_little_endian(); +} + + +/* +Basic Functions +=============== +These are mainly for custom backends so they can make use of some useful functionality without having +to reimplement it themselves. +*/ +#define ma_countof(x) (sizeof(x) / sizeof(x[0])) +#define ma_max(x, y) (((x) > (y)) ? (x) : (y)) +#define ma_min(x, y) (((x) < (y)) ? (x) : (y)) +#define ma_abs(x) (((x) > 0) ? (x) : -(x)) +#define ma_clamp(x, lo, hi) (ma_max(lo, ma_min(x, hi))) +#define ma_offset_ptr(p, offset) (((ma_uint8*)(p)) + (offset)) +#define ma_align(x, a) ((x + (a-1)) & ~(a-1)) +#define ma_align_64(x) ma_align(x, 8) + +MA_API MA_NO_INLINE int ma_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count); + +/* Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */ +static MA_INLINE unsigned int ma_next_power_of_2(unsigned int x) +{ + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + + return x; +} + + + + +/* +Default Data Formats +==================== +These are made public for custom backends so they can consistently make use of standard defaults. The only +time you should be using these are within the implementations of custom backends. It's not recommended to +use these directly in a device config. Instead set them to ma_format_f32 (for format) and 0 for channels and +sample rate. +*/ + +/* The default format when ma_format_unknown (0) is requested when initializing a device. */ +#ifndef MA_DEFAULT_FORMAT +#define MA_DEFAULT_FORMAT ma_format_f32 +#endif + +/* The default channel count to use when 0 is used when initializing a device. */ +#ifndef MA_DEFAULT_CHANNELS +#define MA_DEFAULT_CHANNELS 2 +#endif + +/* The default sample rate to use when 0 is used when initializing a device. */ +#ifndef MA_DEFAULT_SAMPLE_RATE +#define MA_DEFAULT_SAMPLE_RATE 48000 +#endif + /* @@ -4124,9 +4724,6 @@ versions of Visual Studio, which I've confirmed with at least VC6. #endif #endif -typedef struct ma_context ma_context; -typedef struct ma_device ma_device; - typedef ma_uint8 ma_channel; typedef enum { @@ -4188,7 +4785,7 @@ typedef enum /* Aliases. */ MA_CHANNEL_LEFT = MA_CHANNEL_FRONT_LEFT, - MA_CHANNEL_RIGHT = MA_CHANNEL_FRONT_RIGHT, + MA_CHANNEL_RIGHT = MA_CHANNEL_FRONT_RIGHT } _ma_channel_position; /* Do not use `_ma_channel_position` directly. Use `ma_channel` instead. */ typedef enum @@ -4340,10 +4937,12 @@ typedef enum ma_standard_sample_rate_384000 = 384000, ma_standard_sample_rate_min = ma_standard_sample_rate_8000, - ma_standard_sample_rate_max = ma_standard_sample_rate_384000, - ma_standard_sample_rate_count = 14 /* Need to maintain the count manually. Make sure this is updated if items are added to enum. */ + ma_standard_sample_rate_max = ma_standard_sample_rate_384000 } ma_standard_sample_rate; +MA_API const ma_uint32* ma_get_standard_sample_rates(void); +MA_API ma_uint32 ma_get_standard_sample_rate_count(void); + typedef enum { @@ -4366,12 +4965,6 @@ typedef enum ma_standard_channel_map_default = ma_standard_channel_map_microsoft } ma_standard_channel_map; -typedef enum -{ - ma_performance_profile_low_latency = 0, - ma_performance_profile_conservative -} ma_performance_profile; - typedef struct { @@ -4447,59 +5040,82 @@ MA_ATOMIC_SAFE_TYPE_DECL(32, 4, bool32) /* Spinlocks are 32-bit for compatibility reasons. */ typedef ma_uint32 ma_spinlock; +/* Thread priorities should be ordered such that the default priority of the worker thread is 0. */ +typedef enum +{ + ma_thread_priority_idle = -5, + ma_thread_priority_lowest = -4, + ma_thread_priority_low = -3, + ma_thread_priority_normal = -2, + ma_thread_priority_high = -1, + ma_thread_priority_highest = 0, + ma_thread_priority_realtime = 1, + ma_thread_priority_default = 0 +} ma_thread_priority; + #ifndef MA_NO_THREADING - /* Thread priorities should be ordered such that the default priority of the worker thread is 0. */ - typedef enum - { - ma_thread_priority_idle = -5, - ma_thread_priority_lowest = -4, - ma_thread_priority_low = -3, - ma_thread_priority_normal = -2, - ma_thread_priority_high = -1, - ma_thread_priority_highest = 0, - ma_thread_priority_realtime = 1, - ma_thread_priority_default = 0 - } ma_thread_priority; - - #if defined(MA_POSIX) - typedef ma_pthread_t ma_thread; + #if defined(MA_VITA) + #define MA_THREADING_BACKEND_VITA + #elif defined(MA_POSIX) + #define MA_THREADING_BACKEND_POSIX #elif defined(MA_WIN32) - typedef ma_handle ma_thread; + #define MA_THREADING_BACKEND_WIN32 + #else + #define MA_THREADING_BACKEND_NONE #endif +#else + #define MA_THREADING_BACKEND_NONE +#endif - #if defined(MA_POSIX) - typedef ma_pthread_mutex_t ma_mutex; - #elif defined(MA_WIN32) - typedef ma_handle ma_mutex; - #endif +#if defined(MA_THREADING_BACKEND_POSIX) + typedef ma_pthread_t ma_thread; +#elif defined(MA_THREADING_BACKEND_WIN32) + typedef ma_handle ma_thread; +#elif defined(MA_THREADING_BACKEND_VITA) + typedef int ma_thread; +#else + typedef int ma_thread; /* Fallback for platforms without support for threading. Attempting to create a thread will fail. */ +#endif - #if defined(MA_POSIX) - typedef struct - { - ma_uint32 value; - ma_pthread_mutex_t lock; - ma_pthread_cond_t cond; - } ma_event; - #elif defined(MA_WIN32) - typedef ma_handle ma_event; - #endif +#if defined(MA_THREADING_BACKEND_POSIX) + typedef ma_pthread_mutex_t ma_mutex; +#elif defined(MA_THREADING_BACKEND_WIN32) + typedef ma_handle ma_mutex; +#elif defined(MA_THREADING_BACKEND_VITA) + typedef int ma_mutex; +#else + typedef ma_spinlock ma_mutex; /* Fallback for platforms without support for threading. */ +#endif - #if defined(MA_POSIX) - typedef struct - { - int value; - ma_pthread_mutex_t lock; - ma_pthread_cond_t cond; - } ma_semaphore; - #elif defined(MA_WIN32) - typedef ma_handle ma_semaphore; - #endif +#if defined(MA_THREADING_BACKEND_POSIX) + typedef struct + { + ma_uint32 value; + ma_pthread_mutex_t lock; + ma_pthread_cond_t cond; + } ma_event; +#elif defined(MA_THREADING_BACKEND_WIN32) + typedef ma_handle ma_event; +#elif defined(MA_THREADING_BACKEND_VITA) + typedef int ma_event; #else - /* MA_NO_THREADING is set which means threading is disabled. Threading is required by some API families. If any of these are enabled we need to throw an error. */ - #ifndef MA_NO_DEVICE_IO - #error "MA_NO_THREADING cannot be used without MA_NO_DEVICE_IO"; - #endif -#endif /* MA_NO_THREADING */ + typedef ma_uint32 ma_event; /* Fallback for platforms without support for threading. */ +#endif + +#if defined(MA_THREADING_BACKEND_POSIX) + typedef struct + { + int value; + ma_pthread_mutex_t lock; + ma_pthread_cond_t cond; + } ma_semaphore; +#elif defined(MA_THREADING_BACKEND_WIN32) + typedef ma_handle ma_semaphore; +#elif defined(MA_THREADING_BACKEND_VITA) + typedef int ma_semaphore; +#else + typedef ma_uint32 ma_semaphore; /* Fallback for platforms without support for threading. */ +#endif /* @@ -4588,6 +5204,20 @@ MA_API ma_result ma_log_postv(ma_log* pLog, ma_uint32 level, const char* pFormat MA_API ma_result ma_log_postf(ma_log* pLog, ma_uint32 level, const char* pFormat, ...) MA_ATTRIBUTE_FORMAT(3, 4); + +/************************************************************************************************************************************************************** + +Dynamic Linking + +These functions are available for use by custom backends so they can dynamically link to their backend libraries at runtime. + +**************************************************************************************************************************************************************/ +MA_API ma_handle ma_dlopen(ma_log* pLog, const char* filename); +MA_API void ma_dlclose(ma_log* pLog, ma_handle handle); +MA_API ma_proc ma_dlsym(ma_log* pLog, ma_handle handle, const char* symbol); + + + /************************************************************************************************************************************************************** Biquad Filtering @@ -5010,8 +5640,6 @@ typedef struct ma_uint32 sampleRate; ma_uint32 delayInFrames; ma_bool32 delayStart; /* Set to true to delay the start of the output; false otherwise. */ - float wet; /* 0..1. Default = 1. */ - float dry; /* 0..1. Default = 1. */ float decay; /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */ } ma_delay_config; @@ -5029,10 +5657,6 @@ typedef struct MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay); MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount); -MA_API void ma_delay_set_wet(ma_delay* pDelay, float value); -MA_API float ma_delay_get_wet(const ma_delay* pDelay); -MA_API void ma_delay_set_dry(ma_delay* pDelay, float value); -MA_API float ma_delay_get_dry(const ma_delay* pDelay); MA_API void ma_delay_set_decay(ma_delay* pDelay, float value); MA_API float ma_delay_get_decay(const ma_delay* pDelay); @@ -5149,13 +5773,22 @@ typedef struct ma_spinlock lock; } ma_atomic_vec3f; -typedef enum -{ - ma_attenuation_model_none, /* No distance attenuation and no spatialization. */ - ma_attenuation_model_inverse, /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */ - ma_attenuation_model_linear, /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ - ma_attenuation_model_exponential /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ -} ma_attenuation_model; + +/* Callback for attenuation models. The user data is unused for all stock models. */ +typedef float (* ma_attenuation_model)(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff); + +/* No distance attenuation. */ +MA_API float ma_attenuation_model_none(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff); + +/* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */ +MA_API float ma_attenuation_model_inverse(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff); + +/* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ +MA_API float ma_attenuation_model_linear(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff); + +/* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ +MA_API float ma_attenuation_model_exponential(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff); + typedef enum { @@ -5225,6 +5858,7 @@ typedef struct ma_uint32 channelsOut; ma_channel* pChannelMapIn; ma_attenuation_model attenuationModel; + void* pAttenuationUserData; ma_positioning positioning; ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ float minGain; @@ -5250,6 +5884,7 @@ typedef struct ma_uint32 channelsOut; ma_channel* pChannelMapIn; ma_attenuation_model attenuationModel; + void* pAttenuationUserData; ma_positioning positioning; ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ float minGain; @@ -5285,7 +5920,7 @@ MA_API ma_result ma_spatializer_set_master_volume(ma_spatializer* pSpatializer, MA_API ma_result ma_spatializer_get_master_volume(const ma_spatializer* pSpatializer, float* pVolume); MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer); MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer); -MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel); +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel, void* pAttenuationUserData); MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer); MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning); MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer); @@ -5337,19 +5972,28 @@ typedef struct ma_uint32 channels; ma_uint32 sampleRateIn; ma_uint32 sampleRateOut; - ma_uint32 lpfOrder; /* The low-pass filter order. Setting this to 0 will disable low-pass filtering. */ - double lpfNyquistFactor; /* 0..1. Defaults to 1. 1 = Half the sampling frequency (Nyquist Frequency), 0.5 = Quarter the sampling frequency (half Nyquest Frequency), etc. */ + ma_uint32 lpfOrder; /* The low-pass filter order. Setting this to 0 will disable low-pass filtering. Must be a multiple of 2. */ + float lpfNyquistFactor; /* 0..1. Defaults to 1. 1 = Half the sampling frequency (Nyquist Frequency), 0.5 = Quarter the sampling frequency (half Nyquest Frequency), etc. */ } ma_linear_resampler_config; MA_API ma_linear_resampler_config ma_linear_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); typedef struct { - ma_linear_resampler_config config; + void* _pHeap; + ma_bool32 _ownsHeap; + + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; + ma_uint32 lpfOrder; + float lpfNyquistFactor; ma_uint32 inAdvanceInt; ma_uint32 inAdvanceFrac; ma_uint32 inTimeInt; ma_uint32 inTimeFrac; + ma_uint32 cachedFrameCount; union { float* f32; @@ -5360,11 +6004,29 @@ typedef struct float* f32; ma_int16* s16; } x1; /* The next input frame. */ - ma_lpf lpf; - /* Memory management. */ - void* _pHeap; - ma_bool32 _ownsHeap; + /* + The low-pass filter is achieved with a series of 2nd-order biquads. This means there is one + LPF state for each `lpfOrder`, divided by two. So if `lpfOrder` is 4, there will be 2 LPF + states in the array. The structure of each LPF state is as follows: + + +-------------------+------------+ + | Biquad b1 | 1 | + | Biquad b2 | 1 | + | Biquad a1 | 1 | + | Biquad a2 | 1 | + | Biquad register 1 | `channels` | + | Biquad register 2 | `channels` | + +-------------------+------------+ + + If you are familiar with biquads, you'll note that b0 and a0 are missing. This is because b0 + is set the same value as b2, so we just reuse b2, and a0 is just not used. + */ + union + { + float* f32; + ma_int32* s32; + } lpf; } ma_linear_resampler; MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes); @@ -5376,32 +6038,31 @@ MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, m MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut); MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler); MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler); -MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); -MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler); +/* Helper function for calculating the final frame count as if it were resampled by the linear resampler. */ +MA_API ma_uint64 ma_linear_resampler_calculate_frame_count_after_resampling(ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_uint64 frameCountIn); + typedef struct ma_resampler_config ma_resampler_config; typedef void ma_resampling_backend; typedef struct { - ma_result (* onGetHeapSize )(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); - ma_result (* onInit )(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend); - void (* onUninit )(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); - ma_result (* onProcess )(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); - ma_result (* onSetRate )(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); /* Optional. Rate changes will be disabled. */ - ma_uint64 (* onGetInputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ - ma_uint64 (* onGetOutputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ - ma_result (* onGetRequiredInputFrameCount )(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); /* Optional. Latency mitigation will be disabled. */ - ma_result (* onGetExpectedOutputFrameCount)(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); /* Optional. Latency mitigation will be disabled. */ - ma_result (* onReset )(void* pUserData, ma_resampling_backend* pBackend); + ma_result (* onGetHeapSize )(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); + ma_result (* onInit )(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend); + void (* onUninit )(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); + ma_result (* onProcess )(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); + ma_result (* onSetRate )(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); /* Optional. Rate changes will be disabled. */ + ma_uint64 (* onGetInputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_uint64 (* onGetOutputLatency)(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_result (* onReset )(void* pUserData, ma_resampling_backend* pBackend); } ma_resampling_backend_vtable; typedef enum { ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ - ma_resample_algorithm_custom, + ma_resample_algorithm_custom } ma_resample_algorithm; struct ma_resampler_config @@ -5415,7 +6076,7 @@ struct ma_resampler_config void* pBackendUserData; struct { - ma_uint32 lpfOrder; + ma_uint32 lpfOrder; /* Must be a multiple of 2. */ } linear; }; @@ -5459,21 +6120,12 @@ Converts the given input data. Both the input and output frames must be in the format specified in the config when the resampler was initialized. On input, [pFrameCountOut] contains the number of output frames to process. On output it contains the number of output frames that -were actually processed, which may be less than the requested amount which will happen if there's not enough input data. You can use -ma_resampler_get_expected_output_frame_count() to know how many output frames will be processed for a given number of input frames. +were actually processed, which may be less than the requested amount which will happen if there's not enough input data. On input, [pFrameCountIn] contains the number of input frames contained in [pFramesIn]. On output it contains the number of whole -input frames that were actually processed. You can use ma_resampler_get_required_input_frame_count() to know how many input frames -you should provide for a given number of output frames. [pFramesIn] can be NULL, in which case zeroes will be used instead. - -If [pFramesOut] is NULL, a seek is performed. In this case, if [pFrameCountOut] is not NULL it will seek by the specified number of -output frames. Otherwise, if [pFramesCountOut] is NULL and [pFrameCountIn] is not NULL, it will seek by the specified number of input -frames. When seeking, [pFramesIn] is allowed to NULL, in which case the internal timing state will be updated, but no input will be -processed. In this case, any internal filter state will be updated as if zeroes were passed in. +input frames that were actually processed. -It is an error for [pFramesOut] to be non-NULL and [pFrameCountOut] to be NULL. - -It is an error for both [pFrameCountOut] and [pFrameCountIn] to be NULL. +No arguments are allowed to be NULL. */ MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); @@ -5500,21 +6152,6 @@ Retrieves the latency introduced by the resampler in output frames. */ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler); -/* -Calculates the number of whole input frames that would need to be read from the client in order to output the specified -number of output frames. - -The returned value does not include cached input frames. It only returns the number of extra frames that would need to be -read from the input buffer in order to output the specified number of output frames. -*/ -MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); - -/* -Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of -input frames. -*/ -MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); - /* Resets the resampler's timer and clears its internal cache. */ @@ -5560,6 +6197,10 @@ MA_API ma_channel_converter_config ma_channel_converter_config_init(ma_format fo typedef struct { + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; + ma_format format; ma_uint32 channelsIn; ma_uint32 channelsOut; @@ -5574,9 +6215,13 @@ typedef struct ma_int32** s16; } weights; /* [in][out] */ - /* Memory management. */ - void* _pHeap; - ma_bool32 _ownsHeap; + /* + In order to avoid micro heap allocations for the channel map when using a small channel count, + we will have a small local array here for the in and out channel maps. If the channel count + exceeds the capacity of these channel maps it'll fall back to the heap. + */ + ma_channel _smallChannelMapIn[2]; + ma_channel _smallChannelMapOut[2]; } ma_channel_converter; MA_API ma_result ma_channel_converter_get_heap_size(const ma_channel_converter_config* pConfig, size_t* pHeapSizeInBytes); @@ -5657,8 +6302,6 @@ MA_API ma_result ma_data_converter_set_rate(ma_data_converter* pConverter, ma_ui MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, float ratioInOut); MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter); MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* pConverter); -MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); -MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_converter_reset(ma_data_converter* pConverter); @@ -5835,24 +6478,45 @@ Data Source ************************************************************************************************************************************************************/ typedef void ma_data_source; -#define MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT 0x00000001 +#define MA_DATA_SOURCE_GET_DATA_FORMAT 1 /* ma_data_source_data_format. This should be considered mandatory. */ +#define MA_DATA_SOURCE_GET_CHANNEL_MAP 2 /* ma_channel[MA_MAX_CHANNELS]. Implementations need only consider up to the channel count, but should not exceed MA_MAX_CHANNELS. */ +#define MA_DATA_SOURCE_GET_CURSOR 3 /* ma_uint64 */ +#define MA_DATA_SOURCE_GET_LENGTH 4 /* ma_uint64 */ +#define MA_DATA_SOURCE_SET_LOOPING 5 /* ma_bool32 */ +#define MA_DATA_SOURCE_GET_LOOPING 6 /* ma_bool32 */ +#define MA_DATA_SOURCE_SET_RANGE 7 /* ma_pcm_range */ +#define MA_DATA_SOURCE_GET_RANGE 8 /* ma_pcm_range */ +#define MA_DATA_SOURCE_SET_LOOP_POINT 9 /* ma_pcm_range */ +#define MA_DATA_SOURCE_GET_LOOP_POINT 10 /* ma_pcm_range */ + +typedef struct ma_data_source_data_format +{ + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; +} ma_data_source_data_format; + +typedef struct ma_pcm_range +{ + ma_uint64 begInFrames; + ma_uint64 endInFrames; +} ma_pcm_range; typedef struct { - ma_result (* onRead)(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); - ma_result (* onSeek)(ma_data_source* pDataSource, ma_uint64 frameIndex); - ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); - ma_result (* onGetCursor)(ma_data_source* pDataSource, ma_uint64* pCursor); - ma_result (* onGetLength)(ma_data_source* pDataSource, ma_uint64* pLength); - ma_result (* onSetLooping)(ma_data_source* pDataSource, ma_bool32 isLooping); - ma_uint32 flags; + size_t (* onSizeof)(void); /* Should return the size of the the data source implementation's struct. For example, ma_decoder would return sizeof(ma_decoder). */ + void (* onUninit)(ma_data_source* pDataSource); + ma_result (* onCopy )(ma_data_source* pDataSource, ma_data_source* pNewDataSource); /* Optional. Initializes a fully independent copy of a data source. This can be set to NULL, or return MA_NOT_IMPLEMENTED if copying is not supported. */ + ma_result (* onRead )(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); + ma_result (* onSeek )(ma_data_source* pDataSource, ma_uint64 frameIndex); + ma_result (* onProp )(ma_data_source* pDataSource, int prop, void* pData); } ma_data_source_vtable; typedef ma_data_source* (* ma_data_source_get_next_proc)(ma_data_source* pDataSource); typedef struct { - const ma_data_source_vtable* vtable; + const ma_data_source_vtable* pVTable; } ma_data_source_config; MA_API ma_data_source_config ma_data_source_config_init(void); @@ -5860,7 +6524,7 @@ MA_API ma_data_source_config ma_data_source_config_init(void); typedef struct { - const ma_data_source_vtable* vtable; + const ma_data_source_vtable* pVTable; ma_uint64 rangeBegInFrames; ma_uint64 rangeEndInFrames; /* Set to -1 for unranged (default). */ ma_uint64 loopBegInFrames; /* Relative to rangeBegInFrames. */ @@ -5871,14 +6535,17 @@ typedef struct MA_ATOMIC(4, ma_bool32) isLooping; } ma_data_source_base; -MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_data_source* pDataSource); -MA_API void ma_data_source_uninit(ma_data_source* pDataSource); +MA_API ma_result ma_data_source_base_init(const ma_data_source_config* pConfig, ma_data_source* pDataSource); +MA_API void ma_data_source_base_uninit(ma_data_source* pDataSource); +MA_API const ma_data_source_vtable* ma_data_source_get_vtable(const ma_data_source* pDataSource); +MA_API ma_result ma_data_source_init_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource); MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked); /* Can only seek forward. Abstraction to ma_data_source_seek_pcm_frames() */ MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, float seekPointInSeconds); /* Abstraction to ma_data_source_seek_to_pcm_frame() */ -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); +MA_API ma_result ma_data_source_get_channel_map(ma_data_source* pDataSource, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ MA_API ma_result ma_data_source_get_cursor_in_seconds(ma_data_source* pDataSource, float* pCursor); @@ -5897,57 +6564,40 @@ MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, m MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(const ma_data_source* pDataSource); + typedef struct { - ma_data_source_base ds; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; - ma_uint64 cursor; ma_uint64 sizeInFrames; - const void* pData; -} ma_audio_buffer_ref; - -MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, const void* pData, ma_uint64 sizeInFrames, ma_audio_buffer_ref* pAudioBufferRef); -MA_API void ma_audio_buffer_ref_uninit(ma_audio_buffer_ref* pAudioBufferRef); -MA_API ma_result ma_audio_buffer_ref_set_data(ma_audio_buffer_ref* pAudioBufferRef, const void* pData, ma_uint64 sizeInFrames); -MA_API ma_uint64 ma_audio_buffer_ref_read_pcm_frames(ma_audio_buffer_ref* pAudioBufferRef, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop); -MA_API ma_result ma_audio_buffer_ref_seek_to_pcm_frame(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameIndex); -MA_API ma_result ma_audio_buffer_ref_map(ma_audio_buffer_ref* pAudioBufferRef, void** ppFramesOut, ma_uint64* pFrameCount); -MA_API ma_result ma_audio_buffer_ref_unmap(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameCount); /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ -MA_API ma_bool32 ma_audio_buffer_ref_at_end(const ma_audio_buffer_ref* pAudioBufferRef); -MA_API ma_result ma_audio_buffer_ref_get_cursor_in_pcm_frames(const ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pCursor); -MA_API ma_result ma_audio_buffer_ref_get_length_in_pcm_frames(const ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pLength); -MA_API ma_result ma_audio_buffer_ref_get_available_frames(const ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pAvailableFrames); - + const void* pData; /* If set to NULL, will allocate a block of memory for you. */ + const ma_allocation_callbacks* pAllocationCallbacks; +} ma_audio_buffer_config; +MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint64 sizeInFrames, const void* pData, const ma_allocation_callbacks* pAllocationCallbacks); typedef struct { + ma_data_source_base ds; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; + ma_uint64 cursor; ma_uint64 sizeInFrames; - const void* pData; /* If set to NULL, will allocate a block of memory for you. */ - ma_allocation_callbacks allocationCallbacks; -} ma_audio_buffer_config; - -MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint64 sizeInFrames, const void* pData, const ma_allocation_callbacks* pAllocationCallbacks); - -typedef struct -{ - ma_audio_buffer_ref ref; + const void* pData; ma_allocation_callbacks allocationCallbacks; ma_bool32 ownsData; /* Used to control whether or not miniaudio owns the data buffer. If set to true, pData will be freed in ma_audio_buffer_uninit(). */ ma_uint8 _pExtraData[1]; /* For allocating a buffer with the memory located directly after the other memory of the structure. */ } ma_audio_buffer; MA_API ma_result ma_audio_buffer_init(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer); -MA_API ma_result ma_audio_buffer_init_copy(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer); +MA_API ma_result ma_audio_buffer_init_and_copy_data(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer); MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pConfig, ma_audio_buffer** ppAudioBuffer); /* Always copies the data. Doesn't make sense to use this otherwise. Use ma_audio_buffer_uninit_and_free() to uninit. */ +MA_API ma_result ma_audio_buffer_init_copy(ma_audio_buffer* pAudioBuffer, ma_audio_buffer* pNewAudioBuffer); MA_API void ma_audio_buffer_uninit(ma_audio_buffer* pAudioBuffer); MA_API void ma_audio_buffer_uninit_and_free(ma_audio_buffer* pAudioBuffer); -MA_API ma_uint64 ma_audio_buffer_read_pcm_frames(ma_audio_buffer* pAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop); +MA_API ma_result ma_audio_buffer_read_pcm_frames(ma_audio_buffer* pAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_audio_buffer_seek_to_pcm_frame(ma_audio_buffer* pAudioBuffer, ma_uint64 frameIndex); MA_API ma_result ma_audio_buffer_map(ma_audio_buffer* pAudioBuffer, void** ppFramesOut, ma_uint64* pFrameCount); MA_API ma_result ma_audio_buffer_unmap(ma_audio_buffer* pAudioBuffer, ma_uint64 frameCount); /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ @@ -6026,68 +6676,71 @@ MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_b Ring Buffer ************************************************************************************************************************************************************/ -typedef struct +/* BEG ma_ring_buffer.h */ +#define MA_RING_BUFFER_FLAG_MIRRORED (1 << 0) /* When set the buffer is mirrored at capacity * stride. */ + +typedef struct ma_ring_buffer { - void* pBuffer; - ma_uint32 subbufferSizeInBytes; - ma_uint32 subbufferCount; - ma_uint32 subbufferStrideInBytes; - MA_ATOMIC(4, ma_uint32) encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - MA_ATOMIC(4, ma_uint32) encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ - ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ - ma_allocation_callbacks allocationCallbacks; -} ma_rb; - -MA_API ma_result ma_rb_init_ex(size_t subbufferSizeInBytes, size_t subbufferCount, size_t subbufferStrideInBytes, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_rb* pRB); -MA_API ma_result ma_rb_init(size_t bufferSizeInBytes, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_rb* pRB); -MA_API void ma_rb_uninit(ma_rb* pRB); -MA_API void ma_rb_reset(ma_rb* pRB); -MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes); -MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes); -MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes); -MA_API ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes); -MA_API ma_int32 ma_rb_pointer_distance(ma_rb* pRB); /* Returns the distance between the write pointer and the read pointer. Should never be negative for a correct program. Will return the number of bytes that can be read before the read pointer hits the write pointer. */ -MA_API ma_uint32 ma_rb_available_read(ma_rb* pRB); -MA_API ma_uint32 ma_rb_available_write(ma_rb* pRB); -MA_API size_t ma_rb_get_subbuffer_size(ma_rb* pRB); -MA_API size_t ma_rb_get_subbuffer_stride(ma_rb* pRB); -MA_API size_t ma_rb_get_subbuffer_offset(ma_rb* pRB, size_t subbufferIndex); -MA_API void* ma_rb_get_subbuffer_ptr(ma_rb* pRB, size_t subbufferIndex, void* pBuffer); + ma_uint32 head; /* Atomic. Most significant bit is a loop flag. When the head is equal to the tail (including the flag) it means the buffer is empty. If the only difference is the flag, it means the buffer is full. */ + #if MA_CACHE_LINE_SIZE > 4 + ma_uint8 pad0[MA_CACHE_LINE_SIZE - 4]; /* The head will be modified by the producer, but untouched by the consumer. The reverse is true for the tail. The idea here is to ensure the head and tail are on their own cache lines thereby improving cache coherency. */ + #endif + ma_uint32 tail; /* Atomic. Most significant bit is a loop flag. */ + #if MA_CACHE_LINE_SIZE > 4 + ma_uint8 pad1[MA_CACHE_LINE_SIZE - 4]; + #endif + ma_uint32 capacity; /* Capacity of the buffer, in elements. */ + ma_uint32 stride; /* Size of an element in bytes. */ + ma_uint32 flags; + void* pBuffer; /* Must be twice the size of capacity * stride. */ +} ma_ring_buffer; +MA_API void ma_ring_buffer_init(ma_uint32 capacity, ma_uint32 stride, ma_uint32 flags, void* pBuffer, ma_ring_buffer* pRingBuffer); /* Buffer must be `2 * capacity * stride`. That is twice the capacity. You can use a mirrored buffer, in which case specify the MA_RING_BUFFER_FLAG_MIRRORED flag. */ +MA_API ma_uint32 ma_ring_buffer_map_produce(ma_ring_buffer* pRingBuffer, ma_uint32 count, void** ppMappedBuffer); /* Returns the number of elements actually mapped. */ +MA_API void ma_ring_buffer_unmap_produce(ma_ring_buffer* pRingBuffer, ma_uint32 count); +MA_API ma_uint32 ma_ring_buffer_map_consume(ma_ring_buffer* pRingBuffer, ma_uint32 count, void** ppMappedBuffer); /* Returns the number of elements actually mapped. */ +MA_API void ma_ring_buffer_unmap_consume(ma_ring_buffer* pRingBuffer, ma_uint32 count); +MA_API ma_uint32 ma_ring_buffer_length(const ma_ring_buffer* pRingBuffer); /* Returns the number of elements currently in the ring buffer. Should only be called from the producer or consumer thread. If something is in the middle of producing or consuming data on the ring buffer than the returned value may already be out of date. */ +MA_API ma_uint32 ma_ring_buffer_capacity(const ma_ring_buffer* pRingBuffer); +/* END ma_ring_buffer.h */ -typedef struct + +typedef struct ma_audio_ring_buffer_config +{ + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; /* Can be zero. Not used for the actual function of the ring buffer. Stored for the purpose of the data source abstraction. */ + ma_uint32 sizeInFrames; /* The capacity of the ring buffer in frames. */ + ma_uint32 flags; + void* pBuffer; /* Can be null in which case it'll be allocated internally. */ + const ma_allocation_callbacks* pAllocationCallbacks; +} ma_audio_ring_buffer_config; + +MA_API ma_audio_ring_buffer_config ma_audio_ring_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames); + + +typedef struct ma_audio_ring_buffer { ma_data_source_base ds; - ma_rb rb; + ma_ring_buffer rb; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; /* Not required for the ring buffer itself, but useful for associating the data with some sample rate, particularly for data sources. */ -} ma_pcm_rb; - -MA_API ma_result ma_pcm_rb_init_ex(ma_format format, ma_uint32 channels, ma_uint32 subbufferSizeInFrames, ma_uint32 subbufferCount, ma_uint32 subbufferStrideInFrames, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_pcm_rb* pRB); -MA_API ma_result ma_pcm_rb_init(ma_format format, ma_uint32 channels, ma_uint32 bufferSizeInFrames, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_pcm_rb* pRB); -MA_API void ma_pcm_rb_uninit(ma_pcm_rb* pRB); -MA_API void ma_pcm_rb_reset(ma_pcm_rb* pRB); -MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); -MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); -MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); -MA_API ma_result ma_pcm_rb_seek_write(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); -MA_API ma_int32 ma_pcm_rb_pointer_distance(ma_pcm_rb* pRB); /* Return value is in frames. */ -MA_API ma_uint32 ma_pcm_rb_available_read(ma_pcm_rb* pRB); -MA_API ma_uint32 ma_pcm_rb_available_write(ma_pcm_rb* pRB); -MA_API ma_uint32 ma_pcm_rb_get_subbuffer_size(ma_pcm_rb* pRB); -MA_API ma_uint32 ma_pcm_rb_get_subbuffer_stride(ma_pcm_rb* pRB); -MA_API ma_uint32 ma_pcm_rb_get_subbuffer_offset(ma_pcm_rb* pRB, ma_uint32 subbufferIndex); -MA_API void* ma_pcm_rb_get_subbuffer_ptr(ma_pcm_rb* pRB, ma_uint32 subbufferIndex, void* pBuffer); -MA_API ma_format ma_pcm_rb_get_format(const ma_pcm_rb* pRB); -MA_API ma_uint32 ma_pcm_rb_get_channels(const ma_pcm_rb* pRB); -MA_API ma_uint32 ma_pcm_rb_get_sample_rate(const ma_pcm_rb* pRB); -MA_API void ma_pcm_rb_set_sample_rate(ma_pcm_rb* pRB, ma_uint32 sampleRate); + ma_bool32 isOwnerOfBuffer; + void* pBuffer; + ma_allocation_callbacks allocationCallbacks; +} ma_audio_ring_buffer; + +MA_API ma_result ma_audio_ring_buffer_init(const ma_audio_ring_buffer_config* pConfig, ma_audio_ring_buffer* pRingBuffer); +MA_API ma_result ma_audio_ring_buffer_init_copy(ma_audio_ring_buffer* pRingBuffer, ma_audio_ring_buffer* pNewRingBuffer); +MA_API void ma_audio_ring_buffer_uninit(ma_audio_ring_buffer* pRingBuffer); +MA_API ma_uint32 ma_audio_ring_buffer_map_produce(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount, void** ppMappedBuffer); /* Returns the number of frames actually mapped. */ +MA_API void ma_audio_ring_buffer_unmap_produce(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount); +MA_API ma_uint32 ma_audio_ring_buffer_map_consume(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount, void** ppMappedBuffer); /* Returns the number of frames actually mapped. */ +MA_API void ma_audio_ring_buffer_unmap_consume(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount); +MA_API ma_result ma_audio_ring_buffer_write_pcm_frames(ma_audio_ring_buffer* pRingBuffer, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten); +MA_API ma_result ma_audio_ring_buffer_read_pcm_frames(ma_audio_ring_buffer* pRingBuffer, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead); +MA_API ma_result ma_audio_ring_buffer_get_length_in_pcm_frames(ma_audio_ring_buffer* pRingBuffer, ma_uint32* pLength); /* @@ -6102,7 +6755,7 @@ in frames. The internal sample rate of the capture device is also needed in orde */ typedef struct { - ma_pcm_rb rb; + ma_audio_ring_buffer rb; } ma_duplex_rb; MA_API ma_result ma_duplex_rb_init(ma_format captureFormat, ma_uint32 captureChannels, ma_uint32 sampleRate, ma_uint32 captureInternalSampleRate, ma_uint32 captureInternalPeriodSizeInFrames, const ma_allocation_callbacks* pAllocationCallbacks, ma_duplex_rb* pRB); @@ -6119,6 +6772,9 @@ Retrieves a human readable description of the given result code. */ MA_API const char* ma_result_description(ma_result result); +MA_API ma_allocation_callbacks ma_allocation_callbacks_init_default(void); +MA_API ma_allocation_callbacks ma_allocation_callbacks_init_copy(const ma_allocation_callbacks* pSrc); + /* malloc() */ @@ -6290,6 +6946,7 @@ typedef void ma_async_notification; typedef struct { void (* onSignal)(ma_async_notification* pNotification); + void* pUserData; } ma_async_notification_callbacks; MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification); @@ -6498,8 +7155,6 @@ struct ma_job struct { /*ma_resource_manager_data_stream**/ void* pDataStream; - char* pFilePath; /* Allocated when the job is posted, freed by the job thread after loading. */ - wchar_t* pFilePathW; /* ^ As above ^. Only used if pFilePath is NULL. */ ma_uint64 initialSeekPoint; ma_async_notification* pInitNotification; /* Signalled after the first two pages have been decoded and frames can be read from the stream. */ ma_fence* pInitFence; @@ -6521,19 +7176,6 @@ struct ma_job ma_uint64 frameIndex; } seekDataStream; } resourceManager; - - /* Device. */ - union - { - union - { - struct - { - /*ma_device**/ void* pDevice; - /*ma_device_type*/ ma_uint32 deviceType; - } reroute; - } aaudio; - } device; } data; }; @@ -6590,6 +7232,16 @@ MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob); /* Retur +typedef union +{ + ma_int64 counter; + double counterD; +} ma_timer; + +MA_API void ma_timer_init(ma_timer* pTimer); +MA_API double ma_timer_get_time_in_seconds(ma_timer* pTimer); + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -6601,336 +7253,102 @@ This section contains the APIs for device playback and capture. Here is where yo ************************************************************************************************************************************************************* ************************************************************************************************************************************************************/ #ifndef MA_NO_DEVICE_IO -/* Some backends are only supported on certain platforms. */ -#if defined(MA_WIN32) && !defined(MA_XBOX) - #define MA_SUPPORT_WASAPI - #if defined(MA_WIN32_DESKTOP) /* DirectSound and WinMM backends are only supported on desktops. */ - #define MA_SUPPORT_DSOUND - #define MA_SUPPORT_WINMM - #define MA_SUPPORT_JACK /* JACK is technically supported on Windows, but I don't know how many people use it in practice... */ - #endif -#endif -#if defined(MA_UNIX) && !defined(MA_ORBIS) && !defined(MA_PROSPERO) - #if defined(MA_LINUX) - #if !defined(MA_ANDROID) && !defined(MA_EMSCRIPTEN) /* ALSA is not supported on Android. */ - #define MA_SUPPORT_ALSA - #endif - #endif - #if !defined(MA_BSD) && !defined(MA_ANDROID) && !defined(MA_EMSCRIPTEN) - #define MA_SUPPORT_PULSEAUDIO - #define MA_SUPPORT_JACK - #endif - #if defined(__OpenBSD__) /* <-- Change this to "#if defined(MA_BSD)" to enable sndio on all BSD flavors. */ - #define MA_SUPPORT_SNDIO /* sndio is only supported on OpenBSD for now. May be expanded later if there's demand. */ - #endif - #if defined(__NetBSD__) || defined(__OpenBSD__) - #define MA_SUPPORT_AUDIO4 /* Only support audio(4) on platforms with known support. */ - #endif - #if defined(__FreeBSD__) || defined(__DragonFly__) - #define MA_SUPPORT_OSS /* Only support OSS on specific platforms with known support. */ - #endif -#endif -#if defined(MA_ANDROID) - #define MA_SUPPORT_AAUDIO - #define MA_SUPPORT_OPENSL -#endif -#if defined(MA_APPLE) - #define MA_SUPPORT_COREAUDIO -#endif -#if defined(MA_EMSCRIPTEN) - #define MA_SUPPORT_WEBAUDIO -#endif - -/* All platforms should support custom backends. */ -#define MA_SUPPORT_CUSTOM +typedef struct ma_context ma_context; +typedef struct ma_context_config ma_context_config; +typedef struct ma_device ma_device; +typedef struct ma_device_config ma_device_config; +typedef struct ma_device_descriptor ma_device_descriptor; +typedef struct ma_device_backend_vtable ma_device_backend_vtable; -/* Explicitly disable the Null backend for Emscripten because it uses a background thread which is not properly supported right now. */ -#if !defined(MA_EMSCRIPTEN) -#define MA_SUPPORT_NULL -#endif +/************************************************************************************************************************************************************ +BACKENDS -#if defined(MA_SUPPORT_WASAPI) && !defined(MA_NO_WASAPI) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WASAPI)) - #define MA_HAS_WASAPI -#endif -#if defined(MA_SUPPORT_DSOUND) && !defined(MA_NO_DSOUND) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_DSOUND)) - #define MA_HAS_DSOUND -#endif -#if defined(MA_SUPPORT_WINMM) && !defined(MA_NO_WINMM) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WINMM)) - #define MA_HAS_WINMM -#endif -#if defined(MA_SUPPORT_ALSA) && !defined(MA_NO_ALSA) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_ALSA)) - #define MA_HAS_ALSA -#endif -#if defined(MA_SUPPORT_PULSEAUDIO) && !defined(MA_NO_PULSEAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_PULSEAUDIO)) - #define MA_HAS_PULSEAUDIO -#endif -#if defined(MA_SUPPORT_JACK) && !defined(MA_NO_JACK) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_JACK)) - #define MA_HAS_JACK -#endif -#if defined(MA_SUPPORT_COREAUDIO) && !defined(MA_NO_COREAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_COREAUDIO)) - #define MA_HAS_COREAUDIO -#endif -#if defined(MA_SUPPORT_SNDIO) && !defined(MA_NO_SNDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SNDIO)) - #define MA_HAS_SNDIO -#endif -#if defined(MA_SUPPORT_AUDIO4) && !defined(MA_NO_AUDIO4) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_AUDIO4)) - #define MA_HAS_AUDIO4 -#endif -#if defined(MA_SUPPORT_OSS) && !defined(MA_NO_OSS) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_OSS)) - #define MA_HAS_OSS -#endif -#if defined(MA_SUPPORT_AAUDIO) && !defined(MA_NO_AAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_AAUDIO)) - #define MA_HAS_AAUDIO -#endif -#if defined(MA_SUPPORT_OPENSL) && !defined(MA_NO_OPENSL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_OPENSL)) - #define MA_HAS_OPENSL -#endif -#if defined(MA_SUPPORT_WEBAUDIO) && !defined(MA_NO_WEBAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WEBAUDIO)) - #define MA_HAS_WEBAUDIO -#endif -#if defined(MA_SUPPORT_CUSTOM) && !defined(MA_NO_CUSTOM) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_CUSTOM)) - #define MA_HAS_CUSTOM -#endif -#if defined(MA_SUPPORT_NULL) && !defined(MA_NO_NULL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_NULL)) - #define MA_HAS_NULL -#endif +************************************************************************************************************************************************************/ +/* BEG miniaudio_wasapi.h */ +/* WASAPI audio thread priority characteristics. */ typedef enum { - ma_device_state_uninitialized = 0, - ma_device_state_stopped = 1, /* The device's default state after initialization. */ - ma_device_state_started = 2, /* The device is started and is requesting and/or delivering audio data. */ - ma_device_state_starting = 3, /* Transitioning from a stopped state to started. */ - ma_device_state_stopping = 4 /* Transitioning from a started state to stopped. */ -} ma_device_state; - -MA_ATOMIC_SAFE_TYPE_DECL(i32, 4, device_state) - + ma_wasapi_usage_default = 0, + ma_wasapi_usage_games, + ma_wasapi_usage_pro_audio +} ma_wasapi_usage; -#ifdef MA_SUPPORT_WASAPI -/* We need a IMMNotificationClient object for WASAPI. */ -typedef struct -{ - void* lpVtbl; - ma_uint32 counter; - ma_device* pDevice; -} ma_IMMNotificationClient; -#endif -/* Backend enums must be in priority order. */ -typedef enum +typedef struct ma_context_config_wasapi { - ma_backend_wasapi, - ma_backend_dsound, - ma_backend_winmm, - ma_backend_coreaudio, - ma_backend_sndio, - ma_backend_audio4, - ma_backend_oss, - ma_backend_pulseaudio, - ma_backend_alsa, - ma_backend_jack, - ma_backend_aaudio, - ma_backend_opensl, - ma_backend_webaudio, - ma_backend_custom, /* <-- Custom backend, with callbacks defined by the context config. */ - ma_backend_null /* <-- Must always be the last item. Lowest priority, and used as the terminator for backend enumeration. */ -} ma_backend; - -#define MA_BACKEND_COUNT (ma_backend_null+1) + int _unused; +} ma_context_config_wasapi; +MA_API ma_context_config_wasapi ma_context_config_wasapi_init(void); -/* -Device job thread. This is used by backends that require asynchronous processing of certain -operations. It is not used by all backends. -The device job thread is made up of a thread and a job queue. You can post a job to the thread with -ma_device_job_thread_post(). The thread will do the processing of the job. -*/ -typedef struct +typedef struct ma_device_config_wasapi { - ma_bool32 noThread; /* Set this to true if you want to process jobs yourself. */ - ma_uint32 jobQueueCapacity; - ma_uint32 jobQueueFlags; -} ma_device_job_thread_config; + ma_wasapi_usage usage; /* When configured, uses Avrt APIs to set the thread characteristics. */ + ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ + ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ + ma_bool8 noAutoStreamRouting; /* Disables automatic stream routing. */ + ma_bool8 noHardwareOffloading; /* Disables WASAPI's hardware offloading feature. */ + ma_uint32 loopbackProcessID; /* The process ID to include or exclude for loopback mode. Set to 0 to capture audio from all processes. Ignored when an explicit device ID is specified. */ + ma_bool8 loopbackProcessExclude; /* When set to true, excludes the process specified by loopbackProcessID. By default, the process will be included. */ +} ma_device_config_wasapi; -MA_API ma_device_job_thread_config ma_device_job_thread_config_init(void); +MA_API ma_device_config_wasapi ma_device_config_wasapi_init(void); -typedef struct -{ - ma_thread thread; - ma_job_queue jobQueue; - ma_bool32 _hasThread; -} ma_device_job_thread; - -MA_API ma_result ma_device_job_thread_init(const ma_device_job_thread_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_job_thread* pJobThread); -MA_API void ma_device_job_thread_uninit(ma_device_job_thread* pJobThread, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API ma_result ma_device_job_thread_post(ma_device_job_thread* pJobThread, const ma_job* pJob); -MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_job* pJob); +extern ma_device_backend_vtable* ma_device_backend_wasapi; +MA_API ma_device_backend_vtable* ma_wasapi_get_vtable(void); +/* END miniaudio_wasapi.h */ -/* Device notification types. */ -typedef enum +/* BEG miniaudio_dsound.h */ +typedef struct ma_context_config_dsound { - ma_device_notification_type_started, - ma_device_notification_type_stopped, - ma_device_notification_type_rerouted, - ma_device_notification_type_interruption_began, - ma_device_notification_type_interruption_ended, - ma_device_notification_type_unlocked -} ma_device_notification_type; - -typedef struct -{ - ma_device* pDevice; - ma_device_notification_type type; - union - { - struct - { - int _unused; - } started; - struct - { - int _unused; - } stopped; - struct - { - int _unused; - } rerouted; - struct - { - int _unused; - } interruption; - } data; -} ma_device_notification; - -/* -The notification callback for when the application should be notified of a change to the device. - -This callback is used for notifying the application of changes such as when the device has started, -stopped, rerouted or an interruption has occurred. Note that not all backends will post all -notification types. For example, some backends will perform automatic stream routing without any -kind of notification to the host program which means miniaudio will never know about it and will -never be able to fire the rerouted notification. You should keep this in mind when designing your -program. - -The stopped notification will *not* get fired when a device is rerouted. - - -Parameters ----------- -pNotification (in) - A pointer to a structure containing information about the event. Use the `pDevice` member of - this object to retrieve the relevant device. The `type` member can be used to discriminate - against each of the notification types. - - -Remarks -------- -Do not restart or uninitialize the device from the callback. - -Not all notifications will be triggered by all backends, however the started and stopped events -should be reliable for all backends. Some backends do not have a good way to detect device -stoppages due to unplugging the device which may result in the stopped callback not getting -fired. This has been observed with at least one BSD variant. - -The rerouted notification is fired *after* the reroute has occurred. The stopped notification will -*not* get fired when a device is rerouted. The following backends are known to do automatic stream -rerouting, but do not have a way to be notified of the change: - - * DirectSound - -The interruption notifications are used on mobile platforms for detecting when audio is interrupted -due to things like an incoming phone call. Currently this is only implemented on iOS. None of the -Android backends will report this notification. -*/ -typedef void (* ma_device_notification_proc)(const ma_device_notification* pNotification); - - -/* -The callback for processing audio data from the device. - -The data callback is fired by miniaudio whenever the device needs to have more data delivered to a playback device, or when a capture device has some data -available. This is called as soon as the backend asks for more data which means it may be called with inconsistent frame counts. You cannot assume the -callback will be fired with a consistent frame count. - - -Parameters ----------- -pDevice (in) - A pointer to the relevant device. + ma_handle hWnd; /* HWND. Optional window handle to pass into SetCooperativeLevel(). Will default to the foreground window, and if that fails, the desktop window. */ +} ma_context_config_dsound; -pOutput (out) - A pointer to the output buffer that will receive audio data that will later be played back through the speakers. This will be non-null for a playback or - full-duplex device and null for a capture and loopback device. +MA_API ma_context_config_dsound ma_context_config_dsound_init(void); -pInput (in) - A pointer to the buffer containing input data from a recording device. This will be non-null for a capture, full-duplex or loopback device and null for a - playback device. -frameCount (in) - The number of PCM frames to process. Note that this will not necessarily be equal to what you requested when you initialized the device. The - `periodSizeInFrames` and `periodSizeInMilliseconds` members of the device config are just hints, and are not necessarily exactly what you'll get. You must - not assume this will always be the same value each time the callback is fired. - - -Remarks -------- -You cannot stop and start the device from inside the callback or else you'll get a deadlock. You must also not uninitialize the device from inside the -callback. The following APIs cannot be called from inside the callback: - - ma_device_init() - ma_device_init_ex() - ma_device_uninit() - ma_device_start() - ma_device_stop() - -The proper way to stop the device is to call `ma_device_stop()` from a different thread, normally the main application thread. -*/ -typedef void (* ma_device_data_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); +typedef struct ma_device_config_dsound +{ + int _unused; +} ma_device_config_dsound; +MA_API ma_device_config_dsound ma_device_config_dsound_init(void); +extern ma_device_backend_vtable* ma_device_backend_dsound; +MA_API ma_device_backend_vtable* ma_dsound_get_vtable(void); +/* END miniaudio_dsound.h */ -/* -DEPRECATED. Use ma_device_notification_proc instead. -The callback for when the device has been stopped. +/* BEG miniaudio_winmm.h */ +typedef struct ma_context_config_winmm +{ + int _unused; +} ma_context_config_winmm; -This will be called when the device is stopped explicitly with `ma_device_stop()` and also called implicitly when the device is stopped through external forces -such as being unplugged or an internal error occurring. +MA_API ma_context_config_winmm ma_context_config_winmm_init(void); -Parameters ----------- -pDevice (in) - A pointer to the device that has just stopped. +typedef struct ma_device_config_winmm +{ + int _unused; +} ma_device_config_winmm; +MA_API ma_device_config_winmm ma_device_config_winmm_init(void); -Remarks -------- -Do not restart or uninitialize the device from the callback. -*/ -typedef void (* ma_stop_proc)(ma_device* pDevice); /* DEPRECATED. Use ma_device_notification_proc instead. */ -typedef enum -{ - ma_device_type_playback = 1, - ma_device_type_capture = 2, - ma_device_type_duplex = ma_device_type_playback | ma_device_type_capture, /* 3 */ - ma_device_type_loopback = 4 -} ma_device_type; +extern ma_device_backend_vtable* ma_device_backend_winmm; +MA_API ma_device_backend_vtable* ma_winmm_get_vtable(void); +/* END miniaudio_winmm.h */ -typedef enum -{ - ma_share_mode_shared = 0, - ma_share_mode_exclusive -} ma_share_mode; +/* BEG miniaudio_coreaudio.h */ /* iOS/tvOS/watchOS session categories. */ typedef enum { @@ -6953,40 +7371,200 @@ typedef enum ma_ios_session_category_option_default_to_speaker = 0x08, /* AVAudioSessionCategoryOptionDefaultToSpeaker */ ma_ios_session_category_option_interrupt_spoken_audio_and_mix_with_others = 0x11, /* AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers */ ma_ios_session_category_option_allow_bluetooth_a2dp = 0x20, /* AVAudioSessionCategoryOptionAllowBluetoothA2DP */ - ma_ios_session_category_option_allow_air_play = 0x40, /* AVAudioSessionCategoryOptionAllowAirPlay */ + ma_ios_session_category_option_allow_air_play = 0x40 /* AVAudioSessionCategoryOptionAllowAirPlay */ } ma_ios_session_category_option; -/* OpenSL stream types. */ -typedef enum + +typedef struct ma_context_config_coreaudio { - ma_opensl_stream_type_default = 0, /* Leaves the stream type unset. */ - ma_opensl_stream_type_voice, /* SL_ANDROID_STREAM_VOICE */ - ma_opensl_stream_type_system, /* SL_ANDROID_STREAM_SYSTEM */ - ma_opensl_stream_type_ring, /* SL_ANDROID_STREAM_RING */ - ma_opensl_stream_type_media, /* SL_ANDROID_STREAM_MEDIA */ - ma_opensl_stream_type_alarm, /* SL_ANDROID_STREAM_ALARM */ - ma_opensl_stream_type_notification /* SL_ANDROID_STREAM_NOTIFICATION */ -} ma_opensl_stream_type; + ma_ios_session_category sessionCategory; + ma_uint32 sessionCategoryOptions; + ma_bool32 noAudioSessionActivate; /* iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. */ + ma_bool32 noAudioSessionDeactivate; /* iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:false] on uninitialization. */ +} ma_context_config_coreaudio; -/* OpenSL recording presets. */ -typedef enum +MA_API ma_context_config_coreaudio ma_context_config_coreaudio_init(void); + + +typedef struct ma_device_config_coreaudio { - ma_opensl_recording_preset_default = 0, /* Leaves the input preset unset. */ - ma_opensl_recording_preset_generic, /* SL_ANDROID_RECORDING_PRESET_GENERIC */ - ma_opensl_recording_preset_camcorder, /* SL_ANDROID_RECORDING_PRESET_CAMCORDER */ - ma_opensl_recording_preset_voice_recognition, /* SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION */ - ma_opensl_recording_preset_voice_communication, /* SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION */ - ma_opensl_recording_preset_voice_unprocessed /* SL_ANDROID_RECORDING_PRESET_UNPROCESSED */ -} ma_opensl_recording_preset; + ma_bool32 allowNominalSampleRateChange; /* Desktop only. When enabled, allows changing of the sample rate at the operating system level. */ +} ma_device_config_coreaudio; -/* WASAPI audio thread priority characteristics. */ -typedef enum +MA_API ma_device_config_coreaudio ma_device_config_coreaudio_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_coreaudio; +MA_API ma_device_backend_vtable* ma_coreaudio_get_vtable(void); +/* END miniaudio_coreaudio.h */ + + +/* BEG miniaudio_pipewire.h */ +typedef struct { - ma_wasapi_usage_default = 0, - ma_wasapi_usage_games, - ma_wasapi_usage_pro_audio, -} ma_wasapi_usage; + int _unused; +} ma_context_config_pipewire; + +MA_API ma_context_config_pipewire ma_context_config_pipewire_init(void); + + +typedef struct +{ + const char* pThreadName; /* If NULL, defaults to "miniaudio-pipewire". */ + const char* pStreamName; /* If NULL, defaults to "miniaudio" */ + const char* pMediaRole; /* If NULL, defaults to "Game". */ +} ma_device_config_pipewire; + +MA_API ma_device_config_pipewire ma_device_config_pipewire_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_pipewire; +MA_API ma_device_backend_vtable* ma_pipewire_get_vtable(void); +/* END miniaudio_pipewire.h */ + + +/* BEG miniaudio_pulseaudio.h */ +typedef struct ma_context_config_pulseaudio +{ + const char* pApplicationName; + const char* pServerName; + ma_bool32 tryAutoSpawn; /* Enables autospawning of the PulseAudio daemon if necessary. */ +} ma_context_config_pulseaudio; + +MA_API ma_context_config_pulseaudio ma_context_config_pulseaudio_init(void); + + +typedef struct ma_device_config_pulseaudio +{ + const char* pStreamNamePlayback; + const char* pStreamNameCapture; + int channelMap; +} ma_device_config_pulseaudio; + +MA_API ma_device_config_pulseaudio ma_device_config_pulseaudio_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_pulseaudio; +MA_API ma_device_backend_vtable* ma_pulseaudio_get_vtable(void); +/* END miniaudio_pulseaudio.h */ + + +/* BEG miniaudio_jack.h */ +typedef struct ma_context_config_jack +{ + const char* pClientName; + ma_bool32 tryStartServer; +} ma_context_config_jack; + +MA_API ma_context_config_jack ma_context_config_jack_init(void); + + +typedef struct ma_device_config_jack +{ + ma_bool32 noAutoConnect; +} ma_device_config_jack; + +MA_API ma_device_config_jack ma_device_config_jack_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_jack; +MA_API ma_device_backend_vtable* ma_jack_get_vtable(void); +/* END miniaudio_jack.h */ + + +/* BEG miniaudio_alsa.h */ +typedef struct ma_context_config_alsa +{ + int _unused; +} ma_context_config_alsa; + +MA_API ma_context_config_alsa ma_context_config_alsa_init(void); + +typedef struct ma_device_config_alsa +{ + ma_bool32 noMMap; /* Disables MMap mode. */ + ma_bool32 noAutoFormat; /* Opens the ALSA device with SND_PCM_NO_AUTO_FORMAT. */ + ma_bool32 noAutoChannels; /* Opens the ALSA device with SND_PCM_NO_AUTO_CHANNELS. */ + ma_bool32 noAutoResample; /* Opens the ALSA device with SND_PCM_NO_AUTO_RESAMPLE. */ + ma_bool32 assumeDefaultChannelLayout; /* Forces the use of the "default" ALSA channel layout. */ +} ma_device_config_alsa; + +MA_API ma_device_config_alsa ma_device_config_alsa_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_alsa; +MA_API ma_device_backend_vtable* ma_alsa_get_vtable(void); +/* END miniaudio_alsa.h */ + + +/* BEG miniaudio_sndio.h */ +typedef struct ma_context_config_sndio +{ + int _unused; +} ma_context_config_sndio; + +MA_API ma_context_config_sndio ma_context_config_sndio_init(void); + + +typedef struct ma_device_config_sndio +{ + int _unused; +} ma_device_config_sndio; + +MA_API ma_device_config_sndio ma_device_config_sndio_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_sndio; +MA_API ma_device_backend_vtable* ma_sndio_get_vtable(void); +/* END miniaudio_sndio.h */ + + +/* BEG miniaudio_audio4.h */ +typedef struct ma_context_config_audio4 +{ + int _unused; +} ma_context_config_audio4; + +MA_API ma_context_config_audio4 ma_context_config_audio4_init(void); + + +typedef struct ma_device_config_audio4 +{ + int _unused; +} ma_device_config_audio4; + +MA_API ma_device_config_audio4 ma_device_config_audio4_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_audio4; +MA_API ma_device_backend_vtable* ma_audio4_get_vtable(void); +/* END miniaudio_audio4.h */ + + +/* BEG miniaudio_oss.h */ +typedef struct ma_context_config_oss +{ + int _unused; +} ma_context_config_oss; + +MA_API ma_context_config_oss ma_context_config_oss_init(void); + + +typedef struct ma_device_config_oss +{ + int _unused; +} ma_device_config_oss; + +MA_API ma_device_config_oss ma_device_config_oss_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_oss; +MA_API ma_device_backend_vtable* ma_oss_get_vtable(void); +/* END miniaudio_oss.h */ + + +/* BEG miniaudio_aaudio.h */ /* AAudio usage types. */ typedef enum { @@ -7039,11 +7617,480 @@ typedef enum ma_aaudio_allow_capture_by_none /* AAUDIO_ALLOW_CAPTURE_BY_NONE */ } ma_aaudio_allowed_capture_policy; -typedef union +typedef enum { - ma_int64 counter; - double counterD; -} ma_timer; + ma_aaudio_performance_mode_default = 0, /* Leaves the performance mode unset. */ + ma_aaudio_performance_mode_low_latency, /* MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY */ + ma_aaudio_performance_mode_none, /* MA_AAUDIO_PERFORMANCE_MODE_NONE */ + ma_aaudio_performance_mode_power_saving /* MA_AAUDIO_PERFORMANCE_MODE_POWER_SAVING */ +} ma_aaudio_performance_mode; + + +typedef struct ma_context_config_aaudio +{ + int _unused; +} ma_context_config_aaudio; + +MA_API ma_context_config_aaudio ma_context_config_aaudio_init(void); + + +typedef struct ma_device_config_aaudio +{ + ma_aaudio_usage usage; + ma_aaudio_content_type contentType; + ma_aaudio_input_preset inputPreset; + ma_aaudio_allowed_capture_policy allowedCapturePolicy; + ma_aaudio_performance_mode performanceMode; + ma_bool32 noAutoStartAfterReroute; + ma_bool32 allowSetBufferCapacity; +} ma_device_config_aaudio; + +MA_API ma_device_config_aaudio ma_device_config_aaudio_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_aaudio; +MA_API ma_device_backend_vtable* ma_aaudio_get_vtable(void); +/* END miniaudio_aaudio.h */ + + +/* BEG miniaudio_opensl.h */ +/* OpenSL stream types. */ +typedef enum +{ + ma_opensl_stream_type_default = 0, /* Leaves the stream type unset. */ + ma_opensl_stream_type_voice, /* SL_ANDROID_STREAM_VOICE */ + ma_opensl_stream_type_system, /* SL_ANDROID_STREAM_SYSTEM */ + ma_opensl_stream_type_ring, /* SL_ANDROID_STREAM_RING */ + ma_opensl_stream_type_media, /* SL_ANDROID_STREAM_MEDIA */ + ma_opensl_stream_type_alarm, /* SL_ANDROID_STREAM_ALARM */ + ma_opensl_stream_type_notification /* SL_ANDROID_STREAM_NOTIFICATION */ +} ma_opensl_stream_type; + +/* OpenSL recording presets. */ +typedef enum +{ + ma_opensl_recording_preset_default = 0, /* Leaves the input preset unset. */ + ma_opensl_recording_preset_generic, /* SL_ANDROID_RECORDING_PRESET_GENERIC */ + ma_opensl_recording_preset_camcorder, /* SL_ANDROID_RECORDING_PRESET_CAMCORDER */ + ma_opensl_recording_preset_voice_recognition, /* SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION */ + ma_opensl_recording_preset_voice_communication, /* SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION */ + ma_opensl_recording_preset_voice_unprocessed /* SL_ANDROID_RECORDING_PRESET_UNPROCESSED */ +} ma_opensl_recording_preset; + + +typedef struct ma_context_config_opensl +{ + int _unused; +} ma_context_config_opensl; + +MA_API ma_context_config_opensl ma_context_config_opensl_init(void); + + +typedef struct ma_device_config_opensl +{ + ma_opensl_stream_type streamType; + ma_opensl_recording_preset recordingPreset; +} ma_device_config_opensl; + +MA_API ma_device_config_opensl ma_device_config_opensl_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_opensl; +MA_API ma_device_backend_vtable* ma_opensl_get_vtable(void); +/* END miniaudio_opensl.h */ + + +/* BEG miniaudio_webaudio.h */ +typedef enum ma_webaudio_latency_hint +{ + ma_webaudio_latency_hint_interactive = 0, + ma_webaudio_latency_hint_playback, + ma_webaudio_latency_hint_balanced +} ma_webaudio_latency_hint; + + +typedef struct ma_context_config_webaudio +{ + int _unused; +} ma_context_config_webaudio; + +MA_API ma_context_config_webaudio ma_context_config_webaudio_init(void); + + +typedef struct ma_device_config_webaudio +{ + ma_webaudio_latency_hint latencyHint; +} ma_device_config_webaudio; + +MA_API ma_device_config_webaudio ma_device_config_webaudio_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_webaudio; +MA_API ma_device_backend_vtable* ma_webaudio_get_vtable(void); +/* END miniaudio_webaudio.h */ + + +/* BEG miniaudio_dreamcast.h */ +typedef struct +{ + ma_bool32 noInit; /* When set to true, will *not* call snd_init() when the context is initialized. */ +} ma_context_config_dreamcast; + +MA_API ma_context_config_dreamcast ma_context_config_dreamcast_init(void); + + +typedef struct +{ + int voiceChannel; /* For a stereo stream, the left channel will be voiceChannel and the right channel will be voiceChannel+1. */ +} ma_device_config_dreamcast; + +MA_API ma_device_config_dreamcast ma_device_config_dreamcast_init(int voiceChannel); + + +extern ma_device_backend_vtable* ma_device_backend_dreamcast; +MA_API ma_device_backend_vtable* ma_dreamcast_get_vtable(void); +/* END miniaudio_dreamcast.h */ + + +/* BEG miniaudio_xaudio.h */ +extern ma_device_backend_vtable* ma_device_backend_xaudio; +MA_API ma_device_backend_vtable* ma_xaudio_get_vtable(void); + + +typedef struct +{ + int _unused; +} ma_context_config_xaudio; + +MA_API ma_context_config_xaudio ma_context_config_xaudio_init(void); + + +typedef struct +{ + int _unused; +} ma_device_config_xaudio; + +MA_API ma_device_config_xaudio ma_device_config_xaudio_init(void); +/* END miniaudio_xaudio.h */ + + +/* BEG miniaudio_vita.h */ +extern ma_device_backend_vtable* ma_device_backend_vita; +MA_API ma_device_backend_vtable* ma_vita_get_vtable(void); + + +typedef struct +{ + int _unused; +} ma_context_config_vita; + +MA_API ma_context_config_vita ma_context_config_vita_init(void); + + +typedef enum +{ + MA_VITA_PORT_TYPE_BGM = 0, + MA_VITA_PORT_TYPE_MAIN = 1 +} ma_vita_port_type; + +typedef struct +{ + ma_vita_port_type portType; +} ma_device_config_vita; + +MA_API ma_device_config_vita ma_device_config_vita_init(void); +/* END miniaudio_vita.h */ + + +/* BEG miniaudio_null.h */ +typedef struct ma_context_config_null +{ + int _unused; +} ma_context_config_null; + +MA_API ma_context_config_null ma_context_config_null_init(void); + + +typedef struct ma_device_config_null +{ + int _unused; +} ma_device_config_null; + +MA_API ma_device_config_null ma_device_config_null_init(void); + + +extern ma_device_backend_vtable* ma_device_backend_null; +MA_API ma_device_backend_vtable* ma_null_get_vtable(void); +/* END miniaudio_null.h */ + + +/************************************************************************************************************************************************************ + +END BACKENDS + +************************************************************************************************************************************************************/ + +/* 64 should be enough to cover all stock backends, but if somehow this is exceeded we can increase this. */ +#ifndef MA_MAX_STOCK_DEVICE_BACKENDS +#define MA_MAX_STOCK_DEVICE_BACKENDS 64 +#endif + + +/* For mapping a config object to a backend. Used for both context configs or device configs. */ +typedef struct ma_device_backend_config +{ + ma_device_backend_vtable* pVTable; + const void* pConfig; /* Can be a pointer to either a backend-specific context config, or a backend-specific device config, depending on context. */ +} ma_device_backend_config; + +MA_API ma_device_backend_config ma_device_backend_config_init(ma_device_backend_vtable* pVTable, const void* pConfig); + + +/* This is the list of default device backends in priority order. */ +MA_API ma_uint32 ma_get_stock_device_backends(ma_device_backend_config* pBackends, size_t backendsCap); + + +typedef struct ma_device_backend_info +{ + const char* pName; + ma_bool32 isLoopbackSupported; + ma_bool32 noAudioThread; /* When set to true, the backend does not create a miniaudio-managed audio thread. Will be used for backends like Web Audio, or those that run on platforms that do not support multithreading. Depending on the backend, this may force single-threaded mode. Single-threaded mode will not be enforced for the Web Audio backend. */ + ma_bool32 isMultiThreadedModeAllowedWhenThreadingDisabled; /* When set to true, the backend must be 100% thread safe. */ +} ma_device_backend_info; + + +/* +Retrieves information about a backend. +*/ +MA_API void ma_get_device_backend_info(ma_device_backend_vtable* pBackendVTable, ma_device_backend_info* pBackendInfo); + + +typedef enum +{ + ma_device_status_uninitialized = 0, + ma_device_status_stopped = 1, /* The device's default state after initialization. */ + ma_device_status_started = 2, /* The device is started and is requesting and/or delivering audio data. */ + ma_device_status_starting = 3, /* Transitioning from a stopped state to started. */ + ma_device_status_stopping = 4, /* Transitioning from a started state to stopped. */ + ma_device_status_errored = 5 +} ma_device_status; + +MA_ATOMIC_SAFE_TYPE_DECL(i32, 4, device_status) + + + +typedef enum ma_threading_mode +{ + MA_THREADING_MODE_MULTI_THREADED = 0, /* Default. */ + MA_THREADING_MODE_SINGLE_THREADED +} ma_threading_mode; + +typedef enum ma_blocking_mode +{ + MA_BLOCKING_MODE_BLOCKING, + MA_BLOCKING_MODE_NON_BLOCKING +} ma_blocking_mode; + +/* ma_device_op* is used internally. Defined here because it's required by ma_device. */ +typedef struct ma_device_op_completion_event +{ + ma_result result; +#ifndef MA_NO_THREADING + ma_event event; +#endif +} ma_device_op_completion_event; + +typedef enum ma_device_op_type +{ + MA_DEVICE_OP_INIT, + MA_DEVICE_OP_UNINIT, + MA_DEVICE_OP_START, + MA_DEVICE_OP_STOP +} ma_device_op_type; + +typedef union ma_device_op_params +{ + struct + { + const void* pDeviceBackendConfig; + ma_device_descriptor* pDescriptorPlayback; + ma_device_descriptor* pDescriptorCapture; + } init; +} ma_device_op_params; + +typedef struct ma_device_op +{ + ma_device_op_type type; + ma_device_op_params params; + ma_device_op_completion_event* pCompletionEvent; /* This is signalled when the operation is completed. */ +} ma_device_op; + +/* +The device op queue is just a simple list with a mutex for the time being while I get everything set up. Might make +this more efficient later. + +The capacity of this queue is tiny. In practice it's rare that there would be more than one item in the queue because +typically a init/uninit/start/stop will typically be done in an orderly and synchronous manner. +*/ +typedef struct ma_device_op_queue +{ +#ifndef MA_NO_THREADING + ma_mutex lock; + ma_semaphore sem; +#endif + ma_uint32 count; + ma_uint32 tail; + ma_device_op pItems[4]; /* In practice, a capacity of 4 should be more than enough. */ +} ma_device_op_queue; + + + +/* Device notification types. */ +typedef enum +{ + ma_device_notification_type_started, + ma_device_notification_type_stopped, + ma_device_notification_type_rerouted, + ma_device_notification_type_interruption_began, + ma_device_notification_type_interruption_ended, + ma_device_notification_type_unlocked, + ma_device_notification_type_errored +} ma_device_notification_type; + +typedef struct +{ + ma_device* pDevice; + ma_device_notification_type type; + union + { + struct + { + int _unused; + } started; + struct + { + int _unused; + } stopped; + struct + { + int _unused; + } rerouted; + struct + { + int _unused; + } interruption; + struct + { + int _unused; + } unlocked; + struct + { + int _unused; + } errored; + } data; +} ma_device_notification; + +/* +The notification callback for when the application should be notified of a change to the device. + +This callback is used for notifying the application of changes such as when the device has started, +stopped, rerouted or an interruption has occurred. Note that not all backends will post all +notification types. For example, some backends will perform automatic stream routing without any +kind of notification to the host program which means miniaudio will never know about it and will +never be able to fire the rerouted notification. You should keep this in mind when designing your +program. + +The stopped notification will *not* get fired when a device is rerouted. + + +Parameters +---------- +pNotification (in) + A pointer to a structure containing information about the event. Use the `pDevice` member of + this object to retrieve the relevant device. The `type` member can be used to discriminate + against each of the notification types. + + +Remarks +------- +Do not restart or uninitialize the device from the callback. + +Not all notifications will be triggered by all backends, however the started and stopped events +should be reliable for all backends. Some backends do not have a good way to detect device +stoppages due to unplugging the device which may result in the stopped callback not getting +fired. This has been observed with at least one BSD variant. + +The rerouted notification is fired *after* the reroute has occurred. The stopped notification will +*not* get fired when a device is rerouted. The following backends are known to do automatic stream +rerouting, but do not have a way to be notified of the change: + + * DirectSound + +The interruption notifications are used on mobile platforms for detecting when audio is interrupted +due to things like an incoming phone call. Currently this is only implemented on iOS. None of the +Android backends will report this notification. +*/ +typedef void (* ma_device_notification_proc)(const ma_device_notification* pNotification); + + +/* +The callback for processing audio data from the device. + +The data callback is fired by miniaudio whenever the device needs to have more data delivered to a playback device, or when a capture device has some data +available. This is called as soon as the backend asks for more data which means it may be called with inconsistent frame counts. You cannot assume the +callback will be fired with a consistent frame count. + + +Parameters +---------- +pDevice (in) + A pointer to the relevant device. + +pOutput (out) + A pointer to the output buffer that will receive audio data that will later be played back through the speakers. This will be non-null for a playback or + full-duplex device and null for a capture and loopback device. + +pInput (in) + A pointer to the buffer containing input data from a recording device. This will be non-null for a capture, full-duplex or loopback device and null for a + playback device. + +frameCount (in) + The number of PCM frames to process. Note that this will not necessarily be equal to what you requested when you initialized the device. The + `periodSizeInFrames` and `periodSizeInMilliseconds` members of the device config are just hints, and are not necessarily exactly what you'll get. You must + not assume this will always be the same value each time the callback is fired. + + +Remarks +------- +You cannot stop and start the device from inside the callback or else you'll get a deadlock. You must also not uninitialize the device from inside the +callback. The following APIs cannot be called from inside the callback: + + ma_device_init() + ma_device_init_ex() + ma_device_uninit() + ma_device_start() + ma_device_stop() + +The proper way to stop the device is to call `ma_device_stop()` from a different thread, normally the main application thread. +*/ +typedef void (* ma_device_data_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + + + +typedef enum +{ + ma_device_type_playback = 1, + ma_device_type_capture = 2, + ma_device_type_duplex = ma_device_type_playback | ma_device_type_capture, /* 3 */ + ma_device_type_loopback = 4 +} ma_device_type; + +typedef enum +{ + ma_share_mode_shared = 0, + ma_share_mode_exclusive +} ma_share_mode; + typedef union { @@ -7051,8 +8098,9 @@ typedef union ma_uint8 dsound[16]; /* DirectSound uses a GUID for identification. */ /*UINT_PTR*/ ma_uint32 winmm; /* When creating a device, WinMM expects a Win32 UINT_PTR for device identification. In practice it's actually just a UINT. */ char alsa[256]; /* ALSA uses a name string for identification. */ + char pipewire[256]; /* PipeWire uses a string for identification. */ char pulse[256]; /* PulseAudio uses a name string for identification. */ - int jack; /* JACK always uses default devices. */ + char jack[256]; /* JACK uses a name string. */ char coreaudio[256]; /* Core Audio uses a string for identification. */ char sndio[256]; /* "snd/0", etc. */ char audio4[256]; /* "/dev/audio", etc. */ @@ -7060,21 +8108,35 @@ typedef union ma_int32 aaudio; /* AAudio uses a 32-bit integer for identification. */ ma_uint32 opensl; /* OpenSL|ES uses a 32-bit unsigned integer for identification. */ char webaudio[32]; /* Web Audio always uses default devices for now, but if this changes it'll be a GUID. */ + int nullbackend; /* The null backend uses an integer for device IDs. */ union { int i; char s[256]; void* p; - } custom; /* The custom backend could be anything. Give them a few options. */ - int nullbackend; /* The null backend uses an integer for device IDs. */ + } custom; /* External backends could be anything. Give them a few options. */ } ma_device_id; MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB); -typedef struct ma_context_config ma_context_config; -typedef struct ma_device_config ma_device_config; -typedef struct ma_backend_callbacks ma_backend_callbacks; +/* +Describes some basic details about a playback or capture device. +*/ +struct ma_device_descriptor +{ + const ma_device_id* pDeviceID; + ma_share_mode shareMode; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + ma_uint32 periodSizeInFrames; + ma_uint32 periodSizeInMilliseconds; + ma_uint32 periodCount; +}; + + #define MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE (1U << 1) /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ @@ -7084,37 +8146,55 @@ typedef struct ma_backend_callbacks ma_backend_callbacks; typedef struct { - /* Basic info. This is the only information guaranteed to be filled in during device enumeration. */ ma_device_id id; char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* +1 for null terminator. */ ma_bool32 isDefault; + /* + List of natively supported formats. Each entry should have a unique [flags,format] pair which + can be thought of as the key. Then each entry will have a range of supported channels and sample + rates. Note that a device won't necessarily support every single channel and sample rate between + the reported range. For example, a sample rate range of [44100, 48000] may be reported, but + that doesn't mean a rate of, say, 47000 will be supported. The same applies for channels. + */ ma_uint32 nativeDataFormatCount; struct { - ma_format format; /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ + ma_uint32 flags; /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ + ma_format format; /* Sample format. */ + ma_uint32 minChannels; + ma_uint32 maxChannels; + ma_uint32 minSampleRate; + ma_uint32 maxSampleRate; + + /* Legacy deprecated stuff. Will be removed later when the new system is added.. */ ma_uint32 channels; /* If set to 0, all channels are supported. */ ma_uint32 sampleRate; /* If set to 0, all sample rates are supported. */ - ma_uint32 flags; /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ - } nativeDataFormats[/*ma_format_count * ma_standard_sample_rate_count * MA_MAX_CHANNELS*/ 64]; /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */ + } nativeDataFormats[16]; /* Should be big enough for [flags,format] to act as the key. */ } ma_device_info; +MA_API void ma_device_info_add_native_data_format_ex(ma_device_info* pDeviceInfo, ma_uint32 flags, ma_format format, ma_uint32 minChannels, ma_uint32 maxChannels, ma_uint32 minSampleRate, ma_uint32 maxSampleRate); +MA_API void ma_device_info_add_native_data_format(ma_device_info* pDeviceInfo, ma_format format, ma_uint32 minChannels, ma_uint32 maxChannels, ma_uint32 minSampleRate, ma_uint32 maxSampleRate); + + + struct ma_device_config { ma_device_type deviceType; + ma_threading_mode threadingMode; ma_uint32 sampleRate; ma_uint32 periodSizeInFrames; ma_uint32 periodSizeInMilliseconds; ma_uint32 periods; - ma_performance_profile performanceProfile; ma_bool8 noPreSilencedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to silence. */ ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will not be clipped after returning. Only applies when the playback sample format is f32. */ ma_bool8 noDisableDenormals; /* Do not disable denormals when firing the data callback. */ ma_bool8 noFixedSizedCallback; /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ ma_device_data_proc dataCallback; ma_device_notification_proc notificationCallback; - ma_stop_proc stopCallback; void* pUserData; + ma_device_backend_config* pBackendConfigs; + size_t backendConfigCount; ma_resampler_config resampling; struct { @@ -7137,61 +8217,41 @@ struct ma_device_config ma_share_mode shareMode; } capture; - struct - { - ma_wasapi_usage usage; /* When configured, uses Avrt APIs to set the thread characteristics. */ - ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ - ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ - ma_bool8 noAutoStreamRouting; /* Disables automatic stream routing. */ - ma_bool8 noHardwareOffloading; /* Disables WASAPI's hardware offloading feature. */ - ma_uint32 loopbackProcessID; /* The process ID to include or exclude for loopback mode. Set to 0 to capture audio from all processes. Ignored when an explicit device ID is specified. */ - ma_bool8 loopbackProcessExclude; /* When set to true, excludes the process specified by loopbackProcessID. By default, the process will be included. */ - } wasapi; - struct - { - ma_bool32 noMMap; /* Disables MMap mode. */ - ma_bool32 noAutoFormat; /* Opens the ALSA device with SND_PCM_NO_AUTO_FORMAT. */ - ma_bool32 noAutoChannels; /* Opens the ALSA device with SND_PCM_NO_AUTO_CHANNELS. */ - ma_bool32 noAutoResample; /* Opens the ALSA device with SND_PCM_NO_AUTO_RESAMPLE. */ - } alsa; - struct - { - const char* pStreamNamePlayback; - const char* pStreamNameCapture; - int channelMap; - } pulse; - struct - { - ma_bool32 allowNominalSampleRateChange; /* Desktop only. When enabled, allows changing of the sample rate at the operating system level. */ - } coreaudio; - struct - { - ma_opensl_stream_type streamType; - ma_opensl_recording_preset recordingPreset; - ma_bool32 enableCompatibilityWorkarounds; - } opensl; - struct - { - ma_aaudio_usage usage; - ma_aaudio_content_type contentType; - ma_aaudio_input_preset inputPreset; - ma_aaudio_allowed_capture_policy allowedCapturePolicy; - ma_bool32 noAutoStartAfterReroute; - ma_bool32 enableCompatibilityWorkarounds; - ma_bool32 allowSetBufferCapacity; - } aaudio; + /* Stock backend configs. */ + ma_device_config_wasapi wasapi; + ma_device_config_dsound dsound; + ma_device_config_winmm winmm; + ma_device_config_coreaudio coreaudio; + ma_device_config_pipewire pipewire; + ma_device_config_pulseaudio pulseaudio; + ma_device_config_jack jack; + ma_device_config_alsa alsa; + ma_device_config_sndio sndio; + ma_device_config_audio4 audio4; + ma_device_config_oss oss; + ma_device_config_aaudio aaudio; + ma_device_config_opensl opensl; + ma_device_config_webaudio webaudio; + ma_device_config_dreamcast dreamcast; + ma_device_config_xaudio xaudio; + ma_device_config_vita vita; + ma_device_config_null null_backend; }; +typedef enum ma_device_enumeration_result +{ + MA_DEVICE_ENUMERATION_ABORT = 0, + MA_DEVICE_ENUMERATION_CONTINUE = 1 +} ma_device_enumeration_result; + + /* The callback for handling device enumeration. This is fired from `ma_context_enumerate_devices()`. Parameters ---------- -pContext (in) - A pointer to the context performing the enumeration. - deviceType (in) The type of the device being enumerated. This will always be either `ma_device_type_playback` or `ma_device_type_capture`. @@ -7202,26 +8262,16 @@ pInfo (in) pUserData (in) The user data pointer passed into `ma_context_enumerate_devices()`. -*/ -typedef ma_bool32 (* ma_enum_devices_callback_proc)(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData); -/* -Describes some basic details about a playback or capture device. +Return Value +------------ +Implementations should return `MA_DEVICE_ENUMERATION_CONTINUE` to continue enumeration, or `MA_DEVICE_ENUMERATION_ABORT` to abort enumeration. */ -typedef struct -{ - const ma_device_id* pDeviceID; - ma_share_mode shareMode; - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - ma_channel channelMap[MA_MAX_CHANNELS]; - ma_uint32 periodSizeInFrames; - ma_uint32 periodSizeInMilliseconds; - ma_uint32 periodCount; -} ma_device_descriptor; +typedef ma_device_enumeration_result (* ma_enum_devices_callback_proc)(ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData); + +/* TODO: THIS SECTION NEEDS TO BE UPDATED FOR THE NEW BACKEND SYSTEM. */ /* These are the callbacks required to be implemented for a backend. These callbacks are grouped into two parts: context and device. There is one context to many devices. A device is created from a context. @@ -7278,33 +8328,27 @@ This allows miniaudio to then process any necessary data conversion and then pas If the backend requires absolute flexibility with its data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. -The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been +The audio thread should run data delivery logic in a loop while `ma_device_get_status() == ma_device_status_started` and no errors have been encountered. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback. -The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this -callback. When the device is stopped, the `ma_device_get_state() == ma_device_state_started` condition will fail and the loop will be terminated -which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceDataLoop()` callback, -look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceDataLoopWakeup()` callback if you need a mechanism to +The invocation of the `onDeviceLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this +callback. When the device is stopped, the `ma_device_get_status() == ma_device_status_started` condition will fail and the loop will be terminated +which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceLoop()` callback, +look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceWakeup()` callback if you need a mechanism to wake up the audio thread. - -If the backend supports an optimized retrieval of device information from an initialized `ma_device` object, it should implement the -`onDeviceGetInfo()` callback. This is optional, in which case it will fall back to `onContextGetDeviceInfo()` which is less efficient. */ -struct ma_backend_callbacks -{ - ma_result (* onContextInit)(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks); - ma_result (* onContextUninit)(ma_context* pContext); - ma_result (* onContextEnumerateDevices)(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData); - ma_result (* onContextGetDeviceInfo)(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo); - ma_result (* onDeviceInit)(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture); - ma_result (* onDeviceUninit)(ma_device* pDevice); - ma_result (* onDeviceStart)(ma_device* pDevice); - ma_result (* onDeviceStop)(ma_device* pDevice); - ma_result (* onDeviceRead)(ma_device* pDevice, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead); - ma_result (* onDeviceWrite)(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten); - ma_result (* onDeviceDataLoop)(ma_device* pDevice); - ma_result (* onDeviceDataLoopWakeup)(ma_device* pDevice); - ma_result (* onDeviceGetInfo)(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo); +struct ma_device_backend_vtable +{ + void (* onBackendInfo )(ma_device_backend_info* pBackendInfo); + ma_result (* onContextInit )(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState); + void (* onContextUninit )(ma_context* pContext); + ma_result (* onContextEnumerateDevices)(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData); + ma_result (* onDeviceInit )(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState); + void (* onDeviceUninit )(ma_device* pDevice); + ma_result (* onDeviceStart )(ma_device* pDevice); + ma_result (* onDeviceStop )(ma_device* pDevice); + ma_result (* onDeviceStep )(ma_device* pDevice, ma_blocking_mode blockingMode); + void (* onDeviceWakeup )(ma_device* pDevice); }; struct ma_context_config @@ -7314,493 +8358,84 @@ struct ma_context_config size_t threadStackSize; void* pUserData; ma_allocation_callbacks allocationCallbacks; - struct - { - ma_handle hWnd; /* HWND. Optional window handle to pass into SetCooperativeLevel(). Will default to the foreground window, and if that fails, the desktop window. */ - } dsound; - struct - { - ma_bool32 useVerboseDeviceEnumeration; - } alsa; - struct - { - const char* pApplicationName; - const char* pServerName; - ma_bool32 tryAutoSpawn; /* Enables autospawning of the PulseAudio daemon if necessary. */ - } pulse; - struct - { - ma_ios_session_category sessionCategory; - ma_uint32 sessionCategoryOptions; - ma_bool32 noAudioSessionActivate; /* iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. */ - ma_bool32 noAudioSessionDeactivate; /* iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:false] on uninitialization. */ - } coreaudio; - struct - { - const char* pClientName; - ma_bool32 tryStartServer; - } jack; - ma_backend_callbacks custom; + + /* Stock backend configs. */ + ma_context_config_wasapi wasapi; + ma_context_config_dsound dsound; + ma_context_config_winmm winmm; + ma_context_config_coreaudio coreaudio; + ma_context_config_pipewire pipewire; + ma_context_config_pulseaudio pulseaudio; + ma_context_config_jack jack; + ma_context_config_alsa alsa; + ma_context_config_sndio sndio; + ma_context_config_audio4 audio4; + ma_context_config_oss oss; + ma_context_config_aaudio aaudio; + ma_context_config_opensl opensl; + ma_context_config_webaudio webaudio; + ma_context_config_dreamcast dreamcast; + ma_context_config_xaudio xaudio; + ma_context_config_vita vita; + ma_context_config_null null_backend; }; -/* WASAPI specific structure for some commands which must run on a common thread due to bugs in WASAPI. */ -typedef struct -{ - int code; - ma_event* pEvent; /* This will be signalled when the event is complete. */ - union - { - struct - { - int _unused; - } quit; - struct - { - ma_device_type deviceType; - void* pAudioClient; - void** ppAudioClientService; - ma_result* pResult; /* The result from creating the audio client service. */ - } createAudioClient; - struct - { - ma_device* pDevice; - ma_device_type deviceType; - } releaseAudioClient; - } data; -} ma_context_command__wasapi; - struct ma_context { - ma_backend_callbacks callbacks; - ma_backend backend; /* DirectSound, ALSA, etc. */ + ma_device_backend_vtable* pVTable; /* New new system. */ + MA_ATOMIC(MA_SIZEOF_PTR, void*) pBackendState; /* Backend state created by the backend. This will be passed to the relevant backend functions. */ ma_log* pLog; - ma_log log; /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ + ma_log log; /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ ma_thread_priority threadPriority; size_t threadStackSize; void* pUserData; ma_allocation_callbacks allocationCallbacks; - ma_mutex deviceEnumLock; /* Used to make ma_context_get_devices() thread safe. */ - ma_mutex deviceInfoLock; /* Used to make ma_context_get_device_info() thread safe. */ - ma_uint32 deviceInfoCapacity; /* Total capacity of pDeviceInfos. */ + ma_mutex deviceEnumLock; /* Used to make ma_context_get_devices() thread safe. */ + ma_uint32 deviceInfoCapacity; /* Total capacity of pDeviceInfos. */ ma_uint32 playbackDeviceInfoCount; ma_uint32 captureDeviceInfoCount; - ma_device_info* pDeviceInfos; /* Playback devices first, then capture. */ + ma_device_info* pDeviceInfos; /* Playback devices first, then capture. */ - union +#if defined(MA_WIN32) + struct { -#ifdef MA_SUPPORT_WASAPI - struct - { - ma_thread commandThread; - ma_mutex commandLock; - ma_semaphore commandSem; - ma_uint32 commandIndex; - ma_uint32 commandCount; - ma_context_command__wasapi commands[4]; - ma_handle hAvrt; - ma_proc AvSetMmThreadCharacteristicsA; - ma_proc AvRevertMmThreadcharacteristics; - ma_handle hMMDevapi; - ma_proc ActivateAudioInterfaceAsync; - } wasapi; -#endif -#ifdef MA_SUPPORT_DSOUND - struct - { - ma_handle hWnd; /* Can be null. */ - ma_handle hDSoundDLL; - ma_proc DirectSoundCreate; - ma_proc DirectSoundEnumerateA; - ma_proc DirectSoundCaptureCreate; - ma_proc DirectSoundCaptureEnumerateA; - } dsound; + /*HMODULE*/ ma_handle hOle32DLL; + ma_proc CoInitialize; + ma_proc CoInitializeEx; + ma_proc CoUninitialize; + ma_proc CoCreateInstance; + ma_proc CoTaskMemFree; + ma_proc PropVariantClear; + ma_proc StringFromGUID2; + } win32; #endif -#ifdef MA_SUPPORT_WINMM - struct - { - ma_handle hWinMM; - ma_proc waveOutGetNumDevs; - ma_proc waveOutGetDevCapsA; - ma_proc waveOutOpen; - ma_proc waveOutClose; - ma_proc waveOutPrepareHeader; - ma_proc waveOutUnprepareHeader; - ma_proc waveOutWrite; - ma_proc waveOutReset; - ma_proc waveInGetNumDevs; - ma_proc waveInGetDevCapsA; - ma_proc waveInOpen; - ma_proc waveInClose; - ma_proc waveInPrepareHeader; - ma_proc waveInUnprepareHeader; - ma_proc waveInAddBuffer; - ma_proc waveInStart; - ma_proc waveInReset; - } winmm; -#endif -#ifdef MA_SUPPORT_ALSA - struct - { - ma_handle asoundSO; - ma_proc snd_pcm_open; - ma_proc snd_pcm_close; - ma_proc snd_pcm_hw_params_sizeof; - ma_proc snd_pcm_hw_params_any; - ma_proc snd_pcm_hw_params_set_format; - ma_proc snd_pcm_hw_params_set_format_first; - ma_proc snd_pcm_hw_params_get_format_mask; - ma_proc snd_pcm_hw_params_set_channels; - ma_proc snd_pcm_hw_params_set_channels_near; - ma_proc snd_pcm_hw_params_set_channels_minmax; - ma_proc snd_pcm_hw_params_set_rate_resample; - ma_proc snd_pcm_hw_params_set_rate; - ma_proc snd_pcm_hw_params_set_rate_near; - ma_proc snd_pcm_hw_params_set_rate_minmax; - ma_proc snd_pcm_hw_params_set_buffer_size_near; - ma_proc snd_pcm_hw_params_set_periods_near; - ma_proc snd_pcm_hw_params_set_access; - ma_proc snd_pcm_hw_params_get_format; - ma_proc snd_pcm_hw_params_get_channels; - ma_proc snd_pcm_hw_params_get_channels_min; - ma_proc snd_pcm_hw_params_get_channels_max; - ma_proc snd_pcm_hw_params_get_rate; - ma_proc snd_pcm_hw_params_get_rate_min; - ma_proc snd_pcm_hw_params_get_rate_max; - ma_proc snd_pcm_hw_params_get_buffer_size; - ma_proc snd_pcm_hw_params_get_periods; - ma_proc snd_pcm_hw_params_get_access; - ma_proc snd_pcm_hw_params_test_format; - ma_proc snd_pcm_hw_params_test_channels; - ma_proc snd_pcm_hw_params_test_rate; - ma_proc snd_pcm_hw_params; - ma_proc snd_pcm_sw_params_sizeof; - ma_proc snd_pcm_sw_params_current; - ma_proc snd_pcm_sw_params_get_boundary; - ma_proc snd_pcm_sw_params_set_avail_min; - ma_proc snd_pcm_sw_params_set_start_threshold; - ma_proc snd_pcm_sw_params_set_stop_threshold; - ma_proc snd_pcm_sw_params; - ma_proc snd_pcm_format_mask_sizeof; - ma_proc snd_pcm_format_mask_test; - ma_proc snd_pcm_get_chmap; - ma_proc snd_pcm_state; - ma_proc snd_pcm_prepare; - ma_proc snd_pcm_start; - ma_proc snd_pcm_drop; - ma_proc snd_pcm_drain; - ma_proc snd_pcm_reset; - ma_proc snd_device_name_hint; - ma_proc snd_device_name_get_hint; - ma_proc snd_card_get_index; - ma_proc snd_device_name_free_hint; - ma_proc snd_pcm_mmap_begin; - ma_proc snd_pcm_mmap_commit; - ma_proc snd_pcm_recover; - ma_proc snd_pcm_readi; - ma_proc snd_pcm_writei; - ma_proc snd_pcm_avail; - ma_proc snd_pcm_avail_update; - ma_proc snd_pcm_wait; - ma_proc snd_pcm_nonblock; - ma_proc snd_pcm_info; - ma_proc snd_pcm_info_sizeof; - ma_proc snd_pcm_info_get_name; - ma_proc snd_pcm_poll_descriptors; - ma_proc snd_pcm_poll_descriptors_count; - ma_proc snd_pcm_poll_descriptors_revents; - ma_proc snd_config_update_free_global; - - ma_mutex internalDeviceEnumLock; - ma_bool32 useVerboseDeviceEnumeration; - } alsa; -#endif -#ifdef MA_SUPPORT_PULSEAUDIO - struct - { - ma_handle pulseSO; - ma_proc pa_mainloop_new; - ma_proc pa_mainloop_free; - ma_proc pa_mainloop_quit; - ma_proc pa_mainloop_get_api; - ma_proc pa_mainloop_iterate; - ma_proc pa_mainloop_wakeup; - ma_proc pa_threaded_mainloop_new; - ma_proc pa_threaded_mainloop_free; - ma_proc pa_threaded_mainloop_start; - ma_proc pa_threaded_mainloop_stop; - ma_proc pa_threaded_mainloop_lock; - ma_proc pa_threaded_mainloop_unlock; - ma_proc pa_threaded_mainloop_wait; - ma_proc pa_threaded_mainloop_signal; - ma_proc pa_threaded_mainloop_accept; - ma_proc pa_threaded_mainloop_get_retval; - ma_proc pa_threaded_mainloop_get_api; - ma_proc pa_threaded_mainloop_in_thread; - ma_proc pa_threaded_mainloop_set_name; - ma_proc pa_context_new; - ma_proc pa_context_unref; - ma_proc pa_context_connect; - ma_proc pa_context_disconnect; - ma_proc pa_context_set_state_callback; - ma_proc pa_context_get_state; - ma_proc pa_context_get_sink_info_list; - ma_proc pa_context_get_source_info_list; - ma_proc pa_context_get_sink_info_by_name; - ma_proc pa_context_get_source_info_by_name; - ma_proc pa_operation_unref; - ma_proc pa_operation_get_state; - ma_proc pa_channel_map_init_extend; - ma_proc pa_channel_map_valid; - ma_proc pa_channel_map_compatible; - ma_proc pa_stream_new; - ma_proc pa_stream_unref; - ma_proc pa_stream_connect_playback; - ma_proc pa_stream_connect_record; - ma_proc pa_stream_disconnect; - ma_proc pa_stream_get_state; - ma_proc pa_stream_get_sample_spec; - ma_proc pa_stream_get_channel_map; - ma_proc pa_stream_get_buffer_attr; - ma_proc pa_stream_set_buffer_attr; - ma_proc pa_stream_get_device_name; - ma_proc pa_stream_set_write_callback; - ma_proc pa_stream_set_read_callback; - ma_proc pa_stream_set_suspended_callback; - ma_proc pa_stream_set_moved_callback; - ma_proc pa_stream_is_suspended; - ma_proc pa_stream_flush; - ma_proc pa_stream_drain; - ma_proc pa_stream_is_corked; - ma_proc pa_stream_cork; - ma_proc pa_stream_trigger; - ma_proc pa_stream_begin_write; - ma_proc pa_stream_write; - ma_proc pa_stream_peek; - ma_proc pa_stream_drop; - ma_proc pa_stream_writable_size; - ma_proc pa_stream_readable_size; - - /*pa_mainloop**/ ma_ptr pMainLoop; - /*pa_context**/ ma_ptr pPulseContext; - char* pApplicationName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ - char* pServerName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ - } pulse; -#endif -#ifdef MA_SUPPORT_JACK - struct - { - ma_handle jackSO; - ma_proc jack_client_open; - ma_proc jack_client_close; - ma_proc jack_client_name_size; - ma_proc jack_set_process_callback; - ma_proc jack_set_buffer_size_callback; - ma_proc jack_on_shutdown; - ma_proc jack_get_sample_rate; - ma_proc jack_get_buffer_size; - ma_proc jack_get_ports; - ma_proc jack_activate; - ma_proc jack_deactivate; - ma_proc jack_connect; - ma_proc jack_port_register; - ma_proc jack_port_name; - ma_proc jack_port_get_buffer; - ma_proc jack_free; - - char* pClientName; - ma_bool32 tryStartServer; - } jack; -#endif -#ifdef MA_SUPPORT_COREAUDIO - struct - { - ma_handle hCoreFoundation; - ma_proc CFStringGetCString; - ma_proc CFRelease; - - ma_handle hCoreAudio; - ma_proc AudioObjectGetPropertyData; - ma_proc AudioObjectGetPropertyDataSize; - ma_proc AudioObjectSetPropertyData; - ma_proc AudioObjectAddPropertyListener; - ma_proc AudioObjectRemovePropertyListener; - - ma_handle hAudioUnit; /* Could possibly be set to AudioToolbox on later versions of macOS. */ - ma_proc AudioComponentFindNext; - ma_proc AudioComponentInstanceDispose; - ma_proc AudioComponentInstanceNew; - ma_proc AudioOutputUnitStart; - ma_proc AudioOutputUnitStop; - ma_proc AudioUnitAddPropertyListener; - ma_proc AudioUnitGetPropertyInfo; - ma_proc AudioUnitGetProperty; - ma_proc AudioUnitSetProperty; - ma_proc AudioUnitInitialize; - ma_proc AudioUnitRender; - - /*AudioComponent*/ ma_ptr component; - ma_bool32 noAudioSessionDeactivate; /* For tracking whether or not the iOS audio session should be explicitly deactivated. Set from the config in ma_context_init__coreaudio(). */ - } coreaudio; -#endif -#ifdef MA_SUPPORT_SNDIO - struct - { - ma_handle sndioSO; - ma_proc sio_open; - ma_proc sio_close; - ma_proc sio_setpar; - ma_proc sio_getpar; - ma_proc sio_getcap; - ma_proc sio_start; - ma_proc sio_stop; - ma_proc sio_read; - ma_proc sio_write; - ma_proc sio_onmove; - ma_proc sio_nfds; - ma_proc sio_pollfd; - ma_proc sio_revents; - ma_proc sio_eof; - ma_proc sio_setvol; - ma_proc sio_onvol; - ma_proc sio_initpar; - } sndio; -#endif -#ifdef MA_SUPPORT_AUDIO4 - struct - { - int _unused; - } audio4; -#endif -#ifdef MA_SUPPORT_OSS - struct - { - int versionMajor; - int versionMinor; - } oss; -#endif -#ifdef MA_SUPPORT_AAUDIO - struct - { - ma_handle hAAudio; /* libaaudio.so */ - ma_proc AAudio_createStreamBuilder; - ma_proc AAudioStreamBuilder_delete; - ma_proc AAudioStreamBuilder_setDeviceId; - ma_proc AAudioStreamBuilder_setDirection; - ma_proc AAudioStreamBuilder_setSharingMode; - ma_proc AAudioStreamBuilder_setFormat; - ma_proc AAudioStreamBuilder_setChannelCount; - ma_proc AAudioStreamBuilder_setSampleRate; - ma_proc AAudioStreamBuilder_setBufferCapacityInFrames; - ma_proc AAudioStreamBuilder_setFramesPerDataCallback; - ma_proc AAudioStreamBuilder_setDataCallback; - ma_proc AAudioStreamBuilder_setErrorCallback; - ma_proc AAudioStreamBuilder_setPerformanceMode; - ma_proc AAudioStreamBuilder_setUsage; - ma_proc AAudioStreamBuilder_setContentType; - ma_proc AAudioStreamBuilder_setInputPreset; - ma_proc AAudioStreamBuilder_setAllowedCapturePolicy; - ma_proc AAudioStreamBuilder_openStream; - ma_proc AAudioStream_close; - ma_proc AAudioStream_getState; - ma_proc AAudioStream_waitForStateChange; - ma_proc AAudioStream_getFormat; - ma_proc AAudioStream_getChannelCount; - ma_proc AAudioStream_getSampleRate; - ma_proc AAudioStream_getBufferCapacityInFrames; - ma_proc AAudioStream_getFramesPerDataCallback; - ma_proc AAudioStream_getFramesPerBurst; - ma_proc AAudioStream_requestStart; - ma_proc AAudioStream_requestStop; - ma_device_job_thread jobThread; /* For processing operations outside of the error callback, specifically device disconnections and rerouting. */ - } aaudio; -#endif -#ifdef MA_SUPPORT_OPENSL - struct - { - ma_handle libOpenSLES; - ma_handle SL_IID_ENGINE; - ma_handle SL_IID_AUDIOIODEVICECAPABILITIES; - ma_handle SL_IID_ANDROIDSIMPLEBUFFERQUEUE; - ma_handle SL_IID_RECORD; - ma_handle SL_IID_PLAY; - ma_handle SL_IID_OUTPUTMIX; - ma_handle SL_IID_ANDROIDCONFIGURATION; - ma_proc slCreateEngine; - } opensl; -#endif -#ifdef MA_SUPPORT_WEBAUDIO - struct - { - int _unused; - } webaudio; -#endif -#ifdef MA_SUPPORT_NULL - struct - { - int _unused; - } null_backend; -#endif - }; - - union - { -#if defined(MA_WIN32) - struct - { - /*HMODULE*/ ma_handle hOle32DLL; - ma_proc CoInitialize; - ma_proc CoInitializeEx; - ma_proc CoUninitialize; - ma_proc CoCreateInstance; - ma_proc CoTaskMemFree; - ma_proc PropVariantClear; - ma_proc StringFromGUID2; - - /*HMODULE*/ ma_handle hUser32DLL; - ma_proc GetForegroundWindow; - ma_proc GetDesktopWindow; - - /*HMODULE*/ ma_handle hAdvapi32DLL; - ma_proc RegOpenKeyExA; - ma_proc RegCloseKey; - ma_proc RegQueryValueExA; - - /*HRESULT*/ long CoInitializeResult; - } win32; -#endif -#ifdef MA_POSIX - struct - { - int _unused; - } posix; -#endif - int _unused; - }; }; struct ma_device { ma_context* pContext; ma_device_type type; + ma_threading_mode threadingMode; + ma_bool32 hasAudioThread; ma_uint32 sampleRate; - ma_atomic_device_state state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ - ma_device_data_proc onData; /* Set once at initialization time and should not be changed after. */ - ma_device_notification_proc onNotification; /* Set once at initialization time and should not be changed after. */ - ma_stop_proc onStop; /* DEPRECATED. Use the notification callback instead. Set once at initialization time and should not be changed after. */ - void* pUserData; /* Application defined data. */ + ma_atomic_device_status status; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ + ma_device_data_proc onData; /* Set once at initialization time and should not be changed after. */ + ma_device_notification_proc onNotification; /* Set once at initialization time and should not be changed after. */ + void* pUserData; /* Application defined data. */ + MA_ATOMIC(MA_SIZEOF_PTR, void*) pBackendState; /* Backend state created by the backend. This will be passed to the relevant backend functions. */ +#ifndef MA_NO_THREADING + ma_device_op_queue opQueue; /* Operation queue for executing backend init/uninit/start/stop callbacks on the audio thread rather than the calling thread. */ + ma_device_op_completion_event opCompletionEvent; + ma_thread audioThread; ma_mutex startStopLock; - ma_event wakeupEvent; - ma_event startEvent; - ma_event stopEvent; - ma_thread thread; - ma_result workResult; /* This is set by the worker thread after it's finished doing a job. */ - ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ +#endif + ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ ma_bool8 noPreSilencedOutputBuffer; ma_bool8 noClip; ma_bool8 noDisableDenormals; ma_bool8 noFixedSizedCallback; - ma_atomic_float masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ - ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */ + ma_atomic_float masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ + ma_duplex_rb* pDuplexRB; /* Intermediary buffer for duplex devices. Allocated on the heap to ensure it is aligned to MA_CACHE_LINE_SIZE. Will be null for non-duplex devices. */ struct { ma_resample_algorithm algorithm; @@ -7815,15 +8450,12 @@ struct ma_device { ma_device_id* pID; /* Set to NULL if using default ID, otherwise set to the address of "id". */ ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; ma_format internalFormat; ma_uint32 internalChannels; ma_uint32 internalSampleRate; - ma_channel internalChannelMap[MA_MAX_CHANNELS]; ma_uint32 internalPeriodSizeInFrames; ma_uint32 internalPeriods; ma_channel_mix_mode channelMixMode; @@ -7833,23 +8465,20 @@ struct ma_device ma_uint32 intermediaryBufferCap; ma_uint32 intermediaryBufferLen; /* How many valid frames are sitting in the intermediary buffer. */ void* pInputCache; /* In external format. Can be null. */ - ma_uint64 inputCacheCap; - ma_uint64 inputCacheConsumed; - ma_uint64 inputCacheRemaining; + ma_uint32 inputCacheCap; + ma_uint32 inputCacheConsumed; + ma_uint32 inputCacheRemaining; } playback; struct { ma_device_id* pID; /* Set to NULL if using default ID, otherwise set to the address of "id". */ ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; ma_format internalFormat; ma_uint32 internalChannels; ma_uint32 internalSampleRate; - ma_channel internalChannelMap[MA_MAX_CHANNELS]; ma_uint32 internalPeriodSizeInFrames; ma_uint32 internalPeriods; ma_channel_mix_mode channelMixMode; @@ -7859,226 +8488,9 @@ struct ma_device ma_uint32 intermediaryBufferCap; ma_uint32 intermediaryBufferLen; /* How many valid frames are sitting in the intermediary buffer. */ } capture; - - union - { -#ifdef MA_SUPPORT_WASAPI - struct - { - /*IAudioClient**/ ma_ptr pAudioClientPlayback; - /*IAudioClient**/ ma_ptr pAudioClientCapture; - /*IAudioRenderClient**/ ma_ptr pRenderClient; - /*IAudioCaptureClient**/ ma_ptr pCaptureClient; - /*IMMDeviceEnumerator**/ ma_ptr pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. */ - ma_IMMNotificationClient notificationClient; - /*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */ - /*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */ - ma_uint32 actualBufferSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ - ma_uint32 actualBufferSizeInFramesCapture; - ma_uint32 originalPeriodSizeInFrames; - ma_uint32 originalPeriodSizeInMilliseconds; - ma_uint32 originalPeriods; - ma_performance_profile originalPerformanceProfile; - ma_uint32 periodSizeInFramesPlayback; - ma_uint32 periodSizeInFramesCapture; - void* pMappedBufferCapture; - ma_uint32 mappedBufferCaptureCap; - ma_uint32 mappedBufferCaptureLen; - void* pMappedBufferPlayback; - ma_uint32 mappedBufferPlaybackCap; - ma_uint32 mappedBufferPlaybackLen; - ma_atomic_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ - ma_atomic_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ - ma_uint32 loopbackProcessID; - ma_bool8 loopbackProcessExclude; - ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ - ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ - ma_bool8 noHardwareOffloading; - ma_bool8 allowCaptureAutoStreamRouting; - ma_bool8 allowPlaybackAutoStreamRouting; - ma_bool8 isDetachedPlayback; - ma_bool8 isDetachedCapture; - ma_wasapi_usage usage; - void* hAvrtHandle; - ma_mutex rerouteLock; - } wasapi; -#endif -#ifdef MA_SUPPORT_DSOUND - struct - { - /*LPDIRECTSOUND*/ ma_ptr pPlayback; - /*LPDIRECTSOUNDBUFFER*/ ma_ptr pPlaybackPrimaryBuffer; - /*LPDIRECTSOUNDBUFFER*/ ma_ptr pPlaybackBuffer; - /*LPDIRECTSOUNDCAPTURE*/ ma_ptr pCapture; - /*LPDIRECTSOUNDCAPTUREBUFFER*/ ma_ptr pCaptureBuffer; - } dsound; -#endif -#ifdef MA_SUPPORT_WINMM - struct - { - /*HWAVEOUT*/ ma_handle hDevicePlayback; - /*HWAVEIN*/ ma_handle hDeviceCapture; - /*HANDLE*/ ma_handle hEventPlayback; - /*HANDLE*/ ma_handle hEventCapture; - ma_uint32 fragmentSizeInFrames; - ma_uint32 iNextHeaderPlayback; /* [0,periods). Used as an index into pWAVEHDRPlayback. */ - ma_uint32 iNextHeaderCapture; /* [0,periods). Used as an index into pWAVEHDRCapture. */ - ma_uint32 headerFramesConsumedPlayback; /* The number of PCM frames consumed in the buffer in pWAVEHEADER[iNextHeader]. */ - ma_uint32 headerFramesConsumedCapture; /* ^^^ */ - /*WAVEHDR**/ ma_uint8* pWAVEHDRPlayback; /* One instantiation for each period. */ - /*WAVEHDR**/ ma_uint8* pWAVEHDRCapture; /* One instantiation for each period. */ - ma_uint8* pIntermediaryBufferPlayback; - ma_uint8* pIntermediaryBufferCapture; - ma_uint8* _pHeapData; /* Used internally and is used for the heap allocated data for the intermediary buffer and the WAVEHDR structures. */ - } winmm; -#endif -#ifdef MA_SUPPORT_ALSA - struct - { - /*snd_pcm_t**/ ma_ptr pPCMPlayback; - /*snd_pcm_t**/ ma_ptr pPCMCapture; - /*struct pollfd**/ void* pPollDescriptorsPlayback; - /*struct pollfd**/ void* pPollDescriptorsCapture; - int pollDescriptorCountPlayback; - int pollDescriptorCountCapture; - int wakeupfdPlayback; /* eventfd for waking up from poll() when the playback device is stopped. */ - int wakeupfdCapture; /* eventfd for waking up from poll() when the capture device is stopped. */ - ma_bool8 isUsingMMapPlayback; - ma_bool8 isUsingMMapCapture; - } alsa; -#endif -#ifdef MA_SUPPORT_PULSEAUDIO - struct - { - /*pa_mainloop**/ ma_ptr pMainLoop; - /*pa_context**/ ma_ptr pPulseContext; - /*pa_stream**/ ma_ptr pStreamPlayback; - /*pa_stream**/ ma_ptr pStreamCapture; - } pulse; -#endif -#ifdef MA_SUPPORT_JACK - struct - { - /*jack_client_t**/ ma_ptr pClient; - /*jack_port_t**/ ma_ptr* ppPortsPlayback; - /*jack_port_t**/ ma_ptr* ppPortsCapture; - float* pIntermediaryBufferPlayback; /* Typed as a float because JACK is always floating point. */ - float* pIntermediaryBufferCapture; - } jack; -#endif -#ifdef MA_SUPPORT_COREAUDIO - struct - { - ma_uint32 deviceObjectIDPlayback; - ma_uint32 deviceObjectIDCapture; - /*AudioUnit*/ ma_ptr audioUnitPlayback; - /*AudioUnit*/ ma_ptr audioUnitCapture; - /*AudioBufferList**/ ma_ptr pAudioBufferList; /* Only used for input devices. */ - ma_uint32 audioBufferCapInFrames; /* Only used for input devices. The capacity in frames of each buffer in pAudioBufferList. */ - ma_event stopEvent; - ma_uint32 originalPeriodSizeInFrames; - ma_uint32 originalPeriodSizeInMilliseconds; - ma_uint32 originalPeriods; - ma_performance_profile originalPerformanceProfile; - ma_bool32 isDefaultPlaybackDevice; - ma_bool32 isDefaultCaptureDevice; - ma_bool32 isSwitchingPlaybackDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ - ma_bool32 isSwitchingCaptureDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ - void* pNotificationHandler; /* Only used on mobile platforms. Obj-C object for handling route changes. */ - } coreaudio; -#endif -#ifdef MA_SUPPORT_SNDIO - struct - { - ma_ptr handlePlayback; - ma_ptr handleCapture; - ma_bool32 isStartedPlayback; - ma_bool32 isStartedCapture; - } sndio; -#endif -#ifdef MA_SUPPORT_AUDIO4 - struct - { - int fdPlayback; - int fdCapture; - } audio4; -#endif -#ifdef MA_SUPPORT_OSS - struct - { - int fdPlayback; - int fdCapture; - } oss; -#endif -#ifdef MA_SUPPORT_AAUDIO - struct - { - /*AAudioStream**/ ma_ptr pStreamPlayback; - /*AAudioStream**/ ma_ptr pStreamCapture; - ma_mutex rerouteLock; - ma_atomic_bool32 isTearingDown; - ma_aaudio_usage usage; - ma_aaudio_content_type contentType; - ma_aaudio_input_preset inputPreset; - ma_aaudio_allowed_capture_policy allowedCapturePolicy; - ma_bool32 noAutoStartAfterReroute; - } aaudio; -#endif -#ifdef MA_SUPPORT_OPENSL - struct - { - /*SLObjectItf*/ ma_ptr pOutputMixObj; - /*SLOutputMixItf*/ ma_ptr pOutputMix; - /*SLObjectItf*/ ma_ptr pAudioPlayerObj; - /*SLPlayItf*/ ma_ptr pAudioPlayer; - /*SLObjectItf*/ ma_ptr pAudioRecorderObj; - /*SLRecordItf*/ ma_ptr pAudioRecorder; - /*SLAndroidSimpleBufferQueueItf*/ ma_ptr pBufferQueuePlayback; - /*SLAndroidSimpleBufferQueueItf*/ ma_ptr pBufferQueueCapture; - ma_bool32 isDrainingCapture; - ma_bool32 isDrainingPlayback; - ma_uint32 currentBufferIndexPlayback; - ma_uint32 currentBufferIndexCapture; - ma_uint8* pBufferPlayback; /* This is malloc()'d and is used for storing audio data. Typed as ma_uint8 for easy offsetting. */ - ma_uint8* pBufferCapture; - } opensl; -#endif -#ifdef MA_SUPPORT_WEBAUDIO - struct - { - /* AudioWorklets path. */ - /* EMSCRIPTEN_WEBAUDIO_T */ int audioContext; - /* EMSCRIPTEN_WEBAUDIO_T */ int audioWorklet; - float* pIntermediaryBuffer; - void* pStackBuffer; - ma_result initResult; /* Set to MA_BUSY while initialization is in progress. */ - int deviceIndex; /* We store the device in a list on the JavaScript side. This is used to map our C object to the JS object. */ - } webaudio; -#endif -#ifdef MA_SUPPORT_NULL - struct - { - ma_thread deviceThread; - ma_event operationEvent; - ma_event operationCompletionEvent; - ma_semaphore operationSemaphore; - ma_uint32 operation; - ma_result operationResult; - ma_timer timer; - double priorRunTime; - ma_uint32 currentPeriodFramesRemainingPlayback; - ma_uint32 currentPeriodFramesRemainingCapture; - ma_uint64 lastProcessedFramePlayback; - ma_uint64 lastProcessedFrameCapture; - ma_atomic_bool32 isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ - } null_device; -#endif - }; }; #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) -#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) - #pragma GCC diagnostic pop /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ #endif /* @@ -8104,6 +8516,7 @@ ma_context_init() */ MA_API ma_context_config ma_context_config_init(void); + /* Initializes a context. @@ -8113,8 +8526,8 @@ device. There is one context to many devices, and a device is created from a con Parameters ---------- -backends (in, optional) - A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. +pBackends (in, optional) + A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. See examples on how to use this. backendCount (in, optional) The number of items in `backend`. Ignored if `backend` is NULL. @@ -8138,26 +8551,27 @@ Unsafe. Do not call this function across multiple threads as some backends read Remarks ------- -When `backends` is NULL, the default priority order will be used. Below is a list of backends in priority order: - - |-------------|-----------------------|--------------------------------------------------------| - | Name | Enum Name | Supported Operating Systems | - |-------------|-----------------------|--------------------------------------------------------| - | WASAPI | ma_backend_wasapi | Windows Vista+ | - | DirectSound | ma_backend_dsound | Windows XP+ | - | WinMM | ma_backend_winmm | Windows XP+ (may work on older versions, but untested) | - | Core Audio | ma_backend_coreaudio | macOS, iOS | - | ALSA | ma_backend_alsa | Linux | - | PulseAudio | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | - | JACK | ma_backend_jack | Cross Platform (disabled on BSD and Android) | - | sndio | ma_backend_sndio | OpenBSD | - | audio(4) | ma_backend_audio4 | NetBSD, OpenBSD | - | OSS | ma_backend_oss | FreeBSD | - | AAudio | ma_backend_aaudio | Android 8+ | - | OpenSL|ES | ma_backend_opensl | Android (API level 16+) | - | Web Audio | ma_backend_webaudio | Web (via Emscripten) | - | Null | ma_backend_null | Cross Platform (not used on Web) | - |-------------|-----------------------|--------------------------------------------------------| +When `pBackends` is NULL, the default priority order will be used. Below is a list of backends in priority order: + + +-------------+------------------------------+--------------------------------------------------------+ + | Name | Enum Name | Supported Operating Systems | + +-------------+------------------------------+--------------------------------------------------------+ + | WASAPI | ma_device_backend_wasapi | Windows Vista+ | + | DirectSound | ma_device_backend_dsound | Windows XP+ | + | WinMM | ma_device_backend_winmm | Windows 95+ | + | Core Audio | ma_device_backend_coreaudio | macOS, iOS | + | PipeWire | ma_device_backend_pipewire | Linux | + | PulseAudio | ma_device_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | + | JACK | ma_device_backend_jack | Cross Platform (disabled on BSD and Android) | + | ALSA | ma_device_backend_alsa | Linux | + | sndio | ma_device_backend_sndio | OpenBSD | + | audio(4) | ma_device_backend_audio4 | NetBSD, OpenBSD | + | OSS | ma_device_backend_oss | FreeBSD | + | AAudio | ma_device_backend_aaudio | Android 8+ | + | OpenSL|ES | ma_device_backend_opensl | Android (API level 16+) | + | Web Audio | ma_device_backend_webaudio | Web (via Emscripten) | + | Null | ma_device_backend_null | Cross Platform (not used on Web) | + +-------------+------------------------------+--------------------------------------------------------+ The context can be configured via the `pConfig` argument. The config object is initialized with `ma_context_config_init()`. Individual configuration settings can then be set directly on the structure. Below are the members of the `ma_context_config` object. @@ -8169,7 +8583,7 @@ can then be set directly on the structure. Below are the members of the `ma_cont threadPriority The desired priority to use for the audio thread. Allowable values include the following: - |--------------------------------------| + +--------------------------------------+ | Thread Priority | |--------------------------------------| | ma_thread_priority_idle | @@ -8180,7 +8594,7 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_thread_priority_highest (default) | | ma_thread_priority_realtime | | ma_thread_priority_default | - |--------------------------------------| + +--------------------------------------+ threadStackSize The desired size of the stack for the audio thread. Defaults to the operating system's default. @@ -8192,18 +8606,13 @@ can then be set directly on the structure. Below are the members of the `ma_cont Structure containing custom allocation callbacks. Leaving this at defaults will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. These allocation callbacks will be used for anything tied to the context, including devices. - alsa.useVerboseDeviceEnumeration - ALSA will typically enumerate many different devices which can be intrusive and not user-friendly. To combat this, miniaudio will enumerate only unique - card/device pairs by default. The problem with this is that you lose a bit of flexibility and control. Setting alsa.useVerboseDeviceEnumeration makes - it so the ALSA backend includes all devices. Defaults to false. - - pulse.pApplicationName + pulseaudio.pApplicationName PulseAudio only. The application name to use when initializing the PulseAudio context with `pa_context_new()`. - pulse.pServerName + pulseaudio.pServerName PulseAudio only. The name of the server to connect to with `pa_context_connect()`. - pulse.tryAutoSpawn + pulseaudio.tryAutoSpawn PulseAudio only. Whether or not to try automatically starting the PulseAudio daemon. Defaults to false. If you set this to true, keep in mind that miniaudio uses a trial and error method to find the most appropriate backend, and this will result in the PulseAudio daemon starting which may be intrusive for the end user. @@ -8211,9 +8620,9 @@ can then be set directly on the structure. Below are the members of the `ma_cont coreaudio.sessionCategory iOS only. The session category to use for the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. - |-----------------------------------------|-------------------------------------| + +-----------------------------------------+-------------------------------------+ | miniaudio Token | Core Audio Token | - |-----------------------------------------|-------------------------------------| + |-----------------------------------------+-------------------------------------+ | ma_ios_session_category_ambient | AVAudioSessionCategoryAmbient | | ma_ios_session_category_solo_ambient | AVAudioSessionCategorySoloAmbient | | ma_ios_session_category_playback | AVAudioSessionCategoryPlayback | @@ -8222,14 +8631,14 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_ios_session_category_multi_route | AVAudioSessionCategoryMultiRoute | | ma_ios_session_category_none | AVAudioSessionCategoryAmbient | | ma_ios_session_category_default | AVAudioSessionCategoryAmbient | - |-----------------------------------------|-------------------------------------| + +-----------------------------------------+-------------------------------------+ coreaudio.sessionCategoryOptions iOS only. Session category options to use with the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. - |---------------------------------------------------------------------------|------------------------------------------------------------------| + +---------------------------------------------------------------------------+------------------------------------------------------------------+ | miniaudio Token | Core Audio Token | - |---------------------------------------------------------------------------|------------------------------------------------------------------| + +---------------------------------------------------------------------------+------------------------------------------------------------------+ | ma_ios_session_category_option_mix_with_others | AVAudioSessionCategoryOptionMixWithOthers | | ma_ios_session_category_option_duck_others | AVAudioSessionCategoryOptionDuckOthers | | ma_ios_session_category_option_allow_bluetooth | AVAudioSessionCategoryOptionAllowBluetooth | @@ -8237,7 +8646,7 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_ios_session_category_option_interrupt_spoken_audio_and_mix_with_others | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers | | ma_ios_session_category_option_allow_bluetooth_a2dp | AVAudioSessionCategoryOptionAllowBluetoothA2DP | | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | - |---------------------------------------------------------------------------|------------------------------------------------------------------| + +---------------------------------------------------------------------------+------------------------------------------------------------------+ coreaudio.noAudioSessionActivate iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. @@ -8281,11 +8690,12 @@ want an error to be returned if no valid backend is available which they achieve For the configuration, the program wants to capture any log messages so they can, for example, route it to a log file and user interface. ```c -ma_backend backends[] = { - ma_backend_alsa, - ma_backend_pulseaudio, - ma_backend_wasapi, - ma_backend_dsound +ma_device_backend_config backends[] = +{ + { ma_device_backend_alsa, NULL }, + { ma_device_backend_pulseaudio, NULL }, + { ma_device_backend_wasapi, NULL }, + { ma_device_backend_dsound, NULL } }; ma_log log; @@ -8308,13 +8718,17 @@ if (result != MA_SUCCESS) { ma_log_register_callback(ma_context_get_log(&context), ma_log_callback_init(my_log_callback, pMyLogUserData)); ``` +When listing the backends you want to support, it's done in pairs. The first item is the backend identifier. The second is a pointer to a backend-specific +configuration object. Because we are only using stock backends in this example, we can set this to null, and any backend-specific configurations can be +set directly in the `ma_context_config` object. + See Also -------- ma_context_config_init() ma_context_uninit() */ -MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pConfig, ma_context* pContext); +MA_API ma_result ma_context_init(const ma_device_backend_config* pBackends, ma_uint32 backendCount, const ma_context_config* pConfig, ma_context* pContext); /* Uninitializes a context. @@ -8341,6 +8755,7 @@ ma_context_init() */ MA_API ma_result ma_context_uninit(ma_context* pContext); + /* Retrieves the size of the ma_context object. @@ -8348,6 +8763,7 @@ This is mainly for the purpose of bindings to know how much memory to allocate. */ MA_API size_t ma_context_sizeof(void); + /* Retrieves a pointer to the log object associated with this context. @@ -8367,6 +8783,69 @@ NULL will be returned. */ MA_API ma_log* ma_context_get_log(ma_context* pContext); + +/* +Retrieves the allocation callbacks associated with this context. + + +Remarks +------- +You can pass this into `ma_malloc()`, etc. to allocate memory using the allocation callbacks. This +is mainly intended for use by backends. + + +Return Value +------------ +A pointer to the `ma_allocation_callbacks` object. +*/ +MA_API const ma_allocation_callbacks* ma_context_get_allocation_callbacks(ma_context* pContext); + + +/* +Retrieves the thread priority associated with this context. + + +Remarks +------- +This is intended for use by backends that want to run their own thread while respecting the thread +priority requested by the application. + + +Return Value +----------- +The thread priority to use for audio threads by backends. +*/ +MA_API ma_thread_priority ma_context_get_thread_priority(ma_context* pContext); + + +/* +Retrieves the backend-specific state object. + + +Remarks +------- +This should only ever by called by backends. A normal application should never call this function. + +When a backend is initialized, it allocates a backend-specific data structure which is then linked +to the `ma_context` object. This function retrieves a pointer to that backend-specific data so it +can be used by the backend. The meaning of this pointer is only known by the backend that created +it, and is therefore the only thing that need call this function. + + +Return Value +------------ +A pointer to the backend-specific state object. +*/ +MA_API void* ma_context_get_backend_state(ma_context* pContext); + + +/* +Retrieves basic information about the backend, most notably the name. +*/ +MA_API void ma_context_get_backend_info(ma_context* pContext, ma_device_backend_info* pBackendInfo); + + + /* Enumerates over every device (both playback and capture). @@ -8531,22 +9010,6 @@ This leaves pDeviceInfo unmodified in the result of an error. */ MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo); -/* -Determines if the given context supports loopback mode. - - -Parameters ----------- -pContext (in) - A pointer to the context getting queried. - - -Return Value ------------- -MA_TRUE if the context supports loopback mode; MA_FALSE otherwise. -*/ -MA_API ma_bool32 ma_context_is_loopback_supported(ma_context* pContext); - /* @@ -8682,24 +9145,21 @@ then be set directly on the structure. Below are the members of the `ma_device_c Must be `ma_device_type_playback`, `ma_device_type_capture`, `ma_device_type_duplex` of `ma_device_type_loopback`. sampleRate - The sample rate, in hertz. The most common sample rates are 48000 and 44100. Setting this to 0 will use the device's native sample rate. + The sample rate, in hertz. The most common sample rates are 48000 and 44100. Setting this to 0 will use the device's native sample rate. This can be + retrieved after initialization from the device object directly with `ma_device_get_sample_rate()`. periodSizeInFrames The desired size of a period in PCM frames. If this is 0, `periodSizeInMilliseconds` will be used instead. If both are 0 the default buffer size will - be used depending on the selected performance profile. This value affects latency. See below for details. + be used. This value affects latency. See below for details. periodSizeInMilliseconds The desired size of a period in milliseconds. If this is 0, `periodSizeInFrames` will be used instead. If both are 0 the default buffer size will be - used depending on the selected performance profile. The value affects latency. See below for details. + used. The value affects latency. See below for details. periods The number of periods making up the device's entire buffer. The total buffer size is `periodSizeInFrames` or `periodSizeInMilliseconds` multiplied by this value. This is just a hint as backends will be the ones who ultimately decide how your periods will be configured. - performanceProfile - A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or - `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at its default value. - noPreSilencedOutputBuffer When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data @@ -8740,7 +9200,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c resampling.linear.lpfOrder The linear resampler applies a low-pass filter as part of its processing for anti-aliasing. This setting controls the order of the filter. The higher the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is - `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. + `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. It must be a multiple of 2. playback.pDeviceID A pointer to a `ma_device_id` structure containing the ID of the playback device to initialize. Setting this NULL (default) will use the system's @@ -8748,15 +9208,15 @@ then be set directly on the structure. Below are the members of the `ma_device_c playback.format The sample format to use for playback. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after - initialization from the device object directly with `device.playback.format`. + initialization from the device object directly with `ma_device_get_playback_format()`. playback.channels The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization - from the device object directly with `device.playback.channels`. + from the device object directly with `ma_device_get_playback_channels()`. playback.pChannelMap The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.playback.pChannelMap`. When set, the buffer should contain `channels` items. + device object direct with `ma_device_get_playback_channel_map()`. When set, the buffer should contain `channels` items. playback.shareMode The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -8769,15 +9229,15 @@ then be set directly on the structure. Below are the members of the `ma_device_c capture.format The sample format to use for capture. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after - initialization from the device object directly with `device.capture.format`. + initialization from the device object directly with `ma_device_get_capture_format()`. capture.channels The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization - from the device object directly with `device.capture.channels`. + from the device object directly with `ma_device_get_capture_channels()`. capture.pChannelMap The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.capture.pChannelMap`. When set, the buffer should contain `channels` items. + device object direct with `ma_device_get_capture_channel_map()`. When set, the buffer should contain `channels` items. capture.shareMode The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -8809,13 +9269,13 @@ then be set directly on the structure. Below are the members of the `ma_device_c alsa.noAutoResample ALSA only. When set to true, disables ALSA's automatic resampling by including the SND_PCM_NO_AUTO_RESAMPLE flag. Defaults to false. - pulse.pStreamNamePlayback + pulseaudio.pStreamNamePlayback PulseAudio only. Sets the stream name for playback. - pulse.pStreamNameCapture + pulseaudio.pStreamNameCapture PulseAudio only. Sets the stream name for capture. - pulse.channelMap + pulseaudio.channelMap PulseAudio only. Sets the channel map that is requested from PulseAudio. See MA_PA_CHANNEL_MAP_* constants. Defaults to MA_PA_CHANNEL_MAP_AIFF. coreaudio.allowNominalSampleRateChange @@ -8854,9 +9314,7 @@ Once initialized, the device's config is immutable. If you need to change the co After initializing the device it will be in a stopped state. To start it, use `ma_device_start()`. -If both `periodSizeInFrames` and `periodSizeInMilliseconds` are set to zero, it will default to `MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY` or -`MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE`, depending on whether or not `performanceProfile` is set to `ma_performance_profile_low_latency` or -`ma_performance_profile_conservative`. +If both `periodSizeInFrames` and `periodSizeInMilliseconds` are set to zero, it will use `MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS` which defaults to 10ms. If you request exclusive mode and the backend does not support it an error will be returned. For robustness, you may want to first try initializing the device in exclusive mode, and then fall back to shared mode if required. Alternatively you can just request shared mode (the default if you leave it unset in the @@ -8866,8 +9324,8 @@ Starting with Windows 10, miniaudio will use low-latency shared mode where possi When sending or receiving data to/from a device, miniaudio will internally perform a format conversion to convert between the format specified by the config and the format used internally by the backend. If you pass in 0 for the sample format, channel count, sample rate _and_ channel map, data transmission will run -on an optimized pass-through fast path. You can retrieve the format, channel count and sample rate by inspecting the `playback/capture.format`, -`playback/capture.channels` and `sampleRate` members of the device object. +on an optimized pass-through fast path. You can retrieve the format, channel count and sample rate with `ma_device_get_playback_format()`, +`ma_device_get_capture_format()`, `ma_device_get_playback_channels()`, `ma_device_get_capture_channels()` and `ma_device_get_sample_rate()`. When compiling for UWP you must ensure you call this function on the main UI thread because the operating system may need to present the user with a message asking for permissions. Please refer to the official documentation for ActivateAudioInterfaceAsync() for more information. @@ -8957,7 +9415,7 @@ allows you to configure the internally created context. Parameters ---------- -backends (in, optional) +pBackends (in, optional) A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. backendCount (in, optional) @@ -9004,7 +9462,7 @@ ma_device_uninit() ma_device_config_init() ma_context_init() */ -MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pContextConfig, const ma_device_config* pConfig, ma_device* pDevice); +MA_API ma_result ma_device_init_ex(const ma_device_backend_config* pBackends, ma_uint32 backendCount, const ma_context_config* pContextConfig, const ma_device_config* pConfig, ma_device* pDevice); /* Uninitializes a device. @@ -9046,11 +9504,65 @@ Retrieves a pointer to the context that owns the given device. */ MA_API ma_context* ma_device_get_context(ma_device* pDevice); +/* +Retrieves the user data pointer of the given device. +*/ +MA_API void* ma_device_get_user_data(ma_device* pDevice); + /* Helper function for retrieving the log object associated with the context that owns this device. */ MA_API ma_log* ma_device_get_log(ma_device* pDevice); +/* +Helper function for retrieving the device type (playback, capture, etc.) +*/ +MA_API ma_device_type ma_device_get_type(ma_device* pDevice); + +/* +Helper function for retrieving the threading mode. +*/ +MA_API ma_threading_mode ma_device_get_threading_mode(ma_device* pDevice); + + + +/* +Retrieves the allocation callbacks associated with this context. + + +Remarks +------- +You can pass this into `ma_malloc()`, etc. to allocate memory using the allocation callbacks. This +is mainly intended for use by backends. + + +Return Value +------------ +A pointer to the `ma_allocation_callbacks` object. +*/ +MA_API const ma_allocation_callbacks* ma_device_get_allocation_callbacks(ma_device* pDevice); + + +/* +Retrieves the backend-specific state object. + + +Remarks +------- +This should only ever by called by backends. A normal application should never call this function. + +When a backend is initialized, it allocates a backend-specific data structure which is then linked +to the `ma_device` object. This function retrieves a pointer to that backend-specific data so it +can be used by the backend. The meaning of this pointer is only known by the backend that created +it, and is therefore the only thing that need call this function. + + +Return Value +------------ +A pointer to the backend-specific state object. +*/ +MA_API void* ma_device_get_backend_state(ma_device* pDevice); + /* Retrieves information about the device. @@ -9265,30 +9777,32 @@ MA_API ma_bool32 ma_device_is_started(const ma_device* pDevice); /* -Retrieves the state of the device. +Retrieves the status of the device. Parameters ---------- pDevice (in) - A pointer to the device whose state is being retrieved. + A pointer to the device whose status is being retrieved. Return Value ------------ -The current state of the device. The return value will be one of the following: - - +-------------------------------+------------------------------------------------------------------------------+ - | ma_device_state_uninitialized | Will only be returned if the device is in the middle of initialization. | - +-------------------------------+------------------------------------------------------------------------------+ - | ma_device_state_stopped | The device is stopped. The initial state of the device after initialization. | - +-------------------------------+------------------------------------------------------------------------------+ - | ma_device_state_started | The device started and requesting and/or delivering audio data. | - +-------------------------------+------------------------------------------------------------------------------+ - | ma_device_state_starting | The device is in the process of starting. | - +-------------------------------+------------------------------------------------------------------------------+ - | ma_device_state_stopping | The device is in the process of stopping. | - +-------------------------------+------------------------------------------------------------------------------+ +The current status of the device. The return value will be one of the following: + + +--------------------------------+-------------------------------------------------------------------------------+ + | ma_device_status_uninitialized | Will only be returned if the device is in the middle of initialization. | + +--------------------------------+-------------------------------------------------------------------------------+ + | ma_device_status_stopped | The device is stopped. The initial status of the device after initialization. | + +--------------------------------+-------------------------------------------------------------------------------+ + | ma_device_status_started | The device started and requesting and/or delivering audio data. | + +--------------------------------+-------------------------------------------------------------------------------+ + | ma_device_status_starting | The device is in the process of starting. | + +--------------------------------+-------------------------------------------------------------------------------+ + | ma_device_status_stopping | The device is in the process of stopping. | + +--------------------------------+-------------------------------------------------------------------------------+ + | ma_device_status_errored | The device is in an error state. See remarks. | + +--------------------------------+-------------------------------------------------------------------------------+ Thread Safety @@ -9304,68 +9818,368 @@ Safe. This is implemented as a simple accessor. Remarks ------- -The general flow of a devices state goes like this: +The general flow of a devices status goes like this: ``` - ma_device_init() -> ma_device_state_uninitialized -> ma_device_state_stopped - ma_device_start() -> ma_device_state_starting -> ma_device_state_started - ma_device_stop() -> ma_device_state_stopping -> ma_device_state_stopped + ma_device_init() -> ma_device_status_uninitialized -> ma_device_status_stopped + ma_device_start() -> ma_device_status_starting -> ma_device_status_started + ma_device_stop() -> ma_device_status_stopping -> ma_device_status_stopped ``` -When the state of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the +When the status of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the value returned by this function could potentially be out of sync. If this is significant to your program you need to implement your own synchronization. + +If an internal error occurs the backend may put the device into an error state with `ma_device_status_errored`. When this happens, the +device is unusable and your only option is uninitialize it and try initializing it again. Backends will usually do their best to work +around error states so an errored status should be rare. */ -MA_API ma_device_state ma_device_get_state(const ma_device* pDevice); +MA_API ma_device_status ma_device_get_status(const ma_device* pDevice); + +/* +Puts the device into an errored state. + + +Parameters +---------- +pDevice (in) + A pointer to the device that is being put into an errored state. + + +Remarks +------- +Once the device is has been put into this state there is no changing it. The only thing an application can do is reinitialize the device. +*/ +MA_API void ma_device_set_errored(ma_device* pDevice); /* -Performs post backend initialization routines for setting up internal data conversion. +Steps the device. -This should be called whenever the backend is initialized. The only time this should be called from -outside of miniaudio is if you're implementing a custom backend, and you would only do it if you -are reinitializing the backend due to rerouting or reinitializing for some reason. +This should only be called in single-threaded mode. It should be called in a loop. +*/ +MA_API ma_result ma_device_step(ma_device* pDevice, ma_blocking_mode blockingMode); + + +/* +Retrieve the format of the device. Parameters ---------- -pDevice [in] - A pointer to the device. +pDevice (in) + A pointer to the device whose format is being retrieved. -deviceType [in] - The type of the device that was just reinitialized. +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose format is being retrieved. -pPlaybackDescriptor [in] - The descriptor of the playback device containing the internal data format and buffer sizes. -pPlaybackDescriptor [in] - The descriptor of the capture device containing the internal data format and buffer sizes. +Return Value +------------ +The public facing format of the device. + +Will return `ma_format_unknown` if an invalid device or device type is specified. + + +Remarks +------- +Since the capture and playback sides can have different sample formats you need to use the `deviceType` parameter to discrimate between +the two. Use `ma_device_type_playback` for the playback side, or `ma_device_type_capture` for the capture side. You cannot use +`ma_device_type_duplex`. Doing so will result in `ma_format_unknown` being returned. +*/ +MA_API ma_format ma_device_get_format(const ma_device* pDevice, ma_device_type deviceType); + +/* +The equivalent to `ma_device_get_format(pDevice, ma_device_type_playback)` +*/ +MA_API ma_format ma_device_get_playback_format(const ma_device* pDevice); + +/* +The equivalent to `ma_device_get_format(pDevice, ma_device_type_capture)` +*/ +MA_API ma_format ma_device_get_capture_format(const ma_device* pDevice); + +/* +Retrieve the channel count of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose channel count is being retrieved. + +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose channel count is being retrieved. Return Value ------------ -MA_SUCCESS if successful; any other error otherwise. +The public facing channel count of the device. +Will return 0 if an invalid device or device type is specified. -Thread Safety -------------- -Unsafe. This will be reinitializing internal data converters which may be in use by another thread. +Remarks +------- +Since the capture and playback sides can have different channel counts you need to use the `deviceType` parameter to discrimate between +the two. Use `ma_device_type_playback` for the playback side, or `ma_device_type_capture` for the capture side. You cannot use +`ma_device_type_duplex`. Doing so will result in 0 being returned. +*/ +MA_API ma_uint32 ma_device_get_channels(const ma_device* pDevice, ma_device_type deviceType); + +/* +The equivalent to `ma_device_get_channels(pDevice, ma_device_type_playback)`. +*/ +MA_API ma_uint32 ma_device_get_playback_channels(const ma_device* pDevice); -Callback Safety ---------------- -Unsafe. This will be reinitializing internal data converters which may be in use by the callback. +/* +The equivalent to `ma_device_get_channels(pDevice, ma_device_type_capture)`. +*/ +MA_API ma_uint32 ma_device_get_capture_channels(const ma_device* pDevice); + +/* +Retrieve the sample rate of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose sample rate is being retrieved. + + +Return Value +------------ +The public facing sample rate of the device. + + +Remarks +------- +Unlike the sample format, channel count and channel map, the sample rate is shared between the capture and playback sides of the device +which means a device type is not necessary. +*/ +MA_API ma_uint32 ma_device_get_sample_rate(const ma_device* pDevice); + +/* +Retrieves the channel map of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose channel map is being retrieved. + +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose channel map is being retrieved. + +pChannelMap (out) + A pointer to the buffer that will receive the channel map. + +channelMapCap (in) + The capacity of `pChannelMap`. + + +Remarks +------- +Since the capture and playback sides can have different channel maps you need to use the `deviceType` parameter to discrimate between +the two. Use `ma_device_type_playback` for the playback side, or `ma_device_type_capture` for the capture side. You cannot use +`ma_device_type_duplex`. Doing so will result in a blank channel map being returned. +*/ +MA_API void ma_device_get_channel_map(const ma_device* pDevice, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap); + +/* +The equivalent to `ma_device_get_channel_map(pDevice, ma_device_type_playback, pChannelMap, channelMapCap)` +*/ +MA_API void ma_device_get_playback_channel_map(const ma_device* pDevice, ma_channel* pChannelMap, size_t channelMapCap); + +/* +The equivalent to `ma_device_get_channel_map(pDevice, ma_device_type_capture, pChannelMap, channelMapCap)` +*/ +MA_API void ma_device_get_capture_channel_map(const ma_device* pDevice, ma_channel* pChannelMap, size_t channelMapCap); + + +/* +Retrieve the internal format of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose internal format is being retrieved. + +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose internal format is being retrieved. + + +Return Value +------------ +The internal format of the device. + +Will return `ma_format_unknown` if an invalid device or device type is specified. Remarks ------- -For a duplex device, you can call this for only one side of the system. This is why the deviceType -is specified as a parameter rather than deriving it from the device. +This would usually only be called by backends. A normal program should almost never need to call this. + +Since the capture and playback sides can have different sample formats you need to use the `deviceType` parameter to discrimate between +the two. Use `ma_device_type_playback` for the playback side, or `ma_device_type_capture` for the capture side. You cannot use +`ma_device_type_duplex`. Doing so will result in `ma_format_unknown` being returned. +*/ +MA_API ma_format ma_device_get_internal_format(const ma_device* pDevice, ma_device_type deviceType); + +/* +Retrieve the internal channel count of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose internal channel count is being retrieved. + +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose internal channel count is being retrieved. + + +Return Value +------------ +The internal channel count of the device. + +Will return 0 if an invalid device or device type is specified. + + +Remarks +------- +This would usually only be called by backends. A normal program should almost never need to call this. + +Since the capture and playback sides can have different channel counts you need to use the `deviceType` parameter to discrimate between +the two. Use `ma_device_type_playback` for the playback side, or `ma_device_type_capture` for the capture side. You cannot use +`ma_device_type_duplex`. Doing so will result in 0 being returned. +*/ +MA_API ma_uint32 ma_device_get_internal_channels(const ma_device* pDevice, ma_device_type deviceType); + +/* +Retrieve the internal sample rate of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose sample rate is being retrieved. + +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose internal sample rate is being retrieved. + + +Return Value +------------ +The internal sample rate of the device. + +Will return 0 if an invalid device or device type is specified. + + +Remarks +------- +This would usually only be called by backends. A normal program should almost never need to call this. + +Since the capture and playback sides can have different internal sample rates you need to use the `deviceType` parameter to discrimate +between the two. Use `ma_device_type_playback` for the playback side, or `ma_device_type_capture` for the capture side. You cannot use +`ma_device_type_duplex`. Doing so will result in 0 being returned. +*/ +MA_API ma_uint32 ma_device_get_internal_sample_rate(const ma_device* pDevice, ma_device_type deviceType); + +/* +Retrieves the internal channel map of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose internal channel map is being retrieved. + +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose internal channel map is being retrieved. + +pChannelMap (out) + A pointer to the buffer that will receive the internal channel map. + +channelMapCap (in) + The capacity of `pChannelMap`. + + +Remarks +------- +This would usually only be called by backends. A normal program should almost never need to call this. + +Since the capture and playback sides can have different channel maps you need to use the `deviceType` parameter to discrimate between +the two. Use `ma_device_type_playback` for the playback side, or `ma_device_type_capture` for the capture side. You cannot use +`ma_device_type_duplex`. Doing so will result in a blank channel map being returned. +*/ +MA_API void ma_device_get_internal_channel_map(const ma_device* pDevice, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap); + + +/* +Updates the public and internal device descriptors. + +A normal application should never be calling this directly. It would only be called by device backends in response to a device reroute. + +Information describing a device, such as its ID, data format and period size is specified in a structure referred to as a "descriptor". +Where this function matters is with the data format. With the sample format, channel count, sample rate and channel map, there is the +public facing data format which the application requests, and then there is the actual data format that is used by the backend internally. +Sitting between these two is a data converter. The public descriptor will never change after initialization, but sometimes the backend +will change the internal descriptor. When this happens, the data converter needs to be reinitialized, and that is when a backend would +want to call this function. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose descriptor is being updated. + +deviceType (in) + Either `ma_device_type_playback` or `ma_device_type_capture` specifying which side of the device whose descriptor is being updated. + +pPublicDescriptor (in, optional) + A pointer to the public facing descriptor. This should always be NULL. + +pInternalDescriptor (in) + A pointer to the new internal descriptor. This cannot be NULL. + + +Return Value +------------ +`MA_SUCCESS` if successful; any other result code if an error occurs. + + +Remarks +------- +If you are a normal consumer of miniaudio, do not ever call this function. It should only be called by device backends. +*/ +MA_API ma_result ma_device_update_descriptor(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pPublicDescriptor, const ma_device_descriptor* pInternalDescriptor); + + +/* +Gets the period size of the device in frames. + +The returned value will be the frame count that will be passed into the data callback, except for +when the `noFixedSizedCallback` config option is set to true during device initialization. In this +case the returned value will be the period size that backend has reported, which might be +unreliable. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose period size is being retrieved. + + +Return Value +------------ +The period size of the device. -You do not need to call this manually unless you are doing a custom backend, in which case you need -only do it if you're manually performing rerouting or reinitialization. +If you configured the device with `noFixedSizedCallback = MA_TRUE` the returned value should be +treated as a hint. */ -MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pPlaybackDescriptor, const ma_device_descriptor* pCaptureDescriptor); +MA_API ma_uint32 ma_device_get_period_size_in_frames(ma_device* pDevice); /* @@ -9599,14 +10413,27 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void /* -Calculates an appropriate buffer size from a descriptor, native sample rate and performance profile. +Notification helpers. Should only be called from backends. +*/ +MA_API void ma_device_post_notification(ma_device_notification notification); +MA_API void ma_device_post_notification_started(ma_device* pDevice); +MA_API void ma_device_post_notification_stopped(ma_device* pDevice); +MA_API void ma_device_post_notification_rerouted(ma_device* pDevice); +MA_API void ma_device_post_notification_interruption_began(ma_device* pDevice); +MA_API void ma_device_post_notification_interruption_ended(ma_device* pDevice); +MA_API void ma_device_post_notification_unlocked(ma_device* pDevice); +MA_API void ma_device_post_notification_errored(ma_device* pDevice); + + + +/* +Calculates an appropriate buffer size from a descriptor and native sample rate. This function is used by backends for helping determine an appropriately sized buffer to use with the device depending on the values of `periodSizeInFrames` and `periodSizeInMilliseconds` in the `pDescriptor` object. Since buffer size calculations based on time depends on the sample rate, a best guess at the device's native sample rate is also required which is where `nativeSampleRate` -comes in. In addition, the performance profile is also needed for cases where both the period size -in frames and milliseconds are both zero. +comes in. Parameters @@ -9620,11 +10447,6 @@ nativeSampleRate (in) `pDescriptor` is zero. In this case, `periodSizeInMilliseconds` will be used instead, in which case a sample rate is required to convert to a size in frames. -performanceProfile (in) - When both the `periodSizeInFrames` and `periodSizeInMilliseconds` members of `pDescriptor` are - zero, miniaudio will fall back to a buffer size based on the performance profile. The profile - to use for this calculation is determine by this parameter. - Return Value ------------ @@ -9648,102 +10470,38 @@ Remarks If `nativeSampleRate` is zero, this function will fall back to `pDescriptor->sampleRate`. If that is also zero, `MA_DEFAULT_SAMPLE_RATE` will be used instead. */ -MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile); - - - -/* -Retrieves a friendly name for a backend. -*/ -MA_API const char* ma_get_backend_name(ma_backend backend); - -/* -Retrieves the backend enum from the given name. -*/ -MA_API ma_result ma_get_backend_from_name(const char* pBackendName, ma_backend* pBackend); - -/* -Determines whether or not the given backend is available by the compilation environment. -*/ -MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend); +MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate); -/* -Retrieves compile-time enabled backends. - - -Parameters ----------- -pBackends (out, optional) - A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting - the capacity of the buffer to `MA_BACKEND_COUNT` will guarantee it's large enough for all backends. - -backendCap (in) - The capacity of the `pBackends` buffer. - -pBackendCount (out) - A pointer to the variable that will receive the enabled backend count. - - -Return Value ------------- -MA_SUCCESS if successful. -MA_INVALID_ARGS if `pBackendCount` is NULL. -MA_NO_SPACE if the capacity of `pBackends` is not large enough. - -If `MA_NO_SPACE` is returned, the `pBackends` buffer will be filled with `*pBackendCount` values. - - -Thread Safety -------------- -Safe. - - -Callback Safety ---------------- -Safe. - - -Remarks -------- -If you want to retrieve the number of backends so you can determine the capacity of `pBackends` buffer, you can call -this function with `pBackends` set to NULL. - -This will also enumerate the null backend. If you don't want to include this you need to check for `ma_backend_null` -when you enumerate over the returned backends and handle it appropriately. Alternatively, you can disable it at -compile time with `MA_NO_NULL`. - -The returned backends are determined based on compile time settings, not the platform it's currently running on. For -example, PulseAudio will be returned if it was enabled at compile time, even when the user doesn't actually have -PulseAudio installed. - - -Example 1 ---------- -The example below retrieves the enabled backend count using a fixed sized buffer allocated on the stack. The buffer is -given a capacity of `MA_BACKEND_COUNT` which will guarantee it'll be large enough to store all available backends. -Since `MA_BACKEND_COUNT` is always a relatively small value, this should be suitable for most scenarios. - -``` -ma_backend enabledBackends[MA_BACKEND_COUNT]; -size_t enabledBackendCount; - -result = ma_get_enabled_backends(enabledBackends, MA_BACKEND_COUNT, &enabledBackendCount); -if (result != MA_SUCCESS) { - // Failed to retrieve enabled backends. Should never happen in this example since all inputs are valid. -} -``` +/* BEG ma_device_state_async.h */ +typedef struct ma_device_state_async +{ + ma_device_type deviceType; + ma_semaphore semaphore; + struct + { + ma_spinlock lock; + ma_format format; + ma_uint32 channels; + ma_uint32 frameCap; + ma_uint32 frameCount; + void* pBuffer; /* An offset of internal.pBuffer. */ + } playback, capture; + struct + { + void* pBuffer; /* One buffer allocation for both playback and capture. */ + } internal; +} ma_device_state_async; -See Also --------- -ma_is_backend_enabled() -*/ -MA_API ma_result ma_get_enabled_backends(ma_backend* pBackends, size_t backendCap, size_t* pBackendCount); - -/* -Determines whether or not loopback mode is support by a backend. -*/ -MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend); +MA_API ma_result ma_device_state_async_init(ma_device_type deviceType, const ma_device_descriptor* pDescriptorPlayback, const ma_device_descriptor* pDescriptorCapture, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_state_async* pAsyncDeviceState); +MA_API void ma_device_state_async_uninit(ma_device_state_async* pAsyncDeviceState, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_device_state_async_resize(ma_device_state_async* pAsyncDeviceState, ma_uint32 sizeInFramesPlayback, ma_uint32 sizeInFramesCapture, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_device_state_async_flush(ma_device_state_async* pAsyncDeviceState); +MA_API void ma_device_state_async_release(ma_device_state_async* pAsyncDeviceState); +MA_API void ma_device_state_async_wait(ma_device_state_async* pAsyncDeviceState); +MA_API ma_result ma_device_state_async_step(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice, ma_blocking_mode blockingMode, ma_result (* extra)(ma_device* pDevice)); /* Returns MA_SUCCESS if some data was processed, MA_NO_DATA_AVAILABLE if no data was processed. The `extra` function is optional, and is used for doing backend-specific stuff before doing data processing (an example might be device rerouting). */ +MA_API void ma_device_state_async_process(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); +/* END ma_device_state_async.h */ #endif /* MA_NO_DEVICE_IO */ @@ -9883,9 +10641,9 @@ typedef enum typedef enum { - ma_seek_origin_start, - ma_seek_origin_current, - ma_seek_origin_end /* Not used by decoders. */ + MA_SEEK_SET, + MA_SEEK_CUR, + MA_SEEK_END } ma_seek_origin; typedef struct @@ -9938,7 +10696,8 @@ typedef enum ma_encoding_format_wav, ma_encoding_format_flac, ma_encoding_format_mp3, - ma_encoding_format_vorbis + ma_encoding_format_vorbis, + ma_encoding_format_opus } ma_encoding_format; #endif @@ -9955,26 +10714,47 @@ you do your own synchronization. typedef struct ma_decoder ma_decoder; +MA_API ma_encoding_format ma_encoding_format_from_path(const char* pFilePath); +MA_API ma_encoding_format ma_encoding_format_from_path_w(const wchar_t* pFilePath); + + typedef struct { ma_format preferredFormat; - ma_uint32 seekPointCount; /* Set to > 0 to generate a seektable if the decoding backend supports it. */ + ma_uint32 seekPointCount; /* Set to > 0 to generate a seektable if the decoding backend supports it. */ + ma_encoding_format encodingFormat; /* This is the encoding format that the caller wants to use. If set to ma_encoding_format_unknown, the decoding backend should try initializing from any of it's supported formats. */ } ma_decoding_backend_config; -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount); - +typedef struct ma_decoding_backend_info +{ + const char* pName; + const char* pLibraryName; + const char* pVendor; + ma_encoding_format encodingFormat; /* Set to the encoding format of the decoder if recognized by miniaudio. It's OK for this to be set to `ma_encoding_format_unknown`. This is only used as a hint for the initialization process in miniaudio. If your decoding backend supports multiple encoding formats just set this to `ma_encoding_format_unknown`. */ +} ma_decoding_backend_info; typedef struct { + void (* onInfo )(void* pUserData, ma_decoding_backend_info* pInfo); ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); - ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ ma_result (* onInitMemory)(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); } ma_decoding_backend_vtable; -typedef ma_result (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead); /* Returns the number of bytes read. */ +/* +The read callback should return MA_SUCCESS if more than zero bytes were successfully read. If zero +bytes can be read because it reached the end of the file, return MA_AT_END. If any other error +occurs just return any other error code (it's fine to just generically return MA_ERROR). + +Return the actual number of bytes read in `pBytesRead`. Important note: if the number of bytes +actually read is less than the number of bytes requested (bytesToRead), miniaudio will treat it as +if the end of the file has been reached and it will stop decoding. This becomes relevant for things +like streaming data sources, such as internet streams. If you haven't yet received `bytesToRead` +from the network, you need to have your callback wait for it and then only return when the full +number of bytes can be output, or when you've reached the genuine end of the stream. +*/ +typedef ma_result (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead); typedef ma_result (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); typedef ma_result (* ma_decoder_tell_proc)(ma_decoder* pDecoder, ma_int64* pCursor); @@ -9990,36 +10770,40 @@ typedef struct ma_allocation_callbacks allocationCallbacks; ma_encoding_format encodingFormat; ma_uint32 seekPointCount; /* When set to > 0, specifies the number of seek points to use for the generation of a seek table. Not all decoding backends support this. */ - ma_decoding_backend_vtable** ppCustomBackendVTables; - ma_uint32 customBackendCount; - void* pCustomBackendUserData; + ma_decoding_backend_vtable** ppBackendVTables; + void** ppBackendUserData; + ma_uint32 backendCount; } ma_decoder_config; struct ma_decoder { ma_data_source_base ds; - ma_data_source* pBackend; /* The decoding backend we'll be pulling data from. */ - const ma_decoding_backend_vtable* pBackendVTable; /* The vtable for the decoding backend. This needs to be stored so we can access the onUninit() callback. */ + ma_data_source* pBackend; /* The decoding backend we'll be pulling data from. */ + const ma_decoding_backend_vtable* pBackendVTable; /* The vtable for the decoding backend. */ void* pBackendUserData; ma_decoder_read_proc onRead; ma_decoder_seek_proc onSeek; ma_decoder_tell_proc onTell; void* pUserData; - ma_uint64 readPointerInPCMFrames; /* In output sample rate. Used for keeping track of how many frames are available for decoding. */ + ma_uint64 readPointerInPCMFrames; /* In output sample rate. Used for keeping track of how many frames are available for decoding. */ ma_format outputFormat; ma_uint32 outputChannels; ma_uint32 outputSampleRate; - ma_data_converter converter; /* Data conversion is achieved by running frames through this. */ - void* pInputCache; /* In input format. Can be null if it's not needed. */ - ma_uint64 inputCacheCap; /* The capacity of the input cache. */ - ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ - ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cache. */ + ma_data_converter converter; /* Data conversion is achieved by running frames through this. */ + void* pInputCache; /* In input format. Can be null if it's not needed. */ + ma_uint64 inputCacheCap; /* The capacity of the input cache. */ + ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ + ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cache. */ ma_allocation_callbacks allocationCallbacks; + ma_resampler_config resamplingConfig; /* For copying. */ + ma_uint32 seekPointCount; /* For copying. */ union { struct { ma_vfs* pVFS; + char* pFilePath; + wchar_t* pFilePathW; ma_vfs_file file; } vfs; struct @@ -10034,18 +10818,39 @@ struct ma_decoder MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint32 outputChannels, ma_uint32 outputSampleRate); MA_API ma_decoder_config ma_decoder_config_init_default(void); -MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, ma_decoder_tell_proc onTell, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); +MA_API ma_result ma_decoder_init_copy(ma_decoder* pDecoder, ma_decoder* pNewDecoder); /* Uninitializes a decoder. */ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder); +/* +Retrieves a pointer to the backend vtable used by this decoder. + +This is useful for getting an idea on the encoding format of the file being decoded. You can compare the return +value from this function like so: + + if (ma_decoder_get_backend_vtable(pDecoder) == ma_decoding_backend_wav) { + printf("Decoding WAV file...\n"); + } + +You can also use `ma_decoder_get_backend_info()` to get information about the decoding backend. +*/ +MA_API const ma_decoding_backend_vtable* ma_decoder_get_backend_vtable(ma_decoder* pDecoder); + +/* +Retrieves information about the decoding backend. +*/ +MA_API ma_result ma_decoder_get_backend_info(ma_decoder* pDecoder, ma_decoding_backend_info* pInfo); + + /* Reads PCM frames from the given decoder. @@ -10097,6 +10902,7 @@ returned. */ MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64* pAvailableFrames); + /* Helper for opening and decoding a file into a heap allocated block of memory. Free the returned pointer with ma_free(). On input, pConfig should be set to what you want. On output it will be set to what you got. @@ -10105,6 +10911,17 @@ MA_API ma_result ma_decode_from_vfs(ma_vfs* pVFS, const char* pFilePath, ma_deco MA_API ma_result ma_decode_file(const char* pFilePath, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); MA_API ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); + +/* Expose our decoding backend vtables so they can be plugged into a priority list. */ +extern ma_decoding_backend_vtable* ma_decoding_backend_wav; +MA_API ma_decoding_backend_vtable* ma_wav_get_vtable(void); + +extern ma_decoding_backend_vtable* ma_decoding_backend_flac; +MA_API ma_decoding_backend_vtable* ma_flac_get_vtable(void); + +extern ma_decoding_backend_vtable* ma_decoding_backend_mp3; +MA_API ma_decoding_backend_vtable* ma_mp3_get_vtable(void); + #endif /* MA_NO_DECODING */ @@ -10202,6 +11019,7 @@ typedef struct } ma_waveform; MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform); +MA_API ma_result ma_waveform_init_copy(ma_waveform* pWaveform, ma_waveform* pNewWaveform); MA_API void ma_waveform_uninit(ma_waveform* pWaveform); MA_API ma_result ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex); @@ -10261,6 +11079,7 @@ typedef struct { ma_data_source_base ds; ma_noise_config config; + ma_allocation_callbacks allocationCallbacks; ma_lcg lcg; union { @@ -10284,11 +11103,11 @@ typedef struct MA_API ma_result ma_noise_get_heap_size(const ma_noise_config* pConfig, size_t* pHeapSizeInBytes); MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void* pHeap, ma_noise* pNoise); MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_noise* pNoise); -MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_noise_init_copy(ma_noise* pNoise, ma_noise* pNewNoise); +MA_API void ma_noise_uninit(ma_noise* pNoise); MA_API ma_result ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude); MA_API ma_result ma_noise_set_seed(ma_noise* pNoise, ma_int32 seed); -MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type); #endif /* MA_NO_GENERATION */ @@ -10309,7 +11128,7 @@ typedef struct ma_resource_manager ma_resource_manager; typedef struct ma_resource_manager_data_buffer_node ma_resource_manager_data_buffer_node; typedef struct ma_resource_manager_data_buffer ma_resource_manager_data_buffer; typedef struct ma_resource_manager_data_stream ma_resource_manager_data_stream; -typedef struct ma_resource_manager_data_source ma_resource_manager_data_source; +typedef union ma_resource_manager_data_source ma_resource_manager_data_source; typedef enum { @@ -10341,26 +11160,6 @@ MA_API ma_resource_manager_pipeline_notifications ma_resource_manager_pipeline_n -/* BEGIN BACKWARDS COMPATIBILITY */ -/* TODO: Remove this block in version 0.12. */ -#if 1 -#define ma_resource_manager_job ma_job -#define ma_resource_manager_job_init ma_job_init -#define MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_FLAG_NON_BLOCKING MA_JOB_QUEUE_FLAG_NON_BLOCKING -#define ma_resource_manager_job_queue_config ma_job_queue_config -#define ma_resource_manager_job_queue_config_init ma_job_queue_config_init -#define ma_resource_manager_job_queue ma_job_queue -#define ma_resource_manager_job_queue_get_heap_size ma_job_queue_get_heap_size -#define ma_resource_manager_job_queue_init_preallocated ma_job_queue_init_preallocated -#define ma_resource_manager_job_queue_init ma_job_queue_init -#define ma_resource_manager_job_queue_uninit ma_job_queue_uninit -#define ma_resource_manager_job_queue_post ma_job_queue_post -#define ma_resource_manager_job_queue_next ma_job_queue_next -#endif -/* END BACKWARDS COMPATIBILITY */ - - - /* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ #ifndef MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT @@ -10387,7 +11186,6 @@ typedef struct ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointEndInPCMFrames; ma_uint32 flags; - ma_bool32 isLooping; /* Deprecated. Use the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING flag in `flags` instead. */ } ma_resource_manager_data_source_config; MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void); @@ -10477,6 +11275,8 @@ struct ma_resource_manager_data_stream ma_uint32 currentPageIndex; /* Toggles between 0 and 1. Index 0 is the first half of pPageData. Index 1 is the second half. Only ever accessed by the public API. Never accessed by the job thread. */ MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + char* pFilePath; /* We keep track of the file path for the purpose of duplication. */ + wchar_t* pFilePathW; /* Written by the public API, read by the job thread. */ MA_ATOMIC(4, ma_bool32) isLooping; /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */ @@ -10492,17 +11292,10 @@ struct ma_resource_manager_data_stream MA_ATOMIC(4, ma_bool32) seekCounter; /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */ }; -struct ma_resource_manager_data_source +union ma_resource_manager_data_source { - union - { - ma_resource_manager_data_buffer buffer; - ma_resource_manager_data_stream stream; - } backend; /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */ - - ma_uint32 flags; /* The flags that were passed in to ma_resource_manager_data_source_init(). */ - MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ - MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + ma_resource_manager_data_buffer buffer; + ma_resource_manager_data_stream stream; }; typedef struct @@ -10517,9 +11310,9 @@ typedef struct ma_uint32 jobQueueCapacity; /* The maximum number of jobs that can fit in the queue at a time. Defaults to MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY. Cannot be zero. */ ma_uint32 flags; ma_vfs* pVFS; /* Can be NULL in which case defaults will be used. */ - ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; - ma_uint32 customDecodingBackendCount; - void* pCustomDecodingBackendUserData; + ma_decoding_backend_vtable** ppDecodingBackendVTables; + ma_uint32 decodingBackendCount; + void** ppDecodingBackendUserData; ma_resampler_config resampling; } ma_resource_manager_config; @@ -10559,7 +11352,7 @@ MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pRes MA_API ma_result ma_resource_manager_data_buffer_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_buffer* pDataBuffer); MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); -MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager_data_buffer* pDataBuffer, ma_resource_manager_data_buffer* pNewDataBuffer); MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer); MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex); @@ -10575,6 +11368,7 @@ MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resourc MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_stream* pDataStream); MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_init_copy(ma_resource_manager_data_stream* pDataStream, ma_resource_manager_data_stream* pNewDataStream); MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream); MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex); @@ -10590,7 +11384,7 @@ MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resourc MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource); MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); -MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource); MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource); MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex); @@ -10648,6 +11442,7 @@ typedef void ma_node; /* Node flags. */ typedef enum { + MA_NODE_FLAG_NONE = 0x00000000, MA_NODE_FLAG_PASSTHROUGH = 0x00000001, MA_NODE_FLAG_CONTINUOUS_PROCESSING = 0x00000002, MA_NODE_FLAG_ALLOW_NULL_INPUT = 0x00000004, @@ -10667,9 +11462,7 @@ typedef enum typedef struct { /* - Extended processing callback. This callback is used for effects that process input and output - at different rates (i.e. they perform resampling). This is similar to the simple version, only - they take two separate frame counts: one for input, and one for output. + Processing callback. On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas `pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`. @@ -10700,16 +11493,13 @@ typedef struct */ ma_uint8 outputBusCount; - /* - Flags describing characteristics of the node. This is currently just a placeholder for some - ideas for later on. - */ + /* A combination of `MA_NODE_FLAG_*`. Can be 0. */ ma_uint32 flags; } ma_node_vtable; typedef struct { - const ma_node_vtable* vtable; /* Should never be null. Initialization of the node will fail if so. */ + const ma_node_vtable* pVTable; /* Should never be null. Initialization of the node will fail if so. */ ma_node_state initialState; /* Defaults to ma_node_state_started. */ ma_uint32 inputBusCount; /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ ma_uint32 outputBusCount; /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ @@ -10766,7 +11556,7 @@ struct ma_node_base { /* These variables are set once at startup. */ ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ - const ma_node_vtable* vtable; + const ma_node_vtable* pVTable; ma_uint32 inputBusCount; ma_uint32 outputBusCount; ma_node_input_bus* pInputBuses; @@ -10827,9 +11617,11 @@ MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); struct ma_node_graph { + ma_data_source_base ds; + ma_allocation_callbacks allocationCallbacks; + /* Immutable. */ - ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ - ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ + ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ float* pProcessingCache; /* This will be allocated when processingSizeInFrames is non-zero. This is needed because ma_node_graph_read_pcm_frames() can be called with a variable number of frames, and we may need to do some buffering in situations where the caller requests a frame count that's not a multiple of processingSizeInFrames. */ ma_uint32 processingCacheFramesRemaining; ma_uint32 processingSizeInFrames; @@ -10842,7 +11634,7 @@ struct ma_node_graph }; MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); -MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph); MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph); MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph); @@ -11095,10 +11887,6 @@ typedef struct MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode); MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks); -MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value); -MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode); -MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value); -MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode); MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value); MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode); #endif /* MA_NO_NODE_GRAPH */ @@ -11205,38 +11993,40 @@ MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocati #define MA_SOUND_SOURCE_CHANNEL_COUNT 0xFFFFFFFF -/* Callback for when a sound reaches the end. */ -typedef void (* ma_sound_end_proc)(void* pUserData, ma_sound* pSound); +typedef struct +{ + ma_fence* pLoadedFence; /* Set to NULL if not using a fence. */ + void (* onLoaded )(void* pUserData, ma_sound* pSuond); /* Fired by the resource manager when the sound has finished loading. */ + void (* onAtEnd )(void* pUserData, ma_sound* pSound); /* Fired when the sound reaches the end of the data source. */ + void (* onProcess)(void* pUserData, ma_sound* pSound, float* pFrames, ma_uint64 frameCount); + void* pUserData; +} ma_sound_notifications; + +MA_API ma_sound_notifications ma_sound_notifications_init(void); + typedef struct { - const char* pFilePath; /* Set this to load from the resource manager. */ - const wchar_t* pFilePathW; /* Set this to load from the resource manager. */ - ma_data_source* pDataSource; /* Set this to load from an existing data source. */ - ma_node* pInitialAttachment; /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ - ma_uint32 initialAttachmentInputBusIndex; /* The index of the input bus of pInitialAttachment to attach the sound to. */ - ma_uint32 channelsIn; /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ - ma_uint32 channelsOut; /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ - ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ - ma_uint32 flags; /* A combination of MA_SOUND_FLAG_* flags. */ - ma_uint32 volumeSmoothTimeInPCMFrames; /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */ - ma_uint64 initialSeekPointInPCMFrames; /* Initializes the sound such that it's seeked to this location by default. */ + const char* pFilePath; /* Set this to load from the resource manager. */ + const wchar_t* pFilePathW; /* Set this to load from the resource manager. */ + ma_data_source* pDataSource; /* Set this to load from an existing data source. */ + ma_node* pInitialAttachment; /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + ma_uint32 initialAttachmentInputBusIndex; /* The index of the input bus of pInitialAttachment to attach the sound to. */ + ma_uint32 channelsIn; /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + ma_uint32 channelsOut; /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ + ma_uint32 flags; /* A combination of MA_SOUND_FLAG_* flags. */ + ma_uint32 volumeSmoothTimeInPCMFrames; /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */ + ma_uint64 initialSeekPointInPCMFrames; /* Initializes the sound such that it's seeked to this location by default. */ ma_uint64 rangeBegInPCMFrames; ma_uint64 rangeEndInPCMFrames; ma_uint64 loopPointBegInPCMFrames; ma_uint64 loopPointEndInPCMFrames; - ma_sound_end_proc endCallback; /* Fired when the sound reaches the end. Will be fired from the audio thread. Do not restart, uninitialize or otherwise change the state of the sound from here. Instead fire an event or set a variable to indicate to a different thread to change the start of the sound. Will not be fired in response to a scheduled stop with ma_sound_set_stop_time_*(). */ - void* pEndCallbackUserData; + const ma_sound_notifications* pNotifications; /* A pointer to an object containing callbacks for when a sound has finished loading, reached the end, etc. A copy of this structure will be made internally. */ ma_resampler_config pitchResampling; -#ifndef MA_NO_RESOURCE_MANAGER - ma_resource_manager_pipeline_notifications initNotifications; -#endif - ma_fence* pDoneFence; /* Deprecated. Use initNotifications instead. Released when the resource manager has finished decoding the entire sound. Not used with streams. */ - ma_bool32 isLooping; /* Deprecated. Use the MA_SOUND_FLAG_LOOPING flag in `flags` instead. */ } ma_sound_config; -MA_API ma_sound_config ma_sound_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */ -MA_API ma_sound_config ma_sound_config_init_2(ma_engine* pEngine); /* Will be renamed to ma_sound_config_init() in version 0.12. */ +MA_API ma_sound_config ma_sound_config_init(ma_engine* pEngine); struct ma_sound { @@ -11244,19 +12034,14 @@ struct ma_sound ma_data_source* pDataSource; MA_ATOMIC(8, ma_uint64) seekTarget; /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */ MA_ATOMIC(4, ma_bool32) atEnd; - ma_sound_end_proc endCallback; - void* pEndCallbackUserData; + ma_sound_notifications notifications; float* pProcessingCache; /* Will be null if pDataSource is null. */ ma_uint32 processingCacheFramesRemaining; ma_uint32 processingCacheCap; - ma_bool8 ownsDataSource; + ma_bool8 ownsDataSource; - /* - We're declaring a resource manager data source object here to save us a malloc when loading a - sound via the resource manager, which I *think* will be the most common scenario. - */ #ifndef MA_NO_RESOURCE_MANAGER - ma_resource_manager_data_source* pResourceManagerDataSource; + ma_async_notification_callbacks resourceManagerDoneNotification; #endif }; @@ -11273,42 +12058,42 @@ struct ma_sound_inlined typedef ma_sound_config ma_sound_group_config; typedef ma_sound ma_sound_group; -MA_API ma_sound_group_config ma_sound_group_config_init(void); /* Deprecated. Will be removed in version 0.12. Use ma_sound_config_2() instead. */ -MA_API ma_sound_group_config ma_sound_group_config_init_2(ma_engine* pEngine); /* Will be renamed to ma_sound_config_init() in version 0.12. */ +MA_API ma_sound_group_config ma_sound_group_config_init(ma_engine* pEngine); /* Will be renamed to ma_sound_config_init() in version 0.12. */ typedef void (* ma_engine_process_proc)(void* pUserData, float* pFramesOut, ma_uint64 frameCount); typedef struct { #if !defined(MA_NO_RESOURCE_MANAGER) - ma_resource_manager* pResourceManager; /* Can be null in which case a resource manager will be created for you. */ + ma_resource_manager* pResourceManager; /* Can be null in which case a resource manager will be created for you. */ #endif #if !defined(MA_NO_DEVICE_IO) ma_context* pContext; - ma_device* pDevice; /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ - ma_device_id* pPlaybackDeviceID; /* The ID of the playback device to use with the default listener. */ - ma_device_data_proc dataCallback; /* Can be null. Can be used to provide a custom device data callback. */ + ma_device* pDevice; /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ + ma_device_id* pPlaybackDeviceID; /* The ID of the playback device to use with the default listener. */ + ma_device_data_proc dataCallback; /* Can be null. Can be used to provide a custom device data callback. */ ma_device_notification_proc notificationCallback; #endif - ma_log* pLog; /* When set to NULL, will use the context's log. */ - ma_uint32 listenerCount; /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ - ma_uint32 channels; /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ - ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native sample rate of the device. */ - ma_uint32 periodSizeInFrames; /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ - ma_uint32 periodSizeInMilliseconds; /* Used if periodSizeInFrames is unset. */ - ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ - ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ - ma_uint32 defaultVolumeSmoothTimeInPCMFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. High values means more smoothing at the expense of high latency (will take longer to reach the new volume). */ - ma_uint32 preMixStackSizeInBytes; /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */ + ma_log* pLog; /* When set to NULL, will use the context's log. */ + ma_uint32 listenerCount; /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ + ma_uint32 channels; /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ + ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native channel count of the device. */ + ma_uint32 periodSizeInFrames; /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ + ma_uint32 periodSizeInMilliseconds; /* Used if periodSizeInFrames is unset. */ + ma_uint32 spatializationVolumeSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ + ma_uint32 spatializationVolumeSmoothTimeInMilliseconds; /* When set to 0, spatializationVolumeSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ + ma_uint32 defaultVolumeSmoothTimeInFrames; /* Defaults to 0. Controls the default amount of smoothing to apply to volume changes to sounds. Higher values means more smoothing at the expense of higher latency (will take longer to reach the new volume). */ + ma_uint32 preMixStackSizeInBytes; /* A stack is used for internal processing in the node graph. This allows you to configure the size of this stack. Smaller values will reduce the maximum depth of your node graph. You should rarely need to modify this. */ ma_allocation_callbacks allocationCallbacks; - ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ - ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ - ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ - ma_vfs* pResourceManagerVFS; /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ - ma_engine_process_proc onProcess; /* Fired at the end of each call to ma_engine_read_pcm_frames(). For engine's that manage their own internal device (the default configuration), this will be fired from the audio thread, and you do not need to call ma_engine_read_pcm_frames() manually in order to trigger this. */ - void* pProcessUserData; /* User data that's passed into onProcess. */ - ma_resampler_config resourceManagerResampling; /* The resampling config to use with the resource manager. */ - ma_resampler_config pitchResampling; /* The resampling config for the pitch and Doppler effects. You will typically want this to be a fast resampler. For high quality stuff, it's recommended that you pre-resample. */ + ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ + ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ + ma_bool32 noClip; /* When set to true, do not clip samples. */ + ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ + ma_vfs* pResourceManagerVFS; /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ + ma_engine_process_proc onProcess; /* Fired at the end of each call to ma_engine_read_pcm_frames(). For engine's that manage their own internal device (the default configuration), this will be fired from the audio thread, and you do not need to call ma_engine_read_pcm_frames() manually in order to trigger this. */ + void* pProcessUserData; /* User data that's passed into onProcess. */ + ma_resampler_config resourceManagerResampling; /* The resampling config to use with the resource manager. */ + ma_resampler_config pitchResampling; /* The resampling config for the pitch and Doppler effects. You will typically want this to be a fast resampler. For high quality stuff, it's recommended that you pre-resample. */ } ma_engine_config; MA_API ma_engine_config ma_engine_config_init(void); @@ -11316,12 +12101,12 @@ MA_API ma_engine_config ma_engine_config_init(void); struct ma_engine { - ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ + ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ #if !defined(MA_NO_RESOURCE_MANAGER) ma_resource_manager* pResourceManager; #endif #if !defined(MA_NO_DEVICE_IO) - ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ + ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ #endif ma_log* pLog; ma_uint32 sampleRate; @@ -11330,11 +12115,12 @@ struct ma_engine ma_allocation_callbacks allocationCallbacks; ma_bool8 ownsResourceManager; ma_bool8 ownsDevice; - ma_spinlock inlinedSoundLock; /* For synchronizing access to the inlined sound list. */ - ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ - MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ - ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ - ma_uint32 defaultVolumeSmoothTimeInPCMFrames; + ma_bool8 noClip; + ma_spinlock inlinedSoundLock; /* For synchronizing access to the inlined sound list. */ + ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ + ma_uint32 spatializationVolumeSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ + ma_uint32 defaultVolumeSmoothTimeInFrames; ma_mono_expansion_mode monoExpansionMode; ma_engine_process_proc onProcess; void* pProcessUserData; @@ -11348,15 +12134,15 @@ MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine); #if !defined(MA_NO_RESOURCE_MANAGER) MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine); #endif +#if !defined(MA_NO_DEVICE_IO) MA_API ma_device* ma_engine_get_device(ma_engine* pEngine); +#endif MA_API ma_log* ma_engine_get_log(ma_engine* pEngine); MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine); MA_API ma_uint64 ma_engine_get_time_in_pcm_frames(const ma_engine* pEngine); MA_API ma_uint64 ma_engine_get_time_in_milliseconds(const ma_engine* pEngine); MA_API ma_result ma_engine_set_time_in_pcm_frames(ma_engine* pEngine, ma_uint64 globalTime); MA_API ma_result ma_engine_set_time_in_milliseconds(ma_engine* pEngine, ma_uint64 globalTime); -MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine); /* Deprecated. Use ma_engine_get_time_in_pcm_frames(). Will be removed in version 0.12. */ -MA_API ma_result ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime); /* Deprecated. Use ma_engine_set_time_in_pcm_frames(). Will be removed in version 0.12. */ MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine); MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine); @@ -11388,11 +12174,11 @@ MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, #endif #ifndef MA_NO_RESOURCE_MANAGER -MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); -MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); -MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound); +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound); +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound); #endif -MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound); MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound); MA_API void ma_sound_uninit(ma_sound* pSound); MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound); @@ -11425,7 +12211,7 @@ MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z); MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound); MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z); MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound); -MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel); +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel, void* pAttenuationUserData); MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound); MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning); MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound); @@ -11469,7 +12255,6 @@ MA_API ma_result ma_sound_get_cursor_in_pcm_frames(const ma_sound* pSound, ma_ui MA_API ma_result ma_sound_get_length_in_pcm_frames(const ma_sound* pSound, ma_uint64* pLength); MA_API ma_result ma_sound_get_cursor_in_seconds(const ma_sound* pSound, float* pCursor); MA_API ma_result ma_sound_get_length_in_seconds(const ma_sound* pSound, float* pLength); -MA_API ma_result ma_sound_set_end_callback(ma_sound* pSound, ma_sound_end_proc callback, void* pUserData); MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup); MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup); @@ -11497,7 +12282,7 @@ MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup); MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z); MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup); -MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel); +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel, void* pAttenuationUserData); MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup); MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning); MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup); @@ -11589,7 +12374,7 @@ IMPLEMENTATION #endif #if !defined(MA_WIN32) - #if !defined(MA_NO_THREADING) + #if !defined(MA_NO_THREADING) && !defined(MA_DOS) #include #include /* For pthreads. */ #endif @@ -11611,51 +12396,6 @@ IMPLEMENTATION #endif -/* Architecture Detection */ -#if !defined(MA_64BIT) && !defined(MA_32BIT) -#ifdef _WIN32 -#ifdef _WIN64 -#define MA_64BIT -#else -#define MA_32BIT -#endif -#endif -#endif - -#if !defined(MA_64BIT) && !defined(MA_32BIT) -#ifdef __GNUC__ -#ifdef __LP64__ -#define MA_64BIT -#else -#define MA_32BIT -#endif -#endif -#endif - -#if !defined(MA_64BIT) && !defined(MA_32BIT) -#include -#if INTPTR_MAX == INT64_MAX -#define MA_64BIT -#else -#define MA_32BIT -#endif -#endif - -#if defined(__arm__) || defined(_M_ARM) -#define MA_ARM32 -#endif -#if defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) -#define MA_ARM64 -#endif - -#if defined(__x86_64__) || defined(_M_X64) -#define MA_X64 -#elif defined(__i386) || defined(_M_IX86) -#define MA_X86 -#elif defined(MA_ARM32) || defined(MA_ARM64) -#define MA_ARM -#endif - /* Intrinsics Support */ #if defined(MA_X64) || defined(MA_X86) #if defined(_MSC_VER) && !defined(__clang__) @@ -11703,7 +12443,7 @@ IMPLEMENTATION #endif #if defined(MA_ARM) - #if !defined(MA_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + #if !defined(MA_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) #define MA_SUPPORT_NEON #include #endif @@ -11944,22 +12684,6 @@ static MA_INLINE ma_bool32 ma_has_neon(void) #endif -static MA_INLINE ma_bool32 ma_is_little_endian(void) -{ -#if defined(MA_X86) || defined(MA_X64) - return MA_TRUE; -#else - int n = 1; - return (*(char*)&n) == 1; -#endif -} - -static MA_INLINE ma_bool32 ma_is_big_endian(void) -{ - return !ma_is_little_endian(); -} - - static MA_INLINE ma_uint32 ma_swap_endian_uint32(ma_uint32 n) { #ifdef MA_HAS_BYTESWAP32_INTRINSIC @@ -12047,6 +12771,8 @@ static MA_INLINE void ma_yield(void) __asm pause; #endif #endif + #elif defined(__chibicc__) + /* No op. */ #else __asm__ __volatile__ ("rep; nop"); #endif @@ -12157,7 +12883,7 @@ static MA_INLINE void ma_restore_denormals(unsigned int prevState) #ifdef MA_ANDROID #include -int ma_android_sdk_version() +static int ma_android_sdk_version(void) { char sdkVersion[PROP_VALUE_MAX + 1] = {0, }; if (__system_property_get("ro.build.version.sdk", sdkVersion)) { @@ -12197,34 +12923,14 @@ int ma_android_sdk_version() #endif -/* The default format when ma_format_unknown (0) is requested when initializing a device. */ -#ifndef MA_DEFAULT_FORMAT -#define MA_DEFAULT_FORMAT ma_format_f32 -#endif - -/* The default channel count to use when 0 is used when initializing a device. */ -#ifndef MA_DEFAULT_CHANNELS -#define MA_DEFAULT_CHANNELS 2 -#endif - -/* The default sample rate to use when 0 is used when initializing a device. */ -#ifndef MA_DEFAULT_SAMPLE_RATE -#define MA_DEFAULT_SAMPLE_RATE 48000 -#endif - -/* Default periods when none is specified in ma_device_init(). More periods means more work on the CPU. */ +/* Default device period count. */ #ifndef MA_DEFAULT_PERIODS -#define MA_DEFAULT_PERIODS 3 -#endif - -/* The default period size in milliseconds for low latency mode. */ -#ifndef MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY -#define MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY 10 +#define MA_DEFAULT_PERIODS 2 #endif -/* The default buffer size in milliseconds for conservative mode. */ -#ifndef MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE -#define MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE 100 +/* The default period size in milliseconds. */ +#ifndef MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS +#define MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS 10 #endif /* The default LPF filter order for linear resampling. Note that this is clamped to MA_MAX_FILTER_ORDER. */ @@ -12242,8 +12948,8 @@ int ma_android_sdk_version() #pragma GCC diagnostic ignored "-Wunused-variable" #endif -/* Standard sample rates, in order of priority. */ -static ma_uint32 g_maStandardSampleRatePriorities[] = { +static const ma_uint32 ma_standard_sample_rates[] = +{ (ma_uint32)ma_standard_sample_rate_48000, (ma_uint32)ma_standard_sample_rate_44100, @@ -12264,12 +12970,26 @@ static ma_uint32 g_maStandardSampleRatePriorities[] = { (ma_uint32)ma_standard_sample_rate_384000 }; +static const ma_uint32 ma_standard_sample_rate_count = ma_countof(ma_standard_sample_rates); + + +MA_API const ma_uint32* ma_get_standard_sample_rates(void) +{ + return ma_standard_sample_rates; +} + +MA_API ma_uint32 ma_get_standard_sample_rate_count(void) +{ + return ma_standard_sample_rate_count; +} + + static MA_INLINE ma_bool32 ma_is_standard_sample_rate(ma_uint32 sampleRate) { ma_uint32 iSampleRate; - for (iSampleRate = 0; iSampleRate < sizeof(g_maStandardSampleRatePriorities) / sizeof(g_maStandardSampleRatePriorities[0]); iSampleRate += 1) { - if (g_maStandardSampleRatePriorities[iSampleRate] == sampleRate) { + for (iSampleRate = 0; iSampleRate < ma_standard_sample_rate_count; iSampleRate += 1) { + if (ma_standard_sample_rates[iSampleRate] == sampleRate) { return MA_TRUE; } } @@ -12347,7 +13067,6 @@ static MA_INLINE void ma_zero_memory_default(void* p, size_t sz) } } - #ifndef MA_ZERO_MEMORY #define MA_ZERO_MEMORY(p, sz) ma_zero_memory_default((p), (sz)) #endif @@ -12360,15 +13079,6 @@ static MA_INLINE void ma_zero_memory_default(void* p, size_t sz) #define MA_ZERO_OBJECT(p) MA_ZERO_MEMORY((p), sizeof(*(p))) -#define ma_countof(x) (sizeof(x) / sizeof(x[0])) -#define ma_max(x, y) (((x) > (y)) ? (x) : (y)) -#define ma_min(x, y) (((x) < (y)) ? (x) : (y)) -#define ma_abs(x) (((x) > 0) ? (x) : -(x)) -#define ma_clamp(x, lo, hi) (ma_max(lo, ma_min(x, hi))) -#define ma_offset_ptr(p, offset) (((ma_uint8*)(p)) + (offset)) -#define ma_align(x, a) (((x) + ((a)-1)) & ~((a)-1)) -#define ma_align_64(x) ma_align(x, 8) - #define ma_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / ma_get_bytes_per_sample(format) / (channels)) static MA_INLINE double ma_sind(double x) @@ -13322,7 +14032,7 @@ This can be reviewed as compatibility issues arise. The preference is to use _wf fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) && !defined(MA_XBOX_NXDK) - #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) && !defined(__CRTDLL__) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define MA_HAS_WFOPEN #endif #endif @@ -13461,23 +14171,10 @@ static MA_INLINE void ma_zero_memory_64(void* dst, ma_uint64 sizeInBytes) } -/* Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */ -static MA_INLINE unsigned int ma_next_power_of_2(unsigned int x) -{ - x--; - x |= x >> 1; - x |= x >> 2; - x |= x >> 4; - x |= x >> 8; - x |= x >> 16; - x++; - - return x; -} static MA_INLINE unsigned int ma_prev_power_of_2(unsigned int x) { - return ma_next_power_of_2(x) >> 1; + return ma_next_power_of_2(x + 1) >> 1; } static MA_INLINE unsigned int ma_round_to_power_of_2(unsigned int x) @@ -13530,40 +14227,6 @@ static void ma__free_default(void* p, void* pUserData) MA_FREE(p); } -static ma_allocation_callbacks ma_allocation_callbacks_init_default(void) -{ - ma_allocation_callbacks callbacks; - callbacks.pUserData = NULL; - callbacks.onMalloc = ma__malloc_default; - callbacks.onRealloc = ma__realloc_default; - callbacks.onFree = ma__free_default; - - return callbacks; -} - -static ma_result ma_allocation_callbacks_init_copy(ma_allocation_callbacks* pDst, const ma_allocation_callbacks* pSrc) -{ - if (pDst == NULL) { - return MA_INVALID_ARGS; - } - - if (pSrc == NULL) { - *pDst = ma_allocation_callbacks_init_default(); - } else { - if (pSrc->pUserData == NULL && pSrc->onFree == NULL && pSrc->onMalloc == NULL && pSrc->onRealloc == NULL) { - *pDst = ma_allocation_callbacks_init_default(); - } else { - if (pSrc->onFree == NULL || (pSrc->onMalloc == NULL && pSrc->onRealloc == NULL)) { - return MA_INVALID_ARGS; /* Invalid allocation callbacks. */ - } else { - *pDst = *pSrc; - } - } - } - - return MA_SUCCESS; -} - @@ -13574,8 +14237,12 @@ Logging **************************************************************************************************************************************************************/ #ifndef ma_va_copy #if !defined(_MSC_VER) || _MSC_VER >= 1800 - #if (defined(__GNUC__) && __GNUC__ < 3) - #define ma_va_copy(dst, src) ((dst) = (src)) /* This is untested. Not sure if this is correct for old GCC. */ + #if !defined(__STDC_VERSION__) || (defined(__GNUC__) && __GNUC__ < 3) /* <-- va_copy() is not available when using `-std=c89`. The `!defined(__STDC_VERSION__)` parts is what checks for this. */ + #if defined(__va_copy) + #define ma_va_copy(dst, src) __va_copy(dst, src) + #else + #define ma_va_copy(dst, src) ((dst) = (src)) /* This is untested. Not sure if this is correct for old GCC. */ + #endif #else #define ma_va_copy(dst, src) va_copy((dst), (src)) #endif @@ -13614,12 +14281,12 @@ void ma_log_callback_debug(void* pUserData, ma_uint32 level, const char* pMessag #if defined(MA_ANDROID) { /* Android. */ - __android_log_print(ANDROID_LOG_DEBUG, MA_ANDROID_LOG_TAG, "%s: %s", ma_log_level_to_string(level), pMessage); + __android_log_print(ANDROID_LOG_DEBUG, MA_ANDROID_LOG_TAG, "%s: %s\n", ma_log_level_to_string(level), pMessage); } #else { /* Everything else. */ - printf("%s: %s", ma_log_level_to_string(level), pMessage); + printf("%s: %s\n", ma_log_level_to_string(level), pMessage); } #endif } @@ -13644,7 +14311,7 @@ MA_API ma_result ma_log_init(const ma_allocation_callbacks* pAllocationCallbacks } MA_ZERO_OBJECT(pLog); - ma_allocation_callbacks_init_copy(&pLog->allocationCallbacks, pAllocationCallbacks); + pLog->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks); /* We need a mutex for thread safety. */ #ifndef MA_NO_THREADING @@ -14226,7 +14893,8 @@ typedef int ma_atomic_memory_order; !defined(MA_ATOMIC_LEGACY_MSVC_ASM) && \ !defined(MA_ATOMIC_MODERN_GCC) && \ !defined(MA_ATOMIC_LEGACY_GCC) && \ - !defined(MA_ATOMIC_LEGACY_GCC_ASM) + !defined(MA_ATOMIC_LEGACY_GCC_ASM) && \ + !defined(MA_ATOMIC_CHIBICC) #if defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__) || defined(__BORLANDC__) #if (defined(_MSC_VER) && _MSC_VER > 1600) #define MA_ATOMIC_MODERN_MSVC @@ -14239,12 +14907,12 @@ typedef int ma_atomic_memory_order; #endif #elif (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) || defined(__clang__) #define MA_ATOMIC_MODERN_GCC + #elif (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1))) + #define MA_ATOMIC_LEGACY_GCC + #elif defined(__chibicc__) + #define MA_ATOMIC_CHIBICC #else - #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) - #define MA_ATOMIC_LEGACY_GCC - #else - #define MA_ATOMIC_LEGACY_GCC_ASM - #endif + #define MA_ATOMIC_LEGACY_GCC_ASM #endif #endif #if defined(MA_ATOMIC_MODERN_MSVC) || defined(MA_ATOMIC_LEGACY_MSVC) @@ -14556,6 +15224,24 @@ typedef int ma_atomic_memory_order; #endif } #endif +#if defined(MA_ATOMIC_CHIBICC) + #define ma_atomic_memory_order_relaxed 0 + #define ma_atomic_memory_order_consume 1 + #define ma_atomic_memory_order_acquire 2 + #define ma_atomic_memory_order_release 3 + #define ma_atomic_memory_order_acq_rel 4 + #define ma_atomic_memory_order_seq_cst 5 + typedef ma_uint32 ma_atomic_flag; + #define ma_atomic_flag_test_and_set_explicit(dst, order) __builtin_atomic_exchange(dst, 1) + #define ma_atomic_flag_clear_explicit(dst, order) __builtin_atomic_exchange(dst, 0) + static MA_INLINE ma_atomic_flag ma_atomic_flag_load_explicit(volatile const ma_atomic_flag* dst, ma_atomic_memory_order order) + { + ma_atomic_flag expected; + expected = 0; + __builtin_compare_and_swap(dst, &expected, 0); + return expected; + } +#endif #define ma_atomic_flag_test_and_set(dst) ma_atomic_flag_test_and_set_explicit(dst, ma_atomic_memory_order_acquire) #define ma_atomic_flag_clear(dst) ma_atomic_flag_clear_explicit(dst, ma_atomic_memory_order_release) typedef ma_atomic_flag ma_atomic_spinlock; @@ -16930,6 +17616,88 @@ ma_atomic_spinlock ma_atomic_global_lock; #error Unsupported compiler. #endif #endif +#if defined(MA_ATOMIC_CHIBICC) + #define MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE + static void ma_atomic_thread_fence(ma_atomic_memory_order order) + { + static ma_uint32 dummy; + __builtin_atomic_exchange(&dummy, 0); + (void)dummy; + } + #define ma_atomic_signal_fence(order) ma_atomic_thread_fence(order) + #define ma_atomic_is_lock_free_8(ptr) 1 + #define ma_atomic_is_lock_free_16(ptr) 1 + #define ma_atomic_is_lock_free_32(ptr) 1 + #define ma_atomic_is_lock_free_64(ptr) 1 + #define ma_atomic_store_explicit_8( dst, src, order) __builtin_atomic_exchange(dst, src) + #define ma_atomic_store_explicit_16(dst, src, order) __builtin_atomic_exchange(dst, src) + #define ma_atomic_store_explicit_32(dst, src, order) __builtin_atomic_exchange(dst, src) + #define ma_atomic_store_explicit_64(dst, src, order) __builtin_atomic_exchange(dst, src) + #define MA_ATOMIC_CHIBICC_LOAD(sizeInBits) \ + static MA_INLINE ma_uint##sizeInBits ma_atomic_load_explicit_##sizeInBits(volatile ma_uint##sizeInBits* dst, ma_atomic_memory_order order) \ + { \ + ma_uint##sizeInBits expected = 0; \ + __builtin_compare_and_swap(dst, &expected, 0); \ + return expected; \ + } + MA_ATOMIC_CHIBICC_LOAD(8) + MA_ATOMIC_CHIBICC_LOAD(16) + MA_ATOMIC_CHIBICC_LOAD(32) + MA_ATOMIC_CHIBICC_LOAD(64) + #define ma_atomic_exchange_explicit_8( dst, src, order) __builtin_atomic_exchange(dst, src) + #define ma_atomic_exchange_explicit_16(dst, src, order) __builtin_atomic_exchange(dst, src) + #define ma_atomic_exchange_explicit_32(dst, src, order) __builtin_atomic_exchange(dst, src) + #define ma_atomic_exchange_explicit_64(dst, src, order) __builtin_atomic_exchange(dst, src) + #define ma_atomic_compare_exchange_strong_explicit_8( dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + #define ma_atomic_compare_exchange_strong_explicit_16(dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + #define ma_atomic_compare_exchange_strong_explicit_32(dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + #define ma_atomic_compare_exchange_strong_explicit_64(dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + #define ma_atomic_compare_exchange_weak_explicit_8( dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + #define ma_atomic_compare_exchange_weak_explicit_16(dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + #define ma_atomic_compare_exchange_weak_explicit_32(dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + #define ma_atomic_compare_exchange_weak_explicit_64(dst, expected, replacement, successOrder, failureOrder) __builtin_compare_and_swap(dst, expected, replacement) + static MA_INLINE ma_uint8 ma_atomic_compare_and_swap_8 (volatile ma_uint8* dst, ma_uint8 expected, ma_uint8 replacement) { __builtin_compare_and_swap(dst, &expected, replacement); return expected; } + static MA_INLINE ma_uint16 ma_atomic_compare_and_swap_16(volatile ma_uint16* dst, ma_uint16 expected, ma_uint16 replacement) { __builtin_compare_and_swap(dst, &expected, replacement); return expected; } + static MA_INLINE ma_uint32 ma_atomic_compare_and_swap_32(volatile ma_uint32* dst, ma_uint32 expected, ma_uint32 replacement) { __builtin_compare_and_swap(dst, &expected, replacement); return expected; } + static MA_INLINE ma_uint64 ma_atomic_compare_and_swap_64(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 replacement) { __builtin_compare_and_swap(dst, &expected, replacement); return expected; } + #define MA_ATOMIC_CHIBICC_FETCH_OP(sizeInBits, opName, opInst) \ + static MA_INLINE ma_uint##sizeInBits ma_atomic_fetch_##opName##_explicit_##sizeInBits(volatile ma_uint##sizeInBits* dst, ma_uint##sizeInBits src, ma_atomic_memory_order order) \ + { \ + ma_uint##sizeInBits oldValue; \ + ma_uint##sizeInBits newValue; \ + do { \ + oldValue = ma_atomic_load_explicit_##sizeInBits(dst, ma_atomic_memory_order_relaxed); \ + newValue = oldValue opInst src; \ + } while (ma_atomic_compare_and_swap_##sizeInBits(dst, oldValue, newValue) != oldValue); \ + (void)order; \ + return oldValue; \ + } + #define MA_ATOMIC_CHIBICC_FETCH_ADD(sizeInBits) MA_ATOMIC_CHIBICC_FETCH_OP(sizeInBits, add, +) + MA_ATOMIC_CHIBICC_FETCH_ADD(8) + MA_ATOMIC_CHIBICC_FETCH_ADD(16) + MA_ATOMIC_CHIBICC_FETCH_ADD(32) + MA_ATOMIC_CHIBICC_FETCH_ADD(64) + #define MA_ATOMIC_CHIBICC_FETCH_SUB(sizeInBits) MA_ATOMIC_CHIBICC_FETCH_OP(sizeInBits, sub, -) + MA_ATOMIC_CHIBICC_FETCH_SUB(8) + MA_ATOMIC_CHIBICC_FETCH_SUB(16) + MA_ATOMIC_CHIBICC_FETCH_SUB(32) + MA_ATOMIC_CHIBICC_FETCH_SUB(64) + #define MA_ATOMIC_CHIBICC_FETCH_OR(sizeInBits) MA_ATOMIC_CHIBICC_FETCH_OP(sizeInBits, or, |) + MA_ATOMIC_CHIBICC_FETCH_OR(8) + MA_ATOMIC_CHIBICC_FETCH_OR(16) + MA_ATOMIC_CHIBICC_FETCH_OR(32) + MA_ATOMIC_CHIBICC_FETCH_OR(64) + #define MA_ATOMIC_CHIBICC_FETCH_XOR(sizeInBits) MA_ATOMIC_CHIBICC_FETCH_OP(sizeInBits, xor, ^) + MA_ATOMIC_CHIBICC_FETCH_XOR(8) + MA_ATOMIC_CHIBICC_FETCH_XOR(16) + MA_ATOMIC_CHIBICC_FETCH_XOR(32) + MA_ATOMIC_CHIBICC_FETCH_XOR(64) + #define MA_ATOMIC_CHIBICC_FETCH_AND(sizeInBits) MA_ATOMIC_CHIBICC_FETCH_OP(sizeInBits, and, &) + MA_ATOMIC_CHIBICC_FETCH_AND(8) + MA_ATOMIC_CHIBICC_FETCH_AND(16) + MA_ATOMIC_CHIBICC_FETCH_AND(32) + MA_ATOMIC_CHIBICC_FETCH_AND(64) +#endif #if !defined(MA_ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE) static MA_INLINE ma_bool32 ma_atomic_compare_exchange_strong_explicit_8(volatile ma_uint8* dst, ma_uint8* expected, ma_uint8 replacement, ma_atomic_memory_order successOrder, ma_atomic_memory_order failureOrder) { @@ -17454,35 +18222,13 @@ MA_ATOMIC_SAFE_TYPE_IMPL(f32, float) MA_ATOMIC_SAFE_TYPE_IMPL(32, bool32) #if !defined(MA_NO_DEVICE_IO) -MA_ATOMIC_SAFE_TYPE_IMPL(i32, device_state) +MA_ATOMIC_SAFE_TYPE_IMPL(i32, device_status) #endif MA_API ma_uint64 ma_calculate_frame_count_after_resampling(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 frameCountIn) { - /* This is based on the calculation in ma_linear_resampler_get_expected_output_frame_count(). */ - ma_uint64 outputFrameCount; - ma_uint64 preliminaryInputFrameCountFromFrac; - ma_uint64 preliminaryInputFrameCount; - - if (sampleRateIn == 0 || sampleRateOut == 0 || frameCountIn == 0) { - return 0; - } - - if (sampleRateOut == sampleRateIn) { - return frameCountIn; - } - - outputFrameCount = (frameCountIn * sampleRateOut) / sampleRateIn; - - preliminaryInputFrameCountFromFrac = (outputFrameCount * (sampleRateIn / sampleRateOut)) / sampleRateOut; - preliminaryInputFrameCount = (outputFrameCount * (sampleRateIn % sampleRateOut)) + preliminaryInputFrameCountFromFrac; - - if (preliminaryInputFrameCount <= frameCountIn) { - outputFrameCount += 1; - } - - return outputFrameCount; + return ma_linear_resampler_calculate_frame_count_after_resampling(sampleRateIn, sampleRateOut, frameCountIn); } #ifndef MA_DATA_CONVERTER_STACK_BUFFER_SIZE @@ -17562,24 +18308,25 @@ MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock) } -#ifndef MA_NO_THREADING -#if defined(MA_POSIX) - #define MA_THREADCALL - typedef void* ma_thread_result; -#elif defined(MA_WIN32) + +#if defined(MA_THREADING_BACKEND_WIN32) #define MA_THREADCALL WINAPI typedef unsigned long ma_thread_result; +#else + #define MA_THREADCALL + typedef void* ma_thread_result; #endif typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); -#ifdef MA_POSIX + +#ifdef MA_THREADING_BACKEND_POSIX static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority priority, size_t stackSize, ma_thread_entry_proc entryProc, void* pData) { int result; pthread_attr_t* pAttr = NULL; -#if !defined(MA_EMSCRIPTEN) && !defined(MA_3DS) && !defined(MA_SWITCH) +#if !defined(MA_EMSCRIPTEN) && !defined(MA_N3DS) && !defined(MA_SWITCH) && !defined(MA_DREAMCAST) && !defined(MA_VITA) /* Try setting the thread priority. It's not critical if anything fails here. */ pthread_attr_t attr; if (pthread_attr_init(&attr) == 0) { @@ -17615,6 +18362,23 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority { if (stackSize > 0) { pthread_attr_setstacksize(&attr, stackSize); + } else { + /* + Note for Vita SDK + + When using pthread_attr_t (this path we're in now), the default stack size seems to be too small. If the + pthread_attr_t path is skipped entirely, things work fine. It looks like skipping the call to + pthread_attr_setstacksize(), which happens when stackSize is 0, the chosen stack size is smaller than + what it would be if we skipped the pthread_attr_t path. + + There are two work arounds for this. The first is to always skip the pthread_attr_t path. The second is + to have a specialized path here for Vita and explicitly default to something bigger. I'm doing both. + */ + #if defined(MA_VITA) + { + pthread_attr_setstacksize(&attr, 0x10000); + } + #endif } } #else @@ -17656,7 +18420,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority /* I'm not treating a failure of setting the priority as a critical error so not aborting on failure here. */ if (pthread_attr_setschedparam(&attr, &sched) == 0) { - #if (!defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28)) && !defined(MA_BEOS) + #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) { pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); } @@ -17861,7 +18625,7 @@ static ma_result ma_semaphore_release__posix(ma_semaphore* pSemaphore) return MA_SUCCESS; } -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) static int ma_thread_priority_to_win32(ma_thread_priority priority) { switch (priority) { @@ -18001,8 +18765,385 @@ static ma_result ma_semaphore_release__win32(ma_semaphore* pSemaphore) return MA_SUCCESS; } +#elif defined(MA_THREADING_BACKEND_VITA) +#include +#include +#include +#include + +typedef struct +{ + ma_thread_entry_proc entryProc; + void* pData; +} ma_vita_thread_proxy_data; + +static int ma_vita_thread_proxy(SceSize dataSize, void* pData) +{ + ma_vita_thread_proxy_data* pProxyData = (ma_vita_thread_proxy_data*)pData; + + (void)dataSize; + + return (int)pProxyData->entryProc(pProxyData->pData); +} + +static ma_result ma_thread_create__vita(ma_thread* pThread, ma_thread_priority priority, size_t stackSize, ma_thread_entry_proc entryProc, void* pData) +{ + int thd; + const char* pNameBase = "ma_thd_"; + size_t nameBaseLen = strlen(pNameBase); + char name[64]; + static ma_uint32 counter = 0; /* For creating a unique name. Atomically incremented. */ + ma_vita_thread_proxy_data data; + int initPriority; + + if (pThread == NULL) { + return MA_INVALID_ARGS; + } + + ma_strcpy_s(name, sizeof(name), pNameBase); + ma_itoa_s((int)ma_atomic_fetch_add_explicit_32(&counter, 1, ma_atomic_memory_order_relaxed), name + nameBaseLen, sizeof(name) - nameBaseLen, 10); + + /* TODO: Not sure how properly set the priority. */ + (void)priority; + initPriority = 0x10000100; /* This is the value used in the Vita SDK documentation for sceKernelCreateThread(). I'm not quite sure how to properly use this. */ + + /* Make sure we have an appropriate default stack size. */ + if (stackSize == 0) { + stackSize = 0x10000; + } + + thd = sceKernelCreateThread(name, ma_vita_thread_proxy, 0x10000100, (SceSize)stackSize, 0, 0, NULL); + if (thd < 0) { + return MA_ERROR; + } + + data.entryProc = entryProc; + data.pData = pData; + sceKernelStartThread(thd, sizeof(data), &data); + + *pThread = thd; + + return MA_SUCCESS; +} + +static void ma_thread_wait__vita(ma_thread* pThread) +{ + if (pThread == NULL) { + return; + } + + sceKernelWaitThreadEnd(*pThread, NULL, NULL); + sceKernelDeleteThread(*pThread); +} + + +static ma_result ma_mutex_init__vita(ma_mutex* pMutex) +{ + int mtx; + const char* pNameBase = "ma_mtx_"; + size_t nameBaseLen = strlen(pNameBase); + char name[64]; + static ma_uint32 counter = 0; /* For creating a unique name. Atomically incremented. */ + + if (pMutex == NULL) { + return MA_INVALID_ARGS; + } + + ma_strcpy_s(name, sizeof(name), pNameBase); + ma_itoa_s((int)ma_atomic_fetch_add_explicit_32(&counter, 1, ma_atomic_memory_order_relaxed), name + nameBaseLen, sizeof(name) - nameBaseLen, 10); + + mtx = sceKernelCreateMutex(name, 0, 0, NULL); + if (mtx < 0) { + return MA_ERROR; + } + + *pMutex = mtx; + + return MA_SUCCESS; +} + +static void ma_mutex_uninit__vita(ma_mutex* pMutex) +{ + if (pMutex == NULL) { + return; + } + + sceKernelDeleteMutex(*pMutex); +} + +static void ma_mutex_lock__vita(ma_mutex* pMutex) +{ + if (pMutex == NULL) { + return; + } + + sceKernelLockMutex(*pMutex, 1, NULL); +} + +static void ma_mutex_unlock__vita(ma_mutex* pMutex) +{ + if (pMutex == NULL) { + return; + } + + sceKernelUnlockMutex(*pMutex, 1); +} + + +static ma_result ma_event_init__vita(ma_event* pEvent) +{ + int e; + const char* pNameBase = "ma_evt_"; + size_t nameBaseLen = strlen(pNameBase); + char name[64]; + static ma_uint32 counter = 0; /* For creating a unique name. Atomically incremented. */ + + if (pEvent == NULL) { + return MA_INVALID_ARGS; + } + + ma_strcpy_s(name, sizeof(name), pNameBase); + ma_itoa_s((int)ma_atomic_fetch_add_explicit_32(&counter, 1, ma_atomic_memory_order_relaxed), name + nameBaseLen, sizeof(name) - nameBaseLen, 10); + + e = sceKernelCreateEventFlag(name, SCE_EVENT_WAITMULTIPLE, 0, NULL); + if (e < 0) { + return MA_ERROR; + } + + *pEvent = e; + + return MA_SUCCESS; +} + +static void ma_event_uninit__vita(ma_event* pEvent) +{ + if (pEvent == NULL) { + return; + } + + sceKernelDeleteEventFlag(*pEvent); +} + +static ma_result ma_event_wait__vita(ma_event* pEvent) +{ + unsigned int bits; + int result; + + if (pEvent == NULL) { + return MA_INVALID_ARGS; + } + + result = sceKernelWaitEventFlag(*pEvent, 1, SCE_EVENT_WAITAND | SCE_EVENT_WAITCLEAR, &bits, NULL); + if (result < 0) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +static ma_result ma_event_signal__vita(ma_event* pEvent) +{ + int result; + + if (pEvent == NULL) { + return MA_INVALID_ARGS; + } + + result = sceKernelSetEventFlag(*pEvent, 1); + if (result < 0) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + + +static ma_result ma_semaphore_init__vita(int initialValue, ma_semaphore* pSemaphore) +{ + int sem; + const char* pNameBase = "ma_sem_"; + size_t nameBaseLen = strlen(pNameBase); + char name[64]; + static ma_uint32 counter = 0; /* For creating a unique name. Atomically incremented. */ + + if (pSemaphore == NULL) { + return MA_INVALID_ARGS; + } + + ma_strcpy_s(name, sizeof(name), pNameBase); + ma_itoa_s((int)ma_atomic_fetch_add_explicit_32(&counter, 1, ma_atomic_memory_order_relaxed), name + nameBaseLen, sizeof(name) - nameBaseLen, 10); + + sem = sceKernelCreateSema(name, 0, initialValue, 0x7FFFFFFF, NULL); + if (sem < 0) { + return MA_ERROR; + } + + *pSemaphore = sem; + + return MA_SUCCESS; +} + +static void ma_semaphore_uninit__vita(ma_semaphore* pSemaphore) +{ + if (pSemaphore == NULL) { + return; + } + + sceKernelDeleteSema(*pSemaphore); +} + +static ma_result ma_semaphore_wait__vita(ma_semaphore* pSemaphore) +{ + int result; + + if (pSemaphore == NULL) { + return MA_INVALID_ARGS; + } + + result = sceKernelWaitSema(*pSemaphore, 1, NULL); + if (result < 0) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +static ma_result ma_semaphore_release__vita(ma_semaphore* pSemaphore) +{ + int result; + + if (pSemaphore == NULL) { + return MA_INVALID_ARGS; + } + + result = sceKernelSignalSema(*pSemaphore, 1); + if (result < 0) { + return MA_ERROR; + } + + return MA_SUCCESS; +} +#else +static ma_result ma_thread_create__none(ma_thread* pThread, ma_thread_priority priority, size_t stackSize, ma_thread_entry_proc entryProc, void* pData) +{ + (void)pThread; + (void)priority; + (void)stackSize; + (void)entryProc; + (void)pData; + + return MA_NOT_IMPLEMENTED; +} + +static void ma_thread_wait__none(ma_thread* pThread) +{ + (void)pThread; + return; +} + + +static ma_result ma_mutex_init__none(ma_mutex* pMutex) +{ + MA_ZERO_OBJECT(pMutex); + + return MA_SUCCESS; +} + +static void ma_mutex_uninit__none(ma_mutex* pMutex) +{ + (void)pMutex; +} + +static void ma_mutex_lock__none(ma_mutex* pMutex) +{ + ma_spinlock_lock(pMutex); +} + +static void ma_mutex_unlock__none(ma_mutex* pMutex) +{ + ma_spinlock_unlock(pMutex); +} + + +static ma_result ma_event_init__none(ma_event* pEvent) +{ + MA_ZERO_OBJECT(pEvent); + + return MA_SUCCESS; +} + +static void ma_event_uninit__none(ma_event* pEvent) +{ + (void)pEvent; +} + +static ma_result ma_event_wait__none(ma_event* pEvent) +{ + for (;;) { + ma_uint32 oldValue; + ma_uint32 newValue; + + oldValue = 1; + newValue = 0; + + if (ma_atomic_compare_exchange_strong_32(pEvent, &oldValue, newValue)) { + break; + } + + ma_yield(); + } + + return MA_SUCCESS; +} + +static ma_result ma_event_signal__none(ma_event* pEvent) +{ + ma_atomic_store_32(pEvent, 1); + return MA_SUCCESS; +} + + +static ma_result ma_semaphore_init__none(int initialValue, ma_semaphore* pSemaphore) +{ + ma_atomic_store_32(pSemaphore, (ma_uint32)initialValue); + return MA_SUCCESS; +} + +static void ma_semaphore_uninit__none(ma_semaphore* pSemaphore) +{ + (void)pSemaphore; +} + +static ma_result ma_semaphore_wait__none(ma_semaphore* pSemaphore) +{ + for (;;) { + ma_uint32 oldValue; + ma_uint32 newValue; + + oldValue = ma_atomic_load_32(pSemaphore); + if (oldValue == 0) { + ma_yield(); + continue; + } + + newValue = oldValue - 1; + + if (ma_atomic_compare_exchange_strong_32(pSemaphore, &oldValue, newValue)) { + break; + } + } + + return MA_SUCCESS; +} + +static ma_result ma_semaphore_release__none(ma_semaphore* pSemaphore) +{ + (void)ma_atomic_fetch_add_32(pSemaphore, 1); + return MA_SUCCESS; +} #endif + typedef struct { ma_thread_entry_proc entryProc; @@ -18036,7 +19177,7 @@ static ma_thread_result MA_THREADCALL ma_thread_entry_proxy(void* pData) return result; } -static ma_result ma_thread_create(ma_thread* pThread, ma_thread_priority priority, size_t stackSize, ma_thread_entry_proc entryProc, void* pData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_result ma_thread_create(ma_thread* pThread, ma_thread_priority priority, size_t stackSize, ma_thread_entry_proc entryProc, void* pData, const ma_allocation_callbacks* pAllocationCallbacks) { ma_result result; ma_thread_proxy_data* pProxyData; @@ -18057,13 +19198,17 @@ static ma_result ma_thread_create(ma_thread* pThread, ma_thread_priority priorit #endif pProxyData->entryProc = entryProc; - pProxyData->pData = pData; - ma_allocation_callbacks_init_copy(&pProxyData->allocationCallbacks, pAllocationCallbacks); + pProxyData->pData = pData; + pProxyData->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks); -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + result = ma_thread_create__vita(pThread, priority, stackSize, ma_thread_entry_proxy, pProxyData); +#elif defined(MA_THREADING_BACKEND_POSIX) result = ma_thread_create__posix(pThread, priority, stackSize, ma_thread_entry_proxy, pProxyData); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) result = ma_thread_create__win32(pThread, priority, stackSize, ma_thread_entry_proxy, pProxyData); +#else + result = ma_thread_create__none(pThread, priority, stackSize, ma_thread_entry_proxy, pProxyData); #endif if (result != MA_SUCCESS) { @@ -18074,16 +19219,20 @@ static ma_result ma_thread_create(ma_thread* pThread, ma_thread_priority priorit return MA_SUCCESS; } -static void ma_thread_wait(ma_thread* pThread) +MA_API void ma_thread_wait(ma_thread* pThread) { if (pThread == NULL) { return; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + ma_thread_wait__vita(pThread); +#elif defined(MA_THREADING_BACKEND_POSIX) ma_thread_wait__posix(pThread); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) ma_thread_wait__win32(pThread); +#else + ma_thread_wait__none(pThread); #endif } @@ -18095,10 +19244,14 @@ MA_API ma_result ma_mutex_init(ma_mutex* pMutex) return MA_INVALID_ARGS; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + return ma_mutex_init__vita(pMutex); +#elif defined(MA_THREADING_BACKEND_POSIX) return ma_mutex_init__posix(pMutex); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) return ma_mutex_init__win32(pMutex); +#else + return ma_mutex_init__none(pMutex); #endif } @@ -18108,10 +19261,14 @@ MA_API void ma_mutex_uninit(ma_mutex* pMutex) return; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + ma_mutex_uninit__vita(pMutex); +#elif defined(MA_THREADING_BACKEND_POSIX) ma_mutex_uninit__posix(pMutex); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) ma_mutex_uninit__win32(pMutex); +#else + ma_mutex_uninit__none(pMutex); #endif } @@ -18122,10 +19279,14 @@ MA_API void ma_mutex_lock(ma_mutex* pMutex) return; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + ma_mutex_lock__vita(pMutex); +#elif defined(MA_THREADING_BACKEND_POSIX) ma_mutex_lock__posix(pMutex); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) ma_mutex_lock__win32(pMutex); +#else + ma_mutex_lock__none(pMutex); #endif } @@ -18136,10 +19297,14 @@ MA_API void ma_mutex_unlock(ma_mutex* pMutex) return; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + ma_mutex_unlock__vita(pMutex); +#elif defined(MA_THREADING_BACKEND_POSIX) ma_mutex_unlock__posix(pMutex); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) ma_mutex_unlock__win32(pMutex); +#else + ma_mutex_unlock__none(pMutex); #endif } @@ -18151,10 +19316,14 @@ MA_API ma_result ma_event_init(ma_event* pEvent) return MA_INVALID_ARGS; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + return ma_event_init__vita(pEvent); +#elif defined(MA_THREADING_BACKEND_POSIX) return ma_event_init__posix(pEvent); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) return ma_event_init__win32(pEvent); +#else + return ma_event_init__none(pEvent); #endif } @@ -18192,10 +19361,14 @@ MA_API void ma_event_uninit(ma_event* pEvent) return; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + ma_event_uninit__vita(pEvent); +#elif defined(MA_THREADING_BACKEND_POSIX) ma_event_uninit__posix(pEvent); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) ma_event_uninit__win32(pEvent); +#else + ma_event_uninit__none(pEvent); #endif } @@ -18218,10 +19391,14 @@ MA_API ma_result ma_event_wait(ma_event* pEvent) return MA_INVALID_ARGS; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + return ma_event_wait__vita(pEvent); +#elif defined(MA_THREADING_BACKEND_POSIX) return ma_event_wait__posix(pEvent); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) return ma_event_wait__win32(pEvent); +#else + return ma_event_wait__none(pEvent); #endif } @@ -18232,10 +19409,14 @@ MA_API ma_result ma_event_signal(ma_event* pEvent) return MA_INVALID_ARGS; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + return ma_event_signal__vita(pEvent); +#elif defined(MA_THREADING_BACKEND_POSIX) return ma_event_signal__posix(pEvent); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) return ma_event_signal__win32(pEvent); +#else + return ma_event_signal__none(pEvent); #endif } @@ -18247,10 +19428,14 @@ MA_API ma_result ma_semaphore_init(int initialValue, ma_semaphore* pSemaphore) return MA_INVALID_ARGS; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + return ma_semaphore_init__vita(initialValue, pSemaphore); +#elif defined(MA_THREADING_BACKEND_POSIX) return ma_semaphore_init__posix(initialValue, pSemaphore); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) return ma_semaphore_init__win32(initialValue, pSemaphore); +#else + return ma_semaphore_init__none(initialValue, pSemaphore); #endif } @@ -18261,10 +19446,14 @@ MA_API void ma_semaphore_uninit(ma_semaphore* pSemaphore) return; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + ma_semaphore_uninit__vita(pSemaphore); +#elif defined(MA_THREADING_BACKEND_POSIX) ma_semaphore_uninit__posix(pSemaphore); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) ma_semaphore_uninit__win32(pSemaphore); +#else + ma_semaphore_uninit__none(pSemaphore); #endif } @@ -18275,10 +19464,14 @@ MA_API ma_result ma_semaphore_wait(ma_semaphore* pSemaphore) return MA_INVALID_ARGS; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + return ma_semaphore_wait__vita(pSemaphore); +#elif defined(MA_THREADING_BACKEND_POSIX) return ma_semaphore_wait__posix(pSemaphore); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) return ma_semaphore_wait__win32(pSemaphore); +#else + return ma_semaphore_wait__none(pSemaphore); #endif } @@ -18289,18 +19482,16 @@ MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore) return MA_INVALID_ARGS; } -#if defined(MA_POSIX) +#if defined(MA_THREADING_BACKEND_VITA) + return ma_semaphore_release__vita(pSemaphore); +#elif defined(MA_THREADING_BACKEND_POSIX) return ma_semaphore_release__posix(pSemaphore); -#elif defined(MA_WIN32) +#elif defined(MA_THREADING_BACKEND_WIN32) return ma_semaphore_release__win32(pSemaphore); -#endif -} #else -/* MA_NO_THREADING is set which means threading is disabled. Threading is required by some API families. If any of these are enabled we need to throw an error. */ -#ifndef MA_NO_DEVICE_IO -#error "MA_NO_THREADING cannot be used without MA_NO_DEVICE_IO"; + return ma_semaphore_release__none(pSemaphore); #endif -#endif /* MA_NO_THREADING */ +} @@ -18460,7 +19651,7 @@ MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotificati } pNotificationCallbacks->onSignal(pNotification); - return MA_INVALID_ARGS; + return MA_SUCCESS; } @@ -18907,10 +20098,6 @@ static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob static ma_result ma_job_process__resource_manager__page_data_stream(ma_job* pJob); static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob); -#if !defined(MA_NO_DEVICE_IO) -static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob); -#endif - static ma_job_proc g_jobVTable[MA_JOB_TYPE_COUNT] = { /* Miscellaneous. */ @@ -18927,11 +20114,6 @@ static ma_job_proc g_jobVTable[MA_JOB_TYPE_COUNT] = ma_job_process__resource_manager__free_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM */ ma_job_process__resource_manager__page_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_STREAM */ ma_job_process__resource_manager__seek_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_SEEK_DATA_STREAM */ - - /* Device. */ -#if !defined(MA_NO_DEVICE_IO) - ma_job_process__device__aaudio_reroute /* MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE */ -#endif }; MA_API ma_result ma_job_process(ma_job* pJob) @@ -19331,7 +20513,7 @@ Dynamic Linking *******************************************************************************/ /* Disable run-time linking on certain backends and platforms. */ #ifndef MA_NO_RUNTIME_LINKING - #if defined(MA_EMSCRIPTEN) || defined(MA_ORBIS) || defined(MA_PROSPERO) || defined(MA_SWITCH) || defined(MA_DOS) + #if defined(MA_EMSCRIPTEN) || defined(MA_ORBIS) || defined(MA_PROSPERO) || defined(MA_SWITCH) || defined(MA_DREAMCAST) || defined(MA_VITA) #define MA_NO_RUNTIME_LINKING #endif #endif @@ -19428,7 +20610,7 @@ MA_API ma_proc ma_dlsym(ma_log* pLog, ma_handle handle, const char* symbol) { ma_proc proc; - ma_log_postf(pLog, MA_LOG_LEVEL_DEBUG, "Loading symbol: %s\n", symbol); + ma_log_postf(pLog, MA_LOG_LEVEL_DEBUG, "Loading symbol: %s", symbol); #ifdef _WIN32 { @@ -19448,7 +20630,7 @@ MA_API ma_proc ma_dlsym(ma_log* pLog, ma_handle handle, const char* symbol) #endif if (proc == NULL) { - ma_log_postf(pLog, MA_LOG_LEVEL_WARNING, "Failed to load symbol: %s\n", symbol); + ma_log_postf(pLog, MA_LOG_LEVEL_WARNING, "Failed to load symbol: %s", symbol); } (void)pLog; /* It's possible for pContext to be unused. */ @@ -19492,252 +20674,161 @@ DEVICE I/O /* This must be set to at least 26. */ #ifndef MA_AAUDIO_MIN_ANDROID_SDK_VERSION -#define MA_AAUDIO_MIN_ANDROID_SDK_VERSION 27 +#define MA_AAUDIO_MIN_ANDROID_SDK_VERSION 27 #endif +/* This must be set to at least 9. */ +#ifndef MA_OPENSL_MIN_ANDROID_SDK_VERSION +#define MA_OPENSL_MIN_ANDROID_SDK_VERSION 9 +#endif -MA_API void ma_device_info_add_native_data_format(ma_device_info* pDeviceInfo, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags) -{ - if (pDeviceInfo == NULL) { - return; - } - - if (pDeviceInfo->nativeDataFormatCount < ma_countof(pDeviceInfo->nativeDataFormats)) { - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags; - pDeviceInfo->nativeDataFormatCount += 1; - } -} +/************************************************************************************************************************************************************ -typedef struct -{ - ma_backend backend; - const char* pName; -} ma_backend_info; - -static ma_backend_info gBackendInfo[] = /* Indexed by the backend enum. Must be in the order backends are declared in the ma_backend enum. */ -{ - {ma_backend_wasapi, "WASAPI"}, - {ma_backend_dsound, "DirectSound"}, - {ma_backend_winmm, "WinMM"}, - {ma_backend_coreaudio, "Core Audio"}, - {ma_backend_sndio, "sndio"}, - {ma_backend_audio4, "audio(4)"}, - {ma_backend_oss, "OSS"}, - {ma_backend_pulseaudio, "PulseAudio"}, - {ma_backend_alsa, "ALSA"}, - {ma_backend_jack, "JACK"}, - {ma_backend_aaudio, "AAudio"}, - {ma_backend_opensl, "OpenSL|ES"}, - {ma_backend_webaudio, "Web Audio"}, - {ma_backend_custom, "Custom"}, - {ma_backend_null, "Null"} -}; +BACKENDS -MA_API const char* ma_get_backend_name(ma_backend backend) -{ - if (backend < 0 || backend >= (int)ma_countof(gBackendInfo)) { - return "Unknown"; - } +************************************************************************************************************************************************************/ +/* Some backends are only supported on certain platforms. */ +#if defined(MA_WIN32) && !defined(MA_XBOX) + #define MA_SUPPORT_WASAPI - return gBackendInfo[backend].pName; -} + #if defined(MA_WIN32_DESKTOP) /* DirectSound and WinMM backends are only supported on desktops. */ + #define MA_SUPPORT_DSOUND + #define MA_SUPPORT_WINMM + #define MA_SUPPORT_JACK + #endif +#endif +#if defined(MA_UNIX) && !defined(MA_ORBIS) && !defined(MA_PROSPERO) + #if defined(MA_LINUX) + #if !defined(MA_ANDROID) && !defined(MA_EMSCRIPTEN) /* ALSA is not supported on Android. */ + #define MA_SUPPORT_PIPEWIRE + #define MA_SUPPORT_ALSA + #endif + #endif + #if !defined(MA_BSD) && !defined(MA_ANDROID) && !defined(MA_EMSCRIPTEN) + #define MA_SUPPORT_PULSEAUDIO + #define MA_SUPPORT_JACK + #endif + #if defined(__OpenBSD__) /* <-- Change this to "#if defined(MA_BSD)" to enable sndio on all BSD flavors. */ + #define MA_SUPPORT_SNDIO /* sndio is only supported on OpenBSD for now. May be expanded later if there's demand. */ + #endif + #if defined(__NetBSD__) || defined(__OpenBSD__) || defined(MA_SUN) + #define MA_SUPPORT_AUDIO4 /* Only support audio(4) on platforms with known support. */ + #endif + #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(MA_LINUX) && !defined(MA_ANDROID) + #define MA_SUPPORT_OSS /* Only support OSS on specific platforms with known support. */ + #endif +#endif +#if defined(MA_ANDROID) + #define MA_SUPPORT_AAUDIO + #define MA_SUPPORT_OPENSL +#endif +#if defined(MA_APPLE) + #define MA_SUPPORT_COREAUDIO +#endif +#if defined(MA_EMSCRIPTEN) + #define MA_SUPPORT_WEBAUDIO +#endif +#if defined(MA_DREAMCAST) + #define MA_SUPPORT_DREAMCAST +#endif +#if defined(MA_XBOX) + #define MA_SUPPORT_XAUDIO +#endif +#if defined(MA_VITA) + #define MA_SUPPORT_VITA +#endif -MA_API ma_result ma_get_backend_from_name(const char* pBackendName, ma_backend* pBackend) -{ - size_t iBackend; +/* All platforms should support custom backends. */ +#define MA_SUPPORT_CUSTOM - if (pBackendName == NULL) { - return MA_INVALID_ARGS; - } +/* Explicitly disable the Null backend for Emscripten because it uses a background thread which is not properly supported right now. */ +#if !defined(MA_EMSCRIPTEN) +#define MA_SUPPORT_NULL +#endif - for (iBackend = 0; iBackend < ma_countof(gBackendInfo); iBackend += 1) { - if (ma_strcmp(pBackendName, gBackendInfo[iBackend].pName) == 0) { - if (pBackend != NULL) { - *pBackend = gBackendInfo[iBackend].backend; - } - return MA_SUCCESS; - } - } +#if defined(MA_SUPPORT_WASAPI) && !defined(MA_NO_WASAPI) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WASAPI)) + #define MA_HAS_WASAPI +#endif +#if defined(MA_SUPPORT_DSOUND) && !defined(MA_NO_DSOUND) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_DSOUND)) + #define MA_HAS_DSOUND +#endif +#if defined(MA_SUPPORT_WINMM) && !defined(MA_NO_WINMM) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WINMM)) + #define MA_HAS_WINMM +#endif +#if defined(MA_SUPPORT_COREAUDIO) && !defined(MA_NO_COREAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_COREAUDIO)) + #define MA_HAS_COREAUDIO +#endif +#if defined(MA_SUPPORT_PIPEWIRE) && !defined(MA_NO_PIPEWIRE) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_PIPEWIRE)) + #define MA_HAS_PIPEWIRE +#endif +#if defined(MA_SUPPORT_PULSEAUDIO) && !defined(MA_NO_PULSEAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_PULSEAUDIO)) + #define MA_HAS_PULSEAUDIO +#endif +#if defined(MA_SUPPORT_JACK) && !defined(MA_NO_JACK) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_JACK)) + #define MA_HAS_JACK +#endif +#if defined(MA_SUPPORT_ALSA) && !defined(MA_NO_ALSA) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_ALSA)) + #define MA_HAS_ALSA +#endif +#if defined(MA_SUPPORT_SNDIO) && !defined(MA_NO_SNDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SNDIO)) + #define MA_HAS_SNDIO +#endif +#if defined(MA_SUPPORT_AUDIO4) && !defined(MA_NO_AUDIO4) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_AUDIO4)) + #define MA_HAS_AUDIO4 +#endif +#if defined(MA_SUPPORT_OSS) && !defined(MA_NO_OSS) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_OSS)) + #define MA_HAS_OSS +#endif +#if defined(MA_SUPPORT_AAUDIO) && !defined(MA_NO_AAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_AAUDIO)) + #define MA_HAS_AAUDIO +#endif +#if defined(MA_SUPPORT_OPENSL) && !defined(MA_NO_OPENSL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_OPENSL)) + #define MA_HAS_OPENSL +#endif +#if defined(MA_SUPPORT_WEBAUDIO) && !defined(MA_NO_WEBAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WEBAUDIO)) + #define MA_HAS_WEBAUDIO +#endif +#if defined(MA_SUPPORT_DREAMCAST) && !defined(MA_NO_DREAMCAST) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_DREAMCAST)) + #define MA_HAS_DREAMCAST +#endif +#if defined(MA_SUPPORT_XAUDIO) && !defined(MA_NO_XAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_XAUDIO)) + #define MA_HAS_XAUDIO +#endif +#if defined(MA_SUPPORT_VITA) && !defined(MA_NO_VITA) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_VITA)) + #define MA_HAS_VITA +#endif +#if defined(MA_SUPPORT_NULL) && !defined(MA_NO_NULL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_NULL)) + #define MA_HAS_NULL +#endif - /* Getting here means the backend name is unknown. */ - return MA_INVALID_ARGS; -} +/************************************************************************************************************************************************************ -MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend) -{ - /* - This looks a little bit gross, but we want all backends to be included in the switch to avoid warnings on some compilers - about some enums not being handled by the switch statement. - */ - switch (backend) - { - case ma_backend_wasapi: - #if defined(MA_HAS_WASAPI) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_dsound: - #if defined(MA_HAS_DSOUND) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_winmm: - #if defined(MA_HAS_WINMM) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_coreaudio: - #if defined(MA_HAS_COREAUDIO) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_sndio: - #if defined(MA_HAS_SNDIO) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_audio4: - #if defined(MA_HAS_AUDIO4) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_oss: - #if defined(MA_HAS_OSS) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_pulseaudio: - #if defined(MA_HAS_PULSEAUDIO) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_alsa: - #if defined(MA_HAS_ALSA) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_jack: - #if defined(MA_HAS_JACK) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_aaudio: - #if defined(MA_HAS_AAUDIO) - #if defined(MA_ANDROID) - { - return ma_android_sdk_version() >= MA_AAUDIO_MIN_ANDROID_SDK_VERSION; - } - #else - return MA_FALSE; - #endif - #else - return MA_FALSE; - #endif - case ma_backend_opensl: - #if defined(MA_HAS_OPENSL) - #if defined(MA_ANDROID) - { - return ma_android_sdk_version() >= 9; - } - #else - return MA_TRUE; - #endif - #else - return MA_FALSE; - #endif - case ma_backend_webaudio: - #if defined(MA_HAS_WEBAUDIO) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_custom: - #if defined(MA_HAS_CUSTOM) - return MA_TRUE; - #else - return MA_FALSE; - #endif - case ma_backend_null: - #if defined(MA_HAS_NULL) - return MA_TRUE; - #else - return MA_FALSE; - #endif +END BACKENDS - default: return MA_FALSE; - } -} +************************************************************************************************************************************************************/ -MA_API ma_result ma_get_enabled_backends(ma_backend* pBackends, size_t backendCap, size_t* pBackendCount) +MA_API void ma_device_info_add_native_data_format_ex(ma_device_info* pDeviceInfo, ma_uint32 flags, ma_format format, ma_uint32 minChannels, ma_uint32 maxChannels, ma_uint32 minSampleRate, ma_uint32 maxSampleRate) { - size_t backendCount; - size_t iBackend; - ma_result result = MA_SUCCESS; - - if (pBackendCount == NULL) { - return MA_INVALID_ARGS; - } - - backendCount = 0; - - for (iBackend = 0; iBackend <= ma_backend_null; iBackend += 1) { - ma_backend backend = (ma_backend)iBackend; - - if (ma_is_backend_enabled(backend)) { - /* The backend is enabled. Try adding it to the list. If there's no room, MA_NO_SPACE needs to be returned. */ - if (backendCount == backendCap) { - result = MA_NO_SPACE; - break; - } else { - pBackends[backendCount] = backend; - backendCount += 1; - } - } + if (pDeviceInfo == NULL) { + return; } - if (pBackendCount != NULL) { - *pBackendCount = backendCount; + if (pDeviceInfo->nativeDataFormatCount < ma_countof(pDeviceInfo->nativeDataFormats)) { + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].minChannels = minChannels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].maxChannels = maxChannels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].minSampleRate = minSampleRate; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].maxSampleRate = maxSampleRate; + pDeviceInfo->nativeDataFormatCount += 1; } - - return result; } -MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend) +MA_API void ma_device_info_add_native_data_format(ma_device_info* pDeviceInfo, ma_format format, ma_uint32 minChannels, ma_uint32 maxChannels, ma_uint32 minSampleRate, ma_uint32 maxSampleRate) { - switch (backend) - { - case ma_backend_wasapi: return MA_TRUE; - case ma_backend_dsound: return MA_FALSE; - case ma_backend_winmm: return MA_FALSE; - case ma_backend_coreaudio: return MA_FALSE; - case ma_backend_sndio: return MA_FALSE; - case ma_backend_audio4: return MA_FALSE; - case ma_backend_oss: return MA_FALSE; - case ma_backend_pulseaudio: return MA_FALSE; - case ma_backend_alsa: return MA_FALSE; - case ma_backend_jack: return MA_FALSE; - case ma_backend_aaudio: return MA_FALSE; - case ma_backend_opensl: return MA_FALSE; - case ma_backend_webaudio: return MA_FALSE; - case ma_backend_custom: return MA_FALSE; /* <-- Will depend on the implementation of the backend. */ - case ma_backend_null: return MA_FALSE; - default: return MA_FALSE; - } + ma_device_info_add_native_data_format_ex(pDeviceInfo, 0, format, minChannels, maxChannels, minSampleRate, maxSampleRate); } @@ -19947,16 +21038,6 @@ typedef void (WINAPI * MA_PFN_CoTaskMemFree)(void* pv); typedef HRESULT (WINAPI * MA_PFN_PropVariantClear)(MA_PROPVARIANT *pvar); typedef int (WINAPI * MA_PFN_StringFromGUID2)(const GUID* const rguid, WCHAR* lpsz, int cchMax); -typedef HWND (WINAPI * MA_PFN_GetForegroundWindow)(void); -typedef HWND (WINAPI * MA_PFN_GetDesktopWindow)(void); - -#if defined(MA_WIN32_DESKTOP) -/* Microsoft documents these APIs as returning LSTATUS, but the Win32 API shipping with some compilers do not define it. It's just a LONG. */ -typedef LONG (WINAPI * MA_PFN_RegOpenKeyExA)(HKEY hKey, const char* lpSubKey, DWORD ulOptions, DWORD samDesired, HKEY* phkResult); -typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); -typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, const char* lpValueName, DWORD* lpReserved, DWORD* lpType, BYTE* lpData, DWORD* lpcbData); -#endif /* MA_WIN32_DESKTOP */ - static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; /*static GUID MA_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};*/ @@ -20025,7 +21106,7 @@ Timing *******************************************************************************/ #if defined(MA_WIN32) && !defined(MA_POSIX) static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */ - static MA_INLINE void ma_timer_init(ma_timer* pTimer) + MA_API void ma_timer_init(ma_timer* pTimer) { LARGE_INTEGER counter; @@ -20037,7 +21118,7 @@ Timing pTimer->counter = counter.QuadPart; } - static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) + MA_API double ma_timer_get_time_in_seconds(ma_timer* pTimer) { LARGE_INTEGER counter; if (!QueryPerformanceCounter(&counter)) { @@ -20048,7 +21129,7 @@ Timing } #elif defined(MA_APPLE) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101200) static ma_uint64 g_ma_TimerFrequency = 0; - static MA_INLINE void ma_timer_init(ma_timer* pTimer) + MA_API void ma_timer_init(ma_timer* pTimer) { mach_timebase_info_data_t baseTime; mach_timebase_info(&baseTime); @@ -20057,7 +21138,7 @@ Timing pTimer->counter = mach_absolute_time(); } - static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) + MA_API double ma_timer_get_time_in_seconds(ma_timer* pTimer) { ma_uint64 newTimeCounter = mach_absolute_time(); ma_uint64 oldTimeCounter = pTimer->counter; @@ -20065,12 +21146,12 @@ Timing return (newTimeCounter - oldTimeCounter) / g_ma_TimerFrequency; } #elif defined(MA_EMSCRIPTEN) - static MA_INLINE void ma_timer_init(ma_timer* pTimer) + MA_API void ma_timer_init(ma_timer* pTimer) { pTimer->counterD = emscripten_get_now(); } - static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) + MA_API double ma_timer_get_time_in_seconds(ma_timer* pTimer) { return (emscripten_get_now() - pTimer->counterD) / 1000; /* Emscripten is in milliseconds. */ } @@ -20082,7 +21163,7 @@ Timing #define MA_CLOCK_ID CLOCK_REALTIME #endif - static MA_INLINE void ma_timer_init(ma_timer* pTimer) + MA_API void ma_timer_init(ma_timer* pTimer) { struct timespec newTime; clock_gettime(MA_CLOCK_ID, &newTime); @@ -20090,7 +21171,7 @@ Timing pTimer->counter = ((ma_int64)newTime.tv_sec * 1000000000) + newTime.tv_nsec; } - static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) + MA_API double ma_timer_get_time_in_seconds(ma_timer* pTimer) { ma_uint64 newTimeCounter; ma_uint64 oldTimeCounter; @@ -20104,7 +21185,7 @@ Timing return (newTimeCounter - oldTimeCounter) / 1000000000.0; } #else - static MA_INLINE void ma_timer_init(ma_timer* pTimer) + MA_API void ma_timer_init(ma_timer* pTimer) { struct timeval newTime; gettimeofday(&newTime, NULL); @@ -20112,7 +21193,7 @@ Timing pTimer->counter = ((ma_int64)newTime.tv_sec * 1000000) + newTime.tv_usec; } - static MA_INLINE double ma_timer_get_time_in_seconds(ma_timer* pTimer) + MA_API double ma_timer_get_time_in_seconds(ma_timer* pTimer) { ma_uint64 newTimeCounter; ma_uint64 oldTimeCounter; @@ -20137,8 +21218,8 @@ static ma_uint32 ma_get_closest_standard_sample_rate(ma_uint32 sampleRateIn) ma_uint32 closestDiff = 0xFFFFFFFF; size_t iStandardRate; - for (iStandardRate = 0; iStandardRate < ma_countof(g_maStandardSampleRatePriorities); ++iStandardRate) { - ma_uint32 standardRate = g_maStandardSampleRatePriorities[iStandardRate]; + for (iStandardRate = 0; iStandardRate < ma_countof(ma_standard_sample_rates); ++iStandardRate) { + ma_uint32 standardRate = ma_standard_sample_rates[iStandardRate]; ma_uint32 diff; if (sampleRateIn > standardRate) { @@ -20185,61 +21266,6 @@ static MA_INLINE void ma_device_restore_denormals(ma_device* pDevice, unsigned i } } -static ma_device_notification ma_device_notification_init(ma_device* pDevice, ma_device_notification_type type) -{ - ma_device_notification notification; - - MA_ZERO_OBJECT(¬ification); - notification.pDevice = pDevice; - notification.type = type; - - return notification; -} - -static void ma_device__on_notification(ma_device_notification notification) -{ - MA_ASSERT(notification.pDevice != NULL); - - if (notification.pDevice->onNotification != NULL) { - notification.pDevice->onNotification(¬ification); - } - - /* TEMP FOR COMPATIBILITY: If it's a stopped notification, fire the onStop callback as well. This is only for backwards compatibility and will be removed. */ - if (notification.pDevice->onStop != NULL && notification.type == ma_device_notification_type_stopped) { - notification.pDevice->onStop(notification.pDevice); - } -} - -static void ma_device__on_notification_started(ma_device* pDevice) -{ - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_started)); -} - -static void ma_device__on_notification_stopped(ma_device* pDevice) -{ - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_stopped)); -} - -/* Not all platforms support reroute notifications. */ -#if !defined(MA_EMSCRIPTEN) -static void ma_device__on_notification_rerouted(ma_device* pDevice) -{ - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_rerouted)); -} -#endif - -#if defined(MA_EMSCRIPTEN) -#ifdef __cplusplus -extern "C" { -#endif -void EMSCRIPTEN_KEEPALIVE ma_device__on_notification_unlocked(ma_device* pDevice) -{ - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_unlocked)); -} -#ifdef __cplusplus -} -#endif -#endif static void ma_device__on_data_inner(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) @@ -20259,7 +21285,7 @@ static void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* MA_ASSERT(pDevice != NULL); /* Don't read more data from the client if we're in the process of stopping. */ - if (ma_device_get_state(pDevice) == ma_device_state_stopping) { + if (ma_device_get_status(pDevice) == ma_device_status_stopping) { return; } @@ -20395,7 +21421,11 @@ static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut } } - if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) { + /* + Clipping is only necessary when both the external and internal format is f32. For anything else samples + will be clipped naturally as part of format conversion. + */ + if (!pDevice->noClip && pDevice->playback.format == ma_format_f32 && pDevice->playback.internalFormat == ma_format_f32) { ma_clip_samples_f32((float*)pFramesOut, (const float*)pFramesOut, frameCount * pDevice->playback.channels); /* Intentionally specifying the same pointer for both input and output for in-place processing. */ } } @@ -20430,8 +21460,8 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra /* We run slightly different logic depending on whether or not we're using a heap-allocated - buffer for caching input data. This will be the case if the data converter does not have - the ability to retrieve the required input frame count for a given output frame count. + buffer for caching input data. This will be the case if the data converter is doing resampling + because in that case the number of input frames required is not known. */ if (pDevice->playback.pInputCache != NULL) { while (totalFramesReadOut < frameCount) { @@ -20451,8 +21481,8 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra break; } - pDevice->playback.inputCacheConsumed += framesToReadThisIterationIn; - pDevice->playback.inputCacheRemaining -= framesToReadThisIterationIn; + pDevice->playback.inputCacheConsumed += (ma_uint32)framesToReadThisIterationIn; + pDevice->playback.inputCacheRemaining -= (ma_uint32)framesToReadThisIterationIn; totalFramesReadOut += framesToReadThisIterationOut; pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesToReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); @@ -20478,7 +21508,6 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra ma_uint64 framesReadThisIterationIn; ma_uint64 framesToReadThisIterationOut; ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; framesToReadThisIterationOut = (frameCount - totalFramesReadOut); framesToReadThisIterationIn = framesToReadThisIterationOut; @@ -20486,11 +21515,6 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra framesToReadThisIterationIn = intermediaryBufferCap; } - ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, framesToReadThisIterationOut, &requiredInputFrameCount); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } - ma_device__handle_data_callback(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); /* @@ -20563,7 +21587,7 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame } } -static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, ma_uint32 frameCountInDeviceFormat, const void* pFramesInDeviceFormat, ma_pcm_rb* pRB) +static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, ma_uint32 frameCountInDeviceFormat, const void* pFramesInDeviceFormat, ma_audio_ring_buffer* pRB) { ma_result result; ma_uint32 totalDeviceFramesProcessed = 0; @@ -20582,16 +21606,9 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m ma_uint64 framesProcessedInClientFormat; void* pFramesInClientFormat; - result = ma_pcm_rb_acquire_write(pRB, &framesToProcessInClientFormat, &pFramesInClientFormat); - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer."); - break; - } - + framesToProcessInClientFormat = ma_audio_ring_buffer_map_produce(pRB, framesToProcessInClientFormat, &pFramesInClientFormat); if (framesToProcessInClientFormat == 0) { - if (ma_pcm_rb_pointer_distance(pRB) == (ma_int32)ma_pcm_rb_get_subbuffer_size(pRB)) { - break; /* Overrun. Not enough room in the ring buffer for input frame. Excess frames are dropped. */ - } + break; /* Overrun. Not enough room in the ring buffer for input frame. Excess frames are dropped. */ } /* Convert. */ @@ -20602,11 +21619,7 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m break; } - result = ma_pcm_rb_commit_write(pRB, (ma_uint32)framesProcessedInClientFormat); /* Safe cast. */ - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer."); - break; - } + ma_audio_ring_buffer_unmap_produce(pRB, (ma_uint32)framesProcessedInClientFormat); pRunningFramesInDeviceFormat = ma_offset_ptr(pRunningFramesInDeviceFormat, framesProcessedInDeviceFormat * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); totalDeviceFramesProcessed += (ma_uint32)framesProcessedInDeviceFormat; /* Safe cast. */ @@ -20620,9 +21633,8 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m return MA_SUCCESS; } -static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint32 frameCount, void* pFramesInInternalFormat, ma_pcm_rb* pRB) +static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint32 frameCount, void* pFramesInInternalFormat, ma_audio_ring_buffer* pRB) { - ma_result result; ma_uint8 silentInputFrames[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint32 totalFramesReadOut = 0; @@ -20648,11 +21660,11 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint64 framesConvertedOut = (frameCount - totalFramesReadOut); ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut); - pDevice->playback.inputCacheConsumed += framesConvertedIn; - pDevice->playback.inputCacheRemaining -= framesConvertedIn; + pDevice->playback.inputCacheConsumed += (ma_uint32)framesConvertedIn; + pDevice->playback.inputCacheRemaining -= (ma_uint32)framesConvertedIn; - totalFramesReadOut += (ma_uint32)framesConvertedOut; /* Safe cast. */ - pFramesInInternalFormat = ma_offset_ptr(pFramesInInternalFormat, framesConvertedOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + totalFramesReadOut += (ma_uint32)framesConvertedOut; /* Safe cast. */ + pFramesInInternalFormat = ma_offset_ptr(pFramesInInternalFormat, framesConvertedOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); } /* If there's no more data in the cache we'll need to fill it with some. */ @@ -20660,29 +21672,20 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint32 inputFrameCount; void* pInputFrames; - inputFrameCount = (ma_uint32)pDevice->playback.inputCacheCap; - result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); - if (result == MA_SUCCESS) { - if (inputFrameCount > 0) { - ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount); - } else { - if (ma_pcm_rb_pointer_distance(pRB) == 0) { - break; /* Underrun. */ - } - } + inputFrameCount = pDevice->playback.inputCacheCap; + inputFrameCount = ma_audio_ring_buffer_map_consume(pRB, inputFrameCount, &pInputFrames); + if (inputFrameCount > 0) { + ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount); + ma_audio_ring_buffer_unmap_consume(pRB, inputFrameCount); } else { - /* No capture data available. Feed in silence. */ + /* Underrun. No capture data available. Feed in silence. */ inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap, sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, silentInputFrames, inputFrameCount); + ma_audio_ring_buffer_unmap_consume(pRB, 0); /* <-- Don't *actually* have to unmap this since it's a count of zero, but it makes me feel better having it be properly paired. */ } pDevice->playback.inputCacheConsumed = 0; pDevice->playback.inputCacheRemaining = inputFrameCount; - - result = ma_pcm_rb_commit_read(pRB, inputFrameCount); - if (result != MA_SUCCESS) { - return result; /* Should never happen. */ - } } } @@ -20690,9 +21693,18 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, } /* A helper for changing the state of the device. */ -static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_device_state newState) +static MA_INLINE void ma_device_set_status(ma_device* pDevice, ma_device_status newStatus) { - ma_atomic_device_state_set(&pDevice->state, newState); + /* We cannot change away from an errored state. */ + if (ma_device_get_status(pDevice) == ma_device_status_errored) { + if (newStatus != ma_device_status_errored) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Device is in an errored state. The device status cannot be changed."); + } + + return; + } + + ma_atomic_device_status_set(&pDevice->status, newStatus); } @@ -20710,7 +21722,6 @@ MA_API ma_uint32 ma_get_format_priority_index(ma_format format) /* Lower = bette return (ma_uint32)-1; } -static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type deviceType); static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDeviceDescriptor) { @@ -20734,198 +21745,6 @@ static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDevi } -static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = 0; - ma_uint32 playbackDeviceDataCapInFrames = 0; - - MA_ASSERT(pDevice != NULL); - - /* Just some quick validation on the device type and the available callbacks. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - if (pDevice->pContext->callbacks.onDeviceRead == NULL) { - return MA_NOT_IMPLEMENTED; - } - - capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - if (pDevice->pContext->callbacks.onDeviceWrite == NULL) { - return MA_NOT_IMPLEMENTED; - } - - playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - } - - /* NOTE: The device was started outside of this function, in the worker thread. */ - - while (ma_device_get_state(pDevice) == ma_device_state_started && !exitLoop) { - switch (pDevice->type) { - case ma_device_type_duplex: - { - /* The process is: onDeviceRead() -> convert -> callback -> convert -> onDeviceWrite() */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = pDevice->pContext->callbacks.onDeviceRead(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - /* At this point we have our captured data in device format and we now need to convert it to client format. */ - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhausted all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__handle_data_callback(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = pDevice->pContext->callbacks.onDeviceWrite(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__null()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - /* Make sure we don't get stuck in the inner loop. */ - if (capturedDeviceFramesProcessed == 0) { - break; - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - case ma_device_type_loopback: - { - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > capturedDeviceDataCapInFrames) { - framesToReadThisIteration = capturedDeviceDataCapInFrames; - } - - result = pDevice->pContext->callbacks.onDeviceRead(pDevice, capturedDeviceData, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - /* Make sure we don't get stuck in the inner loop. */ - if (framesProcessed == 0) { - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, capturedDeviceData); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > playbackDeviceDataCapInFrames) { - framesToWriteThisIteration = playbackDeviceDataCapInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, playbackDeviceData); - - result = pDevice->pContext->callbacks.onDeviceWrite(pDevice, playbackDeviceData, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - /* Make sure we don't get stuck in the inner loop. */ - if (framesProcessed == 0) { - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* Should never get here. */ - default: break; - } - } - - return result; -} - - - /******************************************************************************* Null Backend @@ -20933,144 +21752,116 @@ Null Backend *******************************************************************************/ #ifdef MA_HAS_NULL -#define MA_DEVICE_OP_NONE__NULL 0 -#define MA_DEVICE_OP_START__NULL 1 -#define MA_DEVICE_OP_SUSPEND__NULL 2 -#define MA_DEVICE_OP_KILL__NULL 3 +typedef struct ma_context_state_null +{ + int _unused; +} ma_context_state_null; -static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData) +typedef struct ma_device_state_null { - ma_device* pDevice = (ma_device*)pData; - MA_ASSERT(pDevice != NULL); + ma_timer timer; + ma_uint64 cursor; /* When the actual time catches up to the cursor it means the next chunk of audio can be processed. If the actual time gets to the cursor + the period size, it's an xrun. */ + void* pDataPlayback; /* Only used as the destination for audio data in step(). Will not actually be used for anything. */ + void* pDataCapture; /* Permanently filled with silence. */ +} ma_device_state_null; - for (;;) { /* Keep the thread alive until the device is uninitialized. */ - ma_uint32 operation; - /* Wait for an operation to be requested. */ - ma_event_wait(&pDevice->null_device.operationEvent); +static ma_context_state_null* ma_context_get_backend_state__null(ma_context* pContext) +{ + return (ma_context_state_null*)ma_context_get_backend_state(pContext); +} - /* At this point an event should have been triggered. */ - operation = pDevice->null_device.operation; +static ma_device_state_null* ma_device_get_backend_state__null(ma_device* pDevice) +{ + return (ma_device_state_null*)ma_device_get_backend_state(pDevice); +} - /* Starting the device needs to put the thread into a loop. */ - if (operation == MA_DEVICE_OP_START__NULL) { - /* Reset the timer just in case. */ - ma_timer_init(&pDevice->null_device.timer); - /* Getting here means a suspend or kill operation has been requested. */ - pDevice->null_device.operationResult = MA_SUCCESS; - ma_event_signal(&pDevice->null_device.operationCompletionEvent); - ma_semaphore_release(&pDevice->null_device.operationSemaphore); - continue; - } +static void ma_backend_info__null(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "Null (Silence)"; +} - /* Suspending the device means we need to stop the timer and just continue the loop. */ - if (operation == MA_DEVICE_OP_SUSPEND__NULL) { - /* We need to add the current run time to the prior run time, then reset the timer. */ - pDevice->null_device.priorRunTime += ma_timer_get_time_in_seconds(&pDevice->null_device.timer); - ma_timer_init(&pDevice->null_device.timer); +static ma_result ma_context_init__null(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_null* pContextStateNull; + const ma_context_config_null* pContextConfigNull = (const ma_context_config_null*)pContextBackendConfig; + ma_context_config_null defaultConfigNull; - /* We're done. */ - pDevice->null_device.operationResult = MA_SUCCESS; - ma_event_signal(&pDevice->null_device.operationCompletionEvent); - ma_semaphore_release(&pDevice->null_device.operationSemaphore); - continue; - } + if (pContextConfigNull == NULL) { + defaultConfigNull = ma_context_config_null_init(); + pContextConfigNull = &defaultConfigNull; + } - /* Killing the device means we need to get out of this loop so that this thread can terminate. */ - if (operation == MA_DEVICE_OP_KILL__NULL) { - pDevice->null_device.operationResult = MA_SUCCESS; - ma_event_signal(&pDevice->null_device.operationCompletionEvent); - ma_semaphore_release(&pDevice->null_device.operationSemaphore); - break; - } + (void)pContextConfigNull; - /* Getting a signal on a "none" operation probably means an error. Return invalid operation. */ - if (operation == MA_DEVICE_OP_NONE__NULL) { - MA_ASSERT(MA_FALSE); /* <-- Trigger this in debug mode to ensure developers are aware they're doing something wrong (or there's a bug in a miniaudio). */ - pDevice->null_device.operationResult = MA_INVALID_OPERATION; - ma_event_signal(&pDevice->null_device.operationCompletionEvent); - ma_semaphore_release(&pDevice->null_device.operationSemaphore); - continue; /* Continue the loop. Don't terminate. */ - } + pContextStateNull = (ma_context_state_null*)ma_calloc(sizeof(*pContextStateNull), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateNull == NULL) { + return MA_OUT_OF_MEMORY; } - return (ma_thread_result)0; + *ppContextState = pContextStateNull; + + return MA_SUCCESS; } -static ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 operation) +static void ma_context_uninit__null(ma_context* pContext) { - ma_result result; + ma_context_state_null* pContextStateNull = ma_context_get_backend_state__null(pContext); - /* - TODO: Need to review this and consider just using mutual exclusion. I think the original motivation - for this was to just post the event to a queue and return immediately, but that has since changed - and now this function is synchronous. I think this can be simplified to just use a mutex. - */ + ma_free(pContextStateNull, ma_context_get_allocation_callbacks(pContext)); +} - /* - The first thing to do is wait for an operation slot to become available. We only have a single slot for this, but we could extend this later - to support queuing of operations. - */ - result = ma_semaphore_wait(&pDevice->null_device.operationSemaphore); - if (result != MA_SUCCESS) { - return result; /* Failed to wait for the event. */ - } +static ma_device_enumeration_result ma_context_enumerate_device_from_type__null(ma_context* pContext, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_device_info deviceInfo; - /* - When we get here it means the background thread is not referencing the operation code and it can be changed. After changing this we need to - signal an event to the worker thread to let it know that it can start work. - */ - pDevice->null_device.operation = operation; + (void)pContext; - /* Once the operation code has been set, the worker thread can start work. */ - if (ma_event_signal(&pDevice->null_device.operationEvent) != MA_SUCCESS) { - return MA_ERROR; - } + MA_ZERO_OBJECT(&deviceInfo); - /* We want everything to be synchronous so we're going to wait for the worker thread to complete it's operation. */ - if (ma_event_wait(&pDevice->null_device.operationCompletionEvent) != MA_SUCCESS) { - return MA_ERROR; - } + /* Default. */ + deviceInfo.isDefault = MA_TRUE; - return pDevice->null_device.operationResult; -} + /* ID. */ + deviceInfo.id.nullbackend = 0; -static ma_uint64 ma_device_get_total_run_time_in_frames__null(ma_device* pDevice) -{ - ma_uint32 internalSampleRate; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - internalSampleRate = pDevice->capture.internalSampleRate; + /* Name. */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Playback Device", (size_t)-1); } else { - internalSampleRate = pDevice->playback.internalSampleRate; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Capture Device", (size_t)-1); } - return (ma_uint64)((pDevice->null_device.priorRunTime + ma_timer_get_time_in_seconds(&pDevice->null_device.timer)) * internalSampleRate); + /* Data Format. */ + ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, MA_MAX_CHANNELS, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, MA_MAX_CHANNELS, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(&deviceInfo, ma_format_s32, 1, MA_MAX_CHANNELS, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(&deviceInfo, ma_format_s24, 1, MA_MAX_CHANNELS, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(&deviceInfo, ma_format_u8, 1, MA_MAX_CHANNELS, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + + return callback(deviceType, &deviceInfo, pUserData); } static ma_result ma_context_enumerate_devices__null(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_bool32 cbResult = MA_TRUE; + ma_context_state_null* pContextStateNull = ma_context_get_backend_state__null(pContext); + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; - MA_ASSERT(pContext != NULL); + MA_ASSERT(pContextStateNull != NULL); MA_ASSERT(callback != NULL); + (void)pContextStateNull; + /* Playback. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Playback Device", (size_t)-1); - deviceInfo.isDefault = MA_TRUE; /* Only one playback and capture device for the null backend, so might as well mark as default. */ - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type__null(pContext, ma_device_type_playback, callback, pUserData); } /* Capture. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "NULL Capture Device", (size_t)-1); - deviceInfo.isDefault = MA_TRUE; /* Only one playback and capture device for the null backend, so might as well mark as default. */ - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type__null(pContext, ma_device_type_capture, callback, pUserData); } (void)cbResult; /* Silence a static analysis warning. */ @@ -21078,327 +21869,229 @@ static ma_result ma_context_enumerate_devices__null(ma_context* pContext, ma_enu return MA_SUCCESS; } -static ma_result ma_context_get_device_info__null(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +static void ma_normalize_descriptor__null(ma_device_descriptor* pDescriptor, ma_uint32 periodSizeInFrames) { - MA_ASSERT(pContext != NULL); + /* The null backend supports all formats, channel counts and sample rates. */ + pDescriptor->format = (pDescriptor->format != ma_format_unknown) ? pDescriptor->format : MA_DEFAULT_FORMAT; + pDescriptor->channels = (pDescriptor->channels != 0) ? pDescriptor->channels : MA_DEFAULT_CHANNELS; + pDescriptor->sampleRate = (pDescriptor->sampleRate != 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; - if (pDeviceID != NULL && pDeviceID->nullbackend != 0) { - return MA_NO_DEVICE; /* Don't know the device. */ + if (pDescriptor->channelMap[0] == MA_CHANNEL_NONE) { + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); } - /* Name / Description */ - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "NULL Playback Device", (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), "NULL Capture Device", (size_t)-1); - } - - pDeviceInfo->isDefault = MA_TRUE; /* Only one playback and capture device for the null backend, so might as well mark as default. */ - - /* Support everything on the null backend. */ - pDeviceInfo->nativeDataFormats[0].format = ma_format_unknown; - pDeviceInfo->nativeDataFormats[0].channels = 0; - pDeviceInfo->nativeDataFormats[0].sampleRate = 0; - pDeviceInfo->nativeDataFormats[0].flags = 0; - pDeviceInfo->nativeDataFormatCount = 1; - - (void)pContext; - return MA_SUCCESS; + pDescriptor->periodSizeInFrames = periodSizeInFrames; } - -static ma_result ma_device_uninit__null(ma_device* pDevice) +static ma_result ma_device_init__null(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - MA_ASSERT(pDevice != NULL); - - /* Keep it clean and wait for the device thread to finish before returning. */ - ma_device_do_operation__null(pDevice, MA_DEVICE_OP_KILL__NULL); - - /* Wait for the thread to finish before continuing. */ - ma_thread_wait(&pDevice->null_device.deviceThread); - - /* At this point the loop in the device thread is as good as terminated so we can uninitialize our events. */ - ma_semaphore_uninit(&pDevice->null_device.operationSemaphore); - ma_event_uninit(&pDevice->null_device.operationCompletionEvent); - ma_event_uninit(&pDevice->null_device.operationEvent); - - return MA_SUCCESS; -} + ma_device_state_null* pDeviceStateNull; + const ma_device_config_null* pDeviceConfigNull = (const ma_device_config_null*)pDeviceBackendConfig; + ma_device_config_null defaultConfigNull; + ma_context_state_null* pContextStateNull = ma_context_get_backend_state__null(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint32 periodSizeInFrames; + size_t dataBufferSizePlayback = 0; + size_t dataBufferSizeCapture = 0; + size_t deviceStateSize; -static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ - ma_result result; + (void)pContextStateNull; - MA_ASSERT(pDevice != NULL); + if (pDeviceConfigNull == NULL) { + defaultConfigNull = ma_device_config_null_init(); + pDeviceConfigNull = &defaultConfigNull; + } - MA_ZERO_OBJECT(&pDevice->null_device); + (void)pDeviceConfigNull; - if (pConfig->deviceType == ma_device_type_loopback) { + if (deviceType == ma_device_type_loopback) { return MA_DEVICE_TYPE_NOT_SUPPORTED; } - /* The null backend supports everything exactly as we specify it. */ - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - pDescriptorCapture->format = (pDescriptorCapture->format != ma_format_unknown) ? pDescriptorCapture->format : MA_DEFAULT_FORMAT; - pDescriptorCapture->channels = (pDescriptorCapture->channels != 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; - pDescriptorCapture->sampleRate = (pDescriptorCapture->sampleRate != 0) ? pDescriptorCapture->sampleRate : MA_DEFAULT_SAMPLE_RATE; - - if (pDescriptorCapture->channelMap[0] == MA_CHANNEL_NONE) { - ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); - } + /* The period size is the same between playback and capture if we're running a duplex device. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate); + } else { + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate); + } - pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); + /* This updates the descriptors with actual values. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_normalize_descriptor__null(pDescriptorCapture, periodSizeInFrames); + dataBufferSizeCapture = periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_normalize_descriptor__null(pDescriptorPlayback, periodSizeInFrames); + dataBufferSizePlayback = periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels); } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - pDescriptorPlayback->format = (pDescriptorPlayback->format != ma_format_unknown) ? pDescriptorPlayback->format : MA_DEFAULT_FORMAT; - pDescriptorPlayback->channels = (pDescriptorPlayback->channels != 0) ? pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS; - pDescriptorPlayback->sampleRate = (pDescriptorPlayback->sampleRate != 0) ? pDescriptorPlayback->sampleRate : MA_DEFAULT_SAMPLE_RATE; - if (pDescriptorPlayback->channelMap[0] == MA_CHANNEL_NONE) { - ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorPlayback->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorPlayback->channels); - } + deviceStateSize = sizeof(*pDeviceStateNull); + deviceStateSize += ma_align_64(dataBufferSizePlayback); + deviceStateSize += ma_align_64(dataBufferSizeCapture); - pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); + pDeviceStateNull = (ma_device_state_null*)ma_calloc(deviceStateSize, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateNull == NULL) { + return MA_OUT_OF_MEMORY; } - /* - In order to get timing right, we need to create a thread that does nothing but keeps track of the timer. This timer is started when the - first period is "written" to it, and then stopped in ma_device_stop__null(). - */ - result = ma_event_init(&pDevice->null_device.operationEvent); - if (result != MA_SUCCESS) { - return result; - } + pDeviceStateNull->pDataPlayback = ma_offset_ptr(pDeviceStateNull, sizeof(*pDeviceStateNull)); + pDeviceStateNull->pDataCapture = ma_offset_ptr(pDeviceStateNull->pDataPlayback, ma_align_64(dataBufferSizePlayback)); - result = ma_event_init(&pDevice->null_device.operationCompletionEvent); - if (result != MA_SUCCESS) { - return result; - } + ma_timer_init(&pDeviceStateNull->timer); - result = ma_semaphore_init(1, &pDevice->null_device.operationSemaphore); /* <-- It's important that the initial value is set to 1. */ - if (result != MA_SUCCESS) { - return result; - } - - result = ma_thread_create(&pDevice->null_device.deviceThread, pDevice->pContext->threadPriority, 0, ma_device_thread__null, pDevice, &pDevice->pContext->allocationCallbacks); - if (result != MA_SUCCESS) { - return result; - } + *ppDeviceState = pDeviceStateNull; return MA_SUCCESS; } -static ma_result ma_device_start__null(ma_device* pDevice) +static void ma_device_uninit__null(ma_device* pDevice) { - MA_ASSERT(pDevice != NULL); + ma_device_state_null* pDeviceStateNull = ma_device_get_backend_state__null(pDevice); - ma_device_do_operation__null(pDevice, MA_DEVICE_OP_START__NULL); + MA_ASSERT(pDeviceStateNull != NULL); - ma_atomic_bool32_set(&pDevice->null_device.isStarted, MA_TRUE); - return MA_SUCCESS; + ma_free(pDeviceStateNull, ma_device_get_allocation_callbacks(pDevice)); } -static ma_result ma_device_stop__null(ma_device* pDevice) +static ma_result ma_device_start__null(ma_device* pDevice) { - MA_ASSERT(pDevice != NULL); + ma_device_state_null* pDeviceStateNull = ma_device_get_backend_state__null(pDevice); - ma_device_do_operation__null(pDevice, MA_DEVICE_OP_SUSPEND__NULL); + MA_ASSERT(pDeviceStateNull != NULL); + (void)pDeviceStateNull; - ma_atomic_bool32_set(&pDevice->null_device.isStarted, MA_FALSE); return MA_SUCCESS; } -static ma_bool32 ma_device_is_started__null(ma_device* pDevice) +static ma_result ma_device_stop__null(ma_device* pDevice) { - MA_ASSERT(pDevice != NULL); + ma_device_state_null* pDeviceStateNull = ma_device_get_backend_state__null(pDevice); - return ma_atomic_bool32_get(&pDevice->null_device.isStarted); + MA_ASSERT(pDeviceStateNull != NULL); + (void)pDeviceStateNull; + + return MA_SUCCESS; } -static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +static ma_uint32 ma_device_get_period_size_in_frames__null(ma_device* pDevice) { - ma_result result = MA_SUCCESS; - ma_uint32 totalPCMFramesProcessed; - ma_bool32 wasStartedOnEntry; + ma_device_type deviceType = ma_device_get_type(pDevice); - if (pFramesWritten != NULL) { - *pFramesWritten = 0; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + return pDevice->capture.internalPeriodSizeInFrames; + } else { + return pDevice->playback.internalPeriodSizeInFrames; } +} - wasStartedOnEntry = ma_device_is_started__null(pDevice); - - /* Keep going until everything has been read. */ - totalPCMFramesProcessed = 0; - while (totalPCMFramesProcessed < frameCount) { - ma_uint64 targetFrame; - - /* If there are any frames remaining in the current period, consume those first. */ - if (pDevice->null_device.currentPeriodFramesRemainingPlayback > 0) { - ma_uint32 framesRemaining = (frameCount - totalPCMFramesProcessed); - ma_uint32 framesToProcess = pDevice->null_device.currentPeriodFramesRemainingPlayback; - if (framesToProcess > framesRemaining) { - framesToProcess = framesRemaining; - } - - /* We don't actually do anything with pPCMFrames, so just mark it as unused to prevent a warning. */ - (void)pPCMFrames; - - pDevice->null_device.currentPeriodFramesRemainingPlayback -= framesToProcess; - totalPCMFramesProcessed += framesToProcess; - } - - /* If we've consumed the current period we'll need to mark it as such an ensure the device is started if it's not already. */ - if (pDevice->null_device.currentPeriodFramesRemainingPlayback == 0) { - pDevice->null_device.currentPeriodFramesRemainingPlayback = 0; - - if (!ma_device_is_started__null(pDevice) && !wasStartedOnEntry) { - result = ma_device_start__null(pDevice); - if (result != MA_SUCCESS) { - break; - } - } - } - - /* If we've consumed the whole buffer we can return now. */ - MA_ASSERT(totalPCMFramesProcessed <= frameCount); - if (totalPCMFramesProcessed == frameCount) { - break; - } - - /* Getting here means we've still got more frames to consume, we but need to wait for it to become available. */ - targetFrame = pDevice->null_device.lastProcessedFramePlayback; - for (;;) { - ma_uint64 currentFrame; +static ma_uint32 ma_device_get_available_frames__null(ma_device* pDevice) +{ + ma_device_state_null* pDeviceStateNull = ma_device_get_backend_state__null(pDevice); + ma_uint64 nowInFrames; + ma_uint32 periodSizeInFrames; + double nowInSeconds; - /* Stop waiting if the device has been stopped. */ - if (!ma_device_is_started__null(pDevice)) { - break; - } + periodSizeInFrames = ma_device_get_period_size_in_frames__null(pDevice); - currentFrame = ma_device_get_total_run_time_in_frames__null(pDevice); - if (currentFrame >= targetFrame) { - break; - } - - /* Getting here means we haven't yet reached the target sample, so continue waiting. */ - ma_sleep(10); + nowInSeconds = ma_timer_get_time_in_seconds(&pDeviceStateNull->timer); + nowInFrames = (ma_uint64)(nowInSeconds * pDevice->sampleRate); + if (nowInFrames < pDeviceStateNull->cursor) { + /* The time has not caught up to the cursor. Nothing can be processed. */ + return 0; + } else { + /* + The time has caught up to the cursor. One way or another, the available frames is equal to + the period size. We need to make sure we check for an xrun, in which case the cursor needs + to be calibrated with the actual time. + */ + if (nowInFrames > pDeviceStateNull->cursor + periodSizeInFrames) { + /* xrun. Normalize the cursor. */ + pDeviceStateNull->cursor = nowInFrames; } - pDevice->null_device.lastProcessedFramePlayback += pDevice->playback.internalPeriodSizeInFrames; - pDevice->null_device.currentPeriodFramesRemainingPlayback = pDevice->playback.internalPeriodSizeInFrames; - } - - if (pFramesWritten != NULL) { - *pFramesWritten = totalPCMFramesProcessed; + return periodSizeInFrames; } - - return result; } -static ma_result ma_device_read__null(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) -{ - ma_result result = MA_SUCCESS; - ma_uint32 totalPCMFramesProcessed; - if (pFramesRead != NULL) { - *pFramesRead = 0; - } +static ma_result ma_device_step__null(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_null* pDeviceStateNull = ma_device_get_backend_state__null(pDevice); + ma_uint32 framesAvailable; + ma_device_type deviceType = ma_device_get_type(pDevice); - /* Keep going until everything has been read. */ - totalPCMFramesProcessed = 0; - while (totalPCMFramesProcessed < frameCount) { - ma_uint64 targetFrame; + for (;;) { + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } - /* If there are any frames remaining in the current period, consume those first. */ - if (pDevice->null_device.currentPeriodFramesRemainingCapture > 0) { - ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 framesRemaining = (frameCount - totalPCMFramesProcessed); - ma_uint32 framesToProcess = pDevice->null_device.currentPeriodFramesRemainingCapture; - if (framesToProcess > framesRemaining) { - framesToProcess = framesRemaining; + framesAvailable = ma_device_get_available_frames__null(pDevice); + if (framesAvailable > 0) { + /* For capture we need to submit silence. For playback we just read and discard. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateNull->pDataCapture, framesAvailable); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_device_handle_backend_data_callback(pDevice, pDeviceStateNull->pDataPlayback, NULL, framesAvailable); } - /* We need to ensure the output buffer is zeroed. */ - MA_ZERO_MEMORY(ma_offset_ptr(pPCMFrames, totalPCMFramesProcessed*bpf), framesToProcess*bpf); - - pDevice->null_device.currentPeriodFramesRemainingCapture -= framesToProcess; - totalPCMFramesProcessed += framesToProcess; - } - - /* If we've consumed the current period we'll need to mark it as such an ensure the device is started if it's not already. */ - if (pDevice->null_device.currentPeriodFramesRemainingCapture == 0) { - pDevice->null_device.currentPeriodFramesRemainingCapture = 0; + /* The cursor needs to be advanced by the number of frames we just processed. */ + pDeviceStateNull->cursor += framesAvailable; } - /* If we've consumed the whole buffer we can return now. */ - MA_ASSERT(totalPCMFramesProcessed <= frameCount); - if (totalPCMFramesProcessed == frameCount) { + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { break; } - /* Getting here means we've still got more frames to consume, we but need to wait for it to become available. */ - targetFrame = pDevice->null_device.lastProcessedFrameCapture + pDevice->capture.internalPeriodSizeInFrames; - for (;;) { - ma_uint64 currentFrame; - - /* Stop waiting if the device has been stopped. */ - if (!ma_device_is_started__null(pDevice)) { - break; - } - - currentFrame = ma_device_get_total_run_time_in_frames__null(pDevice); - if (currentFrame >= targetFrame) { - break; - } + /* Getting here means we're in blocking mode. Relax the CPU a bit and keep waiting for data. */ + ma_sleep(1); + } - /* Getting here means we haven't yet reached the target sample, so continue waiting. */ - ma_sleep(10); - } + return MA_SUCCESS; +} - pDevice->null_device.lastProcessedFrameCapture += pDevice->capture.internalPeriodSizeInFrames; - pDevice->null_device.currentPeriodFramesRemainingCapture = pDevice->capture.internalPeriodSizeInFrames; - } +static ma_device_backend_vtable ma_gDeviceBackendVTable_Null = +{ + ma_backend_info__null, + ma_context_init__null, + ma_context_uninit__null, + ma_context_enumerate_devices__null, + ma_device_init__null, + ma_device_uninit__null, + ma_device_start__null, + ma_device_stop__null, + ma_device_step__null, + NULL /* onDeviceWakeup */ +}; - if (pFramesRead != NULL) { - *pFramesRead = totalPCMFramesProcessed; - } +ma_device_backend_vtable* ma_device_backend_null = &ma_gDeviceBackendVTable_Null; +#else +ma_device_backend_vtable* ma_device_backend_null = NULL; +#endif - return result; +MA_API ma_device_backend_vtable* ma_null_get_vtable(void) +{ + return ma_device_backend_null; } -static ma_result ma_context_uninit__null(ma_context* pContext) +MA_API ma_context_config_null ma_context_config_null_init(void) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_null); + ma_context_config_null config; - (void)pContext; - return MA_SUCCESS; + MA_ZERO_OBJECT(&config); + + return config; } -static ma_result ma_context_init__null(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +MA_API ma_device_config_null ma_device_config_null_init(void) { - MA_ASSERT(pContext != NULL); - - (void)pConfig; - (void)pContext; + ma_device_config_null config; - pCallbacks->onContextInit = ma_context_init__null; - pCallbacks->onContextUninit = ma_context_uninit__null; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__null; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__null; - pCallbacks->onDeviceInit = ma_device_init__null; - pCallbacks->onDeviceUninit = ma_device_uninit__null; - pCallbacks->onDeviceStart = ma_device_start__null; - pCallbacks->onDeviceStop = ma_device_stop__null; - pCallbacks->onDeviceRead = ma_device_read__null; - pCallbacks->onDeviceWrite = ma_device_write__null; - pCallbacks->onDeviceDataLoop = NULL; /* Our backend is asynchronous with a blocking read-write API which means we can get miniaudio to deal with the audio thread. */ + MA_ZERO_OBJECT(&config); - /* The null backend always works. */ - return MA_SUCCESS; + return config; } -#endif @@ -21866,6 +22559,13 @@ typedef struct MA_AUDCLNT_STREAMOPTIONS Options; } ma_AudioClientProperties; +typedef struct +{ + void* lpVtbl; + ma_uint32 counter; + ma_device* pDevice; +} ma_IMMNotificationClient; + /* IUnknown */ typedef struct { @@ -22235,6 +22935,145 @@ typedef HRESULT (WINAPI * MA_PFN_ActivateAudioInterfaceAsync)(const wchar_t* dev typedef HANDLE (WINAPI * MA_PFN_AvSetMmThreadCharacteristicsA)(const char* TaskName, DWORD* TaskIndex); typedef BOOL (WINAPI * MA_PFN_AvRevertMmThreadCharacteristics)(HANDLE AvrtHandle); + +typedef enum +{ + ma_client_notification_type_wasapi_deactivate, + ma_client_notification_type_wasapi_activate, + ma_client_notification_type_wasapi_default_changed, + ma_client_notification_type_wasapi_stop +} ma_client_notification_type_wasapi; + +typedef struct +{ + ma_client_notification_type_wasapi type; + ma_device_type deviceType; /* Either playback, capture or loopback. Should never be duplex. */ +} ma_client_notification_wasapi; + +static MA_INLINE ma_client_notification_wasapi ma_client_notification_wasapi_init(ma_client_notification_type_wasapi notificationType, ma_device_type deviceType) +{ + ma_client_notification_wasapi notification; + + MA_ASSERT(deviceType != ma_device_type_duplex); + + MA_ZERO_OBJECT(¬ification); + notification.type = notificationType; + notification.deviceType = deviceType; + + return notification; +} + + +typedef struct ma_context_state_wasapi +{ + ma_handle hAvrt; + MA_PFN_AvSetMmThreadCharacteristicsA AvSetMmThreadCharacteristicsA; + MA_PFN_AvRevertMmThreadCharacteristics AvRevertMmThreadCharacteristics; + #if defined(MA_WIN32_UWP) + ma_handle hMMDevapi; + MA_PFN_ActivateAudioInterfaceAsync ActivateAudioInterfaceAsync; + #endif +} ma_context_state_wasapi; + +typedef struct ma_device_state_wasapi +{ + ma_IAudioClient* pAudioClientPlayback; + ma_IAudioClient* pAudioClientCapture; + ma_IAudioRenderClient* pRenderClient; + ma_IAudioCaptureClient* pCaptureClient; + #if defined(MA_WIN32_DESKTOP) + ma_IMMDeviceEnumerator* pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. Not used with the UWP build. */ + #endif + ma_IMMNotificationClient notificationClient; + HANDLE hEventPlayback; /* Auto reset. Initialized to signaled. */ + HANDLE hEventCapture; /* Auto reset. Initialized to unsignaled. */ + ma_uint32 actualBufferSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ + ma_uint32 actualBufferSizeInFramesCapture; + ma_uint32 originalPeriodSizeInFrames; + ma_uint32 originalPeriodSizeInMilliseconds; + ma_uint32 originalPeriods; + ma_uint32 periodSizeInFramesPlayback; + ma_uint32 periodSizeInFramesCapture; + ma_uint32 loopbackProcessID; + ma_bool8 loopbackProcessExclude; + ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ + ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ + ma_bool8 noHardwareOffloading; + ma_bool8 allowCaptureAutoStreamRouting; + ma_bool8 allowPlaybackAutoStreamRouting; + ma_bool8 isDetachedPlayback; + ma_bool8 isDetachedCapture; + ma_wasapi_usage usage; + void* hAvrtHandle; + ma_client_notification_wasapi pNotifications[16]; + ma_uint32 notificationIndex; + ma_uint32 notificationCount; + ma_spinlock notificationLock; +} ma_device_state_wasapi; + + +static ma_context_state_wasapi* ma_context_get_backend_state__wasapi(ma_context* pContext) +{ + return (ma_context_state_wasapi*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_wasapi* ma_device_get_backend_state__wasapi(ma_device* pDevice) +{ + return (ma_device_state_wasapi*)ma_device_get_backend_state(pDevice); +} + + +static ma_result ma_device_push_client_notification__wasapi(ma_device* pDevice, ma_client_notification_wasapi notification) +{ + ma_device_state_wasapi* pDeviceStateWASAPI = (ma_device_state_wasapi*)ma_device_get_backend_state(pDevice); + + ma_spinlock_lock(&pDeviceStateWASAPI->notificationLock); + { + if (pDeviceStateWASAPI->notificationCount >= ma_countof(pDeviceStateWASAPI->pNotifications)) { + ma_spinlock_unlock(&pDeviceStateWASAPI->notificationLock); + return MA_NO_SPACE; + } + + pDeviceStateWASAPI->pNotifications[pDeviceStateWASAPI->notificationCount] = notification; + pDeviceStateWASAPI->notificationCount += 1; + } + ma_spinlock_unlock(&pDeviceStateWASAPI->notificationLock); + + /* We need to wakeup so we can process events in the step function. */ + if (notification.deviceType == ma_device_type_playback) { + SetEvent(pDeviceStateWASAPI->hEventPlayback); + } else { + SetEvent(pDeviceStateWASAPI->hEventCapture); + } + + return MA_SUCCESS; +} + +static ma_result ma_device_next_client_notification__wasapi(ma_device* pDevice, ma_client_notification_wasapi* pNotification) +{ + ma_device_state_wasapi* pDeviceStateWASAPI = (ma_device_state_wasapi*)ma_device_get_backend_state(pDevice); + + MA_ASSERT(pNotification != NULL); + + ma_spinlock_lock(&pDeviceStateWASAPI->notificationLock); + { + if (pDeviceStateWASAPI->notificationCount == 0) { + ma_spinlock_unlock(&pDeviceStateWASAPI->notificationLock); + return MA_AT_END; + } + + *pNotification = pDeviceStateWASAPI->pNotifications[0]; + pDeviceStateWASAPI->notificationCount -= 1; + + /* Move everything down. */ + MA_MOVE_MEMORY(pDeviceStateWASAPI->pNotifications, pDeviceStateWASAPI->pNotifications + 1, sizeof(*pDeviceStateWASAPI->pNotifications) * pDeviceStateWASAPI->notificationCount); + } + ma_spinlock_unlock(&pDeviceStateWASAPI->notificationLock); + + return MA_SUCCESS; +} + + #if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) typedef struct ma_completion_handler_uwp ma_completion_handler_uwp; @@ -22369,23 +23208,25 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(m ma_bool32 isThisDevice = MA_FALSE; ma_bool32 isCapture = MA_FALSE; ma_bool32 isPlayback = MA_FALSE; + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pThis->pDevice); + ma_device_type deviceType = ma_device_get_type(pThis->pDevice); #ifdef MA_DEBUG_OUTPUT - /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDeviceStateChanged(pDeviceID=%S, dwNewState=%u)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)", (unsigned int)dwNewState);*/ + /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDeviceStateChanged(pDeviceID=%S, dwNewState=%u)", (pDeviceID != NULL) ? pDeviceID : L"(NULL)", (unsigned int)dwNewState);*/ #endif /* There have been reports of a hang when a playback device is disconnected. The idea with this code is to explicitly stop the device if we detect that the device is disabled or has been unplugged. */ - if (pThis->pDevice->wasapi.allowCaptureAutoStreamRouting && (pThis->pDevice->type == ma_device_type_capture || pThis->pDevice->type == ma_device_type_duplex || pThis->pDevice->type == ma_device_type_loopback)) { + if (pDeviceStateWASAPI->allowCaptureAutoStreamRouting && (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback)) { isCapture = MA_TRUE; if (ma_strcmp_WCHAR(pThis->pDevice->capture.id.wasapi, pDeviceID) == 0) { isThisDevice = MA_TRUE; } } - if (pThis->pDevice->wasapi.allowPlaybackAutoStreamRouting && (pThis->pDevice->type == ma_device_type_playback || pThis->pDevice->type == ma_device_type_duplex)) { + if (pDeviceStateWASAPI->allowPlaybackAutoStreamRouting && (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex)) { isPlayback = MA_TRUE; if (ma_strcmp_WCHAR(pThis->pDevice->playback.id.wasapi, pDeviceID) == 0) { isThisDevice = MA_TRUE; @@ -22405,42 +23246,21 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(m use this to determine whether or not we need to automatically start the device when it's plugged back in again. */ - if (ma_device_get_state(pThis->pDevice) == ma_device_state_started) { - if (isPlayback) { - pThis->pDevice->wasapi.isDetachedPlayback = MA_TRUE; - } - if (isCapture) { - pThis->pDevice->wasapi.isDetachedCapture = MA_TRUE; - } - - ma_device_stop(pThis->pDevice); + if (isPlayback) { + ma_device_push_client_notification__wasapi(pThis->pDevice, ma_client_notification_wasapi_init(ma_client_notification_type_wasapi_deactivate, ma_device_type_playback)); + } + if (isCapture) { + ma_device_push_client_notification__wasapi(pThis->pDevice, ma_client_notification_wasapi_init(ma_client_notification_type_wasapi_deactivate, ma_device_type_capture)); } } if ((dwNewState & MA_MM_DEVICE_STATE_ACTIVE) != 0) { /* The device was activated. If we were detached, we need to start it again. */ - ma_bool8 tryRestartingDevice = MA_FALSE; - if (isPlayback) { - if (pThis->pDevice->wasapi.isDetachedPlayback) { - pThis->pDevice->wasapi.isDetachedPlayback = MA_FALSE; - ma_device_reroute__wasapi(pThis->pDevice, ma_device_type_playback); - tryRestartingDevice = MA_TRUE; - } + ma_device_push_client_notification__wasapi(pThis->pDevice, ma_client_notification_wasapi_init(ma_client_notification_type_wasapi_activate, ma_device_type_playback)); } - if (isCapture) { - if (pThis->pDevice->wasapi.isDetachedCapture) { - pThis->pDevice->wasapi.isDetachedCapture = MA_FALSE; - ma_device_reroute__wasapi(pThis->pDevice, (pThis->pDevice->type == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture); - tryRestartingDevice = MA_TRUE; - } - } - - if (tryRestartingDevice) { - if (pThis->pDevice->wasapi.isDetachedPlayback == MA_FALSE && pThis->pDevice->wasapi.isDetachedCapture == MA_FALSE) { - ma_device_start(pThis->pDevice); - } + ma_device_push_client_notification__wasapi(pThis->pDevice, ma_client_notification_wasapi_init(ma_client_notification_type_wasapi_activate, (deviceType == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture)); } } } @@ -22451,7 +23271,7 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(m static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceAdded(ma_IMMNotificationClient* pThis, const WCHAR* pDeviceID) { #ifdef MA_DEBUG_OUTPUT - /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDeviceAdded(pDeviceID=%S)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)");*/ + /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDeviceAdded(pDeviceID=%S)", (pDeviceID != NULL) ? pDeviceID : L"(NULL)");*/ #endif /* We don't need to worry about this event for our purposes. */ @@ -22463,7 +23283,7 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceAdded(ma_IMMNo static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceRemoved(ma_IMMNotificationClient* pThis, const WCHAR* pDeviceID) { #ifdef MA_DEBUG_OUTPUT - /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDeviceRemoved(pDeviceID=%S)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)");*/ + /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDeviceRemoved(pDeviceID=%S)", (pDeviceID != NULL) ? pDeviceID : L"(NULL)");*/ #endif /* We don't need to worry about this event for our purposes. */ @@ -22474,29 +23294,32 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceRemoved(ma_IMM static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged(ma_IMMNotificationClient* pThis, ma_EDataFlow dataFlow, ma_ERole role, const WCHAR* pDefaultDeviceID) { + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pThis->pDevice); + ma_device_type deviceType = ma_device_get_type(pThis->pDevice); + #ifdef MA_DEBUG_OUTPUT - /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDefaultDeviceChanged(dataFlow=%d, role=%d, pDefaultDeviceID=%S)\n", dataFlow, role, (pDefaultDeviceID != NULL) ? pDefaultDeviceID : L"(NULL)");*/ + /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnDefaultDeviceChanged(dataFlow=%d, role=%d, pDefaultDeviceID=%S)", dataFlow, role, (pDefaultDeviceID != NULL) ? pDefaultDeviceID : L"(NULL)");*/ #endif (void)role; /* We only care about devices with the same data flow as the current device. */ - if ((pThis->pDevice->type == ma_device_type_playback && dataFlow != ma_eRender) || - (pThis->pDevice->type == ma_device_type_capture && dataFlow != ma_eCapture) || - (pThis->pDevice->type == ma_device_type_loopback && dataFlow != ma_eRender)) { - ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because dataFlow does match device type.\n"); + if ((deviceType == ma_device_type_playback && dataFlow != ma_eRender) || + (deviceType == ma_device_type_capture && dataFlow != ma_eCapture) || + (deviceType == ma_device_type_loopback && dataFlow != ma_eRender)) { + ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because dataFlow does match device type."); return S_OK; } /* We need to consider dataFlow as ma_eCapture if device is ma_device_type_loopback */ - if (pThis->pDevice->type == ma_device_type_loopback) { + if (deviceType == ma_device_type_loopback) { dataFlow = ma_eCapture; } /* Don't do automatic stream routing if we're not allowed. */ - if ((dataFlow == ma_eRender && pThis->pDevice->wasapi.allowPlaybackAutoStreamRouting == MA_FALSE) || - (dataFlow == ma_eCapture && pThis->pDevice->wasapi.allowCaptureAutoStreamRouting == MA_FALSE)) { - ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.\n"); + if ((dataFlow == ma_eRender && pDeviceStateWASAPI->allowPlaybackAutoStreamRouting == MA_FALSE) || + (dataFlow == ma_eCapture && pDeviceStateWASAPI->allowCaptureAutoStreamRouting == MA_FALSE)) { + ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config."); return S_OK; } @@ -22507,68 +23330,42 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged */ if ((dataFlow == ma_eRender && pThis->pDevice->playback.shareMode == ma_share_mode_exclusive) || (dataFlow == ma_eCapture && pThis->pDevice->capture.shareMode == ma_share_mode_exclusive)) { - ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because the device shared mode is exclusive.\n"); + ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because the device shared mode is exclusive."); return S_OK; } + /* Don't do anything if we're in the process of starting the device. */ + if (ma_device_get_status(pThis->pDevice) == ma_device_status_uninitialized || ma_device_get_status(pThis->pDevice) == ma_device_status_starting) { + ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because the device is in the process of starting."); + return S_OK; + } /* - Second attempt at device rerouting. We're going to retrieve the device's state at the time of - the route change. We're then going to stop the device, reinitialize the device, and then start - it again if the state before stopping was ma_device_state_started. - */ - { - ma_uint32 previousState = ma_device_get_state(pThis->pDevice); - ma_bool8 restartDevice = MA_FALSE; - - if (previousState == ma_device_state_uninitialized || previousState == ma_device_state_starting) { - ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Stream rerouting abandoned because the device is in the process of starting.\n"); - return S_OK; - } - - if (previousState == ma_device_state_started) { - ma_device_stop(pThis->pDevice); - restartDevice = MA_TRUE; - } - - if (pDefaultDeviceID != NULL) { /* <-- The input device ID will be null if there's no other device available. */ - ma_mutex_lock(&pThis->pDevice->wasapi.rerouteLock); - { - if (dataFlow == ma_eRender) { - ma_device_reroute__wasapi(pThis->pDevice, ma_device_type_playback); + Third attempt at rerouting (we'll get this eventually!). Doing the rerouting straight from this + callback has caused issues: - if (pThis->pDevice->wasapi.isDetachedPlayback) { - pThis->pDevice->wasapi.isDetachedPlayback = MA_FALSE; - - if (pThis->pDevice->type == ma_device_type_duplex && pThis->pDevice->wasapi.isDetachedCapture) { - restartDevice = MA_FALSE; /* It's a duplex device and the capture side is detached. We cannot be restarting the device just yet. */ - } - else { - restartDevice = MA_TRUE; /* It's not a duplex device, or the capture side is also attached so we can go ahead and restart the device. */ - } - } - } - else { - ma_device_reroute__wasapi(pThis->pDevice, (pThis->pDevice->type == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture); + 1) WASAPI requires that you first initialize COM, which needs to be done on a per-thread basis + which means it would need to be initialized here. This is messy. - if (pThis->pDevice->wasapi.isDetachedCapture) { - pThis->pDevice->wasapi.isDetachedCapture = MA_FALSE; + 2) I've had some lingering reports of crashing and suspected race conditions when rerouting. - if (pThis->pDevice->type == ma_device_type_duplex && pThis->pDevice->wasapi.isDetachedPlayback) { - restartDevice = MA_FALSE; /* It's a duplex device and the playback side is detached. We cannot be restarting the device just yet. */ - } - else { - restartDevice = MA_TRUE; /* It's not a duplex device, or the playback side is also attached so we can go ahead and restart the device. */ - } - } - } - } - ma_mutex_unlock(&pThis->pDevice->wasapi.rerouteLock); + Instead of doing the rerouting right here, I'm going to instead do it from our step function. By + doing it this way we should be able to avoid these issues. + */ + { + ma_device_type specificDeviceType; + if (dataFlow == ma_eRender) { + specificDeviceType = ma_device_type_playback; + } else { + specificDeviceType = (deviceType == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture; + } - if (restartDevice) { - ma_device_start(pThis->pDevice); - } + /* If pDefaultDeviceID is NULL, it means there is no other device available. In this case we just want to stop the device. */ + if (pDefaultDeviceID != NULL) { + ma_device_push_client_notification__wasapi(pThis->pDevice, ma_client_notification_wasapi_init(ma_client_notification_type_wasapi_default_changed, specificDeviceType)); + } else { + ma_device_push_client_notification__wasapi(pThis->pDevice, ma_client_notification_wasapi_init(ma_client_notification_type_wasapi_stop, specificDeviceType)); } } @@ -22578,7 +23375,7 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnPropertyValueChanged(ma_IMMNotificationClient* pThis, const WCHAR* pDeviceID, const PROPERTYKEY key) { #ifdef MA_DEBUG_OUTPUT - /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnPropertyValueChanged(pDeviceID=%S)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)");*/ + /*ma_log_postf(ma_device_get_log(pThis->pDevice), MA_LOG_LEVEL_DEBUG, "IMMNotificationClient_OnPropertyValueChanged(pDeviceID=%S)", (pDeviceID != NULL) ? pDeviceID : L"(NULL)");*/ #endif (void)pThis; @@ -22619,207 +23416,41 @@ typedef ma_IUnknown ma_WASAPIDeviceInterface; #endif -#define MA_CONTEXT_COMMAND_QUIT__WASAPI 1 -#define MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI 2 -#define MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI 3 - -static ma_context_command__wasapi ma_context_init_command__wasapi(int code) -{ - ma_context_command__wasapi cmd; - - MA_ZERO_OBJECT(&cmd); - cmd.code = code; - - return cmd; -} - -static ma_result ma_context_post_command__wasapi(ma_context* pContext, const ma_context_command__wasapi* pCmd) -{ - /* For now we are doing everything synchronously, but I might relax this later if the need arises. */ - ma_result result; - ma_bool32 isUsingLocalEvent = MA_FALSE; - ma_event localEvent; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(pCmd != NULL); - - if (pCmd->pEvent == NULL) { - isUsingLocalEvent = MA_TRUE; - - result = ma_event_init(&localEvent); - if (result != MA_SUCCESS) { - return result; /* Failed to create the event for this command. */ - } - } - - /* Here is where we add the command to the list. If there's not enough room we'll spin until there is. */ - ma_mutex_lock(&pContext->wasapi.commandLock); - { - ma_uint32 index; - - /* Spin until we've got some space available. */ - while (pContext->wasapi.commandCount == ma_countof(pContext->wasapi.commands)) { - ma_yield(); - } - - /* Space is now available. Can safely add to the list. */ - index = (pContext->wasapi.commandIndex + pContext->wasapi.commandCount) % ma_countof(pContext->wasapi.commands); - pContext->wasapi.commands[index] = *pCmd; - pContext->wasapi.commands[index].pEvent = &localEvent; - pContext->wasapi.commandCount += 1; - - /* Now that the command has been added, release the semaphore so ma_context_next_command__wasapi() can return. */ - ma_semaphore_release(&pContext->wasapi.commandSem); - } - ma_mutex_unlock(&pContext->wasapi.commandLock); - - if (isUsingLocalEvent) { - ma_event_wait(&localEvent); - ma_event_uninit(&localEvent); - } - - return MA_SUCCESS; -} - -static ma_result ma_context_next_command__wasapi(ma_context* pContext, ma_context_command__wasapi* pCmd) -{ - ma_result result = MA_SUCCESS; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(pCmd != NULL); - - result = ma_semaphore_wait(&pContext->wasapi.commandSem); - if (result == MA_SUCCESS) { - ma_mutex_lock(&pContext->wasapi.commandLock); - { - *pCmd = pContext->wasapi.commands[pContext->wasapi.commandIndex]; - pContext->wasapi.commandIndex = (pContext->wasapi.commandIndex + 1) % ma_countof(pContext->wasapi.commands); - pContext->wasapi.commandCount -= 1; - } - ma_mutex_unlock(&pContext->wasapi.commandLock); - } - - return result; -} - -static ma_thread_result MA_THREADCALL ma_context_command_thread__wasapi(void* pUserData) -{ - ma_result result; - ma_context* pContext = (ma_context*)pUserData; - MA_ASSERT(pContext != NULL); - - for (;;) { - ma_context_command__wasapi cmd; - result = ma_context_next_command__wasapi(pContext, &cmd); - if (result != MA_SUCCESS) { - break; - } - - switch (cmd.code) - { - case MA_CONTEXT_COMMAND_QUIT__WASAPI: - { - /* Do nothing. Handled after the switch. */ - } break; - - case MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI: - { - if (cmd.data.createAudioClient.deviceType == ma_device_type_playback) { - *cmd.data.createAudioClient.pResult = ma_result_from_HRESULT(ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioRenderClient, cmd.data.createAudioClient.ppAudioClientService)); - } else { - *cmd.data.createAudioClient.pResult = ma_result_from_HRESULT(ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioCaptureClient, cmd.data.createAudioClient.ppAudioClientService)); - } - } break; - - case MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI: - { - if (cmd.data.releaseAudioClient.deviceType == ma_device_type_playback) { - if (cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback != NULL) { - ma_IAudioClient_Release((ma_IAudioClient*)cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback); - cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback = NULL; - } - } - - if (cmd.data.releaseAudioClient.deviceType == ma_device_type_capture) { - if (cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture != NULL) { - ma_IAudioClient_Release((ma_IAudioClient*)cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture); - cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture = NULL; - } - } - } break; - - default: - { - /* Unknown command. Ignore it, but trigger an assert in debug mode so we're aware of it. */ - MA_ASSERT(MA_FALSE); - } break; - } - - if (cmd.pEvent != NULL) { - ma_event_signal(cmd.pEvent); - } - - if (cmd.code == MA_CONTEXT_COMMAND_QUIT__WASAPI) { - break; /* Received a quit message. Get out of here. */ - } - } - - return (ma_thread_result)0; -} - static ma_result ma_device_create_IAudioClient_service__wasapi(ma_context* pContext, ma_device_type deviceType, ma_IAudioClient* pAudioClient, void** ppAudioClientService) { ma_result result; - ma_result cmdResult; - ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI); - cmd.data.createAudioClient.deviceType = deviceType; - cmd.data.createAudioClient.pAudioClient = (void*)pAudioClient; - cmd.data.createAudioClient.ppAudioClientService = ppAudioClientService; - cmd.data.createAudioClient.pResult = &cmdResult; /* Declared locally, but won't be dereferenced after this function returns since execution of the command will wait here. */ - - result = ma_context_post_command__wasapi(pContext, &cmd); /* This will not return until the command has actually been run. */ - if (result != MA_SUCCESS) { - return result; - } - - return *cmd.data.createAudioClient.pResult; -} -#if 0 /* Not used at the moment, but leaving here for future use. */ -static ma_result ma_device_release_IAudioClient_service__wasapi(ma_device* pDevice, ma_device_type deviceType) -{ - ma_result result; - ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI); - cmd.data.releaseAudioClient.pDevice = pDevice; - cmd.data.releaseAudioClient.deviceType = deviceType; + (void)pContext; - result = ma_context_post_command__wasapi(pDevice->pContext, &cmd); /* This will not return until the command has actually been run. */ - if (result != MA_SUCCESS) { - return result; + if (deviceType == ma_device_type_playback) { + result = ma_result_from_HRESULT(ma_IAudioClient_GetService(pAudioClient, &MA_IID_IAudioRenderClient, ppAudioClientService)); + } else { + result = ma_result_from_HRESULT(ma_IAudioClient_GetService(pAudioClient, &MA_IID_IAudioCaptureClient, ppAudioClientService)); } - return MA_SUCCESS; + return result; } -#endif static void ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(const MA_WAVEFORMATEX* pWF, ma_share_mode shareMode, ma_device_info* pInfo) { + ma_uint32 flags; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + MA_ASSERT(pWF != NULL); MA_ASSERT(pInfo != NULL); - if (pInfo->nativeDataFormatCount >= ma_countof(pInfo->nativeDataFormats)) { - return; /* Too many data formats. Need to ignore this one. Don't think this should ever happen with WASAPI. */ - } + flags = (shareMode == ma_share_mode_exclusive) ? MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE : 0; + format = ma_format_from_WAVEFORMATEX(pWF); + channels = pWF->nChannels; + sampleRate = pWF->nSamplesPerSec; - pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].format = ma_format_from_WAVEFORMATEX(pWF); - pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].channels = pWF->nChannels; - pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].sampleRate = pWF->nSamplesPerSec; - pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].flags = (shareMode == ma_share_mode_exclusive) ? MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE : 0; - pInfo->nativeDataFormatCount += 1; + ma_device_info_add_native_data_format_ex(pInfo, flags, format, channels, channels, sampleRate, sampleRate); } -static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context* pContext, /*ma_IMMDevice**/void* pMMDevice, ma_IAudioClient* pAudioClient, ma_device_info* pInfo) +static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context* pContext, /*ma_IMMDevice**/ void* pMMDevice, ma_IAudioClient* pAudioClient, ma_device_info* pInfo) { HRESULT hr; MA_WAVEFORMATEX* pWF = NULL; @@ -22828,7 +23459,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context MA_ASSERT(pInfo != NULL); /* Shared Mode. We use GetMixFormat() here. */ - hr = ma_IAudioClient_GetMixFormat((ma_IAudioClient*)pAudioClient, (MA_WAVEFORMATEX**)&pWF); + hr = ma_IAudioClient_GetMixFormat(pAudioClient, (MA_WAVEFORMATEX**)&pWF); if (SUCCEEDED(hr)) { ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(pWF, ma_share_mode_shared, pInfo); } else { @@ -22862,7 +23493,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context In my testing, the format returned by PKEY_AudioEngine_DeviceFormat is suitable for exclusive mode so we check this format first. If this fails, fall back to a search. */ - hr = ma_IAudioClient_IsFormatSupported((ma_IAudioClient*)pAudioClient, MA_AUDCLNT_SHAREMODE_EXCLUSIVE, pWF, NULL); + hr = ma_IAudioClient_IsFormatSupported(pAudioClient, MA_AUDCLNT_SHAREMODE_EXCLUSIVE, pWF, NULL); if (SUCCEEDED(hr)) { /* The format returned by PKEY_AudioEngine_DeviceFormat is supported. */ ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(pWF, ma_share_mode_exclusive, pInfo); @@ -22895,20 +23526,20 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context ma_format format = g_maFormatPriorities[iFormat]; ma_uint32 iSampleRate; - wf.wBitsPerSample = (WORD)(ma_get_bytes_per_sample(format)*8); - wf.nBlockAlign = (WORD)(wf.nChannels * wf.wBitsPerSample / 8); - wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; - wf.Samples.wValidBitsPerSample = /*(format == ma_format_s24_32) ? 24 :*/ wf.wBitsPerSample; + wf.wBitsPerSample = (WORD)(ma_get_bytes_per_sample(format)*8); + wf.nBlockAlign = (WORD)(wf.nChannels * wf.wBitsPerSample / 8); + wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.wBitsPerSample; if (format == ma_format_f32) { wf.SubFormat = MA_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; } else { wf.SubFormat = MA_GUID_KSDATAFORMAT_SUBTYPE_PCM; } - for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); ++iSampleRate) { - wf.nSamplesPerSec = g_maStandardSampleRatePriorities[iSampleRate]; + for (iSampleRate = 0; iSampleRate < ma_countof(ma_standard_sample_rates); ++iSampleRate) { + wf.nSamplesPerSec = ma_standard_sample_rates[iSampleRate]; - hr = ma_IAudioClient_IsFormatSupported((ma_IAudioClient*)pAudioClient, MA_AUDCLNT_SHAREMODE_EXCLUSIVE, (MA_WAVEFORMATEX*)&wf, NULL); + hr = ma_IAudioClient_IsFormatSupported(pAudioClient, MA_AUDCLNT_SHAREMODE_EXCLUSIVE, (MA_WAVEFORMATEX*)&wf, NULL); if (SUCCEEDED(hr)) { ma_add_native_data_format_to_device_info_from_WAVEFORMATEX((MA_WAVEFORMATEX*)&wf, ma_share_mode_exclusive, pInfo); found = MA_TRUE; @@ -22958,6 +23589,7 @@ static ma_EDataFlow ma_device_type_to_EDataFlow(ma_device_type deviceType) } } +#if 0 static ma_result ma_context_create_IMMDeviceEnumerator__wasapi(ma_context* pContext, ma_IMMDeviceEnumerator** ppDeviceEnumerator) { HRESULT hr; @@ -22978,6 +23610,7 @@ static ma_result ma_context_create_IMMDeviceEnumerator__wasapi(ma_context* pCont return MA_SUCCESS; } +#endif static WCHAR* ma_context_get_default_device_id_from_IMMDeviceEnumerator__wasapi(ma_context* pContext, ma_IMMDeviceEnumerator* pDeviceEnumerator, ma_device_type deviceType) { @@ -23015,6 +23648,7 @@ static WCHAR* ma_context_get_default_device_id_from_IMMDeviceEnumerator__wasapi( return pDefaultDeviceID; } +#if 0 static WCHAR* ma_context_get_default_device_id__wasapi(ma_context* pContext, ma_device_type deviceType) /* Free the returned pointer with ma_CoTaskMemFree() */ { ma_result result; @@ -23033,6 +23667,7 @@ static WCHAR* ma_context_get_default_device_id__wasapi(ma_context* pContext, ma_ ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); return pDefaultDeviceID; } +#endif static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_IMMDevice** ppMMDevice) { @@ -23065,7 +23700,7 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device if (CoInitializeResult == S_OK || CoInitializeResult == S_FALSE) { ma_CoUninitialize(pContext); } if (FAILED(hr)) { /* <-- This is checking the call above to ma_CoCreateInstance(). */ - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator."); return ma_result_from_HRESULT(hr); } @@ -23077,7 +23712,7 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); if (FAILED(hr)) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice."); return ma_result_from_HRESULT(hr); } @@ -23111,7 +23746,7 @@ static ma_result ma_context_get_device_id_from_MMDevice__wasapi(ma_context* pCon return MA_ERROR; } -static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pContext, ma_IMMDevice* pMMDevice, WCHAR* pDefaultDeviceID, ma_bool32 onlySimpleInfo, ma_device_info* pInfo) +static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pContext, ma_IMMDevice* pMMDevice, WCHAR* pDefaultDeviceID, ma_device_info* pInfo) { ma_result result; HRESULT hr; @@ -23120,7 +23755,7 @@ static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pC MA_ASSERT(pMMDevice != NULL); MA_ASSERT(pInfo != NULL); - /* ID. */ + /* ID and Default. */ result = ma_context_get_device_id_from_MMDevice__wasapi(pContext, pMMDevice, &pInfo->id); if (result == MA_SUCCESS) { if (pDefaultDeviceID != NULL) { @@ -23149,7 +23784,7 @@ static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pC } /* Format */ - if (!onlySimpleInfo) { + { ma_IAudioClient* pAudioClient; hr = ma_IMMDevice_Activate(pMMDevice, &MA_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient); if (SUCCEEDED(hr)) { @@ -23186,7 +23821,7 @@ static ma_result ma_context_enumerate_devices_by_type__wasapi(ma_context* pConte if (SUCCEEDED(hr)) { hr = ma_IMMDeviceCollection_GetCount(pDeviceCollection, &deviceCount); if (FAILED(hr)) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count."); result = ma_result_from_HRESULT(hr); goto done; } @@ -23199,12 +23834,12 @@ static ma_result ma_context_enumerate_devices_by_type__wasapi(ma_context* pConte hr = ma_IMMDeviceCollection_Item(pDeviceCollection, iDevice, &pMMDevice); if (SUCCEEDED(hr)) { - result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, pDefaultDeviceID, MA_TRUE, &deviceInfo); /* MA_TRUE = onlySimpleInfo. */ + result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, pDefaultDeviceID, &deviceInfo); ma_IMMDevice_Release(pMMDevice); if (result == MA_SUCCESS) { - ma_bool32 cbResult = callback(pContext, deviceType, &deviceInfo, pUserData); - if (cbResult == MA_FALSE) { + ma_device_enumeration_result cbResult = callback(deviceType, &deviceInfo, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_ABORT) { break; } } @@ -23250,6 +23885,7 @@ static ma_result ma_context_get_IAudioClient_Desktop__wasapi(ma_context* pContex #else static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, MA_PROPVARIANT* pActivationParams, ma_IAudioClient** ppAudioClient, ma_IUnknown** ppActivatedInterface) { + ma_context_state_wasapi* pContextStateWASAPI = ma_context_get_backend_state__wasapi(pContext); ma_IActivateAudioInterfaceAsyncOperation *pAsyncOp = NULL; ma_completion_handler_uwp completionHandler; IID iid; @@ -23277,7 +23913,7 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m hr = StringFromIID(&iid, &iidStr); #endif if (FAILED(hr)) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory."); return ma_result_from_HRESULT(hr); } } @@ -23285,15 +23921,15 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m result = ma_completion_handler_uwp_init(&completionHandler); if (result != MA_SUCCESS) { ma_CoTaskMemFree(pContext, iidStr); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync()."); return result; } - hr = ((MA_PFN_ActivateAudioInterfaceAsync)pContext->wasapi.ActivateAudioInterfaceAsync)(iidStr, &MA_IID_IAudioClient, pActivationParams, (ma_IActivateAudioInterfaceCompletionHandler*)&completionHandler, (ma_IActivateAudioInterfaceAsyncOperation**)&pAsyncOp); + hr = pContextStateWASAPI->ActivateAudioInterfaceAsync(iidStr, &MA_IID_IAudioClient, pActivationParams, (ma_IActivateAudioInterfaceCompletionHandler*)&completionHandler, &pAsyncOp); if (FAILED(hr)) { ma_completion_handler_uwp_uninit(&completionHandler); ma_CoTaskMemFree(pContext, iidStr); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed."); return ma_result_from_HRESULT(hr); } @@ -23309,14 +23945,14 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m ma_IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); if (FAILED(hr) || FAILED(activateResult)) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device."); return FAILED(hr) ? ma_result_from_HRESULT(hr) : ma_result_from_HRESULT(activateResult); } /* Here is where we grab the IAudioClient interface. */ hr = ma_IUnknown_QueryInterface(pActivatedInterface, &MA_IID_IAudioClient, (void**)ppAudioClient); if (FAILED(hr)) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface."); return ma_result_from_HRESULT(hr); } @@ -23381,7 +24017,6 @@ typedef struct static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_uint32 loopbackProcessID, ma_bool32 loopbackProcessExclude, ma_IAudioClient** ppAudioClient, ma_WASAPIDeviceInterface** ppDeviceInterface) { -#if !defined(_MSC_VER) || defined(__clang__) ma_result result; ma_bool32 usingProcessLoopback = MA_FALSE; MA_AUDIOCLIENT_ACTIVATION_PARAMS audioclientActivationParams; @@ -23426,171 +24061,260 @@ static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_de */ if (result != MA_SUCCESS) { if (usingProcessLoopback) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Loopback mode requested to %s process ID %u, but initialization failed. Support for this feature begins with Windows 10 Build 20348. Confirm your version of Windows or consider not using process-specific loopback.\n", (loopbackProcessExclude) ? "exclude" : "include", loopbackProcessID); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Loopback mode requested to %s process ID %u, but initialization failed. Support for this feature begins with Windows 10 Build 20348. Confirm your version of Windows or consider not using process-specific loopback.", (loopbackProcessExclude) ? "exclude" : "include", loopbackProcessID); } } return result; -#else - return MA_BACKEND_NOT_ENABLED; -#endif } -static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +static void ma_backend_info__wasapi(ma_device_backend_info* pBackendInfo) { - /* Different enumeration for desktop and UWP. */ -#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - /* Desktop */ - HRESULT hr; - ma_IMMDeviceEnumerator* pDeviceEnumerator; + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "WASAPI"; + pBackendInfo->isLoopbackSupported = MA_TRUE; +} - hr = ma_CoCreateInstance(pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); - if (FAILED(hr)) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); - return ma_result_from_HRESULT(hr); +static ma_result ma_context_init__wasapi(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_wasapi* pContextStateWASAPI = NULL; + const ma_context_config_wasapi* pContextConfigWASAPI = (const ma_context_config_wasapi*)pContextBackendConfig; + ma_context_config_wasapi defaultConfigWASAPI; + ma_result result = MA_SUCCESS; + + if (pContextBackendConfig == NULL) { + defaultConfigWASAPI = ma_context_config_wasapi_init(); + pContextBackendConfig = &defaultConfigWASAPI; } - ma_context_enumerate_devices_by_type__wasapi(pContext, pDeviceEnumerator, ma_device_type_playback, callback, pUserData); - ma_context_enumerate_devices_by_type__wasapi(pContext, pDeviceEnumerator, ma_device_type_capture, callback, pUserData); + (void)pContextConfigWASAPI; - ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); -#else +#ifdef MA_WIN32_DESKTOP /* - UWP - - The MMDevice API is only supported on desktop applications. For now, while I'm still figuring out how to properly enumerate - over devices without using MMDevice, I'm restricting devices to defaults. + WASAPI is only supported in Vista SP1 and newer. The reason for SP1 and not the base version of Vista is that event-driven + exclusive mode does not work until SP1. - Hint: DeviceInformation::FindAllAsync() with DeviceClass.AudioCapture/AudioRender. https://blogs.windows.com/buildingapps/2014/05/15/real-time-audio-in-windows-store-and-windows-phone-apps/ + Unfortunately older compilers don't define these functions so we need to dynamically load them in order to avoid a link error. */ - if (callback) { - ma_bool32 cbResult = MA_TRUE; + { + ma_OSVERSIONINFOEXW osvi; + ma_handle kernel32DLL; + ma_PFNVerifyVersionInfoW _VerifyVersionInfoW; + ma_PFNVerSetConditionMask _VerSetConditionMask; - /* Playback. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - deviceInfo.isDefault = MA_TRUE; - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + kernel32DLL = ma_dlopen(ma_context_get_log(pContext), "kernel32.dll"); + if (kernel32DLL == NULL) { + return MA_NO_BACKEND; } - /* Capture. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - deviceInfo.isDefault = MA_TRUE; - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + _VerifyVersionInfoW = (ma_PFNVerifyVersionInfoW )ma_dlsym(ma_context_get_log(pContext), kernel32DLL, "VerifyVersionInfoW"); + _VerSetConditionMask = (ma_PFNVerSetConditionMask)ma_dlsym(ma_context_get_log(pContext), kernel32DLL, "VerSetConditionMask"); + if (_VerifyVersionInfoW == NULL || _VerSetConditionMask == NULL) { + ma_dlclose(ma_context_get_log(pContext), kernel32DLL); + return MA_NO_BACKEND; } - } -#endif - return MA_SUCCESS; -} + MA_ZERO_OBJECT(&osvi); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = ((MA_WIN32_WINNT_VISTA >> 8) & 0xFF); + osvi.dwMinorVersion = ((MA_WIN32_WINNT_VISTA >> 0) & 0xFF); + osvi.wServicePackMajor = 1; + if (_VerifyVersionInfoW(&osvi, MA_VER_MAJORVERSION | MA_VER_MINORVERSION | MA_VER_SERVICEPACKMAJOR, _VerSetConditionMask(_VerSetConditionMask(_VerSetConditionMask(0, MA_VER_MAJORVERSION, MA_VER_GREATER_EQUAL), MA_VER_MINORVERSION, MA_VER_GREATER_EQUAL), MA_VER_SERVICEPACKMAJOR, MA_VER_GREATER_EQUAL))) { + result = MA_SUCCESS; + } else { + result = MA_NO_BACKEND; + } -static ma_result ma_context_get_device_info__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) -{ -#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - ma_result result; - ma_IMMDevice* pMMDevice = NULL; - WCHAR* pDefaultDeviceID = NULL; + ma_dlclose(ma_context_get_log(pContext), kernel32DLL); + } +#endif - result = ma_context_get_MMDevice__wasapi(pContext, deviceType, pDeviceID, &pMMDevice); if (result != MA_SUCCESS) { return result; } - /* We need the default device ID so we can set the isDefault flag in the device info. */ - pDefaultDeviceID = ma_context_get_default_device_id__wasapi(pContext, deviceType); + pContextStateWASAPI = (ma_context_state_wasapi*)ma_calloc(sizeof(*pContextStateWASAPI), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateWASAPI == NULL) { + return MA_OUT_OF_MEMORY; + } + + #if defined(MA_WIN32_UWP) + { + /* Link to mmdevapi so we can get access to ActivateAudioInterfaceAsync(). */ + pContextStateWASAPI->hMMDevapi = ma_dlopen(ma_context_get_log(pContext), "mmdevapi.dll"); + if (pContextStateWASAPI->hMMDevapi) { + pContextStateWASAPI->ActivateAudioInterfaceAsync = (MA_PFN_ActivateAudioInterfaceAsync)ma_dlsym(ma_context_get_log(pContext), pContextStateWASAPI->hMMDevapi, "ActivateAudioInterfaceAsync"); + if (pContextStateWASAPI->ActivateAudioInterfaceAsync == NULL) { + /* ActivateAudioInterfaceAsync() could not be loaded. */ + ma_free(pContextStateWASAPI, ma_context_get_allocation_callbacks(pContext)); + ma_dlclose(ma_context_get_log(pContext), pContextStateWASAPI->hMMDevapi); + return MA_NO_BACKEND; + } + } else { + /* Failed to load mmdevapi.dll which is required for ActivateAudioInterfaceAsync() */ + ma_free(pContextStateWASAPI, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } + } + #endif - result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, pDefaultDeviceID, MA_FALSE, pDeviceInfo); /* MA_FALSE = !onlySimpleInfo. */ + /* Optionally use the Avrt API to specify the audio thread's latency sensitivity requirements */ + pContextStateWASAPI->hAvrt = ma_dlopen(ma_context_get_log(pContext), "avrt.dll"); + if (pContextStateWASAPI->hAvrt) { + pContextStateWASAPI->AvSetMmThreadCharacteristicsA = (MA_PFN_AvSetMmThreadCharacteristicsA )ma_dlsym(ma_context_get_log(pContext), pContextStateWASAPI->hAvrt, "AvSetMmThreadCharacteristicsA"); + pContextStateWASAPI->AvRevertMmThreadCharacteristics = (MA_PFN_AvRevertMmThreadCharacteristics)ma_dlsym(ma_context_get_log(pContext), pContextStateWASAPI->hAvrt, "AvRevertMmThreadCharacteristics"); - if (pDefaultDeviceID != NULL) { - ma_CoTaskMemFree(pContext, pDefaultDeviceID); - pDefaultDeviceID = NULL; + /* If either function could not be found, disable use of avrt entirely. */ + if (!pContextStateWASAPI->AvSetMmThreadCharacteristicsA || !pContextStateWASAPI->AvRevertMmThreadCharacteristics) { + pContextStateWASAPI->AvSetMmThreadCharacteristicsA = NULL; + pContextStateWASAPI->AvRevertMmThreadCharacteristics = NULL; + ma_dlclose(ma_context_get_log(pContext), pContextStateWASAPI->hAvrt); + pContextStateWASAPI->hAvrt = NULL; + } } - ma_IMMDevice_Release(pMMDevice); + *ppContextState = pContextStateWASAPI; - return result; -#else - ma_IAudioClient* pAudioClient; - ma_result result; + return MA_SUCCESS; +} - /* UWP currently only uses default devices. */ - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); +static void ma_context_uninit__wasapi(ma_context* pContext) +{ + ma_context_state_wasapi* pContextStateWASAPI = ma_context_get_backend_state__wasapi(pContext); + + MA_ASSERT(pContext != NULL); + + if (pContextStateWASAPI->hAvrt) { + ma_dlclose(ma_context_get_log(pContext), pContextStateWASAPI->hAvrt); + pContextStateWASAPI->hAvrt = NULL; } - result = ma_context_get_IAudioClient_UWP__wasapi(pContext, deviceType, pDeviceID, NULL, &pAudioClient, NULL); - if (result != MA_SUCCESS) { - return result; + #if defined(MA_WIN32_UWP) + { + if (pContextStateWASAPI->hMMDevapi) { + ma_dlclose(ma_context_get_log(pContext), pContextStateWASAPI->hMMDevapi); + pContextStateWASAPI->hMMDevapi = NULL; + } } + #endif + + ma_free(pContextStateWASAPI, ma_context_get_allocation_callbacks(pContext)); +} + +#if defined(MA_WIN32_UWP) +static ma_device_enumeration_result ma_context_enumerate_device_from_type_UWP__wasapi(ma_context* pContext, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_device_info deviceInfo; + ma_IAudioClient* pAudioClient; - result = ma_context_get_device_info_from_IAudioClient__wasapi(pContext, NULL, pAudioClient, pDeviceInfo); + MA_ZERO_OBJECT(&deviceInfo); - pDeviceInfo->isDefault = MA_TRUE; /* UWP only supports default devices. */ + /* Default. */ + deviceInfo.isDefault = MA_TRUE; - ma_IAudioClient_Release(pAudioClient); - return result; -#endif + /* ID. */ + /* Nothing to do. Always default. */ + + /* Name. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + + /* Data Format. */ + if (ma_context_get_IAudioClient_UWP__wasapi(pContext, deviceType, NULL, NULL, &pAudioClient, NULL) == MA_SUCCESS) { + ma_context_get_device_info_from_IAudioClient__wasapi(pContext, NULL, pAudioClient, &deviceInfo); + ma_IAudioClient_Release(pAudioClient); + } + + return callback(deviceType, &deviceInfo, pUserData);; } +#endif -static ma_result ma_device_uninit__wasapi(ma_device* pDevice) +typedef struct { - MA_ASSERT(pDevice != NULL); + ma_context* pContext; + ma_enum_devices_callback_proc callback; + void* pUserData; + ma_result result; +} ma_context_enumerate_devices_thread_data__wasapi; - #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) +static ma_thread_result MA_THREADCALL ma_context_enumerate_devices_thread__wasapi(void* pUserData) +{ + ma_context_enumerate_devices_thread_data__wasapi* pData = (ma_context_enumerate_devices_thread_data__wasapi*)pUserData; + + pData->result = MA_SUCCESS; + + ma_CoInitializeEx(pData->pContext, NULL, MA_COINIT_VALUE); { - if (pDevice->wasapi.pDeviceEnumerator) { - ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); - ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); - } + /* Different enumeration for desktop and UWP. */ + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) + { + /* Desktop */ + HRESULT hr; + ma_IMMDeviceEnumerator* pDeviceEnumerator; - ma_mutex_uninit(&pDevice->wasapi.rerouteLock); - } - #endif + hr = ma_CoCreateInstance(pData->pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + if (FAILED(hr)) { + ma_log_postf(ma_context_get_log(pData->pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + pData->result = ma_result_from_HRESULT(hr); + return 1; + } - if (pDevice->wasapi.pRenderClient) { - if (pDevice->wasapi.pMappedBufferPlayback != NULL) { - ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); - pDevice->wasapi.pMappedBufferPlayback = NULL; - pDevice->wasapi.mappedBufferPlaybackCap = 0; - pDevice->wasapi.mappedBufferPlaybackLen = 0; - } + ma_context_enumerate_devices_by_type__wasapi(pData->pContext, pDeviceEnumerator, ma_device_type_playback, pData->callback, pData->pUserData); + ma_context_enumerate_devices_by_type__wasapi(pData->pContext, pDeviceEnumerator, ma_device_type_capture, pData->callback, pData->pUserData); - ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); - } - if (pDevice->wasapi.pCaptureClient) { - if (pDevice->wasapi.pMappedBufferCapture != NULL) { - ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); - pDevice->wasapi.pMappedBufferCapture = NULL; - pDevice->wasapi.mappedBufferCaptureCap = 0; - pDevice->wasapi.mappedBufferCaptureLen = 0; + ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); } + #else + { + /* + UWP - ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); - } + The MMDevice API is only supported on desktop applications. For now, while I'm still figuring out how to properly enumerate + over devices without using MMDevice, I'm restricting devices to defaults. - if (pDevice->wasapi.pAudioClientPlayback) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - } - if (pDevice->wasapi.pAudioClientCapture) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - } + Hint: DeviceInformation::FindAllAsync() with DeviceClass.AudioCapture/AudioRender. https://blogs.windows.com/buildingapps/2014/05/15/real-time-audio-in-windows-store-and-windows-phone-apps/ + */ + if (callback) { + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; + + /* Playback. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type_UWP__wasapi(pData->pContext, ma_device_type_playback, pData->callback, pData->pUserData); + } + + /* Capture. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type_UWP__wasapi(pData->pContext, ma_device_type_capture, pData->callback, pData->pUserData); + } - if (pDevice->wasapi.hEventPlayback) { - CloseHandle((HANDLE)pDevice->wasapi.hEventPlayback); + (void)cbResult; + } + } + #endif } - if (pDevice->wasapi.hEventCapture) { - CloseHandle((HANDLE)pDevice->wasapi.hEventCapture); + ma_CoUninitialize(pData->pContext); + + return 0; +} + +static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_result result; + ma_thread thread; + ma_context_enumerate_devices_thread_data__wasapi data; + + data.pContext = pContext; + data.callback = callback; + data.pUserData = pUserData; + + result = ma_thread_create(&thread, ma_thread_priority_default, 0, ma_context_enumerate_devices_thread__wasapi, &data, ma_context_get_allocation_callbacks(pContext)); + if (result != MA_SUCCESS) { + return result; } - return MA_SUCCESS; + ma_thread_wait(&thread); + + return data.result; } @@ -23605,7 +24329,6 @@ typedef struct ma_uint32 periodSizeInMillisecondsIn; ma_uint32 periodsIn; ma_share_mode shareMode; - ma_performance_profile performanceProfile; ma_bool32 noAutoConvertSRC; ma_bool32 noDefaultQualitySRC; ma_bool32 noHardwareOffloading; @@ -23836,11 +24559,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device pData->periodSizeInFramesOut = pData->periodSizeInFramesIn; if (pData->periodSizeInFramesOut == 0) { if (pData->periodSizeInMillisecondsIn == 0) { - if (pData->performanceProfile == ma_performance_profile_low_latency) { - pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY, wf.nSamplesPerSec); - } else { - pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE, wf.nSamplesPerSec); - } + pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS, wf.nSamplesPerSec); } else { pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->periodSizeInMillisecondsIn, wf.nSamplesPerSec); } @@ -23851,7 +24570,24 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Slightly different initialization for shared and exclusive modes. We try exclusive mode first, and if it fails, fall back to shared mode. */ if (shareMode == MA_AUDCLNT_SHAREMODE_EXCLUSIVE) { - MA_REFERENCE_TIME bufferDuration = periodDurationInMicroseconds * pData->periodsOut * 10; + MA_REFERENCE_TIME bufferDuration = bufferDuration = periodDurationInMicroseconds * 10; + MA_REFERENCE_TIME minPeriod = 0; + + /* There is no notion of a period in Exclusive mode. Set this to 1. */ + pData->periodsOut = 1; + + /* + Grab the minimum period and start with that. If this fails we'll keep trying by incrementing the + buffer duration by the period size. + */ + hr = ma_IAudioClient_GetDevicePeriod(pData->pAudioClient, NULL, &minPeriod); + if (FAILED(hr)) { + minPeriod = 0; + } + + if (bufferDuration < minPeriod) { + bufferDuration = minPeriod; + } /* If the periodicity is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing @@ -23859,7 +24595,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device */ hr = E_FAIL; for (;;) { - hr = ma_IAudioClient_Initialize((ma_IAudioClient*)pData->pAudioClient, shareMode, streamFlags, bufferDuration, bufferDuration, (MA_WAVEFORMATEX*)&wf, NULL); + hr = ma_IAudioClient_Initialize(pData->pAudioClient, shareMode, streamFlags, bufferDuration, bufferDuration, (MA_WAVEFORMATEX*)&wf, NULL); if (hr == MA_AUDCLNT_E_INVALID_DEVICE_PERIOD) { if (bufferDuration > 500*10000) { break; @@ -23868,7 +24604,8 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device break; } - bufferDuration = bufferDuration * 2; + /* Increment by our period size. */ + bufferDuration += (periodDurationInMicroseconds * 10); continue; } } else { @@ -23943,11 +24680,11 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* The period needs to be clamped between minPeriodInFrames and maxPeriodInFrames. */ actualPeriodInFrames = ma_clamp(actualPeriodInFrames, minPeriodInFrames, maxPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=%d)\n", actualPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " defaultPeriodInFrames=%d\n", defaultPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " fundamentalPeriodInFrames=%d\n", fundamentalPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " minPeriodInFrames=%d\n", minPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " maxPeriodInFrames=%d\n", maxPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=%d)", actualPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " defaultPeriodInFrames=%d", defaultPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " fundamentalPeriodInFrames=%d", fundamentalPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " minPeriodInFrames=%d", minPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " maxPeriodInFrames=%d", maxPeriodInFrames); /* If the client requested a largish buffer than we don't actually want to use low latency shared mode because it forces small buffers. */ if (actualPeriodInFrames >= desiredPeriodInFrames) { @@ -23960,16 +24697,16 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device wasInitializedUsingIAudioClient3 = MA_TRUE; pData->periodSizeInFramesOut = actualPeriodInFrames; - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Using IAudioClient3\n"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " periodSizeInFramesOut=%d\n", pData->periodSizeInFramesOut); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Using IAudioClient3"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " periodSizeInFramesOut=%d", pData->periodSizeInFramesOut); } else { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioClient3_InitializeSharedAudioStream failed. Falling back to IAudioClient.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioClient3_InitializeSharedAudioStream failed. Falling back to IAudioClient."); } } else { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Not using IAudioClient3 because the desired period size is larger than the maximum supported by IAudioClient3.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Not using IAudioClient3 because the desired period size is larger than the maximum supported by IAudioClient3."); } } else { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioClient3_GetSharedModeEnginePeriod failed. Falling back to IAudioClient.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioClient3_GetSharedModeEnginePeriod failed. Falling back to IAudioClient."); } ma_IAudioClient3_Release(pAudioClient3); @@ -23979,7 +24716,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device } #else { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Not using IAudioClient3 because MA_WASAPI_NO_LOW_LATENCY_SHARED_MODE is enabled.\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Not using IAudioClient3 because MA_WASAPI_NO_LOW_LATENCY_SHARED_MODE is enabled."); } #endif @@ -24100,7 +24837,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device } if (errorMsg != NULL && errorMsg[0] != '\0') { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "%s\n", errorMsg); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "%s", errorMsg); } return result; @@ -24111,8 +24848,11 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type deviceType) { + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); ma_device_init_internal_data__wasapi data; ma_result result; + ma_device_descriptor internalDescriptor; + const ma_device_id* pDeviceID = NULL; MA_ASSERT(pDevice != NULL); @@ -24122,116 +24862,110 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev } - /* - Before reinitializing the device we need to free the previous audio clients. - - There's a known memory leak here. We will be calling this from the routing change callback that - is fired by WASAPI. If we attempt to release the IAudioClient we will deadlock. In my opinion - this is a bug. I'm not sure what I need to do to handle this cleanly, but I think we'll probably - need some system where we post an event, but delay the execution of it until the callback has - returned. I'm not sure how to do this reliably, however. I have set up some infrastructure for - a command thread which might be useful for this. - */ + /* Before reinitializing the device we need to free the previous audio clients. */ if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { - if (pDevice->wasapi.pCaptureClient) { - ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); - pDevice->wasapi.pCaptureClient = NULL; + if (pDeviceStateWASAPI->pCaptureClient) { + ma_IAudioCaptureClient_Release(pDeviceStateWASAPI->pCaptureClient); + pDeviceStateWASAPI->pCaptureClient = NULL; } - if (pDevice->wasapi.pAudioClientCapture) { - /*ma_device_release_IAudioClient_service__wasapi(pDevice, ma_device_type_capture);*/ - pDevice->wasapi.pAudioClientCapture = NULL; + if (pDeviceStateWASAPI->pAudioClientCapture) { + ma_IAudioClient_Release(pDeviceStateWASAPI->pAudioClientCapture); + pDeviceStateWASAPI->pAudioClientCapture = NULL; } } if (deviceType == ma_device_type_playback) { - if (pDevice->wasapi.pRenderClient) { - ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); - pDevice->wasapi.pRenderClient = NULL; + if (pDeviceStateWASAPI->pRenderClient) { + ma_IAudioRenderClient_Release(pDeviceStateWASAPI->pRenderClient); + pDeviceStateWASAPI->pRenderClient = NULL; } - if (pDevice->wasapi.pAudioClientPlayback) { - /*ma_device_release_IAudioClient_service__wasapi(pDevice, ma_device_type_playback);*/ - pDevice->wasapi.pAudioClientPlayback = NULL; + if (pDeviceStateWASAPI->pAudioClientPlayback) { + ma_IAudioClient_Release(pDeviceStateWASAPI->pAudioClientPlayback); + pDeviceStateWASAPI->pAudioClientPlayback = NULL; } } if (deviceType == ma_device_type_playback) { - data.formatIn = pDevice->playback.format; - data.channelsIn = pDevice->playback.channels; - MA_COPY_MEMORY(data.channelMapIn, pDevice->playback.channelMap, sizeof(pDevice->playback.channelMap)); - data.shareMode = pDevice->playback.shareMode; + pDeviceID = pDevice->playback.pID; + data.shareMode = pDevice->playback.shareMode; } else { - data.formatIn = pDevice->capture.format; - data.channelsIn = pDevice->capture.channels; - MA_COPY_MEMORY(data.channelMapIn, pDevice->capture.channelMap, sizeof(pDevice->capture.channelMap)); - data.shareMode = pDevice->capture.shareMode; - } - - data.sampleRateIn = pDevice->sampleRate; - data.periodSizeInFramesIn = pDevice->wasapi.originalPeriodSizeInFrames; - data.periodSizeInMillisecondsIn = pDevice->wasapi.originalPeriodSizeInMilliseconds; - data.periodsIn = pDevice->wasapi.originalPeriods; - data.performanceProfile = pDevice->wasapi.originalPerformanceProfile; - data.noAutoConvertSRC = pDevice->wasapi.noAutoConvertSRC; - data.noDefaultQualitySRC = pDevice->wasapi.noDefaultQualitySRC; - data.noHardwareOffloading = pDevice->wasapi.noHardwareOffloading; - data.loopbackProcessID = pDevice->wasapi.loopbackProcessID; - data.loopbackProcessExclude = pDevice->wasapi.loopbackProcessExclude; - result = ma_device_init_internal__wasapi(pDevice->pContext, deviceType, NULL, &data); + pDeviceID = pDevice->playback.pID; + data.shareMode = pDevice->capture.shareMode; + } + + data.formatIn = ma_device_get_format(pDevice, deviceType); + data.channelsIn = ma_device_get_channels(pDevice, deviceType); + data.sampleRateIn = ma_device_get_sample_rate(pDevice); + ma_device_get_channel_map(pDevice, deviceType, data.channelMapIn, ma_countof(data.channelMapIn)); + data.periodSizeInFramesIn = pDeviceStateWASAPI->originalPeriodSizeInFrames; + data.periodSizeInMillisecondsIn = pDeviceStateWASAPI->originalPeriodSizeInMilliseconds; + data.periodsIn = pDeviceStateWASAPI->originalPeriods; + data.noAutoConvertSRC = pDeviceStateWASAPI->noAutoConvertSRC; + data.noDefaultQualitySRC = pDeviceStateWASAPI->noDefaultQualitySRC; + data.noHardwareOffloading = pDeviceStateWASAPI->noHardwareOffloading; + data.loopbackProcessID = pDeviceStateWASAPI->loopbackProcessID; + data.loopbackProcessExclude = pDeviceStateWASAPI->loopbackProcessExclude; + result = ma_device_init_internal__wasapi(pDevice->pContext, deviceType, pDeviceID, &data); if (result != MA_SUCCESS) { return result; } /* At this point we have some new objects ready to go. We need to uninitialize the previous ones and then set the new ones. */ if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { - pDevice->wasapi.pAudioClientCapture = data.pAudioClient; - pDevice->wasapi.pCaptureClient = data.pCaptureClient; + pDeviceStateWASAPI->pAudioClientCapture = data.pAudioClient; + pDeviceStateWASAPI->pCaptureClient = data.pCaptureClient; - pDevice->capture.internalFormat = data.formatOut; - pDevice->capture.internalChannels = data.channelsOut; - pDevice->capture.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->capture.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->capture.internalPeriods = data.periodsOut; - ma_strcpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), data.deviceName); + ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDeviceStateWASAPI->pAudioClientCapture, (HANDLE)pDeviceStateWASAPI->hEventCapture); - ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, (HANDLE)pDevice->wasapi.hEventCapture); - - pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); + pDeviceStateWASAPI->periodSizeInFramesCapture = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDeviceStateWASAPI->pAudioClientCapture, &pDeviceStateWASAPI->actualBufferSizeInFramesCapture); /* We must always have a valid ID. */ ma_strcpy_s_WCHAR(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); } if (deviceType == ma_device_type_playback) { - pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; - pDevice->wasapi.pRenderClient = data.pRenderClient; - - pDevice->playback.internalFormat = data.formatOut; - pDevice->playback.internalChannels = data.channelsOut; - pDevice->playback.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->playback.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->playback.internalPeriods = data.periodsOut; - ma_strcpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), data.deviceName); + pDeviceStateWASAPI->pAudioClientPlayback = data.pAudioClient; + pDeviceStateWASAPI->pRenderClient = data.pRenderClient; - ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, (HANDLE)pDevice->wasapi.hEventPlayback); + ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDeviceStateWASAPI->pAudioClientPlayback, (HANDLE)pDeviceStateWASAPI->hEventPlayback); - pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); + pDeviceStateWASAPI->periodSizeInFramesPlayback = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDeviceStateWASAPI->pAudioClientPlayback, &pDeviceStateWASAPI->actualBufferSizeInFramesPlayback); /* We must always have a valid ID because rerouting will look at it. */ ma_strcpy_s_WCHAR(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); } + /* The internal data converter needs to be reinitialized. */ + MA_ZERO_OBJECT(&internalDescriptor); + internalDescriptor.format = data.formatOut; + internalDescriptor.channels = data.channelsOut; + internalDescriptor.sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(internalDescriptor.channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + internalDescriptor.periodSizeInFrames = data.periodSizeInFramesOut; + internalDescriptor.periodCount = data.periodsOut; + + result = ma_device_update_descriptor(pDevice, deviceType, NULL, &internalDescriptor); + if (result != MA_SUCCESS) { + return result; + } + return MA_SUCCESS; } -static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static void ma_device_uninit__wasapi(ma_device* pDevice); + +static ma_result ma_device_init__wasapi(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { + ma_device_state_wasapi* pDeviceStateWASAPI = NULL; + ma_context_state_wasapi* pContextStateWASAPI = (ma_context_state_wasapi*)ma_context_get_backend_state(ma_device_get_context(pDevice)); + const ma_device_config_wasapi* pDeviceConfigWASAPI = (const ma_device_config_wasapi*)pDeviceBackendConfig; + ma_device_config_wasapi defaultConfigWASAPI; + ma_device_type deviceType = ma_device_get_type(pDevice); ma_result result = MA_SUCCESS; #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) @@ -24241,20 +24975,31 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf MA_ASSERT(pDevice != NULL); - MA_ZERO_OBJECT(&pDevice->wasapi); - pDevice->wasapi.usage = pConfig->wasapi.usage; - pDevice->wasapi.noAutoConvertSRC = pConfig->wasapi.noAutoConvertSRC; - pDevice->wasapi.noDefaultQualitySRC = pConfig->wasapi.noDefaultQualitySRC; - pDevice->wasapi.noHardwareOffloading = pConfig->wasapi.noHardwareOffloading; - pDevice->wasapi.loopbackProcessID = pConfig->wasapi.loopbackProcessID; - pDevice->wasapi.loopbackProcessExclude = pConfig->wasapi.loopbackProcessExclude; - /* Exclusive mode is not allowed with loopback. */ - if (pConfig->deviceType == ma_device_type_loopback && pConfig->playback.shareMode == ma_share_mode_exclusive) { + if (deviceType == ma_device_type_loopback && pDescriptorCapture->shareMode == ma_share_mode_exclusive) { return MA_INVALID_DEVICE_CONFIG; } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { + if (pDeviceConfigWASAPI == NULL) { + defaultConfigWASAPI = ma_device_config_wasapi_init(); + pDeviceConfigWASAPI = &defaultConfigWASAPI; + } + + pDeviceStateWASAPI = (ma_device_state_wasapi*)ma_calloc(sizeof(*pDeviceStateWASAPI), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateWASAPI == NULL) { + return MA_OUT_OF_MEMORY; + } + + pDeviceStateWASAPI->usage = pDeviceConfigWASAPI->usage; + pDeviceStateWASAPI->noAutoConvertSRC = pDeviceConfigWASAPI->noAutoConvertSRC; + pDeviceStateWASAPI->noDefaultQualitySRC = pDeviceConfigWASAPI->noDefaultQualitySRC; + pDeviceStateWASAPI->noHardwareOffloading = pDeviceConfigWASAPI->noHardwareOffloading; + pDeviceStateWASAPI->loopbackProcessID = pDeviceConfigWASAPI->loopbackProcessID; + pDeviceStateWASAPI->loopbackProcessExclude = pDeviceConfigWASAPI->loopbackProcessExclude; + + + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { ma_device_init_internal_data__wasapi data; data.formatIn = pDescriptorCapture->format; data.channelsIn = pDescriptorCapture->channels; @@ -24264,49 +25009,49 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf data.periodSizeInMillisecondsIn = pDescriptorCapture->periodSizeInMilliseconds; data.periodsIn = pDescriptorCapture->periodCount; data.shareMode = pDescriptorCapture->shareMode; - data.performanceProfile = pConfig->performanceProfile; - data.noAutoConvertSRC = pConfig->wasapi.noAutoConvertSRC; - data.noDefaultQualitySRC = pConfig->wasapi.noDefaultQualitySRC; - data.noHardwareOffloading = pConfig->wasapi.noHardwareOffloading; - data.loopbackProcessID = pConfig->wasapi.loopbackProcessID; - data.loopbackProcessExclude = pConfig->wasapi.loopbackProcessExclude; - - result = ma_device_init_internal__wasapi(pDevice->pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture, pDescriptorCapture->pDeviceID, &data); + data.noAutoConvertSRC = pDeviceConfigWASAPI->noAutoConvertSRC; + data.noDefaultQualitySRC = pDeviceConfigWASAPI->noDefaultQualitySRC; + data.noHardwareOffloading = pDeviceConfigWASAPI->noHardwareOffloading; + data.loopbackProcessID = pDeviceConfigWASAPI->loopbackProcessID; + data.loopbackProcessExclude = pDeviceConfigWASAPI->loopbackProcessExclude; + + result = ma_device_init_internal__wasapi(pDevice->pContext, (deviceType == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture, pDescriptorCapture->pDeviceID, &data); if (result != MA_SUCCESS) { + ma_free(pDeviceStateWASAPI, ma_device_get_allocation_callbacks(pDevice)); return result; } - pDevice->wasapi.pAudioClientCapture = data.pAudioClient; - pDevice->wasapi.pCaptureClient = data.pCaptureClient; - pDevice->wasapi.originalPeriodSizeInMilliseconds = pDescriptorCapture->periodSizeInMilliseconds; - pDevice->wasapi.originalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; - pDevice->wasapi.originalPeriods = pDescriptorCapture->periodCount; - pDevice->wasapi.originalPerformanceProfile = pConfig->performanceProfile; + pDeviceStateWASAPI->pAudioClientCapture = data.pAudioClient; + pDeviceStateWASAPI->pCaptureClient = data.pCaptureClient; + pDeviceStateWASAPI->originalPeriodSizeInMilliseconds = pDescriptorCapture->periodSizeInMilliseconds; + pDeviceStateWASAPI->originalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + pDeviceStateWASAPI->originalPeriods = pDescriptorCapture->periodCount; /* The event for capture needs to be manual reset for the same reason as playback. We keep the initial state set to unsignaled, however, because we want to block until we actually have something for the first call to ma_device_read(). */ - pDevice->wasapi.hEventCapture = (ma_handle)CreateEventA(NULL, FALSE, FALSE, NULL); /* Auto reset, unsignaled by default. */ - if (pDevice->wasapi.hEventCapture == NULL) { + pDeviceStateWASAPI->hEventCapture = CreateEventA(NULL, FALSE, FALSE, NULL); /* Auto reset, unsignaled by default. */ + if (pDeviceStateWASAPI->hEventCapture == NULL) { result = ma_result_from_GetLastError(GetLastError()); - if (pDevice->wasapi.pCaptureClient != NULL) { - ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); - pDevice->wasapi.pCaptureClient = NULL; + if (pDeviceStateWASAPI->pCaptureClient != NULL) { + ma_IAudioCaptureClient_Release(pDeviceStateWASAPI->pCaptureClient); + pDeviceStateWASAPI->pCaptureClient = NULL; } - if (pDevice->wasapi.pAudioClientCapture != NULL) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - pDevice->wasapi.pAudioClientCapture = NULL; + if (pDeviceStateWASAPI->pAudioClientCapture != NULL) { + ma_IAudioClient_Release(pDeviceStateWASAPI->pAudioClientCapture); + pDeviceStateWASAPI->pAudioClientCapture = NULL; } + ma_free(pDeviceStateWASAPI, ma_device_get_allocation_callbacks(pDevice)); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture."); return result; } - ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, (HANDLE)pDevice->wasapi.hEventCapture); + ma_IAudioClient_SetEventHandle(pDeviceStateWASAPI->pAudioClientCapture, pDeviceStateWASAPI->hEventCapture); - pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); + pDeviceStateWASAPI->periodSizeInFramesCapture = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize(pDeviceStateWASAPI->pAudioClientCapture, &pDeviceStateWASAPI->actualBufferSizeInFramesCapture); /* We must always have a valid ID. */ ma_strcpy_s_WCHAR(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); @@ -24320,7 +25065,7 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf pDescriptorCapture->periodCount = data.periodsOut; } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { ma_device_init_internal_data__wasapi data; data.formatIn = pDescriptorPlayback->format; data.channelsIn = pDescriptorPlayback->channels; @@ -24330,37 +25075,37 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf data.periodSizeInMillisecondsIn = pDescriptorPlayback->periodSizeInMilliseconds; data.periodsIn = pDescriptorPlayback->periodCount; data.shareMode = pDescriptorPlayback->shareMode; - data.performanceProfile = pConfig->performanceProfile; - data.noAutoConvertSRC = pConfig->wasapi.noAutoConvertSRC; - data.noDefaultQualitySRC = pConfig->wasapi.noDefaultQualitySRC; - data.noHardwareOffloading = pConfig->wasapi.noHardwareOffloading; - data.loopbackProcessID = pConfig->wasapi.loopbackProcessID; - data.loopbackProcessExclude = pConfig->wasapi.loopbackProcessExclude; + data.noAutoConvertSRC = pDeviceConfigWASAPI->noAutoConvertSRC; + data.noDefaultQualitySRC = pDeviceConfigWASAPI->noDefaultQualitySRC; + data.noHardwareOffloading = pDeviceConfigWASAPI->noHardwareOffloading; + data.loopbackProcessID = pDeviceConfigWASAPI->loopbackProcessID; + data.loopbackProcessExclude = pDeviceConfigWASAPI->loopbackProcessExclude; result = ma_device_init_internal__wasapi(pDevice->pContext, ma_device_type_playback, pDescriptorPlayback->pDeviceID, &data); if (result != MA_SUCCESS) { - if (pConfig->deviceType == ma_device_type_duplex) { - if (pDevice->wasapi.pCaptureClient != NULL) { - ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); - pDevice->wasapi.pCaptureClient = NULL; + if (deviceType == ma_device_type_duplex) { + if (pDeviceStateWASAPI->pCaptureClient != NULL) { + ma_IAudioCaptureClient_Release(pDeviceStateWASAPI->pCaptureClient); + pDeviceStateWASAPI->pCaptureClient = NULL; } - if (pDevice->wasapi.pAudioClientCapture != NULL) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - pDevice->wasapi.pAudioClientCapture = NULL; + if (pDeviceStateWASAPI->pAudioClientCapture != NULL) { + ma_IAudioClient_Release(pDeviceStateWASAPI->pAudioClientCapture); + pDeviceStateWASAPI->pAudioClientCapture = NULL; } - CloseHandle((HANDLE)pDevice->wasapi.hEventCapture); - pDevice->wasapi.hEventCapture = NULL; + CloseHandle((HANDLE)pDeviceStateWASAPI->hEventCapture); + pDeviceStateWASAPI->hEventCapture = NULL; } + + ma_free(pDeviceStateWASAPI, ma_device_get_allocation_callbacks(pDevice)); return result; } - pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; - pDevice->wasapi.pRenderClient = data.pRenderClient; - pDevice->wasapi.originalPeriodSizeInMilliseconds = pDescriptorPlayback->periodSizeInMilliseconds; - pDevice->wasapi.originalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; - pDevice->wasapi.originalPeriods = pDescriptorPlayback->periodCount; - pDevice->wasapi.originalPerformanceProfile = pConfig->performanceProfile; + pDeviceStateWASAPI->pAudioClientPlayback = data.pAudioClient; + pDeviceStateWASAPI->pRenderClient = data.pRenderClient; + pDeviceStateWASAPI->originalPeriodSizeInMilliseconds = pDescriptorPlayback->periodSizeInMilliseconds; + pDeviceStateWASAPI->originalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + pDeviceStateWASAPI->originalPeriods = pDescriptorPlayback->periodCount; /* The event for playback is needs to be manual reset because we want to explicitly control the fact that it becomes signalled @@ -24369,40 +25114,41 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf The playback event also needs to be initially set to a signaled state so that the first call to ma_device_write() is able to get passed WaitForMultipleObjects(). */ - pDevice->wasapi.hEventPlayback = (ma_handle)CreateEventA(NULL, FALSE, TRUE, NULL); /* Auto reset, signaled by default. */ - if (pDevice->wasapi.hEventPlayback == NULL) { + pDeviceStateWASAPI->hEventPlayback = CreateEventA(NULL, FALSE, TRUE, NULL); /* Auto reset, signaled by default. */ + if (pDeviceStateWASAPI->hEventPlayback == NULL) { result = ma_result_from_GetLastError(GetLastError()); - if (pConfig->deviceType == ma_device_type_duplex) { - if (pDevice->wasapi.pCaptureClient != NULL) { - ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); - pDevice->wasapi.pCaptureClient = NULL; + if (deviceType == ma_device_type_duplex) { + if (pDeviceStateWASAPI->pCaptureClient != NULL) { + ma_IAudioCaptureClient_Release(pDeviceStateWASAPI->pCaptureClient); + pDeviceStateWASAPI->pCaptureClient = NULL; } - if (pDevice->wasapi.pAudioClientCapture != NULL) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - pDevice->wasapi.pAudioClientCapture = NULL; + if (pDeviceStateWASAPI->pAudioClientCapture != NULL) { + ma_IAudioClient_Release(pDeviceStateWASAPI->pAudioClientCapture); + pDeviceStateWASAPI->pAudioClientCapture = NULL; } - CloseHandle((HANDLE)pDevice->wasapi.hEventCapture); - pDevice->wasapi.hEventCapture = NULL; + CloseHandle(pDeviceStateWASAPI->hEventCapture); + pDeviceStateWASAPI->hEventCapture = NULL; } - if (pDevice->wasapi.pRenderClient != NULL) { - ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); - pDevice->wasapi.pRenderClient = NULL; + if (pDeviceStateWASAPI->pRenderClient != NULL) { + ma_IAudioRenderClient_Release(pDeviceStateWASAPI->pRenderClient); + pDeviceStateWASAPI->pRenderClient = NULL; } - if (pDevice->wasapi.pAudioClientPlayback != NULL) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - pDevice->wasapi.pAudioClientPlayback = NULL; + if (pDeviceStateWASAPI->pAudioClientPlayback != NULL) { + ma_IAudioClient_Release(pDeviceStateWASAPI->pAudioClientPlayback); + pDeviceStateWASAPI->pAudioClientPlayback = NULL; } + ma_free(pDeviceStateWASAPI, ma_device_get_allocation_callbacks(pDevice)); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback."); return result; } - ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, (HANDLE)pDevice->wasapi.hEventPlayback); + ma_IAudioClient_SetEventHandle(pDeviceStateWASAPI->pAudioClientPlayback, pDeviceStateWASAPI->hEventPlayback); - pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); + pDeviceStateWASAPI->periodSizeInFramesPlayback = data.periodSizeInFramesOut; + ma_IAudioClient_GetBufferSize(pDeviceStateWASAPI->pAudioClientPlayback, &pDeviceStateWASAPI->actualBufferSizeInFramesPlayback); /* We must always have a valid ID because rerouting will look at it. */ ma_strcpy_s_WCHAR(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); @@ -24422,17 +25168,15 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf stop the device outright and let the application handle it. */ #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) - if (pConfig->wasapi.noAutoStreamRouting == MA_FALSE) { - if ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) && pConfig->capture.pDeviceID == NULL) { - pDevice->wasapi.allowCaptureAutoStreamRouting = MA_TRUE; + if (pDeviceConfigWASAPI->noAutoStreamRouting == MA_FALSE) { + if ((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) && pDescriptorCapture->pDeviceID == NULL) { + pDeviceStateWASAPI->allowCaptureAutoStreamRouting = MA_TRUE; } - if ((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.pDeviceID == NULL) { - pDevice->wasapi.allowPlaybackAutoStreamRouting = MA_TRUE; + if ((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->pDeviceID == NULL) { + pDeviceStateWASAPI->allowPlaybackAutoStreamRouting = MA_TRUE; } } - ma_mutex_init(&pDevice->wasapi.rerouteLock); - hr = ma_CoCreateInstance(pDevice->pContext, &MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { ma_device_uninit__wasapi(pDevice); @@ -24440,27 +25184,80 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf return ma_result_from_HRESULT(hr); } - pDevice->wasapi.notificationClient.lpVtbl = (void*)&g_maNotificationCientVtbl; - pDevice->wasapi.notificationClient.counter = 1; - pDevice->wasapi.notificationClient.pDevice = pDevice; + pDeviceStateWASAPI->notificationClient.lpVtbl = (void*)&g_maNotificationCientVtbl; + pDeviceStateWASAPI->notificationClient.counter = 1; + pDeviceStateWASAPI->notificationClient.pDevice = pDevice; - hr = pDeviceEnumerator->lpVtbl->RegisterEndpointNotificationCallback(pDeviceEnumerator, &pDevice->wasapi.notificationClient); + hr = pDeviceEnumerator->lpVtbl->RegisterEndpointNotificationCallback(pDeviceEnumerator, &pDeviceStateWASAPI->notificationClient); if (SUCCEEDED(hr)) { - pDevice->wasapi.pDeviceEnumerator = (ma_ptr)pDeviceEnumerator; + pDeviceStateWASAPI->pDeviceEnumerator = pDeviceEnumerator; } else { /* Not the end of the world if we fail to register the notification callback. We just won't support automatic stream routing. */ ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); } #endif - ma_atomic_bool32_set(&pDevice->wasapi.isStartedCapture, MA_FALSE); - ma_atomic_bool32_set(&pDevice->wasapi.isStartedPlayback, MA_FALSE); + if (pContextStateWASAPI->hAvrt) { + const char* pTaskName = ma_to_usage_string__wasapi(pDeviceStateWASAPI->usage); + if (pTaskName) { + DWORD idx = 0; + pDeviceStateWASAPI->hAvrtHandle = pContextStateWASAPI->AvSetMmThreadCharacteristicsA(pTaskName, &idx); + } + } + + *ppDeviceState = pDeviceStateWASAPI; return MA_SUCCESS; } +static void ma_device_uninit__wasapi(ma_device* pDevice) +{ + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); + ma_context_state_wasapi* pContextStateWASAPI = (ma_context_state_wasapi*)ma_context_get_backend_state(ma_device_get_context(pDevice)); + + MA_ASSERT(pDevice != NULL); + + if (pDeviceStateWASAPI->hAvrtHandle) { + pContextStateWASAPI->AvRevertMmThreadCharacteristics(pDeviceStateWASAPI->hAvrtHandle); + pDeviceStateWASAPI->hAvrtHandle = NULL; + } + + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) + { + if (pDeviceStateWASAPI->pDeviceEnumerator) { + pDeviceStateWASAPI->pDeviceEnumerator->lpVtbl->UnregisterEndpointNotificationCallback(pDeviceStateWASAPI->pDeviceEnumerator, &pDeviceStateWASAPI->notificationClient); + ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDeviceStateWASAPI->pDeviceEnumerator); + } + } + #endif + + if (pDeviceStateWASAPI->pRenderClient) { + ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDeviceStateWASAPI->pRenderClient); + } + if (pDeviceStateWASAPI->pCaptureClient) { + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDeviceStateWASAPI->pCaptureClient); + } + + if (pDeviceStateWASAPI->pAudioClientPlayback) { + ma_IAudioClient_Release((ma_IAudioClient*)pDeviceStateWASAPI->pAudioClientPlayback); + } + if (pDeviceStateWASAPI->pAudioClientCapture) { + ma_IAudioClient_Release((ma_IAudioClient*)pDeviceStateWASAPI->pAudioClientCapture); + } + + if (pDeviceStateWASAPI->hEventPlayback) { + CloseHandle((HANDLE)pDeviceStateWASAPI->hEventPlayback); + } + if (pDeviceStateWASAPI->hEventCapture) { + CloseHandle((HANDLE)pDeviceStateWASAPI->hEventCapture); + } + + ma_free(pDeviceStateWASAPI, ma_device_get_allocation_callbacks(pDevice)); +} + static ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_IAudioClient* pAudioClient, ma_uint32* pFrameCount) { + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); ma_uint32 paddingFramesCount; HRESULT hr; ma_share_mode shareMode; @@ -24470,7 +25267,7 @@ static ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_ *pFrameCount = 0; - if ((ma_ptr)pAudioClient != pDevice->wasapi.pAudioClientPlayback && (ma_ptr)pAudioClient != pDevice->wasapi.pAudioClientCapture) { + if (pAudioClient == NULL || (pAudioClient != pDeviceStateWASAPI->pAudioClientPlayback && pAudioClient != pDeviceStateWASAPI->pAudioClientCapture)) { return MA_INVALID_OPERATION; } @@ -24489,7 +25286,7 @@ static ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_ Considering this, I'm going to skip GetCurrentPadding() for exclusive mode and just report the entire buffer. This depends on the caller making sure they wait on the event handler. */ - shareMode = ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) ? pDevice->playback.shareMode : pDevice->capture.shareMode; + shareMode = (pAudioClient == pDeviceStateWASAPI->pAudioClientPlayback) ? pDevice->playback.shareMode : pDevice->capture.shareMode; if (shareMode == ma_share_mode_shared) { /* Shared mode. */ hr = ma_IAudioClient_GetCurrentPadding(pAudioClient, &paddingFramesCount); @@ -24497,17 +25294,17 @@ static ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_ return ma_result_from_HRESULT(hr); } - if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { - *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesPlayback - paddingFramesCount; + if (pAudioClient == pDeviceStateWASAPI->pAudioClientPlayback) { + *pFrameCount = pDeviceStateWASAPI->actualBufferSizeInFramesPlayback - paddingFramesCount; } else { *pFrameCount = paddingFramesCount; } } else { /* Exclusive mode. */ - if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { - *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesPlayback; + if (pAudioClient == pDeviceStateWASAPI->pAudioClientPlayback) { + *pFrameCount = pDeviceStateWASAPI->actualBufferSizeInFramesPlayback; } else { - *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesCapture; + *pFrameCount = pDeviceStateWASAPI->actualBufferSizeInFramesCapture; } } @@ -24523,162 +25320,132 @@ static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type de return MA_INVALID_ARGS; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "=== CHANGING DEVICE ===\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "=== CHANGING DEVICE ==="); result = ma_device_reinit__wasapi(pDevice, deviceType); if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WASAPI] Reinitializing device after route change failed.\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WASAPI] Reinitializing device after route change failed."); return result; } - ma_device__post_init_setup(pDevice, deviceType); - ma_device__on_notification_rerouted(pDevice); + ma_device_post_notification_rerouted(pDevice); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "=== DEVICE CHANGED ===\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "=== DEVICE CHANGED ==="); return MA_SUCCESS; } -static ma_result ma_device_start__wasapi_nolock(ma_device* pDevice) +static ma_result ma_device_start__wasapi(ma_device* pDevice) { + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); + ma_context_state_wasapi* pContextStateWASAPI = ma_context_get_backend_state__wasapi(ma_device_get_context(pDevice)); HRESULT hr; - if (pDevice->pContext->wasapi.hAvrt) { - const char* pTaskName = ma_to_usage_string__wasapi(pDevice->wasapi.usage); - if (pTaskName) { - DWORD idx = 0; - pDevice->wasapi.hAvrtHandle = (ma_handle)((MA_PFN_AvSetMmThreadCharacteristicsA)pDevice->pContext->wasapi.AvSetMmThreadCharacteristicsA)(pTaskName, &idx); + /* + We need to prime the data so we don't get an immediate underrun, and also to ensure we have a + buffer of silence for duplex mode. + */ + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_uint32 bufferSizeInFrames = pDeviceStateWASAPI->actualBufferSizeInFramesPlayback; + ma_uint32 padding; + BYTE* pBufferData; + + hr = ma_IAudioClient_GetCurrentPadding(pDeviceStateWASAPI->pAudioClientPlayback, &padding); + if (FAILED(hr)) { + padding = 0; + } + + bufferSizeInFrames -= padding; + + hr = ma_IAudioRenderClient_GetBuffer(pDeviceStateWASAPI->pRenderClient, bufferSizeInFrames, &pBufferData); + if (SUCCEEDED(hr)) { + ma_IAudioRenderClient_ReleaseBuffer(pDeviceStateWASAPI->pRenderClient, bufferSizeInFrames, MA_AUDCLNT_BUFFERFLAGS_SILENT); } } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + hr = ma_IAudioClient_Start(pDeviceStateWASAPI->pAudioClientCapture); if (FAILED(hr)) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device. HRESULT = %d.", (int)hr); return ma_result_from_HRESULT(hr); } - - ma_atomic_bool32_set(&pDevice->wasapi.isStartedCapture, MA_TRUE); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + hr = ma_IAudioClient_Start(pDeviceStateWASAPI->pAudioClientPlayback); if (FAILED(hr)) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device. HRESULT = %d.", (int)hr); return ma_result_from_HRESULT(hr); } - - ma_atomic_bool32_set(&pDevice->wasapi.isStartedPlayback, MA_TRUE); } return MA_SUCCESS; } -static ma_result ma_device_start__wasapi(ma_device* pDevice) -{ - ma_result result; - - MA_ASSERT(pDevice != NULL); - - /* Wait for any rerouting to finish before attempting to start the device. */ - ma_mutex_lock(&pDevice->wasapi.rerouteLock); - { - result = ma_device_start__wasapi_nolock(pDevice); - } - ma_mutex_unlock(&pDevice->wasapi.rerouteLock); - - return result; -} - -static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) +static ma_result ma_device_stop_client_by_type__wasapi(ma_device* pDevice, ma_device_type deviceType) { + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); + ma_context_state_wasapi* pContextStateWASAPI = ma_context_get_backend_state__wasapi(ma_device_get_context(pDevice)); ma_result result; HRESULT hr; - MA_ASSERT(pDevice != NULL); - - if (pDevice->wasapi.hAvrtHandle) { - ((MA_PFN_AvRevertMmThreadCharacteristics)pDevice->pContext->wasapi.AvRevertMmThreadcharacteristics)((HANDLE)pDevice->wasapi.hAvrtHandle); - pDevice->wasapi.hAvrtHandle = NULL; - } - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - /* If we have a mapped buffer we need to release it. */ - if (pDevice->wasapi.pMappedBufferCapture != NULL) { - ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); - pDevice->wasapi.pMappedBufferCapture = NULL; - pDevice->wasapi.mappedBufferCaptureCap = 0; - pDevice->wasapi.mappedBufferCaptureLen = 0; - } + MA_ASSERT(deviceType != ma_device_type_duplex); + MA_ASSERT(deviceType != ma_device_type_loopback); - hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + if (deviceType == ma_device_type_capture && pDeviceStateWASAPI->pAudioClientCapture != NULL) { + hr = ma_IAudioClient_Stop(pDeviceStateWASAPI->pAudioClientCapture); if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device."); return ma_result_from_HRESULT(hr); } /* The audio client needs to be reset otherwise restarting will fail. */ - hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + hr = ma_IAudioClient_Reset(pDeviceStateWASAPI->pAudioClientCapture); if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device."); return ma_result_from_HRESULT(hr); } - - ma_atomic_bool32_set(&pDevice->wasapi.isStartedCapture, MA_FALSE); } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - if (pDevice->wasapi.pMappedBufferPlayback != NULL) { - ma_silence_pcm_frames( - ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels), - pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen, - pDevice->playback.internalFormat, pDevice->playback.internalChannels - ); - ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); - pDevice->wasapi.pMappedBufferPlayback = NULL; - pDevice->wasapi.mappedBufferPlaybackCap = 0; - pDevice->wasapi.mappedBufferPlaybackLen = 0; - } - + if (deviceType == ma_device_type_playback && pDeviceStateWASAPI->pAudioClientPlayback != NULL) { /* The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played. */ - if (ma_atomic_bool32_get(&pDevice->wasapi.isStartedPlayback)) { - /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ - DWORD waitTime = (pDevice->wasapi.actualBufferSizeInFramesPlayback * 1000) / pDevice->playback.internalSampleRate; - if (pDevice->playback.shareMode == ma_share_mode_exclusive) { - WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); - } else { - ma_uint32 prevFramesAvailablePlayback = (ma_uint32)-1; - ma_uint32 framesAvailablePlayback; - for (;;) { - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); - if (result != MA_SUCCESS) { - break; - } + /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ + DWORD waitTime = (pDeviceStateWASAPI->actualBufferSizeInFramesPlayback * 1000) / pDevice->playback.internalSampleRate; - if (framesAvailablePlayback >= pDevice->wasapi.actualBufferSizeInFramesPlayback) { - break; - } + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { + WaitForSingleObject(pDeviceStateWASAPI->hEventPlayback, waitTime); + } else { + ma_uint32 prevFramesAvailablePlayback = (ma_uint32)-1; + ma_uint32 framesAvailablePlayback; + for (;;) { + result = ma_device__get_available_frames__wasapi(pDevice, pDeviceStateWASAPI->pAudioClientPlayback, &framesAvailablePlayback); + if (result != MA_SUCCESS) { + break; + } - /* - Just a safety check to avoid an infinite loop. If this iteration results in a situation where the number of available frames - has not changed, get out of the loop. I don't think this should ever happen, but I think it's nice to have just in case. - */ - if (framesAvailablePlayback == prevFramesAvailablePlayback) { - break; - } - prevFramesAvailablePlayback = framesAvailablePlayback; + if (framesAvailablePlayback >= pDeviceStateWASAPI->actualBufferSizeInFramesPlayback) { + break; + } - ResetEvent((HANDLE)pDevice->wasapi.hEventPlayback); /* Manual reset. */ - WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, waitTime); + /* + Just a safety check to avoid an infinite loop. If this iteration results in a situation where the number of available frames + has not changed, get out of the loop. I don't think this should ever happen, but I think it's nice to have just in case. + */ + if (framesAvailablePlayback == prevFramesAvailablePlayback) { + break; } + prevFramesAvailablePlayback = framesAvailablePlayback; + + ResetEvent(pDeviceStateWASAPI->hEventPlayback); /* Manual reset. */ + WaitForSingleObject(pDeviceStateWASAPI->hEventPlayback, waitTime); } } - hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + hr = ma_IAudioClient_Stop(pDeviceStateWASAPI->pAudioClientPlayback); if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device."); return ma_result_from_HRESULT(hr); @@ -24688,7 +25455,7 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) { ma_int32 retries = 5; - while ((hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback)) == MA_AUDCLNT_E_BUFFER_OPERATION_PENDING && retries > 0) { + while ((hr = ma_IAudioClient_Reset(pDeviceStateWASAPI->pAudioClientPlayback)) == MA_AUDCLNT_E_BUFFER_OPERATION_PENDING && retries > 0) { ma_sleep(10); retries -= 1; } @@ -24698,8 +25465,6 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device."); return ma_result_from_HRESULT(hr); } - - ma_atomic_bool32_set(&pDevice->wasapi.isStartedPlayback, MA_FALSE); } return MA_SUCCESS; @@ -24707,18 +25472,27 @@ static ma_result ma_device_stop__wasapi_nolock(ma_device* pDevice) static ma_result ma_device_stop__wasapi(ma_device* pDevice) { + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); + ma_context_state_wasapi* pContextStateWASAPI = ma_context_get_backend_state__wasapi(ma_device_get_context(pDevice)); ma_result result; MA_ASSERT(pDevice != NULL); - /* Wait for any rerouting to finish before attempting to stop the device. */ - ma_mutex_lock(&pDevice->wasapi.rerouteLock); - { - result = ma_device_stop__wasapi_nolock(pDevice); + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + result = ma_device_stop_client_by_type__wasapi(pDevice, ma_device_type_capture); + if (result != MA_SUCCESS) { + return result; + } } - ma_mutex_unlock(&pDevice->wasapi.rerouteLock); - return result; + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + result = ma_device_stop_client_by_type__wasapi(pDevice, ma_device_type_playback); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; } @@ -24726,57 +25500,157 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) #define MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS 5000 #endif -static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) +static ma_result ma_device_handle_client_notifications__wasapi(ma_device* pDevice) { - ma_result result = MA_SUCCESS; - ma_uint32 totalFramesProcessed = 0; + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_client_notification_wasapi notification; + + while (ma_device_next_client_notification__wasapi(pDevice, ¬ification) == MA_SUCCESS) { + switch (notification.type) + { + case ma_client_notification_type_wasapi_deactivate: + { + if (ma_device_get_status(pDevice) == ma_device_status_started) { + if (notification.deviceType == ma_device_type_playback) { + pDeviceStateWASAPI->isDetachedPlayback = MA_TRUE; + } else { + pDeviceStateWASAPI->isDetachedCapture = MA_TRUE; + } + + ma_device_stop__wasapi(pDevice); + } + } break; + + case ma_client_notification_type_wasapi_activate: + { + ma_bool32 tryRestartingDevice = MA_FALSE; + + if (notification.deviceType == ma_device_type_playback) { + if (pDeviceStateWASAPI->isDetachedPlayback) { + pDeviceStateWASAPI->isDetachedPlayback = MA_FALSE; + ma_device_reroute__wasapi(pDevice, notification.deviceType); + tryRestartingDevice = MA_TRUE; + } + } else { + if (pDeviceStateWASAPI->isDetachedCapture) { + pDeviceStateWASAPI->isDetachedCapture = MA_FALSE; + ma_device_reroute__wasapi(pDevice, notification.deviceType); + tryRestartingDevice = MA_TRUE; + } + } + + if (tryRestartingDevice) { + if (pDeviceStateWASAPI->isDetachedPlayback == MA_FALSE && pDeviceStateWASAPI->isDetachedCapture == MA_FALSE) { + ma_device_start__wasapi(pDevice); + } + } + } break; + + case ma_client_notification_type_wasapi_default_changed: + { + ma_uint32 previousStatus = ma_device_get_status(pDevice); + ma_bool32 restartDevice = MA_FALSE; + + if (previousStatus == ma_device_status_started) { + ma_device_stop__wasapi(pDevice); + restartDevice = MA_TRUE; + } + + ma_device_reroute__wasapi(pDevice, notification.deviceType); + + if (notification.deviceType == ma_device_type_playback) { + if (pDeviceStateWASAPI->isDetachedPlayback) { + pDeviceStateWASAPI->isDetachedPlayback = MA_FALSE; + + if (deviceType == ma_device_type_duplex && pDeviceStateWASAPI->isDetachedCapture) { + restartDevice = MA_FALSE; /* It's a duplex device and the capture side is detached. We cannot be restarting the device just yet. */ + } + else { + restartDevice = MA_TRUE; /* It's not a duplex device, or the capture side is also attached so we can go ahead and restart the device. */ + } + } + } else { + if (pDeviceStateWASAPI->isDetachedCapture) { + pDeviceStateWASAPI->isDetachedCapture = MA_FALSE; + + if (deviceType == ma_device_type_duplex && pDeviceStateWASAPI->isDetachedPlayback) { + restartDevice = MA_FALSE; /* It's a duplex device and the playback side is detached. We cannot be restarting the device just yet. */ + } + else { + restartDevice = MA_TRUE; /* It's not a duplex device, or the playback side is also attached so we can go ahead and restart the device. */ + } + } + } + + if (restartDevice) { + ma_device_start__wasapi(pDevice); + } + } break; + + case ma_client_notification_type_wasapi_stop: + { + ma_device_stop(pDevice); + } break; + } + } + + return MA_SUCCESS; +} + +static ma_result ma_device_step__wasapi(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + HRESULT hr; + HANDLE hEvents[2]; + DWORD eventCount; + DWORD waitResult; + DWORD timeout = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS : 0; /* - When reading, we need to get a buffer and process all of it before releasing it. Because the - frame count (frameCount) can be different to the size of the buffer, we'll need to cache the - pointer to the buffer. + With loopback mode, a timeout from WaitForMultipleObjects() almost certainly means there is simply + nothing being played through the speakers. We don't want to be stalling for ages in this case, so + for loopback we'll set a timeout of 10ms. */ + if (deviceType == ma_device_type_loopback && blockingMode == MA_BLOCKING_MODE_BLOCKING) { + timeout = 10; + } - /* Keep running until we've processed the requested number of frames. */ - while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) { - ma_uint32 framesRemaining = frameCount - totalFramesProcessed; + eventCount = 0; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + hEvents[eventCount++] = pDeviceStateWASAPI->hEventCapture; + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + hEvents[eventCount++] = pDeviceStateWASAPI->hEventPlayback; + } - /* If we have a mapped data buffer, consume that first. */ - if (pDevice->wasapi.pMappedBufferCapture != NULL) { - /* We have a cached data pointer so consume that before grabbing another one from WASAPI. */ - ma_uint32 framesToProcessNow = framesRemaining; - if (framesToProcessNow > pDevice->wasapi.mappedBufferCaptureLen) { - framesToProcessNow = pDevice->wasapi.mappedBufferCaptureLen; - } + waitResult = WaitForMultipleObjects(eventCount, hEvents, FALSE, timeout); + if (waitResult >= WAIT_OBJECT_0 && waitResult <= WAIT_OBJECT_0 + eventCount-1) { + HANDLE hEvent = hEvents[waitResult - WAIT_OBJECT_0]; - /* Now just copy the data over to the output buffer. */ - ma_copy_pcm_frames( - ma_offset_pcm_frames_ptr(pFrames, totalFramesProcessed, pDevice->capture.internalFormat, pDevice->capture.internalChannels), - ma_offset_pcm_frames_const_ptr(pDevice->wasapi.pMappedBufferCapture, pDevice->wasapi.mappedBufferCaptureCap - pDevice->wasapi.mappedBufferCaptureLen, pDevice->capture.internalFormat, pDevice->capture.internalChannels), - framesToProcessNow, - pDevice->capture.internalFormat, pDevice->capture.internalChannels - ); + /* We may have been woken up due to the device stopping. Check for that. */ + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } - totalFramesProcessed += framesToProcessNow; - pDevice->wasapi.mappedBufferCaptureLen -= framesToProcessNow; + /* Client notifications are where rerouting takes place. */ + result = ma_device_handle_client_notifications__wasapi(pDevice); + if (result != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Reroute failed: %s", ma_result_description(result)); + return result; + } - /* If the data buffer has been fully consumed we need to release it. */ - if (pDevice->wasapi.mappedBufferCaptureLen == 0) { - ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); - pDevice->wasapi.pMappedBufferCapture = NULL; - pDevice->wasapi.mappedBufferCaptureCap = 0; - } - } else { - /* We don't have any cached data pointer, so grab another one. */ - HRESULT hr; + /* Capture. */ + if (hEvent == pDeviceStateWASAPI->hEventCapture) { DWORD flags = 0; + ma_uint32 bufferSizeInFrames; + BYTE* pMappedBuffer; /* First just ask WASAPI for a data buffer. If it's not available, we'll wait for more. */ - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pDevice->wasapi.pMappedBufferCapture, &pDevice->wasapi.mappedBufferCaptureCap, &flags, NULL, NULL); + hr = ma_IAudioCaptureClient_GetBuffer(pDeviceStateWASAPI->pCaptureClient, &pMappedBuffer, &bufferSizeInFrames, &flags, NULL, NULL); if (hr == S_OK) { - /* We got a data buffer. Continue to the next loop iteration which will then read from the mapped pointer. */ - pDevice->wasapi.mappedBufferCaptureLen = pDevice->wasapi.mappedBufferCaptureCap; - /* There have been reports that indicate that at times the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY is reported for every call to IAudioCaptureClient_GetBuffer() above which results in spamming of the debug messages below. To partially @@ -24786,432 +25660,171 @@ static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_ui #if defined(MA_DEBUG_OUTPUT) { if (flags != 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld\n", flags); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld", flags); if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=%d\n", pDevice->wasapi.mappedBufferCaptureCap); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). Attempting recovery. bufferSizeInFrames=%d", bufferSizeInFrames); } } } #endif + ma_device_handle_backend_data_callback(pDevice, NULL, pMappedBuffer, bufferSizeInFrames); + ma_IAudioCaptureClient_ReleaseBuffer(pDeviceStateWASAPI->pCaptureClient, bufferSizeInFrames); + /* Overrun detection. */ if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { - /* Glitched. Probably due to an overrun. */ - /* - If we got an overrun it probably means we're straddling the end of the buffer. In normal capture - mode this is the fault of the client application because they're responsible for ensuring data is - processed fast enough. In duplex mode, however, the processing of audio is tied to the playback - device, so this can possibly be the result of a timing de-sync. - - In capture mode we're not going to do any kind of recovery because the real fix is for the client - application to process faster. In duplex mode, we'll treat this as a desync and reset the buffers - to prevent a never-ending sequence of glitches due to straddling the end of the buffer. + Glitched. We need to drain the buffer, but only if we're in duplex mode. In normal capture mode, + the application is responsible for processing fast enough so we leave it to them. In duplex mode + however, the playback and capture devices should be in lockstep so we'll need to drain to get + the capture device back on track. */ - if (pDevice->type == ma_device_type_duplex) { - /* - Experiment: - - If we empty out the *entire* buffer we may end up putting ourselves into an underrun position - which isn't really any better than the overrun we're probably in right now. Instead we'll just - empty out about half. - */ - ma_uint32 i; - ma_uint32 periodCount = (pDevice->wasapi.actualBufferSizeInFramesCapture / pDevice->wasapi.periodSizeInFramesCapture); - ma_uint32 iterationCount = periodCount / 2; - if ((periodCount % 2) > 0) { - iterationCount += 1; - } + if (deviceType == ma_device_type_duplex) { + for (;;) { + ma_uint32 packetFrames; - for (i = 0; i < iterationCount; i += 1) { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); - if (FAILED(hr)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: IAudioCaptureClient_ReleaseBuffer() failed with %ld.\n", hr); + hr = ma_IAudioCaptureClient_GetNextPacketSize(pDeviceStateWASAPI->pCaptureClient, &packetFrames); + if (FAILED(hr) || packetFrames == 0) { break; } - flags = 0; - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pDevice->wasapi.pMappedBufferCapture, &pDevice->wasapi.mappedBufferCaptureCap, &flags, NULL, NULL); - if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || FAILED(hr)) { - /* - The buffer has been completely emptied or an error occurred. In this case we'll need - to reset the state of the mapped buffer which will trigger the next iteration to get - a fresh buffer from WASAPI. - */ - pDevice->wasapi.pMappedBufferCapture = NULL; - pDevice->wasapi.mappedBufferCaptureCap = 0; - pDevice->wasapi.mappedBufferCaptureLen = 0; - - if (hr == MA_AUDCLNT_S_BUFFER_EMPTY) { - if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: Buffer emptied, and data discontinuity still reported.\n"); - } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: Buffer emptied.\n"); - } - } - - if (FAILED(hr)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity recovery: IAudioCaptureClient_GetBuffer() failed with %ld.\n", hr); - } - + hr = ma_IAudioCaptureClient_GetBuffer(pDeviceStateWASAPI->pCaptureClient, &pMappedBuffer, &bufferSizeInFrames, &flags, NULL, NULL); + if (FAILED(hr)) { break; } - } - /* If at this point we have a valid buffer mapped, make sure the buffer length is set appropriately. */ - if (pDevice->wasapi.pMappedBufferCapture != NULL) { - pDevice->wasapi.mappedBufferCaptureLen = pDevice->wasapi.mappedBufferCaptureCap; + ma_IAudioCaptureClient_ReleaseBuffer(pDeviceStateWASAPI->pCaptureClient, bufferSizeInFrames); } } } - - continue; } else { if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || hr == MA_AUDCLNT_E_BUFFER_ERROR) { - /* - No data is available. We need to wait for more. There's two situations to consider - here. The first is normal capture mode. If this times out it probably means the - microphone isn't delivering data for whatever reason. In this case we'll just - abort the read and return whatever we were able to get. The other situations is - loopback mode, in which case a timeout probably just means the nothing is playing - through the speakers. - */ + /* No data available. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioCaptureClient_GetBuffer() returned %s.", (hr == MA_AUDCLNT_E_BUFFER_ERROR) ? "AUDCLNT_E_BUFFER_ERROR" : "AUDCLNT_E_BUFFER_EMPTY"); + } else if (hr == MA_AUDCLNT_E_DEVICE_INVALIDATED) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WASAPI] Capture device invalidated. Attempting reinitialization."); - /* Experiment: Use a shorter timeout for loopback mode. */ - DWORD timeoutInMilliseconds = MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS; - if (pDevice->type == ma_device_type_loopback) { - timeoutInMilliseconds = 10; - } - - if (WaitForSingleObject((HANDLE)pDevice->wasapi.hEventCapture, timeoutInMilliseconds) != WAIT_OBJECT_0) { - if (pDevice->type == ma_device_type_loopback) { - continue; /* Keep waiting in loopback mode. */ - } else { - result = MA_ERROR; - break; /* Wait failed. */ - } + result = ma_device_reroute__wasapi(pDevice, ma_device_type_capture); + if (result != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Capture device invalidated and reinitialization failed. Stopping device."); + ma_device_set_errored(pDevice); + return result; } - /* At this point we should be able to loop back to the start of the loop and try retrieving a data buffer again. */ + ma_device_start__wasapi(pDevice); } else { /* An error occurred and we need to abort. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.\n", (int)hr); - result = ma_result_from_HRESULT(hr); - break; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.", (int)hr); + return ma_result_from_HRESULT(hr); } } } - } - - /* - If we were unable to process the entire requested frame count, but we still have a mapped buffer, - there's a good chance either an error occurred or the device was stopped mid-read. In this case - we'll need to make sure the buffer is released. - */ - if (totalFramesProcessed < frameCount && pDevice->wasapi.pMappedBufferCapture != NULL) { - ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); - pDevice->wasapi.pMappedBufferCapture = NULL; - pDevice->wasapi.mappedBufferCaptureCap = 0; - pDevice->wasapi.mappedBufferCaptureLen = 0; - } - - if (pFramesRead != NULL) { - *pFramesRead = totalFramesProcessed; - } - - return result; -} -static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) -{ - ma_result result = MA_SUCCESS; - ma_uint32 totalFramesProcessed = 0; - - /* Keep writing to the device until it's stopped or we've consumed all of our input. */ - while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) { - ma_uint32 framesRemaining = frameCount - totalFramesProcessed; - - /* - We're going to do this in a similar way to capture. We'll first check if the cached data pointer - is valid, and if so, read from that. Otherwise We will call IAudioRenderClient_GetBuffer() with - a requested buffer size equal to our actual period size. If it returns AUDCLNT_E_BUFFER_TOO_LARGE - it means we need to wait for some data to become available. - */ - if (pDevice->wasapi.pMappedBufferPlayback != NULL) { - /* We still have some space available in the mapped data buffer. Write to it. */ - ma_uint32 framesToProcessNow = framesRemaining; - if (framesToProcessNow > (pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen)) { - framesToProcessNow = (pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen); - } - - /* Now just copy the data over to the output buffer. */ - ma_copy_pcm_frames( - ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels), - ma_offset_pcm_frames_const_ptr(pFrames, totalFramesProcessed, pDevice->playback.internalFormat, pDevice->playback.internalChannels), - framesToProcessNow, - pDevice->playback.internalFormat, pDevice->playback.internalChannels - ); - - totalFramesProcessed += framesToProcessNow; - pDevice->wasapi.mappedBufferPlaybackLen += framesToProcessNow; - - /* If the data buffer has been fully consumed we need to release it. */ - if (pDevice->wasapi.mappedBufferPlaybackLen == pDevice->wasapi.mappedBufferPlaybackCap) { - ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); - pDevice->wasapi.pMappedBufferPlayback = NULL; - pDevice->wasapi.mappedBufferPlaybackCap = 0; - pDevice->wasapi.mappedBufferPlaybackLen = 0; - - /* - In exclusive mode we need to wait here. Exclusive mode is weird because GetBuffer() never - seems to return AUDCLNT_E_BUFFER_TOO_LARGE, which is what we normally use to determine - whether or not we need to wait for more data. - */ - if (pDevice->playback.shareMode == ma_share_mode_exclusive) { - if (WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - result = MA_ERROR; - break; /* Wait failed. Probably timed out. */ - } - } - } - } else { - /* We don't have a mapped data buffer so we'll need to get one. */ - HRESULT hr; + /* Playback. */ + if (hEvent == pDeviceStateWASAPI->hEventPlayback) { ma_uint32 bufferSizeInFrames; + BYTE* pMappedBuffer; /* Special rules for exclusive mode. */ if (pDevice->playback.shareMode == ma_share_mode_exclusive) { - bufferSizeInFrames = pDevice->wasapi.actualBufferSizeInFramesPlayback; + bufferSizeInFrames = pDeviceStateWASAPI->actualBufferSizeInFramesPlayback; } else { - bufferSizeInFrames = pDevice->wasapi.periodSizeInFramesPlayback; + bufferSizeInFrames = pDeviceStateWASAPI->periodSizeInFramesPlayback; } - hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, bufferSizeInFrames, (BYTE**)&pDevice->wasapi.pMappedBufferPlayback); + hr = ma_IAudioRenderClient_GetBuffer(pDeviceStateWASAPI->pRenderClient, bufferSizeInFrames, &pMappedBuffer); if (hr == S_OK) { /* We have data available. */ - pDevice->wasapi.mappedBufferPlaybackCap = bufferSizeInFrames; - pDevice->wasapi.mappedBufferPlaybackLen = 0; + ma_device_handle_backend_data_callback(pDevice, pMappedBuffer, NULL, bufferSizeInFrames); + ma_IAudioRenderClient_ReleaseBuffer(pDeviceStateWASAPI->pRenderClient, bufferSizeInFrames, 0); } else { if (hr == MA_AUDCLNT_E_BUFFER_TOO_LARGE || hr == MA_AUDCLNT_E_BUFFER_ERROR) { - /* Not enough data available. We need to wait for more. */ - if (WaitForSingleObject((HANDLE)pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - result = MA_ERROR; - break; /* Wait failed. Probably timed out. */ + /* Not enough data available. Nothing to do. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioRenderClient_GetBuffer() returned %s.", (hr == MA_AUDCLNT_E_BUFFER_ERROR) ? "AUDCLNT_E_BUFFER_ERROR" : "AUDCLNT_E_BUFFER_TOO_LARGE"); + } else if (hr == MA_AUDCLNT_E_DEVICE_INVALIDATED) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WASAPI] Playback device invalidated. Attempting reinitialization."); + + result = ma_device_reroute__wasapi(pDevice, ma_device_type_playback); + if (result != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Playback device invalidated and reinitialization failed. Stopping device."); + ma_device_set_errored(pDevice); + return result; } + + ma_device_start__wasapi(pDevice); } else { /* Some error occurred. We'll need to abort. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device. HRESULT = %d. Stopping device.\n", (int)hr); - result = ma_result_from_HRESULT(hr); - break; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device. HRESULT = %d. Stopping device.", (int)hr); + return ma_result_from_HRESULT(hr); } } } - } - - if (pFramesWritten != NULL) { - *pFramesWritten = totalFramesProcessed; - } - - return result; -} - -static ma_result ma_device_data_loop_wakeup__wasapi(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - SetEvent((HANDLE)pDevice->wasapi.hEventCapture); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - SetEvent((HANDLE)pDevice->wasapi.hEventPlayback); + } else { + /* Most likely timed out. */ } return MA_SUCCESS; } - -static ma_result ma_context_uninit__wasapi(ma_context* pContext) +static void ma_device_wakeup__wasapi(ma_device* pDevice) { - ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_QUIT__WASAPI); - - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_wasapi); + ma_device_state_wasapi* pDeviceStateWASAPI = ma_device_get_backend_state__wasapi(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); - ma_context_post_command__wasapi(pContext, &cmd); - ma_thread_wait(&pContext->wasapi.commandThread); + MA_ASSERT(pDevice != NULL); - if (pContext->wasapi.hAvrt) { - ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hAvrt); - pContext->wasapi.hAvrt = NULL; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + SetEvent(pDeviceStateWASAPI->hEventCapture); } - #if defined(MA_WIN32_UWP) - { - if (pContext->wasapi.hMMDevapi) { - ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hMMDevapi); - pContext->wasapi.hMMDevapi = NULL; - } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + SetEvent(pDeviceStateWASAPI->hEventPlayback); } - #endif - - /* Only after the thread has been terminated can we uninitialize the sync objects for the command thread. */ - ma_semaphore_uninit(&pContext->wasapi.commandSem); - ma_mutex_uninit(&pContext->wasapi.commandLock); - - return MA_SUCCESS; } -static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static ma_device_backend_vtable ma_gDeviceBackendVTable_WASAPI = { - ma_result result = MA_SUCCESS; - - MA_ASSERT(pContext != NULL); - - (void)pConfig; - -#ifdef MA_WIN32_DESKTOP - /* - WASAPI is only supported in Vista SP1 and newer. The reason for SP1 and not the base version of Vista is that event-driven - exclusive mode does not work until SP1. - - Unfortunately older compilers don't define these functions so we need to dynamically load them in order to avoid a link error. - */ - { - ma_OSVERSIONINFOEXW osvi; - ma_handle kernel32DLL; - ma_PFNVerifyVersionInfoW _VerifyVersionInfoW; - ma_PFNVerSetConditionMask _VerSetConditionMask; - - kernel32DLL = ma_dlopen(ma_context_get_log(pContext), "kernel32.dll"); - if (kernel32DLL == NULL) { - return MA_NO_BACKEND; - } - - _VerifyVersionInfoW = (ma_PFNVerifyVersionInfoW )ma_dlsym(ma_context_get_log(pContext), kernel32DLL, "VerifyVersionInfoW"); - _VerSetConditionMask = (ma_PFNVerSetConditionMask)ma_dlsym(ma_context_get_log(pContext), kernel32DLL, "VerSetConditionMask"); - if (_VerifyVersionInfoW == NULL || _VerSetConditionMask == NULL) { - ma_dlclose(ma_context_get_log(pContext), kernel32DLL); - return MA_NO_BACKEND; - } - - MA_ZERO_OBJECT(&osvi); - osvi.dwOSVersionInfoSize = sizeof(osvi); - osvi.dwMajorVersion = ((MA_WIN32_WINNT_VISTA >> 8) & 0xFF); - osvi.dwMinorVersion = ((MA_WIN32_WINNT_VISTA >> 0) & 0xFF); - osvi.wServicePackMajor = 1; - if (_VerifyVersionInfoW(&osvi, MA_VER_MAJORVERSION | MA_VER_MINORVERSION | MA_VER_SERVICEPACKMAJOR, _VerSetConditionMask(_VerSetConditionMask(_VerSetConditionMask(0, MA_VER_MAJORVERSION, MA_VER_GREATER_EQUAL), MA_VER_MINORVERSION, MA_VER_GREATER_EQUAL), MA_VER_SERVICEPACKMAJOR, MA_VER_GREATER_EQUAL))) { - result = MA_SUCCESS; - } else { - result = MA_NO_BACKEND; - } + ma_backend_info__wasapi, + ma_context_init__wasapi, + ma_context_uninit__wasapi, + ma_context_enumerate_devices__wasapi, + ma_device_init__wasapi, + ma_device_uninit__wasapi, + ma_device_start__wasapi, + ma_device_stop__wasapi, + ma_device_step__wasapi, + ma_device_wakeup__wasapi +}; - ma_dlclose(ma_context_get_log(pContext), kernel32DLL); - } +ma_device_backend_vtable* ma_device_backend_wasapi = &ma_gDeviceBackendVTable_WASAPI; +#else +ma_device_backend_vtable* ma_device_backend_wasapi = NULL; #endif - if (result != MA_SUCCESS) { - return result; - } - - MA_ZERO_OBJECT(&pContext->wasapi); - - - #if defined(MA_WIN32_UWP) - { - /* Link to mmdevapi so we can get access to ActivateAudioInterfaceAsync(). */ - pContext->wasapi.hMMDevapi = ma_dlopen(ma_context_get_log(pContext), "mmdevapi.dll"); - if (pContext->wasapi.hMMDevapi) { - pContext->wasapi.ActivateAudioInterfaceAsync = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hMMDevapi, "ActivateAudioInterfaceAsync"); - if (pContext->wasapi.ActivateAudioInterfaceAsync == NULL) { - ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hMMDevapi); - return MA_NO_BACKEND; /* ActivateAudioInterfaceAsync() could not be loaded. */ - } - } else { - return MA_NO_BACKEND; /* Failed to load mmdevapi.dll which is required for ActivateAudioInterfaceAsync() */ - } - } - #endif - - /* Optionally use the Avrt API to specify the audio thread's latency sensitivity requirements */ - pContext->wasapi.hAvrt = ma_dlopen(ma_context_get_log(pContext), "avrt.dll"); - if (pContext->wasapi.hAvrt) { - pContext->wasapi.AvSetMmThreadCharacteristicsA = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hAvrt, "AvSetMmThreadCharacteristicsA"); - pContext->wasapi.AvRevertMmThreadcharacteristics = ma_dlsym(ma_context_get_log(pContext), pContext->wasapi.hAvrt, "AvRevertMmThreadCharacteristics"); - - /* If either function could not be found, disable use of avrt entirely. */ - if (!pContext->wasapi.AvSetMmThreadCharacteristicsA || !pContext->wasapi.AvRevertMmThreadcharacteristics) { - pContext->wasapi.AvSetMmThreadCharacteristicsA = NULL; - pContext->wasapi.AvRevertMmThreadcharacteristics = NULL; - ma_dlclose(ma_context_get_log(pContext), pContext->wasapi.hAvrt); - pContext->wasapi.hAvrt = NULL; - } - } - - - /* - Annoyingly, WASAPI does not allow you to release an IAudioClient object from a different thread - than the one that retrieved it with GetService(). This can result in a deadlock in two - situations: - - 1) When calling ma_device_uninit() from a different thread to ma_device_init(); and - 2) When uninitializing and reinitializing the internal IAudioClient object in response to - automatic stream routing. - - We could define ma_device_uninit() such that it must be called on the same thread as - ma_device_init(). We could also just not release the IAudioClient when performing automatic - stream routing to avoid the deadlock. Neither of these are acceptable solutions in my view so - we're going to have to work around this with a worker thread. This is not ideal, but I can't - think of a better way to do this. - - More information about this can be found here: - - https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nn-audioclient-iaudiorenderclient - - Note this section: +MA_API ma_device_backend_vtable* ma_wasapi_get_vtable(void) +{ + return ma_device_backend_wasapi; +} - When releasing an IAudioRenderClient interface instance, the client must call the interface's - Release method from the same thread as the call to IAudioClient::GetService that created the - object. - */ - { - result = ma_mutex_init(&pContext->wasapi.commandLock); - if (result != MA_SUCCESS) { - return result; - } +MA_API ma_context_config_wasapi ma_context_config_wasapi_init(void) +{ + ma_context_config_wasapi config; - result = ma_semaphore_init(0, &pContext->wasapi.commandSem); - if (result != MA_SUCCESS) { - ma_mutex_uninit(&pContext->wasapi.commandLock); - return result; - } + MA_ZERO_OBJECT(&config); - result = ma_thread_create(&pContext->wasapi.commandThread, ma_thread_priority_normal, 0, ma_context_command_thread__wasapi, pContext, &pContext->allocationCallbacks); - if (result != MA_SUCCESS) { - ma_semaphore_uninit(&pContext->wasapi.commandSem); - ma_mutex_uninit(&pContext->wasapi.commandLock); - return result; - } - } + return config; +} +MA_API ma_device_config_wasapi ma_device_config_wasapi_init(void) +{ + ma_device_config_wasapi config; - pCallbacks->onContextInit = ma_context_init__wasapi; - pCallbacks->onContextUninit = ma_context_uninit__wasapi; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__wasapi; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__wasapi; - pCallbacks->onDeviceInit = ma_device_init__wasapi; - pCallbacks->onDeviceUninit = ma_device_uninit__wasapi; - pCallbacks->onDeviceStart = ma_device_start__wasapi; - pCallbacks->onDeviceStop = ma_device_stop__wasapi; - pCallbacks->onDeviceRead = ma_device_read__wasapi; - pCallbacks->onDeviceWrite = ma_device_write__wasapi; - pCallbacks->onDeviceDataLoop = NULL; - pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__wasapi; + MA_ZERO_OBJECT(&config); - return MA_SUCCESS; + return config; } -#endif + /****************************************************************************** @@ -25541,11 +26154,132 @@ static MA_INLINE ULONG ma_IDirectSoundNotify_Release(ma_IDirectSoundNotify* pT static MA_INLINE HRESULT ma_IDirectSoundNotify_SetNotificationPositions(ma_IDirectSoundNotify* pThis, DWORD dwPositionNotifies, const MA_DSBPOSITIONNOTIFY* pPositionNotifies) { return pThis->lpVtbl->SetNotificationPositions(pThis, dwPositionNotifies, pPositionNotifies); } -typedef BOOL (CALLBACK * ma_DSEnumCallbackAProc) (GUID* pDeviceGUID, const char* pDeviceDescription, const char* pModule, void* pContext); -typedef HRESULT (WINAPI * ma_DirectSoundCreateProc) (const GUID* pcGuidDevice, ma_IDirectSound** ppDS8, ma_IUnknown* pUnkOuter); -typedef HRESULT (WINAPI * ma_DirectSoundEnumerateAProc) (ma_DSEnumCallbackAProc pDSEnumCallback, void* pContext); -typedef HRESULT (WINAPI * ma_DirectSoundCaptureCreateProc) (const GUID* pcGuidDevice, ma_IDirectSoundCapture** ppDSC8, ma_IUnknown* pUnkOuter); -typedef HRESULT (WINAPI * ma_DirectSoundCaptureEnumerateAProc)(ma_DSEnumCallbackAProc pDSEnumCallback, void* pContext); +typedef BOOL (CALLBACK * ma_DSEnumCallbackAProc)(GUID* pDeviceGUID, const char* pDeviceDescription, const char* pModule, void* pContext); + +typedef HRESULT (WINAPI * ma_DirectSoundCreateProc )(const GUID* pcGuidDevice, ma_IDirectSound** ppDS8, ma_IUnknown* pUnkOuter); +typedef HRESULT (WINAPI * ma_DirectSoundEnumerateAProc )(ma_DSEnumCallbackAProc pDSEnumCallback, void* pContext); +typedef HRESULT (WINAPI * ma_DirectSoundCaptureCreateProc )(const GUID* pcGuidDevice, ma_IDirectSoundCapture** ppDSC8, ma_IUnknown* pUnkOuter); +typedef HRESULT (WINAPI * ma_DirectSoundCaptureEnumerateAProc)(ma_DSEnumCallbackAProc pDSEnumCallback, void* pContext); + +typedef HWND (WINAPI * ma_GetForegroundWindowProc)(void); +typedef HWND (WINAPI * ma_GetDesktopWindowProc )(void); + +typedef struct ma_context_state_dsound +{ + HWND hWnd; /* Can be null. */ + ma_handle hDSoundDLL; + ma_DirectSoundCreateProc DirectSoundCreate; + ma_DirectSoundEnumerateAProc DirectSoundEnumerateA; + ma_DirectSoundCaptureCreateProc DirectSoundCaptureCreate; + ma_DirectSoundCaptureEnumerateAProc DirectSoundCaptureEnumerateA; + + ma_handle hUser32DLL; + ma_GetForegroundWindowProc GetForegroundWindow; + ma_GetDesktopWindowProc GetDesktopWindow; +} ma_context_state_dsound; + +typedef struct ma_device_state_dsound +{ + ma_IDirectSound* pPlayback; + ma_IDirectSoundBuffer* pPlaybackPrimaryBuffer; + ma_IDirectSoundBuffer* pPlaybackBuffer; + ma_IDirectSoundCapture* pCapture; + ma_IDirectSoundCaptureBuffer* pCaptureBuffer; + ma_bool32 isPlaybackDeviceStarted; + DWORD prevReadCursorInBytesCapture; + DWORD prevPlayCursorInBytesPlayback; + ma_bool32 physicalPlayCursorLoopFlagPlayback; + DWORD virtualWriteCursorInBytesPlayback; + ma_bool32 virtualWriteCursorLoopFlagPlayback; + ma_uint32 framesWrittenToPlaybackDevice; /* For knowing whether or not the playback device needs to be started. */ +} ma_device_state_dsound; + + +static ma_context_state_dsound* ma_context_get_backend_state__dsound(ma_context* pContext) +{ + return (ma_context_state_dsound*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_dsound* ma_device_get_backend_state__dsound(ma_device* pDevice) +{ + return (ma_device_state_dsound*)ma_device_get_backend_state(pDevice); +} + + +static void ma_backend_info__dsound(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "DirectSound"; +} + +static ma_result ma_context_init__dsound(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_dsound* pContextStateDSound; + const ma_context_config_dsound* pContextConfigDSound = (const ma_context_config_dsound*)pContextBackendConfig; + ma_context_config_dsound defaultConfigDSound; + + if (pContextConfigDSound == NULL) { + defaultConfigDSound = ma_context_config_dsound_init(); + pContextConfigDSound = &defaultConfigDSound; + } + + pContextStateDSound = (ma_context_state_dsound*)ma_calloc(sizeof(*pContextStateDSound), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateDSound == NULL) { + return MA_OUT_OF_MEMORY; + } + + pContextStateDSound->hDSoundDLL = ma_dlopen(ma_context_get_log(pContext), "dsound.dll"); + if (pContextStateDSound->hDSoundDLL == NULL) { + ma_free(pContextStateDSound, ma_context_get_allocation_callbacks(pContext)); + return MA_API_NOT_FOUND; + } + + pContextStateDSound->DirectSoundCreate = (ma_DirectSoundCreateProc )ma_dlsym(ma_context_get_log(pContext), pContextStateDSound->hDSoundDLL, "DirectSoundCreate"); + pContextStateDSound->DirectSoundEnumerateA = (ma_DirectSoundEnumerateAProc )ma_dlsym(ma_context_get_log(pContext), pContextStateDSound->hDSoundDLL, "DirectSoundEnumerateA"); + pContextStateDSound->DirectSoundCaptureCreate = (ma_DirectSoundCaptureCreateProc )ma_dlsym(ma_context_get_log(pContext), pContextStateDSound->hDSoundDLL, "DirectSoundCaptureCreate"); + pContextStateDSound->DirectSoundCaptureEnumerateA = (ma_DirectSoundCaptureEnumerateAProc)ma_dlsym(ma_context_get_log(pContext), pContextStateDSound->hDSoundDLL, "DirectSoundCaptureEnumerateA"); + + /* + We need to support all functions or nothing. DirectSound with Windows 95 seems to not work too + well in my testing. For example, it's missing DirectSoundCaptureEnumerateA(). This is a convenient + place to just disable the DirectSound backend for Windows 95. + */ + if (pContextStateDSound->DirectSoundCreate == NULL || + pContextStateDSound->DirectSoundEnumerateA == NULL || + pContextStateDSound->DirectSoundCaptureCreate == NULL || + pContextStateDSound->DirectSoundCaptureEnumerateA == NULL) { + ma_free(pContextStateDSound, ma_context_get_allocation_callbacks(pContext)); + return MA_API_NOT_FOUND; + } + + /* User32.dll */ + pContextStateDSound->hUser32DLL = ma_dlopen(ma_context_get_log(pContext), "user32.dll"); + if (pContextStateDSound->hUser32DLL == NULL) { + ma_dlclose(ma_context_get_log(pContext), pContextStateDSound->hDSoundDLL); + return MA_API_NOT_FOUND; + } + + pContextStateDSound->GetForegroundWindow = (ma_GetForegroundWindowProc)ma_dlsym(ma_context_get_log(pContext), pContextStateDSound->hUser32DLL, "GetForegroundWindow"); + pContextStateDSound->GetDesktopWindow = (ma_GetDesktopWindowProc )ma_dlsym(ma_context_get_log(pContext), pContextStateDSound->hUser32DLL, "GetDesktopWindow"); + + pContextStateDSound->hWnd = (HWND)pContextConfigDSound->hWnd; + + *ppContextState = pContextStateDSound; + + return MA_SUCCESS; +} + +static void ma_context_uninit__dsound(ma_context* pContext) +{ + ma_context_state_dsound* pContextStateDSound = ma_context_get_backend_state__dsound(pContext); + + ma_dlclose(ma_context_get_log(pContext), pContextStateDSound->hUser32DLL); + ma_dlclose(ma_context_get_log(pContext), pContextStateDSound->hDSoundDLL); + + ma_free(pContextStateDSound, ma_context_get_allocation_callbacks(pContext)); +} + + static ma_uint32 ma_get_best_sample_rate_within_range(ma_uint32 sampleRateMin, ma_uint32 sampleRateMax) { @@ -25564,8 +26298,8 @@ static ma_uint32 ma_get_best_sample_rate_within_range(ma_uint32 sampleRateMin, m return sampleRateMax; } else { size_t iStandardRate; - for (iStandardRate = 0; iStandardRate < ma_countof(g_maStandardSampleRatePriorities); ++iStandardRate) { - ma_uint32 standardRate = g_maStandardSampleRatePriorities[iStandardRate]; + for (iStandardRate = 0; iStandardRate < ma_countof(ma_standard_sample_rates); ++iStandardRate) { + ma_uint32 standardRate = ma_standard_sample_rates[iStandardRate]; if (standardRate >= sampleRateMin && standardRate <= sampleRateMax) { return standardRate; } @@ -25625,6 +26359,7 @@ static void ma_get_channels_from_speaker_config__dsound(DWORD speakerConfig, WOR static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma_share_mode shareMode, const ma_device_id* pDeviceID, ma_IDirectSound** ppDirectSound) { + ma_context_state_dsound* pContextStateDSound = ma_context_get_backend_state__dsound(pContext); ma_IDirectSound* pDirectSound; HWND hWnd; HRESULT hr; @@ -25635,17 +26370,17 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma *ppDirectSound = NULL; pDirectSound = NULL; - if (FAILED(((ma_DirectSoundCreateProc)pContext->dsound.DirectSoundCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSound, NULL))) { + if (FAILED(pContextStateDSound->DirectSoundCreate((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSound, NULL))) { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device."); return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } /* The cooperative level must be set before doing anything else. */ - hWnd = (HWND)pContext->dsound.hWnd; + hWnd = pContextStateDSound->hWnd; if (hWnd == 0) { - hWnd = ((MA_PFN_GetForegroundWindow)pContext->win32.GetForegroundWindow)(); + hWnd = ((ma_GetForegroundWindowProc)pContextStateDSound->GetForegroundWindow)(); if (hWnd == 0) { - hWnd = ((MA_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); + hWnd = ((ma_GetDesktopWindowProc)pContextStateDSound->GetDesktopWindow)(); } } @@ -25661,6 +26396,7 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma static ma_result ma_context_create_IDirectSoundCapture__dsound(ma_context* pContext, ma_share_mode shareMode, const ma_device_id* pDeviceID, ma_IDirectSoundCapture** ppDirectSoundCapture) { + ma_context_state_dsound* pContextStateDSound = ma_context_get_backend_state__dsound(pContext); ma_IDirectSoundCapture* pDirectSoundCapture; HRESULT hr; @@ -25675,7 +26411,7 @@ static ma_result ma_context_create_IDirectSoundCapture__dsound(ma_context* pCont *ppDirectSoundCapture = NULL; pDirectSoundCapture = NULL; - hr = ((ma_DirectSoundCaptureCreateProc)pContext->dsound.DirectSoundCaptureCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSoundCapture, NULL); + hr = pContextStateDSound->DirectSoundCaptureCreate((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSoundCapture, NULL); if (FAILED(hr)) { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device."); return ma_result_from_HRESULT(hr); @@ -25801,12 +26537,17 @@ static BOOL CALLBACK ma_context_enumerate_devices_callback__dsound(GUID* lpGuid, { ma_context_enumerate_devices_callback_data__dsound* pData = (ma_context_enumerate_devices_callback_data__dsound*)lpContext; ma_device_info deviceInfo; + ma_result result; + HRESULT hr; + ma_uint32 iFormat; + ma_uint32 minSampleRate = 0xFFFFFFFF; + ma_uint32 maxSampleRate = 0; (void)lpcstrModule; MA_ZERO_OBJECT(&deviceInfo); - /* ID. */ + /* ID and Default. */ if (lpGuid != NULL) { MA_COPY_MEMORY(deviceInfo.id.dsound, lpGuid, 16); } else { @@ -25814,139 +26555,27 @@ static BOOL CALLBACK ma_context_enumerate_devices_callback__dsound(GUID* lpGuid, deviceInfo.isDefault = MA_TRUE; } - /* Name / Description */ + /* Name. */ ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), lpcstrDescription, (size_t)-1); - - /* Call the callback function, but make sure we stop enumerating if the callee requested so. */ - MA_ASSERT(pData != NULL); - pData->terminated = (pData->callback(pData->pContext, pData->deviceType, &deviceInfo, pData->pUserData) == MA_FALSE); - if (pData->terminated) { - return FALSE; /* Stop enumeration. */ - } else { - return TRUE; /* Continue enumeration. */ - } -} - -static ma_result ma_context_enumerate_devices__dsound(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_context_enumerate_devices_callback_data__dsound data; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); - - data.pContext = pContext; - data.callback = callback; - data.pUserData = pUserData; - data.terminated = MA_FALSE; - - /* Playback. */ - if (!data.terminated) { - data.deviceType = ma_device_type_playback; - ((ma_DirectSoundEnumerateAProc)pContext->dsound.DirectSoundEnumerateA)(ma_context_enumerate_devices_callback__dsound, &data); - } - - /* Capture. */ - if (!data.terminated) { - data.deviceType = ma_device_type_capture; - ((ma_DirectSoundCaptureEnumerateAProc)pContext->dsound.DirectSoundCaptureEnumerateA)(ma_context_enumerate_devices_callback__dsound, &data); - } - - return MA_SUCCESS; -} - - -typedef struct -{ - const ma_device_id* pDeviceID; - ma_device_info* pDeviceInfo; - ma_bool32 found; -} ma_context_get_device_info_callback_data__dsound; - -static BOOL CALLBACK ma_context_get_device_info_callback__dsound(GUID* lpGuid, const char* lpcstrDescription, const char* lpcstrModule, void* lpContext) -{ - ma_context_get_device_info_callback_data__dsound* pData = (ma_context_get_device_info_callback_data__dsound*)lpContext; - MA_ASSERT(pData != NULL); - - if ((pData->pDeviceID == NULL || ma_is_guid_null(pData->pDeviceID->dsound)) && (lpGuid == NULL || ma_is_guid_null(lpGuid))) { - /* Default device. */ - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); - pData->pDeviceInfo->isDefault = MA_TRUE; - pData->found = MA_TRUE; - return FALSE; /* Stop enumeration. */ - } else { - /* Not the default device. */ - if (lpGuid != NULL && pData->pDeviceID != NULL) { - if (memcmp(pData->pDeviceID->dsound, lpGuid, sizeof(pData->pDeviceID->dsound)) == 0) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); - pData->found = MA_TRUE; - return FALSE; /* Stop enumeration. */ - } - } - } - - (void)lpcstrModule; - return TRUE; -} - -static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) -{ - ma_result result; - HRESULT hr; - - if (pDeviceID != NULL) { - ma_context_get_device_info_callback_data__dsound data; - - /* ID. */ - MA_COPY_MEMORY(pDeviceInfo->id.dsound, pDeviceID->dsound, 16); - - /* Name / Description. This is retrieved by enumerating over each device until we find that one that matches the input ID. */ - data.pDeviceID = pDeviceID; - data.pDeviceInfo = pDeviceInfo; - data.found = MA_FALSE; - if (deviceType == ma_device_type_playback) { - ((ma_DirectSoundEnumerateAProc)pContext->dsound.DirectSoundEnumerateA)(ma_context_get_device_info_callback__dsound, &data); - } else { - ((ma_DirectSoundCaptureEnumerateAProc)pContext->dsound.DirectSoundCaptureEnumerateA)(ma_context_get_device_info_callback__dsound, &data); - } - - if (!data.found) { - return MA_NO_DEVICE; - } - } else { - /* I don't think there's a way to get the name of the default device with DirectSound. In this case we just need to use defaults. */ - - /* ID */ - MA_ZERO_MEMORY(pDeviceInfo->id.dsound, 16); - - /* Name / Description */ - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } - - pDeviceInfo->isDefault = MA_TRUE; - } - - /* Retrieving detailed information is slightly different depending on the device type. */ - if (deviceType == ma_device_type_playback) { + /* Data Format. */ + if (pData->deviceType == ma_device_type_playback) { /* Playback. */ ma_IDirectSound* pDirectSound; MA_DSCAPS caps; WORD channels; - result = ma_context_create_IDirectSound__dsound(pContext, ma_share_mode_shared, pDeviceID, &pDirectSound); + result = ma_context_create_IDirectSound__dsound(pData->pContext, ma_share_mode_shared, &deviceInfo.id, &pDirectSound); if (result != MA_SUCCESS) { - return result; + return MA_TRUE; } MA_ZERO_OBJECT(&caps); caps.dwSize = sizeof(caps); hr = ma_IDirectSound_GetCaps(pDirectSound, &caps); if (FAILED(hr)) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); - return ma_result_from_HRESULT(hr); + ma_log_postf(ma_context_get_log(pData->pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); + return MA_TRUE; } @@ -25974,100 +26603,105 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev in order to keep the size of this within reason. */ if ((caps.dwFlags & MA_DSCAPS_CONTINUOUSRATE) != 0) { - /* Multiple sample rates are supported. We'll report in order of our preferred sample rates. */ - size_t iStandardSampleRate; - for (iStandardSampleRate = 0; iStandardSampleRate < ma_countof(g_maStandardSampleRatePriorities); iStandardSampleRate += 1) { - ma_uint32 sampleRate = g_maStandardSampleRatePriorities[iStandardSampleRate]; - if (sampleRate >= caps.dwMinSecondarySampleRate && sampleRate <= caps.dwMaxSecondarySampleRate) { - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = ma_format_unknown; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; - pDeviceInfo->nativeDataFormatCount += 1; - } - } + /* A range of sample rates are supported. */ + minSampleRate = caps.dwMinSecondarySampleRate; + maxSampleRate = caps.dwMaxSecondarySampleRate; } else { /* Only a single sample rate is supported. */ - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = ma_format_unknown; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = caps.dwMaxSecondarySampleRate; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; - pDeviceInfo->nativeDataFormatCount += 1; + minSampleRate = maxSampleRate = caps.dwMaxSecondarySampleRate; + } + + /* DirectSound will have the minimum sample rate set to something stupidly small like 100. We're going to clamp it. */ + minSampleRate = ma_clamp(minSampleRate, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + maxSampleRate = ma_clamp(maxSampleRate, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + + /* All formats are supported. */ + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) { + ma_device_info_add_native_data_format(&deviceInfo, g_maFormatPriorities[iFormat], channels, channels, minSampleRate, maxSampleRate); } ma_IDirectSound_Release(pDirectSound); } else { /* - Capture. This is a little different to playback due to the say the supported formats are reported. Technically capture + Capture. This is a little different to playback due to the way the supported formats are reported. Technically capture devices can support a number of different formats, but for simplicity and consistency with ma_device_init() I'm just reporting the best format. */ ma_IDirectSoundCapture* pDirectSoundCapture; WORD channels; WORD bitsPerSample; + ma_format format; DWORD sampleRate; - result = ma_context_create_IDirectSoundCapture__dsound(pContext, ma_share_mode_shared, pDeviceID, &pDirectSoundCapture); + result = ma_context_create_IDirectSoundCapture__dsound(pData->pContext, ma_share_mode_shared, &deviceInfo.id, &pDirectSoundCapture); if (result != MA_SUCCESS) { - return result; + return MA_TRUE; } - result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(pContext, pDirectSoundCapture, &channels, &bitsPerSample, &sampleRate); + result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(pData->pContext, pDirectSoundCapture, &channels, &bitsPerSample, &sampleRate); if (result != MA_SUCCESS) { ma_IDirectSoundCapture_Release(pDirectSoundCapture); - return result; + return MA_TRUE; } ma_IDirectSoundCapture_Release(pDirectSoundCapture); /* The format is always an integer format and is based on the bits per sample. */ if (bitsPerSample == 8) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_u8; + format = ma_format_u8; } else if (bitsPerSample == 16) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; + format = ma_format_s16; } else if (bitsPerSample == 24) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_s24; + format = ma_format_s24; } else if (bitsPerSample == 32) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_s32; + format = ma_format_s32; } else { - return MA_FORMAT_NOT_SUPPORTED; + format = ma_format_unknown; } - pDeviceInfo->nativeDataFormats[0].channels = channels; - pDeviceInfo->nativeDataFormats[0].sampleRate = sampleRate; - pDeviceInfo->nativeDataFormats[0].flags = 0; - pDeviceInfo->nativeDataFormatCount = 1; + ma_device_info_add_native_data_format(&deviceInfo, format, channels, channels, sampleRate, sampleRate); } - return MA_SUCCESS; + + /* Call the callback function, but make sure we stop enumerating if the callee requested so. */ + MA_ASSERT(pData != NULL); + pData->terminated = (pData->callback(pData->deviceType, &deviceInfo, pData->pUserData) == MA_DEVICE_ENUMERATION_ABORT); + if (pData->terminated) { + return FALSE; /* Stop enumeration. */ + } else { + return TRUE; /* Continue enumeration. */ + } } +static ma_result ma_context_enumerate_devices__dsound(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_state_dsound* pContextStateDSound = ma_context_get_backend_state__dsound(pContext); + ma_context_enumerate_devices_callback_data__dsound data; + MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); -static ma_result ma_device_uninit__dsound(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + data.pContext = pContext; + data.callback = callback; + data.pUserData = pUserData; + data.terminated = MA_FALSE; - if (pDevice->dsound.pCaptureBuffer != NULL) { - ma_IDirectSoundCaptureBuffer_Release((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - } - if (pDevice->dsound.pCapture != NULL) { - ma_IDirectSoundCapture_Release((ma_IDirectSoundCapture*)pDevice->dsound.pCapture); + /* Playback. */ + if (!data.terminated) { + data.deviceType = ma_device_type_playback; + pContextStateDSound->DirectSoundEnumerateA(ma_context_enumerate_devices_callback__dsound, &data); } - if (pDevice->dsound.pPlaybackBuffer != NULL) { - ma_IDirectSoundBuffer_Release((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer); - } - if (pDevice->dsound.pPlaybackPrimaryBuffer != NULL) { - ma_IDirectSoundBuffer_Release((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer); - } - if (pDevice->dsound.pPlayback != NULL) { - ma_IDirectSound_Release((ma_IDirectSound*)pDevice->dsound.pPlayback); + /* Capture. */ + if (!data.terminated) { + data.deviceType = ma_device_type_capture; + pContextStateDSound->DirectSoundCaptureEnumerateA(ma_context_enumerate_devices_callback__dsound, &data); } return MA_SUCCESS; } + static ma_result ma_config_to_WAVEFORMATEXTENSIBLE(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const ma_channel* pChannelMap, MA_WAVEFORMATEXTENSIBLE* pWF) { GUID subformat; @@ -26119,7 +26753,7 @@ static ma_result ma_config_to_WAVEFORMATEXTENSIBLE(ma_format format, ma_uint32 c return MA_SUCCESS; } -static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__dsound(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__dsound(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate) { /* DirectSound has a minimum period size of 20ms. In practice, this doesn't seem to be enough for @@ -26128,7 +26762,7 @@ static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__dsound(cons ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(30, nativeSampleRate); ma_uint32 periodSizeInFrames; - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate, performanceProfile); + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate); if (periodSizeInFrames < minPeriodSizeInFrames) { periodSizeInFrames = minPeriodSizeInFrames; } @@ -26136,25 +26770,37 @@ static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__dsound(cons return periodSizeInFrames; } -static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static void ma_device_uninit__dsound(ma_device* pDevice); + +static ma_result ma_device_init__dsound(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { + ma_device_state_dsound* pDeviceStateDSound; + const ma_device_config_dsound* pDeviceConfigDSound = (const ma_device_config_dsound*)pDeviceBackendConfig; + ma_device_config_dsound defaultConfigDSound; + ma_device_type deviceType = ma_device_get_type(pDevice); ma_result result; HRESULT hr; - MA_ASSERT(pDevice != NULL); - - MA_ZERO_OBJECT(&pDevice->dsound); + if (pDeviceConfigDSound == NULL) { + defaultConfigDSound = ma_device_config_dsound_init(); + pDeviceConfigDSound = &defaultConfigDSound; + } - if (pConfig->deviceType == ma_device_type_loopback) { + if (deviceType == ma_device_type_loopback) { return MA_DEVICE_TYPE_NOT_SUPPORTED; } + pDeviceStateDSound = (ma_device_state_dsound*)ma_calloc(sizeof(*pDeviceStateDSound), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateDSound == NULL) { + return MA_OUT_OF_MEMORY; + } + /* Unfortunately DirectSound uses different APIs and data structures for playback and capture devices. We need to initialize the capture device first because we'll want to match its buffer size and period count on the playback side if we're using full-duplex mode. */ - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { MA_WAVEFORMATEXTENSIBLE wf; MA_DSCBUFFERDESC descDS; ma_uint32 periodSizeInFrames; @@ -26164,16 +26810,17 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf result = ma_config_to_WAVEFORMATEXTENSIBLE(pDescriptorCapture->format, pDescriptorCapture->channels, pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, &wf); if (result != MA_SUCCESS) { + ma_device_uninit__dsound(pDevice); return result; } - result = ma_context_create_IDirectSoundCapture__dsound(pDevice->pContext, pDescriptorCapture->shareMode, pDescriptorCapture->pDeviceID, (ma_IDirectSoundCapture**)&pDevice->dsound.pCapture); + result = ma_context_create_IDirectSoundCapture__dsound(ma_device_get_context(pDevice), pDescriptorCapture->shareMode, pDescriptorCapture->pDeviceID, &pDeviceStateDSound->pCapture); if (result != MA_SUCCESS) { ma_device_uninit__dsound(pDevice); return result; } - result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(pDevice->pContext, (ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &wf.nChannels, &wf.wBitsPerSample, &wf.nSamplesPerSec); + result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(ma_device_get_context(pDevice), pDeviceStateDSound->pCapture, &wf.nChannels, &wf.wBitsPerSample, &wf.nSamplesPerSec); if (result != MA_SUCCESS) { ma_device_uninit__dsound(pDevice); return result; @@ -26185,15 +26832,15 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf wf.SubFormat = MA_GUID_KSDATAFORMAT_SUBTYPE_PCM; /* The size of the buffer must be a clean multiple of the period count. */ - periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__dsound(pDescriptorCapture, wf.nSamplesPerSec, pConfig->performanceProfile); - periodCount = (pDescriptorCapture->periodCount > 0) ? pDescriptorCapture->periodCount : MA_DEFAULT_PERIODS; + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__dsound(pDescriptorCapture, wf.nSamplesPerSec); + periodCount = 2; /* Always using two periods (double buffering). */ MA_ZERO_OBJECT(&descDS); descDS.dwSize = sizeof(descDS); descDS.dwFlags = 0; descDS.dwBufferBytes = periodSizeInFrames * periodCount * wf.nBlockAlign; descDS.lpwfxFormat = (MA_WAVEFORMATEX*)&wf; - hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); + hr = ma_IDirectSoundCapture_CreateCaptureBuffer(pDeviceStateDSound->pCapture, &descDS, &pDeviceStateDSound->pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); @@ -26202,7 +26849,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf /* Get the _actual_ properties of the buffer. */ pActualFormat = (MA_WAVEFORMATEXTENSIBLE*)rawdata; - hr = ma_IDirectSoundCaptureBuffer_GetFormat((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, (MA_WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); + hr = ma_IDirectSoundCaptureBuffer_GetFormat(pDeviceStateDSound->pCaptureBuffer, (MA_WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer."); @@ -26227,9 +26874,9 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf */ if (periodSizeInFrames != (descDS.dwBufferBytes / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / periodCount)) { descDS.dwBufferBytes = periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * periodCount; - ma_IDirectSoundCaptureBuffer_Release((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); + ma_IDirectSoundCaptureBuffer_Release(pDeviceStateDSound->pCaptureBuffer); - hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); + hr = ma_IDirectSoundCapture_CreateCaptureBuffer(pDeviceStateDSound->pCapture, &descDS, &pDeviceStateDSound->pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); @@ -26237,12 +26884,27 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf } } - /* DirectSound should give us a buffer exactly the size we asked for. */ + /* Grab the actual buffer size. */ + { + MA_DSCBCAPS bufferCaps; + + MA_ZERO_OBJECT(&bufferCaps); + bufferCaps.dwSize = sizeof(bufferCaps); + + hr = ma_IDirectSoundCaptureBuffer_GetCaps(pDeviceStateDSound->pCaptureBuffer, &bufferCaps); + if (FAILED(hr)) { + ma_device_uninit__dsound(pDevice); + return ma_result_from_HRESULT(hr); + } + + periodSizeInFrames = bufferCaps.dwBufferBytes / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / periodCount; + } + pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; pDescriptorCapture->periodCount = periodCount; } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { MA_WAVEFORMATEXTENSIBLE wf; MA_DSBUFFERDESC descDSPrimary; MA_DSCAPS caps; @@ -26259,7 +26921,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf return result; } - result = ma_context_create_IDirectSound__dsound(pDevice->pContext, pDescriptorPlayback->shareMode, pDescriptorPlayback->pDeviceID, (ma_IDirectSound**)&pDevice->dsound.pPlayback); + result = ma_context_create_IDirectSound__dsound(pDevice->pContext, pDescriptorPlayback->shareMode, pDescriptorPlayback->pDeviceID, &pDeviceStateDSound->pPlayback); if (result != MA_SUCCESS) { ma_device_uninit__dsound(pDevice); return result; @@ -26268,7 +26930,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf MA_ZERO_OBJECT(&descDSPrimary); descDSPrimary.dwSize = sizeof(MA_DSBUFFERDESC); descDSPrimary.dwFlags = MA_DSBCAPS_PRIMARYBUFFER | MA_DSBCAPS_CTRLVOLUME; - hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDSPrimary, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL); + hr = ma_IDirectSound_CreateSoundBuffer(pDeviceStateDSound->pPlayback, &descDSPrimary, &pDeviceStateDSound->pPlaybackPrimaryBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer."); @@ -26279,7 +26941,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf /* We may want to make some adjustments to the format if we are using defaults. */ MA_ZERO_OBJECT(&caps); caps.dwSize = sizeof(caps); - hr = ma_IDirectSound_GetCaps((ma_IDirectSound*)pDevice->dsound.pPlayback, &caps); + hr = ma_IDirectSound_GetCaps(pDeviceStateDSound->pPlayback, &caps); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); @@ -26293,7 +26955,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf nativeChannelCount = 2; /* Look at the speaker configuration to get a better idea on the channel count. */ - if (SUCCEEDED(ma_IDirectSound_GetSpeakerConfig((ma_IDirectSound*)pDevice->dsound.pPlayback, &speakerConfig))) { + if (SUCCEEDED(ma_IDirectSound_GetSpeakerConfig(pDeviceStateDSound->pPlayback, &speakerConfig))) { ma_get_channels_from_speaker_config__dsound(speakerConfig, &nativeChannelCount, &nativeChannelMask); } } else { @@ -26326,7 +26988,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer and compare the result with the format that was requested with the SetFormat method. */ - hr = ma_IDirectSoundBuffer_SetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (MA_WAVEFORMATEX*)&wf); + hr = ma_IDirectSoundBuffer_SetFormat(pDeviceStateDSound->pPlaybackPrimaryBuffer, (MA_WAVEFORMATEX*)&wf); if (FAILED(hr)) { /* If setting of the format failed we'll try again with some fallback settings. On Windows 98 I have @@ -26342,7 +27004,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf wf.nBlockAlign = wf.nChannels * (wf.wBitsPerSample / 8); wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign; - hr = ma_IDirectSoundBuffer_SetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (MA_WAVEFORMATEX*)&wf); + hr = ma_IDirectSoundBuffer_SetFormat(pDeviceStateDSound->pPlaybackPrimaryBuffer, (MA_WAVEFORMATEX*)&wf); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer."); @@ -26352,7 +27014,7 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf /* Get the _actual_ properties of the buffer. */ pActualFormat = (MA_WAVEFORMATEXTENSIBLE*)rawdata; - hr = ma_IDirectSoundBuffer_GetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (MA_WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); + hr = ma_IDirectSoundBuffer_GetFormat(pDeviceStateDSound->pPlaybackPrimaryBuffer, (MA_WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer."); @@ -26372,8 +27034,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf } /* The size of the buffer must be a clean multiple of the period count. */ - periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__dsound(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); - periodCount = (pDescriptorPlayback->periodCount > 0) ? pDescriptorPlayback->periodCount : MA_DEFAULT_PERIODS; + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__dsound(pDescriptorPlayback, pDescriptorPlayback->sampleRate); + periodCount = 2; /* Always using two periods (double buffering). */ /* Meaning of dwFlags (from MSDN): @@ -26395,326 +27057,236 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf descDS.dwFlags = MA_DSBCAPS_CTRLPOSITIONNOTIFY | MA_DSBCAPS_GLOBALFOCUS | MA_DSBCAPS_GETCURRENTPOSITION2; descDS.dwBufferBytes = periodSizeInFrames * periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels); descDS.lpwfxFormat = (MA_WAVEFORMATEX*)pActualFormat; - hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL); + hr = ma_IDirectSound_CreateSoundBuffer(pDeviceStateDSound->pPlayback, &descDS, &pDeviceStateDSound->pPlaybackBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer."); return ma_result_from_HRESULT(hr); } - /* DirectSound should give us a buffer exactly the size we asked for. */ + /* Grab the actual buffer size. */ + { + MA_DSBCAPS bufferCaps; + + MA_ZERO_OBJECT(&bufferCaps); + bufferCaps.dwSize = sizeof(bufferCaps); + + hr = ma_IDirectSoundBuffer_GetCaps(pDeviceStateDSound->pPlaybackBuffer, &bufferCaps); + if (FAILED(hr)) { + ma_device_uninit__dsound(pDevice); + return ma_result_from_HRESULT(hr); + } + + periodSizeInFrames = bufferCaps.dwBufferBytes / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / periodCount; + } + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; pDescriptorPlayback->periodCount = periodCount; } + *ppDeviceState = pDeviceStateDSound; + return MA_SUCCESS; } +static void ma_device_uninit__dsound(ma_device* pDevice) +{ + ma_device_state_dsound* pDeviceStateDSound = ma_device_get_backend_state__dsound(pDevice); + + if (pDeviceStateDSound->pCaptureBuffer != NULL) { + ma_IDirectSoundCaptureBuffer_Release(pDeviceStateDSound->pCaptureBuffer); + } + if (pDeviceStateDSound->pCapture != NULL) { + ma_IDirectSoundCapture_Release(pDeviceStateDSound->pCapture); + } + + if (pDeviceStateDSound->pPlaybackBuffer != NULL) { + ma_IDirectSoundBuffer_Release(pDeviceStateDSound->pPlaybackBuffer); + } + if (pDeviceStateDSound->pPlaybackPrimaryBuffer != NULL) { + ma_IDirectSoundBuffer_Release(pDeviceStateDSound->pPlaybackPrimaryBuffer); + } + if (pDeviceStateDSound->pPlayback != NULL) { + ma_IDirectSound_Release(pDeviceStateDSound->pPlayback); + } + + ma_free(pDeviceStateDSound, ma_device_get_allocation_callbacks(pDevice)); +} + -static ma_result ma_device_data_loop__dsound(ma_device* pDevice) +static ma_result ma_device_start__dsound(ma_device* pDevice) { - ma_result result = MA_SUCCESS; - ma_uint32 bpfDeviceCapture = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 bpfDevicePlayback = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_device_state_dsound* pDeviceStateDSound = ma_device_get_backend_state__dsound(pDevice); HRESULT hr; - DWORD lockOffsetInBytesCapture; - DWORD lockSizeInBytesCapture; - DWORD mappedSizeInBytesCapture; - DWORD mappedDeviceFramesProcessedCapture; - void* pMappedDeviceBufferCapture; - DWORD lockOffsetInBytesPlayback; - DWORD lockSizeInBytesPlayback; - DWORD mappedSizeInBytesPlayback; - void* pMappedDeviceBufferPlayback; - DWORD prevReadCursorInBytesCapture = 0; - DWORD prevPlayCursorInBytesPlayback = 0; - ma_bool32 physicalPlayCursorLoopFlagPlayback = 0; - DWORD virtualWriteCursorInBytesPlayback = 0; - ma_bool32 virtualWriteCursorLoopFlagPlayback = 0; - ma_bool32 isPlaybackDeviceStarted = MA_FALSE; - ma_uint32 framesWrittenToPlaybackDevice = 0; /* For knowing whether or not the playback device needs to be started. */ - ma_uint32 waitTimeInMilliseconds = 1; - DWORD playbackBufferStatus = 0; - - MA_ASSERT(pDevice != NULL); - /* The first thing to do is start the capture device. The playback device is only started after the first period is written. */ if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - hr = ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING); + hr = ma_IDirectSoundCaptureBuffer_Start(pDeviceStateDSound->pCaptureBuffer, MA_DSCBSTART_LOOPING); if (FAILED(hr)) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed."); + + /* If we got NODRIVER the application will need to do a full reinitialization to get audio working again. */ + if (hr == MA_DSERR_NODRIVER) { + ma_device_set_errored(pDevice); + } + return ma_result_from_HRESULT(hr); } } - while (ma_device_get_state(pDevice) == ma_device_state_started) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - DWORD physicalCaptureCursorInBytes; - DWORD physicalReadCursorInBytes; - hr = ma_IDirectSoundCaptureBuffer_GetCurrentPosition((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &physicalCaptureCursorInBytes, &physicalReadCursorInBytes); - if (FAILED(hr)) { - return ma_result_from_HRESULT(hr); - } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* The device will be started in step(). */ + } - /* If nothing is available we just sleep for a bit and return from this iteration. */ - if (physicalReadCursorInBytes == prevReadCursorInBytesCapture) { - ma_sleep(waitTimeInMilliseconds); - continue; /* Nothing is available in the capture buffer. */ - } + return MA_SUCCESS; +} - /* - The current position has moved. We need to map all of the captured samples and write them to the playback device, making sure - we don't return until every frame has been copied over. - */ - if (prevReadCursorInBytesCapture < physicalReadCursorInBytes) { - /* The capture position has not looped. This is the simple case. */ - lockOffsetInBytesCapture = prevReadCursorInBytesCapture; - lockSizeInBytesCapture = (physicalReadCursorInBytes - prevReadCursorInBytesCapture); - } else { - /* - The capture position has looped. This is the more complex case. Map to the end of the buffer. If this does not return anything, - do it again from the start. - */ - if (prevReadCursorInBytesCapture < pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture) { - /* Lock up to the end of the buffer. */ - lockOffsetInBytesCapture = prevReadCursorInBytesCapture; - lockSizeInBytesCapture = (pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture) - prevReadCursorInBytesCapture; - } else { - /* Lock starting from the start of the buffer. */ - lockOffsetInBytesCapture = 0; - lockSizeInBytesCapture = physicalReadCursorInBytes; - } - } +static ma_result ma_device_stop__dsound(ma_device* pDevice) +{ + ma_device_state_dsound* pDeviceStateDSound = ma_device_get_backend_state__dsound(pDevice); + HRESULT hr; + ma_bool32 wasSuccessful = MA_TRUE; - if (lockSizeInBytesCapture == 0) { - ma_sleep(waitTimeInMilliseconds); - continue; /* Nothing is available in the capture buffer. */ - } + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + hr = ma_IDirectSoundCaptureBuffer_Stop(pDeviceStateDSound->pCaptureBuffer); + if (FAILED(hr)) { + /*ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed.");*/ + wasSuccessful = MA_FALSE; + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* The playback device should be drained before stopping. All we do is wait until the available bytes is equal to the size of the buffer. */ + if (pDeviceStateDSound->isPlaybackDeviceStarted) { + /* + TODO: I've noticed a crackle when stopping the device, which I suspect might be due to the draining logic here. I'm thinking + that maybe we should write out a total of `pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods` + frames worth of silence, and then stop and reset the cursor. + */ + for (;;) { + DWORD availableBytesPlayback = 0; + DWORD physicalPlayCursorInBytes; + DWORD physicalWriteCursorInBytes; + ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); + hr = ma_IDirectSoundBuffer_GetCurrentPosition(pDeviceStateDSound->pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes); if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); - return ma_result_from_HRESULT(hr); + break; } + if (physicalPlayCursorInBytes < pDeviceStateDSound->prevPlayCursorInBytesPlayback) { + pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback = !pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback; + } + pDeviceStateDSound->prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; - /* At this point we have some input data that we need to output. We do not return until every mapped frame of the input data is written to the playback device. */ - mappedDeviceFramesProcessedCapture = 0; - - for (;;) { /* Keep writing to the playback device. */ - ma_uint8 inputFramesInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 inputFramesInClientFormatCap = sizeof(inputFramesInClientFormat) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint8 outputFramesInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 outputFramesInClientFormatCap = sizeof(outputFramesInClientFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint32 outputFramesInClientFormatCount; - ma_uint32 outputFramesInClientFormatConsumed = 0; - ma_uint64 clientCapturedFramesToProcess = ma_min(inputFramesInClientFormatCap, outputFramesInClientFormatCap); - ma_uint64 deviceCapturedFramesToProcess = (mappedSizeInBytesCapture / bpfDeviceCapture) - mappedDeviceFramesProcessedCapture; - void* pRunningMappedDeviceBufferCapture = ma_offset_ptr(pMappedDeviceBufferCapture, mappedDeviceFramesProcessedCapture * bpfDeviceCapture); - - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningMappedDeviceBufferCapture, &deviceCapturedFramesToProcess, inputFramesInClientFormat, &clientCapturedFramesToProcess); - if (result != MA_SUCCESS) { + if (pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback == pDeviceStateDSound->virtualWriteCursorLoopFlagPlayback) { + /* Same loop iteration. The available bytes wraps all the way around from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes <= pDeviceStateDSound->virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpf) - pDeviceStateDSound->virtualWriteCursorInBytesPlayback; + availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ + } else { break; } + } else { + /* Different loop iterations. The available bytes only goes from the virtual write cursor to the physical play cursor. */ + if (physicalPlayCursorInBytes >= pDeviceStateDSound->virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = physicalPlayCursorInBytes - pDeviceStateDSound->virtualWriteCursorInBytesPlayback; + } else { + break; + } + } - outputFramesInClientFormatCount = (ma_uint32)clientCapturedFramesToProcess; - mappedDeviceFramesProcessedCapture += (ma_uint32)deviceCapturedFramesToProcess; - - ma_device__handle_data_callback(pDevice, outputFramesInClientFormat, inputFramesInClientFormat, (ma_uint32)clientCapturedFramesToProcess); - - /* At this point we have input and output data in client format. All we need to do now is convert it to the output device format. This may take a few passes. */ - for (;;) { - ma_uint32 framesWrittenThisIteration; - DWORD physicalPlayCursorInBytes; - DWORD physicalWriteCursorInBytes; - DWORD availableBytesPlayback; - DWORD silentPaddingInBytes = 0; /* <-- Must be initialized to 0. */ - - /* We need the physical play and write cursors. */ - if (FAILED(ma_IDirectSoundBuffer_GetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes))) { - break; - } - - if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { - physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; - } - prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; - - /* If there's any bytes available for writing we can do that now. The space between the virtual cursor position and play cursor. */ - if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { - /* Same loop iteration. The available bytes wraps all the way around from the virtual write cursor to the physical play cursor. */ - if (physicalPlayCursorInBytes <= virtualWriteCursorInBytesPlayback) { - availableBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - virtualWriteCursorInBytesPlayback; - availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ - } else { - /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback): Play cursor has moved in front of the write cursor (same loop iteration). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); - availableBytesPlayback = 0; - } - } else { - /* Different loop iterations. The available bytes only goes from the virtual write cursor to the physical play cursor. */ - if (physicalPlayCursorInBytes >= virtualWriteCursorInBytesPlayback) { - availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; - } else { - /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback): Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); - availableBytesPlayback = 0; - } - } - - /* If there's no room available for writing we need to wait for more. */ - if (availableBytesPlayback == 0) { - /* If we haven't started the device yet, this will never get beyond 0. In this case we need to get the device started. */ - if (!isPlaybackDeviceStarted) { - hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); - if (FAILED(hr)) { - ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); - return ma_result_from_HRESULT(hr); - } - isPlaybackDeviceStarted = MA_TRUE; - } else { - ma_sleep(waitTimeInMilliseconds); - continue; - } - } - - - /* Getting here means there room available somewhere. We limit this to either the end of the buffer or the physical play cursor, whichever is closest. */ - lockOffsetInBytesPlayback = virtualWriteCursorInBytesPlayback; - if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { - /* Same loop iteration. Go up to the end of the buffer. */ - lockSizeInBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - virtualWriteCursorInBytesPlayback; - } else { - /* Different loop iterations. Go up to the physical play cursor. */ - lockSizeInBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; - } - - hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); - result = ma_result_from_HRESULT(hr); - break; - } - - /* - Experiment: If the playback buffer is being starved, pad it with some silence to get it back in sync. This will cause a glitch, but it may prevent - endless glitching due to it constantly running out of data. - */ - if (isPlaybackDeviceStarted) { - DWORD bytesQueuedForPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - availableBytesPlayback; - if (bytesQueuedForPlayback < (pDevice->playback.internalPeriodSizeInFrames*bpfDevicePlayback)) { - silentPaddingInBytes = (pDevice->playback.internalPeriodSizeInFrames*2*bpfDevicePlayback) - bytesQueuedForPlayback; - if (silentPaddingInBytes > lockSizeInBytesPlayback) { - silentPaddingInBytes = lockSizeInBytesPlayback; - } - - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback) Playback buffer starved. availableBytesPlayback=%ld, silentPaddingInBytes=%ld\n", availableBytesPlayback, silentPaddingInBytes); - } - } - - /* At this point we have a buffer for output. */ - if (silentPaddingInBytes > 0) { - MA_ZERO_MEMORY(pMappedDeviceBufferPlayback, silentPaddingInBytes); - framesWrittenThisIteration = silentPaddingInBytes/bpfDevicePlayback; - } else { - ma_uint64 convertedFrameCountIn = (outputFramesInClientFormatCount - outputFramesInClientFormatConsumed); - ma_uint64 convertedFrameCountOut = mappedSizeInBytesPlayback/bpfDevicePlayback; - void* pConvertedFramesIn = ma_offset_ptr(outputFramesInClientFormat, outputFramesInClientFormatConsumed * bpfDevicePlayback); - void* pConvertedFramesOut = pMappedDeviceBufferPlayback; - - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pConvertedFramesIn, &convertedFrameCountIn, pConvertedFramesOut, &convertedFrameCountOut); - if (result != MA_SUCCESS) { - break; - } - - outputFramesInClientFormatConsumed += (ma_uint32)convertedFrameCountOut; - framesWrittenThisIteration = (ma_uint32)convertedFrameCountOut; - } - + if (availableBytesPlayback >= (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpf)) { + break; + } - hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, framesWrittenThisIteration*bpfDevicePlayback, NULL, 0); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); - result = ma_result_from_HRESULT(hr); - break; - } + ma_sleep(1); + } + } - virtualWriteCursorInBytesPlayback += framesWrittenThisIteration*bpfDevicePlayback; - if ((virtualWriteCursorInBytesPlayback/bpfDevicePlayback) == pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods) { - virtualWriteCursorInBytesPlayback = 0; - virtualWriteCursorLoopFlagPlayback = !virtualWriteCursorLoopFlagPlayback; - } + hr = ma_IDirectSoundBuffer_Stop(pDeviceStateDSound->pPlaybackBuffer); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed."); + wasSuccessful = MA_FALSE; + } - /* - We may need to start the device. We want two full periods to be written before starting the playback device. Having an extra period adds - a bit of a buffer to prevent the playback buffer from getting starved. - */ - framesWrittenToPlaybackDevice += framesWrittenThisIteration; - if (!isPlaybackDeviceStarted && framesWrittenToPlaybackDevice >= (pDevice->playback.internalPeriodSizeInFrames*2)) { - hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); - if (FAILED(hr)) { - ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); - return ma_result_from_HRESULT(hr); - } - isPlaybackDeviceStarted = MA_TRUE; - } + ma_IDirectSoundBuffer_SetCurrentPosition(pDeviceStateDSound->pPlaybackBuffer, 0); - if (framesWrittenThisIteration < mappedSizeInBytesPlayback/bpfDevicePlayback) { - break; /* We're finished with the output data.*/ - } - } + pDeviceStateDSound->prevPlayCursorInBytesPlayback = 0; + pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback = 0; + pDeviceStateDSound->virtualWriteCursorInBytesPlayback = 0; + pDeviceStateDSound->virtualWriteCursorLoopFlagPlayback = 0; + pDeviceStateDSound->framesWrittenToPlaybackDevice = 0; + pDeviceStateDSound->isPlaybackDeviceStarted = MA_FALSE; + } - if (clientCapturedFramesToProcess == 0) { - break; /* We just consumed every input sample. */ - } - } + if (!wasSuccessful) { + return MA_ERROR; + } + return MA_SUCCESS; +} - /* At this point we're done with the mapped portion of the capture buffer. */ - hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); - return ma_result_from_HRESULT(hr); - } - prevReadCursorInBytesCapture = (lockOffsetInBytesCapture + mappedSizeInBytesCapture); - } break; +static ma_result ma_device_step__dsound(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_dsound* pDeviceStateDSound = ma_device_get_backend_state__dsound(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result = MA_SUCCESS; + HRESULT hr = S_OK; + ma_uint32 waitTimeInMilliseconds = 1; + ma_bool32 wasDataProcessed = MA_FALSE; + MA_ASSERT(pDevice != NULL); + /* + I tried experimenting with using notification events to do double buffering, but it was a complete mess so + instead I'm just using a simple polling system, with a sleep to relax the CPU in blocking mode (no sleep is + performed in non-blocking mode). - case ma_device_type_capture: - { - DWORD physicalCaptureCursorInBytes; - DWORD physicalReadCursorInBytes; - hr = ma_IDirectSoundCaptureBuffer_GetCurrentPosition((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, &physicalCaptureCursorInBytes, &physicalReadCursorInBytes); - if (FAILED(hr)) { - return MA_ERROR; - } + There are two issues with using notification events. The first is that I was unable to get the latency as + low as a can with polling. Most likely an issue on my side, but I wasn't able to figure it out. + + The other issue is with the handling of device rerouting. From what I can tell, DirectSound does not have a + way to register an event for detecting a device switch (if this is incorrect, I'd be interested to hear about + it). This make it difficult to handle with respect to `WaitForMultipleObjects()` because when the device + switch happens the respective notification events seem to get stuck in a non-signalled state such that + `WaitForMultipleObjects()` will never return. For playback, the way to work around it is to put a timeout + in place, and upon timing out, restart the buffer if it's not in a playing state. In capture mode, however, + there doesn't seem to be a way to know whether or not the buffer needs to be restarted. + */ - /* If the previous capture position is the same as the current position we need to wait a bit longer. */ - if (prevReadCursorInBytesCapture == physicalReadCursorInBytes) { - ma_sleep(waitTimeInMilliseconds); - continue; - } + /* We keep looping until we process some data. */ + while (ma_device_is_started(pDevice)) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + DWORD physicalCaptureCursorInBytes; + DWORD physicalReadCursorInBytes; + DWORD lockOffsetInBytesCapture; + DWORD lockSizeInBytesCapture; + DWORD mappedSizeInBytesCapture; + void* pMappedDeviceBufferCapture; + ma_uint32 bpfDeviceCapture = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + + hr = ma_IDirectSoundCaptureBuffer_GetCurrentPosition(pDeviceStateDSound->pCaptureBuffer, &physicalCaptureCursorInBytes, &physicalReadCursorInBytes); + if (FAILED(hr)) { + result = ma_result_from_HRESULT(hr); + goto error; + } + /* If the previous capture position is the same as the current position we need to wait a bit longer. */ + if (pDeviceStateDSound->prevReadCursorInBytesCapture != physicalReadCursorInBytes) { /* Getting here means we have capture data available. */ - if (prevReadCursorInBytesCapture < physicalReadCursorInBytes) { + if (pDeviceStateDSound->prevReadCursorInBytesCapture < physicalReadCursorInBytes) { /* The capture position has not looped. This is the simple case. */ - lockOffsetInBytesCapture = prevReadCursorInBytesCapture; - lockSizeInBytesCapture = (physicalReadCursorInBytes - prevReadCursorInBytesCapture); + lockOffsetInBytesCapture = pDeviceStateDSound->prevReadCursorInBytesCapture; + lockSizeInBytesCapture = (physicalReadCursorInBytes - pDeviceStateDSound->prevReadCursorInBytesCapture); } else { /* The capture position has looped. This is the more complex case. Map to the end of the buffer. If this does not return anything, do it again from the start. */ - if (prevReadCursorInBytesCapture < pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture) { + if (pDeviceStateDSound->prevReadCursorInBytesCapture < pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture) { /* Lock up to the end of the buffer. */ - lockOffsetInBytesCapture = prevReadCursorInBytesCapture; - lockSizeInBytesCapture = (pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture) - prevReadCursorInBytesCapture; + lockOffsetInBytesCapture = pDeviceStateDSound->prevReadCursorInBytesCapture; + lockSizeInBytesCapture = (pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture) - pDeviceStateDSound->prevReadCursorInBytesCapture; } else { /* Lock starting from the start of the buffer. */ lockOffsetInBytesCapture = 0; @@ -26722,285 +27294,234 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) } } - if (lockSizeInBytesCapture < pDevice->capture.internalPeriodSizeInFrames) { - ma_sleep(waitTimeInMilliseconds); - continue; /* Nothing is available in the capture buffer. */ - } + if (lockSizeInBytesCapture > 0) { + hr = ma_IDirectSoundCaptureBuffer_Lock(pDeviceStateDSound->pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); + goto error; + } - hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); - result = ma_result_from_HRESULT(hr); - } + if (lockSizeInBytesCapture != mappedSizeInBytesCapture) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Capture) lockSizeInBytesCapture=%ld != mappedSizeInBytesCapture=%ld", lockSizeInBytesCapture, mappedSizeInBytesCapture); + } - if (lockSizeInBytesCapture != mappedSizeInBytesCapture) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Capture) lockSizeInBytesCapture=%ld != mappedSizeInBytesCapture=%ld\n", lockSizeInBytesCapture, mappedSizeInBytesCapture); - } + ma_device_handle_backend_data_callback(pDevice, NULL, pMappedDeviceBufferCapture, mappedSizeInBytesCapture/bpfDeviceCapture); - ma_device__send_frames_to_client(pDevice, mappedSizeInBytesCapture/bpfDeviceCapture, pMappedDeviceBufferCapture); + hr = ma_IDirectSoundCaptureBuffer_Unlock(pDeviceStateDSound->pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); + result = ma_result_from_HRESULT(hr); + goto error; + } + pDeviceStateDSound->prevReadCursorInBytesCapture = lockOffsetInBytesCapture + mappedSizeInBytesCapture; - hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); - return ma_result_from_HRESULT(hr); - } - prevReadCursorInBytesCapture = lockOffsetInBytesCapture + mappedSizeInBytesCapture; + if (pDeviceStateDSound->prevReadCursorInBytesCapture == (pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture)) { + pDeviceStateDSound->prevReadCursorInBytesCapture = 0; + } - if (prevReadCursorInBytesCapture == (pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*bpfDeviceCapture)) { - prevReadCursorInBytesCapture = 0; + wasDataProcessed = MA_TRUE; } - } break; - + } + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + DWORD availableBytesPlayback; + DWORD physicalPlayCursorInBytes; + DWORD physicalWriteCursorInBytes; + DWORD lockOffsetInBytesPlayback; + DWORD lockSizeInBytesPlayback; + DWORD mappedSizeInBytesPlayback; + void* pMappedDeviceBufferPlayback; + DWORD playbackBufferStatus = 0; + ma_uint32 bpfDevicePlayback = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + + hr = ma_IDirectSoundBuffer_GetCurrentPosition(pDeviceStateDSound->pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes); + if (FAILED(hr)) { + result = ma_result_from_HRESULT(hr); + goto error; + } - case ma_device_type_playback: - { - DWORD availableBytesPlayback; - DWORD physicalPlayCursorInBytes; - DWORD physicalWriteCursorInBytes; - hr = ma_IDirectSoundBuffer_GetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes); + /* If the playback device is not started, start it now. */ + hr = ma_IDirectSoundBuffer_GetStatus(pDeviceStateDSound->pPlaybackBuffer, &playbackBufferStatus); + if (SUCCEEDED(hr) && (playbackBufferStatus & MA_DSBSTATUS_PLAYING) == 0 && pDeviceStateDSound->isPlaybackDeviceStarted) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[DirectSound] Attempting to resume audio due to state: %d.", (int)playbackBufferStatus); + hr = ma_IDirectSoundBuffer_Play(pDeviceStateDSound->pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { - break; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed after attempting to resume from state %d.", (int)playbackBufferStatus); + result = ma_result_from_HRESULT(hr); + goto error; } - - hr = ma_IDirectSoundBuffer_GetStatus((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &playbackBufferStatus); - if (SUCCEEDED(hr) && (playbackBufferStatus & MA_DSBSTATUS_PLAYING) == 0 && isPlaybackDeviceStarted) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[DirectSound] Attempting to resume audio due to state: %d.", (int)playbackBufferStatus); - hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); - if (FAILED(hr)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed after attempting to resume from state %d.", (int)playbackBufferStatus); - return ma_result_from_HRESULT(hr); - } - - isPlaybackDeviceStarted = MA_TRUE; - ma_sleep(waitTimeInMilliseconds); - continue; + } else { + if (physicalPlayCursorInBytes < pDeviceStateDSound->prevPlayCursorInBytesPlayback) { + pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback = !pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback; } - if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { - physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; - } - prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; + pDeviceStateDSound->prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; /* If there's any bytes available for writing we can do that now. The space between the virtual cursor position and play cursor. */ - if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { + if (pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback == pDeviceStateDSound->virtualWriteCursorLoopFlagPlayback) { /* Same loop iteration. The available bytes wraps all the way around from the virtual write cursor to the physical play cursor. */ - if (physicalPlayCursorInBytes <= virtualWriteCursorInBytesPlayback) { - availableBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - virtualWriteCursorInBytesPlayback; + if (physicalPlayCursorInBytes <= pDeviceStateDSound->virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - pDeviceStateDSound->virtualWriteCursorInBytesPlayback; availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.", physicalPlayCursorInBytes, pDeviceStateDSound->virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } else { /* Different loop iterations. The available bytes only goes from the virtual write cursor to the physical play cursor. */ - if (physicalPlayCursorInBytes >= virtualWriteCursorInBytesPlayback) { - availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; + if (physicalPlayCursorInBytes >= pDeviceStateDSound->virtualWriteCursorInBytesPlayback) { + availableBytesPlayback = physicalPlayCursorInBytes - pDeviceStateDSound->virtualWriteCursorInBytesPlayback; } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.", physicalPlayCursorInBytes, pDeviceStateDSound->virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } /* If there's no room available for writing we need to wait for more. */ - if (availableBytesPlayback < pDevice->playback.internalPeriodSizeInFrames) { - /* If we haven't started the device yet, this will never get beyond 0. In this case we need to get the device started. */ - if (availableBytesPlayback == 0 && !isPlaybackDeviceStarted) { - hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); - return ma_result_from_HRESULT(hr); - } - isPlaybackDeviceStarted = MA_TRUE; + if (availableBytesPlayback > 0) { + /* Getting here means there room available somewhere. We limit this to either the end of the buffer or the physical play cursor, whichever is closest. */ + lockOffsetInBytesPlayback = pDeviceStateDSound->virtualWriteCursorInBytesPlayback; + if (pDeviceStateDSound->physicalPlayCursorLoopFlagPlayback == pDeviceStateDSound->virtualWriteCursorLoopFlagPlayback) { + /* Same loop iteration. Go up to the end of the buffer. */ + lockSizeInBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - pDeviceStateDSound->virtualWriteCursorInBytesPlayback; } else { - ma_sleep(waitTimeInMilliseconds); - continue; + /* Different loop iterations. Go up to the physical play cursor. */ + lockSizeInBytesPlayback = physicalPlayCursorInBytes - pDeviceStateDSound->virtualWriteCursorInBytesPlayback; } - } - /* Getting here means there room available somewhere. We limit this to either the end of the buffer or the physical play cursor, whichever is closest. */ - lockOffsetInBytesPlayback = virtualWriteCursorInBytesPlayback; - if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { - /* Same loop iteration. Go up to the end of the buffer. */ - lockSizeInBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - virtualWriteCursorInBytesPlayback; - } else { - /* Different loop iterations. Go up to the physical play cursor. */ - lockSizeInBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; - } + hr = ma_IDirectSoundBuffer_Lock(pDeviceStateDSound->pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); + goto error; + } - hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); - result = ma_result_from_HRESULT(hr); - break; - } + /* At this point we have a buffer for output. */ + ma_device_handle_backend_data_callback(pDevice, pMappedDeviceBufferPlayback, NULL, (mappedSizeInBytesPlayback/bpfDevicePlayback)); - /* At this point we have a buffer for output. */ - ma_device__read_frames_from_client(pDevice, (mappedSizeInBytesPlayback/bpfDevicePlayback), pMappedDeviceBufferPlayback); + hr = ma_IDirectSoundBuffer_Unlock(pDeviceStateDSound->pPlaybackBuffer, pMappedDeviceBufferPlayback, mappedSizeInBytesPlayback, NULL, 0); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); + result = ma_result_from_HRESULT(hr); + goto error; + } - hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, mappedSizeInBytesPlayback, NULL, 0); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); - result = ma_result_from_HRESULT(hr); - break; - } + pDeviceStateDSound->virtualWriteCursorInBytesPlayback += mappedSizeInBytesPlayback; + if (pDeviceStateDSound->virtualWriteCursorInBytesPlayback == pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) { + pDeviceStateDSound->virtualWriteCursorInBytesPlayback = 0; + pDeviceStateDSound->virtualWriteCursorLoopFlagPlayback = !pDeviceStateDSound->virtualWriteCursorLoopFlagPlayback; + } - virtualWriteCursorInBytesPlayback += mappedSizeInBytesPlayback; - if (virtualWriteCursorInBytesPlayback == pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) { - virtualWriteCursorInBytesPlayback = 0; - virtualWriteCursorLoopFlagPlayback = !virtualWriteCursorLoopFlagPlayback; - } + /* + We may need to start the device. We want two full periods to be written before starting the playback device. Having an extra period adds + a bit of a buffer to prevent the playback buffer from getting starved. + */ + if (pDeviceStateDSound->framesWrittenToPlaybackDevice < mappedSizeInBytesPlayback/bpfDevicePlayback) { + pDeviceStateDSound->framesWrittenToPlaybackDevice += mappedSizeInBytesPlayback/bpfDevicePlayback; + if (!pDeviceStateDSound->isPlaybackDeviceStarted && pDeviceStateDSound->framesWrittenToPlaybackDevice >= pDevice->playback.internalPeriodSizeInFrames) { + hr = ma_IDirectSoundBuffer_Play(pDeviceStateDSound->pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + result = ma_result_from_HRESULT(hr); + goto error; + } - /* - We may need to start the device. We want two full periods to be written before starting the playback device. Having an extra period adds - a bit of a buffer to prevent the playback buffer from getting starved. - */ - framesWrittenToPlaybackDevice += mappedSizeInBytesPlayback/bpfDevicePlayback; - if (!isPlaybackDeviceStarted && framesWrittenToPlaybackDevice >= pDevice->playback.internalPeriodSizeInFrames) { - hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); - return ma_result_from_HRESULT(hr); + pDeviceStateDSound->isPlaybackDeviceStarted = MA_TRUE; + } } - isPlaybackDeviceStarted = MA_TRUE; - } - } break; + wasDataProcessed = MA_TRUE; + } else { + /* Make sure the device is started or else our pointers will never progress. */ + if (availableBytesPlayback == 0 && !pDeviceStateDSound->isPlaybackDeviceStarted) { + hr = ma_IDirectSoundBuffer_Play(pDeviceStateDSound->pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + result = ma_result_from_HRESULT(hr); + goto error; + } - default: return MA_INVALID_ARGS; /* Invalid device type. */ + pDeviceStateDSound->isPlaybackDeviceStarted = MA_TRUE; + } + } + } } if (result != MA_SUCCESS) { - return result; + goto error; } - } - /* Getting here means the device is being stopped. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - hr = ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed."); - return ma_result_from_HRESULT(hr); + /* If we're in non-blocking mode we can just abort now, regardless of whether or not any data was processed. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING || wasDataProcessed) { + break; } + + /* Getting here means we are in blocking mode and nothing was processed. Keep waiting for some data. We just sleep a bit to relax the CPU. */ + ma_sleep(waitTimeInMilliseconds); } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* The playback device should be drained before stopping. All we do is wait until the available bytes is equal to the size of the buffer. */ - if (isPlaybackDeviceStarted) { - for (;;) { - DWORD availableBytesPlayback = 0; - DWORD physicalPlayCursorInBytes; - DWORD physicalWriteCursorInBytes; - hr = ma_IDirectSoundBuffer_GetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, &physicalPlayCursorInBytes, &physicalWriteCursorInBytes); - if (FAILED(hr)) { - break; - } + return MA_SUCCESS; - if (physicalPlayCursorInBytes < prevPlayCursorInBytesPlayback) { - physicalPlayCursorLoopFlagPlayback = !physicalPlayCursorLoopFlagPlayback; - } - prevPlayCursorInBytesPlayback = physicalPlayCursorInBytes; +error: + /* This can fail with DSERR_NODRIVER. In this case we need to put the device into an errored state. */ + if (hr == MA_DSERR_NODRIVER) { + ma_device_set_errored(pDevice); + } - if (physicalPlayCursorLoopFlagPlayback == virtualWriteCursorLoopFlagPlayback) { - /* Same loop iteration. The available bytes wraps all the way around from the virtual write cursor to the physical play cursor. */ - if (physicalPlayCursorInBytes <= virtualWriteCursorInBytesPlayback) { - availableBytesPlayback = (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback) - virtualWriteCursorInBytesPlayback; - availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ - } else { - break; - } - } else { - /* Different loop iterations. The available bytes only goes from the virtual write cursor to the physical play cursor. */ - if (physicalPlayCursorInBytes >= virtualWriteCursorInBytesPlayback) { - availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; - } else { - break; - } - } + return result; +} - if (availableBytesPlayback >= (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*bpfDevicePlayback)) { - break; - } +static ma_device_backend_vtable ma_gDeviceBackendVTable_DSound = +{ + ma_backend_info__dsound, + ma_context_init__dsound, + ma_context_uninit__dsound, + ma_context_enumerate_devices__dsound, + ma_device_init__dsound, + ma_device_uninit__dsound, + ma_device_start__dsound, + ma_device_stop__dsound, + ma_device_step__dsound, + NULL /* onDeviceWakeup */ +}; - ma_sleep(waitTimeInMilliseconds); - } - } +ma_device_backend_vtable* ma_device_backend_dsound = &ma_gDeviceBackendVTable_DSound; +#else +ma_device_backend_vtable* ma_device_backend_dsound = NULL; +#endif - hr = ma_IDirectSoundBuffer_Stop((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer); - if (FAILED(hr)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed."); - return ma_result_from_HRESULT(hr); - } +MA_API ma_device_backend_vtable* ma_dsound_get_vtable(void) +{ + return ma_device_backend_dsound; +} - ma_IDirectSoundBuffer_SetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0); - } +MA_API ma_context_config_dsound ma_context_config_dsound_init(void) +{ + ma_context_config_dsound config; - return MA_SUCCESS; + MA_ZERO_OBJECT(&config); + + return config; } -static ma_result ma_context_uninit__dsound(ma_context* pContext) +MA_API ma_device_config_dsound ma_device_config_dsound_init(void) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_dsound); + ma_device_config_dsound config; - ma_dlclose(ma_context_get_log(pContext), pContext->dsound.hDSoundDLL); + MA_ZERO_OBJECT(&config); - return MA_SUCCESS; + return config; } -static ma_result ma_context_init__dsound(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) -{ - MA_ASSERT(pContext != NULL); - (void)pConfig; - pContext->dsound.hDSoundDLL = ma_dlopen(ma_context_get_log(pContext), "dsound.dll"); - if (pContext->dsound.hDSoundDLL == NULL) { - return MA_API_NOT_FOUND; - } +/****************************************************************************** - pContext->dsound.DirectSoundCreate = ma_dlsym(ma_context_get_log(pContext), pContext->dsound.hDSoundDLL, "DirectSoundCreate"); - pContext->dsound.DirectSoundEnumerateA = ma_dlsym(ma_context_get_log(pContext), pContext->dsound.hDSoundDLL, "DirectSoundEnumerateA"); - pContext->dsound.DirectSoundCaptureCreate = ma_dlsym(ma_context_get_log(pContext), pContext->dsound.hDSoundDLL, "DirectSoundCaptureCreate"); - pContext->dsound.DirectSoundCaptureEnumerateA = ma_dlsym(ma_context_get_log(pContext), pContext->dsound.hDSoundDLL, "DirectSoundCaptureEnumerateA"); - - /* - We need to support all functions or nothing. DirectSound with Windows 95 seems to not work too - well in my testing. For example, it's missing DirectSoundCaptureEnumerateA(). This is a convenient - place to just disable the DirectSound backend for Windows 95. - */ - if (pContext->dsound.DirectSoundCreate == NULL || - pContext->dsound.DirectSoundEnumerateA == NULL || - pContext->dsound.DirectSoundCaptureCreate == NULL || - pContext->dsound.DirectSoundCaptureEnumerateA == NULL) { - return MA_API_NOT_FOUND; - } - - pContext->dsound.hWnd = pConfig->dsound.hWnd; - - pCallbacks->onContextInit = ma_context_init__dsound; - pCallbacks->onContextUninit = ma_context_uninit__dsound; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__dsound; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__dsound; - pCallbacks->onDeviceInit = ma_device_init__dsound; - pCallbacks->onDeviceUninit = ma_device_uninit__dsound; - pCallbacks->onDeviceStart = NULL; /* Not used. Started in onDeviceDataLoop. */ - pCallbacks->onDeviceStop = NULL; /* Not used. Stopped in onDeviceDataLoop. */ - pCallbacks->onDeviceRead = NULL; /* Not used. Data is read directly in onDeviceDataLoop. */ - pCallbacks->onDeviceWrite = NULL; /* Not used. Data is written directly in onDeviceDataLoop. */ - pCallbacks->onDeviceDataLoop = ma_device_data_loop__dsound; - - return MA_SUCCESS; -} -#endif - - - -/****************************************************************************** - -WinMM Backend +WinMM Backend ******************************************************************************/ #ifdef MA_HAS_WINMM @@ -27098,23 +27619,165 @@ typedef struct GUID NameGuid; } MA_WAVEINCAPS2A; -typedef UINT (WINAPI * MA_PFN_waveOutGetNumDevs)(void); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutGetDevCapsA)(ma_uintptr uDeviceID, MA_WAVEOUTCAPSA* pwoc, UINT cbwoc); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutOpen)(MA_HWAVEOUT* phwo, UINT uDeviceID, const MA_WAVEFORMATEX* pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutClose)(MA_HWAVEOUT hwo); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutPrepareHeader)(MA_HWAVEOUT hwo, MA_WAVEHDR* pwh, UINT cbwh); +typedef UINT (WINAPI * MA_PFN_waveOutGetNumDevs )(void); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutGetDevCapsA )(ma_uintptr uDeviceID, MA_WAVEOUTCAPSA* pwoc, UINT cbwoc); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutOpen )(MA_HWAVEOUT* phwo, UINT uDeviceID, const MA_WAVEFORMATEX* pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutClose )(MA_HWAVEOUT hwo); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutPrepareHeader )(MA_HWAVEOUT hwo, MA_WAVEHDR* pwh, UINT cbwh); typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutUnprepareHeader)(MA_HWAVEOUT hwo, MA_WAVEHDR* pwh, UINT cbwh); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutWrite)(MA_HWAVEOUT hwo, MA_WAVEHDR* pwh, UINT cbwh); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutReset)(MA_HWAVEOUT hwo); -typedef UINT (WINAPI * MA_PFN_waveInGetNumDevs)(void); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInGetDevCapsA)(ma_uintptr uDeviceID, MA_WAVEINCAPSA* pwic, UINT cbwic); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInOpen)(MA_HWAVEIN* phwi, UINT uDeviceID, const MA_WAVEFORMATEX* pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInClose)(MA_HWAVEIN hwi); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInPrepareHeader)(MA_HWAVEIN hwi, MA_WAVEHDR* pwh, UINT cbwh); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInUnprepareHeader)(MA_HWAVEIN hwi, MA_WAVEHDR* pwh, UINT cbwh); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInAddBuffer)(MA_HWAVEIN hwi, MA_WAVEHDR* pwh, UINT cbwh); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInStart)(MA_HWAVEIN hwi); -typedef MA_MMRESULT (WINAPI * MA_PFN_waveInReset)(MA_HWAVEIN hwi); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutWrite )(MA_HWAVEOUT hwo, MA_WAVEHDR* pwh, UINT cbwh); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveOutReset )(MA_HWAVEOUT hwo); +typedef UINT (WINAPI * MA_PFN_waveInGetNumDevs )(void); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInGetDevCapsA )(ma_uintptr uDeviceID, MA_WAVEINCAPSA* pwic, UINT cbwic); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInOpen )(MA_HWAVEIN* phwi, UINT uDeviceID, const MA_WAVEFORMATEX* pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInClose )(MA_HWAVEIN hwi); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInPrepareHeader )(MA_HWAVEIN hwi, MA_WAVEHDR* pwh, UINT cbwh); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInUnprepareHeader )(MA_HWAVEIN hwi, MA_WAVEHDR* pwh, UINT cbwh); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInAddBuffer )(MA_HWAVEIN hwi, MA_WAVEHDR* pwh, UINT cbwh); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInStart )(MA_HWAVEIN hwi); +typedef MA_MMRESULT (WINAPI * MA_PFN_waveInReset )(MA_HWAVEIN hwi); + +#if defined(MA_WIN32_DESKTOP) +/* Microsoft documents these APIs as returning LSTATUS, but the Win32 API shipping with some compilers do not define it. It's just a LONG. */ +typedef LONG (WINAPI * MA_PFN_RegOpenKeyExA)(HKEY hKey, const char* lpSubKey, DWORD ulOptions, DWORD samDesired, HKEY* phkResult); +typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); +typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, const char* lpValueName, DWORD* lpReserved, DWORD* lpType, BYTE* lpData, DWORD* lpcbData); +#endif /* MA_WIN32_DESKTOP */ + + +typedef struct ma_context_state_winmm +{ + ma_handle hWinMM; + MA_PFN_waveOutGetNumDevs waveOutGetNumDevs; + MA_PFN_waveOutGetDevCapsA waveOutGetDevCapsA; + MA_PFN_waveOutOpen waveOutOpen; + MA_PFN_waveOutClose waveOutClose; + MA_PFN_waveOutPrepareHeader waveOutPrepareHeader; + MA_PFN_waveOutUnprepareHeader waveOutUnprepareHeader; + MA_PFN_waveOutWrite waveOutWrite; + MA_PFN_waveOutReset waveOutReset; + MA_PFN_waveInGetNumDevs waveInGetNumDevs; + MA_PFN_waveInGetDevCapsA waveInGetDevCapsA; + MA_PFN_waveInOpen waveInOpen; + MA_PFN_waveInClose waveInClose; + MA_PFN_waveInPrepareHeader waveInPrepareHeader; + MA_PFN_waveInUnprepareHeader waveInUnprepareHeader; + MA_PFN_waveInAddBuffer waveInAddBuffer; + MA_PFN_waveInStart waveInStart; + MA_PFN_waveInReset waveInReset; + + ma_handle hAdvapi32; + MA_PFN_RegOpenKeyExA RegOpenKeyExA; + MA_PFN_RegCloseKey RegCloseKey; + MA_PFN_RegQueryValueExA RegQueryValueExA; +} ma_context_state_winmm; + +typedef struct ma_device_state_winmm +{ + MA_HWAVEOUT hDevicePlayback; + MA_HWAVEIN hDeviceCapture; + HANDLE hEventPlayback; + HANDLE hEventCapture; + ma_uint32 fragmentSizeInFrames; + ma_uint32 iNextHeaderPlayback; /* [0,periods). Used as an index into pWAVEHDRPlayback. */ + ma_uint32 iNextHeaderCapture; /* [0,periods). Used as an index into pWAVEHDRCapture. */ + ma_uint32 headerFramesConsumedPlayback; /* The number of PCM frames consumed in the buffer in pWAVEHEADER[iNextHeader]. */ + ma_uint32 headerFramesConsumedCapture; /* ^^^ */ + MA_WAVEHDR* pWAVEHDRPlayback; /* One instantiation for each period. */ + MA_WAVEHDR* pWAVEHDRCapture; /* One instantiation for each period. */ + ma_uint8* pIntermediaryBufferPlayback; + ma_uint8* pIntermediaryBufferCapture; + ma_uint8* _pHeapData; /* Used internally and is used for the heap allocated data for the intermediary buffer and the WAVEHDR structures. */ +} ma_device_state_winmm; + + +static ma_context_state_winmm* ma_context_get_backend_state__winmm(ma_context* pContext) +{ + return (ma_context_state_winmm*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_winmm* ma_device_get_backend_state__winmm(ma_device* pDevice) +{ + return (ma_device_state_winmm*)ma_device_get_backend_state(pDevice); +} + + +static void ma_backend_info__winmm(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "WinMM"; +} + +static ma_result ma_context_init__winmm(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_winmm* pContextStateWinMM; + const ma_context_config_winmm* pContextConfigWinMM = (const ma_context_config_winmm*)pContextBackendConfig; + ma_context_config_winmm defaultConfigWinMM; + + if (pContextConfigWinMM == NULL) { + defaultConfigWinMM = ma_context_config_winmm_init(); + pContextConfigWinMM = &defaultConfigWinMM; + } + + (void)pContextConfigWinMM; + + pContextStateWinMM = (ma_context_state_winmm*)ma_calloc(sizeof(*pContextStateWinMM), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateWinMM == NULL) { + return MA_OUT_OF_MEMORY; + } + + pContextStateWinMM->hWinMM = ma_dlopen(ma_context_get_log(pContext), "winmm.dll"); + if (pContextStateWinMM->hWinMM == NULL) { + ma_free(pContextStateWinMM, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } + + pContextStateWinMM->waveOutGetNumDevs = (MA_PFN_waveOutGetNumDevs )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutGetNumDevs"); + pContextStateWinMM->waveOutGetDevCapsA = (MA_PFN_waveOutGetDevCapsA )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutGetDevCapsA"); + pContextStateWinMM->waveOutOpen = (MA_PFN_waveOutOpen )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutOpen"); + pContextStateWinMM->waveOutClose = (MA_PFN_waveOutClose )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutClose"); + pContextStateWinMM->waveOutPrepareHeader = (MA_PFN_waveOutPrepareHeader )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutPrepareHeader"); + pContextStateWinMM->waveOutUnprepareHeader = (MA_PFN_waveOutUnprepareHeader)ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutUnprepareHeader"); + pContextStateWinMM->waveOutWrite = (MA_PFN_waveOutWrite )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutWrite"); + pContextStateWinMM->waveOutReset = (MA_PFN_waveOutReset )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveOutReset"); + pContextStateWinMM->waveInGetNumDevs = (MA_PFN_waveInGetNumDevs )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInGetNumDevs"); + pContextStateWinMM->waveInGetDevCapsA = (MA_PFN_waveInGetDevCapsA )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInGetDevCapsA"); + pContextStateWinMM->waveInOpen = (MA_PFN_waveInOpen )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInOpen"); + pContextStateWinMM->waveInClose = (MA_PFN_waveInClose )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInClose"); + pContextStateWinMM->waveInPrepareHeader = (MA_PFN_waveInPrepareHeader )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInPrepareHeader"); + pContextStateWinMM->waveInUnprepareHeader = (MA_PFN_waveInUnprepareHeader )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInUnprepareHeader"); + pContextStateWinMM->waveInAddBuffer = (MA_PFN_waveInAddBuffer )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInAddBuffer"); + pContextStateWinMM->waveInStart = (MA_PFN_waveInStart )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInStart"); + pContextStateWinMM->waveInReset = (MA_PFN_waveInReset )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hWinMM, "waveInReset"); + + + /* Advapi32.dll */ + pContextStateWinMM->hAdvapi32 = ma_dlopen(ma_context_get_log(pContext), "advapi32.dll"); + if (pContextStateWinMM->hAdvapi32 == NULL) { + ma_dlclose(ma_context_get_log(pContext), pContextStateWinMM->hWinMM); + ma_free(pContextStateWinMM, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } + + pContextStateWinMM->RegOpenKeyExA = (MA_PFN_RegOpenKeyExA )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hAdvapi32, "RegOpenKeyExA"); + pContextStateWinMM->RegCloseKey = (MA_PFN_RegCloseKey )ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hAdvapi32, "RegCloseKey"); + pContextStateWinMM->RegQueryValueExA = (MA_PFN_RegQueryValueExA)ma_dlsym(ma_context_get_log(pContext), pContextStateWinMM->hAdvapi32, "RegQueryValueExA"); + + *ppContextState = pContextStateWinMM; + + return MA_SUCCESS; +} + +static void ma_context_uninit__winmm(ma_context* pContext) +{ + ma_context_state_winmm* pContextStateWinMM = ma_context_get_backend_state__winmm(pContext); + + ma_dlclose(ma_context_get_log(pContext), pContextStateWinMM->hAdvapi32); + ma_dlclose(ma_context_get_log(pContext), pContextStateWinMM->hWinMM); + + ma_free(pContextStateWinMM, ma_context_get_allocation_callbacks(pContext)); +} + static ma_result ma_result_from_MMRESULT(MA_MMRESULT resultMM) { @@ -27277,7 +27940,9 @@ static ma_result ma_formats_flags_to_WAVEFORMATEX__winmm(DWORD dwFormats, WORD c static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, MA_WAVECAPSA* pCaps, ma_device_info* pDeviceInfo) { + ma_context_state_winmm* pContextStateWinmm = (ma_context_state_winmm*)ma_context_get_backend_state(pContext); WORD bitsPerSample; + ma_format format; DWORD sampleRate; ma_result result; @@ -27318,11 +27983,11 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, ma_strcpy_s(keyStr, sizeof(keyStr), "SYSTEM\\CurrentControlSet\\Control\\MediaCategories\\"); ma_strcat_s(keyStr, sizeof(keyStr), guidStr); - if (((MA_PFN_RegOpenKeyExA)pContext->win32.RegOpenKeyExA)(HKEY_LOCAL_MACHINE, keyStr, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + if (((MA_PFN_RegOpenKeyExA)pContextStateWinmm->RegOpenKeyExA)(HKEY_LOCAL_MACHINE, keyStr, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { BYTE nameFromReg[512]; DWORD nameFromRegSize = sizeof(nameFromReg); - LONG resultWin32 = ((MA_PFN_RegQueryValueExA)pContext->win32.RegQueryValueExA)(hKey, "Name", 0, NULL, (BYTE*)nameFromReg, (DWORD*)&nameFromRegSize); - ((MA_PFN_RegCloseKey)pContext->win32.RegCloseKey)(hKey); + LONG resultWin32 = ((MA_PFN_RegQueryValueExA)pContextStateWinmm->RegQueryValueExA)(hKey, "Name", 0, NULL, (BYTE*)nameFromReg, (DWORD*)&nameFromRegSize); + ((MA_PFN_RegCloseKey)pContextStateWinmm->RegCloseKey)(hKey); if (resultWin32 == ERROR_SUCCESS) { /* We have the value from the registry, so now we need to construct the name string. */ @@ -27353,25 +28018,23 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, } if (bitsPerSample == 8) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_u8; + format = ma_format_u8; } else if (bitsPerSample == 16) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; + format = ma_format_s16; } else if (bitsPerSample == 24) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_s24; + format = ma_format_s24; } else if (bitsPerSample == 32) { - pDeviceInfo->nativeDataFormats[0].format = ma_format_s32; + format = ma_format_s32; } else { return MA_FORMAT_NOT_SUPPORTED; } - pDeviceInfo->nativeDataFormats[0].channels = pCaps->wChannels; - pDeviceInfo->nativeDataFormats[0].sampleRate = sampleRate; - pDeviceInfo->nativeDataFormats[0].flags = 0; - pDeviceInfo->nativeDataFormatCount = 1; + + ma_device_info_add_native_data_format(pDeviceInfo, format, pCaps->wChannels, pCaps->wChannels, sampleRate, sampleRate); return MA_SUCCESS; } -static ma_result ma_context_get_device_info_from_WAVEOUTCAPS2(ma_context* pContext, MA_WAVEOUTCAPS2A* pCaps, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info_from_WAVEOUTCAPS2__winmm(ma_context* pContext, MA_WAVEOUTCAPS2A* pCaps, ma_device_info* pDeviceInfo) { MA_WAVECAPSA caps; @@ -27386,7 +28049,7 @@ static ma_result ma_context_get_device_info_from_WAVEOUTCAPS2(ma_context* pConte return ma_context_get_device_info_from_WAVECAPS(pContext, &caps, pDeviceInfo); } -static ma_result ma_context_get_device_info_from_WAVEINCAPS2(ma_context* pContext, MA_WAVEINCAPS2A* pCaps, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info_from_WAVEINCAPS2__winmm(ma_context* pContext, MA_WAVEINCAPS2A* pCaps, ma_device_info* pDeviceInfo) { MA_WAVECAPSA caps; @@ -27404,6 +28067,7 @@ static ma_result ma_context_get_device_info_from_WAVEINCAPS2(ma_context* pContex static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { + ma_context_state_winmm* pContextStateWinMM = ma_context_get_backend_state__winmm(pContext); UINT playbackDeviceCount; UINT captureDeviceCount; UINT iPlaybackDevice; @@ -27413,28 +28077,32 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en MA_ASSERT(callback != NULL); /* Playback. */ - playbackDeviceCount = ((MA_PFN_waveOutGetNumDevs)pContext->winmm.waveOutGetNumDevs)(); + playbackDeviceCount = pContextStateWinMM->waveOutGetNumDevs(); for (iPlaybackDevice = 0; iPlaybackDevice < playbackDeviceCount; ++iPlaybackDevice) { MA_MMRESULT result; MA_WAVEOUTCAPS2A caps; MA_ZERO_OBJECT(&caps); - result = ((MA_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(iPlaybackDevice, (MA_WAVEOUTCAPSA*)&caps, sizeof(caps)); + result = pContextStateWinMM->waveOutGetDevCapsA(iPlaybackDevice, (MA_WAVEOUTCAPSA*)&caps, sizeof(caps)); if (result == MA_MMSYSERR_NOERROR) { ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.winmm = iPlaybackDevice; + /* Default. */ /* The first enumerated device is the default device. */ if (iPlaybackDevice == 0) { deviceInfo.isDefault = MA_TRUE; } - if (ma_context_get_device_info_from_WAVEOUTCAPS2(pContext, &caps, &deviceInfo) == MA_SUCCESS) { - ma_bool32 cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - if (cbResult == MA_FALSE) { + /* ID. */ + deviceInfo.id.winmm = iPlaybackDevice; + + /* Name and Data Format. */ + if (ma_context_get_device_info_from_WAVEOUTCAPS2__winmm(pContext, &caps, &deviceInfo) == MA_SUCCESS) { + ma_device_enumeration_result cbResult = callback(ma_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_ABORT) { return MA_SUCCESS; /* Enumeration was stopped. */ } } @@ -27442,28 +28110,32 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en } /* Capture. */ - captureDeviceCount = ((MA_PFN_waveInGetNumDevs)pContext->winmm.waveInGetNumDevs)(); + captureDeviceCount = pContextStateWinMM->waveInGetNumDevs(); for (iCaptureDevice = 0; iCaptureDevice < captureDeviceCount; ++iCaptureDevice) { MA_MMRESULT result; MA_WAVEINCAPS2A caps; MA_ZERO_OBJECT(&caps); - result = ((MA_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(iCaptureDevice, (MA_WAVEINCAPSA*)&caps, sizeof(caps)); + result = pContextStateWinMM->waveInGetDevCapsA(iCaptureDevice, (MA_WAVEINCAPSA*)&caps, sizeof(caps)); if (result == MA_MMSYSERR_NOERROR) { ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.winmm = iCaptureDevice; + /* Defualt. */ /* The first enumerated device is the default device. */ if (iCaptureDevice == 0) { deviceInfo.isDefault = MA_TRUE; } - if (ma_context_get_device_info_from_WAVEINCAPS2(pContext, &caps, &deviceInfo) == MA_SUCCESS) { - ma_bool32 cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); - if (cbResult == MA_FALSE) { + /* ID. */ + deviceInfo.id.winmm = iCaptureDevice; + + /* Name and Data Format. */ + if (ma_context_get_device_info_from_WAVEINCAPS2__winmm(pContext, &caps, &deviceInfo) == MA_SUCCESS) { + ma_device_enumeration_result cbResult = callback(ma_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_ABORT) { return MA_SUCCESS; /* Enumeration was stopped. */ } } @@ -27473,79 +28145,14 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en return MA_SUCCESS; } -static ma_result ma_context_get_device_info__winmm(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) -{ - UINT winMMDeviceID; - - MA_ASSERT(pContext != NULL); - - winMMDeviceID = 0; - if (pDeviceID != NULL) { - winMMDeviceID = (UINT)pDeviceID->winmm; - } - - pDeviceInfo->id.winmm = winMMDeviceID; - - /* The first ID is the default device. */ - if (winMMDeviceID == 0) { - pDeviceInfo->isDefault = MA_TRUE; - } - - if (deviceType == ma_device_type_playback) { - MA_MMRESULT result; - MA_WAVEOUTCAPS2A caps; - - MA_ZERO_OBJECT(&caps); - - result = ((MA_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(winMMDeviceID, (MA_WAVEOUTCAPSA*)&caps, sizeof(caps)); - if (result == MA_MMSYSERR_NOERROR) { - return ma_context_get_device_info_from_WAVEOUTCAPS2(pContext, &caps, pDeviceInfo); - } - } else { - MA_MMRESULT result; - MA_WAVEINCAPS2A caps; - - MA_ZERO_OBJECT(&caps); - - result = ((MA_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(winMMDeviceID, (MA_WAVEINCAPSA*)&caps, sizeof(caps)); - if (result == MA_MMSYSERR_NOERROR) { - return ma_context_get_device_info_from_WAVEINCAPS2(pContext, &caps, pDeviceInfo); - } - } - - return MA_NO_DEVICE; -} - - -static ma_result ma_device_uninit__winmm(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_waveInClose)pDevice->pContext->winmm.waveInClose)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture); - CloseHandle((HANDLE)pDevice->winmm.hEventCapture); - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((MA_HWAVEOUT)pDevice->winmm.hDevicePlayback); - ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((MA_HWAVEOUT)pDevice->winmm.hDevicePlayback); - CloseHandle((HANDLE)pDevice->winmm.hEventPlayback); - } - - ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); - MA_ZERO_OBJECT(&pDevice->winmm); /* Safety. */ - - return MA_SUCCESS; -} - -static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__winmm(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__winmm(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate) { /* WinMM has a minimum period size of 40ms. */ ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(40, nativeSampleRate); ma_uint32 periodSizeInFrames; - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate, performanceProfile); + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate); if (periodSizeInFrames < minPeriodSizeInFrames) { periodSizeInFrames = minPeriodSizeInFrames; } @@ -27553,8 +28160,13 @@ static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__winmm(const return periodSizeInFrames; } -static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static ma_result ma_device_init__winmm(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { + ma_device_state_winmm* pDeviceStateWinMM; + const ma_device_config_winmm* pDeviceConfigWinMM = (const ma_device_config_winmm*)pDeviceBackendConfig; + ma_device_config_winmm defaultConfigWinMM; + ma_context_state_winmm* pContextStateWinMM = ma_context_get_backend_state__winmm(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); const char* errorMsg = ""; ma_result errorCode = MA_ERROR; ma_result result = MA_SUCCESS; @@ -27562,20 +28174,26 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi UINT winMMDeviceIDPlayback = 0; UINT winMMDeviceIDCapture = 0; - MA_ASSERT(pDevice != NULL); - - MA_ZERO_OBJECT(&pDevice->winmm); + if (pDeviceConfigWinMM == NULL) { + defaultConfigWinMM = ma_device_config_winmm_init(); + pDeviceConfigWinMM = &defaultConfigWinMM; + } - if (pConfig->deviceType == ma_device_type_loopback) { + if (deviceType == ma_device_type_loopback) { return MA_DEVICE_TYPE_NOT_SUPPORTED; } /* No exclusive mode with WinMM. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + if (((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } + pDeviceStateWinMM = (ma_device_state_winmm*)ma_calloc(sizeof(*pDeviceStateWinMM), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateWinMM == NULL) { + return MA_OUT_OF_MEMORY; + } + if (pDescriptorPlayback->pDeviceID != NULL) { winMMDeviceIDPlayback = (UINT)pDescriptorPlayback->pDeviceID->winmm; } @@ -27584,20 +28202,20 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi } /* The capture device needs to be initialized first. */ - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { MA_WAVEINCAPSA caps; MA_WAVEFORMATEX wf; MA_MMRESULT resultMM; /* We use an event to know when a new fragment needs to be enqueued. */ - pDevice->winmm.hEventCapture = (ma_handle)CreateEventA(NULL, TRUE, TRUE, NULL); - if (pDevice->winmm.hEventCapture == NULL) { + pDeviceStateWinMM->hEventCapture = CreateEventA(NULL, TRUE, TRUE, NULL); + if (pDeviceStateWinMM->hEventCapture == NULL) { errorMsg = "[WinMM] Failed to create event for fragment enqueuing for the capture device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; } /* The format should be based on the device's actual format. */ - if (((MA_PFN_waveInGetDevCapsA)pDevice->pContext->winmm.waveInGetDevCapsA)(winMMDeviceIDCapture, &caps, sizeof(caps)) != MA_MMSYSERR_NOERROR) { + if (pContextStateWinMM->waveInGetDevCapsA(winMMDeviceIDCapture, &caps, sizeof(caps)) != MA_MMSYSERR_NOERROR) { errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MA_FORMAT_NOT_SUPPORTED; goto on_error; } @@ -27608,7 +28226,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi goto on_error; } - resultMM = ((MA_PFN_waveInOpen)pDevice->pContext->winmm.waveInOpen)((MA_HWAVEIN*)&pDevice->winmm.hDeviceCapture, winMMDeviceIDCapture, &wf, (DWORD_PTR)pDevice->winmm.hEventCapture, (DWORD_PTR)pDevice, MA_CALLBACK_EVENT | MA_WAVE_ALLOWSYNC); + resultMM = pContextStateWinMM->waveInOpen(&pDeviceStateWinMM->hDeviceCapture, winMMDeviceIDCapture, &wf, (DWORD_PTR)pDeviceStateWinMM->hEventCapture, (DWORD_PTR)pDevice, MA_CALLBACK_EVENT | MA_WAVE_ALLOWSYNC); if (resultMM != MA_MMSYSERR_NOERROR) { errorMsg = "[WinMM] Failed to open capture device.", errorCode = MA_FAILED_TO_OPEN_BACKEND_DEVICE; goto on_error; @@ -27619,23 +28237,23 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorCapture->sampleRate = wf.nSamplesPerSec; ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); pDescriptorCapture->periodCount = pDescriptorCapture->periodCount; - pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); + pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorCapture, pDescriptorCapture->sampleRate); } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { MA_WAVEOUTCAPSA caps; MA_WAVEFORMATEX wf; MA_MMRESULT resultMM; /* We use an event to know when a new fragment needs to be enqueued. */ - pDevice->winmm.hEventPlayback = (ma_handle)CreateEventA(NULL, TRUE, TRUE, NULL); - if (pDevice->winmm.hEventPlayback == NULL) { + pDeviceStateWinMM->hEventPlayback = CreateEventA(NULL, TRUE, TRUE, NULL); + if (pDeviceStateWinMM->hEventPlayback == NULL) { errorMsg = "[WinMM] Failed to create event for fragment enqueuing for the playback device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; } /* The format should be based on the device's actual format. */ - if (((MA_PFN_waveOutGetDevCapsA)pDevice->pContext->winmm.waveOutGetDevCapsA)(winMMDeviceIDPlayback, &caps, sizeof(caps)) != MA_MMSYSERR_NOERROR) { + if (pContextStateWinMM->waveOutGetDevCapsA(winMMDeviceIDPlayback, &caps, sizeof(caps)) != MA_MMSYSERR_NOERROR) { errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MA_FORMAT_NOT_SUPPORTED; goto on_error; } @@ -27646,7 +28264,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi goto on_error; } - resultMM = ((MA_PFN_waveOutOpen)pDevice->pContext->winmm.waveOutOpen)((MA_HWAVEOUT*)&pDevice->winmm.hDevicePlayback, winMMDeviceIDPlayback, &wf, (DWORD_PTR)pDevice->winmm.hEventPlayback, (DWORD_PTR)pDevice, MA_CALLBACK_EVENT | MA_WAVE_ALLOWSYNC); + resultMM = pContextStateWinMM->waveOutOpen(&pDeviceStateWinMM->hDevicePlayback, winMMDeviceIDPlayback, &wf, (DWORD_PTR)pDeviceStateWinMM->hEventPlayback, (DWORD_PTR)pDevice, MA_CALLBACK_EVENT | MA_WAVE_ALLOWSYNC); if (resultMM != MA_MMSYSERR_NOERROR) { errorMsg = "[WinMM] Failed to open playback device.", errorCode = MA_FAILED_TO_OPEN_BACKEND_DEVICE; goto on_error; @@ -27657,114 +28275,123 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorPlayback->sampleRate = wf.nSamplesPerSec; ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); pDescriptorPlayback->periodCount = pDescriptorPlayback->periodCount; - pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); + pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorPlayback, pDescriptorPlayback->sampleRate); } /* The heap allocated data is allocated like so: [Capture WAVEHDRs][Playback WAVEHDRs][Capture Intermediary Buffer][Playback Intermediary Buffer] + + We attach this to the end of the device state struct. */ heapSize = 0; - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { heapSize += sizeof(MA_WAVEHDR)*pDescriptorCapture->periodCount + (pDescriptorCapture->periodSizeInFrames * pDescriptorCapture->periodCount * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels)); } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { heapSize += sizeof(MA_WAVEHDR)*pDescriptorPlayback->periodCount + (pDescriptorPlayback->periodSizeInFrames * pDescriptorPlayback->periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels)); } - pDevice->winmm._pHeapData = (ma_uint8*)ma_calloc(heapSize, &pDevice->pContext->allocationCallbacks); - if (pDevice->winmm._pHeapData == NULL) { - errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MA_OUT_OF_MEMORY; - goto on_error; - } + { + size_t newDeviceStateAllocSize; + ma_device_state_winmm* pNewDeviceStateWinMM; - MA_ZERO_MEMORY(pDevice->winmm._pHeapData, heapSize); + newDeviceStateAllocSize = 0; + newDeviceStateAllocSize += ma_align_64(sizeof(*pDeviceStateWinMM)); + newDeviceStateAllocSize += ma_align_64(heapSize); - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + pNewDeviceStateWinMM = (ma_device_state_winmm*)ma_realloc(pDeviceStateWinMM, newDeviceStateAllocSize, ma_device_get_allocation_callbacks(pDevice)); + if (pNewDeviceStateWinMM == NULL) { + goto on_error; + } + + pDeviceStateWinMM = pNewDeviceStateWinMM; + + pDeviceStateWinMM->_pHeapData = (ma_uint8*)ma_offset_ptr(pDeviceStateWinMM, ma_align_64(sizeof(*pDeviceStateWinMM))); + MA_ZERO_MEMORY(pDeviceStateWinMM->_pHeapData, heapSize); + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { ma_uint32 iPeriod; - if (pConfig->deviceType == ma_device_type_capture) { - pDevice->winmm.pWAVEHDRCapture = pDevice->winmm._pHeapData; - pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(MA_WAVEHDR)*(pDescriptorCapture->periodCount)); + if (deviceType == ma_device_type_capture) { + pDeviceStateWinMM->pWAVEHDRCapture = (MA_WAVEHDR*)pDeviceStateWinMM->_pHeapData; + pDeviceStateWinMM->pIntermediaryBufferCapture = pDeviceStateWinMM->_pHeapData + (sizeof(MA_WAVEHDR)*(pDescriptorCapture->periodCount)); } else { - pDevice->winmm.pWAVEHDRCapture = pDevice->winmm._pHeapData; - pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(MA_WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount)); + pDeviceStateWinMM->pWAVEHDRCapture = (MA_WAVEHDR*)pDeviceStateWinMM->_pHeapData; + pDeviceStateWinMM->pIntermediaryBufferCapture = pDeviceStateWinMM->_pHeapData + (sizeof(MA_WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount)); } /* Prepare headers. */ for (iPeriod = 0; iPeriod < pDescriptorCapture->periodCount; ++iPeriod) { ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->format, pDescriptorCapture->channels); - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].lpData = (char*)(pDevice->winmm.pIntermediaryBufferCapture + (periodSizeInBytes*iPeriod)); - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwBufferLength = periodSizeInBytes; - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwFlags = 0L; - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwLoops = 0L; - ((MA_PFN_waveInPrepareHeader)pDevice->pContext->winmm.waveInPrepareHeader)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture, &((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(MA_WAVEHDR)); - - /* - The user data of the MA_WAVEHDR structure is a single flag the controls whether or not it is ready for writing. Consider it to be named "isLocked". A value of 0 means - it's unlocked and available for writing. A value of 1 means it's locked. - */ - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwUser = 0; + pDeviceStateWinMM->pWAVEHDRCapture[iPeriod].lpData = (char*)(pDeviceStateWinMM->pIntermediaryBufferCapture + (periodSizeInBytes*iPeriod)); + pDeviceStateWinMM->pWAVEHDRCapture[iPeriod].dwBufferLength = periodSizeInBytes; + pDeviceStateWinMM->pWAVEHDRCapture[iPeriod].dwFlags = 0L; + pDeviceStateWinMM->pWAVEHDRCapture[iPeriod].dwLoops = 0L; + pContextStateWinMM->waveInPrepareHeader(pDeviceStateWinMM->hDeviceCapture, &pDeviceStateWinMM->pWAVEHDRCapture[iPeriod], sizeof(MA_WAVEHDR)); } } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { ma_uint32 iPeriod; - if (pConfig->deviceType == ma_device_type_playback) { - pDevice->winmm.pWAVEHDRPlayback = pDevice->winmm._pHeapData; - pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(MA_WAVEHDR)*pDescriptorPlayback->periodCount); + if (deviceType == ma_device_type_playback) { + pDeviceStateWinMM->pWAVEHDRPlayback = (MA_WAVEHDR*)pDeviceStateWinMM->_pHeapData; + pDeviceStateWinMM->pIntermediaryBufferPlayback = pDeviceStateWinMM->_pHeapData + (sizeof(MA_WAVEHDR)*pDescriptorPlayback->periodCount); } else { - pDevice->winmm.pWAVEHDRPlayback = pDevice->winmm._pHeapData + (sizeof(MA_WAVEHDR)*(pDescriptorCapture->periodCount)); - pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(MA_WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount)) + (pDescriptorCapture->periodSizeInFrames*pDescriptorCapture->periodCount*ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels)); + pDeviceStateWinMM->pWAVEHDRPlayback = (MA_WAVEHDR*)pDeviceStateWinMM->_pHeapData + pDescriptorCapture->periodCount; + pDeviceStateWinMM->pIntermediaryBufferPlayback = pDeviceStateWinMM->_pHeapData + (sizeof(MA_WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount)) + (pDescriptorCapture->periodSizeInFrames*pDescriptorCapture->periodCount*ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels)); } /* Prepare headers. */ for (iPeriod = 0; iPeriod < pDescriptorPlayback->periodCount; ++iPeriod) { ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->format, pDescriptorPlayback->channels); - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].lpData = (char*)(pDevice->winmm.pIntermediaryBufferPlayback + (periodSizeInBytes*iPeriod)); - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwBufferLength = periodSizeInBytes; - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwFlags = 0L; - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwLoops = 0L; - ((MA_PFN_waveOutPrepareHeader)pDevice->pContext->winmm.waveOutPrepareHeader)((MA_HWAVEOUT)pDevice->winmm.hDevicePlayback, &((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(MA_WAVEHDR)); + pDeviceStateWinMM->pWAVEHDRPlayback[iPeriod].lpData = (char*)(pDeviceStateWinMM->pIntermediaryBufferPlayback + (periodSizeInBytes*iPeriod)); + pDeviceStateWinMM->pWAVEHDRPlayback[iPeriod].dwBufferLength = periodSizeInBytes; + pDeviceStateWinMM->pWAVEHDRPlayback[iPeriod].dwFlags = 0L; + pDeviceStateWinMM->pWAVEHDRPlayback[iPeriod].dwLoops = 0L; + pContextStateWinMM->waveOutPrepareHeader(pDeviceStateWinMM->hDevicePlayback, &pDeviceStateWinMM->pWAVEHDRPlayback[iPeriod], sizeof(MA_WAVEHDR)); /* - The user data of the MA_WAVEHDR structure is a single flag the controls whether or not it is ready for writing. Consider it to be named "isLocked". A value of 0 means - it's unlocked and available for writing. A value of 1 means it's locked. + We use the WHDR_DONE flag to determine whether not not a buffer is available for writing. Since in playback + mode we want buffers to be writable from the start, we will set the flag here at initialization time. */ - ((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwUser = 0; + pDeviceStateWinMM->pWAVEHDRPlayback[iPeriod].dwFlags |= MA_WHDR_DONE; } } + *ppDeviceState = pDeviceStateWinMM; + return MA_SUCCESS; on_error: if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (pDevice->winmm.pWAVEHDRCapture != NULL) { + if (pDeviceStateWinMM->pWAVEHDRCapture != NULL) { ma_uint32 iPeriod; for (iPeriod = 0; iPeriod < pDescriptorCapture->periodCount; ++iPeriod) { - ((MA_PFN_waveInUnprepareHeader)pDevice->pContext->winmm.waveInUnprepareHeader)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture, &((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(MA_WAVEHDR)); + pContextStateWinMM->waveInUnprepareHeader(pDeviceStateWinMM->hDeviceCapture, &pDeviceStateWinMM->pWAVEHDRCapture[iPeriod], sizeof(MA_WAVEHDR)); } } - ((MA_PFN_waveInClose)pDevice->pContext->winmm.waveInClose)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture); + pContextStateWinMM->waveInClose(pDeviceStateWinMM->hDeviceCapture); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - if (pDevice->winmm.pWAVEHDRCapture != NULL) { + if (pDeviceStateWinMM->pWAVEHDRCapture != NULL) { ma_uint32 iPeriod; for (iPeriod = 0; iPeriod < pDescriptorPlayback->periodCount; ++iPeriod) { - ((MA_PFN_waveOutUnprepareHeader)pDevice->pContext->winmm.waveOutUnprepareHeader)((MA_HWAVEOUT)pDevice->winmm.hDevicePlayback, &((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(MA_WAVEHDR)); + pContextStateWinMM->waveOutUnprepareHeader(pDeviceStateWinMM->hDevicePlayback, &pDeviceStateWinMM->pWAVEHDRPlayback[iPeriod], sizeof(MA_WAVEHDR)); } } - ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((MA_HWAVEOUT)pDevice->winmm.hDevicePlayback); + pContextStateWinMM->waveOutClose(pDeviceStateWinMM->hDevicePlayback); } - ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); + ma_free(pDeviceStateWinMM, ma_device_get_allocation_callbacks(pDevice)); if (errorMsg != NULL && errorMsg[0] != '\0') { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "%s", errorMsg); @@ -27773,41 +28400,60 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi return errorCode; } +static void ma_device_uninit__winmm(ma_device* pDevice) +{ + ma_device_state_winmm* pDeviceStateWinMM = ma_device_get_backend_state__winmm(pDevice); + ma_context_state_winmm* pContextStateWinMM = ma_context_get_backend_state__winmm(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStateWinMM->waveInClose(pDeviceStateWinMM->hDeviceCapture); + CloseHandle(pDeviceStateWinMM->hEventCapture); + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStateWinMM->waveOutReset(pDeviceStateWinMM->hDevicePlayback); + pContextStateWinMM->waveOutClose(pDeviceStateWinMM->hDevicePlayback); + CloseHandle(pDeviceStateWinMM->hEventPlayback); + } + + ma_free(pDeviceStateWinMM, ma_device_get_allocation_callbacks(pDevice)); +} + static ma_result ma_device_start__winmm(ma_device* pDevice) { - MA_ASSERT(pDevice != NULL); + ma_device_state_winmm* pDeviceStateWinMM = ma_device_get_backend_state__winmm(pDevice); + ma_context_state_winmm* pContextStateWinMM = ma_context_get_backend_state__winmm(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { MA_MMRESULT resultMM; MA_WAVEHDR* pWAVEHDR; ma_uint32 iPeriod; - pWAVEHDR = (MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture; + pWAVEHDR = pDeviceStateWinMM->pWAVEHDRCapture; /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ - ResetEvent((HANDLE)pDevice->winmm.hEventCapture); + ResetEvent(pDeviceStateWinMM->hEventCapture); /* To start the device we attach all of the buffers and then start it. As the buffers are filled with data we will get notifications. */ for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { - resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture, &((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(MA_WAVEHDR)); + resultMM = pContextStateWinMM->waveInAddBuffer(pDeviceStateWinMM->hDeviceCapture, &pDeviceStateWinMM->pWAVEHDRCapture[iPeriod], sizeof(MA_WAVEHDR)); if (resultMM != MA_MMSYSERR_NOERROR) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture."); return ma_result_from_MMRESULT(resultMM); } - - /* Make sure all of the buffers start out locked. We don't want to access them until the backend tells us we can. */ - pWAVEHDR[iPeriod].dwUser = 1; /* 1 = locked. */ } /* Capture devices need to be explicitly started, unlike playback devices. */ - resultMM = ((MA_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture); + resultMM = pContextStateWinMM->waveInStart(pDeviceStateWinMM->hDeviceCapture); if (resultMM != MA_MMSYSERR_NOERROR) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device."); return ma_result_from_MMRESULT(resultMM); } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { /* Don't need to do anything for playback. It'll be started automatically in ma_device_start__winmm(). */ } @@ -27816,42 +28462,39 @@ static ma_result ma_device_start__winmm(ma_device* pDevice) static ma_result ma_device_stop__winmm(ma_device* pDevice) { + ma_device_state_winmm* pDeviceStateWinMM = ma_device_get_backend_state__winmm(pDevice); + ma_context_state_winmm* pContextStateWinMM = ma_context_get_backend_state__winmm(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); MA_MMRESULT resultMM; - MA_ASSERT(pDevice != NULL); - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (pDevice->winmm.hDeviceCapture == NULL) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (pDeviceStateWinMM->hDeviceCapture == NULL) { return MA_INVALID_ARGS; } - resultMM = ((MA_PFN_waveInReset)pDevice->pContext->winmm.waveInReset)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture); + resultMM = pContextStateWinMM->waveInReset(pDeviceStateWinMM->hDeviceCapture); if (resultMM != MA_MMSYSERR_NOERROR) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset capture device."); } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { ma_uint32 iPeriod; MA_WAVEHDR* pWAVEHDR; - if (pDevice->winmm.hDevicePlayback == NULL) { + if (pDeviceStateWinMM->hDevicePlayback == NULL) { return MA_INVALID_ARGS; } /* We need to drain the device. To do this we just loop over each header and if it's locked just wait for the event. */ - pWAVEHDR = (MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback; + pWAVEHDR = pDeviceStateWinMM->pWAVEHDRPlayback; for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; iPeriod += 1) { - if (pWAVEHDR[iPeriod].dwUser == 1) { /* 1 = locked. */ - if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventPlayback, INFINITE) != WAIT_OBJECT_0) { - break; /* An error occurred so just abandon ship and stop the device without draining. */ - } - - pWAVEHDR[iPeriod].dwUser = 0; + if (WaitForSingleObject(pDeviceStateWinMM->hEventPlayback, INFINITE) != WAIT_OBJECT_0) { + break; /* An error occurred so just abandon ship and stop the device without draining. */ } } - resultMM = ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((MA_HWAVEOUT)pDevice->winmm.hDevicePlayback); + resultMM = pContextStateWinMM->waveOutReset(pDeviceStateWinMM->hDevicePlayback); if (resultMM != MA_MMSYSERR_NOERROR) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset playback device."); } @@ -27860,240 +28503,138 @@ static ma_result ma_device_stop__winmm(ma_device* pDevice) return MA_SUCCESS; } -static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +static ma_result ma_device_step__winmm(ma_device* pDevice, ma_blocking_mode blockingMode) { - ma_result result = MA_SUCCESS; + ma_device_state_winmm* pDeviceStateWinMM = ma_device_get_backend_state__winmm(pDevice); + ma_context_state_winmm* pContextStateWinMM = ma_context_get_backend_state__winmm(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + HANDLE handles[2]; + DWORD handleCount; + DWORD timeout = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? INFINITE : 0; + DWORD waitResult; MA_MMRESULT resultMM; - ma_uint32 totalFramesWritten; - MA_WAVEHDR* pWAVEHDR; - - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pPCMFrames != NULL); - if (pFramesWritten != NULL) { - *pFramesWritten = 0; + handleCount = 0; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + handles[handleCount++] = pDeviceStateWinMM->hEventCapture; + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + handles[handleCount++] = pDeviceStateWinMM->hEventPlayback; } - pWAVEHDR = (MA_WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback; + waitResult = WaitForMultipleObjects(handleCount, handles, FALSE, timeout); + if (waitResult == WAIT_FAILED) { + return MA_ERROR; /* Backend is in an erroneous state. */ + } - /* Keep processing as much data as possible. */ - totalFramesWritten = 0; - while (totalFramesWritten < frameCount) { - /* If the current header has some space available we need to write part of it. */ - if (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser == 0) { /* 0 = unlocked. */ - /* - This header has room in it. We copy as much of it as we can. If we end up fully consuming the buffer we need to - write it out and move on to the next iteration. - */ - ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 framesRemainingInHeader = (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwBufferLength/bpf) - pDevice->winmm.headerFramesConsumedPlayback; + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } - ma_uint32 framesToCopy = ma_min(framesRemainingInHeader, (frameCount - totalFramesWritten)); - const void* pSrc = ma_offset_ptr(pPCMFrames, totalFramesWritten*bpf); - void* pDst = ma_offset_ptr(pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].lpData, pDevice->winmm.headerFramesConsumedPlayback*bpf); - MA_COPY_MEMORY(pDst, pSrc, framesToCopy*bpf); + if (waitResult >= WAIT_OBJECT_0 && waitResult <= WAIT_OBJECT_0 + handleCount-1) { + /* Something is available for processing. */ + HANDLE handle = handles[waitResult - WAIT_OBJECT_0]; + ma_uint32 frameCount; - pDevice->winmm.headerFramesConsumedPlayback += framesToCopy; - totalFramesWritten += framesToCopy; + /* Capture. */ + if (handle == pDeviceStateWinMM->hEventCapture && (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex)) { + MA_WAVEHDR* pWAVEHDR = pDeviceStateWinMM->pWAVEHDRCapture; - /* If we've consumed the buffer entirely we need to write it out to the device. */ - if (pDevice->winmm.headerFramesConsumedPlayback == (pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwBufferLength/bpf)) { - pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser = 1; /* 1 = locked. */ - pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwFlags &= ~MA_WHDR_DONE; /* <-- Need to make sure the WHDR_DONE flag is unset. */ + /* Consume any completed header. */ + while ((pWAVEHDR[pDeviceStateWinMM->iNextHeaderCapture].dwFlags & MA_WHDR_DONE) != 0) { + pWAVEHDR[pDeviceStateWinMM->iNextHeaderCapture].dwFlags &= ~MA_WHDR_DONE; - /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ - ResetEvent((HANDLE)pDevice->winmm.hEventPlayback); + frameCount = pWAVEHDR[pDeviceStateWinMM->iNextHeaderCapture].dwBufferLength / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_device_handle_backend_data_callback(pDevice, NULL, pWAVEHDR[pDeviceStateWinMM->iNextHeaderCapture].lpData, frameCount); /* The device will be started here. */ - resultMM = ((MA_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((MA_HWAVEOUT)pDevice->winmm.hDevicePlayback, &pWAVEHDR[pDevice->winmm.iNextHeaderPlayback], sizeof(MA_WAVEHDR)); + resultMM = pContextStateWinMM->waveInAddBuffer(pDeviceStateWinMM->hDeviceCapture, &pDeviceStateWinMM->pWAVEHDRCapture[pDeviceStateWinMM->iNextHeaderCapture], sizeof(MA_WAVEHDR)); if (resultMM != MA_MMSYSERR_NOERROR) { - result = ma_result_from_MMRESULT(resultMM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed."); - break; + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed."); + return ma_result_from_MMRESULT(resultMM); } /* Make sure we move to the next header. */ - pDevice->winmm.iNextHeaderPlayback = (pDevice->winmm.iNextHeaderPlayback + 1) % pDevice->playback.internalPeriods; - pDevice->winmm.headerFramesConsumedPlayback = 0; + pDeviceStateWinMM->iNextHeaderCapture = (pDeviceStateWinMM->iNextHeaderCapture + 1) % pDevice->capture.internalPeriods; } - /* If at this point we have consumed the entire input buffer we can return. */ - MA_ASSERT(totalFramesWritten <= frameCount); - if (totalFramesWritten == frameCount) { - break; - } - - /* Getting here means there's more to process. */ - continue; + /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ + ResetEvent(pDeviceStateWinMM->hEventCapture); } - /* Getting here means there isn't enough room in the buffer and we need to wait for one to become available. */ - if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventPlayback, INFINITE) != WAIT_OBJECT_0) { - result = MA_ERROR; - break; - } - - /* Something happened. If the next buffer has been marked as done we need to reset a bit of state. */ - if ((pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwFlags & MA_WHDR_DONE) != 0) { - pWAVEHDR[pDevice->winmm.iNextHeaderPlayback].dwUser = 0; /* 0 = unlocked (make it available for writing). */ - pDevice->winmm.headerFramesConsumedPlayback = 0; - } - - /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != ma_device_state_started) { - break; - } - } - - if (pFramesWritten != NULL) { - *pFramesWritten = totalFramesWritten; - } - - return result; -} - -static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) -{ - ma_result result = MA_SUCCESS; - MA_MMRESULT resultMM; - ma_uint32 totalFramesRead; - MA_WAVEHDR* pWAVEHDR; - - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pPCMFrames != NULL); - - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - pWAVEHDR = (MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture; - - /* Keep processing as much data as possible. */ - totalFramesRead = 0; - while (totalFramesRead < frameCount) { - /* If the current header has some space available we need to write part of it. */ - if (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser == 0) { /* 0 = unlocked. */ - /* The buffer is available for reading. If we fully consume it we need to add it back to the buffer. */ - ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 framesRemainingInHeader = (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwBufferLength/bpf) - pDevice->winmm.headerFramesConsumedCapture; - - ma_uint32 framesToCopy = ma_min(framesRemainingInHeader, (frameCount - totalFramesRead)); - const void* pSrc = ma_offset_ptr(pWAVEHDR[pDevice->winmm.iNextHeaderCapture].lpData, pDevice->winmm.headerFramesConsumedCapture*bpf); - void* pDst = ma_offset_ptr(pPCMFrames, totalFramesRead*bpf); - MA_COPY_MEMORY(pDst, pSrc, framesToCopy*bpf); - - pDevice->winmm.headerFramesConsumedCapture += framesToCopy; - totalFramesRead += framesToCopy; + /* Playback. */ + if (handle == pDeviceStateWinMM->hEventPlayback && (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex)) { + MA_WAVEHDR* pWAVEHDR = pDeviceStateWinMM->pWAVEHDRPlayback; - /* If we've consumed the buffer entirely we need to add it back to the device. */ - if (pDevice->winmm.headerFramesConsumedCapture == (pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwBufferLength/bpf)) { - pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser = 1; /* 1 = locked. */ - pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwFlags &= ~MA_WHDR_DONE; /* <-- Need to make sure the WHDR_DONE flag is unset. */ + /* Fill out any completed header with fresh data. */ + while ((pWAVEHDR[pDeviceStateWinMM->iNextHeaderPlayback].dwFlags & MA_WHDR_DONE) != 0) { + pWAVEHDR[pDeviceStateWinMM->iNextHeaderPlayback].dwFlags &= ~MA_WHDR_DONE; - /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ - ResetEvent((HANDLE)pDevice->winmm.hEventCapture); + frameCount = pWAVEHDR[pDeviceStateWinMM->iNextHeaderPlayback].dwBufferLength / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_device_handle_backend_data_callback(pDevice, pWAVEHDR[pDeviceStateWinMM->iNextHeaderPlayback].lpData, NULL, frameCount); /* The device will be started here. */ - resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((MA_HWAVEIN)pDevice->winmm.hDeviceCapture, &((MA_WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[pDevice->winmm.iNextHeaderCapture], sizeof(MA_WAVEHDR)); + resultMM = pContextStateWinMM->waveOutWrite(pDeviceStateWinMM->hDevicePlayback, &pWAVEHDR[pDeviceStateWinMM->iNextHeaderPlayback], sizeof(MA_WAVEHDR)); if (resultMM != MA_MMSYSERR_NOERROR) { - result = ma_result_from_MMRESULT(resultMM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed."); - break; + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed."); + return ma_result_from_MMRESULT(resultMM); } /* Make sure we move to the next header. */ - pDevice->winmm.iNextHeaderCapture = (pDevice->winmm.iNextHeaderCapture + 1) % pDevice->capture.internalPeriods; - pDevice->winmm.headerFramesConsumedCapture = 0; + pDeviceStateWinMM->iNextHeaderPlayback = (pDeviceStateWinMM->iNextHeaderPlayback + 1) % pDevice->playback.internalPeriods; } - /* If at this point we have filled the entire input buffer we can return. */ - MA_ASSERT(totalFramesRead <= frameCount); - if (totalFramesRead == frameCount) { - break; - } - - /* Getting here means there's more to process. */ - continue; - } - - /* Getting here means there isn't enough any data left to send to the client which means we need to wait for more. */ - if (WaitForSingleObject((HANDLE)pDevice->winmm.hEventCapture, INFINITE) != WAIT_OBJECT_0) { - result = MA_ERROR; - break; - } - - /* Something happened. If the next buffer has been marked as done we need to reset a bit of state. */ - if ((pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwFlags & MA_WHDR_DONE) != 0) { - pWAVEHDR[pDevice->winmm.iNextHeaderCapture].dwUser = 0; /* 0 = unlocked (make it available for reading). */ - pDevice->winmm.headerFramesConsumedCapture = 0; - } - - /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != ma_device_state_started) { - break; + /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ + ResetEvent(pDeviceStateWinMM->hEventPlayback); } + } else { + /* Most likely a timeout in non-blocking mode. Nothing to do. */ } - if (pFramesRead != NULL) { - *pFramesRead = totalFramesRead; - } - - return result; + return MA_SUCCESS; } -static ma_result ma_context_uninit__winmm(ma_context* pContext) +static ma_device_backend_vtable ma_gDeviceBackendVTable_WinMM = { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_winmm); + ma_backend_info__winmm, + ma_context_init__winmm, + ma_context_uninit__winmm, + ma_context_enumerate_devices__winmm, + ma_device_init__winmm, + ma_device_uninit__winmm, + ma_device_start__winmm, + ma_device_stop__winmm, + ma_device_step__winmm, + NULL /* onDeviceWakeup */ +}; - ma_dlclose(ma_context_get_log(pContext), pContext->winmm.hWinMM); - return MA_SUCCESS; +ma_device_backend_vtable* ma_device_backend_winmm = &ma_gDeviceBackendVTable_WinMM; +#else +ma_device_backend_vtable* ma_device_backend_winmm = NULL; +#endif + +MA_API ma_device_backend_vtable* ma_winmm_get_vtable(void) +{ + return ma_device_backend_winmm; } -static ma_result ma_context_init__winmm(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +MA_API ma_context_config_winmm ma_context_config_winmm_init(void) { - MA_ASSERT(pContext != NULL); + ma_context_config_winmm config; - (void)pConfig; + MA_ZERO_OBJECT(&config); - pContext->winmm.hWinMM = ma_dlopen(ma_context_get_log(pContext), "winmm.dll"); - if (pContext->winmm.hWinMM == NULL) { - return MA_NO_BACKEND; - } + return config; +} - pContext->winmm.waveOutGetNumDevs = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutGetNumDevs"); - pContext->winmm.waveOutGetDevCapsA = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutGetDevCapsA"); - pContext->winmm.waveOutOpen = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutOpen"); - pContext->winmm.waveOutClose = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutClose"); - pContext->winmm.waveOutPrepareHeader = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutPrepareHeader"); - pContext->winmm.waveOutUnprepareHeader = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutUnprepareHeader"); - pContext->winmm.waveOutWrite = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutWrite"); - pContext->winmm.waveOutReset = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveOutReset"); - pContext->winmm.waveInGetNumDevs = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInGetNumDevs"); - pContext->winmm.waveInGetDevCapsA = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInGetDevCapsA"); - pContext->winmm.waveInOpen = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInOpen"); - pContext->winmm.waveInClose = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInClose"); - pContext->winmm.waveInPrepareHeader = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInPrepareHeader"); - pContext->winmm.waveInUnprepareHeader = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInUnprepareHeader"); - pContext->winmm.waveInAddBuffer = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInAddBuffer"); - pContext->winmm.waveInStart = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInStart"); - pContext->winmm.waveInReset = ma_dlsym(ma_context_get_log(pContext), pContext->winmm.hWinMM, "waveInReset"); +MA_API ma_device_config_winmm ma_device_config_winmm_init(void) +{ + ma_device_config_winmm config; - pCallbacks->onContextInit = ma_context_init__winmm; - pCallbacks->onContextUninit = ma_context_uninit__winmm; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__winmm; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__winmm; - pCallbacks->onDeviceInit = ma_device_init__winmm; - pCallbacks->onDeviceUninit = ma_device_uninit__winmm; - pCallbacks->onDeviceStart = ma_device_start__winmm; - pCallbacks->onDeviceStop = ma_device_stop__winmm; - pCallbacks->onDeviceRead = ma_device_read__winmm; - pCallbacks->onDeviceWrite = ma_device_write__winmm; - pCallbacks->onDeviceDataLoop = NULL; /* This is a blocking read-write API, so this can be NULL since miniaudio will manage the audio thread for us. */ + MA_ZERO_OBJECT(&config); - return MA_SUCCESS; + return config; } -#endif @@ -28103,13 +28644,27 @@ static ma_result ma_context_init__winmm(ma_context* pContext, const ma_context_c ALSA Backend ******************************************************************************/ -#ifdef MA_HAS_ALSA +/* +Our ALSA backend uses poll() and eventfd() which are not available on very old +versions of Linux so we'll need check for it and disable the ALSA backend if +necessary. This should be a non-issue in practice because ALSA itself won't be +available in old versions of Linux anyway. +*/ +#if defined(MA_HAS_ALSA) + #if (!defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 199309L) + #undef MA_HAS_ALSA + #endif +#endif +#if defined(MA_HAS_ALSA) #include /* poll(), struct pollfd */ #include /* eventfd() */ -#ifdef MA_NO_RUNTIME_LINKING +#ifndef MA_ALSA_MAX_NATIVE_CHANNELS +#define MA_ALSA_MAX_NATIVE_CHANNELS 64 +#endif +#ifdef MA_NO_RUNTIME_LINKING /* asoundlib.h marks some functions with "inline" which isn't always supported. Need to emulate it. */ #if !defined(__cplusplus) #if defined(__STRICT_ANSI__) @@ -28138,9 +28693,21 @@ typedef snd_pcm_info_t ma_snd_pcm_info_t; typedef snd_pcm_channel_area_t ma_snd_pcm_channel_area_t; typedef snd_pcm_chmap_t ma_snd_pcm_chmap_t; typedef snd_pcm_state_t ma_snd_pcm_state_t; +typedef snd_pcm_type_t ma_snd_pcm_type_t; + +/* snd_pcm_type_t */ +#define MA_SND_PCM_TYPE_HW SND_PCM_TYPE_HW /* snd_pcm_state_t */ +#define MA_SND_PCM_STATE_OPEN SND_PCM_STATE_OPEN +#define MA_SND_PCM_STATE_SETUP SND_PCM_STATE_SETUP +#define MA_SND_PCM_STATE_PREPARED SND_PCM_STATE_PREPARED +#define MA_SND_PCM_STATE_RUNNING SND_PCM_STATE_RUNNING #define MA_SND_PCM_STATE_XRUN SND_PCM_STATE_XRUN +#define MA_SND_PCM_STATE_DRAINING SND_PCM_STATE_DRAINING +#define MA_SND_PCM_STATE_PAUSED SND_PCM_STATE_PAUSED +#define MA_SND_PCM_STATE_SUSPENDED SND_PCM_STATE_SUSPENDED +#define MA_SND_PCM_STATE_DISCONNECTED SND_PCM_STATE_DISCONNECTED /* snd_pcm_stream_t */ #define MA_SND_PCM_STREAM_PLAYBACK SND_PCM_STREAM_PLAYBACK @@ -28211,6 +28778,7 @@ typedef snd_pcm_state_t ma_snd_pcm_state_t; #define MA_SND_CHMAP_BRC SND_CHMAP_BRC /* Open mode flags. */ +#define MA_SND_PCM_NONBLOCK SND_PCM_NONBLOCK #define MA_SND_PCM_NO_AUTO_RESAMPLE SND_PCM_NO_AUTO_RESAMPLE #define MA_SND_PCM_NO_AUTO_CHANNELS SND_PCM_NO_AUTO_CHANNELS #define MA_SND_PCM_NO_AUTO_FORMAT SND_PCM_NO_AUTO_FORMAT @@ -28222,6 +28790,7 @@ typedef int ma_snd_pcm_stream_t; typedef int ma_snd_pcm_format_t; typedef int ma_snd_pcm_access_t; typedef int ma_snd_pcm_state_t; +typedef int ma_snd_pcm_type_t; typedef struct ma_snd_pcm_t ma_snd_pcm_t; typedef struct ma_snd_pcm_hw_params_t ma_snd_pcm_hw_params_t; typedef struct ma_snd_pcm_sw_params_t ma_snd_pcm_sw_params_t; @@ -28239,95 +28808,107 @@ typedef struct unsigned int pos[1]; } ma_snd_pcm_chmap_t; +/* snd_pcm_type_t */ +#define MA_SND_PCM_TYPE_HW 0 + /* snd_pcm_state_t */ -#define MA_SND_PCM_STATE_OPEN 0 -#define MA_SND_PCM_STATE_SETUP 1 -#define MA_SND_PCM_STATE_PREPARED 2 -#define MA_SND_PCM_STATE_RUNNING 3 -#define MA_SND_PCM_STATE_XRUN 4 -#define MA_SND_PCM_STATE_DRAINING 5 -#define MA_SND_PCM_STATE_PAUSED 6 -#define MA_SND_PCM_STATE_SUSPENDED 7 -#define MA_SND_PCM_STATE_DISCONNECTED 8 +#define MA_SND_PCM_STATE_OPEN 0 +#define MA_SND_PCM_STATE_SETUP 1 +#define MA_SND_PCM_STATE_PREPARED 2 +#define MA_SND_PCM_STATE_RUNNING 3 +#define MA_SND_PCM_STATE_XRUN 4 +#define MA_SND_PCM_STATE_DRAINING 5 +#define MA_SND_PCM_STATE_PAUSED 6 +#define MA_SND_PCM_STATE_SUSPENDED 7 +#define MA_SND_PCM_STATE_DISCONNECTED 8 /* snd_pcm_stream_t */ -#define MA_SND_PCM_STREAM_PLAYBACK 0 -#define MA_SND_PCM_STREAM_CAPTURE 1 +#define MA_SND_PCM_STREAM_PLAYBACK 0 +#define MA_SND_PCM_STREAM_CAPTURE 1 /* snd_pcm_format_t */ -#define MA_SND_PCM_FORMAT_UNKNOWN -1 -#define MA_SND_PCM_FORMAT_U8 1 -#define MA_SND_PCM_FORMAT_S16_LE 2 -#define MA_SND_PCM_FORMAT_S16_BE 3 -#define MA_SND_PCM_FORMAT_S24_LE 6 -#define MA_SND_PCM_FORMAT_S24_BE 7 -#define MA_SND_PCM_FORMAT_S32_LE 10 -#define MA_SND_PCM_FORMAT_S32_BE 11 -#define MA_SND_PCM_FORMAT_FLOAT_LE 14 -#define MA_SND_PCM_FORMAT_FLOAT_BE 15 -#define MA_SND_PCM_FORMAT_FLOAT64_LE 16 -#define MA_SND_PCM_FORMAT_FLOAT64_BE 17 -#define MA_SND_PCM_FORMAT_MU_LAW 20 -#define MA_SND_PCM_FORMAT_A_LAW 21 -#define MA_SND_PCM_FORMAT_S24_3LE 32 -#define MA_SND_PCM_FORMAT_S24_3BE 33 +#define MA_SND_PCM_FORMAT_UNKNOWN -1 +#define MA_SND_PCM_FORMAT_U8 1 +#define MA_SND_PCM_FORMAT_S16_LE 2 +#define MA_SND_PCM_FORMAT_S16_BE 3 +#define MA_SND_PCM_FORMAT_S24_LE 6 +#define MA_SND_PCM_FORMAT_S24_BE 7 +#define MA_SND_PCM_FORMAT_S32_LE 10 +#define MA_SND_PCM_FORMAT_S32_BE 11 +#define MA_SND_PCM_FORMAT_FLOAT_LE 14 +#define MA_SND_PCM_FORMAT_FLOAT_BE 15 +#define MA_SND_PCM_FORMAT_FLOAT64_LE 16 +#define MA_SND_PCM_FORMAT_FLOAT64_BE 17 +#define MA_SND_PCM_FORMAT_MU_LAW 20 +#define MA_SND_PCM_FORMAT_A_LAW 21 +#define MA_SND_PCM_FORMAT_S24_3LE 32 +#define MA_SND_PCM_FORMAT_S24_3BE 33 /* snd_pcm_access_t */ -#define MA_SND_PCM_ACCESS_MMAP_INTERLEAVED 0 -#define MA_SND_PCM_ACCESS_MMAP_NONINTERLEAVED 1 -#define MA_SND_PCM_ACCESS_MMAP_COMPLEX 2 -#define MA_SND_PCM_ACCESS_RW_INTERLEAVED 3 -#define MA_SND_PCM_ACCESS_RW_NONINTERLEAVED 4 +#define MA_SND_PCM_ACCESS_MMAP_INTERLEAVED 0 +#define MA_SND_PCM_ACCESS_MMAP_NONINTERLEAVED 1 +#define MA_SND_PCM_ACCESS_MMAP_COMPLEX 2 +#define MA_SND_PCM_ACCESS_RW_INTERLEAVED 3 +#define MA_SND_PCM_ACCESS_RW_NONINTERLEAVED 4 /* Channel positions. */ -#define MA_SND_CHMAP_UNKNOWN 0 -#define MA_SND_CHMAP_NA 1 -#define MA_SND_CHMAP_MONO 2 -#define MA_SND_CHMAP_FL 3 -#define MA_SND_CHMAP_FR 4 -#define MA_SND_CHMAP_RL 5 -#define MA_SND_CHMAP_RR 6 -#define MA_SND_CHMAP_FC 7 -#define MA_SND_CHMAP_LFE 8 -#define MA_SND_CHMAP_SL 9 -#define MA_SND_CHMAP_SR 10 -#define MA_SND_CHMAP_RC 11 -#define MA_SND_CHMAP_FLC 12 -#define MA_SND_CHMAP_FRC 13 -#define MA_SND_CHMAP_RLC 14 -#define MA_SND_CHMAP_RRC 15 -#define MA_SND_CHMAP_FLW 16 -#define MA_SND_CHMAP_FRW 17 -#define MA_SND_CHMAP_FLH 18 -#define MA_SND_CHMAP_FCH 19 -#define MA_SND_CHMAP_FRH 20 -#define MA_SND_CHMAP_TC 21 -#define MA_SND_CHMAP_TFL 22 -#define MA_SND_CHMAP_TFR 23 -#define MA_SND_CHMAP_TFC 24 -#define MA_SND_CHMAP_TRL 25 -#define MA_SND_CHMAP_TRR 26 -#define MA_SND_CHMAP_TRC 27 -#define MA_SND_CHMAP_TFLC 28 -#define MA_SND_CHMAP_TFRC 29 -#define MA_SND_CHMAP_TSL 30 -#define MA_SND_CHMAP_TSR 31 -#define MA_SND_CHMAP_LLFE 32 -#define MA_SND_CHMAP_RLFE 33 -#define MA_SND_CHMAP_BC 34 -#define MA_SND_CHMAP_BLC 35 -#define MA_SND_CHMAP_BRC 36 +#define MA_SND_CHMAP_UNKNOWN 0 +#define MA_SND_CHMAP_NA 1 +#define MA_SND_CHMAP_MONO 2 +#define MA_SND_CHMAP_FL 3 +#define MA_SND_CHMAP_FR 4 +#define MA_SND_CHMAP_RL 5 +#define MA_SND_CHMAP_RR 6 +#define MA_SND_CHMAP_FC 7 +#define MA_SND_CHMAP_LFE 8 +#define MA_SND_CHMAP_SL 9 +#define MA_SND_CHMAP_SR 10 +#define MA_SND_CHMAP_RC 11 +#define MA_SND_CHMAP_FLC 12 +#define MA_SND_CHMAP_FRC 13 +#define MA_SND_CHMAP_RLC 14 +#define MA_SND_CHMAP_RRC 15 +#define MA_SND_CHMAP_FLW 16 +#define MA_SND_CHMAP_FRW 17 +#define MA_SND_CHMAP_FLH 18 +#define MA_SND_CHMAP_FCH 19 +#define MA_SND_CHMAP_FRH 20 +#define MA_SND_CHMAP_TC 21 +#define MA_SND_CHMAP_TFL 22 +#define MA_SND_CHMAP_TFR 23 +#define MA_SND_CHMAP_TFC 24 +#define MA_SND_CHMAP_TRL 25 +#define MA_SND_CHMAP_TRR 26 +#define MA_SND_CHMAP_TRC 27 +#define MA_SND_CHMAP_TFLC 28 +#define MA_SND_CHMAP_TFRC 29 +#define MA_SND_CHMAP_TSL 30 +#define MA_SND_CHMAP_TSR 31 +#define MA_SND_CHMAP_LLFE 32 +#define MA_SND_CHMAP_RLFE 33 +#define MA_SND_CHMAP_BC 34 +#define MA_SND_CHMAP_BLC 35 +#define MA_SND_CHMAP_BRC 36 /* Open mode flags. */ -#define MA_SND_PCM_NO_AUTO_RESAMPLE 0x00010000 -#define MA_SND_PCM_NO_AUTO_CHANNELS 0x00020000 -#define MA_SND_PCM_NO_AUTO_FORMAT 0x00040000 +#define MA_SND_PCM_NONBLOCK 0x00000001 +#define MA_SND_PCM_NO_AUTO_RESAMPLE 0x00010000 +#define MA_SND_PCM_NO_AUTO_CHANNELS 0x00020000 +#define MA_SND_PCM_NO_AUTO_FORMAT 0x00040000 #endif + + +typedef void (* ma_snd_lib_error_handler_t)(const char* file, int line, const char* function, int err, const char* fmt, ...); + +typedef int (* ma_snd_lib_error_set_handler_proc) (ma_snd_lib_error_handler_t handler); +typedef const char* (* ma_snd_strerror_proc) (int errnum); typedef int (* ma_snd_pcm_open_proc) (ma_snd_pcm_t **pcm, const char *name, ma_snd_pcm_stream_t stream, int mode); typedef int (* ma_snd_pcm_close_proc) (ma_snd_pcm_t *pcm); +typedef int (* ma_snd_pcm_link_proc) (ma_snd_pcm_t* pcm1, ma_snd_pcm_t* pcm2); typedef size_t (* ma_snd_pcm_hw_params_sizeof_proc) (void); typedef int (* ma_snd_pcm_hw_params_any_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params); +typedef int (* ma_snd_pcm_hw_params_current_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params); typedef int (* ma_snd_pcm_hw_params_set_format_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_t val); typedef int (* ma_snd_pcm_hw_params_set_format_first_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_t *format); typedef void (* ma_snd_pcm_hw_params_get_format_mask_proc) (ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_mask_t *mask); @@ -28366,6 +28947,7 @@ typedef size_t (* ma_snd_pcm_format_mask_sizeof_proc) ( typedef int (* ma_snd_pcm_format_mask_test_proc) (const ma_snd_pcm_format_mask_t *mask, ma_snd_pcm_format_t val); typedef ma_snd_pcm_chmap_t * (* ma_snd_pcm_get_chmap_proc) (ma_snd_pcm_t *pcm); typedef ma_snd_pcm_state_t (* ma_snd_pcm_state_proc) (ma_snd_pcm_t *pcm); +typedef ma_snd_pcm_type_t (* ma_snd_pcm_type_proc) (ma_snd_pcm_t *pcm); typedef int (* ma_snd_pcm_prepare_proc) (ma_snd_pcm_t *pcm); typedef int (* ma_snd_pcm_start_proc) (ma_snd_pcm_t *pcm); typedef int (* ma_snd_pcm_drop_proc) (ma_snd_pcm_t *pcm); @@ -28411,6 +28993,101 @@ static const char* g_maBlacklistedCaptureDeviceNamesALSA[] = { }; +typedef struct ma_context_state_alsa +{ + ma_mutex internalDeviceEnumLock; + + ma_handle asoundSO; + ma_snd_lib_error_set_handler_proc snd_lib_error_set_handler; + ma_snd_strerror_proc snd_strerror; + ma_snd_pcm_open_proc snd_pcm_open; + ma_snd_pcm_close_proc snd_pcm_close; + ma_snd_pcm_link_proc snd_pcm_link; + ma_snd_pcm_hw_params_sizeof_proc snd_pcm_hw_params_sizeof; + ma_snd_pcm_hw_params_any_proc snd_pcm_hw_params_any; + ma_snd_pcm_hw_params_current_proc snd_pcm_hw_params_current; + ma_snd_pcm_hw_params_set_format_proc snd_pcm_hw_params_set_format; + ma_snd_pcm_hw_params_set_format_first_proc snd_pcm_hw_params_set_format_first; + ma_snd_pcm_hw_params_get_format_mask_proc snd_pcm_hw_params_get_format_mask; + ma_snd_pcm_hw_params_set_channels_proc snd_pcm_hw_params_set_channels; + ma_snd_pcm_hw_params_set_channels_near_proc snd_pcm_hw_params_set_channels_near; + ma_snd_pcm_hw_params_set_channels_minmax_proc snd_pcm_hw_params_set_channels_minmax; + ma_snd_pcm_hw_params_set_rate_resample_proc snd_pcm_hw_params_set_rate_resample; + ma_snd_pcm_hw_params_set_rate_proc snd_pcm_hw_params_set_rate; + ma_snd_pcm_hw_params_set_rate_near_proc snd_pcm_hw_params_set_rate_near; + ma_snd_pcm_hw_params_set_rate_minmax_proc snd_pcm_hw_params_set_rate_minmax; + ma_snd_pcm_hw_params_set_buffer_size_near_proc snd_pcm_hw_params_set_buffer_size_near; + ma_snd_pcm_hw_params_set_periods_near_proc snd_pcm_hw_params_set_periods_near; + ma_snd_pcm_hw_params_set_access_proc snd_pcm_hw_params_set_access; + ma_snd_pcm_hw_params_get_format_proc snd_pcm_hw_params_get_format; + ma_snd_pcm_hw_params_get_channels_proc snd_pcm_hw_params_get_channels; + ma_snd_pcm_hw_params_get_channels_min_proc snd_pcm_hw_params_get_channels_min; + ma_snd_pcm_hw_params_get_channels_max_proc snd_pcm_hw_params_get_channels_max; + ma_snd_pcm_hw_params_get_rate_proc snd_pcm_hw_params_get_rate; + ma_snd_pcm_hw_params_get_rate_min_proc snd_pcm_hw_params_get_rate_min; + ma_snd_pcm_hw_params_get_rate_max_proc snd_pcm_hw_params_get_rate_max; + ma_snd_pcm_hw_params_get_buffer_size_proc snd_pcm_hw_params_get_buffer_size; + ma_snd_pcm_hw_params_get_periods_proc snd_pcm_hw_params_get_periods; + ma_snd_pcm_hw_params_get_access_proc snd_pcm_hw_params_get_access; + ma_snd_pcm_hw_params_test_format_proc snd_pcm_hw_params_test_format; + ma_snd_pcm_hw_params_test_channels_proc snd_pcm_hw_params_test_channels; + ma_snd_pcm_hw_params_test_rate_proc snd_pcm_hw_params_test_rate; + ma_snd_pcm_hw_params_proc snd_pcm_hw_params; + ma_snd_pcm_sw_params_sizeof_proc snd_pcm_sw_params_sizeof; + ma_snd_pcm_sw_params_current_proc snd_pcm_sw_params_current; + ma_snd_pcm_sw_params_get_boundary_proc snd_pcm_sw_params_get_boundary; + ma_snd_pcm_sw_params_set_avail_min_proc snd_pcm_sw_params_set_avail_min; + ma_snd_pcm_sw_params_set_start_threshold_proc snd_pcm_sw_params_set_start_threshold; + ma_snd_pcm_sw_params_set_stop_threshold_proc snd_pcm_sw_params_set_stop_threshold; + ma_snd_pcm_sw_params_proc snd_pcm_sw_params; + ma_snd_pcm_format_mask_sizeof_proc snd_pcm_format_mask_sizeof; + ma_snd_pcm_format_mask_test_proc snd_pcm_format_mask_test; + ma_snd_pcm_get_chmap_proc snd_pcm_get_chmap; + ma_snd_pcm_state_proc snd_pcm_state; + ma_snd_pcm_type_proc snd_pcm_type; + ma_snd_pcm_prepare_proc snd_pcm_prepare; + ma_snd_pcm_start_proc snd_pcm_start; + ma_snd_pcm_drop_proc snd_pcm_drop; + ma_snd_pcm_drain_proc snd_pcm_drain; + ma_snd_pcm_reset_proc snd_pcm_reset; + ma_snd_device_name_hint_proc snd_device_name_hint; + ma_snd_device_name_get_hint_proc snd_device_name_get_hint; + ma_snd_card_get_index_proc snd_card_get_index; + ma_snd_device_name_free_hint_proc snd_device_name_free_hint; + ma_snd_pcm_mmap_begin_proc snd_pcm_mmap_begin; + ma_snd_pcm_mmap_commit_proc snd_pcm_mmap_commit; + ma_snd_pcm_recover_proc snd_pcm_recover; + ma_snd_pcm_readi_proc snd_pcm_readi; + ma_snd_pcm_writei_proc snd_pcm_writei; + ma_snd_pcm_avail_proc snd_pcm_avail; + ma_snd_pcm_avail_update_proc snd_pcm_avail_update; + ma_snd_pcm_wait_proc snd_pcm_wait; + ma_snd_pcm_nonblock_proc snd_pcm_nonblock; + ma_snd_pcm_info_proc snd_pcm_info; + ma_snd_pcm_info_sizeof_proc snd_pcm_info_sizeof; + ma_snd_pcm_info_get_name_proc snd_pcm_info_get_name; + ma_snd_pcm_poll_descriptors_proc snd_pcm_poll_descriptors; + ma_snd_pcm_poll_descriptors_count_proc snd_pcm_poll_descriptors_count; + ma_snd_pcm_poll_descriptors_revents_proc snd_pcm_poll_descriptors_revents; + ma_snd_config_update_free_global_proc snd_config_update_free_global; +} ma_context_state_alsa; + +typedef struct ma_device_state_alsa +{ + ma_snd_pcm_t* pPCMPlayback; + ma_snd_pcm_t* pPCMCapture; + void* pIntermediaryBuffer; /* Used as a scratch buffer for read() and write(). */ + int wakeupfd; /* eventfd for waking up from poll() when the device is stopped. */ + struct pollfd* pPollDescriptors; /* One array for wakeup, playback and capture. */ + int pollDescriptorCount; + int pollDescriptorCountPlayback; + int pollDescriptorCountCapture; + ma_bool8 isUsingMMapPlayback; + ma_bool8 isUsingMMapCapture; + ma_bool8 isLinked; /* Only relevant for duplex mode. Used to indicate that the capture and playback PCMs are linked with snd_pcm_link(). */ +} ma_device_state_alsa; + + static ma_snd_pcm_format_t ma_convert_ma_format_to_alsa_format(ma_format format) { ma_snd_pcm_format_t ALSAFormats[] = { @@ -28543,7 +29220,6 @@ static ma_bool32 ma_is_device_blacklisted__alsa(ma_device_type deviceType, const } } - static const char* ma_find_char(const char* str, char c, int* index) { int i = 0; @@ -28566,6 +29242,7 @@ static const char* ma_find_char(const char* str, char c, int* index) return NULL; } +#if 0 static ma_bool32 ma_is_device_name_in_hw_format__alsa(const char* hwid) { /* This function is just checking whether or not hwid is in "hw:%d,%d" format. */ @@ -28610,7 +29287,7 @@ static ma_bool32 ma_is_device_name_in_hw_format__alsa(const char* hwid) return MA_TRUE; } -static int ma_convert_device_name_to_hw_format__alsa(ma_context* pContext, char* dst, size_t dstSize, const char* src) /* Returns 0 on success, non-0 on error. */ +static int ma_convert_device_name_to_hw_format__alsa(ma_context_state_alsa* pContextStateALSA, char* dst, size_t dstSize, const char* src) /* Returns 0 on success, non-0 on error. */ { /* src should look something like this: "hw:CARD=I82801AAICH,DEV=0" */ @@ -28651,7 +29328,7 @@ static int ma_convert_device_name_to_hw_format__alsa(ma_context* pContext, char* ma_strncpy_s(card, sizeof(card), src+6, commaPos-6); /* +6 = ":CARD=" */ } - cardIndex = ((ma_snd_card_get_index_proc)pContext->alsa.snd_card_get_index)(card); + cardIndex = pContextStateALSA->snd_card_get_index(card); if (cardIndex < 0) { return -2; /* Failed to retrieve the card index. */ } @@ -28686,156 +29363,363 @@ static ma_bool32 ma_does_id_exist_in_list__alsa(ma_device_id* pUniqueIDs, ma_uin return MA_FALSE; } +#endif +static ma_context_state_alsa* ma_context_get_backend_state__alsa(ma_context* pContext) +{ + return (ma_context_state_alsa*)ma_context_get_backend_state(pContext); +} -static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode shareMode, ma_device_type deviceType, const ma_device_id* pDeviceID, int openMode, ma_snd_pcm_t** ppPCM) +static ma_device_state_alsa* ma_device_get_backend_state__alsa(ma_device* pDevice) { - ma_snd_pcm_t* pPCM; - ma_snd_pcm_stream_t stream; + return (ma_device_state_alsa*)ma_device_get_backend_state(pDevice); +} - MA_ASSERT(pContext != NULL); - MA_ASSERT(ppPCM != NULL); - *ppPCM = NULL; - pPCM = NULL; +static void ma_backend_info__alsa(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "ALSA"; +} - stream = (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE; +static void ma_silence_error__alsa(const char* file, int line, const char* function, int err, const char* fmt, ...) +{ + /* Do nothing. */ + (void)file; + (void)line; + (void)function; + (void)err; + (void)fmt; +} - if (pDeviceID == NULL) { - ma_bool32 isDeviceOpen; - size_t i; +static ma_result ma_context_init__alsa(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_alsa* pContextStateALSA; + const ma_context_config_alsa* pContextConfigALSA = (ma_context_config_alsa*)pContextBackendConfig; + ma_context_config_alsa defaultConfigALSA; + ma_log* pLog = ma_context_get_log(pContext); + ma_result result; - /* - We're opening the default device. I don't know if trying anything other than "default" is necessary, but it makes - me feel better to try as hard as we can get to get _something_ working. - */ - const char* defaultDeviceNames[] = { - "default", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL + if (pContextConfigALSA == NULL) { + defaultConfigALSA = ma_context_config_alsa_init(); + pContextConfigALSA = &defaultConfigALSA; + } + + pContextStateALSA = (ma_context_state_alsa*)ma_calloc(sizeof(*pContextStateALSA), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateALSA == NULL) { + return MA_OUT_OF_MEMORY; + } + + #ifndef MA_NO_RUNTIME_LINKING + { + const char* libasoundNames[] = { + "libasound.so.2", + "libasound.so" }; + size_t i; - if (shareMode == ma_share_mode_exclusive) { - defaultDeviceNames[1] = "hw"; - defaultDeviceNames[2] = "hw:0"; - defaultDeviceNames[3] = "hw:0,0"; - } else { - if (deviceType == ma_device_type_playback) { - defaultDeviceNames[1] = "dmix"; - defaultDeviceNames[2] = "dmix:0"; - defaultDeviceNames[3] = "dmix:0,0"; - } else { - defaultDeviceNames[1] = "dsnoop"; - defaultDeviceNames[2] = "dsnoop:0"; - defaultDeviceNames[3] = "dsnoop:0,0"; + for (i = 0; i < ma_countof(libasoundNames); ++i) { + pContextStateALSA->asoundSO = ma_dlopen(pLog, libasoundNames[i]); + if (pContextStateALSA->asoundSO != NULL) { + break; } - defaultDeviceNames[4] = "hw"; - defaultDeviceNames[5] = "hw:0"; - defaultDeviceNames[6] = "hw:0,0"; } - isDeviceOpen = MA_FALSE; - for (i = 0; i < ma_countof(defaultDeviceNames); ++i) { - if (defaultDeviceNames[i] != NULL && defaultDeviceNames[i][0] != '\0') { - if (((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, defaultDeviceNames[i], stream, openMode) == 0) { - isDeviceOpen = MA_TRUE; - break; - } - } + if (pContextStateALSA->asoundSO == NULL) { + ma_free(pContextStateALSA, ma_context_get_allocation_callbacks(pContext)); + ma_log_postf(pLog, MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to open shared object."); + return MA_NO_BACKEND; } - if (!isDeviceOpen) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } - } else { - /* - We're trying to open a specific device. There's a few things to consider here: + pContextStateALSA->snd_lib_error_set_handler = (ma_snd_lib_error_set_handler_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_lib_error_set_handler"); + pContextStateALSA->snd_strerror = (ma_snd_strerror_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_strerror"); + pContextStateALSA->snd_pcm_open = (ma_snd_pcm_open_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_open"); + pContextStateALSA->snd_pcm_close = (ma_snd_pcm_close_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_close"); + pContextStateALSA->snd_pcm_link = (ma_snd_pcm_link_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_link"); + pContextStateALSA->snd_pcm_hw_params_sizeof = (ma_snd_pcm_hw_params_sizeof_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_sizeof"); + pContextStateALSA->snd_pcm_hw_params_any = (ma_snd_pcm_hw_params_any_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_any"); + pContextStateALSA->snd_pcm_hw_params_current = (ma_snd_pcm_hw_params_current_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_current"); + pContextStateALSA->snd_pcm_hw_params_set_format = (ma_snd_pcm_hw_params_set_format_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_format"); + pContextStateALSA->snd_pcm_hw_params_set_format_first = (ma_snd_pcm_hw_params_set_format_first_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_format_first"); + pContextStateALSA->snd_pcm_hw_params_get_format_mask = (ma_snd_pcm_hw_params_get_format_mask_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_format_mask"); + pContextStateALSA->snd_pcm_hw_params_set_channels = (ma_snd_pcm_hw_params_set_channels_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_channels"); + pContextStateALSA->snd_pcm_hw_params_set_channels_near = (ma_snd_pcm_hw_params_set_channels_near_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_channels_near"); + pContextStateALSA->snd_pcm_hw_params_set_channels_minmax = (ma_snd_pcm_hw_params_set_channels_minmax_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_channels_minmax"); + pContextStateALSA->snd_pcm_hw_params_set_rate_resample = (ma_snd_pcm_hw_params_set_rate_resample_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_rate_resample"); + pContextStateALSA->snd_pcm_hw_params_set_rate = (ma_snd_pcm_hw_params_set_rate_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_rate"); + pContextStateALSA->snd_pcm_hw_params_set_rate_near = (ma_snd_pcm_hw_params_set_rate_near_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_rate_near"); + pContextStateALSA->snd_pcm_hw_params_set_rate_minmax = (ma_snd_pcm_hw_params_set_rate_minmax_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_rate_minmax"); + pContextStateALSA->snd_pcm_hw_params_set_buffer_size_near = (ma_snd_pcm_hw_params_set_buffer_size_near_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_buffer_size_near"); + pContextStateALSA->snd_pcm_hw_params_set_periods_near = (ma_snd_pcm_hw_params_set_periods_near_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_periods_near"); + pContextStateALSA->snd_pcm_hw_params_set_access = (ma_snd_pcm_hw_params_set_access_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_set_access"); + pContextStateALSA->snd_pcm_hw_params_get_format = (ma_snd_pcm_hw_params_get_format_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_format"); + pContextStateALSA->snd_pcm_hw_params_get_channels = (ma_snd_pcm_hw_params_get_channels_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_channels"); + pContextStateALSA->snd_pcm_hw_params_get_channels_min = (ma_snd_pcm_hw_params_get_channels_min_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_channels_min"); + pContextStateALSA->snd_pcm_hw_params_get_channels_max = (ma_snd_pcm_hw_params_get_channels_max_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_channels_max"); + pContextStateALSA->snd_pcm_hw_params_get_rate = (ma_snd_pcm_hw_params_get_rate_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_rate"); + pContextStateALSA->snd_pcm_hw_params_get_rate_min = (ma_snd_pcm_hw_params_get_rate_min_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_rate_min"); + pContextStateALSA->snd_pcm_hw_params_get_rate_max = (ma_snd_pcm_hw_params_get_rate_max_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_rate_max"); + pContextStateALSA->snd_pcm_hw_params_get_buffer_size = (ma_snd_pcm_hw_params_get_buffer_size_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_buffer_size"); + pContextStateALSA->snd_pcm_hw_params_get_periods = (ma_snd_pcm_hw_params_get_periods_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_periods"); + pContextStateALSA->snd_pcm_hw_params_get_access = (ma_snd_pcm_hw_params_get_access_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_get_access"); + pContextStateALSA->snd_pcm_hw_params_test_format = (ma_snd_pcm_hw_params_test_format_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_test_format"); + pContextStateALSA->snd_pcm_hw_params_test_channels = (ma_snd_pcm_hw_params_test_channels_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_test_channels"); + pContextStateALSA->snd_pcm_hw_params_test_rate = (ma_snd_pcm_hw_params_test_rate_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params_test_rate"); + pContextStateALSA->snd_pcm_hw_params = (ma_snd_pcm_hw_params_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_hw_params"); + pContextStateALSA->snd_pcm_sw_params_sizeof = (ma_snd_pcm_sw_params_sizeof_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_sw_params_sizeof"); + pContextStateALSA->snd_pcm_sw_params_current = (ma_snd_pcm_sw_params_current_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_sw_params_current"); + pContextStateALSA->snd_pcm_sw_params_get_boundary = (ma_snd_pcm_sw_params_get_boundary_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_sw_params_get_boundary"); + pContextStateALSA->snd_pcm_sw_params_set_avail_min = (ma_snd_pcm_sw_params_set_avail_min_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_sw_params_set_avail_min"); + pContextStateALSA->snd_pcm_sw_params_set_start_threshold = (ma_snd_pcm_sw_params_set_start_threshold_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_sw_params_set_start_threshold"); + pContextStateALSA->snd_pcm_sw_params_set_stop_threshold = (ma_snd_pcm_sw_params_set_stop_threshold_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_sw_params_set_stop_threshold"); + pContextStateALSA->snd_pcm_sw_params = (ma_snd_pcm_sw_params_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_sw_params"); + pContextStateALSA->snd_pcm_format_mask_sizeof = (ma_snd_pcm_format_mask_sizeof_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_format_mask_sizeof"); + pContextStateALSA->snd_pcm_format_mask_test = (ma_snd_pcm_format_mask_test_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_format_mask_test"); + pContextStateALSA->snd_pcm_get_chmap = (ma_snd_pcm_get_chmap_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_get_chmap"); + pContextStateALSA->snd_pcm_state = (ma_snd_pcm_state_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_state"); + pContextStateALSA->snd_pcm_type = (ma_snd_pcm_type_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_type"); + pContextStateALSA->snd_pcm_prepare = (ma_snd_pcm_prepare_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_prepare"); + pContextStateALSA->snd_pcm_start = (ma_snd_pcm_start_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_start"); + pContextStateALSA->snd_pcm_drop = (ma_snd_pcm_drop_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_drop"); + pContextStateALSA->snd_pcm_drain = (ma_snd_pcm_drain_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_drain"); + pContextStateALSA->snd_pcm_reset = (ma_snd_pcm_reset_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_reset"); + pContextStateALSA->snd_device_name_hint = (ma_snd_device_name_hint_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_device_name_hint"); + pContextStateALSA->snd_device_name_get_hint = (ma_snd_device_name_get_hint_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_device_name_get_hint"); + pContextStateALSA->snd_card_get_index = (ma_snd_card_get_index_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_card_get_index"); + pContextStateALSA->snd_device_name_free_hint = (ma_snd_device_name_free_hint_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_device_name_free_hint"); + pContextStateALSA->snd_pcm_mmap_begin = (ma_snd_pcm_mmap_begin_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_mmap_begin"); + pContextStateALSA->snd_pcm_mmap_commit = (ma_snd_pcm_mmap_commit_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_mmap_commit"); + pContextStateALSA->snd_pcm_recover = (ma_snd_pcm_recover_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_recover"); + pContextStateALSA->snd_pcm_readi = (ma_snd_pcm_readi_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_readi"); + pContextStateALSA->snd_pcm_writei = (ma_snd_pcm_writei_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_writei"); + pContextStateALSA->snd_pcm_avail = (ma_snd_pcm_avail_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_avail"); + pContextStateALSA->snd_pcm_avail_update = (ma_snd_pcm_avail_update_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_avail_update"); + pContextStateALSA->snd_pcm_wait = (ma_snd_pcm_wait_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_wait"); + pContextStateALSA->snd_pcm_nonblock = (ma_snd_pcm_nonblock_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_nonblock"); + pContextStateALSA->snd_pcm_info = (ma_snd_pcm_info_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_info"); + pContextStateALSA->snd_pcm_info_sizeof = (ma_snd_pcm_info_sizeof_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_info_sizeof"); + pContextStateALSA->snd_pcm_info_get_name = (ma_snd_pcm_info_get_name_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_info_get_name"); + pContextStateALSA->snd_pcm_poll_descriptors = (ma_snd_pcm_poll_descriptors_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_poll_descriptors"); + pContextStateALSA->snd_pcm_poll_descriptors_count = (ma_snd_pcm_poll_descriptors_count_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_poll_descriptors_count"); + pContextStateALSA->snd_pcm_poll_descriptors_revents = (ma_snd_pcm_poll_descriptors_revents_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_pcm_poll_descriptors_revents"); + pContextStateALSA->snd_config_update_free_global = (ma_snd_config_update_free_global_proc )ma_dlsym(pLog, pContextStateALSA->asoundSO, "snd_config_update_free_global"); + } + #else + { + /* The system below is just for type safety. */ + ma_snd_lib_error_set_handler_proc _snd_lib_error_set_handler = snd_lib_error_set_handler; + ma_snd_strerror_proc _snd_strerror = snd_strerror; + ma_snd_pcm_open_proc _snd_pcm_open = snd_pcm_open; + ma_snd_pcm_close_proc _snd_pcm_close = snd_pcm_close; + ma_snd_pcm_link_proc _snd_pcm_link = snd_pcm_link; + ma_snd_pcm_hw_params_sizeof_proc _snd_pcm_hw_params_sizeof = snd_pcm_hw_params_sizeof; + ma_snd_pcm_hw_params_any_proc _snd_pcm_hw_params_any = snd_pcm_hw_params_any; + ma_snd_pcm_hw_params_current_proc _snd_pcm_hw_params_current = snd_pcm_hw_params_current; + ma_snd_pcm_hw_params_set_format_proc _snd_pcm_hw_params_set_format = snd_pcm_hw_params_set_format; + ma_snd_pcm_hw_params_set_format_first_proc _snd_pcm_hw_params_set_format_first = snd_pcm_hw_params_set_format_first; + ma_snd_pcm_hw_params_get_format_mask_proc _snd_pcm_hw_params_get_format_mask = snd_pcm_hw_params_get_format_mask; + ma_snd_pcm_hw_params_set_channels_proc _snd_pcm_hw_params_set_channels = snd_pcm_hw_params_set_channels; + ma_snd_pcm_hw_params_set_channels_near_proc _snd_pcm_hw_params_set_channels_near = snd_pcm_hw_params_set_channels_near; + ma_snd_pcm_hw_params_set_channels_minmax_proc _snd_pcm_hw_params_set_channels_minmax = snd_pcm_hw_params_set_channels_minmax; + ma_snd_pcm_hw_params_set_rate_resample_proc _snd_pcm_hw_params_set_rate_resample = snd_pcm_hw_params_set_rate_resample; + ma_snd_pcm_hw_params_set_rate_proc _snd_pcm_hw_params_set_rate = snd_pcm_hw_params_set_rate; + ma_snd_pcm_hw_params_set_rate_near_proc _snd_pcm_hw_params_set_rate_near = snd_pcm_hw_params_set_rate_near; + ma_snd_pcm_hw_params_set_rate_minmax_proc _snd_pcm_hw_params_set_rate_minmax = snd_pcm_hw_params_set_rate_minmax; + ma_snd_pcm_hw_params_set_buffer_size_near_proc _snd_pcm_hw_params_set_buffer_size_near = snd_pcm_hw_params_set_buffer_size_near; + ma_snd_pcm_hw_params_set_periods_near_proc _snd_pcm_hw_params_set_periods_near = snd_pcm_hw_params_set_periods_near; + ma_snd_pcm_hw_params_set_access_proc _snd_pcm_hw_params_set_access = snd_pcm_hw_params_set_access; + ma_snd_pcm_hw_params_get_format_proc _snd_pcm_hw_params_get_format = snd_pcm_hw_params_get_format; + ma_snd_pcm_hw_params_get_channels_proc _snd_pcm_hw_params_get_channels = snd_pcm_hw_params_get_channels; + ma_snd_pcm_hw_params_get_channels_min_proc _snd_pcm_hw_params_get_channels_min = snd_pcm_hw_params_get_channels_min; + ma_snd_pcm_hw_params_get_channels_max_proc _snd_pcm_hw_params_get_channels_max = snd_pcm_hw_params_get_channels_max; + ma_snd_pcm_hw_params_get_rate_proc _snd_pcm_hw_params_get_rate = snd_pcm_hw_params_get_rate; + ma_snd_pcm_hw_params_get_rate_min_proc _snd_pcm_hw_params_get_rate_min = snd_pcm_hw_params_get_rate_min; + ma_snd_pcm_hw_params_get_rate_max_proc _snd_pcm_hw_params_get_rate_max = snd_pcm_hw_params_get_rate_max; + ma_snd_pcm_hw_params_get_buffer_size_proc _snd_pcm_hw_params_get_buffer_size = snd_pcm_hw_params_get_buffer_size; + ma_snd_pcm_hw_params_get_periods_proc _snd_pcm_hw_params_get_periods = snd_pcm_hw_params_get_periods; + ma_snd_pcm_hw_params_get_access_proc _snd_pcm_hw_params_get_access = snd_pcm_hw_params_get_access; + ma_snd_pcm_hw_params_test_format_proc _snd_pcm_hw_params_test_format = snd_pcm_hw_params_test_format; + ma_snd_pcm_hw_params_test_channels_proc _snd_pcm_hw_params_test_channels = snd_pcm_hw_params_test_channels; + ma_snd_pcm_hw_params_test_rate_proc _snd_pcm_hw_params_test_rate = snd_pcm_hw_params_test_rate; + ma_snd_pcm_hw_params_proc _snd_pcm_hw_params = snd_pcm_hw_params; + ma_snd_pcm_sw_params_sizeof_proc _snd_pcm_sw_params_sizeof = snd_pcm_sw_params_sizeof; + ma_snd_pcm_sw_params_current_proc _snd_pcm_sw_params_current = snd_pcm_sw_params_current; + ma_snd_pcm_sw_params_get_boundary_proc _snd_pcm_sw_params_get_boundary = snd_pcm_sw_params_get_boundary; + ma_snd_pcm_sw_params_set_avail_min_proc _snd_pcm_sw_params_set_avail_min = snd_pcm_sw_params_set_avail_min; + ma_snd_pcm_sw_params_set_start_threshold_proc _snd_pcm_sw_params_set_start_threshold = snd_pcm_sw_params_set_start_threshold; + ma_snd_pcm_sw_params_set_stop_threshold_proc _snd_pcm_sw_params_set_stop_threshold = snd_pcm_sw_params_set_stop_threshold; + ma_snd_pcm_sw_params_proc _snd_pcm_sw_params = snd_pcm_sw_params; + ma_snd_pcm_format_mask_sizeof_proc _snd_pcm_format_mask_sizeof = snd_pcm_format_mask_sizeof; + ma_snd_pcm_format_mask_test_proc _snd_pcm_format_mask_test = snd_pcm_format_mask_test; + ma_snd_pcm_get_chmap_proc _snd_pcm_get_chmap = snd_pcm_get_chmap; + ma_snd_pcm_state_proc _snd_pcm_state = snd_pcm_state; + ma_snd_pcm_type_proc _snd_pcm_type = snd_pcm_type; + ma_snd_pcm_prepare_proc _snd_pcm_prepare = snd_pcm_prepare; + ma_snd_pcm_start_proc _snd_pcm_start = snd_pcm_start; + ma_snd_pcm_drop_proc _snd_pcm_drop = snd_pcm_drop; + ma_snd_pcm_drain_proc _snd_pcm_drain = snd_pcm_drain; + ma_snd_pcm_reset_proc _snd_pcm_reset = snd_pcm_reset; + ma_snd_device_name_hint_proc _snd_device_name_hint = snd_device_name_hint; + ma_snd_device_name_get_hint_proc _snd_device_name_get_hint = snd_device_name_get_hint; + ma_snd_card_get_index_proc _snd_card_get_index = snd_card_get_index; + ma_snd_device_name_free_hint_proc _snd_device_name_free_hint = snd_device_name_free_hint; + ma_snd_pcm_mmap_begin_proc _snd_pcm_mmap_begin = snd_pcm_mmap_begin; + ma_snd_pcm_mmap_commit_proc _snd_pcm_mmap_commit = snd_pcm_mmap_commit; + ma_snd_pcm_recover_proc _snd_pcm_recover = snd_pcm_recover; + ma_snd_pcm_readi_proc _snd_pcm_readi = snd_pcm_readi; + ma_snd_pcm_writei_proc _snd_pcm_writei = snd_pcm_writei; + ma_snd_pcm_avail_proc _snd_pcm_avail = snd_pcm_avail; + ma_snd_pcm_avail_update_proc _snd_pcm_avail_update = snd_pcm_avail_update; + ma_snd_pcm_wait_proc _snd_pcm_wait = snd_pcm_wait; + ma_snd_pcm_nonblock_proc _snd_pcm_nonblock = snd_pcm_nonblock; + ma_snd_pcm_info_proc _snd_pcm_info = snd_pcm_info; + ma_snd_pcm_info_sizeof_proc _snd_pcm_info_sizeof = snd_pcm_info_sizeof; + ma_snd_pcm_info_get_name_proc _snd_pcm_info_get_name = snd_pcm_info_get_name; + ma_snd_pcm_poll_descriptors_proc _snd_pcm_poll_descriptors = snd_pcm_poll_descriptors; + ma_snd_pcm_poll_descriptors_count_proc _snd_pcm_poll_descriptors_count = snd_pcm_poll_descriptors_count; + ma_snd_pcm_poll_descriptors_revents_proc _snd_pcm_poll_descriptors_revents = snd_pcm_poll_descriptors_revents; + ma_snd_config_update_free_global_proc _snd_config_update_free_global = snd_config_update_free_global; + + pContextStateALSA->snd_lib_error_set_handler = _snd_lib_error_set_handler; + pContextStateALSA->snd_strerror = _snd_strerror; + pContextStateALSA->snd_pcm_open = _snd_pcm_open; + pContextStateALSA->snd_pcm_close = _snd_pcm_close; + pContextStateALSA->snd_pcm_link = _snd_pcm_link; + pContextStateALSA->snd_pcm_hw_params_sizeof = _snd_pcm_hw_params_sizeof; + pContextStateALSA->snd_pcm_hw_params_any = _snd_pcm_hw_params_any; + pContextStateALSA->snd_pcm_hw_params_current = _snd_pcm_hw_params_current; + pContextStateALSA->snd_pcm_hw_params_set_format = _snd_pcm_hw_params_set_format; + pContextStateALSA->snd_pcm_hw_params_set_format_first = _snd_pcm_hw_params_set_format_first; + pContextStateALSA->snd_pcm_hw_params_get_format_mask = _snd_pcm_hw_params_get_format_mask; + pContextStateALSA->snd_pcm_hw_params_set_channels = _snd_pcm_hw_params_set_channels; + pContextStateALSA->snd_pcm_hw_params_set_channels_near = _snd_pcm_hw_params_set_channels_near; + pContextStateALSA->snd_pcm_hw_params_set_channels_minmax = _snd_pcm_hw_params_set_channels_minmax; + pContextStateALSA->snd_pcm_hw_params_set_rate_resample = _snd_pcm_hw_params_set_rate_resample; + pContextStateALSA->snd_pcm_hw_params_set_rate = _snd_pcm_hw_params_set_rate; + pContextStateALSA->snd_pcm_hw_params_set_rate_near = _snd_pcm_hw_params_set_rate_near; + pContextStateALSA->snd_pcm_hw_params_set_rate_minmax = _snd_pcm_hw_params_set_rate_minmax; + pContextStateALSA->snd_pcm_hw_params_set_buffer_size_near = _snd_pcm_hw_params_set_buffer_size_near; + pContextStateALSA->snd_pcm_hw_params_set_periods_near = _snd_pcm_hw_params_set_periods_near; + pContextStateALSA->snd_pcm_hw_params_set_access = _snd_pcm_hw_params_set_access; + pContextStateALSA->snd_pcm_hw_params_get_format = _snd_pcm_hw_params_get_format; + pContextStateALSA->snd_pcm_hw_params_get_channels = _snd_pcm_hw_params_get_channels; + pContextStateALSA->snd_pcm_hw_params_get_channels_min = _snd_pcm_hw_params_get_channels_min; + pContextStateALSA->snd_pcm_hw_params_get_channels_max = _snd_pcm_hw_params_get_channels_max; + pContextStateALSA->snd_pcm_hw_params_get_rate = _snd_pcm_hw_params_get_rate; + pContextStateALSA->snd_pcm_hw_params_get_rate_min = _snd_pcm_hw_params_get_rate_min; + pContextStateALSA->snd_pcm_hw_params_get_rate_max = _snd_pcm_hw_params_get_rate_max; + pContextStateALSA->snd_pcm_hw_params_get_buffer_size = _snd_pcm_hw_params_get_buffer_size; + pContextStateALSA->snd_pcm_hw_params_get_periods = _snd_pcm_hw_params_get_periods; + pContextStateALSA->snd_pcm_hw_params_get_access = _snd_pcm_hw_params_get_access; + pContextStateALSA->snd_pcm_hw_params_test_format = _snd_pcm_hw_params_test_format; + pContextStateALSA->snd_pcm_hw_params_test_channels = _snd_pcm_hw_params_test_channels; + pContextStateALSA->snd_pcm_hw_params_test_rate = _snd_pcm_hw_params_test_rate; + pContextStateALSA->snd_pcm_hw_params = _snd_pcm_hw_params; + pContextStateALSA->snd_pcm_sw_params_sizeof = _snd_pcm_sw_params_sizeof; + pContextStateALSA->snd_pcm_sw_params_current = _snd_pcm_sw_params_current; + pContextStateALSA->snd_pcm_sw_params_get_boundary = _snd_pcm_sw_params_get_boundary; + pContextStateALSA->snd_pcm_sw_params_set_avail_min = _snd_pcm_sw_params_set_avail_min; + pContextStateALSA->snd_pcm_sw_params_set_start_threshold = _snd_pcm_sw_params_set_start_threshold; + pContextStateALSA->snd_pcm_sw_params_set_stop_threshold = _snd_pcm_sw_params_set_stop_threshold; + pContextStateALSA->snd_pcm_sw_params = _snd_pcm_sw_params; + pContextStateALSA->snd_pcm_format_mask_sizeof = _snd_pcm_format_mask_sizeof; + pContextStateALSA->snd_pcm_format_mask_test = _snd_pcm_format_mask_test; + pContextStateALSA->snd_pcm_get_chmap = _snd_pcm_get_chmap; + pContextStateALSA->snd_pcm_state = _snd_pcm_state; + pContextStateALSA->snd_pcm_type = _snd_pcm_type; + pContextStateALSA->snd_pcm_prepare = _snd_pcm_prepare; + pContextStateALSA->snd_pcm_start = _snd_pcm_start; + pContextStateALSA->snd_pcm_drop = _snd_pcm_drop; + pContextStateALSA->snd_pcm_drain = _snd_pcm_drain; + pContextStateALSA->snd_pcm_reset = _snd_pcm_reset; + pContextStateALSA->snd_device_name_hint = _snd_device_name_hint; + pContextStateALSA->snd_device_name_get_hint = _snd_device_name_get_hint; + pContextStateALSA->snd_card_get_index = _snd_card_get_index; + pContextStateALSA->snd_device_name_free_hint = _snd_device_name_free_hint; + pContextStateALSA->snd_pcm_mmap_begin = _snd_pcm_mmap_begin; + pContextStateALSA->snd_pcm_mmap_commit = _snd_pcm_mmap_commit; + pContextStateALSA->snd_pcm_recover = _snd_pcm_recover; + pContextStateALSA->snd_pcm_readi = _snd_pcm_readi; + pContextStateALSA->snd_pcm_writei = _snd_pcm_writei; + pContextStateALSA->snd_pcm_avail = _snd_pcm_avail; + pContextStateALSA->snd_pcm_avail_update = _snd_pcm_avail_update; + pContextStateALSA->snd_pcm_wait = _snd_pcm_wait; + pContextStateALSA->snd_pcm_nonblock = _snd_pcm_nonblock; + pContextStateALSA->snd_pcm_info = _snd_pcm_info; + pContextStateALSA->snd_pcm_info_sizeof = _snd_pcm_info_sizeof; + pContextStateALSA->snd_pcm_info_get_name = _snd_pcm_info_get_name; + pContextStateALSA->snd_pcm_poll_descriptors = _snd_pcm_poll_descriptors; + pContextStateALSA->snd_pcm_poll_descriptors_count = _snd_pcm_poll_descriptors_count; + pContextStateALSA->snd_pcm_poll_descriptors_revents = _snd_pcm_poll_descriptors_revents; + pContextStateALSA->snd_config_update_free_global = _snd_config_update_free_global; + } + #endif - miniaudio recognizes a special format of device id that excludes the "hw", "dmix", etc. prefix. It looks like this: ":0,0", ":0,1", etc. When - an ID of this format is specified, it indicates to miniaudio that it can try different combinations of plugins ("hw", "dmix", etc.) until it - finds an appropriate one that works. This comes in very handy when trying to open a device in shared mode ("dmix"), vs exclusive mode ("hw"). - */ + result = ma_mutex_init(&pContextStateALSA->internalDeviceEnumLock); + if (result != MA_SUCCESS) { + ma_free(pContextStateALSA, ma_context_get_allocation_callbacks(pContext)); + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration."); + return result; + } - /* May end up needing to make small adjustments to the ID, so make a copy. */ - ma_device_id deviceID = *pDeviceID; - int resultALSA = -ENODEV; + /* ALSA will print errors. We want to shut this up. */ + pContextStateALSA->snd_lib_error_set_handler(ma_silence_error__alsa); - if (deviceID.alsa[0] != ':') { - /* The ID is not in ":0,0" format. Use the ID exactly as-is. */ - resultALSA = ((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, deviceID.alsa, stream, openMode); - } else { - char hwid[256]; + *ppContextState = pContextStateALSA; - /* The ID is in ":0,0" format. Try different plugins depending on the shared mode. */ - if (deviceID.alsa[1] == '\0') { - deviceID.alsa[0] = '\0'; /* An ID of ":" should be converted to "". */ - } + return MA_SUCCESS; +} - if (shareMode == ma_share_mode_shared) { - if (deviceType == ma_device_type_playback) { - ma_strcpy_s(hwid, sizeof(hwid), "dmix"); - } else { - ma_strcpy_s(hwid, sizeof(hwid), "dsnoop"); - } +static void ma_context_uninit__alsa(ma_context* pContext) +{ + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(pContext); - if (ma_strcat_s(hwid, sizeof(hwid), deviceID.alsa) == 0) { - resultALSA = ((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, hwid, stream, openMode); - } - } + MA_ASSERT(pContextStateALSA != NULL); - /* If at this point we still don't have an open device it means we're either preferencing exclusive mode or opening with "dmix"/"dsnoop" failed. */ - if (resultALSA != 0) { - ma_strcpy_s(hwid, sizeof(hwid), "hw"); - if (ma_strcat_s(hwid, sizeof(hwid), deviceID.alsa) == 0) { - resultALSA = ((ma_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)(&pPCM, hwid, stream, openMode); - } - } - } + /* Clean up memory for memory leak checkers. */ + pContextStateALSA->snd_config_update_free_global(); - if (resultALSA < 0) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed."); - return ma_result_from_errno(-resultALSA); - } + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(ma_context_get_log(pContext), pContextStateALSA->asoundSO); } + #endif - *ppPCM = pPCM; - return MA_SUCCESS; + ma_mutex_uninit(&pContextStateALSA->internalDeviceEnumLock); + + ma_free(pContextStateALSA, ma_context_get_allocation_callbacks(pContext)); } static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(pContext); int resultALSA; - ma_bool32 cbResult = MA_TRUE; + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; char** ppDeviceHints; ma_device_id* pUniqueIDs = NULL; - ma_uint32 uniqueIDCount = 0; char** ppNextDeviceHint; + ma_bool32 hasEnumeratedDefaultPlaybackDevice = MA_FALSE; + ma_bool32 hasEnumeratedDefaultCaptureDevice = MA_FALSE; - MA_ASSERT(pContext != NULL); + MA_ASSERT(pContextStateALSA != NULL); MA_ASSERT(callback != NULL); - ma_mutex_lock(&pContext->alsa.internalDeviceEnumLock); + ma_mutex_lock(&pContextStateALSA->internalDeviceEnumLock); - resultALSA = ((ma_snd_device_name_hint_proc)pContext->alsa.snd_device_name_hint)(-1, "pcm", (void***)&ppDeviceHints); + resultALSA = pContextStateALSA->snd_device_name_hint(-1, "pcm", (void***)&ppDeviceHints); if (resultALSA < 0) { - ma_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); + ma_mutex_unlock(&pContextStateALSA->internalDeviceEnumLock); return ma_result_from_errno(-resultALSA); } ppNextDeviceHint = ppDeviceHints; while (*ppNextDeviceHint != NULL) { - char* NAME = ((ma_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "NAME"); - char* DESC = ((ma_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "DESC"); - char* IOID = ((ma_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "IOID"); + char* NAME = pContextStateALSA->snd_device_name_get_hint(*ppNextDeviceHint, "NAME"); + char* DESC = pContextStateALSA->snd_device_name_get_hint(*ppNextDeviceHint, "DESC"); + char* IOID = pContextStateALSA->snd_device_name_get_hint(*ppNextDeviceHint, "IOID"); ma_device_type deviceType = ma_device_type_playback; ma_bool32 stopEnumeration = MA_FALSE; - char hwid[sizeof(pUniqueIDs->alsa)]; ma_device_info deviceInfo; if ((IOID == NULL || ma_strcmp(IOID, "Output") == 0)) { @@ -28845,93 +29729,178 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu deviceType = ma_device_type_capture; } - if (NAME != NULL) { - if (pContext->alsa.useVerboseDeviceEnumeration) { - /* Verbose mode. Use the name exactly as-is. */ - ma_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); - } else { - /* Simplified mode. Use ":%d,%d" format. */ - if (ma_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), NAME) == 0) { - /* - At this point, hwid looks like "hw:0,0". In simplified enumeration mode, we actually want to strip off the - plugin name so it looks like ":0,0". The reason for this is that this special format is detected at device - initialization time and is used as an indicator to try to use the most appropriate plugin depending on the - device type and sharing mode. - */ - char* dst = hwid; - char* src = hwid+2; - while ((*dst++ = *src++)); - } else { - /* Conversion to "hw:%d,%d" failed. Just use the name as-is. */ - ma_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); - } - - if (ma_does_id_exist_in_list__alsa(pUniqueIDs, uniqueIDCount, hwid)) { - goto next_device; /* The device has already been enumerated. Move on to the next one. */ - } else { - /* The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. */ - size_t newCapacity = sizeof(*pUniqueIDs) * (uniqueIDCount + 1); - ma_device_id* pNewUniqueIDs = (ma_device_id*)ma_realloc(pUniqueIDs, newCapacity, &pContext->allocationCallbacks); - if (pNewUniqueIDs == NULL) { - goto next_device; /* Failed to allocate memory. */ - } - - pUniqueIDs = pNewUniqueIDs; - MA_COPY_MEMORY(pUniqueIDs[uniqueIDCount].alsa, hwid, sizeof(hwid)); - uniqueIDCount += 1; - } - } - } else { - MA_ZERO_MEMORY(hwid, sizeof(hwid)); - } - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), hwid, (size_t)-1); + + /* ID. */ + ma_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), NAME, (size_t)-1); /* - There's no good way to determine whether or not a device is the default on Linux. We're just going to do something simple and - just use the name of "default" as the indicator. + Defualt. + + There's no good way to determine whether or not a device is the default with ALSA. We're just going to do + something simple and just use the name of "default" as the indicator. */ - if (ma_strcmp(deviceInfo.id.alsa, "default") == 0) { - deviceInfo.isDefault = MA_TRUE; + if ((deviceType == ma_device_type_playback && !hasEnumeratedDefaultPlaybackDevice) || (deviceType == ma_device_type_capture && !hasEnumeratedDefaultCaptureDevice)) { + /* */ if (ma_strcmp(deviceInfo.id.alsa, "default") == 0) { + deviceInfo.isDefault = MA_TRUE; + } else if (ma_strcmp(deviceInfo.id.alsa, "sysdefault") == 0) { + deviceInfo.isDefault = MA_TRUE; + } } + if (deviceInfo.isDefault) { + if (deviceType == ma_device_type_playback) { + hasEnumeratedDefaultPlaybackDevice = MA_TRUE; + } + if (deviceType == ma_device_type_capture) { + hasEnumeratedDefaultCaptureDevice = MA_TRUE; + } + } /* - DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose - device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish - between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the - description. - - The value in DESC seems to be split into two lines, with the first line being the name of the device and the - second line being a description of the device. I don't like having the description be across two lines because - it makes formatting ugly and annoying. I'm therefore deciding to put it all on a single line with the second line - being put into parentheses. In simplified mode I'm just stripping the second line entirely. + Name. + + DESC is the friendly name. It seems to be split into two lines, with the first line being the name of the device + and the second line being a description of the device. I don't like having the description be across two lines + because it makes formatting ugly and annoying. I'm therefore deciding to put it all on a single line with the + second line being put into parentheses. */ - if (DESC != NULL) { + if (DESC != NULL && DESC[0] != '\0') { int lfPos; const char* line2 = ma_find_char(DESC, '\n', &lfPos); if (line2 != NULL) { line2 += 1; /* Skip past the new-line character. */ - if (pContext->alsa.useVerboseDeviceEnumeration) { - /* Verbose mode. Put the second line in brackets. */ - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, lfPos); - ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), " ("); - ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), line2); - ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), ")"); - } else { - /* Simplified mode. Strip the second line entirely. */ - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, lfPos); - } + /* Put the second line in brackets. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, lfPos); + ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), " ("); + ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), line2); + ma_strcat_s (deviceInfo.name, sizeof(deviceInfo.name), ")"); } else { /* There's no second line. Just copy the whole description. */ ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), DESC, (size_t)-1); } + } else { + /* Getting here means the description is empty. Just set it to NAME. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), NAME, (size_t)-1); + } + + /* + Data Format. + + Retrieving the data format requires us to open the device. + */ + { + ma_snd_pcm_t* pPCM; + ma_snd_pcm_hw_params_t* pHWParams; + ma_uint32 iFormat; + int openMode; + + /* + I'm opening this in non-blocking mode because I'm concerned that ALSA will have it block when an + exclusive hardware device is already in use. + */ + openMode = MA_SND_PCM_NONBLOCK; /*MA_SND_PCM_NO_AUTO_RESAMPLE | MA_SND_PCM_NO_AUTO_CHANNELS | MA_SND_PCM_NO_AUTO_FORMAT;*/ + + /* + For detailed info we need to open the device. I can think of two reasons why opening might fail: + + 1) The device is disabled or physically unplugged. + 2) The device is a hardware device and is already opened in exclusive mode + + When this happens we're still going to enumerate the device, but we're going to just not fill out + the detailed info. + */ + if (pContextStateALSA->snd_pcm_open(&pPCM, deviceInfo.id.alsa, (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE, openMode) == 0) { + /* We need to initialize a HW parameters object in order to know what formats are supported. */ + pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(pContextStateALSA->snd_pcm_hw_params_sizeof(), ma_context_get_allocation_callbacks(pContext)); + if (pHWParams == NULL) { + pContextStateALSA->snd_pcm_close(pPCM); + goto next_device; /* Out of memory. */ + } + + resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams); + if (resultALSA < 0) { + ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters for device \"%s\". snd_pcm_hw_params_any() failed. %s.", deviceInfo.id.alsa, pContextStateALSA->snd_strerror(resultALSA)); + goto next_device; + } + + /* + Some ALSA devices can support many permutations of formats, channels and rates. We only support + a fixed number of permutations which means we need to employ some strategies to ensure the best + combinations are returned. An example is the "pulse" device which can do its own data conversion + in software and as a result can support any combination of format, channels and rate. + + We want to ensure that the first data formats are the best. We have a list of favored sample + formats and sample rates, so these will be the basis of our iteration. + */ + + /* Formats. We just iterate over our standard formats and test them, making sure we reset the configuration space each iteration. */ + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) { + ma_format format = g_maFormatPriorities[iFormat]; + + /* + For each format we need to make sure we reset the configuration space so we don't return + channel counts and rates that aren't compatible with a format. + */ + pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams); + + /* Test the format first. If this fails it means the format is not supported and we can skip it. */ + if (pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)) == 0) { + /* The format is supported. */ + unsigned int minChannels = 0; + unsigned int maxChannels = 0; + unsigned int minSampleRate = 0; + unsigned int maxSampleRate = 0; + int sampleRateDir; /* Not used. Just passed into snd_pcm_hw_params_get_rate_min/max(). */ + + /* + The configuration space needs to be restricted to this format so we can get an accurate + picture of which sample rates and channel counts are support with this format. + */ + pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)); + + /* Now we need to check for supported channels. */ + pContextStateALSA->snd_pcm_hw_params_get_channels_min(pHWParams, &minChannels); + pContextStateALSA->snd_pcm_hw_params_get_channels_max(pHWParams, &maxChannels); + + if (minChannels > MA_MAX_CHANNELS) { + continue; /* Too many channels. */ + } + if (maxChannels < MA_MIN_CHANNELS) { + continue; /* Not enough channels. */ + } + + /* + Make sure the channel count is clamped. This is mainly intended for the max channels + because some devices can report an unbound maximum. + */ + minChannels = ma_clamp(minChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + maxChannels = ma_clamp(maxChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + + pContextStateALSA->snd_pcm_hw_params_get_rate_min(pHWParams, &minSampleRate, &sampleRateDir); + pContextStateALSA->snd_pcm_hw_params_get_rate_max(pHWParams, &maxSampleRate, &sampleRateDir); + + /* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculous. */ + minSampleRate = ma_clamp(minSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); + maxSampleRate = ma_clamp(maxSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); + + ma_device_info_add_native_data_format(&deviceInfo, format, minChannels, maxChannels, minSampleRate, maxSampleRate); + } else { + /* The format is not supported. Skip. */ + } + } + + ma_free(pHWParams, ma_context_get_allocation_callbacks(pContext)); + + pContextStateALSA->snd_pcm_close(pPCM); + } } if (!ma_is_device_blacklisted__alsa(deviceType, NAME)) { - cbResult = callback(pContext, deviceType, &deviceInfo, pUserData); + cbResult = callback(deviceType, &deviceInfo, pUserData); } /* @@ -28939,21 +29908,21 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu again for the other device type in this case. We do this for known devices and where the IOID hint is NULL, which means both Input and Output. */ - if (cbResult) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { if (ma_is_common_device_name__alsa(NAME) || IOID == NULL) { if (deviceType == ma_device_type_playback) { if (!ma_is_capture_device_blacklisted__alsa(NAME)) { - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + cbResult = callback(ma_device_type_capture, &deviceInfo, pUserData); } } else { if (!ma_is_playback_device_blacklisted__alsa(NAME)) { - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + cbResult = callback(ma_device_type_playback, &deviceInfo, pUserData); } } } } - if (cbResult == MA_FALSE) { + if (cbResult == MA_DEVICE_ENUMERATION_ABORT) { stopEnumeration = MA_TRUE; } @@ -28969,9965 +29938,15808 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu } } - ma_free(pUniqueIDs, &pContext->allocationCallbacks); - ((ma_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); + ma_free(pUniqueIDs, ma_context_get_allocation_callbacks(pContext)); + pContextStateALSA->snd_device_name_free_hint((void**)ppDeviceHints); - ma_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); + ma_mutex_unlock(&pContextStateALSA->internalDeviceEnumLock); return MA_SUCCESS; } -typedef struct -{ - ma_device_type deviceType; - const ma_device_id* pDeviceID; - ma_share_mode shareMode; - ma_device_info* pDeviceInfo; - ma_bool32 foundDevice; -} ma_context_get_device_info_enum_callback_data__alsa; -static ma_bool32 ma_context_get_device_info_enum_callback__alsa(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pDeviceInfo, void* pUserData) +static ma_result ma_device_init_by_type__alsa(ma_context* pContext, ma_context_state_alsa* pContextStateALSA, ma_device_state_alsa* pDeviceStateALSA, const ma_device_config_alsa* pDeviceConfigALSA, ma_device_descriptor* pDescriptor, ma_device_type deviceType, void* pParamsMemory) { - ma_context_get_device_info_enum_callback_data__alsa* pData = (ma_context_get_device_info_enum_callback_data__alsa*)pUserData; - MA_ASSERT(pData != NULL); + int resultALSA; + ma_snd_pcm_t* pPCM; + ma_bool32 isUsingMMap; + ma_snd_pcm_format_t formatALSA; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_channel internalChannelMap[MA_MAX_CHANNELS]; + ma_uint32 internalPeriodSizeInFrames; + ma_uint32 internalPeriods; + int openMode; + ma_snd_pcm_hw_params_t* pHWParams; + ma_snd_pcm_sw_params_t* pSWParams; + const char* pDeviceNames[16]; + size_t iDeviceName = 0; + size_t deviceNameCount; - (void)pContext; + MA_ASSERT(pContextStateALSA != NULL); + MA_ASSERT(pDeviceStateALSA != NULL); + MA_ASSERT(pDeviceConfigALSA != NULL); + MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should only be called for playback _or_ capture, never duplex. */ - if (pData->pDeviceID == NULL && ma_strcmp(pDeviceInfo->id.alsa, "default") == 0) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); - pData->foundDevice = MA_TRUE; - } else { - if (pData->deviceType == deviceType && (pData->pDeviceID != NULL && ma_strcmp(pData->pDeviceID->alsa, pDeviceInfo->id.alsa) == 0)) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); - pData->foundDevice = MA_TRUE; - } + formatALSA = ma_convert_ma_format_to_alsa_format(pDescriptor->format); + + openMode = 0; + if (pDeviceConfigALSA->noAutoResample) { + openMode |= MA_SND_PCM_NO_AUTO_RESAMPLE; + } + if (pDeviceConfigALSA->noAutoChannels) { + openMode |= MA_SND_PCM_NO_AUTO_CHANNELS; + } + if (pDeviceConfigALSA->noAutoFormat) { + openMode |= MA_SND_PCM_NO_AUTO_FORMAT; } - /* Keep enumerating until we have found the device. */ - return !pData->foundDevice; -} -static void ma_context_test_rate_and_add_native_data_format__alsa(ma_context* pContext, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags, ma_device_info* pDeviceInfo) -{ - MA_ASSERT(pPCM != NULL); - MA_ASSERT(pHWParams != NULL); - MA_ASSERT(pDeviceInfo != NULL); + /* When a device ID is specified we'll use that exact name, otherwise we'll use a list of default devices to try. */ + MA_ZERO_MEMORY(pDeviceNames, sizeof(pDeviceNames)); + iDeviceName = 0; - if (pDeviceInfo->nativeDataFormatCount < ma_countof(pDeviceInfo->nativeDataFormats) && ((ma_snd_pcm_hw_params_test_rate_proc)pContext->alsa.snd_pcm_hw_params_test_rate)(pPCM, pHWParams, sampleRate, 0) == 0) { - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags; - pDeviceInfo->nativeDataFormatCount += 1; + if (pDescriptor->pDeviceID == NULL || pDescriptor->pDeviceID->alsa[0] == '\0') { + /* Using default device. */ + pDeviceNames[iDeviceName++] = "default"; + pDeviceNames[iDeviceName++] = "sysdefault"; + + if (pDescriptor->shareMode == ma_share_mode_exclusive) { + pDeviceNames[iDeviceName++] = "hw"; + } else { + if (deviceType == ma_device_type_playback) { + pDeviceNames[iDeviceName++] = "dmix"; + } else { + pDeviceNames[iDeviceName++] = "dsnoop"; + } + } + } else { + /* Using a specific device. */ + pDeviceNames[iDeviceName++] = pDescriptor->pDeviceID->alsa; } -} -static void ma_context_iterate_rates_and_add_native_data_format__alsa(ma_context* pContext, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 flags, ma_device_info* pDeviceInfo) -{ - ma_uint32 iSampleRate; - unsigned int minSampleRate; - unsigned int maxSampleRate; - int sampleRateDir; /* Not used. Just passed into snd_pcm_hw_params_get_rate_min/max(). */ + deviceNameCount = iDeviceName; - /* There could be a range. */ - ((ma_snd_pcm_hw_params_get_rate_min_proc)pContext->alsa.snd_pcm_hw_params_get_rate_min)(pHWParams, &minSampleRate, &sampleRateDir); - ((ma_snd_pcm_hw_params_get_rate_max_proc)pContext->alsa.snd_pcm_hw_params_get_rate_max)(pHWParams, &maxSampleRate, &sampleRateDir); - /* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculous. */ - minSampleRate = ma_clamp(minSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); - maxSampleRate = ma_clamp(maxSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); + /* Alias the memory allocation. */ + pHWParams = (ma_snd_pcm_hw_params_t*)pParamsMemory; + pSWParams = (ma_snd_pcm_sw_params_t*)pParamsMemory; - for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); iSampleRate += 1) { - ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iSampleRate]; + for (iDeviceName = 0; iDeviceName < deviceNameCount; iDeviceName += 1) { + const char* pDeviceName = pDeviceNames[iDeviceName]; - if (standardSampleRate >= minSampleRate && standardSampleRate <= maxSampleRate) { - ma_context_test_rate_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, standardSampleRate, flags, pDeviceInfo); + resultALSA = pContextStateALSA->snd_pcm_open(&pPCM, pDeviceName, (deviceType == ma_device_type_playback) ? MA_SND_PCM_STREAM_PLAYBACK : MA_SND_PCM_STREAM_CAPTURE, openMode); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed for device \"%s\". %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; } - } - /* Now make sure our min and max rates are included just in case they aren't in the range of our standard rates. */ - if (!ma_is_standard_sample_rate(minSampleRate)) { - ma_context_test_rate_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, minSampleRate, flags, pDeviceInfo); - } + resultALSA = pContextStateALSA->snd_pcm_hw_params_any(pPCM, pHWParams); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters for device \"%s\". snd_pcm_hw_params_any() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } - if (!ma_is_standard_sample_rate(maxSampleRate) && maxSampleRate != minSampleRate) { - ma_context_test_rate_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, maxSampleRate, flags, pDeviceInfo); - } -} + /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ + isUsingMMap = MA_FALSE; + #if 0 /* NOTE: MMAP mode temporarily disabled. */ + if (deviceType != ma_device_type_capture) { /* <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it which means I can't test it... Contributions welcome. */ + if (!pConfig->alsa.noMMap) { + if (pContextStateALSA->snd_pcm_hw_params_set_access(pPCM, pHWParams, MA_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { + pDeviceStateALSA->isUsingMMap = MA_TRUE; + } + } + } + #endif -static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) -{ - ma_context_get_device_info_enum_callback_data__alsa data; - ma_result result; - int resultALSA; - ma_snd_pcm_t* pPCM; - ma_snd_pcm_hw_params_t* pHWParams; - ma_uint32 iFormat; - ma_uint32 iChannel; + if (!isUsingMMap) { + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_access(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED for device \"%s\". snd_pcm_hw_params_set_access() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + } - MA_ASSERT(pContext != NULL); + /* The configuration order is the same as what aplay does: format, channels, sample rate, periods. */ - /* We just enumerate to find basic information about the device. */ - data.deviceType = deviceType; - data.pDeviceID = pDeviceID; - data.pDeviceInfo = pDeviceInfo; - data.foundDevice = MA_FALSE; - result = ma_context_enumerate_devices__alsa(pContext, ma_context_get_device_info_enum_callback__alsa, &data); - if (result != MA_SUCCESS) { - return result; - } + /* Format. */ + { + /* + At this point we should have a list of supported formats, so now we need to find the best one. We first check if the requested format is + supported, and if so, use that one. If it's not supported, we just run though a list of formats and try to find the best one. + */ + if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN || pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, formatALSA) != 0) { + /* We're either requesting the native format or the specified format is not supported. */ + size_t iFormat; + + formatALSA = MA_SND_PCM_FORMAT_UNKNOWN; + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); ++iFormat) { + if (pContextStateALSA->snd_pcm_hw_params_test_format(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat])) == 0) { + formatALSA = ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat]); + break; + } + } - if (!data.foundDevice) { - return MA_NO_DEVICE; - } + if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device \"%s\" does not support any miniaudio formats. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + } - if (ma_strcmp(pDeviceInfo->id.alsa, "default") == 0) { - pDeviceInfo->isDefault = MA_TRUE; - } + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_format(pPCM, pHWParams, formatALSA); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_hw_params_set_format() failed for device \"%s\". %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } - /* For detailed info we need to open the device. */ - result = ma_context_open_pcm__alsa(pContext, ma_share_mode_shared, deviceType, pDeviceID, 0, &pPCM); - if (result != MA_SUCCESS) { - return result; - } + internalFormat = ma_format_from_alsa(formatALSA); + if (internalFormat == ma_format_unknown) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format used by device \"%s\" is not supported by miniaudio. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + } - /* We need to initialize a HW parameters object in order to know what formats are supported. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); - if (pHWParams == NULL) { - ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); - return MA_OUT_OF_MEMORY; - } + /* Channels. */ + { + unsigned int channels = pDescriptor->channels; - resultALSA = ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); - if (resultALSA < 0) { - ma_free(pHWParams, &pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); - return ma_result_from_errno(-resultALSA); - } + /* If an explicit channel count was requested try using that directly. If this fails we'll fall back to set_channels_near(). */ + resultALSA = -1; + if (channels != 0 && pContextStateALSA->snd_pcm_hw_params_test_channels(pPCM, pHWParams, channels) == 0) { + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_channels(pPCM, pHWParams, channels); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[ALSA] Failed to set channel count for device \"%s\" to %u. Falling back to snd_pcm_hw_params_set_channels_near(). %s.", pDeviceName, channels, pContextStateALSA->snd_strerror(resultALSA)); + } + } - /* - Some ALSA devices can support many permutations of formats, channels and rates. We only support - a fixed number of permutations which means we need to employ some strategies to ensure the best - combinations are returned. An example is the "pulse" device which can do its own data conversion - in software and as a result can support any combination of format, channels and rate. + /* Fallback to set_channels_near() if we couldn't set the exact channel count. */ + if (resultALSA < 0) { + if (channels == 0) { + /* + Gettting here means we want to use the "native" channel count. There's no good way that I can tell + to retrieve this because a device can support multiple channel counts. It's tempting to use the + maximum channel count, but that cannot be used generically because many virtual devices will report + unpractical counts like 64, or even 10000 which I've seen in the wild. - We want to ensure that the first data formats are the best. We have a list of favored sample - formats and sample rates, so these will be the basis of our iteration. - */ + However, I think a good compromise might be to check if we're opening an actual hardware device, and + if so accept the maximum channel count in that case. Otherwise we'll just use stereo as the default. + */ + ma_snd_pcm_type_t pcmType = pContextStateALSA->snd_pcm_type(pPCM); + if (pcmType == MA_SND_PCM_TYPE_HW) { + /* Hardware device. Just use the maximum count available. */ + resultALSA = pContextStateALSA->snd_pcm_hw_params_get_channels_max(pHWParams, &channels); + if (resultALSA < 0) { + channels = MA_DEFAULT_CHANNELS; + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[ALSA] Trying to open hardware device \"%s\", but requesting the maximum channel count failed. Defaulting to stereo. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + } else { + if (channels > MA_ALSA_MAX_NATIVE_CHANNELS) { + channels = MA_ALSA_MAX_NATIVE_CHANNELS; + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[ALSA] Native channel count of %u for hardware device \"%s\" exceeds maximum supported by miniaudio's ALSA backend of %d. Clamping channels to %d.", channels, pDeviceName, MA_ALSA_MAX_NATIVE_CHANNELS, MA_ALSA_MAX_NATIVE_CHANNELS); + } + } + } else { + /* Not a hardware device. No way to determine an appropriate native channel count so just default to stereo. */ + channels = MA_DEFAULT_CHANNELS; + } + } - /* Formats. We just iterate over our standard formats and test them, making sure we reset the configuration space each iteration. */ - for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) { - ma_format format = g_maFormatPriorities[iFormat]; + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_channels_near(pPCM, pHWParams, &channels); + if (resultALSA < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count for device \"%s\" with snd_pcm_hw_params_set_channels_near(). %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + } + } - /* - For each format we need to make sure we reset the configuration space so we don't return - channel counts and rates that aren't compatible with a format. - */ - ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); + /* If we get here and we still haven't been able to find a channel count just bomb out. */ + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + continue; + } - /* Test the format first. If this fails it means the format is not supported and we can skip it. */ - if (((ma_snd_pcm_hw_params_test_format_proc)pContext->alsa.snd_pcm_hw_params_test_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)) == 0) { - /* The format is supported. */ - unsigned int minChannels; - unsigned int maxChannels; + internalChannels = (ma_uint32)channels; + } + + /* Sample Rate */ + { + unsigned int sampleRate; /* - The configuration space needs to be restricted to this format so we can get an accurate - picture of which sample rates and channel counts are support with this format. + It appears there's either a bug in ALSA, a bug in some drivers, or I'm doing something silly; but having resampling enabled causes + problems with some device configurations when used in conjunction with MMAP access mode. To fix this problem we need to disable + resampling. + + To reproduce this problem, open the "plug:dmix" device, and set the sample rate to 44100. Internally, it looks like dmix uses a + sample rate of 48000. The hardware parameters will get set correctly with no errors, but it looks like the 44100 -> 48000 resampling + doesn't work properly - but only with MMAP access mode. You will notice skipping/crackling in the audio, and it'll run at a slightly + faster rate. + + miniaudio has built-in support for sample rate conversion (albeit low quality at the moment), so disabling resampling should be fine + for us. The only problem is that it won't be taking advantage of any kind of hardware-accelerated resampling and it won't be very + good quality until I get a chance to improve the quality of miniaudio's software sample rate conversion. + + I don't currently know if the dmix plugin is the only one with this error. Indeed, this is the only one I've been able to reproduce + this error with. In the future, we may want to restrict the disabling of resampling to only known bad plugins. */ - ((ma_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)); + pContextStateALSA->snd_pcm_hw_params_set_rate_resample(pPCM, pHWParams, 0); - /* Now we need to check for supported channels. */ - ((ma_snd_pcm_hw_params_get_channels_min_proc)pContext->alsa.snd_pcm_hw_params_get_channels_min)(pHWParams, &minChannels); - ((ma_snd_pcm_hw_params_get_channels_max_proc)pContext->alsa.snd_pcm_hw_params_get_channels_max)(pHWParams, &maxChannels); + sampleRate = pDescriptor->sampleRate; + if (sampleRate == 0) { + sampleRate = MA_DEFAULT_SAMPLE_RATE; + } - if (minChannels > MA_MAX_CHANNELS) { - continue; /* Too many channels. */ + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_rate_near(pPCM, pHWParams, &sampleRate, 0); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_hw_params_set_rate_near() failed for device \"%s\". %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; } - if (maxChannels < MA_MIN_CHANNELS) { - continue; /* Not enough channels. */ + + internalSampleRate = (ma_uint32)sampleRate; + } + + /* Periods. */ + { + ma_uint32 periods = pDescriptor->periodCount; + + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_periods_near(pPCM, pHWParams, &periods, NULL); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count for device \"%s\". snd_pcm_hw_params_set_periods_near() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; } + internalPeriods = periods; + } + + /* Buffer Size */ + { + ma_snd_pcm_uframes_t actualBufferSizeInFrames; + /* - Make sure the channel count is clamped. This is mainly intended for the max channels - because some devices can report an unbound maximum. + Down below when we configure our software parameters we'll be setting snd_pcm_sw_params_set_avail_min() to + a value equal to a single period. The ALSA documentation says the following: + + The valid values for ‘val’ are determined by the specific hardware. Most PC sound cards can only accept + power of 2 frame counts (i.e. 512, 1024, 2048). You cannot use this as a high resolution timer - it is + limited to how often the sound card hardware raises an interrupt. + + For this reason I'm going to be forcing periods to a power of 2. I've noticed in practice that this does + indeed seem to make an actual real-world difference. If I set it to, say, 96 frames per period, I will fairly + frequently get horrible glitching in duplex mode, almost as if the buffer is stuck straddling the end of a + buffer. So far I've had no such issues when setting to something like 64. */ - minChannels = ma_clamp(minChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); - maxChannels = ma_clamp(maxChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + actualBufferSizeInFrames = ma_prev_power_of_2(ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate)) * internalPeriods; - if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) { - /* The device supports all channels. Don't iterate over every single one. Instead just set the channels to 0 which means all channels are supported. */ - ma_context_iterate_rates_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, 0, 0, pDeviceInfo); /* Intentionally setting the channel count to 0 as that means all channels are supported. */ - } else { - /* The device only supports a specific set of channels. We need to iterate over all of them. */ - for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) { - /* Test the channel before applying it to the configuration space. */ - unsigned int channels = iChannel; + resultALSA = pContextStateALSA->snd_pcm_hw_params_set_buffer_size_near(pPCM, pHWParams, &actualBufferSizeInFrames); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device \"%s\". snd_pcm_hw_params_set_buffer_size() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + + internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; + } + + /* Apply hardware parameters. */ + resultALSA = pContextStateALSA->snd_pcm_hw_params(pPCM, pHWParams); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters for device \"%s\". snd_pcm_hw_params() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + + /* + The hardware parameters memory is aliased with the software parameters memory, so just clear out this pointer + for safety and to make it clear that it should not be used from here on out. + */ + pHWParams = NULL; + + + /* Software parameters. */ + resultALSA = pContextStateALSA->snd_pcm_sw_params_current(pPCM, pSWParams); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters for device \"%s\". snd_pcm_sw_params_current() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + + resultALSA = pContextStateALSA->snd_pcm_sw_params_set_avail_min(pPCM, pSWParams, internalPeriodSizeInFrames); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed for device \"%s\". %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + + /* + miniaudio uses an explicit snd_pcm_start() to start the PCM. From the ALSA documentation: + + If you want to use explicit start (snd_pcm_start), you can set this value greater than the ring buffer + size (in frames). For that simply using a large constant such as LONG_MAX or the boundary value is not + a bad idea. + + We'll just set the threshold to 0xFFFFFFFF. + + The reason we want explicitness when starting is mainly just duplex mode. We want to pre-fill the buffer + with silence, but we also want to link the playback PCM to capture PCM and use the capture side as the + master. The playback side should not auto-start during this pre-filling, but should instead be delayed to + the explicit snd_pcm_start(). + */ + if (deviceType == ma_device_type_playback) { + resultALSA = pContextStateALSA->snd_pcm_sw_params_set_start_threshold(pPCM, pSWParams, 0xFFFFFFFF); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device \"%s\". snd_pcm_sw_params_set_start_threshold() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + } + + resultALSA = pContextStateALSA->snd_pcm_sw_params(pPCM, pSWParams); + if (resultALSA < 0) { + pContextStateALSA->snd_pcm_close(pPCM); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters for device \"%s\". snd_pcm_sw_params() failed. %s.", pDeviceName, pContextStateALSA->snd_strerror(resultALSA)); + continue; + } + + pSWParams = NULL; - /* Make sure our channel range is reset before testing again or else we'll always fail the test. */ - ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); - ((ma_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)); - if (((ma_snd_pcm_hw_params_test_channels_proc)pContext->alsa.snd_pcm_hw_params_test_channels)(pPCM, pHWParams, channels) == 0) { - /* The channel count is supported. */ + /* Grab the internal channel map. For now we're not going to bother trying to change the channel map and instead just do it ourselves. */ + { + if (pDeviceConfigALSA->assumeDefaultChannelLayout) { + ma_snd_pcm_chmap_t* pChmap = NULL; + if (pContextStateALSA->snd_pcm_get_chmap != NULL) { + pChmap = pContextStateALSA->snd_pcm_get_chmap(pPCM); + } - /* The configuration space now needs to be restricted to the channel count before extracting the sample rate. */ - ((ma_snd_pcm_hw_params_set_channels_proc)pContext->alsa.snd_pcm_hw_params_set_channels)(pPCM, pHWParams, channels); + if (pChmap != NULL) { + ma_uint32 iChannel; - /* Only after the configuration space has been restricted to the specific channel count should we iterate over our sample rates. */ - ma_context_iterate_rates_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, 0, pDeviceInfo); + /* There are cases where the returned channel map can have a different channel count than was returned by snd_pcm_hw_params_set_channels_near(). */ + if (pChmap->channels >= internalChannels) { + /* Drop excess channels. */ + for (iChannel = 0; iChannel < internalChannels; ++iChannel) { + internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); + } } else { - /* The channel count is not supported. Skip. */ + ma_uint32 i; + + /* + Excess channels use defaults. Do an initial fill with defaults, overwrite the first pChmap->channels, validate to ensure there are no duplicate + channels. If validation fails, fall back to defaults. + */ + ma_bool32 isValid = MA_TRUE; + + /* Fill with defaults. */ + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); + + /* Overwrite first pChmap->channels channels. */ + for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { + internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); + } + + /* Validate. */ + for (i = 0; i < internalChannels && isValid; ++i) { + ma_uint32 j; + for (j = i+1; j < internalChannels; ++j) { + if (internalChannelMap[i] == internalChannelMap[j]) { + isValid = MA_FALSE; + break; + } + } + } + + /* If our channel map is invalid, fall back to defaults. */ + if (!isValid) { + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); + } } + + free(pChmap); + pChmap = NULL; + } else { + /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } + } else { + /* The caller has requested that we always use the default ALSA channel layout. */ + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } + } + + + /* If we've made it this far it means we successfully initialized the PCM object. */ + if (deviceType == ma_device_type_capture) { + pDeviceStateALSA->pPCMCapture = pPCM; + pDeviceStateALSA->isUsingMMapCapture = isUsingMMap; } else { - /* The format is not supported. Skip. */ + pDeviceStateALSA->pPCMPlayback = pPCM; + pDeviceStateALSA->isUsingMMapPlayback = isUsingMMap; } - } - ma_free(pHWParams, &pContext->allocationCallbacks); + pDescriptor->format = internalFormat; + pDescriptor->channels = internalChannels; + pDescriptor->sampleRate = internalSampleRate; + ma_channel_map_copy(pDescriptor->channelMap, internalChannelMap, ma_min(internalChannels, MA_MAX_CHANNELS)); + pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; + pDescriptor->periodCount = internalPeriods; - ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); - return MA_SUCCESS; + return MA_SUCCESS; + } + + /* Getting here means we failed to initialize the device. */ + return MA_ERROR; } -static ma_result ma_device_uninit__alsa(ma_device* pDevice) +static void ma_device_uninit_internal__alsa(ma_device* pDevice, ma_device_state_alsa* pDeviceStateALSA) { - MA_ASSERT(pDevice != NULL); + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); - if ((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) { - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - close(pDevice->alsa.wakeupfdCapture); - ma_free(pDevice->alsa.pPollDescriptorsCapture, &pDevice->pContext->allocationCallbacks); + MA_ASSERT(pDeviceStateALSA != NULL); + + if (pDeviceStateALSA->pPCMCapture) { + pContextStateALSA->snd_pcm_close(pDeviceStateALSA->pPCMCapture); + } + if (pDeviceStateALSA->pPCMPlayback) { + pContextStateALSA->snd_pcm_close(pDeviceStateALSA->pPCMPlayback); } - if ((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) { - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); - close(pDevice->alsa.wakeupfdPlayback); - ma_free(pDevice->alsa.pPollDescriptorsPlayback, &pDevice->pContext->allocationCallbacks); + if (pDeviceStateALSA->wakeupfd >= 0) { + close(pDeviceStateALSA->wakeupfd); } - return MA_SUCCESS; + ma_free(pDeviceStateALSA, ma_device_get_allocation_callbacks(pDevice)); } -static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) +static ma_result ma_device_init__alsa(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { + ma_device_state_alsa* pDeviceStateALSA; + const ma_device_config_alsa* pDeviceConfigALSA = (const ma_device_config_alsa*)pDeviceBackendConfig; + ma_device_config_alsa defaultConfigALSA; + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); ma_result result; int resultALSA; - ma_snd_pcm_t* pPCM; - ma_bool32 isUsingMMap; - ma_snd_pcm_format_t formatALSA; - ma_format internalFormat; - ma_uint32 internalChannels; - ma_uint32 internalSampleRate; - ma_channel internalChannelMap[MA_MAX_CHANNELS]; - ma_uint32 internalPeriodSizeInFrames; - ma_uint32 internalPeriods; - int openMode; - ma_snd_pcm_hw_params_t* pHWParams; - ma_snd_pcm_sw_params_t* pSWParams; - ma_snd_pcm_uframes_t bufferBoundary; - int pollDescriptorCount; - struct pollfd* pPollDescriptors; - int wakeupfd; - - MA_ASSERT(pConfig != NULL); - MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should only be called for playback _or_ capture, never duplex. */ - MA_ASSERT(pDevice != NULL); + int pollDescriptorCountPlayback = 0; + int pollDescriptorCountCapture = 0; + ma_uint32 intermediaryBufferSizeCapture = 0; + ma_uint32 intermediaryBufferSizePlayback = 0; + size_t pollDescriptorAllocationSize = 0; + size_t intermediaryBufferAllocationSize = 0; + size_t paramsMemorySize; + void* pParamsMemory; - formatALSA = ma_convert_ma_format_to_alsa_format(pDescriptor->format); + MA_ASSERT(pContextStateALSA != NULL); - openMode = 0; - if (pConfig->alsa.noAutoResample) { - openMode |= MA_SND_PCM_NO_AUTO_RESAMPLE; - } - if (pConfig->alsa.noAutoChannels) { - openMode |= MA_SND_PCM_NO_AUTO_CHANNELS; - } - if (pConfig->alsa.noAutoFormat) { - openMode |= MA_SND_PCM_NO_AUTO_FORMAT; + if (pDeviceConfigALSA == NULL) { + defaultConfigALSA = ma_device_config_alsa_init(); + pDeviceConfigALSA = &defaultConfigALSA; } - result = ma_context_open_pcm__alsa(pDevice->pContext, pDescriptor->shareMode, deviceType, pDescriptor->pDeviceID, openMode, &pPCM); - if (result != MA_SUCCESS) { - return result; + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } + /* + The memory for hw and sw params are allocated on the heap. We'll do that as part of the device state allocation + so I can obsessively reduce our malloc count by 1. It'll be realloc'd later when we append memory for the pollfd + objects and intermediary buffer so shouldn't be wasteful. + */ + paramsMemorySize = ma_max(pContextStateALSA->snd_pcm_hw_params_sizeof(), pContextStateALSA->snd_pcm_sw_params_sizeof()); - /* Hardware parameters. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); - if (pHWParams == NULL) { - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for hardware parameters."); + pDeviceStateALSA = (ma_device_state_alsa*)ma_calloc(ma_align_64(sizeof(*pDeviceStateALSA)) + paramsMemorySize, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateALSA == NULL) { return MA_OUT_OF_MEMORY; } - resultALSA = ((ma_snd_pcm_hw_params_any_proc)pDevice->pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); - if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); - return ma_result_from_errno(-resultALSA); - } + pParamsMemory = ma_offset_ptr(pDeviceStateALSA, ma_align_64(sizeof(*pDeviceStateALSA))); - /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ - isUsingMMap = MA_FALSE; -#if 0 /* NOTE: MMAP mode temporarily disabled. */ - if (deviceType != ma_device_type_capture) { /* <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it which means I can't test it... Contributions welcome. */ - if (!pConfig->alsa.noMMap) { - if (((ma_snd_pcm_hw_params_set_access_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { - pDevice->alsa.isUsingMMap = MA_TRUE; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + /* Duplex mode must have at least two periods. */ + if (deviceType == ma_device_type_duplex) { + if (pDescriptorCapture->periodCount < 2) { + pDescriptorCapture->periodCount = 2; } } - } -#endif - if (!isUsingMMap) { - resultALSA = ((ma_snd_pcm_hw_params_set_access_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); - if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed."); - return ma_result_from_errno(-resultALSA); + result = ma_device_init_by_type__alsa(ma_device_get_context(pDevice), pContextStateALSA, pDeviceStateALSA, pDeviceConfigALSA, pDescriptorCapture, ma_device_type_capture, pParamsMemory); + if (result != MA_SUCCESS) { + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return result; } - } - /* - Most important properties first. The documentation for OSS (yes, I know this is ALSA!) recommends format, channels, then sample rate. I can't - find any documentation for ALSA specifically, so I'm going to copy the recommendation for OSS. - */ + pollDescriptorCountCapture = pContextStateALSA->snd_pcm_poll_descriptors_count(pDeviceStateALSA->pPCMCapture); + if (pollDescriptorCountCapture <= 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return MA_ERROR; + } - /* Format. */ - { - /* - At this point we should have a list of supported formats, so now we need to find the best one. We first check if the requested format is - supported, and if so, use that one. If it's not supported, we just run though a list of formats and try to find the best one. - */ - if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN || ((ma_snd_pcm_hw_params_test_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_test_format)(pPCM, pHWParams, formatALSA) != 0) { - /* We're either requesting the native format or the specified format is not supported. */ - size_t iFormat; - - formatALSA = MA_SND_PCM_FORMAT_UNKNOWN; - for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); ++iFormat) { - if (((ma_snd_pcm_hw_params_test_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_test_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat])) == 0) { - formatALSA = ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat]); - break; - } - } + intermediaryBufferSizeCapture = ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodSizeInFrames; + } - if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats."); - return MA_FORMAT_NOT_SUPPORTED; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + /* Duplex mode must have at least two periods. */ + if (deviceType == ma_device_type_duplex) { + if (pDescriptorPlayback->periodCount < 2) { + pDescriptorPlayback->periodCount = 2; } } - resultALSA = ((ma_snd_pcm_hw_params_set_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, formatALSA); - if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed."); - return ma_result_from_errno(-resultALSA); + result = ma_device_init_by_type__alsa(ma_device_get_context(pDevice), pContextStateALSA, pDeviceStateALSA, pDeviceConfigALSA, pDescriptorPlayback, ma_device_type_playback, pParamsMemory); + if (result != MA_SUCCESS) { + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return result; } - internalFormat = ma_format_from_alsa(formatALSA); - if (internalFormat == ma_format_unknown) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio."); - return MA_FORMAT_NOT_SUPPORTED; + pollDescriptorCountPlayback = pContextStateALSA->snd_pcm_poll_descriptors_count(pDeviceStateALSA->pPCMPlayback); + if (pollDescriptorCountPlayback <= 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return MA_ERROR; } + + intermediaryBufferSizePlayback = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodSizeInFrames; } - /* Channels. */ + pDeviceStateALSA->pollDescriptorCount = 1 + pollDescriptorCountCapture + pollDescriptorCountPlayback; + + /* + Now we need to allocate some memory for the poll descriptors and intermediary buffer. We can do this + as a single allocation, and place it at the end of the device state allocation. + */ + pollDescriptorAllocationSize = ma_align_64(sizeof(struct pollfd) * (pDeviceStateALSA->pollDescriptorCount)); + intermediaryBufferAllocationSize = ma_align_64(ma_max(intermediaryBufferSizeCapture, intermediaryBufferSizePlayback)); + { - unsigned int channels = pDescriptor->channels; - if (channels == 0) { - channels = MA_DEFAULT_CHANNELS; - } + size_t deviceStateAllocationSize; + ma_device_state_alsa* pDeviceStateALSANew; - resultALSA = ((ma_snd_pcm_hw_params_set_channels_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_channels_near)(pPCM, pHWParams, &channels); - if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed."); - return ma_result_from_errno(-resultALSA); + deviceStateAllocationSize = 0; + deviceStateAllocationSize += ma_align_64(sizeof(*pDeviceStateALSA)); + deviceStateAllocationSize += pollDescriptorAllocationSize; + deviceStateAllocationSize += intermediaryBufferAllocationSize; + + pDeviceStateALSANew = (ma_device_state_alsa*)ma_realloc(pDeviceStateALSA, deviceStateAllocationSize, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateALSANew == NULL) { + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return MA_OUT_OF_MEMORY; } - internalChannels = (ma_uint32)channels; + pDeviceStateALSA = pDeviceStateALSANew; } - /* Sample Rate */ - { - unsigned int sampleRate; - - /* - It appears there's either a bug in ALSA, a bug in some drivers, or I'm doing something silly; but having resampling enabled causes - problems with some device configurations when used in conjunction with MMAP access mode. To fix this problem we need to disable - resampling. - - To reproduce this problem, open the "plug:dmix" device, and set the sample rate to 44100. Internally, it looks like dmix uses a - sample rate of 48000. The hardware parameters will get set correctly with no errors, but it looks like the 44100 -> 48000 resampling - doesn't work properly - but only with MMAP access mode. You will notice skipping/crackling in the audio, and it'll run at a slightly - faster rate. + pDeviceStateALSA->pPollDescriptors = (struct pollfd*)ma_offset_ptr(pDeviceStateALSA, ma_align_64(sizeof(*pDeviceStateALSA))); + pDeviceStateALSA->pIntermediaryBuffer = (void*)ma_offset_ptr(pDeviceStateALSA, ma_align_64(sizeof(*pDeviceStateALSA)) + pollDescriptorAllocationSize); - miniaudio has built-in support for sample rate conversion (albeit low quality at the moment), so disabling resampling should be fine - for us. The only problem is that it won't be taking advantage of any kind of hardware-accelerated resampling and it won't be very - good quality until I get a chance to improve the quality of miniaudio's software sample rate conversion. - I don't currently know if the dmix plugin is the only one with this error. Indeed, this is the only one I've been able to reproduce - this error with. In the future, we may want to restrict the disabling of resampling to only known bad plugins. - */ - ((ma_snd_pcm_hw_params_set_rate_resample_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_rate_resample)(pPCM, pHWParams, 0); + /* + We need an array of poll descriptors to check for when data is available. We query these from ALSA, but we also want to + include an extra one specifically for waking up for the purpose of our wakeup() callback. + */ + pDeviceStateALSA->wakeupfd = eventfd(0, 0); + if (pDeviceStateALSA->wakeupfd < 0) { + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup."); + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return MA_ERROR; + } - sampleRate = pDescriptor->sampleRate; - if (sampleRate == 0) { - sampleRate = MA_DEFAULT_SAMPLE_RATE; - } + pDeviceStateALSA->pPollDescriptors[0].fd = pDeviceStateALSA->wakeupfd; + pDeviceStateALSA->pPollDescriptors[0].events = POLLIN; + pDeviceStateALSA->pPollDescriptors[0].revents = 0; - resultALSA = ((ma_snd_pcm_hw_params_set_rate_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_rate_near)(pPCM, pHWParams, &sampleRate, 0); + if (pollDescriptorCountCapture > 0) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors(pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pPollDescriptors + 1, pollDescriptorCountCapture); if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed."); - return ma_result_from_errno(-resultALSA); + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors. %s.", pContextStateALSA->snd_strerror(resultALSA)); + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return MA_ERROR; } - internalSampleRate = (ma_uint32)sampleRate; + pDeviceStateALSA->pollDescriptorCountCapture = pollDescriptorCountCapture; } - /* Periods. */ - { - ma_uint32 periods = pDescriptor->periodCount; - - resultALSA = ((ma_snd_pcm_hw_params_set_periods_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_periods_near)(pPCM, pHWParams, &periods, NULL); + if (pollDescriptorCountPlayback > 0) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors(pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pPollDescriptors + 1 + pollDescriptorCountCapture, pollDescriptorCountPlayback); if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed."); - return ma_result_from_errno(-resultALSA); + ma_log_postf(ma_context_get_log(ma_device_get_context(pDevice)), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors. %s.", pContextStateALSA->snd_strerror(resultALSA)); + ma_device_uninit_internal__alsa(pDevice, pDeviceStateALSA); + return MA_ERROR; } - internalPeriods = periods; + pDeviceStateALSA->pollDescriptorCountPlayback = pollDescriptorCountPlayback; } - /* Buffer Size */ - { - ma_snd_pcm_uframes_t actualBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate, pConfig->performanceProfile) * internalPeriods; - resultALSA = ((ma_snd_pcm_hw_params_set_buffer_size_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)(pPCM, pHWParams, &actualBufferSizeInFrames); - if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed."); - return ma_result_from_errno(-resultALSA); - } + /* Make sure the intermediary buffer is silenced just to be safe. */ + MA_ZERO_MEMORY(pDeviceStateALSA->pIntermediaryBuffer, intermediaryBufferAllocationSize); - internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; - } - /* Apply hardware parameters. */ - resultALSA = ((ma_snd_pcm_hw_params_proc)pDevice->pContext->alsa.snd_pcm_hw_params)(pPCM, pHWParams); - if (resultALSA < 0) { - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed."); - return ma_result_from_errno(-resultALSA); + /* Link the PCMs in duplex mode. */ + if (deviceType == ma_device_type_duplex) { + resultALSA = pContextStateALSA->snd_pcm_link(pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pPCMPlayback); + if (resultALSA == 0) { + pDeviceStateALSA->isLinked = MA_TRUE; + } } - ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); - pHWParams = NULL; + *ppDeviceState = pDeviceStateALSA; - /* Software parameters. */ - pSWParams = (ma_snd_pcm_sw_params_t*)ma_calloc(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); - if (pSWParams == NULL) { - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for software parameters."); - return MA_OUT_OF_MEMORY; - } + return MA_SUCCESS; +} - resultALSA = ((ma_snd_pcm_sw_params_current_proc)pDevice->pContext->alsa.snd_pcm_sw_params_current)(pPCM, pSWParams); - if (resultALSA < 0) { - ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed."); - return ma_result_from_errno(-resultALSA); - } +static void ma_device_uninit__alsa(ma_device* pDevice) +{ + ma_device_uninit_internal__alsa(pDevice, ma_device_get_backend_state__alsa(pDevice)); +} - resultALSA = ((ma_snd_pcm_sw_params_set_avail_min_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); - if (resultALSA < 0) { - ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed."); - return ma_result_from_errno(-resultALSA); +static void ma_device_prime_playback_buffer__alsa(ma_device* pDevice) +{ + ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice); + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); + ma_uint8 buffer[4096]; + ma_uint32 bpf; + ma_uint32 framesToWrite; + ma_uint32 framesWritten; + ma_snd_pcm_sframes_t framesAvail; + + if (pDeviceStateALSA->pPCMPlayback == NULL) { + return; } - resultALSA = ((ma_snd_pcm_sw_params_get_boundary_proc)pDevice->pContext->alsa.snd_pcm_sw_params_get_boundary)(pSWParams, &bufferBoundary); - if (resultALSA < 0) { - bufferBoundary = internalPeriodSizeInFrames * internalPeriods; + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + framesToWrite = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods; + framesWritten = 0; + + MA_ZERO_MEMORY(buffer, sizeof(buffer)); + + /* Guard against the available frame count reported by ALSA just in case we try writing too much and get stuck in snd_pcm_writei().. */ + framesAvail = pContextStateALSA->snd_pcm_avail(pDeviceStateALSA->pPCMPlayback); + if (framesToWrite > framesAvail) { + framesToWrite = framesAvail; } - if (deviceType == ma_device_type_playback && !isUsingMMap) { /* Only playback devices in writei/readi mode need a start threshold. */ - /* - Subtle detail here with the start threshold. When in playback-only mode (no full-duplex) we can set the start threshold to - the size of a period. But for full-duplex we need to set it such that it is at least two periods. - */ - resultALSA = ((ma_snd_pcm_sw_params_set_start_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_start_threshold)(pPCM, pSWParams, internalPeriodSizeInFrames*2); - if (resultALSA < 0) { - ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed."); - return ma_result_from_errno(-resultALSA); + while (framesWritten < framesToWrite) { + ma_uint32 framesToWriteThisIteration = sizeof(buffer) / bpf; + ma_uint32 framesRemaining = framesToWrite - framesWritten; + if (framesToWriteThisIteration > framesRemaining) { + framesToWriteThisIteration = framesRemaining; } - resultALSA = ((ma_snd_pcm_sw_params_set_stop_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_stop_threshold)(pPCM, pSWParams, bufferBoundary); - if (resultALSA < 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ - ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed."); - return ma_result_from_errno(-resultALSA); + /* Just a guard to ensure we don't get stuck in a loop. Should never happen in practice (would require a massive channel count). */ + if (framesToWriteThisIteration == 0) { + break; } - } - resultALSA = ((ma_snd_pcm_sw_params_proc)pDevice->pContext->alsa.snd_pcm_sw_params)(pPCM, pSWParams); - if (resultALSA < 0) { - ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed."); - return ma_result_from_errno(-resultALSA); + pContextStateALSA->snd_pcm_writei(pDeviceStateALSA->pPCMPlayback, buffer, framesToWriteThisIteration); + framesWritten += framesToWriteThisIteration; } +} - ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); - pSWParams = NULL; +static ma_result ma_device_start__alsa(ma_device* pDevice) +{ + ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice); + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); + int resultALSA; + /* + For playback at this point we should have nothing in the internal buffers. We want to output a full + buffer of silence to the playback device, and since the buffer should be empty, this should be + effectively non-blocking. + */ + if (pDeviceStateALSA->pPCMPlayback != NULL) { + ma_device_prime_playback_buffer__alsa(pDevice); + } - /* Grab the internal channel map. For now we're not going to bother trying to change the channel map and instead just do it ourselves. */ - { - ma_snd_pcm_chmap_t* pChmap = NULL; - if (pDevice->pContext->alsa.snd_pcm_get_chmap != NULL) { - pChmap = ((ma_snd_pcm_get_chmap_proc)pDevice->pContext->alsa.snd_pcm_get_chmap)(pPCM); + if (pDeviceStateALSA->pPCMCapture != NULL) { + resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMCapture); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno(-resultALSA); } + } - if (pChmap != NULL) { - ma_uint32 iChannel; - - /* There are cases where the returned channel map can have a different channel count than was returned by snd_pcm_hw_params_set_channels_near(). */ - if (pChmap->channels >= internalChannels) { - /* Drop excess channels. */ - for (iChannel = 0; iChannel < internalChannels; ++iChannel) { - internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); - } - } else { - ma_uint32 i; + if (ma_device_get_type(pDevice) != ma_device_type_duplex || !pDeviceStateALSA->isLinked) { /* <-- In duplex mode the PCMs are linked which means the playback side will be started when the capture side starts. */ + if (pDeviceStateALSA->pPCMPlayback != NULL) { + resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMPlayback); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno(-resultALSA); + } + } + } - /* - Excess channels use defaults. Do an initial fill with defaults, overwrite the first pChmap->channels, validate to ensure there are no duplicate - channels. If validation fails, fall back to defaults. - */ - ma_bool32 isValid = MA_TRUE; + return MA_SUCCESS; +} - /* Fill with defaults. */ - ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); +static ma_result ma_device_stop__alsa(ma_device* pDevice) +{ + ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice); + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); + int resultPoll; + int resultRead; - /* Overwrite first pChmap->channels channels. */ - for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { - internalChannelMap[iChannel] = ma_convert_alsa_channel_position_to_ma_channel(pChmap->pos[iChannel]); - } + if (pDeviceStateALSA->pPCMCapture != NULL) { + /* + In the playback case we drain, but it's a bit different for capture. It is still true that the tail can + be trimmed, but I think it's less of an issue because the person doing the recording has more control + over when they actually stop recording. But a more practical concern I have is that when this function + is called, the device will be in a stopping status, and I don't like the idea of the miniaudio data + callback being fired when in a non-started state. + + Maybe draining can be done at a higher level in the cross-platform section with a crude sleep equal to + the length of the internal buffer? + */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device..."); + pContextStateALSA->snd_pcm_drop(pDeviceStateALSA->pPCMCapture); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device successful."); - /* Validate. */ - for (i = 0; i < internalChannels && isValid; ++i) { - ma_uint32 j; - for (j = i+1; j < internalChannels; ++j) { - if (internalChannelMap[i] == internalChannelMap[j]) { - isValid = MA_FALSE; - break; - } - } - } + /* We need to prepare the device again, otherwise we won't be able to restart the device. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device..."); + if (pContextStateALSA->snd_pcm_prepare(pDeviceStateALSA->pPCMCapture) < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device failed."); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful."); + } + } - /* If our channel map is invalid, fall back to defaults. */ - if (!isValid) { - ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); - } + if (ma_device_get_type(pDevice) != ma_device_type_duplex || !pDeviceStateALSA->isLinked) { /* <-- In duplex mode the PCMs are linked which means the playback side will be stopped when the capture side starts. */ + if (pDeviceStateALSA->pPCMPlayback != NULL) { + /* + When stopping we prefer to drain because if someone is playing a sound and then decides to stop the + device after the last part of the sound has been sent to the device, draining will ensure the tail + is actually played through the speakers. If we drop instead of drain, the tail will get trimmed. + + However, there's a problem. `snd_pcm_drain()` is a blocking call, and I have not one single bit of + confidence that ALSA won't have a bug that results in `snd_pcm_drain()` get stuck in an infinite + hang. I'm going to bet $1 I'll have a bug report about the device hanging when stopping the device, + and it'll be right here in `snd_pcm_drain()`. If this happens we'll just do some kind of hack with + `snd_pcm_avail()` and a sleeping loop. + */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Draining playback device..."); + pContextStateALSA->snd_pcm_drain(pDeviceStateALSA->pPCMPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Draining playback device successful."); + + #if 0 + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device..."); + pContextStateALSA->snd_pcm_drop(pDeviceStateALSA->pPCMPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device successful."); + #endif + + /* We need to prepare the device again, otherwise we won't be able to restart the device. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device..."); + if (pContextStateALSA->snd_pcm_prepare(pDeviceStateALSA->pPCMPlayback) < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device failed."); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device successful."); } + } + } - free(pChmap); - pChmap = NULL; - } else { - /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ - ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); + /* Clear the wakeupfd. */ + resultPoll = poll(pDeviceStateALSA->pPollDescriptors, 1, 0); + if (resultPoll > 0) { + ma_uint64 t; + resultRead = read(pDeviceStateALSA->pPollDescriptors[0].fd, &t, sizeof(t)); + if (resultRead != sizeof(t)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from wakeupfd. read() = %d", resultRead); } } + return MA_SUCCESS; +} - /* - We need to retrieve the poll descriptors so we can use poll() to wait for data to become - available for reading or writing. There's no well defined maximum for this so we're just going - to allocate this on the heap. - */ - pollDescriptorCount = ((ma_snd_pcm_poll_descriptors_count_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_count)(pPCM); - if (pollDescriptorCount <= 0) { - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); - return MA_ERROR; - } +static ma_result ma_device_step__alsa(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice); + ma_context_state_alsa* pContextStateALSA = ma_context_get_backend_state__alsa(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + int timeout = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? 2000 : 0; + int resultALSA; + int resultPoll; + unsigned short revents; - pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), &pDevice->pContext->allocationCallbacks); /* +1 because we want room for the wakeup descriptor. */ - if (pPollDescriptors == NULL) { - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors."); - return MA_OUT_OF_MEMORY; + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; } /* - We need an eventfd to wakeup from poll() and avoid a deadlock in situations where the driver - never returns from writei() and readi(). This has been observed with the "pulse" device. + There have been reports that poll() is returning an error randomly and that instead of + returning an error, simply trying again will work. I'm experimenting with adopting this + advice. */ - wakeupfd = eventfd(0, 0); - if (wakeupfd < 0) { - ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup."); - return ma_result_from_errno(errno); - } + do { + resultPoll = poll(pDeviceStateALSA->pPollDescriptors, pDeviceStateALSA->pollDescriptorCount, timeout); + } while (resultPoll < 0 && errno == EINTR); - /* We'll place the wakeup fd at the start of the buffer. */ - pPollDescriptors[0].fd = wakeupfd; - pPollDescriptors[0].events = POLLIN; /* We only care about waiting to read from the wakeup file descriptor. */ - pPollDescriptors[0].revents = 0; - - /* We can now extract the PCM poll descriptors which we place after the wakeup descriptor. */ - pollDescriptorCount = ((ma_snd_pcm_poll_descriptors_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors)(pPCM, pPollDescriptors + 1, pollDescriptorCount); /* +1 because we want to place these descriptors after the wakeup descriptor. */ - if (pollDescriptorCount <= 0) { - close(wakeupfd); - ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors."); + if (resultPoll < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed."); return MA_ERROR; } - if (deviceType == ma_device_type_capture) { - pDevice->alsa.pollDescriptorCountCapture = pollDescriptorCount; - pDevice->alsa.pPollDescriptorsCapture = pPollDescriptors; - pDevice->alsa.wakeupfdCapture = wakeupfd; - } else { - pDevice->alsa.pollDescriptorCountPlayback = pollDescriptorCount; - pDevice->alsa.pPollDescriptorsPlayback = pPollDescriptors; - pDevice->alsa.wakeupfdPlayback = wakeupfd; - } - /* We're done. Prepare the device. */ - resultALSA = ((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)(pPCM); - if (resultALSA < 0) { - close(wakeupfd); - ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device."); - return ma_result_from_errno(-resultALSA); + /* + In the case of a timeout, this is expected for non-blocking mode and should not be + considered an error. In blocking mode however, we should never be getting a timeout. In + this case it probably means the PCM is stuck. We'll treat this as an error. + */ + if (resultPoll == 0) { /* Timeout. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + return MA_SUCCESS; + } else { + return MA_ERROR; + } } + /* Check if the wakeup fd was triggered before checking anything else. */ + if ((pDeviceStateALSA->pPollDescriptors[0].revents & POLLIN) != 0) { + ma_uint64 t; + int resultRead = read(pDeviceStateALSA->pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + if (resultRead < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed."); + return ma_result_from_errno(errno); + } - if (deviceType == ma_device_type_capture) { - pDevice->alsa.pPCMCapture = (ma_ptr)pPCM; - pDevice->alsa.isUsingMMapCapture = isUsingMMap; - } else { - pDevice->alsa.pPCMPlayback = (ma_ptr)pPCM; - pDevice->alsa.isUsingMMapPlayback = isUsingMMap; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] POLLIN set for wakeupfd."); + return MA_SUCCESS; } - pDescriptor->format = internalFormat; - pDescriptor->channels = internalChannels; - pDescriptor->sampleRate = internalSampleRate; - ma_channel_map_copy(pDescriptor->channelMap, internalChannelMap, ma_min(internalChannels, MA_MAX_CHANNELS)); - pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; - pDescriptor->periodCount = internalPeriods; - - return MA_SUCCESS; -} + /* First check the capture side. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors_revents(pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pPollDescriptors + 1, pDeviceStateALSA->pollDescriptorCountCapture, &revents); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] snd_pcm_poll_descriptors_revents() failed for capture. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return MA_ERROR; + } -static ma_result ma_device_init__alsa(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ - MA_ASSERT(pDevice != NULL); + if ((revents & POLLERR) != 0) { + ma_snd_pcm_state_t state = pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMCapture); + if (state == MA_SND_PCM_STATE_XRUN) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Capture xrun detected."); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d", pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMCapture)); + } + } - MA_ZERO_OBJECT(&pDevice->alsa); + if ((revents & POLLIN) != 0) { + resultALSA = pContextStateALSA->snd_pcm_readi(pDeviceStateALSA->pPCMCapture, pDeviceStateALSA->pIntermediaryBuffer, pDevice->capture.internalPeriodSizeInFrames); + if (resultALSA >= 0) { + /* Success. Process the data. */ + ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateALSA->pIntermediaryBuffer, (ma_uint32)resultALSA); + } else { + /* Failed. No data processing will be done this iteration. What we do here depends on the type of error. */ + if (resultALSA == -EAGAIN) { + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)");*/ + + /* Do nothing. */ + } else if (resultALSA == -EPIPE) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)"); + + /* Overrun. Recover. If this fails we need to return an error. */ + resultALSA = pContextStateALSA->snd_pcm_recover(pDeviceStateALSA->pPCMCapture, resultALSA, MA_TRUE); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover capture device after overrun. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno((int)-resultALSA); + } - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; + resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMCapture); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device after overrun. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno((int)-resultALSA); + } + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Unexpected error when reading from capture device. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno((int)-resultALSA); + } + } + } } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_by_type__alsa(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); - if (result != MA_SUCCESS) { - return result; + /* Now check the playback side. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + resultALSA = pContextStateALSA->snd_pcm_poll_descriptors_revents(pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pPollDescriptors + 1 + pDeviceStateALSA->pollDescriptorCountCapture, pDeviceStateALSA->pollDescriptorCountPlayback, &revents); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] snd_pcm_poll_descriptors_revents() failed for playback. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return MA_ERROR; } - } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_by_type__alsa(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); - if (result != MA_SUCCESS) { - return result; + if ((revents & POLLERR) != 0) { + ma_snd_pcm_state_t state = pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMPlayback); + if (state == MA_SND_PCM_STATE_XRUN) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Playback xrun detected"); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d", pContextStateALSA->snd_pcm_state(pDeviceStateALSA->pPCMPlayback)); + } + } + + if ((revents & POLLOUT) != 0) { + /* Read data from the client first. */ + ma_device_handle_backend_data_callback(pDevice, pDeviceStateALSA->pIntermediaryBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); + + /* Now send the data to ALSA for playback. */ + resultALSA = pContextStateALSA->snd_pcm_writei(pDeviceStateALSA->pPCMPlayback, pDeviceStateALSA->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames); + if (resultALSA >= 0) { + /* Success. */ + } else { + /* Failed. No data processing will be done this iteration. What we do here depends on the type of error. */ + if (resultALSA == -EAGAIN) { + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (write)");*/ + + /* Do nothing. */ + } else if (resultALSA == -EPIPE) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)"); + + /* Underrun. Recover. If this fails we need to return an error. */ + resultALSA = pContextStateALSA->snd_pcm_recover(pDeviceStateALSA->pPCMPlayback, resultALSA, MA_TRUE); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover playback device after underrun. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno((int)-resultALSA); + } + + /* + Before restarting the PCM we need to make sure there's actually some data in the buffer. We're going to + fill it with silence which is consistent with what we do when we start the PCM normally. + */ + ma_device_prime_playback_buffer__alsa(pDevice); + + resultALSA = pContextStateALSA->snd_pcm_start(pDeviceStateALSA->pPCMPlayback); + if (resultALSA < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device after underrun. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno((int)-resultALSA); + } + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Unexpected error when writing to playback device. %s.", pContextStateALSA->snd_strerror(resultALSA)); + return ma_result_from_errno((int)-resultALSA); + } + } } } return MA_SUCCESS; } -static ma_result ma_device_start__alsa(ma_device* pDevice) +static void ma_device_wakeup__alsa(ma_device* pDevice) { - int resultALSA; + ma_device_state_alsa* pDeviceStateALSA = ma_device_get_backend_state__alsa(pDevice); + ma_uint64 t = 1; + int resultWrite = 0; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device."); - return ma_result_from_errno(-resultALSA); - } - } + MA_ASSERT(pDeviceStateALSA != NULL); - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* - When data is written to the device we wait for the device to get ready to receive data with poll(). In my testing - I have observed that poll() can sometimes block forever unless the device is started explicitly with snd_pcm_start() - or some data is written with snd_pcm_writei(). - - To resolve this I've decided to do an explicit start with snd_pcm_start(). The problem with this is that the device - is started without any data in the internal buffer which will result in an immediate underrun. If instead we were - to call into snd_pcm_writei() in an attempt to prevent the underrun, we would run the risk of a weird deadlock - issue as documented inside ma_device_write__alsa(). - */ - resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start playback device."); - return ma_result_from_errno(-resultALSA); - } + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up..."); + + resultWrite = write(pDeviceStateALSA->wakeupfd, &t, sizeof(t)); + if (resultWrite < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] write() failed."); + return; } - return MA_SUCCESS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up completed successfully."); } -static ma_result ma_device_stop__alsa(ma_device* pDevice) +static ma_device_backend_vtable ma_gDeviceBackendVTable_ALSA = { - /* - The stop callback will get called on the worker thread after read/write__alsa() has returned. At this point there is - a small chance that our wakeupfd has not been cleared. We'll clear that out now if applicable. - */ - int resultPoll; - int resultRead; + ma_backend_info__alsa, + ma_context_init__alsa, + ma_context_uninit__alsa, + ma_context_enumerate_devices__alsa, + ma_device_init__alsa, + ma_device_uninit__alsa, + ma_device_start__alsa, + ma_device_stop__alsa, + ma_device_step__alsa, + ma_device_wakeup__alsa +}; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...\n"); - ((ma_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device successful.\n"); +ma_device_backend_vtable* ma_device_backend_alsa = &ma_gDeviceBackendVTable_ALSA; +#else +ma_device_backend_vtable* ma_device_backend_alsa = NULL; +#endif /* MA_HAS_ALSA */ - /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device...\n"); - if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) < 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device failed.\n"); - } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful.\n"); - } +MA_API ma_device_backend_vtable* ma_alsa_get_vtable(void) +{ + return ma_device_backend_alsa; +} - /* Clear the wakeupfd. */ - resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - resultRead = read(((struct pollfd*)pDevice->alsa.pPollDescriptorsCapture)[0].fd, &t, sizeof(t)); - if (resultRead != sizeof(t)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from capture wakeupfd. read() = %d\n", resultRead); - } - } - } +MA_API ma_context_config_alsa ma_context_config_alsa_init(void) +{ + ma_context_config_alsa config; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device...\n"); - ((ma_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device successful.\n"); + MA_ZERO_OBJECT(&config); - /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device...\n"); - if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) < 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device failed.\n"); - } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device successful.\n"); - } + return config; +} - /* Clear the wakeupfd. */ - resultPoll = poll((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback, 1, 0); - if (resultPoll > 0) { - ma_uint64 t; - resultRead = read(((struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback)[0].fd, &t, sizeof(t)); - if (resultRead != sizeof(t)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to read from playback wakeupfd. read() = %d\n", resultRead); - } - } - } +MA_API ma_device_config_alsa ma_device_config_alsa_init(void) +{ + ma_device_config_alsa config; - return MA_SUCCESS; + MA_ZERO_OBJECT(&config); + + return config; } -static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, struct pollfd* pPollDescriptors, int pollDescriptorCount, short requiredEvent) + + +/****************************************************************************** + +PipeWire Backend + +******************************************************************************/ +#if defined(MA_HAS_PIPEWIRE) +#if defined(MA_NO_RUNTIME_LINKING) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpedantic" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc99-extensions" + #pragma GCC diagnostic ignored "-Wgnu-statement-expression-from-macro-expansion" + #pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" + #endif +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#define ma_spa_source_event_func_t spa_source_event_func_t + +#define ma_spa_direction spa_direction +#define ma_spa_audio_format spa_audio_format +#define ma_spa_audio_channel spa_audio_channel +#define ma_spa_callbacks spa_callbacks +#define ma_spa_interface spa_interface +#define ma_spa_dict_item spa_dict_item +#define MA_SPA_DICT_ITEM_INIT SPA_DICT_ITEM_INIT +#define ma_spa_dict spa_dict +#define MA_SPA_DICT_INIT SPA_DICT_INIT +#define ma_spa_dict_lookup spa_dict_lookup +#define ma_spa_chunk spa_chunk +#define ma_spa_data spa_data +#define ma_spa_meta spa_meta +#define ma_spa_buffer spa_buffer +#define ma_spa_list spa_list +#define ma_spa_hook spa_hook +#define ma_spa_pod spa_pod +#define ma_spa_pod_id spa_pod_id +#define ma_spa_pod_int spa_pod_int +#define ma_spa_choice_type spa_choice_type +#define ma_spa_pod_choice spa_pod_choice +#define ma_spa_pod_choice_body spa_pod_choice_body +#define ma_spa_pod_array_body spa_pod_array_body +#define ma_spa_pod_array spa_pod_array +#define ma_spa_pod_object_body spa_pod_object_body +#define ma_spa_pod_object spa_pod_object +#define ma_spa_pod_prop spa_pod_prop +#define ma_spa_command spa_command +#define ma_spa_fraction spa_fraction + +#define MA_SPA_DIRECTION_OUTPUT SPA_DIRECTION_OUTPUT +#define MA_SPA_DIRECTION_INPUT SPA_DIRECTION_INPUT + +#define MA_SPA_TYPE_Id SPA_TYPE_Id +#define MA_SPA_TYPE_Int SPA_TYPE_Int +#define MA_SPA_TYPE_Array SPA_TYPE_Array +#define MA_SPA_TYPE_Object SPA_TYPE_Object +#define MA_SPA_TYPE_Choice SPA_TYPE_Choice + +#define MA_SPA_TYPE_OBJECT_Format SPA_TYPE_OBJECT_Format +#define MA_SPA_TYPE_OBJECT_ParamBuffers SPA_TYPE_OBJECT_ParamBuffers + +#define MA_SPA_PARAM_EnumFormat SPA_PARAM_EnumFormat +#define MA_SPA_PARAM_Format SPA_PARAM_Format +#define MA_SPA_PARAM_Buffers SPA_PARAM_Buffers + +#define MA_SPA_PARAM_BUFFERS_buffers SPA_PARAM_BUFFERS_buffers +#define MA_SPA_PARAM_BUFFERS_blocks SPA_PARAM_BUFFERS_blocks +#define MA_SPA_PARAM_BUFFERS_size SPA_PARAM_BUFFERS_size +#define MA_SPA_PARAM_BUFFERS_stride SPA_PARAM_BUFFERS_stride + +#define MA_SPA_FORMAT_mediaType SPA_FORMAT_mediaType +#define MA_SPA_FORMAT_mediaSubtype SPA_FORMAT_mediaSubtype + +#define MA_SPA_MEDIA_TYPE_audio SPA_MEDIA_TYPE_audio +#define MA_SPA_MEDIA_SUBTYPE_raw SPA_MEDIA_SUBTYPE_raw + +#define MA_SPA_FORMAT_AUDIO_format SPA_FORMAT_AUDIO_format +#define MA_SPA_FORMAT_AUDIO_rate SPA_FORMAT_AUDIO_rate +#define MA_SPA_FORMAT_AUDIO_channels SPA_FORMAT_AUDIO_channels +#define MA_SPA_FORMAT_AUDIO_position SPA_FORMAT_AUDIO_position + +#define MA_SPA_AUDIO_FORMAT_UNKNOWN SPA_AUDIO_FORMAT_UNKNOWN +#define MA_SPA_AUDIO_FORMAT_U8 SPA_AUDIO_FORMAT_U8 +#define MA_SPA_AUDIO_FORMAT_S16_LE SPA_AUDIO_FORMAT_S16_LE +#define MA_SPA_AUDIO_FORMAT_S16_BE SPA_AUDIO_FORMAT_S16_BE +#define MA_SPA_AUDIO_FORMAT_S24_LE SPA_AUDIO_FORMAT_S24_LE +#define MA_SPA_AUDIO_FORMAT_S24_BE SPA_AUDIO_FORMAT_S24_BE +#define MA_SPA_AUDIO_FORMAT_S32_LE SPA_AUDIO_FORMAT_S32_LE +#define MA_SPA_AUDIO_FORMAT_S32_BE SPA_AUDIO_FORMAT_S32_BE +#define MA_SPA_AUDIO_FORMAT_F32_LE SPA_AUDIO_FORMAT_F32_LE +#define MA_SPA_AUDIO_FORMAT_F32_BE SPA_AUDIO_FORMAT_F32_BE + +#define MA_SPA_AUDIO_CHANNEL_MONO SPA_AUDIO_CHANNEL_MONO +#define MA_SPA_AUDIO_CHANNEL_FL SPA_AUDIO_CHANNEL_FL +#define MA_SPA_AUDIO_CHANNEL_FR SPA_AUDIO_CHANNEL_FR +#define MA_SPA_AUDIO_CHANNEL_FC SPA_AUDIO_CHANNEL_FC +#define MA_SPA_AUDIO_CHANNEL_LFE SPA_AUDIO_CHANNEL_LFE +#define MA_SPA_AUDIO_CHANNEL_SL SPA_AUDIO_CHANNEL_SL +#define MA_SPA_AUDIO_CHANNEL_SR SPA_AUDIO_CHANNEL_SR +#define MA_SPA_AUDIO_CHANNEL_FLC SPA_AUDIO_CHANNEL_FLC +#define MA_SPA_AUDIO_CHANNEL_FRC SPA_AUDIO_CHANNEL_FRC +#define MA_SPA_AUDIO_CHANNEL_RC SPA_AUDIO_CHANNEL_RC +#define MA_SPA_AUDIO_CHANNEL_RL SPA_AUDIO_CHANNEL_RL +#define MA_SPA_AUDIO_CHANNEL_RR SPA_AUDIO_CHANNEL_RR +#define MA_SPA_AUDIO_CHANNEL_TC SPA_AUDIO_CHANNEL_TC +#define MA_SPA_AUDIO_CHANNEL_TFL SPA_AUDIO_CHANNEL_TFL +#define MA_SPA_AUDIO_CHANNEL_TFC SPA_AUDIO_CHANNEL_TFC +#define MA_SPA_AUDIO_CHANNEL_TFR SPA_AUDIO_CHANNEL_TFR +#define MA_SPA_AUDIO_CHANNEL_TRL SPA_AUDIO_CHANNEL_TRL +#define MA_SPA_AUDIO_CHANNEL_TRC SPA_AUDIO_CHANNEL_TRC +#define MA_SPA_AUDIO_CHANNEL_TRR SPA_AUDIO_CHANNEL_TRR +#define MA_SPA_AUDIO_CHANNEL_RLC SPA_AUDIO_CHANNEL_RLC +#define MA_SPA_AUDIO_CHANNEL_RRC SPA_AUDIO_CHANNEL_RRC +#define MA_SPA_AUDIO_CHANNEL_FLW SPA_AUDIO_CHANNEL_FLW +#define MA_SPA_AUDIO_CHANNEL_FRW SPA_AUDIO_CHANNEL_FRW +#define MA_SPA_AUDIO_CHANNEL_LFE2 SPA_AUDIO_CHANNEL_LFE2 +#define MA_SPA_AUDIO_CHANNEL_FLH SPA_AUDIO_CHANNEL_FLH +#define MA_SPA_AUDIO_CHANNEL_FCH SPA_AUDIO_CHANNEL_FCH +#define MA_SPA_AUDIO_CHANNEL_FRH SPA_AUDIO_CHANNEL_FRH +#define MA_SPA_AUDIO_CHANNEL_TFLC SPA_AUDIO_CHANNEL_TFLC +#define MA_SPA_AUDIO_CHANNEL_TFRC SPA_AUDIO_CHANNEL_TFRC +#define MA_SPA_AUDIO_CHANNEL_TSL SPA_AUDIO_CHANNEL_TSL +#define MA_SPA_AUDIO_CHANNEL_TSR SPA_AUDIO_CHANNEL_TSR +#define MA_SPA_AUDIO_CHANNEL_LLFE SPA_AUDIO_CHANNEL_LLFE +#define MA_SPA_AUDIO_CHANNEL_RLFE SPA_AUDIO_CHANNEL_RLFE +#define MA_SPA_AUDIO_CHANNEL_BC SPA_AUDIO_CHANNEL_BC +#define MA_SPA_AUDIO_CHANNEL_BLC SPA_AUDIO_CHANNEL_BLC +#define MA_SPA_AUDIO_CHANNEL_BRC SPA_AUDIO_CHANNEL_BRC +#define MA_SPA_AUDIO_CHANNEL_AUX0 SPA_AUDIO_CHANNEL_AUX0 +#define MA_SPA_AUDIO_CHANNEL_AUX1 SPA_AUDIO_CHANNEL_AUX1 +#define MA_SPA_AUDIO_CHANNEL_AUX2 SPA_AUDIO_CHANNEL_AUX2 +#define MA_SPA_AUDIO_CHANNEL_AUX3 SPA_AUDIO_CHANNEL_AUX3 +#define MA_SPA_AUDIO_CHANNEL_AUX4 SPA_AUDIO_CHANNEL_AUX4 +#define MA_SPA_AUDIO_CHANNEL_AUX5 SPA_AUDIO_CHANNEL_AUX5 +#define MA_SPA_AUDIO_CHANNEL_AUX6 SPA_AUDIO_CHANNEL_AUX6 +#define MA_SPA_AUDIO_CHANNEL_AUX7 SPA_AUDIO_CHANNEL_AUX7 +#define MA_SPA_AUDIO_CHANNEL_AUX8 SPA_AUDIO_CHANNEL_AUX8 +#define MA_SPA_AUDIO_CHANNEL_AUX9 SPA_AUDIO_CHANNEL_AUX9 +#define MA_SPA_AUDIO_CHANNEL_AUX10 SPA_AUDIO_CHANNEL_AUX10 +#define MA_SPA_AUDIO_CHANNEL_AUX11 SPA_AUDIO_CHANNEL_AUX11 +#define MA_SPA_AUDIO_CHANNEL_AUX12 SPA_AUDIO_CHANNEL_AUX12 +#define MA_SPA_AUDIO_CHANNEL_AUX13 SPA_AUDIO_CHANNEL_AUX13 +#define MA_SPA_AUDIO_CHANNEL_AUX14 SPA_AUDIO_CHANNEL_AUX14 +#define MA_SPA_AUDIO_CHANNEL_AUX15 SPA_AUDIO_CHANNEL_AUX15 +#define MA_SPA_AUDIO_CHANNEL_AUX16 SPA_AUDIO_CHANNEL_AUX16 +#define MA_SPA_AUDIO_CHANNEL_AUX17 SPA_AUDIO_CHANNEL_AUX17 +#define MA_SPA_AUDIO_CHANNEL_AUX18 SPA_AUDIO_CHANNEL_AUX18 +#define MA_SPA_AUDIO_CHANNEL_AUX19 SPA_AUDIO_CHANNEL_AUX19 +#define MA_SPA_AUDIO_CHANNEL_AUX20 SPA_AUDIO_CHANNEL_AUX20 +#define MA_SPA_AUDIO_CHANNEL_AUX21 SPA_AUDIO_CHANNEL_AUX21 +#define MA_SPA_AUDIO_CHANNEL_AUX22 SPA_AUDIO_CHANNEL_AUX22 +#define MA_SPA_AUDIO_CHANNEL_AUX23 SPA_AUDIO_CHANNEL_AUX23 +#define MA_SPA_AUDIO_CHANNEL_AUX24 SPA_AUDIO_CHANNEL_AUX24 +#define MA_SPA_AUDIO_CHANNEL_AUX25 SPA_AUDIO_CHANNEL_AUX25 +#define MA_SPA_AUDIO_CHANNEL_AUX26 SPA_AUDIO_CHANNEL_AUX26 +#define MA_SPA_AUDIO_CHANNEL_AUX27 SPA_AUDIO_CHANNEL_AUX27 +#define MA_SPA_AUDIO_CHANNEL_AUX28 SPA_AUDIO_CHANNEL_AUX28 +#define MA_SPA_AUDIO_CHANNEL_AUX29 SPA_AUDIO_CHANNEL_AUX29 +#define MA_SPA_AUDIO_CHANNEL_AUX30 SPA_AUDIO_CHANNEL_AUX30 +#define MA_SPA_AUDIO_CHANNEL_AUX31 SPA_AUDIO_CHANNEL_AUX31 +#define MA_SPA_AUDIO_CHANNEL_AUX32 SPA_AUDIO_CHANNEL_AUX32 +#define MA_SPA_AUDIO_CHANNEL_AUX33 SPA_AUDIO_CHANNEL_AUX33 +#define MA_SPA_AUDIO_CHANNEL_AUX34 SPA_AUDIO_CHANNEL_AUX34 +#define MA_SPA_AUDIO_CHANNEL_AUX35 SPA_AUDIO_CHANNEL_AUX35 +#define MA_SPA_AUDIO_CHANNEL_AUX36 SPA_AUDIO_CHANNEL_AUX36 +#define MA_SPA_AUDIO_CHANNEL_AUX37 SPA_AUDIO_CHANNEL_AUX37 +#define MA_SPA_AUDIO_CHANNEL_AUX38 SPA_AUDIO_CHANNEL_AUX38 +#define MA_SPA_AUDIO_CHANNEL_AUX39 SPA_AUDIO_CHANNEL_AUX39 +#define MA_SPA_AUDIO_CHANNEL_AUX40 SPA_AUDIO_CHANNEL_AUX40 +#define MA_SPA_AUDIO_CHANNEL_AUX41 SPA_AUDIO_CHANNEL_AUX41 +#define MA_SPA_AUDIO_CHANNEL_AUX42 SPA_AUDIO_CHANNEL_AUX42 +#define MA_SPA_AUDIO_CHANNEL_AUX43 SPA_AUDIO_CHANNEL_AUX43 +#define MA_SPA_AUDIO_CHANNEL_AUX44 SPA_AUDIO_CHANNEL_AUX44 +#define MA_SPA_AUDIO_CHANNEL_AUX45 SPA_AUDIO_CHANNEL_AUX45 +#define MA_SPA_AUDIO_CHANNEL_AUX46 SPA_AUDIO_CHANNEL_AUX46 +#define MA_SPA_AUDIO_CHANNEL_AUX47 SPA_AUDIO_CHANNEL_AUX47 +#define MA_SPA_AUDIO_CHANNEL_AUX48 SPA_AUDIO_CHANNEL_AUX48 +#define MA_SPA_AUDIO_CHANNEL_AUX49 SPA_AUDIO_CHANNEL_AUX49 +#define MA_SPA_AUDIO_CHANNEL_AUX50 SPA_AUDIO_CHANNEL_AUX50 +#define MA_SPA_AUDIO_CHANNEL_AUX51 SPA_AUDIO_CHANNEL_AUX51 +#define MA_SPA_AUDIO_CHANNEL_AUX52 SPA_AUDIO_CHANNEL_AUX52 +#define MA_SPA_AUDIO_CHANNEL_AUX53 SPA_AUDIO_CHANNEL_AUX53 +#define MA_SPA_AUDIO_CHANNEL_AUX54 SPA_AUDIO_CHANNEL_AUX54 +#define MA_SPA_AUDIO_CHANNEL_AUX55 SPA_AUDIO_CHANNEL_AUX55 +#define MA_SPA_AUDIO_CHANNEL_AUX56 SPA_AUDIO_CHANNEL_AUX56 +#define MA_SPA_AUDIO_CHANNEL_AUX57 SPA_AUDIO_CHANNEL_AUX57 +#define MA_SPA_AUDIO_CHANNEL_AUX58 SPA_AUDIO_CHANNEL_AUX58 +#define MA_SPA_AUDIO_CHANNEL_AUX59 SPA_AUDIO_CHANNEL_AUX59 +#define MA_SPA_AUDIO_CHANNEL_AUX60 SPA_AUDIO_CHANNEL_AUX60 +#define MA_SPA_AUDIO_CHANNEL_AUX61 SPA_AUDIO_CHANNEL_AUX61 +#define MA_SPA_AUDIO_CHANNEL_AUX62 SPA_AUDIO_CHANNEL_AUX62 +#define MA_SPA_AUDIO_CHANNEL_AUX63 SPA_AUDIO_CHANNEL_AUX63 + + +#define MA_PW_KEY_MEDIA_TYPE PW_KEY_MEDIA_TYPE +#define MA_PW_KEY_MEDIA_CATEGORY PW_KEY_MEDIA_CATEGORY +#define MA_PW_KEY_MEDIA_ROLE PW_KEY_MEDIA_ROLE +#define MA_PW_KEY_MEDIA_CLASS PW_KEY_MEDIA_CLASS +#define MA_PW_KEY_NODE_LATENCY PW_KEY_NODE_LATENCY +#define MA_PW_KEY_TARGET_OBJECT PW_KEY_TARGET_OBJECT +#define MA_PW_KEY_METADATA_NAME PW_KEY_METADATA_NAME + +#define MA_PW_TYPE_INTERFACE_Node PW_TYPE_INTERFACE_Node +#define MA_PW_TYPE_INTERFACE_Metadata PW_TYPE_INTERFACE_Metadata + +#define MA_PW_ID_CORE PW_ID_CORE +#define MA_PW_ID_ANY PW_ID_ANY + +#define ma_pw_thread_loop pw_thread_loop +#define ma_pw_loop pw_loop +#define ma_pw_context pw_context +#define ma_pw_core pw_core +#define ma_pw_registry pw_registry +#define ma_pw_metadata pw_metadata +#define ma_pw_metadata_events pw_metadata_events +#define ma_pw_proxy pw_proxy +#define ma_pw_properties pw_properties +#define ma_pw_stream pw_stream +#define ma_pw_stream_control pw_stream_control +#define ma_pw_core_info pw_core_info + +#define ma_pw_stream_state pw_stream_state +#define MA_PW_STREAM_STATE_ERROR PW_STREAM_STATE_ERROR +#define MA_PW_STREAM_STATE_UNCONNECTED PW_STREAM_STATE_UNCONNECTED +#define MA_PW_STREAM_STATE_CONNECTING PW_STREAM_STATE_CONNECTING +#define MA_PW_STREAM_STATE_PAUSED PW_STREAM_STATE_PAUSED +#define MA_PW_STREAM_STATE_STREAMING PW_STREAM_STATE_STREAMING + +#define ma_pw_stream_flags pw_stream_flags +#define MA_PW_STREAM_FLAG_NONE PW_STREAM_FLAG_NONE +#define MA_PW_STREAM_FLAG_AUTOCONNECT PW_STREAM_FLAG_AUTOCONNECT +#define MA_PW_STREAM_FLAG_INACTIVE PW_STREAM_FLAG_INACTIVE +#define MA_PW_STREAM_FLAG_MAP_BUFFERS PW_STREAM_FLAG_MAP_BUFFERS +#define MA_PW_STREAM_FLAG_DRIVER PW_STREAM_FLAG_DRIVER +#define MA_PW_STREAM_FLAG_RT_PROCESS PW_STREAM_FLAG_RT_PROCESS +#define MA_PW_STREAM_FLAG_NO_CONVERT PW_STREAM_FLAG_NO_CONVERT +#define MA_PW_STREAM_FLAG_EXCLUSIVE PW_STREAM_FLAG_EXCLUSIVE +#define MA_PW_STREAM_FLAG_DONT_RECONNECT PW_STREAM_FLAG_DONT_RECONNECT +#define MA_PW_STREAM_FLAG_ALLOC_BUFFERS PW_STREAM_FLAG_ALLOC_BUFFERS +#define MA_PW_STREAM_FLAG_TRIGGER PW_STREAM_FLAG_TRIGGER +#define MA_PW_STREAM_FLAG_ASYNC PW_STREAM_FLAG_ASYNC + +#define ma_pw_buffer pw_buffer +#define ma_pw_time pw_time + +#define MA_PW_VERSION_CORE_EVENTS PW_VERSION_CORE_EVENTS +#define ma_pw_core_events pw_core_events + +#define MA_PW_VERSION_REGISTRY PW_VERSION_REGISTRY +#define MA_PW_VERSION_REGISTRY_EVENTS PW_VERSION_REGISTRY_EVENTS +#define ma_pw_registry_events pw_registry_events + +#define MA_PW_VERSION_METADATA_METHODS PW_VERSION_METADATA_METHODS +#define ma_pw_metadata_methods pw_metadata_methods + +#define MA_PW_VERSION_METADATA PW_VERSION_METADATA +#define MA_PW_VERSION_METADATA_EVENTS PW_VERSION_METADATA_EVENTS +#define ma_pw_metadata_events pw_metadata_events + +#define MA_PW_VERSION_STREAM_EVENTS PW_VERSION_STREAM_EVENTS +#define ma_pw_stream_events pw_stream_events +#else +typedef void (* ma_spa_source_event_func_t)(void* data, ma_uint64 count); + +/* SPA enums. */ +enum ma_spa_direction { - for (;;) { - unsigned short revents; - int resultALSA; - int resultPoll = poll(pPollDescriptors, pollDescriptorCount, -1); - if (resultPoll < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] poll() failed.\n"); + MA_SPA_DIRECTION_INPUT = 0, + MA_SPA_DIRECTION_OUTPUT = 1 +}; - /* - There have been reports that poll() is returning an error randomly and that instead of - returning an error, simply trying again will work. I'm experimenting with adopting this - advice. - */ - continue; - /*return ma_result_from_errno(errno);*/ - } +enum ma_spa_audio_format +{ + MA_SPA_AUDIO_FORMAT_UNKNOWN = 0, + MA_SPA_AUDIO_FORMAT_ENCODED, + + MA_SPA_AUDIO_FORMAT_START_Interleaved = 0x100, + MA_SPA_AUDIO_FORMAT_S8, + MA_SPA_AUDIO_FORMAT_U8, + MA_SPA_AUDIO_FORMAT_S16_LE, + MA_SPA_AUDIO_FORMAT_S16_BE, + MA_SPA_AUDIO_FORMAT_U16_LE, + MA_SPA_AUDIO_FORMAT_U16_BE, + MA_SPA_AUDIO_FORMAT_S24_32_LE, + MA_SPA_AUDIO_FORMAT_S24_32_BE, + MA_SPA_AUDIO_FORMAT_U24_32_LE, + MA_SPA_AUDIO_FORMAT_U24_32_BE, + MA_SPA_AUDIO_FORMAT_S32_LE, + MA_SPA_AUDIO_FORMAT_S32_BE, + MA_SPA_AUDIO_FORMAT_U32_LE, + MA_SPA_AUDIO_FORMAT_U32_BE, + MA_SPA_AUDIO_FORMAT_S24_LE, + MA_SPA_AUDIO_FORMAT_S24_BE, + MA_SPA_AUDIO_FORMAT_U24_LE, + MA_SPA_AUDIO_FORMAT_U24_BE, + MA_SPA_AUDIO_FORMAT_S20_LE, + MA_SPA_AUDIO_FORMAT_S20_BE, + MA_SPA_AUDIO_FORMAT_U20_LE, + MA_SPA_AUDIO_FORMAT_U20_BE, + MA_SPA_AUDIO_FORMAT_S18_LE, + MA_SPA_AUDIO_FORMAT_S18_BE, + MA_SPA_AUDIO_FORMAT_U18_LE, + MA_SPA_AUDIO_FORMAT_U18_BE, + MA_SPA_AUDIO_FORMAT_F32_LE, + MA_SPA_AUDIO_FORMAT_F32_BE, + MA_SPA_AUDIO_FORMAT_F64_LE, + MA_SPA_AUDIO_FORMAT_F64_BE, + MA_SPA_AUDIO_FORMAT_ULAW, + MA_SPA_AUDIO_FORMAT_ALAW +}; - /* - Before checking the ALSA poll descriptor flag we need to check if the wakeup descriptor - has had it's POLLIN flag set. If so, we need to actually read the data and then exit the - function. The wakeup descriptor will be the first item in the descriptors buffer. - */ - if ((pPollDescriptors[0].revents & POLLIN) != 0) { - ma_uint64 t; - int resultRead = read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ - if (resultRead < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed.\n"); - return ma_result_from_errno(errno); - } +enum ma_spa_audio_channel +{ + MA_SPA_AUDIO_CHANNEL_UNKNOWN, + MA_SPA_AUDIO_CHANNEL_NA, + + MA_SPA_AUDIO_CHANNEL_MONO, + MA_SPA_AUDIO_CHANNEL_FL, + MA_SPA_AUDIO_CHANNEL_FR, + MA_SPA_AUDIO_CHANNEL_FC, + MA_SPA_AUDIO_CHANNEL_LFE, + MA_SPA_AUDIO_CHANNEL_SL, + MA_SPA_AUDIO_CHANNEL_SR, + MA_SPA_AUDIO_CHANNEL_FLC, + MA_SPA_AUDIO_CHANNEL_FRC, + MA_SPA_AUDIO_CHANNEL_RC, + MA_SPA_AUDIO_CHANNEL_RL, + MA_SPA_AUDIO_CHANNEL_RR, + MA_SPA_AUDIO_CHANNEL_TC, + MA_SPA_AUDIO_CHANNEL_TFL, + MA_SPA_AUDIO_CHANNEL_TFC, + MA_SPA_AUDIO_CHANNEL_TFR, + MA_SPA_AUDIO_CHANNEL_TRL, + MA_SPA_AUDIO_CHANNEL_TRC, + MA_SPA_AUDIO_CHANNEL_TRR, + MA_SPA_AUDIO_CHANNEL_RLC, + MA_SPA_AUDIO_CHANNEL_RRC, + MA_SPA_AUDIO_CHANNEL_FLW, + MA_SPA_AUDIO_CHANNEL_FRW, + MA_SPA_AUDIO_CHANNEL_LFE2, + MA_SPA_AUDIO_CHANNEL_FLH, + MA_SPA_AUDIO_CHANNEL_FCH, + MA_SPA_AUDIO_CHANNEL_FRH, + MA_SPA_AUDIO_CHANNEL_TFLC, + MA_SPA_AUDIO_CHANNEL_TFRC, + MA_SPA_AUDIO_CHANNEL_TSL, + MA_SPA_AUDIO_CHANNEL_TSR, + MA_SPA_AUDIO_CHANNEL_LLFE, + MA_SPA_AUDIO_CHANNEL_RLFE, + MA_SPA_AUDIO_CHANNEL_BC, + MA_SPA_AUDIO_CHANNEL_BLC, + MA_SPA_AUDIO_CHANNEL_BRC, + + MA_SPA_AUDIO_CHANNEL_START_Aux = 0x1000, + MA_SPA_AUDIO_CHANNEL_AUX0 = MA_SPA_AUDIO_CHANNEL_START_Aux, + MA_SPA_AUDIO_CHANNEL_AUX1, + MA_SPA_AUDIO_CHANNEL_AUX2, + MA_SPA_AUDIO_CHANNEL_AUX3, + MA_SPA_AUDIO_CHANNEL_AUX4, + MA_SPA_AUDIO_CHANNEL_AUX5, + MA_SPA_AUDIO_CHANNEL_AUX6, + MA_SPA_AUDIO_CHANNEL_AUX7, + MA_SPA_AUDIO_CHANNEL_AUX8, + MA_SPA_AUDIO_CHANNEL_AUX9, + MA_SPA_AUDIO_CHANNEL_AUX10, + MA_SPA_AUDIO_CHANNEL_AUX11, + MA_SPA_AUDIO_CHANNEL_AUX12, + MA_SPA_AUDIO_CHANNEL_AUX13, + MA_SPA_AUDIO_CHANNEL_AUX14, + MA_SPA_AUDIO_CHANNEL_AUX15, + MA_SPA_AUDIO_CHANNEL_AUX16, + MA_SPA_AUDIO_CHANNEL_AUX17, + MA_SPA_AUDIO_CHANNEL_AUX18, + MA_SPA_AUDIO_CHANNEL_AUX19, + MA_SPA_AUDIO_CHANNEL_AUX20, + MA_SPA_AUDIO_CHANNEL_AUX21, + MA_SPA_AUDIO_CHANNEL_AUX22, + MA_SPA_AUDIO_CHANNEL_AUX23, + MA_SPA_AUDIO_CHANNEL_AUX24, + MA_SPA_AUDIO_CHANNEL_AUX25, + MA_SPA_AUDIO_CHANNEL_AUX26, + MA_SPA_AUDIO_CHANNEL_AUX27, + MA_SPA_AUDIO_CHANNEL_AUX28, + MA_SPA_AUDIO_CHANNEL_AUX29, + MA_SPA_AUDIO_CHANNEL_AUX30, + MA_SPA_AUDIO_CHANNEL_AUX31, + MA_SPA_AUDIO_CHANNEL_AUX32, + MA_SPA_AUDIO_CHANNEL_AUX33, + MA_SPA_AUDIO_CHANNEL_AUX34, + MA_SPA_AUDIO_CHANNEL_AUX35, + MA_SPA_AUDIO_CHANNEL_AUX36, + MA_SPA_AUDIO_CHANNEL_AUX37, + MA_SPA_AUDIO_CHANNEL_AUX38, + MA_SPA_AUDIO_CHANNEL_AUX39, + MA_SPA_AUDIO_CHANNEL_AUX40, + MA_SPA_AUDIO_CHANNEL_AUX41, + MA_SPA_AUDIO_CHANNEL_AUX42, + MA_SPA_AUDIO_CHANNEL_AUX43, + MA_SPA_AUDIO_CHANNEL_AUX44, + MA_SPA_AUDIO_CHANNEL_AUX45, + MA_SPA_AUDIO_CHANNEL_AUX46, + MA_SPA_AUDIO_CHANNEL_AUX47, + MA_SPA_AUDIO_CHANNEL_AUX48, + MA_SPA_AUDIO_CHANNEL_AUX49, + MA_SPA_AUDIO_CHANNEL_AUX50, + MA_SPA_AUDIO_CHANNEL_AUX51, + MA_SPA_AUDIO_CHANNEL_AUX52, + MA_SPA_AUDIO_CHANNEL_AUX53, + MA_SPA_AUDIO_CHANNEL_AUX54, + MA_SPA_AUDIO_CHANNEL_AUX55, + MA_SPA_AUDIO_CHANNEL_AUX56, + MA_SPA_AUDIO_CHANNEL_AUX57, + MA_SPA_AUDIO_CHANNEL_AUX58, + MA_SPA_AUDIO_CHANNEL_AUX59, + MA_SPA_AUDIO_CHANNEL_AUX60, + MA_SPA_AUDIO_CHANNEL_AUX61, + MA_SPA_AUDIO_CHANNEL_AUX62, + MA_SPA_AUDIO_CHANNEL_AUX63 +}; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] POLLIN set for wakeupfd\n"); - return MA_DEVICE_NOT_STARTED; - } - /* - Getting here means that some data should be able to be read. We need to use ALSA to - translate the revents flags for us. - */ - resultALSA = ((ma_snd_pcm_poll_descriptors_revents_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_revents)(pPCM, pPollDescriptors + 1, pollDescriptorCount - 1, &revents); /* +1, -1 to ignore the wakeup descriptor. */ - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed.\n"); - return ma_result_from_errno(-resultALSA); - } - if ((revents & POLLERR) != 0) { - ma_snd_pcm_state_t state = ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM); - if (state == MA_SND_PCM_STATE_XRUN) { - /* The PCM is in a xrun state. This will be recovered from at a higher level. We can disregard this. */ - } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[ALSA] POLLERR detected. status = %d\n", ((ma_snd_pcm_state_proc)pDevice->pContext->alsa.snd_pcm_state)(pPCM)); - } - } +/* The spa_interface thing is only used by one thing here, and only because the function we want is not exported by the SO. */ +struct ma_spa_callbacks +{ + const void* funcs; + void* data; +}; - if ((revents & requiredEvent) == requiredEvent) { - break; /* We're done. Data available for reading or writing. */ - } - } +struct ma_spa_interface +{ + const char* type; + ma_uint32 version; + struct ma_spa_callbacks cb; +}; - return MA_SUCCESS; -} -static ma_result ma_device_wait_read__alsa(ma_device* pDevice) +/* +spa_dict is just a list of key/value pairs as a string. We can easily write our own version of this to remove +the dependency on the SPA headers. + +We'll keep the naming consistent here with the actual SPA library, with just the "ma_" prefix added. +*/ +struct ma_spa_dict_item { - return ma_device_wait__alsa(pDevice, (ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, (struct pollfd*)pDevice->alsa.pPollDescriptorsCapture, pDevice->alsa.pollDescriptorCountCapture + 1, POLLIN); /* +1 to account for the wakeup descriptor. */ -} + const char* key; + const char* value; +}; -static ma_result ma_device_wait_write__alsa(ma_device* pDevice) +static MA_INLINE struct ma_spa_dict_item MA_SPA_DICT_ITEM_INIT(const char* key, const char* value) { - return ma_device_wait__alsa(pDevice, (ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, (struct pollfd*)pDevice->alsa.pPollDescriptorsPlayback, pDevice->alsa.pollDescriptorCountPlayback + 1, POLLOUT); /* +1 to account for the wakeup descriptor. */ + struct ma_spa_dict_item item; + + item.key = key; + item.value = value; + + return item; } -static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead) + +struct ma_spa_dict { - ma_snd_pcm_sframes_t resultALSA = 0; + ma_uint32 flags; + ma_uint32 n_items; + const struct ma_spa_dict_item* items; +}; - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pFramesOut != NULL); +static MA_INLINE struct ma_spa_dict MA_SPA_DICT_INIT(const struct ma_spa_dict_item* items, ma_uint32 n_items) +{ + struct ma_spa_dict dict; - if (pFramesRead != NULL) { - *pFramesRead = 0; - } + dict.flags = 0; + dict.n_items = n_items; + dict.items = items; - while (ma_device_get_state(pDevice) == ma_device_state_started) { - ma_result result; + return dict; +} - /* The first thing to do is wait for data to become available for reading. This will return an error code if the device has been stopped. */ - result = ma_device_wait_read__alsa(pDevice); - if (result != MA_SUCCESS) { - return result; +static MA_INLINE const char* ma_spa_dict_lookup(const struct ma_spa_dict* dict, const char* key) +{ + ma_uint32 i; + + for (i = 0; i < dict->n_items; i += 1) { + if (strcmp(dict->items[i].key, key) == 0) { + return dict->items[i].value; } + } - /* Getting here means we should have data available. */ - resultALSA = ((ma_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, pFramesOut, frameCount); - if (resultALSA >= 0) { - break; /* Success. */ - } else { - if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)\n");*/ - continue; /* Try again. */ - } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)\n"); - - /* Overrun. Recover and try again. If this fails we need to return an error. */ - resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, resultALSA, MA_TRUE); - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun."); - return ma_result_from_errno((int)-resultALSA); - } + return NULL; +} - resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); - return ma_result_from_errno((int)-resultALSA); - } - continue; /* Try reading again. */ - } - } - } +/* +spa_buffer is just a few trivial structs. +*/ +struct ma_spa_chunk +{ + ma_uint32 offset; + ma_uint32 size; + ma_int32 stride; + ma_int32 flags; +}; - if (pFramesRead != NULL) { - *pFramesRead = resultALSA; - } +struct ma_spa_data +{ + ma_uint32 type; + ma_uint32 flags; + ma_int64 fd; + ma_uint32 mapoffset; + ma_uint32 maxsize; + void* data; + struct ma_spa_chunk* chunk; +}; - return MA_SUCCESS; -} +struct ma_spa_meta +{ + ma_uint32 type; + ma_uint32 size; + void* data; +}; -static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +struct ma_spa_buffer { - ma_snd_pcm_sframes_t resultALSA = 0; + ma_uint32 n_metas; + ma_uint32 n_datas; + struct ma_spa_meta* metas; + struct ma_spa_data* datas; +}; - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pFrames != NULL); - if (pFramesWritten != NULL) { - *pFramesWritten = 0; - } +/* spa_hook */ +struct ma_spa_list +{ + struct ma_spa_list* next; + struct ma_spa_list* prev; +}; - while (ma_device_get_state(pDevice) == ma_device_state_started) { - ma_result result; +struct ma_spa_hook +{ + struct ma_spa_list link; + struct ma_spa_callbacks cb; + void (* removed)(struct ma_spa_hook* hook); + void* priv; +}; - /* The first thing to do is wait for space to become available for writing. This will return an error code if the device has been stopped. */ - result = ma_device_wait_write__alsa(pDevice); - if (result != MA_SUCCESS) { - return result; - } - resultALSA = ((ma_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, pFrames, frameCount); - if (resultALSA >= 0) { - break; /* Success. */ - } else { - if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (write)\n");*/ - continue; /* Try again. */ - } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)\n"); - - /* Underrun. Recover and try again. If this fails we need to return an error. */ - resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */ - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun."); - return ma_result_from_errno((int)-resultALSA); - } +/* spa_pod is the most complicated part of SPA that we use. We're only implementing specifically what we need. */ +#define MA_SPA_TYPE_Id 3 +#define MA_SPA_TYPE_Int 4 +#define MA_SPA_TYPE_Array 13 +#define MA_SPA_TYPE_Object 15 +#define MA_SPA_TYPE_Choice 19 - /* - In my testing I have had a situation where writei() does not automatically restart the device even though I've set it - up as such in the software parameters. What will happen is writei() will block indefinitely even though the number of - frames is well beyond the auto-start threshold. To work around this I've needed to add an explicit start here. Not sure - if this is me just being stupid and not recovering the device properly, but this definitely feels like something isn't - quite right here. - */ - resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); - if (resultALSA < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); - return ma_result_from_errno((int)-resultALSA); - } +#define MA_SPA_TYPE_OBJECT_Format 262147 +#define MA_SPA_TYPE_OBJECT_ParamBuffers 262148 - continue; /* Try writing again. */ - } - } - } +#define MA_SPA_PARAM_EnumFormat 3 +#define MA_SPA_PARAM_Format 4 +#define MA_SPA_PARAM_Buffers 5 - if (pFramesWritten != NULL) { - *pFramesWritten = resultALSA; - } +#define MA_SPA_PARAM_BUFFERS_buffers 1 +#define MA_SPA_PARAM_BUFFERS_blocks 2 +#define MA_SPA_PARAM_BUFFERS_size 3 +#define MA_SPA_PARAM_BUFFERS_stride 4 - return MA_SUCCESS; -} +#define MA_SPA_FORMAT_mediaType 1 +#define MA_SPA_FORMAT_mediaSubtype 2 -static ma_result ma_device_data_loop_wakeup__alsa(ma_device* pDevice) +#define MA_SPA_MEDIA_TYPE_audio 1 +#define MA_SPA_MEDIA_SUBTYPE_raw 1 + +#define MA_SPA_FORMAT_AUDIO_format 65537 +#define MA_SPA_FORMAT_AUDIO_rate 65539 +#define MA_SPA_FORMAT_AUDIO_channels 65540 +#define MA_SPA_FORMAT_AUDIO_position 65541 + + +struct ma_spa_pod { - ma_uint64 t = 1; - int resultWrite = 0; + ma_uint32 size; + ma_uint32 type; +}; - MA_ASSERT(pDevice != NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up...\n"); +struct ma_spa_pod_id +{ + struct ma_spa_pod pod; + ma_uint32 value; + ma_int32 _padding; +}; - /* Write to an eventfd to trigger a wakeup from poll() and abort any reading or writing. */ - if (pDevice->alsa.pPollDescriptorsCapture != NULL) { - resultWrite = write(pDevice->alsa.wakeupfdCapture, &t, sizeof(t)); - } - if (pDevice->alsa.pPollDescriptorsPlayback != NULL) { - resultWrite = write(pDevice->alsa.wakeupfdPlayback, &t, sizeof(t)); - } +struct ma_spa_pod_int +{ + struct ma_spa_pod pod; + ma_int32 value; + ma_int32 _padding; +}; - if (resultWrite < 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] write() failed.\n"); - return ma_result_from_errno(errno); - } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up completed successfully.\n"); +enum ma_spa_choice_type +{ + MA_SPA_CHOICE_None = 0, + MA_SPA_CHOICE_Range, + MA_SPA_CHOICE_Step, + MA_SPA_CHOICE_Enum, + MA_SPA_CHOICE_Flags +}; - return MA_SUCCESS; -} +struct ma_spa_pod_choice_body +{ + ma_uint32 type; + ma_uint32 flags; + struct ma_spa_pod child; +}; -static ma_result ma_context_uninit__alsa(ma_context* pContext) +struct ma_spa_pod_choice { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_alsa); + struct ma_spa_pod pod; + struct ma_spa_pod_choice_body body; +}; - /* Clean up memory for memory leak checkers. */ - ((ma_snd_config_update_free_global_proc)pContext->alsa.snd_config_update_free_global)(); -#ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(ma_context_get_log(pContext), pContext->alsa.asoundSO); -#endif +struct ma_spa_pod_array_body +{ + struct ma_spa_pod child; +}; - ma_mutex_uninit(&pContext->alsa.internalDeviceEnumLock); +struct ma_spa_pod_array +{ + struct ma_spa_pod pod; + struct ma_spa_pod_array_body body; +}; - return MA_SUCCESS; -} -static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +struct ma_spa_pod_object_body { - ma_result result; -#ifndef MA_NO_RUNTIME_LINKING - const char* libasoundNames[] = { - "libasound.so.2", - "libasound.so" - }; - size_t i; + ma_uint32 type; + ma_uint32 id; +}; - for (i = 0; i < ma_countof(libasoundNames); ++i) { - pContext->alsa.asoundSO = ma_dlopen(ma_context_get_log(pContext), libasoundNames[i]); - if (pContext->alsa.asoundSO != NULL) { - break; - } - } +struct ma_spa_pod_object +{ + struct ma_spa_pod pod; + struct ma_spa_pod_object_body body; +}; - if (pContext->alsa.asoundSO == NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[ALSA] Failed to open shared object.\n"); - return MA_NO_BACKEND; - } +struct ma_spa_pod_prop +{ + ma_uint32 key; + ma_uint32 flags; + struct ma_spa_pod value; +}; - pContext->alsa.snd_pcm_open = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_open"); - pContext->alsa.snd_pcm_close = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_close"); - pContext->alsa.snd_pcm_hw_params_sizeof = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_sizeof"); - pContext->alsa.snd_pcm_hw_params_any = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_any"); - pContext->alsa.snd_pcm_hw_params_set_format = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format"); - pContext->alsa.snd_pcm_hw_params_set_format_first = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format_first"); - pContext->alsa.snd_pcm_hw_params_get_format_mask = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format_mask"); - pContext->alsa.snd_pcm_hw_params_set_channels = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels"); - pContext->alsa.snd_pcm_hw_params_set_channels_near = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_near"); - pContext->alsa.snd_pcm_hw_params_set_channels_minmax = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_minmax"); - pContext->alsa.snd_pcm_hw_params_set_rate_resample = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_resample"); - pContext->alsa.snd_pcm_hw_params_set_rate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate"); - pContext->alsa.snd_pcm_hw_params_set_rate_near = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_near"); - pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_buffer_size_near"); - pContext->alsa.snd_pcm_hw_params_set_periods_near = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_periods_near"); - pContext->alsa.snd_pcm_hw_params_set_access = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_set_access"); - pContext->alsa.snd_pcm_hw_params_get_format = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format"); - pContext->alsa.snd_pcm_hw_params_get_channels = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels"); - pContext->alsa.snd_pcm_hw_params_get_channels_min = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels_min"); - pContext->alsa.snd_pcm_hw_params_get_channels_max = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels_max"); - pContext->alsa.snd_pcm_hw_params_get_rate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate"); - pContext->alsa.snd_pcm_hw_params_get_rate_min = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate_min"); - pContext->alsa.snd_pcm_hw_params_get_rate_max = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate_max"); - pContext->alsa.snd_pcm_hw_params_get_buffer_size = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_buffer_size"); - pContext->alsa.snd_pcm_hw_params_get_periods = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_periods"); - pContext->alsa.snd_pcm_hw_params_get_access = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_get_access"); - pContext->alsa.snd_pcm_hw_params_test_format = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_test_format"); - pContext->alsa.snd_pcm_hw_params_test_channels = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_test_channels"); - pContext->alsa.snd_pcm_hw_params_test_rate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params_test_rate"); - pContext->alsa.snd_pcm_hw_params = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_hw_params"); - pContext->alsa.snd_pcm_sw_params_sizeof = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_sw_params_sizeof"); - pContext->alsa.snd_pcm_sw_params_current = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_sw_params_current"); - pContext->alsa.snd_pcm_sw_params_get_boundary = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_sw_params_get_boundary"); - pContext->alsa.snd_pcm_sw_params_set_avail_min = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_sw_params_set_avail_min"); - pContext->alsa.snd_pcm_sw_params_set_start_threshold = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_sw_params_set_start_threshold"); - pContext->alsa.snd_pcm_sw_params_set_stop_threshold = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_sw_params_set_stop_threshold"); - pContext->alsa.snd_pcm_sw_params = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_sw_params"); - pContext->alsa.snd_pcm_format_mask_sizeof = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_format_mask_sizeof"); - pContext->alsa.snd_pcm_format_mask_test = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_format_mask_test"); - pContext->alsa.snd_pcm_get_chmap = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_get_chmap"); - pContext->alsa.snd_pcm_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_state"); - pContext->alsa.snd_pcm_prepare = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_prepare"); - pContext->alsa.snd_pcm_start = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_start"); - pContext->alsa.snd_pcm_drop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_drop"); - pContext->alsa.snd_pcm_drain = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_drain"); - pContext->alsa.snd_pcm_reset = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_reset"); - pContext->alsa.snd_device_name_hint = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_device_name_hint"); - pContext->alsa.snd_device_name_get_hint = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_device_name_get_hint"); - pContext->alsa.snd_card_get_index = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_card_get_index"); - pContext->alsa.snd_device_name_free_hint = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_device_name_free_hint"); - pContext->alsa.snd_pcm_mmap_begin = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_mmap_begin"); - pContext->alsa.snd_pcm_mmap_commit = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_mmap_commit"); - pContext->alsa.snd_pcm_recover = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_recover"); - pContext->alsa.snd_pcm_readi = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_readi"); - pContext->alsa.snd_pcm_writei = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_writei"); - pContext->alsa.snd_pcm_avail = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_avail"); - pContext->alsa.snd_pcm_avail_update = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_avail_update"); - pContext->alsa.snd_pcm_wait = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_wait"); - pContext->alsa.snd_pcm_nonblock = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_nonblock"); - pContext->alsa.snd_pcm_info = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_info"); - pContext->alsa.snd_pcm_info_sizeof = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_info_sizeof"); - pContext->alsa.snd_pcm_info_get_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_info_get_name"); - pContext->alsa.snd_pcm_poll_descriptors = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_poll_descriptors"); - pContext->alsa.snd_pcm_poll_descriptors_count = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_poll_descriptors_count"); - pContext->alsa.snd_pcm_poll_descriptors_revents = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_pcm_poll_descriptors_revents"); - pContext->alsa.snd_config_update_free_global = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->alsa.asoundSO, "snd_config_update_free_global"); -#else - /* The system below is just for type safety. */ - ma_snd_pcm_open_proc _snd_pcm_open = snd_pcm_open; - ma_snd_pcm_close_proc _snd_pcm_close = snd_pcm_close; - ma_snd_pcm_hw_params_sizeof_proc _snd_pcm_hw_params_sizeof = snd_pcm_hw_params_sizeof; - ma_snd_pcm_hw_params_any_proc _snd_pcm_hw_params_any = snd_pcm_hw_params_any; - ma_snd_pcm_hw_params_set_format_proc _snd_pcm_hw_params_set_format = snd_pcm_hw_params_set_format; - ma_snd_pcm_hw_params_set_format_first_proc _snd_pcm_hw_params_set_format_first = snd_pcm_hw_params_set_format_first; - ma_snd_pcm_hw_params_get_format_mask_proc _snd_pcm_hw_params_get_format_mask = snd_pcm_hw_params_get_format_mask; - ma_snd_pcm_hw_params_set_channels_proc _snd_pcm_hw_params_set_channels = snd_pcm_hw_params_set_channels; - ma_snd_pcm_hw_params_set_channels_near_proc _snd_pcm_hw_params_set_channels_near = snd_pcm_hw_params_set_channels_near; - ma_snd_pcm_hw_params_set_channels_minmax_proc _snd_pcm_hw_params_set_channels_minmax = snd_pcm_hw_params_set_channels_minmax; - ma_snd_pcm_hw_params_set_rate_resample_proc _snd_pcm_hw_params_set_rate_resample = snd_pcm_hw_params_set_rate_resample; - ma_snd_pcm_hw_params_set_rate_proc _snd_pcm_hw_params_set_rate = snd_pcm_hw_params_set_rate; - ma_snd_pcm_hw_params_set_rate_near_proc _snd_pcm_hw_params_set_rate_near = snd_pcm_hw_params_set_rate_near; - ma_snd_pcm_hw_params_set_rate_minmax_proc _snd_pcm_hw_params_set_rate_minmax = snd_pcm_hw_params_set_rate_minmax; - ma_snd_pcm_hw_params_set_buffer_size_near_proc _snd_pcm_hw_params_set_buffer_size_near = snd_pcm_hw_params_set_buffer_size_near; - ma_snd_pcm_hw_params_set_periods_near_proc _snd_pcm_hw_params_set_periods_near = snd_pcm_hw_params_set_periods_near; - ma_snd_pcm_hw_params_set_access_proc _snd_pcm_hw_params_set_access = snd_pcm_hw_params_set_access; - ma_snd_pcm_hw_params_get_format_proc _snd_pcm_hw_params_get_format = snd_pcm_hw_params_get_format; - ma_snd_pcm_hw_params_get_channels_proc _snd_pcm_hw_params_get_channels = snd_pcm_hw_params_get_channels; - ma_snd_pcm_hw_params_get_channels_min_proc _snd_pcm_hw_params_get_channels_min = snd_pcm_hw_params_get_channels_min; - ma_snd_pcm_hw_params_get_channels_max_proc _snd_pcm_hw_params_get_channels_max = snd_pcm_hw_params_get_channels_max; - ma_snd_pcm_hw_params_get_rate_proc _snd_pcm_hw_params_get_rate = snd_pcm_hw_params_get_rate; - ma_snd_pcm_hw_params_get_rate_min_proc _snd_pcm_hw_params_get_rate_min = snd_pcm_hw_params_get_rate_min; - ma_snd_pcm_hw_params_get_rate_max_proc _snd_pcm_hw_params_get_rate_max = snd_pcm_hw_params_get_rate_max; - ma_snd_pcm_hw_params_get_buffer_size_proc _snd_pcm_hw_params_get_buffer_size = snd_pcm_hw_params_get_buffer_size; - ma_snd_pcm_hw_params_get_periods_proc _snd_pcm_hw_params_get_periods = snd_pcm_hw_params_get_periods; - ma_snd_pcm_hw_params_get_access_proc _snd_pcm_hw_params_get_access = snd_pcm_hw_params_get_access; - ma_snd_pcm_hw_params_test_format_proc _snd_pcm_hw_params_test_format = snd_pcm_hw_params_test_format; - ma_snd_pcm_hw_params_test_channels_proc _snd_pcm_hw_params_test_channels = snd_pcm_hw_params_test_channels; - ma_snd_pcm_hw_params_test_rate_proc _snd_pcm_hw_params_test_rate = snd_pcm_hw_params_test_rate; - ma_snd_pcm_hw_params_proc _snd_pcm_hw_params = snd_pcm_hw_params; - ma_snd_pcm_sw_params_sizeof_proc _snd_pcm_sw_params_sizeof = snd_pcm_sw_params_sizeof; - ma_snd_pcm_sw_params_current_proc _snd_pcm_sw_params_current = snd_pcm_sw_params_current; - ma_snd_pcm_sw_params_get_boundary_proc _snd_pcm_sw_params_get_boundary = snd_pcm_sw_params_get_boundary; - ma_snd_pcm_sw_params_set_avail_min_proc _snd_pcm_sw_params_set_avail_min = snd_pcm_sw_params_set_avail_min; - ma_snd_pcm_sw_params_set_start_threshold_proc _snd_pcm_sw_params_set_start_threshold = snd_pcm_sw_params_set_start_threshold; - ma_snd_pcm_sw_params_set_stop_threshold_proc _snd_pcm_sw_params_set_stop_threshold = snd_pcm_sw_params_set_stop_threshold; - ma_snd_pcm_sw_params_proc _snd_pcm_sw_params = snd_pcm_sw_params; - ma_snd_pcm_format_mask_sizeof_proc _snd_pcm_format_mask_sizeof = snd_pcm_format_mask_sizeof; - ma_snd_pcm_format_mask_test_proc _snd_pcm_format_mask_test = snd_pcm_format_mask_test; - ma_snd_pcm_get_chmap_proc _snd_pcm_get_chmap = snd_pcm_get_chmap; - ma_snd_pcm_state_proc _snd_pcm_state = snd_pcm_state; - ma_snd_pcm_prepare_proc _snd_pcm_prepare = snd_pcm_prepare; - ma_snd_pcm_start_proc _snd_pcm_start = snd_pcm_start; - ma_snd_pcm_drop_proc _snd_pcm_drop = snd_pcm_drop; - ma_snd_pcm_drain_proc _snd_pcm_drain = snd_pcm_drain; - ma_snd_pcm_reset_proc _snd_pcm_reset = snd_pcm_reset; - ma_snd_device_name_hint_proc _snd_device_name_hint = snd_device_name_hint; - ma_snd_device_name_get_hint_proc _snd_device_name_get_hint = snd_device_name_get_hint; - ma_snd_card_get_index_proc _snd_card_get_index = snd_card_get_index; - ma_snd_device_name_free_hint_proc _snd_device_name_free_hint = snd_device_name_free_hint; - ma_snd_pcm_mmap_begin_proc _snd_pcm_mmap_begin = snd_pcm_mmap_begin; - ma_snd_pcm_mmap_commit_proc _snd_pcm_mmap_commit = snd_pcm_mmap_commit; - ma_snd_pcm_recover_proc _snd_pcm_recover = snd_pcm_recover; - ma_snd_pcm_readi_proc _snd_pcm_readi = snd_pcm_readi; - ma_snd_pcm_writei_proc _snd_pcm_writei = snd_pcm_writei; - ma_snd_pcm_avail_proc _snd_pcm_avail = snd_pcm_avail; - ma_snd_pcm_avail_update_proc _snd_pcm_avail_update = snd_pcm_avail_update; - ma_snd_pcm_wait_proc _snd_pcm_wait = snd_pcm_wait; - ma_snd_pcm_nonblock_proc _snd_pcm_nonblock = snd_pcm_nonblock; - ma_snd_pcm_info_proc _snd_pcm_info = snd_pcm_info; - ma_snd_pcm_info_sizeof_proc _snd_pcm_info_sizeof = snd_pcm_info_sizeof; - ma_snd_pcm_info_get_name_proc _snd_pcm_info_get_name = snd_pcm_info_get_name; - ma_snd_pcm_poll_descriptors_proc _snd_pcm_poll_descriptors = snd_pcm_poll_descriptors; - ma_snd_pcm_poll_descriptors_count_proc _snd_pcm_poll_descriptors_count = snd_pcm_poll_descriptors_count; - ma_snd_pcm_poll_descriptors_revents_proc _snd_pcm_poll_descriptors_revents = snd_pcm_poll_descriptors_revents; - ma_snd_config_update_free_global_proc _snd_config_update_free_global = snd_config_update_free_global; - - pContext->alsa.snd_pcm_open = (ma_proc)_snd_pcm_open; - pContext->alsa.snd_pcm_close = (ma_proc)_snd_pcm_close; - pContext->alsa.snd_pcm_hw_params_sizeof = (ma_proc)_snd_pcm_hw_params_sizeof; - pContext->alsa.snd_pcm_hw_params_any = (ma_proc)_snd_pcm_hw_params_any; - pContext->alsa.snd_pcm_hw_params_set_format = (ma_proc)_snd_pcm_hw_params_set_format; - pContext->alsa.snd_pcm_hw_params_set_format_first = (ma_proc)_snd_pcm_hw_params_set_format_first; - pContext->alsa.snd_pcm_hw_params_get_format_mask = (ma_proc)_snd_pcm_hw_params_get_format_mask; - pContext->alsa.snd_pcm_hw_params_set_channels = (ma_proc)_snd_pcm_hw_params_set_channels; - pContext->alsa.snd_pcm_hw_params_set_channels_near = (ma_proc)_snd_pcm_hw_params_set_channels_near; - pContext->alsa.snd_pcm_hw_params_set_channels_minmax = (ma_proc)_snd_pcm_hw_params_set_channels_minmax; - pContext->alsa.snd_pcm_hw_params_set_rate_resample = (ma_proc)_snd_pcm_hw_params_set_rate_resample; - pContext->alsa.snd_pcm_hw_params_set_rate = (ma_proc)_snd_pcm_hw_params_set_rate; - pContext->alsa.snd_pcm_hw_params_set_rate_near = (ma_proc)_snd_pcm_hw_params_set_rate_near; - pContext->alsa.snd_pcm_hw_params_set_rate_minmax = (ma_proc)_snd_pcm_hw_params_set_rate_minmax; - pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (ma_proc)_snd_pcm_hw_params_set_buffer_size_near; - pContext->alsa.snd_pcm_hw_params_set_periods_near = (ma_proc)_snd_pcm_hw_params_set_periods_near; - pContext->alsa.snd_pcm_hw_params_set_access = (ma_proc)_snd_pcm_hw_params_set_access; - pContext->alsa.snd_pcm_hw_params_get_format = (ma_proc)_snd_pcm_hw_params_get_format; - pContext->alsa.snd_pcm_hw_params_get_channels = (ma_proc)_snd_pcm_hw_params_get_channels; - pContext->alsa.snd_pcm_hw_params_get_channels_min = (ma_proc)_snd_pcm_hw_params_get_channels_min; - pContext->alsa.snd_pcm_hw_params_get_channels_max = (ma_proc)_snd_pcm_hw_params_get_channels_max; - pContext->alsa.snd_pcm_hw_params_get_rate = (ma_proc)_snd_pcm_hw_params_get_rate; - pContext->alsa.snd_pcm_hw_params_get_rate_min = (ma_proc)_snd_pcm_hw_params_get_rate_min; - pContext->alsa.snd_pcm_hw_params_get_rate_max = (ma_proc)_snd_pcm_hw_params_get_rate_max; - pContext->alsa.snd_pcm_hw_params_get_buffer_size = (ma_proc)_snd_pcm_hw_params_get_buffer_size; - pContext->alsa.snd_pcm_hw_params_get_periods = (ma_proc)_snd_pcm_hw_params_get_periods; - pContext->alsa.snd_pcm_hw_params_get_access = (ma_proc)_snd_pcm_hw_params_get_access; - pContext->alsa.snd_pcm_hw_params_test_format = (ma_proc)_snd_pcm_hw_params_test_format; - pContext->alsa.snd_pcm_hw_params_test_channels = (ma_proc)_snd_pcm_hw_params_test_channels; - pContext->alsa.snd_pcm_hw_params_test_rate = (ma_proc)_snd_pcm_hw_params_test_rate; - pContext->alsa.snd_pcm_hw_params = (ma_proc)_snd_pcm_hw_params; - pContext->alsa.snd_pcm_sw_params_sizeof = (ma_proc)_snd_pcm_sw_params_sizeof; - pContext->alsa.snd_pcm_sw_params_current = (ma_proc)_snd_pcm_sw_params_current; - pContext->alsa.snd_pcm_sw_params_get_boundary = (ma_proc)_snd_pcm_sw_params_get_boundary; - pContext->alsa.snd_pcm_sw_params_set_avail_min = (ma_proc)_snd_pcm_sw_params_set_avail_min; - pContext->alsa.snd_pcm_sw_params_set_start_threshold = (ma_proc)_snd_pcm_sw_params_set_start_threshold; - pContext->alsa.snd_pcm_sw_params_set_stop_threshold = (ma_proc)_snd_pcm_sw_params_set_stop_threshold; - pContext->alsa.snd_pcm_sw_params = (ma_proc)_snd_pcm_sw_params; - pContext->alsa.snd_pcm_format_mask_sizeof = (ma_proc)_snd_pcm_format_mask_sizeof; - pContext->alsa.snd_pcm_format_mask_test = (ma_proc)_snd_pcm_format_mask_test; - pContext->alsa.snd_pcm_get_chmap = (ma_proc)_snd_pcm_get_chmap; - pContext->alsa.snd_pcm_state = (ma_proc)_snd_pcm_state; - pContext->alsa.snd_pcm_prepare = (ma_proc)_snd_pcm_prepare; - pContext->alsa.snd_pcm_start = (ma_proc)_snd_pcm_start; - pContext->alsa.snd_pcm_drop = (ma_proc)_snd_pcm_drop; - pContext->alsa.snd_pcm_drain = (ma_proc)_snd_pcm_drain; - pContext->alsa.snd_pcm_reset = (ma_proc)_snd_pcm_reset; - pContext->alsa.snd_device_name_hint = (ma_proc)_snd_device_name_hint; - pContext->alsa.snd_device_name_get_hint = (ma_proc)_snd_device_name_get_hint; - pContext->alsa.snd_card_get_index = (ma_proc)_snd_card_get_index; - pContext->alsa.snd_device_name_free_hint = (ma_proc)_snd_device_name_free_hint; - pContext->alsa.snd_pcm_mmap_begin = (ma_proc)_snd_pcm_mmap_begin; - pContext->alsa.snd_pcm_mmap_commit = (ma_proc)_snd_pcm_mmap_commit; - pContext->alsa.snd_pcm_recover = (ma_proc)_snd_pcm_recover; - pContext->alsa.snd_pcm_readi = (ma_proc)_snd_pcm_readi; - pContext->alsa.snd_pcm_writei = (ma_proc)_snd_pcm_writei; - pContext->alsa.snd_pcm_avail = (ma_proc)_snd_pcm_avail; - pContext->alsa.snd_pcm_avail_update = (ma_proc)_snd_pcm_avail_update; - pContext->alsa.snd_pcm_wait = (ma_proc)_snd_pcm_wait; - pContext->alsa.snd_pcm_nonblock = (ma_proc)_snd_pcm_nonblock; - pContext->alsa.snd_pcm_info = (ma_proc)_snd_pcm_info; - pContext->alsa.snd_pcm_info_sizeof = (ma_proc)_snd_pcm_info_sizeof; - pContext->alsa.snd_pcm_info_get_name = (ma_proc)_snd_pcm_info_get_name; - pContext->alsa.snd_pcm_poll_descriptors = (ma_proc)_snd_pcm_poll_descriptors; - pContext->alsa.snd_pcm_poll_descriptors_count = (ma_proc)_snd_pcm_poll_descriptors_count; - pContext->alsa.snd_pcm_poll_descriptors_revents = (ma_proc)_snd_pcm_poll_descriptors_revents; - pContext->alsa.snd_config_update_free_global = (ma_proc)_snd_config_update_free_global; -#endif - - pContext->alsa.useVerboseDeviceEnumeration = pConfig->alsa.useVerboseDeviceEnumeration; - - result = ma_mutex_init(&pContext->alsa.internalDeviceEnumLock); - if (result != MA_SUCCESS) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration."); - return result; - } - pCallbacks->onContextInit = ma_context_init__alsa; - pCallbacks->onContextUninit = ma_context_uninit__alsa; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__alsa; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__alsa; - pCallbacks->onDeviceInit = ma_device_init__alsa; - pCallbacks->onDeviceUninit = ma_device_uninit__alsa; - pCallbacks->onDeviceStart = ma_device_start__alsa; - pCallbacks->onDeviceStop = ma_device_stop__alsa; - pCallbacks->onDeviceRead = ma_device_read__alsa; - pCallbacks->onDeviceWrite = ma_device_write__alsa; - pCallbacks->onDeviceDataLoop = NULL; - pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__alsa; +/* Miscellaneous SPA references that we don't actively use but are required by structs or function parameters. */ +typedef struct ma_spa_command ma_spa_command; - return MA_SUCCESS; -} -#endif /* MA_HAS_ALSA */ +struct ma_spa_fraction +{ + ma_uint32 num; + ma_uint32 denom; +}; -/****************************************************************************** +#define MA_PW_KEY_MEDIA_TYPE "media.type" +#define MA_PW_KEY_MEDIA_CATEGORY "media.category" +#define MA_PW_KEY_MEDIA_ROLE "media.role" +#define MA_PW_KEY_MEDIA_CLASS "media.class" +#define MA_PW_KEY_NODE_LATENCY "node.latency" +#define MA_PW_KEY_TARGET_OBJECT "target.object" +#define MA_PW_KEY_METADATA_NAME "metadata.name" + +#define MA_PW_TYPE_INTERFACE_Node "PipeWire:Interface:Node" +#define MA_PW_TYPE_INTERFACE_Metadata "PipeWire:Interface:Metadata" + +#define MA_PW_ID_CORE 0 +#define MA_PW_ID_ANY 0xFFFFFFFF + +struct ma_pw_thread_loop; +struct ma_pw_loop; +struct ma_pw_context; +struct ma_pw_core; +struct ma_pw_registry; +struct ma_pw_metadata; +struct ma_pw_metadata_events; +struct ma_pw_proxy; +struct ma_pw_properties; +struct ma_pw_stream; +struct ma_pw_stream_control; +struct ma_pw_core_info; + +enum ma_pw_stream_state +{ + MA_PW_STREAM_STATE_ERROR = -1, + MA_PW_STREAM_STATE_UNCONNECTED = 0, + MA_PW_STREAM_STATE_CONNECTING = 1, + MA_PW_STREAM_STATE_PAUSED = 2, + MA_PW_STREAM_STATE_STREAMING = 3 +}; -PulseAudio Backend +enum ma_pw_stream_flags +{ + MA_PW_STREAM_FLAG_NONE = 0, + MA_PW_STREAM_FLAG_AUTOCONNECT = (1 << 0), + MA_PW_STREAM_FLAG_INACTIVE = (1 << 1), + MA_PW_STREAM_FLAG_MAP_BUFFERS = (1 << 2), + MA_PW_STREAM_FLAG_DRIVER = (1 << 3), + MA_PW_STREAM_FLAG_RT_PROCESS = (1 << 4), + MA_PW_STREAM_FLAG_NO_CONVERT = (1 << 5), + MA_PW_STREAM_FLAG_EXCLUSIVE = (1 << 6), + MA_PW_STREAM_FLAG_DONT_RECONNECT = (1 << 7), + MA_PW_STREAM_FLAG_ALLOC_BUFFERS = (1 << 8), + MA_PW_STREAM_FLAG_TRIGGER = (1 << 9), + MA_PW_STREAM_FLAG_ASYNC = (1 << 10) +}; -******************************************************************************/ -#ifdef MA_HAS_PULSEAUDIO -/* -The PulseAudio API, along with Apple's Core Audio, is the worst of the mainstream audio APIs. This is a brief description of what's going on -in the PulseAudio backend. I apologize if this gets a bit ranty for your liking - you might want to skip this discussion. +struct ma_pw_buffer +{ + struct ma_spa_buffer* buffer; + void* user_data; + ma_uint64 size; + ma_uint64 requested; +}; -PulseAudio has something they call the "Simple API", which unfortunately isn't suitable for miniaudio. I've not seen anywhere where it -allows you to enumerate over devices, nor does it seem to support the ability to stop and start streams. Looking at the documentation, it -appears as though the stream is constantly running and you prevent sound from being emitted or captured by simply not calling the read or -write functions. This is not a professional solution as it would be much better to *actually* stop the underlying stream. Perhaps the -simple API has some smarts to do this automatically, but I'm not sure. Another limitation with the simple API is that it seems inefficient -when you want to have multiple streams to a single context. For these reasons, miniaudio is not using the simple API. +struct ma_pw_time +{ + ma_int64 now; + struct ma_spa_fraction rate; + ma_uint64 ticks; + ma_int64 delay; + ma_uint64 queued; + ma_uint64 buffered; + ma_uint32 queued_buffers; + ma_uint32 avail_buffers; + ma_uint64 size; +}; -Since we're not using the simple API, we're left with the asynchronous API as our only other option. And boy, is this where it starts to -get fun, and I don't mean that in a good way... -The problems start with the very name of the API - "asynchronous". Yes, this is an asynchronous oriented API which means your commands -don't immediately take effect. You instead need to issue your commands, and then wait for them to complete. The waiting mechanism is -enabled through the use of a "main loop". In the asynchronous API you cannot get away from the main loop, and the main loop is where almost -all of PulseAudio's problems stem from. +#define MA_PW_VERSION_CORE_EVENTS 1 +struct ma_pw_core_events +{ + ma_uint32 version; + void (* info )(void* data, const struct ma_pw_core_info* info); + void (* done )(void* data, ma_uint32 id, int seq); + void (* ping )(void* data, ma_uint32 id, int seq); + void (* error )(void* data, ma_uint32 id, int seq, int res, const char* message); + void (* remove_id )(void* data, ma_uint32 id); + void (* bound_id )(void* data, ma_uint32 id, ma_uint32 global_id); + void (* add_mem )(void* data, ma_uint32 id, ma_uint32 type, int fd, ma_uint32 flags); + void (* remove_mem )(void* data, ma_uint32 id); + void (* bound_props)(void* data, ma_uint32 id, ma_uint32 global_id, const struct ma_spa_dict* props); +}; -When you first initialize PulseAudio you need an object referred to as "main loop". You can implement this yourself by defining your own -vtable, but it's much easier to just use one of the built-in main loop implementations. There's two generic implementations called -pa_mainloop and pa_threaded_mainloop, and another implementation specific to GLib called pa_glib_mainloop. We're using pa_threaded_mainloop -because it simplifies management of the worker thread. The idea of the main loop object is pretty self explanatory - you're supposed to use -it to implement a worker thread which runs in a loop. The main loop is where operations are actually executed. -To initialize the main loop, you just use `pa_threaded_mainloop_new()`. This is the first function you'll call. You can then get a pointer -to the vtable with `pa_threaded_mainloop_get_api()` (the main loop vtable is called `pa_mainloop_api`). Again, you can bypass the threaded -main loop object entirely and just implement `pa_mainloop_api` directly, but there's no need for it unless you're doing something extremely -specialized such as if you want to integrate it into your application's existing main loop infrastructure. +#define MA_PW_VERSION_REGISTRY 3 +#define MA_PW_VERSION_REGISTRY_EVENTS 0 +struct ma_pw_registry_events +{ + ma_uint32 version; + void (* global_add )(void* data, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props); + void (* global_remove)(void* data, ma_uint32 id); +}; -(EDIT 2021-01-26: miniaudio is no longer using `pa_threaded_mainloop` due to this issue: https://github.com/mackron/miniaudio/issues/262. -It is now using `pa_mainloop` which turns out to be a simpler solution anyway. The rest of this rant still applies, however.) -Once you have your main loop vtable (the `pa_mainloop_api` object) you can create the PulseAudio context. This is very similar to -miniaudio's context and they map to each other quite well. You have one context to many streams, which is basically the same as miniaudio's -one `ma_context` to many `ma_device`s. Here's where it starts to get annoying, however. When you first create the PulseAudio context, which -is done with `pa_context_new()`, it's not actually connected to anything. When you connect, you call `pa_context_connect()`. However, if -you remember, PulseAudio is an asynchronous API. That means you cannot just assume the context is connected after `pa_context_context()` -has returned. You instead need to wait for it to connect. To do this, you need to either wait for a callback to get fired, which you can -set with `pa_context_set_state_callback()`, or you can continuously poll the context's state. Either way, you need to run this in a loop. -All objects from here out are created from the context, and, I believe, you can't be creating these objects until the context is connected. -This waiting loop is therefore unavoidable. In order for the waiting to ever complete, however, the main loop needs to be running. Before -attempting to connect the context, the main loop needs to be started with `pa_threaded_mainloop_start()`. -The reason for this asynchronous design is to support cases where you're connecting to a remote server, say through a local network or an -internet connection. However, the *VAST* majority of cases don't involve this at all - they just connect to a local "server" running on the -host machine. The fact that this would be the default rather than making `pa_context_connect()` synchronous tends to boggle the mind. +#define PW_VERSION_METADATA_METHODS 0 +struct ma_pw_metadata_methods +{ + ma_uint32 version; + int (* add_listener)(void* object, struct ma_spa_hook* listener, const struct ma_pw_metadata_events* events, void* data); + int (* set_property)(void* object, ma_uint32 subject, const char* key, const char* type, const char* value); + int (* clear )(void* object); +}; -Once the context has been created and connected you can start creating a stream. A PulseAudio stream is analogous to miniaudio's device. -The initialization of a stream is fairly standard - you configure some attributes (analogous to miniaudio's device config) and then call -`pa_stream_new()` to actually create it. Here is where we start to get into "operations". When configuring the stream, you can get -information about the source (such as sample format, sample rate, etc.), however it's not synchronous. Instead, a `pa_operation` object -is returned from `pa_context_get_source_info_by_name()` (capture) or `pa_context_get_sink_info_by_name()` (playback). Then, you need to -run a loop (again!) to wait for the operation to complete which you can determine via a callback or polling, just like we did with the -context. Then, as an added bonus, you need to decrement the reference counter of the `pa_operation` object to ensure memory is cleaned up. -All of that just to retrieve basic information about a device! +#define MA_PW_VERSION_METADATA 3 +#define MA_PW_VERSION_METADATA_EVENTS 0 +struct ma_pw_metadata_events +{ + ma_uint32 version; + int (* property)(void* data, ma_uint32 subject, const char* key, const char* type, const char* value); +}; -Once the basic information about the device has been retrieved, miniaudio can now create the stream with `ma_stream_new()`. Like the -context, this needs to be connected. But we need to be careful here, because we're now about to introduce one of the most horrific design -choices in PulseAudio. -PulseAudio allows you to specify a callback that is fired when data can be written to or read from a stream. The language is important here -because PulseAudio takes it literally, specifically the "can be". You would think these callbacks would be appropriate as the place for -writing and reading data to and from the stream, and that would be right, except when it's not. When you initialize the stream, you can -set a flag that tells PulseAudio to not start the stream automatically. This is required because miniaudio does not auto-start devices -straight after initialization - you need to call `ma_device_start()` manually. The problem is that even when this flag is specified, -PulseAudio will immediately fire its write or read callback. This is *technically* correct (based on the wording in the documentation) -because indeed, data *can* be written at this point. The problem is that it's not *practical*. It makes sense that the write/read callback -would be where a program will want to write or read data to or from the stream, but when it's called before the application has even -requested that the stream be started, it's just not practical because the program probably isn't ready for any kind of data delivery at -that point (it may still need to load files or whatnot). Instead, this callback should only be fired when the application requests the -stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data -callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio -doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been -started. The device state is used for this - if the state is anything other than `ma_device_state_starting` or `ma_device_state_started`, the main data -callback is not fired. +#define MA_PW_VERSION_STREAM_EVENTS 2 +struct ma_pw_stream_events +{ + ma_uint32 version; + void (* destroy )(void* data); + void (* state_changed)(void* data, enum ma_pw_stream_state oldState, enum ma_pw_stream_state newState, const char* error); + void (* control_info )(void* data, ma_uint32 id, const struct ma_pw_stream_control* control); + void (* io_changed )(void* data, ma_uint32 id, void* area, ma_uint32 size); + void (* param_changed)(void* data, ma_uint32 id, const struct ma_spa_pod* param); + void (* add_buffer )(void* data, struct ma_pw_buffer* buffer); + void (* remove_buffer)(void* data, struct ma_pw_buffer* buffer); + void (* process )(void* data); + void (* drained )(void* data); + void (* command )(void* data, const struct ma_spa_command* command); + void (* trigger_done )(void* data); +}; +#endif -This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will -continuously fire the callback at regular intervals based on the size of the internal buffer. This will only ever be fired when the device -is running, and will be fired regardless of whether or not the user actually wrote anything to the device/stream. This not the case in -PulseAudio. In PulseAudio, the data callback will *only* be called if you wrote something to it previously. That means, if you don't call -`pa_stream_write()`, the callback will not get fired. On the surface you wouldn't think this would matter because you should be always -writing data, and if you don't have anything to write, just write silence. That's fine until you want to drain the stream. You see, if -you're continuously writing data to the stream, the stream will never get drained! That means in order to drain the stream, you need to -*not* write data to it! But remember, when you don't write data to the stream, the callback won't get fired again! Why is draining -important? Because that's how we've defined stopping to work in miniaudio. In miniaudio, stopping the device requires it to be drained -before returning from ma_device_stop(). So we've stopped the device, which requires us to drain, but draining requires us to *not* write -data to the stream (or else it won't ever complete draining), but not writing to the stream means the callback won't get fired again! +static MA_INLINE struct ma_spa_pod_id ma_spa_pod_id_init(ma_uint32 value) +{ + struct ma_spa_pod_id pod; -This becomes a problem when stopping and then restarting the device. When the device is stopped, it's drained, which requires us to *not* -write anything to the stream. But then, since we didn't write anything to it, the write callback will *never* get called again if we just -resume the stream naively. This means that starting the stream requires us to write data to the stream from outside the callback. This -disconnect is something PulseAudio has got seriously wrong - there should only ever be a single source of data delivery, that being the -callback. (I have tried using `pa_stream_flush()` to trigger the write callback to fire, but this just doesn't work for some reason.) + MA_ZERO_OBJECT(&pod); + pod.pod.size = 4; /* Does not include the header or padding. */ + pod.pod.type = MA_SPA_TYPE_Id; + pod.value = value; -Once you've created the stream, you need to connect it which involves the whole waiting procedure. This is the same process as the context, -only this time you'll poll for the state with `pa_stream_get_status()`. The starting and stopping of a streaming is referred to as -"corking" in PulseAudio. The analogy is corking a barrel. To start the stream, you uncork it, to stop it you cork it. Personally I think -it's silly - why would you not just call it "starting" and "stopping" like any other normal audio API? Anyway, the act of corking is, you -guessed it, asynchronous. This means you'll need our waiting loop as usual. Again, why this asynchronous design is the default is -absolutely beyond me. Would it really be that hard to just make it run synchronously? + return pod; +} -Teardown is pretty simple (what?!). It's just a matter of calling the relevant `_unref()` function on each object in reverse order that -they were initialized in. +static MA_INLINE struct ma_spa_pod_int ma_spa_pod_int_init(ma_int32 value) +{ + struct ma_spa_pod_int pod; -That's about it from the PulseAudio side. A bit ranty, I know, but they really need to fix that main loop and callback system. They're -embarrassingly unpractical. The main loop thing is an easy fix - have synchronous versions of all APIs. If an application wants these to -run asynchronously, they can execute them in a separate thread themselves. The desire to run these asynchronously is such a niche -requirement - it makes no sense to make it the default. The stream write callback needs to be change, or an alternative provided, that is -constantly fired, regardless of whether or not `pa_stream_write()` has been called, and it needs to take a pointer to a buffer as a -parameter which the program just writes to directly rather than having to call `pa_stream_writable_size()` and `pa_stream_write()`. These -changes alone will change PulseAudio from one of the worst audio APIs to one of the best. -*/ + MA_ZERO_OBJECT(&pod); + pod.pod.size = 4; /* Does not include the header or padding. */ + pod.pod.type = MA_SPA_TYPE_Int; + pod.value = value; + return pod; +} -/* -It is assumed pulseaudio.h is available when linking at compile time. When linking at compile time, we use the declarations in the header -to check for type safety. We cannot do this when linking at run time because the header might not be available. -*/ -#ifdef MA_NO_RUNTIME_LINKING +static MA_INLINE void* ma_spa_pod_choice_get_values(const struct ma_spa_pod_choice* choice) +{ + /* Values are stored as a flat array after the main struct. */ + return (void*)ma_offset_ptr(choice, sizeof(struct ma_spa_pod_choice)); +} -/* pulseaudio.h marks some functions with "inline" which isn't always supported. Need to emulate it. */ -#if !defined(__cplusplus) - #if defined(__STRICT_ANSI__) - #if !defined(inline) - #define inline __inline__ __attribute__((always_inline)) - #define MA_INLINE_DEFINED - #endif - #endif -#endif -#include -#if defined(MA_INLINE_DEFINED) - #undef inline - #undef MA_INLINE_DEFINED +#if 0 +static MA_INLINE ma_uint32 ma_spa_pod_array_get_length(const struct ma_spa_pod_array* array) +{ + return (array->pod.size - sizeof(struct ma_spa_pod_array_body)) / array->body.child.size; +} #endif -#define MA_PA_OK PA_OK -#define MA_PA_ERR_ACCESS PA_ERR_ACCESS -#define MA_PA_ERR_INVALID PA_ERR_INVALID -#define MA_PA_ERR_NOENTITY PA_ERR_NOENTITY -#define MA_PA_ERR_NOTSUPPORTED PA_ERR_NOTSUPPORTED +static MA_INLINE void* ma_spa_pod_array_get_values(const struct ma_spa_pod_array* array) +{ + /* Values are stored as a flat array after the main struct. */ + return (void*)ma_offset_ptr(array, sizeof(struct ma_spa_pod_array)); +} -#define MA_PA_CHANNELS_MAX PA_CHANNELS_MAX -#define MA_PA_RATE_MAX PA_RATE_MAX -typedef pa_context_flags_t ma_pa_context_flags_t; -#define MA_PA_CONTEXT_NOFLAGS PA_CONTEXT_NOFLAGS -#define MA_PA_CONTEXT_NOAUTOSPAWN PA_CONTEXT_NOAUTOSPAWN -#define MA_PA_CONTEXT_NOFAIL PA_CONTEXT_NOFAIL +struct ma_spa_pod_prop_id +{ + ma_uint32 key; + ma_uint32 flags; + struct ma_spa_pod_id value; +}; -typedef pa_stream_flags_t ma_pa_stream_flags_t; -#define MA_PA_STREAM_NOFLAGS PA_STREAM_NOFLAGS -#define MA_PA_STREAM_START_CORKED PA_STREAM_START_CORKED -#define MA_PA_STREAM_INTERPOLATE_TIMING PA_STREAM_INTERPOLATE_TIMING -#define MA_PA_STREAM_NOT_MONOTONIC PA_STREAM_NOT_MONOTONIC -#define MA_PA_STREAM_AUTO_TIMING_UPDATE PA_STREAM_AUTO_TIMING_UPDATE -#define MA_PA_STREAM_NO_REMAP_CHANNELS PA_STREAM_NO_REMAP_CHANNELS -#define MA_PA_STREAM_NO_REMIX_CHANNELS PA_STREAM_NO_REMIX_CHANNELS -#define MA_PA_STREAM_FIX_FORMAT PA_STREAM_FIX_FORMAT -#define MA_PA_STREAM_FIX_RATE PA_STREAM_FIX_RATE -#define MA_PA_STREAM_FIX_CHANNELS PA_STREAM_FIX_CHANNELS -#define MA_PA_STREAM_DONT_MOVE PA_STREAM_DONT_MOVE -#define MA_PA_STREAM_VARIABLE_RATE PA_STREAM_VARIABLE_RATE -#define MA_PA_STREAM_PEAK_DETECT PA_STREAM_PEAK_DETECT -#define MA_PA_STREAM_START_MUTED PA_STREAM_START_MUTED -#define MA_PA_STREAM_ADJUST_LATENCY PA_STREAM_ADJUST_LATENCY -#define MA_PA_STREAM_EARLY_REQUESTS PA_STREAM_EARLY_REQUESTS -#define MA_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND -#define MA_PA_STREAM_START_UNMUTED PA_STREAM_START_UNMUTED -#define MA_PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND -#define MA_PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME -#define MA_PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH +static MA_INLINE struct ma_spa_pod_prop_id ma_spa_pod_prop_id_init(ma_uint32 key, ma_uint32 value) +{ + struct ma_spa_pod_prop_id prop; -typedef pa_sink_flags_t ma_pa_sink_flags_t; -#define MA_PA_SINK_NOFLAGS PA_SINK_NOFLAGS -#define MA_PA_SINK_HW_VOLUME_CTRL PA_SINK_HW_VOLUME_CTRL -#define MA_PA_SINK_LATENCY PA_SINK_LATENCY -#define MA_PA_SINK_HARDWARE PA_SINK_HARDWARE -#define MA_PA_SINK_NETWORK PA_SINK_NETWORK -#define MA_PA_SINK_HW_MUTE_CTRL PA_SINK_HW_MUTE_CTRL -#define MA_PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME -#define MA_PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME -#define MA_PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY -#define MA_PA_SINK_SET_FORMATS PA_SINK_SET_FORMATS + prop.key = key; + prop.flags = 0; + prop.value = ma_spa_pod_id_init(value); -typedef pa_source_flags_t ma_pa_source_flags_t; -#define MA_PA_SOURCE_NOFLAGS PA_SOURCE_NOFLAGS -#define MA_PA_SOURCE_HW_VOLUME_CTRL PA_SOURCE_HW_VOLUME_CTRL -#define MA_PA_SOURCE_LATENCY PA_SOURCE_LATENCY -#define MA_PA_SOURCE_HARDWARE PA_SOURCE_HARDWARE -#define MA_PA_SOURCE_NETWORK PA_SOURCE_NETWORK -#define MA_PA_SOURCE_HW_MUTE_CTRL PA_SOURCE_HW_MUTE_CTRL -#define MA_PA_SOURCE_DECIBEL_VOLUME PA_SOURCE_DECIBEL_VOLUME -#define MA_PA_SOURCE_DYNAMIC_LATENCY PA_SOURCE_DYNAMIC_LATENCY -#define MA_PA_SOURCE_FLAT_VOLUME PA_SOURCE_FLAT_VOLUME + return prop; +} -typedef pa_context_state_t ma_pa_context_state_t; -#define MA_PA_CONTEXT_UNCONNECTED PA_CONTEXT_UNCONNECTED -#define MA_PA_CONTEXT_CONNECTING PA_CONTEXT_CONNECTING -#define MA_PA_CONTEXT_AUTHORIZING PA_CONTEXT_AUTHORIZING -#define MA_PA_CONTEXT_SETTING_NAME PA_CONTEXT_SETTING_NAME -#define MA_PA_CONTEXT_READY PA_CONTEXT_READY -#define MA_PA_CONTEXT_FAILED PA_CONTEXT_FAILED -#define MA_PA_CONTEXT_TERMINATED PA_CONTEXT_TERMINATED -typedef pa_stream_state_t ma_pa_stream_state_t; -#define MA_PA_STREAM_UNCONNECTED PA_STREAM_UNCONNECTED -#define MA_PA_STREAM_CREATING PA_STREAM_CREATING -#define MA_PA_STREAM_READY PA_STREAM_READY -#define MA_PA_STREAM_FAILED PA_STREAM_FAILED -#define MA_PA_STREAM_TERMINATED PA_STREAM_TERMINATED +struct ma_spa_pod_prop_int +{ + ma_uint32 key; + ma_uint32 flags; + struct ma_spa_pod_int value; +}; -typedef pa_operation_state_t ma_pa_operation_state_t; -#define MA_PA_OPERATION_RUNNING PA_OPERATION_RUNNING -#define MA_PA_OPERATION_DONE PA_OPERATION_DONE -#define MA_PA_OPERATION_CANCELLED PA_OPERATION_CANCELLED +static MA_INLINE struct ma_spa_pod_prop_int ma_spa_pod_prop_int_init(ma_uint32 key, ma_int32 value) +{ + struct ma_spa_pod_prop_int prop; -typedef pa_sink_state_t ma_pa_sink_state_t; -#define MA_PA_SINK_INVALID_STATE PA_SINK_INVALID_STATE -#define MA_PA_SINK_RUNNING PA_SINK_RUNNING -#define MA_PA_SINK_IDLE PA_SINK_IDLE -#define MA_PA_SINK_SUSPENDED PA_SINK_SUSPENDED + prop.key = key; + prop.flags = 0; + prop.value = ma_spa_pod_int_init(value); -typedef pa_source_state_t ma_pa_source_state_t; -#define MA_PA_SOURCE_INVALID_STATE PA_SOURCE_INVALID_STATE -#define MA_PA_SOURCE_RUNNING PA_SOURCE_RUNNING -#define MA_PA_SOURCE_IDLE PA_SOURCE_IDLE -#define MA_PA_SOURCE_SUSPENDED PA_SOURCE_SUSPENDED + return prop; +} -typedef pa_seek_mode_t ma_pa_seek_mode_t; -#define MA_PA_SEEK_RELATIVE PA_SEEK_RELATIVE -#define MA_PA_SEEK_ABSOLUTE PA_SEEK_ABSOLUTE -#define MA_PA_SEEK_RELATIVE_ON_READ PA_SEEK_RELATIVE_ON_READ -#define MA_PA_SEEK_RELATIVE_END PA_SEEK_RELATIVE_END -typedef pa_channel_position_t ma_pa_channel_position_t; -#define MA_PA_CHANNEL_POSITION_INVALID PA_CHANNEL_POSITION_INVALID -#define MA_PA_CHANNEL_POSITION_MONO PA_CHANNEL_POSITION_MONO -#define MA_PA_CHANNEL_POSITION_FRONT_LEFT PA_CHANNEL_POSITION_FRONT_LEFT -#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT PA_CHANNEL_POSITION_FRONT_RIGHT -#define MA_PA_CHANNEL_POSITION_FRONT_CENTER PA_CHANNEL_POSITION_FRONT_CENTER -#define MA_PA_CHANNEL_POSITION_REAR_CENTER PA_CHANNEL_POSITION_REAR_CENTER -#define MA_PA_CHANNEL_POSITION_REAR_LEFT PA_CHANNEL_POSITION_REAR_LEFT -#define MA_PA_CHANNEL_POSITION_REAR_RIGHT PA_CHANNEL_POSITION_REAR_RIGHT -#define MA_PA_CHANNEL_POSITION_LFE PA_CHANNEL_POSITION_LFE -#define MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER -#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER -#define MA_PA_CHANNEL_POSITION_SIDE_LEFT PA_CHANNEL_POSITION_SIDE_LEFT -#define MA_PA_CHANNEL_POSITION_SIDE_RIGHT PA_CHANNEL_POSITION_SIDE_RIGHT -#define MA_PA_CHANNEL_POSITION_AUX0 PA_CHANNEL_POSITION_AUX0 -#define MA_PA_CHANNEL_POSITION_AUX1 PA_CHANNEL_POSITION_AUX1 -#define MA_PA_CHANNEL_POSITION_AUX2 PA_CHANNEL_POSITION_AUX2 -#define MA_PA_CHANNEL_POSITION_AUX3 PA_CHANNEL_POSITION_AUX3 -#define MA_PA_CHANNEL_POSITION_AUX4 PA_CHANNEL_POSITION_AUX4 -#define MA_PA_CHANNEL_POSITION_AUX5 PA_CHANNEL_POSITION_AUX5 -#define MA_PA_CHANNEL_POSITION_AUX6 PA_CHANNEL_POSITION_AUX6 -#define MA_PA_CHANNEL_POSITION_AUX7 PA_CHANNEL_POSITION_AUX7 -#define MA_PA_CHANNEL_POSITION_AUX8 PA_CHANNEL_POSITION_AUX8 -#define MA_PA_CHANNEL_POSITION_AUX9 PA_CHANNEL_POSITION_AUX9 -#define MA_PA_CHANNEL_POSITION_AUX10 PA_CHANNEL_POSITION_AUX10 -#define MA_PA_CHANNEL_POSITION_AUX11 PA_CHANNEL_POSITION_AUX11 -#define MA_PA_CHANNEL_POSITION_AUX12 PA_CHANNEL_POSITION_AUX12 -#define MA_PA_CHANNEL_POSITION_AUX13 PA_CHANNEL_POSITION_AUX13 -#define MA_PA_CHANNEL_POSITION_AUX14 PA_CHANNEL_POSITION_AUX14 -#define MA_PA_CHANNEL_POSITION_AUX15 PA_CHANNEL_POSITION_AUX15 -#define MA_PA_CHANNEL_POSITION_AUX16 PA_CHANNEL_POSITION_AUX16 -#define MA_PA_CHANNEL_POSITION_AUX17 PA_CHANNEL_POSITION_AUX17 -#define MA_PA_CHANNEL_POSITION_AUX18 PA_CHANNEL_POSITION_AUX18 -#define MA_PA_CHANNEL_POSITION_AUX19 PA_CHANNEL_POSITION_AUX19 -#define MA_PA_CHANNEL_POSITION_AUX20 PA_CHANNEL_POSITION_AUX20 -#define MA_PA_CHANNEL_POSITION_AUX21 PA_CHANNEL_POSITION_AUX21 -#define MA_PA_CHANNEL_POSITION_AUX22 PA_CHANNEL_POSITION_AUX22 -#define MA_PA_CHANNEL_POSITION_AUX23 PA_CHANNEL_POSITION_AUX23 -#define MA_PA_CHANNEL_POSITION_AUX24 PA_CHANNEL_POSITION_AUX24 -#define MA_PA_CHANNEL_POSITION_AUX25 PA_CHANNEL_POSITION_AUX25 -#define MA_PA_CHANNEL_POSITION_AUX26 PA_CHANNEL_POSITION_AUX26 -#define MA_PA_CHANNEL_POSITION_AUX27 PA_CHANNEL_POSITION_AUX27 -#define MA_PA_CHANNEL_POSITION_AUX28 PA_CHANNEL_POSITION_AUX28 -#define MA_PA_CHANNEL_POSITION_AUX29 PA_CHANNEL_POSITION_AUX29 -#define MA_PA_CHANNEL_POSITION_AUX30 PA_CHANNEL_POSITION_AUX30 -#define MA_PA_CHANNEL_POSITION_AUX31 PA_CHANNEL_POSITION_AUX31 -#define MA_PA_CHANNEL_POSITION_TOP_CENTER PA_CHANNEL_POSITION_TOP_CENTER -#define MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT PA_CHANNEL_POSITION_TOP_FRONT_LEFT -#define MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT PA_CHANNEL_POSITION_TOP_FRONT_RIGHT -#define MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER PA_CHANNEL_POSITION_TOP_FRONT_CENTER -#define MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT PA_CHANNEL_POSITION_TOP_REAR_LEFT -#define MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT PA_CHANNEL_POSITION_TOP_REAR_RIGHT -#define MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER PA_CHANNEL_POSITION_TOP_REAR_CENTER -#define MA_PA_CHANNEL_POSITION_LEFT PA_CHANNEL_POSITION_LEFT -#define MA_PA_CHANNEL_POSITION_RIGHT PA_CHANNEL_POSITION_RIGHT -#define MA_PA_CHANNEL_POSITION_CENTER PA_CHANNEL_POSITION_CENTER -#define MA_PA_CHANNEL_POSITION_SUBWOOFER PA_CHANNEL_POSITION_SUBWOOFER +/* This is a custom pod for our buffer parameters. */ +struct ma_spa_pod_buffer_params +{ + struct ma_spa_pod_object object; + struct ma_spa_pod_prop_int buffers; + struct ma_spa_pod_prop_int blocks; + struct ma_spa_pod_prop_int stride; + struct ma_spa_pod_prop_int size; +}; -typedef pa_channel_map_def_t ma_pa_channel_map_def_t; -#define MA_PA_CHANNEL_MAP_AIFF PA_CHANNEL_MAP_AIFF -#define MA_PA_CHANNEL_MAP_ALSA PA_CHANNEL_MAP_ALSA -#define MA_PA_CHANNEL_MAP_AUX PA_CHANNEL_MAP_AUX -#define MA_PA_CHANNEL_MAP_WAVEEX PA_CHANNEL_MAP_WAVEEX -#define MA_PA_CHANNEL_MAP_OSS PA_CHANNEL_MAP_OSS -#define MA_PA_CHANNEL_MAP_DEFAULT PA_CHANNEL_MAP_DEFAULT +static MA_INLINE struct ma_spa_pod_buffer_params ma_spa_pod_buffer_params_init(ma_uint32 strideInBytes, ma_uint32 bufferSizeInFrames) +{ + struct ma_spa_pod_buffer_params pod; -typedef pa_sample_format_t ma_pa_sample_format_t; -#define MA_PA_SAMPLE_INVALID PA_SAMPLE_INVALID -#define MA_PA_SAMPLE_U8 PA_SAMPLE_U8 -#define MA_PA_SAMPLE_ALAW PA_SAMPLE_ALAW -#define MA_PA_SAMPLE_ULAW PA_SAMPLE_ULAW -#define MA_PA_SAMPLE_S16LE PA_SAMPLE_S16LE -#define MA_PA_SAMPLE_S16BE PA_SAMPLE_S16BE -#define MA_PA_SAMPLE_FLOAT32LE PA_SAMPLE_FLOAT32LE -#define MA_PA_SAMPLE_FLOAT32BE PA_SAMPLE_FLOAT32BE -#define MA_PA_SAMPLE_S32LE PA_SAMPLE_S32LE -#define MA_PA_SAMPLE_S32BE PA_SAMPLE_S32BE -#define MA_PA_SAMPLE_S24LE PA_SAMPLE_S24LE -#define MA_PA_SAMPLE_S24BE PA_SAMPLE_S24BE -#define MA_PA_SAMPLE_S24_32LE PA_SAMPLE_S24_32LE -#define MA_PA_SAMPLE_S24_32BE PA_SAMPLE_S24_32BE + /* + spa_pod_builder_add_object(&podBuilder, + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, + SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(2), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(bytesPerFrame), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(bytesPerFrame * pStreamState->bufferSizeInFrames)); + */ -typedef pa_mainloop ma_pa_mainloop; -typedef pa_threaded_mainloop ma_pa_threaded_mainloop; -typedef pa_mainloop_api ma_pa_mainloop_api; -typedef pa_context ma_pa_context; -typedef pa_operation ma_pa_operation; -typedef pa_stream ma_pa_stream; -typedef pa_spawn_api ma_pa_spawn_api; -typedef pa_buffer_attr ma_pa_buffer_attr; -typedef pa_channel_map ma_pa_channel_map; -typedef pa_cvolume ma_pa_cvolume; -typedef pa_sample_spec ma_pa_sample_spec; -typedef pa_sink_info ma_pa_sink_info; -typedef pa_source_info ma_pa_source_info; + MA_ZERO_OBJECT(&pod); -typedef pa_context_notify_cb_t ma_pa_context_notify_cb_t; -typedef pa_sink_info_cb_t ma_pa_sink_info_cb_t; -typedef pa_source_info_cb_t ma_pa_source_info_cb_t; -typedef pa_stream_success_cb_t ma_pa_stream_success_cb_t; -typedef pa_stream_request_cb_t ma_pa_stream_request_cb_t; -typedef pa_stream_notify_cb_t ma_pa_stream_notify_cb_t; -typedef pa_free_cb_t ma_pa_free_cb_t; -#else -#define MA_PA_OK 0 -#define MA_PA_ERR_ACCESS 1 -#define MA_PA_ERR_INVALID 2 -#define MA_PA_ERR_NOENTITY 5 -#define MA_PA_ERR_NOTSUPPORTED 19 + pod.object.pod.size = sizeof(struct ma_spa_pod_buffer_params) - sizeof(struct ma_spa_pod); /* Don't include the header in the size. */ + pod.object.pod.type = MA_SPA_TYPE_Object; + pod.object.body.type = MA_SPA_TYPE_OBJECT_ParamBuffers; + pod.object.body.id = MA_SPA_PARAM_Buffers; -#define MA_PA_CHANNELS_MAX 32 -#define MA_PA_RATE_MAX 384000 + pod.buffers = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_buffers, 2); + pod.blocks = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_blocks, 1); + pod.stride = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_stride, (ma_int32)strideInBytes); + pod.size = ma_spa_pod_prop_int_init(MA_SPA_PARAM_BUFFERS_size, (ma_int32)(strideInBytes * bufferSizeInFrames)); -typedef int ma_pa_context_flags_t; -#define MA_PA_CONTEXT_NOFLAGS 0x00000000 -#define MA_PA_CONTEXT_NOAUTOSPAWN 0x00000001 -#define MA_PA_CONTEXT_NOFAIL 0x00000002 + return pod; +} -typedef int ma_pa_stream_flags_t; -#define MA_PA_STREAM_NOFLAGS 0x00000000 -#define MA_PA_STREAM_START_CORKED 0x00000001 -#define MA_PA_STREAM_INTERPOLATE_TIMING 0x00000002 -#define MA_PA_STREAM_NOT_MONOTONIC 0x00000004 -#define MA_PA_STREAM_AUTO_TIMING_UPDATE 0x00000008 -#define MA_PA_STREAM_NO_REMAP_CHANNELS 0x00000010 -#define MA_PA_STREAM_NO_REMIX_CHANNELS 0x00000020 -#define MA_PA_STREAM_FIX_FORMAT 0x00000040 -#define MA_PA_STREAM_FIX_RATE 0x00000080 -#define MA_PA_STREAM_FIX_CHANNELS 0x00000100 -#define MA_PA_STREAM_DONT_MOVE 0x00000200 -#define MA_PA_STREAM_VARIABLE_RATE 0x00000400 -#define MA_PA_STREAM_PEAK_DETECT 0x00000800 -#define MA_PA_STREAM_START_MUTED 0x00001000 -#define MA_PA_STREAM_ADJUST_LATENCY 0x00002000 -#define MA_PA_STREAM_EARLY_REQUESTS 0x00004000 -#define MA_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND 0x00008000 -#define MA_PA_STREAM_START_UNMUTED 0x00010000 -#define MA_PA_STREAM_FAIL_ON_SUSPEND 0x00020000 -#define MA_PA_STREAM_RELATIVE_VOLUME 0x00040000 -#define MA_PA_STREAM_PASSTHROUGH 0x00080000 -typedef int ma_pa_sink_flags_t; -#define MA_PA_SINK_NOFLAGS 0x00000000 -#define MA_PA_SINK_HW_VOLUME_CTRL 0x00000001 -#define MA_PA_SINK_LATENCY 0x00000002 -#define MA_PA_SINK_HARDWARE 0x00000004 -#define MA_PA_SINK_NETWORK 0x00000008 -#define MA_PA_SINK_HW_MUTE_CTRL 0x00000010 -#define MA_PA_SINK_DECIBEL_VOLUME 0x00000020 -#define MA_PA_SINK_FLAT_VOLUME 0x00000040 -#define MA_PA_SINK_DYNAMIC_LATENCY 0x00000080 -#define MA_PA_SINK_SET_FORMATS 0x00000100 +/* A custom pod for audio info. */ +struct ma_spa_pod_audio_info_raw +{ + struct ma_spa_pod_object object; + struct ma_spa_pod_prop_id mediaType; + struct ma_spa_pod_prop_id mediaSubtype; + struct ma_spa_pod_prop_id format; + struct ma_spa_pod_prop_int extra[2]; /* Channels and sample rate. Both are optional. */ +}; -typedef int ma_pa_source_flags_t; -#define MA_PA_SOURCE_NOFLAGS 0x00000000 -#define MA_PA_SOURCE_HW_VOLUME_CTRL 0x00000001 -#define MA_PA_SOURCE_LATENCY 0x00000002 -#define MA_PA_SOURCE_HARDWARE 0x00000004 -#define MA_PA_SOURCE_NETWORK 0x00000008 -#define MA_PA_SOURCE_HW_MUTE_CTRL 0x00000010 -#define MA_PA_SOURCE_DECIBEL_VOLUME 0x00000020 -#define MA_PA_SOURCE_DYNAMIC_LATENCY 0x00000040 -#define MA_PA_SOURCE_FLAT_VOLUME 0x00000080 +static MA_INLINE struct ma_spa_pod_audio_info_raw ma_spa_pod_audio_info_raw_init(enum ma_spa_audio_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + struct ma_spa_pod_audio_info_raw pod; + int extraIndex = 0; -typedef int ma_pa_context_state_t; -#define MA_PA_CONTEXT_UNCONNECTED 0 -#define MA_PA_CONTEXT_CONNECTING 1 -#define MA_PA_CONTEXT_AUTHORIZING 2 -#define MA_PA_CONTEXT_SETTING_NAME 3 -#define MA_PA_CONTEXT_READY 4 -#define MA_PA_CONTEXT_FAILED 5 -#define MA_PA_CONTEXT_TERMINATED 6 + /* + struct spa_audio_info_raw audioInfo; + char podBuilderBuffer[1024]; + struct spa_pod_builder podBuilder; -typedef int ma_pa_stream_state_t; -#define MA_PA_STREAM_UNCONNECTED 0 -#define MA_PA_STREAM_CREATING 1 -#define MA_PA_STREAM_READY 2 -#define MA_PA_STREAM_FAILED 3 -#define MA_PA_STREAM_TERMINATED 4 + podBuilder = SPA_POD_BUILDER_INIT(podBuilderBuffer, sizeof(podBuilderBuffer)); -typedef int ma_pa_operation_state_t; -#define MA_PA_OPERATION_RUNNING 0 -#define MA_PA_OPERATION_DONE 1 -#define MA_PA_OPERATION_CANCELLED 2 + memset(&audioInfo, 0, sizeof(audioInfo)); + audioInfo.flags = SPA_AUDIO_FLAG_UNPOSITIONED; + audioInfo.format = (enum spa_audio_format)formatPA; + audioInfo.channels = pDescriptor->channels; + audioInfo.rate = pDescriptor->sampleRate; -typedef int ma_pa_sink_state_t; -#define MA_PA_SINK_INVALID_STATE -1 -#define MA_PA_SINK_RUNNING 0 -#define MA_PA_SINK_IDLE 1 -#define MA_PA_SINK_SUSPENDED 2 + pConnectionParameters[0] = spa_format_audio_raw_build(&podBuilder, SPA_PARAM_EnumFormat, &audioInfo); + */ -typedef int ma_pa_source_state_t; -#define MA_PA_SOURCE_INVALID_STATE -1 -#define MA_PA_SOURCE_RUNNING 0 -#define MA_PA_SOURCE_IDLE 1 -#define MA_PA_SOURCE_SUSPENDED 2 + MA_ZERO_OBJECT(&pod); -typedef int ma_pa_seek_mode_t; -#define MA_PA_SEEK_RELATIVE 0 -#define MA_PA_SEEK_ABSOLUTE 1 -#define MA_PA_SEEK_RELATIVE_ON_READ 2 -#define MA_PA_SEEK_RELATIVE_END 3 + pod.object.pod.size = sizeof(struct ma_spa_pod_object) - sizeof(struct ma_spa_pod); /* Don't include the header in the size. */ + pod.object.pod.type = MA_SPA_TYPE_Object; + pod.object.body.type = MA_SPA_TYPE_OBJECT_Format; + pod.object.body.id = MA_SPA_PARAM_EnumFormat; -typedef int ma_pa_channel_position_t; -#define MA_PA_CHANNEL_POSITION_INVALID -1 -#define MA_PA_CHANNEL_POSITION_MONO 0 -#define MA_PA_CHANNEL_POSITION_FRONT_LEFT 1 -#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT 2 -#define MA_PA_CHANNEL_POSITION_FRONT_CENTER 3 -#define MA_PA_CHANNEL_POSITION_REAR_CENTER 4 -#define MA_PA_CHANNEL_POSITION_REAR_LEFT 5 -#define MA_PA_CHANNEL_POSITION_REAR_RIGHT 6 -#define MA_PA_CHANNEL_POSITION_LFE 7 -#define MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER 8 -#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER 9 -#define MA_PA_CHANNEL_POSITION_SIDE_LEFT 10 -#define MA_PA_CHANNEL_POSITION_SIDE_RIGHT 11 -#define MA_PA_CHANNEL_POSITION_AUX0 12 -#define MA_PA_CHANNEL_POSITION_AUX1 13 -#define MA_PA_CHANNEL_POSITION_AUX2 14 -#define MA_PA_CHANNEL_POSITION_AUX3 15 -#define MA_PA_CHANNEL_POSITION_AUX4 16 -#define MA_PA_CHANNEL_POSITION_AUX5 17 -#define MA_PA_CHANNEL_POSITION_AUX6 18 -#define MA_PA_CHANNEL_POSITION_AUX7 19 -#define MA_PA_CHANNEL_POSITION_AUX8 20 -#define MA_PA_CHANNEL_POSITION_AUX9 21 -#define MA_PA_CHANNEL_POSITION_AUX10 22 -#define MA_PA_CHANNEL_POSITION_AUX11 23 -#define MA_PA_CHANNEL_POSITION_AUX12 24 -#define MA_PA_CHANNEL_POSITION_AUX13 25 -#define MA_PA_CHANNEL_POSITION_AUX14 26 -#define MA_PA_CHANNEL_POSITION_AUX15 27 -#define MA_PA_CHANNEL_POSITION_AUX16 28 -#define MA_PA_CHANNEL_POSITION_AUX17 29 -#define MA_PA_CHANNEL_POSITION_AUX18 30 -#define MA_PA_CHANNEL_POSITION_AUX19 31 -#define MA_PA_CHANNEL_POSITION_AUX20 32 -#define MA_PA_CHANNEL_POSITION_AUX21 33 -#define MA_PA_CHANNEL_POSITION_AUX22 34 -#define MA_PA_CHANNEL_POSITION_AUX23 35 -#define MA_PA_CHANNEL_POSITION_AUX24 36 -#define MA_PA_CHANNEL_POSITION_AUX25 37 -#define MA_PA_CHANNEL_POSITION_AUX26 38 -#define MA_PA_CHANNEL_POSITION_AUX27 39 -#define MA_PA_CHANNEL_POSITION_AUX28 40 -#define MA_PA_CHANNEL_POSITION_AUX29 41 -#define MA_PA_CHANNEL_POSITION_AUX30 42 -#define MA_PA_CHANNEL_POSITION_AUX31 43 -#define MA_PA_CHANNEL_POSITION_TOP_CENTER 44 -#define MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT 45 -#define MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT 46 -#define MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER 47 -#define MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT 48 -#define MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT 49 -#define MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER 50 -#define MA_PA_CHANNEL_POSITION_LEFT MA_PA_CHANNEL_POSITION_FRONT_LEFT -#define MA_PA_CHANNEL_POSITION_RIGHT MA_PA_CHANNEL_POSITION_FRONT_RIGHT -#define MA_PA_CHANNEL_POSITION_CENTER MA_PA_CHANNEL_POSITION_FRONT_CENTER -#define MA_PA_CHANNEL_POSITION_SUBWOOFER MA_PA_CHANNEL_POSITION_LFE + pod.mediaType = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_mediaType, MA_SPA_MEDIA_TYPE_audio); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); -typedef int ma_pa_channel_map_def_t; -#define MA_PA_CHANNEL_MAP_AIFF 0 -#define MA_PA_CHANNEL_MAP_ALSA 1 -#define MA_PA_CHANNEL_MAP_AUX 2 -#define MA_PA_CHANNEL_MAP_WAVEEX 3 -#define MA_PA_CHANNEL_MAP_OSS 4 -#define MA_PA_CHANNEL_MAP_DEFAULT MA_PA_CHANNEL_MAP_AIFF + pod.mediaSubtype = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_mediaSubtype, MA_SPA_MEDIA_SUBTYPE_raw); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); -typedef int ma_pa_sample_format_t; -#define MA_PA_SAMPLE_INVALID -1 -#define MA_PA_SAMPLE_U8 0 -#define MA_PA_SAMPLE_ALAW 1 -#define MA_PA_SAMPLE_ULAW 2 -#define MA_PA_SAMPLE_S16LE 3 -#define MA_PA_SAMPLE_S16BE 4 -#define MA_PA_SAMPLE_FLOAT32LE 5 -#define MA_PA_SAMPLE_FLOAT32BE 6 -#define MA_PA_SAMPLE_S32LE 7 -#define MA_PA_SAMPLE_S32BE 8 -#define MA_PA_SAMPLE_S24LE 9 -#define MA_PA_SAMPLE_S24BE 10 -#define MA_PA_SAMPLE_S24_32LE 11 -#define MA_PA_SAMPLE_S24_32BE 12 + pod.format = ma_spa_pod_prop_id_init(MA_SPA_FORMAT_AUDIO_format, (ma_uint32)format); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_id); -typedef struct ma_pa_mainloop ma_pa_mainloop; -typedef struct ma_pa_threaded_mainloop ma_pa_threaded_mainloop; -typedef struct ma_pa_mainloop_api ma_pa_mainloop_api; -typedef struct ma_pa_context ma_pa_context; -typedef struct ma_pa_operation ma_pa_operation; -typedef struct ma_pa_stream ma_pa_stream; -typedef struct ma_pa_spawn_api ma_pa_spawn_api; + if (channels > 0) { + pod.extra[extraIndex] = ma_spa_pod_prop_int_init(MA_SPA_FORMAT_AUDIO_channels, (ma_int32)channels); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_int); + extraIndex += 1; + } -typedef struct -{ - ma_uint32 maxlength; - ma_uint32 tlength; - ma_uint32 prebuf; - ma_uint32 minreq; - ma_uint32 fragsize; -} ma_pa_buffer_attr; + if (sampleRate > 0) { + pod.extra[extraIndex] = ma_spa_pod_prop_int_init(MA_SPA_FORMAT_AUDIO_rate, (ma_int32)sampleRate); + pod.object.pod.size += sizeof(struct ma_spa_pod_prop_int); + /*extraIndex += 1;*/ + } -typedef struct -{ - ma_uint8 channels; - ma_pa_channel_position_t map[MA_PA_CHANNELS_MAX]; -} ma_pa_channel_map; + /* We're going to leave the channel map alone and just do a conversion ourselves if it differs from the native map. */ -typedef struct -{ - ma_uint8 channels; - ma_uint32 values[MA_PA_CHANNELS_MAX]; -} ma_pa_cvolume; + return pod; +} -typedef struct -{ - ma_pa_sample_format_t format; - ma_uint32 rate; - ma_uint8 channels; -} ma_pa_sample_spec; -typedef struct +/* Extracts the value of an ID from a pod, depending on its type. */ +static MA_INLINE ma_uint32 ma_spa_pod_get_id_value(const struct ma_spa_pod* pod) { - const char* name; - ma_uint32 index; - const char* description; - ma_pa_sample_spec sample_spec; - ma_pa_channel_map channel_map; - ma_uint32 owner_module; - ma_pa_cvolume volume; - int mute; - ma_uint32 monitor_source; - const char* monitor_source_name; - ma_uint64 latency; - const char* driver; - ma_pa_sink_flags_t flags; - void* proplist; - ma_uint64 configured_latency; - ma_uint32 base_volume; - ma_pa_sink_state_t state; - ma_uint32 n_volume_steps; - ma_uint32 card; - ma_uint32 n_ports; - void** ports; - void* active_port; - ma_uint8 n_formats; - void** formats; -} ma_pa_sink_info; + MA_ASSERT(pod != NULL); -typedef struct -{ - const char *name; - ma_uint32 index; - const char *description; - ma_pa_sample_spec sample_spec; - ma_pa_channel_map channel_map; - ma_uint32 owner_module; - ma_pa_cvolume volume; - int mute; - ma_uint32 monitor_of_sink; - const char *monitor_of_sink_name; - ma_uint64 latency; - const char *driver; - ma_pa_source_flags_t flags; - void* proplist; - ma_uint64 configured_latency; - ma_uint32 base_volume; - ma_pa_source_state_t state; - ma_uint32 n_volume_steps; - ma_uint32 card; - ma_uint32 n_ports; - void** ports; - void* active_port; - ma_uint8 n_formats; - void** formats; -} ma_pa_source_info; + switch (pod->type) + { + case MA_SPA_TYPE_Id: + { + const struct ma_spa_pod_id* pPodID = (const struct ma_spa_pod_id*)pod; + return pPodID->value; + } -typedef void (* ma_pa_context_notify_cb_t)(ma_pa_context* c, void* userdata); -typedef void (* ma_pa_sink_info_cb_t) (ma_pa_context* c, const ma_pa_sink_info* i, int eol, void* userdata); -typedef void (* ma_pa_source_info_cb_t) (ma_pa_context* c, const ma_pa_source_info* i, int eol, void* userdata); -typedef void (* ma_pa_stream_success_cb_t)(ma_pa_stream* s, int success, void* userdata); -typedef void (* ma_pa_stream_request_cb_t)(ma_pa_stream* s, size_t nbytes, void* userdata); -typedef void (* ma_pa_stream_notify_cb_t) (ma_pa_stream* s, void* userdata); -typedef void (* ma_pa_free_cb_t) (void* p); -#endif + case MA_SPA_TYPE_Choice: + { + const struct ma_spa_pod_choice* pPodChoice = (const struct ma_spa_pod_choice*)pod; + const void* pValues = ma_spa_pod_choice_get_values(pPodChoice); + /* We're just going to return the first value. */ + return ((ma_uint32*)pValues)[0]; + } -typedef ma_pa_mainloop* (* ma_pa_mainloop_new_proc) (void); -typedef void (* ma_pa_mainloop_free_proc) (ma_pa_mainloop* m); -typedef void (* ma_pa_mainloop_quit_proc) (ma_pa_mainloop* m, int retval); -typedef ma_pa_mainloop_api* (* ma_pa_mainloop_get_api_proc) (ma_pa_mainloop* m); -typedef int (* ma_pa_mainloop_iterate_proc) (ma_pa_mainloop* m, int block, int* retval); -typedef void (* ma_pa_mainloop_wakeup_proc) (ma_pa_mainloop* m); -typedef ma_pa_threaded_mainloop* (* ma_pa_threaded_mainloop_new_proc) (void); -typedef void (* ma_pa_threaded_mainloop_free_proc) (ma_pa_threaded_mainloop* m); -typedef int (* ma_pa_threaded_mainloop_start_proc) (ma_pa_threaded_mainloop* m); -typedef void (* ma_pa_threaded_mainloop_stop_proc) (ma_pa_threaded_mainloop* m); -typedef void (* ma_pa_threaded_mainloop_lock_proc) (ma_pa_threaded_mainloop* m); -typedef void (* ma_pa_threaded_mainloop_unlock_proc) (ma_pa_threaded_mainloop* m); -typedef void (* ma_pa_threaded_mainloop_wait_proc) (ma_pa_threaded_mainloop* m); -typedef void (* ma_pa_threaded_mainloop_signal_proc) (ma_pa_threaded_mainloop* m, int wait_for_accept); -typedef void (* ma_pa_threaded_mainloop_accept_proc) (ma_pa_threaded_mainloop* m); -typedef int (* ma_pa_threaded_mainloop_get_retval_proc) (const ma_pa_threaded_mainloop* m); -typedef ma_pa_mainloop_api* (* ma_pa_threaded_mainloop_get_api_proc) (ma_pa_threaded_mainloop* m); -typedef int (* ma_pa_threaded_mainloop_in_thread_proc) (ma_pa_threaded_mainloop* m); -typedef void (* ma_pa_threaded_mainloop_set_name_proc) (ma_pa_threaded_mainloop* m, const char* name); -typedef ma_pa_context* (* ma_pa_context_new_proc) (ma_pa_mainloop_api* mainloop, const char* name); -typedef void (* ma_pa_context_unref_proc) (ma_pa_context* c); -typedef int (* ma_pa_context_connect_proc) (ma_pa_context* c, const char* server, ma_pa_context_flags_t flags, const ma_pa_spawn_api* api); -typedef void (* ma_pa_context_disconnect_proc) (ma_pa_context* c); -typedef void (* ma_pa_context_set_state_callback_proc) (ma_pa_context* c, ma_pa_context_notify_cb_t cb, void* userdata); -typedef ma_pa_context_state_t (* ma_pa_context_get_state_proc) (const ma_pa_context* c); -typedef ma_pa_operation* (* ma_pa_context_get_sink_info_list_proc) (ma_pa_context* c, ma_pa_sink_info_cb_t cb, void* userdata); -typedef ma_pa_operation* (* ma_pa_context_get_source_info_list_proc) (ma_pa_context* c, ma_pa_source_info_cb_t cb, void* userdata); -typedef ma_pa_operation* (* ma_pa_context_get_sink_info_by_name_proc) (ma_pa_context* c, const char* name, ma_pa_sink_info_cb_t cb, void* userdata); -typedef ma_pa_operation* (* ma_pa_context_get_source_info_by_name_proc)(ma_pa_context* c, const char* name, ma_pa_source_info_cb_t cb, void* userdata); -typedef void (* ma_pa_operation_unref_proc) (ma_pa_operation* o); -typedef ma_pa_operation_state_t (* ma_pa_operation_get_state_proc) (const ma_pa_operation* o); -typedef ma_pa_channel_map* (* ma_pa_channel_map_init_extend_proc) (ma_pa_channel_map* m, unsigned channels, ma_pa_channel_map_def_t def); -typedef int (* ma_pa_channel_map_valid_proc) (const ma_pa_channel_map* m); -typedef int (* ma_pa_channel_map_compatible_proc) (const ma_pa_channel_map* m, const ma_pa_sample_spec* ss); -typedef ma_pa_stream* (* ma_pa_stream_new_proc) (ma_pa_context* c, const char* name, const ma_pa_sample_spec* ss, const ma_pa_channel_map* map); -typedef void (* ma_pa_stream_unref_proc) (ma_pa_stream* s); -typedef int (* ma_pa_stream_connect_playback_proc) (ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags, const ma_pa_cvolume* volume, ma_pa_stream* sync_stream); -typedef int (* ma_pa_stream_connect_record_proc) (ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags); -typedef int (* ma_pa_stream_disconnect_proc) (ma_pa_stream* s); -typedef ma_pa_stream_state_t (* ma_pa_stream_get_state_proc) (const ma_pa_stream* s); -typedef const ma_pa_sample_spec* (* ma_pa_stream_get_sample_spec_proc) (ma_pa_stream* s); -typedef const ma_pa_channel_map* (* ma_pa_stream_get_channel_map_proc) (ma_pa_stream* s); -typedef const ma_pa_buffer_attr* (* ma_pa_stream_get_buffer_attr_proc) (ma_pa_stream* s); -typedef ma_pa_operation* (* ma_pa_stream_set_buffer_attr_proc) (ma_pa_stream* s, const ma_pa_buffer_attr* attr, ma_pa_stream_success_cb_t cb, void* userdata); -typedef const char* (* ma_pa_stream_get_device_name_proc) (const ma_pa_stream* s); -typedef void (* ma_pa_stream_set_write_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); -typedef void (* ma_pa_stream_set_read_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); -typedef void (* ma_pa_stream_set_suspended_callback_proc) (ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); -typedef void (* ma_pa_stream_set_moved_callback_proc) (ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); -typedef int (* ma_pa_stream_is_suspended_proc) (const ma_pa_stream* s); -typedef ma_pa_operation* (* ma_pa_stream_flush_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); -typedef ma_pa_operation* (* ma_pa_stream_drain_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); -typedef int (* ma_pa_stream_is_corked_proc) (const ma_pa_stream* s); -typedef ma_pa_operation* (* ma_pa_stream_cork_proc) (ma_pa_stream* s, int b, ma_pa_stream_success_cb_t cb, void* userdata); -typedef ma_pa_operation* (* ma_pa_stream_trigger_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); -typedef int (* ma_pa_stream_begin_write_proc) (ma_pa_stream* s, void** data, size_t* nbytes); -typedef int (* ma_pa_stream_write_proc) (ma_pa_stream* s, const void* data, size_t nbytes, ma_pa_free_cb_t free_cb, int64_t offset, ma_pa_seek_mode_t seek); -typedef int (* ma_pa_stream_peek_proc) (ma_pa_stream* s, const void** data, size_t* nbytes); -typedef int (* ma_pa_stream_drop_proc) (ma_pa_stream* s); -typedef size_t (* ma_pa_stream_writable_size_proc) (const ma_pa_stream* s); -typedef size_t (* ma_pa_stream_readable_size_proc) (const ma_pa_stream* s); + default: + { + MA_ASSERT(MA_FALSE); /* Unsupported type. */ + return 0; + } + } +} -typedef struct +static MA_INLINE ma_int32 ma_spa_pod_get_int_value(const struct ma_spa_pod* pod) { - ma_uint32 count; - ma_uint32 capacity; - ma_device_info* pInfo; -} ma_pulse_device_enum_data; + MA_ASSERT(pod != NULL); -static ma_result ma_result_from_pulse(int result) -{ - if (result < 0) { - return MA_ERROR; - } + switch (pod->type) + { + case MA_SPA_TYPE_Id: + { + const struct ma_spa_pod_int* pPodInt = (const struct ma_spa_pod_int*)pod; + return pPodInt->value; + } - switch (result) { - case MA_PA_OK: return MA_SUCCESS; - case MA_PA_ERR_ACCESS: return MA_ACCESS_DENIED; - case MA_PA_ERR_INVALID: return MA_INVALID_ARGS; - case MA_PA_ERR_NOENTITY: return MA_NO_DEVICE; - default: return MA_ERROR; - } -} + case MA_SPA_TYPE_Choice: + { + const struct ma_spa_pod_choice* pPodChoice = (const struct ma_spa_pod_choice*)pod; + const void* pValues = ma_spa_pod_choice_get_values(pPodChoice); -#if 0 -static ma_pa_sample_format_t ma_format_to_pulse(ma_format format) -{ - if (ma_is_little_endian()) { - switch (format) { - case ma_format_s16: return MA_PA_SAMPLE_S16LE; - case ma_format_s24: return MA_PA_SAMPLE_S24LE; - case ma_format_s32: return MA_PA_SAMPLE_S32LE; - case ma_format_f32: return MA_PA_SAMPLE_FLOAT32LE; - default: break; + /* We're just going to return the first value. */ + return ((ma_int32*)pValues)[0]; } - } else { - switch (format) { - case ma_format_s16: return MA_PA_SAMPLE_S16BE; - case ma_format_s24: return MA_PA_SAMPLE_S24BE; - case ma_format_s32: return MA_PA_SAMPLE_S32BE; - case ma_format_f32: return MA_PA_SAMPLE_FLOAT32BE; - default: break; + + default: + { + MA_ASSERT(MA_FALSE); /* Unsupported type. */ + return 0; } } +} - /* Endian agnostic. */ - switch (format) { - case ma_format_u8: return MA_PA_SAMPLE_U8; - default: return MA_PA_SAMPLE_INVALID; + + +typedef void (* ma_pw_init_proc )(int* argc, char*** argv); +typedef void (* ma_pw_deinit_proc )(void); +typedef struct ma_pw_loop* (* ma_pw_loop_new_proc )(const struct ma_spa_dict* props); +typedef void (* ma_pw_loop_destroy_proc )(struct ma_pw_loop* loop); +typedef int (* ma_pw_loop_set_name_proc )(struct ma_pw_loop* loop, const char* name); +typedef void (* ma_pw_loop_enter_proc )(struct ma_pw_loop* loop); +typedef void (* ma_pw_loop_leave_proc )(struct ma_pw_loop* loop); +typedef int (* ma_pw_loop_iterate_proc )(struct ma_pw_loop* loop, int timeout); +typedef struct spa_source* (* ma_pw_loop_add_event_proc )(struct ma_pw_loop* loop, ma_spa_source_event_func_t func, void* data); +typedef int (* ma_pw_loop_signal_event_proc )(struct ma_pw_loop* loop, struct spa_source* source); +typedef struct ma_pw_thread_loop* (* ma_pw_thread_loop_new_proc )(const char* name, const struct ma_spa_dict* props); +typedef void (* ma_pw_thread_loop_destroy_proc )(struct ma_pw_thread_loop* loop); +typedef struct ma_pw_loop* (* ma_pw_thread_loop_get_loop_proc )(struct ma_pw_thread_loop* loop); +typedef int (* ma_pw_thread_loop_start_proc )(struct ma_pw_thread_loop* loop); +typedef void (* ma_pw_thread_loop_lock_proc )(struct ma_pw_thread_loop* loop); +typedef void (* ma_pw_thread_loop_unlock_proc )(struct ma_pw_thread_loop* loop); +typedef struct ma_pw_context* (* ma_pw_context_new_proc )(struct ma_pw_loop* loop, struct ma_pw_properties *props, size_t user_data_size); +typedef void (* ma_pw_context_destroy_proc )(struct ma_pw_context* context); +typedef struct ma_pw_core* (* ma_pw_context_connect_proc )(struct ma_pw_context* context, struct ma_pw_properties* properties, size_t user_data_size); +typedef int (* ma_pw_core_disconnect_proc )(struct ma_pw_core* core); +typedef int (* ma_pw_core_add_listener_proc )(struct ma_pw_core* core, struct ma_spa_hook* listener, const struct ma_pw_core_events* events, void* data); +typedef struct ma_pw_registry* (* ma_pw_core_get_registry_proc )(struct ma_pw_core* core, ma_uint32 version, size_t user_data_size); +typedef int (* ma_pw_core_sync_proc )(struct ma_pw_core* core, ma_uint32 id, int seq); +typedef int (* ma_pw_registry_add_listener_proc )(struct ma_pw_registry* registry, struct ma_spa_hook* listener, const struct ma_pw_registry_events* events, void* data); +typedef void* (* ma_pw_registry_bind_proc )(struct ma_pw_registry* registry, ma_uint32 id, const char* type, ma_uint32 version, size_t user_data_size); +typedef void (* ma_pw_proxy_destroy_proc )(struct ma_pw_proxy* proxy); +typedef struct ma_pw_properties* (* ma_pw_properties_new_proc )(const char* key, ...); +typedef void (* ma_pw_properties_free_proc )(struct ma_pw_properties* properties); +typedef int (* ma_pw_properties_set_proc )(struct ma_pw_properties* properties, const char* key, const char* value); +typedef struct ma_pw_stream* (* ma_pw_stream_new_proc )(struct ma_pw_core* core, const char* name, struct ma_pw_properties* props); +typedef void (* ma_pw_stream_destroy_proc )(struct ma_pw_stream* stream); +typedef void (* ma_pw_stream_add_listener_proc )(struct ma_pw_stream* stream, struct ma_spa_hook* listener, const struct ma_pw_stream_events* events, void* data); +typedef int (* ma_pw_stream_connect_proc )(struct ma_pw_stream* stream, enum ma_spa_direction direction, ma_uint32 target_id, enum ma_pw_stream_flags flags, const struct ma_spa_pod** params, ma_uint32 paramCount); +typedef int (* ma_pw_stream_set_active_proc )(struct ma_pw_stream* stream, ma_bool8 active); +typedef struct ma_pw_buffer* (* ma_pw_stream_dequeue_buffer_proc )(struct ma_pw_stream* stream); +typedef int (* ma_pw_stream_queue_buffer_proc )(struct ma_pw_stream* stream, struct ma_pw_buffer* buffer); +typedef int (* ma_pw_stream_update_params_proc )(struct ma_pw_stream* stream, const struct ma_spa_pod** params, ma_uint32 paramCount); +typedef int (* ma_pw_stream_update_properties_proc)(struct ma_pw_stream* stream, const struct ma_spa_dict* dict); +typedef int (* ma_pw_stream_get_time_n_proc )(struct ma_pw_stream* stream, struct ma_pw_time* time, size_t size); + +typedef struct +{ + ma_log* pLog; + ma_handle hPipeWire; + ma_pw_init_proc pw_init; + ma_pw_deinit_proc pw_deinit; + ma_pw_loop_new_proc pw_loop_new; + ma_pw_loop_destroy_proc pw_loop_destroy; + ma_pw_loop_set_name_proc pw_loop_set_name; + ma_pw_loop_enter_proc pw_loop_enter; + ma_pw_loop_leave_proc pw_loop_leave; + ma_pw_loop_iterate_proc pw_loop_iterate; + ma_pw_loop_add_event_proc pw_loop_add_event; + ma_pw_loop_signal_event_proc pw_loop_signal_event; + ma_pw_thread_loop_new_proc pw_thread_loop_new; + ma_pw_thread_loop_destroy_proc pw_thread_loop_destroy; + ma_pw_thread_loop_get_loop_proc pw_thread_loop_get_loop; + ma_pw_thread_loop_start_proc pw_thread_loop_start; + ma_pw_thread_loop_lock_proc pw_thread_loop_lock; + ma_pw_thread_loop_unlock_proc pw_thread_loop_unlock; + ma_pw_context_new_proc pw_context_new; + ma_pw_context_destroy_proc pw_context_destroy; + ma_pw_context_connect_proc pw_context_connect; + ma_pw_core_disconnect_proc pw_core_disconnect; + ma_pw_core_add_listener_proc pw_core_add_listener; + ma_pw_core_get_registry_proc pw_core_get_registry; + ma_pw_core_sync_proc pw_core_sync; + ma_pw_registry_add_listener_proc pw_registry_add_listener; + ma_pw_registry_bind_proc pw_registry_bind; + ma_pw_proxy_destroy_proc pw_proxy_destroy; + ma_pw_properties_new_proc pw_properties_new; + ma_pw_properties_free_proc pw_properties_free; + ma_pw_properties_set_proc pw_properties_set; + ma_pw_stream_new_proc pw_stream_new; + ma_pw_stream_destroy_proc pw_stream_destroy; + ma_pw_stream_add_listener_proc pw_stream_add_listener; + ma_pw_stream_connect_proc pw_stream_connect; + ma_pw_stream_set_active_proc pw_stream_set_active; + ma_pw_stream_dequeue_buffer_proc pw_stream_dequeue_buffer; + ma_pw_stream_queue_buffer_proc pw_stream_queue_buffer; + ma_pw_stream_update_params_proc pw_stream_update_params; + ma_pw_stream_update_properties_proc pw_stream_update_properties; + ma_pw_stream_get_time_n_proc pw_stream_get_time_n; +} ma_context_state_pipewire; + + +#define MA_PIPEWIRE_INIT_STATUS_ERROR 0x01 +#define MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT 0x02 +#define MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY 0x04 +#define MA_PIPEWIRE_INIT_STATUS_INITIALIZED 0x08 + +typedef struct +{ + struct ma_pw_stream* pStream; + struct ma_spa_hook eventListener; + ma_uint32 initStatus; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + ma_uint32 bufferSizeInFrames; + ma_uint32 bufferCount; + ma_uint32 rbSizeInFrames; + ma_audio_ring_buffer rb; /* Only used in single-threaded mode. Acts as an intermediary buffer for step(). For playback, PipeWire will read from this ring buffer. For capture, it'll write to it. */ + ma_device_descriptor* pDescriptor; /* This is only used for setting up internal format. It's needed here because it looks like the only way to get the internal format is via a stupid callback. Will be set to NULL after initialization of the PipeWire stream. */ +} ma_pipewire_stream_state; + +typedef struct +{ + ma_context_state_pipewire* pContextStatePipeWire; + ma_device_type deviceType; + ma_device* pDevice; /* Only needed for ma_stream_event_process__pipewire(). We may change this later in which case we can delete this. */ + struct ma_pw_loop* pLoop; + struct ma_pw_context* pContext; + struct ma_pw_core* pCore; + struct spa_source* pWakeup; /* This is for waking up the loop which we need to do after each data processing callback and the miniaudio wakeup callback. */ + ma_pipewire_stream_state playback; + ma_pipewire_stream_state capture; + struct + { + ma_timer timer; + double lastTimeInSeconds; + } debugging; +} ma_device_state_pipewire; + + +static enum ma_spa_audio_format ma_format_to_pipewire(ma_format format) +{ + if (format == ma_format_u8) { + return MA_SPA_AUDIO_FORMAT_U8; + } + + if (ma_is_little_endian()) { + switch (format) { + case ma_format_s16: return MA_SPA_AUDIO_FORMAT_S16_LE; + case ma_format_s24: return MA_SPA_AUDIO_FORMAT_S24_LE; + case ma_format_s32: return MA_SPA_AUDIO_FORMAT_S32_LE; + case ma_format_f32: return MA_SPA_AUDIO_FORMAT_F32_LE; + default: break; + } + } else { + switch (format) { + case ma_format_s16: return MA_SPA_AUDIO_FORMAT_S16_BE; + case ma_format_s24: return MA_SPA_AUDIO_FORMAT_S24_BE; + case ma_format_s32: return MA_SPA_AUDIO_FORMAT_S32_BE; + case ma_format_f32: return MA_SPA_AUDIO_FORMAT_F32_BE; + default: break; + } } + + return MA_SPA_AUDIO_FORMAT_UNKNOWN; } -#endif -static ma_format ma_format_from_pulse(ma_pa_sample_format_t format) +static ma_format ma_format_from_pipewire(enum ma_spa_audio_format format) { + if (format == MA_SPA_AUDIO_FORMAT_U8) { + return ma_format_u8; + } + if (ma_is_little_endian()) { switch (format) { - case MA_PA_SAMPLE_S16LE: return ma_format_s16; - case MA_PA_SAMPLE_S24LE: return ma_format_s24; - case MA_PA_SAMPLE_S32LE: return ma_format_s32; - case MA_PA_SAMPLE_FLOAT32LE: return ma_format_f32; + case MA_SPA_AUDIO_FORMAT_S16_LE: return ma_format_s16; + case MA_SPA_AUDIO_FORMAT_S24_LE: return ma_format_s24; + case MA_SPA_AUDIO_FORMAT_S32_LE: return ma_format_s32; + case MA_SPA_AUDIO_FORMAT_F32_LE: return ma_format_f32; default: break; } } else { switch (format) { - case MA_PA_SAMPLE_S16BE: return ma_format_s16; - case MA_PA_SAMPLE_S24BE: return ma_format_s24; - case MA_PA_SAMPLE_S32BE: return ma_format_s32; - case MA_PA_SAMPLE_FLOAT32BE: return ma_format_f32; + case MA_SPA_AUDIO_FORMAT_S16_BE: return ma_format_s16; + case MA_SPA_AUDIO_FORMAT_S24_BE: return ma_format_s24; + case MA_SPA_AUDIO_FORMAT_S32_BE: return ma_format_s32; + case MA_SPA_AUDIO_FORMAT_F32_BE: return ma_format_f32; default: break; } } - /* Endian agnostic. */ - switch (format) { - case MA_PA_SAMPLE_U8: return ma_format_u8; - default: return ma_format_unknown; - } + return ma_format_unknown; } -static ma_channel ma_channel_position_from_pulse(ma_pa_channel_position_t position) +static ma_channel ma_channel_from_pipewire(ma_uint32 channel) { - switch (position) + switch (channel) { - case MA_PA_CHANNEL_POSITION_INVALID: return MA_CHANNEL_NONE; - case MA_PA_CHANNEL_POSITION_MONO: return MA_CHANNEL_MONO; - case MA_PA_CHANNEL_POSITION_FRONT_LEFT: return MA_CHANNEL_FRONT_LEFT; - case MA_PA_CHANNEL_POSITION_FRONT_RIGHT: return MA_CHANNEL_FRONT_RIGHT; - case MA_PA_CHANNEL_POSITION_FRONT_CENTER: return MA_CHANNEL_FRONT_CENTER; - case MA_PA_CHANNEL_POSITION_REAR_CENTER: return MA_CHANNEL_BACK_CENTER; - case MA_PA_CHANNEL_POSITION_REAR_LEFT: return MA_CHANNEL_BACK_LEFT; - case MA_PA_CHANNEL_POSITION_REAR_RIGHT: return MA_CHANNEL_BACK_RIGHT; - case MA_PA_CHANNEL_POSITION_LFE: return MA_CHANNEL_LFE; - case MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return MA_CHANNEL_FRONT_LEFT_CENTER; - case MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return MA_CHANNEL_FRONT_RIGHT_CENTER; - case MA_PA_CHANNEL_POSITION_SIDE_LEFT: return MA_CHANNEL_SIDE_LEFT; - case MA_PA_CHANNEL_POSITION_SIDE_RIGHT: return MA_CHANNEL_SIDE_RIGHT; - case MA_PA_CHANNEL_POSITION_AUX0: return MA_CHANNEL_AUX_0; - case MA_PA_CHANNEL_POSITION_AUX1: return MA_CHANNEL_AUX_1; - case MA_PA_CHANNEL_POSITION_AUX2: return MA_CHANNEL_AUX_2; - case MA_PA_CHANNEL_POSITION_AUX3: return MA_CHANNEL_AUX_3; - case MA_PA_CHANNEL_POSITION_AUX4: return MA_CHANNEL_AUX_4; - case MA_PA_CHANNEL_POSITION_AUX5: return MA_CHANNEL_AUX_5; - case MA_PA_CHANNEL_POSITION_AUX6: return MA_CHANNEL_AUX_6; - case MA_PA_CHANNEL_POSITION_AUX7: return MA_CHANNEL_AUX_7; - case MA_PA_CHANNEL_POSITION_AUX8: return MA_CHANNEL_AUX_8; - case MA_PA_CHANNEL_POSITION_AUX9: return MA_CHANNEL_AUX_9; - case MA_PA_CHANNEL_POSITION_AUX10: return MA_CHANNEL_AUX_10; - case MA_PA_CHANNEL_POSITION_AUX11: return MA_CHANNEL_AUX_11; - case MA_PA_CHANNEL_POSITION_AUX12: return MA_CHANNEL_AUX_12; - case MA_PA_CHANNEL_POSITION_AUX13: return MA_CHANNEL_AUX_13; - case MA_PA_CHANNEL_POSITION_AUX14: return MA_CHANNEL_AUX_14; - case MA_PA_CHANNEL_POSITION_AUX15: return MA_CHANNEL_AUX_15; - case MA_PA_CHANNEL_POSITION_AUX16: return MA_CHANNEL_AUX_16; - case MA_PA_CHANNEL_POSITION_AUX17: return MA_CHANNEL_AUX_17; - case MA_PA_CHANNEL_POSITION_AUX18: return MA_CHANNEL_AUX_18; - case MA_PA_CHANNEL_POSITION_AUX19: return MA_CHANNEL_AUX_19; - case MA_PA_CHANNEL_POSITION_AUX20: return MA_CHANNEL_AUX_20; - case MA_PA_CHANNEL_POSITION_AUX21: return MA_CHANNEL_AUX_21; - case MA_PA_CHANNEL_POSITION_AUX22: return MA_CHANNEL_AUX_22; - case MA_PA_CHANNEL_POSITION_AUX23: return MA_CHANNEL_AUX_23; - case MA_PA_CHANNEL_POSITION_AUX24: return MA_CHANNEL_AUX_24; - case MA_PA_CHANNEL_POSITION_AUX25: return MA_CHANNEL_AUX_25; - case MA_PA_CHANNEL_POSITION_AUX26: return MA_CHANNEL_AUX_26; - case MA_PA_CHANNEL_POSITION_AUX27: return MA_CHANNEL_AUX_27; - case MA_PA_CHANNEL_POSITION_AUX28: return MA_CHANNEL_AUX_28; - case MA_PA_CHANNEL_POSITION_AUX29: return MA_CHANNEL_AUX_29; - case MA_PA_CHANNEL_POSITION_AUX30: return MA_CHANNEL_AUX_30; - case MA_PA_CHANNEL_POSITION_AUX31: return MA_CHANNEL_AUX_31; - case MA_PA_CHANNEL_POSITION_TOP_CENTER: return MA_CHANNEL_TOP_CENTER; - case MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return MA_CHANNEL_TOP_FRONT_LEFT; - case MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return MA_CHANNEL_TOP_FRONT_RIGHT; - case MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return MA_CHANNEL_TOP_FRONT_CENTER; - case MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT: return MA_CHANNEL_TOP_BACK_LEFT; - case MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return MA_CHANNEL_TOP_BACK_RIGHT; - case MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER: return MA_CHANNEL_TOP_BACK_CENTER; + case MA_SPA_AUDIO_CHANNEL_MONO: return MA_CHANNEL_MONO; + case MA_SPA_AUDIO_CHANNEL_FL: return MA_CHANNEL_FRONT_LEFT; + case MA_SPA_AUDIO_CHANNEL_FR: return MA_CHANNEL_FRONT_RIGHT; + case MA_SPA_AUDIO_CHANNEL_FC: return MA_CHANNEL_FRONT_CENTER; + case MA_SPA_AUDIO_CHANNEL_LFE: return MA_CHANNEL_LFE; + case MA_SPA_AUDIO_CHANNEL_SL: return MA_CHANNEL_SIDE_LEFT; + case MA_SPA_AUDIO_CHANNEL_SR: return MA_CHANNEL_SIDE_RIGHT; + case MA_SPA_AUDIO_CHANNEL_FLC: return MA_CHANNEL_FRONT_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_FRC: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case MA_SPA_AUDIO_CHANNEL_RC: return MA_CHANNEL_BACK_CENTER; + case MA_SPA_AUDIO_CHANNEL_RL: return MA_CHANNEL_BACK_LEFT; + case MA_SPA_AUDIO_CHANNEL_RR: return MA_CHANNEL_BACK_RIGHT; + case MA_SPA_AUDIO_CHANNEL_TC: return MA_CHANNEL_TOP_CENTER; + case MA_SPA_AUDIO_CHANNEL_TFL: return MA_CHANNEL_TOP_FRONT_LEFT; + case MA_SPA_AUDIO_CHANNEL_TFC: return MA_CHANNEL_TOP_FRONT_CENTER; + case MA_SPA_AUDIO_CHANNEL_TFR: return MA_CHANNEL_TOP_FRONT_RIGHT; + case MA_SPA_AUDIO_CHANNEL_TRL: return MA_CHANNEL_TOP_BACK_LEFT; + case MA_SPA_AUDIO_CHANNEL_TRC: return MA_CHANNEL_TOP_BACK_CENTER; + case MA_SPA_AUDIO_CHANNEL_TRR: return MA_CHANNEL_TOP_BACK_RIGHT; + + /* These need to be added to miniaudio. */ + #if 0 + case MA_SPA_AUDIO_CHANNEL_RLC: return MA_CHANNEL_BACK_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_RRC: return MA_CHANNEL_BACK_RIGHT_CENTER; + case MA_SPA_AUDIO_CHANNEL_FLW: return MA_CHANNEL_FRONT_LEFT_WIDE; + case MA_SPA_AUDIO_CHANNEL_FRW: return MA_CHANNEL_FRONT_RIGHT_WIDE; + case MA_SPA_AUDIO_CHANNEL_LFE2: return MA_CHANNEL_LFE2; + case MA_SPA_AUDIO_CHANNEL_FLH: return MA_CHANNEL_FRONT_LEFT_HIGH; + case MA_SPA_AUDIO_CHANNEL_FCH: return MA_CHANNEL_FRONT_CENTER_HIGH; + case MA_SPA_AUDIO_CHANNEL_FRH: return MA_CHANNEL_FRONT_RIGHT_HIGH; + case MA_SPA_AUDIO_CHANNEL_TFLC: return MA_CHANNEL_TOP_FRONT_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_TFRC: return MA_CHANNEL_TOP_FRONT_RIGHT_CENTER; + case MA_SPA_AUDIO_CHANNEL_TSL: return MA_CHANNEL_TOP_SIDE_LEFT; + case MA_SPA_AUDIO_CHANNEL_TSR: return MA_CHANNEL_TOP_SIDE_RIGHT; + case MA_SPA_AUDIO_CHANNEL_LLFE: return MA_CHANNEL_LEFT_LFE; + case MA_SPA_AUDIO_CHANNEL_RLFE: return MA_CHANNEL_RIGHT_LFE; + case MA_SPA_AUDIO_CHANNEL_BC: return MA_CHANNEL_BOTTOM_CENTER; + case MA_SPA_AUDIO_CHANNEL_BLC: return MA_CHANNEL_BOTTOM_LEFT_CENTER; + case MA_SPA_AUDIO_CHANNEL_BRC: return MA_CHANNEL_BOTTOM_RIGHT_CENTER; + #endif + + case MA_SPA_AUDIO_CHANNEL_AUX0: return MA_CHANNEL_AUX_0; + case MA_SPA_AUDIO_CHANNEL_AUX1: return MA_CHANNEL_AUX_1; + case MA_SPA_AUDIO_CHANNEL_AUX2: return MA_CHANNEL_AUX_2; + case MA_SPA_AUDIO_CHANNEL_AUX3: return MA_CHANNEL_AUX_3; + case MA_SPA_AUDIO_CHANNEL_AUX4: return MA_CHANNEL_AUX_4; + case MA_SPA_AUDIO_CHANNEL_AUX5: return MA_CHANNEL_AUX_5; + case MA_SPA_AUDIO_CHANNEL_AUX6: return MA_CHANNEL_AUX_6; + case MA_SPA_AUDIO_CHANNEL_AUX7: return MA_CHANNEL_AUX_7; + case MA_SPA_AUDIO_CHANNEL_AUX8: return MA_CHANNEL_AUX_8; + case MA_SPA_AUDIO_CHANNEL_AUX9: return MA_CHANNEL_AUX_9; + case MA_SPA_AUDIO_CHANNEL_AUX10: return MA_CHANNEL_AUX_10; + case MA_SPA_AUDIO_CHANNEL_AUX11: return MA_CHANNEL_AUX_11; + case MA_SPA_AUDIO_CHANNEL_AUX12: return MA_CHANNEL_AUX_12; + case MA_SPA_AUDIO_CHANNEL_AUX13: return MA_CHANNEL_AUX_13; + case MA_SPA_AUDIO_CHANNEL_AUX14: return MA_CHANNEL_AUX_14; + case MA_SPA_AUDIO_CHANNEL_AUX15: return MA_CHANNEL_AUX_15; + case MA_SPA_AUDIO_CHANNEL_AUX16: return MA_CHANNEL_AUX_16; + case MA_SPA_AUDIO_CHANNEL_AUX17: return MA_CHANNEL_AUX_17; + case MA_SPA_AUDIO_CHANNEL_AUX18: return MA_CHANNEL_AUX_18; + case MA_SPA_AUDIO_CHANNEL_AUX19: return MA_CHANNEL_AUX_19; + case MA_SPA_AUDIO_CHANNEL_AUX20: return MA_CHANNEL_AUX_20; + case MA_SPA_AUDIO_CHANNEL_AUX21: return MA_CHANNEL_AUX_21; + case MA_SPA_AUDIO_CHANNEL_AUX22: return MA_CHANNEL_AUX_22; + case MA_SPA_AUDIO_CHANNEL_AUX23: return MA_CHANNEL_AUX_23; + case MA_SPA_AUDIO_CHANNEL_AUX24: return MA_CHANNEL_AUX_24; + case MA_SPA_AUDIO_CHANNEL_AUX25: return MA_CHANNEL_AUX_25; + case MA_SPA_AUDIO_CHANNEL_AUX26: return MA_CHANNEL_AUX_26; + case MA_SPA_AUDIO_CHANNEL_AUX27: return MA_CHANNEL_AUX_27; + case MA_SPA_AUDIO_CHANNEL_AUX28: return MA_CHANNEL_AUX_28; + case MA_SPA_AUDIO_CHANNEL_AUX29: return MA_CHANNEL_AUX_29; + case MA_SPA_AUDIO_CHANNEL_AUX30: return MA_CHANNEL_AUX_30; + case MA_SPA_AUDIO_CHANNEL_AUX31: return MA_CHANNEL_AUX_31; + + /* These need to be added to miniaudio. */ + #if 0 + case MA_SPA_AUDIO_CHANNEL_AUX32: return MA_CHANNEL_AUX_32; + case MA_SPA_AUDIO_CHANNEL_AUX33: return MA_CHANNEL_AUX_33; + case MA_SPA_AUDIO_CHANNEL_AUX34: return MA_CHANNEL_AUX_34; + case MA_SPA_AUDIO_CHANNEL_AUX35: return MA_CHANNEL_AUX_35; + case MA_SPA_AUDIO_CHANNEL_AUX36: return MA_CHANNEL_AUX_36; + case MA_SPA_AUDIO_CHANNEL_AUX37: return MA_CHANNEL_AUX_37; + case MA_SPA_AUDIO_CHANNEL_AUX38: return MA_CHANNEL_AUX_38; + case MA_SPA_AUDIO_CHANNEL_AUX39: return MA_CHANNEL_AUX_39; + case MA_SPA_AUDIO_CHANNEL_AUX40: return MA_CHANNEL_AUX_40; + case MA_SPA_AUDIO_CHANNEL_AUX41: return MA_CHANNEL_AUX_41; + case MA_SPA_AUDIO_CHANNEL_AUX42: return MA_CHANNEL_AUX_42; + case MA_SPA_AUDIO_CHANNEL_AUX43: return MA_CHANNEL_AUX_43; + case MA_SPA_AUDIO_CHANNEL_AUX44: return MA_CHANNEL_AUX_44; + case MA_SPA_AUDIO_CHANNEL_AUX45: return MA_CHANNEL_AUX_45; + case MA_SPA_AUDIO_CHANNEL_AUX46: return MA_CHANNEL_AUX_46; + case MA_SPA_AUDIO_CHANNEL_AUX47: return MA_CHANNEL_AUX_47; + case MA_SPA_AUDIO_CHANNEL_AUX48: return MA_CHANNEL_AUX_48; + case MA_SPA_AUDIO_CHANNEL_AUX49: return MA_CHANNEL_AUX_49; + case MA_SPA_AUDIO_CHANNEL_AUX50: return MA_CHANNEL_AUX_50; + case MA_SPA_AUDIO_CHANNEL_AUX51: return MA_CHANNEL_AUX_51; + case MA_SPA_AUDIO_CHANNEL_AUX52: return MA_CHANNEL_AUX_52; + case MA_SPA_AUDIO_CHANNEL_AUX53: return MA_CHANNEL_AUX_53; + case MA_SPA_AUDIO_CHANNEL_AUX54: return MA_CHANNEL_AUX_54; + case MA_SPA_AUDIO_CHANNEL_AUX55: return MA_CHANNEL_AUX_55; + case MA_SPA_AUDIO_CHANNEL_AUX56: return MA_CHANNEL_AUX_56; + case MA_SPA_AUDIO_CHANNEL_AUX57: return MA_CHANNEL_AUX_57; + case MA_SPA_AUDIO_CHANNEL_AUX58: return MA_CHANNEL_AUX_58; + case MA_SPA_AUDIO_CHANNEL_AUX59: return MA_CHANNEL_AUX_59; + case MA_SPA_AUDIO_CHANNEL_AUX60: return MA_CHANNEL_AUX_60; + case MA_SPA_AUDIO_CHANNEL_AUX61: return MA_CHANNEL_AUX_61; + case MA_SPA_AUDIO_CHANNEL_AUX62: return MA_CHANNEL_AUX_62; + case MA_SPA_AUDIO_CHANNEL_AUX63: return MA_CHANNEL_AUX_63; + #endif + default: return MA_CHANNEL_NONE; } } -#if 0 -static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position) + +static ma_context_state_pipewire* ma_context_get_backend_state__pipewire(ma_context* pContext) { - switch (position) + return (ma_context_state_pipewire*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_pipewire* ma_device_get_backend_state__pipewire(ma_device* pDevice) +{ + return (ma_device_state_pipewire*)ma_device_get_backend_state(pDevice); +} + + +static ma_result ma_device_step__pipewire(ma_device* pDevice, ma_blocking_mode blockingMode); + + +static void ma_backend_info__pipewire(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "PipeWire"; +} + +#ifndef MA_NO_RUNTIME_LINKING + #define MA_PIPEWIRE_SYM(functionName) \ + pContextStatePipeWire->functionName = (ma_##functionName##_proc)ma_dlsym(pLog, hPipeWire, #functionName); \ + if (pContextStatePipeWire->functionName == NULL) { \ + ma_dlclose(pLog, hPipeWire); \ + ma_free(pContextStatePipeWire, ma_context_get_allocation_callbacks(pContext)); \ + return MA_NO_BACKEND; \ + } +#else + #define MA_PIPEWIRE_SYM(functionName) \ + pContextStatePipeWire->functionName = pContextStatePipeWire->functionName; +#endif + +static ma_result ma_context_init__pipewire(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + /* We'll use a list of possible shared object names for easier extensibility. */ + ma_context_state_pipewire* pContextStatePipeWire; + ma_context_config_pipewire* pContextConfigPipeWire = (ma_context_config_pipewire*)pContextBackendConfig; + ma_log* pLog = ma_context_get_log(pContext); +#ifndef MA_NO_RUNTIME_LINKING + ma_handle hPipeWire; + size_t iName; + const char* pSONames[] = { + "libpipewire-0.3.so.0", + "libpipewire.so" + }; +#endif + + (void)pContextConfigPipeWire; + + pContextStatePipeWire = (ma_context_state_pipewire*)ma_calloc(sizeof(*pContextStatePipeWire), ma_context_get_allocation_callbacks(pContext)); + if (pContextStatePipeWire == NULL) { + return MA_OUT_OF_MEMORY; + } + + pContextStatePipeWire->pLog = pLog; + + /* Check if we have a PipeWire SO. If we can't find this we need to abort. */ + #ifndef MA_NO_RUNTIME_LINKING { - case MA_CHANNEL_NONE: return MA_PA_CHANNEL_POSITION_INVALID; - case MA_CHANNEL_FRONT_LEFT: return MA_PA_CHANNEL_POSITION_FRONT_LEFT; - case MA_CHANNEL_FRONT_RIGHT: return MA_PA_CHANNEL_POSITION_FRONT_RIGHT; - case MA_CHANNEL_FRONT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_CENTER; - case MA_CHANNEL_LFE: return MA_PA_CHANNEL_POSITION_LFE; - case MA_CHANNEL_BACK_LEFT: return MA_PA_CHANNEL_POSITION_REAR_LEFT; - case MA_CHANNEL_BACK_RIGHT: return MA_PA_CHANNEL_POSITION_REAR_RIGHT; - case MA_CHANNEL_FRONT_LEFT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; - case MA_CHANNEL_FRONT_RIGHT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; - case MA_CHANNEL_BACK_CENTER: return MA_PA_CHANNEL_POSITION_REAR_CENTER; - case MA_CHANNEL_SIDE_LEFT: return MA_PA_CHANNEL_POSITION_SIDE_LEFT; - case MA_CHANNEL_SIDE_RIGHT: return MA_PA_CHANNEL_POSITION_SIDE_RIGHT; - case MA_CHANNEL_TOP_CENTER: return MA_PA_CHANNEL_POSITION_TOP_CENTER; - case MA_CHANNEL_TOP_FRONT_LEFT: return MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT; - case MA_CHANNEL_TOP_FRONT_CENTER: return MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER; - case MA_CHANNEL_TOP_FRONT_RIGHT: return MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; - case MA_CHANNEL_TOP_BACK_LEFT: return MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT; - case MA_CHANNEL_TOP_BACK_CENTER: return MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER; - case MA_CHANNEL_TOP_BACK_RIGHT: return MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT; - case MA_CHANNEL_19: return MA_PA_CHANNEL_POSITION_AUX18; - case MA_CHANNEL_20: return MA_PA_CHANNEL_POSITION_AUX19; - case MA_CHANNEL_21: return MA_PA_CHANNEL_POSITION_AUX20; - case MA_CHANNEL_22: return MA_PA_CHANNEL_POSITION_AUX21; - case MA_CHANNEL_23: return MA_PA_CHANNEL_POSITION_AUX22; - case MA_CHANNEL_24: return MA_PA_CHANNEL_POSITION_AUX23; - case MA_CHANNEL_25: return MA_PA_CHANNEL_POSITION_AUX24; - case MA_CHANNEL_26: return MA_PA_CHANNEL_POSITION_AUX25; - case MA_CHANNEL_27: return MA_PA_CHANNEL_POSITION_AUX26; - case MA_CHANNEL_28: return MA_PA_CHANNEL_POSITION_AUX27; - case MA_CHANNEL_29: return MA_PA_CHANNEL_POSITION_AUX28; - case MA_CHANNEL_30: return MA_PA_CHANNEL_POSITION_AUX29; - case MA_CHANNEL_31: return MA_PA_CHANNEL_POSITION_AUX30; - case MA_CHANNEL_32: return MA_PA_CHANNEL_POSITION_AUX31; - default: return (ma_pa_channel_position_t)position; + for (iName = 0; iName < ma_countof(pSONames); iName += 1) { + hPipeWire = ma_dlopen(pLog, pSONames[iName]); + if (hPipeWire != NULL) { + break; + } + } + + if (hPipeWire == NULL) { + ma_free(pContextStatePipeWire, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; /* PipeWire could not be loaded. */ + } + + pContextStatePipeWire->hPipeWire = hPipeWire; + } + #endif + + MA_PIPEWIRE_SYM(pw_init) + MA_PIPEWIRE_SYM(pw_deinit) + MA_PIPEWIRE_SYM(pw_loop_new) + MA_PIPEWIRE_SYM(pw_loop_destroy) + MA_PIPEWIRE_SYM(pw_loop_set_name) + MA_PIPEWIRE_SYM(pw_loop_enter) + MA_PIPEWIRE_SYM(pw_loop_leave) + MA_PIPEWIRE_SYM(pw_loop_iterate) + MA_PIPEWIRE_SYM(pw_loop_add_event) + MA_PIPEWIRE_SYM(pw_loop_signal_event) + MA_PIPEWIRE_SYM(pw_thread_loop_new) + MA_PIPEWIRE_SYM(pw_thread_loop_destroy) + MA_PIPEWIRE_SYM(pw_thread_loop_get_loop) + MA_PIPEWIRE_SYM(pw_thread_loop_start) + MA_PIPEWIRE_SYM(pw_thread_loop_lock) + MA_PIPEWIRE_SYM(pw_thread_loop_unlock) + MA_PIPEWIRE_SYM(pw_context_new) + MA_PIPEWIRE_SYM(pw_context_destroy) + MA_PIPEWIRE_SYM(pw_context_connect) + MA_PIPEWIRE_SYM(pw_core_disconnect) + MA_PIPEWIRE_SYM(pw_core_add_listener) + MA_PIPEWIRE_SYM(pw_core_get_registry) + MA_PIPEWIRE_SYM(pw_core_sync) + MA_PIPEWIRE_SYM(pw_registry_add_listener) + MA_PIPEWIRE_SYM(pw_registry_bind) + MA_PIPEWIRE_SYM(pw_proxy_destroy) + MA_PIPEWIRE_SYM(pw_properties_new) + MA_PIPEWIRE_SYM(pw_properties_free) + MA_PIPEWIRE_SYM(pw_properties_set) + MA_PIPEWIRE_SYM(pw_stream_new) + MA_PIPEWIRE_SYM(pw_stream_destroy) + MA_PIPEWIRE_SYM(pw_stream_add_listener) + MA_PIPEWIRE_SYM(pw_stream_connect) + MA_PIPEWIRE_SYM(pw_stream_dequeue_buffer) + MA_PIPEWIRE_SYM(pw_stream_queue_buffer) + MA_PIPEWIRE_SYM(pw_stream_update_params) + MA_PIPEWIRE_SYM(pw_stream_update_properties) + MA_PIPEWIRE_SYM(pw_stream_get_time_n) + + /* + Just an annoying detail here for functions that use `bool`. For C89 compatibility we don't actually use `bool`, but + instead `unsigned char`. This requires an explicit cast. We'll just do a hack here to work around it. We could update + `MA_PIPEWIRE_SYM` to do a cast in the general case, but I want the compile time linking path to catch any potential + API and ABI compatibility errors in our own function declarations. + */ + #ifndef MA_NO_RUNTIME_LINKING + { + MA_PIPEWIRE_SYM(pw_stream_set_active) + } + #else + { + pContextStatePipeWire->pw_stream_set_active = (ma_pw_stream_set_active_proc)(void*)pw_stream_set_active; + } + #endif + + + if (pContextStatePipeWire->pw_init != NULL) { + pContextStatePipeWire->pw_init(NULL, NULL); + } + + *ppContextState = pContextStatePipeWire; + + return MA_SUCCESS; +} + +static void ma_context_uninit__pipewire(ma_context* pContext) +{ + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(pContext); + + MA_ASSERT(pContextStatePipeWire != NULL); + + if (pContextStatePipeWire->pw_deinit != NULL) { + pContextStatePipeWire->pw_deinit(); + } + + /* Close the handle to the PipeWire shared object last. */ + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(ma_context_get_log(pContext), pContextStatePipeWire->hPipeWire); + pContextStatePipeWire->hPipeWire = NULL; + } + #endif + + ma_free(pContextStatePipeWire, ma_context_get_allocation_callbacks(pContext)); +} + + +static ma_device_enumeration_result ma_context_enumerate_default_device_by_type__pipewire(ma_context* pContext, ma_device_type deviceType, ma_uint32 sampleRate, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_device_info deviceInfo; + ma_uint32 minSampleRate; + ma_uint32 maxSampleRate; + + (void)pContext; + + if (sampleRate != 0) { + minSampleRate = sampleRate; + maxSampleRate = sampleRate; + } else { + minSampleRate = ma_standard_sample_rate_min; + maxSampleRate = ma_standard_sample_rate_max; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* Default. */ + deviceInfo.isDefault = MA_TRUE; + + /* ID. */ + deviceInfo.id.custom.i = 0; + + /* Name. */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1); + } else { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Capture Device", (size_t)-1); + } + + /* Data Format. */ + ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 64, minSampleRate, maxSampleRate); + + return callback(deviceType, &deviceInfo, pUserData); +} + + + +#define MA_PW_CORE_SYNC_FLAG_ENUM_DONE (1 << 0) +#define MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE (1 << 1) +#define MA_PW_CORE_SYNC_FLAG_SETTINGS_DONE (1 << 2) + +typedef struct +{ + ma_device_info info; + ma_uint32 pwID; +} ma_enumerate_devices_entry_pipewire; + +typedef struct +{ + ma_context_state_pipewire* pContextStatePipeWire; + struct ma_pw_loop* pLoop; + struct ma_pw_core* pCore; + struct ma_pw_registry* pRegistry; + struct ma_pw_metadata* pMetadataDefault; /* "default" metadata. */ + struct ma_pw_metadata* pMetadataSettings; /* "settings" metadata. */ + struct ma_spa_hook metadataListenerDefault; /* "default" metadata */ + struct ma_spa_hook metadataListenerSettings; /* "settings" metadata. */ + int seqDefaults; + int seqSettings; + int seqEnumeration; + ma_uint32 syncFlags; + ma_uint32 clockRate; /* "clock.rate" from the "settings" metadata. */ + const ma_allocation_callbacks* pAllocationCallbacks; + struct + { + ma_device_id defaultDeviceID; + ma_enumerate_devices_entry_pipewire* pDevices; + size_t deviceInfoCount; + size_t deviceInfoCap; + } playback, capture; + ma_bool32 isAborted; /* We can't seem to be able to abort enumeration with PipeWire, so we'll just set a flag to indicate it and then ignore anything that comes after. */ +} ma_enumerate_devices_data_pipewire; + + +static void ma_on_core_done__pipewire(void* pUserData, ma_uint32 id, int seq) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)pUserData; + + (void)id; + (void)seq; + + /*printf("Core Done: ID=%u, Seq=%d\n", id, seq);*/ + if (pEnumData->seqEnumeration == seq) { + pEnumData->syncFlags |= MA_PW_CORE_SYNC_FLAG_ENUM_DONE; + } else if (pEnumData->seqDefaults == seq) { + pEnumData->syncFlags |= MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE; + } +} + +static const struct ma_pw_core_events ma_gCoreEventsPipeWire = +{ + MA_PW_VERSION_CORE_EVENTS, + NULL, + ma_on_core_done__pipewire, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + + +static void ma_enumerate_devices_data_pipewire_init(ma_enumerate_devices_data_pipewire* pEnumData, ma_context_state_pipewire* pContextStatePipeWire, struct ma_pw_loop* pLoop, struct ma_pw_core* pCore, struct ma_pw_registry* pRegistry, const ma_allocation_callbacks* pAllocationCallbacks) +{ + MA_ZERO_OBJECT(pEnumData); + pEnumData->pContextStatePipeWire = pContextStatePipeWire; + pEnumData->pLoop = pLoop; + pEnumData->pCore = pCore; + pEnumData->pRegistry = pRegistry; + pEnumData->pAllocationCallbacks = pAllocationCallbacks; +} + +static void ma_enumerate_devices_data_pipewire_uninit(ma_enumerate_devices_data_pipewire* pEnumData) +{ + if (pEnumData->pMetadataDefault != NULL) { + pEnumData->pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pEnumData->pMetadataDefault); + } + if (pEnumData->pMetadataSettings != NULL) { + pEnumData->pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pEnumData->pMetadataSettings); + } + + ma_free(pEnumData->playback.pDevices, pEnumData->pAllocationCallbacks); + ma_free(pEnumData->capture.pDevices, pEnumData->pAllocationCallbacks); +} + +static ma_result ma_enumerate_devices_data_pipewire_add(ma_enumerate_devices_data_pipewire* pEnumData, ma_device_type deviceType, ma_uint32 pwID, const ma_device_info* pDeviceInfo) +{ + size_t* pDeviceCount; + size_t* pDeviceCap; + ma_enumerate_devices_entry_pipewire** ppDevices; + + if (deviceType == ma_device_type_playback) { + pDeviceCount = &pEnumData->playback.deviceInfoCount; + pDeviceCap = &pEnumData->playback.deviceInfoCap; + ppDevices = &pEnumData->playback.pDevices; + } else { + pDeviceCount = &pEnumData->capture.deviceInfoCount; + pDeviceCap = &pEnumData->capture.deviceInfoCap; + ppDevices = &pEnumData->capture.pDevices; + } + + if (*pDeviceCount + 1 > *pDeviceCap) { + size_t newCap = *pDeviceCap * 2; + ma_enumerate_devices_entry_pipewire* pNewDevices; + + if (newCap == 0) { + newCap = 8; + } + + pNewDevices = (ma_enumerate_devices_entry_pipewire*)ma_realloc(*ppDevices, newCap * sizeof(*pNewDevices), pEnumData->pAllocationCallbacks); + if (pNewDevices == NULL) { + return MA_OUT_OF_MEMORY; + } + + *ppDevices = pNewDevices; + *pDeviceCap = newCap; + } + + ((*ppDevices)[*pDeviceCount]).info = *pDeviceInfo; + ((*ppDevices)[*pDeviceCount]).pwID = pwID; + *pDeviceCount += 1; + + return MA_SUCCESS; +} + + + +static int ma_on_metadata_property_default__pipewire(void* data, ma_uint32 subject, const char* key, const char* type, const char* value) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)data; + + (void)subject; + (void)type; + + if (key == NULL) { + return 0; + } + + /* + Well this is fun. To get the default device we need to get the value of the "default.audio.sink" and "default.audio.source" keys. Sounds + simple enough, except that the value is actually JSON... Why is the default device stored as a JSON string? Who does this? We're just + going to use a simplified parser that finds the first ":\"" and takes everything until the next "\"". + */ + if (strcmp(key, "default.audio.sink") == 0 || strcmp(key, "default.audio.source") == 0) { + const char* pValue = value; + const char* pStart; + const char* pEnd; + ma_device_id* pDefaultDeviceID; + + if (strcmp(key, "default.audio.sink") == 0) { + pDefaultDeviceID = &pEnumData->playback.defaultDeviceID; + } else { + pDefaultDeviceID = &pEnumData->capture.defaultDeviceID; + } + + pStart = strstr(pValue, ":\""); + if (pStart != NULL) { + pStart += 2; /* Move past the :". */ + pEnd = strchr(pStart, '\"'); + if (pEnd != NULL) { + size_t len = (size_t)(pEnd - pStart); + if (len >= sizeof(pDefaultDeviceID->custom.s)) { + len = sizeof(pDefaultDeviceID->custom.s) - 1; + } + + ma_strncpy_s(pDefaultDeviceID->custom.s, sizeof(pDefaultDeviceID->custom.s), pStart, len); + } + } + } + + /*printf("Metadata Property: Subject=%u, Key=%s, Type=%s, Value=%s\n", subject, key, type, value);*/ + return 0; +} + +static struct ma_pw_metadata_events ma_gMetadataEventsPipeWire_Default = +{ + MA_PW_VERSION_METADATA_EVENTS, + ma_on_metadata_property_default__pipewire +}; + + +static int ma_on_metadata_property_settings__pipewire(void* data, ma_uint32 subject, const char* key, const char* type, const char* value) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)data; + + (void)pEnumData; + (void)subject; + (void)type; + + if (key == NULL) { + return 0; + } + + if (strcmp(key, "clock.rate") == 0) { + pEnumData->clockRate = (ma_uint32)atoi(value); + } + + /*printf("Metadata Property: Subject=%u, Key=%s, Type=%s, Value=%s\n", subject, key, type, value);*/ + return 0; +} + +static struct ma_pw_metadata_events ma_gMetadataEventsPipeWire_Settings = +{ + MA_PW_VERSION_METADATA_EVENTS, + ma_on_metadata_property_settings__pipewire +}; + + +typedef struct +{ + enum ma_spa_audio_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + const ma_uint32* pChannelPositions; +} ma_pipewire_audio_info; + +static void ma_pipewire_audio_info_parse(const struct ma_spa_pod* pParam, ma_log* pLog, ma_pipewire_audio_info* pAudioInfo) +{ + struct ma_spa_pod_object* pObject; + struct ma_spa_pod_prop* pNextProp; + ma_uint8* ptr = (ma_uint8*)pParam; + ma_uint32 cursor = 0; + ma_uint32 size; + + MA_ZERO_OBJECT(pAudioInfo); + + if (pParam == NULL) { + return; + } + + if (pParam->type != MA_SPA_TYPE_Object || pParam->size < sizeof(struct ma_spa_pod_object_body)) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid pod type)."); + return; + } + + pObject = (struct ma_spa_pod_object*)pParam; + + /* The size of the pod does not include the header which makes parsing kind of annoying for us. We'll add it here. */ + size = pParam->size + sizeof(struct ma_spa_pod); + cursor += sizeof(struct ma_spa_pod); + + if (pObject->body.type != MA_SPA_TYPE_OBJECT_Format) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid body type)."); + return; + } + + cursor += sizeof(struct ma_spa_pod_object_body); + + /* At this point we have parsed the header part of the object, and what follows now should be a list of properties. */ + while (cursor < size) { + struct ma_spa_pod* pPropValue; + + pNextProp = (struct ma_spa_pod_prop*)(ptr + cursor); + if (cursor + sizeof(struct ma_spa_pod_prop) > size) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid size for property)."); + return; + } + + /* Place the cursor on the prop value, which is a spa_pod. */ + cursor += 8; /* Size of the key and flags. */ + pPropValue = (struct ma_spa_pod*)(ptr + cursor); + + switch (pNextProp->key) + { + case MA_SPA_FORMAT_AUDIO_format: + { + pAudioInfo->format = (enum ma_spa_audio_format)ma_spa_pod_get_id_value(pPropValue); + } break; + + case MA_SPA_FORMAT_AUDIO_channels: + { + pAudioInfo->channels = ma_spa_pod_get_int_value(pPropValue); + } break; + + case MA_SPA_FORMAT_AUDIO_rate: + { + pAudioInfo->sampleRate = ma_spa_pod_get_int_value(pPropValue); + } break; + + case MA_SPA_FORMAT_AUDIO_position: + { + /* I'm assuming we can only get an array back for this. */ + if (pPropValue->type != MA_SPA_TYPE_Array) { + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "Failed to parse PipeWire format parameter (invalid type for position property)."); + return; + } + + pAudioInfo->pChannelPositions = (const ma_uint32*)ma_spa_pod_array_get_values((const struct ma_spa_pod_array*)pPropValue); + } break; + } + + cursor += ma_align_64(8 + pPropValue->size); /* Size of the value pod header + the size of the pod itself. */ + } +} + + +/* +NOTE + +The commented out code below was my first attempt to extract the "native" channel count and sample rate of +a device. Essentially what it's doing is probing each device by creating stream, connecting it, and then +querying the chosen channels and rate. This was a complete fail because there's what I consider to be a bug +somewhere in the PipeWire/ALSA pipeline. I have S/PDIF device with no device actually physically connected +to it. When I probe that device, the system volume for the entire operating system will suddenly go full +blast. This is clearly not something that can be shipped so I've had to comment out this code. I'm leaving +it here more as a reference and a reminder. + +This can be replicated from the OS itself without needing to run a program. In my case, if I switch the +default device to the S/PDIF device, and then back to my headphones, everything goes full volume and your +ears will get blown out. +*/ +#if 0 +typedef struct +{ + ma_log* pLog; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_bool32 done; +} ma_stream_event_param_changed_enumeration_data__pipewire; + +static void ma_stream_event_param_changed_enumeration__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) +{ + ma_stream_event_param_changed_enumeration_data__pipewire* pData = (ma_stream_event_param_changed_enumeration_data__pipewire*)pUserData; + + if (pParam == NULL) { + return; + } + + if (id == MA_SPA_PARAM_Format) { + ma_pipewire_audio_info audioInfo; + ma_pipewire_audio_info_parse(pParam, pData->pLog, &audioInfo); + + pData->channels = audioInfo.channels; + pData->sampleRate = audioInfo.sampleRate; + + pData->done = MA_TRUE; + } +} + +static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Enumeration = +{ + MA_PW_VERSION_STREAM_EVENTS, + NULL, /* destroy */ + NULL, /* state_changed */ + NULL, /* control_info */ + NULL, /* io_changed */ + ma_stream_event_param_changed_enumeration__pipewire, + NULL, /* add_buffer */ + NULL, /* remove_buffer */ + NULL, /* process */ + NULL, /* drained */ + NULL, /* command */ + NULL, /* trigger_done */ +}; + +static void ma_add_native_data_format__pipewire(ma_context_state_pipewire* pContextStatePipeWire, struct ma_pw_core* pCore, struct ma_pw_loop* pLoop, ma_device_type deviceType, ma_device_info* pDeviceInfo) +{ + struct ma_pw_properties* pProperties; + struct ma_pw_stream* pStream; + const char* pNodeName = NULL; + + MA_ASSERT(pDeviceInfo != NULL); + + pNodeName = pDeviceInfo->id.custom.s; + + pProperties = pContextStatePipeWire->pw_properties_new( + MA_PW_KEY_MEDIA_TYPE, "Audio", + MA_PW_KEY_MEDIA_CATEGORY, (deviceType == ma_device_type_playback) ? "Playback" : "Capture", + MA_PW_KEY_MEDIA_ROLE, "Game", + MA_PW_KEY_TARGET_OBJECT, pNodeName, + NULL); + + pStream = pContextStatePipeWire->pw_stream_new(pCore, "miniaudio-enumeration", pProperties); + if (pStream != NULL) { + ma_stream_event_param_changed_enumeration_data__pipewire eventsData; + struct ma_spa_hook eventListener; + enum ma_spa_audio_format formatPA; + struct ma_spa_pod_audio_info_raw podAudioInfo; + const struct ma_spa_pod* pConnectionParameters[1]; + enum ma_pw_stream_flags streamFlags; + int connectResult; + + /* The way to determine the "native" channel count and sample rate is by handling the SPA_PARAM_Format event in the params_changed callback. */ + MA_ZERO_OBJECT(&eventsData); + eventsData.pLog = pContextStatePipeWire->pLog; + + pContextStatePipeWire->pw_stream_add_listener(pStream, &eventListener, &ma_gStreamEventsPipeWire_Enumeration, &eventsData); + + + /* Now we can connect the stream. */ + if (ma_is_little_endian()) { + formatPA = MA_SPA_AUDIO_FORMAT_F32_LE; + } else { + formatPA = MA_SPA_AUDIO_FORMAT_F32_BE; + } + + podAudioInfo = ma_spa_pod_audio_info_raw_init(formatPA, 0, 0); + pConnectionParameters[0] = (struct ma_spa_pod*)&podAudioInfo; + + streamFlags = (enum ma_pw_stream_flags)(MA_PW_STREAM_FLAG_AUTOCONNECT); + connectResult = pContextStatePipeWire->pw_stream_connect(pStream, (deviceType == ma_device_type_playback) ? MA_SPA_DIRECTION_OUTPUT : MA_SPA_DIRECTION_INPUT, MA_PW_ID_ANY, streamFlags, pConnectionParameters, ma_countof(pConnectionParameters)); + if (connectResult >= 0) { + /* Keep looping until we've processed our channel count and sample rate. */ + while (!eventsData.done) { + pContextStatePipeWire->pw_loop_iterate(pLoop, 1); + } + + if (eventsData.channels != 0 && eventsData.sampleRate != 0) { + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_f32, eventsData.channels, eventsData.channels, eventsData.sampleRate, eventsData.sampleRate); + } + } else { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName); + } + + /* We're done with the stream. */ + pContextStatePipeWire->pw_stream_destroy(pStream); + } else { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_ERROR, "Failed to create PipeWire stream for enumeration. Device ID: \"%s\".", pNodeName); + } + + /* + In the logic above we chose the "native" format that PipeWire would pick if no channel count or sample rate is + specified during device initialization. However, PipeWire will actually do it's own data conversion, so from the + perspective of miniaudio, it also "natively" supports any format, channel count and sample rate. I'm not sure if + it's better to include these entries of if we should just leave it set to the one format. + */ + #if 0 + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_s16, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_s32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_s24, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_u8, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + #endif + + /* Just in case something went wrong in the logic above, if we never extracted a native format we'll add a fallback. */ + if (pDeviceInfo->nativeDataFormatCount == 0) { + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_f32, 1, 64, ma_standard_sample_rate_min, ma_standard_sample_rate_max); } } #endif -static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP) +static void ma_registry_event_global_add_enumeration_by_type__pipewire(ma_enumerate_devices_data_pipewire* pEnumData, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props, ma_device_type deviceType) { - int resultPA; - ma_pa_operation_state_t state; + ma_device_info deviceInfo; + const char* pNodeName; /* <-- This is the ID. */ + const char* pNiceName; + + (void)permissions; + (void)version; + (void)type; + + /* The node name is the ID. */ + pNodeName = ma_spa_dict_lookup(props, "node.name"); + + /* Name. */ + pNiceName = ma_spa_dict_lookup(props, "node.description"); + if (pNiceName == NULL) { pNiceName = ma_spa_dict_lookup(props, "device.description"); } + if (pNiceName == NULL) { pNiceName = ma_spa_dict_lookup(props, "device.nick"); } + if (pNiceName == NULL) { pNiceName = pNodeName; } + if (pNiceName == NULL) { pNiceName = "Unknown"; } + + /* Fill out the device info structure. */ + MA_ZERO_OBJECT(&deviceInfo); + + /* The default flag is set later in a second pass. */ + + /* ID. */ + ma_strncpy_s(deviceInfo.id.pipewire, sizeof(deviceInfo.id.pipewire), pNodeName, (size_t)-1); + + /* Name. */ + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pNiceName, (size_t)-1); + + /* The native data format is set later in a second pass. */ + + + /* Finally we can add the device into to our internal list. */ + ma_enumerate_devices_data_pipewire_add(pEnumData, deviceType, id, &deviceInfo); + + /*printf("Registry Global Added By Type: ID=%u, Type=%s, DeviceType=%d, NiceName=%s\n", id, type, deviceType, pNiceName);*/ +} + +static void ma_registry_event_global_add_enumeration__pipewire(void* pUserData, ma_uint32 id, ma_uint32 permissions, const char* type, ma_uint32 version, const struct ma_spa_dict* props) +{ + ma_enumerate_devices_data_pipewire* pEnumData = (ma_enumerate_devices_data_pipewire*)pUserData; + const char* pMediaClass; + + /* Ignore all future iterations if we have aborted. */ + if (pEnumData->isAborted) { + return; + } + + /* Some device info needs to be retrieved from metadata. */ + if (strcmp(type, MA_PW_TYPE_INTERFACE_Metadata) == 0) { + const char* pName; + + pName = ma_spa_dict_lookup(props, MA_PW_KEY_METADATA_NAME); + if (pName != NULL) { + /* Default devices. */ + if (strcmp(pName, "default") == 0) { + pEnumData->pMetadataDefault = (struct ma_pw_metadata*)pEnumData->pContextStatePipeWire->pw_registry_bind(pEnumData->pRegistry, id, MA_PW_TYPE_INTERFACE_Metadata, MA_PW_VERSION_METADATA, 0); + if (pEnumData->pMetadataDefault != NULL) { + MA_ZERO_OBJECT(&pEnumData->metadataListenerDefault); + + /* Not using pw_metadata_add_listener() because it appears to be an inline function and thus not exported by libpipewire. */ + ((struct ma_pw_metadata_methods*)((struct ma_spa_interface*)pEnumData->pMetadataDefault)->cb.funcs)->add_listener(pEnumData->pMetadataDefault, &pEnumData->metadataListenerDefault, &ma_gMetadataEventsPipeWire_Default, pEnumData); + + /*spa_api_method_r(int, -ENOTSUP, ma_pw_metadata, (struct spa_interface*)pEnumData->pMetadata, add_listener, 0, &pEnumData->metadataListener, &ma_gMetadataEventsPipeWire, pEnumData);*/ + /*pEnumData->pContextStatePipeWire->pw_metadata_add_listener(pMetadata, &pEnumData->metadataListener, &ma_gMetadataEventsPipeWire, NULL);*/ + + pEnumData->seqDefaults = pEnumData->pContextStatePipeWire->pw_core_sync(pEnumData->pCore, MA_PW_ID_CORE, 0); + } + } + + /* Sample rate. */ + if (strcmp(pName, "settings") == 0) { + pEnumData->pMetadataSettings = (struct ma_pw_metadata*)pEnumData->pContextStatePipeWire->pw_registry_bind(pEnumData->pRegistry, id, MA_PW_TYPE_INTERFACE_Metadata, MA_PW_VERSION_METADATA, 0); + if (pEnumData->pMetadataSettings != NULL) { + MA_ZERO_OBJECT(&pEnumData->metadataListenerSettings); + ((struct ma_pw_metadata_methods*)((struct ma_spa_interface*)pEnumData->pMetadataSettings)->cb.funcs)->add_listener(pEnumData->pMetadataSettings, &pEnumData->metadataListenerSettings, &ma_gMetadataEventsPipeWire_Settings, pEnumData); + + pEnumData->seqSettings = pEnumData->pContextStatePipeWire->pw_core_sync(pEnumData->pCore, MA_PW_ID_CORE, 0); + } + } + } + + + return; + } + + /* From here on out we only care about nodes. */ + if (strcmp(type, MA_PW_TYPE_INTERFACE_Node) != 0) { + return; + } + + pMediaClass = ma_spa_dict_lookup(props, MA_PW_KEY_MEDIA_CLASS); + if (pMediaClass == NULL) { + return; + } + + /* If the string contains Audio/Sink or Audio/Source we can assume it's an enumerable node. */ + /* */ if (strcmp(pMediaClass, "Audio/Sink") == 0) { + ma_registry_event_global_add_enumeration_by_type__pipewire(pEnumData, id, permissions, type, version, props, ma_device_type_playback); + } else if (strcmp(pMediaClass, "Audio/Source") == 0) { + ma_registry_event_global_add_enumeration_by_type__pipewire(pEnumData, id, permissions, type, version, props, ma_device_type_capture); + } else { + return; /* Not an audio node we care about. */ + } +} + +static const struct ma_pw_registry_events ma_gRegistryEventsPipeWire_Enumeration = +{ + MA_PW_VERSION_REGISTRY_EVENTS, + ma_registry_event_global_add_enumeration__pipewire, + NULL +}; + + +static ma_result ma_context_enumerate_devices__pipewire(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(pContext); + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; + struct ma_pw_loop* pLoop; + struct ma_pw_context* pPipeWireContext; + struct ma_pw_core* pCore; + struct ma_pw_registry* pRegistry; + struct ma_spa_hook coreListener; + struct ma_spa_hook registeryListener; + ma_enumerate_devices_data_pipewire enumData; + + MA_ASSERT(pContextStatePipeWire != NULL); + MA_ASSERT(callback != NULL); + + pLoop = pContextStatePipeWire->pw_loop_new(NULL); + if (pLoop == NULL) { + return MA_ERROR; + } + + pPipeWireContext = pContextStatePipeWire->pw_context_new(pLoop, NULL, 0); + if (pPipeWireContext == NULL) { + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + pCore = pContextStatePipeWire->pw_context_connect(pPipeWireContext, NULL, 0); + if (pCore == NULL) { + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + pContextStatePipeWire->pw_core_add_listener(pCore, &coreListener, &ma_gCoreEventsPipeWire, &enumData); + + pRegistry = pContextStatePipeWire->pw_core_get_registry(pCore, MA_PW_VERSION_REGISTRY, 0); + if (pRegistry == NULL) { + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + ma_enumerate_devices_data_pipewire_init(&enumData, pContextStatePipeWire, pLoop, pCore, pRegistry, ma_context_get_allocation_callbacks(pContext)); + + MA_ZERO_OBJECT(®isteryListener); + pContextStatePipeWire->pw_registry_add_listener(pRegistry, ®isteryListener, &ma_gRegistryEventsPipeWire_Enumeration, &enumData); + + /* + The pw_core_sync() function is extremely confusing. The documentation says this: + + Ask the server to emit the 'done' event with seq. + + The last parameter of pw_core_sync() is `seq`, and in the `done` callback, there is a parameter called `seq`. The documentation + makes it sound like the `seq` argument of the `done` callback will be set to what you specify in the `pw_core_sync()` call, but + this is not the case. The `seq` argument in the `done` callback will actually be the return value of `pw_core_sync()`. If there + is a PipeWire developer reading this, this documentation needs to be addressed. Feedback welcome if I'm misunderstanding or just + doing something wrong here. + */ + enumData.seqEnumeration = pContextStatePipeWire->pw_core_sync(pCore, MA_PW_ID_CORE, 0); + for (;;) { + pContextStatePipeWire->pw_loop_iterate(pLoop, -1); + + if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_ENUM_DONE) != 0) { + if (enumData.seqDefaults == 0) { + break; /* We don't have a "default" metadata. */ + } + if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_DEFAULTS_DONE) != 0) { + break; + } + + if (enumData.seqSettings == 0) { + break; /* We don't have a "settings" metadata. */ + } + if ((enumData.syncFlags & MA_PW_CORE_SYNC_FLAG_SETTINGS_DONE) != 0) { + break; + } + } + } + + + /* Here is where we iterate over each device and fire the callback. */ + { + size_t iDevice; + ma_bool32 hasDefaultPlaybackDevice = MA_FALSE; + ma_bool32 hasDefaultCaptureDevice = MA_FALSE; + ma_uint32 minChannels = 1; + ma_uint32 maxChannels = 64; + ma_uint32 minSampleRate; + ma_uint32 maxSampleRate; + + if (enumData.clockRate != 0) { + minSampleRate = enumData.clockRate; + maxSampleRate = enumData.clockRate; + } else { + minSampleRate = ma_standard_sample_rate_min; + maxSampleRate = ma_standard_sample_rate_max; + } + + /* Playback devices. */ + for (iDevice = 0; iDevice < enumData.playback.deviceInfoCount; iDevice += 1) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + if (enumData.playback.defaultDeviceID.custom.s[0] != '\0' && strcmp(enumData.playback.pDevices[iDevice].info.id.custom.s, enumData.playback.defaultDeviceID.custom.s) == 0) { + enumData.playback.pDevices[iDevice].info.isDefault = MA_TRUE; + hasDefaultPlaybackDevice = MA_TRUE; + } + + /* Now we need to open the stream and get it's native data format. */ + ma_device_info_add_native_data_format(&enumData.playback.pDevices[iDevice].info, ma_format_f32, minChannels, maxChannels, minSampleRate, maxSampleRate); + + cbResult = callback(ma_device_type_playback, &enumData.playback.pDevices[iDevice].info, pUserData); + } + } + + if (enumData.playback.deviceInfoCount > 0 && !hasDefaultPlaybackDevice) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_default_device_by_type__pipewire(pContext, ma_device_type_playback, enumData.clockRate, callback, pUserData); + } + } + + + /* Capture devices. */ + for (iDevice = 0; iDevice < enumData.capture.deviceInfoCount; iDevice += 1) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + if (enumData.capture.defaultDeviceID.custom.s[0] != '\0' && strcmp(enumData.capture.pDevices[iDevice].info.id.custom.s, enumData.capture.defaultDeviceID.custom.s) == 0) { + enumData.capture.pDevices[iDevice].info.isDefault = MA_TRUE; + hasDefaultCaptureDevice = MA_TRUE; + } + + /* Now we need to open the stream and get it's native data format. */ + ma_device_info_add_native_data_format(&enumData.capture.pDevices[iDevice].info, ma_format_f32, minChannels, maxChannels, minSampleRate, maxSampleRate); + + cbResult = callback(ma_device_type_capture, &enumData.capture.pDevices[iDevice].info, pUserData); + } + } + + if (enumData.capture.deviceInfoCount > 0 && !hasDefaultCaptureDevice) { + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_default_device_by_type__pipewire(pContext, ma_device_type_capture, enumData.clockRate, callback, pUserData); + } + } + + (void)cbResult; /* Silence a static analysis warning. Want to keep this assignment in case we extend this logic later. */ + } + + + ma_enumerate_devices_data_pipewire_uninit(&enumData); + pContextStatePipeWire->pw_proxy_destroy((struct ma_pw_proxy*)pRegistry); + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + + return MA_SUCCESS; +} + + +static void ma_stream_event_state_changed__pipewire(void* pUserData, enum ma_pw_stream_state oldState, enum ma_pw_stream_state state, const char* pError, ma_device_type deviceType) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = (ma_device_state_pipewire*)pUserData; + ma_pipewire_stream_state* pStreamState; + + (void)oldState; + (void)pError; + + if (deviceType == ma_device_type_playback) { + pStreamState = &pDeviceStatePipeWire->playback; + } else { + pStreamState = &pDeviceStatePipeWire->capture; + } + + if (state == MA_PW_STREAM_STATE_ERROR) { + pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_ERROR; + } +} + + +static void ma_stream_event_param_changed__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam, ma_device_type deviceType) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = (ma_device_state_pipewire*)pUserData; + ma_context_state_pipewire* pContextStatePipeWire = pDeviceStatePipeWire->pContextStatePipeWire; + + if (id == MA_SPA_PARAM_Format) { + ma_pipewire_stream_state* pStreamState; + struct ma_spa_pod_buffer_params bufferParams; + const struct ma_spa_pod* pBufferParameters[1]; + ma_uint32 bytesPerFrame; + ma_uint32 iChannel; + ma_pipewire_audio_info audioInfo; + + /* It's possible for PipeWire to fire this callback with pParam set to NULL. I noticed it when tearing down a stream. Why does it do this? */ + if (pParam == NULL) { + return; + } + + if (deviceType == ma_device_type_playback) { + pStreamState = &pDeviceStatePipeWire->playback; + } else { + pStreamState = &pDeviceStatePipeWire->capture; + } + + if ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT) != 0) { + ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_WARNING, "PipeWire format parameter changed after device has been initialized."); + return; + } + + /* + We can now determine the format/channels/rate which will let us configure the size of the buffer and set the + internal format of the device. To do this we need to parse the pParam pod. + */ + #if 0 + { + struct spa_audio_info_raw audioInfo; + spa_format_audio_raw_parse(pParam, &audioInfo); + } + #endif + { + ma_pipewire_audio_info_parse(pParam, pContextStatePipeWire->pLog, &audioInfo); + } + + /* Now that we definitely know the sample rate, we can reliably configure the size of the buffer. */ + if (pStreamState->bufferSizeInFrames == 0) { + pStreamState->bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pStreamState->pDescriptor, audioInfo.sampleRate); + } + + pStreamState->format = ma_format_from_pipewire(audioInfo.format); + pStreamState->channels = audioInfo.channels; + pStreamState->sampleRate = audioInfo.sampleRate; + + /* We should always get a channel map, but just to be safe we'll check for it, and if we don't get one back we'll use a default. */ + if (audioInfo.pChannelPositions != NULL) { + for (iChannel = 0; iChannel < audioInfo.channels; iChannel += 1) { + pStreamState->channelMap[iChannel] = ma_channel_from_pipewire(audioInfo.pChannelPositions[iChannel]); + } + } else { + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pStreamState->channelMap, ma_countof(pStreamState->channelMap), audioInfo.channels); + } + + + /* Now that we know both the buffer size and sample rate we can update the latency on the PipeWire side. */ + { + struct ma_spa_dict_item items[1]; + struct ma_spa_dict dict; + char bufferSizeInFramesStr[32]; + char sampleRateStr[32]; + char latencyStr[32]; + + ma_itoa_s(pStreamState->bufferSizeInFrames, bufferSizeInFramesStr, sizeof(bufferSizeInFramesStr), 10); + ma_itoa_s(pStreamState->sampleRate, sampleRateStr, sizeof(sampleRateStr), 10); + + latencyStr[0] = '\0'; + ma_strcat_s(latencyStr, sizeof(latencyStr), bufferSizeInFramesStr); + ma_strcat_s(latencyStr, sizeof(latencyStr), "/"); + ma_strcat_s(latencyStr, sizeof(latencyStr), sampleRateStr); + + items[0] = MA_SPA_DICT_ITEM_INIT(MA_PW_KEY_NODE_LATENCY, latencyStr); + + dict = MA_SPA_DICT_INIT(items, 1); + + pContextStatePipeWire->pw_stream_update_properties(pStreamState->pStream, &dict); + } + + + bytesPerFrame = ma_get_bytes_per_frame(pStreamState->format, pStreamState->channels); + + /* + Now update the PipeWire buffer properties. Note that spa_pod_buffer_params_init() is not a standard SPA function. It's a + custom function to eliminate the dependency on libspa for building miniaudio. + */ + bufferParams = ma_spa_pod_buffer_params_init(bytesPerFrame, pStreamState->bufferSizeInFrames); + pBufferParameters[0] = (struct ma_spa_pod*)&bufferParams; + + pContextStatePipeWire->pw_stream_update_params(pStreamState->pStream, pBufferParameters, sizeof(pBufferParameters) / sizeof(pBufferParameters[0])); + + pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT; + } +} + +static void ma_stream_event_process__pipewire(void* pUserData, ma_device_type deviceType) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = (ma_device_state_pipewire*)pUserData; + ma_context_state_pipewire* pContextStatePipeWire = pDeviceStatePipeWire->pContextStatePipeWire; + ma_pipewire_stream_state* pStreamState; + struct ma_pw_buffer* pBuffer; + ma_uint32 bytesPerFrame; + ma_uint32 frameCount; + + if (deviceType == ma_device_type_playback) { + pStreamState = &pDeviceStatePipeWire->playback; + } else { + pStreamState = &pDeviceStatePipeWire->capture; + } + + /* Debugging stuff. */ + #if 0 + { + double currentTimeInSeconds; + double deltaTimeInSeconds; + + currentTimeInSeconds = ma_timer_get_time_in_seconds(&pDeviceStatePipeWire->debugging.timer); + deltaTimeInSeconds = currentTimeInSeconds - pDeviceStatePipeWire->debugging.lastTimeInSeconds; + pDeviceStatePipeWire->debugging.lastTimeInSeconds = currentTimeInSeconds; + + printf("TRACE: Process Callback %.2f ms\n", deltaTimeInSeconds * 1000.0); + } + #endif + + /* + PipeWire has a bizarre buffer management system. Normally with an audio API you do processing after a certain amount of + time has elapsed, based on the sample rate and buffer size. The frequency at which the processing callback is fired + directly affects latency which is an important metric for audio applications and data management. From what I can tell, + the only way to determine the rate at which this processing callback is fired is from within the callback itself. There + are two ways I'm aware of: + + 1) Dequeue the first buffer and check the `requested` member of `pw_buffer`. + 2) Get the stream time using `pw_stream_get_time_n()` and inspect the `size` member of `pw_time`. + + In capture, the first option cannot be used because `requested` is always zero. That leaves only the second option which + appears to work for both playback and capture. However, the `size` member will only work for the first invocation of the + processing callback because it can change as you enqueue buffers. + + Advice welcome on how to improve this. + */ + if ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY) == 0) { + struct ma_pw_time time; + + pContextStatePipeWire->pw_stream_get_time_n(pStreamState->pStream, &time, sizeof(time)); + + if (pStreamState->rbSizeInFrames > 0) { + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_audio_ring_buffer_uninit(&pStreamState->rb); + } + } + + pStreamState->rbSizeInFrames = (ma_uint32)time.size; + + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_audio_ring_buffer_config ringBufferConfig = ma_audio_ring_buffer_config_init(pStreamState->format, pStreamState->channels, pStreamState->sampleRate, pStreamState->rbSizeInFrames); + ringBufferConfig.pAllocationCallbacks = ma_device_get_allocation_callbacks(pDeviceStatePipeWire->pDevice); + + ma_audio_ring_buffer_init(&ringBufferConfig, &pStreamState->rb); + } + + pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY; + return; + } + + + bytesPerFrame = ma_get_bytes_per_frame(pStreamState->format, pStreamState->channels); + + pBuffer = pContextStatePipeWire->pw_stream_dequeue_buffer(pStreamState->pStream); + if (pBuffer == NULL) { + return; + } + + if (deviceType == ma_device_type_playback) { + frameCount = (ma_uint32)ma_min(pBuffer->requested, pBuffer->buffer->datas[0].maxsize / bytesPerFrame); + /*frameCount = (ma_uint32)(pBuffer->buffer->datas[0].maxsize / bytesPerFrame);*/ + } else { + frameCount = (ma_uint32)(pBuffer->buffer->datas[0].chunk->size / bytesPerFrame); + } + + MA_ASSERT(pBuffer->buffer != NULL); + MA_ASSERT(pBuffer->buffer->n_datas > 0); + MA_ASSERT(pBuffer->buffer->datas[0].data != NULL); + + if (frameCount > 0) { + if (deviceType == ma_device_type_playback) { + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_MULTI_THREADED) { + ma_device_handle_backend_data_callback(pDeviceStatePipeWire->pDevice, pBuffer->buffer->datas[0].data, NULL, frameCount); + } else { + ma_uint32 framesRead; + + ma_audio_ring_buffer_read_pcm_frames(&pStreamState->rb, pBuffer->buffer->datas[0].data, frameCount, &framesRead); + if (framesRead < frameCount) { + /* Underflow. Fill the remaining output with silence. */ + MA_ZERO_MEMORY(ma_offset_ptr(pBuffer->buffer->datas[0].data, framesRead * bytesPerFrame), (frameCount - framesRead) * bytesPerFrame); + } + } + + /*printf("(Playback) Processing %d frames... %d %d\n", (int)frameCount, (int)pBuffer->requested, pBuffer->buffer->datas[0].maxsize / bytesPerFrame);*/ + } else { + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_MULTI_THREADED) { + ma_device_handle_backend_data_callback(pDeviceStatePipeWire->pDevice, NULL, pBuffer->buffer->datas[0].data, frameCount); + } else { + ma_uint32 framesWritten; + + ma_audio_ring_buffer_write_pcm_frames(&pStreamState->rb, pBuffer->buffer->datas[0].data, frameCount, &framesWritten); + if (framesWritten < frameCount) { + /* Overflow. This will glitch. */ + } + } + + /*printf("(Capture) Processing %d frames...\n", (int)frameCount);*/ + } + } else { + /*ma_log_postf(pContextStatePipeWire->pLog, MA_LOG_LEVEL_WARNING, "(PipeWire) No frames to process.\n");*/ + } + + pBuffer->buffer->datas[0].chunk->offset = 0; + pBuffer->buffer->datas[0].chunk->size = frameCount * bytesPerFrame; + + pContextStatePipeWire->pw_stream_queue_buffer(pStreamState->pStream, pBuffer); + + /* We need to make sure the loop is woken up so we can refill the intermediary buffer in the step function. */ + pContextStatePipeWire->pw_loop_signal_event(pDeviceStatePipeWire->pLoop, pDeviceStatePipeWire->pWakeup); +} + + +static void ma_stream_event_state_changed_playback__pipewire(void* pUserData, enum ma_pw_stream_state oldState, enum ma_pw_stream_state state, const char* pError) +{ + ma_stream_event_state_changed__pipewire(pUserData, oldState, state, pError, ma_device_type_playback); +} + +static void ma_stream_event_state_changed_capture__pipewire(void* pUserData, enum ma_pw_stream_state oldState, enum ma_pw_stream_state state, const char* pError) +{ + ma_stream_event_state_changed__pipewire(pUserData, oldState, state, pError, ma_device_type_capture); +} + + +static void ma_stream_event_param_changed_playback__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) +{ + ma_stream_event_param_changed__pipewire(pUserData, id, pParam, ma_device_type_playback); +} + +static void ma_stream_event_param_changed_capture__pipewire(void* pUserData, ma_uint32 id, const struct ma_spa_pod* pParam) +{ + ma_stream_event_param_changed__pipewire(pUserData, id, pParam, ma_device_type_capture); +} + + +static void ma_stream_event_process_playback__pipewire(void* pUserData) +{ + ma_stream_event_process__pipewire(pUserData, ma_device_type_playback); +} + +static void ma_stream_event_process_capture__pipewire(void* pUserData) +{ + ma_stream_event_process__pipewire(pUserData, ma_device_type_capture); +} + + +static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Playback = +{ + MA_PW_VERSION_STREAM_EVENTS, + NULL, /* destroy */ + ma_stream_event_state_changed_playback__pipewire, + NULL, /* control_info */ + NULL, /* io_changed */ + ma_stream_event_param_changed_playback__pipewire, + NULL, /* add_buffer */ + NULL, /* remove_buffer */ + ma_stream_event_process_playback__pipewire, + NULL, /* drained */ + NULL, /* command */ + NULL, /* trigger_done */ +}; + + +static const struct ma_pw_stream_events ma_gStreamEventsPipeWire_Capture = +{ + MA_PW_VERSION_STREAM_EVENTS, + NULL, /* destroy */ + ma_stream_event_state_changed_capture__pipewire, + NULL, /* control_info */ + NULL, /* io_changed */ + ma_stream_event_param_changed_capture__pipewire, + NULL, /* add_buffer */ + NULL, /* remove_buffer */ + ma_stream_event_process_capture__pipewire, + NULL, /* drained */ + NULL, /* command */ + NULL, /* trigger_done */ +}; + +static ma_result ma_device_init_internal__pipewire(ma_device* pDevice, ma_context_state_pipewire* pContextStatePipeWire, ma_device_state_pipewire* pDeviceStatePipeWire, const ma_device_config_pipewire* pDeviceConfigPipeWire, ma_device_type deviceType, ma_device_descriptor* pDescriptor) +{ + ma_pipewire_stream_state* pStreamState; + struct ma_pw_properties* pProperties; + const struct ma_pw_stream_events* pStreamEvents; + enum ma_spa_audio_format formatPA; + ma_uint32 channels; + struct ma_spa_pod_audio_info_raw podAudioInfo; + const struct ma_spa_pod* pConnectionParameters[1]; + enum ma_pw_stream_flags streamFlags; + int connectResult; + + /* This function can only be called for playback or capture sides. */ + if (deviceType != ma_device_type_playback && deviceType != ma_device_type_capture) { + return MA_INVALID_ARGS; + } + + if (deviceType == ma_device_type_playback) { + pStreamState = &pDeviceStatePipeWire->playback; + pStreamEvents = &ma_gStreamEventsPipeWire_Playback; + } else { + pStreamState = &pDeviceStatePipeWire->capture; + pStreamEvents = &ma_gStreamEventsPipeWire_Capture; + } + + /* Set up the buffer size first so the parameter negotiation callback knows how to configure the buffer on the PipeWire side. */ + pStreamState->pDescriptor = pDescriptor; + pStreamState->bufferSizeInFrames = pDescriptor->periodSizeInFrames; + + pProperties = pContextStatePipeWire->pw_properties_new( + MA_PW_KEY_MEDIA_TYPE, "Audio", + MA_PW_KEY_MEDIA_CATEGORY, (deviceType == ma_device_type_playback) ? "Playback" : "Capture", + MA_PW_KEY_MEDIA_ROLE, (pDeviceConfigPipeWire->pMediaRole != NULL) ? pDeviceConfigPipeWire->pMediaRole : "Game", + /* MA_PW_KEY_NODE_LATENCY is set during format negotiation because it depends on knowledge of the sample rate. */ + NULL); + + if (pDescriptor->pDeviceID != NULL) { + pContextStatePipeWire->pw_properties_set(pProperties, MA_PW_KEY_TARGET_OBJECT, pDescriptor->pDeviceID->custom.s); + } + + pStreamState->pStream = pContextStatePipeWire->pw_stream_new(pDeviceStatePipeWire->pCore, (pDeviceConfigPipeWire->pStreamName != NULL) ? pDeviceConfigPipeWire->pStreamName : "miniaudio", pProperties); + if (pStreamState->pStream == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire stream."); + return MA_ERROR; + } + + /* This installs callbacks for process and param_changed. "process" is for queuing audio data, and "param_changed" is for getting the internal format/channels/rate. */ + pContextStatePipeWire->pw_stream_add_listener(pStreamState->pStream, &pStreamState->eventListener, pStreamEvents, pDeviceStatePipeWire); + + /* If the format is SPA_AUDIO_FORMAT_UNKNOWN, PipeWire can pick a planar data layout (de-interleaved) which breaks things for us. Just force interleaved F32 in this case. */ + formatPA = ma_format_to_pipewire(pDescriptor->format); + if (formatPA == MA_SPA_AUDIO_FORMAT_UNKNOWN) { + if (ma_is_little_endian()) { + formatPA = MA_SPA_AUDIO_FORMAT_F32_LE; + } else { + formatPA = MA_SPA_AUDIO_FORMAT_F32_BE; + } + } + + /* + An annoying detail here. When a single channel is requested it will be output to the FL channel and not distributed + to additional channels. This means with headphones the sound will only come out of the left side. But then there are + times when an app would literally want to output to only a single channel. + + The way I'm going to deal with this is when a single channel is requested and the `MA_CHANNEL_MONO` channel position + is explicitly listed in the channel map, we'll request the "native" channel count. + */ + channels = pDescriptor->channels; + if (channels == 1 && (pDescriptor->channelMap[0] == MA_CHANNEL_NONE || pDescriptor->channelMap[0] == MA_CHANNEL_MONO)) { + channels = 0; + } + + podAudioInfo = ma_spa_pod_audio_info_raw_init(formatPA, channels, pDescriptor->sampleRate); + pConnectionParameters[0] = (struct ma_spa_pod*)&podAudioInfo; + + + /* + I'm just using MAP_BUFFERS because it's what the PipeWire examples do. I don't know what this does. Also, what's the + point in the AUTOCONNECT flag? Is that not what we're already doing by calling a function called "connect"?! + + We also can't use INACTIVE because without it, the param_changed callback will not get called, but we depend on that + so we can get access to the internal format/channels/rate. + */ + streamFlags = (enum ma_pw_stream_flags)(MA_PW_STREAM_FLAG_AUTOCONNECT | /*MA_PW_STREAM_FLAG_INACTIVE |*/ MA_PW_STREAM_FLAG_MAP_BUFFERS); + + connectResult = pContextStatePipeWire->pw_stream_connect(pStreamState->pStream, (deviceType == ma_device_type_playback) ? MA_SPA_DIRECTION_OUTPUT : MA_SPA_DIRECTION_INPUT, MA_PW_ID_ANY, streamFlags, pConnectionParameters, ma_countof(pConnectionParameters)); + if (connectResult < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire stream."); + pContextStatePipeWire->pw_stream_destroy(pStreamState->pStream); + return MA_ERROR; + } + + /* + We need to keep iterating until we have finalized our internal format. Note that when there is no + microphone connected the process callback will never get fired which will result in this loop getting + stuck, so we'll use a timeout here. + */ + { + ma_uint32 timeoutMS = 2000; + ma_uint32 elapsedMS = 0; + + while ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_FORMAT) == 0) { + pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, 1); + + elapsedMS += 1; + if (elapsedMS >= timeoutMS || ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_ERROR) != 0)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to initialize capture device. Possibly no microphone connected."); + pContextStatePipeWire->pw_stream_destroy(pStreamState->pStream); + return MA_ERROR; + } + } + } + + /* We should have our format at this point, but we will not know the exact period size yet until we've done the first processing callback. */ + pDescriptor->format = pStreamState->format; + pDescriptor->channels = pStreamState->channels; + pDescriptor->sampleRate = pStreamState->sampleRate; + ma_channel_map_copy_or_default(pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pStreamState->channelMap, pStreamState->channels); + + /* Now we need to wait until we know our period size. */ + { + ma_uint32 timeoutMS = 2000; + ma_uint32 elapsedMS = 0; + + while ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_HAS_LATENCY) == 0) { + pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, 1); + + elapsedMS += 1; + if (elapsedMS >= timeoutMS || ((pStreamState->initStatus & MA_PIPEWIRE_INIT_STATUS_ERROR) != 0)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to initialize capture device. Possibly no microphone connected."); + pContextStatePipeWire->pw_stream_destroy(pStreamState->pStream); + return MA_ERROR; + } + } + } + + pDescriptor->periodSizeInFrames = pStreamState->rbSizeInFrames; + pDescriptor->periodCount = 1; + + /* Devices are in a stopped state by default in miniaudio. */ + pContextStatePipeWire->pw_stream_set_active(pStreamState->pStream, MA_FALSE); + + pStreamState->pDescriptor = NULL; + pStreamState->initStatus |= MA_PIPEWIRE_INIT_STATUS_INITIALIZED; + return MA_SUCCESS; +} + +static void ma_device_on_wakeup__pipewire(void* pUserData, ma_uint64 count) +{ + /* Nothing to do here. This is only used for waking up the loop. */ + (void)pUserData; + (void)count; +} + +static ma_result ma_device_init__pipewire(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + ma_result result; + struct ma_pw_loop* pLoop; + struct ma_pw_context* pPipeWireContext; + struct ma_pw_core* pCore; + ma_context_state_pipewire* pContextStatePipeWire; + ma_device_state_pipewire* pDeviceStatePipeWire; + const ma_device_config_pipewire* pDeviceConfigPipeWire; + ma_device_config_pipewire defaultDeviceConfigPipeWire; + ma_device_type deviceType; + + pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + MA_ASSERT(pContextStatePipeWire != NULL); + + /* Grab the config. This can be null in which case we'll use a default. */ + pDeviceConfigPipeWire = (const ma_device_config_pipewire*)pDeviceBackendConfig; + if (pDeviceConfigPipeWire == NULL) { + defaultDeviceConfigPipeWire = ma_device_config_pipewire_init(); + pDeviceConfigPipeWire = &defaultDeviceConfigPipeWire; + } + + deviceType = ma_device_get_type(pDevice); + + /* Not sure how to do loopback with PipeWire, but it feels like something PipeWire would support. Look into this. */ + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + pLoop = pContextStatePipeWire->pw_loop_new(NULL); + if (pLoop == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire loop."); + return MA_ERROR; + } + + pPipeWireContext = pContextStatePipeWire->pw_context_new(pLoop, NULL, 0); + if (pPipeWireContext == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire context."); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + pCore = pContextStatePipeWire->pw_context_connect(pPipeWireContext, NULL, 0); + if (pCore == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to connect PipeWire context."); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_ERROR; + } + + /* We can now allocate our per-device PipeWire-specific data. */ + pDeviceStatePipeWire = (ma_device_state_pipewire*)ma_calloc(sizeof(*pDeviceStatePipeWire), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStatePipeWire == NULL) { + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + return MA_OUT_OF_MEMORY; + } + + pDeviceStatePipeWire->pContextStatePipeWire = pContextStatePipeWire; + pDeviceStatePipeWire->deviceType = deviceType; + pDeviceStatePipeWire->pDevice = pDevice; + pDeviceStatePipeWire->pLoop = pLoop; + pDeviceStatePipeWire->pContext = pPipeWireContext; + pDeviceStatePipeWire->pCore = pCore; + + /* Enter the main loop before we start iterating. */ + pContextStatePipeWire->pw_loop_enter(pLoop); + + result = MA_DEVICE_TYPE_NOT_SUPPORTED; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__pipewire(pDevice, pContextStatePipeWire, pDeviceStatePipeWire, pDeviceConfigPipeWire, ma_device_type_capture, pDescriptorCapture); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__pipewire(pDevice, pContextStatePipeWire, pDeviceStatePipeWire, pDeviceConfigPipeWire, ma_device_type_playback, pDescriptorPlayback); + } + + if (result != MA_SUCCESS) { + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); + return result; + } + + /* We need an event for waking up the loop. */ + pDeviceStatePipeWire->pWakeup = pContextStatePipeWire->pw_loop_add_event(pLoop, (ma_spa_source_event_func_t)ma_device_on_wakeup__pipewire, pDeviceStatePipeWire); /* Cast should be fine here. It's complaining about the difference between ma_uint64 and uint64_t. */ + if (pDeviceStatePipeWire->pWakeup == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to create PipeWire loop wakeup event."); + pContextStatePipeWire->pw_core_disconnect(pCore); + pContextStatePipeWire->pw_context_destroy(pPipeWireContext); + pContextStatePipeWire->pw_loop_destroy(pLoop); + ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); + return MA_ERROR; + } + + *ppDeviceState = pDeviceStatePipeWire; + + return MA_SUCCESS; +} + +static void ma_device_uninit__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + + if (pDeviceStatePipeWire->capture.pStream != NULL) { + pContextStatePipeWire->pw_stream_destroy(pDeviceStatePipeWire->capture.pStream); + pDeviceStatePipeWire->capture.pStream = NULL; + } + + if (pDeviceStatePipeWire->playback.pStream != NULL) { + pContextStatePipeWire->pw_stream_destroy(pDeviceStatePipeWire->playback.pStream); + pDeviceStatePipeWire->playback.pStream = NULL; + } + + /* This will be called from the same thread that called ma_device_init__pipewire() and is therefore an appropriate place to leave the main loop. */ + pContextStatePipeWire->pw_loop_leave(pDeviceStatePipeWire->pLoop); + + pContextStatePipeWire->pw_core_disconnect(pDeviceStatePipeWire->pCore); + pContextStatePipeWire->pw_context_destroy(pDeviceStatePipeWire->pContext); + pContextStatePipeWire->pw_loop_destroy(pDeviceStatePipeWire->pLoop); + + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_audio_ring_buffer_uninit(&pDeviceStatePipeWire->capture.rb); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_audio_ring_buffer_uninit(&pDeviceStatePipeWire->playback.rb); + } + } + + ma_free(pDeviceStatePipeWire, ma_device_get_allocation_callbacks(pDevice)); +} + + +static ma_result ma_device_start__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + + /* Prepare our buffers before starting the streams. To do this we just need to step. */ + ma_device_step__pipewire(pDevice, MA_BLOCKING_MODE_NON_BLOCKING); + + if (pDeviceStatePipeWire->capture.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->capture.pStream, MA_TRUE); + } + + if (pDeviceStatePipeWire->playback.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->playback.pStream, MA_TRUE); + } + + return MA_SUCCESS; +} + +static ma_result ma_device_stop__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + + if (pDeviceStatePipeWire->capture.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->capture.pStream, MA_FALSE); + } + + if (pDeviceStatePipeWire->playback.pStream != NULL) { + pContextStatePipeWire->pw_stream_set_active(pDeviceStatePipeWire->playback.pStream, MA_FALSE); + } + + return MA_SUCCESS; +} + +static ma_result ma_device_step__pipewire(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + int timeout; + ma_bool32 hasProcessedData = MA_FALSE; + + if (blockingMode == MA_BLOCKING_MODE_BLOCKING) { + timeout = -1; + } else { + timeout = 0; + } + + /* We will keep looping until we've processed some data. This should keep our stepping in time with data processing. */ + for (;;) { + pContextStatePipeWire->pw_loop_iterate(pDeviceStatePipeWire->pLoop, timeout); + + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } + + /* We need only process data here in single-threaded mode. */ + if (ma_device_get_threading_mode(pDeviceStatePipeWire->pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + void* pMappedBuffer; + ma_uint32 framesAvailable; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + framesAvailable = ma_audio_ring_buffer_map_consume(&pDeviceStatePipeWire->capture.rb, 0xFFFFFFFF, &pMappedBuffer); + if (framesAvailable > 0) { + hasProcessedData = MA_TRUE; + } + + ma_device_handle_backend_data_callback(pDevice, NULL, pMappedBuffer, framesAvailable); + ma_audio_ring_buffer_unmap_consume(&pDeviceStatePipeWire->capture.rb, framesAvailable); + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + framesAvailable = ma_audio_ring_buffer_map_produce(&pDeviceStatePipeWire->playback.rb, 0xFFFFFFFF, &pMappedBuffer); + if (framesAvailable > 0) { + hasProcessedData = MA_TRUE; + } + + ma_device_handle_backend_data_callback(pDevice, pMappedBuffer, NULL, framesAvailable); + ma_audio_ring_buffer_unmap_produce(&pDeviceStatePipeWire->playback.rb, framesAvailable); + } + } + + if (hasProcessedData || blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + break; + } + } + + return MA_SUCCESS; +} + +static void ma_device_wakeup__pipewire(ma_device* pDevice) +{ + ma_device_state_pipewire* pDeviceStatePipeWire = ma_device_get_backend_state__pipewire(pDevice); + ma_context_state_pipewire* pContextStatePipeWire = ma_context_get_backend_state__pipewire(ma_device_get_context(pDevice)); + + pContextStatePipeWire->pw_loop_signal_event(pDeviceStatePipeWire->pLoop, pDeviceStatePipeWire->pWakeup); +} + + +static ma_device_backend_vtable ma_gDeviceBackendVTable_PipeWire = +{ + ma_backend_info__pipewire, + ma_context_init__pipewire, + ma_context_uninit__pipewire, + ma_context_enumerate_devices__pipewire, + ma_device_init__pipewire, + ma_device_uninit__pipewire, + ma_device_start__pipewire, + ma_device_stop__pipewire, + ma_device_step__pipewire, + ma_device_wakeup__pipewire +}; + +ma_device_backend_vtable* ma_device_backend_pipewire = &ma_gDeviceBackendVTable_PipeWire; + +#if defined(MA_NO_RUNTIME_LINKING) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif + +#else +ma_device_backend_vtable* ma_device_backend_pipewire = NULL; +#endif /* MA_HAS_PIPEWIRE */ + +MA_API ma_device_backend_vtable* ma_pipewire_get_vtable(void) +{ + return ma_device_backend_pipewire; +} + +MA_API ma_context_config_pipewire ma_context_config_pipewire_init(void) +{ + ma_context_config_pipewire config; + + memset(&config, 0, sizeof(config)); + + return config; +} + +MA_API ma_device_config_pipewire ma_device_config_pipewire_init(void) +{ + ma_device_config_pipewire config; + + memset(&config, 0, sizeof(config)); + + return config; +} + + +/****************************************************************************** + +PulseAudio Backend + +******************************************************************************/ +#ifdef MA_HAS_PULSEAUDIO +/* +The PulseAudio API, along with Apple's Core Audio, is the worst of the mainstream audio APIs. This is a brief description of what's going on +in the PulseAudio backend. I apologize if this gets a bit ranty for your liking - you might want to skip this discussion. + +PulseAudio has something they call the "Simple API", which unfortunately isn't suitable for miniaudio. I've not seen anywhere where it +allows you to enumerate over devices, nor does it seem to support the ability to stop and start streams. Looking at the documentation, it +appears as though the stream is constantly running and you prevent sound from being emitted or captured by simply not calling the read or +write functions. This is not a professional solution as it would be much better to *actually* stop the underlying stream. Perhaps the +simple API has some smarts to do this automatically, but I'm not sure. Another limitation with the simple API is that it seems inefficient +when you want to have multiple streams to a single context. For these reasons, miniaudio is not using the simple API. + +Since we're not using the simple API, we're left with the asynchronous API as our only other option. And boy, is this where it starts to +get fun, and I don't mean that in a good way... + +The problems start with the very name of the API - "asynchronous". Yes, this is an asynchronous oriented API which means your commands +don't immediately take effect. You instead need to issue your commands, and then wait for them to complete. The waiting mechanism is +enabled through the use of a "main loop". In the asynchronous API you cannot get away from the main loop, and the main loop is where almost +all of PulseAudio's problems stem from. + +When you first initialize PulseAudio you need an object referred to as "main loop". You can implement this yourself by defining your own +vtable, but it's much easier to just use one of the built-in main loop implementations. There's two generic implementations called +pa_mainloop and pa_threaded_mainloop, and another implementation specific to GLib called pa_glib_mainloop. We're using pa_threaded_mainloop +because it simplifies management of the worker thread. The idea of the main loop object is pretty self explanatory - you're supposed to use +it to implement a worker thread which runs in a loop. The main loop is where operations are actually executed. + +To initialize the main loop, you just use `pa_threaded_mainloop_new()`. This is the first function you'll call. You can then get a pointer +to the vtable with `pa_threaded_mainloop_get_api()` (the main loop vtable is called `pa_mainloop_api`). Again, you can bypass the threaded +main loop object entirely and just implement `pa_mainloop_api` directly, but there's no need for it unless you're doing something extremely +specialized such as if you want to integrate it into your application's existing main loop infrastructure. + +(EDIT 2021-01-26: miniaudio is no longer using `pa_threaded_mainloop` due to this issue: https://github.com/mackron/miniaudio/issues/262. +It is now using `pa_mainloop` which turns out to be a simpler solution anyway. The rest of this rant still applies, however.) + +Once you have your main loop vtable (the `pa_mainloop_api` object) you can create the PulseAudio context. This is very similar to +miniaudio's context and they map to each other quite well. You have one context to many streams, which is basically the same as miniaudio's +one `ma_context` to many `ma_device`s. Here's where it starts to get annoying, however. When you first create the PulseAudio context, which +is done with `pa_context_new()`, it's not actually connected to anything. When you connect, you call `pa_context_connect()`. However, if +you remember, PulseAudio is an asynchronous API. That means you cannot just assume the context is connected after `pa_context_context()` +has returned. You instead need to wait for it to connect. To do this, you need to either wait for a callback to get fired, which you can +set with `pa_context_set_state_callback()`, or you can continuously poll the context's state. Either way, you need to run this in a loop. +All objects from here out are created from the context, and, I believe, you can't be creating these objects until the context is connected. +This waiting loop is therefore unavoidable. In order for the waiting to ever complete, however, the main loop needs to be running. Before +attempting to connect the context, the main loop needs to be started with `pa_threaded_mainloop_start()`. + +The reason for this asynchronous design is to support cases where you're connecting to a remote server, say through a local network or an +internet connection. However, the *VAST* majority of cases don't involve this at all - they just connect to a local "server" running on the +host machine. The fact that this would be the default rather than making `pa_context_connect()` synchronous tends to boggle the mind. + +Once the context has been created and connected you can start creating a stream. A PulseAudio stream is analogous to miniaudio's device. +The initialization of a stream is fairly standard - you configure some attributes (analogous to miniaudio's device config) and then call +`pa_stream_new()` to actually create it. Here is where we start to get into "operations". When configuring the stream, you can get +information about the source (such as sample format, sample rate, etc.), however it's not synchronous. Instead, a `pa_operation` object +is returned from `pa_context_get_source_info_by_name()` (capture) or `pa_context_get_sink_info_by_name()` (playback). Then, you need to +run a loop (again!) to wait for the operation to complete which you can determine via a callback or polling, just like we did with the +context. Then, as an added bonus, you need to decrement the reference counter of the `pa_operation` object to ensure memory is cleaned up. +All of that just to retrieve basic information about a device! + +Once the basic information about the device has been retrieved, miniaudio can now create the stream with `ma_stream_new()`. Like the +context, this needs to be connected. But we need to be careful here, because we're now about to introduce one of the most horrific design +choices in PulseAudio. + +PulseAudio allows you to specify a callback that is fired when data can be written to or read from a stream. The language is important here +because PulseAudio takes it literally, specifically the "can be". You would think these callbacks would be appropriate as the place for +writing and reading data to and from the stream, and that would be right, except when it's not. When you initialize the stream, you can +set a flag that tells PulseAudio to not start the stream automatically. This is required because miniaudio does not auto-start devices +straight after initialization - you need to call `ma_device_start()` manually. The problem is that even when this flag is specified, +PulseAudio will immediately fire its write or read callback. This is *technically* correct (based on the wording in the documentation) +because indeed, data *can* be written at this point. The problem is that it's not *practical*. It makes sense that the write/read callback +would be where a program will want to write or read data to or from the stream, but when it's called before the application has even +requested that the stream be started, it's just not practical because the program probably isn't ready for any kind of data delivery at +that point (it may still need to load files or whatnot). Instead, this callback should only be fired when the application requests the +stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data +callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio +doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been +started. The device state is used for this - if the state is anything other than `ma_device_status_starting` or `ma_device_status_started`, +the main data callback is not fired. + +This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will +continuously fire the callback at regular intervals based on the size of the internal buffer. This will only ever be fired when the device +is running, and will be fired regardless of whether or not the user actually wrote anything to the device/stream. This not the case in +PulseAudio. In PulseAudio, the data callback will *only* be called if you wrote something to it previously. That means, if you don't call +`pa_stream_write()`, the callback will not get fired. On the surface you wouldn't think this would matter because you should be always +writing data, and if you don't have anything to write, just write silence. That's fine until you want to drain the stream. You see, if +you're continuously writing data to the stream, the stream will never get drained! That means in order to drain the stream, you need to +*not* write data to it! But remember, when you don't write data to the stream, the callback won't get fired again! Why is draining +important? Because that's how we've defined stopping to work in miniaudio. In miniaudio, stopping the device requires it to be drained +before returning from ma_device_stop(). So we've stopped the device, which requires us to drain, but draining requires us to *not* write +data to the stream (or else it won't ever complete draining), but not writing to the stream means the callback won't get fired again! + +This becomes a problem when stopping and then restarting the device. When the device is stopped, it's drained, which requires us to *not* +write anything to the stream. But then, since we didn't write anything to it, the write callback will *never* get called again if we just +resume the stream naively. This means that starting the stream requires us to write data to the stream from outside the callback. This +disconnect is something PulseAudio has got seriously wrong - there should only ever be a single source of data delivery, that being the +callback. (I have tried using `pa_stream_flush()` to trigger the write callback to fire, but this just doesn't work for some reason.) + +Once you've created the stream, you need to connect it which involves the whole waiting procedure. This is the same process as the context, +only this time you'll poll for the state with `pa_stream_get_status()`. The starting and stopping of a streaming is referred to as +"corking" in PulseAudio. The analogy is corking a barrel. To start the stream, you uncork it, to stop it you cork it. Personally I think +it's silly - why would you not just call it "starting" and "stopping" like any other normal audio API? Anyway, the act of corking is, you +guessed it, asynchronous. This means you'll need our waiting loop as usual. Again, why this asynchronous design is the default is +absolutely beyond me. Would it really be that hard to just make it run synchronously? + +Teardown is pretty simple (what?!). It's just a matter of calling the relevant `_unref()` function on each object in reverse order that +they were initialized in. + +That's about it from the PulseAudio side. A bit ranty, I know, but they really need to fix that main loop and callback system. They're +embarrassingly unpractical. The main loop thing is an easy fix - have synchronous versions of all APIs. If an application wants these to +run asynchronously, they can execute them in a separate thread themselves. The desire to run these asynchronously is such a niche +requirement - it makes no sense to make it the default. The stream write callback needs to be change, or an alternative provided, that is +constantly fired, regardless of whether or not `pa_stream_write()` has been called, and it needs to take a pointer to a buffer as a +parameter which the program just writes to directly rather than having to call `pa_stream_writable_size()` and `pa_stream_write()`. These +changes alone will change PulseAudio from one of the worst audio APIs to one of the best. +*/ + + +/* +It is assumed pulseaudio.h is available when linking at compile time. When linking at compile time, we use the declarations in the header +to check for type safety. We cannot do this when linking at run time because the header might not be available. +*/ +#ifdef MA_NO_RUNTIME_LINKING + +/* pulseaudio.h marks some functions with "inline" which isn't always supported. Need to emulate it. */ +#if !defined(__cplusplus) + #if defined(__STRICT_ANSI__) + #if !defined(inline) + #define inline __inline__ __attribute__((always_inline)) + #define MA_INLINE_DEFINED + #endif + #endif +#endif +#include +#if defined(MA_INLINE_DEFINED) + #undef inline + #undef MA_INLINE_DEFINED +#endif + +#define MA_PA_OK PA_OK +#define MA_PA_ERR_ACCESS PA_ERR_ACCESS +#define MA_PA_ERR_INVALID PA_ERR_INVALID +#define MA_PA_ERR_NOENTITY PA_ERR_NOENTITY +#define MA_PA_ERR_NOTSUPPORTED PA_ERR_NOTSUPPORTED + +#define MA_PA_CHANNELS_MAX PA_CHANNELS_MAX +#define MA_PA_RATE_MAX PA_RATE_MAX + +typedef pa_context_flags_t ma_pa_context_flags_t; +#define MA_PA_CONTEXT_NOFLAGS PA_CONTEXT_NOFLAGS +#define MA_PA_CONTEXT_NOAUTOSPAWN PA_CONTEXT_NOAUTOSPAWN +#define MA_PA_CONTEXT_NOFAIL PA_CONTEXT_NOFAIL + +typedef pa_stream_flags_t ma_pa_stream_flags_t; +#define MA_PA_STREAM_NOFLAGS PA_STREAM_NOFLAGS +#define MA_PA_STREAM_START_CORKED PA_STREAM_START_CORKED +#define MA_PA_STREAM_INTERPOLATE_TIMING PA_STREAM_INTERPOLATE_TIMING +#define MA_PA_STREAM_NOT_MONOTONIC PA_STREAM_NOT_MONOTONIC +#define MA_PA_STREAM_AUTO_TIMING_UPDATE PA_STREAM_AUTO_TIMING_UPDATE +#define MA_PA_STREAM_NO_REMAP_CHANNELS PA_STREAM_NO_REMAP_CHANNELS +#define MA_PA_STREAM_NO_REMIX_CHANNELS PA_STREAM_NO_REMIX_CHANNELS +#define MA_PA_STREAM_FIX_FORMAT PA_STREAM_FIX_FORMAT +#define MA_PA_STREAM_FIX_RATE PA_STREAM_FIX_RATE +#define MA_PA_STREAM_FIX_CHANNELS PA_STREAM_FIX_CHANNELS +#define MA_PA_STREAM_DONT_MOVE PA_STREAM_DONT_MOVE +#define MA_PA_STREAM_VARIABLE_RATE PA_STREAM_VARIABLE_RATE +#define MA_PA_STREAM_PEAK_DETECT PA_STREAM_PEAK_DETECT +#define MA_PA_STREAM_START_MUTED PA_STREAM_START_MUTED +#define MA_PA_STREAM_ADJUST_LATENCY PA_STREAM_ADJUST_LATENCY +#define MA_PA_STREAM_EARLY_REQUESTS PA_STREAM_EARLY_REQUESTS +#define MA_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND +#define MA_PA_STREAM_START_UNMUTED PA_STREAM_START_UNMUTED +#define MA_PA_STREAM_FAIL_ON_SUSPEND PA_STREAM_FAIL_ON_SUSPEND +#define MA_PA_STREAM_RELATIVE_VOLUME PA_STREAM_RELATIVE_VOLUME +#define MA_PA_STREAM_PASSTHROUGH PA_STREAM_PASSTHROUGH + +typedef pa_sink_flags_t ma_pa_sink_flags_t; +#define MA_PA_SINK_NOFLAGS PA_SINK_NOFLAGS +#define MA_PA_SINK_HW_VOLUME_CTRL PA_SINK_HW_VOLUME_CTRL +#define MA_PA_SINK_LATENCY PA_SINK_LATENCY +#define MA_PA_SINK_HARDWARE PA_SINK_HARDWARE +#define MA_PA_SINK_NETWORK PA_SINK_NETWORK +#define MA_PA_SINK_HW_MUTE_CTRL PA_SINK_HW_MUTE_CTRL +#define MA_PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME +#define MA_PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME +#define MA_PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY +#define MA_PA_SINK_SET_FORMATS PA_SINK_SET_FORMATS + +typedef pa_source_flags_t ma_pa_source_flags_t; +#define MA_PA_SOURCE_NOFLAGS PA_SOURCE_NOFLAGS +#define MA_PA_SOURCE_HW_VOLUME_CTRL PA_SOURCE_HW_VOLUME_CTRL +#define MA_PA_SOURCE_LATENCY PA_SOURCE_LATENCY +#define MA_PA_SOURCE_HARDWARE PA_SOURCE_HARDWARE +#define MA_PA_SOURCE_NETWORK PA_SOURCE_NETWORK +#define MA_PA_SOURCE_HW_MUTE_CTRL PA_SOURCE_HW_MUTE_CTRL +#define MA_PA_SOURCE_DECIBEL_VOLUME PA_SOURCE_DECIBEL_VOLUME +#define MA_PA_SOURCE_DYNAMIC_LATENCY PA_SOURCE_DYNAMIC_LATENCY +#define MA_PA_SOURCE_FLAT_VOLUME PA_SOURCE_FLAT_VOLUME + +typedef pa_context_state_t ma_pa_context_state_t; +#define MA_PA_CONTEXT_UNCONNECTED PA_CONTEXT_UNCONNECTED +#define MA_PA_CONTEXT_CONNECTING PA_CONTEXT_CONNECTING +#define MA_PA_CONTEXT_AUTHORIZING PA_CONTEXT_AUTHORIZING +#define MA_PA_CONTEXT_SETTING_NAME PA_CONTEXT_SETTING_NAME +#define MA_PA_CONTEXT_READY PA_CONTEXT_READY +#define MA_PA_CONTEXT_FAILED PA_CONTEXT_FAILED +#define MA_PA_CONTEXT_TERMINATED PA_CONTEXT_TERMINATED + +typedef pa_stream_state_t ma_pa_stream_state_t; +#define MA_PA_STREAM_UNCONNECTED PA_STREAM_UNCONNECTED +#define MA_PA_STREAM_CREATING PA_STREAM_CREATING +#define MA_PA_STREAM_READY PA_STREAM_READY +#define MA_PA_STREAM_FAILED PA_STREAM_FAILED +#define MA_PA_STREAM_TERMINATED PA_STREAM_TERMINATED + +typedef pa_operation_state_t ma_pa_operation_state_t; +#define MA_PA_OPERATION_RUNNING PA_OPERATION_RUNNING +#define MA_PA_OPERATION_DONE PA_OPERATION_DONE +#define MA_PA_OPERATION_CANCELLED PA_OPERATION_CANCELLED + +typedef pa_sink_state_t ma_pa_sink_state_t; +#define MA_PA_SINK_INVALID_STATE PA_SINK_INVALID_STATE +#define MA_PA_SINK_RUNNING PA_SINK_RUNNING +#define MA_PA_SINK_IDLE PA_SINK_IDLE +#define MA_PA_SINK_SUSPENDED PA_SINK_SUSPENDED + +typedef pa_source_state_t ma_pa_source_state_t; +#define MA_PA_SOURCE_INVALID_STATE PA_SOURCE_INVALID_STATE +#define MA_PA_SOURCE_RUNNING PA_SOURCE_RUNNING +#define MA_PA_SOURCE_IDLE PA_SOURCE_IDLE +#define MA_PA_SOURCE_SUSPENDED PA_SOURCE_SUSPENDED + +typedef pa_seek_mode_t ma_pa_seek_mode_t; +#define MA_PA_SEEK_RELATIVE PA_SEEK_RELATIVE +#define MA_PA_SEEK_ABSOLUTE PA_SEEK_ABSOLUTE +#define MA_PA_SEEK_RELATIVE_ON_READ PA_SEEK_RELATIVE_ON_READ +#define MA_PA_SEEK_RELATIVE_END PA_SEEK_RELATIVE_END + +typedef pa_channel_position_t ma_pa_channel_position_t; +#define MA_PA_CHANNEL_POSITION_INVALID PA_CHANNEL_POSITION_INVALID +#define MA_PA_CHANNEL_POSITION_MONO PA_CHANNEL_POSITION_MONO +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT PA_CHANNEL_POSITION_FRONT_LEFT +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT PA_CHANNEL_POSITION_FRONT_RIGHT +#define MA_PA_CHANNEL_POSITION_FRONT_CENTER PA_CHANNEL_POSITION_FRONT_CENTER +#define MA_PA_CHANNEL_POSITION_REAR_CENTER PA_CHANNEL_POSITION_REAR_CENTER +#define MA_PA_CHANNEL_POSITION_REAR_LEFT PA_CHANNEL_POSITION_REAR_LEFT +#define MA_PA_CHANNEL_POSITION_REAR_RIGHT PA_CHANNEL_POSITION_REAR_RIGHT +#define MA_PA_CHANNEL_POSITION_LFE PA_CHANNEL_POSITION_LFE +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER +#define MA_PA_CHANNEL_POSITION_SIDE_LEFT PA_CHANNEL_POSITION_SIDE_LEFT +#define MA_PA_CHANNEL_POSITION_SIDE_RIGHT PA_CHANNEL_POSITION_SIDE_RIGHT +#define MA_PA_CHANNEL_POSITION_AUX0 PA_CHANNEL_POSITION_AUX0 +#define MA_PA_CHANNEL_POSITION_AUX1 PA_CHANNEL_POSITION_AUX1 +#define MA_PA_CHANNEL_POSITION_AUX2 PA_CHANNEL_POSITION_AUX2 +#define MA_PA_CHANNEL_POSITION_AUX3 PA_CHANNEL_POSITION_AUX3 +#define MA_PA_CHANNEL_POSITION_AUX4 PA_CHANNEL_POSITION_AUX4 +#define MA_PA_CHANNEL_POSITION_AUX5 PA_CHANNEL_POSITION_AUX5 +#define MA_PA_CHANNEL_POSITION_AUX6 PA_CHANNEL_POSITION_AUX6 +#define MA_PA_CHANNEL_POSITION_AUX7 PA_CHANNEL_POSITION_AUX7 +#define MA_PA_CHANNEL_POSITION_AUX8 PA_CHANNEL_POSITION_AUX8 +#define MA_PA_CHANNEL_POSITION_AUX9 PA_CHANNEL_POSITION_AUX9 +#define MA_PA_CHANNEL_POSITION_AUX10 PA_CHANNEL_POSITION_AUX10 +#define MA_PA_CHANNEL_POSITION_AUX11 PA_CHANNEL_POSITION_AUX11 +#define MA_PA_CHANNEL_POSITION_AUX12 PA_CHANNEL_POSITION_AUX12 +#define MA_PA_CHANNEL_POSITION_AUX13 PA_CHANNEL_POSITION_AUX13 +#define MA_PA_CHANNEL_POSITION_AUX14 PA_CHANNEL_POSITION_AUX14 +#define MA_PA_CHANNEL_POSITION_AUX15 PA_CHANNEL_POSITION_AUX15 +#define MA_PA_CHANNEL_POSITION_AUX16 PA_CHANNEL_POSITION_AUX16 +#define MA_PA_CHANNEL_POSITION_AUX17 PA_CHANNEL_POSITION_AUX17 +#define MA_PA_CHANNEL_POSITION_AUX18 PA_CHANNEL_POSITION_AUX18 +#define MA_PA_CHANNEL_POSITION_AUX19 PA_CHANNEL_POSITION_AUX19 +#define MA_PA_CHANNEL_POSITION_AUX20 PA_CHANNEL_POSITION_AUX20 +#define MA_PA_CHANNEL_POSITION_AUX21 PA_CHANNEL_POSITION_AUX21 +#define MA_PA_CHANNEL_POSITION_AUX22 PA_CHANNEL_POSITION_AUX22 +#define MA_PA_CHANNEL_POSITION_AUX23 PA_CHANNEL_POSITION_AUX23 +#define MA_PA_CHANNEL_POSITION_AUX24 PA_CHANNEL_POSITION_AUX24 +#define MA_PA_CHANNEL_POSITION_AUX25 PA_CHANNEL_POSITION_AUX25 +#define MA_PA_CHANNEL_POSITION_AUX26 PA_CHANNEL_POSITION_AUX26 +#define MA_PA_CHANNEL_POSITION_AUX27 PA_CHANNEL_POSITION_AUX27 +#define MA_PA_CHANNEL_POSITION_AUX28 PA_CHANNEL_POSITION_AUX28 +#define MA_PA_CHANNEL_POSITION_AUX29 PA_CHANNEL_POSITION_AUX29 +#define MA_PA_CHANNEL_POSITION_AUX30 PA_CHANNEL_POSITION_AUX30 +#define MA_PA_CHANNEL_POSITION_AUX31 PA_CHANNEL_POSITION_AUX31 +#define MA_PA_CHANNEL_POSITION_TOP_CENTER PA_CHANNEL_POSITION_TOP_CENTER +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT PA_CHANNEL_POSITION_TOP_FRONT_LEFT +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT PA_CHANNEL_POSITION_TOP_FRONT_RIGHT +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER PA_CHANNEL_POSITION_TOP_FRONT_CENTER +#define MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT PA_CHANNEL_POSITION_TOP_REAR_LEFT +#define MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT PA_CHANNEL_POSITION_TOP_REAR_RIGHT +#define MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER PA_CHANNEL_POSITION_TOP_REAR_CENTER +#define MA_PA_CHANNEL_POSITION_LEFT PA_CHANNEL_POSITION_LEFT +#define MA_PA_CHANNEL_POSITION_RIGHT PA_CHANNEL_POSITION_RIGHT +#define MA_PA_CHANNEL_POSITION_CENTER PA_CHANNEL_POSITION_CENTER +#define MA_PA_CHANNEL_POSITION_SUBWOOFER PA_CHANNEL_POSITION_SUBWOOFER + +typedef pa_channel_map_def_t ma_pa_channel_map_def_t; +#define MA_PA_CHANNEL_MAP_AIFF PA_CHANNEL_MAP_AIFF +#define MA_PA_CHANNEL_MAP_ALSA PA_CHANNEL_MAP_ALSA +#define MA_PA_CHANNEL_MAP_AUX PA_CHANNEL_MAP_AUX +#define MA_PA_CHANNEL_MAP_WAVEEX PA_CHANNEL_MAP_WAVEEX +#define MA_PA_CHANNEL_MAP_OSS PA_CHANNEL_MAP_OSS +#define MA_PA_CHANNEL_MAP_DEFAULT PA_CHANNEL_MAP_DEFAULT + +typedef pa_sample_format_t ma_pa_sample_format_t; +#define MA_PA_SAMPLE_INVALID PA_SAMPLE_INVALID +#define MA_PA_SAMPLE_U8 PA_SAMPLE_U8 +#define MA_PA_SAMPLE_ALAW PA_SAMPLE_ALAW +#define MA_PA_SAMPLE_ULAW PA_SAMPLE_ULAW +#define MA_PA_SAMPLE_S16LE PA_SAMPLE_S16LE +#define MA_PA_SAMPLE_S16BE PA_SAMPLE_S16BE +#define MA_PA_SAMPLE_FLOAT32LE PA_SAMPLE_FLOAT32LE +#define MA_PA_SAMPLE_FLOAT32BE PA_SAMPLE_FLOAT32BE +#define MA_PA_SAMPLE_S32LE PA_SAMPLE_S32LE +#define MA_PA_SAMPLE_S32BE PA_SAMPLE_S32BE +#define MA_PA_SAMPLE_S24LE PA_SAMPLE_S24LE +#define MA_PA_SAMPLE_S24BE PA_SAMPLE_S24BE +#define MA_PA_SAMPLE_S24_32LE PA_SAMPLE_S24_32LE +#define MA_PA_SAMPLE_S24_32BE PA_SAMPLE_S24_32BE + +typedef pa_mainloop ma_pa_mainloop; +typedef pa_threaded_mainloop ma_pa_threaded_mainloop; +typedef pa_mainloop_api ma_pa_mainloop_api; +typedef pa_context ma_pa_context; +typedef pa_operation ma_pa_operation; +typedef pa_stream ma_pa_stream; +typedef pa_spawn_api ma_pa_spawn_api; +typedef pa_buffer_attr ma_pa_buffer_attr; +typedef pa_channel_map ma_pa_channel_map; +typedef pa_cvolume ma_pa_cvolume; +typedef pa_sample_spec ma_pa_sample_spec; +typedef pa_sink_info ma_pa_sink_info; +typedef pa_source_info ma_pa_source_info; + +typedef pa_context_notify_cb_t ma_pa_context_notify_cb_t; +typedef pa_sink_info_cb_t ma_pa_sink_info_cb_t; +typedef pa_source_info_cb_t ma_pa_source_info_cb_t; +typedef pa_stream_success_cb_t ma_pa_stream_success_cb_t; +typedef pa_stream_request_cb_t ma_pa_stream_request_cb_t; +typedef pa_stream_notify_cb_t ma_pa_stream_notify_cb_t; +typedef pa_free_cb_t ma_pa_free_cb_t; +#else +#define MA_PA_OK 0 +#define MA_PA_ERR_ACCESS 1 +#define MA_PA_ERR_INVALID 2 +#define MA_PA_ERR_NOENTITY 5 +#define MA_PA_ERR_NOTSUPPORTED 19 + +#define MA_PA_CHANNELS_MAX 32 +#define MA_PA_RATE_MAX 384000 + +typedef int ma_pa_context_flags_t; +#define MA_PA_CONTEXT_NOFLAGS 0x00000000 +#define MA_PA_CONTEXT_NOAUTOSPAWN 0x00000001 +#define MA_PA_CONTEXT_NOFAIL 0x00000002 + +typedef int ma_pa_stream_flags_t; +#define MA_PA_STREAM_NOFLAGS 0x00000000 +#define MA_PA_STREAM_START_CORKED 0x00000001 +#define MA_PA_STREAM_INTERPOLATE_TIMING 0x00000002 +#define MA_PA_STREAM_NOT_MONOTONIC 0x00000004 +#define MA_PA_STREAM_AUTO_TIMING_UPDATE 0x00000008 +#define MA_PA_STREAM_NO_REMAP_CHANNELS 0x00000010 +#define MA_PA_STREAM_NO_REMIX_CHANNELS 0x00000020 +#define MA_PA_STREAM_FIX_FORMAT 0x00000040 +#define MA_PA_STREAM_FIX_RATE 0x00000080 +#define MA_PA_STREAM_FIX_CHANNELS 0x00000100 +#define MA_PA_STREAM_DONT_MOVE 0x00000200 +#define MA_PA_STREAM_VARIABLE_RATE 0x00000400 +#define MA_PA_STREAM_PEAK_DETECT 0x00000800 +#define MA_PA_STREAM_START_MUTED 0x00001000 +#define MA_PA_STREAM_ADJUST_LATENCY 0x00002000 +#define MA_PA_STREAM_EARLY_REQUESTS 0x00004000 +#define MA_PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND 0x00008000 +#define MA_PA_STREAM_START_UNMUTED 0x00010000 +#define MA_PA_STREAM_FAIL_ON_SUSPEND 0x00020000 +#define MA_PA_STREAM_RELATIVE_VOLUME 0x00040000 +#define MA_PA_STREAM_PASSTHROUGH 0x00080000 + +typedef int ma_pa_sink_flags_t; +#define MA_PA_SINK_NOFLAGS 0x00000000 +#define MA_PA_SINK_HW_VOLUME_CTRL 0x00000001 +#define MA_PA_SINK_LATENCY 0x00000002 +#define MA_PA_SINK_HARDWARE 0x00000004 +#define MA_PA_SINK_NETWORK 0x00000008 +#define MA_PA_SINK_HW_MUTE_CTRL 0x00000010 +#define MA_PA_SINK_DECIBEL_VOLUME 0x00000020 +#define MA_PA_SINK_FLAT_VOLUME 0x00000040 +#define MA_PA_SINK_DYNAMIC_LATENCY 0x00000080 +#define MA_PA_SINK_SET_FORMATS 0x00000100 + +typedef int ma_pa_source_flags_t; +#define MA_PA_SOURCE_NOFLAGS 0x00000000 +#define MA_PA_SOURCE_HW_VOLUME_CTRL 0x00000001 +#define MA_PA_SOURCE_LATENCY 0x00000002 +#define MA_PA_SOURCE_HARDWARE 0x00000004 +#define MA_PA_SOURCE_NETWORK 0x00000008 +#define MA_PA_SOURCE_HW_MUTE_CTRL 0x00000010 +#define MA_PA_SOURCE_DECIBEL_VOLUME 0x00000020 +#define MA_PA_SOURCE_DYNAMIC_LATENCY 0x00000040 +#define MA_PA_SOURCE_FLAT_VOLUME 0x00000080 + +typedef int ma_pa_context_state_t; +#define MA_PA_CONTEXT_UNCONNECTED 0 +#define MA_PA_CONTEXT_CONNECTING 1 +#define MA_PA_CONTEXT_AUTHORIZING 2 +#define MA_PA_CONTEXT_SETTING_NAME 3 +#define MA_PA_CONTEXT_READY 4 +#define MA_PA_CONTEXT_FAILED 5 +#define MA_PA_CONTEXT_TERMINATED 6 + +typedef int ma_pa_stream_state_t; +#define MA_PA_STREAM_UNCONNECTED 0 +#define MA_PA_STREAM_CREATING 1 +#define MA_PA_STREAM_READY 2 +#define MA_PA_STREAM_FAILED 3 +#define MA_PA_STREAM_TERMINATED 4 + +typedef int ma_pa_operation_state_t; +#define MA_PA_OPERATION_RUNNING 0 +#define MA_PA_OPERATION_DONE 1 +#define MA_PA_OPERATION_CANCELLED 2 + +typedef int ma_pa_sink_state_t; +#define MA_PA_SINK_INVALID_STATE -1 +#define MA_PA_SINK_RUNNING 0 +#define MA_PA_SINK_IDLE 1 +#define MA_PA_SINK_SUSPENDED 2 + +typedef int ma_pa_source_state_t; +#define MA_PA_SOURCE_INVALID_STATE -1 +#define MA_PA_SOURCE_RUNNING 0 +#define MA_PA_SOURCE_IDLE 1 +#define MA_PA_SOURCE_SUSPENDED 2 + +typedef int ma_pa_seek_mode_t; +#define MA_PA_SEEK_RELATIVE 0 +#define MA_PA_SEEK_ABSOLUTE 1 +#define MA_PA_SEEK_RELATIVE_ON_READ 2 +#define MA_PA_SEEK_RELATIVE_END 3 + +typedef int ma_pa_channel_position_t; +#define MA_PA_CHANNEL_POSITION_INVALID -1 +#define MA_PA_CHANNEL_POSITION_MONO 0 +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT 1 +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT 2 +#define MA_PA_CHANNEL_POSITION_FRONT_CENTER 3 +#define MA_PA_CHANNEL_POSITION_REAR_CENTER 4 +#define MA_PA_CHANNEL_POSITION_REAR_LEFT 5 +#define MA_PA_CHANNEL_POSITION_REAR_RIGHT 6 +#define MA_PA_CHANNEL_POSITION_LFE 7 +#define MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER 8 +#define MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER 9 +#define MA_PA_CHANNEL_POSITION_SIDE_LEFT 10 +#define MA_PA_CHANNEL_POSITION_SIDE_RIGHT 11 +#define MA_PA_CHANNEL_POSITION_AUX0 12 +#define MA_PA_CHANNEL_POSITION_AUX1 13 +#define MA_PA_CHANNEL_POSITION_AUX2 14 +#define MA_PA_CHANNEL_POSITION_AUX3 15 +#define MA_PA_CHANNEL_POSITION_AUX4 16 +#define MA_PA_CHANNEL_POSITION_AUX5 17 +#define MA_PA_CHANNEL_POSITION_AUX6 18 +#define MA_PA_CHANNEL_POSITION_AUX7 19 +#define MA_PA_CHANNEL_POSITION_AUX8 20 +#define MA_PA_CHANNEL_POSITION_AUX9 21 +#define MA_PA_CHANNEL_POSITION_AUX10 22 +#define MA_PA_CHANNEL_POSITION_AUX11 23 +#define MA_PA_CHANNEL_POSITION_AUX12 24 +#define MA_PA_CHANNEL_POSITION_AUX13 25 +#define MA_PA_CHANNEL_POSITION_AUX14 26 +#define MA_PA_CHANNEL_POSITION_AUX15 27 +#define MA_PA_CHANNEL_POSITION_AUX16 28 +#define MA_PA_CHANNEL_POSITION_AUX17 29 +#define MA_PA_CHANNEL_POSITION_AUX18 30 +#define MA_PA_CHANNEL_POSITION_AUX19 31 +#define MA_PA_CHANNEL_POSITION_AUX20 32 +#define MA_PA_CHANNEL_POSITION_AUX21 33 +#define MA_PA_CHANNEL_POSITION_AUX22 34 +#define MA_PA_CHANNEL_POSITION_AUX23 35 +#define MA_PA_CHANNEL_POSITION_AUX24 36 +#define MA_PA_CHANNEL_POSITION_AUX25 37 +#define MA_PA_CHANNEL_POSITION_AUX26 38 +#define MA_PA_CHANNEL_POSITION_AUX27 39 +#define MA_PA_CHANNEL_POSITION_AUX28 40 +#define MA_PA_CHANNEL_POSITION_AUX29 41 +#define MA_PA_CHANNEL_POSITION_AUX30 42 +#define MA_PA_CHANNEL_POSITION_AUX31 43 +#define MA_PA_CHANNEL_POSITION_TOP_CENTER 44 +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT 45 +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT 46 +#define MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER 47 +#define MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT 48 +#define MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT 49 +#define MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER 50 +#define MA_PA_CHANNEL_POSITION_LEFT MA_PA_CHANNEL_POSITION_FRONT_LEFT +#define MA_PA_CHANNEL_POSITION_RIGHT MA_PA_CHANNEL_POSITION_FRONT_RIGHT +#define MA_PA_CHANNEL_POSITION_CENTER MA_PA_CHANNEL_POSITION_FRONT_CENTER +#define MA_PA_CHANNEL_POSITION_SUBWOOFER MA_PA_CHANNEL_POSITION_LFE + +typedef int ma_pa_channel_map_def_t; +#define MA_PA_CHANNEL_MAP_AIFF 0 +#define MA_PA_CHANNEL_MAP_ALSA 1 +#define MA_PA_CHANNEL_MAP_AUX 2 +#define MA_PA_CHANNEL_MAP_WAVEEX 3 +#define MA_PA_CHANNEL_MAP_OSS 4 +#define MA_PA_CHANNEL_MAP_DEFAULT MA_PA_CHANNEL_MAP_AIFF + +typedef int ma_pa_sample_format_t; +#define MA_PA_SAMPLE_INVALID -1 +#define MA_PA_SAMPLE_U8 0 +#define MA_PA_SAMPLE_ALAW 1 +#define MA_PA_SAMPLE_ULAW 2 +#define MA_PA_SAMPLE_S16LE 3 +#define MA_PA_SAMPLE_S16BE 4 +#define MA_PA_SAMPLE_FLOAT32LE 5 +#define MA_PA_SAMPLE_FLOAT32BE 6 +#define MA_PA_SAMPLE_S32LE 7 +#define MA_PA_SAMPLE_S32BE 8 +#define MA_PA_SAMPLE_S24LE 9 +#define MA_PA_SAMPLE_S24BE 10 +#define MA_PA_SAMPLE_S24_32LE 11 +#define MA_PA_SAMPLE_S24_32BE 12 + +typedef struct ma_pa_mainloop ma_pa_mainloop; +typedef struct ma_pa_threaded_mainloop ma_pa_threaded_mainloop; +typedef struct ma_pa_mainloop_api ma_pa_mainloop_api; +typedef struct ma_pa_context ma_pa_context; +typedef struct ma_pa_operation ma_pa_operation; +typedef struct ma_pa_stream ma_pa_stream; +typedef struct ma_pa_spawn_api ma_pa_spawn_api; + +typedef struct +{ + ma_uint32 maxlength; + ma_uint32 tlength; + ma_uint32 prebuf; + ma_uint32 minreq; + ma_uint32 fragsize; +} ma_pa_buffer_attr; + +typedef struct +{ + ma_uint8 channels; + ma_pa_channel_position_t map[MA_PA_CHANNELS_MAX]; +} ma_pa_channel_map; + +typedef struct +{ + ma_uint8 channels; + ma_uint32 values[MA_PA_CHANNELS_MAX]; +} ma_pa_cvolume; + +typedef struct +{ + ma_pa_sample_format_t format; + ma_uint32 rate; + ma_uint8 channels; +} ma_pa_sample_spec; + +typedef struct +{ + const char* name; + ma_uint32 index; + const char* description; + ma_pa_sample_spec sample_spec; + ma_pa_channel_map channel_map; + ma_uint32 owner_module; + ma_pa_cvolume volume; + int mute; + ma_uint32 monitor_source; + const char* monitor_source_name; + ma_uint64 latency; + const char* driver; + ma_pa_sink_flags_t flags; + void* proplist; + ma_uint64 configured_latency; + ma_uint32 base_volume; + ma_pa_sink_state_t state; + ma_uint32 n_volume_steps; + ma_uint32 card; + ma_uint32 n_ports; + void** ports; + void* active_port; + ma_uint8 n_formats; + void** formats; +} ma_pa_sink_info; + +typedef struct +{ + const char *name; + ma_uint32 index; + const char *description; + ma_pa_sample_spec sample_spec; + ma_pa_channel_map channel_map; + ma_uint32 owner_module; + ma_pa_cvolume volume; + int mute; + ma_uint32 monitor_of_sink; + const char *monitor_of_sink_name; + ma_uint64 latency; + const char *driver; + ma_pa_source_flags_t flags; + void* proplist; + ma_uint64 configured_latency; + ma_uint32 base_volume; + ma_pa_source_state_t state; + ma_uint32 n_volume_steps; + ma_uint32 card; + ma_uint32 n_ports; + void** ports; + void* active_port; + ma_uint8 n_formats; + void** formats; +} ma_pa_source_info; + +typedef void (* ma_pa_context_notify_cb_t)(ma_pa_context* c, void* userdata); +typedef void (* ma_pa_sink_info_cb_t) (ma_pa_context* c, const ma_pa_sink_info* i, int eol, void* userdata); +typedef void (* ma_pa_source_info_cb_t) (ma_pa_context* c, const ma_pa_source_info* i, int eol, void* userdata); +typedef void (* ma_pa_stream_success_cb_t)(ma_pa_stream* s, int success, void* userdata); +typedef void (* ma_pa_stream_request_cb_t)(ma_pa_stream* s, size_t nbytes, void* userdata); +typedef void (* ma_pa_stream_notify_cb_t) (ma_pa_stream* s, void* userdata); +typedef void (* ma_pa_free_cb_t) (void* p); +#endif + + +typedef ma_pa_mainloop* (* ma_pa_mainloop_new_proc )(void); +typedef void (* ma_pa_mainloop_free_proc )(ma_pa_mainloop* m); +typedef void (* ma_pa_mainloop_quit_proc )(ma_pa_mainloop* m, int retval); +typedef ma_pa_mainloop_api* (* ma_pa_mainloop_get_api_proc )(ma_pa_mainloop* m); +typedef int (* ma_pa_mainloop_iterate_proc )(ma_pa_mainloop* m, int block, int* retval); +typedef void (* ma_pa_mainloop_wakeup_proc )(ma_pa_mainloop* m); +typedef ma_pa_threaded_mainloop* (* ma_pa_threaded_mainloop_new_proc )(void); +typedef void (* ma_pa_threaded_mainloop_free_proc )(ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_start_proc )(ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_stop_proc )(ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_lock_proc )(ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_unlock_proc )(ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_wait_proc )(ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_signal_proc )(ma_pa_threaded_mainloop* m, int wait_for_accept); +typedef void (* ma_pa_threaded_mainloop_accept_proc )(ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_get_retval_proc )(const ma_pa_threaded_mainloop* m); +typedef ma_pa_mainloop_api* (* ma_pa_threaded_mainloop_get_api_proc )(ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_in_thread_proc )(ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_set_name_proc )(ma_pa_threaded_mainloop* m, const char* name); +typedef ma_pa_context* (* ma_pa_context_new_proc )(ma_pa_mainloop_api* mainloop, const char* name); +typedef void (* ma_pa_context_unref_proc )(ma_pa_context* c); +typedef int (* ma_pa_context_connect_proc )(ma_pa_context* c, const char* server, ma_pa_context_flags_t flags, const ma_pa_spawn_api* api); +typedef void (* ma_pa_context_disconnect_proc )(ma_pa_context* c); +typedef void (* ma_pa_context_set_state_callback_proc )(ma_pa_context* c, ma_pa_context_notify_cb_t cb, void* userdata); +typedef ma_pa_context_state_t (* ma_pa_context_get_state_proc )(const ma_pa_context* c); +typedef ma_pa_operation* (* ma_pa_context_get_sink_info_list_proc )(ma_pa_context* c, ma_pa_sink_info_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_context_get_source_info_list_proc )(ma_pa_context* c, ma_pa_source_info_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_context_get_sink_info_by_name_proc )(ma_pa_context* c, const char* name, ma_pa_sink_info_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_context_get_source_info_by_name_proc)(ma_pa_context* c, const char* name, ma_pa_source_info_cb_t cb, void* userdata); +typedef void (* ma_pa_operation_unref_proc )(ma_pa_operation* o); +typedef ma_pa_operation_state_t (* ma_pa_operation_get_state_proc )(const ma_pa_operation* o); +typedef ma_pa_channel_map* (* ma_pa_channel_map_init_extend_proc )(ma_pa_channel_map* m, unsigned channels, ma_pa_channel_map_def_t def); +typedef int (* ma_pa_channel_map_valid_proc )(const ma_pa_channel_map* m); +typedef int (* ma_pa_channel_map_compatible_proc )(const ma_pa_channel_map* m, const ma_pa_sample_spec* ss); +typedef ma_pa_stream* (* ma_pa_stream_new_proc )(ma_pa_context* c, const char* name, const ma_pa_sample_spec* ss, const ma_pa_channel_map* map); +typedef void (* ma_pa_stream_unref_proc )(ma_pa_stream* s); +typedef int (* ma_pa_stream_connect_playback_proc )(ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags, const ma_pa_cvolume* volume, ma_pa_stream* sync_stream); +typedef int (* ma_pa_stream_connect_record_proc )(ma_pa_stream* s, const char* dev, const ma_pa_buffer_attr* attr, ma_pa_stream_flags_t flags); +typedef int (* ma_pa_stream_disconnect_proc )(ma_pa_stream* s); +typedef ma_pa_stream_state_t (* ma_pa_stream_get_state_proc )(const ma_pa_stream* s); +typedef const ma_pa_sample_spec* (* ma_pa_stream_get_sample_spec_proc )(ma_pa_stream* s); +typedef const ma_pa_channel_map* (* ma_pa_stream_get_channel_map_proc )(ma_pa_stream* s); +typedef const ma_pa_buffer_attr* (* ma_pa_stream_get_buffer_attr_proc )(ma_pa_stream* s); +typedef ma_pa_operation* (* ma_pa_stream_set_buffer_attr_proc )(ma_pa_stream* s, const ma_pa_buffer_attr* attr, ma_pa_stream_success_cb_t cb, void* userdata); +typedef const char* (* ma_pa_stream_get_device_name_proc )(const ma_pa_stream* s); +typedef void (* ma_pa_stream_set_write_callback_proc )(ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); +typedef void (* ma_pa_stream_set_read_callback_proc )(ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); +typedef void (* ma_pa_stream_set_suspended_callback_proc )(ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); +typedef void (* ma_pa_stream_set_moved_callback_proc )(ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); +typedef int (* ma_pa_stream_is_suspended_proc )(const ma_pa_stream* s); +typedef ma_pa_operation* (* ma_pa_stream_flush_proc )(ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_stream_drain_proc )(ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); +typedef int (* ma_pa_stream_is_corked_proc )(const ma_pa_stream* s); +typedef ma_pa_operation* (* ma_pa_stream_cork_proc )(ma_pa_stream* s, int b, ma_pa_stream_success_cb_t cb, void* userdata); +typedef ma_pa_operation* (* ma_pa_stream_trigger_proc )(ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); +typedef int (* ma_pa_stream_begin_write_proc )(ma_pa_stream* s, void** data, size_t* nbytes); +typedef int (* ma_pa_stream_write_proc )(ma_pa_stream* s, const void* data, size_t nbytes, ma_pa_free_cb_t free_cb, int64_t offset, ma_pa_seek_mode_t seek); +typedef int (* ma_pa_stream_peek_proc )(ma_pa_stream* s, const void** data, size_t* nbytes); +typedef int (* ma_pa_stream_drop_proc )(ma_pa_stream* s); +typedef size_t (* ma_pa_stream_writable_size_proc )(const ma_pa_stream* s); +typedef size_t (* ma_pa_stream_readable_size_proc )(const ma_pa_stream* s); + + +typedef struct ma_context_state_pulseaudio +{ + ma_handle pulseSO; + ma_pa_mainloop_new_proc pa_mainloop_new; + ma_pa_mainloop_free_proc pa_mainloop_free; + ma_pa_mainloop_quit_proc pa_mainloop_quit; + ma_pa_mainloop_get_api_proc pa_mainloop_get_api; + ma_pa_mainloop_iterate_proc pa_mainloop_iterate; + ma_pa_mainloop_wakeup_proc pa_mainloop_wakeup; + ma_pa_threaded_mainloop_new_proc pa_threaded_mainloop_new; + ma_pa_threaded_mainloop_free_proc pa_threaded_mainloop_free; + ma_pa_threaded_mainloop_start_proc pa_threaded_mainloop_start; + ma_pa_threaded_mainloop_stop_proc pa_threaded_mainloop_stop; + ma_pa_threaded_mainloop_lock_proc pa_threaded_mainloop_lock; + ma_pa_threaded_mainloop_unlock_proc pa_threaded_mainloop_unlock; + ma_pa_threaded_mainloop_wait_proc pa_threaded_mainloop_wait; + ma_pa_threaded_mainloop_signal_proc pa_threaded_mainloop_signal; + ma_pa_threaded_mainloop_accept_proc pa_threaded_mainloop_accept; + ma_pa_threaded_mainloop_get_retval_proc pa_threaded_mainloop_get_retval; + ma_pa_threaded_mainloop_get_api_proc pa_threaded_mainloop_get_api; + ma_pa_threaded_mainloop_in_thread_proc pa_threaded_mainloop_in_thread; + ma_pa_threaded_mainloop_set_name_proc pa_threaded_mainloop_set_name; + ma_pa_context_new_proc pa_context_new; + ma_pa_context_unref_proc pa_context_unref; + ma_pa_context_connect_proc pa_context_connect; + ma_pa_context_disconnect_proc pa_context_disconnect; + ma_pa_context_set_state_callback_proc pa_context_set_state_callback; + ma_pa_context_get_state_proc pa_context_get_state; + ma_pa_context_get_sink_info_list_proc pa_context_get_sink_info_list; + ma_pa_context_get_source_info_list_proc pa_context_get_source_info_list; + ma_pa_context_get_sink_info_by_name_proc pa_context_get_sink_info_by_name; + ma_pa_context_get_source_info_by_name_proc pa_context_get_source_info_by_name; + ma_pa_operation_unref_proc pa_operation_unref; + ma_pa_operation_get_state_proc pa_operation_get_state; + ma_pa_channel_map_init_extend_proc pa_channel_map_init_extend; + ma_pa_channel_map_valid_proc pa_channel_map_valid; + ma_pa_channel_map_compatible_proc pa_channel_map_compatible; + ma_pa_stream_new_proc pa_stream_new; + ma_pa_stream_unref_proc pa_stream_unref; + ma_pa_stream_connect_playback_proc pa_stream_connect_playback; + ma_pa_stream_connect_record_proc pa_stream_connect_record; + ma_pa_stream_disconnect_proc pa_stream_disconnect; + ma_pa_stream_get_state_proc pa_stream_get_state; + ma_pa_stream_get_sample_spec_proc pa_stream_get_sample_spec; + ma_pa_stream_get_channel_map_proc pa_stream_get_channel_map; + ma_pa_stream_get_buffer_attr_proc pa_stream_get_buffer_attr; + ma_pa_stream_set_buffer_attr_proc pa_stream_set_buffer_attr; + ma_pa_stream_get_device_name_proc pa_stream_get_device_name; + ma_pa_stream_set_write_callback_proc pa_stream_set_write_callback; + ma_pa_stream_set_read_callback_proc pa_stream_set_read_callback; + ma_pa_stream_set_suspended_callback_proc pa_stream_set_suspended_callback; + ma_pa_stream_set_moved_callback_proc pa_stream_set_moved_callback; + ma_pa_stream_is_suspended_proc pa_stream_is_suspended; + ma_pa_stream_flush_proc pa_stream_flush; + ma_pa_stream_drain_proc pa_stream_drain; + ma_pa_stream_is_corked_proc pa_stream_is_corked; + ma_pa_stream_cork_proc pa_stream_cork; + ma_pa_stream_trigger_proc pa_stream_trigger; + ma_pa_stream_begin_write_proc pa_stream_begin_write; + ma_pa_stream_write_proc pa_stream_write; + ma_pa_stream_peek_proc pa_stream_peek; + ma_pa_stream_drop_proc pa_stream_drop; + ma_pa_stream_writable_size_proc pa_stream_writable_size; + ma_pa_stream_readable_size_proc pa_stream_readable_size; + + ma_pa_mainloop* pMainLoop; + ma_pa_context* pPulseContext; + char* pApplicationName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ + char* pServerName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ +} ma_context_state_pulseaudio; + +typedef struct ma_device_state_pulseaudio +{ + ma_pa_mainloop* pMainLoop; + ma_pa_context* pPulseContext; + struct + { + ma_pa_stream* pStream; + ma_format format; + ma_uint32 channels; + } capture; + struct + { + ma_pa_stream* pStream; + ma_format format; + ma_uint32 channels; + } playback; +} ma_device_state_pulseaudio; + + +typedef struct +{ + ma_uint32 count; + ma_uint32 capacity; + ma_device_info* pInfo; +} ma_pulseaudio_device_enum_data; + + +static ma_result ma_result_from_pulseaudio(int result) +{ + if (result < 0) { + return MA_ERROR; + } + + switch (result) { + case MA_PA_OK: return MA_SUCCESS; + case MA_PA_ERR_ACCESS: return MA_ACCESS_DENIED; + case MA_PA_ERR_INVALID: return MA_INVALID_ARGS; + case MA_PA_ERR_NOENTITY: return MA_NO_DEVICE; + default: return MA_ERROR; + } +} + +static ma_pa_sample_format_t ma_format_to_pulseaudio(ma_format format) +{ + if (ma_is_little_endian()) { + switch (format) { + case ma_format_s16: return MA_PA_SAMPLE_S16LE; + case ma_format_s24: return MA_PA_SAMPLE_S24LE; + case ma_format_s32: return MA_PA_SAMPLE_S32LE; + case ma_format_f32: return MA_PA_SAMPLE_FLOAT32LE; + default: break; + } + } else { + switch (format) { + case ma_format_s16: return MA_PA_SAMPLE_S16BE; + case ma_format_s24: return MA_PA_SAMPLE_S24BE; + case ma_format_s32: return MA_PA_SAMPLE_S32BE; + case ma_format_f32: return MA_PA_SAMPLE_FLOAT32BE; + default: break; + } + } + + /* Endian agnostic. */ + switch (format) { + case ma_format_u8: return MA_PA_SAMPLE_U8; + default: return MA_PA_SAMPLE_INVALID; + } +} + +static ma_format ma_format_from_pulseaudio(ma_pa_sample_format_t format) +{ + if (ma_is_little_endian()) { + switch (format) { + case MA_PA_SAMPLE_S16LE: return ma_format_s16; + case MA_PA_SAMPLE_S24LE: return ma_format_s24; + case MA_PA_SAMPLE_S32LE: return ma_format_s32; + case MA_PA_SAMPLE_FLOAT32LE: return ma_format_f32; + default: break; + } + } else { + switch (format) { + case MA_PA_SAMPLE_S16BE: return ma_format_s16; + case MA_PA_SAMPLE_S24BE: return ma_format_s24; + case MA_PA_SAMPLE_S32BE: return ma_format_s32; + case MA_PA_SAMPLE_FLOAT32BE: return ma_format_f32; + default: break; + } + } + + /* Endian agnostic. */ + switch (format) { + case MA_PA_SAMPLE_U8: return ma_format_u8; + default: return ma_format_unknown; + } +} + +static ma_channel ma_channel_position_from_pulseaudio(ma_pa_channel_position_t position) +{ + switch (position) + { + case MA_PA_CHANNEL_POSITION_INVALID: return MA_CHANNEL_NONE; + case MA_PA_CHANNEL_POSITION_MONO: return MA_CHANNEL_MONO; + case MA_PA_CHANNEL_POSITION_FRONT_LEFT: return MA_CHANNEL_FRONT_LEFT; + case MA_PA_CHANNEL_POSITION_FRONT_RIGHT: return MA_CHANNEL_FRONT_RIGHT; + case MA_PA_CHANNEL_POSITION_FRONT_CENTER: return MA_CHANNEL_FRONT_CENTER; + case MA_PA_CHANNEL_POSITION_REAR_CENTER: return MA_CHANNEL_BACK_CENTER; + case MA_PA_CHANNEL_POSITION_REAR_LEFT: return MA_CHANNEL_BACK_LEFT; + case MA_PA_CHANNEL_POSITION_REAR_RIGHT: return MA_CHANNEL_BACK_RIGHT; + case MA_PA_CHANNEL_POSITION_LFE: return MA_CHANNEL_LFE; + case MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return MA_CHANNEL_FRONT_LEFT_CENTER; + case MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case MA_PA_CHANNEL_POSITION_SIDE_LEFT: return MA_CHANNEL_SIDE_LEFT; + case MA_PA_CHANNEL_POSITION_SIDE_RIGHT: return MA_CHANNEL_SIDE_RIGHT; + case MA_PA_CHANNEL_POSITION_AUX0: return MA_CHANNEL_AUX_0; + case MA_PA_CHANNEL_POSITION_AUX1: return MA_CHANNEL_AUX_1; + case MA_PA_CHANNEL_POSITION_AUX2: return MA_CHANNEL_AUX_2; + case MA_PA_CHANNEL_POSITION_AUX3: return MA_CHANNEL_AUX_3; + case MA_PA_CHANNEL_POSITION_AUX4: return MA_CHANNEL_AUX_4; + case MA_PA_CHANNEL_POSITION_AUX5: return MA_CHANNEL_AUX_5; + case MA_PA_CHANNEL_POSITION_AUX6: return MA_CHANNEL_AUX_6; + case MA_PA_CHANNEL_POSITION_AUX7: return MA_CHANNEL_AUX_7; + case MA_PA_CHANNEL_POSITION_AUX8: return MA_CHANNEL_AUX_8; + case MA_PA_CHANNEL_POSITION_AUX9: return MA_CHANNEL_AUX_9; + case MA_PA_CHANNEL_POSITION_AUX10: return MA_CHANNEL_AUX_10; + case MA_PA_CHANNEL_POSITION_AUX11: return MA_CHANNEL_AUX_11; + case MA_PA_CHANNEL_POSITION_AUX12: return MA_CHANNEL_AUX_12; + case MA_PA_CHANNEL_POSITION_AUX13: return MA_CHANNEL_AUX_13; + case MA_PA_CHANNEL_POSITION_AUX14: return MA_CHANNEL_AUX_14; + case MA_PA_CHANNEL_POSITION_AUX15: return MA_CHANNEL_AUX_15; + case MA_PA_CHANNEL_POSITION_AUX16: return MA_CHANNEL_AUX_16; + case MA_PA_CHANNEL_POSITION_AUX17: return MA_CHANNEL_AUX_17; + case MA_PA_CHANNEL_POSITION_AUX18: return MA_CHANNEL_AUX_18; + case MA_PA_CHANNEL_POSITION_AUX19: return MA_CHANNEL_AUX_19; + case MA_PA_CHANNEL_POSITION_AUX20: return MA_CHANNEL_AUX_20; + case MA_PA_CHANNEL_POSITION_AUX21: return MA_CHANNEL_AUX_21; + case MA_PA_CHANNEL_POSITION_AUX22: return MA_CHANNEL_AUX_22; + case MA_PA_CHANNEL_POSITION_AUX23: return MA_CHANNEL_AUX_23; + case MA_PA_CHANNEL_POSITION_AUX24: return MA_CHANNEL_AUX_24; + case MA_PA_CHANNEL_POSITION_AUX25: return MA_CHANNEL_AUX_25; + case MA_PA_CHANNEL_POSITION_AUX26: return MA_CHANNEL_AUX_26; + case MA_PA_CHANNEL_POSITION_AUX27: return MA_CHANNEL_AUX_27; + case MA_PA_CHANNEL_POSITION_AUX28: return MA_CHANNEL_AUX_28; + case MA_PA_CHANNEL_POSITION_AUX29: return MA_CHANNEL_AUX_29; + case MA_PA_CHANNEL_POSITION_AUX30: return MA_CHANNEL_AUX_30; + case MA_PA_CHANNEL_POSITION_AUX31: return MA_CHANNEL_AUX_31; + case MA_PA_CHANNEL_POSITION_TOP_CENTER: return MA_CHANNEL_TOP_CENTER; + case MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return MA_CHANNEL_TOP_FRONT_LEFT; + case MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return MA_CHANNEL_TOP_FRONT_RIGHT; + case MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return MA_CHANNEL_TOP_FRONT_CENTER; + case MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT: return MA_CHANNEL_TOP_BACK_LEFT; + case MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return MA_CHANNEL_TOP_BACK_RIGHT; + case MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER: return MA_CHANNEL_TOP_BACK_CENTER; + default: return MA_CHANNEL_NONE; + } +} + +#if 0 +static ma_pa_channel_position_t ma_channel_position_to_pulseaudio(ma_channel position) +{ + switch (position) + { + case MA_CHANNEL_NONE: return MA_PA_CHANNEL_POSITION_INVALID; + case MA_CHANNEL_FRONT_LEFT: return MA_PA_CHANNEL_POSITION_FRONT_LEFT; + case MA_CHANNEL_FRONT_RIGHT: return MA_PA_CHANNEL_POSITION_FRONT_RIGHT; + case MA_CHANNEL_FRONT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_CENTER; + case MA_CHANNEL_LFE: return MA_PA_CHANNEL_POSITION_LFE; + case MA_CHANNEL_BACK_LEFT: return MA_PA_CHANNEL_POSITION_REAR_LEFT; + case MA_CHANNEL_BACK_RIGHT: return MA_PA_CHANNEL_POSITION_REAR_RIGHT; + case MA_CHANNEL_FRONT_LEFT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case MA_CHANNEL_FRONT_RIGHT_CENTER: return MA_PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case MA_CHANNEL_BACK_CENTER: return MA_PA_CHANNEL_POSITION_REAR_CENTER; + case MA_CHANNEL_SIDE_LEFT: return MA_PA_CHANNEL_POSITION_SIDE_LEFT; + case MA_CHANNEL_SIDE_RIGHT: return MA_PA_CHANNEL_POSITION_SIDE_RIGHT; + case MA_CHANNEL_TOP_CENTER: return MA_PA_CHANNEL_POSITION_TOP_CENTER; + case MA_CHANNEL_TOP_FRONT_LEFT: return MA_PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case MA_CHANNEL_TOP_FRONT_CENTER: return MA_PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case MA_CHANNEL_TOP_FRONT_RIGHT: return MA_PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case MA_CHANNEL_TOP_BACK_LEFT: return MA_PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case MA_CHANNEL_TOP_BACK_CENTER: return MA_PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case MA_CHANNEL_TOP_BACK_RIGHT: return MA_PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + case MA_CHANNEL_19: return MA_PA_CHANNEL_POSITION_AUX18; + case MA_CHANNEL_20: return MA_PA_CHANNEL_POSITION_AUX19; + case MA_CHANNEL_21: return MA_PA_CHANNEL_POSITION_AUX20; + case MA_CHANNEL_22: return MA_PA_CHANNEL_POSITION_AUX21; + case MA_CHANNEL_23: return MA_PA_CHANNEL_POSITION_AUX22; + case MA_CHANNEL_24: return MA_PA_CHANNEL_POSITION_AUX23; + case MA_CHANNEL_25: return MA_PA_CHANNEL_POSITION_AUX24; + case MA_CHANNEL_26: return MA_PA_CHANNEL_POSITION_AUX25; + case MA_CHANNEL_27: return MA_PA_CHANNEL_POSITION_AUX26; + case MA_CHANNEL_28: return MA_PA_CHANNEL_POSITION_AUX27; + case MA_CHANNEL_29: return MA_PA_CHANNEL_POSITION_AUX28; + case MA_CHANNEL_30: return MA_PA_CHANNEL_POSITION_AUX29; + case MA_CHANNEL_31: return MA_PA_CHANNEL_POSITION_AUX30; + case MA_CHANNEL_32: return MA_PA_CHANNEL_POSITION_AUX31; + default: return (ma_pa_channel_position_t)position; + } +} +#endif + +static ma_result ma_wait_for_operation__pulseaudio(ma_context_state_pulseaudio* pContextStatePulseAudio, ma_pa_mainloop* pMainLoop, ma_pa_operation* pOP) +{ + int resultPA; + ma_pa_operation_state_t state; + + MA_ASSERT(pContextStatePulseAudio != NULL); + MA_ASSERT(pOP != NULL); + + for (;;) { + state = pContextStatePulseAudio->pa_operation_get_state(pOP); + if (state != MA_PA_OPERATION_RUNNING) { + break; /* Done. */ + } + + resultPA = pContextStatePulseAudio->pa_mainloop_iterate(pMainLoop, 1, NULL); + if (resultPA < 0) { + return ma_result_from_pulseaudio(resultPA); + } + } + + return MA_SUCCESS; +} + +static ma_result ma_wait_for_operation_and_unref__pulseaudio(ma_context_state_pulseaudio* pContextStatePulseAudio, ma_pa_mainloop* pMainLoop, ma_pa_operation* pOP) +{ + ma_result result; + + if (pOP == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_wait_for_operation__pulseaudio(pContextStatePulseAudio, pMainLoop, pOP); + pContextStatePulseAudio->pa_operation_unref(pOP); + + return result; +} + +static ma_result ma_wait_for_pa_context_to_connect__pulseaudio(ma_context* pContext, ma_context_state_pulseaudio* pContextStatePulseAudio, ma_pa_mainloop* pMainLoop, ma_pa_context* pPulseContext) +{ + int resultPA; + ma_pa_context_state_t state; + + for (;;) { + state = pContextStatePulseAudio->pa_context_get_state(pPulseContext); + if (state == MA_PA_CONTEXT_READY) { + break; /* Done. */ + } + + if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context."); + return MA_ERROR; + } + + resultPA = pContextStatePulseAudio->pa_mainloop_iterate(pMainLoop, 1, NULL); + if (resultPA < 0) { + return ma_result_from_pulseaudio(resultPA); + } + } + + /* Should never get here. */ + return MA_SUCCESS; +} + +static ma_result ma_wait_for_pa_stream_to_connect__pulseaudio(ma_context* pContext, ma_context_state_pulseaudio* pContextStatePulseAudio, ma_pa_mainloop* pMainLoop, ma_pa_stream* pStream) +{ + int resultPA; + ma_pa_stream_state_t state; + + for (;;) { + state = pContextStatePulseAudio->pa_stream_get_state(pStream); + if (state == MA_PA_STREAM_READY) { + break; /* Done. */ + } + + if (state == MA_PA_STREAM_FAILED || state == MA_PA_STREAM_TERMINATED) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream."); + return MA_ERROR; + } + + resultPA = pContextStatePulseAudio->pa_mainloop_iterate(pMainLoop, 1, NULL); + if (resultPA < 0) { + return ma_result_from_pulseaudio(resultPA); + } + } + + return MA_SUCCESS; +} + + +static ma_result ma_init_pa_mainloop_and_pa_context__pulseaudio(ma_context* pContext, ma_context_state_pulseaudio* pContextStatePulseAudio, const char* pApplicationName, const char* pServerName, ma_bool32 tryAutoSpawn, ma_pa_mainloop** ppMainLoop, ma_pa_context** ppPulseContext) +{ + ma_result result; + ma_pa_mainloop* pMainLoop; + ma_pa_context* pPulseContext; + + MA_ASSERT(ppMainLoop != NULL); + MA_ASSERT(ppPulseContext != NULL); + + pMainLoop = pContextStatePulseAudio->pa_mainloop_new(); + if (pMainLoop == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop."); + return MA_FAILED_TO_INIT_BACKEND; + } + + pPulseContext = pContextStatePulseAudio->pa_context_new(pContextStatePulseAudio->pa_mainloop_get_api(pMainLoop), pApplicationName); + if (pPulseContext == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context."); + pContextStatePulseAudio->pa_mainloop_free(pMainLoop); + return MA_FAILED_TO_INIT_BACKEND; + } + + result = ma_result_from_pulseaudio(pContextStatePulseAudio->pa_context_connect(pPulseContext, pServerName, (tryAutoSpawn) ? MA_PA_CONTEXT_NOFLAGS : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); + pContextStatePulseAudio->pa_context_unref(pPulseContext); + pContextStatePulseAudio->pa_mainloop_free(pMainLoop); + return result; + } + + /* PulseAudio uses an asynchronous initialization process, whereas miniaudio is synchronous. We need to wait for PulseAudio to finish. */ + result = ma_wait_for_pa_context_to_connect__pulseaudio(pContext, pContextStatePulseAudio, pMainLoop, pPulseContext); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Waiting for connection failed."); + pContextStatePulseAudio->pa_context_unref(pPulseContext); + pContextStatePulseAudio->pa_mainloop_free(pMainLoop); + return result; + } + + *ppMainLoop = pMainLoop; + *ppPulseContext = pPulseContext; + + return MA_SUCCESS; +} + + +static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + ma_pa_sink_info* pInfoOut; + + if (endOfList > 0) { + return; + } + + /* + There has been a report that indicates that pInfo can be null which results + in a null pointer dereference below. We'll check for this for safety. + */ + if (pInfo == NULL) { + return; + } + + pInfoOut = (ma_pa_sink_info*)pUserData; + MA_ASSERT(pInfoOut != NULL); + + *pInfoOut = *pInfo; + + (void)pPulseContext; /* Unused. */ +} + +static void ma_device_source_info_callback(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + ma_pa_source_info* pInfoOut; + + if (endOfList > 0) { + return; + } + + /* + There has been a report that indicates that pInfo can be null which results + in a null pointer dereference below. We'll check for this for safety. + */ + if (pInfo == NULL) { + return; + } + + pInfoOut = (ma_pa_source_info*)pUserData; + MA_ASSERT(pInfoOut != NULL); + + *pInfoOut = *pInfo; + + (void)pPulseContext; /* Unused. */ +} + + +static ma_result ma_context_get_sink_info__pulseaudio(ma_context_state_pulseaudio* pContextStatePulseAudio, const char* pDeviceName, ma_pa_sink_info* pSinkInfo) +{ + ma_pa_operation* pOP; + + pOP = pContextStatePulseAudio->pa_context_get_sink_info_by_name(pContextStatePulseAudio->pPulseContext, pDeviceName, ma_device_sink_info_callback, pSinkInfo); + if (pOP == NULL) { + return MA_ERROR; + } + + return ma_wait_for_operation_and_unref__pulseaudio(pContextStatePulseAudio, pContextStatePulseAudio->pMainLoop, pOP); +} + +static ma_result ma_context_get_source_info__pulseaudio(ma_context_state_pulseaudio* pContextStatePulseAudio, const char* pDeviceName, ma_pa_source_info* pSourceInfo) +{ + ma_pa_operation* pOP; + + pOP = pContextStatePulseAudio->pa_context_get_source_info_by_name(pContextStatePulseAudio->pPulseContext, pDeviceName, ma_device_source_info_callback, pSourceInfo); + if (pOP == NULL) { + return MA_ERROR; + } + + return ma_wait_for_operation_and_unref__pulseaudio(pContextStatePulseAudio, pContextStatePulseAudio->pMainLoop, pOP); +} + +static ma_result ma_context_get_default_device_index__pulseaudio(ma_context_state_pulseaudio* pContextStatePulseAudio, ma_device_type deviceType, ma_uint32* pIndex) +{ + ma_result result; + + MA_ASSERT(pContextStatePulseAudio != NULL); + MA_ASSERT(pIndex != NULL); + + if (pIndex != NULL) { + *pIndex = (ma_uint32)-1; + } + + if (deviceType == ma_device_type_playback) { + ma_pa_sink_info sinkInfo; + result = ma_context_get_sink_info__pulseaudio(pContextStatePulseAudio, NULL, &sinkInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pIndex != NULL) { + *pIndex = sinkInfo.index; + } + } + + if (deviceType == ma_device_type_capture) { + ma_pa_source_info sourceInfo; + result = ma_context_get_source_info__pulseaudio(pContextStatePulseAudio, NULL, &sourceInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pIndex != NULL) { + *pIndex = sourceInfo.index; + } + } + + return MA_SUCCESS; +} + + +static ma_context_state_pulseaudio* ma_context_get_backend_state__pulseaudio(ma_context* pContext) +{ + return (ma_context_state_pulseaudio*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_pulseaudio* ma_device_get_backend_state__pulseaudio(ma_device* pDevice) +{ + return (ma_device_state_pulseaudio*)ma_device_get_backend_state(pDevice); +} + + +static void ma_backend_info__pulseaudio(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "PulseAudio"; +} + +static ma_result ma_context_init__pulseaudio(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_pulseaudio* pContextStatePulseAudio; + const ma_context_config_pulseaudio* pContextConfigPulseAudio = (const ma_context_config_pulseaudio*)pContextBackendConfig; + ma_context_config_pulseaudio defaultConfigPulseAudio; + ma_log* pLog = ma_context_get_log(pContext); + ma_result result; + ma_bool32 tryAutoSpawn = MA_FALSE; + size_t applicationNameAllocSize = 0; + size_t serverNameAllocSize = 0; + + if (pContextConfigPulseAudio == NULL) { + defaultConfigPulseAudio = ma_context_config_pulseaudio_init(); + pContextConfigPulseAudio = &defaultConfigPulseAudio; + } + + if (pContextConfigPulseAudio->pApplicationName != NULL) { + applicationNameAllocSize = ma_align_64(strlen(pContextConfigPulseAudio->pApplicationName) + 1); + } + if (pContextConfigPulseAudio->pServerName != NULL) { + serverNameAllocSize = ma_align_64(strlen(pContextConfigPulseAudio->pServerName) + 1); + } + + pContextStatePulseAudio = (ma_context_state_pulseaudio*)ma_calloc(ma_align_64(sizeof(*pContextStatePulseAudio)) + applicationNameAllocSize + serverNameAllocSize, ma_context_get_allocation_callbacks(pContext)); + if (pContextStatePulseAudio == NULL) { + return MA_OUT_OF_MEMORY; + } + + if (pContextConfigPulseAudio->pApplicationName != NULL) { + pContextStatePulseAudio->pApplicationName = (char*)ma_offset_ptr(pContextStatePulseAudio, ma_align_64(sizeof(*pContextStatePulseAudio))); + ma_strcpy_s(pContextStatePulseAudio->pApplicationName, applicationNameAllocSize, pContextConfigPulseAudio->pApplicationName); + } + if (pContextConfigPulseAudio->pServerName != NULL) { + pContextStatePulseAudio->pServerName = (char*)ma_offset_ptr(pContextStatePulseAudio, ma_align_64(sizeof(*pContextStatePulseAudio)) + applicationNameAllocSize); + ma_strcpy_s(pContextStatePulseAudio->pServerName, serverNameAllocSize, pContextConfigPulseAudio->pServerName); + } + + #ifndef MA_NO_RUNTIME_LINKING + { + const char* libpulseNames[] = { + "libpulse.so", + "libpulse.so.0" + }; + size_t i; + + + for (i = 0; i < ma_countof(libpulseNames); i += 1) { + pContextStatePulseAudio->pulseSO = ma_dlopen(pLog, libpulseNames[i]); + if (pContextStatePulseAudio->pulseSO != NULL) { + break; + } + } + + if (pContextStatePulseAudio->pulseSO == NULL) { + ma_free(pContextStatePulseAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } + + pContextStatePulseAudio->pa_mainloop_new = (ma_pa_mainloop_new_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_mainloop_new"); + pContextStatePulseAudio->pa_mainloop_free = (ma_pa_mainloop_free_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_mainloop_free"); + pContextStatePulseAudio->pa_mainloop_quit = (ma_pa_mainloop_quit_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_mainloop_quit"); + pContextStatePulseAudio->pa_mainloop_get_api = (ma_pa_mainloop_get_api_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_mainloop_get_api"); + pContextStatePulseAudio->pa_mainloop_iterate = (ma_pa_mainloop_iterate_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_mainloop_iterate"); + pContextStatePulseAudio->pa_mainloop_wakeup = (ma_pa_mainloop_wakeup_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_mainloop_wakeup"); + pContextStatePulseAudio->pa_threaded_mainloop_new = (ma_pa_threaded_mainloop_new_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_new"); + pContextStatePulseAudio->pa_threaded_mainloop_free = (ma_pa_threaded_mainloop_free_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_free"); + pContextStatePulseAudio->pa_threaded_mainloop_start = (ma_pa_threaded_mainloop_start_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_start"); + pContextStatePulseAudio->pa_threaded_mainloop_stop = (ma_pa_threaded_mainloop_stop_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_stop"); + pContextStatePulseAudio->pa_threaded_mainloop_lock = (ma_pa_threaded_mainloop_lock_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_lock"); + pContextStatePulseAudio->pa_threaded_mainloop_unlock = (ma_pa_threaded_mainloop_unlock_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_unlock"); + pContextStatePulseAudio->pa_threaded_mainloop_wait = (ma_pa_threaded_mainloop_wait_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_wait"); + pContextStatePulseAudio->pa_threaded_mainloop_signal = (ma_pa_threaded_mainloop_signal_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_signal"); + pContextStatePulseAudio->pa_threaded_mainloop_accept = (ma_pa_threaded_mainloop_accept_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_accept"); + pContextStatePulseAudio->pa_threaded_mainloop_get_retval = (ma_pa_threaded_mainloop_get_retval_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_get_retval"); + pContextStatePulseAudio->pa_threaded_mainloop_get_api = (ma_pa_threaded_mainloop_get_api_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_get_api"); + pContextStatePulseAudio->pa_threaded_mainloop_in_thread = (ma_pa_threaded_mainloop_in_thread_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_in_thread"); + pContextStatePulseAudio->pa_threaded_mainloop_set_name = (ma_pa_threaded_mainloop_set_name_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_threaded_mainloop_set_name"); + pContextStatePulseAudio->pa_context_new = (ma_pa_context_new_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_new"); + pContextStatePulseAudio->pa_context_unref = (ma_pa_context_unref_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_unref"); + pContextStatePulseAudio->pa_context_connect = (ma_pa_context_connect_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_connect"); + pContextStatePulseAudio->pa_context_disconnect = (ma_pa_context_disconnect_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_disconnect"); + pContextStatePulseAudio->pa_context_set_state_callback = (ma_pa_context_set_state_callback_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_set_state_callback"); + pContextStatePulseAudio->pa_context_get_state = (ma_pa_context_get_state_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_get_state"); + pContextStatePulseAudio->pa_context_get_sink_info_list = (ma_pa_context_get_sink_info_list_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_get_sink_info_list"); + pContextStatePulseAudio->pa_context_get_source_info_list = (ma_pa_context_get_source_info_list_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_get_source_info_list"); + pContextStatePulseAudio->pa_context_get_sink_info_by_name = (ma_pa_context_get_sink_info_by_name_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_get_sink_info_by_name"); + pContextStatePulseAudio->pa_context_get_source_info_by_name = (ma_pa_context_get_source_info_by_name_proc)ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_context_get_source_info_by_name"); + pContextStatePulseAudio->pa_operation_unref = (ma_pa_operation_unref_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_operation_unref"); + pContextStatePulseAudio->pa_operation_get_state = (ma_pa_operation_get_state_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_operation_get_state"); + pContextStatePulseAudio->pa_channel_map_init_extend = (ma_pa_channel_map_init_extend_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_channel_map_init_extend"); + pContextStatePulseAudio->pa_channel_map_valid = (ma_pa_channel_map_valid_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_channel_map_valid"); + pContextStatePulseAudio->pa_channel_map_compatible = (ma_pa_channel_map_compatible_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_channel_map_compatible"); + pContextStatePulseAudio->pa_stream_new = (ma_pa_stream_new_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_new"); + pContextStatePulseAudio->pa_stream_unref = (ma_pa_stream_unref_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_unref"); + pContextStatePulseAudio->pa_stream_connect_playback = (ma_pa_stream_connect_playback_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_connect_playback"); + pContextStatePulseAudio->pa_stream_connect_record = (ma_pa_stream_connect_record_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_connect_record"); + pContextStatePulseAudio->pa_stream_disconnect = (ma_pa_stream_disconnect_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_disconnect"); + pContextStatePulseAudio->pa_stream_get_state = (ma_pa_stream_get_state_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_get_state"); + pContextStatePulseAudio->pa_stream_get_sample_spec = (ma_pa_stream_get_sample_spec_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_get_sample_spec"); + pContextStatePulseAudio->pa_stream_get_channel_map = (ma_pa_stream_get_channel_map_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_get_channel_map"); + pContextStatePulseAudio->pa_stream_get_buffer_attr = (ma_pa_stream_get_buffer_attr_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_get_buffer_attr"); + pContextStatePulseAudio->pa_stream_set_buffer_attr = (ma_pa_stream_set_buffer_attr_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_set_buffer_attr"); + pContextStatePulseAudio->pa_stream_get_device_name = (ma_pa_stream_get_device_name_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_get_device_name"); + pContextStatePulseAudio->pa_stream_set_write_callback = (ma_pa_stream_set_write_callback_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_set_write_callback"); + pContextStatePulseAudio->pa_stream_set_read_callback = (ma_pa_stream_set_read_callback_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_set_read_callback"); + pContextStatePulseAudio->pa_stream_set_suspended_callback = (ma_pa_stream_set_suspended_callback_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_set_suspended_callback"); + pContextStatePulseAudio->pa_stream_set_moved_callback = (ma_pa_stream_set_moved_callback_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_set_moved_callback"); + pContextStatePulseAudio->pa_stream_is_suspended = (ma_pa_stream_is_suspended_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_is_suspended"); + pContextStatePulseAudio->pa_stream_flush = (ma_pa_stream_flush_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_flush"); + pContextStatePulseAudio->pa_stream_drain = (ma_pa_stream_drain_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_drain"); + pContextStatePulseAudio->pa_stream_is_corked = (ma_pa_stream_is_corked_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_is_corked"); + pContextStatePulseAudio->pa_stream_cork = (ma_pa_stream_cork_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_cork"); + pContextStatePulseAudio->pa_stream_trigger = (ma_pa_stream_trigger_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_trigger"); + pContextStatePulseAudio->pa_stream_begin_write = (ma_pa_stream_begin_write_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_begin_write"); + pContextStatePulseAudio->pa_stream_write = (ma_pa_stream_write_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_write"); + pContextStatePulseAudio->pa_stream_peek = (ma_pa_stream_peek_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_peek"); + pContextStatePulseAudio->pa_stream_drop = (ma_pa_stream_drop_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_drop"); + pContextStatePulseAudio->pa_stream_writable_size = (ma_pa_stream_writable_size_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_writable_size"); + pContextStatePulseAudio->pa_stream_readable_size = (ma_pa_stream_readable_size_proc )ma_dlsym(pLog, pContextStatePulseAudio->pulseSO, "pa_stream_readable_size"); + } + #else + { + (void)pLog; + + /* This strange assignment system is just for type safety. */ + ma_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new; + ma_pa_mainloop_free_proc _pa_mainloop_free = pa_mainloop_free; + ma_pa_mainloop_quit_proc _pa_mainloop_quit = pa_mainloop_quit; + ma_pa_mainloop_get_api_proc _pa_mainloop_get_api = pa_mainloop_get_api; + ma_pa_mainloop_iterate_proc _pa_mainloop_iterate = pa_mainloop_iterate; + ma_pa_mainloop_wakeup_proc _pa_mainloop_wakeup = pa_mainloop_wakeup; + ma_pa_threaded_mainloop_new_proc _pa_threaded_mainloop_new = pa_threaded_mainloop_new; + ma_pa_threaded_mainloop_free_proc _pa_threaded_mainloop_free = pa_threaded_mainloop_free; + ma_pa_threaded_mainloop_start_proc _pa_threaded_mainloop_start = pa_threaded_mainloop_start; + ma_pa_threaded_mainloop_stop_proc _pa_threaded_mainloop_stop = pa_threaded_mainloop_stop; + ma_pa_threaded_mainloop_lock_proc _pa_threaded_mainloop_lock = pa_threaded_mainloop_lock; + ma_pa_threaded_mainloop_unlock_proc _pa_threaded_mainloop_unlock = pa_threaded_mainloop_unlock; + ma_pa_threaded_mainloop_wait_proc _pa_threaded_mainloop_wait = pa_threaded_mainloop_wait; + ma_pa_threaded_mainloop_signal_proc _pa_threaded_mainloop_signal = pa_threaded_mainloop_signal; + ma_pa_threaded_mainloop_accept_proc _pa_threaded_mainloop_accept = pa_threaded_mainloop_accept; + ma_pa_threaded_mainloop_get_retval_proc _pa_threaded_mainloop_get_retval = pa_threaded_mainloop_get_retval; + ma_pa_threaded_mainloop_get_api_proc _pa_threaded_mainloop_get_api = pa_threaded_mainloop_get_api; + ma_pa_threaded_mainloop_in_thread_proc _pa_threaded_mainloop_in_thread = pa_threaded_mainloop_in_thread; + ma_pa_threaded_mainloop_set_name_proc _pa_threaded_mainloop_set_name = pa_threaded_mainloop_set_name; + ma_pa_context_new_proc _pa_context_new = pa_context_new; + ma_pa_context_unref_proc _pa_context_unref = pa_context_unref; + ma_pa_context_connect_proc _pa_context_connect = pa_context_connect; + ma_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect; + ma_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback; + ma_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state; + ma_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list; + ma_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list; + ma_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name; + ma_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name = pa_context_get_source_info_by_name; + ma_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref; + ma_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state; + ma_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend; + ma_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid; + ma_pa_channel_map_compatible_proc _pa_channel_map_compatible = pa_channel_map_compatible; + ma_pa_stream_new_proc _pa_stream_new = pa_stream_new; + ma_pa_stream_unref_proc _pa_stream_unref = pa_stream_unref; + ma_pa_stream_connect_playback_proc _pa_stream_connect_playback = pa_stream_connect_playback; + ma_pa_stream_connect_record_proc _pa_stream_connect_record = pa_stream_connect_record; + ma_pa_stream_disconnect_proc _pa_stream_disconnect = pa_stream_disconnect; + ma_pa_stream_get_state_proc _pa_stream_get_state = pa_stream_get_state; + ma_pa_stream_get_sample_spec_proc _pa_stream_get_sample_spec = pa_stream_get_sample_spec; + ma_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map; + ma_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr; + ma_pa_stream_set_buffer_attr_proc _pa_stream_set_buffer_attr = pa_stream_set_buffer_attr; + ma_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name; + ma_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; + ma_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; + ma_pa_stream_set_suspended_callback_proc _pa_stream_set_suspended_callback = pa_stream_set_suspended_callback; + ma_pa_stream_set_moved_callback_proc _pa_stream_set_moved_callback = pa_stream_set_moved_callback; + ma_pa_stream_is_suspended_proc _pa_stream_is_suspended = pa_stream_is_suspended; + ma_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; + ma_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; + ma_pa_stream_is_corked_proc _pa_stream_is_corked = pa_stream_is_corked; + ma_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork; + ma_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger; + ma_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write; + ma_pa_stream_write_proc _pa_stream_write = pa_stream_write; + ma_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek; + ma_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop; + ma_pa_stream_writable_size_proc _pa_stream_writable_size = pa_stream_writable_size; + ma_pa_stream_readable_size_proc _pa_stream_readable_size = pa_stream_readable_size; + + pContextStatePulseAudio->pa_mainloop_new = _pa_mainloop_new; + pContextStatePulseAudio->pa_mainloop_free = _pa_mainloop_free; + pContextStatePulseAudio->pa_mainloop_quit = _pa_mainloop_quit; + pContextStatePulseAudio->pa_mainloop_get_api = _pa_mainloop_get_api; + pContextStatePulseAudio->pa_mainloop_iterate = _pa_mainloop_iterate; + pContextStatePulseAudio->pa_mainloop_wakeup = _pa_mainloop_wakeup; + pContextStatePulseAudio->pa_threaded_mainloop_new = _pa_threaded_mainloop_new; + pContextStatePulseAudio->pa_threaded_mainloop_free = _pa_threaded_mainloop_free; + pContextStatePulseAudio->pa_threaded_mainloop_start = _pa_threaded_mainloop_start; + pContextStatePulseAudio->pa_threaded_mainloop_stop = _pa_threaded_mainloop_stop; + pContextStatePulseAudio->pa_threaded_mainloop_lock = _pa_threaded_mainloop_lock; + pContextStatePulseAudio->pa_threaded_mainloop_unlock = _pa_threaded_mainloop_unlock; + pContextStatePulseAudio->pa_threaded_mainloop_wait = _pa_threaded_mainloop_wait; + pContextStatePulseAudio->pa_threaded_mainloop_signal = _pa_threaded_mainloop_signal; + pContextStatePulseAudio->pa_threaded_mainloop_accept = _pa_threaded_mainloop_accept; + pContextStatePulseAudio->pa_threaded_mainloop_get_retval = _pa_threaded_mainloop_get_retval; + pContextStatePulseAudio->pa_threaded_mainloop_get_api = _pa_threaded_mainloop_get_api; + pContextStatePulseAudio->pa_threaded_mainloop_in_thread = _pa_threaded_mainloop_in_thread; + pContextStatePulseAudio->pa_threaded_mainloop_set_name = _pa_threaded_mainloop_set_name; + pContextStatePulseAudio->pa_context_new = _pa_context_new; + pContextStatePulseAudio->pa_context_unref = _pa_context_unref; + pContextStatePulseAudio->pa_context_connect = _pa_context_connect; + pContextStatePulseAudio->pa_context_disconnect = _pa_context_disconnect; + pContextStatePulseAudio->pa_context_set_state_callback = _pa_context_set_state_callback; + pContextStatePulseAudio->pa_context_get_state = _pa_context_get_state; + pContextStatePulseAudio->pa_context_get_sink_info_list = _pa_context_get_sink_info_list; + pContextStatePulseAudio->pa_context_get_source_info_list = _pa_context_get_source_info_list; + pContextStatePulseAudio->pa_context_get_sink_info_by_name = _pa_context_get_sink_info_by_name; + pContextStatePulseAudio->pa_context_get_source_info_by_name = _pa_context_get_source_info_by_name; + pContextStatePulseAudio->pa_operation_unref = _pa_operation_unref; + pContextStatePulseAudio->pa_operation_get_state = _pa_operation_get_state; + pContextStatePulseAudio->pa_channel_map_init_extend = _pa_channel_map_init_extend; + pContextStatePulseAudio->pa_channel_map_valid = _pa_channel_map_valid; + pContextStatePulseAudio->pa_channel_map_compatible = _pa_channel_map_compatible; + pContextStatePulseAudio->pa_stream_new = _pa_stream_new; + pContextStatePulseAudio->pa_stream_unref = _pa_stream_unref; + pContextStatePulseAudio->pa_stream_connect_playback = _pa_stream_connect_playback; + pContextStatePulseAudio->pa_stream_connect_record = _pa_stream_connect_record; + pContextStatePulseAudio->pa_stream_disconnect = _pa_stream_disconnect; + pContextStatePulseAudio->pa_stream_get_state = _pa_stream_get_state; + pContextStatePulseAudio->pa_stream_get_sample_spec = _pa_stream_get_sample_spec; + pContextStatePulseAudio->pa_stream_get_channel_map = _pa_stream_get_channel_map; + pContextStatePulseAudio->pa_stream_get_buffer_attr = _pa_stream_get_buffer_attr; + pContextStatePulseAudio->pa_stream_set_buffer_attr = _pa_stream_set_buffer_attr; + pContextStatePulseAudio->pa_stream_get_device_name = _pa_stream_get_device_name; + pContextStatePulseAudio->pa_stream_set_write_callback = _pa_stream_set_write_callback; + pContextStatePulseAudio->pa_stream_set_read_callback = _pa_stream_set_read_callback; + pContextStatePulseAudio->pa_stream_set_suspended_callback = _pa_stream_set_suspended_callback; + pContextStatePulseAudio->pa_stream_set_moved_callback = _pa_stream_set_moved_callback; + pContextStatePulseAudio->pa_stream_is_suspended = _pa_stream_is_suspended; + pContextStatePulseAudio->pa_stream_flush = _pa_stream_flush; + pContextStatePulseAudio->pa_stream_drain = _pa_stream_drain; + pContextStatePulseAudio->pa_stream_is_corked = _pa_stream_is_corked; + pContextStatePulseAudio->pa_stream_cork = _pa_stream_cork; + pContextStatePulseAudio->pa_stream_trigger = _pa_stream_trigger; + pContextStatePulseAudio->pa_stream_begin_write = _pa_stream_begin_write; + pContextStatePulseAudio->pa_stream_write = _pa_stream_write; + pContextStatePulseAudio->pa_stream_peek = _pa_stream_peek; + pContextStatePulseAudio->pa_stream_drop = _pa_stream_drop; + pContextStatePulseAudio->pa_stream_writable_size = _pa_stream_writable_size; + pContextStatePulseAudio->pa_stream_readable_size = _pa_stream_readable_size; + } + #endif + + + tryAutoSpawn = pContextConfigPulseAudio->tryAutoSpawn; + + + result = ma_init_pa_mainloop_and_pa_context__pulseaudio(pContext, pContextStatePulseAudio, pContextStatePulseAudio->pApplicationName, pContextStatePulseAudio->pServerName, tryAutoSpawn, &pContextStatePulseAudio->pMainLoop, &pContextStatePulseAudio->pPulseContext); + if (result != MA_SUCCESS) { + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(pLog, pContextStatePulseAudio->pulseSO); + } + #endif + ma_free(pContextStatePulseAudio, ma_context_get_allocation_callbacks(pContext)); + + return result; + } + + *ppContextState = pContextStatePulseAudio; + + return MA_SUCCESS; +} + +static void ma_context_uninit__pulseaudio(ma_context* pContext) +{ + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(pContext); + + MA_ASSERT(pContextStatePulseAudio != NULL); + + pContextStatePulseAudio->pa_context_disconnect(pContextStatePulseAudio->pPulseContext); + pContextStatePulseAudio->pa_context_unref(pContextStatePulseAudio->pPulseContext); + pContextStatePulseAudio->pa_mainloop_free(pContextStatePulseAudio->pMainLoop); + + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(ma_context_get_log(pContext), pContextStatePulseAudio->pulseSO); + } + #endif + + ma_free(pContextStatePulseAudio, ma_context_get_allocation_callbacks(pContext)); +} + + +typedef struct +{ + ma_context_state_pulseaudio* pContextStatePulseAudio; + ma_enum_devices_callback_proc callback; + void* pUserData; + ma_bool32 isTerminated; + ma_uint32 defaultDeviceIndexPlayback; + ma_uint32 defaultDeviceIndexCapture; +} ma_context_enumerate_devices_callback_data__pulseaudio; + +static void ma_context_enumerate_devices_sink_callback__pulseaudio(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + ma_context_enumerate_devices_callback_data__pulseaudio* pData = (ma_context_enumerate_devices_callback_data__pulseaudio*)pUserData; + ma_device_info deviceInfo; + ma_format nativeFormat; + + MA_ASSERT(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* Default. */ + if (pInfo->index == pData->defaultDeviceIndexPlayback) { + deviceInfo.isDefault = MA_TRUE; + } + + /* ID. The name from PulseAudio is the ID for miniaudio. */ + if (pInfo->name != NULL) { + ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pInfo->name, (size_t)-1); + } + + /* Name. The description from PulseAudio is the name for miniaudio. */ + if (pInfo->description != NULL) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pInfo->description, (size_t)-1); + } + + /* Data Format. */ + nativeFormat = ma_format_from_pulseaudio(pInfo->sample_spec.format); + if (nativeFormat == ma_format_unknown) { + nativeFormat = ma_format_f32; + } + + ma_device_info_add_native_data_format(&deviceInfo, nativeFormat, pInfo->sample_spec.channels, pInfo->sample_spec.channels, pInfo->sample_spec.rate, pInfo->sample_spec.rate); + + pData->isTerminated = (pData->callback(ma_device_type_playback, &deviceInfo, pData->pUserData) == MA_DEVICE_ENUMERATION_ABORT); + + (void)pPulseContext; /* Unused. */ +} + +static void ma_context_enumerate_devices_source_callback__pulseaudio(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + ma_context_enumerate_devices_callback_data__pulseaudio* pData = (ma_context_enumerate_devices_callback_data__pulseaudio*)pUserData; + ma_device_info deviceInfo; + ma_format nativeFormat; + + MA_ASSERT(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* Default. */ + if (pInfo->index == pData->defaultDeviceIndexCapture) { + deviceInfo.isDefault = MA_TRUE; + } + + /* ID. The name from PulseAudio is the ID for miniaudio. */ + if (pInfo->name != NULL) { + ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pInfo->name, (size_t)-1); + } + + /* Name. The description from PulseAudio is the name for miniaudio. */ + if (pInfo->description != NULL) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pInfo->description, (size_t)-1); + } + + /* Data Format. */ + nativeFormat = ma_format_from_pulseaudio(pInfo->sample_spec.format); + if (nativeFormat == ma_format_unknown) { + nativeFormat = ma_format_f32; + } + + ma_device_info_add_native_data_format(&deviceInfo, nativeFormat, pInfo->sample_spec.channels, pInfo->sample_spec.channels, pInfo->sample_spec.rate, pInfo->sample_spec.rate); + + pData->isTerminated = (pData->callback(ma_device_type_capture, &deviceInfo, pData->pUserData) == MA_DEVICE_ENUMERATION_ABORT); + + (void)pPulseContext; /* Unused. */ +} + +static ma_result ma_context_enumerate_devices__pulseaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(pContext); + ma_result result = MA_SUCCESS; + ma_context_enumerate_devices_callback_data__pulseaudio callbackData; + ma_pa_operation* pOP = NULL; + + MA_ASSERT(pContextStatePulseAudio != NULL); + MA_ASSERT(callback != NULL); + + callbackData.pContextStatePulseAudio = pContextStatePulseAudio; + callbackData.callback = callback; + callbackData.pUserData = pUserData; + callbackData.isTerminated = MA_FALSE; + callbackData.defaultDeviceIndexPlayback = (ma_uint32)-1; + callbackData.defaultDeviceIndexCapture = (ma_uint32)-1; + + /* We need to get the index of the default devices. */ + ma_context_get_default_device_index__pulseaudio(pContextStatePulseAudio, ma_device_type_playback, &callbackData.defaultDeviceIndexPlayback); + ma_context_get_default_device_index__pulseaudio(pContextStatePulseAudio, ma_device_type_capture, &callbackData.defaultDeviceIndexCapture); + + /* Playback. */ + if (!callbackData.isTerminated) { + pOP = pContextStatePulseAudio->pa_context_get_sink_info_list(pContextStatePulseAudio->pPulseContext, ma_context_enumerate_devices_sink_callback__pulseaudio, &callbackData); + if (pOP == NULL) { + result = MA_ERROR; + goto done; + } + + result = ma_wait_for_operation__pulseaudio(pContextStatePulseAudio, pContextStatePulseAudio->pMainLoop, pOP); + pContextStatePulseAudio->pa_operation_unref(pOP); + + if (result != MA_SUCCESS) { + goto done; + } + } + + + /* Capture. */ + if (!callbackData.isTerminated) { + pOP = pContextStatePulseAudio->pa_context_get_source_info_list(pContextStatePulseAudio->pPulseContext, ma_context_enumerate_devices_source_callback__pulseaudio, &callbackData); + if (pOP == NULL) { + result = MA_ERROR; + goto done; + } + + result = ma_wait_for_operation__pulseaudio(pContextStatePulseAudio, pContextStatePulseAudio->pMainLoop, pOP); + pContextStatePulseAudio->pa_operation_unref(pOP); + + if (result != MA_SUCCESS) { + goto done; + } + } + +done: + return result; +} + +static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFrames, ma_uint32 periods, const ma_pa_sample_spec* ss) +{ + ma_pa_buffer_attr attr; + attr.maxlength = periodSizeInFrames * periods * ma_get_bytes_per_frame(ma_format_from_pulseaudio(ss->format), ss->channels); + attr.tlength = attr.maxlength / periods; + attr.prebuf = (ma_uint32)-1; + attr.minreq = (ma_uint32)-1; + attr.fragsize = attr.maxlength / periods; + + return attr; +} + +static ma_pa_stream* ma_device__pa_stream_new__pulseaudio(ma_context_state_pulseaudio* pContextStatePulseAudio, ma_device_state_pulseaudio* pDeviceStatePulseAudio, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) +{ + static ma_atomic_uint32 g_StreamCounter = { 0 }; + char actualStreamName[256]; + + if (pStreamName != NULL) { + ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); + } else { + const char* pBaseName = "miniaudio:"; + size_t baseNameLen = strlen(pBaseName); + ma_strcpy_s(actualStreamName, sizeof(actualStreamName), pBaseName); + ma_itoa_s((int)ma_atomic_uint32_get(&g_StreamCounter), actualStreamName + baseNameLen, sizeof(actualStreamName)-baseNameLen, 10); + } + ma_atomic_uint32_fetch_add(&g_StreamCounter, 1); + + return pContextStatePulseAudio->pa_stream_new(pDeviceStatePulseAudio->pPulseContext, actualStreamName, ss, cmap); +} + + +static void ma_device_on_read__pulseaudio(ma_pa_stream* pStream, size_t byteCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_pulseaudio* pDeviceStatePulseAudio = ma_device_get_backend_state__pulseaudio(pDevice); + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + ma_uint32 bpf; + /*ma_device_status deviceStatus;*/ + ma_uint64 frameCount; + ma_uint64 framesProcessed; + + /* + Don't do anything if the device isn't initialized yet. Yes, this can happen because PulseAudio + can fire this callback before the stream has even started. Ridiculous. + */ + #if 0 + deviceStatus = ma_device_get_status(pDevice); + if (deviceStatus != ma_device_status_starting && deviceStatus != ma_device_status_started) { + return; + } + #else + /* TODO: Check this new logic here. We were previously uses the device status, but now we're using pa_stream_is_corked(). */ + if (pContextStatePulseAudio->pa_stream_is_corked(pStream) == 1) { + return; + } + #endif + + bpf = ma_get_bytes_per_frame(pDeviceStatePulseAudio->capture.format, pDeviceStatePulseAudio->capture.channels); + MA_ASSERT(bpf > 0); + + frameCount = byteCount / bpf; + framesProcessed = 0; + + while (framesProcessed < frameCount) { + const void* pMappedPCMFrames; + size_t bytesMapped; + ma_uint64 framesMapped; + + int pulseResult = pContextStatePulseAudio->pa_stream_peek(pStream, &pMappedPCMFrames, &bytesMapped); + if (pulseResult < 0) { + break; /* Failed to map. Abort. */ + } + + framesMapped = bytesMapped / bpf; + if (framesMapped > 0) { + if (pMappedPCMFrames != NULL) { + ma_device_handle_backend_data_callback(pDevice, NULL, pMappedPCMFrames, framesMapped); + } else { + /* It's a hole. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] ma_device_on_read__pulseaudio: Hole."); + } + + pulseResult = pContextStatePulseAudio->pa_stream_drop(pStream); + if (pulseResult < 0) { + break; /* Failed to drop the buffer. */ + } + + framesProcessed += framesMapped; + + } else { + /* Nothing was mapped. Just abort. */ + break; + } + } +} + +static ma_result ma_device_write_to_stream__pulseaudio(ma_device* pDevice, ma_context_state_pulseaudio* pContextStatePulseAudio, ma_device_state_pulseaudio* pDeviceStatePulseAudio, ma_pa_stream* pStream, ma_uint64* pFramesProcessed) +{ + ma_result result = MA_SUCCESS; + ma_uint64 framesProcessed = 0; + size_t bytesMapped; + ma_uint32 bpf; + + MA_ASSERT(pStream != NULL); + + bpf = ma_get_bytes_per_frame(pDeviceStatePulseAudio->playback.format, pDeviceStatePulseAudio->playback.channels); + MA_ASSERT(bpf > 0); + + bytesMapped = pContextStatePulseAudio->pa_stream_writable_size(pStream); + if (bytesMapped != (size_t)-1) { + if (bytesMapped > 0) { + ma_uint64 framesMapped; + void* pMappedPCMFrames; + int pulseResult = pContextStatePulseAudio->pa_stream_begin_write(pStream, &pMappedPCMFrames, &bytesMapped); + if (pulseResult < 0) { + result = ma_result_from_pulseaudio(pulseResult); + goto done; + } + + framesMapped = bytesMapped / bpf; + + if (pContextStatePulseAudio->pa_stream_is_corked(pStream) == 0) { /* Check for starting state just in case this is being used to do the initial fill. */ + ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped); + } else { + /* Device is not started. Write silence. */ + ma_silence_pcm_frames(pMappedPCMFrames, framesMapped, pDeviceStatePulseAudio->playback.format, pDeviceStatePulseAudio->playback.channels); + } + + pulseResult = pContextStatePulseAudio->pa_stream_write(pStream, pMappedPCMFrames, bytesMapped, NULL, 0, MA_PA_SEEK_RELATIVE); + if (pulseResult < 0) { + result = ma_result_from_pulseaudio(pulseResult); + goto done; /* Failed to write data to stream. */ + } + + framesProcessed += framesMapped; + } else { + result = MA_SUCCESS; /* No data available for writing. */ + goto done; + } + } else { + result = MA_ERROR; /* Failed to retrieve the writable size. Abort. */ + goto done; + } + +done: + if (pFramesProcessed != NULL) { + *pFramesProcessed = framesProcessed; + } + + return result; +} + +static void ma_device_on_write__pulseaudio(ma_pa_stream* pStream, size_t byteCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_pulseaudio* pDeviceStatePulseAudio = ma_device_get_backend_state__pulseaudio(pDevice); + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + ma_uint32 bpf; + ma_uint64 frameCount; + ma_uint64 framesProcessed; + ma_result result; + + /* + Don't do anything if the device isn't initialized yet. Yes, this can happen because PulseAudio + can fire this callback before the stream has even started. Ridiculous. + */ + + /* TODO: Check this new logic here. We were previously uses the device status, but now we're using pa_stream_is_corked(). */ + if (pContextStatePulseAudio->pa_stream_is_corked(pStream) == 1) { + return; + } + + bpf = ma_get_bytes_per_frame(pDeviceStatePulseAudio->playback.format, pDeviceStatePulseAudio->playback.channels); + MA_ASSERT(bpf > 0); + + frameCount = byteCount / bpf; + framesProcessed = 0; + + while (framesProcessed < frameCount) { + ma_uint64 framesProcessedThisIteration; + + /* Don't keep trying to process frames if the device isn't started. */ + if (pContextStatePulseAudio->pa_stream_is_corked(pStream) == 1) { + return; + } + + result = ma_device_write_to_stream__pulseaudio(pDevice, pContextStatePulseAudio, pDeviceStatePulseAudio, pStream, &framesProcessedThisIteration); + if (result != MA_SUCCESS) { + break; + } + + framesProcessed += framesProcessedThisIteration; + } +} + +static void ma_device_on_suspended__pulseaudio(ma_pa_stream* pStream, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + int suspended; + + (void)pStream; + + suspended = pContextStatePulseAudio->pa_stream_is_suspended(pStream); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. pa_stream_is_suspended() returned %d.", suspended); + + if (suspended < 0) { + return; + } + + if (suspended == 1) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Suspended."); + } else { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Resumed."); + } +} + +static void ma_device_on_rerouted__pulseaudio(ma_pa_stream* pStream, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + + (void)pStream; + + ma_device_post_notification_rerouted(pDevice); +} + +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__pulseaudio(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate) +{ + MA_ASSERT(nativeSampleRate != 0); + + if (pDescriptor->periodSizeInFrames == 0) { + if (pDescriptor->periodSizeInMilliseconds == 0) { + return ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS, nativeSampleRate); + } else { + return ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); + } + } else { + return pDescriptor->periodSizeInFrames; + } +} + +static ma_result ma_device_init__pulseaudio(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + /* + Notes for PulseAudio: + + - When both the period size in frames and milliseconds are 0, we default to miniaudio's + default buffer sizes rather than leaving it up to PulseAudio because I don't trust + PulseAudio to give us any kind of reasonable latency by default. + + - Do not ever, *ever* forget to use MA_PA_STREAM_ADJUST_LATENCY. If you don't specify this + flag, capture mode will just not work properly until you open another PulseAudio app. + */ + ma_device_state_pulseaudio* pDeviceStatePulseAudio; + const ma_device_config_pulseaudio* pDeviceConfigPulseAudio = (const ma_device_config_pulseaudio*)pDeviceBackendConfig; + ma_device_config_pulseaudio defaultConfigPulseAudio; + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result = MA_SUCCESS; + int error = 0; + const char* devPlayback = NULL; + const char* devCapture = NULL; + ma_pa_sink_info sinkInfo; + ma_pa_source_info sourceInfo; + ma_pa_sample_spec ss; + ma_pa_channel_map cmap; + ma_pa_buffer_attr attr; + const ma_pa_sample_spec* pActualSS = NULL; + const ma_pa_buffer_attr* pActualAttr = NULL; + const ma_pa_channel_map* pActualChannelMap = NULL; + ma_uint32 iChannel; + int streamFlags; + + if (pDeviceConfigPulseAudio == NULL) { + defaultConfigPulseAudio = ma_device_config_pulseaudio_init(); + pDeviceConfigPulseAudio = &defaultConfigPulseAudio; + } + + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + /* No exclusive mode with the PulseAudio backend. */ + if (((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + return MA_SHARE_MODE_NOT_SUPPORTED; + } + + pDeviceStatePulseAudio = (ma_device_state_pulseaudio*)ma_calloc(sizeof(*pDeviceStatePulseAudio), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStatePulseAudio == NULL) { + return MA_OUT_OF_MEMORY; + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (pDescriptorPlayback->pDeviceID != NULL) { + devPlayback = pDescriptorPlayback->pDeviceID->pulse; + } + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (pDescriptorCapture->pDeviceID != NULL) { + devCapture = pDescriptorCapture->pDeviceID->pulse; + } + } + + + + result = ma_init_pa_mainloop_and_pa_context__pulseaudio(ma_device_get_context(pDevice), pContextStatePulseAudio, pContextStatePulseAudio->pApplicationName, pContextStatePulseAudio->pServerName, MA_FALSE, &pDeviceStatePulseAudio->pMainLoop, &pDeviceStatePulseAudio->pPulseContext); + if (result != MA_SUCCESS) { + ma_free(pDeviceStatePulseAudio, ma_device_get_allocation_callbacks(pDevice)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize PA mainloop and context for device."); + return result; + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + result = ma_context_get_source_info__pulseaudio(pContextStatePulseAudio, devCapture, &sourceInfo); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device."); + goto on_error0; + } + + ss = sourceInfo.sample_spec; + cmap = sourceInfo.channel_map; + + /* Use the requested format if we have one. */ + ss.format = ma_format_to_pulseaudio(pDescriptorCapture->format); + + /* Use the requested channel count if we have one. */ + if (pDescriptorCapture->channels != 0) { + ss.channels = pDescriptorCapture->channels; + } + + /* PulseAudio has a maximum channel count of 32. We'll get a crash if this is exceeded. */ + if (ss.channels > 32) { + ss.channels = 32; + } + + /* Use a default channel map. */ + pContextStatePulseAudio->pa_channel_map_init_extend(&cmap, ss.channels, (ma_pa_channel_map_def_t)pDeviceConfigPulseAudio->channelMap); + + /* Use the requested sample rate if one was specified. */ + if (pDescriptorCapture->sampleRate != 0) { + ss.rate = pDescriptorCapture->sampleRate; + } + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY; + + if (ma_format_from_pulseaudio(ss.format) == ma_format_unknown) { + if (ma_is_little_endian()) { + ss.format = MA_PA_SAMPLE_FLOAT32LE; + } else { + ss.format = MA_PA_SAMPLE_FLOAT32BE; + } + /*streamFlags |= MA_PA_STREAM_FIX_FORMAT;*/ /* Using PA_STREAM_FIX_FORMAT will make it so PulseAudio can possibly pick S24_32 which is not supported by miniaudio and will thus result in an error later on. By excluding it, PulseAudio should always pick F32. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32."); + } + if (ss.rate == 0) { + ss.rate = MA_DEFAULT_SAMPLE_RATE; + streamFlags |= MA_PA_STREAM_FIX_RATE; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.", ss.rate); + } + if (ss.channels == 0) { + ss.channels = MA_DEFAULT_CHANNELS; + streamFlags |= MA_PA_STREAM_FIX_CHANNELS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.", ss.channels); + } + + /* We now have enough information to calculate our actual period size in frames. */ + pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulseaudio(pDescriptorCapture, ss.rate); + + attr = ma_device__pa_buffer_attr_new(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->periodCount, &ss); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); + + pDeviceStatePulseAudio->capture.pStream = ma_device__pa_stream_new__pulseaudio(pContextStatePulseAudio, pDeviceStatePulseAudio, pDeviceConfigPulseAudio->pStreamNameCapture, &ss, &cmap); + if (pDeviceStatePulseAudio->capture.pStream == NULL) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream."); + result = MA_ERROR; + goto on_error0; + } + + + /* The callback needs to be set before connecting the stream. */ + pContextStatePulseAudio->pa_stream_set_read_callback(pDeviceStatePulseAudio->capture.pStream, ma_device_on_read__pulseaudio, pDevice); + + /* State callback for checking when the device has been corked. */ + pContextStatePulseAudio->pa_stream_set_suspended_callback(pDeviceStatePulseAudio->capture.pStream, ma_device_on_suspended__pulseaudio, pDevice); + + /* Rerouting notification. */ + pContextStatePulseAudio->pa_stream_set_moved_callback(pDeviceStatePulseAudio->capture.pStream, ma_device_on_rerouted__pulseaudio, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + if (devCapture != NULL) { + streamFlags |= MA_PA_STREAM_DONT_MOVE; + } + + error = pContextStatePulseAudio->pa_stream_connect_record(pDeviceStatePulseAudio->capture.pStream, devCapture, &attr, (ma_pa_stream_flags_t)streamFlags); + if (error != MA_PA_OK) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream."); + result = ma_result_from_pulseaudio(error); + goto on_error1; + } + + result = ma_wait_for_pa_stream_to_connect__pulseaudio(ma_device_get_context(pDevice), pContextStatePulseAudio, pDeviceStatePulseAudio->pMainLoop, pDeviceStatePulseAudio->capture.pStream); + if (result != MA_SUCCESS) { + goto on_error2; + } + + + /* Internal format. */ + pActualSS = pContextStatePulseAudio->pa_stream_get_sample_spec(pDeviceStatePulseAudio->capture.pStream); + if (pActualSS != NULL) { + ss = *pActualSS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture sample spec: format=%s, channels=%d, rate=%d", ma_get_format_name(ma_format_from_pulseaudio(ss.format)), ss.channels, ss.rate); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve capture sample spec."); + } + + pDescriptorCapture->format = ma_format_from_pulseaudio(ss.format); + pDescriptorCapture->channels = ss.channels; + pDescriptorCapture->sampleRate = ss.rate; + + if (pDescriptorCapture->format == ma_format_unknown || pDescriptorCapture->channels == 0 || pDescriptorCapture->sampleRate == 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Capture sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.", ma_get_format_name(pDescriptorCapture->format), pDescriptorCapture->channels, pDescriptorCapture->sampleRate); + result = MA_ERROR; + goto on_error4; + } + + + /* Internal channel map. */ + pActualChannelMap = pContextStatePulseAudio->pa_stream_get_channel_map(pDeviceStatePulseAudio->capture.pStream); + if (pActualChannelMap == NULL) { + pActualChannelMap = &cmap; /* Fallback just in case. */ + } + + /* + Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting + the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono + and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For + all other channel counts we need to just put up with whatever PipeWire reports and hope it gets + fixed sooner than later. I might remove this hack later. + */ + if (pDescriptorCapture->channels > 2) { + for (iChannel = 0; iChannel < pDescriptorCapture->channels; iChannel += 1) { + pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulseaudio(pActualChannelMap->map[iChannel]); + } + } else { + /* Hack for mono and stereo. */ + if (pDescriptorCapture->channels == 1) { + pDescriptorCapture->channelMap[0] = MA_CHANNEL_MONO; + } else if (pDescriptorCapture->channels == 2) { + pDescriptorCapture->channelMap[0] = MA_CHANNEL_FRONT_LEFT; + pDescriptorCapture->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + } + + + /* Buffer. */ + pActualAttr = pContextStatePulseAudio->pa_stream_get_buffer_attr(pDeviceStatePulseAudio->capture.pStream); + if (pActualAttr != NULL) { + attr = *pActualAttr; + } + + if (attr.fragsize > 0) { + pDescriptorCapture->periodCount = ma_max(attr.maxlength / attr.fragsize, 1); + } else { + pDescriptorCapture->periodCount = 1; + } + + pDescriptorCapture->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / pDescriptorCapture->periodCount; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); + + pDeviceStatePulseAudio->capture.format = pDescriptorCapture->format; + pDeviceStatePulseAudio->capture.channels = pDescriptorCapture->channels; + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_context_get_sink_info__pulseaudio(pContextStatePulseAudio, devPlayback, &sinkInfo); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device."); + goto on_error2; + } + + ss = sinkInfo.sample_spec; + cmap = sinkInfo.channel_map; + + /* Use the requested format if we have one. */ + ss.format = ma_format_to_pulseaudio(pDescriptorPlayback->format); + + /* Use the requested channel count if we have one. */ + if (pDescriptorPlayback->channels != 0) { + ss.channels = pDescriptorPlayback->channels; + } + + /* PulseAudio has a maximum channel count of 32. We'll get a crash if this is exceeded. */ + if (ss.channels > 32) { + ss.channels = 32; + } + + /* Use a default channel map. */ + pContextStatePulseAudio->pa_channel_map_init_extend(&cmap, ss.channels, (ma_pa_channel_map_def_t)pDeviceConfigPulseAudio->channelMap); + + + /* Use the requested sample rate if one was specified. */ + if (pDescriptorPlayback->sampleRate != 0) { + ss.rate = pDescriptorPlayback->sampleRate; + } + + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY; + if (ma_format_from_pulseaudio(ss.format) == ma_format_unknown) { + if (ma_is_little_endian()) { + ss.format = MA_PA_SAMPLE_FLOAT32LE; + } else { + ss.format = MA_PA_SAMPLE_FLOAT32BE; + } + /*streamFlags |= MA_PA_STREAM_FIX_FORMAT;*/ /* Using PA_STREAM_FIX_FORMAT will make it so PulseAudio can possibly pick S24_32 which is not supported by miniaudio and will thus result in an error later on. By excluding it, PulseAudio should always pick F32. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32."); + } + if (ss.rate == 0) { + ss.rate = MA_DEFAULT_SAMPLE_RATE; + streamFlags |= MA_PA_STREAM_FIX_RATE; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.", ss.rate); + } + if (ss.channels == 0) { + ss.channels = MA_DEFAULT_CHANNELS; + streamFlags |= MA_PA_STREAM_FIX_CHANNELS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.", ss.channels); + } + + /* We now have enough information to calculate the actual buffer size in frames. */ + pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulseaudio(pDescriptorPlayback, ss.rate); + + attr = ma_device__pa_buffer_attr_new(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->periodCount, &ss); + + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); + + pDeviceStatePulseAudio->playback.pStream = ma_device__pa_stream_new__pulseaudio(pContextStatePulseAudio, pDeviceStatePulseAudio, pDeviceConfigPulseAudio->pStreamNamePlayback, &ss, &cmap); + if (pDeviceStatePulseAudio->playback.pStream == NULL) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream."); + result = MA_ERROR; + goto on_error2; + } + + + /* + Note that this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a + device state of ma_device_status_uninitialized. + */ + pContextStatePulseAudio->pa_stream_set_write_callback(pDeviceStatePulseAudio->playback.pStream, ma_device_on_write__pulseaudio, pDevice); + + /* State callback for checking when the device has been corked. */ + pContextStatePulseAudio->pa_stream_set_suspended_callback(pDeviceStatePulseAudio->playback.pStream, ma_device_on_suspended__pulseaudio, pDevice); + + /* Rerouting notification. */ + pContextStatePulseAudio->pa_stream_set_moved_callback(pDeviceStatePulseAudio->playback.pStream, ma_device_on_rerouted__pulseaudio, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + if (devPlayback != NULL) { + streamFlags |= MA_PA_STREAM_DONT_MOVE; + } + + error = pContextStatePulseAudio->pa_stream_connect_playback(pDeviceStatePulseAudio->playback.pStream, devPlayback, &attr, (ma_pa_stream_flags_t)streamFlags, NULL, NULL); + if (error != MA_PA_OK) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream."); + result = ma_result_from_pulseaudio(error); + goto on_error3; + } + + result = ma_wait_for_pa_stream_to_connect__pulseaudio(ma_device_get_context(pDevice), pContextStatePulseAudio, pDeviceStatePulseAudio->pMainLoop, pDeviceStatePulseAudio->playback.pStream); + if (result != MA_SUCCESS) { + goto on_error3; + } + + + /* Internal format. */ + pActualSS = pContextStatePulseAudio->pa_stream_get_sample_spec(pDeviceStatePulseAudio->playback.pStream); + if (pActualSS != NULL) { + ss = *pActualSS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback sample spec: format=%s, channels=%d, rate=%d", ma_get_format_name(ma_format_from_pulseaudio(ss.format)), ss.channels, ss.rate); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve playback sample spec."); + } + + pDescriptorPlayback->format = ma_format_from_pulseaudio(ss.format); + pDescriptorPlayback->channels = ss.channels; + pDescriptorPlayback->sampleRate = ss.rate; + + if (pDescriptorPlayback->format == ma_format_unknown || pDescriptorPlayback->channels == 0 || pDescriptorPlayback->sampleRate == 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Playback sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.", ma_get_format_name(pDescriptorPlayback->format), pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate); + result = MA_ERROR; + goto on_error4; + } + + + /* Internal channel map. */ + pActualChannelMap = pContextStatePulseAudio->pa_stream_get_channel_map(pDeviceStatePulseAudio->playback.pStream); + if (pActualChannelMap == NULL) { + pActualChannelMap = &cmap; /* Fallback just in case. */ + } + + /* + Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting + the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono + and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For + all other channel counts we need to just put up with whatever PipeWire reports and hope it gets + fixed sooner than later. I might remove this hack later. + */ + if (pDescriptorPlayback->channels > 2) { + for (iChannel = 0; iChannel < pDescriptorPlayback->channels; iChannel += 1) { + pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulseaudio(pActualChannelMap->map[iChannel]); + } + } else { + /* Hack for mono and stereo. */ + if (pDescriptorPlayback->channels == 1) { + pDescriptorPlayback->channelMap[0] = MA_CHANNEL_MONO; + } else if (pDescriptorPlayback->channels == 2) { + pDescriptorPlayback->channelMap[0] = MA_CHANNEL_FRONT_LEFT; + pDescriptorPlayback->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + } + + + /* Buffer. */ + pActualAttr = pContextStatePulseAudio->pa_stream_get_buffer_attr(pDeviceStatePulseAudio->playback.pStream); + if (pActualAttr != NULL) { + attr = *pActualAttr; + } + + if (attr.tlength > 0) { + pDescriptorPlayback->periodCount = ma_max(attr.maxlength / attr.tlength, 1); + } else { + pDescriptorPlayback->periodCount = 1; + } + + pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); + + pDeviceStatePulseAudio->playback.format = pDescriptorPlayback->format; + pDeviceStatePulseAudio->playback.channels = pDescriptorPlayback->channels; + } + + *ppDeviceState = pDeviceStatePulseAudio; + + return MA_SUCCESS; + + +on_error4: + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStatePulseAudio->pa_stream_disconnect(pDeviceStatePulseAudio->playback.pStream); + } +on_error3: + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStatePulseAudio->pa_stream_unref(pDeviceStatePulseAudio->playback.pStream); + } +on_error2: + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStatePulseAudio->pa_stream_disconnect(pDeviceStatePulseAudio->capture.pStream); + } +on_error1: + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStatePulseAudio->pa_stream_unref(pDeviceStatePulseAudio->capture.pStream); + } +on_error0: + ma_free(pDeviceStatePulseAudio, ma_device_get_allocation_callbacks(pDevice)); + return result; +} + +static void ma_device_uninit__pulseaudio(ma_device* pDevice) +{ + ma_device_state_pulseaudio* pDeviceStatePulseAudio = ma_device_get_backend_state__pulseaudio(pDevice); + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStatePulseAudio->pa_stream_disconnect(pDeviceStatePulseAudio->capture.pStream); + pContextStatePulseAudio->pa_stream_unref(pDeviceStatePulseAudio->capture.pStream); + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStatePulseAudio->pa_stream_disconnect(pDeviceStatePulseAudio->playback.pStream); + pContextStatePulseAudio->pa_stream_unref(pDeviceStatePulseAudio->playback.pStream); + } + + pContextStatePulseAudio->pa_context_disconnect(pDeviceStatePulseAudio->pPulseContext); + pContextStatePulseAudio->pa_context_unref(pDeviceStatePulseAudio->pPulseContext); + pContextStatePulseAudio->pa_mainloop_free(pDeviceStatePulseAudio->pMainLoop); + + ma_free(pDeviceStatePulseAudio, ma_device_get_allocation_callbacks(pDevice)); +} + + +static void ma_pulseaudio_operation_complete_callback(ma_pa_stream* pStream, int success, void* pUserData) +{ + ma_bool32* pIsSuccessful = (ma_bool32*)pUserData; + MA_ASSERT(pIsSuccessful != NULL); + + *pIsSuccessful = (ma_bool32)success; + + (void)pStream; /* Unused. */ +} + +static ma_result ma_device__cork_stream__pulseaudio(ma_device* pDevice, ma_context_state_pulseaudio* pContextStatePulseAudio, ma_device_state_pulseaudio* pDeviceStatePulseAudio, ma_device_type deviceType, int cork) +{ + ma_bool32 wasSuccessful; + ma_pa_stream* pStream; + ma_pa_operation* pOP; + ma_result result; + + /* This should not be called with a duplex device type. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + wasSuccessful = MA_FALSE; + + pStream = ((deviceType == ma_device_type_capture) ? pDeviceStatePulseAudio->capture.pStream : pDeviceStatePulseAudio->playback.pStream); + MA_ASSERT(pStream != NULL); + + pOP = pContextStatePulseAudio->pa_stream_cork(pStream, cork, ma_pulseaudio_operation_complete_callback, &wasSuccessful); + if (pOP == NULL) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream."); + return MA_ERROR; + } + + result = ma_wait_for_operation_and_unref__pulseaudio(pContextStatePulseAudio, pDeviceStatePulseAudio->pMainLoop, pOP); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork."); + return result; + } + + if (!wasSuccessful) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to %s PulseAudio stream.", (cork) ? "stop" : "start"); + return MA_ERROR; + } + + return MA_SUCCESS; +} + +static ma_result ma_device_start__pulseaudio(ma_device* pDevice) +{ + ma_device_state_pulseaudio* pDeviceStatePulseAudio = ma_device_get_backend_state__pulseaudio(pDevice); + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + result = ma_device__cork_stream__pulseaudio(pDevice, pContextStatePulseAudio, pDeviceStatePulseAudio, ma_device_type_capture, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + /* + We need to fill some data before uncorking. Not doing this will result in the write callback + never getting fired. We're not going to abort if writing fails because I still want the device + to get uncorked. + */ + ma_device_write_to_stream__pulseaudio(pDevice, pContextStatePulseAudio, pDeviceStatePulseAudio, pDeviceStatePulseAudio->playback.pStream, NULL); /* No need to check the result here. Always want to fall through to an uncork. */ + + result = ma_device__cork_stream__pulseaudio(pDevice, pContextStatePulseAudio, pDeviceStatePulseAudio, ma_device_type_playback, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +static ma_result ma_device_stop__pulseaudio(ma_device* pDevice) +{ + ma_device_state_pulseaudio* pDeviceStatePulseAudio = ma_device_get_backend_state__pulseaudio(pDevice); + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + result = ma_device__cork_stream__pulseaudio(pDevice, pContextStatePulseAudio, pDeviceStatePulseAudio, ma_device_type_capture, 1); + if (result != MA_SUCCESS) { + return result; + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + /* + Ideally we would drain the device here, but there's been cases where PulseAudio seems to be + broken on some systems to the point where no audio processing seems to happen. When this + happens, draining never completes and we get stuck here. For now I'm disabling draining of + the device so we don't just freeze the application. + */ + #if 0 + ma_pa_operation* pOP = pContextStatePulseAudio->pa_stream_drain(pDeviceStatePulseAudio->pStreamPlayback, ma_pulseaudio_operation_complete_callback, &wasSuccessful); + ma_wait_for_operation_and_unref__pulseaudio(pContextStatePulseAudio, pDeviceStatePulseAudio->pMainLoop, pOP); + #endif + + result = ma_device__cork_stream__pulseaudio(pDevice, pContextStatePulseAudio, pDeviceStatePulseAudio, ma_device_type_playback, 1); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +static ma_result ma_device_step__pulseaudio(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_pulseaudio* pDeviceStatePulseAudio = ma_device_get_backend_state__pulseaudio(pDevice); + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + int block = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? 1 : 0; + int resultPA; + + /* + The write callback is fired from pa_mainloop_iterate(). This meets miniaudio's specification that + the data callback be fired from the step() function, so there's no need to do anything here except + iterate the PulseAudio loop. + */ + resultPA = pContextStatePulseAudio->pa_mainloop_iterate(pDeviceStatePulseAudio->pMainLoop, block, NULL); + if (resultPA < 0) { + return MA_ERROR; + } + + return MA_SUCCESS; +} + +static void ma_device_wakeup__pulseaudio(ma_device* pDevice) +{ + ma_device_state_pulseaudio* pDeviceStatePulseAudio = ma_device_get_backend_state__pulseaudio(pDevice); + ma_context_state_pulseaudio* pContextStatePulseAudio = ma_context_get_backend_state__pulseaudio(ma_device_get_context(pDevice)); + + pContextStatePulseAudio->pa_mainloop_wakeup(pDeviceStatePulseAudio->pMainLoop); +} + +static ma_device_backend_vtable ma_gDeviceBackendVTable_PulseAudio = +{ + ma_backend_info__pulseaudio, + ma_context_init__pulseaudio, + ma_context_uninit__pulseaudio, + ma_context_enumerate_devices__pulseaudio, + ma_device_init__pulseaudio, + ma_device_uninit__pulseaudio, + ma_device_start__pulseaudio, + ma_device_stop__pulseaudio, + ma_device_step__pulseaudio, + ma_device_wakeup__pulseaudio +}; + +ma_device_backend_vtable* ma_device_backend_pulseaudio = &ma_gDeviceBackendVTable_PulseAudio; +#else +ma_device_backend_vtable* ma_device_backend_pulseaudio = NULL; +#endif + +MA_API ma_device_backend_vtable* ma_pulseaudio_get_vtable(void) +{ + return ma_device_backend_pulseaudio; +} + +MA_API ma_context_config_pulseaudio ma_context_config_pulseaudio_init(void) +{ + ma_context_config_pulseaudio config; + + MA_ZERO_OBJECT(&config); + + return config; +} + +MA_API ma_device_config_pulseaudio ma_device_config_pulseaudio_init(void) +{ + ma_device_config_pulseaudio config; + + MA_ZERO_OBJECT(&config); + + return config; +} + + + +/****************************************************************************** + +JACK Backend + +******************************************************************************/ +#ifdef MA_HAS_JACK + +/* It is assumed jack.h is available when compile-time linking is being used. */ +#ifdef MA_NO_RUNTIME_LINKING +#include + +typedef jack_nframes_t ma_jack_nframes_t; +typedef jack_options_t ma_jack_options_t; +typedef jack_status_t ma_jack_status_t; +typedef jack_client_t ma_jack_client_t; +typedef jack_port_t ma_jack_port_t; +typedef JackProcessCallback ma_JackProcessCallback; +typedef JackBufferSizeCallback ma_JackBufferSizeCallback; +typedef JackShutdownCallback ma_JackShutdownCallback; +#define MA_JACK_DEFAULT_AUDIO_TYPE JACK_DEFAULT_AUDIO_TYPE +#define ma_JackNullOption JackNullOption +#define ma_JackNoStartServer JackNoStartServer +#define ma_JackPortIsInput JackPortIsInput +#define ma_JackPortIsOutput JackPortIsOutput +#define ma_JackPortIsPhysical JackPortIsPhysical +#else +typedef ma_uint32 ma_jack_nframes_t; +typedef int ma_jack_options_t; +typedef int ma_jack_status_t; +typedef struct ma_jack_client_t ma_jack_client_t; +typedef struct ma_jack_port_t ma_jack_port_t; +typedef int (* ma_JackProcessCallback) (ma_jack_nframes_t nframes, void* arg); +typedef int (* ma_JackBufferSizeCallback)(ma_jack_nframes_t nframes, void* arg); +typedef void (* ma_JackShutdownCallback) (void* arg); +#define MA_JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define ma_JackNullOption 0 +#define ma_JackNoStartServer 1 +#define ma_JackPortIsInput 1 +#define ma_JackPortIsOutput 2 +#define ma_JackPortIsPhysical 4 +#endif + +typedef ma_jack_client_t* (* ma_jack_client_open_proc )(const char* client_name, ma_jack_options_t options, ma_jack_status_t* status, ...); +typedef int (* ma_jack_client_close_proc )(ma_jack_client_t* client); +typedef int (* ma_jack_client_name_size_proc )(void); +typedef int (* ma_jack_set_process_callback_proc )(ma_jack_client_t* client, ma_JackProcessCallback process_callback, void* arg); +typedef int (* ma_jack_set_buffer_size_callback_proc)(ma_jack_client_t* client, ma_JackBufferSizeCallback bufsize_callback, void* arg); +typedef void (* ma_jack_on_shutdown_proc )(ma_jack_client_t* client, ma_JackShutdownCallback function, void* arg); +typedef ma_jack_nframes_t (* ma_jack_get_sample_rate_proc )(ma_jack_client_t* client); +typedef ma_jack_nframes_t (* ma_jack_get_buffer_size_proc )(ma_jack_client_t* client); +typedef const char** (* ma_jack_get_ports_proc )(ma_jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags); +typedef int (* ma_jack_activate_proc )(ma_jack_client_t* client); +typedef int (* ma_jack_deactivate_proc )(ma_jack_client_t* client); +typedef int (* ma_jack_connect_proc )(ma_jack_client_t* client, const char* source_port, const char* destination_port); +typedef ma_jack_port_t* (* ma_jack_port_register_proc )(ma_jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size); +typedef const char* (* ma_jack_port_name_proc )(const ma_jack_port_t* port); +typedef void* (* ma_jack_port_get_buffer_proc )(ma_jack_port_t* port, ma_jack_nframes_t nframes); +typedef void (* ma_jack_free_proc )(void* ptr); + +typedef struct ma_context_state_jack +{ + ma_log* pLog; + ma_handle jackSO; + ma_jack_client_open_proc jack_client_open; + ma_jack_client_close_proc jack_client_close; + ma_jack_client_name_size_proc jack_client_name_size; + ma_jack_set_process_callback_proc jack_set_process_callback; + ma_jack_set_buffer_size_callback_proc jack_set_buffer_size_callback; + ma_jack_on_shutdown_proc jack_on_shutdown; + ma_jack_get_sample_rate_proc jack_get_sample_rate; + ma_jack_get_buffer_size_proc jack_get_buffer_size; + ma_jack_get_ports_proc jack_get_ports; + ma_jack_activate_proc jack_activate; + ma_jack_deactivate_proc jack_deactivate; + ma_jack_connect_proc jack_connect; + ma_jack_port_register_proc jack_port_register; + ma_jack_port_name_proc jack_port_name; + ma_jack_port_get_buffer_proc jack_port_get_buffer; + ma_jack_free_proc jack_free; + + char* pClientName; + ma_bool32 tryStartServer; +} ma_context_state_jack; + +typedef struct ma_device_state_jack +{ + ma_device_state_async async; /* Only used in single-threaded mode. */ + ma_semaphore stepSemaphore; /* Only used in multi-threaded mode. */ + ma_context_state_jack* pContextStateJACK; + ma_bool32 noAutoConnect; + ma_jack_client_t* pClient; + ma_jack_port_t** ppPortsPlayback; + ma_jack_port_t** ppPortsCapture; + float* pIntermediaryBuffer; +} ma_device_state_jack; + +static ma_result ma_context_open_client__jack(ma_context_state_jack* pContextStateJACK, ma_jack_client_t** ppClient) +{ + size_t maxClientNameSize; + char clientName[256]; + ma_jack_status_t status; + ma_jack_client_t* pClient; + + MA_ASSERT(pContextStateJACK != NULL); + MA_ASSERT(ppClient != NULL); + + if (ppClient) { + *ppClient = NULL; + } + + maxClientNameSize = pContextStateJACK->jack_client_name_size(); /* Includes null terminator. */ + ma_strncpy_s(clientName, ma_min(sizeof(clientName), maxClientNameSize), (pContextStateJACK->pClientName != NULL) ? pContextStateJACK->pClientName : "miniaudio", (size_t)-1); + + pClient = pContextStateJACK->jack_client_open(clientName, (pContextStateJACK->tryStartServer) ? ma_JackNullOption : ma_JackNoStartServer, &status, NULL); + if (pClient == NULL) { + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + if (ppClient) { + *ppClient = pClient; + } + + return MA_SUCCESS; +} + + +static ma_context_state_jack* ma_context_get_backend_state__jack(ma_context* pContext) +{ + return (ma_context_state_jack*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_jack* ma_device_get_backend_state__jack(ma_device* pDevice) +{ + return (ma_device_state_jack*)ma_device_get_backend_state(pDevice); +} + + +static void ma_backend_info__jack(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "JACK"; +} + +static ma_result ma_context_init__jack(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_jack* pContextStateJACK; + const ma_context_config_jack* pContextConfigJACK = (const ma_context_config_jack*)pContextBackendConfig; + ma_context_config_jack defaultConfigJACK; + ma_log* pLog = ma_context_get_log(pContext); + size_t clientNameSize; + + if (pContextConfigJACK == NULL) { + defaultConfigJACK = ma_context_config_jack_init(); + pContextConfigJACK = &defaultConfigJACK; + } + + if (pContextConfigJACK->pClientName != NULL) { + clientNameSize = strlen(pContextConfigJACK->pClientName) + 1; + } else { + clientNameSize = 0; + } + + pContextStateJACK = (ma_context_state_jack*)ma_calloc(ma_align_64(sizeof(*pContextStateJACK) + clientNameSize), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateJACK == NULL) { + return MA_OUT_OF_MEMORY; + } + + if (pContextConfigJACK->pClientName != NULL) { + pContextStateJACK->pClientName = (char*)ma_offset_ptr(pContextStateJACK, ma_align_64(sizeof(*pContextStateJACK))); + ma_strcpy_s(pContextStateJACK->pClientName, clientNameSize, pContextConfigJACK->pClientName); + } else { + pContextStateJACK->pClientName = NULL; + } + + #ifndef MA_NO_RUNTIME_LINKING + { + const char* libjackNames[] = { + #if defined(MA_WIN32) + "libjack.dll", + "libjack64.dll" + #else + "libjack.so", + "libjack.so.0" + #endif + }; + size_t i; + + for (i = 0; i < ma_countof(libjackNames); ++i) { + pContextStateJACK->jackSO = ma_dlopen(pLog, libjackNames[i]); + if (pContextStateJACK->jackSO != NULL) { + break; + } + } + + if (pContextStateJACK->jackSO == NULL) { + ma_free(pContextStateJACK, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } + + pContextStateJACK->jack_client_open = (ma_jack_client_open_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_client_open"); + pContextStateJACK->jack_client_close = (ma_jack_client_close_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_client_close"); + pContextStateJACK->jack_client_name_size = (ma_jack_client_name_size_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_client_name_size"); + pContextStateJACK->jack_set_process_callback = (ma_jack_set_process_callback_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_set_process_callback"); + pContextStateJACK->jack_set_buffer_size_callback = (ma_jack_set_buffer_size_callback_proc)ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_set_buffer_size_callback"); + pContextStateJACK->jack_on_shutdown = (ma_jack_on_shutdown_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_on_shutdown"); + pContextStateJACK->jack_get_sample_rate = (ma_jack_get_sample_rate_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_get_sample_rate"); + pContextStateJACK->jack_get_buffer_size = (ma_jack_get_buffer_size_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_get_buffer_size"); + pContextStateJACK->jack_get_ports = (ma_jack_get_ports_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_get_ports"); + pContextStateJACK->jack_activate = (ma_jack_activate_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_activate"); + pContextStateJACK->jack_deactivate = (ma_jack_deactivate_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_deactivate"); + pContextStateJACK->jack_connect = (ma_jack_connect_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_connect"); + pContextStateJACK->jack_port_register = (ma_jack_port_register_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_port_register"); + pContextStateJACK->jack_port_name = (ma_jack_port_name_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_port_name"); + pContextStateJACK->jack_port_get_buffer = (ma_jack_port_get_buffer_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_port_get_buffer"); + pContextStateJACK->jack_free = (ma_jack_free_proc )ma_dlsym(pLog, pContextStateJACK->jackSO, "jack_free"); + } + #else + { + (void)pLog; + + /* + This strange assignment system is here just to ensure type safety of miniaudio's function pointer + types. If anything differs slightly the compiler should throw a warning. + */ + ma_jack_client_open_proc _jack_client_open = jack_client_open; + ma_jack_client_close_proc _jack_client_close = jack_client_close; + ma_jack_client_name_size_proc _jack_client_name_size = jack_client_name_size; + ma_jack_set_process_callback_proc _jack_set_process_callback = jack_set_process_callback; + ma_jack_set_buffer_size_callback_proc _jack_set_buffer_size_callback = jack_set_buffer_size_callback; + ma_jack_on_shutdown_proc _jack_on_shutdown = jack_on_shutdown; + ma_jack_get_sample_rate_proc _jack_get_sample_rate = jack_get_sample_rate; + ma_jack_get_buffer_size_proc _jack_get_buffer_size = jack_get_buffer_size; + ma_jack_get_ports_proc _jack_get_ports = jack_get_ports; + ma_jack_activate_proc _jack_activate = jack_activate; + ma_jack_deactivate_proc _jack_deactivate = jack_deactivate; + ma_jack_connect_proc _jack_connect = jack_connect; + ma_jack_port_register_proc _jack_port_register = jack_port_register; + ma_jack_port_name_proc _jack_port_name = jack_port_name; + ma_jack_port_get_buffer_proc _jack_port_get_buffer = jack_port_get_buffer; + ma_jack_free_proc _jack_free = jack_free; + + pContextStateJACK->jack_client_open = _jack_client_open; + pContextStateJACK->jack_client_close = _jack_client_close; + pContextStateJACK->jack_client_name_size = _jack_client_name_size; + pContextStateJACK->jack_set_process_callback = _jack_set_process_callback; + pContextStateJACK->jack_set_buffer_size_callback = _jack_set_buffer_size_callback; + pContextStateJACK->jack_on_shutdown = _jack_on_shutdown; + pContextStateJACK->jack_get_sample_rate = _jack_get_sample_rate; + pContextStateJACK->jack_get_buffer_size = _jack_get_buffer_size; + pContextStateJACK->jack_get_ports = _jack_get_ports; + pContextStateJACK->jack_activate = _jack_activate; + pContextStateJACK->jack_deactivate = _jack_deactivate; + pContextStateJACK->jack_connect = _jack_connect; + pContextStateJACK->jack_port_register = _jack_port_register; + pContextStateJACK->jack_port_name = _jack_port_name; + pContextStateJACK->jack_port_get_buffer = _jack_port_get_buffer; + pContextStateJACK->jack_free = _jack_free; + } + #endif + + pContextStateJACK->tryStartServer = pContextConfigJACK->tryStartServer; + + /* + Getting here means the JACK library is installed, but it doesn't necessarily mean it's usable. We need to quickly test this by connecting + a temporary client. + */ + { + ma_jack_client_t* pDummyClient; + ma_result result = ma_context_open_client__jack(pContextStateJACK, &pDummyClient); + if (result != MA_SUCCESS) { + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(pLog, pContextStateJACK->jackSO); + } + #endif + + ma_free(pContextStateJACK, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } + + pContextStateJACK->jack_client_close(pDummyClient); + } + + *ppContextState = pContextStateJACK; + + return MA_SUCCESS; +} + +static void ma_context_uninit__jack(ma_context* pContext) +{ + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(pContext); + + MA_ASSERT(pContextStateJACK != NULL); + + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(ma_context_get_log(pContext), pContextStateJACK->jackSO); + } + #endif + + ma_free(pContextStateJACK, ma_context_get_allocation_callbacks(pContext)); +} + +static ma_device_enumeration_result ma_context_enumerate_devices_from_client__jack(ma_context* pContext, ma_jack_client_t* pClient, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(pContext); + ma_device_info deviceInfo; + const char** ppPorts; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; + const char* pCurrentClientName = NULL; + const char* pCurrentClientNameEnd = NULL; + ma_bool32 hasEnumeratedDefaultDevice = MA_FALSE; + int iPort; + + sampleRate = pContextStateJACK->jack_get_sample_rate(pClient); + + /* + Our devices can be derived from the port names. We're going to assume each port name is in the + format `:`. The client name will be our "device" name. Then we just + use the client name as a filter for calcuating the channel count. + */ + ppPorts = pContextStateJACK->jack_get_ports(pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ((deviceType == ma_device_type_capture) ? ma_JackPortIsOutput : ma_JackPortIsInput)); + if (ppPorts == NULL) { + return MA_DEVICE_ENUMERATION_ABORT; + } + + iPort = 0; + while (ppPorts[iPort] != NULL) { /* For each JACK client. */ + pCurrentClientName = ppPorts[iPort]; + pCurrentClientNameEnd = strchr(pCurrentClientName, ':'); + + MA_ZERO_OBJECT(&deviceInfo); + + /* + Default. I'm not sure on a good way to do this with JACK. The JACK way of doing things just + doesn't map very well with the miniaudio way. For now I'm just going to use the first client + as the default. + */ + if (hasEnumeratedDefaultDevice == MA_FALSE) { + hasEnumeratedDefaultDevice = MA_TRUE; + deviceInfo.isDefault = MA_TRUE; + } else { + deviceInfo.isDefault = MA_FALSE; + } + + /* ID. This is just the client name. */ + ma_strncpy_s(deviceInfo.id.jack, sizeof(deviceInfo.id.jack), pCurrentClientName, (size_t)(pCurrentClientNameEnd - pCurrentClientName)); + + /* Name. This is the same as the ID. */ + ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), deviceInfo.id.jack); + + /* For the native data format we need to count the number of ports to determine the channel count. */ + channels = 0; + while (ppPorts[iPort] != NULL) { + const char* pPortClientName = ppPorts[iPort]; + const char* pPortClientNameEnd = strchr(pPortClientName, ':'); + + if ((size_t)(pPortClientNameEnd - pPortClientName) != (size_t)(pCurrentClientNameEnd - pCurrentClientName)) { + break; /* Lengths are no equal so it's impossible for the name to be the same. */ + } + + if (memcmp(pPortClientName, pCurrentClientName, (size_t)(pCurrentClientNameEnd - pCurrentClientName)) != 0) { + break; /* Names are not equal. */ + } + + channels += 1; + iPort += 1; + } + + ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, channels, channels, sampleRate, sampleRate); + + cbResult = callback(deviceType, &deviceInfo, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_ABORT) { + return cbResult; + } + } + + return MA_DEVICE_ENUMERATION_CONTINUE; +} + +static ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(pContext); + ma_jack_client_t* pClient; + ma_result result; + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; + + MA_ASSERT(callback != NULL); + (void)pContext; + + /* In order to determine the sample rate and channnel count we need to open a client. */ + result = ma_context_open_client__jack(pContextStateJACK, &pClient); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Cannot enumerate devices because jack_open_client() failed."); + return result; + } + + /* Playback. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_devices_from_client__jack(pContext, pClient, ma_device_type_playback, callback, pUserData); + } + + /* Capture. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_devices_from_client__jack(pContext, pClient, ma_device_type_capture, callback, pUserData); + } + + (void)cbResult; /* For silencing a static analysis warning. */ + + pContextStateJACK->jack_client_close(pClient); + + return MA_SUCCESS; +} + + +static void ma_device__jack_shutdown_callback(void* pUserData) +{ + /* JACK died. Stop the device. */ + ma_device* pDevice = (ma_device*)pUserData; + MA_ASSERT(pDevice != NULL); + + ma_device_stop(pDevice); +} + +static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_jack* pDeviceStateJACK = ma_device_get_backend_state__jack(pDevice); + size_t bufferSizeCapture = 0; + size_t bufferSizePlayback = 0; + float* pNewBuffer; + + MA_ASSERT(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + bufferSizeCapture = frameCount * (pDevice->capture.internalChannels * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); + pDevice->playback.internalPeriodSizeInFrames = frameCount; + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + bufferSizePlayback = frameCount * (pDevice->playback.internalChannels * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); + pDevice->playback.internalPeriodSizeInFrames = frameCount; + } + + pNewBuffer = (float*)ma_realloc(pDeviceStateJACK->pIntermediaryBuffer, ma_max(bufferSizeCapture, bufferSizePlayback), ma_device_get_allocation_callbacks(pDevice)); + if (pNewBuffer == NULL) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to allocate memory for intermediary buffer after JACK buffer size change."); + return -1; + } + + pDeviceStateJACK->pIntermediaryBuffer = pNewBuffer; + + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_device_state_async_resize(&pDeviceStateJACK->async, frameCount, frameCount, ma_device_get_allocation_callbacks(pDevice)); + } + + return 0; +} + +static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_jack* pDeviceStateJACK = ma_device_get_backend_state__jack(pDevice); + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint32 iChannel; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + /* Channels need to be interleaved. */ + for (iChannel = 0; iChannel < pDevice->capture.internalChannels; iChannel += 1) { + const float* pSrc = (const float*)pContextStateJACK->jack_port_get_buffer(pDeviceStateJACK->ppPortsCapture[iChannel], frameCount); + if (pSrc != NULL) { + float* pDst = pDeviceStateJACK->pIntermediaryBuffer + iChannel; + ma_jack_nframes_t iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + *pDst = *pSrc; + + pDst += pDevice->capture.internalChannels; + pSrc += 1; + } + } + } + + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_device_state_async_process(&pDeviceStateJACK->async, pDevice, NULL, pDeviceStateJACK->pIntermediaryBuffer, frameCount); + } else { + ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateJACK->pIntermediaryBuffer, frameCount); + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_device_state_async_process(&pDeviceStateJACK->async, pDevice, pDeviceStateJACK->pIntermediaryBuffer, NULL, frameCount); + } else { + ma_device_handle_backend_data_callback(pDevice, pDeviceStateJACK->pIntermediaryBuffer, NULL, frameCount); + } + + /* Channels need to be deinterleaved. */ + for (iChannel = 0; iChannel < pDevice->playback.internalChannels; iChannel += 1) { + float* pDst = (float*)pContextStateJACK->jack_port_get_buffer(pDeviceStateJACK->ppPortsPlayback[iChannel], frameCount); + if (pDst != NULL) { + const float* pSrc = pDeviceStateJACK->pIntermediaryBuffer + iChannel; + ma_jack_nframes_t iFrame; + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + *pDst = *pSrc; + + pDst += 1; + pSrc += pDevice->playback.internalChannels; + } + } + } + } + + return 0; +} + +static void ma_device_uninit__jack(ma_device* pDevice); + +static ma_result ma_get_port_range_by_device_id__jack(const char** ppPorts, const ma_device_id* pDeviceID, ma_uint32* pIndex, ma_uint32* pCount) +{ + char pDefaultClientName[256]; + const char* pClientName; + ma_uint32 index = 0; + ma_uint32 count = 0; + ma_uint32 iPort = 0; + + if (pIndex != NULL) { + *pIndex = 0; + } + if (pCount != NULL) { + *pCount = 0; + } + + if (ppPorts == NULL || ppPorts[0] == NULL) { + return MA_DOES_NOT_EXIST; + } + + if (pDeviceID == NULL) { + const char* pPortClientNameEnd = strchr(ppPorts[0], ':'); + if (pPortClientNameEnd == NULL) { + ma_strcpy_s(pDefaultClientName, sizeof(pDefaultClientName), ppPorts[0]); + } else { + ma_strncpy_s(pDefaultClientName, sizeof(pDefaultClientName), ppPorts[0], (size_t)(pPortClientNameEnd - ppPorts[0])); + } + + pClientName = pDefaultClientName; + } else { + pClientName = pDeviceID->jack; + } + + /* We need to first find the index, which is the first port with the name specified by the device ID. */ + while (ppPorts[iPort] != NULL) { + const char* pPortClientName = ppPorts[iPort]; + const char* pPortClientNameEnd = strchr(pPortClientName, ':'); + + if (pPortClientNameEnd == NULL) { + pPortClientNameEnd = pPortClientName + strlen(pPortClientName); + } + + if (memcmp(pClientName, pPortClientName, (size_t)(pPortClientNameEnd - pPortClientName)) == 0) { + if (count == 0) { + index = iPort; + } + + count += 1; + } else { + /* Not this port. Move on or get out. */ + if (count > 0) { + break; /* Getting here means we've finished counting our ports. We're done. */ + } + } + + iPort += 1; + } + + if (pIndex != NULL) { + *pIndex = index; + } + if (pCount != NULL) { + *pCount = count; + } + + return MA_SUCCESS; +} + +static ma_result ma_device_init__jack(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + ma_device_state_jack* pDeviceStateJACK; + const ma_device_config_jack* pDeviceConfigJACK = (const ma_device_config_jack*)pDeviceBackendConfig; + ma_device_config_jack defaultConfigJACK; + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + ma_uint32 periodSizeInFrames; + size_t bufferSizeCapture = 0; + size_t bufferSizePlayback = 0; + ma_uint32 sampleRate; + + if (pDeviceConfigJACK == NULL) { + defaultConfigJACK = ma_device_config_jack_init(); + pDeviceConfigJACK = &defaultConfigJACK; + } + + if (deviceType == ma_device_type_loopback) { + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Loopback mode not supported."); + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + /* No exclusive mode with the JACK backend. */ + if (((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Exclusive mode not supported."); + return MA_SHARE_MODE_NOT_SUPPORTED; + } + + pDeviceStateJACK = (ma_device_state_jack*)ma_malloc(sizeof(*pDeviceStateJACK), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateJACK == NULL) { + return MA_OUT_OF_MEMORY; + } + + pDeviceStateJACK->noAutoConnect = pDeviceConfigJACK->noAutoConnect; + + /* Open the client. */ + result = ma_context_open_client__jack(pContextStateJACK, &pDeviceStateJACK->pClient); + if (result != MA_SUCCESS) { + ma_free(pDeviceStateJACK, ma_device_get_allocation_callbacks(pDevice)); + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); + return result; + } + + /* Callbacks. */ + if (pContextStateJACK->jack_set_process_callback(pDeviceStateJACK->pClient, ma_device__jack_process_callback, pDevice) != 0) { + ma_free(pDeviceStateJACK, ma_device_get_allocation_callbacks(pDevice)); + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + if (pContextStateJACK->jack_set_buffer_size_callback(pDeviceStateJACK->pClient, ma_device__jack_buffer_size_callback, pDevice) != 0) { + ma_free(pDeviceStateJACK, ma_device_get_allocation_callbacks(pDevice)); + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + pContextStateJACK->jack_on_shutdown(pDeviceStateJACK->pClient, ma_device__jack_shutdown_callback, pDevice); + + + /* The sample rate is always the same. */ + sampleRate = pContextStateJACK->jack_get_sample_rate(pDeviceStateJACK->pClient); + + /* The buffer size in frames can change. */ + periodSizeInFrames = pContextStateJACK->jack_get_buffer_size(pDeviceStateJACK->pClient); + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + const char** ppPorts; + ma_uint32 iPort; + ma_uint32 portIndex; + ma_uint32 portCount; + ma_uint32 portCountToAutoConnect; + ma_uint32 channels; + + ppPorts = pContextStateJACK->jack_get_ports(pDeviceStateJACK->pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); + if (ppPorts == NULL) { + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical input ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + result = ma_get_port_range_by_device_id__jack(ppPorts, pDescriptorCapture->pDeviceID, &portIndex, &portCount); + if (result != MA_SUCCESS) { + pContextStateJACK->jack_free((void*)ppPorts); + ma_log_postf(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Could not find ports for client \"%s\".", (pDescriptorCapture->pDeviceID != NULL) ? pDescriptorCapture->pDeviceID->jack : ""); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + /* If the application has requested the default channel count, make it equal to the number of output ports of the physical input device. */ + channels = pDescriptorCapture->channels; + if (channels == 0) { + channels = portCount; + } + + /* We need to know how many ports to auto-connect. */ + portCountToAutoConnect = ma_min(portCount, channels); + + pDeviceStateJACK->ppPortsCapture = (ma_jack_port_t**)ma_malloc(sizeof(*pDeviceStateJACK->ppPortsCapture) * channels, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateJACK->ppPortsCapture == NULL) { + pContextStateJACK->jack_free((void*)ppPorts); + ma_free(pDeviceStateJACK, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < channels; iPort += 1) { + char name[64]; + ma_strcpy_s(name, sizeof(name), "capture"); + ma_itoa_s((int)iPort, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ + + pDeviceStateJACK->ppPortsCapture[iPort] = pContextStateJACK->jack_port_register(pDeviceStateJACK->pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); + if (pDeviceStateJACK->ppPortsCapture[iPort] == NULL) { + ma_device_uninit__jack(pDevice); + pContextStateJACK->jack_free((void*)ppPorts); + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + if (pDeviceConfigJACK->noAutoConnect == MA_FALSE) { + if (iPort < portCountToAutoConnect) { + const char* pServerPort = ppPorts[portIndex + iPort]; + const char* pClientPort = pContextStateJACK->jack_port_name(pDeviceStateJACK->ppPortsCapture[iPort]); + + pContextStateJACK->jack_connect(pDeviceStateJACK->pClient, pServerPort, pClientPort); + } + } + } + + pContextStateJACK->jack_free((void*)ppPorts); + + pDescriptorCapture->format = ma_format_f32; + pDescriptorCapture->sampleRate = sampleRate; + pDescriptorCapture->channels = channels; + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); + pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; + pDescriptorCapture->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ + + bufferSizeCapture = periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels); + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + const char** ppPorts; + ma_uint32 iPort; + ma_uint32 portIndex; + ma_uint32 portCount; + ma_uint32 portCountToAutoConnect; + ma_uint32 channels; + + ppPorts = pContextStateJACK->jack_get_ports(pDeviceStateJACK->pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); + if (ppPorts == NULL) { + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical input ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + result = ma_get_port_range_by_device_id__jack(ppPorts, pDescriptorPlayback->pDeviceID, &portIndex, &portCount); + if (result != MA_SUCCESS) { + pContextStateJACK->jack_free((void*)ppPorts); + ma_log_postf(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Could not find ports for client \"%s\".", (pDescriptorPlayback->pDeviceID != NULL) ? pDescriptorPlayback->pDeviceID->jack : ""); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + /* If the application has requested the default channel count, make it equal to the number of input ports of the physical output device. */ + channels = pDescriptorPlayback->channels; + if (channels == 0) { + channels = portCount; + } + + /* We need to know how many ports to auto-connect. */ + portCountToAutoConnect = ma_min(portCount, channels); + + pDeviceStateJACK->ppPortsPlayback = (ma_jack_port_t**)ma_malloc(sizeof(*pDeviceStateJACK->ppPortsPlayback) * channels, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateJACK->ppPortsPlayback == NULL) { + pContextStateJACK->jack_free((void*)ppPorts); + ma_free(pDeviceStateJACK->ppPortsCapture, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < channels; iPort += 1) { + char name[64]; + ma_strcpy_s(name, sizeof(name), "playback"); + ma_itoa_s((int)iPort, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ + + pDeviceStateJACK->ppPortsPlayback[iPort] = pContextStateJACK->jack_port_register(pDeviceStateJACK->pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); + if (pDeviceStateJACK->ppPortsPlayback[iPort] == NULL) { + ma_device_uninit__jack(pDevice); + pContextStateJACK->jack_free((void*)ppPorts); + ma_log_post(pContextStateJACK->pLog, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + /* Connect the port if we're auto-connecting. */ + if (pDeviceConfigJACK->noAutoConnect == MA_FALSE) { + if (iPort < portCountToAutoConnect) { + const char* pServerPort = ppPorts[portIndex + iPort]; + const char* pClientPort = pContextStateJACK->jack_port_name(pDeviceStateJACK->ppPortsPlayback[iPort]); + + pContextStateJACK->jack_connect(pDeviceStateJACK->pClient, pClientPort, pServerPort); + } + } + } + + pContextStateJACK->jack_free((void*)ppPorts); + + pDescriptorPlayback->format = ma_format_f32; + pDescriptorPlayback->sampleRate = sampleRate; + pDescriptorPlayback->channels = channels; + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; + pDescriptorPlayback->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ + + bufferSizePlayback = periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels); + } - MA_ASSERT(pContext != NULL); - MA_ASSERT(pOP != NULL); + /* We need an intermediary buffer for interleaving and deinterleaving. */ + pDeviceStateJACK->pIntermediaryBuffer = (float*)ma_calloc(ma_max(bufferSizeCapture, bufferSizePlayback), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateJACK->pIntermediaryBuffer == NULL) { + ma_device_uninit__jack(pDevice); + return MA_OUT_OF_MEMORY; + } - for (;;) { - state = ((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP); - if (state != MA_PA_OPERATION_RUNNING) { - break; /* Done. */ - } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); - if (resultPA < 0) { - return ma_result_from_pulse(resultPA); + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + result = ma_device_state_async_init(deviceType, pDescriptorPlayback, pDescriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateJACK->async); + if (result != MA_SUCCESS) { + ma_device_uninit__jack(pDevice); + return result; + } + } else { + result = ma_semaphore_init(0, &pDeviceStateJACK->stepSemaphore); + if (result != MA_SUCCESS) { + ma_device_uninit__jack(pDevice); + return result; } } + *ppDeviceState = pDeviceStateJACK; + return MA_SUCCESS; } -static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP) +static void ma_device_uninit__jack(ma_device* pDevice) { - ma_result result; + ma_device_state_jack* pDeviceStateJACK = ma_device_get_backend_state__jack(pDevice); + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); - if (pOP == NULL) { - return MA_INVALID_ARGS; + if (pDeviceStateJACK->pClient != NULL) { + pContextStateJACK->jack_client_close(pDeviceStateJACK->pClient); + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_free(pDeviceStateJACK->ppPortsCapture, ma_device_get_allocation_callbacks(pDevice)); + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_free(pDeviceStateJACK->ppPortsPlayback, ma_device_get_allocation_callbacks(pDevice)); } - result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_device_state_async_uninit(&pDeviceStateJACK->async, ma_device_get_allocation_callbacks(pDevice)); + } else { + ma_semaphore_uninit(&pDeviceStateJACK->stepSemaphore); + } - return result; + ma_free(pDeviceStateJACK->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice)); + ma_free(pDeviceStateJACK, ma_device_get_allocation_callbacks(pDevice)); } -static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pPulseContext) + +static ma_result ma_device_start__jack(ma_device* pDevice) { - int resultPA; - ma_pa_context_state_t state; + ma_device_state_jack* pDeviceStateJACK = ma_device_get_backend_state__jack(pDevice); + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(ma_device_get_context(pDevice)); + int resultJACK; - for (;;) { - state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pPulseContext); - if (state == MA_PA_CONTEXT_READY) { - break; /* Done. */ - } + resultJACK = pContextStateJACK->jack_activate(pDeviceStateJACK->pClient); + if (resultJACK != 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client."); + return MA_FAILED_TO_START_BACKEND_DEVICE; + } - if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context."); - return MA_ERROR; - } + return MA_SUCCESS; +} - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); - if (resultPA < 0) { - return ma_result_from_pulse(resultPA); - } +static ma_result ma_device_stop__jack(ma_device* pDevice) +{ + ma_device_state_jack* pDeviceStateJACK = ma_device_get_backend_state__jack(pDevice); + ma_context_state_jack* pContextStateJACK = ma_context_get_backend_state__jack(ma_device_get_context(pDevice)); + + if (pContextStateJACK->jack_deactivate(pDeviceStateJACK->pClient) != 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client."); + return MA_ERROR; } - /* Should never get here. */ return MA_SUCCESS; } -static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pStream) +static ma_result ma_device_step__jack(ma_device* pDevice, ma_blocking_mode blockingMode) { - int resultPA; - ma_pa_stream_state_t state; + ma_device_state_jack* pDeviceStateJACK = ma_device_get_backend_state__jack(pDevice); - for (;;) { - state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pStream); - if (state == MA_PA_STREAM_READY) { - break; /* Done. */ + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + return ma_device_state_async_step(&pDeviceStateJACK->async, pDevice, blockingMode, NULL); + } else { + /* In multithreaded mode we don't actually do anything here except wait on a semaphore. */ + if (blockingMode == MA_BLOCKING_MODE_BLOCKING) { + ma_semaphore_wait(&pDeviceStateJACK->stepSemaphore); } - if (state == MA_PA_STREAM_FAILED || state == MA_PA_STREAM_TERMINATED) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream."); - return MA_ERROR; + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); - if (resultPA < 0) { - return ma_result_from_pulse(resultPA); - } + return MA_SUCCESS; } - - return MA_SUCCESS; } +static void ma_device_wakeup__jack(ma_device* pDevice) +{ + ma_device_state_jack* pDeviceStateJACK = ma_device_get_backend_state__jack(pDevice); + + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + ma_device_state_async_release(&pDeviceStateJACK->async); + } else { + ma_semaphore_release(&pDeviceStateJACK->stepSemaphore); + } +} -static ma_result ma_init_pa_mainloop_and_pa_context__pulse(ma_context* pContext, const char* pApplicationName, const char* pServerName, ma_bool32 tryAutoSpawn, ma_ptr* ppMainLoop, ma_ptr* ppPulseContext) +static ma_device_backend_vtable ma_gDeviceBackendVTable_JACK = { - ma_result result; - ma_ptr pMainLoop; - ma_ptr pPulseContext; + ma_backend_info__jack, + ma_context_init__jack, + ma_context_uninit__jack, + ma_context_enumerate_devices__jack, + ma_device_init__jack, + ma_device_uninit__jack, + ma_device_start__jack, + ma_device_stop__jack, + ma_device_step__jack, + ma_device_wakeup__jack +}; - MA_ASSERT(ppMainLoop != NULL); - MA_ASSERT(ppPulseContext != NULL); +ma_device_backend_vtable* ma_device_backend_jack = &ma_gDeviceBackendVTable_JACK; +#else +ma_device_backend_vtable* ma_device_backend_jack = NULL; +#endif /* MA_HAS_JACK */ - /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ - pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop."); - return MA_FAILED_TO_INIT_BACKEND; - } +MA_API ma_device_backend_vtable* ma_jack_get_vtable(void) +{ + return ma_device_backend_jack; +} - pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pMainLoop), pApplicationName); - if (pPulseContext == NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context."); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); - return MA_FAILED_TO_INIT_BACKEND; - } +MA_API ma_context_config_jack ma_context_config_jack_init(void) +{ + ma_context_config_jack config; - /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ - result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pPulseContext, pServerName, (tryAutoSpawn) ? MA_PA_CONTEXT_NOFLAGS : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); - if (result != MA_SUCCESS) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)(pPulseContext)); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); - return result; - } + MA_ZERO_OBJECT(&config); - /* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */ - result = ma_wait_for_pa_context_to_connect__pulse(pContext, pMainLoop, pPulseContext); - if (result != MA_SUCCESS) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Waiting for connection failed."); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)(pPulseContext)); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); - return result; - } + return config; +} - *ppMainLoop = pMainLoop; - *ppPulseContext = pPulseContext; +MA_API ma_device_config_jack ma_device_config_jack_init(void) +{ + ma_device_config_jack config; - return MA_SUCCESS; + MA_ZERO_OBJECT(&config); + + return config; } -static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) -{ - ma_pa_sink_info* pInfoOut; - if (endOfList > 0) { - return; - } +/****************************************************************************** - /* - There has been a report that indicates that pInfo can be null which results - in a null pointer dereference below. We'll check for this for safety. - */ - if (pInfo == NULL) { - return; - } +Core Audio Backend - pInfoOut = (ma_pa_sink_info*)pUserData; - MA_ASSERT(pInfoOut != NULL); +References +========== +- Technical Note TN2091: Device input using the HAL Output Audio Unit + https://developer.apple.com/library/archive/technotes/tn2091/_index.html - *pInfoOut = *pInfo; +******************************************************************************/ +#ifdef MA_HAS_COREAUDIO +#include - (void)pPulseContext; /* Unused. */ -} +#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1 + #define MA_APPLE_MOBILE + #if defined(TARGET_OS_TV) && TARGET_OS_TV == 1 + #define MA_APPLE_TV + #endif + #if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 + #define MA_APPLE_WATCH + #endif + #if __has_feature(objc_arc) + #define MA_BRIDGE_TRANSFER __bridge_transfer + #define MA_BRIDGE_RETAINED __bridge_retained + #else + #define MA_BRIDGE_TRANSFER + #define MA_BRIDGE_RETAINED + #endif +#else + #define MA_APPLE_DESKTOP +#endif -static void ma_device_source_info_callback(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +#if defined(MA_APPLE_DESKTOP) +#include +#else +#include +#endif + +#include + +/* CoreFoundation */ +typedef Boolean (* ma_CFStringGetCString_proc)(CFStringRef theString, char* buffer, CFIndex bufferSize, CFStringEncoding encoding); +typedef void (* ma_CFRelease_proc )(CFTypeRef cf); + +/* CoreAudio */ +#if defined(MA_APPLE_DESKTOP) +typedef OSStatus (* ma_AudioObjectGetPropertyData_proc )(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* ioDataSize, void* outData); +typedef OSStatus (* ma_AudioObjectGetPropertyDataSize_proc )(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize); +typedef OSStatus (* ma_AudioObjectSetPropertyData_proc )(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData); +typedef OSStatus (* ma_AudioObjectAddPropertyListener_proc )(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, AudioObjectPropertyListenerProc inListener, void* inClientData); +typedef OSStatus (* ma_AudioObjectRemovePropertyListener_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, AudioObjectPropertyListenerProc inListener, void* inClientData); +#endif + +/* AudioToolbox */ +typedef AudioComponent (* ma_AudioComponentFindNext_proc )(AudioComponent inComponent, const AudioComponentDescription* inDesc); +typedef OSStatus (* ma_AudioComponentInstanceDispose_proc)(AudioComponentInstance inInstance); +typedef OSStatus (* ma_AudioComponentInstanceNew_proc )(AudioComponent inComponent, AudioComponentInstance* outInstance); +typedef OSStatus (* ma_AudioOutputUnitStart_proc )(AudioUnit inUnit); +typedef OSStatus (* ma_AudioOutputUnitStop_proc )(AudioUnit inUnit); +typedef OSStatus (* ma_AudioUnitAddPropertyListener_proc )(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcUserData); +typedef OSStatus (* ma_AudioUnitGetPropertyInfo_proc )(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32* outDataSize, Boolean* outWriteable); +typedef OSStatus (* ma_AudioUnitGetProperty_proc )(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData, UInt32* ioDataSize); +typedef OSStatus (* ma_AudioUnitSetProperty_proc )(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); +typedef OSStatus (* ma_AudioUnitInitialize_proc )(AudioUnit inUnit); +typedef OSStatus (* ma_AudioUnitRender_proc )(AudioUnit inUnit, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData); + + +#define MA_COREAUDIO_OUTPUT_BUS 0 +#define MA_COREAUDIO_INPUT_BUS 1 + +#if defined(MA_APPLE_DESKTOP) +static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_device_type deviceType, ma_bool32 disposePreviousAudioUnit); +#endif + +/* +Core Audio + +So far, Core Audio has been the worst backend to work with due to being both unintuitive and having almost no documentation +apart from comments in the headers (which admittedly are quite good). For my own purposes, and for anybody out there whose +needing to figure out how this darn thing works, I'm going to outline a few things here. + +Since miniaudio is a fairly low-level API, one of the things it needs is control over specific devices, and it needs to be +able to identify whether or not it can be used as playback and/or capture. The AudioObject API is the only one I've seen +that supports this level of detail. There was some public domain sample code I stumbled across that used the AudioComponent +and AudioUnit APIs, but I couldn't see anything that gave low-level control over device selection and capabilities (the +distinction between playback and capture in particular). Therefore, miniaudio is using the AudioObject API. + +Most (all?) functions in the AudioObject API take a AudioObjectID as its input. This is the device identifier. When +retrieving global information, such as the device list, you use kAudioObjectSystemObject. When retrieving device-specific +data, you pass in the ID for that device. In order to retrieve device-specific IDs you need to enumerate over each of the +devices. This is done using the AudioObjectGetPropertyDataSize() and AudioObjectGetPropertyData() APIs which seem to be +the central APIs for retrieving information about the system and specific devices. + +To use the AudioObjectGetPropertyData() API you need to use the notion of a property address. A property address is a +structure with three variables and is used to identify which property you are getting or setting. The first is the "selector" +which is basically the specific property that you're wanting to retrieve or set. The second is the "scope", which is +typically set to kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput for input-specific properties and +kAudioObjectPropertyScopeOutput for output-specific properties. The last is the "element" which is always set to +kAudioObjectPropertyElementMain in miniaudio's case. I don't know of any cases where this would be set to anything different. + +Back to the earlier issue of device retrieval, you first use the AudioObjectGetPropertyDataSize() API to retrieve the size +of the raw data which is just a list of AudioDeviceID's. You use the kAudioObjectSystemObject AudioObjectID, and a property +address with the kAudioHardwarePropertyDevices selector and the kAudioObjectPropertyScopeGlobal scope. Once you have the +size, allocate a block of memory of that size and then call AudioObjectGetPropertyData(). The data is just a list of +AudioDeviceID's so just do "dataSize/sizeof(AudioDeviceID)" to know the device count. +*/ + +typedef struct ma_context_state_coreaudio { - ma_pa_source_info* pInfoOut; + ma_handle hCoreFoundation; + ma_CFStringGetCString_proc CFStringGetCString; + ma_CFRelease_proc CFRelease; - if (endOfList > 0) { - return; - } +#if defined(MA_APPLE_DESKTOP) + ma_handle hCoreAudio; + ma_AudioObjectGetPropertyData_proc AudioObjectGetPropertyData; + ma_AudioObjectGetPropertyDataSize_proc AudioObjectGetPropertyDataSize; + ma_AudioObjectSetPropertyData_proc AudioObjectSetPropertyData; + ma_AudioObjectAddPropertyListener_proc AudioObjectAddPropertyListener; + ma_AudioObjectRemovePropertyListener_proc AudioObjectRemovePropertyListener; +#endif + + ma_handle hAudioUnit; /* Could possibly be set to AudioToolbox on later versions of macOS. */ + ma_AudioComponentFindNext_proc AudioComponentFindNext; + ma_AudioComponentInstanceDispose_proc AudioComponentInstanceDispose; + ma_AudioComponentInstanceNew_proc AudioComponentInstanceNew; + ma_AudioOutputUnitStart_proc AudioOutputUnitStart; + ma_AudioOutputUnitStop_proc AudioOutputUnitStop; + ma_AudioUnitAddPropertyListener_proc AudioUnitAddPropertyListener; + ma_AudioUnitGetPropertyInfo_proc AudioUnitGetPropertyInfo; + ma_AudioUnitGetProperty_proc AudioUnitGetProperty; + ma_AudioUnitSetProperty_proc AudioUnitSetProperty; + ma_AudioUnitInitialize_proc AudioUnitInitialize; + ma_AudioUnitRender_proc AudioUnitRender; - /* - There has been a report that indicates that pInfo can be null which results - in a null pointer dereference below. We'll check for this for safety. - */ - if (pInfo == NULL) { - return; - } + AudioComponent component; + ma_bool32 noAudioSessionDeactivate; /* For tracking whether or not the iOS audio session should be explicitly deactivated. Set from the config in ma_context_init__coreaudio(). */ +} ma_context_state_coreaudio; + +typedef struct ma_device_state_coreaudio +{ + ma_device_state_async async; + ma_uint32 deviceObjectIDPlayback; + ma_uint32 deviceObjectIDCapture; + AudioUnit audioUnitPlayback; + AudioUnit audioUnitCapture; + AudioBufferList* pAudioBufferList; /* Only used for input devices. */ + ma_uint32 audioBufferCapInFrames; /* Only used for input devices. The capacity in frames of each buffer in pAudioBufferList. */ + ma_event stopEvent; + ma_uint32 originalPeriodSizeInFrames; + ma_uint32 originalPeriodSizeInMilliseconds; + ma_uint32 originalPeriods; + ma_bool32 isDefaultPlaybackDevice; + ma_bool32 isDefaultCaptureDevice; + ma_atomic_bool32 isSwitchingPlaybackDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ + ma_atomic_bool32 isSwitchingCaptureDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ + void* pNotificationHandler; /* Only used on mobile platforms. Obj-C object for handling route changes. */ +} ma_device_state_coreaudio; - pInfoOut = (ma_pa_source_info*)pUserData; - MA_ASSERT(pInfoOut != NULL); - *pInfoOut = *pInfo; +static ma_context_state_coreaudio* ma_context_get_backend_state__coreaudio(ma_context* pContext) +{ + return (ma_context_state_coreaudio*)ma_context_get_backend_state(pContext); +} - (void)pPulseContext; /* Unused. */ +static ma_device_state_coreaudio* ma_device_get_backend_state__coreaudio(ma_device* pDevice) +{ + return (ma_device_state_coreaudio*)ma_device_get_backend_state(pDevice); +} + + +static ma_result ma_result_from_OSStatus(OSStatus status) +{ + switch (status) + { + case noErr: return MA_SUCCESS; + #if defined(MA_APPLE_DESKTOP) + case kAudioHardwareNotRunningError: return MA_DEVICE_NOT_STARTED; + case kAudioHardwareUnspecifiedError: return MA_ERROR; + case kAudioHardwareUnknownPropertyError: return MA_INVALID_ARGS; + case kAudioHardwareBadPropertySizeError: return MA_INVALID_OPERATION; + case kAudioHardwareIllegalOperationError: return MA_INVALID_OPERATION; + case kAudioHardwareBadObjectError: return MA_INVALID_ARGS; + case kAudioHardwareBadDeviceError: return MA_INVALID_ARGS; + case kAudioHardwareBadStreamError: return MA_INVALID_ARGS; + case kAudioHardwareUnsupportedOperationError: return MA_INVALID_OPERATION; + case kAudioDeviceUnsupportedFormatError: return MA_FORMAT_NOT_SUPPORTED; + case kAudioDevicePermissionsError: return MA_ACCESS_DENIED; + #endif + default: return MA_ERROR; + } } #if 0 -static void ma_device_sink_name_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) +static ma_channel ma_channel_from_AudioChannelBitmap(AudioChannelBitmap bit) { - ma_device* pDevice; + switch (bit) + { + case kAudioChannelBit_Left: return MA_CHANNEL_LEFT; + case kAudioChannelBit_Right: return MA_CHANNEL_RIGHT; + case kAudioChannelBit_Center: return MA_CHANNEL_FRONT_CENTER; + case kAudioChannelBit_LFEScreen: return MA_CHANNEL_LFE; + case kAudioChannelBit_LeftSurround: return MA_CHANNEL_BACK_LEFT; + case kAudioChannelBit_RightSurround: return MA_CHANNEL_BACK_RIGHT; + case kAudioChannelBit_LeftCenter: return MA_CHANNEL_FRONT_LEFT_CENTER; + case kAudioChannelBit_RightCenter: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case kAudioChannelBit_CenterSurround: return MA_CHANNEL_BACK_CENTER; + case kAudioChannelBit_LeftSurroundDirect: return MA_CHANNEL_SIDE_LEFT; + case kAudioChannelBit_RightSurroundDirect: return MA_CHANNEL_SIDE_RIGHT; + case kAudioChannelBit_TopCenterSurround: return MA_CHANNEL_TOP_CENTER; + case kAudioChannelBit_VerticalHeightLeft: return MA_CHANNEL_TOP_FRONT_LEFT; + case kAudioChannelBit_VerticalHeightCenter: return MA_CHANNEL_TOP_FRONT_CENTER; + case kAudioChannelBit_VerticalHeightRight: return MA_CHANNEL_TOP_FRONT_RIGHT; + case kAudioChannelBit_TopBackLeft: return MA_CHANNEL_TOP_BACK_LEFT; + case kAudioChannelBit_TopBackCenter: return MA_CHANNEL_TOP_BACK_CENTER; + case kAudioChannelBit_TopBackRight: return MA_CHANNEL_TOP_BACK_RIGHT; + default: return MA_CHANNEL_NONE; + } +} +#endif - if (endOfList > 0) { - return; +static ma_result ma_format_from_AudioStreamBasicDescription(const AudioStreamBasicDescription* pDescription, ma_format* pFormatOut) +{ + MA_ASSERT(pDescription != NULL); + MA_ASSERT(pFormatOut != NULL); + + *pFormatOut = ma_format_unknown; /* Safety. */ + + /* There's a few things miniaudio doesn't support. */ + if (pDescription->mFormatID != kAudioFormatLinearPCM) { + return MA_FORMAT_NOT_SUPPORTED; } - pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + /* We don't support any non-packed formats that are aligned high. */ + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) != 0) { + return MA_FORMAT_NOT_SUPPORTED; + } + + /* Only supporting native-endian. */ + if ((ma_is_little_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) != 0) || (ma_is_big_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) == 0)) { + return MA_FORMAT_NOT_SUPPORTED; + } - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), pInfo->description, (size_t)-1); + /* We are not currently supporting non-interleaved formats (this will be added in a future version of miniaudio). */ + /*if ((pDescription->mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0) { + return MA_FORMAT_NOT_SUPPORTED; + }*/ - (void)pPulseContext; /* Unused. */ + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0) { + if (pDescription->mBitsPerChannel == 32) { + *pFormatOut = ma_format_f32; + return MA_SUCCESS; + } + } else { + if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) != 0) { + if (pDescription->mBitsPerChannel == 16) { + *pFormatOut = ma_format_s16; + return MA_SUCCESS; + } else if (pDescription->mBitsPerChannel == 24) { + if (pDescription->mBytesPerFrame == (pDescription->mBitsPerChannel/8 * pDescription->mChannelsPerFrame)) { + *pFormatOut = ma_format_s24; + return MA_SUCCESS; + } else { + if (pDescription->mBytesPerFrame/pDescription->mChannelsPerFrame == sizeof(ma_int32)) { + /* TODO: Implement ma_format_s24_32. */ + /**pFormatOut = ma_format_s24_32;*/ + /*return MA_SUCCESS;*/ + return MA_FORMAT_NOT_SUPPORTED; + } + } + } else if (pDescription->mBitsPerChannel == 32) { + *pFormatOut = ma_format_s32; + return MA_SUCCESS; + } + } else { + if (pDescription->mBitsPerChannel == 8) { + *pFormatOut = ma_format_u8; + return MA_SUCCESS; + } + } + } + + /* Getting here means the format is not supported. */ + return MA_FORMAT_NOT_SUPPORTED; } -static void ma_device_source_name_callback(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +#if defined(MA_APPLE_DESKTOP) +static ma_channel ma_channel_from_AudioChannelLabel(AudioChannelLabel label) { - ma_device* pDevice; + switch (label) + { + case kAudioChannelLabel_Unknown: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Unused: return MA_CHANNEL_NONE; + case kAudioChannelLabel_UseCoordinates: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Left: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_Right: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_Center: return MA_CHANNEL_FRONT_CENTER; + case kAudioChannelLabel_LFEScreen: return MA_CHANNEL_LFE; + case kAudioChannelLabel_LeftSurround: return MA_CHANNEL_BACK_LEFT; + case kAudioChannelLabel_RightSurround: return MA_CHANNEL_BACK_RIGHT; + case kAudioChannelLabel_LeftCenter: return MA_CHANNEL_FRONT_LEFT_CENTER; + case kAudioChannelLabel_RightCenter: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case kAudioChannelLabel_CenterSurround: return MA_CHANNEL_BACK_CENTER; + case kAudioChannelLabel_LeftSurroundDirect: return MA_CHANNEL_SIDE_LEFT; + case kAudioChannelLabel_RightSurroundDirect: return MA_CHANNEL_SIDE_RIGHT; + case kAudioChannelLabel_TopCenterSurround: return MA_CHANNEL_TOP_CENTER; + case kAudioChannelLabel_VerticalHeightLeft: return MA_CHANNEL_TOP_FRONT_LEFT; + case kAudioChannelLabel_VerticalHeightCenter: return MA_CHANNEL_TOP_FRONT_CENTER; + case kAudioChannelLabel_VerticalHeightRight: return MA_CHANNEL_TOP_FRONT_RIGHT; + case kAudioChannelLabel_TopBackLeft: return MA_CHANNEL_TOP_BACK_LEFT; + case kAudioChannelLabel_TopBackCenter: return MA_CHANNEL_TOP_BACK_CENTER; + case kAudioChannelLabel_TopBackRight: return MA_CHANNEL_TOP_BACK_RIGHT; + case kAudioChannelLabel_RearSurroundLeft: return MA_CHANNEL_BACK_LEFT; + case kAudioChannelLabel_RearSurroundRight: return MA_CHANNEL_BACK_RIGHT; + case kAudioChannelLabel_LeftWide: return MA_CHANNEL_SIDE_LEFT; + case kAudioChannelLabel_RightWide: return MA_CHANNEL_SIDE_RIGHT; + case kAudioChannelLabel_LFE2: return MA_CHANNEL_LFE; + case kAudioChannelLabel_LeftTotal: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_RightTotal: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_HearingImpaired: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Narration: return MA_CHANNEL_MONO; + case kAudioChannelLabel_Mono: return MA_CHANNEL_MONO; + case kAudioChannelLabel_DialogCentricMix: return MA_CHANNEL_MONO; + case kAudioChannelLabel_CenterSurroundDirect: return MA_CHANNEL_BACK_CENTER; + case kAudioChannelLabel_Haptic: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_W: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_X: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_Y: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Ambisonic_Z: return MA_CHANNEL_NONE; + case kAudioChannelLabel_MS_Mid: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_MS_Side: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_XY_X: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_XY_Y: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_HeadphonesLeft: return MA_CHANNEL_LEFT; + case kAudioChannelLabel_HeadphonesRight: return MA_CHANNEL_RIGHT; + case kAudioChannelLabel_ClickTrack: return MA_CHANNEL_NONE; + case kAudioChannelLabel_ForeignLanguage: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Discrete: return MA_CHANNEL_NONE; + case kAudioChannelLabel_Discrete_0: return MA_CHANNEL_AUX_0; + case kAudioChannelLabel_Discrete_1: return MA_CHANNEL_AUX_1; + case kAudioChannelLabel_Discrete_2: return MA_CHANNEL_AUX_2; + case kAudioChannelLabel_Discrete_3: return MA_CHANNEL_AUX_3; + case kAudioChannelLabel_Discrete_4: return MA_CHANNEL_AUX_4; + case kAudioChannelLabel_Discrete_5: return MA_CHANNEL_AUX_5; + case kAudioChannelLabel_Discrete_6: return MA_CHANNEL_AUX_6; + case kAudioChannelLabel_Discrete_7: return MA_CHANNEL_AUX_7; + case kAudioChannelLabel_Discrete_8: return MA_CHANNEL_AUX_8; + case kAudioChannelLabel_Discrete_9: return MA_CHANNEL_AUX_9; + case kAudioChannelLabel_Discrete_10: return MA_CHANNEL_AUX_10; + case kAudioChannelLabel_Discrete_11: return MA_CHANNEL_AUX_11; + case kAudioChannelLabel_Discrete_12: return MA_CHANNEL_AUX_12; + case kAudioChannelLabel_Discrete_13: return MA_CHANNEL_AUX_13; + case kAudioChannelLabel_Discrete_14: return MA_CHANNEL_AUX_14; + case kAudioChannelLabel_Discrete_15: return MA_CHANNEL_AUX_15; + case kAudioChannelLabel_Discrete_65535: return MA_CHANNEL_NONE; - if (endOfList > 0) { - return; - } + #if 0 /* Introduced in a later version of macOS. */ + case kAudioChannelLabel_HOA_ACN: return MA_CHANNEL_NONE; + case kAudioChannelLabel_HOA_ACN_0: return MA_CHANNEL_AUX_0; + case kAudioChannelLabel_HOA_ACN_1: return MA_CHANNEL_AUX_1; + case kAudioChannelLabel_HOA_ACN_2: return MA_CHANNEL_AUX_2; + case kAudioChannelLabel_HOA_ACN_3: return MA_CHANNEL_AUX_3; + case kAudioChannelLabel_HOA_ACN_4: return MA_CHANNEL_AUX_4; + case kAudioChannelLabel_HOA_ACN_5: return MA_CHANNEL_AUX_5; + case kAudioChannelLabel_HOA_ACN_6: return MA_CHANNEL_AUX_6; + case kAudioChannelLabel_HOA_ACN_7: return MA_CHANNEL_AUX_7; + case kAudioChannelLabel_HOA_ACN_8: return MA_CHANNEL_AUX_8; + case kAudioChannelLabel_HOA_ACN_9: return MA_CHANNEL_AUX_9; + case kAudioChannelLabel_HOA_ACN_10: return MA_CHANNEL_AUX_10; + case kAudioChannelLabel_HOA_ACN_11: return MA_CHANNEL_AUX_11; + case kAudioChannelLabel_HOA_ACN_12: return MA_CHANNEL_AUX_12; + case kAudioChannelLabel_HOA_ACN_13: return MA_CHANNEL_AUX_13; + case kAudioChannelLabel_HOA_ACN_14: return MA_CHANNEL_AUX_14; + case kAudioChannelLabel_HOA_ACN_15: return MA_CHANNEL_AUX_15; + case kAudioChannelLabel_HOA_ACN_65024: return MA_CHANNEL_NONE; + #endif - pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + default: return MA_CHANNEL_NONE; + } +} - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), pInfo->description, (size_t)-1); +static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, ma_channel* pChannelMap, size_t channelMapCap) +{ + MA_ASSERT(pChannelLayout != NULL); - (void)pPulseContext; /* Unused. */ -} + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { + UInt32 iChannel; + for (iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions && iChannel < channelMapCap; ++iChannel) { + pChannelMap[iChannel] = ma_channel_from_AudioChannelLabel(pChannelLayout->mChannelDescriptions[iChannel].mChannelLabel); + } + } else +#if 0 + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { + /* This is the same kind of system that's used by Windows audio APIs. */ + UInt32 iChannel = 0; + UInt32 iBit; + AudioChannelBitmap bitmap = pChannelLayout->mChannelBitmap; + for (iBit = 0; iBit < 32 && iChannel < channelMapCap; ++iBit) { + AudioChannelBitmap bit = bitmap & (1 << iBit); + if (bit != 0) { + pChannelMap[iChannel++] = ma_channel_from_AudioChannelBit(bit); + } + } + } else #endif + { + /* + Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should + be updated to determine the mapping based on the tag. + */ + UInt32 channelCount; + + /* Our channel map retrieval APIs below take 32-bit integers, so we'll want to clamp the channel map capacity. */ + if (channelMapCap > 0xFFFFFFFF) { + channelMapCap = 0xFFFFFFFF; + } + + channelCount = ma_min(AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag), (UInt32)channelMapCap); + + switch (pChannelLayout->mChannelLayoutTag) + { + case kAudioChannelLayoutTag_Mono: + case kAudioChannelLayoutTag_Stereo: + case kAudioChannelLayoutTag_StereoHeadphones: + case kAudioChannelLayoutTag_MatrixStereo: + case kAudioChannelLayoutTag_MidSide: + case kAudioChannelLayoutTag_XY: + case kAudioChannelLayoutTag_Binaural: + case kAudioChannelLayoutTag_Ambisonic_B_Format: + { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); + } break; + + case kAudioChannelLayoutTag_Octagonal: + { + pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; + pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; + } MA_FALLTHROUGH; /* Intentional fallthrough. */ + case kAudioChannelLayoutTag_Hexagonal: + { + pChannelMap[5] = MA_CHANNEL_BACK_CENTER; + } MA_FALLTHROUGH; /* Intentional fallthrough. */ + case kAudioChannelLayoutTag_Pentagonal: + { + pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; + } MA_FALLTHROUGH; /* Intentional fallthrough. */ + case kAudioChannelLayoutTag_Quadraphonic: + { + pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; + pChannelMap[2] = MA_CHANNEL_BACK_LEFT; + pChannelMap[1] = MA_CHANNEL_RIGHT; + pChannelMap[0] = MA_CHANNEL_LEFT; + } break; -static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_sink_info* pSinkInfo) -{ - ma_pa_operation* pOP; + /* TODO: Add support for more tags here. */ - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, pDeviceName, ma_device_sink_info_callback, pSinkInfo); - if (pOP == NULL) { - return MA_ERROR; + default: + { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); + } break; + } } - return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); + return MA_SUCCESS; } -static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_source_info* pSourceInfo) -{ - ma_pa_operation* pOP; - - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, pDeviceName, ma_device_source_info_callback, pSourceInfo); - if (pOP == NULL) { - return MA_ERROR; - } +#if (defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0) || \ + (defined(__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) +#define AUDIO_OBJECT_PROPERTY_ELEMENT kAudioObjectPropertyElementMain +#else +/* kAudioObjectPropertyElementMaster is deprecated. */ +#define AUDIO_OBJECT_PROPERTY_ELEMENT kAudioObjectPropertyElementMaster +#endif - return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); -} +/* kAudioDevicePropertyScope* were renamed to kAudioObjectPropertyScope* in 10.8. */ +#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8) +#define kAudioObjectPropertyScopeInput kAudioDevicePropertyScopeInput +#define kAudioObjectPropertyScopeOutput kAudioDevicePropertyScopeOutput +#endif -static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext, ma_device_type deviceType, ma_uint32* pIndex) +static ma_result ma_get_device_object_ids__coreaudio(ma_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) /* NOTE: Free the returned buffer with ma_free(). */ { - ma_result result; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddressDevices; + UInt32 deviceObjectsDataSize; + OSStatus status; + AudioObjectID* pDeviceObjectIDs; MA_ASSERT(pContext != NULL); - MA_ASSERT(pIndex != NULL); + MA_ASSERT(pDeviceCount != NULL); + MA_ASSERT(ppDeviceObjectIDs != NULL); - if (pIndex != NULL) { - *pIndex = (ma_uint32)-1; - } + /* Safety. */ + *pDeviceCount = 0; + *ppDeviceObjectIDs = NULL; - if (deviceType == ma_device_type_playback) { - ma_pa_sink_info sinkInfo; - result = ma_context_get_sink_info__pulse(pContext, NULL, &sinkInfo); - if (result != MA_SUCCESS) { - return result; - } + propAddressDevices.mSelector = kAudioHardwarePropertyDevices; + propAddressDevices.mScope = kAudioObjectPropertyScopeGlobal; + propAddressDevices.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - if (pIndex != NULL) { - *pIndex = sinkInfo.index; - } + status = pContextStateCoreAudio->AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize); + if (status != noErr) { + return ma_result_from_OSStatus(status); } - if (deviceType == ma_device_type_capture) { - ma_pa_source_info sourceInfo; - result = ma_context_get_source_info__pulse(pContext, NULL, &sourceInfo); - if (result != MA_SUCCESS) { - return result; - } + pDeviceObjectIDs = (AudioObjectID*)ma_malloc(deviceObjectsDataSize, &pContext->allocationCallbacks); + if (pDeviceObjectIDs == NULL) { + return MA_OUT_OF_MEMORY; + } - if (pIndex != NULL) { - *pIndex = sourceInfo.index; - } + status = pContextStateCoreAudio->AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize, pDeviceObjectIDs); + if (status != noErr) { + ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); + return ma_result_from_OSStatus(status); } + *pDeviceCount = deviceObjectsDataSize / sizeof(AudioObjectID); + *ppDeviceObjectIDs = pDeviceObjectIDs; + return MA_SUCCESS; } - -typedef struct +static ma_result ma_get_AudioObject_uid_as_CFStringRef(ma_context* pContext, AudioObjectID objectID, CFStringRef* pUID) { - ma_context* pContext; - ma_enum_devices_callback_proc callback; - void* pUserData; - ma_bool32 isTerminated; - ma_uint32 defaultDeviceIndexPlayback; - ma_uint32 defaultDeviceIndexCapture; -} ma_context_enumerate_devices_callback_data__pulse; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; -static void ma_context_enumerate_devices_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) -{ - ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; - ma_device_info deviceInfo; + MA_ASSERT(pContext != NULL); - MA_ASSERT(pData != NULL); + propAddress.mSelector = kAudioDevicePropertyDeviceUID; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - if (endOfList || pData->isTerminated) { - return; + dataSize = sizeof(*pUID); + status = pContextStateCoreAudio->AudioObjectGetPropertyData(objectID, &propAddress, 0, NULL, &dataSize, pUID); + if (status != noErr) { + return ma_result_from_OSStatus(status); } - MA_ZERO_OBJECT(&deviceInfo); + return MA_SUCCESS; +} - /* The name from PulseAudio is the ID for miniaudio. */ - if (pSinkInfo->name != NULL) { - ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); - } +static ma_result ma_get_AudioObject_uid(ma_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) +{ + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + CFStringRef uid; + ma_result result; - /* The description from PulseAudio is the name for miniaudio. */ - if (pSinkInfo->description != NULL) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); - } + MA_ASSERT(pContext != NULL); - if (pSinkInfo->index == pData->defaultDeviceIndexPlayback) { - deviceInfo.isDefault = MA_TRUE; + result = ma_get_AudioObject_uid_as_CFStringRef(pContext, objectID, &uid); + if (result != MA_SUCCESS) { + return result; } - pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_playback, &deviceInfo, pData->pUserData); + if (!pContextStateCoreAudio->CFStringGetCString(uid, bufferOut, bufferSize, kCFStringEncodingUTF8)) { + return MA_ERROR; + } - (void)pPulseContext; /* Unused. */ + pContextStateCoreAudio->CFRelease(uid); + return MA_SUCCESS; } -static void ma_context_enumerate_devices_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pSourceInfo, int endOfList, void* pUserData) +static ma_result ma_get_AudioObject_name(ma_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) { - ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; - ma_device_info deviceInfo; - - MA_ASSERT(pData != NULL); - - if (endOfList || pData->isTerminated) { - return; - } + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddress; + CFStringRef deviceName = NULL; + UInt32 dataSize; + OSStatus status; - MA_ZERO_OBJECT(&deviceInfo); + MA_ASSERT(pContext != NULL); - /* The name from PulseAudio is the ID for miniaudio. */ - if (pSourceInfo->name != NULL) { - ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSourceInfo->name, (size_t)-1); - } + propAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - /* The description from PulseAudio is the name for miniaudio. */ - if (pSourceInfo->description != NULL) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSourceInfo->description, (size_t)-1); + dataSize = sizeof(deviceName); + status = pContextStateCoreAudio->AudioObjectGetPropertyData(objectID, &propAddress, 0, NULL, &dataSize, &deviceName); + if (status != noErr) { + return ma_result_from_OSStatus(status); } - if (pSourceInfo->index == pData->defaultDeviceIndexCapture) { - deviceInfo.isDefault = MA_TRUE; + if (!pContextStateCoreAudio->CFStringGetCString(deviceName, bufferOut, bufferSize, kCFStringEncodingUTF8)) { + return MA_ERROR; } - pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_capture, &deviceInfo, pData->pUserData); - - (void)pPulseContext; /* Unused. */ + pContextStateCoreAudio->CFRelease(deviceName); + return MA_SUCCESS; } -static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioObjectID deviceObjectID, AudioObjectPropertyScope scope) { - ma_result result = MA_SUCCESS; - ma_context_enumerate_devices_callback_data__pulse callbackData; - ma_pa_operation* pOP = NULL; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioBufferList* pBufferList; + ma_bool32 isSupported; MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); - - callbackData.pContext = pContext; - callbackData.callback = callback; - callbackData.pUserData = pUserData; - callbackData.isTerminated = MA_FALSE; - callbackData.defaultDeviceIndexPlayback = (ma_uint32)-1; - callbackData.defaultDeviceIndexCapture = (ma_uint32)-1; - - /* We need to get the index of the default devices. */ - ma_context_get_default_device_index__pulse(pContext, ma_device_type_playback, &callbackData.defaultDeviceIndexPlayback); - ma_context_get_default_device_index__pulse(pContext, ma_device_type_capture, &callbackData.defaultDeviceIndexCapture); - - /* Playback. */ - if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)((ma_pa_context*)(pContext->pulse.pPulseContext), ma_context_enumerate_devices_sink_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MA_ERROR; - goto done; - } - result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + /* To know whether or not a device is an input device we need ot look at the stream configuration. If it has an output channel it's a playback device. */ + propAddress.mSelector = kAudioDevicePropertyStreamConfiguration; + propAddress.mScope = scope; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - if (result != MA_SUCCESS) { - goto done; - } + status = pContextStateCoreAudio->AudioObjectGetPropertyDataSize(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return MA_FALSE; } + pBufferList = (AudioBufferList*)ma_malloc(dataSize, &pContext->allocationCallbacks); + if (pBufferList == NULL) { + return MA_FALSE; /* Out of memory. */ + } - /* Capture. */ - if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)((ma_pa_context*)(pContext->pulse.pPulseContext), ma_context_enumerate_devices_source_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MA_ERROR; - goto done; - } - - result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + status = pContextStateCoreAudio->AudioObjectGetPropertyData(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); + if (status != noErr) { + ma_free(pBufferList, &pContext->allocationCallbacks); + return MA_FALSE; + } - if (result != MA_SUCCESS) { - goto done; - } + isSupported = MA_FALSE; + if (pBufferList->mNumberBuffers > 0) { + isSupported = MA_TRUE; } -done: - return result; + ma_free(pBufferList, &pContext->allocationCallbacks); + return isSupported; } - -typedef struct +static ma_bool32 ma_does_AudioObject_support_playback(ma_context* pContext, AudioObjectID deviceObjectID) { - ma_device_info* pDeviceInfo; - ma_uint32 defaultDeviceIndex; - ma_bool32 foundDevice; -} ma_context_get_device_info_callback_data__pulse; + return ma_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeOutput); +} -static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) +static ma_bool32 ma_does_AudioObject_support_capture(ma_context* pContext, AudioObjectID deviceObjectID) { - ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; - - if (endOfList > 0) { - return; - } + return ma_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeInput); +} - MA_ASSERT(pData != NULL); - pData->foundDevice = MA_TRUE; - if (pInfo->name != NULL) { - ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); - } +static ma_result ma_get_AudioObject_stream_descriptions(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, UInt32* pDescriptionCount, AudioStreamRangedDescription** ppDescriptions) /* NOTE: Free the returned pointer with ma_free(). */ +{ + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioStreamRangedDescription* pDescriptions; - if (pInfo->description != NULL) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); - } + MA_ASSERT(pContext != NULL); + MA_ASSERT(pDescriptionCount != NULL); + MA_ASSERT(ppDescriptions != NULL); /* - We're just reporting a single data format here. I think technically PulseAudio might support - all formats, but I don't trust that PulseAudio will do *anything* right, so I'm just going to - report the "native" device format. + TODO: Experiment with kAudioStreamPropertyAvailablePhysicalFormats instead of (or in addition to) kAudioStreamPropertyAvailableVirtualFormats. My + MacBook Pro uses s24/32 format, however, which miniaudio does not currently support. */ - pData->pDeviceInfo->nativeDataFormats[0].format = ma_format_from_pulse(pInfo->sample_spec.format); - pData->pDeviceInfo->nativeDataFormats[0].channels = pInfo->sample_spec.channels; - pData->pDeviceInfo->nativeDataFormats[0].sampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->nativeDataFormats[0].flags = 0; - pData->pDeviceInfo->nativeDataFormatCount = 1; - - if (pData->defaultDeviceIndex == pInfo->index) { - pData->pDeviceInfo->isDefault = MA_TRUE; - } - - (void)pPulseContext; /* Unused. */ -} - -static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) -{ - ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; - - if (endOfList > 0) { - return; - } - - MA_ASSERT(pData != NULL); - pData->foundDevice = MA_TRUE; + propAddress.mSelector = kAudioStreamPropertyAvailableVirtualFormats; /*kAudioStreamPropertyAvailablePhysicalFormats;*/ + propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - if (pInfo->name != NULL) { - ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + status = pContextStateCoreAudio->AudioObjectGetPropertyDataSize(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return ma_result_from_OSStatus(status); } - if (pInfo->description != NULL) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + pDescriptions = (AudioStreamRangedDescription*)ma_malloc(dataSize, &pContext->allocationCallbacks); + if (pDescriptions == NULL) { + return MA_OUT_OF_MEMORY; } - /* - We're just reporting a single data format here. I think technically PulseAudio might support - all formats, but I don't trust that PulseAudio will do *anything* right, so I'm just going to - report the "native" device format. - */ - pData->pDeviceInfo->nativeDataFormats[0].format = ma_format_from_pulse(pInfo->sample_spec.format); - pData->pDeviceInfo->nativeDataFormats[0].channels = pInfo->sample_spec.channels; - pData->pDeviceInfo->nativeDataFormats[0].sampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->nativeDataFormats[0].flags = 0; - pData->pDeviceInfo->nativeDataFormatCount = 1; - - if (pData->defaultDeviceIndex == pInfo->index) { - pData->pDeviceInfo->isDefault = MA_TRUE; + status = pContextStateCoreAudio->AudioObjectGetPropertyData(deviceObjectID, &propAddress, 0, NULL, &dataSize, pDescriptions); + if (status != noErr) { + ma_free(pDescriptions, &pContext->allocationCallbacks); + return ma_result_from_OSStatus(status); } - (void)pPulseContext; /* Unused. */ + *pDescriptionCount = dataSize / sizeof(*pDescriptions); + *ppDescriptions = pDescriptions; + return MA_SUCCESS; } -static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) + +static ma_result ma_get_AudioObject_channel_layout(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, AudioChannelLayout** ppChannelLayout) /* NOTE: Free the returned pointer with ma_free(). */ { - ma_result result = MA_SUCCESS; - ma_context_get_device_info_callback_data__pulse callbackData; - ma_pa_operation* pOP = NULL; - const char* pDeviceName = NULL; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioChannelLayout* pChannelLayout; MA_ASSERT(pContext != NULL); + MA_ASSERT(ppChannelLayout != NULL); - callbackData.pDeviceInfo = pDeviceInfo; - callbackData.foundDevice = MA_FALSE; - - if (pDeviceID != NULL) { - pDeviceName = pDeviceID->pulse; - } else { - pDeviceName = NULL; - } + *ppChannelLayout = NULL; /* Safety. */ - result = ma_context_get_default_device_index__pulse(pContext, deviceType, &callbackData.defaultDeviceIndex); + propAddress.mSelector = kAudioDevicePropertyPreferredChannelLayout; + propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - if (deviceType == ma_device_type_playback) { - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)(pContext->pulse.pPulseContext), pDeviceName, ma_context_get_device_info_sink_callback__pulse, &callbackData); - } else { - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)(pContext->pulse.pPulseContext), pDeviceName, ma_context_get_device_info_source_callback__pulse, &callbackData); + status = pContextStateCoreAudio->AudioObjectGetPropertyDataSize(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return ma_result_from_OSStatus(status); } - if (pOP != NULL) { - ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); - } else { - result = MA_ERROR; - goto done; + pChannelLayout = (AudioChannelLayout*)ma_malloc(dataSize, &pContext->allocationCallbacks); + if (pChannelLayout == NULL) { + return MA_OUT_OF_MEMORY; } - if (!callbackData.foundDevice) { - result = MA_NO_DEVICE; - goto done; + status = pContextStateCoreAudio->AudioObjectGetPropertyData(deviceObjectID, &propAddress, 0, NULL, &dataSize, pChannelLayout); + if (status != noErr) { + ma_free(pChannelLayout, &pContext->allocationCallbacks); + return ma_result_from_OSStatus(status); } -done: - return result; + *ppChannelLayout = pChannelLayout; + return MA_SUCCESS; } -static ma_result ma_device_uninit__pulse(ma_device* pDevice) +static ma_result ma_get_AudioObject_channel_count(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32* pChannelCount) { - ma_context* pContext; - - MA_ASSERT(pDevice != NULL); + AudioChannelLayout* pChannelLayout; + ma_result result; - pContext = pDevice->pContext; MA_ASSERT(pContext != NULL); + MA_ASSERT(pChannelCount != NULL); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - } + *pChannelCount = 0; /* Safety. */ - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + result = ma_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); + if (result != MA_SUCCESS) { + return result; } - if (pDevice->type == ma_device_type_duplex) { - ma_duplex_rb_uninit(&pDevice->duplexRB); + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { + *pChannelCount = pChannelLayout->mNumberChannelDescriptions; + } else if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { + *pChannelCount = ma_count_set_bits(pChannelLayout->mChannelBitmap); + } else { + *pChannelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); } - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); - + ma_free(pChannelLayout, &pContext->allocationCallbacks); return MA_SUCCESS; } -static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFrames, ma_uint32 periods, const ma_pa_sample_spec* ss) +#if 0 +static ma_result ma_get_AudioObject_channel_map(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap) { - ma_pa_buffer_attr attr; - attr.maxlength = periodSizeInFrames * periods * ma_get_bytes_per_frame(ma_format_from_pulse(ss->format), ss->channels); - attr.tlength = attr.maxlength / periods; - attr.prebuf = (ma_uint32)-1; - attr.minreq = (ma_uint32)-1; - attr.fragsize = attr.maxlength / periods; - - return attr; -} + AudioChannelLayout* pChannelLayout; + ma_result result; -static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) -{ - static ma_atomic_uint32 g_StreamCounter = { 0 }; - char actualStreamName[256]; + MA_ASSERT(pContext != NULL); - if (pStreamName != NULL) { - ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); - } else { - const char* pBaseName = "miniaudio:"; - size_t baseNameLen = strlen(pBaseName); - ma_strcpy_s(actualStreamName, sizeof(actualStreamName), pBaseName); - ma_itoa_s((int)ma_atomic_uint32_get(&g_StreamCounter), actualStreamName + baseNameLen, sizeof(actualStreamName)-baseNameLen, 10); + result = ma_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); + if (result != MA_SUCCESS) { + return result; /* Rather than always failing here, would it be more robust to simply assume a default? */ } - ma_atomic_uint32_fetch_add(&g_StreamCounter, 1); - - return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); -} - -static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, void* pUserData) -{ - ma_device* pDevice = (ma_device*)pUserData; - ma_uint32 bpf; - ma_uint32 deviceState; - ma_uint64 frameCount; - ma_uint64 framesProcessed; - - MA_ASSERT(pDevice != NULL); - - /* - Don't do anything if the device isn't initialized yet. Yes, this can happen because PulseAudio - can fire this callback before the stream has even started. Ridiculous. - */ - deviceState = ma_device_get_state(pDevice); - if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { - return; + result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); + if (result != MA_SUCCESS) { + ma_free(pChannelLayout, &pContext->allocationCallbacks); + return result; } - bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - MA_ASSERT(bpf > 0); - - frameCount = byteCount / bpf; - framesProcessed = 0; - - while (ma_device_get_state(pDevice) == ma_device_state_started && framesProcessed < frameCount) { - const void* pMappedPCMFrames; - size_t bytesMapped; - ma_uint64 framesMapped; - - int pulseResult = ((ma_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)(pStream, &pMappedPCMFrames, &bytesMapped); - if (pulseResult < 0) { - break; /* Failed to map. Abort. */ - } - - framesMapped = bytesMapped / bpf; - if (framesMapped > 0) { - if (pMappedPCMFrames != NULL) { - ma_device_handle_backend_data_callback(pDevice, NULL, pMappedPCMFrames, framesMapped); - } else { - /* It's a hole. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] ma_device_on_read__pulse: Hole.\n"); - } - - pulseResult = ((ma_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)(pStream); - if (pulseResult < 0) { - break; /* Failed to drop the buffer. */ - } - - framesProcessed += framesMapped; - - } else { - /* Nothing was mapped. Just abort. */ - break; - } - } + ma_free(pChannelLayout, &pContext->allocationCallbacks); + return result; } +#endif -static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stream* pStream, ma_uint64* pFramesProcessed) +static ma_result ma_get_AudioObject_sample_rates(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, UInt32* pSampleRateRangesCount, AudioValueRange** ppSampleRateRanges) /* NOTE: Free the returned pointer with ma_free(). */ { - ma_result result = MA_SUCCESS; - ma_uint64 framesProcessed = 0; - size_t bytesMapped; - ma_uint32 bpf; - ma_uint32 deviceState; - - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pStream != NULL); - - bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - MA_ASSERT(bpf > 0); - - deviceState = ma_device_get_state(pDevice); + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; + AudioValueRange* pSampleRateRanges; - bytesMapped = ((ma_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)(pStream); - if (bytesMapped != (size_t)-1) { - if (bytesMapped > 0) { - ma_uint64 framesMapped; - void* pMappedPCMFrames; - int pulseResult = ((ma_pa_stream_begin_write_proc)pDevice->pContext->pulse.pa_stream_begin_write)(pStream, &pMappedPCMFrames, &bytesMapped); - if (pulseResult < 0) { - result = ma_result_from_pulse(pulseResult); - goto done; - } + MA_ASSERT(pContext != NULL); + MA_ASSERT(pSampleRateRangesCount != NULL); + MA_ASSERT(ppSampleRateRanges != NULL); - framesMapped = bytesMapped / bpf; + /* Safety. */ + *pSampleRateRangesCount = 0; + *ppSampleRateRanges = NULL; - if (deviceState == ma_device_state_started || deviceState == ma_device_state_starting) { /* Check for starting state just in case this is being used to do the initial fill. */ - ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped); - } else { - /* Device is not started. Write silence. */ - ma_silence_pcm_frames(pMappedPCMFrames, framesMapped, pDevice->playback.format, pDevice->playback.channels); - } + propAddress.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; + propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - pulseResult = ((ma_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)(pStream, pMappedPCMFrames, bytesMapped, NULL, 0, MA_PA_SEEK_RELATIVE); - if (pulseResult < 0) { - result = ma_result_from_pulse(pulseResult); - goto done; /* Failed to write data to stream. */ - } + status = pContextStateCoreAudio->AudioObjectGetPropertyDataSize(deviceObjectID, &propAddress, 0, NULL, &dataSize); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } - framesProcessed += framesMapped; - } else { - result = MA_SUCCESS; /* No data available for writing. */ - goto done; - } - } else { - result = MA_ERROR; /* Failed to retrieve the writable size. Abort. */ - goto done; + pSampleRateRanges = (AudioValueRange*)ma_malloc(dataSize, &pContext->allocationCallbacks); + if (pSampleRateRanges == NULL) { + return MA_OUT_OF_MEMORY; } -done: - if (pFramesProcessed != NULL) { - *pFramesProcessed = framesProcessed; + status = pContextStateCoreAudio->AudioObjectGetPropertyData(deviceObjectID, &propAddress, 0, NULL, &dataSize, pSampleRateRanges); + if (status != noErr) { + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + return ma_result_from_OSStatus(status); } - return result; + *pSampleRateRangesCount = dataSize / sizeof(*pSampleRateRanges); + *ppSampleRateRanges = pSampleRateRanges; + return MA_SUCCESS; } -static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, void* pUserData) +#if 0 +static ma_result ma_get_AudioObject_get_closest_sample_rate(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32 sampleRateIn, ma_uint32* pSampleRateOut) { - ma_device* pDevice = (ma_device*)pUserData; - ma_uint32 bpf; - ma_uint64 frameCount; - ma_uint64 framesProcessed; - ma_uint32 deviceState; + UInt32 sampleRateRangeCount; + AudioValueRange* pSampleRateRanges; ma_result result; - MA_ASSERT(pDevice != NULL); + MA_ASSERT(pContext != NULL); + MA_ASSERT(pSampleRateOut != NULL); - /* - Don't do anything if the device isn't initialized yet. Yes, this can happen because PulseAudio - can fire this callback before the stream has even started. Ridiculous. - */ - deviceState = ma_device_get_state(pDevice); - if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { - return; + *pSampleRateOut = 0; /* Safety. */ + + result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); + if (result != MA_SUCCESS) { + return result; } - bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - MA_ASSERT(bpf > 0); + if (sampleRateRangeCount == 0) { + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + return MA_ERROR; /* Should never hit this case should we? */ + } - frameCount = byteCount / bpf; - framesProcessed = 0; + if (sampleRateIn == 0) { + /* Search in order of miniaudio's preferred priority. */ + UInt32 iMALSampleRate; + for (iMALSampleRate = 0; iMALSampleRate < ma_countof(ma_standard_sample_rates); ++iMALSampleRate) { + ma_uint32 malSampleRate = ma_standard_sample_rates[iMALSampleRate]; + UInt32 iCASampleRate; + for (iCASampleRate = 0; iCASampleRate < sampleRateRangeCount; ++iCASampleRate) { + AudioValueRange caSampleRate = pSampleRateRanges[iCASampleRate]; + if (caSampleRate.mMinimum <= malSampleRate && caSampleRate.mMaximum >= malSampleRate) { + *pSampleRateOut = malSampleRate; + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + return MA_SUCCESS; + } + } + } + + /* + If we get here it means none of miniaudio's standard sample rates matched any of the supported sample rates from the device. In this + case we just fall back to the first one reported by Core Audio. + */ + MA_ASSERT(sampleRateRangeCount > 0); - while (framesProcessed < frameCount) { - ma_uint64 framesProcessedThisIteration; + *pSampleRateOut = pSampleRateRanges[0].mMinimum; + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + return MA_SUCCESS; + } else { + /* Find the closest match to this sample rate. */ + UInt32 currentAbsoluteDifference = INT32_MAX; + UInt32 iCurrentClosestRange = (UInt32)-1; + UInt32 iRange; + for (iRange = 0; iRange < sampleRateRangeCount; ++iRange) { + if (pSampleRateRanges[iRange].mMinimum <= sampleRateIn && pSampleRateRanges[iRange].mMaximum >= sampleRateIn) { + *pSampleRateOut = sampleRateIn; + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + return MA_SUCCESS; + } else { + UInt32 absoluteDifference; + if (pSampleRateRanges[iRange].mMinimum > sampleRateIn) { + absoluteDifference = pSampleRateRanges[iRange].mMinimum - sampleRateIn; + } else { + absoluteDifference = sampleRateIn - pSampleRateRanges[iRange].mMaximum; + } - /* Don't keep trying to process frames if the device isn't started. */ - deviceState = ma_device_get_state(pDevice); - if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { - break; + if (currentAbsoluteDifference > absoluteDifference) { + currentAbsoluteDifference = absoluteDifference; + iCurrentClosestRange = iRange; + } + } } - result = ma_device_write_to_stream__pulse(pDevice, pStream, &framesProcessedThisIteration); - if (result != MA_SUCCESS) { - break; - } + MA_ASSERT(iCurrentClosestRange != (UInt32)-1); - framesProcessed += framesProcessedThisIteration; + *pSampleRateOut = pSampleRateRanges[iCurrentClosestRange].mMinimum; + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + return MA_SUCCESS; } + + /* Should never get here, but it would mean we weren't able to find any suitable sample rates. */ + /*ma_free(pSampleRateRanges, &pContext->allocationCallbacks);*/ + /*return MA_ERROR;*/ } +#endif -static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData) +static ma_result ma_get_AudioObject_closest_buffer_size_in_frames(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32 bufferSizeInFramesIn, ma_uint32* pBufferSizeInFramesOut) { - ma_device* pDevice = (ma_device*)pUserData; - int suspended; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddress; + AudioValueRange bufferSizeRange; + UInt32 dataSize; + OSStatus status; - (void)pStream; + MA_ASSERT(pContext != NULL); + MA_ASSERT(pBufferSizeInFramesOut != NULL); - suspended = ((ma_pa_stream_is_suspended_proc)pDevice->pContext->pulse.pa_stream_is_suspended)(pStream); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. pa_stream_is_suspended() returned %d.\n", suspended); + *pBufferSizeInFramesOut = 0; /* Safety. */ - if (suspended < 0) { - return; + propAddress.mSelector = kAudioDevicePropertyBufferFrameSizeRange; + propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + + dataSize = sizeof(bufferSizeRange); + status = pContextStateCoreAudio->AudioObjectGetPropertyData(deviceObjectID, &propAddress, 0, NULL, &dataSize, &bufferSizeRange); + if (status != noErr) { + return ma_result_from_OSStatus(status); } - if (suspended == 1) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Suspended.\n"); - ma_device__on_notification_stopped(pDevice); + /* This is just a clamp. */ + if (bufferSizeInFramesIn < bufferSizeRange.mMinimum) { + *pBufferSizeInFramesOut = (ma_uint32)bufferSizeRange.mMinimum; + } else if (bufferSizeInFramesIn > bufferSizeRange.mMaximum) { + *pBufferSizeInFramesOut = (ma_uint32)bufferSizeRange.mMaximum; } else { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Resumed.\n"); - ma_device__on_notification_started(pDevice); + *pBufferSizeInFramesOut = bufferSizeInFramesIn; } + + return MA_SUCCESS; } -static void ma_device_on_rerouted__pulse(ma_pa_stream* pStream, void* pUserData) +static ma_result ma_set_AudioObject_buffer_size_in_frames(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32* pPeriodSizeInOut) { - ma_device* pDevice = (ma_device*)pUserData; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + ma_result result; + ma_uint32 chosenBufferSizeInFrames; + AudioObjectPropertyAddress propAddress; + UInt32 dataSize; + OSStatus status; - (void)pStream; - (void)pUserData; + MA_ASSERT(pContext != NULL); - ma_device__on_notification_rerouted(pDevice); -} + result = ma_get_AudioObject_closest_buffer_size_in_frames(pContext, deviceObjectID, deviceType, *pPeriodSizeInOut, &chosenBufferSizeInFrames); + if (result != MA_SUCCESS) { + return result; + } -static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__pulse(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) -{ - /* - There have been reports from users where buffers of < ~20ms result glitches when running through - PipeWire. To work around this we're going to have to use a different default buffer size. - */ - const ma_uint32 defaultPeriodSizeInMilliseconds_LowLatency = 25; - const ma_uint32 defaultPeriodSizeInMilliseconds_Conservative = MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE; + /* Try setting the size of the buffer... If this fails we just use whatever is currently set. */ + propAddress.mSelector = kAudioDevicePropertyBufferFrameSize; + propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - MA_ASSERT(nativeSampleRate != 0); + pContextStateCoreAudio->AudioObjectSetPropertyData(deviceObjectID, &propAddress, 0, NULL, sizeof(chosenBufferSizeInFrames), &chosenBufferSizeInFrames); - if (pDescriptor->periodSizeInFrames == 0) { - if (pDescriptor->periodSizeInMilliseconds == 0) { - if (performanceProfile == ma_performance_profile_low_latency) { - return ma_calculate_buffer_size_in_frames_from_milliseconds(defaultPeriodSizeInMilliseconds_LowLatency, nativeSampleRate); - } else { - return ma_calculate_buffer_size_in_frames_from_milliseconds(defaultPeriodSizeInMilliseconds_Conservative, nativeSampleRate); - } - } else { - return ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); - } - } else { - return pDescriptor->periodSizeInFrames; + /* Get the actual size of the buffer. */ + dataSize = sizeof(*pPeriodSizeInOut); + status = pContextStateCoreAudio->AudioObjectGetPropertyData(deviceObjectID, &propAddress, 0, NULL, &dataSize, &chosenBufferSizeInFrames); + if (status != noErr) { + return ma_result_from_OSStatus(status); } + + *pPeriodSizeInOut = chosenBufferSizeInFrames; + return MA_SUCCESS; } -static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static ma_result ma_find_default_AudioObjectID(ma_context* pContext, ma_device_type deviceType, AudioObjectID* pDeviceObjectID) { - /* - Notes for PulseAudio: - - - When both the period size in frames and milliseconds are 0, we default to miniaudio's - default buffer sizes rather than leaving it up to PulseAudio because I don't trust - PulseAudio to give us any kind of reasonable latency by default. - - - Do not ever, *ever* forget to use MA_PA_STREAM_ADJUST_LATENCY. If you don't specify this - flag, capture mode will just not work properly until you open another PulseAudio app. - */ - - ma_result result = MA_SUCCESS; - int error = 0; - const char* devPlayback = NULL; - const char* devCapture = NULL; - ma_format format = ma_format_unknown; - ma_uint32 channels = 0; - ma_uint32 sampleRate = 0; - ma_pa_sink_info sinkInfo; - ma_pa_source_info sourceInfo; - ma_pa_sample_spec ss; - ma_pa_channel_map cmap; - ma_pa_buffer_attr attr; - const ma_pa_sample_spec* pActualSS = NULL; - const ma_pa_buffer_attr* pActualAttr = NULL; - const ma_pa_channel_map* pActualChannelMap = NULL; - ma_uint32 iChannel; - int streamFlags; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioObjectPropertyAddress propAddressDefaultDevice; + UInt32 defaultDeviceObjectIDSize = sizeof(AudioObjectID); + AudioObjectID defaultDeviceObjectID; + OSStatus status; - MA_ASSERT(pDevice != NULL); - MA_ZERO_OBJECT(&pDevice->pulse); + MA_ASSERT(pContext != NULL); + MA_ASSERT(pDeviceObjectID != NULL); - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + /* Safety. */ + *pDeviceObjectID = 0; - /* No exclusive mode with the PulseAudio backend. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive)) { - return MA_SHARE_MODE_NOT_SUPPORTED; + propAddressDefaultDevice.mScope = kAudioObjectPropertyScopeGlobal; + propAddressDefaultDevice.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + if (deviceType == ma_device_type_playback) { + propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + } else { + propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultInputDevice; } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - if (pDescriptorPlayback->pDeviceID != NULL) { - devPlayback = pDescriptorPlayback->pDeviceID->pulse; - } - - format = pDescriptorPlayback->format; - channels = pDescriptorPlayback->channels; - sampleRate = pDescriptorPlayback->sampleRate; + defaultDeviceObjectIDSize = sizeof(AudioObjectID); + status = pContextStateCoreAudio->AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAddressDefaultDevice, 0, NULL, &defaultDeviceObjectIDSize, &defaultDeviceObjectID); + if (status == noErr) { + *pDeviceObjectID = defaultDeviceObjectID; + return MA_SUCCESS; } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - if (pDescriptorCapture->pDeviceID != NULL) { - devCapture = pDescriptorCapture->pDeviceID->pulse; - } - - format = pDescriptorCapture->format; - channels = pDescriptorCapture->channels; - sampleRate = pDescriptorCapture->sampleRate; - } + /* If we get here it means we couldn't find the device. */ + return MA_NO_DEVICE; +} +static ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, AudioObjectID* pDeviceObjectID) +{ + MA_ASSERT(pContext != NULL); + MA_ASSERT(pDeviceObjectID != NULL); + /* Safety. */ + *pDeviceObjectID = 0; - result = ma_init_pa_mainloop_and_pa_context__pulse(pDevice->pContext, pDevice->pContext->pulse.pApplicationName, pDevice->pContext->pulse.pServerName, MA_FALSE, &pDevice->pulse.pMainLoop, &pDevice->pulse.pPulseContext); - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize PA mainloop and context for device.\n"); - return result; - } + if (pDeviceID == NULL) { + /* Default device. */ + return ma_find_default_AudioObjectID(pContext, deviceType, pDeviceObjectID); + } else { + /* Explicit device. */ + UInt32 deviceCount; + AudioObjectID* pDeviceObjectIDs; + ma_result result; + UInt32 iDevice; - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - result = ma_context_get_source_info__pulse(pDevice->pContext, devCapture, &sourceInfo); + result = ma_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device."); - goto on_error0; - } - - ss = sourceInfo.sample_spec; - cmap = sourceInfo.channel_map; - - /* Use the requested channel count if we have one. */ - if (pDescriptorCapture->channels != 0) { - ss.channels = pDescriptorCapture->channels; - } - - /* PulseAudio has a maximum channel count of 32. We'll get a crash if this is exceeded. */ - if (ss.channels > 32) { - ss.channels = 32; + return result; } - /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, (ma_pa_channel_map_def_t)pConfig->pulse.channelMap); + for (iDevice = 0; iDevice < deviceCount; ++iDevice) { + AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; - /* Use the requested sample rate if one was specified. */ - if (pDescriptorCapture->sampleRate != 0) { - ss.rate = pDescriptorCapture->sampleRate; - } - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY; + char uid[256]; + if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(uid), uid) != MA_SUCCESS) { + continue; + } - if (ma_format_from_pulse(ss.format) == ma_format_unknown) { - if (ma_is_little_endian()) { - ss.format = MA_PA_SAMPLE_FLOAT32LE; + if (deviceType == ma_device_type_playback) { + if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) { + if (strcmp(uid, pDeviceID->coreaudio) == 0) { + *pDeviceObjectID = deviceObjectID; + ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); + return MA_SUCCESS; + } + } } else { - ss.format = MA_PA_SAMPLE_FLOAT32BE; + if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) { + if (strcmp(uid, pDeviceID->coreaudio) == 0) { + *pDeviceObjectID = deviceObjectID; + ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); + return MA_SUCCESS; + } + } } - streamFlags |= MA_PA_STREAM_FIX_FORMAT; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); - } - if (ss.rate == 0) { - ss.rate = MA_DEFAULT_SAMPLE_RATE; - streamFlags |= MA_PA_STREAM_FIX_RATE; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); - } - if (ss.channels == 0) { - ss.channels = MA_DEFAULT_CHANNELS; - streamFlags |= MA_PA_STREAM_FIX_CHANNELS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); } - /* We now have enough information to calculate our actual period size in frames. */ - pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulse(pDescriptorCapture, ss.rate, pConfig->performanceProfile); - - attr = ma_device__pa_buffer_attr_new(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->periodCount, &ss); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); + ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); + } - pDevice->pulse.pStreamCapture = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNameCapture, &ss, &cmap); - if (pDevice->pulse.pStreamCapture == NULL) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.\n"); - result = MA_ERROR; - goto on_error0; - } + /* If we get here it means we couldn't find the device. */ + return MA_NO_DEVICE; +} - /* The callback needs to be set before connecting the stream. */ - ((ma_pa_stream_set_read_callback_proc)pDevice->pContext->pulse.pa_stream_set_read_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_read__pulse, pDevice); +static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const AudioStreamBasicDescription* pOrigFormat, AudioStreamBasicDescription* pFormat) +{ + UInt32 deviceFormatDescriptionCount; + AudioStreamRangedDescription* pDeviceFormatDescriptions; + ma_result result; + ma_uint32 desiredSampleRate; + ma_uint32 desiredChannelCount; + ma_format desiredFormat; + AudioStreamBasicDescription bestDeviceFormatSoFar; + ma_bool32 hasSupportedFormat; + UInt32 iFormat; - /* State callback for checking when the device has been corked. */ - ((ma_pa_stream_set_suspended_callback_proc)pDevice->pContext->pulse.pa_stream_set_suspended_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_suspended__pulse, pDevice); + result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &deviceFormatDescriptionCount, &pDeviceFormatDescriptions); + if (result != MA_SUCCESS) { + return result; + } - /* Rerouting notification. */ - ((ma_pa_stream_set_moved_callback_proc)pDevice->pContext->pulse.pa_stream_set_moved_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_rerouted__pulse, pDevice); + desiredSampleRate = sampleRate; + if (desiredSampleRate == 0) { + desiredSampleRate = (ma_uint32)pOrigFormat->mSampleRate; + } + desiredChannelCount = channels; + if (desiredChannelCount == 0) { + desiredChannelCount = pOrigFormat->mChannelsPerFrame; + } - /* Connect after we've got all of our internal state set up. */ - if (devCapture != NULL) { - streamFlags |= MA_PA_STREAM_DONT_MOVE; + desiredFormat = format; + if (desiredFormat == ma_format_unknown) { + result = ma_format_from_AudioStreamBasicDescription(pOrigFormat, &desiredFormat); + if (result != MA_SUCCESS || desiredFormat == ma_format_unknown) { + desiredFormat = g_maFormatPriorities[0]; } + } - error = ((ma_pa_stream_connect_record_proc)pDevice->pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, (ma_pa_stream_flags_t)streamFlags); - if (error != MA_PA_OK) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream."); - result = ma_result_from_pulse(error); - goto on_error1; - } + /* + If we get here it means we don't have an exact match to what the client is asking for. We'll need to find the closest one. The next + loop will check for formats that have the same sample rate to what we're asking for. If there is, we prefer that one in all cases. + */ + MA_ZERO_OBJECT(&bestDeviceFormatSoFar); - result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (result != MA_SUCCESS) { - goto on_error2; + hasSupportedFormat = MA_FALSE; + for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { + ma_format formatFromDescription; + ma_result formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &formatFromDescription); + if (formatResult == MA_SUCCESS && formatFromDescription != ma_format_unknown) { + hasSupportedFormat = MA_TRUE; + bestDeviceFormatSoFar = pDeviceFormatDescriptions[iFormat].mFormat; + break; } + } + if (!hasSupportedFormat) { + ma_free(pDeviceFormatDescriptions, &pContext->allocationCallbacks); + return MA_FORMAT_NOT_SUPPORTED; + } - /* Internal format. */ - pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (pActualSS != NULL) { - ss = *pActualSS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture sample spec: format=%s, channels=%d, rate=%d\n", ma_get_format_name(ma_format_from_pulse(ss.format)), ss.channels, ss.rate); - } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve capture sample spec.\n"); - } - pDescriptorCapture->format = ma_format_from_pulse(ss.format); - pDescriptorCapture->channels = ss.channels; - pDescriptorCapture->sampleRate = ss.rate; + for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { + AudioStreamBasicDescription thisDeviceFormat = pDeviceFormatDescriptions[iFormat].mFormat; + ma_format thisSampleFormat; + ma_result formatResult; + ma_format bestSampleFormatSoFar; - if (pDescriptorCapture->format == ma_format_unknown || pDescriptorCapture->channels == 0 || pDescriptorCapture->sampleRate == 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Capture sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.\n", ma_get_format_name(pDescriptorCapture->format), pDescriptorCapture->channels, pDescriptorCapture->sampleRate); - result = MA_ERROR; - goto on_error4; + /* If the format is not supported by miniaudio we need to skip this one entirely. */ + formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &thisSampleFormat); + if (formatResult != MA_SUCCESS || thisSampleFormat == ma_format_unknown) { + continue; /* The format is not supported by miniaudio. Skip. */ } + ma_format_from_AudioStreamBasicDescription(&bestDeviceFormatSoFar, &bestSampleFormatSoFar); - /* Internal channel map. */ - pActualChannelMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (pActualChannelMap == NULL) { - pActualChannelMap = &cmap; /* Fallback just in case. */ - } - - /* - Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting - the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono - and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For - all other channel counts we need to just put up with whatever PipeWire reports and hope it gets - fixed sooner than later. I might remove this hack later. - */ - if (pDescriptorCapture->channels > 2) { - for (iChannel = 0; iChannel < pDescriptorCapture->channels; iChannel += 1) { - pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(pActualChannelMap->map[iChannel]); + /* Getting here means the format is supported by miniaudio which makes this format a candidate. */ + if (thisDeviceFormat.mSampleRate != desiredSampleRate) { + /* + The sample rate does not match, but this format could still be usable, although it's a very low priority. If the best format + so far has an equal sample rate we can just ignore this one. + */ + if (bestDeviceFormatSoFar.mSampleRate == desiredSampleRate) { + continue; /* The best sample rate so far has the same sample rate as what we requested which means it's still the best so far. Skip this format. */ + } else { + /* In this case, neither the best format so far nor this one have the same sample rate. Check the channel count next. */ + if (thisDeviceFormat.mChannelsPerFrame != desiredChannelCount) { + /* This format has a different sample rate _and_ a different channel count. */ + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + continue; /* No change to the best format. */ + } else { + /* + Both this format and the best so far have different sample rates and different channel counts. Whichever has the + best format is the new best. + */ + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format. */ + } + } + } else { + /* This format has a different sample rate but the desired channel count. */ + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + /* Both this format and the best so far have the desired channel count. Whichever has the best format is the new best. */ + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format for now. */ + } + } else { + /* This format has the desired channel count, but the best so far does not. We have a new best. */ + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } + } } } else { - /* Hack for mono and stereo. */ - if (pDescriptorCapture->channels == 1) { - pDescriptorCapture->channelMap[0] = MA_CHANNEL_MONO; - } else if (pDescriptorCapture->channels == 2) { - pDescriptorCapture->channelMap[0] = MA_CHANNEL_FRONT_LEFT; - pDescriptorCapture->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + /* + The sample rates match which makes this format a very high priority contender. If the best format so far has a different + sample rate it needs to be replaced with this one. + */ + if (bestDeviceFormatSoFar.mSampleRate != desiredSampleRate) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; } else { - MA_ASSERT(MA_FALSE); /* Should never hit this. */ + /* In this case both this format and the best format so far have the same sample rate. Check the channel count next. */ + if (thisDeviceFormat.mChannelsPerFrame == desiredChannelCount) { + /* + In this case this format has the same channel count as what the client is requesting. If the best format so far has + a different count, this one becomes the new best. + */ + if (bestDeviceFormatSoFar.mChannelsPerFrame != desiredChannelCount) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + /* In this case both this format and the best so far have the ideal sample rate and channel count. Check the format. */ + if (thisSampleFormat == desiredFormat) { + bestDeviceFormatSoFar = thisDeviceFormat; + break; /* Found the exact match. */ + } else { + /* The formats are different. The new best format is the one with the highest priority format according to miniaudio. */ + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format for now. */ + } + } + } + } else { + /* + In this case the channel count is different to what the client has requested. If the best so far has the same channel + count as the requested count then it remains the best. + */ + if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { + continue; + } else { + /* + This is the case where both have the same sample rate (good) but different channel counts. Right now both have about + the same priority, but we need to compare the format now. + */ + if (thisSampleFormat == bestSampleFormatSoFar) { + if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { + bestDeviceFormatSoFar = thisDeviceFormat; + continue; + } else { + continue; /* No change to the best format for now. */ + } + } + } + } } } + } + *pFormat = bestDeviceFormatSoFar; - /* Buffer. */ - pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pDevice->pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (pActualAttr != NULL) { - attr = *pActualAttr; - } + ma_free(pDeviceFormatDescriptions, &pContext->allocationCallbacks); + return MA_SUCCESS; +} - if (attr.fragsize > 0) { - pDescriptorCapture->periodCount = ma_max(attr.maxlength / attr.fragsize, 1); - } else { - pDescriptorCapture->periodCount = 1; - } +static ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit audioUnit, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + AudioUnitScope deviceScope; + AudioUnitElement deviceBus; + UInt32 channelLayoutSize; + OSStatus status; + AudioChannelLayout* pChannelLayout; + ma_result result; - pDescriptorCapture->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / pDescriptorCapture->periodCount; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); + MA_ASSERT(pContext != NULL); + + if (deviceType == ma_device_type_playback) { + deviceScope = kAudioUnitScope_Input; + deviceBus = MA_COREAUDIO_OUTPUT_BUS; + } else { + deviceScope = kAudioUnitScope_Output; + deviceBus = MA_COREAUDIO_INPUT_BUS; } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_context_get_sink_info__pulse(pDevice->pContext, devPlayback, &sinkInfo); - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.\n"); - goto on_error2; - } + status = pContextStateCoreAudio->AudioUnitGetPropertyInfo(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, &channelLayoutSize, NULL); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } - ss = sinkInfo.sample_spec; - cmap = sinkInfo.channel_map; + pChannelLayout = (AudioChannelLayout*)ma_malloc(channelLayoutSize, &pContext->allocationCallbacks); + if (pChannelLayout == NULL) { + return MA_OUT_OF_MEMORY; + } - /* Use the requested channel count if we have one. */ - if (pDescriptorPlayback->channels != 0) { - ss.channels = pDescriptorPlayback->channels; - } + status = pContextStateCoreAudio->AudioUnitGetProperty(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, pChannelLayout, &channelLayoutSize); + if (status != noErr) { + ma_free(pChannelLayout, &pContext->allocationCallbacks); + return ma_result_from_OSStatus(status); + } + + result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); + if (result != MA_SUCCESS) { + ma_free(pChannelLayout, &pContext->allocationCallbacks); + return result; + } - /* PulseAudio has a maximum channel count of 32. We'll get a crash if this is exceeded. */ - if (ss.channels > 32) { - ss.channels = 32; - } + ma_free(pChannelLayout, &pContext->allocationCallbacks); + return MA_SUCCESS; +} +#endif /* MA_APPLE_DESKTOP */ - /* Use a default channel map. */ - ((ma_pa_channel_map_init_extend_proc)pDevice->pContext->pulse.pa_channel_map_init_extend)(&cmap, ss.channels, (ma_pa_channel_map_def_t)pConfig->pulse.channelMap); +#if defined(MA_APPLE_DESKTOP) +static ma_spinlock g_DeviceTrackingInitLock_CoreAudio = 0; /* A spinlock for mutal exclusion of the init/uninit of the global tracking data. Initialization to 0 is what we need. */ +static ma_uint32 g_DeviceTrackingInitCounter_CoreAudio = 0; +static ma_mutex g_DeviceTrackingMutex_CoreAudio; +static ma_device** g_ppTrackedDevices_CoreAudio = NULL; +static ma_uint32 g_TrackedDeviceCap_CoreAudio = 0; +static ma_uint32 g_TrackedDeviceCount_CoreAudio = 0; - /* Use the requested sample rate if one was specified. */ - if (pDescriptorPlayback->sampleRate != 0) { - ss.rate = pDescriptorPlayback->sampleRate; - } +static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UInt32 addressCount, const AudioObjectPropertyAddress* pAddresses, void* pUserData) +{ + ma_device_type deviceType; - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY; - if (ma_format_from_pulse(ss.format) == ma_format_unknown) { - if (ma_is_little_endian()) { - ss.format = MA_PA_SAMPLE_FLOAT32LE; - } else { - ss.format = MA_PA_SAMPLE_FLOAT32BE; - } - streamFlags |= MA_PA_STREAM_FIX_FORMAT; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); - } - if (ss.rate == 0) { - ss.rate = MA_DEFAULT_SAMPLE_RATE; - streamFlags |= MA_PA_STREAM_FIX_RATE; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); - } - if (ss.channels == 0) { - ss.channels = MA_DEFAULT_CHANNELS; - streamFlags |= MA_PA_STREAM_FIX_CHANNELS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); - } + /* Not sure if I really need to check this, but it makes me feel better. */ + if (addressCount == 0) { + return noErr; + } - /* We now have enough information to calculate the actual buffer size in frames. */ - pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulse(pDescriptorPlayback, ss.rate, pConfig->performanceProfile); + if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultOutputDevice) { + deviceType = ma_device_type_playback; + } else if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultInputDevice) { + deviceType = ma_device_type_capture; + } else { + return noErr; /* Should never hit this. */ + } - attr = ma_device__pa_buffer_attr_new(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->periodCount, &ss); + ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); + { + ma_uint32 iDevice; + for (iDevice = 0; iDevice < g_TrackedDeviceCount_CoreAudio; iDevice += 1) { + ma_device* pDevice; + ma_device_state_coreaudio* pDeviceStateCoreAudio; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); + pDevice = g_ppTrackedDevices_CoreAudio[iDevice]; + pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); - pDevice->pulse.pStreamPlayback = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); - if (pDevice->pulse.pStreamPlayback == NULL) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.\n"); - result = MA_ERROR; - goto on_error2; + if (ma_device_get_type(pDevice) == deviceType || ma_device_get_type(pDevice) == ma_device_type_duplex) { + if (deviceType == ma_device_type_playback) { + ma_atomic_bool32_set(&pDeviceStateCoreAudio->isSwitchingPlaybackDevice, MA_TRUE); + } else { + ma_atomic_bool32_set(&pDeviceStateCoreAudio->isSwitchingCaptureDevice, MA_TRUE); + } + + /* This ensures our step function gets unblocked and executes the reroute. */ + ma_device_state_async_release(&pDeviceStateCoreAudio->async); + } } + } + ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); + /* Unused parameters. */ + (void)objectID; + (void)pUserData; - /* - Note that this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a - device state of ma_device_state_uninitialized. - */ - ((ma_pa_stream_set_write_callback_proc)pDevice->pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); + return noErr; +} - /* State callback for checking when the device has been corked. */ - ((ma_pa_stream_set_suspended_callback_proc)pDevice->pContext->pulse.pa_stream_set_suspended_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_suspended__pulse, pDevice); +static ma_result ma_context__init_device_tracking__coreaudio(ma_context* pContext, ma_context_state_coreaudio* pContextStateCoreAudio) +{ + (void)pContext; - /* Rerouting notification. */ - ((ma_pa_stream_set_moved_callback_proc)pDevice->pContext->pulse.pa_stream_set_moved_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_rerouted__pulse, pDevice); + ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); + { + /* Don't do anything if we've already initialized device tracking. */ + if (g_DeviceTrackingInitCounter_CoreAudio == 0) { + AudioObjectPropertyAddress propAddress; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + ma_mutex_init(&g_DeviceTrackingMutex_CoreAudio); - /* Connect after we've got all of our internal state set up. */ - if (devPlayback != NULL) { - streamFlags |= MA_PA_STREAM_DONT_MOVE; - } + propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + pContextStateCoreAudio->AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - error = ((ma_pa_stream_connect_playback_proc)pDevice->pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, (ma_pa_stream_flags_t)streamFlags, NULL, NULL); - if (error != MA_PA_OK) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream."); - result = ma_result_from_pulse(error); - goto on_error3; + propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + pContextStateCoreAudio->AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); } + g_DeviceTrackingInitCounter_CoreAudio += 1; + } + ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); - result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (result != MA_SUCCESS) { - goto on_error3; + return MA_SUCCESS; +} + +static ma_result ma_context__uninit_device_tracking__coreaudio(ma_context* pContext, ma_context_state_coreaudio* pContextStateCoreAudio) +{ + ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); + { + if (g_DeviceTrackingInitCounter_CoreAudio > 0) { + g_DeviceTrackingInitCounter_CoreAudio -= 1; } + if (g_DeviceTrackingInitCounter_CoreAudio == 0) { + AudioObjectPropertyAddress propAddress; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - /* Internal format. */ - pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (pActualSS != NULL) { - ss = *pActualSS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback sample spec: format=%s, channels=%d, rate=%d\n", ma_get_format_name(ma_format_from_pulse(ss.format)), ss.channels, ss.rate); - } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve playback sample spec.\n"); - } + propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + pContextStateCoreAudio->AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - pDescriptorPlayback->format = ma_format_from_pulse(ss.format); - pDescriptorPlayback->channels = ss.channels; - pDescriptorPlayback->sampleRate = ss.rate; + propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + pContextStateCoreAudio->AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - if (pDescriptorPlayback->format == ma_format_unknown || pDescriptorPlayback->channels == 0 || pDescriptorPlayback->sampleRate == 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Playback sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.\n", ma_get_format_name(pDescriptorPlayback->format), pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate); - result = MA_ERROR; - goto on_error4; + /* At this point there should be no tracked devices. If not there's an error somewhere. */ + if (g_ppTrackedDevices_CoreAudio != NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active."); + ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); + return MA_INVALID_OPERATION; + } + + ma_mutex_uninit(&g_DeviceTrackingMutex_CoreAudio); } + } + ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); + return MA_SUCCESS; +} - /* Internal channel map. */ - pActualChannelMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (pActualChannelMap == NULL) { - pActualChannelMap = &cmap; /* Fallback just in case. */ - } +static ma_result ma_device__track__coreaudio(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); - /* - Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting - the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono - and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For - all other channel counts we need to just put up with whatever PipeWire reports and hope it gets - fixed sooner than later. I might remove this hack later. - */ - if (pDescriptorPlayback->channels > 2) { - for (iChannel = 0; iChannel < pDescriptorPlayback->channels; iChannel += 1) { - pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(pActualChannelMap->map[iChannel]); + ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); + { + /* Allocate memory if required. */ + if (g_TrackedDeviceCap_CoreAudio <= g_TrackedDeviceCount_CoreAudio) { + ma_uint32 newCap; + ma_device** ppNewDevices; + + newCap = g_TrackedDeviceCap_CoreAudio * 2; + if (newCap == 0) { + newCap = 1; } - } else { - /* Hack for mono and stereo. */ - if (pDescriptorPlayback->channels == 1) { - pDescriptorPlayback->channelMap[0] = MA_CHANNEL_MONO; - } else if (pDescriptorPlayback->channels == 2) { - pDescriptorPlayback->channelMap[0] = MA_CHANNEL_FRONT_LEFT; - pDescriptorPlayback->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } else { - MA_ASSERT(MA_FALSE); /* Should never hit this. */ + + ppNewDevices = (ma_device**)ma_realloc(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, ma_device_get_allocation_callbacks(pDevice)); + if (ppNewDevices == NULL) { + ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); + return MA_OUT_OF_MEMORY; } + + g_ppTrackedDevices_CoreAudio = ppNewDevices; + g_TrackedDeviceCap_CoreAudio = newCap; } + g_ppTrackedDevices_CoreAudio[g_TrackedDeviceCount_CoreAudio] = pDevice; + g_TrackedDeviceCount_CoreAudio += 1; + } + ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); - /* Buffer. */ - pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pDevice->pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (pActualAttr != NULL) { - attr = *pActualAttr; - } + return MA_SUCCESS; +} - if (attr.tlength > 0) { - pDescriptorPlayback->periodCount = ma_max(attr.maxlength / attr.tlength, 1); - } else { - pDescriptorPlayback->periodCount = 1; - } +static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); - pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); - } + ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); + { + ma_uint32 iDevice; + for (iDevice = 0; iDevice < g_TrackedDeviceCount_CoreAudio; iDevice += 1) { + if (g_ppTrackedDevices_CoreAudio[iDevice] == pDevice) { + /* We've found the device. We now need to remove it from the list. */ + ma_uint32 jDevice; + for (jDevice = iDevice; jDevice < g_TrackedDeviceCount_CoreAudio-1; jDevice += 1) { + g_ppTrackedDevices_CoreAudio[jDevice] = g_ppTrackedDevices_CoreAudio[jDevice+1]; + } + g_TrackedDeviceCount_CoreAudio -= 1; - /* - We need a ring buffer for handling duplex mode. We can use the main duplex ring buffer in the main - part of the ma_device struct. We cannot, however, depend on ma_device_init() initializing this for - us later on because that will only do it if it's a fully asynchronous backend - i.e. the - onDeviceDataLoop callback is NULL, which is not the case for PulseAudio. - */ - if (pConfig->deviceType == ma_device_type_duplex) { - ma_format rbFormat = (format != ma_format_unknown) ? format : pDescriptorCapture->format; - ma_uint32 rbChannels = (channels > 0) ? channels : pDescriptorCapture->channels; - ma_uint32 rbSampleRate = (sampleRate > 0) ? sampleRate : pDescriptorCapture->sampleRate; + /* If there's nothing else in the list we need to free memory. */ + if (g_TrackedDeviceCount_CoreAudio == 0) { + ma_free(g_ppTrackedDevices_CoreAudio, ma_device_get_allocation_callbacks(pDevice)); + g_ppTrackedDevices_CoreAudio = NULL; + g_TrackedDeviceCap_CoreAudio = 0; + } - result = ma_duplex_rb_init(rbFormat, rbChannels, rbSampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); - if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer. %s.\n", ma_result_description(result)); - goto on_error4; + break; + } } } + ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); return MA_SUCCESS; - - -on_error4: - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_disconnect_proc)pDevice->pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - } -on_error3: - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_unref_proc)pDevice->pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - } -on_error2: - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_disconnect_proc)pDevice->pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - } -on_error1: - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_unref_proc)pDevice->pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - } -on_error0: - return result; } +#endif -static void ma_pulse_operation_complete_callback(ma_pa_stream* pStream, int success, void* pUserData) +static void ma_backend_info__coreaudio(ma_device_backend_info* pBackendInfo) { - ma_bool32* pIsSuccessful = (ma_bool32*)pUserData; - MA_ASSERT(pIsSuccessful != NULL); + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "Core Audio"; +} - *pIsSuccessful = (ma_bool32)success; +#if defined(MA_APPLE_MOBILE) && defined(__IPHONE_12_0) +static AVAudioSessionCategory ma_to_AVAudioSessionCategory(ma_ios_session_category category) +{ + /* The "default" and "none" categories are treated different and should not be used as an input into this function. */ + MA_ASSERT(category != ma_ios_session_category_default); + MA_ASSERT(category != ma_ios_session_category_none); - (void)pStream; /* Unused. */ + switch (category) { + case ma_ios_session_category_ambient: return AVAudioSessionCategoryAmbient; + case ma_ios_session_category_solo_ambient: return AVAudioSessionCategorySoloAmbient; + case ma_ios_session_category_playback: return AVAudioSessionCategoryPlayback; + case ma_ios_session_category_record: return AVAudioSessionCategoryRecord; + case ma_ios_session_category_play_and_record: return AVAudioSessionCategoryPlayAndRecord; + case ma_ios_session_category_multi_route: return AVAudioSessionCategoryMultiRoute; + case ma_ios_session_category_none: return AVAudioSessionCategoryAmbient; + case ma_ios_session_category_default: return AVAudioSessionCategoryAmbient; + default: return AVAudioSessionCategoryAmbient; + } } +#endif -static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_type deviceType, int cork) +static ma_result ma_context_init__coreaudio(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) { - ma_context* pContext = pDevice->pContext; - ma_bool32 wasSuccessful; - ma_pa_stream* pStream; - ma_pa_operation* pOP; - ma_result result; + ma_context_state_coreaudio* pContextStateCoreAudio; + const ma_context_config_coreaudio* pContextConfigCoreAudio = (const ma_context_config_coreaudio*)pContextBackendConfig; + ma_context_config_coreaudio defaultConfigCoreAudio; + + if (pContextConfigCoreAudio == NULL) { + defaultConfigCoreAudio = ma_context_config_coreaudio_init(); + pContextConfigCoreAudio = &defaultConfigCoreAudio; + } - /* This should not be called with a duplex device type. */ - if (deviceType == ma_device_type_duplex) { - return MA_INVALID_ARGS; + pContextStateCoreAudio = (ma_context_state_coreaudio*)ma_calloc(sizeof(*pContextStateCoreAudio), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateCoreAudio == NULL) { + return MA_OUT_OF_MEMORY; } - wasSuccessful = MA_FALSE; + #if defined(MA_APPLE_MOBILE) + { + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + AVAudioSessionCategoryOptions options = pContextConfigCoreAudio->sessionCategoryOptions; - pStream = (ma_pa_stream*)((deviceType == ma_device_type_capture) ? pDevice->pulse.pStreamCapture : pDevice->pulse.pStreamPlayback); - MA_ASSERT(pStream != NULL); + MA_ASSERT(pAudioSession != NULL); - pOP = ((ma_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)(pStream, cork, ma_pulse_operation_complete_callback, &wasSuccessful); - if (pOP == NULL) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream."); - return MA_ERROR; - } + if (pContextConfigCoreAudio->sessionCategory == ma_ios_session_category_default) { + /* + I'm going to use trial and error to determine our default session category. First we'll try PlayAndRecord. If that fails + we'll try Playback and if that fails we'll try record. If all of these fail we'll just not set the category. + */ + #if !defined(MA_APPLE_TV) && !defined(MA_APPLE_WATCH) + options |= AVAudioSessionCategoryOptionDefaultToSpeaker; + #endif - result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP); - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork."); - return result; - } + if ([pAudioSession setCategory: AVAudioSessionCategoryPlayAndRecord withOptions:options error:nil]) { + /* Using PlayAndRecord */ + } else if ([pAudioSession setCategory: AVAudioSessionCategoryPlayback withOptions:options error:nil]) { + /* Using Playback */ + } else if ([pAudioSession setCategory: AVAudioSessionCategoryRecord withOptions:options error:nil]) { + /* Using Record */ + } else { + /* Leave as default? */ + } + } else { + if (pContextConfigCoreAudio->sessionCategory != ma_ios_session_category_none) { + #if defined(__IPHONE_12_0) + if (![pAudioSession setCategory: ma_to_AVAudioSessionCategory(pContextConfigCoreAudio->sessionCategory) withOptions:options error:nil]) { + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_INVALID_OPERATION; /* Failed to set session category. */ + } + #else + /* Ignore the session category on version 11 and older, but post a warning. */ + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Session category only supported in iOS 12 and newer."); + #endif + } + } - if (!wasSuccessful) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to %s PulseAudio stream.", (cork) ? "stop" : "start"); - return MA_ERROR; + if (!pContextConfigCoreAudio->noAudioSessionActivate) { + if (![pAudioSession setActive:true error:nil]) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to activate audio session."); + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_FAILED_TO_INIT_BACKEND; + } + } + } } + #endif - return MA_SUCCESS; -} + #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) + { + pContextStateCoreAudio->hCoreFoundation = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"); + if (pContextStateCoreAudio->hCoreFoundation == NULL) { + return MA_API_NOT_FOUND; + } -static ma_result ma_device_start__pulse(ma_device* pDevice) -{ - ma_result result; + pContextStateCoreAudio->CFStringGetCString = (ma_CFStringGetCString_proc)ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation, "CFStringGetCString"); + pContextStateCoreAudio->CFRelease = (ma_CFRelease_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation, "CFRelease"); - MA_ASSERT(pDevice != NULL); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); - if (result != MA_SUCCESS) { - return result; + pContextStateCoreAudio->hCoreAudio = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreAudio.framework/CoreAudio"); + if (pContextStateCoreAudio->hCoreAudio == NULL) { + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation); + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_API_NOT_FOUND; } - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + pContextStateCoreAudio->AudioObjectGetPropertyData = (ma_AudioObjectGetPropertyData_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio, "AudioObjectGetPropertyData"); + pContextStateCoreAudio->AudioObjectGetPropertyDataSize = (ma_AudioObjectGetPropertyDataSize_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio, "AudioObjectGetPropertyDataSize"); + pContextStateCoreAudio->AudioObjectSetPropertyData = (ma_AudioObjectSetPropertyData_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio, "AudioObjectSetPropertyData"); + pContextStateCoreAudio->AudioObjectAddPropertyListener = (ma_AudioObjectAddPropertyListener_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio, "AudioObjectAddPropertyListener"); + pContextStateCoreAudio->AudioObjectRemovePropertyListener = (ma_AudioObjectRemovePropertyListener_proc)ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio, "AudioObjectRemovePropertyListener"); + /* - We need to fill some data before uncorking. Not doing this will result in the write callback - never getting fired. We're not going to abort if writing fails because I still want the device - to get uncorked. + It looks like Apple has moved some APIs from AudioUnit into AudioToolbox on more recent versions of macOS. They are still + defined in AudioUnit, but just in case they decide to remove them from there entirely I'm going to implement a fallback. + The way it'll work is that it'll first try AudioUnit, and if the required symbols are not present there we'll fall back to + AudioToolbox. */ - ma_device_write_to_stream__pulse(pDevice, (ma_pa_stream*)(pDevice->pulse.pStreamPlayback), NULL); /* No need to check the result here. Always want to fall through an uncork.*/ + pContextStateCoreAudio->hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioUnit.framework/AudioUnit"); + if (pContextStateCoreAudio->hAudioUnit == NULL) { + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation); + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_API_NOT_FOUND; + } - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); - if (result != MA_SUCCESS) { - return result; + if (ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioComponentFindNext") == NULL) { + /* Couldn't find the required symbols in AudioUnit, so fall back to AudioToolbox. */ + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit); + pContextStateCoreAudio->hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox"); + if (pContextStateCoreAudio->hAudioUnit == NULL) { + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation); + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_API_NOT_FOUND; + } } + + pContextStateCoreAudio->AudioComponentFindNext = (ma_AudioComponentFindNext_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioComponentFindNext"); + pContextStateCoreAudio->AudioComponentInstanceDispose = (ma_AudioComponentInstanceDispose_proc)ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioComponentInstanceDispose"); + pContextStateCoreAudio->AudioComponentInstanceNew = (ma_AudioComponentInstanceNew_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioComponentInstanceNew"); + pContextStateCoreAudio->AudioOutputUnitStart = (ma_AudioOutputUnitStart_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioOutputUnitStart"); + pContextStateCoreAudio->AudioOutputUnitStop = (ma_AudioOutputUnitStop_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioOutputUnitStop"); + pContextStateCoreAudio->AudioUnitAddPropertyListener = (ma_AudioUnitAddPropertyListener_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioUnitAddPropertyListener"); + pContextStateCoreAudio->AudioUnitGetPropertyInfo = (ma_AudioUnitGetPropertyInfo_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioUnitGetPropertyInfo"); + pContextStateCoreAudio->AudioUnitGetProperty = (ma_AudioUnitGetProperty_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioUnitGetProperty"); + pContextStateCoreAudio->AudioUnitSetProperty = (ma_AudioUnitSetProperty_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioUnitSetProperty"); + pContextStateCoreAudio->AudioUnitInitialize = (ma_AudioUnitInitialize_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioUnitInitialize"); + pContextStateCoreAudio->AudioUnitRender = (ma_AudioUnitRender_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit, "AudioUnitRender"); } + #else + { + pContextStateCoreAudio->CFStringGetCString = CFStringGetCString; + pContextStateCoreAudio->CFRelease = CFRelease; - return MA_SUCCESS; -} + #if defined(MA_APPLE_DESKTOP) + pContextStateCoreAudio->AudioObjectGetPropertyData = AudioObjectGetPropertyData; + pContextStateCoreAudio->AudioObjectGetPropertyDataSize = AudioObjectGetPropertyDataSize; + pContextStateCoreAudio->AudioObjectSetPropertyData = AudioObjectSetPropertyData; + pContextStateCoreAudio->AudioObjectAddPropertyListener = AudioObjectAddPropertyListener; + pContextStateCoreAudio->AudioObjectRemovePropertyListener = AudioObjectRemovePropertyListener; + #endif -static ma_result ma_device_stop__pulse(ma_device* pDevice) -{ - ma_result result; + pContextStateCoreAudio->AudioComponentFindNext = AudioComponentFindNext; + pContextStateCoreAudio->AudioComponentInstanceDispose = AudioComponentInstanceDispose; + pContextStateCoreAudio->AudioComponentInstanceNew = AudioComponentInstanceNew; + pContextStateCoreAudio->AudioOutputUnitStart = AudioOutputUnitStart; + pContextStateCoreAudio->AudioOutputUnitStop = AudioOutputUnitStop; + pContextStateCoreAudio->AudioUnitAddPropertyListener = AudioUnitAddPropertyListener; + pContextStateCoreAudio->AudioUnitGetPropertyInfo = AudioUnitGetPropertyInfo; + pContextStateCoreAudio->AudioUnitGetProperty = AudioUnitGetProperty; + pContextStateCoreAudio->AudioUnitSetProperty = AudioUnitSetProperty; + pContextStateCoreAudio->AudioUnitInitialize = AudioUnitInitialize; + pContextStateCoreAudio->AudioUnitRender = AudioUnitRender; + } + #endif - MA_ASSERT(pDevice != NULL); + /* Audio component. */ + { + AudioComponentDescription desc; + desc.componentType = kAudioUnitType_Output; + #if defined(MA_APPLE_DESKTOP) + desc.componentSubType = kAudioUnitSubType_HALOutput; + #else + desc.componentSubType = kAudioUnitSubType_RemoteIO; + #endif + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 1); - if (result != MA_SUCCESS) { - return result; + pContextStateCoreAudio->component = pContextStateCoreAudio->AudioComponentFindNext(NULL, &desc); + if (pContextStateCoreAudio->component == NULL) { + #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation); + #endif + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_FAILED_TO_INIT_BACKEND; } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* - Ideally we would drain the device here, but there's been cases where PulseAudio seems to be - broken on some systems to the point where no audio processing seems to happen. When this - happens, draining never completes and we get stuck here. For now I'm disabling draining of - the device so we don't just freeze the application. - */ - #if 0 - ma_pa_operation* pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP); - #endif - - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1); + #if !defined(MA_APPLE_MOBILE) + { + ma_result result = ma_context__init_device_tracking__coreaudio(pContext, pContextStateCoreAudio); if (result != MA_SUCCESS) { + #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation); + #endif + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); return result; } } + #endif + + pContextStateCoreAudio->noAudioSessionDeactivate = pContextConfigCoreAudio->noAudioSessionDeactivate; + + *ppContextState = pContextStateCoreAudio; return MA_SUCCESS; } -static ma_result ma_device_data_loop__pulse(ma_device* pDevice) +static void ma_context_uninit__coreaudio(ma_context* pContext) { - int resultPA; - - MA_ASSERT(pDevice != NULL); - - /* NOTE: Don't start the device here. It'll be done at a higher level. */ + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); - /* - All data is handled through callbacks. All we need to do is iterate over the main loop and let - the callbacks deal with it. - */ - while (ma_device_get_state(pDevice) == ma_device_state_started) { - resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (resultPA < 0) { - break; +#if defined(MA_APPLE_MOBILE) + if (!pContextStateCoreAudio->noAudioSessionDeactivate) { + if (![[AVAudioSession sharedInstance] setActive:false error:nil]) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session."); } } +#endif - /* NOTE: Don't stop the device here. It'll be done at a higher level. */ - return MA_SUCCESS; -} - -static ma_result ma_device_data_loop_wakeup__pulse(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); +#if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hAudioUnit); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreAudio); + ma_dlclose(ma_context_get_log(pContext), pContextStateCoreAudio->hCoreFoundation); +#endif - ((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); +#if !defined(MA_APPLE_MOBILE) + ma_context__uninit_device_tracking__coreaudio(pContext, pContextStateCoreAudio); +#endif - return MA_SUCCESS; + ma_free(pContextStateCoreAudio, ma_context_get_allocation_callbacks(pContext)); } -static ma_result ma_context_uninit__pulse(ma_context* pContext) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_pulseaudio); - - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pContext->pulse.pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop); - - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - -#ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(ma_context_get_log(pContext), pContext->pulse.pulseSO); -#endif - return MA_SUCCESS; +#if !defined(MA_APPLE_DESKTOP) +static void ma_AVAudioSessionPortDescription_to_device_info(AVAudioSessionPortDescription* pPortDesc, ma_device_info* pInfo) +{ + MA_ZERO_OBJECT(pInfo); + ma_strncpy_s(pInfo->name, sizeof(pInfo->name), [pPortDesc.portName UTF8String], (size_t)-1); + ma_strncpy_s(pInfo->id.coreaudio, sizeof(pInfo->id.coreaudio), [pPortDesc.UID UTF8String], (size_t)-1); } +#endif -static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +#if defined(MA_APPLE_DESKTOP) +static ma_device_enumeration_result ma_context_enumerate_device_by_AudioObjectID__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, AudioObjectID defaultDeviceObjectID, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_result result; -#ifndef MA_NO_RUNTIME_LINKING - const char* libpulseNames[] = { - "libpulse.so", - "libpulse.so.0" - }; - size_t i; - - for (i = 0; i < ma_countof(libpulseNames); ++i) { - pContext->pulse.pulseSO = ma_dlopen(ma_context_get_log(pContext), libpulseNames[i]); - if (pContext->pulse.pulseSO != NULL) { - break; - } - } + ma_device_info deviceInfo; - if (pContext->pulse.pulseSO == NULL) { - return MA_NO_BACKEND; - } + MA_ZERO_OBJECT(&deviceInfo); - pContext->pulse.pa_mainloop_new = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_mainloop_new"); - pContext->pulse.pa_mainloop_free = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_mainloop_free"); - pContext->pulse.pa_mainloop_quit = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_mainloop_quit"); - pContext->pulse.pa_mainloop_get_api = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_mainloop_get_api"); - pContext->pulse.pa_mainloop_iterate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_mainloop_iterate"); - pContext->pulse.pa_mainloop_wakeup = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_mainloop_wakeup"); - pContext->pulse.pa_threaded_mainloop_new = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_new"); - pContext->pulse.pa_threaded_mainloop_free = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_free"); - pContext->pulse.pa_threaded_mainloop_start = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_start"); - pContext->pulse.pa_threaded_mainloop_stop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_stop"); - pContext->pulse.pa_threaded_mainloop_lock = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_lock"); - pContext->pulse.pa_threaded_mainloop_unlock = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_unlock"); - pContext->pulse.pa_threaded_mainloop_wait = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_wait"); - pContext->pulse.pa_threaded_mainloop_signal = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_signal"); - pContext->pulse.pa_threaded_mainloop_accept = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_accept"); - pContext->pulse.pa_threaded_mainloop_get_retval = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_get_retval"); - pContext->pulse.pa_threaded_mainloop_get_api = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_get_api"); - pContext->pulse.pa_threaded_mainloop_in_thread = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_in_thread"); - pContext->pulse.pa_threaded_mainloop_set_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_threaded_mainloop_set_name"); - pContext->pulse.pa_context_new = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_new"); - pContext->pulse.pa_context_unref = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_unref"); - pContext->pulse.pa_context_connect = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_connect"); - pContext->pulse.pa_context_disconnect = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_disconnect"); - pContext->pulse.pa_context_set_state_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_set_state_callback"); - pContext->pulse.pa_context_get_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_state"); - pContext->pulse.pa_context_get_sink_info_list = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_sink_info_list"); - pContext->pulse.pa_context_get_source_info_list = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_source_info_list"); - pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_sink_info_by_name"); - pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_context_get_source_info_by_name"); - pContext->pulse.pa_operation_unref = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_unref"); - pContext->pulse.pa_operation_get_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_operation_get_state"); - pContext->pulse.pa_channel_map_init_extend = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_channel_map_init_extend"); - pContext->pulse.pa_channel_map_valid = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_channel_map_valid"); - pContext->pulse.pa_channel_map_compatible = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_channel_map_compatible"); - pContext->pulse.pa_stream_new = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_new"); - pContext->pulse.pa_stream_unref = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_unref"); - pContext->pulse.pa_stream_connect_playback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_connect_playback"); - pContext->pulse.pa_stream_connect_record = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_connect_record"); - pContext->pulse.pa_stream_disconnect = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_disconnect"); - pContext->pulse.pa_stream_get_state = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_state"); - pContext->pulse.pa_stream_get_sample_spec = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_sample_spec"); - pContext->pulse.pa_stream_get_channel_map = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_channel_map"); - pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_buffer_attr"); - pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_buffer_attr"); - pContext->pulse.pa_stream_get_device_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_device_name"); - pContext->pulse.pa_stream_set_write_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_write_callback"); - pContext->pulse.pa_stream_set_read_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_read_callback"); - pContext->pulse.pa_stream_set_suspended_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_suspended_callback"); - pContext->pulse.pa_stream_set_moved_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_moved_callback"); - pContext->pulse.pa_stream_is_suspended = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_is_suspended"); - pContext->pulse.pa_stream_flush = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_flush"); - pContext->pulse.pa_stream_drain = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_drain"); - pContext->pulse.pa_stream_is_corked = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_is_corked"); - pContext->pulse.pa_stream_cork = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_cork"); - pContext->pulse.pa_stream_trigger = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_trigger"); - pContext->pulse.pa_stream_begin_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_begin_write"); - pContext->pulse.pa_stream_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_write"); - pContext->pulse.pa_stream_peek = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_peek"); - pContext->pulse.pa_stream_drop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_drop"); - pContext->pulse.pa_stream_writable_size = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_writable_size"); - pContext->pulse.pa_stream_readable_size = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_readable_size"); -#else - /* This strange assignment system is just for type safety. */ - ma_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new; - ma_pa_mainloop_free_proc _pa_mainloop_free = pa_mainloop_free; - ma_pa_mainloop_quit_proc _pa_mainloop_quit = pa_mainloop_quit; - ma_pa_mainloop_get_api_proc _pa_mainloop_get_api = pa_mainloop_get_api; - ma_pa_mainloop_iterate_proc _pa_mainloop_iterate = pa_mainloop_iterate; - ma_pa_mainloop_wakeup_proc _pa_mainloop_wakeup = pa_mainloop_wakeup; - ma_pa_threaded_mainloop_new_proc _pa_threaded_mainloop_new = pa_threaded_mainloop_new; - ma_pa_threaded_mainloop_free_proc _pa_threaded_mainloop_free = pa_threaded_mainloop_free; - ma_pa_threaded_mainloop_start_proc _pa_threaded_mainloop_start = pa_threaded_mainloop_start; - ma_pa_threaded_mainloop_stop_proc _pa_threaded_mainloop_stop = pa_threaded_mainloop_stop; - ma_pa_threaded_mainloop_lock_proc _pa_threaded_mainloop_lock = pa_threaded_mainloop_lock; - ma_pa_threaded_mainloop_unlock_proc _pa_threaded_mainloop_unlock = pa_threaded_mainloop_unlock; - ma_pa_threaded_mainloop_wait_proc _pa_threaded_mainloop_wait = pa_threaded_mainloop_wait; - ma_pa_threaded_mainloop_signal_proc _pa_threaded_mainloop_signal = pa_threaded_mainloop_signal; - ma_pa_threaded_mainloop_accept_proc _pa_threaded_mainloop_accept = pa_threaded_mainloop_accept; - ma_pa_threaded_mainloop_get_retval_proc _pa_threaded_mainloop_get_retval = pa_threaded_mainloop_get_retval; - ma_pa_threaded_mainloop_get_api_proc _pa_threaded_mainloop_get_api = pa_threaded_mainloop_get_api; - ma_pa_threaded_mainloop_in_thread_proc _pa_threaded_mainloop_in_thread = pa_threaded_mainloop_in_thread; - ma_pa_threaded_mainloop_set_name_proc _pa_threaded_mainloop_set_name = pa_threaded_mainloop_set_name; - ma_pa_context_new_proc _pa_context_new = pa_context_new; - ma_pa_context_unref_proc _pa_context_unref = pa_context_unref; - ma_pa_context_connect_proc _pa_context_connect = pa_context_connect; - ma_pa_context_disconnect_proc _pa_context_disconnect = pa_context_disconnect; - ma_pa_context_set_state_callback_proc _pa_context_set_state_callback = pa_context_set_state_callback; - ma_pa_context_get_state_proc _pa_context_get_state = pa_context_get_state; - ma_pa_context_get_sink_info_list_proc _pa_context_get_sink_info_list = pa_context_get_sink_info_list; - ma_pa_context_get_source_info_list_proc _pa_context_get_source_info_list = pa_context_get_source_info_list; - ma_pa_context_get_sink_info_by_name_proc _pa_context_get_sink_info_by_name = pa_context_get_sink_info_by_name; - ma_pa_context_get_source_info_by_name_proc _pa_context_get_source_info_by_name= pa_context_get_source_info_by_name; - ma_pa_operation_unref_proc _pa_operation_unref = pa_operation_unref; - ma_pa_operation_get_state_proc _pa_operation_get_state = pa_operation_get_state; - ma_pa_channel_map_init_extend_proc _pa_channel_map_init_extend = pa_channel_map_init_extend; - ma_pa_channel_map_valid_proc _pa_channel_map_valid = pa_channel_map_valid; - ma_pa_channel_map_compatible_proc _pa_channel_map_compatible = pa_channel_map_compatible; - ma_pa_stream_new_proc _pa_stream_new = pa_stream_new; - ma_pa_stream_unref_proc _pa_stream_unref = pa_stream_unref; - ma_pa_stream_connect_playback_proc _pa_stream_connect_playback = pa_stream_connect_playback; - ma_pa_stream_connect_record_proc _pa_stream_connect_record = pa_stream_connect_record; - ma_pa_stream_disconnect_proc _pa_stream_disconnect = pa_stream_disconnect; - ma_pa_stream_get_state_proc _pa_stream_get_state = pa_stream_get_state; - ma_pa_stream_get_sample_spec_proc _pa_stream_get_sample_spec = pa_stream_get_sample_spec; - ma_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map; - ma_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr; - ma_pa_stream_set_buffer_attr_proc _pa_stream_set_buffer_attr = pa_stream_set_buffer_attr; - ma_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name; - ma_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; - ma_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; - ma_pa_stream_set_suspended_callback_proc _pa_stream_set_suspended_callback = pa_stream_set_suspended_callback; - ma_pa_stream_set_moved_callback_proc _pa_stream_set_moved_callback = pa_stream_set_moved_callback; - ma_pa_stream_is_suspended_proc _pa_stream_is_suspended = pa_stream_is_suspended; - ma_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; - ma_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; - ma_pa_stream_is_corked_proc _pa_stream_is_corked = pa_stream_is_corked; - ma_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork; - ma_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger; - ma_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write; - ma_pa_stream_write_proc _pa_stream_write = pa_stream_write; - ma_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek; - ma_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop; - ma_pa_stream_writable_size_proc _pa_stream_writable_size = pa_stream_writable_size; - ma_pa_stream_readable_size_proc _pa_stream_readable_size = pa_stream_readable_size; - - pContext->pulse.pa_mainloop_new = (ma_proc)_pa_mainloop_new; - pContext->pulse.pa_mainloop_free = (ma_proc)_pa_mainloop_free; - pContext->pulse.pa_mainloop_quit = (ma_proc)_pa_mainloop_quit; - pContext->pulse.pa_mainloop_get_api = (ma_proc)_pa_mainloop_get_api; - pContext->pulse.pa_mainloop_iterate = (ma_proc)_pa_mainloop_iterate; - pContext->pulse.pa_mainloop_wakeup = (ma_proc)_pa_mainloop_wakeup; - pContext->pulse.pa_threaded_mainloop_new = (ma_proc)_pa_threaded_mainloop_new; - pContext->pulse.pa_threaded_mainloop_free = (ma_proc)_pa_threaded_mainloop_free; - pContext->pulse.pa_threaded_mainloop_start = (ma_proc)_pa_threaded_mainloop_start; - pContext->pulse.pa_threaded_mainloop_stop = (ma_proc)_pa_threaded_mainloop_stop; - pContext->pulse.pa_threaded_mainloop_lock = (ma_proc)_pa_threaded_mainloop_lock; - pContext->pulse.pa_threaded_mainloop_unlock = (ma_proc)_pa_threaded_mainloop_unlock; - pContext->pulse.pa_threaded_mainloop_wait = (ma_proc)_pa_threaded_mainloop_wait; - pContext->pulse.pa_threaded_mainloop_signal = (ma_proc)_pa_threaded_mainloop_signal; - pContext->pulse.pa_threaded_mainloop_accept = (ma_proc)_pa_threaded_mainloop_accept; - pContext->pulse.pa_threaded_mainloop_get_retval = (ma_proc)_pa_threaded_mainloop_get_retval; - pContext->pulse.pa_threaded_mainloop_get_api = (ma_proc)_pa_threaded_mainloop_get_api; - pContext->pulse.pa_threaded_mainloop_in_thread = (ma_proc)_pa_threaded_mainloop_in_thread; - pContext->pulse.pa_threaded_mainloop_set_name = (ma_proc)_pa_threaded_mainloop_set_name; - pContext->pulse.pa_context_new = (ma_proc)_pa_context_new; - pContext->pulse.pa_context_unref = (ma_proc)_pa_context_unref; - pContext->pulse.pa_context_connect = (ma_proc)_pa_context_connect; - pContext->pulse.pa_context_disconnect = (ma_proc)_pa_context_disconnect; - pContext->pulse.pa_context_set_state_callback = (ma_proc)_pa_context_set_state_callback; - pContext->pulse.pa_context_get_state = (ma_proc)_pa_context_get_state; - pContext->pulse.pa_context_get_sink_info_list = (ma_proc)_pa_context_get_sink_info_list; - pContext->pulse.pa_context_get_source_info_list = (ma_proc)_pa_context_get_source_info_list; - pContext->pulse.pa_context_get_sink_info_by_name = (ma_proc)_pa_context_get_sink_info_by_name; - pContext->pulse.pa_context_get_source_info_by_name = (ma_proc)_pa_context_get_source_info_by_name; - pContext->pulse.pa_operation_unref = (ma_proc)_pa_operation_unref; - pContext->pulse.pa_operation_get_state = (ma_proc)_pa_operation_get_state; - pContext->pulse.pa_channel_map_init_extend = (ma_proc)_pa_channel_map_init_extend; - pContext->pulse.pa_channel_map_valid = (ma_proc)_pa_channel_map_valid; - pContext->pulse.pa_channel_map_compatible = (ma_proc)_pa_channel_map_compatible; - pContext->pulse.pa_stream_new = (ma_proc)_pa_stream_new; - pContext->pulse.pa_stream_unref = (ma_proc)_pa_stream_unref; - pContext->pulse.pa_stream_connect_playback = (ma_proc)_pa_stream_connect_playback; - pContext->pulse.pa_stream_connect_record = (ma_proc)_pa_stream_connect_record; - pContext->pulse.pa_stream_disconnect = (ma_proc)_pa_stream_disconnect; - pContext->pulse.pa_stream_get_state = (ma_proc)_pa_stream_get_state; - pContext->pulse.pa_stream_get_sample_spec = (ma_proc)_pa_stream_get_sample_spec; - pContext->pulse.pa_stream_get_channel_map = (ma_proc)_pa_stream_get_channel_map; - pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)_pa_stream_get_buffer_attr; - pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)_pa_stream_set_buffer_attr; - pContext->pulse.pa_stream_get_device_name = (ma_proc)_pa_stream_get_device_name; - pContext->pulse.pa_stream_set_write_callback = (ma_proc)_pa_stream_set_write_callback; - pContext->pulse.pa_stream_set_read_callback = (ma_proc)_pa_stream_set_read_callback; - pContext->pulse.pa_stream_set_suspended_callback = (ma_proc)_pa_stream_set_suspended_callback; - pContext->pulse.pa_stream_set_moved_callback = (ma_proc)_pa_stream_set_moved_callback; - pContext->pulse.pa_stream_is_suspended = (ma_proc)_pa_stream_is_suspended; - pContext->pulse.pa_stream_flush = (ma_proc)_pa_stream_flush; - pContext->pulse.pa_stream_drain = (ma_proc)_pa_stream_drain; - pContext->pulse.pa_stream_is_corked = (ma_proc)_pa_stream_is_corked; - pContext->pulse.pa_stream_cork = (ma_proc)_pa_stream_cork; - pContext->pulse.pa_stream_trigger = (ma_proc)_pa_stream_trigger; - pContext->pulse.pa_stream_begin_write = (ma_proc)_pa_stream_begin_write; - pContext->pulse.pa_stream_write = (ma_proc)_pa_stream_write; - pContext->pulse.pa_stream_peek = (ma_proc)_pa_stream_peek; - pContext->pulse.pa_stream_drop = (ma_proc)_pa_stream_drop; - pContext->pulse.pa_stream_writable_size = (ma_proc)_pa_stream_writable_size; - pContext->pulse.pa_stream_readable_size = (ma_proc)_pa_stream_readable_size; -#endif - - /* We need to make a copy of the application and server names so we can pass them to the pa_context of each device. */ - pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName, &pContext->allocationCallbacks); - if (pContext->pulse.pApplicationName == NULL && pConfig->pulse.pApplicationName != NULL) { - return MA_OUT_OF_MEMORY; + /* Default. */ + if (deviceObjectID == defaultDeviceObjectID) { + deviceInfo.isDefault = MA_TRUE; } - pContext->pulse.pServerName = ma_copy_string(pConfig->pulse.pServerName, &pContext->allocationCallbacks); - if (pContext->pulse.pServerName == NULL && pConfig->pulse.pServerName != NULL) { - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - return MA_OUT_OF_MEMORY; + /* ID. */ + if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(deviceInfo.id.coreaudio), deviceInfo.id.coreaudio) != MA_SUCCESS) { + return MA_DEVICE_ENUMERATION_CONTINUE; } - result = ma_init_pa_mainloop_and_pa_context__pulse(pContext, pConfig->pulse.pApplicationName, pConfig->pulse.pServerName, pConfig->pulse.tryAutoSpawn, &pContext->pulse.pMainLoop, &pContext->pulse.pPulseContext); - if (result != MA_SUCCESS) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(ma_context_get_log(pContext), pContext->pulse.pulseSO); - #endif - return result; + /* Name. */ + if (ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(deviceInfo.name), deviceInfo.name) != MA_SUCCESS) { + return MA_DEVICE_ENUMERATION_CONTINUE; } - /* With pa_mainloop we run a synchronous backend, but we implement our own main loop. */ - pCallbacks->onContextInit = ma_context_init__pulse; - pCallbacks->onContextUninit = ma_context_uninit__pulse; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__pulse; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__pulse; - pCallbacks->onDeviceInit = ma_device_init__pulse; - pCallbacks->onDeviceUninit = ma_device_uninit__pulse; - pCallbacks->onDeviceStart = ma_device_start__pulse; - pCallbacks->onDeviceStop = ma_device_stop__pulse; - pCallbacks->onDeviceRead = NULL; /* Not used because we're implementing onDeviceDataLoop. */ - pCallbacks->onDeviceWrite = NULL; /* Not used because we're implementing onDeviceDataLoop. */ - pCallbacks->onDeviceDataLoop = ma_device_data_loop__pulse; - pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__pulse; - - return MA_SUCCESS; -} -#endif - - -/****************************************************************************** - -JACK Backend - -******************************************************************************/ -#ifdef MA_HAS_JACK - -/* It is assumed jack.h is available when compile-time linking is being used. */ -#ifdef MA_NO_RUNTIME_LINKING -#include + /* Data Format. */ + { + /* + There could be a large number of permutations here. Fortunately there is only a single channel count + being reported which reduces this quite a bit. For sample rates we're only reporting those that are + one of miniaudio's recognized "standard" rates. If there are still more formats than can fit into + our fixed sized array we'll just need to truncate them. This is unlikely and will probably only happen + if some driver performs software data conversion and therefore reports every possible format and + sample rate. + */ + ma_result result; + ma_format uniqueFormats[ma_format_count]; + ma_uint32 uniqueFormatCount = 0; + ma_uint32 channels; + UInt32 streamDescriptionCount; + AudioStreamRangedDescription* pStreamDescriptions; + UInt32 iStreamDescription; -typedef jack_nframes_t ma_jack_nframes_t; -typedef jack_options_t ma_jack_options_t; -typedef jack_status_t ma_jack_status_t; -typedef jack_client_t ma_jack_client_t; -typedef jack_port_t ma_jack_port_t; -typedef JackProcessCallback ma_JackProcessCallback; -typedef JackBufferSizeCallback ma_JackBufferSizeCallback; -typedef JackShutdownCallback ma_JackShutdownCallback; -#define MA_JACK_DEFAULT_AUDIO_TYPE JACK_DEFAULT_AUDIO_TYPE -#define ma_JackNullOption JackNullOption -#define ma_JackNoStartServer JackNoStartServer -#define ma_JackPortIsInput JackPortIsInput -#define ma_JackPortIsOutput JackPortIsOutput -#define ma_JackPortIsPhysical JackPortIsPhysical -#else -typedef ma_uint32 ma_jack_nframes_t; -typedef int ma_jack_options_t; -typedef int ma_jack_status_t; -typedef struct ma_jack_client_t ma_jack_client_t; -typedef struct ma_jack_port_t ma_jack_port_t; -typedef int (* ma_JackProcessCallback) (ma_jack_nframes_t nframes, void* arg); -typedef int (* ma_JackBufferSizeCallback)(ma_jack_nframes_t nframes, void* arg); -typedef void (* ma_JackShutdownCallback) (void* arg); -#define MA_JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" -#define ma_JackNullOption 0 -#define ma_JackNoStartServer 1 -#define ma_JackPortIsInput 1 -#define ma_JackPortIsOutput 2 -#define ma_JackPortIsPhysical 4 -#endif + deviceInfo.nativeDataFormatCount = 0; -typedef ma_jack_client_t* (* ma_jack_client_open_proc) (const char* client_name, ma_jack_options_t options, ma_jack_status_t* status, ...); -typedef int (* ma_jack_client_close_proc) (ma_jack_client_t* client); -typedef int (* ma_jack_client_name_size_proc) (void); -typedef int (* ma_jack_set_process_callback_proc) (ma_jack_client_t* client, ma_JackProcessCallback process_callback, void* arg); -typedef int (* ma_jack_set_buffer_size_callback_proc)(ma_jack_client_t* client, ma_JackBufferSizeCallback bufsize_callback, void* arg); -typedef void (* ma_jack_on_shutdown_proc) (ma_jack_client_t* client, ma_JackShutdownCallback function, void* arg); -typedef ma_jack_nframes_t (* ma_jack_get_sample_rate_proc) (ma_jack_client_t* client); -typedef ma_jack_nframes_t (* ma_jack_get_buffer_size_proc) (ma_jack_client_t* client); -typedef const char** (* ma_jack_get_ports_proc) (ma_jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags); -typedef int (* ma_jack_activate_proc) (ma_jack_client_t* client); -typedef int (* ma_jack_deactivate_proc) (ma_jack_client_t* client); -typedef int (* ma_jack_connect_proc) (ma_jack_client_t* client, const char* source_port, const char* destination_port); -typedef ma_jack_port_t* (* ma_jack_port_register_proc) (ma_jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size); -typedef const char* (* ma_jack_port_name_proc) (const ma_jack_port_t* port); -typedef void* (* ma_jack_port_get_buffer_proc) (ma_jack_port_t* port, ma_jack_nframes_t nframes); -typedef void (* ma_jack_free_proc) (void* ptr); - -static ma_result ma_context_open_client__jack(ma_context* pContext, ma_jack_client_t** ppClient) -{ - size_t maxClientNameSize; - char clientName[256]; - ma_jack_status_t status; - ma_jack_client_t* pClient; + /* Channels. */ + result = ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &channels); + if (result != MA_SUCCESS) { + return MA_DEVICE_ENUMERATION_CONTINUE; /* Failed to get channel count. */ + } - MA_ASSERT(pContext != NULL); - MA_ASSERT(ppClient != NULL); + /* Formats. */ + result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions); + if (result != MA_SUCCESS) { + return MA_DEVICE_ENUMERATION_CONTINUE; + } - if (ppClient) { - *ppClient = NULL; - } + for (iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) { + ma_format format; + ma_bool32 hasFormatBeenHandled = MA_FALSE; + ma_uint32 iOutputFormat; + ma_uint32 iSampleRate; + UInt32 sampleRateRangeCount; + AudioValueRange* pSampleRateRanges; + ma_uint32 minSampleRate = 0xFFFFFFFF; + ma_uint32 maxSampleRate = 0; + + result = ma_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format); + if (result != MA_SUCCESS) { + continue; + } - maxClientNameSize = ((ma_jack_client_name_size_proc)pContext->jack.jack_client_name_size)(); /* Includes null terminator. */ - ma_strncpy_s(clientName, ma_min(sizeof(clientName), maxClientNameSize), (pContext->jack.pClientName != NULL) ? pContext->jack.pClientName : "miniaudio", (size_t)-1); + MA_ASSERT(format != ma_format_unknown); - pClient = ((ma_jack_client_open_proc)pContext->jack.jack_client_open)(clientName, (pContext->jack.tryStartServer) ? ma_JackNullOption : ma_JackNoStartServer, &status, NULL); - if (pClient == NULL) { - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } + /* Make sure the format isn't already in the output list. */ + for (iOutputFormat = 0; iOutputFormat < uniqueFormatCount; iOutputFormat += 1) { + if (uniqueFormats[iOutputFormat] == format) { + hasFormatBeenHandled = MA_TRUE; + break; + } + } - if (ppClient) { - *ppClient = pClient; - } + /* If we've already handled this format just skip it. */ + if (hasFormatBeenHandled) { + continue; + } - return MA_SUCCESS; -} + uniqueFormats[uniqueFormatCount] = format; + uniqueFormatCount += 1; + /* Sample Rates */ + result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); + if (result != MA_SUCCESS) { + return MA_DEVICE_ENUMERATION_CONTINUE; /* Failed to retrieve sample rates. */ + } -static ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_bool32 cbResult = MA_TRUE; + /* + Core Audio reports a sample rate range. We'll grab the min and max values and report those. + */ + for (iSampleRate = 0; iSampleRate < sampleRateRangeCount; iSampleRate += 1) { + if (minSampleRate > pSampleRateRanges[iSampleRate].mMinimum) { + minSampleRate = pSampleRateRanges[iSampleRate].mMinimum; + } + if (maxSampleRate < pSampleRateRanges[iSampleRate].mMaximum) { + maxSampleRate = pSampleRateRanges[iSampleRate].mMaximum; + } + } - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); - /* Playback. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - deviceInfo.isDefault = MA_TRUE; /* JACK only uses default devices. */ - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - } + ma_device_info_add_native_data_format(&deviceInfo, format, channels, channels, minSampleRate, maxSampleRate); + } - /* Capture. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - deviceInfo.isDefault = MA_TRUE; /* JACK only uses default devices. */ - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + ma_free(pStreamDescriptions, &pContext->allocationCallbacks); } - (void)cbResult; /* For silencing a static analysis warning. */ - - return MA_SUCCESS; + return callback(deviceType, &deviceInfo, pUserData); } - -static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +#else +static ma_device_enumeration_result ma_context_enumerate_device_by_AVAudioSessionPortDescription__coreaudio(ma_context* pContext, AVAudioSessionPortDescription* pPortDesc, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_jack_client_t* pClient; ma_result result; - const char** ppPorts; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + ma_device_info deviceInfo; + AudioComponentDescription desc; + AudioComponent component; + AudioUnit audioUnit; + OSStatus status; + AudioUnitScope formatScope; + AudioUnitElement formatElement; + AudioStreamBasicDescription bestFormat; + UInt32 propSize; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; - MA_ASSERT(pContext != NULL); + MA_ZERO_OBJECT(&deviceInfo); + + /* ID and Name. */ + ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &deviceInfo); + + /* + Default. + + I'm not sure how to do this. I'm going to just assume it's the default device for now. Advice welcome! + */ + deviceInfo.isDefault = MA_TRUE; - if (pDeviceID != NULL && pDeviceID->jack != 0) { - return MA_NO_DEVICE; /* Don't know the device. */ + /* Data Format. */ + /* + Retrieving device information is more annoying on mobile than desktop. For simplicity I'm locking this down to whatever format is + reported on a temporary I/O unit. The problem, however, is that this doesn't return a value for the sample rate which we need to + retrieve from the AVAudioSession shared instance. + */ + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + component = pContextStateCoreAudio->AudioComponentFindNext(NULL, &desc); + if (component == NULL) { + return MA_DEVICE_ENUMERATION_CONTINUE; } - /* Name / Description */ - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + status = pContextStateCoreAudio->AudioComponentInstanceNew(component, &audioUnit); + if (status != noErr) { + return MA_DEVICE_ENUMERATION_CONTINUE; } - /* Jack only uses default devices. */ - pDeviceInfo->isDefault = MA_TRUE; + formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; + formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; + + propSize = sizeof(bestFormat); + status = pContextStateCoreAudio->AudioUnitGetProperty(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit); + return MA_DEVICE_ENUMERATION_CONTINUE; + } - /* Jack only supports f32 and has a specific channel count and sample rate. */ - pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; + pContextStateCoreAudio->AudioComponentInstanceDispose(audioUnit); + audioUnit = NULL; - /* The channel count and sample rate can only be determined by opening the device. */ - result = ma_context_open_client__jack(pContext, &pClient); + /* Only a single format is being reported for iOS. */ + result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &format); if (result != MA_SUCCESS) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); - return result; + return MA_DEVICE_ENUMERATION_CONTINUE; } - pDeviceInfo->nativeDataFormats[0].sampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pClient); - pDeviceInfo->nativeDataFormats[0].channels = 0; + channels = bestFormat.mChannelsPerFrame; - ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ((deviceType == ma_device_type_playback) ? ma_JackPortIsInput : ma_JackPortIsOutput)); - if (ppPorts == NULL) { - ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pClient); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } + /* + It looks like Apple are wanting to push the whole AVAudioSession thing. Thus, we need to use that to determine device settings. To do + this we just get the shared instance and inspect. + */ + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + MA_ASSERT(pAudioSession != NULL); - while (ppPorts[pDeviceInfo->nativeDataFormats[0].channels] != NULL) { - pDeviceInfo->nativeDataFormats[0].channels += 1; + sampleRate = (ma_uint32)pAudioSession.sampleRate; } - pDeviceInfo->nativeDataFormats[0].flags = 0; - pDeviceInfo->nativeDataFormatCount = 1; + ma_device_info_add_native_data_format(&deviceInfo, format, channels, channels, sampleRate, sampleRate); - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); - ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pClient); - - (void)pContext; - return MA_SUCCESS; + return callback(deviceType, &deviceInfo, pUserData); } +#endif - -static ma_result ma_device_uninit__jack(ma_device* pDevice) +static ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_context* pContext; + #if defined(MA_APPLE_DESKTOP) + { + UInt32 deviceCount; + AudioObjectID* pDeviceObjectIDs; + AudioObjectID defaultDeviceObjectIDPlayback; + AudioObjectID defaultDeviceObjectIDCapture; + ma_result result; + UInt32 iDevice; - MA_ASSERT(pDevice != NULL); + ma_find_default_AudioObjectID(pContext, ma_device_type_playback, &defaultDeviceObjectIDPlayback); /* OK if this fails. */ + ma_find_default_AudioObjectID(pContext, ma_device_type_capture, &defaultDeviceObjectIDCapture); /* OK if this fails. */ - pContext = pDevice->pContext; - MA_ASSERT(pContext != NULL); + result = ma_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); + if (result != MA_SUCCESS) { + return result; + } - if (pDevice->jack.pClient != NULL) { - ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pDevice->jack.pClient); - } + for (iDevice = 0; iDevice < deviceCount; ++iDevice) { + AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); - ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); + if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) { + ma_device_enumeration_result cbResult = ma_context_enumerate_device_by_AudioObjectID__coreaudio(pContext, deviceObjectID, defaultDeviceObjectIDPlayback, ma_device_type_playback, callback, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_ABORT) { + break; + } + } + + if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) { + ma_device_enumeration_result cbResult = ma_context_enumerate_device_by_AudioObjectID__coreaudio(pContext, deviceObjectID, defaultDeviceObjectIDCapture, ma_device_type_capture, callback, pUserData); + if (cbResult == MA_DEVICE_ENUMERATION_ABORT) { + break; + } + } + } + + ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); } + #else + { + NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs]; + NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs]; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); - ma_free(pDevice->jack.ppPortsPlayback, &pDevice->pContext->allocationCallbacks); + for (AVAudioSessionPortDescription* pPortDesc in pOutputs) { + if (ma_context_enumerate_device_by_AVAudioSessionPortDescription__coreaudio(pContext, pPortDesc, ma_device_type_playback, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT) { + return MA_SUCCESS; + } + } + + for (AVAudioSessionPortDescription* pPortDesc in pInputs) { + if (ma_context_enumerate_device_by_AVAudioSessionPortDescription__coreaudio(pContext, pPortDesc, ma_device_type_capture, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT) { + return MA_SUCCESS; + } + } } + #endif return MA_SUCCESS; } -static void ma_device__jack_shutdown_callback(void* pUserData) +static AudioBufferList* ma_allocate_AudioBufferList__coreaudio(ma_uint32 sizeInFrames, ma_format format, ma_uint32 channels, ma_stream_layout layout, const ma_allocation_callbacks* pAllocationCallbacks) { - /* JACK died. Stop the device. */ - ma_device* pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + AudioBufferList* pBufferList; + UInt32 audioBufferSizeInBytes; + size_t allocationSize; - ma_device_stop(pDevice); -} + MA_ASSERT(sizeInFrames > 0); + MA_ASSERT(format != ma_format_unknown); + MA_ASSERT(channels > 0); -static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, void* pUserData) -{ - ma_device* pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + allocationSize = sizeof(AudioBufferList) - sizeof(AudioBuffer); /* Subtract sizeof(AudioBuffer) because that part is dynamically sized. */ + if (layout == ma_stream_layout_interleaved) { + /* Interleaved case. This is the simple case because we just have one buffer. */ + allocationSize += sizeof(AudioBuffer) * 1; + } else { + /* Non-interleaved case. This is the more complex case because there's more than one buffer. */ + allocationSize += sizeof(AudioBuffer) * channels; + } - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - size_t newBufferSize = frameCount * (pDevice->capture.internalChannels * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); - float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); - if (pNewBuffer == NULL) { - return MA_OUT_OF_MEMORY; - } + allocationSize += sizeInFrames * ma_get_bytes_per_frame(format, channels); - ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + pBufferList = (AudioBufferList*)ma_malloc(allocationSize, pAllocationCallbacks); + if (pBufferList == NULL) { + return NULL; + } - pDevice->jack.pIntermediaryBufferCapture = pNewBuffer; - pDevice->playback.internalPeriodSizeInFrames = frameCount; + audioBufferSizeInBytes = (UInt32)(sizeInFrames * ma_get_bytes_per_sample(format)); + + if (layout == ma_stream_layout_interleaved) { + pBufferList->mNumberBuffers = 1; + pBufferList->mBuffers[0].mNumberChannels = channels; + pBufferList->mBuffers[0].mDataByteSize = audioBufferSizeInBytes * channels; + pBufferList->mBuffers[0].mData = (ma_uint8*)pBufferList + sizeof(AudioBufferList); + } else { + ma_uint32 iBuffer; + pBufferList->mNumberBuffers = channels; + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + pBufferList->mBuffers[iBuffer].mNumberChannels = 1; + pBufferList->mBuffers[iBuffer].mDataByteSize = audioBufferSizeInBytes; + pBufferList->mBuffers[iBuffer].mData = (ma_uint8*)pBufferList + ((sizeof(AudioBufferList) - sizeof(AudioBuffer)) + (sizeof(AudioBuffer) * channels)) + (audioBufferSizeInBytes * iBuffer); + } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - size_t newBufferSize = frameCount * (pDevice->playback.internalChannels * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); - float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); - if (pNewBuffer == NULL) { + return pBufferList; +} + +static ma_result ma_device_realloc_AudioBufferList__coreaudio(ma_device* pDevice, ma_uint32 sizeInFrames, ma_format format, ma_uint32 channels, ma_stream_layout layout) +{ + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + + MA_ASSERT(pDevice != NULL); + MA_ASSERT(format != ma_format_unknown); + MA_ASSERT(channels > 0); + + /* Only resize the buffer if necessary. */ + if (pDeviceStateCoreAudio->audioBufferCapInFrames < sizeInFrames) { + AudioBufferList* pNewAudioBufferList; + + pNewAudioBufferList = ma_allocate_AudioBufferList__coreaudio(sizeInFrames, format, channels, layout, ma_device_get_allocation_callbacks(pDevice)); + if (pNewAudioBufferList == NULL) { return MA_OUT_OF_MEMORY; } - ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); - - pDevice->jack.pIntermediaryBufferPlayback = pNewBuffer; - pDevice->playback.internalPeriodSizeInFrames = frameCount; + /* At this point we'll have a new AudioBufferList and we can free the old one. */ + ma_free(pDeviceStateCoreAudio->pAudioBufferList, ma_device_get_allocation_callbacks(pDevice)); + pDeviceStateCoreAudio->pAudioBufferList = pNewAudioBufferList; + pDeviceStateCoreAudio->audioBufferCapInFrames = sizeInFrames; } - return 0; + /* Getting here means the capacity of the audio is fine. */ + return MA_SUCCESS; } -static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* pUserData) + +static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pBufferList) { - ma_device* pDevice; - ma_context* pContext; - ma_uint32 iChannel; + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + ma_stream_layout layout; - pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - pContext = pDevice->pContext; - MA_ASSERT(pContext != NULL); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d", (int)busNumber, (int)frameCount, (int)pBufferList->mNumberBuffers);*/ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - /* Channels need to be interleaved. */ - for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { - const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[iChannel], frameCount); - if (pSrc != NULL) { - float* pDst = pDevice->jack.pIntermediaryBufferCapture + iChannel; - ma_jack_nframes_t iFrame; - for (iFrame = 0; iFrame < frameCount; ++iFrame) { - *pDst = *pSrc; + /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ + layout = ma_stream_layout_interleaved; + if (pBufferList->mBuffers[0].mNumberChannels != pDevice->playback.internalChannels) { + layout = ma_stream_layout_deinterleaved; + } - pDst += pDevice->capture.internalChannels; - pSrc += 1; + /* + I've observed with iOS that the callback can be fired with inconsistent frame counts, differing from the + reported period size which is what our internal buffer would have been initially sized with. We need to + check what's reported and then resize the buffer as necessary. + */ + { + ma_uint32 totalFrameCount = 0; + + if (layout == ma_stream_layout_interleaved) { + UInt32 iBuffer; + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + totalFrameCount += pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pBufferList->mBuffers[iBuffer].mNumberChannels); + } + } else { + if ((pBufferList->mNumberBuffers % pDevice->playback.internalChannels) == 0) { + UInt32 iBuffer; + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; iBuffer += pDevice->playback.internalChannels) { + totalFrameCount += pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_sample(pDevice->playback.internalFormat); /* <-- Note the use of ma_get_bytes_per_sample() instead of ma_get_bytes_per_frame(). This is a single-channel non-interleaved buffer. */ } } } - ma_device_handle_backend_data_callback(pDevice, NULL, pDevice->jack.pIntermediaryBufferCapture, frameCount); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INTERLEAVED: totalFrameCount=%d", (int)totalFrameCount);*/ + + if (pDeviceStateCoreAudio->async.playback.frameCap < totalFrameCount) { + ma_device_state_async_resize(&pDeviceStateCoreAudio->async, totalFrameCount, pDeviceStateCoreAudio->async.capture.frameCap, ma_device_get_allocation_callbacks(pDevice)); + } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_device_handle_backend_data_callback(pDevice, pDevice->jack.pIntermediaryBufferPlayback, NULL, frameCount); + if (layout == ma_stream_layout_interleaved) { + /* For now we can assume everything is interleaved. */ + UInt32 iBuffer; + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->playback.internalChannels) { + ma_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + if (frameCountForThisBuffer > 0) { + ma_device_state_async_process(&pDeviceStateCoreAudio->async, pDevice, pBufferList->mBuffers[iBuffer].mData, NULL, frameCountForThisBuffer); + /*ma_device_handle_backend_data_callback(pDevice, pBufferList->mBuffers[iBuffer].mData, NULL, frameCountForThisBuffer);*/ + } - /* Channels need to be deinterleaved. */ - for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { - float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[iChannel], frameCount); - if (pDst != NULL) { - const float* pSrc = pDevice->jack.pIntermediaryBufferPlayback + iChannel; - ma_jack_nframes_t iFrame; - for (iFrame = 0; iFrame < frameCount; ++iFrame) { - *pDst = *pSrc; + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INTERLEAVED: frameCount=%d, mNumberChannels=%d, mDataByteSize=%d", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ + } else { + /* + This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + not interleaved, in which case we can't handle right now since miniaudio does not yet support non-interleaved streams. We just + output silence here. + */ + MA_ZERO_MEMORY(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ + } + } + } else { + /* This is the deinterleaved case. We need to update each buffer in groups of internalChannels. This assumes each buffer is the same size. */ + MA_ASSERT(pDevice->playback.internalChannels <= MA_MAX_CHANNELS); /* This should have been validated at initialization time. */ - pDst += 1; - pSrc += pDevice->playback.internalChannels; + /* + For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something + very strange has happened and we're not going to support it. + */ + if ((pBufferList->mNumberBuffers % pDevice->playback.internalChannels) == 0) { + ma_uint8 tempBuffer[4096]; + UInt32 iBuffer; + + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; iBuffer += pDevice->playback.internalChannels) { + ma_uint32 frameCountPerBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_sample(pDevice->playback.internalFormat); + ma_uint32 framesRemaining = frameCountPerBuffer; + + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ + + while (framesRemaining > 0) { + void* ppDeinterleavedBuffers[MA_MAX_CHANNELS]; + ma_uint32 iChannel; + ma_uint32 framesToRead = sizeof(tempBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + if (framesToRead > framesRemaining) { + framesToRead = framesRemaining; + } + + ma_device_state_async_process(&pDeviceStateCoreAudio->async, pDevice, tempBuffer, NULL, framesToRead); + /*ma_device_handle_backend_data_callback(pDevice, tempBuffer, NULL, framesToRead);*/ + + for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pBufferList->mBuffers[iBuffer+iChannel].mData, (frameCountPerBuffer - framesRemaining) * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); + } + + ma_deinterleave_pcm_frames(pDevice->playback.internalFormat, pDevice->playback.internalChannels, framesToRead, tempBuffer, ppDeinterleavedBuffers); + + framesRemaining -= framesToRead; } } } } - return 0; + (void)pActionFlags; + (void)pTimeStamp; + (void)busNumber; + (void)frameCount; + + return noErr; } -static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pUnusedBufferList) { + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); + AudioBufferList* pRenderedBufferList; ma_result result; - ma_uint32 periodSizeInFrames; + ma_stream_layout layout; + ma_uint32 iBuffer; + OSStatus status; - MA_ASSERT(pConfig != NULL); MA_ASSERT(pDevice != NULL); - if (pConfig->deviceType == ma_device_type_loopback) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Loopback mode not supported."); - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + pRenderedBufferList = (AudioBufferList*)pDeviceStateCoreAudio->pAudioBufferList; + MA_ASSERT(pRenderedBufferList); - /* Only supporting default devices with JACK. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->pDeviceID != NULL && pDescriptorPlayback->pDeviceID->jack != 0) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->pDeviceID != NULL && pDescriptorCapture->pDeviceID->jack != 0)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Only default devices are supported."); - return MA_NO_DEVICE; + /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ + layout = ma_stream_layout_interleaved; + if (pRenderedBufferList->mBuffers[0].mNumberChannels != pDevice->capture.internalChannels) { + layout = ma_stream_layout_deinterleaved; } - /* No exclusive mode with the JACK backend. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Exclusive mode not supported."); - return MA_SHARE_MODE_NOT_SUPPORTED; - } + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d", (int)busNumber, (int)frameCount, (int)pRenderedBufferList->mNumberBuffers);*/ - /* Open the client. */ - result = ma_context_open_client__jack(pDevice->pContext, (ma_jack_client_t**)&pDevice->jack.pClient); + /* + There has been a situation reported where frame count passed into this function is greater than the capacity of + our capture buffer. There doesn't seem to be a reliable way to determine what the maximum frame count will be, + so we need to instead resort to dynamically reallocating our buffer to ensure it's large enough to capture the + number of frames requested by this callback. + */ + result = ma_device_realloc_AudioBufferList__coreaudio(pDevice, frameCount, pDevice->capture.internalFormat, pDevice->capture.internalChannels, layout); if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); - return result; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed to allocate AudioBufferList for capture."); + return noErr; } - /* Callbacks. */ - if (((ma_jack_set_process_callback_proc)pDevice->pContext->jack.jack_set_process_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_process_callback, pDevice) != 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + pRenderedBufferList = (AudioBufferList*)pDeviceStateCoreAudio->pAudioBufferList; + MA_ASSERT(pRenderedBufferList); + + /* + When you call AudioUnitRender(), Core Audio tries to be helpful by setting the mDataByteSize to the number of bytes + that were actually rendered. The problem with this is that the next call can fail with -50 due to the size no longer + being set to the capacity of the buffer, but instead the size in bytes of the previous render. This will cause a + problem when a future call to this callback specifies a larger number of frames. + + To work around this we need to explicitly set the size of each buffer to their respective size in bytes. + */ + for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { + pRenderedBufferList->mBuffers[iBuffer].mDataByteSize = pDeviceStateCoreAudio->audioBufferCapInFrames * ma_get_bytes_per_sample(pDevice->capture.internalFormat) * pRenderedBufferList->mBuffers[iBuffer].mNumberChannels; + /*printf("DEBUG: nDataByteSize = %d\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } - if (((ma_jack_set_buffer_size_callback_proc)pDevice->pContext->jack.jack_set_buffer_size_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_buffer_size_callback, pDevice) != 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + + status = pContextStateCoreAudio->AudioUnitRender(pDeviceStateCoreAudio->audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); + if (status != noErr) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "ERROR: AudioUnitRender() failed with %d.", (int)status); + return status; } - ((ma_jack_on_shutdown_proc)pDevice->pContext->jack.jack_on_shutdown)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_shutdown_callback, pDevice); + /* Like with playback we need to make sure our internal buffer is big enough for what's reported by Core Audio. */ + { + ma_uint32 totalFrameCount = 0; + if (layout == ma_stream_layout_interleaved) { + UInt32 iBuffer; + for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { + totalFrameCount += frameCount; + } + } else { + if ((pRenderedBufferList->mNumberBuffers % pDevice->capture.internalChannels) == 0) { + UInt32 iBuffer; + for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; iBuffer += pDevice->capture.internalChannels) { + totalFrameCount += frameCount; + } + } + } - /* The buffer size in frames can change. */ - periodSizeInFrames = ((ma_jack_get_buffer_size_proc)pDevice->pContext->jack.jack_get_buffer_size)((ma_jack_client_t*)pDevice->jack.pClient); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INTERLEAVED: totalFrameCount=%d", (int)totalFrameCount);*/ - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_uint32 iPort; - const char** ppPorts; + if (pDeviceStateCoreAudio->async.capture.frameCap < totalFrameCount) { + ma_device_state_async_resize(&pDeviceStateCoreAudio->async, pDeviceStateCoreAudio->async.playback.frameCap, totalFrameCount, ma_device_get_allocation_callbacks(pDevice)); + } + } - pDescriptorCapture->format = ma_format_f32; - pDescriptorCapture->channels = 0; - pDescriptorCapture->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); + if (layout == ma_stream_layout_interleaved) { + for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { + if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->capture.internalChannels) { + ma_device_state_async_process(&pDeviceStateCoreAudio->async, pDevice, NULL, pRenderedBufferList->mBuffers[iBuffer].mData, frameCount); + /*ma_device_handle_backend_data_callback(pDevice, NULL, pRenderedBufferList->mBuffers[iBuffer].mData, frameCount);*/ + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d.", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ + } else { + /* + This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's + not interleaved, in which case we can't handle right now since miniaudio does not yet support non-interleaved streams. + */ + ma_uint8 silentBuffer[4096]; + ma_uint32 framesRemaining; - ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); - if (ppPorts == NULL) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } + MA_ZERO_MEMORY(silentBuffer, sizeof(silentBuffer)); - /* Need to count the number of ports first so we can allocate some memory. */ - while (ppPorts[pDescriptorCapture->channels] != NULL) { - pDescriptorCapture->channels += 1; - } + framesRemaining = frameCount; + while (framesRemaining > 0) { + ma_uint32 framesToSend = sizeof(silentBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + if (framesToSend > framesRemaining) { + framesToSend = framesRemaining; + } - pDevice->jack.ppPortsCapture = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsCapture) * pDescriptorCapture->channels, &pDevice->pContext->allocationCallbacks); - if (pDevice->jack.ppPortsCapture == NULL) { - return MA_OUT_OF_MEMORY; - } + ma_device_state_async_process(&pDeviceStateCoreAudio->async, pDevice, NULL, silentBuffer, framesToSend); + /*ma_device_handle_backend_data_callback(pDevice, NULL, silentBuffer, framesToSend);*/ - for (iPort = 0; iPort < pDescriptorCapture->channels; iPort += 1) { - char name[64]; - ma_strcpy_s(name, sizeof(name), "capture"); - ma_itoa_s((int)iPort, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ + framesRemaining -= framesToSend; + } - pDevice->jack.ppPortsCapture[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); - if (pDevice->jack.ppPortsCapture[iPort] == NULL) { - ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); - ma_device_uninit__jack(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d", (int)frameCount, (int)pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } } + } else { + /* This is the deinterleaved case. We need to interleave the audio data before sending it to the client. This assumes each buffer is the same size. */ + MA_ASSERT(pDevice->capture.internalChannels <= MA_MAX_CHANNELS); /* This should have been validated at initialization time. */ + + /* + For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something + very strange has happened and we're not going to support it. + */ + if ((pRenderedBufferList->mNumberBuffers % pDevice->capture.internalChannels) == 0) { + ma_uint8 tempBuffer[4096]; + for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; iBuffer += pDevice->capture.internalChannels) { + ma_uint32 framesRemaining = frameCount; + while (framesRemaining > 0) { + void* ppDeinterleavedBuffers[MA_MAX_CHANNELS]; + ma_uint32 iChannel; + ma_uint32 framesToSend = sizeof(tempBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + if (framesToSend > framesRemaining) { + framesToSend = framesRemaining; + } - ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); + for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { + ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pRenderedBufferList->mBuffers[iBuffer+iChannel].mData, (frameCount - framesRemaining) * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); + } - pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; - pDescriptorCapture->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ + ma_interleave_pcm_frames(pDevice->capture.internalFormat, pDevice->capture.internalChannels, framesToSend, (const void**)ppDeinterleavedBuffers, tempBuffer); + + ma_device_state_async_process(&pDeviceStateCoreAudio->async, pDevice, NULL, tempBuffer, framesToSend); + /*ma_device_handle_backend_data_callback(pDevice, NULL, tempBuffer, framesToSend);*/ - pDevice->jack.pIntermediaryBufferCapture = (float*)ma_calloc(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); - if (pDevice->jack.pIntermediaryBufferCapture == NULL) { - ma_device_uninit__jack(pDevice); - return MA_OUT_OF_MEMORY; + framesRemaining -= framesToSend; + } + } } } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_uint32 iPort; - const char** ppPorts; + (void)pActionFlags; + (void)pTimeStamp; + (void)busNumber; + (void)frameCount; + (void)pUnusedBufferList; - pDescriptorPlayback->format = ma_format_f32; - pDescriptorPlayback->channels = 0; - pDescriptorPlayback->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); + return noErr; +} - ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); - if (ppPorts == NULL) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } +static void ma_on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPropertyID propertyID, AudioUnitScope scope, AudioUnitElement element) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); - /* Need to count the number of ports first so we can allocate some memory. */ - while (ppPorts[pDescriptorPlayback->channels] != NULL) { - pDescriptorPlayback->channels += 1; - } + MA_ASSERT(pDevice != NULL); - pDevice->jack.ppPortsPlayback = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsPlayback) * pDescriptorPlayback->channels, &pDevice->pContext->allocationCallbacks); - if (pDevice->jack.ppPortsPlayback == NULL) { - ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); - return MA_OUT_OF_MEMORY; + /* Don't do anything if it looks like we're just reinitializing due to a device switch. */ + if (((audioUnit == pDeviceStateCoreAudio->audioUnitPlayback) && ma_atomic_bool32_get(&pDeviceStateCoreAudio->isSwitchingPlaybackDevice)) || + ((audioUnit == pDeviceStateCoreAudio->audioUnitCapture) && ma_atomic_bool32_get(&pDeviceStateCoreAudio->isSwitchingCaptureDevice))) { + return; + } + + /* + There's been a report of a deadlock here when triggered by ma_device_uninit(). It looks like + AudioUnitGetProprty (called below) and AudioComponentInstanceDispose (called in ma_device_uninit) + can try waiting on the same lock. I'm going to try working around this by not calling any Core + Audio APIs in the callback when the device has been stopped or uninitialized. + */ + if (ma_device_get_status(pDevice) == ma_device_status_uninitialized || ma_device_get_status(pDevice) == ma_device_status_stopping || ma_device_get_status(pDevice) == ma_device_status_stopped) { + /* Device not started. Do nothing. See note just above ^. */ + } else { + UInt32 isRunning; + UInt32 isRunningSize = sizeof(isRunning); + OSStatus status = pContextStateCoreAudio->AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); + if (status != noErr) { + goto done; /* Don't really know what to do in this case... just ignore it, I suppose... */ } - for (iPort = 0; iPort < pDescriptorPlayback->channels; iPort += 1) { - char name[64]; - ma_strcpy_s(name, sizeof(name), "playback"); - ma_itoa_s((int)iPort, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ + if (!isRunning) { + /* + The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: - pDevice->jack.ppPortsPlayback[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); - if (pDevice->jack.ppPortsPlayback[iPort] == NULL) { - ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); - ma_device_uninit__jack(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } - } + 1) When the device is unplugged, this will be called _before_ the default device change notification. + 2) When the device is changed via the default device change notification, this will be called _after_ the switch. - ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); + For case #1, we just check if there's a new default device available. If so, we just ignore the stop event. For case #2 we check a flag. + */ + if (((audioUnit == pDeviceStateCoreAudio->audioUnitPlayback) && pDeviceStateCoreAudio->isDefaultPlaybackDevice) || + ((audioUnit == pDeviceStateCoreAudio->audioUnitCapture) && pDeviceStateCoreAudio->isDefaultCaptureDevice)) { + /* + It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device + via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the + device to be seamless to the client (we don't want them receiving the stopped event and thinking that the device has stopped when it + hasn't!). + */ + if (((audioUnit == pDeviceStateCoreAudio->audioUnitPlayback) && ma_atomic_bool32_get(&pDeviceStateCoreAudio->isSwitchingPlaybackDevice)) || + ((audioUnit == pDeviceStateCoreAudio->audioUnitCapture) && ma_atomic_bool32_get(&pDeviceStateCoreAudio->isSwitchingCaptureDevice))) { + goto done; + } - pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; - pDescriptorPlayback->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ + /* + Getting here means the device is not reinitializing which means it may have been unplugged. From what I can see, it looks like Core Audio + will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most + likely be successful in switching to the new device. - pDevice->jack.pIntermediaryBufferPlayback = (float*)ma_calloc(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); - if (pDevice->jack.pIntermediaryBufferPlayback == NULL) { - ma_device_uninit__jack(pDevice); - return MA_OUT_OF_MEMORY; + TODO: Try to predict if Core Audio will switch devices. If not, the stopped callback needs to be posted. + */ + goto done; + } } } - return MA_SUCCESS; + (void)propertyID; /* Unused. */ + +done: + /* Always signal the stop event. It's possible for the "else" case to get hit which can happen during an interruption. */ + ma_event_signal(&pDeviceStateCoreAudio->stopEvent); } +#if defined(MA_APPLE_MOBILE) +@interface ma_ios_notification_handler:NSObject { + ma_device* m_pDevice; +} +@end -static ma_result ma_device_start__jack(ma_device* pDevice) +@implementation ma_ios_notification_handler +-(id)init:(ma_device*)pDevice { - ma_context* pContext = pDevice->pContext; - int resultJACK; - size_t i; + self = [super init]; + m_pDevice = pDevice; - resultJACK = ((ma_jack_activate_proc)pContext->jack.jack_activate)((ma_jack_client_t*)pDevice->jack.pClient); - if (resultJACK != 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client."); - return MA_FAILED_TO_START_BACKEND_DEVICE; - } + /* For route changes. */ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_route_change:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]]; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); - if (ppServerPorts == NULL) { - ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); - return MA_ERROR; - } + /* For interruptions. */ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; - for (i = 0; ppServerPorts[i] != NULL; ++i) { - const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[i]); + return self; +} - resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); - if (resultJACK != 0) { - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); - ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); - return MA_ERROR; - } - } +-(void)dealloc +{ + [self remove_handler]; + + #if defined(__has_feature) + #if !__has_feature(objc_arc) + [super dealloc]; + #endif + #endif +} - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); - } +-(void)remove_handler +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil]; +} - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); - if (ppServerPorts == NULL) { - ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); - return MA_ERROR; - } +-(void)handle_interruption:(NSNotification*)pNotification +{ + NSInteger type = [[[pNotification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue]; + switch (type) + { + case AVAudioSessionInterruptionTypeBegan: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeBegan"); - for (i = 0; ppServerPorts[i] != NULL; ++i) { - const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[i]); + /* + Core Audio will have stopped the internal device automatically, but we need explicitly + stop it at a higher level to ensure miniaudio-specific state is updated for consistency. + */ + ma_device_stop(m_pDevice); - resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); - if (resultJACK != 0) { - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); - ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); - return MA_ERROR; - } - } + /* + Fire the notification after the device has been stopped to ensure it's in the correct + state when the notification handler is invoked. + */ + ma_device_post_notification_interruption_began(m_pDevice); + } break; - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); + case AVAudioSessionInterruptionTypeEnded: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeEnded"); + ma_device_post_notification_interruption_ended(m_pDevice); + } break; } - - return MA_SUCCESS; } -static ma_result ma_device_stop__jack(ma_device* pDevice) +-(void)handle_route_change:(NSNotification*)pNotification { - ma_context* pContext = pDevice->pContext; + AVAudioSession* pSession = [AVAudioSession sharedInstance]; - if (((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient) != 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client."); - return MA_ERROR; - } + NSInteger reason = [[[pNotification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; + switch (reason) + { + case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); + } break; + + case AVAudioSessionRouteChangeReasonNewDeviceAvailable: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNewDeviceAvailable"); + } break; - ma_device__on_notification_stopped(pDevice); + case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory"); + } break; - return MA_SUCCESS; -} + case AVAudioSessionRouteChangeReasonWakeFromSleep: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonWakeFromSleep"); + } break; + case AVAudioSessionRouteChangeReasonOverride: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOverride"); + } break; -static ma_result ma_context_uninit__jack(ma_context* pContext) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_jack); + case AVAudioSessionRouteChangeReasonCategoryChange: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonCategoryChange"); + } break; - ma_free(pContext->jack.pClientName, &pContext->allocationCallbacks); - pContext->jack.pClientName = NULL; + case AVAudioSessionRouteChangeReasonUnknown: + default: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonUnknown"); + } break; + } -#ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(ma_context_get_log(pContext), pContext->jack.jackSO); -#endif + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Changing Route. inputNumberChannels=%d; outputNumberOfChannels=%d", (int)pSession.inputNumberOfChannels, (int)pSession.outputNumberOfChannels); - return MA_SUCCESS; + /* Let the application know about the route change. */ + ma_device_post_notification_rerouted(m_pDevice); } - -static ma_result ma_context_init__jack(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) -{ -#ifndef MA_NO_RUNTIME_LINKING - const char* libjackNames[] = { -#if defined(MA_WIN32) - "libjack.dll", - "libjack64.dll" -#endif -#if defined(MA_UNIX) - "libjack.so", - "libjack.so.0" +@end #endif - }; - size_t i; - for (i = 0; i < ma_countof(libjackNames); ++i) { - pContext->jack.jackSO = ma_dlopen(ma_context_get_log(pContext), libjackNames[i]); - if (pContext->jack.jackSO != NULL) { - break; - } - } +static void ma_device_uninit__coreaudio(ma_device* pDevice) +{ + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); - if (pContext->jack.jackSO == NULL) { - return MA_NO_BACKEND; + #if defined(MA_APPLE_DESKTOP) + { + /* + Make sure we're no longer tracking the device. It doesn't matter if we call this for a non-default device because it'll + just gracefully ignore it. + */ + ma_device__untrack__coreaudio(pDevice); } - - pContext->jack.jack_client_open = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_client_open"); - pContext->jack.jack_client_close = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_client_close"); - pContext->jack.jack_client_name_size = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_client_name_size"); - pContext->jack.jack_set_process_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_set_process_callback"); - pContext->jack.jack_set_buffer_size_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_set_buffer_size_callback"); - pContext->jack.jack_on_shutdown = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_on_shutdown"); - pContext->jack.jack_get_sample_rate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_get_sample_rate"); - pContext->jack.jack_get_buffer_size = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_get_buffer_size"); - pContext->jack.jack_get_ports = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_get_ports"); - pContext->jack.jack_activate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_activate"); - pContext->jack.jack_deactivate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_deactivate"); - pContext->jack.jack_connect = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_connect"); - pContext->jack.jack_port_register = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_port_register"); - pContext->jack.jack_port_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_port_name"); - pContext->jack.jack_port_get_buffer = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_port_get_buffer"); - pContext->jack.jack_free = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->jack.jackSO, "jack_free"); -#else - /* - This strange assignment system is here just to ensure type safety of miniaudio's function pointer - types. If anything differs slightly the compiler should throw a warning. - */ - ma_jack_client_open_proc _jack_client_open = jack_client_open; - ma_jack_client_close_proc _jack_client_close = jack_client_close; - ma_jack_client_name_size_proc _jack_client_name_size = jack_client_name_size; - ma_jack_set_process_callback_proc _jack_set_process_callback = jack_set_process_callback; - ma_jack_set_buffer_size_callback_proc _jack_set_buffer_size_callback = jack_set_buffer_size_callback; - ma_jack_on_shutdown_proc _jack_on_shutdown = jack_on_shutdown; - ma_jack_get_sample_rate_proc _jack_get_sample_rate = jack_get_sample_rate; - ma_jack_get_buffer_size_proc _jack_get_buffer_size = jack_get_buffer_size; - ma_jack_get_ports_proc _jack_get_ports = jack_get_ports; - ma_jack_activate_proc _jack_activate = jack_activate; - ma_jack_deactivate_proc _jack_deactivate = jack_deactivate; - ma_jack_connect_proc _jack_connect = jack_connect; - ma_jack_port_register_proc _jack_port_register = jack_port_register; - ma_jack_port_name_proc _jack_port_name = jack_port_name; - ma_jack_port_get_buffer_proc _jack_port_get_buffer = jack_port_get_buffer; - ma_jack_free_proc _jack_free = jack_free; - - pContext->jack.jack_client_open = (ma_proc)_jack_client_open; - pContext->jack.jack_client_close = (ma_proc)_jack_client_close; - pContext->jack.jack_client_name_size = (ma_proc)_jack_client_name_size; - pContext->jack.jack_set_process_callback = (ma_proc)_jack_set_process_callback; - pContext->jack.jack_set_buffer_size_callback = (ma_proc)_jack_set_buffer_size_callback; - pContext->jack.jack_on_shutdown = (ma_proc)_jack_on_shutdown; - pContext->jack.jack_get_sample_rate = (ma_proc)_jack_get_sample_rate; - pContext->jack.jack_get_buffer_size = (ma_proc)_jack_get_buffer_size; - pContext->jack.jack_get_ports = (ma_proc)_jack_get_ports; - pContext->jack.jack_activate = (ma_proc)_jack_activate; - pContext->jack.jack_deactivate = (ma_proc)_jack_deactivate; - pContext->jack.jack_connect = (ma_proc)_jack_connect; - pContext->jack.jack_port_register = (ma_proc)_jack_port_register; - pContext->jack.jack_port_name = (ma_proc)_jack_port_name; - pContext->jack.jack_port_get_buffer = (ma_proc)_jack_port_get_buffer; - pContext->jack.jack_free = (ma_proc)_jack_free; -#endif - - if (pConfig->jack.pClientName != NULL) { - pContext->jack.pClientName = ma_copy_string(pConfig->jack.pClientName, &pContext->allocationCallbacks); - } - pContext->jack.tryStartServer = pConfig->jack.tryStartServer; - - /* - Getting here means the JACK library is installed, but it doesn't necessarily mean it's usable. We need to quickly test this by connecting - a temporary client. - */ + #endif + #if defined(MA_APPLE_MOBILE) { - ma_jack_client_t* pDummyClient; - ma_result result = ma_context_open_client__jack(pContext, &pDummyClient); - if (result != MA_SUCCESS) { - ma_free(pContext->jack.pClientName, &pContext->allocationCallbacks); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(ma_context_get_log(pContext), pContext->jack.jackSO); - #endif - return MA_NO_BACKEND; + if (pDeviceStateCoreAudio->pNotificationHandler != NULL) { + ma_ios_notification_handler* pNotificationHandler = (MA_BRIDGE_TRANSFER ma_ios_notification_handler*)pDeviceStateCoreAudio->pNotificationHandler; + [pNotificationHandler remove_handler]; } + } + #endif - ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pDummyClient); + if (pDeviceStateCoreAudio->audioUnitCapture != NULL) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pDeviceStateCoreAudio->audioUnitCapture); + } + if (pDeviceStateCoreAudio->audioUnitPlayback != NULL) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pDeviceStateCoreAudio->audioUnitPlayback); } + if (pDeviceStateCoreAudio->pAudioBufferList) { + ma_free(pDeviceStateCoreAudio->pAudioBufferList, ma_device_get_allocation_callbacks(pDevice)); + } - pCallbacks->onContextInit = ma_context_init__jack; - pCallbacks->onContextUninit = ma_context_uninit__jack; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__jack; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__jack; - pCallbacks->onDeviceInit = ma_device_init__jack; - pCallbacks->onDeviceUninit = ma_device_uninit__jack; - pCallbacks->onDeviceStart = ma_device_start__jack; - pCallbacks->onDeviceStop = ma_device_stop__jack; - pCallbacks->onDeviceRead = NULL; /* Not used because JACK is asynchronous. */ - pCallbacks->onDeviceWrite = NULL; /* Not used because JACK is asynchronous. */ - pCallbacks->onDeviceDataLoop = NULL; /* Not used because JACK is asynchronous. */ + ma_device_state_async_uninit(&pDeviceStateCoreAudio->async, ma_device_get_allocation_callbacks(pDevice)); - return MA_SUCCESS; + ma_free(pDeviceStateCoreAudio, ma_device_get_allocation_callbacks(pDevice)); } -#endif /* MA_HAS_JACK */ - - - -/****************************************************************************** - -Core Audio Backend -References -========== -- Technical Note TN2091: Device input using the HAL Output Audio Unit - https://developer.apple.com/library/archive/technotes/tn2091/_index.html - -******************************************************************************/ -#ifdef MA_HAS_COREAUDIO -#include +typedef struct +{ + ma_bool32 allowNominalSampleRateChange; -#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1 - #define MA_APPLE_MOBILE - #if defined(TARGET_OS_TV) && TARGET_OS_TV == 1 - #define MA_APPLE_TV - #endif - #if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 - #define MA_APPLE_WATCH - #endif - #if __has_feature(objc_arc) - #define MA_BRIDGE_TRANSFER __bridge_transfer - #define MA_BRIDGE_RETAINED __bridge_retained - #else - #define MA_BRIDGE_TRANSFER - #define MA_BRIDGE_RETAINED - #endif -#else - #define MA_APPLE_DESKTOP -#endif + /* Input. */ + ma_format formatIn; + ma_uint32 channelsIn; + ma_uint32 sampleRateIn; + ma_channel channelMapIn[MA_MAX_CHANNELS]; + ma_uint32 periodSizeInFramesIn; + ma_uint32 periodSizeInMillisecondsIn; + ma_uint32 periodsIn; + ma_share_mode shareMode; + ma_bool32 registerStopEvent; + /* Output. */ #if defined(MA_APPLE_DESKTOP) -#include -#else -#include + AudioObjectID deviceObjectID; #endif + AudioComponent component; + AudioUnit audioUnit; + AudioBufferList* pAudioBufferList; /* Only used for input devices. */ + ma_format formatOut; + ma_uint32 channelsOut; + ma_uint32 sampleRateOut; + ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_uint32 periodSizeInFramesOut; + ma_uint32 periodsOut; + char deviceName[256]; +} ma_device_init_internal_data__coreaudio; -#include - -/* CoreFoundation */ -typedef Boolean (* ma_CFStringGetCString_proc)(CFStringRef theString, char* buffer, CFIndex bufferSize, CFStringEncoding encoding); -typedef void (* ma_CFRelease_proc)(CFTypeRef cf); - -/* CoreAudio */ +static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_init_internal_data__coreaudio* pData, void* pDevice_DoNotReference) /* <-- pDevice is typed as void* intentionally so as to avoid accidentally referencing it. */ +{ + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(pContext); + ma_result result = MA_SUCCESS; + OSStatus status; + UInt32 enableIOFlag; + AudioStreamBasicDescription bestFormat; + ma_uint32 actualPeriodSizeInFrames; + AURenderCallbackStruct callbackInfo; #if defined(MA_APPLE_DESKTOP) -typedef OSStatus (* ma_AudioObjectGetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* ioDataSize, void* outData); -typedef OSStatus (* ma_AudioObjectGetPropertyDataSize_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32* outDataSize); -typedef OSStatus (* ma_AudioObjectSetPropertyData_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, UInt32 inQualifierDataSize, const void* inQualifierData, UInt32 inDataSize, const void* inData); -typedef OSStatus (* ma_AudioObjectAddPropertyListener_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, AudioObjectPropertyListenerProc inListener, void* inClientData); -typedef OSStatus (* ma_AudioObjectRemovePropertyListener_proc)(AudioObjectID inObjectID, const AudioObjectPropertyAddress* inAddress, AudioObjectPropertyListenerProc inListener, void* inClientData); + AudioObjectID deviceObjectID; #endif -/* AudioToolbox */ -typedef AudioComponent (* ma_AudioComponentFindNext_proc)(AudioComponent inComponent, const AudioComponentDescription* inDesc); -typedef OSStatus (* ma_AudioComponentInstanceDispose_proc)(AudioComponentInstance inInstance); -typedef OSStatus (* ma_AudioComponentInstanceNew_proc)(AudioComponent inComponent, AudioComponentInstance* outInstance); -typedef OSStatus (* ma_AudioOutputUnitStart_proc)(AudioUnit inUnit); -typedef OSStatus (* ma_AudioOutputUnitStop_proc)(AudioUnit inUnit); -typedef OSStatus (* ma_AudioUnitAddPropertyListener_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitPropertyListenerProc inProc, void* inProcUserData); -typedef OSStatus (* ma_AudioUnitGetPropertyInfo_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, UInt32* outDataSize, Boolean* outWriteable); -typedef OSStatus (* ma_AudioUnitGetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void* outData, UInt32* ioDataSize); -typedef OSStatus (* ma_AudioUnitSetProperty_proc)(AudioUnit inUnit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, const void* inData, UInt32 inDataSize); -typedef OSStatus (* ma_AudioUnitInitialize_proc)(AudioUnit inUnit); -typedef OSStatus (* ma_AudioUnitRender_proc)(AudioUnit inUnit, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData); - + /* This API should only be used for a single device type: playback or capture. No full-duplex mode. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } -#define MA_COREAUDIO_OUTPUT_BUS 0 -#define MA_COREAUDIO_INPUT_BUS 1 + MA_ASSERT(pContext != NULL); + MA_ASSERT(deviceType == ma_device_type_playback || deviceType == ma_device_type_capture); #if defined(MA_APPLE_DESKTOP) -static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_device_type deviceType, ma_bool32 disposePreviousAudioUnit); + pData->deviceObjectID = 0; #endif + pData->component = NULL; + pData->audioUnit = NULL; + pData->pAudioBufferList = NULL; -/* -Core Audio - -So far, Core Audio has been the worst backend to work with due to being both unintuitive and having almost no documentation -apart from comments in the headers (which admittedly are quite good). For my own purposes, and for anybody out there whose -needing to figure out how this darn thing works, I'm going to outline a few things here. - -Since miniaudio is a fairly low-level API, one of the things it needs is control over specific devices, and it needs to be -able to identify whether or not it can be used as playback and/or capture. The AudioObject API is the only one I've seen -that supports this level of detail. There was some public domain sample code I stumbled across that used the AudioComponent -and AudioUnit APIs, but I couldn't see anything that gave low-level control over device selection and capabilities (the -distinction between playback and capture in particular). Therefore, miniaudio is using the AudioObject API. - -Most (all?) functions in the AudioObject API take a AudioObjectID as its input. This is the device identifier. When -retrieving global information, such as the device list, you use kAudioObjectSystemObject. When retrieving device-specific -data, you pass in the ID for that device. In order to retrieve device-specific IDs you need to enumerate over each of the -devices. This is done using the AudioObjectGetPropertyDataSize() and AudioObjectGetPropertyData() APIs which seem to be -the central APIs for retrieving information about the system and specific devices. - -To use the AudioObjectGetPropertyData() API you need to use the notion of a property address. A property address is a -structure with three variables and is used to identify which property you are getting or setting. The first is the "selector" -which is basically the specific property that you're wanting to retrieve or set. The second is the "scope", which is -typically set to kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput for input-specific properties and -kAudioObjectPropertyScopeOutput for output-specific properties. The last is the "element" which is always set to -kAudioObjectPropertyElementMain in miniaudio's case. I don't know of any cases where this would be set to anything different. - -Back to the earlier issue of device retrieval, you first use the AudioObjectGetPropertyDataSize() API to retrieve the size -of the raw data which is just a list of AudioDeviceID's. You use the kAudioObjectSystemObject AudioObjectID, and a property -address with the kAudioHardwarePropertyDevices selector and the kAudioObjectPropertyScopeGlobal scope. Once you have the -size, allocate a block of memory of that size and then call AudioObjectGetPropertyData(). The data is just a list of -AudioDeviceID's so just do "dataSize/sizeof(AudioDeviceID)" to know the device count. -*/ - -#if defined(MA_APPLE_MOBILE) -static void ma_device__on_notification_interruption_began(ma_device* pDevice) -{ - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began)); -} +#if defined(MA_APPLE_DESKTOP) + result = ma_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); + if (result != MA_SUCCESS) { + return result; + } -static void ma_device__on_notification_interruption_ended(ma_device* pDevice) -{ - ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended)); -} + pData->deviceObjectID = deviceObjectID; #endif -static ma_result ma_result_from_OSStatus(OSStatus status) -{ - switch (status) - { - case noErr: return MA_SUCCESS; - #if defined(MA_APPLE_DESKTOP) - case kAudioHardwareNotRunningError: return MA_DEVICE_NOT_STARTED; - case kAudioHardwareUnspecifiedError: return MA_ERROR; - case kAudioHardwareUnknownPropertyError: return MA_INVALID_ARGS; - case kAudioHardwareBadPropertySizeError: return MA_INVALID_OPERATION; - case kAudioHardwareIllegalOperationError: return MA_INVALID_OPERATION; - case kAudioHardwareBadObjectError: return MA_INVALID_ARGS; - case kAudioHardwareBadDeviceError: return MA_INVALID_ARGS; - case kAudioHardwareBadStreamError: return MA_INVALID_ARGS; - case kAudioHardwareUnsupportedOperationError: return MA_INVALID_OPERATION; - case kAudioDeviceUnsupportedFormatError: return MA_FORMAT_NOT_SUPPORTED; - case kAudioDevicePermissionsError: return MA_ACCESS_DENIED; - #endif - default: return MA_ERROR; + /* Core audio doesn't really use the notion of a period so we can leave this unmodified, but not too over the top. */ + pData->periodsOut = pData->periodsIn; + if (pData->periodsOut == 0) { + pData->periodsOut = MA_DEFAULT_PERIODS; } -} - -#if 0 -static ma_channel ma_channel_from_AudioChannelBitmap(AudioChannelBitmap bit) -{ - switch (bit) - { - case kAudioChannelBit_Left: return MA_CHANNEL_LEFT; - case kAudioChannelBit_Right: return MA_CHANNEL_RIGHT; - case kAudioChannelBit_Center: return MA_CHANNEL_FRONT_CENTER; - case kAudioChannelBit_LFEScreen: return MA_CHANNEL_LFE; - case kAudioChannelBit_LeftSurround: return MA_CHANNEL_BACK_LEFT; - case kAudioChannelBit_RightSurround: return MA_CHANNEL_BACK_RIGHT; - case kAudioChannelBit_LeftCenter: return MA_CHANNEL_FRONT_LEFT_CENTER; - case kAudioChannelBit_RightCenter: return MA_CHANNEL_FRONT_RIGHT_CENTER; - case kAudioChannelBit_CenterSurround: return MA_CHANNEL_BACK_CENTER; - case kAudioChannelBit_LeftSurroundDirect: return MA_CHANNEL_SIDE_LEFT; - case kAudioChannelBit_RightSurroundDirect: return MA_CHANNEL_SIDE_RIGHT; - case kAudioChannelBit_TopCenterSurround: return MA_CHANNEL_TOP_CENTER; - case kAudioChannelBit_VerticalHeightLeft: return MA_CHANNEL_TOP_FRONT_LEFT; - case kAudioChannelBit_VerticalHeightCenter: return MA_CHANNEL_TOP_FRONT_CENTER; - case kAudioChannelBit_VerticalHeightRight: return MA_CHANNEL_TOP_FRONT_RIGHT; - case kAudioChannelBit_TopBackLeft: return MA_CHANNEL_TOP_BACK_LEFT; - case kAudioChannelBit_TopBackCenter: return MA_CHANNEL_TOP_BACK_CENTER; - case kAudioChannelBit_TopBackRight: return MA_CHANNEL_TOP_BACK_RIGHT; - default: return MA_CHANNEL_NONE; + if (pData->periodsOut > 16) { + pData->periodsOut = 16; } -} -#endif -static ma_result ma_format_from_AudioStreamBasicDescription(const AudioStreamBasicDescription* pDescription, ma_format* pFormatOut) -{ - MA_ASSERT(pDescription != NULL); - MA_ASSERT(pFormatOut != NULL); - *pFormatOut = ma_format_unknown; /* Safety. */ + /* Audio unit. */ + status = pContextStateCoreAudio->AudioComponentInstanceNew(pContextStateCoreAudio->component, &pData->audioUnit); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } - /* There's a few things miniaudio doesn't support. */ - if (pDescription->mFormatID != kAudioFormatLinearPCM) { - return MA_FORMAT_NOT_SUPPORTED; + + /* The input/output buses need to be explicitly enabled and disabled. We set the flag based on the output unit first, then we just swap it for input. */ + enableIOFlag = 1; + if (deviceType == ma_device_type_capture) { + enableIOFlag = 0; } - /* We don't support any non-packed formats that are aligned high. */ - if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) != 0) { - return MA_FORMAT_NOT_SUPPORTED; + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, MA_COREAUDIO_OUTPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(status); } - /* Only supporting native-endian. */ - if ((ma_is_little_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) != 0) || (ma_is_big_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) == 0)) { - return MA_FORMAT_NOT_SUPPORTED; + enableIOFlag = (enableIOFlag == 0) ? 1 : 0; + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, MA_COREAUDIO_INPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(status); } - /* We are not currently supporting non-interleaved formats (this will be added in a future version of miniaudio). */ - /*if ((pDescription->mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0) { - return MA_FORMAT_NOT_SUPPORTED; - }*/ - if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsFloat) != 0) { - if (pDescription->mBitsPerChannel == 32) { - *pFormatOut = ma_format_f32; - return MA_SUCCESS; - } - } else { - if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsSignedInteger) != 0) { - if (pDescription->mBitsPerChannel == 16) { - *pFormatOut = ma_format_s16; - return MA_SUCCESS; - } else if (pDescription->mBitsPerChannel == 24) { - if (pDescription->mBytesPerFrame == (pDescription->mBitsPerChannel/8 * pDescription->mChannelsPerFrame)) { - *pFormatOut = ma_format_s24; - return MA_SUCCESS; - } else { - if (pDescription->mBytesPerFrame/pDescription->mChannelsPerFrame == sizeof(ma_int32)) { - /* TODO: Implement ma_format_s24_32. */ - /**pFormatOut = ma_format_s24_32;*/ - /*return MA_SUCCESS;*/ - return MA_FORMAT_NOT_SUPPORTED; - } + /* Set the device to use with this audio unit. This is only used on desktop since we are using defaults on mobile. */ +#if defined(MA_APPLE_DESKTOP) + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceObjectID, sizeof(deviceObjectID)); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(result); + } +#else + /* + For some reason it looks like Apple is only allowing selection of the input device. There does not appear to be any way to change + the default output route. I have no idea why this is like this, but for now we'll only be able to configure capture devices. + */ + if (pDeviceID != NULL) { + if (deviceType == ma_device_type_capture) { + ma_bool32 found = MA_FALSE; + NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs]; + for (AVAudioSessionPortDescription* pPortDesc in pInputs) { + if (strcmp(pDeviceID->coreaudio, [pPortDesc.UID UTF8String]) == 0) { + [[AVAudioSession sharedInstance] setPreferredInput:pPortDesc error:nil]; + found = MA_TRUE; + break; } - } else if (pDescription->mBitsPerChannel == 32) { - *pFormatOut = ma_format_s32; - return MA_SUCCESS; } - } else { - if (pDescription->mBitsPerChannel == 8) { - *pFormatOut = ma_format_u8; - return MA_SUCCESS; + + if (found == MA_FALSE) { + return MA_DOES_NOT_EXIST; } } } +#endif - /* Getting here means the format is not supported. */ - return MA_FORMAT_NOT_SUPPORTED; -} + /* + Format. This is the hardest part of initialization because there's a few variables to take into account. + 1) The format must be supported by the device. + 2) The format must be supported miniaudio. + 3) There's a priority that miniaudio prefers. -#if defined(MA_APPLE_DESKTOP) -static ma_channel ma_channel_from_AudioChannelLabel(AudioChannelLabel label) -{ - switch (label) + Ideally we would like to use a format that's as close to the hardware as possible so we can get as close to a passthrough as possible. The + most important property is the sample rate. miniaudio can do format conversion for any sample rate and channel count, but cannot do the same + for the sample data format. If the sample data format is not supported by miniaudio it must be ignored completely. + + On mobile platforms this is a bit different. We just force the use of whatever the audio unit's current format is set to. + */ { - case kAudioChannelLabel_Unknown: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Unused: return MA_CHANNEL_NONE; - case kAudioChannelLabel_UseCoordinates: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Left: return MA_CHANNEL_LEFT; - case kAudioChannelLabel_Right: return MA_CHANNEL_RIGHT; - case kAudioChannelLabel_Center: return MA_CHANNEL_FRONT_CENTER; - case kAudioChannelLabel_LFEScreen: return MA_CHANNEL_LFE; - case kAudioChannelLabel_LeftSurround: return MA_CHANNEL_BACK_LEFT; - case kAudioChannelLabel_RightSurround: return MA_CHANNEL_BACK_RIGHT; - case kAudioChannelLabel_LeftCenter: return MA_CHANNEL_FRONT_LEFT_CENTER; - case kAudioChannelLabel_RightCenter: return MA_CHANNEL_FRONT_RIGHT_CENTER; - case kAudioChannelLabel_CenterSurround: return MA_CHANNEL_BACK_CENTER; - case kAudioChannelLabel_LeftSurroundDirect: return MA_CHANNEL_SIDE_LEFT; - case kAudioChannelLabel_RightSurroundDirect: return MA_CHANNEL_SIDE_RIGHT; - case kAudioChannelLabel_TopCenterSurround: return MA_CHANNEL_TOP_CENTER; - case kAudioChannelLabel_VerticalHeightLeft: return MA_CHANNEL_TOP_FRONT_LEFT; - case kAudioChannelLabel_VerticalHeightCenter: return MA_CHANNEL_TOP_FRONT_CENTER; - case kAudioChannelLabel_VerticalHeightRight: return MA_CHANNEL_TOP_FRONT_RIGHT; - case kAudioChannelLabel_TopBackLeft: return MA_CHANNEL_TOP_BACK_LEFT; - case kAudioChannelLabel_TopBackCenter: return MA_CHANNEL_TOP_BACK_CENTER; - case kAudioChannelLabel_TopBackRight: return MA_CHANNEL_TOP_BACK_RIGHT; - case kAudioChannelLabel_RearSurroundLeft: return MA_CHANNEL_BACK_LEFT; - case kAudioChannelLabel_RearSurroundRight: return MA_CHANNEL_BACK_RIGHT; - case kAudioChannelLabel_LeftWide: return MA_CHANNEL_SIDE_LEFT; - case kAudioChannelLabel_RightWide: return MA_CHANNEL_SIDE_RIGHT; - case kAudioChannelLabel_LFE2: return MA_CHANNEL_LFE; - case kAudioChannelLabel_LeftTotal: return MA_CHANNEL_LEFT; - case kAudioChannelLabel_RightTotal: return MA_CHANNEL_RIGHT; - case kAudioChannelLabel_HearingImpaired: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Narration: return MA_CHANNEL_MONO; - case kAudioChannelLabel_Mono: return MA_CHANNEL_MONO; - case kAudioChannelLabel_DialogCentricMix: return MA_CHANNEL_MONO; - case kAudioChannelLabel_CenterSurroundDirect: return MA_CHANNEL_BACK_CENTER; - case kAudioChannelLabel_Haptic: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_W: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_X: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_Y: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Ambisonic_Z: return MA_CHANNEL_NONE; - case kAudioChannelLabel_MS_Mid: return MA_CHANNEL_LEFT; - case kAudioChannelLabel_MS_Side: return MA_CHANNEL_RIGHT; - case kAudioChannelLabel_XY_X: return MA_CHANNEL_LEFT; - case kAudioChannelLabel_XY_Y: return MA_CHANNEL_RIGHT; - case kAudioChannelLabel_HeadphonesLeft: return MA_CHANNEL_LEFT; - case kAudioChannelLabel_HeadphonesRight: return MA_CHANNEL_RIGHT; - case kAudioChannelLabel_ClickTrack: return MA_CHANNEL_NONE; - case kAudioChannelLabel_ForeignLanguage: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Discrete: return MA_CHANNEL_NONE; - case kAudioChannelLabel_Discrete_0: return MA_CHANNEL_AUX_0; - case kAudioChannelLabel_Discrete_1: return MA_CHANNEL_AUX_1; - case kAudioChannelLabel_Discrete_2: return MA_CHANNEL_AUX_2; - case kAudioChannelLabel_Discrete_3: return MA_CHANNEL_AUX_3; - case kAudioChannelLabel_Discrete_4: return MA_CHANNEL_AUX_4; - case kAudioChannelLabel_Discrete_5: return MA_CHANNEL_AUX_5; - case kAudioChannelLabel_Discrete_6: return MA_CHANNEL_AUX_6; - case kAudioChannelLabel_Discrete_7: return MA_CHANNEL_AUX_7; - case kAudioChannelLabel_Discrete_8: return MA_CHANNEL_AUX_8; - case kAudioChannelLabel_Discrete_9: return MA_CHANNEL_AUX_9; - case kAudioChannelLabel_Discrete_10: return MA_CHANNEL_AUX_10; - case kAudioChannelLabel_Discrete_11: return MA_CHANNEL_AUX_11; - case kAudioChannelLabel_Discrete_12: return MA_CHANNEL_AUX_12; - case kAudioChannelLabel_Discrete_13: return MA_CHANNEL_AUX_13; - case kAudioChannelLabel_Discrete_14: return MA_CHANNEL_AUX_14; - case kAudioChannelLabel_Discrete_15: return MA_CHANNEL_AUX_15; - case kAudioChannelLabel_Discrete_65535: return MA_CHANNEL_NONE; + AudioStreamBasicDescription origFormat; + UInt32 origFormatSize = sizeof(origFormat); + AudioUnitScope formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; + AudioUnitElement formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; - #if 0 /* Introduced in a later version of macOS. */ - case kAudioChannelLabel_HOA_ACN: return MA_CHANNEL_NONE; - case kAudioChannelLabel_HOA_ACN_0: return MA_CHANNEL_AUX_0; - case kAudioChannelLabel_HOA_ACN_1: return MA_CHANNEL_AUX_1; - case kAudioChannelLabel_HOA_ACN_2: return MA_CHANNEL_AUX_2; - case kAudioChannelLabel_HOA_ACN_3: return MA_CHANNEL_AUX_3; - case kAudioChannelLabel_HOA_ACN_4: return MA_CHANNEL_AUX_4; - case kAudioChannelLabel_HOA_ACN_5: return MA_CHANNEL_AUX_5; - case kAudioChannelLabel_HOA_ACN_6: return MA_CHANNEL_AUX_6; - case kAudioChannelLabel_HOA_ACN_7: return MA_CHANNEL_AUX_7; - case kAudioChannelLabel_HOA_ACN_8: return MA_CHANNEL_AUX_8; - case kAudioChannelLabel_HOA_ACN_9: return MA_CHANNEL_AUX_9; - case kAudioChannelLabel_HOA_ACN_10: return MA_CHANNEL_AUX_10; - case kAudioChannelLabel_HOA_ACN_11: return MA_CHANNEL_AUX_11; - case kAudioChannelLabel_HOA_ACN_12: return MA_CHANNEL_AUX_12; - case kAudioChannelLabel_HOA_ACN_13: return MA_CHANNEL_AUX_13; - case kAudioChannelLabel_HOA_ACN_14: return MA_CHANNEL_AUX_14; - case kAudioChannelLabel_HOA_ACN_15: return MA_CHANNEL_AUX_15; - case kAudioChannelLabel_HOA_ACN_65024: return MA_CHANNEL_NONE; - #endif + if (deviceType == ma_device_type_playback) { + status = pContextStateCoreAudio->AudioUnitGetProperty(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, MA_COREAUDIO_OUTPUT_BUS, &origFormat, &origFormatSize); + } else { + status = pContextStateCoreAudio->AudioUnitGetProperty(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MA_COREAUDIO_INPUT_BUS, &origFormat, &origFormatSize); + } + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(status); + } + + #if defined(MA_APPLE_DESKTOP) + result = ma_find_best_format__coreaudio(pContext, deviceObjectID, deviceType, pData->formatIn, pData->channelsIn, pData->sampleRateIn, &origFormat, &bestFormat); + if (result != MA_SUCCESS) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return result; + } + + /* + Technical Note TN2091: Device input using the HAL Output Audio Unit + https://developer.apple.com/library/archive/technotes/tn2091/_index.html + + This documentation says the following: + + The internal AudioConverter can handle any *simple* conversion. Typically, this means that a client can specify ANY + variant of the PCM formats. Consequently, the device's sample rate should match the desired sample rate. If sample rate + conversion is needed, it can be accomplished by buffering the input and converting the data on a separate thread with + another AudioConverter. - default: return MA_CHANNEL_NONE; - } -} + The important part here is the mention that it can handle *simple* conversions, which does *not* include sample rate. We + therefore want to ensure the sample rate stays consistent. This document is specifically for input, but I'm going to play it + safe and apply the same rule to output as well. -static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, ma_channel* pChannelMap, size_t channelMapCap) -{ - MA_ASSERT(pChannelLayout != NULL); + I have tried going against the documentation by setting the sample rate anyway, but this just results in AudioUnitRender() + returning a result code of -10863. I have also tried changing the format directly on the input scope on the input bus, but + this just results in `ca_require: IsStreamFormatWritable(inScope, inElement) NotWritable` when trying to set the format. - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { - UInt32 iChannel; - for (iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions && iChannel < channelMapCap; ++iChannel) { - pChannelMap[iChannel] = ma_channel_from_AudioChannelLabel(pChannelLayout->mChannelDescriptions[iChannel].mChannelLabel); - } - } else -#if 0 - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { - /* This is the same kind of system that's used by Windows audio APIs. */ - UInt32 iChannel = 0; - UInt32 iBit; - AudioChannelBitmap bitmap = pChannelLayout->mChannelBitmap; - for (iBit = 0; iBit < 32 && iChannel < channelMapCap; ++iBit) { - AudioChannelBitmap bit = bitmap & (1 << iBit); - if (bit != 0) { - pChannelMap[iChannel++] = ma_channel_from_AudioChannelBit(bit); + Something that does seem to work, however, has been setting the nominal sample rate on the device object. The problem with + this, however, is that it actually changes the sample rate at the operating system level and not just the application. This + could be intrusive to the user, however, so I don't think it's wise to make this the default. Instead I'm making this a + configuration option. When the `coreaudio.allowNominalSampleRateChange` config option is set to true, changing the sample + rate will be allowed. Otherwise it'll be fixed to the current sample rate. To check the system-defined sample rate, run + the Audio MIDI Setup program that comes installed on macOS and observe how the sample rate changes as the sample rate is + changed by miniaudio. + */ + if (pData->allowNominalSampleRateChange) { + AudioValueRange sampleRateRange; + AudioObjectPropertyAddress propAddress; + + sampleRateRange.mMinimum = bestFormat.mSampleRate; + sampleRateRange.mMaximum = bestFormat.mSampleRate; + + propAddress.mSelector = kAudioDevicePropertyNominalSampleRate; + propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + + status = pContextStateCoreAudio->AudioObjectSetPropertyData(deviceObjectID, &propAddress, 0, NULL, sizeof(sampleRateRange), &sampleRateRange); + if (status != noErr) { + bestFormat.mSampleRate = origFormat.mSampleRate; } + } else { + bestFormat.mSampleRate = origFormat.mSampleRate; } - } else -#endif - { + + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); + if (status != noErr) { + /* We failed to set the format, so fall back to the current format of the audio unit. */ + bestFormat = origFormat; + } + #else + bestFormat = origFormat; + /* - Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should - be updated to determine the mapping based on the tag. + Sample rate is a little different here because for some reason kAudioUnitProperty_StreamFormat returns 0... Oh well. We need to instead try + setting the sample rate to what the user has requested and then just see the results of it. Need to use some Objective-C here for this since + it depends on Apple's AVAudioSession API. To do this we just get the shared AVAudioSession instance and then set it. Note that from what I + can tell, it looks like the sample rate is shared between playback and capture for everything. */ - UInt32 channelCount; + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + MA_ASSERT(pAudioSession != NULL); - /* Our channel map retrieval APIs below take 32-bit integers, so we'll want to clamp the channel map capacity. */ - if (channelMapCap > 0xFFFFFFFF) { - channelMapCap = 0xFFFFFFFF; - } + [pAudioSession setPreferredSampleRate:(double)pData->sampleRateIn error:nil]; + bestFormat.mSampleRate = pAudioSession.sampleRate; - channelCount = ma_min(AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag), (UInt32)channelMapCap); + /* + I've had a report that the channel count returned by AudioUnitGetProperty above is inconsistent with + AVAudioSession outputNumberOfChannels. I'm going to try using the AVAudioSession values instead. - switch (pChannelLayout->mChannelLayoutTag) - { - case kAudioChannelLayoutTag_Mono: - case kAudioChannelLayoutTag_Stereo: - case kAudioChannelLayoutTag_StereoHeadphones: - case kAudioChannelLayoutTag_MatrixStereo: - case kAudioChannelLayoutTag_MidSide: - case kAudioChannelLayoutTag_XY: - case kAudioChannelLayoutTag_Binaural: - case kAudioChannelLayoutTag_Ambisonic_B_Format: - { - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); - } break; + UPDATE 20/02/2025: + When testing on the simulator with an iPhone 15 and iOS 17 I get an error when initializing the audio + unit if set the input channels to pAudioSession.inputNumberOfChannels. What is happening is the channel + count returned from AudioUnitGetProperty() above is set to 2, but pAudioSession is reporting a channel + count of 1. When this happens, the call to AudioUnitSetProprty() below just down below will succeed, but + AudioUnitInitialize() further down will fail. The only solution I have come up with is to not set the + channel count to pAudioSession.inputNumberOfChannels. + */ + if (deviceType == ma_device_type_playback) { + bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.outputNumberOfChannels; + } - case kAudioChannelLayoutTag_Octagonal: - { - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - } MA_FALLTHROUGH; /* Intentional fallthrough. */ - case kAudioChannelLayoutTag_Hexagonal: - { - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - } MA_FALLTHROUGH; /* Intentional fallthrough. */ - case kAudioChannelLayoutTag_Pentagonal: - { - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - } MA_FALLTHROUGH; /* Intentional fallthrough. */ - case kAudioChannelLayoutTag_Quadraphonic: - { - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[1] = MA_CHANNEL_RIGHT; - pChannelMap[0] = MA_CHANNEL_LEFT; - } break; + #if 0 + if (deviceType == ma_device_type_capture) { + /*printf("DEBUG: bestFormat.mChannelsPerFrame = %d; pAudioSession.inputNumberOfChannels = %d", (int)bestFormat.mChannelsPerFrame, (int)pAudioSession.inputNumberOfChannels);*/ + bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.inputNumberOfChannels; + } + #endif + } - /* TODO: Add support for more tags here. */ + + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(status); + } + #endif - default: - { - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); - } break; + result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pData->formatOut); + if (result != MA_SUCCESS) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return result; + } + + if (pData->formatOut == ma_format_unknown) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return MA_FORMAT_NOT_SUPPORTED; } + + pData->channelsOut = bestFormat.mChannelsPerFrame; + pData->sampleRateOut = (ma_uint32)bestFormat.mSampleRate; } - return MA_SUCCESS; -} + /* Clamp the channel count for safety. */ + if (pData->channelsOut > MA_MAX_CHANNELS) { + pData->channelsOut = MA_MAX_CHANNELS; + } -#if (defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0) || \ - (defined(__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) -#define AUDIO_OBJECT_PROPERTY_ELEMENT kAudioObjectPropertyElementMain + /* + Internal channel map. This is weird in my testing. If I use the AudioObject to get the + channel map, the channel descriptions are set to "Unknown" for some reason. To work around + this it looks like retrieving it from the AudioUnit will work. However, and this is where + it gets weird, it doesn't seem to work with capture devices, nor at all on iOS... Therefore + I'm going to fall back to a default assumption in these cases. + */ +#if defined(MA_APPLE_DESKTOP) + result = ma_get_AudioUnit_channel_map(pContext, pData->audioUnit, deviceType, pData->channelMapOut, pData->channelsOut); + if (result != MA_SUCCESS) { + #if 0 + /* Try falling back to the channel map from the AudioObject. */ + result = ma_get_AudioObject_channel_map(pContext, deviceObjectID, deviceType, pData->channelMapOut, pData->channelsOut); + if (result != MA_SUCCESS) { + return result; + } + #else + /* Fall back to default assumptions. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); + #endif + } #else -/* kAudioObjectPropertyElementMaster is deprecated. */ -#define AUDIO_OBJECT_PROPERTY_ELEMENT kAudioObjectPropertyElementMaster + /* TODO: Figure out how to get the channel map using AVAudioSession. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); #endif -/* kAudioDevicePropertyScope* were renamed to kAudioObjectPropertyScope* in 10.8. */ -#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8) -#define kAudioObjectPropertyScopeInput kAudioDevicePropertyScopeInput -#define kAudioObjectPropertyScopeOutput kAudioDevicePropertyScopeOutput -#endif -static ma_result ma_get_device_object_ids__coreaudio(ma_context* pContext, UInt32* pDeviceCount, AudioObjectID** ppDeviceObjectIDs) /* NOTE: Free the returned buffer with ma_free(). */ -{ - AudioObjectPropertyAddress propAddressDevices; - UInt32 deviceObjectsDataSize; - OSStatus status; - AudioObjectID* pDeviceObjectIDs; + /* Buffer size. Not allowing this to be configurable on iOS. */ + if (pData->periodSizeInFramesIn == 0) { + if (pData->periodSizeInMillisecondsIn == 0) { + actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS, pData->sampleRateOut); + } else { + actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->periodSizeInMillisecondsIn, pData->sampleRateOut); + } + } else { + actualPeriodSizeInFrames = pData->periodSizeInFramesIn; + } - MA_ASSERT(pContext != NULL); - MA_ASSERT(pDeviceCount != NULL); - MA_ASSERT(ppDeviceObjectIDs != NULL); +#if defined(MA_APPLE_DESKTOP) + result = ma_set_AudioObject_buffer_size_in_frames(pContext, deviceObjectID, deviceType, &actualPeriodSizeInFrames); + if (result != MA_SUCCESS) { + return result; + } +#else + /* + On iOS, the size of the IO buffer needs to be specified in seconds and is a floating point + number. I don't trust any potential truncation errors due to converting from float to integer + so I'm going to explicitly set the actual period size to the next power of 2. + */ + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + MA_ASSERT(pAudioSession != NULL); - /* Safety. */ - *pDeviceCount = 0; - *ppDeviceObjectIDs = NULL; + [pAudioSession setPreferredIOBufferDuration:((float)actualPeriodSizeInFrames / pAudioSession.sampleRate) error:nil]; + actualPeriodSizeInFrames = ma_next_power_of_2((ma_uint32)(pAudioSession.IOBufferDuration * pAudioSession.sampleRate)); + } +#endif - propAddressDevices.mSelector = kAudioHardwarePropertyDevices; - propAddressDevices.mScope = kAudioObjectPropertyScopeGlobal; - propAddressDevices.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize); + /* + During testing I discovered that the buffer size can be too big. You'll get an error like this: + + kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=4096, mMaxFramesPerSlice=512 + + Note how inFramesToProcess is smaller than mMaxFramesPerSlice. To fix, we need to set kAudioUnitProperty_MaximumFramesPerSlice to that + of the size of our buffer, or do it the other way around and set our buffer size to the kAudioUnitProperty_MaximumFramesPerSlice. + */ + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualPeriodSizeInFrames, sizeof(actualPeriodSizeInFrames)); if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); return ma_result_from_OSStatus(status); } - pDeviceObjectIDs = (AudioObjectID*)ma_malloc(deviceObjectsDataSize, &pContext->allocationCallbacks); - if (pDeviceObjectIDs == NULL) { - return MA_OUT_OF_MEMORY; + pData->periodSizeInFramesOut = (ma_uint32)actualPeriodSizeInFrames; + + /* We need a buffer list if this is an input device. We render into this in the input callback. */ + if (deviceType == ma_device_type_capture) { + ma_bool32 isInterleaved = (bestFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; + AudioBufferList* pBufferList; + + pBufferList = ma_allocate_AudioBufferList__coreaudio(pData->periodSizeInFramesOut, pData->formatOut, pData->channelsOut, (isInterleaved) ? ma_stream_layout_interleaved : ma_stream_layout_deinterleaved, &pContext->allocationCallbacks); + if (pBufferList == NULL) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return MA_OUT_OF_MEMORY; + } + + pData->pAudioBufferList = pBufferList; + } + + /* Callbacks. */ + callbackInfo.inputProcRefCon = pDevice_DoNotReference; + if (deviceType == ma_device_type_playback) { + callbackInfo.inputProc = ma_on_output__coreaudio; + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callbackInfo, sizeof(callbackInfo)); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(status); + } + } else { + callbackInfo.inputProc = ma_on_input__coreaudio; + status = pContextStateCoreAudio->AudioUnitSetProperty(pData->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callbackInfo, sizeof(callbackInfo)); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(status); + } + } + + /* We need to listen for stop events. */ + if (pData->registerStopEvent) { + status = pContextStateCoreAudio->AudioUnitAddPropertyListener(pData->audioUnit, kAudioOutputUnitProperty_IsRunning, ma_on_start_stop__coreaudio, pDevice_DoNotReference); + if (status != noErr) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); + return ma_result_from_OSStatus(status); + } } - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize, pDeviceObjectIDs); + /* Initialize the audio unit. */ + status = pContextStateCoreAudio->AudioUnitInitialize(pData->audioUnit); if (status != noErr) { - ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); + ma_free(pData->pAudioBufferList, &pContext->allocationCallbacks); + pData->pAudioBufferList = NULL; + pContextStateCoreAudio->AudioComponentInstanceDispose(pData->audioUnit); return ma_result_from_OSStatus(status); } - *pDeviceCount = deviceObjectsDataSize / sizeof(AudioObjectID); - *ppDeviceObjectIDs = pDeviceObjectIDs; + /* Grab the name. */ +#if defined(MA_APPLE_DESKTOP) + ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(pData->deviceName), pData->deviceName); +#else + if (deviceType == ma_device_type_playback) { + ma_strcpy_s(pData->deviceName, sizeof(pData->deviceName), MA_DEFAULT_PLAYBACK_DEVICE_NAME); + } else { + ma_strcpy_s(pData->deviceName, sizeof(pData->deviceName), MA_DEFAULT_CAPTURE_DEVICE_NAME); + } +#endif - return MA_SUCCESS; + return result; } -static ma_result ma_get_AudioObject_uid_as_CFStringRef(ma_context* pContext, AudioObjectID objectID, CFStringRef* pUID) +#if defined(MA_APPLE_DESKTOP) +static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_device_type deviceType, ma_bool32 disposePreviousAudioUnit) { - AudioObjectPropertyAddress propAddress; - UInt32 dataSize; - OSStatus status; + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); + ma_device_init_internal_data__coreaudio data; + ma_device_descriptor internalDescriptor; + ma_result result; - MA_ASSERT(pContext != NULL); + /* This should only be called for playback or capture, not duplex. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } - propAddress.mSelector = kAudioDevicePropertyDeviceUID; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + data.allowNominalSampleRateChange = MA_FALSE; /* Don't change the nominal sample rate when switching devices. */ - dataSize = sizeof(*pUID); - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, pUID); - if (status != noErr) { - return ma_result_from_OSStatus(status); + if (deviceType == ma_device_type_capture) { + data.shareMode = pDevice->capture.shareMode; + data.registerStopEvent = MA_TRUE; + + if (disposePreviousAudioUnit) { + pContextStateCoreAudio->AudioOutputUnitStop(pDeviceStateCoreAudio->audioUnitCapture); + pContextStateCoreAudio->AudioComponentInstanceDispose(pDeviceStateCoreAudio->audioUnitCapture); + } + if (pDeviceStateCoreAudio->pAudioBufferList) { + ma_free(pDeviceStateCoreAudio->pAudioBufferList, ma_device_get_allocation_callbacks(pDevice)); + } + } else if (deviceType == ma_device_type_playback) { + data.shareMode = pDevice->playback.shareMode; + data.registerStopEvent = (pDevice->type != ma_device_type_duplex); + + if (disposePreviousAudioUnit) { + pContextStateCoreAudio->AudioOutputUnitStop(pDeviceStateCoreAudio->audioUnitPlayback); + pContextStateCoreAudio->AudioComponentInstanceDispose(pDeviceStateCoreAudio->audioUnitPlayback); + } } - return MA_SUCCESS; -} + data.formatIn = ma_device_get_format(pDevice, deviceType); + data.channelsIn = ma_device_get_channels(pDevice, deviceType); + data.sampleRateIn = ma_device_get_sample_rate(pDevice); + ma_device_get_channel_map(pDevice, deviceType, data.channelMapIn, ma_countof(data.channelMapIn)); + data.periodSizeInFramesIn = pDeviceStateCoreAudio->originalPeriodSizeInFrames; + data.periodSizeInMillisecondsIn = pDeviceStateCoreAudio->originalPeriodSizeInMilliseconds; + data.periodsIn = pDeviceStateCoreAudio->originalPeriods; -static ma_result ma_get_AudioObject_uid(ma_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) -{ - CFStringRef uid; - ma_result result; + /* Need at least 3 periods for duplex. */ + if (data.periodsIn < 3 && pDevice->type == ma_device_type_duplex) { + data.periodsIn = 3; + } - MA_ASSERT(pContext != NULL); + result = ma_device_init_internal__coreaudio(pDevice->pContext, deviceType, NULL, &data, (void*)pDevice); + if (result != MA_SUCCESS) { + return result; + } - result = ma_get_AudioObject_uid_as_CFStringRef(pContext, objectID, &uid); + /* The device's internal descriptor needs to be updated. */ + MA_ZERO_OBJECT(&internalDescriptor); + internalDescriptor.format = data.formatOut; + internalDescriptor.channels = data.channelsOut; + internalDescriptor.sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(internalDescriptor.channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + internalDescriptor.periodSizeInFrames = data.periodSizeInFramesOut; + internalDescriptor.periodCount = data.periodsOut; + + result = ma_device_update_descriptor(pDevice, deviceType, NULL, &internalDescriptor); if (result != MA_SUCCESS) { + ma_device_uninit__coreaudio(pDevice); return result; } - if (!((ma_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(uid, bufferOut, bufferSize, kCFStringEncodingUTF8)) { - return MA_ERROR; + + /* + We need to reinitialize our async state object to account for the new internal format and + buffer configuration. To do this we'll need to set up a dummy descriptor. + */ + ma_device_state_async_uninit(&pDeviceStateCoreAudio->async, ma_device_get_allocation_callbacks(pDevice)); + + if (deviceType == ma_device_type_capture) { + #if defined(MA_APPLE_DESKTOP) + pDeviceStateCoreAudio->deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; + ma_get_AudioObject_uid(pDevice->pContext, pDeviceStateCoreAudio->deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); + #endif + pDeviceStateCoreAudio->audioUnitCapture = data.audioUnit; + pDeviceStateCoreAudio->pAudioBufferList = data.pAudioBufferList; + pDeviceStateCoreAudio->audioBufferCapInFrames = data.periodSizeInFramesOut; + } else if (deviceType == ma_device_type_playback) { + #if defined(MA_APPLE_DESKTOP) + pDeviceStateCoreAudio->deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; + ma_get_AudioObject_uid(pDevice->pContext, pDeviceStateCoreAudio->deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); + #endif + pDeviceStateCoreAudio->audioUnitPlayback = data.audioUnit; + } + + { + ma_device_descriptor descriptorPlayback; + ma_device_descriptor descriptorCapture; + + MA_ZERO_OBJECT(&descriptorPlayback); + descriptorPlayback.format = ma_device_get_internal_format(pDevice, ma_device_type_playback); + descriptorPlayback.channels = ma_device_get_internal_channels(pDevice, ma_device_type_playback); + descriptorPlayback.sampleRate = ma_device_get_internal_sample_rate(pDevice, ma_device_type_playback); + descriptorPlayback.periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; + descriptorPlayback.periodCount = pDevice->playback.internalPeriods; + + MA_ZERO_OBJECT(&descriptorCapture); + descriptorCapture.format = ma_device_get_internal_format(pDevice, ma_device_type_capture); + descriptorCapture.channels = ma_device_get_internal_channels(pDevice, ma_device_type_capture); + descriptorCapture.sampleRate = ma_device_get_internal_sample_rate(pDevice, ma_device_type_capture); + descriptorCapture.periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; + descriptorCapture.periodCount = pDevice->capture.internalPeriods; + + result = ma_device_state_async_init(ma_device_get_type(pDevice), &descriptorPlayback, &descriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateCoreAudio->async); + if (result != MA_SUCCESS) { + ma_device_uninit__coreaudio(pDevice); + return result; + } } - ((ma_CFRelease_proc)pContext->coreaudio.CFRelease)(uid); return MA_SUCCESS; } +#endif /* MA_APPLE_DESKTOP */ -static ma_result ma_get_AudioObject_name(ma_context* pContext, AudioObjectID objectID, size_t bufferSize, char* bufferOut) +static ma_result ma_device_init__coreaudio(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - AudioObjectPropertyAddress propAddress; - CFStringRef deviceName = NULL; - UInt32 dataSize; - OSStatus status; + ma_device_state_coreaudio* pDeviceStateCoreAudio; + const ma_device_config_coreaudio* pDeviceConfigCoreAudio = (const ma_device_config_coreaudio*)pDeviceBackendConfig; + ma_device_config_coreaudio defaultConfigCoreAudio; + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; - MA_ASSERT(pContext != NULL); + if (pDeviceConfigCoreAudio == NULL) { + defaultConfigCoreAudio = ma_device_config_coreaudio_init(); + pDeviceConfigCoreAudio = &defaultConfigCoreAudio; + } - propAddress.mSelector = kAudioDevicePropertyDeviceNameCFString; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } - dataSize = sizeof(deviceName); - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(objectID, &propAddress, 0, NULL, &dataSize, &deviceName); - if (status != noErr) { - return ma_result_from_OSStatus(status); + /* No exclusive mode with the Core Audio backend for now. */ + if (((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive) || + ((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive)) { + return MA_SHARE_MODE_NOT_SUPPORTED; } - if (!((ma_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(deviceName, bufferOut, bufferSize, kCFStringEncodingUTF8)) { - return MA_ERROR; + pDeviceStateCoreAudio = (ma_device_state_coreaudio*)ma_calloc(sizeof(*pDeviceStateCoreAudio), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateCoreAudio == NULL) { + return MA_OUT_OF_MEMORY; } - ((ma_CFRelease_proc)pContext->coreaudio.CFRelease)(deviceName); - return MA_SUCCESS; -} + /* Capture needs to be initialized first. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_device_init_internal_data__coreaudio data; + data.allowNominalSampleRateChange = pDeviceConfigCoreAudio->allowNominalSampleRateChange; + data.formatIn = pDescriptorCapture->format; + data.channelsIn = pDescriptorCapture->channels; + data.sampleRateIn = pDescriptorCapture->sampleRate; + MA_COPY_MEMORY(data.channelMapIn, pDescriptorCapture->channelMap, sizeof(pDescriptorCapture->channelMap)); + data.periodSizeInFramesIn = pDescriptorCapture->periodSizeInFrames; + data.periodSizeInMillisecondsIn = pDescriptorCapture->periodSizeInMilliseconds; + data.periodsIn = pDescriptorCapture->periodCount; + data.shareMode = pDescriptorCapture->shareMode; + data.registerStopEvent = MA_TRUE; -static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioObjectID deviceObjectID, AudioObjectPropertyScope scope) -{ - AudioObjectPropertyAddress propAddress; - UInt32 dataSize; - OSStatus status; - AudioBufferList* pBufferList; - ma_bool32 isSupported; + /* Need at least 3 periods for duplex. */ + if (data.periodsIn < 3 && deviceType == ma_device_type_duplex) { + data.periodsIn = 3; + } - MA_ASSERT(pContext != NULL); + result = ma_device_init_internal__coreaudio(ma_device_get_context(pDevice), ma_device_type_capture, pDescriptorCapture->pDeviceID, &data, (void*)pDevice); + if (result != MA_SUCCESS) { + ma_free(pDeviceStateCoreAudio, ma_device_get_allocation_callbacks(pDevice)); + return result; + } - /* To know whether or not a device is an input device we need ot look at the stream configuration. If it has an output channel it's a playback device. */ - propAddress.mSelector = kAudioDevicePropertyStreamConfiguration; - propAddress.mScope = scope; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + pDeviceStateCoreAudio->isDefaultCaptureDevice = (pDescriptorCapture->pDeviceID == NULL); + #if defined(MA_APPLE_DESKTOP) + pDeviceStateCoreAudio->deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; + #endif + pDeviceStateCoreAudio->audioUnitCapture = data.audioUnit; + pDeviceStateCoreAudio->pAudioBufferList = data.pAudioBufferList; + pDeviceStateCoreAudio->audioBufferCapInFrames = data.periodSizeInFramesOut; + pDeviceStateCoreAudio->originalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + pDeviceStateCoreAudio->originalPeriodSizeInMilliseconds = pDescriptorCapture->periodSizeInMilliseconds; + pDeviceStateCoreAudio->originalPeriods = pDescriptorCapture->periodCount; - status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); - if (status != noErr) { - return MA_FALSE; - } + pDescriptorCapture->format = data.formatOut; + pDescriptorCapture->channels = data.channelsOut; + pDescriptorCapture->sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(pDescriptorCapture->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDescriptorCapture->periodSizeInFrames = data.periodSizeInFramesOut; + pDescriptorCapture->periodCount = data.periodsOut; - pBufferList = (AudioBufferList*)ma_malloc(dataSize, &pContext->allocationCallbacks); - if (pBufferList == NULL) { - return MA_FALSE; /* Out of memory. */ + #if defined(MA_APPLE_DESKTOP) + ma_get_AudioObject_uid(ma_device_get_context(pDevice), pDeviceStateCoreAudio->deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); + + /* + If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly + switch the device in the background. + */ + if (pDescriptorCapture->pDeviceID == NULL) { + ma_device__track__coreaudio(pDevice); + } + #endif } - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); - if (status != noErr) { - ma_free(pBufferList, &pContext->allocationCallbacks); - return MA_FALSE; + /* Playback. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_device_init_internal_data__coreaudio data; + data.allowNominalSampleRateChange = pDeviceConfigCoreAudio->allowNominalSampleRateChange; + data.formatIn = pDescriptorPlayback->format; + data.channelsIn = pDescriptorPlayback->channels; + data.sampleRateIn = pDescriptorPlayback->sampleRate; + MA_COPY_MEMORY(data.channelMapIn, pDescriptorPlayback->channelMap, sizeof(pDescriptorPlayback->channelMap)); + data.shareMode = pDescriptorPlayback->shareMode; + + /* In full-duplex mode we want the playback buffer to be the same size as the capture buffer. */ + if (deviceType == ma_device_type_duplex) { + data.periodSizeInFramesIn = pDescriptorCapture->periodSizeInFrames; + data.periodsIn = pDescriptorCapture->periodCount; + data.registerStopEvent = MA_FALSE; + } else { + data.periodSizeInFramesIn = pDescriptorPlayback->periodSizeInFrames; + data.periodSizeInMillisecondsIn = pDescriptorPlayback->periodSizeInMilliseconds; + data.periodsIn = pDescriptorPlayback->periodCount; + data.registerStopEvent = MA_TRUE; + } + + result = ma_device_init_internal__coreaudio(ma_device_get_context(pDevice), ma_device_type_playback, pDescriptorPlayback->pDeviceID, &data, (void*)pDevice); + if (result != MA_SUCCESS) { + if (deviceType == ma_device_type_duplex) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pDeviceStateCoreAudio->audioUnitCapture); + if (pDeviceStateCoreAudio->pAudioBufferList) { + ma_free(pDeviceStateCoreAudio->pAudioBufferList, ma_device_get_allocation_callbacks(pDevice)); + } + } + + ma_free(pDeviceStateCoreAudio, ma_device_get_allocation_callbacks(pDevice)); + return result; + } + + pDeviceStateCoreAudio->isDefaultPlaybackDevice = (pDescriptorPlayback->pDeviceID == NULL); + #if defined(MA_APPLE_DESKTOP) + pDeviceStateCoreAudio->deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; + #endif + pDeviceStateCoreAudio->audioUnitPlayback = data.audioUnit; + pDeviceStateCoreAudio->originalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + pDeviceStateCoreAudio->originalPeriodSizeInMilliseconds = pDescriptorPlayback->periodSizeInMilliseconds; + pDeviceStateCoreAudio->originalPeriods = pDescriptorPlayback->periodCount; + + pDescriptorPlayback->format = data.formatOut; + pDescriptorPlayback->channels = data.channelsOut; + pDescriptorPlayback->sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(pDescriptorPlayback->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDescriptorPlayback->periodSizeInFrames = data.periodSizeInFramesOut; + pDescriptorPlayback->periodCount = data.periodsOut; + + #if defined(MA_APPLE_DESKTOP) + ma_get_AudioObject_uid(ma_device_get_context(pDevice), pDeviceStateCoreAudio->deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); + + /* + If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly + switch the device in the background. + */ + if (pDescriptorPlayback->pDeviceID == NULL && (deviceType != ma_device_type_duplex || pDescriptorCapture->pDeviceID != NULL)) { + ma_device__track__coreaudio(pDevice); + } + #endif } - isSupported = MA_FALSE; - if (pBufferList->mNumberBuffers > 0) { - isSupported = MA_TRUE; + result = ma_device_state_async_init(deviceType, pDescriptorPlayback, pDescriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateCoreAudio->async); + if (result != MA_SUCCESS) { + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pDeviceStateCoreAudio->audioUnitCapture); + if (pDeviceStateCoreAudio->pAudioBufferList) { + ma_free(pDeviceStateCoreAudio->pAudioBufferList, ma_device_get_allocation_callbacks(pDevice)); + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStateCoreAudio->AudioComponentInstanceDispose(pDeviceStateCoreAudio->audioUnitPlayback); + } } - ma_free(pBufferList, &pContext->allocationCallbacks); - return isSupported; + /* + When stopping the device, a callback is called on another thread. We need to wait for this callback + before returning from ma_device_stop(). This event is used for this. + */ + ma_event_init(&pDeviceStateCoreAudio->stopEvent); + + /* + We need to detect when a route has changed so we can update the data conversion pipeline accordingly. This is done + differently on non-Desktop Apple platforms. + */ +#if defined(MA_APPLE_MOBILE) + pDeviceStateCoreAudio->pNotificationHandler = (MA_BRIDGE_RETAINED void*)[[ma_ios_notification_handler alloc] init:pDevice]; +#endif + + *ppDeviceState = pDeviceStateCoreAudio; + + return MA_SUCCESS; } -static ma_bool32 ma_does_AudioObject_support_playback(ma_context* pContext, AudioObjectID deviceObjectID) -{ - return ma_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeOutput); -} -static ma_bool32 ma_does_AudioObject_support_capture(ma_context* pContext, AudioObjectID deviceObjectID) +static ma_result ma_device_start__coreaudio(ma_device* pDevice) { - return ma_does_AudioObject_support_scope(pContext, deviceObjectID, kAudioObjectPropertyScopeInput); -} + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + OSStatus status = pContextStateCoreAudio->AudioOutputUnitStart(pDeviceStateCoreAudio->audioUnitCapture); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } + } -static ma_result ma_get_AudioObject_stream_descriptions(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, UInt32* pDescriptionCount, AudioStreamRangedDescription** ppDescriptions) /* NOTE: Free the returned pointer with ma_free(). */ -{ - AudioObjectPropertyAddress propAddress; - UInt32 dataSize; - OSStatus status; - AudioStreamRangedDescription* pDescriptions; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + OSStatus status = pContextStateCoreAudio->AudioOutputUnitStart(pDeviceStateCoreAudio->audioUnitPlayback); + if (status != noErr) { + if (deviceType == ma_device_type_duplex) { + pContextStateCoreAudio->AudioOutputUnitStop(pDeviceStateCoreAudio->audioUnitCapture); + } + return ma_result_from_OSStatus(status); + } + } - MA_ASSERT(pContext != NULL); - MA_ASSERT(pDescriptionCount != NULL); - MA_ASSERT(ppDescriptions != NULL); + return MA_SUCCESS; +} - /* - TODO: Experiment with kAudioStreamPropertyAvailablePhysicalFormats instead of (or in addition to) kAudioStreamPropertyAvailableVirtualFormats. My - MacBook Pro uses s24/32 format, however, which miniaudio does not currently support. - */ - propAddress.mSelector = kAudioStreamPropertyAvailableVirtualFormats; /*kAudioStreamPropertyAvailablePhysicalFormats;*/ - propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; +static ma_result ma_device_stop__coreaudio(ma_device* pDevice) +{ + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + ma_context_state_coreaudio* pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); - status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); - if (status != noErr) { - return ma_result_from_OSStatus(status); - } + /* It's not clear from the documentation whether or not AudioOutputUnitStop() actually drains the device or not. */ - pDescriptions = (AudioStreamRangedDescription*)ma_malloc(dataSize, &pContext->allocationCallbacks); - if (pDescriptions == NULL) { - return MA_OUT_OF_MEMORY; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + OSStatus status = pContextStateCoreAudio->AudioOutputUnitStop(pDeviceStateCoreAudio->audioUnitCapture); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } } - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pDescriptions); - if (status != noErr) { - ma_free(pDescriptions, &pContext->allocationCallbacks); - return ma_result_from_OSStatus(status); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + OSStatus status = pContextStateCoreAudio->AudioOutputUnitStop(pDeviceStateCoreAudio->audioUnitPlayback); + if (status != noErr) { + return ma_result_from_OSStatus(status); + } } - *pDescriptionCount = dataSize / sizeof(*pDescriptions); - *ppDescriptions = pDescriptions; + /* We need to wait for the callback to finish before returning. */ + ma_event_wait(&pDeviceStateCoreAudio->stopEvent); return MA_SUCCESS; } -static ma_result ma_get_AudioObject_channel_layout(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, AudioChannelLayout** ppChannelLayout) /* NOTE: Free the returned pointer with ma_free(). */ +static ma_result ma_device_step_extra__coreaudio(ma_device* pDevice) { - AudioObjectPropertyAddress propAddress; - UInt32 dataSize; - OSStatus status; - AudioChannelLayout* pChannelLayout; + #if defined(MA_APPLE_DESKTOP) + { + ma_result result = MA_SUCCESS; + ma_bool32 wasReinitialized = MA_FALSE; + ma_device_state_coreaudio* pDeviceStateCoreAudio; + ma_context_state_coreaudio* pContextStateCoreAudio; + ma_device_type deviceType = ma_device_get_type(pDevice); - MA_ASSERT(pContext != NULL); - MA_ASSERT(ppChannelLayout != NULL); + pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + pContextStateCoreAudio = ma_context_get_backend_state__coreaudio(ma_device_get_context(pDevice)); - *ppChannelLayout = NULL; /* Safety. */ + if (ma_atomic_bool32_get(&pDeviceStateCoreAudio->isSwitchingPlaybackDevice)) { + result = ma_device_reinit_internal__coreaudio(pDevice, ma_device_type_playback, MA_TRUE); + ma_atomic_bool32_set(&pDeviceStateCoreAudio->isSwitchingPlaybackDevice, MA_FALSE); + wasReinitialized = MA_TRUE; + } - propAddress.mSelector = kAudioDevicePropertyPreferredChannelLayout; - propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + if (ma_atomic_bool32_get(&pDeviceStateCoreAudio->isSwitchingCaptureDevice)) { + result = ma_device_reinit_internal__coreaudio(pDevice, ma_device_type_capture, MA_TRUE); + ma_atomic_bool32_set(&pDeviceStateCoreAudio->isSwitchingCaptureDevice, MA_FALSE); + wasReinitialized = MA_TRUE; + } - status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); - if (status != noErr) { - return ma_result_from_OSStatus(status); - } + if (wasReinitialized && result == MA_SUCCESS) { + /* Restart the device if required. If this fails we need to stop the device entirely. */ + if (ma_device_get_status(pDevice) == ma_device_status_started) { + OSStatus status; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + status = pContextStateCoreAudio->AudioOutputUnitStart(pDeviceStateCoreAudio->audioUnitPlayback); + if (status != noErr) { + if (deviceType == ma_device_type_duplex) { + pContextStateCoreAudio->AudioOutputUnitStop(pDeviceStateCoreAudio->audioUnitCapture); + } - pChannelLayout = (AudioChannelLayout*)ma_malloc(dataSize, &pContext->allocationCallbacks); - if (pChannelLayout == NULL) { - return MA_OUT_OF_MEMORY; - } + return ma_result_from_OSStatus(status); + } + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + status = pContextStateCoreAudio->AudioOutputUnitStart(pDeviceStateCoreAudio->audioUnitCapture); + if (status != noErr) { + if (deviceType == ma_device_type_duplex) { + pContextStateCoreAudio->AudioOutputUnitStop(pDeviceStateCoreAudio->audioUnitPlayback); + } - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pChannelLayout); - if (status != noErr) { - ma_free(pChannelLayout, &pContext->allocationCallbacks); - return ma_result_from_OSStatus(status); - } + return ma_result_from_OSStatus(status); + } + } + } - *ppChannelLayout = pChannelLayout; + ma_device_post_notification_rerouted(pDevice); + } + } + #else + { + /* iOS. Nothing to do here. */ + (void)pDevice; + } + #endif + return MA_SUCCESS; } -static ma_result ma_get_AudioObject_channel_count(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32* pChannelCount) +static ma_result ma_device_step__coreaudio(ma_device* pDevice, ma_blocking_mode blockingMode) { - AudioChannelLayout* pChannelLayout; - ma_result result; + ma_device_state_coreaudio* pDeviceStateCoreAudio = ma_device_get_backend_state__coreaudio(pDevice); + return ma_device_state_async_step(&pDeviceStateCoreAudio->async, pDevice, blockingMode, ma_device_step_extra__coreaudio); +} - MA_ASSERT(pContext != NULL); - MA_ASSERT(pChannelCount != NULL); +static ma_device_backend_vtable ma_gDeviceBackendVTable_CoreAudio = +{ + ma_backend_info__coreaudio, + ma_context_init__coreaudio, + ma_context_uninit__coreaudio, + ma_context_enumerate_devices__coreaudio, + ma_device_init__coreaudio, + ma_device_uninit__coreaudio, + ma_device_start__coreaudio, + ma_device_stop__coreaudio, + ma_device_step__coreaudio, + NULL /* onDeviceWakeup */ +}; - *pChannelCount = 0; /* Safety. */ +ma_device_backend_vtable* ma_device_backend_coreaudio = &ma_gDeviceBackendVTable_CoreAudio; +#else +ma_device_backend_vtable* ma_device_backend_coreaudio = NULL; +#endif /* MA_HAS_COREAUDIO */ - result = ma_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); - if (result != MA_SUCCESS) { - return result; - } +MA_API ma_device_backend_vtable* ma_coreaudio_get_vtable(void) +{ + return ma_device_backend_coreaudio; +} - if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { - *pChannelCount = pChannelLayout->mNumberChannelDescriptions; - } else if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { - *pChannelCount = ma_count_set_bits(pChannelLayout->mChannelBitmap); - } else { - *pChannelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); - } +MA_API ma_context_config_coreaudio ma_context_config_coreaudio_init(void) +{ + ma_context_config_coreaudio config; - ma_free(pChannelLayout, &pContext->allocationCallbacks); - return MA_SUCCESS; + MA_ZERO_OBJECT(&config); + + return config; } -#if 0 -static ma_result ma_get_AudioObject_channel_map(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap) +MA_API ma_device_config_coreaudio ma_device_config_coreaudio_init(void) { - AudioChannelLayout* pChannelLayout; - ma_result result; + ma_device_config_coreaudio config; - MA_ASSERT(pContext != NULL); + MA_ZERO_OBJECT(&config); - result = ma_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); - if (result != MA_SUCCESS) { - return result; /* Rather than always failing here, would it be more robust to simply assume a default? */ - } + return config; +} - result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); - if (result != MA_SUCCESS) { - ma_free(pChannelLayout, &pContext->allocationCallbacks); - return result; - } - ma_free(pChannelLayout, &pContext->allocationCallbacks); - return result; -} + +/****************************************************************************** + +sndio Backend + +******************************************************************************/ +#ifdef MA_HAS_SNDIO +#include +#include + +/* +Only supporting OpenBSD. This did not work very well at all on FreeBSD when I tried it. Not sure if this is due +to miniaudio's implementation or if it's some kind of system configuration issue, but basically the default device +just doesn't emit any sound, or at times you'll hear tiny pieces. I will consider enabling this when there's +demand for it or if I can get it tested and debugged more thoroughly. +*/ +#if 0 +#if defined(__NetBSD__) || defined(__OpenBSD__) +#include +#endif +#if defined(__FreeBSD__) || defined(__DragonFly__) +#include +#endif #endif -static ma_result ma_get_AudioObject_sample_rates(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, UInt32* pSampleRateRangesCount, AudioValueRange** ppSampleRateRanges) /* NOTE: Free the returned pointer with ma_free(). */ +#define MA_SIO_DEVANY "default" +#define MA_SIO_PLAY 1 +#define MA_SIO_REC 2 +#define MA_SIO_NENC 8 +#define MA_SIO_NCHAN 8 +#define MA_SIO_NRATE 16 +#define MA_SIO_NCONF 4 + +struct ma_sio_hdl; /* <-- Opaque */ + +struct ma_sio_par { - AudioObjectPropertyAddress propAddress; - UInt32 dataSize; - OSStatus status; - AudioValueRange* pSampleRateRanges; + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + unsigned int rchan; + unsigned int pchan; + unsigned int rate; + unsigned int bufsz; + unsigned int xrun; + unsigned int round; + unsigned int appbufsz; + int __pad[3]; + unsigned int __magic; +}; - MA_ASSERT(pContext != NULL); - MA_ASSERT(pSampleRateRangesCount != NULL); - MA_ASSERT(ppSampleRateRanges != NULL); +struct ma_sio_enc +{ + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; +}; - /* Safety. */ - *pSampleRateRangesCount = 0; - *ppSampleRateRanges = NULL; +struct ma_sio_conf +{ + unsigned int enc; + unsigned int rchan; + unsigned int pchan; + unsigned int rate; +}; - propAddress.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; - propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; +struct ma_sio_cap +{ + struct ma_sio_enc enc[MA_SIO_NENC]; + unsigned int rchan[MA_SIO_NCHAN]; + unsigned int pchan[MA_SIO_NCHAN]; + unsigned int rate[MA_SIO_NRATE]; + int __pad[7]; + unsigned int nconf; + struct ma_sio_conf confs[MA_SIO_NCONF]; +}; - status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); - if (status != noErr) { - return ma_result_from_OSStatus(status); - } +typedef struct ma_sio_hdl* (* ma_sio_open_proc )(const char*, unsigned int, int); +typedef void (* ma_sio_close_proc )(struct ma_sio_hdl*); +typedef int (* ma_sio_setpar_proc )(struct ma_sio_hdl*, struct ma_sio_par*); +typedef int (* ma_sio_getpar_proc )(struct ma_sio_hdl*, struct ma_sio_par*); +typedef int (* ma_sio_getcap_proc )(struct ma_sio_hdl*, struct ma_sio_cap*); +typedef int (* ma_sio_start_proc )(struct ma_sio_hdl*); +typedef int (* ma_sio_stop_proc )(struct ma_sio_hdl*); +typedef size_t (* ma_sio_read_proc )(struct ma_sio_hdl*, void*, size_t); +typedef size_t (* ma_sio_write_proc )(struct ma_sio_hdl*, const void*, size_t); +typedef int (* ma_sio_nfds_proc )(struct ma_sio_hdl*); +typedef int (* ma_sio_pollfd_proc )(struct ma_sio_hdl*, struct pollfd*, int); +typedef int (* ma_sio_revents_proc)(struct ma_sio_hdl*, struct pollfd*); +typedef int (* ma_sio_initpar_proc)(struct ma_sio_par*); - pSampleRateRanges = (AudioValueRange*)ma_malloc(dataSize, &pContext->allocationCallbacks); - if (pSampleRateRanges == NULL) { - return MA_OUT_OF_MEMORY; - } +typedef struct ma_context_state_sndio +{ + ma_handle sndioSO; + ma_sio_open_proc sio_open; + ma_sio_close_proc sio_close; + ma_sio_setpar_proc sio_setpar; + ma_sio_getpar_proc sio_getpar; + ma_sio_getcap_proc sio_getcap; + ma_sio_start_proc sio_start; + ma_sio_stop_proc sio_stop; + ma_sio_read_proc sio_read; + ma_sio_write_proc sio_write; + ma_sio_nfds_proc sio_nfds; + ma_sio_pollfd_proc sio_pollfd; + ma_sio_revents_proc sio_revents; + ma_sio_initpar_proc sio_initpar; +} ma_context_state_sndio; + +typedef struct ma_device_state_sndio +{ + struct pollfd* pPollFDs; + void* pIntermediaryBuffer; + struct + { + struct ma_sio_hdl* handle; + } playback, capture; +} ma_device_state_sndio; - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pSampleRateRanges); - if (status != noErr) { - ma_free(pSampleRateRanges, &pContext->allocationCallbacks); - return ma_result_from_OSStatus(status); - } - *pSampleRateRangesCount = dataSize / sizeof(*pSampleRateRanges); - *ppSampleRateRanges = pSampleRateRanges; - return MA_SUCCESS; +static ma_context_state_sndio* ma_context_get_backend_state__sndio(ma_context* pContext) +{ + return (ma_context_state_sndio*)ma_context_get_backend_state(pContext); } -#if 0 -static ma_result ma_get_AudioObject_get_closest_sample_rate(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32 sampleRateIn, ma_uint32* pSampleRateOut) +static ma_device_state_sndio* ma_device_get_backend_state__sndio(ma_device* pDevice) { - UInt32 sampleRateRangeCount; - AudioValueRange* pSampleRateRanges; - ma_result result; + return (ma_device_state_sndio*)ma_device_get_backend_state(pDevice); +} - MA_ASSERT(pContext != NULL); - MA_ASSERT(pSampleRateOut != NULL); - *pSampleRateOut = 0; /* Safety. */ +static void ma_backend_info__sndio(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "sndio"; +} - result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); - if (result != MA_SUCCESS) { - return result; - } +static ma_result ma_context_init__sndio(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_sndio* pContextStateSndio; + const ma_context_config_sndio* pContextConfigSndio = (const ma_context_config_sndio*)pContextBackendConfig; + ma_context_config_sndio defaultConfigSndio; - if (sampleRateRangeCount == 0) { - ma_free(pSampleRateRanges, &pContext->allocationCallbacks); - return MA_ERROR; /* Should never hit this case should we? */ + if (pContextConfigSndio == NULL) { + defaultConfigSndio = ma_context_config_sndio_init(); + pContextConfigSndio = &defaultConfigSndio; } - if (sampleRateIn == 0) { - /* Search in order of miniaudio's preferred priority. */ - UInt32 iMALSampleRate; - for (iMALSampleRate = 0; iMALSampleRate < ma_countof(g_maStandardSampleRatePriorities); ++iMALSampleRate) { - ma_uint32 malSampleRate = g_maStandardSampleRatePriorities[iMALSampleRate]; - UInt32 iCASampleRate; - for (iCASampleRate = 0; iCASampleRate < sampleRateRangeCount; ++iCASampleRate) { - AudioValueRange caSampleRate = pSampleRateRanges[iCASampleRate]; - if (caSampleRate.mMinimum <= malSampleRate && caSampleRate.mMaximum >= malSampleRate) { - *pSampleRateOut = malSampleRate; - ma_free(pSampleRateRanges, &pContext->allocationCallbacks); - return MA_SUCCESS; - } - } - } - - /* - If we get here it means none of miniaudio's standard sample rates matched any of the supported sample rates from the device. In this - case we just fall back to the first one reported by Core Audio. - */ - MA_ASSERT(sampleRateRangeCount > 0); + pContextStateSndio = (ma_context_state_sndio*)ma_calloc(sizeof(*pContextStateSndio), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateSndio == NULL) { + return MA_OUT_OF_MEMORY; + } - *pSampleRateOut = pSampleRateRanges[0].mMinimum; - ma_free(pSampleRateRanges, &pContext->allocationCallbacks); - return MA_SUCCESS; - } else { - /* Find the closest match to this sample rate. */ - UInt32 currentAbsoluteDifference = INT32_MAX; - UInt32 iCurrentClosestRange = (UInt32)-1; - UInt32 iRange; - for (iRange = 0; iRange < sampleRateRangeCount; ++iRange) { - if (pSampleRateRanges[iRange].mMinimum <= sampleRateIn && pSampleRateRanges[iRange].mMaximum >= sampleRateIn) { - *pSampleRateOut = sampleRateIn; - ma_free(pSampleRateRanges, &pContext->allocationCallbacks); - return MA_SUCCESS; - } else { - UInt32 absoluteDifference; - if (pSampleRateRanges[iRange].mMinimum > sampleRateIn) { - absoluteDifference = pSampleRateRanges[iRange].mMinimum - sampleRateIn; - } else { - absoluteDifference = sampleRateIn - pSampleRateRanges[iRange].mMaximum; - } + #ifndef MA_NO_RUNTIME_LINKING + { + const char* libsndioNames[] = { + "libsndio.so" + }; + size_t i; - if (currentAbsoluteDifference > absoluteDifference) { - currentAbsoluteDifference = absoluteDifference; - iCurrentClosestRange = iRange; - } + for (i = 0; i < ma_countof(libsndioNames); ++i) { + pContextStateSndio->sndioSO = ma_dlopen(ma_context_get_log(pContext), libsndioNames[i]); + if (pContextStateSndio->sndioSO != NULL) { + break; } } - MA_ASSERT(iCurrentClosestRange != (UInt32)-1); + if (pContextStateSndio->sndioSO == NULL) { + ma_free(pContextStateSndio, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } - *pSampleRateOut = pSampleRateRanges[iCurrentClosestRange].mMinimum; - ma_free(pSampleRateRanges, &pContext->allocationCallbacks); - return MA_SUCCESS; + pContextStateSndio->sio_open = (ma_sio_open_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_open"); + pContextStateSndio->sio_close = (ma_sio_close_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_close"); + pContextStateSndio->sio_setpar = (ma_sio_setpar_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_setpar"); + pContextStateSndio->sio_getpar = (ma_sio_getpar_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_getpar"); + pContextStateSndio->sio_getcap = (ma_sio_getcap_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_getcap"); + pContextStateSndio->sio_start = (ma_sio_start_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_start"); + pContextStateSndio->sio_stop = (ma_sio_stop_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_stop"); + pContextStateSndio->sio_read = (ma_sio_read_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_read"); + pContextStateSndio->sio_write = (ma_sio_write_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_write"); + pContextStateSndio->sio_nfds = (ma_sio_nfds_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_nfds"); + pContextStateSndio->sio_pollfd = (ma_sio_pollfd_proc )ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_pollfd"); + pContextStateSndio->sio_revents = (ma_sio_revents_proc)ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_revents"); + pContextStateSndio->sio_initpar = (ma_sio_initpar_proc)ma_dlsym(ma_context_get_log(pContext), pContextStateSndio->sndioSO, "sio_initpar"); + } + #else + { + pContextStateSndio->sio_open = sio_open; + pContextStateSndio->sio_close = sio_close; + pContextStateSndio->sio_setpar = sio_setpar; + pContextStateSndio->sio_getpar = sio_getpar; + pContextStateSndio->sio_getcap = sio_getcap; + pContextStateSndio->sio_start = sio_start; + pContextStateSndio->sio_stop = sio_stop; + pContextStateSndio->sio_read = sio_read; + pContextStateSndio->sio_write = sio_write; + pContextStateSndio->sio_nfds = sio_nfds; + pContextStateSndio->sio_pollfd = sio_pollfd; + pContextStateSndio->sio_revents = sio_revents; + pContextStateSndio->sio_initpar = sio_initpar; } + #endif - /* Should never get here, but it would mean we weren't able to find any suitable sample rates. */ - /*ma_free(pSampleRateRanges, &pContext->allocationCallbacks);*/ - /*return MA_ERROR;*/ + *ppContextState = pContextStateSndio; + + return MA_SUCCESS; } -#endif -static ma_result ma_get_AudioObject_closest_buffer_size_in_frames(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32 bufferSizeInFramesIn, ma_uint32* pBufferSizeInFramesOut) +static void ma_context_uninit__sndio(ma_context* pContext) { - AudioObjectPropertyAddress propAddress; - AudioValueRange bufferSizeRange; - UInt32 dataSize; - OSStatus status; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(pBufferSizeInFramesOut != NULL); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(pContext); - *pBufferSizeInFramesOut = 0; /* Safety. */ + #ifndef MA_NO_RUNTIME_LINKING + { + ma_dlclose(ma_context_get_log(pContext), pContextStateSndio->sndioSO); + } + #endif - propAddress.mSelector = kAudioDevicePropertyBufferFrameSizeRange; - propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + ma_free(pContextStateSndio, ma_context_get_allocation_callbacks(pContext)); +} - dataSize = sizeof(bufferSizeRange); - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &bufferSizeRange); - if (status != noErr) { - return ma_result_from_OSStatus(status); - } - /* This is just a clamp. */ - if (bufferSizeInFramesIn < bufferSizeRange.mMinimum) { - *pBufferSizeInFramesOut = (ma_uint32)bufferSizeRange.mMinimum; - } else if (bufferSizeInFramesIn > bufferSizeRange.mMaximum) { - *pBufferSizeInFramesOut = (ma_uint32)bufferSizeRange.mMaximum; - } else { - *pBufferSizeInFramesOut = bufferSizeInFramesIn; +static ma_uint32 ma_get_standard_sample_rate_priority_index__sndio(ma_uint32 sampleRate) /* Lower = higher priority */ +{ + ma_uint32 i; + for (i = 0; i < ma_countof(ma_standard_sample_rates); ++i) { + if (ma_standard_sample_rates[i] == sampleRate) { + return i; + } } - return MA_SUCCESS; + return (ma_uint32)-1; } -static ma_result ma_set_AudioObject_buffer_size_in_frames(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_uint32* pPeriodSizeInOut) +static ma_format ma_format_from_sio_enc__sndio(unsigned int bits, unsigned int bps, unsigned int sig, unsigned int le, unsigned int msb) { - ma_result result; - ma_uint32 chosenBufferSizeInFrames; - AudioObjectPropertyAddress propAddress; - UInt32 dataSize; - OSStatus status; - - MA_ASSERT(pContext != NULL); - - result = ma_get_AudioObject_closest_buffer_size_in_frames(pContext, deviceObjectID, deviceType, *pPeriodSizeInOut, &chosenBufferSizeInFrames); - if (result != MA_SUCCESS) { - return result; + /* We only support native-endian right now. */ + if ((ma_is_little_endian() && le == 0) || (ma_is_big_endian() && le == 1)) { + return ma_format_unknown; } - /* Try setting the size of the buffer... If this fails we just use whatever is currently set. */ - propAddress.mSelector = kAudioDevicePropertyBufferFrameSize; - propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - - ((ma_AudioObjectSetPropertyData_proc)pContext->coreaudio.AudioObjectSetPropertyData)(deviceObjectID, &propAddress, 0, NULL, sizeof(chosenBufferSizeInFrames), &chosenBufferSizeInFrames); - - /* Get the actual size of the buffer. */ - dataSize = sizeof(*pPeriodSizeInOut); - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &chosenBufferSizeInFrames); - if (status != noErr) { - return ma_result_from_OSStatus(status); + if (bits == 8 && bps == 1 && sig == 0) { + return ma_format_u8; + } + if (bits == 16 && bps == 2 && sig == 1) { + return ma_format_s16; + } + if (bits == 24 && bps == 3 && sig == 1) { + return ma_format_s24; + } + if (bits == 24 && bps == 4 && sig == 1 && msb == 0) { + /*return ma_format_s24_32;*/ + } + if (bits == 32 && bps == 4 && sig == 1) { + return ma_format_s32; } - *pPeriodSizeInOut = chosenBufferSizeInFrames; - return MA_SUCCESS; + return ma_format_unknown; } -static ma_result ma_find_default_AudioObjectID(ma_context* pContext, ma_device_type deviceType, AudioObjectID* pDeviceObjectID) +static ma_format ma_find_best_format_from_sio_cap__sndio(struct ma_sio_cap* caps) { - AudioObjectPropertyAddress propAddressDefaultDevice; - UInt32 defaultDeviceObjectIDSize = sizeof(AudioObjectID); - AudioObjectID defaultDeviceObjectID; - OSStatus status; + ma_format bestFormat; + unsigned int iConfig; - MA_ASSERT(pContext != NULL); - MA_ASSERT(pDeviceObjectID != NULL); + MA_ASSERT(caps != NULL); - /* Safety. */ - *pDeviceObjectID = 0; + bestFormat = ma_format_unknown; + for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + unsigned int iEncoding; + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + ma_format format; - propAddressDefaultDevice.mScope = kAudioObjectPropertyScopeGlobal; - propAddressDefaultDevice.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; - if (deviceType == ma_device_type_playback) { - propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - } else { - propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultInputDevice; - } + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } - defaultDeviceObjectIDSize = sizeof(AudioObjectID); - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDefaultDevice, 0, NULL, &defaultDeviceObjectIDSize, &defaultDeviceObjectID); - if (status == noErr) { - *pDeviceObjectID = defaultDeviceObjectID; - return MA_SUCCESS; + bits = caps->enc[iEncoding].bits; + bps = caps->enc[iEncoding].bps; + sig = caps->enc[iEncoding].sig; + le = caps->enc[iEncoding].le; + msb = caps->enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format == ma_format_unknown) { + continue; /* Format not supported. */ + } + + if (bestFormat == ma_format_unknown) { + bestFormat = format; + } else { + if (ma_get_format_priority_index(bestFormat) > ma_get_format_priority_index(format)) { /* <-- Lower = better. */ + bestFormat = format; + } + } + } } - /* If we get here it means we couldn't find the device. */ - return MA_NO_DEVICE; + return bestFormat; } -static ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, AudioObjectID* pDeviceObjectID) +static ma_uint32 ma_find_best_channels_from_sio_cap__sndio(struct ma_sio_cap* caps, ma_device_type deviceType, ma_format requiredFormat) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pDeviceObjectID != NULL); - - /* Safety. */ - *pDeviceObjectID = 0; + ma_uint32 maxChannels; + unsigned int iConfig; - if (pDeviceID == NULL) { - /* Default device. */ - return ma_find_default_AudioObjectID(pContext, deviceType, pDeviceObjectID); - } else { - /* Explicit device. */ - UInt32 deviceCount; - AudioObjectID* pDeviceObjectIDs; - ma_result result; - UInt32 iDevice; + MA_ASSERT(caps != NULL); + MA_ASSERT(requiredFormat != ma_format_unknown); - result = ma_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); - if (result != MA_SUCCESS) { - return result; - } + /* Just pick whatever configuration has the most channels. */ + maxChannels = 0; + for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + /* The encoding should be of requiredFormat. */ + unsigned int iEncoding; + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int iChannel; + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + ma_format format; - for (iDevice = 0; iDevice < deviceCount; ++iDevice) { - AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } - char uid[256]; - if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(uid), uid) != MA_SUCCESS) { + bits = caps->enc[iEncoding].bits; + bps = caps->enc[iEncoding].bps; + sig = caps->enc[iEncoding].sig; + le = caps->enc[iEncoding].le; + msb = caps->enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format != requiredFormat) { continue; } - if (deviceType == ma_device_type_playback) { - if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) { - if (strcmp(uid, pDeviceID->coreaudio) == 0) { - *pDeviceObjectID = deviceObjectID; - ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); - return MA_SUCCESS; - } + /* Getting here means the format is supported. Iterate over each channel count and grab the biggest one. */ + for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { + unsigned int chan = 0; + unsigned int channels; + + if (deviceType == ma_device_type_playback) { + chan = caps->confs[iConfig].pchan; + } else { + chan = caps->confs[iConfig].rchan; } - } else { - if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) { - if (strcmp(uid, pDeviceID->coreaudio) == 0) { - *pDeviceObjectID = deviceObjectID; - ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); - return MA_SUCCESS; - } + + if ((chan & (1UL << iChannel)) == 0) { + continue; + } + + if (deviceType == ma_device_type_playback) { + channels = caps->pchan[iChannel]; + } else { + channels = caps->rchan[iChannel]; + } + + if (maxChannels < channels) { + maxChannels = channels; } } } - - ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); } - /* If we get here it means we couldn't find the device. */ - return MA_NO_DEVICE; + return maxChannels; } - -static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const AudioStreamBasicDescription* pOrigFormat, AudioStreamBasicDescription* pFormat) +static ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* caps, ma_device_type deviceType, ma_format requiredFormat, ma_uint32 requiredChannels) { - UInt32 deviceFormatDescriptionCount; - AudioStreamRangedDescription* pDeviceFormatDescriptions; - ma_result result; - ma_uint32 desiredSampleRate; - ma_uint32 desiredChannelCount; - ma_format desiredFormat; - AudioStreamBasicDescription bestDeviceFormatSoFar; - ma_bool32 hasSupportedFormat; - UInt32 iFormat; + ma_uint32 firstSampleRate; + ma_uint32 bestSampleRate; + unsigned int iConfig; - result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &deviceFormatDescriptionCount, &pDeviceFormatDescriptions); - if (result != MA_SUCCESS) { - return result; - } + MA_ASSERT(caps != NULL); + MA_ASSERT(requiredFormat != ma_format_unknown); + MA_ASSERT(requiredChannels > 0); + MA_ASSERT(requiredChannels <= MA_MAX_CHANNELS); - desiredSampleRate = sampleRate; - if (desiredSampleRate == 0) { - desiredSampleRate = (ma_uint32)pOrigFormat->mSampleRate; - } + firstSampleRate = 0; /* <-- If the device does not support a standard rate we'll fall back to the first one that's found. */ + bestSampleRate = 0; - desiredChannelCount = channels; - if (desiredChannelCount == 0) { - desiredChannelCount = pOrigFormat->mChannelsPerFrame; - } + for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { + /* The encoding should be of requiredFormat. */ + unsigned int iEncoding; + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int iChannel; + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + ma_format format; - desiredFormat = format; - if (desiredFormat == ma_format_unknown) { - result = ma_format_from_AudioStreamBasicDescription(pOrigFormat, &desiredFormat); - if (result != MA_SUCCESS || desiredFormat == ma_format_unknown) { - desiredFormat = g_maFormatPriorities[0]; - } - } + if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } - /* - If we get here it means we don't have an exact match to what the client is asking for. We'll need to find the closest one. The next - loop will check for formats that have the same sample rate to what we're asking for. If there is, we prefer that one in all cases. - */ - MA_ZERO_OBJECT(&bestDeviceFormatSoFar); + bits = caps->enc[iEncoding].bits; + bps = caps->enc[iEncoding].bps; + sig = caps->enc[iEncoding].sig; + le = caps->enc[iEncoding].le; + msb = caps->enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format != requiredFormat) { + continue; + } - hasSupportedFormat = MA_FALSE; - for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { - ma_format formatFromDescription; - ma_result formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &formatFromDescription); - if (formatResult == MA_SUCCESS && formatFromDescription != ma_format_unknown) { - hasSupportedFormat = MA_TRUE; - bestDeviceFormatSoFar = pDeviceFormatDescriptions[iFormat].mFormat; - break; - } - } + /* Getting here means the format is supported. Iterate over each channel count and grab the biggest one. */ + for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { + unsigned int chan = 0; + unsigned int channels; + unsigned int iRate; - if (!hasSupportedFormat) { - ma_free(pDeviceFormatDescriptions, &pContext->allocationCallbacks); - return MA_FORMAT_NOT_SUPPORTED; - } + if (deviceType == ma_device_type_playback) { + chan = caps->confs[iConfig].pchan; + } else { + chan = caps->confs[iConfig].rchan; + } + if ((chan & (1UL << iChannel)) == 0) { + continue; + } - for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { - AudioStreamBasicDescription thisDeviceFormat = pDeviceFormatDescriptions[iFormat].mFormat; - ma_format thisSampleFormat; - ma_result formatResult; - ma_format bestSampleFormatSoFar; + if (deviceType == ma_device_type_playback) { + channels = caps->pchan[iChannel]; + } else { + channels = caps->rchan[iChannel]; + } - /* If the format is not supported by miniaudio we need to skip this one entirely. */ - formatResult = ma_format_from_AudioStreamBasicDescription(&pDeviceFormatDescriptions[iFormat].mFormat, &thisSampleFormat); - if (formatResult != MA_SUCCESS || thisSampleFormat == ma_format_unknown) { - continue; /* The format is not supported by miniaudio. Skip. */ - } + if (channels != requiredChannels) { + continue; + } - ma_format_from_AudioStreamBasicDescription(&bestDeviceFormatSoFar, &bestSampleFormatSoFar); + /* Getting here means we have found a compatible encoding/channel pair. */ + for (iRate = 0; iRate < MA_SIO_NRATE; iRate += 1) { + ma_uint32 rate = (ma_uint32)caps->rate[iRate]; + ma_uint32 ratePriority; - /* Getting here means the format is supported by miniaudio which makes this format a candidate. */ - if (thisDeviceFormat.mSampleRate != desiredSampleRate) { - /* - The sample rate does not match, but this format could still be usable, although it's a very low priority. If the best format - so far has an equal sample rate we can just ignore this one. - */ - if (bestDeviceFormatSoFar.mSampleRate == desiredSampleRate) { - continue; /* The best sample rate so far has the same sample rate as what we requested which means it's still the best so far. Skip this format. */ - } else { - /* In this case, neither the best format so far nor this one have the same sample rate. Check the channel count next. */ - if (thisDeviceFormat.mChannelsPerFrame != desiredChannelCount) { - /* This format has a different sample rate _and_ a different channel count. */ - if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { - continue; /* No change to the best format. */ - } else { - /* - Both this format and the best so far have different sample rates and different channel counts. Whichever has the - best format is the new best. - */ - if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; /* No change to the best format. */ - } - } - } else { - /* This format has a different sample rate but the desired channel count. */ - if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { - /* Both this format and the best so far have the desired channel count. Whichever has the best format is the new best. */ - if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; /* No change to the best format for now. */ - } - } else { - /* This format has the desired channel count, but the best so far does not. We have a new best. */ - bestDeviceFormatSoFar = thisDeviceFormat; - continue; + if (firstSampleRate == 0) { + firstSampleRate = rate; } - } - } - } else { - /* - The sample rates match which makes this format a very high priority contender. If the best format so far has a different - sample rate it needs to be replaced with this one. - */ - if (bestDeviceFormatSoFar.mSampleRate != desiredSampleRate) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - /* In this case both this format and the best format so far have the same sample rate. Check the channel count next. */ - if (thisDeviceFormat.mChannelsPerFrame == desiredChannelCount) { - /* - In this case this format has the same channel count as what the client is requesting. If the best format so far has - a different count, this one becomes the new best. - */ - if (bestDeviceFormatSoFar.mChannelsPerFrame != desiredChannelCount) { - bestDeviceFormatSoFar = thisDeviceFormat; + + /* Disregard this rate if it's not a standard one. */ + ratePriority = ma_get_standard_sample_rate_priority_index__sndio(rate); + if (ratePriority == (ma_uint32)-1) { continue; - } else { - /* In this case both this format and the best so far have the ideal sample rate and channel count. Check the format. */ - if (thisSampleFormat == desiredFormat) { - bestDeviceFormatSoFar = thisDeviceFormat; - break; /* Found the exact match. */ - } else { - /* The formats are different. The new best format is the one with the highest priority format according to miniaudio. */ - if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; /* No change to the best format for now. */ - } - } } - } else { - /* - In this case the channel count is different to what the client has requested. If the best so far has the same channel - count as the requested count then it remains the best. - */ - if (bestDeviceFormatSoFar.mChannelsPerFrame == desiredChannelCount) { - continue; - } else { - /* - This is the case where both have the same sample rate (good) but different channel counts. Right now both have about - the same priority, but we need to compare the format now. - */ - if (thisSampleFormat == bestSampleFormatSoFar) { - if (ma_get_format_priority_index(thisSampleFormat) < ma_get_format_priority_index(bestSampleFormatSoFar)) { - bestDeviceFormatSoFar = thisDeviceFormat; - continue; - } else { - continue; /* No change to the best format for now. */ - } - } + + if (ma_get_standard_sample_rate_priority_index__sndio(bestSampleRate) > ratePriority) { /* Lower = better. */ + bestSampleRate = rate; } } } } } - *pFormat = bestDeviceFormatSoFar; + /* If a standard sample rate was not found just fall back to the first one that was iterated. */ + if (bestSampleRate == 0) { + bestSampleRate = firstSampleRate; + } - ma_free(pDeviceFormatDescriptions, &pContext->allocationCallbacks); - return MA_SUCCESS; + return bestSampleRate; } -static ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit audioUnit, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap) +static ma_device_enumeration_result ma_context_enumerate_device_from_handle__sndio(ma_context* pContext, struct ma_sio_hdl* handle, const char* name, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) { - AudioUnitScope deviceScope; - AudioUnitElement deviceBus; - UInt32 channelLayoutSize; - OSStatus status; - AudioChannelLayout* pChannelLayout; - ma_result result; + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(pContext); + ma_device_info deviceInfo; + struct ma_sio_cap caps; + unsigned int iConfig; - MA_ASSERT(pContext != NULL); + /* We're only supporting default devices for now. */ + MA_ASSERT(strcmp(name, MA_SIO_DEVANY) == 0); - if (deviceType == ma_device_type_playback) { - deviceScope = kAudioUnitScope_Input; - deviceBus = MA_COREAUDIO_OUTPUT_BUS; - } else { - deviceScope = kAudioUnitScope_Output; - deviceBus = MA_COREAUDIO_INPUT_BUS; - } + MA_ZERO_OBJECT(&deviceInfo); - status = ((ma_AudioUnitGetPropertyInfo_proc)pContext->coreaudio.AudioUnitGetPropertyInfo)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, &channelLayoutSize, NULL); - if (status != noErr) { - return ma_result_from_OSStatus(status); - } + /* Default. */ + deviceInfo.isDefault = MA_TRUE; - pChannelLayout = (AudioChannelLayout*)ma_malloc(channelLayoutSize, &pContext->allocationCallbacks); - if (pChannelLayout == NULL) { - return MA_OUT_OF_MEMORY; - } + /* ID. */ + ma_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), name); - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, pChannelLayout, &channelLayoutSize); - if (status != noErr) { - ma_free(pChannelLayout, &pContext->allocationCallbacks); - return ma_result_from_OSStatus(status); + /* Name. */ + if (deviceType == ma_device_type_playback) { + ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME); + } else { + ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME); } - result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); - if (result != MA_SUCCESS) { - ma_free(pChannelLayout, &pContext->allocationCallbacks); - return result; + /* Format. */ + if (pContextStateSndio->sio_getcap(handle, &caps) == 0) { + return MA_DEVICE_ENUMERATION_CONTINUE; } - ma_free(pChannelLayout, &pContext->allocationCallbacks); - return MA_SUCCESS; -} -#endif /* MA_APPLE_DESKTOP */ + deviceInfo.nativeDataFormatCount = 0; + for (iConfig = 0; iConfig < caps.nconf; iConfig += 1) { + /* + The main thing we care about is that the encoding is supported by miniaudio. If it is, we want to give + preference to some formats over others. + */ + unsigned int iEncoding; + unsigned int iChannel; + unsigned int iRate; -#if !defined(MA_APPLE_DESKTOP) -static void ma_AVAudioSessionPortDescription_to_device_info(AVAudioSessionPortDescription* pPortDesc, ma_device_info* pInfo) -{ - MA_ZERO_OBJECT(pInfo); - ma_strncpy_s(pInfo->name, sizeof(pInfo->name), [pPortDesc.portName UTF8String], (size_t)-1); - ma_strncpy_s(pInfo->id.coreaudio, sizeof(pInfo->id.coreaudio), [pPortDesc.UID UTF8String], (size_t)-1); -} -#endif + for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { + unsigned int bits; + unsigned int bps; + unsigned int sig; + unsigned int le; + unsigned int msb; + ma_format format; -static ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ -#if defined(MA_APPLE_DESKTOP) - UInt32 deviceCount; - AudioObjectID* pDeviceObjectIDs; - AudioObjectID defaultDeviceObjectIDPlayback; - AudioObjectID defaultDeviceObjectIDCapture; - ma_result result; - UInt32 iDevice; + if ((caps.confs[iConfig].enc & (1UL << iEncoding)) == 0) { + continue; + } - ma_find_default_AudioObjectID(pContext, ma_device_type_playback, &defaultDeviceObjectIDPlayback); /* OK if this fails. */ - ma_find_default_AudioObjectID(pContext, ma_device_type_capture, &defaultDeviceObjectIDCapture); /* OK if this fails. */ + bits = caps.enc[iEncoding].bits; + bps = caps.enc[iEncoding].bps; + sig = caps.enc[iEncoding].sig; + le = caps.enc[iEncoding].le; + msb = caps.enc[iEncoding].msb; + format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); + if (format != ma_format_unknown) { + unsigned int minChannels = 0xFFFFFFFF; + unsigned int maxChannels = 0; + unsigned int minSampleRate = 0xFFFFFFFF; + unsigned int maxSampleRate = 0; - result = ma_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); - if (result != MA_SUCCESS) { - return result; - } + /* Channels. */ + for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { + unsigned int chan = 0; - for (iDevice = 0; iDevice < deviceCount; ++iDevice) { - AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; - ma_device_info info; + if (deviceType == ma_device_type_playback) { + chan = caps.confs[iConfig].pchan; + } else { + chan = caps.confs[iConfig].rchan; + } - MA_ZERO_OBJECT(&info); - if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(info.id.coreaudio), info.id.coreaudio) != MA_SUCCESS) { - continue; - } - if (ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(info.name), info.name) != MA_SUCCESS) { - continue; - } + if ((chan & (1UL << iChannel)) != 0) { + unsigned int channels; - if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) { - if (deviceObjectID == defaultDeviceObjectIDPlayback) { - info.isDefault = MA_TRUE; - } + if (deviceType == ma_device_type_playback) { + channels = caps.pchan[iChannel]; + } else { + channels = caps.rchan[iChannel]; + } + + if (minChannels > channels) { + minChannels = channels; + } + if (maxChannels < channels) { + maxChannels = channels; + } + } + } - if (!callback(pContext, ma_device_type_playback, &info, pUserData)) { - break; - } - } - if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) { - if (deviceObjectID == defaultDeviceObjectIDCapture) { - info.isDefault = MA_TRUE; - } + /* Sample Rates. */ + for (iRate = 0; iRate < MA_SIO_NRATE; iRate += 1) { + if ((caps.confs[iConfig].rate & (1UL << iRate)) != 0) { + unsigned int sampleRate = caps.rate[iRate]; - if (!callback(pContext, ma_device_type_capture, &info, pUserData)) { - break; + if (minSampleRate > sampleRate) { + minSampleRate = sampleRate; + } + if (maxSampleRate < sampleRate) { + maxSampleRate = sampleRate; + } + } + } + + ma_device_info_add_native_data_format(&deviceInfo, format, minChannels, maxChannels, minSampleRate, maxSampleRate); + } else { + /* Format not supported. */ } } } - ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); -#else - ma_device_info info; - NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs]; - NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs]; + return callback(deviceType, &deviceInfo, pUserData); +} - for (AVAudioSessionPortDescription* pPortDesc in pOutputs) { - ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info); - if (!callback(pContext, ma_device_type_playback, &info, pUserData)) { - return MA_SUCCESS; +static ma_result ma_context_enumerate_devices__sndio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(pContext); + ma_bool32 isTerminating = MA_FALSE; + struct ma_sio_hdl* handle; + + /* sndio doesn't seem to have a good device enumeration API, so I'm therefore only enumerating over default devices for now. */ + + /* Playback. */ + if (!isTerminating) { + handle = pContextStateSndio->sio_open(MA_SIO_DEVANY, MA_SIO_PLAY, 0); + if (handle != NULL) { + isTerminating = (ma_context_enumerate_device_from_handle__sndio(pContext, handle, MA_SIO_DEVANY, ma_device_type_playback, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + pContextStateSndio->sio_close(handle); } } - for (AVAudioSessionPortDescription* pPortDesc in pInputs) { - ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info); - if (!callback(pContext, ma_device_type_capture, &info, pUserData)) { - return MA_SUCCESS; + /* Capture. */ + if (!isTerminating) { + handle = pContextStateSndio->sio_open(MA_SIO_DEVANY, MA_SIO_REC, 0); + if (handle != NULL) { + isTerminating = (ma_context_enumerate_device_from_handle__sndio(pContext, handle, MA_SIO_DEVANY, ma_device_type_capture, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + pContextStateSndio->sio_close(handle); } } -#endif return MA_SUCCESS; } -static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +static ma_result ma_device_init_by_type__sndio(ma_device* pDevice, ma_device_state_sndio* pDeviceStateSndio, const ma_device_config_sndio* pDeviceConfigSndio, ma_device_descriptor* pDescriptor, ma_device_type deviceType) { - ma_result result; - - MA_ASSERT(pContext != NULL); - -#if defined(MA_APPLE_DESKTOP) - /* Desktop */ - { - AudioObjectID deviceObjectID; - AudioObjectID defaultDeviceObjectID; - UInt32 streamDescriptionCount; - AudioStreamRangedDescription* pStreamDescriptions; - UInt32 iStreamDescription; - UInt32 sampleRateRangeCount; - AudioValueRange* pSampleRateRanges; - - ma_find_default_AudioObjectID(pContext, deviceType, &defaultDeviceObjectID); /* OK if this fails. */ - - result = ma_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); - if (result != MA_SUCCESS) { - return result; - } - - result = ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(pDeviceInfo->id.coreaudio), pDeviceInfo->id.coreaudio); - if (result != MA_SUCCESS) { - return result; - } - - result = ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(pDeviceInfo->name), pDeviceInfo->name); - if (result != MA_SUCCESS) { - return result; - } - - if (deviceObjectID == defaultDeviceObjectID) { - pDeviceInfo->isDefault = MA_TRUE; - } - - /* - There could be a large number of permutations here. Fortunately there is only a single channel count - being reported which reduces this quite a bit. For sample rates we're only reporting those that are - one of miniaudio's recognized "standard" rates. If there are still more formats than can fit into - our fixed sized array we'll just need to truncate them. This is unlikely and will probably only happen - if some driver performs software data conversion and therefore reports every possible format and - sample rate. - */ - pDeviceInfo->nativeDataFormatCount = 0; - - /* Formats. */ - { - ma_format uniqueFormats[ma_format_count]; - ma_uint32 uniqueFormatCount = 0; - ma_uint32 channels; - - /* Channels. */ - result = ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &channels); - if (result != MA_SUCCESS) { - return result; - } - - /* Formats. */ - result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions); - if (result != MA_SUCCESS) { - return result; - } - - for (iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) { - ma_format format; - ma_bool32 hasFormatBeenHandled = MA_FALSE; - ma_uint32 iOutputFormat; - ma_uint32 iSampleRate; - - result = ma_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format); - if (result != MA_SUCCESS) { - continue; - } - - MA_ASSERT(format != ma_format_unknown); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + const char* pDeviceName; + struct ma_sio_hdl* handle; + int openFlags = 0; + struct ma_sio_cap caps; + struct ma_sio_par par; + const ma_device_id* pDeviceID; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_uint32 internalPeriodSizeInFrames; + ma_uint32 internalPeriods; - /* Make sure the format isn't already in the output list. */ - for (iOutputFormat = 0; iOutputFormat < uniqueFormatCount; ++iOutputFormat) { - if (uniqueFormats[iOutputFormat] == format) { - hasFormatBeenHandled = MA_TRUE; - break; - } - } + MA_ASSERT(deviceType != ma_device_type_duplex); + MA_ASSERT(pDevice != NULL); - /* If we've already handled this format just skip it. */ - if (hasFormatBeenHandled) { - continue; - } + (void)pDeviceConfigSndio; - uniqueFormats[uniqueFormatCount] = format; - uniqueFormatCount += 1; + if (deviceType == ma_device_type_capture) { + openFlags = MA_SIO_REC; + } else { + openFlags = MA_SIO_PLAY; + } - /* Sample Rates */ - result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); - if (result != MA_SUCCESS) { - return result; - } + pDeviceID = pDescriptor->pDeviceID; + format = pDescriptor->format; + channels = pDescriptor->channels; + sampleRate = pDescriptor->sampleRate; - /* - Annoyingly Core Audio reports a sample rate range. We just get all the standard rates that are - between this range. - */ - for (iSampleRate = 0; iSampleRate < sampleRateRangeCount; ++iSampleRate) { - ma_uint32 iStandardSampleRate; - for (iStandardSampleRate = 0; iStandardSampleRate < ma_countof(g_maStandardSampleRatePriorities); iStandardSampleRate += 1) { - ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iStandardSampleRate]; - if (standardSampleRate >= pSampleRateRanges[iSampleRate].mMinimum && standardSampleRate <= pSampleRateRanges[iSampleRate].mMaximum) { - /* We have a new data format. Add it to the list. */ - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = standardSampleRate; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; - pDeviceInfo->nativeDataFormatCount += 1; - - if (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) { - break; /* No more room for any more formats. */ - } - } - } - } + pDeviceName = MA_SIO_DEVANY; + if (pDeviceID != NULL) { + pDeviceName = pDeviceID->sndio; + } - ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + handle = pContextStateSndio->sio_open(pDeviceName, openFlags, 0); + if (handle == NULL) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } - if (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) { - break; /* No more room for any more formats. */ - } - } + /* We need to retrieve the device caps to determine the most appropriate format to use. */ + if (pContextStateSndio->sio_getcap(handle, &caps) == 0) { + pContextStateSndio->sio_close(handle); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps."); + return MA_ERROR; + } + + /* + Note: sndio reports a huge range of available channels. This is inconvenient for us because there's no real + way, as far as I can tell, to get the _actual_ channel count of the device. I'm therefore restricting this + to the requested channels, regardless of whether or not the default channel count is requested. - ma_free(pStreamDescriptions, &pContext->allocationCallbacks); + For hardware devices, I'm suspecting only a single channel count will be reported and we can safely use the + value returned by ma_find_best_channels_from_sio_cap__sndio(). + */ + if (deviceType == ma_device_type_capture) { + if (format == ma_format_unknown) { + format = ma_find_best_format_from_sio_cap__sndio(&caps); } - } -#else - /* Mobile */ - { - AudioComponentDescription desc; - AudioComponent component; - AudioUnit audioUnit; - OSStatus status; - AudioUnitScope formatScope; - AudioUnitElement formatElement; - AudioStreamBasicDescription bestFormat; - UInt32 propSize; - - /* We want to ensure we use a consistent device name to device enumeration. */ - if (pDeviceID != NULL && pDeviceID->coreaudio[0] != '\0') { - ma_bool32 found = MA_FALSE; - if (deviceType == ma_device_type_playback) { - NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs]; - for (AVAudioSessionPortDescription* pPortDesc in pOutputs) { - if (strcmp(pDeviceID->coreaudio, [pPortDesc.UID UTF8String]) == 0) { - ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, pDeviceInfo); - found = MA_TRUE; - break; - } - } - } else { - NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs]; - for (AVAudioSessionPortDescription* pPortDesc in pInputs) { - if (strcmp(pDeviceID->coreaudio, [pPortDesc.UID UTF8String]) == 0) { - ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, pDeviceInfo); - found = MA_TRUE; - break; - } - } - } - if (!found) { - return MA_DOES_NOT_EXIST; - } - } else { - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + if (channels == 0) { + if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { + channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + channels = MA_DEFAULT_CHANNELS; } } - - - /* - Retrieving device information is more annoying on mobile than desktop. For simplicity I'm locking this down to whatever format is - reported on a temporary I/O unit. The problem, however, is that this doesn't return a value for the sample rate which we need to - retrieve from the AVAudioSession shared instance. - */ - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_RemoteIO; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - component = ((ma_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); - if (component == NULL) { - return MA_FAILED_TO_INIT_BACKEND; + } else { + if (format == ma_format_unknown) { + format = ma_find_best_format_from_sio_cap__sndio(&caps); } - status = ((ma_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)(component, &audioUnit); - if (status != noErr) { - return ma_result_from_OSStatus(status); + if (channels == 0) { + if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { + channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); + } else { + channels = MA_DEFAULT_CHANNELS; + } } + } - formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; - formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; + if (sampleRate == 0) { + sampleRate = ma_find_best_sample_rate_from_sio_cap__sndio(&caps, deviceType, format, channels); + } - propSize = sizeof(bestFormat); - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); - return ma_result_from_OSStatus(status); - } - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); - audioUnit = NULL; + pContextStateSndio->sio_initpar(&par); + par.msb = 0; + par.le = ma_is_little_endian(); - /* Only a single format is being reported for iOS. */ - pDeviceInfo->nativeDataFormatCount = 1; + switch (format) { + case ma_format_u8: + { + par.bits = 8; + par.bps = 1; + par.sig = 0; + } break; - result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pDeviceInfo->nativeDataFormats[0].format); - if (result != MA_SUCCESS) { - return result; - } + case ma_format_s24: + { + par.bits = 24; + par.bps = 3; + par.sig = 1; + } break; - pDeviceInfo->nativeDataFormats[0].channels = bestFormat.mChannelsPerFrame; + case ma_format_s32: + { + par.bits = 32; + par.bps = 4; + par.sig = 1; + } break; - /* - It looks like Apple are wanting to push the whole AVAudioSession thing. Thus, we need to use that to determine device settings. To do - this we just get the shared instance and inspect. - */ - @autoreleasepool { - AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; - MA_ASSERT(pAudioSession != NULL); + case ma_format_s16: + case ma_format_f32: + case ma_format_unknown: + default: + { + par.bits = 16; + par.bps = 2; + par.sig = 1; + } break; + } - pDeviceInfo->nativeDataFormats[0].sampleRate = (ma_uint32)pAudioSession.sampleRate; - } + if (deviceType == ma_device_type_capture) { + par.rchan = channels; + } else { + par.pchan = channels; } -#endif - (void)pDeviceInfo; /* Unused. */ - return MA_SUCCESS; -} + par.rate = sampleRate; -static AudioBufferList* ma_allocate_AudioBufferList__coreaudio(ma_uint32 sizeInFrames, ma_format format, ma_uint32 channels, ma_stream_layout layout, const ma_allocation_callbacks* pAllocationCallbacks) -{ - AudioBufferList* pBufferList; - UInt32 audioBufferSizeInBytes; - size_t allocationSize; + internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, par.rate); - MA_ASSERT(sizeInFrames > 0); - MA_ASSERT(format != ma_format_unknown); - MA_ASSERT(channels > 0); + par.round = internalPeriodSizeInFrames; + par.appbufsz = par.round * pDescriptor->periodCount; - allocationSize = sizeof(AudioBufferList) - sizeof(AudioBuffer); /* Subtract sizeof(AudioBuffer) because that part is dynamically sized. */ - if (layout == ma_stream_layout_interleaved) { - /* Interleaved case. This is the simple case because we just have one buffer. */ - allocationSize += sizeof(AudioBuffer) * 1; - } else { - /* Non-interleaved case. This is the more complex case because there's more than one buffer. */ - allocationSize += sizeof(AudioBuffer) * channels; + if (pContextStateSndio->sio_setpar(handle, &par) == 0) { + pContextStateSndio->sio_close(handle); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size."); + return MA_ERROR; } - allocationSize += sizeInFrames * ma_get_bytes_per_frame(format, channels); - - pBufferList = (AudioBufferList*)ma_malloc(allocationSize, pAllocationCallbacks); - if (pBufferList == NULL) { - return NULL; + if (pContextStateSndio->sio_getpar(handle, &par) == 0) { + pContextStateSndio->sio_close(handle); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size."); + return MA_ERROR; } - audioBufferSizeInBytes = (UInt32)(sizeInFrames * ma_get_bytes_per_sample(format)); + internalFormat = ma_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); + internalChannels = (deviceType == ma_device_type_capture) ? par.rchan : par.pchan; + internalSampleRate = par.rate; + internalPeriods = par.appbufsz / par.round; + internalPeriodSizeInFrames = par.round; - if (layout == ma_stream_layout_interleaved) { - pBufferList->mNumberBuffers = 1; - pBufferList->mBuffers[0].mNumberChannels = channels; - pBufferList->mBuffers[0].mDataByteSize = audioBufferSizeInBytes * channels; - pBufferList->mBuffers[0].mData = (ma_uint8*)pBufferList + sizeof(AudioBufferList); + if (deviceType == ma_device_type_capture) { + pDeviceStateSndio->capture.handle = handle; } else { - ma_uint32 iBuffer; - pBufferList->mNumberBuffers = channels; - for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { - pBufferList->mBuffers[iBuffer].mNumberChannels = 1; - pBufferList->mBuffers[iBuffer].mDataByteSize = audioBufferSizeInBytes; - pBufferList->mBuffers[iBuffer].mData = (ma_uint8*)pBufferList + ((sizeof(AudioBufferList) - sizeof(AudioBuffer)) + (sizeof(AudioBuffer) * channels)) + (audioBufferSizeInBytes * iBuffer); - } + pDeviceStateSndio->playback.handle = handle; } - return pBufferList; + pDescriptor->format = internalFormat; + pDescriptor->channels = internalChannels; + pDescriptor->sampleRate = internalSampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_sndio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); + pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; + pDescriptor->periodCount = internalPeriods; + + return MA_SUCCESS; } -static ma_result ma_device_realloc_AudioBufferList__coreaudio(ma_device* pDevice, ma_uint32 sizeInFrames, ma_format format, ma_uint32 channels, ma_stream_layout layout) -{ - MA_ASSERT(pDevice != NULL); - MA_ASSERT(format != ma_format_unknown); - MA_ASSERT(channels > 0); - /* Only resize the buffer if necessary. */ - if (pDevice->coreaudio.audioBufferCapInFrames < sizeInFrames) { - AudioBufferList* pNewAudioBufferList; +static void ma_device_uninit_internal__sndio(ma_device* pDevice, ma_device_state_sndio* pDeviceStateSndio) +{ + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); - pNewAudioBufferList = ma_allocate_AudioBufferList__coreaudio(sizeInFrames, format, channels, layout, &pDevice->pContext->allocationCallbacks); - if (pNewAudioBufferList == NULL) { - return MA_OUT_OF_MEMORY; - } + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStateSndio->sio_close(pDeviceStateSndio->capture.handle); + } - /* At this point we'll have a new AudioBufferList and we can free the old one. */ - ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); - pDevice->coreaudio.pAudioBufferList = pNewAudioBufferList; - pDevice->coreaudio.audioBufferCapInFrames = sizeInFrames; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStateSndio->sio_close(pDeviceStateSndio->playback.handle); } - /* Getting here means the capacity of the audio is fine. */ - return MA_SUCCESS; + ma_free(pDeviceStateSndio, ma_device_get_allocation_callbacks(pDevice)); } - -static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pBufferList) +static ma_result ma_device_init__sndio(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - ma_device* pDevice = (ma_device*)pUserData; - ma_stream_layout layout; - - MA_ASSERT(pDevice != NULL); + ma_device_state_sndio* pDeviceStateSndio; + const ma_device_config_sndio* pDeviceConfigSndio = (ma_device_config_sndio*)pDeviceBackendConfig; + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + ma_device_config_sndio defaultConfigSndio; + ma_device_type deviceType = ma_device_get_type(pDevice); + int nfdsCapture = 0; + int nfdsPlayback = 0; + size_t intermediaryBufferSizeCapture = 0; + size_t intermediaryBufferSizePlayback = 0; + size_t pollFDsSize = 0; + size_t intermediaryBufferSize = 0; - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pBufferList->mNumberBuffers);*/ + if (pDeviceConfigSndio == NULL) { + defaultConfigSndio = ma_device_config_sndio_init(); + pDeviceConfigSndio = &defaultConfigSndio; + } - /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ - layout = ma_stream_layout_interleaved; - if (pBufferList->mBuffers[0].mNumberChannels != pDevice->playback.internalChannels) { - layout = ma_stream_layout_deinterleaved; + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - if (layout == ma_stream_layout_interleaved) { - /* For now we can assume everything is interleaved. */ - UInt32 iBuffer; - for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { - if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->playback.internalChannels) { - ma_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - if (frameCountForThisBuffer > 0) { - ma_device_handle_backend_data_callback(pDevice, pBufferList->mBuffers[iBuffer].mData, NULL, frameCountForThisBuffer); - } + pDeviceStateSndio = (ma_device_state_sndio*)ma_calloc(sizeof(*pDeviceStateSndio), ma_device_get_allocation_callbacks(pDevice)); + if (pDevice == NULL) { + return MA_OUT_OF_MEMORY; + } - /*a_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ - } else { - /* - This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - not interleaved, in which case we can't handle right now since miniaudio does not yet support non-interleaved streams. We just - output silence here. - */ - MA_ZERO_MEMORY(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ - } + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_init_by_type__sndio(pDevice, pDeviceStateSndio, pDeviceConfigSndio, pDescriptorCapture, ma_device_type_capture); + if (result != MA_SUCCESS) { + ma_device_uninit_internal__sndio(pDevice, pDeviceStateSndio); + return result; } - } else { - /* This is the deinterleaved case. We need to update each buffer in groups of internalChannels. This assumes each buffer is the same size. */ - MA_ASSERT(pDevice->playback.internalChannels <= MA_MAX_CHANNELS); /* This should have been validated at initialization time. */ - /* - For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something - very strange has happened and we're not going to support it. - */ - if ((pBufferList->mNumberBuffers % pDevice->playback.internalChannels) == 0) { - ma_uint8 tempBuffer[4096]; - UInt32 iBuffer; + nfdsCapture = pContextStateSndio->sio_nfds(pDeviceStateSndio->capture.handle); + intermediaryBufferSizeCapture = ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodSizeInFrames; + } - for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; iBuffer += pDevice->playback.internalChannels) { - ma_uint32 frameCountPerBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_sample(pDevice->playback.internalFormat); - ma_uint32 framesRemaining = frameCountPerBuffer; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_init_by_type__sndio(pDevice, pDeviceStateSndio, pDeviceConfigSndio, pDescriptorPlayback, ma_device_type_playback); + if (result != MA_SUCCESS) { + ma_device_uninit_internal__sndio(pDevice, pDeviceStateSndio); + return result; + } - while (framesRemaining > 0) { - void* ppDeinterleavedBuffers[MA_MAX_CHANNELS]; - ma_uint32 iChannel; - ma_uint32 framesToRead = sizeof(tempBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - if (framesToRead > framesRemaining) { - framesToRead = framesRemaining; - } + nfdsPlayback = pContextStateSndio->sio_nfds(pDeviceStateSndio->playback.handle); + intermediaryBufferSizePlayback = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodSizeInFrames; + } - ma_device_handle_backend_data_callback(pDevice, tempBuffer, NULL, framesToRead); + /* + We need memory for our poll fds and intermediary buffer. This can be done as a single allocation, and we can put it at + the end of our device state allocation. + */ + pollFDsSize = ma_align_64(sizeof(struct pollfd) * (nfdsCapture + nfdsPlayback)); + intermediaryBufferSize = ma_align_64(ma_max(intermediaryBufferSizeCapture, intermediaryBufferSizePlayback)); - for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { - ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pBufferList->mBuffers[iBuffer+iChannel].mData, (frameCountPerBuffer - framesRemaining) * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); - } + { + size_t deviceStateAllocSizeNew; + ma_device_state_sndio* pDeviceStateSndioNew; - ma_deinterleave_pcm_frames(pDevice->playback.internalFormat, pDevice->playback.internalChannels, framesToRead, tempBuffer, ppDeinterleavedBuffers); + deviceStateAllocSizeNew = ma_align_64(sizeof(*pDeviceStateSndio)); + deviceStateAllocSizeNew += pollFDsSize; + deviceStateAllocSizeNew += intermediaryBufferSize; - framesRemaining -= framesToRead; - } - } + pDeviceStateSndioNew = (ma_device_state_sndio*)ma_realloc(pDeviceStateSndio, deviceStateAllocSizeNew, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateSndioNew == NULL) { + ma_device_uninit_internal__sndio(pDevice, pDeviceStateSndio); + return MA_OUT_OF_MEMORY; } + + pDeviceStateSndio = pDeviceStateSndioNew; } - (void)pActionFlags; - (void)pTimeStamp; - (void)busNumber; - (void)frameCount; + pDeviceStateSndio->pPollFDs = (struct pollfd*)ma_offset_ptr(pDeviceStateSndio, ma_align_64(sizeof(*pDeviceStateSndio))); + pDeviceStateSndio->pIntermediaryBuffer = (void*)ma_offset_ptr(pDeviceStateSndio, ma_align_64(sizeof(*pDeviceStateSndio)) + pollFDsSize); - return noErr; + *ppDeviceState = pDeviceStateSndio; + + return MA_SUCCESS; } -static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pUnusedBufferList) +static void ma_device_uninit__sndio(ma_device* pDevice) { - ma_device* pDevice = (ma_device*)pUserData; - AudioBufferList* pRenderedBufferList; - ma_result result; - ma_stream_layout layout; - ma_uint32 iBuffer; - OSStatus status; + ma_device_state_sndio* pDeviceStateSndio = ma_device_get_backend_state__sndio(pDevice); + ma_device_uninit_internal__sndio(pDevice, pDeviceStateSndio); +} - MA_ASSERT(pDevice != NULL); +static void ma_device_prime_playback_buffer__sndio(ma_device* pDevice) +{ + ma_device_state_sndio* pDeviceStateSndio = ma_device_get_backend_state__sndio(pDevice); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint8 buffer[4096]; + ma_uint32 bpf; + ma_uint32 framesToWrite; + ma_uint32 framesWritten; - pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; - MA_ASSERT(pRenderedBufferList); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + framesToWrite = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods; + framesWritten = 0; + + MA_ZERO_MEMORY(buffer, sizeof(buffer)); + + while (framesWritten < framesToWrite) { + ma_uint32 framesToWriteThisIteration = sizeof(buffer) / bpf; + ma_uint32 framesRemaining = framesToWrite - framesWritten; + if (framesToWriteThisIteration > framesRemaining) { + framesToWriteThisIteration = framesRemaining; + } + + /* Just a guard to ensure we don't get stuck in a loop. Should never happen in practice (would require a massive channel count). */ + if (framesToWriteThisIteration == 0) { + break; + } + + pContextStateSndio->sio_write(pDeviceStateSndio->playback.handle, buffer, framesToWriteThisIteration * bpf); + framesWritten += framesToWriteThisIteration; + } + } +} - /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ - layout = ma_stream_layout_interleaved; - if (pRenderedBufferList->mBuffers[0].mNumberChannels != pDevice->capture.internalChannels) { - layout = ma_stream_layout_deinterleaved; +static ma_result ma_device_start__sndio(ma_device* pDevice) +{ + ma_device_state_sndio* pDeviceStateSndio = ma_device_get_backend_state__sndio(pDevice); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStateSndio->sio_start(pDeviceStateSndio->capture.handle); } - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pRenderedBufferList->mNumberBuffers);*/ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStateSndio->sio_start(pDeviceStateSndio->playback.handle); /* <-- Doesn't actually start until data is written. */ - /* - There has been a situation reported where frame count passed into this function is greater than the capacity of - our capture buffer. There doesn't seem to be a reliable way to determine what the maximum frame count will be, - so we need to instead resort to dynamically reallocating our buffer to ensure it's large enough to capture the - number of frames requested by this callback. - */ - result = ma_device_realloc_AudioBufferList__coreaudio(pDevice, frameCount, pDevice->capture.internalFormat, pDevice->capture.internalChannels, layout); - if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed to allocate AudioBufferList for capture.\n"); - return noErr; + /* + Prime the playback buffer with silence. This mainly for duplex mode which needs + a buffer of silence to give the capture side time to produce audio. + */ + ma_device_prime_playback_buffer__sndio(pDevice); } - pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; - MA_ASSERT(pRenderedBufferList); + return MA_SUCCESS; +} + +static ma_result ma_device_stop__sndio(ma_device* pDevice) +{ + ma_device_state_sndio* pDeviceStateSndio = ma_device_get_backend_state__sndio(pDevice); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); /* - When you call AudioUnitRender(), Core Audio tries to be helpful by setting the mDataByteSize to the number of bytes - that were actually rendered. The problem with this is that the next call can fail with -50 due to the size no longer - being set to the capacity of the buffer, but instead the size in bytes of the previous render. This will cause a - problem when a future call to this callback specifies a larger number of frames. + From the documentation: - To work around this we need to explicitly set the size of each buffer to their respective size in bytes. + The sio_stop() function puts the audio subsystem in the same state as before sio_start() is called. It stops recording, drains the play buffer and then + stops playback. If samples to play are queued but playback hasn't started yet then playback is forced immediately; playback will actually stop once the + buffer is drained. In no case are samples in the play buffer discarded. + + Therefore, sio_stop() performs all of the necessary draining for us. */ - for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { - pRenderedBufferList->mBuffers[iBuffer].mDataByteSize = pDevice->coreaudio.audioBufferCapInFrames * ma_get_bytes_per_sample(pDevice->capture.internalFormat) * pRenderedBufferList->mBuffers[iBuffer].mNumberChannels; - /*printf("DEBUG: nDataByteSize = %d\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pContextStateSndio->sio_stop(pDeviceStateSndio->capture.handle); } - status = ((ma_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); - if (status != noErr) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "ERROR: AudioUnitRender() failed with %d.\n", (int)status); - return status; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pContextStateSndio->sio_stop(pDeviceStateSndio->playback.handle); } - if (layout == ma_stream_layout_interleaved) { - for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { - if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->capture.internalChannels) { - ma_device_handle_backend_data_callback(pDevice, NULL, pRenderedBufferList->mBuffers[iBuffer].mData, frameCount); - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d.\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ - } else { - /* - This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's - not interleaved, in which case we can't handle right now since miniaudio does not yet support non-interleaved streams. - */ - ma_uint8 silentBuffer[4096]; - ma_uint32 framesRemaining; + return MA_SUCCESS; +} - MA_ZERO_MEMORY(silentBuffer, sizeof(silentBuffer)); - framesRemaining = frameCount; - while (framesRemaining > 0) { - ma_uint32 framesToSend = sizeof(silentBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - if (framesToSend > framesRemaining) { - framesToSend = framesRemaining; - } +static ma_result ma_device_read__sndio(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) +{ + ma_device_state_sndio* pDeviceStateSndio = ma_device_get_backend_state__sndio(pDevice); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + int resultSndio; - ma_device_handle_backend_data_callback(pDevice, NULL, silentBuffer, framesToSend); + if (pFramesRead != NULL) { + *pFramesRead = 0; + } - framesRemaining -= framesToSend; - } + resultSndio = pContextStateSndio->sio_read(pDeviceStateSndio->capture.handle, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + if (resultSndio == 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device."); + return MA_IO_ERROR; + } - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ - } - } - } else { - /* This is the deinterleaved case. We need to interleave the audio data before sending it to the client. This assumes each buffer is the same size. */ - MA_ASSERT(pDevice->capture.internalChannels <= MA_MAX_CHANNELS); /* This should have been validated at initialization time. */ + if (pFramesRead != NULL) { + *pFramesRead = frameCount; + } - /* - For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something - very strange has happened and we're not going to support it. - */ - if ((pRenderedBufferList->mNumberBuffers % pDevice->capture.internalChannels) == 0) { - ma_uint8 tempBuffer[4096]; - for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; iBuffer += pDevice->capture.internalChannels) { - ma_uint32 framesRemaining = frameCount; - while (framesRemaining > 0) { - void* ppDeinterleavedBuffers[MA_MAX_CHANNELS]; - ma_uint32 iChannel; - ma_uint32 framesToSend = sizeof(tempBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - if (framesToSend > framesRemaining) { - framesToSend = framesRemaining; - } + return MA_SUCCESS; +} - for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { - ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pRenderedBufferList->mBuffers[iBuffer+iChannel].mData, (frameCount - framesRemaining) * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); - } +static ma_result ma_device_write__sndio(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +{ + ma_device_state_sndio* pDeviceStateSndio = ma_device_get_backend_state__sndio(pDevice); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + int resultSndio; - ma_interleave_pcm_frames(pDevice->capture.internalFormat, pDevice->capture.internalChannels, framesToSend, (const void**)ppDeinterleavedBuffers, tempBuffer); - ma_device_handle_backend_data_callback(pDevice, NULL, tempBuffer, framesToSend); + if (pFramesWritten != NULL) { + *pFramesWritten = 0; + } - framesRemaining -= framesToSend; - } - } - } + resultSndio = pContextStateSndio->sio_write(pDeviceStateSndio->playback.handle, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + if (resultSndio == 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device."); + return MA_IO_ERROR; } - (void)pActionFlags; - (void)pTimeStamp; - (void)busNumber; - (void)frameCount; - (void)pUnusedBufferList; + if (pFramesWritten != NULL) { + *pFramesWritten = frameCount; + } - return noErr; + return MA_SUCCESS; } -static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, AudioUnitPropertyID propertyID, AudioUnitScope scope, AudioUnitElement element) + +static ma_result ma_device_step__sndio(ma_device* pDevice, ma_blocking_mode blockingMode) { - ma_device* pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + ma_device_state_sndio* pDeviceStateSndio = ma_device_get_backend_state__sndio(pDevice); + ma_context_state_sndio* pContextStateSndio = ma_context_get_backend_state__sndio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + int timeout = (blockingMode == MA_BLOCKING_MODE_BLOCKING) ? -1 : 0; + int resultPoll; + int pollFDCountCapture = 0; + int pollFDCountPlayback = 0; + unsigned short revents; - /* Don't do anything if it looks like we're just reinitializing due to a device switch. */ - if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isSwitchingPlaybackDevice) || - ((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isSwitchingCaptureDevice)) { - return; - } + do { + int pollFDCount = 0; - /* - There's been a report of a deadlock here when triggered by ma_device_uninit(). It looks like - AudioUnitGetProprty (called below) and AudioComponentInstanceDispose (called in ma_device_uninit) - can try waiting on the same lock. I'm going to try working around this by not calling any Core - Audio APIs in the callback when the device has been stopped or uninitialized. - */ - if (ma_device_get_state(pDevice) == ma_device_state_uninitialized || ma_device_get_state(pDevice) == ma_device_state_stopping || ma_device_get_state(pDevice) == ma_device_state_stopped) { - ma_device__on_notification_stopped(pDevice); - } else { - UInt32 isRunning; - UInt32 isRunningSize = sizeof(isRunning); - OSStatus status = ((ma_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); - if (status != noErr) { - goto done; /* Don't really know what to do in this case... just ignore it, I suppose... */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + pollFDCountCapture = pContextStateSndio->sio_pollfd(pDeviceStateSndio->capture.handle, pDeviceStateSndio->pPollFDs + pollFDCount, POLLIN); + pollFDCount += pollFDCountCapture; } - if (!isRunning) { - /* - The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: - - 1) When the device is unplugged, this will be called _before_ the default device change notification. - 2) When the device is changed via the default device change notification, this will be called _after_ the switch. - - For case #1, we just check if there's a new default device available. If so, we just ignore the stop event. For case #2 we check a flag. - */ - if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isDefaultPlaybackDevice) || - ((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isDefaultCaptureDevice)) { - /* - It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device - via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the - device to be seamless to the client (we don't want them receiving the stopped event and thinking that the device has stopped when it - hasn't!). - */ - if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isSwitchingPlaybackDevice) || - ((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isSwitchingCaptureDevice)) { - goto done; - } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + pollFDCountPlayback = pContextStateSndio->sio_pollfd(pDeviceStateSndio->playback.handle, pDeviceStateSndio->pPollFDs + pollFDCount, POLLOUT); + pollFDCount += pollFDCountPlayback; + } - /* - Getting here means the device is not reinitializing which means it may have been unplugged. From what I can see, it looks like Core Audio - will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most - likely be successful in switching to the new device. + resultPoll = poll(pDeviceStateSndio->pPollFDs, pollFDCount, timeout); + } while (resultPoll < 0 && errno == EINTR); - TODO: Try to predict if Core Audio will switch devices. If not, the stopped callback needs to be posted. - */ - goto done; - } + if (resultPoll < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed."); + return MA_ERROR; + } - /* Getting here means we need to stop the device. */ - ma_device__on_notification_stopped(pDevice); + /* + In the case of a timeout, this is expected for non-blocking mode and should not be + considered an error. In blocking mode however, we should never be getting a timeout. In + this case it probably means the PCM is stuck. We'll treat this as an error. + */ + if (resultPoll == 0) { /* Timeout. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + return MA_SUCCESS; + } else { + return MA_ERROR; } } - (void)propertyID; /* Unused. */ - -done: - /* Always signal the stop event. It's possible for the "else" case to get hit which can happen during an interruption. */ - ma_event_signal(&pDevice->coreaudio.stopEvent); -} + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } -#if defined(MA_APPLE_DESKTOP) -static ma_spinlock g_DeviceTrackingInitLock_CoreAudio = 0; /* A spinlock for mutal exclusion of the init/uninit of the global tracking data. Initialization to 0 is what we need. */ -static ma_uint32 g_DeviceTrackingInitCounter_CoreAudio = 0; -static ma_mutex g_DeviceTrackingMutex_CoreAudio; -static ma_device** g_ppTrackedDevices_CoreAudio = NULL; -static ma_uint32 g_TrackedDeviceCap_CoreAudio = 0; -static ma_uint32 g_TrackedDeviceCount_CoreAudio = 0; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + revents = pContextStateSndio->sio_revents(pDeviceStateSndio->capture.handle, pDeviceStateSndio->pPollFDs); + if ((revents & POLLHUP) != 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[sndio] POLLHUP detected."); + return MA_DEVICE_NOT_STARTED; + } -static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UInt32 addressCount, const AudioObjectPropertyAddress* pAddresses, void* pUserData) -{ - ma_device_type deviceType; + if ((revents & POLLERR) != 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[sndio] POLLERR detected."); + } - /* Not sure if I really need to check this, but it makes me feel better. */ - if (addressCount == 0) { - return noErr; - } + if ((revents & POLLIN) != 0) { + ma_uint32 framesRead; - if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultOutputDevice) { - deviceType = ma_device_type_playback; - } else if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultInputDevice) { - deviceType = ma_device_type_capture; - } else { - return noErr; /* Should never hit this. */ + result = ma_device_read__sndio(pDevice, pDeviceStateSndio->pIntermediaryBuffer, pDevice->capture.internalPeriodSizeInFrames, &framesRead); + if (result != MA_SUCCESS) { + return result; + } + + ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateSndio->pIntermediaryBuffer, framesRead); + } } - ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); - { - ma_uint32 iDevice; - for (iDevice = 0; iDevice < g_TrackedDeviceCount_CoreAudio; iDevice += 1) { - ma_result reinitResult; - ma_device* pDevice; - - pDevice = g_ppTrackedDevices_CoreAudio[iDevice]; - if (pDevice->type == deviceType || pDevice->type == ma_device_type_duplex) { - if (deviceType == ma_device_type_playback) { - pDevice->coreaudio.isSwitchingPlaybackDevice = MA_TRUE; - reinitResult = ma_device_reinit_internal__coreaudio(pDevice, deviceType, MA_TRUE); - pDevice->coreaudio.isSwitchingPlaybackDevice = MA_FALSE; - } else { - pDevice->coreaudio.isSwitchingCaptureDevice = MA_TRUE; - reinitResult = ma_device_reinit_internal__coreaudio(pDevice, deviceType, MA_TRUE); - pDevice->coreaudio.isSwitchingCaptureDevice = MA_FALSE; - } - - if (reinitResult == MA_SUCCESS) { - ma_device__post_init_setup(pDevice, deviceType); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + revents = pContextStateSndio->sio_revents(pDeviceStateSndio->playback.handle, pDeviceStateSndio->pPollFDs); + if ((revents & POLLHUP) != 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[sndio] POLLHUP detected."); + return MA_DEVICE_NOT_STARTED; + } - /* Restart the device if required. If this fails we need to stop the device entirely. */ - if (ma_device_get_state(pDevice) == ma_device_state_started) { - OSStatus status; - if (deviceType == ma_device_type_playback) { - status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); - if (status != noErr) { - if (pDevice->type == ma_device_type_duplex) { - ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); - } - ma_device__set_state(pDevice, ma_device_state_stopped); - } - } else if (deviceType == ma_device_type_capture) { - status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture); - if (status != noErr) { - if (pDevice->type == ma_device_type_duplex) { - ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); - } - ma_device__set_state(pDevice, ma_device_state_stopped); - } - } - } + if ((revents & POLLERR) != 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[sndio] POLLERR detected."); + } - ma_device__on_notification_rerouted(pDevice); - } + if ((revents & POLLOUT) != 0) { + ma_device_handle_backend_data_callback(pDevice, pDeviceStateSndio->pIntermediaryBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); + + result = ma_device_write__sndio(pDevice, pDeviceStateSndio->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames, NULL); + if (result != MA_SUCCESS) { + return result; } } } - ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); - - /* Unused parameters. */ - (void)objectID; - (void)pUserData; - return noErr; + return MA_SUCCESS; } -static ma_result ma_context__init_device_tracking__coreaudio(ma_context* pContext) +static ma_device_backend_vtable ma_gDeviceBackendVTable_sndio = { - MA_ASSERT(pContext != NULL); - - ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); - { - /* Don't do anything if we've already initialized device tracking. */ - if (g_DeviceTrackingInitCounter_CoreAudio == 0) { - AudioObjectPropertyAddress propAddress; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + ma_backend_info__sndio, + ma_context_init__sndio, + ma_context_uninit__sndio, + ma_context_enumerate_devices__sndio, + ma_device_init__sndio, + ma_device_uninit__sndio, + ma_device_start__sndio, + ma_device_stop__sndio, + ma_device_step__sndio, + NULL /* onDeviceWakeup */ +}; - ma_mutex_init(&g_DeviceTrackingMutex_CoreAudio); +ma_device_backend_vtable* ma_device_backend_sndio = &ma_gDeviceBackendVTable_sndio; +#else +ma_device_backend_vtable* ma_device_backend_sndio = NULL; +#endif /* MA_HAS_SNDIO */ - propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); +MA_API ma_device_backend_vtable* ma_sndio_get_vtable(void) +{ + return ma_device_backend_sndio; +} - propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); +MA_API ma_context_config_sndio ma_context_config_sndio_init(void) +{ + ma_context_config_sndio config; - } - g_DeviceTrackingInitCounter_CoreAudio += 1; - } - ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); + MA_ZERO_OBJECT(&config); - return MA_SUCCESS; + return config; } -static ma_result ma_context__uninit_device_tracking__coreaudio(ma_context* pContext) +MA_API ma_device_config_sndio ma_device_config_sndio_init(void) { - MA_ASSERT(pContext != NULL); + ma_device_config_sndio config; - ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); - { - if (g_DeviceTrackingInitCounter_CoreAudio > 0) - g_DeviceTrackingInitCounter_CoreAudio -= 1; + MA_ZERO_OBJECT(&config); - if (g_DeviceTrackingInitCounter_CoreAudio == 0) { - AudioObjectPropertyAddress propAddress; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + return config; +} - propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - /* At this point there should be no tracked devices. If not there's an error somewhere. */ - if (g_ppTrackedDevices_CoreAudio != NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active."); - ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); - return MA_INVALID_OPERATION; - } +/****************************************************************************** - ma_mutex_uninit(&g_DeviceTrackingMutex_CoreAudio); - } - } - ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); +audio(4) Backend - return MA_SUCCESS; -} +******************************************************************************/ +#ifdef MA_HAS_AUDIO4 +#include +#include +#include +#include /* For enumerating over /dev/audio* devices. */ +#include +#include +#include +#include -static ma_result ma_device__track__coreaudio(ma_device* pDevice) +#ifdef __NetBSD__ +#include +#endif + +#if defined(__OpenBSD__) + #include + #if defined(OpenBSD) && OpenBSD >= 201709 + #define MA_AUDIO4_USE_NEW_API + #endif +#endif + +typedef struct ma_context_state_audio4 { - MA_ASSERT(pDevice != NULL); + int _unused; +} ma_context_state_audio4; - ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); - { - /* Allocate memory if required. */ - if (g_TrackedDeviceCap_CoreAudio <= g_TrackedDeviceCount_CoreAudio) { - ma_uint32 newCap; - ma_device** ppNewDevices; +typedef struct ma_device_state_audio4 +{ + int fdPlayback; + int fdCapture; + void* pIntermediaryBuffer; +} ma_device_state_audio4; - newCap = g_TrackedDeviceCap_CoreAudio * 2; - if (newCap == 0) { - newCap = 1; - } - ppNewDevices = (ma_device**)ma_realloc(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, &pDevice->pContext->allocationCallbacks); - if (ppNewDevices == NULL) { - ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); - return MA_OUT_OF_MEMORY; - } +static ma_context_state_audio4* ma_context_get_backend_state__audio4(ma_context* pContext) +{ + return (ma_context_state_audio4*)ma_context_get_backend_state(pContext); +} - g_ppTrackedDevices_CoreAudio = ppNewDevices; - g_TrackedDeviceCap_CoreAudio = newCap; - } +static ma_device_state_audio4* ma_device_get_backend_state__audio4(ma_device* pDevice) +{ + return (ma_device_state_audio4*)ma_device_get_backend_state(pDevice); +} - g_ppTrackedDevices_CoreAudio[g_TrackedDeviceCount_CoreAudio] = pDevice; - g_TrackedDeviceCount_CoreAudio += 1; - } - ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); - return MA_SUCCESS; +static void ma_backend_info__audio4(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "audio(4)"; } -static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) +static ma_result ma_context_init__audio4(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) { - MA_ASSERT(pDevice != NULL); + ma_context_state_audio4* pContextStateAudio4; + const ma_context_config_audio4* pContextConfigAudio4 = (const ma_context_config_audio4*)pContextBackendConfig; + ma_context_config_audio4 defaultConfigAudio4; - ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); + /* Check if /dev/audioctl actually exists. If not, the backend cannot be used. */ + #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ { - ma_uint32 iDevice; - for (iDevice = 0; iDevice < g_TrackedDeviceCount_CoreAudio; iDevice += 1) { - if (g_ppTrackedDevices_CoreAudio[iDevice] == pDevice) { - /* We've found the device. We now need to remove it from the list. */ - ma_uint32 jDevice; - for (jDevice = iDevice; jDevice < g_TrackedDeviceCount_CoreAudio-1; jDevice += 1) { - g_ppTrackedDevices_CoreAudio[jDevice] = g_ppTrackedDevices_CoreAudio[jDevice+1]; - } + struct stat stDefault; - g_TrackedDeviceCount_CoreAudio -= 1; + if (stat("/dev/audioctl", &stDefault) < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[audio4] Failed to open /dev/audioctl. Backend not usable."); + return MA_NO_BACKEND; + } + } + #endif - /* If there's nothing else in the list we need to free memory. */ - if (g_TrackedDeviceCount_CoreAudio == 0) { - ma_free(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); - g_ppTrackedDevices_CoreAudio = NULL; - g_TrackedDeviceCap_CoreAudio = 0; - } + if (pContextConfigAudio4 == NULL) { + defaultConfigAudio4 = ma_context_config_audio4_init(); + pContextConfigAudio4 = &defaultConfigAudio4; + } - break; - } - } + (void)pContextConfigAudio4; + + pContextStateAudio4 = (ma_context_state_audio4*)ma_calloc(sizeof(*pContextStateAudio4), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateAudio4 == NULL) { + return MA_OUT_OF_MEMORY; } - ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); + + *ppContextState = pContextStateAudio4; return MA_SUCCESS; } -#endif -#if defined(MA_APPLE_MOBILE) -@interface ma_ios_notification_handler:NSObject { - ma_device* m_pDevice; +static void ma_context_uninit__audio4(ma_context* pContext) +{ + ma_context_state_audio4* pContextStateAudio4 = ma_context_get_backend_state__audio4(pContext); + + ma_free(pContextStateAudio4, ma_context_get_allocation_callbacks(pContext)); } -@end -@implementation ma_ios_notification_handler --(id)init:(ma_device*)pDevice +#if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ +static void ma_construct_device_id__audio4(char* id, size_t idSize, const char* base, int deviceIndex) { - self = [super init]; - m_pDevice = pDevice; + size_t baseLen; - /* For route changes. */ - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_route_change:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]]; + MA_ASSERT(id != NULL); + MA_ASSERT(idSize > 0); + MA_ASSERT(deviceIndex >= 0); - /* For interruptions. */ - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + baseLen = strlen(base); + MA_ASSERT(idSize > baseLen); - return self; + ma_strcpy_s(id, idSize, base); + ma_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10); } --(void)dealloc +static ma_format ma_format_from_encoding__audio4(unsigned int encoding, unsigned int precision) { - [self remove_handler]; + if (precision == 8) { + switch (encoding) + { + #if defined(AUDIO_ENCODING_LINEAR8) + case AUDIO_ENCODING_LINEAR8: return ma_format_u8; + #endif + #if defined(AUDIO_ENCODING_ULINEAR) + case AUDIO_ENCODING_ULINEAR: return ma_format_u8; + #endif + #if defined(AUDIO_ENCODING_ULINEAR_LE) + case AUDIO_ENCODING_ULINEAR_LE: return ma_format_u8; + #endif + #if defined(AUDIO_ENCODING_ULINEAR_BE) + case AUDIO_ENCODING_ULINEAR_BE: return ma_format_u8; + #endif + default: return ma_format_unknown; + } + } else { + #if defined(AUDIO_ENCODING_LINEAR) + { + if (encoding == AUDIO_ENCODING_LINEAR) { /* Assume native endian. */ + /* */ if (precision == 16) { + return ma_format_s16; + } else if (precision == 24) { + return ma_format_s24; + } else if (precision == 32) { + return ma_format_s32; + } + } + } + #endif - #if defined(__has_feature) - #if !__has_feature(objc_arc) - [super dealloc]; + #if defined(AUDIO_ENCODING_SLINEAR_LE) + { + if (encoding == AUDIO_ENCODING_SLINEAR_LE && ma_is_little_endian()) { /* Explicitly little-endian. Can only be supported on little-endian machines. */ + /* */ if (precision == 16) { + return ma_format_s16; + } else if (precision == 24) { + return ma_format_s24; + } else if (precision == 32) { + return ma_format_s32; + } + } + } #endif - #endif -} --(void)remove_handler -{ - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil]; + #if defined(AUDIO_ENCODING_SLINEAR_BE) + { + if (encoding == AUDIO_ENCODING_SLINEAR_BE && ma_is_big_endian()) { /* Explicitly big-endian. Can only be supported on big-endian machines. */ + /* */ if (precision == 16) { + return ma_format_s16; + } else if (precision == 24) { + return ma_format_s24; + } else if (precision == 32) { + return ma_format_s32; + } + } + } + #endif + } + + return ma_format_unknown; /* Encoding not supported. */ } --(void)handle_interruption:(NSNotification*)pNotification +static void ma_encoding_from_format__audio4(ma_format format, unsigned int* pEncoding, unsigned int* pPrecision) { - NSInteger type = [[[pNotification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue]; - switch (type) - { - case AVAudioSessionInterruptionTypeBegan: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeBegan\n"); + MA_ASSERT(pEncoding != NULL); + MA_ASSERT(pPrecision != NULL); - /* - Core Audio will have stopped the internal device automatically, but we need explicitly - stop it at a higher level to ensure miniaudio-specific state is updated for consistency. - */ - ma_device_stop(m_pDevice); + /* Encoding. */ + if (format == ma_format_u8) { + #if defined(AUDIO_ENCODING_ULINEAR) + *pEncoding = AUDIO_ENCODING_ULINEAR; + #elif defined(AUDIO_ENCODING_LINEAR8) + *pEncoding = AUDIO_ENCODING_LINEAR8; + #else + #error "Don't know how to encode format." + #endif + } else { + #if defined(AUDIO_ENCODING_LINEAR) + *pEncoding = AUDIO_ENCODING_LINEAR; + #else + { + if (ma_is_little_endian()) { + #if defined(AUDIO_ENCODING_SLINEAR_LE) + *pEncoding = AUDIO_ENCODING_SLINEAR_LE; + #else + MA_ASSERT(!"audio(4): Don't know how to set the encoding. This platform cannot use the audio(4) backend. Please submit a bug report."); + *pEncoding = 0; /* Don't know how to set the encoding... */ + #endif + } else { + #if defined(AUDIO_ENCODING_SLINEAR_LE) + *pEncoding = AUDIO_ENCODING_SLINEAR_LE; + #else + MA_ASSERT(!"audio(4): Don't know how to set the encoding. This platform cannot use the audio(4) backend. Please submit a bug report."); + *pEncoding = 0; /* Don't know how to set the encoding... */ + #endif + } + } + #endif + } - /* - Fire the notification after the device has been stopped to ensure it's in the correct - state when the notification handler is invoked. - */ - ma_device__on_notification_interruption_began(m_pDevice); - } break; + /* Precision. */ + switch (format) + { + case ma_format_u8: *pPrecision = 8; break; + case ma_format_s24: *pPrecision = 24; break; + case ma_format_s32: *pPrecision = 32; break; - case AVAudioSessionInterruptionTypeEnded: + /* Default everything else to 16-bit encoding. */ + case ma_format_s16: + case ma_format_f32: + case ma_format_unknown: + default: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeEnded\n"); - ma_device__on_notification_interruption_ended(m_pDevice); + *pPrecision = 16; } break; } } --(void)handle_route_change:(NSNotification*)pNotification +static ma_format ma_format_from_prinfo__audio4(struct audio_prinfo* prinfo) { - AVAudioSession* pSession = [AVAudioSession sharedInstance]; + return ma_format_from_encoding__audio4(prinfo->encoding, prinfo->precision); +} - NSInteger reason = [[[pNotification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; - switch (reason) - { - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOldDeviceUnavailable\n"); - } break; +#if !defined(MA_SUN) +static ma_format ma_best_format_from_fd__audio4(int fd, ma_format preferredFormat, ma_device_type deviceType) +{ + #if defined(MA_SUN) + audio_info_t info; - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNewDeviceAvailable\n"); - } break; + (void)preferredFormat; - case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory\n"); - } break; + if (ioctl(fd, AUDIO_GETINFO, &info) < 0) { + return ma_format_unknown; + } - case AVAudioSessionRouteChangeReasonWakeFromSleep: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonWakeFromSleep\n"); - } break; + if (deviceType == ma_device_type_playback) { + return ma_format_from_encoding__audio4(info.play.encoding, info.play.precision); + } else { + return ma_format_from_encoding__audio4(info.record.encoding, info.record.precision); + } + #else + audio_encoding_t encoding; + ma_uint32 iFormat; + int counter = 0; - case AVAudioSessionRouteChangeReasonOverride: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOverride\n"); - } break; + (void)deviceType; - case AVAudioSessionRouteChangeReasonCategoryChange: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonCategoryChange\n"); - } break; + /* First check to see if the preferred format is supported. */ + if (preferredFormat != ma_format_unknown) { + counter = 0; + for (;;) { + MA_ZERO_OBJECT(&encoding); + encoding.index = counter; + if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + break; + } - case AVAudioSessionRouteChangeReasonUnknown: - default: - { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonUnknown\n"); - } break; + if (preferredFormat == ma_format_from_encoding__audio4(encoding.encoding, encoding.precision)) { + return preferredFormat; /* Found the preferred format. */ + } + + /* Getting here means this encoding does not match our preferred format so we need to more on to the next encoding. */ + counter += 1; + } } - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Changing Route. inputNumberChannels=%d; outputNumberOfChannels=%d\n", (int)pSession.inputNumberOfChannels, (int)pSession.outputNumberOfChannels); + /* Getting here means our preferred format is not supported, so fall back to our standard priorities. */ + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) { + ma_format format = g_maFormatPriorities[iFormat]; - /* Let the application know about the route change. */ - ma_device__on_notification_rerouted(m_pDevice); + counter = 0; + for (;;) { + MA_ZERO_OBJECT(&encoding); + encoding.index = counter; + if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + break; + } + + if (format == ma_format_from_encoding__audio4(encoding.encoding, encoding.precision)) { + return format; /* Found a workable format. */ + } + + /* Getting here means this encoding does not match our preferred format so we need to more on to the next encoding. */ + counter += 1; + } + } + #endif + + /* Getting here means not appropriate format was found. */ + return ma_format_unknown; +} +#endif /* !defined(MA_SUN) */ +#else +static ma_format ma_format_from_swpar__audio4(struct audio_swpar* par) +{ + if (par->bits == 8 && par->bps == 1 && par->sig == 0) { + return ma_format_u8; + } + if (par->bits == 16 && par->bps == 2 && par->sig == 1 && par->le == ma_is_little_endian()) { + return ma_format_s16; + } + if (par->bits == 24 && par->bps == 3 && par->sig == 1 && par->le == ma_is_little_endian()) { + return ma_format_s24; + } + if (par->bits == 32 && par->bps == 4 && par->sig == 1 && par->le == ma_is_little_endian()) { + return ma_format_f32; + } + + /* Format not supported. */ + return ma_format_unknown; } -@end #endif -static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) +#if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ +static ma_result ma_context_get_device_info_from_fd__audio4(int fd, int deviceIndex, struct stat* stDevice, struct stat* stDefault, ma_device_type deviceType, ma_device_info* pDeviceInfo) { - MA_ASSERT(pDevice != NULL); - MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_uninitialized); + audio_device_t fdDevice; -#if defined(MA_APPLE_DESKTOP) - /* - Make sure we're no longer tracking the device. It doesn't matter if we call this for a non-default device because it'll - just gracefully ignore it. - */ - ma_device__untrack__coreaudio(pDevice); -#endif -#if defined(MA_APPLE_MOBILE) - if (pDevice->coreaudio.pNotificationHandler != NULL) { - ma_ios_notification_handler* pNotificationHandler = (MA_BRIDGE_TRANSFER ma_ios_notification_handler*)pDevice->coreaudio.pNotificationHandler; - [pNotificationHandler remove_handler]; + MA_ASSERT(fd >= 0); + MA_ASSERT(pDeviceInfo != NULL); + + if (ioctl(fd, AUDIO_GETDEV, &fdDevice) < 0) { + return MA_ERROR; /* Failed to retrieve device info. */ } -#endif - if (pDevice->coreaudio.audioUnitCapture != NULL) { - ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); + MA_ZERO_OBJECT(pDeviceInfo); + + /* Default. */ + if ((stDefault->st_dev == stDevice->st_dev) && (stDefault->st_ino == stDevice->st_ino)) { + pDeviceInfo->isDefault = MA_TRUE; } - if (pDevice->coreaudio.audioUnitPlayback != NULL) { - ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); + + /* ID. */ + ma_construct_device_id__audio4(pDeviceInfo->id.audio4, sizeof(pDeviceInfo->id.audio4), "/dev/audio", deviceIndex); + + /* Name. */ + ma_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), fdDevice.name); + + #if !defined(MA_AUDIO4_USE_NEW_API) + { + audio_info_t fdInfo; + ma_uint32 channels; + ma_uint32 sampleRate; + + #if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) + if (ioctl(fd, AUDIO_GETFORMAT, &fdInfo) < 0) { + return MA_ERROR; + } + #else + if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { + return MA_ERROR; + } + #endif + + if (deviceType == ma_device_type_playback) { + channels = fdInfo.play.channels; + sampleRate = fdInfo.play.sample_rate; + } else { + channels = fdInfo.record.channels; + sampleRate = fdInfo.record.sample_rate; + } + + /* Supported formats. We get this by looking at the encodings. */ + pDeviceInfo->nativeDataFormatCount = 0; + #if defined(MA_SUN) + { + /* Just reporting s16 here. */ + ma_device_info_add_native_data_format(pDeviceInfo, ma_format_s16, channels, channels, sampleRate, sampleRate); + } + #else + { + int counter = 0; + + for (;;) { + audio_encoding_t encoding; + ma_format format; + + MA_ZERO_OBJECT(&encoding); + encoding.index = counter; + if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + break; + } + + format = ma_format_from_encoding__audio4(encoding.encoding, encoding.precision); + if (format != ma_format_unknown) { + ma_device_info_add_native_data_format(pDeviceInfo, format, channels, channels, sampleRate, sampleRate); + } + + counter += 1; + } + } + #endif } + #else + { + struct audio_swpar fdPar; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + + if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { + return MA_ERROR; + } + + format = ma_format_from_swpar__audio4(&fdPar); + if (format == ma_format_unknown) { + return MA_FORMAT_NOT_SUPPORTED; + } + + if (deviceType == ma_device_type_playback) { + channels = fdPar.pchan; + } else { + channels = fdPar.rchan; + } - if (pDevice->coreaudio.pAudioBufferList) { - ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + sampleRate = fdPar.rate; + + pDeviceInfo->nativeDataFormatCount = 0; + ma_device_info_add_native_data_format(pDeviceInfo, format, channels, channels, sampleRate, sampleRate); } + #endif return MA_SUCCESS; } -typedef struct +static ma_device_enumeration_result ma_context_enumerate_device_from_fd___audio4(int fd, int deviceIndex, struct stat* stDevice, struct stat* stDefault, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_bool32 allowNominalSampleRateChange; + ma_result result; + ma_device_info deviceInfo; - /* Input. */ - ma_format formatIn; - ma_uint32 channelsIn; - ma_uint32 sampleRateIn; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_uint32 periodSizeInFramesIn; - ma_uint32 periodSizeInMillisecondsIn; - ma_uint32 periodsIn; - ma_share_mode shareMode; - ma_performance_profile performanceProfile; - ma_bool32 registerStopEvent; + result = ma_context_get_device_info_from_fd__audio4(fd, deviceIndex, stDevice, stDefault, deviceType, &deviceInfo); + if (result != MA_SUCCESS) { + return MA_DEVICE_ENUMERATION_CONTINUE; + } - /* Output. */ -#if defined(MA_APPLE_DESKTOP) - AudioObjectID deviceObjectID; + return callback(deviceType, &deviceInfo, pUserData); +} #endif - AudioComponent component; - AudioUnit audioUnit; - AudioBufferList* pAudioBufferList; /* Only used for input devices. */ - ma_format formatOut; - ma_uint32 channelsOut; - ma_uint32 sampleRateOut; - ma_channel channelMapOut[MA_MAX_CHANNELS]; - ma_uint32 periodSizeInFramesOut; - ma_uint32 periodsOut; - char deviceName[256]; -} ma_device_init_internal_data__coreaudio; -static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_init_internal_data__coreaudio* pData, void* pDevice_DoNotReference) /* <-- pDevice is typed as void* intentionally so as to avoid accidentally referencing it. */ +static ma_result ma_context_enumerate_devices__audio4(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_result result = MA_SUCCESS; - OSStatus status; - UInt32 enableIOFlag; - AudioStreamBasicDescription bestFormat; - ma_uint32 actualPeriodSizeInFrames; - AURenderCallbackStruct callbackInfo; -#if defined(MA_APPLE_DESKTOP) - AudioObjectID deviceObjectID; -#endif + MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); + + #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ + { + const int maxDevices = 64; + char devpath[256]; + int iDevice; + struct stat stDefault; + + /* + Every device will be named "/dev/audioN", with a "/dev/audioctlN" equivalent. We use the "/dev/audioctlN" + version here since we can open it even when another process has control of the "/dev/audioN" device. + */ + for (iDevice = 0; iDevice < maxDevices; iDevice += 1) { + struct stat st; + int fd; + ma_bool32 isTerminating = MA_FALSE; + + ma_strcpy_s(devpath, sizeof(devpath), "/dev/audioctl"); + ma_itoa_s(iDevice, devpath+strlen(devpath), sizeof(devpath)-strlen(devpath), 10); + + if (stat(devpath, &st) < 0) { + break; + } + + /* The device exists, but we need to check if it's usable as playback and/or capture. */ + + /* Playback. */ + if (!isTerminating) { + fd = open(devpath, O_RDONLY, 0); + if (fd >= 0) { + isTerminating = (ma_context_enumerate_device_from_fd___audio4(fd, iDevice, &st, &stDefault, ma_device_type_playback, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + close(fd); + } + } - /* This API should only be used for a single device type: playback or capture. No full-duplex mode. */ - if (deviceType == ma_device_type_duplex) { - return MA_INVALID_ARGS; + /* Capture. */ + if (!isTerminating) { + fd = open(devpath, O_WRONLY, 0); + if (fd >= 0) { + isTerminating = (ma_context_enumerate_device_from_fd___audio4(fd, iDevice, &st, &stDefault, ma_device_type_capture, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + close(fd); + } + } + + if (isTerminating) { + break; + } + } } + #else + { + DIR *d = opendir("/dev"); + struct dirent *e; + ma_bool32 isTerminating = MA_FALSE; - MA_ASSERT(pContext != NULL); - MA_ASSERT(deviceType == ma_device_type_playback || deviceType == ma_device_type_capture); + while ((e = readdir(d)) != NULL) { + size_t audioctlLen = strlen("audioctl"); -#if defined(MA_APPLE_DESKTOP) - pData->deviceObjectID = 0; -#endif - pData->component = NULL; - pData->audioUnit = NULL; - pData->pAudioBufferList = NULL; + if (isTerminating) { + break; + } -#if defined(MA_APPLE_DESKTOP) - result = ma_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); - if (result != MA_SUCCESS) { - return result; - } + if (strncmp(e->d_name, "audioctl", audioctlLen) == 0 && strspn(e->d_name+audioctlLen, "0123456789") == strlen(e->d_name+audioctlLen)) { + char devnodectl[256]; + int audioctlFD; - pData->deviceObjectID = deviceObjectID; -#endif + ma_strcpy_s(devnodectl, sizeof(devnodectl), "/dev/"); + ma_strcat_s(devnodectl, sizeof(devnodectl), e->d_name); - /* Core audio doesn't really use the notion of a period so we can leave this unmodified, but not too over the top. */ - pData->periodsOut = pData->periodsIn; - if (pData->periodsOut == 0) { - pData->periodsOut = MA_DEFAULT_PERIODS; - } - if (pData->periodsOut > 16) { - pData->periodsOut = 16; - } + audioctlFD = open(devnodectl, O_WRONLY); + if (audioctlFD >= 0) { + char devnode[256]; + audio_device_t audioDevice; + struct audio_swpar audioPar; + ma_strcpy_s(devnode, sizeof(devnode), "/dev/audio"); + ma_strcat_s(devnode, sizeof(devnode), e->d_name+audioctlLen); - /* Audio unit. */ - status = ((ma_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)((AudioComponent)pContext->coreaudio.component, (AudioUnit*)&pData->audioUnit); - if (status != noErr) { - return ma_result_from_OSStatus(status); - } + MA_ZERO_OBJECT(&audioDevice); + if (ioctl(audioctlFD, AUDIO_GETDEV, &audioDevice) >= 0) { + ma_device_info deviceInfo; + int audioFD; + MA_ZERO_OBJECT(&deviceInfo); - /* The input/output buses need to be explicitly enabled and disabled. We set the flag based on the output unit first, then we just swap it for input. */ - enableIOFlag = 1; - if (deviceType == ma_device_type_capture) { - enableIOFlag = 0; - } + /* We just treat `/dev/audio0` as the default. */ + if (strcmp(e->d_name+audioctlLen, "0") == 0) { + deviceInfo.isDefault = MA_TRUE; + } else { + deviceInfo.isDefault = MA_FALSE; + } - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, MA_COREAUDIO_OUTPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } + /* ID. */ + ma_strcpy_s(deviceInfo.id.audio4, sizeof(deviceInfo.id.audio4), devnode); + + /* Name. */ + ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), audioDevice.name); + + /* Formats. */ + if (ioctl(audioctlFD, AUDIO_GETPAR, &audioPar) >= 0) { + /* We need to try opening the device to check if it's a playback or capture device. */ + if (!isTerminating) { + audioFD = open(devnode, O_WRONLY | O_NONBLOCK); + if (audioFD >= 0) { + ma_device_info_add_native_data_format(&deviceInfo, ma_format_from_swpar__audio4(&audioPar), audioPar.pchan, audioPar.pchan, audioPar.rate, audioPar.rate); + { + isTerminating = (callback(ma_device_type_playback, &deviceInfo, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + } + deviceInfo.nativeDataFormatCount = 0; /* <-- Reset channels in case we do another iteration for the capture side. */ + } - enableIOFlag = (enableIOFlag == 0) ? 1 : 0; - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, MA_COREAUDIO_INPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } + close(audioFD); + } + if (!isTerminating) { + audioFD = open(devnode, O_RDONLY | O_NONBLOCK); + if (audioFD >= 0) { + ma_device_info_add_native_data_format(&deviceInfo, ma_format_from_swpar__audio4(&audioPar), audioPar.rchan, audioPar.rchan, audioPar.rate, audioPar.rate); + { + isTerminating = (callback(ma_device_type_capture, &deviceInfo, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + } + deviceInfo.nativeDataFormatCount = 0; + } - /* Set the device to use with this audio unit. This is only used on desktop since we are using defaults on mobile. */ -#if defined(MA_APPLE_DESKTOP) - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceObjectID, sizeof(deviceObjectID)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(result); - } -#else - /* - For some reason it looks like Apple is only allowing selection of the input device. There does not appear to be any way to change - the default output route. I have no idea why this is like this, but for now we'll only be able to configure capture devices. - */ - if (pDeviceID != NULL) { - if (deviceType == ma_device_type_capture) { - ma_bool32 found = MA_FALSE; - NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs]; - for (AVAudioSessionPortDescription* pPortDesc in pInputs) { - if (strcmp(pDeviceID->coreaudio, [pPortDesc.UID UTF8String]) == 0) { - [[AVAudioSession sharedInstance] setPreferredInput:pPortDesc error:nil]; - found = MA_TRUE; - break; + close(audioFD); + } + } + } else { + /* AUDIO_GETDEV failed for audioctlN. */ + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[audio4] AUDIO_GETDEV failed for %s.", devnodectl); + } + } else { + /* Failed to open audioctlN. */ + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[audio4] Failed to open %s.", devnodectl); } } - - if (found == MA_FALSE) { - return MA_DOES_NOT_EXIST; - } } + + closedir(d); } -#endif + #endif - /* - Format. This is the hardest part of initialization because there's a few variables to take into account. - 1) The format must be supported by the device. - 2) The format must be supported miniaudio. - 3) There's a priority that miniaudio prefers. + return MA_SUCCESS; +} - Ideally we would like to use a format that's as close to the hardware as possible so we can get as close to a passthrough as possible. The - most important property is the sample rate. miniaudio can do format conversion for any sample rate and channel count, but cannot do the same - for the sample data format. If the sample data format is not supported by miniaudio it must be ignored completely. +static ma_result ma_device_init_fd__audio4(ma_device* pDevice, ma_device_descriptor* pDescriptor, ma_device_type deviceType, int* pFD) +{ + const char* pDefaultDeviceNames[] = { + "/dev/audio", + "/dev/audio0" + }; + + int fd; + int fdFlags = 0; + size_t iDefaultDevice = (size_t)-1; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_uint32 internalPeriodSizeInFrames; + ma_uint32 internalPeriods; - On mobile platforms this is a bit different. We just force the use of whatever the audio unit's current format is set to. - */ - { - AudioStreamBasicDescription origFormat; - UInt32 origFormatSize = sizeof(origFormat); - AudioUnitScope formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; - AudioUnitElement formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; + MA_ASSERT(deviceType != ma_device_type_duplex); + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pFD != NULL); - if (deviceType == ma_device_type_playback) { - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, MA_COREAUDIO_OUTPUT_BUS, &origFormat, &origFormatSize); - } else { - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MA_COREAUDIO_INPUT_BUS, &origFormat, &origFormatSize); - } - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); + /* The first thing to do is open the file. */ + if (deviceType == ma_device_type_capture) { + fdFlags = O_RDONLY; + } else { + fdFlags = O_WRONLY; + } + /*fdFlags |= O_NONBLOCK;*/ + + /* Find the index of the default device as a start. We'll use this index later. Set it to (size_t)-1 otherwise. */ + if (pDescriptor->pDeviceID == NULL) { + /* Default device. */ + for (iDefaultDevice = 0; iDefaultDevice < ma_countof(pDefaultDeviceNames); ++iDefaultDevice) { + fd = open(pDefaultDeviceNames[iDefaultDevice], fdFlags, 0); + if (fd != -1) { + break; + } } + } else { + /* Specific device. */ + fd = open(pDescriptor->pDeviceID->audio4, fdFlags, 0); - #if defined(MA_APPLE_DESKTOP) - result = ma_find_best_format__coreaudio(pContext, deviceObjectID, deviceType, pData->formatIn, pData->channelsIn, pData->sampleRateIn, &origFormat, &bestFormat); - if (result != MA_SUCCESS) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return result; + for (iDefaultDevice = 0; iDefaultDevice < ma_countof(pDefaultDeviceNames); iDefaultDevice += 1) { + if (ma_strcmp(pDefaultDeviceNames[iDefaultDevice], pDescriptor->pDeviceID->audio4) == 0) { + break; + } } - /* - Technical Note TN2091: Device input using the HAL Output Audio Unit - https://developer.apple.com/library/archive/technotes/tn2091/_index.html + if (iDefaultDevice == ma_countof(pDefaultDeviceNames)) { + iDefaultDevice = (size_t)-1; + } + } - This documentation says the following: + if (fd == -1) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device."); + return ma_result_from_errno(errno); + } - The internal AudioConverter can handle any *simple* conversion. Typically, this means that a client can specify ANY - variant of the PCM formats. Consequently, the device's sample rate should match the desired sample rate. If sample rate - conversion is needed, it can be accomplished by buffering the input and converting the data on a separate thread with - another AudioConverter. + #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ + { + const char* pDefaultDeviceCtlNames[] = { + "/dev/audioctl", + "/dev/audioctl0" + }; + audio_info_t fdInfo; + int fdInfoResult = -1; - The important part here is the mention that it can handle *simple* conversions, which does *not* include sample rate. We - therefore want to ensure the sample rate stays consistent. This document is specifically for input, but I'm going to play it - safe and apply the same rule to output as well. + /* + The documentation is a little bit unclear to me as to how it handles formats. It says the + following: - I have tried going against the documentation by setting the sample rate anyway, but this just results in AudioUnitRender() - returning a result code of -10863. I have also tried changing the format directly on the input scope on the input bus, but - this just results in `ca_require: IsStreamFormatWritable(inScope, inElement) NotWritable` when trying to set the format. + Regardless of formats supported by underlying driver, the audio driver accepts the + following formats. - Something that does seem to work, however, has been setting the nominal sample rate on the device object. The problem with - this, however, is that it actually changes the sample rate at the operating system level and not just the application. This - could be intrusive to the user, however, so I don't think it's wise to make this the default. Instead I'm making this a - configuration option. When the `coreaudio.allowNominalSampleRateChange` config option is set to true, changing the sample - rate will be allowed. Otherwise it'll be fixed to the current sample rate. To check the system-defined sample rate, run - the Audio MIDI Setup program that comes installed on macOS and observe how the sample rate changes as the sample rate is - changed by miniaudio. - */ - if (pData->allowNominalSampleRateChange) { - AudioValueRange sampleRateRange; - AudioObjectPropertyAddress propAddress; + By then the next sentence says this: - sampleRateRange.mMinimum = bestFormat.mSampleRate; - sampleRateRange.mMaximum = bestFormat.mSampleRate; + `encoding` and `precision` are one of the values obtained by AUDIO_GETENC. - propAddress.mSelector = kAudioDevicePropertyNominalSampleRate; - propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; - propAddress.mElement = AUDIO_OBJECT_PROPERTY_ELEMENT; + It sounds like a direct contradiction to me. I'm going to play this safe any only use the + best sample format returned by AUDIO_GETENC. If the requested format is supported we'll + use that, but otherwise we'll just use our standard format priorities to pick an + appropriate one. + */ + AUDIO_INITINFO(&fdInfo); - status = ((ma_AudioObjectSetPropertyData_proc)pContext->coreaudio.AudioObjectSetPropertyData)(deviceObjectID, &propAddress, 0, NULL, sizeof(sampleRateRange), &sampleRateRange); - if (status != noErr) { - bestFormat.mSampleRate = origFormat.mSampleRate; + /* + Get the default format from the audioctl file if we're asking for a default device. If we + retrieve it from /dev/audio it'll default to mono 8000Hz. + */ + if (iDefaultDevice != (size_t)-1) { + /* We're using a default device. Get the info from the /dev/audioctl file instead of /dev/audio. */ + int fdctl = open(pDefaultDeviceCtlNames[iDefaultDevice], fdFlags, 0); + if (fdctl != -1) { +#if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) + fdInfoResult = ioctl(fdctl, AUDIO_GETFORMAT, &fdInfo); +#else + fdInfoResult = ioctl(fdctl, AUDIO_GETINFO, &fdInfo); +#endif + close(fdctl); } - } else { - bestFormat.mSampleRate = origFormat.mSampleRate; } - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); - if (status != noErr) { - /* We failed to set the format, so fall back to the current format of the audio unit. */ - bestFormat = origFormat; + if (fdInfoResult == -1) { + /* We still don't have the default device info so just retrieve it from the main audio device. */ + if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed."); + return ma_result_from_errno(errno); + } } - #else - bestFormat = origFormat; - /* - Sample rate is a little different here because for some reason kAudioUnitProperty_StreamFormat returns 0... Oh well. We need to instead try - setting the sample rate to what the user has requested and then just see the results of it. Need to use some Objective-C here for this since - it depends on Apple's AVAudioSession API. To do this we just get the shared AVAudioSession instance and then set it. Note that from what I - can tell, it looks like the sample rate is shared between playback and capture for everything. - */ - @autoreleasepool { - AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; - MA_ASSERT(pAudioSession != NULL); + /* We get the driver to do as much of the data conversion as possible. */ + if (deviceType == ma_device_type_capture) { + #if defined(MA_SUN) + ma_encoding_from_format__audio4(ma_format_s16, &fdInfo.record.encoding, &fdInfo.record.precision); + #else + fdInfo.mode = AUMODE_RECORD; + ma_encoding_from_format__audio4(ma_best_format_from_fd__audio4(fd, pDescriptor->format, ma_device_type_capture), &fdInfo.record.encoding, &fdInfo.record.precision); + #endif - [pAudioSession setPreferredSampleRate:(double)pData->sampleRateIn error:nil]; - bestFormat.mSampleRate = pAudioSession.sampleRate; + if (pDescriptor->channels != 0) { + #if defined(MA_SUN) + fdInfo.record.channels = ma_clamp(pDescriptor->channels, 1, 2); + #else + fdInfo.record.channels = ma_clamp(pDescriptor->channels, 1, 12); /* From the documentation for NetBSD: `channels` ranges from 1 to 12. */ + #endif + } - /* - I've had a report that the channel count returned by AudioUnitGetProperty above is inconsistent with - AVAudioSession outputNumberOfChannels. I'm going to try using the AVAudioSession values instead. + if (pDescriptor->sampleRate != 0) { + fdInfo.record.sample_rate = ma_clamp(pDescriptor->sampleRate, 1000, 192000); /* From the documentation for NetBSD: `frequency` ranges from 1000Hz to 192000Hz. (They mean `sample_rate` instead of `frequency`.) */ + } + } else { + #if defined(MA_SUN) + ma_encoding_from_format__audio4(ma_format_s16, &fdInfo.play.encoding, &fdInfo.play.precision); + #else + fdInfo.mode = AUMODE_PLAY; + ma_encoding_from_format__audio4(ma_best_format_from_fd__audio4(fd, pDescriptor->format, ma_device_type_playback), &fdInfo.play.encoding, &fdInfo.play.precision); + #endif - UPDATE 20/02/2025: - When testing on the simulator with an iPhone 15 and iOS 17 I get an error when initializing the audio - unit if set the input channels to pAudioSession.inputNumberOfChannels. What is happening is the channel - count returned from AudioUnitGetProperty() above is set to 2, but pAudioSession is reporting a channel - count of 1. When this happens, the call to AudioUnitSetProprty() below just down below will succeed, but - AudioUnitInitialize() further down will fail. The only solution I have come up with is to not set the - channel count to pAudioSession.inputNumberOfChannels. - */ - if (deviceType == ma_device_type_playback) { - bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.outputNumberOfChannels; + if (pDescriptor->channels != 0) { + #if defined(MA_SUN) + fdInfo.play.channels = ma_clamp(pDescriptor->channels, 1, 2); + #else + fdInfo.play.channels = ma_clamp(pDescriptor->channels, 1, 12); /* From the documentation for NetBSD: `channels` ranges from 1 to 12. */ + #endif } - #if 0 - if (deviceType == ma_device_type_capture) { - /*printf("DEBUG: bestFormat.mChannelsPerFrame = %d; pAudioSession.inputNumberOfChannels = %d\n", (int)bestFormat.mChannelsPerFrame, (int)pAudioSession.inputNumberOfChannels);*/ - bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.inputNumberOfChannels; + if (pDescriptor->sampleRate != 0) { + fdInfo.play.sample_rate = ma_clamp(pDescriptor->sampleRate, 1000, 192000); /* From the documentation for NetBSD: `frequency` ranges from 1000Hz to 192000Hz. (They mean `sample_rate` instead of `frequency`.) */ } - #endif } - - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); + if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); } - #endif - result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pData->formatOut); - if (result != MA_SUCCESS) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return result; + if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed."); + return ma_result_from_errno(errno); } - if (pData->formatOut == ma_format_unknown) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + if (deviceType == ma_device_type_capture) { + internalFormat = ma_format_from_prinfo__audio4(&fdInfo.record); + internalChannels = fdInfo.record.channels; + internalSampleRate = fdInfo.record.sample_rate; + } else { + internalFormat = ma_format_from_prinfo__audio4(&fdInfo.play); + internalChannels = fdInfo.play.channels; + internalSampleRate = fdInfo.play.sample_rate; + } + + if (internalFormat == ma_format_unknown) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); return MA_FORMAT_NOT_SUPPORTED; } - pData->channelsOut = bestFormat.mChannelsPerFrame; - pData->sampleRateOut = (ma_uint32)bestFormat.mSampleRate; - } + /* Buffer. */ + internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate); - /* Clamp the channel count for safety. */ - if (pData->channelsOut > MA_MAX_CHANNELS) { - pData->channelsOut = MA_MAX_CHANNELS; - } + #if defined(MA_SUN) + { + /* + No good way to query the buffer size. From the Oracle docs: https://web.archive.org/web/20260128053127/https://docs.oracle.com/cd/E19683-01/817-0669/6mgf1n1ba/index.html - /* - Internal channel map. This is weird in my testing. If I use the AudioObject to get the - channel map, the channel descriptions are set to "Unknown" for some reason. To work around - this it looks like retrieving it from the AudioUnit will work. However, and this is where - it gets weird, it doesn't seem to work with capture devices, nor at all on iOS... Therefore - I'm going to fall back to a default assumption in these cases. - */ -#if defined(MA_APPLE_DESKTOP) - result = ma_get_AudioUnit_channel_map(pContext, pData->audioUnit, deviceType, pData->channelMapOut, pData->channelsOut); - if (result != MA_SUCCESS) { - #if 0 - /* Try falling back to the channel map from the AudioObject. */ - result = ma_get_AudioObject_channel_map(pContext, deviceObjectID, deviceType, pData->channelMapOut, pData->channelsOut); - if (result != MA_SUCCESS) { - return result; + The play.buffer_size field is currently not supported. + + We're just going to assume the requested buffer size and move on. Not much else I can think of. + */ + internalPeriods = 2; + internalPeriodSizeInFrames = ma_clamp(internalPeriodSizeInFrames, 256, 8192); } - #else - /* Fall back to default assumptions. */ - ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); - #endif - } -#else - /* TODO: Figure out how to get the channel map using AVAudioSession. */ - ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); -#endif + #else + { + ma_uint32 internalPeriodSizeInBytes; + internalPeriodSizeInBytes = internalPeriodSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (internalPeriodSizeInBytes < 16) { + internalPeriodSizeInBytes = 16; + } - /* Buffer size. Not allowing this to be configurable on iOS. */ - if (pData->periodSizeInFramesIn == 0) { - if (pData->periodSizeInMillisecondsIn == 0) { - if (pData->performanceProfile == ma_performance_profile_low_latency) { - actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY, pData->sampleRateOut); - } else { - actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE, pData->sampleRateOut); + internalPeriods = pDescriptor->periodCount; + if (internalPeriods < 2) { + internalPeriods = 2; } - } else { - actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->periodSizeInMillisecondsIn, pData->sampleRateOut); - } - } else { - actualPeriodSizeInFrames = pData->periodSizeInFramesIn; - } -#if defined(MA_APPLE_DESKTOP) - result = ma_set_AudioObject_buffer_size_in_frames(pContext, deviceObjectID, deviceType, &actualPeriodSizeInFrames); - if (result != MA_SUCCESS) { - return result; - } -#else - /* - On iOS, the size of the IO buffer needs to be specified in seconds and is a floating point - number. I don't trust any potential truncation errors due to converting from float to integer - so I'm going to explicitly set the actual period size to the next power of 2. - */ - @autoreleasepool { - AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; - MA_ASSERT(pAudioSession != NULL); + /* What miniaudio calls a period, audio4 calls a block. */ + AUDIO_INITINFO(&fdInfo); + fdInfo.hiwat = internalPeriods; + fdInfo.lowat = internalPeriods-1; + fdInfo.blocksize = internalPeriodSizeInBytes; + if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); + } - [pAudioSession setPreferredIOBufferDuration:((float)actualPeriodSizeInFrames / pAudioSession.sampleRate) error:nil]; - actualPeriodSizeInFrames = ma_next_power_of_2((ma_uint32)(pAudioSession.IOBufferDuration * pAudioSession.sampleRate)); + internalPeriods = fdInfo.hiwat; + internalPeriodSizeInFrames = fdInfo.blocksize / ma_get_bytes_per_frame(internalFormat, internalChannels); + } + #endif } -#endif + #else + { + struct audio_swpar fdPar; + /* We need to retrieve the format of the device so we can know the channel count and sample rate. Then we can calculate the buffer size. */ + if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters."); + return ma_result_from_errno(errno); + } - /* - During testing I discovered that the buffer size can be too big. You'll get an error like this: + internalFormat = ma_format_from_swpar__audio4(&fdPar); + internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; + internalSampleRate = fdPar.rate; - kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=4096, mMaxFramesPerSlice=512 + if (internalFormat == ma_format_unknown) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; + } - Note how inFramesToProcess is smaller than mMaxFramesPerSlice. To fix, we need to set kAudioUnitProperty_MaximumFramesPerSlice to that - of the size of our buffer, or do it the other way around and set our buffer size to the kAudioUnitProperty_MaximumFramesPerSlice. - */ - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualPeriodSizeInFrames, sizeof(actualPeriodSizeInFrames)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } + /* Buffer. */ + { + internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate); - pData->periodSizeInFramesOut = (ma_uint32)actualPeriodSizeInFrames; + /* What miniaudio calls a period, audio4 calls a block. */ + fdPar.nblks = pDescriptor->periodCount; + fdPar.round = internalPeriodSizeInFrames; - /* We need a buffer list if this is an input device. We render into this in the input callback. */ - if (deviceType == ma_device_type_capture) { - ma_bool32 isInterleaved = (bestFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; - AudioBufferList* pBufferList; + if (ioctl(fd, AUDIO_SETPAR, &fdPar) < 0) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters."); + return ma_result_from_errno(errno); + } - pBufferList = ma_allocate_AudioBufferList__coreaudio(pData->periodSizeInFramesOut, pData->formatOut, pData->channelsOut, (isInterleaved) ? ma_stream_layout_interleaved : ma_stream_layout_deinterleaved, &pContext->allocationCallbacks); - if (pBufferList == NULL) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return MA_OUT_OF_MEMORY; + if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters."); + return ma_result_from_errno(errno); + } } - pData->pAudioBufferList = pBufferList; - } + /* Put the device into a stopped state initially. If we don't do this, the first AUDIO_START will fail. */ + ioctl(fd, AUDIO_STOP, 0); - /* Callbacks. */ - callbackInfo.inputProcRefCon = pDevice_DoNotReference; - if (deviceType == ma_device_type_playback) { - callbackInfo.inputProc = ma_on_output__coreaudio; - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callbackInfo, sizeof(callbackInfo)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } - } else { - callbackInfo.inputProc = ma_on_input__coreaudio; - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callbackInfo, sizeof(callbackInfo)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } + internalFormat = ma_format_from_swpar__audio4(&fdPar); + internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; + internalSampleRate = fdPar.rate; + internalPeriods = fdPar.nblks; + internalPeriodSizeInFrames = fdPar.round; } + #endif - /* We need to listen for stop events. */ - if (pData->registerStopEvent) { - status = ((ma_AudioUnitAddPropertyListener_proc)pContext->coreaudio.AudioUnitAddPropertyListener)(pData->audioUnit, kAudioOutputUnitProperty_IsRunning, on_start_stop__coreaudio, pDevice_DoNotReference); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } + if (internalFormat == ma_format_unknown) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } - /* Initialize the audio unit. */ - status = ((ma_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit); - if (status != noErr) { - ma_free(pData->pAudioBufferList, &pContext->allocationCallbacks); - pData->pAudioBufferList = NULL; - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); + pDescriptor->format = internalFormat; + pDescriptor->channels = internalChannels; + pDescriptor->sampleRate = internalSampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); + pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; + pDescriptor->periodCount = internalPeriods; + + *pFD = fd; + + return MA_SUCCESS; +} + +static void ma_device_uninit_internal__audio4(ma_device* pDevice, ma_device_state_audio4* pDeviceStateAudio4) +{ + ma_device_type deviceType = ma_device_get_type(pDevice); + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + close(pDeviceStateAudio4->fdCapture); } - /* Grab the name. */ -#if defined(MA_APPLE_DESKTOP) - ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(pData->deviceName), pData->deviceName); -#else - if (deviceType == ma_device_type_playback) { - ma_strcpy_s(pData->deviceName, sizeof(pData->deviceName), MA_DEFAULT_PLAYBACK_DEVICE_NAME); - } else { - ma_strcpy_s(pData->deviceName, sizeof(pData->deviceName), MA_DEFAULT_CAPTURE_DEVICE_NAME); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + close(pDeviceStateAudio4->fdPlayback); } -#endif - return result; + ma_free(pDeviceStateAudio4, ma_device_get_allocation_callbacks(pDevice)); } -#if defined(MA_APPLE_DESKTOP) -static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_device_type deviceType, ma_bool32 disposePreviousAudioUnit) +static ma_result ma_device_init__audio4(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - ma_device_init_internal_data__coreaudio data; - ma_result result; + ma_device_state_audio4* pDeviceStateAudio4; + const ma_device_config_audio4* pDeviceConfigAudio4 = (const ma_device_config_audio4*)pDeviceBackendConfig; + ma_device_config_audio4 defaultConfigAudio4; + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint32 intermediaryBufferSizeCapture = 0; + ma_uint32 intermediaryBufferSizePlayback = 0; + ma_uint32 intermediaryBufferAllocationSize = 0; - /* This should only be called for playback or capture, not duplex. */ - if (deviceType == ma_device_type_duplex) { - return MA_INVALID_ARGS; + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - data.allowNominalSampleRateChange = MA_FALSE; /* Don't change the nominal sample rate when switching devices. */ + if (pDeviceConfigAudio4 == NULL) { + defaultConfigAudio4 = ma_device_config_audio4_init(); + pDeviceConfigAudio4 = &defaultConfigAudio4; + } - if (deviceType == ma_device_type_capture) { - data.formatIn = pDevice->capture.format; - data.channelsIn = pDevice->capture.channels; - data.sampleRateIn = pDevice->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pDevice->capture.channelMap, sizeof(pDevice->capture.channelMap)); - data.shareMode = pDevice->capture.shareMode; - data.performanceProfile = pDevice->coreaudio.originalPerformanceProfile; - data.registerStopEvent = MA_TRUE; + pDeviceStateAudio4 = (ma_device_state_audio4*)ma_calloc(sizeof(*pDeviceStateAudio4), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateAudio4 == NULL) { + return MA_OUT_OF_MEMORY; + } - if (disposePreviousAudioUnit) { - ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); - ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); - } - if (pDevice->coreaudio.pAudioBufferList) { - ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); - } - } else if (deviceType == ma_device_type_playback) { - data.formatIn = pDevice->playback.format; - data.channelsIn = pDevice->playback.channels; - data.sampleRateIn = pDevice->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pDevice->playback.channelMap, sizeof(pDevice->playback.channelMap)); - data.shareMode = pDevice->playback.shareMode; - data.performanceProfile = pDevice->coreaudio.originalPerformanceProfile; - data.registerStopEvent = (pDevice->type != ma_device_type_duplex); + pDeviceStateAudio4->fdCapture = -1; + pDeviceStateAudio4->fdPlayback = -1; - if (disposePreviousAudioUnit) { - ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); - ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); + /* + The version of the operating system dictates whether or not the device is exclusive or shared. NetBSD 8 + introduced in-kernel mixing which means it's shared. All other BSD flavours are exclusive as far as + I'm aware. + */ + #if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 800000000 + { + /* NetBSD 8.0+ */ + if (((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + return MA_SHARE_MODE_NOT_SUPPORTED; } } - data.periodSizeInFramesIn = pDevice->coreaudio.originalPeriodSizeInFrames; - data.periodSizeInMillisecondsIn = pDevice->coreaudio.originalPeriodSizeInMilliseconds; - data.periodsIn = pDevice->coreaudio.originalPeriods; - - /* Need at least 3 periods for duplex. */ - if (data.periodsIn < 3 && pDevice->type == ma_device_type_duplex) { - data.periodsIn = 3; + #else + { + /* All other flavors. */ } + #endif - result = ma_device_init_internal__coreaudio(pDevice->pContext, deviceType, NULL, &data, (void*)pDevice); - if (result != MA_SUCCESS) { - return result; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_init_fd__audio4(pDevice, pDescriptorCapture, ma_device_type_capture, &pDeviceStateAudio4->fdCapture); + if (result != MA_SUCCESS) { + ma_device_uninit_internal__audio4(pDevice, pDeviceStateAudio4); + return result; + } + + intermediaryBufferSizeCapture = ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodSizeInFrames; } - if (deviceType == ma_device_type_capture) { - #if defined(MA_APPLE_DESKTOP) - pDevice->coreaudio.deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; - ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); - #endif - pDevice->coreaudio.audioUnitCapture = (ma_ptr)data.audioUnit; - pDevice->coreaudio.pAudioBufferList = (ma_ptr)data.pAudioBufferList; - pDevice->coreaudio.audioBufferCapInFrames = data.periodSizeInFramesOut; - - pDevice->capture.internalFormat = data.formatOut; - pDevice->capture.internalChannels = data.channelsOut; - pDevice->capture.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->capture.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->capture.internalPeriods = data.periodsOut; - } else if (deviceType == ma_device_type_playback) { - #if defined(MA_APPLE_DESKTOP) - pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; - ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); - #endif - pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_init_fd__audio4(pDevice, pDescriptorPlayback, ma_device_type_playback, &pDeviceStateAudio4->fdPlayback); + if (result != MA_SUCCESS) { + ma_device_uninit_internal__audio4(pDevice, pDeviceStateAudio4); + return result; + } - pDevice->playback.internalFormat = data.formatOut; - pDevice->playback.internalChannels = data.channelsOut; - pDevice->playback.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->playback.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->playback.internalPeriods = data.periodsOut; + intermediaryBufferSizePlayback = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodSizeInFrames; } - return MA_SUCCESS; -} -#endif /* MA_APPLE_DESKTOP */ + /* + We now need an intermediary buffer. This is never used simultaneously between capture and playback so we can + just make it the size of the maximum of the two. We'll append this to the end of the device state allocation. + */ + intermediaryBufferAllocationSize = ma_align_64(ma_max(intermediaryBufferSizeCapture, intermediaryBufferSizePlayback)); -static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ - ma_result result; + { + size_t deviceStateAllocationSizeNew; + ma_device_state_audio4* pDeviceStateAudio4New; - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pConfig != NULL); + deviceStateAllocationSizeNew = 0; + deviceStateAllocationSizeNew += ma_align_64(sizeof(*pDeviceStateAudio4)); + deviceStateAllocationSizeNew += ma_align_64(intermediaryBufferAllocationSize); - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + pDeviceStateAudio4New = (ma_device_state_audio4*)ma_realloc(pDeviceStateAudio4, deviceStateAllocationSizeNew, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateAudio4New == NULL) { + ma_device_uninit_internal__audio4(pDevice, pDeviceStateAudio4); + return MA_OUT_OF_MEMORY; + } - /* No exclusive mode with the Core Audio backend for now. */ - if (((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive)) { - return MA_SHARE_MODE_NOT_SUPPORTED; + pDeviceStateAudio4 = pDeviceStateAudio4New; } - /* Capture needs to be initialized first. */ - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_device_init_internal_data__coreaudio data; - data.allowNominalSampleRateChange = pConfig->coreaudio.allowNominalSampleRateChange; - data.formatIn = pDescriptorCapture->format; - data.channelsIn = pDescriptorCapture->channels; - data.sampleRateIn = pDescriptorCapture->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pDescriptorCapture->channelMap, sizeof(pDescriptorCapture->channelMap)); - data.periodSizeInFramesIn = pDescriptorCapture->periodSizeInFrames; - data.periodSizeInMillisecondsIn = pDescriptorCapture->periodSizeInMilliseconds; - data.periodsIn = pDescriptorCapture->periodCount; - data.shareMode = pDescriptorCapture->shareMode; - data.performanceProfile = pConfig->performanceProfile; - data.registerStopEvent = MA_TRUE; + pDeviceStateAudio4->pIntermediaryBuffer = ma_offset_ptr(pDeviceStateAudio4, ma_align_64(sizeof(*pDeviceStateAudio4))); - /* Need at least 3 periods for duplex. */ - if (data.periodsIn < 3 && pConfig->deviceType == ma_device_type_duplex) { - data.periodsIn = 3; - } - result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_capture, pDescriptorCapture->pDeviceID, &data, (void*)pDevice); - if (result != MA_SUCCESS) { - return result; - } + *ppDeviceState = pDeviceStateAudio4; - pDevice->coreaudio.isDefaultCaptureDevice = (pConfig->capture.pDeviceID == NULL); - #if defined(MA_APPLE_DESKTOP) - pDevice->coreaudio.deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; - #endif - pDevice->coreaudio.audioUnitCapture = (ma_ptr)data.audioUnit; - pDevice->coreaudio.pAudioBufferList = (ma_ptr)data.pAudioBufferList; - pDevice->coreaudio.audioBufferCapInFrames = data.periodSizeInFramesOut; - pDevice->coreaudio.originalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; - pDevice->coreaudio.originalPeriodSizeInMilliseconds = pDescriptorCapture->periodSizeInMilliseconds; - pDevice->coreaudio.originalPeriods = pDescriptorCapture->periodCount; - pDevice->coreaudio.originalPerformanceProfile = pConfig->performanceProfile; - - pDescriptorCapture->format = data.formatOut; - pDescriptorCapture->channels = data.channelsOut; - pDescriptorCapture->sampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDescriptorCapture->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDescriptorCapture->periodSizeInFrames = data.periodSizeInFramesOut; - pDescriptorCapture->periodCount = data.periodsOut; + return MA_SUCCESS; +} - #if defined(MA_APPLE_DESKTOP) - ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); +static void ma_device_uninit__audio4(ma_device* pDevice) +{ + ma_device_state_audio4* pDeviceStateAudio4 = ma_device_get_backend_state__audio4(pDevice); + ma_device_uninit_internal__audio4(pDevice, pDeviceStateAudio4); +} - /* - If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly - switch the device in the background. - */ - if (pConfig->capture.pDeviceID == NULL) { - ma_device__track__coreaudio(pDevice); +static void ma_device_prime_playback_buffer__audio4(ma_device* pDevice) +{ + ma_device_state_audio4* pDeviceStateAudio4 = ma_device_get_backend_state__audio4(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint8 buffer[4096]; + ma_uint32 bpf; + ma_uint32 framesToWrite; + ma_uint32 framesWritten; + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + framesToWrite = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods; + framesWritten = 0; + + MA_ZERO_MEMORY(buffer, sizeof(buffer)); + + while (framesWritten < framesToWrite) { + ma_uint32 framesToWriteThisIteration = sizeof(buffer) / bpf; + ma_uint32 framesRemaining = framesToWrite - framesWritten; + if (framesToWriteThisIteration > framesRemaining) { + framesToWriteThisIteration = framesRemaining; + } + + /* Just a guard to ensure we don't get stuck in a loop. Should never happen in practice (would require a massive channel count). */ + if (framesToWriteThisIteration == 0) { + break; + } + + write(pDeviceStateAudio4->fdPlayback, buffer, framesToWriteThisIteration * bpf); + framesWritten += framesToWriteThisIteration; } - #endif } +} - /* Playback. */ - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_device_init_internal_data__coreaudio data; - data.allowNominalSampleRateChange = pConfig->coreaudio.allowNominalSampleRateChange; - data.formatIn = pDescriptorPlayback->format; - data.channelsIn = pDescriptorPlayback->channels; - data.sampleRateIn = pDescriptorPlayback->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pDescriptorPlayback->channelMap, sizeof(pDescriptorPlayback->channelMap)); - data.shareMode = pDescriptorPlayback->shareMode; - data.performanceProfile = pConfig->performanceProfile; +static ma_result ma_device_start__audio4(ma_device* pDevice) +{ + ma_device_state_audio4* pDeviceStateAudio4 = ma_device_get_backend_state__audio4(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); - /* In full-duplex mode we want the playback buffer to be the same size as the capture buffer. */ - if (pConfig->deviceType == ma_device_type_duplex) { - data.periodSizeInFramesIn = pDescriptorCapture->periodSizeInFrames; - data.periodsIn = pDescriptorCapture->periodCount; - data.registerStopEvent = MA_FALSE; - } else { - data.periodSizeInFramesIn = pDescriptorPlayback->periodSizeInFrames; - data.periodSizeInMillisecondsIn = pDescriptorPlayback->periodSizeInMilliseconds; - data.periodsIn = pDescriptorPlayback->periodCount; - data.registerStopEvent = MA_TRUE; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (pDeviceStateAudio4->fdCapture == -1) { + return MA_INVALID_ARGS; } - result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_playback, pDescriptorPlayback->pDeviceID, &data, (void*)pDevice); - if (result != MA_SUCCESS) { - if (pConfig->deviceType == ma_device_type_duplex) { - ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); - if (pDevice->coreaudio.pAudioBufferList) { - ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); - } + #if !defined(MA_AUDIO4_USE_NEW_API) + { + /* Old API. Do nothing here (will be started automatically). */ + } + #else + { + if (ioctl(pDeviceStateAudio4->fdCapture, AUDIO_START, 0) < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to start device. AUDIO_START failed. %s.", ma_result_description(ma_result_from_errno(errno))); + return ma_result_from_errno(errno); } - return result; } + #endif + } - pDevice->coreaudio.isDefaultPlaybackDevice = (pConfig->playback.pDeviceID == NULL); - #if defined(MA_APPLE_DESKTOP) - pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; - #endif - pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; - pDevice->coreaudio.originalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; - pDevice->coreaudio.originalPeriodSizeInMilliseconds = pDescriptorPlayback->periodSizeInMilliseconds; - pDevice->coreaudio.originalPeriods = pDescriptorPlayback->periodCount; - pDevice->coreaudio.originalPerformanceProfile = pConfig->performanceProfile; - - pDescriptorPlayback->format = data.formatOut; - pDescriptorPlayback->channels = data.channelsOut; - pDescriptorPlayback->sampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDescriptorPlayback->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDescriptorPlayback->periodSizeInFrames = data.periodSizeInFramesOut; - pDescriptorPlayback->periodCount = data.periodsOut; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (pDeviceStateAudio4->fdPlayback == -1) { + return MA_INVALID_ARGS; + } - #if defined(MA_APPLE_DESKTOP) - ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); + ma_device_prime_playback_buffer__audio4(pDevice); - /* - If we are using the default device we'll need to listen for changes to the system's default device so we can seamlessly - switch the device in the background. - */ - if (pDescriptorPlayback->pDeviceID == NULL && (pConfig->deviceType != ma_device_type_duplex || pDescriptorCapture->pDeviceID != NULL)) { - ma_device__track__coreaudio(pDevice); + #if !defined(MA_AUDIO4_USE_NEW_API) + { + /* Old API. Do nothing here (will be started automatically). */ } - #endif + #else + { + if (ioctl(pDeviceStateAudio4->fdPlayback, AUDIO_START, 0) < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to start device. AUDIO_START failed. errno=%d, %s.", errno, ma_result_description(ma_result_from_errno(errno))); + return ma_result_from_errno(errno); + } + } + #endif } - - - /* - When stopping the device, a callback is called on another thread. We need to wait for this callback - before returning from ma_device_stop(). This event is used for this. - */ - ma_event_init(&pDevice->coreaudio.stopEvent); - - /* - We need to detect when a route has changed so we can update the data conversion pipeline accordingly. This is done - differently on non-Desktop Apple platforms. - */ -#if defined(MA_APPLE_MOBILE) - pDevice->coreaudio.pNotificationHandler = (MA_BRIDGE_RETAINED void*)[[ma_ios_notification_handler alloc] init:pDevice]; -#endif - return MA_SUCCESS; } - -static ma_result ma_device_start__coreaudio(ma_device* pDevice) +static ma_result ma_device_stop_fd__audio4(ma_device* pDevice, int fd) { - MA_ASSERT(pDevice != NULL); - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - OSStatus status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture); - if (status != noErr) { - return ma_result_from_OSStatus(status); - } + if (fd == -1) { + return MA_INVALID_ARGS; } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - OSStatus status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); - if (status != noErr) { - if (pDevice->type == ma_device_type_duplex) { - ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); + #if !defined(MA_AUDIO4_USE_NEW_API) + { + #if defined(MA_SUN) + { + /* There is no flush here for Solaris, but we could use the I_FLUSH stream ioctl with FLUSHR for capture?I have no way to test - leaving that to the community. */ + (void)pDevice; + } + #else + { + if (ioctl(fd, AUDIO_FLUSH, 0) < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed."); + return ma_result_from_errno(errno); } - return ma_result_from_OSStatus(status); + } + #endif + } + #else + { + if (ioctl(fd, AUDIO_STOP, 0) < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed."); + return ma_result_from_errno(errno); } } + #endif return MA_SUCCESS; } -static ma_result ma_device_stop__coreaudio(ma_device* pDevice) +static ma_result ma_device_stop__audio4(ma_device* pDevice) { - MA_ASSERT(pDevice != NULL); + ma_device_state_audio4* pDeviceStateAudio4 = ma_device_get_backend_state__audio4(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); - /* It's not clear from the documentation whether or not AudioOutputUnitStop() actually drains the device or not. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_result result; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - OSStatus status = ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); - if (status != noErr) { - return ma_result_from_OSStatus(status); + result = ma_device_stop_fd__audio4(pDevice, pDeviceStateAudio4->fdCapture); + if (result != MA_SUCCESS) { + return result; } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - OSStatus status = ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); - if (status != noErr) { - return ma_result_from_OSStatus(status); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_result result; + + /* Drain the device first. If this fails we'll just need to flush without draining. Unfortunately draining isn't available on newer version of OpenBSD. */ + #if !defined(MA_AUDIO4_USE_NEW_API) + { + ioctl(pDeviceStateAudio4->fdPlayback, AUDIO_DRAIN, 0); + } + #else + { + /* + Unfortunately newer versions of OpenBSD do not have a drain function so we have to do it ourselves. We're just going + to sleep until a whole buffer has been output since starting the drain. This is not technically 100% accurate because + there might be less than a whole buffer needing to be drained. + + If you're developing an audio API, please, for the love of god, add a drain function... This here is the *opposite* + of reliability through simplicity. + */ + struct audio_pos drainBegPos; + + MA_ZERO_OBJECT(&drainBegPos); + if (ioctl(pDeviceStateAudio4->fdPlayback, AUDIO_GETPOS, &drainBegPos) >= 0) { + ma_uint32 timeSinceForwardProgressInMS = 0; + struct audio_pos lastForwardProgressPos = drainBegPos; + + for (;;) { + struct audio_pos pos; + ma_uint32 sleepTimeInMS = 1; + + /* Sleep for a bit to give the device a chance to actually make some progress. */ + ma_sleep(sleepTimeInMS); + + MA_ZERO_OBJECT(&pos); + if (ioctl(pDeviceStateAudio4->fdPlayback, AUDIO_GETPOS, &pos) < 0) { + break; + } + + /* I'm paranoid about getting stuck in a loop here so I'm going to explicitly check that we're making forward progress and if not bomb out. */ + if (memcmp(&lastForwardProgressPos, &drainBegPos, sizeof(struct audio_pos)) == 0) { + timeSinceForwardProgressInMS += sleepTimeInMS; + if (timeSinceForwardProgressInMS > 100) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "audio(4): Abandoning drain because too much time has been spent without making forward progress."); + break; + } + } else { + timeSinceForwardProgressInMS = 0; + lastForwardProgressPos = pos; + } + + if ((pos.play_pos - drainBegPos.play_pos) >= ((pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods) * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels))) { + break; + } + } + } else { + /* Failed to get the position at the start of the drain. Just sleep a bit as an approximation. */ + ma_sleep(((pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods) * 1000) / pDevice->playback.internalSampleRate); + } + } + #endif + + /* Here is where the device is stopped immediately. */ + result = ma_device_stop_fd__audio4(pDevice, pDeviceStateAudio4->fdPlayback); + if (result != MA_SUCCESS) { + return result; } } - /* We need to wait for the callback to finish before returning. */ - ma_event_wait(&pDevice->coreaudio.stopEvent); return MA_SUCCESS; } - -static ma_result ma_context_uninit__coreaudio(ma_context* pContext) +static ma_result ma_device_write__audio4(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_coreaudio); + ma_device_state_audio4* pDeviceStateAudio4 = ma_device_get_backend_state__audio4(pDevice); + int result; -#if defined(MA_APPLE_MOBILE) - if (!pContext->coreaudio.noAudioSessionDeactivate) { - if (![[AVAudioSession sharedInstance] setActive:false error:nil]) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session."); - return MA_FAILED_TO_INIT_BACKEND; - } + if (pFramesWritten != NULL) { + *pFramesWritten = 0; } -#endif -#if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); -#endif + result = write(pDeviceStateAudio4->fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + if (result < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device."); + return ma_result_from_errno(errno); + } -#if !defined(MA_APPLE_MOBILE) - ma_context__uninit_device_tracking__coreaudio(pContext); -#endif + if (pFramesWritten != NULL) { + *pFramesWritten = (ma_uint32)result / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + } - (void)pContext; return MA_SUCCESS; } -#if defined(MA_APPLE_MOBILE) && defined(__IPHONE_12_0) -static AVAudioSessionCategory ma_to_AVAudioSessionCategory(ma_ios_session_category category) +static ma_result ma_device_read__audio4(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) { - /* The "default" and "none" categories are treated different and should not be used as an input into this function. */ - MA_ASSERT(category != ma_ios_session_category_default); - MA_ASSERT(category != ma_ios_session_category_none); + ma_device_state_audio4* pDeviceStateAudio4 = ma_device_get_backend_state__audio4(pDevice); + int result; - switch (category) { - case ma_ios_session_category_ambient: return AVAudioSessionCategoryAmbient; - case ma_ios_session_category_solo_ambient: return AVAudioSessionCategorySoloAmbient; - case ma_ios_session_category_playback: return AVAudioSessionCategoryPlayback; - case ma_ios_session_category_record: return AVAudioSessionCategoryRecord; - case ma_ios_session_category_play_and_record: return AVAudioSessionCategoryPlayAndRecord; - case ma_ios_session_category_multi_route: return AVAudioSessionCategoryMultiRoute; - case ma_ios_session_category_none: return AVAudioSessionCategoryAmbient; - case ma_ios_session_category_default: return AVAudioSessionCategoryAmbient; - default: return AVAudioSessionCategoryAmbient; + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + result = read(pDeviceStateAudio4->fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + if (result < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device."); + return ma_result_from_errno(errno); } + + if (pFramesRead != NULL) { + *pFramesRead = (ma_uint32)result / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + } + + return MA_SUCCESS; } -#endif -static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static ma_result ma_device_step__audio4(ma_device* pDevice, ma_blocking_mode blockingMode) { -#if !defined(MA_APPLE_MOBILE) + ma_device_state_audio4* pDeviceStateAudio4 = ma_device_get_backend_state__audio4(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + struct timeval tv; + struct timeval* pTimeout = NULL; + fd_set fdsRead; + fd_set fdsWrite; + int fdMax; + int selectResult; ma_result result; -#endif - - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pContext != NULL); -#if defined(MA_APPLE_MOBILE) - @autoreleasepool { - AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; - AVAudioSessionCategoryOptions options = pConfig->coreaudio.sessionCategoryOptions; + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + tv.tv_sec = 0; + tv.tv_usec = 0; + pTimeout = &tv; + } - MA_ASSERT(pAudioSession != NULL); + do + { + FD_ZERO(&fdsRead); + FD_ZERO(&fdsWrite); - if (pConfig->coreaudio.sessionCategory == ma_ios_session_category_default) { - /* - I'm going to use trial and error to determine our default session category. First we'll try PlayAndRecord. If that fails - we'll try Playback and if that fails we'll try record. If all of these fail we'll just not set the category. - */ - #if !defined(MA_APPLE_TV) && !defined(MA_APPLE_WATCH) - options |= AVAudioSessionCategoryOptionDefaultToSpeaker; - #endif + fdMax = 0; - if ([pAudioSession setCategory: AVAudioSessionCategoryPlayAndRecord withOptions:options error:nil]) { - /* Using PlayAndRecord */ - } else if ([pAudioSession setCategory: AVAudioSessionCategoryPlayback withOptions:options error:nil]) { - /* Using Playback */ - } else if ([pAudioSession setCategory: AVAudioSessionCategoryRecord withOptions:options error:nil]) { - /* Using Record */ - } else { - /* Leave as default? */ - } - } else { - if (pConfig->coreaudio.sessionCategory != ma_ios_session_category_none) { - #if defined(__IPHONE_12_0) - if (![pAudioSession setCategory: ma_to_AVAudioSessionCategory(pConfig->coreaudio.sessionCategory) withOptions:options error:nil]) { - return MA_INVALID_OPERATION; /* Failed to set session category. */ - } - #else - /* Ignore the session category on version 11 and older, but post a warning. */ - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Session category only supported in iOS 12 and newer."); - #endif - } + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + FD_SET(pDeviceStateAudio4->fdCapture, &fdsRead); + fdMax = ma_max(fdMax, pDeviceStateAudio4->fdCapture); } - - if (!pConfig->coreaudio.noAudioSessionActivate) { - if (![pAudioSession setActive:true error:nil]) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to activate audio session."); - return MA_FAILED_TO_INIT_BACKEND; - } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + FD_SET(pDeviceStateAudio4->fdPlayback, &fdsWrite); + fdMax = ma_max(fdMax, pDeviceStateAudio4->fdPlayback); } - } -#endif -#if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) - pContext->coreaudio.hCoreFoundation = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"); - if (pContext->coreaudio.hCoreFoundation == NULL) { - return MA_API_NOT_FOUND; + selectResult = select(fdMax + 1, &fdsRead, &fdsWrite, NULL, pTimeout); + } while (selectResult < 0 && errno == EINTR); + + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; } - pContext->coreaudio.CFStringGetCString = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation, "CFStringGetCString"); - pContext->coreaudio.CFRelease = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation, "CFRelease"); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (FD_ISSET(pDeviceStateAudio4->fdCapture, &fdsRead)) { + ma_uint32 framesRead; + result = ma_device_read__audio4(pDevice, pDeviceStateAudio4->pIntermediaryBuffer, pDevice->capture.internalPeriodSizeInFrames, &framesRead); + if (result != MA_SUCCESS) { + return result; + } - pContext->coreaudio.hCoreAudio = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/CoreAudio.framework/CoreAudio"); - if (pContext->coreaudio.hCoreAudio == NULL) { - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); - return MA_API_NOT_FOUND; + ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateAudio4->pIntermediaryBuffer, framesRead); + } } - pContext->coreaudio.AudioObjectGetPropertyData = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyData"); - pContext->coreaudio.AudioObjectGetPropertyDataSize = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyDataSize"); - pContext->coreaudio.AudioObjectSetPropertyData = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio, "AudioObjectSetPropertyData"); - pContext->coreaudio.AudioObjectAddPropertyListener = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio, "AudioObjectAddPropertyListener"); - pContext->coreaudio.AudioObjectRemovePropertyListener = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio, "AudioObjectRemovePropertyListener"); - - /* - It looks like Apple has moved some APIs from AudioUnit into AudioToolbox on more recent versions of macOS. They are still - defined in AudioUnit, but just in case they decide to remove them from there entirely I'm going to implement a fallback. - The way it'll work is that it'll first try AudioUnit, and if the required symbols are not present there we'll fall back to - AudioToolbox. - */ - pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioUnit.framework/AudioUnit"); - if (pContext->coreaudio.hAudioUnit == NULL) { - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); - return MA_API_NOT_FOUND; - } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (FD_ISSET(pDeviceStateAudio4->fdPlayback, &fdsWrite)) { + ma_device_handle_backend_data_callback(pDevice, pDeviceStateAudio4->pIntermediaryBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); - if (ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioComponentFindNext") == NULL) { - /* Couldn't find the required symbols in AudioUnit, so fall back to AudioToolbox. */ - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit); - pContext->coreaudio.hAudioUnit = ma_dlopen(ma_context_get_log(pContext), "/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox"); - if (pContext->coreaudio.hAudioUnit == NULL) { - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); - return MA_API_NOT_FOUND; + result = ma_device_write__audio4(pDevice, pDeviceStateAudio4->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames, NULL); + if (result != MA_SUCCESS) { + return result; + } } } - pContext->coreaudio.AudioComponentFindNext = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioComponentFindNext"); - pContext->coreaudio.AudioComponentInstanceDispose = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioComponentInstanceDispose"); - pContext->coreaudio.AudioComponentInstanceNew = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioComponentInstanceNew"); - pContext->coreaudio.AudioOutputUnitStart = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioOutputUnitStart"); - pContext->coreaudio.AudioOutputUnitStop = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioOutputUnitStop"); - pContext->coreaudio.AudioUnitAddPropertyListener = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioUnitAddPropertyListener"); - pContext->coreaudio.AudioUnitGetPropertyInfo = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioUnitGetPropertyInfo"); - pContext->coreaudio.AudioUnitGetProperty = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioUnitGetProperty"); - pContext->coreaudio.AudioUnitSetProperty = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioUnitSetProperty"); - pContext->coreaudio.AudioUnitInitialize = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioUnitInitialize"); - pContext->coreaudio.AudioUnitRender = ma_dlsym(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit, "AudioUnitRender"); -#else - pContext->coreaudio.CFStringGetCString = (ma_proc)CFStringGetCString; - pContext->coreaudio.CFRelease = (ma_proc)CFRelease; + return MA_SUCCESS; +} - #if defined(MA_APPLE_DESKTOP) - pContext->coreaudio.AudioObjectGetPropertyData = (ma_proc)AudioObjectGetPropertyData; - pContext->coreaudio.AudioObjectGetPropertyDataSize = (ma_proc)AudioObjectGetPropertyDataSize; - pContext->coreaudio.AudioObjectSetPropertyData = (ma_proc)AudioObjectSetPropertyData; - pContext->coreaudio.AudioObjectAddPropertyListener = (ma_proc)AudioObjectAddPropertyListener; - pContext->coreaudio.AudioObjectRemovePropertyListener = (ma_proc)AudioObjectRemovePropertyListener; - #endif +static ma_device_backend_vtable ma_gDeviceBackendVTable_Audio4 = +{ + ma_backend_info__audio4, + ma_context_init__audio4, + ma_context_uninit__audio4, + ma_context_enumerate_devices__audio4, + ma_device_init__audio4, + ma_device_uninit__audio4, + ma_device_start__audio4, + ma_device_stop__audio4, + ma_device_step__audio4, + NULL /* onDeviceWakeup */ +}; - pContext->coreaudio.AudioComponentFindNext = (ma_proc)AudioComponentFindNext; - pContext->coreaudio.AudioComponentInstanceDispose = (ma_proc)AudioComponentInstanceDispose; - pContext->coreaudio.AudioComponentInstanceNew = (ma_proc)AudioComponentInstanceNew; - pContext->coreaudio.AudioOutputUnitStart = (ma_proc)AudioOutputUnitStart; - pContext->coreaudio.AudioOutputUnitStop = (ma_proc)AudioOutputUnitStop; - pContext->coreaudio.AudioUnitAddPropertyListener = (ma_proc)AudioUnitAddPropertyListener; - pContext->coreaudio.AudioUnitGetPropertyInfo = (ma_proc)AudioUnitGetPropertyInfo; - pContext->coreaudio.AudioUnitGetProperty = (ma_proc)AudioUnitGetProperty; - pContext->coreaudio.AudioUnitSetProperty = (ma_proc)AudioUnitSetProperty; - pContext->coreaudio.AudioUnitInitialize = (ma_proc)AudioUnitInitialize; - pContext->coreaudio.AudioUnitRender = (ma_proc)AudioUnitRender; -#endif +ma_device_backend_vtable* ma_device_backend_audio4 = &ma_gDeviceBackendVTable_Audio4; +#else +ma_device_backend_vtable* ma_device_backend_audio4 = NULL; +#endif /* MA_HAS_AUDIO4 */ - /* Audio component. */ - { - AudioComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - #if defined(MA_APPLE_DESKTOP) - desc.componentSubType = kAudioUnitSubType_HALOutput; - #else - desc.componentSubType = kAudioUnitSubType_RemoteIO; - #endif - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; +MA_API ma_device_backend_vtable* ma_audio4_get_vtable(void) +{ + return ma_device_backend_audio4; +} - pContext->coreaudio.component = ((ma_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); - if (pContext->coreaudio.component == NULL) { - #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); - #endif - return MA_FAILED_TO_INIT_BACKEND; - } - } +MA_API ma_context_config_audio4 ma_context_config_audio4_init(void) +{ + ma_context_config_audio4 config; -#if !defined(MA_APPLE_MOBILE) - result = ma_context__init_device_tracking__coreaudio(pContext); - if (result != MA_SUCCESS) { - #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hAudioUnit); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreAudio); - ma_dlclose(ma_context_get_log(pContext), pContext->coreaudio.hCoreFoundation); - #endif - return result; - } -#endif + MA_ZERO_OBJECT(&config); - pContext->coreaudio.noAudioSessionDeactivate = pConfig->coreaudio.noAudioSessionDeactivate; + return config; +} - pCallbacks->onContextInit = ma_context_init__coreaudio; - pCallbacks->onContextUninit = ma_context_uninit__coreaudio; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__coreaudio; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__coreaudio; - pCallbacks->onDeviceInit = ma_device_init__coreaudio; - pCallbacks->onDeviceUninit = ma_device_uninit__coreaudio; - pCallbacks->onDeviceStart = ma_device_start__coreaudio; - pCallbacks->onDeviceStop = ma_device_stop__coreaudio; - pCallbacks->onDeviceRead = NULL; - pCallbacks->onDeviceWrite = NULL; - pCallbacks->onDeviceDataLoop = NULL; +MA_API ma_device_config_audio4 ma_device_config_audio4_init(void) +{ + ma_device_config_audio4 config; - return MA_SUCCESS; + MA_ZERO_OBJECT(&config); + + return config; } -#endif /* MA_HAS_COREAUDIO */ /****************************************************************************** -sndio Backend +OSS Backend ******************************************************************************/ -#ifdef MA_HAS_SNDIO +#ifdef MA_HAS_OSS +#include +#include +#include /* For enumerating over /dev/dsp* devices. */ #include +#include -/* -Only supporting OpenBSD. This did not work very well at all on FreeBSD when I tried it. Not sure if this is due -to miniaudio's implementation or if it's some kind of system configuration issue, but basically the default device -just doesn't emit any sound, or at times you'll hear tiny pieces. I will consider enabling this when there's -demand for it or if I can get it tested and debugged more thoroughly. -*/ -#if 0 -#if defined(__NetBSD__) || defined(__OpenBSD__) -#include +/*#define MA_OSS_LEGACY*/ + +#if !defined(MA_OSS_MODERN) && !defined(MA_OSS_LEGACY) + #if defined(SOUND_VERSION) && SOUND_VERSION >= 0x040000 + #define MA_OSS_MODERN + #else + #define MA_OSS_LEGACY + #endif #endif -#if defined(__FreeBSD__) || defined(__DragonFly__) -#include + +#ifndef SNDCTL_DSP_HALT +#define SNDCTL_DSP_HALT SNDCTL_DSP_RESET #endif + +#ifndef OSS_GETVERSION +#define OSS_GETVERSION _IOR('M', 118, int) #endif -#define MA_SIO_DEVANY "default" -#define MA_SIO_PLAY 1 -#define MA_SIO_REC 2 -#define MA_SIO_NENC 8 -#define MA_SIO_NCHAN 8 -#define MA_SIO_NRATE 16 -#define MA_SIO_NCONF 4 +#define MA_OSS_DEFAULT_DEVICE_NAME "/dev/dsp" -struct ma_sio_hdl; /* <-- Opaque */ +typedef struct ma_context_state_oss +{ + int versionMajor; + int versionMinor; +} ma_context_state_oss; -struct ma_sio_par +typedef struct ma_device_state_oss { - unsigned int bits; - unsigned int bps; - unsigned int sig; - unsigned int le; - unsigned int msb; - unsigned int rchan; - unsigned int pchan; - unsigned int rate; - unsigned int bufsz; - unsigned int xrun; - unsigned int round; - unsigned int appbufsz; - int __pad[3]; - unsigned int __magic; -}; + int fdPlayback; + int fdCapture; + void* pIntermediaryBuffer; +} ma_device_state_oss; -struct ma_sio_enc +static ma_context_state_oss* ma_context_get_backend_state__oss(ma_context* pContext) { - unsigned int bits; - unsigned int bps; - unsigned int sig; - unsigned int le; - unsigned int msb; -}; + return (ma_context_state_oss*)ma_context_get_backend_state(pContext); +} -struct ma_sio_conf +static ma_device_state_oss* ma_device_get_backend_state__oss(ma_device* pDevice) { - unsigned int enc; - unsigned int rchan; - unsigned int pchan; - unsigned int rate; -}; + return (ma_device_state_oss*)ma_device_get_backend_state(pDevice); +} -struct ma_sio_cap +static int ma_open_temp_device__oss(void) { - struct ma_sio_enc enc[MA_SIO_NENC]; - unsigned int rchan[MA_SIO_NCHAN]; - unsigned int pchan[MA_SIO_NCHAN]; - unsigned int rate[MA_SIO_NRATE]; - int __pad[7]; - unsigned int nconf; - struct ma_sio_conf confs[MA_SIO_NCONF]; -}; + /* The OSS sample code uses "/dev/mixer" as the device for getting system properties so I'm going to do the same. */ + int fd = open("/dev/mixer", O_RDONLY, 0); + if (fd >= 0) { + return fd; + } + + return -1; +} + +static void ma_backend_info__oss(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "OSS"; +} +static ma_result ma_context_init__oss(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_oss* pContextStateOSS; + const ma_context_config_oss* pContextConfigOSS = (const ma_context_config_oss*)pContextBackendConfig; + ma_context_config_oss defaultConfigOSS; + int fd; + int ossVersion; + int result; + + if (pContextConfigOSS == NULL) { + defaultConfigOSS = ma_context_config_oss_init(); + pContextConfigOSS = &defaultConfigOSS; + } + + (void)pContextConfigOSS; + + pContextStateOSS = (ma_context_state_oss*)ma_calloc(sizeof(*pContextStateOSS), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateOSS == NULL) { + return MA_OUT_OF_MEMORY; + } + + /* Try opening a temporary device first so we can get version information. This is closed at the end. */ + fd = ma_open_temp_device__oss(); + if (fd == -1) { + ma_free(pContextStateOSS, ma_context_get_allocation_callbacks(pContext)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties. errno = %s", strerror(errno)); /* Looks liks OSS isn't installed, or there are no available devices. */ + return MA_NO_BACKEND; + } + + /* Grab the OSS version. */ + ossVersion = 0; + result = ioctl(fd, OSS_GETVERSION, &ossVersion); + if (result == -1) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[OSS] Failed to retrieve OSS version. Assuming the version of SOUND_VERSION (%d).", SOUND_VERSION); + ossVersion = SOUND_VERSION; + } + + /* The file handle to temp device is no longer needed. Close ASAP. */ + close(fd); + + if (ossVersion > 361) { + pContextStateOSS->versionMajor = ((ossVersion & 0xFF0000) >> 16); + pContextStateOSS->versionMinor = ((ossVersion & 0x00FF00) >> 8); + } else { + pContextStateOSS->versionMajor = (ossVersion / 100); + pContextStateOSS->versionMinor = (ossVersion - (pContextStateOSS->versionMajor * 100)) / 10; + } + + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[OSS] Version %d.%d", pContextStateOSS->versionMajor, pContextStateOSS->versionMinor); + + #if defined(MA_OSS_MODERN) + { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[OSS] API: Modern"); + } + #else + { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[OSS] API: Legacy"); + } + #endif -typedef struct ma_sio_hdl* (* ma_sio_open_proc) (const char*, unsigned int, int); -typedef void (* ma_sio_close_proc) (struct ma_sio_hdl*); -typedef int (* ma_sio_setpar_proc) (struct ma_sio_hdl*, struct ma_sio_par*); -typedef int (* ma_sio_getpar_proc) (struct ma_sio_hdl*, struct ma_sio_par*); -typedef int (* ma_sio_getcap_proc) (struct ma_sio_hdl*, struct ma_sio_cap*); -typedef size_t (* ma_sio_write_proc) (struct ma_sio_hdl*, const void*, size_t); -typedef size_t (* ma_sio_read_proc) (struct ma_sio_hdl*, void*, size_t); -typedef int (* ma_sio_start_proc) (struct ma_sio_hdl*); -typedef int (* ma_sio_stop_proc) (struct ma_sio_hdl*); -typedef int (* ma_sio_initpar_proc)(struct ma_sio_par*); + *ppContextState = pContextStateOSS; -static ma_uint32 ma_get_standard_sample_rate_priority_index__sndio(ma_uint32 sampleRate) /* Lower = higher priority */ + return MA_SUCCESS; +} + +static void ma_context_uninit__oss(ma_context* pContext) { - ma_uint32 i; - for (i = 0; i < ma_countof(g_maStandardSampleRatePriorities); ++i) { - if (g_maStandardSampleRatePriorities[i] == sampleRate) { - return i; - } - } + ma_context_state_oss* pContextStateOSS = ma_context_get_backend_state__oss(pContext); - return (ma_uint32)-1; + ma_free(pContextStateOSS, ma_context_get_allocation_callbacks(pContext)); } -static ma_format ma_format_from_sio_enc__sndio(unsigned int bits, unsigned int bps, unsigned int sig, unsigned int le, unsigned int msb) +static ma_result ma_context_open_device__oss(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, int* pfd) { - /* We only support native-endian right now. */ - if ((ma_is_little_endian() && le == 0) || (ma_is_big_endian() && le == 1)) { - return ma_format_unknown; - } + const char* deviceName; + int flags; - if (bits == 8 && bps == 1 && sig == 0) { - return ma_format_u8; - } - if (bits == 16 && bps == 2 && sig == 1) { - return ma_format_s16; + MA_ASSERT(pContext != NULL); + MA_ASSERT(pfd != NULL); + (void)pContext; + + *pfd = -1; + + /* This function should only be called for playback or capture, not duplex. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; } - if (bits == 24 && bps == 3 && sig == 1) { - return ma_format_s24; + + deviceName = MA_OSS_DEFAULT_DEVICE_NAME; + if (pDeviceID != NULL) { + deviceName = pDeviceID->oss; } - if (bits == 24 && bps == 4 && sig == 1 && msb == 0) { - /*return ma_format_s24_32;*/ + + flags = (deviceType == ma_device_type_playback) ? O_WRONLY : O_RDONLY; + if (shareMode == ma_share_mode_exclusive) { + flags |= O_EXCL; } - if (bits == 32 && bps == 4 && sig == 1) { - return ma_format_s32; + + *pfd = open(deviceName, flags, 0); + if (*pfd == -1) { + return ma_result_from_errno(errno); } - return ma_format_unknown; + return MA_SUCCESS; } -static ma_format ma_find_best_format_from_sio_cap__sndio(struct ma_sio_cap* caps) +static int ma_format_to_oss(ma_format format) { - ma_format bestFormat; - unsigned int iConfig; - - MA_ASSERT(caps != NULL); - - bestFormat = ma_format_unknown; - for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { - unsigned int iEncoding; - for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { - unsigned int bits; - unsigned int bps; - unsigned int sig; - unsigned int le; - unsigned int msb; - ma_format format; + int ossFormat = AFMT_U8; + switch (format) { + case ma_format_s16: ossFormat = (ma_is_little_endian()) ? AFMT_S16_LE : AFMT_S16_BE; break; + #if defined(AFMT_S32_LE) && defined(AFMT_S32_BE) + case ma_format_s24: ossFormat = (ma_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; + case ma_format_s32: ossFormat = (ma_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; + #endif + case ma_format_f32: ossFormat = (ma_is_little_endian()) ? AFMT_S16_LE : AFMT_S16_BE; break; + case ma_format_u8: + default: ossFormat = AFMT_U8; break; + } - if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { - continue; - } + return ossFormat; +} - bits = caps->enc[iEncoding].bits; - bps = caps->enc[iEncoding].bps; - sig = caps->enc[iEncoding].sig; - le = caps->enc[iEncoding].le; - msb = caps->enc[iEncoding].msb; - format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); - if (format == ma_format_unknown) { - continue; /* Format not supported. */ +static ma_format ma_format_from_oss(int ossFormat) +{ + if (ossFormat == AFMT_U8) { + return ma_format_u8; + } else { + if (ma_is_little_endian()) { + switch (ossFormat) { + case AFMT_S16_LE: return ma_format_s16; + #if defined(AFMT_S32_LE) + case AFMT_S32_LE: return ma_format_s32; + #endif + default: return ma_format_unknown; } - - if (bestFormat == ma_format_unknown) { - bestFormat = format; - } else { - if (ma_get_format_priority_index(bestFormat) > ma_get_format_priority_index(format)) { /* <-- Lower = better. */ - bestFormat = format; - } + } else { + switch (ossFormat) { + case AFMT_S16_BE: return ma_format_s16; + #if defined(AFMT_S32_BE) + case AFMT_S32_BE: return ma_format_s32; + #endif + default: return ma_format_unknown; } } } - return bestFormat; + return ma_format_unknown; } -static ma_uint32 ma_find_best_channels_from_sio_cap__sndio(struct ma_sio_cap* caps, ma_device_type deviceType, ma_format requiredFormat) +#if defined(MA_OSS_MODERN) +static void ma_context_add_native_data_format__oss(ma_context* pContext, oss_audioinfo* pAudioInfo, ma_format format, ma_device_info* pDeviceInfo) { - ma_uint32 maxChannels; - unsigned int iConfig; + unsigned int minChannels; + unsigned int maxChannels; + unsigned int minSampleRate; + unsigned int maxSampleRate; - MA_ASSERT(caps != NULL); - MA_ASSERT(requiredFormat != ma_format_unknown); + MA_ASSERT(pContext != NULL); + MA_ASSERT(pAudioInfo != NULL); + MA_ASSERT(pDeviceInfo != NULL); - /* Just pick whatever configuration has the most channels. */ - maxChannels = 0; - for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { - /* The encoding should be of requiredFormat. */ - unsigned int iEncoding; - for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { - unsigned int iChannel; - unsigned int bits; - unsigned int bps; - unsigned int sig; - unsigned int le; - unsigned int msb; - ma_format format; + (void)pContext; - if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { - continue; - } + minChannels = ma_clamp(pAudioInfo->min_channels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + maxChannels = ma_clamp(pAudioInfo->max_channels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); - bits = caps->enc[iEncoding].bits; - bps = caps->enc[iEncoding].bps; - sig = caps->enc[iEncoding].sig; - le = caps->enc[iEncoding].le; - msb = caps->enc[iEncoding].msb; - format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); - if (format != requiredFormat) { - continue; - } + minSampleRate = ma_clamp(pAudioInfo->min_rate, ma_standard_sample_rate_min, ma_standard_sample_rate_max); + maxSampleRate = ma_clamp(pAudioInfo->max_rate, ma_standard_sample_rate_min, ma_standard_sample_rate_max); - /* Getting here means the format is supported. Iterate over each channel count and grab the biggest one. */ - for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { - unsigned int chan = 0; - unsigned int channels; + ma_device_info_add_native_data_format(pDeviceInfo, format, minChannels, maxChannels, minSampleRate, maxSampleRate); +} +#endif - if (deviceType == ma_device_type_playback) { - chan = caps->confs[iConfig].pchan; - } else { - chan = caps->confs[iConfig].rchan; - } +static ma_result ma_context_add_native_data_format_legacy__oss(ma_context* pContext, int fdDevice, ma_format format, ma_device_info* pDeviceInfo) +{ + const int maxChannelCount = 8; + int iChannel; + int formatWant; + int formatHave; + int originalChannels; + int originalRate; + + MA_ASSERT(pDeviceInfo != NULL); + MA_ASSERT(pContext != NULL); + (void)pContext; - if ((chan & (1UL << iChannel)) == 0) { - continue; - } + MA_ASSERT(format == ma_format_u8 || format == ma_format_s16); - if (deviceType == ma_device_type_playback) { - channels = caps->pchan[iChannel]; - } else { - channels = caps->rchan[iChannel]; + originalChannels = 0; + ioctl(fdDevice, SNDCTL_DSP_CHANNELS, &originalChannels); + + originalRate = 0; + ioctl(fdDevice, SNDCTL_DSP_SPEED, &originalRate); + + /* + OSS does not have good support for querying supported channels and sample rates. The only way I can think + of to make this work is to probe it. We'll go up to 8 channels, and for each of them probe for each of our + standard rates. + */ + formatWant = ma_format_to_oss(format); + formatHave = formatWant; + if (ioctl(fdDevice, SNDCTL_DSP_SETFMT, &formatHave) >= 0 && formatHave == formatWant) { + int minChannels = 0x7FFFFFFF; + int maxChannels = 0; + int minSampleRate = 0x7FFFFFFF; + int maxSampleRate = 0; + + for (iChannel = 0; iChannel < maxChannelCount; iChannel += 1) { + int channelsWant = iChannel + 1; + int channelsHave = channelsWant; + ma_uint32 iSampleRate; + + if (ioctl(fdDevice, SNDCTL_DSP_CHANNELS, &channelsHave) >= 0) { + if (minChannels > channelsHave) { + minChannels = channelsHave; + } + if (maxChannels < channelsHave) { + maxChannels = channelsHave; } + + for (iSampleRate = 0; iSampleRate < ma_countof(ma_standard_sample_rates); iSampleRate += 1) { + int sampleRateWant = (int)ma_standard_sample_rates[iSampleRate]; + int sampleRateHave = sampleRateWant; - if (maxChannels < channels) { - maxChannels = channels; + if (ioctl(fdDevice, SNDCTL_DSP_SPEED, &sampleRateHave) >= 0) { + if (minSampleRate > sampleRateHave) { + minSampleRate = sampleRateHave; + } + if (maxSampleRate < sampleRateHave) { + maxSampleRate = sampleRateHave; + } + + ioctl(fdDevice, SNDCTL_DSP_SPEED, &originalRate); + } } + + ioctl(fdDevice, SNDCTL_DSP_CHANNELS, &originalChannels); } } + + ma_device_info_add_native_data_format(pDeviceInfo, format, minChannels, maxChannels, minSampleRate, maxSampleRate); + } else { + /* The format is not supported. */ + return MA_ERROR; } - return maxChannels; + return MA_SUCCESS; } -static ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* caps, ma_device_type deviceType, ma_format requiredFormat, ma_uint32 requiredChannels) +static ma_device_enumeration_result ma_context_enumerate_device_from_fd_legacy__oss(ma_context* pContext, int fd, const char* devnode, struct stat* stDefault, ma_device_type deviceType, ma_device_info* pDeviceInfo, ma_enum_devices_callback_proc callback, void* pUserData) /* Returns the result of the callback. */ { - ma_uint32 firstSampleRate; - ma_uint32 bestSampleRate; - unsigned int iConfig; + struct stat stDevice; + int formats; - MA_ASSERT(caps != NULL); - MA_ASSERT(requiredFormat != ma_format_unknown); - MA_ASSERT(requiredChannels > 0); - MA_ASSERT(requiredChannels <= MA_MAX_CHANNELS); + /* Default. */ + if (fstat(fd, &stDevice) == 0) { + if ((stDefault->st_dev == stDevice.st_dev) && (stDefault->st_ino == stDevice.st_ino)) { + pDeviceInfo->isDefault = MA_TRUE; + } + } - firstSampleRate = 0; /* <-- If the device does not support a standard rate we'll fall back to the first one that's found. */ - bestSampleRate = 0; + /* ID. */ + ma_strncpy_s(pDeviceInfo->id.oss, sizeof(pDeviceInfo->id.oss), devnode, (size_t)-1); - for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { - /* The encoding should be of requiredFormat. */ - unsigned int iEncoding; - for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { - unsigned int iChannel; - unsigned int bits; - unsigned int bps; - unsigned int sig; - unsigned int le; - unsigned int msb; - ma_format format; + /* Name. Not sure how to get this friendly name here so just using the file path for now. Advice welcome! */ + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), devnode, (size_t)-1); - if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { - continue; - } + /* Data Formats. */ + formats = 0; + if (ioctl(fd, SNDCTL_DSP_GETFMTS, &formats) < 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[OSS] Failed to retrieve formats for \"%s\" during device enumeration. Skipping", devnode); + return MA_DEVICE_ENUMERATION_CONTINUE; + } - bits = caps->enc[iEncoding].bits; - bps = caps->enc[iEncoding].bps; - sig = caps->enc[iEncoding].sig; - le = caps->enc[iEncoding].le; - msb = caps->enc[iEncoding].msb; - format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); - if (format != requiredFormat) { - continue; - } + /* We need to support at least u8 or s16. */ + if ((formats & AFMT_U8) == 0 && (formats & (AFMT_S16_LE | AFMT_S16_BE)) == 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[OSS] \"%s\" does not support u8 or s16 formats. Skipping", devnode); + return MA_DEVICE_ENUMERATION_CONTINUE; + } - /* Getting here means the format is supported. Iterate over each channel count and grab the biggest one. */ - for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { - unsigned int chan = 0; - unsigned int channels; - unsigned int iRate; + if (((formats & AFMT_S16_LE) != 0 && ma_is_little_endian()) || ((formats & AFMT_S16_BE) != 0 && ma_is_big_endian())) { + ma_context_add_native_data_format_legacy__oss(pContext, fd, ma_format_s16, pDeviceInfo); + } + if ((formats & AFMT_U8) != 0) { + ma_context_add_native_data_format_legacy__oss(pContext, fd, ma_format_u8, pDeviceInfo); + } - if (deviceType == ma_device_type_playback) { - chan = caps->confs[iConfig].pchan; - } else { - chan = caps->confs[iConfig].rchan; - } + return callback(deviceType, pDeviceInfo, pUserData); +} - if ((chan & (1UL << iChannel)) == 0) { - continue; - } +static ma_result ma_context_enumerate_devices_legacy__oss(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + struct stat stDefault; /* For detecting the default device. */ - if (deviceType == ma_device_type_playback) { - channels = caps->pchan[iChannel]; - } else { - channels = caps->rchan[iChannel]; - } + if (stat(MA_OSS_DEFAULT_DEVICE_NAME, &stDefault) < 0) { + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Could not find default device."); + return MA_NO_BACKEND; + } - if (channels != requiredChannels) { - continue; - } + { + DIR *d = opendir("/dev"); + struct dirent *e; + ma_bool32 isTerminating = MA_FALSE; - /* Getting here means we have found a compatible encoding/channel pair. */ - for (iRate = 0; iRate < MA_SIO_NRATE; iRate += 1) { - ma_uint32 rate = (ma_uint32)caps->rate[iRate]; - ma_uint32 ratePriority; + while ((e = readdir(d)) != NULL) { + if (isTerminating) { + break; + } - if (firstSampleRate == 0) { - firstSampleRate = rate; - } + if (strncmp(e->d_name, "dsp", 3) == 0 && strspn(e->d_name+3, "0123456789") == strlen(e->d_name+3)) { + char devnode[256]; + int fdDevice; + struct audio_buf_info bufInfo; + ma_device_info deviceInfo; - /* Disregard this rate if it's not a standard one. */ - ratePriority = ma_get_standard_sample_rate_priority_index__sndio(rate); - if (ratePriority == (ma_uint32)-1) { - continue; + MA_ZERO_OBJECT(&deviceInfo); + + ma_strcpy_s(devnode, sizeof(devnode), "/dev/"); + ma_strcat_s(devnode, sizeof(devnode), e->d_name); + + + /* + We'll need to open the device first. We need to try both O_RDONLY and O_WRONLY, one for capture the other + for playback. The idea behind checking SNDCTL_DSP_GETOSPACE and SNDCTL_DSP_GETISPACE is to confirm that + the device is usable as a playback or capture device. I'm not sure if this is the most robust way to check + for this so advice welcome on that front. + */ + if (!isTerminating) { + fdDevice = open(devnode, O_WRONLY); + if (fdDevice >= 0) { + if (ioctl(fdDevice, SNDCTL_DSP_GETOSPACE, &bufInfo) >= 0) { + isTerminating = (ma_context_enumerate_device_from_fd_legacy__oss(pContext, fdDevice, devnode, &stDefault, ma_device_type_playback, &deviceInfo, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + } + + close(fdDevice); + } else { + /* Not a playback device. */ } + } - if (ma_get_standard_sample_rate_priority_index__sndio(bestSampleRate) > ratePriority) { /* Lower = better. */ - bestSampleRate = rate; + if (!isTerminating) { + fdDevice = open(devnode, O_RDONLY); + if (fdDevice >= 0) { + if (ioctl(fdDevice, SNDCTL_DSP_GETISPACE, &bufInfo) >= 0) { + isTerminating = (ma_context_enumerate_device_from_fd_legacy__oss(pContext, fdDevice, devnode, &stDefault, ma_device_type_capture, &deviceInfo, callback, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + } + + close(fdDevice); + } else { + /* Not a capture device. */ } } } } - } - /* If a standard sample rate was not found just fall back to the first one that was iterated. */ - if (bestSampleRate == 0) { - bestSampleRate = firstSampleRate; + closedir(d); } - return bestSampleRate; + return MA_SUCCESS; } - -static ma_result ma_context_enumerate_devices__sndio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +#if defined(MA_OSS_MODERN) +static ma_result ma_context_enumerate_devices_modern__oss(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_bool32 isTerminating = MA_FALSE; - struct ma_sio_hdl* handle; + oss_sysinfo si; + int result; + int fd; + struct stat stDefault; /* For detecting the default device. */ - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); + /* We need to stat the default device so we use this to determine the default device later. If this fails we need to abort. */ + if (stat(MA_OSS_DEFAULT_DEVICE_NAME, &stDefault) < 0) { + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Could not find default device."); + return MA_NO_BACKEND; + } - /* sndio doesn't seem to have a good device enumeration API, so I'm therefore only enumerating over default devices for now. */ + fd = ma_open_temp_device__oss(); + if (fd != -1) { + result = ioctl(fd, SNDCTL_SYSINFO, &si); + if (result != -1) { + ma_bool32 isTerminating = MA_FALSE; + int iAudioDevice; + for (iAudioDevice = 0; iAudioDevice < si.numaudios && !isTerminating; iAudioDevice += 1) { + oss_audioinfo ai; + ai.dev = iAudioDevice; + result = ioctl(fd, SNDCTL_AUDIOINFO, &ai); + if (result != -1) { + if (ai.devnode[0] != '\0') { /* <-- Can be blank, according to documentation. */ + ma_device_info deviceInfo; + struct stat stDevice; + int formatMask; + + MA_ZERO_OBJECT(&deviceInfo); + + /* Default. */ + if (stat(ai.devnode, &stDevice) == 0) { + if ((stDefault.st_dev == stDevice.st_dev) && (stDefault.st_ino == stDevice.st_ino)) { + deviceInfo.isDefault = MA_TRUE; + } + } - /* Playback. */ - if (!isTerminating) { - handle = ((ma_sio_open_proc)pContext->sndio.sio_open)(MA_SIO_DEVANY, MA_SIO_PLAY, 0); - if (handle != NULL) { - /* Supports playback. */ - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), MA_SIO_DEVANY); - ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME); + /* ID */ + ma_strncpy_s(deviceInfo.id.oss, sizeof(deviceInfo.id.oss), ai.devnode, (size_t)-1); - isTerminating = !callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + /* + Name - ((ma_sio_close_proc)pContext->sndio.sio_close)(handle); - } - } + The human readable device name should be in the "ai.handle" variable, but it can + sometimes be empty in which case we just fall back to "ai.name" which is less user + friendly, but usually has a value. + */ + if (ai.handle[0] != '\0') { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.handle, (size_t)-1); + } else { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.name, (size_t)-1); + } - /* Capture. */ - if (!isTerminating) { - handle = ((ma_sio_open_proc)pContext->sndio.sio_open)(MA_SIO_DEVANY, MA_SIO_REC, 0); - if (handle != NULL) { - /* Supports capture. */ - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), "default"); - ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME); + /* Data Format. */ + deviceInfo.nativeDataFormatCount = 0; + + if ((ai.caps & PCM_CAP_OUTPUT) != 0) { + formatMask = ai.oformats; + } else { + formatMask = ai.iformats; + } + + if (((formatMask & AFMT_S16_LE) != 0 && ma_is_little_endian()) || ((formatMask & AFMT_S16_BE) != 0 && ma_is_big_endian())) { + ma_context_add_native_data_format__oss(pContext, &ai, ma_format_s16, &deviceInfo); + } + #if defined(AFMT_S32_LE) && defined(AFMT_S32_BE) + if (((formatMask & AFMT_S32_LE) != 0 && ma_is_little_endian()) || ((formatMask & AFMT_S32_LE) != 0 && ma_is_big_endian())) { + ma_context_add_native_data_format__oss(pContext, &ai, ma_format_s32, &deviceInfo); + } + #endif + if ((formatMask & AFMT_U8) != 0) { + ma_context_add_native_data_format__oss(pContext, &ai, ma_format_u8, &deviceInfo); + } + + + /* The device can be both playback and capture. */ + if (!isTerminating && (ai.caps & PCM_CAP_OUTPUT) != 0) { + isTerminating = (callback(ma_device_type_playback, &deviceInfo, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + } + if (!isTerminating && (ai.caps & PCM_CAP_INPUT) != 0) { + isTerminating = (callback(ma_device_type_capture, &deviceInfo, pUserData) == MA_DEVICE_ENUMERATION_ABORT); + } - isTerminating = !callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + if (isTerminating) { + break; + } + } + } + } - ((ma_sio_close_proc)pContext->sndio.sio_close)(handle); + close(fd); + } else { + close(fd); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[OSS] Failed to retrieve system information for device enumeration. Falling back to legacy code path."); + return ma_context_enumerate_devices_legacy__oss(pContext, callback, pUserData); } + } else { + close(fd); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration. Falling back to legacy code path."); + return ma_context_enumerate_devices_legacy__oss(pContext, callback, pUserData); } return MA_SUCCESS; } +#endif -static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - char devid[256]; - struct ma_sio_hdl* handle; - struct ma_sio_cap caps; - unsigned int iConfig; - MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); - /* We need to open the device before we can get information about it. */ - if (pDeviceID == NULL) { - ma_strcpy_s(devid, sizeof(devid), MA_SIO_DEVANY); - ma_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (deviceType == ma_device_type_playback) ? MA_DEFAULT_PLAYBACK_DEVICE_NAME : MA_DEFAULT_CAPTURE_DEVICE_NAME); - } else { - ma_strcpy_s(devid, sizeof(devid), pDeviceID->sndio); - ma_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), devid); + #if defined(MA_OSS_MODERN) + { + return ma_context_enumerate_devices_modern__oss(pContext, callback, pUserData); } + #endif - handle = ((ma_sio_open_proc)pContext->sndio.sio_open)(devid, (deviceType == ma_device_type_playback) ? MA_SIO_PLAY : MA_SIO_REC, 0); - if (handle == NULL) { - return MA_NO_DEVICE; + #if defined(MA_OSS_LEGACY) + { + return ma_context_enumerate_devices_legacy__oss(pContext, callback, pUserData); } + #endif +} - if (((ma_sio_getcap_proc)pContext->sndio.sio_getcap)(handle, &caps) == 0) { - return MA_ERROR; - } +static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_config_oss* pDeviceConfigOSS, ma_device_descriptor* pDescriptor, ma_device_type deviceType, int* pFD) +{ + ma_result result; + int ossResult; + int fd; + const ma_device_id* pDeviceID = NULL; + ma_share_mode shareMode; + int ossFormat; + int ossChannels; + int ossSampleRate; + int ossFragment; - pDeviceInfo->nativeDataFormatCount = 0; + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pDeviceConfigOSS != NULL); + MA_ASSERT(pFD != NULL); + MA_ASSERT(deviceType != ma_device_type_duplex); - for (iConfig = 0; iConfig < caps.nconf; iConfig += 1) { - /* - The main thing we care about is that the encoding is supported by miniaudio. If it is, we want to give - preference to some formats over others. - */ - unsigned int iEncoding; - unsigned int iChannel; - unsigned int iRate; + (void)pDeviceConfigOSS; - for (iEncoding = 0; iEncoding < MA_SIO_NENC; iEncoding += 1) { - unsigned int bits; - unsigned int bps; - unsigned int sig; - unsigned int le; - unsigned int msb; - ma_format format; + pDeviceID = pDescriptor->pDeviceID; + shareMode = pDescriptor->shareMode; + ossFormat = ma_format_to_oss((pDescriptor->format != ma_format_unknown) ? pDescriptor->format : ma_format_s16); /* Use s16 by default because OSS doesn't like floating point. */ + ossChannels = (int)(pDescriptor->channels > 0) ? pDescriptor->channels : MA_DEFAULT_CHANNELS; + ossSampleRate = (int)(pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; - if ((caps.confs[iConfig].enc & (1UL << iEncoding)) == 0) { - continue; - } + result = ma_context_open_device__oss(ma_device_get_context(pDevice), deviceType, pDeviceID, shareMode, &fd); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; + } - bits = caps.enc[iEncoding].bits; - bps = caps.enc[iEncoding].bps; - sig = caps.enc[iEncoding].sig; - le = caps.enc[iEncoding].le; - msb = caps.enc[iEncoding].msb; - format = ma_format_from_sio_enc__sndio(bits, bps, sig, le, msb); - if (format == ma_format_unknown) { - continue; /* Format not supported. */ - } + /* + The OSS documentation is very clear about the order we should be initializing the device's properties: + 1) Format + 2) Channels + 3) Sample rate. + */ + + /* Format. */ + ossResult = ioctl(fd, SNDCTL_DSP_SETFMT, &ossFormat); + if (ossResult == -1) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format."); + return ma_result_from_errno(errno); + } + /* Channels. */ + ossResult = ioctl(fd, SNDCTL_DSP_CHANNELS, &ossChannels); + if (ossResult == -1) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count."); + return ma_result_from_errno(errno); + } - /* Channels. */ - for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { - unsigned int chan = 0; - unsigned int channels; + /* Sample Rate. */ + ossResult = ioctl(fd, SNDCTL_DSP_SPEED, &ossSampleRate); + if (ossResult == -1) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate."); + return ma_result_from_errno(errno); + } - if (deviceType == ma_device_type_playback) { - chan = caps.confs[iConfig].pchan; - } else { - chan = caps.confs[iConfig].rchan; - } + /* + Buffer. - if ((chan & (1UL << iChannel)) == 0) { - continue; - } + The documentation says that the fragment settings should be set as soon as possible, but I'm not sure if + it should be done before or after format/channels/rate. - if (deviceType == ma_device_type_playback) { - channels = caps.pchan[iChannel]; - } else { - channels = caps.rchan[iChannel]; - } + OSS wants the fragment size in bytes and a power of 2. When setting, we specify the power, not the actual + value. + */ + { + ma_uint32 periodSizeInFrames; + ma_uint32 periodSizeInBytes; + ma_uint32 ossFragmentSizePower; + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, (ma_uint32)ossSampleRate); - /* Sample Rates. */ - for (iRate = 0; iRate < MA_SIO_NRATE; iRate += 1) { - if ((caps.confs[iConfig].rate & (1UL << iRate)) != 0) { - ma_device_info_add_native_data_format(pDeviceInfo, format, channels, caps.rate[iRate], 0); - } - } - } + periodSizeInBytes = ma_round_to_power_of_2(periodSizeInFrames * ma_get_bytes_per_frame(ma_format_from_oss(ossFormat), ossChannels)); + if (periodSizeInBytes < 16) { + periodSizeInBytes = 16; } + + ossFragmentSizePower = 4; + periodSizeInBytes >>= 4; + while (periodSizeInBytes >>= 1) { + ossFragmentSizePower += 1; + } + + ossFragment = (int)((pDescriptor->periodCount << 16) | ossFragmentSizePower); + ossResult = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &ossFragment); + if (ossResult == -1) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count."); + return ma_result_from_errno(errno); + } + } + + pDescriptor->format = ma_format_from_oss(ossFormat); + pDescriptor->channels = ossChannels; + pDescriptor->sampleRate = ossSampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); + pDescriptor->periodCount = (ma_uint32)(ossFragment >> 16); + pDescriptor->periodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDescriptor->format, pDescriptor->channels); + + if (pDescriptor->format == ma_format_unknown) { + close(fd); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio."); + return MA_FORMAT_NOT_SUPPORTED; } - ((ma_sio_close_proc)pContext->sndio.sio_close)(handle); + *pFD = fd; + return MA_SUCCESS; } -static ma_result ma_device_uninit__sndio(ma_device* pDevice) +static void ma_device_uninit_internal__oss(ma_device* pDevice, ma_device_state_oss* pDeviceStateOSS) { - MA_ASSERT(pDevice != NULL); + ma_device_type deviceType = ma_device_get_type(pDevice); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + close(pDeviceStateOSS->fdCapture); } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + close(pDeviceStateOSS->fdPlayback); } - return MA_SUCCESS; + ma_free(pDeviceStateOSS, ma_device_get_allocation_callbacks(pDevice)); } -static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) +static ma_result ma_device_init__oss(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - const char* pDeviceName; - ma_ptr handle; - int openFlags = 0; - struct ma_sio_cap caps; - struct ma_sio_par par; - const ma_device_id* pDeviceID; - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - ma_format internalFormat; - ma_uint32 internalChannels; - ma_uint32 internalSampleRate; - ma_uint32 internalPeriodSizeInFrames; - ma_uint32 internalPeriods; - - MA_ASSERT(pConfig != NULL); - MA_ASSERT(deviceType != ma_device_type_duplex); - MA_ASSERT(pDevice != NULL); + ma_device_state_oss* pDeviceStateOSS; + const ma_device_config_oss* pDeviceConfigOSS = (const ma_device_config_oss*)pDeviceBackendConfig; + ma_device_config_oss defaultConfigOSS; + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint32 intermediaryBufferSizeCapture = 0; + ma_uint32 intermediaryBufferSizePlayback = 0; + ma_uint32 intermediaryBufferAllocationSize = 0; - if (deviceType == ma_device_type_capture) { - openFlags = MA_SIO_REC; - } else { - openFlags = MA_SIO_PLAY; + if (pDeviceConfigOSS == NULL) { + defaultConfigOSS = ma_device_config_oss_init(); + pDeviceConfigOSS = &defaultConfigOSS; } - pDeviceID = pDescriptor->pDeviceID; - format = pDescriptor->format; - channels = pDescriptor->channels; - sampleRate = pDescriptor->sampleRate; + pDeviceStateOSS = (ma_device_state_oss*)ma_calloc(sizeof(*pDeviceStateOSS), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateOSS == NULL) { + return MA_OUT_OF_MEMORY; + } - pDeviceName = MA_SIO_DEVANY; - if (pDeviceID != NULL) { - pDeviceName = pDeviceID->sndio; + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - handle = (ma_ptr)((ma_sio_open_proc)pDevice->pContext->sndio.sio_open)(pDeviceName, openFlags, 0); - if (handle == NULL) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device."); - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_init_fd__oss(pDevice, pDeviceConfigOSS, pDescriptorCapture, ma_device_type_capture, &pDeviceStateOSS->fdCapture); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + ma_device_uninit_internal__oss(pDevice, pDeviceStateOSS); + return result; + } + + intermediaryBufferSizeCapture = ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodSizeInFrames; } - /* We need to retrieve the device caps to determine the most appropriate format to use. */ - if (((ma_sio_getcap_proc)pDevice->pContext->sndio.sio_getcap)((struct ma_sio_hdl*)handle, &caps) == 0) { - ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps."); - return MA_ERROR; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_init_fd__oss(pDevice, pDeviceConfigOSS, pDescriptorPlayback, ma_device_type_playback, &pDeviceStateOSS->fdPlayback); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + ma_device_uninit_internal__oss(pDevice, pDeviceStateOSS); + return result; + } + + intermediaryBufferSizePlayback = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodSizeInFrames; } /* - Note: sndio reports a huge range of available channels. This is inconvenient for us because there's no real - way, as far as I can tell, to get the _actual_ channel count of the device. I'm therefore restricting this - to the requested channels, regardless of whether or not the default channel count is requested. - - For hardware devices, I'm suspecting only a single channel count will be reported and we can safely use the - value returned by ma_find_best_channels_from_sio_cap__sndio(). + We need an intermediary buffer. Since the capture and playback sides are never used simultaneously we can make + this the size of the maximum of the two. We can attach this to the end of the device state allocation. */ - if (deviceType == ma_device_type_capture) { - if (format == ma_format_unknown) { - format = ma_find_best_format_from_sio_cap__sndio(&caps); - } + intermediaryBufferAllocationSize = ma_align_64(ma_max(intermediaryBufferSizeCapture, intermediaryBufferSizePlayback)); - if (channels == 0) { - if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { - channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); - } else { - channels = MA_DEFAULT_CHANNELS; - } - } - } else { - if (format == ma_format_unknown) { - format = ma_find_best_format_from_sio_cap__sndio(&caps); - } + { + size_t deviceStateAllocationSizeNew; + ma_device_state_oss* pDeviceStateOSSNew; - if (channels == 0) { - if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { - channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); - } else { - channels = MA_DEFAULT_CHANNELS; - } + deviceStateAllocationSizeNew = 0; + deviceStateAllocationSizeNew += ma_align_64(sizeof(*pDeviceStateOSS)); + deviceStateAllocationSizeNew += ma_align_64(intermediaryBufferAllocationSize); + + pDeviceStateOSSNew = (ma_device_state_oss*)ma_realloc(pDeviceStateOSS, deviceStateAllocationSizeNew, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateOSSNew == NULL) { + ma_device_uninit_internal__oss(pDevice, pDeviceStateOSS); + return MA_OUT_OF_MEMORY; } - } - if (sampleRate == 0) { - sampleRate = ma_find_best_sample_rate_from_sio_cap__sndio(&caps, pConfig->deviceType, format, channels); + pDeviceStateOSS = pDeviceStateOSSNew; } + pDeviceStateOSS->pIntermediaryBuffer = ma_offset_ptr(pDeviceStateOSS, ma_align_64(sizeof(*pDeviceStateOSS))); + - ((ma_sio_initpar_proc)pDevice->pContext->sndio.sio_initpar)(&par); - par.msb = 0; - par.le = ma_is_little_endian(); + *ppDeviceState = pDeviceStateOSS; - switch (format) { - case ma_format_u8: - { - par.bits = 8; - par.bps = 1; - par.sig = 0; - } break; + return MA_SUCCESS; +} - case ma_format_s24: - { - par.bits = 24; - par.bps = 3; - par.sig = 1; - } break; +static void ma_device_uninit__oss(ma_device* pDevice) +{ + ma_device_state_oss* pDeviceStateOSS = ma_device_get_backend_state__oss(pDevice); + ma_device_uninit_internal__oss(pDevice, pDeviceStateOSS); +} - case ma_format_s32: - { - par.bits = 32; - par.bps = 4; - par.sig = 1; - } break; - case ma_format_s16: - case ma_format_f32: - case ma_format_unknown: - default: - { - par.bits = 16; - par.bps = 2; - par.sig = 1; - } break; - } +static void ma_device_prime_playback_buffer__oss(ma_device* pDevice) +{ + ma_device_state_oss* pDeviceStateOSS = ma_device_get_backend_state__oss(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint8 buffer[4096]; + ma_uint32 bpf; + ma_uint32 framesToWrite; + ma_uint32 framesWritten; - if (deviceType == ma_device_type_capture) { - par.rchan = channels; - } else { - par.pchan = channels; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + framesToWrite = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods; + framesWritten = 0; + + MA_ZERO_MEMORY(buffer, sizeof(buffer)); + + while (framesWritten < framesToWrite) { + ma_uint32 framesToWriteThisIteration = sizeof(buffer) / bpf; + ma_uint32 framesRemaining = framesToWrite - framesWritten; + if (framesToWriteThisIteration > framesRemaining) { + framesToWriteThisIteration = framesRemaining; + } + + /* Just a guard to ensure we don't get stuck in a loop. Should never happen in practice (would require a massive channel count). */ + if (framesToWriteThisIteration == 0) { + break; + } + + write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + framesWritten += framesToWriteThisIteration; + } } +} - par.rate = sampleRate; +/* +Note on Starting and Stopping +============================= +In the past I was using SNDCTL_DSP_HALT to stop the device, however this results in issues when +trying to resume the device again. If we use SNDCTL_DSP_HALT, the next write() or read() will +fail. Instead what we need to do is just not write or read to and from the device when the +device is not running. As a result, the stop function for OSS is just an empty stub. - internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, par.rate, pConfig->performanceProfile); +The downside to this technique is that I've noticed a fairly lengthy delay in stopping the +device, up to a second. This is on a virtual machine, and as such might just be due to the +virtual drivers, but I'm not fully sure. I am not sure how to work around this problem so for +the moment that's just how it's going to have to be. - par.round = internalPeriodSizeInFrames; - par.appbufsz = par.round * pDescriptor->periodCount; +When starting the device, OSS will automatically start it when write() or read() is called. - if (((ma_sio_setpar_proc)pDevice->pContext->sndio.sio_setpar)((struct ma_sio_hdl*)handle, &par) == 0) { - ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size."); - return MA_ERROR; - } +UPDATE: 2026-01-14 +- I'm no longer getting the stop delay when running from inside Virtual Box. +- In playback mode, draining with SNDCTL_DSP_SYNC seems to work as expected. +*/ +static ma_result ma_device_start__oss(ma_device* pDevice) +{ + ma_device_type deviceType = ma_device_get_type(pDevice); - if (((ma_sio_getpar_proc)pDevice->pContext->sndio.sio_getpar)((struct ma_sio_hdl*)handle, &par) == 0) { - ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size."); - return MA_ERROR; + /* + For playback we'll start the device by priming it with silence. Otherwise we'll do nothing + and let it start in step(). We want the playback buffer to be initially filled with silence + for the benefit of duplex mode. + */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_device_prime_playback_buffer__oss(pDevice); } - internalFormat = ma_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); - internalChannels = (deviceType == ma_device_type_capture) ? par.rchan : par.pchan; - internalSampleRate = par.rate; - internalPeriods = par.appbufsz / par.round; - internalPeriodSizeInFrames = par.round; + return MA_SUCCESS; +} - if (deviceType == ma_device_type_capture) { - pDevice->sndio.handleCapture = handle; - } else { - pDevice->sndio.handlePlayback = handle; - } +static ma_result ma_device_stop__oss(ma_device* pDevice) +{ + ma_device_state_oss* pDeviceStateOSS = ma_device_get_backend_state__oss(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); - pDescriptor->format = internalFormat; - pDescriptor->channels = internalChannels; - pDescriptor->sampleRate = internalSampleRate; - ma_channel_map_init_standard(ma_standard_channel_map_sndio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); - pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; - pDescriptor->periodCount = internalPeriods; + /* Try draining. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ioctl(pDeviceStateOSS->fdPlayback, SNDCTL_DSP_SYNC, 0); + } return MA_SUCCESS; } -static ma_result ma_device_init__sndio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) { - MA_ASSERT(pDevice != NULL); - - MA_ZERO_OBJECT(&pDevice->sndio); + ma_device_state_oss* pDeviceStateOSS = ma_device_get_backend_state__oss(pDevice); + int resultOSS; - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; + if (pFramesWritten != NULL) { + *pFramesWritten = 0; } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_handle__sndio(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); - if (result != MA_SUCCESS) { - return result; - } + resultOSS = write(pDeviceStateOSS->fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + if (resultOSS < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device."); + return ma_result_from_errno(errno); } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_handle__sndio(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); - if (result != MA_SUCCESS) { - return result; - } + if (pFramesWritten != NULL) { + *pFramesWritten = (ma_uint32)resultOSS / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); } return MA_SUCCESS; } -static ma_result ma_device_start__sndio(ma_device* pDevice) +static ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) { - MA_ASSERT(pDevice != NULL); + ma_device_state_oss* pDeviceStateOSS = ma_device_get_backend_state__oss(pDevice); + int resultOSS; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); + if (pFramesRead != NULL) { + *pFramesRead = 0; } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); /* <-- Doesn't actually playback until data is written. */ + resultOSS = read(pDeviceStateOSS->fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + if (resultOSS < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client."); + return ma_result_from_errno(errno); + } + + if (pFramesRead != NULL) { + *pFramesRead = (ma_uint32)resultOSS / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); } return MA_SUCCESS; } -static ma_result ma_device_stop__sndio(ma_device* pDevice) +static ma_result ma_device_step__oss(ma_device* pDevice, ma_blocking_mode blockingMode) { - MA_ASSERT(pDevice != NULL); + ma_device_state_oss* pDeviceStateOSS = ma_device_get_backend_state__oss(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + struct timeval tv; + struct timeval* pTimeout = NULL; + fd_set fdsRead; + fd_set fdsWrite; + int fdMax; + int selectResult; + ma_result result; - /* - From the documentation: + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + tv.tv_sec = 0; + tv.tv_usec = 0; + pTimeout = &tv; + } - The sio_stop() function puts the audio subsystem in the same state as before sio_start() is called. It stops recording, drains the play buffer and then - stops playback. If samples to play are queued but playback hasn't started yet then playback is forced immediately; playback will actually stop once the - buffer is drained. In no case are samples in the play buffer discarded. + do + { + FD_ZERO(&fdsRead); + FD_ZERO(&fdsWrite); - Therefore, sio_stop() performs all of the necessary draining for us. - */ + fdMax = 0; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((ma_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); - } + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + FD_SET(pDeviceStateOSS->fdCapture, &fdsRead); + fdMax = ma_max(fdMax, pDeviceStateOSS->fdCapture); + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + FD_SET(pDeviceStateOSS->fdPlayback, &fdsWrite); + fdMax = ma_max(fdMax, pDeviceStateOSS->fdPlayback); + } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((ma_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); + selectResult = select(fdMax + 1, &fdsRead, &fdsWrite, NULL, pTimeout); + } while (selectResult < 0 && errno == EINTR); + + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; } - return MA_SUCCESS; -} + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (FD_ISSET(pDeviceStateOSS->fdCapture, &fdsRead)) { + ma_uint32 framesRead; -static ma_result ma_device_write__sndio(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) -{ - int result; + result = ma_device_read__oss(pDevice, pDeviceStateOSS->pIntermediaryBuffer, pDevice->capture.internalPeriodSizeInFrames, &framesRead); + if (result != MA_SUCCESS) { + return result; + } - if (pFramesWritten != NULL) { - *pFramesWritten = 0; + ma_device_handle_backend_data_callback(pDevice, NULL, pDeviceStateOSS->pIntermediaryBuffer, framesRead); + } } - result = ((ma_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); - if (result == 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device."); - return MA_IO_ERROR; - } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (FD_ISSET(pDeviceStateOSS->fdPlayback, &fdsWrite)) { + ma_device_handle_backend_data_callback(pDevice, pDeviceStateOSS->pIntermediaryBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); - if (pFramesWritten != NULL) { - *pFramesWritten = frameCount; + result = ma_device_write__oss(pDevice, pDeviceStateOSS->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames, NULL); + if (result != MA_SUCCESS) { + return result; + } + } } return MA_SUCCESS; } -static ma_result ma_device_read__sndio(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) +static ma_device_backend_vtable ma_gDeviceBackendVTable_OSS = { - int result; - - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - result = ((ma_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct ma_sio_hdl*)pDevice->sndio.handleCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - if (result == 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device."); - return MA_IO_ERROR; - } + ma_backend_info__oss, + ma_context_init__oss, + ma_context_uninit__oss, + ma_context_enumerate_devices__oss, + ma_device_init__oss, + ma_device_uninit__oss, + ma_device_start__oss, + ma_device_stop__oss, + ma_device_step__oss, + NULL /* onDeviceWakeup */ +}; - if (pFramesRead != NULL) { - *pFramesRead = frameCount; - } +ma_device_backend_vtable* ma_device_backend_oss = &ma_gDeviceBackendVTable_OSS; +#else +ma_device_backend_vtable* ma_device_backend_oss = NULL; +#endif /* MA_HAS_OSS */ - return MA_SUCCESS; +MA_API ma_device_backend_vtable* ma_oss_get_vtable(void) +{ + return ma_device_backend_oss; } -static ma_result ma_context_uninit__sndio(ma_context* pContext) +MA_API ma_context_config_oss ma_context_config_oss_init(void) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_sndio); + ma_context_config_oss config; - (void)pContext; - return MA_SUCCESS; + MA_ZERO_OBJECT(&config); + + return config; } -static ma_result ma_context_init__sndio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +MA_API ma_device_config_oss ma_device_config_oss_init(void) { -#ifndef MA_NO_RUNTIME_LINKING - const char* libsndioNames[] = { - "libsndio.so" - }; - size_t i; - - for (i = 0; i < ma_countof(libsndioNames); ++i) { - pContext->sndio.sndioSO = ma_dlopen(ma_context_get_log(pContext), libsndioNames[i]); - if (pContext->sndio.sndioSO != NULL) { - break; - } - } - - if (pContext->sndio.sndioSO == NULL) { - return MA_NO_BACKEND; - } + ma_device_config_oss config; - pContext->sndio.sio_open = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_open"); - pContext->sndio.sio_close = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_close"); - pContext->sndio.sio_setpar = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_setpar"); - pContext->sndio.sio_getpar = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_getpar"); - pContext->sndio.sio_getcap = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_getcap"); - pContext->sndio.sio_write = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_write"); - pContext->sndio.sio_read = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_read"); - pContext->sndio.sio_start = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_start"); - pContext->sndio.sio_stop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_stop"); - pContext->sndio.sio_initpar = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->sndio.sndioSO, "sio_initpar"); -#else - pContext->sndio.sio_open = sio_open; - pContext->sndio.sio_close = sio_close; - pContext->sndio.sio_setpar = sio_setpar; - pContext->sndio.sio_getpar = sio_getpar; - pContext->sndio.sio_getcap = sio_getcap; - pContext->sndio.sio_write = sio_write; - pContext->sndio.sio_read = sio_read; - pContext->sndio.sio_start = sio_start; - pContext->sndio.sio_stop = sio_stop; - pContext->sndio.sio_initpar = sio_initpar; -#endif - - pCallbacks->onContextInit = ma_context_init__sndio; - pCallbacks->onContextUninit = ma_context_uninit__sndio; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__sndio; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__sndio; - pCallbacks->onDeviceInit = ma_device_init__sndio; - pCallbacks->onDeviceUninit = ma_device_uninit__sndio; - pCallbacks->onDeviceStart = ma_device_start__sndio; - pCallbacks->onDeviceStop = ma_device_stop__sndio; - pCallbacks->onDeviceRead = ma_device_read__sndio; - pCallbacks->onDeviceWrite = ma_device_write__sndio; - pCallbacks->onDeviceDataLoop = NULL; + MA_ZERO_OBJECT(&config); - (void)pConfig; - return MA_SUCCESS; + return config; } -#endif /* MA_HAS_SNDIO */ /****************************************************************************** -audio(4) Backend +AAudio Backend ******************************************************************************/ -#ifdef MA_HAS_AUDIO4 -#include -#include -#include -#include -#include -#include -#include +#ifdef MA_HAS_AAUDIO -#ifdef __NetBSD__ -#include +#ifdef MA_NO_RUNTIME_LINKING + #include #endif -#if defined(__OpenBSD__) - #include - #if defined(OpenBSD) && OpenBSD >= 201709 - #define MA_AUDIO4_USE_NEW_API - #endif -#endif +typedef int32_t ma_aaudio_result_t; +typedef int32_t ma_aaudio_direction_t; +typedef int32_t ma_aaudio_sharing_mode_t; +typedef int32_t ma_aaudio_format_t; +typedef int32_t ma_aaudio_stream_state_t; +typedef int32_t ma_aaudio_performance_mode_t; +typedef int32_t ma_aaudio_usage_t; +typedef int32_t ma_aaudio_content_type_t; +typedef int32_t ma_aaudio_input_preset_t; +typedef int32_t ma_aaudio_allowed_capture_policy_t; +typedef int32_t ma_aaudio_data_callback_result_t; +typedef struct ma_AAudioStreamBuilder_t* ma_AAudioStreamBuilder; +typedef struct ma_AAudioStream_t* ma_AAudioStream; -static void ma_construct_device_id__audio4(char* id, size_t idSize, const char* base, int deviceIndex) -{ - size_t baseLen; +#define MA_AAUDIO_UNSPECIFIED 0 - MA_ASSERT(id != NULL); - MA_ASSERT(idSize > 0); - MA_ASSERT(deviceIndex >= 0); +/* Result codes. miniaudio only cares about the success code. */ +#define MA_AAUDIO_OK 0 - baseLen = strlen(base); - MA_ASSERT(idSize > baseLen); +/* Directions. */ +#define MA_AAUDIO_DIRECTION_OUTPUT 0 +#define MA_AAUDIO_DIRECTION_INPUT 1 - ma_strcpy_s(id, idSize, base); - ma_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10); -} +/* Sharing modes. */ +#define MA_AAUDIO_SHARING_MODE_EXCLUSIVE 0 +#define MA_AAUDIO_SHARING_MODE_SHARED 1 -static ma_result ma_extract_device_index_from_id__audio4(const char* id, const char* base, int* pIndexOut) -{ - size_t idLen; - size_t baseLen; - const char* deviceIndexStr; +/* Formats. */ +#define MA_AAUDIO_FORMAT_PCM_I16 1 +#define MA_AAUDIO_FORMAT_PCM_FLOAT 2 - MA_ASSERT(id != NULL); - MA_ASSERT(base != NULL); - MA_ASSERT(pIndexOut != NULL); +/* Stream states. */ +#define MA_AAUDIO_STREAM_STATE_UNINITIALIZED 0 +#define MA_AAUDIO_STREAM_STATE_UNKNOWN 1 +#define MA_AAUDIO_STREAM_STATE_OPEN 2 +#define MA_AAUDIO_STREAM_STATE_STARTING 3 +#define MA_AAUDIO_STREAM_STATE_STARTED 4 +#define MA_AAUDIO_STREAM_STATE_PAUSING 5 +#define MA_AAUDIO_STREAM_STATE_PAUSED 6 +#define MA_AAUDIO_STREAM_STATE_FLUSHING 7 +#define MA_AAUDIO_STREAM_STATE_FLUSHED 8 +#define MA_AAUDIO_STREAM_STATE_STOPPING 9 +#define MA_AAUDIO_STREAM_STATE_STOPPED 10 +#define MA_AAUDIO_STREAM_STATE_CLOSING 11 +#define MA_AAUDIO_STREAM_STATE_CLOSED 12 +#define MA_AAUDIO_STREAM_STATE_DISCONNECTED 13 - idLen = strlen(id); - baseLen = strlen(base); - if (idLen <= baseLen) { - return MA_ERROR; /* Doesn't look like the id starts with the base. */ - } +/* Performance modes. */ +#define MA_AAUDIO_PERFORMANCE_MODE_NONE 10 +#define MA_AAUDIO_PERFORMANCE_MODE_POWER_SAVING 11 +#define MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 12 - if (strncmp(id, base, baseLen) != 0) { - return MA_ERROR; /* ID does not begin with base. */ - } +/* Usage types. */ +#define MA_AAUDIO_USAGE_MEDIA 1 +#define MA_AAUDIO_USAGE_VOICE_COMMUNICATION 2 +#define MA_AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING 3 +#define MA_AAUDIO_USAGE_ALARM 4 +#define MA_AAUDIO_USAGE_NOTIFICATION 5 +#define MA_AAUDIO_USAGE_NOTIFICATION_RINGTONE 6 +#define MA_AAUDIO_USAGE_NOTIFICATION_EVENT 10 +#define MA_AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY 11 +#define MA_AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE 12 +#define MA_AAUDIO_USAGE_ASSISTANCE_SONIFICATION 13 +#define MA_AAUDIO_USAGE_GAME 14 +#define MA_AAUDIO_USAGE_ASSISTANT 16 +#define MA_AAUDIO_SYSTEM_USAGE_EMERGENCY 1000 +#define MA_AAUDIO_SYSTEM_USAGE_SAFETY 1001 +#define MA_AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS 1002 +#define MA_AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT 1003 - deviceIndexStr = id + baseLen; - if (deviceIndexStr[0] == '\0') { - return MA_ERROR; /* No index specified in the ID. */ - } +/* Content types. */ +#define MA_AAUDIO_CONTENT_TYPE_SPEECH 1 +#define MA_AAUDIO_CONTENT_TYPE_MUSIC 2 +#define MA_AAUDIO_CONTENT_TYPE_MOVIE 3 +#define MA_AAUDIO_CONTENT_TYPE_SONIFICATION 4 + +/* Input presets. */ +#define MA_AAUDIO_INPUT_PRESET_GENERIC 1 +#define MA_AAUDIO_INPUT_PRESET_CAMCORDER 5 +#define MA_AAUDIO_INPUT_PRESET_VOICE_RECOGNITION 6 +#define MA_AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION 7 +#define MA_AAUDIO_INPUT_PRESET_UNPROCESSED 9 +#define MA_AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE 10 + +/* Allowed Capture Policies */ +#define MA_AAUDIO_ALLOW_CAPTURE_BY_ALL 1 +#define MA_AAUDIO_ALLOW_CAPTURE_BY_SYSTEM 2 +#define MA_AAUDIO_ALLOW_CAPTURE_BY_NONE 3 + +/* Callback results. */ +#define MA_AAUDIO_CALLBACK_RESULT_CONTINUE 0 +#define MA_AAUDIO_CALLBACK_RESULT_STOP 1 + + +typedef ma_aaudio_data_callback_result_t (* ma_AAudioStream_dataCallback )(ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t numFrames); +typedef void (* ma_AAudioStream_errorCallback)(ma_AAudioStream *pStream, void *pUserData, ma_aaudio_result_t error); + +typedef ma_aaudio_result_t (* MA_PFN_AAudio_createStreamBuilder )(ma_AAudioStreamBuilder** ppBuilder); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStreamBuilder_delete )(ma_AAudioStreamBuilder* pBuilder); +typedef void (* MA_PFN_AAudioStreamBuilder_setDeviceId )(ma_AAudioStreamBuilder* pBuilder, int32_t deviceId); +typedef void (* MA_PFN_AAudioStreamBuilder_setDirection )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_direction_t direction); +typedef void (* MA_PFN_AAudioStreamBuilder_setSharingMode )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_sharing_mode_t sharingMode); +typedef void (* MA_PFN_AAudioStreamBuilder_setFormat )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_format_t format); +typedef void (* MA_PFN_AAudioStreamBuilder_setChannelCount )(ma_AAudioStreamBuilder* pBuilder, int32_t channelCount); +typedef void (* MA_PFN_AAudioStreamBuilder_setSampleRate )(ma_AAudioStreamBuilder* pBuilder, int32_t sampleRate); +typedef void (* MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames)(ma_AAudioStreamBuilder* pBuilder, int32_t numFrames); +typedef void (* MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback )(ma_AAudioStreamBuilder* pBuilder, int32_t numFrames); +typedef void (* MA_PFN_AAudioStreamBuilder_setDataCallback )(ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream_dataCallback callback, void* pUserData); +typedef void (* MA_PFN_AAudioStreamBuilder_setErrorCallback )(ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream_errorCallback callback, void* pUserData); +typedef void (* MA_PFN_AAudioStreamBuilder_setPerformanceMode )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_performance_mode_t mode); +typedef void (* MA_PFN_AAudioStreamBuilder_setUsage )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_usage_t contentType); +typedef void (* MA_PFN_AAudioStreamBuilder_setContentType )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_content_type_t contentType); +typedef void (* MA_PFN_AAudioStreamBuilder_setInputPreset )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_input_preset_t inputPreset); +typedef void (* MA_PFN_AAudioStreamBuilder_setAllowedCapturePolicy )(ma_AAudioStreamBuilder* pBuilder, ma_aaudio_allowed_capture_policy_t policy); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStreamBuilder_openStream )(ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream** ppStream); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_close )(ma_AAudioStream* pStream); +typedef ma_aaudio_stream_state_t (* MA_PFN_AAudioStream_getState )(ma_AAudioStream* pStream); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_waitForStateChange )(ma_AAudioStream* pStream, ma_aaudio_stream_state_t inputState, ma_aaudio_stream_state_t* pNextState, int64_t timeoutInNanoseconds); +typedef ma_aaudio_format_t (* MA_PFN_AAudioStream_getFormat )(ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getChannelCount )(ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getSampleRate )(ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getBufferCapacityInFrames )(ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getFramesPerDataCallback )(ma_AAudioStream* pStream); +typedef int32_t (* MA_PFN_AAudioStream_getFramesPerBurst )(ma_AAudioStream* pStream); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_requestStart )(ma_AAudioStream* pStream); +typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_requestStop )(ma_AAudioStream* pStream); - if (pIndexOut) { - *pIndexOut = atoi(deviceIndexStr); +static ma_result ma_result_from_aaudio(ma_aaudio_result_t resultAA) +{ + switch (resultAA) + { + case MA_AAUDIO_OK: return MA_SUCCESS; + default: break; } - return MA_SUCCESS; + return MA_ERROR; } - -#if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ -static ma_format ma_format_from_encoding__audio4(unsigned int encoding, unsigned int precision) +static ma_aaudio_usage_t ma_to_usage__aaudio(ma_aaudio_usage usage) { - if (precision == 8 && (encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR || encoding == AUDIO_ENCODING_ULINEAR_LE || encoding == AUDIO_ENCODING_ULINEAR_BE)) { - return ma_format_u8; - } else { - if (ma_is_little_endian() && encoding == AUDIO_ENCODING_SLINEAR_LE) { - if (precision == 16) { - return ma_format_s16; - } else if (precision == 24) { - return ma_format_s24; - } else if (precision == 32) { - return ma_format_s32; - } - } else if (ma_is_big_endian() && encoding == AUDIO_ENCODING_SLINEAR_BE) { - if (precision == 16) { - return ma_format_s16; - } else if (precision == 24) { - return ma_format_s24; - } else if (precision == 32) { - return ma_format_s32; - } - } + switch (usage) { + case ma_aaudio_usage_media: return MA_AAUDIO_USAGE_MEDIA; + case ma_aaudio_usage_voice_communication: return MA_AAUDIO_USAGE_VOICE_COMMUNICATION; + case ma_aaudio_usage_voice_communication_signalling: return MA_AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING; + case ma_aaudio_usage_alarm: return MA_AAUDIO_USAGE_ALARM; + case ma_aaudio_usage_notification: return MA_AAUDIO_USAGE_NOTIFICATION; + case ma_aaudio_usage_notification_ringtone: return MA_AAUDIO_USAGE_NOTIFICATION_RINGTONE; + case ma_aaudio_usage_notification_event: return MA_AAUDIO_USAGE_NOTIFICATION_EVENT; + case ma_aaudio_usage_assistance_accessibility: return MA_AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY; + case ma_aaudio_usage_assistance_navigation_guidance: return MA_AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; + case ma_aaudio_usage_assistance_sonification: return MA_AAUDIO_USAGE_ASSISTANCE_SONIFICATION; + case ma_aaudio_usage_game: return MA_AAUDIO_USAGE_GAME; + case ma_aaudio_usage_assitant: return MA_AAUDIO_USAGE_ASSISTANT; + case ma_aaudio_usage_emergency: return MA_AAUDIO_SYSTEM_USAGE_EMERGENCY; + case ma_aaudio_usage_safety: return MA_AAUDIO_SYSTEM_USAGE_SAFETY; + case ma_aaudio_usage_vehicle_status: return MA_AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS; + case ma_aaudio_usage_announcement: return MA_AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT; + default: break; } - return ma_format_unknown; /* Encoding not supported. */ + return MA_AAUDIO_USAGE_MEDIA; } -static void ma_encoding_from_format__audio4(ma_format format, unsigned int* pEncoding, unsigned int* pPrecision) +static ma_aaudio_content_type_t ma_to_content_type__aaudio(ma_aaudio_content_type contentType) { - MA_ASSERT(pEncoding != NULL); - MA_ASSERT(pPrecision != NULL); + switch (contentType) { + case ma_aaudio_content_type_speech: return MA_AAUDIO_CONTENT_TYPE_SPEECH; + case ma_aaudio_content_type_music: return MA_AAUDIO_CONTENT_TYPE_MUSIC; + case ma_aaudio_content_type_movie: return MA_AAUDIO_CONTENT_TYPE_MOVIE; + case ma_aaudio_content_type_sonification: return MA_AAUDIO_CONTENT_TYPE_SONIFICATION; + default: break; + } - switch (format) - { - case ma_format_u8: - { - *pEncoding = AUDIO_ENCODING_ULINEAR; - *pPrecision = 8; - } break; + return MA_AAUDIO_CONTENT_TYPE_SPEECH; +} - case ma_format_s24: - { - *pEncoding = (ma_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; - *pPrecision = 24; - } break; +static ma_aaudio_input_preset_t ma_to_input_preset__aaudio(ma_aaudio_input_preset inputPreset) +{ + switch (inputPreset) { + case ma_aaudio_input_preset_generic: return MA_AAUDIO_INPUT_PRESET_GENERIC; + case ma_aaudio_input_preset_camcorder: return MA_AAUDIO_INPUT_PRESET_CAMCORDER; + case ma_aaudio_input_preset_voice_recognition: return MA_AAUDIO_INPUT_PRESET_VOICE_RECOGNITION; + case ma_aaudio_input_preset_voice_communication: return MA_AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION; + case ma_aaudio_input_preset_unprocessed: return MA_AAUDIO_INPUT_PRESET_UNPROCESSED; + case ma_aaudio_input_preset_voice_performance: return MA_AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE; + default: break; + } - case ma_format_s32: - { - *pEncoding = (ma_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; - *pPrecision = 32; - } break; + return MA_AAUDIO_INPUT_PRESET_GENERIC; +} - case ma_format_s16: - case ma_format_f32: - case ma_format_unknown: - default: - { - *pEncoding = (ma_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; - *pPrecision = 16; - } break; +static ma_aaudio_allowed_capture_policy_t ma_to_allowed_capture_policy__aaudio(ma_aaudio_allowed_capture_policy allowedCapturePolicy) +{ + switch (allowedCapturePolicy) { + case ma_aaudio_allow_capture_by_all: return MA_AAUDIO_ALLOW_CAPTURE_BY_ALL; + case ma_aaudio_allow_capture_by_system: return MA_AAUDIO_ALLOW_CAPTURE_BY_SYSTEM; + case ma_aaudio_allow_capture_by_none: return MA_AAUDIO_ALLOW_CAPTURE_BY_NONE; + default: break; } + + return MA_AAUDIO_ALLOW_CAPTURE_BY_ALL; } -static ma_format ma_format_from_prinfo__audio4(struct audio_prinfo* prinfo) +static ma_aaudio_performance_mode_t ma_to_performance_mode__aaudio(ma_aaudio_performance_mode performanceMode) { - return ma_format_from_encoding__audio4(prinfo->encoding, prinfo->precision); + switch (performanceMode) { + case ma_aaudio_performance_mode_none: return MA_AAUDIO_PERFORMANCE_MODE_NONE; + case ma_aaudio_performance_mode_power_saving: return MA_AAUDIO_PERFORMANCE_MODE_POWER_SAVING; + case ma_aaudio_performance_mode_low_latency: return MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY; + default: break; + } + + return MA_AAUDIO_PERFORMANCE_MODE_NONE; } -static ma_format ma_best_format_from_fd__audio4(int fd, ma_format preferredFormat) + +typedef struct ma_context_state_aaudio { - audio_encoding_t encoding; - ma_uint32 iFormat; - int counter = 0; + ma_handle hAAudio; /* libaaudio.so */ + MA_PFN_AAudio_createStreamBuilder AAudio_createStreamBuilder; + MA_PFN_AAudioStreamBuilder_delete AAudioStreamBuilder_delete; + MA_PFN_AAudioStreamBuilder_setDeviceId AAudioStreamBuilder_setDeviceId; + MA_PFN_AAudioStreamBuilder_setDirection AAudioStreamBuilder_setDirection; + MA_PFN_AAudioStreamBuilder_setSharingMode AAudioStreamBuilder_setSharingMode; + MA_PFN_AAudioStreamBuilder_setFormat AAudioStreamBuilder_setFormat; + MA_PFN_AAudioStreamBuilder_setChannelCount AAudioStreamBuilder_setChannelCount; + MA_PFN_AAudioStreamBuilder_setSampleRate AAudioStreamBuilder_setSampleRate; + MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames AAudioStreamBuilder_setBufferCapacityInFrames; + MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback AAudioStreamBuilder_setFramesPerDataCallback; + MA_PFN_AAudioStreamBuilder_setDataCallback AAudioStreamBuilder_setDataCallback; + MA_PFN_AAudioStreamBuilder_setErrorCallback AAudioStreamBuilder_setErrorCallback; + MA_PFN_AAudioStreamBuilder_setPerformanceMode AAudioStreamBuilder_setPerformanceMode; + MA_PFN_AAudioStreamBuilder_setUsage AAudioStreamBuilder_setUsage; + MA_PFN_AAudioStreamBuilder_setContentType AAudioStreamBuilder_setContentType; + MA_PFN_AAudioStreamBuilder_setInputPreset AAudioStreamBuilder_setInputPreset; + MA_PFN_AAudioStreamBuilder_setAllowedCapturePolicy AAudioStreamBuilder_setAllowedCapturePolicy; + MA_PFN_AAudioStreamBuilder_openStream AAudioStreamBuilder_openStream; + MA_PFN_AAudioStream_close AAudioStream_close; + MA_PFN_AAudioStream_getState AAudioStream_getState; + MA_PFN_AAudioStream_waitForStateChange AAudioStream_waitForStateChange; + MA_PFN_AAudioStream_getFormat AAudioStream_getFormat; + MA_PFN_AAudioStream_getChannelCount AAudioStream_getChannelCount; + MA_PFN_AAudioStream_getSampleRate AAudioStream_getSampleRate; + MA_PFN_AAudioStream_getBufferCapacityInFrames AAudioStream_getBufferCapacityInFrames; + MA_PFN_AAudioStream_getFramesPerDataCallback AAudioStream_getFramesPerDataCallback; + MA_PFN_AAudioStream_getFramesPerBurst AAudioStream_getFramesPerBurst; + MA_PFN_AAudioStream_requestStart AAudioStream_requestStart; + MA_PFN_AAudioStream_requestStop AAudioStream_requestStop; +} ma_context_state_aaudio; - /* First check to see if the preferred format is supported. */ - if (preferredFormat != ma_format_unknown) { - counter = 0; - for (;;) { - MA_ZERO_OBJECT(&encoding); - encoding.index = counter; - if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { - break; - } +typedef struct ma_device_state_aaudio +{ + ma_device_state_async async; + ma_AAudioStream* pStreamPlayback; + ma_AAudioStream* pStreamCapture; + ma_atomic_bool32 isTearingDown; + ma_atomic_bool32 isReroutingPlayback; + ma_atomic_bool32 isReroutingCapture; + ma_aaudio_usage usage; + ma_aaudio_content_type contentType; + ma_aaudio_input_preset inputPreset; + ma_aaudio_allowed_capture_policy allowedCapturePolicy; + ma_bool32 noAutoStartAfterReroute; +} ma_device_state_aaudio; - if (preferredFormat == ma_format_from_encoding__audio4(encoding.encoding, encoding.precision)) { - return preferredFormat; /* Found the preferred format. */ - } - /* Getting here means this encoding does not match our preferred format so we need to more on to the next encoding. */ - counter += 1; - } - } +static ma_context_state_aaudio* ma_context_get_backend_state__aaudio(ma_context* pContext) +{ + return (ma_context_state_aaudio*)ma_context_get_backend_state(pContext); +} - /* Getting here means our preferred format is not supported, so fall back to our standard priorities. */ - for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) { - ma_format format = g_maFormatPriorities[iFormat]; +static ma_device_state_aaudio* ma_device_get_backend_state__aaudio(ma_device* pDevice) +{ + return (ma_device_state_aaudio*)ma_device_get_backend_state(pDevice); +} - counter = 0; - for (;;) { - MA_ZERO_OBJECT(&encoding); - encoding.index = counter; - if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { - break; - } - if (format == ma_format_from_encoding__audio4(encoding.encoding, encoding.precision)) { - return format; /* Found a workable format. */ - } +static ma_result ma_device_start__aaudio(ma_device* pDevice); +static ma_result ma_device_step__aaudio(ma_device* pDevice, ma_blocking_mode blockingMode); - /* Getting here means this encoding does not match our preferred format so we need to more on to the next encoding. */ - counter += 1; - } - } - /* Getting here means not appropriate format was found. */ - return ma_format_unknown; -} -#else -static ma_format ma_format_from_swpar__audio4(struct audio_swpar* par) +static void ma_backend_info__aaudio(ma_device_backend_info* pBackendInfo) { - if (par->bits == 8 && par->bps == 1 && par->sig == 0) { - return ma_format_u8; - } - if (par->bits == 16 && par->bps == 2 && par->sig == 1 && par->le == ma_is_little_endian()) { - return ma_format_s16; - } - if (par->bits == 24 && par->bps == 3 && par->sig == 1 && par->le == ma_is_little_endian()) { - return ma_format_s24; - } - if (par->bits == 32 && par->bps == 4 && par->sig == 1 && par->le == ma_is_little_endian()) { - return ma_format_f32; - } - - /* Format not supported. */ - return ma_format_unknown; + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "AAudio"; } -#endif -static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext, ma_device_type deviceType, int fd, ma_device_info* pDeviceInfo) +static ma_result ma_context_init__aaudio(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) { - audio_device_t fdDevice; + ma_context_state_aaudio* pContextStateAAudio; + const ma_context_config_aaudio* pContextConfigAAudio = (const ma_context_config_aaudio*)pContextBackendConfig; + ma_context_config_aaudio defaultConfigAAudio; - MA_ASSERT(pContext != NULL); - MA_ASSERT(fd >= 0); - MA_ASSERT(pDeviceInfo != NULL); + if (pContextConfigAAudio == NULL) { + defaultConfigAAudio = ma_context_config_aaudio_init(); + pContextConfigAAudio = &defaultConfigAAudio; + } - (void)pContext; - (void)deviceType; + (void)pContextConfigAAudio; - if (ioctl(fd, AUDIO_GETDEV, &fdDevice) < 0) { - return MA_ERROR; /* Failed to retrieve device info. */ + if (ma_android_sdk_version() < MA_AAUDIO_MIN_ANDROID_SDK_VERSION) { + return MA_FAILED_TO_INIT_BACKEND; } - /* Name. */ - ma_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), fdDevice.name); + pContextStateAAudio = (ma_context_state_aaudio*)ma_calloc(sizeof(*pContextStateAAudio), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateAAudio == NULL) { + return MA_OUT_OF_MEMORY; + } - #if !defined(MA_AUDIO4_USE_NEW_API) + #if !defined(MA_NO_RUNTIME_LINKING) { - audio_info_t fdInfo; - int counter = 0; - ma_uint32 channels; - ma_uint32 sampleRate; - -#if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) - if (ioctl(fd, AUDIO_GETFORMAT, &fdInfo) < 0) { - return MA_ERROR; - } -#else - if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { - return MA_ERROR; - } -#endif - - if (deviceType == ma_device_type_playback) { - channels = fdInfo.play.channels; - sampleRate = fdInfo.play.sample_rate; - } else { - channels = fdInfo.record.channels; - sampleRate = fdInfo.record.sample_rate; - } - - /* Supported formats. We get this by looking at the encodings. */ - pDeviceInfo->nativeDataFormatCount = 0; - for (;;) { - audio_encoding_t encoding; - ma_format format; + size_t i; + const char* libNames[] = { + "libaaudio.so" + }; - MA_ZERO_OBJECT(&encoding); - encoding.index = counter; - if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + for (i = 0; i < ma_countof(libNames); ++i) { + pContextStateAAudio->hAAudio = ma_dlopen(ma_context_get_log(pContext), libNames[i]); + if (pContextStateAAudio->hAAudio != NULL) { break; } + } - format = ma_format_from_encoding__audio4(encoding.encoding, encoding.precision); - if (format != ma_format_unknown) { - ma_device_info_add_native_data_format(pDeviceInfo, format, channels, sampleRate, 0); - } - - counter += 1; + if (pContextStateAAudio->hAAudio == NULL) { + ma_free(pContextStateAAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_FAILED_TO_INIT_BACKEND; } + + pContextStateAAudio->AAudio_createStreamBuilder = (MA_PFN_AAudio_createStreamBuilder )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudio_createStreamBuilder"); + pContextStateAAudio->AAudioStreamBuilder_delete = (MA_PFN_AAudioStreamBuilder_delete )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_delete"); + pContextStateAAudio->AAudioStreamBuilder_setDeviceId = (MA_PFN_AAudioStreamBuilder_setDeviceId )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setDeviceId"); + pContextStateAAudio->AAudioStreamBuilder_setDirection = (MA_PFN_AAudioStreamBuilder_setDirection )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setDirection"); + pContextStateAAudio->AAudioStreamBuilder_setSharingMode = (MA_PFN_AAudioStreamBuilder_setSharingMode )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setSharingMode"); + pContextStateAAudio->AAudioStreamBuilder_setFormat = (MA_PFN_AAudioStreamBuilder_setFormat )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setFormat"); + pContextStateAAudio->AAudioStreamBuilder_setChannelCount = (MA_PFN_AAudioStreamBuilder_setChannelCount )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setChannelCount"); + pContextStateAAudio->AAudioStreamBuilder_setSampleRate = (MA_PFN_AAudioStreamBuilder_setSampleRate )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setSampleRate"); + pContextStateAAudio->AAudioStreamBuilder_setBufferCapacityInFrames = (MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames)ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setBufferCapacityInFrames"); + pContextStateAAudio->AAudioStreamBuilder_setFramesPerDataCallback = (MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setFramesPerDataCallback"); + pContextStateAAudio->AAudioStreamBuilder_setDataCallback = (MA_PFN_AAudioStreamBuilder_setDataCallback )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setDataCallback"); + pContextStateAAudio->AAudioStreamBuilder_setErrorCallback = (MA_PFN_AAudioStreamBuilder_setErrorCallback )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setErrorCallback"); + pContextStateAAudio->AAudioStreamBuilder_setPerformanceMode = (MA_PFN_AAudioStreamBuilder_setPerformanceMode )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setPerformanceMode"); + pContextStateAAudio->AAudioStreamBuilder_setUsage = (MA_PFN_AAudioStreamBuilder_setUsage )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setUsage"); + pContextStateAAudio->AAudioStreamBuilder_setContentType = (MA_PFN_AAudioStreamBuilder_setContentType )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setContentType"); + pContextStateAAudio->AAudioStreamBuilder_setInputPreset = (MA_PFN_AAudioStreamBuilder_setInputPreset )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setInputPreset"); + pContextStateAAudio->AAudioStreamBuilder_setAllowedCapturePolicy = (MA_PFN_AAudioStreamBuilder_setAllowedCapturePolicy )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_setAllowedCapturePolicy"); + pContextStateAAudio->AAudioStreamBuilder_openStream = (MA_PFN_AAudioStreamBuilder_openStream )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStreamBuilder_openStream"); + pContextStateAAudio->AAudioStream_close = (MA_PFN_AAudioStream_close )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_close"); + pContextStateAAudio->AAudioStream_getState = (MA_PFN_AAudioStream_getState )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_getState"); + pContextStateAAudio->AAudioStream_waitForStateChange = (MA_PFN_AAudioStream_waitForStateChange )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_waitForStateChange"); + pContextStateAAudio->AAudioStream_getFormat = (MA_PFN_AAudioStream_getFormat )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_getFormat"); + pContextStateAAudio->AAudioStream_getChannelCount = (MA_PFN_AAudioStream_getChannelCount )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_getChannelCount"); + pContextStateAAudio->AAudioStream_getSampleRate = (MA_PFN_AAudioStream_getSampleRate )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_getSampleRate"); + pContextStateAAudio->AAudioStream_getBufferCapacityInFrames = (MA_PFN_AAudioStream_getBufferCapacityInFrames )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_getBufferCapacityInFrames"); + pContextStateAAudio->AAudioStream_getFramesPerDataCallback = (MA_PFN_AAudioStream_getFramesPerDataCallback )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_getFramesPerDataCallback"); + pContextStateAAudio->AAudioStream_getFramesPerBurst = (MA_PFN_AAudioStream_getFramesPerBurst )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_getFramesPerBurst"); + pContextStateAAudio->AAudioStream_requestStart = (MA_PFN_AAudioStream_requestStart )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_requestStart"); + pContextStateAAudio->AAudioStream_requestStop = (MA_PFN_AAudioStream_requestStop )ma_dlsym(ma_context_get_log(pContext), pContextStateAAudio->hAAudio, "AAudioStream_requestStop"); } #else { - struct audio_swpar fdPar; - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - - if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { - return MA_ERROR; - } + pContextStateAAudio->AAudio_createStreamBuilder = AAudio_createStreamBuilder; + pContextStateAAudio->AAudioStreamBuilder_delete = AAudioStreamBuilder_delete; + pContextStateAAudio->AAudioStreamBuilder_setDeviceId = AAudioStreamBuilder_setDeviceId; + pContextStateAAudio->AAudioStreamBuilder_setDirection = AAudioStreamBuilder_setDirection; + pContextStateAAudio->AAudioStreamBuilder_setSharingMode = AAudioStreamBuilder_setSharingMode; + pContextStateAAudio->AAudioStreamBuilder_setFormat = AAudioStreamBuilder_setFormat; + pContextStateAAudio->AAudioStreamBuilder_setChannelCount = AAudioStreamBuilder_setChannelCount; + pContextStateAAudio->AAudioStreamBuilder_setSampleRate = AAudioStreamBuilder_setSampleRate; + pContextStateAAudio->AAudioStreamBuilder_setBufferCapacityInFrames = AAudioStreamBuilder_setBufferCapacityInFrames; + pContextStateAAudio->AAudioStreamBuilder_setFramesPerDataCallback = AAudioStreamBuilder_setFramesPerDataCallback; + pContextStateAAudio->AAudioStreamBuilder_setDataCallback = AAudioStreamBuilder_setDataCallback; + pContextStateAAudio->AAudioStreamBuilder_setErrorCallback = AAudioStreamBuilder_setErrorCallback; + pContextStateAAudio->AAudioStreamBuilder_setPerformanceMode = AAudioStreamBuilder_setPerformanceMode; + pContextStateAAudio->AAudioStreamBuilder_setUsage = AAudioStreamBuilder_setUsage; + pContextStateAAudio->AAudioStreamBuilder_setContentType = AAudioStreamBuilder_setContentType; + pContextStateAAudio->AAudioStreamBuilder_setInputPreset = AAudioStreamBuilder_setInputPreset; + #if defined(__ANDROID_API__) && __ANDROID_API__ >= 29 + pContextStateAAudio->AAudioStreamBuilder_setAllowedCapturePolicy = AAudioStreamBuilder_setAllowedCapturePolicy; + #endif + pContextStateAAudio->AAudioStreamBuilder_openStream = AAudioStreamBuilder_openStream; + pContextStateAAudio->AAudioStream_close = AAudioStream_close; + pContextStateAAudio->AAudioStream_getState = AAudioStream_getState; + pContextStateAAudio->AAudioStream_waitForStateChange = AAudioStream_waitForStateChange; + pContextStateAAudio->AAudioStream_getFormat = AAudioStream_getFormat; + pContextStateAAudio->AAudioStream_getChannelCount = AAudioStream_getChannelCount; + pContextStateAAudio->AAudioStream_getSampleRate = AAudioStream_getSampleRate; + pContextStateAAudio->AAudioStream_getBufferCapacityInFrames = AAudioStream_getBufferCapacityInFrames; + pContextStateAAudio->AAudioStream_getFramesPerDataCallback = AAudioStream_getFramesPerDataCallback; + pContextStateAAudio->AAudioStream_getFramesPerBurst = AAudioStream_getFramesPerBurst; + pContextStateAAudio->AAudioStream_requestStart = AAudioStream_requestStart; + pContextStateAAudio->AAudioStream_requestStop = AAudioStream_requestStop; + } + #endif - format = ma_format_from_swpar__audio4(&fdPar); - if (format == ma_format_unknown) { - return MA_FORMAT_NOT_SUPPORTED; - } + *ppContextState = pContextStateAAudio; - if (deviceType == ma_device_type_playback) { - channels = fdPar.pchan; - } else { - channels = fdPar.rchan; - } + return MA_SUCCESS; +} - sampleRate = fdPar.rate; +static void ma_context_uninit__aaudio(ma_context* pContext) +{ + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(pContext); - pDeviceInfo->nativeDataFormatCount = 0; - ma_device_info_add_native_data_format(pDeviceInfo, format, channels, sampleRate, 0); + #if !defined(MA_NO_RUNTIME_LINKING) + { + ma_dlclose(ma_context_get_log(pContext), pContextStateAAudio->hAAudio); } #endif - return MA_SUCCESS; + ma_free(pContextStateAAudio, ma_context_get_allocation_callbacks(pContext)); } -static ma_result ma_context_enumerate_devices__audio4(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUserData, ma_aaudio_result_t error) { - const int maxDevices = 64; - char devpath[256]; - int iDevice; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); - + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(ma_device_get_context(pDevice)); + + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d", error, pContextStateAAudio->AAudioStream_getState(pStream)); + /* - Every device will be named "/dev/audioN", with a "/dev/audioctlN" equivalent. We use the "/dev/audioctlN" - version here since we can open it even when another process has control of the "/dev/audioN" device. + When we get an error, we'll assume that the stream is in an erroneous state and needs to be restarted. From the documentation, + we cannot do this from the error callback, so instead we'll just set a flag and handle it from our step function. */ - for (iDevice = 0; iDevice < maxDevices; ++iDevice) { - struct stat st; - int fd; - ma_bool32 isTerminating = MA_FALSE; - - ma_strcpy_s(devpath, sizeof(devpath), "/dev/audioctl"); - ma_itoa_s(iDevice, devpath+strlen(devpath), sizeof(devpath)-strlen(devpath), 10); - - if (stat(devpath, &st) < 0) { - break; + if (ma_atomic_bool32_get(&pDeviceStateAAudio->isTearingDown)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Tearing down device."); + } else { + if (pStream == pDeviceStateAAudio->pStreamCapture) { + ma_atomic_bool32_set(&pDeviceStateAAudio->isReroutingCapture, MA_TRUE); + } else { + ma_atomic_bool32_set(&pDeviceStateAAudio->isReroutingPlayback, MA_TRUE); } - /* The device exists, but we need to check if it's usable as playback and/or capture. */ - - /* Playback. */ - if (!isTerminating) { - fd = open(devpath, O_RDONLY, 0); - if (fd >= 0) { - /* Supports playback. */ - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_construct_device_id__audio4(deviceInfo.id.audio4, sizeof(deviceInfo.id.audio4), "/dev/audio", iDevice); - if (ma_context_get_device_info_from_fd__audio4(pContext, ma_device_type_playback, fd, &deviceInfo) == MA_SUCCESS) { - isTerminating = !callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - } - - close(fd); - } - } + /* Make sure we wake up the semaphore so we can handle the rerouting in the step function. Not doing this might result in us getting stuck waiting. */ + ma_device_state_async_release(&pDeviceStateAAudio->async); + } +} - /* Capture. */ - if (!isTerminating) { - fd = open(devpath, O_WRONLY, 0); - if (fd >= 0) { - /* Supports capture. */ - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_construct_device_id__audio4(deviceInfo.id.audio4, sizeof(deviceInfo.id.audio4), "/dev/audio", iDevice); - if (ma_context_get_device_info_from_fd__audio4(pContext, ma_device_type_capture, fd, &deviceInfo) == MA_SUCCESS) { - isTerminating = !callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); - } +static ma_aaudio_data_callback_result_t ma_stream_data_callback_capture__aaudio(ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t frameCount) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); - close(fd); - } - } + MA_ASSERT(pDevice != NULL); - if (isTerminating) { - break; - } + if (frameCount > 0) { + ma_device_state_async_process(&pDeviceStateAAudio->async, pDevice, NULL, pAudioData, (ma_uint32)frameCount); } - return MA_SUCCESS; + (void)pStream; + return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; } -static ma_result ma_context_get_device_info__audio4(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +static ma_aaudio_data_callback_result_t ma_stream_data_callback_playback__aaudio(ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t frameCount) { - int fd = -1; - int deviceIndex = -1; - char ctlid[256]; - ma_result result; + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); - MA_ASSERT(pContext != NULL); + MA_ASSERT(pDevice != NULL); /* - We need to open the "/dev/audioctlN" device to get the info. To do this we need to extract the number - from the device ID which will be in "/dev/audioN" format. + I've had a report that AAudio can sometimes post a frame count of 0. We need to check for that here + so we don't get any errors at a deeper level. I'm doing the same with the capture side for safety, + though I've not yet had any reports about that one. */ - if (pDeviceID == NULL) { - /* Default device. */ - ma_strcpy_s(ctlid, sizeof(ctlid), "/dev/audioctl"); - } else { - /* Specific device. We need to convert from "/dev/audioN" to "/dev/audioctlN". */ - result = ma_extract_device_index_from_id__audio4(pDeviceID->audio4, "/dev/audio", &deviceIndex); - if (result != MA_SUCCESS) { - return result; - } - - ma_construct_device_id__audio4(ctlid, sizeof(ctlid), "/dev/audioctl", deviceIndex); - } - - fd = open(ctlid, (deviceType == ma_device_type_playback) ? O_WRONLY : O_RDONLY, 0); - if (fd == -1) { - return MA_NO_DEVICE; - } - - if (deviceIndex == -1) { - ma_strcpy_s(pDeviceInfo->id.audio4, sizeof(pDeviceInfo->id.audio4), "/dev/audio"); - } else { - ma_construct_device_id__audio4(pDeviceInfo->id.audio4, sizeof(pDeviceInfo->id.audio4), "/dev/audio", deviceIndex); + if (frameCount > 0) { + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "AAudio Playback Callback: frameCount=%d, async.frameCap=%d\n", frameCount, pDeviceStateAAudio->async.playback.frameCap);*/ + ma_device_state_async_process(&pDeviceStateAAudio->async, pDevice, pAudioData, NULL, (ma_uint32)frameCount); } - result = ma_context_get_device_info_from_fd__audio4(pContext, deviceType, fd, pDeviceInfo); - - close(fd); - return result; + (void)pStream; + return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; } -static ma_result ma_device_uninit__audio4(ma_device* pDevice) +static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* pContext, const ma_device_config_aaudio* pDeviceConfigAAudio, const ma_device_id* pDeviceID, ma_device_type deviceType, ma_share_mode shareMode, const ma_device_descriptor* pDescriptor, ma_device* pDevice, ma_AAudioStreamBuilder** ppBuilder) { - MA_ASSERT(pDevice != NULL); + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(pContext); + ma_AAudioStreamBuilder* pBuilder; + ma_aaudio_result_t resultAA; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - close(pDevice->audio4.fdCapture); - } + /* Safety. */ + *ppBuilder = NULL; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - close(pDevice->audio4.fdPlayback); + resultAA = pContextStateAAudio->AAudio_createStreamBuilder(&pBuilder); + if (resultAA != MA_AAUDIO_OK) { + return ma_result_from_aaudio(resultAA); } - return MA_SUCCESS; -} + if (pDeviceID != NULL) { + pContextStateAAudio->AAudioStreamBuilder_setDeviceId(pBuilder, pDeviceID->aaudio); + } -static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) -{ - const char* pDefaultDeviceNames[] = { - "/dev/audio", - "/dev/audio0" - }; - const char* pDefaultDeviceCtlNames[] = { - "/dev/audioctl", - "/dev/audioctl0" - }; - int fd; - int fdFlags = 0; - size_t iDefaultDevice = (size_t)-1; - ma_format internalFormat; - ma_uint32 internalChannels; - ma_uint32 internalSampleRate; - ma_uint32 internalPeriodSizeInFrames; - ma_uint32 internalPeriods; + pContextStateAAudio->AAudioStreamBuilder_setDirection(pBuilder, (deviceType == ma_device_type_playback) ? MA_AAUDIO_DIRECTION_OUTPUT : MA_AAUDIO_DIRECTION_INPUT); + pContextStateAAudio->AAudioStreamBuilder_setSharingMode(pBuilder, (shareMode == ma_share_mode_shared) ? MA_AAUDIO_SHARING_MODE_SHARED : MA_AAUDIO_SHARING_MODE_EXCLUSIVE); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(deviceType != ma_device_type_duplex); - MA_ASSERT(pDevice != NULL); - /* The first thing to do is open the file. */ - if (deviceType == ma_device_type_capture) { - fdFlags = O_RDONLY; - } else { - fdFlags = O_WRONLY; - } - /*fdFlags |= O_NONBLOCK;*/ + /* If we have a device descriptor make sure we configure the stream builder to take our requested parameters. */ + if (pDescriptor != NULL) { + MA_ASSERT(pDeviceConfigAAudio != NULL); - /* Find the index of the default device as a start. We'll use this index later. Set it to (size_t)-1 otherwise. */ - if (pDescriptor->pDeviceID == NULL) { - /* Default device. */ - for (iDefaultDevice = 0; iDefaultDevice < ma_countof(pDefaultDeviceNames); ++iDefaultDevice) { - fd = open(pDefaultDeviceNames[iDefaultDevice], fdFlags, 0); - if (fd != -1) { - break; - } + if (pDescriptor->sampleRate != 0) { + pContextStateAAudio->AAudioStreamBuilder_setSampleRate(pBuilder, pDescriptor->sampleRate); } - } else { - /* Specific device. */ - fd = open(pDescriptor->pDeviceID->audio4, fdFlags, 0); - for (iDefaultDevice = 0; iDefaultDevice < ma_countof(pDefaultDeviceNames); iDefaultDevice += 1) { - if (ma_strcmp(pDefaultDeviceNames[iDefaultDevice], pDescriptor->pDeviceID->audio4) == 0) { - break; - } + if (pDescriptor->channels != 0) { + pContextStateAAudio->AAudioStreamBuilder_setChannelCount(pBuilder, pDescriptor->channels); } - if (iDefaultDevice == ma_countof(pDefaultDeviceNames)) { - iDefaultDevice = (size_t)-1; + if (pDescriptor->format != ma_format_unknown) { + pContextStateAAudio->AAudioStreamBuilder_setFormat(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); } - } - if (fd == -1) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device."); - return ma_result_from_errno(errno); - } - - #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ - { - audio_info_t fdInfo; - int fdInfoResult = -1; /* - The documentation is a little bit unclear to me as to how it handles formats. It says the - following: - - Regardless of formats supported by underlying driver, the audio driver accepts the - following formats. - - By then the next sentence says this: - - `encoding` and `precision` are one of the values obtained by AUDIO_GETENC. - - It sounds like a direct contradiction to me. I'm going to play this safe any only use the - best sample format returned by AUDIO_GETENC. If the requested format is supported we'll - use that, but otherwise we'll just use our standard format priorities to pick an - appropriate one. + There have been reports where setting the frames per data callback results in an error. + In particular, re-routing may inadvertently switch from low-latency mode, resulting in a less stable + stream from the legacy path (AudioStreamLegacy). To address this, we simply don't set the value. It + can still be set if it's explicitly requested via the aaudio.allowSetBufferCapacity variable in the + device config. */ - AUDIO_INITINFO(&fdInfo); + if (pDeviceConfigAAudio->allowSetBufferCapacity) { + /* + AAudio is annoying when it comes to its buffer calculation stuff because it doesn't let you + retrieve the actual sample rate until after you've opened the stream. But you need to configure + the buffer capacity before you open the stream... :/ - /* - Get the default format from the audioctl file if we're asking for a default device. If we - retrieve it from /dev/audio it'll default to mono 8000Hz. - */ - if (iDefaultDevice != (size_t)-1) { - /* We're using a default device. Get the info from the /dev/audioctl file instead of /dev/audio. */ - int fdctl = open(pDefaultDeviceCtlNames[iDefaultDevice], fdFlags, 0); - if (fdctl != -1) { -#if defined(__NetBSD__) && (__NetBSD_Version__ >= 900000000) - fdInfoResult = ioctl(fdctl, AUDIO_GETFORMAT, &fdInfo); -#else - fdInfoResult = ioctl(fdctl, AUDIO_GETINFO, &fdInfo); -#endif - close(fdctl); - } - } + To solve, we're just going to assume MA_DEFAULT_SAMPLE_RATE (48000) and move on. + */ + ma_uint32 bufferCapacityInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate) * pDescriptor->periodCount; - if (fdInfoResult == -1) { - /* We still don't have the default device info so just retrieve it from the main audio device. */ - if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed."); - return ma_result_from_errno(errno); - } + pContextStateAAudio->AAudioStreamBuilder_setBufferCapacityInFrames(pBuilder, bufferCapacityInFrames); + pContextStateAAudio->AAudioStreamBuilder_setFramesPerDataCallback(pBuilder, bufferCapacityInFrames / pDescriptor->periodCount); } - /* We get the driver to do as much of the data conversion as possible. */ if (deviceType == ma_device_type_capture) { - fdInfo.mode = AUMODE_RECORD; - ma_encoding_from_format__audio4(ma_best_format_from_fd__audio4(fd, pDescriptor->format), &fdInfo.record.encoding, &fdInfo.record.precision); - - if (pDescriptor->channels != 0) { - fdInfo.record.channels = ma_clamp(pDescriptor->channels, 1, 12); /* From the documentation: `channels` ranges from 1 to 12. */ - } - - if (pDescriptor->sampleRate != 0) { - fdInfo.record.sample_rate = ma_clamp(pDescriptor->sampleRate, 1000, 192000); /* From the documentation: `frequency` ranges from 1000Hz to 192000Hz. (They mean `sample_rate` instead of `frequency`.) */ - } - } else { - fdInfo.mode = AUMODE_PLAY; - ma_encoding_from_format__audio4(ma_best_format_from_fd__audio4(fd, pDescriptor->format), &fdInfo.play.encoding, &fdInfo.play.precision); - - if (pDescriptor->channels != 0) { - fdInfo.play.channels = ma_clamp(pDescriptor->channels, 1, 12); /* From the documentation: `channels` ranges from 1 to 12. */ - } - - if (pDescriptor->sampleRate != 0) { - fdInfo.play.sample_rate = ma_clamp(pDescriptor->sampleRate, 1000, 192000); /* From the documentation: `frequency` ranges from 1000Hz to 192000Hz. (They mean `sample_rate` instead of `frequency`.) */ + if (pDeviceConfigAAudio->inputPreset != ma_aaudio_input_preset_default && pContextStateAAudio->AAudioStreamBuilder_setInputPreset != NULL) { + pContextStateAAudio->AAudioStreamBuilder_setInputPreset(pBuilder, ma_to_input_preset__aaudio(pDeviceConfigAAudio->inputPreset)); } - } - - if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed."); - return ma_result_from_errno(errno); - } - if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed."); - return ma_result_from_errno(errno); - } - - if (deviceType == ma_device_type_capture) { - internalFormat = ma_format_from_prinfo__audio4(&fdInfo.record); - internalChannels = fdInfo.record.channels; - internalSampleRate = fdInfo.record.sample_rate; + pContextStateAAudio->AAudioStreamBuilder_setDataCallback(pBuilder, ma_stream_data_callback_capture__aaudio, (void*)pDevice); } else { - internalFormat = ma_format_from_prinfo__audio4(&fdInfo.play); - internalChannels = fdInfo.play.channels; - internalSampleRate = fdInfo.play.sample_rate; - } - - if (internalFormat == ma_format_unknown) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); - return MA_FORMAT_NOT_SUPPORTED; - } - - /* Buffer. */ - { - ma_uint32 internalPeriodSizeInBytes; - - internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate, pConfig->performanceProfile); - - internalPeriodSizeInBytes = internalPeriodSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); - if (internalPeriodSizeInBytes < 16) { - internalPeriodSizeInBytes = 16; + if (pDeviceConfigAAudio->usage != ma_aaudio_usage_default && pContextStateAAudio->AAudioStreamBuilder_setUsage != NULL) { + pContextStateAAudio->AAudioStreamBuilder_setUsage(pBuilder, ma_to_usage__aaudio(pDeviceConfigAAudio->usage)); } - internalPeriods = pDescriptor->periodCount; - if (internalPeriods < 2) { - internalPeriods = 2; + if (pDeviceConfigAAudio->contentType != ma_aaudio_content_type_default && pContextStateAAudio->AAudioStreamBuilder_setContentType != NULL) { + pContextStateAAudio->AAudioStreamBuilder_setContentType(pBuilder, ma_to_content_type__aaudio(pDeviceConfigAAudio->contentType)); } - /* What miniaudio calls a period, audio4 calls a block. */ - AUDIO_INITINFO(&fdInfo); - fdInfo.hiwat = internalPeriods; - fdInfo.lowat = internalPeriods-1; - fdInfo.blocksize = internalPeriodSizeInBytes; - if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed."); - return ma_result_from_errno(errno); + if (pDeviceConfigAAudio->allowedCapturePolicy != ma_aaudio_allow_capture_default && pContextStateAAudio->AAudioStreamBuilder_setAllowedCapturePolicy != NULL) { + pContextStateAAudio->AAudioStreamBuilder_setAllowedCapturePolicy(pBuilder, ma_to_allowed_capture_policy__aaudio(pDeviceConfigAAudio->allowedCapturePolicy)); } - internalPeriods = fdInfo.hiwat; - internalPeriodSizeInFrames = fdInfo.blocksize / ma_get_bytes_per_frame(internalFormat, internalChannels); - } - } - #else - { - struct audio_swpar fdPar; - - /* We need to retrieve the format of the device so we can know the channel count and sample rate. Then we can calculate the buffer size. */ - if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters."); - return ma_result_from_errno(errno); + pContextStateAAudio->AAudioStreamBuilder_setDataCallback(pBuilder, ma_stream_data_callback_playback__aaudio, (void*)pDevice); } - internalFormat = ma_format_from_swpar__audio4(&fdPar); - internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; - internalSampleRate = fdPar.rate; - - if (internalFormat == ma_format_unknown) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); - return MA_FORMAT_NOT_SUPPORTED; + /* If we set AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, we allow for MMAP (non-legacy path). Beware though, with a conservative performance profile, AAudio will indeed take the legacy path. */ + if (pDeviceConfigAAudio->performanceMode != ma_aaudio_performance_mode_default) { + pContextStateAAudio->AAudioStreamBuilder_setPerformanceMode(pBuilder, ma_to_performance_mode__aaudio(pDeviceConfigAAudio->performanceMode)); } - /* Buffer. */ - { - ma_uint32 internalPeriodSizeInBytes; - - internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate, pConfig->performanceProfile); - - /* What miniaudio calls a period, audio4 calls a block. */ - internalPeriodSizeInBytes = internalPeriodSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); - if (internalPeriodSizeInBytes < 16) { - internalPeriodSizeInBytes = 16; - } - - fdPar.nblks = pDescriptor->periodCount; - fdPar.round = internalPeriodSizeInBytes; - - if (ioctl(fd, AUDIO_SETPAR, &fdPar) < 0) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters."); - return ma_result_from_errno(errno); - } - - if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters."); - return ma_result_from_errno(errno); - } + /* We need to set an error callback to detect device changes. */ + if (pDevice != NULL) { /* <-- pDevice should never be null if pDescriptor is not null, which is always the case if we hit this branch. Check anyway for safety. */ + pContextStateAAudio->AAudioStreamBuilder_setErrorCallback(pBuilder, ma_stream_error_callback__aaudio, (void*)pDevice); } - - internalFormat = ma_format_from_swpar__audio4(&fdPar); - internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; - internalSampleRate = fdPar.rate; - internalPeriods = fdPar.nblks; - internalPeriodSizeInFrames = fdPar.round / ma_get_bytes_per_frame(internalFormat, internalChannels); - } - #endif - - if (internalFormat == ma_format_unknown) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); - return MA_FORMAT_NOT_SUPPORTED; - } - - if (deviceType == ma_device_type_capture) { - pDevice->audio4.fdCapture = fd; - } else { - pDevice->audio4.fdPlayback = fd; } - pDescriptor->format = internalFormat; - pDescriptor->channels = internalChannels; - pDescriptor->sampleRate = internalSampleRate; - ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); - pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; - pDescriptor->periodCount = internalPeriods; + *ppBuilder = pBuilder; return MA_SUCCESS; } -static ma_result ma_device_init__audio4(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static ma_result ma_open_stream_and_close_builder__aaudio(ma_context* pContext, ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream** ppStream) { - MA_ASSERT(pDevice != NULL); - - MA_ZERO_OBJECT(&pDevice->audio4); - - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(pContext); + ma_result result; - pDevice->audio4.fdCapture = -1; - pDevice->audio4.fdPlayback = -1; + result = ma_result_from_aaudio(pContextStateAAudio->AAudioStreamBuilder_openStream(pBuilder, ppStream)); + pContextStateAAudio->AAudioStreamBuilder_delete(pBuilder); - /* - The version of the operating system dictates whether or not the device is exclusive or shared. NetBSD - introduced in-kernel mixing which means it's shared. All other BSD flavours are exclusive as far as - I'm aware. - */ -#if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 800000000 - /* NetBSD 8.0+ */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } -#else - /* All other flavors. */ -#endif + return result; +} - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__audio4(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); - if (result != MA_SUCCESS) { - return result; - } - } +static ma_result ma_open_stream_basic__aaudio(ma_context* pContext, const ma_device_id* pDeviceID, ma_device_type deviceType, ma_share_mode shareMode, ma_AAudioStream** ppStream) +{ + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(pContext); + ma_result result; + ma_AAudioStreamBuilder* pBuilder; - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__audio4(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); - if (result != MA_SUCCESS) { - if (pConfig->deviceType == ma_device_type_duplex) { - close(pDevice->audio4.fdCapture); - } - return result; - } + *ppStream = NULL; + + result = ma_create_and_configure_AAudioStreamBuilder__aaudio(pContext, NULL, pDeviceID, deviceType, shareMode, NULL, NULL, &pBuilder); + if (result != MA_SUCCESS) { + return result; } - return MA_SUCCESS; + /* Let's give AAudio a hint to avoid the legacy path (AudioStreamLegacy). */ + pContextStateAAudio->AAudioStreamBuilder_setPerformanceMode(pBuilder, MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + + return ma_open_stream_and_close_builder__aaudio(pContext, pBuilder, ppStream); } -static ma_result ma_device_start__audio4(ma_device* pDevice) +static ma_result ma_open_stream__aaudio(ma_device* pDevice, const ma_device_config_aaudio* pDeviceConfigAAudio, ma_device_type deviceType, const ma_device_descriptor* pDescriptor, ma_AAudioStream** ppStream) { + ma_result result; + ma_AAudioStreamBuilder* pBuilder; + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pDescriptor != NULL); + MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (pDevice->audio4.fdCapture == -1) { - return MA_INVALID_ARGS; - } - } + *ppStream = NULL; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - if (pDevice->audio4.fdPlayback == -1) { - return MA_INVALID_ARGS; - } + result = ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_device_get_context(pDevice), pDeviceConfigAAudio, pDescriptor->pDeviceID, deviceType, pDescriptor->shareMode, pDescriptor, pDevice, &pBuilder); + if (result != MA_SUCCESS) { + return result; } - return MA_SUCCESS; + return ma_open_stream_and_close_builder__aaudio(ma_device_get_context(pDevice), pBuilder, ppStream); } -static ma_result ma_device_stop_fd__audio4(ma_device* pDevice, int fd) +static ma_result ma_close_stream__aaudio(ma_context* pContext, ma_AAudioStream* pStream) { - if (fd == -1) { + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(pContext); + + if (pStream == NULL) { return MA_INVALID_ARGS; } -#if !defined(MA_AUDIO4_USE_NEW_API) - if (ioctl(fd, AUDIO_FLUSH, 0) < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed."); - return ma_result_from_errno(errno); + return ma_result_from_aaudio(pContextStateAAudio->AAudioStream_close(pStream)); +} + +static ma_result ma_wait_for_simple_state_transition__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_aaudio_stream_state_t oldState, ma_aaudio_stream_state_t newState) +{ + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(pContext); + ma_aaudio_stream_state_t actualNewState; + ma_aaudio_result_t resultAA = pContextStateAAudio->AAudioStream_waitForStateChange(pStream, oldState, &actualNewState, 5000000000); /* 5 second timeout. */ + if (resultAA != MA_AAUDIO_OK) { + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[AAudio] Failed to wait for state change."); + return ma_result_from_aaudio(resultAA); } -#else - if (ioctl(fd, AUDIO_STOP, 0) < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed."); - return ma_result_from_errno(errno); + + if (newState != actualNewState) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[AAudio] Expected state transition to %d but got %d instead.", newState, actualNewState); + return MA_ERROR; /* Failed to transition into the expected state. */ } -#endif return MA_SUCCESS; } -static ma_result ma_device_stop__audio4(ma_device* pDevice) +static void ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_format format, ma_uint32 flags, ma_device_info* pDeviceInfo) { - MA_ASSERT(pDevice != NULL); - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_result result; - - result = ma_device_stop_fd__audio4(pDevice, pDevice->audio4.fdCapture); - if (result != MA_SUCCESS) { - return result; - } - } + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(pContext); + ma_uint32 channels; + ma_uint32 sampleRate; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_result result; + MA_ASSERT(pStream != NULL); + MA_ASSERT(pDeviceInfo != NULL); - /* Drain the device first. If this fails we'll just need to flush without draining. Unfortunately draining isn't available on newer version of OpenBSD. */ - #if !defined(MA_AUDIO4_USE_NEW_API) - ioctl(pDevice->audio4.fdPlayback, AUDIO_DRAIN, 0); - #endif + channels = pContextStateAAudio->AAudioStream_getChannelCount(pStream); + sampleRate = pContextStateAAudio->AAudioStream_getSampleRate(pStream); - /* Here is where the device is stopped immediately. */ - result = ma_device_stop_fd__audio4(pDevice, pDevice->audio4.fdPlayback); - if (result != MA_SUCCESS) { - return result; - } - } + ma_device_info_add_native_data_format_ex(pDeviceInfo, flags, format, channels, channels, sampleRate, sampleRate); +} - return MA_SUCCESS; +static void ma_context_add_native_data_format_from_AAudioStream__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_uint32 flags, ma_device_info* pDeviceInfo) +{ + /* AAudio supports s16 and f32. */ + ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(pContext, pStream, ma_format_f32, flags, pDeviceInfo); + ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(pContext, pStream, ma_format_s16, flags, pDeviceInfo); } -static ma_result ma_device_write__audio4(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +static ma_device_enumeration_result ma_context_enumerate_device_from_type__aaudio(ma_context* pContext, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) { - int result; + ma_result result; + ma_AAudioStream* pStream; + ma_device_info deviceInfo; - if (pFramesWritten != NULL) { - *pFramesWritten = 0; + result = ma_open_stream_basic__aaudio(pContext, NULL, deviceType, ma_share_mode_shared, &pStream); + if (result != MA_SUCCESS) { + return MA_DEVICE_ENUMERATION_CONTINUE; } - result = write(pDevice->audio4.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); - if (result < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device."); - return ma_result_from_errno(errno); - } + MA_ZERO_OBJECT(&deviceInfo); - if (pFramesWritten != NULL) { - *pFramesWritten = (ma_uint32)result / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + /* Default. */ + deviceInfo.isDefault = MA_TRUE; + + /* ID. */ + deviceInfo.id.aaudio = MA_AAUDIO_UNSPECIFIED; + + /* Name. */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } - return MA_SUCCESS; + /* Data Format. */ + ma_context_add_native_data_format_from_AAudioStream__aaudio(pContext, pStream, 0, &deviceInfo); + + /* Done with the stream. */ + ma_close_stream__aaudio(pContext, pStream); + + return callback(deviceType, &deviceInfo, pUserData); } -static ma_result ma_device_read__audio4(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) +static ma_result ma_context_enumerate_devices__aaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - int result; + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; - if (pFramesRead != NULL) { - *pFramesRead = 0; - } + MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); - result = read(pDevice->audio4.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - if (result < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device."); - return ma_result_from_errno(errno); - } + /* + Unfortunately AAudio does not have an enumeration API. Therefore I'm only going to report default + devices. We need to open the stream in order to determine if we have a usable device, and for + extracting format info. + */ - if (pFramesRead != NULL) { - *pFramesRead = (ma_uint32)result / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + /* Playback. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type__aaudio(pContext, ma_device_type_playback, callback, pUserData); } - return MA_SUCCESS; -} + /* Capture. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type__aaudio(pContext, ma_device_type_capture, callback, pUserData); + } -static ma_result ma_context_uninit__audio4(ma_context* pContext) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_audio4); + (void)cbResult; - (void)pContext; return MA_SUCCESS; } -static ma_result ma_context_init__audio4(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static ma_result ma_close_streams__aaudio(ma_device* pDevice) { - MA_ASSERT(pContext != NULL); + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); - (void)pConfig; + MA_ASSERT(pDevice != NULL); + + /* When rerouting, streams may have been closed and never re-opened. Hence the extra checks below. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_close_stream__aaudio(ma_device_get_context(pDevice), pDeviceStateAAudio->pStreamCapture); + pDeviceStateAAudio->pStreamCapture = NULL; + } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_close_stream__aaudio(ma_device_get_context(pDevice), pDeviceStateAAudio->pStreamPlayback); + pDeviceStateAAudio->pStreamPlayback = NULL; + } - pCallbacks->onContextInit = ma_context_init__audio4; - pCallbacks->onContextUninit = ma_context_uninit__audio4; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__audio4; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__audio4; - pCallbacks->onDeviceInit = ma_device_init__audio4; - pCallbacks->onDeviceUninit = ma_device_uninit__audio4; - pCallbacks->onDeviceStart = ma_device_start__audio4; - pCallbacks->onDeviceStop = ma_device_stop__audio4; - pCallbacks->onDeviceRead = ma_device_read__audio4; - pCallbacks->onDeviceWrite = ma_device_write__audio4; - pCallbacks->onDeviceDataLoop = NULL; + ma_device_state_async_uninit(&pDeviceStateAAudio->async, ma_device_get_allocation_callbacks(pDevice)); return MA_SUCCESS; } -#endif /* MA_HAS_AUDIO4 */ +static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_device_config_aaudio* pDeviceConfigAAudio, ma_device_type deviceType, ma_device_descriptor* pDescriptor, ma_AAudioStream** ppStream) +{ + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(ma_device_get_context(pDevice)); + ma_result result; + int32_t bufferCapacityInFrames; + int32_t framesPerDataCallback; + ma_AAudioStream* pStream; -/****************************************************************************** + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pDeviceConfigAAudio != NULL); + MA_ASSERT(pDescriptor != NULL); -OSS Backend + *ppStream = NULL; /* Safety. */ -******************************************************************************/ -#ifdef MA_HAS_OSS -#include -#include -#include -#include + /* First step is to open the stream. From there we'll be able to extract the internal configuration. */ + result = ma_open_stream__aaudio(pDevice, pDeviceConfigAAudio, deviceType, pDescriptor, &pStream); + if (result != MA_SUCCESS) { + return result; /* Failed to open the AAudio stream. */ + } -#ifndef SNDCTL_DSP_HALT -#define SNDCTL_DSP_HALT SNDCTL_DSP_RESET -#endif + /* Now extract the internal configuration. */ + pDescriptor->format = (pContextStateAAudio->AAudioStream_getFormat(pStream) == MA_AAUDIO_FORMAT_PCM_I16) ? ma_format_s16 : ma_format_f32; + pDescriptor->channels = pContextStateAAudio->AAudioStream_getChannelCount(pStream); + pDescriptor->sampleRate = pContextStateAAudio->AAudioStream_getSampleRate(pStream); -#define MA_OSS_DEFAULT_DEVICE_NAME "/dev/dsp" + /* For the channel map we need to be sure we don't overflow any buffers. */ + if (pDescriptor->channels <= MA_MAX_CHANNELS) { + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); /* <-- Cannot find info on channel order, so assuming a default. */ + } else { + ma_channel_map_init_blank(pDescriptor->channelMap, MA_MAX_CHANNELS); /* Too many channels. Use a blank channel map. */ + } -static int ma_open_temp_device__oss(void) -{ - /* The OSS sample code uses "/dev/mixer" as the device for getting system properties so I'm going to do the same. */ - int fd = open("/dev/mixer", O_RDONLY, 0); - if (fd >= 0) { - return fd; + bufferCapacityInFrames = pContextStateAAudio->AAudioStream_getBufferCapacityInFrames(pStream); + framesPerDataCallback = pContextStateAAudio->AAudioStream_getFramesPerDataCallback(pStream); + + if (framesPerDataCallback > 0) { + pDescriptor->periodSizeInFrames = framesPerDataCallback; + pDescriptor->periodCount = bufferCapacityInFrames / framesPerDataCallback; + } else { + pDescriptor->periodSizeInFrames = bufferCapacityInFrames; + pDescriptor->periodCount = 1; } - return -1; + *ppStream = pStream; + + return MA_SUCCESS; } -static ma_result ma_context_open_device__oss(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, int* pfd) +static ma_result ma_device_init_streams__aaudio(ma_device* pDevice, ma_device_state_aaudio* pDeviceStateAAudio, const ma_device_config_aaudio* pDeviceConfigAAudio, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { - const char* deviceName; - int flags; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(pfd != NULL); - (void)pContext; + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; - *pfd = -1; + MA_ASSERT(pDevice != NULL); - /* This function should only be called for playback or capture, not duplex. */ - if (deviceType == ma_device_type_duplex) { - return MA_INVALID_ARGS; + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - deviceName = MA_OSS_DEFAULT_DEVICE_NAME; - if (pDeviceID != NULL) { - deviceName = pDeviceID->oss; + pDeviceStateAAudio->usage = pDeviceConfigAAudio->usage; + pDeviceStateAAudio->contentType = pDeviceConfigAAudio->contentType; + pDeviceStateAAudio->inputPreset = pDeviceConfigAAudio->inputPreset; + pDeviceStateAAudio->allowedCapturePolicy = pDeviceConfigAAudio->allowedCapturePolicy; + pDeviceStateAAudio->noAutoStartAfterReroute = pDeviceConfigAAudio->noAutoStartAfterReroute; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + result = ma_device_init_by_type__aaudio(pDevice, pDeviceConfigAAudio, ma_device_type_capture, pDescriptorCapture, &pDeviceStateAAudio->pStreamCapture); + if (result != MA_SUCCESS) { + return result; + } } - flags = (deviceType == ma_device_type_playback) ? O_WRONLY : O_RDONLY; - if (shareMode == ma_share_mode_exclusive) { - flags |= O_EXCL; + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_device_init_by_type__aaudio(pDevice, pDeviceConfigAAudio, ma_device_type_playback, pDescriptorPlayback, &pDeviceStateAAudio->pStreamPlayback); + if (result != MA_SUCCESS) { + return result; + } } - *pfd = open(deviceName, flags, 0); - if (*pfd == -1) { - return ma_result_from_errno(errno); + result = ma_device_state_async_init(deviceType, pDescriptorPlayback, pDescriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateAAudio->async); + if (result != MA_SUCCESS) { + ma_close_streams__aaudio(pDevice); + return result; } return MA_SUCCESS; } -static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +static ma_result ma_device_init__aaudio(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - int fd; - oss_sysinfo si; - int result; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); + ma_device_state_aaudio* pDeviceStateAAudio; + const ma_device_config_aaudio* pDeviceConfigAAudio = (const ma_device_config_aaudio*)pDeviceBackendConfig; + ma_result result; - fd = ma_open_temp_device__oss(); - if (fd == -1) { - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); - return MA_NO_BACKEND; + pDeviceStateAAudio = (ma_device_state_aaudio*)ma_calloc(sizeof(*pDeviceStateAAudio), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateAAudio == NULL) { + return MA_OUT_OF_MEMORY; } - result = ioctl(fd, SNDCTL_SYSINFO, &si); - if (result != -1) { - int iAudioDevice; - for (iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { - oss_audioinfo ai; - ai.dev = iAudioDevice; - result = ioctl(fd, SNDCTL_AUDIOINFO, &ai); - if (result != -1) { - if (ai.devnode[0] != '\0') { /* <-- Can be blank, according to documentation. */ - ma_device_info deviceInfo; - ma_bool32 isTerminating = MA_FALSE; - - MA_ZERO_OBJECT(&deviceInfo); - - /* ID */ - ma_strncpy_s(deviceInfo.id.oss, sizeof(deviceInfo.id.oss), ai.devnode, (size_t)-1); - - /* - The human readable device name should be in the "ai.handle" variable, but it can - sometimes be empty in which case we just fall back to "ai.name" which is less user - friendly, but usually has a value. - */ - if (ai.handle[0] != '\0') { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.handle, (size_t)-1); - } else { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ai.name, (size_t)-1); - } - - /* The device can be both playback and capture. */ - if (!isTerminating && (ai.caps & PCM_CAP_OUTPUT) != 0) { - isTerminating = !callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - } - if (!isTerminating && (ai.caps & PCM_CAP_INPUT) != 0) { - isTerminating = !callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); - } - - if (isTerminating) { - break; - } - } - } - } - } else { - close(fd); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); - return MA_NO_BACKEND; + result = ma_device_init_streams__aaudio(pDevice, pDeviceStateAAudio, pDeviceConfigAAudio, pDescriptorPlayback, pDescriptorCapture); + if (result != MA_SUCCESS) { + ma_free(pDeviceStateAAudio, ma_device_get_allocation_callbacks(pDevice)); + return result; } - close(fd); + *ppDeviceState = pDeviceStateAAudio; + return MA_SUCCESS; } -static void ma_context_add_native_data_format__oss(ma_context* pContext, oss_audioinfo* pAudioInfo, ma_format format, ma_device_info* pDeviceInfo) +static void ma_device_uninit__aaudio(ma_device* pDevice) { - unsigned int minChannels; - unsigned int maxChannels; - unsigned int iRate; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(pAudioInfo != NULL); - MA_ASSERT(pDeviceInfo != NULL); - - /* If we support all channels we just report 0. */ - minChannels = ma_clamp(pAudioInfo->min_channels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); - maxChannels = ma_clamp(pAudioInfo->max_channels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); /* - OSS has this annoying thing where sample rates can be reported in two ways. We prefer explicitness, - which OSS has in the form of nrates/rates, however there are times where nrates can be 0, in which - case we'll need to use min_rate and max_rate and report only standard rates. + Note: Closing the streams may cause a timeout error, which would then trigger rerouting in our error callback. + We must not schedule a reroute when device is getting destroyed. */ - if (pAudioInfo->nrates > 0) { - for (iRate = 0; iRate < pAudioInfo->nrates; iRate += 1) { - unsigned int rate = pAudioInfo->rates[iRate]; + ma_atomic_bool32_set(&pDeviceStateAAudio->isTearingDown, MA_TRUE); - if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) { - ma_device_info_add_native_data_format(pDeviceInfo, format, 0, rate, 0); /* Set the channel count to 0 to indicate that all channel counts are supported. */ - } else { - unsigned int iChannel; - for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) { - ma_device_info_add_native_data_format(pDeviceInfo, format, iChannel, rate, 0); - } - } - } - } else { - for (iRate = 0; iRate < ma_countof(g_maStandardSampleRatePriorities); iRate += 1) { - ma_uint32 standardRate = g_maStandardSampleRatePriorities[iRate]; + ma_close_streams__aaudio(pDevice); - if (standardRate >= (ma_uint32)pAudioInfo->min_rate && standardRate <= (ma_uint32)pAudioInfo->max_rate) { - if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) { - ma_device_info_add_native_data_format(pDeviceInfo, format, 0, standardRate, 0); /* Set the channel count to 0 to indicate that all channel counts are supported. */ - } else { - unsigned int iChannel; - for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) { - ma_device_info_add_native_data_format(pDeviceInfo, format, iChannel, standardRate, 0); - } - } - } - } - } + ma_free(pDeviceStateAAudio, ma_device_get_allocation_callbacks(pDevice)); } -static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type deviceType) { - ma_bool32 foundDevice; - int fdTemp; - oss_sysinfo si; - int result; - - MA_ASSERT(pContext != NULL); + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); + const ma_int32 maxAttempts = 4; /* Reasonable retry limit. */ - /* Handle the default device a little differently. */ - if (pDeviceID == NULL) { - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } + ma_result result; + ma_int32 iAttempt; - return MA_SUCCESS; - } + MA_ASSERT(pDevice != NULL); + /* We got disconnected! Retry a few times, until we find a connected device! */ + iAttempt = 0; + while (iAttempt++ < maxAttempts) { + ma_device_config_aaudio deviceConfigAAudio; + ma_device_descriptor descriptorPlayback; + ma_device_descriptor descriptorCapture; - /* If we get here it means we are _not_ using the default device. */ - foundDevice = MA_FALSE; + /* Device tearing down? No need to reroute! */ + if (ma_atomic_bool32_get(&pDeviceStateAAudio->isTearingDown)) { + result = MA_SUCCESS; /* Caller should continue as normal. */ + break; + } - fdTemp = ma_open_temp_device__oss(); - if (fdTemp == -1) { - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); - return MA_NO_BACKEND; - } + /* The first thing to do is close the streams. */ + ma_close_streams__aaudio(pDevice); - result = ioctl(fdTemp, SNDCTL_SYSINFO, &si); - if (result != -1) { - int iAudioDevice; - for (iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { - oss_audioinfo ai; - ai.dev = iAudioDevice; - result = ioctl(fdTemp, SNDCTL_AUDIOINFO, &ai); - if (result != -1) { - if (ma_strcmp(ai.devnode, pDeviceID->oss) == 0) { - /* It has the same name, so now just confirm the type. */ - if ((deviceType == ma_device_type_playback && ((ai.caps & PCM_CAP_OUTPUT) != 0)) || - (deviceType == ma_device_type_capture && ((ai.caps & PCM_CAP_INPUT) != 0))) { - unsigned int formatMask; + /* Now we need to reinitialize each streams. The hardest part with this is just filling out the config and descriptors. */ + deviceConfigAAudio = ma_device_config_aaudio_init(); + deviceConfigAAudio.usage = pDeviceStateAAudio->usage; + deviceConfigAAudio.contentType = pDeviceStateAAudio->contentType; + deviceConfigAAudio.inputPreset = pDeviceStateAAudio->inputPreset; + deviceConfigAAudio.allowedCapturePolicy = pDeviceStateAAudio->allowedCapturePolicy; + deviceConfigAAudio.noAutoStartAfterReroute = pDeviceStateAAudio->noAutoStartAfterReroute; - /* ID */ - ma_strncpy_s(pDeviceInfo->id.oss, sizeof(pDeviceInfo->id.oss), ai.devnode, (size_t)-1); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + descriptorCapture.pDeviceID = NULL; /* Only doing rerouting with default devices. */ + descriptorCapture.shareMode = pDevice->capture.shareMode; + descriptorCapture.format = ma_device_get_format(pDevice, ma_device_type_capture); + descriptorCapture.channels = ma_device_get_channels(pDevice, ma_device_type_capture); + descriptorCapture.sampleRate = ma_device_get_sample_rate(pDevice); + descriptorCapture.periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; + descriptorCapture.periodCount = 1; + } - /* - The human readable device name should be in the "ai.handle" variable, but it can - sometimes be empty in which case we just fall back to "ai.name" which is less user - friendly, but usually has a value. - */ - if (ai.handle[0] != '\0') { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ai.handle, (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ai.name, (size_t)-1); - } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + descriptorPlayback.pDeviceID = NULL; /* Only doing rerouting with default devices. */ + descriptorPlayback.shareMode = pDevice->playback.shareMode; + descriptorPlayback.format = ma_device_get_format(pDevice, ma_device_type_playback); + descriptorPlayback.channels = ma_device_get_channels(pDevice, ma_device_type_playback); + descriptorPlayback.sampleRate = ma_device_get_sample_rate(pDevice); + descriptorPlayback.periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; + descriptorPlayback.periodCount = 1; + } + result = ma_device_init_streams__aaudio(pDevice, pDeviceStateAAudio, &deviceConfigAAudio, &descriptorPlayback, &descriptorCapture); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to create stream after route change."); + break; + } - pDeviceInfo->nativeDataFormatCount = 0; + /* The internal data converters need to be updated. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + result = ma_device_update_descriptor(pDevice, ma_device_type_capture, NULL, &descriptorCapture); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to initialize device after route change."); + ma_close_streams__aaudio(pDevice); + break; + } + } - if (deviceType == ma_device_type_playback) { - formatMask = ai.oformats; - } else { - formatMask = ai.iformats; - } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_device_update_descriptor(pDevice, ma_device_type_playback, NULL, &descriptorPlayback); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to initialize device after route change."); + ma_close_streams__aaudio(pDevice); + break; + } + } - if (((formatMask & AFMT_S16_LE) != 0 && ma_is_little_endian()) || (AFMT_S16_BE && ma_is_big_endian())) { - ma_context_add_native_data_format__oss(pContext, &ai, ma_format_s16, pDeviceInfo); - } - if (((formatMask & AFMT_S32_LE) != 0 && ma_is_little_endian()) || (AFMT_S32_BE && ma_is_big_endian())) { - ma_context_add_native_data_format__oss(pContext, &ai, ma_format_s32, pDeviceInfo); - } - if ((formatMask & AFMT_U8) != 0) { - ma_context_add_native_data_format__oss(pContext, &ai, ma_format_u8, pDeviceInfo); - } + /* We'll only ever do this in response to a reroute. */ + ma_device_post_notification_rerouted(pDevice); - foundDevice = MA_TRUE; - break; + /* If the device is started, start the streams. Maybe make this configurable? */ + if (ma_device_get_status(pDevice) == ma_device_status_started) { + if (pDeviceStateAAudio->noAutoStartAfterReroute == MA_FALSE) { + result = ma_device_start__aaudio(pDevice); + if (result != MA_SUCCESS) { + if (iAttempt < maxAttempts) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, retrying(%d)", iAttempt); + } else { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, giving up."); } } + } else { + ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ } } - } else { - close(fdTemp); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); - return MA_NO_BACKEND; - } - - close(fdTemp); - - if (!foundDevice) { - return MA_NO_DEVICE; + if (result == MA_SUCCESS) { + /* Reroute successful! */ + break; + } } - - return MA_SUCCESS; + + return result; } -static ma_result ma_device_uninit__oss(ma_device* pDevice) +static ma_result ma_device_start_stream_internal__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) { + /*ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice);*/ + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(ma_device_get_context(pDevice)); + ma_aaudio_result_t resultAA; + ma_aaudio_stream_state_t currentState; + MA_ASSERT(pDevice != NULL); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - close(pDevice->oss.fdCapture); + if (pStream == NULL) { + return MA_INVALID_ARGS; } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - close(pDevice->oss.fdPlayback); + currentState = pContextStateAAudio->AAudioStream_getState(pStream); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Current stream state before starting: %d", currentState); + if (currentState == MA_AAUDIO_STREAM_STATE_DISCONNECTED) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Failed to start stream because it is disconnected."); + return MA_ERROR; } - return MA_SUCCESS; -} -static int ma_format_to_oss(ma_format format) -{ - int ossFormat = AFMT_U8; - switch (format) { - case ma_format_s16: ossFormat = (ma_is_little_endian()) ? AFMT_S16_LE : AFMT_S16_BE; break; - case ma_format_s24: ossFormat = (ma_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; - case ma_format_s32: ossFormat = (ma_is_little_endian()) ? AFMT_S32_LE : AFMT_S32_BE; break; - case ma_format_f32: ossFormat = (ma_is_little_endian()) ? AFMT_S16_LE : AFMT_S16_BE; break; - case ma_format_u8: - default: ossFormat = AFMT_U8; break; + resultAA = pContextStateAAudio->AAudioStream_requestStart(pStream); + if (resultAA != MA_AAUDIO_OK) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Failed to start stream."); + return ma_result_from_aaudio(resultAA); } - return ossFormat; -} + /* Do we actually need to wait for the device to transition into its started state? */ -static ma_format ma_format_from_oss(int ossFormat) -{ - if (ossFormat == AFMT_U8) { - return ma_format_u8; - } else { - if (ma_is_little_endian()) { - switch (ossFormat) { - case AFMT_S16_LE: return ma_format_s16; - case AFMT_S32_LE: return ma_format_s32; - default: return ma_format_unknown; - } - } else { - switch (ossFormat) { - case AFMT_S16_BE: return ma_format_s16; - case AFMT_S32_BE: return ma_format_s32; - default: return ma_format_unknown; - } + /* The device should be in either a starting or started state. If it's not set to started we need to wait for it to transition. It should go from starting to started. */ + currentState = pContextStateAAudio->AAudioStream_getState(pStream); + if (currentState != MA_AAUDIO_STREAM_STATE_STARTED) { + ma_result result; + + if (currentState != MA_AAUDIO_STREAM_STATE_STARTING) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Failed to start stream because it was expected to be in STREAM_STATE_STARTING."); + return MA_ERROR; /* Expecting the stream to be a starting or started state. */ + } + + result = ma_wait_for_simple_state_transition__aaudio(ma_device_get_context(pDevice), pStream, currentState, MA_AAUDIO_STREAM_STATE_STARTED); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Failed to start stream because it failed to transition to STREAM_STATE_STARTED."); + return result; } } - return ma_format_unknown; + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Stream started successfully."); + return MA_SUCCESS; } -static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) +static ma_result ma_device_start_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) { + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); ma_result result; - int ossResult; - int fd; - const ma_device_id* pDeviceID = NULL; - ma_share_mode shareMode; - int ossFormat; - int ossChannels; - int ossSampleRate; - int ossFragment; - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(deviceType != ma_device_type_duplex); + /* + If the stream is disconnected while the stream is in a stopped state, starting the stream will fail during the state + transition. When starting fails, we'll attempt to reinitialize the stream and try starting again. We'll only attempt + this once. + */ + result = ma_device_start_stream_internal__aaudio(pDevice, pStream); + if (result == MA_ERROR) { + if (pStream == pDeviceStateAAudio->pStreamPlayback) { + result = ma_device_reinit__aaudio(pDevice, ma_device_type_playback); + } else { + result = ma_device_reinit__aaudio(pDevice, ma_device_type_capture); + } - pDeviceID = pDescriptor->pDeviceID; - shareMode = pDescriptor->shareMode; - ossFormat = ma_format_to_oss((pDescriptor->format != ma_format_unknown) ? pDescriptor->format : ma_format_s16); /* Use s16 by default because OSS doesn't like floating point. */ - ossChannels = (int)(pDescriptor->channels > 0) ? pDescriptor->channels : MA_DEFAULT_CHANNELS; - ossSampleRate = (int)(pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; + if (result != MA_SUCCESS) { + return result; + } - result = ma_context_open_device__oss(pDevice->pContext, deviceType, pDeviceID, shareMode, &fd); - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); - return result; + result = ma_device_start_stream_internal__aaudio(pDevice, pStream); } - /* - The OSS documentation is very clear about the order we should be initializing the device's properties: - 1) Format - 2) Channels - 3) Sample rate. - */ + return result; +} - /* Format. */ - ossResult = ioctl(fd, SNDCTL_DSP_SETFMT, &ossFormat); - if (ossResult == -1) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format."); - return ma_result_from_errno(errno); - } +static ma_result ma_device_stop_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) +{ + /*ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice);*/ + ma_context_state_aaudio* pContextStateAAudio = ma_context_get_backend_state__aaudio(ma_device_get_context(pDevice)); + ma_aaudio_result_t resultAA; + ma_aaudio_stream_state_t currentState; - /* Channels. */ - ossResult = ioctl(fd, SNDCTL_DSP_CHANNELS, &ossChannels); - if (ossResult == -1) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count."); - return ma_result_from_errno(errno); - } + MA_ASSERT(pDevice != NULL); - /* Sample Rate. */ - ossResult = ioctl(fd, SNDCTL_DSP_SPEED, &ossSampleRate); - if (ossResult == -1) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate."); - return ma_result_from_errno(errno); + if (pStream == NULL) { + return MA_INVALID_ARGS; } /* - Buffer. + From the AAudio documentation: - The documentation says that the fragment settings should be set as soon as possible, but I'm not sure if - it should be done before or after format/channels/rate. + The stream will stop after all of the data currently buffered has been played. - OSS wants the fragment size in bytes and a power of 2. When setting, we specify the power, not the actual - value. + This maps with miniaudio's requirement that device's be drained which means we don't need to implement any draining logic. */ - { - ma_uint32 periodSizeInFrames; - ma_uint32 periodSizeInBytes; - ma_uint32 ossFragmentSizePower; + currentState = pContextStateAAudio->AAudioStream_getState(pStream); + if (currentState == MA_AAUDIO_STREAM_STATE_DISCONNECTED) { + return MA_SUCCESS; /* The device is disconnected. Don't try stopping it. */ + } - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, (ma_uint32)ossSampleRate, pConfig->performanceProfile); + resultAA = pContextStateAAudio->AAudioStream_requestStop(pStream); + if (resultAA != MA_AAUDIO_OK) { + return ma_result_from_aaudio(resultAA); + } - periodSizeInBytes = ma_round_to_power_of_2(periodSizeInFrames * ma_get_bytes_per_frame(ma_format_from_oss(ossFormat), ossChannels)); - if (periodSizeInBytes < 16) { - periodSizeInBytes = 16; - } + /* The device should be in either a stopping or stopped state. If it's not set to started we need to wait for it to transition. It should go from stopping to stopped. */ + currentState = pContextStateAAudio->AAudioStream_getState(pStream); + if (currentState != MA_AAUDIO_STREAM_STATE_STOPPED) { + ma_result result; - ossFragmentSizePower = 4; - periodSizeInBytes >>= 4; - while (periodSizeInBytes >>= 1) { - ossFragmentSizePower += 1; + if (currentState != MA_AAUDIO_STREAM_STATE_STOPPING) { + return MA_ERROR; /* Expecting the stream to be a stopping or stopped state. */ } - ossFragment = (int)((pConfig->periods << 16) | ossFragmentSizePower); - ossResult = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &ossFragment); - if (ossResult == -1) { - close(fd); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count."); - return ma_result_from_errno(errno); + result = ma_wait_for_simple_state_transition__aaudio(ma_device_get_context(pDevice), pStream, currentState, MA_AAUDIO_STREAM_STATE_STOPPED); + if (result != MA_SUCCESS) { + return result; } } - /* Internal settings. */ - if (deviceType == ma_device_type_capture) { - pDevice->oss.fdCapture = fd; - } else { - pDevice->oss.fdPlayback = fd; + return MA_SUCCESS; +} + +static ma_result ma_device_start__aaudio(ma_device* pDevice) +{ + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + + ma_device_state_async_flush(&pDeviceStateAAudio->async); + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + result = ma_device_start_stream__aaudio(pDevice, pDeviceStateAAudio->pStreamCapture); + if (result != MA_SUCCESS) { + return result; + } } - pDescriptor->format = ma_format_from_oss(ossFormat); - pDescriptor->channels = ossChannels; - pDescriptor->sampleRate = ossSampleRate; - ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); - pDescriptor->periodCount = (ma_uint32)(ossFragment >> 16); - pDescriptor->periodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDescriptor->format, pDescriptor->channels); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + /* Do an initial step to get the buffer ready in preparation for the first callback instantiation. */ + ma_device_step__aaudio(pDevice, MA_BLOCKING_MODE_NON_BLOCKING); - if (pDescriptor->format == ma_format_unknown) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio."); - return MA_FORMAT_NOT_SUPPORTED; + result = ma_device_start_stream__aaudio(pDevice, pDeviceStateAAudio->pStreamPlayback); + if (result != MA_SUCCESS) { + if (deviceType == ma_device_type_duplex) { + ma_device_stop_stream__aaudio(pDevice, pDeviceStateAAudio->pStreamCapture); + } + return result; + } } return MA_SUCCESS; } -static ma_result ma_device_init__oss(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +static ma_result ma_device_stop__aaudio(ma_device* pDevice) { - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pConfig != NULL); - - MA_ZERO_OBJECT(&pDevice->oss); - - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_stop_stream__aaudio(pDevice, pDeviceStateAAudio->pStreamCapture); if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); return result; } } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_result result = ma_device_stop_stream__aaudio(pDevice, pDeviceStateAAudio->pStreamPlayback); if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); return result; } } @@ -38935,3523 +45747,3829 @@ static ma_result ma_device_init__oss(ma_device* pDevice, const ma_device_config* return MA_SUCCESS; } -/* -Note on Starting and Stopping -============================= -In the past I was using SNDCTL_DSP_HALT to stop the device, however this results in issues when -trying to resume the device again. If we use SNDCTL_DSP_HALT, the next write() or read() will -fail. Instead what we need to do is just not write or read to and from the device when the -device is not running. -As a result, both the start and stop functions for OSS are just empty stubs. The starting and -stopping logic is handled by ma_device_write__oss() and ma_device_read__oss(). These will check -the device state, and if the device is stopped they will simply not do any kind of processing. +static ma_result ma_device_step_extra__aaudio(ma_device* pDevice) +{ + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); + ma_result result; -The downside to this technique is that I've noticed a fairly lengthy delay in stopping the -device, up to a second. This is on a virtual machine, and as such might just be due to the -virtual drivers, but I'm not fully sure. I am not sure how to work around this problem so for -the moment that's just how it's going to have to be. + /* Do device rerouting if necessary. */ + if (ma_atomic_bool32_get(&pDeviceStateAAudio->isReroutingCapture)) { + ma_atomic_bool32_set(&pDeviceStateAAudio->isReroutingCapture, MA_FALSE); -When starting the device, OSS will automatically start it when write() or read() is called. -*/ -static ma_result ma_device_start__oss(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + result = ma_device_reinit__aaudio(pDevice, ma_device_type_capture); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Stopping device due to capture reroute failure."); + ma_device_stop(pDevice); + } + } - /* The device is automatically started with reading and writing. */ - (void)pDevice; + if (ma_atomic_bool32_get(&pDeviceStateAAudio->isReroutingPlayback)) { + ma_atomic_bool32_set(&pDeviceStateAAudio->isReroutingPlayback, MA_FALSE); + + result = ma_device_reinit__aaudio(pDevice, ma_device_type_playback); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Stopping device due to playback reroute failure."); + ma_device_stop(pDevice); + } + } return MA_SUCCESS; } -static ma_result ma_device_stop__oss(ma_device* pDevice) +static ma_result ma_device_step__aaudio(ma_device* pDevice, ma_blocking_mode blockingMode) { - MA_ASSERT(pDevice != NULL); + ma_device_state_aaudio* pDeviceStateAAudio = ma_device_get_backend_state__aaudio(pDevice); + return ma_device_state_async_step(&pDeviceStateAAudio->async, pDevice, blockingMode, ma_device_step_extra__aaudio); +} - /* See note above on why this is empty. */ - (void)pDevice; +static ma_device_backend_vtable ma_gDeviceBackendVTable_AAudio = +{ + ma_backend_info__aaudio, + ma_context_init__aaudio, + ma_context_uninit__aaudio, + ma_context_enumerate_devices__aaudio, + ma_device_init__aaudio, + ma_device_uninit__aaudio, + ma_device_start__aaudio, + ma_device_stop__aaudio, + ma_device_step__aaudio, + NULL /* onDeviceWakeup */ +}; - return MA_SUCCESS; +ma_device_backend_vtable* ma_device_backend_aaudio = &ma_gDeviceBackendVTable_AAudio; +#else +ma_device_backend_vtable* ma_device_backend_aaudio = NULL; +#endif /* AAudio */ + +MA_API ma_device_backend_vtable* ma_aaudio_get_vtable(void) +{ + return ma_device_backend_aaudio; } -static ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +MA_API ma_context_config_aaudio ma_context_config_aaudio_init(void) { - int resultOSS; - ma_uint32 deviceState; + ma_context_config_aaudio config; - if (pFramesWritten != NULL) { - *pFramesWritten = 0; + MA_ZERO_OBJECT(&config); + + return config; +} + +MA_API ma_device_config_aaudio ma_device_config_aaudio_init(void) +{ + ma_device_config_aaudio config; + + MA_ZERO_OBJECT(&config); + + return config; +} + + + +/****************************************************************************** + +OpenSL|ES Backend + +******************************************************************************/ +#ifdef MA_HAS_OPENSL +#include +#ifdef MA_ANDROID +#include +#endif + +typedef SLresult (SLAPIENTRY * ma_slCreateEngine_proc)(SLObjectItf* pEngine, SLuint32 numOptions, SLEngineOption* pEngineOptions, SLuint32 numInterfaces, SLInterfaceID* pInterfaceIds, SLboolean* pInterfaceRequired); + +/* OpenSL|ES has one-per-application objects :( */ +static SLObjectItf g_maEngineObjectSL = NULL; +static SLEngineItf g_maEngineSL = NULL; +static ma_uint32 g_maOpenSLInitCounter = 0; +static ma_spinlock g_maOpenSLSpinlock = 0; /* For init/uninit. */ + +#define MA_OPENSL_OBJ(p) (*((SLObjectItf)(p))) +#define MA_OPENSL_OUTPUTMIX(p) (*((SLOutputMixItf)(p))) +#define MA_OPENSL_PLAY(p) (*((SLPlayItf)(p))) +#define MA_OPENSL_RECORD(p) (*((SLRecordItf)(p))) + +#ifdef MA_ANDROID +#define MA_OPENSL_BUFFERQUEUE(p) (*((SLAndroidSimpleBufferQueueItf)(p))) +#else +#define MA_OPENSL_BUFFERQUEUE(p) (*((SLBufferQueueItf)(p))) +#endif + +static ma_result ma_result_from_OpenSL(SLuint32 result) +{ + switch (result) + { + case SL_RESULT_SUCCESS: return MA_SUCCESS; + case SL_RESULT_PRECONDITIONS_VIOLATED: return MA_ERROR; + case SL_RESULT_PARAMETER_INVALID: return MA_INVALID_ARGS; + case SL_RESULT_MEMORY_FAILURE: return MA_OUT_OF_MEMORY; + case SL_RESULT_RESOURCE_ERROR: return MA_INVALID_DATA; + case SL_RESULT_RESOURCE_LOST: return MA_ERROR; + case SL_RESULT_IO_ERROR: return MA_IO_ERROR; + case SL_RESULT_BUFFER_INSUFFICIENT: return MA_NO_SPACE; + case SL_RESULT_CONTENT_CORRUPTED: return MA_INVALID_DATA; + case SL_RESULT_CONTENT_UNSUPPORTED: return MA_FORMAT_NOT_SUPPORTED; + case SL_RESULT_CONTENT_NOT_FOUND: return MA_ERROR; + case SL_RESULT_PERMISSION_DENIED: return MA_ACCESS_DENIED; + case SL_RESULT_FEATURE_UNSUPPORTED: return MA_NOT_IMPLEMENTED; + case SL_RESULT_INTERNAL_ERROR: return MA_ERROR; + case SL_RESULT_UNKNOWN_ERROR: return MA_ERROR; + case SL_RESULT_OPERATION_ABORTED: return MA_ERROR; + case SL_RESULT_CONTROL_LOST: return MA_ERROR; + default: return MA_ERROR; } +} - /* Don't do any processing if the device is stopped. */ - deviceState = ma_device_get_state(pDevice); - if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { - return MA_SUCCESS; +/* Converts an individual OpenSL-style channel identifier (SL_SPEAKER_FRONT_LEFT, etc.) to miniaudio. */ +static ma_uint8 ma_channel_id_to_ma__opensl(SLuint32 id) +{ + switch (id) + { + case SL_SPEAKER_FRONT_LEFT: return MA_CHANNEL_FRONT_LEFT; + case SL_SPEAKER_FRONT_RIGHT: return MA_CHANNEL_FRONT_RIGHT; + case SL_SPEAKER_FRONT_CENTER: return MA_CHANNEL_FRONT_CENTER; + case SL_SPEAKER_LOW_FREQUENCY: return MA_CHANNEL_LFE; + case SL_SPEAKER_BACK_LEFT: return MA_CHANNEL_BACK_LEFT; + case SL_SPEAKER_BACK_RIGHT: return MA_CHANNEL_BACK_RIGHT; + case SL_SPEAKER_FRONT_LEFT_OF_CENTER: return MA_CHANNEL_FRONT_LEFT_CENTER; + case SL_SPEAKER_FRONT_RIGHT_OF_CENTER: return MA_CHANNEL_FRONT_RIGHT_CENTER; + case SL_SPEAKER_BACK_CENTER: return MA_CHANNEL_BACK_CENTER; + case SL_SPEAKER_SIDE_LEFT: return MA_CHANNEL_SIDE_LEFT; + case SL_SPEAKER_SIDE_RIGHT: return MA_CHANNEL_SIDE_RIGHT; + case SL_SPEAKER_TOP_CENTER: return MA_CHANNEL_TOP_CENTER; + case SL_SPEAKER_TOP_FRONT_LEFT: return MA_CHANNEL_TOP_FRONT_LEFT; + case SL_SPEAKER_TOP_FRONT_CENTER: return MA_CHANNEL_TOP_FRONT_CENTER; + case SL_SPEAKER_TOP_FRONT_RIGHT: return MA_CHANNEL_TOP_FRONT_RIGHT; + case SL_SPEAKER_TOP_BACK_LEFT: return MA_CHANNEL_TOP_BACK_LEFT; + case SL_SPEAKER_TOP_BACK_CENTER: return MA_CHANNEL_TOP_BACK_CENTER; + case SL_SPEAKER_TOP_BACK_RIGHT: return MA_CHANNEL_TOP_BACK_RIGHT; + default: return 0; } +} - resultOSS = write(pDevice->oss.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); - if (resultOSS < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device."); - return ma_result_from_errno(errno); +/* Converts an individual miniaudio channel identifier (MA_CHANNEL_FRONT_LEFT, etc.) to OpenSL-style. */ +static SLuint32 ma_channel_id_to_opensl(ma_uint8 id) +{ + switch (id) + { + case MA_CHANNEL_MONO: return SL_SPEAKER_FRONT_CENTER; + case MA_CHANNEL_FRONT_LEFT: return SL_SPEAKER_FRONT_LEFT; + case MA_CHANNEL_FRONT_RIGHT: return SL_SPEAKER_FRONT_RIGHT; + case MA_CHANNEL_FRONT_CENTER: return SL_SPEAKER_FRONT_CENTER; + case MA_CHANNEL_LFE: return SL_SPEAKER_LOW_FREQUENCY; + case MA_CHANNEL_BACK_LEFT: return SL_SPEAKER_BACK_LEFT; + case MA_CHANNEL_BACK_RIGHT: return SL_SPEAKER_BACK_RIGHT; + case MA_CHANNEL_FRONT_LEFT_CENTER: return SL_SPEAKER_FRONT_LEFT_OF_CENTER; + case MA_CHANNEL_FRONT_RIGHT_CENTER: return SL_SPEAKER_FRONT_RIGHT_OF_CENTER; + case MA_CHANNEL_BACK_CENTER: return SL_SPEAKER_BACK_CENTER; + case MA_CHANNEL_SIDE_LEFT: return SL_SPEAKER_SIDE_LEFT; + case MA_CHANNEL_SIDE_RIGHT: return SL_SPEAKER_SIDE_RIGHT; + case MA_CHANNEL_TOP_CENTER: return SL_SPEAKER_TOP_CENTER; + case MA_CHANNEL_TOP_FRONT_LEFT: return SL_SPEAKER_TOP_FRONT_LEFT; + case MA_CHANNEL_TOP_FRONT_CENTER: return SL_SPEAKER_TOP_FRONT_CENTER; + case MA_CHANNEL_TOP_FRONT_RIGHT: return SL_SPEAKER_TOP_FRONT_RIGHT; + case MA_CHANNEL_TOP_BACK_LEFT: return SL_SPEAKER_TOP_BACK_LEFT; + case MA_CHANNEL_TOP_BACK_CENTER: return SL_SPEAKER_TOP_BACK_CENTER; + case MA_CHANNEL_TOP_BACK_RIGHT: return SL_SPEAKER_TOP_BACK_RIGHT; + default: return 0; } +} - if (pFramesWritten != NULL) { - *pFramesWritten = (ma_uint32)resultOSS / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); +/* Converts a channel mapping to an OpenSL-style channel mask. */ +static SLuint32 ma_channel_map_to_channel_mask__opensl(const ma_channel* pChannelMap, ma_uint32 channels) +{ + SLuint32 channelMask = 0; + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; ++iChannel) { + channelMask |= ma_channel_id_to_opensl(pChannelMap[iChannel]); } - return MA_SUCCESS; + return channelMask; } -static ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) +/* Converts an OpenSL-style channel mask to a miniaudio channel map. */ +static void ma_channel_mask_to_channel_map__opensl(SLuint32 channelMask, ma_uint32 channels, ma_channel* pChannelMap) { - int resultOSS; - ma_uint32 deviceState; - - if (pFramesRead != NULL) { - *pFramesRead = 0; + if (channels == 1 && channelMask == 0) { + pChannelMap[0] = MA_CHANNEL_MONO; + } else if (channels == 2 && channelMask == 0) { + pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; + pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + if (channels == 1 && (channelMask & SL_SPEAKER_FRONT_CENTER) != 0) { + pChannelMap[0] = MA_CHANNEL_MONO; + } else { + /* Just iterate over each bit. */ + ma_uint32 iChannel = 0; + ma_uint32 iBit; + for (iBit = 0; iBit < 32 && iChannel < channels; ++iBit) { + SLuint32 bitValue = (channelMask & (1UL << iBit)); + if (bitValue != 0) { + /* The bit is set. */ + pChannelMap[iChannel] = ma_channel_id_to_ma__opensl(bitValue); + iChannel += 1; + } + } + } } +} - /* Don't do any processing if the device is stopped. */ - deviceState = ma_device_get_state(pDevice); - if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { - return MA_SUCCESS; +static SLuint32 ma_round_to_standard_sample_rate__opensl(SLuint32 samplesPerSec) +{ + if (samplesPerSec <= SL_SAMPLINGRATE_8) { + return SL_SAMPLINGRATE_8; } - - resultOSS = read(pDevice->oss.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - if (resultOSS < 0) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client."); - return ma_result_from_errno(errno); + if (samplesPerSec <= SL_SAMPLINGRATE_11_025) { + return SL_SAMPLINGRATE_11_025; + } + if (samplesPerSec <= SL_SAMPLINGRATE_12) { + return SL_SAMPLINGRATE_12; + } + if (samplesPerSec <= SL_SAMPLINGRATE_16) { + return SL_SAMPLINGRATE_16; + } + if (samplesPerSec <= SL_SAMPLINGRATE_22_05) { + return SL_SAMPLINGRATE_22_05; + } + if (samplesPerSec <= SL_SAMPLINGRATE_24) { + return SL_SAMPLINGRATE_24; + } + if (samplesPerSec <= SL_SAMPLINGRATE_32) { + return SL_SAMPLINGRATE_32; + } + if (samplesPerSec <= SL_SAMPLINGRATE_44_1) { + return SL_SAMPLINGRATE_44_1; + } + if (samplesPerSec <= SL_SAMPLINGRATE_48) { + return SL_SAMPLINGRATE_48; } - if (pFramesRead != NULL) { - *pFramesRead = (ma_uint32)resultOSS / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + /* Android doesn't support more than 48000. */ +#ifndef MA_ANDROID + if (samplesPerSec <= SL_SAMPLINGRATE_64) { + return SL_SAMPLINGRATE_64; } + if (samplesPerSec <= SL_SAMPLINGRATE_88_2) { + return SL_SAMPLINGRATE_88_2; + } + if (samplesPerSec <= SL_SAMPLINGRATE_96) { + return SL_SAMPLINGRATE_96; + } + if (samplesPerSec <= SL_SAMPLINGRATE_192) { + return SL_SAMPLINGRATE_192; + } +#endif - return MA_SUCCESS; + return SL_SAMPLINGRATE_16; } -static ma_result ma_context_uninit__oss(ma_context* pContext) + +static SLint32 ma_to_stream_type__opensl(ma_opensl_stream_type streamType) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_oss); + switch (streamType) { + case ma_opensl_stream_type_voice: return SL_ANDROID_STREAM_VOICE; + case ma_opensl_stream_type_system: return SL_ANDROID_STREAM_SYSTEM; + case ma_opensl_stream_type_ring: return SL_ANDROID_STREAM_RING; + case ma_opensl_stream_type_media: return SL_ANDROID_STREAM_MEDIA; + case ma_opensl_stream_type_alarm: return SL_ANDROID_STREAM_ALARM; + case ma_opensl_stream_type_notification: return SL_ANDROID_STREAM_NOTIFICATION; + default: break; + } - (void)pContext; - return MA_SUCCESS; + return SL_ANDROID_STREAM_VOICE; } -static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static SLint32 ma_to_recording_preset__opensl(ma_opensl_recording_preset recordingPreset) { - int fd; - int ossVersion; - int result; + switch (recordingPreset) { + case ma_opensl_recording_preset_generic: return SL_ANDROID_RECORDING_PRESET_GENERIC; + case ma_opensl_recording_preset_camcorder: return SL_ANDROID_RECORDING_PRESET_CAMCORDER; + case ma_opensl_recording_preset_voice_recognition: return SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + case ma_opensl_recording_preset_voice_communication: return SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION; + case ma_opensl_recording_preset_voice_unprocessed: return SL_ANDROID_RECORDING_PRESET_UNPROCESSED; + default: break; + } - MA_ASSERT(pContext != NULL); + return SL_ANDROID_RECORDING_PRESET_NONE; +} - (void)pConfig; - /* Try opening a temporary device first so we can get version information. This is closed at the end. */ - fd = ma_open_temp_device__oss(); - if (fd == -1) { - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties."); /* Looks liks OSS isn't installed, or there are no available devices. */ - return MA_NO_BACKEND; - } +typedef struct ma_context_state_opensl +{ + ma_handle libOpenSLES; + SLInterfaceID SL_IID_ENGINE; + SLInterfaceID SL_IID_AUDIOIODEVICECAPABILITIES; + SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + SLInterfaceID SL_IID_RECORD; + SLInterfaceID SL_IID_PLAY; + SLInterfaceID SL_IID_OUTPUTMIX; + SLInterfaceID SL_IID_ANDROIDCONFIGURATION; + ma_slCreateEngine_proc slCreateEngine; +} ma_context_state_opensl; - /* Grab the OSS version. */ - ossVersion = 0; - result = ioctl(fd, OSS_GETVERSION, &ossVersion); - if (result == -1) { - close(fd); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version."); - return MA_NO_BACKEND; - } +typedef struct ma_device_state_opensl +{ + ma_device_state_async async; + SLObjectItf pOutputMixObj; + SLOutputMixItf pOutputMix; + SLObjectItf pAudioPlayerObj; + SLPlayItf pAudioPlayer; + SLObjectItf pAudioRecorderObj; + SLRecordItf pAudioRecorder; + SLAndroidSimpleBufferQueueItf pBufferQueuePlayback; + SLAndroidSimpleBufferQueueItf pBufferQueueCapture; + ma_bool32 isDrainingCapture; + ma_bool32 isDrainingPlayback; + ma_uint32 currentBufferIndexPlayback; + ma_uint32 currentBufferIndexCapture; + ma_uint8* pBufferPlayback; /* This is malloc()'d and is used for storing audio data. Typed as ma_uint8 for easy offsetting. */ + ma_uint8* pBufferCapture; +} ma_device_state_opensl; - /* The file handle to temp device is no longer needed. Close ASAP. */ - close(fd); +static ma_context_state_opensl* ma_context_get_backend_state__opensl(ma_context* pContext) +{ + return (ma_context_state_opensl*)ma_context_get_backend_state(pContext); +} - pContext->oss.versionMajor = ((ossVersion & 0xFF0000) >> 16); - pContext->oss.versionMinor = ((ossVersion & 0x00FF00) >> 8); +static ma_device_state_opensl* ma_device_get_backend_state__opensl(ma_device* pDevice) +{ + return (ma_device_state_opensl*)ma_device_get_backend_state(pDevice); +} - pCallbacks->onContextInit = ma_context_init__oss; - pCallbacks->onContextUninit = ma_context_uninit__oss; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__oss; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__oss; - pCallbacks->onDeviceInit = ma_device_init__oss; - pCallbacks->onDeviceUninit = ma_device_uninit__oss; - pCallbacks->onDeviceStart = ma_device_start__oss; - pCallbacks->onDeviceStop = ma_device_stop__oss; - pCallbacks->onDeviceRead = ma_device_read__oss; - pCallbacks->onDeviceWrite = ma_device_write__oss; - pCallbacks->onDeviceDataLoop = NULL; - return MA_SUCCESS; +static void ma_backend_info__opensl(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "OpenSL|ES"; } -#endif /* MA_HAS_OSS */ +static ma_result ma_dlsym_SLInterfaceID__opensl(ma_context* pContext, ma_context_state_opensl* pContextStateOpenSL, const char* pName, SLInterfaceID* pHandle) +{ + /* We need to return an error if the symbol cannot be found. This is important because there have been reports that some symbols do not exist. */ + SLInterfaceID* p = (SLInterfaceID*)ma_dlsym(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES, pName); + if (p == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol %s", pName); + return MA_NO_BACKEND; + } + *pHandle = *p; + return MA_SUCCESS; +} +static ma_result ma_context_init_engine_nolock__opensl(ma_context* pContext, ma_context_state_opensl* pContextStateOpenSL) +{ + (void)pContext; -/****************************************************************************** + g_maOpenSLInitCounter += 1; + if (g_maOpenSLInitCounter == 1) { + SLresult resultSL; -AAudio Backend + resultSL = pContextStateOpenSL->slCreateEngine(&g_maEngineObjectSL, 0, NULL, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + g_maOpenSLInitCounter -= 1; + return ma_result_from_OpenSL(resultSL); + } -******************************************************************************/ -#ifdef MA_HAS_AAUDIO + (*g_maEngineObjectSL)->Realize(g_maEngineObjectSL, SL_BOOLEAN_FALSE); -#ifdef MA_NO_RUNTIME_LINKING - #include -#endif + resultSL = (*g_maEngineObjectSL)->GetInterface(g_maEngineObjectSL, pContextStateOpenSL->SL_IID_ENGINE, &g_maEngineSL); + if (resultSL != SL_RESULT_SUCCESS) { + (*g_maEngineObjectSL)->Destroy(g_maEngineObjectSL); + g_maOpenSLInitCounter -= 1; + return ma_result_from_OpenSL(resultSL); + } + } -typedef int32_t ma_aaudio_result_t; -typedef int32_t ma_aaudio_direction_t; -typedef int32_t ma_aaudio_sharing_mode_t; -typedef int32_t ma_aaudio_format_t; -typedef int32_t ma_aaudio_stream_state_t; -typedef int32_t ma_aaudio_performance_mode_t; -typedef int32_t ma_aaudio_usage_t; -typedef int32_t ma_aaudio_content_type_t; -typedef int32_t ma_aaudio_input_preset_t; -typedef int32_t ma_aaudio_allowed_capture_policy_t; -typedef int32_t ma_aaudio_data_callback_result_t; -typedef struct ma_AAudioStreamBuilder_t* ma_AAudioStreamBuilder; -typedef struct ma_AAudioStream_t* ma_AAudioStream; + return MA_SUCCESS; +} -#define MA_AAUDIO_UNSPECIFIED 0 +static ma_result ma_context_init__opensl(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_opensl* pContextStateOpenSL; + const ma_context_config_opensl* pContextConfigAAudio = (const ma_context_config_opensl*)pContextBackendConfig; + ma_context_config_opensl defaultConfigAAudio; + ma_result result; -/* Result codes. miniaudio only cares about the success code. */ -#define MA_AAUDIO_OK 0 + if (pContextConfigAAudio == NULL) { + defaultConfigAAudio = ma_context_config_opensl_init(); + pContextConfigAAudio = &defaultConfigAAudio; + } -/* Directions. */ -#define MA_AAUDIO_DIRECTION_OUTPUT 0 -#define MA_AAUDIO_DIRECTION_INPUT 1 + (void)pContextBackendConfig; -/* Sharing modes. */ -#define MA_AAUDIO_SHARING_MODE_EXCLUSIVE 0 -#define MA_AAUDIO_SHARING_MODE_SHARED 1 + if (ma_android_sdk_version() < MA_OPENSL_MIN_ANDROID_SDK_VERSION) { + return MA_FAILED_TO_INIT_BACKEND; + } -/* Formats. */ -#define MA_AAUDIO_FORMAT_PCM_I16 1 -#define MA_AAUDIO_FORMAT_PCM_FLOAT 2 + pContextStateOpenSL = (ma_context_state_opensl*)ma_calloc(sizeof(*pContextStateOpenSL), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateOpenSL == NULL) { + return MA_OUT_OF_MEMORY; + } -/* Stream states. */ -#define MA_AAUDIO_STREAM_STATE_UNINITIALIZED 0 -#define MA_AAUDIO_STREAM_STATE_UNKNOWN 1 -#define MA_AAUDIO_STREAM_STATE_OPEN 2 -#define MA_AAUDIO_STREAM_STATE_STARTING 3 -#define MA_AAUDIO_STREAM_STATE_STARTED 4 -#define MA_AAUDIO_STREAM_STATE_PAUSING 5 -#define MA_AAUDIO_STREAM_STATE_PAUSED 6 -#define MA_AAUDIO_STREAM_STATE_FLUSHING 7 -#define MA_AAUDIO_STREAM_STATE_FLUSHED 8 -#define MA_AAUDIO_STREAM_STATE_STOPPING 9 -#define MA_AAUDIO_STREAM_STATE_STOPPED 10 -#define MA_AAUDIO_STREAM_STATE_CLOSING 11 -#define MA_AAUDIO_STREAM_STATE_CLOSED 12 -#define MA_AAUDIO_STREAM_STATE_DISCONNECTED 13 + #if !defined(MA_NO_RUNTIME_LINKING) + { + /* + Dynamically link against libOpenSLES.so. I have now had multiple reports that SL_IID_ANDROIDSIMPLEBUFFERQUEUE cannot be found. One + report was happening at compile time and another at runtime. To try working around this, I'm going to link to libOpenSLES at runtime + and extract the symbols rather than reference them directly. This should, hopefully, fix these issues as the compiler won't see any + references to the symbols and will hopefully skip the checks. + */ + size_t i; + const char* libOpenSLESNames[] = { + "libOpenSLES.so" + }; -/* Performance modes. */ -#define MA_AAUDIO_PERFORMANCE_MODE_NONE 10 -#define MA_AAUDIO_PERFORMANCE_MODE_POWER_SAVING 11 -#define MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 12 + for (i = 0; i < ma_countof(libOpenSLESNames); i += 1) { + pContextStateOpenSL->libOpenSLES = ma_dlopen(ma_context_get_log(pContext), libOpenSLESNames[i]); + if (pContextStateOpenSL->libOpenSLES != NULL) { + break; + } + } -/* Usage types. */ -#define MA_AAUDIO_USAGE_MEDIA 1 -#define MA_AAUDIO_USAGE_VOICE_COMMUNICATION 2 -#define MA_AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING 3 -#define MA_AAUDIO_USAGE_ALARM 4 -#define MA_AAUDIO_USAGE_NOTIFICATION 5 -#define MA_AAUDIO_USAGE_NOTIFICATION_RINGTONE 6 -#define MA_AAUDIO_USAGE_NOTIFICATION_EVENT 10 -#define MA_AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY 11 -#define MA_AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE 12 -#define MA_AAUDIO_USAGE_ASSISTANCE_SONIFICATION 13 -#define MA_AAUDIO_USAGE_GAME 14 -#define MA_AAUDIO_USAGE_ASSISTANT 16 -#define MA_AAUDIO_SYSTEM_USAGE_EMERGENCY 1000 -#define MA_AAUDIO_SYSTEM_USAGE_SAFETY 1001 -#define MA_AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS 1002 -#define MA_AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT 1003 + if (pContextStateOpenSL->libOpenSLES == NULL) { + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Could not find libOpenSLES.so"); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return MA_NO_BACKEND; + } -/* Content types. */ -#define MA_AAUDIO_CONTENT_TYPE_SPEECH 1 -#define MA_AAUDIO_CONTENT_TYPE_MUSIC 2 -#define MA_AAUDIO_CONTENT_TYPE_MOVIE 3 -#define MA_AAUDIO_CONTENT_TYPE_SONIFICATION 4 + result = ma_dlsym_SLInterfaceID__opensl(pContext, pContextStateOpenSL, "SL_IID_ENGINE", &pContextStateOpenSL->SL_IID_ENGINE); + if (result != MA_SUCCESS) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return result; + } -/* Input presets. */ -#define MA_AAUDIO_INPUT_PRESET_GENERIC 1 -#define MA_AAUDIO_INPUT_PRESET_CAMCORDER 5 -#define MA_AAUDIO_INPUT_PRESET_VOICE_RECOGNITION 6 -#define MA_AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION 7 -#define MA_AAUDIO_INPUT_PRESET_UNPROCESSED 9 -#define MA_AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE 10 + result = ma_dlsym_SLInterfaceID__opensl(pContext, pContextStateOpenSL, "SL_IID_AUDIOIODEVICECAPABILITIES", &pContextStateOpenSL->SL_IID_AUDIOIODEVICECAPABILITIES); + if (result != MA_SUCCESS) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return result; + } -/* Allowed Capture Policies */ -#define MA_AAUDIO_ALLOW_CAPTURE_BY_ALL 1 -#define MA_AAUDIO_ALLOW_CAPTURE_BY_SYSTEM 2 -#define MA_AAUDIO_ALLOW_CAPTURE_BY_NONE 3 + result = ma_dlsym_SLInterfaceID__opensl(pContext, pContextStateOpenSL, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE", &pContextStateOpenSL->SL_IID_ANDROIDSIMPLEBUFFERQUEUE); + if (result != MA_SUCCESS) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return result; + } -/* Callback results. */ -#define MA_AAUDIO_CALLBACK_RESULT_CONTINUE 0 -#define MA_AAUDIO_CALLBACK_RESULT_STOP 1 + result = ma_dlsym_SLInterfaceID__opensl(pContext, pContextStateOpenSL, "SL_IID_RECORD", &pContextStateOpenSL->SL_IID_RECORD); + if (result != MA_SUCCESS) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return result; + } + result = ma_dlsym_SLInterfaceID__opensl(pContext, pContextStateOpenSL, "SL_IID_PLAY", &pContextStateOpenSL->SL_IID_PLAY); + if (result != MA_SUCCESS) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return result; + } -typedef ma_aaudio_data_callback_result_t (* ma_AAudioStream_dataCallback) (ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t numFrames); -typedef void (* ma_AAudioStream_errorCallback)(ma_AAudioStream *pStream, void *pUserData, ma_aaudio_result_t error); + result = ma_dlsym_SLInterfaceID__opensl(pContext, pContextStateOpenSL, "SL_IID_OUTPUTMIX", &pContextStateOpenSL->SL_IID_OUTPUTMIX); + if (result != MA_SUCCESS) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return result; + } -typedef ma_aaudio_result_t (* MA_PFN_AAudio_createStreamBuilder) (ma_AAudioStreamBuilder** ppBuilder); -typedef ma_aaudio_result_t (* MA_PFN_AAudioStreamBuilder_delete) (ma_AAudioStreamBuilder* pBuilder); -typedef void (* MA_PFN_AAudioStreamBuilder_setDeviceId) (ma_AAudioStreamBuilder* pBuilder, int32_t deviceId); -typedef void (* MA_PFN_AAudioStreamBuilder_setDirection) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_direction_t direction); -typedef void (* MA_PFN_AAudioStreamBuilder_setSharingMode) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_sharing_mode_t sharingMode); -typedef void (* MA_PFN_AAudioStreamBuilder_setFormat) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_format_t format); -typedef void (* MA_PFN_AAudioStreamBuilder_setChannelCount) (ma_AAudioStreamBuilder* pBuilder, int32_t channelCount); -typedef void (* MA_PFN_AAudioStreamBuilder_setSampleRate) (ma_AAudioStreamBuilder* pBuilder, int32_t sampleRate); -typedef void (* MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames)(ma_AAudioStreamBuilder* pBuilder, int32_t numFrames); -typedef void (* MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback) (ma_AAudioStreamBuilder* pBuilder, int32_t numFrames); -typedef void (* MA_PFN_AAudioStreamBuilder_setDataCallback) (ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream_dataCallback callback, void* pUserData); -typedef void (* MA_PFN_AAudioStreamBuilder_setErrorCallback) (ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream_errorCallback callback, void* pUserData); -typedef void (* MA_PFN_AAudioStreamBuilder_setPerformanceMode) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_performance_mode_t mode); -typedef void (* MA_PFN_AAudioStreamBuilder_setUsage) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_usage_t contentType); -typedef void (* MA_PFN_AAudioStreamBuilder_setContentType) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_content_type_t contentType); -typedef void (* MA_PFN_AAudioStreamBuilder_setInputPreset) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_input_preset_t inputPreset); -typedef void (* MA_PFN_AAudioStreamBuilder_setAllowedCapturePolicy) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_allowed_capture_policy_t policy); -typedef ma_aaudio_result_t (* MA_PFN_AAudioStreamBuilder_openStream) (ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream** ppStream); -typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_close) (ma_AAudioStream* pStream); -typedef ma_aaudio_stream_state_t (* MA_PFN_AAudioStream_getState) (ma_AAudioStream* pStream); -typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_waitForStateChange) (ma_AAudioStream* pStream, ma_aaudio_stream_state_t inputState, ma_aaudio_stream_state_t* pNextState, int64_t timeoutInNanoseconds); -typedef ma_aaudio_format_t (* MA_PFN_AAudioStream_getFormat) (ma_AAudioStream* pStream); -typedef int32_t (* MA_PFN_AAudioStream_getChannelCount) (ma_AAudioStream* pStream); -typedef int32_t (* MA_PFN_AAudioStream_getSampleRate) (ma_AAudioStream* pStream); -typedef int32_t (* MA_PFN_AAudioStream_getBufferCapacityInFrames) (ma_AAudioStream* pStream); -typedef int32_t (* MA_PFN_AAudioStream_getFramesPerDataCallback) (ma_AAudioStream* pStream); -typedef int32_t (* MA_PFN_AAudioStream_getFramesPerBurst) (ma_AAudioStream* pStream); -typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_requestStart) (ma_AAudioStream* pStream); -typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_requestStop) (ma_AAudioStream* pStream); + result = ma_dlsym_SLInterfaceID__opensl(pContext, pContextStateOpenSL, "SL_IID_ANDROIDCONFIGURATION", &pContextStateOpenSL->SL_IID_ANDROIDCONFIGURATION); + if (result != MA_SUCCESS) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + return result; + } -static ma_result ma_result_from_aaudio(ma_aaudio_result_t resultAA) -{ - switch (resultAA) + pContextStateOpenSL->slCreateEngine = (ma_slCreateEngine_proc)ma_dlsym(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES, "slCreateEngine"); + if (pContextStateOpenSL->slCreateEngine == NULL) { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol slCreateEngine."); + return MA_NO_BACKEND; + } + } + #else { - case MA_AAUDIO_OK: return MA_SUCCESS; - default: break; + pContextStateOpenSL->SL_IID_ENGINE = SL_IID_ENGINE; + pContextStateOpenSL->SL_IID_AUDIOIODEVICECAPABILITIES = SL_IID_AUDIOIODEVICECAPABILITIES; + pContextStateOpenSL->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + pContextStateOpenSL->SL_IID_RECORD = SL_IID_RECORD; + pContextStateOpenSL->SL_IID_PLAY = SL_IID_PLAY; + pContextStateOpenSL->SL_IID_OUTPUTMIX = SL_IID_OUTPUTMIX; + pContextStateOpenSL->SL_IID_ANDROIDCONFIGURATION = SL_IID_ANDROIDCONFIGURATION; + pContextStateOpenSL->slCreateEngine = slCreateEngine; } + #endif - return MA_ERROR; -} -static ma_aaudio_usage_t ma_to_usage__aaudio(ma_aaudio_usage usage) -{ - switch (usage) { - case ma_aaudio_usage_media: return MA_AAUDIO_USAGE_MEDIA; - case ma_aaudio_usage_voice_communication: return MA_AAUDIO_USAGE_VOICE_COMMUNICATION; - case ma_aaudio_usage_voice_communication_signalling: return MA_AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING; - case ma_aaudio_usage_alarm: return MA_AAUDIO_USAGE_ALARM; - case ma_aaudio_usage_notification: return MA_AAUDIO_USAGE_NOTIFICATION; - case ma_aaudio_usage_notification_ringtone: return MA_AAUDIO_USAGE_NOTIFICATION_RINGTONE; - case ma_aaudio_usage_notification_event: return MA_AAUDIO_USAGE_NOTIFICATION_EVENT; - case ma_aaudio_usage_assistance_accessibility: return MA_AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY; - case ma_aaudio_usage_assistance_navigation_guidance: return MA_AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; - case ma_aaudio_usage_assistance_sonification: return MA_AAUDIO_USAGE_ASSISTANCE_SONIFICATION; - case ma_aaudio_usage_game: return MA_AAUDIO_USAGE_GAME; - case ma_aaudio_usage_assitant: return MA_AAUDIO_USAGE_ASSISTANT; - case ma_aaudio_usage_emergency: return MA_AAUDIO_SYSTEM_USAGE_EMERGENCY; - case ma_aaudio_usage_safety: return MA_AAUDIO_SYSTEM_USAGE_SAFETY; - case ma_aaudio_usage_vehicle_status: return MA_AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS; - case ma_aaudio_usage_announcement: return MA_AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT; - default: break; + /* Initialize global data first if applicable. */ + ma_spinlock_lock(&g_maOpenSLSpinlock); + { + result = ma_context_init_engine_nolock__opensl(pContext, pContextStateOpenSL); } + ma_spinlock_unlock(&g_maOpenSLSpinlock); - return MA_AAUDIO_USAGE_MEDIA; -} + if (result != MA_SUCCESS) { + #if !defined(MA_NO_RUNTIME_LINKING) + { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + } + #endif -static ma_aaudio_content_type_t ma_to_content_type__aaudio(ma_aaudio_content_type contentType) -{ - switch (contentType) { - case ma_aaudio_content_type_speech: return MA_AAUDIO_CONTENT_TYPE_SPEECH; - case ma_aaudio_content_type_music: return MA_AAUDIO_CONTENT_TYPE_MUSIC; - case ma_aaudio_content_type_movie: return MA_AAUDIO_CONTENT_TYPE_MOVIE; - case ma_aaudio_content_type_sonification: return MA_AAUDIO_CONTENT_TYPE_SONIFICATION; - default: break; + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Failed to initialize OpenSL engine."); + return result; } - return MA_AAUDIO_CONTENT_TYPE_SPEECH; + *ppContextState = pContextStateOpenSL; + + return MA_SUCCESS; } -static ma_aaudio_input_preset_t ma_to_input_preset__aaudio(ma_aaudio_input_preset inputPreset) +static void ma_context_uninit__opensl(ma_context* pContext) { - switch (inputPreset) { - case ma_aaudio_input_preset_generic: return MA_AAUDIO_INPUT_PRESET_GENERIC; - case ma_aaudio_input_preset_camcorder: return MA_AAUDIO_INPUT_PRESET_CAMCORDER; - case ma_aaudio_input_preset_voice_recognition: return MA_AAUDIO_INPUT_PRESET_VOICE_RECOGNITION; - case ma_aaudio_input_preset_voice_communication: return MA_AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION; - case ma_aaudio_input_preset_unprocessed: return MA_AAUDIO_INPUT_PRESET_UNPROCESSED; - case ma_aaudio_input_preset_voice_performance: return MA_AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE; - default: break; - } + ma_context_state_opensl* pContextStateOpenSL = ma_context_get_backend_state__opensl(pContext); - return MA_AAUDIO_INPUT_PRESET_GENERIC; -} + /* Uninit global data. */ + ma_spinlock_lock(&g_maOpenSLSpinlock); + { + MA_ASSERT(g_maOpenSLInitCounter > 0); /* If you've triggered this, it means you have ma_context_init/uninit mismatch. Each successful call to ma_context_init() must be matched up with a call to ma_context_uninit(). */ -static ma_aaudio_allowed_capture_policy_t ma_to_allowed_capture_policy__aaudio(ma_aaudio_allowed_capture_policy allowedCapturePolicy) -{ - switch (allowedCapturePolicy) { - case ma_aaudio_allow_capture_by_all: return MA_AAUDIO_ALLOW_CAPTURE_BY_ALL; - case ma_aaudio_allow_capture_by_system: return MA_AAUDIO_ALLOW_CAPTURE_BY_SYSTEM; - case ma_aaudio_allow_capture_by_none: return MA_AAUDIO_ALLOW_CAPTURE_BY_NONE; - default: break; + g_maOpenSLInitCounter -= 1; + if (g_maOpenSLInitCounter == 0) { + (*g_maEngineObjectSL)->Destroy(g_maEngineObjectSL); + } } + ma_spinlock_unlock(&g_maOpenSLSpinlock); - return MA_AAUDIO_ALLOW_CAPTURE_BY_ALL; + #if !defined(MA_NO_RUNTIME_LINKING) + { + ma_dlclose(ma_context_get_log(pContext), pContextStateOpenSL->libOpenSLES); + } + #endif + + ma_free(pContextStateOpenSL, ma_context_get_allocation_callbacks(pContext)); } -static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUserData, ma_aaudio_result_t error) +static void ma_context_add_data_format__opensl(ma_context* pContext, ma_format format, ma_device_info* pDeviceInfo) { - ma_result result; - ma_job job; - ma_device* pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); - - (void)error; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); - /* - When we get an error, we'll assume that the stream is in an erroneous state and needs to be restarted. From the documentation, - we cannot do this from the error callback. Therefore we are going to use an event thread for the AAudio backend to do this - cleanly and safely. + Each sample format can support mono and stereo, and we'll support a small subset of standard + rates (up to 48000). A better solution would be to somehow find a native sample rate. */ - if (ma_atomic_bool32_get(&pDevice->aaudio.isTearingDown)) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Tearing down device.\n"); - } - else { - job = ma_job_init(MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE); - job.data.device.aaudio.reroute.pDevice = pDevice; - - if (pStream == pDevice->aaudio.pStreamCapture) { - job.data.device.aaudio.reroute.deviceType = ma_device_type_capture; - } else { - job.data.device.aaudio.reroute.deviceType = ma_device_type_playback; - } - - result = ma_device_job_thread_post(&pDevice->pContext->aaudio.jobThread, &job); - if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Failed to post job for rerouting.\n"); - return; - } - } + ma_uint32 minChannels = 1; + ma_uint32 maxChannels = 2; + ma_uint32 minSampleRate = (ma_uint32)ma_standard_sample_rate_8000; + ma_uint32 maxSampleRate = (ma_uint32)ma_standard_sample_rate_48000; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pDeviceInfo != NULL); + + ma_device_info_add_native_data_format(pDeviceInfo, format, minChannels, maxChannels, minSampleRate, maxSampleRate); } -static ma_aaudio_data_callback_result_t ma_stream_data_callback_capture__aaudio(ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t frameCount) +static ma_device_enumeration_result ma_context_enumerate_device_from_type__opensl(ma_context* pContext, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_device* pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + ma_device_info deviceInfo; - if (frameCount > 0) { - ma_device_handle_backend_data_callback(pDevice, NULL, pAudioData, (ma_uint32)frameCount); - } + MA_ZERO_OBJECT(&deviceInfo); - (void)pStream; - return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; -} + /* Default. */ + deviceInfo.isDefault = MA_TRUE; -static ma_aaudio_data_callback_result_t ma_stream_data_callback_playback__aaudio(ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t frameCount) -{ - ma_device* pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + /* ID. */ + if (deviceType == ma_device_type_playback) { + deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; + } else { + deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; + } + + /* Name. */ + if (deviceType == ma_device_type_playback) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + /* Data Format. */ /* - I've had a report that AAudio can sometimes post a frame count of 0. We need to check for that here - so we don't get any errors at a deeper level. I'm doing the same with the capture side for safety, - though I've not yet had any reports about that one. + For now we're just outputting a set of values that are supported by the API but not necessarily supported + by the device natively. Later on we should work on this so that it more closely reflects the device's + actual native format. */ - if (frameCount > 0) { - ma_device_handle_backend_data_callback(pDevice, pAudioData, NULL, (ma_uint32)frameCount); - } + deviceInfo.nativeDataFormatCount = 0; +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 + ma_context_add_data_format__opensl(pContext, ma_format_f32, &deviceInfo); +#endif + ma_context_add_data_format__opensl(pContext, ma_format_s16, &deviceInfo); + ma_context_add_data_format__opensl(pContext, ma_format_u8, &deviceInfo); - (void)pStream; - return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; + return callback(deviceType, &deviceInfo, pUserData); } -static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* pContext, const ma_device_id* pDeviceID, ma_device_type deviceType, ma_share_mode shareMode, const ma_device_descriptor* pDescriptor, const ma_device_config* pConfig, ma_device* pDevice, ma_AAudioStreamBuilder** ppBuilder) +static ma_result ma_context_enumerate_devices__opensl(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - ma_AAudioStreamBuilder* pBuilder; - ma_aaudio_result_t resultAA; + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; - /* Safety. */ - *ppBuilder = NULL; + MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); - resultAA = ((MA_PFN_AAudio_createStreamBuilder)pContext->aaudio.AAudio_createStreamBuilder)(&pBuilder); - if (resultAA != MA_AAUDIO_OK) { - return ma_result_from_aaudio(resultAA); + MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to enumerate devices. */ + if (g_maOpenSLInitCounter == 0) { + return MA_INVALID_OPERATION; } - if (pDeviceID != NULL) { - ((MA_PFN_AAudioStreamBuilder_setDeviceId)pContext->aaudio.AAudioStreamBuilder_setDeviceId)(pBuilder, pDeviceID->aaudio); + /* Playback. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type__opensl(pContext, ma_device_type_playback, callback, pUserData); } - ((MA_PFN_AAudioStreamBuilder_setDirection)pContext->aaudio.AAudioStreamBuilder_setDirection)(pBuilder, (deviceType == ma_device_type_playback) ? MA_AAUDIO_DIRECTION_OUTPUT : MA_AAUDIO_DIRECTION_INPUT); - ((MA_PFN_AAudioStreamBuilder_setSharingMode)pContext->aaudio.AAudioStreamBuilder_setSharingMode)(pBuilder, (shareMode == ma_share_mode_shared) ? MA_AAUDIO_SHARING_MODE_SHARED : MA_AAUDIO_SHARING_MODE_EXCLUSIVE); + /* Capture. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type__opensl(pContext, ma_device_type_capture, callback, pUserData); + } + (void)cbResult; - /* If we have a device descriptor make sure we configure the stream builder to take our requested parameters. */ - if (pDescriptor != NULL) { - MA_ASSERT(pConfig != NULL); /* We must have a device config if we also have a descriptor. The config is required for AAudio specific configuration options. */ + return MA_SUCCESS; +} - if (pDescriptor->sampleRate != 0) { - ((MA_PFN_AAudioStreamBuilder_setSampleRate)pContext->aaudio.AAudioStreamBuilder_setSampleRate)(pBuilder, pDescriptor->sampleRate); - } - if (pDescriptor->channels != 0) { - ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); - } +#ifdef MA_ANDROID +/*void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, SLuint32 eventFlags, const void* pBuffer, SLuint32 bufferSize, SLuint32 dataUsed, void* pContext)*/ +static void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_opensl* pDeviceStateOpenSL = ma_device_get_backend_state__opensl(pDevice); + size_t periodSizeInBytes; + ma_uint8* pBuffer; + SLresult resultSL; - if (pDescriptor->format != ma_format_unknown) { - ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); - } + MA_ASSERT(pDevice != NULL); + (void)pBufferQueue; - /* - There have been reports where setting the frames per data callback results in an error. - In particular, re-routing may inadvertently switch from low-latency mode, resulting in a less stable - stream from the legacy path (AudioStreamLegacy). To address this, we simply don't set the value. It - can still be set if it's explicitly requested via the aaudio.allowSetBufferCapacity variable in the - device config. - */ - if ((!pConfig->aaudio.enableCompatibilityWorkarounds || ma_android_sdk_version() > 30) && pConfig->aaudio.allowSetBufferCapacity) { - /* - AAudio is annoying when it comes to its buffer calculation stuff because it doesn't let you - retrieve the actual sample rate until after you've opened the stream. But you need to configure - the buffer capacity before you open the stream... :/ + periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + pBuffer = pDeviceStateOpenSL->pBufferCapture + (pDeviceStateOpenSL->currentBufferIndexCapture * periodSizeInBytes); - To solve, we're just going to assume MA_DEFAULT_SAMPLE_RATE (48000) and move on. - */ - ma_uint32 bufferCapacityInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile) * pDescriptor->periodCount; + ma_device_state_async_process(&pDeviceStateOpenSL->async, pDevice, NULL, pBuffer, pDevice->capture.internalPeriodSizeInFrames); - ((MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames)pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames)(pBuilder, bufferCapacityInFrames); - ((MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback)pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback)(pBuilder, bufferCapacityInFrames / pDescriptor->periodCount); + if (!pDeviceStateOpenSL->isDrainingCapture) { + resultSL = MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueueCapture)->Enqueue(pDeviceStateOpenSL->pBufferQueueCapture, pBuffer, periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + return; } + } - if (deviceType == ma_device_type_capture) { - if (pConfig->aaudio.inputPreset != ma_aaudio_input_preset_default && pContext->aaudio.AAudioStreamBuilder_setInputPreset != NULL) { - ((MA_PFN_AAudioStreamBuilder_setInputPreset)pContext->aaudio.AAudioStreamBuilder_setInputPreset)(pBuilder, ma_to_input_preset__aaudio(pConfig->aaudio.inputPreset)); - } + pDeviceStateOpenSL->currentBufferIndexCapture = (pDeviceStateOpenSL->currentBufferIndexCapture + 1) % pDevice->capture.internalPeriods; +} - ((MA_PFN_AAudioStreamBuilder_setDataCallback)pContext->aaudio.AAudioStreamBuilder_setDataCallback)(pBuilder, ma_stream_data_callback_capture__aaudio, (void*)pDevice); - } else { - if (pConfig->aaudio.usage != ma_aaudio_usage_default && pContext->aaudio.AAudioStreamBuilder_setUsage != NULL) { - ((MA_PFN_AAudioStreamBuilder_setUsage)pContext->aaudio.AAudioStreamBuilder_setUsage)(pBuilder, ma_to_usage__aaudio(pConfig->aaudio.usage)); - } +static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_opensl* pDeviceStateOpenSL = ma_device_get_backend_state__opensl(pDevice); + size_t periodSizeInBytes; + ma_uint8* pBuffer; + SLresult resultSL; - if (pConfig->aaudio.contentType != ma_aaudio_content_type_default && pContext->aaudio.AAudioStreamBuilder_setContentType != NULL) { - ((MA_PFN_AAudioStreamBuilder_setContentType)pContext->aaudio.AAudioStreamBuilder_setContentType)(pBuilder, ma_to_content_type__aaudio(pConfig->aaudio.contentType)); - } + MA_ASSERT(pDevice != NULL); - if (pConfig->aaudio.allowedCapturePolicy != ma_aaudio_allow_capture_default && pContext->aaudio.AAudioStreamBuilder_setAllowedCapturePolicy != NULL) { - ((MA_PFN_AAudioStreamBuilder_setAllowedCapturePolicy)pContext->aaudio.AAudioStreamBuilder_setAllowedCapturePolicy)(pBuilder, ma_to_allowed_capture_policy__aaudio(pConfig->aaudio.allowedCapturePolicy)); - } + (void)pBufferQueue; - ((MA_PFN_AAudioStreamBuilder_setDataCallback)pContext->aaudio.AAudioStreamBuilder_setDataCallback)(pBuilder, ma_stream_data_callback_playback__aaudio, (void*)pDevice); - } + periodSizeInBytes = pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + pBuffer = pDeviceStateOpenSL->pBufferPlayback + (pDeviceStateOpenSL->currentBufferIndexPlayback * periodSizeInBytes); - /* - If we set AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, we allow for MMAP (non-legacy path). - Since there's a mapping between miniaudio's performance profiles and AAudio's performance modes, let's use it. - Beware though, with a conservative performance profile, AAudio will indeed take the legacy path. - */ - ((MA_PFN_AAudioStreamBuilder_setPerformanceMode)pContext->aaudio.AAudioStreamBuilder_setPerformanceMode)(pBuilder, (pConfig->performanceProfile == ma_performance_profile_low_latency) ? MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY : MA_AAUDIO_PERFORMANCE_MODE_NONE); + ma_device_state_async_process(&pDeviceStateOpenSL->async, pDevice, pBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); - /* We need to set an error callback to detect device changes. */ - if (pDevice != NULL) { /* <-- pDevice should never be null if pDescriptor is not null, which is always the case if we hit this branch. Check anyway for safety. */ - ((MA_PFN_AAudioStreamBuilder_setErrorCallback)pContext->aaudio.AAudioStreamBuilder_setErrorCallback)(pBuilder, ma_stream_error_callback__aaudio, (void*)pDevice); + if (!pDeviceStateOpenSL->isDrainingPlayback) { + resultSL = MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueuePlayback)->Enqueue(pDeviceStateOpenSL->pBufferQueuePlayback, pBuffer, periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + return; } } - *ppBuilder = pBuilder; - - return MA_SUCCESS; + pDeviceStateOpenSL->currentBufferIndexPlayback = (pDeviceStateOpenSL->currentBufferIndexPlayback + 1) % pDevice->playback.internalPeriods; } +#endif -static ma_result ma_open_stream_and_close_builder__aaudio(ma_context* pContext, ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream** ppStream) -{ - ma_result result; - - result = ma_result_from_aaudio(((MA_PFN_AAudioStreamBuilder_openStream)pContext->aaudio.AAudioStreamBuilder_openStream)(pBuilder, ppStream)); - ((MA_PFN_AAudioStreamBuilder_delete)pContext->aaudio.AAudioStreamBuilder_delete)(pBuilder); - return result; -} +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 +typedef SLAndroidDataFormat_PCM_EX ma_SLDataFormat_PCM; +#else +typedef SLDataFormat_PCM ma_SLDataFormat_PCM; +#endif -static ma_result ma_open_stream_basic__aaudio(ma_context* pContext, const ma_device_id* pDeviceID, ma_device_type deviceType, ma_share_mode shareMode, ma_AAudioStream** ppStream) +static ma_result ma_SLDataFormat_PCM_init__opensl(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const ma_channel* channelMap, ma_SLDataFormat_PCM* pDataFormat) { - ma_result result; - ma_AAudioStreamBuilder* pBuilder; + /* We need to convert our format/channels/rate so that they aren't set to default. */ + if (format == ma_format_unknown) { + format = MA_DEFAULT_FORMAT; + } + if (channels == 0) { + channels = MA_DEFAULT_CHANNELS; + } + if (sampleRate == 0) { + sampleRate = MA_DEFAULT_SAMPLE_RATE; + } - *ppStream = NULL; +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 + if (format == ma_format_f32) { + pDataFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX; + pDataFormat->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; + } else { + pDataFormat->formatType = SL_DATAFORMAT_PCM; + } +#else + pDataFormat->formatType = SL_DATAFORMAT_PCM; +#endif - result = ma_create_and_configure_AAudioStreamBuilder__aaudio(pContext, pDeviceID, deviceType, shareMode, NULL, NULL, NULL, &pBuilder); - if (result != MA_SUCCESS) { - return result; + pDataFormat->numChannels = channels; + ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate * 1000); /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ + pDataFormat->bitsPerSample = ma_get_bytes_per_sample(format) * 8; + pDataFormat->channelMask = ma_channel_map_to_channel_mask__opensl(channelMap, channels); + pDataFormat->endianness = (ma_is_little_endian()) ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; + + /* + Android has a few restrictions on the format as documented here: https://developer.android.com/ndk/guides/audio/opensl-for-android.html + - Only mono and stereo is supported. + - Only u8 and s16 formats are supported. + - Maximum sample rate of 48000. + */ +#ifdef MA_ANDROID + if (pDataFormat->numChannels > 2) { + pDataFormat->numChannels = 2; + } +#if __ANDROID_API__ >= 21 + if (pDataFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { + /* It's floating point. */ + MA_ASSERT(pDataFormat->representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); + if (pDataFormat->bitsPerSample > 32) { + pDataFormat->bitsPerSample = 32; + } + } else { + if (pDataFormat->bitsPerSample > 16) { + pDataFormat->bitsPerSample = 16; + } + } +#else + if (pDataFormat->bitsPerSample > 16) { + pDataFormat->bitsPerSample = 16; + } +#endif + if (((SLDataFormat_PCM*)pDataFormat)->samplesPerSec > SL_SAMPLINGRATE_48) { + ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = SL_SAMPLINGRATE_48; } +#endif - /* Let's give AAudio a hint to avoid the legacy path (AudioStreamLegacy). */ - ((MA_PFN_AAudioStreamBuilder_setPerformanceMode)pContext->aaudio.AAudioStreamBuilder_setPerformanceMode)(pBuilder, MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); + pDataFormat->containerSize = pDataFormat->bitsPerSample; /* Always tightly packed for now. */ - return ma_open_stream_and_close_builder__aaudio(pContext, pBuilder, ppStream); + return MA_SUCCESS; } -static ma_result ma_open_stream__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_type deviceType, const ma_device_descriptor* pDescriptor, ma_AAudioStream** ppStream) +static ma_result ma_deconstruct_SLDataFormat_PCM__opensl(ma_SLDataFormat_PCM* pDataFormat, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - ma_result result; - ma_AAudioStreamBuilder* pBuilder; + ma_bool32 isFloatingPoint = MA_FALSE; +#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 + if (pDataFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { + MA_ASSERT(pDataFormat->representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); + isFloatingPoint = MA_TRUE; + } +#endif + if (isFloatingPoint) { + if (pDataFormat->bitsPerSample == 32) { + *pFormat = ma_format_f32; + } + } else { + if (pDataFormat->bitsPerSample == 8) { + *pFormat = ma_format_u8; + } else if (pDataFormat->bitsPerSample == 16) { + *pFormat = ma_format_s16; + } else if (pDataFormat->bitsPerSample == 24) { + *pFormat = ma_format_s24; + } else if (pDataFormat->bitsPerSample == 32) { + *pFormat = ma_format_s32; + } + } - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pDescriptor != NULL); - MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ + *pChannels = pDataFormat->numChannels; + *pSampleRate = ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec / 1000; + ma_channel_mask_to_channel_map__opensl(pDataFormat->channelMask, ma_min(pDataFormat->numChannels, channelMapCap), pChannelMap); - *ppStream = NULL; + return MA_SUCCESS; +} - result = ma_create_and_configure_AAudioStreamBuilder__aaudio(pDevice->pContext, pDescriptor->pDeviceID, deviceType, pDescriptor->shareMode, pDescriptor, pConfig, pDevice, &pBuilder); - if (result != MA_SUCCESS) { - return result; - } - return ma_open_stream_and_close_builder__aaudio(pDevice->pContext, pBuilder, ppStream); -} +static void ma_device_uninit__opensl(ma_device* pDevice); -static ma_result ma_close_stream__aaudio(ma_context* pContext, ma_AAudioStream* pStream) +static ma_result ma_device_init__opensl(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - if (pStream == NULL) { - return MA_INVALID_ARGS; - } + ma_device_state_opensl* pDeviceStateOpenSL; + ma_context_state_opensl* pContextStateOpenSL = ma_context_get_backend_state__opensl(ma_device_get_context(pDevice)); + ma_device_config_opensl* pDeviceConfigOpenSL = (ma_device_config_opensl*)pDeviceBackendConfig; + ma_device_config_opensl defaultConfigOpenSL; + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; - return ma_result_from_aaudio(((MA_PFN_AAudioStream_close)pContext->aaudio.AAudioStream_close)(pStream)); -} +#ifdef MA_ANDROID + SLDataLocator_AndroidSimpleBufferQueue queue; + SLresult resultSL; + size_t bufferSizeInBytesPlayback = 0; + size_t bufferSizeInBytesCapture = 0; + SLInterfaceID itfIDs[2]; + const SLboolean itfIDsRequired[] = { + SL_BOOLEAN_TRUE, /* SL_IID_ANDROIDSIMPLEBUFFERQUEUE */ + SL_BOOLEAN_FALSE /* SL_IID_ANDROIDCONFIGURATION */ + }; +#endif -static ma_bool32 ma_has_default_device__aaudio(ma_context* pContext, ma_device_type deviceType) -{ - /* The only way to know this is to try creating a stream. */ - ma_AAudioStream* pStream; - ma_result result = ma_open_stream_basic__aaudio(pContext, NULL, deviceType, ma_share_mode_shared, &pStream); - if (result != MA_SUCCESS) { - return MA_FALSE; + if (pDeviceConfigOpenSL == NULL) { + defaultConfigOpenSL = ma_device_config_opensl_init(); + pDeviceConfigOpenSL = &defaultConfigOpenSL; } - ma_close_stream__aaudio(pContext, pStream); - return MA_TRUE; -} + MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to initialize a new device. */ + if (g_maOpenSLInitCounter == 0) { + return MA_INVALID_OPERATION; + } -static ma_result ma_wait_for_simple_state_transition__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_aaudio_stream_state_t oldState, ma_aaudio_stream_state_t newState) -{ - ma_aaudio_stream_state_t actualNewState; - ma_aaudio_result_t resultAA = ((MA_PFN_AAudioStream_waitForStateChange)pContext->aaudio.AAudioStream_waitForStateChange)(pStream, oldState, &actualNewState, 5000000000); /* 5 second timeout. */ - if (resultAA != MA_AAUDIO_OK) { - return ma_result_from_aaudio(resultAA); + if (deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - if (newState != actualNewState) { - return MA_ERROR; /* Failed to transition into the expected state. */ + /* + For now, only supporting Android implementations of OpenSL|ES since that's the only one I've + been able to test with and I currently depend on Android-specific extensions (simple buffer + queues). + */ +#ifdef MA_ANDROID + itfIDs[0] = pContextStateOpenSL->SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + itfIDs[1] = pContextStateOpenSL->SL_IID_ANDROIDCONFIGURATION; + + /* No exclusive mode with OpenSL|ES. */ + if (((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + return MA_SHARE_MODE_NOT_SUPPORTED; } - return MA_SUCCESS; -} + /* Now we can start initializing the device properly. */ + pDeviceStateOpenSL = (ma_device_state_opensl*)ma_calloc(sizeof(*pDeviceStateOpenSL), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateOpenSL == NULL) { + return MA_OUT_OF_MEMORY; + } + queue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; -static ma_result ma_context_enumerate_devices__aaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_bool32 cbResult = MA_TRUE; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_SLDataFormat_PCM pcm; + SLDataLocator_IODevice locatorDevice; + SLDataSource source; + SLDataSink sink; + SLAndroidConfigurationItf pRecorderConfig; - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); + ma_SLDataFormat_PCM_init__opensl(pDescriptorCapture->format, pDescriptorCapture->channels, pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, &pcm); - /* Unfortunately AAudio does not have an enumeration API. Therefore I'm only going to report default devices, but only if it can instantiate a stream. */ + locatorDevice.locatorType = SL_DATALOCATOR_IODEVICE; + locatorDevice.deviceType = SL_IODEVICE_AUDIOINPUT; + locatorDevice.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; /* Must always use the default device with Android. */ + locatorDevice.device = NULL; - /* Playback. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.aaudio = MA_AAUDIO_UNSPECIFIED; - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + source.pLocator = &locatorDevice; + source.pFormat = NULL; - if (ma_has_default_device__aaudio(pContext, ma_device_type_playback)) { - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - } - } + queue.numBuffers = pDescriptorCapture->periodCount; - /* Capture. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.aaudio = MA_AAUDIO_UNSPECIFIED; - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + sink.pLocator = &queue; + sink.pFormat = (SLDataFormat_PCM*)&pcm; - if (ma_has_default_device__aaudio(pContext, ma_device_type_capture)) { - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDeviceStateOpenSL->pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { + /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ + pcm.formatType = SL_DATAFORMAT_PCM; + pcm.numChannels = 1; + ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; /* The name of the sample rate variable is different between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM. */ + pcm.bitsPerSample = 16; + pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ + pcm.channelMask = 0; + resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDeviceStateOpenSL->pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); } - } - return MA_SUCCESS; -} - -static void ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_format format, ma_uint32 flags, ma_device_info* pDeviceInfo) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pStream != NULL); - MA_ASSERT(pDeviceInfo != NULL); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder."); + return ma_result_from_OpenSL(resultSL); + } - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = ((MA_PFN_AAudioStream_getChannelCount)pContext->aaudio.AAudioStream_getChannelCount)(pStream); - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = ((MA_PFN_AAudioStream_getSampleRate)pContext->aaudio.AAudioStream_getSampleRate)(pStream); - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags; - pDeviceInfo->nativeDataFormatCount += 1; -} -static void ma_context_add_native_data_format_from_AAudioStream__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_uint32 flags, ma_device_info* pDeviceInfo) -{ - /* AAudio supports s16 and f32. */ - ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(pContext, pStream, ma_format_f32, flags, pDeviceInfo); - ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(pContext, pStream, ma_format_s16, flags, pDeviceInfo); -} + /* Set the recording preset before realizing the player. */ + if (pDeviceConfigOpenSL->recordingPreset != ma_opensl_recording_preset_default) { + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioRecorderObj)->GetInterface(pDeviceStateOpenSL->pAudioRecorderObj, pContextStateOpenSL->SL_IID_ANDROIDCONFIGURATION, &pRecorderConfig); + if (resultSL == SL_RESULT_SUCCESS) { + SLint32 recordingPreset = ma_to_recording_preset__opensl(pDeviceConfigOpenSL->recordingPreset); + resultSL = (*pRecorderConfig)->SetConfiguration(pRecorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, &recordingPreset, sizeof(SLint32)); + if (resultSL != SL_RESULT_SUCCESS) { + /* Failed to set the configuration. Just keep going. */ + } + } + } -static ma_result ma_context_get_device_info__aaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) -{ - ma_AAudioStream* pStream; - ma_result result; + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioRecorderObj)->Realize((SLObjectItf)pDeviceStateOpenSL->pAudioRecorderObj, SL_BOOLEAN_FALSE); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder."); + return ma_result_from_OpenSL(resultSL); + } - MA_ASSERT(pContext != NULL); + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioRecorderObj)->GetInterface((SLObjectItf)pDeviceStateOpenSL->pAudioRecorderObj, pContextStateOpenSL->SL_IID_RECORD, &pDeviceStateOpenSL->pAudioRecorder); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface."); + return ma_result_from_OpenSL(resultSL); + } - /* ID */ - if (pDeviceID != NULL) { - pDeviceInfo->id.aaudio = pDeviceID->aaudio; - } else { - pDeviceInfo->id.aaudio = MA_AAUDIO_UNSPECIFIED; - } + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioRecorderObj)->GetInterface(pDeviceStateOpenSL->pAudioRecorderObj, pContextStateOpenSL->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDeviceStateOpenSL->pBufferQueueCapture); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); + } - /* Name */ - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } + resultSL = MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueueCapture)->RegisterCallback(pDeviceStateOpenSL->pBufferQueueCapture, ma_buffer_queue_callback_capture__opensl_android, pDevice); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); + } + /* The internal format is determined by the "pcm" object. */ + ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDescriptorCapture->format, &pDescriptorCapture->channels, &pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap)); - pDeviceInfo->nativeDataFormatCount = 0; + /* Buffer. */ + pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate); + pDeviceStateOpenSL->currentBufferIndexCapture = 0; - /* We'll need to open the device to get accurate sample rate and channel count information. */ - result = ma_open_stream_basic__aaudio(pContext, pDeviceID, deviceType, ma_share_mode_shared, &pStream); - if (result != MA_SUCCESS) { - return result; + bufferSizeInBytesCapture = ma_align_64(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodCount); } - ma_context_add_native_data_format_from_AAudioStream__aaudio(pContext, pStream, 0, pDeviceInfo); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_SLDataFormat_PCM pcm; + SLDataSource source; + SLDataLocator_OutputMix outmixLocator; + SLDataSink sink; + SLAndroidConfigurationItf pPlayerConfig; - ma_close_stream__aaudio(pContext, pStream); - pStream = NULL; + ma_SLDataFormat_PCM_init__opensl(pDescriptorPlayback->format, pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate, pDescriptorPlayback->channelMap, &pcm); - return MA_SUCCESS; -} + resultSL = (*g_maEngineSL)->CreateOutputMix(g_maEngineSL, (SLObjectItf*)&pDeviceStateOpenSL->pOutputMixObj, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix."); + return ma_result_from_OpenSL(resultSL); + } -static ma_result ma_close_streams__aaudio(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pOutputMixObj)->Realize(pDeviceStateOpenSL->pOutputMixObj, SL_BOOLEAN_FALSE); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object."); + return ma_result_from_OpenSL(resultSL); + } - /* When rerouting, streams may have been closed and never re-opened. Hence the extra checks below. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - pDevice->aaudio.pStreamCapture = NULL; - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - pDevice->aaudio.pStreamPlayback = NULL; - } + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pOutputMixObj)->GetInterface(pDeviceStateOpenSL->pOutputMixObj, pContextStateOpenSL->SL_IID_OUTPUTMIX, &pDeviceStateOpenSL->pOutputMix); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface."); + return ma_result_from_OpenSL(resultSL); + } - return MA_SUCCESS; -} + /* Set the output device. */ + if (pDescriptorPlayback->pDeviceID != NULL) { + SLuint32 deviceID_OpenSL = pDescriptorPlayback->pDeviceID->opensl; + MA_OPENSL_OUTPUTMIX(pDeviceStateOpenSL->pOutputMix)->ReRoute((SLOutputMixItf)pDeviceStateOpenSL->pOutputMix, 1, &deviceID_OpenSL); + } -static ma_result ma_device_uninit__aaudio(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + queue.numBuffers = pDescriptorPlayback->periodCount; - /* - Note: Closing the streams may cause a timeout error, which would then trigger rerouting in our error callback. - We must not schedule a reroute when device is getting destroyed. - */ - ma_atomic_bool32_set(&pDevice->aaudio.isTearingDown, MA_TRUE); + source.pLocator = &queue; + source.pFormat = (SLDataFormat_PCM*)&pcm; - /* Wait for any rerouting to finish before attempting to close the streams. */ - ma_mutex_lock(&pDevice->aaudio.rerouteLock); - { - ma_close_streams__aaudio(pDevice); - } - ma_mutex_unlock(&pDevice->aaudio.rerouteLock); + outmixLocator.locatorType = SL_DATALOCATOR_OUTPUTMIX; + outmixLocator.outputMix = (SLObjectItf)pDeviceStateOpenSL->pOutputMixObj; - /* Destroy rerouting lock. */ - ma_mutex_uninit(&pDevice->aaudio.rerouteLock); + sink.pLocator = &outmixLocator; + sink.pFormat = NULL; - return MA_SUCCESS; -} + resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDeviceStateOpenSL->pAudioPlayerObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { + /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ + pcm.formatType = SL_DATAFORMAT_PCM; + pcm.numChannels = 2; + ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; + pcm.bitsPerSample = 16; + pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ + pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDeviceStateOpenSL->pAudioPlayerObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); + } -static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_type deviceType, ma_device_descriptor* pDescriptor, ma_AAudioStream** ppStream) -{ - ma_result result; - int32_t bufferCapacityInFrames; - int32_t framesPerDataCallback; - ma_AAudioStream* pStream; + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player."); + return ma_result_from_OpenSL(resultSL); + } - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDescriptor != NULL); - *ppStream = NULL; /* Safety. */ + /* Set the stream type before realizing the player. */ + if (pDeviceConfigOpenSL->streamType != ma_opensl_stream_type_default) { + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioPlayerObj)->GetInterface(pDeviceStateOpenSL->pAudioPlayerObj, pContextStateOpenSL->SL_IID_ANDROIDCONFIGURATION, &pPlayerConfig); + if (resultSL == SL_RESULT_SUCCESS) { + SLint32 streamType = ma_to_stream_type__opensl(pDeviceConfigOpenSL->streamType); + resultSL = (*pPlayerConfig)->SetConfiguration(pPlayerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); + if (resultSL != SL_RESULT_SUCCESS) { + /* Failed to set the configuration. Just keep going. */ + } + } + } - /* First step is to open the stream. From there we'll be able to extract the internal configuration. */ - result = ma_open_stream__aaudio(pDevice, pConfig, deviceType, pDescriptor, &pStream); - if (result != MA_SUCCESS) { - return result; /* Failed to open the AAudio stream. */ - } + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioPlayerObj)->Realize(pDeviceStateOpenSL->pAudioPlayerObj, SL_BOOLEAN_FALSE); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player."); + return ma_result_from_OpenSL(resultSL); + } - /* Now extract the internal configuration. */ - pDescriptor->format = (((MA_PFN_AAudioStream_getFormat)pDevice->pContext->aaudio.AAudioStream_getFormat)(pStream) == MA_AAUDIO_FORMAT_PCM_I16) ? ma_format_s16 : ma_format_f32; - pDescriptor->channels = ((MA_PFN_AAudioStream_getChannelCount)pDevice->pContext->aaudio.AAudioStream_getChannelCount)(pStream); - pDescriptor->sampleRate = ((MA_PFN_AAudioStream_getSampleRate)pDevice->pContext->aaudio.AAudioStream_getSampleRate)(pStream); + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioPlayerObj)->GetInterface(pDeviceStateOpenSL->pAudioPlayerObj, pContextStateOpenSL->SL_IID_PLAY, &pDeviceStateOpenSL->pAudioPlayer); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface."); + return ma_result_from_OpenSL(resultSL); + } - /* For the channel map we need to be sure we don't overflow any buffers. */ - if (pDescriptor->channels <= MA_MAX_CHANNELS) { - ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); /* <-- Cannot find info on channel order, so assuming a default. */ - } else { - ma_channel_map_init_blank(pDescriptor->channelMap, MA_MAX_CHANNELS); /* Too many channels. Use a blank channel map. */ - } + resultSL = MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioPlayerObj)->GetInterface(pDeviceStateOpenSL->pAudioPlayerObj, pContextStateOpenSL->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDeviceStateOpenSL->pBufferQueuePlayback); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); + } - bufferCapacityInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pDevice->pContext->aaudio.AAudioStream_getBufferCapacityInFrames)(pStream); - framesPerDataCallback = ((MA_PFN_AAudioStream_getFramesPerDataCallback)pDevice->pContext->aaudio.AAudioStream_getFramesPerDataCallback)(pStream); + resultSL = MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueuePlayback)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDeviceStateOpenSL->pBufferQueuePlayback, ma_buffer_queue_callback_playback__opensl_android, pDevice); + if (resultSL != SL_RESULT_SUCCESS) { + ma_device_uninit__opensl(pDevice); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); + } - if (framesPerDataCallback > 0) { - pDescriptor->periodSizeInFrames = framesPerDataCallback; - pDescriptor->periodCount = bufferCapacityInFrames / framesPerDataCallback; - } else { - pDescriptor->periodSizeInFrames = bufferCapacityInFrames; - pDescriptor->periodCount = 1; - } + /* The internal format is determined by the "pcm" object. */ + ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDescriptorPlayback->format, &pDescriptorPlayback->channels, &pDescriptorPlayback->sampleRate, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap)); - *ppStream = pStream; + /* Buffer. */ + pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate); + pDeviceStateOpenSL->currentBufferIndexPlayback = 0; - return MA_SUCCESS; -} + bufferSizeInBytesPlayback = ma_align_64(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodCount); + } -static ma_result ma_device_init_streams__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ - ma_result result; + /* The device state allocation needs to be resized to accomodate our buffers. */ + { + size_t newDeviceStateAllocSize; + ma_device_state_opensl* pNewDeviceStateOpenSL; - MA_ASSERT(pDevice != NULL); + newDeviceStateAllocSize = 0; + newDeviceStateAllocSize += ma_align_64(sizeof(*pDeviceStateOpenSL)); + newDeviceStateAllocSize += bufferSizeInBytesCapture; + newDeviceStateAllocSize += bufferSizeInBytesPlayback; - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + pNewDeviceStateOpenSL = (ma_device_state_opensl*)ma_realloc(pDeviceStateOpenSL, newDeviceStateAllocSize, ma_device_get_allocation_callbacks(pDevice)); + if (pNewDeviceStateOpenSL == NULL) { + ma_device_uninit__opensl(pDevice); + return MA_OUT_OF_MEMORY; + } - pDevice->aaudio.usage = pConfig->aaudio.usage; - pDevice->aaudio.contentType = pConfig->aaudio.contentType; - pDevice->aaudio.inputPreset = pConfig->aaudio.inputPreset; - pDevice->aaudio.allowedCapturePolicy = pConfig->aaudio.allowedCapturePolicy; - pDevice->aaudio.noAutoStartAfterReroute = pConfig->aaudio.noAutoStartAfterReroute; + pDeviceStateOpenSL = pNewDeviceStateOpenSL; - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_by_type__aaudio(pDevice, pConfig, ma_device_type_capture, pDescriptorCapture, (ma_AAudioStream**)&pDevice->aaudio.pStreamCapture); - if (result != MA_SUCCESS) { - return result; + if (bufferSizeInBytesCapture > 0) { + pDeviceStateOpenSL->pBufferCapture = (ma_uint8*)ma_offset_ptr(pDeviceStateOpenSL, ma_align_64(sizeof(*pDeviceStateOpenSL))); + MA_ZERO_MEMORY(pDeviceStateOpenSL->pBufferCapture, bufferSizeInBytesCapture); } - } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_by_type__aaudio(pDevice, pConfig, ma_device_type_playback, pDescriptorPlayback, (ma_AAudioStream**)&pDevice->aaudio.pStreamPlayback); - if (result != MA_SUCCESS) { - return result; + if (bufferSizeInBytesPlayback > 0) { + pDeviceStateOpenSL->pBufferPlayback = (ma_uint8*)ma_offset_ptr(pDeviceStateOpenSL, ma_align_64(sizeof(*pDeviceStateOpenSL)) + bufferSizeInBytesCapture); + MA_ZERO_MEMORY(pDeviceStateOpenSL->pBufferPlayback, bufferSizeInBytesPlayback); } } - return MA_SUCCESS; -} - -static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ - ma_result result; - - MA_ASSERT(pDevice != NULL); - - result = ma_device_init_streams__aaudio(pDevice, pConfig, pDescriptorPlayback, pDescriptorCapture); + result = ma_device_state_async_init(deviceType, pDescriptorPlayback, pDescriptorCapture, ma_device_get_allocation_callbacks(pDevice), &pDeviceStateOpenSL->async); if (result != MA_SUCCESS) { + ma_device_uninit__opensl(pDevice); return result; } - result = ma_mutex_init(&pDevice->aaudio.rerouteLock); - if (result != MA_SUCCESS) { - return result; - } + *ppDeviceState = pDeviceStateOpenSL; return MA_SUCCESS; +#else + return MA_NO_BACKEND; /* Non-Android implementations are not supported. */ +#endif } -static ma_result ma_device_start_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) +static void ma_device_uninit__opensl(ma_device* pDevice) { - ma_aaudio_result_t resultAA; - ma_aaudio_stream_state_t currentState; + ma_device_state_opensl* pDeviceStateOpenSL = ma_device_get_backend_state__opensl(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); MA_ASSERT(pDevice != NULL); - if (pStream == NULL) { - return MA_INVALID_ARGS; + MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it before uninitializing the device. */ + if (g_maOpenSLInitCounter == 0) { + return; } - resultAA = ((MA_PFN_AAudioStream_requestStart)pDevice->pContext->aaudio.AAudioStream_requestStart)(pStream); - if (resultAA != MA_AAUDIO_OK) { - return ma_result_from_aaudio(resultAA); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (pDeviceStateOpenSL->pAudioRecorderObj) { + MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioRecorderObj)->Destroy(pDeviceStateOpenSL->pAudioRecorderObj); + } } - /* Do we actually need to wait for the device to transition into its started state? */ - - /* The device should be in either a starting or started state. If it's not set to started we need to wait for it to transition. It should go from starting to started. */ - currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); - if (currentState != MA_AAUDIO_STREAM_STATE_STARTED) { - ma_result result; - - if (currentState != MA_AAUDIO_STREAM_STATE_STARTING) { - return MA_ERROR; /* Expecting the stream to be a starting or started state. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (pDeviceStateOpenSL->pAudioPlayerObj) { + MA_OPENSL_OBJ(pDeviceStateOpenSL->pAudioPlayerObj)->Destroy(pDeviceStateOpenSL->pAudioPlayerObj); } - - result = ma_wait_for_simple_state_transition__aaudio(pDevice->pContext, pStream, currentState, MA_AAUDIO_STREAM_STATE_STARTED); - if (result != MA_SUCCESS) { - return result; + if (pDeviceStateOpenSL->pOutputMixObj) { + MA_OPENSL_OBJ(pDeviceStateOpenSL->pOutputMixObj)->Destroy(pDeviceStateOpenSL->pOutputMixObj); } } - return MA_SUCCESS; + ma_device_state_async_uninit(&pDeviceStateOpenSL->async, ma_device_get_allocation_callbacks(pDevice)); + + ma_free(pDeviceStateOpenSL, ma_device_get_allocation_callbacks(pDevice)); } -static ma_result ma_device_stop_stream__aaudio(ma_device* pDevice, ma_AAudioStream* pStream) +static ma_result ma_device_start__opensl(ma_device* pDevice) { - ma_aaudio_result_t resultAA; - ma_aaudio_stream_state_t currentState; + ma_device_state_opensl* pDeviceStateOpenSL = ma_device_get_backend_state__opensl(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + SLresult resultSL; + size_t periodSizeInBytes; + ma_uint32 iPeriod; MA_ASSERT(pDevice != NULL); - if (pStream == NULL) { - return MA_INVALID_ARGS; - } - - /* - From the AAudio documentation: - - The stream will stop after all of the data currently buffered has been played. - - This maps with miniaudio's requirement that device's be drained which means we don't need to implement any draining logic. - */ - currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); - if (currentState == MA_AAUDIO_STREAM_STATE_DISCONNECTED) { - return MA_SUCCESS; /* The device is disconnected. Don't try stopping it. */ - } - - resultAA = ((MA_PFN_AAudioStream_requestStop)pDevice->pContext->aaudio.AAudioStream_requestStop)(pStream); - if (resultAA != MA_AAUDIO_OK) { - return ma_result_from_aaudio(resultAA); + MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to start the device. */ + if (g_maOpenSLInitCounter == 0) { + return MA_INVALID_OPERATION; } - /* The device should be in either a stopping or stopped state. If it's not set to started we need to wait for it to transition. It should go from stopping to stopped. */ - currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); - if (currentState != MA_AAUDIO_STREAM_STATE_STOPPED) { - ma_result result; - - if (currentState != MA_AAUDIO_STREAM_STATE_STOPPING) { - return MA_ERROR; /* Expecting the stream to be a stopping or stopped state. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + resultSL = MA_OPENSL_RECORD(pDeviceStateOpenSL->pAudioRecorder)->SetRecordState((SLRecordItf)pDeviceStateOpenSL->pAudioRecorder, SL_RECORDSTATE_RECORDING); + if (resultSL != SL_RESULT_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device."); + return ma_result_from_OpenSL(resultSL); } - result = ma_wait_for_simple_state_transition__aaudio(pDevice->pContext, pStream, currentState, MA_AAUDIO_STREAM_STATE_STOPPED); - if (result != MA_SUCCESS) { - return result; + periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { + resultSL = MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDeviceStateOpenSL->pBufferQueueCapture, pDeviceStateOpenSL->pBufferCapture + (periodSizeInBytes * iPeriod), periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + MA_OPENSL_RECORD(pDeviceStateOpenSL->pAudioRecorder)->SetRecordState((SLRecordItf)pDeviceStateOpenSL->pAudioRecorder, SL_RECORDSTATE_STOPPED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device."); + return ma_result_from_OpenSL(resultSL); + } } } - return MA_SUCCESS; -} - -static ma_result ma_device_start__aaudio(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + resultSL = MA_OPENSL_PLAY(pDeviceStateOpenSL->pAudioPlayer)->SetPlayState((SLPlayItf)pDeviceStateOpenSL->pAudioPlayer, SL_PLAYSTATE_PLAYING); + if (resultSL != SL_RESULT_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device."); + return ma_result_from_OpenSL(resultSL); + } - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_result result = ma_device_start_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - if (result != MA_SUCCESS) { - return result; + /* In playback mode (no duplex) we need to load some initial buffers. In duplex mode we need to enqueue silent buffers. */ + if (deviceType == ma_device_type_duplex) { + MA_ZERO_MEMORY(pDeviceStateOpenSL->pBufferPlayback, pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + } else { + ma_device__read_frames_from_client(pDevice, pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods, pDeviceStateOpenSL->pBufferPlayback); } - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_result result = ma_device_start_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - if (result != MA_SUCCESS) { - if (pDevice->type == ma_device_type_duplex) { - ma_device_stop_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + periodSizeInBytes = pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; ++iPeriod) { + resultSL = MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDeviceStateOpenSL->pBufferQueuePlayback, pDeviceStateOpenSL->pBufferPlayback + (periodSizeInBytes * iPeriod), periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + MA_OPENSL_PLAY(pDeviceStateOpenSL->pAudioPlayer)->SetPlayState((SLPlayItf)pDeviceStateOpenSL->pAudioPlayer, SL_PLAYSTATE_STOPPED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device."); + return ma_result_from_OpenSL(resultSL); } - return result; } } return MA_SUCCESS; } -static ma_result ma_device_stop__aaudio(ma_device* pDevice) +static ma_result ma_device_drain__opensl(ma_device* pDevice, ma_device_type deviceType) { - MA_ASSERT(pDevice != NULL); + ma_device_state_opensl* pDeviceStateOpenSL = ma_device_get_backend_state__opensl(pDevice); + SLAndroidSimpleBufferQueueItf pBufferQueue; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_result result = ma_device_stop_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - if (result != MA_SUCCESS) { - return result; - } + MA_ASSERT(deviceType == ma_device_type_capture || deviceType == ma_device_type_playback); + + if (deviceType == ma_device_type_capture) { + pBufferQueue = pDeviceStateOpenSL->pBufferQueueCapture; + pDeviceStateOpenSL->isDrainingCapture = MA_TRUE; + } else { + pBufferQueue = pDeviceStateOpenSL->pBufferQueuePlayback; + pDeviceStateOpenSL->isDrainingPlayback = MA_TRUE; } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_result result = ma_device_stop_stream__aaudio(pDevice, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - if (result != MA_SUCCESS) { - return result; + for (;;) { + SLAndroidSimpleBufferQueueState state; + + MA_OPENSL_BUFFERQUEUE(pBufferQueue)->GetState(pBufferQueue, &state); + if (state.count == 0) { + break; } + + ma_sleep(10); } - ma_device__on_notification_stopped(pDevice); + if (deviceType == ma_device_type_capture) { + pDeviceStateOpenSL->isDrainingCapture = MA_FALSE; + } else { + pDeviceStateOpenSL->isDrainingPlayback = MA_FALSE; + } return MA_SUCCESS; } -static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type deviceType) +static ma_result ma_device_stop__opensl(ma_device* pDevice) { - const ma_int32 maxAttempts = 4; /* Reasonable retry limit. */ - - ma_result result; - ma_int32 iAttempt; + ma_device_state_opensl* pDeviceStateOpenSL = ma_device_get_backend_state__opensl(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + SLresult resultSL; MA_ASSERT(pDevice != NULL); - /* We got disconnected! Retry a few times, until we find a connected device! */ - iAttempt = 0; - while (iAttempt++ < maxAttempts) { - /* Device tearing down? No need to reroute! */ - if (ma_atomic_bool32_get(&pDevice->aaudio.isTearingDown)) { - result = MA_SUCCESS; /* Caller should continue as normal. */ - break; - } - - /* The first thing to do is close the streams. */ - ma_close_streams__aaudio(pDevice); - - /* Now we need to reinitialize each streams. The hardest part with this is just filling output the config and descriptors. */ - ma_device_config deviceConfig; - ma_device_descriptor descriptorPlayback; - ma_device_descriptor descriptorCapture; - - deviceConfig = ma_device_config_init(deviceType); - deviceConfig.playback.pDeviceID = NULL; /* Only doing rerouting with default devices. */ - deviceConfig.playback.shareMode = pDevice->playback.shareMode; - deviceConfig.playback.format = pDevice->playback.format; - deviceConfig.playback.channels = pDevice->playback.channels; - deviceConfig.capture.pDeviceID = NULL; /* Only doing rerouting with default devices. */ - deviceConfig.capture.shareMode = pDevice->capture.shareMode; - deviceConfig.capture.format = pDevice->capture.format; - deviceConfig.capture.channels = pDevice->capture.channels; - deviceConfig.sampleRate = pDevice->sampleRate; - deviceConfig.aaudio.usage = pDevice->aaudio.usage; - deviceConfig.aaudio.contentType = pDevice->aaudio.contentType; - deviceConfig.aaudio.inputPreset = pDevice->aaudio.inputPreset; - deviceConfig.aaudio.allowedCapturePolicy = pDevice->aaudio.allowedCapturePolicy; - deviceConfig.aaudio.noAutoStartAfterReroute = pDevice->aaudio.noAutoStartAfterReroute; - deviceConfig.periods = 1; - - /* Try to get an accurate period size. */ - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - deviceConfig.periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - } else { - deviceConfig.periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - } - - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - descriptorCapture.pDeviceID = deviceConfig.capture.pDeviceID; - descriptorCapture.shareMode = deviceConfig.capture.shareMode; - descriptorCapture.format = deviceConfig.capture.format; - descriptorCapture.channels = deviceConfig.capture.channels; - descriptorCapture.sampleRate = deviceConfig.sampleRate; - descriptorCapture.periodSizeInFrames = deviceConfig.periodSizeInFrames; - descriptorCapture.periodCount = deviceConfig.periods; - } - - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - descriptorPlayback.pDeviceID = deviceConfig.playback.pDeviceID; - descriptorPlayback.shareMode = deviceConfig.playback.shareMode; - descriptorPlayback.format = deviceConfig.playback.format; - descriptorPlayback.channels = deviceConfig.playback.channels; - descriptorPlayback.sampleRate = deviceConfig.sampleRate; - descriptorPlayback.periodSizeInFrames = deviceConfig.periodSizeInFrames; - descriptorPlayback.periodCount = deviceConfig.periods; - } - - result = ma_device_init_streams__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to create stream after route change."); - /* Reroute failed! */ - break; - } - - result = ma_device_post_init(pDevice, deviceType, &descriptorPlayback, &descriptorCapture); - if (result != MA_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[AAudio] Failed to initialize device after route change."); - ma_close_streams__aaudio(pDevice); - /* Reroute failed! */ - break; - } + MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it before stopping/uninitializing the device. */ + if (g_maOpenSLInitCounter == 0) { + return MA_INVALID_OPERATION; + } - /* We'll only ever do this in response to a reroute. */ - ma_device__on_notification_rerouted(pDevice); + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_device_drain__opensl(pDevice, ma_device_type_capture); - /* If the device is started, start the streams. Maybe make this configurable? */ - if (ma_device_get_state(pDevice) == ma_device_state_started) { - if (pDevice->aaudio.noAutoStartAfterReroute == MA_FALSE) { - result = ma_device_start__aaudio(pDevice); - if (result != MA_SUCCESS) { - if (iAttempt < maxAttempts) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, retrying(%d)", iAttempt); - } else { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Failed to start stream after route change, giving up."); - } - } - } else { - ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ - } + resultSL = MA_OPENSL_RECORD(pDeviceStateOpenSL->pAudioRecorder)->SetRecordState((SLRecordItf)pDeviceStateOpenSL->pAudioRecorder, SL_RECORDSTATE_STOPPED); + if (resultSL != SL_RESULT_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device."); + return ma_result_from_OpenSL(resultSL); } - if (result == MA_SUCCESS) { - /* Reroute successful! */ - break; - } + MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueueCapture)->Clear((SLAndroidSimpleBufferQueueItf)pDeviceStateOpenSL->pBufferQueueCapture); } - - return result; -} - -static ma_result ma_device_get_info__aaudio(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) -{ - ma_AAudioStream* pStream = NULL; - MA_ASSERT(pDevice != NULL); - MA_ASSERT(type != ma_device_type_duplex); - MA_ASSERT(pDeviceInfo != NULL); + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_device_drain__opensl(pDevice, ma_device_type_playback); - if (type == ma_device_type_capture) { - pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamCapture; - pDeviceInfo->id.aaudio = pDevice->capture.id.aaudio; - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ - } - if (type == ma_device_type_playback) { - pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback; - pDeviceInfo->id.aaudio = pDevice->playback.id.aaudio; - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ - } + resultSL = MA_OPENSL_PLAY(pDeviceStateOpenSL->pAudioPlayer)->SetPlayState((SLPlayItf)pDeviceStateOpenSL->pAudioPlayer, SL_PLAYSTATE_STOPPED); + if (resultSL != SL_RESULT_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device."); + return ma_result_from_OpenSL(resultSL); + } - /* Safety. Should never happen. */ - if (pStream == NULL) { - return MA_INVALID_OPERATION; + MA_OPENSL_BUFFERQUEUE(pDeviceStateOpenSL->pBufferQueuePlayback)->Clear((SLAndroidSimpleBufferQueueItf)pDeviceStateOpenSL->pBufferQueuePlayback); } - pDeviceInfo->nativeDataFormatCount = 0; - ma_context_add_native_data_format_from_AAudioStream__aaudio(pDevice->pContext, pStream, 0, pDeviceInfo); - return MA_SUCCESS; } - -static ma_result ma_context_uninit__aaudio(ma_context* pContext) +static ma_result ma_device_step__opensl(ma_device* pDevice, ma_blocking_mode blockingMode) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_aaudio); - - ma_device_job_thread_uninit(&pContext->aaudio.jobThread, &pContext->allocationCallbacks); - - ma_dlclose(ma_context_get_log(pContext), pContext->aaudio.hAAudio); - pContext->aaudio.hAAudio = NULL; - - return MA_SUCCESS; + ma_device_state_opensl* pDeviceStateOpenSL = ma_device_get_backend_state__opensl(pDevice); + return ma_device_state_async_step(&pDeviceStateOpenSL->async, pDevice, blockingMode, NULL); } -static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static ma_device_backend_vtable ma_gDeviceBackendVTable_OpenSL = { -#if !defined(MA_NO_RUNTIME_LINKING) - size_t i; - const char* libNames[] = { - "libaaudio.so" - }; - - for (i = 0; i < ma_countof(libNames); ++i) { - pContext->aaudio.hAAudio = ma_dlopen(ma_context_get_log(pContext), libNames[i]); - if (pContext->aaudio.hAAudio != NULL) { - break; - } - } - - if (pContext->aaudio.hAAudio == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } + ma_backend_info__opensl, + ma_context_init__opensl, + ma_context_uninit__opensl, + ma_context_enumerate_devices__opensl, + ma_device_init__opensl, + ma_device_uninit__opensl, + ma_device_start__opensl, + ma_device_stop__opensl, + ma_device_step__opensl, + NULL /* onDeviceWakeup */ +}; - pContext->aaudio.AAudio_createStreamBuilder = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudio_createStreamBuilder"); - pContext->aaudio.AAudioStreamBuilder_delete = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_delete"); - pContext->aaudio.AAudioStreamBuilder_setDeviceId = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setDeviceId"); - pContext->aaudio.AAudioStreamBuilder_setDirection = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setDirection"); - pContext->aaudio.AAudioStreamBuilder_setSharingMode = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setSharingMode"); - pContext->aaudio.AAudioStreamBuilder_setFormat = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setFormat"); - pContext->aaudio.AAudioStreamBuilder_setChannelCount = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setChannelCount"); - pContext->aaudio.AAudioStreamBuilder_setSampleRate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setSampleRate"); - pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setBufferCapacityInFrames"); - pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setFramesPerDataCallback"); - pContext->aaudio.AAudioStreamBuilder_setDataCallback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setDataCallback"); - pContext->aaudio.AAudioStreamBuilder_setErrorCallback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setErrorCallback"); - pContext->aaudio.AAudioStreamBuilder_setPerformanceMode = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setPerformanceMode"); - pContext->aaudio.AAudioStreamBuilder_setUsage = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setUsage"); - pContext->aaudio.AAudioStreamBuilder_setContentType = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setContentType"); - pContext->aaudio.AAudioStreamBuilder_setInputPreset = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setInputPreset"); - pContext->aaudio.AAudioStreamBuilder_setAllowedCapturePolicy = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_setAllowedCapturePolicy"); - pContext->aaudio.AAudioStreamBuilder_openStream = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStreamBuilder_openStream"); - pContext->aaudio.AAudioStream_close = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_close"); - pContext->aaudio.AAudioStream_getState = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getState"); - pContext->aaudio.AAudioStream_waitForStateChange = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_waitForStateChange"); - pContext->aaudio.AAudioStream_getFormat = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getFormat"); - pContext->aaudio.AAudioStream_getChannelCount = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getChannelCount"); - pContext->aaudio.AAudioStream_getSampleRate = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getSampleRate"); - pContext->aaudio.AAudioStream_getBufferCapacityInFrames = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getBufferCapacityInFrames"); - pContext->aaudio.AAudioStream_getFramesPerDataCallback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getFramesPerDataCallback"); - pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_getFramesPerBurst"); - pContext->aaudio.AAudioStream_requestStart = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStart"); - pContext->aaudio.AAudioStream_requestStop = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->aaudio.hAAudio, "AAudioStream_requestStop"); +ma_device_backend_vtable* ma_device_backend_opensl = &ma_gDeviceBackendVTable_OpenSL; #else - pContext->aaudio.AAudio_createStreamBuilder = (ma_proc)AAudio_createStreamBuilder; - pContext->aaudio.AAudioStreamBuilder_delete = (ma_proc)AAudioStreamBuilder_delete; - pContext->aaudio.AAudioStreamBuilder_setDeviceId = (ma_proc)AAudioStreamBuilder_setDeviceId; - pContext->aaudio.AAudioStreamBuilder_setDirection = (ma_proc)AAudioStreamBuilder_setDirection; - pContext->aaudio.AAudioStreamBuilder_setSharingMode = (ma_proc)AAudioStreamBuilder_setSharingMode; - pContext->aaudio.AAudioStreamBuilder_setFormat = (ma_proc)AAudioStreamBuilder_setFormat; - pContext->aaudio.AAudioStreamBuilder_setChannelCount = (ma_proc)AAudioStreamBuilder_setChannelCount; - pContext->aaudio.AAudioStreamBuilder_setSampleRate = (ma_proc)AAudioStreamBuilder_setSampleRate; - pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames = (ma_proc)AAudioStreamBuilder_setBufferCapacityInFrames; - pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback = (ma_proc)AAudioStreamBuilder_setFramesPerDataCallback; - pContext->aaudio.AAudioStreamBuilder_setDataCallback = (ma_proc)AAudioStreamBuilder_setDataCallback; - pContext->aaudio.AAudioStreamBuilder_setErrorCallback = (ma_proc)AAudioStreamBuilder_setErrorCallback; - pContext->aaudio.AAudioStreamBuilder_setPerformanceMode = (ma_proc)AAudioStreamBuilder_setPerformanceMode; - pContext->aaudio.AAudioStreamBuilder_setUsage = (ma_proc)AAudioStreamBuilder_setUsage; - pContext->aaudio.AAudioStreamBuilder_setContentType = (ma_proc)AAudioStreamBuilder_setContentType; - pContext->aaudio.AAudioStreamBuilder_setInputPreset = (ma_proc)AAudioStreamBuilder_setInputPreset; - #if defined(__ANDROID_API__) && __ANDROID_API__ >= 29 - pContext->aaudio.AAudioStreamBuilder_setAllowedCapturePolicy = (ma_proc)AAudioStreamBuilder_setAllowedCapturePolicy; - #endif - pContext->aaudio.AAudioStreamBuilder_openStream = (ma_proc)AAudioStreamBuilder_openStream; - pContext->aaudio.AAudioStream_close = (ma_proc)AAudioStream_close; - pContext->aaudio.AAudioStream_getState = (ma_proc)AAudioStream_getState; - pContext->aaudio.AAudioStream_waitForStateChange = (ma_proc)AAudioStream_waitForStateChange; - pContext->aaudio.AAudioStream_getFormat = (ma_proc)AAudioStream_getFormat; - pContext->aaudio.AAudioStream_getChannelCount = (ma_proc)AAudioStream_getChannelCount; - pContext->aaudio.AAudioStream_getSampleRate = (ma_proc)AAudioStream_getSampleRate; - pContext->aaudio.AAudioStream_getBufferCapacityInFrames = (ma_proc)AAudioStream_getBufferCapacityInFrames; - pContext->aaudio.AAudioStream_getFramesPerDataCallback = (ma_proc)AAudioStream_getFramesPerDataCallback; - pContext->aaudio.AAudioStream_getFramesPerBurst = (ma_proc)AAudioStream_getFramesPerBurst; - pContext->aaudio.AAudioStream_requestStart = (ma_proc)AAudioStream_requestStart; - pContext->aaudio.AAudioStream_requestStop = (ma_proc)AAudioStream_requestStop; -#endif - - pCallbacks->onContextInit = ma_context_init__aaudio; - pCallbacks->onContextUninit = ma_context_uninit__aaudio; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__aaudio; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__aaudio; - pCallbacks->onDeviceInit = ma_device_init__aaudio; - pCallbacks->onDeviceUninit = ma_device_uninit__aaudio; - pCallbacks->onDeviceStart = ma_device_start__aaudio; - pCallbacks->onDeviceStop = ma_device_stop__aaudio; - pCallbacks->onDeviceRead = NULL; /* Not used because AAudio is asynchronous. */ - pCallbacks->onDeviceWrite = NULL; /* Not used because AAudio is asynchronous. */ - pCallbacks->onDeviceDataLoop = NULL; /* Not used because AAudio is asynchronous. */ - pCallbacks->onDeviceGetInfo = ma_device_get_info__aaudio; - - - /* We need a job thread so we can deal with rerouting. */ - { - ma_result result; - ma_device_job_thread_config jobThreadConfig; +ma_device_backend_vtable* ma_device_backend_opensl = NULL; +#endif /* OpenSL|ES */ - jobThreadConfig = ma_device_job_thread_config_init(); +MA_API ma_device_backend_vtable* ma_opensl_get_vtable(void) +{ + return ma_device_backend_opensl; +} - result = ma_device_job_thread_init(&jobThreadConfig, &pContext->allocationCallbacks, &pContext->aaudio.jobThread); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->aaudio.hAAudio); - pContext->aaudio.hAAudio = NULL; - return result; - } - } +MA_API ma_context_config_opensl ma_context_config_opensl_init(void) +{ + ma_context_config_opensl config; + MA_ZERO_OBJECT(&config); - (void)pConfig; - return MA_SUCCESS; + return config; } -static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) +MA_API ma_device_config_opensl ma_device_config_opensl_init(void) { - ma_result result = MA_SUCCESS; - ma_device* pDevice; - - MA_ASSERT(pJob != NULL); - - pDevice = (ma_device*)pJob->data.device.aaudio.reroute.pDevice; - MA_ASSERT(pDevice != NULL); + ma_device_config_opensl config; - ma_mutex_lock(&pDevice->aaudio.rerouteLock); - { - /* Here is where we need to reroute the device. To do this we need to uninitialize the stream and reinitialize it. */ - result = ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); - if (result != MA_SUCCESS) { - /* - Getting here means we failed to reroute the device. The best thing I can think of here is to - just stop the device. - */ - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[AAudio] Stopping device due to reroute failure."); - ma_device_stop(pDevice); - } - } - ma_mutex_unlock(&pDevice->aaudio.rerouteLock); + MA_ZERO_OBJECT(&config); - return result; -} -#else -/* Getting here means there is no AAudio backend so we need a no-op job implementation. */ -static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) -{ - return ma_job_process__noop(pJob); + return config; } -#endif /* AAudio */ + /****************************************************************************** -OpenSL|ES Backend +Web Audio Backend ******************************************************************************/ -#ifdef MA_HAS_OPENSL -#include -#ifdef MA_ANDROID -#include +#ifdef MA_HAS_WEBAUDIO +#include + +#ifndef MA_EMSCRIPTEN_MAJOR + #if defined(__EMSCRIPTEN_MAJOR__) + #define MA_EMSCRIPTEN_MAJOR __EMSCRIPTEN_MAJOR__ + #else + #define MA_EMSCRIPTEN_MAJOR __EMSCRIPTEN_major__ + #endif +#endif +#ifndef MA_EMSCRIPTEN_MINOR + #if defined(__EMSCRIPTEN_MINOR__) + #define MA_EMSCRIPTEN_MINOR __EMSCRIPTEN_MINOR__ + #else + #define MA_EMSCRIPTEN_MINOR __EMSCRIPTEN_minor__ + #endif +#endif +#ifndef MA_EMSCRIPTEN_TINY + #if defined(__EMSCRIPTEN_TINY__) + #define MA_EMSCRIPTEN_TINY __EMSCRIPTEN_TINY__ + #else + #define MA_EMSCRIPTEN_TINY __EMSCRIPTEN_tiny__ + #endif #endif -typedef SLresult (SLAPIENTRY * ma_slCreateEngine_proc)(SLObjectItf* pEngine, SLuint32 numOptions, SLEngineOption* pEngineOptions, SLuint32 numInterfaces, SLInterfaceID* pInterfaceIds, SLboolean* pInterfaceRequired); +#if (MA_EMSCRIPTEN_MAJOR > 3) || (MA_EMSCRIPTEN_MAJOR == 3 && (MA_EMSCRIPTEN_MINOR > 1 || (MA_EMSCRIPTEN_MINOR == 1 && MA_EMSCRIPTEN_TINY >= 32))) + #include + #define MA_SUPPORT_AUDIO_WORKLETS -/* OpenSL|ES has one-per-application objects :( */ -static SLObjectItf g_maEngineObjectSL = NULL; -static SLEngineItf g_maEngineSL = NULL; -static ma_uint32 g_maOpenSLInitCounter = 0; -static ma_spinlock g_maOpenSLSpinlock = 0; /* For init/uninit. */ + #if (MA_EMSCRIPTEN_MAJOR > 3) || (MA_EMSCRIPTEN_MAJOR == 3 && (MA_EMSCRIPTEN_MINOR > 1 || (MA_EMSCRIPTEN_MINOR == 1 && MA_EMSCRIPTEN_TINY >= 70))) + #define MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE + #endif +#endif -#define MA_OPENSL_OBJ(p) (*((SLObjectItf)(p))) -#define MA_OPENSL_OUTPUTMIX(p) (*((SLOutputMixItf)(p))) -#define MA_OPENSL_PLAY(p) (*((SLPlayItf)(p))) -#define MA_OPENSL_RECORD(p) (*((SLRecordItf)(p))) +/* Silence some warnings when compiling with `-pedantic`. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma GCC diagnostic ignored "-Wvariadic-macro-arguments-omitted" -#ifdef MA_ANDROID -#define MA_OPENSL_BUFFERQUEUE(p) (*((SLAndroidSimpleBufferQueueItf)(p))) -#else -#define MA_OPENSL_BUFFERQUEUE(p) (*((SLBufferQueueItf)(p))) +#if defined(MA_ENABLE_AUDIO_WORKLETS) && defined(MA_SUPPORT_AUDIO_WORKLETS) + #define MA_USE_AUDIO_WORKLETS #endif -static ma_result ma_result_from_OpenSL(SLuint32 result) +/* The thread stack size must be a multiple of 16. */ +#ifndef MA_AUDIO_WORKLETS_THREAD_STACK_SIZE +#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 131072 +#endif + +#if defined(MA_USE_AUDIO_WORKLETS) +#define MA_WEBAUDIO_LATENCY_HINT_BALANCED "balanced" +#define MA_WEBAUDIO_LATENCY_HINT_INTERACTIVE "interactive" +#define MA_WEBAUDIO_LATENCY_HINT_PLAYBACK "playback" +#endif + + +typedef struct ma_context_state_webaudio { - switch (result) - { - case SL_RESULT_SUCCESS: return MA_SUCCESS; - case SL_RESULT_PRECONDITIONS_VIOLATED: return MA_ERROR; - case SL_RESULT_PARAMETER_INVALID: return MA_INVALID_ARGS; - case SL_RESULT_MEMORY_FAILURE: return MA_OUT_OF_MEMORY; - case SL_RESULT_RESOURCE_ERROR: return MA_INVALID_DATA; - case SL_RESULT_RESOURCE_LOST: return MA_ERROR; - case SL_RESULT_IO_ERROR: return MA_IO_ERROR; - case SL_RESULT_BUFFER_INSUFFICIENT: return MA_NO_SPACE; - case SL_RESULT_CONTENT_CORRUPTED: return MA_INVALID_DATA; - case SL_RESULT_CONTENT_UNSUPPORTED: return MA_FORMAT_NOT_SUPPORTED; - case SL_RESULT_CONTENT_NOT_FOUND: return MA_ERROR; - case SL_RESULT_PERMISSION_DENIED: return MA_ACCESS_DENIED; - case SL_RESULT_FEATURE_UNSUPPORTED: return MA_NOT_IMPLEMENTED; - case SL_RESULT_INTERNAL_ERROR: return MA_ERROR; - case SL_RESULT_UNKNOWN_ERROR: return MA_ERROR; - case SL_RESULT_OPERATION_ABORTED: return MA_ERROR; - case SL_RESULT_CONTROL_LOST: return MA_ERROR; - default: return MA_ERROR; - } + int _unused; +} ma_context_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) + EMSCRIPTEN_WEBAUDIO_T audioContext; + EMSCRIPTEN_WEBAUDIO_T audioWorklet; + #endif + float* pIntermediaryBuffer; + void* pStackBuffer; + ma_result initResult; /* Set to MA_BUSY while initialization is in progress. */ + int deviceIndex; /* We store the device in a list on the JavaScript side. This is used to map our C object to the JS object. */ +} ma_device_state_webaudio; + +static ma_context_state_webaudio* ma_context_get_backend_state__webaudio(ma_context* pContext) +{ + return (ma_context_state_webaudio*)ma_context_get_backend_state(pContext); +} + +static ma_device_state_webaudio* ma_device_get_backend_state__webaudio(ma_device* pDevice) +{ + return (ma_device_state_webaudio*)ma_device_get_backend_state(pDevice); } -/* Converts an individual OpenSL-style channel identifier (SL_SPEAKER_FRONT_LEFT, etc.) to miniaudio. */ -static ma_uint8 ma_channel_id_to_ma__opensl(SLuint32 id) + +#if defined(MA_EMSCRIPTEN) +#ifdef __cplusplus +extern "C" { +#endif +void EMSCRIPTEN_KEEPALIVE ma_device_post_notification_unlocked_emscripten(ma_device* pDevice) { - switch (id) - { - case SL_SPEAKER_FRONT_LEFT: return MA_CHANNEL_FRONT_LEFT; - case SL_SPEAKER_FRONT_RIGHT: return MA_CHANNEL_FRONT_RIGHT; - case SL_SPEAKER_FRONT_CENTER: return MA_CHANNEL_FRONT_CENTER; - case SL_SPEAKER_LOW_FREQUENCY: return MA_CHANNEL_LFE; - case SL_SPEAKER_BACK_LEFT: return MA_CHANNEL_BACK_LEFT; - case SL_SPEAKER_BACK_RIGHT: return MA_CHANNEL_BACK_RIGHT; - case SL_SPEAKER_FRONT_LEFT_OF_CENTER: return MA_CHANNEL_FRONT_LEFT_CENTER; - case SL_SPEAKER_FRONT_RIGHT_OF_CENTER: return MA_CHANNEL_FRONT_RIGHT_CENTER; - case SL_SPEAKER_BACK_CENTER: return MA_CHANNEL_BACK_CENTER; - case SL_SPEAKER_SIDE_LEFT: return MA_CHANNEL_SIDE_LEFT; - case SL_SPEAKER_SIDE_RIGHT: return MA_CHANNEL_SIDE_RIGHT; - case SL_SPEAKER_TOP_CENTER: return MA_CHANNEL_TOP_CENTER; - case SL_SPEAKER_TOP_FRONT_LEFT: return MA_CHANNEL_TOP_FRONT_LEFT; - case SL_SPEAKER_TOP_FRONT_CENTER: return MA_CHANNEL_TOP_FRONT_CENTER; - case SL_SPEAKER_TOP_FRONT_RIGHT: return MA_CHANNEL_TOP_FRONT_RIGHT; - case SL_SPEAKER_TOP_BACK_LEFT: return MA_CHANNEL_TOP_BACK_LEFT; - case SL_SPEAKER_TOP_BACK_CENTER: return MA_CHANNEL_TOP_BACK_CENTER; - case SL_SPEAKER_TOP_BACK_RIGHT: return MA_CHANNEL_TOP_BACK_RIGHT; - default: return 0; - } + ma_device_post_notification_unlocked(pDevice); +} +#ifdef __cplusplus } +#endif +#endif -/* Converts an individual miniaudio channel identifier (MA_CHANNEL_FRONT_LEFT, etc.) to OpenSL-style. */ -static SLuint32 ma_channel_id_to_opensl(ma_uint8 id) +static ma_bool32 ma_is_capture_supported__webaudio(void) { - switch (id) - { - case MA_CHANNEL_MONO: return SL_SPEAKER_FRONT_CENTER; - case MA_CHANNEL_FRONT_LEFT: return SL_SPEAKER_FRONT_LEFT; - case MA_CHANNEL_FRONT_RIGHT: return SL_SPEAKER_FRONT_RIGHT; - case MA_CHANNEL_FRONT_CENTER: return SL_SPEAKER_FRONT_CENTER; - case MA_CHANNEL_LFE: return SL_SPEAKER_LOW_FREQUENCY; - case MA_CHANNEL_BACK_LEFT: return SL_SPEAKER_BACK_LEFT; - case MA_CHANNEL_BACK_RIGHT: return SL_SPEAKER_BACK_RIGHT; - case MA_CHANNEL_FRONT_LEFT_CENTER: return SL_SPEAKER_FRONT_LEFT_OF_CENTER; - case MA_CHANNEL_FRONT_RIGHT_CENTER: return SL_SPEAKER_FRONT_RIGHT_OF_CENTER; - case MA_CHANNEL_BACK_CENTER: return SL_SPEAKER_BACK_CENTER; - case MA_CHANNEL_SIDE_LEFT: return SL_SPEAKER_SIDE_LEFT; - case MA_CHANNEL_SIDE_RIGHT: return SL_SPEAKER_SIDE_RIGHT; - case MA_CHANNEL_TOP_CENTER: return SL_SPEAKER_TOP_CENTER; - case MA_CHANNEL_TOP_FRONT_LEFT: return SL_SPEAKER_TOP_FRONT_LEFT; - case MA_CHANNEL_TOP_FRONT_CENTER: return SL_SPEAKER_TOP_FRONT_CENTER; - case MA_CHANNEL_TOP_FRONT_RIGHT: return SL_SPEAKER_TOP_FRONT_RIGHT; - case MA_CHANNEL_TOP_BACK_LEFT: return SL_SPEAKER_TOP_BACK_LEFT; - case MA_CHANNEL_TOP_BACK_CENTER: return SL_SPEAKER_TOP_BACK_CENTER; - case MA_CHANNEL_TOP_BACK_RIGHT: return SL_SPEAKER_TOP_BACK_RIGHT; - default: return 0; - } + return EM_ASM_INT({ + return (navigator.mediaDevices !== undefined && navigator.mediaDevices.getUserMedia !== undefined); + }, 0) != 0; /* Must pass in a dummy argument for C99 compatibility. */ } -/* Converts a channel mapping to an OpenSL-style channel mask. */ -static SLuint32 ma_channel_map_to_channel_mask__opensl(const ma_channel* pChannelMap, ma_uint32 channels) +#ifdef __cplusplus +extern "C" { +#endif +void* EMSCRIPTEN_KEEPALIVE ma_malloc_emscripten(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { - SLuint32 channelMask = 0; - ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; ++iChannel) { - channelMask |= ma_channel_id_to_opensl(pChannelMap[iChannel]); - } + return ma_malloc(sz, pAllocationCallbacks); +} - return channelMask; +void EMSCRIPTEN_KEEPALIVE ma_free_emscripten(void* p, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_free(p, pAllocationCallbacks); } -/* Converts an OpenSL-style channel mask to a miniaudio channel map. */ -static void ma_channel_mask_to_channel_map__opensl(SLuint32 channelMask, ma_uint32 channels, ma_channel* pChannelMap) +void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_capture__webaudio(ma_device* pDevice, int frameCount, float* pFrames) { - if (channels == 1 && channelMask == 0) { - pChannelMap[0] = MA_CHANNEL_MONO; - } else if (channels == 2 && channelMask == 0) { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; + 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 { - if (channels == 1 && (channelMask & SL_SPEAKER_FRONT_CENTER) != 0) { - pChannelMap[0] = MA_CHANNEL_MONO; - } else { - /* Just iterate over each bit. */ - ma_uint32 iChannel = 0; - ma_uint32 iBit; - for (iBit = 0; iBit < 32 && iChannel < channels; ++iBit) { - SLuint32 bitValue = (channelMask & (1UL << iBit)); - if (bitValue != 0) { - /* The bit is set. */ - pChannelMap[iChannel] = ma_channel_id_to_ma__opensl(bitValue); - iChannel += 1; - } - } - } + ma_device_handle_backend_data_callback(pDevice, NULL, pFrames, (ma_uint32)frameCount); } } -static SLuint32 ma_round_to_standard_sample_rate__opensl(SLuint32 samplesPerSec) +void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_playback__webaudio(ma_device* pDevice, int frameCount, float* pFrames) { - if (samplesPerSec <= SL_SAMPLINGRATE_8) { - return SL_SAMPLINGRATE_8; - } - if (samplesPerSec <= SL_SAMPLINGRATE_11_025) { - return SL_SAMPLINGRATE_11_025; - } - if (samplesPerSec <= SL_SAMPLINGRATE_12) { - return SL_SAMPLINGRATE_12; - } - if (samplesPerSec <= SL_SAMPLINGRATE_16) { - return SL_SAMPLINGRATE_16; - } - if (samplesPerSec <= SL_SAMPLINGRATE_22_05) { - return SL_SAMPLINGRATE_22_05; - } - if (samplesPerSec <= SL_SAMPLINGRATE_24) { - return SL_SAMPLINGRATE_24; - } - if (samplesPerSec <= SL_SAMPLINGRATE_32) { - return SL_SAMPLINGRATE_32; - } - if (samplesPerSec <= SL_SAMPLINGRATE_44_1) { - return SL_SAMPLINGRATE_44_1; - } - if (samplesPerSec <= SL_SAMPLINGRATE_48) { - return SL_SAMPLINGRATE_48; - } + ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice); - /* Android doesn't support more than 48000. */ -#ifndef MA_ANDROID - if (samplesPerSec <= SL_SAMPLINGRATE_64) { - return SL_SAMPLINGRATE_64; - } - if (samplesPerSec <= SL_SAMPLINGRATE_88_2) { - return SL_SAMPLINGRATE_88_2; - } - if (samplesPerSec <= SL_SAMPLINGRATE_96) { - return SL_SAMPLINGRATE_96; - } - if (samplesPerSec <= SL_SAMPLINGRATE_192) { - return SL_SAMPLINGRATE_192; + 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); } +} +#ifdef __cplusplus +} #endif - return SL_SAMPLINGRATE_16; +static void ma_backend_info__webaudio(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "Web Audio"; + pBackendInfo->isLoopbackSupported = MA_TRUE; + pBackendInfo->noAudioThread = MA_TRUE; /* We don't want to be creating a miniaudio-managed audio thread with Web Audio. */ + pBackendInfo->isMultiThreadedModeAllowedWhenThreadingDisabled = MA_TRUE; } - -static SLint32 ma_to_stream_type__opensl(ma_opensl_stream_type streamType) +static ma_result ma_context_init__webaudio(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) { - switch (streamType) { - case ma_opensl_stream_type_voice: return SL_ANDROID_STREAM_VOICE; - case ma_opensl_stream_type_system: return SL_ANDROID_STREAM_SYSTEM; - case ma_opensl_stream_type_ring: return SL_ANDROID_STREAM_RING; - case ma_opensl_stream_type_media: return SL_ANDROID_STREAM_MEDIA; - case ma_opensl_stream_type_alarm: return SL_ANDROID_STREAM_ALARM; - case ma_opensl_stream_type_notification: return SL_ANDROID_STREAM_NOTIFICATION; - default: break; - } + ma_context_state_webaudio* pContextStateWebAudio; + const ma_context_config_webaudio* pContextConfigWebAudio = (const ma_context_config_webaudio*)pContextBackendConfig; + ma_context_config_webaudio defaultConfigWebAudio; + int resultFromJS; - return SL_ANDROID_STREAM_VOICE; -} + if (pContextConfigWebAudio == NULL) { + defaultConfigWebAudio = ma_context_config_webaudio_init(); + pContextConfigWebAudio = &defaultConfigWebAudio; + } -static SLint32 ma_to_recording_preset__opensl(ma_opensl_recording_preset recordingPreset) -{ - switch (recordingPreset) { - case ma_opensl_recording_preset_generic: return SL_ANDROID_RECORDING_PRESET_GENERIC; - case ma_opensl_recording_preset_camcorder: return SL_ANDROID_RECORDING_PRESET_CAMCORDER; - case ma_opensl_recording_preset_voice_recognition: return SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; - case ma_opensl_recording_preset_voice_communication: return SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION; - case ma_opensl_recording_preset_voice_unprocessed: return SL_ANDROID_RECORDING_PRESET_UNPROCESSED; - default: break; + pContextStateWebAudio = (ma_context_state_webaudio*)ma_calloc(sizeof(*pContextStateWebAudio), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateWebAudio == NULL) { + return MA_OUT_OF_MEMORY; } - return SL_ANDROID_RECORDING_PRESET_NONE; -} + /* Here is where our global JavaScript object is initialized. */ + resultFromJS = EM_ASM_INT({ + if (typeof window === 'undefined' || (window.AudioContext || window.webkitAudioContext) === undefined) { + return 0; /* Web Audio not supported. */ + } + if (typeof(window.miniaudio) === 'undefined') { + window.miniaudio = { + referenceCount: 0 + }; -static ma_result ma_context_enumerate_devices__opensl(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_bool32 cbResult; + /* Device types. */ + window.miniaudio.device_type = {}; + window.miniaudio.device_type.playback = $0; + window.miniaudio.device_type.capture = $1; + window.miniaudio.device_type.duplex = $2; + window.miniaudio.device_type.loopback = $3; - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); + /* Device states. */ + window.miniaudio.device_status = {}; + window.miniaudio.device_status.stopped = $4; + window.miniaudio.device_status.started = $5; - MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to enumerate devices. */ - if (g_maOpenSLInitCounter == 0) { - return MA_INVALID_OPERATION; - } + /* Device cache for mapping devices to indexes for JavaScript/C interop. */ + let miniaudio = window.miniaudio; + miniaudio.devices = []; - /* - TODO: Test Me. + miniaudio.track_device = function(device) { + /* Try inserting into a free slot first. */ + for (var iDevice = 0; iDevice < miniaudio.devices.length; ++iDevice) { + if (miniaudio.devices[iDevice] == null) { + miniaudio.devices[iDevice] = device; + return iDevice; + } + } - This is currently untested, so for now we are just returning default devices. - */ -#if 0 && !defined(MA_ANDROID) - ma_bool32 isTerminated = MA_FALSE; + /* Getting here means there is no empty slots in the array so we just push to the end. */ + miniaudio.devices.push(device); + return miniaudio.devices.length - 1; + }; - SLuint32 pDeviceIDs[128]; - SLint32 deviceCount = sizeof(pDeviceIDs) / sizeof(pDeviceIDs[0]); + miniaudio.untrack_device_by_index = function(deviceIndex) { + /* We just set the device's slot to null. The slot will get reused in the next call to ma_track_device. */ + miniaudio.devices[deviceIndex] = null; - SLAudioIODeviceCapabilitiesItf deviceCaps; - SLresult resultSL = (*g_maEngineObjectSL)->GetInterface(g_maEngineObjectSL, (SLInterfaceID)pContext->opensl.SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); - if (resultSL != SL_RESULT_SUCCESS) { - /* The interface may not be supported so just report a default device. */ - goto return_default_device; - } + /* Trim the array if possible. */ + while (miniaudio.devices.length > 0) { + if (miniaudio.devices[miniaudio.devices.length-1] == null) { + miniaudio.devices.pop(); + } else { + break; + } + } + }; - /* Playback */ - if (!isTerminated) { - resultSL = (*deviceCaps)->GetAvailableAudioOutputs(deviceCaps, &deviceCount, pDeviceIDs); - if (resultSL != SL_RESULT_SUCCESS) { - return ma_result_from_OpenSL(resultSL); - } + miniaudio.untrack_device = function(device) { + for (var iDevice = 0; iDevice < miniaudio.devices.length; ++iDevice) { + if (miniaudio.devices[iDevice] == device) { + return miniaudio.untrack_device_by_index(iDevice); + } + } + }; - for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.opensl = pDeviceIDs[iDevice]; + miniaudio.get_device_by_index = function(deviceIndex) { + return miniaudio.devices[deviceIndex]; + }; - SLAudioOutputDescriptor desc; - resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, deviceInfo.id.opensl, &desc); - if (resultSL == SL_RESULT_SUCCESS) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)desc.pDeviceName, (size_t)-1); + miniaudio.unlock_event_types = (function(){ + return ['touchend', 'click']; + })(); - ma_bool32 cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - if (cbResult == MA_FALSE) { - isTerminated = MA_TRUE; - break; + miniaudio.unlock = function() { + for(var i = 0; i < miniaudio.devices.length; ++i) { + var device = miniaudio.devices[i]; + if (device != null && device.webaudio != null && device.state === miniaudio.device_status.started) { + device.webaudio.resume().then(() => { + _ma_device_post_notification_unlocked_emscripten(device.pDevice); + }, + (error) => {console.error("Failed to resume audiocontext", error); + }); + } } - } + miniaudio.unlock_event_types.map(function(event_type) { + document.removeEventListener(event_type, miniaudio.unlock, true); + }); + }; + + miniaudio.unlock_event_types.map(function(event_type) { + document.addEventListener(event_type, miniaudio.unlock, true); + }); } + + window.miniaudio.referenceCount += 1; + + return 1; + }, ma_device_type_playback, ma_device_type_capture, ma_device_type_duplex, ma_device_type_loopback, ma_device_status_stopped, ma_device_status_started); + + if (resultFromJS != 1) { + ma_free(pContextStateWebAudio, ma_context_get_allocation_callbacks(pContext)); + return MA_FAILED_TO_INIT_BACKEND; } - /* Capture */ - if (!isTerminated) { - resultSL = (*deviceCaps)->GetAvailableAudioInputs(deviceCaps, &deviceCount, pDeviceIDs); - if (resultSL != SL_RESULT_SUCCESS) { - return ma_result_from_OpenSL(resultSL); - } + *ppContextState = pContextStateWebAudio; - for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.opensl = pDeviceIDs[iDevice]; + return MA_SUCCESS; +} - SLAudioInputDescriptor desc; - resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, deviceInfo.id.opensl, &desc); - if (resultSL == SL_RESULT_SUCCESS) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), (const char*)desc.deviceName, (size_t)-1); +static void ma_context_uninit__webaudio(ma_context* pContext) +{ + ma_context_state_webaudio* pContextStateWebAudio = ma_context_get_backend_state__webaudio(pContext); - ma_bool32 cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); - if (cbResult == MA_FALSE) { - isTerminated = MA_TRUE; - break; - } + /* Remove the global miniaudio object from window if there are no more references to it. */ + EM_ASM({ + if (typeof(window.miniaudio) !== 'undefined') { + window.miniaudio.unlock_event_types.map(function(event_type) { + document.removeEventListener(event_type, window.miniaudio.unlock, true); + }); + + window.miniaudio.referenceCount -= 1; + if (window.miniaudio.referenceCount === 0) { + delete window.miniaudio; } } - } + }); - return MA_SUCCESS; -#else - goto return_default_device; -#endif + ma_free(pContextStateWebAudio, ma_context_get_allocation_callbacks(pContext)); +} + +static ma_device_enumeration_result ma_context_enumerate_device_from_type__webaudio(ma_context* pContext, ma_device_type deviceType, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_device_info deviceInfo; + ma_uint32 sampleRate; -return_default_device:; - cbResult = MA_TRUE; + (void)pContext; + + MA_ZERO_OBJECT(&deviceInfo); - /* Playback. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; + /* Default. */ + deviceInfo.isDefault = MA_TRUE; + + /* ID. */ + /* Nothing to do. Always using defaults. */ + + /* Name. */ + if (deviceType == ma_device_type_playback) { ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + } else { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } + + /* Data Format. */ + /* Web Audio can support any number of channels up to 32. It only supports f32 formats, however. The sample rate can be queried. */ + sampleRate = EM_ASM_INT({ + try { + var temp = new (window.AudioContext || window.webkitAudioContext)(); + var sampleRate = temp.sampleRate; + temp.close(); + return sampleRate; + } catch(e) { + return 0; + } + }, 0); /* Must pass in a dummy argument for C99 compatibility. */ - /* Capture. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + if (sampleRate == 0) { + return MA_DEVICE_ENUMERATION_CONTINUE; } - return MA_SUCCESS; + ma_device_info_add_native_data_format(&deviceInfo, ma_format_f32, 1, 32, sampleRate, sampleRate); + + return callback(deviceType, &deviceInfo, pUserData); } -static void ma_context_add_data_format_ex__opensl(ma_context* pContext, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_device_info* pDeviceInfo) +static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pDeviceInfo != NULL); + ma_device_enumeration_result cbResult = MA_DEVICE_ENUMERATION_CONTINUE; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; - pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; - pDeviceInfo->nativeDataFormatCount += 1; -} + MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); -static void ma_context_add_data_format__opensl(ma_context* pContext, ma_format format, ma_device_info* pDeviceInfo) -{ - ma_uint32 minChannels = 1; - ma_uint32 maxChannels = 2; - ma_uint32 minSampleRate = (ma_uint32)ma_standard_sample_rate_8000; - ma_uint32 maxSampleRate = (ma_uint32)ma_standard_sample_rate_48000; - ma_uint32 iChannel; - ma_uint32 iSampleRate; + /* Only supporting default devices for now. */ + + /* Playback. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + cbResult = ma_context_enumerate_device_from_type__webaudio(pContext, ma_device_type_playback, callback, pUserData); + } + + /* Capture. */ + if (cbResult == MA_DEVICE_ENUMERATION_CONTINUE) { + if (ma_is_capture_supported__webaudio()) { + cbResult = ma_context_enumerate_device_from_type__webaudio(pContext, ma_device_type_capture, callback, pUserData); + } + } + + (void)cbResult; + + return MA_SUCCESS; +} - MA_ASSERT(pContext != NULL); - MA_ASSERT(pDeviceInfo != NULL); +#if !defined(MA_USE_AUDIO_WORKLETS) +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__webaudio(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate) +{ /* - Each sample format can support mono and stereo, and we'll support a small subset of standard - rates (up to 48000). A better solution would be to somehow find a native sample rate. + There have been reports of the default buffer size being too small on some browsers. If we're using + the default buffer size, we'll make sure the period size is bigger than our standard defaults. */ - for (iChannel = minChannels; iChannel < maxChannels; iChannel += 1) { - for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); iSampleRate += 1) { - ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iSampleRate]; - if (standardSampleRate >= minSampleRate && standardSampleRate <= maxSampleRate) { - ma_context_add_data_format_ex__opensl(pContext, format, iChannel, standardSampleRate, pDeviceInfo); - } + ma_uint32 periodSizeInFrames; + + if (nativeSampleRate == 0) { + nativeSampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + if (pDescriptor->periodSizeInFrames == 0) { + if (pDescriptor->periodSizeInMilliseconds == 0) { + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(33, nativeSampleRate); /* 1 frame @ 30 FPS */ + } else { + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); } + } else { + periodSizeInFrames = pDescriptor->periodSizeInFrames; + } + + /* The size of the buffer must be a power of 2 and between 256 and 16384. */ + if (periodSizeInFrames < 256) { + periodSizeInFrames = 256; + } else if (periodSizeInFrames > 16384) { + periodSizeInFrames = 16384; + } else { + periodSizeInFrames = ma_next_power_of_2(periodSizeInFrames); } + + return periodSizeInFrames; } +#endif + -static ma_result ma_context_get_device_info__opensl(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +#if defined(MA_USE_AUDIO_WORKLETS) +typedef struct { - MA_ASSERT(pContext != NULL); + ma_device* pDevice; + ma_device_state_webaudio* pDeviceStateWebAudio; + ma_device_descriptor descriptorPlayback; + ma_device_descriptor descriptorCapture; +} ma_audio_worklet_thread_initialized_data; - MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to get device info. */ - if (g_maOpenSLInitCounter == 0) { - return MA_INVALID_OPERATION; - } +static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const AudioSampleFrame* pInputs, int outputCount, AudioSampleFrame* pOutputs, int paramCount, const AudioParamFrame* pParams, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint32 frameCount; + + (void)paramCount; + (void)pParams; /* - TODO: Test Me. + The Emscripten documentation says that it'll always be 128 frames being passed in. Hard coding it like that feels + like a very bad idea to me. Even if it's hard coded in the backend, the API and documentation should always refer + to variables instead of a hard coded number. In any case, will follow along for the time being. - This is currently untested, so for now we are just returning default devices. + Unfortunately the audio data is not interleaved so we'll need to convert it before we give the data to miniaudio + 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 0 && !defined(MA_ANDROID) - SLAudioIODeviceCapabilitiesItf deviceCaps; - SLresult resultSL = (*g_maEngineObjectSL)->GetInterface(g_maEngineObjectSL, (SLInterfaceID)pContext->opensl.SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); - if (resultSL != SL_RESULT_SUCCESS) { - /* The interface may not be supported so just report a default device. */ - goto return_default_device; + if (deviceType == ma_device_type_playback) { + frameCount = 128; + } else { + frameCount = 128; } - if (deviceType == ma_device_type_playback) { - SLAudioOutputDescriptor desc; - resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, pDeviceID->opensl, &desc); - if (resultSL != SL_RESULT_SUCCESS) { - return ma_result_from_OpenSL(resultSL); + /* + If this is called by the device has not yet been started we need to return early, making sure we output silence to + the output buffer. + */ + if (ma_device_get_status(pDevice) != ma_device_status_started) { + for (int i = 0; i < outputCount; i += 1) { + MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); } - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (const char*)desc.pDeviceName, (size_t)-1); - } else { - SLAudioInputDescriptor desc; - resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, pDeviceID->opensl, &desc); - if (resultSL != SL_RESULT_SUCCESS) { - return ma_result_from_OpenSL(resultSL); + return EM_TRUE; + } + + if (inputCount > 0) { + /* Input data needs to be interleaved before we hand it to the client. */ + for (ma_uint32 iChannel = 0; iChannel < pDevice->capture.internalChannels; iChannel += 1) { + for (ma_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + pDeviceStateWebAudio->pIntermediaryBuffer[iFrame*pDevice->capture.internalChannels + iChannel] = pInputs[0].data[frameCount*iChannel + iFrame]; + } } - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), (const char*)desc.deviceName, (size_t)-1); + ma_device_process_pcm_frames_capture__webaudio(pDevice, frameCount, pDeviceStateWebAudio->pIntermediaryBuffer); } - goto return_detailed_info; -#else - goto return_default_device; -#endif + if (outputCount > 0) { + /* If it's a capture-only device, we'll need to output silence. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + for (int i = 0; i < outputCount; i += 1) { + MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); + } + } else { + ma_device_process_pcm_frames_playback__webaudio(pDevice, frameCount, pDeviceStateWebAudio->pIntermediaryBuffer); -return_default_device: - if (pDeviceID != NULL) { - if ((deviceType == ma_device_type_playback && pDeviceID->opensl != SL_DEFAULTDEVICEID_AUDIOOUTPUT) || - (deviceType == ma_device_type_capture && pDeviceID->opensl != SL_DEFAULTDEVICEID_AUDIOINPUT)) { - return MA_NO_DEVICE; /* Don't know the device. */ + /* We've read the data from the client. Now we need to deinterleave the buffer and output to the output buffer. */ + for (ma_uint32 iChannel = 0; iChannel < pDevice->playback.internalChannels; iChannel += 1) { + for (ma_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { + pOutputs[0].data[frameCount*iChannel + iFrame] = pDeviceStateWebAudio->pIntermediaryBuffer[iFrame*pDevice->playback.internalChannels + iChannel]; + } + } + + /* + Just above we output data to the first output buffer. Here we just make sure we're putting silence into any + remaining output buffers. + */ + for (int i = 1; i < outputCount; i += 1) { /* <-- Note that the counter starts at 1 instead of 0. */ + MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); + } } } - /* ID and Name / Description */ - if (deviceType == ma_device_type_playback) { - pDeviceInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - pDeviceInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } + return EM_TRUE; +} - pDeviceInfo->isDefault = MA_TRUE; - goto return_detailed_info; +static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* pUserData) +{ + ma_audio_worklet_thread_initialized_data* pParameters = (ma_audio_worklet_thread_initialized_data*)pUserData; + ma_device_type deviceType = ma_device_get_type(pParameters->pDevice); + EmscriptenAudioWorkletNodeCreateOptions audioWorkletOptions; + int channels = 0; + size_t intermediaryBufferSizeInFrames; + if (success == EM_FALSE) { + pParameters->pDeviceStateWebAudio->initResult = MA_ERROR; + ma_free(pParameters, ma_device_get_allocation_callbacks(pParameters->pDevice)); + return; + } -return_detailed_info: + /* The next step is to initialize the audio worklet node. */ + MA_ZERO_OBJECT(&audioWorkletOptions); /* - For now we're just outputting a set of values that are supported by the API but not necessarily supported - by the device natively. Later on we should work on this so that it more closely reflects the device's - actual native format. + The way channel counts work with Web Audio is confusing. As far as I can tell, there's no way to know the channel + count from MediaStreamAudioSourceNode (what we use for capture)? The only way to have control is to configure an + output channel count on the capture side. This is slightly confusing for capture mode because intuitively you + wouldn't actually connect an output to an input-only node, but this is what we'll have to do in order to have + proper control over the channel count. In the capture case, we'll have to output silence to its output node. */ - pDeviceInfo->nativeDataFormatCount = 0; -#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 - ma_context_add_data_format__opensl(pContext, ma_format_f32, pDeviceInfo); -#endif - ma_context_add_data_format__opensl(pContext, ma_format_s16, pDeviceInfo); - ma_context_add_data_format__opensl(pContext, ma_format_u8, pDeviceInfo); - - return MA_SUCCESS; -} - + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + MA_ASSERT(pParameters->descriptorCapture.channels > 0); /* Should have been initialized to a valid value earlier. */ + channels = (int)pParameters->descriptorCapture.channels; + audioWorkletOptions.numberOfInputs = 1; + } else { + MA_ASSERT(pParameters->descriptorPlayback.channels > 0); /* Should have been initialized to a valid value earlier. */ + channels = (int)pParameters->descriptorPlayback.channels; -#ifdef MA_ANDROID -/*void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, SLuint32 eventFlags, const void* pBuffer, SLuint32 bufferSize, SLuint32 dataUsed, void* pContext)*/ -static void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) -{ - ma_device* pDevice = (ma_device*)pUserData; - size_t periodSizeInBytes; - ma_uint8* pBuffer; - SLresult resultSL; + if (deviceType == ma_device_type_duplex) { + audioWorkletOptions.numberOfInputs = 1; + } else { + audioWorkletOptions.numberOfInputs = 0; + } + } - MA_ASSERT(pDevice != NULL); + audioWorkletOptions.numberOfOutputs = 1; + audioWorkletOptions.outputChannelCounts = &channels; - (void)pBufferQueue; /* - For now, don't do anything unless the buffer was fully processed. From what I can tell, it looks like - OpenSL|ES 1.1 improves on buffer queues to the point that we could much more intelligently handle this, - but unfortunately it looks like Android is only supporting OpenSL|ES 1.0.1 for now :( + Now that we know the channel count to use we can allocate the intermediary buffer. The + intermediary buffer is used for interleaving and deinterleaving. */ - - /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != ma_device_state_started) { - return; + #if defined(MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE) + { + intermediaryBufferSizeInFrames = (size_t)emscripten_audio_context_quantum_size(audioContext); } - - /* Don't do anything if the device is being drained. */ - if (pDevice->opensl.isDrainingCapture) { - return; + #else + { + intermediaryBufferSizeInFrames = 128; } + #endif - periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - pBuffer = pDevice->opensl.pBufferCapture + (pDevice->opensl.currentBufferIndexCapture * periodSizeInBytes); - - ma_device_handle_backend_data_callback(pDevice, NULL, pBuffer, pDevice->capture.internalPeriodSizeInFrames); - - resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pBuffer, periodSizeInBytes); - if (resultSL != SL_RESULT_SUCCESS) { + pParameters->pDeviceStateWebAudio->pIntermediaryBuffer = (float*)ma_malloc(intermediaryBufferSizeInFrames * (ma_uint32)channels * sizeof(float), &pParameters->pDevice->pContext->allocationCallbacks); + if (pParameters->pDeviceStateWebAudio->pIntermediaryBuffer == NULL) { + pParameters->pDeviceStateWebAudio->initResult = MA_OUT_OF_MEMORY; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); return; } - pDevice->opensl.currentBufferIndexCapture = (pDevice->opensl.currentBufferIndexCapture + 1) % pDevice->capture.internalPeriods; -} - -static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) -{ - ma_device* pDevice = (ma_device*)pUserData; - size_t periodSizeInBytes; - ma_uint8* pBuffer; - SLresult resultSL; + pParameters->pDeviceStateWebAudio->audioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "miniaudio", &audioWorkletOptions, &ma_audio_worklet_process_callback__webaudio, pParameters->pDevice); - MA_ASSERT(pDevice != NULL); + /* With the audio worklet initialized we can now attach it to the graph. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + ma_result attachmentResult = (ma_result)EM_ASM_INT({ + var getMediaResult = 0; + var audioWorklet = emscriptenGetAudioObject($0); + var audioContext = emscriptenGetAudioObject($1); + var deviceType = $2; - (void)pBufferQueue; + if (deviceType == window.miniaudio.device_type.loopback) { + if (navigator.mediaDevices.getDisplayMedia === 'undefined') { + console.log("navigator.mediaDevices.getDisplayMedia undefined. Cannot use loopback mode."); + getMediaResult = -1; + } else { + navigator.mediaDevices.getDisplayMedia({audio:true, video:true}) + .then(function(stream) { + audioContext.streamNode = audioContext.createMediaStreamSource(stream); + audioContext.streamNode.connect(audioWorklet); + audioWorklet.connect(audioContext.destination); + getMediaResult = 0; /* 0 = MA_SUCCESS */ + }) + .catch(function(error) { + console.log("navigator.mediaDevices.getDisplayMedia Failed: " + error); + getMediaResult = -1; /* -1 = MA_ERROR */ + }); + } + } else { + navigator.mediaDevices.getUserMedia({audio:true, video:false}) + .then(function(stream) { + audioContext.streamNode = audioContext.createMediaStreamSource(stream); + audioContext.streamNode.connect(audioWorklet); + audioWorklet.connect(audioContext.destination); + getMediaResult = 0; /* 0 = MA_SUCCESS */ + }) + .catch(function(error) { + console.log("navigator.mediaDevices.getUserMedia Failed: " + error); + getMediaResult = -1; /* -1 = MA_ERROR */ + }); + } - /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != ma_device_state_started) { - return; - } + return getMediaResult; + }, pParameters->pDeviceStateWebAudio->audioWorklet, audioContext, deviceType); - /* Don't do anything if the device is being drained. */ - if (pDevice->opensl.isDrainingPlayback) { - return; + if (attachmentResult != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_ERROR, "Web Audio: Failed to connect capture node."); + emscripten_destroy_web_audio_node(pParameters->pDeviceStateWebAudio->audioWorklet); + pParameters->pDeviceStateWebAudio->initResult = attachmentResult; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); + return; + } } - periodSizeInBytes = pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - pBuffer = pDevice->opensl.pBufferPlayback + (pDevice->opensl.currentBufferIndexPlayback * periodSizeInBytes); - - ma_device_handle_backend_data_callback(pDevice, pBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); + /* If it's playback only we can now attach the worklet node to the graph. This has already been done for the duplex case. */ + if (deviceType == ma_device_type_playback) { + ma_result attachmentResult = (ma_result)EM_ASM_INT({ + var audioWorklet = emscriptenGetAudioObject($0); + var audioContext = emscriptenGetAudioObject($1); + audioWorklet.connect(audioContext.destination); + return 0; /* 0 = MA_SUCCESS */ + }, pParameters->pDeviceStateWebAudio->audioWorklet, audioContext); - resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pBuffer, periodSizeInBytes); - if (resultSL != SL_RESULT_SUCCESS) { - return; + if (attachmentResult != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_ERROR, "Web Audio: Failed to connect playback node."); + pParameters->pDeviceStateWebAudio->initResult = attachmentResult; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); + return; + } } - pDevice->opensl.currentBufferIndexPlayback = (pDevice->opensl.currentBufferIndexPlayback + 1) % pDevice->playback.internalPeriods; + + + /* 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); + pParameters->pDeviceStateWebAudio->initResult = MA_SUCCESS; + ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); } -#endif -static ma_result ma_device_uninit__opensl(ma_device* pDevice) +static void ma_audio_worklet_thread_initialized__webaudio(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* pUserData) { - MA_ASSERT(pDevice != NULL); - - MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it before uninitializing the device. */ - if (g_maOpenSLInitCounter == 0) { - return MA_INVALID_OPERATION; - } + ma_audio_worklet_thread_initialized_data* pParameters = (ma_audio_worklet_thread_initialized_data*)pUserData; + WebAudioWorkletProcessorCreateOptions workletProcessorOptions; - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (pDevice->opensl.pAudioRecorderObj) { - MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj); - } + MA_ASSERT(pParameters != NULL); - ma_free(pDevice->opensl.pBufferCapture, &pDevice->pContext->allocationCallbacks); + if (success == EM_FALSE) { + pParameters->pDeviceStateWebAudio->initResult = MA_ERROR; + return; } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - if (pDevice->opensl.pAudioPlayerObj) { - MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioPlayerObj); - } - if (pDevice->opensl.pOutputMixObj) { - MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj); - } - - ma_free(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); - } + MA_ZERO_OBJECT(&workletProcessorOptions); + workletProcessorOptions.name = "miniaudio"; /* I'm not entirely sure what to call this. Does this need to be globally unique, or does it need only be unique for a given AudioContext? */ - return MA_SUCCESS; + emscripten_create_wasm_audio_worklet_processor_async(audioContext, &workletProcessorOptions, ma_audio_worklet_processor_created__webaudio, pParameters); } - -#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 -typedef SLAndroidDataFormat_PCM_EX ma_SLDataFormat_PCM; -#else -typedef SLDataFormat_PCM ma_SLDataFormat_PCM; #endif -static ma_result ma_SLDataFormat_PCM_init__opensl(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const ma_channel* channelMap, ma_SLDataFormat_PCM* pDataFormat) +static ma_result ma_device_init__webaudio(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) { - /* We need to convert our format/channels/rate so that they aren't set to default. */ - if (format == ma_format_unknown) { - format = MA_DEFAULT_FORMAT; - } - if (channels == 0) { - channels = MA_DEFAULT_CHANNELS; - } - if (sampleRate == 0) { - sampleRate = MA_DEFAULT_SAMPLE_RATE; + ma_device_state_webaudio* pDeviceStateWebAudio; + const ma_device_config_webaudio* pDeviceConfigWebAudio = (const ma_device_config_webaudio*)pDeviceBackendConfig; + ma_device_config_webaudio defaultConfigWebAudio; + ma_device_type deviceType = ma_device_get_type(pDevice); + + if (pDeviceConfigWebAudio == NULL) { + defaultConfigWebAudio = ma_device_config_webaudio_init(); + pDeviceConfigWebAudio = &defaultConfigWebAudio; } -#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 - if (format == ma_format_f32) { - pDataFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - pDataFormat->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; - } else { - pDataFormat->formatType = SL_DATAFORMAT_PCM; + /* No exclusive mode with Web Audio. */ + if (((deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + return MA_SHARE_MODE_NOT_SUPPORTED; } -#else - pDataFormat->formatType = SL_DATAFORMAT_PCM; -#endif - pDataFormat->numChannels = channels; - ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate * 1000); /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ - pDataFormat->bitsPerSample = ma_get_bytes_per_sample(format) * 8; - pDataFormat->channelMask = ma_channel_map_to_channel_mask__opensl(channelMap, channels); - pDataFormat->endianness = (ma_is_little_endian()) ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; + pDeviceStateWebAudio = (ma_device_state_webaudio*)ma_calloc(sizeof(*pDeviceStateWebAudio), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateWebAudio == NULL) { + return MA_OUT_OF_MEMORY; + } /* - Android has a few restrictions on the format as documented here: https://developer.android.com/ndk/guides/audio/opensl-for-android.html - - Only mono and stereo is supported. - - Only u8 and s16 formats are supported. - - Maximum sample rate of 48000. + With AudioWorklets we'll have just a single AudioContext. I'm not sure why I'm not doing this for ScriptProcessorNode so + it might be worthwhile to look into that as well. */ -#ifdef MA_ANDROID - if (pDataFormat->numChannels > 2) { - pDataFormat->numChannels = 2; - } -#if __ANDROID_API__ >= 21 - if (pDataFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { - /* It's floating point. */ - MA_ASSERT(pDataFormat->representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); - if (pDataFormat->bitsPerSample > 32) { - pDataFormat->bitsPerSample = 32; - } - } else { - if (pDataFormat->bitsPerSample > 16) { - pDataFormat->bitsPerSample = 16; - } - } -#else - if (pDataFormat->bitsPerSample > 16) { - pDataFormat->bitsPerSample = 16; - } -#endif - if (((SLDataFormat_PCM*)pDataFormat)->samplesPerSec > SL_SAMPLINGRATE_48) { - ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = SL_SAMPLINGRATE_48; - } -#endif - - pDataFormat->containerSize = pDataFormat->bitsPerSample; /* Always tightly packed for now. */ - - return MA_SUCCESS; -} + #if defined(MA_USE_AUDIO_WORKLETS) + { + EmscriptenWebAudioCreateAttributes audioContextAttributes; + ma_audio_worklet_thread_initialized_data* pInitParameters; + void* pStackBuffer; -static ma_result ma_deconstruct_SLDataFormat_PCM__opensl(ma_SLDataFormat_PCM* pDataFormat, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_bool32 isFloatingPoint = MA_FALSE; -#if defined(MA_ANDROID) && __ANDROID_API__ >= 21 - if (pDataFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { - MA_ASSERT(pDataFormat->representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); - isFloatingPoint = MA_TRUE; - } -#endif - if (isFloatingPoint) { - if (pDataFormat->bitsPerSample == 32) { - *pFormat = ma_format_f32; + /* */ if (pDeviceConfigWebAudio->latencyHint == ma_webaudio_latency_hint_interactive) { + audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_INTERACTIVE; + } else if (pDeviceConfigWebAudio->latencyHint == ma_webaudio_latency_hint_playback) { + audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_PLAYBACK; + } else if (pDeviceConfigWebAudio->latencyHint == ma_webaudio_latency_hint_balanced) { + audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_BALANCED; + } else { + /* Leave the latency hint unset. */ } - } else { - if (pDataFormat->bitsPerSample == 8) { - *pFormat = ma_format_u8; - } else if (pDataFormat->bitsPerSample == 16) { - *pFormat = ma_format_s16; - } else if (pDataFormat->bitsPerSample == 24) { - *pFormat = ma_format_s24; - } else if (pDataFormat->bitsPerSample == 32) { - *pFormat = ma_format_s32; + + /* + In my testing, Firefox does not seem to capture audio data properly if the sample rate is set + to anything other than 48K. This does not seem to be the case for other browsers. For this reason, + if the device type is anything other than playback, we'll leave the sample rate as-is and let the + browser pick the appropriate rate for us. + */ + if (deviceType == ma_device_type_playback) { + audioContextAttributes.sampleRate = pDescriptorPlayback->sampleRate; + } else { + audioContextAttributes.sampleRate = 0; } - } - *pChannels = pDataFormat->numChannels; - *pSampleRate = ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec / 1000; - ma_channel_mask_to_channel_map__opensl(pDataFormat->channelMask, ma_min(pDataFormat->numChannels, channelMapCap), pChannelMap); + /* It's not clear if this can return an error. None of the tests in the Emscripten repository check for this, so neither am I for now. */ + pDeviceStateWebAudio->audioContext = emscripten_create_audio_context(&audioContextAttributes); - return MA_SUCCESS; -} + /* + With the context created we can now create the worklet. We can only have a single worklet per audio + context which means we'll need to craft this appropriately to handle duplex devices correctly. + */ -static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) -{ -#ifdef MA_ANDROID - SLDataLocator_AndroidSimpleBufferQueue queue; - SLresult resultSL; - size_t bufferSizeInBytes; - SLInterfaceID itfIDs[2]; - const SLboolean itfIDsRequired[] = { - SL_BOOLEAN_TRUE, /* SL_IID_ANDROIDSIMPLEBUFFERQUEUE */ - SL_BOOLEAN_FALSE /* SL_IID_ANDROIDCONFIGURATION */ - }; -#endif + /* + We now need to create a worker thread. This is a bit weird because we need to allocate our + own buffer for the thread's stack. The stack needs to be aligned to 16 bytes. I'm going to + allocate this on the heap to keep it simple. + */ + pStackBuffer = ma_aligned_malloc(MA_AUDIO_WORKLETS_THREAD_STACK_SIZE, 16, ma_device_get_allocation_callbacks(pDevice)); + if (pStackBuffer == NULL) { + emscripten_destroy_audio_context(pDeviceStateWebAudio->audioContext); + ma_free(pDeviceStateWebAudio, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; + } - MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to initialize a new device. */ - if (g_maOpenSLInitCounter == 0) { - return MA_INVALID_OPERATION; - } + /* + We can build the descriptors now before we've initialized the device. The format is ma_format_f32, the channel count + is chosen by us, and the sample rate is taken from the audio context which we've initialized just above. The period + size is also chosen by us. + */ + { + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 periodSizeInFrames; - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + /* The format is always 32-bit floating point. */ + format = ma_format_f32; - /* - For now, only supporting Android implementations of OpenSL|ES since that's the only one I've - been able to test with and I currently depend on Android-specific extensions (simple buffer - queues). - */ -#ifdef MA_ANDROID - itfIDs[0] = (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE; - itfIDs[1] = (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDCONFIGURATION; + /* The channels are chosen by us. For duplex mode we'll always use the playback channel count. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + channels = (pDescriptorCapture->channels > 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; + } else { + channels = (pDescriptorPlayback->channels > 0) ? pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS; + } - /* No exclusive mode with OpenSL|ES. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } + /* The sample rate is taken from the audio context. */ + sampleRate = (ma_uint32)EM_ASM_INT({ return emscriptenGetAudioObject($0).sampleRate; }, pDeviceStateWebAudio->audioContext); - /* Now we can start initializing the device properly. */ - MA_ASSERT(pDevice != NULL); - MA_ZERO_OBJECT(&pDevice->opensl); + /* + We're now going to choose a period size. For single-threaded mode, 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. + */ + #if defined(MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE) + { + periodSizeInFrames = (ma_uint32)emscripten_audio_context_quantum_size(pDeviceStateWebAudio->audioContext); + } + #else + { + periodSizeInFrames = 128; + } + #endif - queue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + if (ma_device_get_threading_mode(pDevice) == MA_THREADING_MODE_SINGLE_THREADED) { + if (pDescriptorCapture != NULL) { + if (periodSizeInFrames < pDescriptorCapture->periodSizeInFrames) { + periodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + } + } + if (pDescriptorPlayback != NULL) { + if (periodSizeInFrames < pDescriptorPlayback->periodSizeInFrames) { + periodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + } + } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_SLDataFormat_PCM pcm; - SLDataLocator_IODevice locatorDevice; - SLDataSource source; - SLDataSink sink; - SLAndroidConfigurationItf pRecorderConfig; + /* 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 (periodSizeInFrames < 1024) { + periodSizeInFrames = 1024; + } + } - ma_SLDataFormat_PCM_init__opensl(pDescriptorCapture->format, pDescriptorCapture->channels, pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, &pcm); + /* The descriptors are the same between playback and capture. */ + if (pDescriptorCapture != NULL) { + pDescriptorCapture->format = format; + pDescriptorCapture->channels = channels; + pDescriptorCapture->sampleRate = sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); + pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; + pDescriptorCapture->periodCount = 1; + } - locatorDevice.locatorType = SL_DATALOCATOR_IODEVICE; - locatorDevice.deviceType = SL_IODEVICE_AUDIOINPUT; - locatorDevice.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; /* Must always use the default device with Android. */ - locatorDevice.device = NULL; + if (pDescriptorPlayback != NULL) { + pDescriptorPlayback->format = format; + pDescriptorPlayback->channels = channels; + pDescriptorPlayback->sampleRate = sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; + pDescriptorPlayback->periodCount = 1; + } + } - source.pLocator = &locatorDevice; - source.pFormat = NULL; + 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); + } - queue.numBuffers = pDescriptorCapture->periodCount; + /* Our thread initialization parameters need to be allocated on the heap so they don't go out of scope. */ + pInitParameters = (ma_audio_worklet_thread_initialized_data*)ma_calloc(sizeof(*pInitParameters), ma_device_get_allocation_callbacks(pDevice)); + if (pInitParameters == NULL) { + ma_aligned_free(pStackBuffer, ma_device_get_allocation_callbacks(pDevice)); + emscripten_destroy_audio_context(pDeviceStateWebAudio->audioContext); + ma_free(pDeviceStateWebAudio, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; + } - sink.pLocator = &queue; - sink.pFormat = (SLDataFormat_PCM*)&pcm; + pInitParameters->pDevice = pDevice; + pInitParameters->pDeviceStateWebAudio = pDeviceStateWebAudio; - resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); - if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { - /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ - pcm.formatType = SL_DATAFORMAT_PCM; - pcm.numChannels = 1; - ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; /* The name of the sample rate variable is different between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM. */ - pcm.bitsPerSample = 16; - pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ - pcm.channelMask = 0; - resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); + if (pDescriptorPlayback != NULL) { + pInitParameters->descriptorPlayback = *pDescriptorPlayback; } - - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder."); - return ma_result_from_OpenSL(resultSL); + if (pDescriptorCapture != NULL) { + pInitParameters->descriptorCapture = *pDescriptorCapture; } + /* We need to add an entry to the miniaudio.devices list on the JS side so we can do some JS/C interop. */ + pDeviceStateWebAudio->deviceIndex = EM_ASM_INT({ + return window.miniaudio.track_device({ + webaudio: emscriptenGetAudioObject($0), + state: 1, /* 1 = ma_device_status_stopped */ + pDevice: $1 + }); + }, pDeviceStateWebAudio->audioContext, (ma_uintptr)pDevice); - /* Set the recording preset before realizing the player. */ - if (pConfig->opensl.recordingPreset != ma_opensl_recording_preset_default) { - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDCONFIGURATION, &pRecorderConfig); - if (resultSL == SL_RESULT_SUCCESS) { - SLint32 recordingPreset = ma_to_recording_preset__opensl(pConfig->opensl.recordingPreset); - resultSL = (*pRecorderConfig)->SetConfiguration(pRecorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, &recordingPreset, sizeof(SLint32)); - if (resultSL != SL_RESULT_SUCCESS) { - /* Failed to set the configuration. Just keep going. */ - } - } - } + /* + We now have enough to initialize the audio worklet. This is asynchronous. Waiting for it to complete will + require a call to emscripten_sleep() which in turn requires building with `-sASYNCIFY`. We're just going + to not wait for it and just assume it was successful. Audio should resume once it's all up and running. + */ + emscripten_start_wasm_audio_worklet_thread_async(pDeviceStateWebAudio->audioContext, pStackBuffer, MA_AUDIO_WORKLETS_THREAD_STACK_SIZE, ma_audio_worklet_thread_initialized__webaudio, pInitParameters); + } + #else + { + /* ScriptProcessorNode. This path requires us to do almost everything in JS, but we'll do as much as we can in C. */ + ma_uint32 deviceIndex; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 periodSizeInFrames; - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Realize((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_BOOLEAN_FALSE); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder."); - return ma_result_from_OpenSL(resultSL); + /* The channel count will depend on the device type. If it's a capture, use its, otherwise use the playback side. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + channels = (pDescriptorCapture->channels > 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; + } else { + channels = (pDescriptorPlayback->channels > 0) ? pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS; } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_RECORD, &pDevice->opensl.pAudioRecorder); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface."); - return ma_result_from_OpenSL(resultSL); + /* + When testing in Firefox, I've seen it where capture mode fails if the sample rate is changed to anything other than it's + native rate. For this reason we're leaving the sample rate untouched for capture devices. + */ + if (deviceType == ma_device_type_playback) { + sampleRate = pDescriptorPlayback->sampleRate; + } else { + sampleRate = 0; /* Let the browser decide when capturing. */ } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueueCapture); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); - return ma_result_from_OpenSL(resultSL); + /* The period size needs to be a power of 2. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptorCapture, sampleRate); + } else { + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptorPlayback, sampleRate); } - resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, ma_buffer_queue_callback_capture__opensl_android, pDevice); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); - return ma_result_from_OpenSL(resultSL); + /* We need an intermediary buffer for doing interleaving and deinterleaving. */ + pDeviceStateWebAudio->pIntermediaryBuffer = (float*)ma_malloc(periodSizeInFrames * channels * sizeof(float), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateWebAudio->pIntermediaryBuffer == NULL) { + return MA_OUT_OF_MEMORY; } - /* The internal format is determined by the "pcm" object. */ - ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDescriptorCapture->format, &pDescriptorCapture->channels, &pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap)); - - /* Buffer. */ - pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); - pDevice->opensl.currentBufferIndexCapture = 0; + deviceIndex = EM_ASM_INT({ + var deviceType = $0; + var channels = $1; + var sampleRate = $2; + var bufferSize = $3; + var pIntermediaryBuffer = $4; + var pDevice = $5; - bufferSizeInBytes = pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodCount; - pDevice->opensl.pBufferCapture = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); - if (pDevice->opensl.pBufferCapture == NULL) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); - return MA_OUT_OF_MEMORY; - } - MA_ZERO_MEMORY(pDevice->opensl.pBufferCapture, bufferSizeInBytes); - } + if (typeof(window.miniaudio) === 'undefined') { + return -1; /* Context not initialized. */ + } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_SLDataFormat_PCM pcm; - SLDataSource source; - SLDataLocator_OutputMix outmixLocator; - SLDataSink sink; - SLAndroidConfigurationItf pPlayerConfig; + var device = {}; - ma_SLDataFormat_PCM_init__opensl(pDescriptorPlayback->format, pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate, pDescriptorPlayback->channelMap, &pcm); + /* First thing we need is an AudioContext. */ + var audioContextOptions = {}; + if (deviceType == window.miniaudio.device_type.playback && sampleRate != 0) { + audioContextOptions.sampleRate = sampleRate; + } - resultSL = (*g_maEngineSL)->CreateOutputMix(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pOutputMixObj, 0, NULL, NULL); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix."); - return ma_result_from_OpenSL(resultSL); - } + device.webaudio = new (window.AudioContext || window.webkitAudioContext)(audioContextOptions); + device.webaudio.suspend(); /* The AudioContext must be created in a suspended state. */ + device.state = window.miniaudio.device_status.stopped; - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Realize((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_BOOLEAN_FALSE); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object."); - return ma_result_from_OpenSL(resultSL); - } + /* + We need to create a ScriptProcessorNode. The channel situation is the same as the AudioWorklet path in that we + need to specify an output and configure the channel count there. + */ + var channelCountIn = 0; + var channelCountOut = channels; + if (deviceType != window.miniaudio.device_type.playback) { + channelCountIn = channels; + } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface."); - return ma_result_from_OpenSL(resultSL); - } + device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, channelCountIn, channelCountOut); - /* Set the output device. */ - if (pDescriptorPlayback->pDeviceID != NULL) { - SLuint32 deviceID_OpenSL = pDescriptorPlayback->pDeviceID->opensl; - MA_OPENSL_OUTPUTMIX(pDevice->opensl.pOutputMix)->ReRoute((SLOutputMixItf)pDevice->opensl.pOutputMix, 1, &deviceID_OpenSL); - } + /* The node processing callback. */ + device.scriptNode.onaudioprocess = function(e) { + if (device.intermediaryBufferView == null || device.intermediaryBufferView.length == 0 || device.intermediaryBufferView.buffer !== HEAPF32.buffer) { + device.intermediaryBufferView = new Float32Array(HEAPF32.buffer, Number(pIntermediaryBuffer), bufferSize * channels); + } - queue.numBuffers = pDescriptorPlayback->periodCount; + /* Do the capture side first. */ + if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { + /* The data must be interleaved before being processed miniaudio. */ + for (var iChannel = 0; iChannel < channels; iChannel += 1) { + var inputBuffer = e.inputBuffer.getChannelData(iChannel); + var intermediaryBuffer = device.intermediaryBufferView; - source.pLocator = &queue; - source.pFormat = (SLDataFormat_PCM*)&pcm; + for (var iFrame = 0; iFrame < bufferSize; iFrame += 1) { + intermediaryBuffer[iFrame*channels + iChannel] = inputBuffer[iFrame]; + } + } - outmixLocator.locatorType = SL_DATALOCATOR_OUTPUTMIX; - outmixLocator.outputMix = (SLObjectItf)pDevice->opensl.pOutputMixObj; + _ma_device_process_pcm_frames_capture__webaudio(pDevice, bufferSize, pIntermediaryBuffer); + } - sink.pLocator = &outmixLocator; - sink.pFormat = NULL; + if (deviceType == window.miniaudio.device_type.playback || deviceType == window.miniaudio.device_type.duplex) { + _ma_device_process_pcm_frames_playback__webaudio(pDevice, bufferSize, pIntermediaryBuffer); - resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); - if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { - /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ - pcm.formatType = SL_DATAFORMAT_PCM; - pcm.numChannels = 2; - ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; - pcm.bitsPerSample = 16; - pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ - pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); - } + for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + var outputBuffer = e.outputBuffer.getChannelData(iChannel); + var intermediaryBuffer = device.intermediaryBufferView; - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player."); - return ma_result_from_OpenSL(resultSL); - } + for (var iFrame = 0; iFrame < bufferSize; iFrame += 1) { + outputBuffer[iFrame] = intermediaryBuffer[iFrame*channels + iChannel]; + } + } + } else { + /* It's a capture-only device. Make sure the output is silenced. */ + for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + e.outputBuffer.getChannelData(iChannel).fill(0.0); + } + } + }; + /* Now we need to connect our node to the graph. */ + if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { + navigator.mediaDevices.getUserMedia({audio:true, video:false}) + .then(function(stream) { + device.streamNode = device.webaudio.createMediaStreamSource(stream); + device.streamNode.connect(device.scriptNode); + device.scriptNode.connect(device.webaudio.destination); + }) + .catch(function(error) { + console.log("Failed to get user media: " + error); + }); + } - /* Set the stream type before realizing the player. */ - if (pConfig->opensl.streamType != ma_opensl_stream_type_default) { - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDCONFIGURATION, &pPlayerConfig); - if (resultSL == SL_RESULT_SUCCESS) { - SLint32 streamType = ma_to_stream_type__opensl(pConfig->opensl.streamType); - resultSL = (*pPlayerConfig)->SetConfiguration(pPlayerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); - if (resultSL != SL_RESULT_SUCCESS) { - /* Failed to set the configuration. Just keep going. */ + if (deviceType == window.miniaudio.device_type.loopback) { + if (navigator.mediaDevices.getDisplayMedia === 'undefined') { + return -1; } + + navigator.mediaDevices.getDisplayMedia({audio:true, video:true}) + .then(function(stream) { + device.streamNode = device.webaudio.createMediaStreamSource(stream); + device.streamNode.connect(device.scriptNode); + device.scriptNode.connect(device.webaudio.destination); + }) + .catch(function(error) { + console.log("Failed to get user media: " + error); + }); + } + + if (deviceType == window.miniaudio.device_type.playback) { + device.scriptNode.connect(device.webaudio.destination); } - } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Realize((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_BOOLEAN_FALSE); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player."); - return ma_result_from_OpenSL(resultSL); - } + device.pDevice = pDevice; - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_PLAY, &pDevice->opensl.pAudioPlayer); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface."); - return ma_result_from_OpenSL(resultSL); - } + return window.miniaudio.track_device(device); + }, deviceType, channels, sampleRate, periodSizeInFrames, (ma_uintptr)pDeviceStateWebAudio->pIntermediaryBuffer, (ma_uintptr)pDevice); - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueuePlayback); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); - return ma_result_from_OpenSL(resultSL); + if (deviceIndex < 0) { + ma_free(pDeviceStateWebAudio, ma_device_get_allocation_callbacks(pDevice)); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } - resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, ma_buffer_queue_callback_playback__opensl_android, pDevice); - if (resultSL != SL_RESULT_SUCCESS) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); - return ma_result_from_OpenSL(resultSL); - } + pDeviceStateWebAudio->deviceIndex = deviceIndex; - /* The internal format is determined by the "pcm" object. */ - ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDescriptorPlayback->format, &pDescriptorPlayback->channels, &pDescriptorPlayback->sampleRate, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap)); + /* Grab the sample rate from the audio context directly. */ + sampleRate = (ma_uint32)EM_ASM_INT({ return window.miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); - /* Buffer. */ - pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); - pDevice->opensl.currentBufferIndexPlayback = 0; + if (pDescriptorCapture != NULL) { + pDescriptorCapture->format = ma_format_f32; + pDescriptorCapture->channels = channels; + pDescriptorCapture->sampleRate = sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); + pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; + pDescriptorCapture->periodCount = 1; + } - bufferSizeInBytes = pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodCount; - pDevice->opensl.pBufferPlayback = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); - if (pDevice->opensl.pBufferPlayback == NULL) { - ma_device_uninit__opensl(pDevice); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); - return MA_OUT_OF_MEMORY; + if (pDescriptorPlayback != NULL) { + pDescriptorPlayback->format = ma_format_f32; + pDescriptorPlayback->channels = channels; + pDescriptorPlayback->sampleRate = sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; + 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); } - MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, bufferSizeInBytes); } + #endif + + *ppDeviceState = pDeviceStateWebAudio; return MA_SUCCESS; -#else - return MA_NO_BACKEND; /* Non-Android implementations are not supported. */ -#endif } -static ma_result ma_device_start__opensl(ma_device* pDevice) +static void ma_device_uninit__webaudio(ma_device* pDevice) { - SLresult resultSL; - size_t periodSizeInBytes; - ma_uint32 iPeriod; + ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice); - MA_ASSERT(pDevice != NULL); + #if defined(MA_USE_AUDIO_WORKLETS) + { + EM_ASM({ + var device = window.miniaudio.get_device_by_index($0); - MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to start the device. */ - if (g_maOpenSLInitCounter == 0) { - return MA_INVALID_OPERATION; + if (device.streamNode !== undefined) { + device.streamNode.disconnect(); + device.streamNode = undefined; + } + + device.pDevice = undefined; + }, pDeviceStateWebAudio->deviceIndex); + + emscripten_destroy_web_audio_node(pDeviceStateWebAudio->audioWorklet); + emscripten_destroy_audio_context(pDeviceStateWebAudio->audioContext); + ma_aligned_free(pDeviceStateWebAudio->pStackBuffer, ma_device_get_allocation_callbacks(pDevice)); } + #else + { + EM_ASM({ + var device = window.miniaudio.get_device_by_index($0); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_RECORDING); - if (resultSL != SL_RESULT_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device."); - return ma_result_from_OpenSL(resultSL); - } + /* Make sure all nodes are disconnected and marked for collection. */ + if (device.scriptNode !== undefined) { + device.scriptNode.onaudioprocess = function(e) {}; /* We want to reset the callback to ensure it doesn't get called after AudioContext.close() has returned. Shouldn't happen since we're disconnecting, but just to be safe... */ + device.scriptNode.disconnect(); + device.scriptNode = undefined; + } - periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { - resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pDevice->opensl.pBufferCapture + (periodSizeInBytes * iPeriod), periodSizeInBytes); - if (resultSL != SL_RESULT_SUCCESS) { - MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device."); - return ma_result_from_OpenSL(resultSL); + if (device.streamNode !== undefined) { + device.streamNode.disconnect(); + device.streamNode = undefined; } - } - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_PLAYING); - if (resultSL != SL_RESULT_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device."); - return ma_result_from_OpenSL(resultSL); - } + /* + Stop the device. I think there is a chance the callback could get fired after calling this, hence why we want + to clear the callback before closing. + */ + device.webaudio.close(); + device.webaudio = undefined; + device.pDevice = undefined; + }, pDeviceStateWebAudio->deviceIndex); + } + #endif - /* In playback mode (no duplex) we need to load some initial buffers. In duplex mode we need to enqueue silent buffers. */ - if (pDevice->type == ma_device_type_duplex) { - MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); - } else { - ma_device__read_frames_from_client(pDevice, pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods, pDevice->opensl.pBufferPlayback); - } + /* Clean up the device on the JS side. */ + EM_ASM({ + window.miniaudio.untrack_device_by_index($0); + }, pDeviceStateWebAudio->deviceIndex); - periodSizeInBytes = pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; ++iPeriod) { - resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pDevice->opensl.pBufferPlayback + (periodSizeInBytes * iPeriod), periodSizeInBytes); - if (resultSL != SL_RESULT_SUCCESS) { - MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device."); - return ma_result_from_OpenSL(resultSL); - } - } + 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)); } - return MA_SUCCESS; + ma_free(pDeviceStateWebAudio->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice)); + ma_free(pDeviceStateWebAudio, ma_device_get_allocation_callbacks(pDevice)); } -static ma_result ma_device_drain__opensl(ma_device* pDevice, ma_device_type deviceType) +static ma_result ma_device_start__webaudio(ma_device* pDevice) { - SLAndroidSimpleBufferQueueItf pBufferQueue; + ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice); - MA_ASSERT(deviceType == ma_device_type_capture || deviceType == ma_device_type_playback); + EM_ASM({ + var device = window.miniaudio.get_device_by_index($0); + device.webaudio.resume(); + device.state = window.miniaudio.device_status.started; + }, pDeviceStateWebAudio->deviceIndex); - if (pDevice->type == ma_device_type_capture) { - pBufferQueue = (SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture; - pDevice->opensl.isDrainingCapture = MA_TRUE; - } else { - pBufferQueue = (SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback; - pDevice->opensl.isDrainingPlayback = MA_TRUE; - } + return MA_SUCCESS; +} - for (;;) { - SLAndroidSimpleBufferQueueState state; +static ma_result ma_device_stop__webaudio(ma_device* pDevice) +{ + ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice); - MA_OPENSL_BUFFERQUEUE(pBufferQueue)->GetState(pBufferQueue, &state); - if (state.count == 0) { - break; - } + /* + From the WebAudio API documentation for AudioContext.suspend(): - ma_sleep(10); - } + Suspends the progression of AudioContext's currentTime, allows any current context processing blocks that are already processed to be played to the + destination, and then allows the system to release its claim on audio hardware. - if (pDevice->type == ma_device_type_capture) { - pDevice->opensl.isDrainingCapture = MA_FALSE; - } else { - pDevice->opensl.isDrainingPlayback = MA_FALSE; - } + I read this to mean that "any current context processing blocks" are processed by suspend() - i.e. They are drained. We therefore shouldn't need to + do any kind of explicit draining. + */ + EM_ASM({ + var device = window.miniaudio.get_device_by_index($0); + device.webaudio.suspend(); + device.state = window.miniaudio.device_status.stopped; + }, pDeviceStateWebAudio->deviceIndex); + + ma_device_post_notification_stopped(pDevice); return MA_SUCCESS; } -static ma_result ma_device_stop__opensl(ma_device* pDevice) +static ma_result ma_device_step__webaudio(ma_device* pDevice, ma_blocking_mode blockingMode) { - SLresult resultSL; + ma_device_state_webaudio* pDeviceStateWebAudio = ma_device_get_backend_state__webaudio(pDevice); - MA_ASSERT(pDevice != NULL); + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } - MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it before stopping/uninitializing the device. */ - if (g_maOpenSLInitCounter == 0) { - return MA_INVALID_OPERATION; + /* + 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_MULTI_THREADED) { + MA_ASSERT(!"[Web Audio] Incorrectly calling step() in multi-threaded mode."); + return MA_ERROR; } - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_device_drain__opensl(pDevice, ma_device_type_capture); + /* 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); +} - resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); - if (resultSL != SL_RESULT_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device."); - return ma_result_from_OpenSL(resultSL); - } +#pragma GCC diagnostic pop /* -Wvariadic-macro-arguments-omitted, -Wdollar-in-identifier-extension */ - MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture); - } +static ma_device_backend_vtable ma_gDeviceBackendVTable_WebAudio = +{ + ma_backend_info__webaudio, + ma_context_init__webaudio, + ma_context_uninit__webaudio, + ma_context_enumerate_devices__webaudio, + ma_device_init__webaudio, + ma_device_uninit__webaudio, + ma_device_start__webaudio, + ma_device_stop__webaudio, + ma_device_step__webaudio, + NULL /* onDeviceWakeup */ +}; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_device_drain__opensl(pDevice, ma_device_type_playback); +ma_device_backend_vtable* ma_device_backend_webaudio = &ma_gDeviceBackendVTable_WebAudio; +#else +ma_device_backend_vtable* ma_device_backend_webaudio = NULL; +#endif /* MA_HAS_WEBAUDIO */ - resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); - if (resultSL != SL_RESULT_SUCCESS) { - ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device."); - return ma_result_from_OpenSL(resultSL); - } +MA_API ma_device_backend_vtable* ma_webaudio_get_vtable(void) +{ + return ma_device_backend_webaudio; +} - MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback); - } +MA_API ma_context_config_webaudio ma_context_config_webaudio_init(void) +{ + ma_context_config_webaudio config; + + MA_ZERO_OBJECT(&config); - /* Make sure the client is aware that the device has stopped. There may be an OpenSL|ES callback for this, but I haven't found it. */ - ma_device__on_notification_stopped(pDevice); + return config; +} - return MA_SUCCESS; +MA_API ma_device_config_webaudio ma_device_config_webaudio_init(void) +{ + ma_device_config_webaudio config; + + MA_ZERO_OBJECT(&config); + + return config; } -static ma_result ma_context_uninit__opensl(ma_context* pContext) + +/* BEG miniaudio_dreamcast.c */ +#if defined(MA_HAS_DREAMCAST) +#include +#include +#include + +typedef struct { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_opensl); - (void)pContext; + int _unused; +} ma_context_state_dreamcast; - /* Uninit global data. */ - ma_spinlock_lock(&g_maOpenSLSpinlock); - { - MA_ASSERT(g_maOpenSLInitCounter > 0); /* If you've triggered this, it means you have ma_context_init/uninit mismatch. Each successful call to ma_context_init() must be matched up with a call to ma_context_uninit(). */ +typedef struct +{ + ma_int8 voiceChannel; + ma_int8 subBufferIndex; /* Flip-flops between 0 and 1 and is used to determine which half of the buffer needs to be updated with fresh data. */ + ma_int8 padding[2]; + ma_uint32 buffer; /* Memory allocation on the SPU. Returned by snd_mem_malloc(). */ + void* pIntermediaryBuffer; /* Big enough for one period (one half of the buffer). Used for transferring to the SPU buffer. */ +} ma_device_state_dreamcast; - g_maOpenSLInitCounter -= 1; - if (g_maOpenSLInitCounter == 0) { - (*g_maEngineObjectSL)->Destroy(g_maEngineObjectSL); - } - } - ma_spinlock_unlock(&g_maOpenSLSpinlock); - return MA_SUCCESS; +static ma_context_state_dreamcast* ma_context_get_backend_state__dreamcast(ma_context* pContext) +{ + return (ma_context_state_dreamcast*)ma_context_get_backend_state(pContext); } -static ma_result ma_dlsym_SLInterfaceID__opensl(ma_context* pContext, const char* pName, ma_handle* pHandle) +static ma_device_state_dreamcast* ma_device_get_backend_state__dreamcast(ma_device* pDevice) { - /* We need to return an error if the symbol cannot be found. This is important because there have been reports that some symbols do not exist. */ - ma_handle* p = (ma_handle*)ma_dlsym(ma_context_get_log(pContext), pContext->opensl.libOpenSLES, pName); - if (p == NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol %s", pName); - return MA_NO_BACKEND; + return (ma_device_state_dreamcast*)ma_device_get_backend_state(pDevice); +} + + +static ma_uint32 ma_format_to_dreamcast(ma_format format) +{ + if (format == ma_format_u8) { + return AICA_SM_8BIT; + } else { + return AICA_SM_16BIT; } +} - *pHandle = *p; - return MA_SUCCESS; + +static void ma_backend_info__dreamcast(ma_device_backend_info* pBackendInfo) +{ + MA_ASSERT(pBackendInfo != NULL); + pBackendInfo->pName = "Dreamcast"; } -static ma_result ma_context_init_engine_nolock__opensl(ma_context* pContext) +static ma_result ma_context_init__dreamcast(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) { - g_maOpenSLInitCounter += 1; - if (g_maOpenSLInitCounter == 1) { - SLresult resultSL; + ma_context_state_dreamcast* pContextStateDreamcast; + const ma_context_config_dreamcast* pContextConfigDreamcast = (ma_context_config_dreamcast*)pContextBackendConfig; + ma_log* pLog = ma_context_get_log(pContext); - resultSL = ((ma_slCreateEngine_proc)pContext->opensl.slCreateEngine)(&g_maEngineObjectSL, 0, NULL, 0, NULL, NULL); - if (resultSL != SL_RESULT_SUCCESS) { - g_maOpenSLInitCounter -= 1; - return ma_result_from_OpenSL(resultSL); - } + /* The context config is not currently being used for this backend. */ + (void)pContextConfigDreamcast; + (void)pLog; - (*g_maEngineObjectSL)->Realize(g_maEngineObjectSL, SL_BOOLEAN_FALSE); + if (!pContextConfigDreamcast->noInit) { + snd_init(); + } - resultSL = (*g_maEngineObjectSL)->GetInterface(g_maEngineObjectSL, (SLInterfaceID)pContext->opensl.SL_IID_ENGINE, &g_maEngineSL); - if (resultSL != SL_RESULT_SUCCESS) { - (*g_maEngineObjectSL)->Destroy(g_maEngineObjectSL); - g_maOpenSLInitCounter -= 1; - return ma_result_from_OpenSL(resultSL); + /* We don't actually do anything with context state so there's no need wasting time and memory allocating anything. */ + #if 0 + { + pContextStateDreamcast = (ma_context_state_dreamcast*)ma_calloc(sizeof(*pContextStateDreamcast), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateDreamcast == NULL) { + return MA_OUT_OF_MEMORY; } } + #else + { + pContextStateDreamcast = NULL; + } + #endif + + *ppContextState = pContextStateDreamcast; return MA_SUCCESS; } -static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static void ma_context_uninit__dreamcast(ma_context* pContext) { - ma_result result; + ma_context_state_dreamcast* pContextStateDreamcast = ma_context_get_backend_state__dreamcast(pContext); -#if !defined(MA_NO_RUNTIME_LINKING) - size_t i; - const char* libOpenSLESNames[] = { - "libOpenSLES.so" - }; -#endif + #if 0 + ma_free(pContextStateDreamcast, ma_context_get_allocation_callbacks(pContext)); + #else + (void)pContextStateDreamcast; + #endif +} - MA_ASSERT(pContext != NULL); +static ma_result ma_context_enumerate_devices__dreamcast(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData) +{ + ma_context_state_dreamcast* pContextStateDreamcast = ma_context_get_backend_state__dreamcast(pContext); + ma_device_info deviceInfo; + ma_device_enumeration_result enumerationResult; - (void)pConfig; + (void)pContextStateDreamcast; -#if !defined(MA_NO_RUNTIME_LINKING) - /* - Dynamically link against libOpenSLES.so. I have now had multiple reports that SL_IID_ANDROIDSIMPLEBUFFERQUEUE cannot be found. One - report was happening at compile time and another at runtime. To try working around this, I'm going to link to libOpenSLES at runtime - and extract the symbols rather than reference them directly. This should, hopefully, fix these issues as the compiler won't see any - references to the symbols and will hopefully skip the checks. - */ - for (i = 0; i < ma_countof(libOpenSLESNames); i += 1) { - pContext->opensl.libOpenSLES = ma_dlopen(ma_context_get_log(pContext), libOpenSLESNames[i]); - if (pContext->opensl.libOpenSLES != NULL) { - break; - } - } + /* We're only outputting a single default playback device. */ + MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.isDefault = MA_TRUE; + deviceInfo.id.custom.i = 0; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1); - if (pContext->opensl.libOpenSLES == NULL) { - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Could not find libOpenSLES.so"); - return MA_NO_BACKEND; + /* We can do u8 and s16, mono or stereo, and 11025, 22050 or 44100. */ + ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 2, 11025, 44100); + ma_device_info_add_native_data_format(&deviceInfo, ma_format_u8, 1, 2, 11025, 44100); + + /* The return value of the callback does not matter here because we're only enumerating a single device, but we're putting the checks down anyway for completeness. */ + enumerationResult = callback(ma_device_type_playback, &deviceInfo, pCallbackUserData); + if (enumerationResult == MA_DEVICE_ENUMERATION_ABORT) { + return MA_SUCCESS; } - result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_ENGINE", &pContext->opensl.SL_IID_ENGINE); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - return result; + return MA_SUCCESS; +} + +static ma_result ma_device_init__dreamcast(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + ma_device_state_dreamcast* pDeviceStateDreamcast; + ma_device_config_dreamcast* pDeviceConfigDreamcast = (ma_device_config_dreamcast*)pDeviceBackendConfig; + ma_context_state_dreamcast* pContextStateDreamcast = ma_context_get_backend_state__dreamcast(ma_device_get_context(pDevice)); + ma_device_config_dreamcast defaultConfig; + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_uint32 bufferSizeInBytes; + ma_uint32 bpf; + + (void)pContextStateDreamcast; + (void)pDescriptorCapture; + + if (pDeviceConfigDreamcast == NULL) { + defaultConfig = ma_device_config_dreamcast_init(0); + pDeviceConfigDreamcast = &defaultConfig; } - result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_AUDIOIODEVICECAPABILITIES", &pContext->opensl.SL_IID_AUDIOIODEVICECAPABILITIES); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - return result; + /* Can only support playback. */ + if (deviceType != ma_device_type_playback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE", &pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - return result; + pDeviceStateDreamcast = (ma_device_state_dreamcast*)ma_calloc(sizeof(*pDeviceStateDreamcast), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateDreamcast == NULL) { + return MA_OUT_OF_MEMORY; } - result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_RECORD", &pContext->opensl.SL_IID_RECORD); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - return result; + pDeviceStateDreamcast->subBufferIndex = 1; /* Should start at one. */ + + /* Default to s16 for the format. Can support u8 and s16. */ + if (pDescriptorPlayback->format != ma_format_u8 /*&& pDescriptorPlayback->format != ma_format_s16*/) { + pDescriptorPlayback->format = ma_format_s16; } - result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_PLAY", &pContext->opensl.SL_IID_PLAY); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - return result; + /* The channels needs to be mono or stereo. */ + if (pDescriptorPlayback->channels == 0 || pDescriptorPlayback->channels > 2) { + pDescriptorPlayback->channels = 2; } - result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_OUTPUTMIX", &pContext->opensl.SL_IID_OUTPUTMIX); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - return result; + /* Now that we know our channel count we need to verify our voice channel index. There is a maximum of 64. Stereo uses two of them. */ + if (pDeviceConfigDreamcast->voiceChannel + pDescriptorPlayback->channels > 64) { + return MA_INVALID_ARGS; /* Voice channel exceeds limit. */ } - result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_ANDROIDCONFIGURATION", &pContext->opensl.SL_IID_ANDROIDCONFIGURATION); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - return result; + pDeviceStateDreamcast->voiceChannel = (ma_uint8)pDeviceConfigDreamcast->voiceChannel; + + /* The sample rate is always 44100, 22050 or 11025. */ + if (pDescriptorPlayback->sampleRate != 44100 && pDescriptorPlayback->sampleRate != 22050 && pDescriptorPlayback->sampleRate != 11025) { + pDescriptorPlayback->sampleRate = 44100; } - pContext->opensl.slCreateEngine = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->opensl.libOpenSLES, "slCreateEngine"); - if (pContext->opensl.slCreateEngine == NULL) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol slCreateEngine."); - return MA_NO_BACKEND; + /* Channel map. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); + + /* The period count is always 2 (double buffered). */ + pDescriptorPlayback->periodCount = 2; + + /* + The buffer cannot exceed 65534. Since we'll always be using 2 periods (double buffering), the maximum period + size in frames is 32767 = 65534/2. We're also going to align this to 32 bytes. + */ + pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate); + if (pDescriptorPlayback->periodSizeInFrames > 32767) { + pDescriptorPlayback->periodSizeInFrames = 32767; + } + if (pDescriptorPlayback->periodSizeInFrames < 1536) { + pDescriptorPlayback->periodSizeInFrames = 1536; } -#else - pContext->opensl.SL_IID_ENGINE = (ma_handle)SL_IID_ENGINE; - pContext->opensl.SL_IID_AUDIOIODEVICECAPABILITIES = (ma_handle)SL_IID_AUDIOIODEVICECAPABILITIES; - pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (ma_handle)SL_IID_ANDROIDSIMPLEBUFFERQUEUE; - pContext->opensl.SL_IID_RECORD = (ma_handle)SL_IID_RECORD; - pContext->opensl.SL_IID_PLAY = (ma_handle)SL_IID_PLAY; - pContext->opensl.SL_IID_OUTPUTMIX = (ma_handle)SL_IID_OUTPUTMIX; - pContext->opensl.SL_IID_ANDROIDCONFIGURATION = (ma_handle)SL_IID_ANDROIDCONFIGURATION; - pContext->opensl.slCreateEngine = (ma_proc)slCreateEngine; -#endif + bpf = ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels); + bufferSizeInBytes = (pDescriptorPlayback->periodSizeInFrames & ~31) * bpf * pDescriptorPlayback->periodCount; + pDescriptorPlayback->periodSizeInFrames = bufferSizeInBytes / bpf / pDescriptorPlayback->periodCount; - /* Initialize global data first if applicable. */ - ma_spinlock_lock(&g_maOpenSLSpinlock); - { - result = ma_context_init_engine_nolock__opensl(pContext); + /* We need an intermediary buffer for transferring to the SPU buffer. */ + pDeviceStateDreamcast->pIntermediaryBuffer = ma_aligned_malloc(pDescriptorPlayback->periodSizeInFrames * bpf, 32, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateDreamcast->pIntermediaryBuffer == NULL) { + ma_free(pDeviceStateDreamcast, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; } - ma_spinlock_unlock(&g_maOpenSLSpinlock); - if (result != MA_SUCCESS) { - ma_dlclose(ma_context_get_log(pContext), pContext->opensl.libOpenSLES); - ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Failed to initialize OpenSL engine."); - return result; + MA_ZERO_MEMORY(pDeviceStateDreamcast->pIntermediaryBuffer, pDescriptorPlayback->periodSizeInFrames * bpf); + + /* Now we can allocate our memory for the buffer on the SPU side. */ + pDeviceStateDreamcast->buffer = snd_mem_malloc(bufferSizeInBytes); + if (pDeviceStateDreamcast->buffer == 0) { + ma_free(pDeviceStateDreamcast->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice)); + ma_free(pDeviceStateDreamcast, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; } - pCallbacks->onContextInit = ma_context_init__opensl; - pCallbacks->onContextUninit = ma_context_uninit__opensl; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__opensl; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__opensl; - pCallbacks->onDeviceInit = ma_device_init__opensl; - pCallbacks->onDeviceUninit = ma_device_uninit__opensl; - pCallbacks->onDeviceStart = ma_device_start__opensl; - pCallbacks->onDeviceStop = ma_device_stop__opensl; - pCallbacks->onDeviceRead = NULL; /* Not needed because OpenSL|ES is asynchronous. */ - pCallbacks->onDeviceWrite = NULL; /* Not needed because OpenSL|ES is asynchronous. */ - pCallbacks->onDeviceDataLoop = NULL; /* Not needed because OpenSL|ES is asynchronous. */ + /* We will fill the buffer with initial silence. */ + spu_memset_sq(pDeviceStateDreamcast->buffer, 0, bufferSizeInBytes); /* Can we safely assume the returned value from snd_mem_malloc() is aligned to 32 bytes? */ + + + /* Done. */ + *ppDeviceState = pDeviceStateDreamcast; return MA_SUCCESS; } -#endif /* OpenSL|ES */ +static void ma_device_uninit__dreamcast(ma_device* pDevice) +{ + ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice); -/****************************************************************************** + snd_mem_free(pDeviceStateDreamcast->buffer); + ma_aligned_free(pDeviceStateDreamcast->pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice)); + ma_free(pDeviceStateDreamcast, ma_device_get_allocation_callbacks(pDevice)); +} -Web Audio Backend +static void ma_device_fill_sub_buffer__dreamcast(ma_device* pDevice, int subBufferIndex) +{ + ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice); + ma_uint32 offsetInSamples = 0; + ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); -******************************************************************************/ -#ifdef MA_HAS_WEBAUDIO -#include + /* The offset is in samples, not frames. */ + offsetInSamples = subBufferIndex * pDevice->playback.internalPeriodSizeInFrames; + if (pDevice->playback.internalFormat == ma_format_s16) { + offsetInSamples *= sizeof(ma_int16); + } -#ifndef MA_EMSCRIPTEN_MAJOR - #if defined(__EMSCRIPTEN_MAJOR__) - #define MA_EMSCRIPTEN_MAJOR __EMSCRIPTEN_MAJOR__ - #else - #define MA_EMSCRIPTEN_MAJOR __EMSCRIPTEN_major__ - #endif -#endif -#ifndef MA_EMSCRIPTEN_MINOR - #if defined(__EMSCRIPTEN_MINOR__) - #define MA_EMSCRIPTEN_MINOR __EMSCRIPTEN_MINOR__ - #else - #define MA_EMSCRIPTEN_MINOR __EMSCRIPTEN_minor__ - #endif -#endif -#ifndef MA_EMSCRIPTEN_TINY - #if defined(__EMSCRIPTEN_TINY__) - #define MA_EMSCRIPTEN_TINY __EMSCRIPTEN_TINY__ - #else - #define MA_EMSCRIPTEN_TINY __EMSCRIPTEN_tiny__ - #endif -#endif + /* Do the data callback. */ + ma_device_handle_backend_data_callback(pDevice, pDeviceStateDreamcast->pIntermediaryBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); -#if (MA_EMSCRIPTEN_MAJOR > 3) || (MA_EMSCRIPTEN_MAJOR == 3 && (MA_EMSCRIPTEN_MINOR > 1 || (MA_EMSCRIPTEN_MINOR == 1 && MA_EMSCRIPTEN_TINY >= 32))) - #include - #define MA_SUPPORT_AUDIO_WORKLETS + /* If our format is u8 we want to convert this to signed. */ + if (pDevice->playback.internalFormat == ma_format_u8) { + ma_uint32 sampleCount = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalChannels; + ma_uint32 iSample; - #if (MA_EMSCRIPTEN_MAJOR > 3) || (MA_EMSCRIPTEN_MAJOR == 3 && (MA_EMSCRIPTEN_MINOR > 1 || (MA_EMSCRIPTEN_MINOR == 1 && MA_EMSCRIPTEN_TINY >= 70))) - #define MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE - #endif -#endif + for (iSample = 0; iSample < sampleCount; iSample += 1) { + ((ma_int8*)pDeviceStateDreamcast->pIntermediaryBuffer)[iSample] = (ma_int8)128 - ((ma_uint8*)pDeviceStateDreamcast->pIntermediaryBuffer)[iSample]; + } -#if defined(MA_ENABLE_AUDIO_WORKLETS) && defined(MA_SUPPORT_AUDIO_WORKLETS) - #define MA_USE_AUDIO_WORKLETS -#endif + if (pDevice->playback.internalChannels == 1) { + spu_memload_sq(pDeviceStateDreamcast->buffer + offsetInSamples, pDeviceStateDreamcast->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames * bpf); + } else { + /* There is no snd_pcm8_split_sq() from what I can tell. Might need to figure that out for myself. */ + ma_uint32 l = pDeviceStateDreamcast->buffer + offsetInSamples; + ma_uint32 r = pDeviceStateDreamcast->buffer + (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * bpf) + offsetInSamples; + ma_uint32 framesWritten = 0; + while (framesWritten < pDevice->playback.internalPeriodSizeInFrames) { + ma_uint32 framesToWrite; + ma_int8 tmpl[256] __attribute__((aligned(32))); + ma_int8 tmpr[256] __attribute__((aligned(32))); + void* tmp[2]; + + framesToWrite = pDevice->playback.internalPeriodSizeInFrames - framesWritten; + if (framesToWrite > ma_countof(tmpl)) { + framesToWrite = ma_countof(tmpl); + } + + tmp[0] = tmpl; + tmp[1] = tmpr; + ma_deinterleave_pcm_frames(ma_format_u8, 2, framesToWrite, ma_offset_ptr(pDeviceStateDreamcast->pIntermediaryBuffer, framesWritten * bpf), tmp); + spu_memload_sq(l + (framesWritten * sizeof(ma_int8)), tmpl, framesToWrite * sizeof(ma_int8)); + spu_memload_sq(r + (framesWritten * sizeof(ma_int8)), tmpr, framesToWrite * sizeof(ma_int8)); -/* The thread stack size must be a multiple of 16. */ -#ifndef MA_AUDIO_WORKLETS_THREAD_STACK_SIZE -#define MA_AUDIO_WORKLETS_THREAD_STACK_SIZE 131072 -#endif + framesWritten += framesToWrite; + } + } + } else { + if (pDevice->playback.internalChannels == 1) { + spu_memload_sq(pDeviceStateDreamcast->buffer + offsetInSamples, pDeviceStateDreamcast->pIntermediaryBuffer, pDevice->playback.internalPeriodSizeInFrames * bpf); + } else { + ma_uint32 l = pDeviceStateDreamcast->buffer + offsetInSamples; + ma_uint32 r = pDeviceStateDreamcast->buffer + (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * bpf) + offsetInSamples; -#if defined(MA_USE_AUDIO_WORKLETS) -#define MA_WEBAUDIO_LATENCY_HINT_BALANCED "balanced" -#define MA_WEBAUDIO_LATENCY_HINT_INTERACTIVE "interactive" -#define MA_WEBAUDIO_LATENCY_HINT_PLAYBACK "playback" -#endif + /* + There is an endian bug in snd_pcm16_split_sq(). It merges two signed 16-bit samples into a single 32-bit variable in preparation + for transfer, but the ordering is the wrong way around. This results in a low quality sound. I'm working around this by splitting + this in one pass and then doing the transfer in a second pass. Later on I would like to update this to do the splitting and copy + in a single pass to save on some data movement. + */ + #if 0 + snd_pcm16_split_sq((uint32_t*)pDeviceStateDreamcast->pIntermediaryBuffer, l, r, pDevice->playback.internalPeriodSizeInFrames * bpf); + #else + ma_uint32 framesWritten = 0; + while (framesWritten < pDevice->playback.internalPeriodSizeInFrames) { + ma_uint32 framesToWrite; + ma_int16 tmpl[256] __attribute__((aligned(32))); + ma_int16 tmpr[256] __attribute__((aligned(32))); + void* tmp[2]; -static ma_bool32 ma_is_capture_supported__webaudio() -{ - return EM_ASM_INT({ - return (navigator.mediaDevices !== undefined && navigator.mediaDevices.getUserMedia !== undefined); - }, 0) != 0; /* Must pass in a dummy argument for C99 compatibility. */ + framesToWrite = pDevice->playback.internalPeriodSizeInFrames - framesWritten; + if (framesToWrite > ma_countof(tmpl)) { + framesToWrite = ma_countof(tmpl); + } + + tmp[0] = tmpl; + tmp[1] = tmpr; + ma_deinterleave_pcm_frames(ma_format_s16, 2, framesToWrite, ma_offset_ptr(pDeviceStateDreamcast->pIntermediaryBuffer, framesWritten * bpf), tmp); + spu_memload_sq(l + (framesWritten * sizeof(ma_int16)), tmpl, framesToWrite * sizeof(ma_int16)); + spu_memload_sq(r + (framesWritten * sizeof(ma_int16)), tmpr, framesToWrite * sizeof(ma_int16)); + + framesWritten += framesToWrite; + } + #endif + } + } } -#ifdef __cplusplus -extern "C" { -#endif -void* EMSCRIPTEN_KEEPALIVE ma_malloc_emscripten(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) +static ma_result ma_device_start__dreamcast(ma_device* pDevice) { - return ma_malloc(sz, pAllocationCallbacks); + ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice); + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + /* Make sure our sub-buffer index is reset or else we'll get a glitch when starting. */ + pDeviceStateDreamcast->subBufferIndex = 1; + + /* + We'll want to fire the data callback and fill our buffer with an initial chunk of data. If we don't do this we'll end + up with one period of silence before hearing any audio. + */ + ma_device_fill_sub_buffer__dreamcast(pDevice, 0); + + /* With the buffer pre-filled we can now start the voices. */ + memset(tmp, 0, sizeof(tmp)); + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + chan->cmd = AICA_CH_CMD_START; + chan->base = pDeviceStateDreamcast->buffer; + chan->type = ma_format_to_dreamcast(pDevice->playback.internalFormat); + chan->length = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods; + chan->loop = 1; + chan->loopstart = 0; + chan->loopend = pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods; + chan->freq = pDevice->playback.internalSampleRate; + chan->vol = 255; /* 0..255 */ + chan->pan = 128; /* 0..255 (0=left, 128=center, 255=right) */ + chan->pos = 0; + + if (pDevice->playback.internalChannels == 1) { + snd_sh4_to_aica(tmp, cmd->size); + } else { + snd_sh4_to_aica_stop(); + { + /* Left. Hard pan to the left. */ + cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 0; + chan->base = pDeviceStateDreamcast->buffer + 0; + chan->pan = 0; + snd_sh4_to_aica(tmp, cmd->size); + + /* Right. Hard pan to the right. */ + cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 1; + chan->base = pDeviceStateDreamcast->buffer + (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + chan->pan = 255; + snd_sh4_to_aica(tmp, cmd->size); + } + snd_sh4_to_aica_start(); + } + + return MA_SUCCESS; } -void EMSCRIPTEN_KEEPALIVE ma_free_emscripten(void* p, const ma_allocation_callbacks* pAllocationCallbacks) +static ma_result ma_device_stop__dreamcast(ma_device* pDevice) { - ma_free(p, pAllocationCallbacks); + ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice); + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + memset(tmp, 0, sizeof(tmp)); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + chan->cmd = AICA_CH_CMD_STOP; + + if (pDevice->playback.internalChannels == 1) { + snd_sh4_to_aica(tmp, cmd->size); + } else { + snd_sh4_to_aica_stop(); + { + /* Left. */ + cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 0; + snd_sh4_to_aica(tmp, cmd->size); + + /* Right. */ + cmd->cmd_id = (uint32_t)pDeviceStateDreamcast->voiceChannel + 1; + snd_sh4_to_aica(tmp, cmd->size); + } + snd_sh4_to_aica_start(); + } + + return MA_SUCCESS; } -void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_capture__webaudio(ma_device* pDevice, int frameCount, float* pFrames) +static ma_result ma_device_step__dreamcast(ma_device* pDevice, ma_blocking_mode blockingMode) { - ma_device_handle_backend_data_callback(pDevice, NULL, pFrames, (ma_uint32)frameCount); + ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice); + + /* Keep looping until we have processed some data, or we're in non-blocking mode. */ + for (;;) { + int pos; + ma_int8 currentSubBufferIndex; + ma_bool32 wasDataProcessed = MA_FALSE; + + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } + + /* We're currently using a poll/sleep pattern. Should try figuring out a better way to do this later. */ + pos = (int)snd_get_pos(pDeviceStateDreamcast->voiceChannel); + if (pos < pDevice->playback.internalPeriodSizeInFrames) { + currentSubBufferIndex = 0; + } else { + currentSubBufferIndex = 1; + } + + if (pDeviceStateDreamcast->subBufferIndex != currentSubBufferIndex) { + ma_device_fill_sub_buffer__dreamcast(pDevice, pDeviceStateDreamcast->subBufferIndex); + pDeviceStateDreamcast->subBufferIndex = currentSubBufferIndex; + + wasDataProcessed = MA_TRUE; + } + + if (wasDataProcessed) { + break; + } + + /* Getting here means there is no data to process. In non blocking mode we just bomb out. In blocking mode we wait a bit. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + break; + } + + /* Getting here means we're in blocking mode. */ + ma_sleep(1); + } + + return MA_SUCCESS; } -void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_playback__webaudio(ma_device* pDevice, int frameCount, float* pFrames) +static void ma_device_wakeup__dreamcast(ma_device* pDevice) { - ma_device_handle_backend_data_callback(pDevice, pFrames, NULL, (ma_uint32)frameCount); + ma_device_state_dreamcast* pDeviceStateDreamcast = ma_device_get_backend_state__dreamcast(pDevice); + + /* Nothing to do. */ + (void)pDeviceStateDreamcast; } -#ifdef __cplusplus + +static ma_device_backend_vtable ma_gDeviceBackendVTable_Dreamcast = +{ + ma_backend_info__dreamcast, + ma_context_init__dreamcast, + ma_context_uninit__dreamcast, + ma_context_enumerate_devices__dreamcast, + ma_device_init__dreamcast, + ma_device_uninit__dreamcast, + ma_device_start__dreamcast, + ma_device_stop__dreamcast, + ma_device_step__dreamcast, + ma_device_wakeup__dreamcast +}; + +ma_device_backend_vtable* ma_device_backend_dreamcast = &ma_gDeviceBackendVTable_Dreamcast; +#else +ma_device_backend_vtable* ma_device_backend_dreamcast = NULL; +#endif /* MA_HAS_DREAMCAST */ + + +MA_API ma_device_backend_vtable* ma_dreamcast_get_vtable(void) +{ + return ma_device_backend_dreamcast; } -#endif -static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +MA_API ma_context_config_dreamcast ma_context_config_dreamcast_init(void) { - ma_bool32 cbResult = MA_TRUE; + ma_context_config_dreamcast config; - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); + MA_ZERO_OBJECT(&config); - /* Only supporting default devices for now. */ + return config; +} - /* Playback. */ - if (cbResult) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - deviceInfo.isDefault = MA_TRUE; /* Only supporting default devices. */ - cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - } +MA_API ma_device_config_dreamcast ma_device_config_dreamcast_init(int voiceChannel) +{ + ma_device_config_dreamcast config; - /* Capture. */ - if (cbResult) { - if (ma_is_capture_supported__webaudio()) { - ma_device_info deviceInfo; - MA_ZERO_OBJECT(&deviceInfo); - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - deviceInfo.isDefault = MA_TRUE; /* Only supporting default devices. */ - cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); - } - } + MA_ZERO_OBJECT(&config); + config.voiceChannel = voiceChannel; - return MA_SUCCESS; + return config; } +/* END miniaudio_dreamcast.c */ + -static ma_result ma_context_get_device_info__webaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +/* BEG miniaudio_xaudio.c */ +#if defined(MA_HAS_XAUDIO) +#include +#include + +typedef struct ma_context_state_xaudio { - MA_ASSERT(pContext != NULL); + int _unused; +} ma_context_state_xaudio; - if (deviceType == ma_device_type_capture && !ma_is_capture_supported__webaudio()) { - return MA_NO_DEVICE; - } +typedef struct ma_device_state_xaudio +{ + void* pBuffer; + ma_uint16 subBufferIndex; + ma_uint16 subBufferCount; + ma_uint16 emptyBufferCount; /* How many sub-buffers are available for filling. */ + ma_spinlock lock; /* Using a dumb lock for thread-safety for now, but this can be optimized by having subBufferIndex and emptyBufferCount encoded into a single 32-bit value and using it atomically. */ + ma_timer timer; + double queuedAudioTime; + double periodTimeInSeconds; +} ma_device_state_xaudio; - MA_ZERO_MEMORY(pDeviceInfo->id.webaudio, sizeof(pDeviceInfo->id.webaudio)); +static ma_context_state_xaudio* ma_context_get_backend_state__xaudio(ma_context* pContext) +{ + return (ma_context_state_xaudio*)ma_context_get_backend_state(pContext); +} - /* Only supporting default devices for now. */ - (void)pDeviceID; - if (deviceType == ma_device_type_playback) { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } +static ma_device_state_xaudio* ma_device_get_backend_state__xaudio(ma_device* pDevice) +{ + return (ma_device_state_xaudio*)ma_device_get_backend_state(pDevice); +} - /* Only supporting default devices. */ - pDeviceInfo->isDefault = MA_TRUE; - /* Web Audio can support any number of channels and sample rates. It only supports f32 formats, however. */ - pDeviceInfo->nativeDataFormats[0].flags = 0; - pDeviceInfo->nativeDataFormats[0].format = ma_format_unknown; - pDeviceInfo->nativeDataFormats[0].channels = 0; /* All channels are supported. */ - pDeviceInfo->nativeDataFormats[0].sampleRate = EM_ASM_INT({ - try { - var temp = new (window.AudioContext || window.webkitAudioContext)(); - var sampleRate = temp.sampleRate; - temp.close(); - return sampleRate; - } catch(e) { - return 0; - } - }, 0); /* Must pass in a dummy argument for C99 compatibility. */ +static void ma_backend_info__xaudio(ma_device_backend_info* pBackendInfo) +{ + pBackendInfo->pName = "XAudio"; +} + +static ma_result ma_context_init__xaudio(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_xaudio* pContextStateXAudio; + const ma_context_config_xaudio* pContextConfigXAudio = (ma_context_config_xaudio*)pContextBackendConfig; - if (pDeviceInfo->nativeDataFormats[0].sampleRate == 0) { - return MA_NO_DEVICE; - } + /* The context config is not currently being used for this backend. */ + (void)pContextConfigXAudio; - pDeviceInfo->nativeDataFormatCount = 1; + #if 0 + { + pContextStateXAudio = (ma_context_state_xaudio*)ma_calloc(sizeof(*pContextStateXAudio), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateXAudio == NULL) { + return MA_OUT_OF_MEMORY; + } + } + #else + { + (void)pContextStateXAudio; + } + #endif return MA_SUCCESS; } -static ma_result ma_device_uninit__webaudio(ma_device* pDevice) +static void ma_context_uninit__xaudio(ma_context* pContext) { - MA_ASSERT(pDevice != NULL); + ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(pContext); - #if defined(MA_USE_AUDIO_WORKLETS) + #if 0 { - EM_ASM({ - var device = window.miniaudio.get_device_by_index($0); - - if (device.streamNode !== undefined) { - device.streamNode.disconnect(); - device.streamNode = undefined; - } - - device.pDevice = undefined; - }, pDevice->webaudio.deviceIndex); - - emscripten_destroy_web_audio_node(pDevice->webaudio.audioWorklet); - emscripten_destroy_audio_context(pDevice->webaudio.audioContext); - ma_free(pDevice->webaudio.pStackBuffer, &pDevice->pContext->allocationCallbacks); + ma_free(pContextStateSDL, ma_context_get_allocation_callbacks(pContext)); } #else { - EM_ASM({ - var device = window.miniaudio.get_device_by_index($0); + (void)pContextStateXAudio; + } + #endif +} - /* Make sure all nodes are disconnected and marked for collection. */ - if (device.scriptNode !== undefined) { - device.scriptNode.onaudioprocess = function(e) {}; /* We want to reset the callback to ensure it doesn't get called after AudioContext.close() has returned. Shouldn't happen since we're disconnecting, but just to be safe... */ - device.scriptNode.disconnect(); - device.scriptNode = undefined; - } +static ma_result ma_context_enumerate_devices__xaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData) +{ + ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(pContext); + ma_device_info deviceInfo; + ma_device_enumeration_result enumerationResult; - if (device.streamNode !== undefined) { - device.streamNode.disconnect(); - device.streamNode = undefined; - } + (void)pContextStateXAudio; - /* - Stop the device. I think there is a chance the callback could get fired after calling this, hence why we want - to clear the callback before closing. - */ - device.webaudio.close(); - device.webaudio = undefined; - device.pDevice = undefined; - }, pDevice->webaudio.deviceIndex); - } - #endif + /* We're only outputting a single default playback device. */ + MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.isDefault = MA_TRUE; + deviceInfo.id.custom.i = 0; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1); - /* Clean up the device on the JS side. */ - EM_ASM({ - window.miniaudio.untrack_device_by_index($0); - }, pDevice->webaudio.deviceIndex); + /* Audio is always s16, stereo, 48000. */ + ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 2, 2, 48000, 48000); - ma_free(pDevice->webaudio.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); + /* The return value of the callback does not matter here because we're only enumerating a single device, but we're putting the checks down anyway for completeness. */ + enumerationResult = callback(ma_device_type_playback, &deviceInfo, pCallbackUserData); + if (enumerationResult == MA_DEVICE_ENUMERATION_ABORT) { + return MA_SUCCESS; + } return MA_SUCCESS; } -#if !defined(MA_USE_AUDIO_WORKLETS) -static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__webaudio(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +static void* ma_device_get_sub_buffer__xaudio(ma_device* pDevice, ma_uint32 subBufferIndex) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + return ma_offset_ptr(pDeviceStateXAudio->pBuffer, pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels) * subBufferIndex); +} + +#if 0 +static void ma_device_callback__xaudio(void* pUnused, void* pUserData) { + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + + (void)pUnused; + /* - There have been reports of the default buffer size being too small on some browsers. If we're using - the default buffer size, we'll make sure the period size is bigger than our standard defaults. + I'm not sure how to handle the situation where we don't have any audio data to submit. Can I just + not call XAudioProvideSamples(), or should I submit a chunk of silence? What's making me question + this is that during initialization of the audio system, at no point are we configuring a period + size so how does the audio system know when the fire this callback? If I don't submit anything, + how frequently does this callback get fired to request more data? */ - ma_uint32 periodSizeInFrames; + ma_spinlock_lock(&pDeviceStateXAudio->lock); + { + if (pDeviceStateXAudio->emptyBufferCount == 2) { + /* We don't have anything available. Submit silence. */ + ma_int16 pSilence[2048]; + unsigned short silenceFrameCount = (unsigned short)(ma_countof(pSilence) / pDevice->playback.internalChannels); - if (nativeSampleRate == 0) { - nativeSampleRate = MA_DEFAULT_SAMPLE_RATE; - } + MA_ZERO_MEMORY(pSilence, sizeof(pSilence)); + XAudioProvideSamples((void*)pSilence, silenceFrameCount, 0); + } else { + while (pDeviceStateXAudio->emptyBufferCount < pDeviceStateXAudio->subBufferCount) { + XAudioProvideSamples(ma_device_get_sub_buffer__xaudio(pDevice, pDeviceStateXAudio->subBufferIndex), (unsigned short)pDevice->playback.internalPeriodSizeInFrames, 0); - if (pDescriptor->periodSizeInFrames == 0) { - if (pDescriptor->periodSizeInMilliseconds == 0) { - if (performanceProfile == ma_performance_profile_low_latency) { - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(33, nativeSampleRate); /* 1 frame @ 30 FPS */ - } else { - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(333, nativeSampleRate); + pDeviceStateXAudio->subBufferIndex = (pDeviceStateXAudio->subBufferIndex + 1) & 1; + pDeviceStateXAudio->emptyBufferCount += 1; } - } else { - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); } - } else { - periodSizeInFrames = pDescriptor->periodSizeInFrames; } + ma_spinlock_unlock(&pDeviceStateXAudio->lock); +} +#endif - /* The size of the buffer must be a power of 2 and between 256 and 16384. */ - if (periodSizeInFrames < 256) { - periodSizeInFrames = 256; - } else if (periodSizeInFrames > 16384) { - periodSizeInFrames = 16384; - } else { - periodSizeInFrames = ma_next_power_of_2(periodSizeInFrames); +static ma_result ma_device_init__xaudio(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + ma_device_state_xaudio* pDeviceStateXAudio; + ma_device_config_xaudio* pDeviceConfigXAudio = (ma_device_config_xaudio*)pDeviceBackendConfig; + ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(ma_device_get_context(pDevice)); + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_result result; + int sampleSizeInBits; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 bufferSizeInFrames; + + /* Only playback is supported. */ + if (deviceType != ma_device_type_playback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; } - return periodSizeInFrames; -} -#endif + (void)pDescriptorCapture; -#if defined(MA_USE_AUDIO_WORKLETS) -typedef struct -{ - ma_device* pDevice; - const ma_device_config* pConfig; - ma_device_descriptor* pDescriptorPlayback; - ma_device_descriptor* pDescriptorCapture; -} ma_audio_worklet_thread_initialized_data; + /* We need to allocate our backend-specific data. */ + pDeviceStateXAudio = (ma_device_state_xaudio*)ma_calloc(sizeof(*pDeviceStateXAudio), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateXAudio == NULL) { + return MA_OUT_OF_MEMORY; + } -static EM_BOOL ma_audio_worklet_process_callback__webaudio(int inputCount, const AudioSampleFrame* pInputs, int outputCount, AudioSampleFrame* pOutputs, int paramCount, const AudioParamFrame* pParams, void* pUserData) -{ - ma_device* pDevice = (ma_device*)pUserData; - ma_uint32 frameCount; + pDeviceStateXAudio->emptyBufferCount = 2; /* Both buffers are empty from the start. */ + ma_timer_init(&pDeviceStateXAudio->timer); - (void)paramCount; - (void)pParams; + /* Audio is always s16, stereo, 48000. */ + format = ma_format_s16; + channels = 2; + sampleRate = 48000; - /* - The Emscripten documentation says that it'll always be 128 frames being passed in. Hard coding it like that feels - like a very bad idea to me. Even if it's hard coded in the backend, the API and documentation should always refer - to variables instead of a hard coded number. In any case, will follow along for the time being. + /* Calculate an appropriate buffer size. */ + bufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, sampleRate); - Unfortunately the audio data is not interleaved so we'll need to convert it before we give the data to miniaudio - for further processing. - */ - if (pDevice->type == ma_device_type_playback) { - frameCount = pDevice->playback.internalPeriodSizeInFrames; - } else { - frameCount = pDevice->capture.internalPeriodSizeInFrames; + /* TODO: Do we need to have a minimum buffer size? Need to experiment with this. */ + if (bufferSizeInFrames < 1024) { + bufferSizeInFrames = 1024; } /* - If this is called by the device has not yet been started we need to return early, making sure we output silence to - the output buffer. + XAudioProvideSamples() takes the size of the buffer in bytes, but that parameter is typed as + an unsigned 16-bit value. We need to make sure the size of the buffer does not exceed this. */ - if (ma_device_get_state(pDevice) != ma_device_state_started) { - for (int i = 0; i < outputCount; i += 1) { - MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); - } - - return EM_TRUE; + if (bufferSizeInFrames > 65535 / ma_get_bytes_per_frame(format, channels)) { + bufferSizeInFrames = 65535 / ma_get_bytes_per_frame(format, channels); } - if (inputCount > 0) { - /* Input data needs to be interleaved before we hand it to the client. */ - for (ma_uint32 iChannel = 0; iChannel < pDevice->capture.internalChannels; iChannel += 1) { - for (ma_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { - pDevice->webaudio.pIntermediaryBuffer[iFrame*pDevice->capture.internalChannels + iChannel] = pInputs[0].data[frameCount*iChannel + iFrame]; - } - } + pDeviceStateXAudio->periodTimeInSeconds = bufferSizeInFrames / (double)sampleRate; + pDeviceStateXAudio->subBufferCount = 3; - ma_device_process_pcm_frames_capture__webaudio(pDevice, frameCount, pDevice->webaudio.pIntermediaryBuffer); + /* + Allocate two buffers for double buffering. The nxdk "xaudio" example uses MmAllocateContiguousMemoryEx(), but + I'm not sure if that's strictly required. It'd be nice if instead we could use miniaudio's allocation callbacks + and just allocate this buffer in the same allocation as the state struct, but the example explicitly using + MmAllocateContiguousMemoryEx() makes me feel a bit uneasy about that. Advice welcome. + */ + pDeviceStateXAudio->pBuffer = MmAllocateContiguousMemoryEx(bufferSizeInFrames * ma_get_bytes_per_frame(format, channels) * pDeviceStateXAudio->subBufferCount, 0, 0x03FFAFFF, 0, (PAGE_READWRITE | PAGE_WRITECOMBINE)); + if (pDeviceStateXAudio->pBuffer == NULL) { + ma_free(pDeviceStateXAudio, ma_device_get_allocation_callbacks(pDevice)); + return MA_OUT_OF_MEMORY; } - if (outputCount > 0) { - /* If it's a capture-only device, we'll need to output silence. */ - if (pDevice->type == ma_device_type_capture) { - for (int i = 0; i < outputCount; i += 1) { - MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); - } - } else { - ma_device_process_pcm_frames_playback__webaudio(pDevice, frameCount, pDevice->webaudio.pIntermediaryBuffer); + /* + NOTE: + There is a bug in xemu that is resulting in the callback never getting fired. Since I would + like miniaudio to work on xemu I'm going to not use the callback technique. If this is fixed in + the future I may update this. - /* We've read the data from the client. Now we need to deinterleave the buffer and output to the output buffer. */ - for (ma_uint32 iChannel = 0; iChannel < pDevice->playback.internalChannels; iChannel += 1) { - for (ma_uint32 iFrame = 0; iFrame < frameCount; iFrame += 1) { - pOutputs[0].data[frameCount*iChannel + iFrame] = pDevice->webaudio.pIntermediaryBuffer[iFrame*pDevice->playback.internalChannels + iChannel]; - } - } + With the callback technique `XAudioProvideSamples()` would be called from the callback, but + we're instead going to manually keep track of a timer and call it from our step function. + */ + + /* The audio system must be initialized before it'll be usable. */ + XAudioInit((int)ma_get_bytes_per_sample(format) * 8, (int)channels, NULL, pDevice); - /* - Just above we output data to the first output buffer. Here we just make sure we're putting silence into any - remaining output buffers. - */ - for (int i = 1; i < outputCount; i += 1) { /* <-- Note that the counter starts at 1 instead of 0. */ - MA_ZERO_MEMORY(pOutputs[i].data, pOutputs[i].numberOfChannels * frameCount * sizeof(float)); - } - } - } + /* We're done. */ + pDescriptorPlayback->format = format; + pDescriptorPlayback->channels = channels; + pDescriptorPlayback->sampleRate = sampleRate; + pDescriptorPlayback->periodSizeInFrames = bufferSizeInFrames; + pDescriptorPlayback->periodCount = pDeviceStateXAudio->subBufferCount; - return EM_TRUE; + *ppDeviceState = pDeviceStateXAudio; + + return MA_SUCCESS; } +static void ma_device_uninit__xaudio(ma_device* pDevice) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + /*ma_context_state_xaudio* pContextStateXAudio = ma_context_get_backend_state__xaudio(ma_device_get_context(pDevice));*/ + + MmFreeContiguousMemory(pDeviceStateXAudio->pBuffer); + ma_free(pDeviceStateXAudio, ma_device_get_allocation_callbacks(pDevice)); +} -static void ma_audio_worklet_processor_created__webaudio(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* pUserData) +static void ma_device_fill_sub_buffer__xaudio(ma_device* pDevice, ma_uint32 subBufferIndex) { - ma_audio_worklet_thread_initialized_data* pParameters = (ma_audio_worklet_thread_initialized_data*)pUserData; - EmscriptenAudioWorkletNodeCreateOptions audioWorkletOptions; - int channels = 0; - size_t intermediaryBufferSizeInFrames; - int sampleRate; + /* Do the data callback. */ + ma_device_handle_backend_data_callback(pDevice, ma_device_get_sub_buffer__xaudio(pDevice, subBufferIndex), NULL, pDevice->playback.internalPeriodSizeInFrames); +} - if (success == EM_FALSE) { - pParameters->pDevice->webaudio.initResult = MA_ERROR; - ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); - return; - } +static ma_result ma_device_start__xaudio(ma_device* pDevice) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); - /* The next step is to initialize the audio worklet node. */ - MA_ZERO_OBJECT(&audioWorkletOptions); + /* Clear everything out and reload our buffers. We need to submit data to XAudio before we start playing. */ + #if 0 + pDeviceStateXAudio->subBufferIndex = 0; + pDeviceStateXAudio->emptyBufferCount = 0; + ma_device_fill_sub_buffer__xaudio(pDevice, 0); + ma_device_fill_sub_buffer__xaudio(pDevice, 1); + ma_device_callback__xaudio(NULL, pDevice); + #endif - /* - The way channel counts work with Web Audio is confusing. As far as I can tell, there's no way to know the channel - count from MediaStreamAudioSourceNode (what we use for capture)? The only way to have control is to configure an - output channel count on the capture side. This is slightly confusing for capture mode because intuitively you - wouldn't actually connect an output to an input-only node, but this is what we'll have to do in order to have - proper control over the channel count. In the capture case, we'll have to output silence to its output node. - */ - if (pParameters->pConfig->deviceType == ma_device_type_capture) { - channels = (int)((pParameters->pDescriptorCapture->channels > 0) ? pParameters->pDescriptorCapture->channels : MA_DEFAULT_CHANNELS); - audioWorkletOptions.numberOfInputs = 1; - } else { - channels = (int)((pParameters->pDescriptorPlayback->channels > 0) ? pParameters->pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS); + XAudioPlay(); + pDeviceStateXAudio->queuedAudioTime = ma_timer_get_time_in_seconds(&pDeviceStateXAudio->timer); - if (pParameters->pConfig->deviceType == ma_device_type_duplex) { - audioWorkletOptions.numberOfInputs = 1; - } else { - audioWorkletOptions.numberOfInputs = 0; - } - } + return MA_SUCCESS; +} - audioWorkletOptions.numberOfOutputs = 1; - audioWorkletOptions.outputChannelCounts = &channels; +static ma_result ma_device_stop__xaudio(ma_device* pDevice) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); + (void)pDeviceStateXAudio; + XAudioPause(); - /* - Now that we know the channel count to use we can allocate the intermediary buffer. The - intermediary buffer is used for interleaving and deinterleaving. - */ - #if defined(MA_SUPPORT_AUDIO_WORKLETS_VARIABLE_BUFFER_SIZE) - { - intermediaryBufferSizeInFrames = (size_t)emscripten_audio_context_quantum_size(audioContext); - } - #else - { - intermediaryBufferSizeInFrames = 128; - } - #endif + return MA_SUCCESS; +} - pParameters->pDevice->webaudio.pIntermediaryBuffer = (float*)ma_malloc(intermediaryBufferSizeInFrames * (ma_uint32)channels * sizeof(float), &pParameters->pDevice->pContext->allocationCallbacks); - if (pParameters->pDevice->webaudio.pIntermediaryBuffer == NULL) { - pParameters->pDevice->webaudio.initResult = MA_OUT_OF_MEMORY; - ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); - return; - } +static ma_result ma_device_step__xaudio(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_xaudio* pDeviceStateXAudio = ma_device_get_backend_state__xaudio(pDevice); - pParameters->pDevice->webaudio.audioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, "miniaudio", &audioWorkletOptions, &ma_audio_worklet_process_callback__webaudio, pParameters->pDevice); + /* Keep looping until we have processed some data, or we're in non-blocking mode. */ + for (;;) { + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } - /* With the audio worklet initialized we can now attach it to the graph. */ - if (pParameters->pConfig->deviceType == ma_device_type_capture || pParameters->pConfig->deviceType == ma_device_type_duplex) { - ma_result attachmentResult = (ma_result)EM_ASM_INT({ - var getUserMediaResult = 0; - var audioWorklet = emscriptenGetAudioObject($0); - var audioContext = emscriptenGetAudioObject($1); + #if 0 + { + ma_spinlock_lock(&pDeviceStateXAudio->lock); + { + if (pDeviceStateXAudio->emptyBufferCount > 0) { + ma_uint16 iEmptyBuffer; + ma_uint16 subBufferIndex; + + if (pDeviceStateXAudio->emptyBufferCount == 2) { + subBufferIndex = pDeviceStateXAudio->subBufferIndex; + ma_device_fill_sub_buffer__xaudio(pDevice, subBufferIndex); + pDeviceStateXAudio->emptyBufferCount -= 1; + } - navigator.mediaDevices.getUserMedia({audio:true, video:false}) - .then(function(stream) { - audioContext.streamNode = audioContext.createMediaStreamSource(stream); - audioContext.streamNode.connect(audioWorklet); - audioWorklet.connect(audioContext.destination); - getUserMediaResult = 0; /* 0 = MA_SUCCESS */ - }) - .catch(function(error) { - console.log("navigator.mediaDevices.getUserMedia Failed: " + error); - getUserMediaResult = -1; /* -1 = MA_ERROR */ - }); + MA_ASSERT(pDeviceStateXAudio->emptyBufferCount == 1); - return getUserMediaResult; - }, pParameters->pDevice->webaudio.audioWorklet, audioContext); + subBufferIndex = (pDeviceStateXAudio->subBufferIndex + 1) & ~1; + ma_device_fill_sub_buffer__xaudio(pDevice, subBufferIndex); + pDeviceStateXAudio->emptyBufferCount -= 1; - if (attachmentResult != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_ERROR, "Web Audio: Failed to connect capture node."); - emscripten_destroy_web_audio_node(pParameters->pDevice->webaudio.audioWorklet); - pParameters->pDevice->webaudio.initResult = attachmentResult; - ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); - return; + ma_spinlock_unlock(&pDeviceStateXAudio->lock); + break; + } + } + ma_spinlock_unlock(&pDeviceStateXAudio->lock); } - } + #else + { + double currentTimeInSeconds; + double marginTimeInSeconds; - /* If it's playback only we can now attach the worklet node to the graph. This has already been done for the duplex case. */ - if (pParameters->pConfig->deviceType == ma_device_type_playback) { - ma_result attachmentResult = (ma_result)EM_ASM_INT({ - var audioWorklet = emscriptenGetAudioObject($0); - var audioContext = emscriptenGetAudioObject($1); - audioWorklet.connect(audioContext.destination); - return 0; /* 0 = MA_SUCCESS */ - }, pParameters->pDevice->webaudio.audioWorklet, audioContext); + currentTimeInSeconds = ma_timer_get_time_in_seconds(&pDeviceStateXAudio->timer); - if (attachmentResult != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pParameters->pDevice), MA_LOG_LEVEL_ERROR, "Web Audio: Failed to connect playback node."); - pParameters->pDevice->webaudio.initResult = attachmentResult; - ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); - return; + /* If we underflow reset the timer to help mitigate getting stuck in a permanent glitched state. */ + if (currentTimeInSeconds > pDeviceStateXAudio->queuedAudioTime) { + pDeviceStateXAudio->queuedAudioTime = currentTimeInSeconds; + } + + /* The margin time is how much audio time we want queued ahead of the current time. */ + marginTimeInSeconds = pDeviceStateXAudio->periodTimeInSeconds * (pDeviceStateXAudio->subBufferCount - 1); + + if (pDeviceStateXAudio->queuedAudioTime - currentTimeInSeconds < marginTimeInSeconds) { + ma_device_fill_sub_buffer__xaudio(pDevice, pDeviceStateXAudio->subBufferIndex); + XAudioProvideSamples(ma_device_get_sub_buffer__xaudio(pDevice, pDeviceStateXAudio->subBufferIndex), (unsigned short)(pDevice->playback.internalPeriodSizeInFrames*ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)), 0); + + //debugPrint("TESTING: %d %d\n", (int)(currentTimeInSeconds * 1000), (int)(pDeviceStateXAudio->queuedAudioTime * 1000)); + pDeviceStateXAudio->subBufferIndex += 1; + if (pDeviceStateXAudio->subBufferIndex == pDeviceStateXAudio->subBufferCount) { + pDeviceStateXAudio->subBufferIndex = 0; + } + + pDeviceStateXAudio->queuedAudioTime += pDeviceStateXAudio->periodTimeInSeconds; + + break; + } } - } + #endif - /* 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); + /* Getting here means there is no data to process. In non blocking mode we just bomb out. In blocking mode we wait a bit. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + break; + } - if (pParameters->pDescriptorCapture != NULL) { - pParameters->pDescriptorCapture->format = ma_format_f32; - pParameters->pDescriptorCapture->channels = (ma_uint32)channels; - 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); - pParameters->pDescriptorCapture->periodSizeInFrames = intermediaryBufferSizeInFrames; - pParameters->pDescriptorCapture->periodCount = 1; + /* Getting here means we're in blocking mode. */ + ma_sleep(1); } - if (pParameters->pDescriptorPlayback != NULL) { - pParameters->pDescriptorPlayback->format = ma_format_f32; - pParameters->pDescriptorPlayback->channels = (ma_uint32)channels; - 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); - pParameters->pDescriptorPlayback->periodSizeInFrames = intermediaryBufferSizeInFrames; - pParameters->pDescriptorPlayback->periodCount = 1; - } + return MA_SUCCESS; +} - /* 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\n", pParameters->pDevice->webaudio.audioWorklet); - pParameters->pDevice->webaudio.initResult = MA_SUCCESS; - ma_free(pParameters, &pParameters->pDevice->pContext->allocationCallbacks); +static void ma_device_wakeup__xaudio(ma_device* pDevice) +{ + /* Do nothing. */ + (void)pDevice; } -static void ma_audio_worklet_thread_initialized__webaudio(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void* pUserData) +static ma_device_backend_vtable ma_gDeviceBackendVTable_XAudio = { - ma_audio_worklet_thread_initialized_data* pParameters = (ma_audio_worklet_thread_initialized_data*)pUserData; - WebAudioWorkletProcessorCreateOptions workletProcessorOptions; + ma_backend_info__xaudio, + ma_context_init__xaudio, + ma_context_uninit__xaudio, + ma_context_enumerate_devices__xaudio, + ma_device_init__xaudio, + ma_device_uninit__xaudio, + ma_device_start__xaudio, + ma_device_stop__xaudio, + ma_device_step__xaudio, + ma_device_wakeup__xaudio +}; - MA_ASSERT(pParameters != NULL); +ma_device_backend_vtable* ma_device_backend_xaudio = &ma_gDeviceBackendVTable_XAudio; +#else +ma_device_backend_vtable* ma_device_backend_xaudio = NULL; +#endif - if (success == EM_FALSE) { - pParameters->pDevice->webaudio.initResult = MA_ERROR; - return; - } +MA_API ma_device_backend_vtable* ma_xaudio_get_vtable(void) +{ + return ma_device_backend_xaudio; +} - MA_ZERO_OBJECT(&workletProcessorOptions); - workletProcessorOptions.name = "miniaudio"; /* I'm not entirely sure what to call this. Does this need to be globally unique, or does it need only be unique for a given AudioContext? */ +MA_API ma_context_config_xaudio ma_context_config_xaudio_init(void) +{ + ma_context_config_xaudio config; - emscripten_create_wasm_audio_worklet_processor_async(audioContext, &workletProcessorOptions, ma_audio_worklet_processor_created__webaudio, pParameters); + MA_ZERO_OBJECT(&config); + + return config; } -#endif -static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +MA_API ma_device_config_xaudio ma_device_config_xaudio_init(void) { - if (pConfig->deviceType == ma_device_type_loopback) { - return MA_DEVICE_TYPE_NOT_SUPPORTED; - } + ma_device_config_xaudio config; - /* No exclusive mode with Web Audio. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } + MA_ZERO_OBJECT(&config); - /* - With AudioWorklets we'll have just a single AudioContext. I'm not sure why I'm not doing this for ScriptProcessorNode so - it might be worthwhile to look into that as well. - */ - #if defined(MA_USE_AUDIO_WORKLETS) - { - EmscriptenWebAudioCreateAttributes audioContextAttributes; - ma_audio_worklet_thread_initialized_data* pInitParameters; - void* pStackBuffer; + return config; +} +/* END miniaudio_xaudio.c */ - if (pConfig->performanceProfile == ma_performance_profile_conservative) { - audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_PLAYBACK; - } else { - audioContextAttributes.latencyHint = MA_WEBAUDIO_LATENCY_HINT_INTERACTIVE; - } - /* - In my testing, Firefox does not seem to capture audio data properly if the sample rate is set - to anything other than 48K. This does not seem to be the case for other browsers. For this reason, - if the device type is anything other than playback, we'll leave the sample rate as-is and let the - browser pick the appropriate rate for us. - */ - if (pConfig->deviceType == ma_device_type_playback) { - audioContextAttributes.sampleRate = pDescriptorPlayback->sampleRate; - } else { - audioContextAttributes.sampleRate = 0; - } +/* BEG miniaudio_vita.c */ +#if defined(MA_HAS_VITA) +#include - /* It's not clear if this can return an error. None of the tests in the Emscripten repository check for this, so neither am I for now. */ - pDevice->webaudio.audioContext = emscripten_create_audio_context(&audioContextAttributes); +typedef struct ma_context_state_vita +{ + int _unused; +} ma_context_state_vita; - /* - With the context created we can now create the worklet. We can only have a single worklet per audio - context which means we'll need to craft this appropriately to handle duplex devices correctly. - */ +typedef struct ma_device_state_vita +{ + int port; + ma_bool32 isRunning; /* Used for tracking whether or not the background thread should be terminated. */ + ma_thread thread; /* Need to call sceAudioOutOutput() from a separate thread because it is always blocking. */ + ma_uint32 subBufferIndex; + ma_uint32 validSubBufferCount; + void* pSubBuffers; +} ma_device_state_vita; - /* - We now need to create a worker thread. This is a bit weird because we need to allocate our - own buffer for the thread's stack. The stack needs to be aligned to 16 bytes. I'm going to - allocate this on the heap to keep it simple. - */ - pStackBuffer = ma_aligned_malloc(MA_AUDIO_WORKLETS_THREAD_STACK_SIZE, 16, &pDevice->pContext->allocationCallbacks); - if (pStackBuffer == NULL) { - emscripten_destroy_audio_context(pDevice->webaudio.audioContext); - return MA_OUT_OF_MEMORY; - } - /* Our thread initialization parameters need to be allocated on the heap so they don't go out of scope. */ - pInitParameters = (ma_audio_worklet_thread_initialized_data*)ma_malloc(sizeof(*pInitParameters), &pDevice->pContext->allocationCallbacks); - if (pInitParameters == NULL) { - ma_free(pStackBuffer, &pDevice->pContext->allocationCallbacks); - emscripten_destroy_audio_context(pDevice->webaudio.audioContext); - return MA_OUT_OF_MEMORY; - } +static ma_context_state_vita* ma_context_get_backend_state__vita(ma_context* pContext) +{ + return (ma_context_state_vita*)ma_context_get_backend_state(pContext); +} - pInitParameters->pDevice = pDevice; - pInitParameters->pConfig = pConfig; - pInitParameters->pDescriptorPlayback = pDescriptorPlayback; - pInitParameters->pDescriptorCapture = pDescriptorCapture; +static ma_device_state_vita* ma_device_get_backend_state__vita(ma_device* pDevice) +{ + return (ma_device_state_vita*)ma_device_get_backend_state(pDevice); +} - /* - We need to flag the device as not yet initialized so we can wait on it later. Unfortunately all of - the Emscripten WebAudio stuff is asynchronous. - */ - pDevice->webaudio.initResult = MA_BUSY; - { - emscripten_start_wasm_audio_worklet_thread_async(pDevice->webaudio.audioContext, pStackBuffer, MA_AUDIO_WORKLETS_THREAD_STACK_SIZE, ma_audio_worklet_thread_initialized__webaudio, pInitParameters); - } - while (pDevice->webaudio.initResult == MA_BUSY) { emscripten_sleep(1); } /* We must wait for initialization to complete. We're just spinning here. The emscripten_sleep() call is why we need to build with `-sASYNCIFY`. */ - /* Initialization is now complete. Descriptors were updated when the worklet was initialized. */ - if (pDevice->webaudio.initResult != MA_SUCCESS) { - ma_free(pStackBuffer, &pDevice->pContext->allocationCallbacks); - emscripten_destroy_audio_context(pDevice->webaudio.audioContext); - return pDevice->webaudio.initResult; - } +static void ma_backend_info__vita(ma_device_backend_info* pBackendInfo) +{ + pBackendInfo->pName = "PlayStation Vita"; +} - /* We need to add an entry to the miniaudio.devices list on the JS side so we can do some JS/C interop. */ - pDevice->webaudio.deviceIndex = EM_ASM_INT({ - return window.miniaudio.track_device({ - webaudio: emscriptenGetAudioObject($0), - state: 1, /* 1 = ma_device_state_stopped */ - pDevice: $1 - }); - }, pDevice->webaudio.audioContext, pDevice); +static ma_result ma_context_init__vita(ma_context* pContext, const void* pContextBackendConfig, void** ppContextState) +{ + ma_context_state_vita* pContextStateVita; + const ma_context_config_vita* pContextConfigVita = (ma_context_config_vita*)pContextBackendConfig; - return MA_SUCCESS; + pContextStateVita = (ma_context_state_vita*)ma_calloc(sizeof(*pContextStateVita), ma_context_get_allocation_callbacks(pContext)); + if (pContextStateVita == NULL) { + return MA_OUT_OF_MEMORY; } - #else - { - /* ScriptProcessorNode. This path requires us to do almost everything in JS, but we'll do as much as we can in C. */ - ma_uint32 deviceIndex; - ma_uint32 channels; - ma_uint32 sampleRate; - ma_uint32 periodSizeInFrames; - - /* The channel count will depend on the device type. If it's a capture, use its, otherwise use the playback side. */ - if (pConfig->deviceType == ma_device_type_capture) { - channels = (pDescriptorCapture->channels > 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; - } else { - channels = (pDescriptorPlayback->channels > 0) ? pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS; - } - - /* - When testing in Firefox, I've seen it where capture mode fails if the sample rate is changed to anything other than it's - native rate. For this reason we're leaving the sample rate untouched for capture devices. - */ - if (pConfig->deviceType == ma_device_type_playback) { - sampleRate = pDescriptorPlayback->sampleRate; - } else { - sampleRate = 0; /* Let the browser decide when capturing. */ - } - /* The period size needs to be a power of 2. */ - if (pConfig->deviceType == ma_device_type_capture) { - periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptorCapture, sampleRate, pConfig->performanceProfile); - } else { - periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptorPlayback, sampleRate, pConfig->performanceProfile); - } + (void)pContextConfigVita; + (void)pContext; - /* We need an intermediary buffer for doing interleaving and deinterleaving. */ - pDevice->webaudio.pIntermediaryBuffer = (float*)ma_malloc(periodSizeInFrames * channels * sizeof(float), &pDevice->pContext->allocationCallbacks); - if (pDevice->webaudio.pIntermediaryBuffer == NULL) { - return MA_OUT_OF_MEMORY; - } + *ppContextState = pContextStateVita; - deviceIndex = EM_ASM_INT({ - var deviceType = $0; - var channels = $1; - var sampleRate = $2; - var bufferSize = $3; - var pIntermediaryBuffer = $4; - var pDevice = $5; + return MA_SUCCESS; +} - if (typeof(window.miniaudio) === 'undefined') { - return -1; /* Context not initialized. */ - } +static void ma_context_uninit__vita(ma_context* pContext) +{ + ma_context_state_vita* pContextStateVita = ma_context_get_backend_state__vita(pContext); + ma_free(pContextStateVita, ma_context_get_allocation_callbacks(pContext)); +} - var device = {}; +static ma_result ma_context_enumerate_devices__vita(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pCallbackUserData) +{ + ma_context_state_vita* pContextStateVita = ma_context_get_backend_state__vita(pContext); + ma_device_info deviceInfo; + ma_device_enumeration_result enumerationResult; - /* First thing we need is an AudioContext. */ - var audioContextOptions = {}; - if (deviceType == window.miniaudio.device_type.playback && sampleRate != 0) { - audioContextOptions.sampleRate = sampleRate; - } + (void)pContextStateVita; - device.webaudio = new (window.AudioContext || window.webkitAudioContext)(audioContextOptions); - device.webaudio.suspend(); /* The AudioContext must be created in a suspended state. */ - device.state = window.miniaudio.device_state.stopped; + /* Playback. */ + MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.isDefault = MA_TRUE; + deviceInfo.id.custom.i = 0; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), "Default Playback Device", (size_t)-1); - /* - We need to create a ScriptProcessorNode. The channel situation is the same as the AudioWorklet path in that we - need to specify an output and configure the channel count there. - */ - var channelCountIn = 0; - var channelCountOut = channels; - if (deviceType != window.miniaudio.device_type.playback) { - channelCountIn = channels; - } + /* Only s16 and mono/stereo is natively supported. */ + ma_device_info_add_native_data_format(&deviceInfo, ma_format_s16, 1, 2, 8000, 48000); - device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, channelCountIn, channelCountOut); + enumerationResult = callback(ma_device_type_playback, &deviceInfo, pCallbackUserData); + if (enumerationResult == MA_DEVICE_ENUMERATION_ABORT) { + return MA_SUCCESS; + } - /* The node processing callback. */ - device.scriptNode.onaudioprocess = function(e) { - if (device.intermediaryBufferView == null || device.intermediaryBufferView.length == 0) { - device.intermediaryBufferView = new Float32Array(HEAPF32.buffer, pIntermediaryBuffer, bufferSize * channels); - } + return MA_SUCCESS; +} - /* Do the capture side first. */ - if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { - /* The data must be interleaved before being processed miniaudio. */ - for (var iChannel = 0; iChannel < channels; iChannel += 1) { - var inputBuffer = e.inputBuffer.getChannelData(iChannel); - var intermediaryBuffer = device.intermediaryBufferView; +static void* ma_device_get_sub_buffer__vita(ma_device* pDevice, ma_uint32 subBufferIndex) +{ + ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice); + return ma_offset_ptr(pDeviceStateVita->pSubBuffers, pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels) * subBufferIndex); +} - for (var iFrame = 0; iFrame < bufferSize; iFrame += 1) { - intermediaryBuffer[iFrame*channels + iChannel] = inputBuffer[iFrame]; - } - } +static ma_thread_result MA_THREADCALL ma_device_audio_thread__vita(void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice); - _ma_device_process_pcm_frames_capture__webaudio(pDevice, bufferSize, pIntermediaryBuffer); - } + while (ma_atomic_load_explicit_32(&pDeviceStateVita->isRunning, ma_atomic_memory_order_relaxed)) { + if (ma_atomic_load_explicit_32(&pDeviceStateVita->validSubBufferCount, ma_atomic_memory_order_acquire) > 0) { + ma_uint32 subBufferIndex = ma_atomic_load_explicit_32(&pDeviceStateVita->subBufferIndex, ma_atomic_memory_order_relaxed); - if (deviceType == window.miniaudio.device_type.playback || deviceType == window.miniaudio.device_type.duplex) { - _ma_device_process_pcm_frames_playback__webaudio(pDevice, bufferSize, pIntermediaryBuffer); + sceAudioOutOutput(pDeviceStateVita->port, ma_device_get_sub_buffer__vita(pDevice, subBufferIndex)); - for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { - var outputBuffer = e.outputBuffer.getChannelData(iChannel); - var intermediaryBuffer = device.intermediaryBufferView; + ma_atomic_store_explicit_32(&pDeviceStateVita->subBufferIndex, (subBufferIndex + 1) & 1, ma_atomic_memory_order_relaxed); + ma_atomic_fetch_sub_explicit_32(&pDeviceStateVita->validSubBufferCount, 1, ma_atomic_memory_order_release); + } else { + /* Just a dumb sleep so we don't peg the CPU while the device is not stopped. */ + ma_sleep(10); + } + } - for (var iFrame = 0; iFrame < bufferSize; iFrame += 1) { - outputBuffer[iFrame] = intermediaryBuffer[iFrame*channels + iChannel]; - } - } - } else { - /* It's a capture-only device. Make sure the output is silenced. */ - for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { - e.outputBuffer.getChannelData(iChannel).fill(0.0); - } - } - }; + return (ma_thread_result)0; +} - /* Now we need to connect our node to the graph. */ - if (deviceType == window.miniaudio.device_type.capture || deviceType == window.miniaudio.device_type.duplex) { - navigator.mediaDevices.getUserMedia({audio:true, video:false}) - .then(function(stream) { - device.streamNode = device.webaudio.createMediaStreamSource(stream); - device.streamNode.connect(device.scriptNode); - device.scriptNode.connect(device.webaudio.destination); - }) - .catch(function(error) { - console.log("Failed to get user media: " + error); - }); - } +static ma_result ma_device_init__vita(ma_device* pDevice, const void* pDeviceBackendConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture, void** ppDeviceState) +{ + ma_result result; + ma_device_state_vita* pDeviceStateVita; + ma_device_config_vita* pDeviceConfigVita = (ma_device_config_vita*)pDeviceBackendConfig; + ma_context_state_vita* pContextStateVita = ma_context_get_backend_state__vita(ma_device_get_context(pDevice)); + ma_device_config_vita defaultConfig; + ma_device_type deviceType = ma_device_get_type(pDevice); + ma_log* pLog = ma_device_get_log(pDevice); + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 periodSizeInFrames; + SceAudioOutPortType portType; - if (deviceType == window.miniaudio.device_type.playback) { - device.scriptNode.connect(device.webaudio.destination); - } + (void)pContextStateVita; + (void)pDescriptorCapture; - device.pDevice = pDevice; + /* Use a default config if one was not provided. This is not mandated by miniaudio, but it's good practice. */ + if (pDeviceConfigVita == NULL) { + defaultConfig = ma_device_config_vita_init(); + pDeviceConfigVita = &defaultConfig; + } - return window.miniaudio.track_device(device); - }, pConfig->deviceType, channels, sampleRate, periodSizeInFrames, pDevice->webaudio.pIntermediaryBuffer, pDevice); + /* Return an error for any unsupported device types. */ + if (deviceType != ma_device_type_playback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } - if (deviceIndex < 0) { - return MA_FAILED_TO_OPEN_BACKEND_DEVICE; - } + pDeviceStateVita = (ma_device_state_vita*)ma_calloc(sizeof(*pDeviceStateVita), ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateVita == NULL) { + return MA_OUT_OF_MEMORY; + } - pDevice->webaudio.deviceIndex = deviceIndex; + /* Port type. */ + portType = SCE_AUDIO_OUT_PORT_TYPE_BGM; + if (pDeviceConfigVita->portType == MA_VITA_PORT_TYPE_MAIN) { + portType = SCE_AUDIO_OUT_PORT_TYPE_MAIN; + } - /* Grab the sample rate from the audio context directly. */ - sampleRate = (ma_uint32)EM_ASM_INT({ return window.miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); + /* Format is always s16. */ + format = ma_format_s16; - if (pDescriptorCapture != NULL) { - pDescriptorCapture->format = ma_format_f32; - pDescriptorCapture->channels = channels; - pDescriptorCapture->sampleRate = sampleRate; - ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); - pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; - pDescriptorCapture->periodCount = 1; - } + /* Channels is always mono or stereo. Default to stereo. */ + channels = pDescriptorPlayback->channels; + if (channels != 1 && channels != 2) { + channels = 1; + } - if (pDescriptorPlayback != NULL) { - pDescriptorPlayback->format = ma_format_f32; - pDescriptorPlayback->channels = channels; - pDescriptorPlayback->sampleRate = sampleRate; - ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); - pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; - pDescriptorPlayback->periodCount = 1; - } + /* Sample rate. */ + sampleRate = 0; - return MA_SUCCESS; + if (pDescriptorPlayback->sampleRate != 0) { + if (portType == SCE_AUDIO_OUT_PORT_TYPE_BGM) { + ma_uint32 bgmSampleRates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000}; + ma_uint32 iSampleRate; + + for (iSampleRate = 0; iSampleRate < ma_countof(bgmSampleRates); iSampleRate += 1) { + if (pDescriptorPlayback->sampleRate == bgmSampleRates[iSampleRate]) { + sampleRate = bgmSampleRates[iSampleRate]; + } + } + } } - #endif -} -static ma_result ma_device_start__webaudio(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + if (sampleRate == 0) { + sampleRate = 48000; + } - EM_ASM({ - var device = window.miniaudio.get_device_by_index($0); - device.webaudio.resume(); - device.state = window.miniaudio.device_state.started; - }, pDevice->webaudio.deviceIndex); - return MA_SUCCESS; -} + /* The period size must be a multiple of 64. */ + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, sampleRate); + periodSizeInFrames = ma_clamp((periodSizeInFrames + 63) & ~63, SCE_AUDIO_MIN_LEN, SCE_AUDIO_MAX_LEN); -static ma_result ma_device_stop__webaudio(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); + pDeviceStateVita->pSubBuffers = ma_malloc(periodSizeInFrames * ma_get_bytes_per_frame(format, channels) * 2, ma_device_get_allocation_callbacks(pDevice)); + if (pDeviceStateVita->pSubBuffers == NULL) { + return MA_OUT_OF_MEMORY; + } - /* - From the WebAudio API documentation for AudioContext.suspend(): + pDeviceStateVita->port = sceAudioOutOpenPort(portType, (int)periodSizeInFrames, (int)sampleRate, (channels == 1) ? SCE_AUDIO_OUT_MODE_MONO : SCE_AUDIO_OUT_MODE_STEREO); + if (pDeviceStateVita->port < 0) { + ma_free(pDeviceStateVita->pSubBuffers, ma_device_get_allocation_callbacks(pDevice)); + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "[Vita] Failed to open port."); + return MA_ERROR; + } - Suspends the progression of AudioContext's currentTime, allows any current context processing blocks that are already processed to be played to the - destination, and then allows the system to release its claim on audio hardware. + /* Make sure the running status is set appropriately so the audio thread doesn't immediately terminate itself. */ + ma_atomic_store_explicit_32(&pDeviceStateVita->isRunning, 1, ma_atomic_memory_order_relaxed); - I read this to mean that "any current context processing blocks" are processed by suspend() - i.e. They they are drained. We therefore shouldn't need to - do any kind of explicit draining. + /* + Because sceAudioOutOutput() is always blocking, in order to do non-blocking processing, we'll need to call + it on a separate thread. Make sure the thread is created last. */ - EM_ASM({ - var device = window.miniaudio.get_device_by_index($0); - device.webaudio.suspend(); - device.state = window.miniaudio.device_state.stopped; - }, pDevice->webaudio.deviceIndex); + result = ma_thread_create(&pDeviceStateVita->thread, ma_thread_priority_default, 0, ma_device_audio_thread__vita, pDevice, ma_device_get_allocation_callbacks(pDevice)); + if (result != MA_SUCCESS) { + sceAudioOutReleasePort(pDeviceStateVita->port); + ma_free(pDeviceStateVita->pSubBuffers, ma_device_get_allocation_callbacks(pDevice)); + ma_log_postf(pLog, MA_LOG_LEVEL_ERROR, "[Vita] Failed to create audio thread."); + return MA_ERROR; + } - ma_device__on_notification_stopped(pDevice); + /* Update the descriptor with the actual internal settings. */ + pDescriptorPlayback->format = format; + pDescriptorPlayback->channels = channels; + pDescriptorPlayback->sampleRate = sampleRate; + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; + pDescriptorPlayback->periodCount = 2; + + *ppDeviceState = pDeviceStateVita; return MA_SUCCESS; } -static ma_result ma_context_uninit__webaudio(ma_context* pContext) +static void ma_device_uninit__vita(ma_device* pDevice) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_webaudio); + ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice); - (void)pContext; /* Unused. */ + /* Kill the thread first. */ + ma_atomic_store_explicit_32(&pDeviceStateVita->isRunning, 0, ma_atomic_memory_order_relaxed); + ma_thread_wait(&pDeviceStateVita->thread); - /* Remove the global miniaudio object from window if there are no more references to it. */ - EM_ASM({ - if (typeof(window.miniaudio) !== 'undefined') { - window.miniaudio.unlock_event_types.map(function(event_type) { - document.removeEventListener(event_type, window.miniaudio.unlock, true); - }); + sceAudioOutReleasePort(pDeviceStateVita->port); + ma_free(pDeviceStateVita->pSubBuffers, ma_device_get_allocation_callbacks(pDevice)); + ma_free(pDeviceStateVita, ma_device_get_allocation_callbacks(pDevice)); +} - window.miniaudio.referenceCount -= 1; - if (window.miniaudio.referenceCount === 0) { - delete window.miniaudio; - } - } - }); +static ma_result ma_device_start__vita(ma_device* pDevice) +{ + ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice); + + ma_atomic_store_explicit_32(&pDeviceStateVita->subBufferIndex, 0, ma_atomic_memory_order_relaxed); + ma_atomic_store_explicit_32(&pDeviceStateVita->validSubBufferCount, 0, ma_atomic_memory_order_relaxed); + + /* Don't actually do anything here. We start by simply outputting data. Stopping is just not outputting data. */ return MA_SUCCESS; } -static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +static ma_result ma_device_stop__vita(ma_device* pDevice) { - int resultFromJS; + ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice); + + /* Wait for the buffers to be drained. */ + while (ma_atomic_load_explicit_32(&pDeviceStateVita->validSubBufferCount, ma_atomic_memory_order_relaxed) > 0) { + ma_sleep(1); + } - MA_ASSERT(pContext != NULL); + return MA_SUCCESS; +} - (void)pConfig; /* Unused. */ +static ma_result ma_device_step__vita(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_device_state_vita* pDeviceStateVita = ma_device_get_backend_state__vita(pDevice); - /* Here is where our global JavaScript object is initialized. */ - resultFromJS = EM_ASM_INT({ - if (typeof window === 'undefined' || (window.AudioContext || window.webkitAudioContext) === undefined) { - return 0; /* Web Audio not supported. */ + for (;;) { + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; } - if (typeof(window.miniaudio) === 'undefined') { - window.miniaudio = { - referenceCount: 0 - }; + if (ma_atomic_load_explicit_32(&pDeviceStateVita->validSubBufferCount, ma_atomic_memory_order_acquire) < 2) { + ma_uint32 subBufferIndex = ma_atomic_load_explicit_32(&pDeviceStateVita->subBufferIndex, ma_atomic_memory_order_relaxed); + ma_device_handle_backend_data_callback(pDevice, ma_device_get_sub_buffer__vita(pDevice, subBufferIndex), NULL, pDevice->playback.internalPeriodSizeInFrames); + ma_atomic_fetch_add_explicit_32(&pDeviceStateVita->validSubBufferCount, 1, ma_atomic_memory_order_release); - /* Device types. */ - window.miniaudio.device_type = {}; - window.miniaudio.device_type.playback = $0; - window.miniaudio.device_type.capture = $1; - window.miniaudio.device_type.duplex = $2; + return MA_SUCCESS; + } - /* Device states. */ - window.miniaudio.device_state = {}; - window.miniaudio.device_state.stopped = $3; - window.miniaudio.device_state.started = $4; + /* Getting here means there was no data to process. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + return MA_SUCCESS; + } - /* Device cache for mapping devices to indexes for JavaScript/C interop. */ - let miniaudio = window.miniaudio; - miniaudio.devices = []; + /* Getting here means there was no data to process and we're running in blocking mode. Sleep for a bit and keep trying. */ + ma_sleep(1); + } +} - miniaudio.track_device = function(device) { - /* Try inserting into a free slot first. */ - for (var iDevice = 0; iDevice < miniaudio.devices.length; ++iDevice) { - if (miniaudio.devices[iDevice] == null) { - miniaudio.devices[iDevice] = device; - return iDevice; - } - } +static void ma_device_wakeup__vita(ma_device* pDevice) +{ + /* Nothing to do here. */ + (void)pDevice; +} - /* Getting here means there is no empty slots in the array so we just push to the end. */ - miniaudio.devices.push(device); - return miniaudio.devices.length - 1; - }; +static ma_device_backend_vtable ma_gDeviceBackendVTable_Vita = +{ + ma_backend_info__vita, + ma_context_init__vita, + ma_context_uninit__vita, + ma_context_enumerate_devices__vita, + ma_device_init__vita, + ma_device_uninit__vita, + ma_device_start__vita, + ma_device_stop__vita, + ma_device_step__vita, + ma_device_wakeup__vita +}; - miniaudio.untrack_device_by_index = function(deviceIndex) { - /* We just set the device's slot to null. The slot will get reused in the next call to ma_track_device. */ - miniaudio.devices[deviceIndex] = null; +ma_device_backend_vtable* ma_device_backend_vita = &ma_gDeviceBackendVTable_Vita; +#else +ma_device_backend_vtable* ma_device_backend_vita = NULL; +#endif /* MA_HAS_VITA */ - /* Trim the array if possible. */ - while (miniaudio.devices.length > 0) { - if (miniaudio.devices[miniaudio.devices.length-1] == null) { - miniaudio.devices.pop(); - } else { - break; - } - } - }; +MA_API ma_device_backend_vtable* ma_vita_get_vtable(void) +{ + return ma_device_backend_vita; +} - miniaudio.untrack_device = function(device) { - for (var iDevice = 0; iDevice < miniaudio.devices.length; ++iDevice) { - if (miniaudio.devices[iDevice] == device) { - return miniaudio.untrack_device_by_index(iDevice); - } - } - }; +MA_API ma_context_config_vita ma_context_config_vita_init(void) +{ + ma_context_config_vita config; - miniaudio.get_device_by_index = function(deviceIndex) { - return miniaudio.devices[deviceIndex]; - }; + MA_ZERO_OBJECT(&config); - miniaudio.unlock_event_types = (function(){ - return ['touchend', 'click']; - })(); + return config; +} - miniaudio.unlock = function() { - for(var i = 0; i < miniaudio.devices.length; ++i) { - var device = miniaudio.devices[i]; - if (device != null && - device.webaudio != null && - device.state === miniaudio.device_state.started) { +MA_API ma_device_config_vita ma_device_config_vita_init(void) +{ + ma_device_config_vita config; - device.webaudio.resume().then(() => { - _ma_device__on_notification_unlocked(device.pDevice); - }, - (error) => {console.error("Failed to resume audiocontext", error); - }); - } - } - miniaudio.unlock_event_types.map(function(event_type) { - document.removeEventListener(event_type, miniaudio.unlock, true); - }); - }; + MA_ZERO_OBJECT(&config); + config.portType = MA_VITA_PORT_TYPE_BGM; - miniaudio.unlock_event_types.map(function(event_type) { - document.addEventListener(event_type, miniaudio.unlock, true); - }); - } + return config; +} +/* END miniaudio_vita.c */ - window.miniaudio.referenceCount += 1; - return 1; - }, ma_device_type_playback, ma_device_type_capture, ma_device_type_duplex, ma_device_state_stopped, ma_device_state_started); - if (resultFromJS != 1) { - return MA_FAILED_TO_INIT_BACKEND; +MA_API void ma_get_device_backend_info(ma_device_backend_vtable* pBackendVTable, ma_device_backend_info* pBackendInfo) +{ + if (pBackendVTable == NULL || pBackendVTable->onBackendInfo == NULL || pBackendInfo == NULL) { + return; } - pCallbacks->onContextInit = ma_context_init__webaudio; - pCallbacks->onContextUninit = ma_context_uninit__webaudio; - pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__webaudio; - pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__webaudio; - pCallbacks->onDeviceInit = ma_device_init__webaudio; - pCallbacks->onDeviceUninit = ma_device_uninit__webaudio; - pCallbacks->onDeviceStart = ma_device_start__webaudio; - pCallbacks->onDeviceStop = ma_device_stop__webaudio; - pCallbacks->onDeviceRead = NULL; /* Not needed because WebAudio is asynchronous. */ - pCallbacks->onDeviceWrite = NULL; /* Not needed because WebAudio is asynchronous. */ - pCallbacks->onDeviceDataLoop = NULL; /* Not needed because WebAudio is asynchronous. */ - - return MA_SUCCESS; + MA_ZERO_OBJECT(pBackendInfo); + pBackendVTable->onBackendInfo(pBackendInfo); } -#endif /* MA_HAS_WEBAUDIO */ @@ -42480,134 +49598,129 @@ static ma_bool32 ma__is_channel_map_valid(const ma_channel* pChannelMap, ma_uint } -static ma_bool32 ma_context_is_backend_asynchronous(ma_context* pContext) +MA_API ma_result ma_device_update_descriptor(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pPublicDescriptor, const ma_device_descriptor* pInternalDescriptor) { - MA_ASSERT(pContext != NULL); + ma_result result; + ma_device_descriptor publicDescriptor; + ma_device_descriptor internalDescriptor; + ma_data_converter_config converterConfig; - if (pContext->callbacks.onDeviceRead == NULL && pContext->callbacks.onDeviceWrite == NULL) { - if (pContext->callbacks.onDeviceDataLoop == NULL) { - return MA_TRUE; - } else { - return MA_FALSE; + if (pDevice == NULL || pInternalDescriptor == NULL) { + return MA_INVALID_ARGS; + } + + /* This function should never be called with ma_device_type_duplex. It should be called once for each side. */ + if (deviceType == ma_device_type_duplex) { + return MA_INVALID_ARGS; + } + + /* The public descriptor can be null only if the device has already been initialized, in which case we can synthesise it from existing info. */ + if (pPublicDescriptor == NULL) { + if (ma_device_get_status(pDevice) == ma_device_status_uninitialized) { + return MA_INVALID_ARGS; /* Can't know what the public format it. */ } + + publicDescriptor.format = ma_device_get_format(pDevice, deviceType); + publicDescriptor.channels = ma_device_get_channels(pDevice, deviceType); + publicDescriptor.sampleRate = ma_device_get_sample_rate(pDevice); + ma_device_get_channel_map(pDevice, deviceType, publicDescriptor.channelMap, ma_countof(publicDescriptor.channelMap)); } else { - return MA_FALSE; + publicDescriptor = *pPublicDescriptor; } -} + if (pInternalDescriptor == NULL) { + return MA_INVALID_ARGS; /* Internal format must always be provided. */ + } else { + internalDescriptor = *pInternalDescriptor; + } -static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type deviceType) -{ - ma_result result; - MA_ASSERT(pDevice != NULL); + /* At this point we should not be needing to access `pPublic/InternalDescriptor`. We should be accessing `public/internalDescriptor` instead. We'll clear this to null so we can catch any errors. */ + pPublicDescriptor = NULL; + pInternalDescriptor = NULL; - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - if (pDevice->capture.format == ma_format_unknown) { - pDevice->capture.format = pDevice->capture.internalFormat; - } - if (pDevice->capture.channels == 0) { - pDevice->capture.channels = pDevice->capture.internalChannels; - } - if (pDevice->capture.channelMap[0] == MA_CHANNEL_NONE) { - MA_ASSERT(pDevice->capture.channels <= MA_MAX_CHANNELS); - if (pDevice->capture.internalChannels == pDevice->capture.channels) { - ma_channel_map_copy(pDevice->capture.channelMap, pDevice->capture.internalChannelMap, pDevice->capture.channels); + /* Make sure we have valid values for everything on the public side. */ + if (publicDescriptor.format == ma_format_unknown) { + publicDescriptor.format = internalDescriptor.format; + } + + if (publicDescriptor.channels == 0) { + publicDescriptor.channels = internalDescriptor.channels; + } + + if (publicDescriptor.sampleRate == 0) { + publicDescriptor.sampleRate = internalDescriptor.sampleRate; + } + + if (publicDescriptor.channelMap[0] == MA_CHANNEL_NONE) { + MA_ASSERT(publicDescriptor.channels <= MA_MAX_CHANNELS); + + if (publicDescriptor.channels == internalDescriptor.channels) { + ma_channel_map_copy(publicDescriptor.channelMap, internalDescriptor.channelMap, internalDescriptor.channels); + } else { + ma_channel_mix_mode channelMixMode; + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + channelMixMode = pDevice->capture.channelMixMode; } else { - if (pDevice->capture.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->capture.channelMap, pDevice->capture.channels); - } else { - ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pDevice->capture.channels); - } + channelMixMode = pDevice->playback.channelMixMode; } - } - } - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - if (pDevice->playback.format == ma_format_unknown) { - pDevice->playback.format = pDevice->playback.internalFormat; - } - if (pDevice->playback.channels == 0) { - pDevice->playback.channels = pDevice->playback.internalChannels; - } - if (pDevice->playback.channelMap[0] == MA_CHANNEL_NONE) { - MA_ASSERT(pDevice->playback.channels <= MA_MAX_CHANNELS); - if (pDevice->playback.internalChannels == pDevice->playback.channels) { - ma_channel_map_copy(pDevice->playback.channelMap, pDevice->playback.internalChannelMap, pDevice->playback.channels); + if (channelMixMode == ma_channel_mix_mode_simple) { + ma_channel_map_init_blank(publicDescriptor.channelMap, publicDescriptor.channels); } else { - if (pDevice->playback.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->playback.channelMap, pDevice->playback.channels); - } else { - ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pDevice->playback.channels); - } + ma_channel_map_init_standard(ma_standard_channel_map_default, publicDescriptor.channelMap, ma_countof(publicDescriptor.channelMap), publicDescriptor.channels); } } } - if (pDevice->sampleRate == 0) { - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - pDevice->sampleRate = pDevice->capture.internalSampleRate; - } else { - pDevice->sampleRate = pDevice->playback.internalSampleRate; - } - } - /* Data converters. */ - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - /* Converting from internal device format to client format. */ - ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->capture.internalFormat; - converterConfig.channelsIn = pDevice->capture.internalChannels; - converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; - converterConfig.pChannelMapIn = pDevice->capture.internalChannelMap; - converterConfig.formatOut = pDevice->capture.format; - converterConfig.channelsOut = pDevice->capture.channels; - converterConfig.sampleRateOut = pDevice->sampleRate; - converterConfig.pChannelMapOut = pDevice->capture.channelMap; + /* The data converter can now be initialized. */ + converterConfig = ma_data_converter_config_init_default(); + converterConfig.allowDynamicSampleRate = MA_FALSE; + converterConfig.resampling.algorithm = pDevice->resampling.algorithm; + converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; + converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; + converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + converterConfig.formatIn = internalDescriptor.format; + converterConfig.channelsIn = internalDescriptor.channels; + converterConfig.sampleRateIn = internalDescriptor.sampleRate; + converterConfig.pChannelMapIn = internalDescriptor.channelMap; + converterConfig.formatOut = publicDescriptor.format; + converterConfig.channelsOut = publicDescriptor.channels; + converterConfig.sampleRateOut = publicDescriptor.sampleRate; + converterConfig.pChannelMapOut = publicDescriptor.channelMap; converterConfig.channelMixMode = pDevice->capture.channelMixMode; converterConfig.calculateLFEFromSpatialChannels = pDevice->capture.calculateLFEFromSpatialChannels; - converterConfig.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; - converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; /* Make sure the old converter is uninitialized first. */ - if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { - ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + if (ma_device_get_status(pDevice) != ma_device_status_uninitialized) { + ma_data_converter_uninit(&pDevice->capture.converter, ma_device_get_allocation_callbacks(pDevice)); } - result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->capture.converter); + result = ma_data_converter_init(&converterConfig, ma_device_get_allocation_callbacks(pDevice), &pDevice->capture.converter); if (result != MA_SUCCESS) { return result; } - } - - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - /* Converting from client format to device format. */ - ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->playback.format; - converterConfig.channelsIn = pDevice->playback.channels; - converterConfig.sampleRateIn = pDevice->sampleRate; - converterConfig.pChannelMapIn = pDevice->playback.channelMap; - converterConfig.formatOut = pDevice->playback.internalFormat; - converterConfig.channelsOut = pDevice->playback.internalChannels; - converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; - converterConfig.pChannelMapOut = pDevice->playback.internalChannelMap; - converterConfig.channelMixMode = pDevice->playback.channelMixMode; - converterConfig.calculateLFEFromSpatialChannels = pDevice->playback.calculateLFEFromSpatialChannels; - converterConfig.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; - converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; + } else { + converterConfig.formatIn = publicDescriptor.format; + converterConfig.channelsIn = publicDescriptor.channels; + converterConfig.sampleRateIn = publicDescriptor.sampleRate; + converterConfig.pChannelMapIn = publicDescriptor.channelMap; + converterConfig.formatOut = internalDescriptor.format; + converterConfig.channelsOut = internalDescriptor.channels; + converterConfig.sampleRateOut = internalDescriptor.sampleRate; + converterConfig.pChannelMapOut = internalDescriptor.channelMap; + converterConfig.channelMixMode = pDevice->capture.channelMixMode; + converterConfig.calculateLFEFromSpatialChannels = pDevice->capture.calculateLFEFromSpatialChannels; /* Make sure the old converter is uninitialized first. */ - if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { - ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + if (ma_device_get_status(pDevice) != ma_device_status_uninitialized) { + ma_data_converter_uninit(&pDevice->playback.converter, ma_device_get_allocation_callbacks(pDevice)); } - result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->playback.converter); + result = ma_data_converter_init(&converterConfig, ma_device_get_allocation_callbacks(pDevice), &pDevice->playback.converter); if (result != MA_SUCCESS) { return result; } @@ -42620,65 +49733,89 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d The first is a duplex device for backends that use a callback for data delivery. The reason this is needed is that the input stage needs to have a buffer to place the input data while it - waits for the playback stage, after which the miniaudio data callback will get fired. This is - not needed for backends that use a blocking API because miniaudio manages temporary buffers on - the stack to achieve this. - - The other situation is when the data converter does not have the ability to query the number - of input frames that are required in order to process a given number of output frames. When - performing data conversion, it's useful if miniaudio know exactly how many frames it needs - from the client in order to generate a given number of output frames. This way, only exactly - the number of frames are needed to be read from the client which means no cache is necessary. - On the other hand, if miniaudio doesn't know how many frames to read, it is forced to read - in fixed sized chunks and then cache any residual unused input frames, those of which will be - processed at a later stage. + waits for the playback stage, after which the miniaudio data callback will get fired. + + The other situation is when resampling is required. This is because when resampling, a different + number of input frames will be consumed to the number that is produced. When it is not exactly + equal we'll need to cache any residual. */ - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - ma_uint64 unused; + if (deviceType == ma_device_type_playback) { + ma_bool32 isResampling; pDevice->playback.inputCacheConsumed = 0; pDevice->playback.inputCacheRemaining = 0; - if (pDevice->type == ma_device_type_duplex || /* Duplex. backend may decide to use ma_device_handle_backend_data_callback() which will require this cache. */ - ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, 1, &unused) != MA_SUCCESS) /* Data conversion required input frame calculation not supported. */ - { - /* We need a heap allocated cache. We want to size this based on the period size. */ + isResampling = publicDescriptor.sampleRate != internalDescriptor.sampleRate; + + if (pDevice->type == ma_device_type_duplex || isResampling) { + /* We need a heap allocated cache. We want to size this based on the period size. The size need not be exact, but it's best to keep it approximately correct. */ void* pNewInputCache; ma_uint64 newInputCacheCap; ma_uint64 newInputCacheSizeInBytes; - newInputCacheCap = ma_calculate_frame_count_after_resampling(pDevice->playback.internalSampleRate, pDevice->sampleRate, pDevice->playback.internalPeriodSizeInFrames); + newInputCacheCap = ma_calculate_frame_count_after_resampling(ma_device_get_internal_sample_rate(pDevice, ma_device_type_playback), ma_device_get_sample_rate(pDevice), pDevice->playback.internalPeriodSizeInFrames); - newInputCacheSizeInBytes = newInputCacheCap * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + newInputCacheSizeInBytes = newInputCacheCap * ma_get_bytes_per_frame(ma_device_get_format(pDevice, ma_device_type_playback), ma_device_get_channels(pDevice, ma_device_type_playback)); if (newInputCacheSizeInBytes > MA_SIZE_MAX) { - ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->playback.pInputCache, ma_device_get_allocation_callbacks(pDevice)); pDevice->playback.pInputCache = NULL; pDevice->playback.inputCacheCap = 0; return MA_OUT_OF_MEMORY; /* Allocation too big. Should never hit this, but makes the cast below safer for 32-bit builds. */ } - pNewInputCache = ma_realloc(pDevice->playback.pInputCache, (size_t)newInputCacheSizeInBytes, &pDevice->pContext->allocationCallbacks); + pNewInputCache = ma_realloc(pDevice->playback.pInputCache, (size_t)newInputCacheSizeInBytes, ma_device_get_allocation_callbacks(pDevice)); if (pNewInputCache == NULL) { - ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->playback.pInputCache, ma_device_get_allocation_callbacks(pDevice)); pDevice->playback.pInputCache = NULL; pDevice->playback.inputCacheCap = 0; return MA_OUT_OF_MEMORY; } pDevice->playback.pInputCache = pNewInputCache; - pDevice->playback.inputCacheCap = newInputCacheCap; + pDevice->playback.inputCacheCap = (ma_uint32)newInputCacheCap; } else { /* Heap allocation not required. Make sure we clear out the old cache just in case this function was called in response to a route change. */ - ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->playback.pInputCache, ma_device_get_allocation_callbacks(pDevice)); pDevice->playback.pInputCache = NULL; pDevice->playback.inputCacheCap = 0; } } + + /* Update the internal format, channels, rate, map. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + pDevice->capture.internalFormat = internalDescriptor.format; + pDevice->capture.internalChannels = internalDescriptor.channels; + pDevice->capture.internalSampleRate = internalDescriptor.sampleRate; + pDevice->capture.internalPeriodSizeInFrames = internalDescriptor.periodSizeInFrames; + pDevice->capture.internalPeriods = internalDescriptor.periodCount; + } + + if (deviceType == ma_device_type_playback) { + pDevice->playback.internalFormat = internalDescriptor.format; + pDevice->playback.internalChannels = internalDescriptor.channels; + pDevice->playback.internalSampleRate = internalDescriptor.sampleRate; + pDevice->playback.internalPeriodSizeInFrames = internalDescriptor.periodSizeInFrames; + pDevice->playback.internalPeriods = internalDescriptor.periodCount; + } + + /* Update the public format, channels, rate. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + pDevice->capture.format = publicDescriptor.format; + pDevice->capture.channels = publicDescriptor.channels; + } + + if (deviceType == ma_device_type_playback) { + pDevice->playback.format = publicDescriptor.format; + pDevice->playback.channels = publicDescriptor.channels; + } + + pDevice->sampleRate = publicDescriptor.sampleRate; + return MA_SUCCESS; } -MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pDescriptorPlayback, const ma_device_descriptor* pDescriptorCapture) +static ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pPublicDescriptorPlayback, const ma_device_descriptor* pPublicDescriptorCapture, const ma_device_descriptor* pInternalDescriptorPlayback, const ma_device_descriptor* pInternalDescriptorCapture) { ma_result result; @@ -42688,201 +49825,566 @@ MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceTy /* Capture. */ if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - if (ma_device_descriptor_is_valid(pDescriptorCapture) == MA_FALSE) { + if (ma_device_descriptor_is_valid(pInternalDescriptorCapture) == MA_FALSE) { return MA_INVALID_ARGS; } - pDevice->capture.internalFormat = pDescriptorCapture->format; - pDevice->capture.internalChannels = pDescriptorCapture->channels; - pDevice->capture.internalSampleRate = pDescriptorCapture->sampleRate; - MA_COPY_MEMORY(pDevice->capture.internalChannelMap, pDescriptorCapture->channelMap, sizeof(pDescriptorCapture->channelMap)); - pDevice->capture.internalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; - pDevice->capture.internalPeriods = pDescriptorCapture->periodCount; + pDevice->capture.internalFormat = pInternalDescriptorCapture->format; + pDevice->capture.internalChannels = pInternalDescriptorCapture->channels; + pDevice->capture.internalSampleRate = pInternalDescriptorCapture->sampleRate; + pDevice->capture.internalPeriodSizeInFrames = pInternalDescriptorCapture->periodSizeInFrames; + pDevice->capture.internalPeriods = pInternalDescriptorCapture->periodCount; if (pDevice->capture.internalPeriodSizeInFrames == 0) { - pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptorCapture->periodSizeInMilliseconds, pDescriptorCapture->sampleRate); + pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pInternalDescriptorCapture->periodSizeInMilliseconds, pInternalDescriptorCapture->sampleRate); } } /* Playback. */ if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - if (ma_device_descriptor_is_valid(pDescriptorPlayback) == MA_FALSE) { + if (ma_device_descriptor_is_valid(pInternalDescriptorPlayback) == MA_FALSE) { return MA_INVALID_ARGS; } - pDevice->playback.internalFormat = pDescriptorPlayback->format; - pDevice->playback.internalChannels = pDescriptorPlayback->channels; - pDevice->playback.internalSampleRate = pDescriptorPlayback->sampleRate; - MA_COPY_MEMORY(pDevice->playback.internalChannelMap, pDescriptorPlayback->channelMap, sizeof(pDescriptorPlayback->channelMap)); - pDevice->playback.internalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; - pDevice->playback.internalPeriods = pDescriptorPlayback->periodCount; + pDevice->playback.internalFormat = pInternalDescriptorPlayback->format; + pDevice->playback.internalChannels = pInternalDescriptorPlayback->channels; + pDevice->playback.internalSampleRate = pInternalDescriptorPlayback->sampleRate; + pDevice->playback.internalPeriodSizeInFrames = pInternalDescriptorPlayback->periodSizeInFrames; + pDevice->playback.internalPeriods = pInternalDescriptorPlayback->periodCount; if (pDevice->playback.internalPeriodSizeInFrames == 0) { - pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptorPlayback->periodSizeInMilliseconds, pDescriptorPlayback->sampleRate); + pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pInternalDescriptorPlayback->periodSizeInMilliseconds, pInternalDescriptorPlayback->sampleRate); } } - /* - The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info(pDevice, deviceType)` instead. - For loopback devices, we need to retrieve the name of the playback device. - */ + /* Update descriptors. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + result = ma_device_update_descriptor(pDevice, ma_device_type_capture, pPublicDescriptorCapture, pInternalDescriptorCapture); + if (result != MA_SUCCESS) { + return result; + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_device_update_descriptor(pDevice, ma_device_type_playback, pPublicDescriptorPlayback, pInternalDescriptorPlayback); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + + + +MA_API ma_result ma_device_op_completion_event_init(ma_device_op_completion_event* pCompletionEvent) +{ + if (pCompletionEvent == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pCompletionEvent); + pCompletionEvent->result = MA_BUSY; + + #ifndef MA_NO_THREADING { - ma_device_info deviceInfo; + ma_result result; - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { - result = ma_device_get_info(pDevice, ma_device_type_capture, &deviceInfo); - if (result == MA_SUCCESS) { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); - } else { - /* We failed to retrieve the device info. Fall back to a default name. */ - if (pDescriptorCapture->pDeviceID == NULL) { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "Capture Device", (size_t)-1); - } + result = ma_event_init(&pCompletionEvent->event); + if (result != MA_SUCCESS) { + return result; + } + } + #endif + + return MA_SUCCESS; +} + +MA_API void ma_device_op_completion_event_uninit(ma_device_op_completion_event* pCompletionEvent) +{ + if (pCompletionEvent == NULL) { + return; + } + + #ifndef MA_NO_THREADING + { + ma_event_uninit(&pCompletionEvent->event); + } + #endif +} + +MA_API void ma_device_op_completion_event_signal(ma_device_op_completion_event* pCompletionEvent, ma_result result) +{ + if (pCompletionEvent == NULL) { + return; + } + + pCompletionEvent->result = result; + + #ifndef MA_NO_THREADING + { + ma_event_signal(&pCompletionEvent->event); + } + #endif +} + +MA_API void ma_device_op_completion_event_wait(ma_device_op_completion_event* pCompletionEvent) +{ + if (pCompletionEvent == NULL) { + return; + } + + #ifndef MA_NO_THREADING + { + ma_event_wait(&pCompletionEvent->event); + } + #endif +} + +MA_API ma_result ma_device_op_completion_event_result(ma_device_op_completion_event* pCompletionEvent) +{ + if (pCompletionEvent == NULL) { + return MA_INVALID_ARGS; + } + + ma_device_op_completion_event_wait(pCompletionEvent); + + return pCompletionEvent->result; +} + + + +static ma_result ma_device_op_do_init(ma_device* pDevice, ma_device_op_params params, ma_device_op_completion_event* pCompletionEvent) +{ + ma_result result; + void* pBackendState; + + if (pDevice->pContext->pVTable->onDeviceInit != NULL) { + result = pDevice->pContext->pVTable->onDeviceInit(pDevice, params.init.pDeviceBackendConfig, params.init.pDescriptorPlayback, params.init.pDescriptorCapture, &pBackendState); + } else { + /* Should never hit this. */ + MA_ASSERT(!"No onDeviceInit callback."); + return MA_NOT_IMPLEMENTED; + } + + /* Store the backend state as soon as possible. */ + ma_atomic_store_explicit_ptr((volatile void**)&pDevice->pBackendState, pBackendState, ma_atomic_memory_order_relaxed); + + /* The device is now in a stopped state. */ + ma_device_set_status(pDevice, ma_device_status_stopped); + + ma_device_op_completion_event_signal(pCompletionEvent, result); + return result; +} + +static ma_result ma_device_op_do_uninit(ma_device* pDevice, ma_device_op_completion_event* pCompletionEvent) +{ + if (pDevice->pContext->pVTable->onDeviceUninit != NULL) { + pDevice->pContext->pVTable->onDeviceUninit(pDevice); + } + + ma_device_op_completion_event_signal(pCompletionEvent, MA_SUCCESS); + return MA_SUCCESS; +} + +static ma_result ma_device_op_do_start(ma_device* pDevice, ma_device_op_completion_event* pCompletionEvent) +{ + ma_result result; + + if (pDevice->pContext->pVTable->onDeviceStart != NULL) { + result = pDevice->pContext->pVTable->onDeviceStart(pDevice); + } else { + result = MA_SUCCESS; + } + + if (result == MA_SUCCESS) { + ma_device_set_status(pDevice, ma_device_status_started); + ma_device_post_notification_started(pDevice); + } else { + /* Starting the device failed. Make sure we put the device back into a stopped status. */ + ma_device_set_status(pDevice, ma_device_status_stopped); + } + + ma_device_op_completion_event_signal(pCompletionEvent, result); + return result; +} + +static ma_result ma_device_op_do_stop(ma_device* pDevice, ma_device_op_completion_event* pCompletionEvent) +{ + ma_result result; + + if (pDevice->pContext->pVTable->onDeviceStop != NULL) { + result = pDevice->pContext->pVTable->onDeviceStop(pDevice); + } else { + result = MA_SUCCESS; /* No stop callback with the backend. Just assume successful. */ + } + + ma_device_set_status(pDevice, ma_device_status_stopped); + + /* A stop event should always be posted when the device has stopped. */ + ma_device_post_notification_stopped(pDevice); + + /* Silence an assigned but not used warning (we're always returning MA_SUCCESS here for now). We might change this later to return void. */ + (void)result; + + ma_device_op_completion_event_signal(pCompletionEvent, MA_SUCCESS); + return MA_SUCCESS; +} + + + +MA_API ma_result ma_device_op_queue_init(ma_device_op_queue* pQueue) +{ + if (pQueue == NULL) { + return MA_SUCCESS; + } + + MA_ZERO_OBJECT(pQueue); + + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_mutex_init(&pQueue->lock); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_semaphore_init(0, &pQueue->sem); + if (result != MA_SUCCESS) { + return result; + } + } + #else + { + /* Threading disabled. No sync objects required. */ + } + #endif + + return MA_SUCCESS; +} + +MA_API void ma_device_op_queue_uninit(ma_device_op_queue* pQueue) +{ + if (pQueue != NULL) { + return; + } + + #ifndef MA_NO_THREADING + { + ma_semaphore_uninit(&pQueue->sem); + ma_mutex_uninit(&pQueue->lock); + } + #endif +} + +static void ma_device_op_queue_lock(ma_device_op_queue* pQueue) +{ + #ifndef MA_NO_THREADING + { + ma_mutex_lock(&pQueue->lock); + } + #else + { + /* Threading disabled. Nothing to do. */ + (void)pQueue; + } + #endif +} + +static void ma_device_op_queue_unlock(ma_device_op_queue* pQueue) +{ + #ifndef MA_NO_THREADING + { + ma_mutex_unlock(&pQueue->lock); + } + #else + { + /* Threading disabled. Nothing to do. */ + (void)pQueue; + } + #endif +} + +static ma_uint32 ma_device_op_queue_head(const ma_device_op_queue* pQueue) +{ + if (pQueue->tail < pQueue->count) { + return ma_countof(pQueue->pItems) - (pQueue->count - pQueue->tail); + } else { + return pQueue->tail - pQueue->count; + } +} + +static ma_result ma_device_op_queue_new_nolock(ma_device_op_queue* pQueue, ma_device_op_type type, const ma_device_op_params* pParams, ma_device_op_completion_event* pCompletionEvent) +{ + ma_device_op* pOp = NULL; + + MA_ASSERT(pQueue != NULL); + + if (pQueue->count >= ma_countof(pQueue->pItems)) { + MA_ASSERT(!"Too many device operations queued."); + return MA_INVALID_OPERATION; + } + + pOp = &pQueue->pItems[pQueue->tail]; + + pOp->type = type; + pOp->pCompletionEvent = pCompletionEvent; + + if (pParams != NULL) { + pOp->params = *pParams; + } + + + pQueue->count += 1; + pQueue->tail = (pQueue->tail + 1) % ma_countof(pQueue->pItems); + + return MA_SUCCESS; +} + +MA_API ma_result ma_device_op_queue_push(ma_device_op_queue* pQueue, ma_device_op_type type, const ma_device_op_params* pParams, ma_device_op_completion_event* pCompletionEvent) +{ + ma_result result; + + if (pQueue == NULL) { + return MA_INVALID_ARGS; + } + + ma_device_op_queue_lock(pQueue); + { + result = ma_device_op_queue_new_nolock(pQueue, type, pParams, pCompletionEvent); + if (result == MA_SUCCESS) { + #ifndef MA_NO_THREADING + { + ma_semaphore_release(&pQueue->sem); } + #endif } + } + ma_device_op_queue_unlock(pQueue); - if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - result = ma_device_get_info(pDevice, ma_device_type_playback, &deviceInfo); - if (result == MA_SUCCESS) { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); - } else { - /* We failed to retrieve the device info. Fall back to a default name. */ - if (pDescriptorPlayback->pDeviceID == NULL) { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "Playback Device", (size_t)-1); - } + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +static ma_result ma_device_op_queue_next_nolock(ma_device_op_queue* pQueue, ma_device_op** ppOp) +{ + ma_device_op* pOp = NULL; + ma_uint32 head; + + MA_ASSERT(pQueue != NULL); + MA_ASSERT(ppOp != NULL); + MA_ASSERT(*ppOp == NULL); + MA_ASSERT(pQueue->count > 0); + + head = ma_device_op_queue_head(pQueue); + + pOp = &pQueue->pItems[head]; + pQueue->count -= 1; + + *ppOp = pOp; + + return MA_SUCCESS; +} + +MA_API ma_result ma_device_op_queue_next(ma_device_op_queue* pQueue, ma_blocking_mode blockingMode, ma_device_op** ppOp) +{ + ma_result result; + ma_device_op* pOp = NULL; + + if (ppOp == NULL) { + return MA_INVALID_ARGS; + } + + *ppOp = NULL; + + if (pQueue == NULL) { + return MA_INVALID_ARGS; + } + + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + ma_device_op_queue_lock(pQueue); + { + if (pQueue->count == 0) { + ma_device_op_queue_unlock(pQueue); + return MA_NO_DATA_AVAILABLE; } + + /* Getting here means there is an item available. We still need to decrement the semaphore's counter. */ + #ifndef MA_NO_THREADING + { + ma_semaphore_wait(&pQueue->sem); /* <-- Should not block. */ + } + #endif + + result = ma_device_op_queue_next_nolock(pQueue, &pOp); + } + ma_device_op_queue_unlock(pQueue); + } else { + #ifndef MA_NO_THREADING + { + ma_semaphore_wait(&pQueue->sem); + } + #else + { + if (pQueue->count == 0) { + return MA_NO_DATA_AVAILABLE; + } + } + #endif + + ma_device_op_queue_lock(pQueue); + { + result = ma_device_op_queue_next_nolock(pQueue, &pOp); } + ma_device_op_queue_unlock(pQueue); + } + + if (result != MA_SUCCESS) { + return result; } - /* Update data conversion. */ - return ma_device__post_init_setup(pDevice, deviceType); /* TODO: Should probably rename ma_device__post_init_setup() to something better. */ + *ppOp = pOp; + + return MA_SUCCESS; } -static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) +#ifndef MA_NO_THREADING +static ma_thread_result MA_THREADCALL ma_audio_thread(void* pData) { ma_device* pDevice = (ma_device*)pData; -#if defined(MA_WIN32) && !defined(MA_XBOX) + ma_result result; + ma_device_op* pOp; + #ifdef MA_WIN32 HRESULT CoInitializeResult; -#endif + #endif - MA_ASSERT(pDevice != NULL); + #if defined(MA_WIN32) && !defined(MA_XBOX) + { + CoInitializeResult = ma_CoInitializeEx(pDevice->pContext, NULL, MA_COINIT_VALUE); + } + #endif -#if defined(MA_WIN32) && !defined(MA_XBOX) - CoInitializeResult = ma_CoInitializeEx(pDevice->pContext, NULL, MA_COINIT_VALUE); -#endif + /* We should have an initial MA_DEVICE_OP_INIT in the queue. If not we need to throw an error. */ + result = ma_device_op_queue_next(&pDevice->opQueue, MA_BLOCKING_MODE_BLOCKING, &pOp); + if (result != MA_SUCCESS) { + MA_ASSERT(!"Device operation queue not seeded with an initial MA_DEVICE_OP_INIT."); + goto end_audio_thread; + } - /* - When the device is being initialized its initial state is set to ma_device_state_uninitialized. Before returning from - ma_device_init(), the state needs to be set to something valid. In miniaudio the device's default state immediately - after initialization is stopped, so therefore we need to mark the device as such. miniaudio will wait on the worker - thread to signal an event to know when the worker thread is ready for action. - */ - ma_device__set_state(pDevice, ma_device_state_stopped); - ma_event_signal(&pDevice->stopEvent); + if (pOp->type != MA_DEVICE_OP_INIT) { + MA_ASSERT(!"Device operation queue was seeded with an initial op, but was not of the expected type of MA_DEVICE_OP_INIT."); + goto end_audio_thread; + } - for (;;) { /* <-- This loop just keeps the thread alive. The main audio loop is inside. */ - ma_result startResult; - ma_result stopResult; /* <-- This will store the result from onDeviceStop(). If it returns an error, we don't fire the stopped notification callback. */ + result = ma_device_op_do_init(pDevice, pOp->params, pOp->pCompletionEvent); + if (result != MA_SUCCESS) { + goto end_audio_thread; + } - /* We wait on an event to know when something has requested that the device be started and the main loop entered. */ - ma_event_wait(&pDevice->wakeupEvent); + for (;;) { + ma_blocking_mode blockingMode = MA_BLOCKING_MODE_BLOCKING; - /* Default result code. */ - pDevice->workResult = MA_SUCCESS; + /* If the device has put into an uninitialized state we will make sure the thread is always terminated. */ + if (ma_device_get_status(pDevice) == ma_device_status_uninitialized) { + blockingMode = MA_BLOCKING_MODE_NON_BLOCKING; + } - /* If the reason for the wake up is that we are terminating, just break from the loop. */ - if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + result = ma_device_op_queue_next(&pDevice->opQueue, blockingMode, &pOp); + if (result != MA_SUCCESS) { break; } /* - Getting to this point means the device is wanting to get started. The function that has requested that the device - be started will be waiting on an event (pDevice->startEvent) which means we need to make sure we signal the event - in both the success and error case. It's important that the state of the device is set _before_ signaling the event. + This is a safety mechanism to avoid an infinite loop. If the device has been marked as uninitialized, but we haven't + got anything in the queue, we'll get out of the loop to make sure we don't get stuck. This can *technically* happen + if pushing the MA_DEVICE_OP_UNINIT in `ma_device_uninit()` fails, although it'll be exceptionally rare. */ - MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_starting); - - /* If the device has a start callback, start it now. */ - if (pDevice->pContext->callbacks.onDeviceStart != NULL) { - startResult = pDevice->pContext->callbacks.onDeviceStart(pDevice); - } else { - startResult = MA_SUCCESS; + if (result == MA_NO_DATA_AVAILABLE) { + if (ma_device_get_status(pDevice) == ma_device_status_uninitialized) { + break; + } } - /* - If starting was not successful we'll need to loop back to the start and wait for something - to happen (pDevice->wakeupEvent). - */ - if (startResult != MA_SUCCESS) { - pDevice->workResult = startResult; - ma_event_signal(&pDevice->startEvent); /* <-- Always signal the start event so ma_device_start() can return as it'll be waiting on it. */ - continue; - } + /* */ if (pOp->type == MA_DEVICE_OP_UNINIT) { + break; /* Breaking from the loop will trigger an uninit. */ + } else if (pOp->type == MA_DEVICE_OP_START) { + /* We should never be attempting to start the device unless it's in a stopped state. */ + MA_ASSERT(ma_device_get_status(pDevice) == ma_device_status_starting); - /* Make sure the state is set appropriately. */ - ma_device__set_state(pDevice, ma_device_state_started); /* <-- Set this before signaling the event so that the state is always guaranteed to be good after ma_device_start() has returned. */ - ma_event_signal(&pDevice->startEvent); + result = ma_device_op_do_start(pDevice, pOp->pCompletionEvent); + if (result == MA_SUCCESS) { + /* If the backend is asynchronous, it'll be running it's own loop. No need for us to do it here. */ + if (pDevice->pContext->pVTable->onDeviceStep == NULL) { + continue; /* <-- This just makes the audio thread wait for a new operation to arrive, like an uninit or stop. */ + } else { + MA_ASSERT(pDevice->pContext->pVTable->onDeviceStep != NULL); - ma_device__on_notification_started(pDevice); + while (ma_device_is_started(pDevice)) { + result = pDevice->pContext->pVTable->onDeviceStep(pDevice, MA_BLOCKING_MODE_BLOCKING); + if (result != MA_SUCCESS) { + break; + } + } - if (pDevice->pContext->callbacks.onDeviceDataLoop != NULL) { - pDevice->pContext->callbacks.onDeviceDataLoop(pDevice); - } else { - /* The backend is not using a custom main loop implementation, so now fall back to the blocking read-write implementation. */ - ma_device_audio_thread__default_read_write(pDevice); - } + /* The only op allowed at this point is a stop or uninit. We need to check the queue in case ma_device_stop() or ma_device_uninit() posted an item. */ + result = ma_device_op_queue_next(&pDevice->opQueue, MA_BLOCKING_MODE_NON_BLOCKING, &pOp); + if (result == MA_SUCCESS) { + if (pOp->type == MA_DEVICE_OP_UNINIT) { + ma_device_op_do_stop(pDevice, NULL); + break; /* <-- This gets out of the op queue loop and terminates the audio thread. */ + } else if (pOp->type == MA_DEVICE_OP_STOP) { + ma_device_op_do_stop(pDevice, pOp->pCompletionEvent); + } else { + MA_ASSERT(!"Expecting a stop or uninit op, but got something else."); + } + } else { + /* There is no stop op on the queue which means the backend itself must have terminated from its loop. We'll use a dummy stop event here. */ + ma_device_op_do_stop(pDevice, NULL); + } + } + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to start device: %s.", ma_result_description(result)); + } + } else if (pOp->type == MA_DEVICE_OP_STOP) { + /* + In the `MA_DEVICE_OP_START` case above, we are calling `ma_device_op_do_stop()` directly, but that only applies + to backends with a step callback. We don't want to be calling that a second time, but there is a very small chance + that the application can call `ma_device_stop()` at the same time as the backend itself terminated from its own + loop. In this case we're just going to signal the op. - /* Getting here means we have broken from the main loop which happens the application has requested that device be stopped. */ - if (pDevice->pContext->callbacks.onDeviceStop != NULL) { - stopResult = pDevice->pContext->callbacks.onDeviceStop(pDevice); + For backends without a stop callback, by this point `ma_device_op_do_stop()` has not been called, so we need to do + that here. + */ + if (pDevice->pContext->pVTable->onDeviceStep == NULL) { + ma_device_op_do_stop(pDevice, pOp->pCompletionEvent); + } else { + ma_device_op_completion_event_signal(pOp->pCompletionEvent, MA_SUCCESS); + } } else { - stopResult = MA_SUCCESS; /* No stop callback with the backend. Just assume successful. */ + MA_ASSERT(!"Unexpected device op."); } + } - /* - After the device has stopped, make sure an event is posted. Don't post a stopped event if - stopping failed. This can happen on some backends when the underlying stream has been - stopped due to the device being physically unplugged or disabled via an OS setting. - */ - if (stopResult == MA_SUCCESS) { - ma_device__on_notification_stopped(pDevice); - } + /* When we leave the loop we always need to uninitialize the backend. */ + ma_device_op_do_uninit(pDevice, pOp->pCompletionEvent); - /* If we stopped because the device has been uninitialized, abort now. */ - if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { - break; +end_audio_thread: + #if defined(MA_WIN32) && !defined(MA_XBOX) + { + if (CoInitializeResult == S_OK || CoInitializeResult == S_FALSE) { + ma_CoUninitialize(pDevice->pContext); } - - /* A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. */ - ma_device__set_state(pDevice, ma_device_state_stopped); - ma_event_signal(&pDevice->stopEvent); } - -#if defined(MA_WIN32) && !defined(MA_XBOX) - if (CoInitializeResult == S_OK || CoInitializeResult == S_FALSE) { - ma_CoUninitialize(pDevice->pContext); - } -#endif + #endif return (ma_thread_result)0; } - - -/* Helper for determining whether or not the given device is initialized. */ -static ma_bool32 ma_device__is_initialized(ma_device* pDevice) -{ - if (pDevice == NULL) { - return MA_FALSE; - } - - return ma_device_get_state(pDevice) != ma_device_state_uninitialized; -} - +#endif #ifdef MA_WIN32 static ma_result ma_context_uninit_backend_apis__win32(ma_context* pContext) @@ -42890,20 +50392,6 @@ static ma_result ma_context_uninit_backend_apis__win32(ma_context* pContext) /* For some reason UWP complains when CoUninitialize() is called. I'm just not going to call it on UWP. */ #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) { - /* TODO: Remove this once the new single threaded backend system is in place in 0.12. */ - #if !defined(MA_XBOX) - { - if (pContext->win32.CoInitializeResult == S_OK || pContext->win32.CoInitializeResult == S_FALSE) { - ma_CoUninitialize(pContext); /* TODO: Remove this once the new single threaded backend system is in place in 0.12. */ - } - } - #endif - - #if defined(MA_WIN32_DESKTOP) - ma_dlclose(ma_context_get_log(pContext), pContext->win32.hUser32DLL); - ma_dlclose(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL); - #endif - ma_dlclose(ma_context_get_log(pContext), pContext->win32.hOle32DLL); } #else @@ -42923,30 +50411,6 @@ static ma_result ma_context_init_backend_apis__win32(ma_context* pContext) */ #if (defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK)) && !defined(MA_XBOX) { - #if defined(MA_WIN32_DESKTOP) - { - /* User32.dll */ - pContext->win32.hUser32DLL = ma_dlopen(ma_context_get_log(pContext), "user32.dll"); - if (pContext->win32.hUser32DLL == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } - - pContext->win32.GetForegroundWindow = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hUser32DLL, "GetForegroundWindow"); - pContext->win32.GetDesktopWindow = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hUser32DLL, "GetDesktopWindow"); - - - /* Advapi32.dll */ - pContext->win32.hAdvapi32DLL = ma_dlopen(ma_context_get_log(pContext), "advapi32.dll"); - if (pContext->win32.hAdvapi32DLL == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } - - pContext->win32.RegOpenKeyExA = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegOpenKeyExA"); - pContext->win32.RegCloseKey = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegCloseKey"); - pContext->win32.RegQueryValueExA = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->win32.hAdvapi32DLL, "RegQueryValueExA"); - } - #endif - /* Ole32.dll */ pContext->win32.hOle32DLL = ma_dlopen(ma_context_get_log(pContext), "ole32.dll"); if (pContext->win32.hOle32DLL == NULL) { @@ -42967,13 +50431,6 @@ static ma_result ma_context_init_backend_apis__win32(ma_context* pContext) } #endif - /* TODO: Remove this once the new single threaded backend system is in place in 0.12. */ - #if !defined(MA_XBOX) - { - pContext->win32.CoInitializeResult = ma_CoInitializeEx(pContext, NULL, MA_COINIT_VALUE); - } - #endif - return MA_SUCCESS; } #else @@ -43017,171 +50474,173 @@ static ma_result ma_context_uninit_backend_apis(ma_context* pContext) } -/* The default capacity doesn't need to be too big. */ -#ifndef MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY -#define MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY 32 -#endif - -MA_API ma_device_job_thread_config ma_device_job_thread_config_init(void) -{ - ma_device_job_thread_config config; - - MA_ZERO_OBJECT(&config); - config.noThread = MA_FALSE; - config.jobQueueCapacity = MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY; - config.jobQueueFlags = 0; - - return config; -} - -static ma_thread_result MA_THREADCALL ma_device_job_thread_entry(void* pUserData) +MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB) { - ma_device_job_thread* pJobThread = (ma_device_job_thread*)pUserData; - MA_ASSERT(pJobThread != NULL); - - for (;;) { - ma_result result; - ma_job job; + size_t i; - result = ma_device_job_thread_next(pJobThread, &job); - if (result != MA_SUCCESS) { - break; - } + if (pA == NULL || pB == NULL) { + return MA_FALSE; + } - if (job.toc.breakup.code == MA_JOB_TYPE_QUIT) { - break; + for (i = 0; i < sizeof(ma_device_id); i += 1) { + if (((const char*)pA)[i] != ((const char*)pB)[i]) { + return MA_FALSE; } - - ma_job_process(&job); } - return (ma_thread_result)0; + return MA_TRUE; } -MA_API ma_result ma_device_job_thread_init(const ma_device_job_thread_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_job_thread* pJobThread) -{ - ma_result result; - ma_job_queue_config jobQueueConfig; - - if (pJobThread == NULL) { - return MA_INVALID_ARGS; - } - - MA_ZERO_OBJECT(pJobThread); - - if (pConfig == NULL) { - return MA_INVALID_ARGS; - } - - - /* Initialize the job queue before the thread to ensure it's in a valid state. */ - jobQueueConfig = ma_job_queue_config_init(pConfig->jobQueueFlags, pConfig->jobQueueCapacity); - - result = ma_job_queue_init(&jobQueueConfig, pAllocationCallbacks, &pJobThread->jobQueue); - if (result != MA_SUCCESS) { - return result; /* Failed to initialize job queue. */ - } - /* The thread needs to be initialized after the job queue to ensure the thread doesn't try to access it prematurely. */ - if (pConfig->noThread == MA_FALSE) { - result = ma_thread_create(&pJobThread->thread, ma_thread_priority_normal, 0, ma_device_job_thread_entry, pJobThread, pAllocationCallbacks); - if (result != MA_SUCCESS) { - ma_job_queue_uninit(&pJobThread->jobQueue, pAllocationCallbacks); - return result; /* Failed to create the job thread. */ - } - - pJobThread->_hasThread = MA_TRUE; - } else { - pJobThread->_hasThread = MA_FALSE; - } +MA_API ma_device_backend_config ma_device_backend_config_init(ma_device_backend_vtable* pVTable, const void* pConfig) +{ + ma_device_backend_config config; + config.pVTable = pVTable; + config.pConfig = pConfig; - return MA_SUCCESS; + return config; } -MA_API void ma_device_job_thread_uninit(ma_device_job_thread* pJobThread, const ma_allocation_callbacks* pAllocationCallbacks) + +static const void* ma_find_device_backend_config(const ma_device_backend_config* pBackends, size_t count, ma_device_backend_vtable* pVTable) { - if (pJobThread == NULL) { - return; - } + size_t iBackend; - /* The first thing to do is post a quit message to the job queue. If we're using a thread we'll need to wait for it. */ - { - ma_job job = ma_job_init(MA_JOB_TYPE_QUIT); - ma_device_job_thread_post(pJobThread, &job); + if (pBackends == NULL || count == 0) { + return NULL; } - /* Wait for the thread to terminate naturally. */ - if (pJobThread->_hasThread) { - ma_thread_wait(&pJobThread->thread); + for (iBackend = 0; iBackend < count; iBackend += 1) { + if (pBackends[iBackend].pVTable == pVTable) { + return pBackends[iBackend].pConfig; + } } - /* At this point the thread should be terminated so we can safely uninitialize the job queue. */ - ma_job_queue_uninit(&pJobThread->jobQueue, pAllocationCallbacks); + return NULL; } -MA_API ma_result ma_device_job_thread_post(ma_device_job_thread* pJobThread, const ma_job* pJob) + +MA_API ma_context_config ma_context_config_init(void) { - if (pJobThread == NULL || pJob == NULL) { - return MA_INVALID_ARGS; - } + ma_context_config config; + MA_ZERO_OBJECT(&config); - return ma_job_queue_post(&pJobThread->jobQueue, pJob); + return config; } -MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_job* pJob) +static const void* ma_context_config_find_backend_config(const ma_context_config* pConfig, const ma_device_backend_config* pBackendConfigs, size_t backendConfigCount, ma_device_backend_vtable* pVTable) { - if (pJob == NULL) { - return MA_INVALID_ARGS; - } + const void* pBackendConfig; - MA_ZERO_OBJECT(pJob); - - if (pJobThread == NULL) { - return MA_INVALID_ARGS; + if (pVTable == NULL || pConfig == NULL) { + return NULL; } - return ma_job_queue_next(&pJobThread->jobQueue, pJob); -} - - -MA_API ma_bool32 ma_device_id_equal(const ma_device_id* pA, const ma_device_id* pB) -{ - size_t i; - - if (pA == NULL || pB == NULL) { - return MA_FALSE; + pBackendConfig = ma_find_device_backend_config(pBackendConfigs, backendConfigCount, pVTable); + if (pBackendConfig != NULL) { + return pBackendConfig; } - for (i = 0; i < sizeof(ma_device_id); i += 1) { - if (((const char*)pA)[i] != ((const char*)pB)[i]) { - return MA_FALSE; + /* Getting here means we couldn't find a config pointer. It might have been set to null for stock backend. */ + if (pVTable != NULL) { + if (pVTable == ma_device_backend_wasapi) { + return &pConfig->wasapi; + } + if (pVTable == ma_device_backend_dsound) { + return &pConfig->dsound; + } + if (pVTable == ma_device_backend_winmm) { + return &pConfig->winmm; + } + if (pVTable == ma_device_backend_coreaudio) { + return &pConfig->coreaudio; + } + if (pVTable == ma_device_backend_pipewire) { + return &pConfig->pipewire; + } + if (pVTable == ma_device_backend_pulseaudio) { + return &pConfig->pulseaudio; + } + if (pVTable == ma_device_backend_jack) { + return &pConfig->jack; + } + if (pVTable == ma_device_backend_alsa) { + return &pConfig->alsa; + } + if (pVTable == ma_device_backend_sndio) { + return &pConfig->sndio; + } + if (pVTable == ma_device_backend_audio4) { + return &pConfig->audio4; + } + if (pVTable == ma_device_backend_oss) { + return &pConfig->oss; + } + if (pVTable == ma_device_backend_aaudio) { + return &pConfig->aaudio; + } + if (pVTable == ma_device_backend_opensl) { + return &pConfig->opensl; + } + if (pVTable == ma_device_backend_webaudio) { + return &pConfig->webaudio; + } + if (pVTable == ma_device_backend_dreamcast) { + return &pConfig->dreamcast; + } + if (pVTable == ma_device_backend_xaudio) { + return &pConfig->xaudio; + } + if (pVTable == ma_device_backend_vita) { + return &pConfig->vita; + } + if (pVTable == ma_device_backend_null) { + return &pConfig->null_backend; } } - return MA_TRUE; + return NULL; } -MA_API ma_context_config ma_context_config_init(void) +MA_API ma_uint32 ma_get_stock_device_backends(ma_device_backend_config* pBackends, size_t backendsCap) { - ma_context_config config; - MA_ZERO_OBJECT(&config); + ma_uint32 count = 0; + + /* This must be in priority order. */ + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_wasapi, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_dsound, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_winmm, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_coreaudio, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_pipewire, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_pulseaudio, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_jack, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_alsa, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_sndio, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_audio4, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_oss, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_aaudio, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_opensl, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_webaudio, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_dreamcast, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_xaudio, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_vita, NULL); } + if (backendsCap > count) { pBackends[count++] = ma_device_backend_config_init(ma_device_backend_null, NULL); } + + return count; +} - return config; -} -MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pConfig, ma_context* pContext) +MA_API ma_result ma_context_init(const ma_device_backend_config* pBackends, ma_uint32 backendCount, const ma_context_config* pConfig, ma_context* pContext) { ma_result result; ma_context_config defaultConfig; - ma_backend defaultBackends[ma_backend_null+1]; ma_uint32 iBackend; - ma_backend* pBackendsToIterate; - ma_uint32 backendsToIterateCount; + ma_device_backend_config pStockBackends[MA_MAX_STOCK_DEVICE_BACKENDS]; if (pContext == NULL) { return MA_INVALID_ARGS; @@ -43196,10 +50655,7 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC } /* Allocation callbacks need to come first because they'll be passed around to other areas. */ - result = ma_allocation_callbacks_init_copy(&pContext->allocationCallbacks, &pConfig->allocationCallbacks); - if (result != MA_SUCCESS) { - return result; - } + pContext->allocationCallbacks = ma_allocation_callbacks_init_copy(&pConfig->allocationCallbacks); /* Get a lot set up first so we can start logging ASAP. */ if (pConfig->pLog != NULL) { @@ -43223,169 +50679,63 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC return result; } - for (iBackend = 0; iBackend <= ma_backend_null; ++iBackend) { - defaultBackends[iBackend] = (ma_backend)iBackend; + /* If NULL was passed in for the backend list we'll want to iterate over our default list. */ + if (pBackends == NULL) { + pBackends = pStockBackends; + backendCount = ma_get_stock_device_backends(pStockBackends, ma_countof(pStockBackends)); } - pBackendsToIterate = (ma_backend*)backends; - backendsToIterateCount = backendCount; - if (pBackendsToIterate == NULL) { - pBackendsToIterate = (ma_backend*)defaultBackends; - backendsToIterateCount = ma_countof(defaultBackends); - } + for (iBackend = 0; iBackend < backendCount; iBackend += 1) { + ma_device_backend_config backend = pBackends[iBackend]; + ma_device_backend_info backendInfo; /* For logging the name. */ + void* pContextState = NULL; - MA_ASSERT(pBackendsToIterate != NULL); + /* + If the vtable is NULL just skip it. This is *not* an error - it's possible for a vtable to be null when + a backend has been disabled. + */ + if (backend.pVTable == NULL) { + continue; + } - for (iBackend = 0; iBackend < backendsToIterateCount; iBackend += 1) { - ma_backend backend = pBackendsToIterate[iBackend]; + /* Grab the info we have any. We're going to make this callback mandatory. */ + if (backend.pVTable->onBackendInfo == NULL) { + continue; /* No backend info callback. We need this for logging. */ + } - /* Make sure all callbacks are reset so we don't accidentally drag in any from previously failed initialization attempts. */ - MA_ZERO_OBJECT(&pContext->callbacks); + backend.pVTable->onBackendInfo(&backendInfo); - /* These backends are using the new callback system. */ - switch (backend) { - #ifdef MA_HAS_WASAPI - case ma_backend_wasapi: - { - pContext->callbacks.onContextInit = ma_context_init__wasapi; - } break; - #endif - #ifdef MA_HAS_DSOUND - case ma_backend_dsound: - { - pContext->callbacks.onContextInit = ma_context_init__dsound; - } break; - #endif - #ifdef MA_HAS_WINMM - case ma_backend_winmm: - { - pContext->callbacks.onContextInit = ma_context_init__winmm; - } break; - #endif - #ifdef MA_HAS_COREAUDIO - case ma_backend_coreaudio: - { - pContext->callbacks.onContextInit = ma_context_init__coreaudio; - } break; - #endif - #ifdef MA_HAS_SNDIO - case ma_backend_sndio: - { - pContext->callbacks.onContextInit = ma_context_init__sndio; - } break; - #endif - #ifdef MA_HAS_AUDIO4 - case ma_backend_audio4: - { - pContext->callbacks.onContextInit = ma_context_init__audio4; - } break; - #endif - #ifdef MA_HAS_OSS - case ma_backend_oss: - { - pContext->callbacks.onContextInit = ma_context_init__oss; - } break; - #endif - #ifdef MA_HAS_PULSEAUDIO - case ma_backend_pulseaudio: - { - pContext->callbacks.onContextInit = ma_context_init__pulse; - } break; - #endif - #ifdef MA_HAS_ALSA - case ma_backend_alsa: - { - pContext->callbacks.onContextInit = ma_context_init__alsa; - } break; - #endif - #ifdef MA_HAS_JACK - case ma_backend_jack: - { - pContext->callbacks.onContextInit = ma_context_init__jack; - } break; - #endif - #ifdef MA_HAS_AAUDIO - case ma_backend_aaudio: - { - if (ma_is_backend_enabled(backend)) { - pContext->callbacks.onContextInit = ma_context_init__aaudio; - } - } break; - #endif - #ifdef MA_HAS_OPENSL - case ma_backend_opensl: - { - if (ma_is_backend_enabled(backend)) { - pContext->callbacks.onContextInit = ma_context_init__opensl; - } - } break; - #endif - #ifdef MA_HAS_WEBAUDIO - case ma_backend_webaudio: - { - pContext->callbacks.onContextInit = ma_context_init__webaudio; - } break; - #endif - #ifdef MA_HAS_CUSTOM - case ma_backend_custom: - { - /* Slightly different logic for custom backends. Custom backends can optionally set all of their callbacks in the config. */ - pContext->callbacks = pConfig->custom; - } break; - #endif - #ifdef MA_HAS_NULL - case ma_backend_null: - { - pContext->callbacks.onContextInit = ma_context_init__null; - } break; - #endif + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Attempting to initialize %s backend...", backendInfo.pName); - default: break; + if (backend.pVTable->onContextInit == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Failed to initialize %s backend. No onContextInit callback.", backendInfo.pName); + continue; } - if (pContext->callbacks.onContextInit != NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Attempting to initialize %s backend...\n", ma_get_backend_name(backend)); - result = pContext->callbacks.onContextInit(pContext, pConfig, &pContext->callbacks); - } else { - /* Getting here means the onContextInit callback is not set which means the backend is not enabled. Special case for the custom backend. */ - if (backend != ma_backend_custom) { - result = MA_BACKEND_NOT_ENABLED; - } else { - #if !defined(MA_HAS_CUSTOM) - result = MA_BACKEND_NOT_ENABLED; - #else - result = MA_NO_BACKEND; - #endif - } + result = backend.pVTable->onContextInit(pContext, ma_context_config_find_backend_config(pConfig, pBackends, backendCount, backend.pVTable), &pContextState); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Failed to initialize %s backend. %s.", backendInfo.pName, ma_result_description(result)); + continue; } - /* If this iteration was successful, return. */ - if (result == MA_SUCCESS) { - result = ma_mutex_init(&pContext->deviceEnumLock); - if (result != MA_SUCCESS) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.\n"); - } - - result = ma_mutex_init(&pContext->deviceInfoLock); - if (result != MA_SUCCESS) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.\n"); - } + /* Getting here means initialization was successful. */ + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Successfully initialized %s backend.", backendInfo.pName); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "System Architecture:\n"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " NEON: %s\n", ma_has_neon() ? "YES" : "NO"); + pContext->pVTable = backend.pVTable; + ma_atomic_store_explicit_ptr((volatile void**)&pContext->pBackendState, pContextState, ma_atomic_memory_order_relaxed); - pContext->backend = backend; - return result; - } else { - if (result == MA_BACKEND_NOT_ENABLED) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "%s backend is disabled.\n", ma_get_backend_name(backend)); - } else { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "Failed to initialize %s backend.\n", ma_get_backend_name(backend)); - } + result = ma_mutex_init(&pContext->deviceEnumLock); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe."); } + + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "System Architecture:"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " Endian: %s", ma_is_little_endian() ? "LE" : "BE"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " SSE2: %s", ma_has_sse2() ? "YES" : "NO"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " AVX2: %s", ma_has_avx2() ? "YES" : "NO"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " NEON: %s", ma_has_neon() ? "YES" : "NO"); + + return result; } /* If we get here it means an error occurred. */ @@ -43399,12 +50749,12 @@ MA_API ma_result ma_context_uninit(ma_context* pContext) return MA_INVALID_ARGS; } - if (pContext->callbacks.onContextUninit != NULL) { - pContext->callbacks.onContextUninit(pContext); + if (pContext->pVTable->onContextUninit != NULL) { + pContext->pVTable->onContextUninit(pContext); + pContext->pBackendState = NULL; } ma_mutex_uninit(&pContext->deviceEnumLock); - ma_mutex_uninit(&pContext->deviceInfoLock); ma_free(pContext->pDeviceInfos, &pContext->allocationCallbacks); ma_context_uninit_backend_apis(pContext); @@ -43430,6 +50780,50 @@ MA_API ma_log* ma_context_get_log(ma_context* pContext) return pContext->pLog; } +MA_API const ma_allocation_callbacks* ma_context_get_allocation_callbacks(ma_context* pContext) +{ + if (pContext == NULL) { + return NULL; + } + + return &pContext->allocationCallbacks; +} + +MA_API ma_thread_priority ma_context_get_thread_priority(ma_context* pContext) +{ + if (pContext == NULL) { + return ma_thread_priority_default; + } + + return pContext->threadPriority; +} + +MA_API void* ma_context_get_backend_state(ma_context* pContext) +{ + if (pContext == NULL) { + return NULL; + } + + return ma_atomic_load_explicit_ptr((volatile void**)&pContext->pBackendState, ma_atomic_memory_order_relaxed); +} + + + +MA_API void ma_context_get_backend_info(ma_context* pContext, ma_device_backend_info* pBackendInfo) +{ + if (pBackendInfo == NULL) { + return; + } + + MA_ZERO_OBJECT(pBackendInfo); + + if (pContext == NULL) { + return; + } + + ma_get_device_backend_info(pContext->pVTable, pBackendInfo); +} + MA_API ma_result ma_context_enumerate_devices(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { @@ -43439,13 +50833,13 @@ MA_API ma_result ma_context_enumerate_devices(ma_context* pContext, ma_enum_devi return MA_INVALID_ARGS; } - if (pContext->callbacks.onContextEnumerateDevices == NULL) { + if (pContext->pVTable->onContextEnumerateDevices == NULL) { return MA_INVALID_OPERATION; } ma_mutex_lock(&pContext->deviceEnumLock); { - result = pContext->callbacks.onContextEnumerateDevices(pContext, callback, pUserData); + result = pContext->pVTable->onContextEnumerateDevices(pContext, callback, pUserData); } ma_mutex_unlock(&pContext->deviceEnumLock); @@ -43453,12 +50847,13 @@ MA_API ma_result ma_context_enumerate_devices(ma_context* pContext, ma_enum_devi } -static ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) +static ma_device_enumeration_result ma_context_get_devices__enum_callback(ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) { /* We need to insert the device info into our main internal buffer. Where it goes depends on the device type. If it's a capture device it's just appended to the end. If it's a playback device it's inserted just before the first capture device. */ + ma_context* pContext = (ma_context*)pUserData; /* First make sure we have room. Since the number of devices we add to the list is usually relatively small I've decided to use a @@ -43471,7 +50866,7 @@ static ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_ ma_uint32 newCapacity = pContext->deviceInfoCapacity + bufferExpansionCount; ma_device_info* pNewInfos = (ma_device_info*)ma_realloc(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, &pContext->allocationCallbacks); if (pNewInfos == NULL) { - return MA_FALSE; /* Out of memory. */ + return MA_DEVICE_ENUMERATION_ABORT; /* Out of memory. */ } pContext->pDeviceInfos = pNewInfos; @@ -43498,7 +50893,7 @@ static ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_ } (void)pUserData; - return MA_TRUE; + return MA_DEVICE_ENUMERATION_CONTINUE; } MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** ppPlaybackDeviceInfos, ma_uint32* pPlaybackDeviceCount, ma_device_info** ppCaptureDeviceInfos, ma_uint32* pCaptureDeviceCount) @@ -43515,7 +50910,7 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p return MA_INVALID_ARGS; } - if (pContext->callbacks.onContextEnumerateDevices == NULL) { + if (pContext->pVTable->onContextEnumerateDevices == NULL) { return MA_INVALID_OPERATION; } @@ -43527,7 +50922,7 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p pContext->captureDeviceInfoCount = 0; /* Now enumerate over available devices. */ - result = pContext->callbacks.onContextEnumerateDevices(pContext, ma_context_get_devices__enum_callback, NULL); + result = pContext->pVTable->onContextEnumerateDevices(pContext, ma_context_get_devices__enum_callback, pContext); if (result == MA_SUCCESS) { /* Playback devices. */ if (ppPlaybackDeviceInfos != NULL) { @@ -43556,44 +50951,76 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p return result; } + +typedef struct ma_context_get_device_info_enum_callback_data +{ + ma_device_type deviceType; + const ma_device_id* pDeviceID; + ma_device_info* pDeviceInfo; + ma_bool32 foundDevice; +} ma_context_get_device_info_enum_callback_data; + +static ma_device_enumeration_result ma_context_get_device_info_enum_callback(ma_device_type deviceType, const ma_device_info* pDeviceInfo, void* pUserData) +{ + ma_context_get_device_info_enum_callback_data* pData = (ma_context_get_device_info_enum_callback_data*)pUserData; + + if (pData->deviceType == deviceType) { + if (pData->pDeviceID == NULL) { + if (pDeviceInfo->isDefault) { + /* Found a match. */ + *pData->pDeviceInfo = *pDeviceInfo; + pData->foundDevice = MA_TRUE; + } else { + /* Not a default device. Keep looking. */ + } + } else { + if (ma_device_id_equal(&pDeviceInfo->id, pData->pDeviceID)) { + /* Found a match. */ + *pData->pDeviceInfo = *pDeviceInfo; + pData->foundDevice = MA_TRUE; + } else { + /* IDs do not match. */ + } + } + } else { + /* Device types don't match. */ + } + + /* Abort enumeration once we've found our info. */ + if (pData->foundDevice) { + return MA_DEVICE_ENUMERATION_ABORT; + } + + return MA_DEVICE_ENUMERATION_CONTINUE; +} + MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_result result; - ma_device_info deviceInfo; + ma_context_get_device_info_enum_callback_data data; /* NOTE: Do not clear pDeviceInfo on entry. The reason is the pDeviceID may actually point to pDeviceInfo->id which will break things. */ if (pContext == NULL || pDeviceInfo == NULL) { return MA_INVALID_ARGS; } - MA_ZERO_OBJECT(&deviceInfo); - - /* Help the backend out by copying over the device ID if we have one. */ - if (pDeviceID != NULL) { - MA_COPY_MEMORY(&deviceInfo.id, pDeviceID, sizeof(*pDeviceID)); - } - - if (pContext->callbacks.onContextGetDeviceInfo == NULL) { - return MA_INVALID_OPERATION; - } + /* Device info retrieval can be implemented via device enumeration. */ + MA_ZERO_OBJECT(&data); + data.deviceType = deviceType; + data.pDeviceID = pDeviceID; + data.pDeviceInfo = pDeviceInfo; - ma_mutex_lock(&pContext->deviceInfoLock); - { - result = pContext->callbacks.onContextGetDeviceInfo(pContext, deviceType, pDeviceID, &deviceInfo); + /* We just enumerate over devices until we find one matching our ID. */ + result = ma_context_enumerate_devices(pContext, ma_context_get_device_info_enum_callback, &data); + if (result != MA_SUCCESS) { + return result; } - ma_mutex_unlock(&pContext->deviceInfoLock); - *pDeviceInfo = deviceInfo; - return result; -} - -MA_API ma_bool32 ma_context_is_loopback_supported(ma_context* pContext) -{ - if (pContext == NULL) { - return MA_FALSE; + if (data.foundDevice == MA_FALSE) { + return MA_NO_DEVICE; } - return ma_is_loopback_supported(pContext->backend); + return MA_SUCCESS; } @@ -43607,11 +51034,91 @@ MA_API ma_device_config ma_device_config_init(ma_device_type deviceType) return config; } +static const void* ma_device_config_find_backend_config(const ma_device_config* pConfig, ma_device_backend_vtable* pVTable) +{ + const void* pBackendConfig; + + if (pVTable == NULL || pConfig == NULL) { + return NULL; + } + + pBackendConfig = ma_find_device_backend_config(pConfig->pBackendConfigs, pConfig->backendConfigCount, pVTable); + if (pBackendConfig != NULL) { + return pBackendConfig; + } + + /* Getting here means there was no config pointer. Check stock backends. */ + if (pVTable != NULL) { + if (pVTable == ma_device_backend_wasapi) { + return &pConfig->wasapi; + } + if (pVTable == ma_device_backend_dsound) { + return &pConfig->dsound; + } + if (pVTable == ma_device_backend_winmm) { + return &pConfig->winmm; + } + if (pVTable == ma_device_backend_coreaudio) { + return &pConfig->coreaudio; + } + if (pVTable == ma_device_backend_pipewire) { + return &pConfig->pipewire; + } + if (pVTable == ma_device_backend_pulseaudio) { + return &pConfig->pulseaudio; + } + if (pVTable == ma_device_backend_jack) { + return &pConfig->jack; + } + if (pVTable == ma_device_backend_alsa) { + return &pConfig->alsa; + } + if (pVTable == ma_device_backend_sndio) { + return &pConfig->sndio; + } + if (pVTable == ma_device_backend_audio4) { + return &pConfig->audio4; + } + if (pVTable == ma_device_backend_oss) { + return &pConfig->oss; + } + if (pVTable == ma_device_backend_aaudio) { + return &pConfig->aaudio; + } + if (pVTable == ma_device_backend_opensl) { + return &pConfig->opensl; + } + if (pVTable == ma_device_backend_webaudio) { + return &pConfig->webaudio; + } + if (pVTable == ma_device_backend_dreamcast) { + return &pConfig->dreamcast; + } + if (pVTable == ma_device_backend_xaudio) { + return &pConfig->xaudio; + } + if (pVTable == ma_device_backend_vita) { + return &pConfig->vita; + } + if (pVTable == ma_device_backend_null) { + return &pConfig->null_backend; + } + } + + return NULL; +} + + MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) { ma_result result; + ma_device_backend_info backendInfo; + ma_device_descriptor publicDescriptorPlayback; + ma_device_descriptor publicDescriptorCapture; ma_device_descriptor descriptorPlayback; ma_device_descriptor descriptorCapture; + ma_threading_mode threadingMode; + ma_device_op_params initParams; /* The context can be null, in which case we self-manage it. */ if (pContext == NULL) { @@ -43628,8 +51135,22 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC return MA_INVALID_ARGS; } + /* We'll need the backend info to determine whether or not an audio thread can be created. */ + ma_context_get_backend_info(pContext, &backendInfo); + + threadingMode = pConfig->threadingMode; + + /* Force single-threaded mode if threading has been disabled at compile time. */ + #ifdef MA_NO_THREADING + { + if (!backendInfo.isMultiThreadedModeAllowedWhenThreadingDisabled) { + threadingMode = MA_THREADING_MODE_SINGLE_THREADED; + } + } + #endif + /* Check that we have our callbacks defined. */ - if (pContext->callbacks.onDeviceInit == NULL) { + if (pContext->pVTable->onDeviceInit == NULL) { return MA_INVALID_OPERATION; } @@ -43654,13 +51175,12 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } } - pDevice->pContext = pContext; - - /* Set the user data and log callback ASAP to ensure it is available for the entire initialization process. */ + pDevice->pContext = pContext; + pDevice->threadingMode = threadingMode; + pDevice->hasAudioThread = !(threadingMode == MA_THREADING_MODE_SINGLE_THREADED || backendInfo.noAudioThread); pDevice->pUserData = pConfig->pUserData; pDevice->onData = pConfig->dataCallback; pDevice->onNotification = pConfig->notificationCallback; - pDevice->onStop = pConfig->stopCallback; if (pConfig->playback.pDeviceID != NULL) { MA_COPY_MEMORY(&pDevice->playback.id, pConfig->playback.pDeviceID, sizeof(pDevice->playback.id)); @@ -43692,50 +51212,15 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC pDevice->capture.shareMode = pConfig->capture.shareMode; pDevice->capture.format = pConfig->capture.format; pDevice->capture.channels = pConfig->capture.channels; - ma_channel_map_copy_or_default(pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pConfig->capture.pChannelMap, pConfig->capture.channels); pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; pDevice->capture.calculateLFEFromSpatialChannels = pConfig->capture.calculateLFEFromSpatialChannels; pDevice->playback.shareMode = pConfig->playback.shareMode; pDevice->playback.format = pConfig->playback.format; pDevice->playback.channels = pConfig->playback.channels; - ma_channel_map_copy_or_default(pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pConfig->playback.pChannelMap, pConfig->playback.channels); pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; pDevice->playback.calculateLFEFromSpatialChannels = pConfig->playback.calculateLFEFromSpatialChannels; - result = ma_mutex_init(&pDevice->startStopLock); - if (result != MA_SUCCESS) { - return result; - } - - /* - When the device is started, the worker thread is the one that does the actual startup of the backend device. We - use a semaphore to wait for the background thread to finish the work. The same applies for stopping the device. - - Each of these semaphores is released internally by the worker thread when the work is completed. The start - semaphore is also used to wake up the worker thread. - */ - result = ma_event_init(&pDevice->wakeupEvent); - if (result != MA_SUCCESS) { - ma_mutex_uninit(&pDevice->startStopLock); - return result; - } - - result = ma_event_init(&pDevice->startEvent); - if (result != MA_SUCCESS) { - ma_event_uninit(&pDevice->wakeupEvent); - ma_mutex_uninit(&pDevice->startStopLock); - return result; - } - - result = ma_event_init(&pDevice->stopEvent); - if (result != MA_SUCCESS) { - ma_event_uninit(&pDevice->startEvent); - ma_event_uninit(&pDevice->wakeupEvent); - ma_mutex_uninit(&pDevice->startStopLock); - return result; - } - MA_ZERO_OBJECT(&descriptorPlayback); descriptorPlayback.pDeviceID = pConfig->playback.pDeviceID; @@ -43751,6 +51236,9 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC if (descriptorPlayback.periodCount == 0) { descriptorPlayback.periodCount = MA_DEFAULT_PERIODS; } + if (descriptorPlayback.periodCount < 2 && pConfig->deviceType == ma_device_type_duplex) { + descriptorPlayback.periodCount = 2; + } MA_ZERO_OBJECT(&descriptorCapture); @@ -43767,105 +51255,98 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC if (descriptorCapture.periodCount == 0) { descriptorCapture.periodCount = MA_DEFAULT_PERIODS; } + if (descriptorCapture.periodCount < 2 && pConfig->deviceType == ma_device_type_duplex) { + descriptorCapture.periodCount = 2; + } - - result = pContext->callbacks.onDeviceInit(pDevice, pConfig, &descriptorPlayback, &descriptorCapture); - if (result != MA_SUCCESS) { - ma_event_uninit(&pDevice->startEvent); - ma_event_uninit(&pDevice->wakeupEvent); - ma_mutex_uninit(&pDevice->startStopLock); - return result; + /* Starting and stopping must be mutually exclusive. We just use a mutex for this. */ + #ifndef MA_NO_THREADING + { + result = ma_mutex_init(&pDevice->startStopLock); + if (result != MA_SUCCESS) { + return result; + } } + #endif -#if 0 /* - On output the descriptors will contain the *actual* data format of the device. We need this to know how to convert the data between - the requested format and the internal format. + The backend will change the descriptor from the public format to its internal format. We need to keep track of the + public descriptors in order to know how to initialize the data converter later on. */ - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { - if (!ma_device_descriptor_is_valid(&descriptorCapture)) { - ma_device_uninit(pDevice); - return MA_INVALID_ARGS; - } + publicDescriptorPlayback = descriptorPlayback; + publicDescriptorCapture = descriptorCapture; - pDevice->capture.internalFormat = descriptorCapture.format; - pDevice->capture.internalChannels = descriptorCapture.channels; - pDevice->capture.internalSampleRate = descriptorCapture.sampleRate; - ma_channel_map_copy(pDevice->capture.internalChannelMap, descriptorCapture.channelMap, descriptorCapture.channels); - pDevice->capture.internalPeriodSizeInFrames = descriptorCapture.periodSizeInFrames; - pDevice->capture.internalPeriods = descriptorCapture.periodCount; + /* We are now ready to initialize the backend. */ + initParams.init.pDeviceBackendConfig = ma_device_config_find_backend_config(pConfig, pContext->pVTable); + initParams.init.pDescriptorPlayback = &descriptorPlayback; + initParams.init.pDescriptorCapture = &descriptorCapture; - if (pDevice->capture.internalPeriodSizeInFrames == 0) { - pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorCapture.periodSizeInMilliseconds, descriptorCapture.sampleRate); + /* + We'll want to set up the worker thread fairly early on in the process because that is where the backend will + be initialized. It won't be doing anything at first because no operation will be put onto the queue. + */ + #ifndef MA_NO_THREADING + if (pDevice->hasAudioThread) { + /* We need an operation queue before starting the audio thread. */ + result = ma_device_op_queue_init(&pDevice->opQueue); + if (result != MA_SUCCESS) { + ma_mutex_uninit(&pDevice->startStopLock); + return result; } - } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - if (!ma_device_descriptor_is_valid(&descriptorPlayback)) { - ma_device_uninit(pDevice); - return MA_INVALID_ARGS; + /* + We'll need a completion event for init/uninit/start/stop to wait on. All of these are mutually exclusive + with each other so it should be right to use just the one completion event object. + */ + result = ma_device_op_completion_event_init(&pDevice->opCompletionEvent); + if (result != MA_SUCCESS) { + ma_device_op_queue_uninit(&pDevice->opQueue); + ma_mutex_uninit(&pDevice->startStopLock); + return result; } - pDevice->playback.internalFormat = descriptorPlayback.format; - pDevice->playback.internalChannels = descriptorPlayback.channels; - pDevice->playback.internalSampleRate = descriptorPlayback.sampleRate; - ma_channel_map_copy(pDevice->playback.internalChannelMap, descriptorPlayback.channelMap, descriptorPlayback.channels); - pDevice->playback.internalPeriodSizeInFrames = descriptorPlayback.periodSizeInFrames; - pDevice->playback.internalPeriods = descriptorPlayback.periodCount; - - if (pDevice->playback.internalPeriodSizeInFrames == 0) { - pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorPlayback.periodSizeInMilliseconds, descriptorPlayback.sampleRate); + /* Seed the initial initialization routine. */ + result = ma_device_op_queue_push(&pDevice->opQueue, MA_DEVICE_OP_INIT, &initParams, &pDevice->opCompletionEvent); + if (result != MA_SUCCESS) { + ma_device_op_completion_event_uninit(&pDevice->opCompletionEvent); + ma_device_op_queue_uninit(&pDevice->opQueue); + ma_mutex_uninit(&pDevice->startStopLock); + return result; } - } - - - /* - The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info(pDevice, deviceType)` instead. - For loopback devices, we need to retrieve the name of the playback device. - */ - { - ma_device_info deviceInfo; - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { - result = ma_device_get_info(pDevice, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, &deviceInfo); - if (result == MA_SUCCESS) { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); - } else { - /* We failed to retrieve the device info. Fall back to a default name. */ - if (descriptorCapture.pDeviceID == NULL) { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "Capture Device", (size_t)-1); - } - } + result = ma_thread_create(&pDevice->audioThread, pContext->threadPriority, pContext->threadStackSize, ma_audio_thread, pDevice, &pContext->allocationCallbacks); + if(result != MA_SUCCESS) { + ma_device_op_completion_event_uninit(&pDevice->opCompletionEvent); + ma_device_op_queue_uninit(&pDevice->opQueue); + ma_mutex_uninit(&pDevice->startStopLock); + return result; /* Failed to create the audio thread. */ } - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_get_info(pDevice, ma_device_type_playback, &deviceInfo); - if (result == MA_SUCCESS) { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); - } else { - /* We failed to retrieve the device info. Fall back to a default name. */ - if (descriptorPlayback.pDeviceID == NULL) { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - } else { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "Playback Device", (size_t)-1); - } - } + /* Now we just need to wait for the operation to complete. */ + result = ma_device_op_completion_event_result(&pDevice->opCompletionEvent); /* <-- This will wait for the operation to complete. */ + if (result != MA_SUCCESS) { + ma_thread_wait(&pDevice->audioThread); /* The audio thread will terminate if the init operation failed. We need only wait for the thread - no need to post an event. */ + ma_device_op_completion_event_uninit(&pDevice->opCompletionEvent); + ma_device_op_queue_uninit(&pDevice->opQueue); + ma_mutex_uninit(&pDevice->startStopLock); + return result; + } + } else + #endif + { + /* Getting here means we're running in single-threaded mode or we weren't allowed to create an audio thread. We want to run the init operation immediately. */ + result = ma_device_op_do_init(pDevice, initParams, NULL); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize backend. */ } } - - ma_device__post_init_setup(pDevice, pConfig->deviceType); -#endif - - result = ma_device_post_init(pDevice, pConfig->deviceType, &descriptorPlayback, &descriptorCapture); + result = ma_device_post_init(pDevice, pConfig->deviceType, &publicDescriptorPlayback, &publicDescriptorCapture, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { ma_device_uninit(pDevice); return result; } - /* If we're using fixed sized callbacks we'll need to make use of an intermediary buffer. Needs to be done after post_init_setup() because we'll need access to the sample rate. @@ -43929,112 +51410,111 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } - /* Some backends don't require the worker thread. */ - if (!ma_context_is_backend_asynchronous(pContext)) { - /* The worker thread. */ - result = ma_thread_create(&pDevice->thread, pContext->threadPriority, pContext->threadStackSize, ma_worker_thread, pDevice, &pContext->allocationCallbacks); - if (result != MA_SUCCESS) { - ma_device_uninit(pDevice); - return result; - } - - /* Wait for the worker thread to put the device into its stopped state for real. */ - ma_event_wait(&pDevice->stopEvent); - MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); - } else { - /* - If the backend is asynchronous and the device is duplex, we'll need an intermediary ring buffer. Note that this needs to be done - after ma_device__post_init_setup(). - */ - if (ma_context_is_backend_asynchronous(pContext)) { - if (pConfig->deviceType == ma_device_type_duplex) { - result = ma_duplex_rb_init(pDevice->capture.format, pDevice->capture.channels, pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); - if (result != MA_SUCCESS) { - ma_device_uninit(pDevice); - return result; - } - } + /* + If the device is duplex we may need an intermediary ring buffer. Note that this needs to be done + after ma_device_post_init(). + */ + if (pConfig->deviceType == ma_device_type_duplex) { + /* TODO: Allocate one block of memory big enough for the ma_duplex_rb struct *and* the internal buffer. Then pass in the pre-allocated buffer to the ring buffer init routine. */ + + pDevice->pDuplexRB = (ma_duplex_rb*)ma_aligned_malloc(sizeof(ma_duplex_rb), MA_CACHE_LINE_SIZE, ma_device_get_allocation_callbacks(pDevice)); + if (pDevice->pDuplexRB == NULL) { + ma_device_uninit(pDevice); + return MA_OUT_OF_MEMORY; } - ma_device__set_state(pDevice, ma_device_state_stopped); + result = ma_duplex_rb_init(pDevice->capture.format, pDevice->capture.channels, pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames, ma_device_get_allocation_callbacks(pDevice), pDevice->pDuplexRB); + if (result != MA_SUCCESS) { + ma_device_uninit(pDevice); + return result; + } } + /* Log device information. */ { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); + ma_device_backend_info backendInfo; + ma_context_get_backend_info(pDevice->pContext, &backendInfo); + + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]", backendInfo.pName); if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; ma_device_get_name(pDevice, ma_device_type_capture, name, sizeof(name), NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Capture"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->capture.internalChannels, pDevice->capture.channels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->capture.internalSampleRate, pDevice->sampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->capture.converter.hasPreFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->capture.converter.hasPostFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->capture.converter.hasChannelConverter ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->capture.converter.hasResampler ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->capture.converter.isPassthrough ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)", name, "Capture"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d", pDevice->capture.internalChannels, pDevice->capture.channels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d", pDevice->capture.internalSampleRate, pDevice->sampleRate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)", pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s", pDevice->capture.converter.hasPreFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s", pDevice->capture.converter.hasPostFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s", pDevice->capture.converter.hasChannelConverter ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s", pDevice->capture.converter.hasResampler ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s", pDevice->capture.converter.isPassthrough ? "YES" : "NO"); { + ma_channel internalChannelMap[MA_MAX_CHANNELS]; + ma_channel publicChannelMap[MA_MAX_CHANNELS]; char channelMapStr[1024]; - ma_channel_map_to_string(pDevice->capture.internalChannelMap, pDevice->capture.internalChannels, channelMapStr, sizeof(channelMapStr)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map In: {%s}\n", channelMapStr); - ma_channel_map_to_string(pDevice->capture.channelMap, pDevice->capture.channels, channelMapStr, sizeof(channelMapStr)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map Out: {%s}\n", channelMapStr); + ma_device_get_internal_channel_map(pDevice, ma_device_type_capture, internalChannelMap, ma_countof(internalChannelMap)); + ma_channel_map_to_string(internalChannelMap, ma_device_get_internal_channels(pDevice, ma_device_type_capture), channelMapStr, sizeof(channelMapStr)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map In: {%s}", channelMapStr); + + ma_device_get_channel_map(pDevice, ma_device_type_capture, publicChannelMap, ma_countof(publicChannelMap)); + ma_channel_map_to_string(publicChannelMap, ma_device_get_channels(pDevice, ma_device_type_capture), channelMapStr, sizeof(channelMapStr)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map Out: {%s}", channelMapStr); } } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; ma_device_get_name(pDevice, ma_device_type_playback, name, sizeof(name), NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Playback"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->playback.format), ma_get_format_name(pDevice->playback.internalFormat)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->playback.channels, pDevice->playback.internalChannels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->playback.internalSampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->playback.internalPeriodSizeInFrames, pDevice->playback.internalPeriods, (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->playback.converter.hasPreFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->playback.converter.hasPostFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->playback.converter.hasChannelConverter ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->playback.converter.hasResampler ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)", name, "Playback"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s", ma_get_format_name(pDevice->playback.format), ma_get_format_name(pDevice->playback.internalFormat)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d", pDevice->playback.channels, pDevice->playback.internalChannels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d", pDevice->sampleRate, pDevice->playback.internalSampleRate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)", pDevice->playback.internalPeriodSizeInFrames, pDevice->playback.internalPeriods, (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s", pDevice->playback.converter.hasPreFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s", pDevice->playback.converter.hasPostFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s", pDevice->playback.converter.hasChannelConverter ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s", pDevice->playback.converter.hasResampler ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); { + ma_channel publicChannelMap[MA_MAX_CHANNELS]; + ma_channel internalChannelMap[MA_MAX_CHANNELS]; char channelMapStr[1024]; - ma_channel_map_to_string(pDevice->playback.channelMap, pDevice->playback.channels, channelMapStr, sizeof(channelMapStr)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map In: {%s}\n", channelMapStr); - ma_channel_map_to_string(pDevice->playback.internalChannelMap, pDevice->playback.internalChannels, channelMapStr, sizeof(channelMapStr)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map Out: {%s}\n", channelMapStr); + ma_device_get_channel_map(pDevice, ma_device_type_playback, publicChannelMap, ma_countof(publicChannelMap)); + ma_channel_map_to_string(publicChannelMap, ma_device_get_channels(pDevice, ma_device_type_playback), channelMapStr, sizeof(channelMapStr)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map In: {%s}", channelMapStr); + + ma_device_get_internal_channel_map(pDevice, ma_device_type_playback, internalChannelMap, ma_countof(internalChannelMap)); + ma_channel_map_to_string(internalChannelMap, ma_device_get_internal_channels(pDevice, ma_device_type_playback), channelMapStr, sizeof(channelMapStr)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Map Out: {%s}", channelMapStr); } } } - MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); + MA_ASSERT(ma_device_get_status(pDevice) == ma_device_status_stopped); return MA_SUCCESS; } -MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pContextConfig, const ma_device_config* pConfig, ma_device* pDevice) +MA_API ma_result ma_device_init_ex(const ma_device_backend_config* pBackends, ma_uint32 backendCount, const ma_context_config* pContextConfig, const ma_device_config* pConfig, ma_device* pDevice) { ma_result result; ma_context* pContext; - ma_backend defaultBackends[ma_backend_null+1]; ma_uint32 iBackend; - ma_backend* pBackendsToIterate; - ma_uint32 backendsToIterateCount; ma_allocation_callbacks allocationCallbacks; + ma_device_backend_config pStockBackends[MA_MAX_STOCK_DEVICE_BACKENDS]; if (pConfig == NULL) { return MA_INVALID_ARGS; } if (pContextConfig != NULL) { - result = ma_allocation_callbacks_init_copy(&allocationCallbacks, &pContextConfig->allocationCallbacks); - if (result != MA_SUCCESS) { - return result; - } + allocationCallbacks = ma_allocation_callbacks_init_copy(&pContextConfig->allocationCallbacks); } else { allocationCallbacks = ma_allocation_callbacks_init_default(); } @@ -44044,48 +51524,44 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen return MA_OUT_OF_MEMORY; } - for (iBackend = 0; iBackend <= ma_backend_null; ++iBackend) { - defaultBackends[iBackend] = (ma_backend)iBackend; - } - - pBackendsToIterate = (ma_backend*)backends; - backendsToIterateCount = backendCount; - if (pBackendsToIterate == NULL) { - pBackendsToIterate = (ma_backend*)defaultBackends; - backendsToIterateCount = ma_countof(defaultBackends); + if (pBackends == NULL) { + pBackends = pStockBackends; + backendCount = ma_get_stock_device_backends(pStockBackends, ma_countof(pStockBackends)); } result = MA_NO_BACKEND; - for (iBackend = 0; iBackend < backendsToIterateCount; ++iBackend) { + for (iBackend = 0; iBackend < backendCount; iBackend += 1) { /* This is a hack for iOS. If the context config is null, there's a good chance the `ma_device_init(NULL, &deviceConfig, pDevice);` pattern is being used. In this case, set the session category based on the device type. */ - #if defined(MA_APPLE_MOBILE) - ma_context_config contextConfig; + #if defined(MA_APPLE_MOBILE) + { + ma_context_config contextConfig; - if (pContextConfig == NULL) { - contextConfig = ma_context_config_init(); - switch (pConfig->deviceType) { - case ma_device_type_duplex: { - contextConfig.coreaudio.sessionCategory = ma_ios_session_category_play_and_record; - } break; - case ma_device_type_capture: { - contextConfig.coreaudio.sessionCategory = ma_ios_session_category_record; - } break; - case ma_device_type_playback: - default: { - contextConfig.coreaudio.sessionCategory = ma_ios_session_category_playback; - } break; - } + if (pContextConfig == NULL) { + contextConfig = ma_context_config_init(); + switch (pConfig->deviceType) { + case ma_device_type_duplex: { + contextConfig.coreaudio.sessionCategory = ma_ios_session_category_play_and_record; + } break; + case ma_device_type_capture: { + contextConfig.coreaudio.sessionCategory = ma_ios_session_category_record; + } break; + case ma_device_type_playback: + default: { + contextConfig.coreaudio.sessionCategory = ma_ios_session_category_playback; + } break; + } - pContextConfig = &contextConfig; + pContextConfig = &contextConfig; + } } - #endif + #endif - result = ma_context_init(&pBackendsToIterate[iBackend], 1, pContextConfig, pContext); + result = ma_context_init(&pBackends[iBackend], 1, pContextConfig, pContext); if (result == MA_SUCCESS) { result = ma_device_init(pContext, pConfig, pDevice); if (result == MA_SUCCESS) { @@ -44107,7 +51583,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen MA_API void ma_device_uninit(ma_device* pDevice) { - if (!ma_device__is_initialized(pDevice)) { + if (ma_device_get_status(pDevice) == ma_device_status_uninitialized) { return; } @@ -44130,47 +51606,62 @@ MA_API void ma_device_uninit(ma_device* pDevice) #endif /* Putting the device into an uninitialized state will make the worker thread return. */ - ma_device__set_state(pDevice, ma_device_state_uninitialized); - - /* Wake up the worker thread and wait for it to properly terminate. */ - if (!ma_context_is_backend_asynchronous(pDevice->pContext)) { - ma_event_signal(&pDevice->wakeupEvent); - ma_thread_wait(&pDevice->thread); - } + ma_device_set_status(pDevice, ma_device_status_uninitialized); - if (pDevice->pContext->callbacks.onDeviceUninit != NULL) { - pDevice->pContext->callbacks.onDeviceUninit(pDevice); - } + /* + Post an uninit operation to the audio thread. This will kill the audio thread. There is no + need to wait for the operation to complete. We can just wait for the thread instead. + */ + #ifndef MA_NO_THREADING + if (pDevice->hasAudioThread) { + ma_device_op_queue_push(&pDevice->opQueue, MA_DEVICE_OP_UNINIT, NULL, NULL); + /* + Before waiting on the result, we need to make sure we try waking up the backend first in case it + needs a prod. + */ + if (pDevice->pContext->pVTable->onDeviceWakeup != NULL) { + pDevice->pContext->pVTable->onDeviceWakeup(pDevice); + } - ma_event_uninit(&pDevice->stopEvent); - ma_event_uninit(&pDevice->startEvent); - ma_event_uninit(&pDevice->wakeupEvent); - ma_mutex_uninit(&pDevice->startStopLock); + ma_thread_wait(&pDevice->audioThread); + } else + #endif + { + /* Not using threading. */ + ma_device_op_do_uninit(pDevice, NULL); + } - if (ma_context_is_backend_asynchronous(pDevice->pContext)) { - if (pDevice->type == ma_device_type_duplex) { - ma_duplex_rb_uninit(&pDevice->duplexRB); - } + if (pDevice->type == ma_device_type_duplex) { + ma_duplex_rb_uninit(pDevice->pDuplexRB); + ma_aligned_free(pDevice->pDuplexRB, ma_device_get_allocation_callbacks(pDevice)); } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + ma_data_converter_uninit(&pDevice->capture.converter, ma_device_get_allocation_callbacks(pDevice)); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + ma_data_converter_uninit(&pDevice->playback.converter, ma_device_get_allocation_callbacks(pDevice)); } if (pDevice->playback.pInputCache != NULL) { - ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->playback.pInputCache, ma_device_get_allocation_callbacks(pDevice)); } if (pDevice->capture.pIntermediaryBuffer != NULL) { - ma_free(pDevice->capture.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->capture.pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice)); } if (pDevice->playback.pIntermediaryBuffer != NULL) { - ma_free(pDevice->playback.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->playback.pIntermediaryBuffer, ma_device_get_allocation_callbacks(pDevice)); + } + + #ifndef MA_NO_THREADING + { + ma_device_op_completion_event_uninit(&pDevice->opCompletionEvent); + ma_device_op_queue_uninit(&pDevice->opQueue); + ma_mutex_uninit(&pDevice->startStopLock); } + #endif if (pDevice->isOwnerOfContext) { ma_allocation_callbacks allocationCallbacks = pDevice->pContext->allocationCallbacks; @@ -44191,11 +51682,54 @@ MA_API ma_context* ma_device_get_context(ma_device* pDevice) return pDevice->pContext; } +MA_API void* ma_device_get_user_data(ma_device* pDevice) +{ + if (pDevice == NULL) { + return NULL; + } + + return pDevice->pUserData; +} + MA_API ma_log* ma_device_get_log(ma_device* pDevice) { return ma_context_get_log(ma_device_get_context(pDevice)); } +MA_API ma_device_type ma_device_get_type(ma_device* pDevice) +{ + if (pDevice == NULL) { + return ma_device_type_playback; + } + + return pDevice->type; +} + +MA_API ma_threading_mode ma_device_get_threading_mode(ma_device* pDevice) +{ + if (pDevice == NULL) { + return MA_THREADING_MODE_MULTI_THREADED; + } + + return pDevice->threadingMode; +} + +MA_API const ma_allocation_callbacks* ma_device_get_allocation_callbacks(ma_device* pDevice) +{ + return ma_context_get_allocation_callbacks(ma_device_get_context(pDevice)); +} + +MA_API void* ma_device_get_backend_state(ma_device* pDevice) +{ + if (pDevice == NULL) { + return NULL; + } + + return ma_atomic_load_explicit_ptr((volatile void**)&pDevice->pBackendState, ma_atomic_memory_order_relaxed); +} + + + MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) { if (pDeviceInfo == NULL) { @@ -44208,12 +51742,6 @@ MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_ return MA_INVALID_ARGS; } - /* If the onDeviceGetInfo() callback is set, use that. Otherwise we'll fall back to ma_context_get_device_info(). */ - if (pDevice->pContext->callbacks.onDeviceGetInfo != NULL) { - return pDevice->pContext->callbacks.onDeviceGetInfo(pDevice, type, pDeviceInfo); - } - - /* Getting here means onDeviceGetInfo is not implemented so we need to fall back to an alternative. */ if (type == ma_device_type_playback) { return ma_context_get_device_info(pDevice->pContext, type, pDevice->playback.pID, pDeviceInfo); } else { @@ -44271,6 +51799,35 @@ MA_API ma_result ma_device_get_name(ma_device* pDevice, ma_device_type type, cha return MA_SUCCESS; } + +static void ma_device_start_stop_lock(ma_device* pDevice) +{ + #ifndef MA_NO_THREADING + { + ma_mutex_lock(&pDevice->startStopLock); + } + #else + { + /* Nothing to do. */ + (void)pDevice; + } + #endif +} + +static void ma_device_start_stop_unlock(ma_device* pDevice) +{ + #ifndef MA_NO_THREADING + { + ma_mutex_unlock(&pDevice->startStopLock); + } + #else + { + /* Nothing to do. */ + (void)pDevice; + } + #endif +} + MA_API ma_result ma_device_start(ma_device* pDevice) { ma_result result; @@ -44279,63 +51836,50 @@ MA_API ma_result ma_device_start(ma_device* pDevice) return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + if (ma_device_get_status(pDevice) == ma_device_status_errored) { + return MA_INVALID_OPERATION; /* In an errored state. */ + } + + if (ma_device_get_status(pDevice) == ma_device_status_uninitialized) { return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == ma_device_state_started) { + if (ma_device_get_status(pDevice) == ma_device_status_started) { return MA_SUCCESS; /* Already started. */ } - ma_mutex_lock(&pDevice->startStopLock); + ma_device_start_stop_lock(pDevice); { /* We need to check again if the device is in a started state because it's possible for one thread to have started the device while another was waiting on the mutex. */ - if (ma_device_get_state(pDevice) == ma_device_state_started) { - ma_mutex_unlock(&pDevice->startStopLock); + if (ma_device_get_status(pDevice) == ma_device_status_started) { + ma_device_start_stop_unlock(pDevice); return MA_SUCCESS; /* Already started. */ } /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); - - ma_device__set_state(pDevice, ma_device_state_starting); + MA_ASSERT(ma_device_get_status(pDevice) == ma_device_status_stopped); - /* Asynchronous backends need to be handled differently. */ - if (ma_context_is_backend_asynchronous(pDevice->pContext)) { - if (pDevice->pContext->callbacks.onDeviceStart != NULL) { - result = pDevice->pContext->callbacks.onDeviceStart(pDevice); - } else { - result = MA_INVALID_OPERATION; - } + ma_device_set_status(pDevice, ma_device_status_starting); + #ifndef MA_NO_THREADING + if (pDevice->hasAudioThread) { + result = ma_device_op_queue_push(&pDevice->opQueue, MA_DEVICE_OP_START, NULL, &pDevice->opCompletionEvent); if (result == MA_SUCCESS) { - ma_device__set_state(pDevice, ma_device_state_started); - ma_device__on_notification_started(pDevice); + result = ma_device_op_completion_event_result(&pDevice->opCompletionEvent); /* <-- This will wait for the operation to complete. */ + } else { + /* Failed to push the operation for some reason. Fall through. This should never happen. */ } - } else { - /* - Synchronous backends are started by signaling an event that's being waited on in the worker thread. We first wake up the - thread and then wait for the start event. - */ - ma_event_signal(&pDevice->wakeupEvent); - - /* - Wait for the worker thread to finish starting the device. Note that the worker thread will be the one who puts the device - into the started state. Don't call ma_device__set_state() here. - */ - ma_event_wait(&pDevice->startEvent); - result = pDevice->workResult; - } - - /* We changed the state from stopped to started, so if we failed, make sure we put the state back to stopped. */ - if (result != MA_SUCCESS) { - ma_device__set_state(pDevice, ma_device_state_stopped); + } else + #endif + { + /* Threading is disabled. */ + result = ma_device_op_do_start(pDevice, NULL); } } - ma_mutex_unlock(&pDevice->startStopLock); + ma_device_start_stop_unlock(pDevice); return result; } @@ -44348,59 +51892,55 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + if (ma_device_get_status(pDevice) == ma_device_status_errored) { + return MA_INVALID_OPERATION; /* In an errored state. */ + } + + if (ma_device_get_status(pDevice) == ma_device_status_uninitialized) { return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == ma_device_state_stopped) { + if (ma_device_get_status(pDevice) == ma_device_status_stopped) { return MA_SUCCESS; /* Already stopped. */ } - ma_mutex_lock(&pDevice->startStopLock); + ma_device_start_stop_lock(pDevice); { /* We need to check again if the device is in a stopped state because it's possible for one thread to have stopped the device while another was waiting on the mutex. */ - if (ma_device_get_state(pDevice) == ma_device_state_stopped) { - ma_mutex_unlock(&pDevice->startStopLock); + if (ma_device_get_status(pDevice) == ma_device_status_stopped) { + ma_device_start_stop_unlock(pDevice); return MA_SUCCESS; /* Already stopped. */ } /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_started); + MA_ASSERT(ma_device_get_status(pDevice) == ma_device_status_started); - ma_device__set_state(pDevice, ma_device_state_stopping); - - /* Asynchronous backends need to be handled differently. */ - if (ma_context_is_backend_asynchronous(pDevice->pContext)) { - /* Asynchronous backends must have a stop operation. */ - if (pDevice->pContext->callbacks.onDeviceStop != NULL) { - result = pDevice->pContext->callbacks.onDeviceStop(pDevice); - } else { - result = MA_INVALID_OPERATION; - } + ma_device_set_status(pDevice, ma_device_status_stopping); - ma_device__set_state(pDevice, ma_device_state_stopped); - } else { - /* - Synchronous backends. The stop callback is always called from the worker thread. Do not call the stop callback here. If - the backend is implementing its own audio thread loop we'll need to wake it up if required. Note that we need to make - sure the state of the device is *not* playing right now, which it shouldn't be since we set it above. This is super - important though, so I'm asserting it here as well for extra safety in case we accidentally change something later. - */ - MA_ASSERT(ma_device_get_state(pDevice) != ma_device_state_started); + #ifndef MA_NO_THREADING + if (pDevice->hasAudioThread) { + result = ma_device_op_queue_push(&pDevice->opQueue, MA_DEVICE_OP_STOP, NULL, &pDevice->opCompletionEvent); + if (result == MA_SUCCESS) { + /* + Before waiting on the result, we need to make sure we try waking up the backend first in case it + needs a prod. + */ + if (pDevice->pContext->pVTable->onDeviceWakeup != NULL) { + pDevice->pContext->pVTable->onDeviceWakeup(pDevice); + } - if (pDevice->pContext->callbacks.onDeviceDataLoopWakeup != NULL) { - pDevice->pContext->callbacks.onDeviceDataLoopWakeup(pDevice); + result = ma_device_op_completion_event_result(&pDevice->opCompletionEvent); /* <-- This will wait for the operation to complete. */ + } else { + /* Failed to push the operation for some reason. Fall through. This should never happen. */ } - - /* - We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be - the one who puts the device into the stopped state. Don't call ma_device__set_state() here. - */ - ma_event_wait(&pDevice->stopEvent); - result = MA_SUCCESS; + } else + #endif + { + /* Threading is disabled. */ + result = ma_device_op_do_stop(pDevice, NULL); } /* @@ -44412,23 +51952,231 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) pDevice->playback.inputCacheConsumed = 0; pDevice->playback.inputCacheRemaining = 0; } - ma_mutex_unlock(&pDevice->startStopLock); + ma_device_start_stop_unlock(pDevice); return result; } MA_API ma_bool32 ma_device_is_started(const ma_device* pDevice) { - return ma_device_get_state(pDevice) == ma_device_state_started; + return ma_device_get_status(pDevice) == ma_device_status_started; +} + +MA_API ma_device_status ma_device_get_status(const ma_device* pDevice) +{ + if (pDevice == NULL) { + return ma_device_status_uninitialized; + } + + return ma_atomic_device_status_get((ma_atomic_device_status*)&pDevice->status); /* Naughty cast to get rid of a const warning. */ +} + +MA_API void ma_device_set_errored(ma_device* pDevice) +{ + if (pDevice == NULL) { + return; + } + + if (ma_device_get_status(pDevice) == ma_device_status_errored) { + return; /* Already in an errored state. */ + } + + ma_device_set_status(pDevice, ma_device_status_errored); + ma_device_post_notification_errored(pDevice); +} + +MA_API ma_result ma_device_step(ma_device* pDevice, ma_blocking_mode blockingMode) +{ + ma_result result; + + if (pDevice == NULL) { + return MA_INVALID_ARGS; + } + + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } + + if (pDevice->pContext->pVTable->onDeviceStep) { + result = pDevice->pContext->pVTable->onDeviceStep(pDevice, blockingMode); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + +MA_API ma_format ma_device_get_format(const ma_device* pDevice, ma_device_type deviceType) +{ + if (pDevice == NULL) { + return ma_format_unknown; + } + + if (deviceType == ma_device_type_playback) { + return pDevice->playback.converter.formatIn; + } else { + return pDevice->capture.converter.formatOut; + } +} + +MA_API ma_format ma_device_get_playback_format(const ma_device* pDevice) +{ + return ma_device_get_format(pDevice, ma_device_type_playback); +} + +MA_API ma_format ma_device_get_capture_format(const ma_device* pDevice) +{ + return ma_device_get_format(pDevice, ma_device_type_capture); +} + +MA_API ma_uint32 ma_device_get_channels(const ma_device* pDevice, ma_device_type deviceType) +{ + if (pDevice == NULL) { + return 0; + } + + if (deviceType == ma_device_type_playback) { + return pDevice->playback.converter.channelsIn; + } else { + return pDevice->capture.converter.channelsOut; + } +} + +MA_API ma_uint32 ma_device_get_playback_channels(const ma_device* pDevice) +{ + return ma_device_get_channels(pDevice, ma_device_type_playback); +} + +MA_API ma_uint32 ma_device_get_capture_channels(const ma_device* pDevice) +{ + return ma_device_get_channels(pDevice, ma_device_type_capture); +} + +MA_API ma_uint32 ma_device_get_sample_rate(const ma_device* pDevice) +{ + if (pDevice == NULL) { + return 0; + } + + if (pDevice->type == ma_device_type_playback) { + return pDevice->playback.converter.sampleRateIn; + } else { + return pDevice->capture.converter.sampleRateOut; + } +} + +MA_API void ma_device_get_channel_map(const ma_device* pDevice, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pChannelMap == NULL) { + return; + } + + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + + if (pDevice == NULL) { + return; + } + + if (deviceType == ma_device_type_duplex) { + return; /* Don't know if the playback or capture side is being requested. */ + } + + if (deviceType == ma_device_type_playback) { + ma_data_converter_get_input_channel_map(&pDevice->playback.converter, pChannelMap, channelMapCap); + } else { + ma_data_converter_get_output_channel_map(&pDevice->capture.converter, pChannelMap, channelMapCap); + } +} + +MA_API void ma_device_get_playback_channel_map(const ma_device* pDevice, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_device_get_channel_map(pDevice, ma_device_type_playback, pChannelMap, channelMapCap); +} + +MA_API void ma_device_get_capture_channel_map(const ma_device* pDevice, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_device_get_channel_map(pDevice, ma_device_type_capture, pChannelMap, channelMapCap); +} + + +MA_API ma_format ma_device_get_internal_format(const ma_device* pDevice, ma_device_type deviceType) +{ + if (pDevice == NULL) { + return ma_format_unknown; + } + + if (deviceType == ma_device_type_playback) { + return pDevice->playback.converter.formatOut; + } else { + return pDevice->capture.converter.formatIn; + } +} + +MA_API ma_uint32 ma_device_get_internal_channels(const ma_device* pDevice, ma_device_type deviceType) +{ + if (pDevice == NULL) { + return 0; + } + + if (deviceType == ma_device_type_playback) { + return pDevice->playback.converter.channelsOut; + } else { + return pDevice->capture.converter.channelsIn; + } +} + +MA_API ma_uint32 ma_device_get_internal_sample_rate(const ma_device* pDevice, ma_device_type deviceType) +{ + if (pDevice == NULL) { + return 0; + } + + if (deviceType == ma_device_type_playback) { + return pDevice->playback.converter.sampleRateOut; + } else { + return pDevice->capture.converter.sampleRateIn; + } } -MA_API ma_device_state ma_device_get_state(const ma_device* pDevice) +MA_API void ma_device_get_internal_channel_map(const ma_device* pDevice, ma_device_type deviceType, ma_channel* pChannelMap, size_t channelMapCap) { + if (pChannelMap == NULL) { + return; + } + + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + if (pDevice == NULL) { - return ma_device_state_uninitialized; + return; } - return ma_atomic_device_state_get((ma_atomic_device_state*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ + if (deviceType == ma_device_type_duplex) { + return; /* Don't know if the playback or capture side is being requested. */ + } + + if (deviceType == ma_device_type_playback) { + ma_data_converter_get_output_channel_map(&pDevice->playback.converter, pChannelMap, channelMapCap); + } else { + ma_data_converter_get_input_channel_map(&pDevice->capture.converter, pChannelMap, channelMapCap); + } +} + +MA_API ma_uint32 ma_device_get_period_size_in_frames(ma_device* pDevice) +{ + if (ma_device_get_type(pDevice) == ma_device_type_playback) { + if (pDevice->playback.intermediaryBufferCap > 0) { + return pDevice->playback.intermediaryBufferCap; /* Using a fixed sized processing callback. */ + } else { + return pDevice->playback.internalPeriodSizeInFrames; /* Not using a fixed sized processing callback. Need to estimate the processing size based on the backend. */ + } + } else { + if (pDevice->capture.intermediaryBufferCap > 0) { + return pDevice->capture.intermediaryBufferCap; + } else { + return pDevice->capture.internalPeriodSizeInFrames; + } + } } MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) @@ -44512,12 +52260,14 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void } if (pDevice->type == ma_device_type_duplex) { + MA_ASSERT(pDevice->pDuplexRB != NULL); + if (pInput != NULL) { - ma_device__handle_duplex_callback_capture(pDevice, frameCount, pInput, &pDevice->duplexRB.rb); + ma_device__handle_duplex_callback_capture(pDevice, frameCount, pInput, &pDevice->pDuplexRB->rb); } if (pOutput != NULL) { - ma_device__handle_duplex_callback_playback(pDevice, frameCount, pOutput, &pDevice->duplexRB.rb); + ma_device__handle_duplex_callback_playback(pDevice, frameCount, pOutput, &pDevice->pDuplexRB->rb); } } else { if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_loopback) { @@ -44540,7 +52290,64 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void return MA_SUCCESS; } -MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) + +static ma_device_notification ma_device_notification_init(ma_device* pDevice, ma_device_notification_type type) +{ + ma_device_notification notification; + + MA_ZERO_OBJECT(¬ification); + notification.pDevice = pDevice; + notification.type = type; + + return notification; +} + +MA_API void ma_device_post_notification(ma_device_notification notification) +{ + if (notification.pDevice == NULL || notification.pDevice->onNotification == NULL) { + return; + } + + notification.pDevice->onNotification(¬ification); +} + +MA_API void ma_device_post_notification_started(ma_device* pDevice) +{ + ma_device_post_notification(ma_device_notification_init(pDevice, ma_device_notification_type_started)); +} + +MA_API void ma_device_post_notification_stopped(ma_device* pDevice) +{ + ma_device_post_notification(ma_device_notification_init(pDevice, ma_device_notification_type_stopped)); +} + +MA_API void ma_device_post_notification_rerouted(ma_device* pDevice) +{ + ma_device_post_notification(ma_device_notification_init(pDevice, ma_device_notification_type_rerouted)); +} + +MA_API void ma_device_post_notification_interruption_began(ma_device* pDevice) +{ + ma_device_post_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began)); +} + +MA_API void ma_device_post_notification_interruption_ended(ma_device* pDevice) +{ + ma_device_post_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended)); +} + +MA_API void ma_device_post_notification_unlocked(ma_device* pDevice) +{ + ma_device_post_notification(ma_device_notification_init(pDevice, ma_device_notification_type_unlocked)); +} + +MA_API void ma_device_post_notification_errored(ma_device* pDevice) +{ + ma_device_post_notification(ma_device_notification_init(pDevice, ma_device_notification_type_errored)); +} + + +MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate) { if (pDescriptor == NULL) { return 0; @@ -44563,11 +52370,7 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_dev if (pDescriptor->periodSizeInFrames == 0) { if (pDescriptor->periodSizeInMilliseconds == 0) { - if (performanceProfile == ma_performance_profile_low_latency) { - return ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY, nativeSampleRate); - } else { - return ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE, nativeSampleRate); - } + return ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS, nativeSampleRate); } else { return ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); } @@ -44575,6 +52378,309 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_dev return pDescriptor->periodSizeInFrames; } } + + +/* BEG ma_device_state_async.c */ +MA_API ma_result ma_device_state_async_init(ma_device_type deviceType, const ma_device_descriptor* pDescriptorPlayback, const ma_device_descriptor* pDescriptorCapture, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_state_async* pAsyncDeviceState) +{ + ma_result result; + ma_uint32 periodSizeInFramesPlayback = 0; + ma_uint32 periodSizeInFramesCapture = 0; + + if (pAsyncDeviceState == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pAsyncDeviceState); + pAsyncDeviceState->deviceType = deviceType; + + result = ma_semaphore_init(0, &pAsyncDeviceState->semaphore); + if (result != MA_SUCCESS) { + return result; + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + if (pDescriptorCapture == NULL) { + return MA_INVALID_ARGS; + } + + pAsyncDeviceState->capture.format = pDescriptorCapture->format; + pAsyncDeviceState->capture.channels = pDescriptorCapture->channels; + + periodSizeInFramesCapture = pDescriptorCapture->periodSizeInFrames; + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (pDescriptorPlayback == NULL) { + return MA_INVALID_ARGS; + } + + pAsyncDeviceState->playback.format = pDescriptorPlayback->format; + pAsyncDeviceState->playback.channels = pDescriptorPlayback->channels; + + periodSizeInFramesPlayback = pDescriptorPlayback->periodSizeInFrames; + } + + result = ma_device_state_async_resize(pAsyncDeviceState, periodSizeInFramesPlayback, periodSizeInFramesCapture, pAllocationCallbacks); + if (result != MA_SUCCESS) { + ma_semaphore_uninit(&pAsyncDeviceState->semaphore); + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_device_state_async_uninit(ma_device_state_async* pAsyncDeviceState, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pAsyncDeviceState == NULL) { + return; + } + + ma_free(pAsyncDeviceState->internal.pBuffer, pAllocationCallbacks); + pAsyncDeviceState->internal.pBuffer = NULL; + + ma_semaphore_uninit(&pAsyncDeviceState->semaphore); +} + +MA_API ma_result ma_device_state_async_resize(ma_device_state_async* pAsyncDeviceState, ma_uint32 sizeInFramesPlayback, ma_uint32 sizeInFramesCapture, const ma_allocation_callbacks* pAllocationCallbacks) +{ + size_t bufferAllocSizeInBytesCapture = 0; + size_t bufferAllocSizeInBytesPlayback = 0; + void* pNewBuffer = NULL; + + if (pAsyncDeviceState == NULL) { + return MA_INVALID_ARGS; + } + + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + if (sizeInFramesCapture == 0) { + return MA_INVALID_ARGS; + } + + bufferAllocSizeInBytesCapture = ma_align_64(ma_get_bytes_per_frame(pAsyncDeviceState->capture.format, pAsyncDeviceState->capture.channels) * sizeInFramesCapture); + } + + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + if (sizeInFramesPlayback == 0) { + return MA_INVALID_ARGS; + } + + bufferAllocSizeInBytesPlayback = ma_align_64(ma_get_bytes_per_frame(pAsyncDeviceState->playback.format, pAsyncDeviceState->playback.channels) * sizeInFramesPlayback); + } + + pNewBuffer = ma_calloc(bufferAllocSizeInBytesCapture + bufferAllocSizeInBytesPlayback, pAllocationCallbacks); + if (pNewBuffer == NULL) { + return MA_OUT_OF_MEMORY; + } + + ma_free(pAsyncDeviceState->internal.pBuffer, pAllocationCallbacks); + pAsyncDeviceState->internal.pBuffer = pNewBuffer; + + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + pAsyncDeviceState->capture.pBuffer = pAsyncDeviceState->internal.pBuffer; + pAsyncDeviceState->capture.frameCap = sizeInFramesCapture; + } + + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + pAsyncDeviceState->playback.pBuffer = ma_offset_ptr(pAsyncDeviceState->internal.pBuffer, bufferAllocSizeInBytesCapture); + pAsyncDeviceState->playback.frameCap = sizeInFramesPlayback; + } + + return MA_SUCCESS; +} + +MA_API void ma_device_state_async_flush(ma_device_state_async* pAsyncDeviceState) +{ + if (pAsyncDeviceState == NULL) { + return; + } + + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_spinlock_lock(&pAsyncDeviceState->capture.lock); + { + pAsyncDeviceState->capture.frameCount = pAsyncDeviceState->capture.frameCap; + } + ma_spinlock_unlock(&pAsyncDeviceState->capture.lock); + } + + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_spinlock_lock(&pAsyncDeviceState->playback.lock); + { + pAsyncDeviceState->playback.frameCount = 0; + } + ma_spinlock_unlock(&pAsyncDeviceState->playback.lock); + } +} + +MA_API void ma_device_state_async_release(ma_device_state_async* pAsyncDeviceState) +{ + if (pAsyncDeviceState == NULL) { + return; + } + + ma_semaphore_release(&pAsyncDeviceState->semaphore); +} + +MA_API void ma_device_state_async_wait(ma_device_state_async* pAsyncDeviceState) +{ + if (pAsyncDeviceState == NULL) { + return; + } + + ma_semaphore_wait(&pAsyncDeviceState->semaphore); +} + +static ma_result ma_device_state_async_step_internal(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice) +{ + ma_result result = MA_NO_DATA_AVAILABLE; + + if (pAsyncDeviceState == NULL || pDevice == NULL) { + return MA_INVALID_ARGS; + } + + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_spinlock_lock(&pAsyncDeviceState->capture.lock); + { + if (pAsyncDeviceState->capture.frameCount > 0) { + ma_device_handle_backend_data_callback(pDevice, NULL, pAsyncDeviceState->capture.pBuffer, pAsyncDeviceState->capture.frameCount); + pAsyncDeviceState->capture.frameCount = 0; + result = MA_SUCCESS; + } + } + ma_spinlock_unlock(&pAsyncDeviceState->capture.lock); + } + + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pAsyncDeviceState->playback.format, pAsyncDeviceState->playback.channels); + + ma_spinlock_lock(&pAsyncDeviceState->playback.lock); + { + if (pAsyncDeviceState->playback.frameCount < pAsyncDeviceState->playback.frameCap) { + ma_device_handle_backend_data_callback(pDevice, ma_offset_ptr(pAsyncDeviceState->playback.pBuffer, bytesPerFrame * pAsyncDeviceState->playback.frameCount), NULL, (pAsyncDeviceState->playback.frameCap - pAsyncDeviceState->playback.frameCount)); + pAsyncDeviceState->playback.frameCount = pAsyncDeviceState->playback.frameCap; + result = MA_SUCCESS; + } + } + ma_spinlock_unlock(&pAsyncDeviceState->playback.lock); + } + + return result; +} + +MA_API ma_result ma_device_state_async_step(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice, ma_blocking_mode blockingMode, ma_result (* extra)(ma_device* pDevice)) +{ + ma_result result; + + for (;;) { + if (blockingMode == MA_BLOCKING_MODE_BLOCKING) { + ma_device_state_async_wait(pAsyncDeviceState); + } + + if (!ma_device_is_started(pDevice)) { + return MA_DEVICE_NOT_STARTED; + } + + if (extra != NULL) { + result = extra(pDevice); + if (result != MA_SUCCESS) { + return result; + } + } + + result = ma_device_state_async_step_internal(pAsyncDeviceState, pDevice); + if (result == MA_SUCCESS) { + break; + } + + if (result != MA_NO_DATA_AVAILABLE) { + return result; + } + + /* Getting here means no data was processed. In non-blocking mode we don't care, just get out of the loop. */ + if (blockingMode == MA_BLOCKING_MODE_NON_BLOCKING) { + break; + } + + /* Getting here means we're in blocking mode and no data was processed. In this case we'd rather keep waiting for data to be available. */ + continue; + } + + return MA_SUCCESS; +} + +MA_API void ma_device_state_async_process(ma_device_state_async* pAsyncDeviceState, ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + if (pAsyncDeviceState == NULL || pDevice == NULL) { + return; + } + + if (pInput != NULL) { + if (pAsyncDeviceState->deviceType == ma_device_type_capture || pAsyncDeviceState->deviceType == ma_device_type_duplex || pAsyncDeviceState->deviceType == ma_device_type_loopback) { + ma_spinlock_lock(&pAsyncDeviceState->capture.lock); + { + ma_uint32 framesToCopy; + ma_uint32 framesAvailable; + + MA_ASSERT(pAsyncDeviceState->capture.frameCap >= pAsyncDeviceState->capture.frameCount); + framesAvailable = pAsyncDeviceState->capture.frameCap - pAsyncDeviceState->capture.frameCount; + + framesToCopy = frameCount; + if (framesToCopy > framesAvailable) { + framesToCopy = framesAvailable; + } + + if (framesToCopy > 0) { + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pAsyncDeviceState->capture.format, pAsyncDeviceState->capture.channels); + + MA_COPY_MEMORY(ma_offset_ptr(pAsyncDeviceState->capture.pBuffer, bytesPerFrame * pAsyncDeviceState->capture.frameCount), pInput, bytesPerFrame * framesToCopy); + pAsyncDeviceState->capture.frameCount += framesToCopy; + } + } + ma_spinlock_unlock(&pAsyncDeviceState->capture.lock); + } else { + MA_ASSERT(MA_FALSE); /* Should never get here. */ + } + } + + if (pOutput != NULL) { + if (pAsyncDeviceState->deviceType == ma_device_type_playback || pAsyncDeviceState->deviceType == ma_device_type_duplex) { + ma_spinlock_lock(&pAsyncDeviceState->playback.lock); + { + ma_uint32 framesToCopy; + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pAsyncDeviceState->playback.format, pAsyncDeviceState->playback.channels); + + framesToCopy = frameCount; + if (framesToCopy > pAsyncDeviceState->playback.frameCount) { + framesToCopy = pAsyncDeviceState->playback.frameCount; + } + + if (framesToCopy > 0) { + MA_COPY_MEMORY(pOutput, pAsyncDeviceState->playback.pBuffer, bytesPerFrame * framesToCopy); + + /* Move any remaining data to the front of the buffer. */ + if (framesToCopy < pAsyncDeviceState->playback.frameCount) { + MA_COPY_MEMORY(pAsyncDeviceState->playback.pBuffer, ma_offset_ptr(pAsyncDeviceState->playback.pBuffer, bytesPerFrame * framesToCopy), bytesPerFrame * (pAsyncDeviceState->playback.frameCount - framesToCopy)); + } + + pAsyncDeviceState->playback.frameCount -= framesToCopy; + } + + /* Silence any remaining data in the output buffer. */ + if (framesToCopy < frameCount) { + ma_uint32 framesToSilence = frameCount - framesToCopy; + MA_ZERO_MEMORY(ma_offset_ptr(pOutput, bytesPerFrame * framesToCopy), bytesPerFrame * framesToSilence); + } + } + ma_spinlock_unlock(&pAsyncDeviceState->playback.lock); + } else { + MA_ASSERT(MA_FALSE); /* Should never get here. */ + } + } + + ma_semaphore_release(&pAsyncDeviceState->semaphore); +} +/* END ma_device_state_async.c */ + #endif /* MA_NO_DEVICE_IO */ @@ -46920,64 +55026,6 @@ MA_API void ma_pcm_f32_to_f32(void* dst, const void* src, ma_uint64 count, ma_di } -static void ma_pcm_interleave_f32__reference(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) -{ - float* dst_f32 = (float*)dst; - const float** src_f32 = (const float**)src; - - ma_uint64 iFrame; - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; iChannel += 1) { - dst_f32[iFrame*channels + iChannel] = src_f32[iChannel][iFrame]; - } - } -} - -static void ma_pcm_interleave_f32__optimized(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) -{ - ma_pcm_interleave_f32__reference(dst, src, frameCount, channels); -} - -MA_API void ma_pcm_interleave_f32(void* dst, const void** src, ma_uint64 frameCount, ma_uint32 channels) -{ -#ifdef MA_USE_REFERENCE_CONVERSION_APIS - ma_pcm_interleave_f32__reference(dst, src, frameCount, channels); -#else - ma_pcm_interleave_f32__optimized(dst, src, frameCount, channels); -#endif -} - - -static void ma_pcm_deinterleave_f32__reference(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) -{ - float** dst_f32 = (float**)dst; - const float* src_f32 = (const float*)src; - - ma_uint64 iFrame; - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; iChannel += 1) { - dst_f32[iChannel][iFrame] = src_f32[iFrame*channels + iChannel]; - } - } -} - -static void ma_pcm_deinterleave_f32__optimized(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) -{ - ma_pcm_deinterleave_f32__reference(dst, src, frameCount, channels); -} - -MA_API void ma_pcm_deinterleave_f32(void** dst, const void* src, ma_uint64 frameCount, ma_uint32 channels) -{ -#ifdef MA_USE_REFERENCE_CONVERSION_APIS - ma_pcm_deinterleave_f32__reference(dst, src, frameCount, channels); -#else - ma_pcm_deinterleave_f32__optimized(dst, src, frameCount, channels); -#endif -} - - MA_API void ma_pcm_convert(void* pOut, ma_format formatOut, const void* pIn, ma_format formatIn, ma_uint64 sampleCount, ma_dither_mode ditherMode) { if (formatOut == formatIn) { @@ -47062,15 +55110,176 @@ MA_API void ma_deinterleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ return; /* Invalid args. */ } + /* + You shouldn't really be calling this for mono streams, but I can imagine a situation where it is called + generically without the caller explicitly checking the channel count. We'll implement this as a memcpy() + to keep it fast. + */ + if (channels == 1) { + ma_copy_memory_64(ppDeinterleavedPCMFrames[0], pInterleavedPCMFrames, frameCount * ma_get_bytes_per_frame(format, channels)); + return; + } + /* For efficiency we do this per format. */ switch (format) { + case ma_format_u8: + { + const ma_uint8* pSrcU8 = (const ma_uint8*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + + iPCMFrame = 0; + + /* Specialization for stereo. We can do 4 frames at a time here. */ + if (channels == 2) { + ma_uint32* pDstU32_0 = (ma_uint32*)ppDeinterleavedPCMFrames[0]; + ma_uint32* pDstU32_1 = (ma_uint32*)ppDeinterleavedPCMFrames[1]; + ma_uint64 frameCount4 = frameCount >> 2; + + /* Check alignment of buffers. Use aligned fast path only if all buffers are 4-byte aligned. */ + if (((ma_uintptr)pSrcU8 & 3) == 0 && ((ma_uintptr)pDstU32_0 & 3) == 0 && ((ma_uintptr)pDstU32_1 & 3) == 0) { + #if 1 + { + const ma_uint32* pSrcU32Running = (const ma_uint32*)pSrcU8; + + for (iPCMFrame = 0; iPCMFrame < frameCount4; iPCMFrame += 1) { + ma_uint32 src0; + ma_uint32 src1; + ma_uint32 dst0; + ma_uint32 dst1; + + src0 = pSrcU32Running[0]; + src1 = pSrcU32Running[1]; + + dst0 = ((src0 & 0x00FF0000) >> 8) | ((src0 & 0x000000FF) >> 0) | ((src1 & 0x00FF0000) << 8) | ((src1 & 0x000000FF) << 16); + dst1 = ((src0 & 0xFF000000) >> 16) | ((src0 & 0x0000FF00) >> 8) | ((src1 & 0xFF000000) << 0) | ((src1 & 0x0000FF00) << 8); + + pDstU32_0[iPCMFrame] = dst0; + pDstU32_1[iPCMFrame] = dst1; + + pSrcU32Running += 2; + } + + iPCMFrame *= 4; + } + #else + { + const ma_uint8* pSrcU8Running = (const ma_uint8*)pSrcU8; + + for (iPCMFrame = 0; iPCMFrame + 4 < frameCount; iPCMFrame += 4) { + ma_uint32 dst0; + ma_uint32 dst1; + + dst0 = (pSrcU8Running[0] << 0) | (pSrcU8Running[2] << 8) | (pSrcU8Running[4] << 16) | (pSrcU8Running[6] << 24); + dst1 = (pSrcU8Running[1] << 0) | (pSrcU8Running[3] << 8) | (pSrcU8Running[5] << 16) | (pSrcU8Running[7] << 24); + + *pDstU32_0 = dst0; + *pDstU32_1 = dst1; + + pSrcU8Running += 4 * 2; + pDstU32_0 += 1; + pDstU32_1 += 1; + } + } + #endif + } else { + ma_uint8* pDstU8_0 = (ma_uint8*)ppDeinterleavedPCMFrames[0]; + ma_uint8* pDstU8_1 = (ma_uint8*)ppDeinterleavedPCMFrames[1]; + const ma_uint8* pSrcU8Running = (const ma_uint8*)pSrcU8; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstU8_0[iPCMFrame] = pSrcU8Running[0]; + pDstU8_1[iPCMFrame] = pSrcU8Running[1]; + + pSrcU8Running += 2; + } + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + ma_uint8* pDstU8 = (ma_uint8*)ppDeinterleavedPCMFrames[iChannel]; + pDstU8[iPCMFrame] = pSrcU8[iPCMFrame*channels+iChannel]; + } + } + } break; + case ma_format_s16: { const ma_int16* pSrcS16 = (const ma_int16*)pInterleavedPCMFrames; ma_uint64 iPCMFrame; - for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + + iPCMFrame = 0; + + /* Specialization for stereo. We can do 2 frames at a time here. */ + if (channels == 2) { + ma_uint32* pDstU32_0 = (ma_uint32*)ppDeinterleavedPCMFrames[0]; + ma_uint32* pDstU32_1 = (ma_uint32*)ppDeinterleavedPCMFrames[1]; + ma_uint64 frameCount2 = frameCount >> 1; + + /* Check alignment of buffers. Use aligned fast path only if all buffers are 4-byte aligned. */ + if (((ma_uintptr)pSrcS16 & 3) == 0 && ((ma_uintptr)pDstU32_0 & 3) == 0 && ((ma_uintptr)pDstU32_1 & 3) == 0) { + #if 1 + { + const ma_uint32* pSrcU32Running = (const ma_uint32*)pSrcS16; + + for (iPCMFrame = 0; iPCMFrame < frameCount2; iPCMFrame += 1) { + ma_uint32 src0; + ma_uint32 src1; + ma_uint32 dst0; + ma_uint32 dst1; + + src0 = pSrcU32Running[0]; + src1 = pSrcU32Running[1]; + + dst0 = ((src0 & 0x0000FFFF) << 0) | ((src1 & 0x0000FFFF) << 16); + dst1 = ((src0 & 0xFFFF0000) >> 16) | ((src1 & 0xFFFF0000) >> 0); + + pDstU32_0[iPCMFrame] = dst0; + pDstU32_1[iPCMFrame] = dst1; + + pSrcU32Running += 2; + } + + iPCMFrame *= 2; + } + #else + { + const ma_uint16* pSrcU16Running = (const ma_uint16*)pSrcS16; + + for (iPCMFrame = 0; iPCMFrame + 2 < frameCount; iPCMFrame += 2) { + ma_uint32 dst0; + ma_uint32 dst1; + + dst0 = (pSrcU16Running[0] << 0) | (pSrcU16Running[2] << 16); + dst1 = (pSrcU16Running[1] << 0) | (pSrcU16Running[3] << 16); + + *pDstU32_0 = dst0; + *pDstU32_1 = dst1; + + pSrcU16Running += 2 * 2; + pDstU32_0 += 1; + pDstU32_1 += 1; + } + } + #endif + } else { + ma_int16* pDstS16_0 = (ma_int16*)ppDeinterleavedPCMFrames[0]; + ma_int16* pDstS16_1 = (ma_int16*)ppDeinterleavedPCMFrames[1]; + const ma_int16* pSrcS16Running = (const ma_int16*)pSrcS16; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstS16_0[iPCMFrame] = pSrcS16Running[0]; + pDstS16_1[iPCMFrame] = pSrcS16Running[1]; + + pSrcS16Running += 2; + } + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; ++iChannel) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { ma_int16* pDstS16 = (ma_int16*)ppDeinterleavedPCMFrames[iChannel]; pDstS16[iPCMFrame] = pSrcS16[iPCMFrame*channels+iChannel]; } @@ -47081,22 +55290,108 @@ MA_API void ma_deinterleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ { const float* pSrcF32 = (const float*)pInterleavedPCMFrames; ma_uint64 iPCMFrame; - for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + + iPCMFrame = 0; + + /* Specialization for stereo. */ + if (channels == 2) { + float* pDstF32_0 = (float*)ppDeinterleavedPCMFrames[0]; + float* pDstF32_1 = (float*)ppDeinterleavedPCMFrames[1]; + const float* pSrcF32Running = (const float*)pSrcF32; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstF32_0[iPCMFrame] = pSrcF32Running[0]; + pDstF32_1[iPCMFrame] = pSrcF32Running[1]; + + pSrcF32Running += 2; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; ++iChannel) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { float* pDstF32 = (float*)ppDeinterleavedPCMFrames[iChannel]; pDstF32[iPCMFrame] = pSrcF32[iPCMFrame*channels+iChannel]; } } } break; + case ma_format_s32: + { + const ma_int32* pSrcS32 = (const ma_int32*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + + iPCMFrame = 0; + + /* Specialization for stereo. */ + if (channels == 2) { + ma_uint32* pDstU32_0 = (ma_uint32*)ppDeinterleavedPCMFrames[0]; + ma_uint32* pDstU32_1 = (ma_uint32*)ppDeinterleavedPCMFrames[1]; + const ma_uint32* pSrcU32Running = (const ma_uint32*)pSrcS32; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstU32_0[iPCMFrame] = pSrcU32Running[0]; + pDstU32_1[iPCMFrame] = pSrcU32Running[1]; + + pSrcU32Running += 2; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + ma_int32* pDstS32 = (ma_int32*)ppDeinterleavedPCMFrames[iChannel]; + pDstS32[iPCMFrame] = pSrcS32[iPCMFrame*channels+iChannel]; + } + } + } break; + + case ma_format_s24: + { + const ma_uint8* pSrcS24 = (const ma_uint8*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + + iPCMFrame = 0; + + if (channels == 2) { + ma_uint8* pDstS24_0 = (ma_uint8*)ppDeinterleavedPCMFrames[0]; + ma_uint8* pDstS24_1 = (ma_uint8*)ppDeinterleavedPCMFrames[1]; + const ma_uint8* pSrcS24Running = (const ma_uint8*)pSrcS24; + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstS24_0[0] = pSrcS24Running[0]; + pDstS24_0[1] = pSrcS24Running[1]; + pDstS24_0[2] = pSrcS24Running[2]; + + pDstS24_1[0] = pSrcS24Running[3]; + pDstS24_1[1] = pSrcS24Running[4]; + pDstS24_1[2] = pSrcS24Running[5]; + + pSrcS24Running += 6; + pDstS24_0 += 3; + pDstS24_1 += 3; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + ma_uint8* pDstS24 = (ma_uint8*)ppDeinterleavedPCMFrames[iChannel]; + pDstS24[iPCMFrame*3 + 0] = pSrcS24[(iPCMFrame*channels+iChannel)*3 + 0]; + pDstS24[iPCMFrame*3 + 1] = pSrcS24[(iPCMFrame*channels+iChannel)*3 + 1]; + pDstS24[iPCMFrame*3 + 2] = pSrcS24[(iPCMFrame*channels+iChannel)*3 + 2]; + } + } + } break; + + /* Generic fallback. Should never actually hit this. */ default: { ma_uint32 sampleSizeInBytes = ma_get_bytes_per_sample(format); ma_uint64 iPCMFrame; - for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; ++iChannel) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { void* pDst = ma_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); const void* pSrc = ma_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); memcpy(pDst, pSrc, sampleSizeInBytes); @@ -47108,15 +55403,76 @@ MA_API void ma_deinterleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ MA_API void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames) { + if (ppDeinterleavedPCMFrames == NULL || pInterleavedPCMFrames == NULL) { + return; /* Invalid args. */ + } + + /* + You shouldn't really be calling this for mono streams, but I can imagine a situation where it is called + generically without the caller explicitly checking the channel count. We'll implement this as a memcpy() + to keep it fast. + */ + if (channels == 1) { + ma_copy_memory_64(pInterleavedPCMFrames, ppDeinterleavedPCMFrames[0], frameCount * ma_get_bytes_per_frame(format, channels)); + return; + } + switch (format) { + case ma_format_u8: + { + ma_uint8* pDstU8 = (ma_uint8*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + + iPCMFrame = 0; + + /* Specialization for stereo. */ + if (channels == 2) { + const ma_uint8* pSrcU8_0 = (const ma_uint8*)ppDeinterleavedPCMFrames[0]; + const ma_uint8* pSrcU8_1 = (const ma_uint8*)ppDeinterleavedPCMFrames[1]; + ma_uint8* pDstU8Running = (ma_uint8*)pDstU8; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstU8Running[0] = pSrcU8_0[iPCMFrame]; + pDstU8Running[1] = pSrcU8_1[iPCMFrame]; + + pDstU8Running += 2; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + const ma_uint8* pSrcU8 = (const ma_uint8*)ppDeinterleavedPCMFrames[iChannel]; + pDstU8[iPCMFrame*channels+iChannel] = pSrcU8[iPCMFrame]; + } + } + } break; + case ma_format_s16: { ma_int16* pDstS16 = (ma_int16*)pInterleavedPCMFrames; ma_uint64 iPCMFrame; - for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + + iPCMFrame = 0; + + /* Specialization for stereo. */ + if (channels == 2) { + const ma_int16* pSrcS16_0 = (const ma_int16*)ppDeinterleavedPCMFrames[0]; + const ma_int16* pSrcS16_1 = (const ma_int16*)ppDeinterleavedPCMFrames[1]; + ma_int16* pDstS16Running = (ma_int16*)pDstS16; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstS16Running[0] = pSrcS16_0[iPCMFrame]; + pDstS16Running[1] = pSrcS16_1[iPCMFrame]; + + pDstS16Running += 2; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; ++iChannel) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { const ma_int16* pSrcS16 = (const ma_int16*)ppDeinterleavedPCMFrames[iChannel]; pDstS16[iPCMFrame*channels+iChannel] = pSrcS16[iPCMFrame]; } @@ -47127,22 +55483,107 @@ MA_API void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ui { float* pDstF32 = (float*)pInterleavedPCMFrames; ma_uint64 iPCMFrame; - for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + + iPCMFrame = 0; + + /* Specialization for stereo. */ + if (channels == 2) { + const float* pSrcF32_0 = (const float*)ppDeinterleavedPCMFrames[0]; + const float* pSrcF32_1 = (const float*)ppDeinterleavedPCMFrames[1]; + float* pDstF32Running = (float*)pDstF32; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstF32Running[0] = pSrcF32_0[iPCMFrame]; + pDstF32Running[1] = pSrcF32_1[iPCMFrame]; + + pDstF32Running += 2; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; ++iChannel) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { const float* pSrcF32 = (const float*)ppDeinterleavedPCMFrames[iChannel]; pDstF32[iPCMFrame*channels+iChannel] = pSrcF32[iPCMFrame]; } } } break; + case ma_format_s32: + { + ma_int32* pDstS32 = (ma_int32*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + + iPCMFrame = 0; + + /* Specialization for stereo. */ + if (channels == 2) { + const ma_uint32* pSrcU32_0 = (const ma_uint32*)ppDeinterleavedPCMFrames[0]; + const ma_uint32* pSrcU32_1 = (const ma_uint32*)ppDeinterleavedPCMFrames[1]; + ma_uint32* pDstU32Running = (ma_uint32*)pDstS32; + + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstU32Running[0] = pSrcU32_0[iPCMFrame]; + pDstU32Running[1] = pSrcU32_1[iPCMFrame]; + + pDstU32Running += 2; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + const ma_int32* pSrcS32 = (const ma_int32*)ppDeinterleavedPCMFrames[iChannel]; + pDstS32[iPCMFrame*channels+iChannel] = pSrcS32[iPCMFrame]; + } + } + } break; + + case ma_format_s24: + { + ma_uint8* pDstS24 = (ma_uint8*)pInterleavedPCMFrames; + ma_uint64 iPCMFrame; + + iPCMFrame = 0; + + if (channels == 2) { + const ma_uint8* pSrcS24_0 = (const ma_uint8*)ppDeinterleavedPCMFrames[0]; + const ma_uint8* pSrcS24_1 = (const ma_uint8*)ppDeinterleavedPCMFrames[1]; + ma_uint8* pDstS24Running = (ma_uint8*)pDstS24; + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + pDstS24Running[0] = pSrcS24_0[0]; + pDstS24Running[1] = pSrcS24_0[1]; + pDstS24Running[2] = pSrcS24_0[2]; + + pDstS24Running[3] = pSrcS24_1[0]; + pDstS24Running[4] = pSrcS24_1[1]; + pDstS24Running[5] = pSrcS24_1[2]; + + pDstS24Running += 6; + pSrcS24_0 += 3; + pSrcS24_1 += 3; + } + } + + for (; iPCMFrame < frameCount; iPCMFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + const ma_uint8* pSrcS24 = (const ma_uint8*)ppDeinterleavedPCMFrames[iChannel]; + pDstS24[(iPCMFrame*channels+iChannel)*3 + 0] = pSrcS24[iPCMFrame*3 + 0]; + pDstS24[(iPCMFrame*channels+iChannel)*3 + 1] = pSrcS24[iPCMFrame*3 + 1]; + pDstS24[(iPCMFrame*channels+iChannel)*3 + 2] = pSrcS24[iPCMFrame*3 + 2]; + } + } + } break; + default: { ma_uint32 sampleSizeInBytes = ma_get_bytes_per_sample(format); ma_uint64 iPCMFrame; - for (iPCMFrame = 0; iPCMFrame < frameCount; ++iPCMFrame) { + for (iPCMFrame = 0; iPCMFrame < frameCount; iPCMFrame += 1) { ma_uint32 iChannel; - for (iChannel = 0; iChannel < channels; ++iChannel) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { void* pDst = ma_offset_ptr(pInterleavedPCMFrames, (iPCMFrame*channels+iChannel)*sampleSizeInBytes); const void* pSrc = ma_offset_ptr(ppDeinterleavedPCMFrames[iChannel], iPCMFrame*sampleSizeInBytes); memcpy(pDst, pSrc, sampleSizeInBytes); @@ -47694,7 +56135,6 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, co ma_uint32 c; const ma_uint32 channels = pLPF->channels; const float a = pLPF->a.f32; - const float b = 1 - a; MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { @@ -47702,9 +56142,9 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, co float x = pX[c]; float y; - y = b*x + a*r1; + y = x + a*(r1 - x); - pY[c] = y; + pY[c] = y; pLPF->pR1[c].f32 = y; } } @@ -48566,8 +57006,7 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_f32(ma_hpf1* pHPF, float* pY, co { ma_uint32 c; const ma_uint32 channels = pHPF->channels; - const float a = 1 - pHPF->a.f32; - const float b = 1 - a; + const float b = pHPF->a.f32; MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { @@ -48575,7 +57014,7 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_f32(ma_hpf1* pHPF, float* pY, co float x = pX[c]; float y; - y = b*x - a*r1; + y = b*(x + r1) - r1; pY[c] = y; pHPF->pR1[c].f32 = y; @@ -50389,8 +58828,6 @@ MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sample config.sampleRate = sampleRate; config.delayInFrames = delayInFrames; config.delayStart = (decay == 0) ? MA_TRUE : MA_FALSE; /* Delay the start if it looks like we're not configuring an echo. */ - config.wet = 1; - config.dry = 1; config.decay = decay; return config; @@ -50455,18 +58892,18 @@ MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, /* Delayed start. */ /* Read */ - pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer]; /* Feedback */ - pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + pFramesInF32[iChannel]; } else { /* Immediate start */ /* Feedback */ - pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + pFramesInF32[iChannel]; /* Read */ - pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer]; } } @@ -50479,42 +58916,6 @@ MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, return MA_SUCCESS; } -MA_API void ma_delay_set_wet(ma_delay* pDelay, float value) -{ - if (pDelay == NULL) { - return; - } - - pDelay->config.wet = value; -} - -MA_API float ma_delay_get_wet(const ma_delay* pDelay) -{ - if (pDelay == NULL) { - return 0; - } - - return pDelay->config.wet; -} - -MA_API void ma_delay_set_dry(ma_delay* pDelay, float value) -{ - if (pDelay == NULL) { - return; - } - - pDelay->config.dry = value; -} - -MA_API float ma_delay_get_dry(const ma_delay* pDelay) -{ - if (pDelay == NULL) { - return 0; - } - - return pDelay->config.dry; -} - MA_API void ma_delay_set_decay(ma_delay* pDelay, float value) { if (pDelay == NULL) { @@ -51664,9 +60065,20 @@ static ma_vec3f ma_get_channel_direction(ma_channel channel) } +MA_API float ma_attenuation_model_none(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff) +{ + (void)pUserData; + (void)distance; + (void)minDistance; + (void)maxDistance; + (void)rolloff; + return 1; +} -static float ma_attenuation_inverse(float distance, float minDistance, float maxDistance, float rolloff) +MA_API float ma_attenuation_model_inverse(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff) { + (void)pUserData; + if (minDistance >= maxDistance) { return 1; /* To avoid division by zero. Do not attenuate. */ } @@ -51674,8 +60086,10 @@ static float ma_attenuation_inverse(float distance, float minDistance, float max return minDistance / (minDistance + rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance)); } -static float ma_attenuation_linear(float distance, float minDistance, float maxDistance, float rolloff) +MA_API float ma_attenuation_model_linear(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff) { + (void)pUserData; + if (minDistance >= maxDistance) { return 1; /* To avoid division by zero. Do not attenuate. */ } @@ -51683,8 +60097,10 @@ static float ma_attenuation_linear(float distance, float minDistance, float maxD return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance); } -static float ma_attenuation_exponential(float distance, float minDistance, float maxDistance, float rolloff) +MA_API float ma_attenuation_model_exponential(void* pUserData, float distance, float minDistance, float maxDistance, float rolloff) { + (void)pUserData; + if (minDistance >= maxDistance) { return 1; /* To avoid division by zero. Do not attenuate. */ } @@ -51693,6 +60109,8 @@ static float ma_attenuation_exponential(float distance, float minDistance, float } + + /* Doppler Effect calculation taken from the OpenAL spec, with two main differences: @@ -52203,6 +60621,7 @@ MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* p pSpatializer->channelsIn = pConfig->channelsIn; pSpatializer->channelsOut = pConfig->channelsOut; pSpatializer->attenuationModel = pConfig->attenuationModel; + pSpatializer->pAttenuationUserData = pConfig->pAttenuationUserData; pSpatializer->positioning = pConfig->positioning; pSpatializer->handedness = pConfig->handedness; pSpatializer->minGain = pConfig->minGain; @@ -52348,6 +60767,7 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, pChannelMapIn = pSpatializer->pChannelMapIn; pChannelMapOut = pListener->config.pChannelMapOut; + #if 0 /* If we're not spatializing we need to run an optimized path. */ if (ma_atomic_load_i32(&pSpatializer->attenuationModel) == ma_attenuation_model_none) { if (ma_spatializer_listener_is_enabled(pListener)) { @@ -52367,7 +60787,9 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, the correct thinking so might need to review this later. */ pSpatializer->dopplerPitch = 1; - } else { + } else + #endif + { /* Let's first determine which listener the sound is closest to. Need to keep in mind that we might not have a world or any listeners, in which case we just spatializer based on the @@ -52410,24 +60832,10 @@ MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, distance = ma_vec3f_len(relativePos); /* We've gathered the data, so now we can apply some spatialization. */ - switch (ma_spatializer_get_attenuation_model(pSpatializer)) { - case ma_attenuation_model_inverse: - { - gain = ma_attenuation_inverse(distance, minDistance, maxDistance, rolloff); - } break; - case ma_attenuation_model_linear: - { - gain = ma_attenuation_linear(distance, minDistance, maxDistance, rolloff); - } break; - case ma_attenuation_model_exponential: - { - gain = ma_attenuation_exponential(distance, minDistance, maxDistance, rolloff); - } break; - case ma_attenuation_model_none: - default: - { - gain = 1; - } break; + if (pSpatializer->attenuationModel != NULL) { + gain = pSpatializer->attenuationModel(pSpatializer->pAttenuationUserData, distance, minDistance, maxDistance, rolloff); + } else { + gain = ma_attenuation_model_none(NULL, distance, minDistance, maxDistance, rolloff); } /* Normalize the position. */ @@ -52678,13 +61086,14 @@ MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpati return pSpatializer->channelsOut; } -MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel) +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel, void* pAttenuationUserData) { if (pSpatializer == NULL) { return; } - ma_atomic_exchange_i32(&pSpatializer->attenuationModel, attenuationModel); + pSpatializer->attenuationModel = attenuationModel; + pSpatializer->pAttenuationUserData = pAttenuationUserData; } MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer) @@ -52693,7 +61102,7 @@ MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatia return ma_attenuation_model_none; } - return (ma_attenuation_model)ma_atomic_load_i32(&pSpatializer->attenuationModel); + return pSpatializer->attenuationModel; } MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning) @@ -53057,300 +61466,1371 @@ typedef struct } ma_linear_resampler_heap_layout; -static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* pResampler, ma_uint32 oldSampleRateOut, ma_uint32 newSampleRateOut) -{ - /* - So what's happening here? Basically we need to adjust the fractional component of the time advance based on the new rate. The old time advance will - be based on the old sample rate, but we are needing to adjust it to that it's based on the new sample rate. - */ - ma_uint32 oldRateTimeWhole = pResampler->inTimeFrac / oldSampleRateOut; /* <-- This should almost never be anything other than 0, but leaving it here to make this more general and robust just in case. */ - ma_uint32 oldRateTimeFract = pResampler->inTimeFrac % oldSampleRateOut; +static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* pResampler, ma_uint32 oldSampleRateOut, ma_uint32 newSampleRateOut) +{ + /* + So what's happening here? Basically we need to adjust the fractional component of the time advance based on the new rate. The old time advance will + be based on the old sample rate, but we are needing to adjust it to that it's based on the new sample rate. + */ + ma_uint32 oldRateTimeWhole = pResampler->inTimeFrac / oldSampleRateOut; /* <-- This should almost never be anything other than 0, but leaving it here to make this more general and robust just in case. */ + ma_uint32 oldRateTimeFract = pResampler->inTimeFrac % oldSampleRateOut; + + pResampler->inTimeFrac = + (oldRateTimeWhole * newSampleRateOut) + + ((oldRateTimeFract * newSampleRateOut) / oldSampleRateOut); + + /* Make sure the fractional part is less than the output sample rate. */ + pResampler->inTimeInt += pResampler->inTimeFrac / newSampleRateOut; + pResampler->inTimeFrac = pResampler->inTimeFrac % newSampleRateOut; +} + +/* A cache of samples unrelated to the LPF comes first and needs to be skipped. */ +#define MA_LINEAR_RESAMPLER_GET_LPF_STATE_F32(pResampler, lpfIndex) pResampler->lpf.f32 + (lpfIndex * (4 + (pResampler->channels*2))) +#define MA_LINEAR_RESAMPLER_GET_LPF_STATE_S32(pResampler, lpfIndex) pResampler->lpf.s32 + (lpfIndex * (4 + (pResampler->channels*2))) + +static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) +{ + ma_uint32 gcf; + ma_uint32 lpfSampleRate; + double lpfCutoffFrequency; + ma_uint32 oldSampleRateOut; /* Required for adjusting time advance down the bottom. */ + ma_uint32 minSampleRate; + ma_uint32 maxSampleRate; + ma_uint32 lpfCount; + ma_uint32 iLPF; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (sampleRateIn == 0 || sampleRateOut == 0) { + return MA_INVALID_ARGS; + } + + oldSampleRateOut = pResampler->sampleRateOut; + + pResampler->sampleRateIn = sampleRateIn; + pResampler->sampleRateOut = sampleRateOut; + + /* Simplify the sample rate. */ + gcf = ma_gcf_u32(pResampler->sampleRateIn, pResampler->sampleRateOut); + pResampler->sampleRateIn /= gcf; + pResampler->sampleRateOut /= gcf; + + /* LPF. */ + minSampleRate = ma_min(pResampler->sampleRateIn, pResampler->sampleRateOut); + maxSampleRate = ma_max(pResampler->sampleRateIn, pResampler->sampleRateOut); + + lpfSampleRate = maxSampleRate; + lpfCutoffFrequency = minSampleRate * 0.5 * pResampler->lpfNyquistFactor; + + /* + When the input and output sample rates are the same, there's an edge case with the way the filter works + where we could have a singularity due to `sin(2*pi * cutoff/rate) = sin(pi) = 0`. I'm going to apply + a small clamp in an attempt to avoid hitting this case. + */ + lpfCutoffFrequency = ma_min(lpfCutoffFrequency, 0.5 * minSampleRate * (1.0 - 1e-5)); + lpfCutoffFrequency = ma_max(lpfCutoffFrequency, minSampleRate * 1e-5); + + /* We now need to update our LPF parameters. */ + lpfCount = pResampler->lpfOrder / 2; + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + /* + For our Q value, it's very temping to just use 0.707107 but that won't actually result in a true + Butterworth filter when the order is > 2. Instead it needs to be distributed. + */ + double q = 1 / (2*ma_cosd((1 + iLPF*2) * (MA_PI_D/(pResampler->lpfOrder*2)))); /* <-- This is just distributing 0.707107 over each of our cascading filters. */ + double w = 2 * MA_PI_D * lpfCutoffFrequency / lpfSampleRate; + double s = ma_sind(w); + double c = ma_cosd(w); + double a = s / (2*q); + double b1 = 1 - c; + double b2 = (1 - c) / 2; + double a0 = 1 + a; /* Only used for normalizing below. */ + double a1 = -2 * c; + double a2 = 1 - a; + + /* Biquad parameters need to be normalized. */ + b1 /= a0; + b2 /= a0; + a1 /= a0; + a2 /= a0; + + if (pResampler->format == ma_format_f32) { + float* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_F32(pResampler, iLPF); + + /* We can now initialize our biquad parameters. */ + pLPF[0] = (float)b1; + pLPF[1] = (float)b2; + pLPF[2] = (float)a1; + pLPF[3] = (float)a2; + + /* + For safety, make sure the registers are cleared if this is being called because the resampler + is being initialized fresh. If it's already been initialized, we must not clear out the LPF + state or else we'll get glitching. We want to have smooth transitions between rate changes. + */ + if (!isResamplerAlreadyInitialized) { + MA_ZERO_MEMORY(pLPF + 4, sizeof(float) * pResampler->channels * 2); + } + } else { + ma_int32* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_S32(pResampler, iLPF); + + pLPF[0] = ma_biquad_float_to_fp(b1); + pLPF[1] = ma_biquad_float_to_fp(b2); + pLPF[2] = ma_biquad_float_to_fp(a1); + pLPF[3] = ma_biquad_float_to_fp(a2); + + if (!isResamplerAlreadyInitialized) { + MA_ZERO_MEMORY(pLPF + 4, sizeof(ma_int32) * pResampler->channels * 2); + } + } + } + + pResampler->inAdvanceInt = pResampler->sampleRateIn / pResampler->sampleRateOut; + pResampler->inAdvanceFrac = pResampler->sampleRateIn % pResampler->sampleRateOut; + + /* Our timer was based on the old rate. We need to adjust it so that it's based on the new rate. */ + ma_linear_resampler_adjust_timer_for_new_rate(pResampler, oldSampleRateOut, pResampler->sampleRateOut); + + return MA_SUCCESS; +} + +static ma_result ma_linear_resampler_get_heap_layout(const ma_linear_resampler_config* pConfig, ma_linear_resampler_heap_layout* pHeapLayout) +{ + ma_uint32 lpfOrder; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + lpfOrder = pConfig->lpfOrder; + if ((lpfOrder & 0x1) != 0) { + lpfOrder += 1; /* Round up to even. */ + } + + pHeapLayout->sizeInBytes = 0; + + /* x0 */ + pHeapLayout->x0Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + /* x1 */ + pHeapLayout->x1Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + + /* LPF. */ + pHeapLayout->lpfOffset = pHeapLayout->sizeInBytes; + { + pHeapLayout->sizeInBytes += sizeof(ma_int32) * ((lpfOrder / 2) * (4 + (pConfig->channels * 2))); + } + + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_linear_resampler_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler) +{ + ma_result result; + ma_linear_resampler_heap_layout heapLayout; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pResampler); + + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pResampler->format = pConfig->format; + pResampler->channels = pConfig->channels; + pResampler->sampleRateIn = pConfig->sampleRateIn; + pResampler->sampleRateOut = pConfig->sampleRateOut; + pResampler->lpfOrder = pConfig->lpfOrder; + pResampler->lpfNyquistFactor = pConfig->lpfNyquistFactor; + + /* The order needs to be even. */ + if ((pResampler->lpfOrder & 0x1) != 0) { + pResampler->lpfOrder += 1; /* Round up to even. */ + } + + /* It does not make sense for the Nyquist factor to go beyond 1. */ + if (pResampler->lpfNyquistFactor > 1) { + pResampler->lpfNyquistFactor = 0; + } + + pResampler->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + if (pConfig->format == ma_format_f32) { + pResampler->x0.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } else { + pResampler->x0.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } + + pResampler->lpf.s32 = (ma_int32*)ma_offset_ptr(pHeap, heapLayout.lpfOffset); + + /* Setting the rate will set up the filter and time advances for us. */ + result = ma_linear_resampler_set_rate_internal(pResampler, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); + if (result != MA_SUCCESS) { + return result; + } + + if (pResampler->lpfOrder == 0) { + pResampler->inTimeInt = 0; + } else { + pResampler->inTimeInt = 1; /* Set this to one to force an input sample to always be loaded for the first output frame. */ + } + + pResampler->inTimeFrac = 0; + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_linear_resampler_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_linear_resampler_init_preallocated(pConfig, pHeap, pResampler); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pResampler->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pResampler == NULL) { + return; + } + + if (pResampler->_ownsHeap) { + ma_free(pResampler->_pHeap, pAllocationCallbacks); + } +} + +#define MA_LINEAR_RESAMPLER_LERP_SHIFT MA_BIQUAD_FIXED_POINT_SHIFT + +static MA_INLINE ma_int16 ma_linear_resampler_mix_s16(ma_int16 x, ma_int16 y, ma_uint32 a) +{ + ma_int32 d; + ma_int32 n; + + d = y - x; + n = d * a; + return (ma_int16)(x + (n >> MA_LINEAR_RESAMPLER_LERP_SHIFT)); +} + +static MA_INLINE ma_result ma_linear_resampler_process_pcm_frames_s16_no_lpf(ma_linear_resampler* pResampler, const ma_int16* pFramesInS16, ma_uint64* pFrameCountIn, ma_int16* pFramesOutS16, ma_uint64* pFrameCountOut, ma_uint32 invSampleRateOut) +{ + ma_uint64 frameCountIn; + ma_uint64 frameCountOut; + ma_uint64 framesProcessedIn; + ma_uint64 framesProcessedOut; + ma_uint32 channels; + ma_uint32 c; + + MA_ASSERT(pResampler != NULL); + MA_ASSERT(pFramesInS16 != NULL); + MA_ASSERT(pFrameCountIn != NULL); + MA_ASSERT(pFramesOutS16 != NULL); + MA_ASSERT(pFrameCountOut != NULL); + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + framesProcessedIn = 0; + framesProcessedOut = 0; + channels = pResampler->channels; + + /* If there's a cached frame we need to process it. */ + if (pResampler->inTimeInt == 0) { + MA_ASSERT(pResampler->cachedFrameCount <= 1); /* There is at most one cached frame. */ + + while (pResampler->cachedFrameCount > 0 && frameCountIn > 0 && framesProcessedOut < frameCountOut) { + ma_uint32 a = pResampler->inTimeFrac * invSampleRateOut; + + for (c = 0; c < channels; c += 1) { + pFramesOutS16[c] = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pFramesInS16[c], a); + } + pFramesOutS16 += channels; + + framesProcessedOut += 1; + + /* Advance time forward. */ + pResampler->inTimeInt += pResampler->inAdvanceInt; + pResampler->inTimeFrac += pResampler->inAdvanceFrac; + if (pResampler->inTimeFrac >= pResampler->sampleRateOut) { + pResampler->inTimeFrac -= pResampler->sampleRateOut; + pResampler->inTimeInt += 1; + } + + /* Subtract one from the time to account for the cached frame, but only if the entire frame was processed. */ + if (pResampler->inTimeInt > 0) { + pResampler->inTimeInt -= 1; + pResampler->cachedFrameCount = 0; + } + } + } else { + /* The rate must have changed between calls. Ignore the cached frame. */ + } + + /* Experimental loop unrolling to make it easier for SIMD-ification. */ + #if 1 + { + if (channels == 1) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt[4]; + ma_uint32 inTimeFrac[4]; + ma_int32 x[4]; + ma_int32 y[4]; + ma_int32 a[4]; + ma_int32 d[4]; + ma_int32 n[4]; + ma_int32 r[4]; + int i; + + inTimeIntTemp = pResampler->inTimeInt; + inTimeFracTemp = pResampler->inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt[i] = inTimeIntTemp; + inTimeFrac[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + pResampler->inTimeInt = inTimeIntTemp; + pResampler->inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInS16[inTimeInt[0] + 0]; + x[1] = pFramesInS16[inTimeInt[1] + 0]; + x[2] = pFramesInS16[inTimeInt[2] + 0]; + x[3] = pFramesInS16[inTimeInt[3] + 0]; + + y[0] = pFramesInS16[inTimeInt[0] + 1]; + y[1] = pFramesInS16[inTimeInt[1] + 1]; + y[2] = pFramesInS16[inTimeInt[2] + 1]; + y[3] = pFramesInS16[inTimeInt[3] + 1]; + + a[0] = inTimeFrac[0] * invSampleRateOut; + a[1] = inTimeFrac[1] * invSampleRateOut; + a[2] = inTimeFrac[2] * invSampleRateOut; + a[3] = inTimeFrac[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + + r[0] = x[0] + (n[0] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[1] = x[1] + (n[1] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[2] = x[2] + (n[2] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[3] = x[3] + (n[3] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + + pFramesOutS16[0] = r[0]; + pFramesOutS16[1] = r[1]; + pFramesOutS16[2] = r[2]; + pFramesOutS16[3] = r[3]; + + pFramesOutS16 += 4; + framesProcessedOut += 4; + } + } else if (channels == 2) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt[4]; + ma_uint32 inTimeFrac[4]; + ma_int32 x[8]; + ma_int32 y[8]; + ma_int32 a[8]; + ma_int32 d[8]; + ma_int32 n[8]; + ma_int32 r[8]; + int i; + + inTimeIntTemp = pResampler->inTimeInt; + inTimeFracTemp = pResampler->inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt[i] = inTimeIntTemp; + inTimeFrac[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + pResampler->inTimeInt = inTimeIntTemp; + pResampler->inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInS16[((inTimeInt[0] + 0) * 2) + 0]; + x[1] = pFramesInS16[((inTimeInt[0] + 0) * 2) + 1]; + x[2] = pFramesInS16[((inTimeInt[1] + 0) * 2) + 0]; + x[3] = pFramesInS16[((inTimeInt[1] + 0) * 2) + 1]; + x[4] = pFramesInS16[((inTimeInt[2] + 0) * 2) + 0]; + x[5] = pFramesInS16[((inTimeInt[2] + 0) * 2) + 1]; + x[6] = pFramesInS16[((inTimeInt[3] + 0) * 2) + 0]; + x[7] = pFramesInS16[((inTimeInt[3] + 0) * 2) + 1]; + + y[0] = pFramesInS16[((inTimeInt[0] + 1) * 2) + 0]; + y[1] = pFramesInS16[((inTimeInt[0] + 1) * 2) + 1]; + y[2] = pFramesInS16[((inTimeInt[1] + 1) * 2) + 0]; + y[3] = pFramesInS16[((inTimeInt[1] + 1) * 2) + 1]; + y[4] = pFramesInS16[((inTimeInt[2] + 1) * 2) + 0]; + y[5] = pFramesInS16[((inTimeInt[2] + 1) * 2) + 1]; + y[6] = pFramesInS16[((inTimeInt[3] + 1) * 2) + 0]; + y[7] = pFramesInS16[((inTimeInt[3] + 1) * 2) + 1]; + + a[0] = inTimeFrac[0] * invSampleRateOut; + a[1] = inTimeFrac[0] * invSampleRateOut; + a[2] = inTimeFrac[1] * invSampleRateOut; + a[3] = inTimeFrac[1] * invSampleRateOut; + a[4] = inTimeFrac[2] * invSampleRateOut; + a[5] = inTimeFrac[2] * invSampleRateOut; + a[6] = inTimeFrac[3] * invSampleRateOut; + a[7] = inTimeFrac[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + d[4] = y[4] - x[4]; + d[5] = y[5] - x[5]; + d[6] = y[6] - x[6]; + d[7] = y[7] - x[7]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + n[4] = d[4] * a[4]; + n[5] = d[5] * a[5]; + n[6] = d[6] * a[6]; + n[7] = d[7] * a[7]; + + r[0] = x[0] + (n[0] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[1] = x[1] + (n[1] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[2] = x[2] + (n[2] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[3] = x[3] + (n[3] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[4] = x[4] + (n[4] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[5] = x[5] + (n[5] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[6] = x[6] + (n[6] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[7] = x[7] + (n[7] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + + pFramesOutS16[0] = r[0]; + pFramesOutS16[1] = r[1]; + pFramesOutS16[2] = r[2]; + pFramesOutS16[3] = r[3]; + pFramesOutS16[4] = r[4]; + pFramesOutS16[5] = r[5]; + pFramesOutS16[6] = r[6]; + pFramesOutS16[7] = r[7]; + + pFramesOutS16 += 8; + framesProcessedOut += 4; + } + } else { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt[4]; + ma_uint32 inTimeFrac[4]; + int i; + + inTimeIntTemp = pResampler->inTimeInt; + inTimeFracTemp = pResampler->inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt[i] = inTimeIntTemp; + inTimeFrac[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + pResampler->inTimeInt = inTimeIntTemp; + pResampler->inTimeFrac = inTimeFracTemp; + + for (c = 0; c < channels; c += 1) { + ma_int32 x[4]; + ma_int32 y[4]; + ma_int32 a[4]; + ma_int32 d[4]; + ma_int32 n[4]; + ma_int32 r[4]; + + x[0] = pFramesInS16[((inTimeInt[0] + 0) * channels) + c]; + x[1] = pFramesInS16[((inTimeInt[1] + 0) * channels) + c]; + x[2] = pFramesInS16[((inTimeInt[2] + 0) * channels) + c]; + x[3] = pFramesInS16[((inTimeInt[3] + 0) * channels) + c]; + + y[0] = pFramesInS16[((inTimeInt[0] + 1) * channels) + c]; + y[1] = pFramesInS16[((inTimeInt[1] + 1) * channels) + c]; + y[2] = pFramesInS16[((inTimeInt[2] + 1) * channels) + c]; + y[3] = pFramesInS16[((inTimeInt[3] + 1) * channels) + c]; + + a[0] = inTimeFrac[0] * invSampleRateOut; + a[1] = inTimeFrac[1] * invSampleRateOut; + a[2] = inTimeFrac[2] * invSampleRateOut; + a[3] = inTimeFrac[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + + r[0] = x[0] + (n[0] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[1] = x[1] + (n[1] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[2] = x[2] + (n[2] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[3] = x[3] + (n[3] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + + pFramesOutS16[(0 * channels) + c] = r[0]; + pFramesOutS16[(1 * channels) + c] = r[1]; + pFramesOutS16[(2 * channels) + c] = r[2]; + pFramesOutS16[(3 * channels) + c] = r[3]; + } + + pFramesOutS16 += 4 * channels; + framesProcessedOut += 4; + } + } + } + #endif + + while (framesProcessedOut < frameCountOut && pResampler->inTimeInt < frameCountIn) { + if (pResampler->inTimeInt + 1 < frameCountIn) { + ma_uint32 a = pResampler->inTimeFrac * invSampleRateOut; + + for (c = 0; c < channels; c += 1) { + pFramesOutS16[c] = ma_linear_resampler_mix_s16(pFramesInS16[(pResampler->inTimeInt * channels) + c], pFramesInS16[((pResampler->inTimeInt + 1) * channels) + c], a); + } + pFramesOutS16 += channels; + + framesProcessedOut += 1; + + /* Advance time forward. */ + pResampler->inTimeInt += pResampler->inAdvanceInt; + pResampler->inTimeFrac += pResampler->inAdvanceFrac; + if (pResampler->inTimeFrac >= pResampler->sampleRateOut) { + pResampler->inTimeFrac -= pResampler->sampleRateOut; + pResampler->inTimeInt += 1; + } + } else { + /* + There is not enough input frames to interpolate. We'll need to stop here. But it's important that we cache + the frame to ensure we make some forward progress. + */ + for (c = 0; c < channels; c += 1) { + pResampler->x0.s16[c] = pFramesInS16[(pResampler->inTimeInt * channels) + c]; + } + + pResampler->cachedFrameCount = 1; + pResampler->inTimeInt += 1; + + break; + } + } - pResampler->inTimeFrac = - (oldRateTimeWhole * newSampleRateOut) + - ((oldRateTimeFract * newSampleRateOut) / oldSampleRateOut); + /* The number of frames we processed is simply the difference between our current time and previous time, clamped. */ + framesProcessedIn = pResampler->inTimeInt; + if (framesProcessedIn > frameCountIn) { /* Should never overshoot when upsampling. Downsampling could overshoot. */ + framesProcessedIn = frameCountIn; + } - /* Make sure the fractional part is less than the output sample rate. */ - pResampler->inTimeInt += pResampler->inTimeFrac / pResampler->config.sampleRateOut; - pResampler->inTimeFrac = pResampler->inTimeFrac % pResampler->config.sampleRateOut; + if (pResampler->inTimeInt >= (ma_uint32)framesProcessedIn) { + pResampler->inTimeInt -= (ma_uint32)framesProcessedIn; + } else { + pResampler->inTimeInt = 0; + } + + *pFrameCountIn = framesProcessedIn; + *pFrameCountOut = framesProcessedOut; + + return MA_SUCCESS; } -static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, void* pHeap, ma_linear_resampler_heap_layout* pHeapLayout, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) +static MA_INLINE ma_result ma_linear_resampler_process_pcm_frames_f32_no_lpf(ma_linear_resampler* pResampler, const float* pFramesInF32, ma_uint64* pFrameCountIn, float* pFramesOutF32, ma_uint64* pFrameCountOut, float invSampleRateOut) { - ma_result result; - ma_uint32 gcf; - ma_uint32 lpfSampleRate; - double lpfCutoffFrequency; - ma_lpf_config lpfConfig; - ma_uint32 oldSampleRateOut; /* Required for adjusting time advance down the bottom. */ - - if (pResampler == NULL) { - return MA_INVALID_ARGS; - } + ma_uint64 frameCountIn; + ma_uint64 frameCountOut; + ma_uint64 framesProcessedIn; + ma_uint64 framesProcessedOut; + ma_uint32 channels; + ma_uint32 c; - if (sampleRateIn == 0 || sampleRateOut == 0) { - return MA_INVALID_ARGS; - } + MA_ASSERT(pResampler != NULL); + MA_ASSERT(pFramesInF32 != NULL); + MA_ASSERT(pFrameCountIn != NULL); + MA_ASSERT(pFramesOutF32 != NULL); + MA_ASSERT(pFrameCountOut != NULL); - oldSampleRateOut = pResampler->config.sampleRateOut; + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + framesProcessedIn = 0; + framesProcessedOut = 0; + channels = pResampler->channels; - pResampler->config.sampleRateIn = sampleRateIn; - pResampler->config.sampleRateOut = sampleRateOut; + /* If there's a cached frame we need to process it. */ + if (pResampler->inTimeInt == 0) { + MA_ASSERT(pResampler->cachedFrameCount <= 1); /* There is at most one cached frame. */ - /* Simplify the sample rate. */ - gcf = ma_gcf_u32(pResampler->config.sampleRateIn, pResampler->config.sampleRateOut); - pResampler->config.sampleRateIn /= gcf; - pResampler->config.sampleRateOut /= gcf; + while (pResampler->cachedFrameCount > 0 && frameCountIn > 0 && framesProcessedOut < frameCountOut) { + float a = pResampler->inTimeFrac * invSampleRateOut; - /* Always initialize the low-pass filter, even when the order is 0. */ - if (pResampler->config.lpfOrder > MA_MAX_FILTER_ORDER) { - return MA_INVALID_ARGS; - } + for (c = 0; c < channels; c += 1) { + pFramesOutF32[c] = ma_mix_f32_fast(pResampler->x0.f32[c], pFramesInF32[c], a); + } + pFramesOutF32 += channels; - lpfSampleRate = (ma_uint32)(ma_max(pResampler->config.sampleRateIn, pResampler->config.sampleRateOut)); - lpfCutoffFrequency = ( double)(ma_min(pResampler->config.sampleRateIn, pResampler->config.sampleRateOut) * 0.5 * pResampler->config.lpfNyquistFactor); + framesProcessedOut += 1; - lpfConfig = ma_lpf_config_init(pResampler->config.format, pResampler->config.channels, lpfSampleRate, lpfCutoffFrequency, pResampler->config.lpfOrder); + /* Advance time forward. */ + pResampler->inTimeInt += pResampler->inAdvanceInt; + pResampler->inTimeFrac += pResampler->inAdvanceFrac; + if (pResampler->inTimeFrac >= pResampler->sampleRateOut) { + pResampler->inTimeFrac -= pResampler->sampleRateOut; + pResampler->inTimeInt += 1; + } - /* - If the resampler is already initialized we don't want to do a fresh initialization of the low-pass filter because it will result in the cached frames - getting cleared. Instead we re-initialize the filter which will maintain any cached frames. - */ - if (isResamplerAlreadyInitialized) { - result = ma_lpf_reinit(&lpfConfig, &pResampler->lpf); + /* Subtract one from the time to account for the cached frame, but only if the entire frame was processed. */ + if (pResampler->inTimeInt > 0) { + pResampler->inTimeInt -= 1; + pResampler->cachedFrameCount = 0; + } + } } else { - result = ma_lpf_init_preallocated(&lpfConfig, ma_offset_ptr(pHeap, pHeapLayout->lpfOffset), &pResampler->lpf); + /* The rate must have changed between calls. Ignore the cached frame. */ } - if (result != MA_SUCCESS) { - return result; - } + /* Experimental loop unrolling to make it easier for SIMD-ification. */ + #if 1 + { + if (channels == 1) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt[4]; + ma_uint32 inTimeFrac[4]; + float x[4]; + float y[4]; + float a[4]; + float d[4]; + float n[4]; + float r[4]; + int i; + + inTimeIntTemp = pResampler->inTimeInt; + inTimeFracTemp = pResampler->inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt[i] = inTimeIntTemp; + inTimeFrac[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + pResampler->inTimeInt = inTimeIntTemp; + pResampler->inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInF32[inTimeInt[0] + 0]; + x[1] = pFramesInF32[inTimeInt[1] + 0]; + x[2] = pFramesInF32[inTimeInt[2] + 0]; + x[3] = pFramesInF32[inTimeInt[3] + 0]; + + y[0] = pFramesInF32[inTimeInt[0] + 1]; + y[1] = pFramesInF32[inTimeInt[1] + 1]; + y[2] = pFramesInF32[inTimeInt[2] + 1]; + y[3] = pFramesInF32[inTimeInt[3] + 1]; + + a[0] = inTimeFrac[0] * invSampleRateOut; + a[1] = inTimeFrac[1] * invSampleRateOut; + a[2] = inTimeFrac[2] * invSampleRateOut; + a[3] = inTimeFrac[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + + r[0] = x[0] + n[0]; + r[1] = x[1] + n[1]; + r[2] = x[2] + n[2]; + r[3] = x[3] + n[3]; + + pFramesOutF32[0] = r[0]; + pFramesOutF32[1] = r[1]; + pFramesOutF32[2] = r[2]; + pFramesOutF32[3] = r[3]; + + pFramesOutF32 += 4; + framesProcessedOut += 4; + } + } else if (channels == 2) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt[4]; + ma_uint32 inTimeFrac[4]; + float x[8]; + float y[8]; + float a[8]; + float d[8]; + float n[8]; + float r[8]; + int i; + + inTimeIntTemp = pResampler->inTimeInt; + inTimeFracTemp = pResampler->inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt[i] = inTimeIntTemp; + inTimeFrac[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } - pResampler->inAdvanceInt = pResampler->config.sampleRateIn / pResampler->config.sampleRateOut; - pResampler->inAdvanceFrac = pResampler->config.sampleRateIn % pResampler->config.sampleRateOut; + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + pResampler->inTimeInt = inTimeIntTemp; + pResampler->inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInF32[((inTimeInt[0] + 0) * 2) + 0]; + x[1] = pFramesInF32[((inTimeInt[0] + 0) * 2) + 1]; + x[2] = pFramesInF32[((inTimeInt[1] + 0) * 2) + 0]; + x[3] = pFramesInF32[((inTimeInt[1] + 0) * 2) + 1]; + x[4] = pFramesInF32[((inTimeInt[2] + 0) * 2) + 0]; + x[5] = pFramesInF32[((inTimeInt[2] + 0) * 2) + 1]; + x[6] = pFramesInF32[((inTimeInt[3] + 0) * 2) + 0]; + x[7] = pFramesInF32[((inTimeInt[3] + 0) * 2) + 1]; + + y[0] = pFramesInF32[((inTimeInt[0] + 1) * 2) + 0]; + y[1] = pFramesInF32[((inTimeInt[0] + 1) * 2) + 1]; + y[2] = pFramesInF32[((inTimeInt[1] + 1) * 2) + 0]; + y[3] = pFramesInF32[((inTimeInt[1] + 1) * 2) + 1]; + y[4] = pFramesInF32[((inTimeInt[2] + 1) * 2) + 0]; + y[5] = pFramesInF32[((inTimeInt[2] + 1) * 2) + 1]; + y[6] = pFramesInF32[((inTimeInt[3] + 1) * 2) + 0]; + y[7] = pFramesInF32[((inTimeInt[3] + 1) * 2) + 1]; + + a[0] = inTimeFrac[0] * invSampleRateOut; + a[1] = inTimeFrac[0] * invSampleRateOut; + a[2] = inTimeFrac[1] * invSampleRateOut; + a[3] = inTimeFrac[1] * invSampleRateOut; + a[4] = inTimeFrac[2] * invSampleRateOut; + a[5] = inTimeFrac[2] * invSampleRateOut; + a[6] = inTimeFrac[3] * invSampleRateOut; + a[7] = inTimeFrac[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + d[4] = y[4] - x[4]; + d[5] = y[5] - x[5]; + d[6] = y[6] - x[6]; + d[7] = y[7] - x[7]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + n[4] = d[4] * a[4]; + n[5] = d[5] * a[5]; + n[6] = d[6] * a[6]; + n[7] = d[7] * a[7]; + + r[0] = x[0] + n[0]; + r[1] = x[1] + n[1]; + r[2] = x[2] + n[2]; + r[3] = x[3] + n[3]; + r[4] = x[4] + n[4]; + r[5] = x[5] + n[5]; + r[6] = x[6] + n[6]; + r[7] = x[7] + n[7]; + + pFramesOutF32[0] = r[0]; + pFramesOutF32[1] = r[1]; + pFramesOutF32[2] = r[2]; + pFramesOutF32[3] = r[3]; + pFramesOutF32[4] = r[4]; + pFramesOutF32[5] = r[5]; + pFramesOutF32[6] = r[6]; + pFramesOutF32[7] = r[7]; + + pFramesOutF32 += 8; + framesProcessedOut += 4; + } + } else { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt[4]; + ma_uint32 inTimeFrac[4]; + int i; + + inTimeIntTemp = pResampler->inTimeInt; + inTimeFracTemp = pResampler->inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt[i] = inTimeIntTemp; + inTimeFrac[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } - /* Our timer was based on the old rate. We need to adjust it so that it's based on the new rate. */ - ma_linear_resampler_adjust_timer_for_new_rate(pResampler, oldSampleRateOut, pResampler->config.sampleRateOut); + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } - return MA_SUCCESS; -} + /* Advance the timer. */ + pResampler->inTimeInt = inTimeIntTemp; + pResampler->inTimeFrac = inTimeFracTemp; -static ma_result ma_linear_resampler_get_heap_layout(const ma_linear_resampler_config* pConfig, ma_linear_resampler_heap_layout* pHeapLayout) -{ - MA_ASSERT(pHeapLayout != NULL); + for (c = 0; c < channels; c += 1) { + float x[4]; + float y[4]; + float a[4]; + float d[4]; + float n[4]; + float r[4]; - MA_ZERO_OBJECT(pHeapLayout); + x[0] = pFramesInF32[((inTimeInt[0] + 0) * channels) + c]; + x[1] = pFramesInF32[((inTimeInt[1] + 0) * channels) + c]; + x[2] = pFramesInF32[((inTimeInt[2] + 0) * channels) + c]; + x[3] = pFramesInF32[((inTimeInt[3] + 0) * channels) + c]; - if (pConfig == NULL) { - return MA_INVALID_ARGS; - } + y[0] = pFramesInF32[((inTimeInt[0] + 1) * channels) + c]; + y[1] = pFramesInF32[((inTimeInt[1] + 1) * channels) + c]; + y[2] = pFramesInF32[((inTimeInt[2] + 1) * channels) + c]; + y[3] = pFramesInF32[((inTimeInt[3] + 1) * channels) + c]; - if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { - return MA_INVALID_ARGS; - } + a[0] = inTimeFrac[0] * invSampleRateOut; + a[1] = inTimeFrac[1] * invSampleRateOut; + a[2] = inTimeFrac[2] * invSampleRateOut; + a[3] = inTimeFrac[3] * invSampleRateOut; - if (pConfig->channels == 0) { - return MA_INVALID_ARGS; - } + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; - pHeapLayout->sizeInBytes = 0; + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; - /* x0 */ - pHeapLayout->x0Offset = pHeapLayout->sizeInBytes; - if (pConfig->format == ma_format_f32) { - pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; - } else { - pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; - } + r[0] = x[0] + n[0]; + r[1] = x[1] + n[1]; + r[2] = x[2] + n[2]; + r[3] = x[3] + n[3]; - /* x1 */ - pHeapLayout->x1Offset = pHeapLayout->sizeInBytes; - if (pConfig->format == ma_format_f32) { - pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; - } else { - pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + pFramesOutF32[(0 * channels) + c] = r[0]; + pFramesOutF32[(1 * channels) + c] = r[1]; + pFramesOutF32[(2 * channels) + c] = r[2]; + pFramesOutF32[(3 * channels) + c] = r[3]; + } + + pFramesOutF32 += 4 * channels; + framesProcessedOut += 4; + } + } } + #endif - /* LPF */ - pHeapLayout->lpfOffset = ma_align_64(pHeapLayout->sizeInBytes); - { - ma_result result; - size_t lpfHeapSizeInBytes; - ma_lpf_config lpfConfig = ma_lpf_config_init(pConfig->format, pConfig->channels, 1, 1, pConfig->lpfOrder); /* Sample rate and cutoff frequency do not matter. */ + while (framesProcessedOut < frameCountOut && pResampler->inTimeInt < frameCountIn) { + if (pResampler->inTimeInt + 1 < frameCountIn) { + float a = pResampler->inTimeFrac * invSampleRateOut; - result = ma_lpf_get_heap_size(&lpfConfig, &lpfHeapSizeInBytes); - if (result != MA_SUCCESS) { - return result; + for (c = 0; c < channels; c += 1) { + pFramesOutF32[c] = ma_mix_f32_fast(pFramesInF32[((pResampler->inTimeInt + 0) * channels) + c], pFramesInF32[((pResampler->inTimeInt + 1) * channels) + c], a); + } + pFramesOutF32 += channels; + + framesProcessedOut += 1; + + /* Advance time forward. */ + pResampler->inTimeInt += pResampler->inAdvanceInt; + pResampler->inTimeFrac += pResampler->inAdvanceFrac; + if (pResampler->inTimeFrac >= pResampler->sampleRateOut) { + pResampler->inTimeFrac -= pResampler->sampleRateOut; + pResampler->inTimeInt += 1; + } + } else { + /* + There is not enough input frames to interpolate. We'll need to stop here. But it's important that we cache + the frame to ensure we make some forward progress. + */ + for (c = 0; c < channels; c += 1) { + pResampler->x0.f32[c] = pFramesInF32[((pResampler->inTimeInt + 0) * channels) + c]; + } + + pResampler->cachedFrameCount = 1; + pResampler->inTimeInt += 1; + + break; } + } - pHeapLayout->sizeInBytes += lpfHeapSizeInBytes; + /* The number of frames we processed is simply the difference between our current time and previous time, clamped. */ + framesProcessedIn = pResampler->inTimeInt; + if (framesProcessedIn > frameCountIn) { /* Should never overshoot when upsampling. Downsampling could overshoot. */ + framesProcessedIn = frameCountIn; } - /* Make sure allocation size is aligned. */ - pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + if (pResampler->inTimeInt >= (ma_uint32)framesProcessedIn) { + pResampler->inTimeInt -= (ma_uint32)framesProcessedIn; + } else { + pResampler->inTimeInt = 0; + } + + *pFrameCountIn = framesProcessedIn; + *pFrameCountOut = framesProcessedOut; return MA_SUCCESS; } -MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes) + +static MA_INLINE void ma_linear_resampler_filter_s16(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_uint32 channels, ma_int16* MA_RESTRICT pFrame) { - ma_result result; - ma_linear_resampler_heap_layout heapLayout; + ma_uint32 iLPF; + ma_uint32 iChannel; + + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + ma_int32 b1 = pLPF[0]; + ma_int32 b2 = pLPF[1]; + ma_int32 a1 = pLPF[2]; + ma_int32 a2 = pLPF[3]; + ma_int32* pR0 = pLPF + 4; + ma_int32* pR1 = pLPF + 4 + channels; - if (pHeapSizeInBytes == NULL) { - return MA_INVALID_ARGS; - } + for (iChannel = 0; iChannel < channels; iChannel += 1) { + ma_int32 r0 = pR0[iChannel]; + ma_int32 r1 = pR1[iChannel]; + ma_int32 x = pFrame[iChannel]; + ma_int32 y; - *pHeapSizeInBytes = 0; + y = (b2*x + r0) >> MA_BIQUAD_FIXED_POINT_SHIFT; + r0 = (b1*x - a1*y + r1); + r1 = (b2*x - a2*y); - result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); - if (result != MA_SUCCESS) { - return result; + pFrame[iChannel] = (ma_int16)ma_clamp(y, -32768, 32767); + pR0[iChannel] = r0; + pR1[iChannel] = r1; + } + + /* Go do the next LPF state. */ + pLPF += 4 + (channels * 2); } +} - *pHeapSizeInBytes = heapLayout.sizeInBytes; +static MA_INLINE void ma_linear_resampler_filter_s16_4(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_uint32 channels, ma_int16* MA_RESTRICT pFrames) +{ + ma_linear_resampler_filter_s16(pLPF, lpfCount, channels, pFrames + (0 * channels)); + ma_linear_resampler_filter_s16(pLPF, lpfCount, channels, pFrames + (1 * channels)); + ma_linear_resampler_filter_s16(pLPF, lpfCount, channels, pFrames + (2 * channels)); + ma_linear_resampler_filter_s16(pLPF, lpfCount, channels, pFrames + (3 * channels)); +} - return MA_SUCCESS; +static MA_INLINE void ma_linear_resampler_filter_s16_4_stereo(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_int16* MA_RESTRICT pFrames) +{ + ma_linear_resampler_filter_s16(pLPF, lpfCount, 2, pFrames + 0); + ma_linear_resampler_filter_s16(pLPF, lpfCount, 2, pFrames + 2); + ma_linear_resampler_filter_s16(pLPF, lpfCount, 2, pFrames + 4); + ma_linear_resampler_filter_s16(pLPF, lpfCount, 2, pFrames + 6); } -MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler) +static MA_INLINE void ma_linear_resampler_filter_s32(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_uint32 channels, ma_int32* MA_RESTRICT pFrame) { - ma_result result; - ma_linear_resampler_heap_layout heapLayout; + ma_uint32 iLPF; + ma_uint32 iChannel; + + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + ma_int32 b1 = pLPF[0]; + ma_int32 b2 = pLPF[1]; + ma_int32 a1 = pLPF[2]; + ma_int32 a2 = pLPF[3]; + ma_int32* pR0 = pLPF + 4; + ma_int32* pR1 = pLPF + 4 + channels; - if (pResampler == NULL) { - return MA_INVALID_ARGS; - } + for (iChannel = 0; iChannel < channels; iChannel += 1) { + ma_int32 r0 = pR0[iChannel]; + ma_int32 r1 = pR1[iChannel]; + ma_int32 x = pFrame[iChannel]; + ma_int32 y; - MA_ZERO_OBJECT(pResampler); + y = (b2*x + r0) >> MA_BIQUAD_FIXED_POINT_SHIFT; + r0 = (b1*x - a1*y + r1); + r1 = (b2*x - a2*y); - result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); - if (result != MA_SUCCESS) { - return result; - } + pFrame[iChannel] = ma_clamp(y, -32768, 32767); + pR0[iChannel] = r0; + pR1[iChannel] = r1; + } - pResampler->config = *pConfig; + /* Go do the next LPF state. */ + pLPF += 4 + (channels * 2); + } +} - pResampler->_pHeap = pHeap; - MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); +static MA_INLINE void ma_linear_resampler_filter_s32_mono(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_int32* MA_RESTRICT pFrame) +{ + ma_uint32 iLPF; + ma_uint32 iChannel; + + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + ma_int32 b1 = pLPF[0]; + ma_int32 b2 = pLPF[1]; + ma_int32 a1 = pLPF[2]; + ma_int32 a2 = pLPF[3]; + ma_int32* pR0 = pLPF + 4; + ma_int32* pR1 = pLPF + 4 + 1; - if (pConfig->format == ma_format_f32) { - pResampler->x0.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x0Offset); - pResampler->x1.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x1Offset); - } else { - pResampler->x0.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x0Offset); - pResampler->x1.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x1Offset); - } + for (iChannel = 0; iChannel < 1; iChannel += 1) { + ma_int32 r0 = pR0[iChannel]; + ma_int32 r1 = pR1[iChannel]; + ma_int32 x = pFrame[iChannel]; + ma_int32 y; - /* Setting the rate will set up the filter and time advances for us. */ - result = ma_linear_resampler_set_rate_internal(pResampler, pHeap, &heapLayout, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); - if (result != MA_SUCCESS) { - return result; - } + y = (b2*x + r0) >> MA_BIQUAD_FIXED_POINT_SHIFT; + r0 = (b1*x - a1*y + r1); + r1 = (b2*x - a2*y); - pResampler->inTimeInt = 1; /* Set this to one to force an input sample to always be loaded for the first output frame. */ - pResampler->inTimeFrac = 0; + pFrame[iChannel] = ma_clamp(y, -32768, 32767); + pR0[iChannel] = r0; + pR1[iChannel] = r1; + } - return MA_SUCCESS; + /* Go do the next LPF state. */ + pLPF += 4 + (1 * 2); + } } -MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler) +static MA_INLINE void ma_linear_resampler_filter_s32_stereo(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_int32* MA_RESTRICT pFrame) { - ma_result result; - size_t heapSizeInBytes; - void* pHeap; + ma_uint32 iLPF; + ma_uint32 iChannel; + + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + ma_int32 b1 = pLPF[0]; + ma_int32 b2 = pLPF[1]; + ma_int32 a1 = pLPF[2]; + ma_int32 a2 = pLPF[3]; + ma_int32* pR0 = pLPF + 4; + ma_int32* pR1 = pLPF + 4 + 2; - result = ma_linear_resampler_get_heap_size(pConfig, &heapSizeInBytes); - if (result != MA_SUCCESS) { - return result; - } + for (iChannel = 0; iChannel < 2; iChannel += 1) { + ma_int32 r0 = pR0[iChannel]; + ma_int32 r1 = pR1[iChannel]; + ma_int32 x = pFrame[iChannel]; + ma_int32 y; - if (heapSizeInBytes > 0) { - pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); - if (pHeap == NULL) { - return MA_OUT_OF_MEMORY; + y = (b2*x + r0) >> MA_BIQUAD_FIXED_POINT_SHIFT; + r0 = (b1*x - a1*y + r1); + r1 = (b2*x - a2*y); + + pFrame[iChannel] = ma_clamp(y, -32768, 32767); + pR0[iChannel] = r0; + pR1[iChannel] = r1; } - } else { - pHeap = NULL; - } - result = ma_linear_resampler_init_preallocated(pConfig, pHeap, pResampler); - if (result != MA_SUCCESS) { - ma_free(pHeap, pAllocationCallbacks); - return result; + /* Go do the next LPF state. */ + pLPF += 4 + (2 * 2); } - - pResampler->_ownsHeap = MA_TRUE; - return MA_SUCCESS; } -MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) +static MA_INLINE void ma_linear_resampler_filter_s32_4(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_uint32 channels, ma_int32* MA_RESTRICT pFrames) { - if (pResampler == NULL) { - return; - } + ma_linear_resampler_filter_s32(pLPF, lpfCount, channels, pFrames + (0 * channels)); + ma_linear_resampler_filter_s32(pLPF, lpfCount, channels, pFrames + (1 * channels)); + ma_linear_resampler_filter_s32(pLPF, lpfCount, channels, pFrames + (2 * channels)); + ma_linear_resampler_filter_s32(pLPF, lpfCount, channels, pFrames + (3 * channels)); +} - ma_lpf_uninit(&pResampler->lpf, pAllocationCallbacks); +static MA_INLINE void ma_linear_resampler_filter_s32_4_mono(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_int32* MA_RESTRICT pFrames) +{ + ma_linear_resampler_filter_s32_mono(pLPF, lpfCount, pFrames + 0); + ma_linear_resampler_filter_s32_mono(pLPF, lpfCount, pFrames + 1); + ma_linear_resampler_filter_s32_mono(pLPF, lpfCount, pFrames + 2); + ma_linear_resampler_filter_s32_mono(pLPF, lpfCount, pFrames + 3); +} - if (pResampler->_ownsHeap) { - ma_free(pResampler->_pHeap, pAllocationCallbacks); - } +static MA_INLINE void ma_linear_resampler_filter_s32_4_stereo(ma_int32* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_int32* MA_RESTRICT pFrames) +{ + ma_linear_resampler_filter_s32_stereo(pLPF, lpfCount, pFrames + 0); + ma_linear_resampler_filter_s32_stereo(pLPF, lpfCount, pFrames + 2); + ma_linear_resampler_filter_s32_stereo(pLPF, lpfCount, pFrames + 4); + ma_linear_resampler_filter_s32_stereo(pLPF, lpfCount, pFrames + 6); } -static MA_INLINE ma_int16 ma_linear_resampler_mix_s16(ma_int16 x, ma_int16 y, ma_int32 a, const ma_int32 shift) +static MA_INLINE void ma_linear_resampler_filter_f32(float* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_uint32 channels, float* MA_RESTRICT pFrame) { - ma_int32 b; - ma_int32 c; - ma_int32 r; + ma_uint32 iLPF; + ma_uint32 iChannel; + + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + float b1 = pLPF[0]; + float b2 = pLPF[1]; + float a1 = pLPF[2]; + float a2 = pLPF[3]; + float* pR0 = pLPF + 4; + float* pR1 = pLPF + 4 + channels; + + for (iChannel = 0; iChannel < channels; iChannel += 1) { + float r0 = pR0[iChannel]; + float r1 = pR1[iChannel]; + float x = pFrame[iChannel]; + float y; - MA_ASSERT(a <= (1<> shift); + /* Go do the next LPF state. */ + pLPF += 4 + (channels * 2); + } } -static void ma_linear_resampler_interpolate_frame_s16(ma_linear_resampler* pResampler, ma_int16* MA_RESTRICT pFrameOut) +static MA_INLINE void ma_linear_resampler_filter_f32_mono(float* MA_RESTRICT pLPF, ma_uint32 lpfCount, float* MA_RESTRICT pFrame) { - ma_uint32 c; - ma_uint32 a; - const ma_uint32 channels = pResampler->config.channels; - const ma_uint32 shift = 12; + ma_uint32 iLPF; + ma_uint32 iChannel; + + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + float b1 = pLPF[0]; + float b2 = pLPF[1]; + float a1 = pLPF[2]; + float a2 = pLPF[3]; + float* pR0 = pLPF + 4; + float* pR1 = pLPF + 4 + 1; - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFrameOut != NULL); + for (iChannel = 0; iChannel < 1; iChannel += 1) { + float r0 = pR0[iChannel]; + float r1 = pR1[iChannel]; + float x = pFrame[iChannel]; + float y; - a = (pResampler->inTimeFrac << shift) / pResampler->config.sampleRateOut; + y = b2*x + r0; + r0 = b1*x - a1*y + r1; + r1 = b2*x - a2*y; - MA_ASSUME(channels > 0); - for (c = 0; c < channels; c += 1) { - ma_int16 s = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pResampler->x1.s16[c], a, shift); - pFrameOut[c] = s; + pFrame[iChannel] = y; + pR0[iChannel] = r0; + pR1[iChannel] = r1; + } + + /* Go do the next LPF state. */ + pLPF += 4 + (1 * 2); } } - -static void ma_linear_resampler_interpolate_frame_f32(ma_linear_resampler* pResampler, float* MA_RESTRICT pFrameOut) +static MA_INLINE void ma_linear_resampler_filter_f32_stereo(float* MA_RESTRICT pLPF, ma_uint32 lpfCount, float* MA_RESTRICT pFrame) { - ma_uint32 c; - float a; - const ma_uint32 channels = pResampler->config.channels; + ma_uint32 iLPF; + ma_uint32 iChannel; + + for (iLPF = 0; iLPF < lpfCount; iLPF += 1) { + float b1 = pLPF[0]; + float b2 = pLPF[1]; + float a1 = pLPF[2]; + float a2 = pLPF[3]; + float* pR0 = pLPF + 4; + float* pR1 = pLPF + 4 + 2; - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFrameOut != NULL); + for (iChannel = 0; iChannel < 2; iChannel += 1) { + float r0 = pR0[iChannel]; + float r1 = pR1[iChannel]; + float x = pFrame[iChannel]; + float y; - a = (float)pResampler->inTimeFrac / pResampler->config.sampleRateOut; + y = b2*x + r0; + r0 = b1*x - a1*y + r1; + r1 = b2*x - a2*y; - MA_ASSUME(channels > 0); - for (c = 0; c < channels; c += 1) { - float s = ma_mix_f32_fast(pResampler->x0.f32[c], pResampler->x1.f32[c], a); - pFrameOut[c] = s; + pFrame[iChannel] = y; + pR0[iChannel] = r0; + pR1[iChannel] = r1; + } + + /* Go do the next LPF state. */ + pLPF += 4 + (2 * 2); } } +static MA_INLINE void ma_linear_resampler_filter_f32_4(float* MA_RESTRICT pLPF, ma_uint32 lpfCount, ma_uint32 channels, float* MA_RESTRICT pFrames) +{ + ma_linear_resampler_filter_f32(pLPF, lpfCount, channels, pFrames + (0 * channels)); + ma_linear_resampler_filter_f32(pLPF, lpfCount, channels, pFrames + (1 * channels)); + ma_linear_resampler_filter_f32(pLPF, lpfCount, channels, pFrames + (2 * channels)); + ma_linear_resampler_filter_f32(pLPF, lpfCount, channels, pFrames + (3 * channels)); +} + +static MA_INLINE void ma_linear_resampler_filter_f32_4_mono(float* MA_RESTRICT pLPF, ma_uint32 lpfCount, float* MA_RESTRICT pFrames) +{ + ma_linear_resampler_filter_f32_mono(pLPF, lpfCount, pFrames + 0); + ma_linear_resampler_filter_f32_mono(pLPF, lpfCount, pFrames + 1); + ma_linear_resampler_filter_f32_mono(pLPF, lpfCount, pFrames + 2); + ma_linear_resampler_filter_f32_mono(pLPF, lpfCount, pFrames + 3); +} + +static MA_INLINE void ma_linear_resampler_filter_f32_4_stereo(float* MA_RESTRICT pLPF, ma_uint32 lpfCount, float* MA_RESTRICT pFrames) +{ + ma_linear_resampler_filter_f32_stereo(pLPF, lpfCount, pFrames + 0); + ma_linear_resampler_filter_f32_stereo(pLPF, lpfCount, pFrames + 2); + ma_linear_resampler_filter_f32_stereo(pLPF, lpfCount, pFrames + 4); + ma_linear_resampler_filter_f32_stereo(pLPF, lpfCount, pFrames + 6); +} + + static ma_result ma_linear_resampler_process_pcm_frames_s16_downsample(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { const ma_int16* pFramesInS16; @@ -53359,6 +62839,10 @@ static ma_result ma_linear_resampler_process_pcm_frames_s16_downsample(ma_linear ma_uint64 frameCountOut; ma_uint64 framesProcessedIn; ma_uint64 framesProcessedOut; + ma_uint32 c; + ma_uint32 invSampleRateOut; + ma_uint32 lpfCount; + ma_uint32 channels; MA_ASSERT(pResampler != NULL); MA_ASSERT(pFrameCountIn != NULL); @@ -53370,61 +62854,59 @@ static ma_result ma_linear_resampler_process_pcm_frames_s16_downsample(ma_linear frameCountOut = *pFrameCountOut; framesProcessedIn = 0; framesProcessedOut = 0; + invSampleRateOut = (1 << MA_LINEAR_RESAMPLER_LERP_SHIFT) / pResampler->sampleRateOut; + lpfCount = pResampler->lpfOrder >> 1; + channels = pResampler->channels; - while (framesProcessedOut < frameCountOut) { - /* Before interpolating we need to load the buffers. When doing this we need to ensure we run every input sample through the filter. */ - while (pResampler->inTimeInt > 0 && frameCountIn > framesProcessedIn) { - ma_uint32 iChannel; + if (lpfCount == 0) { + return ma_linear_resampler_process_pcm_frames_s16_no_lpf(pResampler, pFramesInS16, pFrameCountIn, pFramesOutS16, pFrameCountOut, invSampleRateOut); + } else { + ma_int32* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_S32(pResampler, 0); - if (pFramesInS16 != NULL) { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.s16[iChannel] = pResampler->x1.s16[iChannel]; - pResampler->x1.s16[iChannel] = pFramesInS16[iChannel]; - } - pFramesInS16 += pResampler->config.channels; - } else { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.s16[iChannel] = pResampler->x1.s16[iChannel]; - pResampler->x1.s16[iChannel] = 0; + while (framesProcessedOut < frameCountOut) { + ma_uint32 a = pResampler->inTimeFrac * invSampleRateOut; + + /* Before interpolating we need to load the buffers. When doing this we need to ensure we run every input sample through the filter. */ + while (pResampler->inTimeInt > 0 && frameCountIn > framesProcessedIn) { + for (c = 0; c < channels; c += 1) { + pResampler->x0.s16[c] = pResampler->x1.s16[c]; + pResampler->x1.s16[c] = pFramesInS16[c]; } - } + pFramesInS16 += channels; - /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ - if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { - ma_lpf_process_pcm_frame_s16(&pResampler->lpf, pResampler->x1.s16, pResampler->x1.s16); + /* Filter. */ + ma_linear_resampler_filter_s16(pLPF, lpfCount, channels, pResampler->x1.s16); + + framesProcessedIn += 1; + pResampler->inTimeInt -= 1; } - framesProcessedIn += 1; - pResampler->inTimeInt -= 1; - } + if (pResampler->inTimeInt > 0) { + break; /* Ran out of input data. */ + } - if (pResampler->inTimeInt > 0) { - break; /* Ran out of input data. */ - } + /* Getting here means the frames have been loaded and filtered and we can generate the next output frame. */ + for (c = 0; c < channels; c += 1) { + pFramesOutS16[c] = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pResampler->x1.s16[c], a); + } + pFramesOutS16 += channels; - /* Getting here means the frames have been loaded and filtered and we can generate the next output frame. */ - if (pFramesOutS16 != NULL) { - MA_ASSERT(pResampler->inTimeInt == 0); - ma_linear_resampler_interpolate_frame_s16(pResampler, pFramesOutS16); + framesProcessedOut += 1; - pFramesOutS16 += pResampler->config.channels; + /* Advance time forward. */ + pResampler->inTimeInt += pResampler->inAdvanceInt; + pResampler->inTimeFrac += pResampler->inAdvanceFrac; + if (pResampler->inTimeFrac >= pResampler->sampleRateOut) { + pResampler->inTimeFrac -= pResampler->sampleRateOut; + pResampler->inTimeInt += 1; + } } - framesProcessedOut += 1; + *pFrameCountIn = framesProcessedIn; + *pFrameCountOut = framesProcessedOut; - /* Advance time forward. */ - pResampler->inTimeInt += pResampler->inAdvanceInt; - pResampler->inTimeFrac += pResampler->inAdvanceFrac; - if (pResampler->inTimeFrac >= pResampler->config.sampleRateOut) { - pResampler->inTimeFrac -= pResampler->config.sampleRateOut; - pResampler->inTimeInt += 1; - } + return MA_SUCCESS; } - - *pFrameCountIn = framesProcessedIn; - *pFrameCountOut = framesProcessedOut; - - return MA_SUCCESS; } static ma_result ma_linear_resampler_process_pcm_frames_s16_upsample(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) @@ -53435,9 +62917,17 @@ static ma_result ma_linear_resampler_process_pcm_frames_s16_upsample(ma_linear_r ma_uint64 frameCountOut; ma_uint64 framesProcessedIn; ma_uint64 framesProcessedOut; + ma_uint32 c; + ma_uint32 invSampleRateOut; + ma_uint32 lpfCount; + ma_uint32 channels; + ma_uint32 inTimeInt; + ma_uint32 inTimeFrac; MA_ASSERT(pResampler != NULL); + MA_ASSERT(pFramesIn != NULL); MA_ASSERT(pFrameCountIn != NULL); + MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFrameCountOut != NULL); pFramesInS16 = (const ma_int16*)pFramesIn; @@ -53446,68 +62936,406 @@ static ma_result ma_linear_resampler_process_pcm_frames_s16_upsample(ma_linear_r frameCountOut = *pFrameCountOut; framesProcessedIn = 0; framesProcessedOut = 0; + invSampleRateOut = (1 << MA_LINEAR_RESAMPLER_LERP_SHIFT) / pResampler->sampleRateOut; + lpfCount = pResampler->lpfOrder >> 1; + channels = pResampler->channels; + inTimeInt = pResampler->inTimeInt; + inTimeFrac = pResampler->inTimeFrac; + + /* We can run an optimized path when the low-pass filter is not being used. */ + if (lpfCount == 0) { + /* Fast path. No LPF needed. */ + return ma_linear_resampler_process_pcm_frames_s16_no_lpf(pResampler, pFramesInS16, pFrameCountIn, pFramesOutS16, pFrameCountOut, invSampleRateOut); + } else { + /* Slow path. Need LPF. */ + ma_int32* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_S32(pResampler, 0); - while (framesProcessedOut < frameCountOut) { - /* Before interpolating we need to load the buffers. */ - while (pResampler->inTimeInt > 0 && frameCountIn > framesProcessedIn) { - ma_uint32 iChannel; + /* If there's a cached frame we need to process it. */ + if (inTimeInt == 0) { + MA_ASSERT(pResampler->cachedFrameCount <= 1); /* There is at most one cached frame. */ + + while (pResampler->cachedFrameCount > 0 && frameCountIn > 0 && framesProcessedOut < frameCountOut) { + ma_uint32 a = inTimeFrac * invSampleRateOut; - if (pFramesInS16 != NULL) { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.s16[iChannel] = pResampler->x1.s16[iChannel]; - pResampler->x1.s16[iChannel] = pFramesInS16[iChannel]; + for (c = 0; c < channels; c += 1) { + pFramesOutS16[c] = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pFramesInS16[c], a); } - pFramesInS16 += pResampler->config.channels; - } else { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.s16[iChannel] = pResampler->x1.s16[iChannel]; - pResampler->x1.s16[iChannel] = 0; + ma_linear_resampler_filter_s16(pLPF, lpfCount, channels, pFramesOutS16); + + pFramesOutS16 += channels; + + framesProcessedOut += 1; + + /* Advance time forward. */ + inTimeInt += pResampler->inAdvanceInt; + inTimeFrac += pResampler->inAdvanceFrac; + if (inTimeFrac >= pResampler->sampleRateOut) { + inTimeFrac -= pResampler->sampleRateOut; + inTimeInt += 1; } - } - framesProcessedIn += 1; - pResampler->inTimeInt -= 1; + /* Subtract one from the time to account for the cached frame, but only if the entire frame was processed. */ + if (inTimeInt > 0) { + inTimeInt -= 1; + pResampler->cachedFrameCount = 0; + } + } + } else { + /* The rate must have changed between calls. Ignore the cached frame. */ } - if (pResampler->inTimeInt > 0) { - break; /* Ran out of input data. */ + /* + When upsampling we should never be skipping any input frames so processing should always start + from the first input frame. + */ + inTimeInt = 0; + + /* Experimental loop unrolling to make it easier for SIMD-ification. */ + #if 1 + { + if (channels == 1) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt4[4]; + ma_uint32 inTimeFrac4[4]; + ma_int32 x[4]; + ma_int32 y[4]; + ma_int32 a[4]; + ma_int32 d[4]; + ma_int32 n[4]; + ma_int32 r[4]; + int i; + + inTimeIntTemp = inTimeInt; + inTimeFracTemp = inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt4[i] = inTimeIntTemp; + inTimeFrac4[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt4[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + inTimeInt = inTimeIntTemp; + inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInS16[inTimeInt4[0] + 0]; + x[1] = pFramesInS16[inTimeInt4[1] + 0]; + x[2] = pFramesInS16[inTimeInt4[2] + 0]; + x[3] = pFramesInS16[inTimeInt4[3] + 0]; + + y[0] = pFramesInS16[inTimeInt4[0] + 1]; + y[1] = pFramesInS16[inTimeInt4[1] + 1]; + y[2] = pFramesInS16[inTimeInt4[2] + 1]; + y[3] = pFramesInS16[inTimeInt4[3] + 1]; + + a[0] = inTimeFrac4[0] * invSampleRateOut; + a[1] = inTimeFrac4[1] * invSampleRateOut; + a[2] = inTimeFrac4[2] * invSampleRateOut; + a[3] = inTimeFrac4[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + + r[0] = x[0] + (n[0] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[1] = x[1] + (n[1] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[2] = x[2] + (n[2] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[3] = x[3] + (n[3] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + + ma_linear_resampler_filter_s32_4_mono(pLPF, lpfCount, r); + + pFramesOutS16[0] = (ma_int16)r[0]; + pFramesOutS16[1] = (ma_int16)r[1]; + pFramesOutS16[2] = (ma_int16)r[2]; + pFramesOutS16[3] = (ma_int16)r[3]; + + pFramesOutS16 += 4; + framesProcessedOut += 4; + } + } else if (channels == 2) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt4[4]; + ma_uint32 inTimeFrac4[4]; + ma_int32 x[8]; + ma_int32 y[8]; + ma_int32 a[8]; + ma_int32 d[8]; + ma_int32 n[8]; + ma_int32 r[8]; + int i; + + inTimeIntTemp = inTimeInt; + inTimeFracTemp = inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt4[i] = inTimeIntTemp; + inTimeFrac4[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt4[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + inTimeInt = inTimeIntTemp; + inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInS16[((inTimeInt4[0] + 0) * 2) + 0]; + x[1] = pFramesInS16[((inTimeInt4[0] + 0) * 2) + 1]; + x[2] = pFramesInS16[((inTimeInt4[1] + 0) * 2) + 0]; + x[3] = pFramesInS16[((inTimeInt4[1] + 0) * 2) + 1]; + x[4] = pFramesInS16[((inTimeInt4[2] + 0) * 2) + 0]; + x[5] = pFramesInS16[((inTimeInt4[2] + 0) * 2) + 1]; + x[6] = pFramesInS16[((inTimeInt4[3] + 0) * 2) + 0]; + x[7] = pFramesInS16[((inTimeInt4[3] + 0) * 2) + 1]; + + y[0] = pFramesInS16[((inTimeInt4[0] + 1) * 2) + 0]; + y[1] = pFramesInS16[((inTimeInt4[0] + 1) * 2) + 1]; + y[2] = pFramesInS16[((inTimeInt4[1] + 1) * 2) + 0]; + y[3] = pFramesInS16[((inTimeInt4[1] + 1) * 2) + 1]; + y[4] = pFramesInS16[((inTimeInt4[2] + 1) * 2) + 0]; + y[5] = pFramesInS16[((inTimeInt4[2] + 1) * 2) + 1]; + y[6] = pFramesInS16[((inTimeInt4[3] + 1) * 2) + 0]; + y[7] = pFramesInS16[((inTimeInt4[3] + 1) * 2) + 1]; + + a[0] = inTimeFrac4[0] * invSampleRateOut; + a[1] = inTimeFrac4[0] * invSampleRateOut; + a[2] = inTimeFrac4[1] * invSampleRateOut; + a[3] = inTimeFrac4[1] * invSampleRateOut; + a[4] = inTimeFrac4[2] * invSampleRateOut; + a[5] = inTimeFrac4[2] * invSampleRateOut; + a[6] = inTimeFrac4[3] * invSampleRateOut; + a[7] = inTimeFrac4[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + d[4] = y[4] - x[4]; + d[5] = y[5] - x[5]; + d[6] = y[6] - x[6]; + d[7] = y[7] - x[7]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + n[4] = d[4] * a[4]; + n[5] = d[5] * a[5]; + n[6] = d[6] * a[6]; + n[7] = d[7] * a[7]; + + r[0] = x[0] + (n[0] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[1] = x[1] + (n[1] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[2] = x[2] + (n[2] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[3] = x[3] + (n[3] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[4] = x[4] + (n[4] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[5] = x[5] + (n[5] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[6] = x[6] + (n[6] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[7] = x[7] + (n[7] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + + ma_linear_resampler_filter_s32_4_stereo(pLPF, lpfCount, r); + + pFramesOutS16[0] = (ma_int16)r[0]; + pFramesOutS16[1] = (ma_int16)r[1]; + pFramesOutS16[2] = (ma_int16)r[2]; + pFramesOutS16[3] = (ma_int16)r[3]; + pFramesOutS16[4] = (ma_int16)r[4]; + pFramesOutS16[5] = (ma_int16)r[5]; + pFramesOutS16[6] = (ma_int16)r[6]; + pFramesOutS16[7] = (ma_int16)r[7]; + + pFramesOutS16 += 8; + framesProcessedOut += 4; + } + } else { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt4[4]; + ma_uint32 inTimeFrac4[4]; + int i; + + inTimeIntTemp = inTimeInt; + inTimeFracTemp = inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt4[i] = inTimeIntTemp; + inTimeFrac4[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt4[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + inTimeInt = inTimeIntTemp; + inTimeFrac = inTimeFracTemp; + + for (c = 0; c < channels; c += 1) { + ma_int32 x[4]; + ma_int32 y[4]; + ma_int32 a[4]; + ma_int32 d[4]; + ma_int32 n[4]; + ma_int32 r[4]; + + x[0] = pFramesInS16[((inTimeInt4[0] + 0) * channels) + c]; + x[1] = pFramesInS16[((inTimeInt4[1] + 0) * channels) + c]; + x[2] = pFramesInS16[((inTimeInt4[2] + 0) * channels) + c]; + x[3] = pFramesInS16[((inTimeInt4[3] + 0) * channels) + c]; + + y[0] = pFramesInS16[((inTimeInt4[0] + 1) * channels) + c]; + y[1] = pFramesInS16[((inTimeInt4[1] + 1) * channels) + c]; + y[2] = pFramesInS16[((inTimeInt4[2] + 1) * channels) + c]; + y[3] = pFramesInS16[((inTimeInt4[3] + 1) * channels) + c]; + + a[0] = inTimeFrac4[0] * invSampleRateOut; + a[1] = inTimeFrac4[1] * invSampleRateOut; + a[2] = inTimeFrac4[2] * invSampleRateOut; + a[3] = inTimeFrac4[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + + r[0] = x[0] + (n[0] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[1] = x[1] + (n[1] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[2] = x[2] + (n[2] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + r[3] = x[3] + (n[3] >> MA_LINEAR_RESAMPLER_LERP_SHIFT); + + pFramesOutS16[(0 * channels) + c] = (ma_int16)r[0]; + pFramesOutS16[(1 * channels) + c] = (ma_int16)r[1]; + pFramesOutS16[(2 * channels) + c] = (ma_int16)r[2]; + pFramesOutS16[(3 * channels) + c] = (ma_int16)r[3]; + } + + ma_linear_resampler_filter_s16_4(pLPF, lpfCount, channels, pFramesOutS16); + + pFramesOutS16 += 4 * channels; + framesProcessedOut += 4; + } + } } + #endif + + while (framesProcessedOut < frameCountOut && inTimeInt < frameCountIn) { + if (inTimeInt + 1 < frameCountIn) { + ma_uint32 a = inTimeFrac * invSampleRateOut; + + for (c = 0; c < channels; c += 1) { + pFramesOutS16[c] = ma_linear_resampler_mix_s16(pFramesInS16[((inTimeInt + 0) * channels) + c], pFramesInS16[((inTimeInt + 1) * channels) + c], a); + } + ma_linear_resampler_filter_s16(pLPF, lpfCount, channels, pFramesOutS16); + + pFramesOutS16 += channels; + + framesProcessedOut += 1; + + /* Advance time forward. */ + inTimeInt += pResampler->inAdvanceInt; + inTimeFrac += pResampler->inAdvanceFrac; + if (inTimeFrac >= pResampler->sampleRateOut) { + inTimeFrac -= pResampler->sampleRateOut; + inTimeInt += 1; + } + } else { + /* + There is not enough input frames to interpolate. We'll need to stop here. But it's important that we cache + the frame to ensure we make some forward progress. + */ + for (c = 0; c < channels; c += 1) { + pResampler->x0.s16[c] = pFramesInS16[((inTimeInt + 0) * channels) + c]; + } - /* Getting here means the frames have been loaded and we can generate the next output frame. */ - if (pFramesOutS16 != NULL) { - MA_ASSERT(pResampler->inTimeInt == 0); - ma_linear_resampler_interpolate_frame_s16(pResampler, pFramesOutS16); + pResampler->cachedFrameCount = 1; + inTimeInt += 1; - /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ - if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { - ma_lpf_process_pcm_frame_s16(&pResampler->lpf, pFramesOutS16, pFramesOutS16); + break; } + } - pFramesOutS16 += pResampler->config.channels; + /* The number of frames we processed is simply the difference between our current time and previous time, clamped. */ + framesProcessedIn = inTimeInt; + if (framesProcessedIn > frameCountIn) { /* Should never overshoot when upsampling. Downsampling could overshoot. */ + framesProcessedIn = frameCountIn; } - framesProcessedOut += 1; + if (inTimeInt >= (ma_uint32)framesProcessedIn) { + inTimeInt -= (ma_uint32)framesProcessedIn; + } else { + inTimeInt = 0; + } - /* Advance time forward. */ - pResampler->inTimeInt += pResampler->inAdvanceInt; - pResampler->inTimeFrac += pResampler->inAdvanceFrac; - if (pResampler->inTimeFrac >= pResampler->config.sampleRateOut) { - pResampler->inTimeFrac -= pResampler->config.sampleRateOut; - pResampler->inTimeInt += 1; + /* + Now matter what, we want to cache the last input frame. The reason is that if the sample rate changes from upsampling to downsampling, the + downsampling process will be expecting an input frame. + */ + if (framesProcessedIn > 0) { + for (c = 0; c < channels; c += 1) { + pResampler->x1.s16[c] = pFramesInS16[((framesProcessedIn - 1) * channels) + c]; + } } - } - *pFrameCountIn = framesProcessedIn; - *pFrameCountOut = framesProcessedOut; + /* Don't forget to update the time. */ + pResampler->inTimeInt = inTimeInt; + pResampler->inTimeFrac = inTimeFrac; - return MA_SUCCESS; + *pFrameCountIn = framesProcessedIn; + *pFrameCountOut = framesProcessedOut; + + return MA_SUCCESS; + } } static ma_result ma_linear_resampler_process_pcm_frames_s16(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { MA_ASSERT(pResampler != NULL); - if (pResampler->config.sampleRateIn > pResampler->config.sampleRateOut) { + if (pResampler->inAdvanceInt > 0) { return ma_linear_resampler_process_pcm_frames_s16_downsample(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } else { return ma_linear_resampler_process_pcm_frames_s16_upsample(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); @@ -53523,6 +63351,10 @@ static ma_result ma_linear_resampler_process_pcm_frames_f32_downsample(ma_linear ma_uint64 frameCountOut; ma_uint64 framesProcessedIn; ma_uint64 framesProcessedOut; + ma_uint32 c; + float invSampleRateOut; + ma_uint32 lpfCount; + ma_uint32 channels; MA_ASSERT(pResampler != NULL); MA_ASSERT(pFrameCountIn != NULL); @@ -53534,61 +63366,63 @@ static ma_result ma_linear_resampler_process_pcm_frames_f32_downsample(ma_linear frameCountOut = *pFrameCountOut; framesProcessedIn = 0; framesProcessedOut = 0; + invSampleRateOut = 1.0f / pResampler->sampleRateOut; + lpfCount = pResampler->lpfOrder >> 1; + channels = pResampler->channels; - while (framesProcessedOut < frameCountOut) { - /* Before interpolating we need to load the buffers. When doing this we need to ensure we run every input sample through the filter. */ - while (pResampler->inTimeInt > 0 && frameCountIn > framesProcessedIn) { - ma_uint32 iChannel; + if (lpfCount == 0) { + /* Fast path. No LPF needed. */ + return ma_linear_resampler_process_pcm_frames_f32_no_lpf(pResampler, pFramesInF32, pFrameCountIn, pFramesOutF32, pFrameCountOut, invSampleRateOut); + } else { + /* Slow path. Need LPF. */ + float* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_F32(pResampler, 0); - if (pFramesInF32 != NULL) { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.f32[iChannel] = pResampler->x1.f32[iChannel]; - pResampler->x1.f32[iChannel] = pFramesInF32[iChannel]; - } - pFramesInF32 += pResampler->config.channels; - } else { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.f32[iChannel] = pResampler->x1.f32[iChannel]; - pResampler->x1.f32[iChannel] = 0; + /*printf("DOWN\n");*/ + + while (framesProcessedOut < frameCountOut) { + float a = pResampler->inTimeFrac * invSampleRateOut; + + /* Before interpolating we need to load the buffers. When doing this we need to ensure we run every input sample through the filter. */ + while (pResampler->inTimeInt > 0 && frameCountIn > framesProcessedIn) { + for (c = 0; c < channels; c += 1) { + pResampler->x0.f32[c] = pResampler->x1.f32[c]; + pResampler->x1.f32[c] = pFramesInF32[c]; } - } + pFramesInF32 += channels; - /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ - if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { - ma_lpf_process_pcm_frame_f32(&pResampler->lpf, pResampler->x1.f32, pResampler->x1.f32); + /* Filter. */ + ma_linear_resampler_filter_f32(pLPF, lpfCount, channels, pResampler->x1.f32); + + framesProcessedIn += 1; + pResampler->inTimeInt -= 1; } - framesProcessedIn += 1; - pResampler->inTimeInt -= 1; - } + if (pResampler->inTimeInt > 0) { + break; /* Ran out of input data. */ + } - if (pResampler->inTimeInt > 0) { - break; /* Ran out of input data. */ - } + /* Getting here means the frames have been loaded and filtered and we can generate the next output frame. */ + for (c = 0; c < channels; c += 1) { + pFramesOutF32[c] = ma_mix_f32_fast(pResampler->x0.f32[c], pResampler->x1.f32[c], a); + } + pFramesOutF32 += channels; - /* Getting here means the frames have been loaded and filtered and we can generate the next output frame. */ - if (pFramesOutF32 != NULL) { - MA_ASSERT(pResampler->inTimeInt == 0); - ma_linear_resampler_interpolate_frame_f32(pResampler, pFramesOutF32); + framesProcessedOut += 1; - pFramesOutF32 += pResampler->config.channels; + /* Advance time forward. */ + pResampler->inTimeInt += pResampler->inAdvanceInt; + pResampler->inTimeFrac += pResampler->inAdvanceFrac; + if (pResampler->inTimeFrac >= pResampler->sampleRateOut) { + pResampler->inTimeFrac -= pResampler->sampleRateOut; + pResampler->inTimeInt += 1; + } } - framesProcessedOut += 1; + *pFrameCountIn = framesProcessedIn; + *pFrameCountOut = framesProcessedOut; - /* Advance time forward. */ - pResampler->inTimeInt += pResampler->inAdvanceInt; - pResampler->inTimeFrac += pResampler->inAdvanceFrac; - if (pResampler->inTimeFrac >= pResampler->config.sampleRateOut) { - pResampler->inTimeFrac -= pResampler->config.sampleRateOut; - pResampler->inTimeInt += 1; - } + return MA_SUCCESS; } - - *pFrameCountIn = framesProcessedIn; - *pFrameCountOut = framesProcessedOut; - - return MA_SUCCESS; } static ma_result ma_linear_resampler_process_pcm_frames_f32_upsample(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) @@ -53599,6 +63433,12 @@ static ma_result ma_linear_resampler_process_pcm_frames_f32_upsample(ma_linear_r ma_uint64 frameCountOut; ma_uint64 framesProcessedIn; ma_uint64 framesProcessedOut; + ma_uint32 c; + float invSampleRateOut; + ma_uint32 lpfCount; + ma_uint32 channels; + ma_uint32 inTimeInt; + ma_uint32 inTimeFrac; MA_ASSERT(pResampler != NULL); MA_ASSERT(pFrameCountIn != NULL); @@ -53610,68 +63450,407 @@ static ma_result ma_linear_resampler_process_pcm_frames_f32_upsample(ma_linear_r frameCountOut = *pFrameCountOut; framesProcessedIn = 0; framesProcessedOut = 0; + invSampleRateOut = 1.0f / pResampler->sampleRateOut; + lpfCount = pResampler->lpfOrder >> 1; + channels = pResampler->channels; + inTimeInt = pResampler->inTimeInt; + inTimeFrac = pResampler->inTimeFrac; + + if (lpfCount == 0) { + /* Fast path. No LPF needed. */ + return ma_linear_resampler_process_pcm_frames_f32_no_lpf(pResampler, pFramesInF32, pFrameCountIn, pFramesOutF32, pFrameCountOut, invSampleRateOut); + } else { + /* Slow path. Need LPF. */ + float* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_F32(pResampler, 0); - while (framesProcessedOut < frameCountOut) { - /* Before interpolating we need to load the buffers. */ - while (pResampler->inTimeInt > 0 && frameCountIn > framesProcessedIn) { - ma_uint32 iChannel; + /*printf("UP %u\n", pResampler->inTimeInt);*/ + + /* If there's a cached frame we need to process it. */ + if (inTimeInt == 0) { + MA_ASSERT(pResampler->cachedFrameCount <= 1); /* There is at most one cached frame. */ - if (pFramesInF32 != NULL) { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.f32[iChannel] = pResampler->x1.f32[iChannel]; - pResampler->x1.f32[iChannel] = pFramesInF32[iChannel]; + while (pResampler->cachedFrameCount > 0 && frameCountIn > 0 && framesProcessedOut < frameCountOut) { + float a = inTimeFrac * invSampleRateOut; + + for (c = 0; c < channels; c += 1) { + pFramesOutF32[c] = ma_mix_f32_fast(pResampler->x0.f32[c], pFramesInF32[c], a); } - pFramesInF32 += pResampler->config.channels; - } else { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { - pResampler->x0.f32[iChannel] = pResampler->x1.f32[iChannel]; - pResampler->x1.f32[iChannel] = 0; + ma_linear_resampler_filter_f32(pLPF, lpfCount, channels, pFramesOutF32); + + pFramesOutF32 += channels; + + framesProcessedOut += 1; + + /* Advance time forward. */ + inTimeInt += pResampler->inAdvanceInt; + inTimeFrac += pResampler->inAdvanceFrac; + if (inTimeFrac >= pResampler->sampleRateOut) { + inTimeFrac -= pResampler->sampleRateOut; + inTimeInt += 1; } - } - framesProcessedIn += 1; - pResampler->inTimeInt -= 1; + /* Subtract one from the time to account for the cached frame, but only if the entire frame was processed. */ + if (inTimeInt > 0) { + inTimeInt -= 1; + pResampler->cachedFrameCount = 0; + } + } + } else { + /* The rate must have changed between calls. Ignore the cached frame. */ } - if (pResampler->inTimeInt > 0) { - break; /* Ran out of input data. */ + /* + When upsampling we should never be skipping any input frames so processing should always start + from the first input frame. + */ + inTimeInt = 0; + + /* Experimental loop unrolling to make it easier for SIMD-ification. */ + #if 1 + { + if (channels == 1) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt4[4]; + ma_uint32 inTimeFrac4[4]; + float x[4]; + float y[4]; + float a[4]; + float d[4]; + float n[4]; + float r[4]; + int i; + + inTimeIntTemp = inTimeInt; + inTimeFracTemp = inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt4[i] = inTimeIntTemp; + inTimeFrac4[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt4[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + inTimeInt = inTimeIntTemp; + inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInF32[inTimeInt4[0] + 0]; + x[1] = pFramesInF32[inTimeInt4[1] + 0]; + x[2] = pFramesInF32[inTimeInt4[2] + 0]; + x[3] = pFramesInF32[inTimeInt4[3] + 0]; + + y[0] = pFramesInF32[inTimeInt4[0] + 1]; + y[1] = pFramesInF32[inTimeInt4[1] + 1]; + y[2] = pFramesInF32[inTimeInt4[2] + 1]; + y[3] = pFramesInF32[inTimeInt4[3] + 1]; + + a[0] = inTimeFrac4[0] * invSampleRateOut; + a[1] = inTimeFrac4[1] * invSampleRateOut; + a[2] = inTimeFrac4[2] * invSampleRateOut; + a[3] = inTimeFrac4[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + + r[0] = x[0] + n[0]; + r[1] = x[1] + n[1]; + r[2] = x[2] + n[2]; + r[3] = x[3] + n[3]; + + ma_linear_resampler_filter_f32_4_mono(pLPF, lpfCount, r); + + pFramesOutF32[0] = r[0]; + pFramesOutF32[1] = r[1]; + pFramesOutF32[2] = r[2]; + pFramesOutF32[3] = r[3]; + + pFramesOutF32 += 4; + framesProcessedOut += 4; + } + } else if (channels == 2) { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt4[4]; + ma_uint32 inTimeFrac4[4]; + float x[8]; + float y[8]; + float a[8]; + float d[8]; + float n[8]; + float r[8]; + int i; + + inTimeIntTemp = inTimeInt; + inTimeFracTemp = inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt4[i] = inTimeIntTemp; + inTimeFrac4[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt4[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + inTimeInt = inTimeIntTemp; + inTimeFrac = inTimeFracTemp; + + x[0] = pFramesInF32[((inTimeInt4[0] + 0) * 2) + 0]; + x[1] = pFramesInF32[((inTimeInt4[0] + 0) * 2) + 1]; + x[2] = pFramesInF32[((inTimeInt4[1] + 0) * 2) + 0]; + x[3] = pFramesInF32[((inTimeInt4[1] + 0) * 2) + 1]; + x[4] = pFramesInF32[((inTimeInt4[2] + 0) * 2) + 0]; + x[5] = pFramesInF32[((inTimeInt4[2] + 0) * 2) + 1]; + x[6] = pFramesInF32[((inTimeInt4[3] + 0) * 2) + 0]; + x[7] = pFramesInF32[((inTimeInt4[3] + 0) * 2) + 1]; + + y[0] = pFramesInF32[((inTimeInt4[0] + 1) * 2) + 0]; + y[1] = pFramesInF32[((inTimeInt4[0] + 1) * 2) + 1]; + y[2] = pFramesInF32[((inTimeInt4[1] + 1) * 2) + 0]; + y[3] = pFramesInF32[((inTimeInt4[1] + 1) * 2) + 1]; + y[4] = pFramesInF32[((inTimeInt4[2] + 1) * 2) + 0]; + y[5] = pFramesInF32[((inTimeInt4[2] + 1) * 2) + 1]; + y[6] = pFramesInF32[((inTimeInt4[3] + 1) * 2) + 0]; + y[7] = pFramesInF32[((inTimeInt4[3] + 1) * 2) + 1]; + + a[0] = inTimeFrac4[0] * invSampleRateOut; + a[1] = inTimeFrac4[0] * invSampleRateOut; + a[2] = inTimeFrac4[1] * invSampleRateOut; + a[3] = inTimeFrac4[1] * invSampleRateOut; + a[4] = inTimeFrac4[2] * invSampleRateOut; + a[5] = inTimeFrac4[2] * invSampleRateOut; + a[6] = inTimeFrac4[3] * invSampleRateOut; + a[7] = inTimeFrac4[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + d[4] = y[4] - x[4]; + d[5] = y[5] - x[5]; + d[6] = y[6] - x[6]; + d[7] = y[7] - x[7]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + n[4] = d[4] * a[4]; + n[5] = d[5] * a[5]; + n[6] = d[6] * a[6]; + n[7] = d[7] * a[7]; + + r[0] = x[0] + n[0]; + r[1] = x[1] + n[1]; + r[2] = x[2] + n[2]; + r[3] = x[3] + n[3]; + r[4] = x[4] + n[4]; + r[5] = x[5] + n[5]; + r[6] = x[6] + n[6]; + r[7] = x[7] + n[7]; + + ma_linear_resampler_filter_f32_4_stereo(pLPF, lpfCount, r); + + pFramesOutF32[0] = r[0]; + pFramesOutF32[1] = r[1]; + pFramesOutF32[2] = r[2]; + pFramesOutF32[3] = r[3]; + pFramesOutF32[4] = r[4]; + pFramesOutF32[5] = r[5]; + pFramesOutF32[6] = r[6]; + pFramesOutF32[7] = r[7]; + + pFramesOutF32 += 8; + framesProcessedOut += 4; + } + } else { + while (framesProcessedOut + 4 <= frameCountOut) { + ma_uint32 inTimeIntTemp; + ma_uint32 inTimeFracTemp; + ma_uint32 inTimeInt4[4]; + ma_uint32 inTimeFrac4[4]; + int i; + + inTimeIntTemp = inTimeInt; + inTimeFracTemp = inTimeFrac; + + for (i = 0; i < 4; i += 1) { + inTimeInt4[i] = inTimeIntTemp; + inTimeFrac4[i] = inTimeFracTemp; + + inTimeIntTemp += pResampler->inAdvanceInt; + inTimeFracTemp += pResampler->inAdvanceFrac; + if (inTimeFracTemp >= pResampler->sampleRateOut) { + inTimeFracTemp -= pResampler->sampleRateOut; + inTimeIntTemp += 1; + } + } + + /* Check that we have one extra sample at the end for doing the interpolation. */ + if (inTimeInt4[3] + 1 >= frameCountIn) { + break; /* Not enough input frames. */ + } + + /* Advance the timer. */ + inTimeInt = inTimeIntTemp; + inTimeFrac = inTimeFracTemp; + + for (c = 0; c < channels; c += 1) { + float x[4]; + float y[4]; + float a[4]; + float d[4]; + float n[4]; + float r[4]; + + x[0] = pFramesInF32[((inTimeInt4[0] + 0) * channels) + c]; + x[1] = pFramesInF32[((inTimeInt4[1] + 0) * channels) + c]; + x[2] = pFramesInF32[((inTimeInt4[2] + 0) * channels) + c]; + x[3] = pFramesInF32[((inTimeInt4[3] + 0) * channels) + c]; + + y[0] = pFramesInF32[((inTimeInt4[0] + 1) * channels) + c]; + y[1] = pFramesInF32[((inTimeInt4[1] + 1) * channels) + c]; + y[2] = pFramesInF32[((inTimeInt4[2] + 1) * channels) + c]; + y[3] = pFramesInF32[((inTimeInt4[3] + 1) * channels) + c]; + + a[0] = inTimeFrac4[0] * invSampleRateOut; + a[1] = inTimeFrac4[1] * invSampleRateOut; + a[2] = inTimeFrac4[2] * invSampleRateOut; + a[3] = inTimeFrac4[3] * invSampleRateOut; + + d[0] = y[0] - x[0]; + d[1] = y[1] - x[1]; + d[2] = y[2] - x[2]; + d[3] = y[3] - x[3]; + + n[0] = d[0] * a[0]; + n[1] = d[1] * a[1]; + n[2] = d[2] * a[2]; + n[3] = d[3] * a[3]; + + r[0] = x[0] + n[0]; + r[1] = x[1] + n[1]; + r[2] = x[2] + n[2]; + r[3] = x[3] + n[3]; + + pFramesOutF32[(0 * channels) + c] = r[0]; + pFramesOutF32[(1 * channels) + c] = r[1]; + pFramesOutF32[(2 * channels) + c] = r[2]; + pFramesOutF32[(3 * channels) + c] = r[3]; + } + + ma_linear_resampler_filter_f32_4(pLPF, lpfCount, channels, pFramesOutF32); + + pFramesOutF32 += 4 * channels; + framesProcessedOut += 4; + } + } } + #endif + + while (framesProcessedOut < frameCountOut && inTimeInt < frameCountIn) { + if (inTimeInt + 1 < frameCountIn) { + float a = inTimeFrac * invSampleRateOut; + + for (c = 0; c < channels; c += 1) { + pFramesOutF32[c] = ma_mix_f32_fast(pFramesInF32[((inTimeInt + 0) * channels) + c], pFramesInF32[((inTimeInt + 1) * channels) + c], a); + } + ma_linear_resampler_filter_f32(pLPF, lpfCount, channels, pFramesOutF32); + + pFramesOutF32 += channels; + + framesProcessedOut += 1; + + /* Advance time forward. */ + inTimeInt += pResampler->inAdvanceInt; + inTimeFrac += pResampler->inAdvanceFrac; + if (inTimeFrac >= pResampler->sampleRateOut) { + inTimeFrac -= pResampler->sampleRateOut; + inTimeInt += 1; + } + } else { + /* + There is not enough input frames to interpolate. We'll need to stop here. But it's important that we cache + the frame to ensure we make some forward progress. + */ + for (c = 0; c < channels; c += 1) { + pResampler->x0.f32[c] = pFramesInF32[((inTimeInt + 0) * channels) + c]; + } - /* Getting here means the frames have been loaded and we can generate the next output frame. */ - if (pFramesOutF32 != NULL) { - MA_ASSERT(pResampler->inTimeInt == 0); - ma_linear_resampler_interpolate_frame_f32(pResampler, pFramesOutF32); + pResampler->cachedFrameCount = 1; + inTimeInt += 1; - /* Filter. Do not apply filtering if sample rates are the same or else you'll get dangerous glitching. */ - if (pResampler->config.sampleRateIn != pResampler->config.sampleRateOut) { - ma_lpf_process_pcm_frame_f32(&pResampler->lpf, pFramesOutF32, pFramesOutF32); + break; } + } - pFramesOutF32 += pResampler->config.channels; + /* The number of frames we processed is simply the difference between our current time and previous time, clamped. */ + framesProcessedIn = inTimeInt; + if (framesProcessedIn > frameCountIn) { /* Should never overshoot when upsampling. Downsampling could overshoot. */ + framesProcessedIn = frameCountIn; } - framesProcessedOut += 1; + if (inTimeInt >= (ma_uint32)framesProcessedIn) { + inTimeInt -= (ma_uint32)framesProcessedIn; + } else { + inTimeInt = 0; + } - /* Advance time forward. */ - pResampler->inTimeInt += pResampler->inAdvanceInt; - pResampler->inTimeFrac += pResampler->inAdvanceFrac; - if (pResampler->inTimeFrac >= pResampler->config.sampleRateOut) { - pResampler->inTimeFrac -= pResampler->config.sampleRateOut; - pResampler->inTimeInt += 1; + /* + Now matter what, we want to cache the last input frame. The reason is that if the sample rate changes from upsampling to downsampling, the + downsampling process will be expecting an input frame. + */ + if (framesProcessedIn > 0) { + for (c = 0; c < channels; c += 1) { + pResampler->x1.f32[c] = pFramesInF32[((framesProcessedIn - 1) * channels) + c]; + } } - } - *pFrameCountIn = framesProcessedIn; - *pFrameCountOut = framesProcessedOut; + /* Don't forget to update the time. */ + pResampler->inTimeInt = inTimeInt; + pResampler->inTimeFrac = inTimeFrac; - return MA_SUCCESS; + *pFrameCountIn = framesProcessedIn; + *pFrameCountOut = framesProcessedOut; + + return MA_SUCCESS; + } } static ma_result ma_linear_resampler_process_pcm_frames_f32(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { MA_ASSERT(pResampler != NULL); - if (pResampler->config.sampleRateIn > pResampler->config.sampleRateOut) { + if (pResampler->inAdvanceInt > 0) { return ma_linear_resampler_process_pcm_frames_f32_downsample(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } else { return ma_linear_resampler_process_pcm_frames_f32_upsample(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); @@ -53685,9 +63864,9 @@ MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pRe return MA_INVALID_ARGS; } - /* */ if (pResampler->config.format == ma_format_s16) { + /* */ if (pResampler->format == ma_format_s16) { return ma_linear_resampler_process_pcm_frames_s16(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else if (pResampler->config.format == ma_format_f32) { + } else if (pResampler->format == ma_format_f32) { return ma_linear_resampler_process_pcm_frames_f32(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } else { /* Should never get here. Getting here means the format is not supported and you didn't check the return value of ma_linear_resampler_init(). */ @@ -53699,7 +63878,7 @@ MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pRe MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { - return ma_linear_resampler_set_rate_internal(pResampler, NULL, NULL, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); + return ma_linear_resampler_set_rate_internal(pResampler, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); } MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut) @@ -53715,7 +63894,7 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp return MA_INVALID_ARGS; } - d = 1000000; + d = 1000; n = (ma_uint32)(ratioInOut * d); if (n == 0) { @@ -53733,7 +63912,7 @@ MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler return 0; } - return 1 + ma_lpf_get_latency(&pResampler->lpf); + return 1 + pResampler->lpfOrder; } MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) @@ -53742,88 +63921,13 @@ MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resample return 0; } - return ma_linear_resampler_get_input_latency(pResampler) * pResampler->config.sampleRateOut / pResampler->config.sampleRateIn; -} - -MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) -{ - ma_uint64 inputFrameCount; - - if (pInputFrameCount == NULL) { - return MA_INVALID_ARGS; - } - - *pInputFrameCount = 0; - - if (pResampler == NULL) { - return MA_INVALID_ARGS; - } - - if (outputFrameCount == 0) { - return MA_SUCCESS; - } - - /* Any whole input frames are consumed before the first output frame is generated. */ - inputFrameCount = pResampler->inTimeInt; - outputFrameCount -= 1; - - /* The rest of the output frames can be calculated in constant time. */ - inputFrameCount += outputFrameCount * pResampler->inAdvanceInt; - inputFrameCount += (pResampler->inTimeFrac + (outputFrameCount * pResampler->inAdvanceFrac)) / pResampler->config.sampleRateOut; - - *pInputFrameCount = inputFrameCount; - - return MA_SUCCESS; -} - -MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) -{ - ma_uint64 outputFrameCount; - ma_uint64 preliminaryInputFrameCountFromFrac; - ma_uint64 preliminaryInputFrameCount; - - if (pOutputFrameCount == NULL) { - return MA_INVALID_ARGS; - } - - *pOutputFrameCount = 0; - - if (pResampler == NULL) { - return MA_INVALID_ARGS; - } - - /* - The first step is to get a preliminary output frame count. This will either be exactly equal to what we need, or less by 1. We need to - determine how many input frames will be consumed by this value. If it's greater than our original input frame count it means we won't - be able to generate an extra frame because we will have run out of input data. Otherwise we will have enough input for the generation - of an extra output frame. This add-by-one logic is necessary due to how the data loading logic works when processing frames. - */ - outputFrameCount = (inputFrameCount * pResampler->config.sampleRateOut) / pResampler->config.sampleRateIn; - - /* - We need to determine how many *whole* input frames will have been processed to generate our preliminary output frame count. This is - used in the logic below to determine whether or not we need to add an extra output frame. - */ - preliminaryInputFrameCountFromFrac = (pResampler->inTimeFrac + outputFrameCount*pResampler->inAdvanceFrac) / pResampler->config.sampleRateOut; - preliminaryInputFrameCount = (pResampler->inTimeInt + outputFrameCount*pResampler->inAdvanceInt ) + preliminaryInputFrameCountFromFrac; - - /* - If the total number of *whole* input frames that would be required to generate our preliminary output frame count is greater than - the amount of whole input frames we have available as input we need to *not* add an extra output frame as there won't be enough data - to actually process. Otherwise we need to add the extra output frame. - */ - if (preliminaryInputFrameCount <= inputFrameCount) { - outputFrameCount += 1; - } - - *pOutputFrameCount = outputFrameCount; - - return MA_SUCCESS; + return ma_linear_resampler_get_input_latency(pResampler) * pResampler->sampleRateOut / pResampler->sampleRateIn; } MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) { ma_uint32 iChannel; + ma_uint32 iLPF; if (pResampler == NULL) { return MA_INVALID_ARGS; @@ -53834,25 +63938,97 @@ MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) pResampler->inTimeFrac = 0; /* Cached samples need to be cleared. */ - if (pResampler->config.format == ma_format_f32) { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { + if (pResampler->format == ma_format_f32) { + for (iChannel = 0; iChannel < pResampler->channels; iChannel += 1) { pResampler->x0.f32[iChannel] = 0; pResampler->x1.f32[iChannel] = 0; } } else { - for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { + for (iChannel = 0; iChannel < pResampler->channels; iChannel += 1) { pResampler->x0.s16[iChannel] = 0; pResampler->x1.s16[iChannel] = 0; } } /* The low pass filter needs to have its cache reset. */ - ma_lpf_clear_cache(&pResampler->lpf); + for (iLPF = 0; iLPF < pResampler->lpfOrder/2; iLPF += 1) { + if (pResampler->format == ma_format_f32) { + float* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_F32(pResampler, iLPF); + MA_ZERO_MEMORY(pLPF + 4, sizeof(float) * pResampler->channels * 2); + } else { + ma_int32* pLPF = MA_LINEAR_RESAMPLER_GET_LPF_STATE_S32(pResampler, iLPF); + MA_ZERO_MEMORY(pLPF + 4, sizeof(ma_int32) * pResampler->channels * 2); + } + } return MA_SUCCESS; } +static ma_result ma_linear_resampler_get_expected_output_frame_count_ex(ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_uint32 inTimeInt, ma_uint32 inTimeFrac, ma_uint32 inAdvanceInt, ma_uint32 inAdvanceFrac, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + ma_uint64 outputFrameCount; + ma_uint64 preliminaryInputFrameCountFromFrac; + ma_uint64 preliminaryInputFrameCount; + + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + + /* + The first step is to get a preliminary output frame count. This will either be exactly equal to what we need, or less by 1. We need to + determine how many input frames will be consumed by this value. If it's greater than our original input frame count it means we won't + be able to generate an extra frame because we will have run out of input data. Otherwise we will have enough input for the generation + of an extra output frame. This add-by-one logic is necessary due to how the data loading logic works when processing frames. + */ + outputFrameCount = (inputFrameCount * sampleRateOut) / sampleRateIn; + + /* + We need to determine how many *whole* input frames will have been processed to generate our preliminary output frame count. This is + used in the logic below to determine whether or not we need to add an extra output frame. + */ + preliminaryInputFrameCountFromFrac = (inTimeFrac + outputFrameCount*inAdvanceFrac) / sampleRateOut; + preliminaryInputFrameCount = (inTimeInt + outputFrameCount*inAdvanceInt ) + preliminaryInputFrameCountFromFrac; + + /* + If the total number of *whole* input frames that would be required to generate our preliminary output frame count is greater than + the amount of whole input frames we have available as input we need to *not* add an extra output frame as there won't be enough data + to actually process. Otherwise we need to add the extra output frame. + */ + if (preliminaryInputFrameCount <= inputFrameCount) { + outputFrameCount += 1; + } + + *pOutputFrameCount = outputFrameCount; + + return MA_SUCCESS; +} + +MA_API ma_uint64 ma_linear_resampler_calculate_frame_count_after_resampling(ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_uint64 frameCountIn) +{ + ma_result result; + ma_uint64 frameCountOut; + ma_uint32 inAdvanceInt; + ma_uint32 inAdvanceFrac; + + if (frameCountIn == 0) { + return 0; + } + + inAdvanceInt = sampleRateIn / sampleRateOut; + inAdvanceFrac = sampleRateIn % sampleRateOut; + + result = ma_linear_resampler_get_expected_output_frame_count_ex(sampleRateIn, sampleRateOut, 0, 0, inAdvanceInt, inAdvanceFrac, frameCountIn, &frameCountOut); + if (result != MA_SUCCESS) { + return 0; + } + + return frameCountOut; +} + + /* Linear resampler backend vtable. */ static ma_linear_resampler_config ma_resampling_backend_get_config__linear(const ma_resampler_config* pConfig) @@ -53931,20 +64107,6 @@ static ma_uint64 ma_resampling_backend_get_output_latency__linear(void* pUserDat return ma_linear_resampler_get_output_latency((const ma_linear_resampler*)pBackend); } -static ma_result ma_resampling_backend_get_required_input_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) -{ - (void)pUserData; - - return ma_linear_resampler_get_required_input_frame_count((const ma_linear_resampler*)pBackend, outputFrameCount, pInputFrameCount); -} - -static ma_result ma_resampling_backend_get_expected_output_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) -{ - (void)pUserData; - - return ma_linear_resampler_get_expected_output_frame_count((const ma_linear_resampler*)pBackend, inputFrameCount, pOutputFrameCount); -} - static ma_result ma_resampling_backend_reset__linear(void* pUserData, ma_resampling_backend* pBackend) { (void)pUserData; @@ -53961,8 +64123,6 @@ static ma_resampling_backend_vtable g_ma_linear_resampler_vtable = ma_resampling_backend_set_rate__linear, ma_resampling_backend_get_input_latency__linear, ma_resampling_backend_get_output_latency__linear, - ma_resampling_backend_get_required_input_frame_count__linear, - ma_resampling_backend_get_expected_output_frame_count__linear, ma_resampling_backend_reset__linear }; @@ -54132,13 +64292,161 @@ MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_ca } } +static ma_result ma_resampler_process_pcm_frames_no_input_output(ma_resampler* pResampler, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) +{ + ma_uint32 bpf = ma_get_bytes_per_frame(pResampler->format, pResampler->channels); + ma_uint8 framesIn[4096]; + ma_uint64 framesInCap = sizeof(framesIn) / bpf; + ma_uint8 framesOut[4096]; + ma_uint64 framesOutCap = sizeof(framesOut) / bpf; + ma_uint64 totalFramesProcessedIn; + ma_uint64 totalFramesProcessedOut; + ma_uint64 frameCountIn; + ma_uint64 frameCountOut; + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + + /* The input needs to be treated as silence. */ + MA_ZERO_MEMORY(framesIn, sizeof(framesIn)); + + totalFramesProcessedIn = 0; + totalFramesProcessedOut = 0; + + while (totalFramesProcessedIn < frameCountIn && totalFramesProcessedOut < frameCountOut) { + ma_result result; + ma_uint64 framesProcessedIn; + ma_uint64 framesProcessedOut; + + framesProcessedIn = frameCountIn - totalFramesProcessedIn; + if (framesProcessedIn > framesInCap) { + framesProcessedIn = framesInCap; + } + + framesProcessedOut = frameCountOut - totalFramesProcessedOut; + if (framesProcessedOut > framesOutCap) { + framesProcessedOut = framesOutCap; + } + + result = pResampler->pBackendVTable->onProcess(pResampler->pBackendUserData, pResampler->pBackend, framesIn, &framesProcessedIn, framesOut, &framesProcessedOut); + if (result != MA_SUCCESS) { + break; + } + + totalFramesProcessedIn += framesProcessedIn; + totalFramesProcessedOut += framesProcessedOut; + } + + *pFrameCountIn = totalFramesProcessedIn; + *pFrameCountOut = totalFramesProcessedOut; + + return MA_SUCCESS; +} + +static ma_result ma_resampler_process_pcm_frames_no_input(ma_resampler* pResampler, ma_uint64* pFrameCountIn, const void* pFramesOut, ma_uint64* pFrameCountOut) +{ + ma_uint32 bpf = ma_get_bytes_per_frame(pResampler->format, pResampler->channels); + ma_uint8 framesIn[4096]; + ma_uint64 framesInCap = sizeof(framesIn) / bpf; + ma_uint64 totalFramesProcessedIn; + ma_uint64 totalFramesProcessedOut; + ma_uint64 frameCountIn; + ma_uint64 frameCountOut; + + if (pFramesOut == NULL) { + return ma_resampler_process_pcm_frames_no_input_output(pResampler, pFrameCountIn, pFrameCountOut); + } + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + + /* The input needs to be treated as silence. */ + MA_ZERO_MEMORY(framesIn, sizeof(framesIn)); + + totalFramesProcessedIn = 0; + totalFramesProcessedOut = 0; + + while (totalFramesProcessedIn < frameCountIn && totalFramesProcessedOut < frameCountOut) { + ma_result result; + ma_uint64 framesProcessedIn; + ma_uint64 framesProcessedOut; + + framesProcessedIn = frameCountIn - totalFramesProcessedIn; + if (framesProcessedIn > framesInCap) { + framesProcessedIn = framesInCap; + } + + framesProcessedOut = frameCountOut - totalFramesProcessedOut; + + result = pResampler->pBackendVTable->onProcess(pResampler->pBackendUserData, pResampler->pBackend, framesIn, &framesProcessedIn, ma_offset_ptr(pFramesOut, totalFramesProcessedOut * bpf), &framesProcessedOut); + if (result != MA_SUCCESS) { + break; + } + + totalFramesProcessedIn += framesProcessedIn; + totalFramesProcessedOut += framesProcessedOut; + } + + *pFrameCountIn = totalFramesProcessedIn; + *pFrameCountOut = totalFramesProcessedOut; + + return MA_SUCCESS; +} + +static ma_result ma_resampler_process_pcm_frames_no_output(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) +{ + ma_uint32 bpf = ma_get_bytes_per_frame(pResampler->format, pResampler->channels); + ma_uint8 framesOut[4096]; + ma_uint64 framesOutCap = sizeof(framesOut) / bpf; + ma_uint64 totalFramesProcessedIn; + ma_uint64 totalFramesProcessedOut; + ma_uint64 frameCountIn; + ma_uint64 frameCountOut; + + if (pFramesIn == NULL) { + return ma_resampler_process_pcm_frames_no_input_output(pResampler, pFrameCountIn, pFrameCountOut); + } + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + + totalFramesProcessedIn = 0; + totalFramesProcessedOut = 0; + + while (totalFramesProcessedIn < frameCountIn && totalFramesProcessedOut < frameCountOut) { + ma_result result; + ma_uint64 framesProcessedIn; + ma_uint64 framesProcessedOut; + + framesProcessedIn = frameCountIn - totalFramesProcessedIn; + + framesProcessedOut = frameCountOut - totalFramesProcessedOut; + if (framesProcessedOut > framesOutCap) { + framesProcessedOut = framesOutCap; + } + + result = pResampler->pBackendVTable->onProcess(pResampler->pBackendUserData, pResampler->pBackend, ma_offset_ptr(pFramesIn, totalFramesProcessedIn * bpf), &framesProcessedIn, framesOut, &framesProcessedOut); + if (result != MA_SUCCESS) { + break; + } + + totalFramesProcessedIn += framesProcessedIn; + totalFramesProcessedOut += framesProcessedOut; + } + + *pFrameCountIn = totalFramesProcessedIn; + *pFrameCountOut = totalFramesProcessedOut; + + return MA_SUCCESS; +} + MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { if (pResampler == NULL) { return MA_INVALID_ARGS; } - if (pFrameCountOut == NULL && pFrameCountIn == NULL) { + if (pFrameCountOut == NULL || pFrameCountIn == NULL) { return MA_INVALID_ARGS; } @@ -54146,6 +64454,14 @@ MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const return MA_NOT_IMPLEMENTED; } + if (pFramesIn == NULL) { + return ma_resampler_process_pcm_frames_no_input(pResampler, pFrameCountIn, pFramesOut, pFrameCountOut); + } + + if (pFramesOut == NULL) { + return ma_resampler_process_pcm_frames_no_output(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); + } + return pResampler->pBackendVTable->onProcess(pResampler->pBackendUserData, pResampler->pBackend, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } @@ -54227,44 +64543,6 @@ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) return pResampler->pBackendVTable->onGetOutputLatency(pResampler->pBackendUserData, pResampler->pBackend); } -MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) -{ - if (pInputFrameCount == NULL) { - return MA_INVALID_ARGS; - } - - *pInputFrameCount = 0; - - if (pResampler == NULL) { - return MA_INVALID_ARGS; - } - - if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetRequiredInputFrameCount == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pResampler->pBackendVTable->onGetRequiredInputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, outputFrameCount, pInputFrameCount); -} - -MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) -{ - if (pOutputFrameCount == NULL) { - return MA_INVALID_ARGS; - } - - *pOutputFrameCount = 0; - - if (pResampler == NULL) { - return MA_INVALID_ARGS; - } - - if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetExpectedOutputFrameCount == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pResampler->pBackendVTable->onGetExpectedOutputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, inputFrameCount, pOutputFrameCount); -} - MA_API ma_result ma_resampler_reset(ma_resampler* pResampler) { if (pResampler == NULL) { @@ -55208,17 +65486,19 @@ static ma_result ma_channel_converter_get_heap_layout(const ma_channel_converter return MA_INVALID_ARGS; } + MA_ZERO_OBJECT(pHeapLayout); + pHeapLayout->sizeInBytes = 0; /* Input channel map. Only need to allocate this if we have an input channel map (otherwise default channel map is assumed). */ pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes; - if (pConfig->pChannelMapIn != NULL) { + if (pConfig->pChannelMapIn != NULL && pConfig->channelsIn > ma_countof(((ma_channel_converter*)0)->_smallChannelMapIn)) { pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsIn; } /* Output channel map. Only need to allocate this if we have an output channel map (otherwise default channel map is assumed). */ pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes; - if (pConfig->pChannelMapOut != NULL) { + if (pConfig->pChannelMapOut != NULL && pConfig->channelsOut > ma_countof(((ma_channel_converter*)0)->_smallChannelMapOut)) { pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsOut; } @@ -55293,14 +65573,24 @@ MA_API ma_result ma_channel_converter_init_preallocated(const ma_channel_convert pConverter->mixingMode = pConfig->mixingMode; if (pConfig->pChannelMapIn != NULL) { - pConverter->pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + if (pConfig->channelsIn > ma_countof(pConverter->_smallChannelMapIn)) { + pConverter->pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + } else { + pConverter->pChannelMapIn = pConverter->_smallChannelMapIn; + } + ma_channel_map_copy_or_default(pConverter->pChannelMapIn, pConfig->channelsIn, pConfig->pChannelMapIn, pConfig->channelsIn); } else { pConverter->pChannelMapIn = NULL; /* Use default channel map. */ } if (pConfig->pChannelMapOut != NULL) { - pConverter->pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + if (pConfig->channelsOut > ma_countof(pConverter->_smallChannelMapOut)) { + pConverter->pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + } else { + pConverter->pChannelMapOut = pConverter->_smallChannelMapOut; + } + ma_channel_map_copy_or_default(pConverter->pChannelMapOut, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelsOut); } else { pConverter->pChannelMapOut = NULL; /* Use default channel map. */ @@ -55916,7 +66206,7 @@ MA_API ma_data_converter_config ma_data_converter_config_init_default(void) config.allowDynamicSampleRate = MA_FALSE; /* Disable dynamic sample rates by default because dynamic rate adjustments should be quite rare and it allows an optimization for cases when the in and out sample rates are the same. */ /* Linear resampling defaults. */ - config.resampling.linear.lpfOrder = 1; + config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); return config; } @@ -55946,7 +66236,7 @@ static ma_bool32 ma_data_converter_config_is_resampler_required(const ma_data_co { MA_ASSERT(pConfig != NULL); - return pConfig->allowDynamicSampleRate || pConfig->sampleRateIn != pConfig->sampleRateOut; + return (pConfig->allowDynamicSampleRate == MA_TRUE) || (pConfig->sampleRateIn != pConfig->sampleRateOut); } static ma_format ma_data_converter_config_get_mid_format(const ma_data_converter_config* pConfig) @@ -56673,20 +66963,12 @@ static ma_result ma_data_converter_process_pcm_frames__resample_first(ma_data_co /* We need to ensure we don't try to process too many input frames that we run out of room in the output buffer. If this happens we'll end up glitching. */ /* - We need to try to predict how many input frames will be required for the resampler. If the - resampler can tell us, we'll use that. Otherwise we'll need to make a best guess. The further + We need to try to predict how many input frames will be required for the resampler. The further off we are from this, the more wasted format conversions we'll end up doing. */ #if 1 { - ma_uint64 requiredInputFrameCount; - - result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); - if (result != MA_SUCCESS) { - /* Fall back to a best guess. */ - requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; - } - + ma_uint64 requiredInputFrameCount = ((frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut) + 1; /* +1 to account for truncation from the division. */ if (frameCountInThisIteration > requiredInputFrameCount) { frameCountInThisIteration = requiredInputFrameCount; } @@ -56841,14 +67123,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co #if 1 { - ma_uint64 requiredInputFrameCount; - - result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); - if (result != MA_SUCCESS) { - /* Fall back to a best guess. */ - requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; - } - + ma_uint64 requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; if (frameCountInThisIteration > requiredInputFrameCount) { frameCountInThisIteration = requiredInputFrameCount; } @@ -56938,6 +67213,8 @@ MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConver MA_API ma_result ma_data_converter_set_rate(ma_data_converter* pConverter, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { + ma_result result; + if (pConverter == NULL) { return MA_INVALID_ARGS; } @@ -56946,7 +67223,15 @@ MA_API ma_result ma_data_converter_set_rate(ma_data_converter* pConverter, ma_ui return MA_INVALID_OPERATION; /* Dynamic resampling not enabled. */ } - return ma_resampler_set_rate(&pConverter->resampler, sampleRateIn, sampleRateOut); + result = ma_resampler_set_rate(&pConverter->resampler, sampleRateIn, sampleRateOut); + if (result != MA_SUCCESS) { + return result; + } + + pConverter->sampleRateIn = sampleRateIn; + pConverter->sampleRateOut = sampleRateOut; + + return MA_SUCCESS; } MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, float ratioInOut) @@ -56988,46 +67273,6 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* p return 0; /* No latency without a resampler. */ } -MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) -{ - if (pInputFrameCount == NULL) { - return MA_INVALID_ARGS; - } - - *pInputFrameCount = 0; - - if (pConverter == NULL) { - return MA_INVALID_ARGS; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_required_input_frame_count(&pConverter->resampler, outputFrameCount, pInputFrameCount); - } else { - *pInputFrameCount = outputFrameCount; /* 1:1 */ - return MA_SUCCESS; - } -} - -MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) -{ - if (pOutputFrameCount == NULL) { - return MA_INVALID_ARGS; - } - - *pOutputFrameCount = 0; - - if (pConverter == NULL) { - return MA_INVALID_ARGS; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_expected_output_frame_count(&pConverter->resampler, inputFrameCount, pOutputFrameCount); - } else { - *pOutputFrameCount = inputFrameCount; /* 1:1 */ - return MA_SUCCESS; - } -} - MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) { if (pConverter == NULL || pChannelMap == NULL) { @@ -57035,7 +67280,7 @@ MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter } if (pConverter->hasChannelConverter) { - ma_channel_converter_get_output_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + ma_channel_converter_get_input_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); } else { ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsOut); } @@ -57050,7 +67295,7 @@ MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converte } if (pConverter->hasChannelConverter) { - ma_channel_converter_get_input_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + ma_channel_converter_get_output_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); } else { ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsIn); } @@ -58065,23 +68310,23 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const } if (pOut == NULL) { - result = ma_data_converter_get_expected_output_frame_count(&converter, frameCountIn, &frameCountOut); - if (result != MA_SUCCESS) { - if (result == MA_NOT_IMPLEMENTED) { - /* No way to calculate the number of frames, so we'll need to brute force it and loop. */ - frameCountOut = 0; - - while (frameCountIn > 0) { - ma_uint64 framesProcessedIn = frameCountIn; - ma_uint64 framesProcessedOut = 0xFFFFFFFF; + if (converter.sampleRateIn == converter.sampleRateOut) { + frameCountOut = frameCountIn; + } else { + /* No way to calculate the number of frames, so we'll need to brute force it and loop. */ + frameCountOut = 0; - result = ma_data_converter_process_pcm_frames(&converter, pIn, &framesProcessedIn, NULL, &framesProcessedOut); - if (result != MA_SUCCESS) { - break; - } + while (frameCountIn > 0) { + ma_uint64 framesProcessedIn = frameCountIn; + ma_uint64 framesProcessedOut = 0xFFFFFFFF; - frameCountIn -= framesProcessedIn; + result = ma_data_converter_process_pcm_frames(&converter, pIn, &framesProcessedIn, NULL, &framesProcessedOut); + if (result != MA_SUCCESS) { + break; } + + frameCountIn -= framesProcessedIn; + frameCountOut += framesProcessedOut; } } } else { @@ -58101,822 +68346,680 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const Ring Buffer **************************************************************************************************************************************************************/ -static MA_INLINE ma_uint32 ma_rb__extract_offset_in_bytes(ma_uint32 encodedOffset) -{ - return encodedOffset & 0x7FFFFFFF; -} - -static MA_INLINE ma_uint32 ma_rb__extract_offset_loop_flag(ma_uint32 encodedOffset) -{ - return encodedOffset & 0x80000000; -} - -static MA_INLINE void* ma_rb__get_read_ptr(ma_rb* pRB) -{ - MA_ASSERT(pRB != NULL); - return ma_offset_ptr(pRB->pBuffer, ma_rb__extract_offset_in_bytes(ma_atomic_load_32(&pRB->encodedReadOffset))); -} - -static MA_INLINE void* ma_rb__get_write_ptr(ma_rb* pRB) +/* BEG ma_ring_buffer.c */ +MA_API void ma_ring_buffer_init(ma_uint32 capacity, ma_uint32 stride, ma_uint32 flags, void* pBuffer, ma_ring_buffer* pRingBuffer) { - MA_ASSERT(pRB != NULL); - return ma_offset_ptr(pRB->pBuffer, ma_rb__extract_offset_in_bytes(ma_atomic_load_32(&pRB->encodedWriteOffset))); -} - -static MA_INLINE ma_uint32 ma_rb__construct_offset(ma_uint32 offsetInBytes, ma_uint32 offsetLoopFlag) -{ - return offsetLoopFlag | offsetInBytes; -} - -static MA_INLINE void ma_rb__deconstruct_offset(ma_uint32 encodedOffset, ma_uint32* pOffsetInBytes, ma_uint32* pOffsetLoopFlag) -{ - MA_ASSERT(pOffsetInBytes != NULL); - MA_ASSERT(pOffsetLoopFlag != NULL); - - *pOffsetInBytes = ma_rb__extract_offset_in_bytes(encodedOffset); - *pOffsetLoopFlag = ma_rb__extract_offset_loop_flag(encodedOffset); -} - - -MA_API ma_result ma_rb_init_ex(size_t subbufferSizeInBytes, size_t subbufferCount, size_t subbufferStrideInBytes, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_rb* pRB) -{ - ma_result result; - const ma_uint32 maxSubBufferSize = 0x7FFFFFFF - (MA_SIMD_ALIGNMENT-1); - - if (pRB == NULL) { - return MA_INVALID_ARGS; + if (pRingBuffer == NULL) { + return; } - if (subbufferSizeInBytes == 0 || subbufferCount == 0) { - return MA_INVALID_ARGS; - } + ma_atomic_store_explicit_32(&pRingBuffer->head, 0, ma_atomic_memory_order_relaxed); + ma_atomic_store_explicit_32(&pRingBuffer->tail, 0, ma_atomic_memory_order_relaxed); + pRingBuffer->capacity = 0; + pRingBuffer->stride = 0; + pRingBuffer->flags = 0; + pRingBuffer->pBuffer = NULL; - if (subbufferSizeInBytes > maxSubBufferSize) { - return MA_INVALID_ARGS; /* Maximum buffer size is ~2GB. The most significant bit is a flag for use internally. */ + if (pBuffer == NULL || stride == 0 || capacity == 0) { + MA_ASSERT(!"Ring buffer initialized with invalid values. It must have a valid buffer, stride and capacity."); + return; } - - MA_ZERO_OBJECT(pRB); - - result = ma_allocation_callbacks_init_copy(&pRB->allocationCallbacks, pAllocationCallbacks); - if (result != MA_SUCCESS) { - return result; + if (capacity > 0x7FFFFFFF) { + MA_ASSERT(!"Ring buffer capacity exceeds limit of 0x7FFFFFFF."); + return; } - pRB->subbufferSizeInBytes = (ma_uint32)subbufferSizeInBytes; - pRB->subbufferCount = (ma_uint32)subbufferCount; - - if (pOptionalPreallocatedBuffer != NULL) { - pRB->subbufferStrideInBytes = (ma_uint32)subbufferStrideInBytes; - pRB->pBuffer = pOptionalPreallocatedBuffer; - } else { - size_t bufferSizeInBytes; - - /* - Here is where we allocate our own buffer. We always want to align this to MA_SIMD_ALIGNMENT for future SIMD optimization opportunity. To do this - we need to make sure the stride is a multiple of MA_SIMD_ALIGNMENT. - */ - pRB->subbufferStrideInBytes = ma_align(pRB->subbufferSizeInBytes, MA_SIMD_ALIGNMENT); - - bufferSizeInBytes = (size_t)pRB->subbufferCount*pRB->subbufferStrideInBytes; - pRB->pBuffer = ma_aligned_malloc(bufferSizeInBytes, MA_SIMD_ALIGNMENT, &pRB->allocationCallbacks); - if (pRB->pBuffer == NULL) { - return MA_OUT_OF_MEMORY; - } - - MA_ZERO_MEMORY(pRB->pBuffer, bufferSizeInBytes); - pRB->ownsBuffer = MA_TRUE; + /* + When offsetting pointers when mapping we multiply our cursors by the stride. Cursors are clamped to the + capacity. We therefore want to make sure the multiplication of the capacity by the stride does not + overflow our 32-bit range. We could use the maximum value of a size_t here instead, but then there is + slightly different behaviour between 32- and 64-bit builds. For the purpose of this ring buffer, I would + prefer this be totally consistent between architectures so I'm enforcing a 32-bit maximum. + */ + if (capacity > (0xFFFFFFFF / stride)) { + MA_ASSERT(!"Ring buffer capacity multiplied by the stride exceeds the enforced 32-bit limit of 0xFFFFFFFF."); + return; } + - return MA_SUCCESS; + pRingBuffer->capacity = capacity; + pRingBuffer->stride = stride; + pRingBuffer->flags = flags; + pRingBuffer->pBuffer = pBuffer; } -MA_API ma_result ma_rb_init(size_t bufferSizeInBytes, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_rb* pRB) +static MA_INLINE ma_uint32 ma_ring_buffer_calculate_length(ma_uint32 head, ma_uint32 tail, ma_uint32 capacity) { - return ma_rb_init_ex(bufferSizeInBytes, 1, 0, pOptionalPreallocatedBuffer, pAllocationCallbacks, pRB); -} + /* + Our ring buffer encodes a "loop flag" in the head and tail cursors. The flag flip-flops as the cursor + loops around and is basically just used to disambiguate the case when the head and tail are equal (are + they the same because the buffer is empty, or because it's full). + + The head should always be ahead of the tail, and by no more than `capacity`. This algorithm asserts + this rule. Logically it follows that the length is simply the head minus the tail. However, since the + cursors loop, there are times when the tail will be greater than the head which will mess up the math. + To make the math work we need to adjust the head to ensure it's always ahead of the tail. + + When the two cursors are on the same loop there's nothing complicated - just do the subtraction like + normal. When they're on different loops we need only offset the head by the capacity before doing the + subtraction. This can all be done without a branch. + */ + #if 0 + { + ma_uint32 loopFlag = (head & 0x80000000) ^ (tail & 0x80000000); -MA_API void ma_rb_uninit(ma_rb* pRB) -{ - if (pRB == NULL) { - return; + if (loopFlag) { + return ((head & 0x7FFFFFFF) + capacity) - (tail & 0x7FFFFFFF); + } else { + return head - tail; + } } - - if (pRB->ownsBuffer) { - ma_aligned_free(pRB->pBuffer, &pRB->allocationCallbacks); + #else + { + return ((head & 0x7FFFFFFF) + (capacity * (((head & 0x80000000) ^ (tail & 0x80000000)) >> 31))) - (tail & 0x7FFFFFFF); } + #endif } -MA_API void ma_rb_reset(ma_rb* pRB) +static MA_INLINE ma_uint32 ma_ring_buffer_calculate_remaining(ma_uint32 head, ma_uint32 tail, ma_uint32 capacity) { - if (pRB == NULL) { - return; - } - - ma_atomic_exchange_32(&pRB->encodedReadOffset, 0); - ma_atomic_exchange_32(&pRB->encodedWriteOffset, 0); + return capacity - ma_ring_buffer_calculate_length(head, tail, capacity); } -MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut) +MA_API ma_uint32 ma_ring_buffer_map_produce(ma_ring_buffer* pRingBuffer, ma_uint32 count, void** ppMappedBuffer) { - ma_uint32 writeOffset; - ma_uint32 writeOffsetInBytes; - ma_uint32 writeOffsetLoopFlag; - ma_uint32 readOffset; - ma_uint32 readOffsetInBytes; - ma_uint32 readOffsetLoopFlag; - size_t bytesAvailable; - size_t bytesRequested; + ma_uint32 head; + ma_uint32 tail; + ma_uint32 remaining; - if (pRB == NULL || pSizeInBytes == NULL || ppBufferOut == NULL) { - return MA_INVALID_ARGS; + if (ppMappedBuffer == NULL) { + return 0; } - /* The returned buffer should never move ahead of the write pointer. */ - writeOffset = ma_atomic_load_32(&pRB->encodedWriteOffset); - ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + *ppMappedBuffer = NULL; - readOffset = ma_atomic_load_32(&pRB->encodedReadOffset); - ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + if (pRingBuffer == NULL) { + return 0; + } /* - The number of bytes available depends on whether or not the read and write pointers are on the same loop iteration. If so, we - can only read up to the write pointer. If not, we can only read up to the end of the buffer. + For the head, only the producer will be making modifications to it so we can just use relaxed. For the tail + we need to ensure the producer does not overwrite anything that the consumer is still reading so we'll need + to use acquire semantics here (it will be released when the consumer is unmapped). */ - if (readOffsetLoopFlag == writeOffsetLoopFlag) { - bytesAvailable = writeOffsetInBytes - readOffsetInBytes; - } else { - bytesAvailable = pRB->subbufferSizeInBytes - readOffsetInBytes; - } + head = ma_atomic_load_explicit_32(&pRingBuffer->head, ma_atomic_memory_order_relaxed); + tail = ma_atomic_load_explicit_32(&pRingBuffer->tail, ma_atomic_memory_order_acquire); - bytesRequested = *pSizeInBytes; - if (bytesRequested > bytesAvailable) { - bytesRequested = bytesAvailable; + /* Now we need to clamp the count to ensure it never goes beyond our capacity. */ + remaining = ma_ring_buffer_calculate_remaining(head, tail, pRingBuffer->capacity); + if (count > remaining) { + count = remaining; } - *pSizeInBytes = bytesRequested; - (*ppBufferOut) = ma_rb__get_read_ptr(pRB); + if (count > 0) { + /* Our pointer will always just be where our head is pointing. */ + *ppMappedBuffer = ma_offset_ptr(pRingBuffer->pBuffer, (head & 0x7FFFFFFF) * pRingBuffer->stride); + } - return MA_SUCCESS; + return count; } -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes) +MA_API void ma_ring_buffer_unmap_produce(ma_ring_buffer* pRingBuffer, ma_uint32 count) { - ma_uint32 readOffset; - ma_uint32 readOffsetInBytes; - ma_uint32 readOffsetLoopFlag; - ma_uint32 newReadOffsetInBytes; - ma_uint32 newReadOffsetLoopFlag; - - if (pRB == NULL) { - return MA_INVALID_ARGS; - } - - readOffset = ma_atomic_load_32(&pRB->encodedReadOffset); - ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + ma_uint32 head; + ma_uint32 tail; - /* Check that sizeInBytes is correct. It should never go beyond the end of the buffer. */ - newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + sizeInBytes); - if (newReadOffsetInBytes > pRB->subbufferSizeInBytes) { - return MA_INVALID_ARGS; /* <-- sizeInBytes will cause the read offset to overflow. */ + if (pRingBuffer == NULL) { + return; } - /* Move the read pointer back to the start if necessary. */ - newReadOffsetLoopFlag = readOffsetLoopFlag; - if (newReadOffsetInBytes == pRB->subbufferSizeInBytes) { - newReadOffsetInBytes = 0; - newReadOffsetLoopFlag ^= 0x80000000; - } + MA_ASSERT(count <= pRingBuffer->capacity); - ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetInBytes, newReadOffsetLoopFlag)); + head = ma_atomic_load_explicit_32(&pRingBuffer->head, ma_atomic_memory_order_relaxed); + tail = ma_atomic_load_explicit_32(&pRingBuffer->tail, ma_atomic_memory_order_relaxed); - return MA_SUCCESS; -} + MA_ASSERT(count <= ma_ring_buffer_calculate_remaining(head, tail, pRingBuffer->capacity)); -MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut) -{ - ma_uint32 readOffset; - ma_uint32 readOffsetInBytes; - ma_uint32 readOffsetLoopFlag; - ma_uint32 writeOffset; - ma_uint32 writeOffsetInBytes; - ma_uint32 writeOffsetLoopFlag; - size_t bytesAvailable; - size_t bytesRequested; + /* The tail is not modified. We load it for the benefit of the assert above. */ + (void)tail; - if (pRB == NULL || pSizeInBytes == NULL || ppBufferOut == NULL) { - return MA_INVALID_ARGS; + /* If the buffer is not mirrored we need to copy any overflow to the start of the ring buffer. */ + if ((pRingBuffer->flags & MA_RING_BUFFER_FLAG_MIRRORED) == 0) { + ma_uint32 newHead = (head & 0x7FFFFFFF) + count; + if (newHead > pRingBuffer->capacity) { + newHead -= pRingBuffer->capacity; + MA_COPY_MEMORY(pRingBuffer->pBuffer, ma_offset_ptr(pRingBuffer->pBuffer, pRingBuffer->capacity * pRingBuffer->stride), newHead * pRingBuffer->stride); + } } - /* The returned buffer should never overtake the read buffer. */ - readOffset = ma_atomic_load_32(&pRB->encodedReadOffset); - ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); + /* Advance the head. */ + head += count; - writeOffset = ma_atomic_load_32(&pRB->encodedWriteOffset); - ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + /* Check if the head has looped and adjust if so. */ + if ((head & 0x7FFFFFFF) >= pRingBuffer->capacity) { + head -= pRingBuffer->capacity; /* Get the index back into range. */ + head ^= 0x80000000; /* Flip the loop flag. */ + } /* - In the case of writing, if the write pointer and the read pointer are on the same loop iteration we can only - write up to the end of the buffer. Otherwise we can only write up to the read pointer. The write pointer should - never overtake the read pointer. + The consumer will be wanting to read from the buffer that we just wrote so we'll need to use + release semantics here to ensure the consumer does not see the adjustment to the advanced head + until after the data has been written. */ - if (writeOffsetLoopFlag == readOffsetLoopFlag) { - bytesAvailable = pRB->subbufferSizeInBytes - writeOffsetInBytes; - } else { - bytesAvailable = readOffsetInBytes - writeOffsetInBytes; - } - - bytesRequested = *pSizeInBytes; - if (bytesRequested > bytesAvailable) { - bytesRequested = bytesAvailable; - } - - *pSizeInBytes = bytesRequested; - *ppBufferOut = ma_rb__get_write_ptr(pRB); - - /* Clear the buffer if desired. */ - if (pRB->clearOnWriteAcquire) { - MA_ZERO_MEMORY(*ppBufferOut, *pSizeInBytes); - } - - return MA_SUCCESS; + ma_atomic_store_explicit_32(&pRingBuffer->head, head, ma_atomic_memory_order_release); } -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes) +MA_API ma_uint32 ma_ring_buffer_map_consume(ma_ring_buffer* pRingBuffer, ma_uint32 count, void** ppMappedBuffer) { - ma_uint32 writeOffset; - ma_uint32 writeOffsetInBytes; - ma_uint32 writeOffsetLoopFlag; - ma_uint32 newWriteOffsetInBytes; - ma_uint32 newWriteOffsetLoopFlag; + ma_uint32 head; + ma_uint32 tail; + ma_uint32 length; - if (pRB == NULL) { - return MA_INVALID_ARGS; + if (ppMappedBuffer == NULL) { + return 0; } - writeOffset = ma_atomic_load_32(&pRB->encodedWriteOffset); - ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + *ppMappedBuffer = NULL; - /* Check that sizeInBytes is correct. It should never go beyond the end of the buffer. */ - newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + sizeInBytes); - if (newWriteOffsetInBytes > pRB->subbufferSizeInBytes) { - return MA_INVALID_ARGS; /* <-- sizeInBytes will cause the read offset to overflow. */ - } - - /* Move the read pointer back to the start if necessary. */ - newWriteOffsetLoopFlag = writeOffsetLoopFlag; - if (newWriteOffsetInBytes == pRB->subbufferSizeInBytes) { - newWriteOffsetInBytes = 0; - newWriteOffsetLoopFlag ^= 0x80000000; + if (pRingBuffer == NULL) { + return 0; } - ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetInBytes, newWriteOffsetLoopFlag)); - - return MA_SUCCESS; -} - -MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes) -{ - ma_uint32 readOffset; - ma_uint32 readOffsetInBytes; - ma_uint32 readOffsetLoopFlag; - ma_uint32 writeOffset; - ma_uint32 writeOffsetInBytes; - ma_uint32 writeOffsetLoopFlag; - ma_uint32 newReadOffsetInBytes; - ma_uint32 newReadOffsetLoopFlag; + /* + We're about to read data that was written by the producer. We'll need to use acquire semantics + here for the head. + */ + head = ma_atomic_load_explicit_32(&pRingBuffer->head, ma_atomic_memory_order_acquire); + tail = ma_atomic_load_explicit_32(&pRingBuffer->tail, ma_atomic_memory_order_relaxed); - if (pRB == NULL || offsetInBytes > pRB->subbufferSizeInBytes) { - return MA_INVALID_ARGS; + /* Make sure we don't try to consume more than what we have available for consumption. */ + length = ma_ring_buffer_calculate_length(head, tail, pRingBuffer->capacity); + if (count > length) { + count = length; } - readOffset = ma_atomic_load_32(&pRB->encodedReadOffset); - ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); - - writeOffset = ma_atomic_load_32(&pRB->encodedWriteOffset); - ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + if (count > 0) { + /* Our pointer will always just be where our tail is pointing. */ + *ppMappedBuffer = ma_offset_ptr(pRingBuffer->pBuffer, (tail & 0x7FFFFFFF) * pRingBuffer->stride); - newReadOffsetLoopFlag = readOffsetLoopFlag; - - /* We cannot go past the write buffer. */ - if (readOffsetLoopFlag == writeOffsetLoopFlag) { - if ((readOffsetInBytes + offsetInBytes) > writeOffsetInBytes) { - newReadOffsetInBytes = writeOffsetInBytes; - } else { - newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + offsetInBytes); - } - } else { - /* May end up looping. */ - if ((readOffsetInBytes + offsetInBytes) >= pRB->subbufferSizeInBytes) { - newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + offsetInBytes) - pRB->subbufferSizeInBytes; - newReadOffsetLoopFlag ^= 0x80000000; /* <-- Looped. */ - } else { - newReadOffsetInBytes = (ma_uint32)(readOffsetInBytes + offsetInBytes); + /* + If the buffer is not mirrored we may need to copy some data from the start of the buffer to the overflow + part so the caller has a contiguous block to work with. + */ + if ((pRingBuffer->flags & MA_RING_BUFFER_FLAG_MIRRORED) == 0) { + ma_uint32 newTail = (tail & 0x7FFFFFFF) + count; + if (newTail > pRingBuffer->capacity) { + newTail -= pRingBuffer->capacity; + MA_COPY_MEMORY(ma_offset_ptr(pRingBuffer->pBuffer, pRingBuffer->capacity * pRingBuffer->stride), pRingBuffer->pBuffer, newTail * pRingBuffer->stride); + } } } - ma_atomic_exchange_32(&pRB->encodedReadOffset, ma_rb__construct_offset(newReadOffsetInBytes, newReadOffsetLoopFlag)); - return MA_SUCCESS; + return count; } -MA_API ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes) +MA_API void ma_ring_buffer_unmap_consume(ma_ring_buffer* pRingBuffer, ma_uint32 count) { - ma_uint32 readOffset; - ma_uint32 readOffsetInBytes; - ma_uint32 readOffsetLoopFlag; - ma_uint32 writeOffset; - ma_uint32 writeOffsetInBytes; - ma_uint32 writeOffsetLoopFlag; - ma_uint32 newWriteOffsetInBytes; - ma_uint32 newWriteOffsetLoopFlag; + ma_uint32 head; + ma_uint32 tail; - if (pRB == NULL) { - return MA_INVALID_ARGS; + if (pRingBuffer == NULL) { + return; } - readOffset = ma_atomic_load_32(&pRB->encodedReadOffset); - ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); - - writeOffset = ma_atomic_load_32(&pRB->encodedWriteOffset); - ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); + MA_ASSERT(count <= pRingBuffer->capacity); - newWriteOffsetLoopFlag = writeOffsetLoopFlag; + /* + When we first mapped the buffer with `map_consume()` we used acquire semantics for the head. Since we won't + be touching any data that was produced between our map and unmap, we should be able to use relaxed here. + */ + head = ma_atomic_load_explicit_32(&pRingBuffer->head, ma_atomic_memory_order_relaxed); + tail = ma_atomic_load_explicit_32(&pRingBuffer->tail, ma_atomic_memory_order_relaxed); - /* We cannot go past the write buffer. */ - if (readOffsetLoopFlag == writeOffsetLoopFlag) { - /* May end up looping. */ - if ((writeOffsetInBytes + offsetInBytes) >= pRB->subbufferSizeInBytes) { - newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + offsetInBytes) - pRB->subbufferSizeInBytes; - newWriteOffsetLoopFlag ^= 0x80000000; /* <-- Looped. */ - } else { - newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + offsetInBytes); - } - } else { - if ((writeOffsetInBytes + offsetInBytes) > readOffsetInBytes) { - newWriteOffsetInBytes = readOffsetInBytes; - } else { - newWriteOffsetInBytes = (ma_uint32)(writeOffsetInBytes + offsetInBytes); - } - } + MA_ASSERT(count <= ma_ring_buffer_calculate_length(head, tail, pRingBuffer->capacity)); - ma_atomic_exchange_32(&pRB->encodedWriteOffset, ma_rb__construct_offset(newWriteOffsetInBytes, newWriteOffsetLoopFlag)); - return MA_SUCCESS; -} + /* The head is not modified. We load it for the benefit of the assert above. */ + (void)head; -MA_API ma_int32 ma_rb_pointer_distance(ma_rb* pRB) -{ - ma_uint32 readOffset; - ma_uint32 readOffsetInBytes; - ma_uint32 readOffsetLoopFlag; - ma_uint32 writeOffset; - ma_uint32 writeOffsetInBytes; - ma_uint32 writeOffsetLoopFlag; + /* Advance the tail. */ + tail += count; - if (pRB == NULL) { - return 0; + /* Check if the tail has looped and adjust if so. */ + if ((tail & 0x7FFFFFFF) >= pRingBuffer->capacity) { + tail -= pRingBuffer->capacity; /* Get the index back into range. */ + tail ^= 0x80000000; /* Flip the loop flag. */ } - readOffset = ma_atomic_load_32(&pRB->encodedReadOffset); - ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); - - writeOffset = ma_atomic_load_32(&pRB->encodedWriteOffset); - ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); - - if (readOffsetLoopFlag == writeOffsetLoopFlag) { - return writeOffsetInBytes - readOffsetInBytes; - } else { - return writeOffsetInBytes + (pRB->subbufferSizeInBytes - readOffsetInBytes); - } + /* + The producer will be using acquire semantics for the tail, so we'll want to mirror that with a release here. If + we were to use relaxed here we would run the risk of the producer overwriting data before the consumer has + finished reading it. + */ + ma_atomic_store_explicit_32(&pRingBuffer->tail, tail, ma_atomic_memory_order_release); } -MA_API ma_uint32 ma_rb_available_read(ma_rb* pRB) +MA_API ma_uint32 ma_ring_buffer_length(const ma_ring_buffer* pRingBuffer) { - ma_int32 dist; + ma_uint32 head; + ma_uint32 tail; + ma_uint32 length; - if (pRB == NULL) { + if (pRingBuffer == NULL) { return 0; } - dist = ma_rb_pointer_distance(pRB); - if (dist < 0) { - return 0; - } + head = ma_atomic_load_explicit_32(&pRingBuffer->head, ma_atomic_memory_order_relaxed); + tail = ma_atomic_load_explicit_32(&pRingBuffer->tail, ma_atomic_memory_order_relaxed); - return dist; -} + length = ma_ring_buffer_calculate_length(head, tail, pRingBuffer->capacity); -MA_API ma_uint32 ma_rb_available_write(ma_rb* pRB) -{ - if (pRB == NULL) { - return 0; + /* Do a clamp just in case the caller violates the API contract and calls this from a non-consumer and non-producer thread. */ + if (length > pRingBuffer->capacity) { + length = pRingBuffer->capacity; } - return (ma_uint32)(ma_rb_get_subbuffer_size(pRB) - ma_rb_pointer_distance(pRB)); + return length; } -MA_API size_t ma_rb_get_subbuffer_size(ma_rb* pRB) +MA_API ma_uint32 ma_ring_buffer_capacity(const ma_ring_buffer* pRingBuffer) { - if (pRB == NULL) { + if (pRingBuffer == NULL) { return 0; } - return pRB->subbufferSizeInBytes; + return pRingBuffer->capacity; } +/* END ma_ring_buffer.c */ -MA_API size_t ma_rb_get_subbuffer_stride(ma_rb* pRB) -{ - if (pRB == NULL) { - return 0; - } - if (pRB->subbufferStrideInBytes == 0) { - return (size_t)pRB->subbufferSizeInBytes; - } - - return (size_t)pRB->subbufferStrideInBytes; +static size_t ma_audio_ring_buffer__data_source_on_sizeof(void) +{ + return sizeof(ma_audio_ring_buffer); } -MA_API size_t ma_rb_get_subbuffer_offset(ma_rb* pRB, size_t subbufferIndex) +static void ma_audio_ring_buffer__data_source_on_uninit(ma_data_source* pDataSource) { - if (pRB == NULL) { - return 0; - } - - return subbufferIndex * ma_rb_get_subbuffer_stride(pRB); + ma_audio_ring_buffer_uninit((ma_audio_ring_buffer*)pDataSource); } -MA_API void* ma_rb_get_subbuffer_ptr(ma_rb* pRB, size_t subbufferIndex, void* pBuffer) +static ma_result ma_audio_ring_buffer__data_source_on_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) { - if (pRB == NULL) { - return NULL; - } - - return ma_offset_ptr(pBuffer, ma_rb_get_subbuffer_offset(pRB, subbufferIndex)); + return ma_audio_ring_buffer_init_copy((ma_audio_ring_buffer*)pDataSource, (ma_audio_ring_buffer*)pNewDataSource); } - - -static ma_result ma_pcm_rb_data_source__on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +static ma_result ma_audio_ring_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFrames, ma_uint64 frameCount, ma_uint64* pFramesRead) { - /* Since there's no notion of an end, we don't ever want to return MA_AT_END here. But it is possible to return 0. */ - ma_pcm_rb* pRB = (ma_pcm_rb*)pDataSource; + ma_audio_ring_buffer* pRingBuffer = (ma_audio_ring_buffer*)pDataSource; ma_result result; - ma_uint64 totalFramesRead; - - MA_ASSERT(pRB != NULL); - - /* We need to run this in a loop since the ring buffer itself may loop. */ - totalFramesRead = 0; - while (totalFramesRead < frameCount) { - void* pMappedBuffer; - ma_uint32 mappedFrameCount; - ma_uint64 framesToRead = frameCount - totalFramesRead; - if (framesToRead > 0xFFFFFFFF) { - framesToRead = 0xFFFFFFFF; - } - - mappedFrameCount = (ma_uint32)framesToRead; - result = ma_pcm_rb_acquire_read(pRB, &mappedFrameCount, &pMappedBuffer); - if (result != MA_SUCCESS) { - break; - } - - if (mappedFrameCount == 0) { - break; /* <-- End of ring buffer. */ - } - - ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, pRB->format, pRB->channels), pMappedBuffer, mappedFrameCount, pRB->format, pRB->channels); - - result = ma_pcm_rb_commit_read(pRB, mappedFrameCount); - if (result != MA_SUCCESS) { - break; - } + ma_uint32 framesRead; + + if (frameCount > 0xFFFFFFFF) { + frameCount = 0xFFFFFFFF; + } - totalFramesRead += mappedFrameCount; + result = ma_audio_ring_buffer_read_pcm_frames(pRingBuffer, pFrames, (ma_uint32)frameCount, &framesRead); /* Safe cast due to the check above. */ + if (result != MA_SUCCESS) { + return result; } /* There is no notion of an "end" in a ring buffer. If we didn't have enough data to fill the requested frame - count we'll need to pad with silence. If we don't do this, totalFramesRead might equal 0 which will result - in the data source layer at a higher level translating this to MA_AT_END which is incorrect for a ring buffer. + count we'll need to pad with silence. If we don't do this, framesRead might equal 0 which will result in + the data source layer at a higher level translating this to MA_AT_END which is incorrect for a ring buffer. */ - if (totalFramesRead < frameCount) { - ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, pRB->format, pRB->channels), (frameCount - totalFramesRead), pRB->format, pRB->channels); - totalFramesRead = frameCount; + if (framesRead < (ma_uint32)frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFrames, framesRead, pRingBuffer->format, pRingBuffer->channels), (frameCount - framesRead), pRingBuffer->format, pRingBuffer->channels); + framesRead = (ma_uint32)frameCount; } - *pFramesRead = totalFramesRead; + *pFramesRead = framesRead; + return MA_SUCCESS; } -static ma_result ma_pcm_rb_data_source__on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_audio_ring_buffer__data_source_on_prop(ma_data_source* pDataSource, int prop, void* pData) { - ma_pcm_rb* pRB = (ma_pcm_rb*)pDataSource; - MA_ASSERT(pRB != NULL); + ma_audio_ring_buffer* pRingBuffer = (ma_audio_ring_buffer*)pDataSource; - if (pFormat != NULL) { - *pFormat = pRB->format; - } + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; - if (pChannels != NULL) { - *pChannels = pRB->channels; - } + pDataFormat->format = pRingBuffer->format; + pDataFormat->channels = pRingBuffer->channels; + pDataFormat->sampleRate = pRingBuffer->sampleRate; - if (pSampleRate != NULL) { - *pSampleRate = pRB->sampleRate; - } + return MA_SUCCESS; + } - /* Just assume the default channel map. */ - if (pChannelMap != NULL) { - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pRB->channels); + case MA_DATA_SOURCE_GET_LENGTH: + { + ma_result result; + ma_uint32 length; + + result = ma_audio_ring_buffer_get_length_in_pcm_frames(pRingBuffer, &length); + if (result != MA_SUCCESS) { + return result; + } + + *((ma_uint64*)pData) = length; + + return MA_SUCCESS; + } + + default: break; } - return MA_SUCCESS; + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable ma_gRBDataSourceVTable = +static ma_data_source_vtable ma_gDataSourceVTable_AudioRingBuffer = { - ma_pcm_rb_data_source__on_read, - NULL, /* onSeek */ - ma_pcm_rb_data_source__on_get_data_format, - NULL, /* onGetCursor */ - NULL, /* onGetLength */ - NULL, /* onSetLooping */ - 0 + ma_audio_ring_buffer__data_source_on_sizeof, + ma_audio_ring_buffer__data_source_on_uninit, + ma_audio_ring_buffer__data_source_on_copy, + ma_audio_ring_buffer__data_source_on_read, + NULL, /* No seeking in ring buffers. */ + ma_audio_ring_buffer__data_source_on_prop }; -static MA_INLINE ma_uint32 ma_pcm_rb_get_bpf(ma_pcm_rb* pRB) + +MA_API ma_audio_ring_buffer_config ma_audio_ring_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames) { - MA_ASSERT(pRB != NULL); + ma_audio_ring_buffer_config config; - return ma_get_bytes_per_frame(pRB->format, pRB->channels); + MA_ZERO_OBJECT(&config); + config.format = format; + config.channels = channels; + config.sampleRate = sampleRate; + config.sizeInFrames = sizeInFrames; + config.flags = 0; + config.pBuffer = NULL; + + return config; } -MA_API ma_result ma_pcm_rb_init_ex(ma_format format, ma_uint32 channels, ma_uint32 subbufferSizeInFrames, ma_uint32 subbufferCount, ma_uint32 subbufferStrideInFrames, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_pcm_rb* pRB) +MA_API ma_result ma_audio_ring_buffer_init(const ma_audio_ring_buffer_config* pConfig, ma_audio_ring_buffer* pRingBuffer) { - ma_uint32 bpf; ma_result result; + ma_data_source_config dataSourceConfig; + ma_uint32 bpf; + ma_bool32 isOwnerOfBuffer = MA_FALSE; + void* pBuffer; - if (pRB == NULL) { + if (pRingBuffer == NULL) { return MA_INVALID_ARGS; } - MA_ZERO_OBJECT(pRB); + MA_ZERO_OBJECT(pRingBuffer); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } - bpf = ma_get_bytes_per_frame(format, channels); + if (pConfig->format == ma_format_unknown || pConfig->channels == 0 || pConfig->sizeInFrames == 0) { + return MA_INVALID_ARGS; + } + + bpf = ma_get_bytes_per_frame(pConfig->format, pConfig->channels); if (bpf == 0) { return MA_INVALID_ARGS; } - result = ma_rb_init_ex(subbufferSizeInFrames*bpf, subbufferCount, subbufferStrideInFrames*bpf, pOptionalPreallocatedBuffer, pAllocationCallbacks, &pRB->rb); + /* Initialize the data source. */ + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.pVTable = &ma_gDataSourceVTable_AudioRingBuffer; + + result = ma_data_source_base_init(&dataSourceConfig, &pRingBuffer->ds); if (result != MA_SUCCESS) { return result; } - pRB->format = format; - pRB->channels = channels; - pRB->sampleRate = 0; /* The sample rate is not passed in as a parameter. */ + /* Allocate a buffer if necessary. */ + if (pConfig->pBuffer == NULL) { + size_t bufferSizeInBytes; - /* The PCM ring buffer is a data source. We need to get that set up as well. */ - { - ma_data_source_config dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &ma_gRBDataSourceVTable; + bufferSizeInBytes = pConfig->sizeInFrames * bpf * 2; /* Buffer must 2x the capacity. */ + MA_ASSERT(bufferSizeInBytes != 0); - result = ma_data_source_init(&dataSourceConfig, &pRB->ds); - if (result != MA_SUCCESS) { - ma_rb_uninit(&pRB->rb); - return result; + pBuffer = ma_malloc(bufferSizeInBytes, pConfig->pAllocationCallbacks); + if (pBuffer == NULL) { + ma_data_source_base_uninit(&pRingBuffer->ds); + return MA_OUT_OF_MEMORY; } - } - return MA_SUCCESS; -} - -MA_API ma_result ma_pcm_rb_init(ma_format format, ma_uint32 channels, ma_uint32 bufferSizeInFrames, void* pOptionalPreallocatedBuffer, const ma_allocation_callbacks* pAllocationCallbacks, ma_pcm_rb* pRB) -{ - return ma_pcm_rb_init_ex(format, channels, bufferSizeInFrames, 1, 0, pOptionalPreallocatedBuffer, pAllocationCallbacks, pRB); -} - -MA_API void ma_pcm_rb_uninit(ma_pcm_rb* pRB) -{ - if (pRB == NULL) { - return; + isOwnerOfBuffer = MA_TRUE; + } else { + pBuffer = pConfig->pBuffer; + isOwnerOfBuffer = MA_FALSE; } - ma_data_source_uninit(&pRB->ds); - ma_rb_uninit(&pRB->rb); -} + /* Now for the ring buffer. */ + ma_ring_buffer_init(pConfig->sizeInFrames, bpf, pConfig->flags, pBuffer, &pRingBuffer->rb); + pRingBuffer->format = pConfig->format; + pRingBuffer->channels = pConfig->channels; + pRingBuffer->sampleRate = pConfig->sampleRate; + pRingBuffer->isOwnerOfBuffer = isOwnerOfBuffer; + pRingBuffer->pBuffer = pBuffer; + pRingBuffer->allocationCallbacks = ma_allocation_callbacks_init_copy(pConfig->pAllocationCallbacks); -MA_API void ma_pcm_rb_reset(ma_pcm_rb* pRB) -{ - if (pRB == NULL) { - return; - } - - ma_rb_reset(&pRB->rb); + return MA_SUCCESS; } -MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut) +MA_API ma_result ma_audio_ring_buffer_init_copy(ma_audio_ring_buffer* pRingBuffer, ma_audio_ring_buffer* pNewRingBuffer) { - size_t sizeInBytes; - ma_result result; + ma_audio_ring_buffer_config config; - if (pRB == NULL || pSizeInFrames == NULL) { + if (pNewRingBuffer == NULL || pRingBuffer == NULL) { return MA_INVALID_ARGS; } - sizeInBytes = *pSizeInFrames * ma_pcm_rb_get_bpf(pRB); + config = ma_audio_ring_buffer_config_init(pRingBuffer->format, pRingBuffer->channels, pRingBuffer->sampleRate, pRingBuffer->rb.capacity); + config.pAllocationCallbacks = &pRingBuffer->allocationCallbacks; - result = ma_rb_acquire_read(&pRB->rb, &sizeInBytes, ppBufferOut); - if (result != MA_SUCCESS) { - return result; - } - - *pSizeInFrames = (ma_uint32)(sizeInBytes / (size_t)ma_pcm_rb_get_bpf(pRB)); - return MA_SUCCESS; -} - -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) -{ - if (pRB == NULL) { - return MA_INVALID_ARGS; + /* + For the buffer, if it's an internally managed we want to make sure the copy is also internally managed. If it's externally + managed we can make the copy also external. + */ + if (pRingBuffer->isOwnerOfBuffer) { + config.pBuffer = NULL; /* Internally managed. Set the buffer to null to trigger the new ring buffer to also allocate it's own buffer. */ + } else { + config.pBuffer = pRingBuffer->pBuffer; /* Externally managed. Just reuse the buffer. */ } - return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); + return ma_audio_ring_buffer_init(&config, pNewRingBuffer); } -MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut) +MA_API void ma_audio_ring_buffer_uninit(ma_audio_ring_buffer* pRingBuffer) { - size_t sizeInBytes; - ma_result result; - - if (pRB == NULL) { - return MA_INVALID_ARGS; + if (pRingBuffer == NULL) { + return; } - sizeInBytes = *pSizeInFrames * ma_pcm_rb_get_bpf(pRB); - - result = ma_rb_acquire_write(&pRB->rb, &sizeInBytes, ppBufferOut); - if (result != MA_SUCCESS) { - return result; + if (pRingBuffer->isOwnerOfBuffer) { + ma_free(pRingBuffer->pBuffer, &pRingBuffer->allocationCallbacks); } - *pSizeInFrames = (ma_uint32)(sizeInBytes / ma_pcm_rb_get_bpf(pRB)); - return MA_SUCCESS; + ma_data_source_base_uninit(&pRingBuffer->ds); } -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) +MA_API ma_uint32 ma_audio_ring_buffer_map_produce(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount, void** ppMappedBuffer) { - if (pRB == NULL) { - return MA_INVALID_ARGS; + if (pRingBuffer == NULL) { + return 0; } - return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); + return ma_ring_buffer_map_produce(&pRingBuffer->rb, frameCount, ppMappedBuffer); } -MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames) +MA_API void ma_audio_ring_buffer_unmap_produce(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount) { - if (pRB == NULL) { - return MA_INVALID_ARGS; + if (pRingBuffer == NULL) { + return; } - return ma_rb_seek_read(&pRB->rb, offsetInFrames * ma_pcm_rb_get_bpf(pRB)); + ma_ring_buffer_unmap_produce(&pRingBuffer->rb, frameCount); } -MA_API ma_result ma_pcm_rb_seek_write(ma_pcm_rb* pRB, ma_uint32 offsetInFrames) +MA_API ma_uint32 ma_audio_ring_buffer_map_consume(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount, void** ppMappedBuffer) { - if (pRB == NULL) { - return MA_INVALID_ARGS; + if (pRingBuffer == NULL) { + return 0; } - return ma_rb_seek_write(&pRB->rb, offsetInFrames * ma_pcm_rb_get_bpf(pRB)); + return ma_ring_buffer_map_consume(&pRingBuffer->rb, frameCount, ppMappedBuffer); } -MA_API ma_int32 ma_pcm_rb_pointer_distance(ma_pcm_rb* pRB) +MA_API void ma_audio_ring_buffer_unmap_consume(ma_audio_ring_buffer* pRingBuffer, ma_uint32 frameCount) { - if (pRB == NULL) { - return 0; + if (pRingBuffer == NULL) { + return; } - return ma_rb_pointer_distance(&pRB->rb) / ma_pcm_rb_get_bpf(pRB); + ma_ring_buffer_unmap_consume(&pRingBuffer->rb, frameCount); } -MA_API ma_uint32 ma_pcm_rb_available_read(ma_pcm_rb* pRB) +MA_API ma_result ma_audio_ring_buffer_write_pcm_frames(ma_audio_ring_buffer* pRingBuffer, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) { - if (pRB == NULL) { - return 0; - } + void* pMappedBuffer; + ma_uint32 mappedFrameCount; - return ma_rb_available_read(&pRB->rb) / ma_pcm_rb_get_bpf(pRB); -} + if (pFramesWritten != NULL) { + *pFramesWritten = 0; + } -MA_API ma_uint32 ma_pcm_rb_available_write(ma_pcm_rb* pRB) -{ - if (pRB == NULL) { - return 0; + if (pRingBuffer == NULL) { + return MA_INVALID_ARGS; } - return ma_rb_available_write(&pRB->rb) / ma_pcm_rb_get_bpf(pRB); -} + mappedFrameCount = ma_ring_buffer_map_produce(&pRingBuffer->rb, frameCount, &pMappedBuffer); + { + /* + When pFramesWritten is null it means the caller has no way of knowing how many frames were actually written. The most + practical thing to do in this case is to treat it as all or nothing. + */ + if (pFramesWritten == NULL && mappedFrameCount != frameCount) { + mappedFrameCount = 0; + } + + if (mappedFrameCount > 0) { + size_t bytesToCopy = (size_t)mappedFrameCount * ma_get_bytes_per_frame(pRingBuffer->format, pRingBuffer->channels); -MA_API ma_uint32 ma_pcm_rb_get_subbuffer_size(ma_pcm_rb* pRB) -{ - if (pRB == NULL) { - return 0; + if (pFrames != NULL) { + MA_COPY_MEMORY(pMappedBuffer, pFrames, bytesToCopy); + } else { + MA_ZERO_MEMORY(pMappedBuffer, bytesToCopy); + } + } } + ma_ring_buffer_unmap_produce(&pRingBuffer->rb, mappedFrameCount); - return (ma_uint32)(ma_rb_get_subbuffer_size(&pRB->rb) / ma_pcm_rb_get_bpf(pRB)); -} - -MA_API ma_uint32 ma_pcm_rb_get_subbuffer_stride(ma_pcm_rb* pRB) -{ - if (pRB == NULL) { - return 0; + if (pFramesWritten != NULL) { + *pFramesWritten = mappedFrameCount; + } else { + /* When pFramesWritten is null we need to treat it as all or nothing. */ + if (mappedFrameCount != frameCount) { + return MA_NO_SPACE; + } } - return (ma_uint32)(ma_rb_get_subbuffer_stride(&pRB->rb) / ma_pcm_rb_get_bpf(pRB)); + return MA_SUCCESS; } -MA_API ma_uint32 ma_pcm_rb_get_subbuffer_offset(ma_pcm_rb* pRB, ma_uint32 subbufferIndex) +MA_API ma_result ma_audio_ring_buffer_read_pcm_frames(ma_audio_ring_buffer* pRingBuffer, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) { - if (pRB == NULL) { - return 0; - } + void* pMappedBuffer; + ma_uint32 mappedFrameCount; - return (ma_uint32)(ma_rb_get_subbuffer_offset(&pRB->rb, subbufferIndex) / ma_pcm_rb_get_bpf(pRB)); -} + if (pFramesRead != NULL) { + *pFramesRead = 0; + } -MA_API void* ma_pcm_rb_get_subbuffer_ptr(ma_pcm_rb* pRB, ma_uint32 subbufferIndex, void* pBuffer) -{ - if (pRB == NULL) { - return NULL; + if (pRingBuffer == NULL) { + return MA_INVALID_ARGS; } - return ma_rb_get_subbuffer_ptr(&pRB->rb, subbufferIndex, pBuffer); -} + mappedFrameCount = ma_ring_buffer_map_consume(&pRingBuffer->rb, frameCount, &pMappedBuffer); + { + /* + When pFramesRead is null it means the caller has no way of knowing how many frames were actually read. The most + practical thing to do in this case is to treat it as all or nothing. + */ + if (pFramesRead == NULL && mappedFrameCount != frameCount) { + mappedFrameCount = 0; + } -MA_API ma_format ma_pcm_rb_get_format(const ma_pcm_rb* pRB) -{ - if (pRB == NULL) { - return ma_format_unknown; - } + if (mappedFrameCount > 0) { + size_t bytesToCopy = (size_t)mappedFrameCount * ma_get_bytes_per_frame(pRingBuffer->format, pRingBuffer->channels); - return pRB->format; -} + if (pFrames != NULL) { + MA_COPY_MEMORY(pFrames, pMappedBuffer, bytesToCopy); + } + } + } + ma_ring_buffer_unmap_consume(&pRingBuffer->rb, mappedFrameCount); -MA_API ma_uint32 ma_pcm_rb_get_channels(const ma_pcm_rb* pRB) -{ - if (pRB == NULL) { - return 0; + if (pFramesRead != NULL) { + *pFramesRead = mappedFrameCount; + } else { + /* When pFramesRead is null we need to treat it as all or nothing. */ + if (mappedFrameCount != frameCount) { + return MA_NO_DATA_AVAILABLE; + } } - return pRB->channels; + return MA_SUCCESS; } -MA_API ma_uint32 ma_pcm_rb_get_sample_rate(const ma_pcm_rb* pRB) +MA_API ma_result ma_audio_ring_buffer_get_length_in_pcm_frames(ma_audio_ring_buffer* pRingBuffer, ma_uint32* pLength) { - if (pRB == NULL) { - return 0; + if (pLength == NULL) { + return MA_INVALID_ARGS; } - return pRB->sampleRate; -} + *pLength = 0; -MA_API void ma_pcm_rb_set_sample_rate(ma_pcm_rb* pRB, ma_uint32 sampleRate) -{ - if (pRB == NULL) { - return; + if (pRingBuffer == NULL) { + return MA_INVALID_ARGS; } - pRB->sampleRate = sampleRate; + *pLength = ma_ring_buffer_length(&pRingBuffer->rb); + + return MA_SUCCESS; } - MA_API ma_result ma_duplex_rb_init(ma_format captureFormat, ma_uint32 captureChannels, ma_uint32 sampleRate, ma_uint32 captureInternalSampleRate, ma_uint32 captureInternalPeriodSizeInFrames, const ma_allocation_callbacks* pAllocationCallbacks, ma_duplex_rb* pRB) { ma_result result; ma_uint32 sizeInFrames; + ma_audio_ring_buffer_config ringBufferConfig; - sizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(sampleRate, captureInternalSampleRate, captureInternalPeriodSizeInFrames * 5); + sizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(sampleRate, captureInternalSampleRate, captureInternalPeriodSizeInFrames * 3); if (sizeInFrames == 0) { return MA_INVALID_ARGS; } - result = ma_pcm_rb_init(captureFormat, captureChannels, sizeInFrames, NULL, pAllocationCallbacks, &pRB->rb); + ringBufferConfig = ma_audio_ring_buffer_config_init(captureFormat, captureChannels, sampleRate, sizeInFrames); + ringBufferConfig.pAllocationCallbacks = pAllocationCallbacks; + + result = ma_audio_ring_buffer_init(&ringBufferConfig, &pRB->rb); if (result != MA_SUCCESS) { return result; } /* Seek forward a bit so we have a bit of a buffer in case of desyncs. */ - ma_pcm_rb_seek_write((ma_pcm_rb*)pRB, captureInternalPeriodSizeInFrames * 2); + ma_audio_ring_buffer_write_pcm_frames(&pRB->rb, NULL, captureInternalPeriodSizeInFrames * 2, NULL); return MA_SUCCESS; } MA_API ma_result ma_duplex_rb_uninit(ma_duplex_rb* pRB) { - ma_pcm_rb_uninit((ma_pcm_rb*)pRB); + ma_audio_ring_buffer_uninit(&pRB->rb); return MA_SUCCESS; } @@ -59005,6 +69108,35 @@ MA_API const char* ma_result_description(ma_result result) } } +MA_API ma_allocation_callbacks ma_allocation_callbacks_init_default(void) +{ + ma_allocation_callbacks callbacks; + callbacks.pUserData = NULL; + callbacks.onMalloc = ma__malloc_default; + callbacks.onRealloc = ma__realloc_default; + callbacks.onFree = ma__free_default; + + return callbacks; +} + +MA_API ma_allocation_callbacks ma_allocation_callbacks_init_copy(const ma_allocation_callbacks* pSrc) +{ + if (pSrc == NULL) { + return ma_allocation_callbacks_init_default(); + } + + if (pSrc->pUserData == NULL && pSrc->onFree == NULL && pSrc->onMalloc == NULL && pSrc->onRealloc == NULL) { + return ma_allocation_callbacks_init_default(); + } else { + if (pSrc->onFree == NULL || (pSrc->onMalloc == NULL && pSrc->onRealloc == NULL)) { + MA_ASSERT(!"Allocation callbacks must have either all callbacks non-NULL, or all callbacks NULL. Fix your code."); + return ma_allocation_callbacks_init_default(); + } else { + return *pSrc; + } + } +} + MA_API void* ma_malloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { @@ -59139,7 +69271,7 @@ MA_API ma_data_source_config ma_data_source_config_init(void) } -MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_data_source* pDataSource) +MA_API ma_result ma_data_source_base_init(const ma_data_source_config* pConfig, ma_data_source* pDataSource) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -59153,11 +69285,11 @@ MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_da return MA_INVALID_ARGS; } - if (pConfig->vtable == NULL) { + if (pConfig->pVTable == NULL) { return MA_INVALID_ARGS; } - pDataSourceBase->vtable = pConfig->vtable; + pDataSourceBase->pVTable = pConfig->pVTable; pDataSourceBase->rangeBegInFrames = MA_DATA_SOURCE_DEFAULT_RANGE_BEG; pDataSourceBase->rangeEndInFrames = MA_DATA_SOURCE_DEFAULT_RANGE_END; pDataSourceBase->loopBegInFrames = MA_DATA_SOURCE_DEFAULT_LOOP_POINT_BEG; @@ -59169,7 +69301,7 @@ MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_da return MA_SUCCESS; } -MA_API void ma_data_source_uninit(ma_data_source* pDataSource) +MA_API void ma_data_source_base_uninit(ma_data_source* pDataSource) { if (pDataSource == NULL) { return; @@ -59181,6 +69313,51 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource) */ } +MA_API const ma_data_source_vtable* ma_data_source_get_vtable(const ma_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return NULL; + } + + return ((ma_data_source_base*)pDataSource)->pVTable; +} + +MA_API ma_result ma_data_source_init_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + if (pNewDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(pDataSourceBase->pVTable != NULL); + + if (pDataSourceBase->pVTable->onCopy == NULL) { + return MA_INVALID_OPERATION; /* Not copyable. */ + } + + return pDataSourceBase->pVTable->onCopy(pDataSource, pNewDataSource); +} + +static ma_result ma_data_source_prop(ma_data_source* pDataSource, int prop, void* pData) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + MA_ASSERT(pDataSourceBase != NULL); + MA_ASSERT(pDataSourceBase->pVTable != NULL); + + if (pDataSourceBase->pVTable->onProp == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pDataSourceBase->pVTable->onProp(pDataSource, prop, pData); +} + + static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_data_source** ppCurrentDataSource) { ma_data_source_base* pCurrentDataSource = (ma_data_source_base*)pDataSource; @@ -59211,13 +69388,13 @@ static ma_result ma_data_source_read_pcm_frames_from_backend(ma_data_source* pDa { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; - MA_ASSERT(pDataSourceBase != NULL); - MA_ASSERT(pDataSourceBase->vtable != NULL); - MA_ASSERT(pDataSourceBase->vtable->onRead != NULL); + MA_ASSERT(pDataSourceBase != NULL); + MA_ASSERT(pDataSourceBase->pVTable != NULL); + MA_ASSERT(pDataSourceBase->pVTable->onRead != NULL); MA_ASSERT(pFramesRead != NULL); if (pFramesOut != NULL) { - return pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); + return pDataSourceBase->pVTable->onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); } else { /* No output buffer. Probably seeking forward. Read and discard. Can probably optimize this in terms of @@ -59230,7 +69407,7 @@ static ma_result ma_data_source_read_pcm_frames_from_backend(ma_data_source* pDa ma_uint64 discardBufferCapInFrames; ma_uint8 pDiscardBuffer[4096]; - result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0); + result = ma_data_source_get_data_format(pDataSource, &format, &channels, NULL); if (result != MA_SUCCESS) { return result; } @@ -59245,7 +69422,7 @@ static ma_result ma_data_source_read_pcm_frames_from_backend(ma_data_source* pDa framesToRead = discardBufferCapInFrames; } - result = pDataSourceBase->vtable->onRead(pDataSourceBase, pDiscardBuffer, framesToRead, &framesReadThisIteration); + result = pDataSourceBase->pVTable->onRead(pDataSourceBase, pDiscardBuffer, framesToRead, &framesReadThisIteration); if (result != MA_SUCCESS) { return result; } @@ -59278,10 +69455,10 @@ static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDa return MA_INVALID_ARGS; } - MA_ASSERT(pDataSourceBase->vtable != NULL); + MA_ASSERT(pDataSourceBase->pVTable != NULL); - if ((pDataSourceBase->vtable->flags & MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT) != 0 || (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE))) { - /* Either the data source is self-managing the range, or no range is set - just read like normal. The data source itself will tell us when the end is reached. */ + if ((pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE))) { + /* No range is set - just read like normal. The data source itself will tell us when the end is reached. */ result = ma_data_source_read_pcm_frames_from_backend(pDataSource, pFramesOut, frameCount, &framesRead); } else { /* Need to clamp to within the range. */ @@ -59368,7 +69545,7 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi We need to know the data format so we can advance the output buffer as we read frames. If this fails, chaining will not work and we'll just read as much as we can from the current source. */ - if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0) != MA_SUCCESS) { + if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL) != MA_SUCCESS) { result = ma_data_source_resolve_current(pDataSource, (ma_data_source**)&pCurrentDataSource); if (result != MA_SUCCESS) { return result; @@ -59495,7 +69672,7 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m return MA_INVALID_ARGS; } - if (pDataSourceBase->vtable->onSeek == NULL) { + if (pDataSourceBase->pVTable->onSeek == NULL) { return MA_NOT_IMPLEMENTED; } @@ -59503,9 +69680,9 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m return MA_INVALID_OPERATION; /* Trying to seek too far forward. */ } - MA_ASSERT(pDataSourceBase->vtable != NULL); + MA_ASSERT(pDataSourceBase->pVTable != NULL); - return pDataSourceBase->vtable->onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); + return pDataSourceBase->pVTable->onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); } MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float secondCount, float* pSecondsSeeked) @@ -59519,7 +69696,7 @@ MA_API ma_result ma_data_source_seek_seconds(ma_data_source* pDataSource, float return MA_INVALID_ARGS; } - result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate); if (result != MA_SUCCESS) { return result; } @@ -59544,7 +69721,7 @@ MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, floa return MA_INVALID_ARGS; } - result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate); if (result != MA_SUCCESS) { return result; } @@ -59555,13 +69732,10 @@ MA_API ma_result ma_data_source_seek_to_second(ma_data_source* pDataSource, floa return ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); } -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_result result; - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; + ma_data_source_data_format dataFormat; /* Initialize to defaults for safety just in case the data source does not implement this callback. */ if (pFormat != NULL) { @@ -59573,36 +69747,51 @@ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_ if (pSampleRate != NULL) { *pSampleRate = 0; } - if (pChannelMap != NULL) { - MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); - } - - if (pDataSourceBase == NULL) { - return MA_INVALID_ARGS; - } - - MA_ASSERT(pDataSourceBase->vtable != NULL); - - if (pDataSourceBase->vtable->onGetDataFormat == NULL) { - return MA_NOT_IMPLEMENTED; - } - result = pDataSourceBase->vtable->onGetDataFormat(pDataSource, &format, &channels, &sampleRate, pChannelMap, channelMapCap); + result = ma_data_source_prop(pDataSource, MA_DATA_SOURCE_GET_DATA_FORMAT, &dataFormat); if (result != MA_SUCCESS) { return result; } if (pFormat != NULL) { - *pFormat = format; + *pFormat = dataFormat.format; } if (pChannels != NULL) { - *pChannels = channels; + *pChannels = dataFormat.channels; } if (pSampleRate != NULL) { - *pSampleRate = sampleRate; + *pSampleRate = dataFormat.sampleRate; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_source_get_channel_map(ma_data_source* pDataSource, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_result result; + ma_channel pQueriedChannelMap[MA_MAX_CHANNELS]; + + if (pChannelMap == NULL) { + return MA_INVALID_ARGS; } - /* Channel map was passed in directly to the callback. This is safe due to the channelMapCap parameter. */ + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + MA_ZERO_MEMORY(pQueriedChannelMap, sizeof(pQueriedChannelMap)); + + result = ma_data_source_prop(pDataSource, MA_DATA_SOURCE_GET_CHANNEL_MAP, pQueriedChannelMap); + if (result != MA_SUCCESS) { + /* Most likely this just means MA_DATA_SOURCE_GET_CHANNEL_MAP is not implemented which means it's wants to use the default channel map. Just assume that. */ + ma_data_source_data_format dataFormat; + + result = ma_data_source_prop(pDataSource, MA_DATA_SOURCE_GET_DATA_FORMAT, &dataFormat); + if (result != MA_SUCCESS) { + return result; /* Do not know the channel count. Cannot derived a default channel map. */ + } + + ma_channel_map_init_standard(ma_standard_channel_map_default, pQueriedChannelMap, ma_countof(pQueriedChannelMap), dataFormat.channels); + } + + MA_COPY_MEMORY(pChannelMap, pQueriedChannelMap, ma_min(ma_countof(pQueriedChannelMap), channelMapCap)); return MA_SUCCESS; } @@ -59623,15 +69812,9 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo return MA_SUCCESS; } - MA_ASSERT(pDataSourceBase->vtable != NULL); - - if (pDataSourceBase->vtable->onGetCursor == NULL) { - return MA_NOT_IMPLEMENTED; - } - - result = pDataSourceBase->vtable->onGetCursor(pDataSourceBase, &cursor); + result = ma_data_source_prop(pDataSource, MA_DATA_SOURCE_GET_CURSOR, &cursor); if (result != MA_SUCCESS) { - return result; + return result; /* Most likely there is no notion of a cursor. */ } /* The cursor needs to be made relative to the start of the range. */ @@ -59647,6 +69830,8 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + ma_uint64 rangeBegInFrames; + ma_uint64 rangeEndInFrames; if (pLength == NULL) { return MA_INVALID_ARGS; @@ -59658,15 +69843,15 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo return MA_INVALID_ARGS; } - MA_ASSERT(pDataSourceBase->vtable != NULL); - /* If we have a range defined we'll use that to determine the length. This is one of rare times where we'll actually trust the caller. If they've set the range, I think it's mostly safe to assume they've set it based on some higher level knowledge of the structure of the sound bank. */ - if (pDataSourceBase->rangeEndInFrames != ~((ma_uint64)0)) { - *pLength = pDataSourceBase->rangeEndInFrames - pDataSourceBase->rangeBegInFrames; + ma_data_source_get_range_in_pcm_frames(pDataSource, &rangeBegInFrames, &rangeEndInFrames); + + if (rangeEndInFrames != ~((ma_uint64)0)) { + *pLength = rangeEndInFrames - rangeBegInFrames; return MA_SUCCESS; } @@ -59674,11 +69859,7 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo Getting here means a range is not defined so we'll need to get the data source itself to tell us the length. */ - if (pDataSourceBase->vtable->onGetLength == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pDataSourceBase->vtable->onGetLength(pDataSource, pLength); + return ma_data_source_prop(pDataSource, MA_DATA_SOURCE_GET_LENGTH, pLength); } MA_API ma_result ma_data_source_get_cursor_in_seconds(ma_data_source* pDataSource, float* pCursor) @@ -59698,11 +69879,16 @@ MA_API ma_result ma_data_source_get_cursor_in_seconds(ma_data_source* pDataSourc return result; } - result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate); if (result != MA_SUCCESS) { return result; } + /* If there is no notion of a sample rate we can't convert to seconds. */ + if (sampleRate == 0) { + return MA_INVALID_OPERATION; + } + /* VC6 does not support division of unsigned 64-bit integers with floating point numbers. Need to use a signed number. This shouldn't effect anything in practice. */ *pCursor = (ma_int64)cursorInPCMFrames / (float)sampleRate; @@ -59726,11 +69912,16 @@ MA_API ma_result ma_data_source_get_length_in_seconds(ma_data_source* pDataSourc return result; } - result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate); if (result != MA_SUCCESS) { return result; } + /* If there is no notion of a sample rate we can't convert to seconds. */ + if (sampleRate == 0) { + return MA_INVALID_OPERATION; + } + /* VC6 does not support division of unsigned 64-bit integers with floating point numbers. Need to use a signed number. This shouldn't effect anything in practice. */ *pLength = (ma_int64)lengthInPCMFrames / (float)sampleRate; @@ -59740,31 +69931,39 @@ MA_API ma_result ma_data_source_get_length_in_seconds(ma_data_source* pDataSourc MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + ma_result result; if (pDataSource == NULL) { return MA_INVALID_ARGS; } - ma_atomic_exchange_32(&pDataSourceBase->isLooping, isLooping); - - MA_ASSERT(pDataSourceBase->vtable != NULL); - - /* If there's no callback for this just treat it as a successful no-op. */ - if (pDataSourceBase->vtable->onSetLooping == NULL) { + result = ma_data_source_prop(pDataSource, MA_DATA_SOURCE_SET_LOOPING, &isLooping); + if (result == MA_SUCCESS) { return MA_SUCCESS; } - return pDataSourceBase->vtable->onSetLooping(pDataSource, isLooping); + /* Getting here means the backend is not self-managing the looping state so we'll do it ourselves. */ + ma_atomic_exchange_32(&pDataSourceBase->isLooping, isLooping); + + return MA_SUCCESS; } MA_API ma_bool32 ma_data_source_is_looping(const ma_data_source* pDataSource) { const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; + ma_result result; + ma_bool32 isLooping; if (pDataSource == NULL) { return MA_FALSE; } + result = ma_data_source_prop((ma_data_source*)pDataSource, MA_DATA_SOURCE_GET_LOOPING, &isLooping); + if (result == MA_SUCCESS) { + return isLooping; + } + + /* Getting here means the backend is not self-managing the looping state so we'll do it ourselves. */ return ma_atomic_load_32(&pDataSourceBase->isLooping); } @@ -59774,6 +69973,8 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou ma_result result; ma_uint64 relativeCursor; ma_uint64 absoluteCursor; + ma_uint64 oldRangeBegInFrames; + ma_uint64 oldRangeEndInFrames; ma_bool32 doSeekAdjustment = MA_FALSE; if (pDataSource == NULL) { @@ -59784,6 +69985,8 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou return MA_INVALID_ARGS; /* The end of the range must come after the beginning. */ } + ma_data_source_get_range_in_pcm_frames(pDataSource, &oldRangeBegInFrames, &oldRangeEndInFrames); + /* We may need to adjust the position of the cursor to ensure it's clamped to the range. Grab it now so we can calculate its absolute position before we change the range. @@ -59791,7 +69994,7 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &relativeCursor); if (result == MA_SUCCESS) { doSeekAdjustment = MA_TRUE; - absoluteCursor = relativeCursor + pDataSourceBase->rangeBegInFrames; + absoluteCursor = relativeCursor + oldRangeBegInFrames; } else { /* We couldn't get the position of the cursor. It probably means the data source has no notion @@ -59802,21 +70005,34 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou absoluteCursor = 0; } - pDataSourceBase->rangeBegInFrames = rangeBegInFrames; - pDataSourceBase->rangeEndInFrames = rangeEndInFrames; - /* - The commented out logic below was intended to maintain loop points in response to a change in the - range. However, this is not useful because it results in the sound breaking when you move the range - outside of the old loop points. I'm simplifying this by simply resetting the loop points. The - caller is expected to update their loop points if they change the range. - - In practice this should be mostly a non-issue because the majority of the time the range will be - set once right after initialization. + First try letting the backend deal with the range. If the backend doesn't want to deal with, we'll + do it ourselves here. */ - pDataSourceBase->loopBegInFrames = 0; - pDataSourceBase->loopEndInFrames = ~((ma_uint64)0); + { + ma_pcm_range range; + range.begInFrames = rangeBegInFrames; + range.endInFrames = rangeEndInFrames; + + result = ma_data_source_prop(pDataSource, MA_DATA_SOURCE_SET_RANGE, &range); + if (result == MA_NOT_IMPLEMENTED) { + /* The backend doesn't want to deal with the range. We'll do it here. */ + pDataSourceBase->rangeBegInFrames = rangeBegInFrames; + pDataSourceBase->rangeEndInFrames = rangeEndInFrames; + + /* Normalize the result to MA_SUCCESS just in case we update the code in the future to check the result code. */ + result = MA_SUCCESS; + } else if (result != MA_SUCCESS) { + return result; + } + } + /* + It's possible for the range to have moved outside of the loop points. We'll just define this function + such that the loop points are reset. This should not be an issue in practice because the range will + usually be set once right after initialization. + */ + ma_data_source_set_loop_point_in_pcm_frames(pDataSource, 0, ~((ma_uint64)0)); /* Seek to within range. Note that our seek positions here are relative to the new range. We don't want @@ -59837,6 +70053,8 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames) { + ma_result result; + ma_pcm_range range; const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pRangeBegInFrames != NULL) { @@ -59846,22 +70064,32 @@ MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSo *pRangeEndInFrames = 0; } - if (pDataSource == NULL) { + result = ma_data_source_prop((ma_data_source*)pDataSource, MA_DATA_SOURCE_GET_RANGE, &range); + if (result == MA_NOT_IMPLEMENTED) { + /* The backend isn't dealing with the range so we'll use our local members. */ + range.begInFrames = pDataSourceBase->rangeBegInFrames; + range.endInFrames = pDataSourceBase->rangeEndInFrames; + + /* Normalize the result to MA_SUCCESS just in case we update the code in the future to check the result code. */ + result = MA_SUCCESS; + } else if (result != MA_SUCCESS) { return; } if (pRangeBegInFrames != NULL) { - *pRangeBegInFrames = pDataSourceBase->rangeBegInFrames; + *pRangeBegInFrames = range.begInFrames; } - if (pRangeEndInFrames != NULL) { - *pRangeEndInFrames = pDataSourceBase->rangeEndInFrames; + *pRangeEndInFrames = range.endInFrames; } } MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 loopBegInFrames, ma_uint64 loopEndInFrames) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + ma_result result; + ma_uint64 rangeBegInFrames; + ma_uint64 rangeEndInFrames; if (pDataSource == NULL) { return MA_INVALID_ARGS; @@ -59871,16 +70099,29 @@ MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDa return MA_INVALID_ARGS; /* The end of the loop point must come after the beginning. */ } - if (loopEndInFrames > pDataSourceBase->rangeEndInFrames && loopEndInFrames != ~((ma_uint64)0)) { - return MA_INVALID_ARGS; /* The end of the loop point must not go beyond the range. */ + /* The end cannot exceed the range. */ + ma_data_source_get_range_in_pcm_frames(pDataSource, &rangeBegInFrames, &rangeEndInFrames); + + if (loopEndInFrames > (rangeEndInFrames - rangeBegInFrames) && loopEndInFrames != ~((ma_uint64)0)) { + loopEndInFrames = (rangeEndInFrames - rangeBegInFrames); } - pDataSourceBase->loopBegInFrames = loopBegInFrames; - pDataSourceBase->loopEndInFrames = loopEndInFrames; + { + ma_pcm_range loopRange; + loopRange.begInFrames = loopBegInFrames; + loopRange.endInFrames = loopEndInFrames; + + result = ma_data_source_prop(pDataSource, MA_DATA_SOURCE_SET_LOOP_POINT, &loopRange); + if (result == MA_NOT_IMPLEMENTED) { + /* The backend is not dealing with loop points so we'll manage it ourselves here. */ + pDataSourceBase->loopBegInFrames = loopBegInFrames; + pDataSourceBase->loopEndInFrames = loopEndInFrames; - /* The end cannot exceed the range. */ - if (pDataSourceBase->loopEndInFrames > (pDataSourceBase->rangeEndInFrames - pDataSourceBase->rangeBegInFrames) && pDataSourceBase->loopEndInFrames != ~((ma_uint64)0)) { - pDataSourceBase->loopEndInFrames = (pDataSourceBase->rangeEndInFrames - pDataSourceBase->rangeBegInFrames); + /* Normalize the result to MA_SUCCESS just in case we update the code in the future to check the result code. */ + result = MA_SUCCESS; + } else if (result != MA_SUCCESS) { + return result; + } } return MA_SUCCESS; @@ -59888,6 +70129,8 @@ MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDa MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames) { + ma_result result; + ma_pcm_range range; const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pLoopBegInFrames != NULL) { @@ -59897,16 +70140,23 @@ MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pD *pLoopEndInFrames = 0; } - if (pDataSource == NULL) { + result = ma_data_source_prop((ma_data_source*)pDataSource, MA_DATA_SOURCE_GET_LOOP_POINT, &range); + if (result == MA_NOT_IMPLEMENTED) { + /* The backend isn't dealing with the loop points so we'll use our local members. */ + range.begInFrames = pDataSourceBase->loopBegInFrames; + range.endInFrames = pDataSourceBase->loopEndInFrames; + + /* Normalize the result to MA_SUCCESS just in case we update the code in the future to check the result code. */ + result = MA_SUCCESS; + } else if (result != MA_SUCCESS) { return; } if (pLoopBegInFrames != NULL) { - *pLoopBegInFrames = pDataSourceBase->loopBegInFrames; + *pLoopBegInFrames = range.begInFrames; } - if (pLoopEndInFrames != NULL) { - *pLoopEndInFrames = pDataSourceBase->loopEndInFrames; + *pLoopEndInFrames = range.endInFrames; } } @@ -59983,306 +70233,86 @@ MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(const ma_da } -static ma_result ma_audio_buffer_ref__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +static size_t ma_audio_buffer__data_source_on_sizeof(void) { - ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; - ma_uint64 framesRead = ma_audio_buffer_ref_read_pcm_frames(pAudioBufferRef, pFramesOut, frameCount, MA_FALSE); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead < frameCount || framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return sizeof(ma_audio_buffer); } -static ma_result ma_audio_buffer_ref__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +static void ma_audio_buffer__data_source_on_uninit(ma_data_source* pDataSource) { - return ma_audio_buffer_ref_seek_to_pcm_frame((ma_audio_buffer_ref*)pDataSource, frameIndex); + ma_audio_buffer_uninit((ma_audio_buffer*)pDataSource); } -static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_audio_buffer__data_source_on_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) { - ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; - - *pFormat = pAudioBufferRef->format; - *pChannels = pAudioBufferRef->channels; - *pSampleRate = pAudioBufferRef->sampleRate; - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pAudioBufferRef->channels); - - return MA_SUCCESS; + return ma_audio_buffer_init_copy((ma_audio_buffer*)pDataSource, (ma_audio_buffer*)pNewDataSource); } -static ma_result ma_audio_buffer_ref__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +static ma_result ma_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; - - *pCursor = pAudioBufferRef->cursor; - - return MA_SUCCESS; + return ma_audio_buffer_read_pcm_frames((ma_audio_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_audio_buffer_ref__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +static ma_result ma_audio_buffer__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) { - ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; - - *pLength = pAudioBufferRef->sizeInFrames; - - return MA_SUCCESS; + return ma_audio_buffer_seek_to_pcm_frame((ma_audio_buffer*)pDataSource, frameIndex); } -static ma_data_source_vtable g_ma_audio_buffer_ref_data_source_vtable = +static ma_result ma_audio_buffer__data_source_on_prop(ma_data_source* pDataSource, int prop, void* pData) { - ma_audio_buffer_ref__data_source_on_read, - ma_audio_buffer_ref__data_source_on_seek, - ma_audio_buffer_ref__data_source_on_get_data_format, - ma_audio_buffer_ref__data_source_on_get_cursor, - ma_audio_buffer_ref__data_source_on_get_length, - NULL, /* onSetLooping */ - 0 -}; + ma_audio_buffer* pAudioBuffer = (ma_audio_buffer*)pDataSource; -MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, const void* pData, ma_uint64 sizeInFrames, ma_audio_buffer_ref* pAudioBufferRef) -{ - ma_result result; - ma_data_source_config dataSourceConfig; - - if (pAudioBufferRef == NULL) { - return MA_INVALID_ARGS; - } - - MA_ZERO_OBJECT(pAudioBufferRef); - - dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_audio_buffer_ref_data_source_vtable; - - result = ma_data_source_init(&dataSourceConfig, &pAudioBufferRef->ds); - if (result != MA_SUCCESS) { - return result; - } - - pAudioBufferRef->format = format; - pAudioBufferRef->channels = channels; - pAudioBufferRef->sampleRate = 0; /* TODO: Version 0.12. Set this to sampleRate. */ - pAudioBufferRef->cursor = 0; - pAudioBufferRef->sizeInFrames = sizeInFrames; - pAudioBufferRef->pData = pData; - - return MA_SUCCESS; -} - -MA_API void ma_audio_buffer_ref_uninit(ma_audio_buffer_ref* pAudioBufferRef) -{ - if (pAudioBufferRef == NULL) { - return; - } - - ma_data_source_uninit(&pAudioBufferRef->ds); -} - -MA_API ma_result ma_audio_buffer_ref_set_data(ma_audio_buffer_ref* pAudioBufferRef, const void* pData, ma_uint64 sizeInFrames) -{ - if (pAudioBufferRef == NULL) { - return MA_INVALID_ARGS; - } - - pAudioBufferRef->cursor = 0; - pAudioBufferRef->sizeInFrames = sizeInFrames; - pAudioBufferRef->pData = pData; - - return MA_SUCCESS; -} - -MA_API ma_uint64 ma_audio_buffer_ref_read_pcm_frames(ma_audio_buffer_ref* pAudioBufferRef, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop) -{ - ma_uint64 totalFramesRead = 0; - - if (pAudioBufferRef == NULL) { - return 0; - } - - if (frameCount == 0) { - return 0; - } - - while (totalFramesRead < frameCount) { - ma_uint64 framesAvailable = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; - ma_uint64 framesRemaining = frameCount - totalFramesRead; - ma_uint64 framesToRead; + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; - framesToRead = framesRemaining; - if (framesToRead > framesAvailable) { - framesToRead = framesAvailable; + pDataFormat->format = pAudioBuffer->format; + pDataFormat->channels = pAudioBuffer->channels; + pDataFormat->sampleRate = pAudioBuffer->sampleRate; + + return MA_SUCCESS; } - if (pFramesOut != NULL) { - ma_copy_pcm_frames(ma_offset_ptr(pFramesOut, totalFramesRead * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), framesToRead, pAudioBufferRef->format, pAudioBufferRef->channels); + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_audio_buffer_get_cursor_in_pcm_frames(pAudioBuffer, (ma_uint64*)pData); } - totalFramesRead += framesToRead; - - pAudioBufferRef->cursor += framesToRead; - if (pAudioBufferRef->cursor == pAudioBufferRef->sizeInFrames) { - if (loop) { - pAudioBufferRef->cursor = 0; - } else { - break; /* We've reached the end and we're not looping. Done. */ - } + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_audio_buffer_get_length_in_pcm_frames(pAudioBuffer, (ma_uint64*)pData); } - MA_ASSERT(pAudioBufferRef->cursor < pAudioBufferRef->sizeInFrames); - } - - return totalFramesRead; -} - -MA_API ma_result ma_audio_buffer_ref_seek_to_pcm_frame(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameIndex) -{ - if (pAudioBufferRef == NULL) { - return MA_INVALID_ARGS; - } - - if (frameIndex > pAudioBufferRef->sizeInFrames) { - return MA_INVALID_ARGS; - } - - pAudioBufferRef->cursor = (size_t)frameIndex; - - return MA_SUCCESS; -} - -MA_API ma_result ma_audio_buffer_ref_map(ma_audio_buffer_ref* pAudioBufferRef, void** ppFramesOut, ma_uint64* pFrameCount) -{ - ma_uint64 framesAvailable; - ma_uint64 frameCount = 0; - - if (ppFramesOut != NULL) { - *ppFramesOut = NULL; /* Safety. */ - } - - if (pFrameCount != NULL) { - frameCount = *pFrameCount; - *pFrameCount = 0; /* Safety. */ - } - - if (pAudioBufferRef == NULL || ppFramesOut == NULL || pFrameCount == NULL) { - return MA_INVALID_ARGS; - } - - framesAvailable = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; - if (frameCount > framesAvailable) { - frameCount = framesAvailable; - } - - *ppFramesOut = ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)); - *pFrameCount = frameCount; - - return MA_SUCCESS; -} - -MA_API ma_result ma_audio_buffer_ref_unmap(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameCount) -{ - ma_uint64 framesAvailable; - - if (pAudioBufferRef == NULL) { - return MA_INVALID_ARGS; - } - - framesAvailable = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; - if (frameCount > framesAvailable) { - return MA_INVALID_ARGS; /* The frame count was too big. This should never happen in an unmapping. Need to make sure the caller is aware of this. */ - } - - pAudioBufferRef->cursor += frameCount; - - if (pAudioBufferRef->cursor == pAudioBufferRef->sizeInFrames) { - return MA_AT_END; /* Successful. Need to tell the caller that the end has been reached so that it can loop if desired. */ - } else { - return MA_SUCCESS; - } -} - -MA_API ma_bool32 ma_audio_buffer_ref_at_end(const ma_audio_buffer_ref* pAudioBufferRef) -{ - if (pAudioBufferRef == NULL) { - return MA_FALSE; - } - - return pAudioBufferRef->cursor == pAudioBufferRef->sizeInFrames; -} - -MA_API ma_result ma_audio_buffer_ref_get_cursor_in_pcm_frames(const ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pCursor) -{ - if (pCursor == NULL) { - return MA_INVALID_ARGS; - } - - *pCursor = 0; - - if (pAudioBufferRef == NULL) { - return MA_INVALID_ARGS; - } - - *pCursor = pAudioBufferRef->cursor; - - return MA_SUCCESS; -} - -MA_API ma_result ma_audio_buffer_ref_get_length_in_pcm_frames(const ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pLength) -{ - if (pLength == NULL) { - return MA_INVALID_ARGS; - } - - *pLength = 0; - - if (pAudioBufferRef == NULL) { - return MA_INVALID_ARGS; + default: break; } - *pLength = pAudioBufferRef->sizeInFrames; - - return MA_SUCCESS; + return MA_NOT_IMPLEMENTED; } -MA_API ma_result ma_audio_buffer_ref_get_available_frames(const ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pAvailableFrames) +static ma_data_source_vtable ma_gDataSourceVTable_AudioBuffer = { - if (pAvailableFrames == NULL) { - return MA_INVALID_ARGS; - } - - *pAvailableFrames = 0; - - if (pAudioBufferRef == NULL) { - return MA_INVALID_ARGS; - } - - if (pAudioBufferRef->sizeInFrames <= pAudioBufferRef->cursor) { - *pAvailableFrames = 0; - } else { - *pAvailableFrames = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; - } - - return MA_SUCCESS; -} - - + ma_audio_buffer__data_source_on_sizeof, + ma_audio_buffer__data_source_on_uninit, + ma_audio_buffer__data_source_on_copy, + ma_audio_buffer__data_source_on_read, + ma_audio_buffer__data_source_on_seek, + ma_audio_buffer__data_source_on_prop +}; -MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint64 sizeInFrames, const void* pData, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint64 sizeInFrames, const void* pData, const ma_allocation_callbacks* pAllocationCallbacks) { ma_audio_buffer_config config; MA_ZERO_OBJECT(&config); - config.format = format; - config.channels = channels; - config.sampleRate = 0; /* TODO: Version 0.12. Set this to sampleRate. */ - config.sizeInFrames = sizeInFrames; - config.pData = pData; - ma_allocation_callbacks_init_copy(&config.allocationCallbacks, pAllocationCallbacks); + config.format = format; + config.channels = channels; + config.sampleRate = sampleRate; + config.sizeInFrames = sizeInFrames; + config.pData = pData; + config.pAllocationCallbacks = pAllocationCallbacks; return config; } @@ -60290,6 +70320,7 @@ MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_u static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, ma_bool32 doCopy, ma_audio_buffer* pAudioBuffer) { ma_result result; + ma_data_source_config dataSourceConfig; if (pAudioBuffer == NULL) { return MA_INVALID_ARGS; @@ -60305,15 +70336,21 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, return MA_INVALID_ARGS; /* Not allowing buffer sizes of 0 frames. */ } - result = ma_audio_buffer_ref_init(pConfig->format, pConfig->channels, NULL, 0, &pAudioBuffer->ref); + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.pVTable = &ma_gDataSourceVTable_AudioBuffer; + + result = ma_data_source_base_init(&dataSourceConfig, &pAudioBuffer->ds); if (result != MA_SUCCESS) { return result; } - /* TODO: Version 0.12. Set this in ma_audio_buffer_ref_init() instead of here. */ - pAudioBuffer->ref.sampleRate = pConfig->sampleRate; - - ma_allocation_callbacks_init_copy(&pAudioBuffer->allocationCallbacks, &pConfig->allocationCallbacks); + pAudioBuffer->format = pConfig->format; + pAudioBuffer->channels = pConfig->channels; + pAudioBuffer->sampleRate = pConfig->sampleRate; + pAudioBuffer->sizeInFrames = pConfig->sizeInFrames; + pAudioBuffer->pData = pConfig->pData; + pAudioBuffer->cursor = 0; + pAudioBuffer->allocationCallbacks = ma_allocation_callbacks_init_copy(pConfig->pAllocationCallbacks); if (doCopy) { ma_uint64 allocationSizeInBytes; @@ -60335,39 +70372,22 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, ma_silence_pcm_frames(pData, pConfig->sizeInFrames, pConfig->format, pConfig->channels); } - ma_audio_buffer_ref_set_data(&pAudioBuffer->ref, pData, pConfig->sizeInFrames); + pAudioBuffer->pData = pData; pAudioBuffer->ownsData = MA_TRUE; } else { - ma_audio_buffer_ref_set_data(&pAudioBuffer->ref, pConfig->pData, pConfig->sizeInFrames); + pAudioBuffer->pData = pConfig->pData; pAudioBuffer->ownsData = MA_FALSE; } return MA_SUCCESS; } -static void ma_audio_buffer_uninit_ex(ma_audio_buffer* pAudioBuffer, ma_bool32 doFree) -{ - if (pAudioBuffer == NULL) { - return; - } - - if (pAudioBuffer->ownsData && pAudioBuffer->ref.pData != &pAudioBuffer->_pExtraData[0]) { - ma_free((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ - } - - if (doFree) { - ma_free(pAudioBuffer, &pAudioBuffer->allocationCallbacks); - } - - ma_audio_buffer_ref_uninit(&pAudioBuffer->ref); -} - MA_API ma_result ma_audio_buffer_init(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer) { return ma_audio_buffer_init_ex(pConfig, MA_FALSE, pAudioBuffer); } -MA_API ma_result ma_audio_buffer_init_copy(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer) +MA_API ma_result ma_audio_buffer_init_and_copy_data(const ma_audio_buffer_config* pConfig, ma_audio_buffer* pAudioBuffer) { return ma_audio_buffer_init_ex(pConfig, MA_TRUE, pAudioBuffer); } @@ -60390,14 +70410,14 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC } innerConfig = *pConfig; - ma_allocation_callbacks_init_copy(&innerConfig.allocationCallbacks, &pConfig->allocationCallbacks); + innerConfig.pAllocationCallbacks = pConfig->pAllocationCallbacks; allocationSizeInBytes = sizeof(*pAudioBuffer) - sizeof(pAudioBuffer->_pExtraData) + (pConfig->sizeInFrames * ma_get_bytes_per_frame(pConfig->format, pConfig->channels)); if (allocationSizeInBytes > MA_SIZE_MAX) { return MA_OUT_OF_MEMORY; /* Too big. */ } - pAudioBuffer = (ma_audio_buffer*)ma_malloc((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */ + pAudioBuffer = (ma_audio_buffer*)ma_malloc((size_t)allocationSizeInBytes, innerConfig.pAllocationCallbacks); /* Safe cast to size_t. */ if (pAudioBuffer == NULL) { return MA_OUT_OF_MEMORY; } @@ -60412,7 +70432,7 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC result = ma_audio_buffer_init_ex(&innerConfig, MA_FALSE, pAudioBuffer); if (result != MA_SUCCESS) { - ma_free(pAudioBuffer, &innerConfig.allocationCallbacks); + ma_free(pAudioBuffer, innerConfig.pAllocationCallbacks); return result; } @@ -60421,6 +70441,47 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC return MA_SUCCESS; } +MA_API ma_result ma_audio_buffer_init_copy(ma_audio_buffer* pAudioBuffer, ma_audio_buffer* pNewAudioBuffer) +{ + ma_audio_buffer_config config; + + if (pNewAudioBuffer == NULL || pAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + config = ma_audio_buffer_config_init(pAudioBuffer->format, pAudioBuffer->channels, pAudioBuffer->sampleRate, pAudioBuffer->sizeInFrames, NULL, &pAudioBuffer->allocationCallbacks); + config.pAllocationCallbacks = &pAudioBuffer->allocationCallbacks; + + /* + For the buffer, if it's an internally managed we want to make sure the copy is also internally managed. If it's externally + managed we can make the copy also external. + */ + if (pAudioBuffer->ownsData && pAudioBuffer->pData != &pAudioBuffer->_pExtraData[0]) { + config.pData = NULL; /* Internally managed. Set the buffer to null to trigger the new ring buffer to also allocate it's own buffer. */ + } else { + config.pData = pAudioBuffer->pData; /* Externally managed. Just reuse the buffer. */ + } + + return ma_audio_buffer_init(&config, pNewAudioBuffer); +} + +static void ma_audio_buffer_uninit_ex(ma_audio_buffer* pAudioBuffer, ma_bool32 doFree) +{ + if (pAudioBuffer == NULL) { + return; + } + + if (pAudioBuffer->ownsData && pAudioBuffer->pData != &pAudioBuffer->_pExtraData[0]) { + ma_free((void*)pAudioBuffer->pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ + } + + if (doFree) { + ma_free(pAudioBuffer, &pAudioBuffer->allocationCallbacks); + } + + ma_data_source_base_uninit(&pAudioBuffer->ds); +} + MA_API void ma_audio_buffer_uninit(ma_audio_buffer* pAudioBuffer) { ma_audio_buffer_uninit_ex(pAudioBuffer, MA_FALSE); @@ -60431,13 +70492,41 @@ MA_API void ma_audio_buffer_uninit_and_free(ma_audio_buffer* pAudioBuffer) ma_audio_buffer_uninit_ex(pAudioBuffer, MA_TRUE); } -MA_API ma_uint64 ma_audio_buffer_read_pcm_frames(ma_audio_buffer* pAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop) +MA_API ma_result ma_audio_buffer_read_pcm_frames(ma_audio_buffer* pAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + ma_uint64 framesAvailable; + ma_uint64 framesToRead; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + if (pAudioBuffer == NULL) { - return 0; + return MA_INVALID_ARGS; + } + + framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; + + framesToRead = frameCount; + if (framesToRead > framesAvailable) { + framesToRead = framesAvailable; + } + + if (pFramesOut != NULL && framesToRead > 0) { + ma_copy_pcm_frames(pFramesOut, ma_offset_ptr(pAudioBuffer->pData, pAudioBuffer->cursor * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels)), framesToRead, pAudioBuffer->format, pAudioBuffer->channels); + } + + pAudioBuffer->cursor += framesToRead; + + if (pFramesRead != NULL) { + *pFramesRead = framesToRead; } - return ma_audio_buffer_ref_read_pcm_frames(&pAudioBuffer->ref, pFramesOut, frameCount, loop); + if (framesToRead == 0) { + return MA_AT_END; + } else { + return MA_SUCCESS; + } } MA_API ma_result ma_audio_buffer_seek_to_pcm_frame(ma_audio_buffer* pAudioBuffer, ma_uint64 frameIndex) @@ -60446,33 +70535,64 @@ MA_API ma_result ma_audio_buffer_seek_to_pcm_frame(ma_audio_buffer* pAudioBuffer return MA_INVALID_ARGS; } - return ma_audio_buffer_ref_seek_to_pcm_frame(&pAudioBuffer->ref, frameIndex); + if (frameIndex > pAudioBuffer->sizeInFrames) { + return MA_INVALID_ARGS; + } + + pAudioBuffer->cursor = (size_t)frameIndex; + + return MA_SUCCESS; } MA_API ma_result ma_audio_buffer_map(ma_audio_buffer* pAudioBuffer, void** ppFramesOut, ma_uint64* pFrameCount) { + ma_uint64 framesAvailable; + ma_uint64 frameCount = 0; + if (ppFramesOut != NULL) { *ppFramesOut = NULL; /* Safety. */ } - if (pAudioBuffer == NULL) { - if (pFrameCount != NULL) { - *pFrameCount = 0; - } + if (pFrameCount != NULL) { + frameCount = *pFrameCount; + *pFrameCount = 0; /* Safety. */ + } + if (pAudioBuffer == NULL || ppFramesOut == NULL || pFrameCount == NULL) { return MA_INVALID_ARGS; } - return ma_audio_buffer_ref_map(&pAudioBuffer->ref, ppFramesOut, pFrameCount); + framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; + if (frameCount > framesAvailable) { + frameCount = framesAvailable; + } + + *ppFramesOut = ma_offset_ptr(pAudioBuffer->pData, pAudioBuffer->cursor * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels)); + *pFrameCount = frameCount; + + return MA_SUCCESS; } MA_API ma_result ma_audio_buffer_unmap(ma_audio_buffer* pAudioBuffer, ma_uint64 frameCount) { + ma_uint64 framesAvailable; + if (pAudioBuffer == NULL) { return MA_INVALID_ARGS; } - return ma_audio_buffer_ref_unmap(&pAudioBuffer->ref, frameCount); + framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; + if (frameCount > framesAvailable) { + return MA_INVALID_ARGS; /* The frame count was too big. This should never happen in an unmapping. Need to make sure the caller is aware of this. */ + } + + pAudioBuffer->cursor += frameCount; + + if (pAudioBuffer->cursor == pAudioBuffer->sizeInFrames) { + return MA_AT_END; /* Successful. Need to tell the caller that the end has been reached so that it can loop if desired. */ + } else { + return MA_SUCCESS; + } } MA_API ma_bool32 ma_audio_buffer_at_end(const ma_audio_buffer* pAudioBuffer) @@ -60481,25 +70601,41 @@ MA_API ma_bool32 ma_audio_buffer_at_end(const ma_audio_buffer* pAudioBuffer) return MA_FALSE; } - return ma_audio_buffer_ref_at_end(&pAudioBuffer->ref); + return pAudioBuffer->cursor == pAudioBuffer->sizeInFrames; } MA_API ma_result ma_audio_buffer_get_cursor_in_pcm_frames(const ma_audio_buffer* pAudioBuffer, ma_uint64* pCursor) { + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + if (pAudioBuffer == NULL) { return MA_INVALID_ARGS; } - return ma_audio_buffer_ref_get_cursor_in_pcm_frames(&pAudioBuffer->ref, pCursor); + *pCursor = pAudioBuffer->cursor; + + return MA_SUCCESS; } MA_API ma_result ma_audio_buffer_get_length_in_pcm_frames(const ma_audio_buffer* pAudioBuffer, ma_uint64* pLength) { + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + if (pAudioBuffer == NULL) { return MA_INVALID_ARGS; } - return ma_audio_buffer_ref_get_length_in_pcm_frames(&pAudioBuffer->ref, pLength); + *pLength = pAudioBuffer->sizeInFrames; + + return MA_SUCCESS; } MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAudioBuffer, ma_uint64* pAvailableFrames) @@ -60514,7 +70650,13 @@ MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAu return MA_INVALID_ARGS; } - return ma_audio_buffer_ref_get_available_frames(&pAudioBuffer->ref, pAvailableFrames); + if (pAudioBuffer->sizeInFrames <= pAudioBuffer->cursor) { + *pAvailableFrames = 0; + } else { + *pAvailableFrames = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; + } + + return MA_SUCCESS; } @@ -60691,6 +70833,16 @@ MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_a } +static size_t ma_paged_audio_buffer__data_source_on_sizeof(void) +{ + return sizeof(ma_paged_audio_buffer); +} + +static void ma_paged_audio_buffer__data_source_on_uninit(ma_data_source* pDataSource) +{ + ma_paged_audio_buffer_uninit((ma_paged_audio_buffer*)pDataSource); +} + static ma_result ma_paged_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { return ma_paged_audio_buffer_read_pcm_frames((ma_paged_audio_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); @@ -60701,37 +70853,47 @@ static ma_result ma_paged_audio_buffer__data_source_on_seek(ma_data_source* pDat return ma_paged_audio_buffer_seek_to_pcm_frame((ma_paged_audio_buffer*)pDataSource, frameIndex); } -static ma_result ma_paged_audio_buffer__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_paged_audio_buffer__data_source_on_prop(ma_data_source* pDataSource, int prop, void* pData) { ma_paged_audio_buffer* pPagedAudioBuffer = (ma_paged_audio_buffer*)pDataSource; - *pFormat = pPagedAudioBuffer->pData->format; - *pChannels = pPagedAudioBuffer->pData->channels; - *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pPagedAudioBuffer->pData->channels); + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; - return MA_SUCCESS; -} + pDataFormat->format = pPagedAudioBuffer->pData->format; + pDataFormat->channels = pPagedAudioBuffer->pData->channels; + pDataFormat->sampleRate = 0; /* There is no notion of a sample rate with paged audio buffers. */ + + return MA_SUCCESS; + } -static ma_result ma_paged_audio_buffer__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) -{ - return ma_paged_audio_buffer_get_cursor_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pCursor); -} + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_paged_audio_buffer_get_cursor_in_pcm_frames(pPagedAudioBuffer, (ma_uint64*)pData); + } -static ma_result ma_paged_audio_buffer__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) -{ - return ma_paged_audio_buffer_get_length_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pLength); + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_paged_audio_buffer_get_length_in_pcm_frames(pPagedAudioBuffer, (ma_uint64*)pData); + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_paged_audio_buffer_data_source_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_PagedAudioBuffer = { + ma_paged_audio_buffer__data_source_on_sizeof, + ma_paged_audio_buffer__data_source_on_uninit, + NULL, /* onCopy */ ma_paged_audio_buffer__data_source_on_read, ma_paged_audio_buffer__data_source_on_seek, - ma_paged_audio_buffer__data_source_on_get_data_format, - ma_paged_audio_buffer__data_source_on_get_cursor, - ma_paged_audio_buffer__data_source_on_get_length, - NULL, /* onSetLooping */ - 0 + ma_paged_audio_buffer__data_source_on_prop }; MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer) @@ -60755,9 +70917,9 @@ MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* } dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_paged_audio_buffer_data_source_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_PagedAudioBuffer; - result = ma_data_source_init(&dataSourceConfig, &pPagedAudioBuffer->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pPagedAudioBuffer->ds); if (result != MA_SUCCESS) { return result; } @@ -61288,9 +71450,9 @@ static ma_result ma_default_vfs_seek__win32(ma_vfs* pVFS, ma_vfs_file file, ma_i liDistanceToMove.QuadPart = offset; - /* */ if (origin == ma_seek_origin_current) { + /* */ if (origin == MA_SEEK_CUR) { dwMoveMethod = FILE_CURRENT; - } else if (origin == ma_seek_origin_end) { + } else if (origin == MA_SEEK_END) { dwMoveMethod = FILE_END; } else { dwMoveMethod = FILE_BEGIN; @@ -61506,9 +71668,9 @@ static ma_result ma_default_vfs_seek__stdio(ma_vfs* pVFS, ma_vfs_file file, ma_i (void)pVFS; - if (origin == ma_seek_origin_start) { + if (origin == MA_SEEK_SET) { whence = SEEK_SET; - } else if (origin == ma_seek_origin_end) { + } else if (origin == MA_SEEK_END) { whence = SEEK_END; } else { whence = SEEK_CUR; @@ -61737,7 +71899,7 @@ static ma_result ma_default_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_inf return result; } - result = ma_default_vfs_seek(pVFS, file, 0, ma_seek_origin_end); + result = ma_default_vfs_seek(pVFS, file, 0, MA_SEEK_END); if (result != MA_SUCCESS) { return result; } @@ -61749,7 +71911,7 @@ static ma_result ma_default_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_inf pInfo->sizeInBytes = sizeInBytes; - result = ma_default_vfs_seek(pVFS, file, cursor, ma_seek_origin_start); + result = ma_default_vfs_seek(pVFS, file, cursor, MA_SEEK_SET); if (result != MA_SUCCESS) { return result; } @@ -61775,7 +71937,7 @@ MA_API ma_result ma_default_vfs_init(ma_default_vfs* pVFS, const ma_allocation_c pVFS->cb.onSeek = ma_default_vfs_seek; pVFS->cb.onTell = ma_default_vfs_tell; pVFS->cb.onInfo = ma_default_vfs_info; - ma_allocation_callbacks_init_copy(&pVFS->allocationCallbacks, pAllocationCallbacks); + pVFS->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks); return MA_SUCCESS; } @@ -61948,7 +72110,7 @@ extern "C" { #define MA_DR_WAV_XSTRINGIFY(x) MA_DR_WAV_STRINGIFY(x) #define MA_DR_WAV_VERSION_MAJOR 0 #define MA_DR_WAV_VERSION_MINOR 14 -#define MA_DR_WAV_VERSION_REVISION 5 +#define MA_DR_WAV_VERSION_REVISION 6 #define MA_DR_WAV_VERSION_STRING MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MAJOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_MINOR) "." MA_DR_WAV_XSTRINGIFY(MA_DR_WAV_VERSION_REVISION) #include #define MA_DR_WAVE_FORMAT_PCM 0x1 @@ -62383,7 +72545,7 @@ extern "C" { #define MA_DR_FLAC_XSTRINGIFY(x) MA_DR_FLAC_STRINGIFY(x) #define MA_DR_FLAC_VERSION_MAJOR 0 #define MA_DR_FLAC_VERSION_MINOR 13 -#define MA_DR_FLAC_VERSION_REVISION 3 +#define MA_DR_FLAC_VERSION_REVISION 4 #define MA_DR_FLAC_VERSION_STRING MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MAJOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_MINOR) "." MA_DR_FLAC_XSTRINGIFY(MA_DR_FLAC_VERSION_REVISION) #include #if defined(_MSC_VER) && _MSC_VER >= 1700 @@ -62683,7 +72845,7 @@ extern "C" { #define MA_DR_MP3_XSTRINGIFY(x) MA_DR_MP3_STRINGIFY(x) #define MA_DR_MP3_VERSION_MAJOR 0 #define MA_DR_MP3_VERSION_MINOR 7 -#define MA_DR_MP3_VERSION_REVISION 3 +#define MA_DR_MP3_VERSION_REVISION 4 #define MA_DR_MP3_VERSION_STRING MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MAJOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_MINOR) "." MA_DR_MP3_XSTRINGIFY(MA_DR_MP3_VERSION_REVISION) #include #define MA_DR_MP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 @@ -62846,444 +73008,6 @@ Decoding **************************************************************************************************************************************************************/ #ifndef MA_NO_DECODING -static ma_result ma_decoder_read_bytes(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) -{ - MA_ASSERT(pDecoder != NULL); - - return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead, pBytesRead); -} - -static ma_result ma_decoder_seek_bytes(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) -{ - MA_ASSERT(pDecoder != NULL); - - return pDecoder->onSeek(pDecoder, byteOffset, origin); -} - -static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) -{ - MA_ASSERT(pDecoder != NULL); - - if (pDecoder->onTell == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pDecoder->onTell(pDecoder, pCursor); -} - - -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount) -{ - ma_decoding_backend_config config; - - MA_ZERO_OBJECT(&config); - config.preferredFormat = preferredFormat; - config.seekPointCount = seekPointCount; - - return config; -} - - -MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint32 outputChannels, ma_uint32 outputSampleRate) -{ - ma_decoder_config config; - MA_ZERO_OBJECT(&config); - config.format = outputFormat; - config.channels = outputChannels; - config.sampleRate = outputSampleRate; - config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate doesn't matter here. */ - config.encodingFormat = ma_encoding_format_unknown; - - /* Note that we are intentionally leaving the channel map empty here which will cause the default channel map to be used. */ - - return config; -} - -MA_API ma_decoder_config ma_decoder_config_init_default(void) -{ - return ma_decoder_config_init(ma_format_unknown, 0, 0); -} - -MA_API ma_decoder_config ma_decoder_config_init_copy(const ma_decoder_config* pConfig) -{ - ma_decoder_config config; - if (pConfig != NULL) { - config = *pConfig; - } else { - MA_ZERO_OBJECT(&config); - } - - return config; -} - -static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_decoder_config* pConfig) -{ - ma_result result; - ma_data_converter_config converterConfig; - ma_format internalFormat; - ma_uint32 internalChannels; - ma_uint32 internalSampleRate; - ma_channel internalChannelMap[MA_MAX_CHANNELS]; - - MA_ASSERT(pDecoder != NULL); - MA_ASSERT(pConfig != NULL); - - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate, internalChannelMap, ma_countof(internalChannelMap)); - if (result != MA_SUCCESS) { - return result; /* Failed to retrieve the internal data format. */ - } - - - /* Make sure we're not asking for too many channels. */ - if (pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } - - /* The internal channels should have already been validated at a higher level, but we'll do it again explicitly here for safety. */ - if (internalChannels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } - - - /* Output format. */ - if (pConfig->format == ma_format_unknown) { - pDecoder->outputFormat = internalFormat; - } else { - pDecoder->outputFormat = pConfig->format; - } - - if (pConfig->channels == 0) { - pDecoder->outputChannels = internalChannels; - } else { - pDecoder->outputChannels = pConfig->channels; - } - - if (pConfig->sampleRate == 0) { - pDecoder->outputSampleRate = internalSampleRate; - } else { - pDecoder->outputSampleRate = pConfig->sampleRate; - } - - converterConfig = ma_data_converter_config_init( - internalFormat, pDecoder->outputFormat, - internalChannels, pDecoder->outputChannels, - internalSampleRate, pDecoder->outputSampleRate - ); - converterConfig.pChannelMapIn = internalChannelMap; - converterConfig.pChannelMapOut = pConfig->pChannelMap; - converterConfig.channelMixMode = pConfig->channelMixMode; - converterConfig.ditherMode = pConfig->ditherMode; - converterConfig.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ - converterConfig.resampling = pConfig->resampling; - - result = ma_data_converter_init(&converterConfig, &pDecoder->allocationCallbacks, &pDecoder->converter); - if (result != MA_SUCCESS) { - return result; - } - - /* - Now that we have the decoder we need to determine whether or not we need a heap-allocated cache. We'll - need this if the data converter does not support calculation of the required input frame count. To - determine support for this we'll just run a test. - */ - { - ma_uint64 unused; - - result = ma_data_converter_get_required_input_frame_count(&pDecoder->converter, 1, &unused); - if (result != MA_SUCCESS) { - /* - We were unable to calculate the required input frame count which means we'll need to use - a heap-allocated cache. - */ - ma_uint64 inputCacheCapSizeInBytes; - - pDecoder->inputCacheCap = MA_DATA_CONVERTER_STACK_BUFFER_SIZE / ma_get_bytes_per_frame(internalFormat, internalChannels); - - /* Not strictly necessary, but keeping here for safety in case we change the default value of pDecoder->inputCacheCap. */ - inputCacheCapSizeInBytes = pDecoder->inputCacheCap * ma_get_bytes_per_frame(internalFormat, internalChannels); - if (inputCacheCapSizeInBytes > MA_SIZE_MAX) { - ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); - return MA_OUT_OF_MEMORY; - } - - pDecoder->pInputCache = ma_malloc((size_t)inputCacheCapSizeInBytes, &pDecoder->allocationCallbacks); /* Safe cast to size_t. */ - if (pDecoder->pInputCache == NULL) { - ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); - return MA_OUT_OF_MEMORY; - } - } - } - - return MA_SUCCESS; -} - - - -static ma_result ma_decoder_internal_on_read__custom(void* pUserData, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) -{ - ma_decoder* pDecoder = (ma_decoder*)pUserData; - MA_ASSERT(pDecoder != NULL); - - return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead, pBytesRead); -} - -static ma_result ma_decoder_internal_on_seek__custom(void* pUserData, ma_int64 offset, ma_seek_origin origin) -{ - ma_decoder* pDecoder = (ma_decoder*)pUserData; - MA_ASSERT(pDecoder != NULL); - - return ma_decoder_seek_bytes(pDecoder, offset, origin); -} - -static ma_result ma_decoder_internal_on_tell__custom(void* pUserData, ma_int64* pCursor) -{ - ma_decoder* pDecoder = (ma_decoder*)pUserData; - MA_ASSERT(pDecoder != NULL); - - return ma_decoder_tell_bytes(pDecoder, pCursor); -} - - -static ma_result ma_decoder_init_from_vtable__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result; - ma_decoding_backend_config backendConfig; - ma_data_source* pBackend; - - MA_ASSERT(pVTable != NULL); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pVTable->onInit == NULL) { - return MA_NOT_IMPLEMENTED; - } - - backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); - - result = pVTable->onInit(pVTableUserData, ma_decoder_internal_on_read__custom, ma_decoder_internal_on_seek__custom, ma_decoder_internal_on_tell__custom, pDecoder, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); - if (result != MA_SUCCESS) { - return result; /* Failed to initialize the backend from this vtable. */ - } - - /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ - pDecoder->pBackend = pBackend; - pDecoder->pBackendVTable = pVTable; - pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; - - return MA_SUCCESS; -} - -static ma_result ma_decoder_init_from_file__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result; - ma_decoding_backend_config backendConfig; - ma_data_source* pBackend; - - MA_ASSERT(pVTable != NULL); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pVTable->onInitFile == NULL) { - return MA_NOT_IMPLEMENTED; - } - - backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); - - result = pVTable->onInitFile(pVTableUserData, pFilePath, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); - if (result != MA_SUCCESS) { - return result; /* Failed to initialize the backend from this vtable. */ - } - - /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ - pDecoder->pBackend = pBackend; - pDecoder->pBackendVTable = pVTable; - pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; - - return MA_SUCCESS; -} - -static ma_result ma_decoder_init_from_file_w__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result; - ma_decoding_backend_config backendConfig; - ma_data_source* pBackend; - - MA_ASSERT(pVTable != NULL); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pVTable->onInitFileW == NULL) { - return MA_NOT_IMPLEMENTED; - } - - backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); - - result = pVTable->onInitFileW(pVTableUserData, pFilePath, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); - if (result != MA_SUCCESS) { - return result; /* Failed to initialize the backend from this vtable. */ - } - - /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ - pDecoder->pBackend = pBackend; - pDecoder->pBackendVTable = pVTable; - pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; - - return MA_SUCCESS; -} - -static ma_result ma_decoder_init_from_memory__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result; - ma_decoding_backend_config backendConfig; - ma_data_source* pBackend; - - MA_ASSERT(pVTable != NULL); - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pVTable->onInitMemory == NULL) { - return MA_NOT_IMPLEMENTED; - } - - backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); - - result = pVTable->onInitMemory(pVTableUserData, pData, dataSize, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); - if (result != MA_SUCCESS) { - return result; /* Failed to initialize the backend from this vtable. */ - } - - /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ - pDecoder->pBackend = pBackend; - pDecoder->pBackendVTable = pVTable; - pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; - - return MA_SUCCESS; -} - - - -static ma_result ma_decoder_init_custom__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result = MA_NO_BACKEND; - size_t ivtable; - - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pConfig->ppCustomBackendVTables == NULL) { - return MA_NO_BACKEND; - } - - /* The order each backend is listed is what defines the priority. */ - for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { - const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; - if (pVTable != NULL) { - result = ma_decoder_init_from_vtable__internal(pVTable, pConfig->pCustomBackendUserData, pConfig, pDecoder); - if (result == MA_SUCCESS) { - return MA_SUCCESS; - } else { - /* Initialization failed. Move on to the next one, but seek back to the start first so the next vtable starts from the first byte of the file. */ - result = ma_decoder_seek_bytes(pDecoder, 0, ma_seek_origin_start); - if (result != MA_SUCCESS) { - return result; /* Failed to seek back to the start. */ - } - } - } else { - /* No vtable. */ - } - } - - /* Getting here means we couldn't find a backend. */ - return MA_NO_BACKEND; -} - -static ma_result ma_decoder_init_custom_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result = MA_NO_BACKEND; - size_t ivtable; - - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pConfig->ppCustomBackendVTables == NULL) { - return MA_NO_BACKEND; - } - - /* The order each backend is listed is what defines the priority. */ - for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { - const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; - if (pVTable != NULL) { - result = ma_decoder_init_from_file__internal(pVTable, pConfig->pCustomBackendUserData, pFilePath, pConfig, pDecoder); - if (result == MA_SUCCESS) { - return MA_SUCCESS; - } - } else { - /* No vtable. */ - } - } - - /* Getting here means we couldn't find a backend. */ - return MA_NO_BACKEND; -} - -static ma_result ma_decoder_init_custom_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result = MA_NO_BACKEND; - size_t ivtable; - - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pConfig->ppCustomBackendVTables == NULL) { - return MA_NO_BACKEND; - } - - /* The order each backend is listed is what defines the priority. */ - for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { - const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; - if (pVTable != NULL) { - result = ma_decoder_init_from_file_w__internal(pVTable, pConfig->pCustomBackendUserData, pFilePath, pConfig, pDecoder); - if (result == MA_SUCCESS) { - return MA_SUCCESS; - } - } else { - /* No vtable. */ - } - } - - /* Getting here means we couldn't find a backend. */ - return MA_NO_BACKEND; -} - -static ma_result ma_decoder_init_custom_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - ma_result result = MA_NO_BACKEND; - size_t ivtable; - - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); - - if (pConfig->ppCustomBackendVTables == NULL) { - return MA_NO_BACKEND; - } - - /* The order each backend is listed is what defines the priority. */ - for (ivtable = 0; ivtable < pConfig->customBackendCount; ivtable += 1) { - const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; - if (pVTable != NULL) { - result = ma_decoder_init_from_memory__internal(pVTable, pConfig->pCustomBackendUserData, pData, dataSize, pConfig, pDecoder); - if (result == MA_SUCCESS) { - return MA_SUCCESS; - } - } else { - /* No vtable. */ - } - } - - /* Getting here means we couldn't find a backend. */ - return MA_NO_BACKEND; -} - /* WAV */ #ifdef ma_dr_wav_h @@ -63302,10 +73026,8 @@ typedef struct } ma_wav; MA_API ma_result ma_wav_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_wav* pWav); -MA_API ma_result ma_wav_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_wav* pWav); -MA_API ma_result ma_wav_init_file_w(const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_wav* pWav); MA_API ma_result ma_wav_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_wav* pWav); -MA_API void ma_wav_uninit(ma_wav* pWav, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_wav_uninit(ma_wav* pWav); MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_wav_seek_to_pcm_frame(ma_wav* pWav, ma_uint64 frameIndex); MA_API ma_result ma_wav_get_data_format(ma_wav* pWav, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); @@ -63313,40 +73035,68 @@ MA_API ma_result ma_wav_get_cursor_in_pcm_frames(ma_wav* pWav, ma_uint64* pCurso MA_API ma_result ma_wav_get_length_in_pcm_frames(ma_wav* pWav, ma_uint64* pLength); -static ma_result ma_wav_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +static size_t ma_wav_ds_sizeof(void) { - return ma_wav_read_pcm_frames((ma_wav*)pDataSource, pFramesOut, frameCount, pFramesRead); + return sizeof(ma_wav); } -static ma_result ma_wav_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +static void ma_wav_ds_uninit(ma_data_source* pDataSource) { - return ma_wav_seek_to_pcm_frame((ma_wav*)pDataSource, frameIndex); + ma_wav_uninit((ma_wav*)pDataSource); } -static ma_result ma_wav_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_wav_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - return ma_wav_get_data_format((ma_wav*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + return ma_wav_read_pcm_frames((ma_wav*)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_wav_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +static ma_result ma_wav_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) { - return ma_wav_get_cursor_in_pcm_frames((ma_wav*)pDataSource, pCursor); + return ma_wav_seek_to_pcm_frame((ma_wav*)pDataSource, frameIndex); } -static ma_result ma_wav_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +static ma_result ma_wav_ds_prop(ma_data_source* pDataSource, int prop, void* pData) { - return ma_wav_get_length_in_pcm_frames((ma_wav*)pDataSource, pLength); + ma_wav* pWav = (ma_wav*)pDataSource; + + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; + + return ma_wav_get_data_format(pWav, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0); + } + + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + return ma_wav_get_data_format(pWav, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS); + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_wav_get_cursor_in_pcm_frames(pWav, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_wav_get_length_in_pcm_frames(pWav, (ma_uint64*)pData); + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_wav_ds_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_WAV = { + ma_wav_ds_sizeof, + ma_wav_ds_uninit, + NULL, /* onCopy. Copying not supported. */ ma_wav_ds_read, ma_wav_ds_seek, - ma_wav_ds_get_data_format, - ma_wav_ds_get_cursor, - ma_wav_ds_get_length, - NULL, /* onSetLooping */ - 0 + ma_wav_ds_prop }; @@ -63373,11 +73123,11 @@ static ma_bool32 ma_wav_dr_callback__seek(void* pUserData, int offset, ma_dr_wav MA_ASSERT(pWav != NULL); - maSeekOrigin = ma_seek_origin_start; + maSeekOrigin = MA_SEEK_SET; if (origin == MA_DR_WAV_SEEK_CUR) { - maSeekOrigin = ma_seek_origin_current; + maSeekOrigin = MA_SEEK_CUR; } else if (origin == MA_DR_WAV_SEEK_END) { - maSeekOrigin = ma_seek_origin_end; + maSeekOrigin = MA_SEEK_END; } result = pWav->onSeek(pWav->pReadSeekTellUserData, offset, maSeekOrigin); @@ -63428,9 +73178,9 @@ static ma_result ma_wav_init_internal(const ma_decoding_backend_config* pConfig, } dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_wav_ds_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_WAV; - result = ma_data_source_init(&dataSourceConfig, &pWav->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pWav->ds); if (result != MA_SUCCESS) { return result; /* Failed to initialize the base data source. */ } @@ -63519,70 +73269,6 @@ MA_API ma_result ma_wav_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p #endif } -MA_API ma_result ma_wav_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_wav* pWav) -{ - ma_result result; - - result = ma_wav_init_internal(pConfig, pWav); - if (result != MA_SUCCESS) { - return result; - } - - #if !defined(MA_NO_WAV) - { - ma_bool32 wavResult; - - wavResult = ma_dr_wav_init_file(&pWav->dr, pFilePath, pAllocationCallbacks); - if (wavResult != MA_TRUE) { - return MA_INVALID_FILE; - } - - ma_wav_post_init(pWav); - - return MA_SUCCESS; - } - #else - { - /* wav is disabled. */ - (void)pFilePath; - (void)pAllocationCallbacks; - return MA_NOT_IMPLEMENTED; - } - #endif -} - -MA_API ma_result ma_wav_init_file_w(const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_wav* pWav) -{ - ma_result result; - - result = ma_wav_init_internal(pConfig, pWav); - if (result != MA_SUCCESS) { - return result; - } - - #if !defined(MA_NO_WAV) - { - ma_bool32 wavResult; - - wavResult = ma_dr_wav_init_file_w(&pWav->dr, pFilePath, pAllocationCallbacks); - if (wavResult != MA_TRUE) { - return MA_INVALID_FILE; - } - - ma_wav_post_init(pWav); - - return MA_SUCCESS; - } - #else - { - /* wav is disabled. */ - (void)pFilePath; - (void)pAllocationCallbacks; - return MA_NOT_IMPLEMENTED; - } - #endif -} - MA_API ma_result ma_wav_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_wav* pWav) { ma_result result; @@ -63616,14 +73302,12 @@ MA_API ma_result ma_wav_init_memory(const void* pData, size_t dataSize, const ma #endif } -MA_API void ma_wav_uninit(ma_wav* pWav, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API void ma_wav_uninit(ma_wav* pWav) { if (pWav == NULL) { return; } - (void)pAllocationCallbacks; - #if !defined(MA_NO_WAV) { ma_dr_wav_uninit(&pWav->dr); @@ -63635,7 +73319,7 @@ MA_API void ma_wav_uninit(ma_wav* pWav, const ma_allocation_callbacks* pAllocati } #endif - ma_data_source_uninit(&pWav->ds); + ma_data_source_base_uninit(&pWav->ds); } MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) @@ -63853,56 +73537,17 @@ MA_API ma_result ma_wav_get_length_in_pcm_frames(ma_wav* pWav, ma_uint64* pLengt #endif } - -static ma_result ma_decoding_backend_init__wav(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static void ma_decoding_backend_info__wav(void* pUserData, ma_decoding_backend_info* pInfo) { - ma_result result; - ma_wav* pWav; - - (void)pUserData; /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ - - /* For now we're just allocating the decoder backend on the heap. */ - pWav = (ma_wav*)ma_malloc(sizeof(*pWav), pAllocationCallbacks); - if (pWav == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_wav_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pWav); - if (result != MA_SUCCESS) { - ma_free(pWav, pAllocationCallbacks); - return result; - } - - *ppBackend = pWav; - - return MA_SUCCESS; -} - -static ma_result ma_decoding_backend_init_file__wav(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_wav* pWav; - - (void)pUserData; /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ - - /* For now we're just allocating the decoder backend on the heap. */ - pWav = (ma_wav*)ma_malloc(sizeof(*pWav), pAllocationCallbacks); - if (pWav == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_wav_init_file(pFilePath, pConfig, pAllocationCallbacks, pWav); - if (result != MA_SUCCESS) { - ma_free(pWav, pAllocationCallbacks); - return result; - } - - *ppBackend = pWav; + (void)pUserData; - return MA_SUCCESS; + pInfo->pName = "WAV"; + pInfo->pLibraryName = "miniaudio"; + pInfo->pVendor = "miniaudio"; + pInfo->encodingFormat = ma_encoding_format_wav; } -static ma_result ma_decoding_backend_init_file_w__wav(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static ma_result ma_decoding_backend_init__wav(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) { ma_result result; ma_wav* pWav; @@ -63915,7 +73560,7 @@ static ma_result ma_decoding_backend_init_file_w__wav(void* pUserData, const wch return MA_OUT_OF_MEMORY; } - result = ma_wav_init_file_w(pFilePath, pConfig, pAllocationCallbacks, pWav); + result = ma_wav_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pWav); if (result != MA_SUCCESS) { ma_free(pWav, pAllocationCallbacks); return result; @@ -63956,39 +73601,27 @@ static void ma_decoding_backend_uninit__wav(void* pUserData, ma_data_source* pBa (void)pUserData; - ma_wav_uninit(pWav, pAllocationCallbacks); + ma_wav_uninit(pWav); ma_free(pWav, pAllocationCallbacks); } -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_wav = +static ma_decoding_backend_vtable ma_gDecodingBackendVTable_WAV = { + ma_decoding_backend_info__wav, ma_decoding_backend_init__wav, - ma_decoding_backend_init_file__wav, - ma_decoding_backend_init_file_w__wav, ma_decoding_backend_init_memory__wav, ma_decoding_backend_uninit__wav }; +ma_decoding_backend_vtable* ma_decoding_backend_wav = &ma_gDecodingBackendVTable_WAV; +#else +ma_decoding_backend_vtable* ma_decoding_backend_wav = NULL; +#endif /* ma_dr_wav_h */ -static ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_wav, NULL, pConfig, pDecoder); -} - -static ma_result ma_decoder_init_wav_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_wav, NULL, pFilePath, pConfig, pDecoder); -} - -static ma_result ma_decoder_init_wav_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +MA_API ma_decoding_backend_vtable* ma_wav_get_vtable(void) { - return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_wav, NULL, pFilePath, pConfig, pDecoder); + return ma_decoding_backend_wav; } -static ma_result ma_decoder_init_wav_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_wav, NULL, pData, dataSize, pConfig, pDecoder); -} -#endif /* ma_dr_wav_h */ /* FLAC */ #ifdef ma_dr_flac_h @@ -64007,10 +73640,8 @@ typedef struct } ma_flac; MA_API ma_result ma_flac_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_flac* pFlac); -MA_API ma_result ma_flac_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_flac* pFlac); -MA_API ma_result ma_flac_init_file_w(const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_flac* pFlac); MA_API ma_result ma_flac_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_flac* pFlac); -MA_API void ma_flac_uninit(ma_flac* pFlac, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_flac_uninit(ma_flac* pFlac); MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_flac_seek_to_pcm_frame(ma_flac* pFlac, ma_uint64 frameIndex); MA_API ma_result ma_flac_get_data_format(ma_flac* pFlac, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); @@ -64018,40 +73649,68 @@ MA_API ma_result ma_flac_get_cursor_in_pcm_frames(ma_flac* pFlac, ma_uint64* pCu MA_API ma_result ma_flac_get_length_in_pcm_frames(ma_flac* pFlac, ma_uint64* pLength); -static ma_result ma_flac_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +static size_t ma_flac_ds_sizeof(void) { - return ma_flac_read_pcm_frames((ma_flac*)pDataSource, pFramesOut, frameCount, pFramesRead); + return sizeof(ma_flac); } -static ma_result ma_flac_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +static void ma_flac_ds_uninit(ma_data_source* pDataSource) { - return ma_flac_seek_to_pcm_frame((ma_flac*)pDataSource, frameIndex); + ma_flac_uninit((ma_flac*)pDataSource); } -static ma_result ma_flac_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_flac_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - return ma_flac_get_data_format((ma_flac*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + return ma_flac_read_pcm_frames((ma_flac*)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_flac_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +static ma_result ma_flac_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) { - return ma_flac_get_cursor_in_pcm_frames((ma_flac*)pDataSource, pCursor); + return ma_flac_seek_to_pcm_frame((ma_flac*)pDataSource, frameIndex); } -static ma_result ma_flac_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +static ma_result ma_flac_ds_prop(ma_data_source* pDataSource, int prop, void* pData) { - return ma_flac_get_length_in_pcm_frames((ma_flac*)pDataSource, pLength); + ma_flac* pFlac = (ma_flac*)pDataSource; + + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; + + return ma_flac_get_data_format(pFlac, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0); + } + + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + return ma_flac_get_data_format(pFlac, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS); + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_flac_get_cursor_in_pcm_frames(pFlac, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_flac_get_length_in_pcm_frames(pFlac, (ma_uint64*)pData); + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_flac_ds_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_FLAC = { + ma_flac_ds_sizeof, + ma_flac_ds_uninit, + NULL, /* onCopy. Copying not supported. */ ma_flac_ds_read, ma_flac_ds_seek, - ma_flac_ds_get_data_format, - ma_flac_ds_get_cursor, - ma_flac_ds_get_length, - NULL, /* onSetLooping */ - 0 + ma_flac_ds_prop }; @@ -64078,11 +73737,11 @@ static ma_bool32 ma_flac_dr_callback__seek(void* pUserData, int offset, ma_dr_fl MA_ASSERT(pFlac != NULL); - maSeekOrigin = ma_seek_origin_start; + maSeekOrigin = MA_SEEK_SET; if (origin == MA_DR_FLAC_SEEK_CUR) { - maSeekOrigin = ma_seek_origin_current; + maSeekOrigin = MA_SEEK_CUR; } else if (origin == MA_DR_FLAC_SEEK_END) { - maSeekOrigin = ma_seek_origin_end; + maSeekOrigin = MA_SEEK_END; } result = pFlac->onSeek(pFlac->pReadSeekTellUserData, offset, maSeekOrigin); @@ -64133,9 +73792,9 @@ static ma_result ma_flac_init_internal(const ma_decoding_backend_config* pConfig } dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_flac_ds_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_FLAC; - result = ma_data_source_init(&dataSourceConfig, &pFlac->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pFlac->ds); if (result != MA_SUCCESS) { return result; /* Failed to initialize the base data source. */ } @@ -64179,62 +73838,6 @@ MA_API ma_result ma_flac_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_ #endif } -MA_API ma_result ma_flac_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_flac* pFlac) -{ - ma_result result; - - result = ma_flac_init_internal(pConfig, pFlac); - if (result != MA_SUCCESS) { - return result; - } - - #if !defined(MA_NO_FLAC) - { - pFlac->dr = ma_dr_flac_open_file(pFilePath, pAllocationCallbacks); - if (pFlac->dr == NULL) { - return MA_INVALID_FILE; - } - - return MA_SUCCESS; - } - #else - { - /* flac is disabled. */ - (void)pFilePath; - (void)pAllocationCallbacks; - return MA_NOT_IMPLEMENTED; - } - #endif -} - -MA_API ma_result ma_flac_init_file_w(const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_flac* pFlac) -{ - ma_result result; - - result = ma_flac_init_internal(pConfig, pFlac); - if (result != MA_SUCCESS) { - return result; - } - - #if !defined(MA_NO_FLAC) - { - pFlac->dr = ma_dr_flac_open_file_w(pFilePath, pAllocationCallbacks); - if (pFlac->dr == NULL) { - return MA_INVALID_FILE; - } - - return MA_SUCCESS; - } - #else - { - /* flac is disabled. */ - (void)pFilePath; - (void)pAllocationCallbacks; - return MA_NOT_IMPLEMENTED; - } - #endif -} - MA_API ma_result ma_flac_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_flac* pFlac) { ma_result result; @@ -64264,14 +73867,12 @@ MA_API ma_result ma_flac_init_memory(const void* pData, size_t dataSize, const m #endif } -MA_API void ma_flac_uninit(ma_flac* pFlac, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API void ma_flac_uninit(ma_flac* pFlac) { if (pFlac == NULL) { return; } - (void)pAllocationCallbacks; - #if !defined(MA_NO_FLAC) { ma_dr_flac_close(pFlac->dr); @@ -64283,7 +73884,7 @@ MA_API void ma_flac_uninit(ma_flac* pFlac, const ma_allocation_callbacks* pAlloc } #endif - ma_data_source_uninit(&pFlac->ds); + ma_data_source_base_uninit(&pFlac->ds); } MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) @@ -64497,55 +74098,17 @@ MA_API ma_result ma_flac_get_length_in_pcm_frames(ma_flac* pFlac, ma_uint64* pLe } -static ma_result ma_decoding_backend_init__flac(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static void ma_decoding_backend_info__flac(void* pUserData, ma_decoding_backend_info* pInfo) { - ma_result result; - ma_flac* pFlac; - - (void)pUserData; /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ - - /* For now we're just allocating the decoder backend on the heap. */ - pFlac = (ma_flac*)ma_malloc(sizeof(*pFlac), pAllocationCallbacks); - if (pFlac == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_flac_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pFlac); - if (result != MA_SUCCESS) { - ma_free(pFlac, pAllocationCallbacks); - return result; - } - - *ppBackend = pFlac; - - return MA_SUCCESS; -} - -static ma_result ma_decoding_backend_init_file__flac(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_flac* pFlac; - - (void)pUserData; /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ - - /* For now we're just allocating the decoder backend on the heap. */ - pFlac = (ma_flac*)ma_malloc(sizeof(*pFlac), pAllocationCallbacks); - if (pFlac == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_flac_init_file(pFilePath, pConfig, pAllocationCallbacks, pFlac); - if (result != MA_SUCCESS) { - ma_free(pFlac, pAllocationCallbacks); - return result; - } - - *ppBackend = pFlac; + (void)pUserData; - return MA_SUCCESS; + pInfo->pName = "FLAC"; + pInfo->pLibraryName = "miniaudio"; + pInfo->pVendor = "miniaudio"; + pInfo->encodingFormat = ma_encoding_format_flac; } -static ma_result ma_decoding_backend_init_file_w__flac(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static ma_result ma_decoding_backend_init__flac(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) { ma_result result; ma_flac* pFlac; @@ -64558,7 +74121,7 @@ static ma_result ma_decoding_backend_init_file_w__flac(void* pUserData, const wc return MA_OUT_OF_MEMORY; } - result = ma_flac_init_file_w(pFilePath, pConfig, pAllocationCallbacks, pFlac); + result = ma_flac_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pFlac); if (result != MA_SUCCESS) { ma_free(pFlac, pAllocationCallbacks); return result; @@ -64599,39 +74162,27 @@ static void ma_decoding_backend_uninit__flac(void* pUserData, ma_data_source* pB (void)pUserData; - ma_flac_uninit(pFlac, pAllocationCallbacks); + ma_flac_uninit(pFlac); ma_free(pFlac, pAllocationCallbacks); } -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_flac = +static ma_decoding_backend_vtable ma_gDecodingBackendVTable_FLAC = { + ma_decoding_backend_info__flac, ma_decoding_backend_init__flac, - ma_decoding_backend_init_file__flac, - ma_decoding_backend_init_file_w__flac, ma_decoding_backend_init_memory__flac, ma_decoding_backend_uninit__flac }; +ma_decoding_backend_vtable* ma_decoding_backend_flac = &ma_gDecodingBackendVTable_FLAC; +#else +ma_decoding_backend_vtable* ma_decoding_backend_flac = NULL; +#endif /* ma_dr_flac_h */ -static ma_result ma_decoder_init_flac__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +MA_API ma_decoding_backend_vtable* ma_flac_get_vtable(void) { - return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_flac, NULL, pConfig, pDecoder); + return ma_decoding_backend_flac; } -static ma_result ma_decoder_init_flac_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_flac, NULL, pFilePath, pConfig, pDecoder); -} - -static ma_result ma_decoder_init_flac_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_flac, NULL, pFilePath, pConfig, pDecoder); -} - -static ma_result ma_decoder_init_flac_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_flac, NULL, pData, dataSize, pConfig, pDecoder); -} -#endif /* ma_dr_flac_h */ /* MP3 */ #ifdef ma_dr_mp3_h @@ -64652,10 +74203,8 @@ typedef struct } ma_mp3; MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3); -MA_API ma_result ma_mp3_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3); -MA_API ma_result ma_mp3_init_file_w(const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3); MA_API ma_result ma_mp3_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3); -MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_mp3_uninit(ma_mp3* pMP3); MA_API ma_result ma_mp3_read_pcm_frames(ma_mp3* pMP3, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_mp3_seek_to_pcm_frame(ma_mp3* pMP3, ma_uint64 frameIndex); MA_API ma_result ma_mp3_get_data_format(ma_mp3* pMP3, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); @@ -64663,40 +74212,68 @@ MA_API ma_result ma_mp3_get_cursor_in_pcm_frames(ma_mp3* pMP3, ma_uint64* pCurso MA_API ma_result ma_mp3_get_length_in_pcm_frames(ma_mp3* pMP3, ma_uint64* pLength); -static ma_result ma_mp3_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +static size_t ma_mp3_ds_sizeof(void) { - return ma_mp3_read_pcm_frames((ma_mp3*)pDataSource, pFramesOut, frameCount, pFramesRead); + return sizeof(ma_mp3); } -static ma_result ma_mp3_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +static void ma_mp3_ds_uninit(ma_data_source* pDataSource) { - return ma_mp3_seek_to_pcm_frame((ma_mp3*)pDataSource, frameIndex); + ma_mp3_uninit((ma_mp3*)pDataSource); } -static ma_result ma_mp3_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_mp3_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - return ma_mp3_get_data_format((ma_mp3*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + return ma_mp3_read_pcm_frames((ma_mp3*)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_mp3_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +static ma_result ma_mp3_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) { - return ma_mp3_get_cursor_in_pcm_frames((ma_mp3*)pDataSource, pCursor); + return ma_mp3_seek_to_pcm_frame((ma_mp3*)pDataSource, frameIndex); } -static ma_result ma_mp3_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +static ma_result ma_mp3_ds_prop(ma_data_source* pDataSource, int prop, void* pData) { - return ma_mp3_get_length_in_pcm_frames((ma_mp3*)pDataSource, pLength); + ma_mp3* pMP3 = (ma_mp3*)pDataSource; + + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; + + return ma_mp3_get_data_format(pMP3, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0); + } + + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + return ma_mp3_get_data_format(pMP3, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS); + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_mp3_get_cursor_in_pcm_frames(pMP3, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_mp3_get_length_in_pcm_frames(pMP3, (ma_uint64*)pData); + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_mp3_ds_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_MP3 = { + ma_mp3_ds_sizeof, + ma_mp3_ds_uninit, + NULL, /* onCopy. Copying not supported. */ ma_mp3_ds_read, ma_mp3_ds_seek, - ma_mp3_ds_get_data_format, - ma_mp3_ds_get_cursor, - ma_mp3_ds_get_length, - NULL, /* onSetLooping */ - 0 + ma_mp3_ds_prop }; @@ -64724,11 +74301,11 @@ static ma_bool32 ma_mp3_dr_callback__seek(void* pUserData, int offset, ma_dr_mp3 MA_ASSERT(pMP3 != NULL); if (origin == MA_DR_MP3_SEEK_SET) { - maSeekOrigin = ma_seek_origin_start; + maSeekOrigin = MA_SEEK_SET; } else if (origin == MA_DR_MP3_SEEK_END) { - maSeekOrigin = ma_seek_origin_end; + maSeekOrigin = MA_SEEK_END; } else { - maSeekOrigin = ma_seek_origin_current; + maSeekOrigin = MA_SEEK_CUR; } result = pMP3->onSeek(pMP3->pReadSeekTellUserData, offset, maSeekOrigin); @@ -64774,9 +74351,9 @@ static ma_result ma_mp3_init_internal(const ma_decoding_backend_config* pConfig, } dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_mp3_ds_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_MP3; - result = ma_data_source_init(&dataSourceConfig, &pMP3->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pMP3->ds); if (result != MA_SUCCESS) { return result; /* Failed to initialize the base data source. */ } @@ -64871,70 +74448,6 @@ MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p #endif } -MA_API ma_result ma_mp3_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3) -{ - ma_result result; - - result = ma_mp3_init_internal(pConfig, pMP3); - if (result != MA_SUCCESS) { - return result; - } - - #if !defined(MA_NO_MP3) - { - ma_bool32 mp3Result; - - mp3Result = ma_dr_mp3_init_file(&pMP3->dr, pFilePath, pAllocationCallbacks); - if (mp3Result != MA_TRUE) { - return MA_INVALID_FILE; - } - - ma_mp3_post_init(pMP3, pConfig, pAllocationCallbacks); - - return MA_SUCCESS; - } - #else - { - /* mp3 is disabled. */ - (void)pFilePath; - (void)pAllocationCallbacks; - return MA_NOT_IMPLEMENTED; - } - #endif -} - -MA_API ma_result ma_mp3_init_file_w(const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3) -{ - ma_result result; - - result = ma_mp3_init_internal(pConfig, pMP3); - if (result != MA_SUCCESS) { - return result; - } - - #if !defined(MA_NO_MP3) - { - ma_bool32 mp3Result; - - mp3Result = ma_dr_mp3_init_file_w(&pMP3->dr, pFilePath, pAllocationCallbacks); - if (mp3Result != MA_TRUE) { - return MA_INVALID_FILE; - } - - ma_mp3_post_init(pMP3, pConfig, pAllocationCallbacks); - - return MA_SUCCESS; - } - #else - { - /* mp3 is disabled. */ - (void)pFilePath; - (void)pAllocationCallbacks; - return MA_NOT_IMPLEMENTED; - } - #endif -} - MA_API ma_result ma_mp3_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3) { ma_result result; @@ -64968,27 +74481,31 @@ MA_API ma_result ma_mp3_init_memory(const void* pData, size_t dataSize, const ma #endif } -MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API void ma_mp3_uninit(ma_mp3* pMP3) { + ma_allocation_callbacks allocationCallbacks; + if (pMP3 == NULL) { return; } #if !defined(MA_NO_MP3) { + allocationCallbacks = pMP3->dr.allocationCallbacks; ma_dr_mp3_uninit(&pMP3->dr); } #else { /* mp3 is disabled. Should never hit this since initialization would have failed. */ + allocationCallbacks = ma_allocation_callbacks_init_default(); MA_ASSERT(MA_FALSE); } #endif /* Seek points need to be freed after the MP3 decoder has been uninitialized to ensure they're no longer being referenced. */ - ma_free(pMP3->pSeekPoints, pAllocationCallbacks); + ma_free(pMP3->pSeekPoints, &allocationCallbacks); - ma_data_source_uninit(&pMP3->ds); + ma_data_source_base_uninit(&pMP3->ds); } MA_API ma_result ma_mp3_read_pcm_frames(ma_mp3* pMP3, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) @@ -65194,55 +74711,17 @@ MA_API ma_result ma_mp3_get_length_in_pcm_frames(ma_mp3* pMP3, ma_uint64* pLengt } -static ma_result ma_decoding_backend_init__mp3(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_mp3* pMP3; - - (void)pUserData; /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ - - /* For now we're just allocating the decoder backend on the heap. */ - pMP3 = (ma_mp3*)ma_malloc(sizeof(*pMP3), pAllocationCallbacks); - if (pMP3 == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_mp3_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pMP3); - if (result != MA_SUCCESS) { - ma_free(pMP3, pAllocationCallbacks); - return result; - } - - *ppBackend = pMP3; - - return MA_SUCCESS; -} - -static ma_result ma_decoding_backend_init_file__mp3(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static void ma_decoding_backend_info__mp3(void* pUserData, ma_decoding_backend_info* pInfo) { - ma_result result; - ma_mp3* pMP3; - - (void)pUserData; /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ - - /* For now we're just allocating the decoder backend on the heap. */ - pMP3 = (ma_mp3*)ma_malloc(sizeof(*pMP3), pAllocationCallbacks); - if (pMP3 == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_mp3_init_file(pFilePath, pConfig, pAllocationCallbacks, pMP3); - if (result != MA_SUCCESS) { - ma_free(pMP3, pAllocationCallbacks); - return result; - } - - *ppBackend = pMP3; + (void)pUserData; - return MA_SUCCESS; + pInfo->pName = "MP3"; + pInfo->pLibraryName = "miniaudio"; + pInfo->pVendor = "miniaudio"; + pInfo->encodingFormat = ma_encoding_format_mp3; } -static ma_result ma_decoding_backend_init_file_w__mp3(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static ma_result ma_decoding_backend_init__mp3(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) { ma_result result; ma_mp3* pMP3; @@ -65255,7 +74734,7 @@ static ma_result ma_decoding_backend_init_file_w__mp3(void* pUserData, const wch return MA_OUT_OF_MEMORY; } - result = ma_mp3_init_file_w(pFilePath, pConfig, pAllocationCallbacks, pMP3); + result = ma_mp3_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pMP3); if (result != MA_SUCCESS) { ma_free(pMP3, pAllocationCallbacks); return result; @@ -65296,39 +74775,27 @@ static void ma_decoding_backend_uninit__mp3(void* pUserData, ma_data_source* pBa (void)pUserData; - ma_mp3_uninit(pMP3, pAllocationCallbacks); + ma_mp3_uninit(pMP3); ma_free(pMP3, pAllocationCallbacks); } -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_mp3 = +static ma_decoding_backend_vtable ma_gDecodingBackendVTable_MP3 = { + ma_decoding_backend_info__mp3, ma_decoding_backend_init__mp3, - ma_decoding_backend_init_file__mp3, - ma_decoding_backend_init_file_w__mp3, ma_decoding_backend_init_memory__mp3, ma_decoding_backend_uninit__mp3 }; +ma_decoding_backend_vtable* ma_decoding_backend_mp3 = &ma_gDecodingBackendVTable_MP3; +#else +ma_decoding_backend_vtable* ma_decoding_backend_mp3 = NULL; +#endif /* ma_dr_mp3_h */ -static ma_result ma_decoder_init_mp3__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pConfig, pDecoder); -} - -static ma_result ma_decoder_init_mp3_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pFilePath, pConfig, pDecoder); -} - -static ma_result ma_decoder_init_mp3_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +MA_API ma_decoding_backend_vtable* ma_mp3_get_vtable(void) { - return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pFilePath, pConfig, pDecoder); + return ma_decoding_backend_mp3; } -static ma_result ma_decoder_init_mp3_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_mp3, NULL, pData, dataSize, pConfig, pDecoder); -} -#endif /* ma_dr_mp3_h */ /* Vorbis */ #ifdef STB_VORBIS_INCLUDE_STB_VORBIS_H @@ -65366,9 +74833,8 @@ typedef struct } ma_stbvorbis; MA_API ma_result ma_stbvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_stbvorbis* pVorbis); -MA_API ma_result ma_stbvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_stbvorbis* pVorbis); MA_API ma_result ma_stbvorbis_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_stbvorbis* pVorbis); -MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis); MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_stbvorbis_seek_to_pcm_frame(ma_stbvorbis* pVorbis, ma_uint64 frameIndex); MA_API ma_result ma_stbvorbis_get_data_format(ma_stbvorbis* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); @@ -65376,6 +74842,16 @@ MA_API ma_result ma_stbvorbis_get_cursor_in_pcm_frames(ma_stbvorbis* pVorbis, ma MA_API ma_result ma_stbvorbis_get_length_in_pcm_frames(ma_stbvorbis* pVorbis, ma_uint64* pLength); +static size_t ma_stbvorbis_ds_sizeof(void) +{ + return sizeof(ma_stbvorbis); +} + +static void ma_stbvorbis_ds_uninit(ma_data_source* pDataSource) +{ + ma_stbvorbis_uninit((ma_stbvorbis*)pDataSource); +} + static ma_result ma_stbvorbis_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { return ma_stbvorbis_read_pcm_frames((ma_stbvorbis*)pDataSource, pFramesOut, frameCount, pFramesRead); @@ -65386,30 +74862,48 @@ static ma_result ma_stbvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 fra return ma_stbvorbis_seek_to_pcm_frame((ma_stbvorbis*)pDataSource, frameIndex); } -static ma_result ma_stbvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_stbvorbis_ds_prop(ma_data_source* pDataSource, int prop, void* pData) { - return ma_stbvorbis_get_data_format((ma_stbvorbis*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); -} + ma_stbvorbis* pVorbis = (ma_stbvorbis*)pDataSource; -static ma_result ma_stbvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) -{ - return ma_stbvorbis_get_cursor_in_pcm_frames((ma_stbvorbis*)pDataSource, pCursor); -} + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; -static ma_result ma_stbvorbis_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) -{ - return ma_stbvorbis_get_length_in_pcm_frames((ma_stbvorbis*)pDataSource, pLength); + return ma_stbvorbis_get_data_format(pVorbis, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0); + } + + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + return ma_stbvorbis_get_data_format(pVorbis, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS); + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_stbvorbis_get_cursor_in_pcm_frames(pVorbis, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_stbvorbis_get_length_in_pcm_frames(pVorbis, (ma_uint64*)pData); + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_stbvorbis_ds_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_stbvorbis = { + ma_stbvorbis_ds_sizeof, + ma_stbvorbis_ds_uninit, + NULL, /* onCopy. Copying not supported. */ ma_stbvorbis_ds_read, ma_stbvorbis_ds_seek, - ma_stbvorbis_ds_get_data_format, - ma_stbvorbis_ds_get_cursor, - ma_stbvorbis_ds_get_length, - NULL, /* onSetLooping */ - 0 + ma_stbvorbis_ds_prop }; @@ -65428,9 +74922,9 @@ static ma_result ma_stbvorbis_init_internal(const ma_decoding_backend_config* pC pVorbis->format = ma_format_f32; /* Only supporting f32. */ dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_stbvorbis_ds_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_stbvorbis; - result = ma_data_source_init(&dataSourceConfig, &pVorbis->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pVorbis->ds); if (result != MA_SUCCESS) { return result; /* Failed to initialize the base data source. */ } @@ -65546,7 +75040,7 @@ MA_API ma_result ma_stbvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_ pVorbis->onSeek = onSeek; pVorbis->onTell = onTell; pVorbis->pReadSeekTellUserData = pReadSeekTellUserData; - ma_allocation_callbacks_init_copy(&pVorbis->allocationCallbacks, pAllocationCallbacks); + pVorbis->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks); #if !defined(MA_NO_VORBIS) { @@ -65580,45 +75074,6 @@ MA_API ma_result ma_stbvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_ #endif } -MA_API ma_result ma_stbvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_stbvorbis* pVorbis) -{ - ma_result result; - - result = ma_stbvorbis_init_internal(pConfig, pVorbis); - if (result != MA_SUCCESS) { - return result; - } - - #if !defined(MA_NO_VORBIS) - { - (void)pAllocationCallbacks; /* Don't know how to make use of this with stb_vorbis. */ - - /* We can use stb_vorbis' pull mode for file based streams. */ - pVorbis->stb = stb_vorbis_open_filename(pFilePath, NULL, NULL); - if (pVorbis->stb == NULL) { - return MA_INVALID_FILE; - } - - pVorbis->usingPushMode = MA_FALSE; - - result = ma_stbvorbis_post_init(pVorbis); - if (result != MA_SUCCESS) { - stb_vorbis_close(pVorbis->stb); - return result; - } - - return MA_SUCCESS; - } - #else - { - /* vorbis is disabled. */ - (void)pFilePath; - (void)pAllocationCallbacks; - return MA_NOT_IMPLEMENTED; - } - #endif -} - MA_API ma_result ma_stbvorbis_init_memory(const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_stbvorbis* pVorbis) { ma_result result; @@ -65663,7 +75118,7 @@ MA_API ma_result ma_stbvorbis_init_memory(const void* pData, size_t dataSize, co #endif } -MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis) { if (pVorbis == NULL) { return; @@ -65675,7 +75130,7 @@ MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis, const ma_allocation_callb /* We'll have to clear some memory if we're using push mode. */ if (pVorbis->usingPushMode) { - ma_free(pVorbis->push.pData, pAllocationCallbacks); + ma_free(pVorbis->push.pData, &pVorbis->allocationCallbacks); } } #else @@ -65685,7 +75140,7 @@ MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis, const ma_allocation_callb } #endif - ma_data_source_uninit(&pVorbis->ds); + ma_data_source_base_uninit(&pVorbis->ds); } MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) @@ -65885,7 +75340,7 @@ MA_API ma_result ma_stbvorbis_seek_to_pcm_frame(ma_stbvorbis* pVorbis, ma_uint64 MA_ZERO_OBJECT(&pVorbis->push); /* Seek to the start of the file. */ - result = pVorbis->onSeek(pVorbis->pReadSeekTellUserData, 0, ma_seek_origin_start); + result = pVorbis->onSeek(pVorbis->pReadSeekTellUserData, 0, MA_SEEK_SET); if (result != MA_SUCCESS) { return result; } @@ -66050,31 +75505,17 @@ MA_API ma_result ma_stbvorbis_get_length_in_pcm_frames(ma_stbvorbis* pVorbis, ma } -static ma_result ma_decoding_backend_init__stbvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static void ma_decoding_backend_info__stbvorbis(void* pUserData, ma_decoding_backend_info* pInfo) { - ma_result result; - ma_stbvorbis* pVorbis; - - (void)pUserData; /* For now not using pUserData, but once we start storing the vorbis decoder state within the ma_decoder structure this will be set to the decoder so we can avoid a malloc. */ - - /* For now we're just allocating the decoder backend on the heap. */ - pVorbis = (ma_stbvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks); - if (pVorbis == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_stbvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis); - if (result != MA_SUCCESS) { - ma_free(pVorbis, pAllocationCallbacks); - return result; - } - - *ppBackend = pVorbis; + (void)pUserData; - return MA_SUCCESS; + pInfo->pName = "Vorbis"; + pInfo->pLibraryName = "stb_vorbis"; + pInfo->pVendor = "stb_vorbis"; + pInfo->encodingFormat = ma_encoding_format_vorbis; } -static ma_result ma_decoding_backend_init_file__stbvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +static ma_result ma_decoding_backend_init__stbvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) { ma_result result; ma_stbvorbis* pVorbis; @@ -66087,7 +75528,7 @@ static ma_result ma_decoding_backend_init_file__stbvorbis(void* pUserData, const return MA_OUT_OF_MEMORY; } - result = ma_stbvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis); + result = ma_stbvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis); if (result != MA_SUCCESS) { ma_free(pVorbis, pAllocationCallbacks); return result; @@ -66128,88 +75569,579 @@ static void ma_decoding_backend_uninit__stbvorbis(void* pUserData, ma_data_sourc (void)pUserData; - ma_stbvorbis_uninit(pVorbis, pAllocationCallbacks); + ma_stbvorbis_uninit(pVorbis); ma_free(pVorbis, pAllocationCallbacks); } -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_stbvorbis = +static ma_decoding_backend_vtable ma_gDecodingBackendVTable_stbvorbis = { + ma_decoding_backend_info__stbvorbis, ma_decoding_backend_init__stbvorbis, - ma_decoding_backend_init_file__stbvorbis, - NULL, /* onInitFileW() */ ma_decoding_backend_init_memory__stbvorbis, ma_decoding_backend_uninit__stbvorbis }; +#endif /* STB_VORBIS_INCLUDE_STB_VORBIS_H */ + + + +static const char* ma_path_file_name(const char* path) +{ + const char* fileName; + + if (path == NULL) { + return NULL; + } + + fileName = path; + + /* We just loop through the path until we find the last slash. */ + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + /* At this point the file name is sitting on a slash, so just move forward. */ + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + +static const wchar_t* ma_path_file_name_w(const wchar_t* path) +{ + const wchar_t* fileName; + + if (path == NULL) { + return NULL; + } + + fileName = path; + + /* We just loop through the path until we find the last slash. */ + while (path[0] != '\0') { + if (path[0] == '/' || path[0] == '\\') { + fileName = path; + } + + path += 1; + } + + /* At this point the file name is sitting on a slash, so just move forward. */ + while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { + fileName += 1; + } + + return fileName; +} + + +static const char* ma_path_extension(const char* path) +{ + const char* extension; + const char* lastOccurance; + + if (path == NULL) { + path = ""; + } + + extension = ma_path_file_name(path); + lastOccurance = NULL; + + /* Just find the last '.' and return. */ + while (extension[0] != '\0') { + if (extension[0] == '.') { + extension += 1; + lastOccurance = extension; + } -static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) + extension += 1; + } + + return (lastOccurance != NULL) ? lastOccurance : extension; +} + +static const wchar_t* ma_path_extension_w(const wchar_t* path) { - return ma_decoder_init_from_vtable__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pConfig, pDecoder); + const wchar_t* extension; + const wchar_t* lastOccurance; + + if (path == NULL) { + path = L""; + } + + extension = ma_path_file_name_w(path); + lastOccurance = NULL; + + /* Just find the last '.' and return. */ + while (extension[0] != '\0') { + if (extension[0] == '.') { + extension += 1; + lastOccurance = extension; + } + + extension += 1; + } + + return (lastOccurance != NULL) ? lastOccurance : extension; } -static ma_result ma_decoder_init_vorbis_from_file__internal(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) + +static ma_bool32 ma_path_extension_equal(const char* path, const char* extension) { - return ma_decoder_init_from_file__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pFilePath, pConfig, pDecoder); + const char* ext1; + const char* ext2; + + if (path == NULL || extension == NULL) { + return MA_FALSE; + } + + ext1 = extension; + ext2 = ma_path_extension(path); + +#if defined(_MSC_VER) || defined(__DMC__) + return _stricmp(ext1, ext2) == 0; +#else + return strcasecmp(ext1, ext2) == 0; +#endif } -static ma_result ma_decoder_init_vorbis_from_file_w__internal(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* extension) { - return ma_decoder_init_from_file_w__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pFilePath, pConfig, pDecoder); + const wchar_t* ext1; + const wchar_t* ext2; + + if (path == NULL || extension == NULL) { + return MA_FALSE; + } + + ext1 = extension; + ext2 = ma_path_extension_w(path); + + #if (defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__)) && !defined(MA_XBOX_NXDK) + { + return _wcsicmp(ext1, ext2) == 0; + } + #elif !defined(MA_XBOX_NXDK) && !defined(MA_DOS) + { + /* + I'm not aware of a wide character version of strcasecmp(). I'm therefore converting the extensions to multibyte strings and comparing those. This + isn't the most efficient way to do it, but it should work OK. + */ + char ext1MB[4096]; + char ext2MB[4096]; + const wchar_t* pext1 = ext1; + const wchar_t* pext2 = ext2; + mbstate_t mbs1; + mbstate_t mbs2; + + MA_ZERO_OBJECT(&mbs1); + MA_ZERO_OBJECT(&mbs2); + + if (wcsrtombs(ext1MB, &pext1, sizeof(ext1MB), &mbs1) == (size_t)-1) { + return MA_FALSE; + } + if (wcsrtombs(ext2MB, &pext2, sizeof(ext2MB), &mbs2) == (size_t)-1) { + return MA_FALSE; + } + + return strcasecmp(ext1MB, ext2MB) == 0; + } + #else + { + /* Getting here means we don't have a way to do a case-sensitive comparison for wide strings. Fall back to a simple case-sensitive comparison. */ + /* TODO: Implement our own wchar_t-to-char conversion routine and then use the char* version for comparing. */ + return ma_wcscmp(ext1, ext2) == 0; + } + #endif } -static ma_result ma_decoder_init_vorbis_from_memory__internal(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) + +MA_API ma_encoding_format ma_encoding_format_from_path(const char* pFilePath) { - return ma_decoder_init_from_memory__internal(&g_ma_decoding_backend_vtable_stbvorbis, NULL, pData, dataSize, pConfig, pDecoder); + /* */ if (ma_path_extension_equal(pFilePath, "wav")) { + return ma_encoding_format_wav; + } else if (ma_path_extension_equal(pFilePath, "flac")) { + return ma_encoding_format_flac; + } else if (ma_path_extension_equal(pFilePath, "mp3")) { + return ma_encoding_format_mp3; + } else if (ma_path_extension_equal(pFilePath, "ogg")) { + return ma_encoding_format_vorbis; + } else if (ma_path_extension_equal(pFilePath, "opus")) { + return ma_encoding_format_opus; + } else { + return ma_encoding_format_unknown; + } } -#endif /* STB_VORBIS_INCLUDE_STB_VORBIS_H */ +MA_API ma_encoding_format ma_encoding_format_from_path_w(const wchar_t* pFilePath) +{ + /* */ if (ma_path_extension_equal_w(pFilePath, L"wav")) { + return ma_encoding_format_wav; + } else if (ma_path_extension_equal_w(pFilePath, L"flac")) { + return ma_encoding_format_flac; + } else if (ma_path_extension_equal_w(pFilePath, L"mp3")) { + return ma_encoding_format_mp3; + } else if (ma_path_extension_equal_w(pFilePath, L"ogg")) { + return ma_encoding_format_vorbis; + } else if (ma_path_extension_equal_w(pFilePath, L"opus")) { + return ma_encoding_format_opus; + } else { + return ma_encoding_format_unknown; + } +} + + +static ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount, ma_encoding_format encodingFormat) +{ + ma_decoding_backend_config config; + + MA_ZERO_OBJECT(&config); + config.preferredFormat = preferredFormat; + config.seekPointCount = seekPointCount; + config.encodingFormat = encodingFormat; + + return config; +} + + +MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint32 outputChannels, ma_uint32 outputSampleRate) +{ + ma_decoder_config config; + MA_ZERO_OBJECT(&config); + config.format = outputFormat; + config.channels = outputChannels; + config.sampleRate = outputSampleRate; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate doesn't matter here. */ + config.encodingFormat = ma_encoding_format_unknown; + + /* Note that we are intentionally leaving the channel map empty here which will cause the default channel map to be used. */ + + return config; +} + +MA_API ma_decoder_config ma_decoder_config_init_default(void) +{ + return ma_decoder_config_init(ma_format_unknown, 0, 0); +} + + + +static ma_decoding_backend_vtable* ma_DefaultDecodingBackendVTables[] = +{ +#if defined(MA_HAS_WAV) + &ma_gDecodingBackendVTable_WAV, +#else + NULL, +#endif +#if defined(MA_HAS_FLAC) + &ma_gDecodingBackendVTable_FLAC, +#else + NULL, +#endif +#if defined(MA_HAS_MP3) + &ma_gDecodingBackendVTable_MP3, +#else + NULL, +#endif +#if defined(MA_HAS_VORBIS) + &ma_gDecodingBackendVTable_stbvorbis +#else + NULL +#endif +}; + +static ma_decoder_config ma_decoder_config_init_copy(const ma_decoder_config* pConfig) +{ + ma_decoder_config config; + if (pConfig != NULL) { + config = *pConfig; + } else { + MA_ZERO_OBJECT(&config); + } + + /* Make sure we always have a valid VTable for initialization time. */ + if (config.ppBackendVTables == NULL) { + config.ppBackendVTables = ma_DefaultDecodingBackendVTables; + config.backendCount = ma_countof(ma_DefaultDecodingBackendVTables); + } + + return config; +} + +static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_decoder_config* pConfig) +{ + ma_result result; + ma_data_converter_config converterConfig; + ma_format internalFormat; + ma_uint32 internalChannels; + ma_uint32 internalSampleRate; + ma_channel internalChannelMap[MA_MAX_CHANNELS]; + + MA_ASSERT(pDecoder != NULL); + MA_ASSERT(pConfig != NULL); + + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal data format. */ + } + + result = ma_data_source_get_channel_map(pDecoder->pBackend, internalChannelMap, ma_countof(internalChannelMap)); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal channel map. */ + } + + + /* Make sure we're not asking for too many channels. */ + if (pConfig->channels > MA_MAX_CHANNELS) { + return MA_INVALID_ARGS; + } + + /* The internal channels should have already been validated at a higher level, but we'll do it again explicitly here for safety. */ + if (internalChannels > MA_MAX_CHANNELS) { + return MA_INVALID_ARGS; + } + + + /* Output format. */ + if (pConfig->format == ma_format_unknown) { + pDecoder->outputFormat = internalFormat; + } else { + pDecoder->outputFormat = pConfig->format; + } + + if (pConfig->channels == 0) { + pDecoder->outputChannels = internalChannels; + } else { + pDecoder->outputChannels = pConfig->channels; + } + + if (pConfig->sampleRate == 0) { + pDecoder->outputSampleRate = internalSampleRate; + } else { + pDecoder->outputSampleRate = pConfig->sampleRate; + } + + converterConfig = ma_data_converter_config_init( + internalFormat, pDecoder->outputFormat, + internalChannels, pDecoder->outputChannels, + internalSampleRate, pDecoder->outputSampleRate + ); + converterConfig.pChannelMapIn = internalChannelMap; + converterConfig.pChannelMapOut = pConfig->pChannelMap; + converterConfig.channelMixMode = pConfig->channelMixMode; + converterConfig.ditherMode = pConfig->ditherMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ + converterConfig.resampling = pConfig->resampling; + result = ma_data_converter_init(&converterConfig, &pDecoder->allocationCallbacks, &pDecoder->converter); + if (result != MA_SUCCESS) { + return result; + } + + /* + Now that we have the decoder we need to determine whether or not we need a heap-allocated cache. We'll + need this if the data converter is performing resampling. + */ + if (pDecoder->converter.sampleRateIn != pDecoder->converter.sampleRateOut) { + ma_uint64 inputCacheCapSizeInBytes; + + pDecoder->inputCacheCap = MA_DATA_CONVERTER_STACK_BUFFER_SIZE / ma_get_bytes_per_frame(internalFormat, internalChannels); + + /* Not strictly necessary, but keeping here for safety in case we change the default value of pDecoder->inputCacheCap. */ + inputCacheCapSizeInBytes = pDecoder->inputCacheCap * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (inputCacheCapSizeInBytes > MA_SIZE_MAX) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + pDecoder->pInputCache = ma_malloc((size_t)inputCacheCapSizeInBytes, &pDecoder->allocationCallbacks); /* Safe cast to size_t. */ + if (pDecoder->pInputCache == NULL) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + } + + return MA_SUCCESS; +} + + + +static ma_result ma_decoder_on_read(void* pUserData, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + MA_ASSERT(pDecoder != NULL); + + return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead, pBytesRead); +} + +static ma_result ma_decoder_on_seek(void* pUserData, ma_int64 offset, ma_seek_origin origin) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + MA_ASSERT(pDecoder != NULL); + + if (pDecoder->onSeek == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pDecoder->onSeek(pDecoder, offset, origin); +} + +static ma_result ma_decoder_on_tell(void* pUserData, ma_int64* pCursor) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + MA_ASSERT(pDecoder != NULL); + + if (pDecoder->onTell == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pDecoder->onTell(pDecoder, pCursor); +} + + +static ma_result ma_decoder_init_from_vtable__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoding_backend_config backendConfig; + ma_data_source* pBackend; + + MA_ASSERT(pVTable != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pVTable->onInit == NULL) { + return MA_NOT_IMPLEMENTED; + } + + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount, pConfig->encodingFormat); -static ma_result ma_decoder__init_allocation_callbacks(const ma_decoder_config* pConfig, ma_decoder* pDecoder) + result = pVTable->onInit(pVTableUserData, ma_decoder_on_read, ma_decoder_on_seek, ma_decoder_on_tell, pDecoder, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the backend from this vtable. */ + } + + /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ + pDecoder->pBackend = pBackend; + pDecoder->pBackendVTable = pVTable; + pDecoder->pBackendUserData = pVTableUserData; + + return MA_SUCCESS; +} + +static ma_result ma_decoder_init_from_memory__internal(const ma_decoding_backend_vtable* pVTable, void* pVTableUserData, const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoding_backend_config backendConfig; + ma_data_source* pBackend; + + MA_ASSERT(pVTable != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + if (pVTable->onInitMemory == NULL) { + return MA_NOT_IMPLEMENTED; + } + + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount, pConfig->encodingFormat); + + result = pVTable->onInitMemory(pVTableUserData, pData, dataSize, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the backend from this vtable. */ + } + + /* Getting here means we were able to initialize the backend so we can now initialize the decoder. */ + pDecoder->pBackend = pBackend; + pDecoder->pBackendVTable = pVTable; + pDecoder->pBackendUserData = pVTableUserData; + + return MA_SUCCESS; +} + + + +static void ma_decoder__init_allocation_callbacks(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { MA_ASSERT(pDecoder != NULL); if (pConfig != NULL) { - return ma_allocation_callbacks_init_copy(&pDecoder->allocationCallbacks, &pConfig->allocationCallbacks); + pDecoder->allocationCallbacks = ma_allocation_callbacks_init_copy(&pConfig->allocationCallbacks); } else { pDecoder->allocationCallbacks = ma_allocation_callbacks_init_default(); - return MA_SUCCESS; } } + +static size_t ma_decoder__data_source_on_sizeof(void) +{ + return sizeof(ma_decoder); +} + +static void ma_decoder__data_source_on_uninit(ma_data_source* pDataSource) +{ + ma_decoder_uninit((ma_decoder*)pDataSource); +} + +static ma_result ma_decoder__data_source_on_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) +{ + return ma_decoder_init_copy((ma_decoder*)pDataSource, (ma_decoder*)pNewDataSource); +} + static ma_result ma_decoder__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { return ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) -{ - return ma_decoder_seek_to_pcm_frame((ma_decoder*)pDataSource, frameIndex); -} +static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_decoder_seek_to_pcm_frame((ma_decoder*)pDataSource, frameIndex); +} + +static ma_result ma_decoder__data_source_on_prop(ma_data_source* pDataSource, int prop, void* pData) +{ + ma_decoder* pDecoder = (ma_decoder*)pDataSource; + + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; + + return ma_decoder_get_data_format(pDecoder, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0); + } + + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + return ma_decoder_get_data_format(pDecoder, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS); + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_decoder_get_cursor_in_pcm_frames(pDecoder, (ma_uint64*)pData); + } -static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) -{ - return ma_decoder_get_data_format((ma_decoder*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); -} + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_decoder_get_length_in_pcm_frames(pDecoder, (ma_uint64*)pData); + } -static ma_result ma_decoder__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) -{ - return ma_decoder_get_cursor_in_pcm_frames((ma_decoder*)pDataSource, pCursor); -} + default: break; + } -static ma_result ma_decoder__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) -{ - return ma_decoder_get_length_in_pcm_frames((ma_decoder*)pDataSource, pLength); + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_decoder_data_source_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_Decoder = { + ma_decoder__data_source_on_sizeof, + ma_decoder__data_source_on_uninit, + ma_decoder__data_source_on_copy, ma_decoder__data_source_on_read, ma_decoder__data_source_on_seek, - ma_decoder__data_source_on_get_data_format, - ma_decoder__data_source_on_get_cursor, - ma_decoder__data_source_on_get_length, - NULL, /* onSetLooping */ - 0 + ma_decoder__data_source_on_prop }; static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, ma_decoder_tell_proc onTell, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -66226,9 +76158,9 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see MA_ZERO_OBJECT(pDecoder); dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_decoder_data_source_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_Decoder; - result = ma_data_source_init(&dataSourceConfig, &pDecoder->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pDecoder->ds); if (result != MA_SUCCESS) { return result; } @@ -66237,12 +76169,9 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see pDecoder->onSeek = onSeek; pDecoder->onTell = onTell; pDecoder->pUserData = pUserData; - - result = ma_decoder__init_allocation_callbacks(pConfig, pDecoder); - if (result != MA_SUCCESS) { - ma_data_source_uninit(&pDecoder->ds); - return result; - } + ma_decoder__init_allocation_callbacks(pConfig, pDecoder); + pDecoder->resamplingConfig = pConfig->resampling; + pDecoder->seekPointCount = pConfig->seekPointCount; return MA_SUCCESS; } @@ -66262,123 +76191,122 @@ static ma_result ma_decoder__postinit(const ma_decoder_config* pConfig, ma_decod return result; } +static ma_result ma_decoder__postinit_or_uninit(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result; + + result = ma_decoder__postinit(pConfig, pDecoder); + if (result != MA_SUCCESS) { + /* + The backend was initialized successfully, but for some reason post-initialization failed. This is most likely + due to an out of memory error. We're going to abort with an error here and not try to recover. + */ + if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { + pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, pDecoder->pBackend, &pDecoder->allocationCallbacks); + } + + return result; + } + + return MA_SUCCESS; +} + -static ma_result ma_decoder_init__internal(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +static ma_bool32 ma_can_decoding_backend_possibly_handle_encoding_format(const ma_decoding_backend_vtable* pBackendVTable, void* pBackendUserData, ma_encoding_format encodingFormat) { - ma_result result = MA_NO_BACKEND; + ma_encoding_format backendEncodingFormat = ma_encoding_format_unknown; - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pDecoder != NULL); + if (pBackendVTable == NULL) { + return MA_FALSE; + } - /* Silence some warnings in the case that we don't have any decoder backends enabled. */ - (void)onRead; - (void)onSeek; - (void)pUserData; + if (encodingFormat == ma_encoding_format_unknown) { + return MA_TRUE; /* The backend can handle anything. */ + } + if (pBackendVTable != NULL && pBackendVTable->onInfo != NULL) { + ma_decoding_backend_info backendInfo; + pBackendVTable->onInfo(pBackendUserData, &backendInfo); - /* If we've specified a specific encoding type, try that first. */ - if (pConfig->encodingFormat != ma_encoding_format_unknown) { - #ifdef MA_HAS_WAV - if (pConfig->encodingFormat == ma_encoding_format_wav) { - result = ma_decoder_init_wav__internal(pConfig, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (pConfig->encodingFormat == ma_encoding_format_flac) { - result = ma_decoder_init_flac__internal(pConfig, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (pConfig->encodingFormat == ma_encoding_format_mp3) { - result = ma_decoder_init_mp3__internal(pConfig, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (pConfig->encodingFormat == ma_encoding_format_vorbis) { - result = ma_decoder_init_vorbis__internal(pConfig, pDecoder); - } - #endif + backendEncodingFormat = backendInfo.encodingFormat; + } - /* If we weren't able to initialize the decoder, seek back to the start to give the next attempts a clean start. */ - if (result != MA_SUCCESS) { - onSeek(pDecoder, 0, ma_seek_origin_start); - } + if (backendEncodingFormat == ma_encoding_format_unknown) { + return MA_TRUE; /* The backend does not specify an encoding format which means we must assume it can handle anything. */ } - if (result != MA_SUCCESS) { - /* Getting here means we couldn't load a specific decoding backend based on the encoding format. */ + return backendEncodingFormat == encodingFormat; +} - /* - We use trial and error to open a decoder. We prioritize custom decoders so that if they - implement the same encoding format they take priority over the built-in decoders. - */ - result = ma_decoder_init_custom__internal(pConfig, pDecoder); - if (result != MA_SUCCESS) { - onSeek(pDecoder, 0, ma_seek_origin_start); - } +static void* ma_decoder_config_get_backend_user_data(const ma_decoder_config* pConfig, ma_uint32 iBackend) +{ + if (pConfig->ppBackendUserData == NULL) { + return NULL; + } - /* - If we get to this point and we still haven't found a decoder, and the caller has requested a - specific encoding format, there's no hope for it. Abort. - */ - if (pConfig->encodingFormat != ma_encoding_format_unknown) { - return MA_NO_BACKEND; - } + return pConfig->ppBackendUserData[iBackend]; +} - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS) { - result = ma_decoder_init_wav__internal(pConfig, pDecoder); - if (result != MA_SUCCESS) { - onSeek(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS) { - result = ma_decoder_init_flac__internal(pConfig, pDecoder); - if (result != MA_SUCCESS) { - onSeek(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS) { - result = ma_decoder_init_mp3__internal(pConfig, pDecoder); - if (result != MA_SUCCESS) { - onSeek(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - #ifdef MA_HAS_VORBIS - if (result != MA_SUCCESS) { - result = ma_decoder_init_vorbis__internal(pConfig, pDecoder); - if (result != MA_SUCCESS) { - onSeek(pDecoder, 0, ma_seek_origin_start); + +static ma_result ma_decoder_init__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = MA_NO_BACKEND; + ma_result firstError = MA_SUCCESS; + ma_uint32 iBackend; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + /* The backend vtable list should have been filled out by a higher level function. */ + MA_ASSERT(pConfig->ppBackendVTables != NULL); + + for (iBackend = 0; iBackend < pConfig->backendCount; iBackend += 1) { + if (ma_can_decoding_backend_possibly_handle_encoding_format(pConfig->ppBackendVTables[iBackend], ma_decoder_config_get_backend_user_data(pConfig, iBackend), pConfig->encodingFormat)) { + /* Getting here means the backend may support the encoding format. */ + result = ma_decoder_init_from_vtable__internal(pConfig->ppBackendVTables[iBackend], ma_decoder_config_get_backend_user_data(pConfig, iBackend), pConfig, pDecoder); + if (result == MA_SUCCESS) { + return ma_decoder__postinit_or_uninit(pConfig, pDecoder); + } else { + /* + Initialization failed. Move on to the next one, but seek back to the start first so the next vtable starts from + the first byte of the file. We want to propagate the first error code back to the caller in the result of an + initialization failure, so keep track of it here if we haven't already got an error. + */ + if (firstError == MA_SUCCESS) { + firstError = result; + } + + result = ma_decoder_on_seek(pDecoder, 0, MA_SEEK_SET); + if (result != MA_SUCCESS) { + return result; /* Failed to seek back to the start. */ + } } + } else { + /* The backend does not support the specified encoding format. Skip. */ } - #endif } - if (result != MA_SUCCESS) { - return result; + /* Getting here means we couldn't find a backend or there was an error initializing. */ + if (firstError != MA_SUCCESS) { + return firstError; } - return ma_decoder__postinit(pConfig, pDecoder); + return MA_NO_BACKEND; } -MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, ma_decoder_tell_proc onTell, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { ma_decoder_config config; ma_result result; config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit(onRead, onSeek, NULL, pUserData, &config, pDecoder); + result = ma_decoder__preinit(onRead, onSeek, onTell, pUserData, &config, pDecoder); if (result != MA_SUCCESS) { return result; } - return ma_decoder_init__internal(onRead, onSeek, pUserData, &config, pDecoder); + return ma_decoder_init__internal(&config, pDecoder); } @@ -66419,7 +76347,7 @@ static ma_result ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteO return MA_BAD_SEEK; } - if (origin == ma_seek_origin_current) { + if (origin == MA_SEEK_CUR) { if (byteOffset > 0) { if (pDecoder->data.memory.currentReadPos + byteOffset > pDecoder->data.memory.dataSize) { byteOffset = (ma_int64)(pDecoder->data.memory.dataSize - pDecoder->data.memory.currentReadPos); /* Trying to seek too far forward. */ @@ -66434,7 +76362,7 @@ static ma_result ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteO pDecoder->data.memory.currentReadPos -= (size_t)-byteOffset; } } else { - if (origin == ma_seek_origin_end) { + if (origin == MA_SEEK_END) { if (byteOffset < 0) { byteOffset = -byteOffset; } @@ -66489,6 +76417,7 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons { ma_result result; ma_decoder_config config; + ma_uint32 iBackend; config = ma_decoder_config_init_copy(pConfig); @@ -66501,285 +76430,40 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons return MA_INVALID_ARGS; } - /* If the backend has support for loading from a file path we'll want to use that. If that all fails we'll fall back to the VFS path. */ - result = MA_NO_BACKEND; - - if (config.encodingFormat != ma_encoding_format_unknown) { - #ifdef MA_HAS_WAV - if (config.encodingFormat == ma_encoding_format_wav) { - result = ma_decoder_init_wav_from_memory__internal(pData, dataSize, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (config.encodingFormat == ma_encoding_format_flac) { - result = ma_decoder_init_flac_from_memory__internal(pData, dataSize, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (config.encodingFormat == ma_encoding_format_mp3) { - result = ma_decoder_init_mp3_from_memory__internal(pData, dataSize, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (config.encodingFormat == ma_encoding_format_vorbis) { - result = ma_decoder_init_vorbis_from_memory__internal(pData, dataSize, &config, pDecoder); - } - #endif - } - - if (result != MA_SUCCESS) { - /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ - - /* - We use trial and error to open a decoder. We prioritize custom decoders so that if they - implement the same encoding format they take priority over the built-in decoders. - */ - result = ma_decoder_init_custom_from_memory__internal(pData, dataSize, &config, pDecoder); - - /* - If we get to this point and we still haven't found a decoder, and the caller has requested a - specific encoding format, there's no hope for it. Abort. - */ - if (result != MA_SUCCESS && config.encodingFormat != ma_encoding_format_unknown) { - return MA_NO_BACKEND; - } + for (iBackend = 0; iBackend < config.backendCount; iBackend += 1) { + if (ma_can_decoding_backend_possibly_handle_encoding_format(config.ppBackendVTables[iBackend], ma_decoder_config_get_backend_user_data(&config, iBackend), config.encodingFormat)) { + /* Getting here means the backend may support the encoding format. */ + result = ma_decoder_init_from_memory__internal(config.ppBackendVTables[iBackend], ma_decoder_config_get_backend_user_data(&config, iBackend), pData, dataSize, &config, pDecoder); + if (result == MA_SUCCESS) { + return ma_decoder__postinit_or_uninit(&config, pDecoder); + } else { + /* + Initialization failed, but it could just be because the backend does not implement onInitMemory. In this case we need to + try again using callbacks. + */ + if (result == MA_NOT_IMPLEMENTED) { + /* Probably no implementation of onInitMemory. Use miniaudio's abstraction instead. */ + result = ma_decoder__preinit_memory_wrapper(pData, dataSize, &config, pDecoder); + if (result != MA_SUCCESS) { + return result; + } - /* Use trial and error for stock decoders. */ - if (result != MA_SUCCESS) { - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS) { - result = ma_decoder_init_wav_from_memory__internal(pData, dataSize, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS) { - result = ma_decoder_init_flac_from_memory__internal(pData, dataSize, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS) { - result = ma_decoder_init_mp3_from_memory__internal(pData, dataSize, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (result != MA_SUCCESS) { - result = ma_decoder_init_vorbis_from_memory__internal(pData, dataSize, &config, pDecoder); + result = ma_decoder_init__internal(&config, pDecoder); + if (result == MA_SUCCESS) { + return result; /* Don't return ma_decoder__postinit_or_uninit() here because ma_decoder_init__internal() will already have done it. */ + } + } else { + /* Initialization failed. Probably an unsupported format. Skip. */ + } } - #endif - } - } - - /* - If at this point we still haven't successfully initialized the decoder it most likely means - the backend doesn't have an implementation for loading from a file path. We'll try using - miniaudio's built-in file IO for loading file. - */ - if (result == MA_SUCCESS) { - /* Initialization was successful. Finish up. */ - result = ma_decoder__postinit(&config, pDecoder); - if (result != MA_SUCCESS) { - return result; - } - } else { - /* Probably no implementation for loading from a block of memory. Use miniaudio's abstraction instead. */ - result = ma_decoder__preinit_memory_wrapper(pData, dataSize, &config, pDecoder); - if (result != MA_SUCCESS) { - return result; - } - - result = ma_decoder_init__internal(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, &config, pDecoder); - if (result != MA_SUCCESS) { - return result; - } - } - - return MA_SUCCESS; -} - - -#if defined(MA_HAS_WAV) || \ - defined(MA_HAS_MP3) || \ - defined(MA_HAS_FLAC) || \ - defined(MA_HAS_VORBIS) -#define MA_HAS_PATH_API -#endif - -#if defined(MA_HAS_PATH_API) -static const char* ma_path_file_name(const char* path) -{ - const char* fileName; - - if (path == NULL) { - return NULL; - } - - fileName = path; - - /* We just loop through the path until we find the last slash. */ - while (path[0] != '\0') { - if (path[0] == '/' || path[0] == '\\') { - fileName = path; - } - - path += 1; - } - - /* At this point the file name is sitting on a slash, so just move forward. */ - while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { - fileName += 1; - } - - return fileName; -} - -static const wchar_t* ma_path_file_name_w(const wchar_t* path) -{ - const wchar_t* fileName; - - if (path == NULL) { - return NULL; - } - - fileName = path; - - /* We just loop through the path until we find the last slash. */ - while (path[0] != '\0') { - if (path[0] == '/' || path[0] == '\\') { - fileName = path; - } - - path += 1; - } - - /* At this point the file name is sitting on a slash, so just move forward. */ - while (fileName[0] != '\0' && (fileName[0] == '/' || fileName[0] == '\\')) { - fileName += 1; - } - - return fileName; -} - - -static const char* ma_path_extension(const char* path) -{ - const char* extension; - const char* lastOccurance; - - if (path == NULL) { - path = ""; - } - - extension = ma_path_file_name(path); - lastOccurance = NULL; - - /* Just find the last '.' and return. */ - while (extension[0] != '\0') { - if (extension[0] == '.') { - extension += 1; - lastOccurance = extension; - } - - extension += 1; - } - - return (lastOccurance != NULL) ? lastOccurance : extension; -} - -static const wchar_t* ma_path_extension_w(const wchar_t* path) -{ - const wchar_t* extension; - const wchar_t* lastOccurance; - - if (path == NULL) { - path = L""; - } - - extension = ma_path_file_name_w(path); - lastOccurance = NULL; - - /* Just find the last '.' and return. */ - while (extension[0] != '\0') { - if (extension[0] == '.') { - extension += 1; - lastOccurance = extension; + } else { + /* The backend does not support the specified encoding format. Skip. */ } - - extension += 1; - } - - return (lastOccurance != NULL) ? lastOccurance : extension; -} - - -static ma_bool32 ma_path_extension_equal(const char* path, const char* extension) -{ - const char* ext1; - const char* ext2; - - if (path == NULL || extension == NULL) { - return MA_FALSE; - } - - ext1 = extension; - ext2 = ma_path_extension(path); - -#if defined(_MSC_VER) || defined(__DMC__) - return _stricmp(ext1, ext2) == 0; -#else - return strcasecmp(ext1, ext2) == 0; -#endif -} - -static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* extension) -{ - const wchar_t* ext1; - const wchar_t* ext2; - - if (path == NULL || extension == NULL) { - return MA_FALSE; - } - - ext1 = extension; - ext2 = ma_path_extension_w(path); - - #if (defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__)) && !defined(MA_XBOX_NXDK) - { - return _wcsicmp(ext1, ext2) == 0; } - #elif !defined(MA_XBOX_NXDK) && !defined(MA_DOS) - { - /* - I'm not aware of a wide character version of strcasecmp(). I'm therefore converting the extensions to multibyte strings and comparing those. This - isn't the most efficient way to do it, but it should work OK. - */ - char ext1MB[4096]; - char ext2MB[4096]; - const wchar_t* pext1 = ext1; - const wchar_t* pext2 = ext2; - mbstate_t mbs1; - mbstate_t mbs2; - - MA_ZERO_OBJECT(&mbs1); - MA_ZERO_OBJECT(&mbs2); - - if (wcsrtombs(ext1MB, &pext1, sizeof(ext1MB), &mbs1) == (size_t)-1) { - return MA_FALSE; - } - if (wcsrtombs(ext2MB, &pext2, sizeof(ext2MB), &mbs2) == (size_t)-1) { - return MA_FALSE; - } - return strcasecmp(ext1MB, ext2MB) == 0; - } - #else - { - /* Getting here means we don't have a way to do a case-sensitive comparison for wide strings. Fall back to a simple case-sensitive comparison. */ - /* TODO: Implement our own wchar_t-to-char conversion routine and then use the char* version for comparing. */ - return ma_wcscmp(ext1, ext2) == 0; - } - #endif + /* Getting here means we couldn't find a backend. */ + return MA_NO_BACKEND; } -#endif /* MA_HAS_PATH_API */ @@ -66836,102 +76520,33 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config config; config = ma_decoder_config_init_copy(pConfig); + result = ma_decoder__preinit_vfs(pVFS, pFilePath, &config, pDecoder); if (result != MA_SUCCESS) { return result; } - result = MA_NO_BACKEND; - - if (config.encodingFormat != ma_encoding_format_unknown) { - #ifdef MA_HAS_WAV - if (config.encodingFormat == ma_encoding_format_wav) { - result = ma_decoder_init_wav__internal(&config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (config.encodingFormat == ma_encoding_format_flac) { - result = ma_decoder_init_flac__internal(&config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (config.encodingFormat == ma_encoding_format_mp3) { - result = ma_decoder_init_mp3__internal(&config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (config.encodingFormat == ma_encoding_format_vorbis) { - result = ma_decoder_init_vorbis__internal(&config, pDecoder); - } - #endif - - /* Make sure we seek back to the start if we didn't initialize a decoder successfully so the next attempts have a fresh start. */ - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - } - - if (result != MA_SUCCESS) { - /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ - - /* - We use trial and error to open a decoder. We prioritize custom decoders so that if they - implement the same encoding format they take priority over the built-in decoders. - */ - result = ma_decoder_init_custom__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - - /* - If we get to this point and we still haven't found a decoder, and the caller has requested a - specific encoding format, there's no hope for it. Abort. - */ - if (config.encodingFormat != ma_encoding_format_unknown) { - return MA_NO_BACKEND; - } - - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "wav")) { - result = ma_decoder_init_wav__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "flac")) { - result = ma_decoder_init_flac__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "mp3")) { - result = ma_decoder_init_mp3__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - } - - /* If we still haven't got a result just use trial and error. Otherwise we can finish up. */ - if (result != MA_SUCCESS) { - result = ma_decoder_init__internal(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, NULL, &config, pDecoder); - } else { - result = ma_decoder__postinit(&config, pDecoder); + result = ma_decoder_init__internal(&config, pDecoder); + if (result == MA_SUCCESS) { + result = ma_decoder__postinit_or_uninit(&config, pDecoder); } + /* If we failed to initialize a decoder make sure our file handle is closed. */ if (result != MA_SUCCESS) { - if (pDecoder->data.vfs.file != NULL) { /* <-- Will be reset to NULL if ma_decoder_uninit() is called in one of the steps above which allows us to avoid a double close of the file. */ + if (pDecoder->data.vfs.file != NULL) { ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; } + /* We need to track the file path so we can duplicate the decoder if necessary. */ + pDecoder->data.vfs.pFilePath = ma_copy_string(pFilePath, &pDecoder->allocationCallbacks); + if (pDecoder->data.vfs.pFilePath == NULL) { + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); + return MA_OUT_OF_MEMORY; + } + return MA_SUCCESS; } @@ -66967,97 +76582,31 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c ma_decoder_config config; config = ma_decoder_config_init_copy(pConfig); + result = ma_decoder__preinit_vfs_w(pVFS, pFilePath, &config, pDecoder); if (result != MA_SUCCESS) { return result; } - result = MA_NO_BACKEND; - - if (config.encodingFormat != ma_encoding_format_unknown) { - #ifdef MA_HAS_WAV - if (config.encodingFormat == ma_encoding_format_wav) { - result = ma_decoder_init_wav__internal(&config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (config.encodingFormat == ma_encoding_format_flac) { - result = ma_decoder_init_flac__internal(&config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (config.encodingFormat == ma_encoding_format_mp3) { - result = ma_decoder_init_mp3__internal(&config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (config.encodingFormat == ma_encoding_format_vorbis) { - result = ma_decoder_init_vorbis__internal(&config, pDecoder); - } - #endif - - /* Make sure we seek back to the start if we didn't initialize a decoder successfully so the next attempts have a fresh start. */ - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } + result = ma_decoder_init__internal(&config, pDecoder); + if (result == MA_SUCCESS) { + result = ma_decoder__postinit_or_uninit(&config, pDecoder); } + /* If we failed to initialize a decoder make sure our file handle is closed. */ if (result != MA_SUCCESS) { - /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ - - /* - We use trial and error to open a decoder. We prioritize custom decoders so that if they - implement the same encoding format they take priority over the built-in decoders. - */ - result = ma_decoder_init_custom__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - - /* - If we get to this point and we still haven't found a decoder, and the caller has requested a - specific encoding format, there's no hope for it. Abort. - */ - if (config.encodingFormat != ma_encoding_format_unknown) { - return MA_NO_BACKEND; - } - - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"wav")) { - result = ma_decoder_init_wav__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"flac")) { - result = ma_decoder_init_flac__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"mp3")) { - result = ma_decoder_init_mp3__internal(&config, pDecoder); - if (result != MA_SUCCESS) { - ma_decoder__on_seek_vfs(pDecoder, 0, ma_seek_origin_start); - } + if (pDecoder->data.vfs.file != NULL) { + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } - #endif - } - /* If we still haven't got a result just use trial and error. Otherwise we can finish up. */ - if (result != MA_SUCCESS) { - result = ma_decoder_init__internal(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, NULL, &config, pDecoder); - } else { - result = ma_decoder__postinit(&config, pDecoder); + return result; } - if (result != MA_SUCCESS) { + /* We need to track the file path so we can duplicate the decoder if necessary. */ + pDecoder->data.vfs.pFilePathW = ma_copy_string_w(pFilePath, &pDecoder->allocationCallbacks); + if (pDecoder->data.vfs.pFilePathW == NULL) { ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); - return result; + return MA_OUT_OF_MEMORY; } return MA_SUCCESS; @@ -67084,126 +76633,31 @@ MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_co { ma_result result; ma_decoder_config config; + ma_uint32 iBackend; config = ma_decoder_config_init_copy(pConfig); + result = ma_decoder__preinit_file(pFilePath, &config, pDecoder); if (result != MA_SUCCESS) { return result; } - /* If the backend has support for loading from a file path we'll want to use that. If that all fails we'll fall back to the VFS path. */ - result = MA_NO_BACKEND; - - if (config.encodingFormat != ma_encoding_format_unknown) { - #ifdef MA_HAS_WAV - if (config.encodingFormat == ma_encoding_format_wav) { - result = ma_decoder_init_wav_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (config.encodingFormat == ma_encoding_format_flac) { - result = ma_decoder_init_flac_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (config.encodingFormat == ma_encoding_format_mp3) { - result = ma_decoder_init_mp3_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (config.encodingFormat == ma_encoding_format_vorbis) { - result = ma_decoder_init_vorbis_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - } - - if (result != MA_SUCCESS) { - /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ - - /* - We use trial and error to open a decoder. We prioritize custom decoders so that if they - implement the same encoding format they take priority over the built-in decoders. - */ - result = ma_decoder_init_custom_from_file__internal(pFilePath, &config, pDecoder); - - /* - If we get to this point and we still haven't found a decoder, and the caller has requested a - specific encoding format, there's no hope for it. Abort. - */ - if (result != MA_SUCCESS && config.encodingFormat != ma_encoding_format_unknown) { - return MA_NO_BACKEND; - } - - /* First try loading based on the file extension so we don't waste time opening and closing files. */ - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "wav")) { - result = ma_decoder_init_wav_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "flac")) { - result = ma_decoder_init_flac_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "mp3")) { - result = ma_decoder_init_mp3_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (result != MA_SUCCESS && ma_path_extension_equal(pFilePath, "ogg")) { - result = ma_decoder_init_vorbis_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - - /* - If we still haven't got a result just use trial and error. Custom decoders have already been attempted, so here we - need only iterate over our stock decoders. - */ - if (result != MA_SUCCESS) { - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS) { - result = ma_decoder_init_wav_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS) { - result = ma_decoder_init_flac_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS) { - result = ma_decoder_init_mp3_from_file__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (result != MA_SUCCESS) { - result = ma_decoder_init_vorbis_from_file__internal(pFilePath, &config, pDecoder); + for (iBackend = 0; iBackend < config.backendCount; iBackend += 1) { + if (ma_can_decoding_backend_possibly_handle_encoding_format(config.ppBackendVTables[iBackend], ma_decoder_config_get_backend_user_data(&config, iBackend), config.encodingFormat)) { + /* Getting here means the backend may support the encoding format. */ + result = ma_decoder_init_vfs(NULL, pFilePath, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } else { + /* Initialization failed. Probably an unsupported format. Skip. */ } - #endif - } - } - - /* - If at this point we still haven't successfully initialized the decoder it most likely means - the backend doesn't have an implementation for loading from a file path. We'll try using - miniaudio's built-in file IO for loading file. - */ - if (result == MA_SUCCESS) { - /* Initialization was successful. Finish up. */ - result = ma_decoder__postinit(&config, pDecoder); - if (result != MA_SUCCESS) { - return result; - } - } else { - /* Probably no implementation for loading from a file path. Use miniaudio's file IO instead. */ - result = ma_decoder_init_vfs(NULL, pFilePath, pConfig, pDecoder); - if (result != MA_SUCCESS) { - return result; + } else { + /* The backend does not support the specified encoding format. Skip. */ } } - return MA_SUCCESS; + /* Getting here means we couldn't find a backend. */ + return MA_NO_BACKEND; } static ma_result ma_decoder__preinit_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -67226,126 +76680,67 @@ MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decod { ma_result result; ma_decoder_config config; + ma_uint32 iBackend; config = ma_decoder_config_init_copy(pConfig); + result = ma_decoder__preinit_file_w(pFilePath, &config, pDecoder); if (result != MA_SUCCESS) { return result; } - /* If the backend has support for loading from a file path we'll want to use that. If that all fails we'll fall back to the VFS path. */ - result = MA_NO_BACKEND; - - if (config.encodingFormat != ma_encoding_format_unknown) { - #ifdef MA_HAS_WAV - if (config.encodingFormat == ma_encoding_format_wav) { - result = ma_decoder_init_wav_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (config.encodingFormat == ma_encoding_format_flac) { - result = ma_decoder_init_flac_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (config.encodingFormat == ma_encoding_format_mp3) { - result = ma_decoder_init_mp3_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (config.encodingFormat == ma_encoding_format_vorbis) { - result = ma_decoder_init_vorbis_from_file_w__internal(pFilePath, &config, pDecoder); + for (iBackend = 0; iBackend < config.backendCount; iBackend += 1) { + if (ma_can_decoding_backend_possibly_handle_encoding_format(config.ppBackendVTables[iBackend], ma_decoder_config_get_backend_user_data(&config, iBackend), config.encodingFormat)) { + /* Getting here means the backend may support the encoding format. */ + result = ma_decoder_init_vfs_w(NULL, pFilePath, pConfig, pDecoder); + if (result == MA_SUCCESS) { + return MA_SUCCESS; + } else { + /* Initialization failed. Probably an unsupported format. Skip. */ + } + } else { + /* The backend does not support the specified encoding format. Skip. */ } - #endif } - if (result != MA_SUCCESS) { - /* Getting here means we weren't able to initialize a decoder of a specific encoding format. */ - - /* - We use trial and error to open a decoder. We prioritize custom decoders so that if they - implement the same encoding format they take priority over the built-in decoders. - */ - result = ma_decoder_init_custom_from_file_w__internal(pFilePath, &config, pDecoder); - - /* - If we get to this point and we still haven't found a decoder, and the caller has requested a - specific encoding format, there's no hope for it. Abort. - */ - if (result != MA_SUCCESS && config.encodingFormat != ma_encoding_format_unknown) { - return MA_NO_BACKEND; - } + /* Getting here means we couldn't find a backend. */ + return MA_NO_BACKEND; +} - /* First try loading based on the file extension so we don't waste time opening and closing files. */ - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"wav")) { - result = ma_decoder_init_wav_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"flac")) { - result = ma_decoder_init_flac_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"mp3")) { - result = ma_decoder_init_mp3_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (result != MA_SUCCESS && ma_path_extension_equal_w(pFilePath, L"ogg")) { - result = ma_decoder_init_vorbis_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif +MA_API ma_result ma_decoder_init_copy(ma_decoder* pDecoder, ma_decoder* pNewDecoder) +{ + ma_decoder_config config; - /* - If we still haven't got a result just use trial and error. Custom decoders have already been attempted, so here we - need only iterate over our stock decoders. - */ - if (result != MA_SUCCESS) { - #ifdef MA_HAS_WAV - if (result != MA_SUCCESS) { - result = ma_decoder_init_wav_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_FLAC - if (result != MA_SUCCESS) { - result = ma_decoder_init_flac_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_MP3 - if (result != MA_SUCCESS) { - result = ma_decoder_init_mp3_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - #ifdef MA_HAS_VORBIS - if (result != MA_SUCCESS) { - result = ma_decoder_init_vorbis_from_file_w__internal(pFilePath, &config, pDecoder); - } - #endif - } + if (pDecoder == NULL) { + return MA_INVALID_ARGS; } - /* - If at this point we still haven't successfully initialized the decoder it most likely means - the backend doesn't have an implementation for loading from a file path. We'll try using - miniaudio's built-in file IO for loading file. - */ - if (result == MA_SUCCESS) { - /* Initialization was successful. Finish up. */ - result = ma_decoder__postinit(&config, pDecoder); - if (result != MA_SUCCESS) { - return result; + config = ma_decoder_config_init(pDecoder->outputFormat, pDecoder->outputChannels, pDecoder->outputSampleRate); + config.pChannelMap = pDecoder->converter.channelConverter.pChannelMapOut; + config.channelMixMode = pDecoder->converter.channelConverter.mixingMode; + config.ditherMode = pDecoder->converter.ditherMode; + config.resampling = pDecoder->resamplingConfig; + config.allocationCallbacks = pDecoder->allocationCallbacks; + config.seekPointCount = pDecoder->seekPointCount; + config.ppBackendVTables = (ma_decoding_backend_vtable**)&pDecoder->pBackendVTable; /* Safe const-cast. */ + config.ppBackendUserData = &pDecoder->pBackendUserData; + config.backendCount = 1; + + /* Can only be duplicated if created from a file or a block of memory. Cannot reliably copy with arbitrary callbacks. */ + if (pDecoder->onRead == ma_decoder__on_read_vfs) { + if (pDecoder->data.vfs.pFilePath != NULL) { + return ma_decoder_init_vfs(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.pFilePath, &config, pNewDecoder); + } else if (pDecoder->data.vfs.pFilePathW != NULL) { + return ma_decoder_init_vfs_w(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.pFilePathW, &config, pNewDecoder); + } else { + MA_ASSERT(MA_FALSE); + return MA_NOT_IMPLEMENTED; /* Don't have a file path for some reason. Should never hit this. */ } + } else if (pDecoder->onRead == ma_decoder__on_read_memory) { + return ma_decoder_init_memory(pDecoder->data.memory.pData, pDecoder->data.memory.dataSize, &config, pNewDecoder); } else { - /* Probably no implementation for loading from a file path. Use miniaudio's file IO instead. */ - result = ma_decoder_init_vfs_w(NULL, pFilePath, pConfig, pDecoder); - if (result != MA_SUCCESS) { - return result; - } + return MA_NOT_IMPLEMENTED; } - - return MA_SUCCESS; } MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) @@ -67361,12 +76756,14 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) } if (pDecoder->onRead == ma_decoder__on_read_vfs) { + ma_free(pDecoder->data.vfs.pFilePath, &pDecoder->allocationCallbacks); + ma_free(pDecoder->data.vfs.pFilePathW, &pDecoder->allocationCallbacks); ma_vfs_or_default_close(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file); pDecoder->data.vfs.file = NULL; } ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); - ma_data_source_uninit(&pDecoder->ds); + ma_data_source_base_uninit(&pDecoder->ds); if (pDecoder->pInputCache != NULL) { ma_free(pDecoder->pInputCache, &pDecoder->allocationCallbacks); @@ -67375,6 +76772,38 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) return MA_SUCCESS; } +MA_API const ma_decoding_backend_vtable* ma_decoder_get_backend_vtable(ma_decoder* pDecoder) +{ + if (pDecoder == NULL) { + return NULL; + } + + return pDecoder->pBackendVTable; +} + +MA_API ma_result ma_decoder_get_backend_info(ma_decoder* pDecoder, ma_decoding_backend_info* pInfo) +{ + if (pInfo == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pInfo); + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(pDecoder->pBackendVTable != NULL); + + if (pDecoder->pBackendVTable->onInfo == NULL) { + return MA_INVALID_OPERATION; /* Not implemented. */ + } + + pDecoder->pBackendVTable->onInfo(pDecoder->pBackendUserData, pInfo); + + return MA_SUCCESS; +} + MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_result result = MA_SUCCESS; @@ -67415,7 +76844,7 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO totalFramesReadOut = 0; pRunningFramesOut = pFramesOut; - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL, NULL, 0); + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the internal format and channel count. */ } @@ -67478,7 +76907,6 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO ma_uint64 framesReadThisIterationIn; ma_uint64 framesToReadThisIterationOut; ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; framesToReadThisIterationOut = (frameCount - totalFramesReadOut); framesToReadThisIterationIn = framesToReadThisIterationOut; @@ -67486,12 +76914,7 @@ MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO framesToReadThisIterationIn = intermediaryBufferCap; } - ma_data_converter_get_required_input_frame_count(&pDecoder->converter, framesToReadThisIterationOut, &requiredInputFrameCount); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } - - if (requiredInputFrameCount > 0) { + if (framesToReadThisIterationIn > 0) { result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn); /* @@ -67555,7 +76978,7 @@ MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 fr ma_uint32 internalSampleRate; ma_uint64 currentFrameIndex; - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the internal sample rate. */ } @@ -67649,7 +77072,7 @@ MA_API ma_result ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder, ma_ui return result; /* Failed to retrieve the internal length. */ } - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the internal sample rate. */ } @@ -67856,11 +77279,11 @@ static ma_bool32 ma_encoder__internal_on_seek_wav(void* pUserData, int offset, m MA_ASSERT(pEncoder != NULL); - maSeekOrigin = ma_seek_origin_start; + maSeekOrigin = MA_SEEK_SET; if (origin == MA_DR_WAV_SEEK_CUR) { - maSeekOrigin = ma_seek_origin_current; + maSeekOrigin = MA_SEEK_CUR; } else if (origin == MA_DR_WAV_SEEK_END) { - maSeekOrigin = ma_seek_origin_end; + maSeekOrigin = MA_SEEK_END; } result = pEncoder->onSeek(pEncoder, offset, maSeekOrigin); @@ -67956,8 +77379,6 @@ MA_API ma_encoder_config ma_encoder_config_init(ma_encoding_format encodingForma MA_API ma_result ma_encoder_preinit(const ma_encoder_config* pConfig, ma_encoder* pEncoder) { - ma_result result; - if (pEncoder == NULL) { return MA_INVALID_ARGS; } @@ -67973,11 +77394,7 @@ MA_API ma_result ma_encoder_preinit(const ma_encoder_config* pConfig, ma_encoder } pEncoder->config = *pConfig; - - result = ma_allocation_callbacks_init_copy(&pEncoder->config.allocationCallbacks, &pConfig->allocationCallbacks); - if (result != MA_SUCCESS) { - return result; - } + pEncoder->config.allocationCallbacks = ma_allocation_callbacks_init_copy(&pConfig->allocationCallbacks); return MA_SUCCESS; } @@ -68168,6 +77585,22 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch return config; } + +static size_t ma_waveform__data_source_on_sizeof(void) +{ + return sizeof(ma_waveform); +} + +static void ma_waveform__data_source_on_uninit(ma_data_source* pDataSource) +{ + ma_waveform_uninit((ma_waveform*)pDataSource); +} + +static ma_result ma_waveform__data_source_on_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) +{ + return ma_waveform_init_copy((ma_waveform*)pDataSource, (ma_waveform*)pNewDataSource); +} + static ma_result ma_waveform__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { return ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount, pFramesRead); @@ -68178,27 +77611,46 @@ static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, m return ma_waveform_seek_to_pcm_frame((ma_waveform*)pDataSource, frameIndex); } -static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_waveform__data_source_on_prop(ma_data_source* pDataSource, int prop, void* pData) { ma_waveform* pWaveform = (ma_waveform*)pDataSource; - *pFormat = pWaveform->config.format; - *pChannels = pWaveform->config.channels; - *pSampleRate = pWaveform->config.sampleRate; - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pWaveform->config.channels); + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; - return MA_SUCCESS; -} + pDataFormat->format = pWaveform->config.format; + pDataFormat->channels = pWaveform->config.channels; + pDataFormat->sampleRate = pWaveform->config.sampleRate; -static ma_result ma_waveform__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) -{ - ma_waveform* pWaveform = (ma_waveform*)pDataSource; + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + *((ma_uint64*)pData) = (ma_uint64)(pWaveform->time / pWaveform->advance); + return MA_SUCCESS; + } - *pCursor = (ma_uint64)(pWaveform->time / pWaveform->advance); + default: break; + } - return MA_SUCCESS; + return MA_NOT_IMPLEMENTED; } +static ma_data_source_vtable ma_gDataSourceVTable_Waveform = +{ + ma_waveform__data_source_on_sizeof, + ma_waveform__data_source_on_uninit, + ma_waveform__data_source_on_copy, + ma_waveform__data_source_on_read, + ma_waveform__data_source_on_seek, + ma_waveform__data_source_on_prop +}; + + static double ma_waveform__calculate_advance(ma_uint32 sampleRate, double frequency) { return (1.0 / (sampleRate / frequency)); @@ -68209,17 +77661,6 @@ static void ma_waveform__update_advance(ma_waveform* pWaveform) pWaveform->advance = ma_waveform__calculate_advance(pWaveform->config.sampleRate, pWaveform->config.frequency); } -static ma_data_source_vtable g_ma_waveform_data_source_vtable = -{ - ma_waveform__data_source_on_read, - ma_waveform__data_source_on_seek, - ma_waveform__data_source_on_get_data_format, - ma_waveform__data_source_on_get_cursor, - NULL, /* onGetLength. There's no notion of a length in waveforms. */ - NULL, /* onSetLooping */ - 0 -}; - MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform) { ma_result result; @@ -68232,9 +77673,9 @@ MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform MA_ZERO_OBJECT(pWaveform); dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_waveform_data_source_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_Waveform; - result = ma_data_source_init(&dataSourceConfig, &pWaveform->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pWaveform->ds); if (result != MA_SUCCESS) { return result; } @@ -68246,13 +77687,26 @@ MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform return MA_SUCCESS; } +MA_API ma_result ma_waveform_init_copy(ma_waveform* pWaveform, ma_waveform* pNewWaveform) +{ + ma_waveform_config config; + + if (pNewWaveform == NULL || pWaveform == NULL) { + return MA_INVALID_ARGS; + } + + config = pWaveform->config; + + return ma_waveform_init(&config, pNewWaveform); +} + MA_API void ma_waveform_uninit(ma_waveform* pWaveform) { if (pWaveform == NULL) { return; } - ma_data_source_uninit(&pWaveform->ds); + ma_data_source_base_uninit(&pWaveform->ds); } MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude) @@ -68743,6 +78197,21 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels } +static size_t ma_noise__data_source_on_sizeof(void) +{ + return sizeof(ma_noise); +} + +static void ma_noise__data_source_on_uninit(ma_data_source* pDataSource) +{ + ma_noise_uninit((ma_noise*)pDataSource); +} + +static ma_result ma_noise__data_source_on_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) +{ + return ma_noise_init_copy((ma_noise*)pDataSource, (ma_noise*)pNewDataSource); +} + static ma_result ma_noise__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { return ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount, pFramesRead); @@ -68756,27 +78225,37 @@ static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_u return MA_SUCCESS; } -static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_noise__data_source_on_prop(ma_data_source* pDataSource, int prop, void* pData) { - ma_noise* pNoise = (ma_noise*)pDataSource; + ma_waveform* pNoise = (ma_waveform*)pDataSource; - *pFormat = pNoise->config.format; - *pChannels = pNoise->config.channels; - *pSampleRate = 0; /* There is no notion of sample rate with noise generation. */ - ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pNoise->config.channels); + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; - return MA_SUCCESS; + pDataFormat->format = pNoise->config.format; + pDataFormat->channels = pNoise->config.channels; + pDataFormat->sampleRate = 0; /* There is no notion of sample rate with noise generation. */ + + return MA_SUCCESS; + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_noise_data_source_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_Noise = { + ma_noise__data_source_on_sizeof, + ma_noise__data_source_on_uninit, + ma_noise__data_source_on_copy, ma_noise__data_source_on_read, ma_noise__data_source_on_seek, /* No-op for noise. */ - ma_noise__data_source_on_get_data_format, - NULL, /* onGetCursor. No notion of a cursor for noise. */ - NULL, /* onGetLength. No notion of a length for noise. */ - NULL, /* onSetLooping */ - 0 + ma_noise__data_source_on_prop }; @@ -68877,6 +78356,7 @@ MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void } MA_ZERO_OBJECT(pNoise); + pNoise->allocationCallbacks = ma_allocation_callbacks_init_default(); result = ma_noise_get_heap_layout(pConfig, &heapLayout); if (result != MA_SUCCESS) { @@ -68887,9 +78367,9 @@ MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void MA_ZERO_MEMORY(pNoise->_pHeap, heapLayout.sizeInBytes); dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_noise_data_source_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_Noise; - result = ma_data_source_init(&dataSourceConfig, &pNoise->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pNoise->ds); if (result != MA_SUCCESS) { return result; } @@ -68946,20 +78426,35 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocati return result; } + pNoise->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks); + pNoise->_ownsHeap = MA_TRUE; return MA_SUCCESS; } -MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_result ma_noise_init_copy(ma_noise* pNoise, ma_noise* pNewNoise) +{ + ma_noise_config config; + + if (pNewNoise == NULL || pNoise == NULL) { + return MA_INVALID_ARGS; + } + + config = pNoise->config; + + return ma_noise_init(&config, &pNoise->allocationCallbacks, pNewNoise); +} + +MA_API void ma_noise_uninit(ma_noise* pNoise) { if (pNoise == NULL) { return; } - ma_data_source_uninit(&pNoise->ds); + ma_data_source_base_uninit(&pNoise->ds); if (pNoise->_ownsHeap) { - ma_free(pNoise->_pHeap, pAllocationCallbacks); + ma_free(pNoise->_pHeap, &pNoise->allocationCallbacks); } } @@ -68984,23 +78479,6 @@ MA_API ma_result ma_noise_set_seed(ma_noise* pNoise, ma_int32 seed) } -MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type) -{ - if (pNoise == NULL) { - return MA_INVALID_ARGS; - } - - /* - This function should never have been implemented in the first place. Changing the type dynamically is not - supported. Instead you need to uninitialize and reinitialize a fresh `ma_noise` object. This function - will be removed in version 0.12. - */ - MA_ASSERT(MA_FALSE); - (void)type; - - return MA_INVALID_OPERATION; -} - static MA_INLINE float ma_noise_f32_white(ma_noise* pNoise) { return (float)(ma_lcg_rand_f64(&pNoise->lcg) * pNoise->config.amplitude); @@ -70020,7 +79498,7 @@ MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pCon #endif pResourceManager->config = *pConfig; - ma_allocation_callbacks_init_copy(&pResourceManager->config.allocationCallbacks, &pConfig->allocationCallbacks); + pResourceManager->config.allocationCallbacks = ma_allocation_callbacks_init_copy(&pConfig->allocationCallbacks); /* Get the log set up early so we can start using it as soon as possible. */ if (pResourceManager->config.pLog == NULL) { @@ -70076,21 +79554,38 @@ MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pCon /* Custom decoding backends. */ - if (pConfig->ppCustomDecodingBackendVTables != NULL && pConfig->customDecodingBackendCount > 0) { - size_t sizeInBytes = sizeof(*pResourceManager->config.ppCustomDecodingBackendVTables) * pConfig->customDecodingBackendCount; - ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; + if (pConfig->ppDecodingBackendVTables != NULL && pConfig->decodingBackendCount > 0) { + size_t vtableSizeInBytes; + size_t vtableUserDataSizeInBytes; + ma_decoding_backend_vtable** ppDecodingBackendVTables; + void** ppDecodingBackendUserData; + + vtableSizeInBytes = sizeof(*pResourceManager->config.ppDecodingBackendVTables) * pConfig->decodingBackendCount; + + if (pConfig->ppDecodingBackendUserData != NULL) { + vtableUserDataSizeInBytes = sizeof(*pResourceManager->config.ppDecodingBackendUserData) * pConfig->decodingBackendCount; + } else { + vtableUserDataSizeInBytes = 0; /* No vtable user data present. No need for an allocation. */ + } - ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); - if (pResourceManager->config.ppCustomDecodingBackendVTables == NULL) { + ppDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(vtableSizeInBytes + vtableUserDataSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (ppDecodingBackendVTables == NULL) { ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); return MA_OUT_OF_MEMORY; } - MA_COPY_MEMORY(ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + MA_COPY_MEMORY(ppDecodingBackendVTables, pConfig->ppDecodingBackendVTables, vtableSizeInBytes); + + if (pConfig->ppDecodingBackendUserData != NULL) { + ppDecodingBackendUserData = (void**)ma_offset_ptr(ppDecodingBackendVTables, vtableSizeInBytes); + MA_COPY_MEMORY(ppDecodingBackendUserData, pConfig->ppDecodingBackendUserData, vtableUserDataSizeInBytes); + } else { + ppDecodingBackendUserData = NULL; + } - pResourceManager->config.ppCustomDecodingBackendVTables = ppCustomDecodingBackendVTables; - pResourceManager->config.customDecodingBackendCount = pConfig->customDecodingBackendCount; - pResourceManager->config.pCustomDecodingBackendUserData = pConfig->pCustomDecodingBackendUserData; + pResourceManager->config.ppDecodingBackendVTables = ppDecodingBackendVTables; + pResourceManager->config.ppDecodingBackendUserData = ppDecodingBackendUserData; + pResourceManager->config.decodingBackendCount = pConfig->decodingBackendCount; } @@ -70192,7 +79687,7 @@ MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) #endif } - ma_free((ma_decoding_backend_vtable**)pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); /* <-- Naughty const-cast, but this is safe. */ + ma_free((ma_decoding_backend_vtable**)pResourceManager->config.ppDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); /* <-- This also frees pResourceManager->config.ppDecodingBackendUserData because it's all in one allocation. */ if (pResourceManager->config.pLog == &pResourceManager->log) { ma_log_uninit(&pResourceManager->log); @@ -70219,7 +79714,6 @@ MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_co config.rangeEndInPCMFrames = MA_DATA_SOURCE_DEFAULT_RANGE_END; config.loopPointBegInPCMFrames = MA_DATA_SOURCE_DEFAULT_LOOP_POINT_BEG; config.loopPointEndInPCMFrames = MA_DATA_SOURCE_DEFAULT_LOOP_POINT_END; - config.isLooping = MA_FALSE; return config; } @@ -70230,11 +79724,11 @@ static ma_decoder_config ma_resource_manager__init_decoder_config(ma_resource_ma ma_decoder_config config; config = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate); - config.allocationCallbacks = pResourceManager->config.allocationCallbacks; - config.ppCustomBackendVTables = pResourceManager->config.ppCustomDecodingBackendVTables; - config.customBackendCount = pResourceManager->config.customDecodingBackendCount; - config.pCustomBackendUserData = pResourceManager->config.pCustomDecodingBackendUserData; - config.resampling = pResourceManager->config.resampling; + config.allocationCallbacks = pResourceManager->config.allocationCallbacks; + config.ppBackendVTables = pResourceManager->config.ppDecodingBackendVTables; + config.backendCount = pResourceManager->config.decodingBackendCount; + config.ppBackendUserData = pResourceManager->config.ppDecodingBackendUserData; + config.resampling = pResourceManager->config.resampling; return config; } @@ -70253,14 +79747,14 @@ static ma_result ma_resource_manager__init_decoder(ma_resource_manager* pResourc if (pFilePath != NULL) { result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pFilePath, &config, pDecoder); if (result != MA_SUCCESS) { - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.", pFilePath, ma_result_description(result)); return result; } } else { result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pFilePathW, &config, pDecoder); if (result != MA_SUCCESS) { #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.", pFilePathW, ma_result_description(result)); #endif return result; } @@ -70289,7 +79783,7 @@ static ma_data_source* ma_resource_manager_data_buffer_get_connector(ma_resource case ma_resource_manager_data_supply_type_unknown: default: { - ma_log_postf(ma_resource_manager_get_log(pDataBuffer->pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to retrieve data buffer connector. Unknown data supply type.\n"); + ma_log_postf(ma_resource_manager_get_log(pDataBuffer->pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to retrieve data buffer connector. Unknown data supply type."); return NULL; }; }; @@ -70326,7 +79820,7 @@ static ma_result ma_resource_manager_data_buffer_init_connector(ma_resource_mana case ma_resource_manager_data_supply_type_decoded: /* Connector is an audio buffer. */ { ma_audio_buffer_config config; - config = ma_audio_buffer_config_init(pDataBuffer->pNode->data.backend.decoded.format, pDataBuffer->pNode->data.backend.decoded.channels, pDataBuffer->pNode->data.backend.decoded.totalFrameCount, pDataBuffer->pNode->data.backend.decoded.pData, NULL); + config = ma_audio_buffer_config_init(pDataBuffer->pNode->data.backend.decoded.format, pDataBuffer->pNode->data.backend.decoded.channels, pDataBuffer->pNode->data.backend.decoded.sampleRate, pDataBuffer->pNode->data.backend.decoded.totalFrameCount, pDataBuffer->pNode->data.backend.decoded.pData, NULL); result = ma_audio_buffer_init(&config, &pDataBuffer->connector.buffer); } break; @@ -70370,8 +79864,8 @@ static ma_result ma_resource_manager_data_buffer_init_connector(ma_resource_mana ma_data_source_set_loop_point_in_pcm_frames(pDataBuffer, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); } - if (pConfig->isLooping != MA_FALSE) { - ma_data_source_set_looping(pDataBuffer, pConfig->isLooping); + if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0) { + ma_data_source_set_looping(pDataBuffer, MA_TRUE); } ma_atomic_bool32_set(&pDataBuffer->isConnectorInitialized, MA_TRUE); @@ -70443,10 +79937,10 @@ static ma_result ma_resource_manager_data_buffer_node_init_supply_encoded(ma_res result = ma_vfs_open_and_read_file_ex(pResourceManager->config.pVFS, pFilePath, pFilePathW, &pData, &dataSizeInBytes, &pResourceManager->config.allocationCallbacks); if (result != MA_SUCCESS) { if (pFilePath != NULL) { - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.", pFilePath, ma_result_description(result)); } else { #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.", pFilePathW, ma_result_description(result)); #endif } @@ -70752,7 +80246,7 @@ static ma_result ma_resource_manager_data_buffer_node_acquire_critical_section(m if (result != MA_SUCCESS) { /* Failed to post job. Probably ran out of memory. */ - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE job. %s.", ma_result_description(result)); if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { ma_resource_manager_inline_notification_uninit(pInitNotification); @@ -70856,7 +80350,7 @@ static ma_result ma_resource_manager_data_buffer_node_acquire(ma_resource_manage hashed name), but that node has been freed in the meantime, probably from some other thread. This is an invalid operation. */ - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Cloning data buffer node failed because the source node was released. The source node must remain valid until the cloning has completed.\n"); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Cloning data buffer node failed because the source node was released. The source node must remain valid until the cloning has completed."); result = MA_INVALID_OPERATION; goto done; } @@ -70915,21 +80409,26 @@ static ma_result ma_resource_manager_data_buffer_node_acquire(ma_resource_manage } done: - /* If we failed to initialize the data buffer we need to free it. */ - if (result != MA_SUCCESS) { - if (nodeAlreadyExists == MA_FALSE) { - ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); - ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + { + ma_bool32 isDataOwnedByResourceManager = pDataBufferNode->isDataOwnedByResourceManager; + + /* If we failed to initialize the data buffer we need to free it. */ + if (result != MA_SUCCESS) { + if (nodeAlreadyExists == MA_FALSE) { + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + pDataBufferNode = NULL; + } } - } - /* - The init notification needs to be uninitialized. This will be used if the node does not already - exist, and we've specified ASYNC | WAIT_INIT. - */ - if (nodeAlreadyExists == MA_FALSE && pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { - if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { - ma_resource_manager_inline_notification_uninit(&initNotification); + /* + The init notification needs to be uninitialized. This will be used if the node does not already + exist, and we've specified ASYNC | WAIT_INIT. + */ + if (nodeAlreadyExists == MA_FALSE && isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_uninit(&initNotification); + } } } @@ -71015,7 +80514,7 @@ static ma_result ma_resource_manager_data_buffer_node_unacquire(ma_resource_mana result = ma_resource_manager_post_job(pResourceManager, &job); if (result != MA_SUCCESS) { - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE job. %s.", ma_result_description(result)); return result; } @@ -71041,60 +80540,98 @@ static ma_result ma_resource_manager_data_buffer_node_unacquire(ma_resource_mana } - static ma_uint32 ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer* pDataBuffer) { MA_ASSERT(pDataBuffer != NULL); return ma_atomic_fetch_add_32(&pDataBuffer->executionCounter, 1); } -static ma_result ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) + +static size_t ma_resource_manager_data_buffer_cb__sizeof(void) { - return ma_resource_manager_data_buffer_read_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); + return sizeof(ma_resource_manager_data_buffer); } -static ma_result ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +static void ma_resource_manager_data_buffer_cb__uninit(ma_data_source* pDataSource) { - return ma_resource_manager_data_buffer_seek_to_pcm_frame((ma_resource_manager_data_buffer*)pDataSource, frameIndex); + ma_resource_manager_data_buffer_uninit((ma_resource_manager_data_buffer*)pDataSource); } -static ma_result ma_resource_manager_data_buffer_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_resource_manager_data_buffer_cb__copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) { - return ma_resource_manager_data_buffer_get_data_format((ma_resource_manager_data_buffer*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + return ma_resource_manager_data_buffer_init_copy((ma_resource_manager_data_buffer*)pDataSource, (ma_resource_manager_data_buffer*)pNewDataSource); } -static ma_result ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +static ma_result ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pCursor); + return ma_resource_manager_data_buffer_read_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +static ma_result ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) { - return ma_resource_manager_data_buffer_get_length_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pLength); + return ma_resource_manager_data_buffer_seek_to_pcm_frame((ma_resource_manager_data_buffer*)pDataSource, frameIndex); } -static ma_result ma_resource_manager_data_buffer_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +static ma_result ma_resource_manager_data_buffer_cb__prop(ma_data_source* pDataSource, int prop, void* pData) { ma_resource_manager_data_buffer* pDataBuffer = (ma_resource_manager_data_buffer*)pDataSource; - MA_ASSERT(pDataBuffer != NULL); - ma_atomic_exchange_32(&pDataBuffer->isLooping, isLooping); + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; - /* The looping state needs to be set on the connector as well or else looping won't work when we read audio data. */ - ma_data_source_set_looping(ma_resource_manager_data_buffer_get_connector(pDataBuffer), isLooping); + return ma_resource_manager_data_buffer_get_data_format(pDataBuffer, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0); + } - return MA_SUCCESS; + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + return ma_resource_manager_data_buffer_get_data_format(pDataBuffer, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS); + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(pDataBuffer, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_resource_manager_data_buffer_get_length_in_pcm_frames(pDataBuffer, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_SET_LOOPING: + { + ma_bool32 isLooping = *((ma_bool32*)pData); + + ma_atomic_exchange_32(&pDataBuffer->isLooping, isLooping); + + /* The looping state needs to be set on the connector as well or else looping won't work when we read audio data. */ + ma_data_source_set_looping(ma_resource_manager_data_buffer_get_connector(pDataBuffer), isLooping); + + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_LOOPING: + { + *((ma_bool32*)pData) = ma_atomic_load_32(&pDataBuffer->isLooping); + return MA_SUCCESS; + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_resource_manager_data_buffer_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_ResourceManagerDataBuffer = { + ma_resource_manager_data_buffer_cb__sizeof, + ma_resource_manager_data_buffer_cb__uninit, + ma_resource_manager_data_buffer_cb__copy, ma_resource_manager_data_buffer_cb__read_pcm_frames, ma_resource_manager_data_buffer_cb__seek_to_pcm_frame, - ma_resource_manager_data_buffer_cb__get_data_format, - ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames, - ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames, - ma_resource_manager_data_buffer_cb__set_looping, - 0 + ma_resource_manager_data_buffer_cb__prop }; static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_uint32 hashedName32, ma_resource_manager_data_buffer* pDataBuffer) @@ -71132,10 +80669,6 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; } - if (pConfig->isLooping) { - flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; - } - async = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0; /* @@ -71160,9 +80693,9 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma } dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_resource_manager_data_buffer_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_ResourceManagerDataBuffer; - result = ma_data_source_init(&dataSourceConfig, &pDataBuffer->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pDataBuffer->ds); if (result != MA_SUCCESS) { ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL); ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); @@ -71223,7 +80756,7 @@ static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_ma if (result != MA_SUCCESS) { /* We failed to post the job. Most likely there isn't enough room in the queue's buffer. */ - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER job. %s.\n", ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER job. %s.", ma_result_description(result)); ma_atomic_exchange_i32(&pDataBuffer->result, result); /* Release the fences after the result has been set on the data buffer. */ @@ -71297,20 +80830,20 @@ MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pRe return ma_resource_manager_data_buffer_init_ex(pResourceManager, &config, pDataBuffer); } -MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer) +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager_data_buffer* pDataBuffer, ma_resource_manager_data_buffer* pNewDataBuffer) { ma_resource_manager_data_source_config config; - if (pExistingDataBuffer == NULL) { + if (pDataBuffer == NULL) { return MA_INVALID_ARGS; } - MA_ASSERT(pExistingDataBuffer->pNode != NULL); /* <-- If you've triggered this, you've passed in an invalid existing data buffer. */ + MA_ASSERT(pDataBuffer->pNode != NULL); /* <-- If you've triggered this, you've passed in an invalid existing data buffer. */ config = ma_resource_manager_data_source_config_init(); - config.flags = pExistingDataBuffer->flags; + config.flags = pDataBuffer->flags; - return ma_resource_manager_data_buffer_init_ex_internal(pResourceManager, &config, pExistingDataBuffer->pNode->hashedName32, pDataBuffer); + return ma_resource_manager_data_buffer_init_ex_internal(pDataBuffer->pResourceManager, &config, pDataBuffer->pNode->hashedName32, pNewDataBuffer); } static ma_result ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer* pDataBuffer) @@ -71324,7 +80857,7 @@ static ma_result ma_resource_manager_data_buffer_uninit_internal(ma_resource_man ma_resource_manager_data_buffer_node_unacquire(pDataBuffer->pResourceManager, pDataBuffer->pNode, NULL, NULL); /* The base data source needs to be uninitialized as well. */ - ma_data_source_uninit(&pDataBuffer->ds); + ma_data_source_base_uninit(&pDataBuffer->ds); return MA_SUCCESS; } @@ -71338,7 +80871,19 @@ MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data } if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_SUCCESS) { - /* The data buffer can be deleted synchronously. */ + /* + The data buffer can be deleted synchronously. Before doing so we must wait for any + in-flight load job to fully retire. The load job publishes its result code (which is what + makes us take this branch) a few instructions before it finishes referencing pDataBuffer + and increments the execution pointer. Tearing the buffer down the instant we observe + MA_SUCCESS therefore races the tail of the load job, which then reads and writes freed + memory. Wait for the execution pointer to catch up to the execution counter so that no job + is still referencing the buffer, exactly as the asynchronous path relies on for ordering. + */ + while (ma_atomic_load_32(&pDataBuffer->executionPointer) != ma_atomic_load_32(&pDataBuffer->executionCounter)) { + ma_yield(); + } + return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); } else { /* @@ -71524,7 +81069,9 @@ MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_man { case ma_resource_manager_data_supply_type_encoded: { - return ma_data_source_get_data_format(&pDataBuffer->connector.decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + ma_data_source_get_data_format(&pDataBuffer->connector.decoder, pFormat, pChannels, pSampleRate); + ma_data_source_get_channel_map(&pDataBuffer->connector.decoder, pChannelMap, channelMapCap); + return MA_SUCCESS; }; case ma_resource_manager_data_supply_type_decoded: @@ -71789,50 +81336,85 @@ static ma_uint32 ma_resource_manager_data_stream_seek_counter(const ma_resource_ } -static ma_result ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +static size_t ma_resource_manager_data_stream_cb__sizeof(void) { - return ma_resource_manager_data_stream_read_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pFramesOut, frameCount, pFramesRead); + return sizeof(ma_resource_manager_data_stream); } -static ma_result ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +static void ma_resource_manager_data_stream_cb__uninit(ma_data_source* pDataSource) { - return ma_resource_manager_data_stream_seek_to_pcm_frame((ma_resource_manager_data_stream*)pDataSource, frameIndex); + ma_resource_manager_data_stream_uninit((ma_resource_manager_data_stream*)pDataSource); } -static ma_result ma_resource_manager_data_stream_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +static ma_result ma_resource_manager_data_stream_cb__init_copy(ma_data_source* pDataSource, ma_data_source* pNewDataSource) { - return ma_resource_manager_data_stream_get_data_format((ma_resource_manager_data_stream*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + return ma_resource_manager_data_stream_init_copy((ma_resource_manager_data_stream*)pDataSource, (ma_resource_manager_data_stream*)pNewDataSource); } -static ma_result ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +static ma_result ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - return ma_resource_manager_data_stream_get_cursor_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pCursor); + return ma_resource_manager_data_stream_read_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pFramesOut, frameCount, pFramesRead); } -static ma_result ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +static ma_result ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) { - return ma_resource_manager_data_stream_get_length_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pLength); + return ma_resource_manager_data_stream_seek_to_pcm_frame((ma_resource_manager_data_stream*)pDataSource, frameIndex); } -static ma_result ma_resource_manager_data_stream_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +static ma_result ma_resource_manager_data_stream_cb__prop(ma_data_source* pDataSource, int prop, void* pData) { ma_resource_manager_data_stream* pDataStream = (ma_resource_manager_data_stream*)pDataSource; - MA_ASSERT(pDataStream != NULL); - ma_atomic_exchange_32(&pDataStream->isLooping, isLooping); + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; - return MA_SUCCESS; + return ma_resource_manager_data_stream_get_data_format(pDataStream, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0); + } + + case MA_DATA_SOURCE_GET_CHANNEL_MAP: + { + return ma_resource_manager_data_stream_get_data_format(pDataStream, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS); + } + + case MA_DATA_SOURCE_GET_CURSOR: + { + return ma_resource_manager_data_stream_get_cursor_in_pcm_frames(pDataStream, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_GET_LENGTH: + { + return ma_resource_manager_data_stream_get_length_in_pcm_frames(pDataStream, (ma_uint64*)pData); + } + + case MA_DATA_SOURCE_SET_LOOPING: + { + ma_atomic_exchange_32(&pDataStream->isLooping, *((ma_bool32*)pData)); + return MA_SUCCESS; + } + + case MA_DATA_SOURCE_GET_LOOPING: + { + *((ma_bool32*)pData) = ma_atomic_load_32(&pDataStream->isLooping); + return MA_SUCCESS; + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; } -static ma_data_source_vtable g_ma_resource_manager_data_stream_vtable = +static ma_data_source_vtable ma_gDataSourceVTable_ResourceManagerDataStream = { + ma_resource_manager_data_stream_cb__sizeof, + ma_resource_manager_data_stream_cb__uninit, + ma_resource_manager_data_stream_cb__init_copy, ma_resource_manager_data_stream_cb__read_pcm_frames, ma_resource_manager_data_stream_cb__seek_to_pcm_frame, - ma_resource_manager_data_stream_cb__get_data_format, - ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames, - ma_resource_manager_data_stream_cb__get_length_in_pcm_frames, - ma_resource_manager_data_stream_cb__set_looping, - 0 /*MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT*/ + ma_resource_manager_data_stream_cb__prop }; static void ma_resource_manager_data_stream_set_absolute_cursor(ma_resource_manager_data_stream* pDataStream, ma_uint64 absoluteCursor) @@ -71849,8 +81431,6 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR { ma_result result; ma_data_source_config dataSourceConfig; - char* pFilePathCopy = NULL; - wchar_t* pFilePathWCopy = NULL; ma_job job; ma_bool32 waitBeforeReturning = MA_FALSE; ma_resource_manager_inline_notification waitNotification; @@ -71878,18 +81458,15 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR } dataSourceConfig = ma_data_source_config_init(); - dataSourceConfig.vtable = &g_ma_resource_manager_data_stream_vtable; + dataSourceConfig.pVTable = &ma_gDataSourceVTable_ResourceManagerDataStream; - result = ma_data_source_init(&dataSourceConfig, &pDataStream->ds); + result = ma_data_source_base_init(&dataSourceConfig, &pDataStream->ds); if (result != MA_SUCCESS) { ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); return result; } flags = pConfig->flags; - if (pConfig->isLooping) { - flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; - } pDataStream->pResourceManager = pResourceManager; pDataStream->flags = pConfig->flags; @@ -71908,12 +81485,12 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */ if (pConfig->pFilePath != NULL) { - pFilePathCopy = ma_copy_string(pConfig->pFilePath, &pResourceManager->config.allocationCallbacks); + pDataStream->pFilePath = ma_copy_string(pConfig->pFilePath, &pResourceManager->config.allocationCallbacks); } else { - pFilePathWCopy = ma_copy_string_w(pConfig->pFilePathW, &pResourceManager->config.allocationCallbacks); + pDataStream->pFilePathW = ma_copy_string_w(pConfig->pFilePathW, &pResourceManager->config.allocationCallbacks); } - if (pFilePathCopy == NULL && pFilePathWCopy == NULL) { + if (pDataStream->pFilePath == NULL && pDataStream->pFilePathW == NULL) { ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); return MA_OUT_OF_MEMORY; } @@ -71936,8 +81513,6 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM); job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); job.data.resourceManager.loadDataStream.pDataStream = pDataStream; - job.data.resourceManager.loadDataStream.pFilePath = pFilePathCopy; - job.data.resourceManager.loadDataStream.pFilePathW = pFilePathWCopy; job.data.resourceManager.loadDataStream.initialSeekPoint = pConfig->initialSeekPointInPCMFrames; job.data.resourceManager.loadDataStream.pInitNotification = (waitBeforeReturning == MA_TRUE) ? &waitNotification : notifications.init.pNotification; job.data.resourceManager.loadDataStream.pInitFence = notifications.init.pFence; @@ -71950,8 +81525,8 @@ MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pR ma_resource_manager_inline_notification_uninit(&waitNotification); } - ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); - ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); + ma_free(pDataStream->pFilePath, &pResourceManager->config.allocationCallbacks); + ma_free(pDataStream->pFilePathW, &pResourceManager->config.allocationCallbacks); return result; } @@ -72001,6 +81576,22 @@ MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pRe return ma_resource_manager_data_stream_init_ex(pResourceManager, &config, pDataStream); } +MA_API ma_result ma_resource_manager_data_stream_init_copy(ma_resource_manager_data_stream* pDataStream, ma_resource_manager_data_stream* pNewDataStream) +{ + ma_resource_manager_data_source_config config; + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pDataStream->pFilePath; + config.pFilePathW = pDataStream->pFilePathW; + config.flags = pDataStream->flags; + + return ma_resource_manager_data_stream_init_ex(pDataStream->pResourceManager, &config, pNewDataStream); +} + MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream) { ma_resource_manager_inline_notification freeEvent; @@ -72029,6 +81620,9 @@ MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data /* We need to wait for the job to finish processing before we return. */ ma_resource_manager_inline_notification_wait_and_uninit(&freeEvent); + ma_free(pDataStream->pFilePath, &pDataStream->pResourceManager->config.allocationCallbacks); + ma_free(pDataStream->pFilePathW, &pDataStream->pResourceManager->config.allocationCallbacks); + return MA_SUCCESS; } @@ -72375,7 +81969,10 @@ MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_man We're being a little bit naughty here and accessing the internal decoder from the public API. The output data format is constant, and we've defined this function such that the application is responsible for ensuring it's not called while uninitializing so it should be safe. */ - return ma_data_source_get_data_format(&pDataStream->decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + ma_data_source_get_data_format(&pDataStream->decoder, pFormat, pChannels, pSampleRate); + ma_data_source_get_channel_map(&pDataStream->decoder, pChannelMap, channelMapCap); + + return MA_SUCCESS; } MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor) @@ -72502,7 +82099,7 @@ MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resourc } -static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) +MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) { if (pDataSource == NULL) { return MA_INVALID_ARGS; @@ -72518,28 +82115,11 @@ static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pR return MA_INVALID_ARGS; } - pDataSource->flags = pConfig->flags; - if (pConfig->isLooping) { - pDataSource->flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; - } - - return MA_SUCCESS; -} - -MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) -{ - ma_result result; - - result = ma_resource_manager_data_source_preinit(pResourceManager, pConfig, pDataSource); - if (result != MA_SUCCESS) { - return result; - } - /* The data source itself is just a data stream or a data buffer. */ if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_init_ex(pResourceManager, pConfig, &pDataSource->backend.stream); + return ma_resource_manager_data_stream_init_ex(pResourceManager, pConfig, &pDataSource->stream); } else { - return ma_resource_manager_data_buffer_init_ex(pResourceManager, pConfig, &pDataSource->backend.buffer); + return ma_resource_manager_data_buffer_init_ex(pResourceManager, pConfig, &pDataSource->buffer); } } @@ -72567,29 +82147,15 @@ MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pRe return ma_resource_manager_data_source_init_ex(pResourceManager, &config, pDataSource); } -MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource) +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource) { - ma_result result; - ma_resource_manager_data_source_config config; - - if (pExistingDataSource == NULL) { - return MA_INVALID_ARGS; - } - - config = ma_resource_manager_data_source_config_init(); - config.flags = pExistingDataSource->flags; - - result = ma_resource_manager_data_source_preinit(pResourceManager, &config, pDataSource); - if (result != MA_SUCCESS) { - return result; - } - - /* Copying can only be done from data buffers. Streams cannot be copied. */ - if ((pExistingDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return MA_INVALID_OPERATION; - } + (void)pResourceManager; + return ma_data_source_init_copy(pExistingDataSource, pDataSource); +} - return ma_resource_manager_data_buffer_init_copy(pResourceManager, &pExistingDataSource->backend.buffer, &pDataSource->backend.buffer); +static ma_bool32 ma_resource_manager_data_source_is_stream(const ma_resource_manager_data_source* pDataSource) +{ + return ma_data_source_get_vtable(pDataSource) == &ma_gDataSourceVTable_ResourceManagerDataStream; } MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource) @@ -72598,108 +82164,39 @@ MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data return MA_INVALID_ARGS; } - /* All we need to is uninitialize the underlying data buffer or data stream. */ - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_uninit(&pDataSource->backend.stream); + if (ma_resource_manager_data_source_is_stream(pDataSource)) { + return ma_resource_manager_data_stream_uninit(&pDataSource->stream); } else { - return ma_resource_manager_data_buffer_uninit(&pDataSource->backend.buffer); + return ma_resource_manager_data_buffer_uninit(&pDataSource->buffer); } } MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - /* Safety. */ - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_read_pcm_frames(&pDataSource->backend.stream, pFramesOut, frameCount, pFramesRead); - } else { - return ma_resource_manager_data_buffer_read_pcm_frames(&pDataSource->backend.buffer, pFramesOut, frameCount, pFramesRead); - } + return ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, pFramesRead); } MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex) { - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_seek_to_pcm_frame(&pDataSource->backend.stream, frameIndex); - } else { - return ma_resource_manager_data_buffer_seek_to_pcm_frame(&pDataSource->backend.buffer, frameIndex); - } -} - -MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_map(&pDataSource->backend.stream, ppFramesOut, pFrameCount); - } else { - return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ - } -} - -MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount) -{ - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_unmap(&pDataSource->backend.stream, frameCount); - } else { - return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ - } + return ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); } MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_get_data_format(&pDataSource->backend.stream, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); - } else { - return ma_resource_manager_data_buffer_get_data_format(&pDataSource->backend.buffer, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); - } + ma_data_source_get_data_format(pDataSource, pFormat, pChannels, pSampleRate); + ma_data_source_get_channel_map(pDataSource, pChannelMap, channelMapCap); + + return MA_SUCCESS; } MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor) { - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_get_cursor_in_pcm_frames(&pDataSource->backend.stream, pCursor); - } else { - return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(&pDataSource->backend.buffer, pCursor); - } + return ma_data_source_get_cursor_in_pcm_frames(pDataSource, pCursor); } MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength) { - if (pDataSource == NULL) { - return MA_INVALID_ARGS; - } - - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_get_length_in_pcm_frames(&pDataSource->backend.stream, pLength); - } else { - return ma_resource_manager_data_buffer_get_length_in_pcm_frames(&pDataSource->backend.buffer, pLength); - } + return ma_data_source_get_length_in_pcm_frames(pDataSource, pLength); } MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource) @@ -72708,10 +82205,10 @@ MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manage return MA_INVALID_ARGS; } - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_result(&pDataSource->backend.stream); + if (ma_resource_manager_data_source_is_stream(pDataSource)) { + return ma_resource_manager_data_stream_result(&pDataSource->stream); } else { - return ma_resource_manager_data_buffer_result(&pDataSource->backend.buffer); + return ma_resource_manager_data_buffer_result(&pDataSource->buffer); } } @@ -72721,10 +82218,10 @@ MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager return MA_INVALID_ARGS; } - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_set_looping(&pDataSource->backend.stream, isLooping); + if (ma_resource_manager_data_source_is_stream(pDataSource)) { + return ma_resource_manager_data_stream_set_looping(&pDataSource->stream, isLooping); } else { - return ma_resource_manager_data_buffer_set_looping(&pDataSource->backend.buffer, isLooping); + return ma_resource_manager_data_buffer_set_looping(&pDataSource->buffer, isLooping); } } @@ -72734,10 +82231,10 @@ MA_API ma_bool32 ma_resource_manager_data_source_is_looping(const ma_resource_ma return MA_FALSE; } - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_is_looping(&pDataSource->backend.stream); + if (ma_resource_manager_data_source_is_stream(pDataSource)) { + return ma_resource_manager_data_stream_is_looping(&pDataSource->stream); } else { - return ma_resource_manager_data_buffer_is_looping(&pDataSource->backend.buffer); + return ma_resource_manager_data_buffer_is_looping(&pDataSource->buffer); } } @@ -72753,10 +82250,10 @@ MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resourc return MA_INVALID_ARGS; } - if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { - return ma_resource_manager_data_stream_get_available_frames(&pDataSource->backend.stream, pAvailableFrames); + if (ma_resource_manager_data_source_is_stream(pDataSource)) { + return ma_resource_manager_data_stream_get_available_frames(&pDataSource->stream, pAvailableFrames); } else { - return ma_resource_manager_data_buffer_get_available_frames(&pDataSource->backend.buffer, pAvailableFrames); + return ma_resource_manager_data_buffer_get_available_frames(&pDataSource->buffer, pAvailableFrames); } } @@ -72853,10 +82350,10 @@ static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* if (result != MA_SUCCESS) { if (pJob->data.resourceManager.loadDataBufferNode.pFilePath != NULL) { - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%s\". %s.\n", pJob->data.resourceManager.loadDataBufferNode.pFilePath, ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%s\". %s.", pJob->data.resourceManager.loadDataBufferNode.pFilePath, ma_result_description(result)); } else { #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%ls\", %s.\n", pJob->data.resourceManager.loadDataBufferNode.pFilePathW, ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%ls\", %s.", pJob->data.resourceManager.loadDataBufferNode.pFilePathW, ma_result_description(result)); #endif } @@ -72889,7 +82386,7 @@ static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* is set to MA_BUSY. */ if (result != MA_SUCCESS) { - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE job. %s\n", ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE job. %s.", ma_result_description(result)); ma_decoder_uninit(pDecoder); ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); } else { @@ -73100,11 +82597,14 @@ static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob dataSourceConfig.rangeEndInPCMFrames = pJob->data.resourceManager.loadDataBuffer.rangeEndInPCMFrames; dataSourceConfig.loopPointBegInPCMFrames = pJob->data.resourceManager.loadDataBuffer.loopPointBegInPCMFrames; dataSourceConfig.loopPointEndInPCMFrames = pJob->data.resourceManager.loadDataBuffer.loopPointEndInPCMFrames; - dataSourceConfig.isLooping = pJob->data.resourceManager.loadDataBuffer.isLooping; + + if (pJob->data.resourceManager.loadDataBuffer.isLooping) { + dataSourceConfig.flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; + } result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, &dataSourceConfig, pJob->data.resourceManager.loadDataBuffer.pInitNotification, pJob->data.resourceManager.loadDataBuffer.pInitFence); if (result != MA_SUCCESS) { - ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to initialize connector for data buffer. %s.\n", ma_result_description(result)); + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to initialize connector for data buffer. %s.", ma_result_description(result)); goto done; } } else { @@ -73215,10 +82715,10 @@ static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob /* We need to initialize the decoder first so we can determine the size of the pages. */ decoderConfig = ma_resource_manager__init_decoder_config(pResourceManager); - if (pJob->data.resourceManager.loadDataStream.pFilePath != NULL) { - result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pJob->data.resourceManager.loadDataStream.pFilePath, &decoderConfig, &pDataStream->decoder); + if (pDataStream->pFilePath != NULL) { + result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pDataStream->pFilePath, &decoderConfig, &pDataStream->decoder); } else { - result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pJob->data.resourceManager.loadDataStream.pFilePathW, &decoderConfig, &pDataStream->decoder); + result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pDataStream->pFilePathW, &decoderConfig, &pDataStream->decoder); } if (result != MA_SUCCESS) { goto done; @@ -73260,9 +82760,6 @@ static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob result = MA_SUCCESS; done: - ma_free(pJob->data.resourceManager.loadDataStream.pFilePath, &pResourceManager->config.allocationCallbacks); - ma_free(pJob->data.resourceManager.loadDataStream.pFilePathW, &pResourceManager->config.allocationCallbacks); - /* We can only change the status away from MA_BUSY. If it's set to anything else it means an error has occurred somewhere or the uninitialization process has started (most likely). */ ma_atomic_compare_and_swap_i32(&pDataStream->result, MA_BUSY, result); @@ -73306,7 +82803,7 @@ static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob pDataStream->pPageData = NULL; /* Just in case... */ } - ma_data_source_uninit(&pDataStream->ds); + ma_data_source_base_uninit(&pDataStream->ds); /* The event needs to be signalled last. */ if (pJob->data.resourceManager.freeDataStream.pDoneNotification != NULL) { @@ -73502,7 +82999,7 @@ static void ma_stack_free(ma_stack* pStack, void* p) static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime); -MA_API void ma_debug_fill_pcm_frames_with_sine_wave(float* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +MA_API void ma_debug_fill_pcm_frames_with_sine_wave(void* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) { #ifndef MA_NO_GENERATION { @@ -73560,28 +83057,59 @@ static ma_bool32 ma_node_graph_is_reading(ma_node_graph* pNodeGraph) #endif -static void ma_node_graph_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +static size_t ma_node_graph_data_source__on_sizeof(void) { - ma_node_graph* pNodeGraph = (ma_node_graph*)pNode; - ma_uint64 framesRead; + return sizeof(ma_node_graph); +} - ma_node_graph_read_pcm_frames(pNodeGraph, ppFramesOut[0], *pFrameCountOut, &framesRead); +static void ma_node_graph_data_source__on_uninit(ma_data_source* pDataSource) +{ + ma_node_graph_uninit((ma_node_graph*)pDataSource); +} - *pFrameCountOut = (ma_uint32)framesRead; /* Safe cast. */ +static ma_result ma_node_graph_data_source__on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + /* Since there's no notion of an end, we don't ever want to return MA_AT_END here. But it is possible to return 0. */ + ma_node_graph* pNodeGraph = (ma_node_graph*)pDataSource; + MA_ASSERT(pNodeGraph != NULL); - (void)ppFramesIn; - (void)pFrameCountIn; + return ma_node_graph_read_pcm_frames(pNodeGraph, pFramesOut, frameCount, pFramesRead); } -static ma_node_vtable g_node_graph_node_vtable = +static ma_result ma_node_graph_data_source__on_prop(ma_data_source* pDataSource, int prop, void* pData) { - ma_node_graph_node_process_pcm_frames, - NULL, /* onGetRequiredInputFrameCount */ - 0, /* 0 input buses. */ - 1, /* 1 output bus. */ - 0 /* Flags. */ + ma_node_graph* pNodeGraph = (ma_node_graph*)pDataSource; + + switch (prop) + { + case MA_DATA_SOURCE_GET_DATA_FORMAT: + { + ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData; + + pDataFormat->format = ma_format_f32; + pDataFormat->channels = ma_node_graph_get_channels(pNodeGraph); + pDataFormat->sampleRate = 0; /* There's no notion of a sample rate. It's just whatever rate is supplied by the underlying nodes. */ + + return MA_SUCCESS; + } + + default: break; + } + + return MA_NOT_IMPLEMENTED; +} + +static ma_data_source_vtable ma_gDataSourceVTable_NodeGraph = +{ + ma_node_graph_data_source__on_sizeof, + ma_node_graph_data_source__on_uninit, + NULL, /* onCopy. Copying not supported. */ + ma_node_graph_data_source__on_read, + NULL, /* onSeek */ + ma_node_graph_data_source__on_prop }; + static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) { MA_ASSERT(pNode != NULL); @@ -73597,13 +83125,6 @@ static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const floa (void)pFrameCountIn; (void)ppFramesOut; (void)pFrameCountOut; - -#if 0 - /* The data has already been mixed. We just need to move it to the output buffer. */ - if (ppFramesIn != NULL) { - ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNode, 0)); - } -#endif } static ma_node_vtable g_node_graph_endpoint_vtable = @@ -73618,7 +83139,7 @@ static ma_node_vtable g_node_graph_endpoint_vtable = MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph) { ma_result result; - ma_node_config baseConfig; + ma_data_source_config dataSourceConfig; ma_node_config endpointConfig; if (pNodeGraph == NULL) { @@ -73626,14 +83147,19 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m } MA_ZERO_OBJECT(pNodeGraph); + pNodeGraph->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + pNodeGraph->processingSizeInFrames = pConfig->processingSizeInFrames; - /* Base node so we can use the node graph as a node into another graph. */ - baseConfig = ma_node_config_init(); - baseConfig.vtable = &g_node_graph_node_vtable; - baseConfig.pOutputChannels = &pConfig->channels; + /* Data source. */ + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.pVTable = &ma_gDataSourceVTable_NodeGraph; - result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pNodeGraph->base); + result = ma_data_source_base_init(&dataSourceConfig, &pNodeGraph->ds); if (result != MA_SUCCESS) { return result; } @@ -73641,13 +83167,13 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m /* Endpoint. */ endpointConfig = ma_node_config_init(); - endpointConfig.vtable = &g_node_graph_endpoint_vtable; + endpointConfig.pVTable = &g_node_graph_endpoint_vtable; endpointConfig.pInputChannels = &pConfig->channels; endpointConfig.pOutputChannels = &pConfig->channels; result = ma_node_init(pNodeGraph, &endpointConfig, pAllocationCallbacks, &pNodeGraph->endpoint); if (result != MA_SUCCESS) { - ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + ma_data_source_base_uninit(&pNodeGraph->ds); return result; } @@ -73657,7 +83183,6 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m pNodeGraph->pProcessingCache = (float*)ma_malloc(pConfig->processingSizeInFrames * pConfig->channels * sizeof(float), pAllocationCallbacks); if (pNodeGraph->pProcessingCache == NULL) { ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); - ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); return MA_OUT_OF_MEMORY; } } @@ -73675,7 +83200,6 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m pNodeGraph->pPreMixStack = ma_stack_init(preMixStackSizeInBytes, pAllocationCallbacks); if (pNodeGraph->pPreMixStack == NULL) { ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); - ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); if (pNodeGraph->pProcessingCache != NULL) { ma_free(pNodeGraph->pProcessingCache, pAllocationCallbacks); } @@ -73688,22 +83212,22 @@ MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const m return MA_SUCCESS; } -MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph) { if (pNodeGraph == NULL) { return; } - ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); - ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + ma_node_uninit(&pNodeGraph->endpoint, &pNodeGraph->allocationCallbacks); + ma_data_source_base_uninit(&pNodeGraph->ds); if (pNodeGraph->pProcessingCache != NULL) { - ma_free(pNodeGraph->pProcessingCache, pAllocationCallbacks); + ma_free(pNodeGraph->pProcessingCache, &pNodeGraph->allocationCallbacks); pNodeGraph->pProcessingCache = NULL; } if (pNodeGraph->pPreMixStack != NULL) { - ma_stack_uninit(pNodeGraph->pPreMixStack, pAllocationCallbacks); + ma_stack_uninit(pNodeGraph->pPreMixStack, &pNodeGraph->allocationCallbacks); pNodeGraph->pPreMixStack = NULL; } } @@ -74198,6 +83722,7 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ ma_node_output_bus* pFirst; ma_uint32 inputChannels; ma_bool32 doesOutputBufferHaveContent = MA_FALSE; + ma_uint32 framesRead = 0; /* This will be called from the audio thread which means we can't be doing any locking. Basically, @@ -74241,9 +83766,9 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ ma_bool32 isSilentOutput = MA_FALSE; MA_ASSERT(pOutputBus->pNode != NULL); - MA_ASSERT(((ma_node_base*)pOutputBus->pNode)->vtable != NULL); + MA_ASSERT(((ma_node_base*)pOutputBus->pNode)->pVTable != NULL); - isSilentOutput = (((ma_node_base*)pOutputBus->pNode)->vtable->flags & MA_NODE_FLAG_SILENT_OUTPUT) != 0; + isSilentOutput = (((ma_node_base*)pOutputBus->pNode)->pVTable->flags & MA_NODE_FLAG_SILENT_OUTPUT) != 0; if (pFramesOut != NULL) { /* Read. */ @@ -74314,6 +83839,11 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ /* Seek. */ ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, NULL, frameCount, &framesProcessed, globalTime); } + + /* The number of frames read will equal the maximum number of frames that were pulled from the outputs that are attached to this input. */ + if (framesRead < framesProcessed) { + framesRead = framesProcessed; + } } /* If we didn't output anything, output silence. */ @@ -74321,8 +83851,7 @@ static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_ ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, inputChannels); } - /* In this path we always "process" the entire amount. */ - *pFramesRead = frameCount; + *pFramesRead = framesRead; return result; } @@ -74423,22 +83952,22 @@ static ma_result ma_node_translate_bus_counts(const ma_node_config* pConfig, ma_ MA_ASSERT(pOutputBusCount != NULL); /* Bus counts are determined by the vtable, unless they're set to `MA_NODE_BUS_COUNT_UNKNWON`, in which case they're taken from the config. */ - if (pConfig->vtable->inputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + if (pConfig->pVTable->inputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { inputBusCount = pConfig->inputBusCount; } else { - inputBusCount = pConfig->vtable->inputBusCount; + inputBusCount = pConfig->pVTable->inputBusCount; - if (pConfig->inputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->inputBusCount != pConfig->vtable->inputBusCount) { + if (pConfig->inputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->inputBusCount != pConfig->pVTable->inputBusCount) { return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ } } - if (pConfig->vtable->outputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + if (pConfig->pVTable->outputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { outputBusCount = pConfig->outputBusCount; } else { - outputBusCount = pConfig->vtable->outputBusCount; + outputBusCount = pConfig->pVTable->outputBusCount; - if (pConfig->outputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->outputBusCount != pConfig->vtable->outputBusCount) { + if (pConfig->outputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->outputBusCount != pConfig->pVTable->outputBusCount) { return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ } } @@ -74456,12 +83985,12 @@ static ma_result ma_node_translate_bus_counts(const ma_node_config* pConfig, ma_ /* Some special rules for passthrough nodes. */ - if ((pConfig->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { - if ((pConfig->vtable->inputBusCount != 0 && pConfig->vtable->inputBusCount != 1) || pConfig->vtable->outputBusCount != 1) { + if ((pConfig->pVTable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + if ((inputBusCount != 0 && inputBusCount != 1) || outputBusCount != 1) { return MA_INVALID_ARGS; /* Passthrough nodes must have exactly 1 output bus and either 0 or 1 input bus. */ } - if (pConfig->pInputChannels[0] != pConfig->pOutputChannels[0]) { + if (inputBusCount > 0 && pConfig->pInputChannels[0] != pConfig->pOutputChannels[0]) { return MA_INVALID_ARGS; /* Passthrough nodes must have the same number of channels between input and output nodes. */ } } @@ -74483,7 +84012,7 @@ static ma_result ma_node_get_heap_layout(ma_node_graph* pNodeGraph, const ma_nod MA_ZERO_OBJECT(pHeapLayout); - if (pConfig == NULL || pConfig->vtable == NULL || pConfig->vtable->onProcess == NULL) { + if (pConfig == NULL || pConfig->pVTable == NULL || pConfig->pVTable->onProcess == NULL) { return MA_INVALID_ARGS; } @@ -74608,7 +84137,7 @@ MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_n MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); pNodeBase->pNodeGraph = pNodeGraph; - pNodeBase->vtable = pConfig->vtable; + pNodeBase->pVTable = pConfig->pVTable; pNodeBase->state = pConfig->initialState; pNodeBase->stateTimes[ma_node_state_started] = 0; pNodeBase->stateTimes[ma_node_state_stopped] = (ma_uint64)(ma_int64)-1; /* Weird casting for VC6 compatibility. */ @@ -75054,8 +84583,8 @@ static void ma_node_process_pcm_frames_internal(ma_node* pNode, const float** pp MA_ASSERT(pNode != NULL); - if (pNodeBase->vtable->onProcess) { - pNodeBase->vtable->onProcess(pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); + if (pNodeBase->pVTable->onProcess) { + pNodeBase->pVTable->onProcess(pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); } } @@ -75165,7 +84694,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde If it's a passthrough we won't be expecting the callback to output anything, so we'll need to pre-silence the output buffer. */ - if ((pNodeBase->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + if ((pNodeBase->pVTable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, ma_node_get_output_channels(pNode, outputBusIndex)); } @@ -75173,7 +84702,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde totalFramesRead = frameCountOut; } else { /* Slow path. Need to read input data. */ - if ((pNodeBase->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + if ((pNodeBase->pVTable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { /* Fast path. We're running a passthrough. We need to read directly into the output buffer, but still fire the callback so that event handling and trigger nodes can do their thing. Since @@ -75235,8 +84764,8 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde } framesToProcessIn = frameCount; - if (pNodeBase->vtable->onGetRequiredInputFrameCount) { - pNodeBase->vtable->onGetRequiredInputFrameCount(pNode, framesToProcessOut, &framesToProcessIn); /* <-- It does not matter if this fails. */ + if (pNodeBase->pVTable->onGetRequiredInputFrameCount) { + pNodeBase->pVTable->onGetRequiredInputFrameCount(pNode, framesToProcessOut, &framesToProcessIn); /* <-- It does not matter if this fails. */ } if (framesToProcessIn > pNodeBase->cachedDataCapInFramesPerBus) { framesToProcessIn = pNodeBase->cachedDataCapInFramesPerBus; @@ -75328,11 +84857,11 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde pNodeBase->cachedFrameCountIn, which could be 0. Also, we want to check if we can pass in NULL for the input buffer to the callback. */ - if ((pNodeBase->vtable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) { + if ((pNodeBase->pVTable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) { /* We're using continuous processing. Make sure we specify the whole frame count at all times. */ frameCountIn = framesToProcessIn; /* Give the processing function as much input data as we've got in the buffer, including any silenced padding from short reads. */ - if ((pNodeBase->vtable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) { + if ((pNodeBase->pVTable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) { consumeNullInput = MA_TRUE; } else { consumeNullInput = MA_FALSE; @@ -75376,7 +84905,7 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde determining whether or not we need to process the node even when there are no input frames available right now. */ - if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) { + if (frameCountIn > 0 || (pNodeBase->pVTable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) { ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Explicit cast to silence the warning. */ } else { frameCountOut = 0; /* No data was processed. */ @@ -75470,7 +84999,7 @@ static void ma_data_source_node_process_pcm_frames(ma_node* pNode, const float** /* miniaudio should never be calling this with a frame count of zero. */ MA_ASSERT(frameCount > 0); - if (ma_data_source_get_data_format(pDataSourceNode->pDataSource, &format, &channels, NULL, NULL, 0) == MA_SUCCESS) { /* <-- Don't care about sample rate here. */ + if (ma_data_source_get_data_format(pDataSourceNode->pDataSource, &format, &channels, NULL) == MA_SUCCESS) { /* <-- Don't care about sample rate here. */ /* The node graph system requires samples be in floating point format. This is checked in ma_data_source_node_init(). */ MA_ASSERT(format == ma_format_f32); (void)format; /* Just to silence some static analysis tools. */ @@ -75507,7 +85036,7 @@ MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_da return MA_INVALID_ARGS; } - result = ma_data_source_get_data_format(pConfig->pDataSource, &format, &channels, NULL, NULL, 0); /* Don't care about sample rate. This will check pDataSource for NULL. */ + result = ma_data_source_get_data_format(pConfig->pDataSource, &format, &channels, NULL); /* Don't care about sample rate. This will check pDataSource for NULL. */ if (result != MA_SUCCESS) { return result; } @@ -75519,7 +85048,7 @@ MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_da /* The channel count is defined by the data source. If the caller has manually changed the channels we just ignore it. */ baseConfig = pConfig->nodeConfig; - baseConfig.vtable = &g_ma_data_source_node_vtable; /* Explicitly set the vtable here to prevent callers from setting it incorrectly. */ + baseConfig.pVTable = &g_ma_data_source_node_vtable; /* Explicitly set the vtable here to prevent callers from setting it incorrectly. */ /* The channel count is defined by the data source. It is invalid for the caller to manually set @@ -75642,7 +85171,7 @@ MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_split } baseConfig = pConfig->nodeConfig; - baseConfig.vtable = &g_ma_splitter_node_vtable; + baseConfig.pVTable = &g_ma_splitter_node_vtable; baseConfig.pInputChannels = pInputChannels; baseConfig.pOutputChannels = pOutputChannels; baseConfig.outputBusCount = pConfig->outputBusCount; @@ -75718,7 +85247,7 @@ MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_ } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_biquad_node_vtable; + baseNodeConfig.pVTable = &g_ma_biquad_node_vtable; baseNodeConfig.pInputChannels = &pConfig->biquad.channels; baseNodeConfig.pOutputChannels = &pConfig->biquad.channels; @@ -75810,7 +85339,7 @@ MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_c } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_lpf_node_vtable; + baseNodeConfig.pVTable = &g_ma_lpf_node_vtable; baseNodeConfig.pInputChannels = &pConfig->lpf.channels; baseNodeConfig.pOutputChannels = &pConfig->lpf.channels; @@ -75904,7 +85433,7 @@ MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_c } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_hpf_node_vtable; + baseNodeConfig.pVTable = &g_ma_hpf_node_vtable; baseNodeConfig.pInputChannels = &pConfig->hpf.channels; baseNodeConfig.pOutputChannels = &pConfig->hpf.channels; @@ -75999,7 +85528,7 @@ MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_c } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_bpf_node_vtable; + baseNodeConfig.pVTable = &g_ma_bpf_node_vtable; baseNodeConfig.pInputChannels = &pConfig->bpf.channels; baseNodeConfig.pOutputChannels = &pConfig->bpf.channels; @@ -76093,7 +85622,7 @@ MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_no } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_notch_node_vtable; + baseNodeConfig.pVTable = &g_ma_notch_node_vtable; baseNodeConfig.pInputChannels = &pConfig->notch.channels; baseNodeConfig.pOutputChannels = &pConfig->notch.channels; @@ -76188,7 +85717,7 @@ MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_peak_node_vtable; + baseNodeConfig.pVTable = &g_ma_peak_node_vtable; baseNodeConfig.pInputChannels = &pConfig->peak.channels; baseNodeConfig.pOutputChannels = &pConfig->peak.channels; @@ -76282,7 +85811,7 @@ MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshel } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_loshelf_node_vtable; + baseNodeConfig.pVTable = &g_ma_loshelf_node_vtable; baseNodeConfig.pInputChannels = &pConfig->loshelf.channels; baseNodeConfig.pOutputChannels = &pConfig->loshelf.channels; @@ -76376,7 +85905,7 @@ MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishel } baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_hishelf_node_vtable; + baseNodeConfig.pVTable = &g_ma_hishelf_node_vtable; baseNodeConfig.pInputChannels = &pConfig->hishelf.channels; baseNodeConfig.pOutputChannels = &pConfig->hishelf.channels; @@ -76460,7 +85989,7 @@ MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_no } baseConfig = pConfig->nodeConfig; - baseConfig.vtable = &g_ma_delay_node_vtable; + baseConfig.pVTable = &g_ma_delay_node_vtable; baseConfig.pInputChannels = &pConfig->delay.channels; baseConfig.pOutputChannels = &pConfig->delay.channels; @@ -76484,42 +86013,6 @@ MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_ ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks); } -MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value) -{ - if (pDelayNode == NULL) { - return; - } - - ma_delay_set_wet(&pDelayNode->delay, value); -} - -MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode) -{ - if (pDelayNode == NULL) { - return 0; - } - - return ma_delay_get_wet(&pDelayNode->delay); -} - -MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value) -{ - if (pDelayNode == NULL) { - return; - } - - ma_delay_set_dry(&pDelayNode->delay, value); -} - -MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode) -{ - if (pDelayNode == NULL) { - return 0; - } - - return ma_delay_get_dry(&pDelayNode->delay); -} - MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value) { if (pDelayNode == NULL) { @@ -76564,8 +86057,8 @@ static void ma_sound_set_at_end(ma_sound* pSound, ma_bool32 atEnd) #if 0 /* Fire any callbacks or events. */ if (atEnd) { - if (pSound->endCallback != NULL) { - pSound->endCallback(pSound->pEndCallbackUserData, pSound); + if (pSound->notifications.onAtEnd != NULL) { + pSound->notifications.onAtEnd(pSound->notifications.pUserData, pSound); } } #endif @@ -76624,14 +86117,14 @@ static ma_bool32 ma_engine_node_is_pitching_enabled(const ma_engine_node* pEngin MA_ASSERT(pEngineNode != NULL); /* Don't try to be clever by skipping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ - return !ma_atomic_load_explicit_32(&pEngineNode->isPitchDisabled, ma_atomic_memory_order_acquire); + return ma_atomic_load_explicit_32(&pEngineNode->isPitchDisabled, ma_atomic_memory_order_acquire) == 0; } static ma_bool32 ma_engine_node_is_spatialization_enabled(const ma_engine_node* pEngineNode) { MA_ASSERT(pEngineNode != NULL); - return !ma_atomic_load_explicit_32(&pEngineNode->isSpatializationDisabled, ma_atomic_memory_order_acquire); + return ma_atomic_load_explicit_32(&pEngineNode->isSpatializationDisabled, ma_atomic_memory_order_acquire) == 0; } static ma_result ma_engine_node_set_volume(ma_engine_node* pEngineNode, float volume) @@ -76874,6 +86367,27 @@ static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNo } } + /* + Fire the processing callback. We can cast pEngineNode to a `ma_sound` since that's the base object. There's + a few edge cases here. It's possible that a sound does not have a data source or an input which means the + input frame count may be zero. In this case, the output frame count at this point will also be zero since + it was unable to process any data. We need to check for this and just assume that the processing callback + is producing some data. We pre-initialize this to silence. + */ + { + ma_sound* pSound = (ma_sound*)pEngineNode; + if (pSound->notifications.onProcess != NULL) { + if (totalFramesProcessedOut == 0 && totalFramesProcessedIn == 0 && frameCountOut > 0) { + /* Pre-silence the output buffer. This should be the equivalent to setting the output frame count to 0. */ + totalFramesProcessedOut = frameCountOut; + ma_silence_pcm_frames(ppFramesOut[0], totalFramesProcessedOut, ma_format_f32, channelsOut); + } + + pSound->notifications.onProcess(pSound->notifications.pUserData, pSound, ppFramesOut[0], totalFramesProcessedOut); + } + } + + /* At this point we're done processing. */ *pFrameCountIn = totalFramesProcessedIn; *pFrameCountOut = totalFramesProcessedOut; @@ -76900,8 +86414,8 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float if (ma_sound_at_end(pSound)) { ma_sound_stop(pSound); - if (pSound->endCallback != NULL) { - pSound->endCallback(pSound->pEndCallbackUserData, pSound); + if (pSound->notifications.onAtEnd != NULL) { + pSound->notifications.onAtEnd(pSound->notifications.pUserData, pSound); } *pFrameCountOut = 0; @@ -76934,7 +86448,7 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float For the convenience of the caller, we're doing to allow data sources to use non-floating-point formats and channel counts that differ from the main engine. */ - result = ma_data_source_get_data_format(pSound->pDataSource, &dataSourceFormat, &dataSourceChannels, NULL, NULL, 0); + result = ma_data_source_get_data_format(pSound->pDataSource, &dataSourceFormat, &dataSourceChannels, NULL); if (result == MA_SUCCESS) { tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels); @@ -76981,7 +86495,7 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float while (totalFramesConverted < pSound->processingCacheCap) { ma_uint64 framesConverted; - ma_uint32 framesToConvertThisIteration = pSound->processingCacheCap - (ma_uint32)totalFramesConverted; + ma_uint32 framesToConvertThisIteration = pSound->processingCacheCap - (ma_uint32)totalFramesConverted; /* Safe cast because pSound->processingCacheCap is 32-bit (compared above in the loop condition). */ if (framesToConvertThisIteration > tempCapInFrames) { framesToConvertThisIteration = tempCapInFrames; } @@ -76996,6 +86510,15 @@ static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float } framesJustRead = totalFramesConverted; + + /* + When the last iteration of the loop above returns MA_AT_END, we can be in a situation where `result` is MA_AT_END and + framesJustRead is >0. This is never valid in miniaudio so we need to normalize this (MA_AT_END should only be used + when the number of frames read is exactly 0, everywhere in miniaudio). + */ + if (result == MA_AT_END && framesJustRead > 0) { + result = MA_SUCCESS; + } } MA_ASSERT(framesJustRead <= pSound->processingCacheCap); @@ -77059,12 +86582,12 @@ static ma_node_config ma_engine_node_base_node_config_init(const ma_engine_node_ if (pConfig->type == ma_engine_node_type_sound) { /* Sound. */ baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_engine_node_vtable__sound; + baseNodeConfig.pVTable = &g_ma_engine_node_vtable__sound; baseNodeConfig.initialState = ma_node_state_stopped; /* Sounds are stopped by default. */ } else { /* Group. */ baseNodeConfig = ma_node_config_init(); - baseNodeConfig.vtable = &g_ma_engine_node_vtable__group; + baseNodeConfig.pVTable = &g_ma_engine_node_vtable__group; baseNodeConfig.initialState = ma_node_state_started; /* Groups are started by default. */ } @@ -77307,7 +86830,7 @@ MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* p ensure channels counts link up correctly in the node graph. */ spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); - spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->gainSmoothTimeInFrames; + spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->spatializationVolumeSmoothTimeInFrames; if (spatializerConfig.channelsIn == 2) { spatializerConfig.pChannelMapIn = defaultStereoChannelMap; @@ -77404,12 +86927,18 @@ MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocati } -MA_API ma_sound_config ma_sound_config_init(void) + +MA_API ma_sound_notifications ma_sound_notifications_init(void) { - return ma_sound_config_init_2(NULL); + ma_sound_notifications notifications; + + MA_ZERO_OBJECT(¬ifications); + + return notifications; } -MA_API ma_sound_config ma_sound_config_init_2(ma_engine* pEngine) + +MA_API ma_sound_config ma_sound_config_init(ma_engine* pEngine) { ma_sound_config config; @@ -77422,7 +86951,7 @@ MA_API ma_sound_config ma_sound_config_init_2(ma_engine* pEngine) config.monoExpansionMode = ma_mono_expansion_mode_default; config.pitchResampling = ma_resampler_config_init(ma_format_f32, 0, 0, 0, ma_resample_algorithm_linear); - config.pitchResampling.linear.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ + config.pitchResampling.linear.lpfOrder = 0; /* <-- Pitch shifting does not need filtering by default. Might make this configurable later on if I can be sold on it. */ } config.rangeEndInPCMFrames = ~((ma_uint64)0); @@ -77431,12 +86960,7 @@ MA_API ma_sound_config ma_sound_config_init_2(ma_engine* pEngine) return config; } -MA_API ma_sound_group_config ma_sound_group_config_init(void) -{ - return ma_sound_group_config_init_2(NULL); -} - -MA_API ma_sound_group_config ma_sound_group_config_init_2(ma_engine* pEngine) +MA_API ma_sound_group_config ma_sound_group_config_init(ma_engine* pEngine) { ma_sound_group_config config; @@ -77505,21 +87029,6 @@ static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOu ma_engine_read_pcm_frames(pEngine, pFramesOut, frameCount, NULL); } - -static ma_uint32 ma_device__get_processing_size_in_frames(ma_device* pDevice) -{ - /* - The processing size is the period size. The device can have a fixed sized processing size, or - it can be decided by the backend in which case it can be variable. - */ - if (pDevice->playback.intermediaryBufferCap > 0) { - /* Using a fixed sized processing callback. */ - return pDevice->playback.intermediaryBufferCap; - } else { - /* Not using a fixed sized processing callback. Need to estimate the processing size based on the backend. */ - return pDevice->playback.internalPeriodSizeInFrames; - } -} #endif MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine) @@ -77544,11 +87053,12 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng } pEngine->monoExpansionMode = engineConfig.monoExpansionMode; - pEngine->defaultVolumeSmoothTimeInPCMFrames = engineConfig.defaultVolumeSmoothTimeInPCMFrames; + pEngine->defaultVolumeSmoothTimeInFrames = engineConfig.defaultVolumeSmoothTimeInFrames; pEngine->onProcess = engineConfig.onProcess; pEngine->pProcessUserData = engineConfig.pProcessUserData; pEngine->pitchResamplingConfig = engineConfig.pitchResampling; - ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks); + pEngine->noClip = (ma_bool8)engineConfig.noClip; + pEngine->allocationCallbacks = ma_allocation_callbacks_init_copy(&engineConfig.allocationCallbacks); #if !defined(MA_NO_RESOURCE_MANAGER) { @@ -77580,7 +87090,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng deviceConfig.periodSizeInFrames = engineConfig.periodSizeInFrames; deviceConfig.periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds; deviceConfig.noPreSilencedOutputBuffer = MA_TRUE; /* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */ - deviceConfig.noClip = MA_TRUE; /* The engine will do clipping itself. */ + deviceConfig.noClip = engineConfig.noClip; if (engineConfig.pContext == NULL) { ma_context_config contextConfig = ma_context_config_init(); @@ -77621,7 +87131,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng possible that the node graph will split it's processing into multiple passes which can introduce glitching. */ - engineConfig.periodSizeInFrames = ma_device__get_processing_size_in_frames(pEngine->pDevice); + engineConfig.periodSizeInFrames = ma_device_get_period_size_in_frames(pEngine->pDevice); } } #endif @@ -77700,14 +87210,14 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng /* Gain smoothing for spatialized sounds. */ - pEngine->gainSmoothTimeInFrames = engineConfig.gainSmoothTimeInFrames; - if (pEngine->gainSmoothTimeInFrames == 0) { - ma_uint32 gainSmoothTimeInMilliseconds = engineConfig.gainSmoothTimeInMilliseconds; - if (gainSmoothTimeInMilliseconds == 0) { - gainSmoothTimeInMilliseconds = 8; + pEngine->spatializationVolumeSmoothTimeInFrames = engineConfig.spatializationVolumeSmoothTimeInFrames; + if (pEngine->spatializationVolumeSmoothTimeInFrames == 0) { + ma_uint32 spatializationVolumeSmoothTimeInMilliseconds = engineConfig.spatializationVolumeSmoothTimeInMilliseconds; + if (spatializationVolumeSmoothTimeInMilliseconds == 0) { + spatializationVolumeSmoothTimeInMilliseconds = 8; } - pEngine->gainSmoothTimeInFrames = (gainSmoothTimeInMilliseconds * ma_engine_get_sample_rate(pEngine)) / 1000; /* 8ms by default. */ + pEngine->spatializationVolumeSmoothTimeInFrames = (spatializationVolumeSmoothTimeInMilliseconds * ma_engine_get_sample_rate(pEngine)) / 1000; /* 8ms by default. */ } @@ -77724,13 +87234,13 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng } resourceManagerConfig = ma_resource_manager_config_init(); - resourceManagerConfig.pLog = pEngine->pLog; /* Always use the engine's log for internally-managed resource managers. */ - resourceManagerConfig.decodedFormat = ma_format_f32; - resourceManagerConfig.decodedChannels = 0; /* Leave the decoded channel count as 0 so we can get good spatialization. */ - resourceManagerConfig.decodedSampleRate = ma_engine_get_sample_rate(pEngine); - ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks); - resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; - resourceManagerConfig.resampling = engineConfig.resourceManagerResampling; + resourceManagerConfig.pLog = pEngine->pLog; /* Always use the engine's log for internally-managed resource managers. */ + resourceManagerConfig.decodedFormat = ma_format_f32; + resourceManagerConfig.decodedChannels = 0; /* Leave the decoded channel count as 0 so we can get good spatialization. */ + resourceManagerConfig.decodedSampleRate = ma_engine_get_sample_rate(pEngine); + resourceManagerConfig.allocationCallbacks = ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks); + resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; + resourceManagerConfig.resampling = engineConfig.resourceManagerResampling; /* The Emscripten build cannot use threads unless it's targeting pthreads. */ #if defined(MA_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) @@ -77782,7 +87292,7 @@ MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEng ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks); } - ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); + ma_node_graph_uninit(&pEngine->nodeGraph); on_error_1: #if !defined(MA_NO_DEVICE_IO) { @@ -77843,7 +87353,7 @@ MA_API void ma_engine_uninit(ma_engine* pEngine) } /* Make sure the node graph is uninitialized after the audio thread has been shutdown to prevent accessing of the node graph after being uninitialized. */ - ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); + ma_node_graph_uninit(&pEngine->nodeGraph); /* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */ #ifndef MA_NO_RESOURCE_MANAGER @@ -77876,6 +87386,17 @@ MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, pEngine->onProcess(pEngine->pProcessUserData, (float*)pFramesOut, framesRead); /* Safe cast to float* because the engine always works on floating point samples. */ } + /* Apply clipping, unless it's been disabled. */ + if (pEngine->noClip == MA_FALSE) { + /* Don't bother clipping if the device is already doing it. */ + #ifndef MA_NO_DEVICE_IO + if (pEngine->pDevice != NULL && pEngine->pDevice->noClip == MA_FALSE) + #endif + { + ma_clip_samples_f32((float*)pFramesOut, (const float*)pFramesOut, frameCount * ma_engine_get_channels(pEngine)); + } + } + return MA_SUCCESS; } @@ -77907,6 +87428,7 @@ MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine) } #endif +#if !defined(MA_NO_DEVICE_IO) MA_API ma_device* ma_engine_get_device(ma_engine* pEngine) { if (pEngine == NULL) { @@ -77923,6 +87445,7 @@ MA_API ma_device* ma_engine_get_device(ma_engine* pEngine) } #endif } +#endif MA_API ma_log* ma_engine_get_log(ma_engine* pEngine) { @@ -77970,16 +87493,6 @@ MA_API ma_result ma_engine_set_time_in_milliseconds(ma_engine* pEngine, ma_uint6 return ma_engine_set_time_in_pcm_frames(pEngine, globalTime * ma_engine_get_sample_rate(pEngine) / 1000); } -MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine) -{ - return ma_engine_get_time_in_pcm_frames(pEngine); -} - -MA_API ma_result ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime) -{ - return ma_engine_set_time_in_pcm_frames(pEngine, globalTime); -} - MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine) { return ma_node_graph_get_channels(&pEngine->nodeGraph); @@ -78401,6 +87914,10 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con type = ma_engine_node_type_group; } + if (pConfig->pNotifications != NULL) { + pSound->notifications = *pConfig->pNotifications; + } + /* Sounds are engine nodes. Before we can initialize this we need to determine the channel count. If we can't do this we need to abort. It's up to the caller to ensure they're using a data @@ -78413,12 +87930,12 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con engineNodeConfig.monoExpansionMode = pConfig->monoExpansionMode; if (engineNodeConfig.volumeSmoothTimeInPCMFrames == 0) { - engineNodeConfig.volumeSmoothTimeInPCMFrames = pEngine->defaultVolumeSmoothTimeInPCMFrames; + engineNodeConfig.volumeSmoothTimeInPCMFrames = pEngine->defaultVolumeSmoothTimeInFrames; } /* If we're loading from a data source the input channel count needs to be the data source's native channel count. */ if (pConfig->pDataSource != NULL) { - result = ma_data_source_get_data_format(pConfig->pDataSource, NULL, &engineNodeConfig.channelsIn, &engineNodeConfig.sampleRate, NULL, 0); + result = ma_data_source_get_data_format(pConfig->pDataSource, NULL, &engineNodeConfig.channelsIn, &engineNodeConfig.sampleRate); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the channel count. */ } @@ -78484,12 +88001,22 @@ static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, con ma_data_source_set_loop_point_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); } - ma_sound_set_looping(pSound, pConfig->isLooping || ((pConfig->flags & MA_SOUND_FLAG_LOOPING) != 0)); + ma_sound_set_looping(pSound, (pConfig->flags & MA_SOUND_FLAG_LOOPING) != 0); return MA_SUCCESS; } #ifndef MA_NO_RESOURCE_MANAGER +static void ma_sound__on_async_notification_loaded(ma_async_notification* pNotification) +{ + ma_sound* pSound = (ma_sound*)((ma_async_notification_callbacks*)pNotification)->pUserData; + MA_ASSERT(pSound != NULL); + + if (pSound->notifications.onLoaded) { + pSound->notifications.onLoaded(pSound->notifications.pUserData, pSound); + } +} + MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) { ma_result result = MA_SUCCESS; @@ -78508,20 +88035,23 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s it and can avoid accessing the sound from within the notification. */ flags = pConfig->flags | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT; - if (pConfig->isLooping) { - flags |= MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING; - } - pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); - if (pSound->pResourceManagerDataSource == NULL) { + pSound->pDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(ma_resource_manager_data_source), &pEngine->allocationCallbacks); + if (pSound->pDataSource == NULL) { return MA_OUT_OF_MEMORY; } - /* Removed in 0.12. Set pDoneFence on the notifications. */ - notifications = pConfig->initNotifications; - if (pConfig->pDoneFence != NULL && notifications.done.pFence == NULL) { - notifications.done.pFence = pConfig->pDoneFence; + notifications = ma_resource_manager_pipeline_notifications_init(); + if (pConfig->pNotifications != NULL) { + notifications.done.pFence = pConfig->pNotifications->pLoadedFence; + + if (pConfig->pNotifications->onLoaded != NULL) { + pSound->resourceManagerDoneNotification.onSignal = ma_sound__on_async_notification_loaded; + pSound->resourceManagerDoneNotification.pUserData = pSound; + notifications.done.pNotification = &pSound->resourceManagerDoneNotification; + } } + /* We must wrap everything around the fence if one was specified. This ensures ma_fence_wait() does @@ -78539,11 +88069,10 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s resourceManagerDataSourceConfig.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; resourceManagerDataSourceConfig.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; resourceManagerDataSourceConfig.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; - resourceManagerDataSourceConfig.isLooping = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_LOOPING) != 0; - result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, pSound->pResourceManagerDataSource); + result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, (ma_resource_manager_data_source*)pSound->pDataSource); if (result != MA_SUCCESS) { - ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + ma_free(pSound->pDataSource, &pEngine->allocationCallbacks); goto done; } @@ -78553,12 +88082,12 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s config = *pConfig; config.pFilePath = NULL; config.pFilePathW = NULL; - config.pDataSource = pSound->pResourceManagerDataSource; + config.pDataSource = pSound->pDataSource; result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); if (result != MA_SUCCESS) { - ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); - ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + ma_resource_manager_data_source_uninit((ma_resource_manager_data_source*)pSound->pDataSource); + ma_free(pSound->pDataSource, &pEngine->allocationCallbacks); MA_ZERO_OBJECT(pSound); goto done; } @@ -78568,7 +88097,7 @@ MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_s return result; } -MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound) { ma_sound_config config; @@ -78576,16 +88105,16 @@ MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePa return MA_INVALID_ARGS; } - config = ma_sound_config_init_2(pEngine); + config = ma_sound_config_init(pEngine); config.pFilePath = pFilePath; config.flags = flags; config.pInitialAttachment = pGroup; - config.pDoneFence = pDoneFence; + config.pNotifications = pNotifications; return ma_sound_init_ex(pEngine, &config, pSound); } -MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound) { ma_sound_config config; @@ -78593,19 +88122,21 @@ MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pF return MA_INVALID_ARGS; } - config = ma_sound_config_init_2(pEngine); + config = ma_sound_config_init(pEngine); config.pFilePathW = pFilePath; config.flags = flags; config.pInitialAttachment = pGroup; - config.pDoneFence = pDoneFence; + config.pNotifications = pNotifications; return ma_sound_init_ex(pEngine, &config, pSound); } -MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound) { ma_result result; ma_sound_config config; + const ma_data_source_vtable* pDataSourceVTable; + ma_data_source* pNewDataSource; result = ma_sound_preinit(pEngine, pSound); if (result != MA_SUCCESS) { @@ -78616,37 +88147,38 @@ MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistin return MA_INVALID_ARGS; } - /* Cloning only works for data buffers (not streams) that are loaded from the resource manager. */ - if (pExistingSound->pResourceManagerDataSource == NULL) { - return MA_INVALID_OPERATION; + pDataSourceVTable = ma_data_source_get_vtable(pExistingSound->pDataSource); + if (pDataSourceVTable == NULL) { + return MA_INVALID_OPERATION; /* Sound is not tied to a data source. */ } - /* - We need to make a clone of the data source. If the data source is not a data buffer (i.e. a stream) - this will fail. - */ - pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); - if (pSound->pResourceManagerDataSource == NULL) { + if (pDataSourceVTable->onSizeof == NULL || pDataSourceVTable->onCopy == NULL) { + return MA_INVALID_OPERATION; /* Data source is not copyable. */ + } + + pNewDataSource = (ma_data_source*)ma_malloc(pDataSourceVTable->onSizeof(), &pEngine->allocationCallbacks); + if (pNewDataSource == NULL) { return MA_OUT_OF_MEMORY; } - result = ma_resource_manager_data_source_init_copy(pEngine->pResourceManager, pExistingSound->pResourceManagerDataSource, pSound->pResourceManagerDataSource); + result = ma_data_source_init_copy(pExistingSound->pDataSource, pNewDataSource); if (result != MA_SUCCESS) { - ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); - return result; + ma_free(pNewDataSource, &pEngine->allocationCallbacks); + return result; /* Copying probably not supported by the data source. */ } - config = ma_sound_config_init_2(pEngine); - config.pDataSource = pSound->pResourceManagerDataSource; + config = ma_sound_config_init(pEngine); + config.pDataSource = pNewDataSource; config.flags = flags; config.pInitialAttachment = pGroup; config.monoExpansionMode = pExistingSound->engineNode.monoExpansionMode; config.volumeSmoothTimeInPCMFrames = pExistingSound->engineNode.volumeSmoothTimeInPCMFrames; + config.pNotifications = pNotifications; result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); if (result != MA_SUCCESS) { - ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); - ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + if (pDataSourceVTable->onUninit) { pDataSourceVTable->onUninit(pNewDataSource); } + ma_free(pNewDataSource, &pEngine->allocationCallbacks); MA_ZERO_OBJECT(pSound); return result; } @@ -78658,12 +88190,14 @@ MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistin } #endif -MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, const ma_sound_notifications* pNotifications, ma_sound* pSound) { - ma_sound_config config = ma_sound_config_init_2(pEngine); + ma_sound_config config = ma_sound_config_init(pEngine); config.pDataSource = pDataSource; config.flags = flags; config.pInitialAttachment = pGroup; + config.pNotifications = pNotifications; + return ma_sound_init_ex(pEngine, &config, pSound); } @@ -78680,8 +88214,9 @@ MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pCo return MA_INVALID_ARGS; } - pSound->endCallback = pConfig->endCallback; - pSound->pEndCallbackUserData = pConfig->pEndCallbackUserData; + if (pConfig->pNotifications != NULL) { + pSound->notifications = *pConfig->pNotifications; + } /* We need to load the sound differently depending on whether or not we're loading from a file. */ #ifndef MA_NO_RESOURCE_MANAGER @@ -78717,16 +88252,14 @@ MA_API void ma_sound_uninit(ma_sound* pSound) pSound->pProcessingCache = NULL; } - /* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */ -#ifndef MA_NO_RESOURCE_MANAGER if (pSound->ownsDataSource) { - ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); - ma_free(pSound->pResourceManagerDataSource, &pSound->engineNode.pEngine->allocationCallbacks); + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pSound->pDataSource; + MA_ASSERT(pDataSourceBase != NULL); + + if (pDataSourceBase->pVTable->onUninit) { pDataSourceBase->pVTable->onUninit(pSound->pDataSource); } + ma_free(pSound->pDataSource, &pSound->engineNode.pEngine->allocationCallbacks); pSound->pDataSource = NULL; } -#else - MA_ASSERT(pSound->ownsDataSource == MA_FALSE); -#endif } MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound) @@ -79039,13 +88572,13 @@ MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound) return ma_spatializer_get_velocity(&pSound->engineNode.spatializer); } -MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel) +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel, void* pAttenuationUserData) { if (pSound == NULL) { return; } - ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel); + ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel, pAttenuationUserData); } MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound) @@ -79493,7 +89026,19 @@ MA_API ma_result ma_sound_get_data_format(const ma_sound* pSound, ma_format* pFo return MA_SUCCESS; } else { - return ma_data_source_get_data_format(pSound->pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + ma_result result; + + result = ma_data_source_get_data_format(pSound->pDataSource, pFormat, pChannels, pSampleRate); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_data_source_get_channel_map(pSound->pDataSource, pChannelMap, channelMapCap); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; } } @@ -79573,27 +89118,10 @@ MA_API ma_result ma_sound_get_length_in_seconds(const ma_sound* pSound, float* p return ma_data_source_get_length_in_seconds(pSound->pDataSource, pLength); } -MA_API ma_result ma_sound_set_end_callback(ma_sound* pSound, ma_sound_end_proc callback, void* pUserData) -{ - if (pSound == NULL) { - return MA_INVALID_ARGS; - } - - /* The notion of an end is only valid for sounds that are backed by a data source. */ - if (pSound->pDataSource == NULL) { - return MA_INVALID_OPERATION; - } - - pSound->endCallback = callback; - pSound->pEndCallbackUserData = pUserData; - - return MA_SUCCESS; -} - MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup) { - ma_sound_group_config config = ma_sound_group_config_init_2(pEngine); + ma_sound_group_config config = ma_sound_group_config_init(pEngine); config.flags = flags; config.pInitialAttachment = pParentGroup; return ma_sound_group_init_ex(pEngine, &config, pGroup); @@ -79750,9 +89278,9 @@ MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup) return ma_sound_get_velocity(pGroup); } -MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel) +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel, void* pAttenuationUserData) { - ma_sound_set_attenuation_model(pGroup, attenuationModel); + ma_sound_set_attenuation_model(pGroup, attenuationModel, pAttenuationUserData); } MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup) @@ -80341,6 +89869,14 @@ MA_PRIVATE unsigned int ma_dr_wav__chunk_padding_size_w64(ma_uint64 chunkSize) { return (unsigned int)(chunkSize % 8); } +MA_PRIVATE unsigned int ma_dr_wav_calculate_padding_size(ma_dr_wav_container container, ma_uint64 chunkSize) +{ + if (container == ma_dr_wav_container_riff || container == ma_dr_wav_container_rf64) { + return ma_dr_wav__chunk_padding_size_riff(chunkSize); + } else { + return ma_dr_wav__chunk_padding_size_w64(chunkSize); + } +} MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__msadpcm(ma_dr_wav* pWav, ma_uint64 samplesToRead, ma_int16* pBufferOut); MA_PRIVATE ma_uint64 ma_dr_wav_read_pcm_frames_s16__ima(ma_dr_wav* pWav, ma_uint64 samplesToRead, ma_int16* pBufferOut); MA_PRIVATE ma_bool32 ma_dr_wav_init_write__internal(ma_dr_wav* pWav, const ma_dr_wav_data_format* pFormat, ma_uint64 totalSampleCount); @@ -80365,7 +89901,12 @@ MA_PRIVATE ma_result ma_dr_wav__read_chunk_header(ma_dr_wav_read_proc onRead, vo if (onRead(pUserData, sizeInBytes, 8) != 8) { return MA_INVALID_FILE; } - pHeaderOut->sizeInBytes = ma_dr_wav_bytes_to_u64(sizeInBytes) - 24; + pHeaderOut->sizeInBytes = ma_dr_wav_bytes_to_u64(sizeInBytes); + if (pHeaderOut->sizeInBytes >= 24) { + pHeaderOut->sizeInBytes -= 24; + } else { + return MA_INVALID_FILE; + } pHeaderOut->paddingSize = ma_dr_wav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 24; } else { @@ -80533,7 +90074,7 @@ MA_PRIVATE ma_uint64 ma_dr_wav__read_smpl_to_metadata_obj(ma_dr_wav__metadata_pa ma_uint32 loopCount; ma_uint32 calculatedLoopCount; loopCount = ma_dr_wav_bytes_to_u32(smplHeaderData + 28); - calculatedLoopCount = (pChunkHeader->sizeInBytes - MA_DR_WAV_SMPL_BYTES) / MA_DR_WAV_SMPL_LOOP_BYTES; + calculatedLoopCount = (ma_uint32)((pChunkHeader->sizeInBytes - MA_DR_WAV_SMPL_BYTES) / MA_DR_WAV_SMPL_LOOP_BYTES); if (loopCount != calculatedLoopCount) { return totalBytesRead; } @@ -80808,8 +90349,9 @@ MA_PRIVATE ma_uint64 ma_dr_wav__read_bext_to_metadata_obj(ma_dr_wav__metadata_pa if (extraBytes > 0) { pMetadata->data.bext.pCodingHistory = (char*)ma_dr_wav__metadata_get_memory(pParser, extraBytes + 1, 1); MA_DR_WAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); - bytesRead += ma_dr_wav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); - pMetadata->data.bext.codingHistorySize = (ma_uint32)ma_dr_wav__strlen(pMetadata->data.bext.pCodingHistory); + pMetadata->data.bext.codingHistorySize = (ma_uint32)ma_dr_wav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); + pMetadata->data.bext.pCodingHistory[pMetadata->data.bext.codingHistorySize] = '\0'; + bytesRead += pMetadata->data.bext.codingHistorySize; } else { pMetadata->data.bext.pCodingHistory = NULL; pMetadata->data.bext.codingHistorySize = 0; @@ -80951,8 +90493,8 @@ MA_PRIVATE ma_uint64 ma_dr_wav__metadata_process_chunk(ma_dr_wav__metadata_parse bytesJustRead = ma_dr_wav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); if (bytesJustRead == sizeof(buffer)) { ma_uint32 loopCount = ma_dr_wav_bytes_to_u32(buffer); - ma_uint64 calculatedLoopCount; - calculatedLoopCount = (pChunkHeader->sizeInBytes - MA_DR_WAV_SMPL_BYTES) / MA_DR_WAV_SMPL_LOOP_BYTES; + ma_uint32 calculatedLoopCount; + calculatedLoopCount = (ma_uint32)((pChunkHeader->sizeInBytes - MA_DR_WAV_SMPL_BYTES) / MA_DR_WAV_SMPL_LOOP_BYTES); if (calculatedLoopCount == loopCount) { bytesJustRead = ma_dr_wav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); if (bytesJustRead == sizeof(buffer)) { @@ -81366,6 +90908,9 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p if (((pWav->container == ma_dr_wav_container_riff || pWav->container == ma_dr_wav_container_rifx || pWav->container == ma_dr_wav_container_rf64) && ma_dr_wav_fourcc_equal(header.id.fourcc, "fmt ")) || ((pWav->container == ma_dr_wav_container_w64) && ma_dr_wav_guid_equal(header.id.guid, ma_dr_wavGUID_W64_FMT))) { ma_uint8 fmtData[16]; + if (header.sizeInBytes < sizeof(fmtData)) { + return MA_FALSE; + } foundChunk_fmt = MA_TRUE; if (pWav->onRead(pWav->pUserData, fmtData, sizeof(fmtData)) != sizeof(fmtData)) { return MA_FALSE; @@ -81447,6 +90992,9 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p ((pWav->container == ma_dr_wav_container_w64) && ma_dr_wav_guid_equal(header.id.guid, ma_dr_wavGUID_W64_FACT))) { if (pWav->container == ma_dr_wav_container_riff || pWav->container == ma_dr_wav_container_rifx) { ma_uint8 sampleCount[4]; + if (chunkSize < 4) { + return MA_FALSE; + } if (ma_dr_wav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { return MA_FALSE; } @@ -81457,6 +91005,9 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p sampleCountFromFactChunk = 0; } } else if (pWav->container == ma_dr_wav_container_w64) { + if (chunkSize < 8) { + return MA_FALSE; + } if (ma_dr_wav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return MA_FALSE; } @@ -81703,6 +91254,10 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p blockCount += 1; } totalBlockHeaderSizeInBytes = blockCount * (6*fmt.channels); + if (totalBlockHeaderSizeInBytes >= dataChunkSize) { + ma_dr_wav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return MA_FALSE; + } pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; } if (pWav->translatedFormatTag == MA_DR_WAVE_FORMAT_DVI_ADPCM) { @@ -81712,6 +91267,10 @@ MA_PRIVATE ma_bool32 ma_dr_wav_init__internal(ma_dr_wav* pWav, ma_dr_wav_chunk_p blockCount += 1; } totalBlockHeaderSizeInBytes = blockCount * (4*fmt.channels); + if (totalBlockHeaderSizeInBytes >= dataChunkSize) { + ma_dr_wav_free(pWav->pMetadata, &pWav->allocationCallbacks); + return MA_FALSE; + } pWav->totalPCMFrameCount = ((dataChunkSize - totalBlockHeaderSizeInBytes) * 2) / fmt.channels; pWav->totalPCMFrameCount += blockCount; } @@ -82700,6 +92259,49 @@ MA_API ma_bool32 ma_dr_wav_init_memory_write_sequential_pcm_frames(ma_dr_wav* pW } return ma_dr_wav_init_memory_write_sequential(pWav, ppData, pDataSize, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); } +MA_PRIVATE ma_uint32 ma_dr_wav_write_padding(ma_dr_wav* pWav) +{ + ma_uint32 paddingSize = ma_dr_wav_calculate_padding_size(pWav->container, pWav->dataChunkDataSize); + if (paddingSize > 0) { + ma_uint64 paddingData = 0; + ma_dr_wav__write(pWav, &paddingData, paddingSize); + } + return paddingSize; +} +MA_PRIVATE void ma_dr_wav_write_chunk_sizes(ma_dr_wav* pWav) +{ + if (pWav->onSeek != NULL && !pWav->isSequentialWrite) { + if (pWav->container == ma_dr_wav_container_riff) { + if (pWav->onSeek(pWav->pUserData, 4, MA_DR_WAV_SEEK_SET)) { + ma_uint32 riffChunkSize = ma_dr_wav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + ma_dr_wav__write_u32ne_to_le(pWav, riffChunkSize); + } + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, MA_DR_WAV_SEEK_SET)) { + ma_uint32 dataChunkSize = ma_dr_wav__data_chunk_size_riff(pWav->dataChunkDataSize); + ma_dr_wav__write_u32ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == ma_dr_wav_container_w64) { + if (pWav->onSeek(pWav->pUserData, 16, MA_DR_WAV_SEEK_SET)) { + ma_uint64 riffChunkSize = ma_dr_wav__riff_chunk_size_w64(pWav->dataChunkDataSize); + ma_dr_wav__write_u64ne_to_le(pWav, riffChunkSize); + } + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, MA_DR_WAV_SEEK_SET)) { + ma_uint64 dataChunkSize = ma_dr_wav__data_chunk_size_w64(pWav->dataChunkDataSize); + ma_dr_wav__write_u64ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == ma_dr_wav_container_rf64) { + int ds64BodyPos = 12 + 8; + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, MA_DR_WAV_SEEK_SET)) { + ma_uint64 riffChunkSize = ma_dr_wav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); + ma_dr_wav__write_u64ne_to_le(pWav, riffChunkSize); + } + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, MA_DR_WAV_SEEK_SET)) { + ma_uint64 dataChunkSize = ma_dr_wav__data_chunk_size_rf64(pWav->dataChunkDataSize); + ma_dr_wav__write_u64ne_to_le(pWav, dataChunkSize); + } + } + } +} MA_API ma_result ma_dr_wav_uninit(ma_dr_wav* pWav) { ma_result result = MA_SUCCESS; @@ -82707,48 +92309,8 @@ MA_API ma_result ma_dr_wav_uninit(ma_dr_wav* pWav) return MA_INVALID_ARGS; } if (pWav->onWrite != NULL) { - ma_uint32 paddingSize = 0; - if (pWav->container == ma_dr_wav_container_riff || pWav->container == ma_dr_wav_container_rf64) { - paddingSize = ma_dr_wav__chunk_padding_size_riff(pWav->dataChunkDataSize); - } else { - paddingSize = ma_dr_wav__chunk_padding_size_w64(pWav->dataChunkDataSize); - } - if (paddingSize > 0) { - ma_uint64 paddingData = 0; - ma_dr_wav__write(pWav, &paddingData, paddingSize); - } - if (pWav->onSeek && !pWav->isSequentialWrite) { - if (pWav->container == ma_dr_wav_container_riff) { - if (pWav->onSeek(pWav->pUserData, 4, MA_DR_WAV_SEEK_SET)) { - ma_uint32 riffChunkSize = ma_dr_wav__riff_chunk_size_riff(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); - ma_dr_wav__write_u32ne_to_le(pWav, riffChunkSize); - } - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, MA_DR_WAV_SEEK_SET)) { - ma_uint32 dataChunkSize = ma_dr_wav__data_chunk_size_riff(pWav->dataChunkDataSize); - ma_dr_wav__write_u32ne_to_le(pWav, dataChunkSize); - } - } else if (pWav->container == ma_dr_wav_container_w64) { - if (pWav->onSeek(pWav->pUserData, 16, MA_DR_WAV_SEEK_SET)) { - ma_uint64 riffChunkSize = ma_dr_wav__riff_chunk_size_w64(pWav->dataChunkDataSize); - ma_dr_wav__write_u64ne_to_le(pWav, riffChunkSize); - } - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, MA_DR_WAV_SEEK_SET)) { - ma_uint64 dataChunkSize = ma_dr_wav__data_chunk_size_w64(pWav->dataChunkDataSize); - ma_dr_wav__write_u64ne_to_le(pWav, dataChunkSize); - } - } else if (pWav->container == ma_dr_wav_container_rf64) { - int ds64BodyPos = 12 + 8; - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, MA_DR_WAV_SEEK_SET)) { - ma_uint64 riffChunkSize = ma_dr_wav__riff_chunk_size_rf64(pWav->dataChunkDataSize, pWav->pMetadata, pWav->metadataCount); - ma_dr_wav__write_u64ne_to_le(pWav, riffChunkSize); - } - if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, MA_DR_WAV_SEEK_SET)) { - ma_uint64 dataChunkSize = ma_dr_wav__data_chunk_size_rf64(pWav->dataChunkDataSize); - ma_dr_wav__write_u64ne_to_le(pWav, dataChunkSize); - } - } - } if (pWav->isSequentialWrite) { + ma_dr_wav_write_padding(pWav); if (pWav->dataChunkDataSize != pWav->dataChunkDataSizeTargetWrite) { result = MA_INVALID_FILE; } @@ -83011,6 +92573,14 @@ MA_API size_t ma_dr_wav_write_raw(ma_dr_wav* pWav, size_t bytesToWrite, const vo } bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); pWav->dataChunkDataSize += bytesWritten; + if (!pWav->isSequentialWrite) { + ma_uint32 padding; + padding = ma_dr_wav_write_padding(pWav); + ma_dr_wav_write_chunk_sizes(pWav); + if (pWav->onSeek != NULL) { + pWav->onSeek(pWav->pUserData, -(int)padding, MA_DR_WAV_SEEK_END); + } + } return bytesWritten; } MA_API ma_uint64 ma_dr_wav_write_pcm_frames_le(ma_dr_wav* pWav, ma_uint64 framesToWrite, const void* pData) @@ -83426,6 +92996,9 @@ MA_PRIVATE void ma_dr_wav__pcm_to_s16(ma_int16* pOut, const ma_uint8* pIn, size_ sample |= (ma_uint64)(pIn[j]) << shift; shift += 8; } + if (!ma_dr_wav__is_little_endian()) { + sample = ma_dr_wav__bswap64(sample); + } pIn += j; *pOut++ = (ma_int16)((ma_int64)sample >> 48); } @@ -83767,6 +93340,9 @@ MA_PRIVATE void ma_dr_wav__pcm_to_f32(float* pOut, const ma_uint8* pIn, size_t s sample |= (ma_uint64)(pIn[j]) << shift; shift += 8; } + if (!ma_dr_wav__is_little_endian()) { + sample = ma_dr_wav__bswap64(sample); + } pIn += j; *pOut++ = (float)((ma_int64)sample / 9223372036854775807.0); } @@ -84131,6 +93707,9 @@ MA_PRIVATE void ma_dr_wav__pcm_to_s32(ma_int32* pOut, const ma_uint8* pIn, size_ sample |= (ma_uint64)(pIn[j]) << shift; shift += 8; } + if (!ma_dr_wav__is_little_endian()) { + sample = ma_dr_wav__bswap64(sample); + } pIn += j; *pOut++ = (ma_int32)((ma_int64)sample >> 32); } @@ -85065,6 +94644,7 @@ static MA_INLINE ma_bool32 ma_dr_flac_has_sse41(void) #ifndef MA_DR_FLAC_ZERO_OBJECT #define MA_DR_FLAC_ZERO_OBJECT(p) MA_DR_FLAC_ZERO_MEMORY((p), sizeof(*(p))) #endif +#define MA_DR_FLAC_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MA_DR_FLAC_MAX_SIMD_VECTOR_SIZE 64 #define MA_DR_FLAC_SUBFRAME_CONSTANT 0 #define MA_DR_FLAC_SUBFRAME_VERBATIM 1 @@ -88346,7 +97926,6 @@ static ma_bool32 ma_dr_flac__seek_to_pcm_frame__binary_search_internal(ma_dr_fla break; } } else { - const float approxCompressionRatio = (ma_int64)(lastSuccessfulSeekOffset - pFlac->firstFLACFramePosInBytes) / ((ma_int64)(pcmRangeLo * pFlac->channels * pFlac->bitsPerSample)/8.0f); if (pcmRangeLo > pcmFrameIndex) { byteRangeHi = lastSuccessfulSeekOffset; if (byteRangeLo > byteRangeHi) { @@ -88364,11 +97943,12 @@ static ma_bool32 ma_dr_flac__seek_to_pcm_frame__binary_search_internal(ma_dr_fla break; } } else { + const double approxCompressionRatio = (ma_int64)(lastSuccessfulSeekOffset - pFlac->firstFLACFramePosInBytes) / ((ma_int64)(pcmRangeLo * pFlac->channels * pFlac->bitsPerSample)/8.0); byteRangeLo = lastSuccessfulSeekOffset; if (byteRangeHi < byteRangeLo) { byteRangeHi = byteRangeLo; } - targetByte = lastSuccessfulSeekOffset + (ma_uint64)(((ma_int64)((pcmFrameIndex-pcmRangeLo) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * approxCompressionRatio); + targetByte = lastSuccessfulSeekOffset + (ma_uint64)(((ma_int64)((pcmFrameIndex-pcmRangeLo) * pFlac->channels * pFlac->bitsPerSample)/8.0) * approxCompressionRatio); if (targetByte > byteRangeHi) { targetByte = byteRangeHi; } @@ -88649,7 +98229,7 @@ static void* ma_dr_flac__realloc_from_callbacks(void* p, size_t szNew, size_t sz return NULL; } if (p != NULL) { - MA_DR_FLAC_COPY_MEMORY(p2, p, szOld); + MA_DR_FLAC_COPY_MEMORY(p2, p, MA_DR_FLAC_MIN(szNew, szOld)); pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } return p2; @@ -88667,10 +98247,19 @@ static void ma_dr_flac__free_from_callbacks(void* p, const ma_allocation_callbac } static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRead, ma_dr_flac_seek_proc onSeek, ma_dr_flac_tell_proc onTell, ma_dr_flac_meta_proc onMeta, void* pUserData, void* pUserDataMD, ma_uint64* pFirstFramePos, ma_uint64* pSeektablePos, ma_uint32* pSeekpointCount, ma_allocation_callbacks* pAllocationCallbacks) { - ma_uint64 runningFilePos = 42; - ma_uint64 seektablePos = 0; - ma_uint32 seektableSize = 0; - (void)onTell; + ma_uint64 runningFilePos = 42; + ma_uint64 seektablePos = 0; + ma_uint32 seektableSize = 0; + ma_int64 fileSize = 0; + ma_bool32 hasKnownFileSize = MA_FALSE; + if (onTell != NULL && onSeek != NULL) { + if (onSeek(pUserData, 0, MA_DR_FLAC_SEEK_END)) { + if (onTell(pUserData, &fileSize)) { + hasKnownFileSize = MA_TRUE; + } + onSeek(pUserData, (int)runningFilePos, MA_DR_FLAC_SEEK_SET); + } + } for (;;) { ma_dr_flac_metadata metadata; ma_uint8 isLastBlock = 0; @@ -88679,6 +98268,9 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea if (ma_dr_flac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == MA_FALSE) { return MA_FALSE; } + if (hasKnownFileSize && (blockSize > ((ma_uint64)fileSize - runningFilePos))) { + return MA_FALSE; + } runningFilePos += 4; metadata.type = blockType; metadata.rawDataSize = 0; @@ -88767,7 +98359,7 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); return MA_FALSE; } - metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; + metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; metadata.data.vorbis_comment.commentCount = ma_dr_flac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) / sizeof(ma_uint32) < metadata.data.vorbis_comment.commentCount) { ma_dr_flac__free_from_callbacks(pRawData, pAllocationCallbacks); @@ -88900,12 +98492,16 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea } blockSizeRemaining -= 4; metadata.data.picture.mimeLength = ma_dr_flac__be2host_32(metadata.data.picture.mimeLength); + if (blockSizeRemaining < metadata.data.picture.mimeLength) { + result = MA_FALSE; + goto done_flac; + } pMime = (char*)ma_dr_flac__malloc_from_callbacks(metadata.data.picture.mimeLength + 1, pAllocationCallbacks); if (pMime == NULL) { result = MA_FALSE; goto done_flac; } - if (blockSizeRemaining < metadata.data.picture.mimeLength || onRead(pUserData, pMime, metadata.data.picture.mimeLength) != metadata.data.picture.mimeLength) { + if (onRead(pUserData, pMime, metadata.data.picture.mimeLength) != metadata.data.picture.mimeLength) { result = MA_FALSE; goto done_flac; } @@ -88918,12 +98514,16 @@ static ma_bool32 ma_dr_flac__read_and_decode_metadata(ma_dr_flac_read_proc onRea } blockSizeRemaining -= 4; metadata.data.picture.descriptionLength = ma_dr_flac__be2host_32(metadata.data.picture.descriptionLength); + if (blockSizeRemaining < metadata.data.picture.descriptionLength) { + result = MA_FALSE; + goto done_flac; + } pDescription = (char*)ma_dr_flac__malloc_from_callbacks(metadata.data.picture.descriptionLength + 1, pAllocationCallbacks); if (pDescription == NULL) { result = MA_FALSE; goto done_flac; } - if (blockSizeRemaining < metadata.data.picture.descriptionLength || onRead(pUserData, pDescription, metadata.data.picture.descriptionLength) != metadata.data.picture.descriptionLength) { + if (onRead(pUserData, pDescription, metadata.data.picture.descriptionLength) != metadata.data.picture.descriptionLength) { result = MA_FALSE; goto done_flac; } @@ -89882,9 +99482,15 @@ static ma_dr_flac* ma_dr_flac_open_with_metadata_private(ma_dr_flac_read_proc on #endif return NULL; } + if ((0xFFFFFFFF - (seekpointCount * sizeof(ma_dr_flac_seekpoint))) < allocationSize) { + #ifndef MA_DR_FLAC_NO_OGG + ma_dr_flac__free_from_callbacks(pOggbs, &allocationCallbacks); + #endif + return NULL; + } allocationSize += seekpointCount * sizeof(ma_dr_flac_seekpoint); } - pFlac = (ma_dr_flac*)ma_dr_flac__malloc_from_callbacks(allocationSize, &allocationCallbacks); + pFlac = (ma_dr_flac*)ma_dr_flac__malloc_from_callbacks((size_t)allocationSize, &allocationCallbacks); if (pFlac == NULL) { #ifndef MA_DR_FLAC_NO_OGG ma_dr_flac__free_from_callbacks(pOggbs, &allocationCallbacks); @@ -92757,7 +102363,10 @@ MA_API const char* ma_dr_mp3_version_string(void) #define MA_DR_MP3_MIN(a, b) ((a) > (b) ? (b) : (a)) #define MA_DR_MP3_MAX(a, b) ((a) < (b) ? (b) : (a)) #if !defined(MA_DR_MP3_NO_SIMD) -#if !defined(MA_DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) +#if !defined(MA_DR_MP3_ONLY_SIMD) && ((defined(_MSC_VER) && _MSC_VER >= 1400) && defined(_M_X64)) || ((defined(__i386) || defined(_M_IX86) || defined(__i386__) || defined(__x86_64__)) && ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__))) +#define MA_DR_MP3_ONLY_SIMD +#endif +#if !defined(MA_DR_MP3_ONLY_SIMD) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) #define MA_DR_MP3_ONLY_SIMD #endif #if ((defined(_MSC_VER) && _MSC_VER >= 1400) && defined(_M_X64)) || ((defined(__i386) || defined(_M_IX86) || defined(__i386__) || defined(__x86_64__)) && ((defined(_M_IX86_FP) && _M_IX86_FP == 2) || defined(__SSE2__))) @@ -94914,6 +104523,9 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on const ma_uint8* pTagDataBeg; pTagDataBeg = pFirstFrameData + MA_DR_MP3_HDR_SIZE + (bs.pos/8); pTagData = pTagDataBeg; + if (firstFrameInfo.frame_bytes - (size_t)(pTagData - pFirstFrameData) < 8) { + goto done_xing_info; + } isXing = (pTagData[0] == 'X' && pTagData[1] == 'i' && pTagData[2] == 'n' && pTagData[3] == 'g'); isInfo = (pTagData[0] == 'I' && pTagData[1] == 'n' && pTagData[2] == 'f' && pTagData[3] == 'o'); if (isXing || isInfo) { @@ -94921,33 +104533,46 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on ma_uint32 flags = pTagData[7]; pTagData += 8; if (flags & 0x01) { + if (firstFrameInfo.frame_bytes - (size_t)(pTagData - pFirstFrameData) < 4) { + goto done_xing_info; + } detectedMP3FrameCount = (ma_uint32)pTagData[0] << 24 | (ma_uint32)pTagData[1] << 16 | (ma_uint32)pTagData[2] << 8 | (ma_uint32)pTagData[3]; pTagData += 4; } if (flags & 0x02) { + if (firstFrameInfo.frame_bytes - (size_t)(pTagData - pFirstFrameData) < 4) { + goto done_xing_info; + } bytes = (ma_uint32)pTagData[0] << 24 | (ma_uint32)pTagData[1] << 16 | (ma_uint32)pTagData[2] << 8 | (ma_uint32)pTagData[3]; (void)bytes; pTagData += 4; } if (flags & 0x04) { + if (firstFrameInfo.frame_bytes - (size_t)(pTagData - pFirstFrameData) < 100) { + goto done_xing_info; + } pTagData += 100; } if (flags & 0x08) { + if (firstFrameInfo.frame_bytes - (size_t)(pTagData - pFirstFrameData) < 4) { + goto done_xing_info; + } pTagData += 4; } if (pTagData[0]) { + int delayInPCMFrames; + int paddingInPCMFrames; + if (firstFrameInfo.frame_bytes - (size_t)(pTagData - pFirstFrameData) < 36) { + goto done_xing_info; + } pTagData += 21; - if (pTagData - pFirstFrameData + 14 < firstFrameInfo.frame_bytes) { - int delayInPCMFrames; - int paddingInPCMFrames; - delayInPCMFrames = (( (ma_uint32)pTagData[0] << 4) | ((ma_uint32)pTagData[1] >> 4)) + (528 + 1); - paddingInPCMFrames = ((((ma_uint32)pTagData[1] & 0xF) << 8) | ((ma_uint32)pTagData[2] )) - (528 + 1); - if (paddingInPCMFrames < 0) { - paddingInPCMFrames = 0; - } - pMP3->delayInPCMFrames = (ma_uint32)delayInPCMFrames; - pMP3->paddingInPCMFrames = (ma_uint32)paddingInPCMFrames; + delayInPCMFrames = (( (ma_uint32)pTagData[0] << 4) | ((ma_uint32)pTagData[1] >> 4)) + (528 + 1); + paddingInPCMFrames = ((((ma_uint32)pTagData[1] & 0xF) << 8) | ((ma_uint32)pTagData[2] )) - (528 + 1); + if (paddingInPCMFrames < 0) { + paddingInPCMFrames = 0; } + pMP3->delayInPCMFrames = (ma_uint32)delayInPCMFrames; + pMP3->paddingInPCMFrames = (ma_uint32)paddingInPCMFrames; } if (isXing) { pMP3->isVBR = MA_TRUE; @@ -94966,6 +104591,7 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on pMP3->streamCursor = pMP3->streamStartOffset; ma_dr_mp3dec_init(&pMP3->decoder); } + done_xing_info:; } else { } } @@ -94975,7 +104601,7 @@ static ma_bool32 ma_dr_mp3_init_internal(ma_dr_mp3* pMP3, ma_dr_mp3_read_proc on return MA_FALSE; } if (detectedMP3FrameCount != 0xFFFFFFFF) { - pMP3->totalPCMFrameCount = detectedMP3FrameCount * firstFramePCMFrameCount; + pMP3->totalPCMFrameCount = (ma_uint64)detectedMP3FrameCount * firstFramePCMFrameCount; } pMP3->channels = pMP3->mp3FrameChannels; pMP3->sampleRate = pMP3->mp3FrameSampleRate; @@ -95275,7 +104901,7 @@ MA_API ma_uint64 ma_dr_mp3_read_pcm_frames_f32(ma_dr_mp3* pMP3, ma_uint64 frames return ma_dr_mp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); #else { - ma_int16 pTempS16[8192]; + ma_int16 pTempS16[1152*2]; ma_uint64 totalPCMFramesRead = 0; while (totalPCMFramesRead < framesToRead) { ma_uint64 framesJustRead; @@ -95304,7 +104930,7 @@ MA_API ma_uint64 ma_dr_mp3_read_pcm_frames_s16(ma_dr_mp3* pMP3, ma_uint64 frames return ma_dr_mp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); #else { - float pTempF32[4096]; + float pTempF32[1152*2]; ma_uint64 totalPCMFramesRead = 0; while (totalPCMFramesRead < framesToRead) { ma_uint64 framesJustRead; @@ -95635,7 +105261,7 @@ static float* ma_dr_mp3__full_read_and_close_f32(ma_dr_mp3* pMP3, ma_dr_mp3_conf ma_uint64 totalFramesRead = 0; ma_uint64 framesCapacity = 0; float* pFrames = NULL; - float temp[4096]; + float temp[1152*2]; MA_DR_MP3_ASSERT(pMP3 != NULL); for (;;) { ma_uint64 framesToReadRightNow = MA_DR_MP3_COUNTOF(temp) / pMP3->channels; @@ -95688,7 +105314,7 @@ static ma_int16* ma_dr_mp3__full_read_and_close_s16(ma_dr_mp3* pMP3, ma_dr_mp3_c ma_uint64 totalFramesRead = 0; ma_uint64 framesCapacity = 0; ma_int16* pFrames = NULL; - ma_int16 temp[4096]; + ma_int16 temp[1152*2]; MA_DR_MP3_ASSERT(pMP3 != NULL); for (;;) { ma_uint64 framesToReadRightNow = MA_DR_MP3_COUNTOF(temp) / pMP3->channels; From e9b749ec1773baf21b1231d79bb4be66fc3c8acf Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:08:37 +0100 Subject: [PATCH 02/16] fix web build --- src/audio/web/web_audio_system.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/web/web_audio_system.c b/src/audio/web/web_audio_system.c index 479913222..20e31b8ce 100644 --- a/src/audio/web/web_audio_system.c +++ b/src/audio/web/web_audio_system.c @@ -217,7 +217,7 @@ static int32_t webPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prio } slot->ownsDecoder = true; - result = ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, nullptr, &slot->maSound); + result = ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, nullptr, nullptr, &slot->maSound); if (result != MA_SUCCESS) { fprintf(stderr, "Audio: Failed to init sound from decoder for '%s' (error %d)\n", sound->name, result); ma_decoder_uninit(&slot->decoder); From 821c2647c89e012201a1c8179cd8de2b94be460b Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:12:08 +0100 Subject: [PATCH 03/16] restore msvc fix --- vendor/miniaudio/miniaudio.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 1609e75de..0282a5fbb 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -24017,6 +24017,7 @@ typedef struct static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_uint32 loopbackProcessID, ma_bool32 loopbackProcessExclude, ma_IAudioClient** ppAudioClient, ma_WASAPIDeviceInterface** ppDeviceInterface) { +#if !defined(_MSC_VER) || defined(__clang__) ma_result result; ma_bool32 usingProcessLoopback = MA_FALSE; MA_AUDIOCLIENT_ACTIVATION_PARAMS audioclientActivationParams; @@ -24066,6 +24067,9 @@ static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_de } return result; +#else + return MA_BACKEND_NOT_ENABLED; +#endif } From 2af1e976d4f7b0b91de240697e47189880f2ff82 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:13:56 +0100 Subject: [PATCH 04/16] restore haiku fix --- vendor/miniaudio/miniaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 0282a5fbb..19f4f1167 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -18420,7 +18420,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority /* I'm not treating a failure of setting the priority as a critical error so not aborting on failure here. */ if (pthread_attr_setschedparam(&attr, &sched) == 0) { - #if !defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28) + #if (!defined(MA_ANDROID) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28)) && !defined(MA_BEOS) { pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); } From ebe8a83ec027999dd25797114953b29f5bf70f01 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:20:41 +0100 Subject: [PATCH 05/16] werror --- vendor/miniaudio/miniaudio.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 19f4f1167..2d661f802 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -44361,7 +44361,8 @@ static void ma_device_prime_playback_buffer__oss(ma_device* pDevice) break; } - write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + ma_int32 rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + (void)rowsWritten; framesWritten += framesToWriteThisIteration; } } From 96865ed2bc4de49900e2e57b4169187656ee6911 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:21:11 +0100 Subject: [PATCH 06/16] ssize_t --- vendor/miniaudio/miniaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 2d661f802..e80f1ea99 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -44361,7 +44361,7 @@ static void ma_device_prime_playback_buffer__oss(ma_device* pDevice) break; } - ma_int32 rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + ssize_t rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); (void)rowsWritten; framesWritten += framesToWriteThisIteration; } From b62df42a480e1354bb8e5ac2dfa48a874b086d0d Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:22:28 +0100 Subject: [PATCH 07/16] long --- vendor/miniaudio/miniaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index e80f1ea99..49b5612a1 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -44361,7 +44361,7 @@ static void ma_device_prime_playback_buffer__oss(ma_device* pDevice) break; } - ssize_t rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + long rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); (void)rowsWritten; framesWritten += framesToWriteThisIteration; } From 69ec90ad79e344b24c3ee7fa6def73f85e1aac43 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:23:54 +0100 Subject: [PATCH 08/16] ssize_t...again --- vendor/miniaudio/miniaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 49b5612a1..e80f1ea99 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -44361,7 +44361,7 @@ static void ma_device_prime_playback_buffer__oss(ma_device* pDevice) break; } - long rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + ssize_t rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); (void)rowsWritten; framesWritten += framesToWriteThisIteration; } From 1da78b7e193aa6f4d5c39aa3768a56c2fa505dba Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:28:19 +0100 Subject: [PATCH 09/16] crtdll fix --- vendor/miniaudio/miniaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index e80f1ea99..faf8217bc 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -14032,7 +14032,7 @@ This can be reviewed as compatibility issues arise. The preference is to use _wf fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) && !defined(MA_XBOX_NXDK) - #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) && !defined(__CRTDLL__) #define MA_HAS_WFOPEN #endif #endif From a8d97eeab9f817bd1a41e80c11d0dfbe043a4d84 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:34:09 +0100 Subject: [PATCH 10/16] istg --- vendor/miniaudio/miniaudio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index faf8217bc..8387c0b9f 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -30614,7 +30614,7 @@ static void ma_device_prime_playback_buffer__alsa(ma_device* pDevice) /* Guard against the available frame count reported by ALSA just in case we try writing too much and get stuck in snd_pcm_writei().. */ framesAvail = pContextStateALSA->snd_pcm_avail(pDeviceStateALSA->pPCMPlayback); - if (framesToWrite > framesAvail) { + if ((ma_snd_pcm_sframes_t)framesToWrite > framesAvail) { framesToWrite = framesAvail; } From 379ebd3ffec39b3fefc4d9aa87e64530e5f92e19 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:41:04 +0100 Subject: [PATCH 11/16] fuck werror --- src/audio/miniaudio/ma_audio_system.c | 4 ++++ vendor/miniaudio/miniaudio.h | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 2cc4c09a1..3410984a5 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -12,6 +12,10 @@ #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#pragma GCC diagnostic ignored "-Wtype-limits" +#pragma GCC diagnostic ignored "-Werror=sign-compare" #endif #include "miniaudio.h" #if defined(__GNUC__) || defined(__clang__) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 8387c0b9f..81533460d 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -30614,7 +30614,7 @@ static void ma_device_prime_playback_buffer__alsa(ma_device* pDevice) /* Guard against the available frame count reported by ALSA just in case we try writing too much and get stuck in snd_pcm_writei().. */ framesAvail = pContextStateALSA->snd_pcm_avail(pDeviceStateALSA->pPCMPlayback); - if ((ma_snd_pcm_sframes_t)framesToWrite > framesAvail) { + if (framesToWrite > framesAvail) { framesToWrite = framesAvail; } @@ -44361,8 +44361,7 @@ static void ma_device_prime_playback_buffer__oss(ma_device* pDevice) break; } - ssize_t rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); - (void)rowsWritten; + write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); framesWritten += framesToWriteThisIteration; } } From 2651a09361ae41dccbabebf2aaf10aa2fc8ecb71 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:42:14 +0100 Subject: [PATCH 12/16] whoops --- src/audio/miniaudio/ma_audio_system.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 3410984a5..14303fc6e 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -15,7 +15,7 @@ #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #pragma GCC diagnostic ignored "-Wtype-limits" -#pragma GCC diagnostic ignored "-Werror=sign-compare" +#pragma GCC diagnostic ignored "-Wsign-compare" #endif #include "miniaudio.h" #if defined(__GNUC__) || defined(__clang__) From b840709e25e36d3b5bead6de266a3f3da858225c Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:43:42 +0100 Subject: [PATCH 13/16] wno-error when --- src/audio/miniaudio/ma_audio_system.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 14303fc6e..e81fae816 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -16,6 +16,7 @@ #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #pragma GCC diagnostic ignored "-Wtype-limits" #pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wunused-result" #endif #include "miniaudio.h" #if defined(__GNUC__) || defined(__clang__) From db2eec1f5255e6dbd8e06043fb0e863d91a729c0 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 11:59:11 +0100 Subject: [PATCH 14/16] make MA system --- CMakeLists.txt | 4 ++-- src/audio/miniaudio/ma_audio_system.c | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 924d470fa..9cf507aed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,14 +121,14 @@ if(AUDIO_BACKEND STREQUAL "openal") set(AUDIO_LIBRARIES "${OPENAL_LIBRARY}") endif() elseif(AUDIO_BACKEND STREQUAL "miniaudio") - target_include_directories(butterscotch PUBLIC vendor/miniaudio) + target_include_directories(butterscotch SYSTEM PRIVATE vendor/miniaudio) target_include_directories(butterscotch PUBLIC src/audio/miniaudio) add_compile_definitions(USE_MINIAUDIO) if (NOT WIN32) set(AUDIO_LIBRARIES pthread) endif() elseif(AUDIO_BACKEND STREQUAL "web") - target_include_directories(butterscotch PUBLIC vendor/miniaudio) + target_include_directories(butterscotch SYSTEM PRIVATE vendor/miniaudio) target_include_directories(butterscotch PUBLIC src/audio/web) target_include_directories(butterscotch PUBLIC src/audio/miniaudio) add_compile_definitions(USE_WEB_AUDIO) diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index e81fae816..2cc4c09a1 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -12,11 +12,6 @@ #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic ignored "-Wunused-but-set-variable" -#pragma GCC diagnostic ignored "-Wtype-limits" -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-result" #endif #include "miniaudio.h" #if defined(__GNUC__) || defined(__clang__) From 1624a4efb5ce8ec0998906be969bcd3417257249 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 12:00:34 +0100 Subject: [PATCH 15/16] public --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cf507aed..42f125417 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,14 +121,14 @@ if(AUDIO_BACKEND STREQUAL "openal") set(AUDIO_LIBRARIES "${OPENAL_LIBRARY}") endif() elseif(AUDIO_BACKEND STREQUAL "miniaudio") - target_include_directories(butterscotch SYSTEM PRIVATE vendor/miniaudio) + target_include_directories(butterscotch SYSTEM PUBLIC vendor/miniaudio) target_include_directories(butterscotch PUBLIC src/audio/miniaudio) add_compile_definitions(USE_MINIAUDIO) if (NOT WIN32) set(AUDIO_LIBRARIES pthread) endif() elseif(AUDIO_BACKEND STREQUAL "web") - target_include_directories(butterscotch SYSTEM PRIVATE vendor/miniaudio) + target_include_directories(butterscotch SYSTEM PUBLIC vendor/miniaudio) target_include_directories(butterscotch PUBLIC src/audio/web) target_include_directories(butterscotch PUBLIC src/audio/miniaudio) add_compile_definitions(USE_WEB_AUDIO) From 4af635aa47f70bb67f25a12e74bd16f4272085ee Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Sat, 18 Jul 2026 12:16:57 +0100 Subject: [PATCH 16/16] fix makefile build, either that or we ignore unused-result --- vendor/miniaudio/miniaudio.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 81533460d..faf8217bc 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -44361,7 +44361,8 @@ static void ma_device_prime_playback_buffer__oss(ma_device* pDevice) break; } - write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + ssize_t rowsWritten = write(pDeviceStateOSS->fdPlayback, buffer, framesToWriteThisIteration * bpf); + (void)rowsWritten; framesWritten += framesToWriteThisIteration; } }