mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-21 15:56:58 +02:00
Remove the notion of fragments and use a simplified buffer size and periods concept instead.
This commit is contained in:
@@ -112,7 +112,7 @@ typedef void* mal_ptr;
|
||||
#endif
|
||||
|
||||
#ifdef MAL_ENABLE_DSOUND
|
||||
#define MAL_MAX_FRAGMENTS_DSOUND 16
|
||||
#define MAL_MAX_PERIODS_DSOUND 16
|
||||
#endif
|
||||
|
||||
typedef int mal_result;
|
||||
@@ -205,8 +205,8 @@ struct mal_device
|
||||
mal_format format;
|
||||
mal_uint32 channels;
|
||||
mal_uint32 sampleRate;
|
||||
mal_uint32 fragmentSizeInFrames;
|
||||
mal_uint32 fragmentCount;
|
||||
mal_uint32 bufferSizeInFrames;
|
||||
mal_uint32 periods;
|
||||
mal_uint32 state;
|
||||
mal_recv_proc onRecv;
|
||||
mal_send_proc onSend;
|
||||
@@ -231,7 +231,7 @@ struct mal_device
|
||||
/*LPDIRECTSOUNDCAPTURE8*/ mal_ptr pCapture;
|
||||
/*LPDIRECTSOUNDCAPTUREBUFFER8*/ mal_ptr pCaptureBuffer;
|
||||
/*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;
|
||||
mal_uint32 lastProcessedFrame; // This is circular.
|
||||
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
|
||||
// Efficiency: LOW
|
||||
// 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.
|
||||
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.
|
||||
//
|
||||
@@ -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.
|
||||
//
|
||||
// The fragment size specified at initialization time controls the sample count.
|
||||
//
|
||||
// Thread Safety: SAFE
|
||||
// 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);
|
||||
//
|
||||
// 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
|
||||
// any leftover samples with silence. The fragment size specified at initialization time controls the
|
||||
// number of samples the device will request.
|
||||
// any leftover samples with silence.
|
||||
//
|
||||
// Thread Safety: SAFE
|
||||
// This API is implemented as a simple atomic assignment.
|
||||
@@ -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.
|
||||
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
|
||||
// This is calculated from constant values which are set at initialization time and never change.
|
||||
//
|
||||
// Efficiency: HIGH
|
||||
// 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.
|
||||
//
|
||||
@@ -743,7 +740,7 @@ void mal_timer_init(mal_timer* pTimer)
|
||||
struct timespec 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)
|
||||
@@ -751,7 +748,7 @@ double mal_timer_get_time_in_seconds(mal_timer* pTimer)
|
||||
struct timespec 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;
|
||||
|
||||
return (newTimeCounter - oldTimeCounter) / 1000000000.0;
|
||||
@@ -1151,17 +1148,19 @@ static void mal_device_uninit__null(mal_device* pDevice)
|
||||
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);
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
*pCurrentPos = currentFrameAbs % (pDevice->fragmentSizeInFrames * pDevice->fragmentCount);
|
||||
*pCurrentPos = currentFrameAbs % pDevice->bufferSizeInFrames;
|
||||
return MAL_TRUE;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// 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) {
|
||||
mal_uint32 committedBeg = currentFrame;
|
||||
mal_uint32 committedEnd = pDevice->null_device.lastProcessedFrame;
|
||||
@@ -1250,13 +1249,8 @@ static mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice)
|
||||
|
||||
while (!pDevice->null_device.breakFromMainLoop) {
|
||||
mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice);
|
||||
if (framesAvailable == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Never return more frames that will fit in a fragment.
|
||||
if (framesAvailable >= pDevice->fragmentSizeInFrames) {
|
||||
return pDevice->fragmentSizeInFrames;
|
||||
if (framesAvailable > 0) {
|
||||
return framesAvailable;
|
||||
}
|
||||
|
||||
mal_sleep(16);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1444,7 +1438,7 @@ static void mal_device_uninit__dsound(mal_device* pDevice)
|
||||
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]) {
|
||||
CloseHandle(pDevice->dsound.pNotifyEvents[i]);
|
||||
}
|
||||
@@ -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);
|
||||
pDevice->api = mal_api_dsound;
|
||||
@@ -1514,8 +1508,8 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
|
||||
return MAL_FORMAT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (fragmentCount > MAL_MAX_FRAGMENTS_DSOUND) {
|
||||
fragmentCount = MAL_MAX_FRAGMENTS_DSOUND;
|
||||
if (periods > MAL_MAX_PERIODS_DSOUND) {
|
||||
periods = MAL_MAX_PERIODS_DSOUND;
|
||||
}
|
||||
|
||||
WAVEFORMATEXTENSIBLE wf;
|
||||
@@ -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.SubFormat = subformat;
|
||||
|
||||
DWORD fragmentSizeInBytes = 0;
|
||||
DWORD bufferSizeInBytes = 0;
|
||||
|
||||
// Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :(
|
||||
if (type == mal_device_type_playback) {
|
||||
@@ -1587,9 +1581,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
|
||||
|
||||
pDevice->channels = pActualFormat->Format.nChannels;
|
||||
pDevice->sampleRate = pActualFormat->Format.nSamplesPerSec;
|
||||
pDevice->fragmentCount = fragmentCount;
|
||||
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);
|
||||
bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
|
||||
|
||||
// Meaning of dwFlags (from MSDN):
|
||||
//
|
||||
@@ -1608,7 +1600,7 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
|
||||
memset(&descDS, 0, sizeof(DSBUFFERDESC));
|
||||
descDS.dwSize = sizeof(DSBUFFERDESC);
|
||||
descDS.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
|
||||
descDS.dwBufferBytes = fragmentSizeInBytes * pDevice->fragmentCount;
|
||||
descDS.dwBufferBytes = bufferSizeInBytes;
|
||||
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
|
||||
if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND8)pDevice->dsound.pPlayback, &descDS, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackBuffer, NULL))) {
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
|
||||
|
||||
DSCBUFFERDESC descDS;
|
||||
mal_zero_object(&descDS);
|
||||
descDS.dwSize = sizeof(descDS);
|
||||
descDS.dwFlags = 0;
|
||||
descDS.dwBufferBytes = fragmentSizeInBytes * pDevice->fragmentCount;
|
||||
descDS.dwBufferBytes = bufferSizeInBytes;
|
||||
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
|
||||
LPDIRECTSOUNDCAPTUREBUFFER pDSCB_Temp;
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
// We need a notification for each fragment. 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,
|
||||
// whereas for a capture device we want to be notified when a fragment has just _finished_ capturing.
|
||||
DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_FRAGMENTS_DSOUND]; // One notification event for each fragment.
|
||||
for (mal_uint32 i = 0; i < pDevice->fragmentCount; ++i) {
|
||||
// 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 period just starts playing,
|
||||
// whereas for a capture device we want to be notified when a period has just _finished_ capturing.
|
||||
mal_uint32 periodSizeInBytes = pDevice->bufferSizeInFrames / pDevice->periods;
|
||||
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);
|
||||
if (pDevice->dsound.pNotifyEvents[i] == NULL) {
|
||||
mal_device_uninit__dsound(pDevice);
|
||||
@@ -1677,16 +1669,16 @@ static mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type t
|
||||
// The notification offset is in bytes.
|
||||
DWORD dwOffset;
|
||||
if (type == mal_device_type_playback) {
|
||||
dwOffset = i * fragmentSizeInBytes;
|
||||
dwOffset = i * periodSizeInBytes;
|
||||
} else {
|
||||
dwOffset = ((i+1)*fragmentSizeInBytes) % (fragmentSizeInBytes*pDevice->fragmentCount);
|
||||
dwOffset = ((i+1)*periodSizeInBytes) % (bufferSizeInBytes);
|
||||
}
|
||||
|
||||
notifyPoints[i].dwOffset = dwOffset;
|
||||
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);
|
||||
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)
|
||||
|
||||
if (pDevice->type == mal_device_type_playback) {
|
||||
// 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);
|
||||
|
||||
void* pLockPtr;
|
||||
@@ -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
|
||||
// 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) {
|
||||
mal_uint32 committedBeg = currentFrame;
|
||||
mal_uint32 committedEnd;
|
||||
@@ -1849,26 +1841,19 @@ static mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice)
|
||||
{
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
unsigned int eventCount = pDevice->fragmentCount + 1;
|
||||
HANDLE eventHandles[MAL_MAX_FRAGMENTS_DSOUND + 1]; // +1 for the stop event.
|
||||
mal_copy_memory(eventHandles, pDevice->dsound.pNotifyEvents, sizeof(HANDLE) * pDevice->fragmentCount);
|
||||
unsigned int eventCount = pDevice->periods + 1;
|
||||
HANDLE eventHandles[MAL_MAX_PERIODS_DSOUND + 1]; // +1 for the stop event.
|
||||
mal_copy_memory(eventHandles, pDevice->dsound.pNotifyEvents, sizeof(HANDLE) * pDevice->periods);
|
||||
eventHandles[eventCount-1] = pDevice->dsound.hStopEvent;
|
||||
|
||||
while (!pDevice->dsound.breakFromMainLoop) {
|
||||
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) {
|
||||
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.
|
||||
const DWORD timeoutInMilliseconds = 8; // <-- This affects latency. Should this be a property?
|
||||
// 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? Tie this to the period count?
|
||||
DWORD rc = WaitForMultipleObjects(eventCount, eventHandles, FALSE, timeoutInMilliseconds);
|
||||
if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount) {
|
||||
return 0;
|
||||
@@ -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_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);
|
||||
} else {
|
||||
@@ -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_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);
|
||||
}
|
||||
@@ -1952,7 +1937,7 @@ static mal_uint32 mal_device_get_available_rewind_amount__dsound(mal_device* pDe
|
||||
mal_uint32 committedBeg = currentFrame;
|
||||
mal_uint32 committedEnd = pDevice->dsound.lastProcessedFrame;
|
||||
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.
|
||||
@@ -1975,9 +1960,7 @@ static mal_uint32 mal_device_rewind__dsound(mal_device* pDevice, mal_uint32 fram
|
||||
framesToRewind = maxRewind;
|
||||
}
|
||||
|
||||
|
||||
mal_uint32 totalFrameCount = pDevice->fragmentSizeInFrames*pDevice->fragmentCount;
|
||||
mal_uint32 desiredPosition = (pDevice->dsound.lastProcessedFrame + totalFrameCount - framesToRewind) % totalFrameCount; // Wrap around.
|
||||
mal_uint32 desiredPosition = (pDevice->dsound.lastProcessedFrame + pDevice->bufferSizeInFrames - framesToRewind) % pDevice->bufferSizeInFrames; // Wrap around.
|
||||
mal_atomic_exchange_32(&pDevice->dsound.rewindTarget, desiredPosition);
|
||||
|
||||
return framesToRewind;
|
||||
@@ -2017,23 +2000,18 @@ 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
|
||||
// value is the number of frames available. If this is less than the fragment size it means the
|
||||
// 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
|
||||
// main loop being terminated.
|
||||
// value is the number of frames available.
|
||||
//
|
||||
// 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).
|
||||
// This will return early if the main loop is broken with mal_device__break_main_loop().
|
||||
static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice)
|
||||
{
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
while (!pDevice->alsa.breakFromMainLoop) {
|
||||
snd_pcm_sframes_t framesAvailable = snd_pcm_avail((snd_pcm_t*)pDevice->alsa.pPCM);
|
||||
if (framesAvailable >= pDevice->fragmentSizeInFrames) {
|
||||
return pDevice->fragmentSizeInFrames;
|
||||
}
|
||||
if (framesAvailable > 0) {
|
||||
return framesAvailable;
|
||||
}
|
||||
|
||||
if (framesAvailable < 0) {
|
||||
if (framesAvailable == -EPIPE) {
|
||||
@@ -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
|
||||
// more than the size of a fragment.
|
||||
// We'll get here if the loop was terminated. Just return whatever's available.
|
||||
snd_pcm_sframes_t framesAvailable = snd_pcm_avail((snd_pcm_t*)pDevice->alsa.pPCM);
|
||||
if (framesAvailable < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// There's a small chance we'll have more frames available than the size of a fragment.
|
||||
return framesAvailable;
|
||||
}
|
||||
|
||||
@@ -2114,7 +2090,7 @@ static mal_bool32 mal_device_write__alsa(mal_device* pDevice)
|
||||
while (!pDevice->alsa.breakFromMainLoop) {
|
||||
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice);
|
||||
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.
|
||||
@@ -2197,7 +2173,7 @@ static mal_bool32 mal_device_read__alsa(mal_device* pDevice)
|
||||
while (!pDevice->alsa.breakFromMainLoop) {
|
||||
mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice);
|
||||
if (framesAvailable == 0) {
|
||||
return MAL_FALSE;
|
||||
continue;
|
||||
}
|
||||
|
||||
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)
|
||||
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) {
|
||||
return MAL_FALSE;
|
||||
}
|
||||
@@ -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);
|
||||
pDevice->api = mal_api_alsa;
|
||||
@@ -2429,6 +2405,7 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
snd_pcm_hw_params_t* pHWParams = NULL;
|
||||
snd_pcm_hw_params_alloca(&pHWParams);
|
||||
|
||||
@@ -2463,8 +2440,8 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
|
||||
|
||||
|
||||
// Buffer Size
|
||||
snd_pcm_uframes_t bufferSize = fragmentSizeInFrames * pDevice->fragmentCount;
|
||||
if (snd_pcm_hw_params_set_buffer_size_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &bufferSize) < 0) {
|
||||
snd_pcm_uframes_t actualBufferSize = bufferSizeInFrames;
|
||||
if (snd_pcm_hw_params_set_buffer_size_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &actualBufferSize) < 0) {
|
||||
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);
|
||||
}
|
||||
@@ -2472,12 +2449,14 @@ static mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type typ
|
||||
|
||||
// Periods.
|
||||
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);
|
||||
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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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);
|
||||
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 (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);
|
||||
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
|
||||
|
||||
// If we're _not_ using mmap we need to use an intermediary buffer.
|
||||
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) {
|
||||
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_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -2557,7 +2534,7 @@ static mal_result mal_device__start_backend__alsa(mal_device* pDevice)
|
||||
// Prepare the device first...
|
||||
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.
|
||||
if (pDevice->type == mal_device_type_playback) {
|
||||
mal_device_write__alsa(pDevice);
|
||||
@@ -2581,7 +2558,7 @@ static mal_result mal_device__break_main_loop__alsa(mal_device* pDevice)
|
||||
mal_assert(pDevice != NULL);
|
||||
|
||||
// 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;
|
||||
return MAL_SUCCESS;
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2797,7 +2804,7 @@ mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_d
|
||||
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);
|
||||
mal_zero_object(pDevice);
|
||||
@@ -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->format = format;
|
||||
pDevice->channels = channels;
|
||||
pDevice->sampleRate = sampleRate;
|
||||
pDevice->fragmentSizeInFrames = fragmentSizeInFrames;
|
||||
pDevice->fragmentCount = fragmentCount;
|
||||
pDevice->bufferSizeInFrames = bufferSizeInFrames;
|
||||
pDevice->periods = periods;
|
||||
|
||||
if (!mal_mutex_create(&pDevice->lock)) {
|
||||
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
|
||||
mal_result result = MAL_NO_BACKEND;
|
||||
#ifdef MAL_ENABLE_DSOUND
|
||||
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
|
||||
#ifdef MAL_ENABLE_ALSA
|
||||
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
|
||||
#ifdef MAL_ENABLE_NULL
|
||||
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
|
||||
|
||||
@@ -3096,10 +3103,10 @@ mal_uint32 mal_device_rewind(mal_device* pDevice, mal_uint32 framesToRewind)
|
||||
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;
|
||||
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)
|
||||
@@ -3128,10 +3135,8 @@ mal_uint32 mal_get_sample_size_in_bytes(mal_format format)
|
||||
|
||||
// TODO
|
||||
// ====
|
||||
// - Support rewinding. This will enable applications to employ better anti-latency.
|
||||
// - Remove the notion of fragments. Replace it with a buffer size.
|
||||
// - Consider having properties passed to mal_device_init() via a structure and have
|
||||
// some properties support defaults when set to 0.
|
||||
// - DirectSound: Remove notification events.
|
||||
// - onSend and onRecv usage isn't thread-safe.
|
||||
//
|
||||
//
|
||||
// ALSA
|
||||
|
||||
Reference in New Issue
Block a user