Commit 79e83313 authored by David Reid's avatar David Reid

Work in progress on full-duplex for sndio.

parent a311b906
...@@ -2181,8 +2181,10 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device ...@@ -2181,8 +2181,10 @@ MAL_ALIGNED_STRUCT(MAL_SIMD_ALIGNMENT) mal_device
#ifdef MAL_SUPPORT_SNDIO #ifdef MAL_SUPPORT_SNDIO
struct struct
{ {
mal_ptr handle; mal_ptr handlePlayback;
mal_bool32 isStarted; mal_ptr handleCapture;
mal_bool32 isStartedPlayback;
mal_bool32 isStartedCapture;
} sndio; } sndio;
#endif #endif
#ifdef MAL_SUPPORT_AUDIO4 #ifdef MAL_SUPPORT_AUDIO4
...@@ -17621,111 +17623,106 @@ void mal_device_uninit__sndio(mal_device* pDevice) ...@@ -17621,111 +17623,106 @@ void mal_device_uninit__sndio(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
((mal_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
((mal_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handleCapture);
}
if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
((mal_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handlePlayback);
}
} }
mal_result mal_device_init__sndio(mal_context* pContext, const mal_device_config* pConfig, mal_device* pDevice) mal_result mal_device_init_handle__sndio(mal_context* pContext, const mal_device_config* pConfig, mal_device_type deviceType, mal_device* pDevice)
{ {
(void)pContext; mal_result result;
const char* pDeviceName;
mal_ptr handle;
int openFlags = 0;
struct mal_sio_cap caps;
struct mal_sio_par par;
mal_device_id* pDeviceID;
mal_format format;
mal_uint32 channels;
mal_uint32 sampleRate;
mal_format internalFormat;
mal_uint32 internalChannels;
mal_uint32 internalSampleRate;
mal_uint32 internalBufferSizeInFrames;
mal_uint32 internalPeriods;
mal_assert(pDevice != NULL); mal_assert(pContext != NULL);
mal_zero_object(&pDevice->sndio); mal_assert(pConfig != NULL);
mal_assert(deviceType != mal_device_type_duplex);
mal_assert(pDevice != NULL);
/* Full-duplex is not yet implemented. */ if (deviceType == mal_device_type_capture) {
if (pConfig->deviceType == mal_device_type_duplex) { openFlags = MAL_SIO_REC;
return MAL_INVALID_ARGS; pDeviceID = pConfig->capture.pDeviceID;
format = pConfig->capture.format;
channels = pConfig->capture.channels;
sampleRate = pConfig->sampleRate;
} else {
openFlags = MAL_SIO_PLAY;
pDeviceID = pConfig->playback.pDeviceID;
format = pConfig->playback.format;
channels = pConfig->playback.channels;
sampleRate = pConfig->sampleRate;
} }
const char* deviceNamePlayback = MAL_SIO_DEVANY;
const char* deviceNameCapture = MAL_SIO_DEVANY;
if (pConfig->playback.pDeviceID != NULL) { pDeviceName = MAL_SIO_DEVANY;
deviceNamePlayback = pConfig->playback.pDeviceID->sndio; if (pDeviceID != NULL) {
} pDeviceName = pDeviceID->sndio;
if (pConfig->capture.pDeviceID != NULL) {
deviceNameCapture = pConfig->capture.pDeviceID->sndio;
} }
if (pConfig->deviceType == mal_device_type_playback) {
pDevice->sndio.handle = (mal_ptr)((mal_sio_open_proc)pContext->sndio.sio_open)(deviceNamePlayback, MAL_SIO_PLAY, 0);
} else if (pConfig->deviceType == mal_device_type_capture) {
pDevice->sndio.handle = (mal_ptr)((mal_sio_open_proc)pContext->sndio.sio_open)(deviceNameCapture, MAL_SIO_REC, 0);
} else if (pConfig->deviceType == mal_device_type_duplex) {
/*
TODO: Handle this case.
- If the device names are the same, try opening in MAL_SIO_PLAY | MAL_SIO_REC mode. handle = (mal_ptr)((mal_sio_open_proc)pContext->sndio.sio_open)(pDeviceName, openFlags, 0);
- If the device names are different or MAL_SIO_PLAY | MAL_SIO_REC mode fails, fall back to separate device handles. if (handle == NULL) {
*/
mal_assert(MAL_FALSE);
return MAL_INVALID_ARGS;
} else {
mal_assert(MAL_FALSE); /* Should never hit this. */
return MAL_INVALID_ARGS;
}
if (pDevice->sndio.handle == NULL) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE);
} }
// We need to retrieve the device caps to determine the most appropriate format to use. /* We need to retrieve the device caps to determine the most appropriate format to use. */
struct mal_sio_cap caps; if (((mal_sio_getcap_proc)pContext->sndio.sio_getcap)((struct mal_sio_hdl*)handle, &caps) == 0) {
if (((mal_sio_getcap_proc)pContext->sndio.sio_getcap)((struct mal_sio_hdl*)pDevice->sndio.handle, &caps) == 0) { ((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)handle);
((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MAL_ERROR); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MAL_ERROR);
} }
mal_format desiredFormat = pDevice->format;
if (pDevice->usingDefaultFormat) {
desiredFormat = mal_find_best_format_from_sio_cap__sndio(&caps);
}
if (desiredFormat == mal_format_unknown) {
desiredFormat = pDevice->format;
}
// Note: sndio reports a huge range of available channels. This is inconvenient for us because there's no real /*
// way, as far as I can tell, to get the _actual_ channel count of the device. I'm therefore restricting this Note: sndio reports a huge range of available channels. This is inconvenient for us because there's no real
// to the requested channels, regardless of whether or not the default channel count is requested. way, as far as I can tell, to get the _actual_ channel count of the device. I'm therefore restricting this
// to the requested channels, regardless of whether or not the default channel count is requested.
// For hardware devices, I'm suspecting only a single channel count will be reported and we can safely use the
// value returned by mal_find_best_channels_from_sio_cap__sndio(). For hardware devices, I'm suspecting only a single channel count will be reported and we can safely use the
mal_uint32 desiredChannels = pDevice->channels; value returned by mal_find_best_channels_from_sio_cap__sndio().
if (pDevice->usingDefaultChannels) { */
const char* deviceName; if (deviceType == mal_device_type_capture) {
if (pConfig->deviceType == mal_device_type_playback) { if (pDevice->capture.usingDefaultFormat) {
deviceName = deviceNamePlayback; format = mal_find_best_format_from_sio_cap__sndio(&caps);
} else {
deviceName = deviceNameCapture;
} }
if (pDevice->capture.usingDefaultChannels) {
if (strlen(deviceName) > strlen("rsnd/") && strncmp(deviceName, "rsnd/", strlen("rsnd/")) == 0) { if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) {
desiredChannels = mal_find_best_channels_from_sio_cap__sndio(&caps, pConfig->deviceType, desiredFormat); channels = mal_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format);
}
}
} else {
if (pDevice->playback.usingDefaultFormat) {
format = mal_find_best_format_from_sio_cap__sndio(&caps);
}
if (pDevice->playback.usingDefaultChannels) {
if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) {
channels = mal_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format);
}
} }
} }
if (desiredChannels == 0) {
desiredChannels = pDevice->channels;
}
mal_uint32 desiredSampleRate = pDevice->sampleRate;
if (pDevice->usingDefaultSampleRate) { if (pDevice->usingDefaultSampleRate) {
desiredSampleRate = mal_find_best_sample_rate_from_sio_cap__sndio(&caps, pConfig->deviceType, desiredFormat, desiredChannels); sampleRate = mal_find_best_sample_rate_from_sio_cap__sndio(&caps, pConfig->deviceType, format, channels);
}
if (desiredSampleRate == 0) {
desiredSampleRate = pDevice->sampleRate;
} }
struct mal_sio_par par;
((mal_sio_initpar_proc)pDevice->pContext->sndio.sio_initpar)(&par); ((mal_sio_initpar_proc)pDevice->pContext->sndio.sio_initpar)(&par);
par.msb = 0; par.msb = 0;
par.le = mal_is_little_endian(); par.le = mal_is_little_endian();
switch (desiredFormat) { switch (format) {
case mal_format_u8: case mal_format_u8:
{ {
par.bits = 8; par.bits = 8;
...@@ -17757,74 +17754,87 @@ mal_result mal_device_init__sndio(mal_context* pContext, const mal_device_config ...@@ -17757,74 +17754,87 @@ mal_result mal_device_init__sndio(mal_context* pContext, const mal_device_config
} break; } break;
} }
if (pConfig->deviceType == mal_device_type_playback) { if (deviceType == mal_device_type_capture) {
par.pchan = desiredChannels; par.rchan = channels;
} else { } else {
par.rchan = desiredChannels; par.pchan = channels;
} }
par.rate = desiredSampleRate; par.rate = sampleRate;
// Try calculating an appropriate default buffer size after we have the sample rate. internalBufferSizeInFrames = pConfig->bufferSizeInFrames;
mal_uint32 desiredBufferSizeInFrames = pDevice->bufferSizeInFrames; if (internalBufferSizeInFrames == 0) {
if (desiredBufferSizeInFrames == 0) { internalBufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pConfig->bufferSizeInMilliseconds, par.rate);
desiredBufferSizeInFrames = mal_calculate_buffer_size_in_frames_from_milliseconds(pDevice->bufferSizeInMilliseconds, par.rate);
} }
par.round = desiredBufferSizeInFrames / pDevice->periods; par.round = internalBufferSizeInFrames / pConfig->periods;
par.appbufsz = par.round * pDevice->periods; par.appbufsz = par.round * pConfig->periods;
if (((mal_sio_setpar_proc)pContext->sndio.sio_setpar)((struct mal_sio_hdl*)pDevice->sndio.handle, &par) == 0) { if (((mal_sio_setpar_proc)pContext->sndio.sio_setpar)((struct mal_sio_hdl*)handle, &par) == 0) {
((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); ((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MAL_FORMAT_NOT_SUPPORTED);
} }
if (((mal_sio_getpar_proc)pContext->sndio.sio_getpar)((struct mal_sio_hdl*)pDevice->sndio.handle, &par) == 0) { if (((mal_sio_getpar_proc)pContext->sndio.sio_getpar)((struct mal_sio_hdl*)handle, &par) == 0) {
((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)pDevice->sndio.handle); ((mal_sio_close_proc)pContext->sndio.sio_close)((struct mal_sio_hdl*)handle);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MAL_FORMAT_NOT_SUPPORTED);
} }
pDevice->internalFormat = mal_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); internalFormat = mal_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb);
internalChannels = (deviceType == mal_device_type_capture) ? par.rchan : par.pchan;
if (pConfig->deviceType == mal_device_type_playback) { internalSampleRate = par.rate;
pDevice->internalChannels = par.pchan; internalPeriods = par.appbufsz / par.round;
internalBufferSizeInFrames = par.appbufsz;
if (deviceType == mal_device_type_capture) {
pDevice->capture.internalFormat = internalFormat;
pDevice->capture.internalChannels = internalChannels;
pDevice->capture.internalSampleRate = internalSampleRate;
mal_get_standard_channel_map(mal_standard_channel_map_sndio, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap);
pDevice->capture.internalBufferSizeInFrames = internalBufferSizeInFrames;
pDevice->capture.internalPeriods = internalPeriods;
} else { } else {
pDevice->internalChannels = par.rchan; pDevice->playback.internalFormat = internalFormat;
pDevice->playback.internalChannels = internalChannels;
pDevice->playback.internalSampleRate = internalSampleRate;
mal_get_standard_channel_map(mal_standard_channel_map_sndio, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap);
pDevice->playback.internalBufferSizeInFrames = internalBufferSizeInFrames;
pDevice->playback.internalPeriods = internalPeriods;
} }
pDevice->internalSampleRate = par.rate;
pDevice->periods = par.appbufsz / par.round;
if (pDevice->periods < 2) {
pDevice->periods = 2;
}
pDevice->bufferSizeInFrames = par.round * pDevice->periods;
mal_get_standard_channel_map(mal_standard_channel_map_sndio, pDevice->internalChannels, pDevice->internalChannelMap);
#ifdef MAL_DEBUG_OUTPUT #ifdef MAL_DEBUG_OUTPUT
printf("DEVICE INFO\n"); printf("DEVICE INFO\n");
printf(" Format: %s\n", mal_get_format_name(pDevice->internalFormat)); printf(" Format: %s\n", mal_get_format_name(internalFormat));
printf(" Channels: %d\n", pDevice->internalChannels); printf(" Channels: %d\n", internalChannels);
printf(" Sample Rate: %d\n", pDevice->internalSampleRate); printf(" Sample Rate: %d\n", internalSampleRate);
printf(" Buffer Size: %d\n", pDevice->bufferSizeInFrames); printf(" Buffer Size: %d\n", internalBufferSizeInFrames);
printf(" Periods: %d\n", pDevice->periods); printf(" Periods: %d\n", internalPeriods);
printf(" appbufsz: %d\n", par.appbufsz); printf(" appbufsz: %d\n", par.appbufsz);
printf(" round: %d\n", par.round); printf(" round: %d\n", par.round);
#endif #endif
return MAL_SUCCESS; return MAL_SUCCESS;
} }
mal_result mal_device_start__sndio(mal_device* pDevice) mal_result mal_device_init__sndio(mal_context* pContext, const mal_device_config* pConfig, mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
if (((mal_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct mal_sio_hdl*)pDevice->sndio.handle) == 0) { mal_zero_object(&pDevice->sndio);
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to start backend device.", MAL_FAILED_TO_START_BACKEND_DEVICE);
if (pConfig->deviceType == mal_device_type_capture || pConfig->deviceType == mal_device_type_duplex) {
mal_result result = mal_device_init_handle__sndio(pContext, pConfig, mal_device_type_capture, pDevice);
if (result != MAL_SUCCESS) {
return result;
}
}
if (pConfig->deviceType == mal_device_type_playback || pConfig->deviceType == mal_device_type_duplex) {
mal_result result = mal_device_init_handle__sndio(pContext, pConfig, mal_device_type_playback, pDevice);
if (result != MAL_SUCCESS) {
return result;
}
} }
mal_atomic_exchange_32(&pDevice->sndio.isStarted, MAL_TRUE);
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -17832,19 +17842,29 @@ mal_result mal_device_stop__sndio(mal_device* pDevice) ...@@ -17832,19 +17842,29 @@ mal_result mal_device_stop__sndio(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
((mal_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct mal_sio_hdl*)pDevice->sndio.handle); if (pDevice->type == mal_device_type_capture || pDevice->type == mal_device_type_duplex) {
mal_atomic_exchange_32(&pDevice->sndio.isStarted, MAL_FALSE); ((mal_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct mal_sio_hdl*)pDevice->sndio.handleCapture);
mal_atomic_exchange_32(&pDevice->sndio.isStartedCapture, MAL_FALSE);
}
if (pDevice->type == mal_device_type_playback || pDevice->type == mal_device_type_duplex) {
((mal_sio_stop_proc)pDevice->pContext->sndio.sio_stop)((struct mal_sio_hdl*)pDevice->sndio.handlePlayback);
mal_atomic_exchange_32(&pDevice->sndio.isStartedPlayback, MAL_FALSE);
}
return MAL_SUCCESS; return MAL_SUCCESS;
} }
mal_result mal_device_write__sndio(mal_device* pDevice, const void* pPCMFrames, mal_uint32 frameCount) mal_result mal_device_write__sndio(mal_device* pDevice, const void* pPCMFrames, mal_uint32 frameCount)
{ {
if (!pDevice->sndio.isStarted) { int result;
mal_device_start__sndio(pDevice); /* <-- Doesn't actually playback until data is written. */
if (!pDevice->sndio.isStartedPlayback) {
((mal_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct mal_sio_hdl*)pDevice->sndio.handlePlayback); /* <-- Doesn't actually playback until data is written. */
mal_atomic_exchange_32(&pDevice->sndio.isStartedPlayback, MAL_TRUE);
} }
int result = ((mal_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct mal_sio_hdl*)pDevice->sndio.handle, pPCMFrames, frameCount * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels)); result = ((mal_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct mal_sio_hdl*)pDevice->sndio.handlePlayback, pPCMFrames, frameCount * mal_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels));
if (result == 0) { if (result == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
} }
...@@ -17854,11 +17874,14 @@ mal_result mal_device_write__sndio(mal_device* pDevice, const void* pPCMFrames, ...@@ -17854,11 +17874,14 @@ mal_result mal_device_write__sndio(mal_device* pDevice, const void* pPCMFrames,
mal_result mal_device_read__sndio(mal_device* pDevice, void* pPCMFrames, mal_uint32 frameCount) mal_result mal_device_read__sndio(mal_device* pDevice, void* pPCMFrames, mal_uint32 frameCount)
{ {
if (!pDevice->sndio.isStarted) { int result;
mal_device_start__sndio(pDevice);
if (!pDevice->sndio.isStartedCapture) {
((mal_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct mal_sio_hdl*)pDevice->sndio.handleCapture); /* <-- Doesn't actually playback until data is written. */
mal_atomic_exchange_32(&pDevice->sndio.isStartedCapture, MAL_TRUE);
} }
int result = ((mal_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct mal_sio_hdl*)pDevice->sndio.handle, pPCMFrames, frameCount * mal_get_bytes_per_frame(pDevice->internalFormat, pDevice->internalChannels)); result = ((mal_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct mal_sio_hdl*)pDevice->sndio.handleCapture, pPCMFrames, frameCount * mal_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels));
if (result == 0) { if (result == 0) {
return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); return mal_post_error(pDevice, MAL_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE);
} }
...@@ -18320,11 +18343,15 @@ mal_result mal_device_init_fd__audio4(mal_context* pContext, const mal_device_co ...@@ -18320,11 +18343,15 @@ mal_result mal_device_init_fd__audio4(mal_context* pContext, const mal_device_co
/* The first thing to do is open the file. */ /* The first thing to do is open the file. */
pDeviceName = "/dev/audio"; pDeviceName = "/dev/audio";
if (deviceType == mal_device_type_capture) { if (deviceType == mal_device_type_capture) {
pDeviceName = pConfig->capture.pDeviceID->audio4;
fdFlags = O_RDONLY; fdFlags = O_RDONLY;
if (pConfig->capture.pDeviceID != NULL) {
pDeviceName = pConfig->capture.pDeviceID->audio4;
}
} else { } else {
pDeviceName = pConfig->playback.pDeviceID->audio4;
fdFlags = O_WRONLY; fdFlags = O_WRONLY;
if (pConfig->playback.pDeviceID != NULL) {
pDeviceName = pConfig->playback.pDeviceID->audio4;
}
} }
fdFlags |= O_NONBLOCK; fdFlags |= O_NONBLOCK;
...@@ -18629,7 +18656,7 @@ mal_result mal_context_init__audio4(mal_context* pContext) ...@@ -18629,7 +18656,7 @@ mal_result mal_context_init__audio4(mal_context* pContext)
return MAL_SUCCESS; return MAL_SUCCESS;
} }
#endif // audio4 #endif /* audio4 */
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
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