Commit d077e92f authored by David Reid's avatar David Reid

PulseAudio: Fix a crash when initializing multiple devices.

This was due to context-level mainloop being accessed from different
audio threads at the same time. I've fixed this by giving each device
their own pa_mainloop and pa_context objects.

I considered the idea of having only a single context-level mainloop
and just using a mutex for mutal exclusion, but that would involve a
lock in the audio thread's data loop which I wasn't happy about. Also,
I wasn't sure which thread PulseAudio callbacks would get fired from
since the main loop would be iterated on different audio threads.

Public issue https://github.com/mackron/miniaudio/issues/376
parent 4f07898b
...@@ -6516,6 +6516,8 @@ struct ma_context ...@@ -6516,6 +6516,8 @@ struct ma_context
/*pa_mainloop**/ ma_ptr pMainLoop; /*pa_mainloop**/ ma_ptr pMainLoop;
/*pa_context**/ ma_ptr pPulseContext; /*pa_context**/ ma_ptr pPulseContext;
char* pApplicationName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */
char* pServerName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */
} pulse; } pulse;
#endif #endif
#ifdef MA_SUPPORT_JACK #ifdef MA_SUPPORT_JACK
...@@ -6870,6 +6872,8 @@ struct ma_device ...@@ -6870,6 +6872,8 @@ struct ma_device
#ifdef MA_SUPPORT_PULSEAUDIO #ifdef MA_SUPPORT_PULSEAUDIO
struct struct
{ {
/*pa_mainloop**/ ma_ptr pMainLoop;
/*pa_context**/ ma_ptr pPulseContext;
/*pa_stream**/ ma_ptr pStreamPlayback; /*pa_stream**/ ma_ptr pStreamPlayback;
/*pa_stream**/ ma_ptr pStreamCapture; /*pa_stream**/ ma_ptr pStreamCapture;
} pulse; } pulse;
...@@ -11940,6 +11944,10 @@ MA_API int ma_strappend(char* dst, size_t dstSize, const char* srcA, const char* ...@@ -11940,6 +11944,10 @@ MA_API int ma_strappend(char* dst, size_t dstSize, const char* srcA, const char*
MA_API char* ma_copy_string(const char* src, const ma_allocation_callbacks* pAllocationCallbacks) MA_API char* ma_copy_string(const char* src, const ma_allocation_callbacks* pAllocationCallbacks)
{ {
if (src == NULL) {
return NULL;
}
size_t sz = strlen(src)+1; size_t sz = strlen(src)+1;
char* dst = (char*)ma_malloc(sz, pAllocationCallbacks); char* dst = (char*)ma_malloc(sz, pAllocationCallbacks);
if (dst == NULL) { if (dst == NULL) {
...@@ -27157,7 +27165,7 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position ...@@ -27157,7 +27165,7 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position
} }
#endif #endif
static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operation* pOP) static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP)
{ {
int resultPA; int resultPA;
ma_pa_operation_state_t state; ma_pa_operation_state_t state;
...@@ -27171,7 +27179,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat ...@@ -27171,7 +27179,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat
break; /* Done. */ break; /* Done. */
} }
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
if (resultPA < 0) { if (resultPA < 0) {
return ma_result_from_pulse(resultPA); return ma_result_from_pulse(resultPA);
} }
...@@ -27180,7 +27188,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat ...@@ -27180,7 +27188,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat
return MA_SUCCESS; return MA_SUCCESS;
} }
static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_pa_operation* pOP) static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP)
{ {
ma_result result; ma_result result;
...@@ -27188,19 +27196,19 @@ static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma ...@@ -27188,19 +27196,19 @@ static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
result = ma_wait_for_operation__pulse(pContext, pOP); result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP);
((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
return result; return result;
} }
static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pContext) static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pPulseContext)
{ {
int resultPA; int resultPA;
ma_pa_context_state_t state; ma_pa_context_state_t state;
for (;;) { for (;;) {
state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pContext->pulse.pPulseContext); state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pPulseContext);
if (state == MA_PA_CONTEXT_READY) { if (state == MA_PA_CONTEXT_READY) {
break; /* Done. */ break; /* Done. */
} }
...@@ -27210,7 +27218,7 @@ static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pC ...@@ -27210,7 +27218,7 @@ static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pC
return MA_ERROR; return MA_ERROR;
} }
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
if (resultPA < 0) { if (resultPA < 0) {
return ma_result_from_pulse(resultPA); return ma_result_from_pulse(resultPA);
} }
...@@ -27220,13 +27228,13 @@ static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pC ...@@ -27220,13 +27228,13 @@ static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pC
return MA_SUCCESS; return MA_SUCCESS;
} }
static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_pa_stream* pStream) static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pStream)
{ {
int resultPA; int resultPA;
ma_pa_stream_state_t state; ma_pa_stream_state_t state;
for (;;) { for (;;) {
state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)(pStream); state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pStream);
if (state == MA_PA_STREAM_READY) { if (state == MA_PA_STREAM_READY) {
break; /* Done. */ break; /* Done. */
} }
...@@ -27236,7 +27244,7 @@ static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pCo ...@@ -27236,7 +27244,7 @@ static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pCo
return MA_ERROR; return MA_ERROR;
} }
resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL);
if (resultPA < 0) { if (resultPA < 0) {
return ma_result_from_pulse(resultPA); return ma_result_from_pulse(resultPA);
} }
...@@ -27246,6 +27254,52 @@ static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pCo ...@@ -27246,6 +27254,52 @@ static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pCo
} }
static ma_result ma_init_pa_mainloop_and_pa_context__pulse(ma_context* pContext, const char* pApplicationName, const char* pServerName, ma_bool32 tryAutoSpawn, ma_ptr* ppMainLoop, ma_ptr* ppPulseContext)
{
ma_result result;
ma_ptr pMainLoop;
ma_ptr pPulseContext;
MA_ASSERT(ppMainLoop != NULL);
MA_ASSERT(ppPulseContext != NULL);
/* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */
pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)();
if (pMainLoop == NULL) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop.");
return MA_FAILED_TO_INIT_BACKEND;
}
pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pMainLoop), pApplicationName);
if (pPulseContext == NULL) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context.");
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop));
return MA_FAILED_TO_INIT_BACKEND;
}
/* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */
result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pPulseContext, pServerName, (tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL));
if (result != MA_SUCCESS) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.");
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop));
return result;
}
/* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */
result = ma_wait_for_pa_context_to_connect__pulse(pContext, pMainLoop, pPulseContext);
if (result != MA_SUCCESS) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Waiting for connection failed.");
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop));
return result;
}
*ppMainLoop = pMainLoop;
*ppPulseContext = pPulseContext;
return MA_SUCCESS;
}
static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData)
{ {
ma_pa_sink_info* pInfoOut; ma_pa_sink_info* pInfoOut;
...@@ -27320,7 +27374,7 @@ static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const cha ...@@ -27320,7 +27374,7 @@ static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const cha
return MA_ERROR; return MA_ERROR;
} }
return ma_wait_for_operation_and_unref__pulse(pContext, pOP); return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP);
} }
static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_source_info* pSourceInfo) static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_source_info* pSourceInfo)
...@@ -27332,7 +27386,7 @@ static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const c ...@@ -27332,7 +27386,7 @@ static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const c
return MA_ERROR; return MA_ERROR;
} }
return ma_wait_for_operation_and_unref__pulse(pContext, pOP);; return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP);
} }
static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext, ma_device_type deviceType, ma_uint32* pIndex) static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext, ma_device_type deviceType, ma_uint32* pIndex)
...@@ -27476,7 +27530,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en ...@@ -27476,7 +27530,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en
goto done; goto done;
} }
result = ma_wait_for_operation__pulse(pContext, pOP); result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP);
((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -27493,7 +27547,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en ...@@ -27493,7 +27547,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en
goto done; goto done;
} }
result = ma_wait_for_operation__pulse(pContext, pOP); result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP);
((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -27614,7 +27668,7 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi ...@@ -27614,7 +27668,7 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi
} }
if (pOP != NULL) { if (pOP != NULL) {
ma_wait_for_operation_and_unref__pulse(pContext, pOP); ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP);
} else { } else {
result = MA_ERROR; result = MA_ERROR;
goto done; goto done;
...@@ -27652,6 +27706,10 @@ static ma_result ma_device_uninit__pulse(ma_device* pDevice) ...@@ -27652,6 +27706,10 @@ static ma_result ma_device_uninit__pulse(ma_device* pDevice)
ma_duplex_rb_uninit(&pDevice->duplexRB); ma_duplex_rb_uninit(&pDevice->duplexRB);
} }
((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext);
((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext);
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop);
return MA_SUCCESS; return MA_SUCCESS;
} }
...@@ -27667,7 +27725,7 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra ...@@ -27667,7 +27725,7 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra
return attr; return attr;
} }
static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap)
{ {
static int g_StreamCounter = 0; static int g_StreamCounter = 0;
char actualStreamName[256]; char actualStreamName[256];
...@@ -27680,7 +27738,7 @@ static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, cons ...@@ -27680,7 +27738,7 @@ static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, cons
} }
g_StreamCounter += 1; g_StreamCounter += 1;
return ((ma_pa_stream_new_proc)pContext->pulse.pa_stream_new)((ma_pa_context*)pContext->pulse.pPulseContext, actualStreamName, ss, cmap); return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap);
} }
...@@ -27908,6 +27966,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -27908,6 +27966,8 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
MA_ASSERT(pDevice != NULL); MA_ASSERT(pDevice != NULL);
MA_ZERO_OBJECT(&pDevice->pulse); MA_ZERO_OBJECT(&pDevice->pulse);
printf("TESTING\n");
if (pConfig->deviceType == ma_device_type_loopback) { if (pConfig->deviceType == ma_device_type_loopback) {
return MA_DEVICE_TYPE_NOT_SUPPORTED; return MA_DEVICE_TYPE_NOT_SUPPORTED;
} }
...@@ -27938,6 +27998,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -27938,6 +27998,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
sampleRate = pDescriptorCapture->sampleRate; sampleRate = pDescriptorCapture->sampleRate;
} }
result = ma_init_pa_mainloop_and_pa_context__pulse(pDevice->pContext, pDevice->pContext->pulse.pApplicationName, pDevice->pContext->pulse.pServerName, MA_FALSE, &pDevice->pulse.pMainLoop, &pDevice->pulse.pPulseContext);
if (result != MA_SUCCESS) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize PA mainloop and context for device.\n");
return result;
}
if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) {
result = ma_context_get_source_info__pulse(pDevice->pContext, devCapture, &sourceInfo); result = ma_context_get_source_info__pulse(pDevice->pContext, devCapture, &sourceInfo);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -27971,7 +28039,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -27971,7 +28039,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
attr = ma_device__pa_buffer_attr_new(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->periodCount, &ss); attr = ma_device__pa_buffer_attr_new(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->periodCount, &ss);
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames);
pDevice->pulse.pStreamCapture = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNameCapture, &ss, &cmap); pDevice->pulse.pStreamCapture = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNameCapture, &ss, &cmap);
if (pDevice->pulse.pStreamCapture == NULL) { if (pDevice->pulse.pStreamCapture == NULL) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream."); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.");
result = MA_ERROR; result = MA_ERROR;
...@@ -27999,7 +28067,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -27999,7 +28067,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
goto on_error1; goto on_error1;
} }
result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamCapture); result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamCapture);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
goto on_error2; goto on_error2;
} }
...@@ -28036,13 +28104,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -28036,13 +28104,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames);
#if 0
/* Name. */ /* Name. */
devCapture = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture); devCapture = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture);
if (devCapture != NULL) { if (devCapture != NULL) {
ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice);
ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP);
} }
#endif
} }
if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) {
...@@ -28079,7 +28148,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -28079,7 +28148,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames);
pDevice->pulse.pStreamPlayback = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); pDevice->pulse.pStreamPlayback = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNamePlayback, &ss, &cmap);
if (pDevice->pulse.pStreamPlayback == NULL) { if (pDevice->pulse.pStreamPlayback == NULL) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream."); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.");
result = MA_ERROR; result = MA_ERROR;
...@@ -28110,7 +28179,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -28110,7 +28179,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
goto on_error3; goto on_error3;
} }
result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamPlayback);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
goto on_error3; goto on_error3;
} }
...@@ -28147,13 +28216,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi ...@@ -28147,13 +28216,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount;
ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames);
#if 0
/* Name. */ /* Name. */
devPlayback = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); devPlayback = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback);
if (devPlayback != NULL) { if (devPlayback != NULL) {
ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice);
ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP);
} }
#endif
} }
...@@ -28233,7 +28303,7 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ ...@@ -28233,7 +28303,7 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ
return MA_ERROR; return MA_ERROR;
} }
result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork."); ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.");
return result; return result;
...@@ -28293,7 +28363,7 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) ...@@ -28293,7 +28363,7 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice)
if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) {
/* The stream needs to be drained if it's a playback device. */ /* The stream needs to be drained if it's a playback device. */
ma_pa_operation* pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); ma_pa_operation* pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful);
ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP);
result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1); result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
...@@ -28317,7 +28387,7 @@ static ma_result ma_device_data_loop__pulse(ma_device* pDevice) ...@@ -28317,7 +28387,7 @@ static ma_result ma_device_data_loop__pulse(ma_device* pDevice)
the callbacks deal with it. the callbacks deal with it.
*/ */
while (ma_device_get_state(pDevice) == ma_device_state_started) { while (ma_device_get_state(pDevice) == ma_device_state_started) {
resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 1, NULL); resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL);
if (resultPA < 0) { if (resultPA < 0) {
break; break;
} }
...@@ -28331,7 +28401,7 @@ static ma_result ma_device_data_loop_wakeup__pulse(ma_device* pDevice) ...@@ -28331,7 +28401,7 @@ static ma_result ma_device_data_loop_wakeup__pulse(ma_device* pDevice)
{ {
MA_ASSERT(pDevice != NULL); MA_ASSERT(pDevice != NULL);
((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop); ((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pulse.pMainLoop);
return MA_SUCCESS; return MA_SUCCESS;
} }
...@@ -28345,6 +28415,9 @@ static ma_result ma_context_uninit__pulse(ma_context* pContext) ...@@ -28345,6 +28415,9 @@ static ma_result ma_context_uninit__pulse(ma_context* pContext)
((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext);
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop);
ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks);
ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks);
#ifndef MA_NO_RUNTIME_LINKING #ifndef MA_NO_RUNTIME_LINKING
ma_dlclose(pContext, pContext->pulse.pulseSO); ma_dlclose(pContext, pContext->pulse.pulseSO);
#endif #endif
...@@ -28558,48 +28631,28 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c ...@@ -28558,48 +28631,28 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
pContext->pulse.pa_stream_readable_size = (ma_proc)_pa_stream_readable_size; pContext->pulse.pa_stream_readable_size = (ma_proc)_pa_stream_readable_size;
#endif #endif
/* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ /* We need to make a copy of the application and server names so we can pass them to the pa_context of each device. */
pContext->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName, &pContext->allocationCallbacks);
if (pContext->pulse.pMainLoop == NULL) { if (pContext->pulse.pApplicationName == NULL && pConfig->pulse.pApplicationName != NULL) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop."); return MA_OUT_OF_MEMORY;
#ifndef MA_NO_RUNTIME_LINKING
ma_dlclose(pContext, pContext->pulse.pulseSO);
#endif
return MA_FAILED_TO_INIT_BACKEND;
}
pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName);
if (pContext->pulse.pPulseContext == NULL) {
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context.");
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop));
#ifndef MA_NO_RUNTIME_LINKING
ma_dlclose(pContext, pContext->pulse.pulseSO);
#endif
return MA_FAILED_TO_INIT_BACKEND;
} }
/* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ pContext->pulse.pServerName = ma_copy_string(pConfig->pulse.pServerName, &pContext->allocationCallbacks);
result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pContext->pulse.pPulseContext, pConfig->pulse.pServerName, (pConfig->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); if (pContext->pulse.pServerName == NULL && pConfig->pulse.pServerName != NULL) {
if (result != MA_SUCCESS) { ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks);
ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); return MA_OUT_OF_MEMORY;
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop));
#ifndef MA_NO_RUNTIME_LINKING
ma_dlclose(pContext, pContext->pulse.pulseSO);
#endif
return result;
} }
/* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */ result = ma_init_pa_mainloop_and_pa_context__pulse(pContext, pConfig->pulse.pApplicationName, pConfig->pulse.pServerName, pConfig->pulse.tryAutoSpawn, &pContext->pulse.pMainLoop, &pContext->pulse.pPulseContext);
result = ma_context_wait_for_pa_context_to_connect__pulse(pContext);
if (result != MA_SUCCESS) { if (result != MA_SUCCESS) {
((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks);
ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks);
#ifndef MA_NO_RUNTIME_LINKING #ifndef MA_NO_RUNTIME_LINKING
ma_dlclose(pContext, pContext->pulse.pulseSO); ma_dlclose(pContext, pContext->pulse.pulseSO);
#endif #endif
return result; return result;
} }
/* With pa_mainloop we run a synchronous backend, but we implement our own main loop. */ /* With pa_mainloop we run a synchronous backend, but we implement our own main loop. */
pCallbacks->onContextInit = ma_context_init__pulse; pCallbacks->onContextInit = ma_context_init__pulse;
pCallbacks->onContextUninit = ma_context_uninit__pulse; pCallbacks->onContextUninit = ma_context_uninit__pulse;
...@@ -38392,13 +38445,11 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ...@@ -38392,13 +38445,11 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC
return MA_INVALID_ARGS; return MA_INVALID_ARGS;
} }
/* Check that we have our callbacks defined. */ /* Check that we have our callbacks defined. */
if (pContext->callbacks.onDeviceInit == NULL) { if (pContext->callbacks.onDeviceInit == NULL) {
return MA_INVALID_OPERATION; return MA_INVALID_OPERATION;
} }
/* Basic config validation. */ /* Basic config validation. */
if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) {
if (pConfig->capture.channels > MA_MAX_CHANNELS) { if (pConfig->capture.channels > MA_MAX_CHANNELS) {
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment