Commit d0dfd37c authored by David Reid's avatar David Reid

Work on full-duplex for PulseAudio.

parent d2ca42ed
......@@ -1606,7 +1606,8 @@ typedef struct
} alsa;
struct
{
const char* pStreamName;
const char* pStreamNamePlayback;
const char* pStreamNameCapture;
} pulse;
} mal_device_config;
......@@ -1802,12 +1803,15 @@ struct mal_context
mal_proc pa_stream_set_read_callback;
mal_proc pa_stream_flush;
mal_proc pa_stream_drain;
mal_proc pa_stream_is_corked;
mal_proc pa_stream_cork;
mal_proc pa_stream_trigger;
mal_proc pa_stream_begin_write;
mal_proc pa_stream_write;
mal_proc pa_stream_peek;
mal_proc pa_stream_drop;
mal_proc pa_stream_writable_size;
mal_proc pa_stream_readable_size;
} pulse;
#endif
#ifdef MAL_SUPPORT_JACK
......@@ -2136,9 +2140,17 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
/*pa_mainloop**/ mal_ptr pMainLoop;
/*pa_mainloop_api**/ mal_ptr pAPI;
/*pa_context**/ mal_ptr pPulseContext;
/*pa_stream**/ mal_ptr pStream;
/*pa_stream**/ mal_ptr pStreamPlayback;
/*pa_stream**/ mal_ptr pStreamCapture;
/*pa_context_state*/ mal_uint32 pulseContextState;
mal_uint32 fragmentSizeInBytes;
void* pMappedBufferPlayback;
const void* pMappedBufferCapture;
mal_uint32 mappedBufferFramesRemainingPlayback;
mal_uint32 mappedBufferFramesRemainingCapture;
mal_uint32 mappedBufferFramesCapacityPlayback;
mal_uint32 mappedBufferFramesCapacityCapture;
mal_event readEvent;
mal_event writeEvent;
mal_bool32 breakFromMainLoop : 1;
} pulse;
#endif
......@@ -12620,12 +12632,15 @@ typedef void (* mal_pa_stream_set_write_callback_proc)
typedef void (* mal_pa_stream_set_read_callback_proc) (mal_pa_stream* s, mal_pa_stream_request_cb_t cb, void* userdata);
typedef mal_pa_operation* (* mal_pa_stream_flush_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata);
typedef mal_pa_operation* (* mal_pa_stream_drain_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata);
typedef int (* mal_pa_stream_is_corked_proc) (mal_pa_stream* s);
typedef mal_pa_operation* (* mal_pa_stream_cork_proc) (mal_pa_stream* s, int b, mal_pa_stream_success_cb_t cb, void* userdata);
typedef mal_pa_operation* (* mal_pa_stream_trigger_proc) (mal_pa_stream* s, mal_pa_stream_success_cb_t cb, void* userdata);
typedef int (* mal_pa_stream_begin_write_proc) (mal_pa_stream* s, void** data, size_t* nbytes);
typedef int (* mal_pa_stream_write_proc) (mal_pa_stream* s, const void* data, size_t nbytes, mal_pa_free_cb_t free_cb, int64_t offset, mal_pa_seek_mode_t seek);
typedef int (* mal_pa_stream_peek_proc) (mal_pa_stream* s, const void** data, size_t* nbytes);
typedef int (* mal_pa_stream_drop_proc) (mal_pa_stream* s);
typedef size_t (* mal_pa_stream_writable_size_proc) (mal_pa_stream* s);
typedef size_t (* mal_pa_stream_readable_size_proc) (mal_pa_stream* s);
typedef struct
{
......@@ -13136,18 +13151,28 @@ void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes,
mal_device* pDevice = (mal_device*)pUserData;
mal_assert(pDevice != NULL);
(void)sizeInBytes;
#if 0
mal_context* pContext = pDevice->pContext;
mal_assert(pContext != NULL);
#endif
#ifdef MAL_DEBUG_OUTPUT
printf("[PulseAudio] write_callback: sizeInBytes=%d\n", (int)sizeInBytes);
#endif
printf("TRACE: Enter write callback.\n");
/* Don't do anything if the device is stopping. Not doing this will result in draining never completing. */
if (mal_device__get_state(pDevice) == MAL_STATE_STOPPING) {
return;
}
printf("TRACE: write callback signal.\n");
mal_event_signal(&pDevice->pulse.writeEvent);
#if 0
size_t bytesRemaining = sizeInBytes;
while (bytesRemaining > 0) {
size_t bytesToReadFromClient = bytesRemaining;
......@@ -13195,6 +13220,7 @@ void mal_pulse_device_write_callback(mal_pa_stream* pStream, size_t sizeInBytes,
break;
}
}
#endif
}
void mal_pulse_device_read_callback(mal_pa_stream* pStream, size_t sizeInBytes, void* pUserData)
......@@ -13202,14 +13228,19 @@ void mal_pulse_device_read_callback(mal_pa_stream* pStream, size_t sizeInBytes,
mal_device* pDevice = (mal_device*)pUserData;
mal_assert(pDevice != NULL);
#if 0
mal_context* pContext = pDevice->pContext;
mal_assert(pContext != NULL);
#endif
/* Don't do anything if the device is stopping. */
if (mal_device__get_state(pDevice) == MAL_STATE_STOPPING) {
return;
}
mal_event_signal(&pDevice->pulse.readEvent);
#if 0
size_t bytesRemaining = sizeInBytes;
while (bytesRemaining > 0) {
size_t bytesToSendToClient = bytesRemaining;
......@@ -13238,6 +13269,7 @@ void mal_pulse_device_read_callback(mal_pa_stream* pStream, size_t sizeInBytes,
bytesRemaining -= bytesToSendToClient;
}
#endif
}
void mal_device_sink_info_callback(mal_pa_context* pPulseContext, const mal_pa_sink_info* pInfo, int endOfList, void* pUserData)
......@@ -13295,11 +13327,49 @@ void mal_device_uninit__pulse(mal_device* pDevice)
mal_context* pContext = pDevice->pContext;
mal_assert(pContext != NULL);
((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream);
((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream);
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
}
if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) {
((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
}
((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext);
((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext);
((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop);
mal_event_uninit(&pDevice->pulse.readEvent);
mal_event_uninit(&pDevice->pulse.writeEvent);
}
mal_pa_buffer_attr mal_device__pa_buffer_attr_new(mal_uint32 bufferSizeInFrames, mal_uint32 periods, const mal_pa_sample_spec* ss)
{
mal_pa_buffer_attr attr;
attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss->format)) * ss->channels;
attr.tlength = attr.maxlength / periods;
attr.prebuf = (mal_uint32)-1;
attr.minreq = attr.maxlength / periods;
attr.fragsize = attr.maxlength / periods;
return attr;
}
mal_pa_stream* mal_device__pa_stream_new__pulse(mal_device* pDevice, const char* pStreamName, const mal_pa_sample_spec* ss, const mal_pa_channel_map* cmap)
{
static int g_StreamCounter = 0;
char actualStreamName[256];
if (pStreamName != NULL) {
mal_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1);
} else {
mal_strcpy_s(actualStreamName, sizeof(actualStreamName), "mini_al:");
mal_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); // 8 = strlen("mini_al:")
}
g_StreamCounter += 1;
return ((mal_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((mal_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap);
}
mal_result mal_device_init__pulse(mal_context* pContext, const mal_device_config* pConfig, mal_device* pDevice)
......@@ -13309,16 +13379,14 @@ mal_result mal_device_init__pulse(mal_context* pContext, const mal_device_config
mal_assert(pDevice != NULL);
mal_zero_object(&pDevice->pulse);
mal_event_init(pDevice->pContext, &pDevice->pulse.readEvent);
mal_event_init(pDevice->pContext, &pDevice->pulse.writeEvent);
mal_result result = MAL_SUCCESS;
int error = 0;
const char* devPlayback = NULL;
const char* devCapture = NULL;
/* Full-duplex is not yet implemented. */
if (pConfig->deviceType == mal_device_type_duplex) {
return MAL_INVALID_ARGS;
}
/* No exclusive mode with the PulseAudio backend. */
if (((pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) && pConfig->playback.shareMode == mal_share_mode_exclusive) ||
((pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) && pConfig->capture.shareMode == mal_share_mode_exclusive)) {
......@@ -13332,7 +13400,10 @@ mal_result mal_device_init__pulse(mal_context* pContext, const mal_device_config
devCapture = pConfig->capture.pDeviceID->pulse;
}
mal_uint32 bufferSizeInFrames = pConfig->bufferSizeInFrames;
mal_uint32 bufferSizeInMilliseconds = pConfig->bufferSizeInMilliseconds;
if (bufferSizeInMilliseconds == 0) {
bufferSizeInMilliseconds = mal_calculate_buffer_size_in_milliseconds_from_frames(pConfig->bufferSizeInFrames, pConfig->sampleRate);
}
mal_pa_sink_info sinkInfo;
mal_pa_source_info sourceInfo;
......@@ -13341,7 +13412,6 @@ mal_result mal_device_init__pulse(mal_context* pContext, const mal_device_config
mal_pa_sample_spec ss;
mal_pa_channel_map cmap;
mal_pa_buffer_attr attr;
mal_pa_stream_flags_t streamFlags;
const mal_pa_sample_spec* pActualSS = NULL;
const mal_pa_channel_map* pActualCMap = NULL;
......@@ -13403,107 +13473,161 @@ mal_result mal_device_init__pulse(mal_context* pContext, const mal_device_config
}
}
if (pConfig->deviceType == mal_device_type_playback) {
pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, devPlayback, mal_device_sink_info_callback, &sinkInfo);
} else {
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, devCapture, mal_device_source_info_callback, &sourceInfo);
}
if (pOP != NULL) {
mal_device__wait_for_operation__pulse(pDevice, pOP);
((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
} else {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", mal_result_from_pulse(error));
goto on_error3;
}
if (pConfig->deviceType == mal_device_type_playback) {
ss = sinkInfo.sample_spec;
cmap = sinkInfo.channel_map;
} else {
ss = sourceInfo.sample_spec;
cmap = sourceInfo.channel_map;
pDevice->capture.internalBufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, ss.rate);
pDevice->capture.internalPeriods = pConfig->periods;
attr = mal_device__pa_buffer_attr_new(pDevice->capture.internalBufferSizeInFrames, pConfig->periods, &ss);
#ifdef MAL_DEBUG_OUTPUT
printf("[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->capture.internalBufferSizeInFrames);
#endif
pDevice->pulse.pStreamCapture = mal_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNameCapture, &ss, &cmap);
if (pDevice->pulse.pStreamCapture == NULL) {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
goto on_error3;
}
mal_pa_stream_flags_t streamFlags = MAL_PA_STREAM_START_CORKED | MAL_PA_STREAM_FIX_FORMAT | MAL_PA_STREAM_FIX_RATE | MAL_PA_STREAM_FIX_CHANNELS;
if (devCapture != NULL) {
streamFlags |= MAL_PA_STREAM_DONT_MOVE;
}
// Buffer size.
bufferSizeInFrames = pDevice->bufferSizeInFrames;
if (bufferSizeInFrames == 0) {
bufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, ss.rate);
error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags);
if (error != MAL_PA_OK) {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", mal_result_from_pulse(error));
goto on_error4;
}
if (pDevice->usingDefaultBufferSize) {
float bufferSizeScaleFactor = 1.0f;
if (pConfig->deviceType == mal_device_type_capture) {
bufferSizeScaleFactor = 2.0f;
while (((mal_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((mal_pa_stream*)pDevice->pulse.pStreamCapture) != MAL_PA_STREAM_READY) {
error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL);
if (error < 0) {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio capture stream.", mal_result_from_pulse(error));
goto on_error5;
}
}
bufferSizeInFrames = mal_scale_buffer_size(bufferSizeInFrames, bufferSizeScaleFactor);
/* Internal format. */
pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
if (pActualSS != NULL) {
/* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */
if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) {
attr = mal_device__pa_buffer_attr_new(pDevice->capture.internalBufferSizeInFrames, pConfig->periods, pActualSS);
pOP = ((mal_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStreamCapture, &attr, NULL, NULL);
if (pOP != NULL) {
mal_device__wait_for_operation__pulse(pDevice, pOP);
((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
}
}
attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(ss.format))*ss.channels;
attr.tlength = attr.maxlength;
attr.prebuf = (mal_uint32)-1;
attr.minreq = attr.maxlength / pConfig->periods;
attr.fragsize = attr.maxlength / pConfig->periods;
ss = *pActualSS;
}
#ifdef MAL_DEBUG_OUTPUT
printf("[PulseAudio] attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; bufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, bufferSizeInFrames);
#endif
pDevice->capture.internalFormat = mal_format_from_pulse(ss.format);
pDevice->capture.internalChannels = ss.channels;
pDevice->capture.internalSampleRate = ss.rate;
char streamName[256];
if (pConfig->pulse.pStreamName != NULL) {
mal_strncpy_s(streamName, sizeof(streamName), pConfig->pulse.pStreamName, (size_t)-1);
} else {
static int g_StreamCounter = 0;
mal_strcpy_s(streamName, sizeof(streamName), "mini_al:");
mal_itoa_s(g_StreamCounter, streamName + 8, sizeof(streamName)-8, 10); // 8 = strlen("mini_al:")
g_StreamCounter += 1;
/* Internal channel map. */
pActualCMap = ((mal_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
if (pActualCMap != NULL) {
cmap = *pActualCMap;
}
for (mal_uint32 iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) {
pDevice->capture.internalChannelMap[iChannel] = mal_channel_position_from_pulse(cmap.map[iChannel]);
}
pDevice->pulse.pStream = ((mal_pa_stream_new_proc)pContext->pulse.pa_stream_new)((mal_pa_context*)pDevice->pulse.pPulseContext, streamName, &ss, &cmap);
if (pDevice->pulse.pStream == NULL) {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio stream.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
goto on_error3;
/* Buffer. */
pActualAttr = ((mal_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
if (pActualAttr != NULL) {
attr = *pActualAttr;
}
pDevice->capture.internalBufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->capture.internalFormat) * pDevice->capture.internalChannels);
pDevice->capture.internalPeriods = attr.maxlength / attr.fragsize;
#ifdef MAL_DEBUG_OUTPUT
printf("[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->capture.internalBufferSizeInFrames);
#endif
/* Name. */
devCapture = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
if (devCapture != NULL) {
mal_pa_operation* pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, devCapture, mal_device_source_name_callback, pDevice);
if (pOP != NULL) {
mal_device__wait_for_operation__pulse(pDevice, pOP);
((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
}
}
streamFlags = MAL_PA_STREAM_START_CORKED;
if (((pConfig->deviceType == mal_device_type_playback) && devPlayback != NULL) ||
((pConfig->deviceType == mal_device_type_capture) && devCapture != NULL)) {
streamFlags |= MAL_PA_STREAM_DONT_MOVE | MAL_PA_STREAM_FIX_FORMAT | MAL_PA_STREAM_FIX_RATE | MAL_PA_STREAM_FIX_CHANNELS;
/* Callback. */
//((mal_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((mal_pa_stream*)pDevice->pulse.pStreamCapture, mal_pulse_device_read_callback, pDevice);
}
if (pConfig->deviceType == mal_device_type_playback) {
error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStream, devPlayback, &attr, streamFlags, NULL, NULL);
if (pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) {
pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, devPlayback, mal_device_sink_info_callback, &sinkInfo);
if (pOP != NULL) {
mal_device__wait_for_operation__pulse(pDevice, pOP);
((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
} else {
error = ((mal_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((mal_pa_stream*)pDevice->pulse.pStream, devCapture, &attr, streamFlags);
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", mal_result_from_pulse(error));
goto on_error3;
}
ss = sinkInfo.sample_spec;
cmap = sinkInfo.channel_map;
pDevice->playback.internalBufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(bufferSizeInMilliseconds, ss.rate);
pDevice->playback.internalPeriods = pConfig->periods;
attr = mal_device__pa_buffer_attr_new(pDevice->playback.internalBufferSizeInFrames, pConfig->periods, &ss);
#ifdef MAL_DEBUG_OUTPUT
printf("[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->playback.internalBufferSizeInFrames);
#endif
pDevice->pulse.pStreamPlayback = mal_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNamePlayback, &ss, &cmap);
if (pDevice->pulse.pStreamPlayback == NULL) {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
goto on_error3;
}
mal_pa_stream_flags_t streamFlags = MAL_PA_STREAM_START_CORKED | MAL_PA_STREAM_FIX_FORMAT | MAL_PA_STREAM_FIX_RATE | MAL_PA_STREAM_FIX_CHANNELS;
if (devPlayback != NULL) {
streamFlags |= MAL_PA_STREAM_DONT_MOVE;
}
error = ((mal_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((mal_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL);
if (error != MAL_PA_OK) {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio stream.", mal_result_from_pulse(error));
goto on_error4;
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", mal_result_from_pulse(error));
goto on_error6;
}
while (((mal_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((mal_pa_stream*)pDevice->pulse.pStream) != MAL_PA_STREAM_READY) {
while (((mal_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((mal_pa_stream*)pDevice->pulse.pStreamPlayback) != MAL_PA_STREAM_READY) {
error = ((mal_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((mal_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL);
if (error < 0) {
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio stream.", mal_result_from_pulse(error));
goto on_error5;
result = mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio playback stream.", mal_result_from_pulse(error));
goto on_error7;
}
}
// Internal format.
pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStream);
/* Internal format. */
pActualSS = ((mal_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
if (pActualSS != NULL) {
// If anything has changed between the requested and the actual sample spec, we need to update the buffer.
/* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */
if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) {
attr.maxlength = bufferSizeInFrames * mal_get_bytes_per_sample(mal_format_from_pulse(pActualSS->format))*pActualSS->channels;
attr.tlength = attr.maxlength;
attr.prebuf = (mal_uint32)-1;
attr.minreq = attr.maxlength / pConfig->periods;
attr.fragsize = attr.maxlength / pConfig->periods;
attr = mal_device__pa_buffer_attr_new(pDevice->playback.internalBufferSizeInFrames, pConfig->periods, pActualSS);
pOP = ((mal_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStream, &attr, NULL, NULL);
pOP = ((mal_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStreamPlayback, &attr, NULL, NULL);
if (pOP != NULL) {
mal_device__wait_for_operation__pulse(pDevice, pOP);
((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
......@@ -13513,44 +13637,32 @@ mal_result mal_device_init__pulse(mal_context* pContext, const mal_device_config
ss = *pActualSS;
}
pDevice->internalFormat = mal_format_from_pulse(ss.format);
pDevice->internalChannels = ss.channels;
pDevice->internalSampleRate = ss.rate;
pDevice->playback.internalFormat = mal_format_from_pulse(ss.format);
pDevice->playback.internalChannels = ss.channels;
pDevice->playback.internalSampleRate = ss.rate;
// Internal channel map.
pActualCMap = ((mal_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((mal_pa_stream*)pDevice->pulse.pStream);
/* Internal channel map. */
pActualCMap = ((mal_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
if (pActualCMap != NULL) {
cmap = *pActualCMap;
}
for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) {
pDevice->internalChannelMap[iChannel] = mal_channel_position_from_pulse(cmap.map[iChannel]);
for (mal_uint32 iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) {
pDevice->playback.internalChannelMap[iChannel] = mal_channel_position_from_pulse(cmap.map[iChannel]);
}
// Buffer size.
pActualAttr = ((mal_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStream);
/* Buffer. */
pActualAttr = ((mal_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
if (pActualAttr != NULL) {
attr = *pActualAttr;
}
pDevice->playback.internalBufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->playback.internalFormat) * pDevice->playback.internalChannels);
pDevice->playback.internalPeriods = attr.maxlength / attr.tlength;
#ifdef MAL_DEBUG_OUTPUT
printf("[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalBufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->playback.internalBufferSizeInFrames);
#endif
pDevice->bufferSizeInFrames;
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
pDevice->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->capture.internalFormat)*pDevice->capture.internalChannels);
} else {
pDevice->bufferSizeInFrames = attr.maxlength / (mal_get_bytes_per_sample(pDevice->capture.internalFormat)*pDevice->capture.internalChannels);
}
pDevice->periods = attr.maxlength / attr.tlength;
#ifdef MAL_DEBUG_OUTPUT
printf("[PulseAudio] actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; pDevice->bufferSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->bufferSizeInFrames);
#endif
// Grab the name of the device if we can.
if (pDevice->type == mal_device_type_playback) {
devPlayback = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStream);
/* Name. */
devPlayback = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
if (devPlayback != NULL) {
mal_pa_operation* pOP = ((mal_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, devPlayback, mal_device_sink_name_callback, pDevice);
if (pOP != NULL) {
......@@ -13558,33 +13670,30 @@ mal_result mal_device_init__pulse(mal_context* pContext, const mal_device_config
((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
}
}
} else {
devCapture = ((mal_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((mal_pa_stream*)pDevice->pulse.pStream);
if (devCapture != NULL) {
mal_pa_operation* pOP = ((mal_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((mal_pa_context*)pDevice->pulse.pPulseContext, devCapture, mal_device_source_name_callback, pDevice);
if (pOP != NULL) {
mal_device__wait_for_operation__pulse(pDevice, pOP);
((mal_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP);
}
}
}
// Set callbacks for reading and writing data to/from the PulseAudio stream.
if (pConfig->deviceType == mal_device_type_playback) {
((mal_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_write_callback, pDevice);
} else {
((mal_pa_stream_set_read_callback_proc)pContext->pulse.pa_stream_set_read_callback)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_device_read_callback, pDevice);
/* Callback. */
//((mal_pa_stream_set_write_callback_proc)pContext->pulse.pa_stream_set_write_callback)((mal_pa_stream*)pDevice->pulse.pStreamPlayback, mal_pulse_device_write_callback, pDevice);
}
pDevice->pulse.fragmentSizeInBytes = attr.tlength;
return MAL_SUCCESS;
on_error5: ((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStream);
on_error4: ((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStream);
on_error7:
if (pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) {
((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
}
on_error6:
if (pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) {
((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
}
on_error5:
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
((mal_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
}
on_error4:
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
((mal_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
}
on_error3: ((mal_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((mal_pa_context*)pDevice->pulse.pPulseContext);
on_error2: ((mal_pa_context_unref_proc)pContext->pulse.pa_context_unref)((mal_pa_context*)pDevice->pulse.pPulseContext);
on_error1: ((mal_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((mal_pa_mainloop*)pDevice->pulse.pMainLoop);
......@@ -13601,13 +13710,24 @@ void mal_pulse_operation_complete_callback(mal_pa_stream* pStream, int success,
*pIsSuccessful = (mal_bool32)success;
}
mal_result mal_device__cork_stream__pulse(mal_device* pDevice, int cork)
mal_result mal_device__cork_stream__pulse(mal_device* pDevice, mal_device_type deviceType, int cork)
{
mal_context* pContext = pDevice->pContext;
mal_assert(pContext != NULL);
printf("TRACE: Corking: %d\n", cork);
/* This should not be called with a duplex device type. */
if (deviceType == mal_device_type_duplex) {
return MAL_INVALID_ARGS;
}
mal_bool32 wasSuccessful = MAL_FALSE;
mal_pa_operation* pOP = ((mal_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)((mal_pa_stream*)pDevice->pulse.pStream, cork, mal_pulse_operation_complete_callback, &wasSuccessful);
mal_pa_stream* pStream = (mal_pa_stream*)((deviceType == mal_device_type_capture) ? pDevice->pulse.pStreamCapture : pDevice->pulse.pStreamPlayback);
mal_assert(pStream != NULL);
mal_pa_operation* pOP = ((mal_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)(pStream, cork, mal_pulse_operation_complete_callback, &wasSuccessful);
if (pOP == NULL) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MAL_FAILED_TO_START_BACKEND_DEVICE : MAL_FAILED_TO_STOP_BACKEND_DEVICE);
}
......@@ -13630,6 +13750,7 @@ mal_result mal_device__cork_stream__pulse(mal_device* pDevice, int cork)
return MAL_SUCCESS;
}
#if 0
mal_result mal_device_start__pulse(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
......@@ -13637,6 +13758,21 @@ mal_result mal_device_start__pulse(mal_device* pDevice)
mal_context* pContext = pDevice->pContext;
mal_assert(pContext != NULL);
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
mal_result result = mal_device__cork_stream__pulse(pDevice, mal_device_type_capture, 0);
if (result != MAL_SUCCESS) {
return result;
}
}
if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) {
mal_result result = mal_device__cork_stream__pulse(pDevice, mal_device_type_playback, 0);
if (result != MAL_SUCCESS) {
return result;
}
}
// For both playback and capture we need to uncork the stream. Afterwards, for playback we need to fill in an initial chunk
// of data, equal to the trigger length. That should then start actual playback.
mal_result result = mal_device__cork_stream__pulse(pDevice, 0);
......@@ -13661,6 +13797,7 @@ mal_result mal_device_start__pulse(mal_device* pDevice)
return MAL_SUCCESS;
}
#endif
mal_result mal_device_stop__pulse(mal_device* pDevice)
{
......@@ -13670,23 +13807,250 @@ mal_result mal_device_stop__pulse(mal_device* pDevice)
mal_assert(pDevice != NULL);
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
result = mal_device__cork_stream__pulse(pDevice, mal_device_type_capture, 1);
if (result != MAL_SUCCESS) {
return result;
}
}
if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) {
/* The stream needs to be drained if it's a playback device. */
if (pDevice->type == mal_device_type_playback) {
pOP = ((mal_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((mal_pa_stream*)pDevice->pulse.pStream, mal_pulse_operation_complete_callback, &wasSuccessful);
pOP = ((mal_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((mal_pa_stream*)pDevice->pulse.pStreamPlayback, mal_pulse_operation_complete_callback, &wasSuccessful);
if (pOP != NULL) {
mal_device__wait_for_operation__pulse(pDevice, pOP);
((mal_pa_operation_unref_proc)pDevice->pContext->pulse.pa_operation_unref)(pOP);
}
result = mal_device__cork_stream__pulse(pDevice, mal_device_type_playback, 1);
if (result != MAL_SUCCESS) {
return result;
}
}
result = mal_device__cork_stream__pulse(pDevice, 1);
/* After corking we need to make sure any blocking reads and writes are able to return. */
mal_event_signal(&pDevice->pulse.readEvent);
mal_event_signal(&pDevice->pulse.writeEvent);
return MAL_SUCCESS;
}
mal_result mal_device_write__pulse(mal_device* pDevice, const void* pPCMFrames, mal_uint32 frameCount)
{
mal_assert(pDevice != NULL);
mal_assert(pPCMFrames != NULL);
mal_assert(frameCount > 0);
/* The stream needs to be uncorked first. */
if (((mal_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)(pDevice->pulse.pStreamPlayback)) {
mal_result result = mal_device__cork_stream__pulse(pDevice, mal_device_type_playback, 0);
if (result != MAL_SUCCESS) {
return result;
}
}
mal_uint32 totalFramesWritten = 0;
while (totalFramesWritten < frameCount) {
//printf("TRACE: Outer loop.\n");
/* Place the data into the mapped buffer if we have one. */
if (pDevice->pulse.pMappedBufferPlayback != NULL && pDevice->pulse.mappedBufferFramesRemainingPlayback > 0) {
//printf("TRACE: Copying data.\n");
mal_uint32 bpf = mal_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
mal_uint32 mappedBufferFramesConsumed = pDevice->pulse.mappedBufferFramesCapacityPlayback - pDevice->pulse.mappedBufferFramesRemainingPlayback;
void* pDst = (mal_uint8*)pDevice->pulse.pMappedBufferPlayback + (mappedBufferFramesConsumed * bpf);
const void* pSrc = (const mal_uint8*)pPCMFrames + (totalFramesWritten * bpf);
mal_uint32 framesToCopy = mal_min(pDevice->pulse.mappedBufferFramesRemainingPlayback, (frameCount - totalFramesWritten));
mal_copy_memory(pDst, pSrc, framesToCopy * bpf);
pDevice->pulse.mappedBufferFramesRemainingPlayback -= framesToCopy;
totalFramesWritten += framesToCopy;
}
/*
Getting here means we've run out of data in the currently mapped chunk. We need to write this to the device and then try
mapping another chunk. If this fails we need to wait for space to become available.
*/
if (pDevice->pulse.mappedBufferFramesCapacityPlayback > 0 && pDevice->pulse.mappedBufferFramesRemainingPlayback == 0) {
size_t nbytes = pDevice->pulse.mappedBufferFramesCapacityPlayback * mal_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
//printf("TRACE: Submitting data. %d\n", nbytes);
int error = ((mal_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)((mal_pa_stream*)pDevice->pulse.pStreamPlayback, pDevice->pulse.pMappedBufferPlayback, nbytes, NULL, 0, MAL_PA_SEEK_RELATIVE);
if (error < 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", mal_result_from_pulse(error));
}
pDevice->pulse.pMappedBufferPlayback = NULL;
pDevice->pulse.mappedBufferFramesRemainingPlayback = 0;
pDevice->pulse.mappedBufferFramesCapacityPlayback = 0;
}
mal_assert(totalFramesWritten <= frameCount);
if (totalFramesWritten == frameCount) {
break;
}
/* Getting here means we need to map a new buffer. If we don't have enough space we need to wait for more. */
for (;;) {
//printf("TRACE: Inner loop.\n");
/* If the device has been corked, don't try to continue. */
if (((mal_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)(pDevice->pulse.pStreamPlayback)) {
break;
}
size_t writableSizeInBytes = ((mal_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)((mal_pa_stream*)pDevice->pulse.pStreamPlayback);
if (writableSizeInBytes != (size_t)-1) {
size_t periodSizeInBytes = (pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods) * mal_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
if (writableSizeInBytes >= periodSizeInBytes) {
//printf("TRACE: Data available.\n");
/* Data is avaialable. */
size_t bytesToMap = periodSizeInBytes;
int error = ((mal_pa_stream_begin_write_proc)pDevice->pContext->pulse.pa_stream_begin_write)((mal_pa_stream*)pDevice->pulse.pStreamPlayback, &pDevice->pulse.pMappedBufferPlayback, &bytesToMap);
if (error < 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to map write buffer.", mal_result_from_pulse(error));
}
pDevice->pulse.mappedBufferFramesCapacityPlayback = bytesToMap / mal_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels);
pDevice->pulse.mappedBufferFramesRemainingPlayback = pDevice->pulse.mappedBufferFramesCapacityPlayback;
break;
} else {
/* No data available. Need to wait for more. */
printf("TRACE: Playback: pa_mainloop_iterate(). writableSizeInBytes=%d, periodSizeInBytes=%d\n", writableSizeInBytes, periodSizeInBytes);
int error = ((mal_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)(pDevice->pulse.pMainLoop, 1, NULL);
if (error < 0) {
return mal_result_from_pulse(error);
}
continue;
}
} else {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to query the stream's writable size.", MAL_ERROR);
}
}
}
return MAL_SUCCESS;
}
mal_result mal_device_read__pulse(mal_device* pDevice, void* pPCMFrames, mal_uint32 frameCount)
{
mal_assert(pDevice != NULL);
mal_assert(pPCMFrames != NULL);
mal_assert(frameCount > 0);
/* The stream needs to be uncorked first. */
if (((mal_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)(pDevice->pulse.pStreamCapture)) {
mal_result result = mal_device__cork_stream__pulse(pDevice, mal_device_type_capture, 0);
if (result != MAL_SUCCESS) {
return result;
}
}
mal_uint32 totalFramesRead = 0;
while (totalFramesRead < frameCount) {
/* If a buffer is mapped we need to write to that first. Once it's consumed we reset the event and unmap it. */
if (pDevice->pulse.mappedBufferFramesRemainingCapture > 0) {
mal_uint32 bpf = mal_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels);
mal_uint32 mappedBufferFramesConsumed = pDevice->pulse.mappedBufferFramesCapacityCapture - pDevice->pulse.mappedBufferFramesRemainingCapture;
mal_uint32 framesToCopy = mal_min(pDevice->pulse.mappedBufferFramesRemainingCapture, (frameCount - totalFramesRead));
void* pDst = (mal_uint8*)pPCMFrames + (totalFramesRead * bpf);
/*
This little bit of logic here is specifically for PulseAudio and it's hole management. The buffer pointer will be set to NULL
when the current fragment is a hole. For a hole we just output silence.
*/
if (pDevice->pulse.pMappedBufferCapture != NULL) {
const void* pSrc = (const mal_uint8*)pDevice->pulse.pMappedBufferCapture + (mappedBufferFramesConsumed * bpf);
mal_copy_memory(pDst, pSrc, framesToCopy * bpf);
} else {
mal_zero_memory(pDst, framesToCopy * bpf);
}
pDevice->pulse.mappedBufferFramesRemainingCapture -= framesToCopy;
totalFramesRead += framesToCopy;
}
/*
Getting here means we've run out of data in the currently mapped chunk. We need to drop this from the device and then try
mapping another chunk. If this fails we need to wait for data to become available.
*/
if (pDevice->pulse.mappedBufferFramesCapacityCapture > 0 && pDevice->pulse.mappedBufferFramesRemainingCapture == 0) {
//printf("TRACE: Dropping fragment. %d\n", nbytes);
int error = ((mal_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
if (error != 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to drop fragment.", mal_result_from_pulse(error));
}
pDevice->pulse.pMappedBufferCapture = NULL;
pDevice->pulse.mappedBufferFramesRemainingCapture = 0;
pDevice->pulse.mappedBufferFramesCapacityCapture = 0;
}
mal_assert(totalFramesRead <= frameCount);
if (totalFramesRead == frameCount) {
break;
}
/* Getting here means we need to map a new buffer. If we don't have enough data we wait for more. */
for (;;) {
//printf("TRACE: Inner loop.\n");
/* If the device has been corked, don't try to continue. */
if (((mal_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)(pDevice->pulse.pStreamCapture)) {
break;
}
size_t readableSizeInBytes = ((mal_pa_stream_readable_size_proc)pDevice->pContext->pulse.pa_stream_readable_size)((mal_pa_stream*)pDevice->pulse.pStreamCapture);
if (readableSizeInBytes != (size_t)-1) {
size_t periodSizeInBytes = (pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods) * mal_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels);
if (readableSizeInBytes >= periodSizeInBytes) {
//printf("TRACE: Data available.\n");
/* Data is avaialable. */
size_t bytesMapped = periodSizeInBytes;
int error = ((mal_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)((mal_pa_stream*)pDevice->pulse.pStreamCapture, &pDevice->pulse.pMappedBufferCapture, &bytesMapped);
if (error < 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to peek capture buffer.", mal_result_from_pulse(error));
}
if (pDevice->pulse.pMappedBufferCapture == NULL && bytesMapped == 0) {
/* Nothing available. This shouldn't happen because we checked earlier with pa_stream_readable_size(). I'm going to throw an error in this case. */
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Nothing available after peeking capture buffer.", MAL_ERROR);
}
pDevice->pulse.mappedBufferFramesCapacityCapture = bytesMapped / mal_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels);
pDevice->pulse.mappedBufferFramesRemainingCapture = pDevice->pulse.mappedBufferFramesCapacityCapture;
break;
} else {
/* No data available. Need to wait for more. */
printf("TRACE: Capture: pa_mainloop_iterate(). readableSizeInBytes=%d, periodSizeInBytes=%d\n", readableSizeInBytes, periodSizeInBytes);
int error = ((mal_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)(pDevice->pulse.pMainLoop, 1, NULL);
if (error < 0) {
return mal_result_from_pulse(error);
}
continue;
}
} else {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[PulseAudio] Failed to query the stream's readable size.", MAL_ERROR);
}
}
}
return MAL_SUCCESS;
}
#if 0
mal_result mal_device_break_main_loop__pulse(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
......@@ -13723,6 +14087,7 @@ mal_result mal_device_main_loop__pulse(mal_device* pDevice)
return MAL_SUCCESS;
}
#endif
mal_result mal_context_uninit__pulse(mal_context* pContext)
......@@ -13794,12 +14159,15 @@ mal_result mal_context_init__pulse(mal_context* pContext)
pContext->pulse.pa_stream_set_read_callback = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_set_read_callback");
pContext->pulse.pa_stream_flush = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_flush");
pContext->pulse.pa_stream_drain = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drain");
pContext->pulse.pa_stream_is_corked = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_is_corked");
pContext->pulse.pa_stream_cork = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_cork");
pContext->pulse.pa_stream_trigger = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_trigger");
pContext->pulse.pa_stream_begin_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_begin_write");
pContext->pulse.pa_stream_write = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_write");
pContext->pulse.pa_stream_peek = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_peek");
pContext->pulse.pa_stream_drop = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_drop");
pContext->pulse.pa_stream_writable_size = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_writable_size");
pContext->pulse.pa_stream_readable_size = (mal_proc)mal_dlsym(pContext->pulse.pulseSO, "pa_stream_readable_size");
#else
// This strange assignment system is just for type safety.
mal_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new;
......@@ -13837,12 +14205,15 @@ mal_result mal_context_init__pulse(mal_context* pContext)
mal_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback;
mal_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush;
mal_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain;
mal_pa_stream_ic_corked_proc _pa_stream_is_corked = pa_stream_is_corked;
mal_pa_stream_cork_proc _pa_stream_cork = pa_stream_cork;
mal_pa_stream_trigger_proc _pa_stream_trigger = pa_stream_trigger;
mal_pa_stream_begin_write_proc _pa_stream_begin_write = pa_stream_begin_write;
mal_pa_stream_write_proc _pa_stream_write = pa_stream_write;
mal_pa_stream_peek_proc _pa_stream_peek = pa_stream_peek;
mal_pa_stream_drop_proc _pa_stream_drop = pa_stream_drop;
mal_pa_stream_writable_size_proc _pa_stream_writable_size = pa_stream_writable_size;
mal_pa_stream_readable_size_proc _pa_stream_readable_size = pa_stream_readable_size;
pContext->pulse.pa_mainloop_new = (mal_proc)_pa_mainloop_new;
pContext->pulse.pa_mainloop_free = (mal_proc)_pa_mainloop_free;
......@@ -13879,12 +14250,15 @@ mal_result mal_context_init__pulse(mal_context* pContext)
pContext->pulse.pa_stream_set_read_callback = (mal_proc)_pa_stream_set_read_callback;
pContext->pulse.pa_stream_flush = (mal_proc)_pa_stream_flush;
pContext->pulse.pa_stream_drain = (mal_proc)_pa_stream_drain;
pContext->pulse.pa_stream_is_corked = (mal_proc)_pa_stream_is_corked;
pContext->pulse.pa_stream_cork = (mal_proc)_pa_stream_cork;
pContext->pulse.pa_stream_trigger = (mal_proc)_pa_stream_trigger;
pContext->pulse.pa_stream_begin_write = (mal_proc)_pa_stream_begin_write;
pContext->pulse.pa_stream_write = (mal_proc)_pa_stream_write;
pContext->pulse.pa_stream_peek = (mal_proc)_pa_stream_peek;
pContext->pulse.pa_stream_drop = (mal_proc)_pa_stream_drop;
pContext->pulse.pa_stream_writable_size = (mal_proc)_pa_stream_writable_size;
pContext->pulse.pa_stream_readable_size = (mal_proc)_pa_stream_readable_size;
#endif
pContext->onUninit = mal_context_uninit__pulse;
......@@ -13893,9 +14267,10 @@ mal_result mal_context_init__pulse(mal_context* pContext)
pContext->onGetDeviceInfo = mal_context_get_device_info__pulse;
pContext->onDeviceInit = mal_device_init__pulse;
pContext->onDeviceUninit = mal_device_uninit__pulse;
pContext->onDeviceStart = mal_device_start__pulse;
pContext->onDeviceStart = NULL;
pContext->onDeviceStop = mal_device_stop__pulse;
pContext->onDeviceMainLoop = mal_device_main_loop__pulse;
pContext->onDeviceWrite = mal_device_write__pulse;
pContext->onDeviceRead = mal_device_read__pulse;
// Although we have found the libpulse library, it doesn't necessarily mean PulseAudio is useable. We need to initialize
......@@ -20674,11 +21049,11 @@ mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData)
mal_uint32 periodSizeInFrames;
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
mal_assert(pDevice->capture.internalPeriods >= 2);
//mal_assert(pDevice->capture.internalPeriods >= 2);
mal_assert(pDevice->capture.internalBufferSizeInFrames >= pDevice->capture.internalPeriods);
periodSizeInFrames = pDevice->capture.internalBufferSizeInFrames / pDevice->capture.internalPeriods;
} else {
mal_assert(pDevice->playback.internalPeriods >= 2);
//mal_assert(pDevice->playback.internalPeriods >= 2);
mal_assert(pDevice->playback.internalBufferSizeInFrames >= pDevice->playback.internalPeriods);
periodSizeInFrames = pDevice->playback.internalBufferSizeInFrames / pDevice->playback.internalPeriods;
}
......@@ -21515,7 +21890,7 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf
printf(" %s (%s)\n", pDevice->capture.name, "Capture");
printf(" Format: %s -> %s\n", mal_get_format_name(pDevice->capture.format), mal_get_format_name(pDevice->capture.internalFormat));
printf(" Channels: %d -> %d\n", pDevice->capture.channels, pDevice->capture.internalChannels);
printf(" Sample Rate: %d -> %d\n", pDevice->capture.sampleRate, pDevice->capture.internalSampleRate);
printf(" Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->capture.internalSampleRate);
printf(" Conversion:\n");
printf(" Pre Format Conversion: %s\n", pDevice->capture.converter.isPreFormatConversionRequired ? "YES" : "NO");
printf(" Post Format Conversion: %s\n", pDevice->capture.converter.isPostFormatConversionRequired ? "YES" : "NO");
......@@ -21528,7 +21903,7 @@ mal_result mal_device_init(mal_context* pContext, const mal_device_config* pConf
printf(" %s (%s)\n", pDevice->playback.name, "Playback");
printf(" Format: %s -> %s\n", mal_get_format_name(pDevice->playback.format), mal_get_format_name(pDevice->playback.internalFormat));
printf(" Channels: %d -> %d\n", pDevice->playback.channels, pDevice->playback.internalChannels);
printf(" Sample Rate: %d -> %d\n", pDevice->playback.sampleRate, pDevice->playback.internalSampleRate);
printf(" Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->playback.internalSampleRate);
printf(" Conversion:\n");
printf(" Pre Format Conversion: %s\n", pDevice->playback.converter.isPreFormatConversionRequired ? "YES" : "NO");
printf(" Post Format Conversion: %s\n", pDevice->playback.converter.isPostFormatConversionRequired ? "YES" : "NO");
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