mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-24 01:04:02 +02:00
Get audio working with Core Audio.
This commit is contained in:
@@ -532,6 +532,7 @@ typedef struct
|
|||||||
|
|
||||||
#define MAL_MAX_PERIODS_DSOUND 4
|
#define MAL_MAX_PERIODS_DSOUND 4
|
||||||
#define MAL_MAX_PERIODS_OPENAL 4
|
#define MAL_MAX_PERIODS_OPENAL 4
|
||||||
|
#define MAL_MAX_PERIODS_COREAUDIO 4
|
||||||
|
|
||||||
// Standard sample rates.
|
// Standard sample rates.
|
||||||
#define MAL_SAMPLE_RATE_8000 8000
|
#define MAL_SAMPLE_RATE_8000 8000
|
||||||
@@ -1523,6 +1524,9 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
|
|||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
mal_uint32 deviceObjectID;
|
mal_uint32 deviceObjectID;
|
||||||
|
/*AudioQueueRef*/ mal_ptr audioQueue;
|
||||||
|
/*AudioQueueBufferRef*/ mal_ptr audioQueueBuffers[MAL_MAX_PERIODS_COREAUDIO];
|
||||||
|
/*CFRunLoopRef*/ mal_ptr runLoop;
|
||||||
} coreaudio;
|
} coreaudio;
|
||||||
#endif
|
#endif
|
||||||
#ifdef MAL_SUPPORT_OSS
|
#ifdef MAL_SUPPORT_OSS
|
||||||
@@ -12507,6 +12511,8 @@ mal_result mal_device__stop_backend__jack(mal_device* pDevice)
|
|||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
#ifdef MAL_HAS_COREAUDIO
|
#ifdef MAL_HAS_COREAUDIO
|
||||||
#include <CoreAudio/CoreAudio.h>
|
#include <CoreAudio/CoreAudio.h>
|
||||||
|
#include <AudioToolbox/AudioToolbox.h>
|
||||||
|
//#include <AudioUnit/AudioUnit.h>
|
||||||
|
|
||||||
#include <TargetConditionals.h>
|
#include <TargetConditionals.h>
|
||||||
#if defined(TARGET_OS_OSX)
|
#if defined(TARGET_OS_OSX)
|
||||||
@@ -12778,21 +12784,31 @@ mal_result mal_get_device_object_ids__coreaudio(mal_context* pContext, UInt32* p
|
|||||||
return MAL_SUCCESS;
|
return MAL_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
mal_result mal_get_AudioObject_uid(AudioObjectID objectID, size_t bufferSize, char* bufferOut)
|
mal_result mal_get_AudioObject_uid_as_CFStringRef(AudioObjectID objectID, CFStringRef* pUID)
|
||||||
{
|
{
|
||||||
AudioObjectPropertyAddress propAddress;
|
AudioObjectPropertyAddress propAddress;
|
||||||
propAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
propAddress.mSelector = kAudioDevicePropertyDeviceUID;
|
||||||
propAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
propAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||||
propAddress.mElement = kAudioObjectPropertyElementMaster;
|
propAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||||
|
|
||||||
CFStringRef deviceName = NULL;
|
UInt32 dataSize = sizeof(*pUID);
|
||||||
UInt32 dataSize = sizeof(deviceName);
|
OSStatus status = AudioObjectGetPropertyData(objectID, &propAddress, 0, NULL, &dataSize, pUID);
|
||||||
OSStatus status = AudioObjectGetPropertyData(objectID, &propAddress, 0, NULL, &dataSize, &deviceName);
|
|
||||||
if (status != kAudioHardwareNoError) {
|
if (status != kAudioHardwareNoError) {
|
||||||
return mal_result_from_OSStatus(status);
|
return mal_result_from_OSStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CFStringGetCString(deviceName, bufferOut, bufferSize, kCFStringEncodingUTF8)) {
|
return MAL_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_result mal_get_AudioObject_uid(AudioObjectID objectID, size_t bufferSize, char* bufferOut)
|
||||||
|
{
|
||||||
|
CFStringRef uid;
|
||||||
|
mal_result result = mal_get_AudioObject_uid_as_CFStringRef(objectID, &uid);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CFStringGetCString(uid, bufferOut, bufferSize, kCFStringEncodingUTF8)) {
|
||||||
return MAL_ERROR;
|
return MAL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13119,7 +13135,7 @@ mal_result mal_get_AudioObject_sample_rates(AudioObjectID deviceObjectID, mal_de
|
|||||||
return MAL_SUCCESS;
|
return MAL_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
mal_result mal_get_AudioObject_get_closest_sample_rate(AudioObjectID deviceObjectID, mal_device_type type, mal_uint32 sampleRateIn, mal_uint32* pSampleRateOut)
|
mal_result mal_get_AudioObject_get_closest_sample_rate(AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32 sampleRateIn, mal_uint32* pSampleRateOut)
|
||||||
{
|
{
|
||||||
mal_assert(pSampleRateOut != NULL);
|
mal_assert(pSampleRateOut != NULL);
|
||||||
|
|
||||||
@@ -13127,7 +13143,7 @@ mal_result mal_get_AudioObject_get_closest_sample_rate(AudioObjectID deviceObjec
|
|||||||
|
|
||||||
UInt32 sampleRateRangeCount;
|
UInt32 sampleRateRangeCount;
|
||||||
AudioValueRange* pSampleRateRanges;
|
AudioValueRange* pSampleRateRanges;
|
||||||
mal_result result = mal_get_AudioObject_sample_rates(deviceObjectID, type, &sampleRateRangeCount, &pSampleRateRanges);
|
mal_result result = mal_get_AudioObject_sample_rates(deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges);
|
||||||
if (result != MAL_SUCCESS) {
|
if (result != MAL_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -13195,6 +13211,37 @@ mal_result mal_get_AudioObject_get_closest_sample_rate(AudioObjectID deviceObjec
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mal_result mal_get_AudioObject_closest_buffer_size_in_frames(AudioObjectID deviceObjectID, mal_device_type deviceType, mal_uint32 bufferSizeInFramesIn, mal_uint32* pBufferSizeInFramesOut)
|
||||||
|
{
|
||||||
|
mal_assert(pBufferSizeInFramesOut != NULL);
|
||||||
|
|
||||||
|
*pBufferSizeInFramesOut = 0; // Safety.
|
||||||
|
|
||||||
|
AudioObjectPropertyAddress propAddress;
|
||||||
|
propAddress.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
|
||||||
|
propAddress.mScope = (deviceType == mal_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput;
|
||||||
|
propAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||||
|
|
||||||
|
AudioValueRange bufferSizeRange;
|
||||||
|
UInt32 dataSize = sizeof(bufferSizeRange);
|
||||||
|
OSStatus status = AudioObjectGetPropertyData(deviceObjectID, &propAddress, 0, NULL, &dataSize, &bufferSizeRange);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
return mal_result_from_OSStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just a clamp.
|
||||||
|
if (bufferSizeInFramesIn < bufferSizeRange.mMinimum) {
|
||||||
|
*pBufferSizeInFramesOut = (mal_uint32)bufferSizeRange.mMinimum;
|
||||||
|
} else if (bufferSizeInFramesIn > bufferSizeRange.mMaximum) {
|
||||||
|
*pBufferSizeInFramesOut = (mal_uint32)bufferSizeRange.mMaximum;
|
||||||
|
} else {
|
||||||
|
*pBufferSizeInFramesOut = bufferSizeInFramesIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MAL_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
mal_result mal_find_AudioObjectID(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, AudioObjectID* pDeviceObjectID)
|
mal_result mal_find_AudioObjectID(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, AudioObjectID* pDeviceObjectID)
|
||||||
{
|
{
|
||||||
mal_assert(pContext != NULL);
|
mal_assert(pContext != NULL);
|
||||||
@@ -13425,61 +13472,93 @@ mal_result mal_context_uninit__coreaudio(mal_context* pContext)
|
|||||||
void mal_device_uninit__coreaudio(mal_device* pDevice)
|
void mal_device_uninit__coreaudio(mal_device* pDevice)
|
||||||
{
|
{
|
||||||
mal_assert(pDevice != NULL);
|
mal_assert(pDevice != NULL);
|
||||||
|
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED);
|
||||||
|
|
||||||
// TODO: Implement me.
|
// We just need to kill the thread. Which we do by simply waking it up. Upon wakeup, if the thread can see that the
|
||||||
(void)pDevice;
|
// device is being uninitialized (by checking if the state is equal to MAL_STATE_UNINITIALIZED) it will return.
|
||||||
|
mal_event_signal(&pDevice->wakeupEvent);
|
||||||
|
mal_thread_wait(&pDevice->thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
mal_result mal_device_init__coreaudio(mal_context* pContext, mal_device_type type, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice)
|
|
||||||
|
void mal_on_output__core_audio(void* pUserData, AudioQueueRef audioQueue, AudioQueueBufferRef audioQueueBuffer)
|
||||||
{
|
{
|
||||||
mal_assert(pContext != NULL);
|
mal_device* pDevice = (mal_device*)pUserData;
|
||||||
mal_assert(pConfig != NULL);
|
|
||||||
mal_assert(pDevice != NULL);
|
mal_assert(pDevice != NULL);
|
||||||
mal_assert(type == mal_device_type_playback || type == mal_device_type_capture);
|
|
||||||
|
|
||||||
AudioObjectID deviceObjectID;
|
// The callback is called when AudioQueueStop has been called so we want to just ignore everything in this case.
|
||||||
mal_result result = mal_find_AudioObjectID(pContext, type, pDeviceID, &deviceObjectID);
|
if (mal_device__get_state(pDevice) != MAL_STATE_STARTED) {
|
||||||
if (result != MAL_SUCCESS) {
|
return;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pDevice->coreaudio.deviceObjectID = deviceObjectID;
|
mal_uint32 frameCount = audioQueueBuffer->mAudioDataBytesCapacity / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||||
|
mal_assert(frameCount > 0);
|
||||||
|
|
||||||
// Internal format.
|
// When reading frames from the client, keep in mind that mal_device__read_frames_from_client() will padd the output with silence in the
|
||||||
if (pDevice->usingDefaultFormat) {
|
// case that the client has run out of audio data.
|
||||||
pDevice->internalFormat = mal_format_s16;
|
mal_device__read_frames_from_client(pDevice, frameCount, audioQueueBuffer->mAudioData);
|
||||||
} else {
|
audioQueueBuffer->mAudioDataByteSize = audioQueueBuffer->mAudioDataBytesCapacity;
|
||||||
pDevice->internalFormat = pDevice->format;
|
|
||||||
|
// If enqueing the the buffer fails we need to stop the device.
|
||||||
|
OSStatus status = AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffer, 0, NULL);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
// We failed to enqueue the buffer for some reason. If the device is started it needs to be stopped.
|
||||||
|
if (mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
|
||||||
|
mal_device_stop(pDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mal_on_input__core_audio(void* pUserData, AudioQueueRef audioQueue, AudioQueueBufferRef audioQueueBuffer, const AudioTimeStamp* pStartTime, UInt32 packetDescriptionCount, const AudioStreamPacketDescription* pPacketDescriptions)
|
||||||
|
{
|
||||||
|
(void)pStartTime;
|
||||||
|
(void)packetDescriptionCount;
|
||||||
|
(void)pPacketDescriptions;
|
||||||
|
|
||||||
|
mal_device* pDevice = (mal_device*)pUserData;
|
||||||
|
mal_assert(pDevice != NULL);
|
||||||
|
|
||||||
|
// The callback is called when AudioQueueStop has been called so we want to just ignore everything in this case.
|
||||||
|
if (mal_device__get_state(pDevice) != MAL_STATE_STARTED) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal channels.
|
mal_uint32 frameCount = audioQueueBuffer->mAudioDataByteSize / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||||
result = mal_get_AudioObject_channel_count(deviceObjectID, type, &pDevice->internalChannels);
|
if (frameCount == 0) {
|
||||||
if (result != MAL_SUCCESS) {
|
return;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal sample rate.
|
mal_device__send_frames_to_client(pDevice, frameCount, audioQueueBuffer->mAudioData);
|
||||||
result = mal_get_AudioObject_get_closest_sample_rate(deviceObjectID, type, (pDevice->usingDefaultSampleRate) ? 0 : pDevice->sampleRate, &pDevice->internalSampleRate);
|
|
||||||
if (result != MAL_SUCCESS) {
|
// If enqueing the buffer fails we need to stop the device.
|
||||||
return result;
|
OSStatus status = AudioQueueEnqueueBuffer(audioQueue, audioQueueBuffer, 0, NULL);
|
||||||
}
|
if (status != kAudioHardwareNoError) {
|
||||||
|
// We failed to enqueue the buffer for some reason. If the device is started it needs to be stopped.
|
||||||
// Internal channel map.
|
if (mal_device__get_state(pDevice) == MAL_STATE_STARTED) {
|
||||||
result = mal_get_AudioObject_channel_map(deviceObjectID, type, pDevice->internalChannelMap);
|
mal_device_stop(pDevice);
|
||||||
if (result != MAL_SUCCESS) {
|
}
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal format. This depends on the internal sample rate, so it needs to come after that.
|
|
||||||
result = mal_get_AudioObject_best_format(deviceObjectID, type, pDevice->internalSampleRate, &pDevice->internalFormat);
|
|
||||||
if (result != MAL_SUCCESS) {
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
mal_device* pDevice;
|
||||||
|
UInt32 queueBufferSizeInBytes;
|
||||||
|
UInt32 actualBufferSizeInBytes;
|
||||||
|
mal_event initCompletionEvent;
|
||||||
|
mal_result initResult;
|
||||||
|
} mal_AudioQueue_worker_thread_data__coreaudio;
|
||||||
|
|
||||||
|
mal_thread_result MAL_THREADCALL mal_AudioQueue_worker_thread__coreaudio(void* pData)
|
||||||
|
{
|
||||||
|
mal_AudioQueue_worker_thread_data__coreaudio* pThreadData = (mal_AudioQueue_worker_thread_data__coreaudio*)pData;
|
||||||
|
mal_assert(pThreadData != NULL);
|
||||||
|
|
||||||
|
mal_device* pDevice = pThreadData->pDevice;
|
||||||
|
mal_assert(pDevice != NULL);
|
||||||
|
|
||||||
|
// Audio Queue.
|
||||||
AudioStreamBasicDescription streamFormat;
|
AudioStreamBasicDescription streamFormat;
|
||||||
mal_zero_object(&streamFormat);
|
mal_zero_object(&streamFormat);
|
||||||
streamFormat.mSampleRate = (Float64)pDevice->internalSampleRate;
|
streamFormat.mSampleRate = (Float64)pDevice->internalSampleRate;
|
||||||
@@ -13490,19 +13569,312 @@ mal_result mal_device_init__coreaudio(mal_context* pContext, mal_device_type typ
|
|||||||
streamFormat.mBitsPerChannel = mal_get_bytes_per_sample(pDevice->internalFormat) * 8;
|
streamFormat.mBitsPerChannel = mal_get_bytes_per_sample(pDevice->internalFormat) * 8;
|
||||||
streamFormat.mBytesPerFrame = mal_get_bytes_per_sample(pDevice->internalFormat) * streamFormat.mChannelsPerFrame;
|
streamFormat.mBytesPerFrame = mal_get_bytes_per_sample(pDevice->internalFormat) * streamFormat.mChannelsPerFrame;
|
||||||
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket;
|
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket;
|
||||||
if (pConfig->format == mal_format_f32 /*|| pConfig->format == mal_format_f64*/) {
|
if (pDevice->internalFormat == mal_format_f32 /*|| pDevice->internalFormat == mal_format_f64*/) {
|
||||||
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
|
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
|
||||||
} else {
|
} else {
|
||||||
if (pConfig->format != mal_format_u8) {
|
if (pDevice->internalFormat != mal_format_u8) {
|
||||||
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
|
streamFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pDevice->coreaudio.runLoop = CFRunLoopGetCurrent();
|
||||||
|
if (pDevice->coreaudio.runLoop == NULL) {
|
||||||
|
pThreadData->initResult = MAL_ERROR;
|
||||||
|
mal_event_signal(&pThreadData->initCompletionEvent);
|
||||||
|
return (mal_thread_result)(size_t)pThreadData->initResult;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement me.
|
OSStatus status;
|
||||||
return MAL_ERROR;
|
if (pDevice->type == mal_device_type_playback) {
|
||||||
|
status = AudioQueueNewOutput(&streamFormat, mal_on_output__core_audio, pDevice, (CFRunLoopRef)pDevice->coreaudio.runLoop, NULL, 0, (AudioQueueRef*)&pDevice->coreaudio.audioQueue);
|
||||||
|
} else {
|
||||||
|
status = AudioQueueNewInput(&streamFormat, mal_on_input__core_audio, pDevice, (CFRunLoopRef)pDevice->coreaudio.runLoop, NULL, 0, (AudioQueueRef*)&pDevice->coreaudio.audioQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
pThreadData->initResult = mal_result_from_OSStatus(status);
|
||||||
|
mal_event_signal(&pThreadData->initCompletionEvent);
|
||||||
|
return (mal_thread_result)(size_t)pThreadData->initResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Attach the device to the queue.
|
||||||
|
CFStringRef uid;
|
||||||
|
pThreadData->initResult = mal_get_AudioObject_uid_as_CFStringRef(pDevice->coreaudio.deviceObjectID, &uid);
|
||||||
|
if (pThreadData->initResult != MAL_SUCCESS) {
|
||||||
|
AudioQueueDispose((AudioQueueRef)pDevice->coreaudio.audioQueue, MAL_TRUE);
|
||||||
|
pThreadData->initResult = mal_result_from_OSStatus(status);
|
||||||
|
mal_event_signal(&pThreadData->initCompletionEvent);
|
||||||
|
return (mal_thread_result)(size_t)pThreadData->initResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = AudioQueueSetProperty((AudioQueueRef)pDevice->coreaudio.audioQueue, kAudioQueueProperty_CurrentDevice, &uid, sizeof(uid));
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
AudioQueueDispose((AudioQueueRef)pDevice->coreaudio.audioQueue, MAL_TRUE);
|
||||||
|
pThreadData->initResult = mal_result_from_OSStatus(status);
|
||||||
|
mal_event_signal(&pThreadData->initCompletionEvent);
|
||||||
|
return (mal_thread_result)(size_t)pThreadData->initResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// One queue buffer for each period.
|
||||||
|
pThreadData->actualBufferSizeInBytes = 0;
|
||||||
|
for (mal_uint32 iBuffer = 0; iBuffer < pDevice->periods; ++iBuffer) {
|
||||||
|
status = AudioQueueAllocateBuffer((AudioQueueRef)pDevice->coreaudio.audioQueue, pThreadData->queueBufferSizeInBytes, (AudioQueueBufferRef*)&pDevice->coreaudio.audioQueueBuffers[iBuffer]);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
AudioQueueDispose((AudioQueueRef)pDevice->coreaudio.audioQueue, MAL_TRUE);
|
||||||
|
pThreadData->initResult = mal_result_from_OSStatus(status);
|
||||||
|
mal_event_signal(&pThreadData->initCompletionEvent);
|
||||||
|
return (mal_thread_result)(size_t)pThreadData->initResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
pThreadData->actualBufferSizeInBytes += ((AudioQueueBufferRef)pDevice->coreaudio.audioQueueBuffers[iBuffer])->mAudioDataBytesCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the device is initially marked as stopped.
|
||||||
|
pDevice->state = MAL_STATE_STOPPED;
|
||||||
|
|
||||||
|
// Signal the initialization completion event before entering our loop.
|
||||||
|
pThreadData->initResult = MAL_SUCCESS;
|
||||||
|
mal_event_signal(&pThreadData->initCompletionEvent);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// Just wait for something to wake up the thread. We'll then look at the state of the device to know what to do.
|
||||||
|
mal_event_wait(&pDevice->wakeupEvent);
|
||||||
|
|
||||||
|
// Get out of the loop, and the thread, if the device is uninitialized.
|
||||||
|
if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getting here means the device is being started.
|
||||||
|
mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTING);
|
||||||
|
|
||||||
|
// Each of the buffer queues need to be filled before starting.
|
||||||
|
mal_result result = MAL_SUCCESS;
|
||||||
|
for (mal_uint32 iBuffer = 0; iBuffer < pDevice->periods; ++iBuffer) {
|
||||||
|
AudioQueueBufferRef audioQueueBuffer = (AudioQueueBufferRef)pDevice->coreaudio.audioQueueBuffers[iBuffer];
|
||||||
|
|
||||||
|
// Read data from the client for the initial state of the buffers.
|
||||||
|
mal_uint32 frameCount = audioQueueBuffer->mAudioDataBytesCapacity / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||||
|
mal_assert(frameCount > 0);
|
||||||
|
|
||||||
|
mal_device__read_frames_from_client(pDevice, frameCount, audioQueueBuffer->mAudioData);
|
||||||
|
audioQueueBuffer->mAudioDataByteSize = audioQueueBuffer->mAudioDataBytesCapacity;
|
||||||
|
|
||||||
|
// Once loaded with data, make sure it's enqueued.
|
||||||
|
status = AudioQueueEnqueueBuffer((AudioQueueRef)pDevice->coreaudio.audioQueue, audioQueueBuffer, 0, NULL);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
result = mal_result_from_OSStatus(status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
pDevice->workResult = result;
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
|
||||||
|
mal_event_signal(&pDevice->startEvent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// If an error occurs while trying to start the queue, just mark the device as stopped and go back to the start of the loop.
|
||||||
|
status = AudioQueueStart((AudioQueueRef)pDevice->coreaudio.audioQueue, NULL);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
pDevice->workResult = mal_result_from_OSStatus(status);
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
|
||||||
|
mal_event_signal(&pDevice->startEvent);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STARTED);
|
||||||
|
mal_event_signal(&pDevice->startEvent);
|
||||||
|
|
||||||
|
|
||||||
|
// Just keep running the running loop indefinitely. We'll wake it up when we need to.
|
||||||
|
for (;;) {
|
||||||
|
// Getting here means the loop has timed out or was woken up. This gives us a chace to stop the device is requested.
|
||||||
|
if (mal_device__get_state(pDevice) != MAL_STATE_STARTED) {
|
||||||
|
goto stop_device_and_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFRunLoopRun();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Getting here means the device has stopped showhow - either explicitly or due to an error.
|
||||||
|
stop_device_and_continue:
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STOPPING);
|
||||||
|
|
||||||
|
status = AudioQueueStop((AudioQueueRef)pDevice->coreaudio.audioQueue, MAL_TRUE);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
// An error occurred while stopping the audio queue... just continue on I suppose...
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
status = AudioQueueReset((AudioQueueRef)pDevice->coreaudio.audioQueue);
|
||||||
|
if (status != kAudioHardwareNoError) {
|
||||||
|
// Don't think we need to do anything if resetting fails.
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Make sure the client is notified that the device has stopped, but make sure it's done after the status has been set.
|
||||||
|
mal_stop_proc onStop = pDevice->onStop;
|
||||||
|
if (onStop) {
|
||||||
|
onStop(pDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STOPPED);
|
||||||
|
mal_event_signal(&pDevice->stopEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll get here when the device is uninitialized.
|
||||||
|
AudioQueueDispose((AudioQueueRef)pDevice->coreaudio.audioQueue, MAL_TRUE);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mal_result mal_device_init__coreaudio(mal_context* pContext, mal_device_type deviceType, const mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice)
|
||||||
|
{
|
||||||
|
mal_assert(pContext != NULL);
|
||||||
|
mal_assert(pConfig != NULL);
|
||||||
|
mal_assert(pDevice != NULL);
|
||||||
|
mal_assert(deviceType == mal_device_type_playback || deviceType == mal_device_type_capture);
|
||||||
|
|
||||||
|
AudioObjectID deviceObjectID;
|
||||||
|
mal_result result = mal_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pDevice->coreaudio.deviceObjectID = deviceObjectID;
|
||||||
|
|
||||||
|
// Internal channels.
|
||||||
|
result = mal_get_AudioObject_channel_count(deviceObjectID, deviceType, &pDevice->internalChannels);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal sample rate.
|
||||||
|
result = mal_get_AudioObject_get_closest_sample_rate(deviceObjectID, deviceType, (pDevice->usingDefaultSampleRate) ? 0 : pDevice->sampleRate, &pDevice->internalSampleRate);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal channel map.
|
||||||
|
result = mal_get_AudioObject_channel_map(deviceObjectID, deviceType, pDevice->internalChannelMap);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal format. This depends on the internal sample rate, so it needs to come after that.
|
||||||
|
result = mal_get_AudioObject_best_format(deviceObjectID, deviceType, pDevice->internalSampleRate, &pDevice->internalFormat);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need at least 2 queue buffers.
|
||||||
|
if (pDevice->periods < 2) {
|
||||||
|
pDevice->periods = 2;
|
||||||
|
}
|
||||||
|
if (pDevice->periods > MAL_MAX_PERIODS_COREAUDIO) {
|
||||||
|
pDevice->periods = MAL_MAX_PERIODS_COREAUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Buffer size.
|
||||||
|
mal_uint32 requestedBufferSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods;
|
||||||
|
if (requestedBufferSizeInFrames == 0) {
|
||||||
|
requestedBufferSizeInFrames = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pDevice->usingDefaultBufferSize) {
|
||||||
|
// CPU speed is a factor to consider when determine how large of a buffer we need.
|
||||||
|
float fCPUSpeed = mal_calculate_cpu_speed_factor();
|
||||||
|
|
||||||
|
#if 0 // TODO: Test capture latency.
|
||||||
|
// In my testing, capture seems to have worse latency than playback for some reason.
|
||||||
|
float fType;
|
||||||
|
if (type == mal_device_type_playback) {
|
||||||
|
fType = 1.0f;
|
||||||
|
} else {
|
||||||
|
fType = 2.0f;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
float fType = 1.0f;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Backend tax. Need to fiddle with this.
|
||||||
|
float fBackend = 1.0;
|
||||||
|
|
||||||
|
requestedBufferSizeInFrames = mal_calculate_default_buffer_size_in_frames(pConfig->performanceProfile, pConfig->sampleRate, fCPUSpeed*fType*fBackend);
|
||||||
|
if (requestedBufferSizeInFrames == 0) {
|
||||||
|
requestedBufferSizeInFrames = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_uint32 closestBufferSizeInFrames;
|
||||||
|
result = mal_get_AudioObject_closest_buffer_size_in_frames(deviceObjectID, deviceType, requestedBufferSizeInFrames, &closestBufferSizeInFrames);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The audio queue is initialized in the worker thread. We need a signal to know when the thread has been initialized.
|
||||||
|
mal_AudioQueue_worker_thread_data__coreaudio threadData;
|
||||||
|
threadData.pDevice = pDevice;
|
||||||
|
threadData.queueBufferSizeInBytes = closestBufferSizeInFrames * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||||
|
mal_event_init(pDevice->pContext, &threadData.initCompletionEvent);
|
||||||
|
|
||||||
|
// The Core Audio backend is not using the main worker thread object so we can just reuse that one.
|
||||||
|
result = mal_thread_create(pDevice->pContext, &pDevice->thread, mal_AudioQueue_worker_thread__coreaudio, &threadData);
|
||||||
|
if (result != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the thread to finish initializing before returning.
|
||||||
|
mal_event_wait(&threadData.initCompletionEvent);
|
||||||
|
if (threadData.initResult != MAL_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the worker thread is up and running which means we can finish up.
|
||||||
|
pDevice->bufferSizeInFrames = threadData.actualBufferSizeInBytes / mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels);
|
||||||
|
|
||||||
|
|
||||||
|
return MAL_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_result mal_device__start_backend__coreaudio(mal_device* pDevice)
|
||||||
|
{
|
||||||
|
mal_assert(pDevice != NULL);
|
||||||
|
|
||||||
|
// We set the state to playing and wake up the worker thread which is where the device will be started for
|
||||||
|
// real. We need to wait for the startEvent to get signalled before returning.
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STARTING);
|
||||||
|
mal_event_signal(&pDevice->wakeupEvent);
|
||||||
|
|
||||||
|
// Now wait for the thread to let us know that it's started.
|
||||||
|
mal_event_wait(&pDevice->startEvent);
|
||||||
|
|
||||||
|
// We use the work result to know whether or not we were successful.
|
||||||
|
return pDevice->workResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
mal_result mal_device__stop_backend__coreaudio(mal_device* pDevice)
|
||||||
|
{
|
||||||
|
mal_assert(pDevice != NULL);
|
||||||
|
|
||||||
|
// Stopping the device is similar to starting in that we need to let the worker thread do it's thing before
|
||||||
|
// returning, however we need to wake up the thread slightly differently.
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STOPPING);
|
||||||
|
CFRunLoopStop((CFRunLoopRef)pDevice->coreaudio.runLoop); // <-- Wake up the run loop so that the worker thread can see that we're needing to stop.
|
||||||
|
|
||||||
|
// Wait for the device to be stopped on the worker thread before returning.
|
||||||
|
mal_event_wait(&pDevice->stopEvent);
|
||||||
|
return MAL_SUCCESS;
|
||||||
|
}
|
||||||
#endif // Core Audio
|
#endif // Core Audio
|
||||||
|
|
||||||
|
|
||||||
@@ -16786,8 +17158,9 @@ const mal_backend g_malDefaultBackends[] = {
|
|||||||
mal_bool32 mal_is_backend_asynchronous(mal_backend backend)
|
mal_bool32 mal_is_backend_asynchronous(mal_backend backend)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
backend == mal_backend_jack ||
|
backend == mal_backend_jack ||
|
||||||
backend == mal_backend_opensl ||
|
backend == mal_backend_coreaudio ||
|
||||||
|
backend == mal_backend_opensl ||
|
||||||
backend == mal_backend_sdl;
|
backend == mal_backend_sdl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17498,11 +17871,6 @@ void mal_device_uninit(mal_device* pDevice)
|
|||||||
mal_thread_wait(&pDevice->thread);
|
mal_thread_wait(&pDevice->thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
mal_event_uninit(&pDevice->stopEvent);
|
|
||||||
mal_event_uninit(&pDevice->startEvent);
|
|
||||||
mal_event_uninit(&pDevice->wakeupEvent);
|
|
||||||
mal_mutex_uninit(&pDevice->lock);
|
|
||||||
|
|
||||||
#ifdef MAL_HAS_WASAPI
|
#ifdef MAL_HAS_WASAPI
|
||||||
if (pDevice->pContext->backend == mal_backend_wasapi) {
|
if (pDevice->pContext->backend == mal_backend_wasapi) {
|
||||||
mal_device_uninit__wasapi(pDevice);
|
mal_device_uninit__wasapi(pDevice);
|
||||||
@@ -17564,6 +17932,10 @@ void mal_device_uninit(mal_device* pDevice)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
mal_event_uninit(&pDevice->stopEvent);
|
||||||
|
mal_event_uninit(&pDevice->startEvent);
|
||||||
|
mal_event_uninit(&pDevice->wakeupEvent);
|
||||||
|
mal_mutex_uninit(&pDevice->lock);
|
||||||
|
|
||||||
if (pDevice->isOwnerOfContext) {
|
if (pDevice->isOwnerOfContext) {
|
||||||
mal_context_uninit(pDevice->pContext);
|
mal_context_uninit(pDevice->pContext);
|
||||||
@@ -17627,6 +17999,14 @@ mal_result mal_device_start(mal_device* pDevice)
|
|||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef MAL_HAS_COREAUDIO
|
||||||
|
if (pDevice->pContext->backend == mal_backend_coreaudio) {
|
||||||
|
result = mal_device__start_backend__coreaudio(pDevice);
|
||||||
|
if (result == MAL_SUCCESS) {
|
||||||
|
mal_device__set_state(pDevice, MAL_STATE_STARTED);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
#ifdef MAL_HAS_OPENSL
|
#ifdef MAL_HAS_OPENSL
|
||||||
if (pDevice->pContext->backend == mal_backend_opensl) {
|
if (pDevice->pContext->backend == mal_backend_opensl) {
|
||||||
result = mal_device__start_backend__opensl(pDevice);
|
result = mal_device__start_backend__opensl(pDevice);
|
||||||
@@ -17693,6 +18073,11 @@ mal_result mal_device_stop(mal_device* pDevice)
|
|||||||
mal_device__stop_backend__jack(pDevice);
|
mal_device__stop_backend__jack(pDevice);
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef MAL_HAS_COREAUDIO
|
||||||
|
if (pDevice->pContext->backend == mal_backend_coreaudio) {
|
||||||
|
mal_device__stop_backend__coreaudio(pDevice);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
#ifdef MAL_HAS_OPENSL
|
#ifdef MAL_HAS_OPENSL
|
||||||
if (pDevice->pContext->backend == mal_backend_opensl) {
|
if (pDevice->pContext->backend == mal_backend_opensl) {
|
||||||
mal_device__stop_backend__opensl(pDevice);
|
mal_device__stop_backend__opensl(pDevice);
|
||||||
|
|||||||
Reference in New Issue
Block a user