Commit 6ed399d5 authored by David Reid's avatar David Reid

Remove the notion of fragments and use a simplified buffer size and periods concept instead.

parent 08db9fbd
...@@ -112,7 +112,7 @@ typedef void* mal_ptr; ...@@ -112,7 +112,7 @@ typedef void* mal_ptr;
#endif #endif
#ifdef MAL_ENABLE_DSOUND #ifdef MAL_ENABLE_DSOUND
#define MAL_MAX_FRAGMENTS_DSOUND 16 #define MAL_MAX_PERIODS_DSOUND 16
#endif #endif
typedef int mal_result; typedef int mal_result;
...@@ -205,8 +205,8 @@ struct mal_device ...@@ -205,8 +205,8 @@ struct mal_device
mal_format format; mal_format format;
mal_uint32 channels; mal_uint32 channels;
mal_uint32 sampleRate; mal_uint32 sampleRate;
mal_uint32 fragmentSizeInFrames; mal_uint32 bufferSizeInFrames;
mal_uint32 fragmentCount; mal_uint32 periods;
mal_uint32 state; mal_uint32 state;
mal_recv_proc onRecv; mal_recv_proc onRecv;
mal_send_proc onSend; mal_send_proc onSend;
...@@ -231,7 +231,7 @@ struct mal_device ...@@ -231,7 +231,7 @@ struct mal_device
/*LPDIRECTSOUNDCAPTURE8*/ mal_ptr pCapture; /*LPDIRECTSOUNDCAPTURE8*/ mal_ptr pCapture;
/*LPDIRECTSOUNDCAPTUREBUFFER8*/ mal_ptr pCaptureBuffer; /*LPDIRECTSOUNDCAPTUREBUFFER8*/ mal_ptr pCaptureBuffer;
/*LPDIRECTSOUNDNOTIFY*/ mal_ptr pNotify; /*LPDIRECTSOUNDNOTIFY*/ mal_ptr pNotify;
/*HANDLE*/ mal_handle pNotifyEvents[MAL_MAX_FRAGMENTS_DSOUND]; // One event handle for each fragment. /*HANDLE*/ mal_handle pNotifyEvents[MAL_MAX_PERIODS_DSOUND]; // One event handle for each period.
/*HANDLE*/ mal_handle hStopEvent; /*HANDLE*/ mal_handle hStopEvent;
mal_uint32 lastProcessedFrame; // This is circular. mal_uint32 lastProcessedFrame; // This is circular.
mal_uint32 rewindTarget; // Where we want to rewind to. Set to ~0UL when it is not being rewound. mal_uint32 rewindTarget; // Where we want to rewind to. Set to ~0UL when it is not being rewound.
...@@ -312,7 +312,7 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d ...@@ -312,7 +312,7 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d
// Efficiency: LOW // Efficiency: LOW
// This API will dynamically link to backend DLLs/SOs like dsound.dll, and is otherwise just slow // This API will dynamically link to backend DLLs/SOs like dsound.dll, and is otherwise just slow
// due to the nature of it being an initialization API. // due to the nature of it being an initialization API.
mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount, mal_log_proc onLog); mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, mal_uint32 periods, mal_log_proc onLog);
// Uninitializes a device. // Uninitializes a device.
// //
...@@ -337,8 +337,6 @@ void mal_device_uninit(mal_device* pDevice); ...@@ -337,8 +337,6 @@ void mal_device_uninit(mal_device* pDevice);
// Sets the callback to use when the application has received data from the device. // Sets the callback to use when the application has received data from the device.
// //
// The fragment size specified at initialization time controls the sample count.
//
// Thread Safety: SAFE // Thread Safety: SAFE
// This API is implemented as a simple atomic assignment. // This API is implemented as a simple atomic assignment.
// //
...@@ -350,8 +348,7 @@ void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc); ...@@ -350,8 +348,7 @@ void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc);
// //
// Note that the implementation of this callback must copy over as many samples as is available. The // Note that the implementation of this callback must copy over as many samples as is available. The
// return value specifies how many samples were written to the output buffer. The backend will fill // return value specifies how many samples were written to the output buffer. The backend will fill
// any leftover samples with silence. The fragment size specified at initialization time controls the // any leftover samples with silence.
// number of samples the device will request.
// //
// Thread Safety: SAFE // Thread Safety: SAFE
// This API is implemented as a simple atomic assignment. // This API is implemented as a simple atomic assignment.
...@@ -459,14 +456,14 @@ mal_uint32 mal_device_get_available_rewind_amount(mal_device* pDevice); ...@@ -459,14 +456,14 @@ mal_uint32 mal_device_get_available_rewind_amount(mal_device* pDevice);
// This currently waits on a mutex for thread-safety, but should otherwise be fairly efficient. // This currently waits on a mutex for thread-safety, but should otherwise be fairly efficient.
mal_uint32 mal_device_rewind(mal_device* pDevice, mal_uint32 framesToRewind); mal_uint32 mal_device_rewind(mal_device* pDevice, mal_uint32 framesToRewind);
// Retrieves the size of a fragment in bytes for the given device. // Retrieves the size of the in bytes for the given device.
// //
// Thread Safety: SAFE // Thread Safety: SAFE
// This is calculated from constant values which are set at initialization time and never change. // This is calculated from constant values which are set at initialization time and never change.
// //
// Efficiency: HIGH // Efficiency: HIGH
// This is implemented with just a few 32-bit integer multiplications. // This is implemented with just a few 32-bit integer multiplications.
mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice); mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice);
// Retrieves the size of a sample in bytes for the given format. // Retrieves the size of a sample in bytes for the given format.
// //
...@@ -743,7 +740,7 @@ void mal_timer_init(mal_timer* pTimer) ...@@ -743,7 +740,7 @@ void mal_timer_init(mal_timer* pTimer)
struct timespec newTime; struct timespec newTime;
clock_gettime(CLOCK_MONOTONIC, &newTime); clock_gettime(CLOCK_MONOTONIC, &newTime);
pTimer->counter = (newTime.tv_sec * 1000000000LL) + newTime.tv_nsec; pTimer->counter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec;
} }
double mal_timer_get_time_in_seconds(mal_timer* pTimer) double mal_timer_get_time_in_seconds(mal_timer* pTimer)
...@@ -751,7 +748,7 @@ double mal_timer_get_time_in_seconds(mal_timer* pTimer) ...@@ -751,7 +748,7 @@ double mal_timer_get_time_in_seconds(mal_timer* pTimer)
struct timespec newTime; struct timespec newTime;
clock_gettime(CLOCK_MONOTONIC, &newTime); clock_gettime(CLOCK_MONOTONIC, &newTime);
uint64_t newTimeCounter = (newTime.tv_sec * 1000000000LL) + newTime.tv_nsec; uint64_t newTimeCounter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec;
uint64_t oldTimeCounter = pTimer->counter; uint64_t oldTimeCounter = pTimer->counter;
return (newTimeCounter - oldTimeCounter) / 1000000000.0; return (newTimeCounter - oldTimeCounter) / 1000000000.0;
...@@ -1151,17 +1148,19 @@ static void mal_device_uninit__null(mal_device* pDevice) ...@@ -1151,17 +1148,19 @@ static void mal_device_uninit__null(mal_device* pDevice)
mal_free(pDevice->null_device.pBuffer); mal_free(pDevice->null_device.pBuffer);
} }
static mal_result mal_device_init__null(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount) static mal_result mal_device_init__null(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, mal_uint32 periods)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
pDevice->api = mal_api_null; pDevice->api = mal_api_null;
pDevice->bufferSizeInFrames = bufferSizeInFrames;
pDevice->periods = periods;
pDevice->null_device.pBuffer = (mal_uint8*)mal_malloc(mal_device_get_fragment_size_in_bytes(pDevice) * pDevice->fragmentCount); pDevice->null_device.pBuffer = (mal_uint8*)mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format));
if (pDevice->null_device.pBuffer == NULL) { if (pDevice->null_device.pBuffer == NULL) {
return MAL_OUT_OF_MEMORY; return MAL_OUT_OF_MEMORY;
} }
mal_zero_memory(pDevice->null_device.pBuffer, mal_device_get_fragment_size_in_bytes(pDevice) * pDevice->fragmentCount); mal_zero_memory(pDevice->null_device.pBuffer, mal_device_get_buffer_size_in_bytes(pDevice));
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -1199,7 +1198,7 @@ static mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_u ...@@ -1199,7 +1198,7 @@ static mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_u
mal_uint64 currentFrameAbs = (mal_uint64)(mal_timer_get_time_in_seconds(&pDevice->null_device.timer) * pDevice->sampleRate) / pDevice->channels; mal_uint64 currentFrameAbs = (mal_uint64)(mal_timer_get_time_in_seconds(&pDevice->null_device.timer) * pDevice->sampleRate) / pDevice->channels;
*pCurrentPos = currentFrameAbs % (pDevice->fragmentSizeInFrames * pDevice->fragmentCount); *pCurrentPos = currentFrameAbs % pDevice->bufferSizeInFrames;
return MAL_TRUE; return MAL_TRUE;
} }
...@@ -1218,7 +1217,7 @@ static mal_bool32 mal_device__get_available_frames__null(mal_device* pDevice) ...@@ -1218,7 +1217,7 @@ static mal_bool32 mal_device__get_available_frames__null(mal_device* pDevice)
// //
// For a recording device it's the other way around - the last processed frame is always _behind_ the current // For a recording device it's the other way around - the last processed frame is always _behind_ the current
// frame and the space between is the available space. // frame and the space between is the available space.
mal_uint32 totalFrameCount = pDevice->fragmentSizeInFrames*pDevice->fragmentCount; mal_uint32 totalFrameCount = pDevice->bufferSizeInFrames;
if (pDevice->type == mal_device_type_playback) { if (pDevice->type == mal_device_type_playback) {
mal_uint32 committedBeg = currentFrame; mal_uint32 committedBeg = currentFrame;
mal_uint32 committedEnd = pDevice->null_device.lastProcessedFrame; mal_uint32 committedEnd = pDevice->null_device.lastProcessedFrame;
...@@ -1250,15 +1249,10 @@ static mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice) ...@@ -1250,15 +1249,10 @@ static mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice)
while (!pDevice->null_device.breakFromMainLoop) { while (!pDevice->null_device.breakFromMainLoop) {
mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice); mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice);
if (framesAvailable == 0) { if (framesAvailable > 0) {
return 0; return framesAvailable;
}
// Never return more frames that will fit in a fragment.
if (framesAvailable >= pDevice->fragmentSizeInFrames) {
return pDevice->fragmentSizeInFrames;
} }
mal_sleep(16); mal_sleep(16);
} }
...@@ -1297,7 +1291,7 @@ static mal_result mal_device__main_loop__null(mal_device* pDevice) ...@@ -1297,7 +1291,7 @@ static mal_result mal_device__main_loop__null(mal_device* pDevice)
mal_device__send_frames_to_client(pDevice, framesAvailable, pDevice->null_device.pBuffer + lockOffset); mal_device__send_frames_to_client(pDevice, framesAvailable, pDevice->null_device.pBuffer + lockOffset);
} }
pDevice->null_device.lastProcessedFrame = (pDevice->null_device.lastProcessedFrame + framesAvailable) % (pDevice->fragmentSizeInFrames*pDevice->fragmentCount); pDevice->null_device.lastProcessedFrame = (pDevice->null_device.lastProcessedFrame + framesAvailable) % pDevice->bufferSizeInFrames;
} }
return MAL_SUCCESS; return MAL_SUCCESS;
...@@ -1444,7 +1438,7 @@ static void mal_device_uninit__dsound(mal_device* pDevice) ...@@ -1444,7 +1438,7 @@ static void mal_device_uninit__dsound(mal_device* pDevice)
CloseHandle(pDevice->dsound.hStopEvent); CloseHandle(pDevice->dsound.hStopEvent);
} }
for (mal_uint32 i = 0; i < pDevice->fragmentCount; ++i) { for (mal_uint32 i = 0; i < pDevice->periods; ++i) {
if (pDevice->dsound.pNotifyEvents[i]) { if (pDevice->dsound.pNotifyEvents[i]) {
CloseHandle(pDevice->dsound.pNotifyEvents[i]); CloseHandle(pDevice->dsound.pNotifyEvents[i]);
} }
...@@ -1471,7 +1465,7 @@ static void mal_device_uninit__dsound(mal_device* pDevice) ...@@ -1471,7 +1465,7 @@ static void mal_device_uninit__dsound(mal_device* pDevice)
} }
} }
static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount) static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, mal_uint32 periods)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
pDevice->api = mal_api_dsound; pDevice->api = mal_api_dsound;
...@@ -1514,8 +1508,8 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t ...@@ -1514,8 +1508,8 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
return MAL_FORMAT_NOT_SUPPORTED; return MAL_FORMAT_NOT_SUPPORTED;
} }
if (fragmentCount > MAL_MAX_FRAGMENTS_DSOUND) { if (periods > MAL_MAX_PERIODS_DSOUND) {
fragmentCount = MAL_MAX_FRAGMENTS_DSOUND; periods = MAL_MAX_PERIODS_DSOUND;
} }
WAVEFORMATEXTENSIBLE wf; WAVEFORMATEXTENSIBLE wf;
...@@ -1531,7 +1525,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t ...@@ -1531,7 +1525,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
wf.dwChannelMask = (channels <= 2) ? 0 : ~(((DWORD)-1) << channels); wf.dwChannelMask = (channels <= 2) ? 0 : ~(((DWORD)-1) << channels);
wf.SubFormat = subformat; wf.SubFormat = subformat;
DWORD fragmentSizeInBytes = 0; DWORD bufferSizeInBytes = 0;
// Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :( // Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :(
if (type == mal_device_type_playback) { if (type == mal_device_type_playback) {
...@@ -1587,9 +1581,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t ...@@ -1587,9 +1581,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
pDevice->channels = pActualFormat->Format.nChannels; pDevice->channels = pActualFormat->Format.nChannels;
pDevice->sampleRate = pActualFormat->Format.nSamplesPerSec; pDevice->sampleRate = pActualFormat->Format.nSamplesPerSec;
pDevice->fragmentCount = fragmentCount; bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
pDevice->fragmentSizeInFrames = mal_next_power_of_2(fragmentSizeInFrames); // Keeping the fragment size a multiple of 2 just for consistency with ALSA.
fragmentSizeInBytes = mal_device_get_fragment_size_in_bytes(pDevice);
// Meaning of dwFlags (from MSDN): // Meaning of dwFlags (from MSDN):
// //
...@@ -1608,7 +1600,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t ...@@ -1608,7 +1600,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
memset(&descDS, 0, sizeof(DSBUFFERDESC)); memset(&descDS, 0, sizeof(DSBUFFERDESC));
descDS.dwSize = sizeof(DSBUFFERDESC); descDS.dwSize = sizeof(DSBUFFERDESC);
descDS.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; descDS.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
descDS.dwBufferBytes = fragmentSizeInBytes * pDevice->fragmentCount; descDS.dwBufferBytes = bufferSizeInBytes;
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND8)pDevice->dsound.pPlayback, &descDS, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackBuffer, NULL))) { if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND8)pDevice->dsound.pPlayback, &descDS, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackBuffer, NULL))) {
mal_device_uninit__dsound(pDevice); mal_device_uninit__dsound(pDevice);
...@@ -1633,14 +1625,13 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t ...@@ -1633,14 +1625,13 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
return mal_post_error(pDevice, "[DirectSound] DirectSoundCaptureCreate8() failed for capture device.", MAL_DSOUND_FAILED_TO_CREATE_DEVICE); return mal_post_error(pDevice, "[DirectSound] DirectSoundCaptureCreate8() failed for capture device.", MAL_DSOUND_FAILED_TO_CREATE_DEVICE);
} }
pDevice->fragmentSizeInFrames = mal_next_power_of_2(fragmentSizeInFrames); // Keeping the fragment size a multiple of 2 just for consistency with ALSA. bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
fragmentSizeInBytes = mal_device_get_fragment_size_in_bytes(pDevice);
DSCBUFFERDESC descDS; DSCBUFFERDESC descDS;
mal_zero_object(&descDS); mal_zero_object(&descDS);
descDS.dwSize = sizeof(descDS); descDS.dwSize = sizeof(descDS);
descDS.dwFlags = 0; descDS.dwFlags = 0;
descDS.dwBufferBytes = fragmentSizeInBytes * pDevice->fragmentCount; descDS.dwBufferBytes = bufferSizeInBytes;
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
LPDIRECTSOUNDCAPTUREBUFFER pDSCB_Temp; LPDIRECTSOUNDCAPTUREBUFFER pDSCB_Temp;
if (FAILED(IDirectSoundCapture_CreateCaptureBuffer((LPDIRECTSOUNDCAPTURE8)pDevice->dsound.pCapture, &descDS, &pDSCB_Temp, NULL))) { if (FAILED(IDirectSoundCapture_CreateCaptureBuffer((LPDIRECTSOUNDCAPTURE8)pDevice->dsound.pCapture, &descDS, &pDSCB_Temp, NULL))) {
...@@ -1663,11 +1654,12 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t ...@@ -1663,11 +1654,12 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
} }
// We need a notification for each fragment. The notification offset is slightly different depending on whether or not the // We need a notification for each period. The notification offset is slightly different depending on whether or not the
// device is a playback or capture device. For a playback device we want to be notified when a fragment just starts playing, // device is a playback or capture device. For a playback device we want to be notified when a period just starts playing,
// whereas for a capture device we want to be notified when a fragment has just _finished_ capturing. // whereas for a capture device we want to be notified when a period has just _finished_ capturing.
DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_FRAGMENTS_DSOUND]; // One notification event for each fragment. mal_uint32 periodSizeInBytes = pDevice->bufferSizeInFrames / pDevice->periods;
for (mal_uint32 i = 0; i < pDevice->fragmentCount; ++i) { DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_PERIODS_DSOUND]; // One notification event for each period.
for (mal_uint32 i = 0; i < pDevice->periods; ++i) {
pDevice->dsound.pNotifyEvents[i] = CreateEventA(NULL, FALSE, FALSE, NULL); pDevice->dsound.pNotifyEvents[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
if (pDevice->dsound.pNotifyEvents[i] == NULL) { if (pDevice->dsound.pNotifyEvents[i] == NULL) {
mal_device_uninit__dsound(pDevice); mal_device_uninit__dsound(pDevice);
...@@ -1677,16 +1669,16 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t ...@@ -1677,16 +1669,16 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
// The notification offset is in bytes. // The notification offset is in bytes.
DWORD dwOffset; DWORD dwOffset;
if (type == mal_device_type_playback) { if (type == mal_device_type_playback) {
dwOffset = i * fragmentSizeInBytes; dwOffset = i * periodSizeInBytes;
} else { } else {
dwOffset = ((i+1)*fragmentSizeInBytes) % (fragmentSizeInBytes*pDevice->fragmentCount); dwOffset = ((i+1)*periodSizeInBytes) % (bufferSizeInBytes);
} }
notifyPoints[i].dwOffset = dwOffset; notifyPoints[i].dwOffset = dwOffset;
notifyPoints[i].hEventNotify = pDevice->dsound.pNotifyEvents[i]; notifyPoints[i].hEventNotify = pDevice->dsound.pNotifyEvents[i];
} }
if (FAILED(IDirectSoundNotify_SetNotificationPositions((LPDIRECTSOUNDNOTIFY)pDevice->dsound.pNotify, pDevice->fragmentCount, notifyPoints))) { if (FAILED(IDirectSoundNotify_SetNotificationPositions((LPDIRECTSOUNDNOTIFY)pDevice->dsound.pNotify, pDevice->periods, notifyPoints))) {
mal_device_uninit__dsound(pDevice); mal_device_uninit__dsound(pDevice);
return mal_post_error(pDevice, "[DirectSound] IDirectSoundNotify_SetNotificationPositions() failed.", MAL_DSOUND_FAILED_TO_SET_NOTIFICATIONS); return mal_post_error(pDevice, "[DirectSound] IDirectSoundNotify_SetNotificationPositions() failed.", MAL_DSOUND_FAILED_TO_SET_NOTIFICATIONS);
} }
...@@ -1712,7 +1704,7 @@ static mal_result mal_device__start_backend__dsound(mal_device* pDevice) ...@@ -1712,7 +1704,7 @@ static mal_result mal_device__start_backend__dsound(mal_device* pDevice)
if (pDevice->type == mal_device_type_playback) { if (pDevice->type == mal_device_type_playback) {
// Before playing anything we need to grab an initial group of samples from the client. // Before playing anything we need to grab an initial group of samples from the client.
mal_uint32 framesToRead = pDevice->fragmentSizeInFrames; mal_uint32 framesToRead = pDevice->bufferSizeInFrames / pDevice->periods;
mal_uint32 desiredLockSize = framesToRead * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); mal_uint32 desiredLockSize = framesToRead * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
void* pLockPtr; void* pLockPtr;
...@@ -1806,7 +1798,7 @@ static mal_bool32 mal_device__get_available_frames__dsound(mal_device* pDevice) ...@@ -1806,7 +1798,7 @@ static mal_bool32 mal_device__get_available_frames__dsound(mal_device* pDevice)
// //
// For a recording device it's the other way around - the last processed frame is always _behind_ the current // For a recording device it's the other way around - the last processed frame is always _behind_ the current
// frame and the space between is the available space. // frame and the space between is the available space.
mal_uint32 totalFrameCount = pDevice->fragmentSizeInFrames*pDevice->fragmentCount; mal_uint32 totalFrameCount = pDevice->bufferSizeInFrames;
if (pDevice->type == mal_device_type_playback) { if (pDevice->type == mal_device_type_playback) {
mal_uint32 committedBeg = currentFrame; mal_uint32 committedBeg = currentFrame;
mal_uint32 committedEnd; mal_uint32 committedEnd;
...@@ -1849,26 +1841,19 @@ static mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice) ...@@ -1849,26 +1841,19 @@ static mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
unsigned int eventCount = pDevice->fragmentCount + 1; unsigned int eventCount = pDevice->periods + 1;
HANDLE eventHandles[MAL_MAX_FRAGMENTS_DSOUND + 1]; // +1 for the stop event. HANDLE eventHandles[MAL_MAX_PERIODS_DSOUND + 1]; // +1 for the stop event.
mal_copy_memory(eventHandles, pDevice->dsound.pNotifyEvents, sizeof(HANDLE) * pDevice->fragmentCount); mal_copy_memory(eventHandles, pDevice->dsound.pNotifyEvents, sizeof(HANDLE) * pDevice->periods);
eventHandles[eventCount-1] = pDevice->dsound.hStopEvent; eventHandles[eventCount-1] = pDevice->dsound.hStopEvent;
while (!pDevice->dsound.breakFromMainLoop) { while (!pDevice->dsound.breakFromMainLoop) {
mal_uint32 framesAvailable = mal_device__get_available_frames__dsound(pDevice); mal_uint32 framesAvailable = mal_device__get_available_frames__dsound(pDevice);
#if 0
// Never return more frames that will fit in a fragment.
if (framesAvailable >= pDevice->fragmentSizeInFrames) {
return pDevice->fragmentSizeInFrames;
}
#else
if (framesAvailable > 0) { if (framesAvailable > 0) {
return framesAvailable; return framesAvailable;
} }
#endif
// If we get here it means we weren't able to find a full fragment. We'll just wait here for a bit. // If we get here it means we weren't able to find any frames. We'll just wait here for a bit.
const DWORD timeoutInMilliseconds = 8; // <-- This affects latency. Should this be a property? const DWORD timeoutInMilliseconds = 8; // <-- This affects latency. Should this be a property? Tie this to the period count?
DWORD rc = WaitForMultipleObjects(eventCount, eventHandles, FALSE, timeoutInMilliseconds); DWORD rc = WaitForMultipleObjects(eventCount, eventHandles, FALSE, timeoutInMilliseconds);
if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount) { if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount) {
return 0; return 0;
...@@ -1916,7 +1901,7 @@ static mal_result mal_device__main_loop__dsound(mal_device* pDevice) ...@@ -1916,7 +1901,7 @@ static mal_result mal_device__main_loop__dsound(mal_device* pDevice)
mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels;
mal_device__read_frames_from_client(pDevice, frameCount, pLockPtr); mal_device__read_frames_from_client(pDevice, frameCount, pLockPtr);
pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % (pDevice->fragmentSizeInFrames*pDevice->fragmentCount); pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % pDevice->bufferSizeInFrames;
IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2);
} else { } else {
...@@ -1930,7 +1915,7 @@ static mal_result mal_device__main_loop__dsound(mal_device* pDevice) ...@@ -1930,7 +1915,7 @@ static mal_result mal_device__main_loop__dsound(mal_device* pDevice)
mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels;
mal_device__send_frames_to_client(pDevice, frameCount, pLockPtr); mal_device__send_frames_to_client(pDevice, frameCount, pLockPtr);
pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % (pDevice->fragmentSizeInFrames*pDevice->fragmentCount); pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % pDevice->bufferSizeInFrames;
IDirectSoundCaptureBuffer_Unlock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); IDirectSoundCaptureBuffer_Unlock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2);
} }
...@@ -1952,7 +1937,7 @@ static mal_uint32 mal_device_get_available_rewind_amount__dsound(mal_device* pDe ...@@ -1952,7 +1937,7 @@ static mal_uint32 mal_device_get_available_rewind_amount__dsound(mal_device* pDe
mal_uint32 committedBeg = currentFrame; mal_uint32 committedBeg = currentFrame;
mal_uint32 committedEnd = pDevice->dsound.lastProcessedFrame; mal_uint32 committedEnd = pDevice->dsound.lastProcessedFrame;
if (committedEnd <= committedBeg) { if (committedEnd <= committedBeg) {
committedEnd += pDevice->fragmentSizeInFrames*pDevice->fragmentCount; // Wrap around. committedEnd += pDevice->bufferSizeInFrames; // Wrap around.
} }
mal_uint32 padding = (pDevice->sampleRate/1000) * 1; // <-- This is used to prevent the rewind position getting too close to the playback position. mal_uint32 padding = (pDevice->sampleRate/1000) * 1; // <-- This is used to prevent the rewind position getting too close to the playback position.
...@@ -1975,9 +1960,7 @@ static mal_uint32 mal_device_rewind__dsound(mal_device* pDevice, mal_uint32 fram ...@@ -1975,9 +1960,7 @@ static mal_uint32 mal_device_rewind__dsound(mal_device* pDevice, mal_uint32 fram
framesToRewind = maxRewind; framesToRewind = maxRewind;
} }
mal_uint32 desiredPosition = (pDevice->dsound.lastProcessedFrame + pDevice->bufferSizeInFrames - framesToRewind) % pDevice->bufferSizeInFrames; // Wrap around.
mal_uint32 totalFrameCount = pDevice->fragmentSizeInFrames*pDevice->fragmentCount;
mal_uint32 desiredPosition = (pDevice->dsound.lastProcessedFrame + totalFrameCount - framesToRewind) % totalFrameCount; // Wrap around.
mal_atomic_exchange_32(&pDevice->dsound.rewindTarget, desiredPosition); mal_atomic_exchange_32(&pDevice->dsound.rewindTarget, desiredPosition);
return framesToRewind; return framesToRewind;
...@@ -2017,24 +2000,19 @@ static const char* mal_find_char(const char* str, char c, int* index) ...@@ -2017,24 +2000,19 @@ static const char* mal_find_char(const char* str, char c, int* index)
} }
// Waits for a number of frames to become available for either capture or playback. The return // Waits for a number of frames to become available for either capture or playback. The return
// value is the number of frames available. If this is less than the fragment size it means the // value is the number of frames available.
// main loop has been terminated from another thread. The return value will be clamped to the //
// fragment size if the main loop is still running, but could be larger if it returns due to the // This will return early if the main loop is broken with mal_device__break_main_loop().
// main loop being terminated.
//
// This will return early if the main loop is broken with mal_device__break_main_loop(), in
// which case it is possible for the returned number of frames will be greater than the size of
// a fragment (but smaller than the total buffer size).
static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice) static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
snd_pcm_sframes_t framesAvailable = snd_pcm_avail((snd_pcm_t*)pDevice->alsa.pPCM); snd_pcm_sframes_t framesAvailable = snd_pcm_avail((snd_pcm_t*)pDevice->alsa.pPCM);
if (framesAvailable >= pDevice->fragmentSizeInFrames) { if (framesAvailable > 0) {
return pDevice->fragmentSizeInFrames; return framesAvailable;
} }
if (framesAvailable < 0) { if (framesAvailable < 0) {
if (framesAvailable == -EPIPE) { if (framesAvailable == -EPIPE) {
if (snd_pcm_recover((snd_pcm_t*)pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) { if (snd_pcm_recover((snd_pcm_t*)pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) {
...@@ -2055,14 +2033,12 @@ static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice) ...@@ -2055,14 +2033,12 @@ static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice)
} }
} }
// We'll get here if the loop was terminated. Just return whatever's available, making sure it's never // We'll get here if the loop was terminated. Just return whatever's available.
// more than the size of a fragment.
snd_pcm_sframes_t framesAvailable = snd_pcm_avail((snd_pcm_t*)pDevice->alsa.pPCM); snd_pcm_sframes_t framesAvailable = snd_pcm_avail((snd_pcm_t*)pDevice->alsa.pPCM);
if (framesAvailable < 0) { if (framesAvailable < 0) {
return 0; return 0;
} }
// There's a small chance we'll have more frames available than the size of a fragment.
return framesAvailable; return framesAvailable;
} }
...@@ -2114,7 +2090,7 @@ static mal_bool32 mal_device_write__alsa(mal_device* pDevice) ...@@ -2114,7 +2090,7 @@ static mal_bool32 mal_device_write__alsa(mal_device* pDevice)
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice); mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice);
if (framesAvailable == 0) { if (framesAvailable == 0) {
return MAL_FALSE; continue;
} }
// Don't bother asking the client for more audio data if we're just stopping the device anyway. // Don't bother asking the client for more audio data if we're just stopping the device anyway.
...@@ -2197,7 +2173,7 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -2197,7 +2173,7 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice)
while (!pDevice->alsa.breakFromMainLoop) { while (!pDevice->alsa.breakFromMainLoop) {
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice); mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice);
if (framesAvailable == 0) { if (framesAvailable == 0) {
return MAL_FALSE; continue;
} }
framesRead = snd_pcm_readi((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); framesRead = snd_pcm_readi((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable);
...@@ -2210,7 +2186,7 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice) ...@@ -2210,7 +2186,7 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice)
return MAL_FALSE; return MAL_FALSE;
} }
framesRead = snd_pcm_readi((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames); framesRead = snd_pcm_readi((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable);
if (framesRead < 0) { if (framesRead < 0) {
return MAL_FALSE; return MAL_FALSE;
} }
...@@ -2381,7 +2357,7 @@ static void mal_device_uninit__alsa(mal_device* pDevice) ...@@ -2381,7 +2357,7 @@ static void mal_device_uninit__alsa(mal_device* pDevice)
} }
} }
static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount) static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, mal_uint32 periods)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
pDevice->api = mal_api_alsa; pDevice->api = mal_api_alsa;
...@@ -2428,7 +2404,8 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ ...@@ -2428,7 +2404,8 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE); return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE);
} }
} }
snd_pcm_hw_params_t* pHWParams = NULL; snd_pcm_hw_params_t* pHWParams = NULL;
snd_pcm_hw_params_alloca(&pHWParams); snd_pcm_hw_params_alloca(&pHWParams);
...@@ -2463,8 +2440,8 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ ...@@ -2463,8 +2440,8 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
// Buffer Size // Buffer Size
snd_pcm_uframes_t bufferSize = fragmentSizeInFrames * pDevice->fragmentCount; snd_pcm_uframes_t actualBufferSize = bufferSizeInFrames;
if (snd_pcm_hw_params_set_buffer_size_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &bufferSize) < 0) { if (snd_pcm_hw_params_set_buffer_size_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &actualBufferSize) < 0) {
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
...@@ -2472,13 +2449,15 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ ...@@ -2472,13 +2449,15 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
// Periods. // Periods.
int dir = 0; int dir = 0;
if (snd_pcm_hw_params_set_periods_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &fragmentCount, &dir) < 0) { if (snd_pcm_hw_params_set_periods_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &periods, &dir) < 0) {
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set fragment count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
pDevice->fragmentCount = fragmentCount;
pDevice->fragmentSizeInFrames = bufferSize / pDevice->fragmentCount;
pDevice->bufferSizeInFrames = actualBufferSize;
pDevice->periods = periods;
// MMAP Mode // MMAP Mode
// //
...@@ -2515,13 +2494,13 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ ...@@ -2515,13 +2494,13 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
return mal_post_error(pDevice, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); return mal_post_error(pDevice, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS);
} }
if (snd_pcm_sw_params_set_avail_min((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, mal_prev_power_of_2(pDevice->fragmentSizeInFrames)) != 0) { if (snd_pcm_sw_params_set_avail_min((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, (pDevice->sampleRate/1000) * 1) != 0) {
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set fragment size. snd_pcm_sw_params_set_avail_min() failed.", MAL_FORMAT_NOT_SUPPORTED); return mal_post_error(pDevice, "[ALSA] snd_pcm_sw_params_set_avail_min() failed.", MAL_FORMAT_NOT_SUPPORTED);
} }
if (type == mal_device_type_playback) { if (type == mal_device_type_playback) {
if (snd_pcm_sw_params_set_start_threshold((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, mal_prev_power_of_2(pDevice->fragmentSizeInFrames)) != 0) { if (snd_pcm_sw_params_set_start_threshold((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, (pDevice->sampleRate/1000) * 1) != 0) { //mal_prev_power_of_2(pDevice->bufferSizeInFrames/pDevice->periods)
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); return mal_post_error(pDevice, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS);
} }
...@@ -2537,15 +2516,13 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ ...@@ -2537,15 +2516,13 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
// If we're _not_ using mmap we need to use an intermediary buffer. // If we're _not_ using mmap we need to use an intermediary buffer.
if (!pDevice->alsa.isUsingMMap) { if (!pDevice->alsa.isUsingMMap) {
pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->fragmentSizeInFrames*pDevice->fragmentCount * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format)); pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format));
if (pDevice->alsa.pIntermediaryBuffer == NULL) { if (pDevice->alsa.pIntermediaryBuffer == NULL) {
mal_device_uninit__alsa(pDevice); mal_device_uninit__alsa(pDevice);
return mal_post_error(pDevice, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", MAL_OUT_OF_MEMORY); return mal_post_error(pDevice, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", MAL_OUT_OF_MEMORY);
} }
} }
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -2557,7 +2534,7 @@ static mal_result mal_device__start_backend__alsa(mal_device* pDevice) ...@@ -2557,7 +2534,7 @@ static mal_result mal_device__start_backend__alsa(mal_device* pDevice)
// Prepare the device first... // Prepare the device first...
snd_pcm_prepare((snd_pcm_t*)pDevice->alsa.pPCM); snd_pcm_prepare((snd_pcm_t*)pDevice->alsa.pPCM);
// ... and then grab an initial fragment from the client. After this is done, the device should // ... and then grab an initial chunk from the client. After this is done, the device should
// automatically start playing, since that's how we configured the software parameters. // automatically start playing, since that's how we configured the software parameters.
if (pDevice->type == mal_device_type_playback) { if (pDevice->type == mal_device_type_playback) {
mal_device_write__alsa(pDevice); mal_device_write__alsa(pDevice);
...@@ -2581,7 +2558,7 @@ static mal_result mal_device__break_main_loop__alsa(mal_device* pDevice) ...@@ -2581,7 +2558,7 @@ static mal_result mal_device__break_main_loop__alsa(mal_device* pDevice)
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
// Fallback. We just set a variable to tell the worker thread to terminate after handling the // Fallback. We just set a variable to tell the worker thread to terminate after handling the
// next fragment. This is a slow way of handling this. // next bunch of frames. This is a slow way of handling this.
pDevice->alsa.breakFromMainLoop = MAL_TRUE; pDevice->alsa.breakFromMainLoop = MAL_TRUE;
return MAL_SUCCESS; return MAL_SUCCESS;
} }
...@@ -2608,14 +2585,44 @@ static mal_result mal_device__main_loop__alsa(mal_device* pDevice) ...@@ -2608,14 +2585,44 @@ static mal_result mal_device__main_loop__alsa(mal_device* pDevice)
static mal_uint32 mal_device_get_available_rewind_amount__alsa(mal_device* pDevice) static mal_uint32 mal_device_get_available_rewind_amount__alsa(mal_device* pDevice)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
return 0; // Not supporting rewinding with ALSA for the moment.
// Haven't figured out reliable rewinding with ALSA yet...
#if 0
mal_uint32 padding = (pDevice->sampleRate/1000) * 1; // <-- This is used to prevent the rewind position getting too close to the playback position.
snd_pcm_sframes_t result = snd_pcm_rewindable((snd_pcm_t*)pDevice->alsa.pPCM);
if (result < padding) {
return 0;
}
return (mal_uint32)result - padding;
#else
return 0;
#endif
} }
static mal_uint32 mal_device_rewind__alsa(mal_device* pDevice, mal_uint32 framesToRewind) static mal_uint32 mal_device_rewind__alsa(mal_device* pDevice, mal_uint32 framesToRewind)
{ {
mal_assert(pDevice != NULL); mal_assert(pDevice != NULL);
mal_assert(frames > 0); mal_assert(framesToRewind > 0);
// Haven't figured out reliable rewinding with ALSA yet...
#if 0
// Clamp the the maximum allowable rewind amount.
mal_uint32 maxRewind = mal_device_get_available_rewind_amount__alsa(pDevice);
if (framesToRewind > maxRewind) {
framesToRewind = maxRewind;
}
snd_pcm_sframes_t result = snd_pcm_rewind((snd_pcm_t*)pDevice->alsa.pPCM, (snd_pcm_uframes_t)framesToRewind);
if (result < 0) {
return 0;
}
return (mal_uint32)result;
#else
return 0; return 0;
#endif
} }
#endif #endif
...@@ -2797,7 +2804,7 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d ...@@ -2797,7 +2804,7 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d
return result; return result;
} }
mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount, mal_log_proc onLog) mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, mal_uint32 periods, mal_log_proc onLog)
{ {
if (pDevice == NULL) return mal_post_error(pDevice, "mal_device_init() called with invalid arguments.", MAL_INVALID_ARGS); if (pDevice == NULL) return mal_post_error(pDevice, "mal_device_init() called with invalid arguments.", MAL_INVALID_ARGS);
mal_zero_object(pDevice); mal_zero_object(pDevice);
...@@ -2809,14 +2816,14 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device ...@@ -2809,14 +2816,14 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device
} }
} }
if (channels == 0 || sampleRate == 0 || fragmentSizeInFrames == 0 || fragmentCount == 0) return mal_post_error(pDevice, "mal_device_init() called with invalid arguments.", MAL_INVALID_ARGS); if (channels == 0 || sampleRate == 0 || bufferSizeInFrames == 0 || periods == 0) return mal_post_error(pDevice, "mal_device_init() called with invalid arguments.", MAL_INVALID_ARGS);
pDevice->type = type; pDevice->type = type;
pDevice->format = format; pDevice->format = format;
pDevice->channels = channels; pDevice->channels = channels;
pDevice->sampleRate = sampleRate; pDevice->sampleRate = sampleRate;
pDevice->fragmentSizeInFrames = fragmentSizeInFrames; pDevice->bufferSizeInFrames = bufferSizeInFrames;
pDevice->fragmentCount = fragmentCount; pDevice->periods = periods;
if (!mal_mutex_create(&pDevice->lock)) { if (!mal_mutex_create(&pDevice->lock)) {
return mal_post_error(pDevice, "Failed to create mutex.", MAL_FAILED_TO_CREATE_MUTEX); return mal_post_error(pDevice, "Failed to create mutex.", MAL_FAILED_TO_CREATE_MUTEX);
...@@ -2847,17 +2854,17 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device ...@@ -2847,17 +2854,17 @@ mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device
mal_result result = MAL_NO_BACKEND; mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND #ifdef MAL_ENABLE_DSOUND
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
result = mal_device_init__dsound(pDevice, type, pDeviceID, format, channels, sampleRate, fragmentSizeInFrames, fragmentCount); result = mal_device_init__dsound(pDevice, type, pDeviceID, format, channels, sampleRate, bufferSizeInFrames, periods);
} }
#endif #endif
#ifdef MAL_ENABLE_ALSA #ifdef MAL_ENABLE_ALSA
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
result = mal_device_init__alsa(pDevice, type, pDeviceID, format, channels, sampleRate, fragmentSizeInFrames, fragmentCount); result = mal_device_init__alsa(pDevice, type, pDeviceID, format, channels, sampleRate, bufferSizeInFrames, periods);
} }
#endif #endif
#ifdef MAL_ENABLE_NULL #ifdef MAL_ENABLE_NULL
if (result != MAL_SUCCESS) { if (result != MAL_SUCCESS) {
result = mal_device_init__null(pDevice, type, pDeviceID, format, channels, sampleRate, fragmentSizeInFrames, fragmentCount); result = mal_device_init__null(pDevice, type, pDeviceID, format, channels, sampleRate, bufferSizeInFrames, periods);
} }
#endif #endif
...@@ -3096,10 +3103,10 @@ mal_uint32 mal_device_rewind(mal_device* pDevice, mal_uint32 framesToRewind) ...@@ -3096,10 +3103,10 @@ mal_uint32 mal_device_rewind(mal_device* pDevice, mal_uint32 framesToRewind)
return 0; return 0;
} }
mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice) mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice)
{ {
if (pDevice == NULL) return 0; if (pDevice == NULL) return 0;
return pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); return pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
} }
mal_uint32 mal_get_sample_size_in_bytes(mal_format format) mal_uint32 mal_get_sample_size_in_bytes(mal_format format)
...@@ -3128,10 +3135,8 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format) ...@@ -3128,10 +3135,8 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format)
// TODO // TODO
// ==== // ====
// - Support rewinding. This will enable applications to employ better anti-latency. // - DirectSound: Remove notification events.
// - Remove the notion of fragments. Replace it with a buffer size. // - onSend and onRecv usage isn't thread-safe.
// - Consider having properties passed to mal_device_init() via a structure and have
// some properties support defaults when set to 0.
// //
// //
// ALSA // ALSA
......
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