From f83fe571cdf4f696f167310528e4648db2ca77c2 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Tue, 9 Jan 2024 04:08:08 -0500 Subject: [PATCH 01/32] Update Audio API to support surround sound audio playback. Only effective for Windows (WASAPI) at the moment. --- src/matoya.h | 7 +++++- src/unix/apple/audio.c | 4 +++- src/unix/linux/android/audio.c | 4 +++- src/unix/linux/x11/audio.c | 4 +++- src/unix/web/matoya-worker.js | 2 +- src/windows/audio.c | 40 ++++++++++++++++++++++++++++++---- 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index bdebf9d27..0fd32966e 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1442,6 +1442,11 @@ typedef struct MTY_Resampler MTY_Resampler; /// before audio begins getting dropped. The queue will flush to zero, then begin /// building back towards `minBuffer` again before playback resumes. /// @param channels Number of audio channels. +/// @param channelsMask Bitmask defining the configuration of channels. +/// Follows standard surround sound speaker ids (see +/// https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels). +/// If set to 0, then the implementation is free to assume whatever default that it desires. +/// For `channels` > 2, specify this value correctly in order to yield accurate audio playback. /// @param deviceID Specify a specific audio device for playback, or NULL for the default /// device. Windows only. /// @param fallback If `deviceID` is not NULL, set to true to fallback to the default device, or @@ -1450,7 +1455,7 @@ typedef struct MTY_Resampler MTY_Resampler; /// The returned MTY_Audio context must be destroyed with MTY_AudioDestroy. MTY_EXPORT MTY_Audio * MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - const char *deviceID, bool fallback); + uint32_t channelsMask, const char *deviceID, bool fallback); /// @brief Destroy an MTY_Audio context. /// @param audio Passed by reference and set to NULL after being destroyed. diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 314e68700..a2b369b8c 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -32,8 +32,10 @@ static void audio_queue_callback(void *opaque, AudioQueueRef q, AudioQueueBuffer } MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - const char *deviceID, bool fallback) + uint32_t channelsMask, const char *deviceID, bool fallback) { + channelsMask; // XXX: To be implemented + // TODO Should this use the current run loop rather than internal threading? MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index c9a749053..0754585c9 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -61,8 +61,10 @@ static aaudio_data_callback_result_t audio_callback(AAudioStream *stream, void * } MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - const char *deviceID, bool fallback) + uint32_t channelsMask, const char *deviceID, bool fallback) { + channelsMask; + MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); ctx->channels = channels; ctx->sample_rate = sampleRate; diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 927f1a205..70da95414 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -27,8 +27,10 @@ struct MTY_Audio { }; MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - const char *deviceID, bool fallback) + uint32_t channelsMask, const char *deviceID, bool fallback) { + channelsMask; + if (!libasound_global_init()) return NULL; diff --git a/src/unix/web/matoya-worker.js b/src/unix/web/matoya-worker.js index 1dff330e4..fd03b9ab9 100644 --- a/src/unix/web/matoya-worker.js +++ b/src/unix/web/matoya-worker.js @@ -313,7 +313,7 @@ function mty_mutex_unlock(mutex, index, notify) { } const MTY_AUDIO_API = { - MTY_AudioCreate: function (sampleRate, minBuffer, maxBuffer, channels, deviceID, fallback) { + MTY_AudioCreate: function (sampleRate, minBuffer, maxBuffer, channels, channelsMask, deviceID, fallback) { MTY.audio = { sampleRate, minBuffer, diff --git a/src/windows/audio.c b/src/windows/audio.c index 86b346562..3b699804b 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -29,7 +29,9 @@ struct MTY_Audio { uint32_t sample_rate; uint32_t min_buffer; uint32_t max_buffer; + uint32_t channels_mask; uint8_t channels; + WORD bytes_per_sample; WCHAR *device_id; UINT32 buffer_size; IMMDeviceEnumerator *enumerator; @@ -173,6 +175,26 @@ static HRESULT audio_get_extended_format(IMMDevice *device, WAVEFORMATEXTENSIBLE return e; } +static HRESULT audio_get_extended_format2(IAudioClient *client, WAVEFORMATEXTENSIBLE *pwfx) +{ + WAVEFORMATEXTENSIBLE *ptfx = NULL; + + HRESULT e = IAudioClient_GetMixFormat(client, (WAVEFORMATEX **) &ptfx); + if (e != S_OK || !ptfx) { + MTY_Log("IAudioClient_GetMixFormat failed with HRESULT 0x%X", e); + goto except; + } + + *pwfx = *ptfx; + + except: + + if (ptfx) + CoTaskMemFree(ptfx); + + return e; +} + static HRESULT audio_device_create(MTY_Audio *ctx) { HRESULT e = S_OK; @@ -212,20 +234,29 @@ static HRESULT audio_device_create(MTY_Audio *ctx) // We must query extended data for greater than two channels if (ctx->channels > 2) { - e = audio_get_extended_format(device, &pwfx); + // TODO: Ideally, we always use audio_get_extended_format2, but splitting for now so as not to break PS5 stuff + if (ctx->channels_mask) { + e = audio_get_extended_format2(ctx->client, &pwfx); + + } else { + e = audio_get_extended_format(device, &pwfx); + } + if (e != S_OK) goto except; } e = IAudioClient_Initialize(ctx->client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, - AUDIO_BUFFER_SIZE, 0, &pwfx.Format, NULL); + AUDIO_BUFFER_SIZE, 0, (const WAVEFORMATEX *) &pwfx, NULL); if (e != S_OK) { MTY_Log("'IAudioClient_Initialize' failed with HRESULT 0x%X", e); goto except; } + ctx->bytes_per_sample = pwfx.Format.wBitsPerSample / 8; + e = IAudioClient_GetBufferSize(ctx->client, &ctx->buffer_size); if (e != S_OK) { MTY_Log("'IAudioClient_GetBufferSize' failed with HRESULT 0x%X", e); @@ -250,10 +281,11 @@ static HRESULT audio_device_create(MTY_Audio *ctx) } MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - const char *deviceID, bool fallback) + uint32_t channelsMask, const char *deviceID, bool fallback) { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); ctx->sample_rate = sampleRate; + ctx->channels_mask = channelsMask; ctx->channels = channels; ctx->fallback = fallback; @@ -420,7 +452,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) HRESULT e = IAudioRenderClient_GetBuffer(ctx->render, count, &buffer); if (e == S_OK) { - memcpy(buffer, frames, count * ctx->channels * AUDIO_SAMPLE_SIZE); + memcpy(buffer, frames, count * ctx->channels * ctx->bytes_per_sample); IAudioRenderClient_ReleaseBuffer(ctx->render, count, 0); } From 0d6be6cc837717dff0367da2dec4fb65251334ef Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Tue, 9 Jan 2024 18:02:27 -0500 Subject: [PATCH 02/32] TEST: Replace IMMDevice_OpenPropertyStore with IAudioClient_GetMixFormat for both multi-channel audio and PS5 haptics audio. --- src/windows/audio.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/windows/audio.c b/src/windows/audio.c index 3b699804b..7bf886743 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -234,13 +234,14 @@ static HRESULT audio_device_create(MTY_Audio *ctx) // We must query extended data for greater than two channels if (ctx->channels > 2) { - // TODO: Ideally, we always use audio_get_extended_format2, but splitting for now so as not to break PS5 stuff - if (ctx->channels_mask) { - e = audio_get_extended_format2(ctx->client, &pwfx); - - } else { - e = audio_get_extended_format(device, &pwfx); - } + // TODO: Ensure PS5 haptics works fine with this change. If yes just use this. Otherwise, bring back the commented out branch below + e = audio_get_extended_format2(ctx->client, &pwfx); + // if (ctx->channels_mask) { + // e = audio_get_extended_format2(ctx->client, &pwfx); + + // } else { + // e = audio_get_extended_format(device, &pwfx); + // } if (e != S_OK) goto except; From 3cbcba73abe45b62ea473fb4e68c17c42eff13db Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Fri, 12 Jan 2024 18:05:52 -0500 Subject: [PATCH 03/32] Windows WASAPI device enumeration should handle empty string device IDs as a signal for choosing the default audio device. --- src/windows/audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/audio.c b/src/windows/audio.c index 7bf886743..da74028b8 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -200,7 +200,7 @@ static HRESULT audio_device_create(MTY_Audio *ctx) HRESULT e = S_OK; IMMDevice *device = NULL; - if (ctx->device_id) { + if (ctx->device_id && ctx->device_id[0]) { e = IMMDeviceEnumerator_GetDevice(ctx->enumerator, ctx->device_id, &device); if (e != S_OK && !ctx->fallback) { From f7fa9bcf20fbd53bf000b233b414288983ef0f76 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Sat, 13 Jan 2024 01:11:48 -0500 Subject: [PATCH 04/32] MTY_AudioCreate now takes a new struct MTY_AudioFormat which is the specification of the audio format the device should initialize for playback. --- src/matoya.h | 45 ++++++++++++++++++---------- src/unix/apple/audio.c | 17 +++++------ src/unix/linux/android/audio.c | 12 ++++---- src/unix/linux/x11/audio.c | 16 +++++----- src/unix/web/matoya-worker.js | 4 +-- src/windows/audio.c | 55 ++++++++++++++++++++-------------- 6 files changed, 84 insertions(+), 65 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index 0fd32966e..2a9b5eb60 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1427,26 +1427,41 @@ MTY_WaitPtr(int32_t *sync); //- #module Audio //- #mbrief Simple audio playback and resampling. -//- #mdetails This is a very minimal interface that assumes 2-channel, 16-bit signed PCM +//- #mdetails This is a very minimal interface that supports multi-channel PCM audio //- submitted by pushing to a queue. This module also includes a straightforward -//- resampler. +//- resampler for 2-channel, 16-bit signed PCM audio. typedef struct MTY_Audio MTY_Audio; typedef struct MTY_Resampler MTY_Resampler; +/// @brief Audio sample formats. Currently only PCM formats. +typedef enum { + MTY_AUDIO_SAMPLE_FORMAT_UNKNOWN = 0, ///< Unknown sample format. + MTY_AUDIO_SAMPLE_FORMAT_FLOAT = 1, ///< 32-bit floating point. + MTY_AUDIO_SAMPLE_FORMAT_INT16 = 2, ///< 16-bit signed integer. +} MTY_AudioSampleFormat; + +/// @brief Format description for an audio device. +typedef struct { + MTY_AudioSampleFormat sampleFormat; ///< Format of audio samples. + uint32_t sampleRate; ///< Number of audio samples per second. Usually set to 48000. + uint32_t channels; ///< Number of audio channels. + uint32_t channelsMask; ///< Opaque bitmask that defines which channels are present in the audio data. + ///< Follows standard surround sound speaker ids (see + ///< https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels). + ///< For `channels` > 2, specify this value correctly in order + ///< to yield accurate audio playback. +} MTY_AudioFormat; + /// @brief Create an MTY_Audio context for playback. -/// @param sampleRate Audio sample rate in KHz. +/// @param format Format that the audio device will attempt to initialize. If the +/// provided format is supported, the function will fail and return NULL. +/// This parameter is a required argument and MUST NOT be NULL. /// @param minBuffer The minimum amount of audio in milliseconds that must be queued /// before playback begins. /// @param maxBuffer The maximum amount of audio in milliseconds that can be queued /// before audio begins getting dropped. The queue will flush to zero, then begin /// building back towards `minBuffer` again before playback resumes. -/// @param channels Number of audio channels. -/// @param channelsMask Bitmask defining the configuration of channels. -/// Follows standard surround sound speaker ids (see -/// https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels). -/// If set to 0, then the implementation is free to assume whatever default that it desires. -/// For `channels` > 2, specify this value correctly in order to yield accurate audio playback. /// @param deviceID Specify a specific audio device for playback, or NULL for the default /// device. Windows only. /// @param fallback If `deviceID` is not NULL, set to true to fallback to the default device, or @@ -1454,8 +1469,7 @@ typedef struct MTY_Resampler MTY_Resampler; /// @returns On failure, NULL is returned. Call MTY_GetLog for details.\n\n /// The returned MTY_Audio context must be destroyed with MTY_AudioDestroy. MTY_EXPORT MTY_Audio * -MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - uint32_t channelsMask, const char *deviceID, bool fallback); +MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback); /// @brief Destroy an MTY_Audio context. /// @param audio Passed by reference and set to NULL after being destroyed. @@ -1477,11 +1491,12 @@ MTY_AudioGetQueued(MTY_Audio *ctx); /// @brief Queue 16-bit signed PCM audio for playback. /// @param ctx An MTY_Audio context. -/// @param frames Buffer containing 16-bit signed PCM audio frames. The number of audio channels -/// is specified during MTY_AudioCreate. In the case of 2-channel PCM, one audio frame is two -/// samples, each sample being one channel. +/// @param frames Buffer containing PCM audio frames as per the format (channels, sample rate, etc) +/// that was specified during MTY_AudioCreate. A frame is an interleaving of 1 sample per channel. +/// For example, in the case of 2-channel/stereo audio, one audio frame is two samples, each +/// sample being one channel. /// @param count The number of frames contained in `frames`. The number of frames would -/// be the size of `frames` in bytes divided by 2 * `channels` specified during MTY_AudioCreate. +/// be the size of `frames` in bytes divided by sample size * `channels` specified during MTY_AudioCreate. MTY_EXPORT void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count); diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index a2b369b8c..6cbee51a6 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -31,27 +31,26 @@ static void audio_queue_callback(void *opaque, AudioQueueRef q, AudioQueueBuffer buf->mAudioDataByteSize = 0; } -MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - uint32_t channelsMask, const char *deviceID, bool fallback) +MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, uint32_t maxBuffer, + const char *deviceID, bool fallback) { - channelsMask; // XXX: To be implemented - // TODO Should this use the current run loop rather than internal threading? MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_rate = sampleRate; - ctx->channels = channels; + ctx->sample_rate = format_in->sampleRate; + ctx->channels = format_in->channels; - uint32_t frames_per_ms = lrint((float) sampleRate / 1000.0f); + uint32_t frames_per_ms = lrint((float) format_in->sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms; ctx->max_buffer = maxBuffer * frames_per_ms; + // TODO: Need to handle all the supported possibles of MTY_AudioFormat, such as 32-bit float data, more than 2 channels, etc. AudioStreamBasicDescription format = {0}; - format.mSampleRate = sampleRate; + format.mSampleRate = format_in->sampleRate; format.mFormatID = kAudioFormatLinearPCM; format.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; format.mFramesPerPacket = 1; - format.mChannelsPerFrame = channels; + format.mChannelsPerFrame = format_in->channels; format.mBitsPerChannel = AUDIO_SAMPLE_SIZE * 8; format.mBytesPerPacket = AUDIO_SAMPLE_SIZE * format.mChannelsPerFrame; format.mBytesPerFrame = format.mBytesPerPacket; diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 0754585c9..30e5c6559 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -60,18 +60,16 @@ static aaudio_data_callback_result_t audio_callback(AAudioStream *stream, void * return AAUDIO_CALLBACK_RESULT_CONTINUE; } -MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - uint32_t channelsMask, const char *deviceID, bool fallback) +MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, + uint32_t maxBuffer, const char *deviceID, bool fallback) { - channelsMask; - MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->channels = channels; - ctx->sample_rate = sampleRate; + ctx->channels = format->channels; + ctx->sample_rate = format->sampleRate; ctx->mutex = MTY_MutexCreate(); ctx->buffer = MTY_Alloc(AUDIO_BUF_SIZE(ctx), 1); - uint32_t frames_per_ms = lrint((float) sampleRate / 1000.0f); + uint32_t frames_per_ms = lrint((float) format->sample_rate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE; ctx->max_buffer = maxBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE; diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 70da95414..5123db049 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -26,19 +26,17 @@ struct MTY_Audio { size_t pos; }; -MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - uint32_t channelsMask, const char *deviceID, bool fallback) +MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, + uint32_t maxBuffer, const char *deviceID, bool fallback) { - channelsMask; - if (!libasound_global_init()) return NULL; MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_rate = sampleRate; - ctx->channels = channels; + ctx->sample_rate = format->sampleRate; + ctx->channels = format->channels; - uint32_t frames_per_ms = lrint((float) sampleRate / 1000.0f); + uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms; ctx->max_buffer = maxBuffer * frames_per_ms; @@ -57,8 +55,8 @@ MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t max snd_pcm_hw_params_set_access(ctx->pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(ctx->pcm, params, SND_PCM_FORMAT_S16); - snd_pcm_hw_params_set_channels(ctx->pcm, params, channels); - snd_pcm_hw_params_set_rate(ctx->pcm, params, sampleRate, 0); + snd_pcm_hw_params_set_channels(ctx->pcm, params, format->channels); + snd_pcm_hw_params_set_rate(ctx->pcm, params, format->sampleRate, 0); snd_pcm_hw_params(ctx->pcm, params); snd_pcm_nonblock(ctx->pcm, 1); diff --git a/src/unix/web/matoya-worker.js b/src/unix/web/matoya-worker.js index fd03b9ab9..842040eb9 100644 --- a/src/unix/web/matoya-worker.js +++ b/src/unix/web/matoya-worker.js @@ -313,9 +313,9 @@ function mty_mutex_unlock(mutex, index, notify) { } const MTY_AUDIO_API = { - MTY_AudioCreate: function (sampleRate, minBuffer, maxBuffer, channels, channelsMask, deviceID, fallback) { + MTY_AudioCreate: function (format, minBuffer, maxBuffer, deviceID, fallback) { MTY.audio = { - sampleRate, + sampleRate: format.sampleRate, minBuffer, maxBuffer, channels, diff --git a/src/windows/audio.c b/src/windows/audio.c index da74028b8..d00c3b608 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -14,23 +14,26 @@ DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0 DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4C, 0xDBFA, 0x4C32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2); DEFINE_GUID(IID_IAudioRenderClient, 0xF294ACFC, 0x3146, 0x4483, 0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2); DEFINE_GUID(IID_IMMNotificationClient, 0x7991EEC9, 0x7E89, 0x4D85, 0x83, 0x90, 0x6C, 0x70, 0x3C, 0xEC, 0x60, 0xC0); +DEFINE_GUID(OWN_KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +DEFINE_GUID(OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #define COBJMACROS #include #include -#define AUDIO_SAMPLE_SIZE sizeof(int16_t) +#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) #define AUDIO_BUFFER_SIZE ((1 * 1000 * 1000 * 1000) / 100) // 1 second struct MTY_Audio { bool playing; bool notification_init; bool fallback; + MTY_AudioSampleFormat sample_format; uint32_t sample_rate; uint32_t min_buffer; uint32_t max_buffer; uint32_t channels_mask; - uint8_t channels; + uint32_t channels; WORD bytes_per_sample; WCHAR *device_id; UINT32 buffer_size; @@ -223,28 +226,33 @@ static HRESULT audio_device_create(MTY_Audio *ctx) goto except; } + WORD sample_size = AUDIO_SAMPLE_SIZE(ctx->sample_format); + WAVEFORMATEXTENSIBLE pwfx = { .Format.wFormatTag = WAVE_FORMAT_PCM, - .Format.nChannels = ctx->channels, + .Format.nChannels = (WORD) ctx->channels, .Format.nSamplesPerSec = ctx->sample_rate, - .Format.wBitsPerSample = AUDIO_SAMPLE_SIZE * 8, - .Format.nBlockAlign = ctx->channels * AUDIO_SAMPLE_SIZE, - .Format.nAvgBytesPerSec = ctx->sample_rate * ctx->channels * AUDIO_SAMPLE_SIZE, + .Format.wBitsPerSample = sample_size * 8, + .Format.nBlockAlign = (WORD) ctx->channels * sample_size, + .Format.nAvgBytesPerSec = ctx->sample_rate * ctx->channels * sample_size, }; - // We must query extended data for greater than two channels - if (ctx->channels > 2) { - // TODO: Ensure PS5 haptics works fine with this change. If yes just use this. Otherwise, bring back the commented out branch below - e = audio_get_extended_format2(ctx->client, &pwfx); - // if (ctx->channels_mask) { - // e = audio_get_extended_format2(ctx->client, &pwfx); + // TODO: This is a bit of a hacky thing to make PS5 haptics work...clean it up! + if (ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_UNKNOWN) { + // We must query extended data for greater than two channels + if (ctx->channels > 2) { + e = audio_get_extended_format(device, &pwfx); + if (e != S_OK) + goto except; + } - // } else { - // e = audio_get_extended_format(device, &pwfx); - // } + } else if (ctx->channels > 2 || (ctx->channels_mask && ctx->channels_mask != KSAUDIO_SPEAKER_STEREO) || ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT) { + pwfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pwfx.Format.cbSize = 22; - if (e != S_OK) - goto except; + pwfx.Samples.wValidBitsPerSample = pwfx.Format.wBitsPerSample; + pwfx.dwChannelMask = ctx->channels_mask; + pwfx.SubFormat = ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : OWN_KSDATAFORMAT_SUBTYPE_PCM; } e = IAudioClient_Initialize(ctx->client, AUDCLNT_SHAREMODE_SHARED, @@ -281,16 +289,17 @@ static HRESULT audio_device_create(MTY_Audio *ctx) return e; } -MTY_Audio *MTY_AudioCreate(uint32_t sampleRate, uint32_t minBuffer, uint32_t maxBuffer, uint8_t channels, - uint32_t channelsMask, const char *deviceID, bool fallback) +MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, + uint32_t maxBuffer, const char *deviceID, bool fallback) { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_rate = sampleRate; - ctx->channels_mask = channelsMask; - ctx->channels = channels; + ctx->sample_format = format->sampleFormat; + ctx->sample_rate = format->sampleRate; + ctx->channels_mask = format->channelsMask; + ctx->channels = format->channels; ctx->fallback = fallback; - uint32_t frames_per_ms = lrint((float) sampleRate / 1000.0f); + uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms; ctx->max_buffer = maxBuffer * frames_per_ms; From df0b5a0c10213e5b3f39e40b64f1b974ab33c916 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Mon, 22 Jan 2024 02:31:14 -0500 Subject: [PATCH 05/32] Broke PS5 haptics. Trying to fix it. --- src/matoya.h | 1 - src/windows/audio.c | 20 +++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index 2a9b5eb60..7830ab8ca 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1436,7 +1436,6 @@ typedef struct MTY_Resampler MTY_Resampler; /// @brief Audio sample formats. Currently only PCM formats. typedef enum { - MTY_AUDIO_SAMPLE_FORMAT_UNKNOWN = 0, ///< Unknown sample format. MTY_AUDIO_SAMPLE_FORMAT_FLOAT = 1, ///< 32-bit floating point. MTY_AUDIO_SAMPLE_FORMAT_INT16 = 2, ///< 16-bit signed integer. } MTY_AudioSampleFormat; diff --git a/src/windows/audio.c b/src/windows/audio.c index d00c3b608..58535529a 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -237,22 +237,20 @@ static HRESULT audio_device_create(MTY_Audio *ctx) .Format.nAvgBytesPerSec = ctx->sample_rate * ctx->channels * sample_size, }; - // TODO: This is a bit of a hacky thing to make PS5 haptics work...clean it up! - if (ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_UNKNOWN) { - // We must query extended data for greater than two channels - if (ctx->channels > 2) { + if (ctx->channels > 2) { + if (!ctx->channels_mask) { e = audio_get_extended_format(device, &pwfx); if (e != S_OK) goto except; - } - } else if (ctx->channels > 2 || (ctx->channels_mask && ctx->channels_mask != KSAUDIO_SPEAKER_STEREO) || ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT) { - pwfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - pwfx.Format.cbSize = 22; + } else { + pwfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + pwfx.Format.cbSize = 22; - pwfx.Samples.wValidBitsPerSample = pwfx.Format.wBitsPerSample; - pwfx.dwChannelMask = ctx->channels_mask; - pwfx.SubFormat = ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : OWN_KSDATAFORMAT_SUBTYPE_PCM; + pwfx.Samples.wValidBitsPerSample = pwfx.Format.wBitsPerSample; + pwfx.dwChannelMask = ctx->channels_mask; + pwfx.SubFormat = ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : OWN_KSDATAFORMAT_SUBTYPE_PCM; + } } e = IAudioClient_Initialize(ctx->client, AUDCLNT_SHAREMODE_SHARED, From ca24407403a5782c9ef7f9beb3653e069bc4e16f Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Thu, 25 Jan 2024 08:52:06 -0500 Subject: [PATCH 06/32] MacOS playback of multichannel audio. --- src/unix/apple/audio.c | 203 +++++++++++++++++++++++++++++++++++------ 1 file changed, 176 insertions(+), 27 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 6cbee51a6..c0ecd785b 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -4,22 +4,28 @@ #include "matoya.h" +#include #include -#define AUDIO_SAMPLE_SIZE sizeof(int16_t) +#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) #define AUDIO_BUFS 64 #define AUDIO_BUF_SIZE(ctx) \ - ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE) + ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE((ctx)->sample_format)) + +#define AUDIO_HW_ERROR(e) ((e) != kAudioHardwareNoError) +#define AUDIO_SV_ERROR(e) ((e) != kAudioServicesNoError) struct MTY_Audio { AudioQueueRef q; AudioQueueBufferRef audio_buf[AUDIO_BUFS]; MTY_Atomic32 in_use[AUDIO_BUFS]; + MTY_AudioSampleFormat sample_format; uint32_t sample_rate; uint32_t min_buffer; uint32_t max_buffer; uint8_t channels; + uint32_t channelsMask; bool playing; }; @@ -31,39 +37,153 @@ static void audio_queue_callback(void *opaque, AudioQueueRef q, AudioQueueBuffer buf->mAudioDataByteSize = 0; } -MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, uint32_t maxBuffer, - const char *deviceID, bool fallback) +static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPropertyScope prop_scope, AudioObjectPropertyElement prop_element, char **out_uid, CFStringRef *out_uid_cf) { - // TODO Should this use the current run loop rather than internal threading? + OSStatus e = kAudioHardwareNoError; + char *uid = NULL; + CFStringRef uid_cf = NULL; + + UInt32 data_size = sizeof(CFStringRef); + AudioObjectPropertyAddress propAddr = { + .mSelector = kAudioDevicePropertyDeviceUID, + .mScope = prop_scope, + .mElement = prop_element, + }; + + e = AudioObjectGetPropertyData(device, &propAddr, 0, NULL, &data_size, &uid_cf); + if (AUDIO_HW_ERROR(e)) + goto except; - MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_rate = format_in->sampleRate; - ctx->channels = format_in->channels; + UInt32 prop_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(uid_cf), kCFStringEncodingUTF8); + uid = calloc(1, prop_size + 1); + if (!CFStringGetCString(uid_cf, uid, prop_size + 1, kCFStringEncodingUTF8)) { + e = kAudioHardwareBadDeviceError; + goto except; + } - uint32_t frames_per_ms = lrint((float) format_in->sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * frames_per_ms; - ctx->max_buffer = maxBuffer * frames_per_ms; + // OK + *out_uid = uid; + *out_uid_cf = uid_cf; + + except: + + if (AUDIO_HW_ERROR(e)) { + if (uid_cf) + CFRelease(uid_cf); + free(uid); + } + + return e; +} + +static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) +{ + AudioObjectID *selected_device = NULL; + CFStringRef selected_device_uid = NULL; + OSStatus e = kAudioHardwareNoError; + + // Enumerate all output devices and identify the given device + AudioObjectID *device_ids = NULL; + + bool default_dev = !deviceID || !deviceID[0]; + + AudioObjectPropertyAddress propAddr = { + .mSelector = default_dev ? kAudioHardwarePropertyDefaultOutputDevice : kAudioHardwarePropertyDevices, + .mScope = kAudioObjectPropertyScopeOutput, + .mElement = kAudioObjectPropertyElementWildcard, + }; + + UInt32 data_size = 0; + e = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAddr, 0, NULL, &data_size); + if (AUDIO_HW_ERROR(e)) + goto except; + + device_ids = calloc(1, data_size); + e = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAddr, 0, NULL, &data_size, device_ids); + if (AUDIO_HW_ERROR(e)) + goto except; + + uint32_t n = (uint32_t) (data_size / sizeof(AudioObjectID)); + + if (default_dev) { + if (n != 1) { + e = kAudioHardwareBadDeviceError; + goto except; + } + + selected_device = device_ids; + + } else { + // Goal: find the AudioObjectID of the device whose unique string ID matches the given `deviceID` parameter + for (uint32_t i = 0; !selected_device && i < n; i++) { + char *uid = NULL; + CFStringRef uid_cf = NULL; + + if (AUDIO_HW_ERROR(audio_object_get_device_uid(device_ids[i], propAddr.mScope, propAddr.mElement, &uid, &uid_cf))) + continue; + + if (!strcmp(deviceID, uid)) { + selected_device = &device_ids[i]; + selected_device_uid = uid_cf; + uid_cf = NULL; + } + + if (uid_cf) + CFRelease(uid_cf); + free(uid); + } + } + + if (!selected_device) { + e = kAudioHardwareBadDeviceError; + goto except; + } + + // Initialize the selected device - // TODO: Need to handle all the supported possibles of MTY_AudioFormat, such as 32-bit float data, more than 2 channels, etc. AudioStreamBasicDescription format = {0}; - format.mSampleRate = format_in->sampleRate; + format.mSampleRate = ctx->sample_rate; format.mFormatID = kAudioFormatLinearPCM; - format.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + format.mFormatFlags = (ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsPacked; format.mFramesPerPacket = 1; - format.mChannelsPerFrame = format_in->channels; - format.mBitsPerChannel = AUDIO_SAMPLE_SIZE * 8; - format.mBytesPerPacket = AUDIO_SAMPLE_SIZE * format.mChannelsPerFrame; + format.mChannelsPerFrame = ctx->channels; + format.mBitsPerChannel = AUDIO_SAMPLE_SIZE(ctx->sample_format) * 8; + format.mBytesPerPacket = AUDIO_SAMPLE_SIZE(ctx->sample_format) * format.mChannelsPerFrame; format.mBytesPerFrame = format.mBytesPerPacket; - OSStatus e = AudioQueueNewOutput(&format, audio_queue_callback, ctx, NULL, NULL, 0, &ctx->q); - if (e != kAudioServicesNoError) { + // Create a new audio queue, which by default chooses the device's default device + e = AudioQueueNewOutput(&format, audio_queue_callback, ctx, NULL, NULL, 0, &ctx->q); + if (AUDIO_SV_ERROR(e)) { MTY_Log("'AudioQueueNewOutput' failed with error 0x%X", e); goto except; } + // Change the audio queue to be associated with the selected audio device + if (selected_device_uid) { + e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_CurrentDevice, (const void *) &selected_device_uid, sizeof(CFStringRef)); + if (AUDIO_SV_ERROR(e)) { + MTY_Log("'AudioQueueSetProperty(kAudioQueueProperty_CurrentDevice)' failed with error 0x%X", e); + goto except; + } + } + + // Specify channel configuration + if (ctx->channelsMask) { + AudioChannelLayout channel_layout = { + .mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap, + .mChannelBitmap = ctx->channelsMask, // Core Audio channel bitmap follows the spec that the WAVE format follows, so we can simply pass in the mask as-is + }; + + e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_ChannelLayout, (const void *) &channel_layout, sizeof(AudioChannelLayout)); + if (AUDIO_SV_ERROR(e)) { + MTY_Log("'AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)' failed with error 0x%X", e); + goto except; + } + } + for (int32_t x = 0; x < AUDIO_BUFS; x++) { e = AudioQueueAllocateBuffer(ctx->q, AUDIO_BUF_SIZE(ctx), &ctx->audio_buf[x]); - if (e != kAudioServicesNoError) { + if (AUDIO_SV_ERROR(e)) { MTY_Log("'AudioQueueAllocateBuffer' failed with error 0x%X", e); goto except; } @@ -71,7 +191,36 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, except: - if (e != kAudioServicesNoError) + if (selected_device_uid) + CFRelease(selected_device_uid); + free(device_ids); + + return e; +} + +MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, uint32_t maxBuffer, + const char *deviceID, bool fallback) +{ + // TODO Should this use the current run loop rather than internal threading? + + MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); + ctx->sample_format = format_in->sampleFormat; + ctx->sample_rate = format_in->sampleRate; + ctx->channels = format_in->channels; + ctx->channelsMask = format_in->channelsMask; + + uint32_t frames_per_ms = lrint((float) format_in->sampleRate / 1000.0f); + ctx->min_buffer = minBuffer * frames_per_ms; + ctx->max_buffer = maxBuffer * frames_per_ms; + + OSStatus e = audio_device_create(ctx, deviceID); + + // Upon failure initializing the given device, try again with the system default device + if (AUDIO_SV_ERROR(e)) + if (deviceID && deviceID[0]) + e = audio_device_create(ctx, NULL); + + if (AUDIO_SV_ERROR(e)) MTY_AudioDestroy(&ctx); return ctx; @@ -86,7 +235,7 @@ void MTY_AudioDestroy(MTY_Audio **audio) if (ctx->q) { OSStatus e = AudioQueueDispose(ctx->q, true); - if (e != kAudioServicesNoError) + if (AUDIO_SV_ERROR(e)) MTY_Log("'AudioQueueDispose' failed with error 0x%X", e); } @@ -105,7 +254,7 @@ static uint32_t audio_get_queued_frames(MTY_Audio *ctx) } } - return queued / (ctx->channels * AUDIO_SAMPLE_SIZE); + return queued / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format)); } static void audio_play(MTY_Audio *ctx) @@ -113,7 +262,7 @@ static void audio_play(MTY_Audio *ctx) if (ctx->playing) return; - if (AudioQueueStart(ctx->q, NULL) == kAudioServicesNoError) + if (!AUDIO_SV_ERROR(AudioQueueStart(ctx->q, NULL))) ctx->playing = true; } @@ -122,7 +271,7 @@ void MTY_AudioReset(MTY_Audio *ctx) if (!ctx->playing) return; - if (AudioQueueStop(ctx->q, true) == kAudioServicesNoError) + if (!AUDIO_SV_ERROR(AudioQueueStop(ctx->q, true))) ctx->playing = false; } @@ -133,7 +282,7 @@ uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->channels * AUDIO_SAMPLE_SIZE; + size_t size = count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); uint32_t queued = audio_get_queued_frames(ctx); // Stop playing and flush if we've exceeded the maximum buffer or underrun @@ -150,7 +299,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) buf->mUserData = (void *) (uintptr_t) x; OSStatus e = AudioQueueEnqueueBuffer(ctx->q, buf, 0, NULL); - if (e == kAudioServicesNoError) { + if (!AUDIO_SV_ERROR(kAudioServicesNoError)) { MTY_Atomic32Set(&ctx->in_use[x], 1); } else { From abb3beddbb50956e529b3fbcc4a49da32d59d4e4 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Fri, 23 Feb 2024 11:49:44 -0500 Subject: [PATCH 07/32] Make sure multichannel audio changes work properly for Linux and Android too. However, actual surround sound audio won't be rendered on those platforms. --- src/unix/linux/android/audio.c | 18 ++++++++++-------- src/unix/linux/x11/audio.c | 16 +++++++++------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 30e5c6559..412643ea5 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -10,16 +10,17 @@ #include -#define AUDIO_SAMPLE_SIZE sizeof(int16_t) +#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) #define AUDIO_BUF_SIZE(ctx) \ - ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE) + ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE((ctx)->sample_format)) struct MTY_Audio { AAudioStreamBuilder *builder; AAudioStream *stream; MTY_Mutex *mutex; + MTY_AudioSampleFormat sample_format; uint32_t sample_rate; uint32_t min_buffer; uint32_t max_buffer; @@ -43,7 +44,7 @@ static aaudio_data_callback_result_t audio_callback(AAudioStream *stream, void * MTY_MutexLock(ctx->mutex); - size_t want_size = numFrames * ctx->channels * AUDIO_SAMPLE_SIZE; + size_t want_size = numFrames * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); if (ctx->playing && ctx->size >= want_size) { memcpy(audioData, ctx->buffer, want_size); @@ -65,13 +66,14 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); ctx->channels = format->channels; + ctx->sample_format = format->sampleFormat; ctx->sample_rate = format->sampleRate; ctx->mutex = MTY_MutexCreate(); ctx->buffer = MTY_Alloc(AUDIO_BUF_SIZE(ctx), 1); uint32_t frames_per_ms = lrint((float) format->sample_rate / 1000.0f); - ctx->min_buffer = minBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE; - ctx->max_buffer = maxBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE; + ctx->min_buffer = minBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE(format->sampleFormat); + ctx->max_buffer = maxBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE(format->sampleFormat); return ctx; } @@ -116,7 +118,7 @@ void MTY_AudioReset(MTY_Audio *ctx) uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) { - return (ctx->size / (ctx->channels * AUDIO_SAMPLE_SIZE)) / ctx->sample_rate * 1000; + return (ctx->size / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format))) / ctx->sample_rate * 1000; } static void audio_start(MTY_Audio *ctx) @@ -126,7 +128,7 @@ static void audio_start(MTY_Audio *ctx) AAudioStreamBuilder_setDeviceId(ctx->builder, AAUDIO_UNSPECIFIED); AAudioStreamBuilder_setSampleRate(ctx->builder, ctx->sample_rate); AAudioStreamBuilder_setChannelCount(ctx->builder, ctx->channels); - AAudioStreamBuilder_setFormat(ctx->builder, AAUDIO_FORMAT_PCM_I16); + AAudioStreamBuilder_setFormat(ctx->builder, ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? AAUDIO_FORMAT_PCM_FLOAT : AAUDIO_FORMAT_PCM_I16); AAudioStreamBuilder_setPerformanceMode(ctx->builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); AAudioStreamBuilder_setErrorCallback(ctx->builder, audio_error, ctx); AAudioStreamBuilder_setDataCallback(ctx->builder, audio_callback, ctx); @@ -140,7 +142,7 @@ static void audio_start(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t data_size = count * ctx->channels * AUDIO_SAMPLE_SIZE; + size_t data_size = count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); audio_start(ctx); diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 5123db049..c224d8a4b 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -9,15 +9,16 @@ #include "dl/libasound.h" -#define AUDIO_SAMPLE_SIZE sizeof(int16_t) +#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) #define AUDIO_BUF_SIZE(ctx) \ - ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE) + ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE((ctx)->sample_format)) struct MTY_Audio { snd_pcm_t *pcm; bool playing; + MTY_AudioSampleFormat sample_format; uint32_t sample_rate; uint32_t min_buffer; uint32_t max_buffer; @@ -33,6 +34,7 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, return NULL; MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); + ctx->sample_format = format->sampleFormat; ctx->sample_rate = format->sampleRate; ctx->channels = format->channels; @@ -54,7 +56,7 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, snd_pcm_hw_params_any(ctx->pcm, params); snd_pcm_hw_params_set_access(ctx->pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); - snd_pcm_hw_params_set_format(ctx->pcm, params, SND_PCM_FORMAT_S16); + snd_pcm_hw_params_set_format(ctx->pcm, params, format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? SND_PCM_FORMAT_FLOAT : SND_PCM_FORMAT_S16); snd_pcm_hw_params_set_channels(ctx->pcm, params, format->channels); snd_pcm_hw_params_set_rate(ctx->pcm, params, format->sampleRate, 0); snd_pcm_hw_params(ctx->pcm, params); @@ -87,7 +89,7 @@ void MTY_AudioDestroy(MTY_Audio **audio) static uint32_t audio_get_queued_frames(MTY_Audio *ctx) { - uint32_t queued = ctx->pos / (ctx->channels * AUDIO_SAMPLE_SIZE); + uint32_t queued = ctx->pos / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format)); if (ctx->playing) { snd_pcm_status_t *status = NULL; @@ -126,7 +128,7 @@ uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->channels * AUDIO_SAMPLE_SIZE; + size_t size = count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); uint32_t queued = audio_get_queued_frames(ctx); @@ -135,7 +137,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) MTY_AudioReset(ctx); if (ctx->pos + size <= AUDIO_BUF_SIZE(ctx)) { - memcpy(ctx->buf + ctx->pos, frames, count * ctx->channels * AUDIO_SAMPLE_SIZE); + memcpy(ctx->buf + ctx->pos, frames, count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format)); ctx->pos += size; } @@ -144,7 +146,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) audio_play(ctx); if (ctx->playing) { - int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / (ctx->channels * AUDIO_SAMPLE_SIZE)); + int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format))); if (e >= 0) { ctx->pos = 0; From 1d5f23d794ed38ddb460d6ed6ed3eb969919acca Mon Sep 17 00:00:00 2001 From: Bryan McNett <16126809+bmcnett@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:25:47 -0700 Subject: [PATCH 08/32] how did this compile before, i wonder how did this compile before, i wonder --- src/unix/linux/android/audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 412643ea5..3e02293fd 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -71,7 +71,7 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, ctx->mutex = MTY_MutexCreate(); ctx->buffer = MTY_Alloc(AUDIO_BUF_SIZE(ctx), 1); - uint32_t frames_per_ms = lrint((float) format->sample_rate / 1000.0f); + uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE(format->sampleFormat); ctx->max_buffer = maxBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE(format->sampleFormat); From 57fecbacd8874819655f8ad8190e775cf45d0800 Mon Sep 17 00:00:00 2001 From: Bryan McNett <16126809+bmcnett@users.noreply.github.com> Date: Mon, 15 Jul 2024 07:43:23 -0700 Subject: [PATCH 09/32] fix android build fix android build --- src/unix/linux/android/audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index baf102d28..1b4859e02 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -151,7 +151,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) if (ctx->size + data_size >= ctx->max_buffer) ctx->flushing = true; - size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->channels * AUDIO_SAMPLE_SIZE; + size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); if (ctx->flushing && ctx->size < minimum_request) { memset(ctx->buffer, 0, ctx->size); ctx->size = 0; From 44043661cca691da0437c64af4ad17581557c0a1 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Thu, 12 Sep 2024 20:27:49 -0400 Subject: [PATCH 10/32] Code style changes from code review --- src/matoya.h | 2 +- src/unix/apple/audio.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index b4d553697..644444b08 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1478,7 +1478,7 @@ typedef struct { /// @brief Create an MTY_Audio context for playback. /// @param format Format that the audio device will attempt to initialize. If the -/// provided format is supported, the function will fail and return NULL. +/// provided format is not supported, the function will fail and return NULL. /// This parameter is a required argument and MUST NOT be NULL. /// @param minBuffer The minimum amount of audio in milliseconds that must be queued /// before playback begins. diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index c0ecd785b..4f27b6aeb 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -130,6 +130,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) if (uid_cf) CFRelease(uid_cf); + free(uid); } } @@ -193,6 +194,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) if (selected_device_uid) CFRelease(selected_device_uid); + free(device_ids); return e; @@ -216,9 +218,8 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, OSStatus e = audio_device_create(ctx, deviceID); // Upon failure initializing the given device, try again with the system default device - if (AUDIO_SV_ERROR(e)) - if (deviceID && deviceID[0]) - e = audio_device_create(ctx, NULL); + if (AUDIO_SV_ERROR(e) && deviceID && deviceID[0]) + e = audio_device_create(ctx, NULL); if (AUDIO_SV_ERROR(e)) MTY_AudioDestroy(&ctx); From 6de5d379cf575b50db8da1f16095821b59eaa2f0 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Thu, 12 Sep 2024 20:40:53 -0400 Subject: [PATCH 11/32] Consistent name style --- src/unix/apple/audio.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 4f27b6aeb..5914fe421 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -25,7 +25,7 @@ struct MTY_Audio { uint32_t min_buffer; uint32_t max_buffer; uint8_t channels; - uint32_t channelsMask; + uint32_t channels_mask; bool playing; }; @@ -169,10 +169,10 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) } // Specify channel configuration - if (ctx->channelsMask) { + if (ctx->channels_mask) { AudioChannelLayout channel_layout = { .mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap, - .mChannelBitmap = ctx->channelsMask, // Core Audio channel bitmap follows the spec that the WAVE format follows, so we can simply pass in the mask as-is + .mChannelBitmap = ctx->channels_mask, // Core Audio channel bitmap follows the spec that the WAVE format follows, so we can simply pass in the mask as-is }; e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_ChannelLayout, (const void *) &channel_layout, sizeof(AudioChannelLayout)); @@ -209,7 +209,7 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, ctx->sample_format = format_in->sampleFormat; ctx->sample_rate = format_in->sampleRate; ctx->channels = format_in->channels; - ctx->channelsMask = format_in->channelsMask; + ctx->channels_mask = format_in->channelsMask; uint32_t frames_per_ms = lrint((float) format_in->sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms; From be910bf8491b637ad164fe76720dbd169501ea9c Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Thu, 12 Sep 2024 20:49:28 -0400 Subject: [PATCH 12/32] Include channel mask for android audio init. --- src/unix/linux/android/audio.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 1b4859e02..acd521c61 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -25,6 +25,7 @@ struct MTY_Audio { uint32_t min_buffer; uint32_t max_buffer; uint8_t channels; + uint32_t channels_mask; bool flushing; bool playing; @@ -66,6 +67,7 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); ctx->channels = format->channels; + ctx->channels_mask = format->channelsMask; ctx->sample_format = format->sampleFormat; ctx->sample_rate = format->sampleRate; ctx->mutex = MTY_MutexCreate(); @@ -128,6 +130,7 @@ static void audio_start(MTY_Audio *ctx) AAudioStreamBuilder_setDeviceId(ctx->builder, AAUDIO_UNSPECIFIED); AAudioStreamBuilder_setSampleRate(ctx->builder, ctx->sample_rate); AAudioStreamBuilder_setChannelCount(ctx->builder, ctx->channels); + AAudioStreamBuilder_setChannelMask(ctx->builder, ctx->channels_mask ? (aaudio_channel_mask_t) ctx->channels_mask : AAUDIO_UNSPECIFIED); AAudioStreamBuilder_setFormat(ctx->builder, ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? AAUDIO_FORMAT_PCM_FLOAT : AAUDIO_FORMAT_PCM_I16); AAudioStreamBuilder_setPerformanceMode(ctx->builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); AAudioStreamBuilder_setErrorCallback(ctx->builder, audio_error, ctx); From 2ec81d999f12e888be5671a45ae52ead93c71231 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Thu, 12 Sep 2024 21:02:46 -0400 Subject: [PATCH 13/32] Added note for linux that channelsMask is insufficient to define channel config. --- src/unix/linux/x11/audio.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index c224d8a4b..106fef6fe 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -58,6 +58,11 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, snd_pcm_hw_params_set_access(ctx->pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(ctx->pcm, params, format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? SND_PCM_FORMAT_FLOAT : SND_PCM_FORMAT_S16); snd_pcm_hw_params_set_channels(ctx->pcm, params, format->channels); + // XXX: Channel config for ALSA can't be specified via the opaque `format->channelsMask` + // Instead, an explicit channel mapping array is required. To be implemented in the future. + // See `snd_pcm_set_chmap`: + // 1. https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html#ga60ee7d2c2555e21dbc844a1b73839085 + // 2. https://gist.github.com/raydudu/5590a196b9446c709c58a03eff1f38bc snd_pcm_hw_params_set_rate(ctx->pcm, params, format->sampleRate, 0); snd_pcm_hw_params(ctx->pcm, params); snd_pcm_nonblock(ctx->pcm, 1); From d047c192157b17d4c5f0b32aa81fba7484d23ee3 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Wed, 9 Oct 2024 10:18:01 -0400 Subject: [PATCH 14/32] Fix android build failure...leaving comment for Ronald. --- src/unix/linux/android/audio.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index acd521c61..0d2eeb3af 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -130,7 +130,8 @@ static void audio_start(MTY_Audio *ctx) AAudioStreamBuilder_setDeviceId(ctx->builder, AAUDIO_UNSPECIFIED); AAudioStreamBuilder_setSampleRate(ctx->builder, ctx->sample_rate); AAudioStreamBuilder_setChannelCount(ctx->builder, ctx->channels); - AAudioStreamBuilder_setChannelMask(ctx->builder, ctx->channels_mask ? (aaudio_channel_mask_t) ctx->channels_mask : AAUDIO_UNSPECIFIED); + // XXX ATTN Ronald: Requires bumping up android platform from 28 to 32. If OK, let me know and I can uncomment this line. Otherwise, I'll get rid of it + // AAudioStreamBuilder_setChannelMask(ctx->builder, ctx->channels_mask ? (aaudio_channel_mask_t) ctx->channels_mask : AAUDIO_UNSPECIFIED); AAudioStreamBuilder_setFormat(ctx->builder, ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? AAUDIO_FORMAT_PCM_FLOAT : AAUDIO_FORMAT_PCM_I16); AAudioStreamBuilder_setPerformanceMode(ctx->builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); AAudioStreamBuilder_setErrorCallback(ctx->builder, audio_error, ctx); From 196e81f61dd66e7fe4b12fc6e5eea81eb2ba797d Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Tue, 15 Oct 2024 15:17:01 -0400 Subject: [PATCH 15/32] Sundry code review comments --- src/unix/apple/audio.c | 23 +++++++++++------------ src/unix/linux/android/audio.c | 26 +++++++++++++------------- src/unix/linux/x11/audio.c | 22 +++++++++++----------- src/unix/web/matoya-worker.js | 2 +- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 5914fe421..d023dbc8a 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -10,9 +10,6 @@ #define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) #define AUDIO_BUFS 64 -#define AUDIO_BUF_SIZE(ctx) \ - ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE((ctx)->sample_format)) - #define AUDIO_HW_ERROR(e) ((e) != kAudioHardwareNoError) #define AUDIO_SV_ERROR(e) ((e) != kAudioServicesNoError) @@ -26,6 +23,8 @@ struct MTY_Audio { uint32_t max_buffer; uint8_t channels; uint32_t channels_mask; + uint32_t frame_size; + uint32_t buffer_size; bool playing; }; @@ -39,7 +38,6 @@ static void audio_queue_callback(void *opaque, AudioQueueRef q, AudioQueueBuffer static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPropertyScope prop_scope, AudioObjectPropertyElement prop_element, char **out_uid, CFStringRef *out_uid_cf) { - OSStatus e = kAudioHardwareNoError; char *uid = NULL; CFStringRef uid_cf = NULL; @@ -50,7 +48,7 @@ static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPro .mElement = prop_element, }; - e = AudioObjectGetPropertyData(device, &propAddr, 0, NULL, &data_size, &uid_cf); + OSStatus e = AudioObjectGetPropertyData(device, &propAddr, 0, NULL, &data_size, &uid_cf); if (AUDIO_HW_ERROR(e)) goto except; @@ -80,7 +78,6 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) { AudioObjectID *selected_device = NULL; CFStringRef selected_device_uid = NULL; - OSStatus e = kAudioHardwareNoError; // Enumerate all output devices and identify the given device AudioObjectID *device_ids = NULL; @@ -94,7 +91,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) }; UInt32 data_size = 0; - e = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAddr, 0, NULL, &data_size); + OSStatus e = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAddr, 0, NULL, &data_size); if (AUDIO_HW_ERROR(e)) goto except; @@ -149,7 +146,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) format.mFramesPerPacket = 1; format.mChannelsPerFrame = ctx->channels; format.mBitsPerChannel = AUDIO_SAMPLE_SIZE(ctx->sample_format) * 8; - format.mBytesPerPacket = AUDIO_SAMPLE_SIZE(ctx->sample_format) * format.mChannelsPerFrame; + format.mBytesPerPacket = ctx->frame_size; format.mBytesPerFrame = format.mBytesPerPacket; // Create a new audio queue, which by default chooses the device's default device @@ -183,7 +180,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) } for (int32_t x = 0; x < AUDIO_BUFS; x++) { - e = AudioQueueAllocateBuffer(ctx->q, AUDIO_BUF_SIZE(ctx), &ctx->audio_buf[x]); + e = AudioQueueAllocateBuffer(ctx->q, ctx->buffer_size, &ctx->audio_buf[x]); if (AUDIO_SV_ERROR(e)) { MTY_Log("'AudioQueueAllocateBuffer' failed with error 0x%X", e); goto except; @@ -210,6 +207,8 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, ctx->sample_rate = format_in->sampleRate; ctx->channels = format_in->channels; ctx->channels_mask = format_in->channelsMask; + ctx->frame_size = format_in->channels * AUDIO_SAMPLE_SIZE(format_in->sampleFormat); + ctx->buffer_size = format_in->sampleRate * ctx->frame_size; uint32_t frames_per_ms = lrint((float) format_in->sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms; @@ -255,7 +254,7 @@ static uint32_t audio_get_queued_frames(MTY_Audio *ctx) } } - return queued / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format)); + return queued / ctx->frame_size; } static void audio_play(MTY_Audio *ctx) @@ -283,14 +282,14 @@ uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); + size_t size = count * ctx->frame_size; uint32_t queued = audio_get_queued_frames(ctx); // Stop playing and flush if we've exceeded the maximum buffer or underrun if (ctx->playing && (queued > ctx->max_buffer || queued == 0)) MTY_AudioReset(ctx); - if (size <= AUDIO_BUF_SIZE(ctx)) { + if (size <= ctx->buffer_size) { for (uint8_t x = 0; x < AUDIO_BUFS; x++) { if (MTY_Atomic32Get(&ctx->in_use[x]) == 0) { AudioQueueBufferRef buf = ctx->audio_buf[x]; diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 0d2eeb3af..d84298226 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -10,11 +10,6 @@ #include -#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) - -#define AUDIO_BUF_SIZE(ctx) \ - ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE((ctx)->sample_format)) - struct MTY_Audio { AAudioStreamBuilder *builder; AAudioStream *stream; @@ -26,6 +21,8 @@ struct MTY_Audio { uint32_t max_buffer; uint8_t channels; uint32_t channels_mask; + uint32_t frame_size; + uint32_t buffer_size; bool flushing; bool playing; @@ -45,7 +42,7 @@ static aaudio_data_callback_result_t audio_callback(AAudioStream *stream, void * MTY_MutexLock(ctx->mutex); - size_t want_size = numFrames * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); + size_t want_size = numFrames * ctx->frame_size; if (ctx->playing && ctx->size >= want_size) { memcpy(audioData, ctx->buffer, want_size); @@ -70,12 +67,15 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, ctx->channels_mask = format->channelsMask; ctx->sample_format = format->sampleFormat; ctx->sample_rate = format->sampleRate; + ctx->frame_size = format->channels * + (format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); + ctx->buffer_size = format->sampleRate * ctx->frame_size ctx->mutex = MTY_MutexCreate(); - ctx->buffer = MTY_Alloc(AUDIO_BUF_SIZE(ctx), 1); + ctx->buffer = MTY_Alloc(ctx->buffer_size, 1); uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE(format->sampleFormat); - ctx->max_buffer = maxBuffer * frames_per_ms * ctx->channels * AUDIO_SAMPLE_SIZE(format->sampleFormat); + ctx->min_buffer = minBuffer * frames_per_ms * ctx->frame_size; + ctx->max_buffer = maxBuffer * frames_per_ms * ctx->frame_size; return ctx; } @@ -120,7 +120,7 @@ void MTY_AudioReset(MTY_Audio *ctx) uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) { - return (ctx->size / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format))) / ctx->sample_rate * 1000; + return (ctx->size / ctx->frame_size) / ctx->sample_rate * 1000; } static void audio_start(MTY_Audio *ctx) @@ -146,7 +146,7 @@ static void audio_start(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t data_size = count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); + size_t data_size = count * ctx->frame_size; audio_start(ctx); @@ -155,7 +155,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) if (ctx->size + data_size >= ctx->max_buffer) ctx->flushing = true; - size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); + size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->frame_size; if (ctx->flushing && ctx->size < minimum_request) { memset(ctx->buffer, 0, ctx->size); ctx->size = 0; @@ -166,7 +166,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) ctx->flushing = false; } - if (!ctx->flushing && data_size + ctx->size <= AUDIO_BUF_SIZE(ctx)) { + if (!ctx->flushing && data_size + ctx->size <= ctx->buffer_size) { memcpy(ctx->buffer + ctx->size, frames, data_size); ctx->size += data_size; } diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 106fef6fe..2a3c33cc2 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -9,11 +9,6 @@ #include "dl/libasound.h" -#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) - -#define AUDIO_BUF_SIZE(ctx) \ - ((ctx)->sample_rate * (ctx)->channels * AUDIO_SAMPLE_SIZE((ctx)->sample_format)) - struct MTY_Audio { snd_pcm_t *pcm; @@ -23,6 +18,8 @@ struct MTY_Audio { uint32_t min_buffer; uint32_t max_buffer; uint8_t channels; + uint32_t frame_size; + uint32_t buffer_size; uint8_t *buf; size_t pos; }; @@ -37,6 +34,9 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, ctx->sample_format = format->sampleFormat; ctx->sample_rate = format->sampleRate; ctx->channels = format->channels; + ctx->frame_size = format->channels * + (format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); + ctx->buffer_size = format->sampleRate * ctx->frame_size; uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms; @@ -67,7 +67,7 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, snd_pcm_hw_params(ctx->pcm, params); snd_pcm_nonblock(ctx->pcm, 1); - ctx->buf = MTY_Alloc(AUDIO_BUF_SIZE(ctx), 1); + ctx->buf = MTY_Alloc(ctx->buffer_size, 1); except: @@ -94,7 +94,7 @@ void MTY_AudioDestroy(MTY_Audio **audio) static uint32_t audio_get_queued_frames(MTY_Audio *ctx) { - uint32_t queued = ctx->pos / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format)); + uint32_t queued = ctx->pos / ctx->frame_size; if (ctx->playing) { snd_pcm_status_t *status = NULL; @@ -133,7 +133,7 @@ uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format); + size_t size = count * ctx->frame_size; uint32_t queued = audio_get_queued_frames(ctx); @@ -141,8 +141,8 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) if (ctx->playing && (queued > ctx->max_buffer || queued == 0)) MTY_AudioReset(ctx); - if (ctx->pos + size <= AUDIO_BUF_SIZE(ctx)) { - memcpy(ctx->buf + ctx->pos, frames, count * ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format)); + if (ctx->pos + size <= ctx->buffer_size) { + memcpy(ctx->buf + ctx->pos, frames, count * ctx->frame_size); ctx->pos += size; } @@ -151,7 +151,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) audio_play(ctx); if (ctx->playing) { - int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / (ctx->channels * AUDIO_SAMPLE_SIZE(ctx->sample_format))); + int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / ctx->frame_size); if (e >= 0) { ctx->pos = 0; diff --git a/src/unix/web/matoya-worker.js b/src/unix/web/matoya-worker.js index 842040eb9..db1b241a6 100644 --- a/src/unix/web/matoya-worker.js +++ b/src/unix/web/matoya-worker.js @@ -318,7 +318,7 @@ const MTY_AUDIO_API = { sampleRate: format.sampleRate, minBuffer, maxBuffer, - channels, + channels: format.channels, }; return 0xCDD; From efebea08ad385995f0377c8dbb2e139e284ad90b Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Tue, 15 Oct 2024 16:28:33 -0400 Subject: [PATCH 16/32] Whoops compiler error --- src/unix/linux/android/audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index d84298226..2b91a336b 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -69,7 +69,7 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, ctx->sample_rate = format->sampleRate; ctx->frame_size = format->channels * (format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); - ctx->buffer_size = format->sampleRate * ctx->frame_size + ctx->buffer_size = format->sampleRate * ctx->frame_size; ctx->mutex = MTY_MutexCreate(); ctx->buffer = MTY_Alloc(ctx->buffer_size, 1); From 88409c4d4439063ad087d3c191645c4c9893f1f3 Mon Sep 17 00:00:00 2001 From: Ronald Huveneers <114760413+RonaldH-Parsec@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:08:21 -0400 Subject: [PATCH 17/32] Formatting. --- src/matoya.h | 6 ++++-- src/unix/apple/audio.c | 18 ++++++++++++------ src/unix/linux/android/audio.c | 10 +++++++--- src/unix/linux/x11/audio.c | 3 ++- src/windows/audio.c | 15 ++++++++------- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index 0e244f63c..6d1ae9354 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1494,7 +1494,8 @@ typedef struct { /// @returns On failure, NULL is returned. Call MTY_GetLog for details.\n\n /// The returned MTY_Audio context must be destroyed with MTY_AudioDestroy. MTY_EXPORT MTY_Audio * -MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback); +MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, uint32_t maxBuffer, + const char *deviceID, bool fallback); /// @brief Destroy an MTY_Audio context. /// @param audio Passed by reference and set to NULL after being destroyed. @@ -1521,7 +1522,8 @@ MTY_AudioGetQueued(MTY_Audio *ctx); /// For example, in the case of 2-channel/stereo audio, one audio frame is two samples, each /// sample being one channel. /// @param count The number of frames contained in `frames`. The number of frames would -/// be the size of `frames` in bytes divided by sample size * `channels` specified during MTY_AudioCreate. +/// be the size of `frames` in bytes divided by sample size * `channels` specified +/// during MTY_AudioCreate. MTY_EXPORT void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count); diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index d023dbc8a..f1b40846b 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -36,7 +36,8 @@ static void audio_queue_callback(void *opaque, AudioQueueRef q, AudioQueueBuffer buf->mAudioDataByteSize = 0; } -static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPropertyScope prop_scope, AudioObjectPropertyElement prop_element, char **out_uid, CFStringRef *out_uid_cf) +static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPropertyScope prop_scope, + AudioObjectPropertyElement prop_element, char **out_uid, CFStringRef *out_uid_cf) { char *uid = NULL; CFStringRef uid_cf = NULL; @@ -116,7 +117,8 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) char *uid = NULL; CFStringRef uid_cf = NULL; - if (AUDIO_HW_ERROR(audio_object_get_device_uid(device_ids[i], propAddr.mScope, propAddr.mElement, &uid, &uid_cf))) + if (AUDIO_HW_ERROR(audio_object_get_device_uid(device_ids[i], + propAddr.mScope, propAddr.mElement, &uid, &uid_cf))) continue; if (!strcmp(deviceID, uid)) { @@ -142,7 +144,8 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) AudioStreamBasicDescription format = {0}; format.mSampleRate = ctx->sample_rate; format.mFormatID = kAudioFormatLinearPCM; - format.mFormatFlags = (ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsPacked; + format.mFormatFlags = (ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? + kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsPacked; format.mFramesPerPacket = 1; format.mChannelsPerFrame = ctx->channels; format.mBitsPerChannel = AUDIO_SAMPLE_SIZE(ctx->sample_format) * 8; @@ -158,7 +161,8 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) // Change the audio queue to be associated with the selected audio device if (selected_device_uid) { - e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_CurrentDevice, (const void *) &selected_device_uid, sizeof(CFStringRef)); + e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_CurrentDevice, + (const void *) &selected_device_uid, sizeof(CFStringRef)); if (AUDIO_SV_ERROR(e)) { MTY_Log("'AudioQueueSetProperty(kAudioQueueProperty_CurrentDevice)' failed with error 0x%X", e); goto except; @@ -169,10 +173,12 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) if (ctx->channels_mask) { AudioChannelLayout channel_layout = { .mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap, - .mChannelBitmap = ctx->channels_mask, // Core Audio channel bitmap follows the spec that the WAVE format follows, so we can simply pass in the mask as-is + .mChannelBitmap = ctx->channels_mask, // Core Audio channel bitmap follows the spec that the WAVE format + // follows, so we can simply pass in the mask as-is }; - e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_ChannelLayout, (const void *) &channel_layout, sizeof(AudioChannelLayout)); + e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_ChannelLayout, + (const void *) &channel_layout, sizeof(AudioChannelLayout)); if (AUDIO_SV_ERROR(e)) { MTY_Log("'AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)' failed with error 0x%X", e); goto except; diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 2b91a336b..e1d3a6d67 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -130,9 +130,13 @@ static void audio_start(MTY_Audio *ctx) AAudioStreamBuilder_setDeviceId(ctx->builder, AAUDIO_UNSPECIFIED); AAudioStreamBuilder_setSampleRate(ctx->builder, ctx->sample_rate); AAudioStreamBuilder_setChannelCount(ctx->builder, ctx->channels); - // XXX ATTN Ronald: Requires bumping up android platform from 28 to 32. If OK, let me know and I can uncomment this line. Otherwise, I'll get rid of it - // AAudioStreamBuilder_setChannelMask(ctx->builder, ctx->channels_mask ? (aaudio_channel_mask_t) ctx->channels_mask : AAUDIO_UNSPECIFIED); - AAudioStreamBuilder_setFormat(ctx->builder, ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? AAUDIO_FORMAT_PCM_FLOAT : AAUDIO_FORMAT_PCM_I16); + /* XXX ATTN Ronald: Requires bumping up android platform from 28 to 32. + If OK, let me know and I can uncomment this line. Otherwise, I'll get rid of it + AAudioStreamBuilder_setChannelMask(ctx->builder, ctx->channels_mask ? + (aaudio_channel_mask_t) ctx->channels_mask : AAUDIO_UNSPECIFIED); + */ + AAudioStreamBuilder_setFormat(ctx->builder, ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + ? AAUDIO_FORMAT_PCM_FLOAT : AAUDIO_FORMAT_PCM_I16); AAudioStreamBuilder_setPerformanceMode(ctx->builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); AAudioStreamBuilder_setErrorCallback(ctx->builder, audio_error, ctx); AAudioStreamBuilder_setDataCallback(ctx->builder, audio_callback, ctx); diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 2a3c33cc2..f3849b927 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -56,7 +56,8 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, snd_pcm_hw_params_any(ctx->pcm, params); snd_pcm_hw_params_set_access(ctx->pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); - snd_pcm_hw_params_set_format(ctx->pcm, params, format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? SND_PCM_FORMAT_FLOAT : SND_PCM_FORMAT_S16); + snd_pcm_hw_params_set_format(ctx->pcm, params, format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + ? SND_PCM_FORMAT_FLOAT : SND_PCM_FORMAT_S16); snd_pcm_hw_params_set_channels(ctx->pcm, params, format->channels); // XXX: Channel config for ALSA can't be specified via the opaque `format->channelsMask` // Instead, an explicit channel mapping array is required. To be implemented in the future. diff --git a/src/windows/audio.c b/src/windows/audio.c index 89dc14f4e..fe8df8fc9 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -9,12 +9,12 @@ #include #include -DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E); -DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6); -DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4C, 0xDBFA, 0x4C32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2); -DEFINE_GUID(IID_IAudioRenderClient, 0xF294ACFC, 0x3146, 0x4483, 0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2); -DEFINE_GUID(IID_IMMNotificationClient, 0x7991EEC9, 0x7E89, 0x4D85, 0x83, 0x90, 0x6C, 0x70, 0x3C, 0xEC, 0x60, 0xC0); -DEFINE_GUID(OWN_KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6); +DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4C, 0xDBFA, 0x4C32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2); +DEFINE_GUID(IID_IAudioRenderClient, 0xF294ACFC, 0x3146, 0x4483, 0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2); +DEFINE_GUID(IID_IMMNotificationClient, 0x7991EEC9, 0x7E89, 0x4D85, 0x83, 0x90, 0x6C, 0x70, 0x3C, 0xEC, 0x60, 0xC0); +DEFINE_GUID(OWN_KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); DEFINE_GUID(OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #define COBJMACROS @@ -252,7 +252,8 @@ static HRESULT audio_device_create(MTY_Audio *ctx) pwfx.Samples.wValidBitsPerSample = pwfx.Format.wBitsPerSample; pwfx.dwChannelMask = ctx->channels_mask; - pwfx.SubFormat = ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : OWN_KSDATAFORMAT_SUBTYPE_PCM; + pwfx.SubFormat = ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + ? OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : OWN_KSDATAFORMAT_SUBTYPE_PCM; } } From 58bc76097aee8ac8d9c11b24568a5bcfb951627d Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Wed, 30 Oct 2024 09:55:52 -0400 Subject: [PATCH 18/32] Important renaming from frames to samples. --- src/unix/apple/audio.c | 6 +++--- src/unix/linux/android/audio.c | 6 +++--- src/unix/linux/x11/audio.c | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index f1b40846b..75edb703e 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -216,9 +216,9 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, ctx->frame_size = format_in->channels * AUDIO_SAMPLE_SIZE(format_in->sampleFormat); ctx->buffer_size = format_in->sampleRate * ctx->frame_size; - uint32_t frames_per_ms = lrint((float) format_in->sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * frames_per_ms; - ctx->max_buffer = maxBuffer * frames_per_ms; + uint32_t samples_per_ms = lrint((float) format_in->sampleRate / 1000.0f); + ctx->min_buffer = minBuffer * samples_per_ms; + ctx->max_buffer = maxBuffer * samples_per_ms; OSStatus e = audio_device_create(ctx, deviceID); diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index e1d3a6d67..0c542bea1 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -73,9 +73,9 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, ctx->mutex = MTY_MutexCreate(); ctx->buffer = MTY_Alloc(ctx->buffer_size, 1); - uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * frames_per_ms * ctx->frame_size; - ctx->max_buffer = maxBuffer * frames_per_ms * ctx->frame_size; + uint32_t samples_per_ms = lrint((float) format->sampleRate / 1000.0f); + ctx->min_buffer = minBuffer * samples_per_ms * ctx->frame_size; + ctx->max_buffer = maxBuffer * samples_per_ms * ctx->frame_size; return ctx; } diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index f3849b927..f086688f9 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -38,9 +38,9 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, (format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); ctx->buffer_size = format->sampleRate * ctx->frame_size; - uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * frames_per_ms; - ctx->max_buffer = maxBuffer * frames_per_ms; + uint32_t samples_per_ms = lrint((float) format->sampleRate / 1000.0f); + ctx->min_buffer = minBuffer * samples_per_ms; + ctx->max_buffer = maxBuffer * samples_per_ms; bool r = true; From ff37e64cf15c65f6185b2046da664fa51ac8bdee Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Fri, 1 Nov 2024 09:47:18 -0400 Subject: [PATCH 19/32] Address Callum's code review comments --- src/matoya.h | 3 +-- src/unix/apple/audio.c | 18 +++++++++--------- src/unix/linux/android/audio.c | 18 +++++++++--------- src/unix/linux/x11/audio.c | 26 +++++++++++++------------- src/windows/audio.c | 32 ++++++-------------------------- 5 files changed, 38 insertions(+), 59 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index 6d1ae9354..e87d9e10c 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1481,7 +1481,6 @@ typedef struct { /// @brief Create an MTY_Audio context for playback. /// @param format Format that the audio device will attempt to initialize. If the /// provided format is not supported, the function will fail and return NULL. -/// This parameter is a required argument and MUST NOT be NULL. /// @param minBuffer The minimum amount of audio in milliseconds that must be queued /// before playback begins. /// @param maxBuffer The maximum amount of audio in milliseconds that can be queued @@ -1494,7 +1493,7 @@ typedef struct { /// @returns On failure, NULL is returned. Call MTY_GetLog for details.\n\n /// The returned MTY_Audio context must be destroyed with MTY_AudioDestroy. MTY_EXPORT MTY_Audio * -MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, uint32_t maxBuffer, +MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback); /// @brief Destroy an MTY_Audio context. diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 75edb703e..d8d6b7ba8 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -203,20 +203,20 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) return e; } -MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format_in, uint32_t minBuffer, uint32_t maxBuffer, +MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format_in, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback) { // TODO Should this use the current run loop rather than internal threading? MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_format = format_in->sampleFormat; - ctx->sample_rate = format_in->sampleRate; - ctx->channels = format_in->channels; - ctx->channels_mask = format_in->channelsMask; - ctx->frame_size = format_in->channels * AUDIO_SAMPLE_SIZE(format_in->sampleFormat); - ctx->buffer_size = format_in->sampleRate * ctx->frame_size; - - uint32_t samples_per_ms = lrint((float) format_in->sampleRate / 1000.0f); + ctx->sample_format = format_in.sampleFormat; + ctx->sample_rate = format_in.sampleRate; + ctx->channels = format_in.channels; + ctx->channels_mask = format_in.channelsMask; + ctx->frame_size = format_in.channels * AUDIO_SAMPLE_SIZE(format_in.sampleFormat); + ctx->buffer_size = format_in.sampleRate * ctx->frame_size; + + uint32_t samples_per_ms = lrint((float) format_in.sampleRate / 1000.0f); ctx->min_buffer = minBuffer * samples_per_ms; ctx->max_buffer = maxBuffer * samples_per_ms; diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 0c542bea1..cac200827 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -59,21 +59,21 @@ static aaudio_data_callback_result_t audio_callback(AAudioStream *stream, void * return AAUDIO_CALLBACK_RESULT_CONTINUE; } -MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, +MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback) { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->channels = format->channels; - ctx->channels_mask = format->channelsMask; - ctx->sample_format = format->sampleFormat; - ctx->sample_rate = format->sampleRate; - ctx->frame_size = format->channels * - (format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); - ctx->buffer_size = format->sampleRate * ctx->frame_size; + ctx->channels = format.channels; + ctx->channels_mask = format.channelsMask; + ctx->sample_format = format.sampleFormat; + ctx->sample_rate = format.sampleRate; + ctx->frame_size = format.channels * + (format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); + ctx->buffer_size = format.sampleRate * ctx->frame_size; ctx->mutex = MTY_MutexCreate(); ctx->buffer = MTY_Alloc(ctx->buffer_size, 1); - uint32_t samples_per_ms = lrint((float) format->sampleRate / 1000.0f); + uint32_t samples_per_ms = lrint((float) format.sampleRate / 1000.0f); ctx->min_buffer = minBuffer * samples_per_ms * ctx->frame_size; ctx->max_buffer = maxBuffer * samples_per_ms * ctx->frame_size; diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index f086688f9..5c2449986 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -24,21 +24,21 @@ struct MTY_Audio { size_t pos; }; -MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, +MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback) { if (!libasound_global_init()) return NULL; MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_format = format->sampleFormat; - ctx->sample_rate = format->sampleRate; - ctx->channels = format->channels; - ctx->frame_size = format->channels * - (format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); - ctx->buffer_size = format->sampleRate * ctx->frame_size; - - uint32_t samples_per_ms = lrint((float) format->sampleRate / 1000.0f); + ctx->sample_format = format.sampleFormat; + ctx->sample_rate = format.sampleRate; + ctx->channels = format.channels; + ctx->frame_size = format.channels * + (format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); + ctx->buffer_size = format.sampleRate * ctx->frame_size; + + uint32_t samples_per_ms = lrint((float) format.sampleRate / 1000.0f); ctx->min_buffer = minBuffer * samples_per_ms; ctx->max_buffer = maxBuffer * samples_per_ms; @@ -56,15 +56,15 @@ MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, snd_pcm_hw_params_any(ctx->pcm, params); snd_pcm_hw_params_set_access(ctx->pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); - snd_pcm_hw_params_set_format(ctx->pcm, params, format->sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + snd_pcm_hw_params_set_format(ctx->pcm, params, format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? SND_PCM_FORMAT_FLOAT : SND_PCM_FORMAT_S16); - snd_pcm_hw_params_set_channels(ctx->pcm, params, format->channels); - // XXX: Channel config for ALSA can't be specified via the opaque `format->channelsMask` + snd_pcm_hw_params_set_channels(ctx->pcm, params, format.channels); + // XXX: Channel config for ALSA can't be specified via the opaque `format.channelsMask` // Instead, an explicit channel mapping array is required. To be implemented in the future. // See `snd_pcm_set_chmap`: // 1. https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html#ga60ee7d2c2555e21dbc844a1b73839085 // 2. https://gist.github.com/raydudu/5590a196b9446c709c58a03eff1f38bc - snd_pcm_hw_params_set_rate(ctx->pcm, params, format->sampleRate, 0); + snd_pcm_hw_params_set_rate(ctx->pcm, params, format.sampleRate, 0); snd_pcm_hw_params(ctx->pcm, params); snd_pcm_nonblock(ctx->pcm, 1); diff --git a/src/windows/audio.c b/src/windows/audio.c index fe8df8fc9..6431f76bb 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -181,26 +181,6 @@ static HRESULT audio_get_extended_format(IMMDevice *device, WAVEFORMATEXTENSIBLE return e; } -static HRESULT audio_get_extended_format2(IAudioClient *client, WAVEFORMATEXTENSIBLE *pwfx) -{ - WAVEFORMATEXTENSIBLE *ptfx = NULL; - - HRESULT e = IAudioClient_GetMixFormat(client, (WAVEFORMATEX **) &ptfx); - if (e != S_OK || !ptfx) { - MTY_Log("IAudioClient_GetMixFormat failed with HRESULT 0x%X", e); - goto except; - } - - *pwfx = *ptfx; - - except: - - if (ptfx) - CoTaskMemFree(ptfx); - - return e; -} - static HRESULT audio_device_create(MTY_Audio *ctx) { HRESULT e = S_OK; @@ -291,17 +271,17 @@ static HRESULT audio_device_create(MTY_Audio *ctx) return e; } -MTY_Audio *MTY_AudioCreate(const MTY_AudioFormat *format, uint32_t minBuffer, +MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback) { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_format = format->sampleFormat; - ctx->sample_rate = format->sampleRate; - ctx->channels_mask = format->channelsMask; - ctx->channels = format->channels; + ctx->sample_format = format.sampleFormat; + ctx->sample_rate = format.sampleRate; + ctx->channels_mask = format.channelsMask; + ctx->channels = format.channels; ctx->fallback = fallback; - uint32_t frames_per_ms = lrint((float) format->sampleRate / 1000.0f); + uint32_t frames_per_ms = lrint((float) format.sampleRate / 1000.0f); ctx->min_buffer = minBuffer * frames_per_ms; ctx->max_buffer = maxBuffer * frames_per_ms; From 2c4b8892f75aac15c99069eddf8fc44da630cb60 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Wed, 6 Nov 2024 14:29:59 -0500 Subject: [PATCH 20/32] Refactored common audio module init code. --- src/audio-common.h | 34 ++++++++++++++++++++ src/unix/apple/audio.c | 53 +++++++++++-------------------- src/unix/linux/android/audio.c | 58 ++++++++++++++-------------------- src/unix/linux/x11/audio.c | 40 ++++++++--------------- src/windows/audio.c | 51 +++++++++++------------------- 5 files changed, 108 insertions(+), 128 deletions(-) create mode 100644 src/audio-common.h diff --git a/src/audio-common.h b/src/audio-common.h new file mode 100644 index 000000000..a097b9612 --- /dev/null +++ b/src/audio-common.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "matoya.h" + +struct audio_common { + MTY_AudioFormat format; + + // Values derived from `format` + struct { + uint32_t sample_size; ///< Size in bytes of 1 sample of audio from 1 channel + uint32_t frame_size; ///< Size in bytes of 1 frame of audio, i.e. 1 sample of audio from each and every channel combined + uint32_t buffer_size; ///< Size in bytes of a 1-second buffer + uint32_t min_buffer; ///< Minimum number of frames of audio that must be enqueued before playback + uint32_t max_buffer; ///< Maximum number of frames of audio that can be enqueued for playback + } stats; +}; + +static void audio_common_init(struct audio_common *ctx, MTY_AudioFormat fmt, uint32_t min_buffer_ms, + uint32_t max_buffer_ms) +{ + ctx->format = fmt; + + ctx->stats.sample_size = fmt.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + ? sizeof(float) : sizeof(int16_t); + ctx->stats.frame_size = fmt.channels * ctx->stats.sample_size; + ctx->stats.buffer_size = fmt.sampleRate * ctx->stats.frame_size; + + uint32_t samples_per_ms = lrintf(fmt.sampleRate / 1000.0f); + ctx->stats.min_buffer = min_buffer_ms * samples_per_ms; + ctx->stats.max_buffer = max_buffer_ms * samples_per_ms; +} diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index d8d6b7ba8..deae4535c 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -4,27 +4,21 @@ #include "matoya.h" +#include "audio-common.h" + #include #include -#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) #define AUDIO_BUFS 64 #define AUDIO_HW_ERROR(e) ((e) != kAudioHardwareNoError) #define AUDIO_SV_ERROR(e) ((e) != kAudioServicesNoError) struct MTY_Audio { + struct audio_common cmn; AudioQueueRef q; AudioQueueBufferRef audio_buf[AUDIO_BUFS]; MTY_Atomic32 in_use[AUDIO_BUFS]; - MTY_AudioSampleFormat sample_format; - uint32_t sample_rate; - uint32_t min_buffer; - uint32_t max_buffer; - uint8_t channels; - uint32_t channels_mask; - uint32_t frame_size; - uint32_t buffer_size; bool playing; }; @@ -142,14 +136,14 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) // Initialize the selected device AudioStreamBasicDescription format = {0}; - format.mSampleRate = ctx->sample_rate; + format.mSampleRate = ctx->cmn.format.sampleRate; format.mFormatID = kAudioFormatLinearPCM; - format.mFormatFlags = (ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? + format.mFormatFlags = (ctx->cmn.format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsPacked; format.mFramesPerPacket = 1; - format.mChannelsPerFrame = ctx->channels; - format.mBitsPerChannel = AUDIO_SAMPLE_SIZE(ctx->sample_format) * 8; - format.mBytesPerPacket = ctx->frame_size; + format.mChannelsPerFrame = ctx->cmn.format.channels; + format.mBitsPerChannel = ctx->cmn.stats.sample_size * 8; + format.mBytesPerPacket = ctx->cmn.stats.frame_size; format.mBytesPerFrame = format.mBytesPerPacket; // Create a new audio queue, which by default chooses the device's default device @@ -170,11 +164,11 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) } // Specify channel configuration - if (ctx->channels_mask) { + if (ctx->cmn.format.channelsMask) { AudioChannelLayout channel_layout = { .mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap, - .mChannelBitmap = ctx->channels_mask, // Core Audio channel bitmap follows the spec that the WAVE format - // follows, so we can simply pass in the mask as-is + .mChannelBitmap = ctx->cmn.format.channelsMask, // Core Audio channel bitmap follows the spec that the WAVE format + // follows, so we can simply pass in the mask as-is }; e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_ChannelLayout, @@ -186,7 +180,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) } for (int32_t x = 0; x < AUDIO_BUFS; x++) { - e = AudioQueueAllocateBuffer(ctx->q, ctx->buffer_size, &ctx->audio_buf[x]); + e = AudioQueueAllocateBuffer(ctx->q, ctx->cmn.stats.buffer_size, &ctx->audio_buf[x]); if (AUDIO_SV_ERROR(e)) { MTY_Log("'AudioQueueAllocateBuffer' failed with error 0x%X", e); goto except; @@ -209,16 +203,7 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format_in, uint32_t minBuffer, uint32 // TODO Should this use the current run loop rather than internal threading? MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_format = format_in.sampleFormat; - ctx->sample_rate = format_in.sampleRate; - ctx->channels = format_in.channels; - ctx->channels_mask = format_in.channelsMask; - ctx->frame_size = format_in.channels * AUDIO_SAMPLE_SIZE(format_in.sampleFormat); - ctx->buffer_size = format_in.sampleRate * ctx->frame_size; - - uint32_t samples_per_ms = lrint((float) format_in.sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * samples_per_ms; - ctx->max_buffer = maxBuffer * samples_per_ms; + audio_common_init(&ctx->cmn, format_in, minBuffer, maxBuffer); OSStatus e = audio_device_create(ctx, deviceID); @@ -260,7 +245,7 @@ static uint32_t audio_get_queued_frames(MTY_Audio *ctx) } } - return queued / ctx->frame_size; + return queued / ctx->cmn.stats.frame_size; } static void audio_play(MTY_Audio *ctx) @@ -283,19 +268,19 @@ void MTY_AudioReset(MTY_Audio *ctx) uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) { - return lrint((float) audio_get_queued_frames(ctx) / ((float) ctx->sample_rate / 1000.0f)); + return lrint((float) audio_get_queued_frames(ctx) / ((float) ctx->cmn.format.sampleRate / 1000.0f)); } void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->frame_size; + size_t size = count * ctx->cmn.stats.frame_size; uint32_t queued = audio_get_queued_frames(ctx); // Stop playing and flush if we've exceeded the maximum buffer or underrun - if (ctx->playing && (queued > ctx->max_buffer || queued == 0)) + if (ctx->playing && (queued > ctx->cmn.stats.max_buffer || queued == 0)) MTY_AudioReset(ctx); - if (size <= ctx->buffer_size) { + if (size <= ctx->cmn.stats.buffer_size) { for (uint8_t x = 0; x < AUDIO_BUFS; x++) { if (MTY_Atomic32Get(&ctx->in_use[x]) == 0) { AudioQueueBufferRef buf = ctx->audio_buf[x]; @@ -316,7 +301,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) } // Begin playing again when the minimum buffer has been reached - if (!ctx->playing && queued + count >= ctx->min_buffer) + if (!ctx->playing && queued + count >= ctx->cmn.stats.min_buffer) audio_play(ctx); } } diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index cac200827..eeac4da54 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -8,24 +8,22 @@ #include #include +#include "audio-common.h" + #include struct MTY_Audio { + struct audio_common cmn; + AAudioStreamBuilder *builder; AAudioStream *stream; MTY_Mutex *mutex; - MTY_AudioSampleFormat sample_format; - uint32_t sample_rate; - uint32_t min_buffer; - uint32_t max_buffer; - uint8_t channels; - uint32_t channels_mask; - uint32_t frame_size; - uint32_t buffer_size; bool flushing; bool playing; + uint32_t min_buffer_size; + uint32_t max_buffer_size; uint8_t *buffer; size_t size; }; @@ -42,7 +40,7 @@ static aaudio_data_callback_result_t audio_callback(AAudioStream *stream, void * MTY_MutexLock(ctx->mutex); - size_t want_size = numFrames * ctx->frame_size; + size_t want_size = numFrames * ctx->cmn.stats.frame_size; if (ctx->playing && ctx->size >= want_size) { memcpy(audioData, ctx->buffer, want_size); @@ -63,19 +61,12 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback) { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->channels = format.channels; - ctx->channels_mask = format.channelsMask; - ctx->sample_format = format.sampleFormat; - ctx->sample_rate = format.sampleRate; - ctx->frame_size = format.channels * - (format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); - ctx->buffer_size = format.sampleRate * ctx->frame_size; - ctx->mutex = MTY_MutexCreate(); - ctx->buffer = MTY_Alloc(ctx->buffer_size, 1); + audio_common_init(&ctx->cmn, format, minBuffer, maxBuffer); + ctx->min_buffer_size = ctx->cmn.stats.min_buffer * ctx->cmn.stats.frame_size; + ctx->max_buffer_size = ctx->cmn.stats.max_buffer * ctx->cmn.stats.frame_size; - uint32_t samples_per_ms = lrint((float) format.sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * samples_per_ms * ctx->frame_size; - ctx->max_buffer = maxBuffer * samples_per_ms * ctx->frame_size; + ctx->mutex = MTY_MutexCreate(); + ctx->buffer = MTY_Alloc(ctx->cmn.stats.buffer_size, 1); return ctx; } @@ -120,7 +111,7 @@ void MTY_AudioReset(MTY_Audio *ctx) uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) { - return (ctx->size / ctx->frame_size) / ctx->sample_rate * 1000; + return (ctx->size / ctx->cmn.stats.frame_size) / ctx->cmn.format.sampleRate * 1000; } static void audio_start(MTY_Audio *ctx) @@ -128,14 +119,11 @@ static void audio_start(MTY_Audio *ctx) if (!ctx->builder) { AAudio_createStreamBuilder(&ctx->builder); AAudioStreamBuilder_setDeviceId(ctx->builder, AAUDIO_UNSPECIFIED); - AAudioStreamBuilder_setSampleRate(ctx->builder, ctx->sample_rate); - AAudioStreamBuilder_setChannelCount(ctx->builder, ctx->channels); - /* XXX ATTN Ronald: Requires bumping up android platform from 28 to 32. - If OK, let me know and I can uncomment this line. Otherwise, I'll get rid of it - AAudioStreamBuilder_setChannelMask(ctx->builder, ctx->channels_mask ? - (aaudio_channel_mask_t) ctx->channels_mask : AAUDIO_UNSPECIFIED); - */ - AAudioStreamBuilder_setFormat(ctx->builder, ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + AAudioStreamBuilder_setSampleRate(ctx->builder, ctx->cmn.format.sampleRate); + AAudioStreamBuilder_setChannelCount(ctx->builder, ctx->cmn.format.channels); + // XXX: Setting channel mask via AAudioStreamBuilder_setChannelMask requires bumping up android platform from 28 to 32. + // We have decided not to do this as of 11/06/2024 so as not to exclude a significant portion of users. + AAudioStreamBuilder_setFormat(ctx->builder, ctx->cmn.format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? AAUDIO_FORMAT_PCM_FLOAT : AAUDIO_FORMAT_PCM_I16); AAudioStreamBuilder_setPerformanceMode(ctx->builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); AAudioStreamBuilder_setErrorCallback(ctx->builder, audio_error, ctx); @@ -150,16 +138,16 @@ static void audio_start(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t data_size = count * ctx->frame_size; + size_t data_size = count * ctx->cmn.stats.frame_size; audio_start(ctx); MTY_MutexLock(ctx->mutex); - if (ctx->size + data_size >= ctx->max_buffer) + if (ctx->size + data_size >= ctx->max_buffer_size) ctx->flushing = true; - size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->frame_size; + size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->cmn.stats.frame_size; if (ctx->flushing && ctx->size < minimum_request) { memset(ctx->buffer, 0, ctx->size); ctx->size = 0; @@ -170,12 +158,12 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) ctx->flushing = false; } - if (!ctx->flushing && data_size + ctx->size <= ctx->buffer_size) { + if (!ctx->flushing && data_size + ctx->size <= ctx->cmn.stats.buffer_size) { memcpy(ctx->buffer + ctx->size, frames, data_size); ctx->size += data_size; } - if (ctx->size >= ctx->min_buffer) + if (ctx->size >= ctx->min_buffer_size) ctx->playing = true; MTY_MutexUnlock(ctx->mutex); diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 5c2449986..c76c2f057 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -7,19 +7,14 @@ #include #include +#include "audio-common.h" + #include "dl/libasound.h" struct MTY_Audio { + struct audio_common cmn; snd_pcm_t *pcm; - bool playing; - MTY_AudioSampleFormat sample_format; - uint32_t sample_rate; - uint32_t min_buffer; - uint32_t max_buffer; - uint8_t channels; - uint32_t frame_size; - uint32_t buffer_size; uint8_t *buf; size_t pos; }; @@ -31,16 +26,7 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, return NULL; MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_format = format.sampleFormat; - ctx->sample_rate = format.sampleRate; - ctx->channels = format.channels; - ctx->frame_size = format.channels * - (format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)); - ctx->buffer_size = format.sampleRate * ctx->frame_size; - - uint32_t samples_per_ms = lrint((float) format.sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * samples_per_ms; - ctx->max_buffer = maxBuffer * samples_per_ms; + audio_common_init(&ctx->cmn, format, minBuffer, maxBuffer); bool r = true; @@ -68,7 +54,7 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, snd_pcm_hw_params(ctx->pcm, params); snd_pcm_nonblock(ctx->pcm, 1); - ctx->buf = MTY_Alloc(ctx->buffer_size, 1); + ctx->buf = MTY_Alloc(ctx->cmn.stats.buffer_size, 1); except: @@ -95,7 +81,7 @@ void MTY_AudioDestroy(MTY_Audio **audio) static uint32_t audio_get_queued_frames(MTY_Audio *ctx) { - uint32_t queued = ctx->pos / ctx->frame_size; + uint32_t queued = ctx->pos / ctx->cmn.stats.frame_size; if (ctx->playing) { snd_pcm_status_t *status = NULL; @@ -129,30 +115,30 @@ void MTY_AudioReset(MTY_Audio *ctx) uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) { - return lrint((float) audio_get_queued_frames(ctx) / ((float) ctx->sample_rate / 1000.0f)); + return lrint((float) audio_get_queued_frames(ctx) / ((float) ctx->cmn.format.sampleRate / 1000.0f)); } void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->frame_size; + size_t size = count * ctx->cmn.stats.frame_size; uint32_t queued = audio_get_queued_frames(ctx); // Stop playing and flush if we've exceeded the maximum buffer or underrun - if (ctx->playing && (queued > ctx->max_buffer || queued == 0)) + if (ctx->playing && (queued > ctx->cmn.stats.max_buffer || queued == 0)) MTY_AudioReset(ctx); - if (ctx->pos + size <= ctx->buffer_size) { - memcpy(ctx->buf + ctx->pos, frames, count * ctx->frame_size); + if (ctx->pos + size <= ctx->cmn.stats.buffer_size) { + memcpy(ctx->buf + ctx->pos, frames, count * ctx->cmn.stats.frame_size); ctx->pos += size; } // Begin playing again when the minimum buffer has been reached - if (!ctx->playing && queued + count >= ctx->min_buffer) + if (!ctx->playing && queued + count >= ctx->cmn.stats.min_buffer) audio_play(ctx); if (ctx->playing) { - int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / ctx->frame_size); + int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / ctx->cmn.stats.frame_size); if (e >= 0) { ctx->pos = 0; diff --git a/src/windows/audio.c b/src/windows/audio.c index 6431f76bb..434b33410 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -6,6 +6,8 @@ #include +#include "audio-common.h" + #include #include @@ -21,23 +23,17 @@ DEFINE_GUID(OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x8 #include #include -#define AUDIO_SAMPLE_SIZE(fmt) ((fmt) == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t)) #define AUDIO_BUFFER_SIZE ((1 * 1000 * 1000 * 1000) / 100) // 1 second #define AUDIO_REINIT_MAX_TRIES 5 #define AUDIO_REINIT_DELAY_MS 100 struct MTY_Audio { + struct audio_common cmn; bool playing; bool notification_init; bool fallback; - MTY_AudioSampleFormat sample_format; - uint32_t sample_rate; - uint32_t min_buffer; - uint32_t max_buffer; - uint32_t channels_mask; - uint32_t channels; - WORD bytes_per_sample; + WORD bytes_per_frame; WCHAR *device_id; UINT32 buffer_size; IMMDeviceEnumerator *enumerator; @@ -209,19 +205,17 @@ static HRESULT audio_device_create(MTY_Audio *ctx) goto except; } - WORD sample_size = AUDIO_SAMPLE_SIZE(ctx->sample_format); - WAVEFORMATEXTENSIBLE pwfx = { .Format.wFormatTag = WAVE_FORMAT_PCM, - .Format.nChannels = (WORD) ctx->channels, - .Format.nSamplesPerSec = ctx->sample_rate, - .Format.wBitsPerSample = sample_size * 8, - .Format.nBlockAlign = (WORD) ctx->channels * sample_size, - .Format.nAvgBytesPerSec = ctx->sample_rate * ctx->channels * sample_size, + .Format.nChannels = (WORD) ctx->cmn.format.channels, + .Format.nSamplesPerSec = ctx->cmn.format.sampleRate, + .Format.wBitsPerSample = (WORD) ctx->cmn.stats.sample_size * 8, + .Format.nBlockAlign = (WORD) ctx->cmn.stats.frame_size, + .Format.nAvgBytesPerSec = (DWORD) ctx->cmn.stats.buffer_size, }; - if (ctx->channels > 2) { - if (!ctx->channels_mask) { + if (ctx->cmn.format.channels > 2) { + if (!ctx->cmn.format.channelsMask) { e = audio_get_extended_format(device, &pwfx); if (e != S_OK) goto except; @@ -231,8 +225,8 @@ static HRESULT audio_device_create(MTY_Audio *ctx) pwfx.Format.cbSize = 22; pwfx.Samples.wValidBitsPerSample = pwfx.Format.wBitsPerSample; - pwfx.dwChannelMask = ctx->channels_mask; - pwfx.SubFormat = ctx->sample_format == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + pwfx.dwChannelMask = ctx->cmn.format.channelsMask; + pwfx.SubFormat = ctx->cmn.format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : OWN_KSDATAFORMAT_SUBTYPE_PCM; } } @@ -246,7 +240,7 @@ static HRESULT audio_device_create(MTY_Audio *ctx) goto except; } - ctx->bytes_per_sample = pwfx.Format.wBitsPerSample / 8; + ctx->bytes_per_frame = pwfx.Format.nBlockAlign; e = IAudioClient_GetBufferSize(ctx->client, &ctx->buffer_size); if (e != S_OK) { @@ -275,16 +269,9 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, uint32_t maxBuffer, const char *deviceID, bool fallback) { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); - ctx->sample_format = format.sampleFormat; - ctx->sample_rate = format.sampleRate; - ctx->channels_mask = format.channelsMask; - ctx->channels = format.channels; + audio_common_init(&ctx->cmn, format, minBuffer, maxBuffer); ctx->fallback = fallback; - uint32_t frames_per_ms = lrint((float) format.sampleRate / 1000.0f); - ctx->min_buffer = minBuffer * frames_per_ms; - ctx->max_buffer = maxBuffer * frames_per_ms; - if (deviceID) ctx->device_id = MTY_MultiToWideD(deviceID); @@ -443,7 +430,7 @@ uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) { uint32_t queued = 0; audio_get_queued_frames(ctx, &queued); - return lrint((float) queued / ((float) ctx->sample_rate / 1000.0f)); + return lrint((float) queued / ((float) ctx->cmn.format.sampleRate / 1000.0f)); } void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) @@ -466,7 +453,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) } // Stop playing and flush if we've exceeded the maximum buffer or underrun - if (ctx->playing && (queued > ctx->max_buffer || queued == 0)) + if (ctx->playing && (queued > ctx->cmn.stats.max_buffer || queued == 0)) MTY_AudioReset(ctx); if (ctx->buffer_size - queued >= count) { @@ -474,12 +461,12 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) e = IAudioRenderClient_GetBuffer(ctx->render, count, &buffer); if (e == S_OK) { - memcpy(buffer, frames, count * ctx->channels * ctx->bytes_per_sample); + memcpy(buffer, frames, count * ctx->bytes_per_frame); IAudioRenderClient_ReleaseBuffer(ctx->render, count, 0); } // Begin playing again when the minimum buffer has been reached - if (!ctx->playing && queued + count >= ctx->min_buffer) + if (!ctx->playing && queued + count >= ctx->cmn.stats.min_buffer) audio_play(ctx); } } From ade114879b5514c05de61d316b1fd849507f7e1b Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Wed, 6 Nov 2024 15:53:58 -0500 Subject: [PATCH 21/32] For consistency with external literature, rename "channelsMask" to "channelMask" --- src/matoya.h | 2 +- src/unix/apple/audio.c | 4 ++-- src/unix/linux/x11/audio.c | 2 +- src/windows/audio.c | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index e87d9e10c..04352d11b 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1471,7 +1471,7 @@ typedef struct { MTY_AudioSampleFormat sampleFormat; ///< Format of audio samples. uint32_t sampleRate; ///< Number of audio samples per second. Usually set to 48000. uint32_t channels; ///< Number of audio channels. - uint32_t channelsMask; ///< Opaque bitmask that defines which channels are present in the audio data. + uint32_t channelMask; ///< Opaque bitmask that defines which channels are present in the audio data. ///< Follows standard surround sound speaker ids (see ///< https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels). ///< For `channels` > 2, specify this value correctly in order diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index deae4535c..dbac817e6 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -164,10 +164,10 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) } // Specify channel configuration - if (ctx->cmn.format.channelsMask) { + if (ctx->cmn.format.channelMask) { AudioChannelLayout channel_layout = { .mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap, - .mChannelBitmap = ctx->cmn.format.channelsMask, // Core Audio channel bitmap follows the spec that the WAVE format + .mChannelBitmap = ctx->cmn.format.channelMask, // Core Audio channel bitmap follows the spec that the WAVE format // follows, so we can simply pass in the mask as-is }; diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index c76c2f057..a729b363c 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -45,7 +45,7 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, snd_pcm_hw_params_set_format(ctx->pcm, params, format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? SND_PCM_FORMAT_FLOAT : SND_PCM_FORMAT_S16); snd_pcm_hw_params_set_channels(ctx->pcm, params, format.channels); - // XXX: Channel config for ALSA can't be specified via the opaque `format.channelsMask` + // XXX: Channel config for ALSA can't be specified via the opaque `format.channelMask` // Instead, an explicit channel mapping array is required. To be implemented in the future. // See `snd_pcm_set_chmap`: // 1. https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html#ga60ee7d2c2555e21dbc844a1b73839085 diff --git a/src/windows/audio.c b/src/windows/audio.c index 434b33410..b42de6461 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -215,7 +215,7 @@ static HRESULT audio_device_create(MTY_Audio *ctx) }; if (ctx->cmn.format.channels > 2) { - if (!ctx->cmn.format.channelsMask) { + if (!ctx->cmn.format.channelMask) { e = audio_get_extended_format(device, &pwfx); if (e != S_OK) goto except; @@ -225,7 +225,7 @@ static HRESULT audio_device_create(MTY_Audio *ctx) pwfx.Format.cbSize = 22; pwfx.Samples.wValidBitsPerSample = pwfx.Format.wBitsPerSample; - pwfx.dwChannelMask = ctx->cmn.format.channelsMask; + pwfx.dwChannelMask = ctx->cmn.format.channelMask; pwfx.SubFormat = ctx->cmn.format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : OWN_KSDATAFORMAT_SUBTYPE_PCM; } From 6e883d454fc84f45148ba0d13a3052b3fc93410e Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Wed, 27 Nov 2024 09:30:38 -0500 Subject: [PATCH 22/32] Address code review comments --- src/matoya.h | 1 + src/unix/apple/audio.c | 5 +++++ src/unix/linux/android/audio.c | 4 ++-- src/unix/linux/x11/audio.c | 8 ++++---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index 04352d11b..6f06f2ff1 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1462,6 +1462,7 @@ typedef struct MTY_Resampler MTY_Resampler; /// @brief Audio sample formats. Currently only PCM formats. typedef enum { + MTY_AUDIO_SAMPLE_FORMAT_UNKNOWN = 0, ///< Unknown format. MTY_AUDIO_SAMPLE_FORMAT_FLOAT = 1, ///< 32-bit floating point. MTY_AUDIO_SAMPLE_FORMAT_INT16 = 2, ///< 16-bit signed integer. } MTY_AudioSampleFormat; diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index dbac817e6..670ae8d65 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -90,6 +90,11 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) if (AUDIO_HW_ERROR(e)) goto except; + if (data_size == 0) { + e = kAudioHardwareBadDeviceError; + goto except; + } + device_ids = calloc(1, data_size); e = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAddr, 0, NULL, &data_size, device_ids); if (AUDIO_HW_ERROR(e)) diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index eeac4da54..2cbc16bff 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -8,10 +8,10 @@ #include #include -#include "audio-common.h" - #include +#include "audio-common.h" + struct MTY_Audio { struct audio_common cmn; diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index a729b363c..2f5413b82 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -2,15 +2,15 @@ // If a copy of the MIT License was not distributed with this file, // You can obtain one at https://spdx.org/licenses/MIT.html. -#include "matoya.h" - #include #include -#include "audio-common.h" - #include "dl/libasound.h" +#include "matoya.h" + +#include "audio-common.h" + struct MTY_Audio { struct audio_common cmn; snd_pcm_t *pcm; From 07a78998c4bbe27a93d0749a86c19cae66761522 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Wed, 27 Nov 2024 12:59:14 -0500 Subject: [PATCH 23/32] Rename stats -> computed --- src/audio-common.h | 12 ++++++------ src/unix/apple/audio.c | 16 ++++++++-------- src/unix/linux/android/audio.c | 16 ++++++++-------- src/unix/linux/x11/audio.c | 16 ++++++++-------- src/windows/audio.c | 10 +++++----- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/audio-common.h b/src/audio-common.h index a097b9612..5ff8d800d 100644 --- a/src/audio-common.h +++ b/src/audio-common.h @@ -15,7 +15,7 @@ struct audio_common { uint32_t buffer_size; ///< Size in bytes of a 1-second buffer uint32_t min_buffer; ///< Minimum number of frames of audio that must be enqueued before playback uint32_t max_buffer; ///< Maximum number of frames of audio that can be enqueued for playback - } stats; + } computed; }; static void audio_common_init(struct audio_common *ctx, MTY_AudioFormat fmt, uint32_t min_buffer_ms, @@ -23,12 +23,12 @@ static void audio_common_init(struct audio_common *ctx, MTY_AudioFormat fmt, uin { ctx->format = fmt; - ctx->stats.sample_size = fmt.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT + ctx->computed.sample_size = fmt.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? sizeof(float) : sizeof(int16_t); - ctx->stats.frame_size = fmt.channels * ctx->stats.sample_size; - ctx->stats.buffer_size = fmt.sampleRate * ctx->stats.frame_size; + ctx->computed.frame_size = fmt.channels * ctx->computed.sample_size; + ctx->computed.buffer_size = fmt.sampleRate * ctx->computed.frame_size; uint32_t samples_per_ms = lrintf(fmt.sampleRate / 1000.0f); - ctx->stats.min_buffer = min_buffer_ms * samples_per_ms; - ctx->stats.max_buffer = max_buffer_ms * samples_per_ms; + ctx->computed.min_buffer = min_buffer_ms * samples_per_ms; + ctx->computed.max_buffer = max_buffer_ms * samples_per_ms; } diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 670ae8d65..5bd5a3190 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -147,8 +147,8 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsPacked; format.mFramesPerPacket = 1; format.mChannelsPerFrame = ctx->cmn.format.channels; - format.mBitsPerChannel = ctx->cmn.stats.sample_size * 8; - format.mBytesPerPacket = ctx->cmn.stats.frame_size; + format.mBitsPerChannel = ctx->cmn.computed.sample_size * 8; + format.mBytesPerPacket = ctx->cmn.computed.frame_size; format.mBytesPerFrame = format.mBytesPerPacket; // Create a new audio queue, which by default chooses the device's default device @@ -185,7 +185,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) } for (int32_t x = 0; x < AUDIO_BUFS; x++) { - e = AudioQueueAllocateBuffer(ctx->q, ctx->cmn.stats.buffer_size, &ctx->audio_buf[x]); + e = AudioQueueAllocateBuffer(ctx->q, ctx->cmn.computed.buffer_size, &ctx->audio_buf[x]); if (AUDIO_SV_ERROR(e)) { MTY_Log("'AudioQueueAllocateBuffer' failed with error 0x%X", e); goto except; @@ -250,7 +250,7 @@ static uint32_t audio_get_queued_frames(MTY_Audio *ctx) } } - return queued / ctx->cmn.stats.frame_size; + return queued / ctx->cmn.computed.frame_size; } static void audio_play(MTY_Audio *ctx) @@ -278,14 +278,14 @@ uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->cmn.stats.frame_size; + size_t size = count * ctx->cmn.computed.frame_size; uint32_t queued = audio_get_queued_frames(ctx); // Stop playing and flush if we've exceeded the maximum buffer or underrun - if (ctx->playing && (queued > ctx->cmn.stats.max_buffer || queued == 0)) + if (ctx->playing && (queued > ctx->cmn.computed.max_buffer || queued == 0)) MTY_AudioReset(ctx); - if (size <= ctx->cmn.stats.buffer_size) { + if (size <= ctx->cmn.computed.buffer_size) { for (uint8_t x = 0; x < AUDIO_BUFS; x++) { if (MTY_Atomic32Get(&ctx->in_use[x]) == 0) { AudioQueueBufferRef buf = ctx->audio_buf[x]; @@ -306,7 +306,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) } // Begin playing again when the minimum buffer has been reached - if (!ctx->playing && queued + count >= ctx->cmn.stats.min_buffer) + if (!ctx->playing && queued + count >= ctx->cmn.computed.min_buffer) audio_play(ctx); } } diff --git a/src/unix/linux/android/audio.c b/src/unix/linux/android/audio.c index 2cbc16bff..5592aa94d 100644 --- a/src/unix/linux/android/audio.c +++ b/src/unix/linux/android/audio.c @@ -40,7 +40,7 @@ static aaudio_data_callback_result_t audio_callback(AAudioStream *stream, void * MTY_MutexLock(ctx->mutex); - size_t want_size = numFrames * ctx->cmn.stats.frame_size; + size_t want_size = numFrames * ctx->cmn.computed.frame_size; if (ctx->playing && ctx->size >= want_size) { memcpy(audioData, ctx->buffer, want_size); @@ -62,11 +62,11 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, { MTY_Audio *ctx = MTY_Alloc(1, sizeof(MTY_Audio)); audio_common_init(&ctx->cmn, format, minBuffer, maxBuffer); - ctx->min_buffer_size = ctx->cmn.stats.min_buffer * ctx->cmn.stats.frame_size; - ctx->max_buffer_size = ctx->cmn.stats.max_buffer * ctx->cmn.stats.frame_size; + ctx->min_buffer_size = ctx->cmn.computed.min_buffer * ctx->cmn.computed.frame_size; + ctx->max_buffer_size = ctx->cmn.computed.max_buffer * ctx->cmn.computed.frame_size; ctx->mutex = MTY_MutexCreate(); - ctx->buffer = MTY_Alloc(ctx->cmn.stats.buffer_size, 1); + ctx->buffer = MTY_Alloc(ctx->cmn.computed.buffer_size, 1); return ctx; } @@ -111,7 +111,7 @@ void MTY_AudioReset(MTY_Audio *ctx) uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) { - return (ctx->size / ctx->cmn.stats.frame_size) / ctx->cmn.format.sampleRate * 1000; + return (ctx->size / ctx->cmn.computed.frame_size) / ctx->cmn.format.sampleRate * 1000; } static void audio_start(MTY_Audio *ctx) @@ -138,7 +138,7 @@ static void audio_start(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t data_size = count * ctx->cmn.stats.frame_size; + size_t data_size = count * ctx->cmn.computed.frame_size; audio_start(ctx); @@ -147,7 +147,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) if (ctx->size + data_size >= ctx->max_buffer_size) ctx->flushing = true; - size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->cmn.stats.frame_size; + size_t minimum_request = AAudioStream_getFramesPerBurst(ctx->stream) * ctx->cmn.computed.frame_size; if (ctx->flushing && ctx->size < minimum_request) { memset(ctx->buffer, 0, ctx->size); ctx->size = 0; @@ -158,7 +158,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) ctx->flushing = false; } - if (!ctx->flushing && data_size + ctx->size <= ctx->cmn.stats.buffer_size) { + if (!ctx->flushing && data_size + ctx->size <= ctx->cmn.computed.buffer_size) { memcpy(ctx->buffer + ctx->size, frames, data_size); ctx->size += data_size; } diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 2f5413b82..0b8de6de9 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -54,7 +54,7 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format, uint32_t minBuffer, snd_pcm_hw_params(ctx->pcm, params); snd_pcm_nonblock(ctx->pcm, 1); - ctx->buf = MTY_Alloc(ctx->cmn.stats.buffer_size, 1); + ctx->buf = MTY_Alloc(ctx->cmn.computed.buffer_size, 1); except: @@ -81,7 +81,7 @@ void MTY_AudioDestroy(MTY_Audio **audio) static uint32_t audio_get_queued_frames(MTY_Audio *ctx) { - uint32_t queued = ctx->pos / ctx->cmn.stats.frame_size; + uint32_t queued = ctx->pos / ctx->cmn.computed.frame_size; if (ctx->playing) { snd_pcm_status_t *status = NULL; @@ -120,25 +120,25 @@ uint32_t MTY_AudioGetQueued(MTY_Audio *ctx) void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) { - size_t size = count * ctx->cmn.stats.frame_size; + size_t size = count * ctx->cmn.computed.frame_size; uint32_t queued = audio_get_queued_frames(ctx); // Stop playing and flush if we've exceeded the maximum buffer or underrun - if (ctx->playing && (queued > ctx->cmn.stats.max_buffer || queued == 0)) + if (ctx->playing && (queued > ctx->cmn.computed.max_buffer || queued == 0)) MTY_AudioReset(ctx); - if (ctx->pos + size <= ctx->cmn.stats.buffer_size) { - memcpy(ctx->buf + ctx->pos, frames, count * ctx->cmn.stats.frame_size); + if (ctx->pos + size <= ctx->cmn.computed.buffer_size) { + memcpy(ctx->buf + ctx->pos, frames, count * ctx->cmn.computed.frame_size); ctx->pos += size; } // Begin playing again when the minimum buffer has been reached - if (!ctx->playing && queued + count >= ctx->cmn.stats.min_buffer) + if (!ctx->playing && queued + count >= ctx->cmn.computed.min_buffer) audio_play(ctx); if (ctx->playing) { - int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / ctx->cmn.stats.frame_size); + int32_t e = snd_pcm_writei(ctx->pcm, ctx->buf, ctx->pos / ctx->cmn.computed.frame_size); if (e >= 0) { ctx->pos = 0; diff --git a/src/windows/audio.c b/src/windows/audio.c index b42de6461..a119bf82a 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -209,9 +209,9 @@ static HRESULT audio_device_create(MTY_Audio *ctx) .Format.wFormatTag = WAVE_FORMAT_PCM, .Format.nChannels = (WORD) ctx->cmn.format.channels, .Format.nSamplesPerSec = ctx->cmn.format.sampleRate, - .Format.wBitsPerSample = (WORD) ctx->cmn.stats.sample_size * 8, - .Format.nBlockAlign = (WORD) ctx->cmn.stats.frame_size, - .Format.nAvgBytesPerSec = (DWORD) ctx->cmn.stats.buffer_size, + .Format.wBitsPerSample = (WORD) ctx->cmn.computed.sample_size * 8, + .Format.nBlockAlign = (WORD) ctx->cmn.computed.frame_size, + .Format.nAvgBytesPerSec = (DWORD) ctx->cmn.computed.buffer_size, }; if (ctx->cmn.format.channels > 2) { @@ -453,7 +453,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) } // Stop playing and flush if we've exceeded the maximum buffer or underrun - if (ctx->playing && (queued > ctx->cmn.stats.max_buffer || queued == 0)) + if (ctx->playing && (queued > ctx->cmn.computed.max_buffer || queued == 0)) MTY_AudioReset(ctx); if (ctx->buffer_size - queued >= count) { @@ -466,7 +466,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) } // Begin playing again when the minimum buffer has been reached - if (!ctx->playing && queued + count >= ctx->cmn.stats.min_buffer) + if (!ctx->playing && queued + count >= ctx->cmn.computed.min_buffer) audio_play(ctx); } } From 1190e83b74acb3c8e834c3026600e7fa73ebebe5 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Wed, 27 Nov 2024 14:20:35 -0500 Subject: [PATCH 24/32] Include orders --- src/unix/apple/audio.c | 4 ++-- src/windows/audio.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 5bd5a3190..de730539e 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -4,11 +4,11 @@ #include "matoya.h" -#include "audio-common.h" - #include #include +#include "audio-common.h" + #define AUDIO_BUFS 64 #define AUDIO_HW_ERROR(e) ((e) != kAudioHardwareNoError) diff --git a/src/windows/audio.c b/src/windows/audio.c index a119bf82a..7b8e92f37 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -6,8 +6,6 @@ #include -#include "audio-common.h" - #include #include @@ -23,6 +21,8 @@ DEFINE_GUID(OWN_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x8 #include #include +#include "audio-common.h" + #define AUDIO_BUFFER_SIZE ((1 * 1000 * 1000 * 1000) / 100) // 1 second #define AUDIO_REINIT_MAX_TRIES 5 From 89378c10b8efb4683ed0e61c9e9d79de65e11ad4 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Mon, 2 Dec 2024 09:54:43 -0500 Subject: [PATCH 25/32] Reorder a few things --- src/unix/apple/audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index de730539e..fdaca2d09 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -4,8 +4,8 @@ #include "matoya.h" -#include #include +#include #include "audio-common.h" @@ -36,13 +36,13 @@ static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPro char *uid = NULL; CFStringRef uid_cf = NULL; - UInt32 data_size = sizeof(CFStringRef); AudioObjectPropertyAddress propAddr = { .mSelector = kAudioDevicePropertyDeviceUID, .mScope = prop_scope, .mElement = prop_element, }; + UInt32 data_size = sizeof(CFStringRef); OSStatus e = AudioObjectGetPropertyData(device, &propAddr, 0, NULL, &data_size, &uid_cf); if (AUDIO_HW_ERROR(e)) goto except; From 2eaefcc134336f8a3a94f4c8c361a34dbbd4c720 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Mon, 2 Dec 2024 12:12:02 -0500 Subject: [PATCH 26/32] Fixed one bug in macos audio.c and made some style changes. --- src/unix/apple/audio.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index fdaca2d09..32b2e1b49 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -60,7 +60,7 @@ static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPro except: - if (AUDIO_HW_ERROR(e)) { + if (e != 0) { if (uid_cf) CFRelease(uid_cf); free(uid); @@ -116,8 +116,9 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) char *uid = NULL; CFStringRef uid_cf = NULL; - if (AUDIO_HW_ERROR(audio_object_get_device_uid(device_ids[i], - propAddr.mScope, propAddr.mElement, &uid, &uid_cf))) + OSStatus e_uid = audio_object_get_device_uid(device_ids[i], propAddr.mScope, + propAddr.mElement, &uid, &uid_cf); + if (AUDIO_HW_ERROR(e_uid)) continue; if (!strcmp(deviceID, uid)) { @@ -140,11 +141,18 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) // Initialize the selected device + AudioFormatFlags format_flags = kAudioFormatFlagIsPacked; + if (ctx->cmn.format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT) { + format_flags |= kAudioFormatFlagIsFloat; + + } else { + format_flags |= kAudioFormatFlagIsSignedInteger; + } + AudioStreamBasicDescription format = {0}; format.mSampleRate = ctx->cmn.format.sampleRate; format.mFormatID = kAudioFormatLinearPCM; - format.mFormatFlags = (ctx->cmn.format.sampleFormat == MTY_AUDIO_SAMPLE_FORMAT_FLOAT ? - kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger) | kAudioFormatFlagIsPacked; + format.mFormatFlags = format_flags; format.mFramesPerPacket = 1; format.mChannelsPerFrame = ctx->cmn.format.channels; format.mBitsPerChannel = ctx->cmn.computed.sample_size * 8; @@ -169,11 +177,12 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) } // Specify channel configuration - if (ctx->cmn.format.channelMask) { + if (ctx->cmn.format.channelMask != 0) { AudioChannelLayout channel_layout = { .mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap, - .mChannelBitmap = ctx->cmn.format.channelMask, // Core Audio channel bitmap follows the spec that the WAVE format - // follows, so we can simply pass in the mask as-is + + // Core Audio channel bitmap follows the same spec as WAVE format so we can pass in the mask as-is + .mChannelBitmap = ctx->cmn.format.channelMask, }; e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_ChannelLayout, @@ -213,7 +222,7 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format_in, uint32_t minBuffer, uint32 OSStatus e = audio_device_create(ctx, deviceID); // Upon failure initializing the given device, try again with the system default device - if (AUDIO_SV_ERROR(e) && deviceID && deviceID[0]) + if (AUDIO_SV_ERROR(e) && deviceID && deviceID[0] != 0) e = audio_device_create(ctx, NULL); if (AUDIO_SV_ERROR(e)) From d973a821cc546cc47fe06b6ad754c87e41719ed8 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Tue, 3 Dec 2024 16:35:40 -0500 Subject: [PATCH 27/32] A little more cleanup --- src/windows/audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/windows/audio.c b/src/windows/audio.c index 7b8e92f37..9d1fc169f 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -215,7 +215,7 @@ static HRESULT audio_device_create(MTY_Audio *ctx) }; if (ctx->cmn.format.channels > 2) { - if (!ctx->cmn.format.channelMask) { + if (ctx->cmn.format.channelMask == 0) { e = audio_get_extended_format(device, &pwfx); if (e != S_OK) goto except; @@ -233,7 +233,7 @@ static HRESULT audio_device_create(MTY_Audio *ctx) e = IAudioClient_Initialize(ctx->client, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, - AUDIO_BUFFER_SIZE, 0, (const WAVEFORMATEX *) &pwfx, NULL); + AUDIO_BUFFER_SIZE, 0, &pwfx.Format, NULL); if (e != S_OK) { MTY_Log("'IAudioClient_Initialize' failed with HRESULT 0x%X", e); From 29f1f8fd80521312bd2d0a1f829d506fa69f0114 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Fri, 6 Dec 2024 12:45:59 -0500 Subject: [PATCH 28/32] Got rid of strictly unnecessary diffs to make reviewing easier. --- src/audio-common.h | 2 +- src/unix/apple/audio.c | 8 ++++---- src/unix/linux/x11/audio.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/audio-common.h b/src/audio-common.h index 5ff8d800d..fffe3c623 100644 --- a/src/audio-common.h +++ b/src/audio-common.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include "matoya.h" diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 32b2e1b49..05364e26f 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -240,7 +240,7 @@ void MTY_AudioDestroy(MTY_Audio **audio) if (ctx->q) { OSStatus e = AudioQueueDispose(ctx->q, true); - if (AUDIO_SV_ERROR(e)) + if (e != kAudioServicesNoError) MTY_Log("'AudioQueueDispose' failed with error 0x%X", e); } @@ -267,7 +267,7 @@ static void audio_play(MTY_Audio *ctx) if (ctx->playing) return; - if (!AUDIO_SV_ERROR(AudioQueueStart(ctx->q, NULL))) + if (AudioQueueStart(ctx->q, NULL) == kAudioServicesNoError) ctx->playing = true; } @@ -276,7 +276,7 @@ void MTY_AudioReset(MTY_Audio *ctx) if (!ctx->playing) return; - if (!AUDIO_SV_ERROR(AudioQueueStop(ctx->q, true))) + if (AudioQueueStop(ctx->q, true) == kAudioServicesNoError) ctx->playing = false; } @@ -304,7 +304,7 @@ void MTY_AudioQueue(MTY_Audio *ctx, const int16_t *frames, uint32_t count) buf->mUserData = (void *) (uintptr_t) x; OSStatus e = AudioQueueEnqueueBuffer(ctx->q, buf, 0, NULL); - if (!AUDIO_SV_ERROR(kAudioServicesNoError)) { + if (e == kAudioServicesNoError) { MTY_Atomic32Set(&ctx->in_use[x], 1); } else { diff --git a/src/unix/linux/x11/audio.c b/src/unix/linux/x11/audio.c index 0b8de6de9..8c7ad240f 100644 --- a/src/unix/linux/x11/audio.c +++ b/src/unix/linux/x11/audio.c @@ -2,13 +2,13 @@ // If a copy of the MIT License was not distributed with this file, // You can obtain one at https://spdx.org/licenses/MIT.html. +#include "matoya.h" + #include #include #include "dl/libasound.h" -#include "matoya.h" - #include "audio-common.h" struct MTY_Audio { From 3dda3f724e7f4e3382d2c2206d89c5b7ac4a9fc5 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Fri, 6 Dec 2024 12:47:19 -0500 Subject: [PATCH 29/32] MTY CI From 0bc0af9de8eb733495326dbe9e8983bbf6100dad Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Tue, 10 Dec 2024 13:32:07 -0500 Subject: [PATCH 30/32] Remove some more strictly unnecessary diffs --- src/unix/apple/audio.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/unix/apple/audio.c b/src/unix/apple/audio.c index 05364e26f..113c875f6 100644 --- a/src/unix/apple/audio.c +++ b/src/unix/apple/audio.c @@ -11,9 +11,6 @@ #define AUDIO_BUFS 64 -#define AUDIO_HW_ERROR(e) ((e) != kAudioHardwareNoError) -#define AUDIO_SV_ERROR(e) ((e) != kAudioServicesNoError) - struct MTY_Audio { struct audio_common cmn; AudioQueueRef q; @@ -44,7 +41,7 @@ static OSStatus audio_object_get_device_uid(AudioObjectID device, AudioObjectPro UInt32 data_size = sizeof(CFStringRef); OSStatus e = AudioObjectGetPropertyData(device, &propAddr, 0, NULL, &data_size, &uid_cf); - if (AUDIO_HW_ERROR(e)) + if (e != kAudioHardwareNoError) goto except; UInt32 prop_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(uid_cf), kCFStringEncodingUTF8); @@ -87,7 +84,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) UInt32 data_size = 0; OSStatus e = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &propAddr, 0, NULL, &data_size); - if (AUDIO_HW_ERROR(e)) + if (e != kAudioHardwareNoError) goto except; if (data_size == 0) { @@ -97,7 +94,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) device_ids = calloc(1, data_size); e = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAddr, 0, NULL, &data_size, device_ids); - if (AUDIO_HW_ERROR(e)) + if (e != kAudioHardwareNoError) goto except; uint32_t n = (uint32_t) (data_size / sizeof(AudioObjectID)); @@ -118,7 +115,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) OSStatus e_uid = audio_object_get_device_uid(device_ids[i], propAddr.mScope, propAddr.mElement, &uid, &uid_cf); - if (AUDIO_HW_ERROR(e_uid)) + if (e_uid != kAudioHardwareNoError) continue; if (!strcmp(deviceID, uid)) { @@ -161,7 +158,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) // Create a new audio queue, which by default chooses the device's default device e = AudioQueueNewOutput(&format, audio_queue_callback, ctx, NULL, NULL, 0, &ctx->q); - if (AUDIO_SV_ERROR(e)) { + if (e != kAudioServicesNoError) { MTY_Log("'AudioQueueNewOutput' failed with error 0x%X", e); goto except; } @@ -170,7 +167,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) if (selected_device_uid) { e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_CurrentDevice, (const void *) &selected_device_uid, sizeof(CFStringRef)); - if (AUDIO_SV_ERROR(e)) { + if (e != kAudioServicesNoError) { MTY_Log("'AudioQueueSetProperty(kAudioQueueProperty_CurrentDevice)' failed with error 0x%X", e); goto except; } @@ -187,7 +184,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) e = AudioQueueSetProperty(ctx->q, kAudioQueueProperty_ChannelLayout, (const void *) &channel_layout, sizeof(AudioChannelLayout)); - if (AUDIO_SV_ERROR(e)) { + if (e != kAudioServicesNoError) { MTY_Log("'AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)' failed with error 0x%X", e); goto except; } @@ -195,7 +192,7 @@ static OSStatus audio_device_create(MTY_Audio *ctx, const char *deviceID) for (int32_t x = 0; x < AUDIO_BUFS; x++) { e = AudioQueueAllocateBuffer(ctx->q, ctx->cmn.computed.buffer_size, &ctx->audio_buf[x]); - if (AUDIO_SV_ERROR(e)) { + if (e != kAudioServicesNoError) { MTY_Log("'AudioQueueAllocateBuffer' failed with error 0x%X", e); goto except; } @@ -222,10 +219,10 @@ MTY_Audio *MTY_AudioCreate(MTY_AudioFormat format_in, uint32_t minBuffer, uint32 OSStatus e = audio_device_create(ctx, deviceID); // Upon failure initializing the given device, try again with the system default device - if (AUDIO_SV_ERROR(e) && deviceID && deviceID[0] != 0) + if (e != kAudioServicesNoError && deviceID && deviceID[0] != 0) e = audio_device_create(ctx, NULL); - if (AUDIO_SV_ERROR(e)) + if (e != kAudioServicesNoError) MTY_AudioDestroy(&ctx); return ctx; From fdaeca47359c8eb2c41c1f7572ddbf648ee93ef6 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Tue, 17 Dec 2024 15:00:06 -0500 Subject: [PATCH 31/32] It makes much more sense (and was trivial) to have the audio channel IDs and configuration enums in matoya. --- src/matoya.h | 55 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/matoya.h b/src/matoya.h index 6f06f2ff1..0705ca28f 100644 --- a/src/matoya.h +++ b/src/matoya.h @@ -1467,16 +1467,65 @@ typedef enum { MTY_AUDIO_SAMPLE_FORMAT_INT16 = 2, ///< 16-bit signed integer. } MTY_AudioSampleFormat; +/// @brief Surround sound audio channel IDs. +/// @details Used in ::MTY_AudioFormat. Follows standard surround sound speaker ids +/// (see https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels). +typedef enum { + MTY_AUDIO_CHANNEL_FL = 0x00000001, + MTY_AUDIO_CHANNEL_FR = 0x00000002, + MTY_AUDIO_CHANNEL_FC = 0x00000004, + MTY_AUDIO_CHANNEL_LFE = 0x00000008, + MTY_AUDIO_CHANNEL_BL = 0x00000010, + MTY_AUDIO_CHANNEL_BR = 0x00000020, + MTY_AUDIO_CHANNEL_FLC = 0x00000040, + MTY_AUDIO_CHANNEL_FRC = 0x00000080, + MTY_AUDIO_CHANNEL_BC = 0x00000100, + MTY_AUDIO_CHANNEL_SL = 0x00000200, + MTY_AUDIO_CHANNEL_SR = 0x00000400, + MTY_AUDIO_CHANNEL_TC = 0x00000800, + MTY_AUDIO_CHANNEL_TFL = 0x00001000, + MTY_AUDIO_CHANNEL_TFC = 0x00002000, + MTY_AUDIO_CHANNEL_TFR = 0x00004000, + MTY_AUDIO_CHANNEL_TBL = 0x00008000, + MTY_AUDIO_CHANNEL_TBC = 0x00010000, + MTY_AUDIO_CHANNEL_TBR = 0x00020000, + + // Some common configurations + MTY_AUDIO_CHANNEL_CFG_UNKNOWN = 0, + MTY_AUDIO_CHANNEL_CFG_MONO = MTY_AUDIO_CHANNEL_FL, + MTY_AUDIO_CHANNEL_CFG_STEREO = MTY_AUDIO_CHANNEL_FL | MTY_AUDIO_CHANNEL_FR, + MTY_AUDIO_CHANNEL_CFG_5_1_SURROUND = MTY_AUDIO_CHANNEL_FL | MTY_AUDIO_CHANNEL_FR | MTY_AUDIO_CHANNEL_FC | + MTY_AUDIO_CHANNEL_LFE | MTY_AUDIO_CHANNEL_SL | MTY_AUDIO_CHANNEL_SR, + MTY_AUDIO_CHANNEL_CFG_5_1_REAR = MTY_AUDIO_CHANNEL_FL | MTY_AUDIO_CHANNEL_FR | MTY_AUDIO_CHANNEL_FC | + MTY_AUDIO_CHANNEL_LFE | MTY_AUDIO_CHANNEL_BL | MTY_AUDIO_CHANNEL_BR, + MTY_AUDIO_CHANNEL_CFG_7_1_SURROUND = MTY_AUDIO_CHANNEL_FL | MTY_AUDIO_CHANNEL_FR | MTY_AUDIO_CHANNEL_FC | + MTY_AUDIO_CHANNEL_LFE | MTY_AUDIO_CHANNEL_BL | MTY_AUDIO_CHANNEL_BR | + MTY_AUDIO_CHANNEL_SL | MTY_AUDIO_CHANNEL_SR, + MTY_AUDIO_CHANNEL_CFG_7_1_WIDE = MTY_AUDIO_CHANNEL_FL | MTY_AUDIO_CHANNEL_FR | MTY_AUDIO_CHANNEL_FC | + MTY_AUDIO_CHANNEL_LFE | MTY_AUDIO_CHANNEL_BL | MTY_AUDIO_CHANNEL_BR | + MTY_AUDIO_CHANNEL_FLC | MTY_AUDIO_CHANNEL_FRC, + MTY_AUDIO_CHANNEL_CFG_7_1_SIDE = MTY_AUDIO_CHANNEL_FL | MTY_AUDIO_CHANNEL_FR | MTY_AUDIO_CHANNEL_FC | + MTY_AUDIO_CHANNEL_LFE | MTY_AUDIO_CHANNEL_SL | MTY_AUDIO_CHANNEL_SR | + MTY_AUDIO_CHANNEL_FLC | MTY_AUDIO_CHANNEL_FRC, + MTY_AUDIO_CHANNEL_CFG_7_1_4_ATMOS = MTY_AUDIO_CHANNEL_FL | MTY_AUDIO_CHANNEL_FR | MTY_AUDIO_CHANNEL_FC | + MTY_AUDIO_CHANNEL_LFE | MTY_AUDIO_CHANNEL_BL | MTY_AUDIO_CHANNEL_BR | + MTY_AUDIO_CHANNEL_SL | MTY_AUDIO_CHANNEL_SR | MTY_AUDIO_CHANNEL_TFL | + MTY_AUDIO_CHANNEL_TFR | MTY_AUDIO_CHANNEL_TBL | MTY_AUDIO_CHANNEL_TBR, +} MTY_AudioChannelID; + /// @brief Format description for an audio device. typedef struct { MTY_AudioSampleFormat sampleFormat; ///< Format of audio samples. uint32_t sampleRate; ///< Number of audio samples per second. Usually set to 48000. uint32_t channels; ///< Number of audio channels. - uint32_t channelMask; ///< Opaque bitmask that defines which channels are present in the audio data. - ///< Follows standard surround sound speaker ids (see - ///< https://en.wikipedia.org/wiki/Surround_sound#Standard_speaker_channels). + uint32_t channelMask; ///< Bitmask that defines which channels are present in the audio data. + ///< Should contain only values defined in ::MTY_AudioChannelID. ///< For `channels` > 2, specify this value correctly in order ///< to yield accurate audio playback. + ///< Currently only supported by Windows and macOS. All other platforms + ///< will ignore this field. + ///< If set to MTY_AUDIO_CHANNEL_CFG_UNKNOWN, the platform will + ///< attempt to use reasonable defaults based on `channels`. } MTY_AudioFormat; /// @brief Create an MTY_Audio context for playback. From 6341fb16557e87d0ecc2acd3a2611863841e3447 Mon Sep 17 00:00:00 2001 From: Daniel Vijayakumar Date: Thu, 19 Dec 2024 12:59:45 -0500 Subject: [PATCH 32/32] Fix magic constant --- src/windows/audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows/audio.c b/src/windows/audio.c index 9d1fc169f..0ca5024df 100644 --- a/src/windows/audio.c +++ b/src/windows/audio.c @@ -222,7 +222,7 @@ static HRESULT audio_device_create(MTY_Audio *ctx) } else { pwfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - pwfx.Format.cbSize = 22; + pwfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); pwfx.Samples.wValidBitsPerSample = pwfx.Format.wBitsPerSample; pwfx.dwChannelMask = ctx->cmn.format.channelMask;